[
  {
    "path": ".codecov.yml",
    "content": "comment: false\n\ncoverage:\n  status:\n    project:\n      default:\n        threshold: 1%\n    patch:\n      default:\n        threshold: 100%\n\ncodecov:\n  token: 4c73bd46-5ea2-4c6f-8dd1-8422f5490455\n"
  },
  {
    "path": ".coveragerc",
    "content": "[run]\nplugins = Cython.Coverage\nbranch = True\n\n[report]\nomit=*.yml\n     */api.py\n\t */__init__.py\n\t */__config__.py\n\t */tests/*\n\t yt/analysis_modules/*\n\t yt/mods.py\n\t yt/utilities/fits_image.py\n\t yt/utilities/lodgeit.py\n\nignore_errors = True\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# transition to isort\n7edfcee093cca277307aabdb180e0ffc69768291\n81418e459f16c48d6b7a75d6ef8035dfe9651b39\n60f670d75a23a6d094879437a8df455a66acbeaf\n556636e64712a4e161b1d09aeba5833540d05994\n\n# transition to black\nebadee629414aed2c7b6526e22a419205329ec38\n6f0e37c4b926f1411b14bd35da85c7e7e27d0cd9\n\n# update to black 22.1.0\n4036201ab59ac4072defa416ed610c200556b49f\n\n# automated trailing whitespace removal\n3ee548b04a41dfbc009921c492fba6a0682651ca\n\n# converting to f-strings\nad898e8e3954bc348daaa449d5ed73db778785e9\nef51ad5199692afcf1a8ab491aa115c00c423113\n323ac4ddd4e99d6b951666736d4e9b03b6cfa21e\nf7445f02022293f1b089cd8907000301516354bf\n\n# f-strings, black & isort on doc\ne6276f25fc5a570d886b6bbdaa7e3062da2d35ca\n\n# fix EOF\n5e5ce1487f9b04344d297b7901cf5f82c6fee3cb\n\n# fix trailing whitespace\n12068b7a38ec17cb3d83baca32c1dc8d07613f9a\n\n# auto-fixes from pre-commit\nf3a0720bb713aead0f0d50888f2176d20fe02ef4\n7931b96a90c20d835a4dc96b81019382f9461351\n\n# legacy conversion commits authored by \"convert-repo\"\nb7112d4ccbcd309ae2c5c2ee0f9352466a0cecf2\n33f3988bbfa97399114e3df9ac491c828ae59a8c\ncae222aec845d0b400e2aa2804e75b2adef17ccd\n993162d6fb38f55b0bf372da1c3b984ee2d4f085\nef783151bfd7c6777fa25e9e06f95fe47653b3aa\n063e2fb630932cbdcbbbdba603c100a37e7e40f6\n279b0551ccc9d9d4c114904b94bc705381c61105\n\n# apply linting to ipynb files\nec8bb45ea1603f3862041fa9e8ec274afd9bbbfd\n\n# auto upgrade typing idioms from Python 3.8 to 3.9\n4cfd370a8445abd4620e3853c2c047ee3d649fd7\n\n# migration: black -> ruff-format\n3214662dc7d53f7ac56b3589c5a27ef075f83ab4\n\n# auto upgrade syntax from Python 3.9 to 3.10\ne86625e780b77a42aedad711e5a9a945ce650974\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# Auto request reviews from specific users or teams matching patterns\n#\n# - the yt-project retains ownership of any code within this repo unless explicitly stated otherwise\n# - automated requests for reviews are informational and not binding: any party remains\n#   free to conduct reviews or not\n# - opting in is purely on a voluntary basis and cannot be done by a third party\n# - opting out does not require the person or team to open a PR themselves\n# but stakeholders should be contacted if done by a third party.\n#\n# Documentation at\n# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners\n\n# general dependency and CI management\npyproject.toml              @neutrinoceros\nuv.lock                     @neutrinoceros\n.pre-commit-config.yaml     @neutrinoceros\n.git*                       @neutrinoceros\n\n\n# build\nsetup(|ext).py              @neutrinoceros\nMANIFEST.in                 @neutrinoceros\n\n\n# testing\nconftest.py                 @neutrinoceros\n\n\n# frontends\nyt/frontends/amrvac         @neutrinoceros\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "<!--To help us understand and resolve your issue, please fill out the form to\nthe best of your ability.-->\n<!--You can feel free to delete the sections that do not apply.-->\n\n### Bug report\n\n**Bug summary**\n\n<!--A short 1-2 sentences that succinctly describes the bug-->\n\n**Code for reproduction**\n\n<!--A minimum code snippet required to reproduce the bug, also minimizing the\nnumber of dependencies required.-->\n\n<!-- If you need to use a data file to trigger the issue you're having, consider\nusing one of the datasets from the yt data hub (http://yt-project.org/data). If\nyour issue cannot be triggered using a public dataset, you can use the yt\ncurldrop (https://docs.hub.yt/services.html#curldrop) to share data\nfiles. Please include a link to the dataset in the issue if you use the\ncurldrop.-->\n\n```python\n# Paste your code here\n#\n#\n```\n\n**Actual outcome**\n\n<!--The output produced by the above code, which may be a screenshot, console\noutput, etc.-->\n\n```\n# If applicable, paste the console output here\n#\n#\n```\n\n**Expected outcome**\n\n<!--A description of the expected outcome from the code snippet-->\n<!--If this used to work in an earlier version of yt, please note the\nversion it used to work on-->\n\n**Version Information**\n<!--Please specify your platform and versions of the relevant libraries you are\nusing:-->\n  * Operating System:\n  * Python Version:\n  * yt version:\n  * Other Libraries (if applicable):\n\n<!--Please tell us how you installed yt and python e.g., from source,\npip, conda. If you installed from conda, please specify which channel you used\nif not the default-->\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--Thank you so much for your PR! To help us review, fill out the form\nto the best of your ability.  Please make use of the development guide at\nhttp://yt-project.org/docs/dev/developing/index.html-->\n\n<!--Provide a general summary of your changes in the title above, for\nexample \"Raises ValueError on Non-Numeric Input to set_xlim\".  Please avoid\nnon-descriptive titles such as \"Addresses issue #8576\".-->\n\n<!--If you are able to do so, please do not create the PR out of main, but out\nof a separate branch. -->\n\n## PR Summary\n\n<!--Please provide at least 1-2 sentences describing the pull request in\ndetail.  Why is this change required?  What problem does it solve?-->\n\n<!--If it fixes an open issue, please link to the issue here.-->\n\n## PR Checklist\n\n<!-- Note that some of these check boxes may not apply to all pull requests -->\n- [ ] New features are documented, with docstrings and narrative docs\n- [ ] Adds a test for any bugs fixed. Adds tests for new features.\n\n<!--We understand that PRs can sometimes be overwhelming, especially as the\nreviews start coming in.  Please let us know if the reviews are unclear or the\nrecommended next step seems overly demanding , or if you would like help in\naddressing a reviewer's comments.  And please ping us if you've been waiting\ntoo long to hear back on your PR.-->\n"
  },
  {
    "path": ".github/config.yml",
    "content": "# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome\n\n# Comment to be posted to on first time issues\nnewIssueWelcomeComment: >\n  Hi, and welcome to yt!  Thanks for opening your first issue.  We have an issue\n  template that helps us to gather relevant information to help diagnosing and\n  fixing the issue.\n\n# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome\n\n# Comment to be posted to on PRs from first time contributors in your repository\nnewPRWelcomeComment: >\n  Hi!  Welcome, and thanks for opening this pull request.  We have some guidelines for\n  new pull requests, and soon you'll hear back about the results of our tests and\n  continuous integration checks.  Thank you for your contribution!\n\n# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge\n\n# Comment to be posted to on pull requests merged by a first time user\nfirstPRMergeComment: >\n  Hooray!  Congratulations on your first merged pull request!  We hope we keep\n  seeing you around!  :fireworks:\n\n# It is recommend to include as many gifs and emojis as possible\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: github-actions\n  directory: /.github/workflows\n  labels:\n    - dependencies\n    - github_actions\n    - infrastructure\n  schedule:\n    interval: monthly\n  cooldown:\n    default-days: 7\n  groups:\n     gha-patches:\n      update-types:\n      - patch\n\n- package-ecosystem: uv\n  directory: /\n  labels:\n    - dependencies\n    - python:uv\n    - infrastructure\n  schedule:\n    interval: monthly\n  cooldown:\n    default-days: 7\n  groups:\n    uv.lock-patches:\n      update-types:\n      - patch\n"
  },
  {
    "path": ".github/mergeable.yml",
    "content": "# config file for mergeable: https://github.com/mergeability/mergeable\nversion: 2\nmergeable:\n  - when: pull_request.*\n    validate:\n      - do: title\n        must_exclude:\n          regex: ^\\[?WIP\\b\n          message: \"WIP pull requests can't be merged.\"\n      - do: label\n        must_include:\n          regex: 'bug|enhancement|new feature|docs|infrastructure|dead code|refactor'\n          message: \"Please label this pull request with one of: bug, enhancement, new feature, docs or infrastructure.\"\n"
  },
  {
    "path": ".github/workflows/bleeding-edge.yaml",
    "content": "name: CI (bleeding edge)\n# this workflow is heavily inspired from pandas, see\n# https://github.com/pandas-dev/pandas/blob/master/.github/workflows/python-dev.yml\n\n# goals: check stability against\n# - dev version of Python, numpy, matplotlib, and unyt\n# - Cython and pytest pre-releases\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    paths:\n      - .github/workflows/bleeding-edge.yaml\n  schedule:\n    # run this every Wednesday at 3 am UTC\n    - cron: 0 3 * * 3\n  workflow_dispatch:\n\npermissions: {}\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    name: \"tests with bleeding-edge crucial deps\"\n    timeout-minutes: 60\n\n    concurrency:\n      # auto-cancel any in-progress job *on the same branch*\n      group: ${{ github.workflow }}-${{ github.ref }}\n      cancel-in-progress: true\n\n    steps:\n    - name: Checkout repo\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n\n    - name: Set up Python (newest testable version)\n      uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6\n      with:\n        # this version should be upgraded as often as possible, typically once a year when\n        # Cython, numpy and matplotlib are known to be compatible\n        python-version: '3.14'\n        enable-cache: false\n        activate-environment: true # allows using uv pip directly\n\n    - name: Configure uv\n      run: |\n        echo \"UV_PRERELEASE=allow\" >> \"$GITHUB_ENV\"\n        echo \"UV_INDEX=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple\" >> \"$GITHUB_ENV\"\n        echo \"UV_INDEX_STRATEGY=unsafe-best-match\" >> \"$GITHUB_ENV\"\n\n    - name: Install dependencies\n      run: |\n        uv add --no-install-project git+https://github.com/yt-project/unyt.git\n        uv add --no-install-project --optional test git+https://github.com/pytest-dev/pytest.git\n        uv pip install --upgrade setuptools numpy matplotlib Cython ewah-bool-utils\n\n    - name: Build\n      # --no-build-isolation is used to guarantee that build time dependencies\n      # are not installed by uv sync as specified from pyproject.toml, hence we get\n      # to use the dev version of numpy at build time.\n      run: uv pip install --editable .[test] --no-build-isolation\n\n    - run: yt config set --local yt log_level 50  # Disable excessive output\n    - name: Run Tests\n      run: uv run --no-sync pytest yt -vvv --color=yes\n\n  create-issue:\n    if: ${{ failure() && github.event_name == 'schedule' }}\n    needs: [build]\n    permissions:\n      issues: write\n    runs-on: ubuntu-latest\n    name: Create issue on failure\n\n    steps:\n    - name: Create issue on failure\n      uses: imjohnbo/issue-bot@572eed14422c4d6ca37e870f97e7da209422f5bd # v3.4.4\n      with:\n        title: 'TST: Upcoming dependency test failures'\n        body: |\n          The weekly build with future dependencies has failed. Check the logs\n          https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}\n        pinned: false\n        close-previous: false\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/build-test.yaml",
    "content": "name: Build and Test\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n  workflow_dispatch:\n\ndefaults:\n  run:\n    shell: bash\n\npermissions: {}\n\njobs:\n  check-lock-file:\n    # validate uv.lock against requirements\n    # a failure indicates that the lock file is out of sync\n    # with requirements and needs to be updated\n    # this is normally done using uv-pre-commit but yt's dependency\n    # graph is too big to run on pre-commit.ci\n    name: Check uv.lock\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout repo\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Set up uv\n      uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6\n      with:\n        version: \">=0.9.11,<0.10.0\" # pin uv to avoid the lock file format going out-of-sync\n        python-version: 3.14\n        cache-suffix: lockcheck\n        prune-cache: false\n    - run: uv lock --check\n\n  build:\n    name: \"${{ matrix.tests-type }} tests: py${{ matrix.python-version }} on ${{ matrix.os }} (${{ matrix.test-runner }})\"\n    needs: [check-lock-file]\n    strategy:\n      # run all tests even if e.g. image tests fail early\n      fail-fast: false\n      matrix:\n        os:\n          - ubuntu-latest\n          - macos-latest\n        python-version: ['3.13']\n        sync_args: [--extra=full]\n        tests-type: [unit]\n        test-runner: [pytest]\n        cache-suffix: [null]\n        include:\n          - os: windows-latest\n            python-version: '3.13'\n            sync_args: --extra=full\n            tests-type: unit\n            test-runner: pytest\n            cache-suffix: alldeps\n          - os: ubuntu-22.04\n            python-version: '3.10.4'\n            sync_args: --resolution=lowest\n            tests-type: unit\n            test-runner: pytest\n            cache-suffix: oldestdeps\n          - os: ubuntu-latest\n            # this job is necessary for non-answer, 'yield' based tests\n            # because pytest doesn't support such tests\n            python-version: '3.10'\n            sync_args: \"--extra=full --group=nosetest\"\n            tests-type: unit\n            test-runner: nose\n            cache-suffix: alldeps-nose\n          - os: ubuntu-latest\n            # answer tests use 'yield', so they require nose\n            # they are also attached to a specific, occasionally updated, Python version\n            # but it does *not* have to match the current minimal supported version\n            python-version: '3.10'\n            sync_args: \"--extra=full --group=nosetest\"\n            tests-type: answer\n            test-runner: nose\n            cache-suffix: alldeps-nose\n          - os: ubuntu-latest\n            # minimal tests with latest Python and no optional dependencies\n            python-version: '3.14'\n            sync_args: null\n            tests-type: unit\n            test-runner: pytest\n            cache-suffix: minimal\n\n    runs-on: ${{ matrix.os }}\n\n    concurrency:\n      # auto-cancel any in-progress job *on the same branch*\n      group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.tests-type }}-py${{ matrix.python-version }}-${{ matrix.os }}-${{ matrix.test-runner }}\n      cancel-in-progress: true\n\n    steps:\n    - name: Checkout repo\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n        submodules: ${{ matrix.tests-type == 'answer' }}\n    - name: Set up Python\n      uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6\n      with:\n        python-version: ${{ matrix.python-version }}\n        cache-suffix: ${{ matrix.cache-suffix }}\n        prune-cache: false\n    - name: Install dependencies and yt\n      shell: bash\n      env:\n        sync_args: ${{ matrix.sync_args}}\n      run: source ./tests/ci_install.sh\n\n    - name: Patch nosetest\n      if: matrix.test-runner == 'nose'\n      # note: this could be handled with [tool.uv.sources]\n      run: |\n        find .venv/lib/python${{matrix.python-version}}/site-packages/nose -name '*.py' \\\n          -exec sed -i -e s/collections.Callable/collections.abc.Callable/g '{}' ';'\n\n    - name: Run Unit Tests (pytest)\n      if: matrix.test-runner == 'pytest'\n      run: uv run --no-sync pytest yt --color=yes ${{ contains(matrix.sync_args , 'resolution=lowest' ) && '-Wdefault' || '' }}\n      env:\n        MPLCONFIGDIR: tests\n\n    - name: Run Tests (nose)\n      if: matrix.test-runner == 'nose'\n      run: |\n        cat nose_ignores | xargs uv run python -m nose -c nose_unit.cfg --traverse-namespace\n      env:\n        MPLCONFIGDIR: tests\n\n  image-tests:\n    name: Image tests\n    runs-on: ubuntu-latest\n    needs: [check-lock-file]\n\n    concurrency:\n      # auto-cancel any in-progress job *on the same branch*\n      group: ${{ github.workflow }}-${{ github.ref }}\n      cancel-in-progress: true\n\n    steps:\n    - name: Checkout repo (with submodules)\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        submodules: true\n        persist-credentials: false\n    - name: Set up Python\n      uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6\n      with:\n        python-version: '3.11'\n        enable-cache: false\n    - name: Install dependencies and yt\n      shell: bash\n      env:\n        sync_args: --group=mapping\n      run: source ./tests/ci_install.sh\n\n    - run: python -m pip list\n\n    - name: Run Image Tests\n      run: |\n        uv run --no-sync \\\n            pytest yt --color=yes --mpl -m mpl_image_compare \\\n               --mpl-generate-summary=html \\\n               --mpl-results-path=pytest_mpl_results \\\n               --mpl-baseline-path=tests/pytest_mpl_baseline \\\n               -rxXs # show extra info on xfailed, xpassed, and skipped tests\n      env:\n        MPLCONFIGDIR: tests\n\n    - name: Generate new image baseline\n      if: failure()\n      run: |\n        uv run --no-sync \\\n            pytest yt --color=yes --mpl -m mpl_image_compare \\\n               --mpl-generate-path=pytest_mpl_new_baseline \\\n               --last-failed\n      env:\n        MPLCONFIGDIR: tests\n\n    # always attempt to upload artifacts, even\n    # (and especially) in case of failure.\n    - name: Upload pytest-mpl report\n      if: always()\n      uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n      with:\n        name: yt_pytest_mpl_results\n        path: pytest_mpl_results/*\n\n    - name: Upload pytest-mpl baseline\n      if: always()\n      uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n      with:\n        name: yt_pytest_mpl_new_baseline\n        path: pytest_mpl_new_baseline/*\n        if-no-files-found: ignore\n"
  },
  {
    "path": ".github/workflows/type-checking.yaml",
    "content": "name: type checking\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    paths:\n      - yt/**/*.py\n      - pyproject.toml\n      - .github/workflows/type-checking.yaml\n  workflow_dispatch:\n\npermissions: {}\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    name: type check\n    timeout-minutes: 60\n\n    concurrency:\n      # auto-cancel any in-progress job *on the same branch*\n      group: ${{ github.workflow }}-${{ github.ref }}\n      cancel-in-progress: true\n\n    steps:\n    - name: Checkout repo\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n\n    - name: Set up Python\n      uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6\n      with:\n        # run with oldest supported python version\n        # so that we always get compatible versions of\n        # core dependencies at type-check time\n        python-version: '3.10'\n        prune-cache: false\n        cache-suffix: typecheck\n\n    - name: Build\n      run: uv sync --group typecheck\n\n    - name: Run mypy\n      run: uv run --no-sync mypy yt\n"
  },
  {
    "path": ".github/workflows/wheels.yaml",
    "content": "name: Build CI Wheels\n\non:\n  push:\n    branches:\n      - main\n      - stable\n    tags:\n      - 'yt-*'\n  pull_request:\n    paths:\n      - '.github/workflows/wheels.yaml'\n      - MANIFEST.in\n      - pyproject.toml\n      - setup.py\n      - setupext.py\n  workflow_dispatch:\n\npermissions: {}\n\njobs:\n  build_wheels:\n    name: Build ${{ matrix.select }}-${{ matrix.archs }} wheels on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n        - os: ubuntu-latest\n          archs: x86_64\n          select: '*manylinux*'\n          id: manylinux_x86_64\n        - os: ubuntu-latest\n          archs: x86_64\n          select: '*musllinux*'\n          id: musllinux_x86_64\n        - os: macos-latest\n          archs: x86_64\n          select: '*'\n          id: macos_x86_64\n        - os: macos-latest\n          archs: arm64\n          select: '*'\n          id: macos_arm64\n        - os: windows-latest\n          archs: AMD64\n          select: '*'\n          id: windows_AMD64\n\n    steps:\n      - name: Checkout repo\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          persist-credentials: false\n\n      - name: Build wheels for CPython\n        uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0\n        with:\n          extras: uv\n          output-dir: dist\n        env:\n          CIBW_ARCHS: ${{ matrix.archs }}\n          CIBW_BUILD: ${{ matrix.select }}\n\n      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: wheels-${{ matrix.os }}-${{ matrix.id }}\n          path: ./dist/*.whl\n\n  build_sdist:\n    name: Build source distribution\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repo\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          persist-credentials: false\n\n      - name: Set up Python\n        uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6\n        with:\n          python-version: '3.10'\n          enable-cache: false\n\n      - name: Build sdist\n        run: uv build --sdist\n\n      - name: Upload sdist\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: sdist\n          path: dist/*.tar.gz\n\n      - name: copy configuration files\n        run: |\n          mkdir cfg\n          cp pyproject.toml cfg\n          cp conftest.py cfg\n\n      - name: Upload pytest configuration files\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: pytest-conf\n          path: cfg\n\n  test_sdist:\n    name: Test source distribution\n    runs-on: ubuntu-latest\n    needs: [build_sdist]\n    steps:\n      - name: Download sdist\n        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1\n        with:\n          name: sdist\n          path: dist\n\n      - name: Set up Python\n        uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6\n        with:\n          python-version: '3.10'\n          enable-cache: false\n          activate-environment: true # allows using uv pip directly\n\n      - name: Download pytest configuration files\n        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1\n        with:\n          name: pytest-conf\n          path: cfg\n\n      - name: Display structure of downloaded files\n        run: ls -R\n\n      - name: Install sdist\n        run: uv pip install \"$(echo dist/yt-*.tar.gz)[test]\"\n\n      - run: uv pip list\n\n      - name: Test sdist\n        run: |\n          uv run --no-project --directory cfg \\\n            pytest --color=yes --pyargs yt\n\n  check_manifest:\n    name: Check MANIFEST.in\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout repo\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        submodules: true\n        persist-credentials: false\n    - name: Set up Python\n      uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6\n      with:\n        python-version: '3.13'\n        enable-cache: false\n    - name: run check-manifest\n      run: uvx check-manifest -vvv\n\n  deploy:\n    name: Publish to PyPI\n    needs: [build_wheels, test_sdist, check_manifest]\n    runs-on: ubuntu-latest\n    environment:\n      name: pypi\n      url: https://pypi.org/p/yt\n    permissions:\n      id-token: write\n    # upload to PyPI on every tag starting with 'yt-'\n    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/yt-')\n    steps:\n      - name: Download sdist\n        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1\n        with:\n          name: sdist\n          path: dist\n\n      - name: Download wheels\n        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1\n        with:\n          path: dist\n          pattern: wheels-*\n          merge-multiple: true\n\n      - name: Publish to PyPI\n        uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0\n"
  },
  {
    "path": ".gitignore",
    "content": "build\nyt.egg-info\n__config__.py\nfreetype.cfg\nhdf5.cfg\npng.cfg\nrockstar.cfg\nyt_updater.log\nyt/frontends/artio/_artio_caller.c\nyt/frontends/gamer/cfields.c\nyt/analysis_modules/halo_finding/rockstar/rockstar_groupies.c\nyt/analysis_modules/halo_finding/rockstar/rockstar_interface.c\nyt/analysis_modules/ppv_cube/ppv_utils.c\nyt/analysis_modules/photon_simulator/utils.c\nyt/frontends/ramses/_ramses_reader.cpp\nyt/frontends/ramses/io_utils.c\nyt/frontends/sph/smoothing_kernel.c\nyt/geometry/fake_octree.c\nyt/geometry/grid_container.c\nyt/geometry/grid_visitors.c\nyt/geometry/oct_container.c\nyt/geometry/oct_visitors.c\nyt/geometry/particle_deposit.c\nyt/geometry/particle_oct_container.c\nyt/geometry/particle_oct_container.cpp\nyt/geometry/particle_smooth.c\nyt/geometry/selection_routines.c\nyt/utilities/amr_utils.c\nyt/utilities/cython_fortran_utils.c\nyt/utilities/lib/autogenerated_element_samplers.c\nyt/utilities/kdtree/forthonf2c.h\nyt/utilities/libconfig_wrapper.c\nyt/utilities/spatial/ckdtree.c\nyt/utilities/lib/allocation_container.c\nyt/utilities/lib/alt_ray_tracers.c\nyt/utilities/lib/amr_kdtools.c\nyt/utilities/lib/basic_octree.c\nyt/utilities/lib/bitarray.c\nyt/utilities/lib/bounded_priority_queue.c\nyt/utilities/lib/bounding_volume_hierarchy.c\nyt/utilities/lib/contour_finding.c\nyt/utilities/lib/cykdtree/kdtree.cpp\nyt/utilities/lib/cykdtree/utils.cpp\nyt/utilities/lib/cyoctree.c\nyt/utilities/lib/depth_first_octree.c\nyt/utilities/lib/distance_queue.c\nyt/utilities/lib/element_mappings.c\nyt/utilities/lib/fnv_hash.c\nyt/utilities/lib/fortran_reader.c\nyt/utilities/lib/freetype_writer.c\nyt/utilities/lib/geometry_utils.c\nyt/utilities/lib/image_samplers.c\nyt/utilities/lib/image_utilities.c\nyt/utilities/lib/interpolators.c\nyt/utilities/lib/kdtree.c\nyt/utilities/lib/lenses.c\nyt/utilities/lib/embree_mesh/mesh_construction.cpp\nyt/utilities/lib/embree_mesh/mesh_intersection.cpp\nyt/utilities/lib/embree_mesh/mesh_samplers.cpp\nyt/utilities/lib/embree_mesh/mesh_traversal.cpp\nyt/utilities/lib/mesh_triangulation.c\nyt/utilities/lib/mesh_utilities.c\nyt/utilities/lib/pixelization_routines.cpp\nyt/utilities/lib/misc_utilities.c\nyt/utilities/lib/particle_kdtree_tools.cpp\nyt/utilities/lib/particle_mesh_operations.c\nyt/utilities/lib/partitioned_grid.c\nyt/utilities/lib/primitives.c\nyt/utilities/lib/origami.c\nyt/utilities/lib/particle_mesh_operations.c\nyt/utilities/lib/pixelization_routines.c\nyt/utilities/lib/pixelization_routines.cpp\nyt/utilities/lib/png_writer.c\nyt/utilities/lib/points_in_volume.c\nyt/utilities/lib/quad_tree.c\nyt/utilities/lib/ragged_arrays.c\nyt/utilities/lib/cosmology_time.c\nyt/utilities/lib/cosmology_time.cpp\nyt/utilities/lib/grid_traversal.c\nyt/utilities/lib/marching_cubes.c\nyt/utilities/lib/png_writer.h\nyt/utilities/lib/write_array.c\nyt/utilities/lib/partitioned_grid.c\nyt/utilities/lib/volume_container.c\nyt/utilities/lib/lenses.c\nyt/utilities/lib/image_samplers.c\nyt/utilities/lib/_octree_raytracing.cpp\nyt/utilities/lib/bounding_volume_hierarchy.cpp\nyt/utilities/lib/geometry_utils.cpp\nyt/utilities/lib/grid_traversal.cpp\nyt/utilities/lib/image_samplers.cpp\nyt/utilities/lib/marching_cubes.cpp\nyt/utilities/lib/misc_utilities.cpp\nyt/utilities/lib/partitioned_grid.cpp\n*.pyc\n*.pyd\n.*.swp\n*.DS_Store\n*.so\n.idea/*\ntests/results/*\ndoc/build/*\ndoc/source/reference/api/generated/*\ndoc/source/reference/api/yt*\ndoc/cheatsheet.aux\ndoc/cheatsheet.log\ndoc/cheatsheet.pdf\ndoc/_temp/*\ndoc/source/quickstart/.ipynb_checkpoints/\ndist\n.python-version\nanswer_nosetests.xml\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"answer-store\"]\n\tpath = answer-store\n\turl = https://github.com/yt-project/answer-store\n\tbranch = yt-4.0\n[submodule \"tests/pytest_mpl_baseline\"]\n\tpath = tests/pytest_mpl_baseline\n\turl = https://github.com/yt-project/yt_pytest_mpl_baseline\n"
  },
  {
    "path": ".hgchurn",
    "content": "stephenskory@yahoo.com = s@skory.us\n\"Stephen Skory stephenskory@yahoo.com\" = s@skory.us\nbear0980@gmail.com = yuan@astro.columbia.edu\njuxtaposicion@gmail.com = cemoody@ucsc.edu\nchummels@gmail.com = chummels@astro.columbia.edu\njwise@astro.princeton.edu = jwise@physics.gatech.edu\nsam.skillman@gmail.com = samskillman@gmail.com\ncasey@thestarkeffect.com = caseywstark@gmail.com\nchiffre = chiffre@posteo.de\nChristian Karch = chiffre@posteo.de\natmyers@berkeley.edu = atmyers2@gmail.com\natmyers = atmyers2@gmail.com\ndrudd = drudd@uchicago.edu\nawetzel = andrew.wetzel@yale.edu\nDavid Collins (dcollins4096@gmail.com) = dcollins4096@gmail.com\ndcollins4096 = dcollins4096@gmail.com\ndcollins@physics.ucsd.edu = dcollins4096@gmail.com\ntabel = tabel@slac.stanford.edu\nsername=kayleanelson = kaylea.nelson@yale.edu\nkayleanelson = kaylea.nelson@yale.edu\njcforbes@ucsc.edu = jforbes@ucolick.org\nbiondo@wisc.edu = Biondo@wisc.edu\nsamgeen@googlemail.com = samgeen@gmail.com\nfbogert = fbogert@ucsc.edu\nbwoshea = oshea@msu.edu\nmornkr@slac.stanford.edu = me@jihoonkim.org\nkbarrow = kssbarrow@gatech.edu\nkssbarrow@gmail.com = kssbarrow@gatech.edu\nkassbarrow@gmail.com = kssbarrow@gatech.edu\nantoine.strugarek@cea.fr = strugarek@astro.umontreal.ca\nrosen@ucolick.org = alrosen@ucsc.edu\njzuhone = jzuhone@gmail.com\nkarraki@nmsu.edu = karraki@gmail.com\nhckr@eml.cc = astrohckr@gmail.com\njulian3@illinois.edu = astrohckr@gmail.com\naj@hckr.eml.cc = astrohckr@gmail.com\ncosmosquark = bthompson2090@gmail.com\nchris.m.malone@lanl.gov = chris.m.malone@gmail.com\njnaiman@ucolick.org = jnaiman@cfa.harvard.edu\njnaiman = jnaiman@cfa.harvard.edu\nmigueld.deval = miguel.deval@gmail.com\nmiguel@archlinux.net = miguel.deval@gmail.com\nslevy@ncsa.illinois.edu = salevy@illinois.edu\nmalzraa@gmail.com = kellerbw@mcmaster.ca\nNone = convert-repo\ndfenn = df11c@my.fsu.edu\nlangmm = langmm.astro@gmail.com\njmt354 = jmtomlinson95@gmail.com\ndesika = dnarayan@haverford.edu\nBen Thompson = bthompson2090@gmail.com\ngoldbaum@ucolick.org = ngoldbau@illinois.edu\nngoldbau@ucsc.edu = ngoldbau@illinois.edu\nNTAuthority@honeypot.fritz.box = anokfireball@poseto.de\nNTAuthority@guest053.fz-rossendorf.de = anokfireball@poseto.de\nNTAuthority@guest692.fz-rossendorf.de = anokfireball@poseto.de\nFabian Koller = anokfireball@poseto.de\nRafael Ruggiero = rafael.ruggiero@usp.br\njohn.regan@helsinki.fi = john.a.regan@durham.ac.uk\ncode@andre-bubel.de = a.huebl@hzdr.de\n"
  },
  {
    "path": ".hgtags",
    "content": "008b15bd5122e910e321316fae6122da9a46e10f svn.1636\n009bca8c4c596f01526bc8159c90786e7c011d67 svn.347\n00d699ef358745ba38f4381c35f2e7d5a6306161 svn.394\n00e034fabfd073790aaa4ef82bbb666582ae9f57 svn.1265\n01165f5c21dd23b8fadfb36a736c1af7f2f51bea svn.1687\n011e9277ab04e996ac2d710f64cd361235f4103b svn.658\n0157ff037651bf0146f57adaf95a173222a3937c svn.6\n0161f0770fe9d9a6b45cfff81c6c948bf5ac0d23 svn.112\n0173810f90aa32e9f41f822a74f86d7f9aff9406 svn.417\n019c2ed6f48651f2dbcf680d35869da9aad5d990 svn.1007\n01a6acd7b8084564c58060b14f54609e88f173bf svn.1483\n01e8bd44c26797472f364c787dc5cce782d086e5 svn.1204\n022d3c06297845aec35f5649a3237710d8b9f55f svn.1802\n023b31f57812c07152cd34d79f1f9909cae1e556 svn.83\n028e083af0708acaa2050533839115d8a69b9a0c svn.1696\n02f2b6cd7ea1d2d2d357475f2204f2410cf892ab svn.1205\n03164a14f6b23fc6b8bc7abc6c90ab9680740084 svn.646\n03c59e910c318794f99952c90dd146dc7e01e197 svn.258\n040638d12856f79baa0d005d14e11cd36ba8a331 svn.330\n0484b002a76a99a55a13454b6ce17ef45400316b svn.1356\n048721d682f8148428a77d82c59d904f3d9c8504 svn.884\n04a629b15da3ff3678e362065cf884834bd02a22 svn.1197\n04bff5f55cb9e853b6183000e065b0080bcea8dd svn.1002\n04c2b901d8a05070295cbe351fa0c43291ef624c svn.1173\n04c660cac68b46d16341e9c455dc372b1334efda svn.1224\n04db5f9b3122cc2bf749cc2e53383eb86c853a51 svn.769\n04dff7a15ba545a16a93f50d7b125a6fca0ed10f svn.1707\n04e29cfda98d62bab16d2053c3b7a774b04298f0 svn.1445\n059a6f91c2e580b60123f7ac45983801fbd512bc svn.1257\n05a1599e4916c970d398b641bbdb3953f0907ea8 svn.8\n05b44aec7b74826628f5bf03adec7d84511efc65 svn.1411\n05f3e58bc4b9a0b136659a69df4f1d4d05d0702d svn.1613\n060a01faf6ae89a6efb3f6d16fdfee0360e36ecd svn.1757\n060cccf0ba19182d334bbdeb576d8f58bf0d71c7 svn.200\n0637b5808587b7c6e5425330d61045a0ebfc3c7a svn.1035\n0669ff70d50dc275904335d6e5560d8fba1bd524 svn.1014\n0682073503fda2662e7a5d1fdf205787cec2ef29 svn.653\n06d5806a711f394f6a5889da1214051550ff66c6 svn.802\n06e806dbd816945b98448239023700b9ce1feb4f svn.410\n06e83f7e374489e7e930bbb3233a7d48da2a4325 svn.930\n0763601a5bcc18dffc999f8889aaa24a3954b855 svn.234\n07780c9b1b42083cbf29fa4aad7195c65a7155cb svn.1044\n0791309fea40c03510ad3513de4fddb084697d80 svn.1058\n07981139bfd9fdf363c872de11db226c8431841a svn.1318\n079f4596e62fc77247141ff779774a785061abfc svn.1633\n07db277ec95af7cb9849096730666b335bcc9c5e svn.1094\n07dddd86d8fe37233ea506155e3dea8e4280a38d svn.1579\n07f45d81abfb32c883e9f29ff2d6ca442c62faac svn.968\n083153346e4989561bf77e67bcf8b07d6d8ee0d7 svn.475\n085616db39c365408b3b2713cec63cdc75c1b0d3 svn.189\n08930d496c9b0e758bf53d5b8024ded0179cf44f svn.370\n08954e47091923482a4ef97dde985554cc9a23df svn.288\n08b7d403a90b550912cf513433a5d093d5745250 svn.332\n08d3aad985e07d28e217cce7e8849b24d1c18ae3 svn.1435\n08d7629cc529e025b1f85fb5d45326975f48b84b svn.45\n08dca756db3206be21541d869dfccd5dc43247f9 svn.1379\n08febc793acd54cf4b76bbeddcb79637f1553470 svn.1645\n0927e0c91ca66d1d41500c9fd08cde5014ceacf8 svn.1139\n0954054bc59272f7974c63cd77933ea344c64619 svn.744\n0963334d761e9d8db2acf74639cb938c250c8302 svn.1495\n099aa96010cdda52c5f2fc83d72e7e03275acd86 svn.1700\n099b4e43b52a38fa49164d22726650111cb89f30 svn.1329\n09b6347351e9fa337cddc10d39c929b497cc7634 svn.1778\n09e730a2c4fb77abfff2ae0dc0e9ab753683815f svn.854\n09f95c184d9e12dfdfdce597b3812f81976c68fd svn.898\n09fd3ab58e68418a1cceb9db1938655723a8732d svn.1222\n0a3cd2ca46aa4a913dc6cca250deba0aba2f7661 svn.536\n0a661cfd8bcf95d37bcba79cd5eb0b8d194a4228 svn.24\n0a70307856e719657be483e103df1fb3803e6344 svn.182\n0a722d174c5e259d343b64af2dff46682815c84b svn.298\n0a929e7c80049aa2dba28242c3cfa97b2e3a7bb1 svn.402\n0a9a9685cb883fddbba2ad166cfec47924102134 svn.499\n0a9e87864df2ff4434732ae7eba7734630cba360 svn.1192\n0ae3ae67d23e4014f104007447a53bfe770fbbd8 svn.951\n0b0aeb725d723795421475cd3bd285bb0c94c713 svn.1432\n0b0be6afa3e6db0011dbc2d132c0f2ede7c1c905 svn.983\n0b3713cd3b3dafc68054cbcfdaa80442c11ff172 svn.88\n0b78bcc9c1b17e85cd5a57242d3657c2c1bb7a0c svn.559\n0ba47ee1dc4abf6efc06cb04b5994c728572fefe svn.1178\n0bb40f1e128c4eb6cfc42c4fc043f7cb54550de5 svn.1130\n0bc0bdac8bc20d2fb314ae00ccad146883a3311f svn.725\n0beb7e783548eb33c55ffe1ceb0c64fb895b511a svn.341\n0bf4ca209b1f9fc18947bd604d05ac624094d8a6 svn.1304\n0c004242b3f8c110502e4ea6ee1bdb24d6bc75cf svn.271\n0c4173bd9d0801ed84dd884c76874bf96c66cc12 svn.720\n0c561365081c119bd4d7240258909a38e2f4d03f svn.427\n0c7909677b752a79eadcb4325aab9b34f0cb101d svn.1190\n0c987873f61cce2b19b2f227520252c624e7f180 svn.123\n0c9fa0cfd51283b0531891e486402110005dc0cc svn.1022\n0cb628110a0948ef3b8d5f352695321800ec2ff2 svn.1060\n0ce364970738c599e6f1aa27aebf59ddbde79faa svn.253\n0cf48554d6a7d95cae64d5a28fe4ffdcdf6e5efb svn.1766\n0d4a60cf51319720ecd77e7a58dbf59d1a018f45 svn.1017\n0d53d3fa1c6aedc57a4e9b9683f0245f8ee1a65e svn.1492\n0d5c2d29d28dbd71905afe6889d79c8339519beb svn.1026\n0da3c07627aae61a53ca2a8fe5a728acd1831553 svn.1051\n0da429b7799c4e9a251921e59006199ae6e58c29 svn.144\n0e1919bee94c9b5095f334c9cd7caea4aa0dc4e6 svn.710\n0e20a44102dafc5fcc23b4d2429e37a38b218282 svn.668\n0e5d5039e575561eb8dc9c8afd89aca00d586489 svn.1730\n0e5dd9c049a1f89c0d8821dc46f6d8aa29ed1341 svn.412\n0e5f9e70d3a04d88f9b454d8777177aafc48a3a1 svn.64\n0e81d2275949f038fd9874b0394e67976705021a svn.363\n0e938dd99255dbb30683aa7eb73704a60aabcdbd svn.158\n0e9c51767a9ebb496f2ac88bb1de995953b49b59 svn.122\n0ec1bb8306a20d164ad38e02e7b7df60382ac77d svn.118\n0ecf19fa27577dd5f386ad6153da79966eb19f04 svn.1027\n0ed17274b6410a68dd2bf6d82499d5b79a7fbd60 svn.974\n0ee8ebbbafaf6a2966dfaaf8e58c559832dd1797 svn.539\n0ee914b3cb3b97c478c6e10a1fceda690b04af45 svn.522\n0ef34494c8ed215eaeeb41775d671b3da55a23fc svn.445\n0f0c7cb6975400e566e78aa88ea15dec8e937778 svn.447\n0f1154a13b23e757639ace21e41596b061f50133 svn.661\n0f15cd632305fe76dfc6d1639ba093c6a2cf652e svn.1605\n0f214513456482b170e01e3226ac34b6e9cc7532 svn.1165\n0f57be523b3ec9f88963f1964ab63c7e5f4def4b svn.1625\n0f8d2d911aa1c061320825dafda295b56cca2933 svn.1400\n0fd851c1be1b98520ac273b5ccfc6510a7593006 svn.761\n0fdd63f1c8fd2a6063c0f92bc9024b7ac9ade49a svn.1157\n0ff85e6c512a4d14e9dd71ea373efa0c76b24da0 svn.324\n105de8e9c3b4083203db5dd507b2063b6ef9f1bd svn.738\n1098a40cd1bf368573e2178218a4bada315b99a3 svn.979\n111afe4ac34dd880df57aa7c32651d1150bbf625 svn.1765\n11614afba0f7ba29930970da8a55708bdbc14254 svn.864\n1162e6328a888cf4b1d83fe5c10aaec511a680c8 svn.129\n118e1fd5a88e88e9b8efdfe273df0b8cb1a10838 svn.733\n11a938db9e02a14bccf251c1de2f564844afa5a0 svn.579\n120f50838cd6598bd4cdcef4e2bf87a22a502207 svn.1320\n122a58cfd7b4842fbb9317ab01ba2c39f5b0c444 svn.285\n127e39229e9a70084beb16bfde1bf1bde81e0429 svn.560\n12b70ef667d79c49bce71cec7bfe6289bd073e24 svn.722\n12db47fb546b07451b51c62a855e15977ccfbbff svn.1783\n13335553c66d4f48e9ac2ec49cd064d1c0e0f1c7 svn.827\n133f6efb6e1b4ab30e989c793a72123f76d8fc34 svn.830\n1343353d1ff63c13432208a5b7c933e8c947e1c0 svn.1255\n1356fca5f8ff55d7c3a8aff88e6519d5c5637f0f svn.403\n13576ff5249f3cb5a0c41f6186850801c11295f9 svn.114\n136169c5ee3d74488d2f08929d58d2919f053bd7 svn.829\n1371fe7d04067853541990eb05c04b2cd8b0cce2 svn.858\n137d069b382d7175cf5d425fae13d9f20a8d9005 svn.1433\n13bc25d9c7e68124d74e7d3ff85dc0475a12cc37 svn.1366\n143e18f3ce685a33208ce4f470b3ec79e1c2a5ab svn.385\n149036420e91872c752c4ce518bdfeff70330a62 svn.1135\n1490fcfb24c70f19f8821224994b546915f5b4b0 svn.1563\n149c0fa98e192e90bed74344a8ae5ebbf407064d svn.120\n14ac1c49a658eef1e9ee29e22fa2e0d4b91a3237 svn.704\n14b6fa5edc2f5453e9e8ab8113930d53e961374f svn.1789\n14bdaf98da0d90eb722eeaec6157de8d0d0408ac svn.1046\n14f866bbf50c4cd99474e150129144f9c67c9ea2 svn.437\n150f74c95526a7387bc828f957348029f71e61a6 svn.897\n153daa22d893d844ae6797ce8b33bacf016b81f3 svn.91\n159874fa0df3577327f99ed11df19c03bd7d9926 svn.487\n15a63d9a7b7190464132e8f36820b1c69f121458 svn.786\n15cac1fda9ddd07988f4b0a222d09bb89beda8cb svn.1279\n164274e4466e0a8cabd81f8342ccebc918a5121a svn.477\n165bddcbfa004dea70fabe80f82da668831760d0 svn.329\n1665a35cd4ea1b29b6bd93b0d82d423bed317294 svn.541\n166738006c3a058f301632619fbaa9fdd7800e2b svn.495\n167dd46aa411490021352403f7e408ca176e640d svn.556\n16876332fbaab496fa75d8b4cef80d32a096bf15 svn.1091\n16ac4b5438f69ba8320cde2e7c86bf1e50696c1e svn.1249\n17539cf7cec90818d553f47d103067a66b6ece5f svn.75\n177a80a5c2cb7737fd16802e2623d72762e09a33 svn.1183\n17853da5da9438d1549e0b7a74cc590c78102497 svn.1300\n178f1ce70fbd58e636295d8d74de81c2e481046d svn.32\n1790cffb14fc49234d98115217d574f918d05b90 svn.878\n1799a24c3cc76b221815808e883ac018e7cd987a svn.428\n17d411ced9321408ea900b0e5eb89b46c3f43eed svn.214\n17e94e863ca4a724e2e0cd4ab12023872ec4a74c svn.759\n17ffb91f1954a49e66d0809489b74d5f42abffa0 svn.104\n181f4e0072c301c4fbb3146a1558fbf82ab8b4e7 svn.1270\n183ed087726cb1158a7ada1fd9ea59e726dff4de svn.1259\n18495205f69e0f05998f0760c9a3ebf1d58fd1a9 svn.1391\n186b361ed7e6197c89f71dd5a32db8da0178cb89 svn.826\n18880acd09ee8e6dae8fca4442c426bddec35784 svn.1037\n189409797cf12cbefb0028a1f969149fdd7d17ed svn.1628\n18a1dc2a911eeddea1fe71d83759f4eef9b73cc4 svn.709\n18ea5b4ac63865d808f56b04b0c97a08fb314c58 svn.1132\n190508aa312959c217130a0dabd459ac4b85c043 svn.1099\n190731ac2ebeeed836dd407f8f6ffe966ec95016 svn.950\n191e425284f01d40d1b4e1c7b40bd86611ff0fde svn.1401\n19400f194230d1b999065ef031279aff724a0590 svn.1195\n1959f7bb7ef09e346e60ffdefc72de8b56b8139e svn.1063\n195a8ad57107ac668cb656f26cdbb6f8b96b9dc1 svn.1156\n1974365b127a40e9e62b041b6252c902f204c0b7 svn.1640\n19aa76db8a96347d67f1b07cd1fc8a1f26dce3b6 svn.1229\n19d2296f62d8f2e2ebbc7f823608d60683ddbc65 svn.161\n19e098d6fbf349a90d146cb8be3142e418851ba1 svn.393\n1a29c93406b03f5f51bdfde9d1d8e71a5fd0a014 svn.1598\n1a6fa6bbf1d839de617cf6d731f124780505c854 svn.1396\n1aa14a655fceeb59dc35419dadaa3eb9b3ade3a9 svn.16\n1af453e638d1761025e8dadd053d4ceb8d79e90e svn.167\n1b0b000a008adecc82d6fbfb2ba683770140c849 svn.1422\n1b0db0bf5599a565815ae8de98254b653afc8a94 svn.159\n1b0f3eaf60133f5daabf8876df225d7e17a2d7ca svn.273\n1b1fbe998eb3a689db2b2cead578f0bad2bee23a svn.855\n1b2d2b328f1a2268014054a1a106131524b9a58e svn.1732\n1b41f55e39f627f352939c4c40d223666cadd40f svn.108\n1b7c2ab8c72587e372905ba952d301c02fcfa35d svn.191\n1b7d38aae84dfc9a011ad76c2542c138f4bf720b svn.514\n1b8a4c4629be438e8e48b7d5f471b61f7e224eeb svn.294\n1bb32ee8b90599f4e01a894eb8f4e1edb7161782 svn.1036\n1bf7e34e171d1a8758f2b7176b12ab995241c4cc svn.700\n1c37aebefb76e9e26438089b588c1111f1b6d1d7 svn.796\n1c40d66bae3c4bf0d1f101ffeb0acea168926cad svn.1762\n1cb76ee3636da871dedcf57a83da3866fc73bbfd svn.1021\n1cc9cf146664844e360b073dc070fa05d08eaf20 svn.679\n1cf9d0e83cff3079412fd45aa2ee0cac66672588 svn.910\n1d0c7035c1bacb1f256e0a47534c19aeffeee35d svn.946\n1d26ca33c8b44c8c5e8524fed63736780a9e721d svn.1283\n1d30cfbd7a6696dd86c635b3faa263e95194733f svn.991\n1d3e14608e450f1c05fbbd63e3bae8fe8498531a svn.509\n1d9947e7c68242f9a8cb1845c22199d3aabf2dd0 svn.1065\n1da8c1d4b86bf7fe632bab0fa19bd5a0860b6414 svn.909\n1db0d4260c049fa27e83c262405e37e672f09f70 svn.142\n1dd91bfad08fe300ed3202e0b9885434069db507 svn.1446\n1e39a8267b63e6c937d0d9bca0be7ba96e884d9a svn.1214\n1e4ad371a3e51fc6cf8bff8832e307408ea23fb0 svn.1443\n1e5b976a4d43a87b53f6a10ecea1556ede6abb89 svn.729\n1e5bad3af0c62d39a4ff1c997c3d69ce6e93f739 svn.1082\n1e6351ec0582687c7177e9cdca621c18d9c36df7 svn.932\n1e6640c409dd5d809c24df8a1b296359ced079ab svn.1708\n1e844764a83b00bae1a1990b8a30fbcac149de66 svn.1015\n1ea258dafaa4ce328fb224cb7e04b670e3c0856f svn.1239\n1eb85913bbe94398eb973722914de95b28a8f69a svn.902\n1ec3baa5fbf66a9caca0a9b224563d5b28374b22 svn.380\n1ecf758054d3328567440ee82fa62fe0c316deac svn.1790\n1ee66345f10b4429ed1c5b5d3ddc7fd63f05c0ab svn.1710\n1f122ef5ebd5f88e6cc8bae94fa39db423620120 svn.1233\n1f182f60f8208c83a22f0d617941a4d1408b649f svn.376\n1f2849d4a56fa5d7909ecf56a1c5643fe76d7e34 svn.113\n1f583c98024f4582ff3b16447f439663b4ae287e svn.1702\n1f8bab2426fb59f2f4243844626b3ca6b11d75ec svn.896\n1f8c62426fffedfd9471ceedc1096174461c97e7 svn.215\n1fd4998e77ffd91041d43840b7a70c1be34cc759 svn.611\n1fd4b02732eb20bbf3c3f5c4c46d8533f14c166a svn.349\n2048acb78d9f9e117a0841b6a9229f01f56b063f svn.972\n204b732a1ad80ad9f6c2f6e904e418ff1556b23a svn.1244\n2054ca11283e92918cf372404bf773e6c09ab140 svn.782\n206c669b2af6d8c568531423ce447cfd2fd1e122 svn.96\n208f087b08e1f353ea1d96d735e53c0d84a2fec2 svn.1218\n20d3e642f40308d6ce2bf2352d19bd60046bd474 svn.30\n20d866b7ac2534392e54ea71c724940a91e926c4 svn.386\n20e6708ceec570278362d6d32ad64228e8d104af svn.774\n2105424868d0ddfaac6c68356bcc296903e5e2c6 svn.1243\n211283d83105ce19eb95f5c9f26b2b8e9577ab66 svn.756\n212bf834fc7f2a96f407eaca2fc11107a28c7b95 svn.1061\n21a01aef37253089ca9607afbe6bee41617cee94 svn.795\n21ab4b3e629c084ba8f6e39568efc7cb0bea893c svn.1221\n21bfc577a0793032cf3180f3210b80be465a9b47 svn.1597\n21c80a55ccdfeee4164df15049b6ac883dd42886 svn.591\n21ed3bd276caee7cad233138cd85a77985ebfdf4 svn.1184\n224c097dde0c7544d7aebd0c7fc4f3b398ee4ead svn.564\n2260c13777ed3b984596262a7abcf3d74949b74a svn.1112\n2267dbefda0b97873e7e212a9ff953f123c79dbc svn.791\n2283ca6161ff49a0a06f25cf2c4718d3f6a8dd6c svn.1461\n22d90c022f15abbb36d598f0fc12634b7922da39 svn.1030\n22dbd0b1ca710d8534f6bcdfc3d473b1d6c3e0d4 svn.325\n22e6c80fe419953eec283f3f4583ed609c9f4d08 svn.1654\n23262ad8a41ebca12807a1ca65b1cfd95c2d076d svn.1160\n2384a711554a63d4cf9e354848065244b4731419 svn.1503\n23a1a6bf68f52048a0831f583593073751306164 svn.1127\n23e1b62af018845944937b5cca4a15866647e644 svn.1223\n23fae50a1832b41d61b3b0a0a2fb1a8f476821b2 svn.666\n245b211eedfa9ab0f96b24de757b65435075feac svn.1185\n2476faf0f3ea9fb561ff3789d4324e755524f73a svn.1458\n247ff6398810d16538afa9b8cba01c792edc416a svn.1068\n24a8cd53314b5547ef2e524c77fee58d6334ec8e svn.1459\n24afb3cb63a0a1d7f539c942e746a0746b73bcc5 svn.1427\n24be36f110bc8c3fb2167e74f09f7a99f6d20803 svn.272\n24c57902ed27f8cb3a4ca1aafc3b66fbd44fa68e svn.1407\n24e7e09a8e48bf287b911e5e71243a3892427107 svn.913\n25056d70ffd70f590ad36853beeab02ab05b6a90 svn.1303\n25064dde309e79255dc027ccac12860b311f97da svn.1264\n250cd77c08d85ebab76ecc9e4990cc33ee344767 svn.1293\n257c5a7b6c8ef95ccf53da55f692f517a28e6342 svn.282\n25d4c96964f04c8790e12ab8b8144939180ba462 svn.922\n263dad5f5c2b1571208e7f52f8488c0f189e95a2 svn.1796\n266d5d83cf04ff59050e884077f9dabb56b65b2d svn.179\n267d7c34121d8fc3579ba5d148e7080074a0e927 svn.772\n2724aaaa917db3e5d80d8bf547aa6be9f0f2d17e svn.401\n2733c3d4b6355f77b6602b72999a006407d10e3a svn.571\n278c2284d6f7e979a8f7260ea27bcc450303f8d1 svn.1337\n27ba903abf81b7964ae2a50d051ba42183ae1461 svn.740\n27d17ab221f275c243449bc575417a25a70474e4 svn.382\n2815181de43f5b2991aaa55f3e1b86b03d57f892 svn.213\n2824b91af84c42c2690c935dea1991d87b6d0302 svn.1576\n28661086eb26791d5ed1817b47d2e810da311a98 svn.914\n2884b80f3126f6cae7b874abf66b88c81db8db6a svn.243\n2890691c8b0ce589f0c362b1867e594c3ec4fd0c svn.1424\n28e7cbf47cf394681cdc554f46ec13f67d42d5d3 svn.513\n28f9faaa116b4e0c8505883f73160aebecdd49a1 svn.35\n29101d2bc32703f921c90b8c47ab679d3ff1efe7 svn.1600\n29243d349999d43d02851a450f53f031bb40cbb8 svn.803\n2952becb97669b5b69c1d2d9442df916f472f408 svn.1416\n29694b1e1a5ff373db84659e392c64e74933fcba svn.249\n296968b4e2bddcf37cdebb652b2eaf9d662a87d7 svn.1623\n2977040634d83c6aa28ab1cce4ba9f397ff6f18f svn.63\n298d12183d3757c6439d66ef136852f7fa5c30e5 svn.1451\n29acc14771291d9456c9dfb72967a3232b50595f svn.1421\n29b2b49c3210d1cb1b6400dba253957aa2fbf497 svn.1711\n29bc4b09998c5e58e5e881617d6e4c0735bccf47 svn.526\n29e4edf612fbbf9815d8d674f899d89bd19c72e6 svn.1397\n29e77b0d7290dc0add9223c50a656208ca270bc4 svn.235\n2a1293f17651f5a8b96b837366b06e7b366d4406 svn.1364\n2a5783e9aef1edf1a2f5dc9da94b20ada176db28 svn.1614\n2a6029d1a826032c2bb80d38023a6fd37e3ac895 svn.1181\n2a74e5b6ed96bfab3a334059fc6fbfdbe4bda782 svn.583\n2a9a084f0ec1c135287fc824291cf582e2763d1a svn.1808\n2aea310b02d8eb288828b1e38457527d03a2150c svn.1202\n2b0b1ad509c4507b1ee6d4d7215c1dc13f86d323 svn.562\n2b3f5df5d18db2808c64b9fe865af6d081b1b1e0 svn.1351\n2b53901a7520aa476ed751bd0269fee64c015043 svn.480\n2b5da1b0e6f68f6b24eee859c6a5c073b39cce79 svn.1607\n2b616bac66eb2e928e58fdcd260a263cc1e6af77 svn.821\n2ba2575991fe70542ec0b1e6491a401220e707f5 svn.467\n2bd4b3dbcdb6edb2b971bc78f584ec1534010a5c svn.1460\n2bfef661b95619faa38099f97e303917441c9a44 svn.895\n2c2e4f27039f9eed5ddf9d08fec08ae079f3e1d3 svn.1609\n2c775f41d49b7525484622ccda6033e0497c2cef svn.1486\n2cf4d4f200f58d1d4a38eb10f25a305da25dab74 svn.788\n2cfc0b9e83e1bb5a71b274b1293932c25fecb268 svn.201\n2dc572c5ba89d3e2f234adc0e90fe3c379dc556a svn.434\n2e05790c7ff265722b5e610fa8b4c902dbfdbb81 svn.237\n2e114ae2bedf49862289dee0df21fbc8961d58df svn.694\n2e36f21bfbec38ffe5d016fc24a16fa8c08cf634 svn.1482\n2e56f05a2f5c2d1affe804672a96d4a64112fab7 svn.1398\n2e6c33866a213869ef4970cbb9a5fa4251246b41 svn.486\n2e9604d280d1372305aa1e451fb0ff366908070a svn.227\n2f3dc3382cf79dd18095a1d08ae7d86c5ee11fca svn.1242\n2f7c6f7c38ca0b4a2d59246e98f0785af7c586eb svn.531\n2f7c6f7c38ca0b4a2d59246e98f0785af7c586eb svn.532\n2f7c6f7c38ca0b4a2d59246e98f0785af7c586eb svn.533\n2fa342f70f1ab638e7ff6d29ba0c3f27fc136ca7 svn.614\n2fbffc4b553feb03c2f36b1141e0f272478768e4 svn.994\n2fc8ce9aeb98d5e20f7cd2bf04e74d717728b1ec svn.1228\n2feee8280510e184223912daf17898a6b9bd8881 svn.1317\n300b28b4879e45345ebeef6f215e8828074c6f22 svn.934\n3020489da36639adddce2b555be63c0dd19bd838 svn.7\n302bb5f79a4e895853d420e82d9ed656a71b9214 svn.1590\n3059f8a36287ce425e801b8af84ed6135be18280 svn.1594\n306126f9d0f1834277b17adc5b38ac567da555b7 svn.842\n30738047b2640774652e1a5bb403c52589ed9747 svn.1232\n3076d47821899c6ec96abd622a60ff6b6cda0352 svn.1572\n307f6a434baf2c19ed2f04b81559adfc32a19829 svn.1237\n30810691843db9af3ac30247b3cd2ee2983ae8cb svn.453\n309bf66bbf164ad0d715097eeed41a115c50021a svn.293\n30a8cf7bd40464cc8b769f88d51c8c1c3cc14763 svn.260\n30c8d69035cd57735a3f86c6dae40e4a6cf5f5bc svn.1578\n30c9e3ed1cd0af1220a397c058bed58c34d8acd9 svn.889\n30d4b4532a3036cf9839ac248d10454337c49876 svn.1477\n30e0ce89424861f1280edc2f0a40c4d6592b9119 svn.49\n3122e32e962449ef55ae4dbc65478e5af779c4bd svn.1346\n3234b6e46b7bc7a18bc8186d8645784f7a5ccb9c svn.408\n326567793ef0406d889de032e525a66d1924f575 svn.464\n326dc0e28cdffdab9e0cb8018b751a7f7741a93e svn.51\n32a5f8a01d909e39b60f87146383831187ae4b7c svn.153\n32d9ef9dafab130232dfdfd629de466a79ffa2cd svn.1748\n33d50969dd9576bbdf7c28354d26c854cdc2b3cb svn.537\n341577d087f3ea35d05ec6634bbcc956bdb20269 svn.527\n342b79f69394e1ce02d5dd8c24788127aeafb88c svn.1560\n34b94e7914142149e465a83e458b6c8afccd9a86 svn.166\n3510fc0075849ac1601288b8cfd516cc359a86ee svn.1052\n353817e819e90bf135e4577de32eb6fccee9670e svn.473\n35575098e21b39de79637d8a698efe95f61ea750 svn.226\n35609ba9b54cb9a4065d956fc359d49d6a1c3645 svn.1469\n356c0e2a81b234b2d99794f680e7380b0e1987ad svn.1369\n35759321ad5ba59bcc727870fa0b7634c0efc070 svn.372\n35bb46b3e28fba203eb93a8f0904e70b015978b7 svn.1284\n35c3413f27068c320d95f4201f7f0cd240d44cea svn.1360\n35f42dba0eee8a79ef8221e9032072d7f922eabb svn.1660\n3648f2ee0493f7538f15721cafb438e075da0a5f svn.768\n366a30324964d2c88c2731b027861897d5c2d98c svn.496\n366e5cd0fd14dc9192b82e7f5a18397d74e64314 svn.599\n367338cde81885a08621a3b1359140a3fda977cf svn.582\n3677c19e25bb9311ca1b714f7ad350347afbf323 svn.773\n36b3012bbbc073ed5b2c0de776a2738593167bc6 svn.50\n36b804344505a201df09da75721c99f6ba8a9264 svn.132\n36df8de9d0bc05b7ab6382e3e497b3ff92d4d2ba svn.291\n36fdd3794888b2d9feae5e5ab8392ffdaedcb1ed svn.985\n37246737f6859e4117c9a2d6a1d13b2674678cf0 svn.843\n3767f3d630ab1693ce5886653da52fe320be4432 svn.1806\n37ad56d4f517181aeff99df835d5e5adb0f85e8e svn.1639\n3864b1286cf26976ed94cf6a3ec4059edfe08575 svn.1662\n3885cc7d11fcdbe9656d8c2148bc07a30cdbf494 svn.405\n38e412a867fb28db8d00a4e88a37906c76aab33e svn.812\n38f78a2da49df80bbb58119a235811c50aecdad1 svn.69\n391ee7617763b5904068181ec383952cdd7ee142 svn.1006\n3951ca57fc0dae3a992fb790654b04dacf71bd03 svn.86\n396464816d2328b92330a78c22846c56e96a429e svn.1226\n39c43000ec6db4e62ff01772d7521afb796a8af9 svn.155\n39d85e6ced187b299c627269f0b6d52c691fb24d svn.1635\n3a13d92a0679b693d25142b17cbae857c79c0b2d svn.1697\n3a3a0158bafeb652c5e7a4f74552e180534d1572 svn.40\n3a477287d9d2332aa9a924d8282256751bb60712 svn.916\n3a8ff8b2db71d8537af42c5c5cb5ec968af75218 svn.13\n3aae41cd34983023a70726fc5cf78e8ffd3badfb svn.151\n3b037108ef0ea89914009142b1e8ecaf70e1121c svn.1367\n3b1782228da51a8db7c41c35791cfb503ed28a94 svn.1537\n3b51aaeb60e63b2235d486c3b01f08d6d7e99d7d svn.604\n3b7885d4f4e8e6923fdac19ce7a13996a0d53f06 svn.71\n3b9205676f5ac264154b4098908b8e0e81f03cdb svn.1106\n3b9a7834cb3009820861484876f55918f2475053 svn.787\n3beab6acbd602831fa8c3b0c7dcdb52b330f38f9 svn.682\n3bf0dc8a1b82fedad25e742882e1da7c137a53c0 svn.176\n3bfb0809df65d84e820c9d0ec5800b2569589a4a svn.390\n3cd57220b85ed48e04157f86edd19f22cb2f11f3 svn.92\n3d0e66a5066ff4fc4bce6c2717cb1d1ccc840391 svn.662\n3d2e410265d5d695bc76081e4d8bd8c9cfa28777 svn.862\n3d5d909133b1d65527b63a91ec0a331636775a88 svn.638\n3d7c445b1418482e2b20e9332c3f9b948c33493f svn.589\n3da7fe78c823506fa20fe57c772ad1842e922b72 svn.1225\n3e2e7f6475694accf8d6c8b796cb063c3dfac0ac svn.547\n3e41764d307c55c40e69c1d9fbb2240af170ef5f svn.551\n3e77557675c97a0cedbe4863c54eb832568b3c8d svn.538\n3e908dc26ae65cf3c9dc28c49421e578ae3fb49f svn.212\n3e9992635c90bf6809a116a9a91933f950113b34 svn.859\n3eb75d1abf615466c5b4687587d2d72463d8b7e7 svn.1561\n3ebca2e17e8605067905976ce45aa33fd85a2175 svn.1389\n3ec6641e57e5041411874bced46bc471cfa6f556 svn.420\n3ed79db929a9ae296e569b6a0a662b62c03f8430 svn.554\n3ef4c392b225abbaadbc9ec69a59eb6d38ae673b svn.1071\n3f2c22dbcb10b0162c94d32df80a8592d8f7cab8 svn.1551\n3f5d67c3b191ad30069da0a959492e85623437b3 svn.1133\n3fa2f5f309318b5467f0bbeb5542095a393a0578 svn.219\n3fb3e19746293cba3e51ee7dadef127b0064136e svn.871\n3fcf225f835cf7b90d8854b50c994435bf9a0c7c svn.34\n3fee01e8ac38487fdac22bffc36450a18035adf3 svn.361\n4018fe8a6af67d663dcfdc4faf477b1d513388fb svn.781\n4049858156fb8795c270d6815556fb10442580d2 svn.1322\n4056ab55e28813eaf3ea4b702ef1c5980b680088 svn.1532\n40c221438429013119d8d0c6ce987e354f6abb33 svn.1528\n40d77e888c26f923e7731771b92d93aaf1a9228c svn.494\n40e5005d6d7e1f70583840824cc1f80bb70640c6 svn.377\n40e5eb8bfcf1e9f0c9b0ceae0f8a663afce2997f svn.409\n40e8cd5a0a4e2d6d6c6a0fd53590d3e64cd08724 svn.648\n410babe5b4166e31f8fc82cbc2d7e8c156fa1e4e svn.38\n4118634b4393366efbd911b28f190d7260ce418d svn.1767\n4163cad7070177da69b53cd4b9887615da6b50c7 svn.1497\n416d80a781f033c2ca3cdb1243407ceec601875d svn.1278\n417ad747dd1f8672e37a5e51d936dd1052d3713e svn.1273\n41a5bc7c2e91022cc0b99bff3c27d028273f5c15 svn.880\n41b920254644108c6280e941dcdec28f213e320c svn.504\n41d0d10781c8fc39a11f8d863a6c60d9c73316c5 svn.1231\n41e6266889a81500a983fcc598d30c3cd41b016b svn.870\n4201883836c6384f77136fcea4899eac3da1112e svn.202\n420ce4ef78b82b520f2a97ba35b27d792b9d8d29 svn.9\n4213a3828fa82a2f928c979b6c094a14591f1359 svn.828\n423219f35f50085b079baff29646c18928578bd6 svn.1358\n4246c5028815bd713859ac7194531b0dafea0d8e svn.1072\n426da8fd1b6f2d0fb1aacd860b83814b0e6d7e7f svn.736\n427a362a641904fe9956779a75158137d13e648d svn.366\n42aa893a70233f327826b6b88d2595ec58be068c svn.1176\n4359c081ace30b9ee341a5e74af0060ca9625ee6 svn.1701\n4382b85f7f2976cd95d044e5ce3200209556f46b svn.901\n43b5b08e082d211c858fcefa0abc633fa2e2adbb svn.988\n43c47f66811d1c3c54ebcbe645151a6938b51803 svn.1487\n43f8a7df78eb3b1f54ab6557f32c72842e59c6f9 svn.1000\n44482c79545e737aaa40ad828e2b8bc9c32bec7b svn.1666\n4461b8732fbbf0ab6200a6fed0c56b995823ea53 svn.178\n447813e1e520b5a4789ac3553331a36889724e73 svn.1143\n4497b6c8a28f87678baffb8e5ea6a4ab076d3f60 svn.350\n449832bda2e9a04c55df98bd146942a3f77bbe00 svn.1498\n44bc0a7fdd004cceb19e556222ab6a22aaa10aa7 svn.174\n44d880af55ff3e44daeb87c3b6cc5b3e16bf64e8 svn.1008\n44edb74af00db2ffb08016f94b0cd399a5ccfd8d svn.85\n4515bae5095328873b3ddf34bbd4ccc16a4275a4 svn.1705\n451cbca5a3e5304928314da6573264ffe7e0e230 svn.406\n4531a562f716d16bf1eba3f8bd14ea9c2dd8a2a4 svn.1381\n45343776eb76b76496f01462fecfe65c4bc8ed8a svn.131\n457bec224e4b18e0ba380b6956120aa9e55d61e2 svn.173\n45a9d7e5fb62c1ba8ffebc8e5585e993b7bd310e svn.623\n464e7e577c5c69cd2c709d7fc9dc8bd15dcb16b9 svn.639\n4660ab2a5ad8048474c2dfdce74822da12cd5186 svn.1533\n46b682802892389cb8d3b8f9b89584f6dc565d9c svn.1545\n46cfc58ea9781625cd81fb960d2d6a59b96acce8 svn.1758\n46f5d3f78178e6cfc069079e32f13aecb310f24f svn.259\n4822375a0361431ea8760efdb9deb181d2418b92 svn.1661\n48267b66ccee0e9c7618b7ff1ce0e33a33b9e376 svn.1319\n4847223c05ae4e6a6aa1d144bed9a28463cdbbfd svn.1161\n485ffe9198e1bf33c5c3714e80734763b772e098 svn.21\n48637151be9336a68446115339761b22bfc74a79 svn.1426\n488e52ad69c3f3004c4d88e7826894ec3a2a776b svn.469\n489b2b01018eece9a3da626ba0ac2a5e5a5ee68b svn.1041\n48d00abbf8977b912efe5a52bcf442e5ba0474c0 svn.207\n48ee2d9c752ff8436f30f8ad270beccc5a34ef1e svn.1247\n48fa57b1bee688c3523a5763a52d721dc2af6e0e svn.1678\n49045125c9484e0a60487b1cc65a4db189e5cbc0 svn.750\n49218e93b171aa3ff698c921d442b52bb150d487 svn.125\n4969b297df682192273b2e7514dfd93b496a032c svn.1167\n496d7770984285a5db947fbbdfe4ea77802a0631 svn.938\n49712828f5b40038a56b3359ec73397344a413f3 svn.415\n4981eb69b4f367a74842be377dfc8bbd4ca2b762 svn.1115\n4982554bb736c2985de96401f31aa6e3d9fcb433 svn.1734\n49bf13b03356b24d99eab44a38fdae7631a1c41b svn.1043\n49c0c0aa33ab9f6222de994a30c1c8c9d71a5f50 svn.552\n49e282f62ffaec2781f1c4b32a7ae145ad5e18da svn.185\n49eb634fdea7222cd9b28dc9a57b13158077c999 svn.247\n49f6e043889adeafb8b36acb4fe109dee6eea2d1 svn.1810\n4a01a8ab2deaf4b6c368469064d86676974ca5e2 svn.1333\n4a292bf63670c8a83efd41c939a7402235918b48 svn.596\n4a2e5c12b4a23e7d9699c85d4e40b2b0cfbaed1b svn.33\n4a9245b8d1230a4aeebc5b996401848ef2e8aae7 svn.1737\n4aaeb47d11c0f86a6e2ca3c1c3711527e23bd284 svn.27\n4aba19b0b4eb4c840aef00f9663fb1b825b48d6c svn.208\n4af3e4b9f35b438cd71c732c287b05904b0fc508 svn.1109\n4b6e066a9b733af6adf096779d72e14875afc15b svn.357\n4ba988999535326cc253a3049470eefaedf87f90 svn.1722\n4bbb3be5be5b43f84916368d762c83b0698dad1a svn.1454\n4c3d3810975dc9a0948a37e033a3d049658bcfba svn.1743\n4c5e134b91396e80b184dca51144f39f9560343f svn.1549\n4c9e1ab18d0878e9e4c7d551c13cb68baef38bfb svn.1012\n4cc9fa1449811b09112e1e97089be590b7444432 svn.1476\n4cd40c4e44534c0f3a173874891b6dc192302cea svn.18\n4cf5381992a384aabac16831800c7410ac6929a5 svn.115\n4cff5bde76e2deac628cda176628a3cec5a66cc3 svn.645\n4d3cd3e470113b37c3bac7d2052d79d7a097ec96 svn.246\n4d58ac7afb5ee8358d6989989ac04602de8c0a45 svn.342\n4d93b8fb4362acb2626035c0aa12516a81d90fbb svn.148\n4d9628abe1955d19050a2d86dcaf0c4ecede96d7 svn.1077\n4e25a3ec9fe26987dcf14d93f5f0b0ec07a449fb svn.502\n4e7029901b08b617051901d95a383f6242eaa88b svn.1382\n4e74cc5353eb286d9f6697636ceb4c35c02fbcb1 svn.1632\n4e99c598a17d61bcda0604b2a9da8488bc429a2c svn.1695\n4eecc5a5eb0ee949a906d633e73567aa9642e889 svn.1215\n4ef9e46137e79825ad2edae673ab6ee70bc7b44a svn.146\n4f283440ba805368be141e02ea6cf3ca1544efb5 svn.1241\n4fb515e030cd9960c13e228064d8f532653213e8 svn.1134\n4fe3cf7dcc79f8ef6cf60148a573008dbd1b5b7a svn.1402\n503116d6f3e29bb6baccdd808054579fcf9cafa9 svn.1799\n50728409da5a1c155ffafaa2d0f917332766ce91 svn.1749\n508bc08810045e1cf1e6970513c9de8fa028846c svn.1430\n50b1a45d4899fbe5b643fbcf780037b7edb25ca6 svn.1354\n50f6b731870ba3ae773b772050bfb7d27db2e4c7 svn.1615\n511379ab4cd6e5e5ee4144890f7f5343fb87aa4c svn.93\n516801d12403aaeb3be7a1fa3418b4edee09198f svn.218\n517a757f2e616abc924087c336c6a28192649050 svn.1209\n520c515ac241d8ecb87692f4ae5c5f2ac5fa975e svn.835\n521936dbc055716daa6df19efa7e34e857271f05 svn.216\n523845d265b6f21192bf920428ac9a8d7dc066ee svn.903\n5257ab69b04aba9315f60d276ba5854b2f182174 svn.1323\n526d95c3398d2d8a7d6442693704b93542b1221c svn.1102\n5280eb09b8f1cc2c84bb785b19ff807a47254d4b svn.1680\n5287daeb9be1f9c97319242ad4d2c226e266341a svn.1101\n530003732acb6b8004875550c44824850136bcf5 svn.451\n5305e65e9aedf67da3f8028255e21497cbb844f9 svn.77\n5340452bb3df18ee1befcb4daf62b3ec5b9c88c1 svn.66\n5345173dbe7e26057de5a7fc46e787d47495cbd4 svn.1512\n5355b66f4dab824d6a9dec27432b5993ff3d6ccf svn.545\n5363b892e7a0402e9417eb7b3f9ce23ff6fc2f6c svn.686\n5365f158410c88448e3bc294bb15823895be3b07 svn.435\n53cb10663fef331a2252a4da6b96e7e41c5e7474 svn.1620\n53dfcf3deef8c8fde2404b620fcb138fc6d2ec13 svn.1754\n540e50771aa0b70808479222e27dd4239b3c8612 svn.879\n5412bcfdf5df0e75f91f7c242f4f51a094efb62d svn.1004\n542c2f66bc5d0cfce5272b1b4056f097b2950e24 svn.1573\n5504d339253a7af01a6c9005afe29ca7cbe705f7 svn.5\n551dde528c1e313b6ff50bf4d79e3b18e7ac0f31 svn.809\n55e939ca948bfbb641d4ff5892a82280518cb0db svn.250\n55fecb6fc0d3ae3f40cfeded90d1743f595a7732 svn.400\n5616db9bc36e94343e2b92df68c4520d62b38cfe svn.60\n5620cdedb5130bbdbdccef255791b98e83bafbef svn.558\n563a941879b75768d6d73b60277c85964cdf3f60 svn.4\n563fe5a94cabd170cc0cde3ae3d3989a6d1141f7 svn.1110\n564a081accc74d3b805b393d5fa681607f45b916 svn.917\n56931af41cceed819df913b9b2493682405e941f svn.1098\n56abf9195ec818eaa2e835c5d6e5eab7b8f86026 svn.1301\n56b1bec77233c6a2931ae689f41cae9653df9256 svn.665\n571cb5de9bb60035283627328e04e71c7b4de775 svn.1150\n5740382d416f07ccff6d90fae77d808657351836 svn.438\n575c627130dc876053158837c6256830f3fb42d1 svn.850\n58132b46dc40cb7e24fe03d86014f3075df28110 svn.1728\n582f665ff1e445d2fd9d13ab5b4d664e870ac93d svn.874\n583d35ad9048b887d4c133101ae9c887bf6a3ee6 svn.629\n586f3a9bab88e5bec339980ab203fa5158f77632 svn.1326\n587722d62ac08a1bea1dc44bd3558ae627be6b13 svn.521\n589441fe2a35347857e3eee6c4b34f6df60325b4 svn.106\n58c0b0001072e37b6bba4cc3f388988c596ccc5f svn.193\n58e27b76bcc5f805251ab9cb27b3259862b1e9a8 svn.82\n58f2a83ce4bd569a69dec333810dabfbf6f3d4d1 svn.163\n58f3151c7be6b192acc553c9231d671e11c45d49 svn.1760\n58fdd26cbf5ac4b554a0cf795d21e137c9321734 svn.230\n5910084f8704411c3302f85073b08323465cbb6b svn.1261\n59394c9e003e1ade5b90330307f96520b8dd7f67 svn.1515\n595b63f60ba812ff9063a78734ed181dfff09b83 svn.799\n5977f55517263d38698d0fb3568e7e9d0865ae23 svn.987\n59ee86c8657bf5db81aaf4051507e4c330d5d54e svn.362\n59f08140ee87a987e11d5d5788f20f9e899817c1 svn.1627\n5a007c385c4a933cb1efecbb1c5c189f479775d3 svn.945\n5ab8c1db3dbdf9f6d302ef17da48942a289c1583 svn.1013\n5b222b5000d46e6ea65c7d67d6fca5f3a6f5d483 svn.1070\n5b636e141f7273bbe593163202da5ee626b65c75 svn.1388\n5b82a0336225d5317d86d3cfd63571c3676598eb svn.256\n5ba2e572a3f18a22cf11eb07f5c857856f8104ab svn.989\n5bb22493e5e79a48fed30ff120d32233dbec294c svn.391\n5bbaba649fbf8628a729bc6a208b791d2ec092a1 svn.680\n5bd0a4b6acd292e5e40527a43aac64379c19fe48 svn.1410\n5bfc0b24ec3c698ac3e0da0954df1f2d68404a86 svn.29\n5c269386e9fb9dfbe0b3a622d0ac2a40eababe1b svn.954\n5c3608dee30a07df0460ddac10b5ff333e021de5 svn.327\n5c4990c765ac586cb341b41508300c5a88b1126d svn.592\n5cea2a1eaaa054e8e4e204eb27117ab3215adb25 svn.659\n5cf867e52b3db29da760ef52ce2f5e8e8211d3c2 svn.1535\n5cfc7ef09092f692c0dd25a33260952db53afe3f svn.266\n5d563977f72716f03f98049a416d315636723103 svn.621\n5d6603bc222175178fc7da0c5668bc4c54ed3021 svn.1394\n5d7dba2c1c644a3715f716d1ca260db24a768637 svn.275\n5e047513c0ea6bd422ac2690e5b5dde28d7b0cc4 svn.1419\n5e5997d5cb03c6c7181fb513b07850d524a5082e svn.1585\n5ebb6b6af2ad27ce2eeec087b8963239a155725d svn.1669\n5efc0b3122438f19ba247a468922513ff3dc3f4d svn.635\n5f0b8e82d58651c951c356a705e3e4526ba8dbe4 svn.580\n5f4ad86c159e3397e6187c9fd01f229fa72a819d svn.1604\n5f5bda1a0b4af8978f5cd03fdba4d72d114ac1b5 svn.713\n5f5ce1d0313d39aba65fb360c2b43b0f2b6a516a svn.1116\n5f6ee3f0aaee445dfb9bb26c8dbef30bd4f2f63c svn.681\n5f86cdef685f59a577fbf8b91ce8b6419ba83f1c svn.183\n5fa4de6cb71b9e6b9827a25d7612fb4faa0a51f5 svn.966\n5fd9f895384805dfd3ebc54e4b27f1acbe452064 svn.140\n5fe392f15c2ad1970936acae1c40efa804a71276 svn.181\n60864a7b7dce7563b202c1406adca49d761c6504 svn.1276\n60d13dff6a0039f4fa6623a034bd94db7ee7b9ad svn.764\n60d1b329e1a41b4518091df6131b84b0ef37ad90 svn.542\n6131ac5ee202b715f9aa5851f86e1142ef531877 svn.1158\n6146454a0fe14b8258bf030278a01db0f1df4404 svn.1033\n61df1dd85a78eb81890b01fbe2fa91ef4315ecf7 svn.1593\n61f2232514ecd36ae07e1e7d4c199c207de9f047 svn.190\n61f44c6c06abdd3a459eaf4cb84e4ce5689f5b60 svn.845\n6222bb8d5b37ae0132d8ebd7bd4b7a48db94e684 svn.1703\n62631b24fc1de5dacd71f9e72db09763c3cf6268 svn.573\n628d1287a686c1410b25f5911527f11700800d71 svn.1556\n62aa37991851a45af59b39bca7a205454c62ff4b svn.1032\n62bc90b08b309e14eafe31ce70ba0b4b50714a10 svn.67\n62db9cb4103179bdc81fc4aa6dc2c06b51ecdc15 svn.117\n630f4d0b6c7f16d54298d5d9950cdad53ed0cb6d svn.1251\n63150727b1c7fecfa9ce6464779818683fb19487 svn.905\n63283a4591bd46de9a1d814d068f68cc43a9c976 svn.1120\n6374e52f7c0628aa7e52154913ccbd9fa9c24330 svn.279\n6399da8dd8baccfe0d3c23e1a79e38e6c5830f30 svn.737\n63b2f81eede89f8ffca37bfc9346336a1947b862 svn.1659\n6404f6fc8d5b21a924d342f6acc3cea816f7f23f svn.1755\n640a15ddafccbebe106d2b21793cea1c68946a49 svn.1055\n6468c80e3d4a0ee4abea5b54c373e48d503ba593 svn.1683\n646c70e7e5f886dd105d69a66acd1f5c98bcc5c0 svn.72\n64a713646ea00b15ff2939c56b194137cac32e5b svn.442\n64d34a6c43b5d5ced5ea8c59070b9c2c10995f8b svn.97\n64eb4a5f8d329069fb4f3275806980653f0d2276 svn.229\n64fc2c2c26c8db2336b1e0ed2b797f59a63a742d svn.784\n654597a4199eed35dfa820947a59a7c18d723474 svn.1113\n654ad8eff684ed6ca368a00610e5dc2ce86ae3ff svn.970\n65602799686748be921aac98aa0c2ef5a1fffa2f svn.723\n6593d04b9f7cbd1f04d6fd89f3a999c9090caa9d svn.1526\n65b6222af795244499cd3c0202b5114dda0045e0 svn.1801\n65bb67f50da97f1a23c3f36bb9b62496658e9548 svn.1570\n65fd080af335c1b0e73c167521e24e9fc48c5d1d svn.1584\n661648ac517628eb254a31e2bc703d92b999782e svn.1294\n662560be753e9b4a9623791070baf0b86f1b84d0 svn.1488\n6662340b2f89c68c21db7a8299a7082e1dfe8d76 svn.1727\n667d55685a4a9007c7a142f2121fd1eee585b93b svn.727\n6687e9fcf14eceda46be690d854e88c645ea5f04 svn.1691\n67096d5c2f230606afb316251be252c698cdfe7b svn.609\n6723708753a0619323dd98434f752422f0a02cb9 svn.1325\n681fea5f217b1434801bf4693c5d891f0d0902d1 svn.883\n6859b5230d62685f9a1be0e18f4f3e69b6afcaca svn.908\n686a4b9d4f6b43f78a50735357253ab86c8c67b8 svn.1080\n687342e70dd55711de85c904f60f3b15f291f60c svn.413\n687bcd6b35ab8d1e106f0e92520641bb20c6d464 svn.1025\n689b60640d60e1a36429e6f979a6cc06f2be0a11 svn.1686\n68b5418c549309274ab2caf5872b1bcab3ef89ab svn.937\n68c17c12fc55b70c72ff10571f1dd1b0bfec03ce svn.1207\n68c3e7c4f2039b94b3b35150d1f5bd040081359f svn.1440\n68d2052f11c58ed5274309bcb2fabdf46cbd15be svn.566\n68eba7c3b1dc0b8f9a546acf7722c9b7223919f2 svn.236\n68ffb81df06dfe917f1515bbf66ec957c53ee51a svn.520\n692c0b3e97df1a6324a0a97dbe792bc602c499ce svn.172\n697a25de00456d499e7e738e8fc8e7998621b277 svn.617\n698232e5ae717bd73a966b3d7534de356230423d svn.801\n699ed7fe298ed364faa6b6bfdd9bf07705f3ede6 svn.804\n69af6d8252c18596851585164364b63034963663 svn.1311\n6a4c7518d9d62ec15209325b3332b4c6be89801b svn.503\n6a81cc9d3b086744e03f22ab687f2b3fae123b72 svn.1220\n6a9c3420d3ee2c6319c5c3a754bdee278d1c9373 svn.130\n6b089c0f0a634d5638c21a3194a385592ee0946c svn.1362\n6ba57547250acbac6bcff8e2f8938e7e65125ff3 svn.605\n6bbb4ccb07cd993eb5c92481dc46b1f73639c10a svn.717\n6bf4f8d5eff7a64b6d905f0255ba9f79c492851b svn.948\n6c1246ac86ccd1d01c56e80d256758b64837e72c svn.220\n6c545b458dc785a1d22929fe38fc9ef122ed3e62 svn.918\n6c6edf1ce01f47f9a93ced398c14e44e287a65df svn.1653\n6c81db75b3713c0c889b70aca2e1f79ca358d0d1 svn.931\n6c8b5837bc619afb424e773811a2812ce7e2d1b8 svn.619\n6ca95e61d7bbf5829ffb21a4e53569ac9ed9109b svn.239\n6cb0fd747334aadec07001c234477c6c432c8b9a svn.647\n6cdeac9c78459431afd34a2889c791f8136caf91 svn.1694\n6ce14ad1e51f11d3a12f9e87336a824027db1d5e svn.691\n6ce695d22d29d5830eb088c3ed4e8b0686e8161c svn.919\n6cf9a71903dd8c412769486fd3e49d883f0a662d svn.443\n6d2a962a88f101661e49a204a2b927b1ff691f48 svn.678\n6d3b895da46ff0df28d07ff1b44aeafab92420e1 svn.632\n6d619bcb275f9b26471116f080ec40806fb39f57 svn.1287\n6e103b5715f877a532d24467a72ce798e14d7917 svn.1406\n6e1088ed75b3e709e1c1303d4a6174a14092e315 svn.650\n6e36c78fa82cd595dffb857cfcc61974596ffda5 svn.1547\n6e4b1b54fb1655d880c73c0b586fa9846e3e8406 svn.1688\n6e7bbb959a509d936fec8de148068fd5eb4e0e65 svn.1587\n6ea12918249c2998d5322490e2d10c2ebfb850b1 svn.837\n6ea7649495f915e64fa6774b9c3432ccea684619 svn.381\n6ec1218a11b3cceaea156fdc3925b95b5c16ea8b svn.915\n6ef8fcc1c3cb89dadd48075eca10ecbf1521b343 svn.1081\n6eff9c0a4e808a63f5a4ddcdfef919e04d289596 svn.493\n6f38cb6673f788712881c4f21a4976347a4704b1 svn.1511\n6f4eaaa712be7e5e560908b01de6e21a64dff80d svn.127\n6f67d9ddf8d17e4274170ac479cd38e8eeedd5f9 svn.1142\n6f984ee68a329be4627566d3cfb74e902b9478ef svn.46\n6fc6d87835f7cda31110259830cb290751782550 svn.1641\n702eaddefa978d541c1827b8aaccadd1fb936637 svn.433\n70372e292703c1ee793ee744230c04aa2394616a svn.780\n708164359b80fd2fd9ab08eda67d8cd12bcae4b7 svn.343\n709dc1862b92b31553ccc62257d14a1e7d94df39 svn.923\n70a1a0a74e029a222b621a0b51d34aa1eed96c95 svn.491\n70b7809f00af3a82fbe52f4a31bbed429c57fedc svn.1403\n70d83691afa7c302cf779c4318c155711f18a1c4 svn.1657\n70e2a6cfac20b5db6fad69c089c04fe85c28ce66 svn.1539\n711eff34e373cf3adb738ac08b5e3d6bef81167d svn.1349\n712da1b22a9adcb3376d45aab846fea2863dc643 svn.677\n71d855ee2ff5db8d2031e21ef1110f8d1e25d349 svn.947\n71df7de83845443319030e411ca63311b94cb2b7 svn.1478\n71ed0fd7b8b04dfb59e9d042ad5c16fe03e4603c svn.17\n71f40c26df79f4b2abe46202b8702e1e7401f1cd svn.311\n72142f3034df364a1b4c6744029df460e5dd08b7 svn.708\n7237c3e3741bccf2b48c8761b29ec61f7a74140c svn.1453\n72f6e65b00f97472581f40f699677f3295d02f74 svn.1274\n735372855c47bebac5cefb53729706320c3e5db5 svn.933\n73595cab8615e185936ee4f2f5ce8ffd0ba00193 svn.1093\n736b7d80d82e8c49990daa69d6ee152c3f549964 svn.1246\n7394cb16a2a56787d29fb2c1238cfd4375a32609 svn.578\n73a5f43fbe5bf5a022fbaff1c1f681759b24caab svn.1665\n74a4cd9f3ae216250fec79d31d996cf1ca77b84c svn.969\n74cac79b667c492030da162ecd3d1a7dc67f1a08 svn.101\n74cdf93273b4464daaeffab3a89d3e78da35bc0d svn.53\n74e0d6b46fd1d62791d1733a06eb09e4b8fe831f svn.240\n74f83c44f9a21237d01ab53bd7abb8b8c2d69e05 svn.1168\n750a31f01a28f9d0b0d10acbb3ae2c44443484f0 svn.1562\n75210b13d831d6480f07f3972ebe7efca4518663 svn.876\n75309fa251bb4ff0900cbf37a584ec031ee33b16 svn.630\n755d312a7136f6f5a569fdfaec07ad68bac257bf svn.1103\n75a2b0dd253f65fe75245d39f99497dad64da56a svn.1085\n75ac4861c335b9aff04c88be698b230090b03670 svn.839\n75c736efedb4d64cdb5f027e91ed8ad21347dd2e svn.79\n75d8c13524ccb9288d4da283aa7ba549b2a3abcd svn.1761\n760e0159100ea5fefea7242935982079f2602887 svn.1794\n761f86756a571580948a3150ff87221385366a3e svn.1558\n7666c6bbca4e4e3919706326334cae0053f31b16 svn.766\n766e39ca35babe04b4b3250a5b4f0376ecdef01d svn.1746\n767a773a9e84eba48e07336691f67c1edbaf5548 svn.671\n76979776bfc1ec51682e9b97fbeb9d17ab50d32a svn.335\n76e08fa2a955c3e00c1fd0fee294d843aafdeaa5 svn.448\n76fe7986694b008839ebbab7f57fa496fdcf6d25 svn.1189\n7729282bc88011ed2fab9462b4b31098d0b83b82 svn.1079\n772a2004987d3cf59b96d2d9b2975616af016e58 svn.296\n7737bf992702e8ea55f9b8c9aa445ca679eafc9d svn.806\n773914366850d4cdf88ba6357f438cd2427f4756 svn.1090\n77480357e062086dfa46e00a21d21254509573ff svn.1010\n7752741ad8b4c001ade2fe52d850be728c9a6d8f svn.576\n7777f6ab1b807c8ea8374e6ce51c7e23d1a64a2b svn.735\n77ad4648ed8d7caf993cbe9a0c9fa98cbd0c7f22 svn.1429\n77faf7f8d8624cb1cc71a9abbb389264633f245c svn.1599\n78684d161c28f74eb080c8e347cc07affe5e2811 svn.11\n78714479ec4b1655ed63ab753e7eb64194bbce49 svn.1731\n7888876baa70393e25dc7ee3f5d5fc0060cbfc69 svn.396\n78c7a2cd4c6f48e839680c2e0768f61452a1d69a svn.925\n78c9ba1b013b12576663205993f06b43339a75b6 svn.31\n78eeba413131359509eea37964279674e88921bd svn.1664\n78f011aadd008a23a3cf1c16e659a7bfd01f0b80 svn.1525\n78f0aea62cd4526c40099b890429cda11414d684 svn.1213\n79375d6353efca8288ef5673c48e7c465b8a72e2 svn.1003\n79bc3d944298a10b1621e3dafeaa4a46f6d18069 svn.721\n79cc0fb85cd94fb15ea9e2ae09c44c638355aa05 svn.43\n79f2c675f9dbf256fd7c730bd00f742124be519e svn.126\n7a38f6e17dc8c0350c274892ddd761bf5b35cca7 svn.310\n7a4795f2964b41b738e697cce7cec0bbeef758e1 svn.1009\n7a4a905ac83dc3dce44a6b8954221e7a937b1b39 svn.936\n7abdf2775d80674580dcb2ee742af8ce3a28bf80 svn.1439\n7ae0e86d0ae77ac1e40bf9fa7b42cdcebb32ea11 svn.698\n7af1b07907d92d18a1891139956cc0acc63a34f3 svn.1339\n7af46f2766cee10bb777ec28057d33027f54e582 svn.1148\n7b093458569729c245fcbbcff0264a7ec9ddb0aa svn.280\n7b3988fc85a4f3547220effcd9fdc01c85ae29d3 svn.1258\n7b4ea78d32f125a8ed976d98fd7a8270c61ba145 svn.265\n7b79ca45ed746cda3e4be06913a4fc938aac2f2e svn.1169\n7bc1265533579cb4e7542be7376d77a2bbeb42f2 svn.119\n7bea2bddb35fe07eaeea753cab6edc8486df96b3 svn.1286\n7c29c4151f71a24a11af92a03468db6656575db0 svn.1792\n7c2a177f330a773e3378db85d194dec33f84043a svn.1733\n7c341522bf86c7e10bb655f7f6314aa34eca4b16 svn.465\n7c3e587ecad52c6e36daf00795fdb3f7eaa065b2 svn.1616\n7c5785af82057e3a3854e5a3f757f9ba0b7d3d63 svn.697\n7c73ea93d3baf7668938452a5c6ec66aeb8288d8 svn.1105\n7c7d8e04c7806fea442209f75fec5b5b5d753c94 svn.831\n7c8b269f72c0cf2e3a672b0d21b931b89776e4e9 svn.10\n7c903124520810e58dcdac13bd2ed621e12f428b svn.817\n7c9cac9f2405fbcf56e45a7a557b1ac8062dfb19 svn.1050\n7caae7bbcb13c6b8485c0db265f7f69dbe1db4e3 svn.1368\n7ccb68386f7d9364e65edba381b48fc14050b311 svn.1781\n7d0f2b21b8275bf67204c564ea9ac3924d09d24c svn.1066\n7d2a9e665687315a5d1d69e5e9376442f79dae77 svn.1235\n7d2f5d03022601376011f87f26bc0c36ae0ec1c6 svn.1315\n7d309c66861fce2272a43bca230d4ba836f73883 svn.1763\n7d6ad92040f2d8b47aaad7c7abbfff718b66c57b svn.431\n7d6cf76197562ee72b1974a83a30f93c82435a2e svn.471\n7db82d6cda20f252f4733db0ef9102d6ec286e06 svn.489\n7e0d7092347ea2b7624d564cf13d71256b0b60ec svn.452\n7e2d16dd5cf4f8d8172cc7e5751439aad3ecd447 svn.1418\n7e2e758ee8c97c7ce30278510e2a98f7d6ca395e svn.1341\n7e45cc2e299134dcf3ad2acf71201fe776d67454 svn.1752\n7e499a54d7b873e421c947ca39dbefaefaaa320c svn.598\n7e6075aa2eccd5b67e3e5b7b315d2278b2e935e1 svn.1345\n7e65dffd52574ce677cbcef4ce759075cf3fda5a svn.960\n7e71cef8e31d2c47a572349bfc239fefac96667d svn.164\n7e7368e614c8032e21271a16632ef023a49ee8b0 svn.601\n7e7a74d7ccb5b49b1748b3edeae17ff57781bd35 svn.999\n7e84bbe40c5b7cffc577533ccea6a6fd61dc3c1b svn.656\n7e93a63d6a63caf65d7de26223500352cea35c14 svn.1508\n7ee5f2feb9d59aad4eb6af41de7ba2d62494acc1 svn.652\n7ef6994a5fadebf07519c641cf4b72d824cff518 svn.1693\n7f3fd5b887bb0fd1a54a948aa1b5d1026e6bdb96 svn.89\n7f538f497ed08bd28b75ce150f261436e7dddce3 svn.1145\n7fa3ac5cade9adaccefa58726779de6adb1087a1 svn.868\n8015f503c5ec815b64e60a3a67881cfc4c6df7cc svn.597\n80ac83abd52bc4809da224207756eb3da581449d svn.505\n80bad282cbf8785f907a1c198a8161be5a950057 svn.124\n80cc3028703002db687c12bb3ceb64f5d6a53ed7 svn.474\n80cf6aa16a2702f47990aef67530abf723df077a svn.701\n80e43391ea11ba35c3ade9a0427f3b76a1db756e svn.688\n80e7766e9707df387d86d6557efd16208da60b96 svn.1083\n8118a10e79c3287b3df44eb9bb3d83a3499f20a1 svn.673\n812a377719680b1817d6b4d78d88cec45ed11f0e svn.1254\n81393d6765455575a8fb15e37dfba5eaf9f9e3ee svn.1472\n814b7ff8d33c2aeeff8d2f309e05f541201bb6a3 svn.615\n81556cdc5f31b00d5c0cc126799cb8b5885ff312 svn.866\n815662a641f5755da44ff6727ca0f90933f9979f svn.340\n8182c58ad0f3406cf84c30634976f45df119e87f svn.1677\n81938e1ae18d988e40ee758b8e7885d5ca6832b0 svn.612\n81a8b445cc4308eb0e45c9e15b88769bb823a1e9 svn.1018\n81b3007d285a41c18068d8f486bc53359e20a842 svn.1684\n82063f87059b4cf0eb15fda9372c18348a6eeb6f svn.156\n824e85d24342fcbd328df485425677a2bc510066 svn.1342\n825e70252776616ce9d6a6e50d0bc3d55b7197cf svn.432\n828c01a7da237f57e9bdf7846b6dc1e7310bfd78 svn.861\n82951c5f6967e055c4df8413a7ef801b1503a5cc svn.418\n829e66a249048caf82f38cbed01567a37b6e54e4 svn.808\n82c3945c1bbe5e4757e485df57b7d7af4e25d87f svn.557\n83263e58fd561ac31176f8257cf93e0f99306a33 svn.867\n833234d863be03580d95c3978aa7ab5800b7fbdb svn.1586\n833a684b19f01a76e6e083ef4e337f5b5671fbdc svn.1496\n833af911bb6950ed186807b033858421dc9998e9 svn.425\n839a901fc4ee1d5bb5f297d218daf712db35c2e2 svn.848\n83b99a433087db9d4dab352bee880eaf505a9c50 svn.205\n83e37d75ef4005c9accb6b292078cc153fb68808 svn.1019\n83fd1be26619c2509822c713280d1c9719800d38 svn.872\n8406869a75731e17bb48da3dbba9dd22e036cc51 svn.1038\n84135ca746d9115dcf06505e5dc271502515667c svn.1245\n844549edbfeb810f38de00b3d37162bdd121e2ec svn.407\n845b5fda8672799a9a9809b02d3e44eb9dc1b905 svn.524\n84694fb038902d50ac9a399ad4d0ac9d4f3a825a svn.620\n84938b94351b46f0e04c1ae2a30dafe143c8ca8c svn.1280\n849f899f96745f6dfb201a0f08174b5240008c90 svn.1256\n84a09db171e3d5289d3416b606c0ae58727787fc svn.1365\n84a29238ece32992a21439cc1af6d4773739d47c svn.705\n8541a3f921131d33fd10cb03c4af2c0605766be8 svn.712\n858f614dd5289a2905e13f5a0d7e56519f65d359 svn.984\n85a52aea151770555ca64a81003de926b4254de1 svn.731\n861a4b3e654b4af71a3fd84216e91805e93619a0 svn.1536\n862306a8cc96362c083ba6ef874375894d9e1482 svn.649\n8628c6c2dd265f2cf0c86ca9e33c3e86a4baec11 svn.1340\n864e21d9fd45d24ef74b6a81ea7d8eecfcdbe56a svn.978\n86c8c50cb6298a23fddd6b00c6dbf9554561924e svn.734\n86fc114fd878141c10a5829421b1a5bdcc36703b svn.198\n86fd19b1e4db6116e660bad44a8659510bc88350 svn.171\n871854458a1e4c7254aca166b60b38e5fd997812 svn.500\n875dae7f06e77ed6cf9237bdc3165d97f79b503d svn.771\n87a40ec50379ad21cc634f4067552d304a0acbd2 svn.860\n87b4f439784c7827a45f67aca66ea41a9355d9c8 svn.1568\n87c95800fb46da2cb4607a1573e3756f0251c5a2 svn.498\n883691116c1d36a77176710660eb116493b532b5 svn.1514\n886d4605de6ca099390b7b8cc2f61fa332efb084 svn.1393\n88763cfdd8c05e6cfd4280ce9e3f1c9e8ba1f1de svn.245\n8886880992615e4e64d9424b2713a3cde6ecc363 svn.813\n88c2900c197f200bb02dd81bb6dd192f676af620 svn.459\n89845d23e7587f3afdffb2f08db4855f92a5427b svn.758\n89a10337dd0662d72d8590e21e80426a915d7856 svn.1363\n89aabab3d202d8a59c48e95b4953e76bc07e849d svn.1332\n89c0ca6419b02e56437552b95cb1f46de616b375 svn.1355\n89df9a19d2e091086c1dba55d9adfd0dca52a17f svn.1726\n8a0b4b881ee9cc567f8505cf6479b707d318d161 svn.819\n8a1ad6b1f8798ad22c2ccce5dcd67921b7b6845e svn.1277\n8a6463a94c8cffe4fb5b58ba68da7a207446715c svn.168\n8a86a5a85de9e27968db5c173005b42ece4c917d svn.965\n8ab0cf44a1e7f376822f17c166d625ef2174f085 svn.590\n8b3a5ec0bc0818d54abac3652fb23efb28eda087 svn.627\n8be3fe249382ae5799d2f9d8908b550b5a218b9e svn.94\n8be7ecf34c2c2f7e0dcc4f9911183d4b6de7a49f svn.1291\n8c1e1d0828136953953642d54dc1779e192d01ae svn.1310\n8c2230b937c3ee7ce03b391e4a461bd666517591 svn.463\n8c85a4d78312dfe9268477441d51a75421fa4ec1 svn.636\n8cb2595cf85ae6153c354ab362072709d02f930a svn.1674\n8cb3788caf6b60b69fd1fa6871365ad77155c976 svn.553\n8cc439cd4908a37b9a6a003fab64604646439970 svn.877\n8cf9ef42a2ee8743e4dcc0266c137d7290347dbf svn.1803\n8d92c38d2f5071ba04168fa8be79c15a1690a218 svn.711\n8da7a5197b7205998ee9e1b66ace90707dca4372 svn.1324\n8dc392af99318b977258aed56bb551801b964a9e svn.943\n8ddbfa657c154329561a7f7301fe63af1bee5de8 svn.631\n8e09cf9dc462012fa145b992a3762a395a45a6c2 svn.586\n8e1ad8b63c7189647b21a93f31726d5bc989386d svn.337\n8e1f65b8f1fffaeb3012697ea6563b9ad3518df0 svn.1179\n8e46d277270ec44270ba64a0e9b58f049f8f218a svn.1447\n8ea4d84c99709c94fafa5b75d92d8dee46e4dfdc svn.1129\n8ec1f002099a56763ac301023acfea7daa143caa svn.482\n8ecf50d7068b1fcdcd4bc00b8584cdf8e3d6d858 svn.1725\n8ed67117aece3f5023f28a377e3489f706100215 svn.785\n8f1c5ce262983f962bae8390bc319ca7856089fa svn.1107\n8f536aa935a0fef92ad77d8b4dff108d99d07ac3 svn.961\n8f8379ae5ba9cd8c61d259b4bf7e4529f061c7dc svn.906\n8fa7c84becd0341ed84dc67d585e65f3b03f13aa svn.468\n8fb8e05bdb973fd43d9ce5c8b682680aa5199726 svn.1773\n8fceba5b01781a0fd7f4fc3fab2c5a2f6e44423c svn.911\n8fe94e553994a68e484f871b857824b9b8def4a1 svn.1385\n901c52a909f9c90d3ef0997c1e3b57f6df63da01 svn.392\n905743992786d73e673b909a33e0edb12a2e5d8a svn.345\n9057a1191c47b0184bbf355f21cf1fcebaf16311 svn.217\n907c568da1bf769526447f02f8b4a57a62079b55 svn.996\n907feca5bf0c6ac06642cb576bf2254b8b62ac7b svn.1481\n909e90b6ff19245a54a064ae75e378a0d91800cc svn.270\n90b9e95b83b8ca6acdaf5bd13e4f4d1f1361588f svn.488\n91357ef20a1f3f11419c4da9fde1e12708ac838b svn.1444\n913fcb4f0c79472bbd93e9824c62c8aa662b58d1 svn.770\n913fe489ac4e60e9930281b59c0dea68a4bcd323 svn.637\n9159ca04bf16f61e63e01bd20a8c4afffbc699c2 svn.1153\n916a361287e99135ddb8e33b2818aed0ffec6127 svn.351\n91b2973186627526d836f21b172e5ed2377cbabd svn.1699\n91d0807d5270f3006ca34f0ccd9c4a2b81a47cf8 svn.565\n91d52e06894d13f2b8da13baa2d8ddfeb1e049a1 svn.1206\n91e09807e2ae7128e2f5e32b8906935a6bad8685 svn.1395\n92098b0fdd7a4d1d5af9a1bf419709a3f4ac83e2 svn.1502\n92239fdd340a536e2cf989d3f32922d48a83aedb svn.1494\n927a21cab86adc02d2117edf55e2a4ab8b83d005 svn.73\n92820090a70b3d146ccaf34087fd5960e883419f svn.510\n92e482a7a589de11eba163373bca29010a5358c7 svn.823\n9319791488d21c4f716cd76246a50a3c3a93ce13 svn.567\n93321a880a99433b2efc5feb81ec6bd92cfbb4eb svn.810\n936376b8be15747cb0cb070de24d03e69d492029 svn.257\n9377e463277a33a9d103212940ae6b032680e505 svn.210\n9385dddafe44b9aa0b96699d658c07d389de2e13 svn.857\n93f9d91f08e3e8f153a08c83db665283b6a627e6 svn.479\n941cb047d4aaf379d4066c975b68fb831b7a9008 svn.693\n945069070553349eb8ee87bb1cbfda780cbb7ac8 svn.975\n94b38e8ff8b357d66128c0cf658961c5f509b7de svn.865\n94dbd328cae27a2fc2bdfb5fc08bdfbde2d0eeb7 svn.299\n94f4cd4f4a35dca050dc279a66791adca6f0ce40 svn.1642\n951195779aff62f5bb0cb10e2aa06c731e972ab6 svn.1031\n9535b96f0104968ecf5ab3c9e7468ce23a904a96 svn.1689\n9559bda18b4e856a9749c744165735c5155ebfe6 svn.1267\n9559d07fbce62a85ccf4b20db90e9ab3ac61c51e svn.516\n955a7a3e4c97e9e098e9f11b822946a96cd4c2ca svn.74\n95636bc3bc1bc1cdfdc960cd5845d26bc1570a02 svn.1555\n957781635f75d753bd6383ab20fcfdabc3600f31 svn.672\n95bf6395b37c4ba5ae0a5b101682ab5ca5663d69 svn.1739\n95d6cbc8a254c02e9371c64111cf6ea865ad8322 svn.1216\n95d858cd57c1d58933493c48116f1034ab3a8e62 svn.15\n95ec3bcb988f378331758ab60a4be3df7569bcc4 svn.967\n960fc803e2df4ede8b2b874d86107f21c4015c6d svn.508\n9640f12726ac9ff6f41e3367088128958e61e212 svn.232\n965aeb33157cb092f6c567d70cd03b33cac03d9a svn.238\n968eb2518b5e3ec10d5f97929c4826527f824f91 svn.355\n96a8ac92924149e0b53b409da391b2c3157fcb94 svn.353\n96d05f286624040171e9b951269b48d7207c1837 svn.1521\n96d1c770f30975d456e726181723fc236418e52e svn.1771\n9704d8eee09e9aa7ed8a25bb370290e7c165e226 svn.128\n971a354621a1d9d8cdb55c5984b9bc0b04a20bd1 svn.348\n9732bcae276ae99e9e0811f3c1363c88e7d37fdb svn.608\n9734bf27770c8267984a34566db598b3e5728aae svn.1152\n97588038bbf47ca913de4f468442e1cd86472d19 svn.610\n977e4a4d07b40a3b25bd1d70c59224684fae3441 svn.667\n97b4a4692fada54dadb2d1f5a20d10bebc2f2caf svn.188\n97df23113a045033329931b2d5a4fac8cef9c6b3 svn.714\n97fba1eba19a95247d655c901ffe6e182b501d19 svn.1442\n9800c5ba2109682bc8cc6a3ccb43f4df78196423 svn.339\n9876f685c311b200fe993f176ac7e689862dce5d svn.763\n988ec6d271a4678be3f522b5adaa569e78e62a85 svn.14\n98911d91b9f92be65ead0ec3c76ddc1ec64f08fa svn.588\n98a119a759a65fdc3737917dca91e10cd2614595 svn.1589\n98a64b855f10fb4c4ec0fb26b400c930486db61f svn.278\n98ad0b1eea20941012842a7cc35bdc057ea6eeab svn.1194\n98f12553b78be97ab4a82288e826457a83e59a1f svn.600\n9921dc9ed98bcb362576f96422ab51833890a3eb svn.1040\n9931de9a9d88ddd1ef0cad7d0cb3eeb3de899a4e svn.211\n993773404e8211c08aba35c481f7f8f8af95cf2e svn.26\n99652ca45322ff8219909a55c6aea132ab8fc846 svn.1096\n99c15130eb2c9aca7a058421f04342790b77e48b svn.1309\n99f8230eb4fd93cbef225aeae1d025ca77101047 svn.1436\n9a1ebd1808dc4fbc99cd536fa5bef7e562394a5d svn.894\n9a71ecc65e66c69b2993aec0873dac3a3b3940c7 svn.460\n9a93ef3bf70e27d9e625e9b0e8a79ff009be3b0c svn.52\n9a9ada9118516edcea975924ade034bd26dc13ab svn.957\n9aa25fb8967e427a27d50791eb2483d614832461 svn.998\n9aa3a97b4d50e0ff9dfd27a31de00f7c9feeed3a svn.1076\n9ab95a974154bda38fc2619ac6f637ea2b067531 svn.1180\n9aec4be13f8ca6fcb94c8d76d234f9f5fbbd61e5 svn.1428\n9aef30a081b44a2e20669c2b9efc560138e78cea svn.492\n9b10206065a80a8620f63cac765e9afd99d979ee svn.956\n9b29a595eb213a3d8b624ffcfec1264daaeb6297 svn.157\n9b41c90daf4749214ecb173a00f43135246e6781 svn.1714\n9bc1e5dd9832a063fee0f272231d1fcb4fa4c2f4 svn.792\n9c112da285602525338629860292979a2d26b201 svn.869\n9c3344556df0a135022fc51b4550268e0ff8644d svn.379\n9c49782e515a9305eb2520cdb216ce6a3a519115 svn.1608\n9c6a4a5c7f86d77a97aed9365a0d805af7f5d6d2 svn.154\n9cb29d1f9f10f0d2965be923f870f94a529fafd5 svn.55\n9d1e7ed6a6fb317b20bda798ca4d316e4a458a09 svn.1282\n9d92e040e9fae5172dd79cddaf35f70f8989f69f svn.886\n9d95522b58084ed44db218f5f9b9b8120a4429d7 svn.39\n9da0fc2dc4ac8a32ef5dba9721d6ecaf8b556799 svn.715\n9e4f7f432edcb71b2fc63de0aa9ab7d2a4dc48a2 svn.887\n9e632e3d9ab45e6a235bc32a36167c8846bdfd0b svn.1583\n9e6721a9642c652026f903c1d73eee4a112bc642 svn.1200\n9ec9c7afba9db271050517210dfeac944e06ea68 svn.228\n9ee5ca4720a15232000e029f62003dbccf4a90eb svn.1378\n9f1515882b0eb1e9330a2a7bced32d7465db70f3 svn.663\n9f207ebe8da55f96872590bc541aeb1c0e95dc05 svn.1108\n9f38bbd2a7e0b2a946d449cb92bccaa74cedc1de svn.429\n9f8ff6b8d63271ea46fbd5f1c1cc86c011448549 svn.1670\n9f9e9b6346e4b4f2e55d0a73f2620442f6adf92b svn.824\n9fabaecbb5b52610ac55462dc9527d09d6fbfc41 svn.833\n9ff912bc3aa5668736ec642092e9052e4cb5641e svn.940\na0248c89297d1fbcfcc978234971a334d189a509 svn.1078\na02e8681556852d390ca987883bc1f0bf68a883c svn.1095\na06c2aeefa343f95e6114ef42373b41d5168a6fe svn.1234\na0c953d3de30977dfd049bbdb9583babf492f9cd svn.643\na0d79c04569a2fab16b9dcdad2c2279a8d97087f svn.1629\na0d7df05f9a857975a3c5227f3b03f30e9dc984a svn.416\na0f418349af76aa0d155e417b42e62186249d7c8 svn.1648\na10259dbd29b3f03c2b7dc6119e7f646f0cc24f9 svn.1172\na12649dbc73d2cbb659f5c7aac8d018344e0a536 svn.1338\na1295d81ae39fc5a57d0504041c89d2fa48ca6fd svn.644\na163dac0359fbe271c1ca0245d3540857029c3b3 svn.1404\na16838b5cb05592e23262655527f197656796f81 svn.703\na16d588a8e9cf4ec88fd1cb7142c3834f9bc3b73 svn.1804\na18c760d50f79b93a9add8effe453a59432c7775 svn.1759\na1d74d0fc790a3491a52939587410556f6b0a906 svn.1489\na1f4a88de6091601fccaa7d750fc630d89484173 svn.1685\na219cceec164504f0652afba415fcc41eb94dd50 svn.754\na297f869ee4234578f31afdf259e696aa5abac97 svn.1753\na34717eab5696aad8a316ee7e76df8c533b73d4b svn.1720\na3741bbe1e9377d661298a8dbca15698846b80c2 svn.426\na3a91388da54b7cb33dba5f72030690d115686b5 svn.1557\na3e2cea497289ce066176e23cb4300ef42fd2032 svn.268\na3f68e21a03e4f6de50db0a5de6e085147c41ba1 svn.450\na402d09c999716f33b1d5505646bb308d4e39ec5 svn.169\na43969b73291aa0750bfcf156403ea1be4da194e svn.1800\na460319b2b547acc3ba3feaa74b96e1530c7888c svn.838\na48a47a1f51f173205dd227d420ece3611a6ec50 svn.1637\na4fc7736d18490a194bc4a15b778920aee81aa47 svn.1793\na50fe3cbce4e6073c6a645d6b137cdd68e46eefd svn.798\na510c9b3e200cebe7a43326fc9b366952020b70d svn.752\na5267c2eb1d03a1d9c8a699462c7e67533f055e0 svn.1045\na526bb77ca3346dfbf829f14a8b1f8ff31588fdd svn.641\na56aa4ee391728ced5cdf4e896c1b43ae7bfb09d svn.1118\na572e5965967eb1aa33cc2e7d23873258907117a svn.274\na578bb6121dc06b382231f4fa8d5d348bb1271d7 svn.399\na5857bae39ee445ea0c62ba6f4ecdb915230f090 svn.1723\na585956b12cf252a9d003079335feb5c06c99328 svn.800\na586ebd2e3196ff432ce42c22a7f96ce56a40b76 svn.111\na58c3ef148b51785910c57118a514f00ea45c5ab svn.724\na5b10446d760508bf5e8d5aa9a5c9e436249ec8e svn.726\na5b4b425141cf01a7f13ba109ca1f911c7d94d56 svn.1438\na5bab3cf3e5bb59a451209daf738e0d115e4e6f6 svn.1054\na5babafdf8d0662adfbea9079b0c4e3f056bd860 svn.65\na5e219997db4f32ca5c81a2b4aa231ea53a798b3 svn.152\na5ea9c50a0651be227b8b26717cc571749d26604 svn.1201\na5ed10e8bc88fe86cae5592cac69814be8590098 svn.959\na6074bdf26e5e73d9deafb77562d41d63c5e46e1 svn.133\na628cc0521eaf1a5fa06ad4e127359d33ba331f0 svn.1671\na632651a151bbffb850430cf60127d8cbebca142 svn.1336\na646b50a6c312814fbda9365ddb1e2378b601451 svn.1062\na67ce0a27572b4914685b3677f7255886ee53085 svn.755\na6aabdb308ada1f68f6795cac2f3bb1b3b3a270e svn.98\na70125dc712b2cd6f4038c0cc71bdd0726b706a0 svn.1375\na7aac27988511020c8a4b43d97ac296436e85a08 svn.344\na7ff6a46cb6d7d0dc38d744c01ba6d25ce77dcf3 svn.546\na80f3cab483e45ea8c6e7df58780079013a41220 svn.676\na81914dad2eb58c8fa6d345bc5ccc2031774f447 svn.1742\na81931a0ce18e6abe7c31f7a8eda97545f8c4a5e svn.942\na84e91abda7d74e41168d7447ae96bbe1a48e6f6 svn.593\na85b7fd37b4359a40a185d7972411cb171a10f98 svn.196\na86267e508fd91907ed795f277c13849cbfbaa95 svn.1302\na8899e645d975ab0dd8042fd1ba76e660ed84fd2 svn.1591\na8a6f843926816b7d9bdf8a2dbf9423973c4e285 svn.1715\na8d37c1e3ea9582e532abc92c254d7784795eff3 svn.971\na914357b7ae6ae9d06b75cd66b6a90dd612dc4bf svn.1565\na92157febfedda7525b97a3ae3ae102c3f1f9397 svn.570\na9295fc1fe38a604a738dfd593b61251a8876b64 svn.1522\na94d3306a44ee7f0aa98a2f6027ea54b77f21311 svn.501\na94f2e04147d37b0c86e220b47eda0bb842c6693 svn.421\na9997f587f4d1d280852eb4d5a7be43c34d91bd2 svn.36\na99b6b5ef6a1a3a47f2f55b95c622f5cfdfb9ca4 svn.1100\na9c31088a3e164c86f37d3aceec9e8ffd85224a0 svn.928\na9e519c8b5d3010d6ef71a21f1d37a70d302f24b svn.1510\na9f54d68329cef4dbb0fc4aec462a81790a5867c svn.1074\naa1dd68a313f90ed2bed7104cc455049f1d93134 svn.177\naa5a58db2daeacef35ddc3bfe3fb42e2f62dedab svn.1272\naa8c48d95687e4328d713ad09e0a2d5fa8f675d7 svn.25\naaedf208a3c7da4c2f9e4e7090f9a79b6346ef7f svn.233\nab00496b77f0132955f409bd81e661ac498f3fa9 svn.1042\nab16b63dbb36e2fd83ff62f903b7076372ae2280 svn.1501\nab2e41d6b3fbe3fa1b3efab14bd3a269a9c8f2fb svn.1199\nab5719ba28841242625841fde7606293252a1f81 svn.1290\nab5d94b1312c6a7b384630b78b1fca91748567c1 svn.625\nabe1b3db045589ce79ddcb349a73cb165a04db18 svn.692\nabf2124837c9a76b3165f9db1ff627c6ddada1dc svn.221\nac0ea6d8448371423c5981d840ed6dc1a21c66b7 svn.1186\nac1083dddca99afb1d13aab044e0a1ea1621036a svn.1768\nac9413ddb79055da702d50c9150a6172416108d7 svn.1048\nac970ffcd2cdc118cc58f2601c055b36ef016b47 svn.1655\naca911cccf567b3be747ca4e9d2ca2b9e2dab800 svn.747\nacc1571c360ccb3f6b74739da17a84a3aca4281b svn.134\nacccfc516c72622744b6278347abac10650db670 svn.976\nacec51d08d7624fe76a9aadfe3155ee2483506e3 svn.1490\nad0abbc94dd620f6971e040413801bdf7f5dc5cd svn.1675\nad57f1d5390122313bea130fa196e36f86d7ccd0 svn.1769\naddf0f93ae5c8ec8989160d02185e1fcb71e4a43 svn.958\nadf2aa0ce50c1ceeb7ba15d3b621139765b35f15 svn.1047\nadfcc0995620eee56fb64502c3c748058c968072 svn.378\nadffe1af3e0932544c25ac7dfeecce81035b80a4 svn.707\nae058e5b5b0b59b5821b2caa27955794fc5c2820 svn.1651\nae09f960b038793877a44a52e639ff5a6eab6f57 svn.346\nae618970c5d2b233fa46f001a8da2e4d27673214 svn.748\nae7eac9f61672eab28817c79a4c3021577f3bb3a svn.606\naea28bf27fb2695947563966b77625dffc562448 svn.149\naeefcef453f3b17e5bc516ae2ef5783090db3020 svn.622\naef2e9f0f0775830621511191cb1d90cbf31e2dc svn.1348\naef516e1116594cd80a37197723b554b7ed14dd9 svn.1509\naf64be205ea2ab36a0aa9fae890406b01f0b1aef svn.814\naf78ba12e16cd1c350d7b8d276b63dea1828ff6f svn.523\naf86a6b540270ec0b34313cf9a63a959c1c8a903 svn.1735\naf9009caf15aece591529c028ba693c4718599c8 svn.818\nafd4ae9b829680a7e437ba429f1d5ef583977955 svn.534\naff586a05569925695625366d3994efdbfda75fb svn.48\nb017b61136d48d1e975282721dc088ec042ee90b svn.1676\nb02e9e71d58e839ce45a0b61472d9a3231b0bb1d svn.529\nb034778c7095acd6c1a231d6a8359d4e1557353f svn.1592\nb09e8dc65e69b08904691e6fc2da1a3e227b7320 svn.1467\nb0b62515d53ea57e9a74f252cb25d98741f57ef1 svn.476\nb0c19e4145a3ff2410db03cbd552c9e79c5c6cd1 svn.365\nb100edb40a33571f3a8ad85db86fa726f552f7bc svn.1357\nb10baedc76153edc3cf9f62609923b031a0c0743 svn.102\nb15d74802fe758c9d2661854e0a3d98b65f42564 svn.853\nb178007c7e0eeaf82d720f7d67a278581fa68cb6 svn.1162\nb187516681a232ffac94cc6741db31b40089f1cb svn.616\nb1e2a89b438c98b7cfec2bf0d1788656400b65e9 svn.856\nb1f6ec05492ee42c05be7bd552e6a854c1852c0c svn.1016\nb254a7fbdf6e8914ba1b20c72efd82ea2895f233 svn.1177\nb29b47b078d90d27e17172dbd64371663d753f99 svn.982\nb2a797990913cb34b89db24cd90fa0661fce0d10 svn.684\nb2efb3cb1ae8e165a00e4ea416061e54b4375e37 svn.1136\nb2fc28865d5ada46e419fdcdb6138a48946f5c03 svn.1210\nb3508917a48f60870b653906c507c863ecfc6d68 svn.628\nb41bea1bcb1de1c425ca3e0c37af428ddb89f7cf svn.1208\nb49dcbe2d0cb373270e7afbd936a3c425da25a9f svn.389\nb4b34c928a45a00eac301c3eee0056d9e275927a svn.1449\nb4c889499f4c41b9bb6e40cf59a1425d1788f779 svn.41\nb4cce6b7eacf19d77e87b9c9a329770775936a6a svn.543\nb4e3de65979dd083e428846079e509a3041202ad svn.1638\nb4fe74aaa8f6b8d44bb333b9f8e3bab72d8a2649 svn.1196\nb50805f852a8e2be7ea527f7aa361074f461936c svn.269\nb516c2a4c8f925f48f51d4adb3b7f731b586877d svn.1307\nb54cc4d9ea3be89229352b78e540f36302b1b67d svn.87\nb56cd0e4430d71b6628e34b9141b67770e4c189c svn.1716\nb576dbf4b3df62b8fef0fc5b42c1ebb854ebeece svn.670\nb57ef455693f2177dc245989457480095481102c svn.369\nb5aefdd801963043c02bdda4ee3c581d78325709 svn.607\nb6091f03067325dca04b154c82d4ba9473203967 svn.286\nb60b25246b1ea4f377f4ec8218bb6eef2774b267 svn.292\nb6133494d191fe7c524b134c0ac49c73ebf28409 svn.1595\nb62bd324f6a76148938a9f75562bbcc75bad25e0 svn.882\nb62dfdcbd5ed48b6dd97c6c08baf763729af853e svn.105\nb65ee17c29656ad491d5926c92ce6c1511cf59be svn.461\nb66f1ebf55000be1e02f95cc84e2141ab13ac378 svn.760\nb68dbe7d587599bed4f5033cddf6b200b3c0c797 svn.1039\nb6afde5ee4d5012e2041ac8305d4951021fce11a svn.888\nb6df060b80323de2866a13e0fa671652fb0713dd svn.1125\nb6e2c5b077fc4816032af9f272a70633bd8bbc62 svn.1253\nb72c9063105fc37dc44f5b1927303cf435bb512c svn.674\nb7600c714b84873c4ad1c7637de51dcb0595a20f svn.683\nb7763d12ffd2ee7fa08d814a66d77e316334a846 svn.834\nb7dbd20136811c23c0cb14899acd320067f50f28 svn.360\nb7f4ea520c43cfe5409afb6a745ebf32d2f668d9 svn.470\nb8007496c5864fe41804232cae36f938910ea5a6 svn.203\nb80893f1505443f521f049d263d7c587cfcfa1c3 svn.1747\nb8276b8dd4e7ea3eac5b560b0dbb2ab9cbc9e941 svn.252\nb83eccc16f7e7b7e0a40fa7829acba11ee9e55a9 svn.312\nb853cd61ddc4b7690bbdf1951ba2b97915dbf7a4 svn.1140\nb87a153ca34719ca3f7c7bfab49ac497b0fe3575 svn.1374\nb8b1484aeb9371e0ec57133badd8e401451dbd04 svn.138\nb8be5dfcc9256a3021339d39219e5a92e752d07c svn.1149\nb8c88dcfb1690770c3547e51aef90f80eeb25e57 svn.47\nb9004cecfea6859995441ccff047349ac2146372 svn.19\nb9043b843697016c7929edda4c68aa19b1b34538 svn.549\nb90a5e0b349eb2d63def509fd2a1a972714a4827 svn.441\nb92d169eb5e10b1f0240084c6e88e69ff7bc9b71 svn.1334\nb94c331cba4d1d825d907c64eee88ec0d5c6f197 svn.587\nb94fc22d5df709e061d6b45afa4f82962f984cba svn.561\nb988ab9044a405bd54a8b82abad6841abf14e5c3 svn.1530\nb9c726dc2011338c128420ac8a4564373a350c72 svn.977\nb9d559d3c93827503a08f63cd377bb89ba96f94f svn.1174\nb9e6a95cadee468ead8fc31de24f615e397c89d0 svn.1729\nba26a56b3af92dc544aa3bdfc85807f6efc072a1 svn.1787\nba52065cb28fd4cabc1d1b62a2c97e970bca272e svn.23\nba72d14db98d4aec227e6b688a70153393514145 svn.1390\nba88dacae29e8a70d19e53bdd1de023f84ef8cce svn.702\nbad10987839488295256e8d9b4c98bd41d33c144 svn.921\nbaed8f20853f4fa7df7ca7b4e810e0fd57f64ebd svn.135\nbb127bd1be4a29d863e13ea3bb708758bb18af75 svn.1137\nbb1e87ef23ac5ccb79d99a157d7884b0d5acd320 svn.436\nbb86dfb4578f0ad377a6ab15e9eec729960a9602 svn.1306\nbba760a55e02033fd0055bf67b1c893e6b4082db svn.1011\nbba8232865e81643be4c343d4a6bca6687bad827 svn.742\nbbd1d3461d4ae66137c197de5135d3852fe10e1c svn.1230\nbbd2038ed37182bc4ddb3c574c8113b418999d22 svn.414\nbc1400e920f5bed116136d53722700715c07ff54 svn.336\nbc42cad69e953bb617aa31d7a374a1ce8a5f925e svn.544\nbc8afbc57b67f51384ccaedad69ad024a17e55c1 svn.281\nbc9ea94aa2cf29fb36f56ec6d994cba0d9be29ac svn.1166\nbcec0692b627b770f3e5b6bab4a22c830746c5f1 svn.822\nbceeaee5e4975fb7c5b448257fde44af4d13f384 svn.944\nbd0590a19af0a3b7e4f99f7ece4770713b8631f6 svn.397\nbd07a43b624c9e249c76af770a1bb82bc89c72e3 svn.655\nbd0b543a76b1be2416908cc8c002e516254170fd svn.1474\nbd20ff182a70680ed102319d756558b2e6931fbd svn.424\nbd37fb10c639aef30c2634843981e03cb922031d svn.863\nbd518f331c660af28f9653af0c00bb2b2b377191 svn.186\nbd58a3cc636648b5c4165f72df502718001e12e9 svn.446\nbd674100764d4566a7df25e2960efea627c425ce svn.1431\nbd76d70e13036a042d53118dacdee7017ba24470 svn.1553\nbd778de72222fbc5dd4f8f1ca7e2f8e6df9cd8db svn.1456\nbd972fcd4ae0b295afac1b1167edefcff7a799d8 svn.512\nbdb57322f46908ed60e5d8d5344ae9290baa525f svn.528\nbe2016c79358602e569cec87eeaf7c8146873384 svn.569\nbe34fecdf22396fc117ce041d3e350e6bf8460f8 svn.1236\nbe4a915563973368ff28084c8201f37af10ad46d svn.1193\nbe6acb7b9827ff8481db261ddd7f59df1a68025d svn.550\nbe7d1bb42c34dace8162a84a0b9fa85405d580e3 svn.790\nbe8eaf0b54747c56beb61fa29f5a1e52efbe7e30 svn.1425\nbe9c17ec40a22a0f2ac629eae2942c47a1e2e46e svn.455\nbed7ea6f0af813c6bdbeb6ac61ff4622fb24043a svn.78\nbee31785688b4b3ad4b9e754e7fadc6f73dafe07 svn.1724\nbeefaf3b957aa33f06304fbb5bd8d165f10dbca9 svn.484\nbf196d6db35e67bad09d04c5517191d9545f3bb2 svn.175\nbf5176323aac3d989544ca00e830a6ee030e2296 svn.289\nbf78c745075753faf220e6a1127803633e5d74e2 svn.375\nbf9ca3f4fc137b6dbf5f1ecc2f37731f5ba30899 svn.1240\nbfa60de05a0572cc29caa94e0200684fea4dc46b svn.1119\nbfadc2919b665414db3781ee35f6f78ec96571b7 svn.696\nc02c67948ea3bc03cc631a66b6c8a3bbeb7a9a8b svn.199\nc06cb14312e16fe08ed3b49d2dd2961b67823f49 svn.757\nc098f2b1d264b43305edd5e67b84f838759c0df6 svn.139\nc0b88e210dc389abc84d1a5a6b38b468580b9888 svn.753\nc0de8e602dca315eaafdc99f0570a6205f36a799 svn.1117\nc0ef10d55142de04b6552639be0ef3beaecb8268 svn.1650\nc10095fc0b3379a4230af3086c2c82d99fb08f78 svn.383\nc136b560a49ef16431d5ebda46bb181d3f30c5ba svn.1053\nc13e3d84f3a9a779d232194a736472c0c7dd30b6 svn.1238\nc163713e415ac6f33299f8773d95e2872b353910 svn.404\nc167a9fb66504c0527d85bbbb2539550e9ef43a0 svn.2\nc198ce1f5adb895f14699d3c28f1400ecd209222 svn.624\nc19f7596a4c38ccc1376343243b5043a230b8f67 svn.511\nc1b1502bbbc8ae1e989ec9139cdd4d1cc3844f7d svn.1405\nc1c93678d868e72993bcc2f3476ca75b791b1164 svn.1203\nc233bc3385ee77913c98866c10bad6f153f8daaa svn.540\nc246630d322cb58675ddef6ebafaca4b73216df9 svn.506\nc2a41082a6be8099136177ff717fa82cbae9758a svn.1308\nc2b3988e4f9b7530829e3b432df02e7ffe6743dd svn.1582\nc2bb172fd266f6ee32d3d4ba4a68e8ab72d21878 svn.885\nc2bfbeb2a6189251637353781c52cbbf226d1bb4 svn.290\nc2f22be977587da957824faa9aee58ff23f7bc55 svn.58\nc3196a368da7180614190efc0395a63929bc6c08 svn.699\nc332476fd1461f2dbc3467113e0d311b3b1ae528 svn.81\nc3376fa426b1bd1328d044a74f8d98ffe822596f svn.563\nc34c1e6c30e51ec604bcfeaca99a384ba6a08cbb svn.577\nc368b0a6a927578a598c73058e7a089066fe0f2a svn.231\nc3916e50b59483a7d5477a3d2830f7ebaa4e88b4 svn.44\nc39afe8c02faf8e017fec2b555f588eae92caea9 svn.1475\nc3a8df412c3c5a6d85cff623f3c0bff309892c39 svn.1520\nc3cb6c6ea42d39bdff323e454767c966fbe273d6 svn.109\nc3ffe484fd397106b02b346a57d4be5538b29bb7 svn.255\nc415207cceeaa18d4159f1bf54b4622d257f97fa svn.1634\nc44554134dd77398bbdc9ed9d0c6d07ddd9bb0c8 svn.981\nc4c5cddc0193b5f2f539a1164f6ceb64f2db3337 svn.881\nc4d967b289e88afd7aaf513a69137c9ce9256d33 svn.195\nc4ffdb58f38773e05bdb4cba2c07b11f13960a6a svn.333\nc50fc0696ef444d7d29476442993549e858c5daa svn.1275\nc52bb6ff37c1dad3b2d4bbf9ad1523fdb8b28402 svn.1281\nc53e9e42bf066ea0d6cc8727434d0b4b2a475780 svn.1736\nc558481a7b1cb461664e002d6276a4a45bec895b svn.194\nc5799f4131404885f9f5519469ddc7776db6acd3 svn.1673\nc5c15377573c533f5fc534fbf8f41ec86a050410 svn.411\nc6088d1d3e29aa86a5afc947eb35f1e1748cadad svn.832\nc6096476eeadc2981ea9bcc2583602eb9b65e9fa svn.170\nc623c08ff5354afc9dcdaebc4c48e63db16c2925 svn.519\nc6859bf896efd71340811b384ebe188adfc0ae95 svn.1335\nc68a59d35ff4aab7c379c6901a228c7046b9f9df svn.1104\nc6ef0f1ad18b66d53d89f594eb409da7c3f88c11 svn.602\nc709cb0943774485ba12513d41f005dad4e1a73b svn.1121\nc7162a837fec86e35015ff1106affed097263e8d svn.1408\nc74f26086149a38489c2c4dd4d893430513e7d31 svn.633\nc75b8dcb044876ed256dfa807fd6c0140d9533e7 svn.1296\nc773f6d895653c3ecbdf47ba449adaaac871078e svn.440\nc778a0ae6038c46a24c0e8e13039475075ae661c svn.1656\nc80d72a952f4c7dc149862d334656d4f29494606 svn.825\nc81f9bdea878709507f333f0dac8d3edac0ca837 svn.706\nc846c8c22f53d3813d5f48cbbda31848358c73dc svn.585\nc84ea36f4cda9bae146389588b04158d93297f5b svn.1709\nc8713311b73b6b948194bf185a1973b535eb32ba svn.515\nc871904bc45d64e143a583a41c7f57fc8dbf0313 svn.1097\nc89c86f10a3bc43e3a6daa573f91138cc16a0d47 svn.1064\nc8ee9b5d34430df0ed14cf3f0f1dbc7f1d2b4b16 svn.223\nc9773ae3b01374d1b3f9fdf518ca7fd54b573177 svn.1658\nc99bb50a463011e62a54fbbea512e004c7f8c460 svn.20\nc9ca0c16cf8eb49e0e314f65a7a70de87e870bf9 svn.1380\nc9d500439ff90d14fe4769d91134d2f436a1fe50 svn.1706\nca15fea2e6e3e32a908ff6acc3206b7d92667ece svn.204\nca1e28f7895a7a54dc98911762161f370b44f6b5 svn.1386\nca2da2130ce94193b4641865b4b58d278772d9e5 svn.1611\nca5dc5e9e08b86f309231af045059019a9fda402 svn.574\nca6269080027653b454ce5144c0f95c448ec7bbf svn.1024\nca8b8620885703755c186ab7d84dd37157594b2f svn.1601\ncaea955bfc4a4bc41d45f9062f9611d4e1d0229d svn.358\ncaf0c2b42ca9839ed7c717de26c99509062c4f8d svn.811\ncaf4508acac58c6ada5a6e9e192af171711e1a2b svn.206\ncb33bfaa936dd21013a66f1e67845305b028e6e3 svn.581\ncb55e7475f222fe03bbef195d1b4492f482eabb9 svn.242\ncbb1deea25e3a6c5106ca32b9acaad37231b711b svn.472\ncbe7888df0dc04f3349262dd716114bf60584352 svn.1566\ncc1f76d8fe23fea528b8f58c37d15a005c165820 svn.1212\ncc2e1ec4536f68860d74e3d0ff320f5b88fa9237 svn.603\ncc3224d42aebab5e87bcb500a56577bd6e7cf2a8 svn.1059\ncc38929af6efa218b77da7c8a8ad84f0c39f5bb5 svn.1128\ncc74a5c1fe3c598da6cdec23b389ca6e706a8eea svn.1785\ncc7bd4cd0d1b9f9a1091bd678047256d975ecb8d svn.1123\ncc8f3d28257506a3bc3fb7e14aa924ad38d59071 svn.90\ncc91980a1e29913f02f3513fab75f8f23855be4f svn.900\nccf7b27b64988bfa187b588e40db01975e687159 svn.287\ncd0c3084edc9eb40f8310068d6ff6d6c7aad00a8 svn.719\ncd416a203e19971ad4d604ca44aecec2d2917e46 svn.387\ncda04daf48c6309f71c060158c6c5180d90136f6 svn.84\ncdc4f7e698242ed2a17946fa3697e1fc7bc7d1c5 svn.1698\ncdf65e688238eedbc9f1036a903b6f03735c89c2 svn.1384\nce014fc1956a54e948c897831312b28ff2d40db5 svn.1330\nce2a2203d4f5ed56479a7ef201996ca4863a9c18 svn.517\nce2f3a72ca9da939ab91df6d5cf193a093273a03 svn.836\nce4bf5a027a32c44bcd3bbcf217bc00a06c310bb svn.1505\nce7a63e6fe40b68add31be83d48bcae92237fb02 svn.924\ncebe6439577033f1a6727ae2422dccd487729e2a svn.980\ncec5c2f0131a6a68816f3cd653bd86846942777d svn.165\ncf048d23f9bb70efa03d93aa29281ed5bc489d56 svn.1151\ncf0b8fa619131998ad2d9797f62e6d4fdf80dd25 svn.1649\ncf10ea8151cb6d668dd33e5547bd4377c0e699e5 svn.990\ncf26d7b1d28c8c5561998b8ce1df420dca9e75a7 svn.654\ncf5ef451015e5b73967dcba66dda2942d0738030 svn.973\ncfd3034246e5374810351f319e7be0bccc989b2c svn.398\nd00dff1f4de7ee734b29f596b70e2a86ac22b93a svn.507\nd02415804062ce29d1817d6637c2c83ae8c6a985 svn.613\nd03329f07d150def647b13629f2076b7e7fed0d3 svn.295\nd08bb1b3cfb3773e3ae72796242be9cd722efdf2 svn.1271\nd095eeb54e5be45074634e633045237f575e28e6 svn.485\nd0b50a7c7a4b2a071640ed4545a9bef6fd0c5494 svn.1164\nd0ef00548e9d059f838033cf4f9af66de0a5d11f svn.141\nd0f7efcca67d22b8670e257e765fb784dc7f5a98 svn.338\nd12d8030ada4bdd6753f9526ceeee4b87e758081 svn.793\nd16aa2935585fa8422e5b297f5fcd2315b51fdd1 svn.1415\nd1a89f0f04ae32433d86fad2e3d58cc018570315 svn.1524\nd1a8a2656a93d9b645072b1cf6ab5c4e3dd232d4 svn.244\nd1add6a890d9e5f433efe2f4af3f820e94e5ee6e svn.730\nd1b72540f662098dbafd526dabf48f09b5b9b2d2 svn.1745\nd1ecf43dff68904f9123a3a29892c4acc4c2ef26 svn.584\nd1f98a7c06d1921d36afa0741486ea7f765b9aec svn.767\nd2034da217433177c89e79436b59c38144cc9794 svn.1784\nd2173c1262774efeef1291f7062521389b7530f7 svn.890\nd2282685ac5d1e06a7229f8c39db1d93dbe4edbd svn.1005\nd250a805dc0618e8ec8060ed8c1b244d3fd98c09 svn.1056\nd279bbbbc82cdf1c4885a4655f901116146df845 svn.789\nd2893ea69beeefcfc5c9d56019ac5057dbfeeb6e svn.1779\nd294da29169a427c450192c84a94c4dc36917324 svn.535\nd2a61719517d9749934b6ca8b91ef87eab5da8fe svn.1756\nd2cce60885eecfaa844f23a26f0fae43ac34c600 svn.572\nd30456a99475a462bfd2ea3e831e15e27f4463ac svn.1564\nd323733a5c1667fd08cfda8f65df8adfa30b49ef svn.1617\nd34667423b3940c6105dbb70018eba21a0c66524 svn.1371\nd38fe2486bb57ddd100be7ac6d1597fe2a74fb01 svn.1182\nd3bf19cb7ce33da131cb742641b947af098964c5 svn.1260\nd3c0d5a01e96de0fb8357bd98fd53bcd78e3d8c7 svn.57\nd409892c82ab05ee7f678687b756ae192b21cecd svn.1359\nd452bb73bf7d898707ffee6b8388800d4d4c32bb svn.1305\nd46c717574272bd2cc4b97238f42c410f2d9723e svn.664\nd489475826609f4c8013a28bc31b72383a4c896e svn.1441\nd49d0a7941ed7725c7a3382573d06d0447e08503 svn.1631\nd4edf1cf60d10e3593223705033f460f10c70312 svn.634\nd4fac6ea54a62e42d54f846ec3c0df741bd50888 svn.1321\nd5057e6c68d784cc5893da72e5ac5ac72f85b535 svn.575\nd52d833924ed0f1074688d72c4ce2f644bb46f79 svn.525\nd5820ec3227beb823a8a71b1ea5b1648d258dddc svn.1471\nd5941dda907940915cd2b05964593d096c6a2fc2 svn.261\nd5969b00fa9734a4da0e33ce9c13e46abf3c8648 svn.1268\nd5a49ed72a6eb24831dabeed25d2568c7e61a6b9 svn.1131\nd63459c7fbabbdc9c1801175bb5444ed40b5569f svn.136\nd6dcf317e3417fda2e633885be378e116945b9f5 svn.1146\nd6f2fd43ee48e2710a80bbdd99be983aeaa9967d svn.1504\nd76b0bdd379455f66e124a2d968a082dba710dde svn.1719\nd77977e676fdf1c0e2c05e4296f68933747e70ee svn.462\nd77b4d4789f4e93914bb5b435665a77d65e1512e svn.805\nd7900a6464c942596365347e0dac9d225f8efe73 svn.56\nd7b9cd53e639ca63ff18350a898fdb9262305150 svn.1069\nd7c60eeed511fcedfc20d05815ef985ef9b00586 svn.331\nd7d86ab3b0d4690912a88421518e2779e2be717a svn.1452\nd7eaf968bd14944c4399ca18e3b829489e278b87 svn.1376\nd8068096efa1a7478739d255bdc6995b649247d2 svn.1473\nd81716777b972787bf80f27bf34d33f40631ff90 svn.939\nd8226f2dcf0a14dc2edd0704b8ca1d71288f0beb svn.743\nd86244661ec585e3ce0088e7394dbb4e5d530152 svn.1681\nd87671d34c248fbf7b4340eb1d77fd5baf93cb46 svn.367\nd87bf7bd5791e7facd0e88e76e0b9902c4fb8f90 svn.1712\nd8945d2c82435de57ec6a5aadf3fdbcefe89da51 svn.548\nd89a70b3f6b24c62fecd63e30e79730460ed3fb3 svn.1028\nd8d5d0f85bcd1d0c07758d7056f2a007f906e122 svn.1328\nd8f61775a015be47edb16712a599b1831e0cd263 svn.99\nd9236d69454cfafe89415698c11e0fc167dc49cf svn.1777\nd94c1c5eac0a2775caacf5136617e4f6360ef886 svn.150\nd9627b379d0d36e3ca302943cea334cfe052fc8c svn.1554\nd96b5c00a521c6c436d8a6cd506482a24e55e823 svn.222\nd97109d30a699b5ad60025583df7420df09c7c96 svn.457\nd9a2f6d083d569f07828720deb87372fceeca32c svn.1741\nd9a557f509a5ad811568db27ae676efcba75599e svn.248\nd9bbea43918ee4509e12583fd4603f02576707c2 svn.59\nd9ce9b6db9d314a7a518f90d28db3c79686fbeff svn.1764\nd9e3844aafe7c3bbf0b18539bac4b24de5acd0b7 svn.1252\nda1e5cb40d297b11a5728e70b997a20a7f5a4d7d svn.815\nda2fbf9d6dbe9531efc6eec2f99fef06e48b4fb4 svn.1546\nda5a324608caba5c8d1dd13fca193ea7cb3fbc57 svn.690\nda5b5aade6b6c9aaac226c17cf61329043cce3bb svn.1298\nda9027e61c2574ca042f003b2db7a9390340c772 svn.741\nda9a9555e9dd1a7132ba696c7139b5af79e81e8d svn.651\ndaa8e9f1533930e9fe004f28c6a3d8bbaa3873c1 svn.1289\ndace5ca959f3d429a5d8422f8ade8ff2020885ee svn.751\ndad0938a6a73c2cb20fe741ed5885a631bacbb1a svn.875\ndb0c05c310ce7bde74d48f39ed1e4867564e10c6 svn.1191\ndb128cb36550851c7d25f7982a982b335118667c svn.1513\ndb2716d7aacaf947331b437b6f9259a6c3718fed svn.388\ndb2d8340dc8c3f4db047424dbce84829600b4f4c svn.1353\ndb428cad7fb72268daeed2a89e28ef1f4d1642aa svn.1373\ndb56f738ed6ac4634ab1bce96b763d703986c9e2 svn.1144\ndb725052b6e3cd8b708a225b35f7d6df48a2730e svn.1744\ndbb9baaf6763db6c1ec069bda87eb822e4bb2850 svn.1523\ndc3413343f3a6e0f3adb3f5c851611256825278d svn.76\ndc3cbb2f3c8b1d30c1bec6551f29661850c79dc0 svn.192\ndc5c031766069237f69c3fb997e20a0cc1c9a1d4 svn.1227\ndcade05709ccd8d3556c082b9bc17a70d690de71 svn.277\ndcb0b183591b93e51f3c7c084e681a575dc9b43e svn.444\ndcd3605a556003cdd44f15d61fcbd002437b2882 svn.955\ndd14ef8ddc2d8b522b76ea7f186a38ab3b364fca svn.779\ndd198266f3cb029f0d5bf63586693e79e0614a4d svn.595\ndd258ffab1d84f097561d6e2332cef413a531211 svn.929\ndd441b4d3485d9f76b04557b2081407e7b390cbf svn.675\ndd6713a2070d4786faa56ee1b5de99de057b9a25 svn.1567\ndd852bef6ce6f0af3815cb2844ed2425fc387201 svn.1644\ndd8c06cbfd201a05501a77a29ba089b6e7405b6b svn.121\ndd9b5bdb90ae7f0fa40a52256c664c4286a9f00f svn.1692\ndde9bbaaa7f77a7d3a3ffb0a9ac11ed18665d63f svn.1122\nde12d958ec8d61256474cbae0699b46bc245f1c8 svn.1740\nde5a16dd44a65e1864acdd8bf0697f28adb682cd svn.187\nde5df261f5d9f6a66cc5e8dcc6217d28ae3e02f8 svn.1171\nde790db535467baf165508a67ea51f567ef68b4b svn.1155\nde989f9e00795ce23acb6447fe09316d5dc991f2 svn.1713\ndeb4493b2fc41bd98753f9f66a12b4bef8c683a2 svn.1588\ndeb5472bace0f20d2abb196ee608947f4b24e1a7 svn.449\ndec1498ac58f2c3b2d8b4dea3a41330c6942f346 svn.1531\ndee00b9b86e0ff5538b2e973a60753d2798f3995 svn.1020\ndf19be55ad5532cb4a831e24a8c7d29282a90c29 svn.1250\ndf19fde1fafb23c8e6d8eeea7b4a71c3663d939f svn.1602\ndf31e314e96595054761ac488c18cab48607337c svn.276\ndf630d602734c833d58c17456dd0054e4c81dc7e svn.374\ndf9eee094325daa18d3d8443f71c7bb76e7776e2 svn.1717\ndfa1661607fa5daa6f98700397f518ace136fbab svn.776\ne0186afc0c8e2fffa8abf02e4414ce5fc887b1a8 svn.1087\ne060d802c624e7f6b9dcffbd2f4b24c02797939f svn.907\ne08a54b5f3aecea5b384fb7126981c9c9dccf959 svn.594\ne1401f51fca72b608abb63a3cb352c1295b65bf1 svn.12\ne14986fc7821ce361b2a6c1ba13ac90f5dcd1429 svn.1138\ne14a71b2045a992785bb3f3326770efbd02a602e svn.1299\ne175cf81425877f3bc1e6b419bfd90d2bcf43086 svn.1412\ne20489266f4ab2b653edd84cf071748f571e8cf7 svn.851\ne20dca56fba1a0808a13fc700a93527d0b82090c svn.1409\ne22471b904aa14082fa012133b648926bf438c43 svn.992\ne24d68fd580a8526b5976caf4b805cb73bf69f16 svn.765\ne272895dc859c84fa35245a1c186bf4a252defbd svn.1581\ne27c6d750b6b94c4ca510903d46d984028b3b631 svn.1797\ne286ee5b821fc57e18214c2ff70f75cce9cd11d4 svn.354\ne2a5af31a55fac49c28b369e46c03ac0032729dc svn.1571\ne2c4af6d557fd7530e53c6b8ce95986183a5aee8 svn.22\ne2e61b10359f50643a8478d3b497975e369010c8 svn.430\ne2fcb3571d13044d75501ffc65691fd290c988b4 svn.1507\ne31e7ae5250c8356021f9972781c3b7911f41fe2 svn.395\ne381af52bfacd5303d5818f8825db36145412dfe svn.1343\ne38c05dfa50e1f3981da387ee1d151dff5a9dc16 svn.941\ne38cbe2eab336984ca2a298b44c97f4031845096 svn.716\ne3a860dc2b21287ba888135f8892e4b469d17383 svn.1175\ne3aa0614a4d655db7ecabf95adeb4b09f3e07fe2 svn.224\ne3c36f35db79a36c92df45b4b2a7334a2c77fe01 svn.762\ne3ded6a4190ab349961ad3b103f14290bedd35a9 svn.110\ne3f501f80bc2dce414273f63f73f5f6c33aba297 svn.1075\ne3fcc61e983e4bb2c7a5141f5278744e3f2c2c1e svn.1266\ne3fd071777c105975f82c15a7f45b53111871fb2 svn.490\ne42999672ae5fab1bdfdccc0b758405407282bc6 svn.852\ne45f96c679ac12778209b30973538c4313478ce4 svn.893\ne4c6f2afb5e60e5fc975f8ad197b9e05977ad8d5 svn.1479\ne4cb89f2f2b7af049bb785275e75aec6345ee40e svn.184\ne4cfc303bb1f0eec319d337ae4f6d3aaa09e4d3e svn.1392\ne4efa5208288ef858f2ccbfe4211a0a6a2944785 svn.1111\ne53152d529b0834ffb2ba75947ebd8a03ec10e6a svn.660\ne562e09b6f3cebd6cdf17f64fdf6328455b37962 svn.1807\ne5959ab6348453899a6e8bb3bf49de951d03d4d0 svn.454\ne596a16a40a2a897633797103f6d2b30025a8663 svn.1126\ne5bf9353cae7206a2f4fcaaf06db42c853a0e45c svn.297\ne649c5da9087385ded87e7908622a7a3aa432cf0 svn.775\ne65114d76ec41069db902fb50adc1a6caaf59c18 svn.356\ne68e2fb451471695ee1e1ac0895a44847be41b8a svn.364\ne6ca392f44d89c82ed47827cc351e62b93ecf471 svn.95\ne70bb034f46bb63de837c054b9c0082a93eba286 svn.1652\ne71694eebd5988be6220b165de355ad6f5639ed8 svn.1086\ne7308687c3771a5ce5aad9de3c7960678f2d2c64 svn.952\ne746beb215617b69a96729b6fe62d3c16ab2bc1a svn.68\ne764b36940ad11559c8fb355200a5be3dc3e5227 svn.422\ne79547d8e6826ba37bb81bb572f0f116304a0532 svn.1770\ne809a113e46e5f9bcb9e426b722ef93112da30e3 svn.1493\ne80ed81b5533cc74f9b9a3f78cb194a09a2eb9f0 svn.687\ne8257b5e96d8fa36db504f469fedac7aad1c8936 svn.986\ne829e697b9056d623104f8c58c7a17674b15981f svn.1577\ne8460c5500b005428e85959558665ec48edca809 svn.1538\ne854fcfd3bb2f14339c957d2c8348936ae4db763 svn.518\ne865de13f4a9b9ec7d06ba8089c4d5a3f5c70ead svn.61\ne8676721e034172dff52af911b38c952fd3b0f33 svn.1414\ne877277c9bd2d4708ee8376b6884d49742f1178c svn.847\ne880a7d4823404c5e3c23b8e85e5abaae7f989ca svn.1544\ne88577b2e9f7b5cc4b6a71467ddecf926d324d39 svn.1262\ne8be9d29f2ebd43865b52006a536720d34475659 svn.926\ne8c552566b7e611e0b1cf5507fab4c0a71641441 svn.689\ne90d98ceddd5099b8e59776cea78818a4e963cef svn.334\ne943ea00d2b15c1198088741c69aac8a5d1ea97b svn.423\ne951fea3033250e64089a704e19bd9db162f6ae7 svn.1295\ne97ce937fcc7314713f79db741948cdc79939a6d svn.1809\ne9882484b7cdc4fa2408f82a6026df3459fa4c95 svn.1057\ne9c3d7d08b1f72942ee18335ece0c83170d34040 svn.1163\ne9c64f18e08b97a9c2a54180789f65013d61873d svn.419\ne9c7c5702922b8a61da9d0bf435351e7f5e6861b svn.1690\ne9fb3e756f074d3e17131d1315eca56359a3b8ef svn.1073\nea01607fed384e779d95ba04c70d71f2afc3efe1 svn.1269\nea203317990ef509bca17d28c03b924b4d190398 svn.1550\nea21e74c0a987a9b21bb42780b8e5fe427f2a544 svn.749\nea30a44e004f71caf90f435b9a94ed9b447170e3 svn.904\nea3443fc287b6369bcd03204df4881ca25154ada svn.1630\nea3cea63f40d84cc71c1134c35ec0855b53af719 svn.1001\nea9848a0989f43d9443227126e04b57980d5114d svn.1667\neaf6f737155f7883178b450261f306e96f07ada3 svn.695\neb4a739d738bedcd955a47f678793475812429ee svn.1263\neb820e77d4b93ed6c8795aff991f12609bf022ae svn.251\nebc6d7fcbe348258a599674b79925ebcbb3c8e11 svn.1327\nebe4ce0fe92be979d2480f48faedce28a8181fc5 svn.1344\nebe9411ef03491934c417c019a8d251d74ce2de7 svn.899\nec43da2bc1208370cf8306a529bd705bf4490496 svn.1297\nec4cf5d62741efc76e9d0202f302e4e9dd99fc28 svn.1288\nec6b12d6999dc1ca296dcac72bca469d9c26fa0b svn.466\nec84905bae92fe79db17866c3b72deff84efa51f svn.797\nec99f6513ccc04d2649415a033e0e832a2c69fe0 svn.1067\necbad3699297b914ebc10cebcf803150491faec4 svn.254\necc98904c752084403545715b184cf55b8781b39 svn.718\ned216bcd32d70aee3bcff4babcdb2b88b01c8902 svn.1679\ned436c5ee36728b29111f713dd0f242b6ad77c49 svn.368\ned945662fb56dac71f20159d917c458c5a698995 svn.359\ned96e2ae3cba35e2132ecaabf74720cf1b2b4961 svn.777\nedef3025dc5c36b041481564f09873f383a55acf svn.456\nedf4eb2310b560a3832bbecc68544318c8a11f1a svn.891\nedf78a84101a105b2387ecf031bb5cbbc534011c svn.1023\nee7ab4e92c74972fa5ece220604ad7d2f5908fcc svn.143\neeb699ff980833c34f5c8b2b4219d194d73178bc svn.1542\neec38ccb6c6cf3b3ea24e86790e52afee26a21ad svn.1124\need10c69c338f4379b4a2842a0aca95d478b335d svn.497\need9343db961250471a573539bdc4a82fe4e79d8 svn.669\neee672ed42fb075df1900810565659776e536bb5 svn.1114\nef0b08e329dd1467460a7a9c7bf3e53d4010ae0c svn.739\nef3a39e8fd9b8985c6b0c66375adc2ed000ef4ab svn.807\nef3bfe42a50abc9cedf6528545c73d627f390157 svn.103\nef56cb3058242b6841511103d2e067f09933814c svn.107\nef70305750bd1da1b24174a8ee9507770e9fe3b5 svn.160\nef9eacafbae7825e35d64c77f367bbee0947ae03 svn.1485\nefde317797334ba555386283b8e00fca5a77fec8 svn.1612\nefe0af4d7cb04614b52bf99845c489915c73f047 svn.1491\nf014b0f1317696d0743507d02beb5920b075de59 svn.618\nf037671d57fd06d6cb9be940aafaf0e4e7ef37a8 svn.1219\nf06de9448050dfaeb6eb120dfc5859cb4411bfa8 svn.481\nf06eea2eeee19f26d81fcd1580e44da160e7f874 svn.1084\nf0837e1ecbe583af4d8991451dda24383d2833e7 svn.1529\nf089bf123355e4f58cccae286daa8341b0faede8 svn.1285\nf0bfbf4f0ff8234095f4e9260554d47b0d6c7e6d svn.997\nf0c6ace804246be6499292171b3c2a25a2d98469 svn.1455\nf0cbaf7bafdda306990b6d7380d2588078a449f2 svn.1575\nf0d1af1d9f74a64cc2f3ba41c4791f8ceec4154f svn.568\nf0d54897e223f01b8b4f0af89c4c1260b7e5dd1e svn.1352\nf1114268a7373a1decbea482e7d1d042756761ae svn.1534\nf14a3e397ca98de852fa7cce40243435da79ff78 svn.816\nf157b1a713d1f6fd5e2fd6781ed133f248f5ffab svn.1372\nf162e5645d5c5e4fe5022a50b8c4dedd960b6401 svn.657\nf19bf1ae6de86f7ae40021ab70f97976f5e8ca65 svn.37\nf1b05bcc11f5d4e75e88d6314a03f44993124b94 svn.892\nf1eb186c43debebfd70fd7d16ae2a582698077fa svn.1034\nf1ff6856474c9ff16b490b8383b00045b1aca3bf svn.1423\nf249bcbc653b330265ec31240211c662d77b7a29 svn.1434\nf259010e70d330e0a78843d5af6e080c42d598aa svn.1543\nf29c306f22336754779b19213871262f0f8ba7cd svn.28\nf2bc5e164d73596939d721b2a579ec55a58d1274 svn.1788\nf2d3a4034d896bf6b4ecb9960aa2f5b9b671f34e svn.920\nf2e36095b95676637a36541b79f06f6213984bb0 svn.62\nf2ffa05cb5671eb0bad62fcd95d3ba9ecbee3acb svn.1088\nf310923770b1b1341ca9163498b0f01ca04ca84c svn.728\nf37e8c96a1cfdc199dafc786e656188c8f618fd0 svn.1606\nf37f6dcc2fada56360587c2ad701ebf4778e46c9 svn.352\nf38b4427c24b586efc68101d772f6d08fa27f810 svn.1470\nf3978c2cfb085d876d2d56ebb70d8be183043f30 svn.1089\nf3a1522ad4de815aa5b652f140a62a7daa67911b svn.80\nf3b88e440ac38fea149a4203b51d4e4656affb6f svn.100\nf3da9130ae74c98e353cd540f6904d4979a37491 svn.1704\nf40234b616dde3de2d3a9c1c6cd2ec733b9afb7d svn.197\nf4793542a3d86170546bfd2ce6b93214dc1aa753 svn.209\nf487178c028c014939dd70e7b5be731045293465 svn.1559\nf48ce9105d52596c8d44c6398150fbd6aa5ab0b1 svn.1574\nf4b6a2cfa7a672dcfc108ea4287f43bd48927dee svn.1361\nf4d9bb935e4e95550c4044ca22d31558411f0113 svn.962\nf4fa817475a03cfbbcb7f9c359f2142ed09b4556 svn.1211\nf50108a56ae7020f7c1de5364db721956416f5c8 svn.384\nf52b3c8eee5a72a545b2aee5c541e7745fc9129f svn.42\nf59f115516f715da789795a1b6f39c9815c6077b svn.1198\nf6366f0ed82d609d1eedfa18d330b4056fe57515 svn.1188\nf639a64b8d4cd31d7d2f98ee8f71613b613bd1ae svn.145\nf657ebf67d707a3b313b69cabe68d54fcdc2cab7 svn.478\nf6e445f88d811e70314f902456dcf1753b4161cc svn.1527\nf6f3ef09895ad1890e0125dfdc544f8e5f4ad943 svn.927\nf771f2707bd7e55861e4a451dfa7ed4193053486 svn.794\nf773270afc6330b8d95b1da050a0f4d59c40e135 svn.732\nf7862e1b9fade96044ed6c6d07fb80fe4b472e74 svn.1750\nf7d162799d92a2d0fac3185a96dad3aae2c7c0f6 svn.1248\nf7f92abef4aebe413aa4d17fa2018a6da52bd649 svn.1791\nf811e99c84648f894fa79d6e54bc96339e61af9a svn.1029\nf84de6dbd29c9686d91e3e9a818033962fedfff5 svn.555\nf8792cdf3ba035cee963c95858cae239df55af31 svn.116\nf89742203405b18315a5fb2b688b63ab0068f6cb svn.1468\nf8fce3c3ef6220fc74b60123177de796918b4bff svn.912\nf91c56b9314ca1653f870f8d0494a97dd317bc04 svn.1187\nf936f724467761ab6d8c402e2cc52352b267b830 svn.1569\nf938afbd0ef3d4d3ae3f6d82f1039b9d84f2ddf0 svn.1370\nf9caeee4910376f6748008a70712f08b76a5b932 svn.849\nf9e599e709a08f177a0e9c4a73ed3ea2dc883990 svn.1772\nfa0bbf57fc4c71c4890822f1d927eb8c2a39df20 svn.1154\nfa19b4c0a2c0f2e9c94261256bc28fb7cc421e7f svn.162\nfa2f76a678e5aec01e25d9d2d81702225d28287e svn.1738\nfa5f7353102a01fc485e7d5d83aaae50f60c62b7 svn.373\nfa8a351202d659f65fb62e5720f31e73e046a67a svn.1292\nfabe2f3d8e695775b59a0b43edcec84988230cb8 svn.1159\nfac6f6a5ceb212eb3d9b54a28459d6e9e1425aca svn.685\nfad4f97a8a85bb4274f876f0d05ea0506562a130 svn.873\nfad60561bfd2e187de42f3557dedfcf91fe5a35f svn.844\nfaeb3b49abaff460913359e420c0ffbd3b18f2d0 svn.778\nfaff3e0a69c548eef84546f92fce514b1ffeb21c svn.3\nfb4fae98bac891bf929eb44fc1a4e37fb55d678d svn.1718\nfb56a1daf0b6f7583c2eba1aa3ec5c1155584101 svn.935\nfb6ccc3cccc25c03bef4c242428687ca0e233cb9 svn.820\nfb7dfa722237f83e639e50e6975b71095c168862 svn.640\nfb8e171d63c993cad20c02658ceba5a17d04abe8 svn.1413\nfb9021a8268a9162a239877d4d4980265b40107e svn.180\nfba75965849daca6545bf08ec091ce0f0727446b svn.1643\nfbb08d177e3541d70e36347191970557cc07b95e svn.1170\nfc0d258b65596f92647a4c906141ffd514ca6e25 svn.626\nfc2c570351bd40022d7e58d6d03c3f9a6afca8ef svn.241\nfc3bc9d6eb6bf3f7f12cab13dbb1ab20a0f479df svn.54\nfc41bafd040579e1c799ee4cce9e2128fed3ee15 svn.1\nfc45caaf89a5c4f916f8bf014f15a5cd48ae3a75 svn.963\nfc549023f806baf5442569d6599de2f7d1e08b9c svn.995\nfc60e042b59b43410254fef7c87c83dafa26f012 svn.1437\nfc645ca33d5515b3e453aafa4f84f1c0287b1459 svn.846\nfd07495d021768bc0b40ce4e7033bb8f4c13d6c0 svn.1383\nfd0d1b1fa0e1d577a6fe62c1103f2446a989eb89 svn.1049\nfd0f837bb381b6819bd11233838282c3b941960a svn.1610\nfd12c95c101bb1ef71b4b64fffe25e4be8e087e3 svn.949\nfd150370126ae7eeeee46ab7a672a6c9dc08ed7f svn.1417\nfd221c9cac3f6ba41f1be8aeaea2a67de0c2a68f svn.1480\nfd614c5118ed8cf0866a13cec5a008eeeb7ecad9 svn.1798\nfd6d75b921fd5581e7a13596c313afc242ecb7cc svn.137\nfd910d5f2741be52641a7162a2ccbc6bfde91cf2 svn.964\nfda445ffad25d980c9de21be18ca2f8d6a8c2185 svn.147\nfdb1f6fe5de782bf8ecafd7edafda49587b3747d svn.1147\nfdb3c581672de44f67fb43351db58b6ab543b1d1 svn.483\nfdf9b52fe83d221f74dfa77b86df18f52294a320 svn.1217\nfe02c21efeeeb64afa613396d73baf95c16cc636 svn.225\nfe491964b2f1cefd84dc01e728f17d4dcbd24526 svn.1751\nfe54d44addf4db308f24d45765bf2333aa5f033a svn.458\nfef75ecd30caa3b331dd89e5d47afda1c662d9a6 svn.642\nfef950f2d9d99551e46a72006947ad6c14b7718d svn.439\nff2bd18d3a67d9ea19832775d904f9e8b9b3bce7 svn.1721\nff9f8fc0a2748cbb63d2b0d7407dbd71b1935106 svn.1668\nffe0cbfa8a2e1981cf071b180872333043fde7db svn.993\nfff7118f00e25731ccf37cba3082b8fcb73cf90e svn.371\n008b15bd5122e910e321316fae6122da9a46e10f svn.1636\n0000000000000000000000000000000000000000 svn.1636\n009bca8c4c596f01526bc8159c90786e7c011d67 svn.347\n0000000000000000000000000000000000000000 svn.347\n00d699ef358745ba38f4381c35f2e7d5a6306161 svn.394\n0000000000000000000000000000000000000000 svn.394\n00e034fabfd073790aaa4ef82bbb666582ae9f57 svn.1265\n0000000000000000000000000000000000000000 svn.1265\n01165f5c21dd23b8fadfb36a736c1af7f2f51bea svn.1687\n0000000000000000000000000000000000000000 svn.1687\n011e9277ab04e996ac2d710f64cd361235f4103b svn.658\n0000000000000000000000000000000000000000 svn.658\n0157ff037651bf0146f57adaf95a173222a3937c svn.6\n0000000000000000000000000000000000000000 svn.6\n0161f0770fe9d9a6b45cfff81c6c948bf5ac0d23 svn.112\n0000000000000000000000000000000000000000 svn.112\n0173810f90aa32e9f41f822a74f86d7f9aff9406 svn.417\n0000000000000000000000000000000000000000 svn.417\n019c2ed6f48651f2dbcf680d35869da9aad5d990 svn.1007\n0000000000000000000000000000000000000000 svn.1007\n01a6acd7b8084564c58060b14f54609e88f173bf svn.1483\n0000000000000000000000000000000000000000 svn.1483\n01e8bd44c26797472f364c787dc5cce782d086e5 svn.1204\n0000000000000000000000000000000000000000 svn.1204\n022d3c06297845aec35f5649a3237710d8b9f55f svn.1802\n0000000000000000000000000000000000000000 svn.1802\n023b31f57812c07152cd34d79f1f9909cae1e556 svn.83\n0000000000000000000000000000000000000000 svn.83\n028e083af0708acaa2050533839115d8a69b9a0c svn.1696\n0000000000000000000000000000000000000000 svn.1696\n02f2b6cd7ea1d2d2d357475f2204f2410cf892ab svn.1205\n0000000000000000000000000000000000000000 svn.1205\n03164a14f6b23fc6b8bc7abc6c90ab9680740084 svn.646\n0000000000000000000000000000000000000000 svn.646\n03c59e910c318794f99952c90dd146dc7e01e197 svn.258\n0000000000000000000000000000000000000000 svn.258\n040638d12856f79baa0d005d14e11cd36ba8a331 svn.330\n0000000000000000000000000000000000000000 svn.330\n0484b002a76a99a55a13454b6ce17ef45400316b svn.1356\n0000000000000000000000000000000000000000 svn.1356\n048721d682f8148428a77d82c59d904f3d9c8504 svn.884\n0000000000000000000000000000000000000000 svn.884\n04a629b15da3ff3678e362065cf884834bd02a22 svn.1197\n0000000000000000000000000000000000000000 svn.1197\n04bff5f55cb9e853b6183000e065b0080bcea8dd svn.1002\n0000000000000000000000000000000000000000 svn.1002\n04c2b901d8a05070295cbe351fa0c43291ef624c svn.1173\n0000000000000000000000000000000000000000 svn.1173\n04c660cac68b46d16341e9c455dc372b1334efda svn.1224\n0000000000000000000000000000000000000000 svn.1224\n04db5f9b3122cc2bf749cc2e53383eb86c853a51 svn.769\n0000000000000000000000000000000000000000 svn.769\n04dff7a15ba545a16a93f50d7b125a6fca0ed10f svn.1707\n0000000000000000000000000000000000000000 svn.1707\n04e29cfda98d62bab16d2053c3b7a774b04298f0 svn.1445\n0000000000000000000000000000000000000000 svn.1445\n059a6f91c2e580b60123f7ac45983801fbd512bc svn.1257\n0000000000000000000000000000000000000000 svn.1257\n05a1599e4916c970d398b641bbdb3953f0907ea8 svn.8\n0000000000000000000000000000000000000000 svn.8\n05b44aec7b74826628f5bf03adec7d84511efc65 svn.1411\n0000000000000000000000000000000000000000 svn.1411\n05f3e58bc4b9a0b136659a69df4f1d4d05d0702d svn.1613\n0000000000000000000000000000000000000000 svn.1613\n060a01faf6ae89a6efb3f6d16fdfee0360e36ecd svn.1757\n0000000000000000000000000000000000000000 svn.1757\n060cccf0ba19182d334bbdeb576d8f58bf0d71c7 svn.200\n0000000000000000000000000000000000000000 svn.200\n0637b5808587b7c6e5425330d61045a0ebfc3c7a svn.1035\n0000000000000000000000000000000000000000 svn.1035\n0669ff70d50dc275904335d6e5560d8fba1bd524 svn.1014\n0000000000000000000000000000000000000000 svn.1014\n0682073503fda2662e7a5d1fdf205787cec2ef29 svn.653\n0000000000000000000000000000000000000000 svn.653\n06d5806a711f394f6a5889da1214051550ff66c6 svn.802\n0000000000000000000000000000000000000000 svn.802\n06e806dbd816945b98448239023700b9ce1feb4f svn.410\n0000000000000000000000000000000000000000 svn.410\n06e83f7e374489e7e930bbb3233a7d48da2a4325 svn.930\n0000000000000000000000000000000000000000 svn.930\n0763601a5bcc18dffc999f8889aaa24a3954b855 svn.234\n0000000000000000000000000000000000000000 svn.234\n07780c9b1b42083cbf29fa4aad7195c65a7155cb svn.1044\n0000000000000000000000000000000000000000 svn.1044\n0791309fea40c03510ad3513de4fddb084697d80 svn.1058\n0000000000000000000000000000000000000000 svn.1058\n07981139bfd9fdf363c872de11db226c8431841a svn.1318\n0000000000000000000000000000000000000000 svn.1318\n079f4596e62fc77247141ff779774a785061abfc svn.1633\n0000000000000000000000000000000000000000 svn.1633\n07db277ec95af7cb9849096730666b335bcc9c5e svn.1094\n0000000000000000000000000000000000000000 svn.1094\n07dddd86d8fe37233ea506155e3dea8e4280a38d svn.1579\n0000000000000000000000000000000000000000 svn.1579\n07f45d81abfb32c883e9f29ff2d6ca442c62faac svn.968\n0000000000000000000000000000000000000000 svn.968\n083153346e4989561bf77e67bcf8b07d6d8ee0d7 svn.475\n0000000000000000000000000000000000000000 svn.475\n085616db39c365408b3b2713cec63cdc75c1b0d3 svn.189\n0000000000000000000000000000000000000000 svn.189\n08930d496c9b0e758bf53d5b8024ded0179cf44f svn.370\n0000000000000000000000000000000000000000 svn.370\n08954e47091923482a4ef97dde985554cc9a23df svn.288\n0000000000000000000000000000000000000000 svn.288\n08b7d403a90b550912cf513433a5d093d5745250 svn.332\n0000000000000000000000000000000000000000 svn.332\n08d3aad985e07d28e217cce7e8849b24d1c18ae3 svn.1435\n0000000000000000000000000000000000000000 svn.1435\n08d7629cc529e025b1f85fb5d45326975f48b84b svn.45\n0000000000000000000000000000000000000000 svn.45\n08dca756db3206be21541d869dfccd5dc43247f9 svn.1379\n0000000000000000000000000000000000000000 svn.1379\n08febc793acd54cf4b76bbeddcb79637f1553470 svn.1645\n0000000000000000000000000000000000000000 svn.1645\n0927e0c91ca66d1d41500c9fd08cde5014ceacf8 svn.1139\n0000000000000000000000000000000000000000 svn.1139\n0954054bc59272f7974c63cd77933ea344c64619 svn.744\n0000000000000000000000000000000000000000 svn.744\n0963334d761e9d8db2acf74639cb938c250c8302 svn.1495\n0000000000000000000000000000000000000000 svn.1495\n099aa96010cdda52c5f2fc83d72e7e03275acd86 svn.1700\n0000000000000000000000000000000000000000 svn.1700\n099b4e43b52a38fa49164d22726650111cb89f30 svn.1329\n0000000000000000000000000000000000000000 svn.1329\n09b6347351e9fa337cddc10d39c929b497cc7634 svn.1778\n0000000000000000000000000000000000000000 svn.1778\n09e730a2c4fb77abfff2ae0dc0e9ab753683815f svn.854\n0000000000000000000000000000000000000000 svn.854\n09f95c184d9e12dfdfdce597b3812f81976c68fd svn.898\n0000000000000000000000000000000000000000 svn.898\n09fd3ab58e68418a1cceb9db1938655723a8732d svn.1222\n0000000000000000000000000000000000000000 svn.1222\n0a3cd2ca46aa4a913dc6cca250deba0aba2f7661 svn.536\n0000000000000000000000000000000000000000 svn.536\n0a661cfd8bcf95d37bcba79cd5eb0b8d194a4228 svn.24\n0000000000000000000000000000000000000000 svn.24\n0a70307856e719657be483e103df1fb3803e6344 svn.182\n0000000000000000000000000000000000000000 svn.182\n0a722d174c5e259d343b64af2dff46682815c84b svn.298\n0000000000000000000000000000000000000000 svn.298\n0a929e7c80049aa2dba28242c3cfa97b2e3a7bb1 svn.402\n0000000000000000000000000000000000000000 svn.402\n0a9a9685cb883fddbba2ad166cfec47924102134 svn.499\n0000000000000000000000000000000000000000 svn.499\n0a9e87864df2ff4434732ae7eba7734630cba360 svn.1192\n0000000000000000000000000000000000000000 svn.1192\n0ae3ae67d23e4014f104007447a53bfe770fbbd8 svn.951\n0000000000000000000000000000000000000000 svn.951\n0b0aeb725d723795421475cd3bd285bb0c94c713 svn.1432\n0000000000000000000000000000000000000000 svn.1432\n0b0be6afa3e6db0011dbc2d132c0f2ede7c1c905 svn.983\n0000000000000000000000000000000000000000 svn.983\n0b3713cd3b3dafc68054cbcfdaa80442c11ff172 svn.88\n0000000000000000000000000000000000000000 svn.88\n0b78bcc9c1b17e85cd5a57242d3657c2c1bb7a0c svn.559\n0000000000000000000000000000000000000000 svn.559\n0ba47ee1dc4abf6efc06cb04b5994c728572fefe svn.1178\n0000000000000000000000000000000000000000 svn.1178\n0bb40f1e128c4eb6cfc42c4fc043f7cb54550de5 svn.1130\n0000000000000000000000000000000000000000 svn.1130\n0bc0bdac8bc20d2fb314ae00ccad146883a3311f svn.725\n0000000000000000000000000000000000000000 svn.725\n0beb7e783548eb33c55ffe1ceb0c64fb895b511a svn.341\n0000000000000000000000000000000000000000 svn.341\n0bf4ca209b1f9fc18947bd604d05ac624094d8a6 svn.1304\n0000000000000000000000000000000000000000 svn.1304\n0c004242b3f8c110502e4ea6ee1bdb24d6bc75cf svn.271\n0000000000000000000000000000000000000000 svn.271\n0c4173bd9d0801ed84dd884c76874bf96c66cc12 svn.720\n0000000000000000000000000000000000000000 svn.720\n0c561365081c119bd4d7240258909a38e2f4d03f svn.427\n0000000000000000000000000000000000000000 svn.427\n0c7909677b752a79eadcb4325aab9b34f0cb101d svn.1190\n0000000000000000000000000000000000000000 svn.1190\n0c987873f61cce2b19b2f227520252c624e7f180 svn.123\n0000000000000000000000000000000000000000 svn.123\n0c9fa0cfd51283b0531891e486402110005dc0cc svn.1022\n0000000000000000000000000000000000000000 svn.1022\n0cb628110a0948ef3b8d5f352695321800ec2ff2 svn.1060\n0000000000000000000000000000000000000000 svn.1060\n0ce364970738c599e6f1aa27aebf59ddbde79faa svn.253\n0000000000000000000000000000000000000000 svn.253\n0cf48554d6a7d95cae64d5a28fe4ffdcdf6e5efb svn.1766\n0000000000000000000000000000000000000000 svn.1766\n0d4a60cf51319720ecd77e7a58dbf59d1a018f45 svn.1017\n0000000000000000000000000000000000000000 svn.1017\n0d53d3fa1c6aedc57a4e9b9683f0245f8ee1a65e svn.1492\n0000000000000000000000000000000000000000 svn.1492\n0d5c2d29d28dbd71905afe6889d79c8339519beb svn.1026\n0000000000000000000000000000000000000000 svn.1026\n0da3c07627aae61a53ca2a8fe5a728acd1831553 svn.1051\n0000000000000000000000000000000000000000 svn.1051\n0da429b7799c4e9a251921e59006199ae6e58c29 svn.144\n0000000000000000000000000000000000000000 svn.144\n0e1919bee94c9b5095f334c9cd7caea4aa0dc4e6 svn.710\n0000000000000000000000000000000000000000 svn.710\n0e20a44102dafc5fcc23b4d2429e37a38b218282 svn.668\n0000000000000000000000000000000000000000 svn.668\n0e5d5039e575561eb8dc9c8afd89aca00d586489 svn.1730\n0000000000000000000000000000000000000000 svn.1730\n0e5dd9c049a1f89c0d8821dc46f6d8aa29ed1341 svn.412\n0000000000000000000000000000000000000000 svn.412\n0e5f9e70d3a04d88f9b454d8777177aafc48a3a1 svn.64\n0000000000000000000000000000000000000000 svn.64\n0e81d2275949f038fd9874b0394e67976705021a svn.363\n0000000000000000000000000000000000000000 svn.363\n0e938dd99255dbb30683aa7eb73704a60aabcdbd svn.158\n0000000000000000000000000000000000000000 svn.158\n0e9c51767a9ebb496f2ac88bb1de995953b49b59 svn.122\n0000000000000000000000000000000000000000 svn.122\n0ec1bb8306a20d164ad38e02e7b7df60382ac77d svn.118\n0000000000000000000000000000000000000000 svn.118\n0ecf19fa27577dd5f386ad6153da79966eb19f04 svn.1027\n0000000000000000000000000000000000000000 svn.1027\n0ed17274b6410a68dd2bf6d82499d5b79a7fbd60 svn.974\n0000000000000000000000000000000000000000 svn.974\n0ee8ebbbafaf6a2966dfaaf8e58c559832dd1797 svn.539\n0000000000000000000000000000000000000000 svn.539\n0ee914b3cb3b97c478c6e10a1fceda690b04af45 svn.522\n0000000000000000000000000000000000000000 svn.522\n0ef34494c8ed215eaeeb41775d671b3da55a23fc svn.445\n0000000000000000000000000000000000000000 svn.445\n0f0c7cb6975400e566e78aa88ea15dec8e937778 svn.447\n0000000000000000000000000000000000000000 svn.447\n0f1154a13b23e757639ace21e41596b061f50133 svn.661\n0000000000000000000000000000000000000000 svn.661\n0f15cd632305fe76dfc6d1639ba093c6a2cf652e svn.1605\n0000000000000000000000000000000000000000 svn.1605\n0f214513456482b170e01e3226ac34b6e9cc7532 svn.1165\n0000000000000000000000000000000000000000 svn.1165\n0f57be523b3ec9f88963f1964ab63c7e5f4def4b svn.1625\n0000000000000000000000000000000000000000 svn.1625\n0f8d2d911aa1c061320825dafda295b56cca2933 svn.1400\n0000000000000000000000000000000000000000 svn.1400\n0fd851c1be1b98520ac273b5ccfc6510a7593006 svn.761\n0000000000000000000000000000000000000000 svn.761\n0fdd63f1c8fd2a6063c0f92bc9024b7ac9ade49a svn.1157\n0000000000000000000000000000000000000000 svn.1157\n0ff85e6c512a4d14e9dd71ea373efa0c76b24da0 svn.324\n0000000000000000000000000000000000000000 svn.324\n105de8e9c3b4083203db5dd507b2063b6ef9f1bd svn.738\n0000000000000000000000000000000000000000 svn.738\n1098a40cd1bf368573e2178218a4bada315b99a3 svn.979\n0000000000000000000000000000000000000000 svn.979\n111afe4ac34dd880df57aa7c32651d1150bbf625 svn.1765\n0000000000000000000000000000000000000000 svn.1765\n11614afba0f7ba29930970da8a55708bdbc14254 svn.864\n0000000000000000000000000000000000000000 svn.864\n1162e6328a888cf4b1d83fe5c10aaec511a680c8 svn.129\n0000000000000000000000000000000000000000 svn.129\n118e1fd5a88e88e9b8efdfe273df0b8cb1a10838 svn.733\n0000000000000000000000000000000000000000 svn.733\n11a938db9e02a14bccf251c1de2f564844afa5a0 svn.579\n0000000000000000000000000000000000000000 svn.579\n120f50838cd6598bd4cdcef4e2bf87a22a502207 svn.1320\n0000000000000000000000000000000000000000 svn.1320\n122a58cfd7b4842fbb9317ab01ba2c39f5b0c444 svn.285\n0000000000000000000000000000000000000000 svn.285\n127e39229e9a70084beb16bfde1bf1bde81e0429 svn.560\n0000000000000000000000000000000000000000 svn.560\n12b70ef667d79c49bce71cec7bfe6289bd073e24 svn.722\n0000000000000000000000000000000000000000 svn.722\n12db47fb546b07451b51c62a855e15977ccfbbff svn.1783\n0000000000000000000000000000000000000000 svn.1783\n13335553c66d4f48e9ac2ec49cd064d1c0e0f1c7 svn.827\n0000000000000000000000000000000000000000 svn.827\n133f6efb6e1b4ab30e989c793a72123f76d8fc34 svn.830\n0000000000000000000000000000000000000000 svn.830\n1343353d1ff63c13432208a5b7c933e8c947e1c0 svn.1255\n0000000000000000000000000000000000000000 svn.1255\n1356fca5f8ff55d7c3a8aff88e6519d5c5637f0f svn.403\n0000000000000000000000000000000000000000 svn.403\n13576ff5249f3cb5a0c41f6186850801c11295f9 svn.114\n0000000000000000000000000000000000000000 svn.114\n136169c5ee3d74488d2f08929d58d2919f053bd7 svn.829\n0000000000000000000000000000000000000000 svn.829\n1371fe7d04067853541990eb05c04b2cd8b0cce2 svn.858\n0000000000000000000000000000000000000000 svn.858\n137d069b382d7175cf5d425fae13d9f20a8d9005 svn.1433\n0000000000000000000000000000000000000000 svn.1433\n13bc25d9c7e68124d74e7d3ff85dc0475a12cc37 svn.1366\n0000000000000000000000000000000000000000 svn.1366\n143e18f3ce685a33208ce4f470b3ec79e1c2a5ab svn.385\n0000000000000000000000000000000000000000 svn.385\n149036420e91872c752c4ce518bdfeff70330a62 svn.1135\n0000000000000000000000000000000000000000 svn.1135\n1490fcfb24c70f19f8821224994b546915f5b4b0 svn.1563\n0000000000000000000000000000000000000000 svn.1563\n149c0fa98e192e90bed74344a8ae5ebbf407064d svn.120\n0000000000000000000000000000000000000000 svn.120\n14ac1c49a658eef1e9ee29e22fa2e0d4b91a3237 svn.704\n0000000000000000000000000000000000000000 svn.704\n14b6fa5edc2f5453e9e8ab8113930d53e961374f svn.1789\n0000000000000000000000000000000000000000 svn.1789\n14bdaf98da0d90eb722eeaec6157de8d0d0408ac svn.1046\n0000000000000000000000000000000000000000 svn.1046\n14f866bbf50c4cd99474e150129144f9c67c9ea2 svn.437\n0000000000000000000000000000000000000000 svn.437\n150f74c95526a7387bc828f957348029f71e61a6 svn.897\n0000000000000000000000000000000000000000 svn.897\n153daa22d893d844ae6797ce8b33bacf016b81f3 svn.91\n0000000000000000000000000000000000000000 svn.91\n159874fa0df3577327f99ed11df19c03bd7d9926 svn.487\n0000000000000000000000000000000000000000 svn.487\n15a63d9a7b7190464132e8f36820b1c69f121458 svn.786\n0000000000000000000000000000000000000000 svn.786\n15cac1fda9ddd07988f4b0a222d09bb89beda8cb svn.1279\n0000000000000000000000000000000000000000 svn.1279\n164274e4466e0a8cabd81f8342ccebc918a5121a svn.477\n0000000000000000000000000000000000000000 svn.477\n165bddcbfa004dea70fabe80f82da668831760d0 svn.329\n0000000000000000000000000000000000000000 svn.329\n1665a35cd4ea1b29b6bd93b0d82d423bed317294 svn.541\n0000000000000000000000000000000000000000 svn.541\n166738006c3a058f301632619fbaa9fdd7800e2b svn.495\n0000000000000000000000000000000000000000 svn.495\n167dd46aa411490021352403f7e408ca176e640d svn.556\n0000000000000000000000000000000000000000 svn.556\n16876332fbaab496fa75d8b4cef80d32a096bf15 svn.1091\n0000000000000000000000000000000000000000 svn.1091\n16ac4b5438f69ba8320cde2e7c86bf1e50696c1e svn.1249\n0000000000000000000000000000000000000000 svn.1249\n17539cf7cec90818d553f47d103067a66b6ece5f svn.75\n0000000000000000000000000000000000000000 svn.75\n177a80a5c2cb7737fd16802e2623d72762e09a33 svn.1183\n0000000000000000000000000000000000000000 svn.1183\n17853da5da9438d1549e0b7a74cc590c78102497 svn.1300\n0000000000000000000000000000000000000000 svn.1300\n178f1ce70fbd58e636295d8d74de81c2e481046d svn.32\n0000000000000000000000000000000000000000 svn.32\n1790cffb14fc49234d98115217d574f918d05b90 svn.878\n0000000000000000000000000000000000000000 svn.878\n1799a24c3cc76b221815808e883ac018e7cd987a svn.428\n0000000000000000000000000000000000000000 svn.428\n17d411ced9321408ea900b0e5eb89b46c3f43eed svn.214\n0000000000000000000000000000000000000000 svn.214\n17e94e863ca4a724e2e0cd4ab12023872ec4a74c svn.759\n0000000000000000000000000000000000000000 svn.759\n17ffb91f1954a49e66d0809489b74d5f42abffa0 svn.104\n0000000000000000000000000000000000000000 svn.104\n181f4e0072c301c4fbb3146a1558fbf82ab8b4e7 svn.1270\n0000000000000000000000000000000000000000 svn.1270\n183ed087726cb1158a7ada1fd9ea59e726dff4de svn.1259\n0000000000000000000000000000000000000000 svn.1259\n18495205f69e0f05998f0760c9a3ebf1d58fd1a9 svn.1391\n0000000000000000000000000000000000000000 svn.1391\n186b361ed7e6197c89f71dd5a32db8da0178cb89 svn.826\n0000000000000000000000000000000000000000 svn.826\n18880acd09ee8e6dae8fca4442c426bddec35784 svn.1037\n0000000000000000000000000000000000000000 svn.1037\n189409797cf12cbefb0028a1f969149fdd7d17ed svn.1628\n0000000000000000000000000000000000000000 svn.1628\n18a1dc2a911eeddea1fe71d83759f4eef9b73cc4 svn.709\n0000000000000000000000000000000000000000 svn.709\n18ea5b4ac63865d808f56b04b0c97a08fb314c58 svn.1132\n0000000000000000000000000000000000000000 svn.1132\n190508aa312959c217130a0dabd459ac4b85c043 svn.1099\n0000000000000000000000000000000000000000 svn.1099\n190731ac2ebeeed836dd407f8f6ffe966ec95016 svn.950\n0000000000000000000000000000000000000000 svn.950\n191e425284f01d40d1b4e1c7b40bd86611ff0fde svn.1401\n0000000000000000000000000000000000000000 svn.1401\n19400f194230d1b999065ef031279aff724a0590 svn.1195\n0000000000000000000000000000000000000000 svn.1195\n1959f7bb7ef09e346e60ffdefc72de8b56b8139e svn.1063\n0000000000000000000000000000000000000000 svn.1063\n195a8ad57107ac668cb656f26cdbb6f8b96b9dc1 svn.1156\n0000000000000000000000000000000000000000 svn.1156\n1974365b127a40e9e62b041b6252c902f204c0b7 svn.1640\n0000000000000000000000000000000000000000 svn.1640\n19aa76db8a96347d67f1b07cd1fc8a1f26dce3b6 svn.1229\n0000000000000000000000000000000000000000 svn.1229\n19d2296f62d8f2e2ebbc7f823608d60683ddbc65 svn.161\n0000000000000000000000000000000000000000 svn.161\n19e098d6fbf349a90d146cb8be3142e418851ba1 svn.393\n0000000000000000000000000000000000000000 svn.393\n1a29c93406b03f5f51bdfde9d1d8e71a5fd0a014 svn.1598\n0000000000000000000000000000000000000000 svn.1598\n1a6fa6bbf1d839de617cf6d731f124780505c854 svn.1396\n0000000000000000000000000000000000000000 svn.1396\n1aa14a655fceeb59dc35419dadaa3eb9b3ade3a9 svn.16\n0000000000000000000000000000000000000000 svn.16\n1af453e638d1761025e8dadd053d4ceb8d79e90e svn.167\n0000000000000000000000000000000000000000 svn.167\n1b0b000a008adecc82d6fbfb2ba683770140c849 svn.1422\n0000000000000000000000000000000000000000 svn.1422\n1b0db0bf5599a565815ae8de98254b653afc8a94 svn.159\n0000000000000000000000000000000000000000 svn.159\n1b0f3eaf60133f5daabf8876df225d7e17a2d7ca svn.273\n0000000000000000000000000000000000000000 svn.273\n1b1fbe998eb3a689db2b2cead578f0bad2bee23a svn.855\n0000000000000000000000000000000000000000 svn.855\n1b2d2b328f1a2268014054a1a106131524b9a58e svn.1732\n0000000000000000000000000000000000000000 svn.1732\n1b41f55e39f627f352939c4c40d223666cadd40f svn.108\n0000000000000000000000000000000000000000 svn.108\n1b7c2ab8c72587e372905ba952d301c02fcfa35d svn.191\n0000000000000000000000000000000000000000 svn.191\n1b7d38aae84dfc9a011ad76c2542c138f4bf720b svn.514\n0000000000000000000000000000000000000000 svn.514\n1b8a4c4629be438e8e48b7d5f471b61f7e224eeb svn.294\n0000000000000000000000000000000000000000 svn.294\n1bb32ee8b90599f4e01a894eb8f4e1edb7161782 svn.1036\n0000000000000000000000000000000000000000 svn.1036\n1bf7e34e171d1a8758f2b7176b12ab995241c4cc svn.700\n0000000000000000000000000000000000000000 svn.700\n1c37aebefb76e9e26438089b588c1111f1b6d1d7 svn.796\n0000000000000000000000000000000000000000 svn.796\n1c40d66bae3c4bf0d1f101ffeb0acea168926cad svn.1762\n0000000000000000000000000000000000000000 svn.1762\n1cb76ee3636da871dedcf57a83da3866fc73bbfd svn.1021\n0000000000000000000000000000000000000000 svn.1021\n1cc9cf146664844e360b073dc070fa05d08eaf20 svn.679\n0000000000000000000000000000000000000000 svn.679\n1cf9d0e83cff3079412fd45aa2ee0cac66672588 svn.910\n0000000000000000000000000000000000000000 svn.910\n1d0c7035c1bacb1f256e0a47534c19aeffeee35d svn.946\n0000000000000000000000000000000000000000 svn.946\n1d26ca33c8b44c8c5e8524fed63736780a9e721d svn.1283\n0000000000000000000000000000000000000000 svn.1283\n1d30cfbd7a6696dd86c635b3faa263e95194733f svn.991\n0000000000000000000000000000000000000000 svn.991\n1d3e14608e450f1c05fbbd63e3bae8fe8498531a svn.509\n0000000000000000000000000000000000000000 svn.509\n1d9947e7c68242f9a8cb1845c22199d3aabf2dd0 svn.1065\n0000000000000000000000000000000000000000 svn.1065\n1da8c1d4b86bf7fe632bab0fa19bd5a0860b6414 svn.909\n0000000000000000000000000000000000000000 svn.909\n1db0d4260c049fa27e83c262405e37e672f09f70 svn.142\n0000000000000000000000000000000000000000 svn.142\n1dd91bfad08fe300ed3202e0b9885434069db507 svn.1446\n0000000000000000000000000000000000000000 svn.1446\n1e39a8267b63e6c937d0d9bca0be7ba96e884d9a svn.1214\n0000000000000000000000000000000000000000 svn.1214\n1e4ad371a3e51fc6cf8bff8832e307408ea23fb0 svn.1443\n0000000000000000000000000000000000000000 svn.1443\n1e5b976a4d43a87b53f6a10ecea1556ede6abb89 svn.729\n0000000000000000000000000000000000000000 svn.729\n1e5bad3af0c62d39a4ff1c997c3d69ce6e93f739 svn.1082\n0000000000000000000000000000000000000000 svn.1082\n1e6351ec0582687c7177e9cdca621c18d9c36df7 svn.932\n0000000000000000000000000000000000000000 svn.932\n1e6640c409dd5d809c24df8a1b296359ced079ab svn.1708\n0000000000000000000000000000000000000000 svn.1708\n1e844764a83b00bae1a1990b8a30fbcac149de66 svn.1015\n0000000000000000000000000000000000000000 svn.1015\n1ea258dafaa4ce328fb224cb7e04b670e3c0856f svn.1239\n0000000000000000000000000000000000000000 svn.1239\n1eb85913bbe94398eb973722914de95b28a8f69a svn.902\n0000000000000000000000000000000000000000 svn.902\n1ec3baa5fbf66a9caca0a9b224563d5b28374b22 svn.380\n0000000000000000000000000000000000000000 svn.380\n1ecf758054d3328567440ee82fa62fe0c316deac svn.1790\n0000000000000000000000000000000000000000 svn.1790\n1ee66345f10b4429ed1c5b5d3ddc7fd63f05c0ab svn.1710\n0000000000000000000000000000000000000000 svn.1710\n1f122ef5ebd5f88e6cc8bae94fa39db423620120 svn.1233\n0000000000000000000000000000000000000000 svn.1233\n1f182f60f8208c83a22f0d617941a4d1408b649f svn.376\n0000000000000000000000000000000000000000 svn.376\n1f2849d4a56fa5d7909ecf56a1c5643fe76d7e34 svn.113\n0000000000000000000000000000000000000000 svn.113\n1f583c98024f4582ff3b16447f439663b4ae287e svn.1702\n0000000000000000000000000000000000000000 svn.1702\n1f8bab2426fb59f2f4243844626b3ca6b11d75ec svn.896\n0000000000000000000000000000000000000000 svn.896\n1f8c62426fffedfd9471ceedc1096174461c97e7 svn.215\n0000000000000000000000000000000000000000 svn.215\n1fd4998e77ffd91041d43840b7a70c1be34cc759 svn.611\n0000000000000000000000000000000000000000 svn.611\n1fd4b02732eb20bbf3c3f5c4c46d8533f14c166a svn.349\n0000000000000000000000000000000000000000 svn.349\n2048acb78d9f9e117a0841b6a9229f01f56b063f svn.972\n0000000000000000000000000000000000000000 svn.972\n204b732a1ad80ad9f6c2f6e904e418ff1556b23a svn.1244\n0000000000000000000000000000000000000000 svn.1244\n2054ca11283e92918cf372404bf773e6c09ab140 svn.782\n0000000000000000000000000000000000000000 svn.782\n206c669b2af6d8c568531423ce447cfd2fd1e122 svn.96\n0000000000000000000000000000000000000000 svn.96\n208f087b08e1f353ea1d96d735e53c0d84a2fec2 svn.1218\n0000000000000000000000000000000000000000 svn.1218\n20d3e642f40308d6ce2bf2352d19bd60046bd474 svn.30\n0000000000000000000000000000000000000000 svn.30\n20d866b7ac2534392e54ea71c724940a91e926c4 svn.386\n0000000000000000000000000000000000000000 svn.386\n20e6708ceec570278362d6d32ad64228e8d104af svn.774\n0000000000000000000000000000000000000000 svn.774\n2105424868d0ddfaac6c68356bcc296903e5e2c6 svn.1243\n0000000000000000000000000000000000000000 svn.1243\n211283d83105ce19eb95f5c9f26b2b8e9577ab66 svn.756\n0000000000000000000000000000000000000000 svn.756\n212bf834fc7f2a96f407eaca2fc11107a28c7b95 svn.1061\n0000000000000000000000000000000000000000 svn.1061\n21a01aef37253089ca9607afbe6bee41617cee94 svn.795\n0000000000000000000000000000000000000000 svn.795\n21ab4b3e629c084ba8f6e39568efc7cb0bea893c svn.1221\n0000000000000000000000000000000000000000 svn.1221\n21bfc577a0793032cf3180f3210b80be465a9b47 svn.1597\n0000000000000000000000000000000000000000 svn.1597\n21c80a55ccdfeee4164df15049b6ac883dd42886 svn.591\n0000000000000000000000000000000000000000 svn.591\n21ed3bd276caee7cad233138cd85a77985ebfdf4 svn.1184\n0000000000000000000000000000000000000000 svn.1184\n224c097dde0c7544d7aebd0c7fc4f3b398ee4ead svn.564\n0000000000000000000000000000000000000000 svn.564\n2260c13777ed3b984596262a7abcf3d74949b74a svn.1112\n0000000000000000000000000000000000000000 svn.1112\n2267dbefda0b97873e7e212a9ff953f123c79dbc svn.791\n0000000000000000000000000000000000000000 svn.791\n2283ca6161ff49a0a06f25cf2c4718d3f6a8dd6c svn.1461\n0000000000000000000000000000000000000000 svn.1461\n22d90c022f15abbb36d598f0fc12634b7922da39 svn.1030\n0000000000000000000000000000000000000000 svn.1030\n22dbd0b1ca710d8534f6bcdfc3d473b1d6c3e0d4 svn.325\n0000000000000000000000000000000000000000 svn.325\n22e6c80fe419953eec283f3f4583ed609c9f4d08 svn.1654\n0000000000000000000000000000000000000000 svn.1654\n23262ad8a41ebca12807a1ca65b1cfd95c2d076d svn.1160\n0000000000000000000000000000000000000000 svn.1160\n2384a711554a63d4cf9e354848065244b4731419 svn.1503\n0000000000000000000000000000000000000000 svn.1503\n23a1a6bf68f52048a0831f583593073751306164 svn.1127\n0000000000000000000000000000000000000000 svn.1127\n23e1b62af018845944937b5cca4a15866647e644 svn.1223\n0000000000000000000000000000000000000000 svn.1223\n23fae50a1832b41d61b3b0a0a2fb1a8f476821b2 svn.666\n0000000000000000000000000000000000000000 svn.666\n245b211eedfa9ab0f96b24de757b65435075feac svn.1185\n0000000000000000000000000000000000000000 svn.1185\n2476faf0f3ea9fb561ff3789d4324e755524f73a svn.1458\n0000000000000000000000000000000000000000 svn.1458\n247ff6398810d16538afa9b8cba01c792edc416a svn.1068\n0000000000000000000000000000000000000000 svn.1068\n24a8cd53314b5547ef2e524c77fee58d6334ec8e svn.1459\n0000000000000000000000000000000000000000 svn.1459\n24afb3cb63a0a1d7f539c942e746a0746b73bcc5 svn.1427\n0000000000000000000000000000000000000000 svn.1427\n24be36f110bc8c3fb2167e74f09f7a99f6d20803 svn.272\n0000000000000000000000000000000000000000 svn.272\n24c57902ed27f8cb3a4ca1aafc3b66fbd44fa68e svn.1407\n0000000000000000000000000000000000000000 svn.1407\n24e7e09a8e48bf287b911e5e71243a3892427107 svn.913\n0000000000000000000000000000000000000000 svn.913\n25056d70ffd70f590ad36853beeab02ab05b6a90 svn.1303\n0000000000000000000000000000000000000000 svn.1303\n25064dde309e79255dc027ccac12860b311f97da svn.1264\n0000000000000000000000000000000000000000 svn.1264\n250cd77c08d85ebab76ecc9e4990cc33ee344767 svn.1293\n0000000000000000000000000000000000000000 svn.1293\n257c5a7b6c8ef95ccf53da55f692f517a28e6342 svn.282\n0000000000000000000000000000000000000000 svn.282\n25d4c96964f04c8790e12ab8b8144939180ba462 svn.922\n0000000000000000000000000000000000000000 svn.922\n263dad5f5c2b1571208e7f52f8488c0f189e95a2 svn.1796\n0000000000000000000000000000000000000000 svn.1796\n266d5d83cf04ff59050e884077f9dabb56b65b2d svn.179\n0000000000000000000000000000000000000000 svn.179\n267d7c34121d8fc3579ba5d148e7080074a0e927 svn.772\n0000000000000000000000000000000000000000 svn.772\n2724aaaa917db3e5d80d8bf547aa6be9f0f2d17e svn.401\n0000000000000000000000000000000000000000 svn.401\n2733c3d4b6355f77b6602b72999a006407d10e3a svn.571\n0000000000000000000000000000000000000000 svn.571\n278c2284d6f7e979a8f7260ea27bcc450303f8d1 svn.1337\n0000000000000000000000000000000000000000 svn.1337\n27ba903abf81b7964ae2a50d051ba42183ae1461 svn.740\n0000000000000000000000000000000000000000 svn.740\n27d17ab221f275c243449bc575417a25a70474e4 svn.382\n0000000000000000000000000000000000000000 svn.382\n2815181de43f5b2991aaa55f3e1b86b03d57f892 svn.213\n0000000000000000000000000000000000000000 svn.213\n2824b91af84c42c2690c935dea1991d87b6d0302 svn.1576\n0000000000000000000000000000000000000000 svn.1576\n28661086eb26791d5ed1817b47d2e810da311a98 svn.914\n0000000000000000000000000000000000000000 svn.914\n2884b80f3126f6cae7b874abf66b88c81db8db6a svn.243\n0000000000000000000000000000000000000000 svn.243\n2890691c8b0ce589f0c362b1867e594c3ec4fd0c svn.1424\n0000000000000000000000000000000000000000 svn.1424\n28e7cbf47cf394681cdc554f46ec13f67d42d5d3 svn.513\n0000000000000000000000000000000000000000 svn.513\n28f9faaa116b4e0c8505883f73160aebecdd49a1 svn.35\n0000000000000000000000000000000000000000 svn.35\n29101d2bc32703f921c90b8c47ab679d3ff1efe7 svn.1600\n0000000000000000000000000000000000000000 svn.1600\n29243d349999d43d02851a450f53f031bb40cbb8 svn.803\n0000000000000000000000000000000000000000 svn.803\n2952becb97669b5b69c1d2d9442df916f472f408 svn.1416\n0000000000000000000000000000000000000000 svn.1416\n29694b1e1a5ff373db84659e392c64e74933fcba svn.249\n0000000000000000000000000000000000000000 svn.249\n296968b4e2bddcf37cdebb652b2eaf9d662a87d7 svn.1623\n0000000000000000000000000000000000000000 svn.1623\n2977040634d83c6aa28ab1cce4ba9f397ff6f18f svn.63\n0000000000000000000000000000000000000000 svn.63\n298d12183d3757c6439d66ef136852f7fa5c30e5 svn.1451\n0000000000000000000000000000000000000000 svn.1451\n29acc14771291d9456c9dfb72967a3232b50595f svn.1421\n0000000000000000000000000000000000000000 svn.1421\n29b2b49c3210d1cb1b6400dba253957aa2fbf497 svn.1711\n0000000000000000000000000000000000000000 svn.1711\n29bc4b09998c5e58e5e881617d6e4c0735bccf47 svn.526\n0000000000000000000000000000000000000000 svn.526\n29e4edf612fbbf9815d8d674f899d89bd19c72e6 svn.1397\n0000000000000000000000000000000000000000 svn.1397\n29e77b0d7290dc0add9223c50a656208ca270bc4 svn.235\n0000000000000000000000000000000000000000 svn.235\n2a1293f17651f5a8b96b837366b06e7b366d4406 svn.1364\n0000000000000000000000000000000000000000 svn.1364\n2a5783e9aef1edf1a2f5dc9da94b20ada176db28 svn.1614\n0000000000000000000000000000000000000000 svn.1614\n2a6029d1a826032c2bb80d38023a6fd37e3ac895 svn.1181\n0000000000000000000000000000000000000000 svn.1181\n2a74e5b6ed96bfab3a334059fc6fbfdbe4bda782 svn.583\n0000000000000000000000000000000000000000 svn.583\n2a9a084f0ec1c135287fc824291cf582e2763d1a svn.1808\n0000000000000000000000000000000000000000 svn.1808\n2aea310b02d8eb288828b1e38457527d03a2150c svn.1202\n0000000000000000000000000000000000000000 svn.1202\n2b0b1ad509c4507b1ee6d4d7215c1dc13f86d323 svn.562\n0000000000000000000000000000000000000000 svn.562\n2b3f5df5d18db2808c64b9fe865af6d081b1b1e0 svn.1351\n0000000000000000000000000000000000000000 svn.1351\n2b53901a7520aa476ed751bd0269fee64c015043 svn.480\n0000000000000000000000000000000000000000 svn.480\n2b5da1b0e6f68f6b24eee859c6a5c073b39cce79 svn.1607\n0000000000000000000000000000000000000000 svn.1607\n2b616bac66eb2e928e58fdcd260a263cc1e6af77 svn.821\n0000000000000000000000000000000000000000 svn.821\n2ba2575991fe70542ec0b1e6491a401220e707f5 svn.467\n0000000000000000000000000000000000000000 svn.467\n2bd4b3dbcdb6edb2b971bc78f584ec1534010a5c svn.1460\n0000000000000000000000000000000000000000 svn.1460\n2bfef661b95619faa38099f97e303917441c9a44 svn.895\n0000000000000000000000000000000000000000 svn.895\n2c2e4f27039f9eed5ddf9d08fec08ae079f3e1d3 svn.1609\n0000000000000000000000000000000000000000 svn.1609\n2c775f41d49b7525484622ccda6033e0497c2cef svn.1486\n0000000000000000000000000000000000000000 svn.1486\n2cf4d4f200f58d1d4a38eb10f25a305da25dab74 svn.788\n0000000000000000000000000000000000000000 svn.788\n2cfc0b9e83e1bb5a71b274b1293932c25fecb268 svn.201\n0000000000000000000000000000000000000000 svn.201\n2dc572c5ba89d3e2f234adc0e90fe3c379dc556a svn.434\n0000000000000000000000000000000000000000 svn.434\n2e05790c7ff265722b5e610fa8b4c902dbfdbb81 svn.237\n0000000000000000000000000000000000000000 svn.237\n2e114ae2bedf49862289dee0df21fbc8961d58df svn.694\n0000000000000000000000000000000000000000 svn.694\n2e36f21bfbec38ffe5d016fc24a16fa8c08cf634 svn.1482\n0000000000000000000000000000000000000000 svn.1482\n2e56f05a2f5c2d1affe804672a96d4a64112fab7 svn.1398\n0000000000000000000000000000000000000000 svn.1398\n2e6c33866a213869ef4970cbb9a5fa4251246b41 svn.486\n0000000000000000000000000000000000000000 svn.486\n2e9604d280d1372305aa1e451fb0ff366908070a svn.227\n0000000000000000000000000000000000000000 svn.227\n2f3dc3382cf79dd18095a1d08ae7d86c5ee11fca svn.1242\n0000000000000000000000000000000000000000 svn.1242\n2f7c6f7c38ca0b4a2d59246e98f0785af7c586eb svn.531\n0000000000000000000000000000000000000000 svn.531\n2f7c6f7c38ca0b4a2d59246e98f0785af7c586eb svn.532\n0000000000000000000000000000000000000000 svn.532\n2f7c6f7c38ca0b4a2d59246e98f0785af7c586eb svn.533\n0000000000000000000000000000000000000000 svn.533\n2fa342f70f1ab638e7ff6d29ba0c3f27fc136ca7 svn.614\n0000000000000000000000000000000000000000 svn.614\n2fbffc4b553feb03c2f36b1141e0f272478768e4 svn.994\n0000000000000000000000000000000000000000 svn.994\n2fc8ce9aeb98d5e20f7cd2bf04e74d717728b1ec svn.1228\n0000000000000000000000000000000000000000 svn.1228\n2feee8280510e184223912daf17898a6b9bd8881 svn.1317\n0000000000000000000000000000000000000000 svn.1317\n300b28b4879e45345ebeef6f215e8828074c6f22 svn.934\n0000000000000000000000000000000000000000 svn.934\n3020489da36639adddce2b555be63c0dd19bd838 svn.7\n0000000000000000000000000000000000000000 svn.7\n302bb5f79a4e895853d420e82d9ed656a71b9214 svn.1590\n0000000000000000000000000000000000000000 svn.1590\n3059f8a36287ce425e801b8af84ed6135be18280 svn.1594\n0000000000000000000000000000000000000000 svn.1594\n306126f9d0f1834277b17adc5b38ac567da555b7 svn.842\n0000000000000000000000000000000000000000 svn.842\n30738047b2640774652e1a5bb403c52589ed9747 svn.1232\n0000000000000000000000000000000000000000 svn.1232\n3076d47821899c6ec96abd622a60ff6b6cda0352 svn.1572\n0000000000000000000000000000000000000000 svn.1572\n307f6a434baf2c19ed2f04b81559adfc32a19829 svn.1237\n0000000000000000000000000000000000000000 svn.1237\n30810691843db9af3ac30247b3cd2ee2983ae8cb svn.453\n0000000000000000000000000000000000000000 svn.453\n309bf66bbf164ad0d715097eeed41a115c50021a svn.293\n0000000000000000000000000000000000000000 svn.293\n30a8cf7bd40464cc8b769f88d51c8c1c3cc14763 svn.260\n0000000000000000000000000000000000000000 svn.260\n30c8d69035cd57735a3f86c6dae40e4a6cf5f5bc svn.1578\n0000000000000000000000000000000000000000 svn.1578\n30c9e3ed1cd0af1220a397c058bed58c34d8acd9 svn.889\n0000000000000000000000000000000000000000 svn.889\n30d4b4532a3036cf9839ac248d10454337c49876 svn.1477\n0000000000000000000000000000000000000000 svn.1477\n30e0ce89424861f1280edc2f0a40c4d6592b9119 svn.49\n0000000000000000000000000000000000000000 svn.49\n3122e32e962449ef55ae4dbc65478e5af779c4bd svn.1346\n0000000000000000000000000000000000000000 svn.1346\n3234b6e46b7bc7a18bc8186d8645784f7a5ccb9c svn.408\n0000000000000000000000000000000000000000 svn.408\n326567793ef0406d889de032e525a66d1924f575 svn.464\n0000000000000000000000000000000000000000 svn.464\n326dc0e28cdffdab9e0cb8018b751a7f7741a93e svn.51\n0000000000000000000000000000000000000000 svn.51\n32a5f8a01d909e39b60f87146383831187ae4b7c svn.153\n0000000000000000000000000000000000000000 svn.153\n32d9ef9dafab130232dfdfd629de466a79ffa2cd svn.1748\n0000000000000000000000000000000000000000 svn.1748\n33d50969dd9576bbdf7c28354d26c854cdc2b3cb svn.537\n0000000000000000000000000000000000000000 svn.537\n341577d087f3ea35d05ec6634bbcc956bdb20269 svn.527\n0000000000000000000000000000000000000000 svn.527\n342b79f69394e1ce02d5dd8c24788127aeafb88c svn.1560\n0000000000000000000000000000000000000000 svn.1560\n34b94e7914142149e465a83e458b6c8afccd9a86 svn.166\n0000000000000000000000000000000000000000 svn.166\n3510fc0075849ac1601288b8cfd516cc359a86ee svn.1052\n0000000000000000000000000000000000000000 svn.1052\n353817e819e90bf135e4577de32eb6fccee9670e svn.473\n0000000000000000000000000000000000000000 svn.473\n35575098e21b39de79637d8a698efe95f61ea750 svn.226\n0000000000000000000000000000000000000000 svn.226\n35609ba9b54cb9a4065d956fc359d49d6a1c3645 svn.1469\n0000000000000000000000000000000000000000 svn.1469\n356c0e2a81b234b2d99794f680e7380b0e1987ad svn.1369\n0000000000000000000000000000000000000000 svn.1369\n35759321ad5ba59bcc727870fa0b7634c0efc070 svn.372\n0000000000000000000000000000000000000000 svn.372\n35bb46b3e28fba203eb93a8f0904e70b015978b7 svn.1284\n0000000000000000000000000000000000000000 svn.1284\n35c3413f27068c320d95f4201f7f0cd240d44cea svn.1360\n0000000000000000000000000000000000000000 svn.1360\n35f42dba0eee8a79ef8221e9032072d7f922eabb svn.1660\n0000000000000000000000000000000000000000 svn.1660\n3648f2ee0493f7538f15721cafb438e075da0a5f svn.768\n0000000000000000000000000000000000000000 svn.768\n366a30324964d2c88c2731b027861897d5c2d98c svn.496\n0000000000000000000000000000000000000000 svn.496\n366e5cd0fd14dc9192b82e7f5a18397d74e64314 svn.599\n0000000000000000000000000000000000000000 svn.599\n367338cde81885a08621a3b1359140a3fda977cf svn.582\n0000000000000000000000000000000000000000 svn.582\n3677c19e25bb9311ca1b714f7ad350347afbf323 svn.773\n0000000000000000000000000000000000000000 svn.773\n36b3012bbbc073ed5b2c0de776a2738593167bc6 svn.50\n0000000000000000000000000000000000000000 svn.50\n36b804344505a201df09da75721c99f6ba8a9264 svn.132\n0000000000000000000000000000000000000000 svn.132\n36df8de9d0bc05b7ab6382e3e497b3ff92d4d2ba svn.291\n0000000000000000000000000000000000000000 svn.291\n36fdd3794888b2d9feae5e5ab8392ffdaedcb1ed svn.985\n0000000000000000000000000000000000000000 svn.985\n37246737f6859e4117c9a2d6a1d13b2674678cf0 svn.843\n0000000000000000000000000000000000000000 svn.843\n3767f3d630ab1693ce5886653da52fe320be4432 svn.1806\n0000000000000000000000000000000000000000 svn.1806\n37ad56d4f517181aeff99df835d5e5adb0f85e8e svn.1639\n0000000000000000000000000000000000000000 svn.1639\n3864b1286cf26976ed94cf6a3ec4059edfe08575 svn.1662\n0000000000000000000000000000000000000000 svn.1662\n3885cc7d11fcdbe9656d8c2148bc07a30cdbf494 svn.405\n0000000000000000000000000000000000000000 svn.405\n38e412a867fb28db8d00a4e88a37906c76aab33e svn.812\n0000000000000000000000000000000000000000 svn.812\n38f78a2da49df80bbb58119a235811c50aecdad1 svn.69\n0000000000000000000000000000000000000000 svn.69\n391ee7617763b5904068181ec383952cdd7ee142 svn.1006\n0000000000000000000000000000000000000000 svn.1006\n3951ca57fc0dae3a992fb790654b04dacf71bd03 svn.86\n0000000000000000000000000000000000000000 svn.86\n396464816d2328b92330a78c22846c56e96a429e svn.1226\n0000000000000000000000000000000000000000 svn.1226\n39c43000ec6db4e62ff01772d7521afb796a8af9 svn.155\n0000000000000000000000000000000000000000 svn.155\n39d85e6ced187b299c627269f0b6d52c691fb24d svn.1635\n0000000000000000000000000000000000000000 svn.1635\n3a13d92a0679b693d25142b17cbae857c79c0b2d svn.1697\n0000000000000000000000000000000000000000 svn.1697\n3a3a0158bafeb652c5e7a4f74552e180534d1572 svn.40\n0000000000000000000000000000000000000000 svn.40\n3a477287d9d2332aa9a924d8282256751bb60712 svn.916\n0000000000000000000000000000000000000000 svn.916\n3a8ff8b2db71d8537af42c5c5cb5ec968af75218 svn.13\n0000000000000000000000000000000000000000 svn.13\n3aae41cd34983023a70726fc5cf78e8ffd3badfb svn.151\n0000000000000000000000000000000000000000 svn.151\n3b037108ef0ea89914009142b1e8ecaf70e1121c svn.1367\n0000000000000000000000000000000000000000 svn.1367\n3b1782228da51a8db7c41c35791cfb503ed28a94 svn.1537\n0000000000000000000000000000000000000000 svn.1537\n3b51aaeb60e63b2235d486c3b01f08d6d7e99d7d svn.604\n0000000000000000000000000000000000000000 svn.604\n3b7885d4f4e8e6923fdac19ce7a13996a0d53f06 svn.71\n0000000000000000000000000000000000000000 svn.71\n3b9205676f5ac264154b4098908b8e0e81f03cdb svn.1106\n0000000000000000000000000000000000000000 svn.1106\n3b9a7834cb3009820861484876f55918f2475053 svn.787\n0000000000000000000000000000000000000000 svn.787\n3beab6acbd602831fa8c3b0c7dcdb52b330f38f9 svn.682\n0000000000000000000000000000000000000000 svn.682\n3bf0dc8a1b82fedad25e742882e1da7c137a53c0 svn.176\n0000000000000000000000000000000000000000 svn.176\n3bfb0809df65d84e820c9d0ec5800b2569589a4a svn.390\n0000000000000000000000000000000000000000 svn.390\n3cd57220b85ed48e04157f86edd19f22cb2f11f3 svn.92\n0000000000000000000000000000000000000000 svn.92\n3d0e66a5066ff4fc4bce6c2717cb1d1ccc840391 svn.662\n0000000000000000000000000000000000000000 svn.662\n3d2e410265d5d695bc76081e4d8bd8c9cfa28777 svn.862\n0000000000000000000000000000000000000000 svn.862\n3d5d909133b1d65527b63a91ec0a331636775a88 svn.638\n0000000000000000000000000000000000000000 svn.638\n3d7c445b1418482e2b20e9332c3f9b948c33493f svn.589\n0000000000000000000000000000000000000000 svn.589\n3da7fe78c823506fa20fe57c772ad1842e922b72 svn.1225\n0000000000000000000000000000000000000000 svn.1225\n3e2e7f6475694accf8d6c8b796cb063c3dfac0ac svn.547\n0000000000000000000000000000000000000000 svn.547\n3e41764d307c55c40e69c1d9fbb2240af170ef5f svn.551\n0000000000000000000000000000000000000000 svn.551\n3e77557675c97a0cedbe4863c54eb832568b3c8d svn.538\n0000000000000000000000000000000000000000 svn.538\n3e908dc26ae65cf3c9dc28c49421e578ae3fb49f svn.212\n0000000000000000000000000000000000000000 svn.212\n3e9992635c90bf6809a116a9a91933f950113b34 svn.859\n0000000000000000000000000000000000000000 svn.859\n3eb75d1abf615466c5b4687587d2d72463d8b7e7 svn.1561\n0000000000000000000000000000000000000000 svn.1561\n3ebca2e17e8605067905976ce45aa33fd85a2175 svn.1389\n0000000000000000000000000000000000000000 svn.1389\n3ec6641e57e5041411874bced46bc471cfa6f556 svn.420\n0000000000000000000000000000000000000000 svn.420\n3ed79db929a9ae296e569b6a0a662b62c03f8430 svn.554\n0000000000000000000000000000000000000000 svn.554\n3ef4c392b225abbaadbc9ec69a59eb6d38ae673b svn.1071\n0000000000000000000000000000000000000000 svn.1071\n3f2c22dbcb10b0162c94d32df80a8592d8f7cab8 svn.1551\n0000000000000000000000000000000000000000 svn.1551\n3f5d67c3b191ad30069da0a959492e85623437b3 svn.1133\n0000000000000000000000000000000000000000 svn.1133\n3fa2f5f309318b5467f0bbeb5542095a393a0578 svn.219\n0000000000000000000000000000000000000000 svn.219\n3fb3e19746293cba3e51ee7dadef127b0064136e svn.871\n0000000000000000000000000000000000000000 svn.871\n3fcf225f835cf7b90d8854b50c994435bf9a0c7c svn.34\n0000000000000000000000000000000000000000 svn.34\n3fee01e8ac38487fdac22bffc36450a18035adf3 svn.361\n0000000000000000000000000000000000000000 svn.361\n4018fe8a6af67d663dcfdc4faf477b1d513388fb svn.781\n0000000000000000000000000000000000000000 svn.781\n4049858156fb8795c270d6815556fb10442580d2 svn.1322\n0000000000000000000000000000000000000000 svn.1322\n4056ab55e28813eaf3ea4b702ef1c5980b680088 svn.1532\n0000000000000000000000000000000000000000 svn.1532\n40c221438429013119d8d0c6ce987e354f6abb33 svn.1528\n0000000000000000000000000000000000000000 svn.1528\n40d77e888c26f923e7731771b92d93aaf1a9228c svn.494\n0000000000000000000000000000000000000000 svn.494\n40e5005d6d7e1f70583840824cc1f80bb70640c6 svn.377\n0000000000000000000000000000000000000000 svn.377\n40e5eb8bfcf1e9f0c9b0ceae0f8a663afce2997f svn.409\n0000000000000000000000000000000000000000 svn.409\n40e8cd5a0a4e2d6d6c6a0fd53590d3e64cd08724 svn.648\n0000000000000000000000000000000000000000 svn.648\n410babe5b4166e31f8fc82cbc2d7e8c156fa1e4e svn.38\n0000000000000000000000000000000000000000 svn.38\n4118634b4393366efbd911b28f190d7260ce418d svn.1767\n0000000000000000000000000000000000000000 svn.1767\n4163cad7070177da69b53cd4b9887615da6b50c7 svn.1497\n0000000000000000000000000000000000000000 svn.1497\n416d80a781f033c2ca3cdb1243407ceec601875d svn.1278\n0000000000000000000000000000000000000000 svn.1278\n417ad747dd1f8672e37a5e51d936dd1052d3713e svn.1273\n0000000000000000000000000000000000000000 svn.1273\n41a5bc7c2e91022cc0b99bff3c27d028273f5c15 svn.880\n0000000000000000000000000000000000000000 svn.880\n41b920254644108c6280e941dcdec28f213e320c svn.504\n0000000000000000000000000000000000000000 svn.504\n41d0d10781c8fc39a11f8d863a6c60d9c73316c5 svn.1231\n0000000000000000000000000000000000000000 svn.1231\n41e6266889a81500a983fcc598d30c3cd41b016b svn.870\n0000000000000000000000000000000000000000 svn.870\n4201883836c6384f77136fcea4899eac3da1112e svn.202\n0000000000000000000000000000000000000000 svn.202\n420ce4ef78b82b520f2a97ba35b27d792b9d8d29 svn.9\n0000000000000000000000000000000000000000 svn.9\n4213a3828fa82a2f928c979b6c094a14591f1359 svn.828\n0000000000000000000000000000000000000000 svn.828\n423219f35f50085b079baff29646c18928578bd6 svn.1358\n0000000000000000000000000000000000000000 svn.1358\n4246c5028815bd713859ac7194531b0dafea0d8e svn.1072\n0000000000000000000000000000000000000000 svn.1072\n426da8fd1b6f2d0fb1aacd860b83814b0e6d7e7f svn.736\n0000000000000000000000000000000000000000 svn.736\n427a362a641904fe9956779a75158137d13e648d svn.366\n0000000000000000000000000000000000000000 svn.366\n42aa893a70233f327826b6b88d2595ec58be068c svn.1176\n0000000000000000000000000000000000000000 svn.1176\n4359c081ace30b9ee341a5e74af0060ca9625ee6 svn.1701\n0000000000000000000000000000000000000000 svn.1701\n4382b85f7f2976cd95d044e5ce3200209556f46b svn.901\n0000000000000000000000000000000000000000 svn.901\n43b5b08e082d211c858fcefa0abc633fa2e2adbb svn.988\n0000000000000000000000000000000000000000 svn.988\n43c47f66811d1c3c54ebcbe645151a6938b51803 svn.1487\n0000000000000000000000000000000000000000 svn.1487\n43f8a7df78eb3b1f54ab6557f32c72842e59c6f9 svn.1000\n0000000000000000000000000000000000000000 svn.1000\n44482c79545e737aaa40ad828e2b8bc9c32bec7b svn.1666\n0000000000000000000000000000000000000000 svn.1666\n4461b8732fbbf0ab6200a6fed0c56b995823ea53 svn.178\n0000000000000000000000000000000000000000 svn.178\n447813e1e520b5a4789ac3553331a36889724e73 svn.1143\n0000000000000000000000000000000000000000 svn.1143\n4497b6c8a28f87678baffb8e5ea6a4ab076d3f60 svn.350\n0000000000000000000000000000000000000000 svn.350\n449832bda2e9a04c55df98bd146942a3f77bbe00 svn.1498\n0000000000000000000000000000000000000000 svn.1498\n44bc0a7fdd004cceb19e556222ab6a22aaa10aa7 svn.174\n0000000000000000000000000000000000000000 svn.174\n44d880af55ff3e44daeb87c3b6cc5b3e16bf64e8 svn.1008\n0000000000000000000000000000000000000000 svn.1008\n44edb74af00db2ffb08016f94b0cd399a5ccfd8d svn.85\n0000000000000000000000000000000000000000 svn.85\n4515bae5095328873b3ddf34bbd4ccc16a4275a4 svn.1705\n0000000000000000000000000000000000000000 svn.1705\n451cbca5a3e5304928314da6573264ffe7e0e230 svn.406\n0000000000000000000000000000000000000000 svn.406\n4531a562f716d16bf1eba3f8bd14ea9c2dd8a2a4 svn.1381\n0000000000000000000000000000000000000000 svn.1381\n45343776eb76b76496f01462fecfe65c4bc8ed8a svn.131\n0000000000000000000000000000000000000000 svn.131\n457bec224e4b18e0ba380b6956120aa9e55d61e2 svn.173\n0000000000000000000000000000000000000000 svn.173\n45a9d7e5fb62c1ba8ffebc8e5585e993b7bd310e svn.623\n0000000000000000000000000000000000000000 svn.623\n464e7e577c5c69cd2c709d7fc9dc8bd15dcb16b9 svn.639\n0000000000000000000000000000000000000000 svn.639\n4660ab2a5ad8048474c2dfdce74822da12cd5186 svn.1533\n0000000000000000000000000000000000000000 svn.1533\n46b682802892389cb8d3b8f9b89584f6dc565d9c svn.1545\n0000000000000000000000000000000000000000 svn.1545\n46cfc58ea9781625cd81fb960d2d6a59b96acce8 svn.1758\n0000000000000000000000000000000000000000 svn.1758\n46f5d3f78178e6cfc069079e32f13aecb310f24f svn.259\n0000000000000000000000000000000000000000 svn.259\n4822375a0361431ea8760efdb9deb181d2418b92 svn.1661\n0000000000000000000000000000000000000000 svn.1661\n48267b66ccee0e9c7618b7ff1ce0e33a33b9e376 svn.1319\n0000000000000000000000000000000000000000 svn.1319\n4847223c05ae4e6a6aa1d144bed9a28463cdbbfd svn.1161\n0000000000000000000000000000000000000000 svn.1161\n485ffe9198e1bf33c5c3714e80734763b772e098 svn.21\n0000000000000000000000000000000000000000 svn.21\n48637151be9336a68446115339761b22bfc74a79 svn.1426\n0000000000000000000000000000000000000000 svn.1426\n488e52ad69c3f3004c4d88e7826894ec3a2a776b svn.469\n0000000000000000000000000000000000000000 svn.469\n489b2b01018eece9a3da626ba0ac2a5e5a5ee68b svn.1041\n0000000000000000000000000000000000000000 svn.1041\n48d00abbf8977b912efe5a52bcf442e5ba0474c0 svn.207\n0000000000000000000000000000000000000000 svn.207\n48ee2d9c752ff8436f30f8ad270beccc5a34ef1e svn.1247\n0000000000000000000000000000000000000000 svn.1247\n48fa57b1bee688c3523a5763a52d721dc2af6e0e svn.1678\n0000000000000000000000000000000000000000 svn.1678\n49045125c9484e0a60487b1cc65a4db189e5cbc0 svn.750\n0000000000000000000000000000000000000000 svn.750\n49218e93b171aa3ff698c921d442b52bb150d487 svn.125\n0000000000000000000000000000000000000000 svn.125\n4969b297df682192273b2e7514dfd93b496a032c svn.1167\n0000000000000000000000000000000000000000 svn.1167\n496d7770984285a5db947fbbdfe4ea77802a0631 svn.938\n0000000000000000000000000000000000000000 svn.938\n49712828f5b40038a56b3359ec73397344a413f3 svn.415\n0000000000000000000000000000000000000000 svn.415\n4981eb69b4f367a74842be377dfc8bbd4ca2b762 svn.1115\n0000000000000000000000000000000000000000 svn.1115\n4982554bb736c2985de96401f31aa6e3d9fcb433 svn.1734\n0000000000000000000000000000000000000000 svn.1734\n49bf13b03356b24d99eab44a38fdae7631a1c41b svn.1043\n0000000000000000000000000000000000000000 svn.1043\n49c0c0aa33ab9f6222de994a30c1c8c9d71a5f50 svn.552\n0000000000000000000000000000000000000000 svn.552\n49e282f62ffaec2781f1c4b32a7ae145ad5e18da svn.185\n0000000000000000000000000000000000000000 svn.185\n49eb634fdea7222cd9b28dc9a57b13158077c999 svn.247\n0000000000000000000000000000000000000000 svn.247\n49f6e043889adeafb8b36acb4fe109dee6eea2d1 svn.1810\n0000000000000000000000000000000000000000 svn.1810\n4a01a8ab2deaf4b6c368469064d86676974ca5e2 svn.1333\n0000000000000000000000000000000000000000 svn.1333\n4a292bf63670c8a83efd41c939a7402235918b48 svn.596\n0000000000000000000000000000000000000000 svn.596\n4a2e5c12b4a23e7d9699c85d4e40b2b0cfbaed1b svn.33\n0000000000000000000000000000000000000000 svn.33\n4a9245b8d1230a4aeebc5b996401848ef2e8aae7 svn.1737\n0000000000000000000000000000000000000000 svn.1737\n4aaeb47d11c0f86a6e2ca3c1c3711527e23bd284 svn.27\n0000000000000000000000000000000000000000 svn.27\n4aba19b0b4eb4c840aef00f9663fb1b825b48d6c svn.208\n0000000000000000000000000000000000000000 svn.208\n4af3e4b9f35b438cd71c732c287b05904b0fc508 svn.1109\n0000000000000000000000000000000000000000 svn.1109\n4b6e066a9b733af6adf096779d72e14875afc15b svn.357\n0000000000000000000000000000000000000000 svn.357\n4ba988999535326cc253a3049470eefaedf87f90 svn.1722\n0000000000000000000000000000000000000000 svn.1722\n4bbb3be5be5b43f84916368d762c83b0698dad1a svn.1454\n0000000000000000000000000000000000000000 svn.1454\n4c3d3810975dc9a0948a37e033a3d049658bcfba svn.1743\n0000000000000000000000000000000000000000 svn.1743\n4c5e134b91396e80b184dca51144f39f9560343f svn.1549\n0000000000000000000000000000000000000000 svn.1549\n4c9e1ab18d0878e9e4c7d551c13cb68baef38bfb svn.1012\n0000000000000000000000000000000000000000 svn.1012\n4cc9fa1449811b09112e1e97089be590b7444432 svn.1476\n0000000000000000000000000000000000000000 svn.1476\n4cd40c4e44534c0f3a173874891b6dc192302cea svn.18\n0000000000000000000000000000000000000000 svn.18\n4cf5381992a384aabac16831800c7410ac6929a5 svn.115\n0000000000000000000000000000000000000000 svn.115\n4cff5bde76e2deac628cda176628a3cec5a66cc3 svn.645\n0000000000000000000000000000000000000000 svn.645\n4d3cd3e470113b37c3bac7d2052d79d7a097ec96 svn.246\n0000000000000000000000000000000000000000 svn.246\n4d58ac7afb5ee8358d6989989ac04602de8c0a45 svn.342\n0000000000000000000000000000000000000000 svn.342\n4d93b8fb4362acb2626035c0aa12516a81d90fbb svn.148\n0000000000000000000000000000000000000000 svn.148\n4d9628abe1955d19050a2d86dcaf0c4ecede96d7 svn.1077\n0000000000000000000000000000000000000000 svn.1077\n4e25a3ec9fe26987dcf14d93f5f0b0ec07a449fb svn.502\n0000000000000000000000000000000000000000 svn.502\n4e7029901b08b617051901d95a383f6242eaa88b svn.1382\n0000000000000000000000000000000000000000 svn.1382\n4e74cc5353eb286d9f6697636ceb4c35c02fbcb1 svn.1632\n0000000000000000000000000000000000000000 svn.1632\n4e99c598a17d61bcda0604b2a9da8488bc429a2c svn.1695\n0000000000000000000000000000000000000000 svn.1695\n4eecc5a5eb0ee949a906d633e73567aa9642e889 svn.1215\n0000000000000000000000000000000000000000 svn.1215\n4ef9e46137e79825ad2edae673ab6ee70bc7b44a svn.146\n0000000000000000000000000000000000000000 svn.146\n4f283440ba805368be141e02ea6cf3ca1544efb5 svn.1241\n0000000000000000000000000000000000000000 svn.1241\n4fb515e030cd9960c13e228064d8f532653213e8 svn.1134\n0000000000000000000000000000000000000000 svn.1134\n4fe3cf7dcc79f8ef6cf60148a573008dbd1b5b7a svn.1402\n0000000000000000000000000000000000000000 svn.1402\n503116d6f3e29bb6baccdd808054579fcf9cafa9 svn.1799\n0000000000000000000000000000000000000000 svn.1799\n50728409da5a1c155ffafaa2d0f917332766ce91 svn.1749\n0000000000000000000000000000000000000000 svn.1749\n508bc08810045e1cf1e6970513c9de8fa028846c svn.1430\n0000000000000000000000000000000000000000 svn.1430\n50b1a45d4899fbe5b643fbcf780037b7edb25ca6 svn.1354\n0000000000000000000000000000000000000000 svn.1354\n50f6b731870ba3ae773b772050bfb7d27db2e4c7 svn.1615\n0000000000000000000000000000000000000000 svn.1615\n511379ab4cd6e5e5ee4144890f7f5343fb87aa4c svn.93\n0000000000000000000000000000000000000000 svn.93\n516801d12403aaeb3be7a1fa3418b4edee09198f svn.218\n0000000000000000000000000000000000000000 svn.218\n517a757f2e616abc924087c336c6a28192649050 svn.1209\n0000000000000000000000000000000000000000 svn.1209\n520c515ac241d8ecb87692f4ae5c5f2ac5fa975e svn.835\n0000000000000000000000000000000000000000 svn.835\n521936dbc055716daa6df19efa7e34e857271f05 svn.216\n0000000000000000000000000000000000000000 svn.216\n523845d265b6f21192bf920428ac9a8d7dc066ee svn.903\n0000000000000000000000000000000000000000 svn.903\n5257ab69b04aba9315f60d276ba5854b2f182174 svn.1323\n0000000000000000000000000000000000000000 svn.1323\n526d95c3398d2d8a7d6442693704b93542b1221c svn.1102\n0000000000000000000000000000000000000000 svn.1102\n5280eb09b8f1cc2c84bb785b19ff807a47254d4b svn.1680\n0000000000000000000000000000000000000000 svn.1680\n5287daeb9be1f9c97319242ad4d2c226e266341a svn.1101\n0000000000000000000000000000000000000000 svn.1101\n530003732acb6b8004875550c44824850136bcf5 svn.451\n0000000000000000000000000000000000000000 svn.451\n5305e65e9aedf67da3f8028255e21497cbb844f9 svn.77\n0000000000000000000000000000000000000000 svn.77\n5340452bb3df18ee1befcb4daf62b3ec5b9c88c1 svn.66\n0000000000000000000000000000000000000000 svn.66\n5345173dbe7e26057de5a7fc46e787d47495cbd4 svn.1512\n0000000000000000000000000000000000000000 svn.1512\n5355b66f4dab824d6a9dec27432b5993ff3d6ccf svn.545\n0000000000000000000000000000000000000000 svn.545\n5363b892e7a0402e9417eb7b3f9ce23ff6fc2f6c svn.686\n0000000000000000000000000000000000000000 svn.686\n5365f158410c88448e3bc294bb15823895be3b07 svn.435\n0000000000000000000000000000000000000000 svn.435\n53cb10663fef331a2252a4da6b96e7e41c5e7474 svn.1620\n0000000000000000000000000000000000000000 svn.1620\n53dfcf3deef8c8fde2404b620fcb138fc6d2ec13 svn.1754\n0000000000000000000000000000000000000000 svn.1754\n540e50771aa0b70808479222e27dd4239b3c8612 svn.879\n0000000000000000000000000000000000000000 svn.879\n5412bcfdf5df0e75f91f7c242f4f51a094efb62d svn.1004\n0000000000000000000000000000000000000000 svn.1004\n542c2f66bc5d0cfce5272b1b4056f097b2950e24 svn.1573\n0000000000000000000000000000000000000000 svn.1573\n5504d339253a7af01a6c9005afe29ca7cbe705f7 svn.5\n0000000000000000000000000000000000000000 svn.5\n551dde528c1e313b6ff50bf4d79e3b18e7ac0f31 svn.809\n0000000000000000000000000000000000000000 svn.809\n55e939ca948bfbb641d4ff5892a82280518cb0db svn.250\n0000000000000000000000000000000000000000 svn.250\n55fecb6fc0d3ae3f40cfeded90d1743f595a7732 svn.400\n0000000000000000000000000000000000000000 svn.400\n5616db9bc36e94343e2b92df68c4520d62b38cfe svn.60\n0000000000000000000000000000000000000000 svn.60\n5620cdedb5130bbdbdccef255791b98e83bafbef svn.558\n0000000000000000000000000000000000000000 svn.558\n563a941879b75768d6d73b60277c85964cdf3f60 svn.4\n0000000000000000000000000000000000000000 svn.4\n563fe5a94cabd170cc0cde3ae3d3989a6d1141f7 svn.1110\n0000000000000000000000000000000000000000 svn.1110\n564a081accc74d3b805b393d5fa681607f45b916 svn.917\n0000000000000000000000000000000000000000 svn.917\n56931af41cceed819df913b9b2493682405e941f svn.1098\n0000000000000000000000000000000000000000 svn.1098\n56abf9195ec818eaa2e835c5d6e5eab7b8f86026 svn.1301\n0000000000000000000000000000000000000000 svn.1301\n56b1bec77233c6a2931ae689f41cae9653df9256 svn.665\n0000000000000000000000000000000000000000 svn.665\n571cb5de9bb60035283627328e04e71c7b4de775 svn.1150\n0000000000000000000000000000000000000000 svn.1150\n5740382d416f07ccff6d90fae77d808657351836 svn.438\n0000000000000000000000000000000000000000 svn.438\n575c627130dc876053158837c6256830f3fb42d1 svn.850\n0000000000000000000000000000000000000000 svn.850\n58132b46dc40cb7e24fe03d86014f3075df28110 svn.1728\n0000000000000000000000000000000000000000 svn.1728\n582f665ff1e445d2fd9d13ab5b4d664e870ac93d svn.874\n0000000000000000000000000000000000000000 svn.874\n583d35ad9048b887d4c133101ae9c887bf6a3ee6 svn.629\n0000000000000000000000000000000000000000 svn.629\n586f3a9bab88e5bec339980ab203fa5158f77632 svn.1326\n0000000000000000000000000000000000000000 svn.1326\n587722d62ac08a1bea1dc44bd3558ae627be6b13 svn.521\n0000000000000000000000000000000000000000 svn.521\n589441fe2a35347857e3eee6c4b34f6df60325b4 svn.106\n0000000000000000000000000000000000000000 svn.106\n58c0b0001072e37b6bba4cc3f388988c596ccc5f svn.193\n0000000000000000000000000000000000000000 svn.193\n58e27b76bcc5f805251ab9cb27b3259862b1e9a8 svn.82\n0000000000000000000000000000000000000000 svn.82\n58f2a83ce4bd569a69dec333810dabfbf6f3d4d1 svn.163\n0000000000000000000000000000000000000000 svn.163\n58f3151c7be6b192acc553c9231d671e11c45d49 svn.1760\n0000000000000000000000000000000000000000 svn.1760\n58fdd26cbf5ac4b554a0cf795d21e137c9321734 svn.230\n0000000000000000000000000000000000000000 svn.230\n5910084f8704411c3302f85073b08323465cbb6b svn.1261\n0000000000000000000000000000000000000000 svn.1261\n59394c9e003e1ade5b90330307f96520b8dd7f67 svn.1515\n0000000000000000000000000000000000000000 svn.1515\n595b63f60ba812ff9063a78734ed181dfff09b83 svn.799\n0000000000000000000000000000000000000000 svn.799\n5977f55517263d38698d0fb3568e7e9d0865ae23 svn.987\n0000000000000000000000000000000000000000 svn.987\n59ee86c8657bf5db81aaf4051507e4c330d5d54e svn.362\n0000000000000000000000000000000000000000 svn.362\n59f08140ee87a987e11d5d5788f20f9e899817c1 svn.1627\n0000000000000000000000000000000000000000 svn.1627\n5a007c385c4a933cb1efecbb1c5c189f479775d3 svn.945\n0000000000000000000000000000000000000000 svn.945\n5ab8c1db3dbdf9f6d302ef17da48942a289c1583 svn.1013\n0000000000000000000000000000000000000000 svn.1013\n5b222b5000d46e6ea65c7d67d6fca5f3a6f5d483 svn.1070\n0000000000000000000000000000000000000000 svn.1070\n5b636e141f7273bbe593163202da5ee626b65c75 svn.1388\n0000000000000000000000000000000000000000 svn.1388\n5b82a0336225d5317d86d3cfd63571c3676598eb svn.256\n0000000000000000000000000000000000000000 svn.256\n5ba2e572a3f18a22cf11eb07f5c857856f8104ab svn.989\n0000000000000000000000000000000000000000 svn.989\n5bb22493e5e79a48fed30ff120d32233dbec294c svn.391\n0000000000000000000000000000000000000000 svn.391\n5bbaba649fbf8628a729bc6a208b791d2ec092a1 svn.680\n0000000000000000000000000000000000000000 svn.680\n5bd0a4b6acd292e5e40527a43aac64379c19fe48 svn.1410\n0000000000000000000000000000000000000000 svn.1410\n5bfc0b24ec3c698ac3e0da0954df1f2d68404a86 svn.29\n0000000000000000000000000000000000000000 svn.29\n5c269386e9fb9dfbe0b3a622d0ac2a40eababe1b svn.954\n0000000000000000000000000000000000000000 svn.954\n5c3608dee30a07df0460ddac10b5ff333e021de5 svn.327\n0000000000000000000000000000000000000000 svn.327\n5c4990c765ac586cb341b41508300c5a88b1126d svn.592\n0000000000000000000000000000000000000000 svn.592\n5cea2a1eaaa054e8e4e204eb27117ab3215adb25 svn.659\n0000000000000000000000000000000000000000 svn.659\n5cf867e52b3db29da760ef52ce2f5e8e8211d3c2 svn.1535\n0000000000000000000000000000000000000000 svn.1535\n5cfc7ef09092f692c0dd25a33260952db53afe3f svn.266\n0000000000000000000000000000000000000000 svn.266\n5d563977f72716f03f98049a416d315636723103 svn.621\n0000000000000000000000000000000000000000 svn.621\n5d6603bc222175178fc7da0c5668bc4c54ed3021 svn.1394\n0000000000000000000000000000000000000000 svn.1394\n5d7dba2c1c644a3715f716d1ca260db24a768637 svn.275\n0000000000000000000000000000000000000000 svn.275\n5e047513c0ea6bd422ac2690e5b5dde28d7b0cc4 svn.1419\n0000000000000000000000000000000000000000 svn.1419\n5e5997d5cb03c6c7181fb513b07850d524a5082e svn.1585\n0000000000000000000000000000000000000000 svn.1585\n5ebb6b6af2ad27ce2eeec087b8963239a155725d svn.1669\n0000000000000000000000000000000000000000 svn.1669\n5efc0b3122438f19ba247a468922513ff3dc3f4d svn.635\n0000000000000000000000000000000000000000 svn.635\n5f0b8e82d58651c951c356a705e3e4526ba8dbe4 svn.580\n0000000000000000000000000000000000000000 svn.580\n5f4ad86c159e3397e6187c9fd01f229fa72a819d svn.1604\n0000000000000000000000000000000000000000 svn.1604\n5f5bda1a0b4af8978f5cd03fdba4d72d114ac1b5 svn.713\n0000000000000000000000000000000000000000 svn.713\n5f5ce1d0313d39aba65fb360c2b43b0f2b6a516a svn.1116\n0000000000000000000000000000000000000000 svn.1116\n5f6ee3f0aaee445dfb9bb26c8dbef30bd4f2f63c svn.681\n0000000000000000000000000000000000000000 svn.681\n5f86cdef685f59a577fbf8b91ce8b6419ba83f1c svn.183\n0000000000000000000000000000000000000000 svn.183\n5fa4de6cb71b9e6b9827a25d7612fb4faa0a51f5 svn.966\n0000000000000000000000000000000000000000 svn.966\n5fd9f895384805dfd3ebc54e4b27f1acbe452064 svn.140\n0000000000000000000000000000000000000000 svn.140\n5fe392f15c2ad1970936acae1c40efa804a71276 svn.181\n0000000000000000000000000000000000000000 svn.181\n60864a7b7dce7563b202c1406adca49d761c6504 svn.1276\n0000000000000000000000000000000000000000 svn.1276\n60d13dff6a0039f4fa6623a034bd94db7ee7b9ad svn.764\n0000000000000000000000000000000000000000 svn.764\n60d1b329e1a41b4518091df6131b84b0ef37ad90 svn.542\n0000000000000000000000000000000000000000 svn.542\n6131ac5ee202b715f9aa5851f86e1142ef531877 svn.1158\n0000000000000000000000000000000000000000 svn.1158\n6146454a0fe14b8258bf030278a01db0f1df4404 svn.1033\n0000000000000000000000000000000000000000 svn.1033\n61df1dd85a78eb81890b01fbe2fa91ef4315ecf7 svn.1593\n0000000000000000000000000000000000000000 svn.1593\n61f2232514ecd36ae07e1e7d4c199c207de9f047 svn.190\n0000000000000000000000000000000000000000 svn.190\n61f44c6c06abdd3a459eaf4cb84e4ce5689f5b60 svn.845\n0000000000000000000000000000000000000000 svn.845\n6222bb8d5b37ae0132d8ebd7bd4b7a48db94e684 svn.1703\n0000000000000000000000000000000000000000 svn.1703\n62631b24fc1de5dacd71f9e72db09763c3cf6268 svn.573\n0000000000000000000000000000000000000000 svn.573\n628d1287a686c1410b25f5911527f11700800d71 svn.1556\n0000000000000000000000000000000000000000 svn.1556\n62aa37991851a45af59b39bca7a205454c62ff4b svn.1032\n0000000000000000000000000000000000000000 svn.1032\n62bc90b08b309e14eafe31ce70ba0b4b50714a10 svn.67\n0000000000000000000000000000000000000000 svn.67\n62db9cb4103179bdc81fc4aa6dc2c06b51ecdc15 svn.117\n0000000000000000000000000000000000000000 svn.117\n630f4d0b6c7f16d54298d5d9950cdad53ed0cb6d svn.1251\n0000000000000000000000000000000000000000 svn.1251\n63150727b1c7fecfa9ce6464779818683fb19487 svn.905\n0000000000000000000000000000000000000000 svn.905\n63283a4591bd46de9a1d814d068f68cc43a9c976 svn.1120\n0000000000000000000000000000000000000000 svn.1120\n6374e52f7c0628aa7e52154913ccbd9fa9c24330 svn.279\n0000000000000000000000000000000000000000 svn.279\n6399da8dd8baccfe0d3c23e1a79e38e6c5830f30 svn.737\n0000000000000000000000000000000000000000 svn.737\n63b2f81eede89f8ffca37bfc9346336a1947b862 svn.1659\n0000000000000000000000000000000000000000 svn.1659\n6404f6fc8d5b21a924d342f6acc3cea816f7f23f svn.1755\n0000000000000000000000000000000000000000 svn.1755\n640a15ddafccbebe106d2b21793cea1c68946a49 svn.1055\n0000000000000000000000000000000000000000 svn.1055\n6468c80e3d4a0ee4abea5b54c373e48d503ba593 svn.1683\n0000000000000000000000000000000000000000 svn.1683\n646c70e7e5f886dd105d69a66acd1f5c98bcc5c0 svn.72\n0000000000000000000000000000000000000000 svn.72\n64a713646ea00b15ff2939c56b194137cac32e5b svn.442\n0000000000000000000000000000000000000000 svn.442\n64d34a6c43b5d5ced5ea8c59070b9c2c10995f8b svn.97\n0000000000000000000000000000000000000000 svn.97\n64eb4a5f8d329069fb4f3275806980653f0d2276 svn.229\n0000000000000000000000000000000000000000 svn.229\n64fc2c2c26c8db2336b1e0ed2b797f59a63a742d svn.784\n0000000000000000000000000000000000000000 svn.784\n654597a4199eed35dfa820947a59a7c18d723474 svn.1113\n0000000000000000000000000000000000000000 svn.1113\n654ad8eff684ed6ca368a00610e5dc2ce86ae3ff svn.970\n0000000000000000000000000000000000000000 svn.970\n65602799686748be921aac98aa0c2ef5a1fffa2f svn.723\n0000000000000000000000000000000000000000 svn.723\n6593d04b9f7cbd1f04d6fd89f3a999c9090caa9d svn.1526\n0000000000000000000000000000000000000000 svn.1526\n65b6222af795244499cd3c0202b5114dda0045e0 svn.1801\n0000000000000000000000000000000000000000 svn.1801\n65bb67f50da97f1a23c3f36bb9b62496658e9548 svn.1570\n0000000000000000000000000000000000000000 svn.1570\n65fd080af335c1b0e73c167521e24e9fc48c5d1d svn.1584\n0000000000000000000000000000000000000000 svn.1584\n661648ac517628eb254a31e2bc703d92b999782e svn.1294\n0000000000000000000000000000000000000000 svn.1294\n662560be753e9b4a9623791070baf0b86f1b84d0 svn.1488\n0000000000000000000000000000000000000000 svn.1488\n6662340b2f89c68c21db7a8299a7082e1dfe8d76 svn.1727\n0000000000000000000000000000000000000000 svn.1727\n667d55685a4a9007c7a142f2121fd1eee585b93b svn.727\n0000000000000000000000000000000000000000 svn.727\n6687e9fcf14eceda46be690d854e88c645ea5f04 svn.1691\n0000000000000000000000000000000000000000 svn.1691\n67096d5c2f230606afb316251be252c698cdfe7b svn.609\n0000000000000000000000000000000000000000 svn.609\n6723708753a0619323dd98434f752422f0a02cb9 svn.1325\n0000000000000000000000000000000000000000 svn.1325\n681fea5f217b1434801bf4693c5d891f0d0902d1 svn.883\n0000000000000000000000000000000000000000 svn.883\n6859b5230d62685f9a1be0e18f4f3e69b6afcaca svn.908\n0000000000000000000000000000000000000000 svn.908\n686a4b9d4f6b43f78a50735357253ab86c8c67b8 svn.1080\n0000000000000000000000000000000000000000 svn.1080\n687342e70dd55711de85c904f60f3b15f291f60c svn.413\n0000000000000000000000000000000000000000 svn.413\n687bcd6b35ab8d1e106f0e92520641bb20c6d464 svn.1025\n0000000000000000000000000000000000000000 svn.1025\n689b60640d60e1a36429e6f979a6cc06f2be0a11 svn.1686\n0000000000000000000000000000000000000000 svn.1686\n68b5418c549309274ab2caf5872b1bcab3ef89ab svn.937\n0000000000000000000000000000000000000000 svn.937\n68c17c12fc55b70c72ff10571f1dd1b0bfec03ce svn.1207\n0000000000000000000000000000000000000000 svn.1207\n68c3e7c4f2039b94b3b35150d1f5bd040081359f svn.1440\n0000000000000000000000000000000000000000 svn.1440\n68d2052f11c58ed5274309bcb2fabdf46cbd15be svn.566\n0000000000000000000000000000000000000000 svn.566\n68eba7c3b1dc0b8f9a546acf7722c9b7223919f2 svn.236\n0000000000000000000000000000000000000000 svn.236\n68ffb81df06dfe917f1515bbf66ec957c53ee51a svn.520\n0000000000000000000000000000000000000000 svn.520\n692c0b3e97df1a6324a0a97dbe792bc602c499ce svn.172\n0000000000000000000000000000000000000000 svn.172\n697a25de00456d499e7e738e8fc8e7998621b277 svn.617\n0000000000000000000000000000000000000000 svn.617\n698232e5ae717bd73a966b3d7534de356230423d svn.801\n0000000000000000000000000000000000000000 svn.801\n699ed7fe298ed364faa6b6bfdd9bf07705f3ede6 svn.804\n0000000000000000000000000000000000000000 svn.804\n69af6d8252c18596851585164364b63034963663 svn.1311\n0000000000000000000000000000000000000000 svn.1311\n6a4c7518d9d62ec15209325b3332b4c6be89801b svn.503\n0000000000000000000000000000000000000000 svn.503\n6a81cc9d3b086744e03f22ab687f2b3fae123b72 svn.1220\n0000000000000000000000000000000000000000 svn.1220\n6a9c3420d3ee2c6319c5c3a754bdee278d1c9373 svn.130\n0000000000000000000000000000000000000000 svn.130\n6b089c0f0a634d5638c21a3194a385592ee0946c svn.1362\n0000000000000000000000000000000000000000 svn.1362\n6ba57547250acbac6bcff8e2f8938e7e65125ff3 svn.605\n0000000000000000000000000000000000000000 svn.605\n6bbb4ccb07cd993eb5c92481dc46b1f73639c10a svn.717\n0000000000000000000000000000000000000000 svn.717\n6bf4f8d5eff7a64b6d905f0255ba9f79c492851b svn.948\n0000000000000000000000000000000000000000 svn.948\n6c1246ac86ccd1d01c56e80d256758b64837e72c svn.220\n0000000000000000000000000000000000000000 svn.220\n6c545b458dc785a1d22929fe38fc9ef122ed3e62 svn.918\n0000000000000000000000000000000000000000 svn.918\n6c6edf1ce01f47f9a93ced398c14e44e287a65df svn.1653\n0000000000000000000000000000000000000000 svn.1653\n6c81db75b3713c0c889b70aca2e1f79ca358d0d1 svn.931\n0000000000000000000000000000000000000000 svn.931\n6c8b5837bc619afb424e773811a2812ce7e2d1b8 svn.619\n0000000000000000000000000000000000000000 svn.619\n6ca95e61d7bbf5829ffb21a4e53569ac9ed9109b svn.239\n0000000000000000000000000000000000000000 svn.239\n6cb0fd747334aadec07001c234477c6c432c8b9a svn.647\n0000000000000000000000000000000000000000 svn.647\n6cdeac9c78459431afd34a2889c791f8136caf91 svn.1694\n0000000000000000000000000000000000000000 svn.1694\n6ce14ad1e51f11d3a12f9e87336a824027db1d5e svn.691\n0000000000000000000000000000000000000000 svn.691\n6ce695d22d29d5830eb088c3ed4e8b0686e8161c svn.919\n0000000000000000000000000000000000000000 svn.919\n6cf9a71903dd8c412769486fd3e49d883f0a662d svn.443\n0000000000000000000000000000000000000000 svn.443\n6d2a962a88f101661e49a204a2b927b1ff691f48 svn.678\n0000000000000000000000000000000000000000 svn.678\n6d3b895da46ff0df28d07ff1b44aeafab92420e1 svn.632\n0000000000000000000000000000000000000000 svn.632\n6d619bcb275f9b26471116f080ec40806fb39f57 svn.1287\n0000000000000000000000000000000000000000 svn.1287\n6e103b5715f877a532d24467a72ce798e14d7917 svn.1406\n0000000000000000000000000000000000000000 svn.1406\n6e1088ed75b3e709e1c1303d4a6174a14092e315 svn.650\n0000000000000000000000000000000000000000 svn.650\n6e36c78fa82cd595dffb857cfcc61974596ffda5 svn.1547\n0000000000000000000000000000000000000000 svn.1547\n6e4b1b54fb1655d880c73c0b586fa9846e3e8406 svn.1688\n0000000000000000000000000000000000000000 svn.1688\n6e7bbb959a509d936fec8de148068fd5eb4e0e65 svn.1587\n0000000000000000000000000000000000000000 svn.1587\n6ea12918249c2998d5322490e2d10c2ebfb850b1 svn.837\n0000000000000000000000000000000000000000 svn.837\n6ea7649495f915e64fa6774b9c3432ccea684619 svn.381\n0000000000000000000000000000000000000000 svn.381\n6ec1218a11b3cceaea156fdc3925b95b5c16ea8b svn.915\n0000000000000000000000000000000000000000 svn.915\n6ef8fcc1c3cb89dadd48075eca10ecbf1521b343 svn.1081\n0000000000000000000000000000000000000000 svn.1081\n6eff9c0a4e808a63f5a4ddcdfef919e04d289596 svn.493\n0000000000000000000000000000000000000000 svn.493\n6f38cb6673f788712881c4f21a4976347a4704b1 svn.1511\n0000000000000000000000000000000000000000 svn.1511\n6f4eaaa712be7e5e560908b01de6e21a64dff80d svn.127\n0000000000000000000000000000000000000000 svn.127\n6f67d9ddf8d17e4274170ac479cd38e8eeedd5f9 svn.1142\n0000000000000000000000000000000000000000 svn.1142\n6f984ee68a329be4627566d3cfb74e902b9478ef svn.46\n0000000000000000000000000000000000000000 svn.46\n6fc6d87835f7cda31110259830cb290751782550 svn.1641\n0000000000000000000000000000000000000000 svn.1641\n702eaddefa978d541c1827b8aaccadd1fb936637 svn.433\n0000000000000000000000000000000000000000 svn.433\n70372e292703c1ee793ee744230c04aa2394616a svn.780\n0000000000000000000000000000000000000000 svn.780\n708164359b80fd2fd9ab08eda67d8cd12bcae4b7 svn.343\n0000000000000000000000000000000000000000 svn.343\n709dc1862b92b31553ccc62257d14a1e7d94df39 svn.923\n0000000000000000000000000000000000000000 svn.923\n70a1a0a74e029a222b621a0b51d34aa1eed96c95 svn.491\n0000000000000000000000000000000000000000 svn.491\n70b7809f00af3a82fbe52f4a31bbed429c57fedc svn.1403\n0000000000000000000000000000000000000000 svn.1403\n70d83691afa7c302cf779c4318c155711f18a1c4 svn.1657\n0000000000000000000000000000000000000000 svn.1657\n70e2a6cfac20b5db6fad69c089c04fe85c28ce66 svn.1539\n0000000000000000000000000000000000000000 svn.1539\n711eff34e373cf3adb738ac08b5e3d6bef81167d svn.1349\n0000000000000000000000000000000000000000 svn.1349\n712da1b22a9adcb3376d45aab846fea2863dc643 svn.677\n0000000000000000000000000000000000000000 svn.677\n71d855ee2ff5db8d2031e21ef1110f8d1e25d349 svn.947\n0000000000000000000000000000000000000000 svn.947\n71df7de83845443319030e411ca63311b94cb2b7 svn.1478\n0000000000000000000000000000000000000000 svn.1478\n71ed0fd7b8b04dfb59e9d042ad5c16fe03e4603c svn.17\n0000000000000000000000000000000000000000 svn.17\n71f40c26df79f4b2abe46202b8702e1e7401f1cd svn.311\n0000000000000000000000000000000000000000 svn.311\n72142f3034df364a1b4c6744029df460e5dd08b7 svn.708\n0000000000000000000000000000000000000000 svn.708\n7237c3e3741bccf2b48c8761b29ec61f7a74140c svn.1453\n0000000000000000000000000000000000000000 svn.1453\n72f6e65b00f97472581f40f699677f3295d02f74 svn.1274\n0000000000000000000000000000000000000000 svn.1274\n735372855c47bebac5cefb53729706320c3e5db5 svn.933\n0000000000000000000000000000000000000000 svn.933\n73595cab8615e185936ee4f2f5ce8ffd0ba00193 svn.1093\n0000000000000000000000000000000000000000 svn.1093\n736b7d80d82e8c49990daa69d6ee152c3f549964 svn.1246\n0000000000000000000000000000000000000000 svn.1246\n7394cb16a2a56787d29fb2c1238cfd4375a32609 svn.578\n0000000000000000000000000000000000000000 svn.578\n73a5f43fbe5bf5a022fbaff1c1f681759b24caab svn.1665\n0000000000000000000000000000000000000000 svn.1665\n74a4cd9f3ae216250fec79d31d996cf1ca77b84c svn.969\n0000000000000000000000000000000000000000 svn.969\n74cac79b667c492030da162ecd3d1a7dc67f1a08 svn.101\n0000000000000000000000000000000000000000 svn.101\n74cdf93273b4464daaeffab3a89d3e78da35bc0d svn.53\n0000000000000000000000000000000000000000 svn.53\n74e0d6b46fd1d62791d1733a06eb09e4b8fe831f svn.240\n0000000000000000000000000000000000000000 svn.240\n74f83c44f9a21237d01ab53bd7abb8b8c2d69e05 svn.1168\n0000000000000000000000000000000000000000 svn.1168\n750a31f01a28f9d0b0d10acbb3ae2c44443484f0 svn.1562\n0000000000000000000000000000000000000000 svn.1562\n75210b13d831d6480f07f3972ebe7efca4518663 svn.876\n0000000000000000000000000000000000000000 svn.876\n75309fa251bb4ff0900cbf37a584ec031ee33b16 svn.630\n0000000000000000000000000000000000000000 svn.630\n755d312a7136f6f5a569fdfaec07ad68bac257bf svn.1103\n0000000000000000000000000000000000000000 svn.1103\n75a2b0dd253f65fe75245d39f99497dad64da56a svn.1085\n0000000000000000000000000000000000000000 svn.1085\n75ac4861c335b9aff04c88be698b230090b03670 svn.839\n0000000000000000000000000000000000000000 svn.839\n75c736efedb4d64cdb5f027e91ed8ad21347dd2e svn.79\n0000000000000000000000000000000000000000 svn.79\n75d8c13524ccb9288d4da283aa7ba549b2a3abcd svn.1761\n0000000000000000000000000000000000000000 svn.1761\n760e0159100ea5fefea7242935982079f2602887 svn.1794\n0000000000000000000000000000000000000000 svn.1794\n761f86756a571580948a3150ff87221385366a3e svn.1558\n0000000000000000000000000000000000000000 svn.1558\n7666c6bbca4e4e3919706326334cae0053f31b16 svn.766\n0000000000000000000000000000000000000000 svn.766\n766e39ca35babe04b4b3250a5b4f0376ecdef01d svn.1746\n0000000000000000000000000000000000000000 svn.1746\n767a773a9e84eba48e07336691f67c1edbaf5548 svn.671\n0000000000000000000000000000000000000000 svn.671\n76979776bfc1ec51682e9b97fbeb9d17ab50d32a svn.335\n0000000000000000000000000000000000000000 svn.335\n76e08fa2a955c3e00c1fd0fee294d843aafdeaa5 svn.448\n0000000000000000000000000000000000000000 svn.448\n76fe7986694b008839ebbab7f57fa496fdcf6d25 svn.1189\n0000000000000000000000000000000000000000 svn.1189\n7729282bc88011ed2fab9462b4b31098d0b83b82 svn.1079\n0000000000000000000000000000000000000000 svn.1079\n772a2004987d3cf59b96d2d9b2975616af016e58 svn.296\n0000000000000000000000000000000000000000 svn.296\n7737bf992702e8ea55f9b8c9aa445ca679eafc9d svn.806\n0000000000000000000000000000000000000000 svn.806\n773914366850d4cdf88ba6357f438cd2427f4756 svn.1090\n0000000000000000000000000000000000000000 svn.1090\n77480357e062086dfa46e00a21d21254509573ff svn.1010\n0000000000000000000000000000000000000000 svn.1010\n7752741ad8b4c001ade2fe52d850be728c9a6d8f svn.576\n0000000000000000000000000000000000000000 svn.576\n7777f6ab1b807c8ea8374e6ce51c7e23d1a64a2b svn.735\n0000000000000000000000000000000000000000 svn.735\n77ad4648ed8d7caf993cbe9a0c9fa98cbd0c7f22 svn.1429\n0000000000000000000000000000000000000000 svn.1429\n77faf7f8d8624cb1cc71a9abbb389264633f245c svn.1599\n0000000000000000000000000000000000000000 svn.1599\n78684d161c28f74eb080c8e347cc07affe5e2811 svn.11\n0000000000000000000000000000000000000000 svn.11\n78714479ec4b1655ed63ab753e7eb64194bbce49 svn.1731\n0000000000000000000000000000000000000000 svn.1731\n7888876baa70393e25dc7ee3f5d5fc0060cbfc69 svn.396\n0000000000000000000000000000000000000000 svn.396\n78c7a2cd4c6f48e839680c2e0768f61452a1d69a svn.925\n0000000000000000000000000000000000000000 svn.925\n78c9ba1b013b12576663205993f06b43339a75b6 svn.31\n0000000000000000000000000000000000000000 svn.31\n78eeba413131359509eea37964279674e88921bd svn.1664\n0000000000000000000000000000000000000000 svn.1664\n78f011aadd008a23a3cf1c16e659a7bfd01f0b80 svn.1525\n0000000000000000000000000000000000000000 svn.1525\n78f0aea62cd4526c40099b890429cda11414d684 svn.1213\n0000000000000000000000000000000000000000 svn.1213\n79375d6353efca8288ef5673c48e7c465b8a72e2 svn.1003\n0000000000000000000000000000000000000000 svn.1003\n79bc3d944298a10b1621e3dafeaa4a46f6d18069 svn.721\n0000000000000000000000000000000000000000 svn.721\n79cc0fb85cd94fb15ea9e2ae09c44c638355aa05 svn.43\n0000000000000000000000000000000000000000 svn.43\n79f2c675f9dbf256fd7c730bd00f742124be519e svn.126\n0000000000000000000000000000000000000000 svn.126\n7a38f6e17dc8c0350c274892ddd761bf5b35cca7 svn.310\n0000000000000000000000000000000000000000 svn.310\n7a4795f2964b41b738e697cce7cec0bbeef758e1 svn.1009\n0000000000000000000000000000000000000000 svn.1009\n7a4a905ac83dc3dce44a6b8954221e7a937b1b39 svn.936\n0000000000000000000000000000000000000000 svn.936\n7abdf2775d80674580dcb2ee742af8ce3a28bf80 svn.1439\n0000000000000000000000000000000000000000 svn.1439\n7ae0e86d0ae77ac1e40bf9fa7b42cdcebb32ea11 svn.698\n0000000000000000000000000000000000000000 svn.698\n7af1b07907d92d18a1891139956cc0acc63a34f3 svn.1339\n0000000000000000000000000000000000000000 svn.1339\n7af46f2766cee10bb777ec28057d33027f54e582 svn.1148\n0000000000000000000000000000000000000000 svn.1148\n7b093458569729c245fcbbcff0264a7ec9ddb0aa svn.280\n0000000000000000000000000000000000000000 svn.280\n7b3988fc85a4f3547220effcd9fdc01c85ae29d3 svn.1258\n0000000000000000000000000000000000000000 svn.1258\n7b4ea78d32f125a8ed976d98fd7a8270c61ba145 svn.265\n0000000000000000000000000000000000000000 svn.265\n7b79ca45ed746cda3e4be06913a4fc938aac2f2e svn.1169\n0000000000000000000000000000000000000000 svn.1169\n7bc1265533579cb4e7542be7376d77a2bbeb42f2 svn.119\n0000000000000000000000000000000000000000 svn.119\n7bea2bddb35fe07eaeea753cab6edc8486df96b3 svn.1286\n0000000000000000000000000000000000000000 svn.1286\n7c29c4151f71a24a11af92a03468db6656575db0 svn.1792\n0000000000000000000000000000000000000000 svn.1792\n7c2a177f330a773e3378db85d194dec33f84043a svn.1733\n0000000000000000000000000000000000000000 svn.1733\n7c341522bf86c7e10bb655f7f6314aa34eca4b16 svn.465\n0000000000000000000000000000000000000000 svn.465\n7c3e587ecad52c6e36daf00795fdb3f7eaa065b2 svn.1616\n0000000000000000000000000000000000000000 svn.1616\n7c5785af82057e3a3854e5a3f757f9ba0b7d3d63 svn.697\n0000000000000000000000000000000000000000 svn.697\n7c73ea93d3baf7668938452a5c6ec66aeb8288d8 svn.1105\n0000000000000000000000000000000000000000 svn.1105\n7c7d8e04c7806fea442209f75fec5b5b5d753c94 svn.831\n0000000000000000000000000000000000000000 svn.831\n7c8b269f72c0cf2e3a672b0d21b931b89776e4e9 svn.10\n0000000000000000000000000000000000000000 svn.10\n7c903124520810e58dcdac13bd2ed621e12f428b svn.817\n0000000000000000000000000000000000000000 svn.817\n7c9cac9f2405fbcf56e45a7a557b1ac8062dfb19 svn.1050\n0000000000000000000000000000000000000000 svn.1050\n7caae7bbcb13c6b8485c0db265f7f69dbe1db4e3 svn.1368\n0000000000000000000000000000000000000000 svn.1368\n7ccb68386f7d9364e65edba381b48fc14050b311 svn.1781\n0000000000000000000000000000000000000000 svn.1781\n7d0f2b21b8275bf67204c564ea9ac3924d09d24c svn.1066\n0000000000000000000000000000000000000000 svn.1066\n7d2a9e665687315a5d1d69e5e9376442f79dae77 svn.1235\n0000000000000000000000000000000000000000 svn.1235\n7d2f5d03022601376011f87f26bc0c36ae0ec1c6 svn.1315\n0000000000000000000000000000000000000000 svn.1315\n7d309c66861fce2272a43bca230d4ba836f73883 svn.1763\n0000000000000000000000000000000000000000 svn.1763\n7d6ad92040f2d8b47aaad7c7abbfff718b66c57b svn.431\n0000000000000000000000000000000000000000 svn.431\n7d6cf76197562ee72b1974a83a30f93c82435a2e svn.471\n0000000000000000000000000000000000000000 svn.471\n7db82d6cda20f252f4733db0ef9102d6ec286e06 svn.489\n0000000000000000000000000000000000000000 svn.489\n7e0d7092347ea2b7624d564cf13d71256b0b60ec svn.452\n0000000000000000000000000000000000000000 svn.452\n7e2d16dd5cf4f8d8172cc7e5751439aad3ecd447 svn.1418\n0000000000000000000000000000000000000000 svn.1418\n7e2e758ee8c97c7ce30278510e2a98f7d6ca395e svn.1341\n0000000000000000000000000000000000000000 svn.1341\n7e45cc2e299134dcf3ad2acf71201fe776d67454 svn.1752\n0000000000000000000000000000000000000000 svn.1752\n7e499a54d7b873e421c947ca39dbefaefaaa320c svn.598\n0000000000000000000000000000000000000000 svn.598\n7e6075aa2eccd5b67e3e5b7b315d2278b2e935e1 svn.1345\n0000000000000000000000000000000000000000 svn.1345\n7e65dffd52574ce677cbcef4ce759075cf3fda5a svn.960\n0000000000000000000000000000000000000000 svn.960\n7e71cef8e31d2c47a572349bfc239fefac96667d svn.164\n0000000000000000000000000000000000000000 svn.164\n7e7368e614c8032e21271a16632ef023a49ee8b0 svn.601\n0000000000000000000000000000000000000000 svn.601\n7e7a74d7ccb5b49b1748b3edeae17ff57781bd35 svn.999\n0000000000000000000000000000000000000000 svn.999\n7e84bbe40c5b7cffc577533ccea6a6fd61dc3c1b svn.656\n0000000000000000000000000000000000000000 svn.656\n7e93a63d6a63caf65d7de26223500352cea35c14 svn.1508\n0000000000000000000000000000000000000000 svn.1508\n7ee5f2feb9d59aad4eb6af41de7ba2d62494acc1 svn.652\n0000000000000000000000000000000000000000 svn.652\n7ef6994a5fadebf07519c641cf4b72d824cff518 svn.1693\n0000000000000000000000000000000000000000 svn.1693\n7f3fd5b887bb0fd1a54a948aa1b5d1026e6bdb96 svn.89\n0000000000000000000000000000000000000000 svn.89\n7f538f497ed08bd28b75ce150f261436e7dddce3 svn.1145\n0000000000000000000000000000000000000000 svn.1145\n7fa3ac5cade9adaccefa58726779de6adb1087a1 svn.868\n0000000000000000000000000000000000000000 svn.868\n8015f503c5ec815b64e60a3a67881cfc4c6df7cc svn.597\n0000000000000000000000000000000000000000 svn.597\n80ac83abd52bc4809da224207756eb3da581449d svn.505\n0000000000000000000000000000000000000000 svn.505\n80bad282cbf8785f907a1c198a8161be5a950057 svn.124\n0000000000000000000000000000000000000000 svn.124\n80cc3028703002db687c12bb3ceb64f5d6a53ed7 svn.474\n0000000000000000000000000000000000000000 svn.474\n80cf6aa16a2702f47990aef67530abf723df077a svn.701\n0000000000000000000000000000000000000000 svn.701\n80e43391ea11ba35c3ade9a0427f3b76a1db756e svn.688\n0000000000000000000000000000000000000000 svn.688\n80e7766e9707df387d86d6557efd16208da60b96 svn.1083\n0000000000000000000000000000000000000000 svn.1083\n8118a10e79c3287b3df44eb9bb3d83a3499f20a1 svn.673\n0000000000000000000000000000000000000000 svn.673\n812a377719680b1817d6b4d78d88cec45ed11f0e svn.1254\n0000000000000000000000000000000000000000 svn.1254\n81393d6765455575a8fb15e37dfba5eaf9f9e3ee svn.1472\n0000000000000000000000000000000000000000 svn.1472\n814b7ff8d33c2aeeff8d2f309e05f541201bb6a3 svn.615\n0000000000000000000000000000000000000000 svn.615\n81556cdc5f31b00d5c0cc126799cb8b5885ff312 svn.866\n0000000000000000000000000000000000000000 svn.866\n815662a641f5755da44ff6727ca0f90933f9979f svn.340\n0000000000000000000000000000000000000000 svn.340\n8182c58ad0f3406cf84c30634976f45df119e87f svn.1677\n0000000000000000000000000000000000000000 svn.1677\n81938e1ae18d988e40ee758b8e7885d5ca6832b0 svn.612\n0000000000000000000000000000000000000000 svn.612\n81a8b445cc4308eb0e45c9e15b88769bb823a1e9 svn.1018\n0000000000000000000000000000000000000000 svn.1018\n81b3007d285a41c18068d8f486bc53359e20a842 svn.1684\n0000000000000000000000000000000000000000 svn.1684\n82063f87059b4cf0eb15fda9372c18348a6eeb6f svn.156\n0000000000000000000000000000000000000000 svn.156\n824e85d24342fcbd328df485425677a2bc510066 svn.1342\n0000000000000000000000000000000000000000 svn.1342\n825e70252776616ce9d6a6e50d0bc3d55b7197cf svn.432\n0000000000000000000000000000000000000000 svn.432\n828c01a7da237f57e9bdf7846b6dc1e7310bfd78 svn.861\n0000000000000000000000000000000000000000 svn.861\n82951c5f6967e055c4df8413a7ef801b1503a5cc svn.418\n0000000000000000000000000000000000000000 svn.418\n829e66a249048caf82f38cbed01567a37b6e54e4 svn.808\n0000000000000000000000000000000000000000 svn.808\n82c3945c1bbe5e4757e485df57b7d7af4e25d87f svn.557\n0000000000000000000000000000000000000000 svn.557\n83263e58fd561ac31176f8257cf93e0f99306a33 svn.867\n0000000000000000000000000000000000000000 svn.867\n833234d863be03580d95c3978aa7ab5800b7fbdb svn.1586\n0000000000000000000000000000000000000000 svn.1586\n833a684b19f01a76e6e083ef4e337f5b5671fbdc svn.1496\n0000000000000000000000000000000000000000 svn.1496\n833af911bb6950ed186807b033858421dc9998e9 svn.425\n0000000000000000000000000000000000000000 svn.425\n839a901fc4ee1d5bb5f297d218daf712db35c2e2 svn.848\n0000000000000000000000000000000000000000 svn.848\n83b99a433087db9d4dab352bee880eaf505a9c50 svn.205\n0000000000000000000000000000000000000000 svn.205\n83e37d75ef4005c9accb6b292078cc153fb68808 svn.1019\n0000000000000000000000000000000000000000 svn.1019\n83fd1be26619c2509822c713280d1c9719800d38 svn.872\n0000000000000000000000000000000000000000 svn.872\n8406869a75731e17bb48da3dbba9dd22e036cc51 svn.1038\n0000000000000000000000000000000000000000 svn.1038\n84135ca746d9115dcf06505e5dc271502515667c svn.1245\n0000000000000000000000000000000000000000 svn.1245\n844549edbfeb810f38de00b3d37162bdd121e2ec svn.407\n0000000000000000000000000000000000000000 svn.407\n845b5fda8672799a9a9809b02d3e44eb9dc1b905 svn.524\n0000000000000000000000000000000000000000 svn.524\n84694fb038902d50ac9a399ad4d0ac9d4f3a825a svn.620\n0000000000000000000000000000000000000000 svn.620\n84938b94351b46f0e04c1ae2a30dafe143c8ca8c svn.1280\n0000000000000000000000000000000000000000 svn.1280\n849f899f96745f6dfb201a0f08174b5240008c90 svn.1256\n0000000000000000000000000000000000000000 svn.1256\n84a09db171e3d5289d3416b606c0ae58727787fc svn.1365\n0000000000000000000000000000000000000000 svn.1365\n84a29238ece32992a21439cc1af6d4773739d47c svn.705\n0000000000000000000000000000000000000000 svn.705\n8541a3f921131d33fd10cb03c4af2c0605766be8 svn.712\n0000000000000000000000000000000000000000 svn.712\n858f614dd5289a2905e13f5a0d7e56519f65d359 svn.984\n0000000000000000000000000000000000000000 svn.984\n85a52aea151770555ca64a81003de926b4254de1 svn.731\n0000000000000000000000000000000000000000 svn.731\n861a4b3e654b4af71a3fd84216e91805e93619a0 svn.1536\n0000000000000000000000000000000000000000 svn.1536\n862306a8cc96362c083ba6ef874375894d9e1482 svn.649\n0000000000000000000000000000000000000000 svn.649\n8628c6c2dd265f2cf0c86ca9e33c3e86a4baec11 svn.1340\n0000000000000000000000000000000000000000 svn.1340\n864e21d9fd45d24ef74b6a81ea7d8eecfcdbe56a svn.978\n0000000000000000000000000000000000000000 svn.978\n86c8c50cb6298a23fddd6b00c6dbf9554561924e svn.734\n0000000000000000000000000000000000000000 svn.734\n86fc114fd878141c10a5829421b1a5bdcc36703b svn.198\n0000000000000000000000000000000000000000 svn.198\n86fd19b1e4db6116e660bad44a8659510bc88350 svn.171\n0000000000000000000000000000000000000000 svn.171\n871854458a1e4c7254aca166b60b38e5fd997812 svn.500\n0000000000000000000000000000000000000000 svn.500\n875dae7f06e77ed6cf9237bdc3165d97f79b503d svn.771\n0000000000000000000000000000000000000000 svn.771\n87a40ec50379ad21cc634f4067552d304a0acbd2 svn.860\n0000000000000000000000000000000000000000 svn.860\n87b4f439784c7827a45f67aca66ea41a9355d9c8 svn.1568\n0000000000000000000000000000000000000000 svn.1568\n87c95800fb46da2cb4607a1573e3756f0251c5a2 svn.498\n0000000000000000000000000000000000000000 svn.498\n883691116c1d36a77176710660eb116493b532b5 svn.1514\n0000000000000000000000000000000000000000 svn.1514\n886d4605de6ca099390b7b8cc2f61fa332efb084 svn.1393\n0000000000000000000000000000000000000000 svn.1393\n88763cfdd8c05e6cfd4280ce9e3f1c9e8ba1f1de svn.245\n0000000000000000000000000000000000000000 svn.245\n8886880992615e4e64d9424b2713a3cde6ecc363 svn.813\n0000000000000000000000000000000000000000 svn.813\n88c2900c197f200bb02dd81bb6dd192f676af620 svn.459\n0000000000000000000000000000000000000000 svn.459\n89845d23e7587f3afdffb2f08db4855f92a5427b svn.758\n0000000000000000000000000000000000000000 svn.758\n89a10337dd0662d72d8590e21e80426a915d7856 svn.1363\n0000000000000000000000000000000000000000 svn.1363\n89aabab3d202d8a59c48e95b4953e76bc07e849d svn.1332\n0000000000000000000000000000000000000000 svn.1332\n89c0ca6419b02e56437552b95cb1f46de616b375 svn.1355\n0000000000000000000000000000000000000000 svn.1355\n89df9a19d2e091086c1dba55d9adfd0dca52a17f svn.1726\n0000000000000000000000000000000000000000 svn.1726\n8a0b4b881ee9cc567f8505cf6479b707d318d161 svn.819\n0000000000000000000000000000000000000000 svn.819\n8a1ad6b1f8798ad22c2ccce5dcd67921b7b6845e svn.1277\n0000000000000000000000000000000000000000 svn.1277\n8a6463a94c8cffe4fb5b58ba68da7a207446715c svn.168\n0000000000000000000000000000000000000000 svn.168\n8a86a5a85de9e27968db5c173005b42ece4c917d svn.965\n0000000000000000000000000000000000000000 svn.965\n8ab0cf44a1e7f376822f17c166d625ef2174f085 svn.590\n0000000000000000000000000000000000000000 svn.590\n8b3a5ec0bc0818d54abac3652fb23efb28eda087 svn.627\n0000000000000000000000000000000000000000 svn.627\n8be3fe249382ae5799d2f9d8908b550b5a218b9e svn.94\n0000000000000000000000000000000000000000 svn.94\n8be7ecf34c2c2f7e0dcc4f9911183d4b6de7a49f svn.1291\n0000000000000000000000000000000000000000 svn.1291\n8c1e1d0828136953953642d54dc1779e192d01ae svn.1310\n0000000000000000000000000000000000000000 svn.1310\n8c2230b937c3ee7ce03b391e4a461bd666517591 svn.463\n0000000000000000000000000000000000000000 svn.463\n8c85a4d78312dfe9268477441d51a75421fa4ec1 svn.636\n0000000000000000000000000000000000000000 svn.636\n8cb2595cf85ae6153c354ab362072709d02f930a svn.1674\n0000000000000000000000000000000000000000 svn.1674\n8cb3788caf6b60b69fd1fa6871365ad77155c976 svn.553\n0000000000000000000000000000000000000000 svn.553\n8cc439cd4908a37b9a6a003fab64604646439970 svn.877\n0000000000000000000000000000000000000000 svn.877\n8cf9ef42a2ee8743e4dcc0266c137d7290347dbf svn.1803\n0000000000000000000000000000000000000000 svn.1803\n8d92c38d2f5071ba04168fa8be79c15a1690a218 svn.711\n0000000000000000000000000000000000000000 svn.711\n8da7a5197b7205998ee9e1b66ace90707dca4372 svn.1324\n0000000000000000000000000000000000000000 svn.1324\n8dc392af99318b977258aed56bb551801b964a9e svn.943\n0000000000000000000000000000000000000000 svn.943\n8ddbfa657c154329561a7f7301fe63af1bee5de8 svn.631\n0000000000000000000000000000000000000000 svn.631\n8e09cf9dc462012fa145b992a3762a395a45a6c2 svn.586\n0000000000000000000000000000000000000000 svn.586\n8e1ad8b63c7189647b21a93f31726d5bc989386d svn.337\n0000000000000000000000000000000000000000 svn.337\n8e1f65b8f1fffaeb3012697ea6563b9ad3518df0 svn.1179\n0000000000000000000000000000000000000000 svn.1179\n8e46d277270ec44270ba64a0e9b58f049f8f218a svn.1447\n0000000000000000000000000000000000000000 svn.1447\n8ea4d84c99709c94fafa5b75d92d8dee46e4dfdc svn.1129\n0000000000000000000000000000000000000000 svn.1129\n8ec1f002099a56763ac301023acfea7daa143caa svn.482\n0000000000000000000000000000000000000000 svn.482\n8ecf50d7068b1fcdcd4bc00b8584cdf8e3d6d858 svn.1725\n0000000000000000000000000000000000000000 svn.1725\n8ed67117aece3f5023f28a377e3489f706100215 svn.785\n0000000000000000000000000000000000000000 svn.785\n8f1c5ce262983f962bae8390bc319ca7856089fa svn.1107\n0000000000000000000000000000000000000000 svn.1107\n8f536aa935a0fef92ad77d8b4dff108d99d07ac3 svn.961\n0000000000000000000000000000000000000000 svn.961\n8f8379ae5ba9cd8c61d259b4bf7e4529f061c7dc svn.906\n0000000000000000000000000000000000000000 svn.906\n8fa7c84becd0341ed84dc67d585e65f3b03f13aa svn.468\n0000000000000000000000000000000000000000 svn.468\n8fb8e05bdb973fd43d9ce5c8b682680aa5199726 svn.1773\n0000000000000000000000000000000000000000 svn.1773\n8fceba5b01781a0fd7f4fc3fab2c5a2f6e44423c svn.911\n0000000000000000000000000000000000000000 svn.911\n8fe94e553994a68e484f871b857824b9b8def4a1 svn.1385\n0000000000000000000000000000000000000000 svn.1385\n901c52a909f9c90d3ef0997c1e3b57f6df63da01 svn.392\n0000000000000000000000000000000000000000 svn.392\n905743992786d73e673b909a33e0edb12a2e5d8a svn.345\n0000000000000000000000000000000000000000 svn.345\n9057a1191c47b0184bbf355f21cf1fcebaf16311 svn.217\n0000000000000000000000000000000000000000 svn.217\n907c568da1bf769526447f02f8b4a57a62079b55 svn.996\n0000000000000000000000000000000000000000 svn.996\n907feca5bf0c6ac06642cb576bf2254b8b62ac7b svn.1481\n0000000000000000000000000000000000000000 svn.1481\n909e90b6ff19245a54a064ae75e378a0d91800cc svn.270\n0000000000000000000000000000000000000000 svn.270\n90b9e95b83b8ca6acdaf5bd13e4f4d1f1361588f svn.488\n0000000000000000000000000000000000000000 svn.488\n91357ef20a1f3f11419c4da9fde1e12708ac838b svn.1444\n0000000000000000000000000000000000000000 svn.1444\n913fcb4f0c79472bbd93e9824c62c8aa662b58d1 svn.770\n0000000000000000000000000000000000000000 svn.770\n913fe489ac4e60e9930281b59c0dea68a4bcd323 svn.637\n0000000000000000000000000000000000000000 svn.637\n9159ca04bf16f61e63e01bd20a8c4afffbc699c2 svn.1153\n0000000000000000000000000000000000000000 svn.1153\n916a361287e99135ddb8e33b2818aed0ffec6127 svn.351\n0000000000000000000000000000000000000000 svn.351\n91b2973186627526d836f21b172e5ed2377cbabd svn.1699\n0000000000000000000000000000000000000000 svn.1699\n91d0807d5270f3006ca34f0ccd9c4a2b81a47cf8 svn.565\n0000000000000000000000000000000000000000 svn.565\n91d52e06894d13f2b8da13baa2d8ddfeb1e049a1 svn.1206\n0000000000000000000000000000000000000000 svn.1206\n91e09807e2ae7128e2f5e32b8906935a6bad8685 svn.1395\n0000000000000000000000000000000000000000 svn.1395\n92098b0fdd7a4d1d5af9a1bf419709a3f4ac83e2 svn.1502\n0000000000000000000000000000000000000000 svn.1502\n92239fdd340a536e2cf989d3f32922d48a83aedb svn.1494\n0000000000000000000000000000000000000000 svn.1494\n927a21cab86adc02d2117edf55e2a4ab8b83d005 svn.73\n0000000000000000000000000000000000000000 svn.73\n92820090a70b3d146ccaf34087fd5960e883419f svn.510\n0000000000000000000000000000000000000000 svn.510\n92e482a7a589de11eba163373bca29010a5358c7 svn.823\n0000000000000000000000000000000000000000 svn.823\n9319791488d21c4f716cd76246a50a3c3a93ce13 svn.567\n0000000000000000000000000000000000000000 svn.567\n93321a880a99433b2efc5feb81ec6bd92cfbb4eb svn.810\n0000000000000000000000000000000000000000 svn.810\n936376b8be15747cb0cb070de24d03e69d492029 svn.257\n0000000000000000000000000000000000000000 svn.257\n9377e463277a33a9d103212940ae6b032680e505 svn.210\n0000000000000000000000000000000000000000 svn.210\n9385dddafe44b9aa0b96699d658c07d389de2e13 svn.857\n0000000000000000000000000000000000000000 svn.857\n93f9d91f08e3e8f153a08c83db665283b6a627e6 svn.479\n0000000000000000000000000000000000000000 svn.479\n941cb047d4aaf379d4066c975b68fb831b7a9008 svn.693\n0000000000000000000000000000000000000000 svn.693\n945069070553349eb8ee87bb1cbfda780cbb7ac8 svn.975\n0000000000000000000000000000000000000000 svn.975\n94b38e8ff8b357d66128c0cf658961c5f509b7de svn.865\n0000000000000000000000000000000000000000 svn.865\n94dbd328cae27a2fc2bdfb5fc08bdfbde2d0eeb7 svn.299\n0000000000000000000000000000000000000000 svn.299\n94f4cd4f4a35dca050dc279a66791adca6f0ce40 svn.1642\n0000000000000000000000000000000000000000 svn.1642\n951195779aff62f5bb0cb10e2aa06c731e972ab6 svn.1031\n0000000000000000000000000000000000000000 svn.1031\n9535b96f0104968ecf5ab3c9e7468ce23a904a96 svn.1689\n0000000000000000000000000000000000000000 svn.1689\n9559bda18b4e856a9749c744165735c5155ebfe6 svn.1267\n0000000000000000000000000000000000000000 svn.1267\n9559d07fbce62a85ccf4b20db90e9ab3ac61c51e svn.516\n0000000000000000000000000000000000000000 svn.516\n955a7a3e4c97e9e098e9f11b822946a96cd4c2ca svn.74\n0000000000000000000000000000000000000000 svn.74\n95636bc3bc1bc1cdfdc960cd5845d26bc1570a02 svn.1555\n0000000000000000000000000000000000000000 svn.1555\n957781635f75d753bd6383ab20fcfdabc3600f31 svn.672\n0000000000000000000000000000000000000000 svn.672\n95bf6395b37c4ba5ae0a5b101682ab5ca5663d69 svn.1739\n0000000000000000000000000000000000000000 svn.1739\n95d6cbc8a254c02e9371c64111cf6ea865ad8322 svn.1216\n0000000000000000000000000000000000000000 svn.1216\n95d858cd57c1d58933493c48116f1034ab3a8e62 svn.15\n0000000000000000000000000000000000000000 svn.15\n95ec3bcb988f378331758ab60a4be3df7569bcc4 svn.967\n0000000000000000000000000000000000000000 svn.967\n960fc803e2df4ede8b2b874d86107f21c4015c6d svn.508\n0000000000000000000000000000000000000000 svn.508\n9640f12726ac9ff6f41e3367088128958e61e212 svn.232\n0000000000000000000000000000000000000000 svn.232\n965aeb33157cb092f6c567d70cd03b33cac03d9a svn.238\n0000000000000000000000000000000000000000 svn.238\n968eb2518b5e3ec10d5f97929c4826527f824f91 svn.355\n0000000000000000000000000000000000000000 svn.355\n96a8ac92924149e0b53b409da391b2c3157fcb94 svn.353\n0000000000000000000000000000000000000000 svn.353\n96d05f286624040171e9b951269b48d7207c1837 svn.1521\n0000000000000000000000000000000000000000 svn.1521\n96d1c770f30975d456e726181723fc236418e52e svn.1771\n0000000000000000000000000000000000000000 svn.1771\n9704d8eee09e9aa7ed8a25bb370290e7c165e226 svn.128\n0000000000000000000000000000000000000000 svn.128\n971a354621a1d9d8cdb55c5984b9bc0b04a20bd1 svn.348\n0000000000000000000000000000000000000000 svn.348\n9732bcae276ae99e9e0811f3c1363c88e7d37fdb svn.608\n0000000000000000000000000000000000000000 svn.608\n9734bf27770c8267984a34566db598b3e5728aae svn.1152\n0000000000000000000000000000000000000000 svn.1152\n97588038bbf47ca913de4f468442e1cd86472d19 svn.610\n0000000000000000000000000000000000000000 svn.610\n977e4a4d07b40a3b25bd1d70c59224684fae3441 svn.667\n0000000000000000000000000000000000000000 svn.667\n97b4a4692fada54dadb2d1f5a20d10bebc2f2caf svn.188\n0000000000000000000000000000000000000000 svn.188\n97df23113a045033329931b2d5a4fac8cef9c6b3 svn.714\n0000000000000000000000000000000000000000 svn.714\n97fba1eba19a95247d655c901ffe6e182b501d19 svn.1442\n0000000000000000000000000000000000000000 svn.1442\n9800c5ba2109682bc8cc6a3ccb43f4df78196423 svn.339\n0000000000000000000000000000000000000000 svn.339\n9876f685c311b200fe993f176ac7e689862dce5d svn.763\n0000000000000000000000000000000000000000 svn.763\n988ec6d271a4678be3f522b5adaa569e78e62a85 svn.14\n0000000000000000000000000000000000000000 svn.14\n98911d91b9f92be65ead0ec3c76ddc1ec64f08fa svn.588\n0000000000000000000000000000000000000000 svn.588\n98a119a759a65fdc3737917dca91e10cd2614595 svn.1589\n0000000000000000000000000000000000000000 svn.1589\n98a64b855f10fb4c4ec0fb26b400c930486db61f svn.278\n0000000000000000000000000000000000000000 svn.278\n98ad0b1eea20941012842a7cc35bdc057ea6eeab svn.1194\n0000000000000000000000000000000000000000 svn.1194\n98f12553b78be97ab4a82288e826457a83e59a1f svn.600\n0000000000000000000000000000000000000000 svn.600\n9921dc9ed98bcb362576f96422ab51833890a3eb svn.1040\n0000000000000000000000000000000000000000 svn.1040\n9931de9a9d88ddd1ef0cad7d0cb3eeb3de899a4e svn.211\n0000000000000000000000000000000000000000 svn.211\n993773404e8211c08aba35c481f7f8f8af95cf2e svn.26\n0000000000000000000000000000000000000000 svn.26\n99652ca45322ff8219909a55c6aea132ab8fc846 svn.1096\n0000000000000000000000000000000000000000 svn.1096\n99c15130eb2c9aca7a058421f04342790b77e48b svn.1309\n0000000000000000000000000000000000000000 svn.1309\n99f8230eb4fd93cbef225aeae1d025ca77101047 svn.1436\n0000000000000000000000000000000000000000 svn.1436\n9a1ebd1808dc4fbc99cd536fa5bef7e562394a5d svn.894\n0000000000000000000000000000000000000000 svn.894\n9a71ecc65e66c69b2993aec0873dac3a3b3940c7 svn.460\n0000000000000000000000000000000000000000 svn.460\n9a93ef3bf70e27d9e625e9b0e8a79ff009be3b0c svn.52\n0000000000000000000000000000000000000000 svn.52\n9a9ada9118516edcea975924ade034bd26dc13ab svn.957\n0000000000000000000000000000000000000000 svn.957\n9aa25fb8967e427a27d50791eb2483d614832461 svn.998\n0000000000000000000000000000000000000000 svn.998\n9aa3a97b4d50e0ff9dfd27a31de00f7c9feeed3a svn.1076\n0000000000000000000000000000000000000000 svn.1076\n9ab95a974154bda38fc2619ac6f637ea2b067531 svn.1180\n0000000000000000000000000000000000000000 svn.1180\n9aec4be13f8ca6fcb94c8d76d234f9f5fbbd61e5 svn.1428\n0000000000000000000000000000000000000000 svn.1428\n9aef30a081b44a2e20669c2b9efc560138e78cea svn.492\n0000000000000000000000000000000000000000 svn.492\n9b10206065a80a8620f63cac765e9afd99d979ee svn.956\n0000000000000000000000000000000000000000 svn.956\n9b29a595eb213a3d8b624ffcfec1264daaeb6297 svn.157\n0000000000000000000000000000000000000000 svn.157\n9b41c90daf4749214ecb173a00f43135246e6781 svn.1714\n0000000000000000000000000000000000000000 svn.1714\n9bc1e5dd9832a063fee0f272231d1fcb4fa4c2f4 svn.792\n0000000000000000000000000000000000000000 svn.792\n9c112da285602525338629860292979a2d26b201 svn.869\n0000000000000000000000000000000000000000 svn.869\n9c3344556df0a135022fc51b4550268e0ff8644d svn.379\n0000000000000000000000000000000000000000 svn.379\n9c49782e515a9305eb2520cdb216ce6a3a519115 svn.1608\n0000000000000000000000000000000000000000 svn.1608\n9c6a4a5c7f86d77a97aed9365a0d805af7f5d6d2 svn.154\n0000000000000000000000000000000000000000 svn.154\n9cb29d1f9f10f0d2965be923f870f94a529fafd5 svn.55\n0000000000000000000000000000000000000000 svn.55\n9d1e7ed6a6fb317b20bda798ca4d316e4a458a09 svn.1282\n0000000000000000000000000000000000000000 svn.1282\n9d92e040e9fae5172dd79cddaf35f70f8989f69f svn.886\n0000000000000000000000000000000000000000 svn.886\n9d95522b58084ed44db218f5f9b9b8120a4429d7 svn.39\n0000000000000000000000000000000000000000 svn.39\n9da0fc2dc4ac8a32ef5dba9721d6ecaf8b556799 svn.715\n0000000000000000000000000000000000000000 svn.715\n9e4f7f432edcb71b2fc63de0aa9ab7d2a4dc48a2 svn.887\n0000000000000000000000000000000000000000 svn.887\n9e632e3d9ab45e6a235bc32a36167c8846bdfd0b svn.1583\n0000000000000000000000000000000000000000 svn.1583\n9e6721a9642c652026f903c1d73eee4a112bc642 svn.1200\n0000000000000000000000000000000000000000 svn.1200\n9ec9c7afba9db271050517210dfeac944e06ea68 svn.228\n0000000000000000000000000000000000000000 svn.228\n9ee5ca4720a15232000e029f62003dbccf4a90eb svn.1378\n0000000000000000000000000000000000000000 svn.1378\n9f1515882b0eb1e9330a2a7bced32d7465db70f3 svn.663\n0000000000000000000000000000000000000000 svn.663\n9f207ebe8da55f96872590bc541aeb1c0e95dc05 svn.1108\n0000000000000000000000000000000000000000 svn.1108\n9f38bbd2a7e0b2a946d449cb92bccaa74cedc1de svn.429\n0000000000000000000000000000000000000000 svn.429\n9f8ff6b8d63271ea46fbd5f1c1cc86c011448549 svn.1670\n0000000000000000000000000000000000000000 svn.1670\n9f9e9b6346e4b4f2e55d0a73f2620442f6adf92b svn.824\n0000000000000000000000000000000000000000 svn.824\n9fabaecbb5b52610ac55462dc9527d09d6fbfc41 svn.833\n0000000000000000000000000000000000000000 svn.833\n9ff912bc3aa5668736ec642092e9052e4cb5641e svn.940\n0000000000000000000000000000000000000000 svn.940\na0248c89297d1fbcfcc978234971a334d189a509 svn.1078\n0000000000000000000000000000000000000000 svn.1078\na02e8681556852d390ca987883bc1f0bf68a883c svn.1095\n0000000000000000000000000000000000000000 svn.1095\na06c2aeefa343f95e6114ef42373b41d5168a6fe svn.1234\n0000000000000000000000000000000000000000 svn.1234\na0c953d3de30977dfd049bbdb9583babf492f9cd svn.643\n0000000000000000000000000000000000000000 svn.643\na0d79c04569a2fab16b9dcdad2c2279a8d97087f svn.1629\n0000000000000000000000000000000000000000 svn.1629\na0d7df05f9a857975a3c5227f3b03f30e9dc984a svn.416\n0000000000000000000000000000000000000000 svn.416\na0f418349af76aa0d155e417b42e62186249d7c8 svn.1648\n0000000000000000000000000000000000000000 svn.1648\na10259dbd29b3f03c2b7dc6119e7f646f0cc24f9 svn.1172\n0000000000000000000000000000000000000000 svn.1172\na12649dbc73d2cbb659f5c7aac8d018344e0a536 svn.1338\n0000000000000000000000000000000000000000 svn.1338\na1295d81ae39fc5a57d0504041c89d2fa48ca6fd svn.644\n0000000000000000000000000000000000000000 svn.644\na163dac0359fbe271c1ca0245d3540857029c3b3 svn.1404\n0000000000000000000000000000000000000000 svn.1404\na16838b5cb05592e23262655527f197656796f81 svn.703\n0000000000000000000000000000000000000000 svn.703\na16d588a8e9cf4ec88fd1cb7142c3834f9bc3b73 svn.1804\n0000000000000000000000000000000000000000 svn.1804\na18c760d50f79b93a9add8effe453a59432c7775 svn.1759\n0000000000000000000000000000000000000000 svn.1759\na1d74d0fc790a3491a52939587410556f6b0a906 svn.1489\n0000000000000000000000000000000000000000 svn.1489\na1f4a88de6091601fccaa7d750fc630d89484173 svn.1685\n0000000000000000000000000000000000000000 svn.1685\na219cceec164504f0652afba415fcc41eb94dd50 svn.754\n0000000000000000000000000000000000000000 svn.754\na297f869ee4234578f31afdf259e696aa5abac97 svn.1753\n0000000000000000000000000000000000000000 svn.1753\na34717eab5696aad8a316ee7e76df8c533b73d4b svn.1720\n0000000000000000000000000000000000000000 svn.1720\na3741bbe1e9377d661298a8dbca15698846b80c2 svn.426\n0000000000000000000000000000000000000000 svn.426\na3a91388da54b7cb33dba5f72030690d115686b5 svn.1557\n0000000000000000000000000000000000000000 svn.1557\na3e2cea497289ce066176e23cb4300ef42fd2032 svn.268\n0000000000000000000000000000000000000000 svn.268\na3f68e21a03e4f6de50db0a5de6e085147c41ba1 svn.450\n0000000000000000000000000000000000000000 svn.450\na402d09c999716f33b1d5505646bb308d4e39ec5 svn.169\n0000000000000000000000000000000000000000 svn.169\na43969b73291aa0750bfcf156403ea1be4da194e svn.1800\n0000000000000000000000000000000000000000 svn.1800\na460319b2b547acc3ba3feaa74b96e1530c7888c svn.838\n0000000000000000000000000000000000000000 svn.838\na48a47a1f51f173205dd227d420ece3611a6ec50 svn.1637\n0000000000000000000000000000000000000000 svn.1637\na4fc7736d18490a194bc4a15b778920aee81aa47 svn.1793\n0000000000000000000000000000000000000000 svn.1793\na50fe3cbce4e6073c6a645d6b137cdd68e46eefd svn.798\n0000000000000000000000000000000000000000 svn.798\na510c9b3e200cebe7a43326fc9b366952020b70d svn.752\n0000000000000000000000000000000000000000 svn.752\na5267c2eb1d03a1d9c8a699462c7e67533f055e0 svn.1045\n0000000000000000000000000000000000000000 svn.1045\na526bb77ca3346dfbf829f14a8b1f8ff31588fdd svn.641\n0000000000000000000000000000000000000000 svn.641\na56aa4ee391728ced5cdf4e896c1b43ae7bfb09d svn.1118\n0000000000000000000000000000000000000000 svn.1118\na572e5965967eb1aa33cc2e7d23873258907117a svn.274\n0000000000000000000000000000000000000000 svn.274\na578bb6121dc06b382231f4fa8d5d348bb1271d7 svn.399\n0000000000000000000000000000000000000000 svn.399\na5857bae39ee445ea0c62ba6f4ecdb915230f090 svn.1723\n0000000000000000000000000000000000000000 svn.1723\na585956b12cf252a9d003079335feb5c06c99328 svn.800\n0000000000000000000000000000000000000000 svn.800\na586ebd2e3196ff432ce42c22a7f96ce56a40b76 svn.111\n0000000000000000000000000000000000000000 svn.111\na58c3ef148b51785910c57118a514f00ea45c5ab svn.724\n0000000000000000000000000000000000000000 svn.724\na5b10446d760508bf5e8d5aa9a5c9e436249ec8e svn.726\n0000000000000000000000000000000000000000 svn.726\na5b4b425141cf01a7f13ba109ca1f911c7d94d56 svn.1438\n0000000000000000000000000000000000000000 svn.1438\na5bab3cf3e5bb59a451209daf738e0d115e4e6f6 svn.1054\n0000000000000000000000000000000000000000 svn.1054\na5babafdf8d0662adfbea9079b0c4e3f056bd860 svn.65\n0000000000000000000000000000000000000000 svn.65\na5e219997db4f32ca5c81a2b4aa231ea53a798b3 svn.152\n0000000000000000000000000000000000000000 svn.152\na5ea9c50a0651be227b8b26717cc571749d26604 svn.1201\n0000000000000000000000000000000000000000 svn.1201\na5ed10e8bc88fe86cae5592cac69814be8590098 svn.959\n0000000000000000000000000000000000000000 svn.959\na6074bdf26e5e73d9deafb77562d41d63c5e46e1 svn.133\n0000000000000000000000000000000000000000 svn.133\na628cc0521eaf1a5fa06ad4e127359d33ba331f0 svn.1671\n0000000000000000000000000000000000000000 svn.1671\na632651a151bbffb850430cf60127d8cbebca142 svn.1336\n0000000000000000000000000000000000000000 svn.1336\na646b50a6c312814fbda9365ddb1e2378b601451 svn.1062\n0000000000000000000000000000000000000000 svn.1062\na67ce0a27572b4914685b3677f7255886ee53085 svn.755\n0000000000000000000000000000000000000000 svn.755\na6aabdb308ada1f68f6795cac2f3bb1b3b3a270e svn.98\n0000000000000000000000000000000000000000 svn.98\na70125dc712b2cd6f4038c0cc71bdd0726b706a0 svn.1375\n0000000000000000000000000000000000000000 svn.1375\na7aac27988511020c8a4b43d97ac296436e85a08 svn.344\n0000000000000000000000000000000000000000 svn.344\na7ff6a46cb6d7d0dc38d744c01ba6d25ce77dcf3 svn.546\n0000000000000000000000000000000000000000 svn.546\na80f3cab483e45ea8c6e7df58780079013a41220 svn.676\n0000000000000000000000000000000000000000 svn.676\na81914dad2eb58c8fa6d345bc5ccc2031774f447 svn.1742\n0000000000000000000000000000000000000000 svn.1742\na81931a0ce18e6abe7c31f7a8eda97545f8c4a5e svn.942\n0000000000000000000000000000000000000000 svn.942\na84e91abda7d74e41168d7447ae96bbe1a48e6f6 svn.593\n0000000000000000000000000000000000000000 svn.593\na85b7fd37b4359a40a185d7972411cb171a10f98 svn.196\n0000000000000000000000000000000000000000 svn.196\na86267e508fd91907ed795f277c13849cbfbaa95 svn.1302\n0000000000000000000000000000000000000000 svn.1302\na8899e645d975ab0dd8042fd1ba76e660ed84fd2 svn.1591\n0000000000000000000000000000000000000000 svn.1591\na8a6f843926816b7d9bdf8a2dbf9423973c4e285 svn.1715\n0000000000000000000000000000000000000000 svn.1715\na8d37c1e3ea9582e532abc92c254d7784795eff3 svn.971\n0000000000000000000000000000000000000000 svn.971\na914357b7ae6ae9d06b75cd66b6a90dd612dc4bf svn.1565\n0000000000000000000000000000000000000000 svn.1565\na92157febfedda7525b97a3ae3ae102c3f1f9397 svn.570\n0000000000000000000000000000000000000000 svn.570\na9295fc1fe38a604a738dfd593b61251a8876b64 svn.1522\n0000000000000000000000000000000000000000 svn.1522\na94d3306a44ee7f0aa98a2f6027ea54b77f21311 svn.501\n0000000000000000000000000000000000000000 svn.501\na94f2e04147d37b0c86e220b47eda0bb842c6693 svn.421\n0000000000000000000000000000000000000000 svn.421\na9997f587f4d1d280852eb4d5a7be43c34d91bd2 svn.36\n0000000000000000000000000000000000000000 svn.36\na99b6b5ef6a1a3a47f2f55b95c622f5cfdfb9ca4 svn.1100\n0000000000000000000000000000000000000000 svn.1100\na9c31088a3e164c86f37d3aceec9e8ffd85224a0 svn.928\n0000000000000000000000000000000000000000 svn.928\na9e519c8b5d3010d6ef71a21f1d37a70d302f24b svn.1510\n0000000000000000000000000000000000000000 svn.1510\na9f54d68329cef4dbb0fc4aec462a81790a5867c svn.1074\n0000000000000000000000000000000000000000 svn.1074\naa1dd68a313f90ed2bed7104cc455049f1d93134 svn.177\n0000000000000000000000000000000000000000 svn.177\naa5a58db2daeacef35ddc3bfe3fb42e2f62dedab svn.1272\n0000000000000000000000000000000000000000 svn.1272\naa8c48d95687e4328d713ad09e0a2d5fa8f675d7 svn.25\n0000000000000000000000000000000000000000 svn.25\naaedf208a3c7da4c2f9e4e7090f9a79b6346ef7f svn.233\n0000000000000000000000000000000000000000 svn.233\nab00496b77f0132955f409bd81e661ac498f3fa9 svn.1042\n0000000000000000000000000000000000000000 svn.1042\nab16b63dbb36e2fd83ff62f903b7076372ae2280 svn.1501\n0000000000000000000000000000000000000000 svn.1501\nab2e41d6b3fbe3fa1b3efab14bd3a269a9c8f2fb svn.1199\n0000000000000000000000000000000000000000 svn.1199\nab5719ba28841242625841fde7606293252a1f81 svn.1290\n0000000000000000000000000000000000000000 svn.1290\nab5d94b1312c6a7b384630b78b1fca91748567c1 svn.625\n0000000000000000000000000000000000000000 svn.625\nabe1b3db045589ce79ddcb349a73cb165a04db18 svn.692\n0000000000000000000000000000000000000000 svn.692\nabf2124837c9a76b3165f9db1ff627c6ddada1dc svn.221\n0000000000000000000000000000000000000000 svn.221\nac0ea6d8448371423c5981d840ed6dc1a21c66b7 svn.1186\n0000000000000000000000000000000000000000 svn.1186\nac1083dddca99afb1d13aab044e0a1ea1621036a svn.1768\n0000000000000000000000000000000000000000 svn.1768\nac9413ddb79055da702d50c9150a6172416108d7 svn.1048\n0000000000000000000000000000000000000000 svn.1048\nac970ffcd2cdc118cc58f2601c055b36ef016b47 svn.1655\n0000000000000000000000000000000000000000 svn.1655\naca911cccf567b3be747ca4e9d2ca2b9e2dab800 svn.747\n0000000000000000000000000000000000000000 svn.747\nacc1571c360ccb3f6b74739da17a84a3aca4281b svn.134\n0000000000000000000000000000000000000000 svn.134\nacccfc516c72622744b6278347abac10650db670 svn.976\n0000000000000000000000000000000000000000 svn.976\nacec51d08d7624fe76a9aadfe3155ee2483506e3 svn.1490\n0000000000000000000000000000000000000000 svn.1490\nad0abbc94dd620f6971e040413801bdf7f5dc5cd svn.1675\n0000000000000000000000000000000000000000 svn.1675\nad57f1d5390122313bea130fa196e36f86d7ccd0 svn.1769\n0000000000000000000000000000000000000000 svn.1769\naddf0f93ae5c8ec8989160d02185e1fcb71e4a43 svn.958\n0000000000000000000000000000000000000000 svn.958\nadf2aa0ce50c1ceeb7ba15d3b621139765b35f15 svn.1047\n0000000000000000000000000000000000000000 svn.1047\nadfcc0995620eee56fb64502c3c748058c968072 svn.378\n0000000000000000000000000000000000000000 svn.378\nadffe1af3e0932544c25ac7dfeecce81035b80a4 svn.707\n0000000000000000000000000000000000000000 svn.707\nae058e5b5b0b59b5821b2caa27955794fc5c2820 svn.1651\n0000000000000000000000000000000000000000 svn.1651\nae09f960b038793877a44a52e639ff5a6eab6f57 svn.346\n0000000000000000000000000000000000000000 svn.346\nae618970c5d2b233fa46f001a8da2e4d27673214 svn.748\n0000000000000000000000000000000000000000 svn.748\nae7eac9f61672eab28817c79a4c3021577f3bb3a svn.606\n0000000000000000000000000000000000000000 svn.606\naea28bf27fb2695947563966b77625dffc562448 svn.149\n0000000000000000000000000000000000000000 svn.149\naeefcef453f3b17e5bc516ae2ef5783090db3020 svn.622\n0000000000000000000000000000000000000000 svn.622\naef2e9f0f0775830621511191cb1d90cbf31e2dc svn.1348\n0000000000000000000000000000000000000000 svn.1348\naef516e1116594cd80a37197723b554b7ed14dd9 svn.1509\n0000000000000000000000000000000000000000 svn.1509\naf64be205ea2ab36a0aa9fae890406b01f0b1aef svn.814\n0000000000000000000000000000000000000000 svn.814\naf78ba12e16cd1c350d7b8d276b63dea1828ff6f svn.523\n0000000000000000000000000000000000000000 svn.523\naf86a6b540270ec0b34313cf9a63a959c1c8a903 svn.1735\n0000000000000000000000000000000000000000 svn.1735\naf9009caf15aece591529c028ba693c4718599c8 svn.818\n0000000000000000000000000000000000000000 svn.818\nafd4ae9b829680a7e437ba429f1d5ef583977955 svn.534\n0000000000000000000000000000000000000000 svn.534\naff586a05569925695625366d3994efdbfda75fb svn.48\n0000000000000000000000000000000000000000 svn.48\nb017b61136d48d1e975282721dc088ec042ee90b svn.1676\n0000000000000000000000000000000000000000 svn.1676\nb02e9e71d58e839ce45a0b61472d9a3231b0bb1d svn.529\n0000000000000000000000000000000000000000 svn.529\nb034778c7095acd6c1a231d6a8359d4e1557353f svn.1592\n0000000000000000000000000000000000000000 svn.1592\nb09e8dc65e69b08904691e6fc2da1a3e227b7320 svn.1467\n0000000000000000000000000000000000000000 svn.1467\nb0b62515d53ea57e9a74f252cb25d98741f57ef1 svn.476\n0000000000000000000000000000000000000000 svn.476\nb0c19e4145a3ff2410db03cbd552c9e79c5c6cd1 svn.365\n0000000000000000000000000000000000000000 svn.365\nb100edb40a33571f3a8ad85db86fa726f552f7bc svn.1357\n0000000000000000000000000000000000000000 svn.1357\nb10baedc76153edc3cf9f62609923b031a0c0743 svn.102\n0000000000000000000000000000000000000000 svn.102\nb15d74802fe758c9d2661854e0a3d98b65f42564 svn.853\n0000000000000000000000000000000000000000 svn.853\nb178007c7e0eeaf82d720f7d67a278581fa68cb6 svn.1162\n0000000000000000000000000000000000000000 svn.1162\nb187516681a232ffac94cc6741db31b40089f1cb svn.616\n0000000000000000000000000000000000000000 svn.616\nb1e2a89b438c98b7cfec2bf0d1788656400b65e9 svn.856\n0000000000000000000000000000000000000000 svn.856\nb1f6ec05492ee42c05be7bd552e6a854c1852c0c svn.1016\n0000000000000000000000000000000000000000 svn.1016\nb254a7fbdf6e8914ba1b20c72efd82ea2895f233 svn.1177\n0000000000000000000000000000000000000000 svn.1177\nb29b47b078d90d27e17172dbd64371663d753f99 svn.982\n0000000000000000000000000000000000000000 svn.982\nb2a797990913cb34b89db24cd90fa0661fce0d10 svn.684\n0000000000000000000000000000000000000000 svn.684\nb2efb3cb1ae8e165a00e4ea416061e54b4375e37 svn.1136\n0000000000000000000000000000000000000000 svn.1136\nb2fc28865d5ada46e419fdcdb6138a48946f5c03 svn.1210\n0000000000000000000000000000000000000000 svn.1210\nb3508917a48f60870b653906c507c863ecfc6d68 svn.628\n0000000000000000000000000000000000000000 svn.628\nb41bea1bcb1de1c425ca3e0c37af428ddb89f7cf svn.1208\n0000000000000000000000000000000000000000 svn.1208\nb49dcbe2d0cb373270e7afbd936a3c425da25a9f svn.389\n0000000000000000000000000000000000000000 svn.389\nb4b34c928a45a00eac301c3eee0056d9e275927a svn.1449\n0000000000000000000000000000000000000000 svn.1449\nb4c889499f4c41b9bb6e40cf59a1425d1788f779 svn.41\n0000000000000000000000000000000000000000 svn.41\nb4cce6b7eacf19d77e87b9c9a329770775936a6a svn.543\n0000000000000000000000000000000000000000 svn.543\nb4e3de65979dd083e428846079e509a3041202ad svn.1638\n0000000000000000000000000000000000000000 svn.1638\nb4fe74aaa8f6b8d44bb333b9f8e3bab72d8a2649 svn.1196\n0000000000000000000000000000000000000000 svn.1196\nb50805f852a8e2be7ea527f7aa361074f461936c svn.269\n0000000000000000000000000000000000000000 svn.269\nb516c2a4c8f925f48f51d4adb3b7f731b586877d svn.1307\n0000000000000000000000000000000000000000 svn.1307\nb54cc4d9ea3be89229352b78e540f36302b1b67d svn.87\n0000000000000000000000000000000000000000 svn.87\nb56cd0e4430d71b6628e34b9141b67770e4c189c svn.1716\n0000000000000000000000000000000000000000 svn.1716\nb576dbf4b3df62b8fef0fc5b42c1ebb854ebeece svn.670\n0000000000000000000000000000000000000000 svn.670\nb57ef455693f2177dc245989457480095481102c svn.369\n0000000000000000000000000000000000000000 svn.369\nb5aefdd801963043c02bdda4ee3c581d78325709 svn.607\n0000000000000000000000000000000000000000 svn.607\nb6091f03067325dca04b154c82d4ba9473203967 svn.286\n0000000000000000000000000000000000000000 svn.286\nb60b25246b1ea4f377f4ec8218bb6eef2774b267 svn.292\n0000000000000000000000000000000000000000 svn.292\nb6133494d191fe7c524b134c0ac49c73ebf28409 svn.1595\n0000000000000000000000000000000000000000 svn.1595\nb62bd324f6a76148938a9f75562bbcc75bad25e0 svn.882\n0000000000000000000000000000000000000000 svn.882\nb62dfdcbd5ed48b6dd97c6c08baf763729af853e svn.105\n0000000000000000000000000000000000000000 svn.105\nb65ee17c29656ad491d5926c92ce6c1511cf59be svn.461\n0000000000000000000000000000000000000000 svn.461\nb66f1ebf55000be1e02f95cc84e2141ab13ac378 svn.760\n0000000000000000000000000000000000000000 svn.760\nb68dbe7d587599bed4f5033cddf6b200b3c0c797 svn.1039\n0000000000000000000000000000000000000000 svn.1039\nb6afde5ee4d5012e2041ac8305d4951021fce11a svn.888\n0000000000000000000000000000000000000000 svn.888\nb6df060b80323de2866a13e0fa671652fb0713dd svn.1125\n0000000000000000000000000000000000000000 svn.1125\nb6e2c5b077fc4816032af9f272a70633bd8bbc62 svn.1253\n0000000000000000000000000000000000000000 svn.1253\nb72c9063105fc37dc44f5b1927303cf435bb512c svn.674\n0000000000000000000000000000000000000000 svn.674\nb7600c714b84873c4ad1c7637de51dcb0595a20f svn.683\n0000000000000000000000000000000000000000 svn.683\nb7763d12ffd2ee7fa08d814a66d77e316334a846 svn.834\n0000000000000000000000000000000000000000 svn.834\nb7dbd20136811c23c0cb14899acd320067f50f28 svn.360\n0000000000000000000000000000000000000000 svn.360\nb7f4ea520c43cfe5409afb6a745ebf32d2f668d9 svn.470\n0000000000000000000000000000000000000000 svn.470\nb8007496c5864fe41804232cae36f938910ea5a6 svn.203\n0000000000000000000000000000000000000000 svn.203\nb80893f1505443f521f049d263d7c587cfcfa1c3 svn.1747\n0000000000000000000000000000000000000000 svn.1747\nb8276b8dd4e7ea3eac5b560b0dbb2ab9cbc9e941 svn.252\n0000000000000000000000000000000000000000 svn.252\nb83eccc16f7e7b7e0a40fa7829acba11ee9e55a9 svn.312\n0000000000000000000000000000000000000000 svn.312\nb853cd61ddc4b7690bbdf1951ba2b97915dbf7a4 svn.1140\n0000000000000000000000000000000000000000 svn.1140\nb87a153ca34719ca3f7c7bfab49ac497b0fe3575 svn.1374\n0000000000000000000000000000000000000000 svn.1374\nb8b1484aeb9371e0ec57133badd8e401451dbd04 svn.138\n0000000000000000000000000000000000000000 svn.138\nb8be5dfcc9256a3021339d39219e5a92e752d07c svn.1149\n0000000000000000000000000000000000000000 svn.1149\nb8c88dcfb1690770c3547e51aef90f80eeb25e57 svn.47\n0000000000000000000000000000000000000000 svn.47\nb9004cecfea6859995441ccff047349ac2146372 svn.19\n0000000000000000000000000000000000000000 svn.19\nb9043b843697016c7929edda4c68aa19b1b34538 svn.549\n0000000000000000000000000000000000000000 svn.549\nb90a5e0b349eb2d63def509fd2a1a972714a4827 svn.441\n0000000000000000000000000000000000000000 svn.441\nb92d169eb5e10b1f0240084c6e88e69ff7bc9b71 svn.1334\n0000000000000000000000000000000000000000 svn.1334\nb94c331cba4d1d825d907c64eee88ec0d5c6f197 svn.587\n0000000000000000000000000000000000000000 svn.587\nb94fc22d5df709e061d6b45afa4f82962f984cba svn.561\n0000000000000000000000000000000000000000 svn.561\nb988ab9044a405bd54a8b82abad6841abf14e5c3 svn.1530\n0000000000000000000000000000000000000000 svn.1530\nb9c726dc2011338c128420ac8a4564373a350c72 svn.977\n0000000000000000000000000000000000000000 svn.977\nb9d559d3c93827503a08f63cd377bb89ba96f94f svn.1174\n0000000000000000000000000000000000000000 svn.1174\nb9e6a95cadee468ead8fc31de24f615e397c89d0 svn.1729\n0000000000000000000000000000000000000000 svn.1729\nba26a56b3af92dc544aa3bdfc85807f6efc072a1 svn.1787\n0000000000000000000000000000000000000000 svn.1787\nba52065cb28fd4cabc1d1b62a2c97e970bca272e svn.23\n0000000000000000000000000000000000000000 svn.23\nba72d14db98d4aec227e6b688a70153393514145 svn.1390\n0000000000000000000000000000000000000000 svn.1390\nba88dacae29e8a70d19e53bdd1de023f84ef8cce svn.702\n0000000000000000000000000000000000000000 svn.702\nbad10987839488295256e8d9b4c98bd41d33c144 svn.921\n0000000000000000000000000000000000000000 svn.921\nbaed8f20853f4fa7df7ca7b4e810e0fd57f64ebd svn.135\n0000000000000000000000000000000000000000 svn.135\nbb127bd1be4a29d863e13ea3bb708758bb18af75 svn.1137\n0000000000000000000000000000000000000000 svn.1137\nbb1e87ef23ac5ccb79d99a157d7884b0d5acd320 svn.436\n0000000000000000000000000000000000000000 svn.436\nbb86dfb4578f0ad377a6ab15e9eec729960a9602 svn.1306\n0000000000000000000000000000000000000000 svn.1306\nbba760a55e02033fd0055bf67b1c893e6b4082db svn.1011\n0000000000000000000000000000000000000000 svn.1011\nbba8232865e81643be4c343d4a6bca6687bad827 svn.742\n0000000000000000000000000000000000000000 svn.742\nbbd1d3461d4ae66137c197de5135d3852fe10e1c svn.1230\n0000000000000000000000000000000000000000 svn.1230\nbbd2038ed37182bc4ddb3c574c8113b418999d22 svn.414\n0000000000000000000000000000000000000000 svn.414\nbc1400e920f5bed116136d53722700715c07ff54 svn.336\n0000000000000000000000000000000000000000 svn.336\nbc42cad69e953bb617aa31d7a374a1ce8a5f925e svn.544\n0000000000000000000000000000000000000000 svn.544\nbc8afbc57b67f51384ccaedad69ad024a17e55c1 svn.281\n0000000000000000000000000000000000000000 svn.281\nbc9ea94aa2cf29fb36f56ec6d994cba0d9be29ac svn.1166\n0000000000000000000000000000000000000000 svn.1166\nbcec0692b627b770f3e5b6bab4a22c830746c5f1 svn.822\n0000000000000000000000000000000000000000 svn.822\nbceeaee5e4975fb7c5b448257fde44af4d13f384 svn.944\n0000000000000000000000000000000000000000 svn.944\nbd0590a19af0a3b7e4f99f7ece4770713b8631f6 svn.397\n0000000000000000000000000000000000000000 svn.397\nbd07a43b624c9e249c76af770a1bb82bc89c72e3 svn.655\n0000000000000000000000000000000000000000 svn.655\nbd0b543a76b1be2416908cc8c002e516254170fd svn.1474\n0000000000000000000000000000000000000000 svn.1474\nbd20ff182a70680ed102319d756558b2e6931fbd svn.424\n0000000000000000000000000000000000000000 svn.424\nbd37fb10c639aef30c2634843981e03cb922031d svn.863\n0000000000000000000000000000000000000000 svn.863\nbd518f331c660af28f9653af0c00bb2b2b377191 svn.186\n0000000000000000000000000000000000000000 svn.186\nbd58a3cc636648b5c4165f72df502718001e12e9 svn.446\n0000000000000000000000000000000000000000 svn.446\nbd674100764d4566a7df25e2960efea627c425ce svn.1431\n0000000000000000000000000000000000000000 svn.1431\nbd76d70e13036a042d53118dacdee7017ba24470 svn.1553\n0000000000000000000000000000000000000000 svn.1553\nbd778de72222fbc5dd4f8f1ca7e2f8e6df9cd8db svn.1456\n0000000000000000000000000000000000000000 svn.1456\nbd972fcd4ae0b295afac1b1167edefcff7a799d8 svn.512\n0000000000000000000000000000000000000000 svn.512\nbdb57322f46908ed60e5d8d5344ae9290baa525f svn.528\n0000000000000000000000000000000000000000 svn.528\nbe2016c79358602e569cec87eeaf7c8146873384 svn.569\n0000000000000000000000000000000000000000 svn.569\nbe34fecdf22396fc117ce041d3e350e6bf8460f8 svn.1236\n0000000000000000000000000000000000000000 svn.1236\nbe4a915563973368ff28084c8201f37af10ad46d svn.1193\n0000000000000000000000000000000000000000 svn.1193\nbe6acb7b9827ff8481db261ddd7f59df1a68025d svn.550\n0000000000000000000000000000000000000000 svn.550\nbe7d1bb42c34dace8162a84a0b9fa85405d580e3 svn.790\n0000000000000000000000000000000000000000 svn.790\nbe8eaf0b54747c56beb61fa29f5a1e52efbe7e30 svn.1425\n0000000000000000000000000000000000000000 svn.1425\nbe9c17ec40a22a0f2ac629eae2942c47a1e2e46e svn.455\n0000000000000000000000000000000000000000 svn.455\nbed7ea6f0af813c6bdbeb6ac61ff4622fb24043a svn.78\n0000000000000000000000000000000000000000 svn.78\nbee31785688b4b3ad4b9e754e7fadc6f73dafe07 svn.1724\n0000000000000000000000000000000000000000 svn.1724\nbeefaf3b957aa33f06304fbb5bd8d165f10dbca9 svn.484\n0000000000000000000000000000000000000000 svn.484\nbf196d6db35e67bad09d04c5517191d9545f3bb2 svn.175\n0000000000000000000000000000000000000000 svn.175\nbf5176323aac3d989544ca00e830a6ee030e2296 svn.289\n0000000000000000000000000000000000000000 svn.289\nbf78c745075753faf220e6a1127803633e5d74e2 svn.375\n0000000000000000000000000000000000000000 svn.375\nbf9ca3f4fc137b6dbf5f1ecc2f37731f5ba30899 svn.1240\n0000000000000000000000000000000000000000 svn.1240\nbfa60de05a0572cc29caa94e0200684fea4dc46b svn.1119\n0000000000000000000000000000000000000000 svn.1119\nbfadc2919b665414db3781ee35f6f78ec96571b7 svn.696\n0000000000000000000000000000000000000000 svn.696\nc02c67948ea3bc03cc631a66b6c8a3bbeb7a9a8b svn.199\n0000000000000000000000000000000000000000 svn.199\nc06cb14312e16fe08ed3b49d2dd2961b67823f49 svn.757\n0000000000000000000000000000000000000000 svn.757\nc098f2b1d264b43305edd5e67b84f838759c0df6 svn.139\n0000000000000000000000000000000000000000 svn.139\nc0b88e210dc389abc84d1a5a6b38b468580b9888 svn.753\n0000000000000000000000000000000000000000 svn.753\nc0de8e602dca315eaafdc99f0570a6205f36a799 svn.1117\n0000000000000000000000000000000000000000 svn.1117\nc0ef10d55142de04b6552639be0ef3beaecb8268 svn.1650\n0000000000000000000000000000000000000000 svn.1650\nc10095fc0b3379a4230af3086c2c82d99fb08f78 svn.383\n0000000000000000000000000000000000000000 svn.383\nc136b560a49ef16431d5ebda46bb181d3f30c5ba svn.1053\n0000000000000000000000000000000000000000 svn.1053\nc13e3d84f3a9a779d232194a736472c0c7dd30b6 svn.1238\n0000000000000000000000000000000000000000 svn.1238\nc163713e415ac6f33299f8773d95e2872b353910 svn.404\n0000000000000000000000000000000000000000 svn.404\nc167a9fb66504c0527d85bbbb2539550e9ef43a0 svn.2\n0000000000000000000000000000000000000000 svn.2\nc198ce1f5adb895f14699d3c28f1400ecd209222 svn.624\n0000000000000000000000000000000000000000 svn.624\nc19f7596a4c38ccc1376343243b5043a230b8f67 svn.511\n0000000000000000000000000000000000000000 svn.511\nc1b1502bbbc8ae1e989ec9139cdd4d1cc3844f7d svn.1405\n0000000000000000000000000000000000000000 svn.1405\nc1c93678d868e72993bcc2f3476ca75b791b1164 svn.1203\n0000000000000000000000000000000000000000 svn.1203\nc233bc3385ee77913c98866c10bad6f153f8daaa svn.540\n0000000000000000000000000000000000000000 svn.540\nc246630d322cb58675ddef6ebafaca4b73216df9 svn.506\n0000000000000000000000000000000000000000 svn.506\nc2a41082a6be8099136177ff717fa82cbae9758a svn.1308\n0000000000000000000000000000000000000000 svn.1308\nc2b3988e4f9b7530829e3b432df02e7ffe6743dd svn.1582\n0000000000000000000000000000000000000000 svn.1582\nc2bb172fd266f6ee32d3d4ba4a68e8ab72d21878 svn.885\n0000000000000000000000000000000000000000 svn.885\nc2bfbeb2a6189251637353781c52cbbf226d1bb4 svn.290\n0000000000000000000000000000000000000000 svn.290\nc2f22be977587da957824faa9aee58ff23f7bc55 svn.58\n0000000000000000000000000000000000000000 svn.58\nc3196a368da7180614190efc0395a63929bc6c08 svn.699\n0000000000000000000000000000000000000000 svn.699\nc332476fd1461f2dbc3467113e0d311b3b1ae528 svn.81\n0000000000000000000000000000000000000000 svn.81\nc3376fa426b1bd1328d044a74f8d98ffe822596f svn.563\n0000000000000000000000000000000000000000 svn.563\nc34c1e6c30e51ec604bcfeaca99a384ba6a08cbb svn.577\n0000000000000000000000000000000000000000 svn.577\nc368b0a6a927578a598c73058e7a089066fe0f2a svn.231\n0000000000000000000000000000000000000000 svn.231\nc3916e50b59483a7d5477a3d2830f7ebaa4e88b4 svn.44\n0000000000000000000000000000000000000000 svn.44\nc39afe8c02faf8e017fec2b555f588eae92caea9 svn.1475\n0000000000000000000000000000000000000000 svn.1475\nc3a8df412c3c5a6d85cff623f3c0bff309892c39 svn.1520\n0000000000000000000000000000000000000000 svn.1520\nc3cb6c6ea42d39bdff323e454767c966fbe273d6 svn.109\n0000000000000000000000000000000000000000 svn.109\nc3ffe484fd397106b02b346a57d4be5538b29bb7 svn.255\n0000000000000000000000000000000000000000 svn.255\nc415207cceeaa18d4159f1bf54b4622d257f97fa svn.1634\n0000000000000000000000000000000000000000 svn.1634\nc44554134dd77398bbdc9ed9d0c6d07ddd9bb0c8 svn.981\n0000000000000000000000000000000000000000 svn.981\nc4c5cddc0193b5f2f539a1164f6ceb64f2db3337 svn.881\n0000000000000000000000000000000000000000 svn.881\nc4d967b289e88afd7aaf513a69137c9ce9256d33 svn.195\n0000000000000000000000000000000000000000 svn.195\nc4ffdb58f38773e05bdb4cba2c07b11f13960a6a svn.333\n0000000000000000000000000000000000000000 svn.333\nc50fc0696ef444d7d29476442993549e858c5daa svn.1275\n0000000000000000000000000000000000000000 svn.1275\nc52bb6ff37c1dad3b2d4bbf9ad1523fdb8b28402 svn.1281\n0000000000000000000000000000000000000000 svn.1281\nc53e9e42bf066ea0d6cc8727434d0b4b2a475780 svn.1736\n0000000000000000000000000000000000000000 svn.1736\nc558481a7b1cb461664e002d6276a4a45bec895b svn.194\n0000000000000000000000000000000000000000 svn.194\nc5799f4131404885f9f5519469ddc7776db6acd3 svn.1673\n0000000000000000000000000000000000000000 svn.1673\nc5c15377573c533f5fc534fbf8f41ec86a050410 svn.411\n0000000000000000000000000000000000000000 svn.411\nc6088d1d3e29aa86a5afc947eb35f1e1748cadad svn.832\n0000000000000000000000000000000000000000 svn.832\nc6096476eeadc2981ea9bcc2583602eb9b65e9fa svn.170\n0000000000000000000000000000000000000000 svn.170\nc623c08ff5354afc9dcdaebc4c48e63db16c2925 svn.519\n0000000000000000000000000000000000000000 svn.519\nc6859bf896efd71340811b384ebe188adfc0ae95 svn.1335\n0000000000000000000000000000000000000000 svn.1335\nc68a59d35ff4aab7c379c6901a228c7046b9f9df svn.1104\n0000000000000000000000000000000000000000 svn.1104\nc6ef0f1ad18b66d53d89f594eb409da7c3f88c11 svn.602\n0000000000000000000000000000000000000000 svn.602\nc709cb0943774485ba12513d41f005dad4e1a73b svn.1121\n0000000000000000000000000000000000000000 svn.1121\nc7162a837fec86e35015ff1106affed097263e8d svn.1408\n0000000000000000000000000000000000000000 svn.1408\nc74f26086149a38489c2c4dd4d893430513e7d31 svn.633\n0000000000000000000000000000000000000000 svn.633\nc75b8dcb044876ed256dfa807fd6c0140d9533e7 svn.1296\n0000000000000000000000000000000000000000 svn.1296\nc773f6d895653c3ecbdf47ba449adaaac871078e svn.440\n0000000000000000000000000000000000000000 svn.440\nc778a0ae6038c46a24c0e8e13039475075ae661c svn.1656\n0000000000000000000000000000000000000000 svn.1656\nc80d72a952f4c7dc149862d334656d4f29494606 svn.825\n0000000000000000000000000000000000000000 svn.825\nc81f9bdea878709507f333f0dac8d3edac0ca837 svn.706\n0000000000000000000000000000000000000000 svn.706\nc846c8c22f53d3813d5f48cbbda31848358c73dc svn.585\n0000000000000000000000000000000000000000 svn.585\nc84ea36f4cda9bae146389588b04158d93297f5b svn.1709\n0000000000000000000000000000000000000000 svn.1709\nc8713311b73b6b948194bf185a1973b535eb32ba svn.515\n0000000000000000000000000000000000000000 svn.515\nc871904bc45d64e143a583a41c7f57fc8dbf0313 svn.1097\n0000000000000000000000000000000000000000 svn.1097\nc89c86f10a3bc43e3a6daa573f91138cc16a0d47 svn.1064\n0000000000000000000000000000000000000000 svn.1064\nc8ee9b5d34430df0ed14cf3f0f1dbc7f1d2b4b16 svn.223\n0000000000000000000000000000000000000000 svn.223\nc9773ae3b01374d1b3f9fdf518ca7fd54b573177 svn.1658\n0000000000000000000000000000000000000000 svn.1658\nc99bb50a463011e62a54fbbea512e004c7f8c460 svn.20\n0000000000000000000000000000000000000000 svn.20\nc9ca0c16cf8eb49e0e314f65a7a70de87e870bf9 svn.1380\n0000000000000000000000000000000000000000 svn.1380\nc9d500439ff90d14fe4769d91134d2f436a1fe50 svn.1706\n0000000000000000000000000000000000000000 svn.1706\nca15fea2e6e3e32a908ff6acc3206b7d92667ece svn.204\n0000000000000000000000000000000000000000 svn.204\nca1e28f7895a7a54dc98911762161f370b44f6b5 svn.1386\n0000000000000000000000000000000000000000 svn.1386\nca2da2130ce94193b4641865b4b58d278772d9e5 svn.1611\n0000000000000000000000000000000000000000 svn.1611\nca5dc5e9e08b86f309231af045059019a9fda402 svn.574\n0000000000000000000000000000000000000000 svn.574\nca6269080027653b454ce5144c0f95c448ec7bbf svn.1024\n0000000000000000000000000000000000000000 svn.1024\nca8b8620885703755c186ab7d84dd37157594b2f svn.1601\n0000000000000000000000000000000000000000 svn.1601\ncaea955bfc4a4bc41d45f9062f9611d4e1d0229d svn.358\n0000000000000000000000000000000000000000 svn.358\ncaf0c2b42ca9839ed7c717de26c99509062c4f8d svn.811\n0000000000000000000000000000000000000000 svn.811\ncaf4508acac58c6ada5a6e9e192af171711e1a2b svn.206\n0000000000000000000000000000000000000000 svn.206\ncb33bfaa936dd21013a66f1e67845305b028e6e3 svn.581\n0000000000000000000000000000000000000000 svn.581\ncb55e7475f222fe03bbef195d1b4492f482eabb9 svn.242\n0000000000000000000000000000000000000000 svn.242\ncbb1deea25e3a6c5106ca32b9acaad37231b711b svn.472\n0000000000000000000000000000000000000000 svn.472\ncbe7888df0dc04f3349262dd716114bf60584352 svn.1566\n0000000000000000000000000000000000000000 svn.1566\ncc1f76d8fe23fea528b8f58c37d15a005c165820 svn.1212\n0000000000000000000000000000000000000000 svn.1212\ncc2e1ec4536f68860d74e3d0ff320f5b88fa9237 svn.603\n0000000000000000000000000000000000000000 svn.603\ncc3224d42aebab5e87bcb500a56577bd6e7cf2a8 svn.1059\n0000000000000000000000000000000000000000 svn.1059\ncc38929af6efa218b77da7c8a8ad84f0c39f5bb5 svn.1128\n0000000000000000000000000000000000000000 svn.1128\ncc74a5c1fe3c598da6cdec23b389ca6e706a8eea svn.1785\n0000000000000000000000000000000000000000 svn.1785\ncc7bd4cd0d1b9f9a1091bd678047256d975ecb8d svn.1123\n0000000000000000000000000000000000000000 svn.1123\ncc8f3d28257506a3bc3fb7e14aa924ad38d59071 svn.90\n0000000000000000000000000000000000000000 svn.90\ncc91980a1e29913f02f3513fab75f8f23855be4f svn.900\n0000000000000000000000000000000000000000 svn.900\nccf7b27b64988bfa187b588e40db01975e687159 svn.287\n0000000000000000000000000000000000000000 svn.287\ncd0c3084edc9eb40f8310068d6ff6d6c7aad00a8 svn.719\n0000000000000000000000000000000000000000 svn.719\ncd416a203e19971ad4d604ca44aecec2d2917e46 svn.387\n0000000000000000000000000000000000000000 svn.387\ncda04daf48c6309f71c060158c6c5180d90136f6 svn.84\n0000000000000000000000000000000000000000 svn.84\ncdc4f7e698242ed2a17946fa3697e1fc7bc7d1c5 svn.1698\n0000000000000000000000000000000000000000 svn.1698\ncdf65e688238eedbc9f1036a903b6f03735c89c2 svn.1384\n0000000000000000000000000000000000000000 svn.1384\nce014fc1956a54e948c897831312b28ff2d40db5 svn.1330\n0000000000000000000000000000000000000000 svn.1330\nce2a2203d4f5ed56479a7ef201996ca4863a9c18 svn.517\n0000000000000000000000000000000000000000 svn.517\nce2f3a72ca9da939ab91df6d5cf193a093273a03 svn.836\n0000000000000000000000000000000000000000 svn.836\nce4bf5a027a32c44bcd3bbcf217bc00a06c310bb svn.1505\n0000000000000000000000000000000000000000 svn.1505\nce7a63e6fe40b68add31be83d48bcae92237fb02 svn.924\n0000000000000000000000000000000000000000 svn.924\ncebe6439577033f1a6727ae2422dccd487729e2a svn.980\n0000000000000000000000000000000000000000 svn.980\ncec5c2f0131a6a68816f3cd653bd86846942777d svn.165\n0000000000000000000000000000000000000000 svn.165\ncf048d23f9bb70efa03d93aa29281ed5bc489d56 svn.1151\n0000000000000000000000000000000000000000 svn.1151\ncf0b8fa619131998ad2d9797f62e6d4fdf80dd25 svn.1649\n0000000000000000000000000000000000000000 svn.1649\ncf10ea8151cb6d668dd33e5547bd4377c0e699e5 svn.990\n0000000000000000000000000000000000000000 svn.990\ncf26d7b1d28c8c5561998b8ce1df420dca9e75a7 svn.654\n0000000000000000000000000000000000000000 svn.654\ncf5ef451015e5b73967dcba66dda2942d0738030 svn.973\n0000000000000000000000000000000000000000 svn.973\ncfd3034246e5374810351f319e7be0bccc989b2c svn.398\n0000000000000000000000000000000000000000 svn.398\nd00dff1f4de7ee734b29f596b70e2a86ac22b93a svn.507\n0000000000000000000000000000000000000000 svn.507\nd02415804062ce29d1817d6637c2c83ae8c6a985 svn.613\n0000000000000000000000000000000000000000 svn.613\nd03329f07d150def647b13629f2076b7e7fed0d3 svn.295\n0000000000000000000000000000000000000000 svn.295\nd08bb1b3cfb3773e3ae72796242be9cd722efdf2 svn.1271\n0000000000000000000000000000000000000000 svn.1271\nd095eeb54e5be45074634e633045237f575e28e6 svn.485\n0000000000000000000000000000000000000000 svn.485\nd0b50a7c7a4b2a071640ed4545a9bef6fd0c5494 svn.1164\n0000000000000000000000000000000000000000 svn.1164\nd0ef00548e9d059f838033cf4f9af66de0a5d11f svn.141\n0000000000000000000000000000000000000000 svn.141\nd0f7efcca67d22b8670e257e765fb784dc7f5a98 svn.338\n0000000000000000000000000000000000000000 svn.338\nd12d8030ada4bdd6753f9526ceeee4b87e758081 svn.793\n0000000000000000000000000000000000000000 svn.793\nd16aa2935585fa8422e5b297f5fcd2315b51fdd1 svn.1415\n0000000000000000000000000000000000000000 svn.1415\nd1a89f0f04ae32433d86fad2e3d58cc018570315 svn.1524\n0000000000000000000000000000000000000000 svn.1524\nd1a8a2656a93d9b645072b1cf6ab5c4e3dd232d4 svn.244\n0000000000000000000000000000000000000000 svn.244\nd1add6a890d9e5f433efe2f4af3f820e94e5ee6e svn.730\n0000000000000000000000000000000000000000 svn.730\nd1b72540f662098dbafd526dabf48f09b5b9b2d2 svn.1745\n0000000000000000000000000000000000000000 svn.1745\nd1ecf43dff68904f9123a3a29892c4acc4c2ef26 svn.584\n0000000000000000000000000000000000000000 svn.584\nd1f98a7c06d1921d36afa0741486ea7f765b9aec svn.767\n0000000000000000000000000000000000000000 svn.767\nd2034da217433177c89e79436b59c38144cc9794 svn.1784\n0000000000000000000000000000000000000000 svn.1784\nd2173c1262774efeef1291f7062521389b7530f7 svn.890\n0000000000000000000000000000000000000000 svn.890\nd2282685ac5d1e06a7229f8c39db1d93dbe4edbd svn.1005\n0000000000000000000000000000000000000000 svn.1005\nd250a805dc0618e8ec8060ed8c1b244d3fd98c09 svn.1056\n0000000000000000000000000000000000000000 svn.1056\nd279bbbbc82cdf1c4885a4655f901116146df845 svn.789\n0000000000000000000000000000000000000000 svn.789\nd2893ea69beeefcfc5c9d56019ac5057dbfeeb6e svn.1779\n0000000000000000000000000000000000000000 svn.1779\nd294da29169a427c450192c84a94c4dc36917324 svn.535\n0000000000000000000000000000000000000000 svn.535\nd2a61719517d9749934b6ca8b91ef87eab5da8fe svn.1756\n0000000000000000000000000000000000000000 svn.1756\nd2cce60885eecfaa844f23a26f0fae43ac34c600 svn.572\n0000000000000000000000000000000000000000 svn.572\nd30456a99475a462bfd2ea3e831e15e27f4463ac svn.1564\n0000000000000000000000000000000000000000 svn.1564\nd323733a5c1667fd08cfda8f65df8adfa30b49ef svn.1617\n0000000000000000000000000000000000000000 svn.1617\nd34667423b3940c6105dbb70018eba21a0c66524 svn.1371\n0000000000000000000000000000000000000000 svn.1371\nd38fe2486bb57ddd100be7ac6d1597fe2a74fb01 svn.1182\n0000000000000000000000000000000000000000 svn.1182\nd3bf19cb7ce33da131cb742641b947af098964c5 svn.1260\n0000000000000000000000000000000000000000 svn.1260\nd3c0d5a01e96de0fb8357bd98fd53bcd78e3d8c7 svn.57\n0000000000000000000000000000000000000000 svn.57\nd409892c82ab05ee7f678687b756ae192b21cecd svn.1359\n0000000000000000000000000000000000000000 svn.1359\nd452bb73bf7d898707ffee6b8388800d4d4c32bb svn.1305\n0000000000000000000000000000000000000000 svn.1305\nd46c717574272bd2cc4b97238f42c410f2d9723e svn.664\n0000000000000000000000000000000000000000 svn.664\nd489475826609f4c8013a28bc31b72383a4c896e svn.1441\n0000000000000000000000000000000000000000 svn.1441\nd49d0a7941ed7725c7a3382573d06d0447e08503 svn.1631\n0000000000000000000000000000000000000000 svn.1631\nd4edf1cf60d10e3593223705033f460f10c70312 svn.634\n0000000000000000000000000000000000000000 svn.634\nd4fac6ea54a62e42d54f846ec3c0df741bd50888 svn.1321\n0000000000000000000000000000000000000000 svn.1321\nd5057e6c68d784cc5893da72e5ac5ac72f85b535 svn.575\n0000000000000000000000000000000000000000 svn.575\nd52d833924ed0f1074688d72c4ce2f644bb46f79 svn.525\n0000000000000000000000000000000000000000 svn.525\nd5820ec3227beb823a8a71b1ea5b1648d258dddc svn.1471\n0000000000000000000000000000000000000000 svn.1471\nd5941dda907940915cd2b05964593d096c6a2fc2 svn.261\n0000000000000000000000000000000000000000 svn.261\nd5969b00fa9734a4da0e33ce9c13e46abf3c8648 svn.1268\n0000000000000000000000000000000000000000 svn.1268\nd5a49ed72a6eb24831dabeed25d2568c7e61a6b9 svn.1131\n0000000000000000000000000000000000000000 svn.1131\nd63459c7fbabbdc9c1801175bb5444ed40b5569f svn.136\n0000000000000000000000000000000000000000 svn.136\nd6dcf317e3417fda2e633885be378e116945b9f5 svn.1146\n0000000000000000000000000000000000000000 svn.1146\nd6f2fd43ee48e2710a80bbdd99be983aeaa9967d svn.1504\n0000000000000000000000000000000000000000 svn.1504\nd76b0bdd379455f66e124a2d968a082dba710dde svn.1719\n0000000000000000000000000000000000000000 svn.1719\nd77977e676fdf1c0e2c05e4296f68933747e70ee svn.462\n0000000000000000000000000000000000000000 svn.462\nd77b4d4789f4e93914bb5b435665a77d65e1512e svn.805\n0000000000000000000000000000000000000000 svn.805\nd7900a6464c942596365347e0dac9d225f8efe73 svn.56\n0000000000000000000000000000000000000000 svn.56\nd7b9cd53e639ca63ff18350a898fdb9262305150 svn.1069\n0000000000000000000000000000000000000000 svn.1069\nd7c60eeed511fcedfc20d05815ef985ef9b00586 svn.331\n0000000000000000000000000000000000000000 svn.331\nd7d86ab3b0d4690912a88421518e2779e2be717a svn.1452\n0000000000000000000000000000000000000000 svn.1452\nd7eaf968bd14944c4399ca18e3b829489e278b87 svn.1376\n0000000000000000000000000000000000000000 svn.1376\nd8068096efa1a7478739d255bdc6995b649247d2 svn.1473\n0000000000000000000000000000000000000000 svn.1473\nd81716777b972787bf80f27bf34d33f40631ff90 svn.939\n0000000000000000000000000000000000000000 svn.939\nd8226f2dcf0a14dc2edd0704b8ca1d71288f0beb svn.743\n0000000000000000000000000000000000000000 svn.743\nd86244661ec585e3ce0088e7394dbb4e5d530152 svn.1681\n0000000000000000000000000000000000000000 svn.1681\nd87671d34c248fbf7b4340eb1d77fd5baf93cb46 svn.367\n0000000000000000000000000000000000000000 svn.367\nd87bf7bd5791e7facd0e88e76e0b9902c4fb8f90 svn.1712\n0000000000000000000000000000000000000000 svn.1712\nd8945d2c82435de57ec6a5aadf3fdbcefe89da51 svn.548\n0000000000000000000000000000000000000000 svn.548\nd89a70b3f6b24c62fecd63e30e79730460ed3fb3 svn.1028\n0000000000000000000000000000000000000000 svn.1028\nd8d5d0f85bcd1d0c07758d7056f2a007f906e122 svn.1328\n0000000000000000000000000000000000000000 svn.1328\nd8f61775a015be47edb16712a599b1831e0cd263 svn.99\n0000000000000000000000000000000000000000 svn.99\nd9236d69454cfafe89415698c11e0fc167dc49cf svn.1777\n0000000000000000000000000000000000000000 svn.1777\nd94c1c5eac0a2775caacf5136617e4f6360ef886 svn.150\n0000000000000000000000000000000000000000 svn.150\nd9627b379d0d36e3ca302943cea334cfe052fc8c svn.1554\n0000000000000000000000000000000000000000 svn.1554\nd96b5c00a521c6c436d8a6cd506482a24e55e823 svn.222\n0000000000000000000000000000000000000000 svn.222\nd97109d30a699b5ad60025583df7420df09c7c96 svn.457\n0000000000000000000000000000000000000000 svn.457\nd9a2f6d083d569f07828720deb87372fceeca32c svn.1741\n0000000000000000000000000000000000000000 svn.1741\nd9a557f509a5ad811568db27ae676efcba75599e svn.248\n0000000000000000000000000000000000000000 svn.248\nd9bbea43918ee4509e12583fd4603f02576707c2 svn.59\n0000000000000000000000000000000000000000 svn.59\nd9ce9b6db9d314a7a518f90d28db3c79686fbeff svn.1764\n0000000000000000000000000000000000000000 svn.1764\nd9e3844aafe7c3bbf0b18539bac4b24de5acd0b7 svn.1252\n0000000000000000000000000000000000000000 svn.1252\nda1e5cb40d297b11a5728e70b997a20a7f5a4d7d svn.815\n0000000000000000000000000000000000000000 svn.815\nda2fbf9d6dbe9531efc6eec2f99fef06e48b4fb4 svn.1546\n0000000000000000000000000000000000000000 svn.1546\nda5a324608caba5c8d1dd13fca193ea7cb3fbc57 svn.690\n0000000000000000000000000000000000000000 svn.690\nda5b5aade6b6c9aaac226c17cf61329043cce3bb svn.1298\n0000000000000000000000000000000000000000 svn.1298\nda9027e61c2574ca042f003b2db7a9390340c772 svn.741\n0000000000000000000000000000000000000000 svn.741\nda9a9555e9dd1a7132ba696c7139b5af79e81e8d svn.651\n0000000000000000000000000000000000000000 svn.651\ndaa8e9f1533930e9fe004f28c6a3d8bbaa3873c1 svn.1289\n0000000000000000000000000000000000000000 svn.1289\ndace5ca959f3d429a5d8422f8ade8ff2020885ee svn.751\n0000000000000000000000000000000000000000 svn.751\ndad0938a6a73c2cb20fe741ed5885a631bacbb1a svn.875\n0000000000000000000000000000000000000000 svn.875\ndb0c05c310ce7bde74d48f39ed1e4867564e10c6 svn.1191\n0000000000000000000000000000000000000000 svn.1191\ndb128cb36550851c7d25f7982a982b335118667c svn.1513\n0000000000000000000000000000000000000000 svn.1513\ndb2716d7aacaf947331b437b6f9259a6c3718fed svn.388\n0000000000000000000000000000000000000000 svn.388\ndb2d8340dc8c3f4db047424dbce84829600b4f4c svn.1353\n0000000000000000000000000000000000000000 svn.1353\ndb428cad7fb72268daeed2a89e28ef1f4d1642aa svn.1373\n0000000000000000000000000000000000000000 svn.1373\ndb56f738ed6ac4634ab1bce96b763d703986c9e2 svn.1144\n0000000000000000000000000000000000000000 svn.1144\ndb725052b6e3cd8b708a225b35f7d6df48a2730e svn.1744\n0000000000000000000000000000000000000000 svn.1744\ndbb9baaf6763db6c1ec069bda87eb822e4bb2850 svn.1523\n0000000000000000000000000000000000000000 svn.1523\ndc3413343f3a6e0f3adb3f5c851611256825278d svn.76\n0000000000000000000000000000000000000000 svn.76\ndc3cbb2f3c8b1d30c1bec6551f29661850c79dc0 svn.192\n0000000000000000000000000000000000000000 svn.192\ndc5c031766069237f69c3fb997e20a0cc1c9a1d4 svn.1227\n0000000000000000000000000000000000000000 svn.1227\ndcade05709ccd8d3556c082b9bc17a70d690de71 svn.277\n0000000000000000000000000000000000000000 svn.277\ndcb0b183591b93e51f3c7c084e681a575dc9b43e svn.444\n0000000000000000000000000000000000000000 svn.444\ndcd3605a556003cdd44f15d61fcbd002437b2882 svn.955\n0000000000000000000000000000000000000000 svn.955\ndd14ef8ddc2d8b522b76ea7f186a38ab3b364fca svn.779\n0000000000000000000000000000000000000000 svn.779\ndd198266f3cb029f0d5bf63586693e79e0614a4d svn.595\n0000000000000000000000000000000000000000 svn.595\ndd258ffab1d84f097561d6e2332cef413a531211 svn.929\n0000000000000000000000000000000000000000 svn.929\ndd441b4d3485d9f76b04557b2081407e7b390cbf svn.675\n0000000000000000000000000000000000000000 svn.675\ndd6713a2070d4786faa56ee1b5de99de057b9a25 svn.1567\n0000000000000000000000000000000000000000 svn.1567\ndd852bef6ce6f0af3815cb2844ed2425fc387201 svn.1644\n0000000000000000000000000000000000000000 svn.1644\ndd8c06cbfd201a05501a77a29ba089b6e7405b6b svn.121\n0000000000000000000000000000000000000000 svn.121\ndd9b5bdb90ae7f0fa40a52256c664c4286a9f00f svn.1692\n0000000000000000000000000000000000000000 svn.1692\ndde9bbaaa7f77a7d3a3ffb0a9ac11ed18665d63f svn.1122\n0000000000000000000000000000000000000000 svn.1122\nde12d958ec8d61256474cbae0699b46bc245f1c8 svn.1740\n0000000000000000000000000000000000000000 svn.1740\nde5a16dd44a65e1864acdd8bf0697f28adb682cd svn.187\n0000000000000000000000000000000000000000 svn.187\nde5df261f5d9f6a66cc5e8dcc6217d28ae3e02f8 svn.1171\n0000000000000000000000000000000000000000 svn.1171\nde790db535467baf165508a67ea51f567ef68b4b svn.1155\n0000000000000000000000000000000000000000 svn.1155\nde989f9e00795ce23acb6447fe09316d5dc991f2 svn.1713\n0000000000000000000000000000000000000000 svn.1713\ndeb4493b2fc41bd98753f9f66a12b4bef8c683a2 svn.1588\n0000000000000000000000000000000000000000 svn.1588\ndeb5472bace0f20d2abb196ee608947f4b24e1a7 svn.449\n0000000000000000000000000000000000000000 svn.449\ndec1498ac58f2c3b2d8b4dea3a41330c6942f346 svn.1531\n0000000000000000000000000000000000000000 svn.1531\ndee00b9b86e0ff5538b2e973a60753d2798f3995 svn.1020\n0000000000000000000000000000000000000000 svn.1020\ndf19be55ad5532cb4a831e24a8c7d29282a90c29 svn.1250\n0000000000000000000000000000000000000000 svn.1250\ndf19fde1fafb23c8e6d8eeea7b4a71c3663d939f svn.1602\n0000000000000000000000000000000000000000 svn.1602\ndf31e314e96595054761ac488c18cab48607337c svn.276\n0000000000000000000000000000000000000000 svn.276\ndf630d602734c833d58c17456dd0054e4c81dc7e svn.374\n0000000000000000000000000000000000000000 svn.374\ndf9eee094325daa18d3d8443f71c7bb76e7776e2 svn.1717\n0000000000000000000000000000000000000000 svn.1717\ndfa1661607fa5daa6f98700397f518ace136fbab svn.776\n0000000000000000000000000000000000000000 svn.776\ne0186afc0c8e2fffa8abf02e4414ce5fc887b1a8 svn.1087\n0000000000000000000000000000000000000000 svn.1087\ne060d802c624e7f6b9dcffbd2f4b24c02797939f svn.907\n0000000000000000000000000000000000000000 svn.907\ne08a54b5f3aecea5b384fb7126981c9c9dccf959 svn.594\n0000000000000000000000000000000000000000 svn.594\ne1401f51fca72b608abb63a3cb352c1295b65bf1 svn.12\n0000000000000000000000000000000000000000 svn.12\ne14986fc7821ce361b2a6c1ba13ac90f5dcd1429 svn.1138\n0000000000000000000000000000000000000000 svn.1138\ne14a71b2045a992785bb3f3326770efbd02a602e svn.1299\n0000000000000000000000000000000000000000 svn.1299\ne175cf81425877f3bc1e6b419bfd90d2bcf43086 svn.1412\n0000000000000000000000000000000000000000 svn.1412\ne20489266f4ab2b653edd84cf071748f571e8cf7 svn.851\n0000000000000000000000000000000000000000 svn.851\ne20dca56fba1a0808a13fc700a93527d0b82090c svn.1409\n0000000000000000000000000000000000000000 svn.1409\ne22471b904aa14082fa012133b648926bf438c43 svn.992\n0000000000000000000000000000000000000000 svn.992\ne24d68fd580a8526b5976caf4b805cb73bf69f16 svn.765\n0000000000000000000000000000000000000000 svn.765\ne272895dc859c84fa35245a1c186bf4a252defbd svn.1581\n0000000000000000000000000000000000000000 svn.1581\ne27c6d750b6b94c4ca510903d46d984028b3b631 svn.1797\n0000000000000000000000000000000000000000 svn.1797\ne286ee5b821fc57e18214c2ff70f75cce9cd11d4 svn.354\n0000000000000000000000000000000000000000 svn.354\ne2a5af31a55fac49c28b369e46c03ac0032729dc svn.1571\n0000000000000000000000000000000000000000 svn.1571\ne2c4af6d557fd7530e53c6b8ce95986183a5aee8 svn.22\n0000000000000000000000000000000000000000 svn.22\ne2e61b10359f50643a8478d3b497975e369010c8 svn.430\n0000000000000000000000000000000000000000 svn.430\ne2fcb3571d13044d75501ffc65691fd290c988b4 svn.1507\n0000000000000000000000000000000000000000 svn.1507\ne31e7ae5250c8356021f9972781c3b7911f41fe2 svn.395\n0000000000000000000000000000000000000000 svn.395\ne381af52bfacd5303d5818f8825db36145412dfe svn.1343\n0000000000000000000000000000000000000000 svn.1343\ne38c05dfa50e1f3981da387ee1d151dff5a9dc16 svn.941\n0000000000000000000000000000000000000000 svn.941\ne38cbe2eab336984ca2a298b44c97f4031845096 svn.716\n0000000000000000000000000000000000000000 svn.716\ne3a860dc2b21287ba888135f8892e4b469d17383 svn.1175\n0000000000000000000000000000000000000000 svn.1175\ne3aa0614a4d655db7ecabf95adeb4b09f3e07fe2 svn.224\n0000000000000000000000000000000000000000 svn.224\ne3c36f35db79a36c92df45b4b2a7334a2c77fe01 svn.762\n0000000000000000000000000000000000000000 svn.762\ne3ded6a4190ab349961ad3b103f14290bedd35a9 svn.110\n0000000000000000000000000000000000000000 svn.110\ne3f501f80bc2dce414273f63f73f5f6c33aba297 svn.1075\n0000000000000000000000000000000000000000 svn.1075\ne3fcc61e983e4bb2c7a5141f5278744e3f2c2c1e svn.1266\n0000000000000000000000000000000000000000 svn.1266\ne3fd071777c105975f82c15a7f45b53111871fb2 svn.490\n0000000000000000000000000000000000000000 svn.490\ne42999672ae5fab1bdfdccc0b758405407282bc6 svn.852\n0000000000000000000000000000000000000000 svn.852\ne45f96c679ac12778209b30973538c4313478ce4 svn.893\n0000000000000000000000000000000000000000 svn.893\ne4c6f2afb5e60e5fc975f8ad197b9e05977ad8d5 svn.1479\n0000000000000000000000000000000000000000 svn.1479\ne4cb89f2f2b7af049bb785275e75aec6345ee40e svn.184\n0000000000000000000000000000000000000000 svn.184\ne4cfc303bb1f0eec319d337ae4f6d3aaa09e4d3e svn.1392\n0000000000000000000000000000000000000000 svn.1392\ne4efa5208288ef858f2ccbfe4211a0a6a2944785 svn.1111\n0000000000000000000000000000000000000000 svn.1111\ne53152d529b0834ffb2ba75947ebd8a03ec10e6a svn.660\n0000000000000000000000000000000000000000 svn.660\ne562e09b6f3cebd6cdf17f64fdf6328455b37962 svn.1807\n0000000000000000000000000000000000000000 svn.1807\ne5959ab6348453899a6e8bb3bf49de951d03d4d0 svn.454\n0000000000000000000000000000000000000000 svn.454\ne596a16a40a2a897633797103f6d2b30025a8663 svn.1126\n0000000000000000000000000000000000000000 svn.1126\ne5bf9353cae7206a2f4fcaaf06db42c853a0e45c svn.297\n0000000000000000000000000000000000000000 svn.297\ne649c5da9087385ded87e7908622a7a3aa432cf0 svn.775\n0000000000000000000000000000000000000000 svn.775\ne65114d76ec41069db902fb50adc1a6caaf59c18 svn.356\n0000000000000000000000000000000000000000 svn.356\ne68e2fb451471695ee1e1ac0895a44847be41b8a svn.364\n0000000000000000000000000000000000000000 svn.364\ne6ca392f44d89c82ed47827cc351e62b93ecf471 svn.95\n0000000000000000000000000000000000000000 svn.95\ne70bb034f46bb63de837c054b9c0082a93eba286 svn.1652\n0000000000000000000000000000000000000000 svn.1652\ne71694eebd5988be6220b165de355ad6f5639ed8 svn.1086\n0000000000000000000000000000000000000000 svn.1086\ne7308687c3771a5ce5aad9de3c7960678f2d2c64 svn.952\n0000000000000000000000000000000000000000 svn.952\ne746beb215617b69a96729b6fe62d3c16ab2bc1a svn.68\n0000000000000000000000000000000000000000 svn.68\ne764b36940ad11559c8fb355200a5be3dc3e5227 svn.422\n0000000000000000000000000000000000000000 svn.422\ne79547d8e6826ba37bb81bb572f0f116304a0532 svn.1770\n0000000000000000000000000000000000000000 svn.1770\ne809a113e46e5f9bcb9e426b722ef93112da30e3 svn.1493\n0000000000000000000000000000000000000000 svn.1493\ne80ed81b5533cc74f9b9a3f78cb194a09a2eb9f0 svn.687\n0000000000000000000000000000000000000000 svn.687\ne8257b5e96d8fa36db504f469fedac7aad1c8936 svn.986\n0000000000000000000000000000000000000000 svn.986\ne829e697b9056d623104f8c58c7a17674b15981f svn.1577\n0000000000000000000000000000000000000000 svn.1577\ne8460c5500b005428e85959558665ec48edca809 svn.1538\n0000000000000000000000000000000000000000 svn.1538\ne854fcfd3bb2f14339c957d2c8348936ae4db763 svn.518\n0000000000000000000000000000000000000000 svn.518\ne865de13f4a9b9ec7d06ba8089c4d5a3f5c70ead svn.61\n0000000000000000000000000000000000000000 svn.61\ne8676721e034172dff52af911b38c952fd3b0f33 svn.1414\n0000000000000000000000000000000000000000 svn.1414\ne877277c9bd2d4708ee8376b6884d49742f1178c svn.847\n0000000000000000000000000000000000000000 svn.847\ne880a7d4823404c5e3c23b8e85e5abaae7f989ca svn.1544\n0000000000000000000000000000000000000000 svn.1544\ne88577b2e9f7b5cc4b6a71467ddecf926d324d39 svn.1262\n0000000000000000000000000000000000000000 svn.1262\ne8be9d29f2ebd43865b52006a536720d34475659 svn.926\n0000000000000000000000000000000000000000 svn.926\ne8c552566b7e611e0b1cf5507fab4c0a71641441 svn.689\n0000000000000000000000000000000000000000 svn.689\ne90d98ceddd5099b8e59776cea78818a4e963cef svn.334\n0000000000000000000000000000000000000000 svn.334\ne943ea00d2b15c1198088741c69aac8a5d1ea97b svn.423\n0000000000000000000000000000000000000000 svn.423\ne951fea3033250e64089a704e19bd9db162f6ae7 svn.1295\n0000000000000000000000000000000000000000 svn.1295\ne97ce937fcc7314713f79db741948cdc79939a6d svn.1809\n0000000000000000000000000000000000000000 svn.1809\ne9882484b7cdc4fa2408f82a6026df3459fa4c95 svn.1057\n0000000000000000000000000000000000000000 svn.1057\ne9c3d7d08b1f72942ee18335ece0c83170d34040 svn.1163\n0000000000000000000000000000000000000000 svn.1163\ne9c64f18e08b97a9c2a54180789f65013d61873d svn.419\n0000000000000000000000000000000000000000 svn.419\ne9c7c5702922b8a61da9d0bf435351e7f5e6861b svn.1690\n0000000000000000000000000000000000000000 svn.1690\ne9fb3e756f074d3e17131d1315eca56359a3b8ef svn.1073\n0000000000000000000000000000000000000000 svn.1073\nea01607fed384e779d95ba04c70d71f2afc3efe1 svn.1269\n0000000000000000000000000000000000000000 svn.1269\nea203317990ef509bca17d28c03b924b4d190398 svn.1550\n0000000000000000000000000000000000000000 svn.1550\nea21e74c0a987a9b21bb42780b8e5fe427f2a544 svn.749\n0000000000000000000000000000000000000000 svn.749\nea30a44e004f71caf90f435b9a94ed9b447170e3 svn.904\n0000000000000000000000000000000000000000 svn.904\nea3443fc287b6369bcd03204df4881ca25154ada svn.1630\n0000000000000000000000000000000000000000 svn.1630\nea3cea63f40d84cc71c1134c35ec0855b53af719 svn.1001\n0000000000000000000000000000000000000000 svn.1001\nea9848a0989f43d9443227126e04b57980d5114d svn.1667\n0000000000000000000000000000000000000000 svn.1667\neaf6f737155f7883178b450261f306e96f07ada3 svn.695\n0000000000000000000000000000000000000000 svn.695\neb4a739d738bedcd955a47f678793475812429ee svn.1263\n0000000000000000000000000000000000000000 svn.1263\neb820e77d4b93ed6c8795aff991f12609bf022ae svn.251\n0000000000000000000000000000000000000000 svn.251\nebc6d7fcbe348258a599674b79925ebcbb3c8e11 svn.1327\n0000000000000000000000000000000000000000 svn.1327\nebe4ce0fe92be979d2480f48faedce28a8181fc5 svn.1344\n0000000000000000000000000000000000000000 svn.1344\nebe9411ef03491934c417c019a8d251d74ce2de7 svn.899\n0000000000000000000000000000000000000000 svn.899\nec43da2bc1208370cf8306a529bd705bf4490496 svn.1297\n0000000000000000000000000000000000000000 svn.1297\nec4cf5d62741efc76e9d0202f302e4e9dd99fc28 svn.1288\n0000000000000000000000000000000000000000 svn.1288\nec6b12d6999dc1ca296dcac72bca469d9c26fa0b svn.466\n0000000000000000000000000000000000000000 svn.466\nec84905bae92fe79db17866c3b72deff84efa51f svn.797\n0000000000000000000000000000000000000000 svn.797\nec99f6513ccc04d2649415a033e0e832a2c69fe0 svn.1067\n0000000000000000000000000000000000000000 svn.1067\necbad3699297b914ebc10cebcf803150491faec4 svn.254\n0000000000000000000000000000000000000000 svn.254\necc98904c752084403545715b184cf55b8781b39 svn.718\n0000000000000000000000000000000000000000 svn.718\ned216bcd32d70aee3bcff4babcdb2b88b01c8902 svn.1679\n0000000000000000000000000000000000000000 svn.1679\ned436c5ee36728b29111f713dd0f242b6ad77c49 svn.368\n0000000000000000000000000000000000000000 svn.368\ned945662fb56dac71f20159d917c458c5a698995 svn.359\n0000000000000000000000000000000000000000 svn.359\ned96e2ae3cba35e2132ecaabf74720cf1b2b4961 svn.777\n0000000000000000000000000000000000000000 svn.777\nedef3025dc5c36b041481564f09873f383a55acf svn.456\n0000000000000000000000000000000000000000 svn.456\nedf4eb2310b560a3832bbecc68544318c8a11f1a svn.891\n0000000000000000000000000000000000000000 svn.891\nedf78a84101a105b2387ecf031bb5cbbc534011c svn.1023\n0000000000000000000000000000000000000000 svn.1023\nee7ab4e92c74972fa5ece220604ad7d2f5908fcc svn.143\n0000000000000000000000000000000000000000 svn.143\neeb699ff980833c34f5c8b2b4219d194d73178bc svn.1542\n0000000000000000000000000000000000000000 svn.1542\neec38ccb6c6cf3b3ea24e86790e52afee26a21ad svn.1124\n0000000000000000000000000000000000000000 svn.1124\need10c69c338f4379b4a2842a0aca95d478b335d svn.497\n0000000000000000000000000000000000000000 svn.497\need9343db961250471a573539bdc4a82fe4e79d8 svn.669\n0000000000000000000000000000000000000000 svn.669\neee672ed42fb075df1900810565659776e536bb5 svn.1114\n0000000000000000000000000000000000000000 svn.1114\nef0b08e329dd1467460a7a9c7bf3e53d4010ae0c svn.739\n0000000000000000000000000000000000000000 svn.739\nef3a39e8fd9b8985c6b0c66375adc2ed000ef4ab svn.807\n0000000000000000000000000000000000000000 svn.807\nef3bfe42a50abc9cedf6528545c73d627f390157 svn.103\n0000000000000000000000000000000000000000 svn.103\nef56cb3058242b6841511103d2e067f09933814c svn.107\n0000000000000000000000000000000000000000 svn.107\nef70305750bd1da1b24174a8ee9507770e9fe3b5 svn.160\n0000000000000000000000000000000000000000 svn.160\nef9eacafbae7825e35d64c77f367bbee0947ae03 svn.1485\n0000000000000000000000000000000000000000 svn.1485\nefde317797334ba555386283b8e00fca5a77fec8 svn.1612\n0000000000000000000000000000000000000000 svn.1612\nefe0af4d7cb04614b52bf99845c489915c73f047 svn.1491\n0000000000000000000000000000000000000000 svn.1491\nf014b0f1317696d0743507d02beb5920b075de59 svn.618\n0000000000000000000000000000000000000000 svn.618\nf037671d57fd06d6cb9be940aafaf0e4e7ef37a8 svn.1219\n0000000000000000000000000000000000000000 svn.1219\nf06de9448050dfaeb6eb120dfc5859cb4411bfa8 svn.481\n0000000000000000000000000000000000000000 svn.481\nf06eea2eeee19f26d81fcd1580e44da160e7f874 svn.1084\n0000000000000000000000000000000000000000 svn.1084\nf0837e1ecbe583af4d8991451dda24383d2833e7 svn.1529\n0000000000000000000000000000000000000000 svn.1529\nf089bf123355e4f58cccae286daa8341b0faede8 svn.1285\n0000000000000000000000000000000000000000 svn.1285\nf0bfbf4f0ff8234095f4e9260554d47b0d6c7e6d svn.997\n0000000000000000000000000000000000000000 svn.997\nf0c6ace804246be6499292171b3c2a25a2d98469 svn.1455\n0000000000000000000000000000000000000000 svn.1455\nf0cbaf7bafdda306990b6d7380d2588078a449f2 svn.1575\n0000000000000000000000000000000000000000 svn.1575\nf0d1af1d9f74a64cc2f3ba41c4791f8ceec4154f svn.568\n0000000000000000000000000000000000000000 svn.568\nf0d54897e223f01b8b4f0af89c4c1260b7e5dd1e svn.1352\n0000000000000000000000000000000000000000 svn.1352\nf1114268a7373a1decbea482e7d1d042756761ae svn.1534\n0000000000000000000000000000000000000000 svn.1534\nf14a3e397ca98de852fa7cce40243435da79ff78 svn.816\n0000000000000000000000000000000000000000 svn.816\nf157b1a713d1f6fd5e2fd6781ed133f248f5ffab svn.1372\n0000000000000000000000000000000000000000 svn.1372\nf162e5645d5c5e4fe5022a50b8c4dedd960b6401 svn.657\n0000000000000000000000000000000000000000 svn.657\nf19bf1ae6de86f7ae40021ab70f97976f5e8ca65 svn.37\n0000000000000000000000000000000000000000 svn.37\nf1b05bcc11f5d4e75e88d6314a03f44993124b94 svn.892\n0000000000000000000000000000000000000000 svn.892\nf1eb186c43debebfd70fd7d16ae2a582698077fa svn.1034\n0000000000000000000000000000000000000000 svn.1034\nf1ff6856474c9ff16b490b8383b00045b1aca3bf svn.1423\n0000000000000000000000000000000000000000 svn.1423\nf249bcbc653b330265ec31240211c662d77b7a29 svn.1434\n0000000000000000000000000000000000000000 svn.1434\nf259010e70d330e0a78843d5af6e080c42d598aa svn.1543\n0000000000000000000000000000000000000000 svn.1543\nf29c306f22336754779b19213871262f0f8ba7cd svn.28\n0000000000000000000000000000000000000000 svn.28\nf2bc5e164d73596939d721b2a579ec55a58d1274 svn.1788\n0000000000000000000000000000000000000000 svn.1788\nf2d3a4034d896bf6b4ecb9960aa2f5b9b671f34e svn.920\n0000000000000000000000000000000000000000 svn.920\nf2e36095b95676637a36541b79f06f6213984bb0 svn.62\n0000000000000000000000000000000000000000 svn.62\nf2ffa05cb5671eb0bad62fcd95d3ba9ecbee3acb svn.1088\n0000000000000000000000000000000000000000 svn.1088\nf310923770b1b1341ca9163498b0f01ca04ca84c svn.728\n0000000000000000000000000000000000000000 svn.728\nf37e8c96a1cfdc199dafc786e656188c8f618fd0 svn.1606\n0000000000000000000000000000000000000000 svn.1606\nf37f6dcc2fada56360587c2ad701ebf4778e46c9 svn.352\n0000000000000000000000000000000000000000 svn.352\nf38b4427c24b586efc68101d772f6d08fa27f810 svn.1470\n0000000000000000000000000000000000000000 svn.1470\nf3978c2cfb085d876d2d56ebb70d8be183043f30 svn.1089\n0000000000000000000000000000000000000000 svn.1089\nf3a1522ad4de815aa5b652f140a62a7daa67911b svn.80\n0000000000000000000000000000000000000000 svn.80\nf3b88e440ac38fea149a4203b51d4e4656affb6f svn.100\n0000000000000000000000000000000000000000 svn.100\nf3da9130ae74c98e353cd540f6904d4979a37491 svn.1704\n0000000000000000000000000000000000000000 svn.1704\nf40234b616dde3de2d3a9c1c6cd2ec733b9afb7d svn.197\n0000000000000000000000000000000000000000 svn.197\nf4793542a3d86170546bfd2ce6b93214dc1aa753 svn.209\n0000000000000000000000000000000000000000 svn.209\nf487178c028c014939dd70e7b5be731045293465 svn.1559\n0000000000000000000000000000000000000000 svn.1559\nf48ce9105d52596c8d44c6398150fbd6aa5ab0b1 svn.1574\n0000000000000000000000000000000000000000 svn.1574\nf4b6a2cfa7a672dcfc108ea4287f43bd48927dee svn.1361\n0000000000000000000000000000000000000000 svn.1361\nf4d9bb935e4e95550c4044ca22d31558411f0113 svn.962\n0000000000000000000000000000000000000000 svn.962\nf4fa817475a03cfbbcb7f9c359f2142ed09b4556 svn.1211\n0000000000000000000000000000000000000000 svn.1211\nf50108a56ae7020f7c1de5364db721956416f5c8 svn.384\n0000000000000000000000000000000000000000 svn.384\nf52b3c8eee5a72a545b2aee5c541e7745fc9129f svn.42\n0000000000000000000000000000000000000000 svn.42\nf59f115516f715da789795a1b6f39c9815c6077b svn.1198\n0000000000000000000000000000000000000000 svn.1198\nf6366f0ed82d609d1eedfa18d330b4056fe57515 svn.1188\n0000000000000000000000000000000000000000 svn.1188\nf639a64b8d4cd31d7d2f98ee8f71613b613bd1ae svn.145\n0000000000000000000000000000000000000000 svn.145\nf657ebf67d707a3b313b69cabe68d54fcdc2cab7 svn.478\n0000000000000000000000000000000000000000 svn.478\nf6e445f88d811e70314f902456dcf1753b4161cc svn.1527\n0000000000000000000000000000000000000000 svn.1527\nf6f3ef09895ad1890e0125dfdc544f8e5f4ad943 svn.927\n0000000000000000000000000000000000000000 svn.927\nf771f2707bd7e55861e4a451dfa7ed4193053486 svn.794\n0000000000000000000000000000000000000000 svn.794\nf773270afc6330b8d95b1da050a0f4d59c40e135 svn.732\n0000000000000000000000000000000000000000 svn.732\nf7862e1b9fade96044ed6c6d07fb80fe4b472e74 svn.1750\n0000000000000000000000000000000000000000 svn.1750\nf7d162799d92a2d0fac3185a96dad3aae2c7c0f6 svn.1248\n0000000000000000000000000000000000000000 svn.1248\nf7f92abef4aebe413aa4d17fa2018a6da52bd649 svn.1791\n0000000000000000000000000000000000000000 svn.1791\nf811e99c84648f894fa79d6e54bc96339e61af9a svn.1029\n0000000000000000000000000000000000000000 svn.1029\nf84de6dbd29c9686d91e3e9a818033962fedfff5 svn.555\n0000000000000000000000000000000000000000 svn.555\nf8792cdf3ba035cee963c95858cae239df55af31 svn.116\n0000000000000000000000000000000000000000 svn.116\nf89742203405b18315a5fb2b688b63ab0068f6cb svn.1468\n0000000000000000000000000000000000000000 svn.1468\nf8fce3c3ef6220fc74b60123177de796918b4bff svn.912\n0000000000000000000000000000000000000000 svn.912\nf91c56b9314ca1653f870f8d0494a97dd317bc04 svn.1187\n0000000000000000000000000000000000000000 svn.1187\nf936f724467761ab6d8c402e2cc52352b267b830 svn.1569\n0000000000000000000000000000000000000000 svn.1569\nf938afbd0ef3d4d3ae3f6d82f1039b9d84f2ddf0 svn.1370\n0000000000000000000000000000000000000000 svn.1370\nf9caeee4910376f6748008a70712f08b76a5b932 svn.849\n0000000000000000000000000000000000000000 svn.849\nf9e599e709a08f177a0e9c4a73ed3ea2dc883990 svn.1772\n0000000000000000000000000000000000000000 svn.1772\nfa0bbf57fc4c71c4890822f1d927eb8c2a39df20 svn.1154\n0000000000000000000000000000000000000000 svn.1154\nfa19b4c0a2c0f2e9c94261256bc28fb7cc421e7f svn.162\n0000000000000000000000000000000000000000 svn.162\nfa2f76a678e5aec01e25d9d2d81702225d28287e svn.1738\n0000000000000000000000000000000000000000 svn.1738\nfa5f7353102a01fc485e7d5d83aaae50f60c62b7 svn.373\n0000000000000000000000000000000000000000 svn.373\nfa8a351202d659f65fb62e5720f31e73e046a67a svn.1292\n0000000000000000000000000000000000000000 svn.1292\nfabe2f3d8e695775b59a0b43edcec84988230cb8 svn.1159\n0000000000000000000000000000000000000000 svn.1159\nfac6f6a5ceb212eb3d9b54a28459d6e9e1425aca svn.685\n0000000000000000000000000000000000000000 svn.685\nfad4f97a8a85bb4274f876f0d05ea0506562a130 svn.873\n0000000000000000000000000000000000000000 svn.873\nfad60561bfd2e187de42f3557dedfcf91fe5a35f svn.844\n0000000000000000000000000000000000000000 svn.844\nfaeb3b49abaff460913359e420c0ffbd3b18f2d0 svn.778\n0000000000000000000000000000000000000000 svn.778\nfaff3e0a69c548eef84546f92fce514b1ffeb21c svn.3\n0000000000000000000000000000000000000000 svn.3\nfb4fae98bac891bf929eb44fc1a4e37fb55d678d svn.1718\n0000000000000000000000000000000000000000 svn.1718\nfb56a1daf0b6f7583c2eba1aa3ec5c1155584101 svn.935\n0000000000000000000000000000000000000000 svn.935\nfb6ccc3cccc25c03bef4c242428687ca0e233cb9 svn.820\n0000000000000000000000000000000000000000 svn.820\nfb7dfa722237f83e639e50e6975b71095c168862 svn.640\n0000000000000000000000000000000000000000 svn.640\nfb8e171d63c993cad20c02658ceba5a17d04abe8 svn.1413\n0000000000000000000000000000000000000000 svn.1413\nfb9021a8268a9162a239877d4d4980265b40107e svn.180\n0000000000000000000000000000000000000000 svn.180\nfba75965849daca6545bf08ec091ce0f0727446b svn.1643\n0000000000000000000000000000000000000000 svn.1643\nfbb08d177e3541d70e36347191970557cc07b95e svn.1170\n0000000000000000000000000000000000000000 svn.1170\nfc0d258b65596f92647a4c906141ffd514ca6e25 svn.626\n0000000000000000000000000000000000000000 svn.626\nfc2c570351bd40022d7e58d6d03c3f9a6afca8ef svn.241\n0000000000000000000000000000000000000000 svn.241\nfc3bc9d6eb6bf3f7f12cab13dbb1ab20a0f479df svn.54\n0000000000000000000000000000000000000000 svn.54\nfc41bafd040579e1c799ee4cce9e2128fed3ee15 svn.1\n0000000000000000000000000000000000000000 svn.1\nfc45caaf89a5c4f916f8bf014f15a5cd48ae3a75 svn.963\n0000000000000000000000000000000000000000 svn.963\nfc549023f806baf5442569d6599de2f7d1e08b9c svn.995\n0000000000000000000000000000000000000000 svn.995\nfc60e042b59b43410254fef7c87c83dafa26f012 svn.1437\n0000000000000000000000000000000000000000 svn.1437\nfc645ca33d5515b3e453aafa4f84f1c0287b1459 svn.846\n0000000000000000000000000000000000000000 svn.846\nfd07495d021768bc0b40ce4e7033bb8f4c13d6c0 svn.1383\n0000000000000000000000000000000000000000 svn.1383\nfd0d1b1fa0e1d577a6fe62c1103f2446a989eb89 svn.1049\n0000000000000000000000000000000000000000 svn.1049\nfd0f837bb381b6819bd11233838282c3b941960a svn.1610\n0000000000000000000000000000000000000000 svn.1610\nfd12c95c101bb1ef71b4b64fffe25e4be8e087e3 svn.949\n0000000000000000000000000000000000000000 svn.949\nfd150370126ae7eeeee46ab7a672a6c9dc08ed7f svn.1417\n0000000000000000000000000000000000000000 svn.1417\nfd221c9cac3f6ba41f1be8aeaea2a67de0c2a68f svn.1480\n0000000000000000000000000000000000000000 svn.1480\nfd614c5118ed8cf0866a13cec5a008eeeb7ecad9 svn.1798\n0000000000000000000000000000000000000000 svn.1798\nfd6d75b921fd5581e7a13596c313afc242ecb7cc svn.137\n0000000000000000000000000000000000000000 svn.137\nfd910d5f2741be52641a7162a2ccbc6bfde91cf2 svn.964\n0000000000000000000000000000000000000000 svn.964\nfda445ffad25d980c9de21be18ca2f8d6a8c2185 svn.147\n0000000000000000000000000000000000000000 svn.147\nfdb1f6fe5de782bf8ecafd7edafda49587b3747d svn.1147\n0000000000000000000000000000000000000000 svn.1147\nfdb3c581672de44f67fb43351db58b6ab543b1d1 svn.483\n0000000000000000000000000000000000000000 svn.483\nfdf9b52fe83d221f74dfa77b86df18f52294a320 svn.1217\n0000000000000000000000000000000000000000 svn.1217\nfe02c21efeeeb64afa613396d73baf95c16cc636 svn.225\n0000000000000000000000000000000000000000 svn.225\nfe491964b2f1cefd84dc01e728f17d4dcbd24526 svn.1751\n0000000000000000000000000000000000000000 svn.1751\nfe54d44addf4db308f24d45765bf2333aa5f033a svn.458\n0000000000000000000000000000000000000000 svn.458\nfef75ecd30caa3b331dd89e5d47afda1c662d9a6 svn.642\n0000000000000000000000000000000000000000 svn.642\nfef950f2d9d99551e46a72006947ad6c14b7718d svn.439\n0000000000000000000000000000000000000000 svn.439\nff2bd18d3a67d9ea19832775d904f9e8b9b3bce7 svn.1721\n0000000000000000000000000000000000000000 svn.1721\nff9f8fc0a2748cbb63d2b0d7407dbd71b1935106 svn.1668\n0000000000000000000000000000000000000000 svn.1668\nffe0cbfa8a2e1981cf071b180872333043fde7db svn.993\n0000000000000000000000000000000000000000 svn.993\nfff7118f00e25731ccf37cba3082b8fcb73cf90e svn.371\n0000000000000000000000000000000000000000 svn.371\n6528c562fed6f994b8d1ecabaf375ddc4707dade mpi-opaque\n0000000000000000000000000000000000000000 mpi-opaque\nf15825659f5af3ce64aaad30062aff3603cbfb66 hop callback\n0000000000000000000000000000000000000000 hop callback\na71dffe4bc813fdadc506ccad9efb632e23dc843 yt-3.0a1\n954d1ffcbf04c3d1b394c2ea05324d903a9a07cf yt-3.0a2\nf4853999c2b5b852006d6628719c882cddf966df yt-3.0a3\n079e456c38a87676472a458210077e2be325dc85 last_gplv3\nca6e536c15a60070e6988fd472dc771a1897e170 yt-2.0\n882c41eed5dd4a3cdcbb567bcb79b833e46b1f42 yt-2.0.1\na2b3521b1590c25029ca0bc602ad6cb7ae7b8ba2 yt-2.1\n41bd8aacfbc81fa66d7a3f2cd2880f10c3e237a4 yt-2.2\n3836676ee6307f9caf5ccdb0f0dd373676a68535 yt-2.3\n076cec2c57d2e4b508babbfd661f5daa1e34ec80 yt-2.4\nbd285a9a8a643ebb7b47b543e9343da84cd294c5 yt-2.5\n34a5e6774ceb26896c9d767563951d185a720774 yt-2.5.1\n2197c101413723de13e1d0dea153b182342ff719 yt-2.5.2\n59aa6445b5f4a26ecb2449f913c7f2b5fee04bee yt-2.5.3\n4da03e5f00b68c3a52107ff75ce48b09360b30c2 yt-2.5.4\n21c0314cee16242b6685e42a74d16f7a993c9a88 yt-2.5.5\n053487f48672b8fd5c43af992e92bc2f2499f31f yt-2.6\nd43ff9d8e20f2d2b8f31f4189141d2521deb341b yt-2.6.1\nf1e22ef9f3a225f818c43262e6ce9644e05ffa21 yt-2.6.2\n816186f16396a16853810ac9ebcde5057d8d5b1a yt-2.6.3\nf327552a6ede406b82711fb800ebcd5fe692d1cb yt-3.0a4\n73a9f749157260c8949f05c07715305aafa06408 yt-3.0.0\n0cf350f11a551f5a5b4039a70e9ff6d98342d1da yt-3.0.1\n511887af4c995a78fe606e58ce8162c88380ecdc yt-3.0.2\nfd7cdc4836188a3badf81adb477bcc1b9632e485 yt-3.1.0\n28733726b2a751e774c8b7ae46121aa57fd1060f yt-3.2\n425ff6dc64a8eb92354d7e6091653a397c068167 yt-3.2.1\n425ff6dc64a8eb92354d7e6091653a397c068167 yt-3.2.1\n0000000000000000000000000000000000000000 yt-3.2.1\n0000000000000000000000000000000000000000 yt-3.2.1\nf7ca21c7b3fdf25d2ccab139849ae457597cfd5c yt-3.2.1\na7896583c06585be66de8404d76ad5bc3d2caa9a yt-3.2.2\n80aff0c49f40e04f00d7b39149c7fc297b8ed311 yt-3.2.3\n80aff0c49f40e04f00d7b39149c7fc297b8ed311 yt-3.2.3\n0000000000000000000000000000000000000000 yt-3.2.3\n0000000000000000000000000000000000000000 yt-3.2.3\n83d2c1e9313e7d83eb5b96888451ff2646fd8ff3 yt-3.2.3\n7edbfde96c3d55b227194394f46c0b2e6ed2b961 yt-3.3.0\n9bc3d0e9b750c923d44d73c447df64fc431f5838 yt-3.3.1\n0d0af4016c88476e134c46ce6c25d9ef88c84614 yt-3.3.2\n732bbecb894e4e9c8d2d8d0a5f10810acab72d85 yt-3.3.2\n732bbecb894e4e9c8d2d8d0a5f10810acab72d85 yt-3.3.2\n3bdb0ca67dcaa5786d433a148e638687b4be7cdd yt-3.3.3\nca2f21bad5329d35912c4efb5d8d784339b32f3b yt-3.3.4\n7d2618d9ae601f6923415053cddd855c7bdc05e4 yt-3.3.5\nbf36e108e911f0c415b6cc23691723c2d31ec69f yt-3.3.5\n"
  },
  {
    "path": ".mailmap",
    "content": "Stephen Skory <s@skory.us> Stephen Skory <stephenskory@yahoo.com>\nStephen Skory <s@skory.us> \"Stephen Skory stephenskory@yahoo.com\" <\"Stephen Skory stephenskory@yahoo.com\">\nYuan Li <yuan@astro.columbia.edu> Yuan Li <bear0980@gmail.com>\nCameron Hummels <chummels@gmail.com> Cameron Hummels <chummels@astro.columbia.edu>\nCameron Hummels <chummels@gmail.com> chummels <chummels@gmail.com>\nJohn Wise <jwise@physics.gatech.edu> John Wise <jwise@astro.princeton.edu>\nJohn Wise <jwise@physics.gatech.edu> John Wise <jwise77@users.noreply.github.com>\nSam Skillman <samskillman@gmail.com> Sam Skillman <sam.skillman@gmail.com>\nSam Skillman <samskillman@gmail.com> samskillman <sam.skillman@gmail.com>\nCasey Stark <caseywstark@gmail.com> Casey Stark <casey@thestarkeffect.com>\nChristian Karch <chiffre@posteo.de> chiffre <none@none>\nChristian Karch <chiffre@posteo.de> Christian Karch <none@none>\nAndrew Myers <atmyers2@gmail.com> Andrew Myers <atmyers@berkeley.edu>\nAndrew Myers <atmyers2@gmail.com> atmyers <none@none>\nDouglas Rudd <drudd@uchicago.edu> drudd <none@none>\nAndrew Wetzel <arwetzel@gmail.com> awetzel <none@none>\nAndrew Wetzel <arwetzel@gmail.com> Andrew Wetzel <andrew.wetzel@yale.edu>\nDavid Collins <dcollins4096@gmail.com> David Collins <dcollins@physics.ucsd.edu>\nDavid Collins <dcollins4096@gmail.com> dcollins4096 <none@none>\nDavid Collins <dcollins4096@gmail.com> David Collins (dcollins4096@gmail.com) <David Collins (dcollins4096@gmail.com)>\nTom Abel <tabel@slac.stanford.edu> tabel <none@none>\nTom Abel <tabel@slac.stanford.edu> Tom Abel <tabel@stanford.edu>\nKaylea Nelson <kaylea.nelson@yale.edu> sername=kayleanelson <none@none>\nKaylea Nelson <kaylea.nelson@yale.edu> kayleanelson <none@none\nKaylea Nelson <kaylea.nelson@yale.edu> kayleanelson <none@none>\nStephanie Tonnesen <stonnes@gmail.com> stonnes <stonnes@gmail.com>\nJohn Forbes <jcforbes@ucsc.edu> John Forbes <jforbes@ucolick.org>\nElliott Biondo <biondo@wisc.edu> Elliott Biondo <Biondo@wisc.edu>\nSam Geen <samgeen@gmail.com> Sam Geen <samgeen@googlemail.com>\nAlex Bogert <fbogert@ucsc.edu> fbogert <none@none>\nBrian O'Shea <oshea@msu.edu> bwoshea <none@none>\nJi-hoon Kim <me@jihoonkim.org> Ji-hoon Kim <mornkr@slac.stanford.edu>\nKirk Barrow <kssbarrow@gatech.edu> Kirk Barrow <kssbarrow@gmail.com>\nKirk Barrow <kssbarrow@gatech.edu> Kirk Barrow <kassbarrow@gmail.com>\nKirk Barrow <kssbarrow@gatech.edu> kbarrow <none@none>\nKirk Barrow <kssbarrow@gatech.edu> kbarrow <kassbarrow@gmail.com>\nAntoine Strugarek <antoine.strugarek@cea.fr> Antoine Strugarek <strugarek@astro.umontreal.ca>\nAntoine Strugarek <antoine.strugarek@cea.fr> astrugarek <strugarek@astro.umontreal.ca>\nAntoine Strugarek <antoine.strugarek@cea.fr> Antoine Strugarek <antoine.strugarek@cea.fr>\nAnna Rosen <rosen@ucolick.org> Anna Rosen <alrosen@ucsc.edu>\nJohn ZuHone <jzuhone@gmail.com> jzuhone <none@none>\nJohn ZuHone <jzuhone@gmail.com> jzuhone <jzuhone@gmail.com>\nJohn ZuHone <jzuhone@gmail.com> John Zuhone <jzuhone@mfe1.nas.nasa.gov>\nJohn ZuHone <jzuhone@gmail.com> John Zuhone <jzuhone@ldan6.nas.nasa.gov>\nKenz Arraki <karraki@gmail.com> Kenza Arraki <karraki@nmsu.edu>\nKenz Arraki <karraki@gmail.com> Kenza Arraki <karraki@gmail.com>\nAllyson Julian <astrohckr@gmail.com> Allyson Julian <hckr@eml.cc>\nAllyson Julian <astrohckr@gmail.com> Allyson Julian <julian3@illinois.edu>\nAllyson Julian <astrohckr@gmail.com> Allyson Julian <aj@hckr.eml.cc>\nAllyson Julian <astrohckr@gmail.com> AJ <astrohckr@gmail.com>\nAllyson Julian <astrohckr@gmail.com> Al Jul <hckr@eml.cc>\nAllyson Julian <astrohckr@gmail.com> AJ <aj@hckr.eml.cc>\nAllyson Julian <astrohckr@gmail.com> AJ <hckr@eml.cc>\nBrian Crosby <bcrosby.bd@gmail.com> bcrosby <bcrosby.bd@gmail.com>\nBen Thompson <bthompson2090@gmail.com> Ben Thompson <none@none>\nBen Thompson <bthompson2090@gmail.com> cosmosquark <none@none>\nBen Thompson <bthompson2090@gmail.com> Benjamin Thompson <bthompson2090@gmail.com>\nChris Malone <chris.m.malone@gmail.com> Chris Malone <chris.m.malone@lanl.gov>\nChris Malone <chris.m.malone@gmail.com> ChrisMalone <chris.m.malone@gmail.com>\nChris Malone <chris.m.malone@gmail.com> ChrisMalone <chris.m.malone@lanl.gov>\nJill Naiman <jnaiman@illinois.edu> Jill Naiman <jnaiman@cfa.harvard.edu>\nJill Naiman <jnaiman@illinois.edu> Jill Naiman <jnaiman@ucolick.org>\nJill Naiman <jnaiman@illinois.edu> jnaiman <none@none>\nJill Naiman <jnaiman@illinois.edu> jnaiman <jnaiman@ucolick.org>\nMiguel de Val-Borro <miguel.deval@gmail.com> Miguel de Val-Borro <miguel@archlinux.net>\nStuary Levy <salevy@illinois.edu> Stuart Levy <slevy@ncsa.illinois.edu>\nBen Keller <malzraa@gmail.com> Ben Keller <kellerbw@mcmaster.ca>\nDaniel Fenn <df11c@my.fsu.edu> dfenn <none@none>\nMeagan Lang <langmm.astro@gmail.com> langmm <none@none>\nJoseph Tomlinson <jmtomlinson95@gmail.com> jmt354 <none@none>\nDesika Narayanan <dnarayan@haverford.edu> desika <none@none>\nDesika Narayanan <dnarayan@haverford.edu> dnarayanan <desika.narayanan@gmail.com>\nDesika Narayanan <dnarayan@haverford.edu> dnarayanan <dnarayanan@users.noreply.github.com>\nNathan Goldbaum <ngoldbau@illinois.edu> Nathan Goldbaum <goldbaum@ucolick.org>\nNathan Goldbaum <ngoldbau@illinois.edu> Nathan Goldbaum <ngoldbau@ucsc.edu>\nNathan Goldbaum <ngoldbau@illinois.edu> Nathan Goldbaum <nathan12343@gmail.com>\nFabian Koller <anokfireball@posteo.de> NTAuthority@honeypot.fritz.box <NTAuthority@honeypot.fritz.box>\nFabian Koller <anokfireball@posteo.de> NTAuthority@guest053.fz-rossendorf.de <NTAuthority@guest053.fz-rossendorf.de>\nFabian Koller <anokfireball@posteo.de> NTAuthority@guest692.fz-rossendorf.de <NTAuthority@guest692.fz-rossendorf.de>\nFabian Koller <anokfireball@posteo.de> NTAuthority@guest692.fz-rossendorf.de <NTAuthority@guest692.fz-rossendorf.de>\nFabian Koller <anokfireball@posteo.de> Fabian Koller <none@none>\nFabian Koller <anokfireball@posteo.de> Fabian Koller <anokfireball@poseto.de>\nFabian Koller <anokfireball@posteo.de> C0nsultant <anokfireball@posteo.de>\nRafael Ruggiero <ruggiero3n1@gmail.com> Rafael Ruggiero <rafael.ruggiero@usp.br>\nRafael Ruggiero <ruggiero3n1@gmail.com> Rafael Ruggiero <none@none>\nJohn Regan <john.regan@helsinki.fi> John Regan <john.a.regan@durham.ac.uk>\nJohn Regan <john.regan@helsinki.fi> John Regan <john.regan@mu.ie>\nJohn Regan <john.regan@helsinki.fi> John Regan <32193880+fearmayo@users.noreply.github.com>\nJohn Regan <john.regan@helsinki.fi> John Regan <john.regan@dcu.ie>\nDave Grote <grote1@llnl.gov> David Grote <grote1@llnl.gov>\nGabriel Altay <gabriel.altay@gmail.com> galtay <gabriel.altay@gmail.com>\nMark Richardson <Mark.Richardson.Work@gmail.com> Mark Richardson <Mark.L.Richardson@asu.edu>\nJared Coughlin <jcoughl2@nd.edu> Jared <none@none>\nJared Coughlin <jcoughl2@nd.edu> Jared <jcoughl3@illinois.edu>\nJared Coughlin <jcoughl2@nd.edu> Jared William Coughlin <jcoughl2@crcfe02.crc.nd.edu>\nJared Coughlin <jcoughl2@nd.edu> Jared Coughlin <jcoughl3@illinois.edu>\nJared Coughlin <jcoughl2@nd.edu> Jared Coughlin <30010597+jcoughlin11@users.noreply.github.com>\nCorentin Cadiou <corentin.cadiou@iap.fr> cphyc <contact@cphyc.me>\nCorentin Cadiou <corentin.cadiou@iap.fr> ccc@pingouin.local <ccc@pingouin.local>\nCorentin Cadiou <corentin.cadiou@iap.fr> ccc@pingouin-2.local <ccc@pingouin-2.local>\nCorentin Cadiou <corentin.cadiou@iap.fr> Corentin Cadiou <contact@cphyc.me>\nconvert-repo <none@none> None <none@none>\nPatrick Shriwise <shriwise@wisc.edu> pshriwise <shriwise@wisc.edu>\nPatrick Shriwise <shriwise@wisc.edu> Patrick Shriwise <pshriwise@gmail.com>\nMichael Zingale <michael.zingale@stonybrook.edu> Michael  Zingale <michael.zingale@stonybrook.edu>\nSam Leitner <sam.leitner@gmail.com> Sam  Leitner <sam.leitner@gmail.com>\nGeoffrey So <gsiisg@gmail.com> gsiisg <gsiisg@gmail.com>\nMatthew Turk <matthewturk@gmail.com> Matt Turk <matthewturk@gmail.com>\nMatthew Turk <matthewturk@gmail.com> Matthew Turk <mjturk@mste-39.mste.illinois.edu>\nStephanie Ho <stephaniehkho@gmail.com> Stephanie Ho <stephaniehho@tsubasa.physics.ucsb.edu>\nStephanie Ho <stephaniehkho@gmail.com> stephaniehho <20467246+stephaniehho@users.noreply.github.com>\nStephanie Ho <stephaniehkho@gmail.com> Stephanie Ho <stephaniehho@169-231-104-205.wireless.ucsb.edu>\nSalvatore Cielo <cielo@iap.fr> sacielo <sacielo@gmail.com>\nNicholas Earl <nchlsearl@gmail.com> nmearl <nchlsearl@gmail.com>\nMarianne Corvellec <marianne.corvellec@ens-lyon.org> Marianne Corvellec <marianne.corvellec@crim.ca>\nHugo Pfister <pfister@loginhz02.iap.fr> Hugo Pfister <pfister@loginhz01.iap.fr>\nAndré-Patrick Bubel <code@andre-bubel.de> Moredread <code@andre-bubel.de>\nAbhishek Singh <abhisheksing@umass.edu> git-abhishek <36498066+git-abhishek@users.noreply.github.com>\nAbhishek Singh <abhisheksing@umass.edu> Abhishek Singh <36498066+git-abhishek@users.noreply.github.com>\nAshley Kelly <a.j.kelly@durham.ac.uk> ash <Ashley Kelly>\nAshley Kelly <a.j.kelly@durham.ac.uk> Ashley Kelly <ashjkelly0@gmail.co.uk>\nMax Katz <maximilian.katz@stonybrook.edu> Max Katz <maxpkatz@gmail.com>\nSuoqing Ji <jisuoqing@gmail.com> Suoqing JI <jisuoqing@gmail.com>\nBili Dong <qobilidop@gmail.com> qobilidop <qobilidop@gmail.com>\nDonald E Willcox <eugene.willcox@gmail.com> dwillcox <eugene.willcox@gmail.com>\nDonald E Willcox <eugene.willcox@gmail.com> Donald E. Willcox <dewillcox@lbl.gov>\nYingchao Lu <yingchao.lu@gmail.com> yingchaolu <yingchao.lu@gmail.com>\nAndrew Myers <atmyers2@gmail.com> atmyers <atmyers2@gmail.com>\nRicarda Beckmann <ricarda.beckmann@astro.ox.ac.uk> Ricarda Beckmann <Ricarda.Beckmann@astro.ox.ac.uk>\nRicarda Beckmann <beckmann@iap.fr> Ricarda Beckmann <ricarda.beckmann@astro.ox.ac.uk>\nRicarda Beckmann <beckmann@iap.fr> RicardaBeckmann <beckmann@iap.fr>\nRicarda Beckmann <beckmann@iap.fr> Ricarda Beckmann <ricarda.beckmann@astro.ox.ac.uk>\nRicarda Beckmann <beckmann@iap.fr> RicardaBeckmann <ricarda.beckmann@astro.ox.ac.uk>\nCorentin Cadiou <corentin.cadiou@iap.fr> Corentin Cadiou <corentin.cadiou@cphyc.me>\nChris Moody <juxtaposicion@gmail.com> Christopher Moody <juxtaposicion@gmail.com>\nChris Moody <juxtaposicion@gmail.com> Chris Moody <cemoody@ucsc.edu>\nChris Moody <juxtaposicion@gmail.com> Christopher Moody <cemoody@ucsc.edu>\nChris Moody <juxtaposicion@gmail.com> Christopher Erick Moody <cemoody@ucsc.edu>\nCasey W. Stark <caseywstark@gmail.com> Casey W. Stark <casey@thestarkeffect.com>\nBW Keller <kellerbw@mcmaster.ca> Ben Keller <malzraa@gmail.com>\nBenjamin Thompson <bthompson2090@gmail.com> Ben Thompson <bthompson2090@gmail.com>\nAlex Lindsay <alexander.lindsay@inl.gov> Alex Lindsay <al007@illinois.edu>\nAlex Lindsay <alexander.lindsay@inl.gov> Alex Lindsay <alexlindsay239@gmail.com>\nYi-Hao Chen (ychen@astro.wisc.edu) Yi-Hao Chen (yihaochentw@gmail.com)\nKacper Kowalik <xarthisius.kk@gmail.com> Kacper Kowalik (Xarthisius) <xarthisius.kk@gmail.com>\nJohn ZuHone <jzuhone@gmail.com> John Zuhone <jzuhone@lfe8.nas.nasa.gov>\nJohn ZuHone <jzuhone@gmail.com> John Zuhone <jzuhone@lfe7.nas.nasa.gov>\nJohn ZuHone <jzuhone@gmail.com> John Zuhone <jzuhone@lfe4.nas.nasa.gov>\nJohn ZuHone <jzuhone@gmail.com> John Zuhone <jzuhone@lfe3.nas.nasa.gov>\nJohn ZuHone <jzuhone@gmail.com> John Zuhone <jzuhone@lfe2.nas.nasa.gov>\nClayton Strawn <cjstrawn@ucsc.edu> Clayton Strawn <33767568+claytonstrawn@users.noreply.github.com>\nHilary Egan <hilary.egan@colorado.edu> Hilary Egan <hilaryye@gmail.com>\nYi-Hao Chen <ychen@astro.wisc.edu> Yi-Hao Chen <yihaochentw@gmail.com>\nPrateek Gupta <prateek91gidolia@gmail.com> Prateekgidolia <prateek91gidolia@gmail.com>\nMax Gronke <maxbg@astro.uio.no> maxbeegee <maxbg@astro.uio.no>\nAshley Kelly <a.j.kelly@durham.ac.uk> ashkelly <a.j.kelly@durham.ac.uk>\nSean Larkin <sflarkin@ucsc.edu> sflarkin <41403179+sflarkin@users.noreply.github.com>\nChris Havlin <chris.havlin@gmail.com> chrishavlin <chris.havlin@gmail.com>\nMichael Ryan <mtryan83@gmail.com> mtryan83 <mtrdraco@hotmail.com>\nRyan Farber <rjfarber@umich.edu> Ryan Farber <ryanjsfx@MB-145.local>\nRyan Farber <rjfarber@umich.edu> Ryan Farber <ryanjsfx@mpa-garching.mpg.de>\nMax Gronke <max.groenke@gmail.com> Max Gronke <maxbg@astro.uio.no>\nMartin Alvarez Sergio <martin.alvarez.sergio@gmail.com> Sergio <martin.alvarez.sergio@gmail.com>\nMartin Alvarez Sergio <martin.alvarez.sergio@gmail.com> MartinAlvarezSergio <51377626+MartinAlvarezSergio@users.noreply.github.com>\nClaire Kopenhafer <clairekope@gmail.com> Claire Kopenhafer <kopenhaf@msu.edu>\nClément Robert <cr52@protonmail.com> Clément Robert <clement.robert@oca.eu>\nCorentin Cadiou <corentin.cadiou@iap.fr> Corentin Cadiou <c.cadiou@ucl.ac.uk>\nNiels Claes <niels.claes@kuleuven.be> Niels Claes <niels.claes@live.be>\nRevathiJambunathan <revanathan@gmail.com> Revathi Jambunathan <revanathan@pop-os.localdomain>\nMatthew Abruzzo <matthewabruzzo@gmail.com> mabruzzo <matthewabruzzo@gmail.com>\nAustin Gilbert <augilbert4@gmail.com> Austin Gilbert <agilbert39@gatech.edu>\nJosh Borrow <josh@joshborrow.com> Josh Borrow <joshua.borrow@durham.ac.uk>\nRick Wagner <rick@ucsd.edu> Rick Wagner <rwagner@physics.ucsd.edu>\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "# pre-commit 1.1.0 is required for `exclude`\n# however `minimum_pre_commit_version` itself requires 1.15.0\nminimum_pre_commit_version: \"1.15.0\"\n\nexclude: \"^(\\\nyt/extern\\\n|yt/frontends/stream/sample_data\\\n|yt/units\\\n|scripts\\\n|benchmark\\\n|setupext.py\\\n|yt/visualization/_colormap_data.py\\\n)\"\n\nci:\n    autofix_prs: false\n    autoupdate_schedule: monthly\n\nrepos:\n-   repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v6.0.0\n    hooks:\n    - id: trailing-whitespace\n    - id: end-of-file-fixer\n    - id: no-commit-to-branch\n    - id: check-shebang-scripts-are-executable\n    - id: check-executables-have-shebangs\n    - id: check-yaml\n\n- repo: https://github.com/zizmorcore/zizmor-pre-commit\n  rev: v1.23.1\n  hooks:\n    - id: zizmor\n\n# TODO: replace this with ruff when it supports embedded python blocks\n# see https://github.com/astral-sh/ruff/issues/8237\n-   repo: https://github.com/adamchainz/blacken-docs\n    rev: 1.20.0\n    hooks:\n    -   id: blacken-docs\n        additional_dependencies: [black==24.3.0]\n\n- repo: https://github.com/astral-sh/ruff-pre-commit\n  rev: v0.15.9\n  hooks:\n  - id: ruff-format\n  - id: ruff-check\n    args: [\n      --fix,\n      --show-fixes,\n    ]\n\n-   repo: https://github.com/pre-commit/pygrep-hooks\n    rev: v1.10.0\n    hooks:\n    -   id: rst-backticks\n\n- repo: https://github.com/MarcoGorelli/cython-lint\n  rev: v0.19.0\n  hooks:\n  - id: cython-lint\n    args: [--no-pycodestyle]\n"
  },
  {
    "path": ".tours/particle-indexing.tour",
    "content": "{\n  \"$schema\": \"https://aka.ms/codetour-schema\",\n  \"title\": \"particle indexing\",\n  \"steps\": [\n    {\n      \"title\": \"Introduction\",\n      \"description\": \"We're going to walk through how particle indexing works in yt 4.0.\\n\\nImagine you've got a bunch of LEGO blocks that you know build up a model.  (For fun, let's pretend it's the Arendelle castle.)  They're all separated into little bags, and you don't exactly know which pieces are in which bags, but maybe you vaguely know that there's some organizational scheme to them.\\n\\nWhen you build your castle, you *could* read the instructions step by step and inspect every single piece in every single bag until you find the one you're looking for.  But that wouldn't be terribly efficient!  Wouldn't it be easier if you had some way of saying, \\\"I know that this piece is in one of *these* bags, so I'll only look in those bags until I find it.\\\"\\n\\nThat's what we do with indexing in yt, and why particle indexing is particularly tricky -- because when we want to select data, we want to minimize the amount of time we spend searching, checking, and discarding the parts of the dataset that we don't need.\\n\\nSo how do we do this in yt?\"\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"The `ParticleIndex` class is used by all of the frontends that have particles as their principle component.  In `yt`, there are basically four main management classes for a given dataset -- the `Dataset` object itself, which contains metadata and pointers to the other managers, along with a `FieldInfoContainer` that describes the different \\\"fields\\\" that `yt` knows how to generate, an `IOHandler` that manages access to the actual data, and -- most relevant for our purposes today! -- the `Index` object.\\n\\nThere are a few different types, but the one we're going to look at today is the `ParticleIndex` class.\",\n      \"line\": 18,\n      \"selection\": {\n        \"start\": {\n          \"line\": 18,\n          \"character\": 1\n        },\n        \"end\": {\n          \"line\": 18,\n          \"character\": 28\n        }\n      }\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"The `_initialize_index` method is called only once on each `Index` object.  It's where we set up the in-memory data structures that tell us how to map spatial regions in the domain of the dataset to places \\\"on disk\\\" (but not necessarily on an actual disk!  They could be in dictionaries, in remote URLs, etc) that the data can be found.  This is where the bulk of the operations occur, and it can be expensive for some situations, so we try to minimize the incurred cost of time.\",\n      \"line\": 110\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"With particle datasets, if we can't guess the left and right edges, we do a pass over the data to figure them out.\\n\\nThis isn't amazing!  It's definitely not my personal favorite.  But, it is what we do.  There are ways out there of avoiding this -- in fact, it would be completely feasible to just set the domain to be all the way from the minimum double precision representation to the maximum double precision representation, and then only occupy a small portion in a hierarchical mapping.  But we don't do this now.\",\n      \"line\": 121\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"When we don't have a lot of data, for instance if the `data_files` attribute only contains one thing, we don't do any indexing.  We'll have to read the whole thing anyway, so, whatever, right?\\n\\nOne thing that's worth keeping in mind here is that `data_files` are not always just the \\\"files\\\" that live on disk.  Some data formats, for instance, use just a single file, but include a huge number of particles in it.  We sub-chunk these so they appear as several \\\"virtual\\\" data files.\\n\\nThis helps us keep our indexing efficient, since there's no point to doing a potentially-expensive indexing operation on a small dataset, and we also don't want to give up all of our ability to set the size we read from disk.\",\n      \"line\": 145\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"And here's where we start actually setting up our index.\\n\\nBecause the indexing operation can be expensive, we build in ways of caching.  That way, the *second* time you load a dataset, it won't have to conduct the indexing operation unless it really needs to -- it'll just use what it came up with the first time.\",\n      \"line\": 190\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"This is where we build our new index.  Let's take a look at what that looks like!\\n\\nWe have two steps to this process.  The first is to build a \\\"coarse\\\" index -- this is a reasonably low-resolution index to let us know, \\\"Hey, this bit might be relevant!\\\") and the second is a much more \\\"refined\\\" index for when we need to do very detail-oriented subselection.\",\n      \"line\": 200\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"To generate a coarse index, we loop over all of our files, and look at the particles.  (This sure does sound expensive, right?  Good thing we're going to cache the results!)\\n\\nEach `IOHandler` for the particle objects has to provide a `_yield_coordinates` method.  This method just has to yield a bunch of tuples of the particle types and their positions.\",\n      \"line\": 214\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"We won't be diving in to this routine, but let's talk about what it does.\\n\\nThe `regions` attribute on a `ParticleIndex` object is where we set \\\"on\\\" and \\\"off\\\" bits that correspond between spatial locations and `data_file` objects.\\n\\nIf we think about our domain as a big 3D volume, we could divide it up into a grid of positions.  Each of these positions -- `i, j, k` -- can be turned into a single number.  To do this we use a morton index, a way of mapping regions of physical space to unique binary representations of each `i, j, k`.\\n\\nThe purpose of this loop is to turn each `data_file` into a set of \\\"on\\\" and \\\"off\\\" marks, where \\\"off\\\" indicates that no particles exist, and \\\"on\\\" indicates they do.\\n\\nSo, going *in*, each `data_file` is given an array of all zeros, and the array has `I*J*K` *bits*, where `I` is the number of subdivisions in x, `J` is the number in y and `K` is the number in z.  The way we set it up, these are always the same, and they are always equal to `2**index_order1`.  So if your `index_order1` is 3, this would be `I==J==K==8`, and you'd have a total of `8*8*8` bits in the array, corresponding to a total size of 64 bytes.\\n\\nOne important thing to keep in mind is that we save on memory by only storing the *bits*, not the counts!  That's because this is just an indexing system meant to tell us where to look, so we want to keep it as lightweight as possible.\",\n      \"line\": 224\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"This line, which happens *outside* the loop over particle positions, compresses the bitarrays.  That way we keep our memory usage to a minimum.\",\n      \"line\": 225\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"This line is crucial for the next step of computing the refined indexes.  It conducts a set of logical operations to see which bits in the array are set *more than once* -- which means that there's more than one place you'll have to look if you're looking for particles in that region.  It's in those places that we make a second, more refined index.\",\n      \"line\": 227\n    },\n    {\n      \"file\": \"yt/geometry/particle_geometry_handler.py\",\n      \"description\": \"This is the tricky part, where we conduct a second bitmapping operation.\\n\\nHere, we look at those places where collisions in the coarse index have occurred.  We want to do our best to disambiguate the regions that different data files encompass, so in all of those regions, we insert a new *subdivided* region.  So for instance, if the region in `i,j,k` of the coarse index is touched by a couple files, we insert in `i,j,k` a whole new index, this time based on `index_order2`.  And, we index that.  Because we're using compressed indexes, this usually does not take up that much memory, but it can get unwieldy in those cases where you have particles with big smoothing lengths that need to be taken into account.\\n\\nThis loop requires a bit more of a dance, because for each collision in the coarse index, we have to construct a refined index.  So the memory usage is a bit higher here, because we can't compress the arrays until we're totally done with them.\\n\\nBut, at the end of this, what we have is a set of bitmaps -- one for each data file -- that tell us where the data file exerts its influence.  And then, wherever more than one data file exerts its influence, we have *another* set of bitmaps for each of *those* data files that tell us which sub-regions are impacted.\\n\\nThis then gets used whenever we want to read data, to tell us which data files we have to look at.  For sub-selecting from a really big set of particles, we can save an awful lot of time and IO!\",\n      \"line\": 251\n    }\n  ],\n  \"ref\": \"511c82da16e8b86e09a45ac9bb108bc0c47dd87c\"\n}\n"
  },
  {
    "path": "CITATION",
    "content": "To cite yt in publications, please use:\n\nTurk, M. J., Smith, B. D., Oishi, J. S., et al. 2011, ApJS, 192, 9\n\nIn the body of the text, please add a footnote to the yt webpage:\n\nhttp://yt-project.org/\n\nFor LaTex and BibTex users:\n\n\\bibitem[Turk et al.(2011)]{2011ApJS..192....9T} Turk, M.~J., Smith, B.~D.,\nOishi, J.~S., et al.\\ 2011, The Astrophysical Journal Supplement Series, 192, 9\n\n@ARTICLE{2011ApJS..192....9T,\n   author = {{Turk}, M.~J. and {Smith}, B.~D. and {Oishi}, J.~S. and {Skory}, S. and\n{Skillman}, S.~W. and {Abel}, T. and {Norman}, M.~L.},\n    title = \"{yt: A Multi-code Analysis Toolkit for Astrophysical Simulation Data}\",\n  journal = {The Astrophysical Journal Supplement Series},\narchivePrefix = \"arXiv\",\n   eprint = {1011.3514},\n primaryClass = \"astro-ph.IM\",\n keywords = {cosmology: theory, methods: data analysis, methods: numerical},\n     year = 2011,\n    month = jan,\n   volume = 192,\n      eid = {9},\n    pages = {9},\n      doi = {10.1088/0067-0049/192/1/9},\n   adsurl = {http://adsabs.harvard.edu/abs/2011ApJS..192....9T},\n  adsnote = {Provided by the SAO/NASA Astrophysics Data System}\n}\n\nUsing yt can also utilize other functionality.  If you utilize ORIGAMI, we ask\nthat you please cite the ORIGAMI paper:\n\n@ARTICLE{2012ApJ...754..126F,\n   author = {{Falck}, B.~L. and {Neyrinck}, M.~C. and {Szalay}, A.~S.},\n    title = \"{ORIGAMI: Delineating Halos Using Phase-space Folds}\",\n  journal = {\\apj},\narchivePrefix = \"arXiv\",\n   eprint = {1201.2353},\n primaryClass = \"astro-ph.CO\",\n keywords = {dark matter, galaxies: halos, large-scale structure of universe, methods: numerical},\n     year = 2012,\n    month = aug,\n   volume = 754,\n      eid = {126},\n    pages = {126},\n      doi = {10.1088/0004-637X/754/2/126},\n   adsurl = {http://adsabs.harvard.edu/abs/2012ApJ...754..126F},\n  adsnote = {Provided by the SAO/NASA Astrophysics Data System}\n}\n\nThe main homepage for ORIGAMI can be found here:\n\nhttp://icg.port.ac.uk/~falckb/origami.html\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": ".. This document is rendered in HTML with cross-reference links filled in at\n   https://yt-project.org/doc/developing/developing.html\n\n.. _getting-involved:\n\nGetting Involved\n================\n\nThere are *lots* of ways to get involved with yt, as a community and as a\ntechnical system -- not all of them just contributing code, but also\nparticipating in the community, helping us with designing the websites, adding\ndocumentation, and sharing your scripts with others.\n\nCoding is only one way to be involved!\n\nCommunication Channels\n----------------------\n\nThere are three main communication channels for yt:\n\n* Many yt developers participate in the yt Slack community. Slack is a free\n  chat service that many teams use to organize their work. You can get an\n  invite to yt's Slack organization by clicking the \"Join us @ Slack\" button\n  on this page: https://yt-project.org/community.html\n* `yt-users <https://mail.python.org/archives/list/yt-users@python.org/>`_\n  is a relatively high-traffic mailing list where people are encouraged to ask\n  questions about the code, figure things out and so on.\n* `yt-dev <https://mail.python.org/archives/list/yt-dev@python.org/>`_ is\n  a much lower-traffic mailing list designed to focus on discussions of\n  improvements to the code, ideas about planning, development issues, and so\n  on.\n\nThe easiest way to get involved with yt is to read the mailing lists, hang out\nin IRC or slack chat, and participate.  If someone asks a question you know the\nanswer to (or have your own question about!) write back and answer it.\n\nIf you have an idea about something, suggest it!  We not only welcome\nparticipation, we encourage it.\n\nDocumentation\n-------------\n\nThe yt documentation is constantly being updated, and it is a task we would very\nmuch appreciate assistance with.  Whether that is adding a section, updating an\noutdated section, contributing typo or grammatical fixes, adding a FAQ, or\nincreasing coverage of functionality, it would be very helpful if you wanted to\nhelp out.\n\nThe easiest way to help out is to fork the main yt repository and make changes\nin it to contribute back to the ``yt-project``. A fork is a copy\nof a repository; in this case the fork will live in the space under your\nusername on github, rather than the ``yt-project``. If you have never made a\nfork of a repository on github, or are unfamiliar with this process, here is a\nshort article about how to do so:\nhttps://help.github.com/en/github/getting-started-with-github/fork-a-repo .\nThe documentation for\n``yt`` lives in the ``doc`` directory in the root of the yt git\nrepository. To make a contribution to the yt documentation you will\nmake your changes in your own fork of ``yt``.  When you are done,\nissue a pull request through the website for your new fork, and we can comment\nback and forth and eventually accept your changes. See :ref:`sharing-changes` for\nmore information about contributing your changes to yt on GitHub.\n\nGallery Images and Videos\n-------------------------\n\nIf you have an image or video you'd like to display in the image or video\ngalleries, getting it included is easy!  You can either fork the `yt homepage\nrepository <https://github.com/yt-project/website>`_ and add it there, or\nemail it to us and we'll add it to the `Gallery\n<https://yt-project.org/gallery.html>`_.\n\nWe're eager to show off the images and movies you make with yt, so please feel\nfree to drop `us <https://mail.python.org/archives/list/yt-dev@python.org/>`_\na line and let us know if you've got something great!\n\nTechnical Contributions\n-----------------------\n\nContributing code is another excellent way to participate -- whether it's\nbug fixes, new features, analysis modules, or a new code frontend.  See\n:ref:`creating_frontend` for more details.\n\nThe process is pretty simple: fork on GitHub, make changes, issue a pull\nrequest.  We can then go back and forth with comments in the pull request, but\nusually we end up accepting.\n\nFor more information, see :ref:`contributing-code`, where we spell out how to\nget up and running with a development environment, how to commit, and how to\nuse GitHub. When you're ready to share your changes with the community, refer to\n:ref:`sharing-changes` to see how to contribute them back upstream.\n\nOnline Presence\n---------------\n\nSome of these fall under the other items, but if you'd like to help out with\nthe website or any of the other ways yt is presented online, please feel free!\nAlmost everything is kept in git repositories on GitHub, and it is very easy\nto fork and contribute back changes.\n\nPlease feel free to dig in and contribute changes.\n\nWord of Mouth\n-------------\n\nIf you're using yt and it has increased your productivity, please feel\nencouraged to share that information.  Cite our `paper\n<https://ui.adsabs.harvard.edu/abs/2011ApJS..192....9T>`_, tell your colleagues,\nand just spread word of mouth.  By telling people about your successes, you'll\nhelp bring more eyes and hands to the table -- in this manner, by increasing\nparticipation, collaboration, and simply spreading the limits of what the code\nis asked to do, we hope to help scale the utility and capability of yt with the\ncommunity size.\n\nFeel free to `blog <https://blog.yt-project.org/>`_ about, `tweet\n<https://twitter.com/yt_astro>`_ about and talk about what you are up to!\n\nLong-Term Projects\n------------------\n\nThere are some out-there ideas that have been bandied about for the\nfuture directions of yt -- stuff like fun new types of visualization, remapping\nof coordinates, new ways of accessing data, and even new APIs to make life easier.\n\nyt is an ambitious project.  Let's be ambitious together!\n\nyt Community Code of Conduct\n----------------------------\n\nThe community of participants in open source\nScientific projects is made up of members from around the\nglobe with a diverse set of skills, personalities, and\nexperiences. It is through these differences that our\ncommunity experiences success and continued growth. We\nexpect everyone in our community to follow these guidelines\nwhen interacting with others both inside and outside of our\ncommunity. Our goal is to keep ours a positive, inclusive,\nsuccessful, and growing community.\n\nAs members of the community,\n\n- We pledge to treat all people with respect and\n  provide a harassment- and bullying-free environment,\n  regardless of sex, sexual orientation and/or gender\n  identity, disability, physical appearance, body size,\n  race, nationality, ethnicity, and religion. In\n  particular, sexual language and imagery, sexist,\n  racist, or otherwise exclusionary jokes are not\n  appropriate.\n\n- We pledge to respect the work of others by\n  recognizing acknowledgment/citation requests of\n  original authors. As authors, we pledge to be explicit\n  about how we want our own work to be cited or\n  acknowledged.\n\n- We pledge to welcome those interested in joining the\n  community, and realize that including people with a\n  variety of opinions and backgrounds will only serve to\n  enrich our community. In particular, discussions\n  relating to pros/cons of various technologies,\n  programming languages, and so on are welcome, but\n  these should be done with respect, taking proactive\n  measure to ensure that all participants are heard and\n  feel confident that they can freely express their\n  opinions.\n\n- We pledge to welcome questions and answer them\n  respectfully, paying particular attention to those new\n  to the community. We pledge to provide respectful\n  criticisms and feedback in forums, especially in\n  discussion threads resulting from code\n  contributions.\n\n- We pledge to be conscientious of the perceptions of\n  the wider community and to respond to criticism\n  respectfully. We will strive to model behaviors that\n  encourage productive debate and disagreement, both\n  within our community and where we are criticized. We\n  will treat those outside our community with the same\n  respect as people within our community.\n\n- We pledge to help the entire community follow the\n  code of conduct, and to not remain silent when we see\n  violations of the code of conduct. We will take action\n  when members of our community violate this code such as\n  contacting confidential@yt-project.org (all emails sent to\n  this address will be treated with the strictest\n  confidence) or talking privately with the person.\n\nThis code of conduct applies to all\ncommunity situations online and offline, including mailing\nlists, forums, social media, conferences, meetings,\nassociated social events, and one-to-one interactions.\n\nThe yt Community Code of Conduct was adapted from the\n`Astropy Community Code of Conduct\n<https://www.astropy.org/code_of_conduct.html>`_,\nwhich was partially inspired by the PSF code of conduct.\n\n.. _contributing-code:\n\nHow to Develop yt\n=================\n\nyt is a community project!\n\nWe are very happy to accept patches, features, and bugfixes from any member of\nthe community!  yt is developed using git, primarily because it enables\nvery easy and straightforward submission of revisions.  We're eager to hear\nfrom you, and if you are developing yt, we encourage you to subscribe to the\n`developer mailing list\n<https://mail.python.org/archives/list/yt-dev@python.org/>`_. Please feel\nfree to hack around, commit changes, and send them upstream.\n\n.. note:: If you already know how to use the `git version control system\n   <https://git-scm.com/>`_ and are comfortable with handling it yourself,\n   the quickest way to contribute to yt is to `fork us on GitHub\n   <https://github.com/yt-project/yt/fork>`_, make your changes, push the\n   changes to your fork and issue a `pull request\n   <https://github.com/yt-project/yt/pulls>`_.  The rest of this\n   document is just an explanation of how to do that.\n\nSee :ref:`code-style-guide` for more information about coding style in yt and\n:ref:`docstrings` for an example docstring.  Please read them before hacking on\nthe codebase, and feel free to email any of the mailing lists for help with the\ncodebase.\n\nKeep in touch, and happy hacking!\n\n.. _open-issues:\n\nOpen Issues\n-----------\n\nIf you're interested in participating in yt development, take a look at the\n`issue tracker on GitHub\n<https://github.com/yt-project/yt/issues>`_.\nYou can search by labels, indicating estimated level of difficulty or category,\nto find issues that you would like to contribute to.  Good first issues are\nmarked with a label of *new contributor friendly*.  While we try to triage the\nissue tracker regularly to assign appropriate labels to every issue, it may be\nthe case that issues not marked as *new contributor friendly* are actually\nsuitable for new contributors.\n\nHere are some predefined issue searches that might be useful:\n\n* Unresolved issues `marked \"new contributor friendly\"\n  <https://github.com/yt-project/yt/labels/new%20contributor%20friendly>`_.\n* `All unresolved issues <https://github.com/yt-project/yt/issues>`_.\n\nSubmitting Changes\n------------------\n\nWe provide a brief introduction to submitting changes here.  yt thrives on the\nstrength of its communities (https://arxiv.org/abs/1301.7064 has further\ndiscussion) and we encourage contributions from any user.  While we do not\ndiscuss version control, git, or the advanced usage of GitHub in detail\nhere, we do provide an outline of how to submit changes and we are happy to\nprovide further assistance or guidance.\n\nLicensing\n+++++++++\n\nyt is `licensed <https://blog.yt-project.org/posts/relicensing/>`_ under the\nBSD 3-clause license.  Versions previous to yt-2.6 were released under the GPLv3.\n\nAll contributed code must be BSD-compatible.  If you'd rather not license in\nthis manner, but still want to contribute, please consider creating an external\npackage, which we'll happily link to.\n\nHow To Get The Source Code For Editing\n++++++++++++++++++++++++++++++++++++++\n\nyt is hosted on GitHub, and you can see all of the yt repositories at\nhttps://github.com/yt-project/. To fetch and modify source code, make sure you\nhave followed the steps above for bootstrapping your development (to assure you\nhave a GitHub account, etc.).\n\nIn order to modify the source code for yt, we ask that you make a \"fork\" of the\nmain yt repository on GitHub.  A fork is simply an exact copy of the main\nrepository (along with its history) that you will now own and can make\nmodifications as you please.  You can create a personal fork by visiting the yt\nGitHub webpage at https://github.com/yt-project/yt/ .  After logging in,\nyou should see an option near the top right labeled \"fork\". You now have\na forked copy of the yt repository for your own personal modification.\n\nThis forked copy exists on the GitHub repository, so in order to access\nit locally you must clone it onto your machine from the command line:\n\n.. code-block:: bash\n\n   $ git clone https://github.com/<USER>/yt ./yt-git\n\nThis downloads that new forked repository to your local machine, so that you\ncan access it, read it, make modifications, etc.  It will put the repository in\na local directory of the same name as the repository in the current working\ndirectory.\n\n.. code-block:: bash\n\n   $ cd yt-git\n\nVerify that you are on the ``main`` branch of yt by running:\n\n.. code-block:: bash\n\n   $ git branch\n\nYou can see any past state of the code by using the git log command.\nFor example, the following command would show you the last 5 revisions\n(modifications to the code) that were submitted to that repository.\n\n.. code-block:: bash\n\n   $ git log -n 5\n\nUsing the revision specifier (the number or hash identifier next to each\nchangeset), you can update the local repository to any past state of the\ncode (a previous changeset or version) by executing the command:\n\n.. code-block:: bash\n\n   $ git checkout revision_specifier\n\nYou can always return to the most recent version of the code by executing the\nsame command as above with the most recent revision specifier in the\nrepository. However, using ``git log`` when you're checked out to an older\nrevision specifier will not show more recent changes to the repository. An\nalternative option is to use ``checkout`` on a branch. In yt the ``main``\nbranch is our primary development branch, so checking out ``main`` should\nreturn you to the tip (or most up-to-date revision specifier) on the ``main``\nbranch.\n\n.. code-block:: bash\n\n   $ git checkout main\n\nLastly, if you want to use this new downloaded version of your yt repository as\nthe *active* version of yt on your computer (i.e. the one which is executed when\nyou run yt from the command line or the one that is loaded when you do ``import\nyt``), then you must \"activate\" by building yt from source as described in\n:ref:`install-from-source`.\n\n.. _reading-source:\n\nHow To Read The Source Code\n+++++++++++++++++++++++++++\n\nThe root directory of the yt git repository contains a number of\nsubdirectories with different components of the code.  Most of the yt source\ncode is contained in the yt subdirectory.  This directory itself contains\nthe following subdirectories:\n\n``frontends``\n   This is where interfaces to codes are created.  Within each subdirectory of\n   yt/frontends/ there must exist the following files, even if empty:\n\n   * ``data_structures.py``, where subclasses of AMRGridPatch, Dataset\n     and AMRHierarchy are defined.\n   * ``io.py``, where a subclass of IOHandler is defined.\n   * ``fields.py``, where fields we expect to find in datasets are defined\n   * ``misc.py``, where any miscellaneous functions or classes are defined.\n   * ``definitions.py``, where any definitions specific to the frontend are\n     defined.  (i.e., header formats, etc.)\n\n``fields``\n   This is where all of the derived fields that ship with yt are defined.\n\n``geometry``\n   This is where geometric helper routines are defined. Handlers\n   for grid and oct data, as well as helpers for coordinate transformations\n   can be found here.\n\n``visualization``\n   This is where all visualization modules are stored.  This includes plot\n   collections, the volume rendering interface, and pixelization frontends.\n\n``data_objects``\n   All objects that handle data, processed or unprocessed, not explicitly\n   defined as visualization are located in here.  This includes the base\n   classes for data regions, covering grids, time series, and so on.  This\n   also includes derived fields and derived quantities.\n\n``units``\n   This used to be where all the unit-handling code resided, but as of now it's\n   mostly just a thin wrapper around unyt.\n\n``utilities``\n   All broadly useful code that doesn't clearly fit in one of the other\n   categories goes here.\n\n\nIf you're looking for a specific file or function in the yt source code, use\nthe unix find command:\n\n.. code-block:: bash\n\n   $ find <DIRECTORY_TREE_TO_SEARCH> -name '<FILENAME>'\n\nThe above command will find the FILENAME in any subdirectory in the\nDIRECTORY_TREE_TO_SEARCH.  Alternatively, if you're looking for a function\ncall or a keyword in an unknown file in a directory tree, try:\n\n.. code-block:: bash\n\n   $ grep -R <KEYWORD_TO_FIND> <DIRECTORY_TREE_TO_SEARCH>\n\nThis can be very useful for tracking down functions in the yt source.\n\n.. _building-yt:\n\nBuilding yt\n+++++++++++\n\nIf you have made changes to any C or Cython (``.pyx``) modules, you have to\nrebuild yt before your changes are usable. See :ref:`install-from-source`.\n\n.. _requirements-for-code-submission:\n\nRequirements for Code Submission\n--------------------------------\n\nModifications to the code typically fall into one of three categories, each of\nwhich have different requirements for acceptance into the code base.  These\nrequirements are in place for a few reasons -- to make sure that the code is\nmaintainable, testable, and that we can easily include information about\nchanges in changelogs during the release procedure.  (See `YTEP-0008\n<https://ytep.readthedocs.io/en/master/YTEPs/YTEP-0008.html>`_ for more\ndetail.)\n\nFor all types of contributions, it is required that all tests pass, or that all non-passing tests are specifically accounted for.\n\n* New Features\n\n  * New unit tests (possibly new answer tests) (See :ref:`testing`)\n  * Docstrings in the source code for the public API\n  * Addition of new feature to the narrative documentation (See :ref:`writing_documentation`)\n  * Addition of cookbook recipe (See :ref:`writing_documentation`)\n\n* Extension or Breakage of API in Existing Features\n\n  * Update existing narrative docs and docstrings (See :ref:`writing_documentation`)\n  * Update existing cookbook recipes (See :ref:`writing_documentation`)\n  * Modify of create new unit tests (See :ref:`testing`)\n\n* Bug fixes\n\n  * Unit test is encouraged, to ensure breakage does not happen again in the\n    future. (See :ref:`testing`)\n  * At a minimum, a minimal, self-contained example demonstrating the bug should\n    be included in the body of the Pull Request, or as part of an\n    independent issue.\n\nWhen submitting, you will be asked to make sure that your changes meet all of\nthese requirements.  They are pretty easy to meet, and we're also happy to help\nout with them. See :ref:`code-style-guide` for how to easily conform to our\nstyle guide.\n\n\n.. _git-with-yt:\n\nHow to Use git with yt\n----------------------\n\nIf you're new to git, the following resource is pretty great for learning\nthe ins and outs:\n\n* https://git-scm.com/\n\nThere also exist a number of step-by-step git tutorials to help you get used to\nversion controlling files with git. Here are a few resources that you may find\nhelpful:\n\n* http://swcarpentry.github.io/git-novice/\n* https://git-scm.com/docs/gittutorial\n* https://try.github.io/\n\nThe commands that are essential for using git include:\n\n* ``git <command> --help`` which provides help for any git command. For example, you\n  can learn more about the ``log`` command by doing ``git log --help``.\n* ``git add <paths>`` which stages changes to the specified paths for subsequent\n  committing (see below).\n* ``git commit`` which commits staged changes (stage using ``git add`` as above)\n  in the working directory to the repository, creating a new \"revision.\"\n* ``git merge <branch>`` which merges the revisions from the specified branch\n  into the current branch, creating a union of their lines of development. This\n  updates the working directory.\n* ``git pull <remote> <branch>`` which pulls revisions from the specified branch of the\n  specified remote repository into the current local branch. Equivalent to ``git\n  fetch <remote>`` and then ``git merge <remote>/<branch>``. This updates the\n  working directory.\n* ``git push <remote>`` which sends revisions on local branches to matching\n  branches on the specified remote. ``git push <remote> <branch>`` will only\n  push changes for the specified branch.\n* ``git log`` which shows a log of all revisions on the current branch. There\n  are many options you can pass to ``git log`` to get additional\n  information. One example is ``git log --oneline --decorate --graph --all``.\n\nWe are happy to answer questions about git use on our IRC, slack\nchat or on the mailing list to walk you through any troubles you might have.\nHere are some general suggestions for using git with yt:\n\n* Although not necessary, a common development work flow is to create a local\n  named branch other than ``main`` to address a feature request or bugfix. If\n  the dev work addresses a specific yt GitHub issue, you may include that issue\n  number in the branch name. For example, if you want to work on issue number X\n  regarding a cool new slice plot feature, you might name the branch:\n  ``cool_new_plot_feature_X``. When you're ready to share your work, push your\n  feature branch to your remote and create a pull request to the ``main``\n  branch of the yt-project's repository.\n* When contributing changes, you might be asked to make a handful of\n  modifications to your source code.  We'll work through how to do this with\n  you, and try to make it as painless as possible.\n* Your test may fail automated style checks. See :ref:`code-style-guide` for\n  more information about automatically verifying your code style.\n* You should only need one fork.  To keep it in sync, you can sync from the\n  website. See :ref:`sharing-changes` for a description of the basic workflow\n  and :ref:`multiple-PRs` for a discussion about what to do when you want to\n  have multiple open pull requests at the same time.\n* If you run into any troubles, stop by IRC (see :ref:`irc`), Slack, or the\n  mailing list.\n\n.. _sharing-changes:\n\nMaking and Sharing Changes\n--------------------------\n\nThe simplest way to submit changes to yt is to do the following:\n\n* Build yt from the git repository\n* Navigate to the root of the yt repository\n* Make some changes and commit them\n* Fork the `yt repository on GitHub <https://github.com/yt-project/yt>`_\n* Push the changesets to your fork\n* Issue a pull request.\n\nHere's a more detailed flowchart of how to submit changes.\n\n#. Fork yt on GitHub.  (This step only has to be done once.)  You can do\n   this at: https://github.com/yt-project/yt/fork.\n#. Follow :ref:`install-from-source` for instructions on how to build yt\n   from the git repository. (Below, in :ref:`reading-source`, we describe how to\n   find items of interest.) If you have already forked the repository then\n   you can clone your fork locally::\n\n     git clone https://github.com/<USER>/yt ./yt-git\n\n   This will create a local clone of your fork of yt in a folder named\n   ``yt-git``.\n#. Edit the source file you are interested in and\n   test your changes.  (See :ref:`testing` for more information.)\n#. Create a uniquely named branch to track your work. For example: ``git\n   checkout -b my-first-pull-request``\n#. Stage your changes using ``git add <paths>``.  This command take an argument\n   which is a series of filenames whose changes you want to commit. After\n   staging, execute ``git commit -m \"<Commit description>. Addresses Issue\n   #X\"``. Note that supplying an actual GitHub issue # in place of ``X`` will\n   cause your commit to appear in the issue tracker after pushing to your\n   remote. This can be very helpful for others who are interested in what work\n   is being done in connection to that issue.\n#. Remember that this is a large development effort and to keep the code\n   accessible to everyone, good documentation is a must.  Add in source code\n   comments for what you are doing.  Add in docstrings\n   if you are adding a new function or class or keyword to a function.\n   Add documentation to the appropriate section of the online docs so that\n   people other than yourself know how to use your new code.\n#. If your changes include new functionality or cover an untested area of the\n   code, add a test.  (See :ref:`testing` for more information.)  Commit\n   these changes as well.\n#. Add your remote repository with a unique name identifier. It can be anything\n   but it is conventional to call it ``origin``.  You can see names and URLs of\n   all the remotes you currently have configured with::\n\n     git remote -v\n\n   If you already have an ``origin`` remote, you can set it to your fork with::\n\n     git remote set-url origin https://github.com/<USER>/yt\n\n   If you do not have an ``origin`` remote you will need to add it::\n\n     git remote add origin https://github.com/<USER>/yt\n\n   In addition, it is also useful to add a remote for the main yt repository.\n   By convention we name this remote ``upstream``::\n\n     git remote add upstream https://github.com/yt-project/yt\n\n   Note that if you forked the yt repository on GitHub and then cloned from\n   there you will not need to add the ``origin`` remote.\n\n#. Push your changes to your remote fork using the unique identifier you just\n   created and the command::\n\n      git push origin my-first-pull-request\n\n   Where you should substitute the name of the feature branch you are working on for\n   ``my-first-pull-request``.\n\n   .. note::\n     Note that the above approach uses HTTPS as the transfer protocol\n     between your machine and GitHub.  If you prefer to use SSH - or\n     perhaps you're behind a proxy that doesn't play well with SSL via\n     HTTPS - you may want to set up an `SSH key`_ on GitHub.  Then, you use\n     the syntax ``ssh://git@github.com/<USER>/yt``, or equivalent, in\n     place of ``https://github.com/<USER>/yt`` in git commands.\n     For consistency, all commands we list in this document will use the HTTPS\n     protocol.\n\n     .. _SSH key: https://help.github.com/en/articles/connecting-to-github-with-ssh/\n#. Issue a pull request at https://github.com/yt-project/yt/pull/new/main A\n   pull request is essentially just asking people to review and accept the\n   modifications you have made to your personal version of the code.\n\nDuring the course of your pull request you may be asked to make changes.  These\nchanges may be related to style issues, correctness issues, or requesting\ntests.  The process for responding to pull request code review is relatively\nstraightforward.\n\n#. Make requested changes, or leave a comment indicating why you don't think\n   they should be made.\n#. Commit those changes to your local repository.\n#. Push the changes to your fork::\n\n      git push origin my-first-pull-request\n\n#. Your pull request will be automatically updated.\n\nOnce your pull request is merged, sync up with the main yt repository by pulling\nfrom the ``upstream`` remote::\n\n     git checkout main\n     git pull upstream main\n\nYou might also want to sync your fork of yt on GitHub::\n\n     # sync my fork of yt with upstream\n     git push origin main\n\nAnd delete the branch for the merged pull request::\n\n     # delete branch for merged pull request\n     git branch -d my-first-pull-request\n     git push origin --delete my-first-pull-request\n\nThese commands are optional but are nice for keeping your branch list\nmanageable. You can also delete the branch on your fork of yt on GitHub by\nclicking the \"delete branch\" button on the page for the merged pull request on\nGitHub.\n\n.. _multiple-PRs:\n\nWorking with Multiple GitHub Pull Requests\n------------------------------------------\n\nDealing with multiple pull requests on GitHub is straightforward. Development on\none feature should be isolated in one named branch, say ``feature_1`` while\ndevelopment of another feature should be in another named branch, say\n``feature_2``. A push to remote ``feature_1`` will automatically update any\nactive PR for which ``feature_1`` is a pointer to the ``HEAD`` commit. A push to\n``feature_1`` *will not* update any pull requests involving ``feature_2``.\n\n.. _code-style-guide:\n\nCoding Style Guide\n==================\n\nAutomatically checking and fixing code style\n--------------------------------------------\n\nWe use the `pre-commit <https://pre-commit.com>`_ framework to validate and\nautomatically fix code styling.\nIt is recommended (though not required) that you install ``pre-commit`` on your machine\n(see their documentation) and, from the top level of the repo, run\n\n.. code-block:: bash\n\n    $ pre-commit install\n\nSo that our hooks will run and update your changes on every commit.\nIf you do not want to/are unable to configure ``pre-commit`` on your machine, note that\nafter opening a pull request, it will still be run as a static checker as part of our CI.\nSome hooks also come with auto-fixing capabilities, which you can trigger manually in a\nPR by commenting ``pre-commit.ci autofix`` (see ` <https://pre-commit.ci/#features>`_).\n\nWe use a combination of `black <https://black.readthedocs.io/en/stable/>`_,\n`ruff <https://beta.ruff.rs/docs//>`_ and `cython-lint\n<https://github.com/MarcoGorelli/cython-lint>`_. See ``.pre-commit-config.yaml``\nand ``pyproject.toml`` for the complete configuration details.\n\nNote that formatters should not be run directly on the command line as, for instance\n\n.. code-block:: bash\n\n    $ black yt\n\nBut it can still be done as\n\n.. code-block:: bash\n\n    $ pre-commit run black --all-files\n\nThe reason is that you may have a specific version of ``black`` installed which can\nproduce different results, while the one that's installed with pre-commit is guaranteed\nto be in sync with the rest of contributors.\n\nBelow are a list of additional guidelines for coding in yt, that are not automatically\nenforced.\n\n\nSource code style guide\n-----------------------\n\n* In general, follow PEP-8 guidelines.\n  https://www.python.org/dev/peps/pep-0008/\n* Classes are ``ConjoinedCapitals``, methods and functions are\n  ``lowercase_with_underscores``.\n* Do not use nested classes unless you have a very good reason to, such as\n  requiring a namespace or class-definition modification.  Classes should live\n  at the top level.  ``__metaclass__`` is exempt from this.\n* Avoid copying memory when possible.\n* In general, avoid all double-underscore method names: ``__something`` is\n  usually unnecessary.\n* When writing a subclass, use the super built-in to access the super class,\n  rather than explicitly.\n  Ex: ``super().__init__()`` rather than ``SpecialGrid.__init__()``.\n* Docstrings should describe input, output, behavior, and any state changes\n  that occur on an object.  See :ref:`docstrings` below for a fiducial example\n  of a docstring.\n* Unless there is a good reason not to (e.g., to avoid circular imports),\n  imports should happen at the top of the file.\n* If you are comparing with a numpy boolean array, just refer to the array.\n  Ex: do ``np.all(array)`` instead of ``np.all(array == True)``.\n* Only declare local variables if they will be used later. If you do not use the\n  return value of a function, do not store it in a variable.\n\nAPI Style Guide\n---------------\n\n* Internally, only import from source files directly -- instead of:\n\n    ``from yt.visualization.api import ProjectionPlot``\n\n  do:\n\n    ``from yt.visualization.plot_window import ProjectionPlot``\n\n* Import symbols from the module where they are defined, avoid transitive\n  imports.\n* Import standard library modules, functions, and classes from builtins, do not\n  import them from other yt files.\n* Numpy is to be imported as ``np``.\n* Do not use too many keyword arguments.  If you have a lot of keyword\n  arguments, then you are doing too much in ``__init__`` and not enough via\n  parameter setting.\n* Don't create a new class to replicate the functionality of an old class --\n  replace the old class.  Too many options makes for a confusing user\n  experience.\n* Parameter files external to yt are a last resort.\n* The usage of the ``**kwargs`` construction should be avoided.  If they cannot\n  be avoided, they must be documented, even if they are only to be passed on to\n  a nested function.\n\n.. _docstrings:\n\nDocstrings\n----------\n\nThe following is an example docstring. You can use it as a template for\ndocstrings in your code and as a guide for how we expect docstrings to look and\nthe level of detail we are looking for. Note that we use NumPy style docstrings\nwritten in `Sphinx restructured text format\n<http://www.sphinx-doc.org/es/master/usage/restructuredtext/>`_.\n\n.. code-block:: rest\n\n    r\"\"\"A one-line summary that does not use variable names or the\n    function name.\n\n    Several sentences providing an extended description. Refer to\n    variables using back-ticks, e.g. ``var``.\n\n    Parameters\n    ----------\n    var1 : array_like\n        Array_like means all those objects -- lists, nested lists, etc. --\n        that can be converted to an array.  We can also refer to\n        variables like ``var1``.\n    var2 : int\n        The type above can either refer to an actual Python type\n        (e.g. ``int``), or describe the type of the variable in more\n        detail, e.g. ``(N,) ndarray`` or ``array_like``.\n    Long_variable_name : {'hi', 'ho'}, optional\n        Choices in brackets, default first when optional.\n\n    Returns\n    -------\n    describe : type\n        Explanation\n    output : type\n        Explanation\n    tuple : type\n        Explanation\n    items : type\n        even more explaining\n\n    Other Parameters\n    ----------------\n    only_seldom_used_keywords : type\n        Explanation\n    common_parameters_listed_above : type\n        Explanation\n\n    Raises\n    ------\n    BadException\n        Because you shouldn't have done that.\n\n    See Also\n    --------\n    otherfunc : relationship (optional)\n    newfunc : Relationship (optional), which could be fairly long, in which\n              case the line wraps here.\n    thirdfunc, fourthfunc, fifthfunc\n\n    Notes\n    -----\n    Notes about the implementation algorithm (if needed).\n\n    This can have multiple paragraphs.\n\n    You may include some math:\n\n    .. math:: X(e^{j\\omega } ) = x(n)e^{ - j\\omega n}\n\n    And even use a greek symbol like :math:`omega` inline.\n\n    References\n    ----------\n    Cite the relevant literature, e.g. [1]_.  You may also cite these\n    references in the notes section above.\n\n    .. [1] O. McNoleg, \"The integration of GIS, remote sensing,\n       expert systems and adaptive co-kriging for environmental habitat\n       modelling of the Highland Haggis using object-oriented, fuzzy-logic\n       and neural-network techniques,\" Computers & Geosciences, vol. 22,\n       pp. 585-588, 1996.\n\n    Examples\n    --------\n    These are written in doctest format, and should illustrate how to\n    use the function.  Use the variables 'ds' for the dataset, 'pc' for\n    a plot collection, 'c' for a center, and 'L' for a vector.\n\n    >>> a = [1, 2, 3]\n    >>> print([x + 3 for x in a])\n    [4, 5, 6]\n    >>> print(\"a\\n\\nb\")\n    a\n\n    b\n    \"\"\"\n\nVariable Names and Enzo-isms\n----------------------------\nAvoid Enzo-isms.  This includes but is not limited to:\n\n* Hard-coding parameter names that are the same as those in Enzo.  The\n  following translation table should be of some help.  Note that the\n  parameters are now properties on a ``Dataset`` subclass: you access them\n  like ds.refine_by .\n\n  - ``RefineBy `` => `` refine_by``\n  - ``TopGridRank `` => `` dimensionality``\n  - ``TopGridDimensions `` => `` domain_dimensions``\n  - ``InitialTime `` => `` current_time``\n  - ``DomainLeftEdge `` => `` domain_left_edge``\n  - ``DomainRightEdge `` => `` domain_right_edge``\n  - ``CurrentTimeIdentifier `` => `` unique_identifier``\n  - ``CosmologyCurrentRedshift `` => `` current_redshift``\n  - ``ComovingCoordinates `` => `` cosmological_simulation``\n  - ``CosmologyOmegaMatterNow `` => `` omega_matter``\n  - ``CosmologyOmegaLambdaNow `` => `` omega_lambda``\n  - ``CosmologyHubbleConstantNow `` => `` hubble_constant``\n\n* Do not assume that the domain runs from 0 .. 1.  This is not true\n  everywhere.\n* Variable names should be short but descriptive.\n* No globals!\n"
  },
  {
    "path": "COPYING.txt",
    "content": "===============================\n The yt project licensing terms\n===============================\n\nyt is licensed under the terms of the Modified BSD License (also known as New\nor Revised BSD), as follows:\n\nCopyright (c) 2013-, yt Development Team\nCopyright (c) 2006-2013, Matthew Turk <matthewturk@gmail.com>\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\nRedistributions of source code must retain the above copyright notice, this\nlist of conditions and the following disclaimer.\n\nRedistributions in binary form must reproduce the above copyright notice, this\nlist of conditions and the following disclaimer in the documentation and/or\nother materials provided with the distribution.\n\nNeither the name of the yt Development Team nor the names of its\ncontributors may be used to endorse or promote products derived from this\nsoftware without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nAbout the yt Development Team\n-----------------------------\n\nMatthew Turk began yt in 2006 and remains the project lead.  Over time yt has\ngrown to include contributions from a large number of individuals from many\ndiverse institutions, scientific, and technical backgrounds.\n\nUntil the fall of 2013, yt was licensed under the GPLv3.  However, with consent\nfrom all developers and on a public mailing list, yt has been relicensed under\nthe BSD 3-clause under a shared copyright model.  For more information, see:\nhttps://mail.python.org/archives/list/yt-dev@python.org/thread/G4DJDDGB4PSZFJVPWRSHNOSUMTISXC4X/\nand https://yt-project.github.io/blog/posts/relicensing/ . All versions of yt\nprior to this licensing change are available under the GPLv3; all subsequent\nversions (yt versions >= 2.6.0) are available under the BSD 3-clause license.\n\nThe yt Development Team is the set of all contributors to the yt project.  This\nincludes all of the yt subprojects.\n\nThe core team that coordinates development on GitHub can be found here:\nhttps://github.com/yt-project/yt\n\n\nOur Copyright Policy\n--------------------\n\nyt uses a shared copyright model. Each contributor maintains copyright\nover their contributions to yt. But, it is important to note that these\ncontributions are typically only changes to the repositories. Thus, the yt\nsource code, in its entirety is not the copyright of any single person or\ninstitution.  Instead, it is the collective copyright of the entire yt\nDevelopment Team.  If individual contributors want to maintain a record of what\nchanges/contributions they have specific copyright on, they should indicate\ntheir copyright in the commit message of the change, when they commit the\nchange to one of the yt repositories.\n"
  },
  {
    "path": "CREDITS",
    "content": "yt is a group effort.\n\nContributors:\n                Tom Abel (tabel@slac.stanford.edu)\n                Gabriel Altay (gabriel.altay@gmail.com)\n                Kenza Arraki (karraki@nmsu.edu)\n                Kirk Barrow (kssbarrow@gatech.edu)\n                Ricarda Beckmann (ricarda.beckmann@astro.ox.ac.uk)\n                Christoph Behrens (cbehren2@gwdu101.global.gwdg.cluster)\n                Elliott Biondo (biondo@wisc.edu)\n                Alex Bogert (fbogert@ucsc.edu)\n                Josh Borrow (joshua.borrow@durham.ac.uk)\n                Robert Bradshaw (robertwb@gmail.com)\n                André-Patrick Bubel (code@andre-bubel.de)\n                Corentin Cadiou (corentin.cadiou@iap.fr)\n                Pengfei Chen (madcpf@gmail.com)\n                Yi-Hao Chen (ychen@astro.wisc.edu)\n                Yi-Hao Chen (yihaochentw@gmail.com)\n                Salvatore Cielo (cielo@iap.fr)\n                David Collins (dcollins4096@gmail.com)\n                Marianne Corvellec (marianne.corvellec@ens-lyon.org)\n                Jared Coughlin (jcoughl2@nd.edu)\n                Brian Crosby (bcrosby.bd@gmail.com)\n                Weiguang Cui (weiguang.cui@uwa.edu.au)\n                Andrew Cunningham (ajcunn@gmail.com)\n                Bili Dong (qobilidop@gmail.com)\n                Donald E Willcox (eugene.willcox@gmail.com)\n                Nicholas Earl (nchlsearl@gmail.com)\n                Hilary Egan (hilaryye@gmail.com)\n                Daniel Fenn (df11c@my.fsu.edu)\n                John Forbes (jcforbes@ucsc.edu)\n                Enrico Garaldi (egaraldi@uni-bonn.de)\n                Sam Geen (samgeen@gmail.com)\n                Austin Gilbert (agilbert39@gatech.edu)\n                Adam Ginsburg (keflavich@gmail.com)\n                Nick Gnedin (ngnedin@gmail.com)\n                Nathan Goldbaum (ngoldbau@illinois.edu)\n                William Gray (graywilliamj@gmail.com)\n                Philipp Grete (mail@pgrete.de)\n                Max Gronke (max.groenke@gmail.com)\n                Markus Haider (markus.haider@uibk.ac.at)\n                Eric Hallman (hallman13@gmail.com)\n                David Hannasch (David.A.Hannasch@gmail.com)\n                Stephanie Ho (stephaniehkho@gmail.com)\n                Axel Huebl (a.huebl@hzdr.de)\n                Cameron Hummels (chummels@gmail.com)\n                Suoqing Ji (jisuoqing@gmail.com)\n                Allyson Julian (astrohckr@gmail.com)\n                Anni Järvenpää (anni.jarvenpaa@gmail.com)\n                Christian Karch (chiffre@posteo.de)\n                Max Katz (maximilian.katz@stonybrook.edu)\n                BW Keller (kellerbw@mcmaster.ca)\n                Ashley Kelly (a.j.kelly@durham.ac.uk)\n                Chang-Goo Kim (changgoo@princeton.edu)\n                Ji-hoon Kim (me@jihoonkim.org)\n                Steffen Klemer (sklemer@phys.uni-goettingen.de)\n                Fabian Koller (anokfireball@posteo.de)\n                Claire Kopenhafer (clairekope@gmail.com)\n                Kacper Kowalik (xarthisius.kk@gmail.com)\n                Matthew Krafczyk (krafczyk.matthew@gmail.com)\n                Mark Krumholz (mkrumhol@ucsc.edu)\n                Michael Kuhlen (mqk@astro.berkeley.edu)\n                Avik Laha (al3510@moose.cc.columbia.edu)\n                Meagan Lang (langmm.astro@gmail.com)\n                Erwin Lau (ethlau@gmail.com)\n                Doris Lee (dorislee@berkeley.edu)\n                Eve Lee (elee@cita.utoronto.ca)\n                Sam Leitner (sam.leitner@gmail.com)\n                Yuan Li (yuan@astro.columbia.edu)\n                Alex Lindsay (al007@illinois.edu)\n                Yingchao Lu (yingchao.lu@gmail.com)\n                Yinghe Lu (yinghelu@lbl.gov)\n                Chris Malone (chris.m.malone@gmail.com)\n                John McCann (mccann@ucsb.edu)\n                Jonah Miller (jonah.maxwell.miller@gmail.com)\n                Joshua Moloney (joshua.moloney@colorado.edu)\n                Christopher Moody (cemoody@ucsc.edu)\n                Chris Moody (juxtaposicion@gmail.com)\n                Stuart Mumford (stuart@mumford.me.uk)\n                Madicken Munk (madicken.munk@gmail.com)\n                Andrew Myers (atmyers2@gmail.com)\n                Jill Naiman (jnaiman@cfa.harvard.edu)\n                Desika Narayanan (dnarayan@haverford.edu)\n                Kaylea Nelson (kaylea.nelson@yale.edu)\n                Brian O'Shea (oshea@msu.edu)\n                J.S. Oishi (jsoishi@gmail.com)\n                JC Passy (jcpassy@uvic.ca)\n                Hugo Pfister (pfister@loginhz02.iap.fr)\n                David Pérez-Suárez (dps.helio@gmail.com)\n                John Regan (john.regan@helsinki.fi)\n                Mark Richardson (Mark.Richardson.Work@gmail.com)\n                Sherwood Richers (srichers@tapir.caltech.edu)\n                Thomas Robitaille (thomas.robitaille@gmail.com)\n                Anna Rosen (rosen@ucolick.org)\n                Chuck Rozhon (rozhon2@illinois.edu)\n                Douglas Rudd (drudd@uchicago.edu)\n                Rafael Ruggiero (rafael.ruggiero@usp.br)\n                Hsi-Yu Schive (hyschive@gmail.com)\n                Anthony Scopatz (scopatz@gmail.com)\n                Noel Scudder (noel.scudder@stonybrook.edu)\n                Patrick Shriwise (shriwise@wisc.edu)\n                Devin Silvia (devin.silvia@gmail.com)\n                Abhishek Singh (abhisheksing@umass.edu)\n                Sam Skillman (samskillman@gmail.com)\n                Stephen Skory (s@skory.us)\n                Joseph Smidt (josephsmidt@gmail.com)\n                Aaron Smith (asmith@astro.as.utexas.edu)\n                Britton Smith (brittonsmith@gmail.com)\n                Geoffrey So (gsiisg@gmail.com)\n                Josh Soref (jsoref@users.noreply.github.com)\n                Antoine Strugarek (antoine.strugarek@cea.fr)\n                Elizabeth Tasker (tasker@astro1.sci.hokudai.ac.jp)\n                Ben Thompson (bthompson2090@gmail.com)\n                Benjamin Thompson (bthompson2090@gmail.com)\n                Robert Thompson (rthompsonj@gmail.com)\n                Joseph Tomlinson (jmtomlinson95@gmail.com)\n                Stephanie Tonnesen (stonnes@gmail.com)\n                Matthew Turk (matthewturk@gmail.com)\n                Miguel de Val-Borro (miguel.deval@gmail.com)\n                Kausik Venkat (kvenkat2@illinois.edu)\n                Casey W. Stark (caseywstark@gmail.com)\n                Rick Wagner (rwagner@physics.ucsd.edu)\n                Mike Warren (mswarren@gmail.com)\n                Charlie Watson (charlie.watson95@gmail.com)\n                Andrew Wetzel (andrew.wetzel@yale.edu)\n                John Wise (jwise@physics.gatech.edu)\n                Michael Zingale (michael.zingale@stonybrook.edu)\n                John ZuHone (jzuhone@gmail.com)\n\nThe PasteBin interface code (as well as the PasteBin itself)\nwas written by the Pocoo collective (pocoo.org).\ndeveloped by Oliver Hahn.\n\nThanks to everyone for all your contributions!\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README* CREDITS CITATION  setupext.py CONTRIBUTING.rst\ninclude yt/visualization/mapserver/html/map.js\ninclude yt/visualization/mapserver/html/map_index.html\ninclude yt/visualization/mapserver/html/Leaflet.Coordinates-0.1.5.css\ninclude yt/visualization/mapserver/html/Leaflet.Coordinates-0.1.5.src.js\ninclude yt/utilities/tests/cosmology_answers.yml\ninclude yt/utilities/mesh_types.yaml\nexclude yt/utilities/lib/cykdtree/c_kdtree.cpp\nprune tests\nprune answer-store\nrecursive-include yt *.py *.pyx *.pxi *.pxd  README* *.txt *.cu\nrecursive-include doc *.rst *.txt *.py *.ipynb *.png *.jpg *.css *.html\nrecursive-include doc *.h *.c *.sh *.svgz *.pdf *.svg *.pyx\n\n\n# start with excluding all C/C++ files\nrecursive-exclude yt *.h *.c *.hpp *.cpp\n\n# then include back every non-generated C/C++ source file\n# the list can be generated by the following command\n# git ls-files | grep -E '\\.(h|c)(pp)?$'\ninclude yt/frontends/artio/artio_headers/artio.c\ninclude yt/frontends/artio/artio_headers/artio.h\ninclude yt/frontends/artio/artio_headers/artio_endian.c\ninclude yt/frontends/artio/artio_headers/artio_endian.h\ninclude yt/frontends/artio/artio_headers/artio_file.c\ninclude yt/frontends/artio/artio_headers/artio_grid.c\ninclude yt/frontends/artio/artio_headers/artio_internal.h\ninclude yt/frontends/artio/artio_headers/artio_mpi.c\ninclude yt/frontends/artio/artio_headers/artio_mpi.h\ninclude yt/frontends/artio/artio_headers/artio_parameter.c\ninclude yt/frontends/artio/artio_headers/artio_particle.c\ninclude yt/frontends/artio/artio_headers/artio_posix.c\ninclude yt/frontends/artio/artio_headers/artio_selector.c\ninclude yt/frontends/artio/artio_headers/artio_sfc.c\ninclude yt/frontends/artio/artio_headers/cosmology.c\ninclude yt/frontends/artio/artio_headers/cosmology.h\ninclude yt/geometry/vectorized_ops.h\ninclude yt/utilities/lib/_octree_raytracing.hpp\ninclude yt/utilities/lib/cykdtree/c_kdtree.cpp\ninclude yt/utilities/lib/cykdtree/c_kdtree.hpp\ninclude yt/utilities/lib/cykdtree/c_utils.cpp\ninclude yt/utilities/lib/cykdtree/c_utils.hpp\ninclude yt/utilities/lib/cykdtree/windows/stdint.h\ninclude yt/utilities/lib/endian_swap.h\ninclude yt/utilities/lib/fixed_interpolator.cpp\ninclude yt/utilities/lib/fixed_interpolator.hpp\ninclude yt/utilities/lib/marching_cubes.h\ninclude yt/utilities/lib/mesh_triangulation.h\ninclude yt/utilities/lib/origami_tags.c\ninclude yt/utilities/lib/origami_tags.h\ninclude yt/utilities/lib/pixelization_constants.cpp\ninclude yt/utilities/lib/pixelization_constants.hpp\ninclude yt/utilities/lib/platform_dep.h\ninclude yt/utilities/lib/platform_dep_math.hpp\ninclude yt/utilities/lib/tsearch.c\ninclude yt/utilities/lib/tsearch.h\n\ninclude doc/README doc/activate doc/activate.csh doc/cheatsheet.tex\nexclude doc/cheatsheet.pdf\ninclude doc/extensions/README doc/Makefile\nprune doc/source/reference/api/generated\nprune doc/build\nprune .tours\nrecursive-include yt/visualization/volume_rendering/shaders *.fragmentshader *.vertexshader\ninclude yt/sample_data_registry.json\ninclude conftest.py\ninclude yt/py.typed\ninclude yt/default.mplstyle\n\nprune yt/frontends/_skeleton\nrecursive-include yt/frontends/amrvac *.par\nrecursive-exclude requirements *.txt\nexclude minimal_requirements.txt\nexclude .codecov.yml .coveragerc .git-blame-ignore-revs .gitmodules .hgchurn .mailmap\nexclude .pre-commit-config.yaml clean.sh uv.lock\nexclude nose_answer.cfg nose_unit.cfg nose_ignores nose_requirements.txt\n"
  },
  {
    "path": "README.md",
    "content": "# The yt Project\n\n[![PyPI](https://img.shields.io/pypi/v/yt)](https://pypi.org/project/yt)\n[![Supported Python Versions](https://img.shields.io/pypi/pyversions/yt)](https://pypi.org/project/yt/)\n[![Latest Documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)](http://yt-project.org/docs/dev/)\n[![Users' Mailing List](https://img.shields.io/badge/Users-List-lightgrey.svg)](https://mail.python.org/archives/list/yt-users@python.org//)\n[![Devel Mailing List](https://img.shields.io/badge/Devel-List-lightgrey.svg)](https://mail.python.org/archives/list/yt-dev@python.org//)\n[![Data Hub](https://img.shields.io/badge/data-hub-orange.svg)](https://hub.yt/)\n[![Powered by NumFOCUS](https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A)](http://numfocus.org)\n[![Sponsor our Project](https://img.shields.io/badge/donate-to%20yt-blueviolet)](https://numfocus.org/donate-to-yt)\n\n<!--- Tests and style --->\n[![Build and Test](https://github.com/yt-project/yt/actions/workflows/build-test.yaml/badge.svg)](https://github.com/yt-project/yt/actions/workflows/build-test.yaml)\n[![CI (bleeding edge)](https://github.com/yt-project/yt/actions/workflows/bleeding-edge.yaml/badge.svg)](https://github.com/yt-project/yt/actions/workflows/bleeding-edge.yaml)\n[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/yt-project/yt/main.svg)](https://results.pre-commit.ci/latest/github/yt-project/yt/main)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/charliermarsh/ruff)\n\n<!--- [![codecov](https://codecov.io/gh/yt-project/yt/branch/main/graph/badge.svg)](https://codecov.io/gh/yt-project/yt) --->\n\n<a href=\"http://yt-project.org\"><img src=\"https://raw.githubusercontent.com/yt-project/yt/main/doc/source/_static/yt_logo.png\" width=\"300\"></a>\n\nyt is an open-source, permissively-licensed Python library for analyzing and\nvisualizing volumetric data.\n\nyt supports structured, variable-resolution meshes, unstructured meshes, and\ndiscrete or sampled data such as particles. Focused on driving\nphysically-meaningful inquiry, yt has been applied in domains such as\nastrophysics, seismology, nuclear engineering, molecular dynamics, and\noceanography. Composed of a friendly community of users and developers, we want\nto make it easy to use and develop - we'd love it if you got involved!\n\nWe've written a [method\npaper](https://ui.adsabs.harvard.edu/abs/2011ApJS..192....9T) you may be interested\nin; if you use yt in the preparation of a publication, please consider citing\nit.\n\n## Code of Conduct\n\nyt abides by a code of conduct partially modified from the PSF code of conduct,\nand is found [in our contributing\nguide](http://yt-project.org/docs/dev/developing/developing.html#yt-community-code-of-conduct).\n\n## Installation\n\nYou can install the most recent stable version of yt either with conda from\n[conda-forge](https://conda-forge.org/):\n\n```shell\nconda install -c conda-forge yt\n```\n\nor with pip:\n\n```shell\npython -m pip install yt\n```\n\nMore information on the various ways to install yt, and in particular to install from source,\ncan be found on [the project's website](https://yt-project.org/docs/dev/installing.html).\n\n## Getting Started\n\nyt is designed to provide meaningful analysis of data.  We have some Quickstart\nexample notebooks in the repository:\n\n * [Introduction](https://github.com/yt-project/yt/tree/main/doc/source/quickstart/1\\)_Introduction.ipynb)\n * [Data Inspection](https://github.com/yt-project/yt/tree/main/doc/source/quickstart/2\\)_Data_Inspection.ipynb)\n * [Simple Visualization](https://github.com/yt-project/yt/tree/main/doc/source/quickstart/3\\)_Simple_Visualization.ipynb)\n * [Data Objects and Time Series](https://github.com/yt-project/yt/tree/main/doc/source/quickstart/4\\)_Data_Objects_and_Time_Series.ipynb)\n * [Derived Fields and Profiles](https://github.com/yt-project/yt/tree/main/doc/source/quickstart/5\\)_Derived_Fields_and_Profiles.ipynb)\n * [Volume Rendering](https://github.com/yt-project/yt/tree/main/doc/source/quickstart/6\\)_Volume_Rendering.ipynb)\n\nIf you'd like to try these online, you can visit our [yt Hub](https://hub.yt/)\nand run a notebook next to some of our example data.\n\n## Contributing\n\nWe love contributions!  yt is open source, built on open source, and we'd love\nto have you hang out in our community.\n\nWe have developed some [guidelines](CONTRIBUTING.rst) for contributing to yt.\n\n**Imposter syndrome disclaimer**: We want your help. No, really.\n\nThere may be a little voice inside your head that is telling you that you're not\nready to be an open source contributor; that your skills aren't nearly good\nenough to contribute. What could you possibly offer a project like this one?\n\nWe assure you - the little voice in your head is wrong. If you can write code at\nall, you can contribute code to open source. Contributing to open source\nprojects is a fantastic way to advance one's coding skills. Writing perfect code\nisn't the measure of a good developer (that would disqualify all of us!); it's\ntrying to create something, making mistakes, and learning from those\nmistakes. That's how we all improve, and we are happy to help others learn.\n\nBeing an open source contributor doesn't just mean writing code, either. You can\nhelp out by writing documentation, tests, or even giving feedback about the\nproject (and yes - that includes giving feedback about the contribution\nprocess). Some of these contributions may be the most valuable to the project as\na whole, because you're coming to the project with fresh eyes, so you can see\nthe errors and assumptions that seasoned contributors have glossed over.\n\n(This disclaimer was originally written by\n[Adrienne Lowe](https://github.com/adriennefriend) for a\n[PyCon talk](https://www.youtube.com/watch?v=6Uj746j9Heo), and was adapted by yt\nbased on its use in the README file for the\n[MetPy project](https://github.com/Unidata/MetPy))\n\n## Resources\n\nWe have some community and documentation resources available.\n\n * Our latest documentation is always at http://yt-project.org/docs/dev/ and it\n   includes recipes, tutorials, and API documentation\n * The [discussion mailing\n   list](https://mail.python.org/archives/list/yt-users@python.org//)\n   should be your first stop for general questions\n * The [development mailing\n   list](https://mail.python.org/archives/list/yt-dev@python.org//) is\n   better suited for more development issues\n * You can also join us on Slack at yt-project.slack.com ([request an\n   invite](https://yt-project.org/slack.html))\n\nIs your code compatible with yt ? Great ! Please consider giving us a shoutout as a shiny badge in your README\n\n- markdown\n```markdown\n[![yt-project](https://img.shields.io/static/v1?label=\"works%20with\"&message=\"yt\"&color=\"blueviolet\")](https://yt-project.org)\n```\n- rst\n```reStructuredText\n|yt-project|\n\n.. |yt-project| image:: https://img.shields.io/static/v1?label=\"works%20with\"&message=\"yt\"&color=\"blueviolet\"\n   :target: https://yt-project.org\n```\n\n## Powered by NumFOCUS\n\nyt is a fiscally sponsored project of [NumFOCUS](https://numfocus.org/).\nIf you're interested in\nsupporting the active maintenance and development of this project, consider\n[donating to the project](https://numfocus.org/donate-to-yt).\n"
  },
  {
    "path": "clean.sh",
    "content": "#!/usr/bin/env bash\ngit clean -f -d -x yt\n"
  },
  {
    "path": "conftest.py",
    "content": "import os\nimport shutil\nimport sys\nimport tempfile\nfrom importlib.metadata import version\nfrom importlib.util import find_spec\nfrom pathlib import Path\n\nimport pytest\nimport yaml\nfrom packaging.version import Version\n\nfrom yt.config import ytcfg\nfrom yt.utilities.answer_testing.testing_utilities import (\n    _compare_raw_arrays,\n    _hash_results,\n    _save_raw_arrays,\n    _save_result,\n    _streamline_for_io,\n    data_dir_load,\n)\n\nNUMPY_VERSION = Version(version(\"numpy\"))\nPILLOW_VERSION = Version(version(\"Pillow\"))\nMATPLOTLIB_VERSION = Version(version(\"matplotlib\"))\n\n# setuptools does not ship with the standard lib starting in Python 3.12, so we need to\n# be resilient if it's not available at runtime\nif find_spec(\"setuptools\") is not None:\n    SETUPTOOLS_VERSION = Version(version(\"setuptools\"))\nelse:\n    SETUPTOOLS_VERSION = None\n\nif find_spec(\"pandas\") is not None:\n    PANDAS_VERSION = Version(version(\"pandas\"))\nelse:\n    PANDAS_VERSION = None\n\n\ndef pytest_addoption(parser):\n    \"\"\"\n    Lets options be passed to test functions.\n    \"\"\"\n    parser.addoption(\n        \"--with-answer-testing\",\n        action=\"store_true\",\n    )\n    parser.addoption(\n        \"--answer-store\",\n        action=\"store_true\",\n    )\n    parser.addoption(\n        \"--answer-raw-arrays\",\n        action=\"store_true\",\n    )\n    parser.addoption(\n        \"--raw-answer-store\",\n        action=\"store_true\",\n    )\n    parser.addoption(\n        \"--force-overwrite\",\n        action=\"store_true\",\n    )\n    parser.addoption(\n        \"--no-hash\",\n        action=\"store_true\",\n    )\n    parser.addoption(\"--local-dir\", default=None, help=\"Where answers are saved.\")\n    # Tell pytest about the local-dir option in the ini files. This\n    # option is used for creating the answer directory on CI\n    parser.addini(\n        \"local-dir\",\n        default=str(Path(__file__).parent / \"answer-store\"),\n        help=\"answer directory.\",\n    )\n    parser.addini(\n        \"test_data_dir\",\n        default=ytcfg.get(\"yt\", \"test_data_dir\"),\n        help=\"Directory where data for tests is stored.\",\n    )\n\n\ndef pytest_configure(config):\n    r\"\"\"\n    Reads in the tests/tests.yaml file. This file contains a list of\n    each answer test's answer file (including the changeset number).\n    \"\"\"\n    # Register custom marks for answer tests and big data\n    config.addinivalue_line(\"markers\", \"answer_test: Run the answer tests.\")\n    config.addinivalue_line(\n        \"markers\", \"big_data: Run answer tests that require large data files.\"\n    )\n    for value in (\n        # treat most warnings as errors\n        \"error\",\n        # >>> warnings emitted by testing frameworks, or in testing contexts\n        # we still have some yield-based tests, awaiting for transition into pytest\n        \"ignore::pytest.PytestCollectionWarning\",\n        # matplotlib warnings related to the Agg backend which is used in CI, not much we can do about it\n        \"ignore:Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure.:UserWarning\",\n        r\"ignore:tight_layout.+falling back to Agg renderer:UserWarning\",\n        #\n        # >>> warnings from wrong values passed to numpy\n        # these should normally be curated out of the test suite but they are too numerous\n        # to deal with in a reasonable time at the moment.\n        \"ignore:invalid value encountered in log10:RuntimeWarning\",\n        \"ignore:divide by zero encountered in log10:RuntimeWarning\",\n        #\n        # >>> there are many places in yt (most notably at the frontend level)\n        # where we open files but never explicitly close them\n        # Although this is in general bad practice, it can be intentional and\n        # justified in contexts where reading speeds should be optimized.\n        # It is not clear at the time of writing how to approach this,\n        # so I'm going to ignore this class of warnings altogether for now.\n        \"ignore:unclosed file.*:ResourceWarning\",\n    ):\n        config.addinivalue_line(\"filterwarnings\", value)\n\n    if SETUPTOOLS_VERSION is not None and SETUPTOOLS_VERSION >= Version(\"67.3.0\"):\n        # may be triggered by multiple dependencies\n        # see https://github.com/matplotlib/matplotlib/issues/25244\n        config.addinivalue_line(\n            \"filterwarnings\",\n            r\"ignore:(Deprecated call to `pkg_resources\\.declare_namespace\\('.*'\\)`\\.\\n)?\"\n            r\"Implementing implicit namespace packages \\(as specified in PEP 420\\) \"\n            r\"is preferred to `pkg_resources\\.declare_namespace`\\.:DeprecationWarning\",\n        )\n\n    if SETUPTOOLS_VERSION is not None and SETUPTOOLS_VERSION >= Version(\"67.5.0\"):\n        # may be triggered by multiple dependencies\n        # see https://github.com/matplotlib/matplotlib/issues/25244\n        config.addinivalue_line(\n            \"filterwarnings\",\n            \"ignore:pkg_resources is deprecated as an API:DeprecationWarning\",\n        )\n\n    if NUMPY_VERSION >= Version(\"1.25\"):\n        if find_spec(\"h5py\") is not None and (\n            Version(version(\"h5py\")) < Version(\"3.9\")\n        ):\n            # https://github.com/h5py/h5py/pull/2242\n            config.addinivalue_line(\n                \"filterwarnings\",\n                \"ignore:`product` is deprecated as of NumPy 1.25.0:DeprecationWarning\",\n            )\n\n    if PILLOW_VERSION >= Version(\"11.3.0\") and MATPLOTLIB_VERSION <= Version(\"3.10.3\"):\n        # patched upstream: https://github.com/matplotlib/matplotlib/pull/30221\n        config.addinivalue_line(\n            \"filterwarnings\",\n            r\"ignore:'mode' parameter is deprecated:DeprecationWarning\",\n        )\n\n    if PANDAS_VERSION is not None and PANDAS_VERSION >= Version(\"2.2.0\"):\n        config.addinivalue_line(\n            \"filterwarnings\",\n            r\"ignore:\\s*Pyarrow will become a required dependency of pandas:DeprecationWarning\",\n        )\n\n    if sys.version_info >= (3, 12):\n        # already patched (but not released) upstream:\n        # https://github.com/dateutil/dateutil/pull/1285\n        config.addinivalue_line(\n            \"filterwarnings\",\n            r\"ignore:datetime\\.datetime\\.utcfromtimestamp\\(\\) is deprecated:DeprecationWarning\",\n        )\n\n        if find_spec(\"ratarmount\"):\n            # On Python 3.12+, there is a deprecation warning when calling os.fork()\n            # in a multi-threaded process. We use this mechanism to mount archives.\n            config.addinivalue_line(\n                \"filterwarnings\",\n                r\"ignore:This process \\(pid=\\d+\\) is multi-threaded, use of fork\\(\\) \"\n                r\"may lead to deadlocks in the child\\.\"\n                \":DeprecationWarning\",\n            )\n\n    if find_spec(\"datatree\"):\n        # the cf_radial dependency arm-pyart<=1.9.2 installs the now deprecated\n        # xarray-datatree package (which imports as datatree), which triggers\n        # a bunch of runtimewarnings when importing xarray.\n        # https://github.com/yt-project/yt/pull/5042#issuecomment-2457797694\n        config.addinivalue_line(\n            \"filterwarnings\",\n            \"ignore:\" r\"Engine.*loading failed.*\" \":RuntimeWarning\",\n        )\n\n\ndef pytest_collection_modifyitems(config, items):\n    r\"\"\"\n    Decide which tests to skip based on command-line options.\n    \"\"\"\n    # Set up the skip marks\n    skip_answer = pytest.mark.skip(reason=\"--with-answer-testing not set.\")\n    skip_unit = pytest.mark.skip(reason=\"Running answer tests, so skipping unit tests.\")\n    skip_big = pytest.mark.skip(reason=\"--answer-big-data not set.\")\n    # Loop over every collected test function\n    for item in items:\n        # If it's an answer test and the appropriate CL option hasn't\n        # been set, skip it\n        if \"answer_test\" in item.keywords and not config.getoption(\n            \"--with-answer-testing\"\n        ):\n            item.add_marker(skip_answer)\n        # If it's an answer test that requires big data and the CL\n        # option hasn't been set, skip it\n        if (\n            \"big_data\" in item.keywords\n            and not config.getoption(\"--with-answer-testing\")\n            and not config.getoption(\"--answer-big-data\")\n        ):\n            item.add_marker(skip_big)\n        if \"answer_test\" not in item.keywords and config.getoption(\n            \"--with-answer-testing\"\n        ):\n            item.add_marker(skip_unit)\n\n\ndef pytest_itemcollected(item):\n    # Customize pytest-mpl decorator to add sensible defaults\n\n    mpl_marker = item.get_closest_marker(\"mpl_image_compare\")\n    if mpl_marker is not None:\n        # in a future version, pytest-mpl may gain an option for doing this:\n        # https://github.com/matplotlib/pytest-mpl/pull/181\n        mpl_marker.kwargs.setdefault(\"tolerance\", 0.5)\n\n\ndef _param_list(request):\n    r\"\"\"\n    Saves the non-ds, non-fixture function arguments for saving to\n    the answer file.\n    \"\"\"\n    # pytest treats parameterized arguments as fixtures, so there's no\n    # clean way to separate them out from other other fixtures (that I\n    # know of), so we do it explicitly\n    blacklist = [\n        \"hashing\",\n        \"answer_file\",\n        \"request\",\n        \"answer_compare\",\n        \"temp_dir\",\n        \"orbit_traj\",\n        \"etc_traj\",\n    ]\n    test_params = {}\n    for key, val in request.node.funcargs.items():\n        if key not in blacklist:\n            # For plotwindow, the callback arg is a tuple and the second\n            # element contains a memory address, so we need to drop it.\n            # The first element is the callback name, which is all that's\n            # needed\n            if key == \"callback\":\n                val = val[0]\n            test_params[key] = str(val)\n    # Convert python-specific data objects (such as tuples) to a more\n    # io-friendly format (in order to not have python-specific anchors\n    # in the answer yaml file)\n    test_params = _streamline_for_io(test_params)\n    return test_params\n\n\ndef _get_answer_files(request):\n    \"\"\"\n    Gets the path to where the hashed and raw answers are saved.\n    \"\"\"\n    answer_file = f\"{request.cls.__name__}_{request.cls.answer_version}.yaml\"\n    raw_answer_file = f\"{request.cls.__name__}_{request.cls.answer_version}.h5\"\n    # Add the local-dir aspect of the path. If there's a command line value,\n    # have that override the ini file value\n    clLocalDir = request.config.getoption(\"--local-dir\")\n    iniLocalDir = request.config.getini(\"local-dir\")\n    if clLocalDir is not None:\n        answer_file = os.path.join(os.path.expanduser(clLocalDir), answer_file)\n        raw_answer_file = os.path.join(os.path.expanduser(clLocalDir), raw_answer_file)\n    else:\n        answer_file = os.path.join(os.path.expanduser(iniLocalDir), answer_file)\n        raw_answer_file = os.path.join(os.path.expanduser(iniLocalDir), raw_answer_file)\n    # Make sure we don't overwrite unless we mean to\n    overwrite = request.config.getoption(\"--force-overwrite\")\n    storing = request.config.getoption(\"--answer-store\")\n    raw_storing = request.config.getoption(\"--raw-answer-store\")\n    raw = request.config.getoption(\"--answer-raw-arrays\")\n    if os.path.exists(answer_file) and storing and not overwrite:\n        raise FileExistsError(\n            \"Use `--force-overwrite` to overwrite an existing answer file.\"\n        )\n    if os.path.exists(raw_answer_file) and raw_storing and raw and not overwrite:\n        raise FileExistsError(\n            \"Use `--force-overwrite` to overwrite an existing raw answer file.\"\n        )\n    # If we do mean to overwrite, do so here by deleting the original file\n    if os.path.exists(answer_file) and storing and overwrite:\n        os.remove(answer_file)\n    if os.path.exists(raw_answer_file) and raw_storing and raw and overwrite:\n        os.remove(raw_answer_file)\n    print(os.path.abspath(answer_file))\n    return answer_file, raw_answer_file\n\n\n@pytest.fixture(scope=\"function\")\ndef hashing(request):\n    r\"\"\"\n    Handles initialization, generation, and saving of answer test\n    result hashes.\n    \"\"\"\n    no_hash = request.config.getoption(\"--no-hash\")\n    store_hash = request.config.getoption(\"--answer-store\")\n    raw = request.config.getoption(\"--answer-raw-arrays\")\n    raw_store = request.config.getoption(\"--raw-answer-store\")\n    # This check is so that, when checking if the answer file exists in\n    # _get_answer_files, we don't continuously fail. With this check,\n    # _get_answer_files is called once per class, despite this having function\n    # scope\n    if request.cls.answer_file is None:\n        request.cls.answer_file, request.cls.raw_answer_file = _get_answer_files(\n            request\n        )\n    if not no_hash and not store_hash and request.cls.saved_hashes is None:\n        try:\n            with open(request.cls.answer_file) as fd:\n                request.cls.saved_hashes = yaml.safe_load(fd)\n        except FileNotFoundError:\n            module_filename = f\"{request.function.__module__.replace('.', os.sep)}.py\"\n            with open(f\"generate_test_{os.getpid()}.txt\", \"a\") as fp:\n                fp.write(f\"{module_filename}::{request.cls.__name__}\\n\")\n            pytest.fail(msg=\"Answer file not found.\", pytrace=False)\n    request.cls.hashes = {}\n    # Load the saved answers if we're comparing. We don't do this for the raw\n    # answers because those are huge\n    yield\n    # Get arguments and their values passed to the test (e.g., axis, field, etc.)\n    params = _param_list(request)\n    # Hash the test results. Don't save to request.cls.hashes so we still have\n    # raw data, in case we want to work with that\n    hashes = _hash_results(request.cls.hashes)\n    # Add the other test parameters\n    hashes.update(params)\n    # Add the function name as the \"master\" key to the hashes dict\n    hashes = {request.node.name: hashes}\n    # Save hashes\n    if not no_hash and store_hash:\n        _save_result(hashes, request.cls.answer_file)\n    # Compare hashes\n    elif not no_hash and not store_hash:\n        try:\n            for test_name, test_hash in hashes.items():\n                assert test_name in request.cls.saved_hashes\n                assert test_hash == request.cls.saved_hashes[test_name]\n        except AssertionError:\n            pytest.fail(f\"Comparison failure: {request.node.name}\", pytrace=False)\n    # Save raw data\n    if raw and raw_store:\n        _save_raw_arrays(\n            request.cls.hashes, request.cls.raw_answer_file, request.node.name\n        )\n    # Compare raw data. This is done one test at a time because the\n    # arrays can get quite large and storing everything in memory would\n    # be bad\n    if raw and not raw_store:\n        _compare_raw_arrays(\n            request.cls.hashes, request.cls.raw_answer_file, request.node.name\n        )\n\n\n@pytest.fixture(scope=\"function\")\ndef temp_dir():\n    r\"\"\"\n    Creates a temporary directory needed by certain tests.\n    \"\"\"\n    curdir = os.getcwd()\n    if int(os.environ.get(\"GENERATE_YTDATA\", 0)):\n        tmpdir = os.getcwd()\n    else:\n        tmpdir = tempfile.mkdtemp()\n    os.chdir(tmpdir)\n    yield tmpdir\n    os.chdir(curdir)\n    if tmpdir != curdir:\n        shutil.rmtree(tmpdir)\n\n\n@pytest.fixture(scope=\"class\")\ndef ds(request):\n    # data_dir_load can take the cls, args, and kwargs. These optional\n    # arguments, if present,  are given in a dictionary as the second\n    # element of the list\n    if isinstance(request.param, str):\n        ds_fn = request.param\n        opts = {}\n    else:\n        ds_fn, opts = request.param\n    try:\n        return data_dir_load(\n            ds_fn, cls=opts.get(\"cls\"), args=opts.get(\"args\"), kwargs=opts.get(\"kwargs\")\n        )\n    except FileNotFoundError:\n        return pytest.skip(f\"Data file: `{request.param}` not found.\")\n\n\n@pytest.fixture(scope=\"class\")\ndef field(request):\n    \"\"\"\n    Fixture for returning the field. Needed because indirect=True is\n    used for loading the datasets.\n    \"\"\"\n    return request.param\n\n\n@pytest.fixture(scope=\"class\")\ndef dobj(request):\n    \"\"\"\n    Fixture for returning the ds_obj. Needed because indirect=True is\n    used for loading the datasets.\n    \"\"\"\n    return request.param\n\n\n@pytest.fixture(scope=\"class\")\ndef axis(request):\n    \"\"\"\n    Fixture for returning the axis. Needed because indirect=True is\n    used for loading the datasets.\n    \"\"\"\n    return request.param\n\n\n@pytest.fixture(scope=\"class\")\ndef weight(request):\n    \"\"\"\n    Fixture for returning the weight_field. Needed because\n    indirect=True is used for loading the datasets.\n    \"\"\"\n    return request.param\n\n\n@pytest.fixture(scope=\"class\")\ndef ds_repr(request):\n    \"\"\"\n    Fixture for returning the string representation of a dataset.\n    Needed because indirect=True is used for loading the datasets.\n    \"\"\"\n    return request.param\n\n\n@pytest.fixture(scope=\"class\")\ndef Npart(request):\n    \"\"\"\n    Fixture for returning the number of particles in a dataset.\n    Needed because indirect=True is used for loading the datasets.\n    \"\"\"\n    return request.param\n"
  },
  {
    "path": "doc/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html        to make standalone HTML files\"\n\t@echo \"  dirhtml     to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml  to make a single large HTML file\"\n\t@echo \"  pickle      to make pickle files\"\n\t@echo \"  json        to make JSON files\"\n\t@echo \"  htmlhelp    to make HTML files and a HTML help project\"\n\t@echo \"  qthelp      to make HTML files and a qthelp project\"\n\t@echo \"  devhelp     to make HTML files and a Devhelp project\"\n\t@echo \"  epub        to make an epub\"\n\t@echo \"  latex       to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf    to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text        to make text files\"\n\t@echo \"  man         to make manual pages\"\n\t@echo \"  changes     to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck   to check all external links for integrity\"\n\t@echo \"  doctest     to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  clean \t     to remove the build directory\"\n\t@echo \"  recipeclean to remove files produced by running the cookbook scripts\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\t-rm -rf source/reference/api/yt.*\n\t-rm -rf source/reference/api/modules.rst\n\nfullclean: clean\n\nrecipeclean:\n\t-rm -rf _temp/*.done source/cookbook/_static/*\n\nhtml:\nifneq ($(READTHEDOCS),True)\n\tSPHINX_APIDOC_OPTIONS=members,undoc-members,inherited-members,show-inheritance sphinx-apidoc \\\n        -o source/reference/api/ \\\n        -e ../yt $(shell find ../yt -name \"*tests*\" -type d) ../yt/utilities/voropp* ../yt/analysis_modules/*\nendif\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/yt.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/yt.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/yt\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/yt\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\tmake -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "doc/README",
    "content": "This directory contains the uncompiled yt documentation.  It's written to be\nused with Sphinx, a tool designed for writing Python documentation.  Sphinx is\navailable at this URL:\n\nhttp://www.sphinx-doc.org/en/master/\n\nBecause the documentation requires a number of dependencies, we provide\npre-built versions online, accessible here:\n\nhttps://yt-project.org/docs/dev/\n"
  },
  {
    "path": "doc/activate",
    "content": "### Adapted from virtualenv's activate script\n\n# This file must be used with \"source bin/activate\" *from bash*\n# you cannot run it directly\n\ndeactivate () {\n    # reset old environment variables\n    if [ -n \"$_OLD_VIRTUAL_PATH\" ] ; then\n        PATH=\"$_OLD_VIRTUAL_PATH\"\n        export PATH\n        unset _OLD_VIRTUAL_PATH\n    fi\n    if [ -n \"$_OLD_VIRTUAL_PYTHONHOME\" ] ; then\n        PYTHONHOME=\"$_OLD_VIRTUAL_PYTHONHOME\"\n        export PYTHONHOME\n        unset _OLD_VIRTUAL_PYTHONHOME\n    fi\n\n    ### Begin extra yt vars\n    if [ -n \"$_OLD_VIRTUAL_YT_DEST\" ] ; then\n        YT_DEST=\"$_OLD_VIRTUAL_YT_DEST\"\n        export YT_DEST\n        unset _OLD_VIRTUAL_PYTHONHOME\n    fi\n    if [ -n \"$_OLD_VIRTUAL_PYTHONPATH\" ] ; then\n        PYTHONPATH=\"$_OLD_VIRTUAL_PYTHONPATH\"\n        export PYTHONPATH\n        unset _OLD_VIRTUAL_PYTHONPATH\n    fi\n    if [ -n \"$_OLD_VIRTUAL_LD_LIBRARY_PATH\" ] ; then\n        LD_LIBRARY_PATH=\"$_OLD_VIRTUAL_LD_LIBRARY_PATH\"\n        export LD_LIBRARY_PATH\n        unset _OLD_VIRTUAL_LD_LIBRARY_PATH\n    fi\n    ### End extra yt vars\n\n    # This should detect bash and zsh, which have a hash command that must\n    # be called to get it to forget past commands.  Without forgetting\n    # past commands the $PATH changes we made may not be respected\n    if [ -n \"$BASH\" -o -n \"$ZSH_VERSION\" ] ; then\n        hash -r\n    fi\n\n    if [ -n \"$_OLD_VIRTUAL_PS1\" ] ; then\n        PS1=\"$_OLD_VIRTUAL_PS1\"\n        export PS1\n        unset _OLD_VIRTUAL_PS1\n    fi\n\n    unset VIRTUAL_ENV\n    if [ ! \"$1\" = \"nondestructive\" ] ; then\n    # Self destruct!\n        unset -f deactivate\n    fi\n}\n\n# unset irrelevant variables\ndeactivate nondestructive\n\nVIRTUAL_ENV=\"__YT_DIR__\"\nexport VIRTUAL_ENV\n\n_OLD_VIRTUAL_PATH=\"$PATH\"\nPATH=\"$VIRTUAL_ENV/bin:$PATH\"\nexport PATH\n\n### Begin extra env vars for yt\n_OLD_VIRTUAL_YT_DEST=\"$YT_DEST\"\nYT_DEST=\"$VIRTUAL_ENV\"\nexport YT_DEST\n\n_OLD_VIRTUAL_PYTHONPATH=\"$PYTHONPATH\"\n\n_OLD_VIRTUAL_LD_LIBRARY_PATH=\"$LD_LIBRARY_PATH\"\nLD_LIBRARY_PATH=\"$VIRTUAL_ENV/lib:$LD_LIBRARY_PATH\"\nexport LD_LIBRARY_PATH\n### End extra env vars for yt\n\n# unset PYTHONHOME if set\n# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)\n# could use `if (set -u; : $PYTHONHOME) ;` in bash\nif [ -n \"$PYTHONHOME\" ] ; then\n    _OLD_VIRTUAL_PYTHONHOME=\"$PYTHONHOME\"\n    unset PYTHONHOME\nfi\n\nif [ -z \"$VIRTUAL_ENV_DISABLE_PROMPT\" ] ; then\n    _OLD_VIRTUAL_PS1=\"$PS1\"\n    if [ \"x\" != x ] ; then\n\tPS1=\"$PS1\"\n    else\n    if [ \"`basename \\\"$VIRTUAL_ENV\\\"`\" = \"__\" ] ; then\n        # special case for Aspen magic directories\n        # see http://www.zetadev.com/software/aspen/\n        PS1=\"[`basename \\`dirname \\\"$VIRTUAL_ENV\\\"\\``] $PS1\"\n    else\n        PS1=\"(`basename \\\"$VIRTUAL_ENV\\\"`)$PS1\"\n    fi\n    fi\n    export PS1\nfi\n\n# This should detect bash and zsh, which have a hash command that must\n# be called to get it to forget past commands.  Without forgetting\n# past commands the $PATH changes we made may not be respected\nif [ -n \"$BASH\" -o -n \"$ZSH_VERSION\" ] ; then\n    hash -r\nfi\n"
  },
  {
    "path": "doc/activate.csh",
    "content": "# This file must be used with \"source bin/activate.csh\" *from csh*.\n# You cannot run it directly.\n# Created by Davide Di Blasi <davidedb@gmail.com>.\n\nalias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH \"$_OLD_VIRTUAL_PATH\" && unset _OLD_VIRTUAL_PATH; test $?_OLD_VIRTUAL_YT_DEST != 0 && setenv YT_DEST \"$_OLD_VIRTUAL_YT_DEST\" && unset _OLD_VIRTUAL_YT_DEST; test $?_OLD_VIRTUAL_PYTHONPATH != 0 && setenv PYTHONPATH \"$_OLD_VIRTUAL_PYTHONPATH\" && unset _OLD_VIRTUAL_PYTHONPATH; test $?_OLD_VIRTUAL_LD_LIBRARY_PATH != 0 && setenv LD_LIBRARY_PATH \"$_OLD_VIRTUAL_LD_LIBRARY_PATH\" && unset _OLD_VIRTUAL_LD_LIBRARY_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt=\"$_OLD_VIRTUAL_PROMPT\" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test \"\\!:*\" != \"nondestructive\" && unalias deactivate'\n\n# Unset irrelevant variables.\ndeactivate nondestructive\n\nsetenv VIRTUAL_ENV \"__YT_DIR__\"\n\nif ($?PATH == 0) then\n    setenv PATH\nendif\nset _OLD_VIRTUAL_PATH=\"$PATH\"\nsetenv PATH \"${VIRTUAL_ENV}/bin:${PATH}\"\n\n### Begin extra yt vars\nif ($?YT_DEST == 0) then\n    setenv YT_DEST\nendif\nset _OLD_VIRTUAL_YT_DEST=\"$YT_DEST\"\nsetenv YT_DEST \"${VIRTUAL_ENV}\"\n\nif ($?PYTHONPATH == 0) then\n    setenv PYTHONPATH\nendif\nset _OLD_VIRTUAL_PYTHONPATH=\"$PYTHONPATH\"\nsetenv PYTHONPATH \"${VIRTUAL_ENV}/lib/python2.7/site-packages:${PYTHONPATH}\"\n\nif ($?LD_LIBRARY_PATH == 0) then\n    setenv LD_LIBRARY_PATH\nendif\nset _OLD_VIRTUAL_LD_LIBRARY_PATH=\"$LD_LIBRARY_PATH\"\nsetenv LD_LIBRARY_PATH \"${VIRTUAL_ENV}/lib:${LD_LIBRARY_PATH}\"\n### End extra yt vars\n\nset _OLD_VIRTUAL_PROMPT=\"$prompt\"\n\nif (\"\" != \"\") then\n    set env_name = \"\"\nelse\n    if (`basename \"$VIRTUAL_ENV\"` == \"__\") then\n        # special case for Aspen magic directories\n        # see http://www.zetadev.com/software/aspen/\n        set env_name = `basename \\`dirname \"$VIRTUAL_ENV\"\\``\n    else\n        set env_name = `basename \"$VIRTUAL_ENV\"`\n    endif\nendif\nset prompt = \"[$env_name] $prompt\"\nunset env_name\n\nrehash\n"
  },
  {
    "path": "doc/cheatsheet.tex",
    "content": "\\documentclass[10pt,landscape]{article}\n\\usepackage{multicol}\n\\usepackage{calc}\n\\usepackage{ifthen}\n\\usepackage[landscape]{geometry}\n\\usepackage[hyphens]{url}\n\n% To make this come out properly in landscape mode, do one of the following\n% 1.\n%  pdflatex cheatsheet.tex\n%\n% 2.\n%  latex cheatsheet.tex\n%  dvips -P pdf  -t landscape cheatsheet.dvi\n%  ps2pdf cheatsheet.ps\n\n\n% If you're reading this, be prepared for confusion.  Making this was\n% a learning experience for me, and it shows.  Much of the placement\n% was hacked in; if you make it better, let me know...\n\n\n% 2008-04\n% Changed page margin code to use the geometry package. Also added code for\n% conditional page margins, depending on paper size. Thanks to Uwe Ziegenhagen\n% for the suggestions.\n\n% 2006-08\n% Made changes based on suggestions from Gene Cooperman. <gene at ccs.neu.edu>\n\n% 2012-11 - Stephen Skory\n% Converted the latex cheat sheet to a yt cheat sheet, taken from\n% http://www.stdout.org/~winston/latex/\n\n\n% This sets page margins to .5 inch if using letter paper, and to 1cm\n% if using A4 paper. (This probably isn't strictly necessary.)\n% If using another size paper, use default 1cm margins.\n\\ifthenelse{\\lengthtest { \\paperwidth = 11in}}\n\t{ \\geometry{top=.5in,left=.5in,right=.5in,bottom=0.85in} }\n\t{\\ifthenelse{ \\lengthtest{ \\paperwidth = 297mm}}\n\t\t{\\geometry{top=1cm,left=1cm,right=1cm,bottom=1cm} }\n\t\t{\\geometry{top=1cm,left=1cm,right=1cm,bottom=1cm} }\n\t}\n\n% Turn off header and footer\n\\pagestyle{empty}\n\n\n% Redefine section commands to use less space\n\\makeatletter\n\\renewcommand{\\section}{\\@startsection{section}{1}{0mm}%\n                                {-1ex plus -.5ex minus -.2ex}%\n                                {0.5ex plus .2ex}%x\n                                {\\normalfont\\large\\bfseries}}\n\\renewcommand{\\subsection}{\\@startsection{subsection}{2}{0mm}%\n                                {-1explus -.5ex minus -.2ex}%\n                                {0.5ex plus .2ex}%\n                                {\\normalfont\\normalsize\\bfseries}}\n\\renewcommand{\\subsubsection}{\\@startsection{subsubsection}{3}{0mm}%\n                                {-1ex plus -.5ex minus -.2ex}%\n                                {1ex plus .2ex}%\n                                {\\normalfont\\small\\bfseries}}\n\\makeatother\n\n% Define BibTeX command\n\\def\\BibTeX{{\\rm B\\kern-.05em{\\sc i\\kern-.025em b}\\kern-.08em\n    T\\kern-.1667em\\lower.7ex\\hbox{E}\\kern-.125emX}}\n\n% Don't print section numbers\n\\setcounter{secnumdepth}{0}\n\n\n\\setlength{\\parindent}{0pt}\n\\setlength{\\parskip}{0pt plus 0.5ex}\n\n\n% -----------------------------------------------------------------------\n\n\\begin{document}\n\n\\raggedright\n\\fontsize{3mm}{3mm}\\selectfont\n\\begin{multicols}{3}\n\n\n% multicol parameters\n% These lengths are set only within the two main columns\n%\\setlength{\\columnseprule}{0.25pt}\n\\setlength{\\premulticols}{1pt}\n\\setlength{\\postmulticols}{1pt}\n\\setlength{\\multicolsep}{1pt}\n\\setlength{\\columnsep}{2pt}\n\n\\begin{center}\n     \\Large{\\textbf{yt Cheat Sheet}} \\\\\n\\end{center}\n\n\\subsection{General Info}\nFor everything yt please see \\url{http://yt-project.org}.\nDocumentation \\url{http://yt-project.org/doc/index.html}.\nNeed help? Start here \\url{http://yt-project.org/doc/help/} and then\ntry the IRC chat room \\url{http://yt-project.org/irc.html},\nor the mailing list \\url{https://mail.python.org/archives/list/yt-users@python.org/}. \\\\\n\n\\subsection{Installing yt} The easiest way to install yt is to use the\ninstallation script found on the yt homepage or the docs linked above.  If you\nalready have python set up with \\texttt{numpy}, \\texttt{scipy},\n\\texttt{matplotlib}, \\texttt{h5py}, and \\texttt{cython}, you can also use\n\\texttt{pip install yt}\n\n\\subsection{Command Line yt}\nyt, and its convenience functions, are launched from a command line prompt.\nMany commands have flags to control behavior.\nCommands can be followed by\n{\\bf {-}{-}help} (e.g. {\\bf yt render {-}{-}help}) for detailed help for that command\nincluding a list of the available flags.\n\n\\texttt{yt load} \\textit{dataset}   \\textemdash\\ Load a single dataset.  \\\\\n\\texttt{yt help} \\textemdash\\ Print yt help information. \\\\\n\\texttt{yt stats} \\textit{dataset} \\textemdash\\ Print stats of a dataset. \\\\\n\\texttt{yt update} \\textemdash\\ Update yt to most recent version.\\\\\n\\texttt{yt update --all} \\textemdash\\ Update yt and dependencies to most recent version. \\\\\n\\texttt{yt version} \\textemdash\\ yt installation information. \\\\\n\\texttt{yt upload\\_image} \\textit{image.png} \\textemdash\\ Upload PNG image to imgur.com. \\\\\n\\texttt{yt upload\\_notebook} \\textit{notebook.nb} \\textemdash\\ Upload IPython notebook to \\url{https://girder.hub.yt}.\\\\\n\\texttt{yt plot} \\textit{dataset} \\textemdash\\ Create a set of images.\\\\\n\\texttt{yt render} \\textit{dataset} \\textemdash\\ Create a simple\n volume rendering. \\\\\n\\texttt{yt mapserver} \\textit{dataset} \\textemdash\\ View a plot/projection in a Gmaps-like\n interface. \\\\\n\\texttt{yt pastebin} \\textit{text.out} \\textemdash\\ Post text to the pastebin at\n paste.yt-project.org. \\\\\n\\texttt{yt pastebin\\_grab} \\textit{identifier} \\textemdash\\ Print content of pastebin to\n STDOUT. \\\\\n\\texttt{yt bugreport} \\textemdash\\ Report a yt bug. \\\\\n\\texttt{yt hop} \\textit{dataset} \\textemdash\\  Run hop on a dataset. \\\\\n\n\\subsection{yt Imports}\nIn order to use yt, Python must load the relevant yt modules into memory.\nThe import commands are entered in the Python/IPython shell or\nused as part of a script.\n\\newlength{\\MyLen}\n\\settowidth{\\MyLen}{\\texttt{letterpaper}/\\texttt{a4paper} \\ }\n\\texttt{import yt}  \\textemdash\\\nLoad yt. \\\\\n\\texttt{from yt.config import ytcfg}  \\textemdash\\\nUsed to set yt configuration options.\nIf used, must be called before importing any other module.\\\\\n\\texttt{from yt.analysis\\_modules.\\emph{halo\\_finding}.api import \\textasteriskcentered}  \\textemdash\\\nLoad halo finding modules. Other modules\nare loaded in a similar way by swapping the\n\\emph{emphasized} text.\nSee the \\textbf{Analysis Modules} section for a listing and short descriptions of each.\n\n\\subsection{YTArray}\nSimulation data in yt is returned as a YTArray.  YTArray is a numpy array that\nhas unit data attached to it and can automatically handle unit conversions and\ndetect unit errors. Just like a numpy array, YTArray provides a wealth of\nbuilt-in functions to calculate properties of the data in the array. Here is a\nvery brief list of some useful ones.\n\\settowidth{\\MyLen}{\\texttt{multicol} }\\\\\n\\texttt{v = a.in\\_cgs()} \\textemdash\\ Return the array in CGS units \\\\\n\\texttt{v = a.in\\_units('Msun/pc**3')} \\textemdash\\ Return the array in solar masses per cubic parsec \\\\\n\\texttt{v = a.max(), a.min()} \\textemdash\\ Return maximum, minimum of \\texttt{a}. \\\\\n\\texttt{index = a.argmax(), a.argmin()} \\textemdash\\ Return index of max,\nmin value of \\texttt{a}.\\\\\n\\texttt{v = a[}\\textit{index}\\texttt{]} \\textemdash\\ Select a single value from \\texttt{a} at location \\textit{index}.\\\\\n\\texttt{b = a[}\\textit{i:j}\\texttt{]} \\textemdash\\ Select the slice of values from\n\\texttt{a} between\nlocations \\textit{i} to \\textit{j-1} saved to a new Numpy array \\texttt{b} with length \\textit{j-i}. \\\\\n\\texttt{sel = (a > const)} \\textemdash\\ Create a new boolean Numpy array\n\\texttt{sel}, of the same shape as \\texttt{a},\nthat marks which values of \\texttt{a > const}. Other operators (e.g. \\textless, !=, \\%) work as well.\\\\\n\\texttt{b = a[sel]} \\textemdash\\ Create a new Numpy array \\texttt{b} made up of\nelements from \\texttt{a} that correspond to elements of \\texttt{sel}\nthat are \\textit{True}. In the above example \\texttt{b} would be all elements of \\texttt{a} that are greater than \\texttt{const}.\\\\\n\\texttt{a.write\\_hdf5(\\textit{filename.h5})} \\textemdash\\ Save \\texttt{a} to the hdf5 file \\textit{filename.h5}.\\\\\n\n\\subsection{IPython Tips}\n\\settowidth{\\MyLen}{\\texttt{multicol} }\nThese tips work if IPython has been loaded, typically either by invoking\n\\texttt{yt load} on the command line.\n\\texttt{Tab complete} \\textemdash\\ IPython will attempt to auto-complete a\nvariable or function name when the \\texttt{Tab} key is pressed, e.g. \\textit{HaloFi}\\textendash\\texttt{Tab} would auto-complete\nto \\textit{HaloFinder}. This also works with imports, e.g. \\textit{from numpy.random.}\\textendash\\texttt{Tab}\nwould give you a list of random functions (note the trailing period before hitting \\texttt{Tab}).\\\\\n\\texttt{?, ??} \\textemdash\\ Appending one or two question marks at the end of any object gives you\ndetailed information about it, e.g. \\textit{variable\\_name}?.\\\\\nBelow a few IPython ``magics'' are listed, which are IPython-specific shortcut commands.\\\\\n\\texttt{\\%paste} \\textemdash\\ Paste content from the system clipboard into the IPython shell.\\\\\n\\texttt{\\%hist} \\textemdash\\ Print recent command history.\\\\\n\\texttt{\\%quickref} \\textemdash\\ Print IPython quick reference.\\\\\n\\texttt{\\%pdb} \\textemdash\\ Automatically enter the Python debugger at an exception.\\\\\n\\texttt{\\%debug} \\textemdash\\ Drop into a debugger at the location of the last unhandled exception. \\\\\n\\texttt{\\%time, \\%timeit} \\textemdash\\ Find running time of expressions for benchmarking.\\\\\n\\texttt{\\%lsmagic} \\textemdash\\ List all available IPython magics. Hint: \\texttt{?} works with magics.\\\\\n\n\nPlease see \\url{http://ipython.org/documentation.html} for the full\nIPython documentation.\n\n\\subsection{Load and Access Data}\nThe first step in using yt is to reference a simulation snapshot.\nAfter that, simulation data is generally accessed in yt using \\textit{Data Containers} which are Python objects\nthat define a region of simulation space from which data should be selected.\n\\settowidth{\\MyLen}{\\texttt{multicol} }\n\\texttt{ds = yt.load(}\\textit{dataset}\\texttt{)} \\textemdash\\   Reference a single snapshot.\\\\\n\\texttt{dd = ds.all\\_data()} \\textemdash\\ Select the entire volume.\\\\\n\\texttt{a = dd[}\\textit{field\\_name}\\texttt{]} \\textemdash\\ Copies the contents of \\textit{field} into the\nYTArray \\texttt{a}. Similarly for other data containers.\\\\\n\\texttt{ds.field\\_list} \\textemdash\\ A list of available fields in the snapshot. \\\\\n\\texttt{ds.derived\\_field\\_list} \\textemdash\\ A list of available derived fields\nin the snapshot. \\\\\n\\texttt{val, loc = ds.find\\_max(\"Density\")} \\textemdash\\ Find the \\texttt{val}ue of\nthe maximum of the field \\texttt{Density} and its \\texttt{loc}ation. \\\\\n\\texttt{sp = ds.sphere(}\\textit{cen}\\texttt{,}\\textit{radius}\\texttt{)} \\textemdash\\   Create a spherical data\ncontainer. \\textit{cen} may be a coordinate, or ``max'' which\ncenters on the max density point. \\textit{radius} may be a float in\ncode units or a tuple of (\\textit{length, unit}).\\\\\n\n\\texttt{re = ds.region(\\textit{cen}, \\textit{left edge}, \\textit{right edge})} \\textemdash\\ Create a\nrectilinear data container. \\textit{cen} is required but not used.\n\\textit{left} and \\textit{right edge} are coordinate values that define the region.\n\n\\texttt{di = ds.disk(\\textit{cen}, \\textit{normal}, \\textit{radius}, \\textit{height})} \\textemdash\\\nCreate a cylindrical data container centered at \\textit{cen} along the\ndirection set by \\textit{normal},with total length\n 2$\\times$\\textit{height} and with radius \\textit{radius}. \\\\\n\n\\texttt{ds.save\\_object(sp, \\textit{``sp\\_for\\_later''})} \\textemdash\\ Save an object (\\texttt{sp}) for later use.\\\\\n\\texttt{sp = ds.load\\_object(\\textit{``sp\\_for\\_later''})} \\textemdash\\ Recover a saved object.\\\\\n\n\n\\subsection{Defining New Fields}\n\\texttt{yt} expects on-disk fields, fields generated on-demand and in-memory.\nField can either be created before a dataset is loaded using \\texttt{add\\_field}:\n\\texttt{def \\_metal\\_mass(\\textit{field},\\textit{data})}\\\\\n\\texttt{\\hspace{4 mm} return data[\"metallicity\"]*data[\"cell\\_mass\"]}\\\\\n\\texttt{add\\_field(\"metal\\_mass\", units='g', function=\\_metal\\_mass)}\\\\\nOr added to an existing dataset using \\texttt{ds.add\\_field}:\n\\texttt{ds.add\\_field(\"metal\\_mass\", units='g', function=\\_metal\\_mass)}\\\\\n\n\\subsection{Slices and Projections}\n\\settowidth{\\MyLen}{\\texttt{multicol} }\n\\texttt{slc = yt.SlicePlot(ds, \\textit{axis or normal vector}, \\textit{fields}, \\textit{center=}, \\textit{width=}, \\textit{weight\\_field=}, \\textit{additional parameters})} \\textemdash\\ Make a slice plot\nperpendicular to \\textit{axis} (specified via 'x', 'y', or 'z') or a normal vector for an off-axis slice of \\textit{fields} weighted by \\textit{weight\\_field} at (code-units) \\textit{center} with\n\\textit{width} in code units or a (value, unit) tuple. Hint: try \\textit{yt.SlicePlot?} in IPython to see additional parameters.\\\\\n\\texttt{slc.save(\\textit{file\\_prefix})} \\textemdash\\ Save the slice to a png with name prefix \\textit{file\\_prefix}.\n\\texttt{.save()} works similarly for the commands below.\\\\\n\n\\texttt{prj = yt.ProjectionPlot(ds, \\textit{axis or normal vector}, \\textit{fields}, \\textit{additional params})} \\textemdash\\ Same as \\texttt{yt.SlicePlot} but for projections.\\\\\n\n\\subsection{Plot Annotations}\n\\settowidth{\\MyLen}{\\texttt{multicol} }\nPlot callbacks are functions itemized in a registry that is attached to every plot object. They can be accessed and then called like \\texttt{ prj.annotate\\_velocity(factor=16, normalize=False)}. Most callbacks also accept a \\textit{plot\\_args} dict that is fed to matplotlib annotator. \\\\\n\\texttt{velocity(\\textit{factor=},\\textit{scale=},\\textit{scale\\_units=}, \\textit{normalize=})} \\textemdash\\ Uses field \"x-velocity\" to draw quivers\\\\\n\\texttt{magnetic\\_field(\\textit{factor=},\\textit{scale=},\\textit{scale\\_units=}, \\textit{normalize=})} \\textemdash\\ Uses field \"Bx\" to draw quivers\\\\\n\\texttt{quiver(\\textit{field\\_x},\\textit{field\\_y},\\textit{factor=},\\textit{scale=},\\textit{scale\\_units=}, \\textit{normalize=})} \\\\\n\\texttt{contour(\\textit{field=},\\textit{levels=},\\textit{factor=},\\textit{clim=},\\textit{take\\_log=}, \\textit{additional parameters})} \\textemdash Plots a number of contours \\textit{ncont} to interpolate \\textit{field} optionally using \\textit{take\\_log}, upper and lower \\textit{c}ontour\\textit{lim}its and \\textit{factor} number of points in the interpolation.\\\\\n\\texttt{grids(\\textit{alpha=}, \\textit{draw\\_ids=}, \\textit{periodic=}, \\textit{min\\_level=}, \\textit{max\\_level=})} \\textemdash Add grid boundaries. \\\\\n\\texttt{streamlines(\\textit{field\\_x},\\textit{field\\_y},\\textit{factor=},\\textit{density=})}\\\\\n\\texttt{clumps(\\textit{clumplist})} \\textemdash\\ Generate \\textit{clumplist} using the clump finder and plot. \\\\\n\\texttt{arrow(\\textit{pos}, \\textit{code\\_size})} Add an arrow at a \\textit{pos}ition. \\\\\n\\texttt{point(\\textit{pos}, \\textit{text})} \\textemdash\\ Add text at a \\textit{pos}ition. \\\\\n\\texttt{marker(\\textit{pos}, \\textit{marker=})} \\textemdash\\ Add a matplotlib-defined marker at a \\textit{pos}ition. \\\\\n\\texttt{sphere(\\textit{center}, \\textit{radius}, \\textit{text=})} \\textemdash\\ Draw a circle and append \\textit{text}.\\\\\n\\texttt{hop\\_circles(\\textit{hop\\_output}, \\textit{max\\_number=}, \\textit{annotate=}, \\textit{min\\_size=}, \\textit{max\\_size=}, \\textit{font\\_size=}, \\textit{print\\_halo\\_size=}, \\textit{fixed\\_radius=}, \\textit{min\\_mass=}, \\textit{print\\_halo\\_mass=}, \\textit{width=})} \\textemdash\\ Draw a halo, printing it's ID, mass, clipping halos depending on number of particles (\\textit{size}) and optionally fixing the drawn circle radius to be constant for all halos.\\\\\n\\texttt{hop\\_particles(\\textit{hop\\_output},\\textit{max\\_number=},\\textit{p\\_size=},\\\\\n\\textit{min\\_size},\\textit{alpha=})} \\textemdash\\ Draw particle positions for member halos with a certain number of pixels per particle.\\\\\n\\texttt{particles(\\textit{width},\\textit{p\\_size=},\\textit{col=}, \\textit{marker=}, \\textit{stride=}, \\textit{ptype=}, \\textit{stars\\_only=}, \\textit{dm\\_only=}, \\textit{minimum\\_mass=}, \\textit{alpha=})}  \\textemdash\\  Draw particles of \\textit{p\\_size} pixels in a slab of \\textit{width} with \\textit{col}or using a matplotlib \\textit{marker} plotting only every \\textit{stride} number of particles.\\\\\n\\texttt{title(\\textit{text})}\\\\\n\n\\subsection{The $\\sim$/.yt/ Directory}\n\\settowidth{\\MyLen}{\\texttt{multicol} }\nyt will automatically check for configuration files in a special directory (\\texttt{\\$HOME/.yt/}) in the user's home directory.\n\nThe \\texttt{config} file \\textemdash\\ Settings that control runtime behavior. \\\\\nThe \\texttt{my\\_plugins.py} file \\textemdash\\ Add functions, derived fields, constants, or other commonly-used Python code to yt.\n\n\n\\subsection{Analysis Modules}\n\\settowidth{\\MyLen}{\\texttt{multicol}}\nThe import name for each module is listed at the end of each description (see \\textbf{yt Imports}).\n\n\\texttt{Absorption Spectrum} \\textemdash\\ (\\texttt{absorption\\_spectrum}). \\\\\n\\texttt{Clump Finder} \\textemdash\\ Find clumps defined by density thresholds (\\texttt{level\\_sets}). \\\\\n\\texttt{Halo Finding} \\textemdash\\ Locate halos of dark matter particles (\\texttt{halo\\_finding}). \\\\\n\\texttt{Light Cone Generator} \\textemdash\\ Stitch datasets together to perform analysis over cosmological volumes. \\\\\n\\texttt{Light Ray Generator} \\textemdash\\ Analyze the path of light rays.\\\\\n\\texttt{Rockstar Halo Finding} \\textemdash\\ Locate halos of dark matter using the Rockstar halo finder (\\texttt{halo\\_finding.rockstar}). \\\\\n\\texttt{Star Particle Analysis} \\textemdash\\ Analyze star formation history and assemble spectra (\\texttt{star\\_analysis}). \\\\\n\\texttt{Sunrise Exporter} \\textemdash\\ Export data to the sunrise visualization format (\\texttt{sunrise\\_export}). \\\\\n\n\n\\subsection{Parallel Analysis}\n\\settowidth{\\MyLen}{\\texttt{multicol}}\nNearly all of yt is parallelized using\nMPI\\@.  The \\textit{mpi4py} package must be installed for parallelism in yt.  To\ninstall \\textit{pip install mpi4py} on the command line usually works.\nExecute python in parallel similar to this:\\\\\n\\textit{mpirun -n 12 python script.py}\\\\\nThe file \\texttt{script.py} must call the \\texttt{yt.enable\\_parallelism()} to\nturn on yt's parallelism.  If this doesn't happen, all cores will execute the\nsame serial yt script.  This command may differ for each system on which you use\nyt; please consult the system documentation for details on how to run parallel\napplications.\n\n\\texttt{parallel\\_objects()} \\textemdash\\ A way to parallelize analysis over objects\n(such as halos or clumps).\\\\\n\n\n\\subsection{Git}\n\\settowidth{\\MyLen}{\\texttt{multicol}}\nPlease see \\url{https://git-scm.com/} for the latest Git documentation.\n\n\\texttt{git clone https://github.com/yt-project/yt} \\textemdash\\ Clone the yt\nrepository. \\\\\n\\texttt{git status} \\textemdash\\ Show status of working tree.\\\\\n\\texttt{git diff} \\textemdash\\ Show changed files in the working tree. \\\\\n\\texttt{git log} \\textemdash\\ Show a log of changes in reverse chronological\norder.\\\\\n\\texttt{git revert <commit>} \\textemdash\\ Revert the changes in an existing\ncommit and create a new commit with reverted changes. \\\\\n\\texttt{git add <pathspec>} \\textemdash\\ Stage changes in the working tree to\nthe index. \\\\\n\\texttt{git commit} \\textemdash\\ Commit staged changes to the repository. \\\\\n\\texttt{git merge <branch>} Merge the revisions from the specified branch on\ntop of the current branch.\\\\\n\\texttt{git push <remote>} \\textemdash\\ Push changes to remote repository. \\\\\n\\texttt{git push <remote> <branch>} \\textemdash\\ Push changes in specified\nbranch to remote repository. \\\\\n\\texttt{git pull <remote> <branch>} \\textemdash\\ Pull changes from the\nspecified branch of the remote repository. This is equivalent to \\texttt{git\nfetch <remote>} and then \\texttt{git merge <remote>/<branch>}.\\\\\n\n\\subsection{FAQ}\n\\settowidth{\\MyLen}{\\texttt{multicol}}\n\n\\texttt{slc.set\\_log('field', False)} \\textemdash\\ When plotting \\texttt{field}, use linear scaling instead of log scaling.\n\n\n%\\rule{0.3\\linewidth}{0.25pt}\n%\\scriptsize\n\n% Can put some final stuff here like copyright etc...\n\n\\end{multicols}\n\n\\end{document}\n"
  },
  {
    "path": "doc/docstring_idioms.txt",
    "content": "Idioms for Docstrings in yt\n===========================\n\nFor a full list of recognized constructs for marking up docstrings, see the\nSphinx documentation:\n\nhttp://www.sphinx-doc.org/en/master/\n\nSpecifically, this section:\n\nhttp://www.sphinx-doc.org/en/master/usage/restructuredtext/\nhttp://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#cross-referencing-syntax\n\nVariables in Examples\n---------------------\n\nIn order to construct short, useful examples, some variables must be specified.\nHowever, because often examples require a bit of setup, here is a list of\nuseful variable names that correspond to specific instances that the user is\npresupposed to have created.\n\n   * `ds`: a dataset, loaded successfully\n   * `sp`: a sphere\n   * `c`: a 3-component \"center\"\n   * `L`: a 3-component vector that corresponds to either angular momentum or a\n     normal vector\n\nCross-Referencing\n-----------------\n\nTo enable sufficient linkages between different sections of the documentation,\ngood cross-referencing is key.  To reference a section of the documentation,\nyou can use this construction:\n\n    For more information, see :ref:`image_writer`.\n\nThis will insert a link to the section in the documentation which has been\nidentified with `image_writer` as its name.\n\nReferencing Classes and Functions\n---------------------------------\n\nTo indicate the return type of a given object, you can reference it using this\nconstruction:\n\n    This function returns a :class:`ProjectionPlot`.\n\nTo reference a function, you can use:\n\n    To write out this array, use :func:`save_image`.\n\nTo reference a method, you can use:\n\n    To add a projection, use :meth:`ProjectionPlot.set_width`.\n"
  },
  {
    "path": "doc/extensions/README",
    "content": "This includes a version of the Numpy Documentation extension that has been\nslightly modified to emit extra TOC tree items.\n\n-- Matt Turk, March 25, 2011\n"
  },
  {
    "path": "doc/extensions/config_help.py",
    "content": "import re\nimport subprocess\n\nfrom docutils import statemachine\nfrom docutils.parsers.rst import Directive\n\n\ndef setup(app):\n    app.add_directive(\"config_help\", GetConfigHelp)\n    setup.app = app\n    setup.config = app.config\n    setup.confdir = app.confdir\n\n    retdict = dict(version=\"1.0\", parallel_read_safe=True, parallel_write_safe=True)\n\n    return retdict\n\n\nclass GetConfigHelp(Directive):\n    required_arguments = 1\n    optional_arguments = 0\n    final_argument_whitespace = True\n\n    def run(self):\n        rst_file = self.state_machine.document.attributes[\"source\"]\n        data = (\n            subprocess.check_output(self.arguments[0].split(\" \") + [\"-h\"])\n            .decode(\"utf8\")\n            .split(\"\\n\")\n        )\n        ind = next(\n            (i for i, val in enumerate(data) if re.match(r\"\\s{0,3}\\{.*\\}\\s*$\", val))\n        )\n        lines = [\".. code-block:: none\", \"\"] + data[ind + 1 :]\n        self.state_machine.insert_input(\n            statemachine.string2lines(\"\\n\".join(lines)), rst_file\n        )\n        return []\n"
  },
  {
    "path": "doc/extensions/pythonscript_sphinxext.py",
    "content": "import errno\nimport glob\nimport os\nimport shutil\nimport subprocess\nimport tempfile\nimport time\nimport uuid\n\nfrom docutils import nodes\nfrom docutils.parsers.rst import Directive\n\n\nclass PythonScriptDirective(Directive):\n    \"\"\"Execute an inline python script and display images.\n\n    This uses exec to execute an inline python script, copies\n    any images produced by the script, and embeds them in the document\n    along with the script.\n\n    \"\"\"\n\n    required_arguments = 0\n    optional_arguments = 0\n    has_content = True\n\n    def run(self):\n        cwd = os.getcwd()\n        tmpdir = tempfile.mkdtemp()\n        os.chdir(tmpdir)\n\n        rst_file = self.state_machine.document.attributes[\"source\"]\n        rst_dir = os.path.abspath(os.path.dirname(rst_file))\n\n        image_dir, image_rel_dir = make_image_dir(setup, rst_dir)\n\n        # Construct script from cell content\n        content = \"\\n\".join(self.content)\n        with open(\"temp.py\", \"w\") as f:\n            f.write(content)\n\n        # Use sphinx logger?\n        uid = uuid.uuid4().hex[:8]\n        print(\"\")\n        print(f\">> Contents of the script: {uid}\")\n        print(content)\n        print(\"\")\n\n        start = time.time()\n        subprocess.call([\"python\", \"temp.py\"])\n        print(f\">> The execution of the script {uid} took {time.time() - start:f} s\")\n        text = \"\"\n        for im in sorted(glob.glob(\"*.png\")):\n            text += get_image_tag(im, image_dir, image_rel_dir)\n\n        code = content\n\n        literal = nodes.literal_block(code, code)\n        literal[\"language\"] = \"python\"\n\n        attributes = {\"format\": \"html\"}\n        img_node = nodes.raw(\"\", text, **attributes)\n\n        # clean up\n        os.chdir(cwd)\n        shutil.rmtree(tmpdir, True)\n\n        return [literal, img_node]\n\n\ndef setup(app):\n    app.add_directive(\"python-script\", PythonScriptDirective)\n    setup.app = app\n    setup.config = app.config\n    setup.confdir = app.confdir\n\n    retdict = dict(version=\"0.1\", parallel_read_safe=True, parallel_write_safe=True)\n\n    return retdict\n\n\ndef get_image_tag(filename, image_dir, image_rel_dir):\n    my_uuid = uuid.uuid4().hex\n    shutil.move(filename, image_dir + os.path.sep + my_uuid + filename)\n    relative_filename = image_rel_dir + os.path.sep + my_uuid + filename\n    return f'<img src=\"{relative_filename}\" width=\"600\"><br>'\n\n\ndef make_image_dir(setup, rst_dir):\n    image_dir = os.path.join(setup.app.builder.outdir, \"_images\")\n    rel_dir = os.path.relpath(setup.confdir, rst_dir)\n    image_rel_dir = os.path.join(rel_dir, \"_images\")\n    thread_safe_mkdir(image_dir)\n    return image_dir, image_rel_dir\n\n\ndef thread_safe_mkdir(dirname):\n    try:\n        os.makedirs(dirname)\n    except OSError as e:\n        if e.errno != errno.EEXIST:\n            raise\n"
  },
  {
    "path": "doc/extensions/yt_colormaps.py",
    "content": "# This extension is quite simple:\n#  1. It accepts a script name\n#  2. This script is added to the document in a literalinclude\n#  3. Any _static images found will be added\n\nimport glob\nimport os\nimport shutil\n\nfrom docutils.parsers.rst import Directive, directives\n\n# Some of this magic comes from the matplotlib plot_directive.\n\n\ndef setup(app):\n    app.add_directive(\"yt_colormaps\", ColormapScript)\n    setup.app = app\n    setup.config = app.config\n    setup.confdir = app.confdir\n\n    retdict = dict(version=\"0.1\", parallel_read_safe=True, parallel_write_safe=True)\n\n    return retdict\n\n\nclass ColormapScript(Directive):\n    required_arguments = 1\n    optional_arguments = 0\n\n    def run(self):\n        rst_file = self.state_machine.document.attributes[\"source\"]\n        rst_dir = os.path.abspath(os.path.dirname(rst_file))\n        script_fn = directives.path(self.arguments[0])\n        script_bn = os.path.basename(script_fn)\n\n        # This magic is from matplotlib\n        dest_dir = os.path.abspath(\n            os.path.join(setup.app.builder.outdir, os.path.dirname(script_fn))\n        )\n        if not os.path.exists(dest_dir):\n            os.makedirs(dest_dir)  # no problem here for me, but just use built-ins\n\n        rel_dir = os.path.relpath(rst_dir, setup.confdir)\n        place = os.path.join(dest_dir, rel_dir)\n        if not os.path.isdir(place):\n            os.makedirs(place)\n        shutil.copyfile(\n            os.path.join(rst_dir, script_fn), os.path.join(place, script_bn)\n        )\n\n        im_path = os.path.join(rst_dir, \"_static\")\n        images = sorted(glob.glob(os.path.join(im_path, \"*.png\")))\n        lines = []\n        for im in images:\n            im_name = os.path.join(\"_static\", os.path.basename(im))\n            lines.append(f\".. image:: {im_name}\")\n            lines.append(\"   :width: 400\")\n            lines.append(f\"   :target: ../../_images/{os.path.basename(im)}\")\n            lines.append(\"\\n\")\n        lines.append(\"\\n\")\n        self.state_machine.insert_input(lines, rst_file)\n        return []\n"
  },
  {
    "path": "doc/extensions/yt_cookbook.py",
    "content": "# This extension is quite simple:\n#  1. It accepts a script name\n#  2. This script is added to the document in a literalinclude\n#  3. Any _static images found will be added\n\nimport glob\nimport os\nimport shutil\n\nfrom docutils.parsers.rst import Directive, directives\n\n# Some of this magic comes from the matplotlib plot_directive.\n\n\ndef setup(app):\n    app.add_directive(\"yt_cookbook\", CookbookScript)\n    setup.app = app\n    setup.config = app.config\n    setup.confdir = app.confdir\n\n    retdict = dict(version=\"0.1\", parallel_read_safe=True, parallel_write_safe=True)\n\n    return retdict\n\n\ndata_patterns = [\"*.h5\", \"*.out\", \"*.dat\", \"*.mp4\"]\n\n\nclass CookbookScript(Directive):\n    required_arguments = 1\n    optional_arguments = 0\n\n    def run(self):\n        rst_file = self.state_machine.document.attributes[\"source\"]\n        rst_dir = os.path.abspath(os.path.dirname(rst_file))\n        script_fn = directives.path(self.arguments[0])\n        script_bn = os.path.basename(script_fn)\n        script_name = os.path.basename(self.arguments[0]).split(\".\")[0]\n\n        # This magic is from matplotlib\n        dest_dir = os.path.abspath(\n            os.path.join(setup.app.builder.outdir, os.path.dirname(script_fn))\n        )\n        if not os.path.exists(dest_dir):\n            os.makedirs(dest_dir)  # no problem here for me, but just use built-ins\n\n        rel_dir = os.path.relpath(rst_dir, setup.confdir)\n        place = os.path.join(dest_dir, rel_dir)\n        if not os.path.isdir(place):\n            os.makedirs(place)\n        shutil.copyfile(\n            os.path.join(rst_dir, script_fn), os.path.join(place, script_bn)\n        )\n\n        im_path = os.path.join(rst_dir, \"_static\")\n        images = sorted(glob.glob(os.path.join(im_path, f\"{script_name}__*.png\")))\n        lines = []\n        lines.append(f\"(`{script_bn} <{script_fn}>`__)\")\n        lines.append(\"\\n\")\n        lines.append(\"\\n\")\n        lines.append(f\".. literalinclude:: {self.arguments[0]}\")\n        lines.append(\"\\n\")\n        lines.append(\"\\n\")\n        for im in images:\n            im_name = os.path.join(\"_static\", os.path.basename(im))\n            lines.append(f\".. image:: {im_name}\")\n            lines.append(\"   :width: 400\")\n            lines.append(f\"   :target: ../_images/{os.path.basename(im)}\")\n            lines.append(\"\\n\")\n        lines.append(\"\\n\")\n        for ext in data_patterns:\n            data_files = sorted(\n                glob.glob(os.path.join(im_path, f\"{script_name}__*.{ext}\"))\n            )\n            for df in data_files:\n                df_bn = os.path.basename(df)\n                shutil.copyfile(\n                    os.path.join(rst_dir, df), os.path.join(dest_dir, rel_dir, df_bn)\n                )\n                lines.append(f\" * Data: `{df_bn} <{df}>`__)\")\n            lines.append(\"\\n\")\n        self.state_machine.insert_input(lines, rst_file)\n        return []\n"
  },
  {
    "path": "doc/extensions/yt_showfields.py",
    "content": "import subprocess\nimport sys\n\nfrom docutils.parsers.rst import Directive\n\n\ndef setup(app):\n    app.add_directive(\"yt_showfields\", ShowFields)\n    setup.app = app\n    setup.config = app.config\n    setup.confdir = app.confdir\n\n    retdict = dict(version=\"1.0\", parallel_read_safe=True, parallel_write_safe=True)\n\n    return retdict\n\n\nclass ShowFields(Directive):\n    required_arguments = 0\n    optional_arguments = 0\n    parallel_read_safe = True\n    parallel_write_safe = True\n\n    def run(self):\n        rst_file = self.state_machine.document.attributes[\"source\"]\n        lines = subprocess.check_output(\n            [sys.executable, \"./helper_scripts/show_fields.py\"]\n        )\n        lines = lines.decode(\"utf8\")\n        lines = lines.split(\"\\n\")\n        self.state_machine.insert_input(lines, rst_file)\n        return []\n"
  },
  {
    "path": "doc/helper_scripts/code_support.py",
    "content": "vals = [\n    \"FluidQuantities\",\n    \"Particles\",\n    \"Parameters\",\n    \"Units\",\n    \"ReadOnDemand\",\n    \"LoadRawData\",\n    \"LevelOfSupport\",\n    \"ContactPerson\",\n]\n\n\nclass CodeSupport:\n    def __init__(self, **kwargs):\n        self.support = {}\n        for v in vals:\n            self.support[v] = \"N\"\n        for k, v in kwargs.items():\n            if k in vals:\n                self.support[k] = v\n\n\nY = \"Y\"\nN = \"N\"\n\ncode_names = [\"Enzo\", \"Orion\", \"FLASH\", \"RAMSES\", \"Chombo\", \"Gadget\", \"ART\", \"ZEUS\"]\n\n\ncodes = dict(\n    Enzo=CodeSupport(\n        FluidQuantities=Y,\n        Particles=Y,\n        Parameters=Y,\n        Units=Y,\n        ReadOnDemand=Y,\n        LoadRawData=Y,\n        ContactPerson=\"Matt Turk\",\n        LevelOfSupport=\"Full\",\n    ),\n    Orion=CodeSupport(\n        FluidQuantities=Y,\n        Particles=N,\n        Parameters=Y,\n        Units=Y,\n        ReadOnDemand=Y,\n        LoadRawData=Y,\n        ContactPerson=\"Jeff Oishi\",\n        LevelOfSupport=\"Full\",\n    ),\n    FLASH=CodeSupport(\n        FluidQuantities=Y,\n        Particles=N,\n        Parameters=N,\n        Units=Y,\n        ReadOnDemand=Y,\n        LoadRawData=Y,\n        ContactPerson=\"John !ZuHone\",\n        LevelOfSupport=\"Partial\",\n    ),\n    RAMSES=CodeSupport(\n        FluidQuantities=Y,\n        Particles=N,\n        Parameters=N,\n        Units=N,\n        ReadOnDemand=Y,\n        LoadRawData=Y,\n        ContactPerson=\"Matt Turk\",\n        LevelOfSupport=\"Partial\",\n    ),\n    Chombo=CodeSupport(\n        FluidQuantities=Y,\n        Particles=N,\n        Parameters=N,\n        Units=N,\n        ReadOnDemand=Y,\n        LoadRawData=Y,\n        ContactPerson=\"Jeff Oishi\",\n        LevelOfSupport=\"Partial\",\n    ),\n    Gadget=CodeSupport(\n        FluidQuantities=N,\n        Particles=Y,\n        Parameters=Y,\n        Units=Y,\n        ReadOnDemand=N,\n        LoadRawData=N,\n        ContactPerson=\"Chris Moody\",\n        LevelOfSupport=\"Partial\",\n    ),\n    ART=CodeSupport(\n        FluidQuantities=N,\n        Particles=N,\n        Parameters=N,\n        Units=N,\n        ReadOnDemand=N,\n        LoadRawData=N,\n        ContactPerson=\"Matt Turk\",\n        LevelOfSupport=\"None\",\n    ),\n    ZEUS=CodeSupport(\n        FluidQuantities=N,\n        Particles=N,\n        Parameters=N,\n        Units=N,\n        ReadOnDemand=N,\n        LoadRawData=N,\n        ContactPerson=\"Matt Turk\",\n        LevelOfSupport=\"None\",\n    ),\n)\n\nprint(\"|| . ||\", end=\" \")\nfor c in code_names:\n    print(f\"{c} || \", end=\" \")\nprint()\n\nfor vn in vals:\n    print(f\"|| !{vn} ||\", end=\" \")\n    for c in code_names:\n        print(f\"{codes[c].support[vn]} || \", end=\" \")\n    print()\n"
  },
  {
    "path": "doc/helper_scripts/parse_cb_list.py",
    "content": "import inspect\nfrom textwrap import TextWrapper\n\nimport yt\n\nds = yt.load(\"RD0005-mine/RedshiftOutput0005\")\n\noutput = open(\"source/visualizing/_cb_docstrings.inc\", \"w\")\n\ntemplate = \"\"\"\n\n.. function:: %(clsname)s%(sig)s:\n\n   (This is a proxy for :class:`~%(clsproxy)s`.)\n\n%(docstring)s\n\n\"\"\"\n\ntw = TextWrapper(initial_indent=\"   \", subsequent_indent=\"   \", width=60)\n\n\ndef write_docstring(f, name, cls):\n    if not hasattr(cls, \"_type_name\") or cls._type_name is None:\n        return\n    for clsi in inspect.getmro(cls):\n        docstring = inspect.getdoc(clsi.__init__)\n        if docstring is not None:\n            break\n    clsname = cls._type_name\n    sig = inspect.formatargspec(*inspect.getargspec(cls.__init__))\n    sig = sig.replace(\"**kwargs\", \"**field_parameters\")\n    clsproxy = f\"yt.visualization.plot_modifications.{cls.__name__}\"\n    # docstring = \"\\n\".join([\"   %s\" % line for line in docstring.split(\"\\n\")])\n    # print(docstring)\n    f.write(\n        template\n        % dict(\n            clsname=clsname,\n            sig=sig,\n            clsproxy=clsproxy,\n            docstring=\"\\n\".join(tw.wrap(docstring)),\n        )\n    )\n    # docstring = docstring))\n\n\nfor n, c in sorted(yt.visualization.api.callback_registry.items()):\n    write_docstring(output, n, c)\n    print(f\".. autoclass:: yt.visualization.plot_modifications.{n}\")\n    print(\"   :members:\")\n    print()\n"
  },
  {
    "path": "doc/helper_scripts/parse_dq_list.py",
    "content": "import inspect\nfrom textwrap import TextWrapper\n\nimport yt\n\nds = yt.load(\"RD0005-mine/RedshiftOutput0005\")\n\noutput = open(\"source/analyzing/_dq_docstrings.inc\", \"w\")\n\ntemplate = \"\"\"\n\n.. function:: %(funcname)s%(sig)s:\n\n   (This is a proxy for :func:`~%(funcproxy)s`.)\n%(docstring)s\n\n\"\"\"\n\ntw = TextWrapper(initial_indent=\"   \", subsequent_indent=\"   \", width=60)\n\n\ndef write_docstring(f, name, func):\n    docstring = inspect.getdoc(func)\n    funcname = name\n    sig = inspect.formatargspec(*inspect.getargspec(func))\n    sig = sig.replace(\"data, \", \"\")\n    sig = sig.replace(\"(data)\", \"()\")\n    funcproxy = f\"yt.data_objects.derived_quantities.{func.__name__}\"\n    docstring = \"\\n\".join(\"   %s\" % line for line in docstring.split(\"\\n\"))\n    f.write(\n        template\n        % dict(funcname=funcname, sig=sig, funcproxy=funcproxy, docstring=docstring)\n    )\n    # docstring = \"\\n\".join(tw.wrap(docstring))))\n\n\ndd = ds.all_data()\nfor n, func in sorted(dd.quantities.functions.items()):\n    print(n, func)\n    write_docstring(output, n, func[1])\n"
  },
  {
    "path": "doc/helper_scripts/parse_object_list.py",
    "content": "import inspect\nfrom textwrap import TextWrapper\n\nimport yt\n\nds = yt.load(\"RD0005-mine/RedshiftOutput0005\")\n\noutput = open(\"source/analyzing/_obj_docstrings.inc\", \"w\")\n\ntemplate = \"\"\"\n\n.. class:: %(clsname)s%(sig)s:\n\n   For more information, see :ref:`%(docstring)s`\n   (This is a proxy for :class:`~%(clsproxy)sBase`.)\n\"\"\"\n\ntw = TextWrapper(initial_indent=\"   \", subsequent_indent=\"   \", width=60)\n\n\ndef write_docstring(f, name, cls):\n    for clsi in inspect.getmro(cls):\n        docstring = inspect.getdoc(clsi.__init__)\n        if docstring is not None:\n            break\n    clsname = name\n    sig = inspect.formatargspec(*inspect.getargspec(cls.__init__))\n    sig = sig.replace(\"**kwargs\", \"**field_parameters\")\n    clsproxy = f\"yt.data_objects.data_containers.{cls.__name__}\"\n    f.write(\n        template\n        % dict(\n            clsname=clsname, sig=sig, clsproxy=clsproxy, docstring=\"physical-object-api\"\n        )\n    )\n\n\nfor n, c in sorted(ds.__dict__.items()):\n    if hasattr(c, \"_con_args\"):\n        print(n)\n        write_docstring(output, n, c)\n"
  },
  {
    "path": "doc/helper_scripts/run_recipes.py",
    "content": "#!/usr/bin/env python3\n\nimport glob\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\nimport traceback\nfrom multiprocessing import Pool\n\nimport matplotlib\n\nfrom yt.config import ytcfg\n\nmatplotlib.use(\"Agg\")\n\nFPATTERNS = [\"*.png\", \"*.txt\", \"*.h5\", \"*.dat\", \"*.mp4\"]\nDPATTERNS = [\"LC*\", \"LR\", \"DD0046\"]\nBADF = [\n    \"cloudy_emissivity.h5\",\n    \"apec_emissivity.h5\",\n    \"xray_emissivity.h5\",\n    \"AMRGridData_Slice_x_density.png\",\n]\nCWD = os.getcwd()\nytcfg[\"yt\", \"serialize\"] = False\nBLACKLIST = [\"opengl_ipython\", \"opengl_vr\"]\n\n\ndef prep_dirs():\n    for directory in glob.glob(f\"{ytcfg.get('yt', 'test_data_dir')}/*\"):\n        os.symlink(directory, os.path.basename(directory))\n\n\ndef run_recipe(payload):\n    (recipe,) = payload\n    module_name, ext = os.path.splitext(os.path.basename(recipe))\n    dest = os.path.join(os.path.dirname(recipe), \"_static\", module_name)\n    if module_name in BLACKLIST:\n        return 0\n    if not os.path.exists(f\"{CWD}/_temp/{module_name}.done\"):\n        sys.stderr.write(f\"Started {module_name}\\n\")\n        tmpdir = tempfile.mkdtemp()\n        os.chdir(tmpdir)\n        prep_dirs()\n        try:\n            subprocess.check_call([\"python\", recipe])\n        except Exception as exc:\n            trace = \"\".join(traceback.format_exception(*sys.exc_info()))\n            trace += f\" in module: {module_name}\\n\"\n            trace += f\" recipe: {recipe}\\n\"\n            raise Exception(trace) from exc\n        open(f\"{CWD}/_temp/{module_name}.done\", \"wb\").close()\n        for pattern in FPATTERNS:\n            for fname in glob.glob(pattern):\n                if fname not in BADF:\n                    shutil.move(fname, f\"{dest}__{fname}\")\n        for pattern in DPATTERNS:\n            for dname in glob.glob(pattern):\n                shutil.move(dname, dest)\n        os.chdir(CWD)\n        shutil.rmtree(tmpdir, True)\n        sys.stderr.write(f\"Finished with {module_name}\\n\")\n    return 0\n\n\nfor path in [\n    \"_temp\",\n    \"source/cookbook/_static\",\n    \"source/visualizing/colormaps/_static\",\n]:\n    fpath = os.path.join(CWD, path)\n    if os.path.exists(fpath):\n        shutil.rmtree(fpath)\n    os.makedirs(fpath)\n\nos.chdir(\"_temp\")\nrecipes = []\nfor rpath in [\"source/cookbook\", \"source/visualizing/colormaps\"]:\n    fpath = os.path.join(CWD, rpath)\n    sys.path.append(fpath)\n    recipes += glob.glob(f\"{fpath}/*.py\")\nWPOOL = Pool(processes=6)\nRES = WPOOL.map_async(run_recipe, ((recipe,) for recipe in recipes))\nRES.get()\nos.chdir(CWD)\n"
  },
  {
    "path": "doc/helper_scripts/show_fields.py",
    "content": "import inspect\n\nimport numpy as np\n\nimport yt.frontends as frontends_module\nfrom yt.config import ytcfg\nfrom yt.fields.derived_field import NullFunc\nfrom yt.frontends.api import _frontends\nfrom yt.frontends.stream.fields import StreamFieldInfo\nfrom yt.funcs import obj_length\nfrom yt.testing import fake_random_ds\nfrom yt.units import dimensions\nfrom yt.units.yt_array import Unit\nfrom yt.utilities.cosmology import Cosmology\n\nfields, units = [], []\n\nfor fname, (code_units, _aliases, _dn) in StreamFieldInfo.known_other_fields:\n    fields.append((\"gas\", fname))\n    units.append(code_units)\nbase_ds = fake_random_ds(4, fields=fields, units=units)\nbase_ds.index\nbase_ds.cosmological_simulation = 1\nbase_ds.cosmology = Cosmology()\n\n\nytcfg[\"yt\", \"internals\", \"within_testing\"] = True\nnp.seterr(all=\"ignore\")\n\n\ndef _strip_ftype(field):\n    if not isinstance(field, tuple):\n        return field\n    elif field[0] == \"all\":\n        return field\n    return field[1]\n\n\nnp.random.seed(int(0x4D3D3D3))\nunits = [base_ds._get_field_info(f).units for f in fields]\nfields = [_strip_ftype(f) for f in fields]\nds = fake_random_ds(16, fields=fields, units=units, particles=1)\nds.parameters[\"HydroMethod\"] = \"streaming\"\nds.parameters[\"EOSType\"] = 1.0\nds.parameters[\"EOSSoundSpeed\"] = 1.0\nds.conversion_factors[\"Time\"] = 1.0\nds.conversion_factors.update({f: 1.0 for f in fields})\nds.gamma = 5.0 / 3.0\nds.current_redshift = 0.0001\nds.cosmological_simulation = 1\nds.hubble_constant = 0.7\nds.omega_matter = 0.27\nds.omega_lambda = 0.73\nds.cosmology = Cosmology(\n    hubble_constant=ds.hubble_constant,\n    omega_matter=ds.omega_matter,\n    omega_lambda=ds.omega_lambda,\n    unit_registry=ds.unit_registry,\n)\nfor my_unit in [\"m\", \"pc\", \"AU\", \"au\"]:\n    new_unit = f\"{my_unit}cm\"\n    my_u = Unit(my_unit, registry=ds.unit_registry)\n    ds.unit_registry.add(\n        new_unit,\n        my_u.base_value,\n        dimensions.length,\n        \"\\\\rm{%s}/(1+z)\" % my_unit,\n        prefixable=True,\n    )\n\n\nheader = r\"\"\"\n.. _field-list:\n\nField List\n==========\n\nThis is a list of many of the fields available in yt.  We have attempted to\ninclude most of the fields that are accessible through the plugin system, as\nwell as the fields that are known by the frontends, however it is possible to\ngenerate many more permutations, particularly through vector operations. For\nmore information about the fields framework, see :ref:`fields`.\n\nSome fields are recognized by specific frontends only. These are typically\nfields like density and temperature that have their own names and units in\nthe different frontend datasets. Often, these fields are aliased to their\nyt-named counterpart fields (typically 'gas' fieldtypes). For example, in\nthe ``FLASH`` frontend, the ``dens`` field (i.e. ``(flash, dens)``) is aliased\nto the gas field density (i.e. ``(gas, density)``), similarly ``(flash, velx)``\nis aliased to ``(gas, velocity_x)``, and so on. In what follows, if a field\nis aliased it will be noted.\n\nTry using the ``ds.field_list`` and ``ds.derived_field_list`` to view the\nnative and derived fields available for your dataset respectively. For example\nto display the native fields in alphabetical order:\n\n.. notebook-cell::\n\n  import yt\n  ds = yt.load(\"Enzo_64/DD0043/data0043\")\n  for i in sorted(ds.field_list):\n    print(i)\n\nTo figure out out what all of the field types here mean, see\n:ref:`known-field-types`.\n\n.. contents:: Table of Contents\n   :depth: 1\n   :local:\n   :backlinks: none\n\n.. _yt-fields:\n\nUniversal Fields\n----------------\n\"\"\"\n\nfooter = \"\"\"\n\nIndex of Fields\n---------------\n\n.. contents::\n   :depth: 3\n   :backlinks: none\n\n\"\"\"\nprint(header)\n\nseen = []\n\n\ndef fix_units(units, in_cgs=False):\n    unit_object = Unit(units, registry=ds.unit_registry)\n    if in_cgs:\n        unit_object = unit_object.get_cgs_equivalent()\n    latex = unit_object.latex_representation()\n    return latex.replace(r\"\\ \", \"~\")\n\n\ndef print_all_fields(fl):\n    for fn in sorted(fl):\n        df = fl[fn]\n        f = df._function\n        s = f\"{df.name}\"\n        print(s)\n        print(\"^\" * len(s))\n        print()\n        if obj_length(df.units) > 0:\n            # Most universal fields are in CGS except for these special fields\n            if df.name[1] in [\n                \"particle_position\",\n                \"particle_position_x\",\n                \"particle_position_y\",\n                \"particle_position_z\",\n                \"entropy\",\n                \"kT\",\n                \"metallicity\",\n                \"dx\",\n                \"dy\",\n                \"dz\",\n                \"cell_volume\",\n                \"x\",\n                \"y\",\n                \"z\",\n            ]:\n                print(f\"   * Units: :math:`{fix_units(df.units)}`\")\n            else:\n                print(f\"   * Units: :math:`{fix_units(df.units, in_cgs=True)}`\")\n        print(f\"   * Sampling Method: {df.sampling_type}\")\n        print()\n        print(\"**Field Source**\")\n        print()\n        if f == NullFunc:\n            print(\"No source available.\")\n            print()\n            continue\n        else:\n            print(\".. code-block:: python\")\n            print()\n            for line in inspect.getsource(f).split(\"\\n\"):\n                print(\"  \" + line)\n            print()\n\n\nds.index\nprint_all_fields(ds.field_info)\n\n\nclass FieldInfo:\n    \"\"\"a simple container to hold the information about fields\"\"\"\n\n    def __init__(self, ftype, field, ptype):\n        name = field[0]\n        self.units = \"\"\n        u = field[1][0]\n        if len(u) > 0:\n            self.units = r\":math:`\\mathrm{%s}`\" % fix_units(u)\n        a = [f\"``{f}``\" for f in field[1][1] if f]\n        self.aliases = \" \".join(a)\n        self.dname = \"\"\n        if field[1][2] is not None:\n            self.dname = f\":math:`{field[1][2]}`\"\n\n        if ftype != \"particle_type\":\n            ftype = f\"'{ftype}'\"\n        self.name = f\"({ftype}, '{name}')\"\n        self.ptype = ptype\n\n\ncurrent_frontends = [f for f in _frontends if f not in [\"stream\"]]\n\nfor frontend in current_frontends:\n    this_f = getattr(frontends_module, frontend)\n    field_info_names = [fi for fi in dir(this_f) if \"FieldInfo\" in fi]\n    dataset_names = [dset for dset in dir(this_f) if \"Dataset\" in dset]\n\n    if frontend == \"gadget\":\n        # Drop duplicate entry for GadgetHDF5, add special case for FieldInfo\n        # entry\n        dataset_names = [\"GadgetDataset\"]\n        field_info_names = [\"GadgetFieldInfo\"]\n    elif frontend == \"boxlib\":\n        field_info_names = []\n        for d in dataset_names:\n            if \"Maestro\" in d:\n                field_info_names.append(\"MaestroFieldInfo\")\n            elif \"Castro\" in d:\n                field_info_names.append(\"CastroFieldInfo\")\n            else:\n                field_info_names.append(\"BoxlibFieldInfo\")\n    elif frontend == \"chombo\":\n        # remove low dimensional field info containers for ChomboPIC\n        field_info_names = [\n            f for f in field_info_names if \"1D\" not in f and \"2D\" not in f\n        ]\n\n    for dset_name, fi_name in zip(dataset_names, field_info_names):\n        fi = getattr(this_f, fi_name)\n        nfields = 0\n        if hasattr(fi, \"known_other_fields\"):\n            known_other_fields = fi.known_other_fields\n            nfields += len(known_other_fields)\n        else:\n            known_other_fields = []\n        if hasattr(fi, \"known_particle_fields\"):\n            known_particle_fields = fi.known_particle_fields\n            if \"Tipsy\" in fi_name:\n                known_particle_fields += tuple(fi.aux_particle_fields.values())\n            nfields += len(known_particle_fields)\n        else:\n            known_particle_fields = []\n        if nfields > 0:\n            print(f\".. _{dset_name.replace('Dataset', '')}_specific_fields:\\n\")\n            h = f\"{dset_name.replace('Dataset', '')}-Specific Fields\"\n            print(h)\n            print(\"-\" * len(h) + \"\\n\")\n\n            field_stuff = []\n            for field in known_other_fields:\n                field_stuff.append(FieldInfo(frontend, field, False))\n            for field in known_particle_fields:\n                if frontend in [\"sph\", \"halo_catalogs\", \"sdf\"]:\n                    field_stuff.append(FieldInfo(\"particle_type\", field, True))\n                else:\n                    field_stuff.append(FieldInfo(\"io\", field, True))\n\n            # output\n            len_name = 10\n            len_units = 5\n            len_aliases = 7\n            len_part = 9\n            len_disp = 12\n            for f in field_stuff:\n                len_name = max(len_name, len(f.name))\n                len_aliases = max(len_aliases, len(f.aliases))\n                len_units = max(len_units, len(f.units))\n                len_disp = max(len_disp, len(f.dname))\n\n            fstr = \"{nm:{nw}}  {un:{uw}}  {al:{aw}}  {pt:{pw}}  {dp:{dw}}\"\n            header = fstr.format(\n                nm=\"field name\",\n                nw=len_name,\n                un=\"units\",\n                uw=len_units,\n                al=\"aliases\",\n                aw=len_aliases,\n                pt=\"particle?\",\n                pw=len_part,\n                dp=\"display name\",\n                dw=len_disp,\n            )\n\n            div = fstr.format(\n                nm=\"=\" * len_name,\n                nw=len_name,\n                un=\"=\" * len_units,\n                uw=len_units,\n                al=\"=\" * len_aliases,\n                aw=len_aliases,\n                pt=\"=\" * len_part,\n                pw=len_part,\n                dp=\"=\" * len_disp,\n                dw=len_disp,\n            )\n            print(div)\n            print(header)\n            print(div)\n\n            for f in field_stuff:\n                print(\n                    fstr.format(\n                        nm=f.name,\n                        nw=len_name,\n                        un=f.units,\n                        uw=len_units,\n                        al=f.aliases,\n                        aw=len_aliases,\n                        pt=f.ptype,\n                        pw=len_part,\n                        dp=f.dname,\n                        dw=len_disp,\n                    )\n                )\n\n            print(div)\n            print(\"\")\n\nprint(footer)\n"
  },
  {
    "path": "doc/helper_scripts/split_auto.py",
    "content": "import collections\n\ntemplates = dict(\n    autoclass=r\"\"\"\n%(name)s\n%(header)s\n\n.. autoclass:: %(name)s\n   :members:\n   :inherited-members:\n   :undoc-members:\n\n\"\"\",\n    autofunction=r\"\"\"\n%(name)s\n%(header)s\n\n.. autofunction:: %(name)s\n\n\"\"\",\n    index_file=r\"\"\"\n%(title)s\n%(header)s\n\n.. autosummary::\n   :toctree: generated/%(dn)s\n\n\"\"\",\n)\n\nfile_names = dict(\n    ft=(\"Field Types\", \"source/api/field_types/%s.rst\"),\n    pt=(\"Plot Types\", \"source/api/plot_types/%s.rst\"),\n    cl=(\"Callback List\", \"source/api/callback_list/%s.rst\"),\n    ee=(\"Extension Types\", \"source/api/extension_types/%s.rst\"),\n    dd=(\"Derived Datatypes\", \"source/api/derived_datatypes/%s.rst\"),\n    mt=(\"Miscellaneous Types\", \"source/api/misc_types/%s.rst\"),\n    fl=(\"Function List\", \"source/api/function_list/%s.rst\"),\n    ds=(\"Data Sources\", \"source/api/data_sources/%s.rst\"),\n    dq=(\"Derived Quantities\", \"source/api/derived_quantities/%s.rst\"),\n)\n\nto_include = collections.defaultdict(list)\n\nfor line in open(\"auto_generated.txt\"):\n    ftype, name, file_name = (s.strip() for s in line.split(\"::\"))\n    cn = name.split(\".\")[-1]\n    if cn[0] == \"_\":\n        cn = cn[1:]  # For leading _\n    fn = file_names[file_name][1] % cn\n    # if not os.path.exists(os.path.dirname(fn)):\n    #    os.mkdir(os.path.dirname(fn))\n    header = \"-\" * len(name)\n    dd = dict(header=header, name=name)\n    # open(fn, \"w\").write(templates[ftype] % dd)\n    to_include[file_name].append(name)\n\nfor key, val in file_names.items():\n    title, file = val\n    fn = file.rsplit(\"/\", 1)[0] + \".rst\"\n    print(fn)\n    f = open(fn, \"w\")\n    dn = fn.split(\"/\")[-1][:-4]\n    dd = dict(header=\"=\" * len(title), title=title, dn=dn)\n    f.write(templates[\"index_file\"] % dd)\n    for obj in sorted(to_include[key]):\n        f.write(f\"   {obj}\\n\")\n"
  },
  {
    "path": "doc/helper_scripts/table.py",
    "content": "contents = [\n    (\n        \"Getting Started\",\n        [\n            (\"welcome/index.html\", \"Welcome to yt!\", \"What's yt all about?\"),\n            (\n                \"orientation/index.html\",\n                \"yt Orientation\",\n                \"Quickly get up and running with yt: zero to sixty.\",\n            ),\n            (\n                \"help/index.html\",\n                \"How to Ask for Help\",\n                \"Some guidelines on how and where to ask for help with yt\",\n            ),\n            (\n                \"workshop.html\",\n                \"Workshop Tutorials\",\n                \"Videos, slides and scripts from the 2012 workshop covering many \"\n                + \"aspects of yt, from beginning to advanced.\",\n            ),\n        ],\n    ),\n    (\n        \"Everyday yt\",\n        [\n            (\n                \"analyzing/index.html\",\n                \"Analyzing Data\",\n                \"An overview of different ways to handle and process data: loading \"\n                + \"data, using and manipulating objects and fields, examining and \"\n                + \"manipulating particles, derived fields, generating processed data, \"\n                + \"time series analysis.\",\n            ),\n            (\n                \"visualizing/index.html\",\n                \"Visualizing Data\",\n                \"An overview of different ways to visualize data: making projections, \"\n                + \"slices, phase plots, streamlines, and volume rendering; modifying \"\n                + \"plots; the fixed resolution buffer.\",\n            ),\n            (\n                \"interacting/index.html\",\n                \"Interacting with yt\",\n                \"Different ways -- scripting, GUIs, prompts, explorers -- to explore \"\n                + \"your data.\",\n            ),\n        ],\n    ),\n    (\n        \"Advanced Usage\",\n        [\n            (\n                \"advanced/index.html\",\n                \"Advanced yt usage\",\n                \"Advanced topics: parallelism, debugging, ways to drive yt, \"\n                + \"developing\",\n            ),\n            (\n                \"getting_involved/index.html\",\n                \"Getting Involved\",\n                \"How to participate in the community, contribute code and share \"\n                + \"scripts\",\n            ),\n        ],\n    ),\n    (\n        \"Reference Materials\",\n        [\n            (\n                \"cookbook/index.html\",\n                \"The Cookbook\",\n                \"A bunch of illustrated examples of how to do things\",\n            ),\n            (\n                \"reference/index.html\",\n                \"Reference Materials\",\n                \"A list of all bundled fields, API documentation, the Change Log...\",\n            ),\n            (\"faq/index.html\", \"FAQ\", \"Frequently Asked Questions: answered for you!\"),\n        ],\n    ),\n]\n\nheading_template = r\"\"\"\n<h2>%s</h2>\n<table class=\"contentstable\" align=\"center\">\n%s\n</table>\n\"\"\"\n\nsubheading_template = r\"\"\"\n  <tr valign=\"top\">\n    <td width=\"25%%\">\n      <p>\n        <a href=\"%s\">%s</a>\n      </p>\n    </td>\n    <td width=\"75%%\">\n      <p class=\"linkdescr\">%s</p>\n    </td>\n  </tr>\n\"\"\"\n\nt = \"\"\nfor heading, items in contents:\n    s = \"\"\n    for subheading in items:\n        s += subheading_template % subheading\n    t += heading_template % (heading, s)\nprint(t)\n"
  },
  {
    "path": "doc/source/_static/custom.css",
    "content": "blockquote {\n    font-size: 16px;\n    border-left: none;\n}\n\ndd {\n    margin-left: 30px;\n}\n\n/*\n\nCollapse the navbar when its width is less than 1200 pixels.  This may need to\nbe adjusted if the navbar menu changes.\n\n*/\n\n@media (max-width: 1200px) {\n    .navbar-header {\n        float: none;\n    }\n    .navbar-toggle {\n        display: block;\n    }\n    .navbar-collapse {\n        border-top: 1px solid transparent;\n        box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);\n    }\n    .navbar-collapse.collapse {\n        display: none!important;\n    }\n    .navbar-nav {\n        float: none!important;\n        margin: 7.5px -15px;\n    }\n    .navbar-nav>li {\n        float: none;\n    }\n    .navbar-nav>li>a {\n        padding-top: 10px;\n        padding-bottom: 10px;\n    }\n    /* since 3.1.0 */\n    .navbar-collapse.collapse.in {\n        display: block!important;\n    }\n    .collapsing {\n        overflow: hidden!important;\n    }\n}\n\n/*\n\nSphinx code literals conflict with the notebook code tag, so we special-case\nliterals that are inside text.\n\n*/\n\np code {\n    color:  #d14;\n    white-space: nowrap;\n    font-size: 90%;\n    background-color: #f9f2f4;\n    font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;\n}\n\n/*\n\nNicer, controllable formatting for tables that have multi-line headers.\n\n*/\n\nth.head {\n    white-space: pre;\n}\n\n/*\n\nlabels have a crappy default color that is almost invisible in our doc theme so\nwe use a darker color.\n\n*/\n\n.label {\n    color: #333333;\n}\n\n/*\n\nHack to prevent internal link targets being positioned behind the navbar.\n\nSee: https://github.com/twbs/bootstrap/issues/1768\n\n*/\n\n*[id]:before :not(p) {\n  display: block;\n  content: \" \";\n  margin-top: -45px;\n  height: 45px;\n  visibility: hidden;\n}\n\n/*\n\nMake tables span only half the page.\n\n*/\n\n.table {\n    width: 50%\n}\n\n\n.navbar-form.navbar-right:last-child {\n    margin-right: -60px;\n    float: left !important;\n}\n"
  },
  {
    "path": "doc/source/_templates/autosummary/class.rst",
    "content": "{% extends \"!autosummary/class.rst\" %}\n\n{% block methods %}\n{% if methods %}\n.. autosummary::\n   :toctree:\n   {% for item in methods %}\n       ~{{ name }}.{{ item }}\n   {%- endfor %}\n\n{% endif %}\n{% endblock %}\n\n{% block attributes %}\n{% if attributes %}\n.. autosummary::\n   :toctree:\n   {% for item in attributes %}\n       ~{{ name }}.{{ item }}\n   {%- endfor %}\n\n{% endif %}\n{% endblock %}\n"
  },
  {
    "path": "doc/source/_templates/layout.html",
    "content": "{% extends '!layout.html' %}\n\n{%- block linktags %}\n    <link href=\"https://yt-project.org/doc/{{ pagename }}.html\" rel=\"canonical\" />\n    {{ super() }}\n{%- endblock %}\n\n{%- block extrahead %}\n    {{ super() }}\n    <script type=\"text/javascript\">\n\n      var _gaq = _gaq || [];\n      _gaq.push(['_setAccount', 'UA-391373-2']);\n      _gaq.push(['_setDomainName', 'yt-project.org']);\n      _gaq.push(['_setAllowHash', false]);\n      _gaq.push(['_trackPageview']);\n\n      (function() {\n        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;\n        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';\n        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);\n      })();\n\n    </script>\n{%- endblock %}\n\n{%- block footer %}\n    <div class=\"footer\">\n    {%- if hasdoc('copyright') %}\n      {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href=\"{{ path }}\">Copyright</a> {{ copyright }}.{% endtrans %}\n    {%- else %}\n      {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}\n    {%- endif %}\n    {%- if last_updated %}\n      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}\n    {%- endif %}\n    {%- if show_sphinx %}\n      {% trans sphinx_version=sphinx_version|e %}Created using <a href=\"http://www.sphinx-doc.org/en/master/\">Sphinx</a> {{ sphinx_version }}.{% endtrans %}\n    {%- endif %}\n    </div>\n{%- endblock %}\n\n{# Custom CSS overrides #}\n{% set bootswatch_css_custom = ['_static/custom.css'] %}\n"
  },
  {
    "path": "doc/source/about/index.rst",
    "content": ".. _aboutyt:\n\nAbout yt\n========\n\n.. contents::\n   :depth: 1\n   :local:\n   :backlinks: none\n\nWhat is yt?\n-----------\n\nyt is a toolkit for analyzing and visualizing quantitative data.  Originally\nwritten to analyze 3D grid-based astrophysical simulation data,\nit has grown to handle any kind of data represented in a 2D or 3D volume.\nyt is an Python-based open source project and is open for anyone to use or\ncontribute code.  The entire source code and history is available to all\nat https://github.com/yt-project/yt .\n\n.. _who-is-yt:\n\nWho is yt?\n----------\n\nAs an open-source project, yt has a large number of user-developers.\nIn September of 2014, the yt developer community collectively decided to endow\nthe title of *member* on individuals who had contributed in a significant way\nto the project.  For a list of those members and a description of their\ncontributions to the code, see\n`our members website. <https://yt-project.org/members.html>`_\n\nHistory of yt\n-------------\n\nyt was originally created to study datasets generated by cosmological\nsimulations of galaxy and star formation conducted by the simulation code Enzo.\nAfter expanding to address data output by other simulation platforms, it further\nbroadened to include alternate, grid-free methods of simulating -- particularly,\nparticles and unstructured meshes.\n\nWith the release of yt 4.0, we are proud that the community has continued to\nexpand, that yt continues to participate in the broader ecosystem, and that the\ndevelopment process is continuing to improve in both inclusivity and openness.\n\nFor a more personal retrospective by the original author, Matthew Turk, you can\nsee this `blog post from\n2017 <https://medium.com/@matthewturk/10-years-of-yt-c93b2f1cef8c>`_.\n\nHow do I contact yt?\n--------------------\n\nIf you have any questions about the code, please contact the `yt users email\nlist <https://mail.python.org/archives/list/yt-users@python.org/>`_.  If\nyou're having other problems, please follow the steps in\n:ref:`asking-for-help`, particularly including Slack and GitHub issues.\n\nHow do I cite yt?\n-----------------\n\nIf you use yt in a publication, we'd very much appreciate a citation!  You\nshould feel free to cite the `ApJS paper\n<https://ui.adsabs.harvard.edu/abs/2011ApJS..192....9T>`_ with the following BibTeX\nentry: ::\n\n   @ARTICLE{2011ApJS..192....9T,\n      author = {{Turk}, M.~J. and {Smith}, B.~D. and {Oishi}, J.~S. and {Skory}, S. and\n   \t{Skillman}, S.~W. and {Abel}, T. and {Norman}, M.~L.},\n       title = \"{yt: A Multi-code Analysis Toolkit for Astrophysical Simulation Data}\",\n     journal = {The Astrophysical Journal Supplement Series},\n   archivePrefix = \"arXiv\",\n      eprint = {1011.3514},\n    primaryClass = \"astro-ph.IM\",\n    keywords = {cosmology: theory, methods: data analysis, methods: numerical },\n        year = 2011,\n       month = jan,\n      volume = 192,\n         eid = {9},\n       pages = {9},\n         doi = {10.1088/0067-0049/192/1/9},\n      adsurl = {https://ui.adsabs.harvard.edu/abs/2011ApJS..192....9T},\n     adsnote = {Provided by the SAO/NASA Astrophysics Data System}\n   }\n\nWhile this paper is somewhat out of date -- and certainly does not include the\nappropriate list of authors -- we are preparing a new method paper as well as\npreparing a new strategy for ensuring equal credit distribution for\ncontributors.  Some of this work can be found at the `yt-4.0-paper\n<https://github.com/yt-project/yt-4.0-paper/>`_ repository.\n"
  },
  {
    "path": "doc/source/analyzing/Particle_Trajectories.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Particle Trajectories\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"One can create particle trajectories from a `DatasetSeries` object for a specified list of particles identified by their unique indices using the `particle_trajectories` method. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"import glob\\n\",\n    \"from os.path import join\\n\",\n    \"\\n\",\n    \"import yt\\n\",\n    \"from yt.config import ytcfg\\n\",\n    \"\\n\",\n    \"path = ytcfg.get(\\\"yt\\\", \\\"test_data_dir\\\")\\n\",\n    \"import matplotlib.pyplot as plt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"First, let's start off with a FLASH dataset containing only two particles in a mutual circular orbit. We can get the list of filenames this way:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"my_fns = glob.glob(join(path, \\\"Orbit\\\", \\\"orbit_hdf5_chk_00[0-9][0-9]\\\"))\\n\",\n    \"my_fns.sort()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"And let's define a list of fields that we want to include in the trajectories. The position fields will be included by default, so let's just ask for the velocity fields:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"fields = [\\\"particle_velocity_x\\\", \\\"particle_velocity_y\\\", \\\"particle_velocity_z\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"There are only two particles, but for consistency's sake let's grab their indices from the dataset itself:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load(my_fns[0])\\n\",\n    \"dd = ds.all_data()\\n\",\n    \"indices = dd[\\\"all\\\", \\\"particle_index\\\"].astype(\\\"int\\\")\\n\",\n    \"print(indices)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"which is what we expected them to be. Now we're ready to create a `DatasetSeries` object and use it to create particle trajectories: \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ts = yt.DatasetSeries(my_fns)\\n\",\n    \"# suppress_logging=True cuts down on a lot of noise\\n\",\n    \"trajs = ts.particle_trajectories(indices, fields=fields, suppress_logging=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `ParticleTrajectories` object `trajs` is essentially a dictionary-like container for the particle fields along the trajectory, and can be accessed as such:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(trajs[\\\"all\\\", \\\"particle_position_x\\\"])\\n\",\n    \"print(trajs[\\\"all\\\", \\\"particle_position_x\\\"].shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that each field is a 2D NumPy array with the different particle indices along the first dimension and the times along the second dimension. As such, we can access them individually by indexing the field:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"plt.figure(figsize=(6, 6))\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_position_x\\\"][0], trajs[\\\"all\\\", \\\"particle_position_y\\\"][0])\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_position_x\\\"][1], trajs[\\\"all\\\", \\\"particle_position_y\\\"][1])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"And we can plot the velocity fields as well:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"plt.figure(figsize=(6, 6))\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_velocity_x\\\"][0], trajs[\\\"all\\\", \\\"particle_velocity_y\\\"][0])\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_velocity_x\\\"][1], trajs[\\\"all\\\", \\\"particle_velocity_y\\\"][1])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If we want to access the time along the trajectory, we use the key `\\\"particle_time\\\"`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"plt.figure(figsize=(6, 6))\\n\",\n    \"plt.plot(trajs[\\\"particle_time\\\"], trajs[\\\"particle_velocity_x\\\"][1])\\n\",\n    \"plt.plot(trajs[\\\"particle_time\\\"], trajs[\\\"particle_velocity_y\\\"][1])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Alternatively, if we know the particle index we'd like to examine, we can get an individual trajectory corresponding to that index:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"particle1 = trajs.trajectory_from_index(1)\\n\",\n    \"plt.figure(figsize=(6, 6))\\n\",\n    \"plt.plot(particle1[\\\"all\\\", \\\"particle_time\\\"], particle1[\\\"all\\\", \\\"particle_position_x\\\"])\\n\",\n    \"plt.plot(particle1[\\\"all\\\", \\\"particle_time\\\"], particle1[\\\"all\\\", \\\"particle_position_y\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now let's look at a more complicated (and fun!) example. We'll use an Enzo cosmology dataset. First, we'll find the maximum density in the domain, and obtain the indices of the particles within some radius of the center. First, let's have a look at what we're getting:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load(\\\"enzo_tiny_cosmology/DD0046/DD0046\\\")\\n\",\n    \"slc = yt.SlicePlot(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"x\\\",\\n\",\n    \"    [(\\\"gas\\\", \\\"density\\\"), (\\\"gas\\\", \\\"dark_matter_density\\\")],\\n\",\n    \"    center=\\\"max\\\",\\n\",\n    \"    width=(3.0, \\\"Mpc\\\"),\\n\",\n    \")\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"So far, so good--it looks like we've centered on a galaxy cluster. Let's grab all of the dark matter particles within a sphere of 0.5 Mpc (identified by `\\\"particle_type == 1\\\"`):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sp = ds.sphere(\\\"max\\\", (0.5, \\\"Mpc\\\"))\\n\",\n    \"indices = sp[\\\"all\\\", \\\"particle_index\\\"][sp[\\\"all\\\", \\\"particle_type\\\"] == 1]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Next we'll get the list of datasets we want, and create trajectories for these particles:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"my_fns = glob.glob(join(path, \\\"enzo_tiny_cosmology/DD*/*.hierarchy\\\"))\\n\",\n    \"my_fns.sort()\\n\",\n    \"ts = yt.DatasetSeries(my_fns)\\n\",\n    \"trajs = ts.particle_trajectories(indices, fields=fields, suppress_logging=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Matplotlib can make 3D plots, so let's pick three particle trajectories at random and look at them in the volume:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"fig = plt.figure(figsize=(8.0, 8.0))\\n\",\n    \"ax = fig.add_subplot(111, projection=\\\"3d\\\")\\n\",\n    \"ax.plot(\\n\",\n    \"    trajs[\\\"all\\\", \\\"particle_position_x\\\"][100],\\n\",\n    \"    trajs[\\\"all\\\", \\\"particle_position_y\\\"][100],\\n\",\n    \"    trajs[\\\"all\\\", \\\"particle_position_z\\\"][100],\\n\",\n    \")\\n\",\n    \"ax.plot(\\n\",\n    \"    trajs[\\\"all\\\", \\\"particle_position_x\\\"][8],\\n\",\n    \"    trajs[\\\"all\\\", \\\"particle_position_y\\\"][8],\\n\",\n    \"    trajs[\\\"all\\\", \\\"particle_position_z\\\"][8],\\n\",\n    \")\\n\",\n    \"ax.plot(\\n\",\n    \"    trajs[\\\"all\\\", \\\"particle_position_x\\\"][25],\\n\",\n    \"    trajs[\\\"all\\\", \\\"particle_position_y\\\"][25],\\n\",\n    \"    trajs[\\\"all\\\", \\\"particle_position_z\\\"][25],\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"It looks like these three different particles fell into the cluster along different filaments. We can also look at their x-positions only as a function of time:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"plt.figure(figsize=(6, 6))\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_time\\\"], trajs[\\\"all\\\", \\\"particle_position_x\\\"][100])\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_time\\\"], trajs[\\\"all\\\", \\\"particle_position_x\\\"][8])\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_time\\\"], trajs[\\\"all\\\", \\\"particle_position_x\\\"][25])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Suppose we wanted to know the gas density along the particle trajectory, but there wasn't a particle field corresponding to that in our dataset. Never fear! If the field exists as a grid field, yt will interpolate this field to the particle positions and add the interpolated field to the trajectory. To add such a field (or any field, including additional particle fields) we can call the `add_fields` method:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"trajs.add_fields([(\\\"gas\\\", \\\"density\\\")])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We also could have included `\\\"density\\\"` in our original field list. Now, plot up the gas density for each particle as a function of time:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"plt.figure(figsize=(6, 6))\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_time\\\"], trajs[\\\"gas\\\", \\\"density\\\"][100])\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_time\\\"], trajs[\\\"gas\\\", \\\"density\\\"][8])\\n\",\n    \"plt.plot(trajs[\\\"all\\\", \\\"particle_time\\\"], trajs[\\\"gas\\\", \\\"density\\\"][25])\\n\",\n    \"plt.yscale(\\\"log\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, the particle trajectories can be written to disk. Two options are provided: ASCII text files with a column for each field and the time, and HDF5 files:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"trajs.write_out(\\n\",\n    \"    \\\"halo_trajectories\\\"\\n\",\n    \")  # This will write a separate file for each trajectory\\n\",\n    \"trajs.write_out_h5(\\n\",\n    \"    \\\"halo_trajectories.h5\\\"\\n\",\n    \")  # This will write all trajectories to a single file\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"anaconda-cloud\": {},\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.12\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/analyzing/_static/axes.c",
    "content": "#include \"axes.h\"\n\nvoid calculate_axes(ParticleCollection *part,\n    double *ax1, double *ax2, double *ax3)\n{\n    int i;\n    for (i = 0; i < part->npart; i++) {\n        if (ax1[0] > part->xpos[i]) ax1[0] = part->xpos[i];\n        if (ax2[0] > part->ypos[i]) ax2[0] = part->ypos[i];\n        if (ax3[0] > part->zpos[i]) ax3[0] = part->zpos[i];\n        if (ax1[1] < part->xpos[i]) ax1[1] = part->xpos[i];\n        if (ax2[1] < part->ypos[i]) ax2[1] = part->ypos[i];\n        if (ax3[1] < part->zpos[i]) ax3[1] = part->zpos[i];\n    }\n}\n"
  },
  {
    "path": "doc/source/analyzing/_static/axes.h",
    "content": "typedef struct structParticleCollection {\n     long npart;\n     double *xpos;\n     double *ypos;\n     double *zpos;\n} ParticleCollection;\n\nvoid calculate_axes(ParticleCollection *part,\n         double *ax1, double *ax2, double *ax3);\n"
  },
  {
    "path": "doc/source/analyzing/_static/axes_calculator.pyx",
    "content": "import numpy as np\ncimport numpy as np\n\n\ncdef extern from \"axes.h\":\n    ctypedef struct ParticleCollection:\n            long npart\n            double *xpos\n            double *ypos\n            double *zpos\n\n    void calculate_axes(ParticleCollection *part,\n             double *ax1, double *ax2, double *ax3)\n\ndef examine_axes(np.ndarray[np.float64_t, ndim=1] xpos,\n                 np.ndarray[np.float64_t, ndim=1] ypos,\n                 np.ndarray[np.float64_t, ndim=1] zpos):\n    cdef double ax1[3]\n    cdef double ax2[3]\n    cdef double ax3[3]\n    cdef ParticleCollection particles\n    cdef int i\n\n    particles.npart = len(xpos)\n    particles.xpos = <double *> xpos.data\n    particles.ypos = <double *> ypos.data\n    particles.zpos = <double *> zpos.data\n\n    for i in range(particles.npart):\n        particles.xpos[i] = xpos[i]\n        particles.ypos[i] = ypos[i]\n        particles.zpos[i] = zpos[i]\n\n    calculate_axes(&particles, ax1, ax2, ax3)\n\n    return ( (ax1[0], ax1[1], ax1[2]),\n             (ax2[0], ax2[1], ax2[2]),\n             (ax3[0], ax3[1], ax3[2]) )\n"
  },
  {
    "path": "doc/source/analyzing/_static/axes_calculator_setup.txt",
    "content": "NAME = \"axes_calculator\"\nEXT_SOURCES = [\"axes.c\"]\nEXT_LIBRARIES = []\nEXT_LIBRARY_DIRS = []\nEXT_INCLUDE_DIRS = []\nDEFINES = []\n\nfrom distutils.core import setup\nfrom distutils.extension import Extension\nfrom Cython.Distutils import build_ext\n\next_modules = [Extension(NAME,\n                 [NAME+\".pyx\"] + EXT_SOURCES,\n                 libraries = EXT_LIBRARIES,\n                 library_dirs = EXT_LIBRARY_DIRS,\n                 include_dirs = EXT_INCLUDE_DIRS,\n                 define_macros = DEFINES)\n]\n\nsetup(\n  name = NAME,\n  cmdclass = {'build_ext': build_ext},\n  ext_modules = ext_modules\n)\n"
  },
  {
    "path": "doc/source/analyzing/astropy_integrations.rst",
    "content": ".. _astropy-integrations:\n\nAstroPy Integrations\n====================\n\nyt enables a number of integrations with the AstroPy package. These\nare listed below, but more detailed descriptions are given at the\ngiven documentation links.\n\nRound-Trip Unit Conversions Between yt and AstroPy\n--------------------------------------------------\n\nAstroPy has a `symbolic units implementation <https://docs.astropy.org/en/stable/units/>`_\nsimilar to that in yt. For this reason, we have implemented \"round-trip\"\nconversions between :class:`~yt.units.yt_array.YTArray` objects\nand AstroPy's :class:`~astropy.units.Quantity` objects. These are implemented\nin the :meth:`~yt.units.yt_array.YTArray.from_astropy` and\n:meth:`~yt.units.yt_array.YTArray.to_astropy` methods.\n\nFITS Image File Reading and Writing\n-----------------------------------\n\nReading and writing FITS files is supported in yt using\n`AstroPy's FITS file handling. <https://docs.astropy.org/en/stable/io/fits/>`_\n\nyt has basic support for reading two and three-dimensional image data from FITS\nfiles. Some limited ability to parse certain types of data (e.g., spectral cubes,\nimages with sky coordinates, images written using the\n:class:`~yt.visualization.fits_image.FITSImageData` class described below) is\npossible. See :ref:`loading-fits-data` for more information.\n\nFixed-resolution two-dimensional images generated from datasets using yt (such as\nslices or projections) and fixed-resolution three-dimensional grids can be written\nto FITS files using yt's :class:`~yt.visualization.fits_image.FITSImageData` class\nand its subclasses. Multiple images can be combined into a single file, operations\ncan be performed on the images and their coordinates, etc. See\n:doc:`../visualizing/FITSImageData` for more information.\n\nConverting Field Container and 1D Profile Data to AstroPy Tables\n----------------------------------------------------------------\n\nData in field containers, such as spheres, rectangular regions, rays,\ncylinders, etc., are represented as 1D YTArrays. A set of these arrays\ncan then be exported to an\n`AstroPy Table <http://docs.astropy.org/en/stable/table/>`_ object,\nspecifically a\n`QTable <http://docs.astropy.org/en/stable/table/mixin_columns.html#quantity-and-qtable>`_.\n``QTable`` is unit-aware, and can be manipulated in a number of ways\nand written to disk in several formats, including ASCII text or FITS\nfiles.\n\nSimilarly, 1D profile objects can also be exported to AstroPy\n``QTable``, optionally writing all of the profile bins or only the ones\nwhich are used. For more details, see :ref:`profile-astropy-export`.\n"
  },
  {
    "path": "doc/source/analyzing/domain_analysis/XrayEmissionFields.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# X-ray Emission Fields\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"> Note: If you came here trying to figure out how to create simulated X-ray photons and observations,\\n\",\n    \"  you should go [here](http://hea-www.cfa.harvard.edu/~jzuhone/pyxsim/) instead.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This functionality provides the ability to create metallicity-dependent X-ray luminosity, emissivity, and photon emissivity fields for a given photon energy range.  This works by interpolating from emission tables created from the photoionization code [Cloudy](https://www.nublado.org/) or the collisional ionization database [AtomDB](http://www.atomdb.org). These can be downloaded from https://yt-project.org/data from the command line like so:\\n\",\n    \"\\n\",\n    \"`# Put the data in a directory you specify`  \\n\",\n    \"`yt download cloudy_emissivity_v2.h5 /path/to/data`\\n\",\n    \"\\n\",\n    \"`# Put the data in the location set by \\\"supp_data_dir\\\"`  \\n\",\n    \"`yt download apec_emissivity_v3.h5 supp_data_dir`\\n\",\n    \"\\n\",\n    \"The data path can be a directory on disk, or it can be \\\"supp_data_dir\\\", which will download the data to the directory specified by the `\\\"supp_data_dir\\\"` yt configuration entry. It is easiest to put these files in the directory from which you will be running yt or `\\\"supp_data_dir\\\"`, but see the note below about putting them in alternate locations.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Emission fields can be made for any energy interval between 0.1 keV and 100 keV, and will always be created for luminosity $(\\\\rm{erg~s^{-1}})$, emissivity $\\\\rm{(erg~s^{-1}~cm^{-3})}$, and photon emissivity $\\\\rm{(photons~s^{-1}~cm^{-3})}$.  The only required arguments are the\\n\",\n    \"dataset object, and the minimum and maximum energies of the energy band. However, typically one needs to decide what will be used for the metallicity. This can either be a floating-point value representing a spatially constant metallicity, or a prescription for a metallicity field, e.g. `(\\\"gas\\\", \\\"metallicity\\\")`. For this first example, where the dataset has no metallicity field, we'll just assume $Z = 0.3~Z_\\\\odot$ everywhere:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\\n\",\n    \"\\n\",\n    \"ds = yt.load(\\n\",\n    \"    \\\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\\\", default_species_fields=\\\"ionized\\\"\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"xray_fields = yt.add_xray_emissivity_field(\\n\",\n    \"    ds, 0.5, 7.0, table_type=\\\"apec\\\", metallicity=0.3\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"> Note: If you place the HDF5 emissivity tables in a location other than the current working directory or the location \\n\",\n    \"  specified by the \\\"supp_data_dir\\\" configuration value, you will need to specify it in the call to \\n\",\n    \"  `add_xray_emissivity_field`:  \\n\",\n    \"  `xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, data_dir=\\\"/path/to/data\\\", table_type='apec', metallicity=0.3)`\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Having made the fields, one can see which fields were made:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(xray_fields)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The luminosity field is useful for summing up in regions like this:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sp = ds.sphere(\\\"c\\\", (2.0, \\\"Mpc\\\"))\\n\",\n    \"print(sp.quantities.total_quantity((\\\"gas\\\", \\\"xray_luminosity_0.5_7.0_keV\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Whereas the emissivity fields may be useful in derived fields or for plotting:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    [\\n\",\n    \"        (\\\"gas\\\", \\\"xray_emissivity_0.5_7.0_keV\\\"),\\n\",\n    \"        (\\\"gas\\\", \\\"xray_photon_emissivity_0.5_7.0_keV\\\"),\\n\",\n    \"    ],\\n\",\n    \"    width=(0.75, \\\"Mpc\\\"),\\n\",\n    \")\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The emissivity and the luminosity fields take the values one would see in the frame of the source. However, if one wishes to make projections of the X-ray emission from a cosmologically distant object, the energy band will be redshifted. For this case, one can supply a `redshift` parameter and a `Cosmology` object (either from the dataset or one made on your own) to compute X-ray intensity fields along with the emissivity and luminosity fields.\\n\",\n    \"\\n\",\n    \"This example shows how to do that, Where we also use a spatially dependent metallicity field and the Cloudy tables instead of the APEC tables we used previously:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ds2 = yt.load(\\\"D9p_500/10MpcBox_HartGal_csf_a0.500.d\\\", default_species_fields=\\\"ionized\\\")\\n\",\n    \"\\n\",\n    \"# In this case, use the redshift and cosmology from the dataset,\\n\",\n    \"# but in theory you could put in something different\\n\",\n    \"xray_fields2 = yt.add_xray_emissivity_field(\\n\",\n    \"    ds2,\\n\",\n    \"    0.5,\\n\",\n    \"    2.0,\\n\",\n    \"    redshift=ds2.current_redshift,\\n\",\n    \"    cosmology=ds2.cosmology,\\n\",\n    \"    metallicity=(\\\"gas\\\", \\\"metallicity\\\"),\\n\",\n    \"    table_type=\\\"cloudy\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, one can see that two new fields have been added, corresponding to X-ray intensity / surface brightness when projected:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(xray_fields2)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note also that the energy range now corresponds to the *observer* frame, whereas in the source frame the energy range is between `emin*(1+redshift)` and `emax*(1+redshift)`. Let's zoom in on a galaxy and make a projection of the energy intensity field:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ProjectionPlot(\\n\",\n    \"    ds2, \\\"x\\\", (\\\"gas\\\", \\\"xray_intensity_0.5_2.0_keV\\\"), center=\\\"max\\\", width=(40, \\\"kpc\\\")\\n\",\n    \")\\n\",\n    \"prj.set_zlim(\\\"xray_intensity_0.5_2.0_keV\\\", 1.0e-32, 5.0e-24)\\n\",\n    \"prj.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"> Warning: The X-ray fields depend on the number density of hydrogen atoms, given by the yt field\\n\",\n    \"  `H_nuclei_density`. In the case of the APEC model, this assumes that all of the hydrogen in your\\n\",\n    \"  dataset is ionized, whereas in the Cloudy model the ionization level is taken into account. If \\n\",\n    \"  this field is not defined (either in the dataset or by the user), it will be constructed using\\n\",\n    \"  abundance information from your dataset. Finally, if your dataset contains no abundance information,\\n\",\n    \"  a primordial hydrogen mass fraction (X = 0.76) will be assumed.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, if you want to place the source at a local, non-cosmological distance, you can forego the `redshift` and `cosmology` arguments and supply a `dist` argument instead, which is either a `(value, unit)` tuple or a `YTQuantity`. Note that here the redshift is assumed to be zero. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"xray_fields3 = yt.add_xray_emissivity_field(\\n\",\n    \"    ds2,\\n\",\n    \"    0.5,\\n\",\n    \"    2.0,\\n\",\n    \"    dist=(1.0, \\\"Mpc\\\"),\\n\",\n    \"    metallicity=(\\\"gas\\\", \\\"metallicity\\\"),\\n\",\n    \"    table_type=\\\"cloudy\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ProjectionPlot(\\n\",\n    \"    ds2,\\n\",\n    \"    \\\"x\\\",\\n\",\n    \"    (\\\"gas\\\", \\\"xray_photon_intensity_0.5_2.0_keV\\\"),\\n\",\n    \"    center=\\\"max\\\",\\n\",\n    \"    width=(40, \\\"kpc\\\"),\\n\",\n    \")\\n\",\n    \"prj.set_zlim(\\\"xray_photon_intensity_0.5_2.0_keV\\\", 1.0e-24, 5.0e-16)\\n\",\n    \"prj.show()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"anaconda-cloud\": {},\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.12\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/analyzing/domain_analysis/clump_finding.rst",
    "content": ".. _clump_finding:\n\nClump Finding\n=============\n\nThe clump finder uses a contouring algorithm to identified topologically\ndisconnected structures within a dataset.  This works by first creating a\nsingle contour over the full range of the contouring field, then continually\nincreasing the lower value of the contour until it reaches the maximum value\nof the field.  As disconnected structures are identified as separate contours,\nthe routine continues recursively through each object, creating a hierarchy of\nclumps.  Individual clumps can be kept or removed from the hierarchy based on\nthe result of user-specified functions, such as checking for gravitational\nboundedness.  A sample recipe can be found in :ref:`cookbook-find_clumps`.\n\nSetting up the Clump Finder\n---------------------------\n\nThe clump finder requires a data object (see :ref:`data-objects`) and a field\nover which the contouring is to be performed.  The data object is then used\nto create the initial\n:class:`~yt.data_objects.level_sets.clump_handling.Clump` object that\nacts as the base for clump finding.\n\n.. code:: python\n\n   import yt\n   from yt.data_objects.level_sets.api import *\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n   data_source = ds.disk([0.5, 0.5, 0.5], [0.0, 0.0, 1.0], (8, \"kpc\"), (1, \"kpc\"))\n\n   master_clump = Clump(data_source, (\"gas\", \"density\"))\n\nClump Validators\n----------------\n\nAt this point, every isolated contour will be considered a clump,\nwhether this is physical or not.  Validator functions can be added to\ndetermine if an individual contour should be considered a real clump.\nThese functions are specified with the\n:func:`~yt.data_objects.level_sets.clump_handling.Clump.add_validator`\nfunction.  Current, two validators exist: a minimum number of cells and gravitational\nboundedness.\n\n.. code:: python\n\n   master_clump.add_validator(\"min_cells\", 20)\n\n   master_clump.add_validator(\"gravitationally_bound\", use_particles=False)\n\nAs many validators as desired can be added, and a clump is only kept if all\nreturn True.  If not, a clump is remerged into its parent.  Custom validators\ncan easily be added.  A validator function must only accept a ``Clump`` object\nand either return True or False.\n\n.. code:: python\n\n   def _minimum_gas_mass(clump, min_mass):\n       return clump[\"gas\", \"mass\"].sum() >= min_mass\n\n\n   add_validator(\"minimum_gas_mass\", _minimum_gas_mass)\n\nThe :func:`~yt.data_objects.level_sets.clump_validators.add_validator`\nfunction adds the validator to a registry that can\nbe accessed by the clump finder.  Then, the validator can be added to the\nclump finding just like the others.\n\n.. code:: python\n\n   master_clump.add_validator(\"minimum_gas_mass\", ds.quan(1.0, \"Msun\"))\n\nRunning the Clump Finder\n------------------------\n\nClump finding then proceeds by calling the\n:func:`~yt.data_objects.level_sets.clump_handling.find_clumps` function.\nThis function accepts the\n:class:`~yt.data_objects.level_sets.clump_handling.Clump` object, the initial\nminimum and maximum of the contouring field, and the step size.  The lower value\nof the contour finder will be continually multiplied by the step size.\n\n.. code:: python\n\n   c_min = data_source[\"gas\", \"density\"].min()\n   c_max = data_source[\"gas\", \"density\"].max()\n   step = 2.0\n   find_clumps(master_clump, c_min, c_max, step)\n\nCalculating Clump Quantities\n----------------------------\n\nBy default, a number of quantities will be calculated for each clump when the\nclump finding process has finished.  The default quantities are: ``total_cells``,\n``mass``, ``mass_weighted_jeans_mass``, ``volume_weighted_jeans_mass``,\n``max_grid_level``, ``min_number_density``, and ``max_number_density``.\nAdditional items can be added with the\n:func:`~yt.data_objects.level_sets.clump_handling.Clump.add_info_item`\nfunction.\n\n.. code:: python\n\n   master_clump.add_info_item(\"total_cells\")\n\nJust like the validators, custom info items can be added by defining functions\nthat minimally accept a\n:class:`~yt.data_objects.level_sets.clump_handling.Clump` object and return\na format string to be printed and the value.  These are then added to the list\nof available info items by calling\n:func:`~yt.data_objects.level_sets.clump_info_items.add_clump_info`:\n\n.. code:: python\n\n   def _mass_weighted_jeans_mass(clump):\n       jeans_mass = clump.data.quantities.weighted_average_quantity(\n           \"jeans_mass\", (\"gas\", \"mass\")\n       ).in_units(\"Msun\")\n       return \"Jeans Mass (mass-weighted): %.6e Msolar.\" % jeans_mass\n\n\n   add_clump_info(\"mass_weighted_jeans_mass\", _mass_weighted_jeans_mass)\n\nThen, add it to the list:\n\n.. code:: python\n\n   master_clump.add_info_item(\"mass_weighted_jeans_mass\")\n\nOnce you have run the clump finder, you should be able to access the data for\nthe info item you have defined via the ``info`` attribute of a ``Clump`` object:\n\n.. code:: python\n\n   clump = leaf_clumps[0]\n   print(clump.info[\"mass_weighted_jeans_mass\"])\n\nBesides the quantities calculated by default, the following are available:\n``center_of_mass`` and ``distance_to_main_clump``.\n\nWorking with Clumps\n-------------------\n\nAfter the clump finding has finished, the master clump will represent the top\nof a hierarchy of clumps.  The ``children`` attribute within a\n:class:`~yt.data_objects.level_sets.clump_handling.Clump` object\ncontains a list of all sub-clumps.  Each sub-clump is also a\n:class:`~yt.data_objects.level_sets.clump_handling.Clump` object\nwith its own ``children`` attribute, and so on.\n\n.. code:: python\n\n   print(master_clump[\"gas\", \"density\"])\n   print(master_clump.children)\n   print(master_clump.children[0][\"gas\", \"density\"])\n\nThe entire clump tree can traversed with a loop syntax:\n\n.. code:: python\n\n   for clump in master_clump:\n       print(clump.clump_id)\n\nThe ``leaves`` attribute of a ``Clump`` object will return a list of the\nindividual clumps that have no children of their own (the leaf clumps).\n\n.. code:: python\n\n   # Get a list of just the leaf nodes.\n   leaf_clumps = master_clump.leaves\n\n   print(leaf_clumps[0][\"gas\", \"density\"])\n   print(leaf_clumps[0][\"all\", \"particle_mass\"])\n   print(leaf_clumps[0].quantities.total_mass())\n\nVisualizing Clumps\n------------------\n\nClumps can be visualized using the ``annotate_clumps`` callback.\n\n.. code:: python\n\n   prj = yt.ProjectionPlot(ds, 2, (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n   prj.annotate_clumps(leaf_clumps)\n   prj.save(\"clumps\")\n\nSaving and Reloading Clump Data\n-------------------------------\n\nThe clump tree can be saved as a reloadable dataset with the\n:func:`~yt.data_objects.level_sets.clump_handling.Clump.save_as_dataset`\nfunction.  This will save all info items that have been calculated as well as\nany field values specified with the *fields* keyword.  This function\ncan be called for any clump in the tree, saving that clump and all those\nbelow it.\n\n.. code:: python\n\n   fn = master_clump.save_as_dataset(fields=[\"density\", \"particle_mass\"])\n\nThe clump tree can then be reloaded as a regular dataset.  The ``tree`` attribute\nassociated with the dataset provides access to the clump tree.  The tree can be\niterated over in the same fashion as the original tree.\n\n.. code:: python\n\n   ds_clumps = yt.load(fn)\n   for clump in ds_clumps.tree:\n       print(clump.clump_id)\n\nThe ``leaves`` attribute returns a list of all leaf clumps.\n\n.. code:: python\n\n   print(ds_clumps.leaves)\n\nInfo items for each clump can be accessed with the ``\"clump\"`` field type.  Gas\nor grid fields should be accessed using the ``\"grid\"`` field type and particle\nfields should be access using the specific particle type.\n\n.. code:: python\n\n   my_clump = ds_clumps.leaves[0]\n   print(my_clumps[\"clump\", \"mass\"])\n   print(my_clumps[\"grid\", \"density\"])\n   print(my_clumps[\"all\", \"particle_mass\"])\n"
  },
  {
    "path": "doc/source/analyzing/domain_analysis/cosmology_calculator.rst",
    "content": ".. _cosmology-calculator:\n\nCosmology Calculator\n====================\n\nThe cosmology calculator can be used to calculate cosmological distances and\ntimes given a set of cosmological parameters.  A cosmological dataset, ``ds``,\nwill automatically have a cosmology calculator configured with the correct\nparameters associated with it as ``ds.cosmology``.  A standalone\n:class:`~yt.utilities.cosmology.Cosmology` calculator object can be created\nin the following way:\n\n.. code-block:: python\n\n   from yt.utilities.cosmology import Cosmology\n\n   co = Cosmology(\n       hubble_constant=0.7,\n       omega_matter=0.3,\n       omega_lambda=0.7,\n       omega_curvature=0.0,\n       omega_radiation=0.0,\n   )\n\nOnce created, various distance calculations as well as conversions between\nredshift and time are available:\n\n.. notebook-cell::\n\n   from yt.utilities.cosmology import Cosmology\n\n   co = Cosmology()\n\n   # Hubble distance (c / h)\n   print(\"hubble distance\", co.hubble_distance())\n\n   # distance from z = 0 to 0.5\n   print(\"comoving radial distance\", co.comoving_radial_distance(0, 0.5).in_units(\"Mpccm/h\"))\n\n   # transverse distance\n   print(\"transverse distance\", co.comoving_transverse_distance(0, 0.5).in_units(\"Mpccm/h\"))\n\n   # comoving volume\n   print(\"comoving volume\", co.comoving_volume(0, 0.5).in_units(\"Gpccm**3\"))\n\n   # angular diameter distance\n   print(\"angular diameter distance\", co.angular_diameter_distance(0, 0.5).in_units(\"Mpc/h\"))\n\n   # angular scale\n   print(\"angular scale\", co.angular_scale(0, 0.5).in_units(\"Mpc/degree\"))\n\n   # luminosity distance\n   print(\"luminosity distance\", co.luminosity_distance(0, 0.5).in_units(\"Mpc/h\"))\n\n   # time between two redshifts\n   print(\"lookback time\", co.lookback_time(0, 0.5).in_units(\"Gyr\"))\n\n   # critical density\n   print(\"critical density\", co.critical_density(0))\n\n   # Hubble parameter at a given redshift\n   print(\"hubble parameter\", co.hubble_parameter(0).in_units(\"km/s/Mpc\"))\n\n   # convert time after Big Bang to redshift\n   my_t = co.quan(8, \"Gyr\")\n   print(\"z from t\", co.z_from_t(my_t))\n\n   # convert redshift to time after Big Bang\n   print(\"t from z\", co.t_from_z(0.5).in_units(\"Gyr\"))\n\n.. warning::\n\n   Cosmological distance calculations return values that are either\n   in the comoving or proper frame, depending on the specific quantity.  For\n   simplicity, the proper and comoving frames are set equal to each other\n   within the cosmology calculator.  This means that for some distance value,\n   x, x.to(\"Mpc\") and x.to(\"Mpccm\") will be the same.  The user should take\n   care to understand which reference frame is correct for the given calculation.\n\nThe helper functions, ``co.quan``\nand ``co.arr`` exist to create unitful ``YTQuantities`` and ``YTArray`` with the\nunit registry of the cosmology calculator.  For more information on the usage\nand meaning of each calculation, consult the reference documentation at\n:ref:`cosmology-calculator-ref`.\n"
  },
  {
    "path": "doc/source/analyzing/domain_analysis/index.rst",
    "content": ".. _domain-analysis:\n\nDomain-Specific Analysis\n========================\n\nyt powers a number modules that provide specialized analysis tools\nrelevant to one or a few domains. Some of these are internal to yt,\nbut many exist as external packages, either maintained by the yt\nproject or independently.\n\nInternal Analysis Modules\n-------------------------\n\nThese modules exist within yt itself.\n\n.. note::\n\n   As of yt version 3.5, most of the astrophysical analysis tools\n   have been moved to the :ref:`yt-astro` and :ref:`attic`\n   packages. See below for more information.\n\n.. toctree::\n   :maxdepth: 2\n\n   cosmology_calculator\n   clump_finding\n   XrayEmissionFields\n   xray_data_README\n\nExternal Analysis Modules\n-------------------------\n\nThese are external packages maintained by the yt project.\n\n.. _yt-astro:\n\nyt Astro Analysis\n^^^^^^^^^^^^^^^^^\n\nSource: https://github.com/yt-project/yt_astro_analysis\n\nDocumentation: https://yt-astro-analysis.readthedocs.io/\n\nThe ``yt_astro_analysis`` package houses most of the astrophysical\nanalysis tools that were formerly in the ``yt.analysis_modules``\nimport. These include halo finding, custom halo analysis, synthetic\nobservations, and exports to radiative transfer codes. See\n:ref:`yt_astro_analysis:modules` for a list of available\nfunctionality.\n\n.. _attic:\n\nyt Attic\n^^^^^^^^\n\nSource: https://github.com/yt-project/yt_attic\n\nDocumentation: https://yt-attic.readthedocs.io/\n\nThe ``yt_attic`` contains former yt analysis modules that have\nfallen by the wayside. These may have small bugs or were simply\nnot kept up to date as yt evolved. Tools in here are looking for\na new owner and a new home. If you find something in here that\nyou'd like to bring back to life, either by adding it to\n:ref:`yt-astro` or as part of your own package, you are welcome\nto it! If you'd like any help, let us know! See\n:ref:`yt_attic:attic-modules` for a list of inventory of the\nattic.\n\nExtensions\n----------\n\nThere are a number of independent, yt-related packages for things\nlike visual effects, interactive widgets, synthetic absorption\nspectra, X-ray observations, and merger-trees. See the\n`yt Extensions <http://yt-project.org/extensions.html>`_ page for\na list of available extension packages.\n"
  },
  {
    "path": "doc/source/analyzing/domain_analysis/xray_data_README.rst",
    "content": ".. _xray_data_README:\n\nAuxiliary Data Files for use with yt's Photon Simulator\n=======================================================\n\nIncluded in the `xray_data <https://yt-project.org/data/xray_data.tar.gz>`_ package are a number of files that you may find\nuseful when working with yt's X-ray `photon_simulator\n<photon_simulator.html>`_  analysis module. They have been tested to give spectral fitting results\nconsistent with input parameters.\n\nSpectral Model Tables\n---------------------\n\n* tbabs_table.h5:\n\n  Tabulated values of the galactic absorption cross-section in HDF5\n  format, generated from the routines at http://pulsar.sternwarte.uni-erlangen.de/wilms/research/tbabs/\n\nARFs and RMFs\n-------------\n\nWe have tested the following ARFs and RMFs with the photon\nsimulator. These can be used to generate a very simplified\nrepresentation of an X-ray observation, using a uniform, on-axis\nresponse. For more accurate models of X-ray observations we suggest\nusing MARX or SIMX (detailed below_).\n\n* Chandra: chandra_ACIS-S3_onaxis_arf.fits, chandra_ACIS-S3_onaxis_rmf.fits\n\n  Generated from the CIAO tools, on-axis on the ACIS-S3 chip.\n\n* XMM-Newton: pn-med.arf, pn-med.rmf\n\n  EPIC pn CCDs (medium filter), taken from SIMX\n\n* Astro-H: sxt-s_100208_ts02um_intall.arf, ah_sxs_7ev_basefilt_20090216.rmf\n\n  SXT-S+SXS responses taken from http://astro-h.isas.jaxa.jp/researchers/sim/response.html\n\n* NuSTAR: nustarA.arf, nustarA.rmf\n\n  Averaged responses for NuSTAR telescope A generated by Dan Wik (NASA/GSFC)\n\n.. _below:\n\n\nOther Useful Things Not Included Here\n-------------------------------------\n\n* AtomDB: http://www.atomdb.org\n\n  FITS table data for emission lines and continuum emission. Must have\n  it installed to use the TableApecModel spectral model.\n\n* PyXspec: https://heasarc.gsfc.nasa.gov/xanadu/xspec/python/html/\n\n  Python interface to the XSPEC spectral-fitting program. Two of the\n  spectral models for the photon simulator use it.\n\n* MARX: https://space.mit.edu/ASC/MARX/\n\n  Detailed ray-trace simulations of Chandra.\n\n* SIMX: http://hea-www.harvard.edu/simx/\n\n  Simulates a photon-counting detector's response to an input source,\n  including a simplified models of telescopes.\n"
  },
  {
    "path": "doc/source/analyzing/fields.rst",
    "content": ".. _fields:\n\nFields in yt\n============\n\nFields are spatially-dependent quantities associated with a parent dataset.\nExamples of fields are gas density, gas temperature, particle mass, etc.\nThe fundamental way to query data in yt is to access a field, either in its raw\nform (by examining a data container) or a processed form (derived quantities,\nprojections, aggregations, and so on).  \"Field\" is something of a loaded word,\nas it can refer to quantities that are defined everywhere, which we refer to as\n\"mesh\" or \"fluid\" fields, or discrete points that populate the domain,\ntraditionally thought of as \"particle\" fields.  The word \"particle\" here is\ngradually falling out of favor, as these discrete fields can be any type of\nsparsely populated data.\n\nIf you are developing a frontend or need to customize what yt thinks of as the\nfields for a given datast, see both :ref:`per-field-plotconfig` and\n:ref:`per-field-config` for information on how to change the display units,\non-disk units, display name, etc.\n\n.. _what-are-fields:\n\nWhat are fields?\n----------------\n\nFields in yt are denoted by a two-element string tuple, of the form ``(field_type,\nfield_name)``. The first element, the \"field type\" is a category for a\nfield. Possible field types used in yt include ``'gas'`` (for fluid mesh fields\ndefined on a mesh) or ``'io'`` (for fields defined at particle locations). Field\ntypes can also correspond to distinct particle of fluid types in a single\nsimulation. For example, a plasma physics simulation using the Particle in Cell\nmethod might have particle types corresponding to ``'electrons'`` and ``'ions'``. See\n:ref:`known-field-types` below for more info about field types in yt.\n\nThe second element of field tuples, the ``field_name``, denotes the specific field\nto select, given the field type. Possible field names include ``'density'``,\n``'velocity_x'`` or ``'pressure'`` --- these three fields are examples of field names\nthat might be used for a fluid defined on a mesh. Examples of particle fields\ninclude ``'particle_mass'``, ``'particle_position'`` or ``'particle_velocity_x'``. In\ngeneral, particle field names are prefixed by ``particle_``, which makes it easy\nto distinguish between a particle field or a mesh field when no field type is\nprovided.\n\nWhat fields are available?\n--------------------------\n\nWe provide a full list of fields that yt recognizes by default at\n:ref:`field-list`.  If you want to create additional custom derived fields,\nsee :ref:`creating-derived-fields`.\n\nEvery dataset has an attribute, ``ds.fields``.  This attribute possesses\nattributes itself, each of which is a \"field type,\" and each field type has as\nits attributes the fields themselves.  When one of these is printed, it returns\ninformation about the field and things like units and so on.  You can use this\nfor tab-completing as well as easier access to information.\n\nAdditionally, if you have `ipywidgets\n<https://ipywidgets.readthedocs.io/en/stable/>`_ installed and are in a `Jupyter\nenvironment <https://jupyter.org/>`_, you can view the rich representation of\nthe fields (including source code) by either typing ``ds.fields`` as the last\nitem in a cell or by calling ``display(ds.fields)``.  The resulting output will\nhave tabs and source:\n\n.. image:: _images/fields_ipywidget.png\n   :scale: 50%\n\nAs an example, you might browse the available fields like so:\n\n.. code-block:: python\n\n  print(dir(ds.fields))\n  print(dir(ds.fields.gas))\n  print(ds.fields.gas.density)\n\nOn an Enzo dataset, the result from the final command would look something like\nthis:::\n\n  Alias Field for ('enzo', 'Density') ('gas', 'density'): (units: 'g/cm**3')\n\nYou can use this to easily explore available fields, particularly through\ntab-completion in Jupyter/IPython.\n\nIt's also possible to iterate over the list of fields associated with each\nfield type. For example, to print all of the ``'gas'`` fields, one might do:\n\n.. code-block:: python\n\n   for field in ds.fields.gas:\n       print(field)\n\nYou can also check if a given field is associated with a field type using\nstandard python syntax:\n\n.. code-block:: python\n\n   # these examples evaluate to True for a dataset that has ('gas', 'density')\n   \"density\" in ds.fields.gas\n   (\"gas\", \"density\") in ds.fields.gas\n   ds.fields.gas.density in ds.fields.gas\n\nFor a more programmatic method of accessing fields, you can utilize the\n``ds.field_list``, ``ds.derived_field_list`` and some accessor methods to gain\ninformation about fields.  The full list of fields available for a dataset can\nbe found as the attribute ``field_list`` for native, on-disk fields and\n``derived_field_list`` for derived fields (``derived_field_list`` is a superset\nof ``field_list``).  You can view these lists by examining a dataset like this:\n\n.. code-block:: python\n\n   ds = yt.load(\"my_data\")\n   print(ds.field_list)\n   print(ds.derived_field_list)\n\nBy using the ``field_info()`` class, one can access information about a given\nfield, like its default units or the source code for it.\n\n.. code-block:: python\n\n   ds = yt.load(\"my_data\")\n   ds.index\n   print(ds.field_info[\"gas\", \"pressure\"].get_units())\n   print(ds.field_info[\"gas\", \"pressure\"].get_source())\n\nUsing fields to access data\n---------------------------\n\n.. warning::\n\n   These *specific* operations will load the entire field -- which can be\n   extremely memory intensive with large datasets!  If you are looking to\n   compute quantities, see :ref:`Data-objects` for methods for computing\n   aggregates, averages, subsets, regriddings, etc.\n\nThe primary *use* of fields in yt is to access data from a dataset. For example,\nif I want to use a data object (see :ref:`Data-objects` for more detail about\ndata objects) to access the ``('gas', 'density')`` field, one can do any of the\nfollowing:\n\n.. code-block:: python\n\n    ad = ds.all_data()\n\n    # just a field name\n    density = ad[\"density\"]\n\n    # field tuple with no parentheses\n    density = ad[\"gas\", \"density\"]\n\n    # full field tuple\n    density = ad[\"gas\", \"density\"]\n\n    # through the ds.fields object\n    density = ad[ds.fields.gas.density]\n\nThe first data access example is the simplest. In that example, the field type\nis inferred from the name of the field. However, an error will be raised if there are multiple\nfield names that could be meant by this simple string access.  The next two examples\nuse the field type explicitly, this might be necessary if there is more than one field\ntype with a ``'density'`` field defined in the same dataset. The third example is slightly\nmore verbose but is syntactically identical to the second example due to the way\nindexing works in the Python language.\n\nThe final example uses the ``ds.fields`` object described above. This way of\naccessing fields lends itself to interactive use, especially if you make heavy\nuse of IPython's tab completion features. Any of these ways of denoting the\n``('gas', 'density')`` field can be used when supplying a field name to a yt\ndata object, analysis routines, or plotting and visualization function.\n\nAccessing Fields without a Field Type\n-------------------------------------\n\nIn previous versions of yt, there was a single mechanism of accessing fields on\na data container -- by their name, which was mandated to be a single string, and\nwhich often varied between different code frontends.  yt 3.0 allows for datasets\ncontaining multiple different types of fluid fields, mesh fields, particles\n(with overlapping or disjoint lists of fields). However, to preserve backward\ncompatibility and make interactive use simpler, yt 4.1 and newer will still\naccept field names given as a string *if and only if they match exactly one\nexisting field*.\n\nAs an example, we may be in a situation where have multiple types of particles\nwhich possess the ``'particle_position'`` field.  In the case where a data\ncontainer, here called ``ad`` (short for \"all data\") contains a field, we can\nspecify which particular particle type we want to query:\n\n.. code-block:: python\n\n   print(ad[\"dark_matter\", \"particle_position\"])\n   print(ad[\"stars\", \"particle_position\"])\n   print(ad[\"black_holes\", \"particle_position\"])\n\nEach of these three fields may have different sizes.  In order to enable\nfalling back on asking only for a field by the name, yt will use the most\nrecently requested field type for subsequent queries.  (By default, if no field\nhas been queried, it will look for the special field ``'all'``, which\nconcatenates all particle types.)  For example, if I were to then query for the\nvelocity:\n\n.. code-block:: python\n\n   print(ad[\"particle_velocity\"])\n\nit would select ``black_holes`` as the field type, since the last field accessed\nused that field type.\n\nThe same operations work for fluid and mesh fields.  As an example, in some\ncosmology simulations, we may want to examine the mass of particles in a region\nversus the mass of gas.  We can do so by examining the special \"deposit\" field\ntypes (described below) versus the gas fields:\n\n.. code-block:: python\n\n   print(ad[\"deposit\", \"dark_matter_density\"] / ad[\"gas\", \"density\"])\n\nThe ``'deposit'`` field type is a mesh field, so it will have the same shape as\nthe gas density.  If we weren't using ``'deposit'``, and instead directly\nquerying a particle field, this *wouldn't* work, as they are different shapes.\nThis is the primary difference, in practice, between mesh and particle fields\n-- they will be different shapes and so cannot be directly compared without\ntranslating one to the other, typically through a \"deposition\" or \"smoothing\"\nstep.\n\nHow are fields implemented?\n---------------------------\n\nThere are two classes of fields in yt.  The first are those fields that exist\nexternal to yt, which are immutable and can be queried -- most commonly, these\nare fields that exist on disk.  These will often be returned in units that are\nnot in a known, external unit system (except possibly by design, on the part of\nthe code that wrote the data), and yt will take every effort possible to use\nthe names by which they are referred to by the data producer.  The default\nfield type for mesh fields that are \"on-disk\" is the name of the code frontend.\n(For example, ``'art'``, ``'enzo'``, ``'pyne'``, and so on.) The default name for\nparticle fields, if they do not have a particle type affiliated with them, is\n``'io'``.\n\nThe second class of field is the \"derived field.\"  These are fields that are\nfunctionally defined, either *ab initio* or as a transformation or combination\nof other fields.  For example, when dealing with simulation codes, often the\nfields that are evolved and output to disk are not the fields that are the most\nrelevant to researchers.  Rather than examining the internal gas energy, it is\nmore convenient to think of the temperature.  By applying one or multiple\nfunctions to on-disk quantities, yt can construct new derived fields from them.\nDerived fields do not always have to relate to the data found on disk; special\nfields such as ``'x'``, ``'y'``, ``'phi'`` and ``'dz'`` all relate exclusively to the\ngeometry of the mesh, and provide information about the mesh that can be used\nelsewhere for further transformations.\n\nFor more information, see :ref:`creating-derived-fields`.\n\nThere is a third, borderline class of field in yt, as well.  This is the\n\"alias\" type, where a field on disk (for example, (``'*frontend*''``, ``'Density'``)) is\naliased into an internal yt-name (for example, (``'gas'``, ``'density'``)). The\naliasing process allows universally-defined derived fields to take advantage of\ninternal names, and it also provides an easy way to address what units something\nshould be returned in.  If an aliased field is requested (and aliased fields\nwill always be lowercase, with underscores separating words) it will be returned\nin the units specified by the unit system of the database, whereas if the\nfrontend-specific field is requested, it will not undergo any unit conversions\nfrom its natural units.  (This rule is occasionally violated for fields which\nare mesh-dependent, specifically particle masses in some cosmology codes.)\n\n.. _known-field-types:\n\nField types known to yt\n-----------------------\n\nRecall that fields are formally accessed in two parts: ``('*field type*',\n'*field name*')``.  Here we describe the different field types you will encounter:\n\n* frontend-name -- Mesh or fluid fields that exist on-disk default to having\n  the name of the frontend as their type name (e.g., ``'enzo'``, ``'flash'``,\n  ``'pyne'`` and so on).  The units of these types are whatever units are\n  designated by the source frontend when it writes the data.\n* ``'index'`` -- This field type refers to characteristics of the mesh, whether\n  that mesh is defined by the simulation or internally by an octree indexing\n  of particle data.  A few handy fields are ``'x'``, ``'y'``, ``'z'``, ``'theta'``,\n  ``'phi'``, ``'radius'``, ``'dx'``, ``'dy'``, ``'dz'`` and so on.  Default units\n  are in CGS.\n* ``'gas'`` -- This is the usual default for simulation frontends for fluid\n  types.  These fields are typically aliased to the frontend-specific mesh\n  fields for grid-based codes or to the deposit fields for particle-based\n  codes.  Default units are in the unit system of the dataset.\n* particle type -- These are particle fields that exist on-disk as written\n  by individual frontends.  If the frontend designates names for these particles\n  (i.e. particle type) those names are the field types.\n  Additionally, any particle unions or filters will be accessible as field\n  types.  Examples of particle types are ``'Stars'``, ``'DM'``, ``'io'``, etc.\n  Like the front-end specific mesh or fluid fields, the units of these fields\n  are whatever was designated by the source frontend when written to disk.\n* ``'io'`` -- If a data frontend does not have a set of multiple particle types,\n  this is the default for all particles.\n* ``'all'`` and ``'nbody'`` -- These are special particle field types that represent a\n  concatenation of several particle field types using :ref:`particle-unions`.\n  ``'all'`` contains every base particle types, while ``'nbody'`` contains only the ones\n  for which a ``'particle_mass'`` field is defined.\n* ``'deposit'`` -- This field type refers to the deposition of particles\n  (discrete data) onto a mesh, typically to compute smoothing kernels, local\n  density estimates, counts, and the like.  See :ref:`deposited-particle-fields`\n  for more information.\n\nWhile it is best to be explicit access fields by their full names\n(i.e. ``('*field type*', '*field name*')``), yt provides an abbreviated\ninterface for accessing common fields (i.e. ``'*field name*'``).  In the abbreviated\ncase, yt will assume you want the last *field type* accessed.  If you\nhaven't previously accessed a *field type*, it will default to *field type* =\n``'all'`` in the case of particle fields and *field type* = ``'gas'`` in the\ncase of mesh fields.\n\nField Plugins\n-------------\n\nDerived fields are organized via plugins.  Inside yt are a number of field\nplugins, which take information about fields in a dataset and then construct\nderived fields on top of them.  This allows them to take into account\nvariations in naming system, units, data representations, and most importantly,\nallows only the fields that are relevant to be added.  This system will be\nexpanded in future versions to enable much deeper semantic awareness of the\ndata types being analyzed by yt.\n\nThe field plugin system works in this order:\n\n * Available, inherent fields are identified by yt\n * The list of enabled field plugins is iterated over.  Each is called, and new\n   derived fields are added as relevant.\n * Any fields which are not available, or which throw errors, are discarded.\n * Remaining fields are added to the list of derived fields available for a\n   dataset\n * Dependencies for every derived field are identified, to enable data\n   preloading\n\nField plugins can be loaded dynamically, although at present this is not\nparticularly useful.  Plans for extending field plugins to dynamically load, to\nenable simple definition of common types (divergence, curl, etc), and to\nmore verbosely describe available fields, have been put in place for future\nversions.\n\nThe field plugins currently available include:\n\n * Angular momentum fields for particles and fluids\n * Astrophysical fields, such as those related to cosmology\n * Vector fields for fluid fields, such as gradients and divergences\n * Particle vector fields\n * Magnetic field-related fields\n * Species fields, such as for chemistry species (yt can recognize the entire\n   periodic table in field names and construct ionization fields as need be)\n\n\nField Labeling\n--------------\n\nBy default yt formats field labels nicely for plots. To adjust the chosen\nformat you can use the ``ds.set_field_label_format`` method like so:\n\n\n.. code-block:: python\n\n   ds = yt.load(\"my_data\")\n   ds.set_field_label_format(\"ionization_label\", \"plus_minus\")\n\n\nThe first argument accepts a ``format_property``, or specific aspect of the labeling, and the\nsecond sets the corresponding ``value``. Currently available format properties are\n\n    * ``ionization_label``: sets how the ionization state of ions are labeled. Available\n            options are ``\"plus_minus\"`` and ``\"roman_numeral\"``\n\n.. _efields:\n\nEnergy and Momentum Fields\n--------------------------\n\nFields in yt representing energy and momentum quantities follow a specific\nnaming convention (as of yt-4.x). In hydrodynamic simulations, the relevant\nquantities are often energy per unit mass or volume, momentum, or momentum\ndensity. To distinguish clearly between the different types of fields, the\nfollowing naming convention is adhered to:\n\n* Energy per unit mass fields are named as ``'specific_*_energy'``\n* Energy per unit volume fields are named as ``'*_energy_density'``\n* Momentum fields should be named ``'momentum_density_*'`` for momentum per\n  unit density, or ``'momentum_*'`` for momentum, where the ``*`` indicates\n  one of three coordinate axes in any supported coordinate system.\n\nFor example, in the case of kinetic energy, the fields should be\n``'kinetic_energy_density'`` and ``'specific_kinetic_energy'``.\n\nIn versions of yt previous to v4.0.0, these conventions were not adopted, and so\nenergy fields in particular could be ambiguous with respect to units. For\nexample, the ``'kinetic_energy'`` field was actually kinetic energy per unit\nvolume, whereas the ``'thermal_energy'`` field, usually defined by various\nfrontends, was typically thermal energy per unit mass. The above scheme\nrectifies these problems, but for the time being the previous field names are\nmapped to the current field naming scheme with a deprecation warning. These\naliases were removed in yt v4.1.0.\n\n.. _bfields:\n\nMagnetic Fields\n---------------\n\nMagnetic fields require special handling, because their dimensions are different in\ndifferent systems of units, in particular between the CGS and MKS (SI) systems of units.\nSuperficially, it would appear that they are in the same dimensions, since the units\nof the magnetic field in the CGS and MKS system are gauss (:math:`\\rm{G}`) and tesla\n(:math:`\\rm{T}`), respectively, and numerically :math:`1~\\rm{G} = 10^{-4}~\\rm{T}`. However,\nif we examine the base units, we find that they do indeed have different dimensions:\n\n.. math::\n\n    \\rm{1~G = 1~\\frac{\\sqrt{g}}{\\sqrt{cm}\\cdot{s}}} \\\\\n    \\rm{1~T = 1~\\frac{kg}{A\\cdot{s^2}}}\n\nIt is easier to see the difference between the dimensionality of the magnetic field in the two\nsystems in terms of the definition of the magnetic pressure and the Alfvén speed:\n\n.. math::\n\n    p_B = \\frac{B^2}{8\\pi}~\\rm{(cgs)} \\\\\n    p_B = \\frac{B^2}{2\\mu_0}~\\rm{(MKS)}\n\n.. math::\n\n    v_A = \\frac{B}{\\sqrt{4\\pi\\rho}}~\\rm{(cgs)} \\\\\n    v_A = \\frac{B}{\\sqrt{\\mu_0\\rho}}~\\rm{(MKS)}\n\nwhere :math:`\\mu_0 = 4\\pi \\times 10^{-7}~\\rm{N/A^2}` is the vacuum permeability. This\ndifferent normalization in the definition of the magnetic field may show up in other\nrelevant quantities as well.\n\nFor certain frontends, a third definition of the magnetic field and the magnetic\npressure may be useful. In many MHD simulations and in some physics areas (such\nas particle physics/GR) it is more common to use the \"Lorentz-Heaviside\" convention,\nwhich results in:\n\n.. math::\n\n    p_B = \\frac{B^2}{2} \\\\\n    v_A = \\frac{B}{\\sqrt{\\rho}}\n\nUsing this convention is currently only available for :ref:`Athena<loading-athena-data>`,\n:ref:`Athena++<loading-athena-pp-data>`, and :ref:`AthenaPK<loading-parthenon-data>` datasets,\nthough it will likely be available for more datasets in the future.\n\nyt automatically detects on a per-frontend basis what units the magnetic should be in, and allows conversion between\ndifferent magnetic field units in the different unit systems as well. To determine how to set up special magnetic field handling when designing a new frontend, check out\n:ref:`bfields-frontend`.\n\n.. _species-fields:\n\nSpecies Fields\n--------------\n\nFor many types of data, yt is able to detect different chemical elements and molecules\nwithin the dataset, as well as their abundances and ionization states. Examples include:\n\n* CO (Carbon monoxide)\n* Co (Cobalt)\n* OVI (Oxygen ionized five times)\n* H:math:`^{2+}` (Molecular Hydrogen ionized once)\n* H:math:`^{-}` (Hydrogen atom with an additional electron)\n\nThe naming scheme for the fields starts with prefixes in the form ``MM[_[mp][NN]]``. ``MM``\nis the molecule, defined as a concatenation of atomic symbols and numbers, with no spaces or\nunderscores. The second sequence is only required if ionization states are present in the\ndataset, and is of the form ``p`` and ``m`` to indicate \"plus\" or \"minus\" respectively,\nfollowed by the number. If a given species has no ionization states given, the prefix is\nsimply ``MM``.\n\nFor the examples above, the prefixes would be:\n\n* ``CO``\n* ``Co``\n* ``O_p5``\n* ``H2_p1``\n* ``H_m1``\n\nThe name ``El`` is used for electron fields, as it is unambiguous and will not be\nutilized elsewhere. Neutral ionic species (e.g. H I, O I) are represented as ``MM_p0``.\nAdditionally, the isotope of :math:`^2`H will be included as ``D``.\n\nFinally, in those frontends which are single-fluid, these fields for each species are\ndefined:\n\n* ``MM[_[mp][NN]]_fraction``\n* ``MM[_[mp][NN]]_number_density``\n* ``MM[_[mp][NN]]_density``\n* ``MM[_[mp][NN]]_mass``\n\nTo refer to the number density of the entirety of a single atom or molecule (regardless\nof its ionization state), please use the ``MM_nuclei_density`` fields.\n\nMany datasets do not have species defined, but there may be an underlying assumption\nof primordial abundances of H and He which are either fully ionized or fully neutral.\nThis will also determine the value of the mean molecular weight of the gas, which\nwill determine the value of the temperature if derived from another quantity like the\npressure or thermal energy. To allow for these possibilities, there is a keyword\nargument ``default_species_fields`` which can be passed to :func:`~yt.loaders.load`:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\n        \"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\", default_species_fields=\"ionized\"\n    )\n\nBy default, the value of this optional argument is ``None``, which will not initialize\nany default species fields. If the ``default_species_fields`` argument is not set to\n``None``, then the following fields are defined:\n\n* ``H_nuclei_density``\n* ``He_nuclei_density``\n\nMore specifically, if ``default_species_fields=\"ionized\"``, then these\nadditional fields are defined:\n\n* ``H_p1_number_density`` (Ionized hydrogen: equal to the value of ``H_nuclei_density``)\n* ``He_p2_number_density`` (Doubly ionized helium: equal to the value of ``He_nuclei_density``)\n* ``El_number_density`` (Free electrons: assuming full ionization)\n\nWhereas if ``default_species_fields=\"neutral\"``, then these additional\nfields are defined:\n\n* ``H_p0_number_density`` (Neutral hydrogen: equal to the value of ``H_nuclei_density``)\n* ``He_p0_number_density`` (Neutral helium: equal to the value of ``He_nuclei_density``)\n\nIn this latter case, because the gas is neutral, ``El_number_density`` is not defined.\n\nThe ``mean_molecular_weight`` field will be constructed from the abundances of the elements\nin the dataset. If no element or molecule fields are defined, the value of this field\nis determined by the value of ``default_species_fields``. If it is set to ``None`` or\n``\"ionized\"``, the ``mean_molecular_weight`` field is set to :math:`\\mu \\approx 0.6`,\nwhereas if ``default_species_fields`` is set to ``\"neutral\"``, then the\n``mean_molecular_weight`` field is set to :math:`\\mu \\approx 1.14`. Some frontends do\nnot directly store the gas temperature in their datasets, in which case it must be\ncomputed from the pressure and/or thermal energy as well as the mean molecular weight,\nso check this carefully!\n\nParticle Fields\n---------------\n\nNaturally, particle fields contain properties of particles rather than\ngrid cells.  By examining the particle field in detail, you can see that\neach element of the field array represents a single particle, whereas in mesh\nfields each element represents a single mesh cell.  This means that for the\nmost part, operations cannot operate on both particle fields and mesh fields\nsimultaneously in the same way, like filters (see :ref:`filtering-data`).\nHowever, many of the particle fields have corresponding mesh fields that\ncan be populated by \"depositing\" the particle values onto a yt grid as\ndescribed below.\n\n.. _field_parameters:\n\nField Parameters\n----------------\n\nCertain fields require external information in order to be calculated.  For\nexample, the radius field has to be defined based on some point of reference\nand the radial velocity field needs to know the bulk velocity of the data object\nso that it can be subtracted.  This information is passed into a field function\nby setting field parameters, which are user-specified data that can be associated\nwith a data object.  The\n:meth:`~yt.data_objects.data_containers.YTDataContainer.set_field_parameter`\nand\n:meth:`~yt.data_objects.data_containers.YTDataContainer.get_field_parameter`\nfunctions are\nused to set and retrieve field parameter values for a given data object.  In the\ncases above, the field parameters are ``center`` and ``bulk_velocity`` respectively --\nthe two most commonly used field parameters.\n\n.. code-block:: python\n\n   ds = yt.load(\"my_data\")\n   ad = ds.all_data()\n\n   ad.set_field_parameter(\"wickets\", 13)\n\n   print(ad.get_field_parameter(\"wickets\"))\n\nIf a field parameter is not set, ``get_field_parameter`` will return None.\nWithin a field function, these can then be retrieved and used in the same way.\n\n.. code-block:: python\n\n   def _wicket_density(data):\n       n_wickets = data.get_field_parameter(\"wickets\")\n       if n_wickets is None:\n           # use a default if unset\n           n_wickets = 88\n       return data[\"gas\", \"density\"] * n_wickets\n\nFor a practical application of this, see :ref:`cookbook-radial-velocity`.\n\n.. _gradient_fields:\n\nGradient Fields\n---------------\n\nyt provides a way to compute gradients of spatial fields using the\n:meth:`~yt.data_objects.static_output.Dataset.add_gradient_fields`\nmethod. If you have a spatially-based field such as density or temperature,\nand want to calculate the gradient of that field, you can do it like so:\n\n.. code-block:: python\n\n    ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n    grad_fields = ds.add_gradient_fields((\"gas\", \"temperature\"))\n\nwhere the ``grad_fields`` list will now have a list of new field names that can be used\nin calculations, representing the 3 different components of the field and the magnitude\nof the gradient, e.g., ``\"temperature_gradient_x\"``, ``\"temperature_gradient_y\"``,\n``\"temperature_gradient_z\"``, and ``\"temperature_gradient_magnitude\"``. To see an example\nof how to create and use these fields, see :ref:`cookbook-complicated-derived-fields`.\n\n.. _relative_fields:\n\nRelative Vector Fields\n----------------------\n\nyt makes use of \"relative\" fields for certain vector fields, which are fields\nwhich have been defined relative to a particular origin in the space of that\nfield. For example, relative particle positions can be specified relative to\na center coordinate, and relative velocities can be specified relative to a\nbulk velocity. These origin points are specified by setting field parameters\nas detailed below (see :ref:`field_parameters` for more information).\n\nThe relative fields which are currently supported for gas fields are:\n\n* ``('gas', 'relative_velocity_x')``, defined by setting the\n  ``'bulk_velocity'`` field parameter\n* ``('gas', 'relative_magnetic_field_x')``, defined by setting the\n  ``'bulk_magnetic_field'`` field parameter\n\nNote that fields ending in ``'_x'`` are defined for each component.\n\nFor particle fields, for a given particle type ``ptype``, the relative\nfields which are supported are:\n\n* ``(*ptype*, 'relative_particle_position')``, defined by setting the\n  ``'center'`` field parameter\n* ``(*ptype*, 'relative_particle_velocity')``, defined by setting the\n  ``'bulk_velocity'`` field parameter\n* ``(*ptype*, 'relative_particle_position_x')``, defined by setting the\n  ``'center'`` field parameter\n* ``(*ptype*, 'relative_particle_velocity_x')``, defined by setting the\n  ``'bulk_velocity'`` field parameter\n\nThese fields are in use when defining magnitude fields, line-of-sight fields,\netc.. The ``'bulk_*'`` field parameters are ``[0.0, 0.0, 0.0]`` by default,\nand the ``'center'`` field parameter depends on the data container in use.\n\nThere is currently no mechanism to create new relative fields, but one may be\nadded at a later time.\n\n.. _los_fields:\n\nLine of Sight Fields\n--------------------\n\nIn astrophysics applications, one often wants to know the component of a vector\nfield along a given line of sight. If you are doing a projection of a vector\nfield along an axis, or just want to obtain the values of a vector field\ncomponent along an axis, you can use a line-of-sight field. For projections,\nthis will be handled automatically:\n\n.. code-block:: python\n\n    prj = yt.ProjectionPlot(\n        ds,\n        \"z\",\n        fields=(\"gas\", \"velocity_los\"),\n        weight_field=(\"gas\", \"density\"),\n    )\n\nWhich, because the axis is ``'z'``, will give you the same result if you had\nprojected the ``'velocity_z'`` field. This also works for off-axis projections,\nusing an arbitrary normal vector\n\n.. code-block:: python\n\n    prj = yt.ProjectionPlot(\n        ds,\n        [0.1, -0.2, 0.3],\n        fields=(\"gas\", \"velocity_los\"),\n        weight_field=(\"gas\", \"density\"),\n    )\n\n\nThis shows that the projection axis can be along a principle axis of the domain\nor an arbitrary off-axis 3-vector (which will be automatically normalized). If\nyou want to examine a line-of-sight vector within a 3-D data object, set the\n``'axis'`` field parameter:\n\n.. code-block:: python\n\n    dd = ds.all_data()\n    # Set to one of [0, 1, 2] for [\"x\", \"y\", \"z\"] axes\n    dd.set_field_parameter(\"axis\", 1)\n    print(dd[\"gas\", \"magnetic_field_los\"])\n    # Set to a three-vector for an off-axis component\n    dd.set_field_parameter(\"axis\", [0.3, 0.4, -0.7])\n    print(dd[\"gas\", \"velocity_los\"])\n    # particle fields are supported too!\n    print(dd[\"all\", \"particle_velocity_los\"])\n\n.. warning::\n\n    If you need to change the axis of the line of sight on the *same* data container\n    (sphere, box, cylinder, or whatever), you will need to delete the field using (e.g.)\n    ``del dd['velocity_los']`` and re-generate it.\n\nAt this time, this functionality is enabled for the velocity and magnetic vector\nfields, ``('gas', 'velocity_los')`` and ``('gas', 'magnetic_field_los')`` for\nthe ``\"gas\"`` field type, as well as every particle type with\na velocity field, e.g. ``(\"all\", \"particle_velocity_los\")``. The following fields\nbuilt into yt make use of these line-of-sight fields:\n\n* ``('gas', 'sz_kinetic')`` uses ``('gas', 'velocity_los')``\n* ``('gas', 'rotation_measure')`` uses ``('gas', 'magnetic_field_los')``\n\n\nGeneral Particle Fields\n-----------------------\n\nEvery particle will contain both a ``'particle_position'`` and ``'particle_velocity'``\nthat tracks the position and velocity (respectively) in code units.\n\n.. _deposited-particle-fields:\n\nDeposited Particle Fields\n-------------------------\n\nIn order to turn particle (discrete) fields into fields that are deposited in\nsome regular, space-filling way (even if that space is empty, it is defined\neverywhere) yt provides mechanisms for depositing particles onto a mesh.  These\nare in the special field-type space ``'deposit'``, and are typically of the form\n``('deposit', 'particletype_depositiontype')`` where ``depositiontype`` is the\nmechanism by which the field is deposited, and ``particletype`` is the particle\ntype of the particles being deposited.  If you are attempting to examine the\ncloud-in-cell (``cic``) deposition of the ``all`` particle type, you would\naccess the field ``('deposit', 'all_cic''')``.\n\nyt defines a few particular types of deposition internally, and creating new\nones can be done by modifying the files ``yt/geometry/particle_deposit.pyx``\nand ``yt/fields/particle_fields.py``, although that is an advanced topic\nsomewhat outside the scope of this section.  The default deposition types\navailable are:\n\n* ``count`` - this field counts the total number of particles of a given type\n  in a given mesh zone.  Note that because, in general, the mesh for particle\n  datasets is defined by the number of particles in a region, this may not be\n  the most useful metric.  This may be made more useful by depositing particle\n  data onto an :ref:`arbitrary-grid`.\n* ``density`` - this field takes the total sum of ``particle_mass`` in a given\n  mesh field and divides by the volume.\n* ``mass`` - this field takes the total sum of ``particle_mass`` in each mesh\n  zone.\n* ``cic`` - this field performs cloud-in-cell interpolation (see `Section 2.2\n  <http://ta.twi.tudelft.nl/dv/users/lemmens/MThesis.TTH/chapter4.html>`_ for more\n  information) of the density of particles in a given mesh zone.\n* ``smoothed`` - this is a special deposition type.  See discussion below for\n  more information, in :ref:`sph-fields`.\n\nYou can also directly use the\n:meth:`~yt.data_objects.static_outputs.add_deposited_particle_field` function\ndefined on each dataset to depose any particle field onto the mesh like so:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"output_00080/info_00080.txt\")\n   fname = ds.add_deposited_particle_field(\n       (\"all\", \"particle_velocity_x\"), method=\"nearest\"\n   )\n\n   print(f\"The velocity of the particles are (stored in {fname}\")\n   print(ds.r[fname])\n\n.. note::\n\n   In this example, we are using the returned field name as our input.  You\n   *could* also access it directly, but it might take a slightly different form\n   than you expect -- in this particular case, the field name will be\n   ``(\"deposit\", \"all_nn_velocity_x\")``, which has removed the prefix\n   ``particle_`` from the deposited name!\n\nPossible deposition methods are:\n\n* ``'simple_smooth'`` - perform an SPH-like deposition of the field onto the mesh\n  optionally accepting a ``kernel_name``.\n* ``'sum'`` - sums the value of the particle field for all particles found in\n  each cell.\n* ``'std'`` - computes the standard deviation of the value of the particle field\n  for all particles found in each cell.\n* ``'cic'`` - performs cloud-in-cell interpolation (see `Section 2.2\n  <http://ta.twi.tudelft.nl/dv/users/lemmens/MThesis.TTH/chapter4.html>`_ for more\n  information) of the particle field on a given mesh zone.\n* ``'weighted_mean'`` - computes the mean of the particle field, weighted by\n  the field passed into ``weight_field`` (by default, it uses the particle\n  mass).\n* ``'count'`` - counts the number of particles in each cell.\n* ``'nearest'`` - assign to each cell the value of the closest particle.\n\nIn addition, the :meth:`~yt.data_objects.static_outputs.add_deposited_particle_field` function\nreturns the name of the newly created field.\n\nDeposited particle fields can be useful for visualizing particle data, including\nparticles without defined smoothing lengths. See :ref:`particle-plotting-workarounds`\nfor more information.\n\n.. _mesh-sampling-particle-fields:\n\nMesh Sampling Particle Fields\n-----------------------------\n\nIn order to turn mesh fields into discrete particle field, yt provides\na mechanism to do sample mesh fields at particle locations. This operation is\nthe inverse operation of :ref:`deposited-particle-fields`: for each\nparticle the cell containing the particle is found and the value of\nthe field in the cell is assigned to the particle. This is for\nexample useful when using tracer particles to have access to the\nEulerian information for Lagrangian particles.\n\nThe particle fields are named ``('*ptype*', 'cell_*ftype*_*fname*')`` where\n``ptype`` is the particle type onto which the deposition occurs,\n``ftype`` is the mesh field type (e.g. ``'gas'``) and ``fname`` is the\nfield (e.g. ``'temperature'``, ``'density'``, ...). You can directly use\nthe :meth:`~yt.data_objects.static_output.Dataset.add_mesh_sampling_particle_field`\nfunction defined on each dataset to impose a field onto the particles like so:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"output_00080/info_00080.txt\")\n   ds.add_mesh_sampling_particle_field((\"gas\", \"temperature\"), ptype=\"all\")\n\n   print(\"The temperature at the location of the particles is\")\n   print(ds.r[\"all\", \"cell_gas_temperature\"])\n\nFor octree codes (e.g. RAMSES), you can trigger the build of an index so\nthat the next sampling operations will be mush faster\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"output_00080/info_00080.txt\")\n   ds.add_mesh_sampling_particle_field((\"gas\", \"temperature\"), ptype=\"all\")\n\n   ad = ds.all_data()\n   ad[\n       \"all\", \"cell_index\"\n   ]  # Trigger the build of the index of the cell containing the particles\n   ad[\"all\", \"cell_gas_temperature\"]  # This is now much faster\n\n.. _sph-fields:\n\nSPH Fields\n----------\n\nSee :ref:`yt4differences`.\n\nIn previous versions of yt, there were ways of computing the distance to the\nN-th nearest neighbor of a particle, as well as computing the nearest particle\nvalue on a mesh.  Unfortunately, because of changes to the way that particles\nare regarded in yt, these are not currently available.  We hope that this will\nbe rectified in future versions and are tracking this in `Issue 3301\n<https://github.com/yt-project/yt/issues/3301>`_.  You can read a bit more\nabout the way yt now handles particles in the section :ref:`demeshening`.\n\n**But!**  It is possible to compute the smoothed values from SPH particles on\ngrids.  For example, one can construct a covering grid that extends over the\nentire domain of a simulation, with resolution 256x256x256, and compute the gas\ndensity with this reasonable terse command:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"snapshot_033/snap_033.0.hdf5\")\n   cg = ds.r[::256j, ::256j, ::256j]\n   smoothed_values = cg[\"gas\", \"density\"]\n\nThis will work for any smoothed field; any field that is under the ``'gas'``\nfield type will be a smoothed field in an SPH-based simulation.  Here we have\nused the ``ds.r[]`` notation, as described in :ref:`quickly-selecting-data` for\ncreating what's called an \"arbitrary grid\"\n(:class:`~yt.data_objects.construction_data_containers.YTArbitraryGrid`).  You\ncan, of course, also supply left and right edges to make the grid take up a\nmuch smaller portion of the domain, as well, by supplying the arguments as\ndetailed in :ref:`arbitrary-grid-selection` and supplying the bounds as the\nfirst and second elements in each element of the slice.\n"
  },
  {
    "path": "doc/source/analyzing/filtering.rst",
    "content": ".. _filtering-data:\n\nFiltering your Dataset\n======================\n\nLarge datasets are oftentimes too overwhelming to deal with in their\nentirety, and it can be useful and faster\nto analyze subsets of these datasets.  Furthermore, filtering the dataset\nbased on some field condition can reveal subtle information not easily\naccessible by looking at the whole dataset.\nFilters can be generated based on spatial position, say in a sphere\nin the center of your dataset space, or more generally they can be\ndefined by the properties of any field in the simulation.\n\nBecause *mesh fields* are internally different from *particle fields*,\nthere are different ways of filtering each type as indicated below;\nhowever, filtering fields by spatial location (i.e. geometric\nobjects) will apply to both types equally.\n\n.. _filtering-mesh:\n\nFiltering Mesh Fields\n----------------------\n\nMesh fields can be filtered by two methods: cut region objects\n(:class:`~yt.data_objects.selection_data_containers.YTCutRegion`)\nand NumPy boolean masks.  Boolean masks are simpler, but they only work\nfor examining datasets, whereas cut regions objects create wholly new\ndata objects suitable for full analysis (data examination, image generation,\netc.)\n\nBoolean Masks\n^^^^^^^^^^^^^\n\nNumPy boolean masks can be used with any NumPy array simply by passing the\narray a conditional.  As a general example of this:\n\n.. notebook-cell::\n\n    import numpy as np\n\n    a = np.arange(5)\n    bigger_than_two = a > 2\n    print(\"Original Array: a = \\n%s\" % a)\n    print(\"Boolean Mask: bigger_than_two = \\n%s\" % bigger_than_two)\n    print(\"Masked Array: a[bigger_than_two] = \\n%s\" % a[bigger_than_two])\n\nSimilarly, if you've created a yt data object (e.g. a region, a sphere), you\ncan examine its field values as a NumPy array by simply indexing it with the\nfield name.  Thus, it too can be masked using a NumPy boolean mask.  Let's\nset a simple mask based on the contents of one of our fields.\n\n.. notebook-cell::\n\n    import yt\n\n    ds = yt.load(\"Enzo_64/DD0042/data0042\")\n    ad = ds.all_data()\n    hot = ad[\"gas\", \"temperature\"].in_units(\"K\") > 1e6\n    print(\n        'Temperature of all data: ad[\"gas\", \"temperature\"] = \\n%s'\n        % ad[\"gas\", \"temperature\"]\n    )\n    print(\"Boolean Mask: hot = \\n%s\" % hot)\n    print(\n        'Temperature of \"hot\" data: ad[\"gas\", \"temperature\"][hot] = \\n%s'\n        % ad[\"gas\", \"temperature\"][hot]\n    )\n\nThis was a simple example, but one can make the conditionals that define\na boolean mask have multiple parts, and one can stack masks together to\nmake very complex cuts on one's data.  Once the data is filtered, it can be\nused if you simply need to access the NumPy arrays:\n\n.. notebook-cell::\n\n    import yt\n\n    ds = yt.load(\"Enzo_64/DD0042/data0042\")\n    ad = ds.all_data()\n    overpressure_and_fast = (\n        (ad[\"gas\", \"pressure\"] > 1e-14) &\n        (ad[\"gas\", \"velocity_magnitude\"].in_units(\"km/s\") > 1e2)\n    )\n    density = ad[\"gas\", \"density\"]\n    print('Density of all data: ad[\"gas\", \"density\"] = \\n%s' % density)\n    print(\n        'Density of \"overpressure and fast\" data: overpressure_and_fast[\"gas\", \"density\"] = \\n%s'\n        % density[overpressure_and_fast]\n    )\n\n.. _cut-regions:\n\nCut Regions\n^^^^^^^^^^^\n\nCut regions are a more general solution to filtering mesh fields. The output\nof a cut region is an entirely new data object, which can be treated like any\nother data object to generate images, examine its values, etc. See `this <mesh_filter>`_.\n\n.. notebook-cell::\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0042/data0042\")\n   ad = ds.all_data()\n\n   low_density = ad.cut_region(\n      ds.fields.gas.density < ds.quan(0.1, \"mp/cm**3\")\n   )\n   high_temperature = ad.cut_region(\n       ds.fields.gas.temperature > ds.quan(1e5, \"K\")\n   )\n\nIn addition to using directly cut_region to specify filters,\nwrapper functions exist that allow the user to use a simplified syntax for\nfiltering out unwanted regions. Such wrapper functions are methods of\n:func: ``YTSelectionContainer3D``.\n\n   overpressure_and_fast = ad.include_above((\"gas\", \"pressure\"), 1e-14)\n   # You can chain include_xx and exclude_xx to produce the intersection of cut regions\n   overpressure_and_fast = overpressure_and_fast.include_above(\n       (\"gas\", \"velocity_magnitude\"), 1e2, \"km/s\"\n   )\n\n   print('Density of all data: ad[\"gas\", \"density\"] = \\n%s' % ad[\"gas\", \"density\"])\n   print(\n       'Density of \"overpressure and fast\" data: overpressure_and_fast[\"gas\", \"density\"] = \\n%s'\n       % overpressure_and_fast[\"gas\", \"density\"]\n   )\n\nThe following exclude and include functions are supported:\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.include_equal` - Only include values equal to given value\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.exclude_equal`- Exclude values equal to given value\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.include_inside` - Only include values inside closed interval\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.exclude_inside` - Exclude values inside closed interval\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.include_outside` - Only include values outside closed interval\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.exclude_outside` - Exclude values outside closed interval\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.exclude_nan` - Exclude NaN values\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.include_above` - Only include values above given value\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.exclude_above` - Exclude values above given value\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.include_below` - Only include values below given balue\n   - :func:`~yt.data_objects.data_containers.YTSelectionContainer3D.exclude_below` - Exclude values below given value\n\n\n.. warning::\n\n    Cut regions are unstable when used on particle fields. Though you can create\n    a cut region using a mesh field or fields as a filter and then obtain a\n    particle field within that region, you cannot create a cut region using\n    particle fields in the filter, as yt will currently raise an error. If\n    you want to filter particle fields, see the next section\n    :ref:`filtering-particles` instead.\n\n.. _filtering-particles:\n\nFiltering Particle Fields\n-------------------------\n\nParticle filters create new particle fields based on the manipulation and\ncuts on existing particle fields.  You can apply cuts to them to effectively\nmask out everything except the particles with which you are concerned.\n\nCreating a particle filter takes a few steps.  You must first define a\nfunction which accepts a data object (e.g. all_data, sphere, etc.)\nas its argument.  It uses the fields and information in this geometric\nobject in order to produce some sort of conditional mask that is then returned\nto create a new particle type.\n\nHere is a particle filter to create a new ``star`` particle type.  For Enzo\nsimulations, stars have ``particle_type`` set to 2, so our filter will select\nonly the particles with ``particle_type`` (i.e.  field = ``('all',\n'particle_type')`` equal to 2.\n\n.. code-block:: python\n\n    @yt.particle_filter(requires=[\"particle_type\"], filtered_type=\"all\")\n    def stars(pfilter, data):\n        filter = data[pfilter.filtered_type, \"particle_type\"] == 2\n        return filter\n\nThe :func:`~yt.data_objects.particle_filters.particle_filter` decorator takes a\nfew options.  You must specify the names of the particle fields that are\nrequired in order to define the filter --- in this case the ``particle_type``\nfield.  Additionally, you must specify the particle type to be filtered --- in\nthis case we filter all the particle in dataset by specifying the ``all``\nparticle type.\n\nIn addition, you may specify a name for the newly defined particle type.  If no\nname is specified, the name for the particle type will be inferred from the name\nof the filter definition --- in this case the inferred name will be ``stars``.\n\nAs an alternative syntax, you can also define a new particle filter via the\n:func:`~yt.data_objects.particle_filter.add_particle_filter` function.\n\n.. code-block:: python\n\n    def stars(pfilter, data):\n        filter = data[pfilter.filtered_type, \"particle_type\"] == 2\n        return filter\n\n\n    yt.add_particle_filter(\n        \"stars\", function=stars, filtered_type=\"all\", requires=[\"particle_type\"]\n    )\n\nThis is equivalent to our use of the ``particle_filter`` decorator above.  The\nchoice to use either the ``particle_filter`` decorator or the\n``add_particle_filter`` function is a purely stylistic choice.\n\nLastly, the filter must be applied to our dataset of choice.  Note that this\nfilter can be added to as many datasets as we wish.  It will only actually\ncreate new filtered fields if the dataset has the required fields, though.\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    ds.add_particle_filter(\"stars\")\n\nAnd that's it!  We can now access all of the ('stars', field) fields from\nour dataset ``ds`` and treat them as any other particle field.  In addition,\nit created some ``deposit`` fields, where the particles were deposited on to\nthe grid as mesh fields.\n\nWe can create additional filters building on top of the filters we have.\nFor example, we can identify the young stars based on their age, which is\nthe difference between current time and their creation_time.\n\n.. code-block:: python\n\n    def young_stars(pfilter, data):\n        age = data.ds.current_time - data[pfilter.filtered_type, \"creation_time\"]\n        filter = np.logical_and(age.in_units(\"Myr\") <= 5, age >= 0)\n        return filter\n\n\n    yt.add_particle_filter(\n        \"young_stars\",\n        function=young_stars,\n        filtered_type=\"stars\",\n        requires=[\"creation_time\"],\n    )\n\nIf we properly define all the filters using the decorator ``yt.particle_filter``\nor the function ``yt.add_particle_filter`` in advance. We can add the filter\nwe need to the dataset. If the ``filtered_type`` is already defined but not\nadded to the dataset, it will automatically add the filter first. For example,\nif we add the ``young_stars`` filter, which is filtered from ``stars``,\nto the dataset, it will also add ``stars`` filter to the dataset.\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    ds.add_particle_filter(\"young_stars\")\n\n\nAdditional example of particle filters can be found in the `notebook <particle_filter>`_.\n\n.. _particle-unions:\n\nParticle Unions\n---------------\n\nMultiple types of particles can be combined into a single, conceptual type.  As\nan example, the NMSU-ART code has multiple \"species\" of dark matter, which we\nunion into a single ``darkmatter`` field.  The ``all`` particle type is a\nspecial case of this.\n\nTo create a particle union, you need to import the ``ParticleUnion`` class from\n``yt.data_objects.unions``, which you then create and pass into\n``add_particle_union`` on a dataset object.\n\nHere is an example, where we union the ``halo`` and ``disk`` particle types\ninto a single type, ``star``.  yt will then determine which fields are\naccessible to this new particle type and it will add them.\n\n.. code-block:: python\n\n   from yt.data_objects.unions import ParticleUnion\n\n   u = ParticleUnion(\"star\", [\"halo\", \"disk\"])\n   ds.add_particle_union(u)\n\n.. _filtering-by-location:\n\nFiltering Fields by Spatial Location: Geometric Objects\n-------------------------------------------------------\n\nCreating geometric objects for a dataset provides a means for filtering\na field based on spatial location.  The most commonly used of these are\nspheres, regions (3D prisms), ellipsoids, disks, and rays.  The ``all_data``\nobject which gets used throughout this documentation section is an example of\na geometric object, but it defaults to including all the data in the dataset\nvolume.  To see all of the geometric objects available, see\n:ref:`available-objects`.\n\nConsult the object documentation section for all of the different objects\none can use, but here is a simple example using a sphere object to filter\na dataset.  Let's filter out everything not within 10 Mpc of some random\nlocation, say [0.2, 0.5, 0.1], in the simulation volume.  The resulting object\nwill only contain grid cells with centers falling inside of our defined sphere,\nwhich may look offset based on the presence of different resolution elements\ndistributed throughout the dataset.\n\n.. notebook-cell::\n\n    import yt\n\n    ds = yt.load(\"Enzo_64/DD0042/data0042\")\n    center = [0.20, 0.50, 0.10]\n\n    sp = ds.sphere(center, (10, \"Mpc\"))\n    prj = yt.ProjectionPlot(\n        ds, \"x\", (\"gas\", \"density\"), center=center, width=(50, \"Mpc\"), data_source=sp\n    )\n\n    # Mark the center with a big X\n    prj.annotate_marker(center, \"x\", s=100)\n\n    prj.show()\n\n    slc = yt.SlicePlot(\n        ds, \"x\", (\"gas\", \"density\"), center=center, width=(50, \"Mpc\"), data_source=sp\n    )\n\n    slc.show()\n"
  },
  {
    "path": "doc/source/analyzing/generating_processed_data.rst",
    "content": ".. _generating-processed-data:\n\nGenerating Processed Data\n=========================\n\nAlthough yt provides a number of built-in visualization methods that can\nprocess data and construct from that plots, it is often useful to generate the\ndata by hand and construct plots which can then be combined with other plots,\nmodified in some way, or even (gasp) created and modified in some other tool or\nprogram.\n\n.. _exporting-container-data:\n\nExporting Container Data\n------------------------\n\nFields from data containers such as regions, spheres, cylinders, etc. can be exported\ntabular format using either a :class:`~pandas.DataFrame` or an :class:`~astropy.table.QTable`.\n\nTo export to a :class:`~pandas.DataFrame`, use\n:meth:`~yt.data_objects.data_containers.YTDataContainer.to_dataframe`:\n\n.. code-block:: python\n\n    sp = ds.sphere(\"c\", (0.2, \"unitary\"))\n    df2 = sp.to_dataframe([(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n\nTo export to a :class:`~astropy.table.QTable`, use\n:meth:`~yt.data_objects.data_containers.YTDataContainer.to_astropy_table`:\n\n.. code-block:: python\n\n    sp = ds.sphere(\"c\", (0.2, \"unitary\"))\n    at2 = sp.to_astropy_table(fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n\nFor exports to :class:`~pandas.DataFrame` objects, the unit information is lost, but for\nexports to :class:`~astropy.table.QTable` objects, the :class:`~yt.units.yt_array.YTArray`\nobjects are converted to :class:`~astropy.units.Quantity` objects.\n\n.. _generating-2d-image-arrays:\n\n2D Image Arrays\n---------------\n\nWhen making a slice, a projection or an oblique slice in yt, the resultant\n:class:`~yt.data_objects.data_containers.YTSelectionContainer2D` object is created and\ncontains flattened arrays of the finest available data.  This means a set of\narrays for the x, y, (possibly z), dx, dy, (possibly dz) and data values, for\nevery point that constitutes the object.\n\n\nThis presents something of a challenge for visualization, as it will require\nthe transformation of a variable mesh of points consisting of positions and\nsizes into a fixed-size array that appears like an image.  This process is that\nof pixelization, which yt handles transparently internally.  You can access\nthis functionality by constructing a\n:class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer`\nand supplying\nto it your :class:`~yt.data_objects.data_containers.YTSelectionContainer2D`\nobject, as well as some information about how you want the final image to look.\nYou can specify both the bounds of the image (in the appropriate x-y plane) and\nthe resolution of the output image.  You can then have yt pixelize any field\nyou like.\n\n.. note:: In previous versions of yt, there was a special class of\n          FixedResolutionBuffer for off-axis slices.  This is still used\n          for off-axis SPH data projections: OffAxisFixedResolutionBuffer.\n\nTo create :class:`~yt.data_objects.data_containers.YTSelectionContainer2D` objects, you can\naccess them as described in :ref:`data-objects`, specifically the section\n:ref:`available-objects`.  Here is an example of how to window into a slice\nof resolution(512, 512) with bounds of (0.3, 0.5) and (0.6, 0.8).  The next\nstep is to generate the actual 2D image array, which is accomplished by\naccessing the desired field.\n\n.. code-block:: python\n\n   sl = ds.slice(0, 0.5)\n   frb = FixedResolutionBuffer(sl, (0.3, 0.5, 0.6, 0.8), (512, 512))\n   my_image = frb[\"density\"]\n\nThis image may then be used in a hand-constructed Matplotlib image, for instance using\n:func:`~matplotlib.pyplot.imshow`.\n\nThe buffer arrays can be saved out to disk in either HDF5 or FITS format:\n\n.. code-block:: python\n\n   frb.save_as_dataset(\"my_images.h5\", fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n   frb.export_fits(\n       \"my_images.fits\",\n       fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n       clobber=True,\n       units=\"kpc\",\n   )\n\nIn the HDF5 case, the created file can be reloaded just like a regular dataset with\n``yt.load`` and will, itself, be a first-class dataset.  For more information on\nthis, see :ref:`saving-grid-data-containers`.\nIn the FITS case, there is an option for setting the ``units`` of the coordinate system in\nthe file. If you want to overwrite a file with the same name, set ``clobber=True``.\n\nThe :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer`\n(and its\n:class:`~yt.visualization.fixed_resolution.OffAxisProjectionFixedResolutionBuffer`\nsubclass) can even be exported\nas a 2D dataset itself, which may be operated on in the same way as any other dataset in yt:\n\n.. code-block:: python\n\n   ds_frb = frb.export_dataset(\n       fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")], nprocs=8\n   )\n   sp = ds_frb.sphere(\"c\", (100.0, \"kpc\"))\n\nwhere the ``nprocs`` parameter can be used to decompose the image into ``nprocs`` number of grids.\n\n.. _generating-profiles-and-histograms:\n\nProfiles and Histograms\n-----------------------\n\nProfiles and histograms can also be generated using the\n:class:`~yt.visualization.profile_plotter.ProfilePlot` and\n:class:`~yt.visualization.profile_plotter.PhasePlot` functions\n(described in :ref:`how-to-make-1d-profiles` and\n:ref:`how-to-make-2d-profiles`).  These generate profiles transparently, but the\nobjects they handle and create can be handled manually, as well, for more\ncontrol and access.  The :func:`~yt.data_objects.profiles.create_profile` function\ncan be used to generate 1, 2, and 3D profiles.\n\nProfile objects can be created from any data object (see :ref:`data-objects`,\nspecifically the section :ref:`available-objects` for more information) and are\nbest thought of as distribution calculations.  They can either sum up or average\none quantity with respect to one or more other quantities, and they do this over\nall the data contained in their source object.  When calculating average values,\nthe standard deviation will also be calculated.\n\nTo generate a profile, one need only specify the binning fields and the field\nto be profiled.  The binning fields are given together in a list.  The\n:func:`~yt.data_objects.profiles.create_profile` function will guess the\ndimensionality of the profile based on the number of fields given.  For example,\na one-dimensional profile of the mass-weighted average temperature as a function of\ndensity within a sphere can be created in the following way:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"galaxy0030/galaxy0030\")\n   source = ds.sphere(\"c\", (10, \"kpc\"))\n   profile = source.profile(\n       [(\"gas\", \"density\")],  # the bin field\n       [\n           (\"gas\", \"temperature\"),  # profile field\n           (\"gas\", \"radial_velocity\"),\n       ],  # profile field\n       weight_field=(\"gas\", \"mass\"),\n   )\n\nThe binning, weight, and profile data can now be access as:\n\n.. code-block:: python\n\n   print(profile.x)  # bin field\n   print(profile.weight)  # weight field\n   print(profile[\"gas\", \"temperature\"])  # profile field\n   print(profile[\"gas\", \"radial_velocity\"])  # profile field\n\nThe ``profile.used`` attribute gives a boolean array of the bins which actually\nhave data.\n\n.. code-block:: python\n\n   print(profile.used)\n\nIf a weight field was given, the profile data will represent the weighted mean\nof a field.  In this case, the weighted standard deviation will be calculated\nautomatically and can be access via the ``profile.standard_deviation``\nattribute.\n\n.. code-block:: python\n\n   print(profile.standard_deviation[\"gas\", \"temperature\"])\n\nA two-dimensional profile of the total gas mass in bins of density and\ntemperature can be created as follows:\n\n.. code-block:: python\n\n   profile2d = source.profile(\n       [\n           (\"gas\", \"density\"),\n           (\"gas\", \"temperature\"),\n       ],  # the x bin field  # the y bin field\n       [(\"gas\", \"mass\")],  # the profile field\n       weight_field=None,\n   )\n\nAccessing the x, y, and profile fields work just as with one-dimensional profiles:\n\n.. code-block:: python\n\n   print(profile2d.x)\n   print(profile2d.y)\n   print(profile2d[\"gas\", \"mass\"])\n\nOne of the more interesting things that is enabled with this approach is\nthe generation of 1D profiles that correspond to 2D profiles.  For instance, a\nphase plot that shows the distribution of mass in the density-temperature\nplane, with the average temperature overplotted.  The\n:func:`~matplotlib.pyplot.pcolormesh` function can be used to manually plot\nthe 2D profile.  If you want to generate a default profile plot, you can simply\ncall:::\n\n  profile.plot()\n\nThree-dimensional profiles can be generated and accessed following\nthe same procedures.  Additional keyword arguments are available to control\nthe following for each of the bin fields: the number of bins, min and max, units,\nwhether to use a log or linear scale, and whether or not to do accumulation to\ncreate a cumulative distribution function.  For more information, see the API\ndocumentation on the :func:`~yt.data_objects.profiles.create_profile` function.\n\nFor custom bins the other keyword arguments can be overridden using the\n``override_bins`` keyword argument. This accepts a dictionary with an array\nfor each bin field or ``None`` to use the default settings.\n\n.. code-block:: python\n\n    custom_bins = np.array([1e-27, 1e-25, 2e-25, 5e-25, 1e-23])\n    profile2d = source.profile(\n        [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        [(\"gas\", \"mass\")],\n        override_bins={(\"gas\", \"density\"): custom_bins, (\"gas\", \"temperature\"): None},\n    )\n\n.. _profile-dataframe-export:\n\nExporting Profiles to DataFrame\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nOne-dimensional profile data can be exported to a :class:`~pandas.DataFrame` object\nusing the :meth:`yt.data_objects.profiles.Profile1D.to_dataframe` method. Bins which\ndo not have data will have their fields filled with ``NaN``, except for the bin field\nitself. If you only want to export the bins which are used, set ``only_used=True``,\nand if you want to export the standard deviation of the profile as well, set\n``include_std=True``:\n\n.. code-block:: python\n\n    # Adds all of the data to the DataFrame, but non-used bins are filled with NaNs\n    df = profile.to_dataframe()\n    # Only adds the used bins to the DataFrame\n    df_used = profile.to_dataframe(only_used=True)\n    # Only adds the density and temperature fields\n    df2 = profile.to_dataframe(fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n    # Include standard deviation\n    df3 = profile.to_dataframe(include_std=True)\n\nThe :class:`~pandas.DataFrame` can then analyzed and/or written to disk using pandas\nmethods. Note that unit information is lost in this export.\n\n.. _profile-astropy-export:\n\nExporting Profiles to QTable\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nOne-dimensional profile data also can be exported to an AstroPy :class:`~astropy.table.QTable`\nobject. This table can then be written to disk in a number of formats, such as ASCII text\nor FITS files, and manipulated in a number of ways. Bins which do not have data\nwill have their mask values set to ``False``. If you only want to export the bins\nwhich are used, set ``only_used=True``. If you want to include the standard deviation\nof the field in the export, set ``include_std=True``. Units are preserved in the table\nby converting each :class:`~yt.units.yt_array.YTArray` to an :class:`~astropy.units.Quantity`.\n\nTo export the 1D profile to a Table object, simply call\n:meth:`yt.data_objects.profiles.Profile1D.to_astropy_table`:\n\n.. code-block:: python\n\n    # Adds all of the data to the Table, but non-used bins are masked\n    t = profile.to_astropy_table()\n    # Only adds the used bins to the Table\n    t_used = profile.to_astropy_table(only_used=True)\n    # Only adds the density and temperature fields\n    t2 = profile.to_astropy_table(fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n    # Export the standard deviation\n    t3 = profile.to_astropy_table(include_std=True)\n\n.. _generating-line-queries:\n\nLine Queries and Planar Integrals\n---------------------------------\n\nTo calculate the values along a line connecting two points in a simulation, you\ncan use the object :class:`~yt.data_objects.selection_data_containers.YTRay`,\naccessible as the ``ray`` property on a index.  (See :ref:`data-objects`\nfor more information on this.)  To do so, you can supply two points and access\nfields within the returned object.  For instance, this code will generate a ray\nbetween the points (0.3, 0.5, 0.9) and (0.1, 0.8, 0.5) and examine the density\nalong that ray:\n\n.. code-block:: python\n\n   ray = ds.ray((0.3, 0.5, 0.9), (0.1, 0.8, 0.5))\n   print(ray[\"gas\", \"density\"])\n\nThe points are not ordered, so you may need to sort the data (see the\nexample in the\n:class:`~yt.data_objects.selection_data_containers.YTRay` docs).  Also\nnote, the ray is traversing cells of varying length, as well as\ntaking a varying distance to cross each cell.  To determine the\ndistance traveled by the ray within each cell (for instance, for\nintegration) the field ``dt`` is available; this field will sum to\n1.0, as the ray's path will be normalized to 1.0, independent of how\nfar it travels through the domain.  To determine the value of ``t`` at\nwhich the ray enters each cell, the field ``t`` is available.  For\ninstance:\n\n.. code-block:: python\n\n   print(ray[\"dts\"].sum())\n   print(ray[\"t\"])\n\nThese can be used as inputs to, for instance, the Matplotlib function\n:func:`~matplotlib.pyplot.plot`, or they can be saved to disk.\n\nThe volume rendering functionality in yt can also be used to calculate\noff-axis plane integrals, using the\n:class:`~yt.visualization.volume_rendering.transfer_functions.ProjectionTransferFunction`\nin a manner similar to that described in :ref:`volume_rendering`.\n\n.. _generating-xarray:\n\nRegular Grids to xarray\n-----------------------\n\nObjects that subclass from\n:class:`~yt.data_objects.construction_data_containers.YTCoveringGrid` are able\nto export to `xarray <https://xarray.pydata.org/>`_.  This enables\ninteroperability with anything that can take xarray data.  The classes that can do this are\n:class:`~yt.data_objects.construction_data_containers.YTCoveringGrid`,\n:class:`~yt.data_objects.construction_data_containers.YTArbitraryGrid`, and\n:class:`~yt.data_objects.construction_data_containers.YTSmoothedCoveringGrid`.  For example, you can:\n\n.. code-block:: python\n\n   grid = ds.r[::256j, ::256j, ::256j]\n   obj = grid.to_xarray(fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n\nThe returned object, ``obj``, will now have the correct labelled axes and so forth.\n"
  },
  {
    "path": "doc/source/analyzing/index.rst",
    "content": ".. _analyzing:\n\nGeneral Data Analysis\n=====================\n\nThis documentation describes much of the yt infrastructure for manipulating\none's data to extract the relevant information.  Fields, data objects, and\nunits are at the heart of how yt represents data.  Beyond this, we provide\na full description for how to filter your datasets based on specific criteria,\nhow to analyze chronological datasets from the same underlying simulation or\nsource (i.e. time series analysis), and how to run yt in parallel on\nmultiple processors to accomplish tasks faster.\n\n.. toctree::\n   :maxdepth: 2\n\n   fields\n   ../developing/creating_derived_fields\n   objects\n   units\n   filtering\n   generating_processed_data\n   saving_data\n   time_series_analysis\n   Particle_Trajectories\n   parallel_computation\n   astropy_integrations\n"
  },
  {
    "path": "doc/source/analyzing/ionization_cube.py",
    "content": "import time\n\nimport h5py\nimport numpy as np\n\nimport yt\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import communication_system\n\n\n@yt.derived_field(\n    name=\"IonizedHydrogen\", units=\"\", display_name=r\"\\frac{\\rho_{HII}}{\\rho_H}\"\n)\ndef IonizedHydrogen(data):\n    return data[\"gas\", \"HII_Density\"] / (\n        data[\"gas\", \"HI_Density\"] + data[\"gas\", \"HII_Density\"]\n    )\n\n\nts = yt.DatasetSeries(\"SED800/DD*/*.index\", parallel=8)\n\nionized_z = np.zeros(ts[0].domain_dimensions, dtype=\"float32\")\n\nt1 = time.time()\nfor ds in ts.piter():\n    z = ds.current_redshift\n    for g in yt.parallel_objects(ds.index.grids, njobs=16):\n        i1, j1, k1 = g.get_global_startindex()  # Index into our domain\n        i2, j2, k2 = g.get_global_startindex() + g.ActiveDimensions\n        # Look for the newly ionized gas\n        newly_ion = (g[\"IonizedHydrogen\"] > 0.999) & (\n            ionized_z[i1:i2, j1:j2, k1:k2] < z\n        )\n        ionized_z[i1:i2, j1:j2, k1:k2][newly_ion] = z\n        g.clear_data()\n\nprint(f\"Iteration completed  {time.time() - t1:0.3e}\")\ncomm = communication_system.communicators[-1]\nfor i in range(ionized_z.shape[0]):\n    ionized_z[i, :, :] = comm.mpi_allreduce(ionized_z[i, :, :], op=\"max\")\n    print(\"Slab % 3i has minimum z of %0.3e\" % (i, ionized_z[i, :, :].max()))\nt2 = time.time()\nprint(f\"Completed.  {t2 - t1:0.3e}\")\n\nif comm.rank == 0:\n    f = h5py.File(\"IonizationCube.h5\", mode=\"w\")\n    f.create_dataset(\"/z\", data=ionized_z)\n"
  },
  {
    "path": "doc/source/analyzing/mesh_filter.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Filtering Grid Data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let us demonstrate this with an example using the same dataset as we used with the boolean masks.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\\n\",\n    \"\\n\",\n    \"ds = yt.load(\\\"Enzo_64/DD0042/data0042\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The only argument to a cut region is a conditional or a list of conditionals on field output from a data object.\\n\",\n    \"\\n\",\n    \"Here we create three new data objects which are copies of the all_data object (a region object covering the entire spatial domain of the simulation), but we've filtered on just \\\"hot\\\" material, the \\\"dense\\\" material, and the \\\"overpressure and fast\\\" material.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ad = ds.all_data()\\n\",\n    \"\\n\",\n    \"hot_ad = ad.cut_region(ds.fields.gas.temperature > ds.quan(1e6, \\\"K\\\"))\\n\",\n    \"dense_ad = ad.cut_region(ds.fields.gas.density > ds.quan(5e-30, \\\"g/cm**3\\\"))\\n\",\n    \"\\n\",\n    \"# You can further cut a cut_region\\n\",\n    \"dense_and_cool_ad = dense_ad.cut_region(ds.fields.gas.temperature < ds.quan(1e5, \\\"K\\\"))\\n\",\n    \"\\n\",\n    \"# Or you can provide multiple cuts in one go\\n\",\n    \"overpressure_and_fast_ad = ad.cut_region([\\n\",\n    \"    ds.fields.gas.pressure > ds.quan(1e-14, \\\"dyne/cm**2\\\"),\\n\",\n    \"    ds.fields.gas.velocity_magnitude > ds.quan(1e2, \\\"km/s\\\")\\n\",\n    \"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"It is also possible to define a cut region using the `ad.cut_region('obj[...]')` syntax. The content of the string will be evaluated as a Python expression, the only catch is that you *must* denote the data object in the conditional as `obj` regardless of the actual object's name.\\n\",\n    \"\\n\",\n    \"The following is equivalent to the previous examples:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"hot_ad = ad.cut_region(['obj[\\\"gas\\\", \\\"temperature\\\"] > 1e6'])\\n\",\n    \"dense_ad = ad.cut_region(['obj[\\\"gas\\\", \\\"density\\\"] > 5e-30'])\\n\",\n    \"\\n\",\n    \"# you can chain cut regions in two ways:\\n\",\n    \"dense_and_cool_ad = dense_ad.cut_region(['obj[\\\"gas\\\", \\\"temperature\\\"] < 1e5'])\\n\",\n    \"overpressure_and_fast_ad = ad.cut_region(\\n\",\n    \"    [\\n\",\n    \"        '(obj[\\\"gas\\\", \\\"pressure\\\"] > 1e-14) & (obj[\\\"gas\\\", \\\"velocity_magnitude\\\"].in_units(\\\"km/s\\\") > 1e2)'\\n\",\n    \"    ]\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You can also construct a `cut_region` using the `include_` and `exclude_` functions.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ad = ds.all_data()\\n\",\n    \"hot_ad = ad.include_above((\\\"gas\\\", \\\"temperature\\\"), 1e6)\\n\",\n    \"dense_ad = ad.include_above((\\\"gas\\\", \\\"density\\\"), 5e-30)\\n\",\n    \"\\n\",\n    \"# These can be chained as well\\n\",\n    \"dense_and_cool_ad = dense_ad.include_below((\\\"gas\\\", \\\"temperature\\\"), 1e5)\\n\",\n    \"overpressure_and_fast_ad = ad.include_above((\\\"gas\\\", \\\"pressure\\\"), 1e-14)\\n\",\n    \"overpressure_and_fast_ad = overpressure_and_fast_ad.include_above(\\n\",\n    \"    (\\\"gas\\\", \\\"velocity_magnitude\\\"), 1e2, \\\"km/s\\\"\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Upon inspection of our \\\"hot_ad\\\" object, we can still get the same results as we got with the boolean masks example above:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(\\n\",\n    \"    \\\"Temperature of all cells:\\\\n ad['temperature'] = \\\\n%s\\\\n\\\" % ad[\\\"gas\\\", \\\"temperature\\\"]\\n\",\n    \")\\n\",\n    \"print(\\n\",\n    \"    \\\"Temperatures of all \\\\\\\"hot\\\\\\\" cells:\\\\n hot_ad['temperature'] = \\\\n%s\\\"\\n\",\n    \"    % hot_ad[\\\"gas\\\", \\\"temperature\\\"]\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(\\n\",\n    \"    \\\"Density of dense, cool material:\\\\n dense_and_cool_ad['density'] = \\\\n%s\\\\n\\\"\\n\",\n    \"    % dense_and_cool_ad[\\\"gas\\\", \\\"density\\\"]\\n\",\n    \")\\n\",\n    \"print(\\n\",\n    \"    \\\"Temperature of dense, cool material:\\\\n dense_and_cool_ad['temperature'] = \\\\n%s\\\"\\n\",\n    \"    % dense_and_cool_ad[\\\"gas\\\", \\\"temperature\\\"]\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now that we've constructed a `cut_region`, we can use it as a data source for further analysis. To create a plot based on a `cut_region`, use the `data_source` keyword argument provided by yt's plotting objects.\\n\",\n    \"\\n\",\n    \"Here's an example using projections:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"proj1 = yt.ProjectionPlot(ds, \\\"x\\\", (\\\"gas\\\", \\\"density\\\"), weight_field=(\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"proj1.annotate_title(\\\"No Cuts\\\")\\n\",\n    \"proj1.set_figure_size(5)\\n\",\n    \"proj1.show()\\n\",\n    \"\\n\",\n    \"proj2 = yt.ProjectionPlot(\\n\",\n    \"    ds, \\\"x\\\", (\\\"gas\\\", \\\"density\\\"), weight_field=(\\\"gas\\\", \\\"density\\\"), data_source=hot_ad\\n\",\n    \")\\n\",\n    \"proj2.annotate_title(\\\"Hot Gas\\\")\\n\",\n    \"proj2.set_zlim((\\\"gas\\\", \\\"density\\\"), 3e-31, 3e-27)\\n\",\n    \"proj2.set_figure_size(5)\\n\",\n    \"proj2.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `data_source` keyword argument is also accepted by `SlicePlot`, `ProfilePlot` and `PhasePlot`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"slc1 = yt.SlicePlot(ds, \\\"x\\\", (\\\"gas\\\", \\\"density\\\"), center=\\\"m\\\")\\n\",\n    \"slc1.set_zlim((\\\"gas\\\", \\\"density\\\"), 3e-31, 3e-27)\\n\",\n    \"slc1.annotate_title(\\\"No Cuts\\\")\\n\",\n    \"slc1.set_figure_size(5)\\n\",\n    \"slc1.show()\\n\",\n    \"\\n\",\n    \"slc2 = yt.SlicePlot(ds, \\\"x\\\", (\\\"gas\\\", \\\"density\\\"), center=\\\"m\\\", data_source=dense_ad)\\n\",\n    \"slc2.set_zlim((\\\"gas\\\", \\\"density\\\"), 3e-31, 3e-27)\\n\",\n    \"slc2.annotate_title(\\\"Dense Gas\\\")\\n\",\n    \"slc2.set_figure_size(5)\\n\",\n    \"slc2.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ph1 = yt.PhasePlot(\\n\",\n    \"    ad, (\\\"gas\\\", \\\"density\\\"), (\\\"gas\\\", \\\"temperature\\\"), (\\\"gas\\\", \\\"mass\\\"), weight_field=None\\n\",\n    \")\\n\",\n    \"ph1.set_xlim(3e-31, 3e-27)\\n\",\n    \"ph1.annotate_title(\\\"No Cuts\\\")\\n\",\n    \"ph1.set_figure_size(5)\\n\",\n    \"ph1.show()\\n\",\n    \"\\n\",\n    \"ph1 = yt.PhasePlot(\\n\",\n    \"    dense_ad,\\n\",\n    \"    (\\\"gas\\\", \\\"density\\\"),\\n\",\n    \"    (\\\"gas\\\", \\\"temperature\\\"),\\n\",\n    \"    (\\\"gas\\\", \\\"mass\\\"),\\n\",\n    \"    weight_field=None,\\n\",\n    \")\\n\",\n    \"ph1.set_xlim(3e-31, 3e-27)\\n\",\n    \"ph1.annotate_title(\\\"Dense Gas\\\")\\n\",\n    \"ph1.set_figure_size(5)\\n\",\n    \"ph1.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.6.3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 1\n}\n"
  },
  {
    "path": "doc/source/analyzing/objects.rst",
    "content": ".. _Data-objects:\n\nData Objects\n============\n\nWhat are Data Objects in yt?\n----------------------------\n\nData objects (also called *Data Containers*) are used in yt as convenience\nstructures for grouping data in logical ways that make sense in the context\nof the dataset as a whole.  Some of the data objects are geometrical groupings\nof data (e.g. sphere, box, cylinder, etc.).  Others represent\ndata products derived from your dataset (e.g. slices, streamlines, surfaces).\nStill other data objects group multiple objects together or filter them\n(e.g. data collection, cut region).\n\nTo generate standard plots, objects rarely need to be directly constructed.\nHowever, for detailed data inspection as well as hand-crafted derived data,\nobjects can be exceptionally useful and even necessary.\n\nHow to Create and Use an Object\n-------------------------------\n\nTo create an object, you usually only need a loaded dataset, the name of\nthe object type, and the relevant parameters for your object.  Here is a common\nexample for creating a ``Region`` object that covers all of your data volume.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"RedshiftOutput0005\")\n   ad = ds.all_data()\n\nAlternatively, we could create a sphere object of radius 1 kpc on location\n[0.5, 0.5, 0.5]:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"RedshiftOutput0005\")\n   sp = ds.sphere([0.5, 0.5, 0.5], (1, \"kpc\"))\n\nAfter an object has been created, it can be used as a data_source to certain\ntasks like ``ProjectionPlot`` (see\n:class:`~yt.visualization.plot_window.ProjectionPlot`), one can compute the\nbulk quantities associated with that object (see :ref:`derived-quantities`),\nor the data can be examined directly. For example, if you want to figure out\nthe temperature at all indexed locations in the central sphere of your\ndataset you could:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"RedshiftOutput0005\")\n   sp = ds.sphere([0.5, 0.5, 0.5], (1, \"kpc\"))\n\n   # Show all temperature values\n   print(sp[\"gas\", \"temperature\"])\n\n   # Print things in a more human-friendly manner: one temperature at a time\n   print(\"(x,  y,  z) Temperature\")\n   print(\"-----------------------\")\n   for i in range(sp[\"gas\", \"temperature\"].size):\n       print(\n           \"(%f,  %f,  %f)    %f\"\n           % (\n               sp[\"gas\", \"x\"][i],\n               sp[\"gas\", \"y\"][i],\n               sp[\"gas\", \"z\"][i],\n               sp[\"gas\", \"temperature\"][i],\n           )\n       )\n\nData objects can also be cloned; for instance:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"RedshiftOutput0005\")\n   sp = ds.sphere([0.5, 0.5, 0.5], (1, \"kpc\"))\n   sp_copy = sp.clone()\n\nThis can be useful for when manually chunking data or exploring different field\nparameters.\n\n.. _quickly-selecting-data:\n\nSlicing Syntax for Selecting Data\n---------------------------------\n\nyt provides a mechanism for easily selecting data while doing interactive work\non the command line.  This allows for region selection based on the full domain\nof the object.  Selecting in this manner is exposed through a slice-like\nsyntax.  All of these attributes are exposed through the ``RegionExpression``\nobject, which is an attribute of a ``DataSet`` object, called ``r``.\n\nGetting All The Data\n^^^^^^^^^^^^^^^^^^^^\n\nThe ``.r`` attribute serves as a persistent means of accessing the full data\nfrom a dataset.  You can access this shorthand operation by querying any field\non the ``.r`` object, like so:\n\n.. code-block:: python\n\n   ds = yt.load(\"RedshiftOutput0005\")\n   rho = ds.r[\"gas\", \"density\"]\n\nThis will return a *flattened* array of data.  The region expression object\n(``r``) doesn't have any derived quantities on it.  This is completely\nequivalent to this set of statements:\n\n.. code-block:: python\n\n   ds = yt.load(\"RedshiftOutput0005\")\n   dd = ds.all_data()\n   rho = dd[\"gas\", \"density\"]\n\n.. warning::\n\n   One thing to keep in mind with accessing data in this way is that it is\n   *persistent*.  It is loaded into memory, and then retained until the dataset\n   is deleted or garbage collected.\n\nSelecting Multiresolution Regions\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo select rectilinear regions, where the data is selected the same way that it\nis selected in a :ref:`region-reference`, you can utilize slice-like syntax,\nsupplying start and stop, but not supplying a step argument.  This requires\nthat three components of the slice must be specified.  These take a start and a\nstop, and are for the three axes in simulation order (if your data is ordered\nz, y, x for instance, this would be in z, y, x order).\n\nThe slices can have both position and, optionally, unit values.  These define\nthe value with respect to the ``domain_left_edge`` of the dataset.  So for\ninstance, you could specify it like so:\n\n.. code-block:: python\n\n   ds.r[(100, \"kpc\"):(200, \"kpc\"), :, :]\n\nThis would return a region that included everything between 100 kpc from the\nleft edge of the dataset to 200 kpc from the left edge of the dataset in the\nfirst dimension, and which spans the entire dataset in the second and third\ndimensions.  By default, if the units are unspecified, they are in the \"native\"\ncode units of the dataset.\n\nThis works in all types of datasets, as well.  For instance, if you have a\ngeographic dataset (which is usually ordered latitude, longitude, altitude) you\ncan easily select, for instance, one hemisphere with a region selection:\n\n.. code-block:: python\n\n   ds.r[:, -180:0, :]\n\nIf you specify a single slice, it will be repeated along all three dimensions.\nFor instance, this will give all data:\n\n.. code-block:: python\n\n   ds.r[:]\n\nAnd this will select a box running from 0.4 to 0.6 along all three\ndimensions:\n\n.. code-block:: python\n\n   ds.r[0.4:0.6]\n\n\n.. _arbitrary-grid-selection:\n\nSelecting Fixed Resolution Regions\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nyt also provides functionality for selecting regions that have been turned into\nvoxels.  This returns an :ref:`arbitrary-grid` object.  It can be created by\nspecifying a complex slice \"step\", where the start and stop follow the same\nrules as above.  This is similar to how the numpy ``mgrid`` operation works.\nFor instance, this code block will generate a grid covering the full domain,\nbut converted to being 21x35x100 dimensions:\n\n.. code-block:: python\n\n   region = ds.r[::21j, ::35j, ::100j]\n\nThe left and right edges, as above, can be specified to provide bounds as well.\nFor instance, to select a 10 meter cube, with 24 cells in each dimension, we\ncould supply:\n\n.. code-block:: python\n\n   region = ds.r[(20, \"m\"):(30, \"m\"):24j, (30, \"m\"):(40, \"m\"):24j, (7, \"m\"):(17, \"m\"):24j]\n\nThis can select both particles and mesh fields.  Mesh fields will be 3D arrays,\nand generated through volume-weighted overlap calculations.\n\nSelecting Slices\n^^^^^^^^^^^^^^^^\n\nIf one dimension is specified as a single value, that will be the dimension\nalong which a slice is made.  This provides a simple means of generating a\nslice from a subset of the data.  For instance, to create a slice of a dataset,\nyou can very simply specify the full domain along two axes:\n\n.. code-block:: python\n\n    sl = ds.r[:, :, 0.25]\n\nThis can also be very easily plotted:\n\n.. code-block:: python\n\n   sl = ds.r[:, :, 0.25]\n   sl.plot()\n\nThis accepts arguments the same way:\n\n.. code-block:: python\n\n   sl = ds.r[(20.1, \"km\"):(31.0, \"km\"), (504.143, \"m\"):(1000.0, \"m\"), (900.1, \"m\")]\n   sl.plot()\n\nMaking Image Buffers\n^^^^^^^^^^^^^^^^^^^^\n\nUsing the slicing syntax above for choosing a slice, if you also provide an\nimaginary step value you can obtain a\n:class:`~yt.visualization.api.FixedResolutionBuffer` of the chosen resolution.\n\nFor instance, to obtain a 1024 by 1024 buffer covering the entire\ndomain but centered at 0.5 in code units, you can do:\n\n.. code-block:: python\n\n   frb = ds.r[0.5, ::1024j, ::1024j]\n\nThis ``frb`` object then can be queried like a normal fixed resolution buffer,\nand it will return arrays of shape (1024, 1024).\n\nMaking Rays\n^^^^^^^^^^^\n\nThe slicing syntax can also be used select 1D rays of points, whether along\nan axis or off-axis. To create a ray along an axis:\n\n.. code-block:: python\n\n   ortho_ray = ds.r[(500.0, \"kpc\"), (200, \"kpc\"):(300.0, \"kpc\"), (-2.0, \"Mpc\")]\n\nTo create a ray off-axis, use a single slice between the start and end points\nof the ray:\n\n.. code-block:: python\n\n   start = [0.1, 0.2, 0.3]  # interpreted in code_length\n   end = [0.4, 0.5, 0.6]  # interpreted in code_length\n   ray = ds.r[start:end]\n\nAs for the other slicing options, combinations of unitful quantities with even\ndifferent units can be used. Here's a somewhat convoluted (yet working) example:\n\n.. code-block:: python\n\n   start = ((500.0, \"kpc\"), (0.2, \"Mpc\"), (100.0, \"kpc\"))\n   end = ((1.0, \"Mpc\"), (300.0, \"kpc\"), (0.0, \"kpc\"))\n   ray = ds.r[start:end]\n\nMaking Fixed-Resolution Rays\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nRays can also be constructed to have fixed resolution if an imaginary step value\nis provided, similar to the 2 and 3-dimensional cases described above. This\nworks for rays directed along an axis:\n\n.. code-block:: python\n\n   ortho_ray = ds.r[0.1:0.6:500j, 0.3, 0.2]\n\nor off-axis rays as well:\n\n.. code-block:: python\n\n   start = [0.1, 0.2, 0.3]  # interpreted in code_length\n   end = [0.4, 0.5, 0.6]  # interpreted in code_length\n   ray = ds.r[start:end:100j]\n\nSelecting Points\n^^^^^^^^^^^^^^^^\n\nFinally, you can quickly select a single point within the domain by providing\na single coordinate for every axis:\n\n.. code-block:: python\n\n   pt = ds.r[(10.0, \"km\"), (200, \"m\"), (1.0, \"km\")]\n\nQuerying this object for fields will give you the value of the field at that\npoint.\n\n.. _available-objects:\n\nAvailable Objects\n-----------------\n\nAs noted above, there are numerous types of objects.  Here we group them\ninto:\n\n* *Geometric Objects*\n  Data is selected based on spatial shapes in the dataset\n* *Filtering Objects*\n  Data is selected based on other field criteria\n* *Collection Objects*\n  Multiple objects grouped together\n* *Construction Objects*\n  Objects represent some sort of data product constructed by additional analysis\n\nIf you want to create your own custom data object type, see\n:ref:`creating-objects`.\n\n.. _geometric-objects:\n\nGeometric Objects\n^^^^^^^^^^^^^^^^^\n\nFor 0D, 1D, and 2D geometric objects, if the extent of the object\nintersects a grid cell, then the cell is included in the object; however,\nfor 3D objects the *center* of the cell must be within the object in order\nfor the grid cell to be incorporated.\n\n0D Objects\n\"\"\"\"\"\"\"\"\"\"\n\n**Point**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTPoint`\n    | Usage: ``point(coord, ds=None, field_parameters=None, data_source=None)``\n    | A point defined by a single cell at specified coordinates.\n\n1D Objects\n\"\"\"\"\"\"\"\"\"\"\n\n**Ray (Axis-Aligned)**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTOrthoRay`\n    | Usage: ``ortho_ray(axis, coord, ds=None, field_parameters=None, data_source=None)``\n    | A line (of data cells) stretching through the full domain\n      aligned with one of the x,y,z axes.  Defined by an axis and a point\n      to be intersected.  Please see this\n      :ref:`note about ray data value ordering <ray-data-ordering>`.\n\n**Ray (Arbitrarily-Aligned)**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTRay`\n    | Usage: ``ray(start_coord, end_coord, ds=None, field_parameters=None, data_source=None)``\n    | A line (of data cells) defined by arbitrary start and end coordinates.\n      Please see this\n      :ref:`note about ray data value ordering <ray-data-ordering>`.\n\n2D Objects\n\"\"\"\"\"\"\"\"\"\"\n\n**Slice (Axis-Aligned)**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTSlice`\n    | Usage: ``slice(axis, coord, center=None, ds=None, field_parameters=None, data_source=None)``\n    | A plane normal to one of the axes and intersecting a particular\n      coordinate.\n\n**Slice (Arbitrarily-Aligned)**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTCuttingPlane`\n    | Usage: ``cutting(normal, coord, north_vector=None, ds=None, field_parameters=None, data_source=None)``\n    | A plane normal to a specified vector and intersecting a particular\n      coordinate.\n\n.. _region-reference:\n\n3D Objects\n\"\"\"\"\"\"\"\"\"\"\n\n**All Data**\n    | Function :meth:`~yt.data_objects.static_output.Dataset.all_data`\n    | Usage: ``all_data(find_max=False)``\n    | ``all_data()`` is a wrapper on the Box Region class which defaults to\n      creating a Region covering the entire dataset domain.  It is effectively\n      ``ds.region(ds.domain_center, ds.domain_left_edge, ds.domain_right_edge)``.\n\n**Box Region**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTRegion`\n    | Usage: ``region(center, left_edge, right_edge, fields=None, ds=None, field_parameters=None, data_source=None)``\n    | Alternatively: ``box(left_edge, right_edge, fields=None, ds=None, field_parameters=None, data_source=None)``\n    | A box-like region aligned with the grid axis orientation.  It is\n      defined by a left_edge, a right_edge, and a center.  The left_edge\n      and right_edge are the minimum and maximum bounds in the three axes\n      respectively.  The center is arbitrary and must only be contained within\n      the left_edge and right_edge.  By using the ``box`` wrapper, the center\n      is assumed to be the midpoint between the left and right edges.\n\n**Disk/Cylinder**\n    | Class: :class:`~yt.data_objects.selection_data_containers.YTDisk`\n    | Usage: ``disk(center, normal, radius, height, fields=None, ds=None, field_parameters=None, data_source=None)``\n    | A cylinder defined by a point at the center of one of the circular bases,\n      a normal vector to it defining the orientation of the length of the\n      cylinder, and radius and height values for the cylinder's dimensions.\n      Note: ``height`` is the distance from midplane to the top or bottom of the\n      cylinder, i.e., ``height`` is half that of the cylinder object that is\n      created.\n\n**Ellipsoid**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTEllipsoid`\n    | Usage: ``ellipsoid(center, semi_major_axis_length, semi_medium_axis_length, semi_minor_axis_length, semi_major_vector, tilt, fields=None, ds=None, field_parameters=None, data_source=None)``\n    | An ellipsoid with axis magnitudes set by ``semi_major_axis_length``,\n     ``semi_medium_axis_length``, and ``semi_minor_axis_length``.  ``semi_major_vector``\n     sets the direction of the ``semi_major_axis``.  ``tilt`` defines the orientation\n     of the semi-medium and semi_minor axes.\n\n**Sphere**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTSphere`\n    | Usage: ``sphere(center, radius, ds=None, field_parameters=None, data_source=None)``\n    | A sphere defined by a central coordinate and a radius.\n\n**Minimal Bounding Sphere**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTMinimalSphere`\n    | Usage: ``minimal_sphere(points, ds=None, field_parameters=None, data_source=None)``\n    | A sphere that contains all the points passed as argument.\n\n.. _collection-objects:\n\nFiltering and Collection Objects\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSee also the section on :ref:`filtering-data`.\n\n**Intersecting Regions**\n    | Most Region objects provide a data_source parameter, which allows you to subselect\n    | one region from another (in the coordinate system of the DataSet). Note, this can\n    | easily lead to empty data for non-intersecting regions.\n    | Usage: ``slice(axis, coord, ds, data_source=sph)``\n\n**Union Regions**\n    | Usage: ``union()``\n    | See :ref:`boolean_data_objects`.\n\n**Intersection Regions**\n    | Usage: ``intersection()``\n    | See :ref:`boolean_data_objects`.\n\n**Filter**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTCutRegion`\n    | Usage: ``cut_region(base_object, conditionals, ds=None, field_parameters=None)``\n    | A ``cut_region`` is a filter which can be applied to any other data\n      object.  The filter is defined by the conditionals present, which\n      apply cuts to the data in the object.  A ``cut_region`` will work\n      for either particle fields or mesh fields, but not on both simultaneously.\n      For more detailed information and examples, see :ref:`cut-regions`.\n\n**Collection of Data Objects**\n    | Class :class:`~yt.data_objects.selection_data_containers.YTDataCollection`\n    | Usage: ``data_collection(center, obj_list, ds=None, field_parameters=None)``\n    | A ``data_collection`` is a list of data objects that can be\n      sampled and processed as a whole in a single data object.\n\n.. _construction-objects:\n\nConstruction Objects\n^^^^^^^^^^^^^^^^^^^^\n\n**Fixed-Resolution Region**\n    | Class :class:`~yt.data_objects.construction_data_containers.YTCoveringGrid`\n    | Usage: ``covering_grid(level, left_edge, dimensions, fields=None, ds=None, num_ghost_zones=0, use_pbar=True, field_parameters=None)``\n    | A 3D region with all data extracted to a single, specified resolution.\n      See :ref:`examining-grid-data-in-a-fixed-resolution-array`.\n\n**Fixed-Resolution Region with Smoothing**\n    | Class :class:`~yt.data_objects.construction_data_containers.YTSmoothedCoveringGrid`\n    | Usage: ``smoothed_covering_grid(level, left_edge, dimensions, fields=None, ds=None, num_ghost_zones=0, use_pbar=True, field_parameters=None)``\n    | A 3D region with all data extracted and interpolated to a single,\n      specified resolution.  Identical to covering_grid, except that it\n      interpolates as necessary from coarse regions to fine.  See\n      :ref:`examining-grid-data-in-a-fixed-resolution-array`.\n\n**Fixed-Resolution Region**\n    | Class :class:`~yt.data_objects.construction_data_containers.YTArbitraryGrid`\n    | Usage: ``arbitrary_grid(left_edge, right_edge, dimensions, ds=None, field_parameters=None)``\n    | When particles are deposited on to mesh fields, they use the existing\n      mesh structure, but this may have too much or too little resolution\n      relative to the particle locations (or it may not exist at all!).  An\n      `arbitrary_grid` provides a means for generating a new independent mesh\n      structure for particle deposition and simple mesh field interpolation.\n      See :ref:`arbitrary-grid` for more information.\n\n**Projection**\n    | Class :class:`~yt.data_objects.construction_data_containers.YTQuadTreeProj`\n    | Usage: ``proj(field, axis, weight_field=None, center=None, ds=None, data_source=None, method=\"integrate\", field_parameters=None)``\n    | A 2D projection of a 3D volume along one of the axis directions.\n      By default, this is a line integral through the entire simulation volume\n      (although it can be a subset of that volume specified by a data object\n      with the ``data_source`` keyword).  Alternatively, one can specify\n      a weight_field and different ``method`` values to change the nature\n      of the projection outcome.  See :ref:`projection-types` for more information.\n\n**Streamline**\n    | Class :class:`~yt.data_objects.construction_data_containers.YTStreamline`\n    | Usage: ``streamline(coord_list, length, fields=None, ds=None, field_parameters=None)``\n    | A ``streamline`` can be traced out by identifying a starting coordinate (or\n      list of coordinates) and allowing it to trace a vector field, like gas\n      velocity.  See :ref:`streamlines` for more information.\n\n**Surface**\n    | Class :class:`~yt.data_objects.construction_data_containers.YTSurface`\n    | Usage: ``surface(data_source, field, field_value)``\n    | The surface defined by all an isocontour in any mesh field.  An existing\n      data object must be provided as the source, as well as a mesh field\n      and the value of the field which you desire the isocontour.  See\n      :ref:`extracting-isocontour-information`.\n\n.. _derived-quantities:\n\nProcessing Objects: Derived Quantities\n--------------------------------------\n\nDerived quantities are a way of calculating some bulk quantities associated\nwith all of the grid cells contained in a data object.\nDerived quantities can be accessed via the ``quantities`` interface.\nHere is an example of how to get the angular momentum vector calculated from\nall the cells contained in a sphere at the center of our dataset.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"my_data\")\n   sp = ds.sphere(\"c\", (10, \"kpc\"))\n   print(sp.quantities.angular_momentum_vector())\n\nSome quantities can be calculated for a specific particle type only. For example, to\nget the center of mass of only the stars within the sphere:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"my_data\")\n   sp = ds.sphere(\"c\", (10, \"kpc\"))\n   print(\n       sp.quantities.center_of_mass(\n           use_gas=False, use_particles=True, particle_type=\"star\"\n       )\n   )\n\n\nQuickly Processing Data\n^^^^^^^^^^^^^^^^^^^^^^^\n\nMost data objects now have multiple numpy-like methods that allow you to\nquickly process data.  More of these methods will be added over time and added\nto this list.  Most, if not all, of these map to other yt operations and are\ndesigned as syntactic sugar to slightly simplify otherwise somewhat obtuse\npipelines.\n\nThese operations are parallelized.\n\nYou can compute the extrema of a field by using the ``max`` or ``min``\nfunctions.  This will cache the extrema in between, so calling ``min`` right\nafter ``max`` will be considerably faster.  Here is an example.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   reg = ds.r[0.3:0.6, 0.2:0.4, 0.9:0.95]\n   min_rho = reg.min((\"gas\", \"density\"))\n   max_rho = reg.max((\"gas\", \"density\"))\n\nThis is equivalent to:\n\n.. code-block:: python\n\n   min_rho, max_rho = reg.quantities.extrema((\"gas\", \"density\"))\n\nThe ``max`` operation can also compute the maximum intensity projection:\n\n.. code-block:: python\n\n   proj = reg.max((\"gas\", \"density\"), axis=\"x\")\n   proj.plot()\n\nThis is equivalent to:\n\n.. code-block:: python\n\n   proj = ds.proj((\"gas\", \"density\"), \"x\", data_source=reg, method=\"max\")\n   proj.plot()\n\nThe same can be done with the ``min`` operation, computing a minimum\nintensity projection:\n\n.. code-block:: python\n\n   proj = reg.min((\"gas\", \"density\"), axis=\"x\")\n   proj.plot()\n\nThis is equivalent to:\n\n.. code-block:: python\n\n   proj = ds.proj((\"gas\", \"density\"), \"x\", data_source=reg, method=\"min\")\n   proj.plot()\n\nYou can also compute the ``mean`` value, which accepts a field, axis, and weight\nfunction.  If the axis is not specified, it will return the average value of\nthe specified field, weighted by the weight argument.  The weight argument\ndefaults to ``ones``, which performs an arithmetic average.  For instance:\n\n.. code-block:: python\n\n   mean_rho = reg.mean((\"gas\", \"density\"))\n   rho_by_vol = reg.mean((\"gas\", \"density\"), weight=(\"gas\", \"cell_volume\"))\n\nThis is equivalent to:\n\n.. code-block:: python\n\n   mean_rho = reg.quantities.weighted_average(\n       (\"gas\", \"density\"), weight_field=(\"index\", \"ones\")\n   )\n   rho_by_vol = reg.quantities.weighted_average(\n       (\"gas\", \"density\"), weight_field=(\"gas\", \"cell_volume\")\n   )\n\nIf an axis is provided, it will project along that axis and return it to you:\n\n.. code-block:: python\n\n   rho_proj = reg.mean((\"gas\", \"temperature\"), axis=\"y\", weight=(\"gas\", \"density\"))\n   rho_proj.plot()\n\nYou can also compute the ``std`` (standard deviation), which accepts a field,\naxis, and weight function. If the axis is not specified, it will\nreturn the standard deviation of the specified field, weighted by the weight\nargument.  The weight argument defaults to ``ones``. For instance:\n\n.. code-block:: python\n\n   std_rho = reg.std((\"gas\", \"density\"))\n   std_rho_by_vol = reg.std((\"gas\", \"density\"), weight=(\"gas\", \"cell_volume\"))\n\nThis is equivalent to:\n\n.. code-block:: python\n\n   std_rho = reg.quantities.weighted_standard_deviation(\n       (\"gas\", \"density\"), weight_field=(\"index\", \"ones\")\n   )\n   std_rho_by_vol = reg.quantities.weighted_standard_deviation(\n       (\"gas\", \"density\"), weight_field=(\"gas\", \"cell_volume\")\n   )\n\nIf an axis is provided, it will project along that axis and return it to you:\n\n.. code-block:: python\n\n   vy_std = reg.std((\"gas\", \"velocity_y\"), axis=\"y\", weight=(\"gas\", \"density\"))\n   vy_std.plot()\n\nThe ``sum`` function will add all the values in the data object.  It accepts a\nfield and, optionally, an axis.  If the axis is left unspecified, it will sum\nthe values in the object:\n\n.. code-block:: python\n\n   vol = reg.sum((\"gas\", \"cell_volume\"))\n\nIf the axis is specified, it will compute a projection using the method ``sum``\n(which does *not* take into account varying path length!) and return that to\nyou.\n\n.. code-block:: python\n\n   cell_count = reg.sum((\"index\", \"ones\"), axis=\"z\")\n   cell_count.plot()\n\nTo compute a projection where the path length *is* taken into account, you can\nuse the ``integrate`` function:\n\n.. code-block:: python\n\n   proj = reg.integrate((\"gas\", \"density\"), \"x\")\n\nAll of these projections supply the data object as their base input.\n\nOften, it can be useful to sample a field at the minimum and maximum of a\ndifferent field.  You can use the ``argmax`` and ``argmin`` operations to do\nthis.\n\n.. code-block:: python\n\n   reg.argmin((\"gas\", \"density\"), axis=(\"gas\", \"temperature\"))\n\nThis will return the temperature at the minimum density.\n\nIf you don't specify an ``axis``, it will return the spatial position of\nthe maximum value of the queried field.  Here is an example::\n\n  x, y, z = reg.argmin((\"gas\", \"density\"))\n\nAvailable Derived Quantities\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**Angular Momentum Vector**\n    | Class :class:`~yt.data_objects.derived_quantities.AngularMomentumVector`\n    | Usage: ``angular_momentum_vector(use_gas=True, use_particles=True, particle_type='all')``\n    | The mass-weighted average angular momentum vector of the particles, gas,\n      or both. The quantity can be calculated for all particles or a given\n      particle_type only.\n\n**Bulk Velocity**\n    | Class :class:`~yt.data_objects.derived_quantities.BulkVelocity`\n    | Usage: ``bulk_velocity(use_gas=True, use_particles=True, particle_type='all')``\n    | The mass-weighted average velocity of the particles, gas, or both.\n      The quantity can be calculated for all particles or a given\n      particle_type only.\n\n**Center of Mass**\n    | Class :class:`~yt.data_objects.derived_quantities.CenterOfMass`\n    | Usage: ``center_of_mass(use_cells=True, use_particles=False, particle_type='all')``\n    | The location of the center of mass. By default, it computes of\n      the *non-particle* data in the object, but it can be used on\n      particles, gas, or both. The quantity can be\n      calculated for all particles or a given particle_type only.\n\n\n**Extrema**\n    | Class :class:`~yt.data_objects.derived_quantities.Extrema`\n    | Usage: ``extrema(fields, non_zero=False)``\n    | The extrema of a field or list of fields.\n\n**Maximum Location Sampling**\n    | Class :class:`~yt.data_objects.derived_quantities.SampleAtMaxFieldValues`\n    | Usage: ``sample_at_max_field_values(fields, sample_fields)``\n    | The value of sample_fields at the maximum value in fields.\n\n**Minimum Location Sampling**\n    | Class :class:`~yt.data_objects.derived_quantities.SampleAtMinFieldValues`\n    | Usage: ``sample_at_min_field_values(fields, sample_fields)``\n    | The value of sample_fields at the minimum value in fields.\n\n**Minimum Location**\n    | Class :class:`~yt.data_objects.derived_quantities.MinLocation`\n    | Usage: ``min_location(fields)``\n    | The minimum of a field or list of fields as well\n      as the x,y,z location of that minimum.\n\n**Maximum Location**\n    | Class :class:`~yt.data_objects.derived_quantities.MaxLocation`\n    | Usage: ``max_location(fields)``\n    | The maximum of a field or list of fields as well\n      as the x,y,z location of that maximum.\n\n**Spin Parameter**\n    | Class :class:`~yt.data_objects.derived_quantities.SpinParameter`\n    | Usage: ``spin_parameter(use_gas=True, use_particles=True, particle_type='all')``\n    | The spin parameter for the baryons using the particles, gas, or both. The\n      quantity can be calculated for all particles or a given particle_type only.\n\n**Total Mass**\n    | Class :class:`~yt.data_objects.derived_quantities.TotalMass`\n    | Usage: ``total_mass()``\n    | The total mass of the object as a tuple of (total gas, total particle)\n      mass.\n\n**Total of a Field**\n    | Class :class:`~yt.data_objects.derived_quantities.TotalQuantity`\n    | Usage: ``total_quantity(fields)``\n    | The sum of a given field (or list of fields) over the entire object.\n\n**Weighted Average of a Field**\n    | Class :class:`~yt.data_objects.derived_quantities.WeightedAverageQuantity`\n    | Usage: ``weighted_average_quantity(fields, weight)``\n    | The weighted average of a field (or list of fields)\n      over an entire data object.  If you want an unweighted average,\n      then set your weight to be the field: ``ones``.\n\n**Weighted Standard Deviation of a Field**\n    | Class :class:`~yt.data_objects.derived_quantities.WeightedStandardDeviation`\n    | Usage: ``weighted_standard_deviation(fields, weight)``\n    | The weighted standard deviation of a field (or list of fields)\n      over an entire data object and the weighted mean.\n      If you want an unweighted standard deviation, then\n      set your weight to be the field: ``ones``.\n\n.. _arbitrary-grid:\n\nArbitrary Grids Objects\n-----------------------\n\nThe covering grid and smoothed covering grid objects mandate that they be\nexactly aligned with the mesh.  This is a\nholdover from the time when yt was used exclusively for data that came in\nregularly structured grid patches, and does not necessarily work as well for\ndata that is composed of discrete objects like particles.  To augment this, the\n:class:`~yt.data_objects.construction_data_containers.YTArbitraryGrid` object\nwas created, which enables construction of meshes (onto which particles can be\ndeposited or smoothed) in arbitrary regions.  This eliminates any assumptions\non yt's part about how the data is organized, and will allow for more\nfine-grained control over visualizations.\n\nAn example of creating an arbitrary grid would be to construct one, then query\nthe deposited particle density, like so:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"snapshot_010.hdf5\")\n\n   obj = ds.arbitrary_grid([0.0, 0.0, 0.0], [0.99, 0.99, 0.99], dims=[128, 128, 128])\n   print(obj[\"deposit\", \"all_density\"])\n\nWhile these cannot yet be used as input to projections or slices, slices and\nprojections can be taken of the data in them and visualized by hand.\n\nThese objects, as of yt 3.3, are now also able to \"voxelize\" mesh fields.  This\nmeans that you can query the \"density\" field and it will return the density\nfield as deposited, identically to how it would be deposited in a fixed\nresolution buffer.  Note that this means that contributions from misaligned or\npartially-overlapping cells are added in a volume-weighted way, which makes it\ninappropriate for some types of analysis.\n\n.. _boolean_data_objects:\n\nCombining Objects: Boolean Data Objects\n---------------------------------------\n\nA special type of data object is the *boolean* data object, which works with\ndata selection objects of any dimension.  It is built by relating already existing\ndata objects with the bitwise operators for AND, OR and XOR, as well as the\nsubtraction operator.  These are created by using the operators ``&`` for an\nintersection (\"AND\"), ``|`` for a union (\"OR\"), ``^`` for an exclusive or\n(\"XOR\"), and ``+`` and ``-`` for addition (\"OR\") and subtraction (\"NEG\").\nHere are some examples:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"snapshot_010.hdf5\")\n\n   sp1 = ds.sphere(\"c\", (0.1, \"unitary\"))\n   sp2 = ds.sphere(sp1.center + 2.0 * sp1.radius, (0.2, \"unitary\"))\n   sp3 = ds.sphere(\"c\", (0.05, \"unitary\"))\n\n   new_obj = sp1 + sp2\n   cutout = sp1 - sp3\n   sp4 = sp1 ^ sp2\n   sp5 = sp1 & sp2\n\n\nNote that the ``+`` operation and the ``|`` operation are identical.  For when\nmultiple objects are to be combined in an intersection or a union, there are\nthe data objects ``intersection`` and ``union`` which can be called, and which\nwill yield slightly higher performance than a sequence of calls to ``+`` or\n``&``.  For instance:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n   sp1 = ds.sphere((0.1, 0.2, 0.3), (0.05, \"unitary\"))\n   sp2 = ds.sphere((0.2, 0.2, 0.3), (0.10, \"unitary\"))\n   sp3 = ds.sphere((0.3, 0.2, 0.3), (0.15, \"unitary\"))\n\n   isp = ds.intersection([sp1, sp2, sp3])\n   usp = ds.union([sp1, sp2, sp3])\n\nThe ``isp`` and ``usp`` objects will act the same as a set of chained ``&`` and\n``|`` operations (respectively) but are somewhat easier to construct.\n\n.. _extracting-connected-sets:\n\nConnected Sets and Clump Finding\n--------------------------------\n\nThe underlying machinery used in :ref:`clump_finding` is accessible from any\ndata object.  This includes the ability to obtain and examine topologically\nconnected sets.  These sets are identified by examining cells between two\nthreshold values and connecting them.  What is returned to the user is a list\nof the intervals of values found, and extracted regions that contain only those\ncells that are connected.\n\nTo use this, call\n:meth:`~yt.data_objects.data_containers.YTSelectionContainer3D.extract_connected_sets` on\nany 3D data object.  This requests a field, the number of levels of levels sets to\nextract, the min and the max value between which sets will be identified, and\nwhether or not to conduct it in log space.\n\n.. code-block:: python\n\n   sp = ds.sphere(\"max\", (1.0, \"pc\"))\n   contour_values, connected_sets = sp.extract_connected_sets(\n       (\"gas\", \"density\"), 3, 1e-30, 1e-20\n   )\n\nThe first item, ``contour_values``, will be an array of the min value for each\nset of level sets.  The second (``connected_sets``) will be a dict of dicts.\nThe key for the first (outer) dict is the level of the contour, corresponding\nto ``contour_values``.  The inner dict returned is keyed by the contour ID.  It\ncontains :class:`~yt.data_objects.selection_data_containers.YTCutRegion`\nobjects.  These can be queried just as any other data object.  The clump finder\n(:ref:`clump_finding`) differs from the above method in that the contour\nidentification is performed recursively within each individual structure, and\nstructures can be kept or remerged later based on additional criteria, such as\ngravitational boundedness.\n\n.. _object-serialization:\n\nStoring and Loading Objects\n---------------------------\n\nOften, when operating interactively or via the scripting interface, it is\nconvenient to save an object to disk and then restart the calculation later or\ntransfer the data from a container to another filesystem.  This can be\nparticularly useful when working with extremely large datasets.  Field data\ncan be saved to disk in a format that allows for it to be reloaded just like\na regular dataset.  For information on how to do this, see\n:ref:`saving-data-containers`.\n"
  },
  {
    "path": "doc/source/analyzing/parallel_computation.rst",
    "content": ".. _parallel-computation:\n\nParallel Computation With yt\n============================\n\nyt has been instrumented with the ability to compute many -- most, even --\nquantities in parallel.  This utilizes the package\n`mpi4py <https://bitbucket.org/mpi4py/mpi4py>`_ to parallelize using the Message\nPassing Interface, typically installed on clusters.\n\n.. _capabilities:\n\nCapabilities\n------------\n\nCurrently, yt is able to perform the following actions in parallel:\n\n* Projections (:ref:`projection-plots`)\n* Slices (:ref:`slice-plots`)\n* Cutting planes (oblique slices) (:ref:`off-axis-slices`)\n* Covering grids (:ref:`examining-grid-data-in-a-fixed-resolution-array`)\n* Derived Quantities (total mass, angular momentum, etc)\n* 1-, 2-, and 3-D profiles (:ref:`generating-profiles-and-histograms`)\n* Halo analysis (:ref:`halo-analysis`)\n* Volume rendering (:ref:`volume_rendering`)\n* Isocontours & flux calculations (:ref:`extracting-isocontour-information`)\n\nThis list covers just about every action yt can take!  Additionally, almost all\nscripts will benefit from parallelization with minimal modification.  The goal\nof Parallel-yt has been to retain API compatibility and abstract all\nparallelism.\n\nSetting Up Parallel yt\n--------------------------\n\nTo run scripts in parallel, you must first install `mpi4py\n<https://bitbucket.org/mpi4py/mpi4py>`_ as well as an MPI library, if one is not\nalready available on your system.  Instructions for doing so are provided on the\nmpi4py website, but you may have luck by just running:\n\n.. code-block:: bash\n\n    $ python -m pip install mpi4py\n\nIf you have an Anaconda installation of yt and there is no MPI library on the\nsystem you are using try:\n\n.. code-block:: bash\n\n    $ conda install mpi4py\n\nThis will install `MPICH2 <https://www.mpich.org/>`_ and will interfere with\nother MPI libraries that are already installed. Therefore, it is preferable to\nuse the ``pip`` installation method.\n\nOnce mpi4py has been installed, you're all done!  You just need to launch your\nscripts with ``mpirun`` (or equivalent) and signal to yt that you want to\nrun them in parallel by invoking the ``yt.enable_parallelism()`` function in\nyour script.  In general, that's all it takes to get a speed benefit on a\nmulti-core machine.  Here is an example on an 8-core desktop:\n\n.. code-block:: bash\n\n    $ mpirun -np 8 python script.py\n\nThroughout its normal operation, yt keeps you aware of what is happening with\nregular messages to the stderr usually prefaced with:\n\n.. code-block:: bash\n\n    yt : [INFO   ] YYY-MM-DD HH:MM:SS\n\nHowever, when operating in parallel mode, yt outputs information from each\nof your processors to this log mode, as in:\n\n.. code-block:: bash\n\n    P000 yt : [INFO   ] YYY-MM-DD HH:MM:SS\n    P001 yt : [INFO   ] YYY-MM-DD HH:MM:SS\n\nin the case of two cores being used.\n\nIt's important to note that all of the processes listed in :ref:`capabilities`\nwork in parallel -- and no additional work is necessary to parallelize those\nprocesses.\n\nRunning a yt Script in Parallel\n-------------------------------\n\nMany basic yt operations will run in parallel if yt's parallelism is enabled at\nstartup.  For example, the following script finds the maximum density location\nin the simulation and then makes a plot of the projected density:\n\n.. code-block:: python\n\n   import yt\n\n   yt.enable_parallelism()\n\n   ds = yt.load(\"RD0035/RedshiftOutput0035\")\n   v, c = ds.find_max((\"gas\", \"density\"))\n   print(v, c)\n   p = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\"))\n   p.save()\n\nIf this script is run in parallel, two of the most expensive operations -\nfinding of the maximum density and the projection will be calculated in\nparallel.  If we save the script as ``my_script.py``, we would run it on 16 MPI\nprocesses using the following Bash command:\n\n.. code-block:: bash\n\n   $ mpirun -np 16 python my_script.py\n\n.. note::\n\n   If you run into problems, the you can use :ref:`remote-debugging` to examine\n   what went wrong.\n\nHow do I run my yt job on a subset of available processes\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\nYou can set the ``communicator`` keyword in the\n:func:`~yt.utilities.parallel_tools.parallel_analysis_interface.enable_parallelism`\ncall to a specific MPI communicator to specify a subset of available MPI\nprocesses.  If none is specified, it defaults to ``COMM_WORLD``.\n\nCreating Parallel and Serial Sections in a Script\n+++++++++++++++++++++++++++++++++++++++++++++++++\n\nMany yt operations will automatically run in parallel (see the next section for\na full enumeration), however some operations, particularly ones that print\noutput or save data to the filesystem, will be run by all processors in a\nparallel script.  For example, in the script above the lines ``print(v, c)`` and\n``p.save()`` will be run on all 16 processors.  This means that your terminal\noutput will contain 16 repetitions of the output of the print statement and the\nplot will be saved to disk 16 times (overwritten each time).\n\nyt provides two convenience functions that make it easier to run most of a\nscript in parallel but run some subset of the script on only one processor.  The\nfirst, :func:`~yt.funcs.is_root`, returns ``True`` if run on the 'root'\nprocessor (the processor with MPI rank 0) and ``False`` otherwise.  One could\nrewrite the above script to take advantage of :func:`~yt.funcs.is_root` like\nso:\n\n.. code-block:: python\n\n   import yt\n\n   yt.enable_parallelism()\n\n   ds = yt.load(\"RD0035/RedshiftOutput0035\")\n   v, c = ds.find_max((\"gas\", \"density\"))\n   p = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\"))\n   if yt.is_root():\n       print(v, c)\n       p.save()\n\nThe second function, :func:`~yt.funcs.only_on_root` accepts the name of a\nfunction as well as a set of parameters and keyword arguments to pass to the\nfunction.  This is useful when the serial component of your parallel script\nwould clutter the script or if you like writing your scripts as a series of\nisolated function calls.  I can rewrite the example from the beginning of this\nsection once more using :func:`~yt.funcs.only_on_root` to give you the flavor of\nhow to use it:\n\n.. code-block:: python\n\n   import yt\n\n   yt.enable_parallelism()\n\n\n   def print_and_save_plot(v, c, plot, verbose=True):\n       if verbose:\n           print(v, c)\n       plot.save()\n\n\n   ds = yt.load(\"RD0035/RedshiftOutput0035\")\n   v, c = ds.find_max((\"gas\", \"density\"))\n   p = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\"))\n   yt.only_on_root(print_and_save_plot, v, c, plot, verbose=True)\n\nTypes of Parallelism\n--------------------\n\nIn order to divide up the work, yt will attempt to send different tasks to\ndifferent processors.  However, to minimize inter-process communication, yt\nwill decompose the information in different ways based on the task.\n\nSpatial Decomposition\n+++++++++++++++++++++\n\nDuring this process, the index will be decomposed along either all three\naxes or along an image plane, if the process is that of projection.  This type\nof parallelism is overall less efficient than grid-based parallelism, but it\nhas been shown to obtain good results overall.\n\nThe following operations use spatial decomposition:\n\n* :ref:`halo-analysis`\n* :ref:`volume_rendering`\n\nGrid Decomposition\n++++++++++++++++++\n\nThe alternative to spatial decomposition is a simple round-robin of data chunks,\nwhich could be grids, octs, or whatever chunking mechanism is used by the code\nfrontend begin used.  This process allows yt to pool data access to a given\ndata file, which ultimately results in faster read times and better parallelism.\n\nThe following operations use chunk decomposition:\n\n* Projections (see :ref:`available-objects`)\n* Slices (see :ref:`available-objects`)\n* Cutting planes (see :ref:`available-objects`)\n* Covering grids (see :ref:`construction-objects`)\n* Derived Quantities (see :ref:`derived-quantities`)\n* 1-, 2-, and 3-D profiles (see :ref:`generating-profiles-and-histograms`)\n* Isocontours & flux calculations (see :ref:`surfaces`)\n\nParallelization over Multiple Objects and Datasets\n++++++++++++++++++++++++++++++++++++++++++++++++++\n\nIf you have a set of computational steps that need to apply identically and\nindependently to several different objects or datasets, a so-called\n`embarrassingly parallel <https://en.wikipedia.org/wiki/Embarrassingly_parallel>`_\ntask, yt can do that easily.  See the sections below on\n:ref:`parallelizing-your-analysis` and :ref:`parallel-time-series-analysis`.\n\nUse of ``piter()``\n^^^^^^^^^^^^^^^^^^\n\nIf you use parallelism over objects or datasets, you will encounter\nthe :func:`~yt.data_objects.time_series.DatasetSeries.piter` function.\n:func:`~yt.data_objects.time_series.DatasetSeries.piter` is a parallel iterator,\nwhich effectively doles out each item of a DatasetSeries object to a different\nprocessor.  In serial processing, you might iterate over a DatasetSeries by:\n\n.. code-block:: python\n\n    for dataset in dataset_series:\n        ...  # process\n\nBut in parallel, you can use ``piter()`` to force each dataset to go to\na different processor:\n\n.. code-block:: python\n\n    yt.enable_parallelism()\n    for dataset in dataset_series.piter():\n        ...  # process\n\nIn order to store information from the parallel processing step to\na data structure that exists on all of the processors operating in parallel\nwe offer the ``storage`` keyword in the\n:func:`~yt.data_objects.time_series.DatasetSeries.piter` function.\nYou may define an empty dictionary and include it as the keyword argument\n``storage`` to :func:`~yt.data_objects.time_series.DatasetSeries.piter`.\nThen, during the processing step, you can access\nthis dictionary as the ``sto`` object.  After the\nloop is finished, the dictionary is re-aggregated from all of the processors,\nand you can access the contents:\n\n.. code-block:: python\n\n    yt.enable_parallelism()\n    my_dictionary = {}\n    for sto, dataset in dataset_series.piter(storage=my_dictionary):\n        ...  # process\n        sto.result = ...  # some information processed for this dataset\n        sto.result_id = ...  # some identifier for this dataset\n\n    print(my_dictionary)\n\nBy default, the dataset series will be divided as equally as possible\namong the cores.  Often some datasets will require more work than\nothers.  We offer the ``dynamic`` keyword in the\n:func:`~yt.data_objects.time_series.DatasetSeries.piter` function to\nenable dynamic load balancing with a task queue.  Dynamic load\nbalancing works best with more cores and a variable workload.  Here\none process will act as a server to assign the next available dataset\nto any free client.  For example, a 16 core job will have 15 cores\nanalyzing the data with 1 core acting as the task manager.\n\n.. _parallelizing-your-analysis:\n\nParallelizing over Multiple Objects\n-----------------------------------\n\nIt is easy within yt to parallelize a list of tasks, as long as those tasks\nare independent of one another. Using object-based parallelism, the function\n:func:`~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_objects`\nwill automatically split up a list of tasks over the specified number of\nprocessors (or cores).  Please see this heavily-commented example:\n\n.. code-block:: python\n\n   # As always...\n   import yt\n\n   yt.enable_parallelism()\n\n   import glob\n\n   # The number 4, below, is the number of processes to parallelize over, which\n   # is generally equal to the number of MPI tasks the job is launched with.\n   # If num_procs is set to zero or a negative number, the for loop below\n   # will be run such that each iteration of the loop is done by a single MPI\n   # task. Put another way, setting it to zero means that no matter how many\n   # MPI tasks the job is run with, num_procs will default to the number of\n   # MPI tasks automatically.\n   num_procs = 4\n\n   # fns is a list of all the simulation data files in the current directory.\n   fns = glob.glob(\"./plot*\")\n   fns.sort()\n\n   # This dict will store information collected in the loop, below.\n   # Inside the loop each task will have a local copy of the dict, but\n   # the dict will be combined once the loop finishes.\n   my_storage = {}\n\n   # In this example, because the storage option is used in the\n   # parallel_objects function, the loop yields a tuple, which gets used\n   # as (sto, fn) inside the loop.\n   # In the loop, sto is essentially my_storage, but a local copy of it.\n   # If data does not need to be combined after the loop is done, the line\n   # would look like:\n   #       for fn in parallel_objects(fns, num_procs):\n   for sto, fn in yt.parallel_objects(fns, num_procs, storage=my_storage):\n       # Open a data file, remembering that fn is different on each task.\n       ds = yt.load(fn)\n       dd = ds.all_data()\n\n       # This copies fn and the min/max of density to the local copy of\n       # my_storage\n       sto.result_id = fn\n       sto.result = dd.quantities.extrema((\"gas\", \"density\"))\n\n       # Makes and saves a plot of the gas density.\n       p = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\"))\n       p.save()\n\n   # At this point, as the loop exits, the local copies of my_storage are\n   # combined such that all tasks now have an identical and full version of\n   # my_storage. Until this point, each task is unaware of what the other\n   # tasks have produced.\n   # Below, the values in my_storage are printed by only one task. The other\n   # tasks do nothing.\n   if yt.is_root():\n       for fn, vals in sorted(my_storage.items()):\n           print(fn, vals)\n\nThis example above can be modified to loop over anything that can be saved to\na Python list: halos, data files, arrays, and more.\n\n.. _parallel-time-series-analysis:\n\nParallelization over Multiple Datasets (including Time Series)\n--------------------------------------------------------------\n\nThe same ``parallel_objects`` machinery discussed above is turned on by\ndefault when using a :class:`~yt.data_objects.time_series.DatasetSeries` object\n(see :ref:`time-series-analysis`) to iterate over simulation outputs.  The\nsyntax for this is very simple.  As an example, we can use the following script\nto find the angular momentum vector in a 1 pc sphere centered on the maximum\ndensity cell in a large number of simulation outputs:\n\n.. code-block:: python\n\n   import yt\n\n   yt.enable_parallelism()\n\n   # Load all of the DD*/output_* files into a DatasetSeries object\n   # in this case it is a Time Series\n   ts = yt.load(\"DD*/output_*\")\n\n   # Define an empty storage dictionary for collecting information\n   # in parallel through processing\n   storage = {}\n\n   # Use piter() to iterate over the time series, one proc per dataset\n   # and store the resulting information from each dataset in\n   # the storage dictionary\n   for sto, ds in ts.piter(storage=storage):\n       sphere = ds.sphere(\"max\", (1.0, \"pc\"))\n       sto.result = sphere.quantities.angular_momentum_vector()\n       sto.result_id = str(ds)\n\n   # Print out the angular momentum vector for all of the datasets\n   for L in sorted(storage.items()):\n       print(L)\n\nNote that this script can be run in serial or parallel with an arbitrary number\nof processors.  When running in parallel, each output is given to a different\nprocessor.\n\nYou can also request a fixed number of processors to calculate each\nangular momentum vector.  For example, the following script will calculate each\nangular momentum vector using 4 workgroups, splitting up the pool available\nprocessors.  Note that parallel=1 implies that the analysis will be run using\n1 workgroup, whereas parallel=True will run with Nprocs workgroups.\n\n.. code-block:: python\n\n   import yt\n\n   yt.enable_parallelism()\n\n   ts = yt.DatasetSeries(\"DD*/output_*\", parallel=4)\n\n   for ds in ts.piter():\n       sphere = ds.sphere(\"max\", (1.0, \"pc\"))\n       L_vecs = sphere.quantities.angular_momentum_vector()\n\nIf you do not want to use ``parallel_objects`` parallelism when using a\nDatasetSeries object, set ``parallel = False``.  When running python in parallel,\nthis will use all of the available processors to evaluate the requested\noperation on each simulation output.  Some care and possibly trial and error\nmight be necessary to estimate the correct settings for your simulation\noutputs.\n\nNote, when iterating over several large datasets, running out of memory may\nbecome an issue as the internal data structures associated with each dataset\nmay not be properly de-allocated at the end of an iteration. If memory use\nbecomes a problem, it may be necessary to manually delete some of the larger\ndata structures.\n\n.. code-block:: python\n\n   import yt\n\n   yt.enable_parallelism()\n\n   ts = yt.DatasetSeries(\"DD*/output_*\", parallel=4)\n\n   for ds in ts.piter():\n       # do analysis here\n\n       ds.index.clear_all_data()\n\nMulti-level Parallelism\n-----------------------\n\nBy default, the\n:func:`~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_objects`\nand :func:`~yt.data_objects.time_series.DatasetSeries.piter` functions will allocate a\nsingle processor to each iteration of the parallelized loop. However, there may be\nsituations in which it is advantageous to have multiple processors working together\non each loop iteration. Like with any traditional for loop, nested loops with multiple\ncalls to :func:`~yt.utilities.parallel_tools.parallel_analysis_interface.enable_parallelism`\ncan be used to parallelize the functionality within a given loop iteration.\n\nIn the example below, we will create projections along the x, y, and z axis of the\ndensity and temperature fields. We will assume a total of 6 processors are available,\nallowing us to allocate to processors to each axis and project each field with a\nseparate processor.\n\n.. code-block:: python\n\n   import yt\n\n   yt.enable_parallelism()\n\n   # assume 6 total cores\n   # allocate 3 work groups of 2 cores each\n   for ax in yt.parallel_objects(\"xyz\", njobs=3):\n       # project each field with one of the two cores in the workgroup\n       for field in yt.parallel_objects([(\"gas\", \"density\"), (\"gas\", \"temperature\")]):\n           p = yt.ProjectionPlot(ds, ax, field, weight_field=(\"gas\", \"density\"))\n           p.save(\"figures/\")\n\nNote, in the above example, if the inner\n:func:`~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_objects`\ncall were removed from the loop, the two-processor work group would work together to\nproject each of the density and temperature fields. This is because the projection\nfunctionality itself is parallelized internally.\n\nThe :func:`~yt.data_objects.time_series.DatasetSeries.piter` function can also be used\nin the above manner with nested\n:func:`~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_objects`\nloops to allocate multiple processors to work on each dataset. As discussed above in\n:ref:`parallel-time-series-analysis`, the ``parallel`` keyword is used to control\nthe number of workgroups created for iterating over multiple datasets.\n\nParallel Performance, Resources, and Tuning\n-------------------------------------------\n\nOptimizing parallel jobs in yt is difficult; there are many parameters that\naffect how well and quickly the job runs.  In many cases, the only way to find\nout what the minimum (or optimal) number of processors is, or amount of memory\nneeded, is through trial and error.  However, this section will attempt to\nprovide some insight into what are good starting values for a given parallel\ntask.\n\nChunk Decomposition\n+++++++++++++++++++\n\nIn general, these types of parallel calculations scale very well with number of\nprocessors.  They are also fairly memory-conservative.  The two limiting factors\nis therefore the number of chunks in the dataset, and the speed of the disk the\ndata is stored on.  There is no point in running a parallel job of this kind\nwith more processors than chunks, because the extra processors will do absolutely\nnothing, and will in fact probably just serve to slow down the whole calculation\ndue to the extra overhead.  The speed of the disk is also a consideration - if\nit is not a high-end parallel file system, adding more tasks will not speed up\nthe calculation if the disk is already swamped with activity.\n\nThe best advice for these sort of calculations is to run with just a few\nprocessors and go from there, seeing if it the runtime improves noticeably.\n\n**Projections, Slices, Cutting Planes and Covering Grids**\n\nProjections, slices and cutting planes are the most common methods of creating\ntwo-dimensional representations of data.  All three have been parallelized in a\nchunk-based fashion.\n\n* **Projections**: projections are parallelized utilizing a quad-tree approach.\n  Data is loaded for each processor, typically by a process that consolidates\n  open/close/read operations, and each grid is then iterated over and cells are\n  deposited into a data structure that stores values corresponding to positions\n  in the two-dimensional plane.  This provides excellent load balancing, and in\n  serial is quite fast.  However, the operation by which quadtrees are joined\n  across processors scales poorly; while memory consumption scales well, the\n  time to completion does not.  As such, projections can often be done very\n  fast when operating only on a single processor!  The quadtree algorithm can\n  be used inline (and, indeed, it is for this reason that it is slow.)  It is\n  recommended that you attempt to project in serial before projecting in\n  parallel; even for the very largest datasets (Enzo 1024^3 root grid with 7\n  levels of refinement) in the absence of IO the quadtree algorithm takes only\n  three minutes or so on a decent processor.\n\n* **Slices**: to generate a slice, chunks that intersect a given slice are iterated\n  over and their finest-resolution cells are deposited.  The chunks are\n  decomposed via standard load balancing.  While this operation is parallel,\n  **it is almost never necessary to slice a dataset in parallel**, as all data is\n  loaded on demand anyway.  The slice operation has been parallelized so as to\n  enable slicing when running *in situ*.\n\n* **Cutting planes**: cutting planes are parallelized exactly as slices are.\n  However, in contrast to slices, because the data-selection operation can be\n  much more time consuming, cutting planes often benefit from parallelism.\n\n* **Covering Grids**: covering grids are parallelized exactly as slices are.\n\nObject-Based\n++++++++++++\n\nLike chunk decomposition, it does not help to run with more processors than the\nnumber of objects to be iterated over.\nThere is also the matter of the kind of work being done on each object, and\nwhether it is disk-intensive, cpu-intensive, or memory-intensive.\nIt is up to the user to figure out what limits the performance of their script,\nand use the correct amount of resources, accordingly.\n\nDisk-intensive jobs are limited by the speed of the file system, as above,\nand extra processors beyond its capability are likely counter-productive.\nIt may require some testing or research (e.g. supercomputer documentation)\nto find out what the file system is capable of.\n\nIf it is cpu-intensive, it's best to use as many processors as possible\nand practical.\n\nFor a memory-intensive job, each processor needs to be able to allocate enough\nmemory, which may mean using fewer than the maximum number of tasks per compute\nnode, and increasing the number of nodes.\nThe memory used per processor should be calculated, compared to the memory\non each compute node, which dictates how many tasks per node.\nAfter that, the number of processors used overall is dictated by the\ndisk system or CPU-intensity of the job.\n\n\nDomain Decomposition\n++++++++++++++++++++\n\nThe various types of analysis that utilize domain decomposition use them in\ndifferent enough ways that they are discussed separately.\n\n**Halo-Finding**\n\nHalo finding, along with the merger tree that uses halo finding, operates on the\nparticles in the volume, and is therefore mostly chunk-agnostic.  Generally, the\nbiggest concern for halo finding is the amount of memory needed.  There is\nsubtle art in estimating the amount of memory needed for halo finding, but a\nrule of thumb is that the HOP halo finder is the most memory intensive\n(:func:`HaloFinder`), and Friends of Friends (:func:`FOFHaloFinder`) being the\nmost memory-conservative. For more information, see :ref:`halo-analysis`.\n\n**Volume Rendering**\n\nThe simplest way to think about volume rendering, is that it load-balances over\nthe i/o chunks in the dataset.  Each processor is given roughly the same sized\nvolume to operate on.  In practice, there are just a few things to keep in mind\nwhen doing volume rendering.  First, it only uses a power of two number of\nprocessors.  If the job is run with 100 processors, only 64 of them will\nactually do anything.  Second, the absolute maximum number of processors is the\nnumber of chunks.  In order to keep work distributed evenly, typically the\nnumber of processors should be no greater than one-eighth or one-quarter the\nnumber of processors that were used to produce the dataset.\nFor more information, see :ref:`volume_rendering`.\n\nAdditional Tips\n---------------\n\n* Don't be afraid to change how a parallel job is run. Change the\n  number of processors, or memory allocated, and see if things work better\n  or worse. After all, it's just a computer, it doesn't pass moral judgment!\n\n* Similarly, human time is more valuable than computer time. Try increasing\n  the number of processors, and see if the runtime drops significantly.\n  There will be a sweet spot between speed of run and the waiting time in\n  the job scheduler queue; it may be worth trying to find it.\n\n* If you are using object-based parallelism but doing CPU-intensive computations\n  on each object, you may find that setting ``num_procs`` equal to the\n  number of processors per compute node can lead to significant speedups.\n  By default, most mpi implementations will assign tasks to processors on a\n  'by-slot' basis, so this setting will tell yt to do computations on a single\n  object using only the processors on a single compute node.  A nice application\n  for this type of parallelism is calculating a list of derived quantities for\n  a large number of simulation outputs.\n\n* It is impossible to tune a parallel operation without understanding what's\n  going on. Read the documentation, look at the underlying code, or talk to\n  other yt users. Get informed!\n\n* Sometimes it is difficult to know if a job is cpu, memory, or disk\n  intensive, especially if the parallel job utilizes several of the kinds of\n  parallelism discussed above. In this case, it may be worthwhile to put\n  some simple timers in your script (as below) around different parts.\n\n.. code-block:: python\n\n   import time\n\n   import yt\n\n   yt.enable_parallelism()\n\n   ds = yt.load(\"DD0152\")\n   t0 = time.time()\n   bigstuff, hugestuff = StuffFinder(ds)\n   BigHugeStuffParallelFunction(ds, bigstuff, hugestuff)\n   t1 = time.time()\n   for i in range(1000000):\n       tinystuff, ministuff = GetTinyMiniStuffOffDisk(\"in%06d.txt\" % i)\n       array = TinyTeensyParallelFunction(ds, tinystuff, ministuff)\n       SaveTinyMiniStuffToDisk(\"out%06d.txt\" % i, array)\n   t2 = time.time()\n\n   if yt.is_root():\n       print(\n           \"BigStuff took {:.5e} sec, TinyStuff took {:.5e} sec\".format(t1 - t0, t2 - t1)\n       )\n\n* Remember that if the script handles disk IO explicitly, and does not use\n  a built-in yt function to write data to disk,\n  care must be taken to\n  avoid `race-conditions <https://en.wikipedia.org/wiki/Race_conditions>`_.\n  Be explicit about which MPI task writes to disk using a construction\n  something like this:\n\n.. code-block:: python\n\n   if yt.is_root():\n       file = open(\"out.txt\", \"w\")\n       file.write(stuff)\n       file.close()\n\n* Many supercomputers allow users to ssh into the nodes that their job is\n  running on.\n  Many job schedulers send the names of the nodes that are\n  used in the notification emails, or a command like ``qstat -f NNNN``, where\n  ``NNNN`` is the job ID, will also show this information.\n  By ssh-ing into nodes, the memory usage of each task can be viewed in\n  real-time as the job runs (using ``top``, for example),\n  and can give valuable feedback about the\n  resources the task requires.\n\nAn Advanced Worked Example\n--------------------------\n\nBelow is a script used to calculate the redshift of first 99.9% ionization in a\nsimulation.  This script was designed to analyze a set of 100 outputs on\nGordon, running on 128 processors.  This script goes through three phases:\n\n#. Define a new derived field, which calculates the fraction of ionized\n   hydrogen as a function only of the total hydrogen density.\n#. Load a time series up, specifying ``parallel = 8``.  This means that it\n   will decompose into 8 jobs.  So if we ran on 128 processors, we would have\n   16 processors assigned to each output in the time series.\n#. Creating a big cube that will hold our results for this set of processors.\n   Note that this will be only for each output considered by this processor,\n   and this cube will not necessarily be filled in every cell.\n#. For each output, distribute the grids to each of the sixteen processors\n   working on that output.  Each of these takes the max of the ionized\n   redshift in their zone versus the accumulation cube.\n#. Iterate over slabs and find the maximum redshift in each slab of our\n   accumulation cube.\n\nAt the end, the root processor (of the global calculation) writes out an\nionization cube that contains the redshift of first reionization for each zone\nacross all outputs.\n\n.. literalinclude:: ionization_cube.py\n"
  },
  {
    "path": "doc/source/analyzing/particle_filter.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Filtering Particle Data\\n\",\n    \"Let us go through a full worked example.  Here we have a Tipsy SPH dataset.  By general\\n\",\n    \"inspection, we see that there are stars present in the dataset, since\\n\",\n    \"there are fields with field type: `Stars` in the `ds.field_list`. Let's look \\n\",\n    \"at the `derived_field_list` for all of the `Stars` fields. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"import yt\\n\",\n    \"\\n\",\n    \"ds = yt.load(\\\"TipsyGalaxy/galaxy.00300\\\")\\n\",\n    \"for field in ds.derived_field_list:\\n\",\n    \"    if field[0] == \\\"Stars\\\":\\n\",\n    \"        print(field)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We will filter these into young stars and old stars by masking on the ('Stars', 'creation_time') field. \\n\",\n    \"\\n\",\n    \"In order to do this, we first make a function which applies our desired cut.  This function must accept two arguments: `pfilter` and `data`.  The first argument is a `ParticleFilter` object that contains metadata about the filter its self.  The second argument is a yt data container.\\n\",\n    \"\\n\",\n    \"Let's call \\\"young\\\" stars only those stars with ages less 5 million years.  Since Tipsy assigns a very large `creation_time` for stars in the initial conditions, we need to also exclude stars with negative ages. \\n\",\n    \"\\n\",\n    \"Conversely, let's define \\\"old\\\" stars as those stars formed dynamically in the simulation with ages greater than 5 Myr.  We also include stars with negative ages, since these stars were included in the simulation initial conditions.\\n\",\n    \"\\n\",\n    \"We make use of `pfilter.filtered_type` so that the filter definition will use the same particle type as the one specified in the call to `add_particle_filter` below.  This makes the filter definition usable for arbitrary particle types.  Since we're only filtering the `\\\"Stars\\\"` particle type in this example, we could have also replaced `pfilter.filtered_type` with `\\\"Stars\\\"` and gotten the same result.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def young_stars(pfilter, data):\\n\",\n    \"    age = data.ds.current_time - data[pfilter.filtered_type, \\\"creation_time\\\"]\\n\",\n    \"    filter = np.logical_and(age.in_units(\\\"Myr\\\") <= 5, age >= 0)\\n\",\n    \"    return filter\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def old_stars(pfilter, data):\\n\",\n    \"    age = data.ds.current_time - data[pfilter.filtered_type, \\\"creation_time\\\"]\\n\",\n    \"    filter = np.logical_or(age.in_units(\\\"Myr\\\") >= 5, age < 0)\\n\",\n    \"    return filter\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we define these as particle filters within the yt universe with the\\n\",\n    \"`add_particle_filter()` function.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"yt.add_particle_filter(\\n\",\n    \"    \\\"young_stars\\\",\\n\",\n    \"    function=young_stars,\\n\",\n    \"    filtered_type=\\\"Stars\\\",\\n\",\n    \"    requires=[\\\"creation_time\\\"],\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"yt.add_particle_filter(\\n\",\n    \"    \\\"old_stars\\\", function=old_stars, filtered_type=\\\"Stars\\\", requires=[\\\"creation_time\\\"]\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let us now apply these filters specifically to our dataset.\\n\",\n    \"\\n\",\n    \"Let's double check that it worked by looking at the derived_field_list for any new fields created by our filter.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.add_particle_filter(\\\"young_stars\\\")\\n\",\n    \"ds.add_particle_filter(\\\"old_stars\\\")\\n\",\n    \"\\n\",\n    \"for field in ds.derived_field_list:\\n\",\n    \"    if \\\"young_stars\\\" in field or \\\"young_stars\\\" in field[1]:\\n\",\n    \"        print(field)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We see all of the new `young_stars` fields as well as the 4 deposit fields.  These deposit fields are `mesh` fields generated by depositing particle fields on the grid.  Let's generate a couple of projections of where the young and old stars reside in this simulation by accessing some of these new fields.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"p = yt.ProjectionPlot(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    [(\\\"deposit\\\", \\\"young_stars_cic\\\"), (\\\"deposit\\\", \\\"old_stars_cic\\\")],\\n\",\n    \"    width=(40, \\\"kpc\\\"),\\n\",\n    \"    center=\\\"m\\\",\\n\",\n    \")\\n\",\n    \"p.set_figure_size(5)\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We see that young stars are concentrated in regions of active star formation, while old stars are more spatially extended.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.5.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/analyzing/saving_data.rst",
    "content": ".. _saving_data:\n\nSaving Reloadable Data\n======================\n\nMost of the data loaded into or generated with yt can be saved to a\nformat that can be reloaded as a first-class dataset.  This includes\nthe following:\n\n  * geometric data containers (regions, spheres, disks, rays, etc.)\n\n  * grid data containers (covering grids, arbitrary grids, fixed\n    resolution buffers)\n\n  * spatial plots (projections, slices, cutting planes)\n\n  * profiles\n\n  * generic array data\n\nIn the case of projections, slices, and profiles, reloaded data can be\nused to remake plots.  For information on this, see :ref:`remaking-plots`.\n\n.. _saving-data-containers:\n\nGeometric Data Containers\n-------------------------\n\nData from geometric data containers can be saved with the\n:func:`~yt.data_objects.data_containers.YTDataContainer.save_as_dataset` function.\n\n.. notebook-cell::\n\n   import yt\n\n   ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n\n   sphere = ds.sphere([0.5] * 3, (10, \"Mpc\"))\n   fn = sphere.save_as_dataset(fields=[(\"gas\", \"density\"), (\"all\", \"particle_mass\")])\n   print(fn)\n\nThis function will return the name of the file to which the dataset\nwas saved.  The filename will be a combination of the name of the\noriginal dataset and the type of data container.  Optionally, a\nspecific filename can be given with the ``filename`` keyword.  If no\nfields are given, the fields that have previously been queried will\nbe saved.\n\nThe newly created dataset can be loaded like all other supported\ndata through ``yt.load``.  Once loaded, field data can be accessed\nthrough the traditional data containers or through the ``data``\nattribute, which will be a data container configured like the\noriginal data container used to make the dataset.  Grid data is\naccessed by the ``grid`` data type and particle data is accessed\nwith the original particle type.  As with the original dataset, grid\npositions and cell sizes are accessible with, for example,\n(\"grid\", \"x\") and (\"grid\", \"dx\").  Particle positions are\naccessible as (<particle_type>, \"particle_position_x\").  All original\nsimulation parameters are accessible in the ``parameters``\ndictionary, normally associated with all datasets.\n\n.. code-block:: python\n\n   sphere_ds = yt.load(\"DD0046_sphere.h5\")\n\n   # use the original data container\n   print(sphere_ds.data[\"grid\", \"density\"])\n\n   # create a new data container\n   ad = sphere_ds.all_data()\n\n   # grid data\n   print(ad[\"grid\", \"density\"])\n   print(ad[\"grid\", \"x\"])\n   print(ad[\"grid\", \"dx\"])\n\n   # particle data\n   print(ad[\"all\", \"particle_mass\"])\n   print(ad[\"all\", \"particle_position_x\"])\n\nNote that because field data queried from geometric containers is\nreturned as unordered 1D arrays, data container datasets are treated,\neffectively, as particle data.  Thus, 3D indexing of grid data from\nthese datasets is not possible.\n\n.. _saving-grid-data-containers:\n\nGrid Data Containers\n--------------------\n\nData containers that return field data as multidimensional arrays\ncan be saved so as to preserve this type of access.  This includes\ncovering grids, arbitrary grids, and fixed resolution buffers.\nSaving data from these containers works just as with geometric data\ncontainers.  Field data can be accessed through geometric data\ncontainers.\n\n.. code-block:: python\n\n   cg = ds.covering_grid(level=0, left_edge=[0.25] * 3, dims=[16] * 3)\n   fn = cg.save_as_dataset(fields=[(\"gas\", \"density\"), (\"all\", \"particle_mass\")])\n\n   cg_ds = yt.load(fn)\n   ad = cg_ds.all_data()\n   print(ad[\"grid\", \"density\"])\n\nMultidimensional indexing of field data is also available through\nthe ``data`` attribute.\n\n.. code-block:: python\n\n   print(cg_ds.data[\"grid\", \"density\"])\n\nFixed resolution buffers work just the same.\n\n.. code-block:: python\n\n   my_proj = ds.proj((\"gas\", \"density\"), \"x\", weight_field=(\"gas\", \"density\"))\n   frb = my_proj.to_frb(1.0, (800, 800))\n   fn = frb.save_as_dataset(fields=[(\"gas\", \"density\")])\n   frb_ds = yt.load(fn)\n   print(frb_ds.data[\"gas\", \"density\"])\n\n.. _saving-spatial-plots:\n\nSpatial Plots\n-------------\n\nSpatial plots, such as projections, slices, and off-axis slices\n(cutting planes) can also be saved and reloaded.\n\n.. code-block:: python\n\n   proj = ds.proj((\"gas\", \"density\"), \"x\", weight_field=(\"gas\", \"density\"))\n   proj.save_as_dataset()\n\nOnce reloaded, they can be handed to their associated plotting\nfunctions to make images.\n\n.. code-block:: python\n\n   proj_ds = yt.load(\"DD0046_proj.h5\")\n   p = yt.ProjectionPlot(proj_ds, \"x\", (\"gas\", \"density\"), weight_field=(\"gas\", \"density\"))\n   p.save()\n\n.. _saving-profile-data:\n\nProfiles\n--------\n\nProfiles created with :func:`~yt.data_objects.profiles.create_profile`,\n:class:`~yt.visualization.profile_plotter.ProfilePlot`, and\n:class:`~yt.visualization.profile_plotter.PhasePlot` can be saved with\nthe :func:`~yt.data_objects.profiles.save_as_dataset` function, which\nworks just as above.  Profile datasets are a type of non-spatial grid\ndatasets.  Geometric selection is not possible, but data can be\naccessed through the ``.data`` attribute.\n\n.. notebook-cell::\n\n   import yt\n   ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n   ad = ds.all_data()\n\n   profile_2d = yt.create_profile(ad, [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n                                  (\"gas\", \"mass\"), weight_field=None,\n                                  n_bins=(128, 128))\n   profile_2d.save_as_dataset()\n\n   prof_2d_ds = yt.load(\"DD0046_Profile2D.h5\")\n   print (prof_2d_ds.data[\"gas\", \"mass\"])\n\nThe x, y (if at least 2D), and z (if 3D) bin fields can be accessed as 1D\narrays with \"x\", \"y\", and \"z\".\n\n.. code-block:: python\n\n   print(prof_2d_ds.data[\"gas\", \"x\"])\n\nThe bin fields can also be returned with the same shape as the profile\ndata by accessing them with their original names.  This allows for\nboolean masking of profile data using the bin fields.\n\n.. code-block:: python\n\n   # density is the x bin field\n   print(prof_2d_ds.data[\"gas\", \"density\"])\n\nFor 1, 2, and 3D profile datasets, a fake profile object will be\nconstructed by accessing the \".profile\" attribute.  This is used\nprimarily in the case of 1 and 2D profiles to create figures using\n:class:`~yt.visualization.profile_plotter.ProfilePlot` and\n:class:`~yt.visualization.profile_plotter.PhasePlot`.\n\n.. code-block:: python\n\n   p = yt.PhasePlot(\n       prof_2d_ds.data,\n       (\"gas\", \"density\"),\n       (\"gas\", \"temperature\"),\n       (\"gas\", \"mass\"),\n       weight_field=None,\n   )\n   p.save()\n\n.. _saving-array-data:\n\nGeneric Array Data\n------------------\n\nGeneric arrays can be saved and reloaded as non-spatial data using\nthe :func:`~yt.frontends.ytdata.utilities.save_as_dataset` function,\nalso available as ``yt.save_as_dataset``.  As with profiles, geometric\nselection is not possible, but the data can be accessed through the\n``.data`` attribute.\n\n.. notebook-cell::\n\n   import yt\n   ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n\n   region = ds.box([0.25]*3, [0.75]*3)\n   sphere = ds.sphere(ds.domain_center, (10, \"Mpc\"))\n   my_data = {}\n   my_data[\"region_density\"] = region[\"gas\", \"density\"]\n   my_data[\"sphere_density\"] = sphere[\"gas\", \"density\"]\n   yt.save_as_dataset(ds, \"test_data.h5\", my_data)\n\n   array_ds = yt.load(\"test_data.h5\")\n   print (array_ds.data[\"data\", \"region_density\"])\n   print (array_ds.data[\"data\", \"sphere_density\"])\n\nArray data can be saved with or without a dataset loaded.  If no\ndataset has been loaded, as fake dataset can be provided as a\ndictionary.\n\n.. notebook-cell::\n\n   import numpy as np\n   import yt\n\n   my_data = {\"density\": yt.YTArray(np.random.random(10), \"g/cm**3\"),\n              \"temperature\": yt.YTArray(np.random.random(10), \"K\")}\n   fake_ds = {\"current_time\": yt.YTQuantity(10, \"Myr\")}\n   yt.save_as_dataset(fake_ds, \"random_data.h5\", my_data)\n\n   new_ds = yt.load(\"random_data.h5\")\n   print (new_ds.data[\"data\", \"density\"])\n"
  },
  {
    "path": "doc/source/analyzing/time_series_analysis.rst",
    "content": ".. _time-series-analysis:\n\nTime Series Analysis\n====================\n\nOften, one wants to analyze a continuous set of outputs from a simulation in a\nuniform manner.  A simple example would be to calculate the peak density in a\nset of outputs that were written out.  The problem with time series analysis in\nyt is general an issue of verbosity and clunkiness. Typically, one sets up a\nloop:\n\n.. code-block:: python\n\n   for dsi in range(30):\n       fn = \"DD%04i/DD%04i\" % (dsi, dsi)\n       ds = load(fn)\n       process_output(ds)\n\nBut this is not really very nice.  This ends up requiring a lot of maintenance.\nThe :class:`~yt.data_objects.time_series.DatasetSeries` object has been\ndesigned to remove some of this clunkiness and present an easier, more unified\napproach to analyzing sets of data.  Even better,\n:class:`~yt.data_objects.time_series.DatasetSeries` works in parallel by\ndefault (see :ref:`parallel-computation`), so you can use a ``DatasetSeries``\nobject to quickly and easily parallelize your analysis.  Since doing the same\nanalysis task on many simulation outputs is 'embarrassingly' parallel, this\nnaturally allows for almost arbitrary speedup - limited only by the number of\navailable processors and the number of simulation outputs.\n\nThe idea behind the current implementation of time series analysis is that\nthe underlying data and the operators that act on that data can and should be\ndistinct.  There are several operators provided, as well as facilities for\ncreating your own, and these operators can be applied either to datasets on the\nwhole or to subregions of individual datasets.\n\nThe simplest mechanism for creating a ``DatasetSeries`` object is to pass a glob\npattern to the ``yt.load`` function.\n\n.. code-block:: python\n\n   import yt\n\n   ts = yt.load(\"DD????/DD????\")\n\nThis will create a new time series, populated with all datasets that match the\npattern \"DD\" followed by four digits.  This object, here called ``ts``, can now\nbe analyzed in bulk.  Alternately, you can specify an already formatted list of\nfilenames directly to the :class:`~yt.data_objects.time_series.DatasetSeries`\ninitializer:\n\n.. code-block:: python\n\n   import yt\n\n   ts = yt.DatasetSeries([\"DD0030/DD0030\", \"DD0040/DD0040\"])\n\nAnalyzing Each Dataset In Sequence\n----------------------------------\n\nThe :class:`~yt.data_objects.time_series.DatasetSeries` object has two primary\nmethods of iteration.  The first is a very simple iteration, where each object\nis returned for iteration:\n\n.. code-block:: python\n\n   import yt\n\n   ts = yt.load(\"*/*.index\")\n   for ds in ts:\n       print(ds.current_time)\n\nThis can also operate in parallel, using\n:meth:`~yt.data_objects.time_series.DatasetSeries.piter`.  For more examples,\nsee:\n\n * :ref:`parallel-time-series-analysis`\n * The cookbook recipe for :ref:`cookbook-time-series-analysis`\n * :class:`~yt.data_objects.time_series.DatasetSeries`\n\nIn addition, the :class:`~yt.data_objects.time_series.DatasetSeries` object allows to\nselect an output based on its time or by its redshift (if defined) as follows:\n\n.. code-block:: python\n\n   import yt\n\n   ts = yt.load(\"*/*.index\")\n   # Get output at 3 Gyr\n   ds = ts.get_by_time((3, \"Gyr\"))\n   # This will fail if no output is found within 100 Myr\n   ds = ts.get_by_time((3, \"Gyr\"), tolerance=(100, \"Myr\"))\n   # Get the output at the time right before and after 3 Gyr\n   ds_before = ts.get_by_time((3, \"Gyr\"), prefer=\"smaller\")\n   ds_after = ts.get_by_time((3, \"Gyr\"), prefer=\"larger\")\n\n   # For cosmological simulations, you can also select an output by its redshift\n   # with the same options as above\n   ds = ts.get_by_redshift(0.5)\n\nFor more information, see :meth:`~yt.data_objects.time_series.DatasetSeries.get_by_time` and\n:meth:`~yt.data_objects.time_series.DatasetSeries.get_by_redshift`.\n\n.. _analyzing-an-entire-simulation:\n\nAnalyzing an Entire Simulation\n------------------------------\n\n.. note:: Implemented for the Enzo, Gadget, OWLS, and Exodus II frontends.\n\nThe parameter file used to run a simulation contains all the information\nnecessary to know what datasets should be available.  The ``simulation``\nconvenience function allows one to create a ``DatasetSeries`` object of all\nor a subset of all data created by a single simulation.\n\nTo instantiate, give the parameter file and the simulation type.\n\n.. code-block:: python\n\n  import yt\n\n  my_sim = yt.load_simulation(\"enzo_tiny_cosmology/32Mpc_32.enzo\", \"Enzo\")\n\nThen, create a ``DatasetSeries`` object with the\n:meth:`frontends.enzo.simulation_handling.EnzoSimulation.get_time_series`\nfunction.  With no additional keywords, the time series will include every\ndataset.  If the ``find_outputs`` keyword is set to ``True``, a search of the\nsimulation directory will be performed looking for potential datasets.  These\ndatasets will be temporarily loaded in order to figure out the time and\nredshift associated with them.  This can be used when simulation data was\ncreated in a non-standard way, making it difficult to guess the corresponding\ntime and redshift information\n\n.. code-block:: python\n\n  my_sim.get_time_series()\n\nAfter this, time series analysis can be done normally.\n\n.. code-block:: python\n\n  for ds in my_sim.piter():\n      all_data = ds.all_data()\n      print(all_data.quantities.extrema((\"gas\", \"density\")))\n\nAdditional keywords can be given to\n:meth:`frontends.enzo.simulation_handling.EnzoSimulation.get_time_series`\nto select a subset of the total data:\n\n* ``time_data`` (*bool*): Whether or not to include time outputs when\n  gathering datasets for time series.  Default: True.  (Enzo only)\n\n* ``redshift_data`` (*bool*): Whether or not to include redshift outputs\n  when gathering datasets for time series.  Default: True.  (Enzo only)\n\n* ``initial_time`` (*float*): The earliest time for outputs to be included.\n  If None, the initial time of the simulation is used.  This can be used in\n  combination with either ``final_time`` or ``final_redshift``.  Default: None.\n\n* ``final_time`` (*float*): The latest time for outputs to be included.  If\n  None, the final time of the simulation is used.  This can be used in\n  combination with either ``initial_time`` or ``initial_redshift``.  Default: None.\n\n* ``times`` (*list*): A list of times for which outputs will be found.\n  Default: None.\n\n* ``initial_redshift`` (*float*): The earliest redshift for outputs to be\n  included.  If None, the initial redshift of the simulation is used.  This\n  can be used in combination with either ``final_time`` or ``final_redshift``.\n  Default: None.\n\n* ``final_redshift`` (*float*): The latest redshift for outputs to be included.\n  If None, the final redshift of the simulation is used.  This can be used\n  in combination with either ``initial_time`` or ``initial_redshift``.\n  Default: None.\n\n* ``redshifts`` (*list*): A list of redshifts for which outputs will be found.\n  Default: None.\n\n* ``initial_cycle`` (*float*): The earliest cycle for outputs to be\n  included.  If None, the initial cycle of the simulation is used.  This can\n  only be used with final_cycle.  Default: None.  (Enzo only)\n\n* ``final_cycle`` (*float*): The latest cycle for outputs to be included.\n  If None, the final cycle of the simulation is used.  This can only be used\n  in combination with initial_cycle.  Default: None.  (Enzo only)\n\n* ``tolerance`` (*float*):  Used in combination with ``times`` or ``redshifts``\n  keywords, this is the tolerance within which outputs are accepted given\n  the requested times or redshifts.  If None, the nearest output is always\n  taken.  Default: None.\n\n* ``parallel`` (*bool*/*int*): If True, the generated ``DatasetSeries`` will\n  divide the work such that a single processor works on each dataset.  If an\n  integer is supplied, the work will be divided into that number of jobs.\n  Default: True.\n"
  },
  {
    "path": "doc/source/analyzing/units.rst",
    "content": ".. _units:\n\nSymbolic Units\n==============\n\nThis section describes yt's symbolic unit capabilities. This is provided as\nquick introduction for those who are already familiar with yt but want to learn\nmore about the unit system.  Please see :ref:`analyzing` and :ref:`visualizing`\nfor more detail about querying, analyzing, and visualizing data in yt.\n\nOriginally the unit system was a part of yt proper but since the yt 4.0 release,\nthe unit system has been split off into `its own library\n<https://github.com/yt-project/unyt>`_, ``unyt``.\n\nFor a detailed discussion of how to use ``unyt``, we suggest taking a look at\nthe unyt documentation available at https://unyt.readthedocs.io/, however yt\nadds additional capabilities above and beyond what is provided by ``unyt``\nalone, we describe those capabilities below.\n\nSelecting data from a data object\n---------------------------------\n\nThe data returned by yt will have units attached to it. For example, let's query\na data object for the ``('gas', 'density')`` field:\n\n    >>> import yt\n    >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')\n    >>> dd = ds.all_data()\n    >>> dd['gas', 'density']\n    unyt_array([4.92775113e-31, 4.94005233e-31, 4.93824694e-31, ...,\n                1.12879234e-25, 1.59561490e-25, 1.09824903e-24], 'g/cm**3')\n\nWe can see how we get back a ``unyt_array`` instance. A ``unyt_array`` is a\nsubclass of NumPy's NDarray type that has units attached to it:\n\n    >>> dd['gas', 'density'].units\n    g/cm**3\n\nIt is straightforward to convert data to different units:\n\n    >>> dd['gas', 'density'].to('Msun/kpc**3')\n    unyt_array([7.28103608e+00, 7.29921182e+00, 7.29654424e+00, ...,\n               1.66785569e+06, 2.35761291e+06, 1.62272618e+07], 'Msun/kpc**3')\n\nFor more details about working with ``unyt_array``, see the `the documentation\n<https://unyt.readthedocs.io>`__ for ``unyt``.\n\nApplying Units to Data\n----------------------\n\nA ``unyt_array`` can be created from a list, tuple, or NumPy array using\nmultiplication with a ``Unit`` object. For convenience, each yt dataset has a\n``units`` attribute one can use to obtain unit objects for this purpose:\n\n   >>> data = np.random.random((100, 100))\n   >>> data_with_units = data * ds.units.gram\n\nAll units known to the dataset will be available via ``ds.units``, including\ncode units and comoving units.\n\nDerived Field Units\n-------------------\n\nSpecial care often needs to be taken to ensure the result of a derived field\nwill come out in the correct units. The yt unit system will double-check for you\nto make sure you are not accidentally making a unit conversion mistake. To see\nwhat that means in practice, let's define a derived field corresponding to the\nsquare root of the gas density:\n\n    >>> import yt\n    >>> import numpy as np\n\n    >>> def root_density(data):\n    ...     return np.sqrt(data['gas', 'density'])\n\n    >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')\n\n    >>> ds.add_field((\"gas\", \"root_density\"), units=\"(g/cm**3)**(1/2)\",\n    ...              function=root_density, sampling_type='cell')\n\n    >>> ad = ds.all_data()\n    >>> ad['gas', 'root_density']\n    unyt_array([7.01979425e-16, 7.02855059e-16, 7.02726614e-16, ...,\n                3.35975050e-13, 3.99451486e-13, 1.04797377e-12], 'sqrt(g)/cm**(3/2)')\n\nNo special unit logic needs to happen inside of the function: the result of\n``np.sqrt`` will have the correct units:\n\n    >>> np.sqrt(ad['gas', 'density'])\n    unyt_array([7.01979425e-16, 7.02855059e-16, 7.02726614e-16, ...,\n                3.35975050e-13, 3.99451486e-13, 1.04797377e-12], 'sqrt(g)/cm**(3/2)')\n\nOne could also specify any other units that have dimensions of square root of\ndensity and yt would automatically convert the return value of the field\nfunction to the specified units. An error would be raised if the units are not\ndimensionally equivalent to the return value of the field function.\n\nCode Units\n----------\n\nAll yt datasets are associated with a \"code\" unit system that corresponds to\nwhatever unit system the data is represented in on-disk. Let's take a look at\nthe data in an Enzo simulation, specifically the ``(\"enzo\", \"Density\")`` field:\n\n    >>> import yt\n    >>> ds = yt.load('Enzo_64/DD0043/data0043')\n    >>> ad = ds.all_data()\n    >>> ad[\"enzo\", \"Density\"]\n    unyt_array([6.74992726e-02, 6.12111635e-02, 8.92988636e-02, ...,\n                9.09875931e+01, 5.66932465e+01, 4.27780263e+01], 'code_mass/code_length**3')\n\nwe see we get back data from yt in units of ``code_mass/code_length**3``. This\nis the density unit formed out of the base units of mass and length in the\ninternal unit system in the simulation. We can see the values of these units by\nlooking at the ``length_unit`` and ``mass_unit`` attributes of the dataset\nobject:\n\n    >>> ds.length_unit\n    unyt_quantity(128, 'Mpccm/h')\n    >>> ds.mass_unit\n    unyt_quantity(4.89045159e+50, 'g')\n\nAnd we can see that both of these have values of 1 in the code unit system.\n\n    >>> ds.length_unit.to('code_length')\n    unyt_quantity(1., 'code_length')\n    >>> ds.mass_unit.to('code_mass')\n    unyt_quantity(1., 'code_mass')\n\nIn addition to ``length_unit`` and ``mass_unit``, there are also ``time_unit``,\n``velocity_unit``, and ``magnetic_unit`` attributes for this dataset. Some\nfrontends also define a ``density_unit``, ``pressure_unit``,\n``temperature_unit``, and ``specific_energy`` attribute. If these are not defined\nthen the corresponding unit is calculated from the base length, mass, and time unit.\nEach of these attributes corresponds to a unit in the code unit system:\n\n    >>> [un for un in dir(ds.units) if un.startswith('code')]\n    ['code_density',\n     'code_length',\n     'code_magnetic',\n     'code_mass',\n     'code_metallicity',\n     'code_pressure',\n     'code_specific_energy',\n     'code_temperature',\n     'code_time',\n     'code_velocity']\n\nYou can use these unit names to convert arbitrary data into a dataset's code\nunit system:\n\n    >>> u = ds.units\n    >>> data = 10**-30 * u.g / u.cm**3\n    >>> data.to('code_density')\n    unyt_quantity(0.36217187, 'code_density')\n\nNote how in this example we used ``ds.units`` instead of the top-level ``unyt``\nnamespace or ``yt.units``. This is because the units from ``ds.units`` know\nabout the dataset's code unit system and can convert data into it. Unit objects\nfrom ``unyt`` or ``yt.units`` will not know about any particular dataset's unit\nsystem.\n\n\n.. _cosmological-units:\n\nComoving units for Cosmological Simulations\n-------------------------------------------\n\nThe length unit of the dataset I used above uses a cosmological unit:\n\n    >>> print(ds.length_unit)\n    128 Mpccm/h\n\nIn English, this says that the length unit is 128 megaparsecs in the comoving\nframe, scaled as if the hubble constant were 100 km/s/Mpc. Although :math:`h`\nisn't really a unit, yt treats it as one for the purposes of the unit system.\n\nAs an aside, `Darren Croton's research note <https://arxiv.org/abs/1308.4150>`_\non the history, use, and interpretation of :math:`h` as it appears in the\nastronomical literature is pretty much required reading for anyone who has to\ndeal with factors of :math:`h` every now and then.\n\nIn yt, comoving length unit symbols are named following the pattern ``< length\nunit >cm``, i.e. ``pccm`` for comoving parsec or ``mcm`` for a comoving\nmeter. A comoving length unit is different from the normal length unit by a\nfactor of :math:`(1+z)`:\n\n    >>> u = ds.units\n    >>> print((1*u.Mpccm)/(1*u.Mpc))\n    0.9986088499304777 dimensionless\n    >>> 1 / (1 + ds.current_redshift)\n    0.9986088499304776\n\nAs we saw before, h is treated like any other unit symbol. It has dimensionless\nunits, just like a scalar:\n\n    >>> (1*u.Mpc)/(1*u.Mpc/u.h)\n    unyt_quantity(0.71, '(dimensionless)')\n    >>> ds.hubble_constant\n    0.71\n\nUsing parsec as an example,\n\n  * ``pc``\n    Proper parsecs, :math:`\\rm{pc}`.\n\n  * ``pccm``\n    Comoving parsecs, :math:`\\rm{pc}/(1+z)`.\n\n  * ``pccm/h``\n    Comoving parsecs normalized by the scaled hubble constant, :math:`\\rm{pc}/h/(1+z)`.\n\n  * ``pc/h``\n    Proper parsecs, normalized by the scaled hubble constant, :math:`\\rm{pc}/h`.\n\nOverriding Code Unit Definitions\n--------------------------------\n\nOn occasion, you might have a dataset for a supported frontend that does not\nhave the conversions to code units accessible or you may want to change them\noutright. ``yt`` provides a mechanism so that one may provide their own code\nunit definitions to ``yt.load``, which override the default rules for a given\nfrontend for defining code units.\n\nThis is provided through the ``units_override`` argument to ``yt.load``. We'll\nuse an example of an Athena dataset. First, a call to ``yt.load`` without\n``units_override``:\n\n    >>> ds = yt.load(\"MHDSloshing/virgo_low_res.0054.vtk\")\n    >>> ds.length_unit\n    unyt_quantity(1., 'cm')\n    >>> ds.mass_unit\n    unyt_quantity(1., 'g')\n    >>> ds.time_unit\n    unyt_quantity(1., 's')\n    >>> sp1 = ds1.sphere(\"c\", (0.1, \"unitary\"))\n    >>> print(sp1[\"gas\", \"density\"])\n    [0.05134981 0.05134912 0.05109047 ... 0.14608461 0.14489453 0.14385277] g/cm**3\n\nThis particular simulation is of a galaxy cluster merger so these density values\nare way, way too high. This is happening because Athena does not encode any\ninformation about the unit system used in the simulation or the output data, so\nyt cannot infer that information and must make an educated guess. In this case\nit incorrectly assumes the data are in CGS units.\n\nHowever, we know *a priori* what the unit system *should* be, and we can supply\na ``units_override`` dictionary to ``yt.load`` to override the incorrect\nassumptions yt is making about this dataset. Let's define:\n\n    >>> units_override = {\"length_unit\": (1.0, \"Mpc\"),\n    ...                   \"time_unit\": (1.0, \"Myr\"),\n    ...                   \"mass_unit\": (1.0e14, \"Msun\")}\n\nThe ``units_override`` dictionary can take the following keys:\n\n    * ``length_unit``\n    * ``time_unit``\n    * ``mass_unit``\n    * ``magnetic_unit``\n    * ``temperature_unit``\n\nand the associated values can be ``(value, \"unit\")`` tuples, ``unyt_quantity``\ninstances, or floats (in the latter case they are assumed to have the\ncorresponding cgs unit). Now let's reload the dataset using our\n``units_override`` dict:\n\n    >>> ds = yt.load(\"MHDSloshing/virgo_low_res.0054.vtk\",\n    ...              units_override=units_override)\n    >>> sp = ds.sphere(\"c\",(0.1,\"unitary\"))\n    >>> print(sp[\"gas\", \"density\"])\n    [3.47531683e-28 3.47527018e-28 3.45776515e-28 ... 9.88689766e-28\n     9.80635384e-28 9.73584863e-28] g/cm**3\n\nand we see how the data now have much more sensible values for a galaxy cluster\nmerge simulation.\n\nComparing Units From Different Simulations\n------------------------------------------\n\nThe code units from different simulations will have different conversions to\nphysical coordinates. This can get confusing when working with data from more\nthan one simulation or from a single simulation where the units change with\ntime.\n\nAs an example, let's load up two enzo datasets from different redshifts in the\nsame cosmology simulation, one from high redshift:\n\n    >>> ds1 = yt.load('Enzo_64/DD0002/data0002')\n    >>> ds1.current_redshift\n    7.8843748886903\n    >>> ds1.length_unit\n    unyt_quantity(128, 'Mpccm/h')\n    >>> ds1.length_unit.in_cgs()\n    unyt_quantity(6.26145538e+25, 'cm')\n\nAnd another from low redshift:\n\n    >>> ds2 = yt.load('Enzo_64/DD0043/data0043')\n    >>> ds2.current_redshift\n    0.0013930880640796\n    >>> ds2.length_unit\n    unyt_quantity(128, 'Mpccm/h')\n    >>> ds2.length_unit.in_cgs()\n    unyt_quantity(5.55517285e+26, 'cm')\n\nNow despite the fact that ``'Mpccm/h'`` means different things for the two\ndatasets, it's still a well-defined operation to take the ratio of the two\nlength units:\n\n    >>> ds2.length_unit / ds1.length_unit\n    unyt_quantity(8.87201539, '(dimensionless)')\n\nBecause code units and comoving units are defined relative to a physical unit\nsystem, ``unyt`` is able to give the correct answer here. So long as the result\ncomes out dimensionless or in a physical unit then the answer will be\nwell-defined. However, if we want the answer to come out in the internal units\nof one particular dataset, additional care must be taken. For an example where\nthis might be an issue, let's try to compute the sum of two comoving distances\nfrom each simulation:\n\n    >>> d1 = 12 * ds1.units.Mpccm\n    >>> d2 = 12 * ds2.units.Mpccm\n    >>> d1 + d2\n    unyt_quantity(118.46418468, 'Mpccm')\n    >>> d2 + d1\n    unyt_quantity(13.35256754, 'Mpccm')\n\nSo this is definitely weird - addition appears to not be associative anymore!\nHowever, both answers are correct, the confusion is arising because ``\"Mpccm\"``\nis ambiguous in these expressions. In situations like this, ``unyt`` will use\nthe definition for units from the leftmost term in an expression, so the first\nexample is returning data in high-redshift comoving megaparsecs, while the\nsecond example returns data in low-redshift comoving megaparsecs.\n\nWherever possible it's best to do calculations in physical units when working\nwith more than one dataset. If you need to use comoving units or code units then\nextra care must be taken in your code to avoid ambiguity.\n"
  },
  {
    "path": "doc/source/conf.py",
    "content": "#\n# yt documentation build configuration file, created by\n# sphinx-quickstart on Tue Jan 11 09:46:53 2011.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport glob\nimport os\nimport sys\n\nimport sphinx_bootstrap_theme\n\non_rtd = os.environ.get(\"READTHEDOCS\", None) == \"True\"\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, os.path.abspath(\"../extensions/\"))\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.mathjax\",\n    \"sphinx.ext.viewcode\",\n    \"sphinx.ext.napoleon\",\n    \"yt_cookbook\",\n    \"yt_colormaps\",\n    \"config_help\",\n    \"yt_showfields\",\n    \"nbsphinx\",\n]\n\nif not on_rtd:\n    extensions.append(\"sphinx.ext.autosummary\")\n    extensions.append(\"pythonscript_sphinxext\")\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix of source filenames.\nsource_suffix = \".rst\"\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = \"The yt Project\"\ncopyright = \"2013-2021, the yt Project\"\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = \"4.5\"\n# The full version, including alpha/beta/rc tags.\nrelease = \"4.5-dev\"\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\nshow_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = \"bootstrap\"\nhtml_theme_path = sphinx_bootstrap_theme.get_html_theme_path()\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\nhtml_theme_options = dict(\n    bootstrap_version=\"3\",\n    bootswatch_theme=\"readable\",\n    navbar_links=[\n        (\"\", \"\"),  # see https://github.com/yt-project/yt/pull/3423\n        (\"How to get help\", \"help/index\"),\n        (\"Quickstart notebooks\", \"quickstart/index\"),\n        (\"Cookbook\", \"cookbook/index\"),\n    ],\n    navbar_sidebarrel=False,\n    globaltoc_depth=2,\n)\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n# html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = \"_static/yt_icon.png\"\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\", \"analyzing/_static\"]\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n# html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\nhtml_domain_indices = False\n\n# If false, no index is generated.\nhtml_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\nhtml_show_sourcelink = False\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"ytdoc\"\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\n# The paper size ('letter' or 'a4').\n# latex_paper_size = 'letter'\n\n# The font size ('10pt', '11pt' or '12pt').\n# latex_font_size = '10pt'\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n    (\"index\", \"yt.tex\", \"yt Documentation\", \"The yt Project\", \"manual\"),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n# latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n# latex_appendices = []\n\n# If false, no module index is generated.\n# latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(\"index\", \"yt\", \"yt Documentation\", [\"The yt Project\"], 1)]\n\nnbsphinx_allow_errors = True\n\n# Example configuration for intersphinx: refer to the Python standard library.\nintersphinx_mapping = {\n    \"python\": (\"https://docs.python.org/3/\", None),\n    \"ipython\": (\"https://ipython.readthedocs.io/en/stable/\", None),\n    \"numpy\": (\"https://numpy.org/doc/stable/\", None),\n    \"matplotlib\": (\"https://matplotlib.org/stable/\", None),\n    \"astropy\": (\"https://docs.astropy.org/en/stable\", None),\n    \"pandas\": (\"https://pandas.pydata.org/pandas-docs/stable\", None),\n    \"trident\": (\"https://trident.readthedocs.io/en/latest/\", None),\n    \"yt_astro_analysis\": (\"https://yt-astro-analysis.readthedocs.io/en/latest/\", None),\n    \"yt_attic\": (\"https://yt-attic.readthedocs.io/en/latest/\", None),\n    \"pytest\": (\"https://docs.pytest.org/en/stable\", None),\n}\n\nif not on_rtd:\n    autosummary_generate = glob.glob(\"reference/api/api.rst\")\n\n\n# as of Sphinx 3.1.2 this is the supported way to link custom style sheets\ndef setup(app):\n    app.add_css_file(\"custom.css\")\n"
  },
  {
    "path": "doc/source/cookbook/amrkdtree_downsampling.py",
    "content": "# Using AMRKDTree Homogenized Volumes to examine large datasets\n# at lower resolution.\n\n# In this example we will show how to use the AMRKDTree to take a simulation\n# with 8 levels of refinement and only use levels 0-3 to render the dataset.\n\n# Currently this cookbook is flawed in that the data that is covered by the\n# higher resolution data gets masked during the rendering.  This should be\n# fixed by changing either the data source or the code in\n# yt/utilities/amr_kdtree.py where data is being masked for the partitioned\n# grid.  Right now the quick fix is to create a data_collection, but this\n# will only work for patch based simulations that have ds.index.grids.\n\n# We begin by loading up yt, and importing the AMRKDTree\nimport numpy as np\n\nimport yt\nfrom yt.utilities.amr_kdtree.api import AMRKDTree\n\n# Load up a dataset and define the kdtree\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\nim, sc = yt.volume_render(ds, (\"gas\", \"density\"), fname=\"v0.png\")\nsc.camera.set_width(ds.arr(100, \"kpc\"))\nrender_source = sc.get_source()\nkd = render_source.volume\n\n# Print out specifics of KD Tree\nprint(\"Total volume of all bricks = %i\" % kd.count_volume())\nprint(\"Total number of cells = %i\" % kd.count_cells())\n\nnew_source = ds.all_data()\nnew_source.max_level = 3\nkd_low_res = AMRKDTree(ds, data_source=new_source)\nprint(kd_low_res.count_volume())\nprint(kd_low_res.count_cells())\n\n# Now we pass this in as the volume to our camera, and render the snapshot\n# again.\n\nrender_source.set_volume(kd_low_res)\nrender_source.set_field((\"gas\", \"density\"))\nsc.save(\"v1.png\", sigma_clip=6.0)\n\n# This operation was substantially faster.  Now lets modify the low resolution\n# rendering until we find something we like.\n\ntf = render_source.transfer_function\ntf.clear()\ntf.add_layers(\n    4,\n    0.01,\n    col_bounds=[-27.5, -25.5],\n    alpha=np.ones(4, dtype=\"float64\"),\n    colormap=\"RdBu_r\",\n)\nsc.save(\"v2.png\", sigma_clip=6.0)\n\n# This looks better.  Now let's try turning on opacity.\n\ntf.grey_opacity = True\nsc.save(\"v3.png\", sigma_clip=6.0)\n#\n## That seemed to pick out some interesting structures.  Now let's bump up the\n## opacity.\n#\ntf.clear()\ntf.add_layers(\n    4,\n    0.01,\n    col_bounds=[-27.5, -25.5],\n    alpha=10.0 * np.ones(4, dtype=\"float64\"),\n    colormap=\"RdBu_r\",\n)\ntf.add_layers(\n    4,\n    0.01,\n    col_bounds=[-27.5, -25.5],\n    alpha=10.0 * np.ones(4, dtype=\"float64\"),\n    colormap=\"RdBu_r\",\n)\nsc.save(\"v4.png\", sigma_clip=6.0)\n#\n## This looks pretty good, now lets go back to the full resolution AMRKDTree\n#\nrender_source.set_volume(kd)\nsc.save(\"v5.png\", sigma_clip=6.0)\n\n# This looks great!\n"
  },
  {
    "path": "doc/source/cookbook/annotate_timestamp_and_scale.py",
    "content": "import yt\n\nts = yt.load(\"enzo_tiny_cosmology/DD000?/DD000?\")\nfor ds in ts:\n    p = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"))\n    p.annotate_timestamp(corner=\"upper_left\", redshift=True, draw_inset_box=True)\n    p.annotate_scale(corner=\"upper_right\")\n    p.save()\n"
  },
  {
    "path": "doc/source/cookbook/annotations.py",
    "content": "import yt\n\nds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\np = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"))\np.annotate_sphere([0.54, 0.72], radius=(1, \"Mpc\"), coord_system=\"axis\", text=\"Halo #7\")\np.annotate_sphere(\n    [0.65, 0.38, 0.3],\n    radius=(1.5, \"Mpc\"),\n    coord_system=\"data\",\n    circle_args={\"color\": \"green\", \"linewidth\": 4, \"linestyle\": \"dashed\"},\n)\np.annotate_arrow([0.87, 0.59, 0.2], coord_system=\"data\", color=\"red\")\np.annotate_text([10, 20], \"Some halos\", coord_system=\"plot\")\np.annotate_marker([0.45, 0.1, 0.4], coord_system=\"data\", color=\"yellow\", s=500)\np.annotate_line([0.2, 0.4], [0.3, 0.9], coord_system=\"axis\")\np.annotate_timestamp(redshift=True)\np.annotate_scale()\np.save()\n"
  },
  {
    "path": "doc/source/cookbook/average_value.py",
    "content": "import yt\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")  # load data\n\nfield = (\"gas\", \"temperature\")  # The field to average\nweight = (\"gas\", \"mass\")  # The weight for the average\n\nad = ds.all_data()  # This is a region describing the entire box,\n# but note it doesn't read anything in yet!\n\n# We now use our 'quantities' call to get the average quantity\naverage_value = ad.quantities.weighted_average_quantity(field, weight)\n\nprint(\n    \"Average %s (weighted by %s) is %0.3e %s\"\n    % (field[1], weight[1], average_value, average_value.units)\n)\n"
  },
  {
    "path": "doc/source/cookbook/calculating_information.rst",
    "content": "Calculating Dataset Information\n-------------------------------\n\nThese recipes demonstrate methods of calculating quantities in a simulation,\neither for later visualization or for understanding properties of fluids and\nparticles in the simulation.\n\nAverage Field Value\n~~~~~~~~~~~~~~~~~~~\n\nThis recipe is a very simple method of calculating the global average of a\ngiven field, as weighted by another field.\nSee :ref:`derived-quantities` for more information.\n\n.. yt_cookbook:: average_value.py\n\nMass Enclosed in a Sphere\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe constructs a sphere and then sums the total mass in particles and\nfluids in the sphere.\nSee :ref:`available-objects` and :ref:`derived-quantities` for more information.\n\n.. yt_cookbook:: sum_mass_in_sphere.py\n\nGlobal Phase Plot\n~~~~~~~~~~~~~~~~~\n\nThis is a simple recipe to show how to open a dataset and then plot a couple\nglobal phase diagrams, save them, and quit.\nSee :ref:`how-to-make-2d-profiles` for more information.\n\n.. yt_cookbook:: global_phase_plots.py\n\n.. _cookbook-radial-velocity:\n\nRadial Velocity Profile\n~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to subtract off a bulk velocity on a sphere before\ncalculating the radial velocity within that sphere.\nSee :ref:`how-to-make-1d-profiles` for more information on creating profiles and\n:ref:`field_parameters` for an explanation of how the bulk velocity is provided\nto the radial velocity field function.\n\n.. yt_cookbook:: rad_velocity.py\n\nSimulation Analysis\n~~~~~~~~~~~~~~~~~~~\n\nThis uses :class:`~yt.data_objects.time_series.DatasetSeries` to\ncalculate the extrema of a series of outputs, whose names it guesses in\nadvance.  This will run in parallel and take advantage of multiple MPI tasks.\nSee :ref:`parallel-computation` and :ref:`time-series-analysis` for more\ninformation.\n\n.. yt_cookbook:: simulation_analysis.py\n\n\n.. _cookbook-time-series-analysis:\n\nTime Series Analysis\n~~~~~~~~~~~~~~~~~~~~\n\nThis recipe shows how to calculate a number of quantities on a set of parameter\nfiles.  Note that it is parallel aware, and that if you only wanted to run in\nserial the operation ``for pf in ts:`` would also have worked identically.\nSee :ref:`parallel-computation` and :ref:`time-series-analysis` for more\ninformation.\n\n.. yt_cookbook:: time_series.py\n\n.. _cookbook-simple-derived-fields:\n\nSimple Derived Fields\n~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to create a simple derived field,\n``thermal_energy_density``, and then generate a projection from it.\nSee :ref:`creating-derived-fields` and :ref:`projection-plots` for more\ninformation.\n\n.. yt_cookbook:: derived_field.py\n\n.. _cookbook-complicated-derived-fields:\n\nComplicated Derived Fields\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to use the\n:meth:`~yt.frontends.flash.data_structures.FLASHDataset.add_gradient_fields` method\nto generate gradient fields and use them in a more complex derived field.\n\n.. yt_cookbook:: hse_field.py\n\nUsing Particle Filters to Calculate Star Formation Rates\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to use a particle filter to calculate the star\nformation rate in a galaxy evolution simulation.\nSee :ref:`filtering-particles` for more information.\n\n.. yt_cookbook:: particle_filter_sfr.py\n\nMaking a Turbulent Kinetic Energy Power Spectrum\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe shows how to use ``yt`` to read data and put it on a uniform\ngrid to interface with the NumPy FFT routines and create a turbulent\nkinetic energy power spectrum.  (Note: the dataset used here is of low\nresolution, so the turbulence is not very well-developed.  The spike\nat high wavenumbers is due to non-periodicity in the z-direction).\n\n.. yt_cookbook:: power_spectrum_example.py\n\nDownsampling an AMR Dataset\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe shows how to use the ``max_level`` attribute of a yt data\nobject to only select data up to a maximum AMR level.\n\n.. yt_cookbook:: downsampling_amr.py\n"
  },
  {
    "path": "doc/source/cookbook/camera_movement.py",
    "content": "import numpy as np\n\nimport yt\n\nds = yt.load(\"MOOSE_sample_data/out.e-s010\")\nsc = yt.create_scene(ds)\ncam = sc.camera\n\n# save an image at the starting position\nframe = 0\nsc.save(\"camera_movement_%04i.png\" % frame)\nframe += 1\n\n# Zoom out by a factor of 2 over 5 frames\nfor _ in cam.iter_zoom(0.5, 5):\n    sc.save(\"camera_movement_%04i.png\" % frame)\n    frame += 1\n\n# Move to the position [-10.0, 10.0, -10.0] over 5 frames\npos = ds.arr([-10.0, 10.0, -10.0], \"code_length\")\nfor _ in cam.iter_move(pos, 5):\n    sc.save(\"camera_movement_%04i.png\" % frame)\n    frame += 1\n\n# Rotate by 180 degrees over 5 frames\nfor _ in cam.iter_rotate(np.pi, 5):\n    sc.save(\"camera_movement_%04i.png\" % frame)\n    frame += 1\n"
  },
  {
    "path": "doc/source/cookbook/changing_label_formats.py",
    "content": "import yt\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Set the format of the ionozation_label to be `plus_minus`\n# instead of the default `roman_numeral`\nds.set_field_label_format(\"ionization_label\", \"plus_minus\")\n\nslc = yt.SlicePlot(ds, \"x\", (\"gas\", \"H_p1_number_density\"))\nslc.save(\"plus_minus_ionization_format_sliceplot.png\")\n"
  },
  {
    "path": "doc/source/cookbook/colormaps.py",
    "content": "import yt\n\n# Load the dataset\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create a projection and save it with the default colormap ('cmyt.arbre')\np = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), width=(100, \"kpc\"))\np.save()\n\n# Change the colormap to 'cmyt.dusk' and save again.  We must specify\n# a different filename here or it will save it over the top of\n# our first projection.\np.set_cmap(field=(\"gas\", \"density\"), cmap=\"cmyt.dusk\")\np.save(\"proj_with_dusk_cmap.png\")\n\n# Change the colormap to 'hot' and save again.\np.set_cmap(field=(\"gas\", \"density\"), cmap=\"hot\")\np.save(\"proj_with_hot_cmap.png\")\n"
  },
  {
    "path": "doc/source/cookbook/complex_plots.rst",
    "content": "A Few Complex Plots\n-------------------\n\nThe built-in plotting functionality covers the very simple use cases that are\nmost common.  These scripts will demonstrate how to construct more complex\nplots or publication-quality plots.  In many cases these show how to make\nmulti-panel plots.\n\nMulti-Width Image\n~~~~~~~~~~~~~~~~~\n\nThis is a simple recipe to show how to open a dataset and then plot slices\nthrough it at varying widths.\nSee :ref:`slice-plots` for more information.\n\n.. yt_cookbook:: multi_width_image.py\n\n.. _image-resolution-primer:\n\nVarying the resolution of an image\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis illustrates the various parameters that control the resolution\nof an image, including the (deprecated) refinement level, the size of\nthe :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer`,\nand the number of pixels in the output image.\n\nIn brief, there are three parameters that control the final resolution,\nwith a fourth entering for particle data that is deposited onto a mesh\n(i.e. pre-4.0).  Those are:\n\n1. ``buff_size``, which can be altered with\n:meth:`~yt.visualization.plot_window.PlotWindow.set_buff_size`, which\nis inherited by\n:class:`~yt.visualization.plot_window.AxisAlignedSlicePlot`,\n:class:`~yt.visualization.plot_window.OffAxisSlicePlot`,\n:class:`~yt.visualization.plot_window.AxisAlignedProjectionPlot`, and\n:class:`~yt.visualization.plot_window.OffAxisProjectionPlot`.  This\ncontrols the number of resolution elements in the\n:class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer`,\nwhich can be thought of as the number of individually colored\nsquares (on a side) in a 2D image. ``buff_size`` can be set\nafter creating the image with\n:meth:`~yt.visualization.plot_window.PlotWindow.set_buff_size`,\nor during image creation with the ``buff_size`` argument to any\nof the four preceding classes.\n\n2. ``figure_size``, which can be altered with either\n:meth:`~yt.visualization.plot_container.PlotContainer.set_figure_size`,\nor can be set during image creation with the ``window_size`` argument.\nThis sets the size of the final image (including the visualization and,\nif applicable, the axes and colorbar as well) in inches.\n\n3. ``dpi``, i.e. the dots-per-inch in your final file, which can also\nbe thought of as the actual resolution of your image.  This can\nonly be set on save via the ``mpl_kwargs`` parameter to\n:meth:`~yt.visualization.plot_container.PlotContainer.save`.  The\n``dpi`` and ``figure_size`` together set the true resolution of your\nimage (final image will be ``dpi`` :math:`*` ``figure_size`` pixels on a\nside), so if these are set too low, then your ``buff_size`` will not\nmatter.  On the other hand, increasing these without increasing\n``buff_size`` accordingly will simply blow up your resolution\nelements to fill several real pixels.\n\n4. (only for meshed particle data) ``n_ref``, the maximum number of\nparticles in a cell in the oct-tree allowed before it is refined\n(removed in yt-4.0 as particle data is no longer deposited onto\nan oct-tree).  For particle data, ``n_ref`` effectively sets the\nunderlying resolution of your simulation.  Regardless, for either\ngrid data or deposited particle data, your image will never be\nhigher resolution than your simulation data.  In other words,\nif you are visualizing a region 50 kpc across that includes\ndata that reaches a resolution of 100 pc, then there's no reason\nto set a ``buff_size`` (or a ``dpi`` :math:`*` ``figure_size``) above\n50 kpc/ 100 pc = 500.\n\nThe below script demonstrates how each of these can be varied.\n\n.. yt_cookbook:: image_resolution.py\n\n\nMultipanel with Axes Labels\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis illustrates how to use a SlicePlot to control a multipanel plot.  This\nplot uses axes labels to illustrate the length scales in the plot.\nSee :ref:`slice-plots` and the\n`Matplotlib AxesGrid Object <https://matplotlib.org/mpl_toolkits/axes_grid/api/axes_grid_api.html>`_\nfor more information.\n\n.. yt_cookbook:: multiplot_2x2.py\n\nThe above example gives you full control over the plots, but for most\npurposes, the ``export_to_mpl_figure`` method is a simpler option,\nallowing us to make a similar plot as:\n\n.. yt_cookbook:: multiplot_export_to_mpl.py\n\nMultipanel with PhasePlot\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis illustrates how to use PhasePlot in a multipanel plot.\nSee :ref:`how-to-make-2d-profiles` and the\n`Matplotlib AxesGrid Object <https://matplotlib.org/mpl_toolkits/axes_grid/api/axes_grid_api.html>`_\nfor more information.\n\n.. yt_cookbook:: multiplot_phaseplot.py\n\nTime Series Multipanel\n~~~~~~~~~~~~~~~~~~~~~~\n\nThis illustrates how to create a multipanel plot of a time series dataset.\nSee :ref:`projection-plots`, :ref:`time-series-analysis`, and the\n`Matplotlib AxesGrid Object <https://matplotlib.org/mpl_toolkits/axes_grid/api/axes_grid_api.html>`_\nfor more information.\n\n.. yt_cookbook:: multiplot_2x2_time_series.py\n\nMultiple Slice Multipanel\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis illustrates how to create a multipanel plot of slices along the coordinate\naxes.  To focus on what's happening in the x-y plane, we make an additional\nTemperature slice for the bottom-right subpanel.\nSee :ref:`slice-plots` and the\n`Matplotlib AxesGrid Object <https://matplotlib.org/mpl_toolkits/axes_grid/api/axes_grid_api.html>`_\nfor more information.\n\n.. yt_cookbook:: multiplot_2x2_coordaxes_slice.py\n\nMulti-Plot Slice and Projections\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis shows how to combine multiple slices and projections into a single image,\nwith detailed control over colorbars, titles and color limits.\nSee :ref:`slice-plots` and :ref:`projection-plots` for more information.\n\n.. yt_cookbook:: multi_plot_slice_and_proj.py\n\n.. _advanced-multi-panel:\n\nAdvanced Multi-Plot Multi-Panel\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis produces a series of slices of multiple fields with different color maps\nand zlimits, and makes use of the FixedResolutionBuffer. While this is more\ncomplex than the equivalent plot collection-based solution, it allows for a\n*lot* more flexibility. Every part of the script uses matplotlib commands,\nallowing its full power to be exercised.\nSee :ref:`slice-plots` and :ref:`projection-plots` for more information.\n\n.. yt_cookbook:: multi_plot_3x2_FRB.py\n\nTime Series Movie\n~~~~~~~~~~~~~~~~~\n\nThis shows how to use matplotlib's animation framework with yt plots.\n\n.. yt_cookbook:: matplotlib-animation.py\n\n.. _cookbook-offaxis_projection:\n\nOff-Axis Projection (an alternate method)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to take an image-plane line integral along an\narbitrary axis in a simulation.  This uses alternate machinery than the\nstandard :ref:`PlotWindow interface <off-axis-projections>` to create an\noff-axis projection as demonstrated in this\n:ref:`recipe <cookbook-simple-off-axis-projection>`.\n\n.. yt_cookbook:: offaxis_projection.py\n\nOff-Axis Projection with a Colorbar (an alternate method)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe shows how to generate a colorbar with a projection of a dataset\nfrom an arbitrary projection angle (so you are not confined to the x, y, and z\naxes).\n\nThis uses alternate machinery than the standard\n:ref:`PlotWindow interface <off-axis-projections>` to create an off-axis\nprojection as demonstrated in this\n:ref:`recipe <cookbook-simple-off-axis-projection>`.\n\n.. yt_cookbook:: offaxis_projection_colorbar.py\n\n.. _thin-slice-projections:\n\nThin-Slice Projections\n~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe is an example of how to project through only a given data object,\nin this case a thin region, and then display the result.\nSee :ref:`projection-plots` and :ref:`available-objects` for more information.\n\n.. yt_cookbook:: thin_slice_projection.py\n\nPlotting Particles Over Fluids\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to overplot particles on top of a fluid image.\nSee :ref:`annotate-particles` for more information.\n\n.. yt_cookbook:: overplot_particles.py\n\nPlotting Grid Edges Over Fluids\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to overplot grid boxes on top of a fluid image.\nEach level is represented with a different color from white (low refinement) to\nblack (high refinement).  One can change the colormap used for the grids colors\nby using the cmap keyword (or set it to None to get all grid edges as black).\nSee :ref:`annotate-grids` for more information.\n\n.. yt_cookbook:: overplot_grids.py\n\nOverplotting Velocity Vectors\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to plot velocity vectors on top of a slice.\nSee :ref:`annotate-velocity` for more information.\n\n.. yt_cookbook:: velocity_vectors_on_slice.py\n\nOverplotting Contours\n~~~~~~~~~~~~~~~~~~~~~\n\nThis is a simple recipe to show how to open a dataset, plot a slice through it,\nand add contours of another quantity on top.\nSee :ref:`annotate-contours` for more information.\n\n.. yt_cookbook:: contours_on_slice.py\n\nSimple Contours in a Slice\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nSometimes it is useful to plot just a few contours of a quantity in a\ndataset.  This shows how one does this by first making a slice, adding\ncontours, and then hiding the colormap plot of the slice to leave the\nplot containing only the contours that one has added.\nSee :ref:`annotate-contours` for more information.\n\n.. yt_cookbook:: simple_contour_in_slice.py\n\nStyling Radial Profile Plots\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates a method of calculating radial profiles for several\nquantities, styling them and saving out the resultant plot.\nSee :ref:`how-to-make-1d-profiles` for more information.\n\n.. yt_cookbook:: radial_profile_styles.py\n\nCustomized Profile Plot\n~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to create a fully customized 1D profile object\nusing the :func:`~yt.data_objects.profiles.create_profile` function and then\ncreate a :class:`~yt.visualization.profile_plotter.ProfilePlot` using the\ncustomized profile.  This illustrates how a ``ProfilePlot`` created this way\ninherits the properties of the profile it is constructed from.\nSee :ref:`how-to-make-1d-profiles` for more information.\n\n.. yt_cookbook:: customized_profile_plot.py\n\nCustomized Phase Plot\n~~~~~~~~~~~~~~~~~~~~~\n\nSimilar to the recipe above, this demonstrates how to create a fully customized\n2D profile object using the :func:`~yt.data_objects.profiles.create_profile`\nfunction and then create a :class:`~yt.visualization.profile_plotter.PhasePlot`\nusing the customized profile object.  This illustrates how a ``PhasePlot``\ncreated this way inherits the properties of the profile object from which it\nis constructed. See :ref:`how-to-make-2d-profiles` for more information.\n\n.. yt_cookbook:: customized_phase_plot.py\n\n.. _cookbook-camera_movement:\n\nMoving a Volume Rendering Camera\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn this recipe, we move a camera through a domain and take multiple volume\nrendering snapshots. This recipe uses an unstructured mesh dataset (see\n:ref:`unstructured_mesh_rendering`), which makes it easier to visualize what\nthe Camera is doing, but you can manipulate the Camera for other dataset types\nin exactly the same manner.\n\nSee :ref:`camera_movement` for more information.\n\n.. yt_cookbook:: camera_movement.py\n\nVolume Rendering with Custom Camera\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn this recipe we modify the :ref:`cookbook-simple_volume_rendering` recipe to\nuse customized camera properties. See :ref:`volume_rendering` for more\ninformation.\n\n.. yt_cookbook:: custom_camera_volume_rendering.py\n\n.. _cookbook-custom-transfer-function:\n\nVolume Rendering with a Custom Transfer Function\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn this recipe we modify the :ref:`cookbook-simple_volume_rendering` recipe to\nuse customized camera properties. See :ref:`volume_rendering` for more\ninformation.\n\n.. yt_cookbook:: custom_transfer_function_volume_rendering.py\n\n.. _cookbook-sigma_clip:\n\nVolume Rendering with Sigma Clipping\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn this recipe we output several images with different values of sigma_clip\nset in order to change the contrast of the resulting image.  See\n:ref:`sigma_clip` for more information.\n\n.. yt_cookbook:: sigma_clip.py\n\nZooming into an Image\n~~~~~~~~~~~~~~~~~~~~~\n\nThis is a recipe that takes a slice through the most dense point, then creates\na bunch of frames as it zooms in.  It's important to note that this particular\nrecipe is provided to show how to be more flexible and add annotations and the\nlike -- the base system, of a zoomin, is provided by the \"yt zoomin\" command on\nthe command line.\nSee :ref:`slice-plots` and :ref:`callbacks` for more information.\n\n.. yt_cookbook:: zoomin_frames.py\n\n.. _cookbook-various_lens:\n\nVarious Lens Types for Volume Rendering\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis example illustrates the usage and feature of different lenses for volume rendering.\n\n.. yt_cookbook:: various_lens.py\n\n.. _cookbook-opaque_rendering:\n\nOpaque Volume Rendering\n~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to make semi-opaque volume renderings, but also\nhow to step through and try different things to identify the type of volume\nrendering you want.\nSee :ref:`opaque_rendering` for more information.\n\n.. yt_cookbook:: opaque_rendering.py\n\nVolume Rendering Multiple Fields\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can render multiple fields by adding new ``VolumeSource`` objects to the\nscene for each field you want to render.\n\n.. yt_cookbook:: render_two_fields.py\n\n.. _cookbook-amrkdtree_downsampling:\n\nDownsampling Data for Volume Rendering\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to downsample data in a simulation to speed up\nvolume rendering.\nSee :ref:`volume_rendering` for more information.\n\n.. yt_cookbook:: amrkdtree_downsampling.py\n\n.. _cookbook-volume_rendering_annotations:\n\nVolume Rendering with Bounding Box and Overlaid Grids\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to overplot a bounding box on a volume rendering\nas well as overplotting grids representing the level of refinement achieved\nin different regions of the code.\nSee :ref:`volume_rendering_annotations` for more information.\n\n.. yt_cookbook:: rendering_with_box_and_grids.py\n\nVolume Rendering with Annotation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to write the simulation time, show an\naxis triad indicating the direction of the coordinate system, and show\nthe transfer function on a volume rendering.  Please note that this\nrecipe relies on the old volume rendering interface.  While one can\ncontinue to use this interface, it may be incompatible with some of the\nnew developments and the infrastructure described in :ref:`volume_rendering`.\n\n.. yt_cookbook:: vol-annotated.py\n\n.. _cookbook-render_two_fields_tf:\n\nVolume Rendering Multiple Fields And Annotation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe shows how to display the transfer functions when rendering multiple\nfields in a volume render.\n\n.. yt_cookbook:: render_two_fields_tf.py\n\n.. _cookbook-vol-points:\n\nVolume Rendering with Points\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to make a volume rendering composited with point\nsources. This could represent star or dark matter particles, for example.\n\n.. yt_cookbook:: vol-points.py\n\n.. _cookbook-vol-lines:\n\nVolume Rendering with Lines\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to make a volume rendering composited with line\nsources.\n\n.. yt_cookbook:: vol-lines.py\n\nPlotting Streamlines\n~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to display streamlines in a simulation.  (Note:\nstreamlines can also be queried for values!)\nSee :ref:`streamlines` for more information.\n\n.. yt_cookbook:: streamlines.py\n\nPlotting Isocontours\n~~~~~~~~~~~~~~~~~~~~\n\nThis recipe demonstrates how to extract an isocontour and then plot it in\nmatplotlib, coloring the surface by a second quantity.\nSee :ref:`surfaces` for more information.\n\n.. yt_cookbook:: surface_plot.py\n\nPlotting Isocontours and Streamlines\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis recipe plots both isocontours and streamlines simultaneously.  Note that\nthis will not include any blending, so streamlines that are occluded by the\nsurface will still be visible.\nSee :ref:`streamlines` and :ref:`surfaces` for more information.\n\n.. yt_cookbook:: streamlines_isocontour.py\n"
  },
  {
    "path": "doc/source/cookbook/constructing_data_objects.rst",
    "content": "Constructing Data Objects\n-------------------------\n\nThese recipes demonstrate a few uncommon methods of constructing data objects\nfrom a simulation.\n\nCreating Particle Filters\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nCreate particle filters based on the age of star particles in an isolated\ndisk galaxy simulation.  Determine the total mass of each stellar age bin\nin the simulation.  Generate projections for each of the stellar age bins.\n\n.. yt_cookbook:: particle_filter.py\n\n.. _cookbook-find_clumps:\n\nIdentifying Clumps\n~~~~~~~~~~~~~~~~~~\n\nThis is a recipe to show how to find topologically connected sets of cells\ninside a dataset.  It returns these clumps and they can be inspected or\nvisualized as would any other data object.  More detail on this method can be\nfound in `Smith et al. 2009\n<https://ui.adsabs.harvard.edu/abs/2009ApJ...691..441S>`_.\n\n.. yt_cookbook:: find_clumps.py\n\n.. _extract_frb:\n\nExtracting Fixed Resolution Data\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis is a recipe to show how to open a dataset and extract it to a file at a\nfixed resolution with no interpolation or smoothing.  Additionally, this recipe\nshows how to insert a dataset into an external HDF5 file using h5py.\n\n.. yt_cookbook:: extract_fixed_resolution_data.py\n"
  },
  {
    "path": "doc/source/cookbook/contours_on_slice.py",
    "content": "import yt\n\n# first add density contours on a density slice\nds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n\n# add density contours on the density slice.\np = yt.SlicePlot(ds, \"x\", (\"gas\", \"density\"))\np.annotate_contour((\"gas\", \"density\"))\np.save()\n\n# then add temperature contours on the same density slice\np = yt.SlicePlot(ds, \"x\", (\"gas\", \"density\"))\np.annotate_contour((\"gas\", \"temperature\"))\np.save(str(ds) + \"_T_contour\")\n"
  },
  {
    "path": "doc/source/cookbook/count.sh",
    "content": "for fn in *.py\ndo\n    COUNT=`cat *.rst | grep --count ${fn}`\n    if [ $COUNT -lt 1 ]\n    then\n        echo ${fn}\n    fi\ndone\n"
  },
  {
    "path": "doc/source/cookbook/custom_camera_volume_rendering.py",
    "content": "import yt\n\n# Load the dataset\nds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n# Create a volume rendering\nsc = yt.create_scene(ds, field=(\"gas\", \"density\"))\n\n# Now increase the resolution\nsc.camera.resolution = (1024, 1024)\n\n# Set the camera focus to a position that is offset from the center of\n# the domain\nsc.camera.focus = ds.arr([0.3, 0.3, 0.3], \"unitary\")\n\n# Move the camera position to the other side of the dataset\nsc.camera.position = ds.arr([0, 0, 0], \"unitary\")\n\n# save to disk with a custom filename and apply sigma clipping to eliminate\n# very bright pixels, producing an image with better contrast.\nsc.render()\nsc.save(\"custom.png\", sigma_clip=4)\n"
  },
  {
    "path": "doc/source/cookbook/custom_colorbar_tickmarks.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Custom Colorbar Tickmarks\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load(\\\"IsolatedGalaxy/galaxy0030/galaxy0030\\\")\\n\",\n    \"slc = yt.SlicePlot(ds, \\\"x\\\", (\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"slc\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`PlotWindow` plots are containers for plots, keyed to field names.  Below, we get a copy of the plot for the `Density` field.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"plot = slc.plots[\\\"gas\\\", \\\"density\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The plot has a few attributes that point to underlying `matplotlib` plot primitives.  For example, the `colorbar` object corresponds to the `cb` attribute of the plot.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"colorbar = plot.cb\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Next, we call `render()` to ensure the plot is properly rendered. Without this, the custom tickmarks we are adding will be ignored.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc.render()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To set custom tickmarks, simply call the `matplotlib` [`set_ticks`](https://matplotlib.org/stable/api/colorbar_api.html#matplotlib.colorbar.ColorbarBase.set_ticks) and [`set_ticklabels`](https://matplotlib.org/stable/api/colorbar_api.html#matplotlib.colorbar.ColorbarBase.set_ticklabels) functions.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"colorbar.set_ticks([1e-28])\\n\",\n    \"colorbar.set_ticklabels([\\\"$10^{-28}$\\\"])\\n\",\n    \"slc\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.5.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/cookbook/custom_transfer_function_volume_rendering.py",
    "content": "import numpy as np\n\nimport yt\n\n# Load the dataset\nds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n# Create a volume rendering\nsc = yt.create_scene(ds, field=(\"gas\", \"density\"))\n\n# Modify the transfer function\n\n# First get the render source, in this case the entire domain,\n# with field ('gas','density')\nrender_source = sc.get_source()\n\n# Clear the transfer function\nrender_source.transfer_function.clear()\n\n# Map a range of density values (in log space) to the Reds_r colormap\nrender_source.transfer_function.map_to_colormap(\n    np.log10(ds.quan(5.0e-31, \"g/cm**3\")),\n    np.log10(ds.quan(1.0e-29, \"g/cm**3\")),\n    scale=30.0,\n    colormap=\"RdBu_r\",\n)\n\nsc.save(\"new_tf.png\")\n"
  },
  {
    "path": "doc/source/cookbook/customized_phase_plot.py",
    "content": "import yt\nimport yt.units as u\n\nds = yt.load(\"HiresIsolatedGalaxy/DD0044/DD0044\")\n\ncenter = [0.53, 0.53, 0.53]\nnormal = [0, 0, 1]\nradius = 40 * u.kpc\nheight = 2 * u.kpc\n\ndisk = ds.disk(center, [0, 0, 1], radius, height)\n\nprofile = yt.create_profile(\n    data_source=disk,\n    bin_fields=[(\"index\", \"radius\"), (\"gas\", \"velocity_cylindrical_theta\")],\n    fields=[(\"gas\", \"mass\")],\n    n_bins=256,\n    units=dict(radius=\"kpc\", velocity_cylindrical_theta=\"km/s\", mass=\"Msun\"),\n    logs=dict(radius=False, velocity_cylindrical_theta=False),\n    weight_field=None,\n    extrema=dict(radius=(0, 40), velocity_cylindrical_theta=(-250, 250)),\n)\n\nplot = yt.PhasePlot.from_profile(profile)\nplot.set_cmap((\"gas\", \"mass\"), \"YlOrRd\")\n\nplot.save()\n"
  },
  {
    "path": "doc/source/cookbook/customized_profile_plot.py",
    "content": "import yt\nimport yt.units as u\n\nds = yt.load(\"HiresIsolatedGalaxy/DD0044/DD0044\")\n\ncenter = [0.53, 0.53, 0.53]\nnormal = [0, 0, 1]\nradius = 40 * u.kpc\nheight = 5 * u.kpc\n\ndisk = ds.disk(center, [0, 0, 1], radius, height)\n\nprofile = yt.create_profile(\n    data_source=disk,\n    bin_fields=[(\"index\", \"radius\")],\n    fields=[(\"gas\", \"velocity_cylindrical_theta\")],\n    n_bins=256,\n    units=dict(radius=\"kpc\", velocity_cylindrical_theta=\"km/s\"),\n    logs=dict(radius=False),\n    weight_field=(\"gas\", \"mass\"),\n    extrema=dict(radius=(0, 40)),\n)\n\nplot = yt.ProfilePlot.from_profiles(profile)\n\nplot.set_log((\"gas\", \"velocity_cylindrical_theta\"), False)\nplot.set_ylim((\"gas\", \"velocity_cylindrical_theta\"), 60, 160)\n\nplot.save()\n"
  },
  {
    "path": "doc/source/cookbook/derived_field.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# You can create a derived field by manipulating any existing derived fields\n# in any way you choose.  In this case, let's just make a simple one:\n# thermal_energy_density = 3/2 nkT\n\n\n# First create a function which yields your new derived field\ndef thermal_energy_dens(data):\n    return (3 / 2) * data[\"gas\", \"number_density\"] * data[\"gas\", \"kT\"]\n\n\n# Then add it to your dataset and define the units\nds.add_field(\n    (\"gas\", \"thermal_energy_density\"),\n    units=\"erg/cm**3\",\n    function=thermal_energy_dens,\n    sampling_type=\"cell\",\n)\n\n# It will now show up in your derived_field_list\nfor i in sorted(ds.derived_field_list):\n    print(i)\n\n# Let's use it to make a projection\nad = ds.all_data()\nyt.ProjectionPlot(\n    ds,\n    \"x\",\n    (\"gas\", \"thermal_energy_density\"),\n    weight_field=(\"gas\", \"density\"),\n    width=(200, \"kpc\"),\n).save()\n"
  },
  {
    "path": "doc/source/cookbook/downsampling_amr.py",
    "content": "import yt\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# The maximum refinement level of this dataset is 8\nprint(ds.max_level)\n\n# If we ask for *all* of the AMR data, we get back field\n# values sampled at about 3.6 million AMR zones\nad = ds.all_data()\nprint(ad[\"gas\", \"density\"].shape)\n\n# Let's only sample data up to AMR level 2\nad.max_level = 2\n\n# Now we only sample from about 200,000 zones\nprint(ad[\"gas\", \"density\"].shape)\n\n# Note that this includes data at level 2 that would\n# normally be masked out. There aren't any \"holes\" in\n# the downsampled AMR mesh, the volume still sums to\n# the volume of the domain:\nprint(ad[\"gas\", \"volume\"].sum())\nprint(ds.domain_width.prod())\n\n# Now let's make a downsampled plot\nplot = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), data_source=ad)\nplot.save(\"downsampled.png\")\n"
  },
  {
    "path": "doc/source/cookbook/extract_fixed_resolution_data.py",
    "content": "# For this example we will use h5py to write to our output file.\nimport h5py\n\nimport yt\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\nlevel = 2\ndims = ds.domain_dimensions * ds.refine_by**level\n\n# We construct an object that describes the data region and structure we want\n# In this case, we want all data up to the maximum \"level\" of refinement\n# across the entire simulation volume.  Higher levels than this will not\n# contribute to our covering grid.\ncube = ds.covering_grid(\n    level,\n    left_edge=[0.0, 0.0, 0.0],\n    dims=dims,\n    # And any fields to preload (this is optional!)\n    fields=[(\"gas\", \"density\")],\n)\n\n# Now we open our output file using h5py\n# Note that we open with 'w' (write), which will overwrite existing files!\nf = h5py.File(\"my_data.h5\", mode=\"w\")\n\n# We create a dataset at the root, calling it \"density\"\nf.create_dataset(\"/density\", data=cube[\"gas\", \"density\"])\n\n# We close our file\nf.close()\n\n# If we want to then access this datacube in the h5 file, we can now...\nf = h5py.File(\"my_data.h5\", mode=\"r\")\nprint(f[\"density\"][()])\n"
  },
  {
    "path": "doc/source/cookbook/find_clumps.py",
    "content": "import numpy as np\n\nimport yt\nfrom yt.data_objects.level_sets.api import Clump, find_clumps\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\ndata_source = ds.disk([0.5, 0.5, 0.5], [0.0, 0.0, 1.0], (8, \"kpc\"), (1, \"kpc\"))\n\n# the field to be used for contouring\nfield = (\"gas\", \"density\")\n\n# This is the multiplicative interval between contours.\nstep = 2.0\n\n# Now we set some sane min/max values between which we want to find contours.\n# This is how we tell the clump finder what to look for -- it won't look for\n# contours connected below or above these threshold values.\nc_min = 10 ** np.floor(np.log10(data_source[field]).min())\nc_max = 10 ** np.floor(np.log10(data_source[field]).max() + 1)\n\n# Now find get our 'base' clump -- this one just covers the whole domain.\nmaster_clump = Clump(data_source, field)\n\n# Add a \"validator\" to weed out clumps with less than 20 cells.\n# As many validators can be added as you want.\nmaster_clump.add_validator(\"min_cells\", 20)\n\n# Calculate center of mass for all clumps.\nmaster_clump.add_info_item(\"center_of_mass\")\n\n# Begin clump finding.\nfind_clumps(master_clump, c_min, c_max, step)\n\n# Save the clump tree as a reloadable dataset\nfn = master_clump.save_as_dataset(fields=[(\"gas\", \"density\"), (\"all\", \"particle_mass\")])\n\n# We can traverse the clump hierarchy to get a list of all of the 'leaf' clumps\nleaf_clumps = master_clump.leaves\n\n# Get total cell and particle masses for each leaf clump\nleaf_masses = [leaf.quantities.total_mass() for leaf in leaf_clumps]\n\n# If you'd like to visualize these clumps, a list of clumps can be supplied to\n# the \"clumps\" callback on a plot.  First, we create a projection plot:\nprj = yt.ProjectionPlot(ds, 2, field, center=\"c\", width=(20, \"kpc\"))\n\n# Next we annotate the plot with contours on the borders of the clumps\nprj.annotate_clumps(leaf_clumps)\n\n# Save the plot to disk.\nprj.save(\"clumps\")\n\n# Reload the clump dataset.\ncds = yt.load(fn)\n\n# Clump annotation can also be done with the reloaded clump dataset.\n\n# Remove the original clump annotation\nprj.clear_annotations()\n\n# Get the leaves and add the callback.\nleaf_clumps_reloaded = cds.leaves\nprj.annotate_clumps(leaf_clumps_reloaded)\nprj.save(\"clumps_reloaded\")\n\n# Query fields for clumps in the tree.\nprint(cds.tree[\"clump\", \"center_of_mass\"])\nprint(cds.tree.children[0][\"grid\", \"density\"])\nprint(cds.tree.children[1][\"all\", \"particle_mass\"])\n\n# Get all of the leaf clumps.\nprint(cds.leaves)\nprint(cds.leaves[0][\"clump\", \"cell_mass\"])\n"
  },
  {
    "path": "doc/source/cookbook/fits_radio_cubes.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Analyzing FITS Radio Cubes\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This notebook demonstrates some of the capabilities of yt on some FITS \\\"position-position-spectrum\\\" cubes of radio data.\\n\",\n    \"\\n\",\n    \"Note that it depends on some external dependencies, including `astropy` and `regions`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## M33 VLA Image\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The dataset `\\\"m33_hi.fits\\\"` has `NaN`s in it, so we'll mask them out by setting `nan_mask` = 0:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load(\\\"radio_fits/m33_hi.fits\\\", nan_mask=0.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"First, we'll take a slice of the data along the z-axis, which is the velocity axis of the FITS cube:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(ds, \\\"z\\\", (\\\"fits\\\", \\\"intensity\\\"), origin=\\\"native\\\")\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The x and y axes are in units of the image pixel. When making plots of FITS data, to see the image coordinates as they are in the file, it is helpful to set the keyword `origin = \\\"native\\\"`. If you want to see the celestial coordinates along the axes, you can import the `PlotWindowWCS` class and feed it the `SlicePlot`. For this to work, a version of AstroPy >= 1.3 needs to be installed.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from yt.frontends.fits.misc import PlotWindowWCS\\n\",\n    \"\\n\",\n    \"PlotWindowWCS(slc)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Generally, it is best to get the plot in the shape you want it before feeding it to `PlotWindowWCS`. Once it looks the way you want, you can save it just like a normal `PlotWindow` plot:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc.save()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also take slices of this dataset at a few different values along the \\\"z\\\" axis (corresponding to the velocity), so let's try a few. To pick specific velocity values for slices, we will need to use the dataset's `spec2pixel` method to determine which pixels to slice on:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import yt.units as u\\n\",\n    \"\\n\",\n    \"new_center = ds.domain_center\\n\",\n    \"new_center[2] = ds.spec2pixel(-250000.0 * u.m / u.s)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we can use this new center to create a new slice:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(ds, \\\"z\\\", (\\\"fits\\\", \\\"intensity\\\"), center=new_center, origin=\\\"native\\\")\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can do this a few more times for different values of the velocity:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"new_center[2] = ds.spec2pixel(-100000.0 * u.m / u.s)\\n\",\n    \"slc = yt.SlicePlot(ds, \\\"z\\\", (\\\"fits\\\", \\\"intensity\\\"), center=new_center, origin=\\\"native\\\")\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"new_center[2] = ds.spec2pixel(-150000.0 * u.m / u.s)\\n\",\n    \"slc = yt.SlicePlot(ds, \\\"z\\\", (\\\"fits\\\", \\\"intensity\\\"), center=new_center, origin=\\\"native\\\")\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"These slices demonstrate the intensity of the radio emission at different line-of-sight velocities. \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also make a projection of all the emission along the line of sight:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ProjectionPlot(ds, \\\"z\\\", (\\\"fits\\\", \\\"intensity\\\"), origin=\\\"native\\\")\\n\",\n    \"prj.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also look at the slices perpendicular to the other axes, which will show us the structure along the velocity axis:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(ds, \\\"x\\\", (\\\"fits\\\", \\\"intensity\\\"), origin=\\\"native\\\", window_size=(8, 8))\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(ds, \\\"y\\\", (\\\"fits\\\", \\\"intensity\\\"), origin=\\\"native\\\", window_size=(8, 8))\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In these cases, we needed to explicitly declare a square `window_size` to get a figure that looks good. \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## $^{13}$CO GRS Data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This next example uses one of the cubes from the [Boston University Galactic Ring Survey](http://www.bu.edu/galacticring/new_index.htm). \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load(\\\"radio_fits/grs-50-cube.fits\\\", nan_mask=0.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can use the `quantities` methods to determine derived quantities of the dataset. For example, we could find the maximum and minimum temperature:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"dd = ds.all_data()  # A region containing the entire dataset\\n\",\n    \"extrema = dd.quantities.extrema((\\\"fits\\\", \\\"temperature\\\"))\\n\",\n    \"print(extrema)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can compute the average temperature along the \\\"velocity\\\" axis for all positions by making a `ProjectionPlot`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ProjectionPlot(\\n\",\n    \"    ds, \\\"z\\\", (\\\"fits\\\", \\\"temperature\\\"), origin=\\\"native\\\", weight_field=(\\\"index\\\", \\\"ones\\\")\\n\",\n    \")  # \\\"ones\\\" weights each cell by 1\\n\",\n    \"prj.set_zlim((\\\"fits\\\", \\\"temperature\\\"), zmin=(1e-3, \\\"K\\\"))\\n\",\n    \"prj.set_log((\\\"fits\\\", \\\"temperature\\\"), True)\\n\",\n    \"prj.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also make a histogram of the temperature field of this region:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"pplot = yt.ProfilePlot(\\n\",\n    \"    dd, (\\\"fits\\\", \\\"temperature\\\"), [(\\\"index\\\", \\\"ones\\\")], weight_field=None, n_bins=128\\n\",\n    \")\\n\",\n    \"pplot.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can see from this histogram and our calculation of the dataset's extrema that there is a lot of noise. Suppose we wanted to make a projection, but instead make it only of the cells which had a positive temperature value. We can do this by doing a \\\"field cut\\\" on the data:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"fc = dd.cut_region(['obj[\\\"fits\\\", \\\"temperature\\\"] > 0'])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now let's check the extents of this region:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(fc.quantities.extrema((\\\"fits\\\", \\\"temperature\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Looks like we were successful in filtering out the negative temperatures. To compute the average temperature of this new region:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"fc.quantities.weighted_average_quantity((\\\"fits\\\", \\\"temperature\\\"), (\\\"index\\\", \\\"ones\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, let's make a projection of the dataset, using the field cut `fc` as a `data_source`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ProjectionPlot(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    [(\\\"fits\\\", \\\"temperature\\\")],\\n\",\n    \"    data_source=fc,\\n\",\n    \"    origin=\\\"native\\\",\\n\",\n    \"    weight_field=(\\\"index\\\", \\\"ones\\\"),\\n\",\n    \")  # \\\"ones\\\" weights each cell by 1\\n\",\n    \"prj.set_log((\\\"fits\\\", \\\"temperature\\\"), True)\\n\",\n    \"prj.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, we can also take an existing [ds9](http://ds9.si.edu/site/Home.html) region and use it to create a \\\"cut region\\\" as well, using `ds9_region` (the [regions](https://astropy-regions.readthedocs.io/) package needs to be installed for this):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from yt.frontends.fits.misc import ds9_region\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For this example we'll create a ds9 region from scratch and load it up:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"region = 'galactic;box(+49:26:35.150,-0:30:04.410,1926.1927\\\",1483.3701\\\",0.0)'\\n\",\n    \"box_reg = ds9_region(ds, region)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This region may now be used to compute derived quantities:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(box_reg.quantities.extrema((\\\"fits\\\", \\\"temperature\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Or in projections:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ProjectionPlot(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    (\\\"fits\\\", \\\"temperature\\\"),\\n\",\n    \"    origin=\\\"native\\\",\\n\",\n    \"    data_source=box_reg,\\n\",\n    \"    weight_field=(\\\"index\\\", \\\"ones\\\"),\\n\",\n    \")  # \\\"ones\\\" weights each cell by 1\\n\",\n    \"prj.set_zlim((\\\"fits\\\", \\\"temperature\\\"), 1.0e-2, 1.5)\\n\",\n    \"prj.set_log((\\\"fits\\\", \\\"temperature\\\"), True)\\n\",\n    \"prj.show()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.5.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/cookbook/fits_xray_images.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# X-ray FITS Images\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This notebook shows how to use yt to make plots and examine FITS X-ray images and events files. \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Sloshing, Shocks, and Bubbles in Abell 2052\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This example uses data provided by [Scott Randall](http://hea-www.cfa.harvard.edu/~srandall/), presented originally in [Blanton, E.L., Randall, S.W., Clarke, T.E., et al. 2011, ApJ, 737, 99](https://ui.adsabs.harvard.edu/abs/2011ApJ...737...99B). They consist of two files, a \\\"flux map\\\" in counts/s/pixel between 0.3 and 2 keV, and a spectroscopic temperature map in keV. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load(\\n\",\n    \"    \\\"xray_fits/A2052_merged_0.3-2_match-core_tmap_bgecorr.fits\\\",\\n\",\n    \"    auxiliary_files=[\\\"xray_fits/A2052_core_tmap_b1_m2000_.fits\\\"],\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Since the flux and projected temperature images are in two different files, we had to use one of them (in this case the \\\"flux\\\" file) as a master file, and pass in the \\\"temperature\\\" file with the `auxiliary_files` keyword to `load`. \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Next, let's derive some new fields for the number of counts, the \\\"pseudo-pressure\\\", and the \\\"pseudo-entropy\\\":\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def _counts(data):\\n\",\n    \"    exposure_time = data.get_field_parameter(\\\"exposure_time\\\")\\n\",\n    \"    return data[\\\"fits\\\", \\\"flux\\\"] * data[\\\"fits\\\", \\\"pixel\\\"] * exposure_time\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"ds.add_field(\\n\",\n    \"    (\\\"gas\\\", \\\"counts\\\"),\\n\",\n    \"    function=_counts,\\n\",\n    \"    sampling_type=\\\"cell\\\",\\n\",\n    \"    units=\\\"counts\\\",\\n\",\n    \"    take_log=False,\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def _pp(data):\\n\",\n    \"    return np.sqrt(data[\\\"gas\\\", \\\"counts\\\"]) * data[\\\"fits\\\", \\\"projected_temperature\\\"]\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"ds.add_field(\\n\",\n    \"    (\\\"gas\\\", \\\"pseudo_pressure\\\"),\\n\",\n    \"    function=_pp,\\n\",\n    \"    sampling_type=\\\"cell\\\",\\n\",\n    \"    units=\\\"sqrt(counts)*keV\\\",\\n\",\n    \"    take_log=False,\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def _pe(data):\\n\",\n    \"    return data[\\\"fits\\\", \\\"projected_temperature\\\"] * data[\\\"gas\\\", \\\"counts\\\"] ** (-1.0 / 3.0)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"ds.add_field(\\n\",\n    \"    (\\\"gas\\\", \\\"pseudo_entropy\\\"),\\n\",\n    \"    function=_pe,\\n\",\n    \"    sampling_type=\\\"cell\\\",\\n\",\n    \"    units=\\\"keV*(counts)**(-1/3)\\\",\\n\",\n    \"    take_log=False,\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here, we're deriving a \\\"counts\\\" field from the \\\"flux\\\" field by passing it a `field_parameter` for the exposure time of the time and multiplying by the pixel scale. Second, we use the fact that the surface brightness is strongly dependent on density ($S_X \\\\propto \\\\rho^2$) to use the counts in each pixel as a \\\"stand-in\\\". Next, we'll grab the exposure time from the primary FITS header of the flux file and create a `YTQuantity` from it, to be used as a `field_parameter`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"exposure_time = ds.quan(ds.primary_header[\\\"exposure\\\"], \\\"s\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, we can make the `SlicePlot` object of the fields we want, passing in the `exposure_time` as a `field_parameter`. We'll also set the width of the image to 250 pixels.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    [\\n\",\n    \"        (\\\"fits\\\", \\\"flux\\\"),\\n\",\n    \"        (\\\"fits\\\", \\\"projected_temperature\\\"),\\n\",\n    \"        (\\\"gas\\\", \\\"pseudo_pressure\\\"),\\n\",\n    \"        (\\\"gas\\\", \\\"pseudo_entropy\\\"),\\n\",\n    \"    ],\\n\",\n    \"    origin=\\\"native\\\",\\n\",\n    \"    field_parameters={\\\"exposure_time\\\": exposure_time},\\n\",\n    \")\\n\",\n    \"slc.set_log((\\\"fits\\\", \\\"flux\\\"), True)\\n\",\n    \"slc.set_zlim((\\\"fits\\\", \\\"flux\\\"), 1e-5)\\n\",\n    \"slc.set_log((\\\"gas\\\", \\\"pseudo_pressure\\\"), False)\\n\",\n    \"slc.set_log((\\\"gas\\\", \\\"pseudo_entropy\\\"), False)\\n\",\n    \"slc.set_width(250.0)\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To add the celestial coordinates to the image, we can use `PlotWindowWCS`, if you have a recent version of AstroPy (>= 1.3) installed:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from yt.frontends.fits.misc import PlotWindowWCS\\n\",\n    \"\\n\",\n    \"wcs_slc = PlotWindowWCS(slc)\\n\",\n    \"wcs_slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can make use of yt's facilities for profile plotting as well.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"v, c = ds.find_max((\\\"fits\\\", \\\"flux\\\"))  # Find the maximum flux and its center\\n\",\n    \"my_sphere = ds.sphere(c, (100.0, \\\"code_length\\\"))  # Radius of 150 pixels\\n\",\n    \"my_sphere.set_field_parameter(\\\"exposure_time\\\", exposure_time)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Such as a radial profile plot:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"radial_profile = yt.ProfilePlot(\\n\",\n    \"    my_sphere,\\n\",\n    \"    \\\"radius\\\",\\n\",\n    \"    [\\\"counts\\\", \\\"pseudo_pressure\\\", \\\"pseudo_entropy\\\"],\\n\",\n    \"    n_bins=30,\\n\",\n    \"    weight_field=\\\"ones\\\",\\n\",\n    \")\\n\",\n    \"radial_profile.set_log(\\\"counts\\\", True)\\n\",\n    \"radial_profile.set_log(\\\"pseudo_pressure\\\", True)\\n\",\n    \"radial_profile.set_log(\\\"pseudo_entropy\\\", True)\\n\",\n    \"radial_profile.set_xlim(3, 100.0)\\n\",\n    \"radial_profile.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Or a phase plot:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"phase_plot = yt.PhasePlot(\\n\",\n    \"    my_sphere, \\\"pseudo_pressure\\\", \\\"pseudo_entropy\\\", [\\\"counts\\\"], weight_field=None\\n\",\n    \")\\n\",\n    \"phase_plot.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, we can also take an existing [ds9](http://ds9.si.edu/site/Home.html) region and use it to create a \\\"cut region\\\", using `ds9_region` (the [regions](https://astropy-regions.readthedocs.io/) package needs to be installed for this):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from yt.frontends.fits.misc import ds9_region\\n\",\n    \"\\n\",\n    \"reg_file = [\\n\",\n    \"    \\\"# Region file format: DS9 version 4.1\\\\n\\\",\\n\",\n    \"    \\\"global color=green dashlist=8 3 width=3 include=1 source=1\\\\n\\\",\\n\",\n    \"    \\\"FK5\\\\n\\\",\\n\",\n    \"    'circle(15:16:44.817,+7:01:19.62,34.6256\\\")',\\n\",\n    \"]\\n\",\n    \"f = open(\\\"circle.reg\\\", \\\"w\\\")\\n\",\n    \"f.writelines(reg_file)\\n\",\n    \"f.close()\\n\",\n    \"circle_reg = ds9_region(\\n\",\n    \"    ds, \\\"circle.reg\\\", field_parameters={\\\"exposure_time\\\": exposure_time}\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This region may now be used to compute derived quantities:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(\\n\",\n    \"    circle_reg.quantities.weighted_average_quantity(\\\"projected_temperature\\\", \\\"counts\\\")\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Or used in projections:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ProjectionPlot(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    [\\n\",\n    \"        (\\\"fits\\\", \\\"flux\\\"),\\n\",\n    \"        (\\\"fits\\\", \\\"projected_temperature\\\"),\\n\",\n    \"        (\\\"gas\\\", \\\"pseudo_pressure\\\"),\\n\",\n    \"        (\\\"gas\\\", \\\"pseudo_entropy\\\"),\\n\",\n    \"    ],\\n\",\n    \"    origin=\\\"native\\\",\\n\",\n    \"    field_parameters={\\\"exposure_time\\\": exposure_time},\\n\",\n    \"    data_source=circle_reg,\\n\",\n    \"    method=\\\"sum\\\",\\n\",\n    \")\\n\",\n    \"prj.set_log((\\\"fits\\\", \\\"flux\\\"), True)\\n\",\n    \"prj.set_zlim((\\\"fits\\\", \\\"flux\\\"), 1e-5)\\n\",\n    \"prj.set_log((\\\"gas\\\", \\\"pseudo_pressure\\\"), False)\\n\",\n    \"prj.set_log((\\\"gas\\\", \\\"pseudo_entropy\\\"), False)\\n\",\n    \"prj.set_width(250.0)\\n\",\n    \"prj.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## The Bullet Cluster\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This example uses an events table file from a ~100 ks exposure of the \\\"Bullet Cluster\\\" from the [Chandra Data Archive](http://cxc.harvard.edu/cda/). In this case, the individual photon events are treated as particle fields in yt. However, you can make images of the object in different energy bands using the `setup_counts_fields` function. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from yt.frontends.fits.api import setup_counts_fields\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`load` will handle the events file as FITS image files, and will set up a grid using the WCS information in the file. Optionally, the events may be reblocked to a new resolution. by setting the `\\\"reblock\\\"` parameter in the `parameters` dictionary in `load`. `\\\"reblock\\\"` must be a power of 2. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds2 = yt.load(\\\"xray_fits/acisf05356N003_evt2.fits.gz\\\", parameters={\\\"reblock\\\": 2})\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`setup_counts_fields` will take a list of energy bounds (emin, emax) in keV and create a new field from each where the photons in that energy range will be deposited onto the image grid. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ebounds = [(0.1, 2.0), (2.0, 5.0)]\\n\",\n    \"setup_counts_fields(ds2, ebounds)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The \\\"x\\\", \\\"y\\\", \\\"energy\\\", and \\\"time\\\" fields in the events table are loaded as particle fields. Each one has a name given by \\\"event\\\\_\\\" plus the name of the field:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"dd = ds2.all_data()\\n\",\n    \"print(dd[\\\"io\\\", \\\"event_x\\\"])\\n\",\n    \"print(dd[\\\"io\\\", \\\"event_y\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, we'll make a plot of the two counts fields we made, and pan and zoom to the bullet:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(\\n\",\n    \"    ds2, \\\"z\\\", [(\\\"gas\\\", \\\"counts_0.1-2.0\\\"), (\\\"gas\\\", \\\"counts_2.0-5.0\\\")], origin=\\\"native\\\"\\n\",\n    \")\\n\",\n    \"slc.pan((100.0, 100.0))\\n\",\n    \"slc.set_width(500.0)\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The counts fields can take the field parameter `\\\"sigma\\\"` and use [AstroPy's convolution routines](https://astropy.readthedocs.io/en/latest/convolution/) to smooth the data with a Gaussian:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(\\n\",\n    \"    ds2,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    [(\\\"gas\\\", \\\"counts_0.1-2.0\\\"), (\\\"gas\\\", \\\"counts_2.0-5.0\\\")],\\n\",\n    \"    origin=\\\"native\\\",\\n\",\n    \"    field_parameters={\\\"sigma\\\": 2.0},\\n\",\n    \")  # This value is in pixel scale\\n\",\n    \"slc.pan((100.0, 100.0))\\n\",\n    \"slc.set_width(500.0)\\n\",\n    \"slc.set_zlim((\\\"gas\\\", \\\"counts_0.1-2.0\\\"), 0.01, 100.0)\\n\",\n    \"slc.set_zlim((\\\"gas\\\", \\\"counts_2.0-5.0\\\"), 0.01, 50.0)\\n\",\n    \"slc.show()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.5.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/cookbook/geographic_xforms_and_projections.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Geographic Transforms and Projections\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Loading the GEOS data \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For this analysis we'll be loading some global climate data into yt. A frontend does not exist for this dataset yet, so we'll load it in as a uniform grid with netcdf4.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import os\\n\",\n    \"import re\\n\",\n    \"\\n\",\n    \"import netCDF4 as nc4\\n\",\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def get_data_path(arg):\\n\",\n    \"    if os.path.exists(arg):\\n\",\n    \"        return arg\\n\",\n    \"    else:\\n\",\n    \"        return os.path.join(yt.config.ytcfg.get(\\\"yt\\\", \\\"test_data_dir\\\"), arg)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"n = nc4.Dataset(get_data_path(\\\"geos/GEOS.fp.asm.inst3_3d_aer_Nv.20180822_0900.V01.nc4\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Using the loaded data we'll fill arrays with the data dimensions and limits. We'll also rename `vertical level` to `altitude` to be clearer. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"dims = []\\n\",\n    \"sizes = []\\n\",\n    \"bbox = []\\n\",\n    \"ndims = len(n.dimensions)\\n\",\n    \"for dim in n.dimensions.keys():\\n\",\n    \"    size = n.variables[dim].size\\n\",\n    \"    if size > 1:\\n\",\n    \"        bbox.append([n.variables[dim][:].min(), n.variables[dim][:].max()])\\n\",\n    \"        dims.append(n.variables[dim].long_name)\\n\",\n    \"        sizes.append(size)\\n\",\n    \"dims.reverse()  # Fortran ordering\\n\",\n    \"sizes.reverse()\\n\",\n    \"bbox.reverse()\\n\",\n    \"dims = [f.replace(\\\"vertical level\\\", \\\"altitude\\\") for f in dims]\\n\",\n    \"bbox = np.array(bbox)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We'll also load the data into a container dictionary and create a lookup for the short to the long names \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"w_regex = re.compile(r\\\"([a-zA-Z]+)(.*)\\\")\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def regex_parser(s):\\n\",\n    \"    try:\\n\",\n    \"        return \\\"**\\\".join(filter(None, w_regex.search(s).groups()))\\n\",\n    \"    except AttributeError:\\n\",\n    \"        return s\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"data = {}\\n\",\n    \"names = {}\\n\",\n    \"for field, d in n.variables.items():\\n\",\n    \"    if d.ndim != ndims:\\n\",\n    \"        continue\\n\",\n    \"    units = n.variables[field].units\\n\",\n    \"    units = \\\" * \\\".join(map(regex_parser, units.split()))\\n\",\n    \"    data[field] = (np.squeeze(d), str(units))\\n\",\n    \"    names[field] = n.variables[field].long_name.replace(\\\"_\\\", \\\" \\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now the data can be loaded with yt's `load_uniform_grid` function. We also need to say that the geometry is a `geographic` type. This will ensure that the axes created are matplotlib GeoAxes and that the transform functions are available to use for projections. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_uniform_grid(\\n\",\n    \"    data, sizes, 1.0, geometry=\\\"geographic\\\", bbox=bbox, axis_order=dims\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Default projection with geographic geometry\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now that the data is loaded, we can plot it with a yt SlicePlot along the altitude. This will create a figure with latitude and longitude as the plot axes and the colormap will correspond to the air density. Because no projection type has been set, the geographic geometry type assumes that the data is of the `PlateCarree` form. The resulting figure will be a `Mollweide` plot. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p = yt.SlicePlot(ds, \\\"altitude\\\", \\\"AIRDENS\\\")\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that this doesn't have a lot of contextual information. We can add annotations for the coastlines just as we would with matplotlib. Before the annotations are set, we need to call `p.render` to make the axes available for annotation. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p = yt.SlicePlot(ds, \\\"altitude\\\", \\\"AIRDENS\\\")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Using geographic transforms to project data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If a projection other than the default `Mollweide` is desired, then we can pass an argument to the `set_mpl_projection()` function to set a different projection than the default. This will set the projection to a Robinson projection. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p = yt.SlicePlot(ds, \\\"altitude\\\", \\\"AIRDENS\\\")\\n\",\n    \"p.set_mpl_projection(\\\"Robinson\\\")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`geo_projection` accepts a string or a 2- to 3- length sequence describing the projection the second item in the sequence are the args and the third item is the kwargs. This can be used for further customization of the projection. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p = yt.SlicePlot(ds, \\\"altitude\\\", \\\"AIRDENS\\\")\\n\",\n    \"p.set_mpl_projection((\\\"Robinson\\\", (37.5,)))\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We don't actually need to keep creating a SlicePlot to change the projection type. We can use the function `set_mpl_projection()` and pass in a string of the transform type that we desire after an existing `SlicePlot` instance has been created. This will set the figure to an `Orthographic` projection. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection(\\\"Orthographic\\\")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`set_mpl_projection()` can be used in a number of ways to customize the projection type. \\n\",\n    \"* If a **string** is passed, then the string must correspond to the transform name, which is exclusively cartopy transforms at this time. This looks like: `set_mpl_projection('ProjectionType')`\\n\",\n    \"\\n\",\n    \"* If a **tuple** is passed, the first item of the tuple is a string of the transform name and the second two items are args and kwargs. These can be used to further customize the transform (by setting the latitude and longitude, for example. This looks like: \\n\",\n    \"  * `set_mpl_projection(('ProjectionType', (args)))`\\n\",\n    \"  * `set_mpl_projection(('ProjectionType', (args), {kwargs}))`\\n\",\n    \"* A **transform object** can also be passed. This can be any transform type -- a cartopy transform or a matplotlib transform. This allows users to either pass the same transform object around between plots or define their own transform and use that in yt's plotting functions. With a standard cartopy transform, this would look like:\\n\",\n    \"  * `set_mpl_projection(cartopy.crs.PlateCarree())`\\n\",\n    \"  \\n\",\n    \"To summarize:\\n\",\n    \"The function `set_mpl_projection` can take one of several input types:\\n\",\n    \"* `set_mpl_projection('ProjectionType')`\\n\",\n    \"* `set_mpl_projection(('ProjectionType', (args)))`\\n\",\n    \"* `set_mpl_projection(('ProjectionType', (args), {kwargs}))`\\n\",\n    \"* `set_mpl_projection(cartopy.crs.MyTransform())`\\n\",\n    \"\\n\",\n    \"For example, we can make the same Orthographic projection and pass in the central latitude and longitude for the projection: \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection((\\\"Orthographic\\\", (90, 45)))\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Or we can pass in the arguments to this function as kwargs by passing a three element tuple. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection(\\n\",\n    \"    (\\\"Orthographic\\\", (), {\\\"central_latitude\\\": -45, \\\"central_longitude\\\": 275})\\n\",\n    \")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### A few examples of different projections\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This next section will show a few of the different projections that one can use. This isn't meant to be complete, but it'll give you a visual idea of how these transforms can be used to illustrate geographic data for different purposes. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection((\\\"RotatedPole\\\", (177.5, 37.5)))\\n\",\n    \"p.redner()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection(\\n\",\n    \"    (\\\"RotatedPole\\\", (), {\\\"pole_latitude\\\": 37.5, \\\"pole_longitude\\\": 177.5})\\n\",\n    \")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection(\\\"NorthPolarStereo\\\")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection(\\\"AlbersEqualArea\\\")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection(\\\"InterruptedGoodeHomolosine\\\")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection(\\\"Robinson\\\")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection(\\\"Gnomonic\\\")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Modifying the data transform\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"While the data projection modifies how the data is displayed in our plot, the data transform describes the coordinate system that the data is actually described by. By default, the data is assumed to have a `PlateCarree` data transform. If you would like to change this, you can access the dictionary in the coordinate handler and set it to something else. The dictionary is structured such that each axis has its own default transform, so be sure to set the axis you intend to change. This next example changes the transform to a Miller type. Because our data is not in Miller coordinates, it will be skewed. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ds.coordinates.data_transform[\\\"altitude\\\"] = \\\"Miller\\\"\\n\",\n    \"p = yt.SlicePlot(ds, \\\"altitude\\\", \\\"AIRDENS\\\")\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Because the transform type shouldn't change as we make subsequent figures, once it is changed it will be the same for all other figures made with the same dataset object. Note that this particular dataset is not actually in a Miller system, which is why the data now doesn't span the entire globe. Setting the new projection to Robinson results in Miller-skewed data in our next figure. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_mpl_projection(\\\"Robinson\\\")\\n\",\n    \"p.render()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.set_global()\\n\",\n    \"p.plots[\\\"AIRDENS\\\"].axes.coastlines()\\n\",\n    \"p.show()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.9.10\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/cookbook/global_phase_plots.py",
    "content": "import yt\n\n# load the dataset\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# This is an object that describes the entire box\nad = ds.all_data()\n\n# We plot the average velocity magnitude (mass-weighted) in our object\n# as a function of density and temperature\nplot = yt.PhasePlot(\n    ad, (\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"velocity_magnitude\")\n)\n\n# save the plot\nplot.save()\n"
  },
  {
    "path": "doc/source/cookbook/hse_field.py",
    "content": "import numpy as np\n\nimport yt\n\n# Open a dataset from when there's a lot of sloshing going on.\n\nds = yt.load(\"GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0350\")\n\n# Define the components of the gravitational acceleration vector field by\n# taking the gradient of the gravitational potential\ngrad_fields = ds.add_gradient_fields((\"gas\", \"gravitational_potential\"))\n\n# We don't need to do the same for the pressure field because yt already\n# has pressure gradient fields. Now, define the \"degree of hydrostatic\n# equilibrium\" field.\n\n\ndef _hse(data):\n    # Remember that g is the negative of the potential gradient\n    gx = -data[\"gas\", \"density\"] * data[\"gas\", \"gravitational_potential_gradient_x\"]\n    gy = -data[\"gas\", \"density\"] * data[\"gas\", \"gravitational_potential_gradient_y\"]\n    gz = -data[\"gas\", \"density\"] * data[\"gas\", \"gravitational_potential_gradient_z\"]\n    hx = data[\"gas\", \"pressure_gradient_x\"] - gx\n    hy = data[\"gas\", \"pressure_gradient_y\"] - gy\n    hz = data[\"gas\", \"pressure_gradient_z\"] - gz\n    h = np.sqrt((hx * hx + hy * hy + hz * hz) / (gx * gx + gy * gy + gz * gz))\n    return h\n\n\nds.add_field(\n    (\"gas\", \"HSE\"),\n    function=_hse,\n    units=\"\",\n    take_log=False,\n    display_name=\"Hydrostatic Equilibrium\",\n    sampling_type=\"cell\",\n)\n\n# The gradient operator requires periodic boundaries.  This dataset has\n# open boundary conditions.\nds.force_periodicity()\n\n# Take a slice through the center of the domain\nslc = yt.SlicePlot(ds, 2, [(\"gas\", \"density\"), (\"gas\", \"HSE\")], width=(1, \"Mpc\"))\n\nslc.save(\"hse\")\n"
  },
  {
    "path": "doc/source/cookbook/image_background_colors.py",
    "content": "import yt\n\n# This shows how to save ImageArray objects, such as those returned from\n# volume renderings, to pngs with varying backgrounds.\n\n# First we use the simple_volume_rendering.py recipe from above to generate\n# a standard volume rendering.\n\nds = yt.load(\"Enzo_64/DD0043/data0043\")\nim, sc = yt.volume_render(ds, (\"gas\", \"density\"))\nim.write_png(\"original.png\", sigma_clip=8.0)\n\n# Our image array can now be transformed to include different background\n# colors.  By default, the background color is black.  The following\n# modifications can be used on any image array.\n\n# write_png accepts a background keyword argument that defaults to 'black'.\n# Other choices include:\n# black (0.,0.,0.,1.)\n# white (1.,1.,1.,1.)\n# None  (0.,0.,0.,0.) <-- Transparent!\n# any rgba list/array: [r,g,b,a], bounded by 0..1\n\n# We include the sigma_clip=8 keyword here to bring out more contrast between\n# the background and foreground, but it is entirely optional.\n\nim.write_png(\"black_bg.png\", background=\"black\", sigma_clip=8.0)\nim.write_png(\"white_bg.png\", background=\"white\", sigma_clip=8.0)\nim.write_png(\"green_bg.png\", background=[0.0, 1.0, 0.0, 1.0], sigma_clip=8.0)\nim.write_png(\"transparent_bg.png\", background=None, sigma_clip=8.0)\n"
  },
  {
    "path": "doc/source/cookbook/image_resolution.py",
    "content": "import numpy as np\n\nimport yt\n\n# Load the dataset.  We'll work with a some Gadget data to illustrate all\n# the different ways in which the effective resolution can vary.  Specifically,\n# we'll use the GadgetDiskGalaxy dataset available at\n#  http://yt-project.org/data/GadgetDiskGalaxy.tar.gz\n\n# load the data with a refinement criteria of 2 particle per cell\n# n.b. -- in yt-4.0, n_ref no longer exists as the data is no longer\n# deposited only a grid.  At present (03/15/2019), there is no way to\n# handle non-gas data in Gadget snapshots, though that is work in progress\nif int(yt.__version__[0]) < 4:\n    # increasing n_ref will result in a \"lower resolution\" (but faster) image,\n    # while decreasing it will go the opposite way\n    ds = yt.load(\"GadgetDiskGalaxy/snapshot_200.hdf5\", n_ref=16)\nelse:\n    ds = yt.load(\"GadgetDiskGalaxy/snapshot_200.hdf5\")\n\n# Create projections of the density (max value in each resolution element in the image):\nprj = yt.ProjectionPlot(\n    ds, \"x\", (\"gas\", \"density\"), method=\"max\", center=\"max\", width=(100, \"kpc\")\n)\n\n# nicen up the plot by using a better interpolation:\nplot = prj.plots[list(prj.plots)[0]]\nax = plot.axes\nimg = ax.images[0]\nimg.set_interpolation(\"bicubic\")\n\n# nicen up the plot by setting the background color to the minimum of the colorbar\nprj.set_background_color((\"gas\", \"density\"))\n\n# vary the buff_size -- the number of resolution elements in the actual visualization\n# set it to 2000x2000\nbuff_size = 2000\nprj.set_buff_size(buff_size)\n\n# set the figure size in inches\nfigure_size = 10\nprj.set_figure_size(figure_size)\n\n# if the image does not fill the plot (as is default, since the axes and\n# colorbar contribute as well), then figuring out the proper dpi for a given\n# buff_size and figure_size is non-trivial -- it requires finding the bbox\n# for the actual image:\nbounding_box = ax.get_position()\n# we're going to scale to the larger of the two sides\nimage_size = figure_size * max([bounding_box.width, bounding_box.height])\n# now save with a dpi that's scaled to the buff_size:\ndpi = np.rint(np.ceil(buff_size / image_size))\nprj.save(\"with_axes_colorbar.png\", mpl_kwargs=dict(dpi=dpi))\n\n# in the case where the image fills the entire plot (i.e. if the axes and colorbar\n# are turned off), it's trivial to figure out the correct dpi from the buff_size and\n# figure_size (or vice versa):\n\n# hide the colorbar:\nprj.hide_colorbar()\n\n# hide the axes, while still keeping the background color correct:\nprj.hide_axes(draw_frame=True)\n\n# save with a dpi that makes sense:\ndpi = np.rint(np.ceil(buff_size / figure_size))\nprj.save(\"no_axes_colorbar.png\", mpl_kwargs=dict(dpi=dpi))\n"
  },
  {
    "path": "doc/source/cookbook/index.rst",
    "content": ".. _cookbook:\n\nThe Cookbook\n============\n\nyt provides a great deal of functionality to the user, but sometimes it can\nbe a bit complex.  This section of the documentation lays out examples recipes\nfor how to do a variety of tasks.  Most of the early, simple code\ndemonstrations are small scripts which you can easily copy and paste into\nyour own code; however, as we move to more complex tasks, the recipes move to\niPython notebooks to display intermediate steps.  All of these recipes are\navailable for download in a link next to the recipe.\n\nGetting the Sample Data\n-----------------------\n\nAll of the data used in the cookbook is freely available\n`here <https://yt-project.org/data/>`_, where you will find links to download\nindividual datasets.\n\n.. note:: To contribute your own recipes, please follow the instructions\n    on how to contribute documentation code: :ref:`writing_documentation`.\n\nExample Scripts\n---------------\n.. toctree::\n   :maxdepth: 2\n\n   simple_plots\n   calculating_information\n   complex_plots\n   constructing_data_objects\n\n.. _example-notebooks:\n\nExample Notebooks\n-----------------\n.. toctree::\n   :maxdepth: 1\n\n   notebook_tutorial\n   custom_colorbar_tickmarks\n   yt_gadget_analysis\n   yt_gadget_owls_analysis\n   ../visualizing/TransferFunctionHelper_Tutorial\n   fits_radio_cubes\n   fits_xray_images\n   geographic_xforms_and_projections\n   tipsy_and_yt\n   ../visualizing/Volume_Rendering_Tutorial\n"
  },
  {
    "path": "doc/source/cookbook/matplotlib-animation.py",
    "content": "from matplotlib import rc_context\nfrom matplotlib.animation import FuncAnimation\n\nimport yt\n\nts = yt.load(\"GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_*\")\n\nplot = yt.SlicePlot(ts[0], \"z\", (\"gas\", \"density\"))\nplot.set_zlim((\"gas\", \"density\"), 8e-29, 3e-26)\n\nfig = plot.plots[\"gas\", \"density\"].figure\n\n\n# animate must accept an integer frame number. We use the frame number\n# to identify which dataset in the time series we want to load\ndef animate(i):\n    ds = ts[i]\n    plot._switch_ds(ds)\n\n\nanimation = FuncAnimation(fig, animate, frames=len(ts))\n\n# Override matplotlib's defaults to get a nicer looking font\nwith rc_context({\"mathtext.fontset\": \"stix\"}):\n    animation.save(\"animation.mp4\")\n"
  },
  {
    "path": "doc/source/cookbook/multi_plot_3x2_FRB.py",
    "content": "import numpy as np\nfrom matplotlib.colors import LogNorm\n\nimport yt\nfrom yt.visualization.api import get_multi_plot\n\nfn = \"Enzo_64/RD0006/RedshiftOutput0006\"  # dataset to load\n\n# load data and get center value and center location as maximum density location\nds = yt.load(fn)\nv, c = ds.find_max((\"gas\", \"density\"))\n\n# set up our Fixed Resolution Buffer parameters: a width, resolution, and center\nwidth = (1.0, \"unitary\")\nres = [1000, 1000]\n#  get_multi_plot returns a containing figure, a list-of-lists of axes\n#   into which we can place plots, and some axes that we'll put\n#   colorbars.\n\n#  it accepts: # of x-axis plots, # of y-axis plots, and how the\n#  colorbars are oriented (this also determines where they go: below\n#  in the case of 'horizontal', on the right in the case of\n#  'vertical'), bw is the base-width in inches (4 is about right for\n#  most cases)\n\norient = \"horizontal\"\nfig, axes, colorbars = get_multi_plot(2, 3, colorbar=orient, bw=6)\n\n# Now we follow the method of \"multi_plot.py\" but we're going to iterate\n# over the columns, which will become axes of slicing.\nplots = []\nfor ax in range(3):\n    sli = ds.slice(ax, c[ax])\n    frb = sli.to_frb(width, res)\n    den_axis = axes[ax][0]\n    temp_axis = axes[ax][1]\n\n    # here, we turn off the axes labels and ticks, but you could\n    # customize further.\n    for ax in (den_axis, temp_axis):\n        ax.xaxis.set_visible(False)\n        ax.yaxis.set_visible(False)\n\n    # converting our fixed resolution buffers to NDarray so matplotlib can\n    # render them\n    dens = np.array(frb[\"gas\", \"density\"])\n    temp = np.array(frb[\"gas\", \"temperature\"])\n\n    plots.append(den_axis.imshow(dens, norm=LogNorm()))\n    plots[-1].set_clim((5e-32, 1e-29))\n    plots[-1].set_cmap(\"bds_highcontrast\")\n\n    plots.append(temp_axis.imshow(temp, norm=LogNorm()))\n    plots[-1].set_clim((1e3, 1e8))\n    plots[-1].set_cmap(\"hot\")\n\n# Each 'cax' is a colorbar-container, into which we'll put a colorbar.\n# the zip command creates triples from each element of the three lists\n# .  Note that it cuts off after the shortest iterator is exhausted,\n# in this case, titles.\ntitles = [\n    r\"$\\mathrm{density}\\ (\\mathrm{g\\ cm^{-3}})$\",\n    r\"$\\mathrm{temperature}\\ (\\mathrm{K})$\",\n]\nfor p, cax, t in zip(plots, colorbars, titles):\n    # Now we make a colorbar, using the 'image' we stored in plots\n    # above. note this is what is *returned* by the imshow method of\n    # the plots.\n    cbar = fig.colorbar(p, cax=cax, orientation=orient)\n    cbar.set_label(t)\n\n# And now we're done!\nfig.savefig(f\"{ds}_3x2.png\")\n"
  },
  {
    "path": "doc/source/cookbook/multi_plot_slice_and_proj.py",
    "content": "import numpy as np\nfrom matplotlib.colors import LogNorm\n\nimport yt\nfrom yt.visualization.base_plot_types import get_multi_plot\n\nfn = \"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\"  # dataset to load\norient = \"horizontal\"\n\nds = yt.load(fn)  # load data\n\n# There's a lot in here:\n#   From this we get a containing figure, a list-of-lists of axes into which we\n#   can place plots, and some axes that we'll put colorbars.\n# We feed it:\n#   Number of plots on the x-axis, number of plots on the y-axis, and how we\n#   want our colorbars oriented.  (This governs where they will go, too.\n#   bw is the base-width in inches, but 4 is about right for most cases.\nfig, axes, colorbars = get_multi_plot(3, 2, colorbar=orient, bw=4)\n\nslc = yt.SlicePlot(\n    ds,\n    \"z\",\n    fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"velocity_magnitude\")],\n)\nproj = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), weight_field=(\"gas\", \"density\"))\n\nslc_frb = slc.data_source.to_frb((1.0, \"Mpc\"), 512)\nproj_frb = proj.data_source.to_frb((1.0, \"Mpc\"), 512)\n\ndens_axes = [axes[0][0], axes[1][0]]\ntemp_axes = [axes[0][1], axes[1][1]]\nvels_axes = [axes[0][2], axes[1][2]]\n\nfor dax, tax, vax in zip(dens_axes, temp_axes, vels_axes):\n    dax.xaxis.set_visible(False)\n    dax.yaxis.set_visible(False)\n    tax.xaxis.set_visible(False)\n    tax.yaxis.set_visible(False)\n    vax.xaxis.set_visible(False)\n    vax.yaxis.set_visible(False)\n\n# Converting our Fixed Resolution Buffers to numpy arrays so that matplotlib\n# can render them\n\nslc_dens = np.array(slc_frb[\"gas\", \"density\"])\nproj_dens = np.array(proj_frb[\"gas\", \"density\"])\nslc_temp = np.array(slc_frb[\"gas\", \"temperature\"])\nproj_temp = np.array(proj_frb[\"gas\", \"temperature\"])\nslc_vel = np.array(slc_frb[\"gas\", \"velocity_magnitude\"])\nproj_vel = np.array(proj_frb[\"gas\", \"velocity_magnitude\"])\n\nplots = [\n    dens_axes[0].imshow(slc_dens, origin=\"lower\", norm=LogNorm()),\n    dens_axes[1].imshow(proj_dens, origin=\"lower\", norm=LogNorm()),\n    temp_axes[0].imshow(slc_temp, origin=\"lower\"),\n    temp_axes[1].imshow(proj_temp, origin=\"lower\"),\n    vels_axes[0].imshow(slc_vel, origin=\"lower\", norm=LogNorm()),\n    vels_axes[1].imshow(proj_vel, origin=\"lower\", norm=LogNorm()),\n]\n\nplots[0].set_clim((1.0e-27, 1.0e-25))\nplots[0].set_cmap(\"bds_highcontrast\")\nplots[1].set_clim((1.0e-27, 1.0e-25))\nplots[1].set_cmap(\"bds_highcontrast\")\nplots[2].set_clim((1.0e7, 1.0e8))\nplots[2].set_cmap(\"hot\")\nplots[3].set_clim((1.0e7, 1.0e8))\nplots[3].set_cmap(\"hot\")\nplots[4].set_clim((1e6, 1e8))\nplots[4].set_cmap(\"gist_rainbow\")\nplots[5].set_clim((1e6, 1e8))\nplots[5].set_cmap(\"gist_rainbow\")\n\ntitles = [\n    r\"$\\mathrm{Density}\\ (\\mathrm{g\\ cm^{-3}})$\",\n    r\"$\\mathrm{Temperature}\\ (\\mathrm{K})$\",\n    r\"$\\mathrm{Velocity Magnitude}\\ (\\mathrm{cm\\ s^{-1}})$\",\n]\n\nfor p, cax, t in zip(plots[0:6:2], colorbars, titles):\n    cbar = fig.colorbar(p, cax=cax, orientation=orient)\n    cbar.set_label(t)\n\n# And now we're done!\nfig.savefig(f\"{ds}_3x2\")\n"
  },
  {
    "path": "doc/source/cookbook/multi_width_image.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create a slice plot for the dataset.  With no additional arguments,\n# the width will be the size of the domain and the center will be the\n# center of the simulation box\nslc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n\n# Create a list of a couple of widths and units.\n# (N.B. Mpc (megaparsec) != mpc (milliparsec)\nwidths = [(1, \"Mpc\"), (15, \"kpc\")]\n\n# Loop through the list of widths and units.\nfor width, unit in widths:\n    # Set the width.\n    slc.set_width(width, unit)\n\n    # Write out the image with a unique name.\n    slc.save(\"%s_%010d_%s\" % (ds, width, unit))\n\nzoomFactors = [2, 4, 5]\n\n# recreate the original slice\nslc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n\nfor zoomFactor in zoomFactors:\n    # zoom in\n    slc.zoom(zoomFactor)\n\n    # Write out the image with a unique name.\n    slc.save(\"%s_%i\" % (ds, zoomFactor))\n"
  },
  {
    "path": "doc/source/cookbook/multiplot_2x2.py",
    "content": "import matplotlib.pyplot as plt\nfrom mpl_toolkits.axes_grid1 import AxesGrid\n\nimport yt\n\nfn = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\nds = yt.load(fn)  # load data\n\nfig = plt.figure()\n\n# See http://matplotlib.org/mpl_toolkits/axes_grid/api/axes_grid_api.html\n# These choices of keyword arguments produce a four panel plot that includes\n# four narrow colorbars, one for each plot.  Axes labels are only drawn on the\n# bottom left hand plot to avoid repeating information and make the plot less\n# cluttered.\ngrid = AxesGrid(\n    fig,\n    (0.075, 0.075, 0.85, 0.85),\n    nrows_ncols=(2, 2),\n    axes_pad=1.0,\n    label_mode=\"1\",\n    share_all=True,\n    cbar_location=\"right\",\n    cbar_mode=\"each\",\n    cbar_size=\"3%\",\n    cbar_pad=\"0%\",\n)\n\nfields = [\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_x\"),\n    (\"gas\", \"velocity_y\"),\n    (\"gas\", \"velocity_magnitude\"),\n]\n\n# Create the plot.  Since SlicePlot accepts a list of fields, we need only\n# do this once.\np = yt.SlicePlot(ds, \"z\", fields)\n\n# Velocity is going to be both positive and negative, so let's make these\n# slices use a linear colorbar scale\np.set_log((\"gas\", \"velocity_x\"), False)\np.set_log((\"gas\", \"velocity_y\"), False)\n\np.zoom(2)\n\n# For each plotted field, force the SlicePlot to redraw itself onto the AxesGrid\n# axes.\nfor i, field in enumerate(fields):\n    plot = p.plots[field]\n    plot.figure = fig\n    plot.axes = grid[i].axes\n    plot.cax = grid.cbar_axes[i]\n\n# Finally, redraw the plot on the AxesGrid axes.\np.render()\n\nplt.savefig(\"multiplot_2x2.png\")\n"
  },
  {
    "path": "doc/source/cookbook/multiplot_2x2_coordaxes_slice.py",
    "content": "import matplotlib.pyplot as plt\nfrom mpl_toolkits.axes_grid1 import AxesGrid\n\nimport yt\n\nfn = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\nds = yt.load(fn)  # load data\n\nfig = plt.figure()\n\n# See http://matplotlib.org/mpl_toolkits/axes_grid/api/axes_grid_api.html\n# These choices of keyword arguments produce two colorbars, both drawn on the\n# right hand side.  This means there are only two colorbar axes, one for Density\n# and another for temperature.  In addition, axes labels will be drawn for all\n# plots.\ngrid = AxesGrid(\n    fig,\n    (0.075, 0.075, 0.85, 0.85),\n    nrows_ncols=(2, 2),\n    axes_pad=1.0,\n    label_mode=\"all\",\n    share_all=True,\n    cbar_location=\"right\",\n    cbar_mode=\"edge\",\n    cbar_size=\"5%\",\n    cbar_pad=\"0%\",\n)\n\ncuts = [\"x\", \"y\", \"z\", \"z\"]\nfields = [\n    (\"gas\", \"density\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"temperature\"),\n]\n\nfor i, (direction, field) in enumerate(zip(cuts, fields)):\n    # Load the data and create a single plot\n    p = yt.SlicePlot(ds, direction, field)\n    p.zoom(40)\n\n    # This forces the ProjectionPlot to redraw itself on the AxesGrid axes.\n    plot = p.plots[field]\n    plot.figure = fig\n    plot.axes = grid[i].axes\n\n    # Since there are only two colorbar axes, we need to make sure we don't try\n    # to set the temperature colorbar to cbar_axes[4], which would if we used i\n    # to index cbar_axes, yielding a plot without a temperature colorbar.\n    # This unnecessarily redraws the Density colorbar three times, but that has\n    # no effect on the final plot.\n    if field == (\"gas\", \"density\"):\n        plot.cax = grid.cbar_axes[0]\n    elif field == (\"gas\", \"temperature\"):\n        plot.cax = grid.cbar_axes[1]\n\n    # Finally, redraw the plot.\n    p.render()\n\nplt.savefig(\"multiplot_2x2_coordaxes_slice.png\")\n"
  },
  {
    "path": "doc/source/cookbook/multiplot_2x2_time_series.py",
    "content": "import matplotlib.pyplot as plt\nfrom mpl_toolkits.axes_grid1 import AxesGrid\n\nimport yt\n\nfns = [\n    \"Enzo_64/DD0005/data0005\",\n    \"Enzo_64/DD0015/data0015\",\n    \"Enzo_64/DD0025/data0025\",\n    \"Enzo_64/DD0035/data0035\",\n]\n\nfig = plt.figure()\n\n# See http://matplotlib.org/mpl_toolkits/axes_grid/api/axes_grid_api.html\n# These choices of keyword arguments produce a four panel plot with a single\n# shared narrow colorbar on the right hand side of the multipanel plot. Axes\n# labels are drawn for all plots since we're slicing along different directions\n# for each plot.\ngrid = AxesGrid(\n    fig,\n    (0.075, 0.075, 0.85, 0.85),\n    nrows_ncols=(2, 2),\n    axes_pad=0.05,\n    label_mode=\"L\",\n    share_all=True,\n    cbar_location=\"right\",\n    cbar_mode=\"single\",\n    cbar_size=\"3%\",\n    cbar_pad=\"0%\",\n)\n\nfor i, fn in enumerate(fns):\n    # Load the data and create a single plot\n    ds = yt.load(fn)  # load data\n    p = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), width=(55, \"Mpccm\"))\n\n    # Ensure the colorbar limits match for all plots\n    p.set_zlim((\"gas\", \"density\"), 1e-4, 1e-2)\n\n    # This forces the ProjectionPlot to redraw itself on the AxesGrid axes.\n    plot = p.plots[\"gas\", \"density\"]\n    plot.figure = fig\n    plot.axes = grid[i].axes\n    plot.cax = grid.cbar_axes[i]\n\n    # Finally, this actually redraws the plot.\n    p.render()\n\nplt.savefig(\"multiplot_2x2_time_series.png\")\n"
  },
  {
    "path": "doc/source/cookbook/multiplot_export_to_mpl.py",
    "content": "import yt\n\nds = yt.load_sample(\"IsolatedGalaxy\")\n\nfields = [\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_x\"),\n    (\"gas\", \"velocity_y\"),\n    (\"gas\", \"velocity_magnitude\"),\n]\n\np = yt.SlicePlot(ds, \"z\", fields)\np.set_log((\"gas\", \"velocity_x\"), False)\np.set_log((\"gas\", \"velocity_y\"), False)\n\n# this returns a matplotlib figure with an ImageGrid and the slices\n# added to the grid of axes (in this case, 2x2)\nfig = p.export_to_mpl_figure((2, 2))\n\nfig.tight_layout()\n\nfig.savefig(\"multiplot_export_to_mpl.png\")\n"
  },
  {
    "path": "doc/source/cookbook/multiplot_phaseplot.py",
    "content": "import matplotlib.pyplot as plt\nfrom mpl_toolkits.axes_grid1 import AxesGrid\n\nimport yt\n\nfig = plt.figure()\n\n# See http://matplotlib.org/mpl_toolkits/axes_grid/api/axes_grid_api.html\ngrid = AxesGrid(\n    fig,\n    (0.085, 0.085, 0.83, 0.83),\n    nrows_ncols=(1, 2),\n    axes_pad=0.05,\n    label_mode=\"L\",\n    share_all=True,\n    cbar_location=\"right\",\n    cbar_mode=\"single\",\n    cbar_size=\"3%\",\n    cbar_pad=\"0%\",\n    aspect=False,\n)\n\nfor i, SnapNum in enumerate([10, 40]):\n    # Load the data and create a single plot\n    ds = yt.load(\"enzo_tiny_cosmology/DD00%2d/DD00%2d\" % (SnapNum, SnapNum))\n    ad = ds.all_data()\n    p = yt.PhasePlot(\n        ad,\n        (\"gas\", \"density\"),\n        (\"gas\", \"temperature\"),\n        [\n            (\"gas\", \"mass\"),\n        ],\n        weight_field=None,\n    )\n\n    # Ensure the axes and colorbar limits match for all plots\n    p.set_xlim(1.0e-32, 8.0e-26)\n    p.set_ylim(1.0e1, 2.0e7)\n    p.set_zlim((\"gas\", \"mass\"), 1e42, 1e46)\n\n    # This forces the ProjectionPlot to redraw itself on the AxesGrid axes.\n    plot = p.plots[\"gas\", \"mass\"]\n    plot.figure = fig\n    plot.axes = grid[i].axes\n    if i == 0:\n        plot.cax = grid.cbar_axes[i]\n\n    # Actually redraws the plot.\n    p.render()\n\n    # Modify the axes properties **after** p.render() so that they\n    # are not overwritten.\n    plot.axes.xaxis.set_minor_locator(plt.LogLocator(base=10.0, subs=[2.0, 5.0, 8.0]))\n\nplt.savefig(\"multiplot_phaseplot.png\")\n"
  },
  {
    "path": "doc/source/cookbook/notebook_tutorial.rst",
    "content": ".. _notebook-tutorial:\n\nNotebook Tutorial\n-----------------\n\nThe IPython notebook is a powerful system for literate coding - a style of\nwriting code that embeds input, output, and explanatory text into one document.\n\nyt has deep integration with the IPython notebook, explained in-depth in the\nother example notebooks and the rest of the yt documentation.  This page is here\nto give a brief introduction to the notebook itself.\n\nTo start the notebook, enter the following command at the bash command line:\n\n.. code-block:: bash\n\n   $ ipython notebook\n\nDepending on your default web browser and system setup this will open a web\nbrowser and direct you to the notebook dashboard.  If it does not,  you might\nneed to connect to the notebook manually.  See the `IPython documentation\n<http://ipython.org/ipython-doc/stable/notebook/notebook.html#starting-the-notebook-server>`_\nfor more details.\n\nFor the notebook tutorial, we rely on example notebooks that are part of the\nIPython documentation.  We link to static nbviewer versions of the 'evaluated'\nversions of these example notebooks.  If you would like to run them locally on\nyour own computer, simply download the notebook by clicking the 'Download\nNotebook' link in the top right corner of each page.\n\n1. `IPython Notebook Tutorials <https://nbviewer.jupyter.org/github/ipython/ipython/blob/master/examples/Index.ipynb>`_\n"
  },
  {
    "path": "doc/source/cookbook/offaxis_projection.py",
    "content": "import numpy as np\n\nimport yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Choose a center for the render.\nc = [0.5, 0.5, 0.5]\n\n# Our image plane will be normal to some vector.  For things like collapsing\n# objects, you could set it the way you would a cutting plane -- but for this\n# dataset, we'll just choose an off-axis value at random.  This gets normalized\n# automatically.\nL = [1.0, 0.0, 0.0]\n\n# Our \"width\" is the width of the image plane as well as the depth.\n# The first element is the left to right width, the second is the\n# top-bottom width, and the last element is the back-to-front width\n# (all in code units)\nW = [0.04, 0.04, 0.4]\n\n# The number of pixels along one side of the image.\n# The final image will have Npixel^2 pixels.\nNpixels = 512\n\n# Create the off axis projection.\n# Setting no_ghost to False speeds up the process, but makes a\n# slightly lower quality image.\nimage = yt.off_axis_projection(ds, c, L, W, Npixels, (\"gas\", \"density\"), no_ghost=False)\n\n# Write out the final image and give it a name\n# relating to what our dataset is called.\n# We save the log of the values so that the colors do not span\n# many orders of magnitude.  Try it without and see what happens.\nyt.write_image(np.log10(image), f\"{ds}_offaxis_projection.png\")\n"
  },
  {
    "path": "doc/source/cookbook/offaxis_projection_colorbar.py",
    "content": "import yt\n\nfn = \"IsolatedGalaxy/galaxy0030/galaxy0030\"  # dataset to load\n\nds = yt.load(fn)  # load data\n\n# Now we need a center of our volume to render.  Here we'll just use\n# 0.5,0.5,0.5, because volume renderings are not periodic.\nc = [0.5, 0.5, 0.5]\n\n# Our image plane will be normal to some vector.  For things like collapsing\n# objects, you could set it the way you would a cutting plane -- but for this\n# dataset, we'll just choose an off-axis value at random.  This gets normalized\n# automatically.\nL = [0.5, 0.4, 0.7]\n\n# Our \"width\" is the width of the image plane as well as the depth.\n# The first element is the left to right width, the second is the\n# top-bottom width, and the last element is the back-to-front width\n# (all in code units)\nW = [0.04, 0.04, 0.4]\n\n# The number of pixels along one side of the image.\n# The final image will have Npixel^2 pixels.\nNpixels = 512\n\n# Now we call the off_axis_projection function, which handles the rest.\n# Note that we set no_ghost equal to False, so that we *do* include ghost\n# zones in our data.  This takes longer to calculate, but the results look\n# much cleaner than when you ignore the ghost zones.\n# Also note that we set the field which we want to project as \"density\", but\n# really we could use any arbitrary field like \"temperature\", \"metallicity\"\n# or whatever.\nimage = yt.off_axis_projection(ds, c, L, W, Npixels, (\"gas\", \"density\"), no_ghost=False)\n\n# Image is now an NxN array representing the intensities of the various pixels.\n# And now, we call our direct image saver.  We save the log of the result.\nyt.write_projection(\n    image,\n    \"offaxis_projection_colorbar.png\",\n    colorbar_label=\"Column Density (cm$^{-2}$)\",\n)\n"
  },
  {
    "path": "doc/source/cookbook/opaque_rendering.py",
    "content": "import numpy as np\n\nimport yt\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# We start by building a default volume rendering scene\n\nim, sc = yt.volume_render(ds, field=(\"gas\", \"density\"), fname=\"v0.png\", sigma_clip=6.0)\n\nsc.camera.set_width(ds.arr(0.1, \"code_length\"))\ntf = sc.get_source().transfer_function\ntf.clear()\ntf.add_layers(\n    4, 0.01, col_bounds=[-27.5, -25.5], alpha=np.logspace(-3, 0, 4), colormap=\"RdBu_r\"\n)\nsc.save(\"v1.png\", sigma_clip=6.0)\n\n# In this case, the default alphas used (np.logspace(-3,0,Nbins)) does not\n# accentuate the outer regions of the galaxy. Let's start by bringing up the\n# alpha values for each contour to go between 0.1 and 1.0\n\ntf = sc.get_source().transfer_function\ntf.clear()\ntf.add_layers(\n    4, 0.01, col_bounds=[-27.5, -25.5], alpha=np.logspace(0, 0, 4), colormap=\"RdBu_r\"\n)\nsc.save(\"v2.png\", sigma_clip=6.0)\n\n# Now let's set the grey_opacity to True.  This should make the inner portions\n# start to be obscured\n\ntf.grey_opacity = True\nsc.save(\"v3.png\", sigma_clip=6.0)\n\n# That looks pretty good, but let's start bumping up the opacity.\n\ntf.clear()\ntf.add_layers(\n    4,\n    0.01,\n    col_bounds=[-27.5, -25.5],\n    alpha=10.0 * np.ones(4, dtype=\"float64\"),\n    colormap=\"RdBu_r\",\n)\nsc.save(\"v4.png\", sigma_clip=6.0)\n\n# Let's bump up again to see if we can obscure the inner contour.\n\ntf.clear()\ntf.add_layers(\n    4,\n    0.01,\n    col_bounds=[-27.5, -25.5],\n    alpha=30.0 * np.ones(4, dtype=\"float64\"),\n    colormap=\"RdBu_r\",\n)\nsc.save(\"v5.png\", sigma_clip=6.0)\n\n# Now we are losing sight of everything.  Let's see if we can obscure the next\n# layer\n\ntf.clear()\ntf.add_layers(\n    4,\n    0.01,\n    col_bounds=[-27.5, -25.5],\n    alpha=100.0 * np.ones(4, dtype=\"float64\"),\n    colormap=\"RdBu_r\",\n)\nsc.save(\"v6.png\", sigma_clip=6.0)\n\n# That is very opaque!  Now lets go back and see what it would look like with\n# grey_opacity = False\n\ntf.grey_opacity = False\nsc.save(\"v7.png\", sigma_clip=6.0)\n\n# That looks pretty different, but the main thing is that you can see that the\n# inner contours are somewhat visible again.\n"
  },
  {
    "path": "doc/source/cookbook/overplot_grids.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n# Make a density projection.\np = yt.ProjectionPlot(ds, \"y\", (\"gas\", \"density\"))\n\n# Modify the projection\n# The argument specifies the region along the line of sight\n# for which particles will be gathered.\n# 1.0 signifies the entire domain in the line of sight.\np.annotate_grids()\n\n# Save the image.\n# Optionally, give a string as an argument\n# to name files with a keyword.\np.save()\n"
  },
  {
    "path": "doc/source/cookbook/overplot_particles.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n# Make a density projection centered on the 'm'aximum density location\n# with a width of 10 Mpc..\np = yt.ProjectionPlot(ds, \"y\", (\"gas\", \"density\"), center=\"m\", width=(10, \"Mpc\"))\n\n# Modify the projection\n# The argument specifies the region along the line of sight\n# for which particles will be gathered.\n# 1.0 signifies the entire domain in the line of sight\n# p.annotate_particles(1.0)\n# but in this case we only go 10 Mpc in depth\np.annotate_particles((10, \"Mpc\"))\n\n# Save the image.\n# Optionally, give a string as an argument\n# to name files with a keyword.\np.save()\n"
  },
  {
    "path": "doc/source/cookbook/particle_filter.py",
    "content": "import numpy as np\n\nimport yt\nfrom yt.data_objects.particle_filters import add_particle_filter\n\n\n# Define filter functions for our particle filters based on stellar age.\n# In this dataset particles in the initial conditions are given creation\n# times arbitrarily far into the future, so stars with negative ages belong\n# in the old stars filter.\ndef stars_10Myr(pfilter, data):\n    age = data.ds.current_time - data[\"Stars\", \"creation_time\"]\n    filter = np.logical_and(age >= 0, age.in_units(\"Myr\") < 10)\n    return filter\n\n\ndef stars_100Myr(pfilter, data):\n    age = (data.ds.current_time - data[\"Stars\", \"creation_time\"]).in_units(\"Myr\")\n    filter = np.logical_and(age >= 10, age < 100)\n    return filter\n\n\ndef stars_old(pfilter, data):\n    age = data.ds.current_time - data[\"Stars\", \"creation_time\"]\n    filter = np.logical_or(age < 0, age.in_units(\"Myr\") >= 100)\n    return filter\n\n\n# Create the particle filters\nadd_particle_filter(\n    \"stars_young\",\n    function=stars_10Myr,\n    filtered_type=\"Stars\",\n    requires=[\"creation_time\"],\n)\nadd_particle_filter(\n    \"stars_medium\",\n    function=stars_100Myr,\n    filtered_type=\"Stars\",\n    requires=[\"creation_time\"],\n)\nadd_particle_filter(\n    \"stars_old\", function=stars_old, filtered_type=\"Stars\", requires=[\"creation_time\"]\n)\n\n# Load a dataset and apply the particle filters\nfilename = \"TipsyGalaxy/galaxy.00300\"\nds = yt.load(filename)\nds.add_particle_filter(\"stars_young\")\nds.add_particle_filter(\"stars_medium\")\nds.add_particle_filter(\"stars_old\")\n\n# What are the total masses of different ages of star in the whole simulation\n# volume?\nad = ds.all_data()\nmass_young = ad[\"stars_young\", \"particle_mass\"].in_units(\"Msun\").sum()\nmass_medium = ad[\"stars_medium\", \"particle_mass\"].in_units(\"Msun\").sum()\nmass_old = ad[\"stars_old\", \"particle_mass\"].in_units(\"Msun\").sum()\nprint(f\"Mass of young stars = {mass_young:g}\")\nprint(f\"Mass of medium stars = {mass_medium:g}\")\nprint(f\"Mass of old stars = {mass_old:g}\")\n\n# Generate 4 projections: gas density, young stars, medium stars, old stars\nfields = [\n    (\"stars_young\", \"particle_mass\"),\n    (\"stars_medium\", \"particle_mass\"),\n    (\"stars_old\", \"particle_mass\"),\n]\n\nprj1 = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), center=\"max\", width=(100, \"kpc\"))\nprj1.save()\nprj2 = yt.ParticleProjectionPlot(ds, \"z\", fields, center=\"max\", width=(100, \"kpc\"))\nprj2.save()\n"
  },
  {
    "path": "doc/source/cookbook/particle_filter_sfr.py",
    "content": "import numpy as np\nfrom matplotlib import pyplot as plt\n\nimport yt\nfrom yt.data_objects.particle_filters import add_particle_filter\n\n\ndef formed_star(pfilter, data):\n    filter = data[\"all\", \"creation_time\"] > 0\n    return filter\n\n\nadd_particle_filter(\n    \"formed_star\", function=formed_star, filtered_type=\"all\", requires=[\"creation_time\"]\n)\n\nfilename = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\nds = yt.load(filename)\nds.add_particle_filter(\"formed_star\")\nad = ds.all_data()\nmasses = ad[\"formed_star\", \"particle_mass\"].in_units(\"Msun\")\nformation_time = ad[\"formed_star\", \"creation_time\"].in_units(\"yr\")\n\ntime_range = [0, 5e8]  # years\nn_bins = 1000\nhist, bins = np.histogram(\n    formation_time,\n    bins=n_bins,\n    range=time_range,\n)\ninds = np.digitize(formation_time, bins=bins)\ntime = (bins[:-1] + bins[1:]) / 2\n\nsfr = np.array(\n    [masses[inds == j + 1].sum() / (bins[j + 1] - bins[j]) for j in range(len(time))]\n)\nsfr[sfr == 0] = np.nan\n\nplt.plot(time / 1e6, sfr)\nplt.xlabel(\"Time  [Myr]\")\nplt.ylabel(r\"SFR  [M$_\\odot$ yr$^{-1}$]\")\nplt.savefig(\"filter_sfr.png\")\n"
  },
  {
    "path": "doc/source/cookbook/particle_one_color_plot.py",
    "content": "import yt\n\n# load the dataset\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# create our plot\np = yt.ParticlePlot(\n    ds, (\"all\", \"particle_position_x\"), (\"all\", \"particle_position_y\"), color=\"b\"\n)\n\n# zoom in a little bit\np.set_width(500, \"kpc\")\n\n# save result\np.save()\n"
  },
  {
    "path": "doc/source/cookbook/particle_xvz_plot.py",
    "content": "import yt\n\n# load the dataset\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# create our plot\np = yt.ParticlePlot(\n    ds,\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_velocity_z\"),\n    [(\"all\", \"particle_mass\")],\n)\n\n# pick some appropriate units\np.set_unit((\"all\", \"particle_position_x\"), \"Mpc\")\np.set_unit((\"all\", \"particle_velocity_z\"), \"km/s\")\np.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n\n# save result\np.save()\n"
  },
  {
    "path": "doc/source/cookbook/particle_xy_plot.py",
    "content": "import yt\n\n# load the dataset\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# create our plot\np = yt.ParticlePlot(\n    ds,\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_position_y\"),\n    (\"all\", \"particle_mass\"),\n    width=(0.5, 0.5),\n)\n\n# pick some appropriate units\np.set_axes_unit(\"kpc\")\np.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n\n# save result\np.save()\n"
  },
  {
    "path": "doc/source/cookbook/power_spectrum_example.py",
    "content": "import matplotlib.pyplot as plt\nimport numpy as np\n\nimport yt\n\n\"\"\"\nMake a turbulent KE power spectrum.  Since we are stratified, we use\na rho**(1/3) scaling to the velocity to get something that would\nlook Kolmogorov (if the turbulence were fully developed).\n\nUltimately, we aim to compute:\n\n                      1  ^      ^*\n     E(k) = integral  -  V(k) . V(k) dS\n                      2\n\n             n                                               ^\nwhere V = rho  U is the density-weighted velocity field, and V is the\nFFT of V.\n\n(Note: sometimes we normalize by 1/volume to get a spectral\nenergy density spectrum).\n\n\n\"\"\"\n\n\ndef doit(ds):\n    # a FFT operates on uniformly gridded data.  We'll use the yt\n    # covering grid for this.\n\n    max_level = ds.index.max_level\n\n    ref = int(np.prod(ds.ref_factors[0:max_level]))\n\n    low = ds.domain_left_edge\n    dims = ds.domain_dimensions * ref\n\n    nx, ny, nz = dims\n\n    nindex_rho = 1.0 / 3.0\n\n    Kk = np.zeros((nx // 2 + 1, ny // 2 + 1, nz // 2 + 1))\n\n    for vel in [(\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"), (\"gas\", \"velocity_z\")]:\n        Kk += 0.5 * fft_comp(\n            ds, (\"gas\", \"density\"), vel, nindex_rho, max_level, low, dims\n        )\n\n    # wavenumbers\n    L = (ds.domain_right_edge - ds.domain_left_edge).d\n\n    kx = np.fft.rfftfreq(nx) * nx / L[0]\n    ky = np.fft.rfftfreq(ny) * ny / L[1]\n    kz = np.fft.rfftfreq(nz) * nz / L[2]\n\n    # physical limits to the wavenumbers\n    kmin = np.min(1.0 / L)\n    kmax = np.min(0.5 * dims / L)\n\n    kbins = np.arange(kmin, kmax, kmin)\n    N = len(kbins)\n\n    # bin the Fourier KE into radial kbins\n    kx3d, ky3d, kz3d = np.meshgrid(kx, ky, kz, indexing=\"ij\")\n    k = np.sqrt(kx3d**2 + ky3d**2 + kz3d**2)\n\n    whichbin = np.digitize(k.flat, kbins)\n    ncount = np.bincount(whichbin)\n\n    E_spectrum = np.zeros(len(ncount) - 1)\n\n    for n in range(1, len(ncount)):\n        E_spectrum[n - 1] = np.sum(Kk.flat[whichbin == n])\n\n    k = 0.5 * (kbins[0 : N - 1] + kbins[1:N])\n    E_spectrum = E_spectrum[1:N]\n\n    index = np.argmax(E_spectrum)\n    kmax = k[index]\n    Emax = E_spectrum[index]\n\n    plt.loglog(k, E_spectrum)\n    plt.loglog(k, Emax * (k / kmax) ** (-5.0 / 3.0), ls=\":\", color=\"0.5\")\n\n    plt.xlabel(r\"$k$\")\n    plt.ylabel(r\"$E(k)dk$\")\n\n    plt.savefig(\"spectrum.png\")\n\n\ndef fft_comp(ds, irho, iu, nindex_rho, level, low, delta):\n    cube = ds.covering_grid(level, left_edge=low, dims=delta, fields=[irho, iu])\n\n    rho = cube[irho].d\n    u = cube[iu].d\n\n    nx, ny, nz = rho.shape\n\n    # do the FFTs -- note that since our data is real, there will be\n    # too much information here.  fftn puts the positive freq terms in\n    # the first half of the axes -- that's what we keep.  Our\n    # normalization has an '8' to account for this clipping to one\n    # octant.\n    ru = np.fft.fftn(rho**nindex_rho * u)[\n        0 : nx // 2 + 1, 0 : ny // 2 + 1, 0 : nz // 2 + 1\n    ]\n    ru = 8.0 * ru / (nx * ny * nz)\n\n    return np.abs(ru) ** 2\n\n\nds = yt.load(\"maestro_xrb_lores_23437\")\ndoit(ds)\n"
  },
  {
    "path": "doc/source/cookbook/profile_with_standard_deviation.py",
    "content": "import matplotlib.pyplot as plt\n\nimport yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create a sphere of radius 1 Mpc centered on the max density location.\nsp = ds.sphere(\"max\", (1, \"Mpc\"))\n\n# Calculate and store the bulk velocity for the sphere.\nbulk_velocity = sp.quantities.bulk_velocity()\nsp.set_field_parameter(\"bulk_velocity\", bulk_velocity)\n\n# Create a 1D profile object for profiles over radius\n# and add a velocity profile.\nprof = yt.create_profile(\n    sp,\n    \"radius\",\n    (\"gas\", \"velocity_magnitude\"),\n    units={\"radius\": \"kpc\"},\n    extrema={\"radius\": ((0.1, \"kpc\"), (1000.0, \"kpc\"))},\n    weight_field=(\"gas\", \"mass\"),\n)\n\n# Create arrays to plot.\nradius = prof.x\nmean = prof[\"gas\", \"velocity_magnitude\"]\nstd = prof.standard_deviation[\"gas\", \"velocity_magnitude\"]\n\n# Plot the average velocity magnitude.\nplt.loglog(radius, mean, label=\"Mean\")\n# Plot the standard deviation of the velocity magnitude.\nplt.loglog(radius, std, label=\"Standard Deviation\")\nplt.xlabel(\"r [kpc]\")\nplt.ylabel(\"v [cm/s]\")\nplt.legend()\n\nplt.savefig(\"velocity_profiles.png\")\n"
  },
  {
    "path": "doc/source/cookbook/rad_velocity.py",
    "content": "import matplotlib.pyplot as plt\n\nimport yt\n\nds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n\n# Get the first sphere\nsp0 = ds.sphere(ds.domain_center, (500.0, \"kpc\"))\n\n# Compute the bulk velocity from the cells in this sphere\nbulk_vel = sp0.quantities.bulk_velocity()\n\n\n# Get the second sphere\nsp1 = ds.sphere(ds.domain_center, (500.0, \"kpc\"))\n\n# Set the bulk velocity field parameter\nsp1.set_field_parameter(\"bulk_velocity\", bulk_vel)\n\n# Radial profile without correction\n\nrp0 = yt.create_profile(\n    sp0,\n    (\"index\", \"radius\"),\n    (\"gas\", \"radial_velocity\"),\n    units={(\"index\", \"radius\"): \"kpc\"},\n    logs={(\"index\", \"radius\"): False},\n)\n\n# Radial profile with correction for bulk velocity\n\nrp1 = yt.create_profile(\n    sp1,\n    (\"index\", \"radius\"),\n    (\"gas\", \"radial_velocity\"),\n    units={(\"index\", \"radius\"): \"kpc\"},\n    logs={(\"index\", \"radius\"): False},\n)\n\n# Make a plot using matplotlib\n\nfig = plt.figure()\nax = fig.add_subplot(111)\n\nax.plot(\n    rp0.x.value,\n    rp0[\"gas\", \"radial_velocity\"].in_units(\"km/s\").value,\n    rp1.x.value,\n    rp1[\"gas\", \"radial_velocity\"].in_units(\"km/s\").value,\n)\n\nax.set_xlabel(r\"$\\mathrm{r\\ (kpc)}$\")\nax.set_ylabel(r\"$\\mathrm{v_r\\ (km/s)}$\")\nax.legend([\"Without Correction\", \"With Correction\"])\n\nfig.savefig(f\"{ds}_profiles.png\")\n"
  },
  {
    "path": "doc/source/cookbook/radial_profile_styles.py",
    "content": "import matplotlib.pyplot as plt\n\nimport yt\n\nds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n\n# Get a sphere object\n\nsp = ds.sphere(ds.domain_center, (500.0, \"kpc\"))\n\n# Bin up the data from the sphere into a radial profile\n\nrp = yt.create_profile(\n    sp,\n    \"radius\",\n    [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n    units={\"radius\": \"kpc\"},\n    logs={\"radius\": False},\n)\n\n# Make plots using matplotlib\n\nfig = plt.figure()\nax = fig.add_subplot(111)\n\n# Plot the density as a log-log plot using the default settings\ndens_plot = ax.loglog(rp.x.value, rp[\"gas\", \"density\"].value)\n\n# Here we set the labels of the plot axes\n\nax.set_xlabel(r\"$\\mathrm{r\\ (kpc)}$\")\nax.set_ylabel(r\"$\\mathrm{\\rho\\ (g\\ cm^{-3})}$\")\n\n# Save the default plot\n\nfig.savefig(\"density_profile_default.png\" % ds)\n\n# The \"dens_plot\" object is a list of plot objects. In our case we only have one,\n# so we index the list by '0' to get it.\n\n# Plot using dashed red lines\n\ndens_plot[0].set_linestyle(\"--\")\ndens_plot[0].set_color(\"red\")\n\nfig.savefig(\"density_profile_dashed_red.png\")\n\n# Increase the line width and add points in the shape of x's\n\ndens_plot[0].set_linewidth(5)\ndens_plot[0].set_marker(\"x\")\ndens_plot[0].set_markersize(10)\n\nfig.savefig(\"density_profile_thick_with_xs.png\")\n"
  },
  {
    "path": "doc/source/cookbook/render_two_fields.py",
    "content": "import yt\nfrom yt.visualization.volume_rendering.api import Scene, create_volume_source\n\nfilePath = \"Sedov_3d/sedov_hdf5_chk_0003\"\nds = yt.load(filePath)\nds.force_periodicity()\n\nsc = Scene()\n\n# set up camera\ncam = sc.add_camera(ds, lens_type=\"perspective\")\ncam.resolution = [400, 400]\n\ncam.position = ds.arr([1, 1, 1], \"cm\")\ncam.switch_orientation()\n\n# add rendering of density field\ndens = create_volume_source(ds, field=(\"flash\", \"dens\"))\ndens.use_ghost_zones = True\nsc.add_source(dens)\nsc.save(\"density.png\", sigma_clip=6)\n\n# add rendering of x-velocity field\nvel = create_volume_source(ds, field=(\"flash\", \"velx\"))\nvel.use_ghost_zones = True\nsc.add_source(vel)\nsc.save(\"density_any_velocity.png\", sigma_clip=6)\n"
  },
  {
    "path": "doc/source/cookbook/render_two_fields_tf.py",
    "content": "import numpy as np\n\nimport yt\nfrom yt.visualization.volume_rendering.api import Scene, create_volume_source\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# create a scene and add volume sources to it\n\nsc = Scene()\n\n# Add density\n\nfield = \"density\"\n\nvol = create_volume_source(ds, field=field)\nvol.use_ghost_zones = True\n\ntf = yt.ColorTransferFunction([-28, -25])\ntf.clear()\ntf.add_layers(4, 0.02, alpha=np.logspace(-3, -1, 4), colormap=\"winter\")\n\nvol.set_transfer_function(tf)\nsc.add_source(vol)\n\n# Add temperature\n\nfield = \"temperature\"\n\nvol2 = create_volume_source(ds, field=field)\nvol2.use_ghost_zones = True\n\ntf = yt.ColorTransferFunction([4.5, 7.5])\ntf.clear()\ntf.add_layers(4, 0.02, alpha=np.logspace(-0.2, 0, 4), colormap=\"autumn\")\n\nvol2.set_transfer_function(tf)\nsc.add_source(vol2)\n\n# setup the camera\n\ncam = sc.add_camera(ds, lens_type=\"perspective\")\ncam.resolution = (1600, 900)\ncam.zoom(20.0)\n\n# Render the image.\n\nsc.render()\n\nsc.save_annotated(\n    \"render_two_fields_tf.png\",\n    sigma_clip=6.0,\n    tf_rect=[0.88, 0.15, 0.03, 0.8],\n    render=False,\n)\n"
  },
  {
    "path": "doc/source/cookbook/rendering_with_box_and_grids.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"Enzo_64/DD0043/data0043\")\nsc = yt.create_scene(ds, (\"gas\", \"density\"))\n\n# You may need to adjust the alpha values to get a rendering with good contrast\n# For annotate_domain, the fourth color value is alpha.\n\n# Draw the domain boundary\nsc.annotate_domain(ds, color=[1, 1, 1, 0.01])\nsc.save(f\"{ds}_vr_domain.png\", sigma_clip=4)\n\n# Draw the grid boundaries\nsc.annotate_grids(ds, alpha=0.01)\nsc.save(f\"{ds}_vr_grids.png\", sigma_clip=4)\n\n# Draw a coordinate axes triad\nsc.annotate_axes(alpha=0.01)\nsc.save(f\"{ds}_vr_coords.png\", sigma_clip=4)\n"
  },
  {
    "path": "doc/source/cookbook/show_hide_axes_colorbar.py",
    "content": "import yt\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\nslc = yt.SlicePlot(ds, \"x\", (\"gas\", \"density\"))\nslc.save(\"default_sliceplot.png\")\n\nslc.hide_axes()\nslc.save(\"no_axes_sliceplot.png\")\n\nslc.hide_colorbar()\nslc.save(\"no_axes_no_colorbar_sliceplot.png\")\n\nslc.show_axes()\nslc.save(\"no_colorbar_sliceplot.png\")\n"
  },
  {
    "path": "doc/source/cookbook/sigma_clip.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"enzo_tiny_cosmology/RD0009/RD0009\")\n\n# Create a volume rendering, which will determine data bounds, use the first\n# acceptable field in the field_list, and set up a default transfer function.\n\n# Render and save output images with different levels of sigma clipping.\n# Sigma clipping removes the highest intensity pixels in a volume render,\n# which affects the overall contrast of the image.\nsc = yt.create_scene(ds, field=(\"gas\", \"density\"))\nsc.save(\"clip_0.png\")\nsc.save(\"clip_2.png\", sigma_clip=2)\nsc.save(\"clip_4.png\", sigma_clip=4)\nsc.save(\"clip_6.png\", sigma_clip=6)\n"
  },
  {
    "path": "doc/source/cookbook/simple_1d_line_plot.py",
    "content": "import yt\n\n# Load the dataset\nds = yt.load(\"SecondOrderTris/RZ_p_no_parts_do_nothing_bcs_cone_out.e\", step=-1)\n\n# Create a line plot of the variables 'u' and 'v' with 1000 sampling points evenly\n# spaced between the coordinates (0, 0, 0) and (0, 1, 0)\nplot = yt.LinePlot(\n    ds, [(\"all\", \"v\"), (\"all\", \"u\")], (0.0, 0.0, 0.0), (0.0, 1.0, 0.0), 1000\n)\n\n# Add a legend\nplot.annotate_legend((\"all\", \"v\"))\n\n# Save the line plot\nplot.save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_contour_in_slice.py",
    "content": "import yt\n\n# Load the data file.\nds = yt.load(\"Sedov_3d/sedov_hdf5_chk_0002\")\n\n# Make a traditional slice plot.\nsp = yt.SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n\n# Overlay the slice plot with thick red contours of density.\nsp.annotate_contour(\n    (\"gas\", \"density\"),\n    levels=3,\n    clim=(1e-2, 1e-1),\n    label=True,\n    plot_args={\"colors\": \"red\", \"linewidths\": 2},\n)\n\n# What about some nice temperature contours in blue?\nsp.annotate_contour(\n    (\"gas\", \"temperature\"),\n    levels=3,\n    clim=(1e-8, 1e-6),\n    label=True,\n    plot_args={\"colors\": \"blue\", \"linewidths\": 2},\n)\n\n# This is the plot object.\npo = sp.plots[\"gas\", \"density\"]\n\n# Turn off the colormap image, leaving just the contours.\npo.axes.images[0].set_visible(False)\n\n# Remove the colorbar and its label.\npo.figure.delaxes(po.figure.axes[1])\n\n# Save it and ask for a close fit to get rid of the space used by the colorbar.\nsp.save(mpl_kwargs={\"bbox_inches\": \"tight\"})\n"
  },
  {
    "path": "doc/source/cookbook/simple_off_axis_projection.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create a 15 kpc radius sphere, centered on the center of the sim volume\nsp = ds.sphere(\"center\", (15.0, \"kpc\"))\n\n# Get the angular momentum vector for the sphere.\nL = sp.quantities.angular_momentum_vector()\n\nprint(f\"Angular momentum vector: {L}\")\n\n# Create an off-axis ProjectionPlot of density centered on the object with the L\n# vector as its normal and a width of 25 kpc on a side\np = yt.ProjectionPlot(\n    ds, L, fields=(\"gas\", \"density\"), center=sp.center, width=(25, \"kpc\")\n)\np.save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_off_axis_slice.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create a 15 kpc radius sphere, centered on the center of the sim volume\nsp = ds.sphere(\"center\", (15.0, \"kpc\"))\n\n# Get the angular momentum vector for the sphere.\nL = sp.quantities.angular_momentum_vector()\n\nprint(f\"Angular momentum vector: {L}\")\n\n# Create an OffAxisSlicePlot of density centered on the object with the L\n# vector as its normal and a width of 25 kpc on a side\np = yt.OffAxisSlicePlot(ds, L, (\"gas\", \"density\"), sp.center, (25, \"kpc\"))\np.save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_pdf.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"GalaxyClusterMerger/fiducial_1to3_b0.273d_hdf5_plt_cnt_0175\")\n\n# Create a data object that represents the whole box.\nad = ds.all_data()\n\n# This is identical to the simple phase plot, except we supply\n# the fractional=True keyword to divide the profile data by the sum.\nplot = yt.PhasePlot(\n    ad,\n    (\"gas\", \"density\"),\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"mass\"),\n    weight_field=None,\n    fractional=True,\n)\n\n# Save the image.\n# Optionally, give a string as an argument\n# to name files with a keyword.\nplot.save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_phase.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create a sphere of radius 100 kpc in the center of the domain.\nmy_sphere = ds.sphere(\"c\", (100.0, \"kpc\"))\n\n# Create a PhasePlot object.\n# Setting weight to None will calculate a sum.\n# Setting weight to a field will calculate an average\n# weighted by that field.\nplot = yt.PhasePlot(\n    my_sphere,\n    (\"gas\", \"density\"),\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"mass\"),\n    weight_field=None,\n)\n\n# Set the units of mass to be in solar masses (not the default in cgs)\nplot.set_unit((\"gas\", \"mass\"), \"Msun\")\n\n# Save the image.\n# Optionally, give a string as an argument\n# to name files with a keyword.\nplot.save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_plots.rst",
    "content": "Making Simple Plots\n-------------------\n\nOne of the easiest ways to interact with yt is by creating simple\nvisualizations of your data.  Below we show how to do this, as well as how to\nextend these plots to be ready for publication.\n\nSimple Slices\n~~~~~~~~~~~~~\n\nThis script shows the simplest way to make a slice through a dataset.  See\n:ref:`slice-plots` for more information.\n\n.. yt_cookbook:: simple_slice.py\n\nSimple Projections (Non-Weighted)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis is the simplest way to make a projection through a dataset.  There are\nseveral different :ref:`projection-types`, but non-weighted line integrals\nand weighted line integrals are the two most common.  Here we create\ndensity projections (non-weighted line integral).\nSee :ref:`projection-plots` for more information.\n\n.. yt_cookbook:: simple_projection.py\n\nSimple Projections (Weighted)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAnd here we produce density-weighted temperature projections (weighted line\nintegral) for the same dataset as the non-weighted projections above.\nSee :ref:`projection-plots` for more information.\n\n.. yt_cookbook:: simple_projection_weighted.py\n\nSimple Projections (Methods)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAnd here we illustrate different methods for projection plots (integrate,\nminimum, maximum).\n\n.. yt_cookbook:: simple_projection_methods.py\n\nSimple Projections (Weighted Standard Deviation)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAnd here we produce a density-weighted projection (weighted line integral)\nof the line-of-sight velocity from the same dataset (see :ref:`projection-plots`\nfor more information).\n\n.. yt_cookbook:: simple_projection_stddev.py\n\nSimple Phase Plots\n~~~~~~~~~~~~~~~~~~\n\nThis demonstrates how to make a phase plot.  Phase plots can be thought of as\ntwo-dimensional histograms, where the value is either the weighted-average or\nthe total accumulation in a cell.\nSee :ref:`how-to-make-2d-profiles` for more information.\n\n.. yt_cookbook:: simple_phase.py\n\nSimple 1D Line Plotting\n~~~~~~~~~~~~~~~~~~~~~~~\n\nThis script shows how to make a ``LinePlot`` through a dataset.\nSee :ref:`manual-line-plots` for more information.\n\n.. yt_cookbook:: simple_1d_line_plot.py\n\n.. note:: Not every data types have support for ``yt.LinePlot`` yet.\n   Currently, this operation is supported for grid based data with cartesian geometry.\n\nSimple Probability Distribution Functions\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nOften, one wants to examine the distribution of one variable as a function of\nanother.  This shows how to see the distribution of mass in a simulation, with\nrespect to the total mass in the simulation.\nSee :ref:`how-to-make-2d-profiles` for more information.\n\n.. yt_cookbook:: simple_pdf.py\n\nSimple 1D Histograms (Profiles)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis is a \"profile,\" which is a 1D histogram.  This can be thought of as either\nthe total accumulation (when weight_field is set to ``None``) or the average\n(when a weight_field is supplied.)\nSee :ref:`how-to-make-1d-profiles` for more information.\n\n.. yt_cookbook:: simple_profile.py\n\nSimple Radial Profiles\n~~~~~~~~~~~~~~~~~~~~~~\n\nThis shows how to make a profile of a quantity with respect to the radius.\nSee :ref:`how-to-make-1d-profiles` for more information.\n\n.. yt_cookbook:: simple_radial_profile.py\n\n1D Profiles Over Time\n~~~~~~~~~~~~~~~~~~~~~\n\nThis is a simple example of overplotting multiple 1D profiles from a number\nof datasets to show how they evolve over time.\nSee :ref:`how-to-make-1d-profiles` for more information.\n\n.. yt_cookbook:: time_series_profiles.py\n\n.. _cookbook-profile-stddev:\n\nProfiles with Standard Deviation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis shows how to plot a 1D profile with error bars indicating the standard\ndeviation of the field values in each profile bin.  In this example, we manually\ncreate a 1D profile object, which gives us access to the standard deviation\ndata.  See :ref:`how-to-make-1d-profiles` for more information.\n\n.. yt_cookbook:: profile_with_standard_deviation.py\n\nMaking Plots of Multiple Fields Simultaneously\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nBy adding multiple fields to a single\n:class:`~yt.visualization.plot_window.SlicePlot` or\n:class:`~yt.visualization.plot_window.ProjectionPlot` some of the overhead of\ncreating the data object can be reduced, and better performance squeezed out.\nThis recipe shows how to add multiple fields to a single plot.\nSee :ref:`slice-plots` and :ref:`projection-plots` for more information.\n\n.. yt_cookbook:: simple_slice_with_multiple_fields.py\n\nOff-Axis Slicing\n~~~~~~~~~~~~~~~~\n\nOne can create slices from any arbitrary angle, not just those aligned with\nthe x,y,z axes.\nSee :ref:`off-axis-slices` for more information.\n\n.. yt_cookbook:: simple_off_axis_slice.py\n\n.. _cookbook-simple-off-axis-projection:\n\nOff-Axis Projection\n~~~~~~~~~~~~~~~~~~~\n\nLike off-axis slices, off-axis projections can be created from any arbitrary\nviewing angle.\nSee :ref:`off-axis-projections` for more information.\n\n.. yt_cookbook:: simple_off_axis_projection.py\n\n.. _cookbook-simple-particle-plot:\n\nSimple Particle Plot\n~~~~~~~~~~~~~~~~~~~~\n\nYou can also use yt to make particle-only plots. This script shows how to\nplot all the particle x and y positions in a dataset, using the particle mass\nto set the color scale.\nSee :ref:`particle-plots` for more information.\n\n.. yt_cookbook:: particle_xy_plot.py\n\n.. _cookbook-non-spatial-particle-plot:\n\nNon-spatial Particle Plots\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou are not limited to plotting spatial fields on the x and y axes. This\nexample shows how to plot the particle x-coordinates versus their z-velocities,\nagain using the particle mass to set the colorbar.\nSee :ref:`particle-plots` for more information.\n\n.. yt_cookbook:: particle_xvz_plot.py\n\n.. _cookbook-single-color-particle-plot:\n\nSingle-color Particle Plots\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf you don't want to display a third field on the color bar axis, simply pass\nin a color string instead of a particle field.\nSee :ref:`particle-plots` for more information.\n\n.. yt_cookbook:: particle_one_color_plot.py\n\n.. _cookbook-simple_volume_rendering:\n\nSimple Volume Rendering\n~~~~~~~~~~~~~~~~~~~~~~~\n\nVolume renderings are 3D projections rendering isocontours in any arbitrary\nfield (e.g. density, temperature, pressure, etc.)\nSee :ref:`volume_rendering` for more information.\n\n.. yt_cookbook:: simple_volume_rendering.py\n\n.. _show-hide-axes-colorbar:\n\nShowing and Hiding Axis Labels and Colorbars\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis example illustrates how to create a SlicePlot and then suppress the axes\nlabels and colorbars.  This is useful when you don't care about the physical\nscales and just want to take a closer look at the raw plot data.  See\n:ref:`hiding-colorbar-and-axes` for more information.\n\n.. yt_cookbook:: show_hide_axes_colorbar.py\n\n\n.. _cookbook_label_formats:\n\nSetting Field Label Formats\n---------------------------\n\nThis example illustrates how to change the label format for\nion species from the default roman numeral style.\n\n.. yt_cookbook:: changing_label_formats.py\n\n\n.. _matplotlib-primitives:\n\nAccessing and Modifying Plots Directly\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhile often the Plot Window, and its affiliated :ref:`callbacks` can\ncover normal use cases, sometimes more direct access to the underlying\nMatplotlib engine is necessary.  This recipe shows how to modify the plot\nwindow :class:`matplotlib.axes.Axes` object directly.\nSee :ref:`matplotlib-customization` for more information.\n\n.. yt_cookbook:: simple_slice_matplotlib_example.py\n\nChanging the Colormap used in a Plot\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nyt has sensible defaults for colormaps, but there are over a hundred available\nfor customizing your plots.  Here we generate a projection and then change\nits colormap.  See :ref:`colormaps` for a list and for images of all the\navailable colormaps.\n\n.. yt_cookbook:: colormaps.py\n\nImage Background Colors\n~~~~~~~~~~~~~~~~~~~~~~~\n\nHere we see how to take an image and save it using different background colors.\n\nIn this case we use the :ref:`cookbook-simple_volume_rendering`\nrecipe to generate the image, but it works for any NxNx4 image array\n(3 colors and 1 opacity channel).  See :ref:`volume_rendering` for more\ninformation.\n\n.. yt_cookbook:: image_background_colors.py\n\n.. _annotations-recipe:\n\nAnnotating Plots to Include Lines, Text, Shapes, etc.\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIt can be useful to add annotations to plots to show off certain features\nand make it easier for your audience to understand the plot's purpose.  There\nare a variety of available :ref:`plot modifications <callbacks>` one can use\nto add annotations to their plots.  Below includes just a handful, but please\nlook at the other :ref:`plot modifications <callbacks>` to get a full\ndescription of what you can do to highlight your figures.\n\n.. yt_cookbook:: annotations.py\n\nAnnotating Plots with a Timestamp and Physical Scale\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhen creating movies of multiple outputs from the same simulation (see :ref:`time-series-analysis`), it can be helpful to include a timestamp and the physical scale of each individual output.  This is simply achieved using the :ref:`annotate_timestamp() <annotate-timestamp>` and :ref:`annotate_scale() <annotate-scale>` callbacks on your plots.  For more information about similar plot modifications using other callbacks, see the section on :ref:`Plot Modifications <callbacks>`.\n\n.. yt_cookbook:: annotate_timestamp_and_scale.py\n"
  },
  {
    "path": "doc/source/cookbook/simple_profile.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create a 1D profile within a sphere of radius 100 kpc\n# of the average temperature and average velocity_x\n# vs. density, weighted by mass.\nsphere = ds.sphere(\"c\", (100.0, \"kpc\"))\nplot = yt.ProfilePlot(\n    sphere,\n    (\"gas\", \"density\"),\n    [(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")],\n    weight_field=(\"gas\", \"mass\"),\n)\nplot.set_log((\"gas\", \"velocity_x\"), False)\n\n# Save the image.\n# Optionally, give a string as an argument\n# to name files with a keyword.\nplot.save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_projection.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"GalaxyClusterMerger/fiducial_1to3_b0.273d_hdf5_plt_cnt_0175\")\n\n# Create projections of the gas density (non-weighted line integrals).\n\nyt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\")).save()\nyt.ProjectionPlot(ds, \"y\", (\"gas\", \"density\")).save()\nyt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\")).save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_projection_methods.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"GalaxyClusterMerger/fiducial_1to3_b0.273d_hdf5_plt_cnt_0175\")\n\n# Create projections of temperature (with different methods)\n\n\nfor method in [\"integrate\", \"min\", \"max\"]:\n    proj = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"temperature\"), method=method)\n    proj.save(f\"projection_method_{method}.png\")\n"
  },
  {
    "path": "doc/source/cookbook/simple_projection_stddev.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"GalaxyClusterMerger/fiducial_1to3_b0.273d_hdf5_plt_cnt_0175\")\n\n# Create density-weighted projections of standard deviation of the velocity\n# (weighted line integrals)\n\nfor normal in \"xyz\":\n    yt.ProjectionPlot(\n        ds,\n        normal,\n        (\"gas\", f\"velocity_{normal}\"),\n        weight_field=(\"gas\", \"density\"),\n        moment=2,\n    ).save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_projection_weighted.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"GalaxyClusterMerger/fiducial_1to3_b0.273d_hdf5_plt_cnt_0175\")\n\n# Create density-weighted projections of temperature (weighted line integrals)\n\nfor normal in \"xyz\":\n    yt.ProjectionPlot(\n        ds, normal, (\"gas\", \"temperature\"), weight_field=(\"gas\", \"density\")\n    ).save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_radial_profile.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create a sphere of radius 100 kpc in the center of the box.\nmy_sphere = ds.sphere(\"c\", (100.0, \"kpc\"))\n\n# Create a profile of the average density vs. radius.\nplot = yt.ProfilePlot(\n    my_sphere,\n    (\"index\", \"radius\"),\n    (\"gas\", \"density\"),\n    weight_field=(\"gas\", \"mass\"),\n)\n\n# Change the units of the radius into kpc (and not the default in cgs)\nplot.set_unit((\"index\", \"radius\"), \"kpc\")\n\n# Save the image.\n# Optionally, give a string as an argument\n# to name files with a keyword.\nplot.save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_slice.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n\n# Create gas density slices in all three axes.\nyt.SlicePlot(ds, \"x\", (\"gas\", \"density\"), width=(800.0, \"kpc\")).save()\nyt.SlicePlot(ds, \"y\", (\"gas\", \"density\"), width=(800.0, \"kpc\")).save()\nyt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(800.0, \"kpc\")).save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_slice_matplotlib_example.py",
    "content": "import numpy as np\n\nimport yt\n\n# Load the dataset.\nds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n\n# Create a slice object\nslc = yt.SlicePlot(ds, \"x\", (\"gas\", \"density\"), width=(800.0, \"kpc\"))\n\n# Rendering should be performed explicitly *before* any modification is\n# performed directly with matplotlib.\nslc.render()\n\n# Get a reference to the matplotlib axes object for the plot\nax = slc.plots[\"gas\", \"density\"].axes\n\n# Let's adjust the x axis tick labels\nfor label in ax.xaxis.get_ticklabels():\n    label.set_color(\"red\")\n    label.set_fontsize(16)\n\n# Get a reference to the matplotlib figure object for the plot\nfig = slc.plots[\"gas\", \"density\"].figure\n\n# And create a mini-panel of a gaussian histogram inside the plot\nrect = (0.2, 0.2, 0.2, 0.2)\nnew_ax = fig.add_axes(rect)\n\nn, bins, patches = new_ax.hist(\n    np.random.randn(1000) + 20, 50, facecolor=\"black\", edgecolor=\"black\"\n)\n\n# Make sure its visible\nnew_ax.tick_params(colors=\"white\")\n\n# And label it\nla = new_ax.set_xlabel(\"Dinosaurs per furlong\")\nla.set_color(\"white\")\n\nslc.save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_slice_with_multiple_fields.py",
    "content": "import yt\n\n# Load the dataset\nds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n\n# Create gas density slices of several fields along the x axis simultaneously\nyt.SlicePlot(\n    ds,\n    \"x\",\n    [(\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"pressure\")],\n    width=(800.0, \"kpc\"),\n).save()\n"
  },
  {
    "path": "doc/source/cookbook/simple_volume_rendering.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n# Create a volume rendering, which will determine data bounds, use the first\n# acceptable field in the field_list, and set up a default transfer function.\n\n# This will save a file named 'data0043_Render_density.png' to disk.\nim, sc = yt.volume_render(ds, field=(\"gas\", \"density\"))\n"
  },
  {
    "path": "doc/source/cookbook/simulation_analysis.py",
    "content": "import yt\n\nyt.enable_parallelism()\n\n# Enable parallelism in the script (assuming it was called with\n# `mpirun -np <n_procs>` )\nyt.enable_parallelism()\n\n# By using wildcards such as ? and * with the load command, we can load up a\n# Time Series containing all of these datasets simultaneously.\nts = yt.load(\"enzo_tiny_cosmology/DD????/DD????\")\n\n# Calculate and store density extrema for all datasets along with redshift\n# in a data dictionary with entries as tuples\n\n# Create an empty dictionary\ndata = {}\n\n# Iterate through each dataset in the Time Series (using piter allows it\n# to happen in parallel automatically across available processors)\nfor ds in ts.piter():\n    ad = ds.all_data()\n    extrema = ad.quantities.extrema((\"gas\", \"density\"))\n\n    # Fill the dictionary with extrema and redshift information for each dataset\n    data[ds.basename] = (extrema, ds.current_redshift)\n\n# Sort dict by keys\ndata = {k: v for k, v in sorted(data.items())}\n\n# Print out all the values we calculated.\nprint(\"Dataset      Redshift        Density Min      Density Max\")\nprint(\"---------------------------------------------------------\")\nfor key, val in data.items():\n    print(\n        \"%s       %05.3f          %5.3g g/cm^3   %5.3g g/cm^3\"\n        % (key, val[1], val[0][0], val[0][1])\n    )\n"
  },
  {
    "path": "doc/source/cookbook/streamlines.py",
    "content": "import matplotlib.pyplot as plt\nimport numpy as np\nfrom mpl_toolkits.mplot3d import Axes3D\n\nimport yt\nfrom yt.units import Mpc\nfrom yt.visualization.api import Streamlines\n\n# Load the dataset\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Define c: the center of the box, N: the number of streamlines,\n# scale: the spatial scale of the streamlines relative to the boxsize,\n# and then pos: the random positions of the streamlines.\nc = ds.domain_center\nN = 100\nscale = ds.domain_width[0]\npos_dx = np.random.random((N, 3)) * scale - scale / 2.0\npos = c + pos_dx\n\n# Create streamlines of the 3D vector velocity and integrate them through\n# the box defined above\nstreamlines = Streamlines(\n    ds,\n    pos,\n    (\"gas\", \"velocity_x\"),\n    (\"gas\", \"velocity_y\"),\n    (\"gas\", \"velocity_z\"),\n    length=1.0 * Mpc,\n    get_magnitude=True,\n)\nstreamlines.integrate_through_volume()\n\n# Create a 3D plot, trace the streamlines through the 3D volume of the plot\nfig = plt.figure()\nax = Axes3D(fig, auto_add_to_figure=False)\nfig.add_axes(ax)\n\nfor stream in streamlines.streamlines:\n    stream = stream[np.all(stream != 0.0, axis=1)]\n    ax.plot3D(stream[:, 0], stream[:, 1], stream[:, 2], alpha=0.1)\n\n# Save the plot to disk.\nplt.savefig(\"streamlines.png\")\n"
  },
  {
    "path": "doc/source/cookbook/streamlines_isocontour.py",
    "content": "import matplotlib.pyplot as plt\nimport numpy as np\nfrom mpl_toolkits.mplot3d import Axes3D\nfrom mpl_toolkits.mplot3d.art3d import Poly3DCollection\n\nimport yt\nfrom yt.visualization.api import Streamlines\n\n# Load the dataset\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Define c: the center of the box, N: the number of streamlines,\n# scale: the spatial scale of the streamlines relative to the boxsize,\n# and then pos: the random positions of the streamlines.\nc = ds.arr([0.5] * 3, \"code_length\")\nN = 30\nscale = ds.quan(15, \"kpc\").in_units(\"code_length\")  # 15 kpc in code units\npos_dx = np.random.random((N, 3)) * scale - scale / 2.0\npos = c + pos_dx\n\n# Create the streamlines from these positions with the velocity fields as the\n# fields to be traced\nstreamlines = Streamlines(\n    ds,\n    pos,\n    (\"gas\", \"velocity_x\"),\n    (\"gas\", \"velocity_y\"),\n    (\"gas\", \"velocity_z\"),\n    length=1.0,\n)\nstreamlines.integrate_through_volume()\n\n# Create a 3D matplotlib figure for visualizing the streamlines\nfig = plt.figure()\nax = Axes3D(fig, auto_add_to_figure=False)\nfig.add_axes(ax)\n\n# Trace the streamlines through the volume of the 3D figure\nfor stream in streamlines.streamlines:\n    stream = stream[np.all(stream != 0.0, axis=1)]\n\n    # Make the colors of each stream vary continuously from blue to red\n    # from low-x to high-x of the stream start position (each color is R, G, B)\n    # can omit and just set streamline colors to a fixed color\n    x_start_pos = ds.arr(stream[0, 0], \"code_length\")\n    x_start_pos -= ds.arr(0.5, \"code_length\")\n    x_start_pos /= scale\n    x_start_pos += 0.5\n    color = np.array([x_start_pos, 0, 1 - x_start_pos])\n\n    # Plot the stream in 3D\n    ax.plot3D(stream[:, 0], stream[:, 1], stream[:, 2], alpha=0.3, color=color)\n\n# Create a sphere object centered on the highest density point in the simulation\n# with radius = 1 Mpc\nsphere = ds.sphere(\"max\", (1.0, \"Mpc\"))\n\n# Identify the isodensity surface in this sphere with density = 1e-24 g/cm^3\nsurface = ds.surface(sphere, (\"gas\", \"density\"), 1e-24)\n\n# Color this isodensity surface according to the log of the temperature field\ncolors = yt.apply_colormap(np.log10(surface[\"gas\", \"temperature\"]), cmap_name=\"hot\")\n\n# Render this surface\np3dc = Poly3DCollection(surface.triangles, linewidth=0.0)\ncolors = colors[0, :, :] / 255.0  # scale to [0,1]\ncolors[:, 3] = 0.3  # alpha = 0.3\np3dc.set_facecolors(colors)\nax.add_collection(p3dc)\n\n# Save the figure\nplt.savefig(\"streamlines_isocontour.png\")\n"
  },
  {
    "path": "doc/source/cookbook/sum_mass_in_sphere.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"Enzo_64/DD0029/data0029\")\n\n# Create a 1 Mpc radius sphere, centered on the max density.\nsp = ds.sphere(\"max\", (1.0, \"Mpc\"))\n\n# Use the total_quantity derived quantity to sum up the\n# values of the mass and particle_mass fields\n# within the sphere.\nbaryon_mass, particle_mass = sp.quantities.total_quantity(\n    [(\"gas\", \"mass\"), (\"all\", \"particle_mass\")]\n)\n\nprint(\n    \"Total mass in sphere is %0.3e Msun (gas = %0.3e Msun, particles = %0.3e Msun)\"\n    % (\n        (baryon_mass + particle_mass).in_units(\"Msun\"),\n        baryon_mass.in_units(\"Msun\"),\n        particle_mass.in_units(\"Msun\"),\n    )\n)\n"
  },
  {
    "path": "doc/source/cookbook/surface_plot.py",
    "content": "import matplotlib.pyplot as plt\nimport numpy as np\nfrom mpl_toolkits.mplot3d import Axes3D  # noqa: F401\nfrom mpl_toolkits.mplot3d.art3d import Poly3DCollection\n\nimport yt\n\n# Load the dataset\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create a sphere object centered on the highest density point in the simulation\n# with radius 1 Mpc\nsphere = ds.sphere(\"max\", (1.0, \"Mpc\"))\n\n# Identify the isodensity surface in this sphere with density = 1e-24 g/cm^3\nsurface = ds.surface(sphere, (\"gas\", \"density\"), 1e-24)\n\n# Color this isodensity surface according to the log of the temperature field\ncolors = yt.apply_colormap(np.log10(surface[\"gas\", \"temperature\"]), cmap_name=\"hot\")\n\n# Create a 3D matplotlib figure for visualizing the surface\nfig = plt.figure()\nax = fig.add_subplot(projection=\"3d\")\np3dc = Poly3DCollection(surface.triangles, linewidth=0.0)\n\n# Set the surface colors in the right scaling [0,1]\np3dc.set_facecolors(colors[0, :, :] / 255.0)\nax.add_collection(p3dc)\n\n# Let's keep the axis ratio fixed in all directions by taking the maximum\n# extent in one dimension and make it the bounds in all dimensions\nmax_extent = (surface.vertices.max(axis=1) - surface.vertices.min(axis=1)).max()\ncenters = (surface.vertices.max(axis=1) + surface.vertices.min(axis=1)) / 2\nbounds = np.zeros([3, 2])\nbounds[:, 0] = centers[:] - max_extent / 2\nbounds[:, 1] = centers[:] + max_extent / 2\nax.auto_scale_xyz(bounds[0, :], bounds[1, :], bounds[2, :])\n\n# Save the figure\nplt.savefig(f\"{ds}_Surface.png\")\n"
  },
  {
    "path": "doc/source/cookbook/tests/test_cookbook.py",
    "content": "\"\"\"Module for cookbook testing\n\n\nThis test should be run from main yt directory.\n\nExample:\n\n      $ sed -e '/where/d' -i nose.cfg setup.cfg\n      $ nosetests doc/source/cookbook/tests/test_cookbook.py -P -v\n\"\"\"\n\nimport subprocess\nimport sys\n\nfrom pathlib import Path\n\n\nBLACKLIST = [\n    \"matplotlib-animation.py\",\n]\n\n\ndef test_recipe():\n    \"\"\"Dummy test grabbing all cookbook's recipes\"\"\"\n    COOKBOOK_DIR = Path(\"doc\", \"source\", \"cookbook\")\n    for fname in sorted(COOKBOOK_DIR.glob(\"*.py\")):\n        if fname.name in BLACKLIST:\n            continue\n        check_recipe.description = f\"Testing recipe: {fname.name}\"\n        yield check_recipe, [\"python\", str(fname)]\n\n\ndef check_recipe(cmd):\n    \"\"\"Run single recipe\"\"\"\n    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    out, err = proc.communicate()\n    if out:\n        sys.stdout.write(out.decode(\"utf8\"))\n    if err:\n        sys.stderr.write(err.decode(\"utf8\"))\n\n    if proc.returncode != 0:\n        retstderr = \" \".join(cmd)\n        retstderr += \"\\n\\nTHIS IS THE REAL CAUSE OF THE FAILURE:\\n\"\n        retstderr += err.decode(\"UTF-8\") + \"\\n\"\n        raise subprocess.CalledProcessError(proc.returncode, retstderr)\n"
  },
  {
    "path": "doc/source/cookbook/thin_slice_projection.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"Enzo_64/DD0030/data0030\")\n\n# Make a projection that is the full width of the domain,\n# but only 5 Mpc in depth.  This is done by creating a\n# region object with this exact geometry and providing it\n# as a data_source for the projection.\n\n# Center on the domain center\ncenter = ds.domain_center.copy()\n\n# First make the left and right corner of the region based\n# on the full domain.\nleft_corner = ds.domain_left_edge.copy()\nright_corner = ds.domain_right_edge.copy()\n\n# Now adjust the size of the region along the line of sight (x axis).\ndepth = ds.quan(5.0, \"Mpc\")\nleft_corner[0] = center[0] - 0.5 * depth\nright_corner[0] = center[0] + 0.5 * depth\n\n# Create the region\nregion = ds.box(left_corner, right_corner)\n\n# Create a density projection and supply the region we have just created.\n# Only cells within the region will be included in the projection.\n# Try with another data container, like a sphere or disk.\nplot = yt.ProjectionPlot(\n    ds, \"x\", (\"gas\", \"density\"), weight_field=(\"gas\", \"density\"), data_source=region\n)\n\n# Save the image with the keyword.\nplot.save(\"Thin_Slice\")\n"
  },
  {
    "path": "doc/source/cookbook/time_series.py",
    "content": "import matplotlib.pyplot as plt\nimport numpy as np\n\nimport yt\n\n# Enable parallelism in the script (assuming it was called with\n# `mpirun -np <n_procs>` )\nyt.enable_parallelism()\n\n# By using wildcards such as ? and * with the load command, we can load up a\n# Time Series containing all of these datasets simultaneously.\n# The \"entropy\" field that we will use below depends on the electron number\n# density, which is not in these datasets by default, so we assume full\n# ionization using the \"default_species_fields\" kwarg.\nts = yt.load(\n    \"GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0*\",\n    default_species_fields=\"ionized\",\n)\n\nstorage = {}\n\n# By using the piter() function, we can iterate on every dataset in\n# the TimeSeries object.  By using the storage keyword, we can populate\n# a dictionary where the dataset is the key, and sto.result is the value\n# for later use when the loop is complete.\n\n# The serial equivalent of piter() here is just \"for ds in ts:\" .\n\nfor store, ds in ts.piter(storage=storage):\n    # Create a sphere of radius 100 kpc at the center of the dataset volume\n    sphere = ds.sphere(\"c\", (100.0, \"kpc\"))\n    # Calculate the entropy within that sphere\n    entr = sphere[\"gas\", \"entropy\"].sum()\n    # Store the current time and sphere entropy for this dataset in our\n    # storage dictionary as a tuple\n    store.result = (ds.current_time.in_units(\"Gyr\"), entr)\n\n# Convert the storage dictionary values to a Nx2 array, so the can be easily\n# plotted\narr = np.array(list(storage.values()))\n\n# Plot up the results: time versus entropy\nplt.semilogy(arr[:, 0], arr[:, 1], \"r-\")\nplt.xlabel(\"Time (Gyr)\")\nplt.ylabel(\"Entropy (ergs/K)\")\nplt.savefig(\"time_versus_entropy.png\")\n"
  },
  {
    "path": "doc/source/cookbook/time_series_profiles.py",
    "content": "import yt\n\n# Create a time-series object.\nsim = yt.load_simulation(\"enzo_tiny_cosmology/32Mpc_32.enzo\", \"Enzo\")\nsim.get_time_series(redshifts=[5, 4, 3, 2, 1, 0])\n\n# Lists to hold profiles, labels, and plot specifications.\nprofiles = []\nlabels = []\nplot_specs = []\n\n# Loop over each dataset in the time-series.\nfor ds in sim:\n    # Create a data container to hold the whole dataset.\n    ad = ds.all_data()\n    # Create a 1d profile of density vs. temperature.\n    profiles.append(\n        yt.create_profile(ad, [(\"gas\", \"density\")], fields=[(\"gas\", \"temperature\")])\n    )\n    # Add labels and linestyles.\n    labels.append(f\"z = {ds.current_redshift:.2f}\")\n    plot_specs.append(dict(linewidth=2, alpha=0.7))\n\n# Create the profile plot from the list of profiles.\nplot = yt.ProfilePlot.from_profiles(profiles, labels=labels, plot_specs=plot_specs)\n# Save the image.\nplot.save()\n"
  },
  {
    "path": "doc/source/cookbook/tipsy_and_yt.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading Tipsy Data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Alright, let's start with some basics.  Before we do anything, we will need to load a snapshot.  You can do this using the ```load_sample``` convenience function.  yt will autodetect that you want a tipsy snapshot and download it from the yt hub.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We will be looking at a fairly low resolution dataset.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \">This dataset is available for download at https://yt-project.org/data/TipsyGalaxy.tar.gz (10 MB).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_sample(\\\"TipsyGalaxy\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We now have a `TipsyDataset` object called `ds`.  Let's see what fields it has.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.field_list\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"yt also defines so-called \\\"derived\\\" fields.  These fields are functions of the on-disk fields that live in the `field_list`.  There is a `derived_field_list` attribute attached to the `Dataset` object - let's take look at the derived fields in this dataset:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.derived_field_list\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"All of the field in the `field_list` are arrays containing the values for the associated particles.  These haven't been smoothed or gridded in any way. We can grab the array-data for these particles using `ds.all_data()`. For example, let's take a look at a temperature-colored scatterplot of the gas particles in this output.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"import numpy as np\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ad = ds.all_data()\\n\",\n    \"xcoord = ad[\\\"Gas\\\", \\\"Coordinates\\\"][:, 0].v\\n\",\n    \"ycoord = ad[\\\"Gas\\\", \\\"Coordinates\\\"][:, 1].v\\n\",\n    \"logT = np.log10(ad[\\\"Gas\\\", \\\"Temperature\\\"])\\n\",\n    \"plt.scatter(\\n\",\n    \"    xcoord, ycoord, c=logT, s=2 * logT, marker=\\\"o\\\", edgecolor=\\\"none\\\", vmin=2, vmax=6\\n\",\n    \")\\n\",\n    \"plt.xlim(-20, 20)\\n\",\n    \"plt.ylim(-20, 20)\\n\",\n    \"cb = plt.colorbar()\\n\",\n    \"cb.set_label(r\\\"$\\\\log_{10}$ Temperature\\\")\\n\",\n    \"plt.gcf().set_size_inches(15, 10)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Making Smoothed Images\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"yt will automatically generate smoothed versions of these fields that you can use to plot.  Let's make a temperature slice and a density projection.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"yt.SlicePlot(ds, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"), width=(40, \\\"kpc\\\"), center=\\\"m\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"yt.ProjectionPlot(ds, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"), width=(40, \\\"kpc\\\"), center=\\\"m\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Not only are the values in the tipsy snapshot read and automatically smoothed, the auxiliary files that have physical significance are also smoothed.  Let's look at a slice of Iron mass fraction.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"yt.SlicePlot(ds, \\\"z\\\", (\\\"gas\\\", \\\"Fe_fraction\\\"), width=(40, \\\"kpc\\\"), center=\\\"m\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.5.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/cookbook/various_lens.py",
    "content": "import numpy as np\n\nimport yt\nfrom yt.visualization.volume_rendering.api import Scene, create_volume_source\n\nfield = (\"gas\", \"density\")\n\n# normal_vector points from camera to the center of the final projection.\n# Now we look at the positive x direction.\nnormal_vector = [1.0, 0.0, 0.0]\n# north_vector defines the \"top\" direction of the projection, which is\n# positive z direction here.\nnorth_vector = [0.0, 0.0, 1.0]\n\n# Follow the simple_volume_rendering cookbook for the first part of this.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\nsc = Scene()\nvol = create_volume_source(ds, field=field)\ntf = vol.transfer_function\ntf.grey_opacity = True\n\n# Plane-parallel lens\ncam = sc.add_camera(ds, lens_type=\"plane-parallel\")\n# Set the resolution of the final projection.\ncam.resolution = [250, 250]\n# Set the location of the camera to be (x=0.2, y=0.5, z=0.5)\n# For plane-parallel lens, the location info along the normal_vector (here\n# is x=0.2) is ignored.\ncam.position = ds.arr(np.array([0.2, 0.5, 0.5]), \"code_length\")\n# Set the orientation of the camera.\ncam.switch_orientation(normal_vector=normal_vector, north_vector=north_vector)\n# Set the width of the camera, where width[0] and width[1] specify the length and\n# height of final projection, while width[2] in plane-parallel lens is not used.\ncam.set_width(ds.domain_width * 0.5)\nsc.add_source(vol)\nsc.save(\"lens_plane-parallel.png\", sigma_clip=6.0)\n\n# Perspective lens\ncam = sc.add_camera(ds, lens_type=\"perspective\")\ncam.resolution = [250, 250]\n# Standing at (x=0.2, y=0.5, z=0.5), we look at the area of x>0.2 (with some open angle\n# specified by camera width) along the positive x direction.\ncam.position = ds.arr([0.2, 0.5, 0.5], \"code_length\")\ncam.switch_orientation(normal_vector=normal_vector, north_vector=north_vector)\n# Set the width of the camera, where width[0] and width[1] specify the length and\n# height of the final projection, while width[2] specifies the distance between the\n# camera and the final image.\ncam.set_width(ds.domain_width * 0.5)\nsc.add_source(vol)\nsc.save(\"lens_perspective.png\", sigma_clip=6.0)\n\n# Stereo-perspective lens\ncam = sc.add_camera(ds, lens_type=\"stereo-perspective\")\n# Set the size ratio of the final projection to be 2:1, since stereo-perspective lens\n# will generate the final image with both left-eye and right-eye ones jointed together.\ncam.resolution = [500, 250]\ncam.position = ds.arr([0.2, 0.5, 0.5], \"code_length\")\ncam.switch_orientation(normal_vector=normal_vector, north_vector=north_vector)\ncam.set_width(ds.domain_width * 0.5)\n# Set the distance between left-eye and right-eye.\ncam.lens.disparity = ds.domain_width[0] * 1.0e-3\nsc.add_source(vol)\nsc.save(\"lens_stereo-perspective.png\", sigma_clip=6.0)\n\n# Fisheye lens\ndd = ds.sphere(ds.domain_center, ds.domain_width[0] / 10)\ncam = sc.add_camera(dd, lens_type=\"fisheye\")\ncam.resolution = [250, 250]\nv, c = ds.find_max(field)\ncam.set_position(c - 0.0005 * ds.domain_width)\ncam.switch_orientation(normal_vector=normal_vector, north_vector=north_vector)\ncam.set_width(ds.domain_width)\ncam.lens.fov = 360.0\nsc.add_source(vol)\nsc.save(\"lens_fisheye.png\", sigma_clip=6.0)\n\n# Spherical lens\ncam = sc.add_camera(ds, lens_type=\"spherical\")\n# Set the size ratio of the final projection to be 2:1, since spherical lens\n# will generate the final image with length of 2*pi and height of pi.\n# Recommended resolution for YouTube 360-degree videos is [3840, 2160]\ncam.resolution = [500, 250]\n# Standing at (x=0.4, y=0.5, z=0.5), we look in all the radial directions\n# from this point in spherical coordinate.\ncam.position = ds.arr([0.4, 0.5, 0.5], \"code_length\")\ncam.switch_orientation(normal_vector=normal_vector, north_vector=north_vector)\n# In (stereo)spherical camera, camera width is not used since the entire volume\n# will be rendered\nsc.add_source(vol)\nsc.save(\"lens_spherical.png\", sigma_clip=6.0)\n\n# Stereo-spherical lens\ncam = sc.add_camera(ds, lens_type=\"stereo-spherical\")\n# Set the size ratio of the final projection to be 1:1, since spherical-perspective lens\n# will generate the final image with both left-eye and right-eye ones jointed together,\n# with left-eye image on top and right-eye image on bottom.\n# Recommended resolution for YouTube virtual reality videos is [3840, 2160]\ncam.resolution = [500, 500]\ncam.position = ds.arr([0.4, 0.5, 0.5], \"code_length\")\ncam.switch_orientation(normal_vector=normal_vector, north_vector=north_vector)\n# In (stereo)spherical camera, camera width is not used since the entire volume\n# will be rendered\n# Set the distance between left-eye and right-eye.\ncam.lens.disparity = ds.domain_width[0] * 1.0e-3\nsc.add_source(vol)\nsc.save(\"lens_stereo-spherical.png\", sigma_clip=6.0)\n"
  },
  {
    "path": "doc/source/cookbook/velocity_vectors_on_slice.py",
    "content": "import yt\n\n# Load the dataset.\nds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n\np = yt.SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n\n# Draw a velocity vector every 16 pixels.\np.annotate_velocity(factor=16)\np.save()\n"
  },
  {
    "path": "doc/source/cookbook/vol-annotated.py",
    "content": "import yt\n\nds = yt.load(\"Enzo_64/DD0043/data0043\")\n\nsc = yt.create_scene(ds, lens_type=\"perspective\")\n\nsource = sc[0]\n\nsource.set_field((\"gas\", \"density\"))\nsource.set_log(True)\n\n# Set up the camera parameters: focus, width, resolution, and image orientation\nsc.camera.focus = ds.domain_center\nsc.camera.resolution = 1024\nsc.camera.north_vector = [0, 0, 1]\nsc.camera.position = [1.7, 1.7, 1.7]\n\n# You may need to adjust the alpha values to get an image with good contrast.\n# For the annotate_domain call, the fourth value in the color tuple is the\n# alpha value.\nsc.annotate_axes(alpha=0.02)\nsc.annotate_domain(ds, color=[1, 1, 1, 0.01])\n\ntext_string = f\"T = {float(ds.current_time.to('Gyr'))} Gyr\"\n\n# save an annotated version of the volume rendering including a representation\n# of the transfer function and a nice label showing the simulation time.\nsc.save_annotated(\n    \"vol_annotated.png\", sigma_clip=6, text_annotate=[[(0.1, 0.95), text_string]]\n)\n"
  },
  {
    "path": "doc/source/cookbook/vol-lines.py",
    "content": "import numpy as np\n\nimport yt\nfrom yt.units import kpc\nfrom yt.visualization.volume_rendering.api import LineSource\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\nsc = yt.create_scene(ds)\n\nnp.random.seed(1234567)\n\nnlines = 50\nvertices = (np.random.random([nlines, 2, 3]) - 0.5) * 200 * kpc\ncolors = np.random.random([nlines, 4])\ncolors[:, 3] = 0.1\n\nlines = LineSource(vertices, colors)\nsc.add_source(lines)\n\nsc.camera.width = 300 * kpc\n\nsc.save(sigma_clip=4.0)\n"
  },
  {
    "path": "doc/source/cookbook/vol-points.py",
    "content": "import numpy as np\n\nimport yt\nfrom yt.units import kpc\nfrom yt.visualization.volume_rendering.api import PointSource\n\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\nsc = yt.create_scene(ds)\n\nnp.random.seed(1234567)\n\nnpoints = 1000\n\n# Random particle positions\nvertices = np.random.random([npoints, 3]) * 200 * kpc\n\n# Random colors\ncolors = np.random.random([npoints, 4])\n\n# Set alpha value to something that produces a good contrast with the volume\n# rendering\ncolors[:, 3] = 0.1\n\npoints = PointSource(vertices, colors=colors)\nsc.add_source(points)\n\nsc.camera.width = 300 * kpc\n\nsc.save(sigma_clip=5)\n"
  },
  {
    "path": "doc/source/cookbook/yt_gadget_analysis.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading Gadget data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"First we set up our imports:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"First we load the data set, specifying both the unit length/mass/velocity, as well as the size of the bounding box (which should encapsulate all the particles in the data set)\\n\",\n    \"\\n\",\n    \"At the end, we flatten the data into \\\"ad\\\" in case we want access to the raw simulation data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \">This dataset is available for download at https://yt-project.org/data/GadgetDiskGalaxy.tar.gz (430 MB).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"fname = \\\"GadgetDiskGalaxy/snapshot_200.hdf5\\\"\\n\",\n    \"\\n\",\n    \"unit_base = {\\n\",\n    \"    \\\"UnitLength_in_cm\\\": 3.08568e21,\\n\",\n    \"    \\\"UnitMass_in_g\\\": 1.989e43,\\n\",\n    \"    \\\"UnitVelocity_in_cm_per_s\\\": 100000,\\n\",\n    \"}\\n\",\n    \"\\n\",\n    \"bbox_lim = 1e5  # kpc\\n\",\n    \"\\n\",\n    \"bbox = [[-bbox_lim, bbox_lim], [-bbox_lim, bbox_lim], [-bbox_lim, bbox_lim]]\\n\",\n    \"\\n\",\n    \"ds = yt.load(fname, unit_base=unit_base, bounding_box=bbox)\\n\",\n    \"ds.index\\n\",\n    \"ad = ds.all_data()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's make a projection plot to look at the entire volume\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"px = yt.ProjectionPlot(ds, \\\"x\\\", (\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"px.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's print some quantities about the domain, as well as the physical properties of the simulation\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(\\\"left edge: \\\", ds.domain_left_edge)\\n\",\n    \"print(\\\"right edge: \\\", ds.domain_right_edge)\\n\",\n    \"print(\\\"center: \\\", ds.domain_center)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also see the fields that are available to query in the dataset\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sorted(ds.field_list)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's create a data object that represents the full simulation domain, and find the total mass in gas and dark matter particles contained in it:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ad = ds.all_data()\\n\",\n    \"\\n\",\n    \"# total_mass returns a list, representing the total gas and dark matter + stellar mass, respectively\\n\",\n    \"print([tm.in_units(\\\"Msun\\\") for tm in ad.quantities.total_mass()])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now let's say we want to zoom in on the box (since clearly the bounding we chose initially is much larger than the volume containing the gas particles!), and center on wherever the highest gas density peak is.  First, let's find this peak:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"density = ad[\\\"PartType0\\\", \\\"density\\\"]\\n\",\n    \"wdens = np.where(density == np.max(density))\\n\",\n    \"coordinates = ad[\\\"PartType0\\\", \\\"Coordinates\\\"]\\n\",\n    \"center = coordinates[wdens][0]\\n\",\n    \"print(\\\"center = \\\", center)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Set up the box to zoom into\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"new_box_size = ds.quan(250, \\\"code_length\\\")\\n\",\n    \"\\n\",\n    \"left_edge = center - new_box_size / 2\\n\",\n    \"right_edge = center + new_box_size / 2\\n\",\n    \"\\n\",\n    \"print(new_box_size.in_units(\\\"Mpc\\\"))\\n\",\n    \"print(left_edge.in_units(\\\"Mpc\\\"))\\n\",\n    \"print(right_edge.in_units(\\\"Mpc\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ad2 = ds.region(center=center, left_edge=left_edge, right_edge=right_edge)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Using this new data object, let's confirm that we're only looking at a subset of the domain by first calculating the total mass in gas and particles contained in the subvolume:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print([tm.in_units(\\\"Msun\\\") for tm in ad2.quantities.total_mass()])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"And then by visualizing what the new zoomed region looks like\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"px = yt.ProjectionPlot(ds, \\\"x\\\", (\\\"gas\\\", \\\"density\\\"), center=center, width=new_box_size)\\n\",\n    \"px.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Cool - there's a disk galaxy there!\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.5.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/cookbook/yt_gadget_owls_analysis.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading Gadget OWLS Data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Setup\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The first thing you will need to run these examples is a working installation of yt.  The author or these examples followed the instructions under \\\"Get yt: from source\\\" at https://yt-project.org/ to install an up to date development version of yt.\\n\",\n    \"\\n\",\n    \"We will be working with an OWLS snapshot: snapshot_033\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we will tell the notebook that we want figures produced inline. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Loading\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we will load the snapshot.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_sample(\\\"snapshot_033\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Set a ``YTRegion`` that contains all the data.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ad = ds.all_data()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Inspecting \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The dataset can tell us what fields it knows about, \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.field_list\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.derived_field_list\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that the ion fields follow the naming convention described in YTEP-0003 http://ytep.readthedocs.org/en/latest/YTEPs/YTEP-0003.html#molecular-and-atomic-species-names\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Accessing Particle Data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The raw particle data can be accessed using the particle types.  This corresponds directly with what is in the hdf5 snapshots. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ad[\\\"PartType0\\\", \\\"Coordinates\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ad[\\\"PartType4\\\", \\\"IronFromSNIa\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ad[\\\"PartType1\\\", \\\"ParticleIDs\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ad[\\\"PartType0\\\", \\\"Hydrogen\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Projection Plots\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The projection plots make use of derived fields that store the smoothed particle data (particles smoothed onto an oct-tree).  Below we make a projection of all hydrogen gas followed by only the neutral hydrogen gas. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"pz = yt.ProjectionPlot(ds, \\\"z\\\", (\\\"gas\\\", \\\"H_density\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"pz.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"pz = yt.ProjectionPlot(ds, \\\"z\\\", (\\\"gas\\\", \\\"H_p0_density\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"pz.show()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.5.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/cookbook/zoomin_frames.py",
    "content": "import numpy as np\n\nimport yt\n\n# load data\nfn = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\nds = yt.load(fn)\n\n# This is the number of frames to make -- below, you can see how this is used.\nn_frames = 5\n\n# This is the minimum size in smallest_dx of our last frame.\n# Usually it should be set to something like 400, but for THIS\n# dataset, we actually don't have that great of resolution.\nmin_dx = 40\n\nframe_template = \"frame_%05i\"  # Template for frame filenames\n\np = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"))  # Add our slice, along z\np.annotate_contour((\"gas\", \"temperature\"))  # We'll contour in temperature\n\n# What we do now is a bit fun.  \"enumerate\" returns a tuple for every item --\n# the index of the item, and the item itself.  This saves us having to write\n# something like \"i = 0\" and then inside the loop \"i += 1\" for ever loop.  The\n# argument to enumerate is the 'logspace' function, which takes a minimum and a\n# maximum and the number of items to generate.  It returns 10^power of each\n# item it generates.\n\nfor i, v in enumerate(\n    np.logspace(0, np.log10(ds.index.get_smallest_dx() * min_dx), n_frames)\n):\n    # We set our width as necessary for this frame\n    p.set_width(v, \"unitary\")\n    # save\n    p.save(frame_template % (i))\n"
  },
  {
    "path": "doc/source/developing/building_the_docs.rst",
    "content": ".. _documentation:\n\nDocumentation\n=============\n\n.. _writing_documentation:\n\nHow to Write Documentation\n--------------------------\n\nWriting documentation is one of the most important but often overlooked tasks\nfor increasing yt's impact in the community.  It is the way in which the\nworld will understand how to use our code, so it needs to be done concisely\nand understandably.  Typically, when a developer submits some piece of code\nwith new functionality, the developer should also include documentation on how\nto use that functionality (as per :ref:`requirements-for-code-submission`).\nDepending on the nature of the code addition, this could be a new narrative\ndocs section describing how the new code works and how to use it, it could\ninclude a recipe in the cookbook section, or it could simply be adding a note\nin the relevant docs text somewhere.\n\nThe documentation exists in the main code repository for yt in the\n``doc`` directory (i.e. ``$YT_GIT/doc/source`` where ``$YT_GIT`` is the path of\nthe yt git repository).  It is organized hierarchically into the main\ncategories of:\n\n* Visualizing\n* Analyzing\n* Analysis Modules\n* Examining\n* Cookbook\n* Quickstart\n* Developing\n* Reference\n* FAQ\n* Help\n\nYou will have to figure out where your new/modified doc fits into this, but\nbrowsing through the existing documentation is a good way to sort that out.\n\nAll the source for the documentation is written in\n`Sphinx <http://www.sphinx-doc.org/en/master/>`_, which uses ReST for markup.  ReST is very\nstraightforward to markup in a text editor, and if you are new to it, we\nrecommend just using other .rst files in the existing yt documentation as\ntemplates or checking out the\n`ReST reference documentation <http://www.sphinx-doc.org/en/master/usage/restructuredtext/>`_.\n\nNew cookbook recipes (see :ref:`cookbook`) are very helpful for the community\nas they provide simple annotated recipes on how to use specific functionality.\nTo add one, create a concise Python script which demonstrates some\nfunctionality and pare it down to its minimum.  Add some comment lines to\ndescribe what it is that you're doing along the way.  Place this ``.py`` file\nin the ``source/cookbook/`` directory, and then link to it explicitly in one\nof the relevant ``.rst`` files in that directory (e.g. ``complex_plots.rst``,\netc.), and add some description of what the script\nactually does.  We recommend that you use one of the\n`sample data sets <https://yt-project.org/data>`_ in your recipe.  When the full\ndocs are built, each of the cookbook recipes is executed dynamically on\na system which has access to all of the sample datasets.  Any output images\ngenerated by your script will then be attached inline in the built documentation\ndirectly following your script.\n\nAfter you have made your modifications to the docs, you will want to make sure\nthat they render the way you expect them to render.  For more information on\nthis, see the section on :ref:`docs_build`.  Unless you're contributing cookbook\nrecipes or notebooks which require a dynamic build, you can probably get away\nwith just doing a 'quick' docs build.\n\nWhen you have completed your documentation additions, commit your changes\nto your repository and make a pull request in the same way you would contribute\na change to the codebase, as described in the section on :ref:`sharing-changes`.\n\n.. _docs_build:\n\nBuilding the Documentation\n--------------------------\n\nThe yt documentation makes heavy use of the Sphinx documentation automation\nsuite.  Sphinx, written in Python, was originally created for the documentation\nof the Python project and has many nice capabilities for managing the\ndocumentation of Python code.\n\nWhile much of the yt documentation is static text, we make heavy use of\ncross-referencing with API documentation that is automatically generated at\nbuild time by Sphinx.  We also use Sphinx to run code snippets (e.g. the\ncookbook and the notebooks) and embed resulting images and example data.\n\nEssential tools for building the docs can be installed alongside yt itself. From\nthe top level of a local copy, run\n\n.. code-block:: bash\n\n   $ python -m pip install -e . --group docs\n\nQuick versus Full Documentation Builds\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nBuilding the entire set of yt documentation is a laborious task, since you\nneed to have a large number of packages in order to successfully execute\nand render all of the notebooks and yt recipes drawing from every corner\nof the yt source.  As a quick alternative, one can do a ``quick`` build\nof the documentation, which eschews the need for downloading all of these\ndependencies, but it only produces the static docs.  The static docs do\nnot include the cookbook outputs and the notebooks, but this is good\nenough for most cases of people testing out whether or not their documentation\ncontributions look OK before submitting them to the yt repository.\n\nIf you want to create the full documentation locally, then you'll need\nto follow the instructions for building the ``full`` docs, so that you can\ndynamically execute and render the cookbook recipes, the notebooks, etc.\n\nBuilding the Docs (Quick)\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn order to tell Sphinx not to do all of the dynamic building, you must set the\n``$READTHEDOCS`` environment variable to be ``True`` by run from the command\nline (using bash syntax for example), as\n\n.. code-block:: bash\n\n   export READTHEDOCS=True\n\nThis variable is set for automated builds on the free ReadTheDocs service but\ncan be used by anyone to force a quick, minimal build.\n\nNow all you need to do is execute Sphinx on the yt doc source.  Go to the\ndocumentation directory and build the docs:\n\n.. code-block:: bash\n\n   cd $YT_GIT/doc\n   make html\n\nThis will produce an html version of the documentation locally in the\n``$YT_GIT/doc/build/html`` directory.  You can now go there and open\nup ``index.html`` or whatever file you wish in your web browser.\n\nBuilding the Docs (Full)\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nAs alluded to earlier, building the full documentation is a bit more involved\nthan simply building the static documentation.\n\nThe full documentation makes heavy use of custom Sphinx extensions to transform\nrecipes, notebooks, and inline code snippets into Python scripts, IPython_\nnotebooks, or notebook cells that are executed when the docs are built.\n\nTo do this, we use Jupyter's nbconvert module to transform notebooks into\nHTML. to simplify versioning of the notebook JSON format, we store notebooks in\nan unevaluated state.\n\nTo build the full documentation, you will need yt, jupyter, and all dependencies\nneeded for yt's analysis modules installed. The following dependencies were\nused to generate the yt documentation during the release of yt 3.2 in 2015.\n\n* Sphinx_ 1.3.1\n* Jupyter 1.0.0\n* RunNotebook 0.1\n* pandoc_ 1.13.2\n* Rockstar halo finder 0.99.6\n* SZpack_ 1.1.1\n* ffmpeg_ 2.7.1 (compiled with libvpx support)\n* Astropy_ 0.4.4\n\n.. _SZpack: http://www.jb.man.ac.uk/~jchluba/Science/SZpack/SZpack.html\n.. _Astropy: https://www.astropy.org/\n.. _Sphinx: http://www.sphinx-doc.org/en/master/\n.. _pandoc: https://pandoc.org/\n.. _ffmpeg: http://www.ffmpeg.org/\n.. _IPython: https://ipython.org/\n\nYou will also need the full yt suite of `yt test data\n<https://yt-project.org/data/>`_, including the larger datasets that are not used\nin the answer tests.\n\nYou will need to ensure that your testing configuration is properly\nconfigured and that all of the yt test data is in the testing directory.  See\n:ref:`run_answer_testing` for more details on how to set up the testing\nconfiguration.\n\nNow that you have everything set up properly, go to the documentation directory\nand build it using Sphinx:\n\n.. code-block:: bash\n\n   cd $YT_GIT/doc\n   make html\n\nIf all of the dependencies are installed and all of the test data is in the\ntesting directory, this should churn away for a while (several hours) and\neventually generate a docs build.  We suggest setting\n:code:`suppress_stream_logging = True` in your yt configuration (See\n:ref:`configuration-file`) to suppress large amounts of debug output from\nyt.\n\nTo clean the docs build, use :code:`make clean`.\n\nBuilding the Docs (Hybrid)\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt's also possible to create a custom Sphinx build that builds a restricted set\nof notebooks or scripts.  This can be accomplished by editing the Sphinx\n:code:`conf.py` file included in the :code:`source` directory at the top level\nof the docs.  The extensions included in the build are contained in the\n:code:`extensions` list.  To disable an extension, simply remove it from the\nlist.  Doing so will raise a warning when Sphinx encounters the directive in the\ndocs and will prevent Sphinx from evaluating the directive.\n\nAs a concrete example, if one wanted to include the :code:`notebook`, and\n:code:`notebook-cell` directives, but not the :code:`python-script` or\n:code:`autosummary` directives, one would just need to comment out the lines\nthat append these extensions to the :code:`extensions` list. The resulting docs\nbuild will be significantly quicker since it would avoid executing the lengthy\nAPI autodocumentation as well as a large number of Python script snippets in\nthe narrative docs.\n"
  },
  {
    "path": "doc/source/developing/creating_datatypes.rst",
    "content": ".. _creating-objects:\n\nCreating Data Objects\n=====================\n\nThe three-dimensional datatypes in yt follow a fairly simple protocol.  The\nbasic principle is that if you want to define a region in space, that region\nmust be identifiable from some sort of cut applied against the cells --\ntypically, in yt, this is done by examining the geometry.\n\nCreating a new data object requires modifications to two different files, one\nof which is in Python and the other in Cython.  First, a subclass of\n:class:`~yt.data_objects.data_containers.YTDataContainer` must be defined;\ntypically you actually want to subclass one of:\n:class:`~yt.data_objects.data_containers.YTSelectionContainer0D`\n:class:`~yt.data_objects.data_containers.YTSelectionContainer1D`\n:class:`~yt.data_objects.data_containers.YTSelectionContainer2D`\n:class:`~yt.data_objects.data_containers.YTSelectionContainer3D`.\nThe following attributes must be defined:\n\n * ``_type_name`` - this is the short name by which the object type will be\n   known as.  Remember this for later, as we will have to use it when defining\n   the underlying selector.\n * ``_con_args`` - this is the set of arguments passed to the object, and their\n   names as attributes on the data object.\n * ``_container_fields`` - any fields that are generated by the object, rather\n   than by another derived field in yt.\n\nThe rest of the object can be defined in Cython, in the file\n``yt/geometry/selection_routines.pyx``.  You must define a subclass of\n``SelectorObject``, which will require implementation of the following methods:\n\n * ``fill_mask`` - this takes a grid object and fills a mask of which zones\n   should be included.  It must take into account the child mask of the grid.\n * ``select_cell`` - this routine accepts a position and a width, and returns\n   either zero or one for whether or not that cell is included in the selector.\n * ``select_sphere`` - this routine returns zero or one whether a sphere (point\n   and radius) is included in the selector.\n * ``select_point`` - this identifies whether or not a point is included in the\n   selector.  It should be identical to selecting a cell or a sphere with\n   zero extent.\n * ``select_bbox`` - this returns whether or not a bounding box (i.e., grid) is\n   included in the selector.\n * ``_hash_vals`` - this must return some combination of parameters that\n   semi-uniquely identifies the selector.\n\nOnce the object has been defined, it must then be aliased within\n``selection_routines.pyx`` as ``typename_selector``.  For instance,\n``ray_selector`` or ``sphere_selector`` for ``_type_name`` values of ``ray``\nand ``sphere``, respectively.\n"
  },
  {
    "path": "doc/source/developing/creating_derived_fields.rst",
    "content": ".. _creating-derived-fields:\n\nCreating Derived Fields\n=======================\n\nOne of the more powerful means of extending yt is through the usage of derived\nfields.  These are fields that describe a value at each cell in a simulation.\n\nDefining a New Field\n--------------------\n\nOnce a new field has been conceived of, the best way to create it is to\nconstruct a function that performs an array operation -- operating on a\ncollection of data, neutral to its size, shape, and type.\n\nA simple example of this is the pressure field, which demonstrates the ease of\nthis approach.\n\n.. code-block:: python\n\n   import yt\n\n\n   def _pressure(data):\n       return (\n           (data.ds.gamma - 1.0)\n           * data[\"gas\", \"density\"]\n           * data[\"gas\", \"specific_thermal_energy\"]\n       )\n\nNote that we do a couple different things here.  We access the ``gamma``\nparameter from the dataset, we access the ``density`` field and we access\nthe ``specific_thermal_energy`` field.  ``specific_thermal_energy`` is, in\nfact, another derived field!  We don't do any loops, we don't do any\ntype-checking, we can simply multiply the three items together.\n\nIn this example, the ``density`` field will return data with units of\n``g/cm**3`` and the ``specific_thermal_energy`` field will return data units of\n``erg/g``, so the result will automatically have units of pressure,\n``erg/cm**3``. This assumes the unit system is set to the default, which is\nCGS: if a different unit system is selected, the result will be in the same\ndimensions of pressure but different units. See :ref:`units` for more\ninformation.\n\nOnce we've defined our function, we need to notify yt that the field is\navailable.  The :func:`add_field` function is the means of doing this; it has a\nnumber of fairly specific parameters that can be passed in, but here we'll only\nlook at the most basic ones needed for a simple scalar baryon field.\n\n.. note::\n\n    There are two different :func:`add_field` functions.  For the differences,\n    see :ref:`faq-add-field-diffs`.\n\n.. code-block:: python\n\n    yt.add_field(\n        name=(\"gas\", \"pressure\"),\n        function=_pressure,\n        sampling_type=\"local\",\n        units=\"dyne/cm**2\",\n    )\n\nWe feed it the name of the field, the name of the function, the sampling type,\nand the units. The ``sampling_type`` keyword determines which elements are\nused to make the field (i.e., grid cell or particles) and controls how volume\nis calculated. It can be set to \"cell\" for grid/mesh fields, \"particle\" for\nparticle and SPH fields, or \"local\" to use the primary format of the loaded\ndataset. In most cases, \"local\" is sufficient, but \"cell\" and \"particle\"\ncan be used to specify the source for datasets that have both grids and\nparticles. In a dataset with both grids and particles, using \"cell\" will\nensure a field is created with a value for every grid cell, while using\n\"particle\" will result in a field with a value for every particle.\n\nThe units parameter is a \"raw\" string, in the format that yt\nuses in its :ref:`symbolic units implementation <units>` (e.g., employing only\nunit names, numbers, and mathematical operators in the string, and using\n``\"**\"`` for exponentiation). For cosmological datasets and fields, see\n:ref:`cosmological-units <cosmological-units>`.  We suggest that you name the function that creates\na derived field with the intended field name prefixed by a single underscore,\nas in the ``_pressure`` example above.\n\nField definitions return array data with units. If the field function returns\ndata in a dimensionally equivalent unit (e.g. a ``\"dyne\"`` versus a ``\"N\"``), the\nfield data will be converted to the units specified in ``add_field`` before\nbeing returned in a data object selection. If the field function returns data\nwith dimensions that are incompatible with units specified in ``add_field``,\nyou will see an error. To clear this error, you must ensure that your field\nfunction returns data in the correct units. Often, this means applying units to\na dimensionless float or array.\n\nIf your field definition includes physical constants rather than defining a\nconstant as a float, you can import it from ``yt.units``\nto get a predefined version of the constant with the correct units. If you know\nthe units your data is supposed to have ahead of time, you can also import unit\nsymbols like ``g`` or ``cm`` from the ``yt.units`` namespace and multiply the\nreturn value of your field function by the appropriate combination of unit\nsymbols for your field's units. You can also convert floats or NumPy arrays into\n:class:`~yt.units.yt_array.YTArray` or :class:`~yt.units.yt_array.YTQuantity`\ninstances by making use of the\n:func:`~yt.data_objects.static_output.Dataset.arr` and\n:func:`~yt.data_objects.static_output.Dataset.quan` convenience functions.\n\nLastly, if you do not know the units of your field ahead of time, you can\nspecify ``units='auto'`` in the call to ``add_field`` for your field.  This will\nautomatically determine the appropriate units based on the units of the data\nreturned by the field function. This is also a good way to let your derived\nfields be automatically converted to the units of the unit system in your\ndataset.\n\nIf ``units='auto'`` is set, it is also required to set the ``dimensions`` keyword\nargument so that error-checking can be done on the derived field to make sure that\nthe dimensionality of the returned array and the field are the same:\n\n.. code-block:: python\n\n    import yt\n    from yt.units import dimensions\n\n\n    def _pressure(data):\n        return (\n            (data.ds.gamma - 1.0)\n            * data[\"gas\", \"density\"]\n            * data[\"gas\", \"specific_thermal_energy\"]\n        )\n\n\n    yt.add_field(\n        (\"gas\", \"pressure\"),\n        function=_pressure,\n        sampling_type=\"local\",\n        units=\"auto\",\n        dimensions=dimensions.pressure,\n    )\n\nIf ``dimensions`` is not set, an error will be thrown. The ``dimensions`` keyword\ncan be a SymPy ``symbol`` object imported from ``yt.units.dimensions``, a compound\ndimension of these, or a string corresponding to one of these objects.\n\n:func:`add_field` can be invoked in two other ways. The first is by the\nfunction decorator :func:`derived_field`. The following code is equivalent to\nthe previous example:\n\n.. code-block:: python\n\n   from yt import derived_field\n\n\n   @derived_field(name=\"pressure\", sampling_type=\"cell\", units=\"dyne/cm**2\")\n   def _pressure(data):\n       return (\n           (data.ds.gamma - 1.0)\n           * data[\"gas\", \"density\"]\n           * data[\"gas\", \"specific_thermal_energy\"]\n       )\n\nThe :func:`derived_field` decorator takes the same arguments as\n:func:`add_field`, and is often a more convenient shorthand in cases where\nyou want to quickly set up a new field.\n\nDefining derived fields in the above fashion must be done before a dataset is\nloaded, in order for the dataset to recognize it. If you want to set up a\nderived field after you have loaded a dataset, or if you only want to set up\na derived field for a particular dataset, there is an\n:func:`~yt.data_objects.static_output.Dataset.add_field` method that hangs off\ndataset objects. The calling syntax is the same:\n\n.. code-block:: python\n\n   ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0100\")\n   ds.add_field(\n       (\"gas\", \"pressure\"),\n       function=_pressure,\n       sampling_type=\"cell\",\n       units=\"dyne/cm**2\",\n   )\n\nIf you specify fields in this way, you can take advantage of the dataset's unit\nsystem to define the units for you, so that the units will be returned in the\nunits of that system:\n\n.. code-block:: python\n\n    ds.add_field(\n        (\"gas\", \"pressure\"),\n        function=_pressure,\n        sampling_type=\"cell\",\n        units=ds.unit_system[\"pressure\"],\n    )\n\nSince the :class:`yt.units.unit_systems.UnitSystem` object returns a :class:`yt.units.unit_object.Unit` object when\nqueried, you're not limited to specifying units in terms of those already available. You can specify units for fields\nusing basic arithmetic if necessary:\n\n.. code-block:: python\n\n    ds.add_field(\n        (\"gas\", \"my_acceleration\"),\n        function=_my_acceleration,\n        sampling_type=\"cell\",\n        units=ds.unit_system[\"length\"] / ds.unit_system[\"time\"] ** 2,\n    )\n\nIf you find yourself using the same custom-defined fields over and over, you should put them in your plugins file as\ndescribed in :ref:`plugin-file`.\n\nShort-hand syntax for simple cases\n----------------------------------\nThe workflow described above (define a function, pass it to ``ds.add_field`` specifying\n``name``, ``sampling_type``, and ``units``) gives you maximal freedom. However, it can\nbe somewhat verbose, especially for simple cases. Take the first example, where we\nderive the pressure from the gas density and specific thermal energy. We can employ\nthe following short-hand syntax:\n\n.. code-block:: python\n\n    ds.add_field(\n        (\"gas\", \"pressure\"),\n        ds.fields.gas.density / ds.fields.gas.specific_thermal_energy * (ds.gamma - 1.0),\n        units=\"dyne/cm**2\",\n    )\n\nThis saves you from writing a separate function. Another allowed -- an even more compact -- syntax\nis:\n\n.. code-block:: python\n\n    ds.fields.gas.pressure = (\n        ds.fields.gas.density / ds.fields.gas.specific_thermal_energy * (ds.gamma - 1.0)\n    )\n\nNote that, here, you lose the ability to specify the ``sampling_type`` and the ``units``.\nInternally, yt will inherit the sampling type from the terms of the equation and infer the units\nautomatically.\n\nThese two syntaxes only support simple algebraic expressions (``+``, ``-``, ``*``, ``/``, ``<``, ``>``, ``<=`` and ``==``)\nwith operands that are either (combination of) fields or constants.\nIt is sufficient for many simple cases, but if you need to do something more complex, you\nwill need to write a separate function. See for example the following section.\n\nA More Complicated Example\n--------------------------\n\nBut what if we want to do something a bit more fancy?  Here's an example of getting\nparameters from the data object and using those to define the field;\nspecifically, here we obtain the ``center`` and ``bulk_velocity`` parameters\nand use those to define a field for radial velocity (there is already\na ``radial_velocity`` field in yt, but we create this one here just as a\ntransparent and simple example).\n\n.. code-block:: python\n\n   import numpy as np\n\n   from yt.fields.api import ValidateParameter\n\n\n   def _my_radial_velocity(data):\n       if data.has_field_parameter(\"bulk_velocity\"):\n           bv = data.get_field_parameter(\"bulk_velocity\").in_units(\"cm/s\")\n       else:\n           bv = data.ds.arr(np.zeros(3), \"cm/s\")\n       xv = data[\"gas\", \"velocity_x\"] - bv[0]\n       yv = data[\"gas\", \"velocity_y\"] - bv[1]\n       zv = data[\"gas\", \"velocity_z\"] - bv[2]\n       center = data.get_field_parameter(\"center\")\n       x_hat = data[\"gas\", \"x\"] - center[0]\n       y_hat = data[\"gas\", \"y\"] - center[1]\n       z_hat = data[\"gas\", \"z\"] - center[2]\n       r = np.sqrt(x_hat * x_hat + y_hat * y_hat + z_hat * z_hat)\n       x_hat /= r\n       y_hat /= r\n       z_hat /= r\n       return xv * x_hat + yv * y_hat + zv * z_hat\n\n\n   yt.add_field(\n       (\"gas\", \"my_radial_velocity\"),\n       function=_my_radial_velocity,\n       sampling_type=\"cell\",\n       units=\"cm/s\",\n       take_log=False,\n       validators=[ValidateParameter([\"center\", \"bulk_velocity\"])],\n   )\n\nNote that we have added a few optional arguments to ``yt.add_field``; we specify\nthat we do not wish to display this field as logged, that we require both the\n``bulk_velocity`` and ``center`` field parameters to be present in a given data\nobject we wish to calculate this for, and we say that it should not be displayed\nin a drop-down box of fields to display. This is done through the parameter\n*validators*, which accepts a list of\n:class:`~yt.fields.derived_field.FieldValidator` objects. These objects define\nthe way in which the field is generated, and when it is able to be created. In\nthis case, we mandate that parameters ``center`` and ``bulk_velocity`` are set\nbefore creating the field. These are set via\n:meth:`~yt.data_objects.data_containers.set_field_parameter`, which can be\ncalled on any object that has fields:\n\n.. code-block:: python\n\n   ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0100\")\n   sp = ds.sphere(\"max\", (200.0, \"kpc\"))\n   sp.set_field_parameter(\"bulk_velocity\", yt.YTArray([-100.0, 200.0, 300.0], \"km/s\"))\n\nIn this case, we already know what the ``center`` of the sphere is, so we do\nnot set it. Also, note that ``center`` and ``bulk_velocity`` need to be\n:class:`~yt.units.yt_array.YTArray` objects with units.\n\nIf you are writing a derived field that uses a field parameter that changes the\nbehavior of the field depending on the value of the field parameter, you can\nmake yt test to make sure the field handles all possible values for the field\nparameter using a special form of the ``ValidateParameter`` field validator. In\nparticular, ``ValidateParameter`` supports an optional second argument, which\ntakes a dictionary mapping from parameter names to parameter values that\nyou would like yt to test. This is useful when a field will select different\nfields to access based on the value of a field parameter. This option allows you\nto force yt to select *all* needed dependent fields for your derived field\ndefinition at field detection time. This can avoid errors related to missing fields.\n\nFor example, let's write a field that depends on a field parameter named ``'axis'``:\n\n.. code-block:: python\n\n   def my_axis_field(fdata):\n       axis = data.get_field_parameter(\"axis\")\n       if axis == 0:\n           return data[\"gas\", \"velocity_x\"]\n       elif axis == 1:\n           return data[\"gas\", \"velocity_y\"]\n       elif axis == 2:\n           return data[\"gas\", \"velocity_z\"]\n       else:\n           raise ValueError\n\n\n   ds.add_field(\n       \"my_axis_field\",\n       function=my_axis_field,\n       units=\"cm/s\",\n       validators=[ValidateParameter(\"axis\", {\"axis\": [0, 1, 2]})],\n   )\n\nIn this example, we've told yt's field system that the data object we are\nquerying ``my_axis_field`` must have the ``axis`` field parameter set. In\naddition, it forces yt to recognize that this field might depend on any one of\n``x-velocity``, ``y-velocity``, or ``z-velocity``. By specifying that ``axis``\nmight be 0, 1, or 2 in the ``ValidataParameter`` call, this ensures that this\nfield will only be valid and available for datasets that have all three fields\navailable.\n\nOther examples for creating derived fields can be found in the cookbook recipe\n:ref:`cookbook-simple-derived-fields`.\n\n.. _derived-field-options:\n\nAccessing field metadata\n------------------------\n\nIn some cases, it is useful to introspect the field under construction, for instance\nif specialized behavior is needed for different particle types, or to customize return\nunits. These use cases are supported through the optional ``field`` keyword argument.\nLet's look at a couple short examples.\n\n.. code-block:: python\n\n    def get_position_fields(data, field):\n        axis_names = [data.ds.coordinates.axis_name[num] for num in [0, 1, 2]]\n        ftype, _fname = data._determine_fields(field)\n        finfo = data.ds.field_info[ftype]\n        if finfo.sampling_type == \"particle\":\n            ftype, _fname = finfo.alias_name if finfo.is_alias else finfo.name\n            position_fields = [(ftype, f\"particle_position_{d}\") for d in axis_names]\n        else:\n            position_fields = [(\"index\", ax_name) for ax_name in axis_names]\n\n        return position_fields\n\n\n.. code-block:: python\n\n    def entropy(data, field):\n        ftype, _fname = data._determine_fields(field)\n        mgammam1 = -2.0 / 3.0\n        tr = data[ftype, \"kT\"] * data[ftype, \"El_number_density\"] ** mgammam1\n        return data.apply_units(tr, field.units)\n\nField Options\n-------------\n\nThe arguments to :func:`add_field` are passed on to the constructor of :class:`DerivedField`.\nThere are a number of options available, but the only mandatory ones are ``name``,\n``units``, and ``function``.\n\n``name``\n     This is the name of the field -- how you refer to it.  For instance,\n     ``pressure`` or ``magnetic_field_strength``.\n``function``\n     This is a function handle that defines the field\n``units``\n     This is a string that describes the units, or a query to a UnitSystem\n     object, e.g. ``ds.unit_system[\"energy\"]``. Powers must be in Python syntax (``**``\n     instead of ``^``). Alternatively, it may be set to ``\"auto\"`` to have the units\n     determined automatically. In this case, the ``dimensions`` keyword must be set to the\n     correct dimensions of the field.\n``display_name``\n     This is a name used in the plots, for instance ``\"Divergence of\n     Velocity\"``.  If not supplied, the ``name`` value is used.  Note:\n     for math expressions, do not wrap in ``$``, i.e., use ``r\"\\epsilon\"``\n     and not ``r\"$\\epsilon$\"``.\n``take_log``\n     This is *True* or *False* and describes whether the field should be logged\n     when plotted.\n``particle_type``\n     Is this field a *particle* field?\n``validators``\n     (*Advanced*) This is a list of :class:`FieldValidator` objects, for instance to mandate\n     spatial data.\n``display_field``\n     (*Advanced*) Should this field appear in the dropdown box in Reason?\n``not_in_all``\n     (*Advanced*) If this is *True*, the field may not be in all the grids.\n``output_units``\n     (*Advanced*) For fields that exist on disk, which we may want to convert to other\n     fields or that get aliased to themselves, we can specify a different\n     desired output unit than the unit found on disk.\n``force_override``\n     (*Advanced*) Overrides the definition of an old field if a field with the\n     same name has already been defined.\n``dimensions``\n     Set this if ``units=\"auto\"``. Can be either a string or a dimension object from\n     ``yt.units.dimensions``.\n\nDebugging a Derived Field\n-------------------------\n\nIf your derived field is not behaving as you would like, you can insert a call\nto ``data._debug()`` to spawn an interactive interpreter whenever that line is\nreached.  Note that this is slightly different from calling\n``pdb.set_trace()``, as it will *only* trigger when the derived field is being\ncalled on an actual data object, rather than during the field detection phase.\nThe starting position will be one function lower in the stack than you are\nlikely interested in, but you can either step through back to the derived field\nfunction, or simply type ``u`` to go up a level in the stack.\n\nFor instance, if you had defined this derived field:\n\n.. code-block:: python\n\n   @yt.derived_field(name=(\"gas\", \"funthings\"))\n   def funthings(data):\n       return data[\"sillythings\"] + data[\"humorousthings\"] ** 2.0\n\nAnd you wanted to debug it, you could do:\n\n.. code-block:: python\n\n   @yt.derived_field(name=(\"gas\", \"funthings\"))\n   def funthings(data):\n       data._debug()\n       return data[\"sillythings\"] + data[\"humorousthings\"] ** 2.0\n\nAnd now, when that derived field is actually used, you will be placed into a\ndebugger.\n"
  },
  {
    "path": "doc/source/developing/creating_frontend.rst",
    "content": ".. _creating_frontend:\n\nCreating A New Code Frontend\n============================\n\nyt is designed to support analysis and visualization of data from\nmultiple different simulation codes. For a list of codes and the level\nof support they enjoy, see :ref:`code-support`.\n\nWe'd like to support a broad range of codes, both Adaptive Mesh\nRefinement (AMR)-based and otherwise. To add support for a new code, a\nfew things need to be put into place. These necessary structures can\nbe classified into a couple categories:\n\n * Data meaning: This is the set of parameters that convert the data into\n   physically relevant units; things like spatial and mass conversions, time\n   units, and so on.\n * Data localization: These are structures that help make a \"first pass\" at data\n   loading. Essentially, we need to be able to make a first pass at guessing\n   where data in a given physical region would be located on disk. With AMR\n   data, this is typically quite easy: the grid patches are the \"first pass\" at\n   localization.\n * Data reading: This is the set of routines that actually perform a read of\n   either all data in a region or a subset of that data.\n\n\nNote that a frontend can be built as an external package. This is useful to\ndevelop and maintain a maturing frontend at your own pace. For technical details, see\n:ref:`frontends-as-extensions`.\n\nIf you are interested in adding a new code, be sure to drop us a line on\n`yt-dev <https://mail.python.org/archives/list/yt-dev@python.org/>`_!\n\n\nBootstrapping a new frontend\n----------------------------\n\nTo get started\n\n * make a new directory in ``yt/frontends`` with the name of your code and add the name\n   into ``yt/frontends/api.py:_frontends`` (in alphabetical order).\n * copy the contents of the ``yt/frontends/_skeleton`` directory, and replace every\n   occurrence of ``Skeleton`` with your frontend's name (preserving case). This\n   adds a lot of boilerplate for the required classes and methods that are needed.\n\n\nData Meaning Structures\n-----------------------\n\nYou will need to create a subclass of ``Dataset`` in the ``data_structures.py``\nfile. This subclass will need to handle conversion between the different physical\nunits and the code units (typically in the ``_set_code_unit_attributes()``\nmethod), read in metadata describing the overall data on disk (via the\n``_parse_parameter_file()`` method), and provide a ``classmethod``\ncalled ``_is_valid()`` that lets the ``yt.load`` method help identify an\ninput file as belonging to *this* particular ``Dataset`` subclass\n(see :ref:`data-format-detection`).\nFor the most part, the examples of\n``yt.frontends.amrex.data_structures.OrionDataset`` and\n``yt.frontends.enzo.data_structures.EnzoDataset`` should be followed,\nbut ``yt.frontends.chombo.data_structures.ChomboDataset``, as a\nslightly newer addition, can also be used as an instructive example.\n\nA new set of fields must be added in the file ``fields.py`` in your\nnew directory.  For the most part this means subclassing\n``FieldInfoContainer`` and adding the necessary fields specific to\nyour code. Here is a snippet from the base BoxLib field container (defined in\n``yt.frontends.amrex.fields``):\n\n.. code-block:: python\n\n    from yt.fields.field_info_container import FieldInfoContainer\n\n\n    class BoxlibFieldInfo(FieldInfoContainer):\n        known_other_fields = (\n            (\"density\", (rho_units, [\"density\"], None)),\n            (\"eden\", (eden_units, [\"energy_density\"], None)),\n            (\"xmom\", (mom_units, [\"momentum_x\"], None)),\n            (\"ymom\", (mom_units, [\"momentum_y\"], None)),\n            (\"zmom\", (mom_units, [\"momentum_z\"], None)),\n            (\"temperature\", (\"K\", [\"temperature\"], None)),\n            (\"Temp\", (\"K\", [\"temperature\"], None)),\n            (\"x_velocity\", (\"cm/s\", [\"velocity_x\"], None)),\n            (\"y_velocity\", (\"cm/s\", [\"velocity_y\"], None)),\n            (\"z_velocity\", (\"cm/s\", [\"velocity_z\"], None)),\n            (\"xvel\", (\"cm/s\", [\"velocity_x\"], None)),\n            (\"yvel\", (\"cm/s\", [\"velocity_y\"], None)),\n            (\"zvel\", (\"cm/s\", [\"velocity_z\"], None)),\n        )\n\n        known_particle_fields = (\n            (\"particle_mass\", (\"code_mass\", [], None)),\n            (\"particle_position_x\", (\"code_length\", [], None)),\n            (\"particle_position_y\", (\"code_length\", [], None)),\n            (\"particle_position_z\", (\"code_length\", [], None)),\n            (\"particle_momentum_x\", (mom_units, [], None)),\n            (\"particle_momentum_y\", (mom_units, [], None)),\n            (\"particle_momentum_z\", (mom_units, [], None)),\n            (\"particle_angmomen_x\", (\"code_length**2/code_time\", [], None)),\n            (\"particle_angmomen_y\", (\"code_length**2/code_time\", [], None)),\n            (\"particle_angmomen_z\", (\"code_length**2/code_time\", [], None)),\n            (\"particle_id\", (\"\", [\"particle_index\"], None)),\n            (\"particle_mdot\", (\"code_mass/code_time\", [], None)),\n        )\n\nThe tuples, ``known_other_fields`` and ``known_particle_fields`` contain\nentries, which are tuples of the form ``(\"name\", (\"units\", [\"fields\", \"to\",\n\"alias\"], \"display_name\"))``.  ``\"name\"`` is the name of a field stored on-disk\nin the dataset. ``\"units\"`` corresponds to the units of that field.  The list\n``[\"fields\", \"to\", \"alias\"]`` allows you to specify additional aliases to this\nparticular field; for example, if your on-disk field for the x-direction\nvelocity were ``\"x-direction-velocity\"``, maybe you'd prefer to alias to the\nmore terse name of ``\"xvel\"``.  By convention in yt we use a set of \"universal\"\nfields. Currently these fields are enumerated in the stream frontend. If you\ntake a look at ``yt/frontends/stream/fields.py``, you will see a listing of\nfields following the format described above with field names that will be\nrecognized by the rest of the built-in yt field system. In the example from the\nboxlib frontend above many of the fields in the ``known_other_fields`` tuple\nfollow this convention. If you would like your frontend to mesh nicely with the\nrest of yt's built-in fields, it is probably a good idea to alias your\nfrontend's field names to the yt \"universal\" field names. Finally,\n\"display_name\"`` is an optional parameter that can be used to specify how you\nwant the field to be displayed on a plot; this can be LaTeX code, for example\nthe density field could have a display name of ``r\"\\rho\"``.  Omitting the\n``\"display_name\"`` will result in using a capitalized version of the ``\"name\"``.\n\n\n.. _data-format-detection:\n\nHow to make ``yt.load`` magically detect your data format ?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``yt.load`` takes in a file or directory name, as well as any number of\npositional and keyword arguments. On call, ``yt.load`` attempts to determine\nwhat ``Dataset`` subclasses are compatible with the set of arguments it\nreceived. It does so by passing its arguments to *every* ``Dataset`` subclasses'\n``_is_valid`` method. These methods are intended to be heuristics that quickly\ndetermine whether the arguments (in particular the file/directory) can be loaded\nwith their respective classes. In some cases, more than one class might be\ndetected as valid. If all candidate classes are siblings, ``yt.load`` will\nselect the most specialized one.\n\nWhen writing a new frontend, it is important to write ``_is_valid`` methods to be\nas specific as possible, otherwise one might constrain the design space for\nfuture frontends or in some cases deny their ability to leverage ``yt.load``'s\nmagic.\n\nPerformance is also critical since the new method is going to get called every\nsingle time along with ``yt.load``, even for unrelated data formats.\n\nNote that ``yt.load`` knows about every ``Dataset`` subclass because they are\nautomatically registered on creation.\n\n.. _bfields-frontend:\n\nCreating Aliases for Magnetic Fields\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSetting up access to the magnetic fields in your dataset requires special\nhandling, because in different unit systems magnetic fields have different\ndimensions (see :ref:`bfields` for an explanation). If your dataset includes\nmagnetic fields, you should include them in ``known_other_fields``, but do\nnot set up aliases for them--instead use the special handling function\n:meth:`~yt.fields.magnetic_fields.setup_magnetic_field_aliases`. It takes\nas arguments the ``FieldInfoContainer`` instance, the field type of the\nfrontend, and the list of magnetic fields from the frontend. Here is an\nexample of how this is implemented in the FLASH frontend:\n\n.. code-block:: python\n\n    class FLASHFieldInfo(FieldInfoContainer):\n        known_other_fields = (\n            (\"magx\", (b_units, [], \"B_x\")),  # Note there is no alias here\n            (\"magy\", (b_units, [], \"B_y\")),\n            (\"magz\", (b_units, [], \"B_z\")),\n            ...,\n        )\n\n        def setup_fluid_fields(self):\n            from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n            ...\n            setup_magnetic_field_aliases(self, \"flash\", [\"mag%s\" % ax for ax in \"xyz\"])\n\nThis function should always be imported and called from within the\n``setup_fluid_fields`` method of the ``FieldInfoContainer``. If this\nfunction is used, converting between magnetic fields in different\nunit systems will be handled automatically.\n\nData Localization Structures\n----------------------------\n\nThese functions and classes let yt know about how the arrangement of\ndata on disk corresponds to the physical arrangement of data within\nthe simulation.  yt has grid datastructures for handling both\npatch-based and octree-based AMR codes.  The terms 'patch-based'\nand 'octree-based' are used somewhat loosely here.  For example,\ntraditionally, the FLASH code used the paramesh AMR library, which is\nbased on a tree structure, but the FLASH frontend in yt utilizes yt's\npatch-based datastructures.  It is up to the frontend developer to\ndetermine which yt datastructures best match the datastructures of\ntheir simulation code.\n\nBoth approaches -- patch-based and octree-based -- have a concept of a\n*Hierarchy* or *Index* (used somewhat interchangeably in the code) of\ndatastructures and something that describes the elements that make up\nthe Hierarchy or Index.  For patch-based codes, the Index is a\ncollection of ``AMRGridPatch`` objects that describe a block of zones.\nFor octree-based codes, the Index contains datastructures that hold\ninformation about the individual octs, namely an ``OctreeContainer``.\n\nHierarchy or Index\n^^^^^^^^^^^^^^^^^^\n\nTo set up data localization, a ``GridIndex`` subclass for patch-based\ncodes or an ``OctreeIndex`` subclass for octree-based codes must be\nadded in the file ``data_structures.py``. Examples of these different\ntypes of ``Index`` can be found in, for example, the\n``yt.frontends.chombo.data_structures.ChomboHierarchy`` for patch-based\ncodes and ``yt.frontends.ramses.data_structures.RAMSESIndex`` for\noctree-based codes.\n\nFor the most part, the ``GridIndex`` subclass must override (at a\nminimum) the following methods:\n\n * ``_detect_output_fields()``: ``self.field_list`` must be populated as a list\n   of strings corresponding to \"native\" fields in the data files.\n * ``_count_grids()``: this must set ``self.num_grids`` to be the total number\n   of grids (equivalently ``AMRGridPatch``'es) in the simulation.\n * ``_parse_index()``: this must fill in ``grid_left_edge``,\n   ``grid_right_edge``, ``grid_particle_count``, ``grid_dimensions`` and\n   ``grid_levels`` with the appropriate information.  Each of these variables\n   is an array, with an entry for each of the ``self.num_grids`` grids.\n   Additionally, ``grids``  must be an array of ``AMRGridPatch`` objects that\n   already know their IDs.\n * ``_populate_grid_objects()``: this initializes the grids by calling\n   ``_prepare_grid()`` and ``_setup_dx()`` on all of them.  Additionally, it\n   should set up ``Children`` and ``Parent`` lists on each grid object.\n\nThe ``OctreeIndex`` has somewhat analogous methods, but often with\ndifferent names; both ``OctreeIndex`` and ``GridIndex`` are subclasses\nof the ``Index`` class.  In particular, for the ``OctreeIndex``, the\nmethod ``_initialize_oct_handler()`` setups up much of the oct\nmetadata that is analogous to the grid metadata created in the\n``GridIndex`` methods ``_count_grids()``, ``_parse_index()``, and\n``_populate_grid_objects()``.\n\nGrids\n^^^^^\n\n.. note:: This section only applies to the approach using yt's patch-based\n\t  datastructures.  For the octree-based approach, one does not create\n\t  a grid object, but rather an ``OctreeSubset``, which has methods\n\t  for filling out portions of the octree structure.  Again, see the\n\t  code in ``yt.frontends.ramses.data_structures`` for an example of\n\t  the octree approach.\n\nA new grid object, subclassing ``AMRGridPatch``, will also have to be added in\n``data_structures.py``. For the most part, this may be all\nthat is needed:\n\n.. code-block:: python\n\n    class ChomboGrid(AMRGridPatch):\n        _id_offset = 0\n        __slots__ = [\"_level_id\"]\n\n        def __init__(self, id, index, level=-1):\n            AMRGridPatch.__init__(self, id, filename=index.index_filename, index=index)\n            self.Parent = None\n            self.Children = []\n            self.Level = level\n\n\nEven one of the more complex grid objects,\n``yt.frontends.amrex.BoxlibGrid``, is still relatively simple.\n\nData Reading Functions\n----------------------\n\nIn ``io.py``, there are a number of IO handlers that handle the\nmechanisms by which data is read off disk.  To implement a new data\nreader, you must subclass ``BaseIOHandler``.  The various frontend IO\nhandlers are stored in an IO registry - essentially a dictionary that\nuses the name of the frontend as a key, and the specific IO handler as\na value.  It is important, therefore, to set the ``dataset_type``\nattribute of your subclass, which is what is used as the key in the IO\nregistry.  For example:\n\n.. code-block:: python\n\n    class IOHandlerBoxlib(BaseIOHandler):\n        _dataset_type = \"boxlib_native\"\n\t...\n\nAt a minimum, one should also override the following methods\n\n* ``_read_fluid_selection()``: this receives a collection of data \"chunks\", a\n  selector describing which \"chunks\" you are concerned with, a list of fields,\n  and the size of the data to read.  It should create and return a dictionary\n  whose keys are the fields, and whose values are numpy arrays containing the\n  data.  The data should actually be read via the ``_read_chunk_data()``\n  method.\n* ``_read_chunk_data()``: this method receives a \"chunk\" of data along with a\n  list of fields we want to read.  It loops over all the grid objects within\n  the \"chunk\" of data and reads from disk the specific fields, returning a\n  dictionary whose keys are the fields and whose values are numpy arrays of\n  the data.\n\nIf your dataset has particle information, you'll want to override the\n``_read_particle_coords()`` and ``read_particle_fields()`` methods as\nwell.  Each code is going to read data from disk in a different\nfashion, but the ``yt.frontends.amrex.io.IOHandlerBoxlib`` is a\ndecent place to start.\n\nAnd that just about covers it. Please feel free to email\n`yt-users <https://mail.python.org/archives/list/yt-users@python.org/>`_ or\n`yt-dev <https://mail.python.org/archives/list/yt-dev@python.org/>`_ with\nany questions, or to let us know you're thinking about adding a new code to yt.\n\n\nHow to add extra dependencies ?\n-------------------------------\n\n.. note:: This section covers the technical details of how optional runtime\n   dependencies are implemented and used in yt.\n   If your frontend has specific or complicated dependencies other than yt's,\n   we advise writing your frontend as an extension package :ref:`frontends-as-extensions`\n\nIt is required that a specific target be added to ``pyproject.toml`` to define a list\nof additional requirements (even if empty), see :ref:`install-additional`.\n\nAt runtime, extra third party dependencies should be loaded lazily, meaning their import\nneeds to be delayed until actually needed. This is achieved by importing a wrapper from\n``yt.utitilies.on_demand_imports.py``, instead of the actual package like so\n\n.. code-block:: python\n\n    from yt.utilities.on_demand_imports import _mypackage as mypackage\n\nSuch import statements can live at the top of a module without generating overhead or errors\nin case the actual package isn't installed.\n\nIf the extra third party dependency is new, a new import wrapper must also be added. To do so,\nfollow the example of the existing wrappers in ``yt.utilities.on_demand_imports.py``.\n"
  },
  {
    "path": "doc/source/developing/debugdrive.rst",
    "content": ".. _debug-drive:\n\nDebugging yt\n============\n\nThere are several different convenience functions that allow you to control yt\nin perhaps unexpected and unorthodox manners.  These will allow you to conduct\nin-depth debugging of processes that may be running in parallel on multiple\nprocessors, as well as providing a mechanism of signalling to yt that you need\nmore information about a running process.  Additionally, yt has a built-in\nmechanism for optional reporting of errors to a central server.  All of these\nallow for more rapid development and debugging of any problems you might\nencounter.\n\nAdditionally, yt is able to leverage existing developments in the IPython\ncommunity for parallel, interactive analysis.  This allows you to initialize\nmultiple yt processes through ``mpirun`` and interact with all of them from a\nsingle, unified interactive prompt.  This enables and facilitates parallel\nanalysis without sacrificing interactivity and flexibility.\n\n.. _pastebin:\n\nPastebin\n--------\n\nA pastebin is a website where you can easily copy source code and error\nmessages to share with yt developers or your collaborators. At\nhttp://paste.yt-project.org/ a pastebin is available for placing scripts.  With\nyt the script ``yt_lodgeit.py`` is distributed and wrapped with\nthe ``pastebin`` and ``pastebin_grab`` commands, which allow for commandline\nuploading and downloading of pasted snippets.  To upload a script you\nwould supply it to the command:\n\n.. code-block:: bash\n\n   $ yt pastebin some_script.py\n\nThe URL will be returned.  If you'd like it to be marked 'private' and not show\nup in the list of pasted snippets, supply the argument ``--private``.  All\nsnippets are given either numbers or hashes.  To download a pasted snippet, you\nwould use the ``pastebin_grab`` option:\n\n.. code-block:: bash\n\n   $ yt pastebin_grab 1768\n\nThe snippet will be output to the window, so output redirection can be used to\nstore it in a file.\n\nUse the Python Debugger\n-----------------------\n\nyt is almost entirely composed of python code, so it makes sense to use\nthe `python debugger`_ as your first stop in trying to debug it.\n\n.. _python debugger: https://docs.python.org/3/library/pdb.html\n\nSignaling yt to Do Something\n----------------------------\n\nDuring startup, yt inserts handlers for two operating system-level signals.\nThese provide two diagnostic methods for interacting with a running process.\nSignalling the python process that is running your script with these signals\nwill induce the requested behavior.\n\n   SIGUSR1\n     This will cause the python code to print a stack trace, showing exactly\n     where in the function stack it is currently executing.\n   SIGUSR2\n     This will cause the python code to insert an IPython session wherever it\n     currently is, with all local variables in the local namespace.  It should\n     allow you to change the state variables.\n\nIf your yt-running process has PID 5829, you can signal it to print a\ntraceback with:\n\n.. code-block:: bash\n\n   $ kill -SIGUSR1 5829\n\nNote, however, that if the code is currently inside a C function, the signal\nwill not be handled, and the stacktrace will not be printed, until it returns\nfrom that function.\n\n.. _remote-debugging:\n\nRemote and Disconnected Debugging\n---------------------------------\n\nIf you are running a parallel job that fails, often it can be difficult to do a\npost-mortem analysis to determine what went wrong.  To facilitate this, yt\nhas implemented an `XML-RPC <https://en.wikipedia.org/wiki/XML-RPC>`_ interface\nto the Python debugger (``pdb``) event loop.\n\nRunning with the ``--rpdb`` command will cause any uncaught exception during\nexecution to spawn this interface, which will sit and wait for commands,\nexposing the full Python debugger.  Additionally, a frontend to this is\nprovided through the yt command.  So if you run the command:\n\n.. code-block:: bash\n\n   $ mpirun -np 4 python some_script.py --parallel --rpdb\n\nand it reaches an error or an exception, it will launch the debugger.\nAdditionally, instructions will be printed for connecting to the debugger.\nEach of the four processes will be accessible via:\n\n.. code-block:: bash\n\n   $ yt rpdb 0\n\nwhere ``0`` here indicates the process 0.\n\nFor security reasons, this will only work on local processes; to connect on a\ncluster, you will have to execute the command ``yt rpdb`` on the node on which\nthat process was launched.\n"
  },
  {
    "path": "doc/source/developing/deprecating_features.rst",
    "content": ".. how-to-deprecate::\n\nHow to deprecate a feature\n--------------------------\n\nSince the 4.0.0 release, deprecation happens on a per-release basis.\nA functionality can be marked as deprecated using\n``~yt._maintenance.deprecation.issue_deprecation_warning``, which takes a warning\nmessage and two version numbers, indicating the earliest release deprecating the feature\nand the one in which it will be removed completely.\n\nThe message should indicate a viable alternative to replace the deprecated\nfeature at the user level. ``since`` and ``removal`` arguments should indicate\nin which release something was first deprecated, and when it's expected to be\nremoved. While ``since`` is required, ``removal`` is optional.\n\nHere's an example call.\n\n.. code-block::python\n\n    def old_function(*args, **kwargs):\n        from yt._maintenance.deprecation import issue_deprecation_warning\n        issue_deprecation_warning(\n            \"`old_function` is deprecated, use `replacement_function` instead.\"\n            stacklevel=3,\n            since=\"4.0\",\n            removal=\"4.1.0\",\n        )\n        ...\n\nIf a whole function or class is marked as deprecated, it should be removed from\n``doc/source/reference/api/api.rst``.\n\n\nDeprecating Derived Fields\n--------------------------\n\nOccasionally, one may want to deprecate a derived field in yt, normally\nbecause naming conventions for fields have changed, or simply because a\nfield has outlived its usefulness. There are two ways to mark fields as\ndeprecated in yt.\n\nThe first way is if you simply want to mark a specific derived field as\ndeprecated. In that case, you call\n:meth:`~yt.fields.field_info_container.FieldInfoContainer.add_deprecated_field`:\n\n.. code-block:: python\n\n    def _cylindrical_radial_absolute(data):\n        \"\"\"This field is deprecated and will be removed in a future version\"\"\"\n        return np.abs(data[ftype, f\"{basename}_cylindrical_radius\"])\n\n\n    registry.add_deprecated_field(\n        (ftype, f\"cylindrical_radial_{basename}_absolute\"),\n        sampling_type=\"local\",\n        function=_cylindrical_radial_absolute,\n        since=\"4.0\",\n        removal=\"4.1.0\",\n        units=field_units,\n        validators=[ValidateParameter(\"normal\")],\n    )\n\nNote that the signature for\n:meth:`~yt.fields.field_info_container.FieldInfoContainer.add_deprecated_field`\nis the same as :meth:`~yt.fields.field_info_container.FieldInfoContainer.add_field`,\nwith the exception of the ``since`` and ``removal`` arguments which indicate in\nwhat version the field was deprecated and in what version it will be removed.\nThe effect is to add a warning to the logger when the field is first used:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0100\")\n    sp = ds.sphere(\"c\", (100.0, \"kpc\"))\n    print(sp[\"gas\", \"cylindrical_radial_velocity_absolute\"])\n\n.. code-block:: pycon\n\n    yt : [WARNING  ] 2021-03-09 16:30:47,460 The Derived Field\n    ('gas', 'cylindrical_radial_velocity_absolute') is deprecated\n    as of yt v4.0.0 and will be removed in yt v4.1.0\n\nThe second way to deprecate a derived field is to take an existing field\ndefinition and change its name. In order to mark the original name as deprecated,\nuse the :meth:`~yt.fields.field_info_container.FieldInfoContainer.alias` method\nand pass the ``since`` and ``removal`` arguments (see above) as a tuple in the\n``deprecate`` keyword argument:\n\n.. code-block:: python\n\n    registry.alias(\n        (ftype, \"kinetic_energy\"),\n        (ftype, \"kinetic_energy_density\"),\n        deprecate=(\"4.0.0\", \"4.1.0\"),\n    )\n\nNote that the old field name which is to be deprecated goes first, and the new,\nreplacement field name goes second. In this case, the log message reports to\nthe user what field they should use:\n\n.. code-block:: python\n\n    print(sp[\"gas\", \"kinetic_energy\"])\n\n.. code-block:: pycon\n\n    yt : [WARNING  ] 2021-03-09 16:29:12,911 The Derived Field\n    ('gas', 'kinetic_energy') is deprecated as of yt v4.0.0 and will be removed\n    in yt v4.1.0 Use ('gas', 'kinetic_energy_density') instead.\n\nIn most cases, the ``since`` and ``removal`` arguments should have a delta of\none minor release, and that should be the minimum value. However, the developer\nis free to use their judgment about whether or not the delta should be multiple\nminor releases if the field has a long provenance.\n"
  },
  {
    "path": "doc/source/developing/developing.rst",
    "content": ".. include:: ../../../CONTRIBUTING.rst\n"
  },
  {
    "path": "doc/source/developing/extensions.rst",
    "content": ".. _extensions:\n\nExtension Packages\n==================\n\n.. note:: For some additional discussion, see `YTEP-0029\n          <https://ytep.readthedocs.io/en/master/YTEPs/YTEP-0029.html>`_, where\n          this plan was designed.\n\nAs of version 3.3 of yt, we have put into place new methods for easing the\nprocess of developing \"extensions\" to yt.  Extensions might be analysis\npackages, visualization tools, or other software projects that use yt as a base\nengine but that are versioned, developed and distributed separately.  This\nbrings with it the advantage of retaining control over the versioning,\ncontribution guidelines, scope, etc, while also providing a mechanism for\ndisseminating information about it, and potentially a method of interacting\nwith other extensions.\n\nWe have created a few pieces of infrastructure for developing extensions,\nmaking them discoverable, and distributing them to collaborators.\n\nIf you have a module you would like to retain some external control over, or\nthat you don't feel would fit into yt, we encourage you to build it as an\nextension module and distribute and version it independently.\n\nHooks for Extensions\n--------------------\n\nStarting with version 3.3 of yt, any package named with the prefix ``yt_`` is\nimportable from the namespace ``yt.extensions``.  For instance, the\n``yt_interaction`` package ( https://bitbucket.org/data-exp-lab/yt_interaction\n) is importable as ``yt.extensions.interaction``.\n\nIn subsequent versions, we plan to include in yt a catalog of known extensions\nand where to find them; this will put discoverability directly into the code\nbase.\n\n\n.. _frontends-as-extensions:\n\nFrontends as extensions\n-----------------------\n\nStarting with version 4.2 of yt, any externally installed package that exports\n:class:`~yt.data_objects.static_output.Dataset` subclass as an entrypoint in\n``yt.frontends`` namespace in ``setup.py`` or ``pyproject.toml`` will be\nautomatically loaded and immediately available in :func:`~yt.loaders.load`.\n\nTo add an entrypoint in an external project's ``setup.py``:\n\n.. code-block:: python\n\n   setup(\n       # ...,\n       entry_points={\n           \"yt.frontends\": [\n               \"myFrontend = my_frontend.api.MyFrontendDataset\",\n               \"myOtherFrontend = my_frontend.api.MyOtherFrontendDataset\",\n           ]\n       }\n   )\n\nor ``pyproject.toml``:\n\n.. code-block:: toml\n\n   [project.entry-points.\"yt.frontends\"]\n   myFrontend = \"my_frontend.api:MyFrontendDataset\"\n   myOtherFrontend = \"my_frontend.api:MyOtherFrontendDataset\"\n\n\nExtension Template\n------------------\n\nA template for starting an extension module (or converting an existing set of\ncode to an extension module) can be found at\nhttps://github.com/yt-project/yt_extension_template .\n\nTo get started, download a zipfile of the template (\nhttps://codeload.github.com/yt-project/yt_extension_template/zip/master ) and\nfollow the directions in ``README.md`` to modify the metadata.\n\nDistributing Extensions\n-----------------------\n\nWe encourage you to version on your choice of hosting platform (Bitbucket,\nGitHub, etc), and to distribute your extension widely.  We are presently\nworking on deploying a method for listing extension modules on the yt webpage.\n"
  },
  {
    "path": "doc/source/developing/external_analysis.rst",
    "content": ".. _external-analysis-tools:\n\nUsing yt with External Analysis Tools\n=====================================\n\nyt can be used as a ``glue`` code between simulation data and other methods of\nanalyzing data.  Its facilities for understanding units, disk IO and data\nselection set it up ideally to use other mechanisms for analyzing, processing\nand visualizing data.\n\nCalling External Python Codes\n-----------------------------\n\nCalling external Python codes very straightforward.  For instance, if you had a\nPython code that accepted a set of structured meshes and then post-processed\nthem to apply radiative feedback, one could imagine calling it directly:\n\n.. code-block:: python\n\n   import radtrans\n\n   import yt\n\n   ds = yt.load(\"DD0010/DD0010\")\n   rt_grids = []\n\n   for grid in ds.index.grids:\n       rt_grid = radtrans.RegularBox(\n           grid.LeftEdge,\n           grid.RightEdge,\n           grid[\"density\"],\n           grid[\"temperature\"],\n           grid[\"metallicity\"],\n       )\n       rt_grids.append(rt_grid)\n       grid.clear_data()\n\n   radtrans.process(rt_grids)\n\nOr if you wanted to run a population synthesis module on a set of star\nparticles (and you could fit them all into memory) it might look something like\nthis:\n\n.. code-block:: python\n\n   import pop_synthesis\n\n   import yt\n\n   ds = yt.load(\"DD0010/DD0010\")\n   ad = ds.all_data()\n   star_masses = ad[\"StarMassMsun\"]\n   star_metals = ad[\"StarMetals\"]\n\n   pop_synthesis.CalculateSED(star_masses, star_metals)\n\nIf you have a code that's written in Python that you are having trouble getting\ndata into from yt, please feel encouraged to email the users list and we'll\nhelp out.\n\nCalling Non-Python External Codes\n---------------------------------\n\nIndependent of its ability to process, analyze and visualize data, yt can also\nserve as a mechanism for reading and selecting simulation data.  In this way,\nit can be used to supply data to an external analysis routine written in\nFortran, C or C++.  This document describes how to supply that data, using the\nexample of a simple code that calculates the best axes that describe a\ndistribution of particles as a starting point.  (The underlying method is left\nas an exercise for the reader; we're only currently interested in the function\nspecification and structs.)\n\nIf you have written a piece of code that performs some analysis function, and\nyou would like to include it in the base distribution of yt, we would be happy\nto do so; drop us a line or see :ref:`contributing-code` for more information.\n\nTo accomplish the process of linking Python with our external code, we will be\nusing a language called `Cython <https://cython.org/>`_, which is\nessentially a superset of Python that compiles down to C.  It is aware of NumPy\narrays, and it is able to massage data between the interpreted language Python\nand C, Fortran or C++.  It will be much easier to utilize routines and analysis\ncode that have been separated into subroutines that accept data structures, so\nwe will assume that our halo axis calculator accepts a set of structs.\n\nOur Example Code\n++++++++++++++++\n\nHere is the ``axes.h`` file in our imaginary code, which we will then wrap:\n\n.. code-block:: c\n\n   typedef struct structParticleCollection {\n        long npart;\n        double *xpos;\n        double *ypos;\n        double *zpos;\n   } ParticleCollection;\n\n   void calculate_axes(ParticleCollection *part,\n            double *ax1, double *ax2, double *ax3);\n\nThere are several components to this analysis routine which we will have to\nwrap.\n\n#. We have to wrap the creation of an instance of ``ParticleCollection``.\n#. We have to transform a set of NumPy arrays into pointers to doubles.\n#. We have to create a set of doubles into which ``calculate_axes`` will be\n   placing the values of the axes it calculates.\n#. We have to turn the return values back into Python objects.\n\nEach of these steps can be handled in turn, and we'll be doing it using Cython\nas our interface code.\n\nSetting Up and Building Our Wrapper\n+++++++++++++++++++++++++++++++++++\n\nTo get started, we'll need to create two files:\n\n.. code-block:: bash\n\n   axes_calculator.pyx\n   axes_calculator_setup.py\n\nThese can go anywhere, but it might be useful to put them in their own\ndirectory.  The contents of ``axes_calculator.pyx`` will be left for the next\nsection, but we will need to put some boilerplate code into\n``axes_calculator_setup.pyx``.  As a quick sidenote, you should call these\nwhatever is most appropriate for the external code you are wrapping;\n``axes_calculator`` is probably not the best bet.\n\nHere's a rough outline of what should go in ``axes_calculator_setup.py``:\n\n.. code-block:: python\n\n   NAME = \"axes_calculator\"\n   EXT_SOURCES = []\n   EXT_LIBRARIES = [\"axes_utils\", \"m\"]\n   EXT_LIBRARY_DIRS = [\"/home/rincewind/axes_calculator/\"]\n   EXT_INCLUDE_DIRS = []\n   DEFINES = []\n\n   from distutils.core import setup\n   from distutils.extension import Extension\n\n   from Cython.Distutils import build_ext\n\n   ext_modules = [\n       Extension(\n           NAME,\n           [NAME + \".pyx\"] + EXT_SOURCES,\n           libraries=EXT_LIBRARIES,\n           library_dirs=EXT_LIBRARY_DIRS,\n           include_dirs=EXT_INCLUDE_DIRS,\n           define_macros=DEFINES,\n       )\n   ]\n\n   setup(name=NAME, cmdclass={\"build_ext\": build_ext}, ext_modules=ext_modules)\n\nThe only variables you should have to change in this are the first six, and\npossibly only the first one.  We'll go through these variables one at a time.\n\n``NAME``\n   This is the name of our source file, minus the ``.pyx``.  We're also\n   mandating that it be the name of the module we import.  You're free to\n   modify this.\n``EXT_SOURCES``\n   Any additional sources can be listed here.  For instance, if you are only\n   linking against a single ``.c`` file, you could list it here -- if our axes\n   calculator were fully contained within a file called ``calculate_my_axes.c``\n   we could link against it using this variable, and then we would not have to\n   specify any libraries.  This is usually the simplest way to do things, and in\n   fact, yt makes use of this itself for things like HEALPix and interpolation\n   functions.\n``EXT_LIBRARIES``\n   Any libraries that will need to be linked against (like ``m``!) should be\n   listed here.  Note that these are the name of the library minus the leading\n   ``lib`` and without the trailing ``.so``.  So ``libm.so`` would become ``m``\n   and ``libluggage.so`` would become ``luggage``.\n``EXT_LIBRARY_DIRS``\n   If the libraries listed in ``EXT_LIBRARIES`` reside in some other directory\n   or directories, those directories should be listed here.  For instance,\n   ``[\"/usr/local/lib\", \"/home/rincewind/luggage/\"]`` .\n``EXT_INCLUDE_DIRS``\n   If any header files have been included that live in external directories,\n   those directories should be included here.\n``DEFINES``\n   Any define macros that should be passed to the C compiler should be listed\n   here; if they just need to be defined, then they should be specified to be\n   defined as \"None.\"  For instance, if you wanted to pass ``-DTWOFLOWER``, you\n   would set this to equal: ``[(\"TWOFLOWER\", None)]``.\n\nTo build our extension, we would run:\n\n.. code-block:: bash\n\n   $ python axes_calculator_setup.py build_ext -i\n\nNote that since we don't yet have an ``axes_calculator.pyx``, this will fail.\nBut once we have it, it ought to run.\n\nWriting and Calling our Wrapper\n+++++++++++++++++++++++++++++++\n\nNow we begin the tricky part, of writing our wrapper code.  We've already\nfigured out how to build it, which is halfway to being able to test that it\nworks, and we now need to start writing Cython code.\n\nFor a more detailed introduction to Cython, see the Cython documentation at\nhttp://docs.cython.org/en/latest/ .  We'll cover a few of the basics for wrapping code\nhowever.\n\nTo start out with, we need to open up and edit our file,\n``axes_calculator.pyx``.  Open this in your favorite version of vi (mine is\nvim) and we will get started by declaring the struct we need to pass in.  But\nfirst, we need to include some header information:\n\n.. code-block:: cython\n\n   import numpy as np\n   cimport numpy as np\n   cimport cython\n   from stdlib cimport malloc, free\n\nThese lines simply import and \"Cython import\" some common routines.  For more\ninformation about what is already available, see the Cython documentation.  For\nnow, we need to start translating our data.\n\nTo do so, we tell Cython both where the struct should come from, and then we\ndescribe the struct itself.  One fun thing to note is that if you don't need to\nset or access all the values in a struct, and it just needs to be passed around\nopaquely, you don't have to include them in the definition.  For an example of\nthis, see the ``png_writer.pyx`` file in the yt repository.  Here's the syntax\nfor pulling in (from a file called ``axes_calculator.h``) a struct like the one\ndescribed above:\n\n.. code-block:: cython\n\n   cdef extern from \"axes_calculator.h\":\n       ctypedef struct ParticleCollection:\n           long npart\n           double *xpos\n           double *ypos\n           double *zpos\n\nSo far, pretty easy!  We've basically just translated the declaration from the\n``.h`` file.  Now that we have done so, any other Cython code can create and\nmanipulate these ``ParticleCollection`` structs -- which we'll do shortly.\nNext up, we need to declare the function we're going to call, which looks\nnearly exactly like the one in the ``.h`` file.  (One common problem is that\nCython doesn't know what ``const`` means, so just remove it wherever you see\nit.)  Declare it like so:\n\n.. code-block:: cython\n\n       void calculate_axes(ParticleCollection *part,\n                double *ax1, double *ax2, double *ax3)\n\nNote that this is indented one level, to indicate that it, too, comes from\n``axes_calculator.h``.  The next step is to create a function that accepts\narrays and converts them to the format the struct likes.  We declare our\nfunction just like we would a normal Python function, using ``def``.  You can\nalso use ``cdef`` if you only want to call a function from within Cython.  We\nwant to call it from Python, too, so we just use ``def``.  Note that we don't\nhere specify types for the various arguments.  In a moment we'll refine this to\nhave better argument types.\n\n.. code-block:: cython\n\n   def examine_axes(xpos, ypos, zpos):\n       cdef double ax1[3], ax2[3], ax3[3]\n       cdef ParticleCollection particles\n       cdef int i\n\n       particles.npart = len(xpos)\n       particles.xpos = <double *> malloc(particles.npart * sizeof(double))\n       particles.ypos = <double *> malloc(particles.npart * sizeof(double))\n       particles.zpos = <double *> malloc(particles.npart * sizeof(double))\n\n       for i in range(particles.npart):\n           particles.xpos[i] = xpos[i]\n           particles.ypos[i] = ypos[i]\n           particles.zpos[i] = zpos[i]\n\n       calculate_axes(&particles, ax1, ax2, ax3)\n\n       free(particles.xpos)\n       free(particles.ypos)\n       free(particles.zpos)\n\n       return ( (ax1[0], ax1[1], ax1[2]),\n                (ax2[0], ax2[1], ax2[2]),\n                (ax3[0], ax3[1], ax3[2]) )\n\nThis does the rest.  Note that we've weaved in C-type declarations (ax1, ax2,\nax3) and Python access to the variables fed in.  This function will probably be\nquite slow -- because it doesn't know anything about the variables xpos, ypos,\nzpos, it won't be able to speed up access to them.  Now we will see what we can\ndo by declaring them to be of array-type before we start handling them at all.\nWe can do that by annotating in the function argument list.  But first, let's\ntest that it works.  From the directory in which you placed these files, run:\n\n.. code-block:: bash\n\n   $ python2.6 setup.py build_ext -i\n\nNow, create a sample file that feeds in the particles:\n\n.. code-block:: python\n\n    import axes_calculator\n\n    axes_calculator.examine_axes(xpos, ypos, zpos)\n\nMost of the time in that function is spent in converting the data.  So now we\ncan go back and we'll try again, rewriting our converter function to believe\nthat its being fed arrays from NumPy:\n\n.. code-block:: cython\n\n   def examine_axes(np.ndarray[np.float64_t, ndim=1] xpos,\n                    np.ndarray[np.float64_t, ndim=1] ypos,\n                    np.ndarray[np.float64_t, ndim=1] zpos):\n       cdef double ax1[3], ax2[3], ax3[3]\n       cdef ParticleCollection particles\n       cdef int i\n\n       particles.npart = len(xpos)\n       particles.xpos = <double *> malloc(particles.npart * sizeof(double))\n       particles.ypos = <double *> malloc(particles.npart * sizeof(double))\n       particles.zpos = <double *> malloc(particles.npart * sizeof(double))\n\n       for i in range(particles.npart):\n           particles.xpos[i] = xpos[i]\n           particles.ypos[i] = ypos[i]\n           particles.zpos[i] = zpos[i]\n\n       calculate_axes(&particles, ax1, ax2, ax3)\n\n       free(particles.xpos)\n       free(particles.ypos)\n       free(particles.zpos)\n\n       return ( (ax1[0], ax1[1], ax1[2]),\n                (ax2[0], ax2[1], ax2[2]),\n                (ax3[0], ax3[1], ax3[2]) )\n\nThis should be substantially faster, assuming you feed it arrays.\n\nNow, there's one last thing we can try.  If we know our function won't modify\nour arrays, and they are C-Contiguous, we can simply grab pointers to the data:\n\n.. code-block:: cython\n\n   def examine_axes(np.ndarray[np.float64_t, ndim=1] xpos,\n                    np.ndarray[np.float64_t, ndim=1] ypos,\n                    np.ndarray[np.float64_t, ndim=1] zpos):\n       cdef double ax1[3], ax2[3], ax3[3]\n       cdef ParticleCollection particles\n       cdef int i\n\n       particles.npart = len(xpos)\n       particles.xpos = <double *> xpos.data\n       particles.ypos = <double *> ypos.data\n       particles.zpos = <double *> zpos.data\n\n       for i in range(particles.npart):\n           particles.xpos[i] = xpos[i]\n           particles.ypos[i] = ypos[i]\n           particles.zpos[i] = zpos[i]\n\n       calculate_axes(&particles, ax1, ax2, ax3)\n\n       return ( (ax1[0], ax1[1], ax1[2]),\n                (ax2[0], ax2[1], ax2[2]),\n                (ax3[0], ax3[1], ax3[2]) )\n\nBut note!  This will break or do weird things if you feed it arrays that are\nnon-contiguous.\n\nAt this point, you should have a mostly working piece of wrapper code.  And it\nwas pretty easy!  Let us know if you run into any problems, or if you are\ninterested in distributing your code with yt.\n\nA complete set of files is available with this documentation.  These are\nslightly different, so that the whole thing will simply compile, but they\nprovide a useful example.\n\n * `axes.c <../_static/axes.c>`_\n * `axes.h <../_static/axes.h>`_\n * `axes_calculator.pyx <../_static/axes_calculator.pyx>`_\n * `axes_calculator_setup.py <../_static/axes_calculator_setup.txt>`_\n\nExporting Data from yt\n----------------------\n\nyt is installed alongside h5py.  If you need to export your data from yt, to\nshare it with people or to use it inside another code, h5py is a good way to do\nso.  You can write out complete datasets with just a few commands.  You have to\nimport, and then save things out into a file.\n\n.. code-block:: python\n\n   import h5py\n\n   f = h5py.File(\"some_file.h5\", mode=\"w\")\n   f.create_dataset(\"/data\", data=some_data)\n\nThis will create ``some_file.h5`` if necessary and add a new dataset\n(``/data``) to it.  Writing out in ASCII should be relatively straightforward.\nFor instance:\n\n.. code-block:: python\n\n   f = open(\"my_file.txt\", \"w\")\n   for halo in halos:\n       x, y, z = halo.center_of_mass()\n       f.write(\"%0.2f %0.2f %0.2f\\n\", x, y, z)\n   f.close()\n\nThis example could be extended to work with any data object's fields, as well.\n"
  },
  {
    "path": "doc/source/developing/index.rst",
    "content": "Developing in yt\n================\n\nyt is an open-source project with a community of contributing scientists.\nWhile you can use the existing framework within yt to help answer questions\nabout your own datasets, yt thrives by the addition of new functionality by\nusers just like yourself.  Maybe you have a new data format that you would like\nsupported, a new derived quantity that you feel should be included, or a new\nway of visualizing data--please add them to the code base!  We are eager to\nhelp you make it happen.\n\nThere are many ways to get involved with yt -- participating in the mailing\nlist, helping people out in IRC, providing suggestions for the documentation,\nand contributing code!\n\n.. toctree::\n   :maxdepth: 2\n\n   developing\n   building_the_docs\n   testing\n   extensions\n   debugdrive\n   releasing\n   creating_datatypes\n   creating_derived_fields\n   creating_frontend\n   external_analysis\n   deprecating_features\n"
  },
  {
    "path": "doc/source/developing/releasing.rst",
    "content": "How to Do a Release\n-------------------\n\nPeriodically, the yt development community issues new releases. yt loosely follows\n`semantic versioning <https://semver.org/>`_. The type of release can be read off\nfrom the version number used. Version numbers should follow the scheme\n``MAJOR.MINOR.PATCH``. There are three kinds of possible releases:\n\n* Bugfix releases\n\n  These releases should contain only fixes for bugs discovered in\n  earlier releases and should not contain new features or API changes. Bugfix\n  releases only increment the ``PATCH`` version number. Bugfix releases are\n  generated from a dedicated backport branch that contains cherry-picked\n  commits for bug fixes (handled semi-automatically as pull requests are\n  merged), see :ref:`doing-a-bugfix-release` for more details. Version\n  ``3.2.2`` is a bugfix release.\n\n* Minor releases\n\n  Minor releases include new features and fixes for bugs if the fix is\n  determined to be too invasive for a bugfix release. Minor releases\n  should *not* include backwards-incompatible changes and should not change APIs.  If an API change\n  is deemed to be necessary, the old API should continue to function but might\n  trigger deprecation warnings. Minor releases should happen by creating a new series\n  branch off of ``main`` branch. Minor releases should increment the\n  ``MINOR`` version number and reset the ``PATCH`` version number to zero.\n  Version ``3.3.0`` is a minor release and has a corresponding series branch ``yt-3.3.x``.\n  After release, the new series branch becomes the backport branch for any bug fix releases\n  in the series.\n\n* Major releases\n\n  These releases happen when the development community decides to make major\n  backwards-incompatible changes intentionally. In principle a major version release could\n  include arbitrary changes to the library or remove previously deprecated features. Major version releases\n  should only happen after extensive discussion and vetting among the developer and user\n  community. Like minor releases, a major release should happen by creating\n  a new series branch off of the ``main`` branch from which to release. Major releases should\n  increment the ``MAJOR`` version number and reset the ``MINOR`` and ``PATCH``\n  version numbers to zero. Version ``4.0.0`` is a major release.\n\nThe job of doing a release differs depending on the kind of release. Below, we\ndescribe the necessary steps for each kind of release in detail. Several of the\nfollowing steps require that you have write privileges for the main yt GitHub\nrepository (if you're reading this, you probably already do).\n\n.. note::\n\n  This documentation assumes that your local copy of the yt repository has the\n  main yt repository as the ``upstream`` remote (the default configuration when\n  forking with ``gh fork yt-project/yt``). You can double check with:\n  ``git remote -v``, which will display all the remote sources you have setup\n  as well as their addresses. If you are missing an ``upstream`` remote (for\n  example, after a standard ``git clone`` of your yt fork), you can add it\n  with ``git remote add upstream git@github.com:yt-project/yt.git``.\n\n.. _prepping-release-notes:\n\nPrepping Release Notes\n~~~~~~~~~~~~~~~~~~~~~~\n\nBefore starting the release process, it's useful to create a draft of a GitHub release. Doing this\nat the start lets you share the upcoming release notes with other developers before the release\nactually happens. To create a new draft release, go to\nhttps://github.com/yt-project/yt/releases and click \"Draft a new release\". Use the version tag\nas the release title (e.g., ``yt-4.4.1``). For the target tag, enter the tag that you will use\n(see  :ref:`tagging-a-release-release`) and select the branch from which the release will be cut.\n\nNow it's time to generate some release notes. To get a nice starting point, try using the ``uv`` script\n`draft_yt_release_notes.py <https://gist.github.com/chrishavlin/248adea4296abb7bcdbaac952f304cf0>`_. You can\nrun the script directly from the gist using ``uv`` and the url to the raw script (check the readme at the gist).\nThis script will pull all the issues and pull requests that have been tagged to a specified GitHub milestone\nand do some initial categorizing to produce a decent draft for release notes. You can then create a draft\nrelease manually via the GitHub interface and copy in your draft notes, or if using ``gh``, you can do so\nfrom the command line with ``gh release create --draft --notes-file <file> --target <branch>``. The initial\nnotes created by the ``draft_yt_release_notes.py`` will still need some manual attention: you should update\nthe release summary text, add any highlights if desired, add PRs that are missing. At present, you'll also\nneed to manually sort the frontend-specific changes by frontend to match previous release notes.\n\nWhen updating the draft via the GitHub interface, click \"save\" at any point to save a draft.\nDo NOT publish the draft yet.\n\n.. _doing-a-bugfix-release:\n\nDoing a Bugfix Release\n~~~~~~~~~~~~~~~~~~~~~~\n\nAs described above, bugfix releases are regularly scheduled updates for minor\nreleases to ensure fixes for bugs make their way out to users in a timely\nmanner. Since bugfix releases should not include new features, we do not issue\nbugfix releases by simply merging from the development ``main`` branch into\nthe designated backport branch for the series. Instead, commits are cherry-picked\nfrom the ``main`` branch to a backport branch, and the backport branch is tagged\nwith the release tags.\n\nBackport branches are named after the minor version they descend from, followed by\nan ``x``. For instance, ``yt-4.0.x`` is the backport branch for all releases in the 4.0 series.\nThe backport branches are initially created during the release of a new minor or major\nversion (see :ref:`doing-a-minor-or-major-release`).\n\nBackporting bugfixes can be done automatically using the `MeeseeksBox bot\n<https://meeseeksbox.github.io>`_ with a GitHub milestone linked to a backport branch.\nTo set up a new milestone linked to a backport branch, click the New Milestone button\non the `GitHub interface <https://github.com/yt-project/yt/milestones>`_` then name it\nwith the version for the future release and add the following within the description field:\n``on-merge: backport to <name of backport branch>``, for example:\n``on-merge: backport to yt-4.0.x``.\n\nThen, every PR that was triaged into the milestone will be replicated as a\nbackport PR by the bot when it's merged into main. Some backports are non-trivial and\nrequire human attention; if conflicts occur, the bot will provide detailed\ninstructions to perfom the task manually. If you forget to assign a backport branch label\nbefore merging a PR, you can tag the bot in a comment on the merged PR to have it\ncreate a new backport PR for the already merged PR (see `here <https://github.com/scientific-python/MeeseeksDev>`_\nfor a list of commands understood by the Meeseeks bot).\n\nIn short, a manual backport consist of 4 steps\n\n- checking out the backport branch locally\n- create a new branch from there\n- cherry-picking the merge commit from the original PR with ``git cherry-pick -m1 <commit sha>``\n- opening a PR to the backport branch\n\n.. _doing-a-minor-or-major-release:\n\nDoing a Minor or Major Release\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis is simpler than a bugfix release. Simply create a new\nseries branch off of the ``main`` branch (for example ``git switch -c yt-4.5.x``)\nand push the new branch up to the yt repository.\n\n.. code-block:: bash\n  git fetch upstream\n  git switch upstream/main\n  git switch -c yt-4.5.x\n  git push --set-upstream upstream yt-4.5.x\n\nAfter the series branch is up, you will bump the version number and generate a git tag\nas described below.\n\nAfter the completion of the release, the new series branch becomes the\nbackport branch for subsequent bugfix releases.\n\nRemoving Deprecations in a Major Release\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nFor a major release that includes removal of deprecated features, make sure that all\ndeprecated features targeted for removal in the new release are removed from the\n``main`` branch prior to creating the new series branch (:ref:`doing-a-minor-or-major-release`).\nIdeally, the deprecated features should be removed in a single PR.\n\n\nIncrementing Version Numbers\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nBefore creating the tag for the release, you must increment the version numbers\nthat are hard-coded in a few files in the yt source so that version metadata\nfor the code is generated correctly. The paths relative to the root of the\nrepository for the three files that need to be edited are:\n\n* ``doc/source/conf.py``\n\n  The ``version`` and ``release`` variables need to be updated.\n\n* ``pyproject.toml``\n\n  The ``version`` variable needs to be updated\n\n* ``yt/_version.py``\n\n  The ``__version__`` variable must be updated.\n\nTo update these files, check out and update the branch that will be released (``main``\nif doing a major or minor release, or the backport branch if doing a bugfix release).\n\nOnce these files have been updated, commit these updates and submit a pull request\ntargeting the branch that will be released. This is the commit we\nwill tag for the release. After a major or minor release is complete, the version numbers\non ``main`` should be updated to reflect development version via another pull request.\n\n\n.. _tagging-a-release:\n\nTagging a Release\n~~~~~~~~~~~~~~~~~\n\nAfter incrementing version numbers, checkout and update the branch that will be released\nand actually create the tag by issuing the following command:\n\n.. code-block:: bash\n\n   git tag <tag-name>\n\nWhere ``<tag-name>`` follows the project's naming scheme for tags\n(e.g. ``yt-3.2.1``). Once you are done, you will need to push the\ntag to the yt repository on github::\n\n  git push upstream --tag\n\nThis assumes that you have configured the remote ``upstream`` to point at the main\nyt git repository. If you are doing a minor or major version number release, you\nwill also need to update back to the development branch and update the\ndevelopment version numbers in the same files.\n\n\nPublishing\n~~~~~~~~~~\n\nWe distribute yt on two main channels: PyPI.org and conda-forge, in this order.\n\nPyPI\n++++\n\nThe publication process for PyPI is automated for the most part, via Github\nactions, using ``.github/workflows/wheels.yaml``. Specifically, a release is\npushed to PyPI when a new git tag starting with ``yt-`` is pushed to the main\nrepo. Let's review the details here.\n\nPyPI releases contain the source code (as a tarball), and wheels. Wheels are\ncompiled distributions of the source code. They are OS specific as well as\nPython-version specific. Producing wheels for every supported combination of OS\nand Python versions is done with `cibuildwheels\n<https://cibuildwheel.readthedocs.org>`_\n\nUpload to PyPI is automated via Github Actions `upload-artifact\n<https://github.com/actions/upload-artifact>`_ and `download-artifact\n<https://github.com/actions/upload-artifact>`_.\n\nIf that worked, you can skip to the next section. Otherwise, upload can be\nperfomed manually by first downloading the artifacts ``wheels`` and ``tarball``\nfrom the workflow webpage, then at the command line (make sure that the\n``dist`` directory doesn't exist or is empty)\n\n.. code-block:: bash\n\n   unzip tarball.zip -d dist\n   unzip wheels.zip -d dist\n   python -m pip install --upgrade twine\n   twine upload dist/*\n\nYou will be prompted for your PyPI credentials and then the package should\nupload. Note that for this to complete successfully, you will need an account on\nPyPI and that account will need to be registered as an \"owner\" or \"maintainer\"\nof the yt package.\n\n\n``conda-forge``\n+++++++++++++++\n\nConda-forge packages for yt are managed via the yt feedstock, located at\nhttps://github.com/conda-forge/yt-feedstock. When a release is pushed to PyPI a\nbot should detect a new version and issue a PR to the feedstock with the new\nversion automatically. When this feedstock is updated, make sure that the\nSHA256 hash of the tarball matches the one you uploaded to PyPI and that\nthe version number matches the one that is being released.\n\nIn case the automated PR fails CI, feedstock maintainers are allowed to push to\nthe bot's branch with any fixes required.\n\nShould you need to update the feedstock manually, you will\nneed to update the ``meta.yaml`` file located in the ``recipe`` folder in the\nroot of the feedstock repository. Most likely you will only need to update the\nversion number and the SHA256 hash of the tarball. If yt's dependencies change\nyou may also need to update the recipe. Once you have updated the recipe,\npropose a pull request on github and merge it once all builds pass.\n\nNote that the above actions all require that you are a feedstock maintainer.\nNew maintainers can be added by opening a new issue in the ``yt-feedstock`` repo\nusing the following for the title and body: ``@conda-forge-admin, please add user @user``\n(see `here <https://github.com/conda-forge/yt-feedstock/issues/102>`_ for an example),\nwhich will trigger a bot-issued PR to add the user as a feedstock maintainer. A current\nmaintainer will need to approve and merge the PR.\n\n\nAnnouncing\n~~~~~~~~~~\n\nAfter the release is uploaded to `PyPI <https://pypi.org/project/yt/#files>`_ and\n`conda-forge <https://anaconda.org/conda-forge/yt>`_, you should publish the\nGitHub draft release (see :ref:`prepping-release-notes`) and then you should\nsend out an announcement e-mail to the yt mailing lists and the yt slack. Optionally,\nfor major and minor releases, you can send the announcement to other interested\nmailing lists. Include a brief overview of the changes in the release and link to\nthe GitHub release.\n"
  },
  {
    "path": "doc/source/developing/testing.rst",
    "content": ".. _testing:\n\nTesting\n=======\n\nyt includes a testing suite that one can run on the code base to ensure that no\nbreaks in functionality have occurred. This suite is based on the `pytest <https://docs.pytest.org/en/stable/>`_ testing framework and consists of two types of tests:\n\n* Unit tests. These make sure that small pieces of code run (or fail) as intended in predictable contexts. See :ref:`unit_testing`.\n\n* Answer tests. These generate outputs from the user-facing yt API and compare them against the outputs produced using an older, \"known good\", version of yt. See :ref:`answer_testing`.\n\nThese tests ensure consistency in results as development proceeds.\n\nWe recommend that developers run tests locally on changed features when developing to help ensure that the new code does not break any existing functionality. To further this goal and ensure that changes do not propagate errors or have unintentional consequences on the rest of the codebase, the full test suite is run through our continuous integration (CI) servers. CI is run on push on open pull requests on a variety of computational platforms using Github Actions and a `continuous integration server <https://tests.yt-project.org>`_ at the University of Illinois. The full test suite may take several hours to run, so we do not recommend running it locally.\n\n.. _unit_testing:\n\nUnit Testing\n------------\n\nWhat Do Unit Tests Do\n^^^^^^^^^^^^^^^^^^^^^\n\nUnit tests are tests that operate on some small piece of code and verify\nthat it behaves as intended. In\npractice, this means that we write simple assertions (or ``assert`` statements) about results and then let pytest go through them. A test is considered a success when no error (in particular ``AssertionError``) occurs.\n\nHow to Run the Unit Tests\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nOne can run the unit tests by navigating to the base directory of the yt git\nrepository and invoking ``pytest``:\n\n.. code-block:: bash\n\n   $ cd $YT_GIT\n   $ pytest\n\nwhere ``$YT_GIT`` is the path to the root of the yt git repository.\n\nIf you only want to run tests in a specific file, you can do so by specifying the path of the test relative to the\n``$YT_GIT/`` directory. For example, if you want to run the ``plot_window`` tests, you'd\nrun:\n\n.. code-block:: bash\n\n   $ pytest yt/visualization/tests/test_plotwindow.py\n\nfrom the yt source code root directory.\n\nAdditionally, if you only want to run a specific test in a test file (rather than all of the tests contained in the file), such as, ``test_all_fields`` in ``plot_window.py``, you can do so by running:\n\n\n.. code-block:: bash\n\n    $ pytest yt/visualization/tests/test_plotwindow.py::test_all_fields\n\nfrom the yt source code rood directory\n\nSee the pytest documentation for more on how to `invoke pytest <https://docs.pytest.org/en/stable/usage.html?highlight=invocation>`_ and `select tests <https://docs.pytest.org/en/stable/usage.html#specifying-tests-selecting-tests>`_.\n\n\nUnit Test Tools\n^^^^^^^^^^^^^^^\n\nyt provides several helper functions and decorators to write unit tests. These tools all reside in the ``yt.testing``\nmodule.  Describing them all in detail is outside the scope of this\ndocument, as in some cases they belong to other packages.\n\nHow To Write New Unit Tests\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo create new unit tests:\n\n#. Create a new ``tests/`` directory next to the file containing the\n   functionality you want to test and add an empty ``__init__.py`` file to\n   it.\n   #. If a ``tests/`` directory already exists, there is no need to create a new one.\n#. Inside this new ``tests/`` directory, create a new python file prefixed with ``test_`` and\n   including the name of the functionality or source file being tested.\n   #. If a file testing the functionality you're interested in already exists, please add your tests to the existing there.\n#. Inside this new ``test_`` file, create functions  prefixed with ``test_`` that\n   accept no arguments.\n#. Each test function should do some work that tests some\n   functionality and should also verify that the results are correct using\n   assert statements or functions.\n#. If a dataset is needed, use ``fake_random_ds``, ``fake_amr_ds``, or ``fake_particle_ds`` (the former two of which have support for particles that may be utilized) and be sure to test for\n   several combinations of ``nproc`` so that domain decomposition can be\n   tested as well.\n#. To iterate over multiple options, or combinations of options,\n   use the `@pytest.mark.parametrize decorator <https://docs.pytest.org/en/6.2.x/parametrize.html#parametrizemark>`_.\n\nFor an example of how to write unit tests, look at the file\n``yt/data_objects/tests/test_covering_grid.py``, which covers a great deal of\nfunctionality.\n\nDebugging Failing Tests\n^^^^^^^^^^^^^^^^^^^^^^^\nWhen writing new tests, one often exposes bugs or writes a test incorrectly,\n\ncausing an exception to be raised or a failed test. To help debug issues like\nthis, ``pytest`` can `drop into a debugger <https://docs.pytest.org/en/6.2.x/usage.html#dropping-to-pdb-python-debugger-on-failures>`_ whenever a test fails or raises an\nexception.\n\nIn addition, one can debug more crudely using print statements. To do this,\nyou can add print statements to the code as normal. However, the test runner\nwill capture all print output by default. To ensure that output gets printed\nto your terminal while the tests are running, pass ``-s`` (which will disable stdout and stderr capturing) to the ``pytest``\nexecutable.\n\n.. code-block:: bash\n\n    $ pytest -s\n\nLastly, to quickly debug a specific failing test, it is best to only run that\none test during your testing session. This can be accomplished by explicitly\npassing the name of the test function or class to ``pytest``, as in the\nfollowing example:\n\n.. code-block:: bash\n\n    $ pytest yt/visualization/tests/test_plotwindow.py::TestSetWidth\n\nThis pytest invocation will only run the tests defined by the\n``TestSetWidth`` class. See the `pytest documentation <https://docs.pytest.org/en/6.2.x/usage.html>`_ for more on the various ways to invoke pytest.\n\nFinally, to determine which test is failing while the tests are running, it helps\nto run the tests in \"verbose\" mode. This can be done by passing the ``-v`` option\nto the ``pytest`` executable.\n\n.. code-block:: bash\n\n    $ pytest -v\n\nAll of the above ``pytest`` options can be combined. So, for example, to run\nthe ``TestSetWidth`` tests with verbose output, letting the output of print\nstatements come out on the terminal prompt, and enabling pdb debugging on errors\nor test failures, one would do:\n\n.. code-block:: bash\n\n    $ pytest yt/visualization/tests/test_plotwindow.py::TestSetWidth -v -s --pdb\n\nMore pytest options can be found by using the ``--help`` flag\n\n.. code-block:: bash\n\n    $ pytest --help\n\n.. _answer_testing:\n\nAnswer Testing\n--------------\n.. note::\n    This section documents answer tests run with ``pytest``. The plan is to\n    switch to using ``pytest`` for answer tests at some point in the future,\n    but currently (July 2024), answer tests are still implemented and run with\n    ``nose``. We generally encourage developers to use ``pytest`` for any new\n    tests, but if you need to change or update one of the older ``nose``\n    tests, or are, e.g., writing a new frontend,\n    an `older version of this documentation <https://yt-project.org/docs/4.0.0/developing/testing.html#answer-testing>`_\n    decribes how the ``nose`` tests work.\n\n.. note::\n    Given that nose never had support for Python 3.10 (which as of yt 4.4 is our\n    oldest supported version), it is necessary to patch it to get tests running.\n    This is the command we run on CI to this end\n    ``find .venv/lib/python3.10/site-packages/nose -name '*.py' -exec sed -i -e s/collections.Callable/collections.abc.Callable/g '{}' ';'``\n\nWhat Do Answer Tests Do\n^^^^^^^^^^^^^^^^^^^^^^^\n\nAnswer tests use `actual data <https://yt-project.org/data/>`_ to test reading, writing, and various manipulations of that data. Answer tests are how we test frontends, as opposed to operations, in yt.\n\nIn order to ensure that each of these operations are performed correctly, we store gold standard versions of yaml files called answer files. More generally, an answer file is a yaml file containing the results of having run the answer tests, which can be compared to a reference, enabling us to control that results do not drift over time.\n\n.. _run_answer_testing:\n\nHow to Run the Answer Tests\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn order to run the answer tests locally:\n\n* Create a directory to hold the data you'll be using for the answer tests you'll be writing or the answer tests you'll be running. This directory should be outside the yt git repository in a place that is logical to where you would normally store data.\n\n* Add folders of the required data to this directory. Other yt data, such as ``IsolatedGalaxy``, can be downloaded to this directory as well.\n\n* Tell yt where it can find the data. This is done by setting the config parameter ``test_data_dir`` to the path of the directory with the test data downloaded from https://yt-project.org/data/. For example,\n\n.. code-block:: bash\n\n   $ yt config set yt test_data_dir /Users/tomservo/src/yt-data\n\nthis should only need to be done once (unless you change where you're storing the data, in which case you'll need to repeat this step so yt looks in the right place).\n\n* Generate or obtain a set of gold standard answer files. In order to generate gold standard answer files, wwitch to a \"known good\" version of yt and then run the answer tests as described below. Once done, switch back to the version of yt you wish to test.\n* Now you're ready to run the answer tests!\n\nAs an example, let's focus on running the answer tests for the tipsy frontend. Let's also assume that we need to generate a gold standard answer file. To do this, we first switch to a \"known good\" version of yt and run the following command from the top of the yt git directory (i.e., ``$YT_GIT``) in order to generate the gold standard answer file:\n\n.. note::\n    It's possible to run the answer tests for **all** the frontends, but due to the large number of test datasets we currently use this is not normally done except on the yt project's contiguous integration server.\n\n.. code-block:: bash\n\n   $ cd $YT_GIT\n   $ pytest --with-answer-testing --answer-store --local-dir=\"$HOME/Documents/test\" -k \"TestTipsy\"\n\nThe ``--with-answer-testing`` tells pytest that we want to run answer tests. Without this option, the unit tests will be run instead of the answer tests. The ``--answer-store`` option tells pytest to save the results produced by each test to a local gold standard answer file. Omitting this option is how we tell pytest to compare the results to a gold standard. The ``--local-dir`` option specifies where the gold standard answer file will be saved (or is already located, in the case that ``--answer-store`` is omitted). The ``-k`` option tells pytest that we only want to run tests whose name matches the given pattern.\n\n.. note::\n    The path specified by ``--local-dir`` can, but does not have to be, the same directory as the ``test_data_dir`` configuration variable. It is best practice to keep the data that serves as input to yt separate from the answers produced by yt's tests, however.\n\n.. note::\n    The value given to the `-k` option (e.g., `\"TestTipsy\"`) is the name of the class containing the answer tests. You do not need to specify the path.\n\nThe newly generated gold standard answer file will be named ``tipsy_answers_xyz.yaml``, where ``xyz`` denotes the version number of the gold standard answers. The answer version number is determined by the ``answer_version`` attribute of the class being tested (e.g., ``TestTipsy.answer_version``).\n\n.. note::\n    Changes made to yt sometimes result in known, expected changes to the way certain operations behave. This necessitates updating the gold standard answer files. This process is accomplished by changing the version number specified in each answer test class (e.g., ``TestTipsy.answer_version``). The answer version for each test class can be found as the attribute `answer_version` of that class.\n\nOnce the gold standard answer file has been generated we switch back to the version of yt we want to test, recompile if necessary, and run the tests using the following command:\n\n.. code-block:: bash\n\n   $ pytest --with-answer-testing --local-dir=\"$HOME/Documents/test\" -k \"TestTipsy\"\n\nThe result of each test is printed to STDOUT. If a test passes, pytest prints a period. If a test fails, encounters an\nexception, or errors out for some reason, then an F is printed.  Explicit descriptions for each test\nare also printed if you pass ``-v`` to the ``pytest`` executable. Similar to the unit tests, the ``-s`` and ``--pdb`` options can be passed, as well.\n\n\nHow to Write Answer Tests\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo add a new answer test:\n\n#. Create a new directory called ``tests`` inside the directory where the component you want to test resides and add an empty ``__init__.py`` file to it.\n\n#. Create a new file in the ``tests`` directory that will hold the new answer tests. The name of the file should begin with ``test_``.\n\n#. Create a new class whose name begins with ``Test`` (e.g., ``TestTipsy``).\n\n#. Decorate the class with ``pytest.mark.answer_test``. This decorator is used to tell pytest which tests are answer tests.\n\n   .. note::\n\n      Tests that do not have this decorator are considered to be unit tests.\n\n#. Add the following three attributes to the class: ``answer_file=None``, ``saved_hashes=None``, and ``answer_version=000``. These attributes are used by the ``hashing`` fixture (discussed below) to automate the creation of new answer files as well as facilitate the comparison to existing answers.\n\n#. Add methods to the class that test a number of different fields and data objects.\n\n#. If these methods are performing calculations or data manipulation, they should store the result in a ``ndarray``, if possible. This array should be be added to the ``hashes`` (see below) dictionary like so: ``self.hashes.update(<test_name>:<array>)``, where ``<test_name>`` is the name of the function from ``yt/utilities/answer_testing/answer_tests.py`` that is being used and ``<array>`` is the ``ndarray`` holding the result\n\nIf you are adding to a frontend that has tests already, simply add methods to the existing test class.\n\nThere are several things that can make the test writing process easier:\n\n* ``yt/utilities/answer_testing/testing_utilities.py`` contains a large number of helper functions.\n* Most frontends end up needing to test much of the same functionality as other frontends. As such, a list of functions that perform such work can be found in ``yt/utilities/answer_testing/answer_tests.py``.\n* `Fixtures <https://docs.pytest.org/en/stable/fixture.html>`_! You can find the set of fixtures that have already been built for yt in ``$YT_GIT/conftest.py``. If you need/want to add additional fixtures, please add them there.\n* The `parametrize decorator <https://docs.pytest.org/en/stable/example/parametrize.html?highlight=parametrizing%20tests>`_ is extremely useful for performing iteration over various combinations of test parameters. It should be used whenever possible.\n    * The use of this decorator allows pytest to write the names and values of the test parameters to the generated answer files, which can make debugging failing tests easier, since one can easily see exactly which combination of parameters were used for a given test.\n    * It is also possible to employ the ``requires_ds`` decorator to ensure that a test does not run unless a specific dataset is found, but not necessary. If the dataset is parametrized over, then the ``ds`` fixture found in the root ``conftest.py`` file performs the same check and marks the test as failed if the dataset isn't found.\n\nHere is what a minimal example might look like for a new frontend:\n\n.. code-block:: python\n\n    # Content of yt/frontends/new_frontend/tests/test_outputs.py\n    import pytest\n\n    from yt.utilities.answer_testing.answer_tests import field_values\n\n    # Parameters to test with\n    ds1 = \"my_first_dataset\"\n    ds2 = \"my_second_dataset\"\n    field1 = (\"Gas\", \"Density\")\n    field2 = (\"Gas\", \"Temperature\")\n    obj1 = None\n    obj2 = (\"sphere\", (\"c\", (0.1, \"unitary\")))\n\n\n    @pytest.mark.answer_test\n    class TestNewFrontend:\n        answer_file = None\n        saved_hashes = None\n        answer_version = \"000\"\n\n        @pytest.mark.usefixtures(\"hashing\")\n        @pytest.mark.parametrize(\"ds\", [ds1, ds2], indirect=True)\n        @pytest.mark.parametrize(\"field\", [field1, field2], indirect=True)\n        @pytest.mark.parametrize(\"dobj\", [obj1, obj2], indirect=True)\n        def test_fields(self, ds, field, dobj):\n            self.hashes.update({\"field_values\": field_values(ds, field, dobj)})\n\nAnswer test examples can be found in ``yt/frontends/enzo/tests/test_outputs.py``.\n\n\n.. _update_image_tests:\n\nCreating and Updating Image Baselines for pytest-mpl Tests\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWe use `pytest-mpl <https://pytest-mpl.readthedocs.io/en/latest/>`_ for image comparison\ntests. These tests take the form of functions, which must be decorated with\n``@pytest.mark.mpl_image_compare`` and return a ``matplotlib.figure.Figure`` object.\n\nThe collection of reference images is kept as git submodule in ``tests/pytest_mpl_baseline/``.\n\nThere are 4 situations where updating reference images may be necessary\n\n- adding new tests\n- bugfixes\n- intentional change of style in yt\n- old baseline fails with a new version of matplotlib, but changes are not noticeable to the human eye\n\nThe process of updating images is the same in all cases. It involves opening two Pull Requests (PR)\nthat we'll number PR1 and PR2.\n\n1. open a Pull Request (PR1) to yt's main repo with the code changes\n2. wait for tests jobs to complete\n3. go to the \"Checks\" tab on the PR page (``https://github.com/yt-project/yt/pull/<PR number>/checks``)\n4. if all tests passed, you're done !\n5. if tests other than image tests failed, fix them, and go back to step 2.\n  Otherwise, if only image tests failed, navigate to the \"Build and Tests\" job summary page.\n6. at the bottom of the page, you'll find \"Artifacts\".\n   Download ``yt_pytest_mpl_results.zip``, unzip it and open ``fig_comparison.html`` therein;\n   This document is an interactive report of the test job.\n   Inspect failed tests results and verify that any differences are either intended or insignificant.\n   If they are not, fix the code and go back to step 2\n7. clone ``https://github.com/yt-project/yt_pytest_mpl_baseline.git`` and unzip the new baseline\n8. Download the other artifact (``yt_pytest_mpl_new_baseline.zip``),\n   unzip it within your clone of ``yt_pytest_mpl_baseline``.\n9. create a branch, commit all changes, and open a Pull Request (PR2) to ``https://github.com/yt-project/yt_pytest_mpl_baseline``\n   (PR2 should link to PR1)\n10. wait for this second PR to be merged\n11. Now it's time to update PR1: navigate back to your local copy of ``yt``'s main repository.\n12. run the following commands\n\n.. code-block:: bash\n\n  $ git submodule update --init\n  $ cd tests/pytest_mpl_baseline\n  $ git checkout main\n  $ git pull\n  $ cd ../\n  $ git add pytest_mpl_baseline\n  $ git commit -m \"update image test baseline\"\n  $ git push\n\n13. go back to step 2. This time everything should pass. If not, ask for help !\n\n.. note::\n    Though it is technically possible to (re)generate reference images locally, it is\n    best not to, because at a pixel level, matplotlib's behaviour is platform-dependent.\n    By letting CI runners generate images, we ensure pixel-perfect comparison is possible\n    in CI, which is where image comparison tests are most often run.\n\n\n.. _deprecated_generic_image:\n\nHow to Write Image Comparison Tests (deprecated API)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. warning::\n    this section describes deprecated API. New test code should follow :ref:`_update_image_tests`\n\n\nMany of yt's operations involve creating and manipulating images. As such, we have a number of tests designed to compare images. These tests employ functionality from matplotlib to automatically compare images and detect\ndifferences, if any. Image comparison tests are used in the plotting and volume\nrendering machinery.\n\nThe easiest way to use the image comparison tests is to make use of the\n``generic_image`` function. As an argument, this function takes a function the test machinery can call which will save an image to disk. The test will then find any images that get created and compare them with the stored \"correct\" answer.\n\nHere is an example test function (from ``yt/visualization/tests/test_raw_field_slices.py``):\n\n.. code-block:: python\n\n    import pytest\n\n    import yt\n    from yt.utilities.answer_testing.answer_tests import generic_image\n    from yt.utilities.answer_testing.testing_utilities import data_dir_load, requires_ds\n\n    # Test data\n    raw_fields = \"Laser/plt00015\"\n\n\n    def compare(ds, field):\n        def slice_image(im_name):\n            sl = yt.SlicePlot(ds, \"z\", field)\n            sl.set_log(\"all\", False)\n            image_file = sl.save(im_name)\n            return image_file\n\n        gi = generic_image(slice_image)\n        # generic_image returns a list. In this case, there's only one entry,\n        # which is a np array with the data we want\n        assert len(gi) == 1\n        return gi[0]\n\n\n    @pytest.mark.answer_test\n    @pytest.mark.usefixtures(\"temp_dir\")\n    class TestRawFieldSlices:\n        answer_file = None\n        saved_hashes = None\n        answer_version = \"000\"\n\n        @pytest.mark.usefixtures(\"hashing\")\n        @requires_ds(raw_fields)\n        def test_raw_field_slices(self, field):\n            ds = data_dir_load(raw_fields)\n            gi = compare(ds, field)\n            self.hashes.update({\"generic_image\": gi})\n\n.. note::\n    The inner function ``slice_image`` can create any number of images, as long as the corresponding filenames conform to the prefix.\n\nAnother good example of an image comparison test is the\n``plot_window_attribute`` defined in the ``yt/utilities/answer_testing/answer_tests.py`` and used in\n``yt/visualization/tests/test_plotwindow.py``. This sort of image comparison\ntest is more useful if you are finding yourself writing a ton of boilerplate\ncode to get your image comparison test working.  The ``generic_image`` function is\nmore useful if you only need to do a one-off image comparison test.\n\n\nUpdating Answers\n~~~~~~~~~~~~~~~~\n\nIn order to regenerate answers for a particular set of tests it is sufficient to\nchange the ``answer_version`` attribute in the desired test class.\n\nWhen adding tests to an existing set of answers (like ``local_owls_000.yaml`` or ``local_varia_000.yaml``),\nit is considered best practice to first submit a pull request adding the tests WITHOUT incrementing\nthe version number. Then, allow the tests to run (resulting in \"no old answer\" errors for the missing\nanswers). If no other failures are present, you can then increment the version number to regenerate\nthe answers. This way, we can avoid accidentally covering up test breakages.\n\n.. _handling_dependencies:\n\nHandling yt Dependencies\n------------------------\n\nOur dependencies are specified in ``pyproject.toml``. Hard dependencies are found in\n``project.dependencies``, while optional dependencies are specified in\n``project.optional-dependencies``. The ``full`` target contains the specs to run our\ntest suite, which are intended to be as modern as possible (we don't set upper\nlimits to versions unless we need to).\n\nThe ``test`` target specifies the tools needed to run the tests, but\nnot needed by yt itself.\n\nDocumentation and typechecking requirements are defined as ``docs`` and\n``typecheck`` dependency groups respectively.\n\n**Python version support.**\nWe vow to follow numpy's deprecation plan regarding our supported versions for Python\nand numpy, defined formally in\n`NEP 29 <https://numpy.org/neps/nep-0029-deprecation_policy.html>`_, but generally\nsupport larger version intervals than recommended in this document.\n\n**Third party dependencies.**\nWe attempt to make yt compatible with a wide variety of upstream software\nversions.\nHowever, sometimes a specific version of a project that yt depends on\ncauses some breakage and must be blacklisted in the tests or a more\nexperimental project that yt depends on optionally might change sufficiently\nthat the yt community decides not to support an old version of that project.\n\n**Note.**\nSome of our optional dependencies are not trivial to install and their support\nmay vary across platforms.\n\nIf you would like to add a new dependency for yt (even an optional dependency)\nor would like to update a version of a yt dependency, you must edit the\n``pyproject.toml`` file. For new dependencies, simply append the name of the new\ndependency to the end of the file, along with a pin to the latest version\nnumber of the package. To update a package's version, simply update the version\nnumber in the entry for that package.\n\nFinally, we also run a set of tests with \"minimal\" dependencies installed.\nWhen adding tests that depend on an optional dependency, you can wrap the test\nwith the ``yt.testing.requires_module decorator`` to ensure it does not run\nduring the minimal dependency tests (see\n``yt/frontends/amrvac/tests/test_read_amrvac_namelist.py`` for a good example).\nIf for some reason you need to update the listing of packages that are installed\nfor the \"minimal\" dependency tests, you will need to update\n``minimal_requirements.txt``.\n"
  },
  {
    "path": "doc/source/examining/Loading_Data_via_Functions.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"35cb9c22-04eb-476c-a7b6-afd27690be4d\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading Data via Functions\\n\",\n    \"\\n\",\n    \"One of the things that yt provides is the notion of \\\"derived fields.\\\"  These are fields that exist only in memory, and are defined by a functional form, typically transforming other fields.  Starting with version 4.1 of yt, however, we have made it much, much easier to define so-called \\\"on-disk\\\" fields through their functional forms, akin to how derived fields are generated.  **At present, this is only available for grid-based frontends.  Extension to other types is anticipated in future versions of yt.**\\n\",\n    \"\\n\",\n    \"What this means is that if you have a way to grab data -- at any resolution -- but don't want to either load it into memory in advance or write a complete \\\"frontend\\\", you can just write some functions and use those to construct a fully-functional dataset using the existing `load_amr_grids` and `load_uniform_grid` functions, supplying *functions* instead of arrays.\\n\",\n    \"\\n\",\n    \"There are a few immediate use cases that can be seen for this:\\n\",\n    \"\\n\",\n    \" - Data is accessible through another library, for instance if a library exists that reads subsets of data (or regularizes that data to given regions) or if you are calling yt from an *in situ* analysis library\\n\",\n    \" - Data can be remotely accessed on-demand\\n\",\n    \" - You have a straightforward data format for which a frontend does not exist\\n\",\n    \" - All of the data can be generated through an analytical definition\\n\",\n    \" \\n\",\n    \"The last one is what we'll use to demonstrate this.  Let's imagine that I had a grid structure that I wanted to explore, but I wanted all of my data to be generated through functions that were exclusively dependent on the spatial position.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"fd2b8726-5e3b-4641-8b13-7c1163336c8b\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import matplotlib.pyplot as plt\\n\",\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2f2074a7-a71a-4032-8e92-11e908e0f15c\",\n   \"metadata\": {},\n   \"source\": [\n    \"The example we've cooked up is going to be a bit silly, and we'll demonstrate it a little bit with one and two dimensional setups before getting into the full yt demonstration.  (If you have ideas for a better one, please do suggest them!)  We'll start with some overlapping trigonometric functions, which we'll attenuate by their distance from the center.\\n\",\n    \"\\n\",\n    \"So we'll set up some coefficients for different periods of the functions (the `coefficients` variable) and we'll sum up those functions.  The next thing we'll do, so that we have some global attenuation we can see, is use a Gaussian function centered at the center of our domain.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"27180398-def2-4a65-bc3f-c807ee0c13a4\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x = np.mgrid[0.0:1.0:512j]\\n\",\n    \"coefficients = (100, 50, 30, 10)\\n\",\n    \"y = sum(c * np.sin(2 ** (2 + i) * (x * np.pi * 2)) for i, c in enumerate(coefficients))\\n\",\n    \"atten = np.exp(-20 * (1.1 * (x - 0.5)) ** 2)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"22c9b8e5-32ca-4402-95e6-452b3d71418c\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now let's plot it!  The top right is the attenuation, bottom left is the base sum of trig functions, and then the bottom right is the product.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"dbd391fb-10ff-4f2e-8b3e-bbd11f4cc4a6\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, dpi=150)\\n\",\n    \"ax4.plot(x, y * atten)\\n\",\n    \"ax2.plot(x, atten)\\n\",\n    \"ax3.plot(x, y)\\n\",\n    \"ax1.axis(False);\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"958c652a-987a-40cf-86ff-fa0bd7050996\",\n   \"metadata\": {},\n   \"source\": [\n    \"Well, that looks like it might have some structure at different scales!  We should be able to use something like this to show sampling errors and so on in AMR, and it'll have structure down to a reasonably high level of detail.  Let's briefly demonstrate in 2D before moving on to 3D, using similar functions.  This is all basically the same as the previous cells, except we're overlaying along a couple different dimensions.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9e7897e1-b593-43bc-aeaa-1e86ff94566a\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x, y = np.mgrid[0.0:1.0:512j, 0.0:1.0:512j]\\n\",\n    \"\\n\",\n    \"x_coefficients = (100, 50, 30, 10)\\n\",\n    \"y_coefficients = (20, 90, 80, 30)\\n\",\n    \"\\n\",\n    \"z = sum(\\n\",\n    \"    c * np.sin(2 ** (1 + i) * (x * np.pi * 2 + 2**i))\\n\",\n    \"    for i, c in enumerate(x_coefficients)\\n\",\n    \") * sum(\\n\",\n    \"    c * np.sin(2 ** (1 + i) * (y * np.pi * 2 + 2**i))\\n\",\n    \"    for i, c in enumerate(y_coefficients)\\n\",\n    \")\\n\",\n    \"r = np.sqrt(((x - 0.5) ** 2) + ((y - 0.5) ** 2))\\n\",\n    \"atten = np.exp(-20 * (1.1 * r**2))\\n\",\n    \"\\n\",\n    \"plt.pcolormesh(x, y, z * atten)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2fc53e45-ff35-4d15-a2eb-b74a32be9609\",\n   \"metadata\": {},\n   \"source\": [\n    \"This is an image of the full dataset, but what happens if we coarsely sample, as we would in AMR simulations?  We can stride along the axes (let's say, every 32nd point) to get an idea of what that looks like.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"797c884d-3ccb-412b-af13-a46de54e5ab9\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"plt.pcolormesh(x[::32, ::32], y[::32, ::32], (z * atten)[::32, ::32])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"9355880c-8579-4dd9-b24f-bf931f1d84f3\",\n   \"metadata\": {},\n   \"source\": [\n    \"For moving to 3D, I'm going to add on some higher-frequency modes, which I'll also amplify a bit more.  We'll use the standard attenuation (although a directionally-dependent attenuation would be nice, wouldn't it?)\\n\",\n    \"\\n\",\n    \"And this time, we'll write them into a *special* function.  This is the function we'll use to supply to our `load_amr_grids` function -- it has different arguments than a derived field; because it is assumed to always return three-dimensional data, it accepts a proper grid object (which may have spatial or other attributes) and it also receives the field name.\\n\",\n    \"\\n\",\n    \"Using this, we will compute the cell-centers for all of the cells in the grid, and use them to compute our overlapping functions and apply the attenuation.  In doing so, we should be able to see structure at different levels.  This is the same way you would write a function that loaded a file from disk, or received over HTTP, or used a library to read data; by having access to the grid and the field name, it should be completely determinative of how to access the data.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"9ce0d056-2e08-4ff2-bddf-1594cfa4a801\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"x_coefficients = (100, 50, 30, 10, 20)\\n\",\n    \"y_coefficients = (20, 90, 80, 30, 30)\\n\",\n    \"z_coefficients = (50, 10, 90, 40, 40)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def my_function(grid, field_name):\\n\",\n    \"    # We want N points from the cell-center to the cell-center on the other side\\n\",\n    \"    x, y, z = (\\n\",\n    \"        np.linspace(\\n\",\n    \"            grid.LeftEdge[i] + grid.dds[i] / 2,\\n\",\n    \"            grid.RightEdge[i] - grid.dds[i] / 2,\\n\",\n    \"            grid.ActiveDimensions[i],\\n\",\n    \"        )\\n\",\n    \"        for i in (0, 1, 2)\\n\",\n    \"    )\\n\",\n    \"    r = np.sqrt(\\n\",\n    \"        ((x.d - 0.5) ** 2)[:, None, None]\\n\",\n    \"        + ((y.d - 0.5) ** 2)[None, :, None]\\n\",\n    \"        + ((z.d - 0.5) ** 2)[None, None, :]\\n\",\n    \"    )\\n\",\n    \"    atten = np.exp(-20 * (1.1 * r**2))\\n\",\n    \"    xv = sum(\\n\",\n    \"        c * np.sin(2 ** (1 + i) * (x.d * np.pi * 2))\\n\",\n    \"        for i, c in enumerate(x_coefficients)\\n\",\n    \"    )\\n\",\n    \"    yv = sum(\\n\",\n    \"        c * np.sin(2 ** (1 + i) * (y.d * np.pi * 2))\\n\",\n    \"        for i, c in enumerate(y_coefficients)\\n\",\n    \"    )\\n\",\n    \"    zv = sum(\\n\",\n    \"        c * np.sin(2 ** (1 + i) * (z.d * np.pi * 2))\\n\",\n    \"        for i, c in enumerate(z_coefficients)\\n\",\n    \"    )\\n\",\n    \"    return atten * (xv[:, None, None] * yv[None, :, None] * zv[None, None, :])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"479d7e97-a3ad-409e-a067-e722b3acab0f\",\n   \"metadata\": {},\n   \"source\": [\n    \"We'll use a standard grid hierarchy -- which is used internally in yt testing -- and fill it up with a single field that provides this function rather than any arrays.  We'll then use `load_amr_grids` to read it; note that we're not creating any arrays ahead of time.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"132c3f99-5b6e-4042-8fc5-49acb8cd9d8c\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from yt.testing import _amr_grid_index\\n\",\n    \"\\n\",\n    \"grid_data = []\\n\",\n    \"for level, le, re, dims in _amr_grid_index:\\n\",\n    \"    grid_data.append(\\n\",\n    \"        {\\n\",\n    \"            \\\"level\\\": level,\\n\",\n    \"            \\\"left_edge\\\": le,\\n\",\n    \"            \\\"right_edge\\\": re,\\n\",\n    \"            \\\"dimensions\\\": dims,\\n\",\n    \"            \\\"density\\\": my_function,\\n\",\n    \"        }\\n\",\n    \"    )\\n\",\n    \"ds = yt.load_amr_grids(\\n\",\n    \"    grid_data, [32, 32, 32], bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2972eb49-823b-41d2-a132-4a0fd4be2904\",\n   \"metadata\": {},\n   \"source\": [\n    \"And finally, we'll demonstrate it with a slice along the y axis.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"d35407c6-da5d-47e1-93bd-54672c33fbe8\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p = ds.r[:, 0.5, :].plot(\\\"density\\\").set_log(\\\"density\\\", False)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"cfd5d56e-5d3a-4537-98a5-22f659268f80\",\n   \"metadata\": {},\n   \"source\": [\n    \"And with a quick zoom, we can see that the structure is indeed present *and* subject to the sampling effects we discussed earlier.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"aa420efd-536f-45e2-90ae-51bc8dc68c5f\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"p.zoom(4)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.12\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "doc/source/examining/Loading_Generic_Array_Data.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading Generic Array Data\\n\",\n    \"\\n\",\n    \"Even if your data is not strictly related to fields commonly used in\\n\",\n    \"astrophysical codes or your code is not supported yet, you can still feed it to\\n\",\n    \"yt to use its advanced visualization and analysis facilities. The only\\n\",\n    \"requirement is that your data can be represented as three-dimensional NumPy arrays with a consistent grid structure. What follows are some common examples of loading in generic array data that you may find useful. \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Generic Unigrid Data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The simplest case is that of a single grid of data spanning the domain, with one or more fields. The data could be generated from a variety of sources; we'll just give three common examples:\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Data generated \\\"on-the-fly\\\"\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The most common example is that of data that is generated in memory from the currently running script or notebook. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"from numpy.random import default_rng  # we'll be generating random numbers here\\n\",\n    \"\\n\",\n    \"import yt\\n\",\n    \"\\n\",\n    \"prng = default_rng(seed=42)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In this example, we'll just create a 3-D array of random floating-point data using NumPy:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"arr = prng.random(size=(64, 64, 64))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To load this data into yt, we need associate it with a field. The `data` dictionary consists of one or more fields, each consisting of a tuple of a NumPy array and a unit string. Then, we can call `load_uniform_grid`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"data = {\\\"density\\\": (arr, \\\"g/cm**3\\\")}\\n\",\n    \"bbox = np.array([[-1.5, 1.5], [-1.5, 1.5], [-1.5, 1.5]])\\n\",\n    \"ds = yt.load_uniform_grid(data, arr.shape, length_unit=\\\"Mpc\\\", bbox=bbox, nprocs=64)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`load_uniform_grid` takes the following arguments and optional keywords:\\n\",\n    \"\\n\",\n    \"* `data` : This is a dict of numpy arrays, where the keys are the field names\\n\",\n    \"* `domain_dimensions` : The domain dimensions of the unigrid\\n\",\n    \"* `length_unit` : The unit that corresponds to `code_length`, can be a string, tuple, or floating-point number\\n\",\n    \"* `bbox` : Size of computational domain in units of `code_length`\\n\",\n    \"* `nprocs` : If greater than 1, will create this number of subarrays out of data\\n\",\n    \"* `sim_time` : The simulation time in seconds\\n\",\n    \"* `mass_unit` : The unit that corresponds to `code_mass`, can be a string, tuple, or floating-point number\\n\",\n    \"* `time_unit` : The unit that corresponds to `code_time`, can be a string, tuple, or floating-point number\\n\",\n    \"* `velocity_unit` : The unit that corresponds to `code_velocity`\\n\",\n    \"* `magnetic_unit` : The unit that corresponds to `code_magnetic`, i.e. the internal units used to represent magnetic field strengths. NOTE: if you want magnetic field units to be in the SI unit system, you must specify it here, e.g. `magnetic_unit=(1.0, \\\"T\\\")`\\n\",\n    \"* `periodicity` : A tuple of booleans that determines whether the data will be treated as periodic along each axis\\n\",\n    \"* `geometry` : The geometry of the dataset, can be `cartesian`, `cylindrical`, `polar`, `spherical`, `geographic` or `spectral_cube`\\n\",\n    \"* `default_species_fields` : if set to `ionized` or `neutral`, default species fields are accordingly created for H and He which also set mean molecular weight\\n\",\n    \"* `axis_order` : The order of the axes in the data array, e.g. `(\\\"z\\\", \\\"y\\\", \\\"x\\\")` with cartesian geometry\\n\",\n    \"* `cell_widths` : If set, specify the cell widths along each dimension. Must be consistent with the `domain_dimensions` argument\\n\",\n    \"* `parameters` : A dictionary of dataset parameters, , useful for storing dataset metadata\\n\",\n    \"* `dataset_name` : The name of the dataset. Stream datasets will use this value in place of a filename (in image prefixing, etc.)\\n\",\n    \"\\n\",\n    \"This example creates a yt-native dataset `ds` that will treat your array as a\\n\",\n    \"density field in cubic domain of 3 Mpc edge size and simultaneously divide the \\n\",\n    \"domain into `nprocs` = 64 chunks, so that you can take advantage\\n\",\n    \"of the underlying parallelism. \\n\",\n    \"\\n\",\n    \"The optional unit keyword arguments allow for the default units of the dataset to be set. They can be:\\n\",\n    \"* A string, e.g. `length_unit=\\\"Mpc\\\"`\\n\",\n    \"* A tuple, e.g. `mass_unit=(1.0e14, \\\"Msun\\\")`\\n\",\n    \"* A floating-point value, e.g. `time_unit=3.1557e13`\\n\",\n    \"\\n\",\n    \"In the latter case, the unit is assumed to be cgs. \\n\",\n    \"\\n\",\n    \"The resulting `ds` functions exactly like a dataset like any other yt can handle--it can be sliced, and we can show the grid boundaries:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(ds, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"slc.set_cmap((\\\"gas\\\", \\\"density\\\"), \\\"Blues\\\")\\n\",\n    \"slc.annotate_grids(cmap=None)\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Particle fields are detected as one-dimensional fields. Particle fields are then added as one-dimensional arrays in\\n\",\n    \"a similar manner as the three-dimensional grid fields:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"posx_arr = prng.uniform(low=-1.5, high=1.5, size=10000)\\n\",\n    \"posy_arr = prng.uniform(low=-1.5, high=1.5, size=10000)\\n\",\n    \"posz_arr = prng.uniform(low=-1.5, high=1.5, size=10000)\\n\",\n    \"data = {\\n\",\n    \"    \\\"density\\\": (prng.random(size=(64, 64, 64)), \\\"Msun/kpc**3\\\"),\\n\",\n    \"    \\\"particle_position_x\\\": (posx_arr, \\\"code_length\\\"),\\n\",\n    \"    \\\"particle_position_y\\\": (posy_arr, \\\"code_length\\\"),\\n\",\n    \"    \\\"particle_position_z\\\": (posz_arr, \\\"code_length\\\"),\\n\",\n    \"}\\n\",\n    \"bbox = np.array([[-1.5, 1.5], [-1.5, 1.5], [-1.5, 1.5]])\\n\",\n    \"ds = yt.load_uniform_grid(\\n\",\n    \"    data,\\n\",\n    \"    data[\\\"density\\\"][0].shape,\\n\",\n    \"    length_unit=(1.0, \\\"Mpc\\\"),\\n\",\n    \"    mass_unit=(1.0, \\\"Msun\\\"),\\n\",\n    \"    bbox=bbox,\\n\",\n    \"    nprocs=4,\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In this example only the particle position fields have been assigned. If no particle arrays are supplied, then the number of particles is assumed to be zero. Take a slice, and overlay particle positions:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(ds, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"slc.set_cmap((\\\"gas\\\", \\\"density\\\"), \\\"Blues\\\")\\n\",\n    \"slc.annotate_particles(0.25, p_size=12.0, col=\\\"Red\\\")\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### HDF5 data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"HDF5 is a convenient format to store data. If you have unigrid data stored in an HDF5 file, it is possible to load it into memory and then use `load_uniform_grid` to get it into yt:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from os.path import join\\n\",\n    \"\\n\",\n    \"import h5py\\n\",\n    \"\\n\",\n    \"from yt.config import ytcfg\\n\",\n    \"\\n\",\n    \"data_dir = ytcfg.get(\\\"yt\\\", \\\"test_data_dir\\\")\\n\",\n    \"from yt.utilities.physical_ratios import cm_per_kpc\\n\",\n    \"\\n\",\n    \"f = h5py.File(\\n\",\n    \"    join(data_dir, \\\"UnigridData\\\", \\\"turb_vels.h5\\\"), \\\"r\\\"\\n\",\n    \")  # Read-only access to the file\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The HDF5 file handle's keys correspond to the datasets stored in the file:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(f.keys())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We need to add some unit information. It may be stored in the file somewhere, or we may know it from another source. In this case, the units are simply cgs:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"units = [\\n\",\n    \"    \\\"gauss\\\",\\n\",\n    \"    \\\"gauss\\\",\\n\",\n    \"    \\\"gauss\\\",\\n\",\n    \"    \\\"g/cm**3\\\",\\n\",\n    \"    \\\"erg/cm**3\\\",\\n\",\n    \"    \\\"K\\\",\\n\",\n    \"    \\\"cm/s\\\",\\n\",\n    \"    \\\"cm/s\\\",\\n\",\n    \"    \\\"cm/s\\\",\\n\",\n    \"    \\\"cm/s\\\",\\n\",\n    \"    \\\"cm/s\\\",\\n\",\n    \"    \\\"cm/s\\\",\\n\",\n    \"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can iterate over the items in the file handle and the units to get the data into a dictionary, which we will then load:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"data = {k: (v[()], u) for (k, v), u in zip(f.items(), units)}\\n\",\n    \"bbox = np.array([[-0.5, 0.5], [-0.5, 0.5], [-0.5, 0.5]])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_uniform_grid(\\n\",\n    \"    data,\\n\",\n    \"    data[\\\"Density\\\"][0].shape,\\n\",\n    \"    length_unit=250.0 * cm_per_kpc,\\n\",\n    \"    bbox=bbox,\\n\",\n    \"    nprocs=8,\\n\",\n    \"    periodicity=(False, False, False),\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In this case, the data came from a simulation which was 250 kpc on a side. An example projection of two fields:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ProjectionPlot(\\n\",\n    \"    ds, \\\"z\\\", [\\\"z-velocity\\\", \\\"Temperature\\\", \\\"Bx\\\"], weight_field=\\\"Density\\\"\\n\",\n    \")\\n\",\n    \"prj.set_log(\\\"z-velocity\\\", False)\\n\",\n    \"prj.set_log(\\\"Bx\\\", False)\\n\",\n    \"prj.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Volume Rendering Loaded Data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Volume rendering requires defining a `TransferFunction` to map data to color and opacity and a `camera` to create a viewport and render the image.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Find the min and max of the field\\n\",\n    \"mi, ma = ds.all_data().quantities.extrema(\\\"Temperature\\\")\\n\",\n    \"# Reduce the dynamic range\\n\",\n    \"mi = mi.value + 1.5e7\\n\",\n    \"ma = ma.value - 0.81e7\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Define the properties and size of the `camera` viewport:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Choose a vector representing the viewing direction.\\n\",\n    \"L = [0.5, 0.5, 0.5]\\n\",\n    \"# Define the center of the camera to be the domain center\\n\",\n    \"c = ds.domain_center[0]\\n\",\n    \"# Define the width of the image\\n\",\n    \"W = 1.5 * ds.domain_width[0]\\n\",\n    \"# Define the number of pixels to render\\n\",\n    \"Npixels = 512\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Create a `camera` object and \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sc = yt.create_scene(ds, \\\"Temperature\\\")\\n\",\n    \"dd = ds.all_data()\\n\",\n    \"\\n\",\n    \"source = sc[0]\\n\",\n    \"\\n\",\n    \"source.log_field = False\\n\",\n    \"\\n\",\n    \"tf = yt.ColorTransferFunction((mi, ma), grey_opacity=False)\\n\",\n    \"tf.map_to_colormap(mi, ma, scale=15.0, colormap=\\\"cmyt.algae\\\")\\n\",\n    \"\\n\",\n    \"source.set_transfer_function(tf)\\n\",\n    \"\\n\",\n    \"sc.add_source(source)\\n\",\n    \"\\n\",\n    \"cam = sc.add_camera()\\n\",\n    \"cam.width = W\\n\",\n    \"cam.center = c\\n\",\n    \"cam.normal_vector = L\\n\",\n    \"cam.north_vector = [0, 0, 1]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sc.show(sigma_clip=4)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### FITS image data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The FITS file format is a common astronomical format for 2-D images, but it can store three-dimensional data as well. The [AstroPy](https://www.astropy.org) project has modules for FITS reading and writing.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import astropy.io.fits as pyfits\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Using `pyfits` we can open a FITS file. If we call `info()` on the file handle, we can figure out some information about the file's contents. The file in this example has a primary HDU (header-data-unit) with no data, and three HDUs with 3-D data. In this case, the data consists of three velocity fields:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"f = pyfits.open(join(data_dir, \\\"UnigridData\\\", \\\"velocity_field_20.fits\\\"))\\n\",\n    \"f.info()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can put it into a dictionary in the same way as before, but we slice the file handle `f` so that we don't use the `PrimaryHDU`. `hdu.name` is the field name and `hdu.data` is the actual data. Each of these velocity fields is in km/s. We can check that we got the correct fields. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"data = {}\\n\",\n    \"for hdu in f:\\n\",\n    \"    name = hdu.name.lower()\\n\",\n    \"    data[name] = (hdu.data, \\\"km/s\\\")\\n\",\n    \"print(data.keys())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The velocity field names in this case are slightly different than the standard yt field names for velocity fields, so we will reassign the field names:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"data[\\\"velocity_x\\\"] = data.pop(\\\"x-velocity\\\")\\n\",\n    \"data[\\\"velocity_y\\\"] = data.pop(\\\"y-velocity\\\")\\n\",\n    \"data[\\\"velocity_z\\\"] = data.pop(\\\"z-velocity\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we load the data into yt. Let's assume that the box size is a Mpc. Since these are velocity fields, we can overlay velocity vectors on slices, just as if we had loaded in data from a supported code. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_uniform_grid(data, data[\\\"velocity_x\\\"][0].shape, length_unit=(1.0, \\\"Mpc\\\"))\\n\",\n    \"slc = yt.SlicePlot(\\n\",\n    \"    ds, \\\"x\\\", [(\\\"gas\\\", \\\"velocity_x\\\"), (\\\"gas\\\", \\\"velocity_y\\\"), (\\\"gas\\\", \\\"velocity_z\\\")]\\n\",\n    \")\\n\",\n    \"for ax in \\\"xyz\\\":\\n\",\n    \"    slc.set_log((\\\"gas\\\", f\\\"velocity_{ax}\\\"), False)\\n\",\n    \"slc.annotate_velocity()\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Generic AMR Data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In a similar fashion to unigrid data, data gridded into rectangular patches at varying levels of resolution may also be loaded into yt. In this case, a list of grid dictionaries should be provided, with the requisite information about each grid's properties. This example sets up two grids: a top-level grid (`level == 0`) covering the entire domain and a subgrid at `level == 1`. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"grid_data = [\\n\",\n    \"    {\\n\",\n    \"        \\\"left_edge\\\": [0.0, 0.0, 0.0],\\n\",\n    \"        \\\"right_edge\\\": [1.0, 1.0, 1.0],\\n\",\n    \"        \\\"level\\\": 0,\\n\",\n    \"        \\\"dimensions\\\": [32, 32, 32],\\n\",\n    \"    },\\n\",\n    \"    {\\n\",\n    \"        \\\"left_edge\\\": [0.25, 0.25, 0.25],\\n\",\n    \"        \\\"right_edge\\\": [0.75, 0.75, 0.75],\\n\",\n    \"        \\\"level\\\": 1,\\n\",\n    \"        \\\"dimensions\\\": [32, 32, 32],\\n\",\n    \"    },\\n\",\n    \"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We'll just fill each grid with random density data, with a scaling with the grid refinement level.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"for g in grid_data:\\n\",\n    \"    g[\\\"density\\\"] = (prng.random(g[\\\"dimensions\\\"]) * 2 ** g[\\\"level\\\"], \\\"g/cm**3\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Particle fields are supported by adding 1-dimensional arrays to each `grid`. If a grid has no particles, the particle fields still have to be defined since they are defined elsewhere; set them to empty NumPy arrays:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"grid_data[0][\\\"particle_position_x\\\"] = (\\n\",\n    \"    np.array([]),\\n\",\n    \"    \\\"code_length\\\",\\n\",\n    \")  # No particles, so set empty arrays\\n\",\n    \"grid_data[0][\\\"particle_position_y\\\"] = (np.array([]), \\\"code_length\\\")\\n\",\n    \"grid_data[0][\\\"particle_position_z\\\"] = (np.array([]), \\\"code_length\\\")\\n\",\n    \"grid_data[1][\\\"particle_position_x\\\"] = (\\n\",\n    \"    prng.uniform(low=0.25, high=0.75, size=1000),\\n\",\n    \"    \\\"code_length\\\",\\n\",\n    \")\\n\",\n    \"grid_data[1][\\\"particle_position_y\\\"] = (\\n\",\n    \"    prng.uniform(low=0.25, high=0.75, size=1000),\\n\",\n    \"    \\\"code_length\\\",\\n\",\n    \")\\n\",\n    \"grid_data[1][\\\"particle_position_z\\\"] = (\\n\",\n    \"    prng.uniform(low=0.25, high=0.75, size=1000),\\n\",\n    \"    \\\"code_length\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Then, call `load_amr_grids`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_amr_grids(grid_data, [32, 32, 32])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`load_amr_grids` also takes the same keywords `bbox` and `sim_time` as `load_uniform_grid`. We could have also specified the length, time, velocity, and mass units in the same manner as before. Let's take a slice:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(ds, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"slc.annotate_particles(0.25, p_size=15.0, col=\\\"Pink\\\")\\n\",\n    \"slc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"source\": [\n    \"## Species fields\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"source\": [\n    \"One can also supply species fields to a stream dataset, in the form of mass fractions. These will then be used to generate derived fields for mass, number, and nuclei densities of the separate species. The naming conventions for the mass fractions should correspond to the format specified in [the yt documentation for species fields](https://yt-project.org/doc/analyzing/fields.html#species-fields).\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"arr = prng.random(size=(64, 64, 64))\\n\",\n    \"data = {\\n\",\n    \"    \\\"density\\\": (arr, \\\"g/cm**3\\\"),\\n\",\n    \"    \\\"H_p0_fraction\\\": (0.37 * np.ones_like(arr), \\\"dimensionless\\\"),\\n\",\n    \"    \\\"H_p1_fraction\\\": (0.37 * np.ones_like(arr), \\\"dimensionless\\\"),\\n\",\n    \"    \\\"He_fraction\\\": (0.24 * np.ones_like(arr), \\\"dimensionless\\\"),\\n\",\n    \"    \\\"CO_fraction\\\": (0.02 * np.ones_like(arr), \\\"dimensionless\\\"),\\n\",\n    \"}\\n\",\n    \"bbox = np.array([[-1.5, 1.5], [-1.5, 1.5], [-1.5, 1.5]])\\n\",\n    \"ds = yt.load_uniform_grid(data, arr.shape, length_unit=\\\"Mpc\\\", bbox=bbox, nprocs=64)\\n\",\n    \"dd = ds.all_data()\\n\",\n    \"print(dd.mean((\\\"gas\\\", \\\"CO_density\\\")))\\n\",\n    \"print(dd.min((\\\"gas\\\", \\\"H_nuclei_density\\\")))\\n\",\n    \"print(dd.max((\\\"gas\\\", \\\"He_number_density\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Multiple Particle Types\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For both uniform grid data and AMR data, one can specify particle fields with multiple types if the particle field names are given as field tuples instead of strings (the default particle type is `\\\"io\\\"`):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"posxr_arr = prng.uniform(low=-1.5, high=1.5, size=10000)\\n\",\n    \"posyr_arr = prng.uniform(low=-1.5, high=1.5, size=10000)\\n\",\n    \"poszr_arr = prng.uniform(low=-1.5, high=1.5, size=10000)\\n\",\n    \"posxb_arr = prng.uniform(low=-1.5, high=1.5, size=20000)\\n\",\n    \"posyb_arr = prng.uniform(low=-1.5, high=1.5, size=20000)\\n\",\n    \"poszb_arr = prng.uniform(low=-1.5, high=1.5, size=20000)\\n\",\n    \"data = {\\n\",\n    \"    (\\\"gas\\\", \\\"density\\\"): (prng.random(size=(64, 64, 64)), \\\"Msun/kpc**3\\\"),\\n\",\n    \"    (\\\"red\\\", \\\"particle_position_x\\\"): (posxr_arr, \\\"code_length\\\"),\\n\",\n    \"    (\\\"red\\\", \\\"particle_position_y\\\"): (posyr_arr, \\\"code_length\\\"),\\n\",\n    \"    (\\\"red\\\", \\\"particle_position_z\\\"): (poszr_arr, \\\"code_length\\\"),\\n\",\n    \"    (\\\"blue\\\", \\\"particle_position_x\\\"): (posxb_arr, \\\"code_length\\\"),\\n\",\n    \"    (\\\"blue\\\", \\\"particle_position_y\\\"): (posyb_arr, \\\"code_length\\\"),\\n\",\n    \"    (\\\"blue\\\", \\\"particle_position_z\\\"): (poszb_arr, \\\"code_length\\\"),\\n\",\n    \"}\\n\",\n    \"bbox = np.array([[-1.5, 1.5], [-1.5, 1.5], [-1.5, 1.5]])\\n\",\n    \"ds = yt.load_uniform_grid(\\n\",\n    \"    data,\\n\",\n    \"    data[\\\"gas\\\", \\\"density\\\"][0].shape,\\n\",\n    \"    length_unit=(1.0, \\\"Mpc\\\"),\\n\",\n    \"    mass_unit=(1.0, \\\"Msun\\\"),\\n\",\n    \"    bbox=bbox,\\n\",\n    \"    nprocs=4,\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can now see we have multiple particle types:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"dd = ds.all_data()\\n\",\n    \"print(ds.particle_types)\\n\",\n    \"print(dd[\\\"red\\\", \\\"particle_position_x\\\"].size)\\n\",\n    \"print(dd[\\\"blue\\\", \\\"particle_position_x\\\"].size)\\n\",\n    \"print(dd[\\\"all\\\", \\\"particle_position_x\\\"].size)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Caveats for Loading Generic Array Data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"* Particles may be difficult to integrate.\\n\",\n    \"* Data must already reside in memory before loading it in to yt, whether it is generated at runtime or loaded from disk. \\n\",\n    \"* Some functions may behave oddly, and parallelism will be disappointing or non-existent in most cases.\\n\",\n    \"* No consistency checks are performed on the hierarchy\\n\",\n    \"* Consistency between particle positions and grids is not checked; `load_amr_grids` assumes that particle positions associated with one grid are not bounded within another grid at a higher level, so this must be ensured by the user prior to loading the grid data. \"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.12\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/examining/Loading_Generic_Particle_Data.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading Generic Particle Data\\n\",\n    \"\\n\",\n    \"This example creates a fake in-memory particle dataset and then loads it as a yt dataset using the `load_particles` function.\\n\",\n    \"\\n\",\n    \"Our \\\"fake\\\" dataset will be numpy arrays filled with normally distributed randoml particle positions and uniform particle masses.  Since real data is often scaled, I arbitrarily multiply by 1e6 to show how to deal with scaled data.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"rng = np.random.default_rng()\\n\",\n    \"n_particles = 5_000_000\\n\",\n    \"\\n\",\n    \"ppx, ppy, ppz = 1e6 * rng.normal(size=(3, n_particles))\\n\",\n    \"\\n\",\n    \"ppm = np.ones(n_particles)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `load_particles` function accepts a dictionary populated with particle data fields loaded in memory as numpy arrays or python lists:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"data = {\\n\",\n    \"    (\\\"io\\\", \\\"particle_position_x\\\"): ppx,\\n\",\n    \"    (\\\"io\\\", \\\"particle_position_y\\\"): ppy,\\n\",\n    \"    (\\\"io\\\", \\\"particle_position_z\\\"): ppz,\\n\",\n    \"    (\\\"io\\\", \\\"particle_mass\\\"): ppm,\\n\",\n    \"}\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To hook up with yt's internal field system, the dictionary keys must be 'particle_position_x', 'particle_position_y', 'particle_position_z', and 'particle_mass', as well as any other particle field provided by one of the particle frontends.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `load_particles` function transforms the `data` dictionary into an in-memory yt `Dataset` object, providing an interface for further analysis with yt. The example below illustrates how to load the data dictionary we created above.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\\n\",\n    \"from yt.units import Msun, parsec\\n\",\n    \"\\n\",\n    \"bbox = 1.1 * np.array(\\n\",\n    \"    [[min(ppx), max(ppx)], [min(ppy), max(ppy)], [min(ppz), max(ppz)]]\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"ds = yt.load_particles(data, length_unit=1.0 * parsec, mass_unit=1e8 * Msun, bbox=bbox)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `length_unit` and `mass_unit` are the conversion from the units used in the `data` dictionary to CGS.  I've arbitrarily chosen one parsec and 10^8 Msun for this example. \\n\",\n    \"\\n\",\n    \"The `n_ref` parameter controls how many particle it takes to accumulate in an oct-tree cell to trigger refinement.  Larger `n_ref` will decrease poisson noise at the cost of resolution in the octree.  \\n\",\n    \"\\n\",\n    \"Finally, the `bbox` parameter is a bounding box in the units of the dataset that contains all of the particles.  This is used to set the size of the base octree block.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This new dataset acts like any other yt `Dataset` object, and can be used to create data objects and query for yt fields.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ad = ds.all_data()\\n\",\n    \"print(ad.mean((\\\"io\\\", \\\"particle_position_x\\\")))\\n\",\n    \"print(ad.sum((\\\"io\\\", \\\"particle_mass\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can project the particle mass field like so:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ParticleProjectionPlot(ds, \\\"z\\\", (\\\"io\\\", \\\"particle_mass\\\"))\\n\",\n    \"prj.set_width((8, \\\"Mpc\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, one can specify multiple particle types in the `data` directory by setting the field names to be field tuples (the default field type for particles is `\\\"io\\\"`) if one is not specified:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"n_gas_particles = 1_000_000\\n\",\n    \"n_star_particles = 1_000_000\\n\",\n    \"n_dm_particles = 2_000_000\\n\",\n    \"\\n\",\n    \"ppxg, ppyg, ppzg = 1e6 * rng.normal(size=(3, n_gas_particles))\\n\",\n    \"ppmg = np.ones(n_gas_particles)\\n\",\n    \"hsml = 10000 * np.ones(n_gas_particles)\\n\",\n    \"dens = 2.0e-4 * np.ones(n_gas_particles)\\n\",\n    \"\\n\",\n    \"ppxd, ppyd, ppzd = 1e6 * rng.normal(size=(3, n_dm_particles))\\n\",\n    \"ppmd = np.ones(n_dm_particles)\\n\",\n    \"\\n\",\n    \"ppxs, ppys, ppzs = 5e5 * rng.normal(size=(3, n_star_particles))\\n\",\n    \"ppms = 0.1 * np.ones(n_star_particles)\\n\",\n    \"\\n\",\n    \"bbox = 1.1 * np.array(\\n\",\n    \"    [\\n\",\n    \"        [\\n\",\n    \"            min(ppxg.min(), ppxd.min(), ppxs.min()),\\n\",\n    \"            max(ppxg.max(), ppxd.max(), ppxs.max()),\\n\",\n    \"        ],\\n\",\n    \"        [\\n\",\n    \"            min(ppyg.min(), ppyd.min(), ppys.min()),\\n\",\n    \"            max(ppyg.max(), ppyd.max(), ppys.max()),\\n\",\n    \"        ],\\n\",\n    \"        [\\n\",\n    \"            min(ppzg.min(), ppzd.min(), ppzs.min()),\\n\",\n    \"            max(ppzg.max(), ppzd.max(), ppzs.max()),\\n\",\n    \"        ],\\n\",\n    \"    ]\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"data2 = {\\n\",\n    \"    (\\\"gas\\\", \\\"particle_position_x\\\"): ppxg,\\n\",\n    \"    (\\\"gas\\\", \\\"particle_position_y\\\"): ppyg,\\n\",\n    \"    (\\\"gas\\\", \\\"particle_position_z\\\"): ppzg,\\n\",\n    \"    (\\\"gas\\\", \\\"particle_mass\\\"): ppmg,\\n\",\n    \"    (\\\"gas\\\", \\\"smoothing_length\\\"): hsml,\\n\",\n    \"    (\\\"gas\\\", \\\"density\\\"): dens,\\n\",\n    \"    (\\\"dm\\\", \\\"particle_position_x\\\"): ppxd,\\n\",\n    \"    (\\\"dm\\\", \\\"particle_position_y\\\"): ppyd,\\n\",\n    \"    (\\\"dm\\\", \\\"particle_position_z\\\"): ppzd,\\n\",\n    \"    (\\\"dm\\\", \\\"particle_mass\\\"): ppmd,\\n\",\n    \"    (\\\"star\\\", \\\"particle_position_x\\\"): ppxs,\\n\",\n    \"    (\\\"star\\\", \\\"particle_position_y\\\"): ppys,\\n\",\n    \"    (\\\"star\\\", \\\"particle_position_z\\\"): ppzs,\\n\",\n    \"    (\\\"star\\\", \\\"particle_mass\\\"): ppms,\\n\",\n    \"}\\n\",\n    \"\\n\",\n    \"ds2 = yt.load_particles(\\n\",\n    \"    data2, length_unit=1.0 * parsec, mass_unit=1e8 * Msun, bbox=bbox\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We now have separate `\\\"gas\\\"`, `\\\"dm\\\"`, and `\\\"star\\\"` particles. Since the `\\\"gas\\\"` particles have `\\\"density\\\"` and `\\\"smoothing_length\\\"` fields, they are recognized as SPH particles:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ad = ds2.all_data()\\n\",\n    \"c = np.array([ad.mean((\\\"gas\\\", ax)).to(\\\"code_length\\\") for ax in \\\"xyz\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"slc = yt.SlicePlot(ds2, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"), center=c)\\n\",\n    \"slc.set_zlim((\\\"gas\\\", \\\"density\\\"), 1e-19, 2.0e-18)\\n\",\n    \"slc.set_width((4, \\\"Mpc\\\"))\\n\",\n    \"slc.show()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.12.0\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/examining/Loading_Spherical_Data.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Loading Spherical Data\\n\",\n    \"\\n\",\n    \"Support in yt for non-Cartesian geometries is partial and is still being extended, but here is an example of how to load spherical data from a regularly-spaced grid. For irregularly spaced grids, a similar setup can be used, either by using supplying the `cell_widths` parameter to `load_uniform_grid` (see [Stretched Grid Data](https://yt-project.org/docs/dev/examining/loading_data.html#stretched-grid-data) for more details), or by using the `load_hexahedral_mesh` method.\\n\",\n    \"\\n\",\n    \"Note that in yt, \\\"spherical\\\" means that it is ordered $r$, $\\\\theta$, $\\\\phi$, where $\\\\theta$ is the declination from the azimuth (running from $0$ to $\\\\pi$ and $\\\\phi$ is the angle around the zenith (running from $0$ to $2\\\\pi$).\\n\",\n    \"\\n\",\n    \"We first start out by loading yt.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, we create a few derived fields.  The first three are just straight translations of the Cartesian coordinates, so that we can see where we are located in the data, and understand what we're seeing.  The final one is just a fun field that is some combination of the three coordinates, and will vary in all dimensions.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"@yt.derived_field(name=\\\"sphx\\\", units=\\\"cm\\\", take_log=False, sampling_type=\\\"cell\\\")\\n\",\n    \"def sphx(data):\\n\",\n    \"    return np.cos(data[\\\"phi\\\"]) * np.sin(data[\\\"theta\\\"]) * data[\\\"r\\\"]\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"@yt.derived_field(name=\\\"sphy\\\", units=\\\"cm\\\", take_log=False, sampling_type=\\\"cell\\\")\\n\",\n    \"def sphy(data):\\n\",\n    \"    return np.sin(data[\\\"phi\\\"]) * np.sin(data[\\\"theta\\\"]) * data[\\\"r\\\"]\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"@yt.derived_field(name=\\\"sphz\\\", units=\\\"cm\\\", take_log=False, sampling_type=\\\"cell\\\")\\n\",\n    \"def sphz(data):\\n\",\n    \"    return np.cos(data[\\\"theta\\\"]) * data[\\\"r\\\"]\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"@yt.derived_field(name=\\\"funfield\\\", units=\\\"cm\\\", take_log=False, sampling_type=\\\"cell\\\")\\n\",\n    \"def funfield(data):\\n\",\n    \"    return (np.sin(data[\\\"phi\\\"]) ** 2 + np.cos(data[\\\"theta\\\"]) ** 2) * (\\n\",\n    \"        1.0 * data[\\\"r\\\"].uq + data[\\\"r\\\"]\\n\",\n    \"    )\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Loading Data\\n\",\n    \"\\n\",\n    \"Now we can actually load our data.  We use the `load_uniform_grid` function here.  Normally, the first argument would be a dictionary of field data, where the keys were the field names and the values the field data arrays.  Here, we're just going to look at derived fields, so we supply an empty one.\\n\",\n    \"\\n\",\n    \"The next few arguments are the number of dimensions, the bounds, and we then specify the geometry as spherical.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_uniform_grid(\\n\",\n    \"    {},\\n\",\n    \"    [128, 128, 128],\\n\",\n    \"    bbox=np.array([[0.0, 1.0], [0.0, np.pi], [0.0, 2 * np.pi]]),\\n\",\n    \"    geometry=\\\"spherical\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Looking at Data\\n\",\n    \"\\n\",\n    \"Now we can take slices.  The first thing we will try is making a slice of data along the \\\"phi\\\" axis, here $\\\\pi/2$, which will be along the y axis in the positive direction.  We use the `.slice` attribute, which creates a slice, and then we convert this into a plot window.  Note that here 2 is used to indicate the third axis (0-indexed) which for spherical data is $\\\\phi$.\\n\",\n    \"\\n\",\n    \"This is the manual way of creating a plot -- below, we'll use the standard, automatic ways.  Note that the coordinates run from $-r$ to $r$ along the $z$ axis and from $0$ to $r$ along the $R$ axis.  We use the capital $R$ to indicate that it's the $R$ along the $x-y$ plane.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"s = ds.slice(2, np.pi / 2)\\n\",\n    \"p = s.to_pw(\\\"funfield\\\", origin=\\\"native\\\")\\n\",\n    \"p.set_zlim(\\\"all\\\", 0.0, 4.0)\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also slice along $r$.  For now, this creates a regular grid with *incorrect* units for phi and theta.  We are currently exploring two other options -- a simple aitoff projection, and fixing it to use the correct units as-is.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"s = yt.SlicePlot(ds, \\\"r\\\", \\\"funfield\\\")\\n\",\n    \"s.set_zlim(\\\"all\\\", 0.0, 4.0)\\n\",\n    \"s.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also slice at constant $\\\\theta$.  But, this is a weird thing!  We're slicing at a constant declination from the azimuth.  What this means is that when thought of in a Cartesian domain, this slice is actually a cone.  The axes have been labeled appropriately, to indicate that these are not exactly the $x$ and $y$ axes, but instead differ by a factor of $\\\\sin(\\\\theta))$.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"s = yt.SlicePlot(ds, \\\"theta\\\", \\\"funfield\\\")\\n\",\n    \"s.set_zlim(\\\"all\\\", 0.0, 4.0)\\n\",\n    \"s.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We've seen lots of the `funfield` plots, but we can also look at the Cartesian axes.  This next plot plots the Cartesian $x$, $y$ and $z$ values on a $\\\\theta$ slice.  Because we're not supplying an argument to the `center` parameter, yt will place it at the center of the $\\\\theta$ axis, which will be at $\\\\pi/2$, where it will be aligned with the $x-y$ plane.  The slight change in `sphz` results from the cells themselves migrating, and plotting the center of those cells.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"s = yt.SlicePlot(ds, \\\"theta\\\", [\\\"sphx\\\", \\\"sphy\\\", \\\"sphz\\\"])\\n\",\n    \"s.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can do the same with the $\\\\phi$ axis.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"s = yt.SlicePlot(ds, \\\"phi\\\", [\\\"sphx\\\", \\\"sphy\\\", \\\"sphz\\\"])\\n\",\n    \"s.show()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.5.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/examining/index.rst",
    "content": ".. _examining-data:\n\nLoading and Examining Data\n==========================\n\nNominally, one should just be able to run ``yt.load()`` on a dataset and start\ncomputing; however, there may be additional notes associated with different\ndata formats as described below.  Furthermore, we provide methods for loading\ndata from unsupported data formats in :doc:`Loading_Generic_Array_Data`,\n:doc:`Loading_Generic_Particle_Data`, and :doc:`Loading_Spherical_Data`.\nLastly, if you want to examine the raw data for your particular dataset, visit\n:ref:`low-level-data-inspection`.\n\n.. toctree::\n   :maxdepth: 2\n\n   loading_data\n   Loading_Generic_Array_Data\n   Loading_Generic_Particle_Data\n   Loading_Data_via_Functions\n   Loading_Spherical_Data\n   low_level_inspection\n"
  },
  {
    "path": "doc/source/examining/loading_data.rst",
    "content": ".. _loading-data:\n\nLoading Data\n============\n\nThis section contains information on how to load data into yt, as well as\nsome important caveats about different data formats.\n\n.. _loading-sample-data:\n\nSample Data\n-----------\n\nThe yt community has provided a large number of sample datasets, which are\naccessible from https://yt-project.org/data/ .  yt also provides a helper\nfunction, ``yt.load_sample``, that can load from a set of sample datasets.  The\nquickstart notebooks in this documentation utilize this.\n\nThe files are, in general, named identically to their listings on the data\ncatalog page.  For instance, you can load ``IsolatedGalaxy`` by executing:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load_sample(\"IsolatedGalaxy\")\n\nTo find a list of all available datasets, you can call ``load_sample`` without\nany arguments, and it will return a list of the names that can be supplied:\n\n.. code-block:: python\n\n   import yt\n\n   yt.load_sample()\n\nThis will return a list of possible filenames; more information can be accessed on the data catalog.\n\n\n.. _loading-archived-data:\n\nArchived Data\n-------------\n\nIf your data is stored as a (compressed) tar file, you can access the contained\ndataset directly without extracting the tar file.\nThis can be achieved using the ``load_archive`` function:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load_archive(\"IsolatedGalaxy.tar.gz\", \"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\nThe first argument is the path to the archive file, the second one is the path to the file to load\nin the archive. Subsequent arguments are passed to ``yt.load``.\n\nThe functionality requires the package `ratarmount <https://github.com/mxmlnkn/ratarmount/>`_ to be installed.\nUnder the hood, yt will mount the archive as a (read-only) filesystem. Note that this requires the\nentire archive to be read once to compute the location of each file in the archive; subsequent accesses\nwill be much faster.\nAll archive formats supported by `ratarmount <https://github.com/mxmlnkn/ratarmount>`__ should be loadable, provided\nthe dependencies are installed; this includes ``tar``, ``tar.gz`` and tar.bz2`` formats.\n\n.. _loading-hdf5-data:\n\nSimple HDF5 Data\n----------------\n\n.. note::\n\n   This wrapper takes advantage of the functionality described in\n   :doc:`Loading_Data_via_Functions`\n   but the basics of setting up function handlers,\n   guessing fields, etc, are handled by yt.\n\nUsing the function :func:`yt.loaders.load_hdf5_file`, you can load a generic\nset of fields from an HDF5 file and have a fully-operational yt dataset.  For\ninstance, in the yt sample data repository, we have the `UniGrid\nData <https://yt-project.org/data/UnigridData.tar.gz>`_ dataset (~1.6GB).  This dataset includes the file ``turb_vels.h5`` with this structure:\n\n.. code-block:: bash\n\n   $ h5ls -r h5ls -r ./UnigridData/turb_vels.h5\n\n   /                        Group\n   /Bx                      Dataset {256, 256, 256}\n   /By                      Dataset {256, 256, 256}\n   /Bz                      Dataset {256, 256, 256}\n   /Density                 Dataset {256, 256, 256}\n   /MagneticEnergy          Dataset {256, 256, 256}\n   /Temperature             Dataset {256, 256, 256}\n   /turb_x-velocity         Dataset {256, 256, 256}\n   /turb_y-velocity         Dataset {256, 256, 256}\n   /turb_z-velocity         Dataset {256, 256, 256}\n   /x-velocity              Dataset {256, 256, 256}\n   /y-velocity              Dataset {256, 256, 256}\n   /z-velocity              Dataset {256, 256, 256}\n\nIn versions of yt prior to 4.1, these could be loaded into memory individually\nand then accessed *en masse* by the :func:`yt.loaders.load_uniform_grid`\nfunction.  Introduced in version 4.1, however, was the ability to provide the\nfilename and then allow yt to identify the available fields and even subset\nthem into chunks to preserve memory.  Only those requested fields will be\nloaded at the time of the request, and they will be subset into chunks to avoid\nover-allocating for reduction operations.\n\nTo use the auto-loader, call :func:`~yt.loaders.load_hdf5_file` with the name\nof the file.  Optionally, you can specify the root node of the file to probe\nfor fields -- for instance, if all of the fields are stored under ``/grid`` (as\nthey are in output from the ytdata frontend).  You can also provide the\nexpected bounding box, which will otherwise default to 0..1 in all dimensions,\nthe names of fields to make available (by default yt will probe for them) and\nthe number of chunks to subdivide the file into.  If the number of chunks is\nnot specified it defaults to trying to keep the size of each individual chunk\nno more than $64^3$ zones.\n\nTo load the above file, we would use the function as follows:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load_hdf5_file(\"UnigridData/turb_vels.h5\")\n\nAt this point, we now have a dataset that we can do all of our normal\noperations on, and all of the known yt derived fields will be available.\n\n.. _loading-amrvac-data:\n\nAMRVAC Data\n-----------\n\nTo load data to yt, simply use\n\n.. code-block::\n\n  import yt\n  ds = yt.load(\"output0010.dat\")\n\n\n.. rubric:: Dataset geometry & periodicity\n\nStarting from AMRVAC 2.2, and datfile format 5, a geometry flag\n(e.g. \"Cartesian_2.5D\", \"Polar_2D\", \"Cylindrical_1.5D\"...) was added\nto the datfile header.  yt will fall back to a cartesian mesh if the\ngeometry flag is not found.  For older datfiles however it is possible\nto provide it externally with the ``geometry_override`` parameter.\n\n.. code-block:: python\n\n  # examples\n  ds = yt.load(\"output0010.dat\", geometry_override=\"polar\")\n  ds = yt.load(\"output0010.dat\", geometry_override=\"cartesian\")\n\nNote that ``geometry_override`` has priority over any ``geometry`` flag\npresent in recent datfiles, which means it can be used to force ``r``\nVS ``theta`` 2D plots in polar geometries (for example), but this may\nproduce unpredictable behaviour and comes with no guarantee.\n\nA ``ndim``-long ``periodic`` boolean array was also added to improve\ncompatibility with yt. See http://amrvac.org/md_doc_fileformat.html\nfor details.\n\n.. rubric:: Auto-setup for derived fields\n\nYt will attempt to mimic the way AMRVAC internally defines kinetic energy,\npressure, and sound speed. To see a complete list of fields that are defined after\nloading, one can simply type\n\n.. code-block:: python\n\n  print(ds.derived_field_list)\n\nNote that for adiabatic (magneto-)hydrodynamics, i.e. ``(m)hd_energy = False`` in\nAMRVAC, additional input data is required in order to setup some of these fields.\nThis is done by passing the corresponding parfile(s) at load time\n\n.. code-block:: python\n\n  # example using a single parfile\n  ds = yt.load(\"output0010.dat\", parfiles=\"amrvac.par\")\n\n  # ... or using multiple parfiles\n  ds = yt.load(\"output0010.dat\", parfiles=[\"amrvac.par\", \"modifier.par\"])\n\nIn case more than one parfile is passed, yt will create a single namelist by\nreplicating AMRVAC's rules (see \"Using multiple par files\"\nhttp://amrvac.org/md_doc_commandline.html).\n\n\n.. rubric:: Unit System\n\nAMRVAC only supports dimensionless fields and as such, no unit system\nis ever attached to any given dataset.  yt however defines physical\nquantities and give them units. As is customary in yt, the default\nunit system is ``cgs``, e.g. lengths are read as \"cm\" unless specified\notherwise.\n\nThe user has two ways to control displayed units, through\n``unit_system`` (``\"cgs\"``, ``\"mks\"`` or ``\"code\"``) and\n``units_override``. Example:\n\n.. code-block:: python\n\n  units_override = dict(length_unit=(100.0, \"au\"), mass_unit=yt.units.mass_sun)\n  ds = yt.load(\"output0010.dat\", units_override=units_override, unit_system=\"mks\")\n\nTo ensure consistency with normalisations as used in AMRVAC we only allow\noverriding a maximum of three units. Allowed unit combinations at the moment are\n\n.. code-block:: none\n\n  {numberdensity_unit, temperature_unit, length_unit}\n  {mass_unit, temperature_unit, length_unit}\n  {mass_unit, time_unit, length_unit}\n  {numberdensity_unit, velocity_unit, length_unit}\n  {mass_unit, velocity_unit, length_unit}\n\nAppropriate errors are thrown for other combinations.\n\n\n.. rubric:: Partially supported and unsupported features\n\n* a maximum of 100 dust species can be read by yt at the moment.\n  If your application needs this limit increased, please report an issue\n  https://github.com/yt-project/yt/issues\n* particle data: currently not supported (but might come later)\n* staggered grids (AMRVAC 2.2 and later): yt logs a warning if you load\n  staggered datasets, but the flag is currently ignored.\n* \"stretched grids\" are being implemented in yt, but are not yet\n  fully-supported.  (Previous versions of this file suggested they would\n  \"never\" be supported, which we hope to prove incorrect once we finish\n  implementing stretched grids in AMR.  At present, stretched grids are\n  only supported on a single level of refinement.)\n\n.. note::\n\n   Ghost cells exist in .dat files but never read by yt.\n\n.. _loading-art-data:\n\nART Data\n--------\n\nART data has been supported in the past by Christopher Moody and is currently\ncared for by Kenza Arraki.  Please contact the ``yt-dev`` mailing list if you\nare interested in using yt for ART data, or if you are interested in assisting\nwith development of yt to work with ART data.\n\nTo load an ART dataset you can use the ``yt.load`` command and provide it the\ngas mesh file. It will search for and attempt to find the complementary dark\nmatter and stellar particle header and data files. However, your simulations may\nnot follow the same naming convention.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"D9p_500/10MpcBox_HartGal_csf_a0.500.d\")\n\n\nIt will search for and attempt to find the complementary dark matter and stellar\nparticle header and data files. However, your simulations may not follow the\nsame naming convention.\n\nFor example, the single snapshot given in the sample data has a series of files\nthat look like this:\n\n.. code-block:: none\n\n   10MpcBox_HartGal_csf_a0.500.d  #Gas mesh\n   PMcrda0.500.DAT                #Particle header\n   PMcrs0a0.500.DAT               #Particle data (positions,velocities)\n   stars_a0.500.dat               #Stellar data (metallicities, ages, etc.)\n\nThe ART frontend tries to find the associated files matching the\nabove, but if that fails you can specify ``file_particle_header``,\n``file_particle_data``, and ``file_particle_stars``, in addition to\nspecifying the gas mesh. Note that the ``pta0.500.dat`` or ``pt.dat``\nfile containing particle time steps is not loaded by yt.\n\nYou also have the option of gridding particles and assigning them onto the\nmeshes.  This process is in beta, and for the time being, it's probably best to\nleave ``do_grid_particles=False`` as the default.\n\nTo speed up the loading of an ART file, you have a few options. You can turn\noff the particles entirely by setting ``discover_particles=False``. You can\nalso only grid octs up to a certain level, ``limit_level=5``, which is useful\nwhen debugging by artificially creating a 'smaller' dataset to work with.\n\nFinally, when stellar ages are computed we 'spread' the ages evenly within a\nsmoothing window. By default this is turned on and set to 10Myr. To turn this\noff you can set ``spread=False``, and you can tweak the age smoothing window\nby specifying the window in seconds, ``spread=1.0e7*365*24*3600``.\n\nThere is currently preliminary support for dark matter only ART data. To load a\ndataset use the ``yt.load`` command and provide it the particle data file. It\nwill search for the complementary particle header file.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"PMcrs0a0.500.DAT\")\n\nImportant: This should not be used for loading just the dark matter\ndata for a 'regular' hydrodynamical data set as the units and IO are\ndifferent!\n\n\n.. _loading-artio-data:\n\nARTIO Data\n----------\n\nARTIO data has a well-specified internal parameter system and has few free\nparameters.  However, for optimization purposes, the parameter that provides\nthe most guidance to yt as to how to manage ARTIO data is ``max_range``.  This\ngoverns the maximum number of space-filling curve cells that will be used in a\nsingle \"chunk\" of data read from disk.  For small datasets, setting this number\nvery large will enable more data to be loaded into memory at any given time;\nfor very large datasets, this parameter can be left alone safely.  By default\nit is set to 1024; it can in principle be set as high as the total number of\nSFC cells.\n\nTo load ARTIO data, you can specify a command such as this:\n\n.. code-block:: python\n\n   ds = load(\"./A11QR1/s11Qzm1h2_a1.0000.art\")\n\n.. _loading-athena-data:\n\nAthena Data\n-----------\n\nAthena 4.x VTK data is supported and cared for by John ZuHone. Both uniform grid\nand SMR datasets are supported.\n\n.. note::\n\n   yt also recognizes Fargo3D data written to VTK files as\n   Athena data, but support for Fargo3D data is preliminary.\n\nLoading Athena datasets is slightly different depending on whether\nyour dataset came from a serial or a parallel run. If the data came\nfrom a serial run or you have joined the VTK files together using the\nAthena tool ``join_vtk``, you can load the data like this:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"kh.0010.vtk\")\n\nThe filename corresponds to the file on SMR level 0, whereas if there\nare multiple levels the corresponding files will be picked up\nautomatically, assuming they are laid out in ``lev*`` subdirectories\nunder the directory where the base file is located.\n\nFor parallel datasets, yt assumes that they are laid out in\ndirectories named ``id*``, one for each processor number, each with\n``lev*`` subdirectories for additional refinement levels. To load this\ndata, call ``load`` with the base file in the ``id0`` directory:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"id0/kh.0010.vtk\")\n\nwhich will pick up all of the files in the different ``id*`` directories for\nthe entire dataset.\n\nThe default unit system in yt is cgs (\"Gaussian\") units, but Athena data is not\nnormally stored in these units, so the code unit system is the default unit\nsystem for Athena data. This means that answers to field queries from data\nobjects and plots of data will be expressed in code units. Note that the default\nconversions from these units will still be in terms of cgs units, e.g. 1\n``code_length`` equals 1 cm, and so on. If you would like to provided different\nconversions, you may supply conversions for length, time, and mass to ``load``\nusing the ``units_override`` functionality:\n\n.. code-block:: python\n\n   import yt\n\n   units_override = {\n       \"length_unit\": (1.0, \"Mpc\"),\n       \"time_unit\": (1.0, \"Myr\"),\n       \"mass_unit\": (1.0e14, \"Msun\"),\n   }\n\n   ds = yt.load(\"id0/cluster_merger.0250.vtk\", units_override=units_override)\n\nThis means that the yt fields, e.g. ``(\"gas\",\"density\")``,\n``(\"gas\",\"velocity_x\")``, ``(\"gas\",\"magnetic_field_x\")``, will be in cgs units\n(or whatever unit system was specified), but the Athena fields, e.g.,\n``(\"athena\",\"density\")``, ``(\"athena\",\"velocity_x\")``,\n``(\"athena\",\"cell_centered_B_x\")``, will be in code units.\n\nThe default normalization for various magnetic-related quantities such as\nmagnetic pressure, Alfven speed, etc., as well as the conversion between\nmagnetic code units and other units, is Gaussian/CGS, meaning that factors\nof :math:`4\\pi` or :math:`\\sqrt{4\\pi}` will appear in these quantities, e.g.\n:math:`p_B = B^2/8\\pi`. To use the Lorentz-Heaviside normalization instead,\nin which the factors of :math:`4\\pi` are dropped (:math:`p_B = B^2/2), for\nexample), set ``magnetic_normalization=\"lorentz_heaviside\"`` in the call to\n``yt.load``:\n\n.. code-block:: python\n\n    ds = yt.load(\n        \"id0/cluster_merger.0250.vtk\",\n        units_override=units_override,\n        magnetic_normalization=\"lorentz_heaviside\",\n    )\n\nSome 3D Athena outputs may have large grids (especially parallel datasets\nsubsequently joined with the ``join_vtk`` script), and may benefit from being\nsubdivided into \"virtual grids\". For this purpose, one can pass in the\n``nprocs`` parameter:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"sloshing.0000.vtk\", nprocs=8)\n\nwhich will subdivide each original grid into ``nprocs`` grids. Note that this\nparameter is independent of the number of MPI tasks assigned to analyze the data\nset in parallel (see :ref:`parallel-computation`), and ideally should be (much)\nlarger than this.\n\n.. note::\n\n    Virtual grids are only supported (and really only necessary) for 3D data.\n\nAlternative values for the following simulation parameters may be specified\nusing a ``parameters`` dict, accepting the following keys:\n\n* ``gamma``: ratio of specific heats, Type: Float. If not specified,\n  :math:`\\gamma = 5/3` is assumed.\n* ``geometry``: Geometry type, currently accepts ``\"cartesian\"`` or\n  ``\"cylindrical\"``. Default is ``\"cartesian\"``.\n* ``periodicity``: Is the domain periodic? Type: Tuple of boolean values\n  corresponding to each dimension. Defaults to ``True`` in all directions.\n* ``mu``: mean molecular weight, Type: Float. If not specified, :math:`\\mu = 0.6`\n  (for a fully ionized primordial plasma) is assumed.\n\n.. code-block:: python\n\n   import yt\n\n   parameters = {\n       \"gamma\": 4.0 / 3.0,\n       \"geometry\": \"cylindrical\",\n       \"periodicity\": (False, False, False),\n   }\n\n   ds = yt.load(\"relativistic_jet_0000.vtk\", parameters=parameters)\n\n.. rubric:: Caveats\n\n* yt primarily works with primitive variables. If the Athena dataset contains\n  conservative variables, the yt primitive fields will be generated from the\n  conserved variables on disk.\n* Special relativistic datasets may be loaded, but at this time not all of\n  their fields are fully supported. In particular, the relationships between\n  quantities such as pressure and thermal energy will be incorrect, as it is\n  currently assumed that their relationship is that of an ideal a\n  :math:`\\gamma`-law equation of state. This will be rectified in a future\n  release.\n* Domains may be visualized assuming periodicity.\n* Particle list data is currently unsupported.\n\n.. _loading-athena-pp-data:\n\nAthena++ Data\n-------------\n\nAthena++ HDF5 data is supported and cared for by John ZuHone. Uniform-grid, SMR,\nand AMR datasets in cartesian coordinates are fully supported. Support for\ncurvilinear coordinates and/or non-constant grid cell sizes exists, but is preliminary.\n\nThe default unit system in yt is cgs (\"Gaussian\") units, but Athena++ data is\nnot normally stored in these units, so the code unit system is the default unit\nsystem for Athena++ data. This means that answers to field queries from data\nobjects and plots of data will be expressed in code units. Note that the default\nconversions from these units will still be in terms of cgs units, e.g. 1\n``code_length`` equals 1 cm, and so on. If you would like to provided different\nconversions, you may supply conversions for length, time, and mass to ``load``\nusing the ``units_override`` functionality:\n\n.. code-block:: python\n\n   import yt\n\n   units_override = {\n       \"length_unit\": (1.0, \"Mpc\"),\n       \"time_unit\": (1.0, \"Myr\"),\n       \"mass_unit\": (1.0e14, \"Msun\"),\n   }\n\n   ds = yt.load(\"AM06/AM06.out1.00400.athdf\", units_override=units_override)\n\nThis means that the yt fields, e.g. ``(\"gas\",\"density\")``,\n``(\"gas\",\"velocity_x\")``, ``(\"gas\",\"magnetic_field_x\")``, will be in cgs units\n(or whatever unit system was specified), but the Athena fields, e.g.,\n``(\"athena_pp\",\"density\")``, ``(\"athena_pp\",\"vel1\")``, ``(\"athena_pp\",\"Bcc1\")``,\nwill be in code units.\n\nThe default normalization for various magnetic-related quantities such as\nmagnetic pressure, Alfven speed, etc., as well as the conversion between\nmagnetic code units and other units, is Gaussian/CGS, meaning that factors\nof :math:`4\\pi` or :math:`\\sqrt{4\\pi}` will appear in these quantities, e.g.\n:math:`p_B = B^2/8\\pi`. To use the Lorentz-Heaviside normalization instead,\nin which the factors of :math:`4\\pi` are dropped (:math:`p_B = B^2/2), for\nexample), set ``magnetic_normalization=\"lorentz_heaviside\"`` in the call to\n``yt.load``:\n\n.. code-block:: python\n\n    ds = yt.load(\n        \"AM06/AM06.out1.00400.athdf\",\n        units_override=units_override,\n        magnetic_normalization=\"lorentz_heaviside\",\n    )\n\nAlternative values for the following simulation parameters may be specified\nusing a ``parameters`` dict, accepting the following keys:\n\n* ``gamma``: ratio of specific heats, Type: Float. If not specified,\n  :math:`\\gamma = 5/3` is assumed.\n* ``geometry``: Geometry type, currently accepts ``\"cartesian\"`` or\n  ``\"cylindrical\"``. Default is ``\"cartesian\"``.\n* ``periodicity``: Is the domain periodic? Type: Tuple of boolean values\n  corresponding to each dimension. Defaults to ``True`` in all directions.\n* ``mu``: mean molecular weight, Type: Float. If not specified, :math:`\\mu = 0.6`\n  (for a fully ionized primordial plasma) is assumed.\n\n.. rubric:: Caveats\n\n* yt primarily works with primitive variables. If the Athena++ dataset contains\n  conservative variables, the yt primitive fields will be generated from the\n  conserved variables on disk.\n* Special relativistic datasets may be loaded, but at this time not all of their\n  fields are fully supported. In particular, the relationships between\n  quantities such as pressure and thermal energy will be incorrect, as it is\n  currently assumed that their relationship is that of an ideal\n  :math:`\\gamma`-law equation of state. This will be rectified in a future\n  release.\n* Domains may be visualized assuming periodicity.\n\n.. _loading-parthenon-data:\n\nParthenon Data\n--------------\n\nParthenon HDF5 data is supported and cared for by Forrest Glines and Philipp Grete.\nThe Parthenon framework is the basis for various downstream codes, e.g.,\n`AthenaPK <https://github.com/parthenon-hpc-lab/athenapk>`_,\n`Phoebus <https://github.com/lanl/phoebus>`_,\n`KHARMA <https://github.com/AFD-Illinois/kharma>`_,\nRIOT, and the\n`parthenon-hydro <https://github.com/parthenon-hpc-lab/parthenon-hydro>`_ miniapp.\nSupport for these codes is handled through the common Parthenon frontend with\nspecifics described in the following.\nNote that only AthenaPK data is currently automatically converted to the\nstandard fields known by yt.\nFor other codes, the raw data of the fields stored in the output file is accessible\nand a conversion between those fields and yt standard fields needs to be done manually.\n\n   .. rubric:: Caveats\n\n* Reading particle data from Parthenon output is currently not supported.\n* Spherical and cylindrical coordinates only work for AthenaPK data.\n* Only periodic boundary conditions are properly handled. Calculating quantities requiring\nlarger stencils (like derivatives) will be incorrect at mesh boundaries that are not periodic.\n\nAthenaPK\n^^^^^^^^\n\nFluid data on uniform-grid, SMR, and AMR datasets in Cartesian coordinates are fully supported.\n\nAthenaPK data may contain information on units in the output (when specified\nvia the ``<units>`` block in the input file when the simulation was originally run).\nIf that information is present, it will be used by yt.\nOtherwise the default unit system will be the code unit\nsystem with conversion of 1 ``code_length`` equalling 1 cm, and so on (given yt's default\ncgs/\"Gaussian\" unit system).  If you would like to provided different\nconversions, you may supply conversions for length, time, and mass to ``load``\nusing the ``units_override`` functionality:\n\n.. code-block:: python\n\n   import yt\n\n   units_override = {\n       \"length_unit\": (1.0, \"Mpc\"),\n       \"time_unit\": (1.0, \"Myr\"),\n       \"mass_unit\": (1.0e14, \"Msun\"),\n   }\n\n   ds = yt.load(\"parthenon.restart.final.rhdf\", units_override=units_override)\n\nThis means that the yt fields, e.g. ``(\"gas\",\"density\")``,\n``(\"gas\",\"velocity_x\")``, ``(\"gas\",\"magnetic_field_x\")``, will be in cgs units\n(or whatever unit system was specified), but the AthenaPK fields, e.g.,\n``(\"parthenon\",\"prim_density\")``, ``(\"parthenon\",\"prim_velocity_1\")``,\n``(\"parthenon\",\"prim_magnetic_field_1\")``, will be in code units.\n\nThe default normalization for various magnetic-related quantities such as\nmagnetic pressure, Alfven speed, etc., as well as the conversion between\nmagnetic code units and other units, is Gaussian/CGS, meaning that factors\nof :math:`4\\pi` or :math:`\\sqrt{4\\pi}` will appear in these quantities, e.g.\n:math:`p_B = B^2/8\\pi`. To use the Lorentz-Heaviside normalization instead,\nin which the factors of :math:`4\\pi` are dropped (:math:`p_B = B^2/2), for\nexample), set ``magnetic_normalization=\"lorentz_heaviside\"`` in the call to\n``yt.load``:\n\n.. code-block:: python\n\n    ds = yt.load(\n        \"parthenon.restart.final.rhdf\",\n        units_override=units_override,\n        magnetic_normalization=\"lorentz_heaviside\",\n    )\n\nAlternative values (i.e., overriding the default ones stored in the simulation\noutput) for the following simulation parameters may be specified\nusing a ``parameters`` dict, accepting the following keys:\n\n* ``gamma``: ratio of specific heats, Type: Float. If not specified,\n  :math:`\\gamma = 5/3` is assumed.\n* ``mu``: mean molecular weight, Type: Float. If not specified, :math:`\\mu = 0.6`\n  (for a fully ionized primordial plasma) is assumed.\n\nOther Parthenon based codes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAs mentioned above, a default conversion from code fields to yt fields (e.g.,\nfrom a density field to ``(\"gas\",\"density\")``) is currently not available --\nthough more specialized frontends may be added in the future.\n\nAll raw data of a Parthenon-based simulation output is available through\nthe ``(\"parthenon\",\"NAME\")`` fields where ``NAME`` varies between codes\nand the respective code documentation should be consulted.\n\nOne option to manually convert those raw fields to the standard yt fields\nis by adding derived fields, e.g., for the field named \"``mass.density``\"\nthat is stored in cgs units on disk:\n\n.. code-block:: python\n\n    from yt import derived_field\n\n\n    @derived_field(name=\"density\", units=\"g*cm**-3\", sampling_type=\"cell\")\n    def _density(data):\n        return data[(\"parthenon\", \"mass.density\")] * yt.units.g / yt.units.cm**3\n\nMoreover, an ideal equation of state is assumed with the following parameters,\nwhich may be specified using a ``parameters`` dict, accepting the following keys:\n\n* ``gamma``: ratio of specific heats, Type: Float. If not specified,\n  :math:`\\gamma = 5/3` is assumed.\n* ``mu``: mean molecular weight, Type: Float. If not specified, :math:`\\mu = 0.6`\n  (for a fully ionized primordial plasma) is assumed.\n\n\n.. _loading-orion-data:\n\nAMReX / BoxLib Data\n-------------------\n\nAMReX and BoxLib share a frontend, since\nthe file format is nearly identical.  yt has been tested with AMReX/BoxLib\ndata generated by Orion, Nyx, Maestro, Castro, IAMR, and\nWarpX. Currently it is cared for by a combination of Andrew Myers,\nMatthew Turk, and Mike Zingale.\n\nTo load an AMReX/BoxLib dataset, you can use the ``yt.load`` command on\nthe plotfile directory name.  In general, you must also have the\n``inputs`` file in the base directory, but Maestro, Castro, Nyx, and WarpX will get\nall the necessary parameter information from the ``job_info`` file in\nthe plotfile directory.  For instance, if you were in a\ndirectory with the following files:\n\n.. code-block:: none\n\n   inputs\n   pltgmlcs5600/\n   pltgmlcs5600/Header\n   pltgmlcs5600/Level_0\n   pltgmlcs5600/Level_0/Cell_H\n   pltgmlcs5600/Level_1\n   pltgmlcs5600/Level_1/Cell_H\n   pltgmlcs5600/Level_2\n   pltgmlcs5600/Level_2/Cell_H\n   pltgmlcs5600/Level_3\n   pltgmlcs5600/Level_3/Cell_H\n   pltgmlcs5600/Level_4\n   pltgmlcs5600/Level_4/Cell_H\n\nYou would feed it the filename ``pltgmlcs5600``:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"pltgmlcs5600\")\n\nFor Maestro, Castro, Nyx, and WarpX, you would not need the ``inputs`` file, and you\nwould have a ``job_info`` file in the plotfile directory.\n\n.. rubric:: Caveats\n\n* yt does not read the Maestro base state (although you can have Maestro\n  map it to a full Cartesian state variable before writing the plotfile\n  to get around this).  E-mail the dev list if you need this support.\n* yt supports AMReX/BoxLib particle data stored in the standard format used\n  by Nyx and WarpX, and optionally Castro. It currently does not support the ASCII particle\n  data used by Maestro and Castro.\n* For Maestro, yt aliases either \"tfromp\" or \"tfromh to\" ``temperature``\n  depending on the value of the ``use_tfromp`` runtime parameter.\n* For Maestro, some velocity fields like ``velocity_magnitude`` or\n  ``mach_number`` will always use the on-disk value, and not have yt\n  derive it, due to the complex interplay of the base state velocity.\n\nViewing raw fields in WarpX\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nMost AMReX/BoxLib codes output cell-centered data. If the underlying discretization\nis not cell-centered, then fields are typically averaged to cell centers before\nthey are written to plot files for visualization. WarpX, however, has the option\nto output the raw (i.e., not averaged to cell centers) data as well.  If you\nrun your WarpX simulation with ``warpx.plot_raw_fields = 1`` in your inputs\nfile, then you should get an additional ``raw_fields`` subdirectory inside your\nplot file. When you load this dataset, yt will have additional on-disk fields\ndefined, with the \"raw\" field type:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\"Laser/plt00015/\")\n    print(ds.field_list)\n\nThe raw fields in WarpX are nodal in at least one direction. We define a field\nto be \"nodal\" in a given direction if the field data is defined at the \"low\"\nand \"high\" sides of the cell in that direction, rather than at the cell center.\nInstead of returning one field value per cell selected, nodal fields return a\nnumber of values, depending on their centering. This centering is marked by\na ``nodal_flag`` that describes whether the fields is nodal in each dimension.\n``nodal_flag = [0, 0, 0]`` means that the field is cell-centered, while\n``nodal_flag = [0, 0, 1]`` means that the field is nodal in the z direction\nand cell centered in the others, i.e. it is defined on the z faces of each cell.\n``nodal_flag = [1, 1, 0]`` would mean that the field is centered in the z direction,\nbut nodal in the other two, i.e. it lives on the four cell edges that are normal\nto the z direction.\n\n.. code-block:: python\n\n    ds.index\n    ad = ds.all_data()\n    print(ds.field_info[\"raw\", \"Ex\"].nodal_flag)\n    print(ad[\"raw\", \"Ex\"].shape)\n    print(ds.field_info[\"raw\", \"Bx\"].nodal_flag)\n    print(ad[\"raw\", \"Bx\"].shape)\n    print(ds.field_info[\"raw\", \"Bx\"].nodal_flag)\n    print(ad[\"raw\", \"Bx\"].shape)\n\nHere, the field ``('raw', 'Ex')`` is nodal in two directions, so four values per cell\nare returned, corresponding to the four edges in each cell on which the variable\nis defined. ``('raw', 'Bx')`` is nodal in one direction, so two values are returned\nper cell. The standard, averaged-to-cell-centers fields are still available.\n\nCurrently, slices and data selection are implemented for nodal fields. Projections,\nvolume rendering, and many of the analysis modules will not work.\n\n.. _loading-pluto-data:\n\nPluto Data (AMR)\n----------------\n\nSupport for Pluto AMR data is provided through the Chombo frontend, which\nis currently maintained by Andrew Myers. Pluto output files that don't use\nthe Chombo HDF5 format are currently not supported. To load a Pluto dataset,\nyou can use the ``yt.load`` command on the ``*.hdf5`` files. For example, the\nKelvinHelmholtz sample dataset is a directory that contains the following\nfiles:\n\n.. code-block:: none\n\n   data.0004.hdf5\n   pluto.ini\n\nTo load it, you can navigate into that directory and do:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"data.0004.hdf5\")\n\nThe ``pluto.ini`` file must also be present alongside the HDF5 file.\nBy default, all of the Pluto fields will be in code units.\n\n\n.. _loading-idefix-data:\n\nIdefix, Pluto VTK and Pluto XDMF Data\n-------------------------------------\n\nSupport for Idefix ``.dmp``, ``.vtk`` data is provided through the ``yt_idefix``\nextension.\nIt also supports monogrid ``.vtk`` and ``.h5`` data from Pluto.\nSee `the PyPI page <https://pypi.org/project/yt-idefix/>`_ for details.\n\n\n.. _loading-enzo-data:\n\nEnzo Data\n---------\n\nEnzo data is fully supported and cared for by Matthew Turk.  To load an Enzo\ndataset, you can use the ``yt.load`` command and provide it the dataset name.\nThis would be the name of the output file, and it\ncontains no extension.  For instance, if you have the following files:\n\n.. code-block:: none\n\n   DD0010/\n   DD0010/data0010\n   DD0010/data0010.index\n   DD0010/data0010.cpu0000\n   DD0010/data0010.cpu0001\n   DD0010/data0010.cpu0002\n   DD0010/data0010.cpu0003\n\nYou would feed the ``load`` command the filename ``DD0010/data0010`` as\nmentioned.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"DD0010/data0010\")\n\n.. rubric:: Caveats\n\n* There are no major caveats for Enzo usage\n* Units should be correct, if you utilize standard unit-setting routines.  yt\n  will notify you if it cannot determine the units, although this\n  notification will be passive.\n* 2D and 1D data are supported, but the extraneous dimensions are set to be\n  of length 1.0 in \"code length\" which may produce strange results for volume\n  quantities.\n\n\nEnzo MHDCT data\n^^^^^^^^^^^^^^^\n\nThe electric and magnetic fields for Enzo MHDCT simulations are defined on cell\nfaces, unlike other Enzo fields which are defined at cell centers. In yt, we\ncall face-centered fields like this \"nodal\".  We define a field to be nodal in\na given direction if the field data is defined at the \"low\" and \"high\" sides of\nthe cell in that direction, rather than at the cell center.  Instead of\nreturning one field value per cell selected, nodal fields return a number of\nvalues, depending on their centering. This centering is marked by a ``nodal_flag``\nthat describes whether the fields is nodal in each dimension.  ``nodal_flag =\n[0, 0, 0]`` means that the field is cell-centered, while ``nodal_flag = [0, 0,\n1]`` means that the field is nodal in the z direction and cell centered in the\nothers, i.e. it is defined on the z faces of each cell.  ``nodal_flag = [1, 1,\n0]`` would mean that the field is centered in the z direction, but nodal in the\nother two, i.e. it lives on the four cell edges that are normal to the z\ndirection.\n\n.. code-block:: python\n\n    ds.index\n    ad = ds.all_data()\n    print(ds.field_info[\"enzo\", \"Ex\"].nodal_flag)\n    print(ad[\"enzo\", \"Ex\"].shape)\n    print(ds.field_info[\"enzo\", \"BxF\"].nodal_flag)\n    print(ad[\"enzo\", \"Bx\"].shape)\n    print(ds.field_info[\"enzo\", \"Bx\"].nodal_flag)\n    print(ad[\"enzo\", \"Bx\"].shape)\n\nHere, the field ``('enzo', 'Ex')`` is nodal in two directions, so four values\nper cell are returned, corresponding to the four edges in each cell on which the\nvariable is defined. ``('enzo', 'BxF')`` is nodal in one direction, so two\nvalues are returned per cell. The standard, non-nodal field ``('enzo', 'Bx')``\nis also available.\n\nCurrently, slices and data selection are implemented for nodal\nfields. Projections, volume rendering, and many of the analysis modules will not\nwork.\n\n.. _loading-enzoe-data:\n\nEnzo-E Data\n-----------\n\nEnzo-E outputs have three types of files.\n\n.. code-block:: none\n\n   hello-0200/\n   hello-0200/hello-0200.block_list\n   hello-0200/hello-0200.file_list\n   hello-0200/hello-0200.hello-c0020-p0000.h5\n\nTo load Enzo-E data into yt, provide the block list file:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"hello-0200/hello-0200.block_list\")\n\nMesh and particle fields are fully supported for 1, 2, and 3D datasets.  Enzo-E\nsupports arbitrary particle types defined by the user.  The available particle\ntypes will be known as soon as the dataset index is created.\n\n.. code-block:: python\n\n   ds = yt.load(\"ENZOP_DD0140/ENZOP_DD0140.block_list\")\n   ds.index\n   print(ds.particle_types)\n   print(ds.particle_type_counts)\n   print(ds.r[\"dark\", \"particle_position\"])\n\n.. _loading-exodusii-data:\n\nExodus II Data\n--------------\n\n.. note::\n\n   To load Exodus II data, you need to have the `netcdf4 <http://unidata.github.io/\n   netcdf4-python/>`_ python interface installed.\n\nExodus II is a file format for Finite Element datasets that is used by the MOOSE\nframework for file IO. Support for this format (and for unstructured mesh data in\ngeneral) is a new feature as of yt 3.3, so while we aim to fully support it, we\nalso expect there to be some buggy features at present. Currently, yt can visualize\nquads, hexes, triangles, and tetrahedral element types at first order. Additionally,\nthere is experimental support for the high-order visualization of 20-node hex elements.\nDevelopment of more high-order visualization capability is a work in progress.\n\nTo load an Exodus II dataset, you can use the ``yt.load`` command on the Exodus II\nfile:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\", step=0)\n\nBecause Exodus II datasets can have multiple steps (which can correspond to time steps,\nPicard iterations, non-linear solve iterations, etc...), you can also specify a step\nargument when you load an Exodus II data that defines the index at which to look when\nyou read data from the file. Omitting this argument is the same as passing in 0, and\nsetting ``step=-1`` selects the last time output in the file.\n\nYou can access the connectivity information directly by doing:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\", step=-1)\n   print(ds.index.meshes[0].connectivity_coords)\n   print(ds.index.meshes[0].connectivity_indices)\n   print(ds.index.meshes[1].connectivity_coords)\n   print(ds.index.meshes[1].connectivity_indices)\n\nThis particular dataset has two meshes in it, both of which are made of 8-node hexes.\nyt uses a field name convention to access these different meshes in plots and data\nobjects. To see all the fields found in a particular dataset, you can do:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n   print(ds.field_list)\n\nThis will give you a list of field names like ``('connect1', 'diffused')`` and\n``('connect2', 'convected')``. Here, fields labelled with ``'connect1'`` correspond to the\nfirst mesh, and those with ``'connect2'`` to the second, and so on. To grab the value\nof the ``'convected'`` variable at all the nodes in the first mesh, for example, you\nwould do:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n   ad = ds.all_data()  # geometric selection, this just grabs everything\n   print(ad[\"connect1\", \"convected\"])\n\nIn this dataset, ``('connect1', 'convected')`` is nodal field, meaning that the field values\nare defined at the vertices of the elements. If we examine the shape of the returned array:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n   ad = ds.all_data()\n   print(ad[\"connect1\", \"convected\"].shape)\n\nwe see that this mesh has 12480 8-node hexahedral elements, and that we get 8 field values\nfor each element. To get the vertex positions at which these field values are defined, we\ncan do, for instance:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n   ad = ds.all_data()\n   print(ad[\"connect1\", \"vertex_x\"])\n\nIf we instead look at an element-centered field, like ``('connect1', 'conv_indicator')``,\nwe get:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n   ad = ds.all_data()\n   print(ad[\"connect1\", \"conv_indicator\"].shape)\n\nwe instead get only one field value per element.\n\nFor information about visualizing unstructured mesh data, including Exodus II datasets,\nplease see :ref:`unstructured-mesh-slices` and :ref:`unstructured_mesh_rendering`.\n\nDisplacement Fields\n^^^^^^^^^^^^^^^^^^^\n\nFinite element codes often solve for the displacement of each vertex from its\noriginal position as a node variable, rather than updating the actual vertex\npositions with time. For analysis and visualization, it is often useful to turn\nthese displacements on or off, and to be able to scale them arbitrarily to\nemphasize certain features of the solution. To allow this, if ``yt`` detects\ndisplacement fields in an Exodus II dataset (using the convention that they will\nbe named ``disp_x``, ``disp_y``, etc...), it will optionally add these to\nthe mesh vertex positions for the purposes of visualization. Displacement fields\ncan be controlled when a dataset is loaded by passing in an optional dictionary\nto the ``yt.load`` command. This feature is turned off by default, meaning that\na dataset loaded as\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/mps_out.e\")\n\nwill not include the displacements in the vertex positions. The displacements can\nbe turned on separately for each mesh in the file by passing in a tuple of\n(scale, offset) pairs for the meshes you want to enable displacements for.\nFor example, the following code snippet turns displacements on for the second\nmesh, but not the first:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\n        \"MOOSE_sample_data/mps_out.e\",\n        step=10,\n        displacements={\"connect2\": (1.0, [0.0, 0.0, 0.0])},\n    )\n\nThe displacements can also be scaled by an arbitrary factor before they are\nadded in to the vertex positions. The following code turns on displacements\nfor both ``connect1`` and ``connect2``, scaling the former by a factor of 5.0\nand the later by a factor of 10.0:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\n        \"MOOSE_sample_data/mps_out.e\",\n        step=10,\n        displacements={\n            \"connect1\": (5.0, [0.0, 0.0, 0.0]),\n            \"connect2\": (10.0, [0.0, 0.0, 0.0]),\n        },\n    )\n\nFinally, we can also apply an arbitrary offset to the mesh vertices after\nthe scale factor is applied. For example, the following code scales all\ndisplacements in the second mesh by a factor of 5.0, and then shifts\neach vertex in the mesh by 1.0 unit in the z-direction:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\n        \"MOOSE_sample_data/mps_out.e\",\n        step=10,\n        displacements={\"connect2\": (5.0, [0.0, 0.0, 1.0])},\n    )\n\n.. _loading-fits-data:\n\nFITS Data\n---------\n\nFITS data is *mostly* supported and cared for by John ZuHone. In order to\nread FITS data, `AstroPy <https://www.astropy.org>`_ must be installed. FITS\ndata cubes can be loaded in the same way by yt as other datasets. yt\ncan read FITS image files that have the following (case-insensitive) suffixes:\n\n* fits\n* fts\n* fits.gz\n* fts.gz\n\nyt can currently read two kinds of FITS files: FITS image files and FITS\nbinary table files containing positions, times, and energies of X-ray\nevents. These are described in more detail below.\n\nTypes of FITS Datasets Supported by yt\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nyt FITS Data Standard\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nyt has facilities for creating 2 and 3-dimensional FITS images from derived,\nfixed-resolution data products from other datasets. These include images\nproduced from slices, projections, and 3D covering grids. The resulting\nFITS images are fully-describing in that unit, parameter, and coordinate\ninformation is passed from the original dataset. These can be created via the\n:class:`~yt.visualization.fits_image.FITSImageData` class and its subclasses.\nFor information about how to use these special classes, see\n:doc:`../visualizing/FITSImageData`.\n\nOnce you have produced a FITS file in this fashion, you can load it using\nyt and it will be detected as a ``YTFITSDataset`` object, and it can be analyzed\nin the same way as any other dataset in yt.\n\nAstronomical Image Data\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThese files are one of three types:\n\n* Generic two-dimensional FITS images in sky coordinates\n* Three or four-dimensional \"spectral cubes\"\n* *Chandra* event files\n\nThese FITS images typically are in celestial or galactic coordinates, and\nfor 3D spectral cubes the third axis is typically in velocity, wavelength,\nor frequency units. For these datasets, since yt does not yet recognize\nnon-spatial axes, the coordinates are in units of the image pixels. The\ncoordinates of these pixels in the WCS coordinate systems will be available\nin separate fields.\n\nOften, the aspect ratio of 3D spectral cubes can be far from unity. Because yt\nsets the pixel scale as the ``code_length``, certain visualizations (such as\nvolume renderings) may look extended or distended in ways that are\nundesirable. To adjust the width in ``code_length`` of the spectral axis, set\n``spectral_factor`` equal to a constant which gives the desired scaling, or set\nit to ``\"auto\"`` to make the width the same as the largest axis in the sky\nplane:\n\n.. code-block:: python\n\n   ds = yt.load(\"m33_hi.fits.gz\", spectral_factor=0.1)\n\nFor 4D spectral cubes, the fourth axis is assumed to be composed of different\nfields altogether (e.g., Stokes parameters for radio data).\n\n*Chandra* X-ray event data, which is in tabular form, will be loaded as\nparticle fields in yt, but a grid will be constructed from the WCS\ninformation in the FITS header. There is a helper function,\n``setup_counts_fields``, which may be used to make deposited image fields\nfrom the event data for different energy bands (for an example see\n:doc:`../cookbook/fits_xray_images`).\n\nGeneric FITS Images\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf the FITS file contains images but does not have adequate header information\nto fall into one of the above categories, yt will still load the data, but\nthe resulting field and/or coordinate information will necessarily be\nincomplete. Field names may not be descriptive, and units may be incorrect. To\nget the full use out of yt for FITS files, make sure that the file is sufficiently\nself-descripting to fall into one of the above categories.\n\nMaking the Most of yt for FITS Data\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nyt will load data without WCS information and/or some missing header keywords,\nbut the resulting field and/or coordinate information will necessarily be\nincomplete. For example, field names may not be descriptive, and units will not\nbe correct. To get the full use out of yt for FITS files, make sure that for\neach image HDU the following standard header keywords have sensible values:\n\n* ``CDELTx``: The pixel width in along axis ``x``\n* ``CRVALx``: The coordinate value at the reference position along axis ``x``\n* ``CRPIXx``: The reference pixel along axis ``x``\n* ``CTYPEx``: The projection type of axis ``x``\n* ``CUNITx``: The units of the coordinate along axis ``x``\n* ``BTYPE``: The type of the image, this will be used as the field name\n* ``BUNIT``: The units of the image\n\nFITS header keywords can easily be updated using AstroPy. For example,\nto set the ``BTYPE`` and ``BUNIT`` keywords:\n\n.. code-block:: python\n\n   from astropy.io import fits\n\n   f = fits.open(\"xray_flux_image.fits\", mode=\"update\")\n   f[0].header[\"BUNIT\"] = \"cts/s/pixel\"\n   f[0].header[\"BTYPE\"] = \"flux\"\n   f.flush()\n   f.close()\n\n\nFITS Data Decomposition\n^^^^^^^^^^^^^^^^^^^^^^^\n\nThough a FITS image is composed of a single array in the FITS file,\nupon being loaded into yt it is automatically decomposed into grids:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"m33_hi.fits\")\n   ds.print_stats()\n\n.. parsed-literal::\n\n   level  # grids         # cells     # cells^3\n   ----------------------------------------------\n     0       512          981940800       994\n   ----------------------------------------------\n             512          981940800\n\nFor 3D spectral-cube data, the decomposition into grids will be done along the\nspectral axis since this will speed up many common operations for this\nparticular type of dataset.\n\nyt will generate its own domain decomposition, but the number of grids can be\nset manually by passing the ``nprocs`` parameter to the ``load`` call:\n\n.. code-block:: python\n\n   ds = yt.load(\"m33_hi.fits\", nprocs=64)\n\nFields in FITS Datasets\n^^^^^^^^^^^^^^^^^^^^^^^\n\nMultiple fields can be included in a FITS dataset in several different ways.\nThe first way, and the simplest, is if more than one image HDU is\ncontained within the same file. The field names will be determined by the\nvalue of ``BTYPE`` in the header, and the field units will be determined by\nthe value of ``BUNIT``. The second way is if a dataset has a fourth axis,\nwith each slice along this axis corresponding to a different field. In this\ncase, the field names will be determined by the value of the ``CTYPE4`` keyword\nand the index of the slice. So, for example, if ``BTYPE`` = ``\"intensity\"`` and\n``CTYPE4`` = ``\"stokes\"``, then the fields will be named\n``\"intensity_stokes_1\"``, ``\"intensity_stokes_2\"``, and so on.\n\nThe third way is if auxiliary files are included along with the main file, like so:\n\n.. code-block:: python\n\n   ds = yt.load(\"flux.fits\", auxiliary_files=[\"temp.fits\", \"metal.fits\"])\n\nThe image blocks in each of these files will be loaded as a separate field,\nprovided they have the same dimensions as the image blocks in the main file.\n\nAdditionally, fields corresponding to the WCS coordinates will be generated\nbased on the corresponding ``CTYPEx`` keywords. When queried, these fields\nwill be generated from the pixel coordinates in the file using the WCS\ntransformations provided by AstroPy.\n\n.. note::\n\n  Each FITS image from a single dataset, whether from one file or from one of\n  multiple files, must have the same dimensions and WCS information as the\n  first image in the primary file. If this is not the case,\n  yt will raise a warning and will not load this field.\n\n.. _additional_fits_options:\n\nAdditional Options\n^^^^^^^^^^^^^^^^^^\n\nThe following are additional options that may be passed to the ``load`` command\nwhen analyzing FITS data:\n\n``nan_mask``\n\"\"\"\"\"\"\"\"\"\"\"\"\n\nFITS image data may include ``NaNs``. If you wish to mask this data out,\nyou may supply a ``nan_mask`` parameter, which may either be a\nsingle floating-point number (applies to all fields) or a Python dictionary\ncontaining different mask values for different fields:\n\n.. code-block:: python\n\n   # passing a single float for all images\n   ds = yt.load(\"m33_hi.fits\", nan_mask=0.0)\n\n   # passing a dict\n   ds = yt.load(\"m33_hi.fits\", nan_mask={\"intensity\": -1.0, \"temperature\": 0.0})\n\n``suppress_astropy_warnings``\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nGenerally, AstroPy may generate a lot of warnings about individual FITS\nfiles, many of which you may want to ignore. If you want to see these\nwarnings, set ``suppress_astropy_warnings = False``.\n\nMiscellaneous Tools for Use with FITS Data\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA number of tools have been prepared for use with FITS data that enhance yt's\nvisualization and analysis capabilities for this particular type of data. These\nare included in the ``yt.frontends.fits.misc`` module, and can be imported like\nso:\n\n.. code-block:: python\n\n  from yt.frontends.fits.misc import PlotWindowWCS, ds9_region, setup_counts_fields\n\n``setup_counts_fields``\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThis function can be used to create image fields from X-ray counts data in\ndifferent energy bands:\n\n.. code-block:: python\n\n  ebounds = [(0.1, 2.0), (2.0, 5.0)]  # Energies are in keV\n  setup_counts_fields(ds, ebounds)\n\nwhich would make two fields, ``\"counts_0.1-2.0\"`` and ``\"counts_2.0-5.0\"``,\nand add them to the field registry for the dataset ``ds``.\n\n``ds9_region``\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThis function takes a `ds9 <http://ds9.si.edu/site/Home.html>`_ region and\ncreates a \"cut region\" data container from it, that can be used to select\nthe cells in the FITS dataset that fall within the region. To use this\nfunctionality, the `regions <https://github.com/astropy/regions/>`_\npackage must be installed.\n\n.. code-block:: python\n\n  ds = yt.load(\"m33_hi.fits\")\n  circle_region = ds9_region(ds, \"circle.reg\")\n  print(circle_region.quantities.extrema(\"flux\"))\n\n\n``PlotWindowWCS``\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThis class takes a on-axis ``SlicePlot`` or ``ProjectionPlot`` of FITS\ndata and adds celestial coordinates to the plot axes. To use it, a\nversion of AstroPy >= 1.3 must be installed.\n\n.. code-block:: python\n\n  wcs_slc = PlotWindowWCS(slc)\n  wcs_slc.show()  # for Jupyter notebooks\n  wcs_slc.save()\n\n``WCSAxes`` is still in an experimental state, but as its functionality\nimproves it will be utilized more here.\n\n``create_spectral_slabs``\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. note::\n\n  The following functionality requires the\n  `spectral-cube <https://spectral-cube.readthedocs.io/en/latest/>`_ library to be\n  installed.\n\nIf you have a spectral intensity dataset of some sort, and would like to\nextract emission in particular slabs along the spectral axis of a certain\nwidth, ``create_spectral_slabs`` can be used to generate a dataset with\nthese slabs as different fields. In this example, we use it to extract\nindividual lines from an intensity cube:\n\n.. code-block:: python\n\n  slab_centers = {\n      \"13CN\": (218.03117, \"GHz\"),\n      \"CH3CH2CHO\": (218.284256, \"GHz\"),\n      \"CH3NH2\": (218.40956, \"GHz\"),\n  }\n  slab_width = (0.05, \"GHz\")\n  ds = create_spectral_slabs(\n      \"intensity_cube.fits\", slab_centers, slab_width, nan_mask=0.0\n  )\n\nAll keyword arguments to ``create_spectral_slabs`` are passed on to ``load`` when\ncreating the dataset (see :ref:`additional_fits_options` above). In the\nreturned dataset, the different slabs will be different fields, with the field\nnames taken from the keys in ``slab_centers``. The WCS coordinates on the\nspectral axis are reset so that the center of the domain along this axis is\nzero, and the left and right edges of the domain along this axis are\n:math:`\\pm` ``0.5*slab_width``.\n\nExamples of Using FITS Data\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe following Jupyter notebooks show examples of working with FITS data in yt,\nwhich we recommend you look at in the following order:\n\n* :doc:`../cookbook/fits_radio_cubes`\n* :doc:`../cookbook/fits_xray_images`\n* :doc:`../visualizing/FITSImageData`\n\n.. _loading-flash-data:\n\nFLASH Data\n----------\n\nFLASH HDF5 data is *mostly* supported and cared for by John ZuHone.  To load a\nFLASH dataset, you can use the ``yt.load`` command and provide it the file name of\na plot file, checkpoint file, or particle file. Particle files require special handling\ndepending on the situation, the main issue being that they typically lack grid information.\nThe first case is when you have a plotfile and a particle file that you would like to\nload together. In the simplest case, this occurs automatically. For instance, if you\nwere in a directory with the following files:\n\n.. code-block:: none\n\n   radio_halo_1kpc_hdf5_plt_cnt_0100 # plotfile\n   radio_halo_1kpc_hdf5_part_0100 # particle file\n\nwhere the plotfile and the particle file were created at the same time (therefore having\nparticle data consistent with the grid structure of the former). Notice also that the\nprefix ``\"radio_halo_1kpc_\"`` and the file number ``100`` are the same. In this special case,\nthe particle file will be loaded automatically when ``yt.load`` is called on the plotfile.\nThis also works when loading a number of files in a time series.\n\nIf the two files do not have the same prefix and number, but they nevertheless have the same\ngrid structure and are at the same simulation time, the particle data may be loaded with the\n``particle_filename`` optional argument to ``yt.load``:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\n        \"radio_halo_1kpc_hdf5_plt_cnt_0100\",\n        particle_filename=\"radio_halo_1kpc_hdf5_part_0100\",\n    )\n\nHowever, if you don't have a corresponding plotfile for a particle file, but would still\nlike to load the particle data, you can still call ``yt.load`` on the file. However, the\ngrid information will not be available, and the particle data will be loaded in a fashion\nsimilar to other particle-based datasets in yt.\n\nMean Molecular Weight and Number Density Fields\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe way the mean molecular weight and number density fields are defined depends on\nwhat type of simulation you are running. If you are running a simulation without\nspecies and a :math:`\\gamma`-law equation of state, then the mean molecular weight\nis defined using the ``eos_singleSpeciesA`` parameter in the FLASH dataset. If you\nhave multiple species and your dataset contains the FLASH field ``\"abar\"``, then\nthis is used as the mean molecular weight. In either case, the number density field\nis calculated using this weight.\n\nIf you are running a FLASH simulation where the fields ``\"sumy\"`` and ``\"ye\"`` are\npresent, Then the mean molecular weight is the inverse of ``\"sumy\"``, and the fields\n``\"El_number_density\"``, ``\"ion_number_density\"``, and ``\"number_density\"`` are\ndefined using the following mathematical definitions:\n\n* ``\"El_number_density\"`` :math:`n_e = N_AY_e\\rho`\n* ``\"ion_number_density\"`` :math:`n_i = N_A\\rho/\\bar{A}`\n* ``\"number_density\"`` :math:`n = n_e + n_i`\n\nwhere :math:`n_e` and :math:`n_i` are the electron and ion number densities,\n:math:`\\rho` is the mass density, :math:`Y_e` is the electron number per baryon,\n:math:`\\bar{A}` is the mean molecular weight, and :math:`N_A` is Avogadro's number.\n\n.. rubric:: Caveats\n\n* Please be careful that the units are correctly utilized; yt assumes cgs by default, but conversion to\n  other unit systems is also possible.\n\n.. _loading-gadget-data:\n\nGadget Data\n-----------\n\n.. note::\n\n   For more information about how yt indexes and reads particle data, set the\n   section :ref:`demeshening`.\n\nyt has support for reading Gadget data in both raw binary and HDF5 formats.  It\nis able to access the particles as it would any other particle dataset, and it\ncan apply smoothing kernels to the data to produce both quantitative analysis\nand visualization. See :ref:`loading-sph-data` for more details and\n:doc:`../cookbook/yt_gadget_analysis` for a detailed example\nof loading, analyzing, and visualizing a Gadget dataset.  An example which\nmakes use of a Gadget snapshot from the OWLS project can be found in\n:doc:`../cookbook/yt_gadget_owls_analysis`.\n\n.. note::\n\n   If you are loading a multi-file dataset with Gadget, you can either supply the *zeroth*\n   file to the ``load`` command or the directory containing all of the files.\n   For instance, to load the *zeroth* file: ``yt.load(\"snapshot_061.0.hdf5\")`` . To\n   give just the directory, if you have all of your ``snapshot_000.*`` files in a directory\n   called ``snapshot_000``, do: ``yt.load(\"/path/to/snapshot_000\")``.\n\nGadget data in HDF5 format can be loaded with the ``load`` command:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"snapshot_061.hdf5\")\n\nGadget data in raw binary format can also be loaded with the ``load`` command.\nThis is supported for snapshots created with the ``SnapFormat`` parameter\nset to 1 or 2.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"snapshot_061\")\n\n.. _particle-bbox:\n\nUnits and Bounding Boxes\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nThere are two additional pieces of information that may be needed.  If your\nsimulation is cosmological, yt can often guess the bounding box and the units of\nthe simulation.  However, for isolated simulations and for cosmological\nsimulations with non-standard units, these must be supplied by the user.  For\nexample, if a length unit of 1.0 corresponds to a kiloparsec, you can supply\nthis in the constructor.  yt can accept units such as ``Mpc``, ``kpc``, ``cm``,\n``Mpccm/h`` and so on.  In particular, note that ``Mpc/h`` and ``Mpccm/h``\n(``cm`` for comoving here) are usable unit definitions.\n\nyt will attempt to use units for ``mass``, ``length``, ``time``, and\n``magnetic`` as supplied in the argument ``unit_base``.  The ``bounding_box``\nargument is a list of two-item tuples or lists that describe the left and right\nextents of the particles. In this example we load a dataset with a custom bounding\nbox and units.\n\n.. code-block:: python\n\n   bbox = [[-600.0, 600.0], [-600.0, 600.0], [-600.0, 600.0]]\n   unit_base = {\n       \"length\": (1.0, \"kpc\"),\n       \"velocity\": (1.0, \"km/s\"),\n       \"mass\": (1.0, \"Msun\"),\n   }\n\n   ds = yt.load(\"snap_004\", unit_base=unit_base, bounding_box=bbox)\n\n.. warning::\n\n    If a ``bounding_box`` argument is supplied and the original dataset\n    has periodic boundaries, it will no longer have periodic boundaries\n    after the bounding box is applied.\n\nIn addition, you can use ``UnitLength_in_cm``, ``UnitVelocity_in_cm_per_s``,\n``UnitMass_in_g``, and ``UnitMagneticField_in_gauss`` as keys for the\n``unit_base`` dictionary. These name come from the names used in the Gadget\nruntime parameter file. This example will initialize a dataset with the same\nunits as the example above:\n\n.. code-block:: python\n\n  unit_base = {\n      \"UnitLength_in_cm\": 3.09e21,\n      \"UnitVelocity_in_cm_per_s\": 1e5,\n      \"UnitMass_in_g\": 1.989e33,\n  }\n\n  ds = yt.load(\"snap_004\", unit_base=unit_base, bounding_box=bbox)\n\n.. _gadget-field-spec:\n\nField Specifications\n^^^^^^^^^^^^^^^^^^^^\n\nBinary Gadget outputs often have additional fields or particle types that are\nnon-standard from the default Gadget distribution format.  These can be\nspecified in the call to ``GadgetDataset`` by either supplying one of the\nsets of field specifications as a string or by supplying a field specification\nitself.  As an example, yt has built-in definitions for ``default`` (the\ndefault), ``agora_unlv``, ``group0000``, and ``magneticum_box2_hr``. They can\nbe used like this:\n\n.. code-block:: python\n\n   ds = yt.load(\"snap_100\", field_spec=\"group0000\")\n\nField specifications must be tuples, and must be of this format:\n\n.. code-block:: python\n\n   default = (\n       \"Coordinates\",\n       \"Velocities\",\n       \"ParticleIDs\",\n       \"Mass\",\n       (\"InternalEnergy\", \"Gas\"),\n       (\"Density\", \"Gas\"),\n       (\"SmoothingLength\", \"Gas\"),\n   )\n\nThis is the default specification used by the Gadget frontend.  It means that\nthe fields are, in order, Coordinates, Velocities, ParticleIDs, Mass, and the\nfields InternalEnergy, Density and SmoothingLength *only* for Gas particles.\nSo for example, if you have defined a Metallicity field for the particle type\nHalo, which comes right after ParticleIDs in the file, you could define it like\nthis:\n\n.. code-block:: python\n\n   import yt\n\n   my_field_def = (\n       \"Coordinates\",\n       \"Velocities\",\n       \"ParticleIDs\",\n       (\"Metallicity\", \"Halo\"),\n       \"Mass\",\n       (\"InternalEnergy\", \"Gas\"),\n       (\"Density\", \"Gas\"),\n       (\"SmoothingLength\", \"Gas\"),\n   )\n\n   ds = yt.load(\"snap_100\", field_spec=my_field_def)\n\nTo save time, you can utilize the plugins file for yt and use it to add items\nto the dictionary where these definitions are stored.  You could do this like\nso:\n\n.. code-block:: python\n\n   import yt\n   from yt.frontends.gadget.definitions import gadget_field_specs\n\n   gadget_field_specs[\"my_field_def\"] = my_field_def\n\n   ds = yt.load(\"snap_100\", field_spec=\"my_field_def\")\n\nPlease also feel free to issue a pull request with any new field\nspecifications, as we're happy to include them in the main distribution!\n\nMagneticum halos downloaded using the SIMCUT method from the\n`Cosmological Web Portal <https://c2papcosmosim.uc.lrz.de/>`_ can be loaded\nusing the ``\"magneticum_box2_hr\"`` value for the ``field_spec`` argumemt.\nHowever, this is strictly only true for halos downloaded after May 14, 2021,\nsince before then the halos had the following signature (with the ``\"StellarAge\"``\nfield for the ``\"Bndry\"`` particles missing):\n\n.. code-block:: python\n\n    magneticum_box2_hr = (\n        \"Coordinates\",\n        \"Velocities\",\n        \"ParticleIDs\",\n        \"Mass\",\n        (\"InternalEnergy\", \"Gas\"),\n        (\"Density\", \"Gas\"),\n        (\"SmoothingLength\", \"Gas\"),\n        (\"ColdFraction\", \"Gas\"),\n        (\"Temperature\", \"Gas\"),\n        (\"StellarAge\", \"Stars\"),\n        \"Potential\",\n        (\"InitialMass\", \"Stars\"),\n        (\"ElevenMetalMasses\", (\"Gas\", \"Stars\")),\n        (\"StarFormationRate\", \"Gas\"),\n        (\"TrueMass\", \"Bndry\"),\n        (\"AccretionRate\", \"Bndry\"),\n    )\n\nand before November 20, 2020, the field specification had the ``\"ParticleIDs\"`` and ``\"Mass\"``\nfields swapped:\n\n.. code-block:: python\n\n    magneticum_box2_hr = (\n        \"Coordinates\",\n        \"Velocities\",\n        \"Mass\",\n        \"ParticleIDs\",\n        (\"InternalEnergy\", \"Gas\"),\n        (\"Density\", \"Gas\"),\n        (\"SmoothingLength\", \"Gas\"),\n        (\"ColdFraction\", \"Gas\"),\n        (\"Temperature\", \"Gas\"),\n        (\"StellarAge\", \"Stars\"),\n        \"Potential\",\n        (\"InitialMass\", \"Stars\"),\n        (\"ElevenMetalMasses\", (\"Gas\", \"Stars\")),\n        (\"StarFormationRate\", \"Gas\"),\n        (\"TrueMass\", \"Bndry\"),\n        (\"AccretionRate\", \"Bndry\"),\n    )\n\nIn general, to determine what fields are in your Gadget binary file, it may\nbe useful to inspect them with the `g3read <https://github.com/aragagnin/g3read>`_\ncode first.\n\n.. _gadget-species-fields:\n\nGadget Species Fields\n^^^^^^^^^^^^^^^^^^^^^\n\nGas and star particles in Gadget binary and HDF5 files can have fields\ncorresponding to different species fractions or masses. The following field\ndefinitions are supported, in the sense that they are automatically detected\nand will be used to construct species fractions, densities, and number densities\nafter the manner specified in :ref:`species-fields`. For Gadget binary files, the\nfollowing fields (as specified in the ``field_spec`` argument) are supported:\n\n* ``\"ElevenMetalMasses\"``: 11 mass fields: He, C, Ca, O, N, Ne, Mg, S, Si, Fe, Ej\n* ``\"FourMetalFractions\"``: 4 fraction fields: C, O, Si, Fe\n\nFor Gadget HDF5 files, the fields ``\"MetalMasses\"`` or ``\"Mass Of Metals\"`` are\nsupported, with the number of species determined by the size of the dataset's\nsecond dimension in the file. Four different numbers of species in these fields\nare supported, corresponding to the following species:\n\n* 7, corresponding to C, N, O, Mg, Si, Fe, Ej\n* 8, corresponding to He, C, O, Mg, S, Si, Fe, Ej\n* 11, corresponding to He, C, Ca, O, N, Ne, Mg, S, Si, Fe, Ej\n* 15, corresponding to He, C, Ca, O, N, Ne, Mg, S, Si, Fe, Na, Al, Ar, Ni, Ej\n\nTwo points should be noted about the above: the \"Ej\" species corresponds to the\nremaining mass of elements heavier than hydrogen and not enumerated, and in the\ncase of 8, 11, and 15 species, hydrogen is assumed to be the remaining mass\nfraction.\n\nFinally, for Gadget HDF5 files, element fields which are of the form\n``\"X_fraction\"`` are also suppoted, and correspond to the mass fraction of element\nX.\n\n.. _gadget-long-ids:\n\nLong Particle IDs\n^^^^^^^^^^^^^^^^^\n\nSome Gadget binary files use 64-bit integers for particle IDs. To use these,\nsimply set ``long_ids=True`` when loading the dataset:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\"snap_100\", long_ids=True)\n\nThis is needed, for example, for Magneticum halos downloaded using the SIMCUT\nmethod from the `Cosmological Web Portal <https://c2papcosmosim.uc.lrz.de/>`_\n\n.. _gadget-ptype-spec:\n\nParticle Type Definitions\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn some cases, research groups add new particle types or re-order them.  You\ncan supply alternate particle types by using the keyword ``ptype_spec`` to the\n``GadgetDataset`` call.  The default for Gadget binary data is:\n\n.. code-block:: python\n\n   (\"Gas\", \"Halo\", \"Disk\", \"Bulge\", \"Stars\", \"Bndry\")\n\nYou can specify alternate names, but note that this may cause problems with the\nfield specification if none of the names match old names.\n\n.. _gadget-header-spec:\n\nHeader Specification\n^^^^^^^^^^^^^^^^^^^^\n\nIf you have modified the header in your Gadget binary file, you can specify an\nalternate header specification with the keyword ``header_spec``.  This can\neither be a list of strings corresponding to individual header types known to\nyt, or it can be a combination of strings and header specifications.  The\ndefault header specification (found in ``yt/frontends/sph/definitions.py``) is:\n\n.. code-block:: python\n\n   default = (\n       (\"Npart\", 6, \"i\"),\n       (\"Massarr\", 6, \"d\"),\n       (\"Time\", 1, \"d\"),\n       (\"Redshift\", 1, \"d\"),\n       (\"FlagSfr\", 1, \"i\"),\n       (\"FlagFeedback\", 1, \"i\"),\n       (\"Nall\", 6, \"i\"),\n       (\"FlagCooling\", 1, \"i\"),\n       (\"NumFiles\", 1, \"i\"),\n       (\"BoxSize\", 1, \"d\"),\n       (\"Omega0\", 1, \"d\"),\n       (\"OmegaLambda\", 1, \"d\"),\n       (\"HubbleParam\", 1, \"d\"),\n       (\"FlagAge\", 1, \"i\"),\n       (\"FlagMEtals\", 1, \"i\"),\n       (\"NallHW\", 6, \"i\"),\n       (\"unused\", 16, \"i\"),\n   )\n\nThese items will all be accessible inside the object ``ds.parameters``, which\nis a dictionary.  You can add combinations of new items, specified in the same\nway, or alternately other types of headers.  The other string keys defined are\n``pad32``, ``pad64``, ``pad128``, and ``pad256`` each of which corresponds to\nan empty padding in bytes.  For example, if you have an additional 256 bytes of\npadding at the end, you can specify this with:\n\n.. code-block:: python\n\n   header_spec = \"default+pad256\"\n\nNote that a single string like this means a single header block.  To specify\nmultiple header blocks, use a list of strings instead:\n\n.. code-block:: python\n\n  header_spec = [\"default\", \"pad256\"]\n\nThis can then be supplied to the constructor.  Note that you can also define\nheader items manually, for instance with:\n\n.. code-block:: python\n\n   from yt.frontends.gadget.definitions import gadget_header_specs\n\n   gadget_header_specs[\"custom\"] = ((\"some_value\", 8, \"d\"), (\"another_value\", 1, \"i\"))\n   header_spec = \"default+custom\"\n\nThe letters correspond to data types from the Python struct module.  Please\nfeel free to submit alternate header types to the main yt repository.\n\n.. _specifying-gadget-units:\n\nSpecifying Units\n^^^^^^^^^^^^^^^^\n\nIf you are running a cosmology simulation, yt will be able to guess the units\nwith some reliability.  However, if you are not and you do not specify a\ndataset, yt will not be able to and will use the defaults of length\nbeing 1.0 Mpc/h (comoving), velocity being in cm/s, and mass being in 10^10\nMsun/h.  You can specify alternate units by supplying the ``unit_base`` keyword\nargument of this form:\n\n.. code-block:: python\n\n   unit_base = {\"length\": (1.0, \"cm\"), \"mass\": (1.0, \"g\"), \"time\": (1.0, \"s\")}\n\nyt will utilize length, mass and time to set up all other units.\n\n.. _loading-swift-data:\n\nSWIFT Data\n----------\n\n.. note::\n\n   For more information about how yt indexes and reads particle data, set the\n   section :ref:`demeshening`.\n\nyt has support for reading in SWIFT data from the HDF5 file format. It is able\nto access all particles and fields which are stored on-disk and it is also able\nto generate derived fields, i.e, linear momentum from on-disk fields.\n\nIt is also possible to smooth the data onto a grid or an octree. This\ninterpolation can be done using an SPH kernel using either the scatter or gather\napproach. The SWIFT frontend is supported and cared for by Ashley Kelly.\n\nSWIFT data in HDF5 format can be loaded with the ``load`` command:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"EAGLE_6/eagle_0005.hdf5\")\n\n.. _arepo-data:\n\nArepo Data\n----------\n\n.. note::\n\n   For more information about how yt indexes and reads discrete data, set the\n   section :ref:`demeshening`.\n\nArepo data is currently treated as SPH data. The gas cells have smoothing lengths\nassigned using the following prescription for a given gas cell :math:`i`:\n\n.. math::\n\n    h_{\\rm sml} = \\alpha\\left(\\frac{3}{4\\pi}\\frac{m_i}{\\rho_i}\\right)^{1/3}\n\nwhere :math:`\\alpha` is a constant factor. By default, :math:`\\alpha = 2`. In\npractice, smoothing lengths are only used for creating slices and projections,\nand this value of :math:`\\alpha` works well for this purpose. However, this\nvalue can be changed when loading an Arepo dataset by setting the\n``smoothing_factor`` parameter:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"snapshot_100.hdf5\", smoothing_factor=1.5)\n\nCurrently, only Arepo HDF5 snapshots are supported.\n\nIf the \"GFM\" metal fields are present in your dataset, they will be loaded in\nand aliased to the appropriate species fields in the ``\"GFM_Metals\"`` field\non-disk. For more information, see the\n`Illustris TNG documentation <http://www.tng-project.org/data/docs/specifications/#sec1b>`_.\n\nIf passive scalar fields are present in your dataset, they will be loaded in\nand aliased to fields with the naming convention ``\"PassiveScalars_XX\"`` where\n``XX`` is the number of the passive scalar array, e.g. ``\"00\"``, ``\"01\"``, etc.\n\nHDF5 snapshots will be detected as Arepo data if they have the ``\"GFM_Metals\"``\nfield present, or if they have a ``\"Config\"`` group in the header. If neither of\nthese are the case, and your snapshot *is* Arepo data, you can fix this with the\nfollowing:\n\n.. code-block:: python\n\n    import h5py\n\n    with h5py.File(saved_filename, \"r+\") as f:\n        f.create_group(\"Config\")\n        f[\"/Config\"].attrs[\"VORONOI\"] = 1\n\n.. _loading-gamer-data:\n\nGAMER Data\n----------\n\nGAMER HDF5 data is supported and cared for by Hsi-Yu Schive and John ZuHone.\nDatasets using hydrodynamics, particles, magnetohydrodynamics, wave dark matter,\nand special relativistic hydrodynamics are supported. You can load the data like\nthis:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"InteractingJets/jet_000002\")\n\nFor simulations without units (i.e., ``OPT__UNIT = 0``), you can supply conversions\nfor length, time, and mass to ``load`` using the ``units_override``\nfunctionality:\n\n.. code-block:: python\n\n   import yt\n\n   code_units = {\n       \"length_unit\": (1.0, \"kpc\"),\n       \"time_unit\": (3.08567758096e13, \"s\"),\n       \"mass_unit\": (1.4690033e36, \"g\"),\n   }\n   ds = yt.load(\"InteractingJets/jet_000002\", units_override=code_units)\n\nParticle data are supported and are always stored in the same file as the grid\ndata.\n\nFor special relativistic simulations, both the gamma-law and Taub-Mathews EOSes\nare supported, and the following fields are defined:\n\n* ``(\"gas\", \"density\")``: Comoving rest-mass density :math:`\\rho`\n* ``(\"gas\", \"frame_density\")``: Coordinate-frame density :math:`D = \\gamma\\rho`\n* ``(\"gas\", \"gamma\")``: Ratio of specific heats :math:`\\Gamma`\n* ``(\"gas\", \"four_velocity_[txyz]\")``: Four-velocity fields :math:`U_t, U_x, U_y, U_z`\n* ``(\"gas\", \"lorentz_factor\")``: Lorentz factor :math:`\\gamma = \\sqrt{1+U_iU^i/c^2}`\n  (where :math:`i` runs over the spatial indices)\n* ``(\"gas\", \"specific_reduced_enthalpy\")``: Specific reduced enthalpy :math:`\\tilde{h} = \\epsilon + p/\\rho`\n* ``(\"gas\", \"specific_enthalpy\")``: Specific enthalpy :math:`h = c^2 + \\epsilon + p/\\rho`\n\nThese, and other fields following them (3-velocity, energy densities, etc.) are\ncomputed in the same manner as in the\n`GAMER-SR paper <https://ui.adsabs.harvard.edu/abs/2021MNRAS.504.3298T/abstract>`_\nto avoid catastrophic cancellations.\n\nAll of the special relativistic fields will only be available if the ``Temp`` and\n``Enth`` fields are present in the dataset, which can be ensured if the runtime\noptions ``OPT__OUTPUT_TEMP = 1`` and ``OPT__OUTPUT_ENTHALPY  = 1`` are set in the\n``Input__Parameter`` file when running the simulation. This greatly speeds up\ncalculations of the above derived fields in yt.\n\n.. rubric:: Caveats\n\n* GAMER data in raw binary format (i.e., ``OPT__OUTPUT_TOTAL = \"C-binary\"``) is not\n  supported.\n\n.. _loading-amr-data:\n\nGeneric AMR Data\n----------------\n\nSee :doc:`Loading_Generic_Array_Data` and\n:func:`~yt.frontends.stream.data_structures.load_amr_grids` for more detail.\n\n.. note::\n\n   It is now possible to load data using *only functions*, rather than using the\n   fully-in-memory method presented here.  For more information and examples,\n   see :doc:`Loading_Data_via_Functions`.\n\nIt is possible to create native yt dataset from Python's dictionary\nthat describes set of rectangular patches of data of possibly varying\nresolution.\n\n.. code-block:: python\n\n   import yt\n\n   grid_data = [\n       dict(\n           left_edge=[0.0, 0.0, 0.0],\n           right_edge=[1.0, 1.0, 1.0],\n           level=0,\n           dimensions=[32, 32, 32],\n       ),\n       dict(\n           left_edge=[0.25, 0.25, 0.25],\n           right_edge=[0.75, 0.75, 0.75],\n           level=1,\n           dimensions=[32, 32, 32],\n       ),\n   ]\n\n   for g in grid_data:\n       g[\"density\"] = np.random.random(g[\"dimensions\"]) * 2 ** g[\"level\"]\n\n   ds = yt.load_amr_grids(grid_data, [32, 32, 32], 1.0)\n\n.. note::\n\n   yt only supports a block structure where the grid edges on the ``n``-th\n   refinement level are aligned with the cell edges on the ``n-1``-th level.\n\nParticle fields are supported by adding 1-dimensional arrays to each\n``grid``'s dict:\n\n.. code-block:: python\n\n   for g in grid_data:\n       g[\"particle_position_x\"] = np.random.random(size=100000)\n\n.. rubric:: Caveats\n\n* Some functions may behave oddly, and parallelism will be disappointing or\n  non-existent in most cases.\n* No consistency checks are performed on the index\n* Data must already reside in memory.\n* Consistency between particle positions and grids is not checked;\n  ``load_amr_grids`` assumes that particle positions associated with one grid are\n  not bounded within another grid at a higher level, so this must be\n  ensured by the user prior to loading the grid data.\n\nGeneric Array Data\n------------------\n\nSee :doc:`Loading_Generic_Array_Data` and\n:func:`~yt.frontends.stream.data_structures.load_uniform_grid` for more detail.\n\nEven if your data is not strictly related to fields commonly used in\nastrophysical codes or your code is not supported yet, you can still feed it to\nyt to use its advanced visualization and analysis facilities. The only\nrequirement is that your data can be represented as one or more uniform, three\ndimensional numpy arrays. Assuming that you have your data in ``arr``,\nthe following code:\n\n.. code-block:: python\n\n   import yt\n\n   data = dict(Density=arr)\n   bbox = np.array([[-1.5, 1.5], [-1.5, 1.5], [1.5, 1.5]])\n   ds = yt.load_uniform_grid(data, arr.shape, 3.08e24, bbox=bbox, nprocs=12)\n\nwill create yt-native dataset ``ds`` that will treat your array as\ndensity field in cubic domain of 3 Mpc edge size (3 * 3.08e24 cm) and\nsimultaneously divide the domain into 12 chunks, so that you can take advantage\nof the underlying parallelism.\n\nParticle fields are added as one-dimensional arrays in a similar manner as the\nthree-dimensional grid fields:\n\n.. code-block:: python\n\n   import yt\n\n   data = dict(\n       Density=dens,\n       particle_position_x=posx_arr,\n       particle_position_y=posy_arr,\n       particle_position_z=posz_arr,\n   )\n   bbox = np.array([[-1.5, 1.5], [-1.5, 1.5], [1.5, 1.5]])\n   ds = yt.load_uniform_grid(data, arr.shape, 3.08e24, bbox=bbox, nprocs=12)\n\nwhere in this example the particle position fields have been assigned. If no\nparticle fields are supplied, then the number of particles is assumed to be\nzero.\n\n.. rubric:: Caveats\n\n* Particles may be difficult to integrate.\n* Data must already reside in memory.\n\n.. _loading-semi-structured-mesh-data:\n\nSemi-Structured Grid Data\n-------------------------\n\n.. note::\n\n   With the release of yt-4.1, functionality has been added to allow loading\n   \"stretched\" grids that are operated on in a more efficient way.  This is done\n   via the :func:`~yt.frontends.stream.data_structures.load_uniform_grid`\n   operation, supplying the ``cell_widths`` argument.  Using the hexahedral mesh\n   is no longer suggested for situations where the mesh can be adequately\n   described with three arrays of cell widths.\n\n   See :ref:`loading-stretched-grids` for more information.\n\nSee :doc:`Loading_Generic_Array_Data`,\n:func:`~yt.frontends.stream.data_structures.hexahedral_connectivity`,\n:func:`~yt.frontends.stream.data_structures.load_hexahedral_mesh` for\nmore detail.\n\nIn addition to uniform grids as described above, you can load in data\nwith non-uniform spacing between datapoints. To load this type of\ndata, you must first specify a hexahedral mesh, a mesh of six-sided\ncells, on which it will live. You define this by specifying the x,y,\nand z locations of the corners of the hexahedral cells. The following\ncode:\n\n.. code-block:: python\n\n   import numpy\n\n   import yt\n\n   xgrid = numpy.array([-1, -0.65, 0, 0.65, 1])\n   ygrid = numpy.array([-1, 0, 1])\n   zgrid = numpy.array([-1, -0.447, 0.447, 1])\n\n   coordinates, connectivity = yt.hexahedral_connectivity(xgrid, ygrid, zgrid)\n\nwill define the (x,y,z) coordinates of the hexahedral cells and\ninformation about that cell's neighbors such that the cell corners\nwill be a grid of points constructed as the Cartesian product of\nxgrid, ygrid, and zgrid.\n\nThen, to load your data, which should be defined on the interiors of\nthe hexahedral cells, and thus should have the shape,\n``(len(xgrid)-1, len(ygrid)-1, len(zgrid)-1)``, you can use the following code:\n\n.. code-block:: python\n\n   bbox = numpy.array(\n       [\n           [numpy.min(xgrid), numpy.max(xgrid)],\n           [numpy.min(ygrid), numpy.max(ygrid)],\n           [numpy.min(zgrid), numpy.max(zgrid)],\n       ]\n   )\n   data = {\"density\": arr}\n   ds = yt.load_hexahedral_mesh(data, conn, coords, 1.0, bbox=bbox)\n\nto load your data into the dataset ``ds`` as described above, where we\nhave assumed your data is stored in the three-dimensional array\n``arr``.\n\n.. rubric:: Caveats\n\n* Integration is not implemented.\n* Some functions may behave oddly or not work at all.\n* Data must already reside in memory.\n\n.. _loading-stretched-grids:\n\nStretched Grid Data\n-------------------\n\n.. warning::\n\n   API consistency for loading stretched grids is not guaranteed until at least\n   yt 4.2!  There may be changes in between then and now, as this is a\n   preliminary feature.\n\nWith version 4.1, yt has the ability to specify cell widths for grids.  This\nallows situations where a grid has a functional form for cell widths, or where\nwidths are provided in advance.\n\n.. note::\n\n   At present, stretched grids are restricted to a single level of refinement.\n   Future versions of yt will have more complete and flexible support!\n\nTo load a stretched grid, you use the standard (and now rather-poorly named)\n``load_uniform_grid`` function, but supplying a ``cell_widths`` argument.  This\nargument should be a list of three arrays, corresponding to the first, second\nand third index-direction cell widths.  (For instance, in a \"standard\"\ncartesian dataset, this would be x, y, z.)\n\nThis script,\ndemonstrates loading a simple \"random\" dataset with a random set of cell-widths.\n\n.. code:: python\n\n   import yt\n   import numpy as np\n\n   N = 8\n\n   data = {\"density\": np.random.random((N, N, N))}\n\n   cell_widths = []\n   for i in range(3):\n       widths = np.random.random(N)\n       widths /= widths.sum()  # Normalize to span 0 .. 1.\n       cell_widths.append(widths)\n\n   ds = yt.load_uniform_grid(\n       data,\n       [N, N, N],\n       bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]]),\n       cell_widths=cell_widths,\n   )\n\n\nThis can be modified to load data from a file, as well as to use more (or\nfewer) cells. Like with a standard uniform grid, providing ``nprocs>1`` will\ndecompose the domain into multiple grids (without refinement).\n\nUnstructured Grid Data\n----------------------\n\nSee :doc:`Loading_Generic_Array_Data`,\n:func:`~yt.frontends.stream.data_structures.load_unstructured_mesh` for\nmore detail.\n\nIn addition to the above grid types, you can also load data stored on\nunstructured meshes. This type of mesh is used, for example, in many\nfinite element calculations. Currently, hexahedral and tetrahedral\nmesh elements are supported.\n\nTo load an unstructured mesh, you need to specify the following. First,\nyou need to have a coordinates array, which should be an (L, 3) array\nthat stores the (x, y, z) positions of all of the vertices in the mesh.\nSecond, you need to specify a connectivity array, which describes how\nthose vertices are connected into mesh elements. The connectivity array\nshould be (N, M), where N is the number of elements and M is the\nconnectivity length, i.e. the number of vertices per element. Finally,\nyou must also specify a data dictionary, where the keys should be\nthe names of the fields and the values should be numpy arrays that\ncontain the field data. These arrays can either supply the cell-averaged\ndata for each element, in which case they would be (N, 1), or they\ncan have node-centered data, in which case they would also be (N, M).\n\nHere is an example of how to load an in-memory, unstructured mesh dataset:\n\n.. code-block:: python\n\n   import numpy as np\n\n   import yt\n\n   coords = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], dtype=np.float64)\n\n   connect = np.array([[0, 1, 3], [1, 2, 3]], dtype=np.int64)\n\n   data = {}\n   data[\"connect1\", \"test\"] = np.array(\n       [[0.0, 1.0, 3.0], [1.0, 2.0, 3.0]], dtype=np.float64\n   )\n\nHere, we have made up a simple, 2D unstructured mesh dataset consisting of two\ntriangles and one node-centered data field. This data can be loaded as an in-memory\ndataset as follows:\n\n.. code-block:: python\n\n    ds = yt.load_unstructured_mesh(connect, coords, data)\n\nThe in-memory dataset can then be visualized as usual, e.g.:\n\n.. code-block:: python\n\n    sl = yt.SlicePlot(ds, \"z\", (\"connect1\", \"test\"))\n    sl.annotate_mesh_lines()\n\nNote that load_unstructured_mesh can take either a single mesh or a list of meshes.\nTo load multiple meshes, you can do:\n\n.. code-block:: python\n\n   import numpy as np\n\n   import yt\n\n   coordsMulti = np.array(\n       [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], dtype=np.float64\n   )\n\n   connect1 = np.array(\n       [\n           [0, 1, 3],\n       ],\n       dtype=np.int64,\n   )\n   connect2 = np.array(\n       [\n           [1, 2, 3],\n       ],\n       dtype=np.int64,\n   )\n\n   data1 = {}\n   data2 = {}\n   data1[\"connect1\", \"test\"] = np.array(\n       [\n           [0.0, 1.0, 3.0],\n       ],\n       dtype=np.float64,\n   )\n   data2[\"connect2\", \"test\"] = np.array(\n       [\n           [1.0, 2.0, 3.0],\n       ],\n       dtype=np.float64,\n   )\n\n   connectList = [connect1, connect2]\n   dataList = [data1, data2]\n\n   ds = yt.load_unstructured_mesh(connectList, coordsMulti, dataList)\n\n   # only plot the first mesh\n   sl = yt.SlicePlot(ds, \"z\", (\"connect1\", \"test\"))\n\n   # only plot the second\n   sl = yt.SlicePlot(ds, \"z\", (\"connect2\", \"test\"))\n\n   # plot both\n   sl = yt.SlicePlot(ds, \"z\", (\"all\", \"test\"))\n\nNote that you must respect the field naming convention that fields on the first\nmesh will have the type ``connect1``, fields on the second will have ``connect2``, etc...\n\n.. rubric:: Caveats\n\n* Integration is not implemented.\n* Some functions may behave oddly or not work at all.\n* Data must already reside in memory.\n\nGeneric Particle Data\n---------------------\n\n.. note::\n\n   For more information about how yt indexes and reads particle data, set the\n   section :ref:`demeshening`.\n\nSee :doc:`Loading_Generic_Particle_Data` and\n:func:`~yt.frontends.stream.data_structures.load_particles` for more detail.\n\nYou can also load generic particle data using the same ``stream`` functionality\ndiscussed above to load in-memory grid data.  For example, if your particle\npositions and masses are stored in ``positions`` and ``masses``, a\nvertically-stacked array of particle x,y, and z positions, and a 1D array of\nparticle masses respectively, you would load them like this:\n\n.. code-block:: python\n\n    import yt\n\n    data = dict(particle_position=positions, particle_mass=masses)\n    ds = yt.load_particles(data)\n\nYou can also load data using 1D x, y, and z position arrays:\n\n.. code-block:: python\n\n    import yt\n\n    data = dict(\n        particle_position_x=posx,\n        particle_position_y=posy,\n        particle_position_z=posz,\n        particle_mass=masses,\n    )\n    ds = yt.load_particles(data)\n\nThe ``load_particles`` function also accepts the following keyword parameters:\n\n``length_unit``\n      The units used for particle positions.\n\n``mass_unit``\n       The units of the particle masses.\n\n``time_unit``\n       The units used to represent times. This is optional and is only used if\n       your data contains a ``creation_time`` field or a ``particle_velocity`` field.\n\n``velocity_unit``\n       The units used to represent velocities.  This is optional and is only used\n       if you supply a velocity field.  If this is not supplied, it is inferred from\n       the length and time units.\n\n``bbox``\n       The bounding box for the particle positions.\n\n.. _smooth-non-sph:\n\nAdding Smoothing Lengths for Non-SPH Particles\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA novel use of the ``load_particles`` function is to facilitate SPH\nvisualization of non-SPH particles. See the example below:\n\n.. code-block:: python\n\n    import yt\n\n    # Load dataset and center on the dense region\n    ds = yt.load(\"FIRE_M12i_ref11/snapshot_600.hdf5\")\n    _, center = ds.find_max((\"PartType0\", \"density\"))\n\n    # Reload DM particles into a stream dataset\n    ad = ds.all_data()\n    pt = \"PartType1\"\n    fields = [\"particle_mass\"] + [f\"particle_position_{ax}\" for ax in \"xyz\"]\n    data = {field: ad[pt, field] for field in fields}\n    ds_dm = yt.load_particles(data, data_source=ad)\n\n    # Generate the missing SPH fields\n    ds_dm.add_sph_fields()\n\n    # Make the SPH projection plot\n    p = yt.ProjectionPlot(ds_dm, \"z\", (\"io\", \"density\"), center=center, width=(1, \"Mpc\"))\n    p.set_unit((\"io\", \"density\"), \"Msun/kpc**2\")\n    p.show()\n\nHere we see two new things. First, ``load_particles`` accepts a ``data_source``\nargument to infer parameters like code units, which could be tedious to provide\notherwise. Second, the returned\n:class:`~yt.frontends.stream.data_structures.StreamParticleDataset` has an\n:meth:`~yt.frontends.stream.data_structures.StreamParticleDataset.add_sph_fields`\nmethod, to create the ``smoothing_length`` and ``density`` fields required for\nSPH visualization to work.\n\n.. _loading-gizmo-data:\n\nGizmo Data\n----------\n\n.. note::\n\n   For more information about how yt indexes and reads particle data, set the\n   section :ref:`demeshening`.\n\nGizmo datasets, including FIRE outputs, can be loaded into yt in the usual\nmanner.  Like other SPH data formats, yt loads Gizmo data as particle fields\nand then uses smoothing kernels to deposit those fields to an underlying\ngrid structure as spatial fields as described in :ref:`loading-gadget-data`.\nTo load Gizmo datasets using the standard HDF5 output format::\n\n   import yt\n   ds = yt.load(\"snapshot_600.hdf5\")\n\nBecause the Gizmo output format is similar to the Gadget format, yt\nmay load Gizmo datasets as Gadget depending on the circumstances, but this\nshould not pose a problem in most situations.  FIRE outputs will be loaded\naccordingly due to the number of metallicity fields found (11 or 17).\n\nIf ``(\"PartType0\", \"MagneticField\")`` is present in the output, it would be\nloaded and aliased to ``(\"PartType0\", \"particle_magnetic_field\")``. The\ncorresponding component field like ``(\"PartType0\", \"particle_magnetic_field_x\")``\nwould be added automatically.\n\nNote that ``(\"PartType4\", \"StellarFormationTime\")`` field has different\nmeanings depending on whether it is a cosmological simulation. For cosmological\nruns this is the scale factor at the redshift when the star particle formed.\nFor non-cosmological runs it is the time when the star particle formed. (See the\n`GIZMO User Guide <http://www.tapir.caltech.edu/~phopkins/Site/GIZMO_files/gizmo_documentation.html>`_)\nFor this reason, ``(\"PartType4\", \"StellarFormationTime\")`` is loaded as a\ndimensionless field. We defined two related fields\n``(\"PartType4\", \"creation_time\")``, and ``(\"PartType4\", \"age\")`` with physical\nunits for your convenience.\n\nFor Gizmo outputs written as raw binary outputs, you may have to specify\na bounding box, field specification, and units as are done for standard\nGadget outputs.  See :ref:`loading-gadget-data` for more information.\n\n.. _halo-catalog-data:\n\nHalo Catalog Data\n-----------------\n\n.. note::\n\n   For more information about how yt indexes and reads particle data, set the\n   section :ref:`demeshening`.\n\nyt has support for reading halo catalogs produced by the AdaptaHOP, Amiga Halo\nFinder (AHF), Rockstar and the inline FOF/SUBFIND halo finders of Gadget and\nOWLS.  The halo catalogs are treated as particle datasets where each particle\nrepresents a single halo.  For example, this means that the ``\"particle_mass\"``\nfield refers to the mass of the halos.  For Gadget FOF/SUBFIND catalogs, the\nmember particles for a given halo can be accessed by creating ``halo`` data\ncontainers.  See :ref:`halo_containers` for more information.\n\nIf you have access to both the halo catalog and the simulation snapshot from\nthe same redshift, additional analysis can be performed for each halo using\n:ref:`halo-analysis`.  The resulting product can be reloaded in a similar manner\nto the other halo catalogs shown here.\n\nAdataHOP\n^^^^^^^^\n\n`AdaptaHOP <https://ascl.net/1305.004>`_ halo catalogs are loaded by providing\nthe path to the ``tree_bricksXXX`` file. As the halo catalog does not contain\nall the information about the simulation (for example the cosmological\nparameters), you also need to pass the parent dataset for it to load correctly.\nSome fields of note available from AdaptaHOP are:\n\n+---------------------+---------------------------+\n| Rockstar field      | yt field name             |\n+=====================+===========================+\n| halo id             | particle_identifier       |\n+---------------------+---------------------------+\n| halo mass           | particle_mass             |\n+---------------------+---------------------------+\n| virial mass         | virial_mass               |\n+---------------------+---------------------------+\n| virial radius       | virial_radius             |\n+---------------------+---------------------------+\n| virial temperature  | virial_temperature        |\n+---------------------+---------------------------+\n| halo position       | particle_position_(x,y,z) |\n+---------------------+---------------------------+\n| halo velocity       | particle_velocity_(x,y,z) |\n+---------------------+---------------------------+\n\nNumerous other AdataHOP fields exist.  To see them, check the field list by\ntyping ``ds.field_list`` for a dataset loaded as ``ds``.  Like all other datasets,\nfields must be accessed through :ref:`Data-objects`.\n\n.. code-block:: python\n\n   import yt\n\n   parent_ds = yt.load(\"output_00080/info_00080.txt\")\n   ds = yt.load(\"output_00080_halos/tree_bricks080\", parent_ds=parent_ds)\n   ad = ds.all_data()\n   # halo masses\n   print(ad[\"halos\", \"particle_mass\"])\n   # halo radii\n   print(ad[\"halos\", \"virial_radius\"])\n\nHalo Data Containers\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nHalo member particles are accessed by creating halo data containers with the\nthe halo id and the type of the particles.  Scalar values for halos\ncan be accessed in the same way.  Halos also have mass, position, velocity, and\nmember ids attributes.\n\n.. code-block:: python\n\n   halo = ds.halo(1, ptype=\"io\")\n   # member particles for this halo\n   print(halo.member_ids)\n   # masses of the halo particles\n   print(halo[\"io\", \"particle_mass\"])\n   # halo mass\n   print(halo.mass)\n\nIn addition, the halo container contains a sphere container. This is the smallest\nsphere that contains all the halos' particles\n\n.. code-block:: python\n\n  halo = ds.halo(1, ptype=\"io\")\n  sp = halo.sphere\n  # Density in halo\n  sp[\"gas\", \"density\"]\n  # Entropy in halo\n  sp[\"gas\", \"entropy\"]\n\n\n.. _ahf:\n\nAmiga Halo Finder\n^^^^^^^^^^^^^^^^^\n\nAmiga Halo Finder (AHF) halo catalogs are loaded by providing the path to the\n.parameter files.  The corresponding .log and .AHF_halos files must exist for\ndata loading to succeed. The field type for all fields is \"halos\". Some fields\nof note available from AHF are:\n\n+----------------+---------------------------+\n| AHF field      | yt field name             |\n+================+===========================+\n| ID             | particle_identifier       |\n+----------------+---------------------------+\n| Mvir           | particle_mass             |\n+----------------+---------------------------+\n| Rvir           | virial_radius             |\n+----------------+---------------------------+\n| (X,Y,Z)c       | particle_position_(x,y,z) |\n+----------------+---------------------------+\n| V(X,Y,Z)c      | particle_velocity_(x,y,z) |\n+----------------+---------------------------+\n\nNumerous other AHF fields exist.  To see them, check the field list by typing\n``ds.field_list`` for a dataset loaded as ``ds``.  Like all other datasets, fields\nmust be accessed through :ref:`Data-objects`.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"ahf_halos/snap_N64L16_135.parameter\", hubble_constant=0.7)\n   ad = ds.all_data()\n   # halo masses\n   print(ad[\"halos\", \"particle_mass\"])\n   # halo radii\n   print(ad[\"halos\", \"virial_radius\"])\n\n.. note::\n\n  Currently the dimensionless Hubble parameter that yt needs is not provided in\n  AHF outputs. So users need to provide the ``hubble_constant`` (default to 1.0)\n  while loading datasets, as shown above.\n\n.. _rockstar:\n\nRockstar\n^^^^^^^^\n\nRockstar halo catalogs are loaded by providing the path to one of the .bin files.\nIn the case where multiple files were produced, one need only provide the path\nto a single one of them.  The field type for all fields is \"halos\".  Some fields\nof note available from Rockstar are:\n\n+----------------+---------------------------+\n| Rockstar field | yt field name             |\n+================+===========================+\n| halo id        | particle_identifier       |\n+----------------+---------------------------+\n| virial mass    | particle_mass             |\n+----------------+---------------------------+\n| virial radius  | virial_radius             |\n+----------------+---------------------------+\n| halo position  | particle_position_(x,y,z) |\n+----------------+---------------------------+\n| halo velocity  | particle_velocity_(x,y,z) |\n+----------------+---------------------------+\n\nNumerous other Rockstar fields exist.  To see them, check the field list by\ntyping ``ds.field_list`` for a dataset loaded as ``ds``.  Like all other datasets,\nfields must be accessed through :ref:`Data-objects`.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"rockstar_halos/halos_0.0.bin\")\n   ad = ds.all_data()\n   # halo masses\n   print(ad[\"halos\", \"particle_mass\"])\n   # halo radii\n   print(ad[\"halos\", \"virial_radius\"])\n\n.. _gadget_fof:\n\nGadget FOF/SUBFIND\n^^^^^^^^^^^^^^^^^^\n\nGadget FOF/SUBFIND halo catalogs work in the same way as those created by\n:ref:`rockstar`, except there are two field types: ``FOF`` for friend-of-friends\ngroups and ``Subhalo`` for halos found with the SUBFIND substructure finder.\nAlso like Rockstar, there are a number of fields specific to these halo\ncatalogs.\n\n+-------------------+---------------------------+\n| FOF/SUBFIND field | yt field name             |\n+===================+===========================+\n| halo id           | particle_identifier       |\n+-------------------+---------------------------+\n| halo mass         | particle_mass             |\n+-------------------+---------------------------+\n| halo position     | particle_position_(x,y,z) |\n+-------------------+---------------------------+\n| halo velocity     | particle_velocity_(x,y,z) |\n+-------------------+---------------------------+\n| num. of particles | particle_number           |\n+-------------------+---------------------------+\n| num. of subhalos  | subhalo_number (FOF only) |\n+-------------------+---------------------------+\n\nMany other fields exist, especially for SUBFIND subhalos.  Check the field\nlist by typing ``ds.field_list`` for a dataset loaded as ``ds``.  Like all\nother datasets, fields must be accessed through :ref:`Data-objects`.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"gadget_fof_halos/groups_042/fof_subhalo_tab_042.0.hdf5\")\n   ad = ds.all_data()\n   # The halo mass\n   print(ad[\"Group\", \"particle_mass\"])\n   print(ad[\"Subhalo\", \"particle_mass\"])\n   # Halo ID\n   print(ad[\"Group\", \"particle_identifier\"])\n   print(ad[\"Subhalo\", \"particle_identifier\"])\n   # positions\n   print(ad[\"Group\", \"particle_position_x\"])\n   # velocities\n   print(ad[\"Group\", \"particle_velocity_x\"])\n\nMultidimensional fields can be accessed through the field name followed by an\nunderscore and the index.\n\n.. code-block:: python\n\n   # x component of the spin\n   print(ad[\"Subhalo\", \"SubhaloSpin_0\"])\n\n.. _halo_containers:\n\nHalo Data Containers\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nHalo member particles are accessed by creating halo data containers with the\ntype of halo (\"Group\" or \"Subhalo\") and the halo id.  Scalar values for halos\ncan be accessed in the same way.  Halos also have mass, position, and velocity\nattributes.\n\n.. code-block:: python\n\n   halo = ds.halo(\"Group\", 0)\n   # member particles for this halo\n   print(halo[\"member_ids\"])\n   # halo virial radius\n   print(halo[\"Group_R_Crit200\"])\n   # halo mass\n   print(halo.mass)\n\nSubhalos containers can be created using either their absolute ids or their\nsubhalo ids.\n\n.. code-block:: python\n\n   # first subhalo of the first halo\n   subhalo = ds.halo(\"Subhalo\", (0, 0))\n   # this subhalo's absolute id\n   print(subhalo.group_identifier)\n   # member particles\n   print(subhalo[\"member_ids\"])\n\nOWLS FOF/SUBFIND\n^^^^^^^^^^^^^^^^\n\nOWLS halo catalogs have a very similar structure to regular Gadget halo catalogs.\nThe two field types are ``FOF`` and ``SUBFIND``.  See :ref:`gadget_fof` for more\ninformation.  At this time, halo member particles cannot be loaded.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"owls_fof_halos/groups_008/group_008.0.hdf5\")\n   ad = ds.all_data()\n   # The halo mass\n   print(ad[\"FOF\", \"particle_mass\"])\n\n.. _halocatalog:\n\nYTHaloCatalog\n^^^^^^^^^^^^^\n\nThese are catalogs produced by the analysis discussed in :ref:`halo-analysis`.\nIn the case where multiple files were produced, one need only provide the path\nto a single one of them.  The field type for all fields is \"halos\".  The fields\navailable here are similar to other catalogs.  Any addition\n:ref:`halo_catalog_quantities` will also be accessible as fields.\n\n+-------------------+---------------------------+\n| HaloCatalog field | yt field name             |\n+===================+===========================+\n| halo id           | particle_identifier       |\n+-------------------+---------------------------+\n| virial mass       | particle_mass             |\n+-------------------+---------------------------+\n| virial radius     | virial_radius             |\n+-------------------+---------------------------+\n| halo position     | particle_position_(x,y,z) |\n+-------------------+---------------------------+\n| halo velocity     | particle_velocity_(x,y,z) |\n+-------------------+---------------------------+\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"tiny_fof_halos/DD0046/DD0046.0.h5\")\n   ad = ds.all_data()\n   # The halo mass\n   print(ad[\"halos\", \"particle_mass\"])\n\nHalo Data Containers\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nHalo particles can be accessed by creating halo data containers with the\ntype of halo (\"halos\") and the halo id and then querying the \"member_ids\"\nfield. Halo containers have mass, radius, position, and velocity\nattributes. Additional fields for which there will be one value per halo\ncan be accessed in the same manner as conventional data containers.\n\n.. code-block:: python\n\n   halo = ds.halo(\"halos\", 0)\n   # particles for this halo\n   print(halo[\"member_ids\"])\n   # halo properties\n   print(halo.mass, halo.radius, halo.position, halo.velocity)\n\n.. _loading-openpmd-data:\n\nopenPMD Data\n------------\n\n`openPMD <https://www.openpmd.org>`_ is an open source meta-standard and naming\nscheme for mesh based data and particle data. It does not actually define a file\nformat.\n\nHDF5-containers respecting the minimal set of meta information from\nversions 1.0.0 and 1.0.1 of the standard are compatible.\nSupport for the ED-PIC extension is not available. Mesh data in cartesian coordinates\nand particle data can be read by this frontend.\n\nTo load the first in-file iteration of a openPMD datasets using the standard HDF5\noutput format:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"example-3d/hdf5/data00000100.h5\")\n\nIf you operate on large files, you may want to modify the virtual chunking behaviour through\n``open_pmd_virtual_gridsize``. The supplied value is an estimate of the size of a single read request\nfor each particle attribute/mesh (in Byte).\n\n.. code-block:: python\n\n  import yt\n\n  ds = yt.load(\"example-3d/hdf5/data00000100.h5\", open_pmd_virtual_gridsize=10e4)\n  sp = yt.SlicePlot(ds, \"x\", (\"openPMD\", \"rho\"))\n  sp.show()\n\nParticle data is fully supported:\n\n.. code-block:: python\n\n  import yt\n\n  ds = yt.load(\"example-3d/hdf5/data00000100.h5\")\n  ad = f.all_data()\n  ppp = yt.ParticlePhasePlot(\n      ad,\n      (\"all\", \"particle_position_y\"),\n      (\"all\", \"particle_momentum_y\"),\n      (\"all\", \"particle_weighting\"),\n  )\n  ppp.show()\n\n.. rubric:: Caveats\n\n* 1D, 2D and 3D data is compatible, but lower dimensional data might yield\n  strange results since it gets padded and treated as 3D. Extraneous dimensions are\n  set to be of length 1.0m and have a width of one cell.\n* The frontend has hardcoded logic for renaming the openPMD ``position``\n  of particles to ``positionCoarse``\n\n.. _loading-pyne-data:\n\nPyNE Data\n---------\n\n`PyNE <http://pyne.io/>`_ is an open source nuclear engineering toolkit\nmaintained by the PyNE development team (pyne-dev@googlegroups.com).\nPyNE meshes utilize the Mesh-Oriented datABase\n`(MOAB) <https://press3.mcs.anl.gov/sigma/moab-library/>`_ and can be\nCartesian or tetrahedral. In addition to field data, pyne meshes store pyne\nMaterial objects which provide a rich set of capabilities for nuclear\nengineering tasks. PyNE Cartesian (Hex8) meshes are supported by yt.\n\nTo create a pyne mesh:\n\n.. code-block:: python\n\n  from pyne.mesh import Mesh\n\n  num_divisions = 50\n  coords = linspace(-1, 1, num_divisions)\n  m = Mesh(structured=True, structured_coords=[coords, coords, coords])\n\nField data can then be added:\n\n.. code-block:: python\n\n  from pyne.mesh import iMeshTag\n\n  m.neutron_flux = IMeshTag()\n  # neutron_flux_data is a list or numpy array of size num_divisions^3\n  m.neutron_flux[:] = neutron_flux_data\n\nAny field data or material data on the mesh can then be viewed just like any other yt dataset!\n\n.. code-block:: python\n\n  import yt\n\n  pf = yt.frontends.moab.data_structures.PyneMoabHex8Dataset(m)\n  s = yt.SlicePlot(pf, \"z\", \"neutron_flux\")\n  s.display()\n\n.. _loading-ramses-data:\n\nRAMSES Data\n-----------\n\nIn yt-4.x, RAMSES data is fully supported.  If you are interested in taking a\ndevelopment or stewardship role, please contact the yt-dev mailing list.  To\nload a RAMSES dataset, you can use the ``yt.load`` command and provide it\nthe ``info*.txt`` filename.  For instance, if you were in a\ndirectory with the following files:\n\n.. code-block:: none\n\n   output_00007\n   output_00007/amr_00007.out00001\n   output_00007/grav_00007.out00001\n   output_00007/hydro_00007.out00001\n   output_00007/info_00007.txt\n   output_00007/part_00007.out00001\n\nYou would feed it the filename ``output_00007/info_00007.txt``:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"output_00007/info_00007.txt\")\n\nyt will attempt to guess the fields in the file. For more control over the hydro fields or the particle fields, see :ref:`loading-ramses-data-args`.\n\nyt also support the new way particles are handled introduced after\nversion ``stable_17_09`` (the version introduced after the 2017 Ramses\nUser Meeting). In this case, the file ``part_file_descriptor.txt``\ncontaining the different fields in the particle files will be read. If\nyou use a custom version of RAMSES, make sure this file is up-to-date\nand reflects the true layout of the particles.\n\nyt supports outputs made by the mainline ``RAMSES`` code as well as the\n``RAMSES-RT`` fork. Files produces by ``RAMSES-RT`` are recognized as such\nbased on the presence of a ``info_rt_*.txt`` file in the output directory.\n\n.. note::\n   for backward compatibility, particles from the\n   ``part_XXXXX.outYYYYY`` files have the particle type ``io`` by\n   default (including dark matter, stars, tracer particles, ...). Sink\n   particles have the particle type ``sink``.\n\n.. _loading-ramses-data-args:\n\nArguments passed to the load function\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nIt is possible to provide extra arguments to the load function when loading RAMSES datasets. Here is a list of the ones specific to RAMSES:\n\n``fields``\n    A list of fields to read from the hydro files. For example, in a pure\n    hydro simulation with an extra custom field named ``my-awesome-field``, one\n    would specify the fields argument following this example:\n\n      .. code-block:: python\n\n          import yt\n\n          fields = [\n              \"Density\",\n              \"x-velocity\",\n              \"y-velocity\",\n              \"z-velocity\",\n              \"Pressure\",\n              \"my-awesome-field\",\n          ]\n          ds = yt.load(\"output_00123/info_00123.txt\", fields=fields)\n          \"my-awesome-field\" in ds.field_list  # is True\n\n\n``extra_particle_fields``\n      A list of tuples describing extra particles fields to read in. By\n      default, yt will try to detect as many fields as possible,\n      assuming the extra ones to be double precision floats. This\n      argument is useful if you have extra fields besides the particle mass,\n      position, and velocity fields that yt cannot detect automatically. For\n      example, for a dataset containing two extra particle integer fields named\n      ``family`` and ``info``, one would do:\n\n      .. code-block:: python\n\n          import yt\n\n          extra_fields = [(\"family\", \"I\"), (\"info\", \"I\")]\n          ds = yt.load(\"output_00001/info_00001.txt\", extra_particle_fields=extra_fields)\n          # ('all', 'family') and ('all', 'info') now in ds.field_list\n\n      The format of the ``extra_particle_fields`` argument is as follows:\n      ``[('field_name_1', 'type_1'), ..., ('field_name_n', 'type_n')]`` where\n      the second element of the tuple follows the `python struct format\n      convention\n      <https://docs.python.org/3.5/library/struct.html#format-characters>`_.\n      Note that if ``extra_particle_fields`` is defined, yt will not assume\n      that the ``particle_birth_time`` and ``particle_metallicity`` fields\n      are present in the dataset. If these fields are present, they must be\n      explicitly enumerated in the ``extra_particle_fields`` argument.\n\n``cosmological``\n      Force yt to consider a simulation to be cosmological or\n      not. This may be useful for some specific simulations e.g. that\n      run down to negative redshifts.\n\n``bbox``\n      The subbox to load. yt will only read CPUs intersecting with the\n      subbox. This is especially useful for large simulations or\n      zoom-in simulations, where you don't want to have access to data\n      outside of a small region of interest. This argument will prevent\n      yt from loading AMR files outside the subbox and will hence\n      spare memory and time.\n      For example, one could use\n\n      .. code-block:: python\n\n          import yt\n\n          # Only load a small cube of size (0.1)**3\n          bbox = [[0.0, 0.0, 0.0], [0.1, 0.1, 0.1]]\n          ds = yt.load(\"output_00001/info_00001.txt\", bbox=bbox)\n\n          # See the note below for the following examples\n          ds.right_edge == [1, 1, 1]  # is True\n\n          ad = ds.all_data()\n          ad[\"all\", \"particle_position_x\"].max() > 0.1  # _may_ be True\n\n          bb = ds.box(left_edge=bbox[0], right_edge=bbox[1])\n          bb[\"all\", \"particle_position_x\"].max() < 0.1  # is True\n\n      .. note::\n         When using the bbox argument, yt will read all the CPUs\n         intersecting with the subbox. However it may also read some\n         data *outside* the selected region. This is due to the fact\n         that domains have a complicated shape when using Hilbert\n         ordering. Internally, yt will hence assume the loaded dataset\n         covers the entire simulation. If you only want the data from\n         the selected region, you may want to use ``ds.box(...)``.\n\n      .. note::\n         The ``bbox`` feature is only available for datasets using\n         Hilbert ordering.\n\n``max_level, max_level_convention``\n      This will set the deepest level to be read from file. Both arguments\n      have to be set, where the convention can be either \"ramses\" or \"yt\".\n\n      In the \"ramses\" convention, levels go from 1 (the root grid)\n      to levelmax, such that the finest cells have a size of ``boxsize/2**levelmax``.\n      In the \"yt\" convention, levels are numbered from 0 (the coarsest\n      uniform grid at RAMSES' ``levelmin``) to ``max_level``, such that\n      the finest cells are ``2**max_level`` smaller than the coarsest.\n\n\n      .. code-block:: python\n\n          import yt\n\n          # Assuming RAMSES' levelmin=6, i.e. the structure is full\n          # down to levelmin=6\n          ds_all = yt.load(\"output_00080/info_00080.txt\")\n          ds_yt = yt.load(\"output_00080/info_00080.txt\", max_level=2, max_level_convention=\"yt\")\n          ds_ramses = yt.load(\n              \"output_00080/info_00080.txt\",\n              max_level=8,\n              max_level_convention=\"ramses\",\n          )\n\n          any(ds_all.r[\"index\", \"grid_level\"] > 2)  # True\n          all(ds_yt.r[\"index\", \"grid_level\"] <= 2)  # True\n          all(ds_ramses.r[\"index\", \"grid_level\"] <= 2)  # True\n\n\n\nAdding custom particle fields\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThere are three way to make yt detect all the particle fields. For example, if you wish to make yt detect the birth time and metallicity of your particles, use one of these methods\n\n1. ``yt.load`` method. Whenever loading a dataset, add the extra particle fields as a keyword argument to the ``yt.load`` call.\n\n   .. code-block:: python\n\n      import yt\n\n      epf = [(\"particle_birth_time\", \"d\"), (\"particle_metallicity\", \"d\")]\n      ds = yt.load(\"dataset\", extra_particle_fields=epf)\n\n      (\"io\", \"particle_birth_time\") in ds.derived_field_list  # is True\n      (\"io\", \"particle_metallicity\") in ds.derived_field_list  # is True\n\n2. yt config method. If you don't want to pass the arguments for each call of ``yt.load``, you can add in your configuration\n\n   .. code-block:: none\n\n      [ramses-particles]\n      fields = \"\"\"\n         particle_position_x, d\n         particle_position_y, d\n         particle_position_z, d\n         particle_velocity_x, d\n         particle_velocity_y, d\n         particle_velocity_z, d\n         particle_mass, d\n         particle_identifier, i\n         particle_refinement_level, I\n         particle_birth_time, d\n         particle_metallicity, d\n      \"\"\"\n\n   Each line should contain the name of the field and its data type (``d`` for double precision, ``f`` for single precision, ``i`` for integer and ``l`` for long integer). You can also configure the auto detected fields for fluid types by adding a section ``ramses-hydro``, ``ramses-grav`` or ``ramses-rt`` in the config file. For example, if you customized your gravity files so that they contain the potential, the potential in the previous timestep and the x, y and z accelerations, all in single-precision, you can use :\n\n   .. code-block:: none\n\n      [ramses-grav]\n      fields = [ \"Potential,f\", \"Potential-old,f\", \"x-acceleration,f\", \"y-acceleration,f\", \"z-acceleration,f\" ]\n\n   Importantly, the order of the fields should match the order in which they are written in the RAMSES output files.\n   yt will also assume that each entry is formed of a field name followed by its type (``f`` for single precision, ``d`` for double precision), separated by a comma. Field names containing commas are not supported, other precisions (e.g. integers) are not supported either. Presently, all fields in a given file type (gravity, hydro, ...) need to have the same precision (e.g. all single-precision or all double-precision), combinations are not supported. Internally, yt will convert all data into double-precisoin floats, but this will allow it to read the data correctly from file.\n\n3. New RAMSES way. Recent versions of RAMSES automatically write in their output an ``hydro_file_descriptor.txt`` file that gives information about which field is where. If you wish, you can simply create such a file in the folder containing the ``info_xxxxx.txt`` file\n\n   .. code-block:: none\n\n      # version:  1\n      # ivar, variable_name, variable_type\n       1, position_x, d\n       2, position_y, d\n       3, position_z, d\n       4, velocity_x, d\n       5, velocity_y, d\n       6, velocity_z, d\n       7, mass, d\n       8, identity, i\n       9, levelp, i\n      10, birth_time, d\n      11, metallicity, d\n\n   It is important to note that this file should not end with an empty line (but in this case with ``11, metallicity, d``).\n   Note that, for grid fields (hydro, gravity, rt), all the fields need to have the same precision (``f`` or ``d``).\n   Combinations are not supported.\n\n.. note::\n\n   The kind (``i``, ``d``, ``I``, ...) of the field follow the `python convention <https://docs.python.org/3.5/library/struct.html#format-characters>`_.\n\n\n\nCustomizing the particle type association\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn versions of RAMSES more recent than December 2017, particles carry\nalong a ``family`` array. The value of this array gives the kind of\nthe particle, e.g. 1 for dark matter. It is possible to customize the\nassociation between particle type and family by customizing the yt\nconfig (see :ref:`configuration-file`), adding\n\n.. code-block:: none\n\n   [ramses-families]\n   gas_tracer = 100\n   star_tracer = 101\n   dm = 0\n   star = 1\n\n\n\nParticle ages and formation times\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nFor non-cosmological simulations, particle ages are stored in physical units on\ndisk. To access the birth time for the particles, use the\n``particle_birth_time`` field. The time recorded in this field is relative to\nthe beginning of the simulation. Particles that were present in the initial\nconditions will have negative values for ``particle_birth_time``.\n\nFor cosmological simulations that include star particles, RAMSES stores particle\nformation times as conformal times. To access the formation time field data in\nconformal units use the ``conformal_birth_time`` field. This will return the\nformation times of particles in the simulation in conformal units as a\ndimensionless array. To access the formation time in physical units, use the\n``particle_birth_time`` field. Finally, to access the ages of star particles in\nyour simulation, use the ``star_age`` field. Note that this field is defined for\nall particle types but will only make sense for star particles.\n\nFor simulations conducted in Newtownian coordinates, with no cosmology or\ncomoving expansion, the time is equal to zero at the beginning of the\nsimulation. That means that particles present in the initial conditions may have\nnegative birth times. This can happen, for example, in idealized isolated galaxy\nsimulations, where star particles are included in the initial conditions. For\nsimulations conducted in cosmological comoving units, the time is equal to zero\nat the big bang, and all particles should have positive values for the\n``particle_birth_time`` field.\n\nTo help clarify the above discussion, the following table describes the meaning\nof the various particle formation time and age fields:\n\n+------------------+--------------------------+--------------------------------+\n| Simulation type  | Field name               | Description                    |\n+==================+==========================+================================+\n| cosmological     | ``conformal_birth_time`` | Formation time in conformal    |\n|                  |                          | units (dimensionless)          |\n+------------------+--------------------------+--------------------------------+\n| any              | ``particle_birth_time``  | The time relative to the       |\n|                  |                          | beginning of the simulation    |\n|                  |                          | when the particle was formed.  |\n|                  |                          | For non-cosmological           |\n|                  |                          | simulations, this field will   |\n|                  |                          | have positive values for       |\n|                  |                          | particles formed during the    |\n|                  |                          | simulation and negative for    |\n|                  |                          | particles of finite age in the |\n|                  |                          | initial conditions. For        |\n|                  |                          | cosmological simulations this  |\n|                  |                          | is the time the particle       |\n|                  |                          | formed relative to the big     |\n|                  |                          | bang, therefore the value of   |\n|                  |                          | this field should be between   |\n|                  |                          | 0 and 13.7 Gyr.                |\n+------------------+--------------------------+--------------------------------+\n| any              | ``star_age``             | Age of the particle.           |\n|                  |                          | Only physically meaningful for |\n|                  |                          | stars and particles that       |\n|                  |                          | formed dynamically during the  |\n|                  |                          | simulation.                    |\n+------------------+--------------------------+--------------------------------+\n\nRAMSES datasets produced by a version of the code newer than November 2017\ncontain the metadata necessary for yt to automatically distinguish between star\nparticles and other particle types. If you are working with a dataset produced\nby a version of RAMSES older than November 2017, yt will only automatically\nrecognize a single particle ``io``. It may be convenient to define a particle\nfilter in your scripts to distinguish between particles present in the initial\nconditions and particles that formed dynamically during the simulation by\nfiltering particles with ``\"conformal_birth_time\"`` values equal to zero and not\nequal to zero.  An example particle filter definition for dynamically formed\nstars might look like this:\n\n.. code-block:: python\n\n    @yt.particle_filter(requires=[\"conformal_birth_time\"], filtered_type=\"io\")\n    def stars(pfilter, data):\n        filter = data[pfilter.filtered_type, \"conformal_birth_time\"] != 0\n        return filter\n\nFor a cosmological simulation, this filter will distinguish between stars and\ndark matter particles.\n\n.. _loading-sph-data:\n\nSPH Particle Data\n-----------------\n\n.. note::\n\n   For more information about how yt indexes and reads particle data, set the\n   section :ref:`demeshening`.\n\nFor all of the SPH frontends, yt uses cython-based SPH smoothing onto an\nin-memory octree to create deposited mesh fields from individual SPH particle\nfields.\n\nThis uses a standard M4 smoothing kernel and the ``smoothing_length``\nfield to calculate SPH sums, filling in the mesh fields.  This gives you the\nability to both track individual particles (useful for tasks like following\ncontiguous clouds of gas that would be require a clump finder in grid data) as\nwell as doing standard grid-based analysis (i.e. slices, projections, and profiles).\n\nThe ``smoothing_length`` variable is also useful for determining which particles\ncan interact with each other, since particles more distant than twice the\nsmoothing length do not typically see each other in SPH simulations.  By\nchanging the value of the ``smoothing_length`` and then re-depositing particles\nonto the grid, you can also effectively mimic what your data would look like at\nlower resolution.\n\n.. _loading-tipsy-data:\n\nTipsy Data\n----------\n\n.. note::\n\n   For more information about how yt indexes and reads particle data, set the\n   section :ref:`demeshening`.\n\nSee :doc:`../cookbook/tipsy_and_yt` and :ref:`loading-sph-data` for more details.\n\nyt also supports loading Tipsy data.  Many of its characteristics are similar\nto how Gadget data is loaded.\n\n.. code-block:: python\n\n   ds = load(\"./halo1e11_run1.00400\")\n\n.. _specifying-cosmology-tipsy:\n\nSpecifying Tipsy Cosmological Parameters and Setting Default Units\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCosmological parameters can be specified to Tipsy to enable computation of\ndefault units.  For example do the following, to load a Tipsy dataset whose\npath is stored in the variable ``my_filename`` with specified cosmology\nparameters:\n\n.. code-block:: python\n\n   cosmology_parameters = {\n       \"current_redshift\": 0.0,\n       \"omega_lambda\": 0.728,\n       \"omega_matter\": 0.272,\n       \"hubble_constant\": 0.702,\n   }\n\n   ds = yt.load(my_filename, cosmology_parameters=cosmology_parameters)\n\nIf you wish to set the unit system directly, you can do so by using the\n``unit_base`` keyword in the load statement.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(filename, unit_base={\"length\", (1.0, \"Mpc\")})\n\nSee the documentation for the\n:class:`~yt.frontends.tipsy.data_structures.TipsyDataset` class for more\ninformation.\n\nLoading Cosmological Simulations\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you are not using a parameter file (i.e. non-Gasoline users), then you must\nuse keyword ``cosmology_parameters`` when loading your data set to indicate to\nyt that it is a cosmological data set. If you do not wish to set any\nnon-default cosmological parameters, you may pass an empty dictionary.\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(filename, cosmology_parameters={})\n\n.. _loading-cfradial-data:\n\nCfRadial Data\n-------------\n\nCf/Radial is a CF compliant netCDF convention for radial data from radar and\nlidar platforms that supports both airborne and ground-based sensors. Because\nof its CF-compliance, CfRadial will allow researchers familiar with CF to read\nthe data into a wide variety of analysis tools, models etc. For more see:\n[CfRadialDoc.v1.4.20160801.pdf](https://github.com/NCAR/CfRadial/blob/d4562a995d0589cea41f4f6a4165728077c9fc9b/docs/CfRadialDoc.v1.4.20160801.pdf)\n\nyt provides support for loading cartesian-gridded CfRadial netcdf-4 files as\nwell as polar coordinate Cfradial netcdf-4 files. When loading a standard\nCfRadial dataset in polar coordinates, yt will first build a sample on a\ncartesian grid (see :ref:`cfradial_gridding`). To load a CfRadial data file:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"CfRadialGrid/grid1.nc\")\n\n.. _cfradial_gridding:\n\nGridding Behavior\n^^^^^^^^^^^^^^^^^\n\nWhen you load a CfRadial dataset in polar coordinates (elevation, azimuth and\nrange), yt will first build a sample by mapping the data onto a cartesian grid\nusing the Python-ARM Radar Toolkit (`pyart <https://github.com/ARM-DOE/pyart>`_).\nGrid points are found by interpolation of all data points within a specified radius of influence.\nThis data, now in x, y, z coordinate domain is then saved as a new dataset and subsequent\nloads of the original native CfRadial dataset will use the gridded file.\nMapping the data from spherical to Cartesian coordinates is useful for 3D volume\nrendering the data using yt.\n\nSee the documentation for the\n:class:`~yt.frontends.cf_radial.data_structures.CFRadialDataset` class for a\ndescription of how to adjust the gridding parameters and storage of the gridded\nfile.\n"
  },
  {
    "path": "doc/source/examining/low_level_inspection.rst",
    "content": ".. _low-level-data-inspection:\n\nLow-Level Data Inspection: Accessing Raw Data\n=============================================\n\nyt can not only provide high-level access to data, such as through slices,\nprojections, object queries and the like, but it can also provide low-level\naccess to the raw data.\n\n.. note:: This section is tuned for patch- or block-based simulations.  Future\n          versions of yt will enable more direct access to particle and oct\n          based simulations.  For now, these are represented as patches, with\n          the attendant properties.\n\nFor a more basic introduction, see :ref:`quickstart` and more specifically\n:doc:`../quickstart/2)_Data_Inspection`.\n\n.. _examining-grid-hierarchies:\n\nExamining Grid Hierarchies\n--------------------------\n\nyt organizes grids in a hierarchical fashion; a coarser grid that contains (or\noverlaps with) a finer grid is referred to as its parent.  yt organizes these\nonly a single level of refinement at a time.  To access grids, the ``grids``\nattribute on a :class:`~yt.geometry.grid_geometry_handler.GridIndex` object.  (For\nfast operations, a number of additional arrays prefixed with ``grid`` are also\navailable, such as ``grid_left_edges`` and so on.)  This returns an instance of\n:class:`~yt.data_objects.grid_patch.AMRGridPatch`, which can be queried for\neither data or index information.\n\nThe :class:`~yt.data_objects.grid_patch.AMRGridPatch` object itself provides\nthe following attributes:\n\n* ``Children``: a list of grids contained within this one, of one higher level\n  of refinement\n* ``Parent``: a single object or a list of objects this grid is contained\n  within, one level of refinement coarser\n* ``child_mask``: a mask of 0's and 1's, representing where no finer data is\n  available in refined grids (1) or where this grid is covered by finer regions\n  (0).  Note that to get back the final data contained within a grid, one can\n  multiple a field by this attribute.\n* ``child_indices``: a mask of booleans, where False indicates no finer data\n  is available.  This is essentially the inverse of ``child_mask``.\n* ``child_index_mask``: a mask of indices into the ``ds.index.grids`` array of the\n  child grids.\n* ``LeftEdge``: the left edge, in native code coordinates, of this grid\n* ``RightEdge``: the right edge, in native code coordinates, of this grid\n* ``dds``: the width of a cell in this grid\n* ``id``: the id (not necessarily the index) of this grid.  Defined such that\n  subtracting the property ``_id_offset`` gives the index into ``ds.index.grids``.\n* ``NumberOfParticles``: the number of particles in this grid\n* ``OverlappingSiblings``: a list of sibling grids that this grid overlaps\n  with.  Likely only defined for Octree-based codes.\n\nIn addition, the method\n:meth:`~yt.data_objects.grid_patch.AMRGridPatch.get_global_startindex` can be\nused to get the integer coordinates of the upper left edge.  These integer\ncoordinates are defined with respect to the current level; this means that they\nare the offset of the left edge, with respect to the left edge of the domain,\ndivided by the local ``dds``.\n\nTo traverse a series of grids, this type of construction can be used:\n\n.. code-block:: python\n\n   g = ds.index.grids[1043]\n   g2 = g.Children[1].Children[0]\n   print(g2.LeftEdge)\n\n.. _examining-grid-data:\n\nExamining Grid Data\n-------------------\n\nOnce you have identified a grid you wish to inspect, there are two ways to\nexamine data.  You can either ask the grid to read the data and pass it to you\nas normal, or you can manually intercept the data from the IO handler and\nexamine it before it has been unit converted.  This allows for much more raw\ndata inspection.\n\nTo access data that has been read in the typical fashion and unit-converted as\nnormal, you can access the grid as you would a normal object:\n\n.. code-block:: python\n\n   g = ds.index.grids[1043]\n   print(g[\"gas\", \"density\"])\n   print(g[\"gas\", \"density\"].min())\n\nTo access the raw data (as found in the file), use\n\n.. code-block:: python\n\n   g = ds.index.grids[1043]\n   rho = g[\"gas\", \"density\"].in_base(\"code\")\n\n.. _finding-data-at-fixed-points:\n\nFinding Data at Fixed Points\n----------------------------\n\nOne of the most common questions asked of data is, what is the value *at this\nspecific point*.  While there are several ways to find out the answer to this\nquestion, a few helper routines are provided as well.  To identify the\nfinest-resolution (i.e., most canonical) data at a given point, use\nthe point data object::\n\n  from yt.units import kpc\n  point_obj = ds.point([30, 75, 80]*kpc)\n  density_at_point = point_obj['gas', 'density']\n\nThe point data object works just like any other yt data object. It is special\nbecause it is the only zero-dimensional data object: it will only return data at\nthe exact point specified when creating the point data object. For more\ninformation about yt data objects, see :ref:`Data-objects`.\n\nIf you need to find field values at many points, the\n:meth:`~yt.data_objects.static_output.Dataset.find_field_values_at_points`\nfunction may be more efficient. This function returns a nested list of field\nvalues at multiple points in the simulation volume. For example, if one wanted\nto find the value of a mesh field at the location of the particles in a\nsimulation, one could do::\n\n  ad = ds.all_data()\n  ppos = ad[\"all\", \"particle_position\"]\n  ppos_den_vel = ds.find_field_values_at_points(\n    [(\"gas\", \"density\"), (\"gas\", \"velocity_x\")],\n    ppos\n  )\n\nIn this example, ``ppos_den_vel`` will be a list of arrays. The first array will\ncontain the density values at the particle positions, the second will contain\nthe x velocity values at the particle positions.\n\n.. _examining-grid-data-in-a-fixed-resolution-array:\n\nExamining Grid Data in a Fixed Resolution Array\n-----------------------------------------------\n\nIf you have a dataset, either AMR or single resolution, and you want to just\nstick it into a fixed resolution numpy array for later examination, then you\nwant to use a :ref:`Covering Grid <available-objects>`.  You must specify the\nmaximum level at which to sample the data, a left edge of the data where you\nwill start, and the resolution at which you want to sample.\n\nFor example, let's use the :ref:`sample dataset <getting-sample-data>`\n``Enzo_64``.  This dataset is at a resolution of 64^3 with 5 levels of AMR,\nso if we want a 64^3 array covering the entire volume and sampling just the\nlowest level data, we run:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n   all_data_level_0 = ds.covering_grid(level=0, left_edge=[0, 0.0, 0.0], dims=[64, 64, 64])\n\nNote that we can also get the same result and rely on the dataset to know\nits own underlying dimensions:\n\n.. code-block:: python\n\n   all_data_level_0 = ds.covering_grid(\n       level=0, left_edge=[0, 0.0, 0.0], dims=ds.domain_dimensions\n   )\n\nWe can now access our underlying data at the lowest level by specifying what\n:ref:`field <field-list>` we want to examine:\n\n.. code-block:: python\n\n  print(all_data_level_0[\"gas\", \"density\"].shape)\n  # (64, 64, 64)\n\n  print(all_data_level_0[\"gas\", \"density\"])\n  # array([[[  1.92588925e-31,   1.74647692e-31,   2.54787518e-31, ...,\n\n  print(all_data_level_0[\"gas\", \"temperature\"].shape)\n  # (64, 64, 64)\n\nIf you create a covering grid that spans two child grids of a single parent\ngrid, it will fill those zones covered by a zone of a child grid with the\ndata from that child grid. Where it is covered only by the parent grid, the\ncells from the parent grid will be duplicated (appropriately) to fill the\ncovering grid.\n\nLet's say we now want to look at that entire data volume and sample it at\na higher resolution (i.e. level 2).  As stated above, we'll be oversampling\nunder-refined regions, but that's OK.  We must also increase the resolution\nof our output array by a factor of 2^2 in each direction to hold this new\nlarger dataset:\n\n.. code-block:: python\n\n   all_data_level_2 = ds.covering_grid(\n       level=2, left_edge=[0, 0.0, 0.0], dims=ds.domain_dimensions * 2**2\n   )\n\nAnd let's see what's the density in the central location:\n\n.. code-block:: python\n\n   print(all_data_level_2[\"gas\", \"density\"].shape)\n   (256, 256, 256)\n\n   print(all_data_level_2[\"gas\", \"density\"][128, 128, 128])\n   1.7747457571203124e-31\n\nThere are two different types of covering grids: unsmoothed and smoothed.\nSmoothed grids will be filled through a cascading interpolation process;\nthey will be filled at level 0, interpolated to level 1, filled at level 1,\ninterpolated to level 2, filled at level 2, etc. This will help to reduce\nedge effects. Unsmoothed covering grids will not be interpolated, but rather\nvalues will be duplicated multiple times.\n\nTo sample our dataset from above with a smoothed covering grid in order\nto reduce edge effects, it is a nearly identical process:\n\n.. code-block:: python\n\n   all_data_level_2_s = ds.smoothed_covering_grid(\n       2, [0.0, 0.0, 0.0], ds.domain_dimensions * 2**2\n   )\n\n   print(all_data_level_2_s[\"gas\", \"density\"].shape)\n   (256, 256, 256)\n\n   print(all_data_level_2_s[\"gas\", \"density\"][128, 128, 128])\n   1.763744852165591e-31\n\n\nCovering grids can also accept a ``data_source`` argument, in which case only\nthe cells of the covering grid that are contained by the ``data_source`` will be\nfilled. This can be useful to create regularized arrays of more complex\ngeometries. For example, if we provide a sphere, we see that the covering grid\nshape is the same, but the number of cells with data is less\n\n.. code-block:: python\n\n   sp = ds.sphere(ds.domain_center, (0.25, \"code_length\"))\n   cg_sp = ds.covering_grid(\n       level=0, left_edge=[0, 0.0, 0.0], dims=ds.domain_dimensions, data_source=sp\n   )\n\n   print(cg_sp[\"gas\", \"density\"].shape)\n   (64, 64, 64)\n\n   print(cg_sp[\"gas\", \"density\"].size)\n   262144\n\n   print(cg_sp[\"gas\", \"density\"][cg_sp[\"gas\", \"density\"] != 0].size)\n   17256\n\nThe ``data_source`` can be any :ref:`3D Data Container <region-reference>`. Also\nnote that the ``data_source`` argument is only available for the ``covering_grid``\nat present (not the ``smoothed_covering_grid``).\n\n.. _examining-image-data-in-a-fixed-resolution-array:\n\nExamining Image Data in a Fixed Resolution Array\n------------------------------------------------\n\nIn the same way that one can sample a multi-resolution 3D dataset by placing\nit into a fixed resolution 3D array as a\n:ref:`Covering Grid <examining-grid-data-in-a-fixed-resolution-array>`, one can\nalso access the raw image data that is returned from various yt functions\ndirectly as a fixed resolution array.  This provides a means for bypassing the\nyt method for generating plots, and allows the user the freedom to use\nwhatever interface they wish for displaying and saving their image data.\nYou can use the :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer`\nto accomplish this as described in :ref:`fixed-resolution-buffers`.\n\nHigh-level Information about Particles\n--------------------------------------\n\nThere are a number of high-level helpers attached to ``Dataset`` objects to find\nout information about the particles in an output file. First, one can check if\nthere are any particles in a dataset at all by examining\n``ds.particles_exist``. This will be ``True`` for datasets the include particles\nand ``False`` otherwise.\n\nOne can also see which particle types are available in a dataset. Particle types\nthat are available in the dataset's on-disk output are known as \"raw\" particle\ntypes, and they will appear in ``ds.particle_types_raw``. Particle types that\nare dynamically defined via a particle filter of a particle union will also\nappear in the ``ds.particle_types`` list. If the simulation only has one\nparticle type on-disk, its name will by ``'io'``. If there is more than one\nparticle type, the names of the particle types will be inferred from the output\nfile. For example, Gadget HDF5 files have particle type names like ``PartType0``\nand ``PartType1``, while Enzo data, which usually only has one particle type,\nwill only have a particle named ``io``.\n\nFinally, one can see the number of each particle type by inspecting\n``ds.particle_type_counts``. This will be a dictionary mapping the names of\nparticle types in ``ds.particle_types_raw`` to the number of each particle type\nin a simulation output.\n"
  },
  {
    "path": "doc/source/faq/index.rst",
    "content": ".. _faq:\n\n\nFrequently Asked Questions\n==========================\n\n.. contents::\n   :depth: 2\n   :local:\n   :backlinks: none\n\nVersion & Installation\n----------------------\n\n.. _determining-version:\n\nHow can I tell what version of yt I'm using?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you run into problems with yt and you're writing to the mailing list\nor contacting developers on Slack, they will likely want to know what version of\nyt you're using.  Often times, you'll want to know both the yt version,\nas well as the last changeset that was committed to the branch you're using.\nTo reveal this, go to a command line and type:\n\n.. code-block:: bash\n\n    $ yt version\n\nThe result will look something like this:\n\n.. code-block:: bash\n\n    yt module located at:\n        /Users/mitchell/src/yt-conda/src/yt-git\n\n    The current version of yt is:\n\n    ---\n    Version = 4.0.dev0\n    Changeset = 9f947a930ab4\n    ---\n    This installation CAN be automatically updated.\n\n\nFor more information on this topic, see :ref:`updating`.\n\n.. _yt-3.0-problems:\n\nI upgraded to yt 4.0 but my code no longer works.  What do I do?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWe've tried to keep the number of backward-incompatible changes to a minimum\nwith the release of yt-4.0, but because of the wide-reaching changes to how\nyt manages data, there may be updates you have to make.\nYou can see many of the changes in :ref:`yt4differences`, and\nin :ref:`transitioning-to-4.0` there are helpful tips on how to modify your scripts to update them.\n\nCode Errors and Failures\n------------------------\n\nPython fails saying that it cannot import yt modules\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis is commonly exhibited with an error about not being able to import code\nthat is part of yt. This is likely because the code that is failing to import\nneeds to be compiled or recompiled.\n\nThis error tends to occur when there are changes in the underlying Cython files\nthat need to be rebuilt, like after a major code update or when switching\nbetween distant branches.\n\nThis is solved by running the install command again. See\n:ref:`install-from-source`.\n\n\n.. _faq-mpi4py:\n\nyt complains that it needs the mpi4py module\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nFor yt to be able to incorporate parallelism on any of its analysis (see\n:ref:`parallel-computation`), it needs to be able to use MPI libraries.\nThis requires the ``mpi4py`` module to be installed in your version of python.\nUnfortunately, installation of ``mpi4py`` is *just* tricky enough to elude the\nyt batch installer.  So if you get an error in yt complaining about mpi4py\nlike:\n\n.. code-block:: bash\n\n    ImportError: No module named mpi4py\n\nthen you should install ``mpi4py``.  The easiest way to install it is through\nthe pip interface.  At the command line, type:\n\n.. code-block:: bash\n\n    $ python -m pip install mpi4py\n\nWhat this does is it finds your default installation of Python (presumably\nin the yt source directory), and it installs the mpi4py module.  If this\naction is successful, you should never have to worry about your aforementioned\nproblems again.  If, on the other hand, this installation fails (as it does on\nsuch machines as NICS Kraken, NASA Pleaides and more), then you will have to\ntake matters into your own hands.  Usually when it fails, it is due to pip\nbeing unable to find your MPI C/C++ compilers (look at the error message).\nIf this is the case, you can specify them explicitly as per:\n\n.. code-block:: bash\n\n    $ env MPICC=/path/to/MPICC python -m pip install mpi4py\n\nSo for example, on Kraken, I switch to the gnu C compilers (because yt\ndoesn't work with the portland group C compilers), then I discover that\ncc is the mpi-enabled C compiler (and it is in my path), so I run:\n\n.. code-block:: bash\n\n    $ module swap PrgEnv-pgi PrgEnv-gnu\n    $ env MPICC=cc python -m pip install mpi4py\n\nAnd voila!  It installs!  If this *still* fails for you, then you can\nbuild and install from source and specify the mpi-enabled c and c++\ncompilers in the mpi.cfg file.  See the\n`mpi4py installation page <https://mpi4py.readthedocs.io/en/stable/install.html>`_\nfor details.\n\n\nUnits\n-----\n\n.. _conversion-factors:\n\nHow do I convert between code units and physical units for my dataset?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nStarting with yt-3.0, and continuing to yt-4.0, yt uses an internal symbolic\nunit system.  In yt-3.0 this was bundled with the main yt codebase, and with\nyt-4.0 it is now available as a separate package called `unyt\n<https://unyt.readthedocs.org/>`_.  Conversion factors are tied up in the\n``length_unit``, ``times_unit``, ``mass_unit``, and ``velocity_unit``\nattributes, which can be converted to any arbitrary desired physical unit:\n\n.. code-block:: python\n\n    print(\"Length unit: \", ds.length_unit)\n    print(\"Time unit: \", ds.time_unit)\n    print(\"Mass unit: \", ds.mass_unit)\n    print(\"Velocity unit: \", ds.velocity_unit)\n\n    print(\"Length unit: \", ds.length_unit.in_units(\"code_length\"))\n    print(\"Time unit: \", ds.time_unit.in_units(\"code_time\"))\n    print(\"Mass unit: \", ds.mass_unit.in_units(\"kg\"))\n    print(\"Velocity unit: \", ds.velocity_unit.in_units(\"Mpc/year\"))\n\nSo to accomplish the example task of converting a scalar variable ``x`` in\ncode units to kpc in yt-4.0, you can do one of two things.  If ``x`` is\nalready a YTQuantity with units in ``code_length``, you can run:\n\n.. code-block:: python\n\n    x.in_units(\"kpc\")\n\nHowever, if ``x`` is just a numpy array or native python variable without\nunits, you can convert it to a YTQuantity with units of ``kpc`` by running:\n\n.. code-block:: python\n\n    x = x * ds.length_unit.in_units(\"kpc\")\n\nFor more information about unit conversion, see :ref:`units`.\n\nHow do I make a YTQuantity tied to a specific dataset's units?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you want to create a variable or array that is tied to a particular dataset\n(and its specific conversion factor to code units), use the ``ds.quan`` (for\nindividual variables) and ``ds.arr`` (for arrays):\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(filename)\n    one_Mpc = ds.quan(1, \"Mpc\")\n    x_vector = ds.arr([1, 0, 0], \"code_length\")\n\nYou can then naturally exploit the units system:\n\n.. code-block:: python\n\n    print(\"One Mpc in code_units:\", one_Mpc.in_units(\"code_length\"))\n    print(\"One Mpc in AU:\", one_Mpc.in_units(\"AU\"))\n    print(\"One Mpc in comoving kpc:\", one_Mpc.in_units(\"kpccm\"))\n\nFor more information about unit conversion, see :ref:`units`.\n\n.. _accessing-unitless-data:\n\nHow do I access the unitless data in a YTQuantity or YTArray?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhile there are numerous benefits to having units tied to individual\nquantities in yt, they can also produce issues when simply trying to combine\nYTQuantities with numpy arrays or native python floats that lack units.  A\nsimple example of this is::\n\n    # Create a YTQuantity that is 1 kpc in length and tied to the units of\n    # dataset ds\n    >>> x = ds.quan(1, 'kpc')\n\n    # Try to add this to some non-dimensional quantity\n    >>> print(x + 1)\n\n    YTUnitOperationError: The addition operator for YTArrays with units (kpc) and (1) is not well defined.\n\nThe solution to this means using the YTQuantity and YTArray objects for all\nof one's computations, but this isn't always feasible.  A quick fix for this\nis to just grab the unitless data out of a YTQuantity or YTArray object with\nthe ``value`` and ``v`` attributes, which return a copy, or with the ``d``\nattribute, which returns the data itself:\n\n.. code-block:: python\n\n    x = ds.quan(1, \"kpc\")\n    x_val = x.v\n    print(x_val)\n\n    array(1.0)\n\n    # Try to add this to some non-dimensional quantity\n    print(x + 1)\n\n    2.0\n\nFor more information about this functionality with units, see :ref:`units`.\n\nFields\n------\n\n.. _faq-handling-log-vs-linear-space:\n\nHow do I modify whether or not yt takes the log of a particular field?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nyt sets up defaults for many fields for whether or not a field is presented\nin log or linear space. To override this behavior, you can modify the\n``field_info`` dictionary.  For example, if you prefer that ``density`` not be\nlogged, you could type:\n\n.. code-block:: python\n\n    ds = load(\"my_data\")\n    ds.index\n    ds.field_info[\"gas\", \"density\"].take_log = False\n\nFrom that point forward, data products such as slices, projections, etc., would\nbe presented in linear space. Note that you have to instantiate ds.index before\nyou can access ds.field info.  For more information see the documentation on\n:ref:`fields` and :ref:`creating-derived-fields`.\n\n.. _faq-new-field:\n\nI added a new field to my simulation data, can yt see it?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nYes! yt identifies all the fields in the simulation's output file\nand will add them to its ``field_list`` even if they aren't listed in\n:ref:`field-list`. These can then be accessed in the usual manner. For\nexample, if you have created a field for the potential called\n``PotentialField``, you could type:\n\n.. code-block:: python\n\n   ds = load(\"my_data\")\n   ad = ds.all_data()\n   potential_field = ad[\"PotentialField\"]\n\nThe same applies to fields you might derive inside your yt script\nvia :ref:`creating-derived-fields`. To check what fields are\navailable, look at the properties ``field_list`` and ``derived_field_list``:\n\n.. code-block:: python\n\n   print(ds.field_list)\n   print(ds.derived_field_list)\n\nor for a more legible version, try:\n\n.. code-block:: python\n\n   for field in ds.derived_field_list:\n       print(field)\n\n.. _faq-add-field-diffs:\n\nWhat is the difference between ``yt.add_field()`` and ``ds.add_field()``?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe global ``yt.add_field()``\n(:meth:`~yt.fields.field_info_container.FieldInfoContainer.add_field`)\nfunction is for adding a field for every subsequent dataset that is loaded\nin a particular python session, whereas ``ds.add_field()``\n(:meth:`~yt.data_objects.static_output.Dataset.add_field`) will only add it\nto dataset ``ds``.\n\nData Objects\n------------\n\n.. _ray-data-ordering:\n\nWhy are the values in my Ray object out of order?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nUsing the Ray objects\n(:class:`~yt.data_objects.selection_data_containers.YTOrthoRay` and\n:class:`~yt.data_objects.selection_data_containers.YTRay`) with AMR data\ngives non-contiguous cell information in the Ray's data array. The\nhigher-resolution cells are appended to the end of the array.  Unfortunately,\ndue to how data is loaded by chunks for data containers, there is really no\neasy way to fix this internally.  However, there is an easy workaround.\n\nOne can sort the ``Ray`` array data by the ``t`` field, which is the value of\nthe parametric variable that goes from 0 at the start of the ray to 1 at the\nend. That way the data will always be ordered correctly. As an example you can:\n\n.. code-block:: python\n\n    my_ray = ds.ray(...)\n    ray_sort = np.argsort(my_ray[\"t\"])\n    density = my_ray[\"gas\", \"density\"][ray_sort]\n\nThere is also a full example in the :ref:`manual-line-plots` section of the\ndocs.\n\nDeveloping\n----------\n\n.. _making-a-PR:\n\nSomeone asked me to make a Pull Request (PR) to yt.  How do I do that?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA pull request is the action by which you contribute code to yt.  You make\nmodifications in your local copy of the source code, then *request* that\nother yt developers review and accept your changes to the main code base.\nFor a full description of the steps necessary to successfully contribute\ncode and issue a pull request (or manage multiple versions of the source code)\nplease see :ref:`sharing-changes`.\n\n.. _making-an-issue:\n\nSomeone asked me to file an issue or a bug report for a bug I found.  How?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSee :ref:`reporting-a-bug` and :ref:`sharing-changes`.\n\nMiscellaneous\n-------------\n\n.. _getting-sample-data:\n\nHow can I get some sample data for yt?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nMany different sample datasets can be found at https://yt-project.org/data/ .\nThese can be downloaded, unarchived, and they will each create their own\ndirectory.  It is generally straight forward to load these datasets, but if\nyou have any questions about loading data from a code with which you are\nunfamiliar, please visit :ref:`loading-data`.\n\nTo make things easier to load these sample datasets, you can add the parent\ndirectory to your downloaded sample data to your *yt path*.\nIf you set the option ``test_data_dir``, in the section ``[yt]``,\nin ``~/.config/yt/yt.toml``, yt will search this path for them.\n\nThis means you can download these datasets to ``/big_drive/data_for_yt`` , add\nthe appropriate item to ``~/.config/yt/yt.toml``, and no matter which directory you are\nin when running yt, it will also check in *that* directory.\n\nIn many cases, these are also available using the ``load_sample`` command,\ndescribed in :ref:`loading-sample-data`.\n\n\n.. _faq-scroll-up:\n\nI can't scroll-up to previous commands inside python\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf the up-arrow key does not recall the most recent commands, there is\nprobably an issue with the readline library. To ensure the yt python\nenvironment can use readline, run the following command:\n\n.. code-block:: bash\n\n   $ python -m pip install gnureadline\n\n.. _faq-old-data:\n\n.. _faq-log-level:\n\nHow can I change yt's log level?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nyt's default log level is ``INFO``. However, you may want less voluminous logging,\nespecially if you are in an IPython notebook or running a long or parallel script.\nOn the other hand, you may want it to output a lot more, since you can't figure out\nexactly what's going wrong, and you want to output some debugging information.\nThe default yt log level can be changed using the :ref:`configuration-file`,\neither by setting it in the ``$HOME/.config/yt/yt.toml`` file:\n\n.. code-block:: bash\n\n   $ yt config set yt log_level 10  # This sets the log level to \"DEBUG\"\n\nwhich would produce debug (as well as info, warning, and error) messages, or at runtime:\n\n.. code-block:: python\n\n   yt.set_log_level(\"error\")\n\nThis is the same as doing:\n\n.. code-block:: python\n\n   yt.set_log_level(40)\n\nwhich in this case would suppress everything below error messages. For reference,\nthe numerical values corresponding to different log levels are:\n\n.. csv-table::\n   :header: Level, Numeric Value\n   :widths: 10, 10\n\n   ``CRITICAL``,50\n   ``ERROR``,40\n   ``WARNING``,30\n   ``INFO``,20\n   ``DEBUG``,10\n   ``NOTSET``,0\n\nCan I always load custom data objects, fields, quantities, and colormaps with every dataset?\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe :ref:`plugin-file` provides a means for always running custom code whenever\nyt is loaded up.  This custom code can be new data objects, or fields, or\ncolormaps, which will then be accessible in any future session without having\nmodified the source code directly.  See the description in :ref:`plugin-file`\nfor more details.\n\nHow do I cite yt?\n^^^^^^^^^^^^^^^^^\n\nIf you use yt in a publication, we'd very much appreciate a citation!  You\nshould feel free to cite the `ApJS paper\n<https://ui.adsabs.harvard.edu/abs/2011ApJS..192....9T>`_ with the following BibTeX\nentry: ::\n\n   @ARTICLE{2011ApJS..192....9T,\n      author = {{Turk}, M.~J. and {Smith}, B.~D. and {Oishi}, J.~S. and {Skory}, S. and\n   \t{Skillman}, S.~W. and {Abel}, T. and {Norman}, M.~L.},\n       title = \"{yt: A Multi-code Analysis Toolkit for Astrophysical Simulation Data}\",\n     journal = {The Astrophysical Journal Supplement Series},\n   archivePrefix = \"arXiv\",\n      eprint = {1011.3514},\n    primaryClass = \"astro-ph.IM\",\n    keywords = {cosmology: theory, methods: data analysis, methods: numerical },\n        year = 2011,\n       month = jan,\n      volume = 192,\n         eid = {9},\n       pages = {9},\n         doi = {10.1088/0067-0049/192/1/9},\n      adsurl = {https://ui.adsabs.harvard.edu/abs/2011ApJS..192....9T},\n     adsnote = {Provided by the SAO/NASA Astrophysics Data System}\n   }\n"
  },
  {
    "path": "doc/source/help/index.rst",
    "content": ".. _asking-for-help:\n\nWhat to do if you run into problems\n===================================\n\nIf you run into problems with yt, there are a number of steps to follow\nto come to a solution.  The first handful of options are things you can do\non your own, but if those don't yield results, we have provided a number of\nways to connect with our community of users and developers to solve the\nproblem together.\n\nTo summarize, here are the steps in order:\n\n.. contents::\n   :depth: 1\n   :local:\n   :backlinks: none\n\n.. _dont-panic:\n\nDon't panic and don't give up\n-----------------------------\n\nThis may seem silly, but it's effective.  While yt is a robust code with\nlots of functionality, like all actively-developed codes sometimes there\nare bugs.  Chances are good that your problems have a quick fix, either\nbecause someone encountered it before and fixed it, the documentation is\nout of date, or some other simple solution.  Don't give up!  We want\nto help you succeed!\n\n.. _update-the-code:\n\nUpdate to the latest version\n----------------------------\n\nSometimes the pace of development is pretty fast on yt, particularly in the\ndevelopment branch, so a fix to your problem may have already been developed by\nthe time you encounter it.  Many users' problems can simply be corrected by\nupdating to the latest version of the code and/or its dependencies. If you have\ninstalled the latest stable release of yt then you should update yt using the\npackage manager appropriate for your python installation. See :ref:updating.\n\n.. _search-the-documentation:\n\nSearch the documentation, FAQ, and mailing lists\n------------------------------------------------\n\nThe documentation has a lot of the answers to everyday problems.  This doesn't\nmean you have to read all of the docs top-to-bottom, but you should at least\nrun a search to see if relevant topics have been answered in the docs.  Click\non the search field to the right of this window and enter your text.  Another\ngood place to look for answers in the documentation is our :ref:`faq` page.\n\nOK, so there was no obvious solution to your problem in the documentation.\nIt is possible that someone else experienced the problem before you did, and\nwrote to the mailing list about it.  You can easily check the mailing list\narchive with the other search field to the right of this window (or you can\nuse the search field below).\n\n.. raw:: html\n\n   <form action=\"http://www.google.com/cse\" id=\"cse-search-box\">\n     <div>\n       <input type=\"hidden\" name=\"cx\" value=\"010428198273461986377:xyfd9ztykqm\" />\n       <input type=\"hidden\" name=\"ie\" value=\"UTF-8\" />\n       <input type=\"text\" name=\"q\" size=\"31\" />\n       <input type=\"submit\" name=\"sa\" value=\"Search\" />\n     </div>\n   </form>\n   <script type=\"text/javascript\" src=\"http://www.google.com/cse/brand?form=cse-search-box&lang=en\"></script>\n\n.. _look-at-the-source:\n\nLook at the source code\n-----------------------\n\nWe've done our best to make the source clean, and it is easily searchable from\nyour computer.\n\nIf you have not done so already (see :ref:`install-from-source`), clone a copy\nof the yt git repository and make it the 'active' installation by doing\n\n\nOnce inside the yt git repository, you can then search for the class,\nfunction, or keyword which is giving you problems with ``grep -r *``, which will\nrecursively search throughout the code base.  (For a much faster and cleaner\nexperience, we recommend ``grin`` instead of ``grep -r *``.  To install ``grin``\nwith python, just type ``python -m pip install grin``.)\n\nSo let's say that ``SlicePlot`` is giving you problems still, and you want to\nlook at the source to figure out what is going on.\n\n.. code-block:: bash\n\n  $ cd $YT_GIT/yt\n  $ grep -r SlicePlot *         (or $ grin SlicePlot)\n\nThis will print a number of locations in the yt source tree where ``SlicePlot``\nis mentioned.  You can now follow-up on this and open up the files that have\nreferences to ``SlicePlot`` (particularly the one that defines SlicePlot) and\ninspect their contents for problems or clarification.\n\n.. _isolate_and_document:\n\nIsolate and document your problem\n---------------------------------\n\nAs you gear up to take your question to the rest of the community, try to distill\nyour problem down to the fewest number of steps needed to produce it in a\nscript.  This can help you (and us) to identify the basic problem.  Follow\nthese steps:\n\n* Identify what it is that went wrong, and how you knew it went wrong.\n* Put your script, errors, inputs and outputs online:\n\n  * ``$ yt pastebin script.py`` - pastes script.py online\n  * ``$ yt upload_image image.png`` - pastes image online\n  * ``$ yt upload my_input.tar`` - pastes my_input.tar online\n\n* Identify which version of the code you’re using.\n\n  * ``$ yt version`` - provides version information, including changeset hash\n\nIt may be that through the mere process of doing this, you end up solving\nthe problem!\n\n.. _irc:\n\nGo on Slack to ask a question\n-----------------------------\n\nIf you want a fast, interactive experience, you could try jumping into our Slack\nto get your questions answered in a chatroom style environment.\n\nTo join our slack channel you will need to request an invite by going to\nhttps://yt-project.org/development.html, click the \"Join as @ Slack!\" button, and\nfill out the form. You will get an invite as soon as an administrator approves\nyour request.\n\n.. _mailing-list:\n\nAsk the mailing list\n--------------------\n\nIf you still haven't yet found a solution, feel free to\nwrite to the mailing list regarding your problems.  There are two mailing lists,\n`yt-users <https://mail.python.org/archives/list/yt-users@python.org/>`_ and\n`yt-dev <https://mail.python.org/archives/list/yt-dev@python.org/>`_.  The\nfirst should be used for asking for help, suggesting features and so on, and\nthe latter has more chatter about the way the code is developed and discussions\nof changes and feature improvements.\n\nIf you email ``yt-users`` asking for help, remember to include the information\nabout your problem you identified in :ref:`this step <isolate_and_document>`.\n\nWhen you email the list, providing this information can help the developers\nunderstand what you did, how it went wrong, and any potential fixes or similar\nproblems they have seen in the past.  Without this context, it can be very\ndifficult to help out!\n\n.. _reporting-a-bug:\n\nSubmit a bug report\n-------------------\n\nIf you have gone through all of the above steps, and you're still encountering\nproblems, then you have found a bug.  To submit a bug report, you can either\ndirectly create one through the GitHub `web interface\n<https://github.com/yt-project/yt/issues/new>`_.  Alternatively, email the\n``yt-users`` mailing list and we will construct a new ticket in your stead.\nRemember to include the information about your problem you identified in\n:ref:`this step <isolate_and_document>`.\n\nSpecial Issues\n--------------\n\nInstallation Issues\n^^^^^^^^^^^^^^^^^^^\n\nIf you are having installation issues and nothing from the\n:ref:`installation instructions <installing-yt>` seems to work, you should\n*definitely* email the ``yt-users`` email list.  You should provide information\nabout the host, the version of the code you are using, and the output of\n``yt_install.log`` from your installation.  We are very interested in making\nsure that yt installs everywhere!\n\nCustomization and Scripting Issues\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you have customized yt in some way, or created your own plugins file (as\ndescribed in :ref:`plugin-file`) then it may be necessary to supply users\nwilling to help you (or the mailing list) with both your patches to the\nsource, the plugin file, and perhaps even the datafile on which you're running.\n"
  },
  {
    "path": "doc/source/index.rst",
    "content": "yt Overview\n===========\n\nyt is a community-developed analysis and visualization toolkit for\nvolumetric data.  yt has been applied mostly to astrophysical simulation data,\nbut it can be applied to many different types of data including seismology,\nradio telescope data, weather simulations, and nuclear engineering simulations.\nyt is developed in Python under the open-source model.\n\nyt supports :ref:`many different code formats <code-support>`, and we provide\n:ref:`sample data for each format <getting-sample-data>` with\n:ref:`instructions on how to load and examine each data type <examining-data>`.\n\n\n\nTable of Contents\n-----------------\n\n.. raw:: html\n\n   <table class=\"contentstable\" align=\"left\">\n\n     <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"intro/index.html\">Introduction to yt</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">What does yt offer? How can I use it? How to think in yt?</p>\n       </td>\n     </tr>\n      <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"yt4differences.html\">yt 4.0</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">How yt-4.0 differs from past versions</p>\n       </td>\n     </tr>\n     <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"yt3differences.html\">yt 3.0</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">How yt-3.0 differs from past versions</p>\n       </td>\n     </tr>\n      <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"installing.html\">Installation</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">Getting, installing, and updating yt</p>\n       </td>\n     </tr>\n     <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"quickstart/index.html\">yt Quickstart</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">Demonstrations of what yt can do</p>\n       </td>\n     </tr>\n     <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"examining/index.html\">Loading and Examining Data</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">How to load all dataset types in yt and examine raw data</p>\n       </td>\n     </tr>\n    <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"cookbook/index.html\">The Cookbook</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">Example recipes for how to accomplish a variety of tasks</p>\n       </td>\n     </tr>\n     <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"visualizing/index.html\">Visualizing Data</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">Make plots, projections, volume renderings, movies, and more</p>\n       </td>\n     </tr>\n     <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"analyzing/index.html\">General Data Analysis</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">The nuts and bolts of manipulating yt datasets</p>\n       </td>\n     </tr>\n     <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"analyzing/domain_analysis/index.html\">Domain-Specific Analysis</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">Astrophysical analysis, clump finding, cosmology calculations, and more</p>\n       </td>\n     </tr>\n      <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"developing/index.html\">Developer Guide</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">Catering yt to work for your exact use case</p>\n       </td>\n     </tr>\n     <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"reference/index.html\">Reference Materials</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">Lists of fields, quantities, classes, functions, and more</p>\n       </td>\n     </tr>\n     <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"faq/index.html\">Frequently Asked Questions</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">Solutions for common questions and problems</p>\n       </td>\n     </tr>\n      <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"help/index.html\">Getting help</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">What to do if you run into problems</p>\n       </td>\n     </tr>\n      <tr valign=\"top\">\n       <td width=\"25%\">\n         <p>\n           <a href=\"about/index.html\">About yt</a>\n         </p>\n       </td>\n       <td width=\"75%\">\n         <p class=\"linkdescr\">What is yt?</p>\n       </td>\n     </tr>\n   </table>\n\n.. toctree::\n   :hidden:\n\n   intro/index\n   installing\n   yt Quickstart <quickstart/index>\n   yt4differences\n   yt3differences\n   cookbook/index\n   visualizing/index\n   analyzing/index\n   analyzing/domain_analysis/index\n   examining/index\n   developing/index\n   reference/index\n   faq/index\n   Getting Help <help/index>\n   about/index\n"
  },
  {
    "path": "doc/source/installing.rst",
    "content": ".. _installing-yt:\n\nGetting and Installing yt\n=========================\n\n.. contents::\n   :depth: 2\n   :local:\n   :backlinks: none\n\n.. _getting-yt:\n\nDisclaimer\n----------\n\nThe Python ecosystem offers many viable tools to setup isolated\nPython environments, including but not restricted to\n\n- `venv <https://docs.python.org/3/library/venv.html>`_ (part of the Python standard library)\n- `Anaconda/conda <https://docs.conda.io/en/latest/index.html>`_\n- `virtualenv <https://virtualenv.pypa.io/en/latest/>`_\n\nWe strongly recommend you choose and learn one. However, it is beyond the\nscope of this page to cover every situation.\n\nWe will show you how to install a stable release or from source, using conda\nor pip, and we will *assume* that you do so in an isolated environment.\n\nAlso note that each yt release supports a limited range of Python versions.\nHere's a summary for most recent releases\n\n+------------+------------+----------------+-----------------+\n| yt release | Python 2.7 | Python3 min    | Python3 max     |\n+============+============+================+=================+\n| 4.4.x      | no         | 3.10.3         | 3.13 (expected) |\n+------------+------------+----------------|-----------------|\n| 4.3.x      | no         | 3.9.2          | 3.12            |\n+------------+------------+----------------+-----------------+\n| 4.2.x      | no         | 3.8            | 3.11            |\n+------------+------------+----------------+-----------------+\n| 4.1.x      | no         | 3.7            | 3.11            |\n+------------+------------+----------------+-----------------+\n| 4.0.x      | no         | 3.6            | 3.10            |\n+------------+------------+----------------+-----------------+\n| 3.6.x      | no         | 3.5            | 3.8             |\n+------------+------------+----------------+-----------------+\n| 3.5.x      | yes        | 3.4            | 3.5             |\n+------------+------------+----------------+-----------------+\n\nMinimum Python versions are strict requirements, while maximum\nindicates the newest version for which the yt development team\nprovides pre-compiled binaries via PyPI and conda-forge.\nIt may be possible to compile existing yt versions under more\nrecent Python versions, though this is never guaranteed.\n\nyt also adheres to `SPEC 0 <https://scientific-python.org/specs/spec-0000/>`_ as a soft\nguideline for our support policy of core dependencies (Python, numpy, matplotlib ...).\n\n\nGetting yt\n----------\n\nIn this document we describe several methods for installing yt. The method that\nwill work best for you depends on your precise situation:\n\n* If you need a stable build, see :ref:`install-stable`\n\n* If you want to build the development version of yt see :ref:`install-from-source`.\n\n.. _install-stable:\n\nInstalling a stable release\n+++++++++++++++++++++++++++\n\nThe latest stable release can be obtained from PyPI with pip\n\n.. code-block:: bash\n\n  $ python -m pip install --user yt\n\n\nOr using the Anaconda/Miniconda Python distributions\n\n.. code-block:: bash\n\n  $ conda install --channel conda-forge yt\n\n\n\n.. _install-additional:\n\nAdditional requirements (pip only)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nyt knows about several data file formats. In many cases (e.g. HDF5), additional\ndependencies are needed to enable parsing, that are not installed with yt by default.\nIn order to install all required packages for a specific format alongside yt,\none can specify them as, for instance\n\n.. code-block:: bash\n\n  $ python -m pip install --user \"yt[ramses]\"\n\nExtra requirements can be combined, separated by commas (say ``yt[ramses,enzo_e]``).\nNote that all format names are normalized to lower case.\n\n\n.. _install-from-source:\n\nBuilding from source\n++++++++++++++++++++\n\nThere are a couple ways to build yt from source with e.g., ``pip``, all of which\nrequire a C compiler (such as ``gcc`` or ``clang``).\n\nyt is primarily distributed on PyPI, in the form of pre-built binaries (wheels).\nSince version 4.5.0, these binaries are optimized for portability accross Python versions.\n\nIf you need a stable release, but pre-built binaries are not available for your platform,\n``pip install`` will automatically select a source distribution and compile the package\nfor you. You may opt-into this behavior deliberately by specifying the ``--no-binary``\nflag, in which case the resulting installation might be slightly more performant, because\nit will be compiled specifically for your Python version.\nIf, on the other hand, you *specifically* want a portable binary (as the ones we provide on\nPyPI), this is achieved by setting ``YT_LIMITED_API=1`` in your build environment.\n\nYou may also want to build yt directly from the github repository (which requires ``git``),\nfor instance if you need the latest development version, or if you want to contribute to\nthe project. Run\n.. code-block:: bash\n\n  $ git clone https://github.com/yt-project/yt\n  $ cd yt\n  $ python -m pip install --user -e .\n\n\n.. _optional-runtime-deps:\n\nLeveraging optional yt runtime dependencies\n+++++++++++++++++++++++++++++++++++++++++++\n\nSome relatively heavy runtime dependencies are not included in your build by\ndefault as they may be irrelevant in your workflow. Common examples include\nh5py, mpi4py, astropy or scipy. yt implements a on-demand import mechanism that\nallows it to run even when they are not installed *until they're needed*, in\nwhich case it will raise an ``ImportError``, pointing to the missing requirement.\n\nIf you wish to get everything from the start, you may specify it when building\nyt as by appending ``[full]`` to the target name when calling pip, i.e.,\n\n.. code-block:: bash\n\n  $ # stable release\n  $ python -m pip install --user yt[full]\n  $ # from source\n  $ python -m pip install --user -e .[full]\n\n\n.. _testing-installation:\n\nTesting Your Installation\n+++++++++++++++++++++++++\n\nTo make sure everything is installed properly, try running yt at\nthe command line:\n\n.. code-block:: bash\n\n  $ python -c \"import yt\"\n\nIf this runs without raising errors, you have successfully installed yt. Congratulations!\n\nOtherwise, read the error message carefully and follow any instructions it gives\nyou to resolve the issue. Do not hesitate to :ref:`contact us <asking-for-help>`\nso we can help you figure it out.\n\n\n\n.. _updating:\n\nUpdating yt\n+++++++++++\n\nFor pip-based installations:\n\n.. code-block:: bash\n\n  $ python -m pip install --upgrade yt\n\n\nFor conda-based installations:\n\n.. code-block:: bash\n\n  $ conda update yt\n\n\nFor git-based installations (yt installed from source), we provide the following\none-liner facility\n\n.. code-block:: bash\n\n  $ yt update\n\nThis will pull any changes from GitHub, and recompile yt if necessary.\n\n\nUninstalling yt\n+++++++++++++++\n\nIf you've installed via pip (either from Pypi or from source)\n\n.. code-block:: bash\n\n  $ python -m pip uninstall yt\n\nOr with conda\n\n.. code-block:: bash\n\n  $ conda uninstall yt\n\n\nTroubleShooting\n---------------\n\nIf you are unable to locate the yt executable (i.e. executing ``yt version``\nat the bash command line fails), then you likely need to add the\n``$HOME/.local/bin`` (or the equivalent on your OS) to your PATH. Some Linux\ndistributions do not include this directory in the default search path.\n\n\nAdditional Resources\n--------------------\n\n.. _distro-packages:\n\nyt Distribution Packages\n++++++++++++++++++++++++\n\nSome operating systems have yt pre-built packages that can be installed with the\nsystem package manager. Note that the packages in some of these distributions\nmay be out of date.\n\n.. note::\n\n  Since the third-party packages listed below are not officially supported by\n  yt developers, support should not be sought out on the project mailing lists\n  or Slack channels.  All support requests related to these packages should be\n  directed to their official maintainers.\n\nWhile we recommended installing yt with either pip or conda, a number of\nthird-party packages exist for the distributions listed below.\n\n.. image:: https://repology.org/badge/vertical-allrepos/python:yt.svg?header=yt%20packaging%20status\n    :target: https://repology.org/project/python:yt/versions\n\n\nIntel distribution for Python\n+++++++++++++++++++++++++++++\n\nA viable alternative to the installation based on Anaconda is the use of the\n`Intel Distribution for Python\n<https://software.intel.com/en-us/distribution-for-python>`_. For `Parallel\nComputation\n<http://yt-project.org/docs/dev/analyzing/parallel_computation.html>`_ on Intel\narchitectures, especially on supercomputers, a large `performance and\nscalability improvement <https://arxiv.org/abs/1910.07855>`_ over several common\ntasks has been demonstrated.   See `Parallel Computation\n<http://yt-project.org/docs/dev/analyzing/parallel_computation.html>`_ for a\ndiscussion on using yt in parallel. Leveraing this specialized distribution for\nyt requires that you install some dependencies from the intel conda channel\nbefore installing yt itself, like so\n\n.. code-block:: bash\n\n  $ conda install -c intel numpy scipy mpi4py cython git sympy ipython matplotlib netCDF4\n  $ python -m install --user yt\n"
  },
  {
    "path": "doc/source/intro/index.rst",
    "content": "Introduction to yt\n==================\n\nHerein, we present a brief introduction to yt's capabilities and\ninfrastructure with numerous links to relevant portions of the documentation\non each topic.  It is our hope that readers will not only gain insight to\nwhat can be done with yt, but also they will learn how to *think in yt* to\nsolve science questions, learn some of the yt jargon, and figure out\nwhere to go in the docs for help.\n\n.. contents::\n   :depth: 2\n   :local:\n   :backlinks: none\n\nFields\n^^^^^^\n\nyt is an analysis toolkit operating on multidimensional datasets for\n:ref:`a variety of data formats <code-support>`.  It represents quantities\nvarying over a multidimensional space as :ref:`fields <fields>` such as gas density,\ngas temperature, etc.  Many fields are defined when yt :ref:`loads the external\ndataset <examining-data>` into \"native fields\" as defined by individual\nfrontends for each code format.  However, yt additionally\ncreates many \"derived fields\" by manipulating and combining\nnative fields.  yt comes with a large existing :ref:`set of derived fields\n<field-list>`, but you can also :ref:`create your own\n<creating-derived-fields>`.\n\nObjects\n^^^^^^^\n\nCentral to yt's infrastructure are :ref:`data objects <data-objects>`,\nwhich act as a means of :ref:`filtering data <filtering-data>` based on\n:ref:`spatial location <geometric-objects>` (e.g. lines, spheres, boxes,\ncylinders), based on :ref:`field values <collection-objects>` (e.g. all gas >\n10^6 K), or for :ref:`constructing new data products <construction-objects>`\n(e.g. projections, slices, isosurfaces).  Furthermore, yt can calculate\nthe :ref:`bulk quantities <derived-quantities>` associated with these data\nobjects (e.g. total mass, bulk velocity, angular momentum).\n\nGeneral Analysis\n^^^^^^^^^^^^^^^^\n\nThe documentation section on :ref:`analyzing data <analyzing>` has a full\ndescription of :ref:`fields <fields>`, :ref:`data objects <data-objects>`,\nand :ref:`filters <filtering-data>`.  It also includes an explanation of how\nthe :ref:`units system <units>` works to tag every individual field and\nquantity with a physical unit (e.g. cm, AU, kpc, Mpc, etc.), and it describes\nways of analyzing multiple chronological data outputs from the same underlying\ndataset known as :ref:`time series <time-series-analysis>`.  Lastly, it includes\ninformation on how to enable yt to operate :ref:`in parallel over multiple\nprocessors simultaneously <parallel-computation>`.\n\nDatasets can be analyzed by simply :ref:`examining raw source data\n<low-level-data-inspection>`, or they can be processed in a number of ways\nto extract relevant information and to explore the data including\n:ref:`visualizing data <visualizing>`.\n\nVisualization\n^^^^^^^^^^^^^\n\nyt provides many tools for :ref:`visualizing data <visualizing>`, and herein\nwe highlight a few of them.  yt can create :ref:`slice plots <slice-plots>`,\nwherein a three-dimensional volume (or any of the :ref:`data objects\n<data-objects>`) is *sliced* by a plane to return the two-dimensional field\ndata intersected by that plane.  Similarly, yt can generate\n:ref:`line queries (i.e. rays) <generating-line-queries>` of a single\nline intersecting a three-dimensional dataset.  :ref:`Projection plots\n<projection-plots>` are generated by projecting a three-dimensional volume\ninto two dimensions either :ref:`by summing or integrating <projection-types>`\nthe field along each pixel's line of sight with or without a weighting field.\nSlices, projections, and rays can be made to align with the primary axes of\nthe simulation (e.g. x,y,z) or at any arbitrary angle throughout the volume.\nFor these operations, a number of :ref:`\"callbacks\" <callbacks>` exist that\nwill annotate your figures with field contours, velocity vectors, particle and\nhalo positions, streamlines, simple shapes, and text.\n\nyt can examine correlations between two or three fields simultaneously with\n:ref:`profile plots <how-to-make-1d-profiles>` and :ref:`phase plots\n<how-to-make-2d-profiles>`.  By querying field data for two separate fields\nat each position in your dataset or :ref:`data object <data-objects>`, yt\ncan show the relationship between those two fields in a :ref:`profile plot\n<how-to-make-1d-profiles>` (e.g. average gas density as a function radius).\nSimilarly, a :ref:`phase plot <how-to-make-2d-profiles>` correlates two fields\nas described above, but it weights those fields by a third field.  Phase plots\ncommonly use mass as the weighting field and are oftentimes used to relate\ngas density and temperature.\n\nMore advanced visualization functionality in yt includes generating\n:ref:`streamlines <streamlines>` to track the velocity flow in your datasets,\ncreating photorealistic isocontour images of your data called :ref:`volume\nrenderings <volume_rendering>`, and :ref:`visualizing isosurfaces in an external\ninteractive tool <surfaces>`.  yt even has a special web-based tool for\nexploring your data with a :ref:`google-maps-like interface <mapserver>`.\n\nExecuting and Scripting yt\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nyt is written almost entirely in python and it functions as a library\nthat you can import into your python scripts.  There is full docstring\ndocumentation for all of the major classes and functions in the :ref:`API docs\n<api-reference>`.  yt has support for running in IPython and for running\nIPython notebooks for fully interactive sessions both\nlocally and on remote supercomputers.  yt also has a number of ways it can\nbe :ref:`executed at the command line <command-line>` for simple tasks like\nautomatically loading a dataset, updating the yt sourcecode, starting an\nIPython notebook, or uploading scripts and images to public locations.  There\nis an optional :ref:`yt configuration file <configuration-file>` you can\nmodify for controlling local settings like color, logging, output settings.\nThere is also an optional :ref:`yt plugin file <plugin-file>` you can create\nto automatically load certain datasets, custom derived fields, derived\nquantities, and more.\n\nCookbook and Quickstart\n^^^^^^^^^^^^^^^^^^^^^^^\n\nyt contains a number of example recipes for demonstrating simple and complex\ntasks in :ref:`the cookbook <cookbook>` including many of the topics discussed\nabove.  The cookbook also contains :ref:`more lengthy notebooks\n<example-notebooks>` to demonstrate more sophisticated machinery on a variety\nof topics.  If you're new to yt and you just want to see a broad demonstration\nof some of the things yt can do, check out the\n:ref:`yt quickstart <quickstart>`.\n\nDeveloping in yt\n^^^^^^^^^^^^^^^^\n\nyt is an open source development project, with only scientist-developers\nlike you to support it, add code, add documentation, etc.  As such, we welcome\nmembers of the public to join :ref:`our community <who-is-yt>` by contributing\ncode, bug reports, documentation, and helping to :ref:`support the code in a\nnumber of ways <getting-involved>`.  Sooner or later, you'll want to\n:ref:`add your own derived field <creating-derived-fields>`, :ref:`data object\n<creating-objects>`, :ref:`code frontend <creating_frontend>` or :ref:`make\nyt compatible with an external code <external-analysis-tools>`.  We have\ndetailed instructions on how to :ref:`contribute code <contributing-code>`\n:ref:`documentation <documentation>`, and :ref:`tests <testing>`, and how\nto :ref:`debug this code <debug-drive>`.\n\nGetting Help\n^^^^^^^^^^^^\n\nWe have all been there, where something is going wrong and we cannot\nunderstand why.  Check out our :ref:`frequently asked questions <faq>` and\nthe documentation section :ref:`asking-for-help` to get solutions for your\nproblems.\n\nGetting Started\n^^^^^^^^^^^^^^^\n\nWe have detailed :ref:`installation instructions <installing-yt>`\nand support for a number of platforms including Unix, Linux, MacOS, and\nWindows.  If you are new to yt, check out the :ref:`yt Quickstart\n<quickstart>` and the :ref:`cookbook <cookbook>` for a demonstration of yt's\ncapabilities.  If you previously used yt version 2, check out our guide\non :ref:`how to make your scripts work in yt 3 <yt3differences>`.  So what\nare you waiting for?  Good luck and welcome to the yt community.\n"
  },
  {
    "path": "doc/source/quickstart/1)_Introduction.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Welcome to the yt quickstart!\\n\",\n    \"\\n\",\n    \"In this brief tutorial, we'll go over how to load up data, analyze things, inspect your data, and make some visualizations.\\n\",\n    \"\\n\",\n    \"Our documentation page can provide information on a variety of the commands that are used here, both in narrative documentation as well as recipes for specific functionality in our cookbook.  The documentation exists at https://yt-project.org/doc/.  If you encounter problems, look for help here: https://yt-project.org/doc/help/index.html.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Acquiring the datasets for this tutorial\\n\",\n    \"\\n\",\n    \"If you are executing these tutorials interactively, you need some sample datasets on which to run the code.  You can download these datasets at https://yt-project.org/data/, or you can use the built-in yt sample data loader (using [pooch](https://www.fatiando.org/pooch/latest/api/index.html) under the hood) to automatically download the data for you.\\n\",\n    \"\\n\",\n    \"The datasets necessary for each lesson are noted next to the corresponding tutorial, and by default it will use the pooch-based dataset downloader.  If you would like to supply your own paths, you can choose to do so.\\n\",\n    \"\\n\",\n    \"## Using the Automatic Downloader\\n\",\n    \"\\n\",\n    \"For the purposes of this tutorial, or whenever you want to use sample data, you can use the `load_sample` command to utilize the pooch auto-downloader.  For instance:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"ds = yt.load_sample(\\\"IsolatedGalaxy\\\")\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"## Using manual loading\\n\",\n    \"\\n\",\n    \"The way you will *most frequently* interact with `yt` is using the standard `load` command.  This accepts a path and optional arguments.  For instance:\\n\",\n    \"\\n\",\n    \"```python\\n\",\n    \"ds = yt.load(\\\"IsolatedGalaxy/galaxy0030/galaxy0030\\\")\\n\",\n    \"```\\n\",\n    \"\\n\",\n    \"would load the `IsolatedGalaxy` dataset by supplying the full path to the parameter file.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## What's Next?\\n\",\n    \"\\n\",\n    \"The Notebooks are meant to be explored in this order:\\n\",\n    \"\\n\",\n    \"1. Introduction (this file!)\\n\",\n    \"2. Data Inspection (IsolatedGalaxy dataset)\\n\",\n    \"3. Simple Visualization (enzo_tiny_cosmology & Enzo_64 datasets)\\n\",\n    \"4. Data Objects and Time Series (IsolatedGalaxy dataset)\\n\",\n    \"5. Derived Fields and Profiles (IsolatedGalaxy dataset)\\n\",\n    \"6. Volume Rendering (IsolatedGalaxy dataset)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "doc/source/quickstart/2)_Data_Inspection.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Starting Out and Loading Data\\n\",\n    \"\\n\",\n    \"We're going to get started by loading up yt.  This next command brings all of the libraries into memory and sets up our environment.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now that we've loaded yt, we can load up some data.  Let's load the `IsolatedGalaxy` dataset.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_sample(\\\"IsolatedGalaxy\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Fields and Facts\\n\",\n    \"\\n\",\n    \"When you call the `load` function, yt tries to do very little -- this is designed to be a fast operation, just setting up some information about the simulation.  Now, the first time you access the \\\"index\\\" it will read and load the mesh and then determine where data is placed in the physical domain and on disk.  Once it knows that, yt can tell you some statistics about the simulation:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.print_stats()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"yt can also tell you the fields it found on disk:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.field_list\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"And, all of the fields it thinks it knows how to generate:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.derived_field_list\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"yt can also transparently generate fields.  However, we encourage you to examine exactly what yt is doing when it generates those fields.  To see, you can ask for the source of a given field.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(ds.field_info[\\\"gas\\\", \\\"vorticity_x\\\"].get_source())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"yt stores information about the domain of the simulation:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.domain_width\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"yt can also convert this into various units:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(ds.domain_width.in_units(\\\"kpc\\\"))\\n\",\n    \"print(ds.domain_width.in_units(\\\"au\\\"))\\n\",\n    \"print(ds.domain_width.in_units(\\\"mile\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, we can get basic information about the particle types and number of particles in a simulation:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(ds.particle_types)\\n\",\n    \"print(ds.particle_types_raw)\\n\",\n    \"print(ds.particle_type_counts)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For this dataset, we see that there are two particle types defined, (`io` and `all`), but that only one of these particle types in `ds.particle_types_raw`. The `ds.particle_types` list contains *all* particle types in the simulation, including ones that are dynamically defined like particle unions. The `ds.particle_types_raw` list includes only particle types that are in the output file we loaded the dataset from.\\n\",\n    \"\\n\",\n    \"We can also see that there are a bit more than 1.1 million particles in this simulation. Only particle types in `ds.particle_types_raw` will appear in the `ds.particle_type_counts` dictionary.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Mesh Structure\\n\",\n    \"\\n\",\n    \"If you're using a simulation type that has grids (for instance, here we're using an Enzo simulation) you can examine the structure of the mesh.  For the most part, you probably won't have to use this unless you're debugging a simulation or examining in detail what is going on.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(ds.index.grid_left_edge)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"But, you may have to access information about individual grid objects!  Each grid object mediates accessing data from the disk and has a number of attributes that tell you about it.  The index (`ds.index` here) has an attribute `grids` which is all of the grid objects.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds.index.grids[1]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"g = ds.index.grids[1]\\n\",\n    \"print(g)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Grids have dimensions, extents, level, and even a list of Child grids.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"g.ActiveDimensions\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"g.LeftEdge, g.RightEdge\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"g.Level\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"g.Children\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Advanced Grid Inspection\\n\",\n    \"\\n\",\n    \"If we want to examine grids only at a given level, we can!  Not only that, but we can load data and take a look at various fields.\\n\",\n    \"\\n\",\n    \"*This section can be skipped!*\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"gs = ds.index.select_grids(ds.index.max_level)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"g2 = gs[0]\\n\",\n    \"print(g2)\\n\",\n    \"print(g2.Parent)\\n\",\n    \"print(g2.get_global_startindex())\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"g2[\\\"density\\\"][:, :, 0]\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print((g2.Parent.child_mask == 0).sum() * 8)\\n\",\n    \"print(g2.ActiveDimensions.prod())\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"for f in ds.field_list:\\n\",\n    \"    fv = g[f]\\n\",\n    \"    if fv.size == 0:\\n\",\n    \"        continue\\n\",\n    \"    print(f, fv.min(), fv.max())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Examining Data in Regions\\n\",\n    \"\\n\",\n    \"yt provides data object selectors.  In subsequent notebooks we'll examine these in more detail, but we can select a sphere of data and perform a number of operations on it.  yt makes it easy to operate on fluid fields in an object in *bulk*, but you can also examine individual field values.\\n\",\n    \"\\n\",\n    \"This creates a sphere selector positioned at the most dense point in the simulation that has a radius of 10 kpc.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sp = ds.sphere(\\\"max\\\", (10, \\\"kpc\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sp\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can calculate a bunch of bulk quantities.  Here's that list, but there's a list in the docs, too!\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"list(sp.quantities.keys())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's look at the total mass.  This is how you call a given quantity.  yt calls these \\\"Derived Quantities\\\".  We'll talk about a few in a later notebook.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sp.quantities.total_mass()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/quickstart/3)_Simple_Visualization.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Simple Visualizations of Data\\n\",\n    \"\\n\",\n    \"Just like in our first notebook, we have to load yt and then some data.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For this notebook, we'll load up a cosmology dataset.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_sample(\\\"enzo_tiny_cosmology\\\")\\n\",\n    \"print(\\\"Redshift =\\\", ds.current_redshift)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In the terms that yt uses, a projection is a line integral through the domain.  This can either be unweighted (in which case a column density is returned) or weighted, in which case an average value is returned.  Projections are, like all other data objects in yt, full-fledged data objects that churn through data and present that to you.  However, we also provide a simple method of creating Projections and plotting them in a single step.  This is called a Plot Window, here specifically known as a `ProjectionPlot`.  One thing to note is that in yt, we project all the way through the entire domain at a single time.  This means that the first call to projecting can be somewhat time consuming, but panning, zooming and plotting are all quite fast.\\n\",\n    \"\\n\",\n    \"yt is designed to make it easy to make nice plots and straightforward to modify those plots directly.  The cookbook in the documentation includes detailed examples of this.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"p = yt.ProjectionPlot(ds, \\\"y\\\", (\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `show` command simply sends the plot to the IPython notebook.  You can also call `p.save()` which will save the plot to the file system.  This function accepts an argument, which will be prepended to the filename and can be used to name it based on the width or to supply a location.\\n\",\n    \"\\n\",\n    \"Now we'll zoom and pan a bit.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"p.zoom(2.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"p.pan_rel((0.1, 0.0))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"p.zoom(10.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"p.pan_rel((-0.25, -0.5))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"p.zoom(0.1)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If we specify multiple fields, each time we call `show` we get multiple plots back.  Same for `save`!\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"p = yt.ProjectionPlot(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    [(\\\"gas\\\", \\\"density\\\"), (\\\"gas\\\", \\\"temperature\\\")],\\n\",\n    \"    weight_field=(\\\"gas\\\", \\\"density\\\"),\\n\",\n    \")\\n\",\n    \"p.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can adjust the colormap on a field-by-field basis.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"p.set_cmap((\\\"gas\\\", \\\"temperature\\\"), \\\"hot\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"And, we can re-center the plot on different locations.  One possible use of this would be to make a single `ProjectionPlot` which you move around to look at different regions in your simulation, saving at each one.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"v, c = ds.find_max((\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"p.set_center((c[0], c[1]))\\n\",\n    \"p.zoom(10)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Okay, let's load up a bigger simulation (from `Enzo_64` this time) and make a slice plot.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_sample(\\\"Enzo_64/DD0043/data0043\\\")\\n\",\n    \"s = yt.SlicePlot(\\n\",\n    \"    ds, \\\"z\\\", [(\\\"gas\\\", \\\"density\\\"), (\\\"gas\\\", \\\"velocity_magnitude\\\")], center=\\\"max\\\"\\n\",\n    \")\\n\",\n    \"s.set_cmap((\\\"gas\\\", \\\"velocity_magnitude\\\"), \\\"cmyt.pastel\\\")\\n\",\n    \"s.zoom(10.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can adjust the logging of various fields:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"s.set_log((\\\"gas\\\", \\\"velocity_magnitude\\\"), True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"yt provides many different annotations for your plots.  You can see all of these in the documentation, or if you type `s.annotate_` and press tab, a list will show up here.  We'll annotate with velocity arrows.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"s.annotate_velocity()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Contours can also be overlaid:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"s = yt.SlicePlot(ds, \\\"x\\\", (\\\"gas\\\", \\\"density\\\"), center=\\\"max\\\")\\n\",\n    \"s.annotate_contour((\\\"gas\\\", \\\"temperature\\\"))\\n\",\n    \"s.zoom(2.5)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, we can save out to the file system.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"s.save()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/quickstart/4)_Data_Objects_and_Time_Series.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Data Objects and Time Series Data\\n\",\n    \"\\n\",\n    \"Just like before, we will load up yt.  Since we'll be using pyplot to plot some data in this notebook, we additionally tell matplotlib to place plots inline inside the notebook.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Time Series Data\\n\",\n    \"\\n\",\n    \"Unlike before, instead of loading a single dataset, this time we'll load a bunch which we'll examine in sequence.  This command creates a `DatasetSeries` object, which can be iterated over (including in parallel, which is outside the scope of this quickstart) and analyzed.  There are some other helpful operations it can provide, but we'll stick to the basics here.\\n\",\n    \"\\n\",\n    \"Note that you can specify either a list of filenames, or a glob (i.e., asterisk) pattern in this.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ts = yt.load(\\\"enzo_tiny_cosmology/DD????/DD????\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Simple Time Series\\n\",\n    \"\\n\",\n    \"As a simple example of how we can use this functionality, let's find the min and max of the density as a function of time in this simulation.  To do this we use the construction `for ds in ts` where `ds` means \\\"Dataset\\\" and `ts` is the \\\"Time Series\\\" we just loaded up.  For each dataset, we'll create an object (`ad`) that covers the entire domain.  (`all_data` is a shorthand function for this.)  We'll then call the `extrema` Derived Quantity, and append the min and max to our extrema outputs.  Lastly, we're turn down yt's logging to only show \\\"error\\\"s so as to not produce too much logging text, as it loads each individual dataset below.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"yt.set_log_level(\\\"error\\\")\\n\",\n    \"rho_ex = []\\n\",\n    \"times = []\\n\",\n    \"for ds in ts:\\n\",\n    \"    ad = ds.all_data()\\n\",\n    \"    rho_ex.append(ad.quantities.extrema(\\\"density\\\"))\\n\",\n    \"    times.append(ds.current_time.in_units(\\\"Gyr\\\"))\\n\",\n    \"rho_ex = np.array(rho_ex)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we plot the minimum and the maximum:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"fig, ax = plt.subplots()\\n\",\n    \"ax.set(\\n\",\n    \"    xlabel=\\\"Time (Gyr)\\\",\\n\",\n    \"    ylabel=\\\"Density ($g/cm^3$)\\\",\\n\",\n    \"    yscale=\\\"log\\\",\\n\",\n    \"    ylim=(1e-32, 1e-21),\\n\",\n    \")\\n\",\n    \"ax.plot(times, rho_ex[:, 0], \\\"-xk\\\", label=\\\"Minimum\\\")\\n\",\n    \"ax.plot(times, rho_ex[:, 1], \\\"-xr\\\", label=\\\"Maximum\\\")\\n\",\n    \"ax.legend()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Data Objects\\n\",\n    \"\\n\",\n    \"Time series data have many applications, but most of them rely on examining the underlying data in some way.  Below, we'll see how to use and manipulate data objects.\\n\",\n    \"\\n\",\n    \"### Ray Queries\\n\",\n    \"\\n\",\n    \"yt provides the ability to examine rays, or lines, through the domain.  Note that these are not periodic, unlike most other data objects.  We create a ray object and can then examine quantities of it.  Rays have the special fields `t` and `dts`, which correspond to the time the ray enters a given cell and the distance it travels through that cell.\\n\",\n    \"\\n\",\n    \"To create a ray, we specify the start and end points.\\n\",\n    \"\\n\",\n    \"Note that we need to convert these arrays to numpy arrays due to a bug in matplotlib 1.3.1.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"fig, ax = plt.subplots()\\n\",\n    \"ray = ds.ray([0.1, 0.2, 0.3], [0.9, 0.8, 0.7])\\n\",\n    \"ax.semilogy(np.array(ray[\\\"t\\\"]), np.array(ray[\\\"density\\\"]))\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(ray[\\\"dts\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(ray[\\\"t\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(ray[\\\"gas\\\", \\\"x\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Slice Queries\\n\",\n    \"\\n\",\n    \"While slices are often used for visualization, they can be useful for other operations as well.  yt regards slices as multi-resolution objects.  They are an array of cells that are not all the same size; it only returns the cells at the highest resolution that it intersects.  (This is true for all yt data objects.)  Slices and projections have the special fields `px`, `py`, `pdx` and `pdy`, which correspond to the coordinates and half-widths in the pixel plane.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_sample(\\\"IsolatedGalaxy\\\")\\n\",\n    \"v, c = ds.find_max((\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"sl = ds.slice(2, c[0])\\n\",\n    \"print(sl[\\\"index\\\", \\\"x\\\"])\\n\",\n    \"print(sl[\\\"index\\\", \\\"z\\\"])\\n\",\n    \"print(sl[\\\"pdx\\\"])\\n\",\n    \"print(sl[\\\"gas\\\", \\\"density\\\"].shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If we want to do something interesting with a `Slice`, we can turn it into a `FixedResolutionBuffer`.  This object can be queried and will return a 2D array of values.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"frb = sl.to_frb((50.0, \\\"kpc\\\"), 1024)\\n\",\n    \"print(frb[\\\"gas\\\", \\\"density\\\"].shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"yt provides a few functions for writing arrays to disk, particularly in image form.  Here we'll write out the log of `density`, and then use IPython to display it back here.  Note that for the most part, you will probably want to use a `PlotWindow` for this, but in the case that it is useful you can directly manipulate the data.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"yt.write_image(np.log10(frb[\\\"gas\\\", \\\"density\\\"]), \\\"temp.png\\\")\\n\",\n    \"from IPython.display import Image\\n\",\n    \"\\n\",\n    \"Image(filename=\\\"temp.png\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Off-Axis Slices\\n\",\n    \"\\n\",\n    \"yt provides not only slices, but off-axis slices that are sometimes called \\\"cutting planes.\\\"  These are specified by (in order) a normal vector and a center.  Here we've set the normal vector to `[0.2, 0.3, 0.5]` and the center to be the point of maximum density.\\n\",\n    \"\\n\",\n    \"We can then turn these directly into plot windows using `to_pw`.  Note that the `to_pw` and `to_frb` methods are available on slices, off-axis slices, and projections, and can be used on any of them.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"cp = ds.cutting([0.2, 0.3, 0.5], \\\"max\\\")\\n\",\n    \"pw = cp.to_pw(fields=[(\\\"gas\\\", \\\"density\\\")])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Once we have our plot window from our cutting plane, we can show it here.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"pw.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"pw.zoom(10)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can, as noted above, do the same with our slice:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"pws = sl.to_pw(fields=[(\\\"gas\\\", \\\"density\\\")])\\n\",\n    \"pws.show()\\n\",\n    \"print(list(pws.plots.keys()))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Covering Grids\\n\",\n    \"\\n\",\n    \"If we want to access a 3D array of data that spans multiple resolutions in our simulation, we can use a covering grid.  This will return a 3D array of data, drawing from up to the resolution level specified when creating the data.  For example, if you create a covering grid that spans two child grids of a single parent grid, it will fill those zones covered by a zone of a child grid with the data from that child grid.  Where it is covered only by the parent grid, the cells from the parent grid will be duplicated (appropriately) to fill the covering grid.\\n\",\n    \"\\n\",\n    \"There are two different types of covering grids: unsmoothed and smoothed.  Smoothed grids will be filled through a cascading interpolation process; they will be filled at level 0, interpolated to level 1, filled at level 1, interpolated to level 2, filled at level 2, etc.  This will help to reduce edge effects.  Unsmoothed covering grids will not be interpolated, but rather values will be duplicated multiple times.\\n\",\n    \"\\n\",\n    \"For SPH datasets, the covering grid gives the SPH-interpolated value of a field at each grid cell center. This is done for unsmoothed grids; smoothed grids are not available for SPH data.\\n\",\n    \"\\n\",\n    \"Here we create an unsmoothed covering grid at level 2, with the left edge at `[0.0, 0.0, 0.0]` and with dimensions equal to those that would cover the entire domain at level 2.  We can then ask for the Density field, which will be a 3D array.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"cg = ds.covering_grid(2, [0.0, 0.0, 0.0], ds.domain_dimensions * 2**2)\\n\",\n    \"print(cg[\\\"density\\\"].shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In this example, we do exactly the same thing: except we ask for a *smoothed* covering grid, which will reduce edge effects.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"scg = ds.smoothed_covering_grid(2, [0.0, 0.0, 0.0], ds.domain_dimensions * 2**2)\\n\",\n    \"print(scg[\\\"density\\\"].shape)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3.9.5 64-bit ('yt-dev': pyenv)\",\n   \"metadata\": {\n    \"interpreter\": {\n     \"hash\": \"14363bd97bed451d1329fb3e06aa057a9e955a9421c5343dd7530f5497723a41\"\n    }\n   },\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.9.5\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/quickstart/5)_Derived_Fields_and_Profiles.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Derived Fields and Profiles\\n\",\n    \"\\n\",\n    \"One of the most powerful features in yt is the ability to create derived fields that act and look exactly like fields that exist on disk.  This means that they will be generated on demand and can be used anywhere a field that exists on disk would be used.  Additionally, you can create them by just writing python functions.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"%matplotlib inline\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"import numpy as np\\n\",\n    \"\\n\",\n    \"import yt\\n\",\n    \"from yt import derived_field\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Derived Fields\\n\",\n    \"\\n\",\n    \"This is an example of the simplest possible way to create a derived field.  All derived fields are defined by a function and some metadata; that metadata can include units, LaTeX-friendly names, conversion factors, and so on.  Fields can be defined in the way in the next cell.  What this does is create a function which accepts two arguments and then provide the units for that field.  In this case, our field is `dinosaurs` and our units are `K*cm/s`.  The function itself can access any fields that are in the simulation, and it does so by requesting data from the object called `data`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"@derived_field(name=\\\"dinosaurs\\\", units=\\\"K * cm/s\\\", sampling_type=\\\"cell\\\")\\n\",\n    \"def _dinos(data):\\n\",\n    \"    return data[\\\"gas\\\", \\\"temperature\\\"] * data[\\\"gas\\\", \\\"velocity_magnitude\\\"]\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"One important thing to note is that derived fields must be defined *before* any datasets are loaded.  Let's load up our data and take a look at some quantities.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load_sample(\\\"IsolatedGalaxy\\\")\\n\",\n    \"dd = ds.all_data()\\n\",\n    \"print(list(dd.quantities.keys()))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"One interesting question is, what are the minimum and maximum values of dinosaur production rates in our isolated galaxy?  We can do that by examining the `extrema` quantity -- the exact same way that we would for density, temperature, and so on.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(dd.quantities.extrema((\\\"gas\\\", \\\"dinosaurs\\\")))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can do the same for the average quantities as well.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(\\n\",\n    \"    dd.quantities.weighted_average_quantity(\\n\",\n    \"        (\\\"gas\\\", \\\"dinosaurs\\\"), weight=(\\\"gas\\\", \\\"temperature\\\")\\n\",\n    \"    )\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## A Few Other Quantities\\n\",\n    \"\\n\",\n    \"We can ask other quantities of our data, as well.  For instance, this sequence of operations will find the most dense point, center a sphere on it, calculate the bulk velocity of that sphere, calculate the baryonic angular momentum vector, and then the density extrema.  All of this is done in a memory conservative way: if you have an absolutely enormous dataset, yt will split that dataset into pieces, apply intermediate reductions and then a final reduction to calculate your quantity.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sp = ds.sphere(\\\"max\\\", (10.0, \\\"kpc\\\"))\\n\",\n    \"bv = sp.quantities.bulk_velocity()\\n\",\n    \"L = sp.quantities.angular_momentum_vector()\\n\",\n    \"rho_min, rho_max = sp.quantities.extrema((\\\"gas\\\", \\\"density\\\"))\\n\",\n    \"print(bv)\\n\",\n    \"print(L)\\n\",\n    \"print(rho_min, rho_max)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Profiles\\n\",\n    \"\\n\",\n    \"yt provides the ability to bin in 1, 2 and 3 dimensions.  This means discretizing in one or more dimensions of phase space (density, temperature, etc) and then calculating either the total value of a field in each bin or the average value of a field in each bin.\\n\",\n    \"\\n\",\n    \"We do this using the objects `Profile1D`, `Profile2D`, and `Profile3D`.  The first two are the most common since they are the easiest to visualize.\\n\",\n    \"\\n\",\n    \"This first set of commands manually creates a profile object the sphere we created earlier, binned in 32 bins according to density between `rho_min` and `rho_max`, and then takes the density-weighted average of the fields `temperature` and (previously-defined) `dinosaurs`.  We then plot it in a loglog plot.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prof = yt.Profile1D(\\n\",\n    \"    sp, (\\\"gas\\\", \\\"density\\\"), 32, rho_min, rho_max, True, weight_field=(\\\"gas\\\", \\\"mass\\\")\\n\",\n    \")\\n\",\n    \"prof.add_fields([(\\\"gas\\\", \\\"temperature\\\"), (\\\"gas\\\", \\\"dinosaurs\\\")])\\n\",\n    \"\\n\",\n    \"fig, ax = plt.subplots()\\n\",\n    \"ax.loglog(np.array(prof.x), np.array(prof[\\\"gas\\\", \\\"temperature\\\"]), \\\"-x\\\")\\n\",\n    \"ax.set(\\n\",\n    \"    xlabel=\\\"Density $(g/cm^3)$\\\",\\n\",\n    \"    ylabel=\\\"Temperature $(K)$\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now we plot the `dinosaurs` field.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"fig, ax = plt.subplots()\\n\",\n    \"ax.loglog(np.array(prof.x), np.array(prof[\\\"gas\\\", \\\"dinosaurs\\\"]), \\\"-x\\\")\\n\",\n    \"ax.set(\\n\",\n    \"    xlabel=\\\"Density $(g/cm^3)$\\\",\\n\",\n    \"    ylabel=\\\"Dinosaurs $(K cm / s)$\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If we want to see the total mass in every bin, we profile the `mass` field with no weight.  Specifying `weight=None` will simply take the total value in every bin and add that up.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prof = yt.Profile1D(\\n\",\n    \"    sp, (\\\"gas\\\", \\\"density\\\"), 32, rho_min, rho_max, True, weight_field=None\\n\",\n    \")\\n\",\n    \"prof.add_fields([(\\\"gas\\\", \\\"mass\\\")])\\n\",\n    \"\\n\",\n    \"fig, ax = plt.subplots()\\n\",\n    \"ax.loglog(np.array(prof.x), np.array(prof[\\\"gas\\\", \\\"mass\\\"].in_units(\\\"Msun\\\")), \\\"-x\\\")\\n\",\n    \"ax.set(\\n\",\n    \"    xlabel=\\\"Density $(g/cm^3)$\\\",\\n\",\n    \"    ylabel=r\\\"Cell mass $(M_\\\\odot)$\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"In addition to the low-level `ProfileND` interface, it's also quite straightforward to quickly create plots of profiles using the `ProfilePlot` class.  Let's redo the last plot using `ProfilePlot`\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prof = yt.ProfilePlot(sp, (\\\"gas\\\", \\\"density\\\"), (\\\"gas\\\", \\\"mass\\\"), weight_field=None)\\n\",\n    \"prof.set_unit((\\\"gas\\\", \\\"mass\\\"), \\\"Msun\\\")\\n\",\n    \"prof.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Field Parameters\\n\",\n    \"\\n\",\n    \"Field parameters are a method of passing information to derived fields.  For instance, you might pass in information about a vector you want to use as a basis for a coordinate transformation.  yt often uses things like `bulk_velocity` to identify velocities that should be subtracted off.  Here we show how that works:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sp_small = ds.sphere(\\\"max\\\", (50.0, \\\"kpc\\\"))\\n\",\n    \"bv = sp_small.quantities.bulk_velocity()\\n\",\n    \"\\n\",\n    \"sp = ds.sphere(\\\"max\\\", (0.1, \\\"Mpc\\\"))\\n\",\n    \"rv1 = sp.quantities.extrema((\\\"gas\\\", \\\"radial_velocity\\\"))\\n\",\n    \"\\n\",\n    \"sp.clear_data()\\n\",\n    \"sp.set_field_parameter(\\\"bulk_velocity\\\", bv)\\n\",\n    \"rv2 = sp.quantities.extrema((\\\"gas\\\", \\\"radial_velocity\\\"))\\n\",\n    \"\\n\",\n    \"print(bv)\\n\",\n    \"print(rv1)\\n\",\n    \"print(rv2)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/quickstart/6)_Volume_Rendering.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# A Brief Demo of Volume Rendering\\n\",\n    \"\\n\",\n    \"This shows a small amount of volume rendering.  Really, just enough to get your feet wet!\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\\n\",\n    \"\\n\",\n    \"ds = yt.load_sample(\\\"IsolatedGalaxy\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To create a volume rendering, we need a camera and a transfer function.  We'll use the `ColorTransferFunction`, which accepts (in log space) the minimum and maximum bounds of our transfer function.  This means behavior for data outside these values is undefined.\\n\",\n    \"\\n\",\n    \"We then add on \\\"layers\\\" like an onion.  This function can accept a width (here specified) in data units, and also a color map.  Here we add on four layers.\\n\",\n    \"\\n\",\n    \"Finally, we create a camera.  The focal point is `[0.5, 0.5, 0.5]`, the width is 20 kpc (including front-to-back integration) and we specify a transfer function.  Once we've done that, we call `show` to actually cast our rays and display them inline.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sc = yt.create_scene(ds)\\n\",\n    \"\\n\",\n    \"sc.camera.set_width(ds.quan(20, \\\"kpc\\\"))\\n\",\n    \"\\n\",\n    \"source = sc.sources[\\\"source_00\\\"]\\n\",\n    \"\\n\",\n    \"tf = yt.ColorTransferFunction((-28, -24))\\n\",\n    \"tf.add_layers(4, w=0.01)\\n\",\n    \"\\n\",\n    \"source.set_transfer_function(tf)\\n\",\n    \"\\n\",\n    \"sc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If we want to apply a clipping, we can specify the `sigma_clip`.  This will clip the upper bounds to this value times the standard deviation of the values in the image array.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sc.show(sigma_clip=4)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"There are several other options we can specify.  Note that here we have turned on the use of ghost zones, shortened the data interval for the transfer function, and widened our gaussian layers.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"sc = yt.create_scene(ds)\\n\",\n    \"\\n\",\n    \"sc.camera.set_width(ds.quan(20, \\\"kpc\\\"))\\n\",\n    \"\\n\",\n    \"source = sc.sources[\\\"source_00\\\"]\\n\",\n    \"\\n\",\n    \"source.field = \\\"density\\\"\\n\",\n    \"\\n\",\n    \"tf = yt.ColorTransferFunction((-28, -25))\\n\",\n    \"tf.add_layers(4, w=0.03)\\n\",\n    \"\\n\",\n    \"source.transfer_function = tf\\n\",\n    \"\\n\",\n    \"sc.show(sigma_clip=4.0)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/quickstart/index.rst",
    "content": ".. _quickstart:\n\nyt Quickstart\n=============\n\nThe quickstart is a series of worked examples of how to use much of the\nfunctionality of yt.  These are simple, short introductions to give you a taste\nof what the code can do and are not meant to be detailed walkthroughs.\n\nThere are two ways in which you can go through the quickstart: interactively and\nnon-interactively.  We recommend the interactive method, but if you're pressed\non time, you can non-interactively go through the linked pages below and view the\nworked examples.\n\nTo execute the quickstart interactively, you have a couple of options: 1) run\nthe notebook from your own system or 2) run it from the url\nhttps://girder.hub.yt/#raft/5b5b4686323d12000122aa8a.\nOption 1 requires an existing installation of yt (see\n:ref:`installing-yt`), a copy of the yt source (which you may\nalready have depending on your installation choice), and a download of the\ntutorial data-sets (total about 3 GB). If you know you are going to be a yt user\nand have the time to download the data-sets, option 1 is a good choice. However,\nif you're only interested in getting a feel for yt and its capabilities, or you\nalready have yt but don't want to spend time downloading the data, go ahead to\nhttps://girder.hub.yt/#raft/5b5b4686323d12000122aa8a.\n\nIf you're running the tutorial from your own system and you do not already have\nthe yt repository, the easiest way to get the repository is to clone it using\ngit:\n\n.. code-block:: bash\n\n   git clone https://github.com/yt-project/yt\n\nNow start the IPython notebook from within the repository (we presume you have\nyt and [jupyterlab](https://jupyterlab.readthedocs.io/en/latest/) installed):\n\n.. code-block:: bash\n\n   cd yt/doc/source/quickstart\n   jupyter lab\n\nThis command will give you information about the notebook server and how to\naccess it.  You will basically just pick a password (for security reasons) and then\nredirect your web browser to point to the notebook server.\nOnce you have done so, choose \"Introduction\" from the list of\nnotebooks, which includes an introduction and information about how to download\nthe sample data.\n\n.. warning:: The pre-filled out notebooks are *far* less fun than running them\n             yourselves!  Check out the repo and give it a try.\n\nHere are the notebooks, which have been filled in for inspection:\n\n.. toctree::\n   :maxdepth: 1\n\n   1)_Introduction\n   2)_Data_Inspection\n   3)_Simple_Visualization\n   4)_Data_Objects_and_Time_Series\n   5)_Derived_Fields_and_Profiles\n   6)_Volume_Rendering\n\n.. note::\n\n   The notebooks use sample datasets that are available for download at\n   https://yt-project.org/data.  See :doc:`1)_Introduction` for more\n   details.\n\nLet us know if you would like to contribute other example notebooks, or have\nany suggestions for how these can be improved.\n"
  },
  {
    "path": "doc/source/reference/api/api.rst",
    "content": ".. _api-reference:\n\nAPI Reference\n=============\n\nPlots and the Plotting Interface\n--------------------------------\n\nSlicePlot and ProjectionPlot\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. autosummary::\n\n   ~yt.visualization.plot_window.SlicePlot\n   ~yt.visualization.plot_window.AxisAlignedSlicePlot\n   ~yt.visualization.plot_window.OffAxisSlicePlot\n   ~yt.visualization.plot_window.ProjectionPlot\n   ~yt.visualization.plot_window.AxisAlignedProjectionPlot\n   ~yt.visualization.plot_window.OffAxisProjectionPlot\n   ~yt.visualization.plot_window.WindowPlotMPL\n   ~yt.visualization.plot_window.PlotWindow\n   ~yt.visualization.plot_window.plot_2d\n\nProfilePlot and PhasePlot\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. autosummary::\n\n   ~yt.visualization.profile_plotter.ProfilePlot\n   ~yt.visualization.profile_plotter.PhasePlot\n   ~yt.visualization.profile_plotter.PhasePlotMPL\n\nParticle Plots\n^^^^^^^^^^^^^^\n\n.. autosummary::\n\n   ~yt.visualization.particle_plots.ParticleProjectionPlot\n   ~yt.visualization.particle_plots.ParticlePhasePlot\n   ~yt.visualization.particle_plots.ParticlePlot\n\nFixed Resolution Pixelization\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. autosummary::\n\n   ~yt.visualization.fixed_resolution.FixedResolutionBuffer\n   ~yt.visualization.fixed_resolution.ParticleImageBuffer\n   ~yt.visualization.fixed_resolution.CylindricalFixedResolutionBuffer\n   ~yt.visualization.fixed_resolution.OffAxisProjectionFixedResolutionBuffer\n\nWriting FITS images\n^^^^^^^^^^^^^^^^^^^\n\n.. autosummary::\n\n   ~yt.visualization.fits_image.FITSImageData\n   ~yt.visualization.fits_image.FITSSlice\n   ~yt.visualization.fits_image.FITSProjection\n   ~yt.visualization.fits_image.FITSOffAxisSlice\n   ~yt.visualization.fits_image.FITSOffAxisProjection\n   ~yt.visualization.fits_image.FITSParticleProjection\n\nData Sources\n------------\n\n.. _physical-object-api:\n\nPhysical Objects\n^^^^^^^^^^^^^^^^\n\nThese are the objects that act as physical selections of data, describing a\nregion in space.  These are not typically addressed directly; see\n:ref:`available-objects` for more information.\n\nBase Classes\n++++++++++++\n\nThese will almost never need to be instantiated on their own.\n\n.. autosummary::\n\n   ~yt.data_objects.data_containers.YTDataContainer\n   ~yt.data_objects.selection_objects.data_selection_objects.YTSelectionContainer\n   ~yt.data_objects.selection_objects.data_selection_objects.YTSelectionContainer0D\n   ~yt.data_objects.selection_objects.data_selection_objects.YTSelectionContainer1D\n   ~yt.data_objects.selection_objects.data_selection_objects.YTSelectionContainer2D\n   ~yt.data_objects.selection_objects.data_selection_objects.YTSelectionContainer3D\n\nSelection Objects\n+++++++++++++++++\n\nThese objects are defined by some selection method or mechanism.  Most are\ngeometric.\n\n.. autosummary::\n\n   ~yt.data_objects.selection_objects.point.YTPoint\n   ~yt.data_objects.selection_objects.ray.YTOrthoRay\n   ~yt.data_objects.selection_objects.ray.YTRay\n   ~yt.data_objects.selection_objects.slices.YTSlice\n   ~yt.data_objects.selection_objects.slices.YTCuttingPlane\n   ~yt.data_objects.selection_objects.disk.YTDisk\n   ~yt.data_objects.selection_objects.region.YTRegion\n   ~yt.data_objects.selection_objects.object_collection.YTDataCollection\n   ~yt.data_objects.selection_objects.spheroids.YTSphere\n   ~yt.data_objects.selection_objects.spheroids.YTEllipsoid\n   ~yt.data_objects.selection_objects.cut_region.YTCutRegion\n   ~yt.data_objects.index_subobjects.grid_patch.AMRGridPatch\n   ~yt.data_objects.index_subobjects.octree_subset.OctreeSubset\n   ~yt.data_objects.index_subobjects.particle_container.ParticleContainer\n   ~yt.data_objects.index_subobjects.unstructured_mesh.UnstructuredMesh\n   ~yt.data_objects.index_subobjects.unstructured_mesh.SemiStructuredMesh\n\nConstruction Objects\n++++++++++++++++++++\n\nThese objects typically require some effort to build.  Often this means\nintegrating through the simulation in some way, or creating some large or\nexpensive set of intermediate data.\n\n.. autosummary::\n\n   ~yt.data_objects.construction_data_containers.YTStreamline\n   ~yt.data_objects.construction_data_containers.YTQuadTreeProj\n   ~yt.data_objects.construction_data_containers.YTCoveringGrid\n   ~yt.data_objects.construction_data_containers.YTArbitraryGrid\n   ~yt.data_objects.construction_data_containers.YTSmoothedCoveringGrid\n   ~yt.data_objects.construction_data_containers.YTSurface\n\nTime Series Objects\n^^^^^^^^^^^^^^^^^^^\n\nThese are objects that either contain and represent or operate on series of\ndatasets.\n\n.. autosummary::\n\n   ~yt.data_objects.time_series.DatasetSeries\n   ~yt.data_objects.time_series.DatasetSeriesObject\n   ~yt.data_objects.time_series.SimulationTimeSeries\n   ~yt.data_objects.time_series.TimeSeriesQuantitiesContainer\n   ~yt.data_objects.time_series.AnalysisTaskProxy\n   ~yt.data_objects.particle_trajectories.ParticleTrajectories\n\nGeometry Handlers\n-----------------\n\nThese objects generate an \"index\" into multiresolution data.\n\n.. autosummary::\n\n   ~yt.geometry.geometry_handler.Index\n   ~yt.geometry.grid_geometry_handler.GridIndex\n   ~yt.geometry.oct_geometry_handler.OctreeIndex\n   ~yt.geometry.particle_geometry_handler.ParticleIndex\n   ~yt.geometry.unstructured_mesh_handler.UnstructuredIndex\n\nUnits\n-----\n\nyt's symbolic unit handling system is now based on the external library unyt. In\ncomplement, Dataset objects support the following methods to build arrays and\nscalars with physical dimensions.\n\n.. autosummary::\n\n   yt.data_objects.static_output.Dataset.arr\n   yt.data_objects.static_output.Dataset.quan\n\n\n\nFrontends\n---------\n\n.. autosummary::\n\nAMRVAC\n^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.amrvac.data_structures.AMRVACGrid\n   ~yt.frontends.amrvac.data_structures.AMRVACHierarchy\n   ~yt.frontends.amrvac.data_structures.AMRVACDataset\n   ~yt.frontends.amrvac.fields.AMRVACFieldInfo\n   ~yt.frontends.amrvac.io.AMRVACIOHandler\n   ~yt.frontends.amrvac.io.read_amrvac_namelist\n\nARTIO\n^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.artio.data_structures.ARTIOIndex\n   ~yt.frontends.artio.data_structures.ARTIOOctreeSubset\n   ~yt.frontends.artio.data_structures.ARTIORootMeshSubset\n   ~yt.frontends.artio.data_structures.ARTIODataset\n   ~yt.frontends.artio.definitions.ARTIOconstants\n   ~yt.frontends.artio.fields.ARTIOFieldInfo\n   ~yt.frontends.artio.io.IOHandlerARTIO\n\n\nAthena\n^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.athena.data_structures.AthenaGrid\n   ~yt.frontends.athena.data_structures.AthenaHierarchy\n   ~yt.frontends.athena.data_structures.AthenaDataset\n   ~yt.frontends.athena.fields.AthenaFieldInfo\n   ~yt.frontends.athena.io.IOHandlerAthena\n\nAMReX/Boxlib\n^^^^^^^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.amrex.data_structures.BoxlibGrid\n   ~yt.frontends.amrex.data_structures.BoxlibHierarchy\n   ~yt.frontends.amrex.data_structures.BoxlibDataset\n   ~yt.frontends.amrex.data_structures.CastroDataset\n   ~yt.frontends.amrex.data_structures.MaestroDataset\n   ~yt.frontends.amrex.data_structures.NyxHierarchy\n   ~yt.frontends.amrex.data_structures.NyxDataset\n   ~yt.frontends.amrex.data_structures.OrionHierarchy\n   ~yt.frontends.amrex.data_structures.OrionDataset\n   ~yt.frontends.amrex.fields.BoxlibFieldInfo\n   ~yt.frontends.amrex.io.IOHandlerBoxlib\n   ~yt.frontends.amrex.io.IOHandlerOrion\n\nCfRadial\n^^^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.cf_radial.data_structures.CFRadialGrid\n   ~yt.frontends.cf_radial.data_structures.CFRadialHierarchy\n   ~yt.frontends.cf_radial.data_structures.CFRadialDataset\n   ~yt.frontends.cf_radial.fields.CFRadialFieldInfo\n   ~yt.frontends.cf_radial.io.CFRadialIOHandler\n\nChombo\n^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.chombo.data_structures.ChomboGrid\n   ~yt.frontends.chombo.data_structures.ChomboHierarchy\n   ~yt.frontends.chombo.data_structures.ChomboDataset\n   ~yt.frontends.chombo.data_structures.Orion2Hierarchy\n   ~yt.frontends.chombo.data_structures.Orion2Dataset\n   ~yt.frontends.chombo.io.IOHandlerChomboHDF5\n   ~yt.frontends.chombo.io.IOHandlerOrion2HDF5\n\nEnzo\n^^^^\n\n.. autosummary::\n\n   ~yt.frontends.enzo.answer_testing_support.ShockTubeTest\n   ~yt.frontends.enzo.data_structures.EnzoGrid\n   ~yt.frontends.enzo.data_structures.EnzoGridGZ\n   ~yt.frontends.enzo.data_structures.EnzoGridInMemory\n   ~yt.frontends.enzo.data_structures.EnzoHierarchy1D\n   ~yt.frontends.enzo.data_structures.EnzoHierarchy2D\n   ~yt.frontends.enzo.data_structures.EnzoHierarchy\n   ~yt.frontends.enzo.data_structures.EnzoHierarchyInMemory\n   ~yt.frontends.enzo.data_structures.EnzoDatasetInMemory\n   ~yt.frontends.enzo.data_structures.EnzoDataset\n   ~yt.frontends.enzo.fields.EnzoFieldInfo\n   ~yt.frontends.enzo.io.IOHandlerInMemory\n   ~yt.frontends.enzo.io.IOHandlerPacked1D\n   ~yt.frontends.enzo.io.IOHandlerPacked2D\n   ~yt.frontends.enzo.io.IOHandlerPackedHDF5\n   ~yt.frontends.enzo.io.IOHandlerPackedHDF5GhostZones\n   ~yt.frontends.enzo.simulation_handling.EnzoCosmology\n   ~yt.frontends.enzo.simulation_handling.EnzoSimulation\n\nFITS\n^^^^\n\n.. autosummary::\n\n   ~yt.frontends.fits.data_structures.FITSGrid\n   ~yt.frontends.fits.data_structures.FITSHierarchy\n   ~yt.frontends.fits.data_structures.FITSDataset\n   ~yt.frontends.fits.fields.FITSFieldInfo\n   ~yt.frontends.fits.io.IOHandlerFITS\n\nFLASH\n^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.flash.data_structures.FLASHGrid\n   ~yt.frontends.flash.data_structures.FLASHHierarchy\n   ~yt.frontends.flash.data_structures.FLASHDataset\n   ~yt.frontends.flash.fields.FLASHFieldInfo\n   ~yt.frontends.flash.io.IOHandlerFLASH\n\nGDF\n^^^\n\n.. autosummary::\n\n   ~yt.frontends.gdf.data_structures.GDFGrid\n   ~yt.frontends.gdf.data_structures.GDFHierarchy\n   ~yt.frontends.gdf.data_structures.GDFDataset\n   ~yt.frontends.gdf.io.IOHandlerGDFHDF5\n\nHalo Catalogs\n^^^^^^^^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.ahf.data_structures.AHFHalosDataset\n   ~yt.frontends.ahf.fields.AHFHalosFieldInfo\n   ~yt.frontends.ahf.io.IOHandlerAHFHalos\n   ~yt.frontends.gadget_fof.data_structures.GadgetFOFDataset\n   ~yt.frontends.gadget_fof.data_structures.GadgetFOFHDF5File\n   ~yt.frontends.gadget_fof.data_structures.GadgetFOFHaloDataset\n   ~yt.frontends.gadget_fof.io.IOHandlerGadgetFOFHDF5\n   ~yt.frontends.gadget_fof.io.IOHandlerGadgetFOFHaloHDF5\n   ~yt.frontends.gadget_fof.fields.GadgetFOFFieldInfo\n   ~yt.frontends.gadget_fof.fields.GadgetFOFHaloFieldInfo\n   ~yt.frontends.halo_catalog.data_structures.YTHaloCatalogFile\n   ~yt.frontends.halo_catalog.data_structures.YTHaloCatalogDataset\n   ~yt.frontends.halo_catalog.fields.YTHaloCatalogFieldInfo\n   ~yt.frontends.halo_catalog.io.IOHandlerYTHaloCatalog\n   ~yt.frontends.owls_subfind.data_structures.OWLSSubfindParticleIndex\n   ~yt.frontends.owls_subfind.data_structures.OWLSSubfindHDF5File\n   ~yt.frontends.owls_subfind.data_structures.OWLSSubfindDataset\n   ~yt.frontends.owls_subfind.fields.OWLSSubfindFieldInfo\n   ~yt.frontends.owls_subfind.io.IOHandlerOWLSSubfindHDF5\n   ~yt.frontends.rockstar.data_structures.RockstarBinaryFile\n   ~yt.frontends.rockstar.data_structures.RockstarDataset\n   ~yt.frontends.rockstar.fields.RockstarFieldInfo\n   ~yt.frontends.rockstar.io.IOHandlerRockstarBinary\n\nMOAB\n^^^^\n\n.. autosummary::\n\n   ~yt.frontends.moab.data_structures.MoabHex8Hierarchy\n   ~yt.frontends.moab.data_structures.MoabHex8Mesh\n   ~yt.frontends.moab.data_structures.MoabHex8Dataset\n   ~yt.frontends.moab.data_structures.PyneHex8Mesh\n   ~yt.frontends.moab.data_structures.PyneMeshHex8Hierarchy\n   ~yt.frontends.moab.data_structures.PyneMoabHex8Dataset\n   ~yt.frontends.moab.io.IOHandlerMoabH5MHex8\n   ~yt.frontends.moab.io.IOHandlerMoabPyneHex8\n\nOpenPMD\n^^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.open_pmd.data_structures.OpenPMDGrid\n   ~yt.frontends.open_pmd.data_structures.OpenPMDHierarchy\n   ~yt.frontends.open_pmd.data_structures.OpenPMDDataset\n   ~yt.frontends.open_pmd.fields.OpenPMDFieldInfo\n   ~yt.frontends.open_pmd.io.IOHandlerOpenPMDHDF5\n   ~yt.frontends.open_pmd.misc.parse_unit_dimension\n   ~yt.frontends.open_pmd.misc.is_const_component\n   ~yt.frontends.open_pmd.misc.get_component\n\nRAMSES\n^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.ramses.data_structures.RAMSESDomainFile\n   ~yt.frontends.ramses.data_structures.RAMSESDomainSubset\n   ~yt.frontends.ramses.data_structures.RAMSESIndex\n   ~yt.frontends.ramses.data_structures.RAMSESDataset\n   ~yt.frontends.ramses.fields.RAMSESFieldInfo\n   ~yt.frontends.ramses.io.IOHandlerRAMSES\n\nSPH and Particle Codes\n^^^^^^^^^^^^^^^^^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.gadget.data_structures.GadgetBinaryFile\n   ~yt.frontends.gadget.data_structures.GadgetHDF5Dataset\n   ~yt.frontends.gadget.data_structures.GadgetDataset\n   ~yt.frontends.http_stream.data_structures.HTTPParticleFile\n   ~yt.frontends.http_stream.data_structures.HTTPStreamDataset\n   ~yt.frontends.owls.data_structures.OWLSDataset\n   ~yt.frontends.sph.data_structures.ParticleDataset\n   ~yt.frontends.tipsy.data_structures.TipsyFile\n   ~yt.frontends.tipsy.data_structures.TipsyDataset\n   ~yt.frontends.sph.fields.SPHFieldInfo\n   ~yt.frontends.gadget.io.IOHandlerGadgetBinary\n   ~yt.frontends.gadget.io.IOHandlerGadgetHDF5\n   ~yt.frontends.http_stream.io.IOHandlerHTTPStream\n   ~yt.frontends.owls.io.IOHandlerOWLS\n   ~yt.frontends.tipsy.io.IOHandlerTipsyBinary\n\nStream\n^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.stream.data_structures.StreamDictFieldHandler\n   ~yt.frontends.stream.data_structures.StreamGrid\n   ~yt.frontends.stream.data_structures.StreamHandler\n   ~yt.frontends.stream.data_structures.StreamHexahedralHierarchy\n   ~yt.frontends.stream.data_structures.StreamHexahedralMesh\n   ~yt.frontends.stream.data_structures.StreamHexahedralDataset\n   ~yt.frontends.stream.data_structures.StreamHierarchy\n   ~yt.frontends.stream.data_structures.StreamOctreeHandler\n   ~yt.frontends.stream.data_structures.StreamOctreeDataset\n   ~yt.frontends.stream.data_structures.StreamOctreeSubset\n   ~yt.frontends.stream.data_structures.StreamParticleFile\n   ~yt.frontends.stream.data_structures.StreamParticleIndex\n   ~yt.frontends.stream.data_structures.StreamParticlesDataset\n   ~yt.frontends.stream.data_structures.StreamDataset\n   ~yt.frontends.stream.fields.StreamFieldInfo\n   ~yt.frontends.stream.io.IOHandlerStream\n   ~yt.frontends.stream.io.IOHandlerStreamHexahedral\n   ~yt.frontends.stream.io.IOHandlerStreamOctree\n   ~yt.frontends.stream.io.StreamParticleIOHandler\n\nytdata\n^^^^^^\n\n.. autosummary::\n\n   ~yt.frontends.ytdata.data_structures.YTDataContainerDataset\n   ~yt.frontends.ytdata.data_structures.YTSpatialPlotDataset\n   ~yt.frontends.ytdata.data_structures.YTGridDataset\n   ~yt.frontends.ytdata.data_structures.YTGridHierarchy\n   ~yt.frontends.ytdata.data_structures.YTGrid\n   ~yt.frontends.ytdata.data_structures.YTNonspatialDataset\n   ~yt.frontends.ytdata.data_structures.YTNonspatialHierarchy\n   ~yt.frontends.ytdata.data_structures.YTNonspatialGrid\n   ~yt.frontends.ytdata.data_structures.YTProfileDataset\n   ~yt.frontends.ytdata.data_structures.YTClumpTreeDataset\n   ~yt.frontends.ytdata.data_structures.YTClumpContainer\n   ~yt.frontends.ytdata.fields.YTDataContainerFieldInfo\n   ~yt.frontends.ytdata.fields.YTGridFieldInfo\n   ~yt.frontends.ytdata.io.IOHandlerYTDataContainerHDF5\n   ~yt.frontends.ytdata.io.IOHandlerYTGridHDF5\n   ~yt.frontends.ytdata.io.IOHandlerYTSpatialPlotHDF5\n   ~yt.frontends.ytdata.io.IOHandlerYTNonspatialhdf5\n\nLoading Data\n------------\n\n.. autosummary::\n\n   ~yt.loaders.load\n   ~yt.loaders.load_uniform_grid\n   ~yt.loaders.load_amr_grids\n   ~yt.loaders.load_particles\n   ~yt.loaders.load_octree\n   ~yt.loaders.load_hexahedral_mesh\n   ~yt.loaders.load_unstructured_mesh\n   ~yt.loaders.load_sample\n\nDerived Datatypes\n-----------------\n\nProfiles and Histograms\n^^^^^^^^^^^^^^^^^^^^^^^\n\nThese types are used to sum data up and either return that sum or return an\naverage.  Typically they are more easily used through the ``ProfilePlot``\n``PhasePlot`` interface. We also provide the ``create_profile`` function\nto create these objects in a uniform manner.\n\n\n.. autosummary::\n\n   ~yt.data_objects.profiles.ProfileND\n   ~yt.data_objects.profiles.Profile1D\n   ~yt.data_objects.profiles.Profile2D\n   ~yt.data_objects.profiles.Profile3D\n   ~yt.data_objects.profiles.ParticleProfile\n   ~yt.data_objects.profiles.create_profile\n\n.. _clump_finding_ref:\n\nClump Finding\n^^^^^^^^^^^^^\n\nThe ``Clump`` object and associated functions can be used for identification\nof topologically disconnected structures, i.e., clump finding.\n\n.. autosummary::\n\n   ~yt.data_objects.level_sets.clump_handling.Clump\n   ~yt.data_objects.level_sets.clump_handling.Clump.add_info_item\n   ~yt.data_objects.level_sets.clump_handling.Clump.add_validator\n   ~yt.data_objects.level_sets.clump_handling.Clump.save_as_dataset\n   ~yt.data_objects.level_sets.clump_handling.find_clumps\n   ~yt.data_objects.level_sets.clump_info_items.add_clump_info\n   ~yt.data_objects.level_sets.clump_validators.add_validator\n\nX-ray Emission Fields\n^^^^^^^^^^^^^^^^^^^^^\n\nThis can be used to create derived fields of X-ray emission in\ndifferent energy bands.\n\n.. autosummary::\n\n   ~yt.fields.xray_emission_fields.XrayEmissivityIntegrator\n   ~yt.fields.xray_emission_fields.add_xray_emissivity_field\n\nField Types\n-----------\n\n.. autosummary::\n\n   ~yt.fields.field_info_container.FieldInfoContainer\n   ~yt.fields.derived_field.DerivedField\n   ~yt.fields.derived_field.ValidateDataField\n   ~yt.fields.derived_field.ValidateGridType\n   ~yt.fields.derived_field.ValidateParameter\n   ~yt.fields.derived_field.ValidateProperty\n   ~yt.fields.derived_field.ValidateSpatial\n\nField Functions\n---------------\n\n.. autosummary::\n\n   ~yt.fields.field_info_container.FieldInfoContainer.add_field\n   ~yt.data_objects.static_output.Dataset.add_field\n   ~yt.data_objects.static_output.Dataset.add_deposited_particle_field\n   ~yt.data_objects.static_output.Dataset.add_mesh_sampling_particle_field\n   ~yt.data_objects.static_output.Dataset.add_gradient_fields\n   ~yt.frontends.stream.data_structures.StreamParticlesDataset.add_sph_fields\n\nParticle Filters\n----------------\n\n.. autosummary::\n\n   ~yt.data_objects.particle_filters.add_particle_filter\n   ~yt.data_objects.particle_filters.particle_filter\n\nImage Handling\n--------------\n\nFor volume renderings and fixed resolution buffers the image object returned is\nan ``ImageArray`` object, which has useful functions for image saving and\nwriting to bitmaps.\n\n.. autosummary::\n\n   ~yt.data_objects.image_array.ImageArray\n\nVolume Rendering\n^^^^^^^^^^^^^^^^\n\nSee also :ref:`volume_rendering`.\n\nHere are the primary entry points and the main classes involved in the\nScene infrastructure:\n\n.. autosummary::\n\n   ~yt.visualization.volume_rendering.volume_rendering.volume_render\n   ~yt.visualization.volume_rendering.volume_rendering.create_scene\n   ~yt.visualization.volume_rendering.off_axis_projection.off_axis_projection\n   ~yt.visualization.volume_rendering.scene.Scene\n   ~yt.visualization.volume_rendering.camera.Camera\n   ~yt.utilities.amr_kdtree.amr_kdtree.AMRKDTree\n\nThe different kinds of sources:\n\n.. autosummary::\n\n   ~yt.visualization.volume_rendering.render_source.RenderSource\n   ~yt.visualization.volume_rendering.render_source.VolumeSource\n   ~yt.visualization.volume_rendering.render_source.PointSource\n   ~yt.visualization.volume_rendering.render_source.LineSource\n   ~yt.visualization.volume_rendering.render_source.BoxSource\n   ~yt.visualization.volume_rendering.render_source.GridSource\n   ~yt.visualization.volume_rendering.render_source.CoordinateVectorSource\n   ~yt.visualization.volume_rendering.render_source.MeshSource\n\nThe different kinds of transfer functions:\n\n.. autosummary::\n\n   ~yt.visualization.volume_rendering.transfer_functions.TransferFunction\n   ~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction\n   ~yt.visualization.volume_rendering.transfer_functions.ProjectionTransferFunction\n   ~yt.visualization.volume_rendering.transfer_functions.PlanckTransferFunction\n   ~yt.visualization.volume_rendering.transfer_functions.MultiVariateTransferFunction\n   ~yt.visualization.volume_rendering.transfer_function_helper.TransferFunctionHelper\n\nThe different kinds of lenses:\n\n.. autosummary::\n\n   ~yt.visualization.volume_rendering.lens.Lens\n   ~yt.visualization.volume_rendering.lens.PlaneParallelLens\n   ~yt.visualization.volume_rendering.lens.PerspectiveLens\n   ~yt.visualization.volume_rendering.lens.StereoPerspectiveLens\n   ~yt.visualization.volume_rendering.lens.FisheyeLens\n   ~yt.visualization.volume_rendering.lens.SphericalLens\n   ~yt.visualization.volume_rendering.lens.StereoSphericalLens\n\nStreamlining\n^^^^^^^^^^^^\n\nSee also :ref:`streamlines`.\n\n\n.. autosummary::\n\n   ~yt.visualization.streamlines.Streamlines\n\nImage Writing\n^^^^^^^^^^^^^\n\nThese functions are all used for fast writing of images directly to disk,\nwithout calling matplotlib.  This can be very useful for high-cadence outputs\nwhere colorbars are unnecessary or for volume rendering.\n\n\n.. autosummary::\n\n   ~yt.visualization.image_writer.multi_image_composite\n   ~yt.visualization.image_writer.write_bitmap\n   ~yt.visualization.image_writer.write_projection\n   ~yt.visualization.image_writer.write_image\n   ~yt.visualization.image_writer.map_to_colors\n   ~yt.visualization.image_writer.strip_colormap_data\n   ~yt.visualization.image_writer.splat_points\n   ~yt.visualization.image_writer.scale_image\n\nWe also provide a module that is very good for generating EPS figures,\nparticularly with complicated layouts.\n\n.. autosummary::\n\n   ~yt.visualization.eps_writer.DualEPS\n   ~yt.visualization.eps_writer.single_plot\n   ~yt.visualization.eps_writer.multiplot\n   ~yt.visualization.eps_writer.multiplot_yt\n   ~yt.visualization.eps_writer.return_colormap\n\n.. _derived-quantities-api:\n\nDerived Quantities\n------------------\n\nSee :ref:`derived-quantities`.\n\n\n.. autosummary::\n\n   ~yt.data_objects.derived_quantities.DerivedQuantity\n   ~yt.data_objects.derived_quantities.DerivedQuantityCollection\n   ~yt.data_objects.derived_quantities.WeightedAverageQuantity\n   ~yt.data_objects.derived_quantities.AngularMomentumVector\n   ~yt.data_objects.derived_quantities.BulkVelocity\n   ~yt.data_objects.derived_quantities.CenterOfMass\n   ~yt.data_objects.derived_quantities.Extrema\n   ~yt.data_objects.derived_quantities.MaxLocation\n   ~yt.data_objects.derived_quantities.MinLocation\n   ~yt.data_objects.derived_quantities.SpinParameter\n   ~yt.data_objects.derived_quantities.TotalMass\n   ~yt.data_objects.derived_quantities.TotalQuantity\n   ~yt.data_objects.derived_quantities.WeightedAverageQuantity\n\n.. _callback-api:\n\nCallback List\n-------------\n\n\nSee also :ref:`callbacks`.\n\n.. autosummary::\n\n   ~yt.visualization.plot_window.PWViewerMPL.clear_annotations\n   ~yt.visualization.plot_modifications.ArrowCallback\n   ~yt.visualization.plot_modifications.CellEdgesCallback\n   ~yt.visualization.plot_modifications.ClumpContourCallback\n   ~yt.visualization.plot_modifications.ContourCallback\n   ~yt.visualization.plot_modifications.CuttingQuiverCallback\n   ~yt.visualization.plot_modifications.GridBoundaryCallback\n   ~yt.visualization.plot_modifications.ImageLineCallback\n   ~yt.visualization.plot_modifications.LinePlotCallback\n   ~yt.visualization.plot_modifications.MagFieldCallback\n   ~yt.visualization.plot_modifications.MarkerAnnotateCallback\n   ~yt.visualization.plot_modifications.ParticleCallback\n   ~yt.visualization.plot_modifications.PointAnnotateCallback\n   ~yt.visualization.plot_modifications.QuiverCallback\n   ~yt.visualization.plot_modifications.RayCallback\n   ~yt.visualization.plot_modifications.ScaleCallback\n   ~yt.visualization.plot_modifications.SphereCallback\n   ~yt.visualization.plot_modifications.StreamlineCallback\n   ~yt.visualization.plot_modifications.TextLabelCallback\n   ~yt.visualization.plot_modifications.TimestampCallback\n   ~yt.visualization.plot_modifications.TitleCallback\n   ~yt.visualization.plot_modifications.TriangleFacetsCallback\n   ~yt.visualization.plot_modifications.VelocityCallback\n\nColormap Functions\n------------------\n\n\nSee also :ref:`colormaps`.\n\n.. autosummary::\n\n   ~yt.visualization.color_maps.add_colormap\n   ~yt.visualization.color_maps.make_colormap\n   ~yt.visualization.color_maps.show_colormaps\n\nFunction List\n-------------\n\n\n.. autosummary::\n\n   ~yt.frontends.ytdata.utilities.save_as_dataset\n   ~yt.data_objects.data_containers.YTDataContainer.save_as_dataset\n   ~yt.data_objects.static_output.Dataset.all_data\n   ~yt.data_objects.static_output.Dataset.box\n   ~yt.funcs.enable_plugins\n   ~yt.funcs.get_pbar\n   ~yt.funcs.humanize_time\n   ~yt.funcs.insert_ipython\n   ~yt.funcs.is_root\n   ~yt.funcs.is_sequence\n   ~yt.funcs.iter_fields\n   ~yt.funcs.just_one\n   ~yt.funcs.only_on_root\n   ~yt.funcs.paste_traceback\n   ~yt.funcs.pdb_run\n   ~yt.funcs.print_tb\n   ~yt.funcs.rootonly\n   ~yt.funcs.time_execution\n   ~yt.data_objects.level_sets.contour_finder.identify_contours\n   ~yt.utilities.parallel_tools.parallel_analysis_interface.enable_parallelism\n   ~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_blocking_call\n   ~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_objects\n   ~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_passthrough\n   ~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_root_only\n   ~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_simple_proxy\n   ~yt.data_objects.data_containers.YTDataContainer.get_field_parameter\n   ~yt.data_objects.data_containers.YTDataContainer.set_field_parameter\n\nMath Utilities\n--------------\n\n\n.. autosummary::\n\n   ~yt.utilities.math_utils.periodic_position\n   ~yt.utilities.math_utils.periodic_dist\n   ~yt.utilities.math_utils.euclidean_dist\n   ~yt.utilities.math_utils.rotate_vector_3D\n   ~yt.utilities.math_utils.modify_reference_frame\n   ~yt.utilities.math_utils.compute_rotational_velocity\n   ~yt.utilities.math_utils.compute_parallel_velocity\n   ~yt.utilities.math_utils.compute_radial_velocity\n   ~yt.utilities.math_utils.compute_cylindrical_radius\n   ~yt.utilities.math_utils.ortho_find\n   ~yt.utilities.math_utils.quartiles\n   ~yt.utilities.math_utils.get_rotation_matrix\n   ~yt.utilities.math_utils.get_sph_r\n   ~yt.utilities.math_utils.resize_vector\n   ~yt.utilities.math_utils.get_sph_theta\n   ~yt.utilities.math_utils.get_sph_phi\n   ~yt.utilities.math_utils.get_cyl_r\n   ~yt.utilities.math_utils.get_cyl_z\n   ~yt.utilities.math_utils.get_cyl_theta\n   ~yt.utilities.math_utils.get_cyl_r_component\n   ~yt.utilities.math_utils.get_cyl_theta_component\n   ~yt.utilities.math_utils.get_cyl_z_component\n   ~yt.utilities.math_utils.get_sph_r_component\n   ~yt.utilities.math_utils.get_sph_phi_component\n   ~yt.utilities.math_utils.get_sph_theta_component\n\n\nMiscellaneous Types\n-------------------\n\n\n.. autosummary::\n\n   ~yt.config.YTConfig\n   ~yt.utilities.parameter_file_storage.ParameterFileStore\n   ~yt.utilities.parallel_tools.parallel_analysis_interface.ObjectIterator\n   ~yt.utilities.parallel_tools.parallel_analysis_interface.ParallelAnalysisInterface\n   ~yt.utilities.parallel_tools.parallel_analysis_interface.ParallelObjectIterator\n\n.. _cosmology-calculator-ref:\n\nCosmology Calculator\n--------------------\n\n.. autosummary::\n\n   ~yt.utilities.cosmology.Cosmology\n   ~yt.utilities.cosmology.Cosmology.hubble_distance\n   ~yt.utilities.cosmology.Cosmology.comoving_radial_distance\n   ~yt.utilities.cosmology.Cosmology.comoving_transverse_distance\n   ~yt.utilities.cosmology.Cosmology.comoving_volume\n   ~yt.utilities.cosmology.Cosmology.angular_diameter_distance\n   ~yt.utilities.cosmology.Cosmology.angular_scale\n   ~yt.utilities.cosmology.Cosmology.luminosity_distance\n   ~yt.utilities.cosmology.Cosmology.lookback_time\n   ~yt.utilities.cosmology.Cosmology.critical_density\n   ~yt.utilities.cosmology.Cosmology.hubble_parameter\n   ~yt.utilities.cosmology.Cosmology.expansion_factor\n   ~yt.utilities.cosmology.Cosmology.z_from_t\n   ~yt.utilities.cosmology.Cosmology.t_from_z\n   ~yt.utilities.cosmology.Cosmology.get_dark_factor\n\nTesting Infrastructure\n----------------------\n\nThe core set of testing functions are re-exported from NumPy,\nand are deprecated (prefer using\n`numpy.testing <https://numpy.org/doc/stable/reference/routines.testing.html>`_\ndirectly).\n\n.. autosummary::\n\n   ~yt.testing.assert_array_equal\n   ~yt.testing.assert_almost_equal\n   ~yt.testing.assert_approx_equal\n   ~yt.testing.assert_array_almost_equal\n   ~yt.testing.assert_equal\n   ~yt.testing.assert_array_less\n   ~yt.testing.assert_string_equal\n   ~yt.testing.assert_array_almost_equal_nulp\n   ~yt.testing.assert_allclose\n   ~yt.testing.assert_raises\n\n`unyt.testing <https://unyt.readthedocs.io/en/stable/modules/unyt.testing.html>`_\nalso provides some specialized functions for comparing arrays in a units-aware\nfashion.\n\nFinally, yt provides the following functions:\n\n.. autosummary::\n\n   ~yt.testing.assert_rel_equal\n   ~yt.testing.amrspace\n   ~yt.testing.expand_keywords\n   ~yt.testing.fake_random_ds\n   ~yt.testing.fake_amr_ds\n   ~yt.testing.fake_particle_ds\n   ~yt.testing.fake_tetrahedral_ds\n   ~yt.testing.fake_hexahedral_ds\n   ~yt.testing.small_fake_hexahedral_ds\n   ~yt.testing.fake_stretched_ds\n   ~yt.testing.fake_vr_orientation_test_ds\n   ~yt.testing.fake_sph_orientation_ds\n   ~yt.testing.fake_sph_grid_ds\n   ~yt.testing.fake_octree_ds\n\nThese are for the pytest infrastructure:\n\n.. autosummary::\n\n    ~conftest.hashing\n    ~yt.utilities.answer_testing.answer_tests.grid_hierarchy\n    ~yt.utilities.answer_testing.answer_tests.parentage_relationships\n    ~yt.utilities.answer_testing.answer_tests.grid_values\n    ~yt.utilities.answer_testing.answer_tests.projection_values\n    ~yt.utilities.answer_testing.answer_tests.field_values\n    ~yt.utilities.answer_testing.answer_tests.pixelized_projection_values\n    ~yt.utilities.answer_testing.answer_tests.small_patch_amr\n    ~yt.utilities.answer_testing.answer_tests.big_patch_amr\n    ~yt.utilities.answer_testing.answer_tests.generic_array\n    ~yt.utilities.answer_testing.answer_tests.sph_answer\n    ~yt.utilities.answer_testing.answer_tests.get_field_size_and_mean\n    ~yt.utilities.answer_testing.answer_tests.plot_window_attribute\n    ~yt.utilities.answer_testing.answer_tests.phase_plot_attribute\n    ~yt.utilities.answer_testing.answer_tests.generic_image\n    ~yt.utilities.answer_testing.answer_tests.axial_pixelization\n    ~yt.utilities.answer_testing.answer_tests.extract_connected_sets\n    ~yt.utilities.answer_testing.answer_tests.VR_image_comparison\n"
  },
  {
    "path": "doc/source/reference/changelog.rst",
    "content": ".. _changelog:\n\nChangeLog\n=========\n\nThis is a non-comprehensive log of changes to yt over its many releases.\n\nContributors\n------------\n\nThe `CREDITS file <https://github.com/yt-project/yt/blob/main/CREDITS>`_\ncontains the most up-to-date list of everyone who has contributed to the yt\nsource code.\n\nyt 4.0\n------\n\nWelcome to yt 4.0! This release is the result of several years worth of\ndeveloper effort and has been in progress since the mid 3.x series. Please keep\nin mind that this release **will** have breaking changes. Please see the yt 4.0\ndifferences page for how you can expect behavior to differ from the 3.x series.\n\nThis is a manually curated list of pull requests that went in to yt 4.0,\nrepresenting a subset of `the full\nlist <https://gist.github.com/matthewturk/7a1f21d98aa5188de7645eda082ce4e6>`__.\n\nNew Functions\n^^^^^^^^^^^^^\n\n-  ``yt.load_sample`` (PR\n   #\\ `2417 <https://github.com/yt-project/yt/pull/2417>`__, PR\n   #\\ `2496 <https://github.com/yt-project/yt/pull/2496>`__, PR\n   #\\ `2875 <https://github.com/yt-project/yt/pull/2875>`__, PR\n   #\\ `2877 <https://github.com/yt-project/yt/pull/2877>`__, PR\n   #\\ `2894 <https://github.com/yt-project/yt/pull/2894>`__, PR\n   #\\ `3262 <https://github.com/yt-project/yt/pull/3262>`__, PR\n   #\\ `3263 <https://github.com/yt-project/yt/pull/3263>`__, PR\n   #\\ `3277 <https://github.com/yt-project/yt/pull/3277>`__, PR\n   #\\ `3309 <https://github.com/yt-project/yt/pull/3309>`__, and PR\n   #\\ `3336 <https://github.com/yt-project/yt/pull/3336>`__)\n-  ``yt.set_log_level`` (PR\n   #\\ `2869 <https://github.com/yt-project/yt/pull/2869>`__ and PR\n   #\\ `3094 <https://github.com/yt-project/yt/pull/3094>`__)\n-  ``list_annotations`` method for plots (PR\n   #\\ `2562 <https://github.com/yt-project/yt/pull/2562>`__)\n\nAPI improvements\n^^^^^^^^^^^^^^^^\n\n-  ``yt.load`` with support for ``os.PathLike`` objects, improved UX\n   and moved a new ``yt.loaders`` module, along with sibling functions (PR\n   #\\ `2405 <https://github.com/yt-project/yt/pull/2405>`__, PR\n   #\\ `2722 <https://github.com/yt-project/yt/pull/2722>`__, PR\n   #\\ `2695 <https://github.com/yt-project/yt/pull/2695>`__, PR\n   #\\ `2818 <https://github.com/yt-project/yt/pull/2818>`__, and PR\n   #\\ `2831 <https://github.com/yt-project/yt/pull/2831>`__, PR\n   #\\ `2832 <https://github.com/yt-project/yt/pull/2832>`__)\n-  ``Dataset`` now has a more useful repr (PR\n   #\\ `3217 <https://github.com/yt-project/yt/pull/3217>`__)\n-  Explicit JPEG export support (PR\n   #\\ `2549 <https://github.com/yt-project/yt/pull/2549>`__)\n-  ``annotate_clear`` is now ``clear_annotations`` (PR\n   #\\ `2569 <https://github.com/yt-project/yt/pull/2569>`__)\n-  Throw an error if field access is ambiguous (PR\n   #\\ `2967 <https://github.com/yt-project/yt/pull/2967>`__)\n\nNewly supported data formats\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nArepo\n~~~~~\n\n-  PR #\\ `1807 <https://github.com/yt-project/yt/pull/1807>`__\n-  PR #\\ `2236 <https://github.com/yt-project/yt/pull/2236>`__\n-  PR #\\ `2244 <https://github.com/yt-project/yt/pull/2244>`__\n-  PR #\\ `2344 <https://github.com/yt-project/yt/pull/2344>`__\n-  PR #\\ `2434 <https://github.com/yt-project/yt/pull/2434>`__\n-  PR #\\ `3258 <https://github.com/yt-project/yt/pull/3258>`__\n-  PR #\\ `3265 <https://github.com/yt-project/yt/pull/3265>`__\n-  PR #\\ `3291 <https://github.com/yt-project/yt/pull/3291>`__\n\nSwift\n~~~~~\n\n-  PR #\\ `1962 <https://github.com/yt-project/yt/pull/1962>`__\n\nImproved support and frontend specific bugfixes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nadaptahop\n~~~~~~~~~\n\n-  PR #\\ `2678 <https://github.com/yt-project/yt/pull/2678>`__\n\nAMRVAC\n~~~~~~\n\n-  PR #\\ `2541 <https://github.com/yt-project/yt/pull/2541>`__\n-  PR #\\ `2745 <https://github.com/yt-project/yt/pull/2745>`__\n-  PR #\\ `2746 <https://github.com/yt-project/yt/pull/2746>`__\n-  PR #\\ `3215 <https://github.com/yt-project/yt/pull/3215>`__\n\nART\n~~~\n\n-  PR #\\ `2688 <https://github.com/yt-project/yt/pull/2688>`__\n\nARTIO\n~~~~~\n\n-  PR #\\ `2613 <https://github.com/yt-project/yt/pull/2613>`__\n\nAthena++\n~~~~~~~~\n\n-  PR #\\ `2985 <https://github.com/yt-project/yt/pull/2985>`__\n\nBoxlib\n~~~~~~\n\n-  PR #\\ `2807 <https://github.com/yt-project/yt/pull/2807>`__\n-  PR #\\ `2814 <https://github.com/yt-project/yt/pull/2814>`__\n-  PR #\\ `2938 <https://github.com/yt-project/yt/pull/2938>`__ (AMReX)\n\nEnzo-E (formerly Enzo-P)\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n-  PR #\\ `3273 <https://github.com/yt-project/yt/pull/3273>`__\n-  PR #\\ `3274 <https://github.com/yt-project/yt/pull/3274>`__\n-  PR #\\ `3290 <https://github.com/yt-project/yt/pull/3290>`__\n-  PR #\\ `3372 <https://github.com/yt-project/yt/pull/3372>`__\n\nfits\n~~~~\n\n-  PR #\\ `2246 <https://github.com/yt-project/yt/pull/2246>`__\n-  PR #\\ `2345 <https://github.com/yt-project/yt/pull/2345>`__\n\nGadget\n~~~~~~\n\n-  PR #\\ `2145 <https://github.com/yt-project/yt/pull/2145>`__\n-  PR #\\ `3233 <https://github.com/yt-project/yt/pull/3233>`__\n-  PR #\\ `3258 <https://github.com/yt-project/yt/pull/3258>`__\n\nGadget FOF Halo\n~~~~~~~~~~~~~~~\n\n-  PR #\\ `2296 <https://github.com/yt-project/yt/pull/2296>`__\n\nGAMER\n~~~~~\n\n-  PR #\\ `3033 <https://github.com/yt-project/yt/pull/3033>`__\n\nGizmo\n~~~~~\n\n-  PR #\\ `3234 <https://github.com/yt-project/yt/pull/3234>`__\n\nMOAB\n~~~~\n\n-  PR #\\ `2856 <https://github.com/yt-project/yt/pull/2856>`__\n\nOwls\n~~~~\n\n-  PR #\\ `3325 <https://github.com/yt-project/yt/pull/3325>`__\n\nRamses\n~~~~~~\n\n-  PR #\\ `2679 <https://github.com/yt-project/yt/pull/2679>`__\n-  PR #\\ `2714 <https://github.com/yt-project/yt/pull/2714>`__\n-  PR #\\ `2960 <https://github.com/yt-project/yt/pull/2960>`__\n-  PR #\\ `3017 <https://github.com/yt-project/yt/pull/3017>`__\n-  PR #\\ `3018 <https://github.com/yt-project/yt/pull/3018>`__\n\nTipsy\n~~~~~\n\n-  PR #\\ `2193 <https://github.com/yt-project/yt/pull/2193>`__\n\nOctree Frontends\n~~~~~~~~~~~~~~~~\n\n-  Ghost zone access (PR\n   #\\ `2425 <https://github.com/yt-project/yt/pull/2425>`__ and PR\n   #\\ `2958 <https://github.com/yt-project/yt/pull/2958>`__)\n-  Volume Rendering (PR\n   #\\ `2610 <https://github.com/yt-project/yt/pull/2610>`__)\n\nConfiguration file\n^^^^^^^^^^^^^^^^^^\n\n-  Config files are now in `TOML <https://toml.io/en/>`__ (PR\n   #\\ `2981 <https://github.com/yt-project/yt/pull/2981>`__)\n-  Allow a local plugin file (PR\n   #\\ `2534 <https://github.com/yt-project/yt/pull/2534>`__)\n-  Allow per-field local config (PR\n   #\\ `1931 <https://github.com/yt-project/yt/pull/1931>`__)\n\nyt CLI\n^^^^^^\n\n-  Fix broken command-line options (PR\n   #\\ `3361 <https://github.com/yt-project/yt/pull/3361>`__)\n-  Drop yt hub command (PR\n   #\\ `3363 <https://github.com/yt-project/yt/pull/3363>`__)\n\nDeprecations\n^^^^^^^^^^^^\n\n-  Smoothed fields are no longer necessary (PR\n   #\\ `2194 <https://github.com/yt-project/yt/pull/2194>`__)\n-  Energy and momentum field names are more accurate (PR\n   #\\ `3059 <https://github.com/yt-project/yt/pull/3059>`__)\n-  Incorrectly-named ``WeightedVariance`` is now\n   ``WeightedStandardDeviation`` and the old name has been deprecated\n   (PR #\\ `3132 <https://github.com/yt-project/yt/pull/3132>`__)\n-  Colormap auto-registration has been changed and yt 4.1 will not\n   register ``cmocean`` (PR\n   #\\ `3175 <https://github.com/yt-project/yt/pull/3175>`__ and PR\n   #\\ `3214 <https://github.com/yt-project/yt/pull/3214>`__)\n\nRemovals\n~~~~~~~~\n\n-  ``analysis_modules`` has been\n   `extracted <https://github.com/yt-project/yt_astro_analysis/>`__ (PR\n   #\\ `2081 <https://github.com/yt-project/yt/pull/2081>`__)\n-  Interactive volume rendering has been\n   `extracted <https://github.com/yt-project/yt_idv/>`__ (PR\n   #\\ `2896 <https://github.com/yt-project/yt/pull/2896>`__)\n-  The bundled version of ``poster`` has been removed (PR\n   #\\ `2783 <https://github.com/yt-project/yt/pull/2783>`__)\n-  The deprecated ``particle_position_relative`` field has been removed\n   (PR #\\ `2901 <https://github.com/yt-project/yt/pull/2901>`__)\n-  Deprecated functions have been removed (PR\n   #\\ `3007 <https://github.com/yt-project/yt/pull/3007>`__)\n-  Vendored packages have been removed (PR\n   #\\ `3008 <https://github.com/yt-project/yt/pull/3008>`__)\n-  ``yt.pmods`` has been removed (PR\n   #\\ `3061 <https://github.com/yt-project/yt/pull/3061>`__)\n-  yt now utilizes unyt as an external package (PR\n   #\\ `2219 <https://github.com/yt-project/yt/pull/2219>`__, PR\n   #\\ `2300 <https://github.com/yt-project/yt/pull/2300>`__, and PR\n   #\\ `2303 <https://github.com/yt-project/yt/pull/2303>`__)\n\nVersion 3.6.1\n-------------\n\nVersion 3.6.1 is a bugfix release. It includes the following backport:\n\n- hotfix: support matplotlib 3.3.0.\n  See `PR 2754 <https://github.com/yt-project/yt/pull/2754>`__.\n\nVersion 3.6.0\n-------------\n\nVersion 3.6.0 our next major release since 3.5.1, which was in February\n2019. It includes roughly 180 pull requests contributed from 39 contributors,\n22 of which committed for their first time to the project.\n\nWe have also updated our project governance and contribution guidelines, which\nyou can `view here <https://yt-project.github.io/governance/>`_ .\n\nWe'd like to thank all of the individuals who contributed to this release. There\nare lots of new features and we're excited to share them with the community.\n\nBreaking Changes\n^^^^^^^^^^^^^^^^\n\nThe following breaking change was introduced. Please be aware that this could\nimpact your code if you use this feature.\n\n- The angular momentum has been reversed compared to previous versions of yt.\n  See `PR 2043 <https://github.com/yt-project/yt/pull/2043>`__.\n\n\nMajor Changes and New Features\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n\n- New frontend support for the code AMRVAC. Many thanks to Clément Robert\n  and Niels Claes who were major contributors to this initiative. Relevant PRs include\n\n  - Initial PR to support AMRVAC native data files\n    `PR 2321 <https://github.com/yt-project/yt/pull/2321>`__.\n  - added support for dust fields and derived fields\n    `PR 2387 <https://github.com/yt-project/yt/pull/2387>`__.\n  - added support for derived fields for hydro runs\n    `PR 2381 <https://github.com/yt-project/yt/pull/2381>`__.\n  - API documentation and docstrings for AMRVAC frontend\n    `PR 2384 <https://github.com/yt-project/yt/pull/2384>`__,\n    `PR 2380 <https://github.com/yt-project/yt/pull/2380>`__,\n    `PR 2382 <https://github.com/yt-project/yt/pull/2382>`__.\n  - testing-related PRs for AMRVAC:\n    `PR 2379 <https://github.com/yt-project/yt/pull/2379>`__,\n    `PR 2360 <https://github.com/yt-project/yt/pull/2360>`__.\n  - add verbosity to logging of geometry or ``geometry_override``\n    `PR 2421 <https://github.com/yt-project/yt/pull/2421>`__.\n  - add attribute to ``_code_unit_attributes`` specific to AMRVAC to ensure\n    consistent renormalisation of AMRVAC datasets. See\n    `PR 2357 <https://github.com/yt-project/yt/pull/2357>`__.\n  - parse AMRVAC's parfiles if user-provided\n    `PR 2369 <https://github.com/yt-project/yt/pull/2369>`__.\n  - ensure that min_level reflects dataset that has refinement\n    `PR 2475 <https://github.com/yt-project/yt/pull/2475>`__.\n  - fix derived unit parsing  `PR 2362 <https://github.com/yt-project/yt/pull/2362>`__.\n  - update energy field to be ``energy_density`` and have units of code\n    pressure  `PR 2376 <https://github.com/yt-project/yt/pull/2376>`__.\n\n- Support for the AdaptaHOP halo finder code\n  `PR 2385 <https://github.com/yt-project/yt/pull/2385>`__.\n- yt now supports geographic transforms and projections of data with\n  cartopy with support from `PR 1966 <https://github.com/yt-project/yt/pull/1966>`__.\n- annotations used to work for only a single point, they now work for multiple points\n  on a plot, see `PR 2122 <https://github.com/yt-project/yt/pull/2122>`__.\n- cosmology calculations now have support for the relativistic energy density of the\n  universe, see `PR 1714 <https://github.com/yt-project/yt/pull/1714>`__.\n  This feature is accessible to cosmology datasets and was added to the Enzo frontend.\n- the eps writer now allows for arrow rotation. this is accessible with\n  the ``rotate`` kwarg in the ``arrow`` function.\n  See `PR 2151 <https://github.com/yt-project/yt/pull/2151>`__.\n- allow for dynamic load balancing with parallel loading of timeseries\n  data using the ``dynamic`` kwarg. `PR 2149 <https://github.com/yt-project/yt/pull/2149>`__.\n- show/hide colorbar and show/hide axes are now available for\n  ``ProfilePlot`` s. These functions were also moved from the PlotWindow to the\n  PlotContainer class. `PR 2169 <https://github.com/yt-project/yt/pull/2169>`__.\n- add support for ipywidgets with an ``__ipython_display__`` method on the\n  FieldTypeContainer. Field variables, source, and the field array can be\n  viewed with this widget. See PRs `PR 1844 <https://github.com/yt-project/yt/pull/1844>`__\n  and `PR 1848 <https://github.com/yt-project/yt/pull/1848>`__,\n  or try ``display(ds.fields)`` in a Jupyter notebook.\n- cut regions can now be made with ``exclude_`` and ``include_`` on a number of objects,\n  including above and below values, inside or outside regions, equal values, or nans.\n  See `PR 1964 <https://github.com/yt-project/yt/pull/1964>`__ and supporting\n  documentation fix at `PR 2262 <https://github.com/yt-project/yt/pull/2262>`__.\n- previously aliased fluid vector fields in curvilinear geometries were not\n  converted to curvilinear coordinates, this was addressed in\n  `PR 2105 <https://github.com/yt-project/yt/pull/2105>`__.\n- 2d polar and 3d cylindrical geometries now support annotate_quivers,\n  streamlines, line integral convolutions, see\n  `PR 2105 <https://github.com/yt-project/yt/pull/2105>`__.\n- add support for exporting data to firefly `PR 2190 <https://github.com/yt-project/yt/pull/2190>`__.\n- gradient fields are now supported in curvilinear geometries. See\n  `PR 2483 <https://github.com/yt-project/yt/pull/2483>`__.\n- plotwindow colorbars now utilize mathtext in their labels,\n  from `PR 2516 <https://github.com/yt-project/yt/pull/2516>`__.\n- raise deprecation warning when using ``mylog.warn``. Instead use\n  ``mylog.warning``. See `PR 2285 <https://github.com/yt-project/yt/pull/2285>`__.\n- extend support of the ``marker``, ``text``, ``line`` and ``sphere`` annotation\n  callbacks to polar geometries  `PR 2466 <https://github.com/yt-project/yt/pull/2466>`__.\n- Support MHD in the GAMER frontend  `PR 2306 <https://github.com/yt-project/yt/pull/2306>`__.\n- Export data container and profile fields to AstroPy QTables and\n  pandas DataFrames  `PR 2418 <https://github.com/yt-project/yt/pull/2418>`__.\n- Add turbo colormap, a colorblind safe version of jet.  See\n  `PR 2339 <https://github.com/yt-project/yt/pull/2339>`__.\n- Enable exporting regular grids (i.e., covering grids, arbitrary grids and\n  smoothed grids) to ``xarray`` `PR 2294 <https://github.com/yt-project/yt/pull/2294>`__.\n- add automatic loading of ``namelist.txt``, which contains the parameter file\n  RAMSES uses to produce output `PR 2347 <https://github.com/yt-project/yt/pull/2347>`__.\n- adds support for a nearest neighbor value field, accessible with\n  the ``add_nearest_neighbor_value_field`` function for particle fields. See\n  `PR 2301 <https://github.com/yt-project/yt/pull/2301>`__.\n- speed up mesh deposition (uses caching) `PR 2136 <https://github.com/yt-project/yt/pull/2136>`__.\n- speed up ghost zone generation.  `PR 2403 <https://github.com/yt-project/yt/pull/2403>`__.\n- ensure that a series dataset has kwargs passed down to data objects `PR 2366 <https://github.com/yt-project/yt/pull/2366>`__.\n\nDocumentation Changes\n^^^^^^^^^^^^^^^^^^^^^\n\nOur documentation has received some attention in the following PRs:\n\n- include donation/funding links in README `PR 2520 <https://github.com/yt-project/yt/pull/2520>`__.\n- Included instructions on how to install yt on the\n  Intel Distribution `PR 2355 <https://github.com/yt-project/yt/pull/2355>`__.\n- include documentation on package vendors `PR 2494 <https://github.com/yt-project/yt/pull/2494>`__.\n- update links to yt hub cookbooks `PR 2477 <https://github.com/yt-project/yt/pull/2477>`__.\n- include relevant API docs in .gitignore `PR 2467 <https://github.com/yt-project/yt/pull/2467>`__.\n- added docstrings for volume renderer cython code. see\n  `PR 2456 <https://github.com/yt-project/yt/pull/2456>`__ and\n  for `PR 2449 <https://github.com/yt-project/yt/pull/2449>`__.\n- update documentation install recommendations to include newer\n  python versions `PR 2452 <https://github.com/yt-project/yt/pull/2452>`__.\n- update custom CSS on docs to sphinx >=1.6.1. See\n  `PR 2199 <https://github.com/yt-project/yt/pull/2199>`__.\n- enhancing the contribution documentation on git, see\n  `PR 2420 <https://github.com/yt-project/yt/pull/2420>`__.\n- update documentation to correctly reference issues suitable for new\n  contributors `PR 2346 <https://github.com/yt-project/yt/pull/2346>`__.\n- fix URLs and spelling errors in a number of the cookbook notebooks\n  `PR 2341 <https://github.com/yt-project/yt/pull/2341>`__.\n- update release docs to include information about building binaries, tagging,\n  and various upload locations. See\n  `PR 2156 <https://github.com/yt-project/yt/pull/2156>`__ and\n  `PR 2160 <https://github.com/yt-project/yt/pull/2160>`__.\n- ensuring the ``load_octree`` API docs are rendered\n  `PR 2088 <https://github.com/yt-project/yt/pull/2088>`__.\n- fixing doc build errors, see: `PR 2077 <https://github.com/yt-project/yt/pull/2077>`__.\n- add an instruction to the doc about continuous mesh colormap\n  `PR 2358 <https://github.com/yt-project/yt/pull/2358>`__.\n- Fix minor typo  `PR 2327 <https://github.com/yt-project/yt/pull/2327>`__.\n- Fix some docs examples `PR 2316 <https://github.com/yt-project/yt/pull/2316>`__.\n- fix sphinx formatting `PR 2409 <https://github.com/yt-project/yt/pull/2409>`__.\n- Improve doc and fix docstring in deposition\n  `PR 2453 <https://github.com/yt-project/yt/pull/2453>`__.\n- Update documentation to reflect usage of rcfile (no brackets allowed),\n  including strings. See `PR 2440 <https://github.com/yt-project/yt/pull/2440>`__.\n\nMinor Enhancements and Bugfixes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n- update pressure units in artio frontend (they were unitless\n  previously) `PR 2521 <https://github.com/yt-project/yt/pull/2521>`__.\n- ensure that modules supported by ``on_demand_imports`` are imported\n  with that functionality `PR 2436 <https://github.com/yt-project/yt/pull/2436/files>`__.\n- fix issues with groups in python3 in Ramses frontend\n  `PR 2092 <https://github.com/yt-project/yt/pull/2092>`__.\n- add tests to ytdata frontend api `PR 2075 <https://github.com/yt-project/yt/pull/2075>`__.\n- update internal field usage from ``particle_{}_relative`` to ``relative_particle_{}``\n  so particle-based fields don't see deprecation warnings\n  see `PR 2073 <https://github.com/yt-project/yt/pull/2073>`__.\n- update save of ``field_data`` in clump finder, see\n  `PR 2079 <https://github.com/yt-project/yt/pull/2079>`__.\n- ensure map.js is included in the sdist for mapserver. See\n  `PR 2158 <https://github.com/yt-project/yt/pull/2158>`__.\n- add wrapping around ``yt_astro_analysis`` where it is used, in case it\n  isn't installed `PR 2159 <https://github.com/yt-project/yt/pull/2159>`__.\n- the contour finder now uses a maximum data value supplied by the user,\n  rather than assuming the maximum value in the data container.\n  Previously this caused issues in the clump finder.\n  See `PR 2170 <https://github.com/yt-project/yt/pull/2170>`__.\n- previously ramses data with non-hilbert ordering crashed.\n  fixed by `PR 2200 <https://github.com/yt-project/yt/pull/2200>`__.\n- fix an issue related to creating a ds9 region with\n  FITS `PR 2335 <https://github.com/yt-project/yt/pull/2335>`__.\n- add a check to see if pluginfilename is specified in\n  ytrc `PR 2319 <https://github.com/yt-project/yt/pull/2319>`__.\n- sort .so input file list so that the yt package builds in a reproducible\n  way `PR 2206 <https://github.com/yt-project/yt/pull/2206>`__.\n- update ``stack`` ufunc usage to include ``axis`` kwarg.\n  See `PR 2204 <https://github.com/yt-project/yt/pull/2204>`__.\n- extend support for field names in RAMSES descriptor file to include all names\n  that don't include a comma. See `PR 2202 <https://github.com/yt-project/yt/pull/2202>`__.\n- ``set_buff_size`` now works for ``OffAxisProjectionPlot``,\n  see `PR 2239 <https://github.com/yt-project/yt/pull/2239>`__.\n- fix chunking for chained cut regions. previously chunking commands would\n  only look at the most recent cut region conditionals, and not any of the\n  previous cut regions. See `PR 2234 <https://github.com/yt-project/yt/pull/2234>`__.\n- update git command in Castro frontend to\n  include ``git describe`` `PR 2235 <https://github.com/yt-project/yt/pull/2235>`__.\n- in datasets with a single oct correctly guess the shape of the\n  array `PR 2241 <https://github.com/yt-project/yt/pull/2241>`__.\n- update ``get_yt_version`` function to support python 3.\n  See `PR 2226 <https://github.com/yt-project/yt/pull/2226>`__.\n- the ``\"stream\"`` frontend now correctly returns ``min_level`` for the mesh refinement.\n  `PR 2519 <https://github.com/yt-project/yt/pull/2519>`__.\n- region expressions (``ds.r[]``) can now be used on 2D\n  datasets `PR 2482 <https://github.com/yt-project/yt/pull/2482>`__.\n- background colors in cylindrical coordinate plots are now set\n  correctly `PR 2517 <https://github.com/yt-project/yt/pull/2517>`__.\n- Utilize current matplotlib interface for the ``_png`` module to write\n  images to disk `PR 2514 <https://github.com/yt-project/yt/pull/2514>`__.\n- fix issue with fortran utils where empty records were not\n  supported `PR 2259 <https://github.com/yt-project/yt/pull/2259>`__.\n- add support for python 3.7 in iterator used by dynamic parallel\n  loading `PR 2265 <https://github.com/yt-project/yt/pull/2265>`__.\n- add support to handle boxlib data where ``raw_fields`` contain\n  ghost zones `PR 2255 <https://github.com/yt-project/yt/pull/2255>`__.\n- update quiver fields to use native units, not assuming\n  cgs `PR 2292 <https://github.com/yt-project/yt/pull/2292>`__.\n- fix annotations on semi-structured mesh data with\n  exodus II `PR 2274 <https://github.com/yt-project/yt/pull/2274>`__.\n- extend support for loading exodus II data\n  `PR 2274 <https://github.com/yt-project/yt/pull/2274>`__.\n- add support for yt to load data generated by WarpX code that\n  includes ``rigid_injected`` species `PR 2289 <https://github.com/yt-project/yt/pull/2289>`__.\n- fix issue in GAMER frontend where periodic boundary conditions were not\n  identified `PR 2287 <https://github.com/yt-project/yt/pull/2287>`__.\n- fix issue in ytdata frontend where data size was calculated to have size\n  ``(nparticles, dimensions)``. Now updated to use\n  ``(nparticles, nparticles, dimensions)``.\n  see `PR 2280 <https://github.com/yt-project/yt/pull/2280>`__.\n- extend support for OpenPMD frontend to load data containing no particles\n  see `PR 2270 <https://github.com/yt-project/yt/pull/2270>`__.\n- raise a meaningful error on negative and zero zooming factors,\n  see `PR 2443 <https://github.com/yt-project/yt/pull/2443>`__.\n- ensure Datasets are consistent in their ``min_level`` attribute.\n  See `PR 2478 <https://github.com/yt-project/yt/pull/2478>`__.\n- adding matplotlib to trove classifiers  `PR 2473 <https://github.com/yt-project/yt/pull/2473>`__.\n- Add support for saving additional formats supported by\n  matplotlib `PR 2318 <https://github.com/yt-project/yt/pull/2318>`__.\n- add support for numpy 1.18.1 and help ensure consistency with unyt\n  `PR 2448 <https://github.com/yt-project/yt/pull/2448>`__.\n- add support for spherical geometries in ``plot_2d``. See\n  `PR 2371 <https://github.com/yt-project/yt/pull/2371>`__.\n- add support for sympy 1.5  `PR 2407 <https://github.com/yt-project/yt/pull/2407>`__.\n- backporting unyt PR 102 for clip  `PR 2329 <https://github.com/yt-project/yt/pull/2329>`__.\n- allow code units in fields ``jeans_mass`` and ``dynamical_time``.\n  See`PR 2454 <https://github.com/yt-project/yt/pull/2454>`__.\n- fix for the case where boxlib nghost is different in different\n  directions `PR 2343 <https://github.com/yt-project/yt/pull/2343>`__.\n- bugfix for numpy 1.18  `PR 2419 <https://github.com/yt-project/yt/pull/2419>`__.\n- Invoke ``_setup_dx`` in the enzo inline analysis. See\n  `PR 2460 <https://github.com/yt-project/yt/pull/2460>`__.\n- Update annotate_timestamp to work with ``\"code\"`` unit system. See\n  `PR 2435 <https://github.com/yt-project/yt/pull/2435>`__.\n- use ``dict.get`` to pull attributes that may not exist in ytdata\n  frontend `PR 2471 <https://github.com/yt-project/yt/pull/2471>`__.\n- solved bug related to slicing out ghost cells in\n  chombo  `PR 2388 <https://github.com/yt-project/yt/pull/2388>`__.\n- correctly register reversed versions of cmocean\n  cmaps  `PR 2390 <https://github.com/yt-project/yt/pull/2390>`__.\n- correctly set plot axes units to ``\"code length\"`` for datasets\n  loaded with ``unit_system=\"code\"``  `PR 2354 <https://github.com/yt-project/yt/pull/2354>`__.\n- deprecate ``ImagePlotContainer.set_cbar_minorticks``. See\n  `PR 2444 <https://github.com/yt-project/yt/pull/2444>`__.\n- enzo-e frontend bugfix for single block datasets. See\n  `PR 2424 <https://github.com/yt-project/yt/pull/2424>`__.\n- explicitly default to solid lines in contour callback. See\n  `PR 2330 <https://github.com/yt-project/yt/pull/2330>`__.\n- replace all bare ``Except`` statements `PR 2474 <https://github.com/yt-project/yt/pull/2474>`__.\n- fix an inconsistency between ``argmax`` and ``argmin`` methods in\n  YTDataContainer class  `PR 2457 <https://github.com/yt-project/yt/pull/2457>`__.\n- fixed extra extension added by ``ImageArray.save()``. See\n  `PR 2364 <https://github.com/yt-project/yt/pull/2364>`__.\n- fix incorrect usage of ``is`` comparison with ``==`` comparison throughout the codebase\n  `PR 2351 <https://github.com/yt-project/yt/pull/2351>`__.\n- fix streamlines ``_con_args`` attribute `PR 2470 <https://github.com/yt-project/yt/pull/2470>`__.\n- fix python 3.8 warnings  `PR 2386 <https://github.com/yt-project/yt/pull/2386>`__.\n- fix some invalid escape sequences.  `PR 2488 <https://github.com/yt-project/yt/pull/2488>`__.\n- fix typo in ``_vorticity_z`` field definition. See\n  `PR 2398 <https://github.com/yt-project/yt/pull/2398>`__.\n- fix an inconsistency in annotate_sphere callback.\n  See `PR 2464 <https://github.com/yt-project/yt/pull/2464>`__.\n- initialize unstructured mesh visualization\n  background to ``nan``  `PR 2308 <https://github.com/yt-project/yt/pull/2308>`__.\n- raise a meaningful error on negative and zero\n  zooming factors  `PR 2443 <https://github.com/yt-project/yt/pull/2443>`__.\n- set ``symlog`` scaling to ``log`` if ``vmin > 0``.\n  See `PR 2485 <https://github.com/yt-project/yt/pull/2485>`__.\n- skip blank lines when reading parameters.\n  See `PR 2406 <https://github.com/yt-project/yt/pull/2406>`__.\n- Update magnetic field handling for RAMSES.\n  See `PR 2377 <https://github.com/yt-project/yt/pull/2377>`__.\n- Update ARTIO frontend to support compressed files.\n  See `PR 2314 <https://github.com/yt-project/yt/pull/2314>`__.\n- Use mirror copy of SDF data  `PR 2334 <https://github.com/yt-project/yt/pull/2334>`__.\n- Use sorted glob in athena to ensure reproducible ordering of\n  grids `PR 2363 <https://github.com/yt-project/yt/pull/2363>`__.\n- fix cartopy failures by ensuring data is in lat/lon when passed to\n  cartopy `PR 2378 <https://github.com/yt-project/yt/pull/2378>`__.\n- enforce unit consistency in plot callbacks, which fixes some unexpected\n  behaviour in the plot annotations callbacks that use the plot\n  window width or the data width `PR 2524 <https://github.com/yt-project/yt/pull/2524>`__.\n\nSeparate from our list of minor enhancements and bugfixes, we've grouped PRs\nrelated to infrastructure and testing in the next three sub-sub-sub sections.\n\nTesting and Infrastructure\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n- infrastructure to change our testing from nose to pytest, see\n  `PR 2401 <https://github.com/yt-project/yt/pull/2401>`__.\n- Adding test_requirements and test_minimum requirements files to have\n  bounds on installed testing versioning `PR 2083 <https://github.com/yt-project/yt/pull/2083>`__.\n- Update the test failure report to include all failed tests related\n  to a single test specification `PR 2084 <https://github.com/yt-project/yt/pull/2084>`__.\n- add required dependencies for docs testing on Jenkins. See\n  `PR 2090 <https://github.com/yt-project/yt/pull/2090>`__.\n- suppress pyyaml warning that pops up when running\n  tests `PR 2182 <https://github.com/yt-project/yt/pull/2182>`__.\n- add tests for pre-existing ytdata datasets. See\n  `PR 2229 <https://github.com/yt-project/yt/pull/2229>`__.\n- add a test to check if cosmology calculator and cosmology dataset\n  share the same unit registry `PR 2230 <https://github.com/yt-project/yt/pull/2230>`__.\n- fix kh2d test name  `PR 2342 <https://github.com/yt-project/yt/pull/2342>`__.\n- disable OSNI projection answer test to remove cartopy errors `PR 2350 <https://github.com/yt-project/yt/pull/2350>`__.\n\nCI related support\n~~~~~~~~~~~~~~~~~~\n\n- disable coverage on OSX to speed up travis testing and avoid\n  timeouts `PR 2076 <https://github.com/yt-project/yt/pull/2076>`__.\n- update travis base images on Linux and\n  MacOSX `PR 2093 <https://github.com/yt-project/yt/pull/2093>`__.\n- add ``W504`` and ``W605`` to ignored flake8 errors, see\n  `PR 2078 <https://github.com/yt-project/yt/pull/2078>`__.,\n- update pyyaml version in ``test_requirements.txt`` file to address\n  github warning `PR 2148 <https://github.com/yt-project/yt/pull/2148/files>`__.,\n- fix travis build errors resulting from numpy and cython being\n  unavailable `PR 2171 <https://github.com/yt-project/yt/pull/2171>`__.\n- fix appveyor build failures `PR 2231 <https://github.com/yt-project/yt/pull/2231>`__.\n- Add Python 3.7 and Python 3.8 to CI test jobs. See\n  `PR 2450 <https://github.com/yt-project/yt/pull/2450>`__.\n- fix build failure on Windows `PR 2333 <https://github.com/yt-project/yt/pull/2333>`__.\n- fix warnings due to travis configuration file. See\n  `PR 2451 <https://github.com/yt-project/yt/pull/2451>`__.\n- install pyyaml on appveyor `PR 2367 <https://github.com/yt-project/yt/pull/2367>`__.\n- install sympy 1.4 on appveyor to work around regression in\n  1.5  `PR 2395 <https://github.com/yt-project/yt/pull/2395>`__.\n- update CI recipes to fix recent failures  `PR 2489 <https://github.com/yt-project/yt/pull/2489>`__.\n\nOther Infrastructure\n~~~~~~~~~~~~~~~~~~~~\n\n- Added a welcomebot to our github page for new contributors, see\n  `PR 2181 <https://github.com/yt-project/yt/pull/2181>`__.\n- Added a pep8 bot to pre-run before tests, see\n  `PR 2179 <https://github.com/yt-project/yt/pull/2179>`__,\n  `PR 2184 <https://github.com/yt-project/yt/pull/2184>`__ and\n  `PR 2185 <https://github.com/yt-project/yt/pull/2185>`__.\n\nVersion 3.5.0\n-------------\n\nVersion 3.5.0 is the first major release of yt since August 2017. It includes\n328 pull requests from 41 contributors, including 22 new contributors.\n\nMajor Changes\n^^^^^^^^^^^^^\n\n- ``yt.analysis_modules`` has been deprecated in favor of the new\n  ``yt_astro_analysis`` package. New features and new astronomy-specific\n  analysis modules will go into ``yt_astro_analysis`` and importing from\n  ``yt.analysis_modules`` will raise a noisy warning. We will remove\n  ``yt.analysis_modules`` in a future release. See `PR 1938\n  <https://github.com/yt-project/yt/pull/1938>`__.\n- Vector fields and derived fields depending on vector fields have been\n  systematically updated to account for a bulk correction field parameter. For\n  example, for the velocity field, all derived fields that depend on velocity\n  will now account for the ``\"bulk_velocity\"`` field parameter. In addition, we\n  have defined ``\"relative_velocity\"`` and ``\"relative_magnetic_field\"`` fields\n  that include the bulk correction. Both of these are vector fields, to access\n  the components, use e.g. ``\"relative_velocity_x\"``. The\n  ``\"particle_position_relative\"`` and ``\"particle_velocity_relative\"`` fields\n  have been deprecated. See `PR 1693\n  <https://github.com/yt-project/yt/pull/1693>`__ and `PR 2022\n  <https://github.com/yt-project/yt/pull/2022>`__.\n- Aliases to spatial fields with the ``\"gas\"`` field type will now be returned\n  in the default unit system for the dataset. As an example the ``\"x\"`` field\n  might resolve to the field tuples ``(\"index\", \"x\")`` or ``(\"gas\",\n  \"x\")``. Accessing the former will return data in code units while the latter\n  will return data in whatever unit system the dataset is configured to use\n  (CGS, by default). This means that to ensure the units of a spatial field will\n  always be consistent, one must access the field as a tuple, explicitly\n  specifying the field type. Accessing a spatial field using a string field name\n  may return data in either code units or the dataset's default unit system\n  depending on the history of field accesses prior to accessing that field. In\n  the future accessing fields using an ambiguous field name will raise an\n  error. See `PR 1799 <https://github.com/yt-project/yt/pull/1799>`__ and `PR\n  1850 <https://github.com/yt-project/yt/pull/1850>`__.\n- The ``max_level`` and ``min_level`` attributes of yt data objects now\n  correctly update the state of the underlying data objects when set. In\n  addition we have added an example to the cookbook that shows how to downsample\n  AMR data using this functionality. See `PR 1737\n  <https://github.com/yt-project/yt/pull/1737>`__.\n- It is now possible to customize the formatting of labels for ion species\n  fields. Rather than using the default spectroscopic notation, one can call\n  ``ds.set_field_label_format(\"ionization_label\", \"plus_minus\")`` to use the\n  more traditional notation where ionization state is indicated with ``+`` and\n  ``-`` symbols. See `PR 1867 <https://github.com/yt-project/yt/pull/1867>`__.\n\nImprovements to the RAMSES frontend\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWe would particularly like to recognize Corentin Cadiou for his tireless work over the past year on improving support for RAMSES and octree AMR data in yt.\n\n- Added support for reading RAMSES sink particles. See `PR 1548\n  <https://github.com/yt-project/yt/pull/1548>`__.\n- Add support for the new self-describing Ramses particle output format. See `PR\n  1616 <https://github.com/yt-project/yt/pull/1616>`__.\n- It is now possible to restrict the domain of a loaded Ramses dataset by\n  passing a ``bbox`` keyword argument to ``yt.load()``. If passed this\n  corresponds to the coordinates of the top-left and bottom-right hand corner of\n  the subvolume to load. Data outside the bounding box will be ignored. This is\n  useful for loading very large Ramses datasets where yt currently has poor\n  scaling. See `PR 1637 <https://github.com/yt-project/yt/pull/1637>`__.\n- The Ramses ``\"particle_birth_time\"`` field now contains the time when star\n  particles form in a simulation in CGS units, formerly these times were only\n  accessible via the incorrectly named ``\"particle_age\"`` field in conformal\n  units. Correspondingly the ``\"particle_age\"`` field has been deprecated. The\n  conformal birth time is not available via the ``\"conformal_birth_time``\"\n  field. See `PR 1649 <https://github.com/yt-project/yt/pull/1649>`__.\n- Substantial performance improvement for reading RAMSES AMR data. See `PR 1671\n  <https://github.com/yt-project/yt/pull/1671>`__.\n- The RAMSES frontend will now produce less voluminous logging feedback when\n  loading the dataset or reading data. This is particularly noticeable for very\n  large datasets with many CPU files. See `PR 1738\n  <https://github.com/yt-project/yt/pull/1738>`__.\n- Avoid repeated parsing of RAMSES particle and RT descriptors. See `PR 1739\n  <https://github.com/yt-project/yt/pull/1739>`__.\n- Added support for reading the RAMSES gravitational potential field. See `PR\n  1751 <https://github.com/yt-project/yt/pull/1751>`__.\n- Add support for RAMSES datasets that use the ``groupsize`` feature. See `PR\n  1769 <https://github.com/yt-project/yt/pull/1769>`__.\n- Dramatically improve the overall performance of the RAMSES frontend. See `PR\n  1771 <https://github.com/yt-project/yt/pull/1771>`__.\n\nAdditional Improvements\n^^^^^^^^^^^^^^^^^^^^^^^\n\n- Added support for particle data in the Enzo-E frontend. See `PR 1490\n  <https://github.com/yt-project/yt/pull/1490>`__.\n- Added an ``equivalence`` keyword argument to ``YTArray.in_units()`` and\n  ``YTArray.to()``. This makes it possible to specify an equivalence when\n  converting data to a new unit. Also added ``YTArray.to_value()`` which allows\n  converting to a new unit, then stripping off the units to return a plain numpy\n  array. See `PR 1563 <https://github.com/yt-project/yt/pull/1563>`__.\n- Rather than crashing, yt will now assume default values for cosmology\n  parameters in Gadget HDF5 data if it cannot find the relevant header\n  information. See `PR 1578\n  <https://github.com/yt-project/yt/pull/1578>`__.\n- Improve detection for OpenMP support at compile-time, including adding support\n  for detecting OpenMP on Windows. See `PR 1591\n  <https://github.com/yt-project/yt/pull/1591>`__, `PR 1695\n  <https://github.com/yt-project/yt/pull/1695>`__ and `PR 1696\n  <https://github.com/yt-project/yt/pull/1696>`__.\n- Add support for 2D cylindrical data for most plot callbacks. See `PR 1598\n  <https://github.com/yt-project/yt/pull/1598>`__.\n- Particles outside the domain are now ignored by ``load_uniform_grid()`` and\n  ``load_amr_grids()``. See `PR 1602\n  <https://github.com/yt-project/yt/pull/1602>`__.\n- Fix incorrect units for the Gadget internal energy field in cosmology\n  simulations. See `PR 1611\n  <https://github.com/yt-project/yt/pull/1611>`__.\n- Add support for calculating covering grids in parallel. See `PR 1612\n  <https://github.com/yt-project/yt/pull/1612>`__.\n- The number of particles in a dataset loaded by the stream frontend (e.g. via\n  ``load_uniform_grid``) no longer needs to be explicitly provided via the\n  ``number_of_particles`` keyword argument, using the ``number_of_particles``\n  keyword will now generate a deprecation warning. See `PR 1620\n  <https://github.com/yt-project/yt/pull/1620>`__.\n- Add support for non-cartesian GAMER data. See `PR 1622\n  <https://github.com/yt-project/yt/pull/1622>`__.\n- If a particle filter depends on another particle filter, both particle filters\n  will be registered for a dataset if the dependent particle filter is\n  registered with a dataset. See `PR 1624\n  <https://github.com/yt-project/yt/pull/1624>`__.\n- The ``save()`` method of the various yt plot objects now optionally can accept\n  a tuple of strings instead of a string. If a tuple is supplied, the elements\n  are joined with ``os.sep`` to form a path. See `PR 1630\n  <https://github.com/yt-project/yt/pull/1630>`__.\n- The quiver callback now accepts a ``plot_args`` keyword argument that allows\n  passing keyword arguments to matplotlib to allow for customization of the\n  quiver plot. See `PR 1636 <https://github.com/yt-project/yt/pull/1636>`__.\n- Updates and improvements for the OpenPMD frontend. See `PR 1645\n  <https://github.com/yt-project/yt/pull/1645>`__.\n- The mapserver now works correctly under Python3 and has new features like a\n  colormap selector and plotting multiple fields via layers. See `PR 1654\n  <https://github.com/yt-project/yt/pull/1654>`__ and `PR 1668\n  <https://github.com/yt-project/yt/pull/1668>`__.\n- Substantial performance improvement for calculating the gravitational\n  potential in the clump finder. See `PR 1684\n  <https://github.com/yt-project/yt/pull/1684>`__.\n- Added new methods to ``ProfilePlot``: ``set_xlabel()``, ``set_ylabel()``,\n  ``annotate_title()``, and ``annotate_text()``. See `PR 1700\n  <https://github.com/yt-project/yt/pull/1700>`__ and `PR 1705\n  <https://github.com/yt-project/yt/pull/1705>`__.\n- Speedup for parallel halo finding operation for the FOF and HOP halo\n  finders. See `PR 1724 <https://github.com/yt-project/yt/pull/1724>`__.\n- Add support for halo finding using the rockstar halo finder on Python3. See\n  `PR 1740 <https://github.com/yt-project/yt/pull/1740>`__.\n- The ``ValidateParameter`` field validator has gained the ability for users to\n  explicitly specify the values of field parameters during field detection. This\n  makes it possible to write fields that access different sets of fields\n  depending on the value of the field parameter. For example, a field might\n  define an ``'axis'`` field parameter that can be either ``'x'``, ``'y'`` or\n  ``'z'``. One can now explicitly tell the field detection system to access the\n  field using all three values of ``'axis'``. This improvement avoids errors one\n  would see now where only one value or an invalid value of the field parameter\n  will be tested by yt. See `PR 1741\n  <https://github.com/yt-project/yt/pull/1741>`__.\n- It is now legal to pass a dataset instance as the first argument to\n  ``ProfilePlot`` and ``PhasePlot``. This is equivalent to passing\n  ``ds.all_data()``.\n- Functions that accept a ``(length, unit)`` tuple (e.g. ``(3, 'km')`` for 3\n  kilometers) will not raise an error if ``length`` is a ``YTQuantity`` instance\n  with units attached. See `PR 1749\n  <https://github.com/yt-project/yt/pull/1749>`__.\n- The ``annotate_timestamp`` plot annotation now optionally accepts a\n  ``time_offset`` keyword argument that sets the zero point of the time\n  scale. Additionally, the ``annotate_scale`` plot annotation now accepts a\n  ``format`` keyword argument, allowing custom formatting of the scale\n  annotation. See `PR 1755 <https://github.com/yt-project/yt/pull/1755>`__.\n- Add support for magnetic field variables and creation time fields in the GIZMO\n  frontend. See `PR 1756 <https://github.com/yt-project/yt/pull/1756>`__ and `PR\n  1914 <https://github.com/yt-project/yt/pull/1914>`__.\n- ``ParticleProjectionPlot`` now supports the ``annotate_particles`` plot\n  callback. See `PR 1765 <https://github.com/yt-project/yt/pull/1765>`__.\n- Optimized the performance of off-axis projections for octree AMR data. See `PR\n  1766 <https://github.com/yt-project/yt/pull/1766>`__.\n- Added support for several radiative transfer fields in the ARTIO frontend. See\n  `PR 1804 <https://github.com/yt-project/yt/pull/1804>`__.\n- Performance improvement for Boxlib datasets that don't use AMR. See `PR 1834\n  <https://github.com/yt-project/yt/pull/1834>`__.\n- It is now possible to set custom profile bin edges. See `PR 1837\n  <https://github.com/yt-project/yt/pull/1837>`__.\n- Dropped support for Python3.4. See `PR 1840\n  <https://github.com/yt-project/yt/pull/1840>`__.\n- Add support for reading RAMSES cooling fields. See `PR 1853\n  <https://github.com/yt-project/yt/pull/1853>`__.\n- Add support for NumPy 1.15. See `PR 1854\n  <https://github.com/yt-project/yt/pull/1854>`__.\n- Ensure that functions defined in the plugins file are available in the yt\n  namespace. See `PR 1855 <https://github.com/yt-project/yt/pull/1855>`__.\n- Creating a profiles with log-scaled bins but where the bin edges are negative\n  or zero now raises an error instead of silently generating a corrupt,\n  incorrect answer. See `PR 1856\n  <https://github.com/yt-project/yt/pull/1856>`__.\n- Systematically added validation for inputs to data object initializers. See\n  `PR 1871 <https://github.com/yt-project/yt/pull/1871>`__.\n- It is now possible to select only a specific particle type in the particle\n  trajectories analysis module. See `PR 1887\n  <https://github.com/yt-project/yt/pull/1887>`__.\n- Substantially improve the performance of selecting particle fields with a\n  ``cut_region`` data object. See `PR 1892\n  <https://github.com/yt-project/yt/pull/1892>`__.\n- The ``iyt`` command-line entry-point into IPython now installs yt-specific\n  tab-completions. See `PR 1900 <https://github.com/yt-project/yt/pull/1900>`__.\n- Derived quantities have been systematically updated to accept a\n  ``particle_type`` keyword argument, allowing easier analysis of only a single\n  particle type. See `PR 1902 <https://github.com/yt-project/yt/pull/1902>`__\n  and `PR 1922 <https://github.com/yt-project/yt/pull/1922>`__.\n- The ``annotate_streamlines()`` function now accepts a ``display_threshold``\n  keyword argument. This suppresses drawing streamlines over any region of a\n  dataset where the field being displayed is less than the threshold. See `PR\n  1922 <https://github.com/yt-project/yt/pull/1922>`__.\n- Add support for 2D nodal data. See `PR 1923\n  <https://github.com/yt-project/yt/pull/1923>`__.\n- Add support for GAMER outputs that use patch groups. This substantially\n  reduces the memory requirements for loading large GAMER datasets. See `PR 1935\n  <https://github.com/yt-project/yt/pull/1935>`__.\n- Add a ``data_source`` keyword argument to the ``annotate_particles`` plot\n  callback. See `PR 1937 <https://github.com/yt-project/yt/pull/1937>`__.\n- Define species fields in the NMSU Art frontend. See `PR 1981\n  <https://github.com/yt-project/yt/pull/1981>`__.\n- Added a ``__format__`` implementation for ``YTArray``. See `PR 1985\n  <https://github.com/yt-project/yt/pull/1985>`__.\n- Derived fields that use a particle filter now only need to be derived for the\n  particle filter type, not for the particle types used to define the particle\n  filter. See `PR 1993 <https://github.com/yt-project/yt/pull/1993>`__.\n- Added support for periodic visualizations using\n  ``ParticleProjectionPlot``. See `PR 1996\n  <https://github.com/yt-project/yt/pull/1996>`__.\n- Added ``YTArray.argsort()``. See `PR 2002\n  <https://github.com/yt-project/yt/pull/2002>`__.\n- Calculate the header size from the header specification in the Gadget frontend\n  to allow reading from Gadget binary datasets with nonstandard headers. See `PR\n  2005 <https://github.com/yt-project/yt/pull/2005>`__ and `PR 2036\n  <https://github.com/yt-project/yt/pull/2036>`__.\n- Save the standard deviation in ``profile.save_as_dataset()``. See `PR 2008\n  <https://github.com/yt-project/yt/pull/2008>`__.\n- Allow the ``color`` keyword argument to be passed to matplotlib in the\n  ``annotate_clumps`` callback to control the color of the clump annotation. See\n  `PR 2019 <https://github.com/yt-project/yt/pull/2019>`__.\n- Raise an exception when profiling fields of unequal shape. See `PR 2025\n  <https://github.com/yt-project/yt/pull/2025>`__.\n- The clump info dictionary is now populated as clumps get created instead of\n  during ``clump.save_as_dataset()``. See `PR 2053\n  <https://github.com/yt-project/yt/pull/2053>`__.\n- Avoid segmentation fault in slice selector by clipping slice integer\n  coordinates. See `PR 2055 <https://github.com/yt-project/yt/pull/2055>`__.\n\n\nMinor Enhancements and Bugfixes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n- Fix incorrect use of floating point division in the parallel analysis framework.\n  See `PR 1538 <https://github.com/yt-project/yt/pull/1538>`__.\n- Fix integration with that matplotlib QT backend for interactive plotting.\n  See `PR 1540 <https://github.com/yt-project/yt/pull/1540>`__.\n- Add support for the particle creation time field in the GAMER frontend.\n  See `PR 1546 <https://github.com/yt-project/yt/pull/1546>`__.\n- Various minor improvements to the docs. See `PR 1542\n  <https://github.com/yt-project/yt/pull/1542>`__. and `PR 1547\n  <https://github.com/yt-project/yt/pull/1547>`__.\n- Add better error handling for invalid tipsy aux files. See `PR 1549\n  <https://github.com/yt-project/yt/pull/1549>`__.\n- Fix typo in default Gadget header specification. See `PR 1550\n  <https://github.com/yt-project/yt/pull/1550>`__.\n- Use the git version in the get_yt_version function. See `PR 1551\n  <https://github.com/yt-project/yt/pull/1551>`__.\n- Assume dimensionless units for fields from FITS datasets when we can't infer\n  the units. See `PR 1553 <https://github.com/yt-project/yt/pull/1553>`__.\n- Autodetect ramses extra particle fields. See `PR 1555\n  <https://github.com/yt-project/yt/pull/1555>`__.\n- Fix issue with handling unitless halo quantities in HaloCatalog. See `PR 1558\n  <https://github.com/yt-project/yt/pull/1558>`__.\n- Track the halo catalog creation process using a parallel-safe progress bar.\n  See `PR 1559 <https://github.com/yt-project/yt/pull/1559>`__.\n- The PPV Cube functionality no longer crashes if there is no temperature field\n  in the dataset. See `PR 1562\n  <https://github.com/yt-project/yt/pull/1562>`__.\n- Fix crash caused by saving the ``'x'``, ``'y'``, or ``'z'`` fields in\n  clump.save_as_dataset().  See `PR 1567\n  <https://github.com/yt-project/yt/pull/1567>`__.\n- Accept both string and tuple field names in ``ProfilePlot.set_unit()`` and\n  ``PhasePlot.set_unit()``. See `PR 1568\n  <https://github.com/yt-project/yt/pull/1568>`__.\n- Fix issues with some arbitrary grid attributes not being reloaded properly\n  after being saved with ``save_as_dataset()``. See `PR 1569\n  <https://github.com/yt-project/yt/pull/1569>`__.\n- Fix units issue in the light cone projection operation. See `PR 1574\n  <https://github.com/yt-project/yt/pull/1574>`__.\n- Use ``astropy.wcsaxes`` instead of the independent ``wcsaxes`` project.  See\n  `PR 1577 <https://github.com/yt-project/yt/pull/1577>`__.\n- Correct typo in WarpX field definitions. See `PR 1583\n  <https://github.com/yt-project/yt/pull/1583>`__.\n- Avoid crashing when loading an Enzo dataset with a parameter file that has\n  commented out parameters. See `PR 1586\n  <https://github.com/yt-project/yt/pull/1586>`__.\n- Fix a corner case in the clump finding machinery where the reference to the\n  parent clump is invalid after pruning a child clump that has no siblings. See\n  `PR 1587 <https://github.com/yt-project/yt/pull/1587>`__.\n- Fix issues with setting up yt fields for the magnetic and velocity field\n  components and associated derived fields in curvilinear coordinate\n  systems. See `PR 1588 <https://github.com/yt-project/yt/pull/1588>`__ and `PR\n  1687 <https://github.com/yt-project/yt/pull/1687>`__.\n- Fix incorrect profile values when the profile weight field has values equal to\n  zero. See `PR 1590 <https://github.com/yt-project/yt/pull/1590>`__.\n- Fix issues with making matplotlib animations of a\n  ``ParticleProjectionPlot``. See `PR 1594\n  <https://github.com/yt-project/yt/pull/1594>`__.\n- The ``Scene.annotate_axes()`` function will now use the correct colors for\n  drawing the axes annotation. See `PR 1596\n  <https://github.com/yt-project/yt/pull/1596>`__.\n- Fix incorrect default plot bounds for a zoomed-in slice plot of a 2D\n  cylindrical dataset. See `PR 1597\n  <https://github.com/yt-project/yt/pull/1597>`__.\n- Fix issue where field accesses on 2D grids would return data with incorrect\n  shapes. See `PR 1603 <https://github.com/yt-project/yt/pull/1603>`__.\n- Added a cookbook example for a multipanel phase plot. See `PR 1605\n  <https://github.com/yt-project/yt/pull/1605>`__.\n- Boolean simulation parameters in the Boxlib frontend will now be interpreted\n  correctly. See `PR 1619 <https://github.com/yt-project/yt/pull/1619>`__.\n- The ``ds.particle_type_counts`` attribute will now be populated correctly for\n  AMReX data.\n- The ``\"rad\"`` unit (added for compatibility with astropy) now has the correct\n  dimensions of angle instead of solid angle. See `PR 1628\n  <https://github.com/yt-project/yt/pull/1628>`__.\n- Fix units issues in several plot callbacks. See `PR 1633\n  <https://github.com/yt-project/yt/pull/1633>`__ and `PR 1674\n  <https://github.com/yt-project/yt/pull/1674>`__.\n- Various fixes for how WarpX fields are interpreted. See `PR 1634\n  <https://github.com/yt-project/yt/pull/1634>`__.\n- Fix incorrect units in the automatically deposited particle fields. See `PR\n  1638 <https://github.com/yt-project/yt/pull/1638>`__.\n- It is now possible to set the axes background color after calling\n  ``plot.hide_axes()``. See `PR 1662\n  <https://github.com/yt-project/yt/pull/1662>`__.\n- Fix a typo in the name of the ``colors`` keyword argument passed to matplotlib\n  for the contour callback. See `PR 1664\n  <https://github.com/yt-project/yt/pull/1664>`__.\n- Add support for Enzo Active Particle fields that arrays. See `PR 1665\n  <https://github.com/yt-project/yt/pull/1665>`__.\n- Avoid crash when generating halo catalogs from the rockstar halo finder for\n  small simulation domains. See `PR 1679\n  <https://github.com/yt-project/yt/pull/1679>`__.\n- The clump callback now functions correctly for a reloaded clump dataset. See\n  `PR 1683 <https://github.com/yt-project/yt/pull/1683>`__.\n- Fix incorrect calculation for tangential components of vector fields. See `PR\n  1688 <https://github.com/yt-project/yt/pull/1688>`__.\n- Allow halo finders to run in parallel on Python3. See `PR 1690\n  <https://github.com/yt-project/yt/pull/1690>`__.\n- Fix issues with Gadget particle IDs for simulations with large numbers of\n  particles being incorrectly rounded. See `PR 1692\n  <https://github.com/yt-project/yt/pull/1692>`__.\n- ``ParticlePlot`` no longer needs to be passed spatial fields in a particular\n  order to ensure that a ``ParticleProjectionPlot`` is returned. See `PR 1697\n  <https://github.com/yt-project/yt/pull/1697>`__.\n- Accessing data from a FLASH grid directly now returns float64 data. See `PR\n  1708 <https://github.com/yt-project/yt/pull/1708>`__.\n- Fix periodicity check in ``YTPoint`` data object. See `PR 1712\n  <https://github.com/yt-project/yt/pull/1712>`__.\n- Avoid crash on matplotlib 2.2.0 when generating yt plots with symlog\n  colorbars. See `PR 1720 <https://github.com/yt-project/yt/pull/1720>`__.\n- Avoid crash when FLASH ``\"unitsystem\"`` parameter is quoted in the HDF5\n  file. See `PR 1722 <https://github.com/yt-project/yt/pull/1722>`__.\n- Avoid issues with creating custom particle filters for OWLS/EAGLE\n  datasets. See `PR 1723 <https://github.com/yt-project/yt/pull/1723>`__.\n- Adapt to behavior change in matplotlib that caused plot inset boxes for\n  annotated text to be drawn when none was requested. See `PR 1731\n  <https://github.com/yt-project/yt/pull/1731>`__ and `PR 1827\n  <https://github.com/yt-project/yt/pull/1827>`__.\n- Fix clump finder ignoring field parameters. See `PR 1732\n  <https://github.com/yt-project/yt/pull/1732>`__.\n- Avoid generating NaNs in x-ray emission fields. See `PR 1742\n  <https://github.com/yt-project/yt/pull/1742>`__.\n- Fix compatibility with Sphinx 1.7 when building the docs. See `PR 1743\n  <https://github.com/yt-project/yt/pull/1743>`__.\n- Eliminate usage of deprecated ``\"clobber\"`` keyword argument for various\n  usages of astropy in yt. See `PR 1744\n  <https://github.com/yt-project/yt/pull/1744>`__.\n- Fix incorrect definition of the ``\"d\"`` unit (an alias of ``\"day\"``). See `PR\n  1746 <https://github.com/yt-project/yt/pull/1746>`__.\n- ``PhasePlot.set_log()`` now correctly handles tuple field names as well as\n  string field names. See `PR 1787\n  <https://github.com/yt-project/yt/pull/1787>`__.\n- Fix incorrect axis order in aitoff pixelizer. See `PR 1791\n  <https://github.com/yt-project/yt/pull/1791>`__.\n- Fix crash in when exporting a surface as a ply model. See `PR 1792\n  <https://github.com/yt-project/yt/pull/1792>`__ and `PR 1817\n  <https://github.com/yt-project/yt/pull/1817>`__.\n- Fix crash in scene.save_annotated() in newer numpy versions. See `PR 1793\n  <https://github.com/yt-project/yt/pull/1793>`__.\n- Many tests no longer depend on real datasets. See `PR 1801\n  <https://github.com/yt-project/yt/pull/1801>`__, `PR 1805\n  <https://github.com/yt-project/yt/pull/1805>`__, `PR 1809\n  <https://github.com/yt-project/yt/pull/1809>`__, `PR 1883\n  <https://github.com/yt-project/yt/pull/1883>`__, and `PR 1941\n  <https://github.com/yt-project/yt/pull/1941>`__\n- New tests were added to improve test coverage or the performance of the\n  tests. See `PR 1820 <https://github.com/yt-project/yt/pull/1820>`__, `PR 1831\n  <https://github.com/yt-project/yt/pull/1831>`__, `PR 1833\n  <https://github.com/yt-project/yt/pull/1833>`__, `PR 1841\n  <https://github.com/yt-project/yt/pull/1841>`__, `PR 1842\n  <https://github.com/yt-project/yt/pull/1842>`__, `PR 1885\n  <https://github.com/yt-project/yt/pull/1885>`__, `PR 1886\n  <https://github.com/yt-project/yt/pull/1886>`__, `PR 1952\n  <https://github.com/yt-project/yt/pull/1952>`__, `PR 1953\n  <https://github.com/yt-project/yt/pull/1953>`__, `PR 1955\n  <https://github.com/yt-project/yt/pull/1955>`__, and `PR 1957\n  <https://github.com/yt-project/yt/pull/1957>`__.\n- The particle trajectories machinery will raise an error if it is asked to\n  analyze a set of particles with duplicated particle IDs. See `PR 1818\n  <https://github.com/yt-project/yt/pull/1818>`__.\n- Fix incorrect velocity unit int he ``gadget_fof`` frontend. See `PR 1829\n  <https://github.com/yt-project/yt/pull/1829>`__.\n- Making an off-axis projection of a cut_region data object with an octree AMR\n  dataset now works correctly. See `PR 1858\n  <https://github.com/yt-project/yt/pull/1858>`__.\n- Replace hard-coded constants in Enzo frontend with calculations to improve\n  agreement with Enzo's internal constants and improve clarity. See `PR 1873\n  <https://github.com/yt-project/yt/pull/1873>`__.\n- Correct issues with Enzo magnetic units in cosmology simulations. See `PR 1876\n  <https://github.com/yt-project/yt/pull/1876>`__.\n- Use the species names from the dataset rather than hardcoding species names in\n  the WarpX frontend. See `PR 1884\n  <https://github.com/yt-project/yt/pull/1884>`__.\n- Fix issue with masked I/O for unstructured mesh data. See `PR 1918\n  <https://github.com/yt-project/yt/pull/1918>`__.\n- Fix crash when reading DM-only Enzo datasets where some grids have no particles. See `PR 1919 <https://github.com/yt-project/yt/pull/1919>`__.\n- Fix crash when loading pure-hydro Nyx dataset. See `PR 1950\n  <https://github.com/yt-project/yt/pull/1950>`__.\n- Avoid crashes when plotting fields that contain NaN. See `PR 1951\n  <https://github.com/yt-project/yt/pull/1951>`__.\n- Avoid crashes when loading NMSU ART data. See `PR 1960\n  <https://github.com/yt-project/yt/pull/1960>`__.\n- Avoid crash when loading WarpX dataset with no particles. See `PR 1979\n  <https://github.com/yt-project/yt/pull/1979>`__.\n- Adapt to API change in glue to fix the ``to_glue()`` method on yt data\n  objects. See `PR 1991 <https://github.com/yt-project/yt/pull/1991>`__.\n- Fix incorrect width calculation in the ``annotate_halos()`` plot callback. See\n  `PR 1995 <https://github.com/yt-project/yt/pull/1995>`__.\n- Don't try to read from files containing zero halos in the ``gadget_fof``\n  frontend. See `PR 2001 <https://github.com/yt-project/yt/pull/2001>`__.\n- Fix incorrect calculation in ``get_ortho_base()``. See `PR 2013\n  <https://github.com/yt-project/yt/pull/2013>`__.\n- Avoid issues with the axes background color being inconsistently set. See `PR\n  2018 <https://github.com/yt-project/yt/pull/2018>`__.\n- Fix issue with reading multiple fields at once for octree AMR data sometimes\n  returning data for another field for one of the requested fields. See `PR 2020\n  <https://github.com/yt-project/yt/pull/2020>`__.\n- Fix incorrect domain annotation for ``Scene.annotate_domain()`` when using the\n  plane-parallel camera. See `PR 2024\n  <https://github.com/yt-project/yt/pull/2024>`__.\n- Avoid crash when particles are on the domain edges for ``gadget_fof``\n  data. See `PR 2034 <https://github.com/yt-project/yt/pull/2034>`__.\n- Avoid stripping code units when processing units through a dataset's unit\n  system. See `PR 2035 <https://github.com/yt-project/yt/pull/2035>`__.\n- Avoid incorrectly rescaling units of metalicity fields. See `PR 2038\n  <https://github.com/yt-project/yt/pull/2038>`__.\n- Fix incorrect units for FLASH ``\"divb\"`` field. See `PR 2062\n  <https://github.com/yt-project/yt/pull/2062>`__.\n\nVersion 3.4\n-----------\n\nVersion 3.4 is the first major release of yt since July 2016. It includes 450\npull requests from 44 contributors including 18 new contributors.\n\n-  yt now supports displaying plots using the interactive matplotlib\n   backends. To enable this functionality call\n   ``yt.toggle_interactivity()``. This is currently supported at an\n   experimental level, please let us know if you come across issues\n   using it. See `Bitbucket PR\n   2294 <https://bitbucket.org/yt_analysis/yt/pull-requests/2294>`__.\n-  The yt configuration file should now be located in a location\n   following the XDG\\_CONFIG convention (usually ``~/.config/yt/ytrc``)\n   rather than the old default location (usually ``~/.yt/config``). You\n   can use ``yt config migrate`` at the bash command line to migrate\n   your configuration file to the new location. See `Bitbucket PR\n   2343 <https://bitbucket.org/yt_analysis/yt/pull-requests/2343>`__.\n-  Added ``yt.LinePlot``, a new plotting class for creating 1D plots\n   along lines through a dataset. See `Github PR\n   1509 <https://github.com/yt-project/yt/pull/1509>`__ and `Github PR\n   1440 <https://github.com/yt-project/yt/pull/1440>`__.\n-  Added ``yt.define_unit`` to easily define new units in yt's unit\n   system. See `Bitbucket PR\n   2485 <https://bitbucket.org/yt_analysis/yt/pull-requests/2485>`__.\n-  Added ``yt.plot_2d``, a wrapper around SlicePlot for plotting 2D\n   datasets. See `Github PR\n   1476 <https://github.com/yt-project/yt/pull/1476>`__.\n-  We have restored support for boolean data objects. Boolean objects\n   are data objects that are defined in terms of boolean operations on\n   other data objects. See `Bitbucket PR\n   2257 <https://bitbucket.org/yt_analysis/yt/pull-requests/2257>`__.\n-  Datasets now have a ``fields`` attribute that allows access to fields\n   via a python object. For example, instead of using a tuple field name\n   like ``('gas', 'density')``, one can now use\n   ``ds.fields.gas.density``. See `Bitbucket PR\n   2459 <https://bitbucket.org/yt_analysis/yt/pull-requests/2459>`__.\n-  It is now possible to create a wider variety of data objects via\n   ``ds.r``, including rays, fixed resolution rays, points, and images.\n   See `Github PR 1518 <https://github.com/yt-project/yt/pull/1518>`__\n   and `Github PR 1393 <https://github.com/yt-project/yt/pull/1393>`__.\n-  ``add_field`` and ``ds.add_field`` must now be called with a\n   ``sampling_type`` keyword argument. Possible values are currently\n   ``cell`` and ``particle``. We have also deprecated the\n   ``particle_type`` keyword argument in favor of\n   ``sampling_type='cell'``. For now a ``'cell'`` ``sampling_type`` is\n   assumed if ``sampling_type`` is not specified but in the future\n   ``sampling_type`` will always need to be specified.\n-  Added support for the ``Athena++`` code. See `Bitbucket PR\n   2149 <https://bitbucket.org/yt_analysis/yt/pull-requests/2149>`__.\n-  Added support for the ``Enzo-E`` code. See `Github PR\n   1447 <https://github.com/yt-project/yt/pull/1447>`__, `Github PR\n   1443 <https://github.com/yt-project/yt/pull/1443>`__ and `Github PR\n   1439 <https://github.com/yt-project/yt/pull/1439>`__.\n-  Added support for the ``AMReX`` code. See `Bitbucket PR\n   2530 <https://bitbucket.org/yt_analysis/yt/pull-requests/2530>`__.\n-  Added support for the ``openPMD`` output format. See `Bitbucket PR\n   2376 <https://bitbucket.org/yt_analysis/yt/pull-requests/2376>`__.\n-  Added support for reading face-centered and vertex-centered fields\n   for block AMR codes. See `Bitbucket PR\n   2575 <https://bitbucket.org/yt_analysis/yt/pull-requests/2575>`__.\n-  Added support for loading outputs from the Amiga Halo Finder. See\n   `Github PR 1477 <https://github.com/yt-project/yt/pull/1477>`__.\n-  Added support for particle fields for Boxlib data. See `Bitbucket PR\n   2510 <https://bitbucket.org/yt_analysis/yt/pull-requests/2510>`__ and\n   `Bitbucket PR\n   2497 <https://bitbucket.org/yt_analysis/yt/pull-requests/2497>`__.\n-  Added support for custom RAMSES particle fields. See `Github PR\n   1470 <https://github.com/yt-project/yt/pull/1470>`__.\n-  Added support for RAMSES-RT data. See `Github PR\n   1456 <https://github.com/yt-project/yt/pull/1456>`__ and `Github PR\n   1449 <https://github.com/yt-project/yt/pull/1449>`__.\n-  Added support for Enzo MHDCT fields. See `Github PR\n   1438 <https://github.com/yt-project/yt/pull/1438>`__.\n-  Added support for units and particle fields to the GAMER frontend.\n   See `Bitbucket PR\n   2366 <https://bitbucket.org/yt_analysis/yt/pull-requests/2366>`__ and\n   `Bitbucket PR\n   2408 <https://bitbucket.org/yt_analysis/yt/pull-requests/2408>`__.\n-  Added support for type 2 Gadget binary outputs. See `Bitbucket PR\n   2355 <https://bitbucket.org/yt_analysis/yt/pull-requests/2355>`__.\n-  Added the ability to detect and read double precision Gadget data.\n   See `Bitbucket PR\n   2537 <https://bitbucket.org/yt_analysis/yt/pull-requests/2537>`__.\n-  Added the ability to detect and read in big endian Gadget data. See\n   `Github PR 1353 <https://github.com/yt-project/yt/pull/1353>`__.\n-  Added support for Nyx datasets that do not contain particles. See\n   `Bitbucket PR\n   2571 <https://bitbucket.org/yt_analysis/yt/pull-requests/2571>`__\n-  A number of untested and unmaintained modules have been deprecated\n   and moved to the `yt attic\n   repository <https://github.com/yt-project/yt_attic>`__. This includes\n   the functionality for calculating two point functions, the Sunrise\n   exporter, the star analysis module, and the functionality for\n   calculating halo mass functions. If you are interested in working on\n   restoring the functionality in these modules, we welcome\n   contributions. Please contact us on the mailing list or by opening an\n   issue on GitHub if you have questions.\n-  The particle trajectories functionality has been removed from the\n   analysis modules API and added as a method of the ``DatasetSeries``\n   object. You can now create a ``ParticleTrajectories`` object using\n   ``ts.particle_trajectories()`` where ``ts`` is a time series of\n   datasets.\n-  The ``spectral_integrator`` analysis module is now available via\n   ``yt.fields.xray_emission_fields``. See `Bitbucket PR\n   2465 <https://bitbucket.org/yt_analysis/yt/pull-requests/2465>`__.\n-  The ``photon_simulator`` analysis module has been deprecated in favor\n   of the ``pyXSIM`` package, available separately from ``yt``. See\n   `Bitbucket PR\n   2441 <https://bitbucket.org/yt_analysis/yt/pull-requests/2441>`__.\n-  ``yt.utilities.fits_image`` is now available as\n   ``yt.visualization.fits_image``. In addition classes that were in the\n   ``yt.utilities.fits_image`` namespace are now available in the main\n   ``yt`` namespace.\n-  The ``profile.variance`` attribute has been deprecated in favor of\n   ``profile.standard_deviation``.\n-  The ``number_of_particles`` key no longer needs to be defined when\n   loading data via the stream frontend. See `Github PR\n   1428 <https://github.com/yt-project/yt/pull/1428>`__.\n-  The install script now only supports installing via miniconda. We\n   have removed support for compiling python and yt's dependencies from\n   source. See `Github PR\n   1459 <https://github.com/yt-project/yt/pull/1459>`__.\n-  Added ``plot.set_background_color`` for ``PlotWindow`` and\n   ``PhasePlot`` plots. This lets users specify a color to fill in the\n   background of a plot instead of the default color, white. See\n   `Bitbucket PR\n   2513 <https://bitbucket.org/yt_analysis/yt/pull-requests/2513>`__.\n-  ``PlotWindow`` plots can now optionally use a right-handed coordinate\n   system. See `Bitbucket PR\n   2318 <https://bitbucket.org/yt_analysis/yt/pull-requests/2318>`__.\n-  The isocontour API has been overhauled to make use of units. See\n   `Bitbucket PR\n   2453 <https://bitbucket.org/yt_analysis/yt/pull-requests/2453>`__.\n-  ``Dataset`` instances now have a ``checksum`` property, which can be\n   accessed via ``ds.checksum``. This provides a unique identifier that\n   is guaranteed to be the same from session to session. See `Bitbucket\n   PR 2503 <https://bitbucket.org/yt_analysis/yt/pull-requests/2503>`__.\n-  Added a ``data_source`` keyword argument to\n   ``OffAxisProjectionPlot``. See `Bitbucket PR\n   2490 <https://bitbucket.org/yt_analysis/yt/pull-requests/2490>`__.\n-  Added a ``yt download`` command-line helper to download test data\n   from https://yt-project.org/data. For more information see\n   ``yt download --help`` at the bash command line. See `Bitbucket PR\n   2495 <https://bitbucket.org/yt_analysis/yt/pull-requests/2495>`__ and\n   `Bitbucket PR\n   2471 <https://bitbucket.org/yt_analysis/yt/pull-requests/2471>`__.\n-  Added a ``yt upload`` command-line helper to upload files to the `yt\n   curldrop <https://docs.hub.yt/services.html#curldrop>`__ at the bash\n   command line. See `Github PR\n   1471 <https://github.com/yt-project/yt/pull/1471>`__.\n-  If it's installed, colormaps from the `cmocean\n   package <https://matplotlib.org/cmocean/>`__ will be made available as\n   yt colormaps. See `Bitbucket PR\n   2439 <https://bitbucket.org/yt_analysis/yt/pull-requests/2439>`__.\n-  It is now possible to visualize unstructured mesh fields defined on\n   multiple mesh blocks. See `Bitbucket PR\n   2487 <https://bitbucket.org/yt_analysis/yt/pull-requests/2487>`__.\n-  Add support for second-order interpolation when slicing tetrahedral\n   unstructured meshes. See `Bitbucket PR\n   2550 <https://bitbucket.org/yt_analysis/yt/pull-requests/2550>`__.\n-  Add support for volume rendering second-order tetrahedral meshes. See\n   `Bitbucket PR\n   2401 <https://bitbucket.org/yt_analysis/yt/pull-requests/2401>`__.\n-  Add support for QUAD9 mesh elements. See `Bitbucket PR\n   2549 <https://bitbucket.org/yt_analysis/yt/pull-requests/2549>`__.\n-  Add support for second-order triangle mesh elements. See `Bitbucket\n   PR 2378 <https://bitbucket.org/yt_analysis/yt/pull-requests/2378>`__.\n-  Added support for dynamical dark energy parameterizations to the\n   ``Cosmology`` object. See `Bitbucket PR\n   2572 <https://bitbucket.org/yt_analysis/yt/pull-requests/2572>`__.\n-  ``ParticleProfile`` can now handle log-scaled bins and data with\n   negative values. See `Bitbucket PR\n   2564 <https://bitbucket.org/yt_analysis/yt/pull-requests/2564>`__ and\n   `Github PR 1510 <https://github.com/yt-project/yt/pull/1510>`__.\n-  Cut region data objects can now be saved as reloadable datasets using\n   ``save_as_dataset``. See `Bitbucket PR\n   2541 <https://bitbucket.org/yt_analysis/yt/pull-requests/2541>`__.\n-  Clump objects can now be saved as reloadable datasets using\n   ``save_as_dataset``. See `Bitbucket PR\n   2326 <https://bitbucket.org/yt_analysis/yt/pull-requests/2326>`__.\n-  It is now possible to specify the field to use for the size of the\n   circles in the ``annotate_halos`` plot modifying function. See\n   `Bitbucket PR\n   2493 <https://bitbucket.org/yt_analysis/yt/pull-requests/2493>`__.\n-  The ``ds.max_level`` attribute is now a property that is computed on\n   demand. The more verbose ``ds.index.max_level`` will continue to\n   work. See `Bitbucket PR\n   2461 <https://bitbucket.org/yt_analysis/yt/pull-requests/2461>`__.\n-  The ``PointSource`` volume rendering source now optionally accepts a\n   ``radius`` keyword argument to draw spatially extended points. See\n   `Bitbucket PR\n   2404 <https://bitbucket.org/yt_analysis/yt/pull-requests/2404>`__.\n-  It is now possible to save volume rendering images in eps, ps, and\n   pdf format. See `Github PR\n   1504 <https://github.com/yt-project/yt/pull/1504>`__.\n\nMinor Enhancements and Bugfixes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n-  Fixed issue selecting and visualizing data at very high AMR levels.\n   See `Github PR 1521 <https://github.com/yt-project/yt/pulls/1521>`__\n   and `Github PR 1433 <https://github.com/yt-project/yt/pull/1433>`__.\n-  Print a more descriptive error message when defining a particle\n   filter fails with missing fields See `Github PR\n   1517 <https://github.com/yt-project/yt/pull/1517>`__.\n-  Removed grid edge rounding from the FLASH frontend. This fixes a\n   number of pernicious visualization artifacts for FLASH data. See\n   `Github PR 1493 <https://github.com/yt-project/yt/pull/1493>`__.\n-  Parallel projections no longer error if there are less io chunks than\n   MPI tasks. See `Github PR\n   1488 <https://github.com/yt-project/yt/pull/1488>`__.\n-  A memory leak in the volume renderer has been fixed. See `Github PR\n   1485 <https://github.com/yt-project/yt/pull/1485>`__ and `Github PR\n   1435 <https://github.com/yt-project/yt/pull/1435>`__.\n-  The ``force_override`` keyword argument now raises an error when used\n   with on-disk fields. See `Github PR\n   1516 <https://github.com/yt-project/yt/pull/1516>`__.\n-  Restore support for making plots from reloaded plots. See `Github PR\n   1514 <https://github.com/yt-project/yt/pull/1514>`__\n-  Don't ever try to read inputs or probin files for Castro and Maestro.\n   See `Github PR 1445 <https://github.com/yt-project/yt/pull/1445>`__.\n-  Fixed issue that caused visualization artifacts when creating an\n   off-axis projection for particle or octree AMR data. See `Github PR\n   1434 <https://github.com/yt-project/yt/pull/1434>`__.\n-  Fix i/o for the Enzo ``'Dark_Matter_Density'`` field. See `Github PR\n   1360 <https://github.com/yt-project/yt/pull/1360>`__.\n-  Create the ``'particle_ones'`` field even if we don't have a particle\n   mass field. See `Github PR\n   1424 <https://github.com/yt-project/yt/pull/1424>`__.\n-  Fixed issues with minor colorbar ticks with symlog colorbar scaling.\n   See `Github PR 1423 <https://github.com/yt-project/yt/pull/1423>`__.\n-  Using the rockstar halo finder is now supported under Python3. See\n   `Github PR 1414 <https://github.com/yt-project/yt/pull/1414>`__.\n-  Fixed issues with orientations of volume renderings when compositing\n   multiple sources. See `Github PR\n   1411 <https://github.com/yt-project/yt/pull/1411>`__.\n-  Added a check for valid AMR structure in ``load_amr_grids``. See\n   `Github PR 1408 <https://github.com/yt-project/yt/pull/1408>`__.\n-  Fix bug in handling of periodic boundary conditions in the\n   ``annotate_halos`` plot modifying function. See `Github PR\n   1351 <https://github.com/yt-project/yt/pull/1351>`__.\n-  Add support for plots with non-unit aspect ratios to the\n   ``annotate_scale`` plot modifying function. See `Bitbucket PR\n   2551 <https://bitbucket.org/yt_analysis/yt/pull-requests/2551>`__.\n-  Fixed issue with saving light ray datasets. See `Bitbucket PR\n   2589 <https://bitbucket.org/yt_analysis/yt/pull-requests/2589>`__.\n-  Added support for 2D WarpX data. ee `Bitbucket PR\n   2583 <https://bitbucket.org/yt_analysis/yt/pull-requests/2583>`__.\n-  Ensure the ``particle_radius`` field is always accessed with the\n   correct field type. See `Bitbucket PR\n   2562 <https://bitbucket.org/yt_analysis/yt/pull-requests/2562>`__.\n-  It is now possible to use a covering grid to access particle filter\n   fields. See `Bitbucket PR\n   2569 <https://bitbucket.org/yt_analysis/yt/pull-requests/2569>`__.\n-  The x limits of a ``ProfilePlot`` will now snap exactly to the limits\n   specified in calls to ``ProfilePlot.set_xlim``. See `Bitbucket PR\n   2546 <https://bitbucket.org/yt_analysis/yt/pull-requests/2546>`__.\n-  Added a cookbook example showing how to make movies using\n   matplotlib's animation framework. See `Bitbucket PR\n   2544 <https://bitbucket.org/yt_analysis/yt/pull-requests/2544>`__.\n-  Use a parallel-safe wrapper around mkdir when creating new\n   directories. See `Bitbucket PR\n   2570 <https://bitbucket.org/yt_analysis/yt/pull-requests/2570>`__.\n-  Removed ``yt.utilities.spatial``. This was a forked version of\n   ``scipy.spatial`` with support for a periodic KD-tree. Scipy now has\n   a periodic KD-tree, so we have removed the forked version from yt.\n   Please use ``scipy.spatial`` if you were relying on\n   ``yt.utilities.spatial``. See `Bitbucket PR\n   2576 <https://bitbucket.org/yt_analysis/yt/pull-requests/2576>`__.\n-  Improvements for the ``HaloCatalog``. See `Bitbucket PR\n   2536 <https://bitbucket.org/yt_analysis/yt/pull-requests/2536>`__ and\n   `Bitbucket PR\n   2535 <https://bitbucket.org/yt_analysis/yt/pull-requests/2535>`__.\n-  Removed ``'log'`` in colorbar label in annotated volume rendering.\n   See `Bitbucket PR\n   2548 <https://bitbucket.org/yt_analysis/yt/pull-requests/2548>`__\n-  Fixed a crash triggered by depositing particle data onto a covering\n   grid. See `Bitbucket PR\n   2545 <https://bitbucket.org/yt_analysis/yt/pull-requests/2545>`__.\n-  Ensure field type guessing is deterministic on Python3. See\n   `Bitbucket PR\n   2559 <https://bitbucket.org/yt_analysis/yt/pull-requests/2559>`__.\n-  Removed unused yt.utilities.exodusII\\_reader module. See `Bitbucket\n   PR 2533 <https://bitbucket.org/yt_analysis/yt/pull-requests/2533>`__.\n-  The ``cell_volume`` field in curvilinear coordinates now uses an\n   exact rather than an approximate definition. See `Bitbucket PR\n   2466 <https://bitbucket.org/yt_analysis/yt/pull-requests/2466>`__.\n\nVersion 3.3\n-----------\n\nVersion 3.3 is the first major release of yt since July 2015. It includes more\nthan 3000 commits from 41 contributors, including 12 new contributors.\n\nMajor enhancements\n^^^^^^^^^^^^^^^^^^\n\n* Raw and processed data from selections, projections, profiles and so forth can\n  now be saved in a ytdata format and loaded back in by yt. See\n  :ref:`saving_data`.\n* Totally re-worked volume rendering API. The old API is still available for users\n  who prefer it, however. See :ref:`volume_rendering`.\n* Support for unstructured mesh visualization. See\n  :ref:`unstructured-mesh-slices` and :ref:`unstructured_mesh_rendering`.\n* Interactive Data Visualization for AMR and unstructured mesh datasets. See\n  :ref:`interactive_data_visualization`.\n* Several new colormaps, including a new default, 'arbre'. The other new\n  colormaps are named 'octarine', 'kelp', and 'dusk'. All these new colormaps\n  were generated using the `viscm package\n  <https://github.com/matplotlib/viscm>`_ and should do a better job of\n  representing the data for colorblind viewers and when printed out in\n  grayscale. See :ref:`colormaps` for more detail.\n* New frontends for the :ref:`ExodusII <loading-exodusii-data>`,\n  :ref:`GAMER <loading-gamer-data>`, and :ref:`Gizmo <loading-gizmo-data>` data\n  formats.\n* The unit system associated with a dataset is now customizable, defaulting to\n  CGS.\n* Enhancements and usability improvements for analysis modules, especially the\n  ``absorption_spectrum``, ``photon_simulator``, and ``light_ray`` modules. See\n  :ref:`synthetic-observations`.\n* Data objects can now be created via an alternative Numpy-like API. See\n  :ref:`quickly-selecting-data`.\n* A line integral convolution plot modification. See\n  :ref:`annotate-line-integral-convolution`.\n* Many speed optimizations, including to the volume rendering, units, tests,\n  covering grids, the absorption spectrum and photon simulator analysis modules,\n  and ghost zone generation.\n* Packaging and release-related improvements: better install and setup scripts,\n  automated PR backporting.\n* Readability improvements to the codebase, including linting, removing dead\n  code, and refactoring much of the Cython.\n* Improvements to the CI infrastructure, including more extensible answer tests\n  and automated testing for Python 3 and Windows.\n* Numerous documentation improvements, including formatting tweaks, bugfixes,\n  and many new cookbook recipes.\n* Support for geographic (lat/lon) coordinates.\n* Several improvements for SPH codes, including alternative smoothing kernels,\n  an ``add_smoothed_particle_field`` function, and particle type-aware octree\n  construction for Gadget data.\n* Roundtrip conversions between Pint and yt units.\n* Added halo data containers for gadget_fof frontend.\n* Enabled support for spherical datasets in the BoxLib frontend.\n* Many new tests have been added.\n* Better hashing for Selector objects.\n\nMinor enhancements and bugfixes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n* Fixed many bugs related to Python 3 compatibility\n* Fixed bugs related to compatibility issues with newer versions of numpy\n* Added the ability to export data objects to a Pandas dataframe\n* Added support for the fabs ufunc to YTArray\n* Fixed two licensing issues\n* Fixed a number of bugs related to Windows compatibility.\n* We now avoid hard-to-decipher tracebacks when loading empty files or\n  directories\n* Fixed a bug related to ART star particle creation time field\n* Fixed a bug caused by using the wrong int type for indexing in particle deposit\n* Fixed a NameError bug in comparing temperature units with offsets\n* Fixed an API bug in YTArray casting during coercion from YTQuantity\n* Added loadtxt and savetxt convenience functions for ``YTArray``\n* Fixed an issue caused by not sort species names with Enzo\n* Fixed a units bug for RAMSES when ``boxlen > 1``.\n* Fixed ``process_chunk`` function for non-cartesian geometry.\n* Added ``scale_factor`` attribute to cosmological simulation datasets\n* Fixed a bug where \"center\" vectors are used instead of \"normal\" vectors in\n  get_sph_phi(), etc.\n* Fixed issues involving invalid FRBs when uses called _setup_plots in their\n  scripts\n* Added a ``text_args`` keyword to ``annotate_scale()`` callback\n* Added a print_stats function for RAMSES\n* Fixed a number of bugs in the Photon Simulator\n* Added support for particle fields to the [Min,Max]Location derived quantities\n* Fixed some units bugs for Gadget cosmology simulations\n* Fixed a bug with Gadget/GIZMO StarFormationRate units\n* Fixed an issue in TimeSeriesData where all the filenames were getting passed\n  to ``load`` on each processor.\n* Fixed a units bug in the Tipsy frontend\n* Ensured that ARTIOIndex.get_smallest_dx() returns a quantity with units\n* Ensured that plots are valid after invalidating the figure\n* Fixed a bug regarding code unit labels\n* Fixed a bug with reading Tipsy Aux files\n* Added an effective redshift field to the Light Ray analysis module for use in\n  AbsorptionSpectrum\n* Fixed a bug with the redshift calculation in LightRay analysis module\n* Fixed a bug in the Orion frontend when you had more than 10 on-disk particle\n  fields in the file\n* Detect more types of ART files\n* Update derived_field_list in add_volume_weighted_smoothed_field\n* Fixed casting issues for 1D and 2D Enzo simulations\n* Avoid type indirection when setting up data object entry points\n* Fixed issues with SIMPUT files\n* Fixed loading athena data in python3 with provided parameters\n* Tipsy cosmology unit fixes\n* Fixed bad unit labels for compound units\n* Making the xlim and ylim of the PhasePlot plot axes controllable\n* Adding grid_arrays to grid_container\n* An Athena and a GDF bugfix\n* A small bugfix and some small enhancements for sunyaev_zeldovich\n* Defer to coordinate handlers for width\n* Make array_like_field return same units as get_data\n* Fixing bug in ray \"dts\" and \"t\" fields\n* Check against string_types not str\n* Closed a loophole that allowed improper LightRay use\n* Enabling AbsorptionSpectrum to deposit unresolved spectral lines\n* Fixed an ART byte/string/array issue\n* Changing AbsorptionSpectrum attribute lambda_bins to be lambda_field for\n  consistency\n* No longer require user to save to disk when generating an AbsorptionSpectrum\n* ParticlePlot FRBs can now use save_as_dataset and save attributes properly\n* Added checks to assure ARTIO creates a metal_density field from existing metal\n  fields.\n* Added mask to LightRay to assure output elements have non-zero density (a\n  problem in some SPH datasets)\n* Added a \"fields\" attribute to datasets\n* Updated the TransferFunctionHelper to work with new profiles\n* Fixed a bug where the field_units kwarg to load_amr_grids didn't do anything\n* Changed photon_simulator's output file structure\n* Fixed a bug related to setting output_units.\n* Implemented ptp operation.\n* Added effects of transverse doppler redshift to LightRay\n* Fixed a casting error for float and int64 multiplication in sdf class\n* Added ability to read and write YTArrays to and from groups within HDF5 files\n* Made ftype of \"on-disk\" stream fields \"stream\"\n* Fixed a strings decoding issue in the photon simulator\n* Fixed an incorrect docstring in load_uniform_grid\n* Made PlotWindow show/hide helpers for axes and colorbar return self\n* Made Profile objects store field metadata.\n* Ensured GDF unit names are strings\n* Taught off_axis_projection about its resolution keyword.\n* Reintroduced sanitize_width for polar/cyl coordinates.\n* We now fail early when load_uniform_grid is passed data with an incorrect shape\n* Replaced progress bar with tqdm\n* Fixed redshift scaling of \"Overdensity\" field in yt-2.x\n* Fixed several bugs in the eps_writer\n* Fixed bug affecting 2D BoxLib simulations.\n* Implemented to_json and from_json for the UnitRegistry object\n* Fixed a number of issues with ds.find_field_values_at_point[s]\n* Fixed a bug where sunrise_exporter was using wrong imports\n* Import HUGE from utilities.physical_ratios\n* Fixed bug in ARTIO table look ups\n* Adding support for longitude and latitude\n* Adding halo data containers for gadget_fof frontend.\n* Can now compare YTArrays without copying them\n* Fixed several bugs related to active particle datasets\n* Angular_momentum_vector now only includes space for particle fields if they\n  exist.\n* Image comparison tests now print a meaningful error message if they fail.\n* Fixed numpy 1.11 compatibility issues.\n* Changed _skip_cache to be True by default.\n* Enable support for spherical datasets in the BoxLib frontend.\n* Fixed a bug in add_deposited_particle_field.\n* Fixed issues with input sanitization in the point data object.\n* Fixed a copy/paste error introduced by refactoring WeightedMenParticleField\n* Fixed many formatting issues in the docs build\n* Now avoid creating particle unions for particle types that have no common\n  fields\n* Patched ParticlePlot to work with filtered particle fields.\n* Fixed a couple corner cases in gadget_fof frontend\n* We now properly normalise all normal vectors in functions that take a normal\n  vector (for e.g. get_sph_theta)\n* Fixed a bug where the transfer function features were not always getting\n  cleared properly.\n* Made the Chombo frontend is_valid method smarter.\n* Added a get_hash() function to yt/funcs.py which returns a hash for a file\n* Added Sievert to the default unit symbol table\n* Corrected an issue with periodic \"wiggle\" in AbsorptionSpectrum instances\n* Made ``ds.field_list`` sorted by default\n* Bug fixes for the Nyx frontend\n* Fixed a bug where the index needed to be created before calling derived\n  quantities\n* Made latex_repr a property, computed on-demand\n* Fixed a bug in off-axis slice deposition\n* Fixed a bug with some types of octree block traversal\n* Ensured that mpi operations retain ImageArray type instead of downgrading to\n  YTArray parent class\n* Added a call to _setup_plots in the custom colorbar tickmark example\n* Fixed two minor bugs in save_annotated\n* Added ability to specify that DatasetSeries is not a mixed data type\n* Fixed a memory leak in ARTIO\n* Fixed copy/paste error in to_frb method.\n* Ensured that particle dataset max_level is consistent with the index max_level\n* Fixed an issue where fields were getting added multiple times to\n  field_info.field_list\n* Enhanced annotate_ray and annotate_arrow callbacks\n* Added GDF answer tests\n* Made the YTFieldTypeNotFound exception more informative\n* Added a new function, fake_vr_orientation_test_ds(), for use in testing\n* Ensured that instances of subclasses of YTArray have the correct type\n* Re-enabled max_level for projections, ProjectionPlot, and OffAxisProjectionPlot\n* Fixed a bug in the Orion 2 field definitions\n* Fixed a bug caused by matplotlib not being added to install_requires\n* Edited PhasePlot class to have an annotate_title method\n* Implemented annotate_cell_edges\n* Handled KeyboardInterrupt in volume rendering Cython loop\n* Made old halo finders now accept ptype\n* Updated the latex commands in yt cheatsheet\n* Fixed a circular dependency loop bug in abar field definition for FLASH\n  datasets\n* Added neutral species aliases as described in YTEP 0003\n* Fixed a logging issue: don't create a StreamHandler unless we will use it\n* Correcting how theta and phi are calculated in\n  ``_particle_velocity_spherical_radius``,\n  ``_particle_velocity_spherical_theta``,\n  ``_particle_velocity_cylindrical_radius``, and\n  ``_particle_velocity_cylindrical_theta``\n* Fixed a bug related to the field dictionary in ``load_particles``\n* Allowed for the special case of supplying width as a tuple of tuples\n* Made yt compile with MSVC on Windows\n* Fixed a bug involving mask for dt in octree\n* Merged the get_yt.sh and install_script.sh into one\n* Added tests for the install script\n* Allowed use axis names instead of dimensions for spherical pixelization\n* Fixed a bug where close() wasn't being called in HDF5FileHandler\n* Enhanced commandline image upload/delete\n* Added get_brewer_cmap to get brewer colormaps without importing palettable at\n  the top level\n* Fixed a bug where a parallel_root_only function was getting called inside\n  another parallel_root_only function\n* Exit the install script early if python can't import '_ssl' module\n* Make PlotWindow's annotate_clear method invalidate the plot\n* Adding int wrapper to avoid deprecation warning from numpy\n* Automatically create vector fields for magnetic_field\n* Allow users to completely specify the filename of a 1D profile\n* Force nose to produce meaningful traceback for cookbook recipes' tests\n* Fixed x-ray display_name and documentation\n* Try to guess and load particle file for FLASH dataset\n* Sped up top-level yt import\n* Set the field type correctly for fields added as particle fields\n* Added a position location method for octrees\n* Fixed a copy/paste error in uhstack function\n* Made trig functions give correct results when supplied data with dimensions of\n  angle but units that aren't radian\n* Print out some useful diagnostic information if check_for_openmp() fails\n* Give user-added derived fields a default field type\n* Added support for periodicity in annotate_particles.\n* Added a check for whether returned field has units in volume-weighted smoothed\n  fields\n* Casting array indices as ints in colormaps infrastructure\n* Fixed a bug where the standard particle fields weren't getting set up\n  correctly for the Orion frontends\n* Enabled LightRay to accept loaded datasets instead of just filenames\n* Allowed for adding or subtracting arrays filled with zeros without checking\n  units.\n* Fixed a bug in selection for semistructured meshes.\n* Removed 'io' from enzo particle types for active particle datasets\n* Added support for FLASH particle datasets.\n* Silenced a deprecation warning from IPython\n* Eliminated segfaults in KDTree construction\n* Fixed add_field handling when passed a tuple\n* Ensure field parameters are correct for fields that need ghost zones\n* Made it possible to use DerivedField instances to access data\n* Added ds.particle_type_counts\n* Bug fix and improvement for generating Google Cardboard VR in\n  StereoSphericalLens\n* Made DarkMatterARTDataset more robust in its _is_valid\n* Added Earth radius to units\n* Deposit hydrogen fields to grid in gizmo frontend\n* Switch to index values being int64\n* ValidateParameter ensures parameter values are used during field detection\n* Switched to using cythonize to manage dependencies in the setup script\n* ProfilePlot style changes and refactoring\n* Cancel terms with identical LaTeX representations in a LaTeX representation of\n  a unit\n* Only return early from comparison validation if base values are equal\n* Enabled particle fields for clump objects\n* Added validation checks for data types in callbacks\n* Enabled modification of image axis names in coordinate handlers\n* Only add OWLS/EAGLE ion fields if they are present\n* Ensured that PlotWindow plots continue to look the same under matplotlib 2.0\n* Fixed bug in quiver callbacks for off-axis slice plots\n* Only visit octree children if going to next level\n* Check that CIC always gets at least two cells\n* Fixed compatibility with matplotlib 1.4.3 and earlier\n* Fixed two EnzoSimulation bugs\n* Moved extraction code from YTSearchCmd to its own utility module\n* Changed amr_kdtree functions to be Node class methods\n* Sort block indices in order of ascending levels to match order of grid patches\n* MKS code unit system fixes\n* Disabled bounds checking on pixelize_element_mesh\n* Updated light_ray.py for domain width != 1\n* Implemented a DOAP file generator\n* Fixed bugs for 2D and 1D enzo IO\n* Converted mutable Dataset attributes to be properties that return copies\n* Allowing LightRay segments to extend further than one box length\n* Fixed a divide-by-zero error that occasionally happens in\n  triangle_plane_intersect\n* Make sure we have an index in subclassed derived quantities\n* Added an initial draft of an extensions document\n* Made it possible to pass field tuples to command-line plotting\n* Ensured the positions of coordinate vector lines are in code units\n* Added a minus sign to definition of sz_kinetic field\n* Added grid_levels and grid_indices fields to octrees\n* Added a morton_index derived field\n* Added Exception to AMRKDTree in the case of particle of oct-based data\n\n\n\nVersion 3.2\n-----------\n\nMajor enhancements\n^^^^^^^^^^^^^^^^^^\n\n* Particle-Only Plots - a series of new plotting functions for visualizing\n  particle data.  See here for more information.\n* Late-stage beta support for Python 3 - unit tests and answer tests pass for\n  all the major frontends under python 3.4, and yt should now be mostly if not\n  fully usable.  Because many of the yt developers are still on Python 2 at\n  this point, this should be considered a \"late stage beta\" as there may be\n  remaining issues yet to be identified or worked out.\n* Now supporting Gadget Friend-of-Friends/Subfind catalogs - see here to learn\n  how to load halo catalogs as regular yt datasets.\n* Custom colormaps can now be easily defined and added - see here to learn how!\n* Now supporting Fargo3D data\n* Performance improvements throughout the code base for memory and speed\n\nMinor enhancements\n^^^^^^^^^^^^^^^^^^\n\n* Various updates to the following frontends: ART, Athena, Castro, Chombo,\n  Gadget, GDF, Maestro, Pluto, RAMSES, Rockstar, SDF, Tipsy\n* Numerous documentation updates\n* Generic hexahedral mesh pixelizer\n* Adding annotate_ray() callback for plots\n* AbsorptionSpectrum returned to full functionality and now using faster SciPy\n  Voigt profile\n* Add a color_field argument to annotate_streamline\n* Smoothing lengths auto-calculated for Tipsy Datasets\n* Adding SimulationTimeSeries support for Gadget and OWLS.\n* Generalizing derived quantity outputs to all be YTArrays or lists of\n  YTArrays as appropriate\n* Star analysis returned to full functionality\n* FITS image writing refactor\n* Adding gradient fields on the fly\n* Adding support for Gadget Nx4 metallicity fields\n* Updating value of solar metal mass fraction to be consistent with Cloudy.\n* Gadget raw binary snapshot handling & non-cosmological simulation units\n* Adding support for LightRay class to work with Gadget+Tipsy\n* Add support for subclasses of frontends\n* Dependencies updated\n* Serialization for projections using minimal representation\n* Adding Grid visitors in Cython\n* Improved semantics for derived field units\n* Add a yaw() method for the PerspectiveCamera + switch back to LHS\n* Adding annotate_clear() function to remove previous callbacks from a plot\n* Added documentation for hexahedral mesh on website\n* Speed up nearest neighbor evaluation\n* Add a convenience method to create deposited particle fields\n* UI and docs updates for 3D streamlines\n* Ensure particle fields are tested in the field unit tests\n* Allow a suffix to be specified to save()\n* Add profiling using airspeed velocity\n* Various plotting enhancements and bugfixes\n* Use hglib to update\n* Various minor updates to halo_analysis toolkit\n* Docker-based tests for install_script.sh\n* Adding support for single and non-cosmological datasets to LightRay\n* Adding the Pascal unit\n* Add weight_field to PPVCube\n* FITS reader: allow HDU in auxiliary\n* Fixing electromagnetic units\n* Specific Angular Momentum [xyz] computed relative to a normal vector\n\nBugfixes\n^^^^^^^^\n\n* Adding ability to create union fields from alias fields\n* Small fix to allow enzo AP datasets to load in parallel when no APs present\n* Use proper cell dimension in gradient function.\n* Minor memory optimization for smoothed particle fields\n* Fix thermal_energy for Enzo HydroMethod==6\n* Make sure annotate_particles handles unitful widths properly\n* Improvements for add_particle_filter and particle_filter\n* Specify registry in off_axis_projection's image finalization\n* Apply fix for particle momentum units to the boxlib frontend\n* Avoid traceback in \"yt version\" when python-hglib is not installed\n* Expose no_ghost from export_sketchfab down to _extract_isocontours_from_grid\n* Fix broken magnetic_unit attribute\n* Fixing an off-by-one error in the set x/y lim methods for profile plots\n* Providing better error messages to PlotWindow callbacks\n* Updating annotate_timestamp to avoid auto-override\n* Updating callbacks to consistently define coordinate system\n* Fixing species fields for OWLS and tipsy\n* Fix extrapolation for vertex-centered data\n* Fix periodicity check in FRBs\n* Rewrote project_to_plane() in PerspectiveCamera for draw_domain()\n* Fix intermittent failure in test_add_deposited_particle_field\n* Improve minorticks for a symlog plot with one-sided data\n* Fix smoothed covering grid cell computation\n* Absorption spectrum generator now 3.0 compliant\n* Fix off-by-one-or-more in particle smallest dx\n* Fix dimensionality mismatch error in covering grid\n* Fix curvature term in cosmology calculator\n* Fix geographic axes and pixelization\n* Ensure axes aspect ratios respect the user-selected plot aspect ratio\n* Avoid clobbering field_map when calling profile.add_fields\n* Fixing the arbitrary grid deposit code\n* Fix spherical plotting centering\n* Make the behavior of to_frb consistent with the docstring\n* Ensure projected units are initialized when there are no chunks.\n* Removing \"field already exists\" warnings from the Owls and Gadget frontends\n* Various photon simulator bugs\n* Fixed use of LaTeX math mode\n* Fix upload_image\n* Enforce plot width in CSS when displayed in a notebook\n* Fix cStringIO.StringIO -> cStringIO in png_writer\n* Add some input sanitizing and error checking to covering_grid initializer\n* Fix for geographic plotting\n* Use the correct filename template for single-file OWLS datasets.\n* Fix Enzo IO performance for 32 bit datasets\n* Adding a number density field for Enzo MultiSpecies=0 datasets.\n* Fix RAMSES block ordering\n* Updating ragged array tests for NumPy 1.9.1\n* Force returning lists for HDF5FileHandler\n\nVersion 3.1\n-----------\n\nThis is a scheduled feature release.  Below are the itemized, aggregate changes\nsince version 3.0.\n\n\nMajor changes:\n^^^^^^^^^^^^^^\n\n* The RADMC-3D export analysis module has been updated. `PR 1358 <https://bitbucket.org/yt_analysis/yt/pull-requests/1358>`_, `PR 1332 <https://bitbucket.org/yt_analysis/yt/pull-requests/1332>`_.\n\n* Performance improvements for grid frontends. `PR 1350 <https://bitbucket.org/yt_analysis/yt/pull-requests/1350>`_. `PR 1382 <https://bitbucket.org/yt_analysis/yt/pull-requests/1382>`_, `PR 1322 <https://bitbucket.org/yt_analysis/yt/pull-requests/1322>`_.\n\n* Added a frontend for Dark Matter-only NMSU Art simulations. `PR 1258 <https://bitbucket.org/yt_analysis/yt/pull-requests/1258>`_.\n\n* The absorption spectrum generator has been updated. `PR 1356 <https://bitbucket.org/yt_analysis/yt/pull-requests/1356>`_.\n\n* The PerspectiveCamera has been updated and a new SphericalCamera has been\n  added. `PR 1346 <https://bitbucket.org/yt_analysis/yt/pull-requests/1346>`_, `PR 1299 <https://bitbucket.org/yt_analysis/yt/pull-requests/1299>`_.\n\n* The unit system now supports unit equivalencies and has improved support for MKS units.  See :ref:`unit_equivalencies`. `PR 1291 <https://bitbucket.org/yt_analysis/yt/pull-requests/1291>`_, `PR 1286 <https://bitbucket.org/yt_analysis/yt/pull-requests/1286>`_.\n\n* Data object selection can now be chained, allowing selecting based on multiple constraints. `PR 1264 <https://bitbucket.org/yt_analysis/yt/pull-requests/1264>`_.\n\n* Added the ability to manually override the simulation unit system. `PR 1236 <https://bitbucket.org/yt_analysis/yt/pull-requests/1236>`_.\n\n* The documentation has been reorganized and has seen substantial improvements. `PR 1383 <https://bitbucket.org/yt_analysis/yt/pull-requests/1383>`_, `PR 1373 <https://bitbucket.org/yt_analysis/yt/pull-requests/1373>`_, `PR 1364 <https://bitbucket.org/yt_analysis/yt/pull-requests/1364>`_, `PR 1351 <https://bitbucket.org/yt_analysis/yt/pull-requests/1351>`_, `PR 1345 <https://bitbucket.org/yt_analysis/yt/pull-requests/1345>`_. `PR 1333 <https://bitbucket.org/yt_analysis/yt/pull-requests/1333>`_, `PR 1342 <https://bitbucket.org/yt_analysis/yt/pull-requests/1342>`_, `PR 1338 <https://bitbucket.org/yt_analysis/yt/pull-requests/1338>`_, `PR 1330 <https://bitbucket.org/yt_analysis/yt/pull-requests/1330>`_, `PR 1326 <https://bitbucket.org/yt_analysis/yt/pull-requests/1326>`_, `PR 1323 <https://bitbucket.org/yt_analysis/yt/pull-requests/1323>`_, `PR 1315 <https://bitbucket.org/yt_analysis/yt/pull-requests/1315>`_, `PR 1305 <https://bitbucket.org/yt_analysis/yt/pull-requests/1305>`_, `PR 1289 <https://bitbucket.org/yt_analysis/yt/pull-requests/1289>`_, `PR 1276 <https://bitbucket.org/yt_analysis/yt/pull-requests/1276>`_.\n\nMinor or bugfix changes:\n^^^^^^^^^^^^^^^^^^^^^^^^\n\n* The Ampere unit now accepts SI prefixes.  `PR 1393 <https://bitbucket.org/yt_analysis/yt/pull-requests/1393>`_.\n\n* The Gadget InternalEnergy and StarFormationRate fields are now read in with the correct units.  `PR 1392 <https://bitbucket.org/yt_analysis/yt/pull-requests/1392>`_, `PR 1379 <https://bitbucket.org/yt_analysis/yt/pull-requests/1379>`_.\n\n* Substantial improvements for the PPVCube analysis module and support for FITS dataset. `PR 1390 <https://bitbucket.org/yt_analysis/yt/pull-requests/1390>`_, `PR 1367 <https://bitbucket.org/yt_analysis/yt/pull-requests/1367>`_, `PR 1347 <https://bitbucket.org/yt_analysis/yt/pull-requests/1347>`_, `PR 1326 <https://bitbucket.org/yt_analysis/yt/pull-requests/1326>`_, `PR 1280 <https://bitbucket.org/yt_analysis/yt/pull-requests/1280>`_, `PR 1336 <https://bitbucket.org/yt_analysis/yt/pull-requests/1336>`_.\n\n* The center of a PlotWindow plot can now be set to the maximum or minimum of any field. `PR 1280 <https://bitbucket.org/yt_analysis/yt/pull-requests/1280>`_.\n\n* Fixes for yt testing infrastructure. `PR 1388 <https://bitbucket.org/yt_analysis/yt/pull-requests/1388>`_, `PR 1348 <https://bitbucket.org/yt_analysis/yt/pull-requests/1348>`_.\n\n* Projections are now performed using an explicit path length field for all\n  coordinate systems. `PR 1307 <https://bitbucket.org/yt_analysis/yt/pull-requests/1307>`_.\n\n* An example notebook for simulations using the OWLS data format has been added\n  to the documentation. `PR 1386 <https://bitbucket.org/yt_analysis/yt/pull-requests/1386>`_.\n\n* Fix for the camera.draw_line function. `PR 1380 <https://bitbucket.org/yt_analysis/yt/pull-requests/1380>`_.\n\n* Minor fixes and improvements for yt plots. `PR 1376 <https://bitbucket.org/yt_analysis/yt/pull-requests/1376>`_, `PR 1374 <https://bitbucket.org/yt_analysis/yt/pull-requests/1374>`_, `PR 1288 <https://bitbucket.org/yt_analysis/yt/pull-requests/1288>`_, `PR 1290 <https://bitbucket.org/yt_analysis/yt/pull-requests/1290>`_.\n\n* Significant documentation reorganization and improvement. `PR 1375 <https://bitbucket.org/yt_analysis/yt/pull-requests/1375>`_, `PR 1359 <https://bitbucket.org/yt_analysis/yt/pull-requests/1359>`_.\n\n* Fixed a conflict in the CFITSIO library used by the x-ray analysis module. `PR 1365 <https://bitbucket.org/yt_analysis/yt/pull-requests/1365>`_.\n\n* Miscellaneous code cleanup. `PR 1371 <https://bitbucket.org/yt_analysis/yt/pull-requests/1371>`_, `PR 1361 <https://bitbucket.org/yt_analysis/yt/pull-requests/1361>`_.\n\n* yt now hooks up to the python logging infrastructure in a more standard\n  fashion, avoiding issues with yt logging showing up with using other\n  libraries. `PR 1355 <https://bitbucket.org/yt_analysis/yt/pull-requests/1355>`_, `PR 1362 <https://bitbucket.org/yt_analysis/yt/pull-requests/1362>`_, `PR 1360 <https://bitbucket.org/yt_analysis/yt/pull-requests/1360>`_.\n\n* The docstring for the projection data object has been corrected. `PR 1366 <https://bitbucket.org/yt_analysis/yt/pull-requests/1366>`_\n\n* A bug in the calculation of the plot bounds for off-axis slice plots has been fixed. `PR 1357 <https://bitbucket.org/yt_analysis/yt/pull-requests/1357>`_.\n\n* Improvements for the yt-rockstar interface. `PR 1352 <https://bitbucket.org/yt_analysis/yt/pull-requests/1352>`_, `PR 1317 <https://bitbucket.org/yt_analysis/yt/pull-requests/1317>`_.\n\n* Fix issues with plot positioning with saving to postscript or encapsulated postscript. `PR 1353 <https://bitbucket.org/yt_analysis/yt/pull-requests/1353>`_.\n\n* It is now possible to supply a default value for get_field_parameter. `PR 1343 <https://bitbucket.org/yt_analysis/yt/pull-requests/1343>`_.\n\n* A bug in the interpretation of the units of RAMSES simulations has been fixed. `PR 1335 <https://bitbucket.org/yt_analysis/yt/pull-requests/1335>`_.\n\n* Plot callbacks are now only executed once before the plot is saved. `PR 1328 <https://bitbucket.org/yt_analysis/yt/pull-requests/1328>`_.\n\n* Performance improvements for smoothed covering grid alias fields. `PR 1331 <https://bitbucket.org/yt_analysis/yt/pull-requests/1331>`_.\n\n* Improvements and bugfixes for the halo analysis framework. `PR 1349 <https://bitbucket.org/yt_analysis/yt/pull-requests/1349>`_, `PR 1325 <https://bitbucket.org/yt_analysis/yt/pull-requests/1325>`_.\n\n* Fix issues with the default setting for the ``center`` field parameter. `PR 1327 <https://bitbucket.org/yt_analysis/yt/pull-requests/1327>`_.\n\n* Avoid triggering warnings in numpy and matplotlib. `PR 1334 <https://bitbucket.org/yt_analysis/yt/pull-requests/1334>`_, `PR 1300 <https://bitbucket.org/yt_analysis/yt/pull-requests/1300>`_.\n\n* Updates for the field list reference. `PR 1344 <https://bitbucket.org/yt_analysis/yt/pull-requests/1344>`_, `PR 1321 <https://bitbucket.org/yt_analysis/yt/pull-requests/1321>`_, `PR 1318 <https://bitbucket.org/yt_analysis/yt/pull-requests/1318>`_.\n\n* yt can now be run in parallel on a subset of available processors using an MPI subcommunicator. `PR 1340 <https://bitbucket.org/yt_analysis/yt/pull-requests/1340>`_\n\n* Fix for incorrect units when loading an Athena simulation as a time series. `PR 1341 <https://bitbucket.org/yt_analysis/yt/pull-requests/1341>`_.\n\n* Improved support for Enzo 3.0 simulations that have not produced any active particles. `PR 1329 <https://bitbucket.org/yt_analysis/yt/pull-requests/1329>`_.\n\n* Fix for parsing OWLS outputs with periods in the file path.  `PR 1320 <https://bitbucket.org/yt_analysis/yt/pull-requests/1320>`_.\n\n* Fix for periodic radius vector calculation. `PR 1311 <https://bitbucket.org/yt_analysis/yt/pull-requests/1311>`_.\n\n* Improvements for the Maestro and Castro frontends. `PR 1319 <https://bitbucket.org/yt_analysis/yt/pull-requests/1319>`_.\n\n* Clump finding is now supported for more generic types of data. `PR 1314 <https://bitbucket.org/yt_analysis/yt/pull-requests/1314>`_\n\n* Fix unit consistency issue when mixing dimensionless unit symbols. `PR 1300 <https://bitbucket.org/yt_analysis/yt/pull-requests/1300>`_.\n\n* Improved memory footprint in the photon_simulator. `PR 1304 <https://bitbucket.org/yt_analysis/yt/pull-requests/1304>`_.\n\n* Large grids in Athena datasets produced by the join_vtk script can now be optionally split, improving parallel performance.  `PR 1304 <https://bitbucket.org/yt_analysis/yt/pull-requests/1304>`_.\n\n* Slice plots now accept a ``data_source`` keyword argument. `PR 1310 <https://bitbucket.org/yt_analysis/yt/pull-requests/1310>`_.\n\n* Corrected inconsistent octrees in the RAMSES frontend. `PR 1302 <https://bitbucket.org/yt_analysis/yt/pull-requests/1302>`_\n\n* Nearest neighbor distance field added.  `PR 1138 <https://bitbucket.org/yt_analysis/yt/pull-requests/1138>`_.\n\n* Improvements for the ORION2 frontend. `PR 1303 <https://bitbucket.org/yt_analysis/yt/pull-requests/1303>`_\n\n* Enzo 3.0 frontend can now read active particle attributes that are arrays of any shape. `PR 1248 <https://bitbucket.org/yt_analysis/yt/pull-requests/1248>`_.\n\n* Answer tests added for halo finders. `PR 1253 <https://bitbucket.org/yt_analysis/yt/pull-requests/1253>`_\n\n* A ``setup_function`` has been added to the LightRay initializer. `PR 1295 <https://bitbucket.org/yt_analysis/yt/pull-requests/1295>`_.\n\n* The SPH code frontends have been reorganized into separate frontend directories. `PR 1281 <https://bitbucket.org/yt_analysis/yt/pull-requests/1281>`_.\n\n* Fixes for accessing deposit fields for FLASH data. `PR 1294 <https://bitbucket.org/yt_analysis/yt/pull-requests/1294>`_\n\n* Added tests for ORION datasets containing sink and star particles. `PR 1252 <https://bitbucket.org/yt_analysis/yt/pull-requests/1252>`_\n\n* Fix for field names in the particle generator. `PR 1278 <https://bitbucket.org/yt_analysis/yt/pull-requests/1278>`_.\n\n* Added wrapper functions for numpy array manipulation functions.  `PR 1287 <https://bitbucket.org/yt_analysis/yt/pull-requests/1287>`_.\n\n* Added support for packed HDF5 Enzo datasets. `PR 1282 <https://bitbucket.org/yt_analysis/yt/pull-requests/1282>`_.\n\nVersion 3.0\n-----------\n\nThis release of yt features an entirely rewritten infrastructure for\ndata ingestion, indexing, and representation.  While past versions of\nyt were focused on analysis and visualization of data structured as\nregular grids, this release features full support for particle\n(discrete point) data such as N-body and SPH data, irregular\nhexahedral mesh data, and data organized via octrees.  This\ninfrastructure will be extended in future versions for high-fidelity\nrepresentation of unstructured mesh datasets.\n\nHighlighted changes in yt 3.0:\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n * Units now permeate the code base, enabling self-consistent unit\n   transformations of all arrays and quantities returned by yt.\n * Particle data is now supported using a lightweight octree.  SPH\n   data can be smoothed onto an adaptively-defined mesh using standard\n   SPH smoothing\n * Support for octree AMR codes\n * Preliminary Support for non-Cartesian data, such as cylindrical,\n   spherical, and geographical\n * Revamped analysis framework for halos and halo catalogs, including\n   direct ingestion and analysis of halo catalogs of several different\n   formats\n * Support for multi-fluid datasets and datasets containing multiple\n   particle types\n * Flexible support for dynamically defining new particle types using\n   filters on existing particle types or by combining different particle\n   types.\n * Vastly improved support for loading generic grid, AMR, hexahedral\n   mesh, and particle without hand-coding a frontend for a particular\n   data format.\n * New frontends for ART, ARTIO, Boxlib, Chombo, FITS, GDF, Subfind,\n   Rockstar, Pluto, RAMSES, SDF, Gadget, OWLS, PyNE, Tipsy, as well as\n   rewritten frontends for Enzo, FLASH, Athena, and generic data.\n * First release to support installation of yt on Windows\n * Extended capabilities for construction of simulated observations,\n   and new facilities for analyzing and visualizing FITS images and cube\n   data\n * Many performance improvements\n\nThis release is the first of several; while most functionality from\nthe previous generation of yt has been updated to work with yt 3.0, it\ndoes not yet have feature parity in all respects.  While the core of\nyt is stable, we suggest the support for analysis modules and volume\nrendering be viewed as a late-stage beta, with a series of additional\nreleases (3.1, 3.2, etc) appearing over the course of the next year to\nimprove support in these areas.\n\nFor a description of how to bring your 2.x scripts up to date to 3.0,\nand a summary of common gotchas in this transition, please see\n:ref:`yt3differences`.\n\nVersion 2.6\n-----------\n\nThis is a scheduled release, bringing to a close the development in the 2.x\nseries.  Below are the itemized, aggregate changes since version 2.5.\n\nMajor changes:\n^^^^^^^^^^^^^^\n\n  * yt is now licensed under the 3-clause BSD license.\n  * HEALPix has been removed for the time being, as a result of licensing\n    incompatibility.\n  * The addition of a frontend for the Pluto code\n  * The addition of an OBJ exporter to enable transparent and multi-surface\n    exports of surfaces to Blender and Sketchfab\n  * New absorption spectrum analysis module with documentation\n  * Adding ability to draw lines with Grey Opacity in volume rendering\n  * Updated physical constants to reflect 2010 CODATA data\n  * Dependency updates (including IPython 1.0)\n  * Better notebook support for yt plots\n  * Considerably (10x+) faster kD-tree building for volume rendering\n  * yt can now export to RADMC3D\n  * Athena frontend now supports Static Mesh Refinement and units (\n    http://hub.yt-project.org/nb/7l1zua )\n  * Fix long-standing bug for plotting arrays with range of zero\n  * Adding option to have interpolation based on non-uniform bins in\n    interpolator code\n  * Upgrades to most of the dependencies in the install script\n  * ProjectionPlot now accepts a data_source keyword argument\n\nMinor or bugfix changes:\n^^^^^^^^^^^^^^^^^^^^^^^^\n\n  * Fix for volume rendering on the command line\n  * map_to_colormap will no longer return out-of-bounds errors\n  * Fixes for dds in covering grid calculations\n  * Library searching for build process is now more reliable\n  * Unit fix for \"VorticityGrowthTimescale\" field\n  * Pyflakes stylistic fixes\n  * Number density added to FLASH\n  * Many fixes for Athena frontend\n  * Radius and ParticleRadius now work for reduced-dimensionality datasets\n  * Source distributions now work again!\n  * Athena data now 64 bits everywhere\n  * Grids displays on plots are now shaded to reflect the level of refinement\n  * show_colormaps() is a new function for displaying all known colormaps\n  * PhasePlotter by default now adds a colormap.\n  * System build fix for POSIX systems\n  * Fixing domain offsets for halo centers-of-mass\n  * Removing some Enzo-specific terminology in the Halo Mass Function\n  * Addition of coordinate vectors on volume render\n  * Pickling fix for extracted regions\n  * Addition of some tracer particle annotation functions\n  * Better error message for \"yt\" command\n  * Fix for radial vs poloidal fields\n  * Piernik 2D data handling fix\n  * Fixes for FLASH current redshift\n  * PlotWindows now have a set_font function and a new default font setting\n  * Colorbars less likely to extend off the edge of a PlotWindow\n  * Clumps overplotted on PlotWindows are now correctly contoured\n  * Many fixes to light ray and profiles for integrated cosmological analysis\n  * Improvements to OpenMP compilation\n  * Typo in value for km_per_pc (not used elsewhere in the code base) has been\n    fixed\n  * Enable parallel IPython notebook sessions (\n    http://hub.yt-project.org/nb/qgn19h )\n  * Change (~1e-6) to particle_density deposition, enabling it to be used by\n    FLASH and other frontends\n  * Addition of is_root function for convenience in parallel analysis sessions\n  * Additions to Orion particle reader\n  * Fixing TotalMass for case when particles not present\n  * Fixing the density threshold or HOP and pHOP to match the merger tree\n  * Reason can now plot with latest plot window\n  * Issues with VelocityMagnitude and aliases with velo have been corrected in\n    the FLASH frontend\n  * Halo radii are calculated correctly for domains that do not start at 0,0,0.\n  * Halo mass function now works for non-Enzo frontends.\n  * Bug fixes for directory creation, typos in docstrings\n  * Speed improvements to ellipsoidal particle detection\n  * Updates to FLASH fields\n  * CASTRO frontend bug fixes\n  * Fisheye camera bug fixes\n  * Answer testing now includes plot window answer testing\n  * Athena data serialization\n  * load_uniform_grid can now decompose dims >= 1024.  (#537)\n  * Axis unit setting works correctly for unit names  (#534)\n  * ThermalEnergy is now calculated correctly for Enzo MHD simulations (#535)\n  * Radius fields had an asymmetry in periodicity calculation (#531)\n  * Boolean regions can now be pickled (#517)\n\nVersion 2.5\n-----------\n\nMany below-the-surface changes happened in yt 2.5 to improve reliability,\nfidelity of the answers, and streamlined user interface.  The major change in\nthis release has been the immense expansion in testing of yt.  We now have over\n2000 unit tests (run on every commit, thanks to both Kacper Kowalik and Shining\nPanda) as well as answer testing for FLASH, Enzo, Chombo and Orion data.\n\nThe Stream frontend, which can construct datasets in memory, has been improved\nconsiderably.  It's now easier than ever to load data from disk.  If you know\nhow to get volumetric data into Python, you can use either the\n``load_uniform_grid`` function or the ``load_amr_grid`` function to create an\nin-memory dataset that yt can analyze.\n\nyt now supports the Athena code.\n\nyt is now focusing on providing first class support for the IPython notebook.\nIn this release, plots can be displayed inline.  The Reason HTML5 GUI will be\nmerged with the IPython notebook in a future release.\n\nInstall Script Changes:\n^^^^^^^^^^^^^^^^^^^^^^^\n\n * SciPy can now be installed\n * Rockstar can now be installed\n * Dependencies can be updated with \"yt update --all\"\n * Cython has been upgraded to 0.17.1\n * Python has been upgraded to 2.7.3\n * h5py has been upgraded to 2.1.0\n * hdf5 has been upgraded to 1.8.9\n * matplotlib has been upgraded to 1.2.0\n * IPython has been upgraded to 0.13.1\n * Forthon has been upgraded to 0.8.10\n * nose has been added\n * sympy has been added\n * python-hglib has been added\n\nWe've also improved support for installing on OSX, Ubuntu and OpenSUSE.\n\nMost Visible Improvements\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\n * Nearly 200 pull requests and over 1000 changesets have been merged since yt\n   2.4 was release on August 2nd, 2012.\n * numpy is now imported as np, not na.  na will continue to work for the\n   foreseeable future.\n * You can now get a `yt cheat sheet <http://yt-project.org/docs/2.5/cheatsheet.pdf>`_!\n * yt can now load simulation data created by Athena.\n * The Rockstar halo finder can now be installed by the install script\n * SciPy can now be installed by the install script\n * Data can now be written out in two ways:\n\n   * Sidecar files containing expensive derived fields can be written and\n     implicitly loaded from.\n   * GDF files, which are portable yt-specific representations of full\n     simulations, can be created from any dataset.  Work is underway on\n     a pure C library that can be linked against to load these files into\n     simulations.\n\n * The \"Stream\" frontend, for loading raw data in memory, has been greatly\n   expanded and now includes initial conditions generation functionality,\n   particle fields, and simple loading of AMR grids with ``load_amr_grids``.\n * Spherical and Cylindrical fields have been sped up and made to have a\n   uniform interface.  These fields can be the building blocks of more advanced\n   fields.\n * Coordinate transformations have been sped up and streamlined. It is now\n   possible to convert any scalar or vector field to a new cartesian, spherical,\n   or cylindrical coordinate system with an arbitrary orientation. This makes it\n   possible to do novel analyses like profiling the toroidal and poloidal\n   velocity as a function of radius in an inclined disk.\n * Many improvements to the EnzoSimulation class, which can now find many\n   different types of data.\n * Image data is now encapsulated in an ImageArray class, which carries with it\n   provenance information about its trajectory through yt.\n * Streamlines now query at every step along the streamline, not just at every\n   cell.\n * Surfaces can now be extracted and examined, as well as uploaded to\n   Sketchfab.com for interactive visualization in a web browser.\n * allsky_projection can now accept a datasource, making it easier to cut out\n   regions to examine.\n * Many, many improvements to PlotWindow.  If you're still using\n   PlotCollection, check out ``ProjectionPlot``, ``SlicePlot``,\n   ``OffAxisProjectionPlot`` and ``OffAxisSlicePlot``.\n * PlotWindow can now accept a timeseries instead of a dataset.\n * Many fixes for 1D and 2D data, especially in FLASH datasets.\n * Vast improvements to the particle file handling for FLASH datasets.\n * Particles can now be created ex nihilo with CICSample_3.\n * Rockstar halo finding is now a targeted goal.  Support for using Rockstar\n   has improved dramatically.\n * Increased support for tracking halos across time using the FOF halo finder.\n * The command ``yt notebook`` has been added to spawn an IPython notebook\n   server, and the ``yt.imods`` module can replace ``yt.mods`` in the IPython\n   Notebook to enable better integration.\n * Metallicity-dependent X-ray fields have now been added.\n * Grid lines can now be added to volume renderings.\n * Volume rendering backend has been updated to use an alpha channel, fixing\n   parallel opaque volume renderings.  This also enables easier blending of\n   multiple images and annotations to the rendering. Users are encouraged\n   to look at the capabilities of the ``ImageArray`` for writing out renders,\n   as updated in the cookbook examples. Volume renders can now be saved with\n   an arbitrary background color.\n * Periodicity, or alternately non-periodicity, is now a part of radius\n   calculations.\n * The AMRKDTree has been rewritten.  This allows parallelism with other than\n   power-of-2 MPI processes, arbitrary sets of grids, and splitting of\n   unigrids.\n * Fixed Resolution Buffers and volume rendering images now utilize a new\n   ImageArray class that stores information such as data source, field names,\n   and other information in a .info dictionary. See the ``ImageArray``\n   docstrings for more information on how they can be used to save to a bitmap\n   or hdf5 file.\n\nVersion 2.4\n-----------\n\nThe 2.4 release was particularly large, encompassing nearly a thousand\nchangesets and a number of new features.\n\nTo help you get up to speed, we've made an IPython notebook file demonstrating\na few of the changes to the scripting API.  You can\n`download it here <http://yt-project.org/files/yt24.ipynb>`_.\n\nMost Visible Improvements\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\n * Threaded volume renderer, completely refactored from the ground up for\n   speed and parallelism.\n * The Plot Window (see :ref:`simple-inspection`) is now fully functional!  No\n   more PlotCollections, and full, easy access to Matplotlib axes objects.\n * Many improvements to Time Series analysis:\n    * EnzoSimulation now integrates with TimeSeries analysis!\n    * Auto-parallelization of analysis and parallel iteration\n    * Memory usage when iterating over datasets reduced substantially\n * Many improvements to Reason, the yt GUI\n    * Addition of \"yt reason\" as a startup command\n    * Keyboard shortcuts in projection & slice mode: z, Z, x, X for zooms,\n      hjkl, HJKL for motion\n    * Drag to move in projection & slice mode\n    * Contours and vector fields in projection & slice mode\n    * Color map selection in projection & slice mode\n    * 3D Scene\n * Integration with the all new yt Hub ( http://hub.yt-project.org/ ): upload\n   variable resolution projections, slices, project information, vertices and\n   plot collections right from the yt command line!\n\nOther Changes\n^^^^^^^^^^^^^\n\n * :class:`~yt.visualization.plot_window.ProjectionPlot` and\n   :class:`~yt.visualization.plot_window.SlicePlot` supplant the functionality\n   of PlotCollection.\n * Camera path creation from keyframes and splines\n * Ellipsoidal data containers and ellipsoidal parameter calculation for halos\n * PyX and ZeroMQ now available in the install script\n * Consolidation of unit handling\n * HDF5 updated to 1.8.7, Mercurial updated to 2.2, IPython updated to 0.12\n * Preview of integration with Rockstar halo finder\n * Improvements to merger tree speed and memory usage\n * Sunrise exporter now compatible with Sunrise 4.0\n * Particle trajectory calculator now available!\n * Speed and parallel scalability improvements in projections, profiles and HOP\n * New Vorticity-related fields\n * Vast improvements to the ART frontend\n * Many improvements to the FLASH frontend, including full parameter reads,\n   speedups, and support for more corner cases of FLASH 2, 2.5 and 3 data.\n * Integration of the Grid Data Format frontend, and a converter for Athena\n   data to this format.\n * Improvements to command line parsing\n * Parallel import improvements on parallel filesystems\n   (``from yt.pmods import *``)\n * proj_style keyword for projections, for Maximum Intensity Projections\n   (``proj_style = \"mip\"``)\n * Fisheye rendering for planetarium rendering\n * Profiles now provide \\*_std fields for standard deviation of values\n * Generalized Orientation class, providing 6DOF motion control\n * parallel_objects iteration now more robust, provides optional barrier.\n   (Also now being used as underlying iteration mechanism in many internal\n   routines.)\n * Dynamic load balancing in parallel_objects iteration.\n * Parallel-aware objects can now be pickled.\n * Many new colormaps included\n * Numerous improvements to the PyX-based eps_writer module\n * FixedResolutionBuffer to FITS export.\n * Generic image to FITS export.\n * Multi-level parallelism for extremely large cameras in volume rendering\n * Light cone and light ray updates to fit with current best practices for\n   parallelism\n\nVersion 2.3\n-----------\n\n`(yt 2.3 docs) <http://yt-project.org/docs/2.3>`_\n * Multi-level parallelism\n * Real, extensive answer tests\n * Boolean data regions (see :ref:`boolean_data_objects`)\n * Isocontours / flux calculations (see :ref:`extracting-isocontour-information`)\n * Field reorganization\n * PHOP memory improvements\n * Bug fixes for tests\n * Parallel data loading for RAMSES, along with other speedups and improvements\n   there\n * WebGL interface for isocontours and a pannable map widget added to Reason\n * Performance improvements for volume rendering\n * Adaptive HEALPix support\n * Column density calculations\n * Massive speedup for 1D profiles\n * Lots more, bug fixes etc.\n * Substantial improvements to the documentation, including\n   :ref:`manual-plotting` and a revamped orientation.\n\nVersion 2.2\n-----------\n\n`(yt 2.2 docs) <http://yt-project.org/docs/2.2>`_\n * Command-line submission to the yt Hub (http://hub.yt-project.org/)\n * Initial release of the web-based GUI Reason, designed for efficient remote\n   usage over SSH tunnels\n * Absorption line spectrum generator for cosmological simulations (see\n   :ref:`absorption_spectrum`)\n * Interoperability with ParaView for volume rendering, slicing, and so forth\n * Support for the Nyx code\n * An order of magnitude speed improvement in the RAMSES support\n * Quad-tree projections, speeding up the process of projecting by up to an\n   order of magnitude and providing better load balancing\n * \"mapserver\" for in-browser, Google Maps-style slice and projection\n   visualization (see :ref:`mapserver`)\n * Many bug fixes and performance improvements\n * Halo loader\n\nVersion 2.1\n-----------\n\n`(yt 2.1 docs) <http://yt-project.org/docs/2.1>`_\n * HEALPix-based volume rendering for 4pi, allsky volume rendering\n * libconfig is now included\n * SQLite3 and Forthon now included by default in the install script\n * Development guide has been lengthened substantially and a development\n   bootstrap script is now included.\n * Installation script now installs Python 2.7 and HDF5 1.8.6\n * iyt now tab-completes field names\n * Halos can now be stored on-disk much more easily between HaloFinding runs.\n * Halos found inline in Enzo can be loaded and merger trees calculated\n * Support for CASTRO particles has been added\n * Chombo support updated and fixed\n * New code contributions\n * Contour finder has been sped up by a factor of a few\n * Constrained two-point functions are now possible, for LOS power spectra\n * Time series analysis (:ref:`time-series-analysis`) now much easier\n * Stream Lines now a supported 1D data type\n * Stream Lines now able to be calculated and plotted (:ref:`streamlines`)\n * In situ Enzo visualization now much faster\n * \"gui\" source directory reorganized and cleaned up\n * Cython now a compile-time dependency, reducing the size of source tree\n   updates substantially\n * ``yt-supplemental`` repository now checked out by default, containing\n   cookbook, documentation, handy mercurial extensions, and advanced plotting\n   examples and helper scripts.\n * Pasteboards now supported and available\n * Parallel yt efficiency improved by removal of barriers and improvement of\n   collective operations\n\nVersion 2.0\n-----------\n\n * Major reorganization of the codebase for speed, ease of modification, and maintainability\n * Re-organization of documentation and addition of Orientation Session\n * Support for FLASH code\n * Preliminary support for MAESTRO, CASTRO, ART, and RAMSES (contributions welcome!)\n * Perspective projection for volume rendering\n * Exporting to Sunrise\n * Preliminary particle rendering in volume rendering visualization\n * Drastically improved parallel volume rendering, via kD-tree decomposition\n * Simple merger tree calculation for FOF catalogs\n * New and greatly expanded documentation, with a \"source\" button\n\nVersion 1.7\n-----------\n\n * Direct writing of PNGs\n * Multi-band image writing\n * Parallel halo merger tree (see :ref:`merger_tree`)\n * Parallel structure function generator (see :ref:`two_point_functions`)\n * Image pan and zoom object and display widget.\n * Parallel volume rendering (see :ref:`volume_rendering`)\n * Multivariate volume rendering, allowing for multiple forms of emission and\n   absorption, including approximate scattering and Planck emissions. (see\n   :ref:`volume_rendering`)\n * Added Camera interface to volume rendering (See :ref:`volume_rendering`)\n * Off-axis projection (See :ref:`volume_rendering`)\n * Stereo (toe-in) volume rendering (See :ref:`volume_rendering`)\n * DualEPS extension for better EPS construction\n * yt now uses Distribute instead of SetupTools\n * Better ``iyt`` initialization for GUI support\n * Rewritten, memory conservative and speed-improved contour finding algorithm\n * Speed improvements to volume rendering\n * Preliminary support for the Tiger code\n * Default colormap is now ``algae``\n * Lightweight projection loading with ``projload``\n * Improvements to ``yt.data_objects.time_series``\n * Improvements to :class:`yt.extensions.EnzoSimulation` (See\n   :ref:`analyzing-an-entire-simulation`)\n * Removed ``direct_ray_cast``\n * Fixed bug causing double data-read in projections\n * Added Cylinder support to ParticleIO\n * Fixes for 1- and 2-D Enzo datasets\n * Preliminary, largely non-functional Gadget support\n * Speed improvements to basic HOP\n * Added physical constants module\n * Beginning to standardize and enforce docstring requirements, changing to\n   ``autosummary``-based API documentation.\n\nVersion 1.6.1\n-------------\n\n * Critical fixes to ParticleIO\n * Halo mass function fixes for comoving coordinates\n * Fixes to halo finding\n * Fixes to the installation script\n * \"yt instinfo\" command to report current installation information as well as\n   auto-update some types of installations\n * Optimizations to the volume renderer (2x-26x reported speedups)\n\nVersion 1.6\n-----------\n\nVersion 1.6 is a point release, primarily notable for the new parallel halo\nfinder (see :ref:`halo-analysis`)\n\n * (New) Parallel HOP ( https://arxiv.org/abs/1001.3411 , :ref:`halo-analysis` )\n * (Beta) Software ray casting and volume rendering\n   (see :ref:`volume_rendering`)\n * Rewritten, faster and better contouring engine for clump identification\n * Spectral Energy Distribution calculation for stellar populations\n   (see :ref:`synthetic_spectrum`)\n * Optimized data structures such as the index\n * Star particle analysis routines\n   (see :ref:`star_analysis`)\n * Halo mass function routines\n * Completely rewritten, massively faster and more memory efficient Particle IO\n * Fixes for plots, including normalized phase plots\n * Better collective communication in parallel routines\n * Consolidation of optimized C routines into ``amr_utils``\n * Many bug fixes and minor optimizations\n\nVersion 1.5\n-----------\n\nVersion 1.5 features many new improvements, most prominently that of the\naddition of parallel computing abilities (see :ref:`parallel-computation`) and\ngeneralization for multiple AMR data formats, specifically both Enzo and Orion.\n\n * Rewritten documentation\n * Fully parallel slices, projections, cutting planes, profiles,\n   quantities\n * Parallel HOP\n * Friends-of-friends halo finder\n * Object storage and serialization\n * Major performance improvements to the clump finder (factor of five)\n * Generalized domain sizes\n * Generalized field info containers\n * Dark Matter-only simulations\n * 1D and 2D simulations\n * Better IO for HDF5 sets\n * Support for the Orion AMR code\n * Spherical re-gridding\n * Halo profiler\n * Disk image stacker\n * Light cone generator\n * Callback interface improved\n * Several new callbacks\n * New data objects -- ortho and non-ortho rays, limited ray-tracing\n * Fixed resolution buffers\n * Spectral integrator for CLOUDY data\n * Substantially better interactive interface\n * Performance improvements *everywhere*\n * Command-line interface to *many* common tasks\n * Isolated plot handling, independent of PlotCollections\n\nVersion 1.0\n-----------\n\n * Initial release!\n"
  },
  {
    "path": "doc/source/reference/code_support.rst",
    "content": "\n.. _code-support:\n\nCode Support\n============\n\nLevels of Support for Various Codes\n-----------------------------------\n\nyt provides frontends to support several different simulation code formats\nas inputs.  Below is a list showing what level of support is provided for\neach code. See :ref:`loading-data` for examples of loading a dataset from\neach supported output format using yt.\n\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Capability ►          | Fluid      | Particles | Parameters | Units | Read on  | Load Raw | Part of    | Level of    |\n| Code/Format ▼         | Quantities |           |            |       | Demand   | Data     | test suite | Support     |\n+=======================+============+===========+============+=======+==========+==========+============+=============+\n| AMRVAC                |     Y      |     N     |      Y     |   Y   |    Y     |    Y     |     Y      | Partial     |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| AREPO                 |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      | Full [#f4]_ |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| ART                   |     Y      |     Y     |      Y     |   Y   | Y [#f2]_ |    Y     |     N      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| ARTIO                 |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Athena                |     Y      |     N     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Athena++              |     Y      |     N     |      Y     |   Y   |    Y     |    Y     |     Y      | Partial     |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Castro                |     Y      |  Y [#f3]_ |   Partial  |   Y   |    Y     |    Y     |     N      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| CfRadial              |     Y      |    N/A    |      Y     |   Y   |    Y     |    Y     |     Y      |   [#f5]_    |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| CHOLLA                |     Y      |    N/A    |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Chombo                |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Enzo                  |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Enzo-E                |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Exodus II             |     ?      |     ?     |      ?     |   ?   |    ?     |    ?     |     ?      |   ?         |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| FITS                  |     Y      |    N/A    |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| FLASH                 |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Gadget                |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| GAMER                 |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Gasoline              |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Gizmo                 |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Grid Data Format (GDF)|     Y      |    N/A    |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| IAMR                  |     ?      |     ?     |      ?     |   ?   |    ?     |    ?     |     ?      | ?           |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Maestro               |   Y [#f1]_ |     N     |      Y     |   Y   |    Y     |    Y     |     N      | Partial     |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| MOAB                  |     Y      |    N/A    |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Nyx                   |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| openPMD               |     Y      |     Y     |      N     |   Y   |    Y     |    Y     |     N      | Partial     |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Orion                 |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| OWLS/EAGLE            |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Parthenon             |     Y      |     N     |      Y     |   Y   |    Y     |    Y     |     Y      | Partial     |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Piernik               |     Y      |    N/A    |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Pluto                 |     Y      |     N     |      Y     |   Y   |    Y     |    Y     |     Y      | Partial     |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| RAMSES                |     Y      |     Y     |      Y     |   Y   | Y [#f2]_ |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| Tipsy                 |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n| WarpX                 |     Y      |     Y     |      Y     |   Y   |    Y     |    Y     |     Y      |   Full      |\n+-----------------------+------------+-----------+------------+-------+----------+----------+------------+-------------+\n\n.. [#f1] one-dimensional base-state not read in currently.\n.. [#f2] These handle mesh fields using an in-memory octree that has not been parallelized.\n         Datasets larger than approximately 1024^3 will not scale well.\n.. [#f3] Newer versions of Castro that use BoxLib's standard particle format are supported.\n          The older ASCII format is not.\n.. [#f4] The Voronoi cells are currently treated as SPH-like particles, with a smoothing\n         length proportional to the cube root of the cell volume.\n.. [#f5] yt provides support for cartesian-gridded CfRadial datasets. Data in native\n         CFRadial coordinates will be gridded on load, see :ref:`loading-cfradial-data`.\n\nIf you have a dataset that uses an output format not yet supported by yt, you\ncan either input your data following :doc:`../examining/Loading_Generic_Array_Data` or\n:doc:`../examining/Loading_Generic_Particle_Data`, or help us by :ref:`creating_frontend`\nfor this new format.\n"
  },
  {
    "path": "doc/source/reference/command-line.rst",
    "content": ".. _command-line:\n\nCommand-Line Usage\n------------------\n\nCommand-line Functions\n~~~~~~~~~~~~~~~~~~~~~~\n\nThe :code:`yt` command-line tool allows you to access some of yt's basic\nfunctionality without opening a python interpreter.  The tools is a collection of\nsubcommands.  These can quickly making plots of slices and projections through a\ndataset, updating yt's codebase, print basic statistics about a dataset, launch\nan IPython notebook session, and more. To get a quick list of what is\navailable, just type:\n\n.. code-block:: bash\n\n   yt -h\n\nThis will print the list of available subcommands,\n\n.. config_help:: yt\n\nTo execute any such function, simply run:\n\n.. code-block:: bash\n\n   yt <subcommand>\n\nFinally, to identify the options associated with any of these subcommand, run:\n\n.. code-block:: bash\n\n   yt <subcommand> -h\n\nPlotting from the command line\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nFirst, we'll discuss plotting from the command line, then we will give a brief\nsummary of the functionality provided by each command line subcommand. This\nexample uses the :code:`DD0010/moving7_0010` dataset distributed in the yt\ngit repository.\n\nFirst let's see what our options are for plotting:\n\n.. code-block:: bash\n\n  $ yt plot --help\n\nThere are many! We can choose whether we want a slice (default) or a\nprojection (``-p``), the field, the colormap, the center of the image, the\nwidth and unit of width of the image, the limits, the weighting field for\nprojections, and on and on. By default the plotting command will execute the\nsame thing along all three axes, so keep that in mind if it takes three times\nas long as you'd like! The center of a slice defaults to the center of\nthe domain, so let's just give that a shot and see what it looks like:\n\n.. code-block:: bash\n\n  $ yt plot DD0010/moving7_0010\n\nWell, that looks pretty bad! What has happened here is that the center of the\ndomain only has some minor shifts in density, so the plot is essentially\nincomprehensible. Let's try it again, but instead of slicing, let's project.\nThis is a line integral through the domain, and for the density field this\nbecomes a column density:\n\n.. code-block:: bash\n\n  $ yt plot -p DD0010/moving7_0010\n\nNow that looks much better! Note that all three axes' projections appear\nnearly indistinguishable, because of how the two spheres are located in the\ndomain. We could center our domain on one of the spheres and take a slice, as\nwell. Now let's see what the domain looks like with grids overlaid, using the\n``--show-grids`` option:\n\n.. code-block:: bash\n\n  $ yt plot --show-grids -p DD0010/moving7_0010\n\nWe can now see all the grids in the field of view. If you want to\nannotate your plot with a scale bar, you can use the\n``--show-scale-bar`` option:\n\n.. code-block:: bash\n\n  $ yt plot --show-scale-bar -p DD0010/moving7_0010\n\n\nCommand-line subcommand summary\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nhelp\n++++\n\nHelp lists all of the various command-line options in yt.\n\ninstinfo and version\n++++++++++++++++++++\n\nThis gives information about where your yt installation is, what version\nand changeset you're using and more.\n\n\nmapserver\n+++++++++\n\nEver wanted to interact with your data using a\n`google maps <http://maps.google.com/>`_-style interface?  Now you can by using the\nyt mapserver. See :ref:`mapserver` for more details.\n\npastebin and pastebin_grab\n++++++++++++++++++++++++++\n\nThe `pastebin <http://paste.yt-project.org/>`_ is an online location where\nyou can anonymously post code snippets and error messages to share with\nother users in a quick, informal way. It is often useful for debugging\ncode or co-developing. By running the ``pastebin`` subcommand with a\ntext file, you send the contents of that file to an anonymous pastebin;\n\n.. code-block:: bash\n\n   yt pastebin my_script.py\n\nBy running the ``pastebin_grab`` subcommand with a pastebin number\n(e.g. 1768), it will grab the contents of that pastebin\n(e.g. the website http://paste.yt-project.org/show/1768 ) and send it to\nSTDOUT for local use.  See :ref:`pastebin` for more information.\n\n.. code-block:: bash\n\n   yt pastebin_grab 1768\n\nupload\n++++++\n\nUpload a file to a public curldrop instance. Curldrop is a simple web\napplication that allows you to upload and download files straight from your\nTerminal with an http client like e.g. curl. It was initially developed by\n`Kevin Kennell <https://github.com/kennell/curldrop>`_ and later forked and\nadjusted for yt’s needs. After a successful upload you will receive a url that\ncan be used to share the data with other people.\n\n.. code-block:: bash\n\n   yt upload my_file.tar.gz\n\nplot\n++++\n\nThis command generates one or many simple plots for a single dataset.\nBy specifying the axis, center, width, etc. (run ``yt help plot`` for\ndetails), you can create slices and projections easily at the\ncommand-line.\n\nrpdb\n++++\n\nConnect to a currently running (on localhost) rpdb session. See\n:ref:`remote-debugging` for more info.\n\nnotebook\n++++++++\n\nLaunches a Jupyter notebook server and prints out instructions on how to open\nan ssh tunnel to connect to the notebook server with a web browser. This is\nmost useful when you want to run a Jupyter notebook using CPUs on a remote\nhost.\n\nstats\n+++++\n\nThis subcommand provides you with some basic statistics on a given dataset.\nIt provides you with the number of grids and cells in each level, the time\nof the dataset, and the resolution. It is tantamount to calling the\n``Dataset.print_stats`` method.\n\nAdditionally, there is the option to print the minimum, maximum, or both for\na given field. The field is assumed to be density by default:\n\n.. code-block:: bash\n\n   yt stats GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150 --max --min\n\nor a different field can be specified using the ``-f`` flag:\n\n.. code-block:: bash\n\n   yt stats GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150 --max --min -f gas,temperature\n\nThe field-related stats output from this command can be directed to a file using\nthe ``-o`` flag:\n\n.. code-block:: bash\n\n   yt stats GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150 --max -o out_stats.dat\n\nupdate\n++++++\n\nThis subcommand updates the yt installation to the most recent version for\nyour repository (e.g. stable, 2.0, development, etc.). Adding the ``--all``\nflag will update the dependencies as well.\n\n.. _upload-image:\n\nupload_image\n++++++++++++\n\nImages are often worth a thousand words, so when you're trying to\nshare a piece of code that generates an image, or you're trying to\ndebug image-generation scripts, it can be useful to send your\nco-authors a link to the image.  This subcommand makes such sharing\na breeze.  By specifying the image to share, ``upload_image`` automatically\nuploads it anonymously to the website `imgur.com <https://imgur.com/>`_ and\nprovides you with a link to share with your collaborators.  Note that the\nimage *must* be in the PNG format in order to use this function.\n\ndelete_image\n++++++++++++\n\nThe image uploaded using ``upload_image`` is assigned with a unique hash that\ncan be used to remove it. This subcommand provides an easy way to send a delete\nrequest directly to the `imgur.com <https://imgur.com/>`_.\n\ndownload\n~~~~~~~~\n\nThis subcommand downloads a file from https://yt-project.org/data. Using ``yt download``,\none can download a file to:\n\n* ``\"test_data_dir\"``: Save the file to the location specified in\n  the ``\"test_data_dir\"`` configuration entry for test data.\n* ``\"supp_data_dir\"``: Save the file to the location specified in\n  the ``\"supp_data_dir\"`` configuration entry for supplemental data.\n* Any valid path to a location on disk, e.g. ``/home/jzuhone/data``.\n\nExamples:\n\n.. code-block:: bash\n\n   $ yt download apec_emissivity_v2.h5 supp_data_dir\n\n.. code-block:: bash\n\n   $ yt download GasSloshing.tar.gz test_data_dir\n\n.. code-block:: bash\n\n   $ yt download ZeldovichPancake.tar.gz /Users/jzuhone/workspace\n\nIf the configuration values ``\"test_data_dir\"`` or ``\"supp_data_dir\"`` have not\nbeen set by the user, an error will be thrown.\n\nConfig helper\n~~~~~~~~~~~~~\n\nThe :code:`yt config` command-line tool allows you to modify and access yt's\nconfiguration without manually locating and opening the config file in an editor.\nTo get a quick list of available commands, just type:\n\n.. code-block:: bash\n\n   yt config -h\n\nThis will print the list of available subcommands:\n\n.. config_help:: yt config\n\n\nSince yt version 4, the configuration file is located in ``$XDG_CONFIG_HOME/yt/yt.toml`` adhering to the\n`XDG Base Directory Specification\n<https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_.\nUnless customized, this defaults to ``$HOME/.config/`` on Unix-like systems (macOS, Linux, ...).\nThe old configuration file (``$XDG_CONFIG_HOME/yt/ytrc``) is deprecated.\nIn order to perform an automatic migration of the old config, you are\nencouraged to run:\n\n.. code-block:: bash\n\n   yt config migrate\n\nThis will convert your old config file to the toml format. The original file\nwill be moved to ``$XDG_CONFIG_HOME/yt/ytrc.bak``.\n\nExamples\n++++++++\n\nListing current content of the config file:\n\n.. code-block:: bash\n\n   $ yt config list\n   [yt]\n   log_level = 50\n\nObtaining a single config value by name:\n\n.. code-block:: bash\n\n   $ yt config get yt log_level\n   50\n\nChanging a single config value:\n\n.. code-block:: bash\n\n   $ yt config set yt log_level 10\n\nRemoving a single config entry:\n\n.. code-block:: bash\n\n   $ yt config rm yt log_level\n"
  },
  {
    "path": "doc/source/reference/configuration.rst",
    "content": "Customizing yt: The Configuration and Plugin Files\n==================================================\n\nyt features ways to customize it to your personal preferences in terms of\nhow much output it displays, loading custom fields, loading custom colormaps,\naccessing test datasets regardless of where you are in the file system, etc.\nThis customization is done through :ref:`configuration-file` and\n:ref:`plugin-file` both of which exist in your ``$HOME/.config/yt`` directory.\n\n.. _configuration-file:\n\nThe Configuration\n-----------------\n\nThe configuration is stored in simple text files (in the `toml <https://github.com/toml-lang/toml>`_ format).\nThe files allow to set internal yt variables to custom default values to be used in future sessions.\nThe configuration can either be stored :ref:`globally <global-conf>` or :ref:`locally <local-conf>`.\n\n.. _global-conf:\n\nGlobal Configuration\n^^^^^^^^^^^^^^^^^^^^\n\nIf no local configuration file exists, yt will look for and recognize the file\n``$HOME/.config/yt/yt.toml`` as a configuration file, containing several options\nthat can be modified and adjusted to control runtime behavior.  For example, a sample\n``$HOME/.config/yt/yt.toml`` file could look\nlike:\n\n.. code-block:: none\n\n   [yt]\n   log_level = 1\n   maximum_stored_datasets = 10000\n\nThis configuration file would set the logging threshold much lower, enabling\nmuch more voluminous output from yt.  Additionally, it increases the number of\ndatasets tracked between instantiations of yt. The configuration file can be\nmanaged using the ``yt config --global`` helper. It can list, add, modify and remove\noptions from the configuration file, e.g.:\n\n.. code-block:: none\n\n   $ yt config -h\n   $ yt config list\n   $ yt config set yt log_level 1\n   $ yt config rm yt maximum_stored_datasets\n\n\n.. _local-conf:\n\nLocal Configuration\n^^^^^^^^^^^^^^^^^^^\n\nyt will look for a file named ``yt.toml`` in the current directory, and upwards\nin the file tree until a match is found. If so, its options are loaded and any\nglobal configuration is ignored. Local configuration files can contain the same\noptions as the global one.\n\nLocal configuration files can either be edited manually, or alternatively they\ncan be managed using ``yt config --local``. It can list, add, modify and remove\noptions, and display the path to the local configuration file, e.g.:\n\n.. code-block:: none\n\n   $ yt config -h\n   $ yt config list --local\n   $ yt config set --local yt log_level 1\n   $ yt config rm --local yt maximum_stored_datasets\n   $ yt config print-path --local\n\nIf no local configuration file is present, these commands will create an (empty) one\nin the current working directory.\n\nConfiguration Options At Runtime\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn addition to setting parameters in the configuration file itself, you can set\nthem at runtime.\n\n.. warning:: Several parameters are only accessed when yt starts up: therefore,\n   if you want to modify any configuration parameters at runtime, you should\n   execute the appropriate commands at the *very top* of your script!\n\nThis involves importing the configuration object and then setting a given\nparameter to be equal to a specific string.  Note that even for items that\naccept integers, floating points and other non-string types, you *must* set\nthem to be a string or else the configuration object will consider them broken.\n\nHere is an example script, where we adjust the logging at startup:\n\n.. code-block:: python\n\n   import yt\n\n   yt.set_log_level(1)\n\n   ds = yt.load(\"my_data0001\")\n   ds.print_stats()\n\nThis has the same effect as setting ``log_level = 1`` in the configuration\nfile. Note that a log level of 1 means that all log messages are printed to\nstdout.  To disable logging, set the log level to 50.\n\n\n.. _config-options:\n\nAvailable Configuration Options\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe following external parameters are available.  A number of parameters are\nused internally.\n\n* ``colored_logs`` (default: ``False``): Should logs be colored?\n* ``default_colormap`` (default: ``cmyt.arbre``): What colormap should be used by\n  default for yt-produced images?\n* ``plugin_filename``  (default ``my_plugins.py``) The name of our plugin file.\n* ``log_level`` (default: ``20``): What is the threshold (0 to 50) for\n  outputting log files?\n* ``test_data_dir`` (default: ``/does/not/exist``): The default path the\n  ``load()`` function searches for datasets when it cannot find a dataset in the\n  current directory.\n* ``reconstruct_index`` (default: ``True``): If true, grid edges for patch AMR\n  datasets will be adjusted such that they fall as close as possible to an\n  integer multiple of the local cell width. If you are working with a dataset\n  with a large number of grids, setting this to False can speed up loading\n  your dataset possibly at the cost of grid-aligned artifacts showing up in\n  slice visualizations.\n* ``requires_ds_strict`` (default: ``True``): If true, answer tests wrapped\n  with :func:`~yt.utilities.answer_testing.framework.requires_ds` will raise\n  :class:`~yt.utilities.exceptions.YTUnidentifiedDataType` rather than consuming\n  it if required dataset is not present.\n* ``serialize`` (default: ``False``): If true, perform automatic\n  :ref:`object serialization <object-serialization>`\n* ``sketchfab_api_key`` (default: empty): API key for https://sketchfab.com/ for\n  uploading AMRSurface objects.\n* ``suppress_stream_logging`` (default: ``False``): If true, execution mode will be\n  quiet.\n* ``stdout_stream_logging`` (default: ``False``): If true, logging is directed\n  to stdout rather than stderr\n* ``skip_dataset_cache`` (default: ``False``): If true, automatic caching of datasets\n  is turned off.\n* ``supp_data_dir`` (default: ``/does/not/exist``): The default path certain\n  submodules of yt look in for supplemental data files.\n\n\n.. _per-field-plotconfig:\n\nAvailable per-field Plot Options\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is possible to customize the default behaviour of plots using per-field configuration.\nThe default options for plotting a given field can be specified in the configuration file\nin ``[plot.field_type.field_name]`` blocks. The available keys are\n\n* ``cmap`` (default: ``yt.default_colormap``, see :ref:`config-options`): the colormap to\n  use for the field.\n* ``log`` (default: ``True``): use a log scale (or symlog if ``linthresh`` is also set).\n* ``linthresh`` (default: ``None``): if set to a float different than ``None`` and ``log`` is\n  ``True``, use a symlog normalization with the given linear threshold.\n* ``units`` (defaults to the units of the field): the units to use to represent the field.\n* ``path_length_units`` (default: ``cm``): the unit of the integration length when doing\n  e.g. projections. This always has the dimensions of a length. Note that this will only\n  be used if ``units`` is also set for the field. The final units will then be\n  ``units*path_length_units``.\n\nYou can also set defaults for all fields of a given field type by omitting the field name,\nas illustrated below in the deposit block.\n\n.. code-block:: toml\n\n  [plot.gas.density]\n  cmap = \"plasma\"\n  log = true\n  units = \"mp/cm**3\"\n\n  [plot.gas.velocity_divergence]\n  cmap = \"bwr\"  # use a diverging colormap\n  log = false   # and a linear scale\n\n  [plot.deposit]\n  path_length_units = \"kpc\"  # use kpc for deposition projections\n\n.. _per-field-config:\n\nAvailable per-Field Configuration Options\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is possible to set attributes for fields that would typically be set by the\nfrontend source code, such as the aliases for field, the units that field\nshould be expected in, and the display name.  This allows individuals to\ncustomize what yt expects of a given dataset without modifying the yt source\ncode.  For instance, if your dataset has an on-disk field called\n\"particle_extra_field_1\" you could specify its units, display name, and what yt\nshould think of it as with:\n\n.. code-block:: toml\n\n   [fields.nbody.particle_extra_field_1]\n   aliases = [\"particle_other_fancy_name\", \"particle_alternative_fancy_name\"]\n   units = \"code_time\"\n   display_name = \"Dinosaurs Density\"\n\n.. _plugin-file:\n\nPlugin Files\n------------\n\nPlugin files are a means of creating custom fields, quantities, data objects,\ncolormaps, and other code executable functions or classes to be used in future\nyt sessions without modifying the source code directly.\n\nTo enable a plugin file, call the function\n:func:`~yt.funcs.enable_plugins` at the top of your script.\n\nGlobal system plugin file\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nyt will look for and recognize the file ``$HOME/.config/yt/my_plugins.py`` as a\nplugin file. It is possible to rename this file to ``$HOME/.config/yt/<plugin_filename>.py``\nby defining ``plugin_filename`` in your ``yt.toml`` file, as mentioned above.\n\n.. note::\n\n   You can tell that your system plugin file is being parsed by watching for a logging\n   message when you import yt. Note that the ``yt load`` command line entry point parses\n   the plugin file.\n\n\nLocal project plugin file\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nOptionally, :func:`~yt.funcs.enable_plugins` can be passed an argument to specify\na custom location for a plugin file. This can be useful to define project wise customizations.\nIn that use case, any system-level plugin file will be ignored.\n\nPlugin File Format\n^^^^^^^^^^^^^^^^^^\n\nPlugin files should contain pure Python code. If accessing yt functions and classes\nthey will not require the ``yt.`` prefix, because of how they are loaded.\n\nFor example, if one created a plugin file containing:\n\n.. code-block:: python\n\n   def _myfunc(data):\n       return np.random.random(data[\"density\"].shape)\n\n\n   add_field(\n       \"random\",\n       function=_myfunc,\n       sampling_type=\"cell\",\n       dimensions=\"dimensionless\",\n       units=\"auto\",\n   )\n\nthen all of my data objects would have access to the field ``random``.\n\nYou can also define other convenience functions in your plugin file.  For\ninstance, you could define some variables or functions, and even import common\nmodules:\n\n.. code-block:: python\n\n   import os\n\n   HOMEDIR = \"/home/username/\"\n   RUNDIR = \"/scratch/runs/\"\n\n\n   def load_run(fn):\n       if not os.path.exists(RUNDIR + fn):\n           return None\n       return load(RUNDIR + fn)\n\nIn this case, we've written ``load_run`` to look in a specific directory to see\nif it can find an output with the given name.  So now we can write scripts that\nuse this function:\n\n.. code-block:: python\n\n   import yt\n\n   yt.enable_plugins()\n\n   my_run = yt.load_run(\"hotgasflow/DD0040/DD0040\")\n\nAnd because we have used ``yt.enable_plugins`` we have access to the\n``load_run`` function defined in our plugin file.\n\n.. note::\n    if your convenience function's name colliding with an existing object\n    within yt's namespace, it will be ignored.\n\nNote that using the plugins file implies that your script is no longer fully\nreproducible. If you share your script with someone else and use some of the\nfunctionality if your plugins file, you will also need to share your plugins\nfile for someone else to re-run your script properly.\n\nAdding Custom Colormaps\n^^^^^^^^^^^^^^^^^^^^^^^\n\nTo add custom :ref:`colormaps` to your plugin file, you must use the\n:func:`~yt.visualization.color_maps.make_colormap` function to generate a\ncolormap of your choice and then add it to the plugin file. You can see\nan example of this in :ref:`custom-colormaps`. Remember that you don't need\nto prefix commands in your plugin file with ``yt.``, but you'll only be\nable to access the colormaps when you load the ``yt.mods`` module, not simply\n``yt``.\n"
  },
  {
    "path": "doc/source/reference/demeshening.rst",
    "content": ".. _demeshening:\n\nHow Particles are Indexed\n=========================\n\nWith yt-4.0, the method by which particles are indexed changed considerably.\nWhereas in previous versions, particles were indexed based on their position in\nan octree (the structure of which was determined by particle number density),\nin yt-4.0 this system was overhauled to utilize a `bitmap\nindex <https://en.wikipedia.org/wiki/Bitmap_index>`_ based on a space-filling\ncurve, using a `enhanced word-aligned\nhybrid <https://github.com/lemire/ewahboolarray>`_ boolean array as their\nbackend.\n\n.. note::\n\n   You may see scattered references to \"the demeshening\" (including in the\n   filename for this document!). This was a humorous name used in the yt\n   development process to refer to removing a global (octree) mesh for\n   particle codes.\n\nBy avoiding the use of octrees as a base mesh, yt is able to create *much* more\naccurate SPH visualizations.  We have a `gallery demonstrating\nthis <https://matthewturk.github.io/yt4-gallery/>`_ but even in this\nside-by-side comparison the differences can be seen quite easily, with the left\nimage being from the old, octree-based approach and the right image the new,\nmeshless approach.\n\n.. image:: _images/yt3_p0010_proj_density_None_x_z002.png\n   :width: 45 %\n.. image:: _images/yt4_p0010_proj_density_None_x_z002.png\n   :width: 45 %\n\nEffectively, what \"the demeshening\" does is allow yt to treat the particles as\ndiscrete objects (or with an area of influence) and use their positions in a\nmulti-level index to optimize and minimize the disk operations necessary to\nload only those particles it needs.\n\n.. note::\n\n   The theory and implementation of yt's bitmap indexing system is described in\n   some detail in the `yt 4.0 paper <https://yt-project.github.io/yt-4.0-paper/>`_\n   in the section entitled `Indexing Discrete-Point Datasets <https://yt-project.github.io/yt-4.0-paper/#sec:point_indexing>`_.\n\nIn brief, however, what this relies on is two numbers, ``index_order1`` and\n``index_order2``.  These control the \"coarse\" and \"refined\" sets of indices,\nand they are supplied to any particle dataset ``load()`` in the form of a tuple\nas the argument ``index_order``.  By default these are set to 5 and 7,\nrespectively, but it is entirely likely that a different set of values will\nwork better for your purposes.\n\nFor example, if you were to use the sample Gadget-3 dataset, you could override\nthe default values and use values of 5 and 5 by specifying this argument to the\n``load_sample`` function; this works with ``load`` as well.\n\n.. code-block:: python\n\n   ds = yt.load_sample(\"Gadget3-snap-format2\", index_order=(5, 5))\n\nSo this is how you *change* the index order, but it doesn't explain precisely\nwhat this \"index order\" actually is.\n\nIndexing and Why yt Does it\n---------------------------\n\nyt is based on the idea that data should be selected and read only when it is\nneeded.  So for instance, if you only want particles or grid cells from a small\nregion in the center of your dataset, yt wants to avoid any reading of the data\n*outside* of that region.  Now, in practice, this isn't entirely possible --\nparticularly with particles, you can't actually tell when something is inside\nor outside of a region *until* you read it, because the particle locations are\n*stored in the dataset*.\n\nOne way to avoid this is to have an index of the data, so that yt can know that\nsome of the data that is located *here* in space is located *there* in the file\nor files on disk.  So if you're able to say, I only care about data in \"region\nA\", you can look for those files that contain data within \"region A,\" read\nthose, and discard the parts of them that are *not* within \"region A.\"\n\nThe finer grained the index, the longer it takes to build that index -- and the\nlarger than index is, and the longer it takes to query.  The cost of having too\n*coarse* an index, on the other hand, is that the IO conducted to read a given\nregion is likely to be *too much*, and more particles will be discarded after\nbeing read, before being \"selected\" by the data selector (sphere, region, etc).\n\nAn important note about all of this is that the index system is not meant to\n*replace* the positions stored on disk, but instead to speed up queries of\nthose positions -- the index is meant to be lossy in representation, and only\nprovides means of generating IO information.  Additionally, the atomic unit\nthat yt considers when conducting IO or selection queries is called a \"chunk\"\ninternally.  For situations where the individual *files* are very, very large,\nyt will \"sub-chunk\" these into smaller bits, which are by-default set to $64^3$\nparticles.  Whenever indexing is done, it is done at this granular level, with\noffsets to individual particle collections stored.  For instance, if you had a\n(single) file with $1024^3$ particles in it, yt would instead regard this as a\nseries of $64^3$ particle files, and index each one individually.\n\nIndex Order\n-----------\n\nThe bitmap index system is based on a two-level scheme for assigning positions\nin three-dimensional space to integer values.  What this means is that each\nparticle is assigned a \"coarse\" index, which is global to the full domain of\nthe collection of particles, and *if necessary* an additional \"refined\" index\nis assigned to the particle, within that coarse index.\n\nThe index \"order\" values refer to the number of entries on a side that each\nindex system is allowed.  For instance, if we allow the particles to be\nsubdivided into 8 \"bins\" in each direction, this would correspond to an index\norder of 3 (as $2^3 = 8$); correspondingly, an index order of 5 would be 32\nbins in each direction, and an index order of 7 would be 128 bins in each\ndirection.  Each particle is then assigned a set of i, j, k values for the bin\nvalue in each dimension, and these i, j, k values are combined into a single\n(64-bit) integer according to a space-filling curve.\n\nThe process by which this is done by yt is as follows:\n\n  1. For each \"chunk\" of data -- which may be a file, or a subset of a file in\n     which particles are contained -- assign each particle to an integer value\n     according to the space-filling curve and the coarse index order.  Set the\n     \"bit\" in an array of boolean values that each of these integers correspond\n     to.  Note that this is almost certainly *reductive* -- there will be fewer\n     bits set than there are particles, which is *by design*.\n  2. Once all chunks or files have been assigned an array of bits that\n     correspond to the places where, according to the coarse indexing scheme,\n     they have particles, identify all those \"bits\" that have been set by more\n     than one chunk.  All of these bits correspond to locations where more than\n     one file contains particles -- so if you want to select something from\n     this region, you'd need to read more than one file.\n  3. For each \"collision\" location, apply a *second-order* index, to identify\n     which sub-regions are touched by more than one file.\n\nAt the end of this process, each file will be associated with a single \"coarse\"\nindex (which covers the entire domain of the data), as well as a set of\n\"collision\" locations, and in each \"collision\" location a set of bitarrays that\ncorrespond to that subregion.\n\nWhen reading data, yt will identify which \"coarse\" index regions are necessary\nto read.  If any of those coarse index regions are covered by more than one\nfile, it will examine the \"refined\" index for those regions and see if it is able\nto subset more efficiently.  Because all of these operations can be done with\nlogical operations, this considerably reduces the amount of data that needs to\nbe read from disk before expensive selection operations are conducted.\n\nFor those situations that involve particles with regions of influence -- such\nas smoothed particle hydrodynamics, where particles have associated smoothing\nlengths -- these are taken into account when conducting the indexing system.\n\nEfficiency of Index Orders\n--------------------------\n\nWhat this can lead to, however, is situations where (particularly at the edges\nof regions populated by SPH particles) the indexing system identifies\ncollisions, but the relatively small number of particles and correspondingly\nlarge \"smoothing lengths\" result in a large number of \"refined\" index values that\nneed to be set.\n\nCounterintuitively, this actually means that occasionally the \"refined\" indexing\nprocess can take an inordinately long amount of time for *small* datasets,\nrather than large datasets.\n\nIn these situations, it is typically sufficient to set the \"refined\" index order\nto be much lower than its default value.  For instance, setting the\n``index_order`` to (5, 3) means that the full domain will be subdivided into 32\nbins in each dimension, and any \"collision\" zones will be further subdivided\ninto 8 bins in each dimension (corresponding to an effective 256 bins across\nthe full domain).\n\nIf you are experiencing very long index times, this may be a productive\nparameter to modify.  For instance, if you are seeing very rapid \"coarse\"\nindexing followed by very, very slow \"refined\" indexing, this likely plays a\npart; often this will be most obvious in small-ish (i.e., $256^3$ or smaller)\ndatasets.\n\nIndex Caching\n-------------\n\nThe index values are cached between instantiation, in a sidecar file named with\nthe name of the dataset file and the suffix ``.indexII_JJ.ewah``, where ``II``\nand ``JJ`` are ``index_order1`` and ``index_order2``.  So for instance, if\n``index_order`` is set to (5, 7), and you are loading a dataset file named\n\"snapshot_200.hdf5\", after indexing, you will have an index sidecar file named\n``snapshot_200.hdf5.index5_7.ewah``.  On subsequent loads, this index file will\nbe reused, rather than re-generated.\n\nBy *default* these sidecars are stored next to the dataset itself, in the same\ndirectory.  However, the filename scheme (and thus location) can be changed by\nsupplying an alternate filename to the ``load`` command with the argument\n``index_filename``.  For instance, if you are accessing data in a read-only\nlocation, you can specify that the index will be cached in a location that is\nwrite-accessible to you.\n\nThese files contain the *compressed* bitmap index values, along with some\nmetadata that describes the version of the indexing system they use and so\nforth.  If the version of the index that yt uses has changed, they will be\nregenerated; in general this will not vary very often (and should be much less\nfrequent than, for instance, yt releases) and yt will provide a message to let\nyou know it is doing it.\n\nThe file size of these cached index files can be difficult to estimate; because\nit is based on a compressed bitmap arrays, it will depend on the spatial\norganization of the particles it is indexing, and how co-located they are\naccording to the space filling curve.  For very small datasets it will be\nsmall, but we do not expect these index files to grow beyond a few hundred\nmegabytes even in the extreme case of large datasets that have little to no\ncoherence in their clustering.\n"
  },
  {
    "path": "doc/source/reference/field_list.rst",
    "content": ".. _available-fields:\n\n.. yt_showfields::\n"
  },
  {
    "path": "doc/source/reference/index.rst",
    "content": "Reference Materials\n===================\n\nHere we include reference materials for yt with a list of all the code formats\nsupported, a description of how to use yt at the command line, a detailed\nlisting of individual classes and functions, a description of the useful\nconfig file, and finally a list of changes between each major release of the\ncode.\n\n.. toctree::\n   :maxdepth: 2\n\n   code_support\n   command-line\n   api/api\n   api/modules\n   configuration\n   python_introduction\n   field_list\n   demeshening\n   changelog\n"
  },
  {
    "path": "doc/source/reference/python_introduction.rst",
    "content": "A Brief Introduction to Python\n------------------------------\n\nAll scripts that use yt are really Python scripts that use yt as a library.\nThe great thing about Python is that the standard set of libraries that come\nwith it are very extensive -- Python comes with everything you need to write\nand run mail servers and web servers, create Logo-style turtle graphics, do\narbitrary precision math, interact with the operating system, and many other\nthings.  In addition to that, efforts by the scientific community to improve\nPython for computational science have created libraries for fast array\ncomputation, GPGPU operations, distributed computing, and visualization.\n\nSo when you use yt through the scripting interface, you get for free the\nability to interlink it with any of the other libraries available for Python.\nIn the past, this has been used to create new types of visualization using\nOpenGL, data management using Google Docs, and even a simulation that sends an\nSMS when it has new data to report on.\n\nBut, this also means learning a little bit of Python!  This next section\npresents a short tutorial of how to start up and think about Python, and then\nmoves on to how to use Python with yt.\n\nStarting Python\n+++++++++++++++\n\nPython has two different modes of execution: interactive execution, and\nscripted execution.  We'll start with interactive execution and then move on to\nhow to write and use scripts.\n\nBefore we get started, we should briefly touch upon the commands ``help`` and\n``dir``.  These two commands provide a level of introspection:\n``help(something)`` will return the internal documentation on ``something``,\nincluding how it can be used and all of the possible \"methods\" that can be\ncalled on it.  ``dir()`` will return the available commands and objects that\ncan be directly called, and ``dir(something)`` will return information about\nall the commands that ``something`` provides.  This probably sounds a bit\nopaque, but it will become clearer with time -- it's also probably helpful to\ncall ``help`` on any or all of the objects we create during this orientation.\n\nTo start up Python, at your prompt simply type:\n\n.. code-block:: bash\n\n  $ python\n\nThis will open up Python and give to you a simple prompt of three greater-than\nsigns.  Let's inaugurate the occasion appropriately -- type this::\n\n   >>> print(\"Hello, world.\")\n\nAs you can see, this printed out the string \"Hello, world.\" just as we\nexpected.  Now let's try a more advanced string, one with a number in it.  For\nthis we'll use an \"f-string\", which is the preferred way to format strings in modern Python.\nWe'll print pi, but only with three digits of accuracy.::\n\n   >>> print(f\"Pi is precisely {3.1415926:0.2f}\")\n\nThis took the number we fed it (3.1415926) and printed it out as a floating\npoint number with two decimal places. Now let's try something a bit different\n-- let's print out both the name of the number and its value.::\n\n   >>> print(f\"{'pi'} is precisely {3.1415926:0.2f}\")\n\nAnd there you have it -- the very basics of starting up Python, and some very\nsimple mechanisms for printing values out.  Now let's explore a few types of\ndata that Python can store and manipulate.\n\nData Types\n++++++++++\n\nPython provides a number of datatypes, but the main ones that we'll concern\nourselves with at first are lists, tuples, strings, numbers, and dictionaries.\nMost of these can be instantiated in a couple different ways, and we'll look at\na few of them.  Some of these objects can be modified in place, which is called\nbeing mutable, and some are immutable and cannot be modified in place.  We'll\ntalk below about what that means.\n\nPerhaps most importantly, though, is an idea about how Python works in terms of\nnames and bindings -- this is called the \"object model.\"  When you create an\nobject, that is independent from binding it to a name -- think of this like\npointers in C.  This also operates a bit differently for mutable and immutable\ntypes.  We'll talk a bit more about this later, but it's handy to initially\nthink of things in terms of references.  (This is also, not coincidentally, how\nPython thinks of things internally as well!)  When you create an object,\ninitially it has no references to it -- there's nothing that points to it.\nWhen you bind it to a name, then you are making a reference, and so its\n\"reference count\" is now 1.  When something else makes a reference to it, the\nreference count goes up to 2.  When the reference count returns to 0, the\nobject is deleted -- but not before.  This concept of reference counting comes\nup from time to time, but it's not going to be a focus of this orientation.\n\nThe two easiest datatypes are simply strings and numbers.  We can make a string\nvery easily::\n\n   >>> my_string = \"Hello there\"\n   >>> print(my_string)\n\nWe can also take a look at each individual part of a string.  We'll use the\n'slicing' notation for this.  As a brief note, slicing is 0-indexed, so that\nelement 0 corresponds to the first element.  If we wanted to see the third\nelement of our string::\n\n   >>> print(my_string[2])\n\nWe can also take the third through the 5 elements::\n\n   >>> print(my_string[2:5])\n\nBut note that if you try to change an element directly, Python objects and it\nwon't let you -- that's because strings are immutable.  (But, note that because\nof how the += operator works, we can do \"my_string += '1'\" without issue.)\n\nTo create a number, we do something similar::\n\n   >>> a = 10\n   >>> print(a)\n\nThis works for floating points as well.  Now we can do math on these numbers::\n\n   >>> print(a**2)\n   >>> print(a + 5)\n   >>> print(a + 5.1)\n   >>> print(a / 2.0)\n\n\nNow that we have a couple primitive datatypes, we can move on to sequences --\nlists and tuples.  These two objects are very similar, in that they are\ncollections of arbitrary data types.  We'll only look at collections of strings\nand numbers for now, but these can be filled with arbitrary datatypes\n(including objects that yt provides, like spheres, datasets, grids, and\nso on.)  The easiest way to create a list is to simply construct one::\n\n   >>> my_list = []\n\nAt this point, you can find out how long it is, you can append elements, and\nyou can access them at will::\n\n   >>> my_list.append(1)\n   >>> my_list.append(my_string)\n   >>> print(my_list[0])\n   >>> print(my_list[-1])\n   >>> print(len(my_list))\n\nYou can also create a list already containing an initial set of elements::\n\n   >>> my_list = [1, 2, 3, \"four\"]\n   >>> my_list[2] = \"three!!\"\n\nLists are very powerful objects, which we'll talk about a bit below when\ndiscussing how iteration works in Python.\n\nA tuple is like a list, in that it's a sequence of objects, and it can be\nsliced and examined piece by piece.  But unlike a list, it's immutable:\nwhatever a tuple contains at instantiation is what it contains for the rest of\nits existence.  Creating a tuple is just like creating a list, except that you\nuse parentheses instead of brackets::\n\n   >>> my_tuple = (1, \"a\", 62.6)\n\nTuples show up very commonly when handling arguments to Python functions and\nwhen dealing with multiple return values from a function.  They can also be\nunpacked::\n\n   >>> v1, v2, v3 = my_tuple\n\nwill assign 1, \"a\", and 62.6 to v1, v2, and v3, respectively.\n\nMutables vs Immutables and Is Versus Equals\n+++++++++++++++++++++++++++++++++++++++++++\n\nThis section is not a \"must read\" -- it's more of an exploration of how\nPython's objects work.  At some point this is something you may want to be\nfamiliar with, but it's not strictly necessary on your first pass.\n\nPython provides the operator ``is`` as well as the comparison operator ``==``.\nThe operator ``is`` determines whether two objects are in fact the same object,\nwhereas the operator ``==`` determines if they are equal, according to some\narbitrarily defined equality operation.  Think of this like comparing the\nserial numbers on two pictures of a dollar bill (the ``is`` operator) versus\ncomparing the values of two pieces of currency (the ``==`` operator).\n\nThis digs in to the idea of how the Python object model works, so let's test\nsome things out.  For instance, let's take a look at comparing two floating\npoint numbers::\n\n   >>> a = 10.1\n   >>> b = 10.1\n   >>> print(a == b)\n   >>> print(a is b)\n\nThe first one returned True, but the second one returned False.  Even though\nboth numbers are equal, they point to different points in memory.  Now let's\ntry assigning things a bit differently::\n\n   >>> b = a\n   >>> print(a is b)\n\nThis time it's true -- they point to the same part of memory.  Try incrementing\none and seeing what happens.  Now let's try this with a string::\n\n   >>> a = \"Hi there\"\n   >>> b = a\n   >>> print(a is b)\n\nOkay, so our intuition here works the same way, and it returns True.  But what\nhappens if we modify the string?::\n\n   >>> a += \"!\"\n   >>> print(a)\n   >>> print(b)\n   >>> print(a is b)\n\nAs you can see, now not only does a contain the value \"Hi there!\", but it also\nis a different value than what b contains, and it also points to a different\nregion in memory.  That's because strings are immutable -- the act of adding on\n\"!\" actually creates an entirely new string and assigns that entirely new\nstring to the variable a, leaving the string pointed to by b untouched.\n\nWith lists, which are mutable, we have a bit more liberty with how we modify\nthe items and how that modifies the object and its pointers.  A list is really\njust a pointer to a collection; the list object itself does not have any\nspecial knowledge of what constitutes that list.  So when we initialize a and\nb::\n\n   >>> a = [1, 5, 1094.154]\n   >>> b = a\n\nWe end up with two pointers to the same set of objects.  (We can also have a\nlist inside a list, which adds another fun layer.)  Now when we modify a, it\nshows up in b::\n\n   >>> a.append(\"hat wobble\")\n   >>> print(b[-1])\n\nThis also works with the concatenation operator::\n\n   >>> a += [\"beta sequences\"]\n   >>> print(a[-1], b[-1])\n\nBut we can force a break in this by slicing the list when we initialize::\n\n   >>> a = [1, 2, 3, 4]\n   >>> b = a[:]\n   >>> a.append(5)\n   >>> print(b[-1], a[-1])\n\nHere they are different, because we have sliced the list when initializing b.\n\nThe coolest datatype available in Python, however, is the dictionary.  This is\na mapping object of key:value pairs, where one value is used to look up another\nvalue.  We can instantiate a dictionary in a variety of ways, but for now we'll\nonly look at one of the simplest mechanisms for doing so::\n\n   >>> my_dict = {}\n   >>> my_dict[\"A\"] = 1.0\n   >>> my_dict[\"B\"] = 154.014\n   >>> my_dict[14001] = \"This number is great\"\n   >>> print(my_dict[\"A\"])\n\nAs you can see, one value can be used to look up another.  Almost all datatypes\n(with a few notable exceptions, but for the most part these are quite uncommon)\ncan be used as a key, and you can use any object as a value.\n\nWe won't spend too much time discussing dictionaries explicitly, but I will\nleave you with a word on their efficiency: the Python lookup algorithm is known\nfor its hand-tuned optimization and speed, and it's very common to use\ndictionaries to look up hundreds of thousands or even millions of elements and\nto expect it to be responsive.\n\nLooping\n+++++++\n\nLooping in Python is both different and more powerful than in lower-level\nlanguages.  Rather than looping based exclusively on conditionals (which is\npossible in Python) the fundamental mode of looping in Python is iterating\nover objects.  In C, one might construct a loop where some counter variable is\ninitialized, and at each iteration of the loop it is incremented and compared\nagainst a reference value; when the counter variable reaches the reference\nvariable, the loop is terminated.\n\nIn Python, on the other hand, to accomplish iteration through a set of\nsequential integers, one actually constructs a sequence of those integers, and\niterates over that sequence.  For more discussion of this, and some very, very\npowerful ways of accomplishing this iteration process, look through the Python\ndocumentation for the words 'iterable' and 'generator.'\n\nTo see this in action, let's first take a look at the built-in function\n``range``. ::\n\n   >>> print(range(10))\n\nAs you can see, what the function ``range`` returns is a list of integers,\nstarting at zero, that is as long as the argument to the ``range`` function.\nIn practice, this means that calling ``range(N)`` returns ``0, 1, 2, ... N-1``\nin a list.  So now we can execute a for loop, but first, an important\ninterlude:\n\nControl blocks in Python are delimited by white space.\n\nThis means that, unlike in C with its brackets, you indicate an isolated\ncontrol block for conditionals, function declarations, loops and other things\nwith an indentation.  When that control block ends, you dedent the text.  In\nyt, we use four spaces -- I recommend you do the same -- which can be inserted\nby a text editor in place of tab characters.\n\nLet's try this out with a for loop.  First type ``for i in range(10):`` and\npress enter.  This will change the prompt to be three periods, instead of three\ngreater-than signs, and you will be expected to hit the tab key to indent.\nThen type \"print(i)\", press enter, and then instead of indenting again, press\nenter again.  The entire entry should look like this::\n\n   >>> for i in range(10):\n   ...     print(i)\n   ...\n\nAs you can see, it prints out each integer in turn.  So far this feels a lot\nlike C.  (It won't, if you start using iterables in place of sequences -- for\ninstance, ``xrange`` operates just like range, except instead of returning an\nalready-created list, it returns the promise of a sequence, whose elements\naren't created until they are requested.)  Let's try it with our earlier list::\n\n   >>> my_sequence = [\"a\", \"b\", 4, 110.4]\n   >>> for i in my_sequence:\n   ...     print(i)\n   ...\n\nThis time it prints out every item in the sequence.\n\nA common idiom that gets used a lot is to figure out which index the loop is\nat.  The first time this is written, it usually goes something like this::\n\n   >>> index = 0\n   >>> my_sequence = [\"a\", \"b\", 4, 110.4]\n   >>> for i in my_sequence:\n   ...     print(\"%s = %s\" % (index, i))\n   ...     index += 1\n   ...\n\nThis does what you would expect: it prints out the index we're at, then the\nvalue of that index in the list.  But there's an easier way to do this, less\nprone to error -- and a bit cleaner!  You can use the ``enumerate`` function to\naccomplish this::\n\n   >>> my_sequence = [\"a\", \"b\", 4, 110.4]\n   >>> for index, val in enumerate(my_sequence):\n   ...     print(\"%s = %s\" % (index, val))\n   ...\n\nThis does the exact same thing, but we didn't have to keep track of the counter\nvariable ourselves.  You can use the function ``reversed`` to reverse a\nsequence in a similar fashion.  Try this out::\n\n   >>> my_sequence = range(10)\n   >>> for val in reversed(my_sequence):\n   ...     print(val)\n   ...\n\nWe can even combine the two!::\n\n   >>> my_sequence = range(10)\n   >>> for index, val in enumerate(reversed(my_sequence)):\n   ...     print(\"%s = %s\" % (index, val))\n   ...\n\nThe most fun of all the built-in functions that operate on iterables, however,\nis the ``zip`` function.  This function will combine two sequences (but only up\nto the shorter of the two -- so if one has 16 elements and the other 1000, the\nzipped sequence will only have 16) and produce iterators over both.\n\nAs an example, let's say you have two sequences of values, and you want to\nproduce a single combined sequence from them.::\n\n   >>> seq1 = [\"Hello\", \"What's up\", \"I'm fine\"]\n   >>> seq2 = [\"!\", \"?\", \".\"]\n   >>> seq3 = []\n   >>> for v1, v2 in zip(seq1, seq2):\n   ...     seq3.append(v1 + v2)\n   ...\n   >>> print(seq3)\n\nAs you can see, this is much easier than constructing index values by hand and\nthen drawing from the two sequences using those index values.  I should note\nthat while this is great in some instances, for numeric operations, NumPy\narrays (discussed below) will invariably be faster.\n\nConditionals\n++++++++++++\n\nConditionals, like loops, are delimited by indentation.  They follow a\nrelatively simple structure, with an \"if\" statement, followed by the\nconditional itself, and then a block of indented text to be executed in the\nevent of the success of that conditional.  For subsequent conditionals, the\nword \"elif\" is used, and for the default, the word \"else\" is used.\n\nAs a brief aside, the case/switch statement in Python is typically executed\nusing an if/elif/else block; this can be done using more complicated\ndictionary-type statements with functions, but that typically only adds\nunnecessary complexity.\n\nFor a simple example of how to do an if/else statement, we'll return to the\nidea of iterating over a loop of numbers.  We'll use the ``%`` operator, which\nis a binary modulus operation: it divides the first number by the second and\nthen returns the remainder.  Our first pass will examine the remainders from\ndividing by 2, and print out all the even numbers.  (There are of course easier\nways of determining which numbers are multiples of 2 -- particularly using\nNumPy, as we'll do below.)::\n\n   >>> for val in range(100):\n   ...     if val % 2 == 0:\n   ...         print(\"%s is a multiple of 2\" % (val))\n   ...\n\nNow we'll add on an ``else`` statement, so that we print out all the odd\nnumbers as well, with the caveat that they are not multiples of 2.::\n\n   >>> for val in range(100):\n   ...     if val % 2 == 0:\n   ...         print(\"%s is a multiple of 2\" % (val))\n   ...     else:\n   ...         print(\"%s is not a multiple of 2\" % (val))\n   ...\n\nLet's extend this to check the remainders of division with both 2 and 3, and\ndetermine which numbers are multiples of 2, 3, or neither.  We'll do this for\nall numbers between 0 and 99.::\n\n   >>> for val in range(100):\n   ...     if val % 2 == 0:\n   ...         print(\"%s is a multiple of 2\" % (val))\n   ...     elif val % 3 == 0:\n   ...         print(\"%s is a multiple of 3\" % (val))\n   ...     else:\n   ...         print(\"%s is not a multiple of 2 or 3\" % (val))\n   ...\n\nThis should print out which numbers are multiples of 2 or 3 -- but note that\nwe're not catching all the multiples of 6, which are multiples of both 2 and 3.\nTo do that, we have a couple options, but we can start with just changing the\nfirst if statement to encompass both, using the ``and`` operator::\n\n   >>> for val in range(100):\n   ...     if val % 2 == 3 and val % 3 == 0:\n   ...         print(\"%s is a multiple of 6\" % (val))\n   ...     elif val % 2 == 0:\n   ...         print(\"%s is a multiple of 2\" % (val))\n   ...     elif val % 3 == 0:\n   ...         print(\"%s is a multiple of 3\" % (val))\n   ...     else:\n   ...         print(\"%s is not a multiple of 2 or 3\" % (val))\n   ...\n\nIn addition to the ``and`` statement, the ``or`` and ``not`` statements work in\nthe expected manner.  There are also several built-in operators, including\n``any`` and ``all`` that operate on sequences of conditionals, but those are\nperhaps better saved for later.\n\nArray Operations\n++++++++++++++++\n\nIn general, iteration over sequences carries with it some substantial overhead:\neach value is selected, bound to a local name, and then its type is determined\nwhen it is acted upon.  This is, regrettably, the price of the generality that\nPython brings with it.  While this overhead is minimal for operations acting on\na handful of values, if you have a million floating point elements in a\nsequence and you want to simply add 1.2 to all of them, or multiply them by\n2.5, or exponentiate them, this carries with it a substantial performance hit.\n\nTo accommodate this, the NumPy library has been created to provide very fast\noperations on arrays of numerical elements.  When you create a NumPy array, you\nare creating a shaped array of (potentially) sequential locations in memory\nwhich can be operated on at the C-level, rather than at the interpreted Python\nlevel.  For this reason, which NumPy arrays can act like Python sequences can,\nand can thus be iterated over, modified in place, and sliced, they can also be\naddressed as a monolithic block.  All of the fluid and particle quantities used\nin yt will be expressed as NumPy arrays, allowing for both efficient\ncomputation and a minimal memory footprint.\n\nFor instance, the following operation will not work in standard Python::\n\n   >>> vals = range(10)\n   >>> vals *= 2.0\n\n(Note that multiplying vals by the integer 2 will not do what you think: rather\nthan multiplying each value by 2.0, it will simply double the length of the\nsequence!)\n\nTo get started with array operations, let's first import the NumPy library.\nThis is the first time we've seen an import in this orientation, so we'll\ndwell for a moment on what this means.  When a library is imported, it is read\nfrom disk, the functions are loaded into memory, and they are made available\nto the user.  So when we execute::\n\n   >>> import numpy\n\nThe ``numpy`` module is loaded, and then can be accessed::\n\n   >>> numpy.arange(10)\n\nThis calls the ``arange`` function that belongs to the ``numpy`` module's\n\"namespace.\"  We'll use the term namespace to refer to the variables,\nfunctions, and submodules that belong to a given conceptual region.  We can\nalso extend our current namespace with the contents of the ``numpy`` module, so\nthat we don't have to prefix all of our calling of ``numpy`` functions with\n``numpy.`` but we will not do so here, so as to preserve the distinction\nbetween the built-in Python functions and the NumPy-provided functions.\n\nTo get started, let's perform the NumPy version of getting a sequence of\nnumbers from 0 to 99::\n\n   >>> my_array = numpy.arange(100)\n   >>> print(my_array)\n   >>> print(my_array * 2.0)\n   >>> print( my_array * 2)\n\nAs you can see, each of these operations does exactly what we think it ought\nto.  And, in fact, so does this one::\n\n   >>> my_array *= 2.0\n\nSo far we've only examined what happens if we have operate on a single array of\na given shape -- specifically, if we have an array that is N elements long, but\nonly one dimensional.  NumPy arrays are, for the most part, defined by their\ndata, their shape, and their data type.  We can examine both the shape (which\nincludes dimensionality) and the size (strictly the total number of elements)\nin an array by looking at a couple properties of the array::\n\n   >>> print(my_array.size)\n   >>> print(my_array.shape)\n\nNote that size must be the product of the components of the shape.  In this\ncase, both are 100.  We can obtain a new array of a different shape by calling\nthe ``reshape`` method on an array::\n\n   >>> print(my_array.reshape((10, 10)))\n\nIn this case, we have not modified ``my_array`` but instead created a new array\ncontaining the same elements, but with a different dimensionality and shape.\nYou can modify an array's shape in place, as well, but that should be done with\ncare and the explanation of how that works and its caveats can come a bit\nlater.\n\nThere are a few other important characteristics of arrays, and ways to create\nthem.  We can see what kind of datatype an array is by examining its ``dtype``\nattribute::\n\n   >>> print(my_array.dtype)\n\nThis can be changed by calling ``astype`` with another datatype.  Datatypes\ninclude, but are not limited to, ``int32``, ``int64``, ``float32``,\n``float64``.::\n\n   >>> float_array = my_array.astype(\"float64\")\n\nArrays can also be operated on together, in lieu of something like an iteration\nusing the ``zip`` function.  To show this, we'll use the\n``numpy.random.random`` function to generate a random set of values of length\n100, and then we'll multiply our original array against those random values.::\n\n   >>> rand_array = numpy.random.random(100)\n   >>> print(rand_array * my_array)\n\nThere are a number of functions you can call on arrays, as well.  For\ninstance::\n\n   >>> print(rand_array.sum())\n   >>> print(rand_array.mean())\n   >>> print(rand_array.min())\n   >>> print(rand_array.max())\n\nIndexing in NumPy is very fun, and also provides some advanced functionality\nfor selecting values.  You can slice and dice arrays::\n\n   >>> print(my_array[50:60])\n   >>> print(my_array[::2])\n   >>> print(my_array[:-10])\n\nBut Numpy also provides the ability to construct boolean arrays, which are the\nresult of conditionals.  For example, let's say that you wanted to generate a\nrandom set of values, and select only those less than 0.2::\n\n   >>> rand_array = numpy.random.random(100)\n   >>> print(rand_array < 0.2)\n\nWhat is returned is a long list of booleans.  Boolean arrays can be used as\nindices -- what this means is that you can construct an index array and then\nuse that toe select only those values where that index array is true.  In this\nexample we also use the ``numpy.all`` and ``numpy.any`` functions, which do\nexactly what you might think -- they evaluate a statement and see if all\nelements satisfy it, and if any individual element satisfies it,\nrespectively.::\n\n   >>> ind_array = rand_array < 0.2\n   >>> print(rand_array[ind_array])\n   >>> print(numpy.all(rand_array[ind_array] < 0.2))\n\nYou can even skip the creation of the variable ``ind_array`` completely, and\ninstead just coalesce the statements into a single statement::\n\n   >>> print(numpy.all(rand_array[rand_array < 0.2] < 0.2))\n   >>> print(numpy.any(rand_array[rand_array < 0.2] > 0.2))\n\nYou might look at these and wonder why this is useful -- we've already selected\nthose elements that are less than 0.2, so why do we want to re-evaluate it?\nBut the interesting component to this is that a conditional applied to one\narray can be used to index another array.  For instance::\n\n   >>> print(my_array[rand_array < 0.2])\n\nHere we've identified those elements in our random number array that are less\nthan 0.2, and printed the corresponding elements from our original sequential\narray of integers.  This is actually a great way of selecting a random sample\nof a dataset -- in this case we get back approximately 20% of the dataset\n``my_array``, selected at random.\n\nTo create arrays from nothing, several options are available.  The command\n``numpy.array`` will create an array from any arbitrary sequence::\n\n   >>> my_sequence = [1.0, 510.42, 1789532.01482]\n   >>> my_array = numpy.array(my_sequence)\n\nAdditionally, arrays full of ones and zeros can be created::\n\n   >>> my_integer_ones = numpy.ones(100)\n   >>> my_float_ones = numpy.ones(100, dtype=\"float64\")\n   >>> my_integer_zeros = numpy.zeros(100)\n   >>> my_float_zeros = numpy.zeros(100, dtype=\"float64\")\n\nThe function ``numpy.concatenate`` is also useful, but outside the scope of\nthis orientation.\n\nThe NumPy documentation has a number of more advanced mechanisms for combining\narrays; the documentation for \"broadcasting\" in particular is very useful, and\ncovers mechanisms for combining arrays of different shapes and sizes, which can\nbe tricky but also extremely powerful.  We won't discuss the idea of\nbroadcasting here, simply because I don't know that I could do it justice!  The\nNumPy Docs have a great `section on broadcasting\n<https://numpy.org/doc/stable/user/basics.broadcasting.html>`_.\n\nScripted Usage\n++++++++++++++\n\nWe've now explored Python interactively.  However, for long-running analysis\ntasks or analysis tasks meant to be run on a compute cluster non-interactively,\nwe will want to utilize its scripting interface.  Let's start by quitting out\nof the interpreter.  If you have not already done so, you can quit by pressing\n\"Ctrl-D\", which will free all memory used by Python and return you to your\nshell's command prompt.\n\nAt this point, open up a text editor and edit a file called\n``my_first_script.py``.  Python scripts typically end in the extension ``.py``.\nWe'll start our scripting tests by doing some timing of array operations versus\nsequence operations.  Into this file, type this text::\n\n   import numpy\n   import time\n\n   my_array = numpy.arange(1000000, dtype=\"float64\")\n\n   t1 = time.time()\n   my_array_squared = my_array**2.0\n   t2 = time.time()\n\n   print(\"It took me %0.3e seconds to square the array using NumPy\" % (t2-t1))\n\n   t1 = time.time()\n   my_sequence_squared = []\n   for i in range(1000000):\n       my_sequence_squared.append(i**2.0)\n   t2 = time.time()\n\n   print(\"It took me %0.3e seconds to square the sequence without NumPy\" % (t2-t1))\n\nNow save this file, and return to the command prompt.  We can execute it by\nsupplying it to Python:\n\n.. code-block:: bash\n\n   $ python my_first_script.py\n\nIt should run, display two pieces of information, and terminate, leaving you\nback at the command prompt.  On my laptop, the array operation is approximately\n42 times faster than the sequence operation!  Of course, depending on the\noperation conducted, this number can go up quite substantially.\n\nIf you want to run a Python script and then be given a Python interpreter\nprompt, you can call the ``python`` command with the option ``-i``:\n\n.. code-block:: bash\n\n   $ python -i my_first_script.py\n\nPython will execute the script and when it has reached the end it will give you\na command prompt.  At this point, all of the variables you have set up and\ncreated will be available to you -- so you can, for instance, print out the\ncontents of ``my_array_squared``::\n\n   >>> print(my_array_squared)\n\nThe scripting interface for Python is quite powerful, and by combining it with\ninteractive execution, you can, for instance, set up variables and functions\nfor interactive exploration of data.\n\nFunctions and Objects\n+++++++++++++++++++++\n\nFunctions and Objects in Python are the easiest way to perform very complex,\npowerful actions in Python.  For the most part we will not discuss them; in\nfact, the standard Python tutorial that comes with the Python documentation is\na very good explanation of how to create and use objects and functions, and\nattempting to replicate it here would simply be futile.\n\nyt provides both many objects and functions for your usage, and it is through\nthe usage and combination of functions and objects that you will be able to\ncreate plots, manipulate data, and visualize your data.\n\nAnd with that, we conclude our brief introduction to Python.  I recommend\nchecking out the standard Python tutorial or browsing some of the NumPy\ndocumentation.  If you're looking for a book to buy, the only book I've\npersonally ever been completely satisfied with has been David Beazley's book on\nPython Essentials and the Python standard library, but I've also heard good\nthings about many of the others, including those by Alex Martelli and Wesley\nChun.\n\nWe'll now move on to talking more about how to use yt, both from a scripting\nperspective and interactively.\n\nPython and Related References\n+++++++++++++++++++++++++++++\n    * `Python quickstart <https://docs.python.org/3/tutorial/>`_\n    * `Learn Python the Hard Way <https://learnpythonthehardway.org/python3/>`_\n    * `Byte of Python <https://python.swaroopch.com/>`_\n    * `Dive Into Python <https://diveintopython3.problemsolving.io/>`_\n    * `Numpy docs <https://numpy.org/doc/stable/>`_\n    * `Matplotlib docs <https://matplotlib.org>`_\n"
  },
  {
    "path": "doc/source/visualizing/FITSImageData.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Writing FITS Images\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"yt has capabilities for writing 2D and 3D uniformly gridded data generated from datasets to FITS files. This is via the `FITSImageData` class. We'll test these capabilities out on an Athena dataset.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"units_override = {\\n\",\n    \"    \\\"length_unit\\\": (1.0, \\\"Mpc\\\"),\\n\",\n    \"    \\\"mass_unit\\\": (1.0e14, \\\"Msun\\\"),\\n\",\n    \"    \\\"time_unit\\\": (1.0, \\\"Myr\\\"),\\n\",\n    \"}\\n\",\n    \"ds = yt.load(\\\"MHDSloshing/virgo_low_res.0054.vtk\\\", units_override=units_override)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Creating FITS images from Slices and Projections\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"There are several ways to make a `FITSImageData` instance. The most intuitive ways are to use the `FITSSlice`, `FITSProjection`, `FITSOffAxisSlice`, and `FITSOffAxisProjection` classes to write slices and projections directly to FITS. To demonstrate a useful example of creating a FITS file, let's first make a `ProjectionPlot`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj = yt.ProjectionPlot(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    (\\\"gas\\\", \\\"temperature\\\"),\\n\",\n    \"    weight_field=(\\\"gas\\\", \\\"density\\\"),\\n\",\n    \"    width=(500.0, \\\"kpc\\\"),\\n\",\n    \")\\n\",\n    \"prj.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Suppose that we wanted to write this projection to a FITS file for analysis and visualization in other programs, such as ds9. We can do that using `FITSProjection`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits = yt.FITSProjection(\\n\",\n    \"    ds, \\\"z\\\", (\\\"gas\\\", \\\"temperature\\\"), weight_field=(\\\"gas\\\", \\\"density\\\")\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"which took the same parameters as `ProjectionPlot` except the width, because `FITSProjection` and `FITSSlice` always make slices and projections of the width of the domain size, at the finest resolution available in the simulation, in a unit determined to be appropriate for the physical size of the dataset.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also set width manually in `FITSProjection`. For example, set the width to 500 kiloparsec to get FITS file of the same projection plot as discussed above.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits = yt.FITSProjection(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    (\\\"gas\\\", \\\"temperature\\\"),\\n\",\n    \"    weight_field=(\\\"gas\\\", \\\"density\\\"),\\n\",\n    \"    width=(500.0, \\\"kpc\\\"),\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If you want the center coordinates of the image in either a slice or a projection to be (0,0) instead of the domain coordinates, set `origin=\\\"image\\\"`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits_img = yt.FITSProjection(\\n\",\n    \"    ds,\\n\",\n    \"    \\\"z\\\",\\n\",\n    \"    (\\\"gas\\\", \\\"temperature\\\"),\\n\",\n    \"    weight_field=(\\\"gas\\\", \\\"density\\\"),\\n\",\n    \"    width=(500.0, \\\"kpc\\\"),\\n\",\n    \"    origin=\\\"image\\\",\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"source\": [\n    \"## Making FITS images from Particle Projections\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"source\": [\n    \"To create a FITS image from a particle field which is smeared onto the image, we can use\\n\",\n    \"`FITSParticleProjection`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"dsp = yt.load(\\\"gizmo_64/output/snap_N64L16_135.hdf5\\\")\\n\",\n    \"prjp_fits = yt.FITSParticleProjection(\\n\",\n    \"    dsp, \\\"x\\\", (\\\"PartType1\\\", \\\"particle_mass\\\"), deposition=\\\"cic\\\"\\n\",\n    \")\\n\",\n    \"prjp_fits.writeto(\\\"prjp.fits\\\", overwrite=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"source\": [\n    \"Note that we used the \\\"Cloud-In-Cell\\\" interpolation method (`\\\"cic\\\"`) instead of the default\\n\",\n    \"\\\"Nearest-Grid-Point\\\" (`\\\"ngp\\\"`) method. \\n\",\n    \"\\n\",\n    \"If you want the projection to be divided by the pixel area (to make a projection of mass density, \\n\",\n    \"for example), supply the ``density`` keyword argument:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prjpd_fits = yt.FITSParticleProjection(\\n\",\n    \"    dsp, \\\"x\\\", (\\\"PartType1\\\", \\\"particle_mass\\\"), density=True, deposition=\\\"cic\\\"\\n\",\n    \")\\n\",\n    \"prjpd_fits.writeto(\\\"prjpd.fits\\\", overwrite=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"source\": [\n    \"`FITSParticleOffAxisProjection` can be used to make a projection along any arbitrary sight line:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"L = [1, -1, 1]  # normal or \\\"line of sight\\\" vector\\n\",\n    \"N = [0, 0, 1]  # north or \\\"up\\\" vector\\n\",\n    \"poff_fits = yt.FITSParticleOffAxisProjection(\\n\",\n    \"    dsp, L, (\\\"PartType1\\\", \\\"particle_mass\\\"), deposition=\\\"cic\\\", north_vector=N\\n\",\n    \")\\n\",\n    \"poff_fits.writeto(\\\"poff.fits\\\", overwrite=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"source\": [\n    \"## Using `HDUList` Methods\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can call a number of the [AstroPy `HDUList`](https://astropy.readthedocs.io/en/latest/io/fits/api/hdulists.html) class's methods from a `FITSImageData` object. For example, `info` shows us the contents of the virtual FITS file:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits.info()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also look at the header for a particular field:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits[\\\"temperature\\\"].header\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"where we can see that the units of the temperature field are Kelvin and the cell widths are in kiloparsecs. Note that the length, time, mass, velocity, and magnetic field units of the dataset have been copied into the header \"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \" If we want the raw image data with units, we can use the `data` attribute of this field:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits[\\\"temperature\\\"].data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Changing Aspects of the Images\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can use the `set_unit` method to change the units of a particular field:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits.set_unit(\\\"temperature\\\", \\\"R\\\")\\n\",\n    \"prj_fits[\\\"temperature\\\"].data\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The length units of the image (and its coordinate system), as well as the resolution of the image, can be adjusted when creating it using the `length_unit` and `image_res` keyword arguments, respectively:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# length_unit defaults to that from the dataset\\n\",\n    \"# image_res defaults to 512\\n\",\n    \"slc_fits = yt.FITSSlice(\\n\",\n    \"    ds, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"), width=(500, \\\"kpc\\\"), length_unit=\\\"ly\\\", image_res=256\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can now check that this worked by looking at the header, notice in particular the `NAXIS[12]` and `CUNIT[12]` keywords (the `CDELT[12]` and `CRPIX[12]` values also change):\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"slc_fits[\\\"density\\\"].header\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Saving and Loading Images\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The image can be written to disk using the `writeto` method:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits.writeto(\\\"sloshing.fits\\\", overwrite=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Since yt can read FITS image files, it can be loaded up just like any other dataset. Since we created this FITS file with `FITSImageData`, the image will contain information about the units and the current time of the dataset:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ds2 = yt.load(\\\"sloshing.fits\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"and we can make a `SlicePlot` of the 2D image, which shows the same data as the previous image:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"slc2 = yt.SlicePlot(ds2, \\\"z\\\", (\\\"gas\\\", \\\"temperature\\\"), width=(500.0, \\\"kpc\\\"))\\n\",\n    \"slc2.set_log(\\\"temperature\\\", True)\\n\",\n    \"slc2.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Creating `FITSImageData` Instances Directly from FRBs, PlotWindow instances, and 3D Grids\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If you want more fine-grained control over what goes into the FITS file, you can call `FITSImageData` directly, with various kinds of inputs. For example, you could use a `FixedResolutionBuffer`, and specify you want the units in parsecs instead:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"slc3 = ds.slice(0, 0.0)\\n\",\n    \"frb = slc3.to_frb((500.0, \\\"kpc\\\"), 800)\\n\",\n    \"fid_frb = frb.to_fits_data(\\n\",\n    \"    fields=[(\\\"gas\\\", \\\"density\\\"), (\\\"gas\\\", \\\"temperature\\\")], length_unit=\\\"pc\\\"\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"If one creates a `PlotWindow` instance, e.g. `SlicePlot`, `ProjectionPlot`, etc., you can also call this same method there:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fid_pw = prj.to_fits_data(\\n\",\n    \"    fields=[(\\\"gas\\\", \\\"density\\\"), (\\\"gas\\\", \\\"temperature\\\")], length_unit=\\\"pc\\\"\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"A 3D FITS cube can also be created from regularly gridded 3D data. In yt, there are covering grids and \\\"arbitrary grids\\\". The easiest way to make an arbitrary grid object is using `ds.r`, where we can index the dataset like a NumPy array, creating a grid of 1.0 Mpc on a side, centered on the origin, with 64 cells on a side:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"grid = ds.r[\\n\",\n    \"    (-0.5, \\\"Mpc\\\"):(0.5, \\\"Mpc\\\"):64j,\\n\",\n    \"    (-0.5, \\\"Mpc\\\"):(0.5, \\\"Mpc\\\"):64j,\\n\",\n    \"    (-0.5, \\\"Mpc\\\"):(0.5, \\\"Mpc\\\"):64j,\\n\",\n    \"]\\n\",\n    \"fid_grid = grid.to_fits_data(\\n\",\n    \"    fields=[(\\\"gas\\\", \\\"density\\\"), (\\\"gas\\\", \\\"temperature\\\")], length_unit=\\\"Mpc\\\"\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Other `FITSImageData` Methods\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Creating Images from Others\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"A `FITSImageData` instance can be generated from one previously written to disk using the `from_file` classmethod:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fid = yt.FITSImageData.from_file(\\\"sloshing.fits\\\")\\n\",\n    \"fid.info()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Multiple `FITSImageData` can be combined to create a new one, provided that the coordinate information is the same:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits2 = yt.FITSProjection(ds, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"), width=(500.0, \\\"kpc\\\"))\\n\",\n    \"prj_fits3 = yt.FITSImageData.from_images([prj_fits, prj_fits2])\\n\",\n    \"prj_fits3.info()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Alternatively, individual fields can be popped as well to produce new instances of `FITSImageData`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"dens_fits = prj_fits3.pop(\\\"density\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"So this new instance would only have the `\\\"density\\\"` field:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"dens_fits.info()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"and the old one has the `\\\"density\\\"` field removed:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits3.info()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Adding Sky Coordinates to Images\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"So far, the FITS images we have shown have linear spatial coordinates. We can see this by looking at the header for one of the fields, and examining the `CTYPE1` and `CTYPE2` keywords:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits[\\\"temperature\\\"].header\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The `WCSNAME` keyword is set to `\\\"yt\\\"` by default. \\n\",\n    \"\\n\",\n    \"However, one may want to take a projection of an object and make a crude mock observation out of it, with celestial coordinates. For this, we can use the `create_sky_wcs` method. Specify a center (RA, Dec) coordinate in degrees, as well as a linear scale in terms of angle per distance:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sky_center = [30.0, 45.0]  # in degrees\\n\",\n    \"sky_scale = (2.5, \\\"arcsec/kpc\\\")  # could also use a YTQuantity\\n\",\n    \"prj_fits.create_sky_wcs(sky_center, sky_scale, ctype=[\\\"RA---TAN\\\", \\\"DEC--TAN\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"By default, a tangent RA/Dec projection is used, but one could also use another projection using the `ctype` keyword. We can now look at the header and see it has the appropriate WCS now. The old `\\\"yt\\\"` WCS has been added to a second WCS in the header, where the parameters have an `\\\"A\\\"` appended to them:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits[\\\"temperature\\\"].header\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"and now the `WCSNAME` has been set to `\\\"celestial\\\"`. If you want the original WCS to remain in the original place, then you can make the call to `create_sky_wcs` and set `replace_old_wcs=False`, which will put the new, celestial WCS in the second one:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true,\n    \"jupyter\": {\n     \"outputs_hidden\": true\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits3.create_sky_wcs(\\n\",\n    \"    sky_center, sky_scale, ctype=[\\\"RA---TAN\\\", \\\"DEC--TAN\\\"], replace_old_wcs=False\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"prj_fits3[\\\"temperature\\\"].header\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Updating Header Parameters\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can also add header keywords to a single field or for all fields in the FITS image using `update_header`:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fid_frb.update_header(\\\"all\\\", \\\"time\\\", 0.1)  # Update all the fields\\n\",\n    \"fid_frb.update_header(\\\"temperature\\\", \\\"scale\\\", \\\"Rankine\\\")  # Update just one field\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(fid_frb[\\\"density\\\"].header[\\\"time\\\"])\\n\",\n    \"print(fid_frb[\\\"temperature\\\"].header[\\\"scale\\\"])\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Changing Image Names\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"You can use the `change_image_name` method to change the name of an image in a `FITSImageData` instance:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"fid_frb.change_image_name(\\\"density\\\", \\\"mass_per_volume\\\")\\n\",\n    \"fid_frb.info()  # now \\\"density\\\" should be gone and \\\"mass_per_volume\\\" should be in its place\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Convolving FITS Images\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Finally, you can convolve an image inside a `FITSImageData` instance with a kernel, either a Gaussian with a specific standard deviation, or any kernel provided by AstroPy. See AstroPy's [Convolution and filtering](http://docs.astropy.org/en/stable/convolution/index.html) for more details.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"dens_fits.writeto(\\\"not_convolved.fits\\\", overwrite=True)\\n\",\n    \"# Gaussian kernel with standard deviation of 3.0 kpc\\n\",\n    \"dens_fits.convolve(\\\"density\\\", 3.0)\\n\",\n    \"dens_fits.writeto(\\\"convolved.fits\\\", overwrite=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now let's load these up as datasets and see the difference:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"ds0 = yt.load(\\\"not_convolved.fits\\\")\\n\",\n    \"dsc = yt.load(\\\"convolved.fits\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"slc3 = yt.SlicePlot(ds0, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"), width=(500.0, \\\"kpc\\\"))\\n\",\n    \"slc3.set_log(\\\"density\\\", True)\\n\",\n    \"slc3.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"slc4 = yt.SlicePlot(dsc, \\\"z\\\", (\\\"gas\\\", \\\"density\\\"), width=(500.0, \\\"kpc\\\"))\\n\",\n    \"slc4.set_log(\\\"density\\\", True)\\n\",\n    \"slc4.show()\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"anaconda-cloud\": {},\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.12\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/visualizing/TransferFunctionHelper_Tutorial.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Transfer Function Helper Tutorial\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Here, we explain how to use TransferFunctionHelper to visualize and interpret yt volume rendering transfer functions.  Creating a custom transfer function is a process that usually involves some trial-and-error. TransferFunctionHelper is a utility class designed to help you visualize the probability density functions of yt fields that you might want to volume render.  This makes it easier to choose a nice transfer function that highlights interesting physical regimes.\\n\",\n    \"\\n\",\n    \"First, we set up our namespace and define a convenience function to display volume renderings inline in the notebook.  Using `%matplotlib inline` makes it so matplotlib plots display inline in the notebook.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    },\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"from IPython.core.display import Image\\n\",\n    \"\\n\",\n    \"import yt\\n\",\n    \"from yt.visualization.volume_rendering.transfer_function_helper import (\\n\",\n    \"    TransferFunctionHelper,\\n\",\n    \")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Next, we load up a low resolution Enzo cosmological simulation.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    },\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ds = yt.load(\\\"Enzo_64/DD0043/data0043\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now that we have the dataset loaded, let's create a `TransferFunctionHelper` to visualize the dataset and transfer function we'd like to use.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    },\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"tfh = TransferFunctionHelper(ds)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"`TransferFunctionHelpler` will intelligently choose transfer function bounds based on the data values.  Use the `plot()` method to take a look at the transfer function.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    },\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"# Build a transfer function that is a multivariate gaussian in temperature\\n\",\n    \"tfh = TransferFunctionHelper(ds)\\n\",\n    \"tfh.set_field((\\\"gas\\\", \\\"temperature\\\"))\\n\",\n    \"tfh.set_log(True)\\n\",\n    \"tfh.set_bounds()\\n\",\n    \"tfh.build_transfer_function()\\n\",\n    \"tfh.tf.add_layers(5)\\n\",\n    \"tfh.plot()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's also look at the probability density function of the `mass` field as a function of `temperature`.  This might give us an idea where there is a lot of structure. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    },\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"tfh.plot(profile_field=(\\\"gas\\\", \\\"mass\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"It looks like most of the gas is hot but there is still a lot of low-density cool gas.  Let's construct a transfer function that highlights both the rarefied hot gas and the dense cool gas simultaneously.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    },\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"tfh = TransferFunctionHelper(ds)\\n\",\n    \"tfh.set_field((\\\"gas\\\", \\\"temperature\\\"))\\n\",\n    \"tfh.set_bounds()\\n\",\n    \"tfh.set_log(True)\\n\",\n    \"tfh.build_transfer_function()\\n\",\n    \"tfh.tf.map_to_colormap(6.0, 8.0, colormap=\\\"Reds\\\")\\n\",\n    \"tfh.tf.map_to_colormap(-1.0, 6.0, colormap=\\\"Blues_r\\\")\\n\",\n    \"\\n\",\n    \"tfh.plot(profile_field=(\\\"gas\\\", \\\"mass\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's take a look at the volume rendering. First use the helper function to create a default rendering, then we override this with the transfer function we just created.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"im, sc = yt.volume_render(ds, [(\\\"gas\\\", \\\"temperature\\\")])\\n\",\n    \"\\n\",\n    \"source = sc.get_source()\\n\",\n    \"source.set_transfer_function(tfh.tf)\\n\",\n    \"sc.render()\\n\",\n    \"sc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"That looks okay, but the red gas (associated with temperatures between 1e6 and 1e8 K) is a bit hard to see in the image. To fix this, we can make that gas contribute a larger alpha value to the image by using the ``scale`` keyword argument in ``map_to_colormap``.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    },\n    \"tags\": []\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"tfh2 = TransferFunctionHelper(ds)\\n\",\n    \"tfh2.set_field((\\\"gas\\\", \\\"temperature\\\"))\\n\",\n    \"tfh2.set_bounds()\\n\",\n    \"tfh2.set_log(True)\\n\",\n    \"tfh2.build_transfer_function()\\n\",\n    \"tfh2.tf.map_to_colormap(6.0, 8.0, colormap=\\\"Reds\\\", scale=5.0)\\n\",\n    \"tfh2.tf.map_to_colormap(-1.0, 6.0, colormap=\\\"Blues_r\\\", scale=1.0)\\n\",\n    \"\\n\",\n    \"tfh2.plot(profile_field=(\\\"gas\\\", \\\"mass\\\"))\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that the height of the red portion of the transfer function has increased by a factor of 5.0. If we use this transfer function to make the final image:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"jupyter\": {\n     \"outputs_hidden\": false\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"source.set_transfer_function(tfh2.tf)\\n\",\n    \"sc.render()\\n\",\n    \"sc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The red gas is now much more prominent in the image. We can clearly see that the hot gas is mostly associated with bound structures while the cool gas is associated with low-density voids.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.11\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/visualizing/Volume_Rendering_Tutorial.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Volume Rendering Tutorial \\n\",\n    \"\\n\",\n    \"This notebook shows how to use the new (in version 3.3) Scene interface to create custom volume renderings. The tutorial proceeds in the following steps: \\n\",\n    \"\\n\",\n    \"1. [Creating the Scene](#1.-Creating-the-Scene)\\n\",\n    \"2. [Displaying the Scene](#2.-Displaying-the-Scene)\\n\",\n    \"3. [Adjusting Transfer Functions](#3.-Adjusting-Transfer-Functions)\\n\",\n    \"4. [Saving an Image](#4.-Saving-an-Image)\\n\",\n    \"5. [Adding Annotations](#5.-Adding-Annotations)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 1. Creating the Scene \\n\",\n    \"\\n\",\n    \"To begin, we load up a dataset and use the `yt.create_scene` method to set up a basic Scene. We store the Scene in a variable called `sc` and render the default `('gas', 'density')` field.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"import yt\\n\",\n    \"from yt.visualization.volume_rendering.transfer_function_helper import (\\n\",\n    \"    TransferFunctionHelper,\\n\",\n    \")\\n\",\n    \"\\n\",\n    \"ds = yt.load(\\\"IsolatedGalaxy/galaxy0030/galaxy0030\\\")\\n\",\n    \"sc = yt.create_scene(ds)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Note that to render a different field, we would use pass the field name to `yt.create_scene` using the `field` argument. \\n\",\n    \"\\n\",\n    \"Now we can look at some information about the Scene we just created using the python print keyword:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(sc)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This prints out information about the Sources, Camera, and Lens associated with this Scene. Each of these can also be printed individually. For example, to print only the information about the first (and currently, only) Source, we can do:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(sc.get_source())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 2. Displaying the Scene \\n\",\n    \"\\n\",\n    \"We can see that the `yt.create_source` method has created a `VolumeSource` with default values for the center, bounds, and transfer function. Now, let's see what this Scene looks like. In the notebook, we can do this by calling `sc.show()`. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"That looks okay, but it's a little too zoomed-out. To fix this, let's modify the Camera associated with our Scene. This next bit of code will zoom in the camera (i.e. decrease the width of the view) by a factor of 3.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.camera.zoom(3.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now when we print the Scene, we see that the Camera width has decreased by a factor of 3:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(sc)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"To see what this looks like, we re-render the image and display the scene again. Note that we don't actually have to call `sc.show()` here - we can just have Ipython evaluate the Scene and that will display it automatically.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.render()\\n\",\n    \"sc\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"That's better! The image looks a little washed-out though, so we use the `sigma_clip` argument to `sc.show()` to improve the contrast:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.show(sigma_clip=4.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Applying different values of `sigma_clip` with `sc.show()` is a relatively fast process because `sc.show()` will pull the most recently rendered image and apply the contrast adjustment without rendering the scene again. While this is useful for quickly testing the affect of different values of `sigma_clip`, it can lead to confusion if we don't remember to render after making changes to the camera. For example, if we zoom in again and simply call `sc.show()`, then we get the same image as before:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.camera.zoom(3.0)\\n\",\n    \"sc.show(sigma_clip=4.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For the change to the camera to take affect, we have to explicitly render again: \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.render()\\n\",\n    \"sc.show(sigma_clip=4.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"As a general rule, any changes to the scene itself such as adjusting the camera or changing transfer functions requires rendering again. Before moving on, let's undo the last zoom:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.camera.zoom(1.0 / 3.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 3. Adjusting Transfer Functions\\n\",\n    \"\\n\",\n    \"Next, we demonstrate how to change the mapping between the field values and the colors in the image. We use the TransferFunctionHelper to create a new transfer function using the `gist_rainbow` colormap, and then re-create the image as follows:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Set up a custom transfer function using the TransferFunctionHelper.\\n\",\n    \"# We use 10 Gaussians evenly spaced logarithmically between the min and max\\n\",\n    \"# field values.\\n\",\n    \"tfh = TransferFunctionHelper(ds)\\n\",\n    \"tfh.set_field(\\\"density\\\")\\n\",\n    \"tfh.set_log(True)\\n\",\n    \"tfh.set_bounds()\\n\",\n    \"tfh.build_transfer_function()\\n\",\n    \"tfh.tf.add_layers(10, colormap=\\\"gist_rainbow\\\")\\n\",\n    \"\\n\",\n    \"# Grab the first render source and set it to use the new transfer function\\n\",\n    \"render_source = sc.get_source()\\n\",\n    \"render_source.transfer_function = tfh.tf\\n\",\n    \"\\n\",\n    \"sc.render()\\n\",\n    \"sc.show(sigma_clip=4.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now, let's try using a different lens type. We can give a sense of depth to the image by using the perspective lens. To do, we create a new Camera below. We also demonstrate how to switch the camera to a new position and orientation.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"cam = sc.add_camera(ds, lens_type=\\\"perspective\\\")\\n\",\n    \"\\n\",\n    \"# Standing at (x=0.05, y=0.5, z=0.5), we look at the area of x>0.05 (with some open angle\\n\",\n    \"# specified by camera width) along the positive x direction.\\n\",\n    \"cam.position = ds.arr([0.05, 0.5, 0.5], \\\"code_length\\\")\\n\",\n    \"\\n\",\n    \"normal_vector = [1.0, 0.0, 0.0]\\n\",\n    \"north_vector = [0.0, 0.0, 1.0]\\n\",\n    \"cam.switch_orientation(normal_vector=normal_vector, north_vector=north_vector)\\n\",\n    \"\\n\",\n    \"# The width determines the opening angle\\n\",\n    \"cam.set_width(ds.domain_width * 0.5)\\n\",\n    \"\\n\",\n    \"print(sc.camera)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"The resulting image looks like:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.render()\\n\",\n    \"sc.show(sigma_clip=4.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 4. Saving an Image\\n\",\n    \"\\n\",\n    \"To save a volume rendering to an image file at any point, we can use `sc.save` as follows:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.save(\\\"volume_render.png\\\", render=False)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Including the keyword argument `render=False` indicates that the most recently rendered image will be saved (otherwise, `sc.save()` will trigger a call to `sc.render()`). This behavior differs from `sc.show()`, which always uses the most recently rendered image. \\n\",\n    \"\\n\",\n    \"An additional caveat is that if we used `sigma_clip` in our call to `sc.show()`, then we must **also** pass it to `sc.save()` as sigma clipping is applied on top of a rendered image array. In that case, we would do the following: \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"sc.save(\\\"volume_render_clip4.png\\\", sigma_clip=4.0, render=False)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 5. Adding Annotations\\n\",\n    \"\\n\",\n    \"Finally, the next cell restores the lens and the transfer function to the defaults, moves the camera, and adds an opaque source  that shows the axes of the simulation coordinate system.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# set the lens type back to plane-parallel\\n\",\n    \"sc.camera.set_lens(\\\"plane-parallel\\\")\\n\",\n    \"\\n\",\n    \"# move the camera to the left edge of the domain\\n\",\n    \"sc.camera.set_position(ds.domain_left_edge)\\n\",\n    \"sc.camera.switch_orientation()\\n\",\n    \"\\n\",\n    \"# add an opaque source to the scene\\n\",\n    \"sc.annotate_axes()\\n\",\n    \"\\n\",\n    \"sc.render()\\n\",\n    \"sc.show(sigma_clip=4.0)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.10.12\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "doc/source/visualizing/callbacks.rst",
    "content": ".. _callbacks:\n\nPlot Modifications: Overplotting Contours, Velocities, Particles, and More\n==========================================================================\n\nAdding callbacks to plots\n-------------------------\n\nAfter a plot is generated using the standard tools (e.g. SlicePlot,\nProjectionPlot, etc.), it can be annotated with any number of ``callbacks``\nbefore being saved to disk.  These callbacks can modify the plots by adding\nlines, text, markers, streamlines, velocity vectors, contours, and more.\n\nCallbacks can be applied to plots created with\n:class:`~yt.visualization.plot_window.SlicePlot`,\n:class:`~yt.visualization.plot_window.ProjectionPlot`,\n:class:`~yt.visualization.plot_window.AxisAlignedSlicePlot`,\n:class:`~yt.visualization.plot_window.AxisAlignedProjectionPlot`,\n:class:`~yt.visualization.plot_window.OffAxisSlicePlot`, or\n:class:`~yt.visualization.plot_window.OffAxisProjectionPlot`, by calling\none of the ``annotate_`` methods that hang off of the plot object.\nThe ``annotate_`` methods are dynamically generated based on the list\nof available callbacks.  For example:\n\n.. code-block:: python\n\n   slc = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n   slc.annotate_title(\"This is a Density plot\")\n\nwould add the :func:`~yt.visualization.plot_modifications.TitleCallback` to\nthe plot object.  All of the callbacks listed below are available via\nsimilar ``annotate_`` functions.\n\nTo clear one or more annotations from an existing plot, see the\n:ref:`clear_annotations function <clear-annotations>`.\n\nFor a brief demonstration of a few of these callbacks in action together,\nsee the cookbook recipe: :ref:`annotations-recipe`.\n\nAlso note that new ``annotate_`` methods can be defined without modifying yt's\nsource code, see :ref:`extend-annotations`.\n\n\nCoordinate Systems in Callbacks\n-------------------------------\n\nMany of the callbacks (e.g.\n:class:`~yt.visualization.plot_modifications.TextLabelCallback`) are specified\nto occur at user-defined coordinate locations (like where to place a marker\nor text on the plot).  There are several different coordinate systems used\nto identify these locations.  These coordinate systems can be specified with\nthe ``coord_system`` keyword in the relevant callback, which is by default\nset to ``data``.  The valid coordinate systems are:\n\n    ``data`` – the 3D dataset coordinates\n\n    ``plot`` – the 2D coordinates defined by the actual plot limits\n\n    ``axis`` – the MPL axis coordinates: (0,0) is lower left; (1,1) is upper right\n\n    ``figure`` – the MPL figure coordinates: (0,0) is lower left, (1,1) is upper right\n\nHere we will demonstrate these different coordinate systems for an projection\nof the x-plane (i.e. with axes in the y and z directions):\n\n.. python-script::\n\n    import yt\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    s = yt.SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n    s.set_axes_unit(\"kpc\")\n\n    # Plot marker and text in data coords\n    s.annotate_marker((0.2, 0.5, 0.9), coord_system=\"data\")\n    s.annotate_text((0.2, 0.5, 0.9), \"data: (0.2, 0.5, 0.9)\", coord_system=\"data\")\n\n    # Plot marker and text in plot coords\n    s.annotate_marker((200, -300), coord_system=\"plot\")\n    s.annotate_text((200, -300), \"plot: (200, -300)\", coord_system=\"plot\")\n\n    # Plot marker and text in axis coords\n    s.annotate_marker((0.1, 0.2), coord_system=\"axis\")\n    s.annotate_text((0.1, 0.2), \"axis: (0.1, 0.2)\", coord_system=\"axis\")\n\n    # Plot marker and text in figure coords\n    # N.B. marker will not render outside of axis bounds\n    s.annotate_marker((0.1, 0.2), coord_system=\"figure\", color=\"black\")\n    s.annotate_text(\n        (0.1, 0.2),\n        \"figure: (0.1, 0.2)\",\n        coord_system=\"figure\",\n        text_args={\"color\": \"black\"},\n    )\n    s.save()\n\nNote that for non-cartesian geometries and ``coord_system=\"data\"``, the coordinates\nare still interpreted in the corresponding cartesian system. For instance using a polar\ndataset from AMRVAC :\n\n.. python-script::\n\n    import yt\n\n    ds = yt.load(\"amrvac/bw_polar_2D0000.dat\")\n    s = yt.plot_2d(ds, (\"gas\", \"density\"))\n    s.set_background_color(\"density\", \"black\")\n\n    # Plot marker and text in data coords\n    s.annotate_marker((0.2, 0.5, 0.9), coord_system=\"data\")\n    s.annotate_text((0.2, 0.5, 0.9), \"data: (0.2, 0.5, 0.9)\", coord_system=\"data\")\n\n    # Plot marker and text in plot coords\n    s.annotate_marker((0.4, -0.5), coord_system=\"plot\")\n    s.annotate_text((0.4, -0.5), \"plot: (0.4, -0.5)\", coord_system=\"plot\")\n\n    # Plot marker and text in axis coords\n    s.annotate_marker((0.1, 0.2), coord_system=\"axis\")\n    s.annotate_text((0.1, 0.2), \"axis: (0.1, 0.2)\", coord_system=\"axis\")\n\n    # Plot marker and text in figure coords\n    # N.B. marker will not render outside of axis bounds\n    s.annotate_marker((0.6, 0.2), coord_system=\"figure\")\n    s.annotate_text((0.6, 0.2), \"figure: (0.6, 0.2)\", coord_system=\"figure\")\n    s.save()\n\nAvailable Callbacks\n-------------------\n\nThe underlying functions are more thoroughly documented in :ref:`callback-api`.\n\n.. _clear-annotations:\n\nClear Callbacks (Some or All)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: clear_annotations(index=None)\n\n    This function will clear previous annotations (callbacks) in the plot.\n    If no index is provided, it will clear all annotations to the plot.\n    If an index is provided, it will clear only the Nth annotation\n    to the plot.  Note that the index goes from 0..N, and you can\n    specify the index of the last added annotation as -1.\n\n    (This is a proxy for\n    :func:`~yt.visualization.plot_window.clear_annotations`.)\n\n.. python-script::\n\n    import yt\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    p = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n    p.annotate_scale()\n    p.annotate_timestamp()\n\n    # Oops, I didn't want any of that.\n    p.clear_annotations()\n    p.save()\n\n.. _annotate-list:\n\nList Currently Applied Callbacks\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: list_annotations()\n\n   This function will print a list of each of the currently applied\n   callbacks together with their index.  The index can be used with\n   :ref:`clear_annotations() function <clear-annotations>` to remove a\n   specific callback.\n\n   (This is a proxy for\n   :func:`~yt.visualization.plot_window.list_annotations`.)\n\n.. python-script::\n\n    import yt\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    p = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n    p.annotate_scale()\n    p.annotate_timestamp()\n    p.list_annotations()\n\n.. _annotate-arrow:\n\nOverplot Arrow\n~~~~~~~~~~~~~~\n\n.. function:: annotate_arrow(self, pos, length=0.03, coord_system='data', **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.ArrowCallback`.)\n\n    Overplot an arrow pointing at a position for highlighting a specific\n    feature.  Arrow points from lower left to the designated position with\n    arrow length \"length\".\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"), center=\"c\")\n   slc.annotate_arrow((0.5, 0.5, 0.5), length=0.06, color=\"blue\")\n   slc.save()\n\n.. _annotate-clumps:\n\nClump Finder Callback\n~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_clumps(self, clumps, **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.ClumpContourCallback`.)\n\n   Take a list of ``clumps`` and plot them as a set of\n   contours.\n\n.. python-script::\n\n   import numpy as np\n\n   import yt\n   from yt.data_objects.level_sets.api import Clump, find_clumps\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   data_source = ds.disk([0.5, 0.5, 0.5], [0.0, 0.0, 1.0], (8.0, \"kpc\"), (1.0, \"kpc\"))\n\n   c_min = 10 ** np.floor(np.log10(data_source[\"gas\", \"density\"]).min())\n   c_max = 10 ** np.floor(np.log10(data_source[\"gas\", \"density\"]).max() + 1)\n\n   master_clump = Clump(data_source, (\"gas\", \"density\"))\n   master_clump.add_validator(\"min_cells\", 20)\n\n   find_clumps(master_clump, c_min, c_max, 2.0)\n   leaf_clumps = master_clump.leaves\n\n   prj = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n   prj.annotate_clumps(leaf_clumps)\n   prj.save(\"clumps\")\n\n.. _annotate-contours:\n\nOverplot Contours\n~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_contour(self, field, levels=5, factor=4, take_log=False,\\\n                               clim=None, plot_args=None, label=False, \\\n                               text_args=None, data_source=None)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.ContourCallback`.)\n\n   Add contours in ``field`` to the plot.  ``levels`` governs the number of\n   contours generated, ``factor`` governs the number of points used in the\n   interpolation, ``take_log`` governs how it is contoured and ``clim`` gives\n   the (lower, upper) limits for contouring.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n   s = yt.SlicePlot(ds, \"x\", (\"gas\", \"density\"), center=\"max\")\n   s.annotate_contour((\"gas\", \"temperature\"))\n   s.save()\n\n.. _annotate-quivers:\n\nOverplot Quivers\n~~~~~~~~~~~~~~~~\n\nAxis-Aligned Data Sources\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. function:: annotate_quiver(self, field_x, field_y, field_c=None, *, factor=16, scale=None, \\\n                              scale_units=None, normalize=False, **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.QuiverCallback`.)\n\n   Adds a 'quiver' plot to any plot, using the ``field_x`` and ``field_y`` from\n   the associated data, skipping every ``factor`` pixels in the\n   discretization. A third field, ``field_c``, can be used as color; which is the\n   counterpart of ``matplotlib.axes.Axes.quiver``'s final positional argument ``C``.\n   ``scale`` is the data units per arrow length unit using\n   ``scale_units``. If ``normalize`` is ``True``, the fields will be scaled by\n   their local (in-plane) length, allowing morphological features to be more\n   clearly seen for fields with substantial variation in field strength.\n   All additional keyword arguments are passed down to ``matplotlib.Axes.axes.quiver``.\n\n   Example using a constant color\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ProjectionPlot(\n       ds,\n       \"z\",\n       (\"gas\", \"density\"),\n       center=[0.5, 0.5, 0.5],\n       weight_field=\"density\",\n       width=(20, \"kpc\"),\n   )\n   p.annotate_quiver(\n      (\"gas\", \"velocity_x\"),\n      (\"gas\", \"velocity_y\"),\n      factor=16,\n      color=\"purple\",\n   )\n   p.save()\n\n\nAnd now using a continuous colormap\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ProjectionPlot(\n       ds,\n       \"z\",\n       (\"gas\", \"density\"),\n       center=[0.5, 0.5, 0.5],\n       weight_field=\"density\",\n       width=(20, \"kpc\"),\n   )\n   p.annotate_quiver(\n      (\"gas\", \"velocity_x\"),\n      (\"gas\", \"velocity_y\"),\n      (\"gas\", \"vorticity_z\"),\n      factor=16,\n      cmap=\"inferno_r\",\n   )\n   p.save()\n\n\nOff-Axis Data Sources\n^^^^^^^^^^^^^^^^^^^^^\n\n.. function:: annotate_cquiver(self, field_x, field_y, field_c=None, *, factor=16, scale=None, \\\n                               scale_units=None, normalize=False, **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.CuttingQuiverCallback`.)\n\n   Get a quiver plot on top of a cutting plane, using the ``field_x`` and\n   ``field_y`` from the associated data, skipping every ``factor`` datapoints in\n   the discretization. ``scale`` is the data units per arrow length unit using\n   ``scale_units``. If ``normalize`` is ``True``, the fields will be scaled by\n   their local (in-plane) length, allowing morphological features to be more\n   clearly seen for fields with substantial variation in field strength.\n   Additional arguments can be passed to the ``plot_args`` dictionary, see\n   matplotlib.axes.Axes.quiver for more info.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n   s = yt.OffAxisSlicePlot(ds, [1, 1, 0], [(\"gas\", \"density\")], center=\"c\")\n   s.annotate_cquiver(\n       (\"gas\", \"cutting_plane_velocity_x\"),\n       (\"gas\", \"cutting_plane_velocity_y\"),\n       factor=10,\n       color=\"orange\",\n   )\n   s.zoom(1.5)\n   s.save()\n\n.. _annotate-grids:\n\nOverplot Grids\n~~~~~~~~~~~~~~\n\n.. function:: annotate_grids(self, alpha=0.7, min_pix=1, min_pix_ids=20, \\\n                             draw_ids=False, id_loc=\"lower left\", \\\n                             periodic=True, min_level=None, \\\n                             max_level=None, cmap='B-W Linear_r', \\\n                             edgecolors=None, linewidth=1.0)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.GridBoundaryCallback`.)\n\n   Adds grid boundaries to a plot, optionally with alpha-blending via the\n   ``alpha`` keyword. Cuttoff for display is at ``min_pix`` wide. ``draw_ids``\n   puts the grid id in the ``id_loc`` corner of the grid. (``id_loc`` can be\n   upper/lower left/right. ``draw_ids`` is not so great in projections...)\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"), center=\"max\")\n   slc.annotate_grids()\n   slc.save()\n\n.. _annotate-cell-edges:\n\nOverplot Cell Edges\n~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_cell_edges(line_width=0.002, alpha=1.0, color='black')\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.CellEdgesCallback`.)\n\n    Annotate the edges of cells, where the ``line_width`` relative to size of\n    the longest plot axis is specified.  The ``alpha`` of the overlaid image and\n    the ``color`` of the lines are also specifiable.  Note that because the\n    lines are drawn from both sides of a cell, the image sometimes has the\n    effect of doubling the line width.  Color here is a matplotlib color name or\n    a 3-tuple of RGB float values.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"), center=\"max\")\n   slc.annotate_cell_edges()\n   slc.save()\n\n\n.. _annotate-image-line:\n\nOverplot a Straight Line\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_line(self, p1, p2, *, coord_system='data', **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.LinePlotCallback`.)\n\n    Overplot a line with endpoints at p1 and p2.  p1 and p2\n    should be 2D or 3D coordinates consistent with the coordinate\n    system denoted in the \"coord_system\" keyword.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), center=\"m\", width=(10, \"kpc\"))\n   p.annotate_line((0.3, 0.4), (0.8, 0.9), coord_system=\"axis\")\n   p.save()\n\n.. _annotate-magnetic-field:\n\nOverplot Magnetic Field Quivers\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_magnetic_field(self, factor=16, *, scale=None, \\\n                                      scale_units=None, normalize=False, \\\n                                      **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.MagFieldCallback`.)\n\n   Adds a 'quiver' plot of magnetic field to the plot, skipping every ``factor``\n   datapoints in the discretization. ``scale`` is the data units per arrow\n   length unit using ``scale_units``. If ``normalize`` is ``True``, the\n   magnetic fields will be scaled by their local (in-plane) length, allowing\n   morphological features to be more clearly seen for fields with substantial\n   variation in field strength. Additional arguments can be passed to the\n   ``plot_args`` dictionary, see matplotlib.axes.Axes.quiver for more info.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\n       \"MHDSloshing/virgo_low_res.0054.vtk\",\n       units_override={\n           \"time_unit\": (1, \"Myr\"),\n           \"length_unit\": (1, \"Mpc\"),\n           \"mass_unit\": (1e17, \"Msun\"),\n       },\n   )\n   p = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(300, \"kpc\"))\n   p.annotate_magnetic_field(headlength=3)\n   p.save()\n\n.. _annotate-marker:\n\nAnnotate a Point With a Marker\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_marker(self, pos, marker='x', *, coord_system='data', **kwargs)\n\n    (This is a proxy for\n    :class:`~yt.visualization.plot_modifications.MarkerAnnotateCallback`.)\n\n    Overplot a marker on a position for highlighting specific features.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   s = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(10, \"kpc\"))\n   s.annotate_marker((-2, -2), coord_system=\"plot\", color=\"blue\", s=500)\n   s.save()\n\n.. _annotate-particles:\n\nOverplotting Particle Positions\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_particles(self, width, p_size=1.0, col='k', marker='o',\\\n                                 stride=1, ptype='all', alpha=1.0, data_source=None)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.ParticleCallback`.)\n\n   Adds particle positions, based on a thick slab along ``axis`` with a\n   ``width`` along the line of sight.  ``p_size`` controls the number of pixels\n   per particle, and ``col`` governs the color.  ``ptype`` will restrict plotted\n   particles to only those that are of a given type.  ``data_source`` will only\n   plot particles contained within the data_source object.\n\n   WARNING: if ``data_source`` is a :class:`yt.data_objects.selection_data_containers.YTCutRegion`\n   then the ``width`` parameter is ignored.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n   p = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\"), center=\"m\", width=(10, \"Mpc\"))\n   p.annotate_particles((10, \"Mpc\"))\n   p.save()\n\nTo plot only the central particles\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n   p = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\"), center=\"m\", width=(10, \"Mpc\"))\n   sp = ds.sphere(p.data_source.center, ds.quan(1, \"Mpc\"))\n   p.annotate_particles((10, \"Mpc\"), data_source=sp)\n   p.save()\n\n.. _annotate-sphere:\n\nOverplot a Circle on a Plot\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_sphere(self, center, radius, circle_args=None, \\\n                              coord_system='data', text=None, text_args=None)\n\n    (This is a proxy for\n    :class:`~yt.visualization.plot_modifications.SphereCallback`.)\n\n    Overplot a circle with designated center and radius with optional text.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n   p.annotate_sphere([0.5, 0.5, 0.5], radius=(2, \"kpc\"), circle_args={\"color\": \"black\"})\n   p.save()\n\n.. _annotate-streamlines:\n\nOverplot Streamlines\n~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_streamlines(self, field_x, field_y, *, linewidth=1.0, linewidth_upscaling=1.0, \\\n                                   color=None, color_threshold=float('-inf'), factor=16, **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.StreamlineCallback`.)\n\n   Add streamlines to any plot, using the ``field_x`` and ``field_y`` from the\n   associated data, using ``nx`` and ``ny`` starting points that are bounded by\n   ``xstart`` and ``ystart``.  To begin streamlines from the left edge of the\n   plot, set ``start_at_xedge`` to ``True``; for the bottom edge, use\n   ``start_at_yedge``.  A line with the qmean vector magnitude will cover\n   1.0/``factor`` of the image.\n\n   Additional keyword arguments are passed down to\n   `matplotlib.axes.Axes.streamplot <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.streamplot.html>`_\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   s = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n   s.annotate_streamlines((\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"))\n   s.save()\n\n.. _annotate-line-integral-convolution:\n\nOverplot Line Integral Convolution\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_line_integral_convolution(self, field_x, field_y, \\\n                                                 texture=None, kernellen=50., \\\n                                                 lim=(0.5,0.6), cmap='binary', \\\n                                                 alpha=0.8, const_alpha=False)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.LineIntegralConvolutionCallback`.)\n\n   Add line integral convolution to any plot, using the ``field_x`` and ``field_y``\n   from the associated data. A white noise background will be used for ``texture``\n   as default. Adjust the bounds of ``lim`` in the range of ``[0, 1]`` which applies\n   upper and lower bounds to the values of line integral convolution and enhance\n   the visibility of plots. When ``const_alpha=False``, alpha will be weighted\n   spatially by the values of line integral convolution; otherwise a constant value\n   of the given alpha is used.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   s = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n   s.annotate_line_integral_convolution((\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"), lim=(0.5, 0.65))\n   s.save()\n\n.. _annotate-text:\n\nOverplot Text\n~~~~~~~~~~~~~\n\n.. function:: annotate_text(self, pos, text, coord_system='data', \\\n                            text_args=None, inset_box_args=None)\n\n    (This is a proxy for\n    :class:`~yt.visualization.plot_modifications.TextLabelCallback`.)\n\n    Overplot text on the plot at a specified position. If you desire an inset\n    box around your text, set one with the inset_box_args dictionary\n    keyword.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   s = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), center=\"max\", width=(10, \"kpc\"))\n   s.annotate_text((2, 2), \"Galaxy!\", coord_system=\"plot\")\n   s.save()\n\n.. _annotate-title:\n\nAdd a Title\n~~~~~~~~~~~\n\n.. function:: annotate_title(self, title='Plot')\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.TitleCallback`.)\n\n   Accepts a ``title`` and adds it to the plot.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n   p.annotate_title(\"Density Plot\")\n   p.save()\n\n.. _annotate-velocity:\n\nOverplot Quivers for the Velocity Field\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_velocity(self, factor=16, *, scale=None, scale_units=None, \\\n                                normalize=False, **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.VelocityCallback`.)\n\n   Adds a 'quiver' plot of velocity to the plot, skipping every ``factor``\n   datapoints in the discretization. ``scale`` is the data units per arrow\n   length unit using ``scale_units``. If ``normalize`` is ``True``, the\n   velocity fields will be scaled by their local (in-plane) length, allowing\n   morphological features to be more clearly seen for fields with substantial\n   variation in field strength. Additional arguments can be passed to the\n   ``plot_args`` dictionary, see matplotlib.axes.Axes.quiver for more info.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), center=\"m\", width=(10, \"kpc\"))\n   p.annotate_velocity(headwidth=4)\n   p.save()\n\n.. _annotate-timestamp:\n\nAdd the Current Time and/or Redshift\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_timestamp(x_pos=None, y_pos=None, corner='lower_left',\\\n                                 time=True, redshift=False, \\\n                                 time_format='t = {time:.1f} {units}', \\\n                                 time_unit=None, time_offset=None, \\\n                                 redshift_format='z = {redshift:.2f}', \\\n                                 draw_inset_box=False, coord_system='axis', \\\n                                 text_args=None, inset_box_args=None)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.TimestampCallback`.)\n\n    Annotates the timestamp and/or redshift of the data output at a specified\n    location in the image (either in a present corner, or by specifying (x,y)\n    image coordinates with the x_pos, y_pos arguments.  If no time_units are\n    specified, it will automatically choose appropriate units.  It allows for\n    custom formatting of the time and redshift information, the specification\n    of an inset box around the text, and changing the value of the timestamp\n    via a constant offset.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n   p.annotate_timestamp()\n   p.save()\n\n.. _annotate-scale:\n\nAdd a Physical Scale Bar\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_scale(corner='lower_right', coeff=None, \\\n                             unit=None, pos=None, \\\n                             scale_text_format=\"{scale} {units}\", \\\n                             max_frac=0.16, min_frac=0.015, \\\n                             coord_system='axis', text_args=None, \\\n                             size_bar_args=None, draw_inset_box=False, \\\n                             inset_box_args=None)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.ScaleCallback`.)\n\n    Annotates the scale of the plot at a specified location in the image\n    (either in a preset corner, or by specifying (x,y) image coordinates with\n    the pos argument.  Coeff and units (e.g. 1 Mpc or 100 kpc) refer to the\n    distance scale you desire to show on the plot.  If no coeff and units are\n    specified, an appropriate pair will be determined such that your scale bar\n    is never smaller than min_frac or greater than max_frac of your plottable\n    axis length.  Additional customization of the scale bar is possible by\n    adjusting the text_args and size_bar_args dictionaries.  The text_args\n    dictionary accepts matplotlib's font_properties arguments to override\n    the default font_properties for the current plot.  The size_bar_args\n    dictionary accepts keyword arguments for the AnchoredSizeBar class in\n    matplotlib's axes_grid toolkit. Finally, the format of the scale bar text\n    can be adjusted using the scale_text_format keyword argument.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), center=\"c\", width=(20, \"kpc\"))\n   p.annotate_scale()\n   p.save()\n\n.. _annotate-triangle-facets:\n\nAnnotate Triangle Facets Callback\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_triangle_facets(triangle_vertices, **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.TriangleFacetsCallback`.)\n\n   This add a line collection of a SlicePlot's plane-intersection\n   with the triangles to the plot. This callback is ideal for a\n   dataset representing a geometric model of triangular facets.\n\n.. python-script::\n\n   import os\n\n   import h5py\n\n   import yt\n\n   # Load data file\n   ds = yt.load(\"MoabTest/fng_usrbin22.h5m\")\n\n   # Create the desired slice plot\n   s = yt.SlicePlot(ds, \"z\", (\"moab\", \"TALLY_TAG\"))\n\n   # get triangle vertices from file (in this case hdf5)\n\n   # setup file path for yt test directory\n   filename = os.path.join(\n       yt.config.ytcfg.get(\"yt\", \"test_data_dir\"), \"MoabTest/mcnp_n_impr_fluka.h5m\"\n   )\n   f = h5py.File(filename, mode=\"r\")\n   coords = f[\"/tstt/nodes/coordinates\"][:]\n   conn = f[\"/tstt/elements/Tri3/connectivity\"][:]\n   points = coords[conn - 1]\n\n   # Annotate slice-triangle intersection contours to the plot\n   s.annotate_triangle_facets(points, colors=\"black\")\n   s.save()\n\n.. _annotate-mesh-lines:\n\nAnnotate Mesh Lines Callback\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_mesh_lines(**kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.MeshLinesCallback`.)\n\n   This draws the mesh line boundaries over a plot using a Matplotlib\n   line collection. This callback is only useful for unstructured or\n   semi-structured mesh datasets.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e\")\n   sl = yt.SlicePlot(ds, \"z\", (\"connect1\", \"nodal_aux\"))\n   sl.annotate_mesh_lines(color=\"black\")\n   sl.save()\n\n.. _annotate-ray:\n\nOverplot the Path of a Ray\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. function:: annotate_ray(ray, *, arrow=False, **kwargs)\n\n   (This is a proxy for\n   :class:`~yt.visualization.plot_modifications.RayCallback`.)\n\n    Adds a line representing the projected path of a ray across the plot.  The\n    ray can be either a\n    :class:`~yt.data_objects.selection_objects.ray.YTOrthoRay`,\n    :class:`~yt.data_objects.selection_objects.ray.YTRay`, or a\n    Trident :class:`~trident.light_ray.LightRay`\n    object.  annotate_ray() will properly account for periodic rays across the\n    volume.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   oray = ds.ortho_ray(0, (0.3, 0.4))\n   ray = ds.ray((0.1, 0.2, 0.3), (0.6, 0.7, 0.8))\n   p = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"))\n   p.annotate_ray(oray)\n   p.annotate_ray(ray)\n   p.save()\n\n\nApplying filters on the final image\n-----------------------------------\n\nIt is also possible to operate on the plotted image directly by using\none of the fixed resolution buffer filter as described in\n:ref:`frb-filters`.\nNote that it is necessary to call the plot object's ``refresh`` method\nto apply filters.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')\n   p = yt.SlicePlot(ds, 'z', 'density')\n   p.frb.apply_gauss_beam(sigma=30)\n   p.refresh()\n   p.save()\n\n\n.. _extend-annotations:\n\n\nExtending annotations methods\n-----------------------------\n\nNew ``annotate_`` methods can be added to plot objects at runtime (i.e., without\nmodifying yt's source code) by subclassing the base ``PlotCallback`` class.\nThis is the recommended way to add custom and unique annotations to yt plots,\nas it can be done through local plugins, individual scripts, or even external packages.\n\nHere's a minimal example:\n\n\n.. python-script::\n\n   import yt\n   from yt.visualization.api import PlotCallback\n\n\n   class TextToPositionCallback(PlotCallback):\n      # bind a new `annotate_text_to_position` plot method\n      _type_name = \"text_to_position\"\n\n      def __init__(self, text, x, y):\n         # this method can have arbitrary arguments\n         # and should store them without alteration,\n         # but not run expensive computations\n         self.text = text\n         self.position = (x, y)\n\n      def __call__(self, plot):\n         # this method's signature is required\n         # this is where we perform potentially expensive operations\n\n         # the plot argument exposes matplotlib objects:\n         # - plot._axes is a matplotlib.axes.Axes object\n         # - plot._figure is a matplotlib.figure.Figure object\n         plot._axes.annotate(\n               self.text,\n               xy=self.position,\n               xycoords=\"data\",\n               xytext=(0.2, 0.6),\n               textcoords=\"axes fraction\",\n               color=\"white\",\n               fontsize=30,\n               arrowprops=dict(facecolor=\"black\", shrink=0.05),\n         )\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.SlicePlot(ds, \"z\", \"density\")\n   p.annotate_text_to_position(\"Galactic center !\", x=0, y=0)\n   p.save()\n"
  },
  {
    "path": "doc/source/visualizing/colormaps/cmap_images.py",
    "content": "import matplotlib as mpl\n\nimport yt\n\n# Load the dataset.\nds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n# Create projections using each colormap available.\np = yt.ProjectionPlot(ds, \"z\", \"density\", weight_field=\"density\", width=0.4)\n\nfor cmap in mpl.colormaps:\n    if cmap.startswith(\"idl\"):\n        continue\n    p.set_cmap(field=\"density\", cmap=cmap)\n    p.annotate_title(cmap)\n    p.save(f\"Projection_{cmap.replace(' ', '_')}.png\")\n"
  },
  {
    "path": "doc/source/visualizing/colormaps/index.rst",
    "content": ".. _colormaps:\n\nColormaps\n=========\n\nThere are several colormaps available for yt.  yt includes all of the\nmatplotlib colormaps as well for nearly all functions.  Individual\nvisualization functions usually allow you to specify a colormap with the\n``cmap`` flag.\n\nIn yt 3.3, we changed the default yt colormap from ``algae`` to ``arbre``.\nThis colormap was designed and voted on by the yt community and is designed to\nbe easier for people with different color sensitivities as well as when printed\nin black and white.  In 3.3, additional colormaps ``dusk``, ``kelp`` and\n``octarine`` were also added, following the same guidelines.  For a deeper dive\ninto colormaps, see the SciPy 2015 talk by Stéfan van der Walt and Nathaniel\nSmith about the new matplotlib colormap ``viridis`` at\nhttps://www.youtube.com/watch?v=xAoljeRJ3lU .\n\nTo specify a different default colormap (including ``viridis``), in your yt\nconfiguration file (see :ref:`configuration-file`) you can set the value\n``default_colormap`` to the name of the colormap you would like.  In contrast\nto previous versions of yt, starting in 3.3 yt no longer overrides any\nmatplotlib defaults and instead only applies the colormap to yt-produced plots.\n\n.. _install-palettable:\n\nPalettable and ColorBrewer2\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhile colormaps that employ a variety of colors often look attractive,\nthey are not always the best choice to convey information to one's audience.\nThere are numerous `articles <https://eagereyes.org/basics/rainbow-color-map>`_\nand\n`presentations <https://speakerdeck.com/kthyng/perceptions-of-matplotlib-colormaps>`_\nthat discuss how rainbow-based colormaps fail with regard to black-and-white\nreproductions, colorblind audience members, and confusing in color ordering.\nDepending on the application, the consensus seems to be that gradients between\none or two colors are the best way for the audience to extract information\nfrom one's figures.  Many such colormaps are found in palettable.\n\nIf you have installed `palettable <http://jiffyclub.github.io/palettable/>`_\n(formerly brewer2mpl), you can also access the discrete colormaps available\nto that package including those from `colorbrewer <http://colorbrewer2.org>`_.\nInstall `palettable <http://jiffyclub.github.io/palettable/>`_ with\n``pip install palettable``.  To access these maps in yt, instead of supplying\nthe colormap name, specify a tuple of the form (name, type, number), for\nexample ``('RdBu', 'Diverging', 9)``.  These discrete colormaps will\nnot be interpolated, and can be useful for creating\ncolorblind/printer/grayscale-friendly plots. For more information, visit\n`http://colorbrewer2.org <http://colorbrewer2.org>`_.\n\n\n.. _custom-colormaps:\n\nMaking and Viewing Custom Colormaps\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nyt can also accommodate custom colormaps using the\n:func:`~yt.visualization.color_maps.make_colormap` function\nThese custom colormaps can be made to an arbitrary level of\ncomplexity.  You can make these on the fly for each yt session, or you can\nstore them in your :ref:`plugin-file` for access to them in every future yt\nsession.  The example below creates two custom colormaps, one that has\nthree equally spaced bars of blue, white and red, and the other that\ninterpolates in increasing lengthed intervals from black to red, to green,\nto blue.  These will be accessible for the rest of the yt session as\n'french_flag' and 'weird'.  See\n:func:`~yt.visualization.color_maps.make_colormap` and\n:func:`~yt.visualization.color_maps.show_colormaps` for more details.\n\n.. code-block:: python\n\n    yt.make_colormap(\n        [(\"blue\", 20), (\"white\", 20), (\"red\", 20)],\n        name=\"french_flag\",\n        interpolate=False,\n    )\n    yt.make_colormap(\n        [(\"black\", 5), (\"red\", 10), (\"green\", 20), (\"blue\", 0)],\n        name=\"weird\",\n        interpolate=True,\n    )\n    yt.show_colormaps(subset=[\"french_flag\", \"weird\"], filename=\"cmaps.png\")\n\nAll Colormaps (including matplotlib)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis is a chart of all of the yt and matplotlib colormaps available.  In\naddition to each colormap displayed here, you can access its \"reverse\" by simply\nappending a ``\"_r\"`` to the end of the colormap name.\n\n.. image:: ../_images/all_colormaps.png\n   :width: 512\n\nNative yt Colormaps\n~~~~~~~~~~~~~~~~~~~\n\n.. image:: ../_images/native_yt_colormaps.png\n   :width: 512\n\nDisplaying Colormaps Locally\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTo display the most up to date colormaps locally, you can use the\n:func:`~yt.visualization.color_maps.show_colormaps` function.  By default,\nyou'll see every colormap available to you, but you can specify subsets\nof colormaps to display, either as just the ``yt_native`` colormaps, or\nby specifying a list of colormap names.  This will display all the colormaps\navailable in a local window:\n\n.. code-block:: python\n\n    import yt\n\n    yt.show_colormaps()\n\nor to output the original yt colormaps to an image file, try:\n\n.. code-block:: python\n\n    import yt\n\n    yt.show_colormaps(\n        subset=[\n            \"cmyt.algae\",\n            \"cmyt.arbre\",\n            \"cmyt.dusk\",\n            \"cmyt.kelp\",\n            \"cmyt.octarine\",\n            \"cmyt.pastel\",\n        ],\n        filename=\"yt_native.png\",\n    )\n\n.. note ::\n\n    Since yt 4.1, yt native colormaps are shipped as a separate package\n    `cmyt <https://pypi.org/project/cmyt/>`_ that can be used\n    outside yt itself.\n    Within `yt` functions, these colormaps can still be referenced without\n    the ``\"cmyt.\"`` prefix. However, there is no guarantee that this will\n    work in upcoming version of matplotlib, so our recommentation is to keep\n    the prefix at all times to retain forward compatibility.\n    yt also retains compatibility with names these colormaps were formerly\n    known as (for instance ``cmyt.pastel`` used to be named ``kamae``).\n\n\nApplying a Colormap to your Rendering\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAll of the visualization functions in yt have a keyword allowing you to\nmanually specify a specific colormap.  For example:\n\n.. code-block:: python\n\n    yt.write_image(im, \"output.png\", cmap_name=\"jet\")\n\nIf you're using the Plot Window interface (e.g. SlicePlot, ProjectionPlot,\netc.), it's even easier than that.  Simply create your rendering, and you\ncan quickly swap the colormap on the fly after the fact with the ``set_cmap``\ncallback:\n\n.. code-block:: python\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    p = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"))\n\n    p.set_cmap(field=(\"gas\", \"density\"), cmap=\"turbo\")\n    p.save(\"proj_with_jet_cmap.png\")\n\n    p.set_cmap(field=(\"gas\", \"density\"), cmap=\"hot\")\n    p.save(\"proj_with_hot_cmap.png\")\n\nFor more information about the callbacks available to Plot Window objects,\nsee :ref:`callbacks`.\n\nExamples of Each Colormap\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTo give the reader a better feel for how a colormap appears once it is applied\nto a dataset, below we provide a library of identical projections of an\nisolated galaxy where only the colormap has changed.  They use the sample\ndataset \"IsolatedGalaxy\" available at\n`https://yt-project.org/data <https://yt-project.org/data>`_.\n\n.. yt_colormaps:: cmap_images.py\n"
  },
  {
    "path": "doc/source/visualizing/geographic_projections_and_transforms.rst",
    "content": ".. _geographic_projections_and_transforms:\n\nGeographic Projections and Transforms\n=====================================\n\nGeographic data that is on a sphere can be visualized by projecting that data\nonto a representation of that sphere flattened into 2d space. There exist a\nnumber of projection types, which can be found in the `the cartopy\ndocumentation <https://cartopy.readthedocs.io/stable/reference/projections.html>`_.\nWith support from `cartopy <https://cartopy.readthedocs.io/stable/>`_,\n``yt`` now supports these projection\ntypes for geographically loaded data.\nUnderlying data is assumed to have a transform of `PlateCarree\n<https://cartopy.readthedocs.io/stable/reference/projections.html#platecarree>`__,\nwhich is data on a flattened, rectangular, latitude/longitude grid. This is a\na typical format for geographic data.\n\nThe distinction between the data transform and projection is worth noting. The data\ntransform is what system your data is defined with and the data projection is\nwhat the resulting plot will display. For more information on this difference,\nrefer to `the cartopy documentation on these differences\n<https://cartopy.readthedocs.io/stable/tutorials/understanding_transform.html>`_.\nIf your data is not of this form, feel free to open an issue or file a pull\nrequest on the ``yt`` github page for this feature.\n\nIt should be noted that\nthese projections are not the same as yt's ProjectionPlot. For more information\non yt's projection plots, see :ref:`projection-types`.\n\n.. _install-cartopy:\n\nInstalling Cartopy\n^^^^^^^^^^^^^^^^^^\n\nIn order to access the geographic projection functionality, you will need to have an\ninstallation of ``cartopy`` available on your machine. Please refer to `Cartopy's\ndocumentation for detailed instructions <https://cartopy.readthedocs.io/stable/installing.html>`_\n\nUsing Basic Transforms\n^^^^^^^^^^^^^^^^^^^^^^^\n\nAs mentioned above, the default data transform is assumed to be of `PlateCarree\n<https://cartopy.readthedocs.io/stable/reference/projections.html#platecarree>`__,\nwhich is data on a flattened, rectangular, latitude/longitude grid. To set\nsomething other than ``PlateCarree``, the user can access the dictionary in the coordinate\nhandler that defines the coordinate transform to change the default transform\ntype. Because the transform\ndescribes the underlying data coordinate system, the loaded dataset will carry\nthis newly set attribute and all future plots will have the user-defined data\ntransform. Also note that the dictionary is ordered by axis type. Because\nslicing along the altitude may differ from, say, the latitude axis, we may\nchoose to have different transforms for each axis.\n\n.. code-block:: python\n\n    ds = yt.load_uniform_grid(data, sizes, 1.0, geometry=(\"geographic\", dims), bbox=bbox)\n    ds.coordinates.data_transform[\"altitude\"] = \"Miller\"\n    p = yt.SlicePlot(ds, \"altitude\", \"AIRDENS\")\n\nIn this example, the ``data_transform`` kwarg has been changed from its default\nof ``PlateCarree`` to ``Miller``. You can check that you have successfully changed\nthe defaults by inspecting the ``data_transform`` and ``data_projection`` dictionaries\nin the coordinate\nhandler. For this dataset, that would be accessed by:\n\n.. code-block:: python\n\n    print(ds.coordinates.data_transform[\"altitude\"])\n    print(ds.coordinates.data_projection[\"altitude\"])\n\n\n\nUsing Basic Projections\n^^^^^^^^^^^^^^^^^^^^^^^\n\nAll of the transforms available in ``Cartopy`` v0.15 and above are accessible\nwith this functionality.\n\nThe next few examples will use a GEOS dataset accessible from the ``yt`` data\ndownloads page. For details about loading this data, please\nsee :doc:`../cookbook/geographic_xforms_and_projections`.\n\nIf a geographic dataset is loaded without any defined projection the default\noption of ``Mollweide`` will be displayed.\n\n.. code-block:: python\n\n    ds = yt.load_uniform_grid(data, sizes, 1.0, geometry=(\"geographic\", dims), bbox=bbox)\n    p = yt.SlicePlot(ds, \"altitude\", \"AIRDENS\")\n\nIf an option other than ``Mollweide`` is desired, the plot projection type can\nbe set with the ``set_mpl_projection`` function. The next code block illustrates how to\nset the projection to a ``Robinson`` projection from the default ``PlateCarree``.\n\n.. code-block:: python\n\n    ds = yt.load_uniform_grid(data, sizes, 1.0, geometry=(\"geographic\", dims), bbox=bbox)\n    p = yt.SlicePlot(ds, \"altitude\", \"AIRDENS\")\n    p.set_mpl_projection(\"Robinson\")\n    p.show()\n\nThe axes attributes of the plot can be accessed to add in annotations, such as\ncoastlines. The axes are matplotlib ``GeoAxes`` so any of the annotations\navailable with matplotlib should be available for customization. Here a\n``Robinson`` plot is made with coastline annotations.\n\n.. code-block:: python\n\n    p.set_mpl_projection(\"Robinson\")\n    p.render()\n    p.plots[\"AIRDENS\"].axes.set_global()\n    p.plots[\"AIRDENS\"].axes.coastlines()\n    p.show()\n\n``p.render()`` is required here to access the plot axes. When a new\nprojection is called the plot axes are reset and are not available unless set\nup again.\n\nAdditional arguments can be passed to the projection function for further\ncustomization. If additional arguments are desired, then rather than passing a\nstring of the projection name, one would pass a 2 or 3-item tuple, the first\nitem of the tuple corresponding to a string of the transform name, and the\nsecond and third items corresponding to the args and kwargs of the transform,\nrespectively.\n\nAlternatively, a user can pass a transform object rather than a string or tuple.\nThis allows for users to\ncreate and define their own transforms, beyond what is available in cartopy.\nThe type must be a cartopy GeoAxes object or a matplotlib transform object. For\ncreating custom transforms, see `the matplotlib example\n<https://matplotlib.org/examples/api/custom_projection_example.html>`_.\n\nThe function ``set_mpl_projection()`` accepts several input types for varying\nlevels of customization:\n\n.. code-block:: python\n\n    set_mpl_projection(\"ProjectionType\")\n    set_mpl_projection((\"ProjectionType\", (args)))\n    set_mpl_projection((\"ProjectionType\", (args), {kwargs}))\n    set_mpl_projection(cartopy.crs.PlateCarree())\n\nFurther examples of using the geographic transforms with this dataset\ncan be found in :doc:`../cookbook/geographic_xforms_and_projections`.\n"
  },
  {
    "path": "doc/source/visualizing/index.rst",
    "content": ".. _visualizing:\n\nVisualizing Data\n================\n\nyt comes with a number of ways for visualizing one's data including slices,\nprojections, line plots, profiles, phase plots, volume rendering, 3D surfaces,\nstreamlines, and a google-maps-like interface for exploring one's dataset\ninteractively.\n\n.. toctree::\n   :maxdepth: 2\n\n   plots\n   callbacks\n   manual_plotting\n   volume_rendering\n   unstructured_mesh_rendering\n   interactive_data_visualization\n   visualizing_particle_datasets_with_firefly\n   sketchfab\n   mapserver\n   streamlines\n   colormaps/index\n   geographic_projections_and_transforms\n   FITSImageData\n"
  },
  {
    "path": "doc/source/visualizing/interactive_data_visualization.rst",
    "content": ".. _interactive_data_visualization:\n\nInteractive Data Visualization\n==============================\n\nThe interactive, OpenGL-based volume rendering system for yt has been exported\ninto its own package, called ``yt_idv``.\n\nDocumentation, including installation instructions, can be found at `its\nwebsite <https://yt-idv.readthedocs.io/en/latest/>`_, and the source code is\nhosted under the yt-project organization on github at `yt_idv\n<https://github.com/yt-project/yt_idv>`_.\n"
  },
  {
    "path": "doc/source/visualizing/manual_plotting.rst",
    "content": ".. _manual-plotting:\n\nUsing the Manual Plotting Interface\n===================================\n\nSometimes you need a lot of flexibility in creating plots. While the\n:class:`~yt.visualization.plot_window.PlotWindow` provides an easy to\nuse object that can create nice looking, publication quality plots with a\nminimum of effort, there are often times when its ease of use conflicts with\nyour need to change the font only on the x-axis, or whatever your\nneed/desire/annoying coauthor requires. To that end, yt provides a number of\nways of getting the raw data that goes into a plot to you in the form of a one\nor two dimensional dataset that you can plot using any plotting method you like.\nmatplotlib or another python library are easiest, but these methods allow you to\ntake your data and plot it in gnuplot, or any unnamed commercial plotting\npackages.\n\nNote that the index object associated with your snapshot file contains a\nlist of plots you've made in ``ds.plots``.\n\n.. _fixed-resolution-buffers:\n\nSlice, Projections, and other Images: The Fixed Resolution Buffer\n-----------------------------------------------------------------\n\nFor slices and projects, yt provides a manual plotting interface based on\nthe :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer` (hereafter\nreferred to as FRB) object. Despite its somewhat unwieldy name, at its heart, an\nFRB is a very simple object: it's essentially a window into your data: you give\nit a center and a width or a left and right edge, and an image resolution, and\nthe FRB returns a fully pixelized image. The simplest way to\ngenerate an FRB is to use the ``.to_frb(width, resolution, center=None)`` method\nof any data two-dimensional data object:\n\n.. python-script::\n\n   import matplotlib\n\n   matplotlib.use(\"Agg\")\n   import numpy as np\n   from matplotlib import pyplot as plt\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n   _, c = ds.find_max((\"gas\", \"density\"))\n   proj = ds.proj((\"gas\", \"density\"), 0)\n\n   width = (10, \"kpc\")  # we want a 1.5 mpc view\n   res = [1000, 1000]  # create an image with 1000x1000 pixels\n   frb = proj.to_frb(width, res, center=c)\n\n   plt.imshow(np.array(frb[\"gas\", \"density\"]))\n   plt.savefig(\"my_perfect_figure.png\")\n\nNote that in the above example the axes tick marks indicate pixel indices.  If you\nwant to represent physical distances on your plot axes, you will need to use the\n``extent`` keyword of the ``imshow`` function.\n\nThe FRB is a very small object that can be deleted and recreated quickly (in\nfact, this is how ``PlotWindow`` plots work behind the scenes). Furthermore, you\ncan add new fields in the same \"window\", and each of them can be plotted with\ntheir own zlimit. This is quite useful for creating a mosaic of the same region\nin space with Density, Temperature, and x-velocity, for example. Each of these\nquantities requires a substantially different set of limits.\n\nA more complex example, showing a few yt helper functions that can make\nsetting up multiple axes with colorbars easier than it would be using only\nmatplotlib can be found in the :ref:`advanced-multi-panel` cookbook recipe.\n\n.. _frb-filters:\n\nFixed Resolution Buffer Filters\n-------------------------------\n\nThe FRB can be modified by using set of predefined filters in order to e.g.\ncreate realistically looking, mock observation images out of simulation data.\nApplying filter is an irreversible operation, hence the order in which you are\nusing them matters.\n\n.. python-script::\n\n   import matplotlib\n\n   matplotlib.use(\"Agg\")\n   from matplotlib import pyplot as plt\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = ds.slice(\"z\", 0.5)\n   frb = slc.to_frb((20, \"kpc\"), 512)\n   frb.apply_gauss_beam(nbeam=30, sigma=2.0)\n   frb.apply_white_noise(5e-23)\n   plt.imshow(frb[\"gas\", \"density\"].d)\n   plt.savefig(\"frb_filters.png\")\n\nCurrently available filters:\n\nGaussian Smoothing\n~~~~~~~~~~~~~~~~~~\n\n.. function:: apply_gauss_beam(self, nbeam=30, sigma=2.0)\n\n   (This is a proxy for\n   :class:`~yt.visualization.fixed_resolution_filters.FixedResolutionBufferGaussBeamFilter`.)\n\n    This filter convolves the FRB with 2d Gaussian that is \"nbeam\" pixel wide\n    and has standard deviation \"sigma\".\n\nWhite Noise\n~~~~~~~~~~~\n\n.. function:: apply_white_noise(self, bg_lvl=None)\n\n   (This is a proxy for\n   :class:`~yt.visualization.fixed_resolution_filters.FixedResolutionBufferWhiteNoiseFilter`.)\n\n    This filter adds white noise with the amplitude \"bg_lvl\" to the FRB.\n    If \"bg_lvl\" is not present, 10th percentile of the FRB's values is used\n    instead.\n\n.. _manual-line-plots:\n\nLine Plots\n----------\n\nThis is perhaps the simplest thing to do. yt provides a number of one\ndimensional objects, and these return a 1-D numpy array of their contents with\ndirect dictionary access. As a simple example, take a\n:class:`~yt.data_objects.selection_data_containers.YTOrthoRay` object, which can be\ncreated from a index by calling ``ds.ortho_ray(axis, center)``.\n\n.. python-script::\n\n   import matplotlib\n\n   matplotlib.use(\"Agg\")\n   import numpy as np\n   from matplotlib import pyplot as plt\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   _, c = ds.find_max((\"gas\", \"density\"))\n   ax = 0  # take a line cut along the x axis\n\n   # cutting through the y0,z0 such that we hit the max density\n   ray = ds.ortho_ray(ax, (c[1], c[2]))\n\n   # Sort the ray values by 'x' so there are no discontinuities\n   # in the line plot\n   srt = np.argsort(ray[\"index\", \"x\"])\n\n   plt.subplot(211)\n   plt.semilogy(np.array(ray[\"index\", \"x\"][srt]), np.array(ray[\"gas\", \"density\"][srt]))\n   plt.ylabel(\"density\")\n   plt.subplot(212)\n   plt.semilogy(np.array(ray[\"index\", \"x\"][srt]), np.array(ray[\"gas\", \"temperature\"][srt]))\n   plt.xlabel(\"x\")\n   plt.ylabel(\"temperature\")\n\n   plt.savefig(\"den_temp_xsweep.png\")\n\nOf course, you'll likely want to do something more sophisticated than using the\nmatplotlib defaults, but this gives the general idea.\n"
  },
  {
    "path": "doc/source/visualizing/mapserver.rst",
    "content": ".. _mapserver:\n\nMapserver - A Google-Maps-like Interface to your Data\n-----------------------------------------------------\n\nThe mapserver is an experimental feature.  It's based on `Leaflet\n<https://leafletjs.com/>`_, a library written to create zoomable,\nmap-tile interfaces.  (Similar to Google Maps.)  yt provides everything you\nneed to start up a web server that will interactively re-pixelize an adaptive\nimage.  This means you can explore your datasets in a fully pan-n-zoom\ninterface.\n\n.. note::\n\n   Previous versions of yt bundled the necessary dependencies, but with more\n   recent released you will need to install the package ``bottle`` via pip or\n   conda.\n\nTo start up the mapserver, you can use the command yt (see\n:ref:`command-line`) with the ``mapserver`` subcommand.  It takes several of\nthe same options and arguments as the ``plot`` subcommand.  For instance:\n\n.. code-block:: bash\n\n   yt mapserver DD0050/DD0050\n\nThat will take a slice along the x axis at the center of the domain.  The\nfield, projection, weight and axis can all be specified on the command line.\n\nWhen you do this, it will spawn a micro-webserver on your localhost, and output\nthe URL to connect to standard output.  You can connect to it (or create an\nSSH tunnel to connect to it) and explore your data.  Double-clicking zooms, and\ndragging drags.\n\n.. image:: _images/mapserver.png\n   :scale: 50%\n\nThis is also functional on touch-capable devices such as Android Tablets and\niPads/iPhones.\n"
  },
  {
    "path": "doc/source/visualizing/plots.rst",
    "content": "\n.. _how-to-make-plots:\n\nHow to Make Plots\n=================\n\n.. note::\n\n   In this document, and the rest of the yt documentation, we use field tuples;\n   for instance, we specify density as ``(\"gas\", \"density\")`` whereas in\n   previous versions of this document we typically just used ``\"density\"``.\n   While the latter will still work in many or most cases, and may suffice for\n   your purposes, for ensuring we explicitly avoid ambiguity we use field tuples\n   here.\n\nIn this section we explain how to use yt to create visualizations\nof simulation data, derived fields, and the data produced by yt\nanalysis objects.  For details about the data extraction and\nalgorithms used to produce the image and analysis data, please see the\nyt `method paper\n<https://ui.adsabs.harvard.edu/abs/2011ApJS..192....9T>`_.  There are also\nmany example scripts in :ref:`cookbook`.\n\nThe :class:`~yt.visualization.plot_window.PlotWindow` interface is useful for\ntaking a quick look at simulation outputs.  Simple mechanisms exist for making\nplots of slices, projections, 1D spatial line plots, 1D profiles, and 2D\nprofiles (phase plots), all of which are described below.\n\n.. _viewing-plots:\n\nViewing Plots\n-------------\n\nyt uses an environment neutral plotting mechanism that detects the appropriate\nmatplotlib configuration for a given environment, however it defaults to a basic\nrenderer. To utilize interactive plots in matplotlib supported\nenvironments (Qt, GTK, WX, etc.) simply call the ``toggle_interactivity()`` function. Below is an\nexample in a jupyter notebook environment, but the same command should work\nin other environments as well:\n\n.. code-block:: IPython\n\n   %matplotlib notebook\n   import yt\n   yt.toggle_interactivity()\n\n.. _simple-inspection:\n\nSlices & Projections\n--------------------\n\nIf you need to take a quick look at a single simulation output, yt\nprovides the :class:`~yt.visualization.plot_window.PlotWindow` interface for\ngenerating annotated 2D visualizations of simulation data.  You can create a\n:class:`~yt.visualization.plot_window.PlotWindow` plot by\nsupplying a dataset, a list of fields to plot, and a plot center to\ncreate a :class:`~yt.visualization.plot_window.AxisAlignedSlicePlot`,\n:class:`~yt.visualization.plot_window.OffAxisSlicePlot`,\n:class:`~yt.visualization.plot_window.ProjectionPlot`, or\n:class:`~yt.visualization.plot_window.OffAxisProjectionPlot`.\n\nPlot objects use yt data objects to extract the maximum resolution\ndata available to render a 2D image of a field. Whenever a\ntwo-dimensional image is created, the plotting object first obtains\nthe necessary data at the *highest resolution*.  Every time an image\nis requested of it -- for instance, when the width or field is changed\n-- this high-resolution data is then pixelized and placed in a buffer\nof fixed size. This is accomplished behind the scenes using\n:class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer`.\n\nThe :class:`~yt.visualization.plot_window.PlotWindow` class exposes the\nunderlying matplotlib\n`figure <https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure>`_\nand `axes <https://matplotlib.org/stable/api/axes_api.html#matplotlib.axes.Axes>`_\nobjects, making it easy to customize your plots and\nadd new annotations.  See :ref:`matplotlib-customization` for more information.\n\n.. _slice-plots:\n\nSlice Plots\n~~~~~~~~~~~\n\nThe quickest way to plot a slice of a field through your data is via\n:class:`~yt.visualization.plot_window.SlicePlot`.  These plots are generally\nquicker than projections because they only need to read and process a slice\nthrough the dataset.\n\nThe following script plots a slice through the density field along the z-axis\ncentered on the center of the simulation box in a simulation dataset we've\nopened and stored in ``ds``:\n\n.. code-block:: python\n\n    slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    slc.save()\n\nThese two commands will create a slice object and store it in a variable we've\ncalled ``slc``.  Since this plot is aligned with the simulation coordinate\nsystem, ``slc`` is an instance of\n:class:`~yt.visualization.plot_window.AxisAlignedSlicePlot`. We then call the\n``save()`` function, which automatically saves the plot in png image format with\nan automatically generated filename.  If you don't want the slice object to\nstick around, you can accomplish the same thing in one line:\n\n.. code-block:: python\n\n    yt.SlicePlot(ds, \"z\", (\"gas\", \"density\")).save()\n\nIt's nice to keep the slice object around if you want to modify the plot.  By\ndefault, the plot width will be set to the size of the simulation box.  To zoom\nin by a factor of ten, you can call the zoom function attached to the slice\nobject:\n\n.. code-block:: python\n\n    slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    slc.zoom(10)\n    slc.save(\"zoom\")\n\nThis will save a new plot to disk with a different filename - prepended with\n'zoom' instead of the name of the dataset. If you want to set the width\nmanually, you can do that as well. For example, the following sequence of\ncommands will create a slice, set the width of the plot to 10 kiloparsecs, and\nsave it to disk, with the filename prefix being ``10kpc`` and the rest determined\nby the field, visualization method, etc.\n\n.. code-block:: python\n\n    from yt.units import kpc\n\n    slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    slc.set_width(10 * kpc)\n    slc.save(\"10kpc\")\n\nThe plot width can be specified independently along the x and y direction by\npassing a tuple of widths.  An individual width can also be represented using a\n``(value, unit)`` tuple.  The following sequence of commands all equivalently\nset the width of the plot to 200 kiloparsecs in the ``x`` and ``y`` direction.\n\n.. code-block:: python\n\n    from yt.units import kpc\n\n    slc.set_width(200 * kpc)\n    slc.set_width((200, \"kpc\"))\n    slc.set_width((200 * kpc, 200 * kpc))\n\nThe ``SlicePlot`` also optionally accepts the coordinate to center the plot on\nand the width of the plot:\n\n.. code-block:: python\n\n    yt.SlicePlot(\n        ds, \"z\", (\"gas\", \"density\"), center=[0.2, 0.3, 0.8], width=(10, \"kpc\")\n    ).save()\n\nNote that, by default,\n:class:`~yt.visualization.plot_window.SlicePlot` shifts the\ncoordinates on the axes such that the origin is at the center of the\nslice.  To instead use the coordinates as defined in the dataset, use\nthe optional argument: ``origin=\"native\"``\n\nIf supplied without units, the center is assumed by in code units.  There are also\nthe following alternative options for the ``center`` keyword:\n\n* ``\"center\"``, ``\"c\"``: the domain center\n* ``\"left\"``, ``\"l\"``, ``\"right\"`` ``\"r\"``: the domain's left/right edge along the normal direction\n  (``SlicePlot``'s second argument). Remaining axes use their respective domain center values.\n* ``\"min\"``: the position of the minimum density\n* ``\"max\"``, ``\"m\"``: the position of the maximum density\n* ``\"min/max_<field name>\"``: the position of the minimum/maximum in the first field matching field name\n* ``(\"min\", field)``: the position of the minimum of ``field``\n* ``(\"max\", field)``: the position of the maximum of ``field``\n\nwhere for the last two objects any spatial field, such as ``\"density\"``,\n``\"velocity_z\"``,\netc., may be used, e.g. ``center=(\"min\", (\"gas\", \"temperature\"))``.\n``\"left\"`` and ``\"right\"`` are not allowed for off-axis slices.\n\nThe effective resolution of the plot (i.e. the number of resolution elements\nin the image itself) can be controlled with the ``buff_size`` argument:\n\n.. code-block:: python\n\n    yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), buff_size=(1000, 1000))\n\n\nHere is an example that combines all of the options we just discussed.\n\n.. python-script::\n\n   import yt\n   from yt.units import kpc\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(\n       ds,\n       \"z\",\n       (\"gas\", \"density\"),\n       center=[0.5, 0.5, 0.5],\n       width=(20, \"kpc\"),\n       buff_size=(1000, 1000),\n   )\n   slc.save()\n\nThe above example will display an annotated plot of a slice of the\nDensity field in a 20 kpc square window centered on the coordinate\n(0.5, 0.5, 0.5) in the x-y plane.  The axis to slice along is keyed to the\nletter 'z', corresponding to the z-axis.  Finally, the image is saved to\na png file.\n\nConceptually, you can think of the plot object as an adjustable window\ninto the data. For example:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"pressure\"), center=\"c\")\n   slc.save()\n   slc.zoom(30)\n   slc.save(\"zoom\")\n\nwill save a plot of the pressure field in a slice along the z\naxis across the entire simulation domain followed by another plot that\nis zoomed in by a factor of 30 with respect to the original\nimage. Both plots will be centered on the center of the simulation box.\nWith these sorts of manipulations, one can easily pan and zoom onto an\ninteresting region in the simulation and adjust the boundaries of the\nregion to visualize on the fly.\n\nIf you want to slice through a subset of the full dataset volume,\nyou can use the ``data_source`` keyword with a :ref:`data object <data-objects>`\nor a :ref:`cut region <cut-regions>`.\n\nSee :class:`~yt.visualization.plot_window.AxisAlignedSlicePlot` for the\nfull class description.\n\n.. _plot-2d:\n\nPlots of 2D Datasets\n~~~~~~~~~~~~~~~~~~~~\n\nIf you have a two-dimensional cartesian, cylindrical, or polar dataset,\n:func:`~yt.visualization.plot_window.plot_2d` is a way to make a plot\nwithin the dataset's plane without having to specify the axis, which\nin this case is redundant. Otherwise, ``plot_2d`` accepts the same\narguments as ``SlicePlot``. The one other difference is that the\n``center`` keyword argument can be a two-dimensional coordinate instead\nof a three-dimensional one:\n\n.. python-script::\n\n    import yt\n\n    ds = yt.load(\"WindTunnel/windtunnel_4lev_hdf5_plt_cnt_0030\")\n    p = yt.plot_2d(ds, (\"gas\", \"density\"), center=[1.0, 0.4])\n    p.set_log((\"gas\", \"density\"), False)\n    p.save()\n\nSee :func:`~yt.visualization.plot_window.plot_2d` for the full description\nof the function and its keywords.\n\n.. _off-axis-slices:\n\nOff Axis Slices\n~~~~~~~~~~~~~~~\n\nOff axis slice plots can be generated in much the same way as\ngrid-aligned slices.  Off axis slices use\n:class:`~yt.data_objects.selection_data_containers.YTCuttingPlane` to slice\nthrough simulation domains at an arbitrary oblique angle.  A\n:class:`~yt.visualization.plot_window.OffAxisSlicePlot` can be\ninstantiated by specifying a dataset, the normal to the cutting\nplane, and the name of the fields to plot.  Just like an\n:class:`~yt.visualization.plot_window.AxisAlignedSlicePlot`, an\n:class:`~yt.visualization.plot_window.OffAxisSlicePlot` can be created via the\n:class:`~yt.visualization.plot_window.SlicePlot` class. For example:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   L = [1, 1, 0]  # vector normal to cutting plane\n   north_vector = [-1, 1, 0]\n   cut = yt.SlicePlot(ds, L, (\"gas\", \"density\"), width=(25, \"kpc\"), north_vector=north_vector)\n   cut.save()\n\nIn this case, a normal vector for the cutting plane is supplied in the second\nargument. Optionally, a ``north_vector`` can be specified to fix the orientation\nof the image plane.\n\n.. note:: Not every data types have support for off-axis slices yet.\n   Currently, this operation is supported for grid based and SPH data with cartesian geometry.\n   In some cases an off-axis projection over a thin region might be used instead.\n\n.. _projection-plots:\n\nProjection Plots\n~~~~~~~~~~~~~~~~\n\nUsing a fast adaptive projection, yt is able to quickly project\nsimulation data along the coordinate axes.\n\nProjection plots are created by instantiating a\n:class:`~yt.visualization.plot_window.ProjectionPlot` object.  For\nexample:\n\n.. python-script::\n\n   import yt\n   from yt.units import kpc\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   prj = yt.ProjectionPlot(\n       ds,\n       \"z\",\n       (\"gas\", \"temperature\"),\n       width=25 * kpc,\n       weight_field=(\"gas\", \"density\"),\n       buff_size=(1000, 1000),\n   )\n   prj.save()\n\nwill create a density-weighted projection of the temperature field along\nthe x axis with 1000 resolution elements per side, plot it, and then save\nthe plot to a png image file.\n\nLike :ref:`slice-plots`, annotations and modifications can be applied\nafter creating the ``ProjectionPlot`` object.  Annotations are\ndescribed in :ref:`callbacks`.  See\n:class:`~yt.visualization.plot_window.ProjectionPlot` for the full\nclass description.\n\nIf you want to project through a subset of the full dataset volume,\nyou can use the ``data_source`` keyword with a :ref:`data object <data-objects>`.\nThe :ref:`thin-slice-projections` recipes demonstrates this functionality.\n\n.. note:: Not every data types have support for off-axis projections yet.\n   Currently, this operation is supported for grid based data with cartesian geometry,\n   as well as SPH particles data.\n\n.. _projection-types:\n\nTypes of Projections\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThere are several different methods of projections that can be made either\nwhen creating a projection with :meth:`~yt.static_output.Dataset.proj` or\nwhen making a :class:`~yt.visualization.plot_window.ProjectionPlot`.\nIn either construction method, set the ``method`` keyword to be one of the\nfollowing:\n\n``integrate`` (unweighted):\n+++++++++++++++++++++++++++\n\nThis is the default projection method. It simply integrates the\nrequested field :math:`f({\\bf x})` along a line of sight :math:`\\hat{\\bf n}` ,\ngiven by the axis parameter (e.g. :math:`\\hat{\\bf i},\\hat{\\bf j},` or\n:math:`\\hat{\\bf k}`). The units of the projected field\n:math:`g({\\bf X})` will be the units of the unprojected field :math:`f({\\bf x})`\nmultiplied by the appropriate length unit, e.g., density in\n:math:`\\mathrm{g\\ cm^{-3}}` will be projected to  :math:`\\mathrm{g\\ cm^{-2}}`.\n\n.. math::\n\n    g({\\bf X}) = {\\int\\ {f({\\bf x})\\hat{\\bf n}\\cdot{d{\\bf x}}}}\n\n``integrate`` (weighted):\n+++++++++++++++++++++++++\n\nWhen using the ``integrate``  method, a ``weight_field`` argument may also\nbe specified, which will produce a weighted projection. :math:`w({\\bf x})`\nis the field used as a weight. One common example would\nbe to weight the \"temperature\" field by the \"density\" field. In this case,\nthe units of the projected field are the same as the unprojected field.\n\n.. math::\n\n    g({\\bf X}) = \\int\\ {f({\\bf x})\\tilde{w}({\\bf x})\\hat{\\bf n}\\cdot{d{\\bf x}}}\n\nwhere the \"~\" over :math:`w({\\bf x})` reflects the fact that it has been normalized\nlike so:\n\n.. math::\n\n    \\tilde{w}({\\bf x}) = \\frac{w({\\bf x})}{\\int\\ {w({\\bf x})\\hat{\\bf n}\\cdot{d{\\bf x}}}}\n\nFor weighted projections using the ``integrate`` method, it is also possible\nto project the standard deviation of a field. In this case, the projected\nfield is mathematically given by:\n\n.. math::\n\n    g({\\bf X}) = \\left[\\int\\ {f({\\bf x})^2\\tilde{w}({\\bf x})\\hat{\\bf n}\\cdot{d{\\bf x}}} - \\left(\\int\\ {f({\\bf x})\\tilde{w}({\\bf x})\\hat{\\bf n}\\cdot{d{\\bf x}}}\\right)^2\\right]^{1/2}\n\nin order to make a weighted projection of the standard deviation of a field\nalong a line of sight, the ``moment`` keyword argument should be set to 2.\n\n``max``:\n++++++++\n\nThis method picks out the maximum value of a field along the line of\nsight given by the axis parameter.\n\n``min``:\n++++++++\n\nThis method picks out the minimum value of a field along the line of\nsight given by the axis parameter.\n\n``sum``:\n++++++++\n\nThis method is the same as ``integrate``, except that it does not\nmultiply by a path length when performing the integration, and is just a\nstraight summation of the field along the given axis. The units of the\nprojected field will be the same as those of the unprojected field. This\nmethod is typically only useful for datasets such as 3D FITS cubes where\nthe third axis of the dataset is something like velocity or frequency, and\nshould *only* be used with fixed-resolution grid-based datasets.\n\n.. _off-axis-projections:\n\nOff Axis Projection Plots\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nInternally, off axis projections are created using :ref:`camera`\nby applying the\n:class:`~yt.visualization.volume_rendering.transfer_functions.ProjectionTransferFunction`.\nIn this use case, the volume renderer casts a set of plane parallel rays, one\nfor each pixel in the image.  The data values along each ray are summed,\ncreating the final image buffer.\nFor SPH datsets, the coordinates are instead simply rotated before the axis-aligned\nprojection function is applied.\n\n.. _off-axis-projection-function:\n\nTo avoid manually creating a camera and setting the transfer\nfunction, yt provides the\n:func:`~yt.visualization.volume_rendering.off_axis_projection.off_axis_projection`\nfunction, which wraps the camera interface to create an off axis\nprojection image buffer.  These images can be saved to disk or\nused in custom plots.  This snippet creates an off axis\nprojection through a simulation.\n\n.. python-script::\n\n   import numpy as np\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   L = [1, 1, 0]  # vector normal to cutting plane\n   W = [0.02, 0.02, 0.02]\n   c = [0.5, 0.5, 0.5]\n   N = 512\n   image = yt.off_axis_projection(ds, c, L, W, N, (\"gas\", \"density\"))\n   yt.write_image(np.log10(image), \"%s_offaxis_projection.png\" % ds)\n\nHere, ``W`` is the width of the projection in the x, y, *and* z\ndirections.\n\nOne can also generate annotated off axis projections using\n:class:`~yt.visualization.plot_window.ProjectionPlot`. These\nplots can be created in much the same way as an\n``OffAxisSlicePlot``, requiring only an open dataset, a direction\nto project along, and a field to project.  For example:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   L = [1, 1, 0]  # vector normal to cutting plane\n   north_vector = [-1, 1, 0]\n   prj = yt.ProjectionPlot(\n       ds, L, (\"gas\", \"density\"), width=(25, \"kpc\"), north_vector=north_vector\n   )\n   prj.save()\n\n``OffAxisProjectionPlot`` objects can also be created with a number of\nkeyword arguments, as described in\n:class:`~yt.visualization.plot_window.OffAxisProjectionPlot`.\n\nLike on-axis projections, the projection of the standard deviation\nof a weighted field can be created by setting ``moment=2`` in the call\nto :class:`~yt.visualization.plot_window.ProjectionPlot`.\n\n.. _slices-and-projections-in-spherical-geometry:\n\nSlice Plots and Projection Plots in Spherical Geometry\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhat to expect when plotting data in spherical geometry? Here we explain\nthe notations and projections system yt uses for to render 2D images of\nspherical data.\n\nThe native spherical coordinates are\n\n- the spherical radius :math:`r`\n- the colatitude :math:`\\theta`, defined between :math:`0` and :math:`\\pi`\n- the azimuth :math:`\\varphi`, defined between :math:`0` and :math:`2\\pi`\n\n:math:`\\varphi`-normal slices are represented in the poloidal plane, with axes :math:`R, z`, where\n\n- :math:`R = r \\sin \\theta` is the cylindrical radius\n- :math:`z = r \\cos \\theta` is the elevation\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load_sample(\"KeplerianDisk\", unit_system=\"cgs\")\n   slc = yt.SlicePlot(ds, \"phi\", (\"gas\", \"density\"))\n   slc.save()\n\n:math:`\\theta`-normal slices are represented in a\n:math:`x/\\sin(\\theta)` VS :math:`y/\\sin(\\theta)` plane, where\n\n- :math:`x = R \\cos \\varphi`\n- :math:`y = R \\sin \\varphi`\n\nare the cartesian plane coordinates\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load_sample(\"KeplerianDisk\", unit_system=\"cgs\")\n   slc = yt.SlicePlot(ds, \"theta\", (\"gas\", \"density\"))\n   slc.save()\n\n\nFinally, :math:`r`-normal slices are represented following a\n`Aitoff-Hammer projection <http://paulbourke.net/geometry/transformationprojection/>`_\n\nWe denote\n\n- the latitude :math:`\\bar\\theta = \\frac{\\pi}{2} - \\theta`\n- the longitude :math:`\\lambda = \\varphi - \\pi`\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load_sample(\"KeplerianDisk\", unit_system=\"cgs\")\n   slc = yt.SlicePlot(ds, \"r\", (\"gas\", \"density\"))\n   slc.save()\n\n\n.. _unstructured-mesh-slices:\n\nUnstructured Mesh Slices\n------------------------\n\nUnstructured Mesh datasets can be sliced using the same syntax as above.\nHere is an example script using a publicly available MOOSE dataset:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n   sl = yt.SlicePlot(ds, \"x\", (\"connect1\", \"diffused\"))\n   sl.zoom(0.75)\n   sl.save()\n\nHere, we plot the ``'diffused'`` variable, using a slice normal to the ``'x'`` direction,\nthrough the meshed labelled by ``'connect1'``. By default, the slice goes through the\ncenter of the domain. We have also zoomed out a bit to get a better view of the\nresulting structure. To instead plot the ``'convected'`` variable, using a slice normal\nto the ``'z'`` direction through the mesh labelled by ``'connect2'``, we do:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n   sl = yt.SlicePlot(ds, \"z\", (\"connect2\", \"convected\"))\n   sl.zoom(0.75)\n   sl.save()\n\nThese slices are made by sampling the finite element solution at the points corresponding\nto each pixel of the image. The ``'convected'`` and ``'diffused'`` variables are node-centered,\nso this interpolation is performed by converting the sample point the reference coordinate\nsystem of the element and evaluating the appropriate shape functions. You can also\nplot element-centered fields:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n   sl = yt.SlicePlot(ds, \"y\", (\"connect1\", \"conv_indicator\"))\n   sl.zoom(0.75)\n   sl.save()\n\nWe can also annotate the mesh lines, as follows:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n   sl = yt.SlicePlot(ds, \"z\", (\"connect1\", \"diffused\"))\n   sl.annotate_mesh_lines(color=\"black\")\n   sl.zoom(0.75)\n   sl.save()\n\nThe ``plot_args`` parameter is a dictionary of keyword arguments that will be passed\nto matplotlib. It can be used to control the mesh line color, thickness, etc...\n\nThe above examples all involve 8-node hexahedral mesh elements. Here is another example from\na dataset that uses 6-node wedge elements:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/wedge_out.e\")\n   sl = yt.SlicePlot(ds, \"z\", (\"connect2\", \"diffused\"))\n   sl.save()\n\nSlices can also be used to examine 2D unstructured mesh datasets, but the\nslices must be taken to be normal to the ``'z'`` axis, or you'll get an error. Here is\nan example using another MOOSE dataset that uses triangular mesh elements:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/out.e\")\n   sl = yt.SlicePlot(ds, \"z\", (\"connect1\", \"nodal_aux\"))\n   sl.save()\n\nYou may run into situations where you have a variable you want to visualize that\nexists on multiple mesh blocks. To view the variable on ``all`` mesh blocks,\nsimply pass ``all`` as the first argument of the field tuple:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"MultiRegion/two_region_example_out.e\", step=-1)\n   sl = yt.SlicePlot(ds, \"z\", (\"all\", \"diffused\"))\n   sl.save()\n\n.. _particle-plotting-workarounds:\n\nAdditional Notes for Plotting Particle Data\n-------------------------------------------\n\nSince version 4.2.0, off-axis projections ares supported for non-SPH particle data.\nPrevious to that, this operation was only supported for SPH particles. Two historical\nworkaround methods were available for plotting non-SPH particles with off-axis\nprojections.\n\n1. :ref:`smooth-non-sph` - this method involves extracting particle data to be\n   reloaded with :class:`~yt.loaders.load_particles` and using the\n   :class:`~yt.frontends.stream.data_structures.StreamParticlesDataset.add_sph_fields`\n   function to create smoothing lengths. This works well for relatively small datasets,\n   but is not parallelized and may take too long for larger data.\n\n2. Plot from a saved\n   :class:`~yt.data_objects.construction_data_containers.YTCoveringGrid`,\n   :class:`~yt.data_objects.construction_data_containers.YTSmoothedCoveringGrid`,\n   or :class:`~yt.data_objects.construction_data_containers.YTArbitraryGrid`\n   dataset.\n\nThis second method is illustrated below. First, construct one of the grid data\nobjects listed above. Then, use the\n:class:`~yt.data_objects.data_containers.YTDataContainer.save_as_dataset`\nfunction (see :ref:`saving_data`) to save a deposited particle field\n(see :ref:`deposited-particle-fields`) as a reloadable dataset. This dataset\ncan then be loaded and visualized using both off-axis projections and slices.\nNote, the change in the field name from ``(\"deposit\", \"nbody_mass\")`` to\n``(\"grid\", \"nbody_mass\")`` after reloading.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"gizmo_cosmology_plus/snap_N128L16_132.hdf5\")\n   # create a 128^3 covering grid over the entire domain\n   L = 7\n   cg = ds.covering_grid(level=L, left_edge=ds.domain_left_edge, dims=[2**L]*3)\n\n   fn = cg.save_as_dataset(fields=[(\"deposit\", \"nbody_mass\")])\n   ds_grid = yt.load(fn)\n   p = yt.ProjectionPlot(ds_grid, [1, 1, 1], (\"grid\", \"nbody_mass\"))\n   p.save()\n\nPlot Customization: Recentering, Resizing, Colormaps, and More\n--------------------------------------------------------------\n\nYou can customize each of the four plot types above in identical ways.  We'll go\nover each of the customizations methods below.  For each of the examples below we\nwill modify the following plot.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.save()\n\nPanning and zooming\n~~~~~~~~~~~~~~~~~~~\n\nThere are three methods to dynamically pan around the data.\n\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.pan` accepts x and y\ndeltas.\n\n.. python-script::\n\n   import yt\n   from yt.units import kpc\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.pan((2 * kpc, 2 * kpc))\n   slc.save()\n\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.pan_rel` accepts deltas\nin units relative to the field of view of the plot.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.pan_rel((0.1, -0.1))\n   slc.save()\n\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.zoom` accepts a factor to zoom in by.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.zoom(2)\n   slc.save()\n\nSet axes units\n~~~~~~~~~~~~~~\n\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_axes_unit` allows the customization of\nthe axes unit labels.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_axes_unit(\"Mpc\")\n   slc.save()\n\nThe same result could have been accomplished by explicitly setting the ``width``\nto ``(.01, 'Mpc')``.\n\n\n.. _set-image-units:\n\nSet image units\n~~~~~~~~~~~~~~~\n\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_axes_unit` allows\nthe customization of the units used for the image and colorbar.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_unit((\"gas\", \"density\"), \"Msun/pc**3\")\n   slc.save()\n\nIf the unit you would like to convert to needs an equivalency, this can be\nspecified via the ``equivalency`` keyword argument of ``set_unit``. For\nexample, let's make a plot of the temperature field, but present it using\nan energy unit instead of a temperature unit:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"temperature\"), width=(10, \"kpc\"))\n   slc.set_unit((\"gas\", \"temperature\"), \"keV\", equivalency=\"thermal\")\n   slc.save()\n\nSet the plot center\n~~~~~~~~~~~~~~~~~~~\n\nThe :meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_center`\nfunction accepts a new center for the plot, in code units.  New centers must be\ntwo element tuples.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_center((0.5, 0.503))\n   slc.save()\n\nAdjusting the plot view axes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThere are a number of ways in which the initial orientation of a :class:`~yt.visualization.plot_window.PlotWindow`\nobject can be adjusted.\n\nThe first two axis orientation modifications,\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.flip_horizontal`\nand :meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.flip_vertical`, are\nequivalent to the ``invert_xaxis`` and ``invert_yaxis`` of matplotlib ``Axes``\nobjects. ``flip_horizontal`` will invert the plot's x-axis while the :meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.flip_vertical` method\nwill invert the plot's y-axis\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   # slicing with standard view (right-handed)\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"velocity_x\"), width=(20, 'kpc'))\n   slc.annotate_title(\"Standard Horizontal (Right Handed)\")\n   slc.save(\"Standard.png\")\n\n   # flip the horizontal axis (not right handed)\n   slc.flip_horizontal()\n   slc.annotate_title(\"Horizontal Flipped (Not Right Handed)\")\n   slc.save(\"NotRightHanded.png\")\n\n   # flip the vertical axis\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"velocity_x\"), width=(20, 'kpc'))\n   slc.flip_vertical()\n   slc.annotate_title(\"Flipped vertical\")\n   slc.save(\"FlippedVertical.png\")\n\nIn addition to inverting the direction of each axis,\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.swap_axes` will exchange\nthe plot's vertical and horizontal axes:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   # slicing with non right-handed coordinates\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"velocity_x\"), width=(20, 'kpc'))\n   slc.swap_axes()\n   slc.annotate_title(\"Swapped axes\")\n   slc.save(\"SwappedAxes.png\")\n\n   # toggle swap_axes (return to standard view)\n   slc.swap_axes()\n   slc.annotate_title(\"Standard Axes\")\n   slc.save(\"StandardAxes.png\")\n\nWhen using the ``flip_horizontal`` and ``flip_vertical`` with ``swap_axes``, it\nis important to remember that any ``flip_horizontal`` and ``flip_vertical``\noperations are applied to the image axes (not underlying dataset coordinates)\nafter any ``swap_axes`` calls, regardless of the order in which the callbacks\nare added. Also note that when using ``swap_axes``, any plot modifications\nrelating to limits, image width or resolution should still be supplied in reference\nto the standard (unswapped) orientation rather than the swapped view.\n\nFinally, it's worth mentioning that these three methods can be used in combination\nto rotate the view:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   # initial view\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"velocity_x\"), width=(20, 'kpc'))\n   slc.save(\"InitialOrientation.png\")\n   slc.annotate_title(\"Initial View\")\n\n   # swap + vertical flip = rotate 90 degree rotation (clockwise)\n   slc.swap_axes()\n   slc.flip_vertical()\n   slc.annotate_title(\"90 Degree Clockwise Rotation\")\n   slc.save(\"SwappedAxes90CW.png\")\n\n   # vertical flip + horizontal flip = rotate 180 degree rotation\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"velocity_x\"), width=(20, 'kpc'))\n   slc.flip_horizontal()\n   slc.flip_vertical()\n   slc.annotate_title(\"180 Degree Rotation\")\n   slc.save(\"FlipAxes180.png\")\n\n   # swap + horizontal flip = rotate 90 degree rotation (counter clockwise)\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"velocity_x\"), width=(20, 'kpc'))\n   slc.swap_axes()\n   slc.flip_horizontal()\n   slc.annotate_title(\"90 Degree Counter Clockwise Rotation\")\n   slc.save(\"SwappedAxes90CCW.png\")\n\n.. _hiding-colorbar-and-axes:\n\nHiding the Colorbar and Axis Labels\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe :class:`~yt.visualization.plot_window.PlotWindow` class has functions\nattached for hiding/showing the colorbar and axes.  This allows for making\nminimal plots that focus on the data:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.hide_colorbar()\n   slc.hide_axes()\n   slc.save()\n\nSee the cookbook recipe :ref:`show-hide-axes-colorbar` and the full function\ndescription :class:`~yt.visualization.plot_window.PlotWindow` for more\ninformation.\n\nFonts\n~~~~~\n\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_font` allows font\ncustomization.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_font({\"family\": \"sans-serif\", \"style\": \"italic\", \"weight\": \"bold\", \"size\": 24})\n   slc.save()\n\nColormaps\n~~~~~~~~~\n\nEach of these functions accepts at least two arguments.  In all cases the first argument\nis a field name.  This makes it possible to use different custom colormaps for\ndifferent fields tracked by the plot object.\n\nTo change the colormap for the plot, call the\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_cmap` function.\nUse any of the colormaps listed in the :ref:`colormaps` section.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_cmap((\"gas\", \"density\"), \"RdBu_r\")\n   slc.save()\n\nColorbar Normalization / Scaling\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nFor a general introduction to the topic of colorbar scaling, see\n`<https://matplotlib.org/stable/tutorials/colors/colormapnorms.html>`_. Here we\nwill focus on the defaults, and the ways to customize them, of yt plot classes.\nIn this section, \"norm\" is used as short for \"normalization\", and is\ninterchangeable with \"scaling\".\n\nMap-like plots e.g., ``SlicePlot``, ``ProjectionPlot`` and ``PhasePlot``,\ndefault to `logarithmic (log)\n<https://matplotlib.org/stable/tutorials/colors/colormapnorms.html#logarithmic>`_\nnormalization when all values are strictly positive, and `symmetric log (symlog)\n<https://matplotlib.org/stable/tutorials/colors/colormapnorms.html#symmetric-logarithmic>`_\notherwise. yt supports two different interfaces to move away from the defaults.\nSee **constrained norms** and **arbitrary norm** hereafter.\n\n.. note:: defaults can be configured on a per-field basis, see :ref:`per-field-plotconfig`\n\n**Constrained norms**\n\nThe standard way to change colorbar scalings between linear, log, and symmetric\nlog (symlog).  Colorbar properties can be constrained via two methods:\n\n- :meth:`~yt.visualization.plot_container.PlotContainer.set_zlim` controls the limits\n  of the colorbar range: ``zmin`` and ``zmax``.\n- :meth:`~yt.visualization.plot_container.ImagePlotContainer.set_log` allows switching to\n  linear or symlog normalization. With symlog, the linear threshold can be set\n  explicitly. Otherwise, yt will dynamically determine a reasonable value.\n\nUse the :meth:`~yt.visualization.plot_container.PlotContainer.set_zlim`\nmethod to set a custom colormap range.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_zlim((\"gas\", \"density\"), zmin=(1e-30, \"g/cm**3\"), zmax=(1e-25, \"g/cm**3\"))\n   slc.save()\n\nUnits can be left out, in which case they implicitly match the current display\nunits of the colorbar (controlled with the ``set_unit`` method, see\n:ref:`set-image-units`).\n\nIt is not required to specify both ``zmin`` and ``zmax``. Left unset, they will\ndefault to the extreme values in the current view. This default behavior can be\nenforced or restored by passing ``zmin=\"min\"`` (reps. ``zmax=\"max\"``)\nexplicitly.\n\n\n:meth:`~yt.visualization.plot_container.ImagePlotContainer.set_log` takes a boolean argument\nto select log (``True``) or linear (``False``) scalings.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_log((\"gas\", \"density\"), False) # switch to linear scaling\n   slc.save()\n\nOne can switch to `symlog\n<https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.SymLogNorm.html?highlight=symlog#matplotlib.colors.SymLogNorm>`_\nby providing a \"linear threshold\" (``linthresh``) value.\nWith ``linthresh=\"auto\"`` yt will switch to symlog norm and guess an appropriate value\nautomatically, with different behavior depending on the dynamic range of the data.\nWhen the dynamic range of the symlog scale is less than 15 orders of magnitude, the\nlinthresh value will be the minimum absolute nonzero value, as in\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load_sample(\"IsolatedGalaxy\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_log((\"gas\", \"density\"), linthresh=\"auto\")\n   slc.save()\n\nWhen the dynamic range of the symlog scale exceeds 15 orders of magnitude, the\nlinthresh value is calculated as 1/10\\ :sup:`15` of the maximum nonzero value in\norder to avoid possible floating point precision issues. The following plot\ntriggers the dynamic range cutoff\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load_sample(\"FIRE_M12i_ref11\")\n   p = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\"), width=(30, \"Mpc\"))\n   p.set_log((\"gas\", \"density\"), linthresh=\"auto\")\n   p.save()\n\nIn the previous example, it is actually safe to expand the dynamic range and in\nother cases you may find that the selected linear threshold is not well suited to\nyour dataset. To pass an explicit value instead\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load_sample(\"FIRE_M12i_ref11\")\n   p = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\"), width=(30, \"Mpc\"))\n   p.set_log((\"gas\", \"density\"), linthresh=(1e-22, \"g/cm**2\"))\n   p.save()\n\nSimilar to the ``zmin`` and ``zmax`` arguments of the ``set_zlim`` method, units\ncan be left out in ``linthresh``.\n\n\n**Arbitrary norms**\n\nAlternatively, arbitrary `matplotlib norms\n<https://matplotlib.org/stable/tutorials/colors/colormapnorms.html>`_ can be\npassed via the :meth:`~yt.visualization.plot_container.PlotContainer.set_norm`\nmethod. In that case, any numeric value is treated as having implicit units,\nmatching the current display units. This alternative interface is more flexible,\nbut considered experimental as of yt 4.1. Don't forget that with great power\ncomes great responsibility.\n\n\n.. python-script::\n\n   import yt\n   from matplotlib.colors import TwoSlopeNorm\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"velocity_x\"), width=(30, \"kpc\"))\n   slc.set_norm((\"gas\", \"velocity_x\"), TwoSlopeNorm(vcenter=0))\n\n   # using a diverging colormap to emphasize that vcenter corresponds to the\n   # middle value in the color range\n   slc.set_cmap((\"gas\", \"velocity_x\"), \"RdBu\")\n   slc.save()\n\n.. note:: When calling\n  :meth:`~yt.visualization.plot_container.PlotContainer.set_norm`, any constraints\n  previously set with\n  :meth:`~yt.visualization.plot_container.PlotContainer.set_log` or\n  :meth:`~yt.visualization.plot_container.PlotContainer.set_zlim` will be dropped.\n  Conversely, calling ``set_log`` or ``set_zlim`` will have the\n  effect of dropping any norm previously set via ``set_norm``.\n\n\nThe :meth:`~yt.visualization.plot_container.ImagePlotContainer.set_background_color`\nfunction accepts a field name and a color (optional). If color is given, the function\nwill set the plot's background color to that. If not, it will set it to the bottom\nvalue of the color map.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(1.5, \"Mpc\"))\n   slc.set_background_color((\"gas\", \"density\"))\n   slc.save(\"bottom_colormap_background\")\n   slc.set_background_color((\"gas\", \"density\"), color=\"black\")\n   slc.save(\"black_background\")\n\n\nAnnotations\n~~~~~~~~~~~\n\nA slice object can also add annotations like a title, an overlying\nquiver plot, the location of grid boundaries, halo-finder annotations,\nand many other annotations, including user-customizable annotations.\nFor example:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.annotate_grids()\n   slc.save()\n\nwill plot the density field in a 10 kiloparsec slice through the\nz-axis centered on the highest density point in the simulation domain.\nBefore saving the plot, the script annotates it with the grid\nboundaries, which are drawn as lines in the plot, with colors going\nfrom black to white depending on the AMR level of the grid.\n\nAnnotations are described in :ref:`callbacks`.\n\nSet the size and resolution of the plot\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTo set the size of the plot, use the\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_figure_size` function.  The argument\nis the size of the longest edge of the plot in inches.  View the full resolution\nimage to see the difference more clearly.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_figure_size(10)\n   slc.save()\n\nTo change the resolution of the image, call the\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_buff_size` function.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_buff_size(1600)\n   slc.save()\n\nAlso see cookbook recipe :ref:`image-resolution-primer` for more information\nabout the parameters that determine the resolution of your images.\n\nTurning off minorticks\n~~~~~~~~~~~~~~~~~~~~~~\n\nBy default minorticks for the x and y axes are turned on.\nThe minorticks may be removed using the\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_minorticks`\nfunction, which either accepts a specific field name including the 'all' alias\nand the desired state for the plot as 'on' or 'off'. There is also an analogous\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_colorbar_minorticks`\nfunction for the colorbar axis.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"), width=(10, \"kpc\"))\n   slc.set_minorticks(\"all\", False)\n   slc.set_colorbar_minorticks(\"all\", False)\n   slc.save()\n\n\n.. _matplotlib-customization:\n\nFurther customization via matplotlib\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nEach :class:`~yt.visualization.plot_window.PlotWindow` object is really a\ncontainer for plots - one plot for each field specified in the list of fields\nsupplied when the plot object is created. The individual plots can be\naccessed via the ``plots`` dictionary attached to each\n:class:`~yt.visualization.plot_window.PlotWindow` object:\n\n.. code-block:: python\n\n    slc = SlicePlot(ds, 2, [(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n    dens_plot = slc.plots[\"gas\", \"density\"]\n\nIn this example ``dens_plot`` is an instance of\n:class:`~yt.visualization.plot_window.WindowPlotMPL`, an object that wraps the\nmatplotlib\n`figure <https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure>`_\nand `axes <https://matplotlib.org/stable/api/axes_api.html#matplotlib.axes.Axes>`_\nobjects.  We can access these matplotlib primitives via attributes of\n``dens_plot``.\n\n.. code-block:: python\n\n    figure = dens_plot.figure\n    axes = dens_plot.axes\n    colorbar_axes = dens_plot.cax\n\nThese are the\n`figure <https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure>`_\nand `axes <https://matplotlib.org/stable/api/axes_api.html#matplotlib.axes.Axes>`_\nobjects that control the actual drawing of the plot.  Arbitrary plot\ncustomizations are possible by manipulating these objects.  See\n:ref:`matplotlib-primitives` for an example.\n\n.. _how-to-make-1d-profiles:\n\n1D Profile Plots\n----------------\n\n1D profiles are used to calculate the average or the sum of a given quantity\nwith respect to a second quantity.  Two common examples are the \"average density\nas a function of radius\" or \"the total mass within a given set of density bins.\"\nWhen created, they default to the average: in fact, they default to the average\nas weighted by the total cell mass.  However, this can be modified to take\neither the total value or the average with respect to a different quantity.\n\nProfiles operate on :ref:`data objects <data-objects>`; they will take the\nentire data contained in a sphere, a prism, an extracted region and so on, and\nthey will calculate and use that as input to their calculation.  To make a 1D\nprofile plot, create a (:class:`~yt.visualization.profile_plotter.ProfilePlot`)\nobject, supplying the data object, the field for binning, and a list of fields\nto be profiled.\n\n.. python-script::\n\n   import yt\n   from yt.units import kpc\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   my_galaxy = ds.disk(ds.domain_center, [0.0, 0.0, 1.0], 10 * kpc, 3 * kpc)\n   plot = yt.ProfilePlot(my_galaxy, (\"gas\", \"density\"), [(\"gas\", \"temperature\")])\n   plot.save()\n\nThis will create a :class:`~yt.data_objects.selection_data_containers.YTDisk`\ncentered at [0.5, 0.5, 0.5], with a normal vector of [0.0, 0.0, 1.0], radius of\n10 kiloparsecs and height of 3 kiloparsecs and will then make a plot of the\nmass-weighted average temperature as a function of density for all of the gas\ncontained in the cylinder.\n\nWe could also have made a profile considering only the gas in a sphere.\nFor instance:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   my_sphere = ds.sphere([0.5, 0.5, 0.5], (100, \"kpc\"))\n   plot = yt.ProfilePlot(my_sphere, (\"gas\", \"temperature\"), [(\"gas\", \"mass\")], weight_field=None)\n   plot.save()\n\nNote that because we have specified the weighting field to be ``None``, the\nprofile plot will display the accumulated cell mass as a function of temperature\nrather than the average. Also note the use of a ``(value, unit)`` tuple. These\ncan be used interchangeably with units explicitly imported from ``yt.units`` when\ncreating yt plots.\n\nWe can also accumulate along the bin field of a ``ProfilePlot`` (the bin field\nis the x-axis in a ``ProfilePlot``, in the last example the bin field is\n``Temperature``) by setting the ``accumulation`` keyword argument to ``True``.\nThe following example uses ``weight_field = None`` and ``accumulation = True`` to\ngenerate a plot of the enclosed mass in a sphere:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   my_sphere = ds.sphere([0.5, 0.5, 0.5], (100, \"kpc\"))\n   plot = yt.ProfilePlot(\n       my_sphere, \"radius\", [(\"gas\", \"mass\")], weight_field=None, accumulation=True\n   )\n   plot.save()\n\nNotably, above we have specified the field tuple for the mass, but not for the\n``radius`` field.  The ``radius`` field will not be ambiguous, but if you want\nto ensure that it refers to the radius of the cells on which the \"gas\" field\ntype is defined, you can specify it using the field tuple ``(\"index\",\n\"radius\")``.\n\nYou can also access the data generated by profiles directly, which can be\nuseful for overplotting average quantities on top of phase plots, or for\nexporting and plotting multiple profiles simultaneously from a time series.\nThe ``profiles`` attribute contains a list of all profiles that have been\nmade.  For each item in the list, the x field data can be accessed with ``x``.\nThe profiled fields can be accessed from the dictionary ``field_data``.\n\n.. code-block:: python\n\n   plot = ProfilePlot(\n       my_sphere, (\"gas\", \"temperature\"), [(\"gas\", \"mass\")], weight_field=None\n   )\n   profile = plot.profiles[0]\n   # print the bin field, in this case temperature\n   print(profile.x)\n   # print the profiled mass field\n   print(profile[\"gas\", \"mass\"])\n\nOther options, such as the number of bins, are also configurable. See the\ndocumentation for :class:`~yt.visualization.profile_plotter.ProfilePlot` for\nmore information.\n\nOverplotting Multiple 1D Profiles\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIt is often desirable to overplot multiple 1D profile to show evolution\nwith time.  This is supported with the ``from_profiles`` class method.\n1D profiles are created with the :func:`~yt.data_objects.profiles.create_profile`\nmethod and then given to the ProfilePlot object.\n\n.. python-script::\n\n   import yt\n\n   # Create a time-series object.\n   es = yt.load_simulation(\"enzo_tiny_cosmology/32Mpc_32.enzo\", \"Enzo\")\n   es.get_time_series(redshifts=[5, 4, 3, 2, 1, 0])\n\n   # Lists to hold profiles, labels, and plot specifications.\n   profiles = []\n   labels = []\n\n   # Loop over each dataset in the time-series.\n   for ds in es:\n       # Create a data container to hold the whole dataset.\n       ad = ds.all_data()\n       # Create a 1d profile of density vs. temperature.\n       profiles.append(\n           yt.create_profile(\n               ad,\n               [(\"gas\", \"temperature\")],\n               fields=[(\"gas\", \"mass\")],\n               weight_field=None,\n               accumulation=True,\n           )\n       )\n\n       # Add labels\n       labels.append(\"z = %.2f\" % ds.current_redshift)\n\n   # Create the profile plot from the list of profiles.\n   plot = yt.ProfilePlot.from_profiles(profiles, labels=labels)\n\n   # Save the image.\n   plot.save()\n\n\nCustomizing axis limits\n~~~~~~~~~~~~~~~~~~~~~~~\n\nBy default the x and y limits for ``ProfilePlot`` are determined using the\n:class:`~yt.data_objects.derived_quantities.Extrema` derived quantity.  If you\nwant to create a plot with custom axis limits, you have two options.\n\nFirst, you can create a custom profile object using\n:func:`~yt.data_objects.profiles.create_profile`.\nThis function accepts a dictionary of ``(max, min)`` tuples keyed to field names.\n\n.. python-script::\n\n    import yt\n    import yt.units as u\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    sp = ds.sphere(\"m\", 10 * u.kpc)\n    profiles = yt.create_profile(\n        sp,\n        (\"gas\", \"temperature\"),\n        (\"gas\", \"density\"),\n        weight_field=None,\n        extrema={(\"gas\", \"temperature\"): (1e3, 1e7), (\"gas\", \"density\"): (1e-26, 1e-22)},\n    )\n    plot = yt.ProfilePlot.from_profiles(profiles)\n    plot.save()\n\nYou can also make use of the\n:meth:`~yt.visualization.profile_plotter.ProfilePlot.set_xlim` and\n:meth:`~yt.visualization.profile_plotter.ProfilePlot.set_ylim` functions to\ncustomize the axes limits of a plot that has already been created.  Note that\ncalling ``set_xlim`` is much slower than calling ``set_ylim``.  This is because\n``set_xlim`` must recreate the profile object using the specified extrema.\nCreating a profile directly via :func:`~yt.data_objects.profiles.create_profile`\nmight be significantly faster.\nNote that since there is only one bin field, ``set_xlim``\ndoes not accept a field name as the first argument.\n\n.. python-script::\n\n   import yt\n   import yt.units as u\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   sp = ds.sphere(\"m\", 10 * u.kpc)\n   plot = yt.ProfilePlot(sp, (\"gas\", \"temperature\"), (\"gas\", \"density\"), weight_field=None)\n   plot.set_xlim(1e3, 1e7)\n   plot.set_ylim((\"gas\", \"density\"), 1e-26, 1e-22)\n   plot.save()\n\n\nCustomizing Units\n~~~~~~~~~~~~~~~~~\n\nUnits for both the x and y axis can be controlled via the\n:meth:`~yt.visualization.profile_plotter.ProfilePlot.set_unit` method.\nAdjusting the plot units does not require recreating the histogram, so adjusting\nunits will always be inexpensive, requiring only an in-place unit conversion.\n\nIn the following example we create a plot of the average density in solar\nmasses per cubic parsec as a function of radius in kiloparsecs.\n\n.. python-script::\n\n    import yt\n    import yt.units as u\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    sp = ds.sphere(\"m\", 10 * u.kpc)\n    plot = yt.ProfilePlot(sp, \"radius\", (\"gas\", \"density\"), weight_field=None)\n    plot.set_unit((\"gas\", \"density\"), \"msun/pc**3\")\n    plot.set_unit(\"radius\", \"kpc\")\n    plot.save()\n\nLinear and Logarithmic Scaling\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe axis scaling can be manipulated via the\n:meth:`~yt.visualization.profile_plotter.ProfilePlot.set_log` function.  This\nfunction accepts a field name and a boolean.  If the boolean is ``True``, the\nfield is plotted in log scale.  If ``False``, the field is plotted in linear\nscale.\n\nIn the following example we create a plot of the average x velocity as a\nfunction of radius.  Since the x component of the velocity vector can be\nnegative, we set the scaling to be linear for this field.\n\n.. python-script::\n\n   import yt\n   import yt.units as u\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   sp = ds.sphere(\"m\", 10 * u.kpc)\n   plot = yt.ProfilePlot(sp, \"radius\", (\"gas\", \"velocity_x\"), weight_field=None)\n   plot.set_log((\"gas\", \"velocity_x\"), False)\n   plot.save()\n\nSetting axis labels\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe axis labels can be manipulated via the\n:meth:`~yt.visualization.profile_plotter.ProfilePlot.set_ylabel` and\n:meth:`~yt.visualization.profile_plotter.ProfilePlot.set_xlabel` functions.  The\n:meth:`~yt.visualization.profile_plotter.ProfilePlot.set_ylabel` function accepts a field name\nand a string with the desired label. The :meth:`~yt.visualization.profile_plotter.ProfilePlot.set_xlabel`\nfunction just accepts the desired label and applies this to all of the plots.\n\nIn the following example we create a plot of the average x-velocity and density as a\nfunction of radius. The xlabel is set to \"Radius\", for all plots, and the ylabel is set to\n\"velocity in x direction\" for the x-velocity plot.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n   ad = ds.all_data()\n   plot = yt.ProfilePlot(ad, \"radius\", [(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")], weight_field=None)\n   plot.set_xlabel(\"Radius\")\n   plot.set_ylabel((\"gas\", \"velocity_x\"), \"velocity in x direction\")\n   plot.save()\n\nAdding plot title\n~~~~~~~~~~~~~~~~~\n\nPlot title can be set via the\n:meth:`~yt.visualization.profile_plotter.ProfilePlot.annotate_title` function.\nIt accepts a string argument which is the plot title and an optional ``field`` parameter which specifies\nthe field for which plot title should be added. ``field`` could be a string or a list of string.\nIf ``field`` is not passed, plot title will be added for the fields.\n\nIn the following example we create a plot and set the plot title.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n   ad = ds.all_data()\n   plot = yt.ProfilePlot(ad, (\"gas\", \"density\"), [(\"gas\", \"temperature\")], weight_field=None)\n   plot.annotate_title(\"Temperature vs Density Plot\")\n   plot.save()\n\nAnother example where we create plots from profile. By specifying the fields we can add plot title to a\nspecific plot.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n   sphere = ds.sphere(\"max\", (1.0, \"Mpc\"))\n   profiles = []\n   profiles.append(yt.create_profile(sphere, [\"radius\"], fields=[(\"gas\", \"density\")], n_bins=64))\n   profiles.append(\n       yt.create_profile(sphere, [\"radius\"], fields=[\"dark_matter_density\"], n_bins=64)\n   )\n   plot = yt.ProfilePlot.from_profiles(profiles)\n   plot.annotate_title(\"Plot Title: Density\", (\"gas\", \"density\"))\n   plot.annotate_title(\"Plot Title: Dark Matter Density\", \"dark_matter_density\")\n   plot.save()\n\nHere, ``plot.annotate_title(\"Plot Title: Density\", (\"gas\", \"density\"))`` will only set the plot title for the ``\"density\"``\nfield. Thus, allowing us the option to have different plot titles for different fields.\n\n\nAnnotating plot with text\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nPlots can be annotated at a desired (x,y) coordinate using :meth:`~yt.visualization.profile_plotter.ProfilePlot.annotate_text` function.\nThis function accepts the x-position, y-position, a text string to\nbe annotated in the plot area, and an optional list of fields for annotating plots with the specified field.\nFurthermore, any keyword argument accepted by the matplotlib ``axes.text`` function could also be passed which will can be useful to change fontsize, text-alignment, text-color or other such properties of annotated text.\n\nIn the following example we create a plot and add a simple annotation.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n   ad = ds.all_data()\n   plot = yt.ProfilePlot(ad, (\"gas\", \"density\"), [(\"gas\", \"temperature\")], weight_field=None)\n   plot.annotate_text(1e-30, 1e7, \"Annotated Text\")\n   plot.save()\n\nTo add annotations to a particular set of fields we need to pass in the list of fields as follows,\nwhere ``\"ftype1\"`` and ``\"ftype2\"`` are the field types (and may be the same):\n\n.. code-block:: python\n\n   plot.annotate_text(\n       1e-30, 1e7, \"Annotation\", [(\"ftype1\", \"field1\"), (\"ftype2\", \"field2\")]\n   )\n\n\nTo change the text annotated text properties, we need to pass the matplotlib ``axes.text`` arguments as follows:\n\n.. code-block:: python\n\n  plot.annotate_text(\n      1e-30,\n      1e7,\n      \"Annotation\",\n      fontsize=20,\n      bbox=dict(facecolor=\"red\", alpha=0.5),\n      horizontalalignment=\"center\",\n      verticalalignment=\"center\",\n  )\n\nThe above example will set the fontsize of annotation to 20, add a bounding box of red color and center align\nhorizontally and vertically. The is just an example to modify the text properties, for further options please check\n`matplotlib.axes.Axes.text <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.text.html>`_.\n\nAltering Line Properties\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nLine properties for any and all of the profiles can be changed with the\n:func:`~yt.visualization.profile_plotter.set_line_property` function.\nThe two arguments given are the line property and desired value.\n\n.. code-block:: python\n\n    plot.set_line_property(\"linestyle\", \"--\")\n\nWith no additional arguments, all of the lines plotted will be altered.  To\nchange the property of a single line, give also the index of the profile.\n\n.. code-block:: python\n\n    # change only the first line\n    plot.set_line_property(\"linestyle\", \"--\", 0)\n\n.. _how-to-1d-unstructured-mesh:\n\n1D Line Sampling\n----------------\n\nYT has the ability to sample datasets along arbitrary lines\nand plot the result. You must supply five arguments to the ``LinePlot``\nclass. They are enumerated below:\n\n1. Dataset\n2. A list of fields or a single field you wish to plot\n3. The starting point of the sampling line. This should be an n-element list, tuple,\n   ndarray, or YTArray with the elements corresponding to the coordinates of the\n   starting point. (n should equal the dimension of the dataset)\n4. The ending point of the sampling line. This should also be an n-element list, tuple,\n   ndarray, or YTArray with the elements corresponding to the coordinates of the\n   ending point.\n5. The number of sampling points along the line, e.g. if 1000 is specified, then\n   data will be sampled at 1000 points evenly spaced between the starting and\n   ending points.\n\nThe below code snippet illustrates how this is done:\n\n.. code-block:: python\n\n   ds = yt.load(\"SecondOrderTris/RZ_p_no_parts_do_nothing_bcs_cone_out.e\", step=-1)\n   plot = yt.LinePlot(ds, [(\"all\", \"v\"), (\"all\", \"u\")], (0, 0, 0), (0, 1, 0), 1000)\n   plot.save()\n\nIf working in a Jupyter Notebook, ``LinePlot`` also has the ``show()`` method.\n\nYou can add a legend to a 1D sampling plot. The legend process takes two steps:\n\n1. When instantiating the ``LinePlot``, pass a dictionary of\n   labels with keys corresponding to the field names\n2. Call the ``LinePlot`` ``annotate_legend`` method\n\nX- and Y- axis units can be set with ``set_x_unit`` and ``set_unit`` methods\nrespectively. The below code snippet combines all the features we've discussed:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n   plot = yt.LinePlot(ds, (\"gas\", \"density\"), [0, 0, 0], [1, 1, 1], 512)\n   plot.annotate_legend((\"gas\", \"density\"))\n   plot.set_x_unit(\"cm\")\n   plot.set_unit((\"gas\", \"density\"), \"kg/cm**3\")\n   plot.save()\n\nIf a list of fields is passed to ``LinePlot``, yt will create a number of\nindividual figures equal to the number of different dimensional\nquantities. E.g. if ``LinePlot`` receives two fields with units of \"length/time\"\nand a field with units of \"temperature\", two different figures will be created,\none with plots of the \"length/time\" fields and another with the plot of the\n\"temperature\" field. It is only necessary to call ``annotate_legend``\nfor one field of a multi-field plot to produce a legend containing all the\nlabels passed in the initial construction of the ``LinePlot`` instance. Example:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"SecondOrderTris/RZ_p_no_parts_do_nothing_bcs_cone_out.e\", step=-1)\n   plot = yt.LinePlot(\n       ds,\n       [(\"all\", \"v\"), (\"all\", \"u\")],\n       [0, 0, 0],\n       [0, 1, 0],\n       100,\n       field_labels={(\"all\", \"u\"): r\"v$_x$\", (\"all\", \"v\"): r\"v$_y$\"},\n   )\n   plot.annotate_legend((\"all\", \"u\"))\n   plot.save()\n\n``LinePlot`` is a bit different from yt ray objects which are data\ncontainers. ``LinePlot`` is a plotting class that may use yt ray objects to\nsupply field plotting information. However, perhaps the most important\ndifference to highlight between rays and ``LinePlot`` is that rays return data\nelements that intersect with the ray and make no guarantee about the spacing\nbetween data elements. ``LinePlot`` sampling points are guaranteed to be evenly\nspaced. In the case of cell data where multiple points fall within the same\ncell, the ``LinePlot`` object will show the same field value for each sampling\npoint that falls within the same cell.\n\n.. _how-to-make-2d-profiles:\n\n2D Phase Plots\n--------------\n\n2D phase plots function in much the same was as 1D phase plots, but with a\n:class:`~yt.visualization.profile_plotter.PhasePlot` object.  Much like 1D\nprofiles, 2D profiles (phase plots) are best thought of as plotting a\ndistribution of points, either taking the average or the accumulation in a bin.\nThe default behavior is to average, using the cell mass as the weighting,\nbut this behavior can be controlled through the ``weight_field`` parameter.\nFor example, to generate a 2D distribution of mass enclosed in density and\ntemperature bins, you can do:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   my_sphere = ds.sphere(\"c\", (50, \"kpc\"))\n   plot = yt.PhasePlot(\n       my_sphere, (\"gas\", \"density\"), (\"gas\", \"temperature\"), [(\"gas\", \"mass\")], weight_field=None\n   )\n   plot.save()\n\nIf you would rather see the average value of a field as a function of two other\nfields, leave off the ``weight_field`` argument, and it will average by\nthe cell mass.  This would look\nsomething like:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   my_sphere = ds.sphere(\"c\", (50, \"kpc\"))\n   plot = yt.PhasePlot(my_sphere, (\"gas\", \"density\"), (\"gas\", \"temperature\"), [(\"gas\", \"H_p0_fraction\")])\n   plot.save()\n\nCustomizing Phase Plots\n~~~~~~~~~~~~~~~~~~~~~~~\n\nSimilarly to 1D profile plots, :class:`~yt.visualization.profile_plotter.PhasePlot`\ncan be customized via ``set_unit``,\n``set_xlim``, ``set_ylim``, and ``set_zlim``.  The following example illustrates\nhow to manipulate these functions. :class:`~yt.visualization.profile_plotter.PhasePlot`\ncan also be customized in a similar manner as\n:class:`~yt.visualization.plot_window.SlicePlot`, such as with ``hide_colorbar``\nand ``show_colorbar``.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"sizmbhloz-clref04SNth-rs9_a0.9011/sizmbhloz-clref04SNth-rs9_a0.9011.art\")\n   center = ds.arr([64.0, 64.0, 64.0], \"code_length\")\n   rvir = ds.quan(1e-1, \"Mpccm/h\")\n   sph = ds.sphere(center, rvir)\n\n   plot = yt.PhasePlot(sph, (\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"mass\"), weight_field=None)\n   plot.set_unit((\"gas\", \"density\"), \"Msun/pc**3\")\n   plot.set_unit((\"gas\", \"mass\"), \"Msun\")\n   plot.set_xlim(1e-5, 1e1)\n   plot.set_ylim(1, 1e7)\n   plot.save()\n\nIt is also possible to construct a custom 2D profile object and then use the\n:meth:`~yt.visualization.profile_plotter.PhasePlot.from_profile` function to\ncreate a ``PhasePlot`` using the profile object.\nThis will sometimes be faster, especially if you need custom x and y axes\nlimits.  The following example illustrates this workflow:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"sizmbhloz-clref04SNth-rs9_a0.9011/sizmbhloz-clref04SNth-rs9_a0.9011.art\")\n   center = ds.arr([64.0, 64.0, 64.0], \"code_length\")\n   rvir = ds.quan(1e-1, \"Mpccm/h\")\n   sph = ds.sphere(center, rvir)\n   units = {(\"gas\", \"density\"): \"Msun/pc**3\", (\"gas\", \"mass\"): \"Msun\"}\n   extrema = {(\"gas\", \"density\"): (1e-5, 1e1), (\"gas\", \"temperature\"): (1, 1e7)}\n\n   profile = yt.create_profile(\n       sph,\n       [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n       n_bins=[128, 128],\n       fields=[(\"gas\", \"mass\")],\n       weight_field=None,\n       units=units,\n       extrema=extrema,\n   )\n\n   plot = yt.PhasePlot.from_profile(profile)\n\n   plot.save()\n\nProbability Distribution Functions and Accumulation\n---------------------------------------------------\n\nBoth 1D and 2D profiles which show the total of amount of some field, such as\nmass, in a bin (done by setting the ``weight_field`` keyword to ``None``) can be\nturned into probability distribution functions (PDFs) by setting the\n``fractional`` keyword to ``True``.  When set to ``True``, the value in each bin\nis divided by the sum total from all bins.  These can be turned into cumulative\ndistribution functions (CDFs) by setting the ``accumulation`` keyword to\n``True``.  This will make it so that the value in any bin N is the cumulative\nsum of all bins from 0 to N.  The direction of the summation can be reversed by\nsetting ``accumulation`` to ``-True``.  For ``PhasePlot``, the accumulation can\nbe set independently for each axis by setting ``accumulation`` to a list of\n``True``/ ``-True`` /``False`` values.\n\n.. _particle-plots:\n\nParticle Plots\n--------------\n\nSlice and projection plots both provide a callback for over-plotting particle\npositions onto gas fields. However, sometimes you want to plot the particle\nquantities by themselves, perhaps because the gas fields are not relevant to\nyour use case, or perhaps because your dataset doesn't contain any gas fields\nin the first place. Additionally, you may want to plot your particles with a\nthird field, such as particle mass or age,  mapped to a colorbar.\n:class:`~yt.visualization.particle_plots.ParticlePlot` provides a convenient\nway to do this in yt.\n\nThe easiest way to make a :class:`~yt.visualization.particle_plots.ParticlePlot`\nis to use the convenience routine. This has the syntax:\n\n.. code-block:: python\n\n   p = yt.ParticlePlot(ds, (\"all\", \"particle_position_x\"), (\"all\", \"particle_position_y\"))\n   p.save()\n\nHere, ``ds`` is a dataset we've previously opened. The commands create a particle\nplot that shows the x and y positions of all the particles in ``ds`` and save the\nresult to a file on the disk. The type of plot returned depends on the fields you\npass in; in this case, ``p`` will be an :class:`~yt.visualization.particle_plots.ParticleProjectionPlot`,\nbecause the fields are aligned to the coordinate system of the simulation.\nThe above example is equivalent to the following:\n\n.. code-block:: python\n\n   p = yt.ParticleProjectionPlot(ds, \"z\")\n   p.save()\n\nMost of the callbacks the work for slice and projection plots also work for\n:class:`~yt.visualization.particle_plots.ParticleProjectionPlot`.\nFor instance, we can zoom in:\n\n.. code-block:: python\n\n   p = yt.ParticlePlot(ds, (\"all\", \"particle_position_x\"), (\"all\", \"particle_position_y\"))\n   p.zoom(10)\n   p.save(\"zoom\")\n\nchange the width:\n\n.. code-block:: python\n\n   p.set_width((500, \"kpc\"))\n\nor change the axis units:\n\n.. code-block:: python\n\n   p.set_unit((\"all\", \"particle_position_x\"), \"Mpc\")\n\nHere is a full example that shows the simplest way to use\n:class:`~yt.visualization.particle_plots.ParticlePlot`:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ParticlePlot(ds, (\"all\", \"particle_position_x\"), (\"all\", \"particle_position_y\"))\n   p.save()\n\nIn the above examples, we are simply splatting particle x and y positions onto\na plot using some color. Colors can be applied to the plotted particles by\nproviding a ``z_field``, which will be summed along the line of sight in a manner\nsimilar to a projection.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ParticlePlot(ds, (\"all\", \"particle_position_x\"), (\"all\", \"particle_position_y\"), (\"all\", \"particle_mass\"))\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n   p.zoom(32)\n   p.save()\n\nAdditionally, a ``weight_field`` can be given such that the value in each\npixel is the weighted average along the line of sight.\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ParticlePlot(\n       ds,\n       (\"all\", \"particle_position_x\"),\n       (\"all\", \"particle_position_y\"),\n       (\"all\", \"particle_mass\"),\n       weight_field=(\"all\", \"particle_ones\"),\n   )\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n   p.zoom(32)\n   p.save()\n\nNote the difference in the above two plots. The first shows the\ntotal mass along the line of sight. The density is higher in the\ninner regions, and hence there are more particles and more mass along\nthe line of sight. The second plot shows the average mass per particle\nalong the line of sight. The inner region is dominated by low mass\nstar particles, whereas the outer region is comprised of higher mass\ndark matter particles.\n\nBoth :class:`~yt.visualization.particle_plots.ParticleProjectionPlot` and\n:class:`~yt.visualization.particle_plots.ParticlePhasePlot` objects\naccept a ``deposition`` argument which controls the order of the \"splatting\"\nof the particles onto the pixels in the plot. The default option, ``\"ngp\"``,\ncorresponds to the \"Nearest-Grid-Point\" (0th-order) method, which simply\nfinds the pixel the particle is located in and deposits 100% of the particle\nor its plotted quantity into that pixel. The other option, ``\"cic\"``,\ncorresponds to the \"Cloud-In-Cell\" (1st-order) method, which linearly\ninterpolates the particle or its plotted quantity into the four nearest\npixels in the plot.\n\nHere is a complete example that uses the ``particle_mass`` field\nto set the colorbar and shows off some of the modification functions for\n:class:`~yt.visualization.particle_plots.ParticleProjectionPlot`:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ParticlePlot(\n       ds,\n       (\"all\", \"particle_position_x\"),\n       (\"all\", \"particle_position_y\"),\n       (\"all\", \"particle_mass\"),\n       width=(0.5, 0.5),\n   )\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n   p.zoom(32)\n   p.annotate_title(\"Zoomed-in Particle Plot\")\n   p.save()\n\nIf the fields passed in to :class:`~yt.visualization.particle_plots.ParticlePlot`\ndo not correspond to a valid :class:`~yt.visualization.particle_plots.ParticleProjectionPlot`,\na :class:`~yt.visualization.particle_plots.ParticlePhasePlot` will be returned instead.\n:class:`~yt.visualization.particle_plots.ParticlePhasePlot` is used to plot arbitrary particle\nfields against each other, and do not support some of the callbacks available in\n:class:`~yt.visualization.particle_plots.ParticleProjectionPlot` -\nfor instance, :meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.pan` and\n:meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.zoom` don't make much sense when of your axes is a position\nand the other is a velocity. The modification functions defined for :class:`~yt.visualization.profile_plotter.PhasePlot`\nshould all work, however.\n\nHere is an example of making a :class:`~yt.visualization.particle_plots.ParticlePhasePlot`\nof ``particle_position_x`` versus ``particle_velocity_z``, with the ``particle_mass`` on the colorbar:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ParticlePlot(ds, (\"all\", \"particle_position_x\"), (\"all\", \"particle_velocity_z\"), (\"all\", \"particle_mass\"))\n   p.set_unit((\"all\", \"particle_position_x\"), \"Mpc\")\n   p.set_unit((\"all\", \"particle_velocity_z\"), \"km/s\")\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n   p.save()\n\nand here is one with the particle x and y velocities on the plot axes:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ParticlePlot(ds, (\"all\", \"particle_velocity_x\"), (\"all\", \"particle_velocity_y\"), (\"all\", \"particle_mass\"))\n   p.set_unit((\"all\", \"particle_velocity_x\"), \"km/s\")\n   p.set_unit((\"all\", \"particle_velocity_y\"), \"km/s\")\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n   p.set_ylim(-400, 400)\n   p.set_xlim(-400, 400)\n   p.save()\n\nIf you want more control over the details of the :class:`~yt.visualization.particle_plots.ParticleProjectionPlot` or\n:class:`~yt.visualization.particle_plots.ParticlePhasePlot`, you can always use these classes directly. For instance,\nhere is an example of using the ``depth`` argument to :class:`~yt.visualization.particle_plots.ParticleProjectionPlot`\nto only plot the particles that live in a thin slice around the center of the\ndomain:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n   p = yt.ParticleProjectionPlot(ds, 2, [(\"all\", \"particle_mass\")], width=(0.5, 0.5), depth=0.01)\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n   p.save()\n\nUsing :class:`~yt.visualization.particle_plots.ParticleProjectionPlot`, you can also plot particles\nalong an off-axis direction:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n   L = [1, 1, 1] # normal or \"line of sight\" vector\n   N = [0, 1, 0] # north or \"up\" vector\n\n    p = yt.ParticleProjectionPlot(\n        ds, L, [(\"all\", \"particle_mass\")], width=(0.05, 0.05), depth=0.3, north_vector=N\n    )\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n   p.save()\n\nHere is an example of using the ``data_source`` argument to :class:`~yt.visualization.particle_plots.ParticlePhasePlot`\nto only consider the particles that lie within a 50 kpc sphere around the domain center:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n   my_sphere = ds.sphere(\"c\", (50.0, \"kpc\"))\n\n   p = yt.ParticlePhasePlot(\n       my_sphere,\n       (\"all\", \"particle_velocity_x\"),\n       (\"all\", \"particle_velocity_y\"),\n       (\"all\", \"particle_mass\")\n   )\n   p.set_unit((\"all\", \"particle_velocity_x\"), \"km/s\")\n   p.set_unit((\"all\", \"particle_velocity_y\"), \"km/s\")\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n   p.set_ylim(-400, 400)\n   p.set_xlim(-400, 400)\n\n   p.save()\n\n:class:`~yt.visualization.particle_plots.ParticleProjectionPlot` objects also admit a ``density``\nflag, which allows one to plot the surface density of a projected quantity. This simply divides\nthe quantity in each pixel of the plot by the area of that pixel. It also changes the label on the\ncolorbar to reflect the new units and the fact that it is a density. This may make most sense in\nthe case of plotting the projected particle mass, in which case you can plot the projected particle\nmass density:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n   p = yt.ParticleProjectionPlot(ds, 2, [(\"all\", \"particle_mass\")], width=(0.5, 0.5), density=True)\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun/kpc**2\") # Note that the dimensions reflect the density flag\n   p.save()\n\nFinally, with 1D and 2D Profiles, you can create a :class:`~yt.data_objects.profiles.ParticleProfile`\nobject separately using the :func:`~yt.data_objects.profiles.create_profile` function, and then use it\ncreate a :class:`~yt.visualization.particle_plots.ParticlePhasePlot` object using the\n:meth:`~yt.visualization.particle_plots.ParticlePhasePlot.from_profile` method. In this example,\nwe have also used the ``weight_field`` argument to compute the average ``particle_mass`` in each\npixel, instead of the total:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n   ad = ds.all_data()\n\n   profile = yt.create_profile(\n       ad,\n       [(\"all\", \"particle_velocity_x\"), (\"all\", \"particle_velocity_y\")],\n       [(\"all\", \"particle_mass\")],\n       n_bins=800,\n       weight_field=(\"all\", \"particle_ones\"),\n   )\n\n   p = yt.ParticlePhasePlot.from_profile(profile)\n   p.set_unit((\"all\", \"particle_velocity_x\"), \"km/s\")\n   p.set_unit((\"all\", \"particle_velocity_y\"), \"km/s\")\n   p.set_unit((\"all\", \"particle_mass\"), \"Msun\")\n   p.set_ylim(-400, 400)\n   p.set_xlim(-400, 400)\n   p.save()\n\nUnder the hood, the :class:`~yt.data_objects.profiles.ParticleProfile` class works a lot like a\n:class:`~yt.data_objects.profiles.Profile2D` object, except that instead of just binning the\nparticle field, you can also use higher-order deposition functions like the cloud-in-cell\ninterpolant to spread out the particle quantities over a few cells in the profile. The\n:func:`~yt.data_objects.profiles.create_profile` will automatically detect when all the fields\nyou pass in are particle fields, and return a :class:`~yt.data_objects.profiles.ParticleProfile`\nif that is the case. For a complete description of the :class:`~yt.data_objects.profiles.ParticleProfile`\nclass please consult the reference documentation.\n\n.. _interactive-plotting:\n\nInteractive Plotting\n--------------------\n\nThe best way to interactively plot data is through the IPython notebook.  Many\ndetailed tutorials on using the IPython notebook can be found at\n:ref:`notebook-tutorial`. The simplest way to launch the notebook it is to\ntype:\n\n.. code-block:: bash\n\n   jupyter lab\n\nat the command line.  This will prompt you for a password (so that if you're on\na shared user machine no one else can pretend to be you!) and then spawn an\nIPython notebook you can connect to.\n\nIf you want to see yt plots inline inside your notebook, you need only create a\nplot and then call ``.show()`` and the image will appear inline:\n\n.. notebook-cell::\n\n   import yt\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   p = yt.ProjectionPlot(ds, \"z\", (\"gas\", \"density\"), center='m', width=(10,'kpc'),\n                      weight_field=(\"gas\", \"density\"))\n   p.set_figure_size(5)\n   p.show()\n\n.. _saving_plots:\n\nSaving Plots\n------------\n\nIf you want to save your yt plots, you have a couple of options for customizing\nthe plot filenames. If you don't care what the filenames are, just calling the\n``save`` method with no additional arguments usually suffices:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0100\")\n   slc = yt.SlicePlot(ds, \"z\", [(\"gas\", \"kT\"), (\"gas\", \"density\")], width=(500.0, \"kpc\"))\n   slc.save()\n\nwhich will yield PNG plots with the filenames\n\n.. code-block:: bash\n\n   $ ls \\*.png\n   sloshing_nomag2_hdf5_plt_cnt_0100_Slice_z_density.png\n   sloshing_nomag2_hdf5_plt_cnt_0100_Slice_z_kT.png\n\nwhich has a general form of\n\n.. code-block:: bash\n\n   [dataset name]_[plot type]_[axis]_[field name].[suffix]\n\nCalling ``save`` with a single argument or the ``name`` keyword argument\nspecifies an alternative name for the plot:\n\n.. code-block:: python\n\n   slc.save(\"bananas\")\n\nor\n\n.. code-block:: python\n\n   slc.save(name=\"bananas\")\n\nyields\n\n.. code-block:: bash\n\n   $ ls \\*.png\n   bananas_Slice_z_kT.png\n   bananas_Slice_z_density.png\n\nIf you call ``save`` with a full filename with a file suffix, the plot\nwill be saved with that filename:\n\n.. code-block:: python\n\n   slc.save(\"sloshing.png\")\n\nsince this will take any field and plot it with this filename, it is\ntypically only useful if you are plotting one field. If you want to\nsimply change the image format of the plotted file, use the ``suffix``\nkeyword:\n\n.. code-block:: python\n\n   slc.save(name=\"bananas\", suffix=\"eps\")\n\nyielding\n\n.. code-block:: bash\n\n   $ ls *.eps\n   bananas_Slice_z_kT.eps\n   bananas_Slice_z_density.eps\n\n.. _remaking-plots:\n\nRemaking Figures from Plot Datasets\n-----------------------------------\n\nWhen working with datasets that are too large to be stored locally,\nmaking figures just right can be cumbersome as it requires continuously\nmoving images somewhere they can be viewed.  However, image creation is\nactually a two step process of first creating the projection, slice,\nor profile object, and then converting that object into an actual image.\nFortunately, the hard part (creating slices, projections, profiles) can\nbe separated from the easy part (generating images).  The intermediate\nslice, projection, and profile objects can be saved as reloadable\ndatasets, then handed back to the plotting machinery discussed here.\n\nFor slices and projections, the saveable object is associated with the\nplot object as ``data_source``.  This can be saved with the\n:func:`~yt.data_objects.data_containers.YTDataContainer.save_as_dataset` function.  For\nmore information, see :ref:`saving_data`.\n\n.. code-block:: python\n\n   p = yt.ProjectionPlot(ds, \"x\", (\"gas\", \"density\"), weight_field=(\"gas\", \"density\"))\n   fn = p.data_source.save_as_dataset()\n\nThis function will optionally take a ``filename`` keyword that follows\nthe same logic as discussed above in :ref:`saving_plots`.  The filename\nto which the dataset was written will be returned.\n\nOnce saved, this file can be reloaded completely independently of the\noriginal dataset and given back to the plot function with the same\narguments.  One can now continue to tweak the figure to one's liking.\n\n.. code-block:: python\n\n   new_ds = yt.load(fn)\n   new_p = yt.ProjectionPlot(\n       new_ds, \"x\", (\"gas\", \"density\"), weight_field=(\"gas\", \"density\")\n   )\n   new_p.save()\n\nThe same functionality is available for profile and phase plots.  In\neach case, a special data container, ``data``, is given to the plotting\nfunctions.\n\nFor ``ProfilePlot``:\n\n.. code-block:: python\n\n   ad = ds.all_data()\n   p1 = yt.ProfilePlot(\n       ad, (\"gas\", \"density\"), (\"gas\", \"temperature\"), weight_field=(\"gas\", \"mass\")\n   )\n\n   # note that ProfilePlots can hold a list of profiles\n   fn = p1.profiles[0].save_as_dataset()\n\n   new_ds = yt.load(fn)\n   p2 = yt.ProfilePlot(\n       new_ds.data,\n       (\"gas\", \"density\"),\n       (\"gas\", \"temperature\"),\n       weight_field=(\"gas\", \"mass\"),\n   )\n   p2.save()\n\nFor ``PhasePlot``:\n\n.. code-block:: python\n\n   ad = ds.all_data()\n   p1 = yt.PhasePlot(\n       ad, (\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"mass\"), weight_field=None\n   )\n   fn = p1.profile.save_as_dataset()\n\n   new_ds = yt.load(fn)\n   p2 = yt.PhasePlot(\n       new_ds.data,\n       (\"gas\", \"density\"),\n       (\"gas\", \"temperature\"),\n       (\"gas\", \"mass\"),\n       weight_field=None,\n   )\n   p2.save()\n\n.. _eps-writer:\n\nPublication-ready Figures\n-------------------------\n\nWhile the routines above give a convenient method to inspect and\nvisualize your data, publishers often require figures to be in PDF or\nEPS format.  While the matplotlib supports vector graphics and image\ncompression in PDF formats, it does not support compression in EPS\nformats.  The :class:`~yt.visualization.eps_writer.DualEPS` module\nprovides an interface with the `PyX <https://pyx-project.org/>`_,\nwhich is a Python abstraction of the PostScript drawing model with a\nLaTeX interface.  It is optimal for publications to provide figures\nwith vector graphics to avoid rasterization of the lines and text,\nalong with compression to produce figures that do not have a large\nfilesize.\n\n.. note::\n   PyX must be installed, which can be accomplished either manually\n   with ``python -m pip install pyx``.\n\nThis module can take any of the plots mentioned above and create an\nEPS or PDF figure.  For example,\n\n.. code-block:: python\n\n    import yt.visualization.eps_writer as eps\n\n    slc = yt.SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    slc.set_width(25, \"kpc\")\n    eps_fig = eps.single_plot(slc)\n    eps_fig.save_fig(\"zoom\", format=\"eps\")\n    eps_fig.save_fig(\"zoom-pdf\", format=\"pdf\")\n\nThe ``eps_fig`` object exposes all of the low-level functionality of\n``PyX`` for further customization (see the `PyX documentation\n<https://pyx-project.org/manual/>`_).  There are a few\nconvenience routines in ``eps_writer``, such as drawing a circle,\n\n.. code-block:: python\n\n    eps_fig.circle(radius=0.2, loc=(0.5, 0.5))\n    eps_fig.sav_fig(\"zoom-circle\", format=\"eps\")\n\nwith a radius of 0.2 at a center of (0.5, 0.5), both of which are in\nunits of the figure's field of view.  The\n:func:`~yt.visualization.eps_writer.multiplot_yt` routine also\nprovides a convenient method to produce multi-panel figures\nfrom a PlotWindow.  For example,\n\n.. code-block:: python\n\n    import yt\n    import yt.visualization.eps_writer as eps\n\n    slc = yt.SlicePlot(\n        ds,\n        \"z\",\n        [\n            (\"gas\", \"density\"),\n            (\"gas\", \"temperature\"),\n            (\"gas\", \"pressure\"),\n            (\"gas\", \"velocity_magnitude\"),\n        ],\n    )\n    slc.set_width(25, \"kpc\")\n    eps_fig = eps.multiplot_yt(2, 2, slc, bare_axes=True)\n    eps_fig.scale_line(0.2, \"5 kpc\")\n    eps_fig.save_fig(\"multi\", format=\"eps\")\n\nwill produce a 2x2 panel figure with a scale bar indicating 5 kpc.\nThe routine will try its best to place the colorbars in the optimal\nmargin, but it can be overridden by providing the keyword\n``cb_location`` with a dict of either ``right, left, top, bottom``\nwith the fields as the keys.\n\nYou can also combine slices, projections, and phase plots. Here is\nan example that includes slices and phase plots:\n\n.. code-block:: python\n\n    from yt import PhasePlot, SlicePlot\n    from yt.visualization.eps_writer import multiplot_yt\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    p1 = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n    p1.set_width(10, \"kpc\")\n\n    p2 = SlicePlot(ds, \"x\", (\"gas\", \"temperature\"))\n    p2.set_width(10, \"kpc\")\n    p2.set_cmap((\"gas\", \"temperature\"), \"hot\")\n\n    sph = ds.sphere(ds.domain_center, (10, \"kpc\"))\n    p3 = PhasePlot(\n        sph,\n        \"radius\",\n        (\"gas\", \"density\"),\n        (\"gas\", \"temperature\"),\n        weight_field=(\"gas\", \"mass\"),\n    )\n\n    p4 = PhasePlot(\n        sph, \"radius\", (\"gas\", \"density\"), (\"gas\", \"pressure\"), weight_field=(\"gas\", \"mass\")\n    )\n\n    mp = multiplot_yt(\n        2,\n        2,\n        [p1, p2, p3, p4],\n        savefig=\"yt\",\n        shrink_cb=0.9,\n        bare_axes=False,\n        yt_nocbar=False,\n        margins=(0.5, 0.5),\n    )\n\n    mp.save_fig(\"multi_slice_phase\")\n\n\nUsing yt's style with matplotlib\n--------------------------------\n\nIt is possible to use yt's plot style outside of yt itself, with the\n:func:`~matplotlib.pyplot.style_context` context manager\n\n.. code-block:: python\n\n   import matplotlib.pyplot as plt\n   import numpy as np\n\n   plt.rcParams[\"font.size\"] = 14\n\n   x = np.linspace(-np.pi, np.pi, 100)\n   y = np.sin(x)\n\n   with plt.style.context(\"yt.default\"):\n       fig, ax = plt.subplots()\n       ax.plot(x, y)\n       ax.set(\n           xlabel=r\"$x$\",\n           ylabel=r\"$y$\",\n           title=\"A yt-styled matplotlib figure\",\n       )\n\nNote that :func:`~matplotlib.pyplot.style_context` doesn't control the font\nsize, so we adjust it manually in the preamble.\nYou may also enable yt's style without a context manager as\n\n.. code-block:: python\n\n   import matplotlib.pyplot as plt\n   import numpy as np\n\n   plt.style.use(\"yt.default\")\n   plt.rcParams[\"font.size\"] = 14\n\n   x = np.linspace(-np.pi, np.pi, 100)\n   y = np.sin(x)\n\n   fig, ax = plt.subplots()\n   ax.plot(x, y)\n   ax.set(\n       xlabel=r\"$x$\",\n       ylabel=r\"$y$\",\n       title=\"A yt-styled matplotlib figure\",\n   )\n\nFor more details, see `matplotlib's documentation\n<https://matplotlib.org/stable/tutorials/introductory/customizing.html#customizing-with-style-sheets>_`\n"
  },
  {
    "path": "doc/source/visualizing/sketchfab.rst",
    "content": ".. _extracting-isocontour-information:\n.. _surfaces:\n\n3D Surfaces and Sketchfab\n=========================\n\n.. sectionauthor:: Jill Naiman and Matthew Turk\n\nSurface Objects and Extracting Isocontour Information\n-----------------------------------------------------\n\nyt contains an implementation of the `Marching Cubes\n<https://en.wikipedia.org/wiki/Marching_cubes>`__ algorithm, which can operate on\n3D data objects.  This provides two things.  The first is to identify\nisocontours and return either the geometry of those isocontours or to return\nanother field value sampled along that isocontour.  The second piece of\nfunctionality is to calculate the flux of a field over an isocontour.\n\nNote that these isocontours are not guaranteed to be topologically connected.\nIn fact, inside a given data object, the marching cubes algorithm will return\nall isocontours, not just a single connected one.  This means if you encompass\ntwo clumps of a given density in your data object and extract an isocontour at\nthat density, it will include both of the clumps.\n\nThis means that with adaptive mesh refinement\ndata, you *will* see cracks across refinement boundaries unless a\n\"crack-fixing\" step is applied to match up these boundaries.  yt does not\nperform such an operation, and so there will be seams visible in 3D views of\nyour isosurfaces.\n\n\nSurfaces can be exported in `OBJ format\n<https://en.wikipedia.org/wiki/Wavefront_.obj_file>`_, values can be samples\nat the center of each face of the surface, and flux of a given field could be\ncalculated over the surface.  This means you can, for instance, extract an\nisocontour in density and calculate the mass flux over that isocontour.  It\nalso means you can export a surface from yt and view it in something like\n`Blender <https://www.blender.org/>`__, `MeshLab\n<http://www.meshlab.net>`__, or even on your Android or iOS device in\n`MeshPad <http://www.meshpad.org/>`__.\n\nTo extract geometry or sample a field, call\n:meth:`~yt.data_objects.data_containers.YTSelectionContainer3D.extract_isocontours`.  To\ncalculate a flux, call\n:meth:`~yt.data_objects.data_containers.YTSelectionContainer3D.calculate_isocontour_flux`.\nboth of these operations will run in parallel.  For more information on enabling\nparallelism in yt, see :ref:`parallel-computation`.\n\nAlternatively, you can make an object called ``YTSurface`` that makes\nthis process much easier.  You can create one of these objects by specifying a\nsource data object and a field over which to identify a surface at a given\nvalue.  For example:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   sphere = ds.sphere(\"max\", (1.0, \"Mpc\"))\n   surface = ds.surface(sphere, (\"gas\", \"density\"), 1e-27)\n\nThis object, ``surface``, can be queried for values on the surface.  For\ninstance:\n\n.. code-block:: python\n\n   print(surface[\"gas\", \"temperature\"].min(), surface[\"gas\", \"temperature\"].max())\n\nwill return the values 11850.7476943 and 13641.0663899.  These values are\ninterpolated to the face centers of every triangle that constitutes a portion\nof the surface.  Note that reading a new field requires re-calculating the\nentire surface, so it's not the fastest operation.  You can get the vertices of\nthe triangle by looking at the property ``.vertices``.\n\nExporting to a File\n-------------------\n\nIf you want to export this to a `PLY file\n<https://en.wikipedia.org/wiki/PLY_(file_format)>`_ you can call the routine\n``export_ply``, which will write to a file and optionally sample a field at\nevery face or vertex, outputting a color value to the file as well.  This file\ncan then be viewed in MeshLab, Blender or on the website `Sketchfab.com\n<https://sketchfab.com>`__.  But if you want to view it on Sketchfab, there's an\neven easier way!\n\nExporting to Sketchfab\n----------------------\n\n`Sketchfab <https://sketchfab.com>`__ is a website that uses WebGL, a relatively\nnew technology for displaying 3D graphics in any browser.  It's very fast and\ntypically requires no plugins.  Plus, it means that you can share data with\nanyone and they can view it immersively without having to download the data or\nany software packages!  Sketchfab provides a free tier for up to 10 models, and\nthese models can be embedded in websites.\n\nThere are lots of reasons to want to export to Sketchfab.  For instance, if\nyou're looking at a galaxy formation simulation and you publish a paper, you\ncan include a link to the model in that paper (or in the arXiv listing) so that\npeople can explore and see what the data looks like.  You can also embed a\nmodel in a website with other supplemental data, or you can use Sketchfab to\ndiscuss morphological properties of a dataset with collaborators.  It's also\njust plain cool.\n\nThe ``YTSurface`` object includes a method to upload directly to Sketchfab,\nbut it requires that you get an API key first.  You can get this API key by\ncreating an account and then going to your \"dashboard,\" where it will be listed\non the right hand side.  Once you've obtained it, put it into your\n``~/.config/yt/yt.toml`` file under the heading ``[yt]`` as the variable\n``sketchfab_api_key``.  If you don't want to do this, you can also supply it as\nan argument to the function ``export_sketchfab``.\n\nNow you can run a script like this:\n\n.. code-block:: python\n\n    import yt\n    from yt.units import kpc\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    dd = ds.sphere(ds.domain_center, (500, \"kpc\"))\n    rho = 1e-28\n\n    bounds = [[dd.center[i] - 250 * kpc, dd.center[i] + 250 * kpc] for i in range(3)]\n\n    surf = ds.surface(dd, (\"gas\", \"density\"), rho)\n\n    upload_id = surf.export_sketchfab(\n        title=\"galaxy0030 - 1e-28\",\n        description=\"Extraction of Density (colored by temperature) at 1e-28 g/cc\",\n        color_field=(\"gas\", \"temperature\"),\n        color_map=\"hot\",\n        color_log=True,\n        bounds=bounds,\n    )\n\nand yt will extract a surface, convert to a format that Sketchfab.com\nunderstands (PLY, in a zip file) and then upload it using your API key.  For\nthis demo, I've used data kindly provided by Ryan Joung from a simulation of\ngalaxy formation.  Here's what my newly-uploaded model looks like, using the\nembed code from Sketchfab:\n\n.. raw:: html\n\n     <iframe width=\"640\" height=\"480\" src=\"https://sketchfab.com/models/ff59dacd55824110ad5bcc292371a514/embed\" frameborder=\"0\" allowfullscreen mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\" onmousewheel=\"\"></iframe>\n\nAs a note, Sketchfab has a maximum model size of 50MB for the free account.\n50MB is pretty hefty, though, so it shouldn't be a problem for most\nneeds. Additionally, if you have an eligible e-mail address associated with a\nschool or university, you can request a free professional account, which allows\nmodels up to 200MB. See https://sketchfab.com/education for details.\n\nOBJ and MTL Files\n-----------------\n\nIf the ability to maneuver around an isosurface of your 3D simulation in\n`Sketchfab <https://sketchfab.com>`__ cost you half a day of work (let's be\nhonest, 2 days), prepare to be even less productive.  With a new  `OBJ file\n<https://en.wikipedia.org/wiki/Wavefront_.obj_file>`__ exporter, you can now\nupload multiple surfaces of different transparencies in the same file.\nThe following code snippet produces two files which contain the vertex info\n(surfaces.obj) and color/transparency info (surfaces.mtl) for a 3D\ngalaxy simulation:\n\n.. code-block:: python\n\n   import yt\n\n   ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n   rho = [2e-27, 1e-27]\n   trans = [1.0, 0.5]\n   filename = \"./surfaces\"\n\n   sphere = ds.sphere(\"max\", (1.0, \"Mpc\"))\n   for i, r in enumerate(rho):\n       surf = ds.surface(sphere, (\"gas\", \"density\"), r)\n       surf.export_obj(\n           filename,\n           transparency=trans[i],\n           color_field=(\"gas\", \"temperature\"),\n           plot_index=i,\n       )\n\nThe calling sequence is fairly similar to the ``export_ply`` function\n`previously used <https://blog.yt-project.org/post/3DSurfacesAndSketchFab/>`__\nto export 3D surfaces.  However, one can now specify a transparency for each\nsurface of interest, and each surface is enumerated in the OBJ files with ``plot_index``.\nThis means one could potentially add surfaces to a previously\ncreated file by setting ``plot_index`` to the number of previously written\nsurfaces.\n\nOne tricky thing: the header of the OBJ file points to the MTL file (with\nthe header command ``mtllib``).  This means if you move one or both of the files\nyou may have to change the header to reflect their new directory location.\n\nA Few More Options\n------------------\n\nThere are a few extra inputs for formatting the surface files you may want to use.\n\n(1) Setting ``dist_fac`` will divide all the vertex coordinates by this factor.\nDefault will scale the vertices by the physical bounds of your sphere.\n\n(2) Setting ``color_field_max`` and/or ``color_field_min`` will scale the colors\nof all surfaces between this min and max.  Default is to scale the colors of each\nsurface to their own min and max values.\n\nUploading to SketchFab\n----------------------\n\nTo upload to `Sketchfab <https://sketchfab.com>`__ one only needs to zip the\nOBJ and MTL files together, and then upload via your dashboard prompts in\nthe usual way.  For example, the above script produces:\n\n.. raw:: html\n\n   <iframe frameborder=\"0\" height=\"480\" width=\"854\" allowFullScreen\n   webkitallowfullscreen=\"true\" mozallowfullscreen=\"true\"\n   src=\"https://skfb.ly/5k4j2fdcb?autostart=0&transparent=0&autospin=0&controls=1&watermark=1\">\n   </iframe>\n\nImporting to MeshLab and Blender\n--------------------------------\n\nThe new OBJ formatting will produce multi-colored surfaces in both\n`MeshLab <http://www.meshlab.net/>`__ and `Blender <https://www.blender.org/>`__,\na feature not possible with the\n`previous PLY exporter <https://blog.yt-project.org/post/3DSurfacesAndSketchFab/>`__.\nTo see colors in MeshLab go to the \"Render\" tab and\nselect \"Color -> Per Face\".  Note in both MeshLab and Blender, unlike Sketchfab, you can't see\ntransparencies until you render.\n\n...One More Option\n------------------\n\nIf you've started poking around the actual code instead of skipping off to\nlose a few days running around your own simulations\nyou may have noticed there are a few more options then those listed above,\nspecifically, a few related to something called \"Emissivity.\"  This allows you\nto output one more type of variable on your surfaces.  For example:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    rho = [2e-27, 1e-27]\n    trans = [1.0, 0.5]\n    filename = \"./surfaces\"\n\n\n    def emissivity(data):\n        return data[\"gas\", \"density\"] ** 2 * np.sqrt(data[\"gas\", \"temperature\"])\n\n\n    add_field(\"emissivity\", function=_Emissivity, sampling_type=\"cell\", units=r\"g*K/cm**6\")\n\n    sphere = ds.sphere(\"max\", (1.0, \"Mpc\"))\n    for i, r in enumerate(rho):\n        surf = ds.surface(sphere, (\"gas\", \"density\"), r)\n        surf.export_obj(\n            filename,\n            transparency=trans[i],\n            color_field=(\"gas\", \"temperature\"),\n            emit_field=\"emissivity\",\n            plot_index=i,\n        )\n\nwill output the same OBJ and MTL as in our previous example, but it will scale\nan emissivity parameter by our new field.  Technically, this makes our outputs\nnot really OBJ files at all, but a new sort of hybrid file, however we needn't worry\ntoo much about that for now.\n\nThis parameter is useful if you want to upload your files in Blender and have the\nembedded rendering engine do some approximate ray-tracing on your transparencies\nand emissivities.   This does take some slight modifications to the OBJ importer\nscripts in Blender.  For example, on a Mac, you would modify the file\n\"/Applications/Blender/blender.app/Contents/MacOS/2.65/scripts/addons/io_scene_obj/import_obj.py\",\nin the function \"create_materials\" with:\n\n.. code-block:: diff\n\n   # ...\n\n                    elif line_lower.startswith(b'tr'):  # translucency\n                        context_material.translucency = float_func(line_split[1])\n                    elif line_lower.startswith(b'tf'):\n                        # rgb, filter color, blender has no support for this.\n                        pass\n                    elif line_lower.startswith(b'em'): # MODIFY: ADD THIS LINE\n                        context_material.emit = float_func(line_split[1]) # MODIFY: THIS LINE TOO\n                    elif line_lower.startswith(b'illum'):\n                        illum = int(line_split[1])\n\n   # ...\n\nTo use this in Blender, you might create a\n`Blender script <https://docs.blender.org/manual/en/latest/advanced/scripting/introduction.html>`__\nlike the following:\n\n.. code-block:: python\n\n   from math import radians\n\n   import bpy\n\n   bpy.ops.import_scene.obj(filepath=\"./surfaces.obj\")  # will use new importer\n\n   # set up lighting = indirect\n   bpy.data.worlds[\"World\"].light_settings.use_indirect_light = True\n   bpy.data.worlds[\"World\"].horizon_color = [0.0, 0.0, 0.0]  # background = black\n   # have to use approximate, not ray tracing for emitting objects ...\n   #   ... for now...\n   bpy.data.worlds[\"World\"].light_settings.gather_method = \"APPROXIMATE\"\n   bpy.data.worlds[\"World\"].light_settings.indirect_factor = 20.0  # turn up all emiss\n\n   # set up camera to be on -x axis, facing toward your object\n   scene = bpy.data.scenes[\"Scene\"]\n   scene.camera.location = [-0.12, 0.0, 0.0]  # location\n   scene.camera.rotation_euler = [\n       radians(90.0),\n       0.0,\n       radians(-90.0),\n   ]  # face to (0,0,0)\n\n   # render\n   scene.render.filepath = \"/Users/jillnaiman/surfaces_blender\"  # needs full path\n   bpy.ops.render.render(write_still=True)\n\nThis above bit of code would produce an image like so:\n\n.. image:: _images/surfaces_blender.png\n\nNote that the hottest stuff is brightly shining, while the cool stuff is less so\n(making the inner isodensity contour barely visible from the outside of the surfaces).\n\nIf the Blender image caught your fancy, you'll be happy to know there is a greater\nintegration of Blender and yt in the works, so stay tuned!\n"
  },
  {
    "path": "doc/source/visualizing/streamlines.rst",
    "content": ".. _streamlines:\n\nStreamlines: Tracking the Trajectories of Tracers in your Data\n==============================================================\n\nStreamlines, as implemented in yt, are defined as being parallel to a\nvector field at all points.  While commonly used to follow the\nvelocity flow or magnetic field lines, they can be defined to follow\nany three-dimensional vector field.  Once an initial condition and\ntotal length of the streamline are specified, the streamline is\nuniquely defined.  Relatedly, yt also has the ability to follow\n:doc:`../analyzing/Particle_Trajectories`.\n\nMethod\n------\n\nStreamlining through a volume is useful for a variety of analysis\ntasks.  By specifying a set of starting positions, the user is\nreturned a set of 3D positions that can, in turn, be used to visualize\nthe 3D path of the streamlines.  Additionally, individual streamlines\ncan be converted into\n:class:`~yt.data_objects.construction_data_containers.YTStreamline` objects,\nand queried for all the available fields along the streamline.\n\nThe implementation of streamlining  in yt is described below.\n\n#. Decompose the volume into a set of non-overlapping, fully domain\n   tiling bricks, using the\n   :class:`~yt.utilities.amr_kdtree.amr_kdtree.AMRKDTree` homogenized\n   volume.\n#. For every streamline starting position:\n\n   #. While the length of the streamline is less than the requested\n      length:\n\n      #. Find the brick that contains the current position\n      #. If not already present, generate vertex-centered data for\n         the vector fields defining the streamline.\n      #. While inside the brick\n\n         #. Integrate the streamline path using a Runge-Kutta 4th\n            order method and the vertex centered data.\n\t #. During the intermediate steps of each RK4 step, if the\n            position is updated to outside the current brick,\n            interrupt the integration and locate a new brick at the\n            intermediate position.\n\n#. The set of streamline positions are stored in the\n   :class:`~yt.visualization.streamlines.Streamlines` object.\n\nExample Script\n++++++++++++++\n\n.. python-script::\n\n    import matplotlib.pyplot as plt\n    import numpy as np\n    from mpl_toolkits.mplot3d import Axes3D\n\n    import yt\n    from yt.units import Mpc\n    from yt.visualization.api import Streamlines\n\n    # Load the dataset\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    # Define c: the center of the box, N: the number of streamlines,\n    # scale: the spatial scale of the streamlines relative to the boxsize,\n    # and then pos: the random positions of the streamlines.\n    c = ds.domain_center\n    N = 100\n    scale = ds.domain_width[0]\n    pos_dx = np.random.random((N, 3)) * scale - scale / 2.0\n    pos = c + pos_dx\n\n    # Create streamlines of the 3D vector velocity and integrate them through\n    # the box defined above\n    streamlines = Streamlines(\n        ds,\n        pos,\n        (\"gas\", \"velocity_x\"),\n        (\"gas\", \"velocity_y\"),\n        (\"gas\", \"velocity_z\"),\n        length=1.0 * Mpc,\n        get_magnitude=True,\n    )\n    streamlines.integrate_through_volume()\n\n    # Create a 3D plot, trace the streamlines through the 3D volume of the plot\n    fig = plt.figure()\n    ax = Axes3D(fig, auto_add_to_figure=False)\n    fig.add_axes(ax)\n    for stream in streamlines.streamlines:\n        stream = stream[np.all(stream != 0.0, axis=1)]\n        ax.plot3D(stream[:, 0], stream[:, 1], stream[:, 2], alpha=0.1)\n\n    # Save the plot to disk.\n    plt.savefig(\"streamlines.png\")\n\n\nData Access Along the Streamline\n--------------------------------\n\n.. note::\n\n    This functionality has not been implemented yet in the 3.x series of\n    yt.  If you are interested in working on this and have questions, please\n    let us know on the yt-dev mailing list.\n\nOnce the streamlines are found, a\n:class:`~yt.data_objects.construction_data_containers.YTStreamline` object can\nbe created using the\n:meth:`~yt.visualization.streamlines.Streamlines.path` function, which\ntakes as input the index of the streamline requested. This conversion\nis done by creating a mask that defines where the streamline is, and\ncreating 't' and 'dts' fields that define the dimensionless streamline\nintegration coordinate and integration step size. Once defined, fields\ncan be accessed in the standard manner.\n\nExample Script\n++++++++++++++++\n\n.. code-block:: python\n\n    import matplotlib.pyplot as plt\n    import yt\n    from yt.visualization.api import Streamlines\n\n    ds = yt.load(\"DD1701\")  # Load ds\n    streamlines = Streamlines(ds, ds.domain_center)\n    streamlines.integrate_through_volume()\n    stream = streamlines.path(0)\n    plt.semilogy(stream[\"t\"], stream[\"gas\", \"density\"], \"-x\")\n\n\nRunning in Parallel\n--------------------\n\nThe integration of the streamline paths is \"embarrassingly\" parallelized by\nsplitting the streamlines up between the processors.  Upon completion,\neach processor has access to all of the streamlines through the use of\na reduction operation.\n\nFor more information on enabling parallelism in yt, see\n:ref:`parallel-computation`.\n"
  },
  {
    "path": "doc/source/visualizing/unstructured_mesh_rendering.rst",
    "content": ".. _unstructured_mesh_rendering:\n\nUnstructured Mesh Rendering\n===========================\n\nBeginning with version 3.3, yt has the ability to volume render unstructured\nmesh data like that created by finite element calculations. No additional\ndependencies are required in order to use this feature. However, it is\npossible to speed up the rendering operation by installing with\n`Embree <https://www.embree.org>`_ support. Embree is a fast ray-tracing\nlibrary from Intel that can substantially speed up the mesh rendering operation\non large datasets. You can read about how to install yt with Embree support\nbelow, or you can skip to the examples.\n\nOptional Embree Installation\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nYou'll need to `install Python bindings for netCDF4 <https://github.com/Unidata/netcdf4-python#installation>`_.\nThen you'll need to get Embree itself and its corresponding Python bindings (pyembree).\nFor conda-based systems, this is trivial, see\n`pyembree's doc <https://github.com/scopatz/pyembree#installation>`_\n\nFor systems other than conda, you will need to install Embree first, either by\n`compiling from source <https://github.com/embree/embree#installation-of-embree>`_\nor by using one of the pre-built binaries available at Embree's\n`releases <https://github.com/embree/embree/releases>`_ page.\n\nThen you'll want to install pyembree from source as follows.\n\n.. code-block:: bash\n\n    git clone https://github.com/scopatz/pyembree\n\nTo install, navigate to the root directory and run the setup script.\nIf Embree was installed to some location that is not in your path by default,\nyou will need to pass in CFLAGS and LDFLAGS to the setup.py script. For example,\nthe Mac OS X package installer puts the installation at /opt/local/ instead of\nusr/local. To account for this, you would do:\n\n.. code-block:: bash\n\n    CFLAGS='-I/opt/local/include' LDFLAGS='-L/opt/local/lib' python setup.py install\n\nOnce Embree and pyembree are installed, a,d in order to use the unstructured\nmesh rendering capability, you must :ref:`rebuild yt from source\n<install-from-source>`, . Once again, if embree is installed in a location that\nis not part of your default search path, you must tell yt where to find it.\nThere are a number of ways to do this. One way is to again manually pass in the\nflags when running the setup script in the yt-git directory:\n\n.. code-block:: bash\n\n    CFLAGS='-I/opt/local/include' LDFLAGS='-L/opt/local/lib' python setup.py develop\n\nYou can also set EMBREE_DIR environment variable to '/opt/local', in which case\nyou could just run\n\n.. code-block:: bash\n\n   python setup.py develop\n\nas usual. Finally, if you create a file called embree.cfg in the yt-git directory with\nthe location of the embree installation, the setup script will find this and use it,\nprovided EMBREE_DIR is not set. An example embree.cfg file could like this:\n\n.. code-block:: bash\n\n   /opt/local/\n\nWe recommend one of the later two methods, especially\nif you plan on re-compiling the cython extensions regularly. Note that none of this is\nnecessary if you installed embree into a location that is in your default path, such\nas /usr/local.\n\nExamples\n^^^^^^^^\n\nFirst, here is an example of rendering an 8-node, hexahedral MOOSE dataset.\n\n.. python-script::\n\n    import yt\n\n    ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n\n    # create a default scene\n    sc = yt.create_scene(ds)\n\n    # override the default colormap\n    ms = sc.get_source()\n    ms.cmap = \"Eos A\"\n\n    # adjust the camera position and orientation\n    cam = sc.camera\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam_pos = ds.arr([-3.0, 3.0, -3.0], \"code_length\")\n    north_vector = ds.arr([0.0, -1.0, -1.0], \"dimensionless\")\n    cam.set_position(cam_pos, north_vector)\n\n    # increase the default resolution\n    cam.resolution = (800, 800)\n\n    # render and save\n    sc.save()\n\nYou can also overplot the mesh boundaries:\n\n.. python-script::\n\n    import yt\n\n    ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n\n    # create a default scene\n    sc = yt.create_scene(ds)\n\n    # override the default colormap\n    ms = sc.get_source()\n    ms.cmap = \"Eos A\"\n\n    # adjust the camera position and orientation\n    cam = sc.camera\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam_pos = ds.arr([-3.0, 3.0, -3.0], \"code_length\")\n    north_vector = ds.arr([0.0, -1.0, -1.0], \"dimensionless\")\n    cam.set_position(cam_pos, north_vector)\n\n    # increase the default resolution\n    cam.resolution = (800, 800)\n\n    # render, draw the element boundaries, and save\n    sc.render()\n    sc.annotate_mesh_lines()\n    sc.save()\n\nAs with slices, you can visualize different meshes and different fields. For example,\nHere is a script similar to the above that plots the \"diffused\" variable\nusing the mesh labelled by \"connect2\":\n\n.. python-script::\n\n    import yt\n\n    ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n\n    # create a default scene\n    sc = yt.create_scene(ds, (\"connect2\", \"diffused\"))\n\n    # override the default colormap\n    ms = sc.get_source()\n    ms.cmap = \"Eos A\"\n\n    # adjust the camera position and orientation\n    cam = sc.camera\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam_pos = ds.arr([-3.0, 3.0, -3.0], \"code_length\")\n    north_vector = ds.arr([0.0, -1.0, -1.0], \"dimensionless\")\n    cam.set_position(cam_pos, north_vector)\n\n    # increase the default resolution\n    cam.resolution = (800, 800)\n\n    # render and save\n    sc.save()\n\nNext, here is an example of rendering a dataset with tetrahedral mesh elements.\nNote that in this dataset, there are multiple \"steps\" per file, so we specify\nthat we want to look at the last one.\n\n.. python-script::\n\n    import yt\n\n    filename = \"MOOSE_sample_data/high_order_elems_tet4_refine_out.e\"\n    ds = yt.load(filename, step=-1)  # we look at the last time frame\n\n    # create a default scene\n    sc = yt.create_scene(ds, (\"connect1\", \"u\"))\n\n    # override the default colormap\n    ms = sc.get_source()\n    ms.cmap = \"Eos A\"\n\n    # adjust the camera position and orientation\n    cam = sc.camera\n    camera_position = ds.arr([3.0, 3.0, 3.0], \"code_length\")\n    cam.set_width(ds.arr([2.0, 2.0, 2.0], \"code_length\"))\n    north_vector = ds.arr([0.0, -1.0, 0.0], \"dimensionless\")\n    cam.set_position(camera_position, north_vector)\n\n    # increase the default resolution\n    cam.resolution = (800, 800)\n\n    # render and save\n    sc.save()\n\nHere is an example using 6-node wedge elements:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"MOOSE_sample_data/wedge_out.e\")\n\n   # create a default scene\n   sc = yt.create_scene(ds, (\"connect2\", \"diffused\"))\n\n   # override the default colormap\n   ms = sc.get_source()\n   ms.cmap = \"Eos A\"\n\n   # adjust the camera position and orientation\n   cam = sc.camera\n   cam.set_position(ds.arr([1.0, -1.0, 1.0], \"code_length\"))\n   cam.width = ds.arr([1.5, 1.5, 1.5], \"code_length\")\n\n   # render and save\n   sc.save()\n\nAnother example, this time plotting the temperature field from a 20-node hex\nMOOSE dataset:\n\n.. python-script::\n\n    import yt\n\n    # We load the last time frame\n    ds = yt.load(\"MOOSE_sample_data/mps_out.e\", step=-1)\n\n    # create a default scene\n    sc = yt.create_scene(ds, (\"connect2\", \"temp\"))\n\n    # override the default colormap. This time we also override\n    # the default color bounds\n    ms = sc.get_source()\n    ms.cmap = \"hot\"\n    ms.color_bounds = (500.0, 1700.0)\n\n    # adjust the camera position and orientation\n    cam = sc.camera\n    camera_position = ds.arr([-1.0, 1.0, -0.5], \"code_length\")\n    north_vector = ds.arr([0.0, -1.0, -1.0], \"dimensionless\")\n    cam.width = ds.arr([0.04, 0.04, 0.04], \"code_length\")\n    cam.set_position(camera_position, north_vector)\n\n    # increase the default resolution\n    cam.resolution = (800, 800)\n\n    # render, draw the element boundaries, and save\n    sc.render()\n    sc.annotate_mesh_lines()\n    sc.save()\n\nThe dataset in the above example contains displacement fields, so this is a good\nopportunity to demonstrate their use. The following example is exactly like the\nabove, except we scale the displacements by a factor of a 10.0, and additionally\nadd an offset to the mesh by 1.0 unit in the x-direction:\n\n.. python-script::\n\n    import yt\n\n    # We load the last time frame\n    ds = yt.load(\n        \"MOOSE_sample_data/mps_out.e\",\n        step=-1,\n        displacements={\"connect2\": (10.0, [0.01, 0.0, 0.0])},\n    )\n\n    # create a default scene\n    sc = yt.create_scene(ds, (\"connect2\", \"temp\"))\n\n    # override the default colormap. This time we also override\n    # the default color bounds\n    ms = sc.get_source()\n    ms.cmap = \"hot\"\n    ms.color_bounds = (500.0, 1700.0)\n\n    # adjust the camera position and orientation\n    cam = sc.camera\n    camera_position = ds.arr([-1.0, 1.0, -0.5], \"code_length\")\n    north_vector = ds.arr([0.0, -1.0, -1.0], \"dimensionless\")\n    cam.width = ds.arr([0.05, 0.05, 0.05], \"code_length\")\n    cam.set_position(camera_position, north_vector)\n\n    # increase the default resolution\n    cam.resolution = (800, 800)\n\n    # render, draw the element boundaries, and save\n    sc.render()\n    sc.annotate_mesh_lines()\n    sc.save()\n\nAs with other volume renderings in yt, you can swap out different lenses. Here is\nan example that uses a \"perspective\" lens, for which the rays diverge from the\ncamera position according to some opening angle:\n\n.. python-script::\n\n    import yt\n\n    ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n\n    # create a default scene\n    sc = yt.create_scene(ds, (\"connect2\", \"diffused\"))\n\n    # override the default colormap\n    ms = sc.get_source()\n    ms.cmap = \"Eos A\"\n\n    # Create a perspective Camera\n    cam = sc.add_camera(ds, lens_type=\"perspective\")\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam_pos = ds.arr([-4.5, 4.5, -4.5], \"code_length\")\n    north_vector = ds.arr([0.0, -1.0, -1.0], \"dimensionless\")\n    cam.set_position(cam_pos, north_vector)\n\n    # increase the default resolution\n    cam.resolution = (800, 800)\n\n    # render, draw the element boundaries, and save\n    sc.render()\n    sc.annotate_mesh_lines()\n    sc.save()\n\nYou can also create scenes that have multiple meshes. The ray-tracing infrastructure\nwill keep track of the depth information for each source separately, and composite\nthe final image accordingly. In the next example, we show how to render a scene\nwith two meshes on it:\n\n.. python-script::\n\n    import yt\n    from yt.visualization.volume_rendering.api import MeshSource, Scene\n\n    ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n\n    # this time we create an empty scene and add sources to it one-by-one\n    sc = Scene()\n\n    # set up our Camera\n    cam = sc.add_camera(ds)\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam.set_position(\n        ds.arr([-3.0, 3.0, -3.0], \"code_length\"),\n        ds.arr([0.0, -1.0, 0.0], \"dimensionless\"),\n    )\n    cam.set_width = ds.arr([8.0, 8.0, 8.0], \"code_length\")\n    cam.resolution = (800, 800)\n\n    # create two distinct MeshSources from 'connect1' and 'connect2'\n    ms1 = MeshSource(ds, (\"connect1\", \"diffused\"))\n    ms2 = MeshSource(ds, (\"connect2\", \"diffused\"))\n\n    sc.add_source(ms1)\n    sc.add_source(ms2)\n\n    # render and save\n    im = sc.render()\n    sc.save()\n\nHowever, in the rendered image above, we note that the color is discontinuous on\nin the middle and upper part of the cylinder's side. In the original data,\nthere are two parts but the value of ``diffused`` is continuous at the interface.\nThis discontinuous color is due to an independent colormap setting for the two\nmesh sources. To fix it, we can explicitly specify the colormap bound for each\nmesh source as follows:\n\n.. python-script::\n\n    import yt\n    from yt.visualization.volume_rendering.api import MeshSource, Scene\n\n    ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n\n    # this time we create an empty scene and add sources to it one-by-one\n    sc = Scene()\n\n    # set up our Camera\n    cam = sc.add_camera(ds)\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam.set_position(\n        ds.arr([-3.0, 3.0, -3.0], \"code_length\"),\n        ds.arr([0.0, -1.0, 0.0], \"dimensionless\"),\n    )\n    cam.set_width = ds.arr([8.0, 8.0, 8.0], \"code_length\")\n    cam.resolution = (800, 800)\n\n    # create two distinct MeshSources from 'connect1' and 'connect2'\n    ms1 = MeshSource(ds, (\"connect1\", \"diffused\"))\n    ms2 = MeshSource(ds, (\"connect2\", \"diffused\"))\n\n    # add the following lines to set the range of the two mesh sources\n    ms1.color_bounds = (0.0, 3.0)\n    ms2.color_bounds = (0.0, 3.0)\n\n    sc.add_source(ms1)\n    sc.add_source(ms2)\n\n    # render and save\n    im = sc.render()\n    sc.save()\n\nMaking Movies\n^^^^^^^^^^^^^\n\nHere are a couple of example scripts that show how to create image frames that\ncan later be stitched together into a movie. In the first example, we look at a\nsingle dataset at a fixed time, but we move the camera around to get a different\nvantage point. We call the rotate() method 300 times, saving a new image to the\ndisk each time.\n\n.. code-block:: python\n\n    import numpy as np\n\n    import yt\n\n    ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n\n    # create a default scene\n    sc = yt.create_scene(ds)\n\n    # override the default colormap\n    ms = sc.get_source()\n    ms.cmap = \"Eos A\"\n\n    # adjust the camera position and orientation\n    cam = sc.camera\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam_pos = ds.arr([-3.0, 3.0, -3.0], \"code_length\")\n    north_vector = ds.arr([0.0, -1.0, -1.0], \"dimensionless\")\n    cam.set_position(cam_pos, north_vector)\n\n    # increase the default resolution\n    cam.resolution = (800, 800)\n\n    # set the camera to use \"steady_north\"\n    cam.steady_north = True\n\n    # make movie frames\n    num_frames = 301\n    for i in range(num_frames):\n        cam.rotate(2.0 * np.pi / num_frames)\n        sc.render()\n        sc.save(\"movie_frames/surface_render_%.4d.png\" % i)\n\nFinally, this example demonstrates how to loop over the time steps in a single\nfile with a fixed camera position:\n\n.. code-block:: python\n\n    import matplotlib.pyplot as plt\n\n    import yt\n    from yt.visualization.volume_rendering.api import MeshSource\n\n    NUM_STEPS = 127\n    CMAP = \"hot\"\n    VMIN = 300.0\n    VMAX = 2000.0\n\n    for step in range(NUM_STEPS):\n        ds = yt.load(\"MOOSE_sample_data/mps_out.e\", step=step)\n\n\ttime = ds._get_current_time()\n\n\t# the field name is a tuple of strings. The first string\n\t# specifies which mesh will be plotted, the second string\n\t# specifies the name of the field.\n\tfield_name = ('connect2', 'temp')\n\n\t# this initializes the render source\n\tms = MeshSource(ds, field_name)\n\n\t# set up the camera here. these values were arrived by\n\t# calling pitch, yaw, and roll in the notebook until I\n\t# got the angle I wanted.\n\tsc.add_camera(ds)\n\tcamera_position = ds.arr([0.1, 0.0, 0.1], 'code_length')\n\tcam.focus = ds.domain_center\n\tnorth_vector = ds.arr([-0.3032476, -0.71782557, 0.62671153], 'dimensionless')\n\tcam.width = ds.arr([ 0.04,  0.04,  0.04], 'code_length')\n\tcam.resolution = (800, 800)\n\tcam.set_position(camera_position, north_vector)\n\n\t# actually make the image here\n\tim = ms.render(cam, cmap=CMAP, color_bounds=(VMIN, VMAX))\n\n\t# Plot the result using matplotlib and save.\n\t# Note that we are setting the upper and lower\n\t# bounds of the colorbar to be the same for all\n\t# frames of the image.\n\n\t# must clear the image between frames\n\tplt.clf()\n\tfig = plt.gcf()\n\tax = plt.gca()\n\tax.imshow(im, interpolation='nearest', origin='lower')\n\n\t# Add the colorbar using a fake (not shown) image.\n\tp = ax.imshow(ms.data, visible=False, cmap=CMAP, vmin=VMIN, vmax=VMAX)\n\tcb = fig.colorbar(p)\n\tcb.set_label(field_name[1])\n\n\tax.text(25, 750, 'time = %.2e' % time, color='k')\n\tax.axes.get_xaxis().set_visible(False)\n\tax.axes.get_yaxis().set_visible(False)\n\n\tplt.savefig('movie_frames/test_%.3d' % step)\n"
  },
  {
    "path": "doc/source/visualizing/visualizing_particle_datasets_with_firefly.rst",
    "content": ".. _visualizing_particle_datasets_with_firefly:\n\nVisualizing Particle Datasets with Firefly\n==========================================\n`Firefly <https://github.com/ageller/Firefly>`_\nis an interactive, browser-based,\nparticle visualization platform that allows you to filter, colormap, and fly\nthrough their data. The Python frontend allows users to both load in their\nown datasets and customize every aspect of the user interface.\nyt offers to ability\nto export your data to Firefly's ffly or JSON format through the\n:meth:`~yt.data_objects.data_containers.YTDataContainer.create_firefly_object`\nmethod.\n\nYou can adjust the interface settings, particle colors, decimation factors, and\nother `Firefly settings <https://ageller.github.io/Firefly/docs/build/html/index.html>`_\nthrough the returned ``Firefly.reader`` object. Once the\nsettings are tuned to your liking, calling the ``reader.writeToDisk()`` method will\nproduce the final ffly files. Note that ``reader.clean_datadir`` defaults to true\nwhen using\n:meth:`~yt.data_objects.data_containers.YTDataContainer.create_firefly_object`\nso if you would like to manage multiple datasets make sure to pass different\n``datadir`` keyword arguments.\n\n.. image:: _images/firefly_example.png\n   :width: 85%\n   :align: center\n   :alt: Screenshot of a sample Firefly visualization\n\nExporting an Example Dataset to Firefly\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nHere is an example of how to use yt to export data to Firefly using some\n`sample data <https://yt-project.org/data/>`_.\n\n.. code-block:: python\n\n   ramses_ds = yt.load(\"DICEGalaxyDisk_nonCosmological/output_00002/info_00002.txt\")\n\n   region = ramses_ds.sphere(ramses_ds.domain_center, (1000, \"kpc\"))\n\n   reader = region.create_firefly_object(\n       \"IsoGalaxyRamses\",\n       fields_to_include=[\"particle_extra_field_1\", \"particle_extra_field_2\"],\n       fields_units=[\"dimensionless\", \"dimensionless\"],\n   )\n\n   ## adjust some of the options\n   reader.settings[\"color\"][\"io\"] = [1, 1, 0, 1]  ## set default color\n   reader.particleGroups[0].decimation_factor = 100  ## increase the decimation factor\n\n   ## dump files to\n   ##  ~/IsoGalaxyRamses/Dataio000.ffly\n   ##  ~/IsoGalaxyRamses/filenames.json\n   ##  ~/IsoGalaxyRamses/DataSettings.json\n   reader.writeToDisk()\n"
  },
  {
    "path": "doc/source/visualizing/volume_rendering.rst",
    "content": ".. _volume_rendering:\n\n3D Visualization and Volume Rendering\n=====================================\n\nyt has the ability to create 3D visualizations using a process known as *volume\nrendering* (oftentimes abbreviated VR).  This volume rendering code differs\nfrom the standard yt infrastructure for generating :ref:`simple-inspection`\nin that it evaluates the radiative transfer equations through the volume with\nuser-defined transfer functions for each ray.  Thus it can accommodate both\nopaque and transparent structures appropriately.  Currently all of the\nrendering capabilities are implemented in software, requiring no specialized\nhardware. Optimized versions implemented with OpenGL and utilizing graphics\nprocessors are being actively developed.\n\n.. note::\n\n   There is a Jupyter notebook containing a volume rendering tutorial:\n   :doc:`Volume_Rendering_Tutorial`.\n\nVolume Rendering Introduction\n-----------------------------\n\nConstructing a 3D visualization is a process of describing the \"scene\" that\nwill be rendered.  This includes the location of the viewing point (i.e., where\nthe \"camera\" is placed), the method by which a system would be viewed (i.e.,\nthe \"lens,\" which may be orthographic, perspective, fisheye, spherical, and so\non) and the components that will be rendered (render \"sources,\" such as volume\nelements, lines, annotations, and opaque surfaces).  The 3D plotting\ninfrastructure then develops a resultant image from this scene, which can be\nsaved to a file or viewed inline.\n\nBy constructing the scene in this programmatic way, full control can be had\nover each component in the scene as well as the method by which the scene is\nrendered; this can be used to prototype visualizations, inject annotation such\nas grid or continent lines, and then to render a production-quality\nvisualization.  By changing the \"lens\" used, a single camera path can output\nimages suitable for planetarium domes, immersive and head tracking systems\n(such as the Oculus Rift or recent 360-degree/virtual reality movie viewers\nsuch as the mobile YouTube app), as well as standard screens.\n\n.. image:: _images/scene_diagram.svg\n   :width: 50%\n   :align: center\n   :alt: Diagram of a 3D Scene\n\n.. _scene-description:\n\nVolume Rendering Components\n---------------------------\n\nThe Scene class and its subcomponents are organized as follows.  Indented\nobjects *hang* off of their parent object.\n\n* :ref:`Scene <scene>` - container object describing a volume and its contents\n    * :ref:`Sources <render-sources>` - objects to be rendered\n        * :ref:`VolumeSource <volume-sources>` - simulation volume tied to a dataset\n            * :ref:`TransferFunction <transfer_functions>` - mapping of simulation field values to color, brightness, and transparency\n        * :ref:`OpaqueSource <opaque-sources>` - Opaque structures like lines, dots, etc.\n        * :ref:`Annotations <volume_rendering_annotations>` - Annotated structures like grid cells, simulation boundaries, etc.\n    * :ref:`Camera <camera>` - object for rendering; consists of a location, focus, orientation, and resolution\n        * :ref:`Lens <lenses>` - object describing method for distributing rays through Sources\n\n.. _scene:\n\nScene\n^^^^^\n\nThe :class:`~yt.visualization.volume_rendering.scene.Scene`\nis the container class which encompasses the whole of the volume\nrendering interface.  At its base level, it describes an infinite volume,\nwith a series of\n:class:`~yt.visualization.volume_rendering.render_source.RenderSource` objects\nhanging off of it that describe the contents\nof that volume.  It also contains a\n:class:`~yt.visualization.volume_rendering.camera.Camera` for rendering that\nvolume.  All of its classes can be\naccessed and modified as properties hanging off of the scene.\nThe scene's most important functions are\n:meth:`~yt.visualization.volume_rendering.scene.Scene.render` for\ncasting rays through the scene and\n:meth:`~yt.visualization.volume_rendering.scene.Scene.save` for saving the\nresulting rendered image to disk (see note on :ref:`when_to_render`).\n\nThe easiest way to create a scene with sensible defaults is to use the\nfunctions:\n:func:`~yt.visualization.volume_rendering.volume_rendering.create_scene`\n(creates the scene) or\n:func:`~yt.visualization.volume_rendering.volume_rendering.volume_render`\n(creates the scene and then triggers ray tracing to produce an image).\nSee the :ref:`annotated-vr-example` for details.\n\n.. _render-sources:\n\nRenderSources\n^^^^^^^^^^^^^\n\n:class:`~yt.visualization.volume_rendering.render_source.RenderSource` objects\ncomprise the contents of what is actually *rendered*.  One can add several\ndifferent RenderSources to a Scene and the ray-tracing step will pass rays\nthrough all of them to produce the final rendered image.\n\n.. _volume-sources:\n\nVolumeSources\n+++++++++++++\n\n:class:`~yt.visualization.volume_rendering.render_source.VolumeSource` objects\nare 3D :ref:`geometric-objects` of individual datasets placed into the scene\nfor rendering.  Each VolumeSource requires a\n:ref:`TransferFunction <transfer_functions>` to describe how the fields in\nthe VolumeSource dataset produce different colors and brightnesses in the\nresulting image.\n\n.. _opaque-sources:\n\nOpaqueSources\n+++++++++++++\n\nIn addition to semi-transparent objects, fully opaque structures can be added\nto a scene as\n:class:`~yt.visualization.volume_rendering.render_source.OpaqueSource` objects\nincluding\n:class:`~yt.visualization.volume_rendering.render_source.LineSource` objects\nand\n:class:`~yt.visualization.volume_rendering.render_source.PointSource` objects.\nThese are useful if you want to annotate locations or particles in an image,\nor if you want to draw lines connecting different regions or\nvertices.  For instance, lines can be used to draw outlines of regions or\ncontinents.\n\nWorked examples of using the ``LineSource`` and ``PointSource`` are available at\n:ref:`cookbook-vol-points` and :ref:`cookbook-vol-lines`.\n\n.. _volume_rendering_annotations:\n\nAnnotations\n+++++++++++\n\nSimilar to OpaqueSources, annotations enable the user to highlight\ncertain information with opaque structures.  Examples include\n:class:`~yt.visualization.volume_rendering.api.BoxSource`,\n:class:`~yt.visualization.volume_rendering.api.GridSource`, and\n:class:`~yt.visualization.volume_rendering.api.CoordinateVectorSource`.  These\nannotations will operate in data space and can draw boxes, grid information,\nand also provide a vector orientation within the image.\n\nFor example scripts using these features,\nsee :ref:`cookbook-volume_rendering_annotations`.\n\n.. _transfer_functions:\n\nTransfer Functions\n^^^^^^^^^^^^^^^^^^\n\nA transfer function describes how rays that pass through the domain of a\n:class:`~yt.visualization.volume_rendering.render_source.VolumeSource` are\nmapped from simulation field values to color, brightness, and opacity in the\nresulting rendered image.  A transfer function consists of an array over\nthe x and y dimensions.  The x dimension typically represents field values in\nyour underlying dataset to which you want your rendering to be sensitive (e.g.\ndensity from 1e20 to 1e23).  The y dimension consists of 4 channels for red,\ngreen, blue, and alpha (opacity).  A transfer function starts with all zeros\nfor its y dimension values, implying that rays traversing the VolumeSource\nwill not show up at all in the final image.  However, you can add features to\nthe transfer function that will highlight certain field values in your\nrendering.\n\n.. _transfer-function-helper:\n\nTransferFunctionHelper\n++++++++++++++++++++++\n\nBecause good transfer functions can be difficult to generate, the\n:class:`~yt.visualization.volume_rendering.transfer_function_helper.TransferFunctionHelper`\nexists in order to help create and modify transfer functions with smart\ndefaults for your datasets.\n\nTo ease constructing transfer functions, each ``VolumeSource`` instance has a\n``TransferFunctionHelper`` instance associated with it. This is the easiest way\nto construct and customize a ``ColorTransferFunction`` for a volume rendering.\n\nIn the following example, we make use of the ``TransferFunctionHelper``\nassociated with a scene's ``VolumeSource`` to create an appealing transfer\nfunction between a physically motivated range of densities in a cosmological\nsimulation:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n   sc = yt.create_scene(ds, lens_type=\"perspective\")\n\n   # Get a reference to the VolumeSource associated with this scene\n   # It is the first source associated with the scene, so we can refer to it\n   # using index 0.\n   source = sc[0]\n\n   # Set the bounds of the transfer function\n   source.tfh.set_bounds((3e-31, 5e-27))\n\n   # set that the transfer function should be evaluated in log space\n   source.tfh.set_log(True)\n\n   # Make underdense regions appear opaque\n   source.tfh.grey_opacity = True\n\n   # Plot the transfer function, along with the CDF of the density field to\n   # see how the transfer function corresponds to structure in the CDF\n   source.tfh.plot(\"transfer_function.png\", profile_field=(\"gas\", \"density\"))\n\n   # save the image, flooring especially bright pixels for better contrast\n   sc.save(\"rendering.png\", sigma_clip=6.0)\n\nFor fun, let's make the same volume_rendering, but this time setting\n``grey_opacity=False``, which will make overdense regions stand out more:\n\n.. python-script::\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n   sc = yt.create_scene(ds, lens_type=\"perspective\")\n\n   source = sc[0]\n\n   # Set transfer function properties\n   source.tfh.set_bounds((3e-31, 5e-27))\n   source.tfh.set_log(True)\n   source.tfh.grey_opacity = False\n\n   source.tfh.plot(\"transfer_function.png\", profile_field=(\"gas\", \"density\"))\n\n   sc.save(\"rendering.png\", sigma_clip=4.0)\n\nTo see a full example on how to use the ``TransferFunctionHelper`` interface,\nfollow the annotated :doc:`TransferFunctionHelper_Tutorial`.\n\nColor Transfer Functions\n++++++++++++++++++++++++\n\nA :class:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction`\nis the standard way to map dataset field values to colors, brightnesses,\nand opacities in the rendered rays.  One can add discrete features to the\ntransfer function, which will render isocontours in the field data and\nworks well for visualizing nested structures in a simulation.  Alternatively,\none can also add continuous features to the transfer function.\n\nSee :ref:`cookbook-custom-transfer-function` for an annotated, runnable tutorial\nexplaining usage of the ColorTransferFunction.\n\nThere are several methods to create a\n:class:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction`\nfor a volume rendering. We will describe the low-level interface for\nconstructing color transfer functions here, and provide examples for each\noption.\n\nadd_layers\n\"\"\"\"\"\"\"\"\"\"\n\nThe easiest way to create a ColorTransferFunction is to use the\n:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_layers` function,\nwhich will add evenly spaced isocontours along the transfer function, sampling a\ncolormap to determine the colors of the layers.\n\n.. python-script::\n\n   import numpy as np\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n   sc = yt.create_scene(ds, lens_type=\"perspective\")\n\n   source = sc[0]\n\n   source.set_field((\"gas\", \"density\"))\n   source.set_log(True)\n\n   bounds = (3e-31, 5e-27)\n\n   # Since this rendering is done in log space, the transfer function needs\n   # to be specified in log space.\n   tf = yt.ColorTransferFunction(np.log10(bounds))\n\n   tf.add_layers(5, colormap=\"cmyt.arbre\")\n\n   source.tfh.tf = tf\n   source.tfh.bounds = bounds\n\n   source.tfh.plot(\"transfer_function.png\", profile_field=(\"gas\", \"density\"))\n\n   sc.save(\"rendering.png\", sigma_clip=6)\n\nsample_colormap\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nTo add a single gaussian layer with a color determined by a colormap value, use\n:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.sample_colormap`.\n\n.. python-script::\n\n   import numpy as np\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n   sc = yt.create_scene(ds, lens_type=\"perspective\")\n\n   source = sc[0]\n\n   source.set_field((\"gas\", \"density\"))\n   source.set_log(True)\n\n   bounds = (3e-31, 5e-27)\n\n   # Since this rendering is done in log space, the transfer function needs\n   # to be specified in log space.\n   tf = yt.ColorTransferFunction(np.log10(bounds))\n\n   tf.sample_colormap(np.log10(1e-30), w=0.01, colormap=\"cmyt.arbre\")\n\n   source.tfh.tf = tf\n   source.tfh.bounds = bounds\n\n   source.tfh.plot(\"transfer_function.png\", profile_field=(\"gas\", \"density\"))\n\n   sc.save(\"rendering.png\", sigma_clip=6)\n\n\nadd_gaussian\n\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you would like to add a gaussian with a customized color or no color, use\n:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.add_gaussian`.\n\n.. python-script::\n\n   import numpy as np\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n   sc = yt.create_scene(ds, lens_type=\"perspective\")\n\n   source = sc[0]\n\n   source.set_field((\"gas\", \"density\"))\n   source.set_log(True)\n\n   bounds = (3e-31, 5e-27)\n\n   # Since this rendering is done in log space, the transfer function needs\n   # to be specified in log space.\n   tf = yt.ColorTransferFunction(np.log10(bounds))\n\n   tf.add_gaussian(np.log10(1e-29), width=0.005, height=[0.753, 1.0, 0.933, 1.0])\n\n   source.tfh.tf = tf\n   source.tfh.bounds = bounds\n\n   source.tfh.plot(\"transfer_function.png\", profile_field=(\"gas\", \"density\"))\n\n   sc.save(\"rendering.png\", sigma_clip=6)\n\n\nmap_to_colormap\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nFinally, to map a colormap directly to a range in densities use\n:meth:`~yt.visualization.volume_rendering.transfer_functions.ColorTransferFunction.map_to_colormap`. This\nmakes it possible to map a segment of the transfer function space to a colormap\nat a single alpha value. Where the above options produced layered volume\nrenderings, this allows all of the density values in a dataset to contribute to\nthe volume rendering.\n\n.. python-script::\n\n   import numpy as np\n\n   import yt\n\n   ds = yt.load(\"Enzo_64/DD0043/data0043\")\n\n   sc = yt.create_scene(ds, lens_type=\"perspective\")\n\n   source = sc[0]\n\n   source.set_field((\"gas\", \"density\"))\n   source.set_log(True)\n\n   bounds = (3e-31, 5e-27)\n\n   # Since this rendering is done in log space, the transfer function needs\n   # to be specified in log space.\n   tf = yt.ColorTransferFunction(np.log10(bounds))\n\n\n   def linramp(vals, minval, maxval):\n       return (vals - vals.min()) / (vals.max() - vals.min())\n\n\n   tf.map_to_colormap(\n       np.log10(3e-31), np.log10(5e-27), colormap=\"cmyt.arbre\", scale_func=linramp\n   )\n\n   source.tfh.tf = tf\n   source.tfh.bounds = bounds\n\n   source.tfh.plot(\"transfer_function.png\", profile_field=(\"gas\", \"density\"))\n\n   sc.save(\"rendering.png\", sigma_clip=6)\n\nProjection Transfer Function\n++++++++++++++++++++++++++++\n\nThis is designed to allow you to generate projections like what you obtain\nfrom the standard :ref:`projection-plots`, and it forms the basis of\n:ref:`off-axis-projections`.  See :ref:`cookbook-offaxis_projection` for a\nsimple example.  Note that the integration here is scaled to a width of 1.0;\nthis means that if you want to apply a colorbar, you will have to multiply by\nthe integration width (specified when you initialize the volume renderer) in\nwhatever units are appropriate.\n\nPlanck Transfer Function\n++++++++++++++++++++++++\n\nThis transfer function is designed to apply a semi-realistic color field based\non temperature, emission weighted by density, and approximate scattering based\non the density.  This class is currently under-documented, and it may be best\nto examine the source code to use it.\n\nMore Complicated Transfer Functions\n+++++++++++++++++++++++++++++++++++\n\nFor more complicated transfer functions, you can use the\n:class:`~yt.visualization.volume_rendering.transfer_functions.MultiVariateTransferFunction`\nobject.  This allows for a set of weightings, linkages and so on.\nAll of the information about how all transfer functions are used and values are\nextracted is contained in the sourcefile ``utilities/lib/grid_traversal.pyx``.\nFor more information on how the transfer function is actually applied, look\nover the source code there.\n\n.. _camera:\n\nCamera\n^^^^^^\n\nThe :class:`~yt.visualization.volume_rendering.camera.Camera` object\nis what it sounds like, a camera within the Scene.  It possesses the\nquantities:\n\n* :meth:`~yt.visualization.volume_rendering.camera.Camera.position` - the position of the camera in scene-space\n* :meth:`~yt.visualization.volume_rendering.camera.Camera.width` - the width of the plane the camera can see\n* :meth:`~yt.visualization.volume_rendering.camera.Camera.focus` - the point in space the camera is looking at\n* :meth:`~yt.visualization.volume_rendering.camera.Camera.resolution` - the image resolution\n* ``north_vector`` - a vector defining the \"up\" direction in an image\n* :ref:`lens <lenses>` - an object controlling how rays traverse the Scene\n\n.. _camera_movement:\n\nMoving and Orienting the Camera\n+++++++++++++++++++++++++++++++\n\nThere are multiple ways to manipulate the camera viewpoint and orientation.\nOne can set the properties listed above explicitly, or one can use the\n:class:`~yt.visualization.volume_rendering.camera.Camera` helper methods.\nIn either case, any change triggers an update of all of the other properties.\nNote that the camera exists in a right-handed coordinate system centered on\nthe camera.\n\nRotation-related methods\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.pitch` - rotate about the lateral axis\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.yaw` - rotate about the vertical axis (i.e. ``north_vector``)\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.roll` - rotate about the longitudinal axis (i.e. ``normal_vector``)\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.rotate` - rotate about an arbitrary axis\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.iter_rotate` - iteratively rotate about an arbitrary axis\n\nFor the rotation methods, the camera pivots around the ``rot_center`` rotation\ncenter.  By default, this is the camera position, which means that the\ncamera doesn't change its position at all, it just changes its orientation.\n\nZoom-related methods\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.set_width` - change the width of the FOV\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.zoom` - change the width of the FOV\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.iter_zoom` - iteratively change the width of the FOV\n\nPerhaps counterintuitively, the camera does not get closer to the focus\nduring a zoom; it simply reduces the width of the field of view.\n\nTranslation-related methods\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.set_position` - change the location of the camera keeping the focus fixed\n * :meth:`~yt.visualization.volume_rendering.camera.Camera.iter_move` - iteratively change the location of the camera keeping the focus fixed\n\nThe iterative methods provide iteration over a series of changes in the\nposition or orientation of the camera.  These can be used within a loop.\nFor an example on how to use all of these camera movement functions, see\n:ref:`cookbook-camera_movement`.\n\n.. _lenses:\n\nCamera Lenses\n^^^^^^^^^^^^^\n\nCameras possess :class:`~yt.visualization.volume_rendering.lens.Lens` objects,\nwhich control the geometric path in which rays travel to the camera.  These\nlenses can be swapped in and out of an existing camera to produce different\nviews of the same Scene.  For a full demonstration of a Scene object\nrendered with different lenses, see :ref:`cookbook-various_lens`.\n\nPlane Parallel\n++++++++++++++\n\nThe :class:`~yt.visualization.volume_rendering.lens.PlaneParallelLens` is the\nstandard lens type used for orthographic projections.  All rays emerge\nparallel to each other, arranged along a plane.\n\nPerspective and Stereo Perspective\n++++++++++++++++++++++++++++++++++\n\nThe :class:`~yt.visualization.volume_rendering.lens.PerspectiveLens`\nadjusts for an opening view angle, so that the scene will have an\nelement of perspective to it.\n:class:`~yt.visualization.volume_rendering.lens.StereoPerspectiveLens`\nis identical to PerspectiveLens, but it produces two images from nearby\ncamera positions for use in 3D viewing. How 3D the image appears at viewing\nwill depend upon the value of\n:attr:`~yt.visualization.volume_rendering.lens.StereoPerspectiveLens.disparity`,\nwhich is half the maximum distance between two corresponding points in the left\nand right images. By default, it is set to 3 pixels.\n\n\nFisheye or Dome\n+++++++++++++++\n\nThe :class:`~yt.visualization.volume_rendering.lens.FisheyeLens`\nis appropriate for viewing an arbitrary field of view.  Fisheye images\nare typically used for dome-based presentations; the Hayden planetarium\nfor instance has a field of view of 194.6.  The images returned by this\ncamera will be flat pixel images that can and should be reshaped to the\nresolution.\n\nSpherical and Stereo Spherical\n++++++++++++++++++++++++++++++\n\nThe :class:`~yt.visualization.volume_rendering.lens.SphericalLens` produces\na cylindrical-spherical projection.  Movies rendered in this way can be\ndisplayed as YouTube 360-degree videos (for more information see\n`the YouTube help: Upload 360-degree videos\n<https://support.google.com/youtube/answer/6178631?hl=en>`_).\n:class:`~yt.visualization.volume_rendering.lens.StereoSphericalLens`\nis identical to :class:`~yt.visualization.volume_rendering.lens.SphericalLens`\nbut it produces two images from nearby camera positions for virtual reality\nmovies, which can be displayed in head-tracking devices (e.g. Oculus Rift)\nor in mobile YouTube app with Google Cardboard (for more information\nsee `the YouTube help: Upload virtual reality videos\n<https://support.google.com/youtube/answer/6316263?hl=en>`_).\n`This virtual reality video\n<https://youtu.be/ZYWY53X7UQE>`_ on YouTube is an example produced with\n:class:`~yt.visualization.volume_rendering.lens.StereoSphericalLens`. As in\nthe case of\n:class:`~yt.visualization.volume_rendering.lens.StereoPerspectiveLens`, the\ndifference between the two images can be controlled by changing the value of\n:attr:`~yt.visualization.volume_rendering.lens.StereoSphericalLens.disparity`\n(See above).\n\n.. _annotated-vr-example:\n\nAnnotated Examples\n------------------\n\n.. warning:: 3D visualizations can be fun but frustrating!  Tuning the\n             parameters to both look nice and convey useful scientific\n             information can be hard.  We've provided information about best\n             practices and tried to make the interface easy to develop nice\n             visualizations, but getting them *just right* is often\n             time-consuming.  It's usually best to start out simple and expand\n             and tweak as needed.\n\nThe scene interface provides a modular interface for creating renderings\nof arbitrary data sources. As such, manual composition of a scene can require\na bit more work, but we will also provide several helper functions that attempt\nto create satisfactory default volume renderings.\n\nWhen the\n:func:`~yt.visualization.volume_rendering.volume_rendering.volume_render`\nfunction is called, first an empty\n:class:`~yt.visualization.volume_rendering.scene.Scene` object is created.\nNext, a :class:`~yt.visualization.volume_rendering.api.VolumeSource`\nobject is created, which decomposes the volume elements\ninto a tree structure to provide back-to-front rendering of fixed-resolution\nblocks of data.  (If the volume elements are grids, this uses a\n:class:`~yt.utilities.amr_kdtree.amr_kdtree.AMRKDTree` object.) When the\n:class:`~yt.visualization.volume_rendering.api.VolumeSource`\nobject is created, by default it will create a transfer function\nbased on the extrema of the field that you are rendering. The transfer function\ndescribes how rays that pass through the domain are \"transferred\" and thus how\nbrightness and color correlates to the field values.  Modifying and adjusting\nthe transfer function is the primary way to modify the appearance of an image\nbased on volumes.\n\nOnce the basic set of objects to be rendered is constructed (e.g.\n:class:`~yt.visualization.volume_rendering.scene.Scene`,\n:class:`~yt.visualization.volume_rendering.render_source.RenderSource`, and\n:class:`~yt.visualization.volume_rendering.api.VolumeSource` objects) , a\n:class:`~yt.visualization.volume_rendering.camera.Camera` is created and\nadded to the scene.  By default the creation of a camera also creates a\nplane-parallel :class:`~yt.visualization.volume_rendering.lens.Lens`\nobject. The analog to a real camera is intentional -- a camera can take a\npicture of a scene from a particular point in time and space, but different\nlenses can be swapped in and out.  For example, this might include a fisheye\nlens, a spherical lens, or some other method of describing the direction and\norigin of rays for rendering. Once the camera is added to the scene object, we\ncall the main methods of the\n:class:`~yt.visualization.volume_rendering.scene.Scene` class,\n:meth:`~yt.visualization.volume_rendering.scene.Scene.render` and\n:meth:`~yt.visualization.volume_rendering.scene.Scene.save`.  When rendered,\nthe scene will loop through all of the\n:class:`~yt.visualization.volume_rendering.render_source.RenderSource` objects\nthat have been added and integrate the radiative transfer equations through the\nvolume. Finally, the image and scene object is returned to the user. An example\nscript the uses the high-level :func:`~yt.visualization.volume_rendering.volume_rendering.volume_render`\nfunction to quickly set up defaults is:\n\n.. python-script::\n\n  import yt\n\n  # load the data\n  ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n  # volume render the (\"gas\", \"density\") field, and save the resulting image\n  im, sc = yt.volume_render(ds, (\"gas\", \"density\"), fname=\"rendering.png\")\n\n  # im is the image array generated. it is also saved to 'rendering.png'.\n  # sc is an instance of a Scene object, which allows you to further refine\n  # your renderings and later save them.\n\n  # Let's zoom in and take a closer look\n  sc.camera.width = (300, \"kpc\")\n  sc.camera.switch_orientation()\n\n  # Save the zoomed in rendering\n  sc.save(\"zoomed_rendering.png\")\n\nAlternatively, if you don't want to immediately generate an image of your\nvolume rendering, and you just want access to the default scene object,\nyou can skip the expensive operation of rendering by just running the\n:func:`~yt.visualization.volume_rendering.volume_rendering.create_scene`\nfunction in lieu of the\n:func:`~yt.visualization.volume_rendering.volume_rendering.volume_render`\nfunction. Example:\n\n.. python-script::\n\n    import numpy as np\n\n    import yt\n\n    ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    sc = yt.create_scene(ds, (\"gas\", \"density\"))\n\n    source = sc[0]\n\n    source.transfer_function = yt.ColorTransferFunction(\n        np.log10((1e-30, 1e-23)), grey_opacity=True\n    )\n\n\n    def linramp(vals, minval, maxval):\n        return (vals - vals.min()) / (vals.max() - vals.min())\n\n\n    source.transfer_function.map_to_colormap(\n        np.log10(1e-25), np.log10(8e-24), colormap=\"cmyt.arbre\", scale_func=linramp\n    )\n\n    # For this low resolution dataset it's very important to use interpolated\n    # vertex centered data to avoid artifacts. For high resolution data this\n    # setting may cause a substantial slowdown for marginal visual improvement.\n    source.set_use_ghost_zones(True)\n\n    cam = sc.camera\n\n    cam.width = 15 * yt.units.kpc\n    cam.focus = ds.domain_center\n    cam.normal_vector = [-0.3, -0.3, 1]\n    cam.switch_orientation()\n\n    sc.save(\"rendering.png\")\n\nFor an in-depth tutorial on how to create a Scene and modify its contents,\nsee this annotated :doc:`Volume_Rendering_Tutorial`.\n\n\n.. _volume-rendering-method:\n\nVolume Rendering Method\n-----------------------\n\nDirect ray casting through a volume enables the generation of new types of\nvisualizations and images describing a simulation.  yt has the facility\nto generate volume renderings by a direct ray casting method.  However, the\nability to create volume renderings informed by analysis by other mechanisms --\nfor instance, halo location, angular momentum, spectral energy distributions --\nis useful.\n\nThe volume rendering in yt follows a relatively straightforward approach.\n\n#. Create a set of transfer functions governing the emission and absorption as\n   a function of one or more variables. (:math:`f(v) \\rightarrow (r,g,b,a)`)\n   These can be functions of any field variable, weighted by independent\n   fields, and even weighted by other evaluated transfer functions.  (See\n   ref:`_transfer_functions`.)\n#. Partition all chunks into non-overlapping, fully domain-tiling \"bricks.\"\n   Each of these \"bricks\" contains the finest available data at any location.\n#. Generate vertex-centered data for all grids in the volume rendered domain.\n#. Order the bricks from front-to-back.\n#. Construct plane of rays parallel to the image plane, with initial values set\n   to zero and located at the back of the region to be rendered.\n#. For every brick, identify which rays intersect.  These are then each 'cast'\n   through the brick.\n\n   #. Every cell a ray intersects is sampled 5 times (adjustable by parameter),\n      and data values at each sampling point are trilinearly interpolated from\n      the vertex-centered data.\n   #. Each transfer function is evaluated at each sample point.  This gives us,\n      for each channel, both emission (:math:`j`) and absorption\n      (:math:`\\alpha`) values.\n   #. The value for the pixel corresponding to the current ray is updated with\n      new values calculated by rectangular integration over the path length:\n\n      :math:`v^{n+1}_{i} =  j_{i}\\Delta s + (1 - \\alpha_{i}\\Delta s )v^{n}_{i}`\n\n      where :math:`n` and :math:`n+1` represent the pixel before and after\n      passing through a sample, :math:`i` is the color (red, green, blue) and\n      :math:`\\Delta s` is the path length between samples.\n   #. Determine if any addition integrate will change the sample value; if not,\n      terminate integration.  (This reduces integration time when rendering\n      front-to-back.)\n#. The image is returned to the user:\n\n.. image:: _images/vr_sample.jpg\n   :width: 512\n\nParallelism\n-----------\n\nyt can utilize both MPI and OpenMP parallelism for volume rendering.  Both, and\ntheir combination, are described below.\n\nMPI Parallelization\n^^^^^^^^^^^^^^^^^^^\n\nCurrently the volume renderer is parallelized using MPI to decompose the volume\nby attempting to split up the\n:class:`~yt.utilities.amr_kdtree.amr_kdtree.AMRKDTree` in a balanced way.  This\nhas two advantages:\n\n#.  The :class:`~yt.utilities.amr_kdtree.amr_kdtree.AMRKDTree`\n    construction is parallelized since each MPI task only needs\n    to know about the part of the tree it will traverse.\n#.  Each MPI task will only read data for portion of the volume that it has\n    assigned.\n\nOnce the :class:`~yt.utilities.amr_kdtree.amr_kdtree.AMRKDTree` has been\nconstructed, each MPI task begins the rendering\nphase until all of its bricks are completed.  At that point, each MPI task has\na full image plane which we then use a tree reduction to construct the final\nimage, using alpha blending to add the images together at each reduction phase.\n\nCaveats:\n\n#.  At this time, the :class:`~yt.utilities.amr_kdtree.amr_kdtree.AMRKDTree`\n    can only be decomposed by a power of 2 MPI\n    tasks.  If a number of tasks not equal to a power of 2 are used, the largest\n    power of 2 below that number is used, and the remaining cores will be idle.\n    This issue is being actively addressed by current development.\n#.  Each MPI task, currently, holds the entire image plane.  Therefore when\n    image plane sizes get large (>2048^2), the memory usage can also get large,\n    limiting the number of MPI tasks you can use.  This is also being addressed\n    in current development by using image plane decomposition.\n\nFor more information about enabling parallelism, see :ref:`parallel-computation`.\n\nOpenMP Parallelization\n^^^^^^^^^^^^^^^^^^^^^^\n\nThe volume rendering also parallelized using the OpenMP interface in Cython.\nWhile the MPI parallelization is done using domain decomposition, the OpenMP\nthreading parallelizes the rays intersecting a given brick of data.  As the\naverage brick size relative to the image plane increases, the parallel\nefficiency increases.\n\nBy default, the volume renderer will use the total number of cores available on\nthe symmetric multiprocessing (SMP) compute platform.  For example, if you have\na shiny new laptop with 8 cores, you'll by default launch 8 OpenMP threads.\nThe number of threads can be controlled with the num_threads keyword in\n:meth:`~yt.visualization.volume_rendering.camera.Camera.snapshot`.  You may also restrict the number of OpenMP threads used\nby default by modifying the environment variable OMP_NUM_THREADS.\n\nRunning in Hybrid MPI + OpenMP\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe two methods for volume rendering parallelization can be used together to\nleverage large supercomputing resources.  When choosing how to balance the\nnumber of MPI tasks vs OpenMP threads, there are a few things to keep in mind.\nFor these examples, we will assume you are using Nmpi MPI tasks, and Nmp OpenMP\ntasks, on a total of P cores. We will assume that the machine has a Nnode SMP\nnodes, each with cores_per_node cores per node.\n\n#.  For each MPI task, num_threads (or OMP_NUM_THREADS) OpenMP threads will be\n    used. Therefore you should usually make sure that Nmpi*Nmp = P.\n#.  For simulations with many grids/AMRKDTree bricks, you generally want to increase Nmpi.\n#.  For simulations with large image planes (>2048^2), you generally want to\n    decrease Nmpi and increase Nmp. This is because, currently, each MPI task\n    stores the entire image plane, and doing so can approach the memory limits\n    of a given SMP node.\n#.  Please make sure you understand the (super)computer topology in terms of\n    the numbers of cores per socket, node, etc when making these decisions.\n#.  For many cases when rendering using your laptop/desktop, OpenMP will\n    provide a good enough speedup by default that it is preferable to launching\n    the MPI tasks.\n\nFor more information about enabling parallelism, see :ref:`parallel-computation`.\n\n.. _vr-faq:\n\nVolume Rendering Frequently Asked Questions\n-------------------------------------------\n\n.. _opaque_rendering:\n\nOpacity\n^^^^^^^\n\nThere are currently two models for opacity when rendering a volume, which are\ncontrolled in the ``ColorTransferFunction`` with the keyword\n``grey_opacity=False`` or ``True`` (the default). The first will act such for\neach of the red, green, and blue channels, each channel is only opaque to\nitself.  This means that if a ray that has some amount of red then encounters\nmaterial that emits blue, the red will still exist and in the end that pixel\nwill be a combination of blue and red.  However, if the ColorTransferFunction is\nset up with grey_opacity=True, then blue will be opaque to red, and only the\nblue emission will remain.\n\nFor an in-depth example, please see the cookbook example on opaque renders here:\n:ref:`cookbook-opaque_rendering`.\n\n.. _sigma_clip:\n\nImproving Image Contrast with Sigma Clipping\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf your images appear to be too dark, you can try using the ``sigma_clip``\nkeyword in the :meth:`~yt.visualization.volume_rendering.scene.Scene.save`\nor :func:`~yt.visualization.volume_rendering.volume_rendering.volume_render`\nfunctions.  Because the brightness range in an image is scaled to match the\nrange of emissivity values of underlying rendering, if you have a few really\nhigh-emissivity points, they will scale the rest of your image to be quite\ndark.  ``sigma_clip = N`` can address this by removing values that are more\nthan ``N`` standard deviations brighter than the mean of your image.\nTypically, a choice of 4 to 6 will help dramatically with your resulting image.\nSee the cookbook recipe :ref:`cookbook-sigma_clip` for a demonstration.\n\n.. _when_to_render:\n\nWhen to Render\n^^^^^^^^^^^^^^\n\nThe rendering of a scene is the most computationally demanding step in\ncreating a final image and there are a number of ways to control at which point\na scene is actually rendered. The default behavior of the\n:meth:`~yt.visualization.volume_rendering.scene.Scene.save` function includes\na call to :meth:`~yt.visualization.volume_rendering.scene.Scene.render`. This\nmeans that in most cases (including the above examples), after you set up your\nscene and volumes, you can simply call\n:meth:`~yt.visualization.volume_rendering.scene.Scene.save` without first\ncalling :meth:`~yt.visualization.volume_rendering.scene.Scene.render`. If you\nwish to save the most recently rendered image without rendering again, set\n``render=False`` in the call to\n:meth:`~yt.visualization.volume_rendering.scene.Scene.save`.  Cases where you\nmay wish to use ``render=False`` include saving images at different\n``sigma_clip`` values (see :ref:`cookbook-sigma_clip`) or when saving an image\nthat has already been rendered in a Jupyter notebook using\n:meth:`~yt.visualization.volume_rendering.scene.Scene.show`. Changes to the\nscene including adding sources, modifying transfer functions or adjusting camera\nsettings generally require rendering again.\n"
  },
  {
    "path": "doc/source/yt3differences.rst",
    "content": ".. _yt3differences:\n\nWhat's New and Different in yt 3.0?\n===================================\n\nIf you are new to yt, welcome!  If you're coming to yt 3.0 from an older\nversion, however, there may be a few things in this version that are different\nthan what you are used to.  We have tried to build compatibility layers to\nminimize disruption to existing scripts, but necessarily things will be\ndifferent in some ways.\n\n.. contents::\n   :depth: 2\n   :local:\n   :backlinks: none\n\nUpdating to yt 3.0 from Old Versions (and going back)\n-----------------------------------------------------\n\nFirst off, you need to update your version of yt to yt 3.0.  If you're\ninstalling yt for the first time, please visit :ref:`installing-yt`.\nIf you already have a version of yt installed, you should just need one\ncommand:\n\n.. code-block:: bash\n\n    $ yt update\n\nThis will update yt to the most recent version and rebuild the source base.\nIf you installed using the installer script, it will assure you have all of the\nlatest dependencies as well.  This step may take a few minutes.  To test\nthat yt is correctly installed, try:\n\n.. code-block:: bash\n\n    $ python -c \"import yt\"\n\n\n.. _transitioning-to-3.0:\n\nConverting Old Scripts to Work with yt 3.0\n------------------------------------------\n\nAfter installing yt-3.0, you'll want to change your old scripts in a few key\nways.  After accounting for the changes described in the list below, try\nrunning your script.  If it still fails, the callback failures in python are\nfairly descriptive and it may be possible to deduce what remaining changes are\nnecessary.  If you continue to have trouble, please don't hesitate to\n:ref:`request help <asking-for-help>`.\n\nThe list below is arranged in order of most important changes to least\nimportant changes.\n\n* **Replace** ``from yt.mods import *`` **with** ``import yt`` **and prepend yt\n  classes and functions with** ``yt.``\n  We have reworked yt's import system so that most commonly-used yt functions\n  and classes live in the top-level yt namespace. That means you can now\n  import yt with ``import yt``, load a dataset with ``ds = yt.load(filename)``\n  and create a plot with ``yt.SlicePlot``.  See :ref:`api-reference` for a full\n  API listing.  You can still import using ``from yt.mods import *`` to get a\n  pylab-like experience.\n* **Unit conversions are different**\n  Fields and metadata for data objects and datasets now have units.  The unit\n  system keeps you from making weird things like ``ergs`` + ``g`` and can\n  handle things like ``g`` + ``kg`` or ``kg*m/s**2 == Newton``.  See\n  :ref:`units` and :ref:`conversion-factors` for more information.\n* **Change field names from CamelCase to lower_case_with_underscores**\n  Previously, yt would use \"Enzo-isms\" for field names. We now very\n  specifically define fields as lowercase with underscores.  For instance,\n  what used to be ``VelocityMagnitude`` would now be ``velocity_magnitude``.\n  Axis names are now at the *end* of field names, not the beginning.\n  ``x-velocity`` is now ``velocity_x``.  For a full list of all of the fields,\n  see :ref:`field-list`.\n* **Full field names have two parts now**\n  Fields can be accessed by a single name, but they are named internally as\n  ``(field_type, field_name)`` for more explicit designation which can address\n  particles, deposited fluid quantities, and more.  See :ref:`fields`.\n* **Code-specific field names can be accessed by the name defined by the\n  external code**\n  Mesh fields that exist on-disk in an output file can be read in using whatever\n  name is used by the output file.  On-disk fields are always returned in code\n  units.  The full field name will be ``(code_name, field_name)``. See\n  :ref:`field-list`.\n* **Particle fields are now more obviously different than mesh fields**\n  Particle fields on-disk will also be in code units, and will be named\n  ``(particle_type, field_name)``.  If there is only one particle type in the\n  output file, all particles will use ``io`` as the particle type. See\n  :ref:`fields`.\n* **Change** ``pf`` **to** ``ds``\n  The objects we used to refer to as \"parameter files\" we now refer to as\n  datasets.  Instead of ``pf``, we now suggest you use ``ds`` to refer to an\n  object returned by ``yt.load``.\n* **Remove any references to** ``pf.h`` **with** ``ds``\n  You can now create data objects without referring to the hierarchy. Instead\n  of ``pf.h.all_data()``, you can now say ``ds.all_data()``.  The hierarchy is\n  still there, but it is now called the index: ``ds.index``.\n* **Use** ``yt.enable_parallelism()`` **to make a script parallel-compatible**\n  Command line arguments are only parsed when yt is imported using ``from\n  yt.mods import *``. Since command line arguments are not parsed when using\n  ``import yt``, it is no longer necessary to specify ``--parallel`` at the\n  command line when running a parallel computation. Use\n  ``yt.enable_parallelism()`` in your script instead.  See\n  :ref:`parallel-computation` for more details.\n* **Change your derived quantities to the new syntax**\n  Derived quantities have been reworked.  You can now do\n  ``dd.quantities.total_mass()`` instead of ``dd.quantities['TotalMass']()``.\n  See :ref:`derived-quantities`.\n* **Change your method of accessing the** ``grids`` **attribute**\n  The ``grids`` attribute of data objects no longer exists.  To get this\n  information, you have to use spatial chunking and then access them.  See\n  :ref:`here <grid-chunking>` for an example.  For datasets that use grid\n  hierarchies, you can also access the grids for the entire dataset via\n  ``ds.index.grids``.  This attribute is not defined for particle or octree\n  datasets.\n\nCool New Things\n---------------\n\nLots of new things have been added in yt 3.0!  Below we summarize a handful of\nthese.\n\nLots of New Codes are Supported\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nBecause of the additions of **Octrees**, **Particle Deposition**,\nand **Irregular Grids**, we now support a bunch more codes.  See\n:ref:`code-support` for more information.\n\nOctrees\n^^^^^^^\n\nOctree datasets such as RAMSES, ART and ARTIO are now supported -- without any\nregridding!  We have a native, lightweight octree indexing system.\n\nIrregular Grids\n^^^^^^^^^^^^^^^\n\nMOAB Hex8 format is supported, and non-regular grids can be added relatively\neasily.\n\nBetter Particle Support\n^^^^^^^^^^^^^^^^^^^^^^^\n\nParticle Codes and SPH\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nyt 3.0 features particle selection, smoothing, and deposition.  This utilizes a\ncombination of coarse-grained indexing and octree indexing for particles.\n\nParticle Deposition\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIn yt-3.0, we provide mechanisms for describing and creating fields generated\nby depositing particles into one or a handful of zones.  This could include\ndeposited mass or density, average values, and the like.  For instance, the\ntotal stellar mass in some region can be deposited and averaged.\n\nParticle Filters and Unions\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThroughout yt, the notion of \"particle types\" has been more deeply embedded.\nThese particle types can be dynamically defined at runtime, for instance by\ntaking a filter of a given type or the union of several different types.  This\nmight be, for instance, defining a new type called ``young_stars`` that is a\nfiltering of ``star_age`` to be fewer than a given threshold, or ``fast`` that\nfilters based on the velocity of a particle.  Unions could be the joining of\nmultiple types of particles -- the default union of which is ``all``,\nrepresenting all particle types in the simulation.\n\nUnits\n^^^^^\n\nyt now has a unit system.  This is one of the bigger features, and in essence it means\nthat you can convert units between anything.  In practice, it makes it much\neasier to define fields and convert data between different unit systems. See\n:ref:`units` for more information.\n\nNon-Cartesian Coordinates\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nPreliminary support for non-cartesian coordinates has been added.  We expect\nthis to be considerably solidified and expanded in yt 3.1.\n\nReworked Import System\n^^^^^^^^^^^^^^^^^^^^^^\n\nIt's now possible to import all yt functionality using ``import yt``. Rather\nthan using ``from yt.mods import *``, we suggest using ``import yt`` in new\nscripts.  Most commonly used yt functionality is attached to the ``yt`` module.\nLoad a dataset with ``yt.load()``, create a phase plot using ``yt.PhasePlot``,\nand much more, see :ref:`the api docs <api-reference>` to learn more about what's\nin the ``yt`` namespace, or just use tab completion in IPython: ``yt.<tab>``.\n\nIt's still possible to use ``from yt.mods import *`` to create an interactive\npylab-like experience.  Importing yt this way has several side effects, most\nnotably the command line arguments parsing and other startup tasks will run.\n\nAPI Changes\n-----------\n\nThese are the items that have already changed in *user-facing* API:\n\nField Naming\n^^^^^^^^^^^^\n\n.. warning:: Field naming is probably the single biggest change you will\n             encounter in yt 3.0.\n\nFields can be accessed by their short names, but yt now has an explicit\nmechanism of distinguishing between field types and particle types.  This is\nexpressed through a two-key description.  For example::\n\n   my_object[\"gas\", \"density\"]\n\nwill return the gas field density.  In this example \"gas\" is the field type and\n\"density\" is the field name.  Field types are a bit like a namespace.  This\nsystem extends to particle types as well.  By default you do *not* need to use\nthe field \"type\" key, but in case of ambiguity it will utilize the default value\nin its place.  This should therefore be identical to::\n\n   my_object[\"density\"]\n\nTo enable a compatibility layer, on the dataset you simply need to call the\nmethod ``setup_deprecated_fields`` like so:\n\n.. code-block:: python\n\n   ds = yt.load(\"MyData\")\n   ds.setup_deprecated_fields()\n\nThis sets up aliases from the old names to the new.  See :ref:`fields` and\n:ref:`field-list` for more information.\n\nUnits of Fields\n^^^^^^^^^^^^^^^\n\nFields now are all subclasses of NumPy arrays, the ``YTArray``, which carries\nalong with it units.  This means that if you want to manipulate fields, you\nhave to modify them in a unitful way.  See :ref:`units`.\n\nParameter Files are Now Datasets\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWherever possible, we have attempted to replace the term \"parameter file\"\n(i.e., ``pf``) with the term \"dataset.\"  In yt-3.0, all of\nthe ``pf`` attributes of objects are now ``ds`` or ``dataset`` attributes.\n\nHierarchy is Now Index\n^^^^^^^^^^^^^^^^^^^^^^\n\nThe hierarchy object (``pf.h``) is now referred to as an index (``ds.index``).\nIt is no longer necessary to directly refer to the ``index`` as often, since\ndata objects are now attached to the to the ``dataset`` object.  Before, you\nwould say ``pf.h.sphere()``, now you can say ``ds.sphere()``.\n\nNew derived quantities interface\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nDerived quantities can now be accessed via a function that hangs off of the\n``quantities`` attribute of data objects. Instead of\n``dd.quantities['TotalMass']()``, you can now use ``dd.quantities.total_mass()``\nto do the same thing. All derived quantities can be accessed via a function that\nhangs off of the ``quantities`` attribute of data objects.\n\nAny derived quantities that *always* returned lists (like ``Extrema``, which\nwould return a list even if you only ask for one field) now only returns a\nsingle result if you only ask for one field.  Results for particle and mesh\nfields will also be returned separately.  See :ref:`derived-quantities` for more\ninformation.\n\n\nField Info\n^^^^^^^^^^\n\nIn previous versions of yt, the ``dataset`` object (what we used to call a\nparameter file) had a ``field_info`` attribute which was a dictionary leading to\nderived field definitions.  At the present time, because of the field naming\nchanges (i.e., access-by-tuple) it is better to utilize the function\n``_get_field_info`` than to directly access the ``field_info`` dictionary.  For\nexample::\n\n   finfo = ds._get_field_info(\"gas\", \"density\")\n\nThis function respects the special \"field type\" ``unknown`` and will search all\nfield types for the field name.\n\nProjection Argument Order\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nPreviously, projections were inconsistent with the other data objects.\n(The API for Plot Windows is the same.)  The argument order is now ``field``\nthen ``axis`` as seen here:\n:class:`~yt.data_objects.construction_data_containers.YTQuadTreeProj`.\n\nField Parameters\n^^^^^^^^^^^^^^^^\n\nAll data objects now accept an explicit list of ``field_parameters`` rather\nthan accepting ``kwargs`` and supplying them to field parameters.  See\n:ref:`field_parameters`.\n\nObject Renaming\n^^^^^^^^^^^^^^^\n\nNearly all internal objects have been renamed.  Typically this means either\nremoving ``AMR`` from the prefix or replacing it with ``YT``.  All names of\nobjects remain the same for the purposes of selecting data and creating them;\ni.e., ``sphere`` objects are still called ``sphere`` - you can access or create one\nvia ``ds.sphere``.  For a detailed description and index see\n:ref:`available-objects`.\n\nBoolean Regions\n^^^^^^^^^^^^^^^\n\nBoolean regions are not yet implemented in yt 3.0.\n\n.. _grid-chunking:\n\nGrids\n^^^^^\n\nIt used to be that one could get access to the grids that belonged to a data\nobject.  Because we no longer have just grid-based data in yt, this attribute\ndoes not make sense.  If you need to determine which grids contribute to a\ngiven object, you can either query the ``grid_indices`` field, or mandate\nspatial chunking like so:\n\n.. code-block:: python\n\n   for chunk in obj.chunks([], \"spatial\"):\n       for grid in chunk._current_chunk.objs:\n           print(grid)\n\nThis will \"spatially\" chunk the ``obj`` object and print out all the grids\nincluded.\n\nHalo Catalogs\n^^^^^^^^^^^^^\n\nThe ``Halo Profiler`` infrastructure has been fundamentally rewritten and now\nexists using the ``Halo Catalog`` framework.  See :ref:`halo-analysis`.\n\nAnalysis Modules\n^^^^^^^^^^^^^^^^\n\nWhile we're trying to port over all of the old analysis modules, we have not\ngotten all of them working in 3.0 yet.  The docs pages for those modules\nnot-yet-functioning are clearly marked.\n"
  },
  {
    "path": "doc/source/yt4differences.rst",
    "content": ".. _yt4differences:\n\nWhat's New and Different in yt 4.0?\n===================================\n\nIf you are new to yt, welcome!  If you're coming to yt 4.0 from an older\nversion, however, there may be a few things in this version that are different\nthan what you are used to.  We have tried to build compatibility layers to\nminimize disruption to existing scripts, but necessarily things will be\ndifferent in some ways.\n\n.. contents::\n   :depth: 2\n   :local:\n   :backlinks: none\n\nUpdating to yt 4.0 from Old Versions (and going back)\n-----------------------------------------------------\n\n\n.. _transitioning-to-4.0:\n\nConverting Old Scripts to Work with yt 4.0\n------------------------------------------\n\nAfter installing yt-4.0, you’ll want to change your old scripts in a few key\nways. After accounting for the changes described in the list below, try\nrunning your script. If it still fails, the Python tracebacks\nshould be fairly descriptive and it may be possible to deduce what remaining\nchanges are necessary. If you continue to have trouble, please don’t hesitate\nto :ref:`request help <asking-for-help>`.\n\nThe list below is arranged in order of most to least important changes.\n\n* **Fields should be specified as tuples not as strings**\n  In the past, you could specify fields as strings like ``\"density\"``, but\n  with the growth of yt and its many derived fields, there can be sometimes\n  be overlapping field names (e.g., ``(\"gas\", \"density\")`` and\n  ``(\"PartType0\", \"density\")``), where yt doesn't know which to use.  To remove\n  any ambiguity, it is now strongly recommended to explicitly specify the full\n  tuple form of all fields. Just search for all field accesses in your scripts,\n  and replace strings with tuples (e.g. replace ``\"a\"``  with\n  ``(\"gas\", \"a\" )``).  There is a compatibility rule in yt-4.0 to allow strings\n  to continue to work until yt-4.1, but you may get unexpected behavior.  Any\n  field specifications that are ambiguous will throw an error in future\n  versions of yt.  See our :ref:`fields`, and :ref:`available field list\n  <available-fields>` documentation for more information.\n* **Use Newer Versions of Python**\n  The yt-4.0 release will be the final release of yt to support Python 3.6.\n  Starting with yt-4.1, python 3.6 will no longer be supported, so please\n  start using 3.7+ as soon as possible.\n* **Particle-based datasets no longer accept n_ref and over_refine_factor**\n  One of the major upgrades in yt-4 is native treatment of particle-based\n  datasets.  This is in contrast to previous yt behavior which loaded particle-based\n  datasets as octrees, which could then be treated like grid-based datasets.\n  In order to define the octrees, users were required to specify ``n_ref``\n  and ``over_refine_factor`` values at load time.  Please remove\n  any reference to ``n_ref`` and ``over_refine_factor`` in your scripts.\n* **Neutral ion fields changing format**\n  In previous versions, neutral ion fields were specified as\n  ``ELEMENT_number_density`` (e.g., ``H_number_density`` to represent H I\n  number density).  This led to a lot of confusion, because some people assumed\n  these fields were the total hydrogen density, not neutral hydrogen density.\n  In yt-4.0, we have resolved this issue by explicitly calling total hydrogen\n  number density ``H_nuclei_density`` and neutral hydrogen density\n  ``H_p0_number_density`` (where ``p0`` refers to plus 0 charge).  This syntax\n  follows the rule for other ions: H II = ``H_p1`` = ionized hydrogen.  Change\n  your scripts accordingly.  See :ref:`species-fields` for more information.\n* **Change in energy and momentum field names**\n  Fields representing energy and momentum quantities are now given names which\n  reflect their dimensionality. For example, the ``(\"gas\", \"kinetic_energy\")``\n  field was actually a field for kinetic energy density, and so it has been\n  renamed to ``(\"gas\", \"kinetic_energy_density\")``. The old name still exists\n  as an alias as of yt v4.0.0, but it will be removed in yt v4.1.0. See\n  next item below for more information.\n  Other examples include ``\"gas\", \"specific_thermal_energy\"`` for thermal\n  energy per unit mass, and ``(\"gas\", \"momentum_density_x\")`` for the x-axis\n  component of momentum density. See :ref:`efields` for more information.\n* **Deprecated field names**\n  Certain field names are deprecated within yt v4.0.x and removed in\n  yt v4.1. For example, ``(\"gas\", \"kinetic_energy\")`` has been renamed to\n  ``(\"gas\", \"kinetic_energy_density\")``, though the former name has been added\n  as an alias. Other fields, such as\n  ``(\"gas\", \"cylindrical_tangential_velocity_absolute\")``, are being removed\n  entirely. When the deprecated field names are used for the first time in a\n  session, a warning will be logged, so it is advisable to set\n  your logging level to ``WARNING`` (``yt.set_log_level(\"error\")``) at a\n  minimum to catch these.  See :ref:`faq-log-level` for more information on\n  setting your log level and :ref:`available-fields` to see all available\n  fields.\n* ``cmocean`` **colormaps need prefixing**\n  yt used to automatically load and register external colormaps from the\n  ``cmocean`` package unprefixed (e.g., ``set_cmap(FIELD, \"balance\")``.  This\n  became unsustainable with the 3.4 release of Matplotlib, in which colormaps\n  with colliding names raise errors. The fix is to explicitly import the\n  ``cmocean`` module and prefix ``cmocean`` colormaps (like ``balance``) with\n  ``cmo.`` (e.g., ``cmo.balance``).  Note that this solution works with any\n  yt-supported version of Matplotlib, but is not backward compatible with\n  earlier versions of yt.\n* Position and velocity fields now default to using linear scaling in profiles\n  and phase plots, whereas previously behavior was determined by whether the\n  dataset was particle- or grid-based.  Efforts have been made to standardize\n  the treatment of other fields in profile and phase plots for particle and\n  grid datasets.\n\nImportant New Aliases\n^^^^^^^^^^^^^^^^^^^^^\n\nWith the advent of supporting SPH data at the particle level instead of smoothing\nonto an octree (see below), a new alias for both gas particle masses and cell masses\nhas been created: ``(\"gas\", \"mass\")``, which aliases to ``(\"gas\", \"cell_mass\")`` for\ngrid-based frontends and to the gas particle mass for SPH frontends. In a number of\nplaces in yt, code that used ``(\"gas\", \"cell_mass\")`` has been replaced by\n``(\"gas\", \"mass\")``. Since the latter is an alias for the former, old scripts which\nuse ``(\"gas\", \"cell_mass\")`` should not break.\n\nDeprecations\n^^^^^^^^^^^^\n\nThe following methods and method arguments are deprecated as of yt 4.0 and will be\nremoved in yt 4.1\n\n * :meth:`~yt.visualization.plot_window.PlotWindow.set_window_size` is deprecated\n   in favor to :meth:`~yt.visualization.plot_container.PlotContainer.set_figure_size`\n * :meth:`~yt.visualization.eps_writer.return_cmap` is deprecated in favor to\n   :meth:`~yt.visualization.eps_writer.return_colormap`\n * :meth:`~yt.data_objects.derived_quantities.WeightedVariance` is deprecated in favor\n   to :meth:`~yt.data_objects.derived_quantities.WeightedStandardDeviation`\n * :meth:`~yt.visualization.plot_window.PWViewerMPL.annotate_clear` is deprecated in\n   favor to :meth:`~yt.visualization.plot_window.PWViewerMPL.clear_annotations`\n * :meth:`~yt.visualization.color_maps.add_cmap` is deprecated in favor to\n   :meth:`~yt.visualization.color_maps.add_colormap`\n * :meth:`~yt.loaders.simulation` is deprecated in favor to :meth:`~yt.loaders.load_simulation`\n * :meth:`~yt.data_objects.index_subobjects.octree_subset.OctreeSubset.get_vertex_centered_data`\n   now takes a list of fields as input, passing a single field is deprecated\n * manually updating the ``periodicity`` attributed of a :class:`~yt.data_objects.static_output.Dataset` object is deprecated. Use the\n   :meth:`~yt.data_objects.static_output.Dataset.force_periodicity` if you need to force periodicity to ``True`` or ``False`` along all axes.\n * the :meth:`~yt.data_objects.static_output.Dataset.add_smoothed_particle_field` method is deprecated and already has no effect in yt 4.0 .\n   See :ref:`sph-data`\n * the :meth:`~yt.data_objects.static_output.Dataset.add_gradient_fields` used to accept an ``input_field`` keyword argument, now deprecated\n   in favor to ``fields``\n * :meth:`~yt.data_objects.time_series.DatasetSeries.from_filenames` is deprecated because its functionality is now\n   included in the basic ``__init__`` method. Use :class:`~yt.data_objects.time_series.DatasetSeries` directly.\n * the ``particle_type`` keyword argument from ``yt.add_field()`` (:meth:`~yt.fields.field_info_container.FieldInfoContainer.add_field`) and ``ds.add_field()`` (:meth:`~yt.data_objects.static_output.Dataset.add_field`) methods is now a deprecated in favor to\n   the ``sampling_type`` keyword argument.\n * the :meth:`~yt.fields.particle_fields.add_volume_weighted_smoothed_field` is deprecated and already has no effect in yt 4.0 .\n   See :ref:`sph-data`\n * the :meth:`~yt.utilities.amr_kdtree.amr_kdtree.AMRKDTree.locate_brick` method is deprecated in favor to, and is now an alias for :meth:`~yt.utilities.amr_kdtree.amr_kdtree.AMRKDTree.locate_node`\n * the :class:`~yt.utilities.exceptions.YTOutputNotIdentified` error is a deprecated alias for :class:`~yt.utilities.exceptions.YTUnidentifiedDataType`\n * the ``limits`` argument from :meth:`~yt.visualization.image_writer.write_projection` is deprecated in\n   favor to ``vmin`` and ``vmax``\n * :meth:`~yt.visualization.plot_container.ImagePlotContainer.set_cbar_minorticks` is a deprecated alias for :meth:`~yt.visualization.plot_container.ImagePlotContainer.set_colorbar_minorticks`\n * the ``axis`` argument from :meth:`yt.visualization.plot_window.SlicePlot` is a deprecated alias for the ``normal`` argument\n * the old configuration file ``ytrc`` is deprecated in favor of the new ``yt.toml`` format. In yt 4.0,\n   you'll get a warning every time you import yt if you're still using the old configuration file,\n   which will instruct you to invoke the yt command line interface to convert automatically to the new format.\n * the ``load_field_plugins`` parameter is deprecated from the configuration file (note that it is already not used as of yt 4.0)\n\n\nCool New Things\n---------------\n\nChanges for Working with SPH Data\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIn yt-3.0 most user-facing operations on SPH data are produced by interpolating\nSPH data onto a volume-filling octree mesh. Historically this was easier to\nimplement When support for SPH data was added to yt as it allowed re-using a lot\nof the existing infrastructure. This had some downsides because the octree was a\nsingle, global object, the memory and CPU overhead of smoothing SPH data onto\nthe octree can be prohibitive on particle datasets produced by large\nsimulations. Constructing the octree during the initial indexing phase also\nrequired each particle (albeit, in a 64-bit integer) to be present in memory\nsimultaneously for a sorting operation, which was memory prohibitive.\nVisualizations of slices and projections produced by yt using the default\nsettings are somewhat blocky since by default we use a relatively coarse octree\nto preserve memory.\n\nIn yt-4.0 this has all changed! Over the past two years, Nathan Goldbaum, Meagan\nLang and Matt Turk implemented a new approach for handling I/O of particle data,\nbased on storing compressed bitmaps containing Morton indices instead of an\nin-memory octree. This new capability means that the global octree index is now\nno longer necessary to enable I/O chunking and spatial indexing of particle data\nin yt.\n\nThe new I/O method has opened up a new way of dealing with the particle data and\nin particular, SPH data.\n\n.. _sph-data:\n\nScatter and Gather approach for SPH data\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAs mentioned, previously operations such as slice, projection and arbitrary\ngrids would smooth the particle data onto the global octree. As this is no\nlonger used, a different approach was required to visualize the SPH data. Using\nSPLASH as inspiration, SPH smoothing pixelization operations were created using\nsmoothing operations via \"scatter\" and \"gather\" approaches. We estimate the\ncontributions of a particle to a single pixel by considering the point at the\ncentre of the pixel and using the standard SPH smoothing formula. The heavy\nlifting in these functions is undertaken by cython functions.\n\nIt is now possible to generate slice plots, projection plots, covering grids and\narbitrary grids of smoothed quantities using these operations. The following\ncode demonstrates how this could be achieved. The following would use the scatter\nmethod:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\"snapshot_033/snap_033.0.hdf5\")\n\n    plot = yt.SlicePlot(ds, 2, (\"gas\", \"density\"))\n    plot.save()\n\n    plot = yt.ProjectionPlot(ds, 2, (\"gas\", \"density\"))\n    plot.save()\n\n    arbitrary_grid = ds.arbitrary_grid([0.0, 0.0, 0.0], [25, 25, 25], dims=[16, 16, 16])\n    ag_density = arbitrary_grid[\"gas\", \"density\"]\n\n    covering_grid = ds.covering_grid(4, 0, 16)\n    cg_density = covering_grid[\"gas\", \"density\"]\n\nIn the above example the ``covering_grid`` and the ``arbitrary_grid`` will return\nthe same data. In fact, these containers are very similar but provide a\nslightly different API.\n\nThe above code can be modified to use the gather approach by changing a global\nsetting for the dataset. This can be achieved with\n``ds.sph_smoothing_style = \"gather\"``, so far, the gather approach is not\nsupported for projections.\n\nThe default behaviour for SPH interpolation is that the values are normalized\ninline with Eq. 9 in `SPLASH, Price (2009) <https://arxiv.org/pdf/0709.0832.pdf>`_.\nThis can be disabled with ``ds.use_sph_normalization = False``. This will\ndisable the normalization for all future interpolations.\n\nThe gather approach requires finding nearest neighbors using the KDTree. The\nfirst call will generate a KDTree for the entire dataset which will be stored in\na sidecar file. This will be loaded whenever necessary.\n\nOff-Axis Projection for SPH Data\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe current ``OffAxisProjectionPlot`` class will now support SPH projection plots.\n\nThe following is a code example:\n\n.. code-block:: python\n\n    import yt\n\n    ds = yt.load(\"Data/GadgetDiskGalaxy/snapshot_200.hdf5\")\n\n    smoothing_field = (\"gas\", \"density\")\n\n    _, center = ds.find_max(smoothing_field)\n\n    sp = ds.sphere(center, (10, \"kpc\"))\n\n    normal_vector = sp.quantities.angular_momentum_vector()\n\n    prj = yt.OffAxisProjectionPlot(ds, normal_vector, smoothing_field, center, (20, \"kpc\"))\n\n    prj.save()\n\nSmoothing Data onto an Octree\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhilst the move away from the global octree is a promising one in terms of\nperformance and dealing with SPH data in a more intuitive manner, it does remove\na useful feature. We are aware that many users will have older scripts which take\nadvantage of the global octree.\n\nAs such, we have added support to smooth SPH data onto an octree when desired by\nthe users. The new octree is designed to give results consistent with those of\nthe previous octree, but the new octree takes advantage of the scatter and\ngather machinery also added.\n\n.. code-block:: python\n\n    import numpy as np\n\n    import yt\n\n    ds = yt.load(\"GadgetDiskGalaxy/snapshot_200.hdf5\")\n    left = np.array([0, 0, 0], dtype=\"float64\")\n    right = np.array([64000, 64000, 64000], dtype=\"float64\")\n\n    # generate an octree\n    octree = ds.octree(left, right, n_ref=64)\n\n    # Scatter deposition is the default now, and thus this will print scatter\n    print(octree.sph_smoothing_style)\n\n    # the density will be calculated using SPH scatter\n    density = octree[\"PartType0\", \"density\"]\n\n    # this will return the x positions of the octs\n    x = octree[\"index\", \"x\"]\n\nThe above code can be modified to use the gather approach by using\n``ds.sph_smoothing_style = 'gather'`` before any field access. The octree just\nuses the smoothing style and number of neighbors defined by the dataset.\n\nThe octree implementation is very simple. It uses a recursive algorithm to build\na ``depth-first`` which is consistent with the results from yt-3. Depth-first\nsearch (DFS) means that tree starts refining at the root node (this is the\nlargest node which contains every particles) and refines as far as possible\nalong each branch before backtracking.\n\n.. _yt-units-is-now-unyt:\n\n``yt.units`` Is Now a Wrapper for ``unyt``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWe have extracted ``yt.units`` into ``unyt``, its own library that you can\ninstall separately from yt from ``pypi`` and ``conda-forge``. You can find out\nmore about using ``unyt`` in `its documentation\n<https://unyt.readthedocs.io/en/stable/>`_ and in `a paper in the Journal of\nOpen Source Software <http://joss.theoj.org/papers/10.21105/joss.00809>`_.\n\nFrom the perspective of a user of yt, very little should change. While things in\n``unyt`` have different names -- for example ``YTArray`` is now called\n``unyt_array`` -- we have provided wrappers in ``yt.units`` so imports in your\nold scripts should continue to work without issue. If you have any old scripts\nthat don't work due to issues with how yt is using ``unyt`` or units issues in\ngeneral please let us know by `filing an issue on GitHub\n<https://github.com/yt-project/yt/issues/new>`_.\n\nMoving ``unyt`` into its own library has made it much easier to add some cool\nnew features, which we detail below.\n\n``ds.units``\n~~~~~~~~~~~~\n\nEach dataset now has a set of unit symbols and physical constants associated\nwith it, allowing easier customization and smoother interaction, especially in\nworkflows that need to use code units or cosmological units. The ``ds.units``\nobject has a large number of attributes corresponding to the names of units and\nphysical constants. All units known to the dataset will be available, including\ncustom units. In situations where you might have used ``ds.arr`` or ``ds.quan``\nbefore, you can now safely use ``ds.units``:\n\n   >>> ds = yt.load('IsolatedGalaxy/galaxy0030/galaxy0030')\n   >>> u = ds.units\n   >>> ad = ds.all_data()\n   >>> data = ad['Enzo', 'Density']\n   >>> data + 12*u.code_mass/u.code_length**3\n   unyt_array([1.21784693e+01, 1.21789148e+01, 1.21788494e+01, ...,\n               4.08936836e+04, 5.78006836e+04, 3.97766906e+05], 'code_mass/code_length**3')\n   >>> data + .0001*u.mh/u.cm**3\n   unyt_array([6.07964513e+01, 6.07968968e+01, 6.07968314e+01, ...,\n               4.09423016e+04, 5.78493016e+04, 3.97815524e+05], 'code_mass/code_length**3')\n\n\nAutomatic Unit Simplification\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nOften the results of an operation will result in a unit expression that can be\nsimplified by cancelling pairs of factors. Before yt 4.0, these pairs of factors\nwere only cancelled if the same unit appeared in both the numerator and\ndenominator of an expression. Now, all pairs of factors have have inverse\ndimensions are cancelled, and the appropriate scaling factor is incorporated\ninto the result. For example, ``Hz`` and ``s`` will now appropriately be recognized\nas inverses:\n\n    >>> from yt.units import Hz, s\n    >>> frequency = 60*Hz\n    >>> time = 60*s\n    >>> frequency*time\n    unyt_quantity(3600, '(dimensionless)')\n\nSimilar simplifications will happen even if units aren't reciprocals of each\nother, for example here ``hour`` and ``minute`` automatically cancel each other:\n\n    >>> from yt.units import erg, minute, hour\n    >>> power = [20, 40, 80] * erg / minute\n    >>> elapsed_time = 3*hour\n    >>> print(power*elapsed_time)\n    [ 3600.  7200. 14400.] erg\n\nAlternate Unit Name Resolution\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIt's now possible to use a number of common alternate spellings for unit names\nand if ``unyt`` knows about the alternate spelling it will automatically resolve\nalternate spellings to a canonical name. For example, it's now possible to do\nthings like this:\n\n    >>> import yt.units as u\n    >>> d = 20*u.mile\n    >>> d.to('km')\n    unyt_quantity(32.18688, 'km')\n    >>> d.to('kilometer')\n    unyt_quantity(32.18688, 'km')\n    >>> d.to('kilometre')\n    unyt_quantity(32.18688, 'km')\n\nYou can also use alternate unit names in more complex algebraic unit expressions:\n\n    >>> v = d / (20*u.minute)\n    >>> v.to('kilometre/hour')\n    unyt_quantity(96.56064, 'km/hr')\n\nIn this example the common british spelling ``\"kilometre\"`` is resolved to\n``\"km\"`` and ``\"hour\"`` is resolved to ``\"hr\"``.\n\nField-Specific Configuration\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nYou can now set configuration values on a per-field basis.  For instance, this\nmeans that if you always want a particular colormap associated with a particular\nfield, you can do so!\n\nThis is documented under :ref:`per-field-plotconfig`, and was added in `PR\n1931 <https://github.com/yt-project/yt/pull/1931>`_.\n\nNew Method for Accessing Sample Datasets\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThere is now a function entitled ``load_sample()`` that allows the user to\nautomatically load sample data from the yt hub in a local yt session.\nPreviously, users would have to explicitly download these data directly from\n`https://yt-project.org/data <https://yt-project.org/data>`_, unpackage them,\nand load them into a yt session, but now this occurs from within a python\nsession.  For more information see:\n:ref:`Loading Sample Data <loading-sample-data>`\n\nSome Widgets\n^^^^^^^^^^^^\n\nIn yt, we now have some simple display wrappers for objects if you are running\nin a Jupyter environment with the `ipywidgets\n<https://ipywidgets.readthedocs.io/>`_ package installed.  For instance, the\n``ds.fields`` object will now display field information in an interactive\nwidget, and three-element unyt arrays (such as ``ds.domain_left_edge``) will be\ndisplayed interactively as well.\n\nThe package `widgyts <https://widgyts.readthedocs.io>`_ provides interactive,\nyt-specific visualization of slices, projections, and additional dataset display\ninformation.\n\nNew External Packages\n^^^^^^^^^^^^^^^^^^^^^\n\nAs noted above (:ref:`yt-units-is-now-unyt`), unyt has been extracted from\nyt, and we now use it as an external library.  In addition, other parts of yt\nsuch as :ref:`interactive_data_visualization` have been extracted, and we are\nworking toward a more modular approach for things such as Jupyter widgets and other \"value-added\" integrations.\n"
  },
  {
    "path": "nose_answer.cfg",
    "content": "[nosetests]\nattr=answer_test\ndetailed-errors=1\nlocal-dir=../answer-store\nlocal=1\nnocapture=1\nnologcapture=1\nverbosity=2\nwhere=yt\nwith-answer-testing=1\nwith-timer=1\nwith-xunit=1\nxunit-file=answer_nosetests.xml\nignore-files=(test_raw_field_slices\\.py)\n"
  },
  {
    "path": "nose_ignores",
    "content": "--ignore-file=test_add_field\\.py\n--ignore-file=test_ambiguous_fields\\.py\n--ignore-file=test_base_plot_types\\.py\n--ignore-file=test_callable_grids\\.py\n--ignore-file=test_callbacks_geographic\\.py\n--ignore-file=test_commons\\.py\n--ignore-file=test_cython_fortran_utils\\.py\n--ignore-file=test_data_reload\\.py\n--ignore-file=test_eps_writer\\.py\n--ignore-file=test_ewah_write_load\\.py\n--ignore-file=test_external_frontends\\.py\n--ignore-file=test_field_access_pytest\\.py\n--ignore-file=test_file_sanitizer\\.py\n--ignore-file=test_firefly\\.py\n--ignore-file=test_geometries\\.py\n--ignore-file=test_geographic_coordinates\\.py\n--ignore-file=test_glue\\.py\n--ignore-file=test_image_comp_2D_plots\\.py\n--ignore-file=test_image_comp_geo\\.py\n--ignore-file=test_invalid_origin\\.py\n--ignore-file=test_line_annotation_unit\\.py\n--ignore-file=test_line_plots\\.py\n--ignore-file=test_load_archive\\.py\n--ignore-file=test_load_errors\\.py\n--ignore-file=test_load_sample\\.py\n--ignore-file=test_mesh_render\\.py\n--ignore-file=test_mesh_slices\\.py\n--ignore-file=test_normal_plot_api\\.py\n--ignore-file=test_on_demand_imports\\.py\n--ignore-file=test_outputs_pytest\\.py\n--ignore-file=test_profile_plots\\.py\n--ignore-file=test_raw_field_slices\\.py\n--ignore-file=test_registration\\.py\n--ignore-file=test_sanitize_center\\.py\n--ignore-file=test_save\\.py\n--ignore-file=test_set_zlim\\.py\n--ignore-file=test_stream_particles\\.py\n--ignore-file=test_stream_stretched\\.py\n--ignore-file=test_version\\.py\n--ignore-file=test_gadget_pytest\\.py\n--ignore-file=test_vr_orientation\\.py\n--ignore-file=test_particle_trajectories_pytest\\.py\n--ignore-file=test_image_array\\.py\n--ignore-file=test_alt_ray_tracers\\.py\n--ignore-file=test_minimal_representation\\.py\n--ignore-file=test_set_log_level\\.py\n--ignore-file=test_field_parsing\\.py\n--ignore-file=test_disks\\.py\n--ignore-file=test_offaxisprojection_pytestonly\\.py\n--ignore-file=test_sph_pixelization_pytestonly\\.py\n--ignore-file=test_time_series\\.py\n--ignore-file=test_cf_radial_pytest\\.py\n--ignore-file=test_data_containers\\.py\n--ignore-file=test_fields_pytest\\.py\n"
  },
  {
    "path": "nose_unit.cfg",
    "content": "[nosetests]\nattr=!answer_test\ndetailed-errors=1\nnocapture=1\nnologcapture=1\nverbosity=2\nwhere=yt\nwith-timer=1\nexclude-test=yt.frontends.gdf.tests.test_outputs.TestGDF\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\n# keep in sync with .github/workflows/wheels.yaml\nrequires = [\n  \"setuptools>=77.0.0\",\n  \"Cython>=3.0.3\",\n  \"numpy>=2.0.0\",\n  \"ewah-bool-utils>=1.2.0\",\n]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"yt\"\nversion = \"4.5.dev0\"\ndescription = \"An analysis and visualization toolkit for volumetric data\"\nauthors = [\n    { name = \"The yt project\", email = \"yt-dev@python.org\" },\n]\nlicense = \"BSD-3-Clause\"\nlicense-files = [\n    \"COPYING.txt\",\n    \"yt/frontends/artio/artio_headers/LICENSE\",\n]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Environment :: Console\",\n    \"Framework :: Matplotlib\",\n    \"Intended Audience :: Science/Research\",\n    \"Operating System :: MacOS :: MacOS X\",\n    \"Operating System :: POSIX :: AIX\",\n    \"Operating System :: POSIX :: Linux\",\n    \"Programming Language :: C\",\n    \"Programming Language :: C++\",\n    \"Programming Language :: Cython\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3 :: Only\",\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 :: Python :: 3.14\",\n    \"Topic :: Scientific/Engineering :: Astronomy\",\n    \"Topic :: Scientific/Engineering :: Physics\",\n    \"Topic :: Scientific/Engineering :: Visualization\",\n]\nkeywords = [\n    \"adaptivemeshrefinement\",\n    \"amr\",\n    \"astronomy\",\n    \"astrophysics\",\n    \"visualization\",\n]\nrequires-python = \">=3.10.4\"\n\n# keep in sync with minimal_requirements.txt\ndependencies = [\n    \"cmyt>=1.1.2\",\n    \"ewah-bool-utils>=1.2.0\",\n    \"matplotlib>=3.7.0\",\n    \"more-itertools>=8.4\",\n    \"numpy>=1.21.3, <3\", # keep minimal requirement in sync with NPY_TARGET_VERSION\n    # https://github.com/numpy/numpy/issues/27037\n    \"numpy!=2.0.1 ; platform_machine=='arm64' and platform_system=='Darwin'\",\n    \"packaging>=21.0\",\n    \"pillow>=8.3.2\",\n    \"rlic>=0.3.2\",\n    \"tomli-w>=0.4.0\",\n    \"tqdm>=3.4.0\",\n    \"unyt>=2.9.2\",\n    \"tomli>=1.2.3;python_version < '3.11'\",\n    \"typing-extensions>=4.6.0 ; python_version < '3.12'\",\n]\n\n[project.readme]\nfile = \"README.md\"\ncontent-type = \"text/markdown\"\n\n[project.urls]\nHomepage = \"https://yt-project.org/\"\nDocumentation = \"https://yt-project.org/doc/\"\nSource = \"https://github.com/yt-project/yt/\"\nTracker = \"https://github.com/yt-project/yt/issues\"\n\n[project.entry-points.\"nose.plugins.0.10\"]\n# this section can be cleaned when nose tests on GHA are removed\nanswer-testing = \"yt.utilities.answer_testing.framework:AnswerTesting\"\n\n[project.optional-dependencies]\n# some generic, reusable constraints on optional-deps\nHDF5 = [\n  \"h5py>=3.6.0\",\n  \"h5py!=3.12.0 ; platform_system=='Windows'\", # https://github.com/h5py/h5py/issues/2505\n  \"h5py!=3.15.0 ; platform_system=='Darwin'\", # https://github.com/yt-project/yt/issues/5301\n]\nnetCDF4 = [\"netCDF4!=1.6.1,>=1.5.8\"] # see https://github.com/Unidata/netcdf4-python/issues/1192\nFortran = [\"f90nml>=1.1\"]\n\n# frontend-specific requirements\n# all frontends should have a target, even if no additional requirements are needed\n# note that, because pip normalizes underscores to hyphens, we need to apply this transformation\n# in target names here so that recursive dependencies link correctly.\n# This does *not* prevent end-users to write, say `pip install yt[enzo_e]`.\n# We also normalize all target names to lower case for consistency.\nadaptahop = []\nahf = []\namrex = []\namrvac = [\"yt[Fortran]\"]\nart = []\narepo = [\"yt[HDF5]\"]\nartio = []\nathena = []\nathena-pp = []\nboxlib = []\ncf-radial = [\"xarray>=0.16.1\", \"arm-pyart>=1.19.2\",]\nchimera = [\"yt[HDF5]\"]\nchombo = [\"yt[HDF5]\"]\ncholla = [\"yt[HDF5]\"]\neagle = [\"yt[HDF5]\"]\nenzo-e = [\"yt[HDF5]\", \"libconf>=1.0.1\"]\nenzo = [\"yt[HDF5]\", \"libconf>=1.0.1\"]\nexodus-ii = [\"yt[netCDF4]\"]\nfits = [\"astropy>=5.0.1\", \"regions>=0.7\"]\nflash = [\"yt[HDF5]\"]\ngadget = [\"yt[HDF5]\"]\ngadget-fof = [\"yt[HDF5]\"]\ngamer = [\"yt[HDF5]\"]\ngdf = [\"yt[HDF5]\"]\ngizmo = [\"yt[HDF5]\"]\nhalo-catalog = [\"yt[HDF5]\"]\nhttp-stream = [\"requests>=2.30.0\"]\nidefix = [\"yt_idefix[HDF5]>=2.3.0\"] # externally packaged frontend\nmoab = [\"yt[HDF5]\"]\nnc4-cm1 = [\"yt[netCDF4]\"]\nopen-pmd = [\"yt[HDF5]\"]\nowls = [\"yt[HDF5]\"]\nowls-subfind = [\"yt[HDF5]\"]\nparthenon = [\"yt[HDF5]\"]\nramses = [\"yt[Fortran]\", \"scipy>=1.7.2\"]\nrockstar = []\nsdf = [\"requests>=2.30.0\"]\nstream = []\nswift = [\"yt[HDF5]\"]\ntipsy = []\nytdata = [\"yt[HDF5]\"]\n\n# \"full\" should contain all optional dependencies intended for users (not devs)\n# in particular it should enable support for all frontends\nfull = [\n    \"cartopy>=0.22.0\",\n    \"firefly>=3.2.0\",\n    \"ipython>=7.23.1\",\n    \"ipywidgets>=8.0.0\",\n    \"miniballcpp>=0.2.1\",\n    \"mpi4py>=3.1.6\",\n    \"pandas>=1.3.4\",\n    \"pooch>=0.7.0\",\n    \"pyaml>=17.10.0\",\n    \"pykdtree>=1.3.4\",\n    \"pyx>=0.15\",\n    \"scipy>=1.7.2\",\n    \"ratarmount~=0.8.1;platform_system!='Windows' and platform_system!='Darwin'\",\n    \"yt[adaptahop]\",\n    \"yt[ahf]\",\n    \"yt[amrex]\",\n    \"yt[amrvac]\",\n    \"yt[art]\",\n    \"yt[arepo]\",\n    \"yt[artio]\",\n    \"yt[athena]\",\n    \"yt[athena_pp]\",\n    \"yt[boxlib]\",\n    \"yt[cf_radial]\",\n    \"yt[chimera]\",\n    \"yt[chombo]\",\n    \"yt[cholla]\",\n    \"yt[eagle]\",\n    \"yt[enzo_e]\",\n    \"yt[enzo]\",\n    \"yt[exodus_ii]\",\n    \"yt[fits]\",\n    \"yt[flash]\",\n    \"yt[gadget]\",\n    \"yt[gadget_fof]\",\n    \"yt[gamer]\",\n    \"yt[gdf]\",\n    \"yt[gizmo]\",\n    \"yt[halo_catalog]\",\n    \"yt[http_stream]\",\n    \"yt[idefix]\",\n    \"yt[moab]\",\n    \"yt[nc4_cm1]\",\n    \"yt[open_pmd]\",\n    \"yt[owls]\",\n    \"yt[owls_subfind]\",\n    \"yt[parthenon]\",\n    \"yt[ramses]\",\n    \"yt[rockstar]\",\n    \"yt[sdf]\",\n    \"yt[stream]\",\n    \"yt[swift]\",\n    \"yt[tipsy]\",\n    \"yt[ytdata]\",\n]\n\n# dev-only extra targets\ntest = [\n    \"pyaml>=17.10.0\",\n    \"pytest>=8.0.1\",\n    \"pytest-mpl>=0.16.1\",\n]\n\n[project.scripts]\nyt = \"yt.utilities.command_line:run_main\"\n\n[dependency-groups]\ndocs = [\n    \"alabaster>=0.7.13\",\n    \"bottle>=0.12.25\",\n    \"ipykernel>=6.29.4\",\n    \"jinja2<3.2.0\",\n    \"jupyter-client>=8.3.1\",\n    \"nbsphinx>=0.9.3\",\n    \"nose~=1.3.7 ; python_full_version < '3.10'\",\n    \"pytest>=6.1\",\n    \"pyx>=0.15\",\n    \"sphinx>=7.2.5\",\n    \"sphinx-bootstrap-theme>=0.8.1\",\n    \"sphinx-rtd-theme>=1.3.0\",\n]\nmapserver = [\n    \"bottle>=0.13.0\",\n]\nmapping = [\n    \"cartopy[plotting]>=0.22.0\",\n]\nnosetest = [\n    \"nose~=1.3.7\",\n    \"nose-exclude>=0.5.0\",\n    \"nose-timer~=1.0.0\",\n    # nose's plugin system silently requires pkg_resources,\n    # which was part of setuptools but removed in version 82.0.0\n    \"setuptools>=77.0.1, <82.0.0\",\n]\ntypecheck = [\n    \"mypy>=1.18.2\",\n    \"types-chardet>=5.0.4.6\",\n    \"types-pyyaml>=6.0.12.20250915\",\n    \"types-requests>=2.32.4.20250913\",\n    \"typing-extensions>=4.6.0 ; python_full_version < '3.12'\",\n]\n\n[tool.setuptools]\ninclude-package-data = true\nzip-safe = false\n\n[tool.setuptools.packages.find]\nnamespaces = false\n\n[tool.black]\n# TODO: drop this section when ruff supports embedded python blocks\n# see https://github.com/astral-sh/ruff/issues/8237\ninclude = '\\.pyi?$'\nexclude = '''\n/(\n    \\.eggs\n  | \\.git\n  | \\.hg\n  | \\.mypy_cache\n  | \\.tox\n  | \\.venv\n  | _build\n  | buck-out\n  | build\n  | dist\n  | yt/frontends/stream/sample_data\n)/\n| yt/visualization/_colormap_data.py\n'''\n\n[tool.ruff]\nexclude = [\n    \"doc\",\n    \"benchmarks\",\n    \"*/api.py\",\n    \"*/__init__.py\",\n    \"*/__config__.py\",\n    \"yt/units\",\n    \"yt/frontends/stream/sample_data\",\n    \"yt/utilities/fits_image.py\",\n    \"yt/utilities/lodgeit.py\",\n    \"yt/mods.py\",\n    \"yt/visualization/_colormap_data.py\",\n    \"yt/exthook.py\",\n]\n\n[tool.ruff.lint]\npreview = true\nexplicit-preview-rules = true\nselect = [\n    \"E\",\n    \"F\",\n    \"W\",\n    \"C4\",   # flake8-comprehensions\n    \"B\",    # flake8-bugbear\n    \"G\",    # flake8-logging-format\n    \"TC\",   # flake8-type-checking\n    \"YTT\",  # flake8-2020\n    \"UP\",   # pyupgrade\n    \"I\",    # isort\n    \"NPY\",  # numpy specific rules\n\n    # preview rules\n    \"RUF031\", # incorrectly-parenthesized-tuple-in-subscript\n]\nignore = [\n    \"E501\",  # line too long\n    \"E741\",  # Do not use variables named 'I', 'O', or 'l'\n    \"B018\",  # Found useless expression. # disabled because ds.index is idiomatic\n]\n\n[tool.ruff.lint.per-file-ignores]\n\"test_*\" = [\"NPY002\"]\n\n[tool.ruff.lint.isort]\ncombine-as-imports = true\n\n# The -s option prevents pytest from capturing output sent to stdout\n# -v runs pytest in verbose mode\n# -rsfE: The -r tells pytest to provide extra test summary info on the events\n# specified by the characters following the r. s: skipped, f: failed, E: error\n[tool.pytest.ini_options]\naddopts = '''\n    -rsfE\n    --ignore-glob='/*_nose.py'\n    --ignore-glob='/*/yt/data_objects/level_sets/tests/test_clump_finding.py'\n    --ignore-glob='/*/yt/data_objects/tests/test_connected_sets.py'\n    --ignore-glob='/*/yt/data_objects/tests/test_dataset_access.py'\n    --ignore-glob='/*/yt/data_objects/tests/test_particle_filter.py'\n    --ignore-glob='/*/yt/data_objects/tests/test_particle_trajectories.py'\n    --ignore-glob='/*/yt/data_objects/tests/test_pickling.py'\n    --ignore-glob='/*/yt/data_objects/tests/test_regions.py'\n    --ignore-glob='/*/yt/fields/tests/test_particle_fields.py'\n    --ignore-glob='/*/yt/fields/tests/test_vector_fields.py'\n    --ignore-glob='/*/yt/fields/tests/test_xray_fields.py'\n    --ignore-glob='/*/yt/frontends/adaptahop/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/ahf/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/amrex/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/amrvac/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/amrvac/tests/test_units_override.py'\n    --ignore-glob='/*/yt/frontends/arepo/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/art/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/artio/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/athena/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/athena_pp/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/boxlib/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/cf_radial/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/chimera/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/cholla/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/chombo/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/eagle/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/enzo/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/enzo_e/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/exodus_ii/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/fits/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/flash/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/gadget/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/gadget_fof/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/gamer/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/gdf/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/gdf/tests/test_outputs_nose.py'\n    --ignore-glob='/*/yt/frontends/gizmo/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/halo_catalog/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/moab/tests/test_c5.py'\n    --ignore-glob='/*/yt/frontends/nc4_cm1/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/open_pmd/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/owls/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/owls_subfind/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/parthenon/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/ramses/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/rockstar/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/tipsy/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/ytdata/tests/test_old_outputs.py'\n    --ignore-glob='/*/yt/frontends/ytdata/tests/test_outputs.py'\n    --ignore-glob='/*/yt/frontends/ytdata/tests/test_unit.py'\n    --ignore-glob='/*/yt/geometry/coordinates/tests/test_axial_pixelization.py'\n    --ignore-glob='/*/yt/geometry/coordinates/tests/test_cylindrical_coordinates.py'\n    --ignore-glob='/*/yt/geometry/coordinates/tests/test_spherical_coordinates.py'\n    --ignore-glob='/*/yt/tests/test_funcs.py'\n    --ignore-glob='/*/yt/utilities/lib/cykdtree/tests/__init__.py'\n    --ignore-glob='/*/yt/utilities/lib/cykdtree/tests/test_kdtree.py'\n    --ignore-glob='/*/yt/utilities/lib/cykdtree/tests/test_plot.py'\n    --ignore-glob='/*/yt/utilities/lib/cykdtree/tests/test_utils.py'\n    --ignore-glob='/*/yt/utilities/tests/test_cosmology.py'\n    --ignore-glob='/*/yt/visualization/tests/test_callbacks.py'\n    --ignore-glob='/*/yt/visualization/tests/test_color_maps.py'\n    --ignore-glob='/*/yt/visualization/tests/test_geo_projections.py'\n    --ignore-glob='/*/yt/visualization/tests/test_image_writer.py'\n    --ignore-glob='/*/yt/visualization/tests/test_line_plots.py'\n    --ignore-glob='/*/yt/visualization/tests/test_mesh_slices.py'\n    --ignore-glob='/*/yt/visualization/tests/test_norm_api_custom_norm.py'\n    --ignore-glob='/*/yt/visualization/tests/test_norm_api_inf_zlim.py'\n    --ignore-glob='/*/yt/visualization/tests/test_norm_api_lineplot.py'\n    --ignore-glob='/*/yt/visualization/tests/test_norm_api_particleplot.py'\n    --ignore-glob='/*/yt/visualization/tests/test_norm_api_phaseplot_set_colorbar_explicit.py'\n    --ignore-glob='/*/yt/visualization/tests/test_norm_api_phaseplot_set_colorbar_implicit.py'\n    --ignore-glob='/*/yt/visualization/tests/test_norm_api_profileplot.py'\n    --ignore-glob='/*/yt/visualization/tests/test_norm_api_set_background_color.py'\n    --ignore-glob='/*/yt/visualization/tests/test_particle_plot.py'\n    --ignore-glob='/*/yt/visualization/tests/test_plot_modifications.py'\n    --ignore-glob='/*/yt/visualization/tests/test_plotwindow.py'\n    --ignore-glob='/*/yt/visualization/tests/test_raw_field_slices.py'\n    --ignore-glob='/*/yt/visualization/volume_rendering/tests/test_mesh_render.py'\n    --ignore-glob='/*/yt/visualization/volume_rendering/tests/test_vr_orientation.py'\n\n'''\n\n\n[tool.check-manifest]\n# ignore generated C/C++ files, otherwise reported as \"missing from VCS\" (Version Control System)\n# Please resist the temptation to use patterns instead of exact file names here.\nignore = [\n  \"yt/frontends/artio/_artio_caller.c\",\n  \"yt/frontends/gamer/cfields.c\",\n  \"yt/frontends/ramses/io_utils.c\",\n  \"yt/geometry/fake_octree.c\",\n  \"yt/geometry/grid_container.c\",\n  \"yt/geometry/grid_visitors.c\",\n  \"yt/geometry/oct_container.c\",\n  \"yt/geometry/oct_visitors.c\",\n  \"yt/geometry/particle_deposit.c\",\n  \"yt/geometry/particle_oct_container.cpp\",\n  \"yt/geometry/particle_smooth.c\",\n  \"yt/geometry/selection_routines.c\",\n  \"yt/utilities/cython_fortran_utils.c\",\n  \"yt/utilities/lib/_octree_raytracing.cpp\",\n  \"yt/utilities/lib/allocation_container.c\",\n  \"yt/utilities/lib/alt_ray_tracers.c\",\n  \"yt/utilities/lib/amr_kdtools.c\",\n  \"yt/utilities/lib/autogenerated_element_samplers.c\",\n  \"yt/utilities/lib/basic_octree.c\",\n  \"yt/utilities/lib/bitarray.c\",\n  \"yt/utilities/lib/bounded_priority_queue.c\",\n  \"yt/utilities/lib/bounding_volume_hierarchy.cpp\",\n  \"yt/utilities/lib/contour_finding.c\",\n  \"yt/utilities/lib/cosmology_time.c\",\n  \"yt/utilities/lib/cykdtree/kdtree.cpp\",\n  \"yt/utilities/lib/cykdtree/utils.cpp\",\n  \"yt/utilities/lib/cyoctree.c\",\n  \"yt/utilities/lib/cyoctree.cpp\",\n  \"yt/utilities/lib/depth_first_octree.c\",\n  \"yt/utilities/lib/distance_queue.c\",\n  \"yt/utilities/lib/element_mappings.c\",\n  \"yt/utilities/lib/ewah_bool_wrap.cpp\",\n  \"yt/utilities/lib/fnv_hash.c\",\n  \"yt/utilities/lib/fortran_reader.c\",\n  \"yt/utilities/lib/geometry_utils.cpp\",\n  \"yt/utilities/lib/grid_traversal.cpp\",\n  \"yt/utilities/lib/image_samplers.cpp\",\n  \"yt/utilities/lib/image_utilities.c\",\n  \"yt/utilities/lib/interpolators.c\",\n  \"yt/utilities/lib/lenses.c\",\n  \"yt/utilities/lib/marching_cubes.cpp\",\n  \"yt/utilities/lib/mesh_triangulation.c\",\n  \"yt/utilities/lib/mesh_utilities.c\",\n  \"yt/utilities/lib/misc_utilities.cpp\",\n  \"yt/utilities/lib/origami.c\",\n  \"yt/utilities/lib/particle_kdtree_tools.cpp\",\n  \"yt/utilities/lib/particle_mesh_operations.c\",\n  \"yt/utilities/lib/partitioned_grid.cpp\",\n  \"yt/utilities/lib/pixelization_routines.cpp\",\n  \"yt/utilities/lib/points_in_volume.c\",\n  \"yt/utilities/lib/primitives.c\",\n  \"yt/utilities/lib/quad_tree.c\",\n  \"yt/utilities/lib/ragged_arrays.c\",\n  \"yt/utilities/lib/write_array.c\",\n]\n\n\n[tool.mypy]\npython_version = '3.10'\nshow_error_codes = true\nignore_missing_imports = true\nwarn_unused_configs = true\nwarn_unused_ignores = true\nwarn_unreachable = true\nshow_error_context = true\nexclude = \"(test_*|lodgeit)\"\n\n[tool.cibuildwheel]\nbuild-verbosity = 1\nskip = [\"cp314t-*\"]\nbuild-frontend = \"build[uv]\"\ntest-extras = \"test\"\ntest-command = [\n    'python -c \"import yt\"',\n    \"python -m pytest -c {project}/pyproject.toml --rootdir . --color=yes --pyargs yt -ra\",\n]\nenvironment = {\"YT_LIMITED_API\" = \"1\"}\n\n[tool.uv]\n# never build the following packages from sources (always prefer wheels)\nno-build-package = [\n    # \"arm-pyart\", # temporarily disabled because there are no cp313 wheels yet\n    \"astropy\",\n    \"appnope\",\n    \"bottleneck\",\n    \"cartopy\",\n    \"cffi\",\n    \"contourpy\",\n    \"ewah-bool-utils\",\n    \"fast-histogram\",\n    \"h5py\",\n    \"jinja2\",\n    \"kiwisolver\",\n    \"markupsafe\",\n    \"matplotlib\",\n    \"numpy\",\n    \"netCDF4\",\n    \"pandas\",\n    \"pillow\",\n    \"psutil\",\n    \"py\",\n    \"pykdtree\",\n    \"pyproj\",\n    \"pyyaml\",\n    \"rlic\",\n    \"scipy\",\n    \"shapely\",\n\n    # via firefly\n    \"flask\",\n    \"gevent\",\n    \"greenlet\",\n    \"zope-interface\",\n\n    # via astropy\n    \"pyerfa\",\n]\nconstraint-dependencies = [\n    \"imageio!=2.35.0\", # see https://github.com/yt-project/yt/issues/4966\n    \"sympy!=1.10,!=1.9\", # see https://github.com/sympy/sympy/issues/22241\n    \"jinja2>=2.11.3\",\n\n    # wheels\n    \"mpmath>=1.2.1\", # via sympy\n    \"iniconfig>=1.0.1\", # via pytest\n\n    # from ipython (these can be removed when ipython<9.7 is dropped)\n    \"ipython-pygments-lexers>=1.0.0\",\n    \"decorator>=4.3.2\",\n    \"jedi>=0.18.1\",\n    \"stack_data>=0.6.0\",\n    \"pygments>=2.11.0\",\n\n    # from pyart\n    # see https://github.com/ARM-DOE/pyart/pull/1813\n    \"cftime>=1.5.1.1\",\n    \"fsspec>=2021.11.0\",\n    \"mda-xdrlib>=0.2.0\",\n    \"open-radar-data>=0.5.0\",\n    \"pint>=0.18.0\",\n    \"s3fs>=2021.11.0\",\n]\n"
  },
  {
    "path": "setup.py",
    "content": "import glob\nimport os\nimport sys\nfrom collections import defaultdict\nfrom distutils.ccompiler import get_default_compiler\nfrom importlib import resources as importlib_resources\n\nfrom setuptools import Distribution, setup\n\n# ensure enclosing directory is in PYTHON_PATH to allow importing from setupext.py\nif (script_dir := os.path.dirname(__file__)) not in sys.path:\n    sys.path.insert(0, script_dir)\n\nfrom setupext import (\n    NUMPY_MACROS,\n    check_CPP14_flags,\n    check_for_openmp,\n    check_for_pyembree,\n    create_build_ext,\n    get_python_include_dirs,\n    get_setup_options,\n    install_ccompiler,\n)\n\ninstall_ccompiler()\n\nif os.path.exists(\"MANIFEST\"):\n    os.remove(\"MANIFEST\")\n\nwith open(\"README.md\") as file:\n    long_description = file.read()\n\nCPP14_CONFIG = defaultdict(\n    lambda: check_CPP14_flags([\"-std=c++14\", \"-std=c++1y\", \"-std=gnu++0x\"]),\n    {\"msvc\": [\"/std:c++14\"]},\n)\nCPP11_CONFIG = defaultdict(lambda: [\"-std=c++11\"], {\"msvc\": [\"/std:c++11\"]})\n\n_COMPILER = get_default_compiler()\n\nomp_args, _ = check_for_openmp()\n\nif os.name == \"nt\":\n    std_libs = []\nelse:\n    std_libs = [\"m\"]\n\nCPP14_FLAG = CPP14_CONFIG[_COMPILER]\nCPP11_FLAG = CPP11_CONFIG[_COMPILER]\n\nFIXED_INTERP = \"fixed_interpolator\"\n\ncythonize_aliases = {\n    \"LIB_DIR\": \"yt/utilities/lib/\",\n    \"LIB_DIR_GEOM\": [\"yt/utilities/lib/\", \"yt/geometry/\"],\n    \"LIB_DIR_GEOM_ARTIO\": [\n        \"yt/utilities/lib/\",\n        \"yt/geometry/\",\n        \"yt/frontends/artio/artio_headers/\",\n    ],\n    \"STD_LIBS\": std_libs,\n    \"EWAH_LIBS\": std_libs\n    + [os.path.abspath(importlib_resources.files(\"ewah_bool_utils\"))],\n    \"OMP_ARGS\": omp_args,\n    \"FIXED_INTERP\": FIXED_INTERP,\n    \"ARTIO_SOURCE\": sorted(glob.glob(\"yt/frontends/artio/artio_headers/*.c\")),\n    \"CPP14_FLAG\": CPP14_FLAG,\n    \"CPP11_FLAG\": CPP11_FLAG,\n}\n\nlib_exts = [\n    \"yt/geometry/*.pyx\",\n    \"yt/utilities/cython_fortran_utils.pyx\",\n    \"yt/frontends/ramses/io_utils.pyx\",\n    \"yt/frontends/gamer/cfields.pyx\",\n    \"yt/utilities/lib/cykdtree/kdtree.pyx\",\n    \"yt/utilities/lib/cykdtree/utils.pyx\",\n    \"yt/frontends/artio/_artio_caller.pyx\",\n    \"yt/utilities/lib/*.pyx\",\n]\n\nembree_libs, embree_aliases = check_for_pyembree(std_libs)\ncythonize_aliases.update(embree_aliases)\nlib_exts += embree_libs\n\n# This overrides using lib_exts, so it has to happen after lib_exts is fully defined\nbuild_ext, sdist = create_build_ext(lib_exts, cythonize_aliases)\n\n\n# Force setuptools to consider that there are ext modules, even if empty.\n# See https://github.com/yt-project/yt/issues/2922 and\n# https://stackoverflow.com/a/62668026/2601223 for the fix.\nclass BinaryDistribution(Distribution):\n    \"\"\"Distribution which always forces a binary package with platform name.\"\"\"\n\n    def has_ext_modules(self):\n        return True\n\n\nif __name__ == \"__main__\":\n    # Avoid a race condition on fixed_interpolator.o during parallel builds by\n    # building it only once and storing it in a static library.\n    # See https://github.com/yt-project/yt/issues/4278 and\n    # https://github.com/pypa/setuptools/issues/3119#issuecomment-2076922303\n    # for the inspiration for this fix.\n\n    # build_clib doesn't add the Python include dirs (for Python.h) by default,\n    # as opposed to build_ext, so we need to add them manually.\n    clib_include_dirs = get_python_include_dirs()\n\n    # fixed_interpolator.cpp uses Numpy types\n    import numpy\n\n    clib_include_dirs.append(numpy.get_include())\n\n    fixed_interp_lib = (\n        FIXED_INTERP,\n        {\n            \"sources\": [\"yt/utilities/lib/fixed_interpolator.cpp\"],\n            \"include_dirs\": clib_include_dirs,\n            \"define_macros\": NUMPY_MACROS,\n        },\n    )\n\n    setup(\n        cmdclass={\"sdist\": sdist, \"build_ext\": build_ext},\n        distclass=BinaryDistribution,\n        libraries=[fixed_interp_lib],\n        ext_modules=[],  # !!! We override this inside build_ext above\n        options=get_setup_options(),\n    )\n"
  },
  {
    "path": "setupext.py",
    "content": "import contextlib\nimport glob\nimport logging\nimport os\nimport platform\nimport shutil\nimport subprocess\nimport sys\nimport tempfile\nfrom textwrap import dedent\nfrom concurrent.futures import ThreadPoolExecutor\nfrom distutils import sysconfig\nfrom distutils.ccompiler import CCompiler, new_compiler\nfrom distutils.sysconfig import customize_compiler\nfrom subprocess import PIPE, Popen\nfrom sys import platform as _platform\nimport ewah_bool_utils\nfrom setuptools.command.build_ext import build_ext as _build_ext\nfrom setuptools.command.sdist import sdist as _sdist\nfrom setuptools.errors import CompileError, LinkError\nimport importlib.resources as importlib_resources\n\nlog = logging.getLogger(\"setupext\")\n\nUSE_PY_LIMITED_API = (\n    os.getenv('YT_LIMITED_API', '0') == '1'\n    and sys.version_info >= (3, 11)\n    and not sysconfig.get_config_var(\"Py_GIL_DISABLED\")\n)\nABI3_TARGET_VERSION = \"\".join(str(_) for _ in sys.version_info[:2])\nABI3_TARGET_HEX = hex(sys.hexversion & 0xFFFF00F0)\n\n\n@contextlib.contextmanager\ndef stdchannel_redirected(stdchannel, dest_filename):\n    \"\"\"\n    A context manager to temporarily redirect stdout or stderr\n\n    e.g.:\n\n    with stdchannel_redirected(sys.stderr, os.devnull):\n        if compiler.has_function('clock_gettime', libraries=['rt']):\n            libraries.append('rt')\n\n    Code adapted from https://stackoverflow.com/a/17752455/1382869\n    \"\"\"\n\n    try:\n        oldstdchannel = os.dup(stdchannel.fileno())\n        dest_file = open(dest_filename, \"w\")\n        os.dup2(dest_file.fileno(), stdchannel.fileno())\n\n        yield\n    finally:\n        if oldstdchannel is not None:\n            os.dup2(oldstdchannel, stdchannel.fileno())\n        if dest_file is not None:\n            dest_file.close()\n\n\ndef check_for_openmp():\n    \"\"\"Returns OpenMP compiler and linker flags if local setup supports\n    OpenMP or [], [] otherwise\n\n    Code adapted from astropy_helpers, originally written by Tom\n    Robitaille and Curtis McCully.\n    \"\"\"\n\n    # Create a temporary directory\n    ccompiler = new_compiler()\n    customize_compiler(ccompiler)\n\n    tmp_dir = tempfile.mkdtemp()\n    start_dir = os.path.abspath(\".\")\n\n    CCODE = dedent(\"\"\"\\\n        #include <omp.h>\n        #include <stdio.h>\n        int main() {\n            omp_set_num_threads(2);\n            #pragma omp parallel\n            printf(\"nthreads=%d\\\\n\", omp_get_num_threads());\n            return 0;\n        }\"\"\"\n    )\n\n    # TODO: test more known compilers:\n    # MinGW, AppleClang with libomp, MSVC, ICC, XL, PGI, ...\n    if os.name == \"nt\":\n        # TODO: make this work with mingw\n        # AFAICS there's no easy way to get the compiler distutils\n        # will be using until compilation actually happens\n        compile_flags = [\"-openmp\"]\n        link_flags = [\"\"]\n    else:\n        compile_flags = [\"-fopenmp\"]\n        link_flags = [\"-fopenmp\"]\n\n    try:\n        os.chdir(tmp_dir)\n\n        with open(\"test_openmp.c\", \"w\") as f:\n            f.write(CCODE)\n\n        os.mkdir(\"objects\")\n\n        # Compile, link, and run test program\n        with stdchannel_redirected(sys.stderr, os.devnull):\n            ccompiler.compile(\n                [\"test_openmp.c\"], output_dir=\"objects\", extra_postargs=compile_flags\n            )\n            ccompiler.link_executable(\n                glob.glob(os.path.join(\"objects\", \"*\")),\n                \"test_openmp\",\n                extra_postargs=link_flags,\n            )\n            output = (\n                subprocess.check_output(\"./test_openmp\")\n                .decode(sys.stdout.encoding or \"utf-8\")\n                .splitlines()\n            )\n\n        if \"nthreads=\" in output[0]:\n            nthreads = int(output[0].strip().split(\"=\")[1])\n            if len(output) == nthreads:\n                using_openmp = True\n            else:\n                log.warning(\n                    \"Unexpected number of lines from output of test \"\n                    \"OpenMP program (output was %s)\",\n                    output,\n                )\n                using_openmp = False\n        else:\n            log.warning(\n                \"Unexpected output from test OpenMP program (output was %s)\", output\n            )\n            using_openmp = False\n\n    except (CompileError, LinkError):\n        using_openmp = False\n    finally:\n        os.chdir(start_dir)\n\n    if using_openmp:\n        log.warning(\"Using OpenMP to compile parallel extensions\")\n    else:\n        log.warning(\n            \"Unable to compile OpenMP test program so Cython\\n\"\n            \"extensions will be compiled without parallel support\"\n        )\n\n    if using_openmp:\n        return compile_flags, link_flags\n    else:\n        return [], []\n\n\ndef check_CPP14_flag(compile_flags):\n    # Create a temporary directory\n    ccompiler = new_compiler()\n    customize_compiler(ccompiler)\n\n    tmp_dir = tempfile.mkdtemp()\n    start_dir = os.path.abspath(\".\")\n\n    # Note: This code requires C++14 functionalities (also required to compile yt)\n    # It compiles on gcc 4.7.4 (together with the entirety of yt) with the flag \"-std=gnu++0x\".\n    # It does not compile on gcc 4.6.4 (neither does yt).\n    CPPCODE = dedent(\"\"\"\\\n        #include <vector>\n\n        struct node {\n            std::vector<int> vic;\n            bool visited = false;\n        };\n\n        int main() {\n            return 0;\n        }\"\"\"\n    )\n\n    os.chdir(tmp_dir)\n    try:\n        with open(\"test_cpp14.cpp\", \"w\") as f:\n            f.write(CPPCODE)\n\n        os.mkdir(\"objects\")\n\n        # Compile, link, and run test program\n        with stdchannel_redirected(sys.stderr, os.devnull):\n            ccompiler.compile(\n                [\"test_cpp14.cpp\"], output_dir=\"objects\", extra_postargs=compile_flags\n            )\n        return True\n    except CompileError:\n        return False\n    finally:\n        os.chdir(start_dir)\n\n\ndef check_CPP14_flags(possible_compile_flags):\n    for flags in possible_compile_flags:\n        if check_CPP14_flag([flags]):\n            return flags\n\n    log.warning(\n        \"Your compiler seems to be too old to support C++14. \"\n        \"yt may not be able to compile. Please use a newer version.\"\n    )\n    return []\n\n\ndef check_for_pyembree(std_libs):\n    embree_libs = []\n    embree_aliases = {}\n\n    try:\n        importlib_resources.files(\"pyembree\")\n    except ImportError:\n        return embree_libs, embree_aliases\n\n    embree_prefix = os.path.abspath(read_embree_location())\n    embree_inc_dir = os.path.join(embree_prefix, \"include\")\n    embree_lib_dir = os.path.join(embree_prefix, \"lib\")\n\n    if _platform == \"darwin\":\n        embree_lib_name = \"embree.2\"\n    else:\n        embree_lib_name = \"embree\"\n\n    embree_aliases[\"EMBREE_INC_DIR\"] = [\"yt/utilities/lib/\", embree_inc_dir]\n    embree_aliases[\"EMBREE_LIB_DIR\"] = [embree_lib_dir]\n    embree_aliases[\"EMBREE_LIBS\"] = std_libs + [embree_lib_name]\n    embree_libs += [\"yt/utilities/lib/embree_mesh/*.pyx\"]\n\n    if in_conda_env():\n        conda_basedir = os.path.dirname(os.path.dirname(sys.executable))\n        embree_aliases[\"EMBREE_INC_DIR\"].append(os.path.join(conda_basedir, \"include\"))\n        embree_aliases[\"EMBREE_LIB_DIR\"].append(os.path.join(conda_basedir, \"lib\"))\n\n    return embree_libs, embree_aliases\n\n\ndef in_conda_env():\n    return any(s in sys.version for s in (\"Anaconda\", \"Continuum\", \"conda-forge\"))\n\n\ndef read_embree_location():\n    \"\"\"\n\n    Attempts to locate the embree installation. First, we check for an\n    EMBREE_DIR environment variable. If one is not defined, we look for\n    an embree.cfg file in the root yt source directory. Finally, if that\n    is not present, we default to /usr/local. If embree is installed in a\n    non-standard location and none of the above are set, the compile will\n    not succeed. This only gets called if check_for_pyembree() returns\n    something other than None.\n\n    \"\"\"\n\n    rd = os.environ.get(\"EMBREE_DIR\")\n    if rd is None:\n        try:\n            rd = open(\"embree.cfg\").read().strip()\n        except IOError:\n            rd = \"/usr/local\"\n\n    fail_msg = (\n        \"I attempted to find Embree headers in %s. \\n\"\n        \"If this is not correct, please set your correct embree location \\n\"\n        \"using EMBREE_DIR environment variable or your embree.cfg file. \\n\"\n        \"Please see http://yt-project.org/docs/dev/visualizing/unstructured_mesh_rendering.html \"\n        \"for more information. \\n\" % rd\n    )\n\n    # Create a temporary directory\n    tmpdir = tempfile.mkdtemp()\n    curdir = os.getcwd()\n\n    try:\n        os.chdir(tmpdir)\n\n        # Get compiler invocation\n        compiler = os.getenv(\"CXX\", \"c++\")\n        compiler = compiler.split(\" \")\n\n        # Attempt to compile a test script.\n        filename = r\"test.cpp\"\n        file = open(filename, \"wt\", 1)\n        CCODE = dedent(\"\"\"\\\n            #include \"embree2/rtcore.h\n            int main() {\n                return 0;\n            }\"\"\"\n        )\n        file.write(CCODE)\n        file.flush()\n        p = Popen(\n            compiler + [\"-I%s/include/\" % rd, filename],\n            stdin=PIPE,\n            stdout=PIPE,\n            stderr=PIPE,\n        )\n        output, err = p.communicate()\n        exit_code = p.returncode\n\n        if exit_code != 0:\n            log.warning(\n                \"Pyembree is installed, but I could not compile Embree test code.\"\n            )\n            log.warning(\"The error message was: \")\n            log.warning(err)\n            log.warning(fail_msg)\n\n        # Clean up\n        file.close()\n\n    except OSError:\n        log.warning(\n            \"read_embree_location() could not find your C compiler. \"\n            \"Attempted to use '%s'.\",\n            compiler,\n        )\n        return False\n\n    finally:\n        os.chdir(curdir)\n        shutil.rmtree(tmpdir)\n\n    return rd\n\n\ndef get_cpu_count():\n    if platform.system() == \"Windows\":\n        return 0\n\n    cpu_count = os.cpu_count()\n    try:\n        user_max_cores = int(os.getenv(\"MAX_BUILD_CORES\", cpu_count))\n    except ValueError as e:\n        raise ValueError(\n            \"MAX_BUILD_CORES must be set to an integer. \"\n            + \"See above for original error.\"\n        ) from e\n    max_cores = min(cpu_count, user_max_cores)\n    return max_cores\n\n\ndef install_ccompiler():\n    def _compile(\n        self,\n        sources,\n        output_dir=None,\n        macros=None,\n        include_dirs=None,\n        debug=0,\n        extra_preargs=None,\n        extra_postargs=None,\n        depends=None,\n    ):\n        \"\"\"Function to monkey-patch distutils.ccompiler.CCompiler\"\"\"\n        macros, objects, extra_postargs, pp_opts, build = self._setup_compile(\n            output_dir, macros, include_dirs, sources, depends, extra_postargs\n        )\n        cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)\n\n        for obj in objects:\n            try:\n                src, ext = build[obj]\n            except KeyError:\n                continue\n            self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)\n\n        # Return *all* object filenames, not just the ones we just built.\n        return objects\n\n    CCompiler.compile = _compile\n\n\ndef get_python_include_dirs():\n    \"\"\"Extracted from distutils.command.build_ext.build_ext.finalize_options(),\n    https://github.com/python/cpython/blob/812245ecce2d8344c3748228047bab456816180a/Lib/distutils/command/build_ext.py#L148-L167\n    \"\"\"\n    include_dirs = []\n\n    # Make sure Python's include directories (for Python.h, pyconfig.h,\n    # etc.) are in the include search path.\n    py_include = sysconfig.get_python_inc()\n    plat_py_include = sysconfig.get_python_inc(plat_specific=1)\n\n    # If in a virtualenv, add its include directory\n    # Issue 16116\n    if sys.exec_prefix != sys.base_exec_prefix:\n        include_dirs.append(os.path.join(sys.exec_prefix, 'include'))\n\n    # Put the Python \"system\" include dir at the end, so that\n    # any local include dirs take precedence.\n    include_dirs.extend(py_include.split(os.path.pathsep))\n    if plat_py_include != py_include:\n        include_dirs.extend(plat_py_include.split(os.path.pathsep))\n\n    return include_dirs\n\n\nNUMPY_MACROS = [\n    (\"NPY_NO_DEPRECATED_API\", \"NPY_1_7_API_VERSION\"),\n    # keep in sync with runtime requirements (pyproject.toml)\n    (\"NPY_TARGET_VERSION\", \"NPY_1_21_API_VERSION\"),\n]\n\n\ndef create_build_ext(lib_exts, cythonize_aliases):\n    class build_ext(_build_ext):\n        # subclass setuptools extension builder to avoid importing cython and numpy\n        # at top level in setup.py. See http://stackoverflow.com/a/21621689/1382869\n        # NOTE: this is likely not necessary anymore since\n        # pyproject.toml was introduced in the project\n\n        def finalize_options(self):\n            from Cython.Build import cythonize\n\n            # Override the list of extension modules\n            self.distribution.ext_modules[:] = cythonize(\n                lib_exts,\n                aliases=cythonize_aliases,\n                compiler_directives={\"language_level\": 3},\n                nthreads=get_cpu_count(),\n            )\n            _build_ext.finalize_options(self)\n            # Prevent numpy from thinking it is still in its setup process\n            # see http://stackoverflow.com/a/21621493/1382869\n            if isinstance(__builtins__, dict):\n                # sometimes this is a dict so we need to check for that\n                # https://docs.python.org/3/library/builtins.html\n                __builtins__[\"__NUMPY_SETUP__\"] = False\n            else:\n                __builtins__.__NUMPY_SETUP__ = False\n            import numpy\n\n            self.include_dirs.append(numpy.get_include())\n            self.include_dirs.append(ewah_bool_utils.get_include())\n\n            define_macros = NUMPY_MACROS\n            if USE_PY_LIMITED_API:\n                define_macros.append((\"Py_LIMITED_API\", ABI3_TARGET_HEX))\n                for ext in self.extensions:\n                    ext.py_limited_api = True\n\n            if self.define is None:\n                self.define = define_macros\n            else:\n                self.define.extend(define_macros)\n\n        def build_extensions(self):\n            self.check_extensions_list(self.extensions)\n\n            ncpus = get_cpu_count()\n            if ncpus > 0:\n                with ThreadPoolExecutor(ncpus) as executor:\n                    results = {\n                        executor.submit(self.build_extension, extension): extension\n                        for extension in self.extensions\n                    }\n                for result in results:\n                    result.result()\n            else:\n                super().build_extensions()\n\n        def build_extension(self, extension):\n            try:\n                super().build_extension(extension)\n            except CompileError as exc:\n                print(f\"While building '{extension.name}' following error was raised:\\n {exc}\")\n                raise\n\n    class sdist(_sdist):\n        # subclass setuptools source distribution builder to ensure cython\n        # generated C files are included in source distribution.\n        # See http://stackoverflow.com/a/18418524/1382869\n        def run(self):\n            # Make sure the compiled Cython files in the distribution are up-to-date\n            from Cython.Build import cythonize\n\n            cythonize(\n                lib_exts,\n                aliases=cythonize_aliases,\n                compiler_directives={\"language_level\": 3},\n                nthreads=get_cpu_count(),\n            )\n            _sdist.run(self)\n\n    return build_ext, sdist\n\ndef get_setup_options():\n    if USE_PY_LIMITED_API:\n        return {\"bdist_wheel\": {\"py_limited_api\": f\"cp{ABI3_TARGET_VERSION}\"}}\n    else:\n        return {}\n"
  },
  {
    "path": "tests/DD0000/moving7_0000",
    "content": "CurrentTimeIdentifier = 1188160080\n\nMetaDataString      = new_output\nCompilerPrecision   = r8\n\nInitialCycleNumber  = 0\nInitialTime         = 0.81651319185139\nInitialCPUTime      = 0\n\nStopTime            = 20.097276379555\nStopCycle           = 10000\nStopCPUTime         = 360000\n\nTimeLastRestartDump = 0\ndtRestartDump       = 18000\nTimeLastDataDump    = 0.81651309185139\ndtDataDump          = 0.01\ndtDynamicalFrac     = 0.000000\nTimeLastHistoryDump = 0.81651319185139\ndtHistoryDump       = 0\n\nTimeLastMovieDump     = 0.81651319185139\ndtMovieDump           = 0\nTracerParticleOn           = 0\nTimeLastTracerParticleDump = 0.81651319185139\ndtTracerParticleDump       = 0\nMovieRegionLeftEdge   = 0 0 0\nMovieRegionRightEdge  = 1 1 1\n\nNewMovieLeftEdge   = 0 0 0\nNewMovieRightEdge  = 1 1 1\nMovieSkipTimestep = -99999\nMovie2DTextures   = 0\nMovie3DVolumes    = 0\nNewMovieParticleOn = 0\nMovieDataField = -99999 -99999 -99999 -99999 -99999 -99999\nNewMovieDumpNumber = 0\nNewMovieName = MoviePack\n\nCycleLastRestartDump = 0\nCycleSkipRestartDump = 0\nCycleLastDataDump    = 0\nCycleSkipDataDump    = 0\nCycleLastHistoryDump = 0\nCycleSkipHistoryDump = 0\n\nOutputFirstTimeAtLevel = 0\nStopFirstTimeAtLevel = 0\n\nRestartDumpNumber   = 0\nDataDumpNumber      = 1\nHistoryDumpNumber   = 0\nMovieDumpNumber     = 0\nTracerParticleDumpNumber = 0\nRestartDumpName        = restart\nDataDumpName           = moving7_\nHistoryDumpName        = history\nMovieDumpName          = MovieOutput\nTracerParticleDumpName = TracerOutput\nRedshiftDumpName       = RedshiftOutput\n\nStaticHierarchy     = 0\nTopGridRank         = 3\nTopGridDimensions   = 16 16 16\n\nTopGridGravityBoundary = 0\nParticleBoundaryType   = 3\nNumberOfParticles      = 0 (do not modify)\nCourantSafetyNumber    = 0.5\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\n\nProblemType            = 27\nHydroMethod            = 2\nRateSolver             = 0\nhuge_number            = 1.000000e+20\ntiny_number            = 1.000000e-20\nGamma                  = 1.6667\nPressureFree           = 0\nRefineBy               = 2\nMaximumRefinementLevel = 9\nMaximumGravityRefinementLevel = 9\nMaximumParticleRefinementLevel = -1\nCellFlaggingMethod     = 2 -99999 -99999 -99999 -99999\nCellFlaggingSlopeFields     = -99999 -99999 -99999 -99999 -99999 -99999 -99999 -99999 -99999 -99999\nCellFlaggingMagnitudeFields     = -99999 -99999 -99999 -99999 -99999 -99999 -99999 -99999 -99999 -99999\nMinimumMagnitudeForRefinement     = -99999 -99999 -99999 -99999 -99999 -99999 -99999 -99999 -99999 -99999\nFluxCorrection         = 1\nInterpolationMethod    = 3\nConservativeInterpolation = 0\nMinimumEfficiency      = 0.3\nMaximumSubgridSize     = 10000\nNumberOfBufferZones    = 1\n\nDomainLeftEdge         = 0 0 0\nDomainRightEdge        = 1 1 1\nGridVelocity           = 0 0 0\nRefineRegionLeftEdge   = 0.2 0.2 0.2\nRefineRegionRightEdge  = 0.9 0.9 0.9\nRefineRegionTimeType   = 1\n\nDataLabel[0]              = Density\n#DataCGSConversionFactor[0] = 6.2557e-27 g/cm^3\nDataLabel[1]              = TotalEnergy\nDataLabel[2]              = x-velocity\n#DataCGSConversionFactor[2] = 4.06287e+07 cm/s\nDataLabel[3]              = y-velocity\n#DataCGSConversionFactor[3] = 4.06287e+07 cm/s\nDataLabel[4]              = z-velocity\n#DataCGSConversionFactor[4] = 4.06287e+07 cm/s\n#CGSConversionFactorTime       = 1.38147e+16\n#CGSConversionFactorTemp       = 2.068e+07\n\nUniformGravity             = 0\nUniformGravityDirection    = 0\nUniformGravityConstant     = 1\nPointSourceGravity           = 0\nPointSourceGravityPosition   = 0.5 0.5 0.5\nPointSourceGravityConstant   = 0.02\nPointSourceGravityCoreRadius = 0\n\nSelfGravity                    = 1\nGravitationalConstant          = 1.000000e+00\nS2ParticleSize                 = 3\nGravityResolution              = 1\nComputePotential               = 0\nPotentialIterations            = 1\nBaryonSelfGravityApproximation = 0\n\nGreensFunctionMaxNumber     = 10\nGreensFunctionMaxSize       = 1\nDualEnergyFormalism         = 0\nDualEnergyFormalismEta1     = 1.000000e-03\nDualEnergyFormalismEta2     = 1.000000e-01\nParticleCourantSafetyNumber = 0.5\n\nRadiativeCooling               = 1\nMultiSpecies                   = 0\nCIECooling                     = 0\nH2OpticalDepthApproximation    = 0\nMagneticField             = 0\nMagneticDebug             = 0\nMagneticFieldMode         = 3\nMagneticFieldAdd         = 0\nMagneticFieldUnits             = 0\nMagneticSmallNumber             = 0.000001\nTracerMagneticField            = 0\nMagneticFieldMethod            = 0\nMagneticBiermannSave            = 0\nMagneticLagrangeRho            = 0\nMagneticUpwind            = 0\nMagneticLimiter            = 0\nUniformBField    = 0.000000 0.000000 0.000000\nUniformAVector    = 0.000000 0.000000 0.000000\nGradientAVector    = 0 0.000000 0.000000 0.000000\nRadiationFieldType             = 0\nRadiationFieldLevelRecompute   = 0\nRadiationSpectrumNormalization = 1e-21\nRadiationSpectrumSlope         = 1.5\nSterileNeutrinoMass            = 0\nSterileNeutrinoMixingAngle     = 0\nZEUSLinearArtificialViscosity    = 0\nZEUSQuadraticArtificialViscosity = 2\nUseMinimumPressureSupport        = 0\nMinimumPressureSupportParameter  = 100.000000\nRefineByJeansLengthSafetyFactor  = 4.000000\nRefineByCoolingTimeSafetyFactor  = 1.000000\nMustRefineParticlesRefineToLevel = 0\nParticleTypeInFile               = 1\n\nOutputToScratchSpace            = 0\nDataDirectory    = (null)\nScratchDirectory = (null)\nScratchDirectoryCleanupScript = procyon1\n\nParallelRootGridIO              = 0\nMinimumOverDensityForRefinement = 0.2 1.5 1.5 1.5 1.5\nMinimumMassForRefinement = 4.88281257e-05 0.000366210938 0.000366210938 0.000366210938 0.000366210938\nMinimumMassForRefinementLevelExponent = -0.100000 0.000000 0.000000 0.000000 0.000000\nMinimumSlopeForRefinement             = 3.000000e-01\nMinimumPressureJumpForRefinement      = 3.300000e-01\nMinimumEnergyRatioForRefinement       = 4.000000e-01\nComovingCoordinates                   = 1\nStarParticleCreation                  = 0\nStarParticleFeedback                  = 0\nNumberOfParticleAttributes            = 0\nStarMakerOverDensityThreshold         = 100\nStarMakerMassEfficiency               = 1\nStarMakerMinimumMass                  = 1e+09\nStarMakerMinimumDynamicalTime         = 1e+06\nStarMassEjectionFraction              = 0.25\nStarMetalYield                        = 0.02\nStarEnergyToThermalFeedback           = 1e-05\nStarEnergyToStellarUV                 = 3e-06\nStarEnergyToQuasarUV                  = 5e-06\n\nLeftFaceBoundaryCondition  = 3 3 3\nRightFaceBoundaryCondition = 3 3 3\nBoundaryConditionName      = moving7_0000.boundary\n\nCosmologyHubbleConstantNow = 0.5\nCosmologyOmegaMatterNow    = 1\nCosmologyOmegaLambdaNow    = 0\nCosmologyComovingBoxSize   = 1\nCosmologyMaxExpansionRate  = 0.015\nCosmologyInitialRedshift   = 10\nCosmologyFinalRedshift     = 0.3\nCosmologyCurrentRedshift   = 10\n\nVersionNumber              = 1.300000\n"
  },
  {
    "path": "tests/DD0000/moving7_0000.boundary",
    "content": "BoundaryRank         = 3\nBoundaryDimension    = 22 22 22\nNumberOfBaryonFields = 5\nParticleBoundaryType = 3\nBoundaryFieldType    = 0 1 4 5 6\nBaryonFileName       = moving7_0000.boundary.hdf\nBoundaryValuePresent = 0 0 0 0 0 0\n"
  },
  {
    "path": "tests/DD0000/moving7_0000.hierarchy",
    "content": "\nGrid = 1\nGridRank          = 3\nGridDimension     = 22 22 22\nGridStartIndex    = 3 3 3\nGridEndIndex      = 18 18 18\nGridLeftEdge      = 0 0 0\nGridRightEdge     = 1 1 1\nLevel             = 0\nTime              = 0.81651319185139\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = moving7_0000.grid0001\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 0\nGravityBoundaryType = 0\nPointer: Grid[1]->NextGridThisLevel = 0\nPointer: Grid[1]->NextGridNextLevel = 2\n\nGrid = 2\nGridRank          = 3\nGridDimension     = 28 28 28\nGridStartIndex    = 3 3 3\nGridEndIndex      = 24 24 24\nGridLeftEdge      = 0.1875 0.1875 0.1875\nGridRightEdge     = 0.875 0.875 0.875\nLevel             = 1\nTime              = 0.81651319185139\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = moving7_0000.grid0002\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 0\nGravityBoundaryType = 2\nPointer: Grid[2]->NextGridThisLevel = 0\nPointer: Grid[2]->NextGridNextLevel = 3\n\nGrid = 3\nGridRank          = 3\nGridDimension     = 34 34 34\nGridStartIndex    = 3 3 3\nGridEndIndex      = 30 30 30\nGridLeftEdge      = 0.28125 0.28125 0.28125\nGridRightEdge     = 0.71875 0.71875 0.71875\nLevel             = 2\nTime              = 0.81651319185139\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = moving7_0000.grid0003\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 0\nGravityBoundaryType = 2\nPointer: Grid[3]->NextGridThisLevel = 0\nPointer: Grid[3]->NextGridNextLevel = 0\n"
  },
  {
    "path": "tests/DD0010/moving7_0010",
    "content": "InitialCycleNumber  = 10\nInitialTime         = 0.81751317119117\nInitialCPUTime      = 2.15207e+09\nCurrentTimeIdentifier = 0\n\nStopTime            = 20.097275649537\nStopCycle           = 10000\nStopCPUTime         = 360000\n\nTimeLastRestartDump = 0\ndtRestartDump       = 18000\nTimeLastDataDump    = 0.81751316119217\ndtDataDump          = 0.0001\nTimeLastHistoryDump = 0.81651316219217\ndtHistoryDump       = 0\n\nTimeLastMovieDump     = 0.81651316219217\ndtMovieDump           = 0\nTracerParticleOn           = 0\nTimeLastTracerParticleDump = 0.81651316219217\ndtTracerParticleDump       = 0\nMovieRegionLeftEdge   = 0 0 0\nMovieRegionRightEdge  = 1 1 1\n\nNewMovieLeftEdge   = 0 0 0\nNewMovieRightEdge  = 1 1 1\nMovieSkipTimestep = -99999\nNewMovieParticleOn = 0\nMovieDataField = -99999\nNewMovieDumpNumber = 0\nNewMovieName = MoviePack\n\nCycleLastRestartDump = 0\nCycleSkipRestartDump = 0\nCycleLastDataDump    = 0\nCycleSkipDataDump    = 0\nCycleLastHistoryDump = 0\nCycleSkipHistoryDump = 0\n\nCycleSkipGlobalDataDump = 0\n\nOutputFirstTimeAtLevel = 0\nStopFirstTimeAtLevel = 0\n\nRestartDumpNumber   = 0\nDataDumpNumber      = 11\nHistoryDumpNumber   = 0\nMovieDumpNumber     = 0\nTracerParticleDumpNumber = 0\nRestartDumpName     = restart\nDataDumpName        = moving7_\nHistoryDumpName     = history\nMovieDumpName       = MovieOutput\nTracerParticleDumpName = TracerOutput\nRedshiftDumpName    = RedshiftOutput\n\nRestartDumpDir      = RS\nDataDumpDir         = DD\nHistoryDumpDir      = HD\nMovieDumpDir        = MD\nTracerParticleDumpDir = TD\nRedshiftDumpDir     = RD\n\nGlobalDir           = /rmount/users08/stanford/mturk/data\nStaticHierarchy     = 0\nTopGridRank         = 3\nTopGridDimensions   = 16 16 16\n\nTopGridGravityBoundary = 0\nParticleBoundaryType   = 3\nNumberOfParticles      = 0 (do not modify)\nCourantSafetyNumber    = 0.5\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\n\nProblemType            = 27\nHydroMethod            = 2\nhuge_number            = 1.000000e+20\ntiny_number            = 1.000000e-20\nGamma                  = 1.6667\nPressureFree           = 0\nRefineBy               = 2\nMaximumRefinementLevel = 9\nMaximumGravityRefinementLevel = 9\nMaximumParticleRefinementLevel = -1\nCellFlaggingMethod     = 2 -99999 -99999 -99999 -99999 -99999 -99999\nFluxCorrection         = 1\nInterpolationMethod    = 3\nConservativeInterpolation = 0\nMinimumEfficiency      = 0.3\nMinimumSubgridEdge     = 4\nMaximumSubgridSize     = 2000\nNumberOfBufferZones    = 1\n\nMustRefineRegionMinRefinementLevel = -1\nMetallicityRefinementMinLevel = -1\nMetallicityRefinementMinMetallicity      = 1e-05\nDomainLeftEdge         = 0 0 0\nDomainRightEdge        = 1 1 1\nGridVelocity           = 0 0 0\nRefineRegionLeftEdge   = 0.2 0.2 0.2\nRefineRegionRightEdge  = 0.9 0.9 0.9\nMustRefineRegionLeftEdge   = 0 0 0\nMustRefineRegionRightEdge  = 1 1 1\n\nDataLabel[0]              = Density\nDataUnits[0]              = none\n#DataCGSConversionFactor[0] = 6.24041e-27\nDataLabel[1]              = TotalEnergy\nDataUnits[1]              = none\nDataLabel[2]              = x-velocity\nDataUnits[2]              = none\n#DataCGSConversionFactor[2] = 4.06287e+07\nDataLabel[3]              = y-velocity\nDataUnits[3]              = none\n#DataCGSConversionFactor[3] = 4.06287e+07\nDataLabel[4]              = z-velocity\nDataUnits[4]              = none\n#DataCGSConversionFactor[4] = 4.06287e+07\nDataUnits[5]              = none\n\nUniformGravity             = 0\nUniformGravityDirection    = 0\nUniformGravityConstant     = 1\nPointSourceGravity           = 0\nPointSourceGravityPosition   = 0.5 0.5 0.5\nPointSourceGravityConstant   = 0.02\nPointSourceGravityCoreRadius = 0\n\nSelfGravity                    = 1\nGravitationalConstant          = 1.000000e+00\nS2ParticleSize                 = 3\nGravityResolution              = 1\nComputePotential               = 0\nWritePotential                 = 0\nBaryonSelfGravityApproximation = 0\n\nGreensFunctionMaxNumber     = 10\nGreensFunctionMaxSize       = 1\nDualEnergyFormalism         = 0\nDualEnergyFormalismEta1     = 1.000000e-03\nDualEnergyFormalismEta2     = 1.000000e-01\nParticleCourantSafetyNumber = 0.5\n\nRandomForcing               = 0\nRandomForcingEdot           = -1\nRadiativeCooling               = 1\nGadgetEquilibriumCooling       = 0\nMultiSpecies                   = 0\nRadiationFieldType             = 0\nGloverChemistryModel           = 0\nGloverRadiationBackground      = 0\nGloverOpticalDepth             = 0\nCloudyCooling                  = 0\nCloudyCoolingGridRank          = 0\nCloudyCoolingGridRunFile       =\nIncludeCloudyHeating           = 0\nCMBTemperatureFloor            = 0\nCloudyMetallicityNormalization = 0.018477\nAdjustUVBackground             = 1\nSetUVBAmplitude                = 1\nSetHeIIHeatingScale            = 1.8\nRadiationFieldLevelRecompute   = 0\nRadiationSpectrumNormalization = 1e-21\nRadiationSpectrumSlope         = 1.5\nZEUSLinearArtificialViscosity    = 0\nZEUSQuadraticArtificialViscosity = 2\nUseMinimumPressureSupport        = 0\nMinimumPressureSupportParameter  = 100.000000\nRefineByJeansLengthSafetyFactor  = 4.000000\nMustRefineParticlesRefineToLevel = 0\nParticleTypeInFile               = 1\nParallelRootGridIO              = 0\nParallelParticleIO              = 0\nUnigrid                         = 0\nPartitionNestedGrids            = 0\nExtractFieldsOnly               = 1\nCubeDumpEnabled                 = 0\nSRBprefix           = /NONE\nMinimumOverDensityForRefinement = 0.2 1.5 1.5 1.5 1.5 1.5 1.5\nMinimumMassForRefinement = 4.8828125e-05 0.000366210938 0.000366210938 0.000366210938 0.000366210938 0.000366210938 0.000366210938\nMinimumMassForRefinementLevelExponent = -0.100000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000\nMinimumSlopeForRefinement             = 3.000000e-01\nMinimumShearForRefinement             = 1.000000e+00\nMinimumPressureJumpForRefinement      = 3.300000e-01\nMinimumEnergyRatioForRefinement       = 4.000000e-01\nComovingCoordinates                   = 1\nStarParticleCreation                  = 0\nStarParticleFeedback                  = 0\nNumberOfParticleAttributes            = 0\nStarMakerOverDensityThreshold         = 100\nStarMakerMassEfficiency               = 1\nStarMakerMinimumMass                  = 1e+09\nStarMakerMinimumDynamicalTime         = 1e+06\nStarMassEjectionFraction              = 0.25\nStarMetalYield                        = 0.02\nStarEnergyToThermalFeedback           = 1e-05\nStarEnergyToStellarUV                 = 3e-06\nStarEnergyToQuasarUV                  = 5e-06\n\nMultiMetals                           = 0\nLeftFaceBoundaryCondition  = 3 3 3\nRightFaceBoundaryCondition = 3 3 3\nBoundaryConditionName      = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.boundary\n\nCosmologyHubbleConstantNow = 0.5\nCosmologyOmegaMatterNow    = 1\nCosmologyOmegaLambdaNow    = 0\nCosmologyComovingBoxSize   = 1\nCosmologyMaxExpansionRate  = 0.015\nCosmologyInitialRedshift   = 10\nCosmologyFinalRedshift     = 0.3\nCosmologyCurrentRedshift   = 9.9910277956689\n\nVersionNumber              = 1.300000\n"
  },
  {
    "path": "tests/DD0010/moving7_0010.boundary",
    "content": "BoundaryRank         = 3\nBoundaryDimension    = 22 22 22\nNumberOfBaryonFields = 5\nParticleBoundaryType = 3\nBoundaryFieldType    = 0 1 4 5 6\nBaryonFileName       = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.boundary.hdf\nBoundaryValuePresent = 0 0 0 0 0 0\n"
  },
  {
    "path": "tests/DD0010/moving7_0010.hierarchy",
    "content": "\nGrid = 1\nGridRank          = 3\nGridDimension     = 22 22 22\nGridStartIndex    = 3 3 3\nGridEndIndex      = 18 18 18\nGridLeftEdge      = 0 0 0\nGridRightEdge     = 1 1 1\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 4962\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 0\nPointer: Grid[1]->NextGridThisLevel = 0\nPointer: Grid[1]->NextGridNextLevel = 2\n\nGrid = 2\nGridRank          = 3\nGridDimension     = 16 16 16\nGridStartIndex    = 3 3 3\nGridEndIndex      = 12 12 12\nGridLeftEdge      = 0.1875 0.1875 0.1875\nGridRightEdge     = 0.5 0.5 0.5\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 577\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 2\nPointer: Grid[2]->NextGridThisLevel = 3\n\nGrid = 3\nGridRank          = 3\nGridDimension     = 16 18 18\nGridStartIndex    = 3 3 3\nGridEndIndex      = 12 14 14\nGridLeftEdge      = 0.5625 0.5 0.5\nGridRightEdge     = 0.875 0.875 0.875\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 49\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 2\nPointer: Grid[3]->NextGridThisLevel = 4\n\nGrid = 4\nGridRank          = 3\nGridDimension     = 8 16 16\nGridStartIndex    = 3 3 3\nGridEndIndex      = 4 12 12\nGridLeftEdge      = 0.5 0.5625 0.5625\nGridRightEdge     = 0.5625 0.875 0.875\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 28\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 2\nPointer: Grid[4]->NextGridThisLevel = 0\nPointer: Grid[4]->NextGridNextLevel = 0\nPointer: Grid[3]->NextGridNextLevel = 5\n\nGrid = 5\nGridRank          = 3\nGridDimension     = 26 26 26\nGridStartIndex    = 3 3 3\nGridEndIndex      = 22 22 22\nGridLeftEdge      = 0.5625 0.5625 0.5625\nGridRightEdge     = 0.875 0.875 0.875\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 1670\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 2\nPointer: Grid[5]->NextGridThisLevel = 0\nPointer: Grid[5]->NextGridNextLevel = 6\n\nGrid = 6\nGridRank          = 3\nGridDimension     = 26 26 26\nGridStartIndex    = 3 3 3\nGridEndIndex      = 22 22 22\nGridLeftEdge      = 0.671875 0.671875 0.671875\nGridRightEdge     = 0.828125 0.828125 0.828125\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 1173\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 2\nPointer: Grid[6]->NextGridThisLevel = 0\nPointer: Grid[6]->NextGridNextLevel = 7\n\nGrid = 7\nGridRank          = 3\nGridDimension     = 22 22 22\nGridStartIndex    = 3 3 3\nGridEndIndex      = 18 18 18\nGridLeftEdge      = 0.71875 0.71875 0.71875\nGridRightEdge     = 0.78125 0.78125 0.78125\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 490\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 2\nPointer: Grid[7]->NextGridThisLevel = 0\nPointer: Grid[7]->NextGridNextLevel = 8\n\nGrid = 8\nGridRank          = 3\nGridDimension     = 18 18 18\nGridStartIndex    = 3 3 3\nGridEndIndex      = 14 14 14\nGridLeftEdge      = 0.73828125 0.73828125 0.73828125\nGridRightEdge     = 0.76171875 0.76171875 0.76171875\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 169\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 2\nPointer: Grid[8]->NextGridThisLevel = 0\nPointer: Grid[8]->NextGridNextLevel = 9\n\nGrid = 9\nGridRank          = 3\nGridDimension     = 16 16 16\nGridStartIndex    = 3 3 3\nGridEndIndex      = 12 12 12\nGridLeftEdge      = 0.74609375 0.74609375 0.74609375\nGridRightEdge     = 0.755859375 0.755859375 0.755859375\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 67\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 2\nPointer: Grid[9]->NextGridThisLevel = 0\nPointer: Grid[9]->NextGridNextLevel = 10\n\nGrid = 10\nGridRank          = 3\nGridDimension     = 14 16 16\nGridStartIndex    = 3 3 3\nGridEndIndex      = 10 12 12\nGridLeftEdge      = 0.7490234375 0.748046875 0.748046875\nGridRightEdge     = 0.7529296875 0.7529296875 0.7529296875\nTime              = 0.81751317119117\nSubgridsAreStatic = 0\nNumberOfBaryonFields = 5\nFieldType = 0 1 4 5 6\nBaryonFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nCourantSafetyNumber    = 0.500000\nPPMFlatteningParameter = 0\nPPMDiffusionParameter  = 0\nPPMSteepeningParameter = 0\nNumberOfParticles   = 48\nParticleFileName = /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000\nGravityBoundaryType = 2\nPointer: Grid[10]->NextGridThisLevel = 0\nPointer: Grid[10]->NextGridNextLevel = 0\nPointer: Grid[2]->NextGridNextLevel = 0\n"
  },
  {
    "path": "tests/DD0010/moving7_0010.procmap",
    "content": "       1  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000001\n       2  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000002\n       3  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000003\n       4  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000004\n       5  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000005\n       6  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000006\n       7  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000007\n       8  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000008\n       9  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000009\n      10  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000010\n       1  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000001\n       2  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000002\n       3  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000003\n       4  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000004\n       5  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000005\n       6  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000006\n       7  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000007\n       8  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000008\n       9  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000009\n      10  /rmount/users08/stanford/mturk/data/DD0010/moving7_0010.cpu0000  Grid00000010\n"
  },
  {
    "path": "tests/README",
    "content": "This directory contains two tiny enzo cosmological datasets.\n\nThey were added a long time ago and are provided for testing purposes.\n"
  },
  {
    "path": "tests/ci_install.sh",
    "content": "set -x   # Show which command is being run\n\n# Step 1: install system level dependencies for ratarmount\nif [[ ${sync_args} == *\"full\"* ]]; then\n    case ${RUNNER_OS} in\n    linux|Linux)\n        sudo apt-get -qqy update\n        sudo apt-get -qqy install libfuse2\n        ;;\n    osx|macOS)\n        sudo mkdir -p /usr/local/man\n        sudo chown -R \"${USER}:admin\" /usr/local/man\n        HOMEBREW_NO_AUTO_UPDATE=1 brew install macfuse\n        ;;\n    esac\nfi\n\n# Step 2: install deps and yt\n# installing in editable mode so this script may be used locally by developers\n# but the primary intention is to embed this script in CI jobs\nuv sync --extra=test ${sync_args}\n\n# Disable excessive output\nuv run --no-sync yt config set --local yt log_level 50\ncat yt.toml\n\nset +x\n"
  },
  {
    "path": "tests/local001.bak",
    "content": ""
  },
  {
    "path": "tests/local001.dir",
    "content": ""
  },
  {
    "path": "tests/matplotlibrc",
    "content": "#### MATPLOTLIBRC FORMAT\n\nbackend : Agg\n"
  },
  {
    "path": "tests/nose_runner.py",
    "content": "import multiprocessing\nimport os\nimport sys\n\nimport nose\nimport numpy\nimport yaml\n\nfrom yt.config import ytcfg\nfrom yt.utilities.answer_testing.framework import AnswerTesting\n\nnumpy.set_printoptions(threshold=5, edgeitems=1, precision=4)\n\n\nclass NoseWorker(multiprocessing.Process):\n    def __init__(self, task_queue, result_queue):\n        multiprocessing.Process.__init__(self)\n        self.task_queue = task_queue\n        self.result_queue = result_queue\n\n    def run(self):\n        proc_name = self.name\n        while True:\n            next_task = self.task_queue.get()\n            if next_task is None:\n                print(f\"{proc_name}: Exiting\")\n                self.task_queue.task_done()\n                break\n            print(f\"{proc_name}: {next_task}\")\n            result = next_task()\n            self.task_queue.task_done()\n            self.result_queue.put(result)\n            if next_task.exclusive:\n                print(f\"{proc_name}: Exiting (exclusive)\")\n                break\n        return\n\n\nclass NoseTask:\n    def __init__(self, job):\n        argv, exclusive = job\n        self.argv = argv\n        self.name = argv[0]\n        self.exclusive = exclusive\n\n    def __call__(self):\n        test_dir = ytcfg.get(\"yt\", \"test_data_dir\")\n        answers_dir = os.path.join(test_dir, \"answers\")\n        if \"--with-answer-testing\" in self.argv and not os.path.isdir(\n            os.path.join(answers_dir, self.name)\n        ):\n            nose.run(\n                argv=self.argv + [\"--answer-store\"],\n                addplugins=[AnswerTesting()],\n                exit=False,\n            )\n        if os.path.isfile(f\"{self.name}.xml\"):\n            os.remove(f\"{self.name}.xml\")\n        nose.run(argv=self.argv, addplugins=[AnswerTesting()], exit=False)\n        return \"\"\n\n    def __str__(self):\n        return f\"WILL DO self.name = {self.name}\"\n\n\ndef generate_tasks_input():\n    pyver = f\"py{sys.version_info.major}{sys.version_info.minor}\"\n    test_dir = ytcfg.get(\"yt\", \"test_data_dir\")\n    answers_dir = os.path.join(test_dir, \"answers\")\n    tests = yaml.load(open(\"tests/tests.yaml\"), Loader=yaml.FullLoader)\n\n    base_argv = [\"-s\", \"--nologcapture\", \"--with-xunit\"]\n\n    base_answer_argv = [\n        f\"--local-dir={answers_dir}\",\n        \"--with-answer-testing\",\n        \"--answer-big-data\",\n        \"--local\",\n    ]\n\n    args = []\n\n    for test in list(tests[\"other_tests\"].keys()):\n        args.append(([test] + base_argv + tests[\"other_tests\"][test], True))\n    for answer in list(tests[\"answer_tests\"].keys()):\n        if tests[\"answer_tests\"][answer] is None:\n            continue\n        argv = [f\"{pyver}_{answer}\"]\n        argv += base_argv + base_answer_argv\n        argv.append(f\"--answer-name={argv[0]}\")\n        argv += tests[\"answer_tests\"][answer]\n        args.append((argv, False))\n\n    exclude_answers = []\n    answer_tests = tests[\"answer_tests\"]\n    for key in answer_tests:\n        for t in answer_tests[key]:\n            exclude_answers.append(t.replace(\".py:\", \".\").replace(\"/\", \".\"))\n    exclude_answers = [f\"--exclude-test={ex}\" for ex in exclude_answers]\n\n    args = [\n        (\n            (item + [f\"--xunit-file={item[0]}.xml\"], exclusive)\n            if item[0] != \"unittests\"\n            else (item + [\"--xunit-file=unittests.xml\"] + exclude_answers, exclusive)\n        )\n        for item, exclusive in args\n    ]\n    return args\n\n\nif __name__ == \"__main__\":\n    # multiprocessing.log_to_stderr(logging.DEBUG)\n    tasks = multiprocessing.JoinableQueue()\n    results = multiprocessing.Queue()\n\n    num_consumers = int(os.environ.get(\"NUM_WORKERS\", 6))\n    consumers = [NoseWorker(tasks, results) for i in range(num_consumers)]\n    for w in consumers:\n        w.start()\n\n    num_jobs = 0\n    for job in generate_tasks_input():\n        if job[1]:\n            num_consumers -= 1  # take into account exclusive jobs\n        tasks.put(NoseTask(job))\n        num_jobs += 1\n\n    for _i in range(num_consumers):\n        tasks.put(None)\n\n    tasks.join()\n\n    while num_jobs:\n        result = results.get()\n        num_jobs -= 1\n"
  },
  {
    "path": "tests/pytest_runner.py",
    "content": "\"\"\"This is a helper script for running answer tests on CI services.\n\nIt's currently used on:\n  * Jenkins\n  * GHA\nfor executing answer tests and optionally generating new answers.\n\"\"\"\n\nimport glob\nimport os\n\nimport pytest\n\nif __name__ == \"__main__\":\n    os.environ[\"OMP_NUM_THREADS\"] = \"1\"\n    pytest_args = [\n        \"-s\",\n        \"-v\",\n        \"-rsfE\",  # it means -r \"sfE\" (show skipped, failed, errors), no -r -s -f -E\n        \"--with-answer-testing\",\n        \"-m answer_test\",\n        f\"-n {int(os.environ.get('NUM_WORKERS', 1))}\",\n        \"--dist=loadscope\",\n    ]\n    pytest.main(pytest_args + [\"--local-dir=answer-store\", \"--junitxml=answers.xml\"])\n\n    if files := glob.glob(\"generate_test*.txt\"):\n        tests = set()\n        for fname in files:\n            with open(fname) as fp:\n                tests |= set(fp.read().splitlines())\n        output_dir = \"artifacts\"\n        if not os.path.isdir(output_dir):\n            os.mkdir(output_dir)\n        pytest.main(\n            pytest_args + [f\"--local-dir={output_dir}\", \"--answer-store\"] + list(tests)\n        )\n"
  },
  {
    "path": "tests/report_failed_answers.py",
    "content": "\"\"\"\nScript to generate report of failed answer tests or to generate golden answers\non cloud platforms like Travis\n\n\"\"\"\n\nimport argparse\nimport base64\nimport collections\nimport datetime\nimport logging\nimport os\nimport re\nimport shutil\nimport sys\nimport tempfile\nimport xml.etree.ElementTree as ET\n\nimport nose\nimport numpy\nimport requests\n\nfrom yt.config import ytcfg\nfrom yt.utilities.answer_testing.framework import AnswerTesting\nfrom yt.utilities.command_line import FileStreamer\n\nlogging.basicConfig(level=logging.INFO)\nlog = logging.getLogger(\"yt_report_failed_answers\")\nnumpy.set_printoptions(threshold=5, edgeitems=1, precision=4)\n\n\ndef generate_failed_answers_html(failed_answers):\n    \"\"\"Generates html for the failed answer tests\n\n    This function creates a html and embeds the images (actual, expected,\n    difference) in it for the failed answers.\n\n    Parameters\n    ----------\n    failed_answers : dict mapping string to dict\n        the key is a string denoting the test name, the value is a\n        dictionary that stores the actual, expected and difference plot\n        file locations of the test.\n\n    Returns\n    -------\n    string\n        a html page\n\n    \"\"\"\n\n    html_template = \"\"\"\n    <html><head>\n    <style media=\"screen\" type=\"text/css\">\n    img{{\n      width:100%;\n      max-width:800px;\n    }}\n    </style>\n    <h1 style=\"text-align: center;\">Failed Answer Tests</h1>\n    <p>\n      This report shows images of answer tests that failed when running\n      the answer tests.\n    </p>\n    <p>\n      <strong>Actual Image:</strong> plot generated while running the test<br/>\n      <strong>Expected Image:</strong> golden answer image<br/>\n      <strong>Difference Image:</strong> difference in the \"actual\"\n      and \"expected\" image\n    </p>\n    <hr/>\n    </head><body>\n    <table>{rows}</table>\n    </body></html>\n    \"\"\"\n\n    row_template = \"\"\"\n    <tr>\n    <td align=\"center\">Actual</td>\n    <td align=\"center\">Expected</td>\n    <td align=\"center\">Difference</td>\n    </tr>\n    <tr>\n    <td><img src=\"data:image/png;base64,{0}\"></td>\n    <td><img src=\"data:image/png;base64,{1}\"></td>\n    <td><img src=\"data:image/png;base64,{2}\"></td>\n    </tr>\n    <tr><td align=\"center\" colspan=\"3\"><b>Test: {3}</b><hr/></td></tr>\n    \"\"\"\n\n    rows = []\n\n    for failed_test_file in failed_answers.values():\n        for test_name, images in failed_test_file.items():\n            encoded_images = {}\n            for key in images:\n                with open(images[key], \"rb\") as img:\n                    img_data = base64.b64encode(img.read()).decode()\n                    encoded_images[key] = img_data\n\n            formatted_row = row_template.format(\n                encoded_images[\"Actual\"],\n                encoded_images[\"Expected\"],\n                encoded_images[\"Difference\"],\n                test_name,\n            )\n            rows.append(formatted_row)\n\n    html = html_template.format(rows=\"\\n\".join(rows))\n    return html\n\n\ndef upload_to_curldrop(data, filename):\n    \"\"\"Uploads file to yt's curldrop server\n\n    Uploads bytes `data` by the name `filename` to yt curldrop server.\n\n    Parameters\n    ----------\n    data : bytes\n        Content to be uploaded\n\n    filename : string\n        Name of file at curldrop's upload server\n\n    Returns\n    -------\n    requests.models.Response\n        Response returned by curldrop server\n\n    \"\"\"\n    if \"TRAVIS\" in os.environ:\n        job_num = os.environ[\"TRAVIS_JOB_NUMBER\"]\n        file_id = \"Travis_Job_Num_\" + job_num.replace(\".\", \"_\")\n    else:\n        file_id = datetime.datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\")\n    filename = filename.format(file_id)\n\n    base_url = ytcfg.get(\"yt\", \"curldrop_upload_url\")\n    upload_url = base_url + \"/\" + os.path.basename(filename)\n    response = requests.put(upload_url, data=data)\n    return response\n\n\ndef upload_failed_answers(failed_answers):\n    \"\"\"Uploads the result of failed answer tests\n\n    Uploads a html page of the failed answer tests.\n\n    Parameters\n    ----------\n    failed_answers : dict mapping string to dict\n        the key is a string denoting the test name, the value is a\n        dictionary that stores the actual, expected and difference plot\n        file locations of the test.\n\n    Returns\n    -------\n    requests.models.Response\n        Response as returned by the upload service\n\n    \"\"\"\n    html = generate_failed_answers_html(failed_answers)\n    # convert html str to bytes\n    html = html.encode()\n    response = upload_to_curldrop(data=html, filename=\"failed_answers_{}.html\")\n\n    return response\n\n\ndef generate_answers(answer_dir, answers):\n    \"\"\"Generate golden answers\n\n    Generates golden answers for the list of answers in ``answers`` and\n    saves them at ``answer_dir``.\n\n    Parameters\n    ----------\n    answer_dir : string\n        directory location to save the generated answers\n\n    answers : list of string\n        Collection of missing answer tests specifying full name of the test.\n        eg. ['yt.visualization.tests.test_line_plots:test_multi_line_plot']\n\n    Returns\n    -------\n    bool\n        True, if all the missing answers are successfully generated\n        False, otherwise\n\n    \"\"\"\n    status = True\n    test_argv = [\n        os.path.basename(__file__),\n        \"--with-answer-testing\",\n        \"--nologcapture\",\n        \"-s\",\n        \"-d\",\n        \"-v\",\n        \"--local\",\n        f\"--local-dir={answer_dir}\",\n        \"--answer-store\",\n    ]\n\n    for job in answers:\n        log.info(\"\\n Generating answers for %s\", job)\n        status &= nose.run(\n            argv=test_argv + [job], addplugins=[AnswerTesting()], exit=False\n        )\n    return status\n\n\ndef upload_answers(answers):\n    \"\"\"Uploads answers not present in answer-store\n\n    This function generates the answers for tests that are not present in\n    answer store and uploads a zip file of the same.\n\n    Parameters\n    ----------\n    answers : list of string\n        Collection of missing answer tests specifying full name of the test.\n        eg. ['yt.visualization.tests.test_line_plots:test_multi_line_plot']\n\n    Returns\n    -------\n    requests.models.Response\n        Response as returned by the upload service when answers are\n        successfully uploaded\n\n    None\n        for the case when there was some error while generating the missing\n        golden-answers\n\n    \"\"\"\n    # Create temporary location to save new answers\n    tmpdir = tempfile.mkdtemp()\n    answer_dir = os.path.join(tmpdir, \"answer-store\")\n    if not os.path.exists(answer_dir):\n        os.mkdir(answer_dir)\n    zip_file = os.path.join(tmpdir, \"new-answers\")\n\n    status = generate_answers(answer_dir, answers)\n    if status:\n        zip_file = shutil.make_archive(zip_file, \"zip\", answer_dir)\n        data = iter(FileStreamer(open(zip_file, \"rb\")))\n        response = upload_to_curldrop(data=data, filename=\"new_answers_{}.zip\")\n        shutil.rmtree(tmpdir)\n        return response\n    return None\n\n\ndef extract_image_locations(error_string):\n    \"\"\"Regex based function to extract image file locations.\n\n    Parameters\n    ----------\n    error_string : String\n        The input string having file locations of 'Actual', 'Expected' and\n        'Difference' plots. This string is generated by yt's answer-testing\n        plugin, when the plot generated in the test does not match to its\n        golden answer image.\n\n    Returns\n    -------\n    dict\n        If the `error_string` is successfully parsed to extract plot locations,\n        then a dictionary with the keys 'Actual', 'Expected','Difference' and\n        values having corresponding plot file locations is returned.\n        eg. {'Actual': '/usr/tmp/tmp43la9b0w.png',\n             'Expected': '/usr/tmp/tmpbpaqbgi3.png',\n             'Difference': '/usr/tmp/tmp43la9b0w-failed-diff.png'}\n    None\n        When `error_string` does not conform to yt's answer-testing error\n        message, which has the information for plot file locations on disk.\n\n    \"\"\"\n    unknown_failure = False\n    base_regex = r\"\\s*\\n\\s*(.*?.png)\"\n    img_regex = {\n        \"Actual\": \"Actual:\" + base_regex,\n        \"Expected\": \"Expected:\" + base_regex,\n        \"Difference\": \"Difference:\" + base_regex,\n    }\n    img_path = {}\n    for key in img_regex:\n        result = re.search(img_regex[key], error_string, re.MULTILINE)\n        if not result:\n            unknown_failure = True\n            break\n        # store the locations of actual, expected and diff plot files\n        img_path[key] = result.group(1)\n\n    if not unknown_failure:\n        return img_path\n    return None\n\n\ndef parse_nose_xml(nose_xml):\n    \"\"\"Parse xml file generated by nosetests.\n\n    Parse nose xml file to find following details:\n        Failed tests: These could be due to difference in golden answer image\n        and corresponding test plot.\n\n        Missing tests: These errors occur when a corresponding golden answer\n        image is not found.\n\n    Parameters\n    ----------\n    nose_xml : string\n        full path of xml file to be parsed\n\n    Returns\n    -------\n    tuple : (failed_answers, missing_answers)\n\n        failed_answers : list of tuples (string, dict)\n        Collection of tuples where the first part is a string denoting the\n        test name, the second part is a dictionary that stores the actual,\n        expected and difference plot file locations of the test.\n        eg. [('yt.visualization.tests.test_line_plots:test_line_plot',\n                {'Actual': '/usr/tmp/tmp43la9b0w.png',\n                'Expected': '/usr/tmp/tmpbpaqbgi3.png',\n                'Difference': '/usr/tmp/tmp43la9b0w-failed-diff.png'}\n            )]\n\n        missing_answers : list of string\n        Collection of missing answer tests specifying full name of the test.\n        eg. ['yt.visualization.tests.test_line_plots:test_multi_line_plot']\n\n    \"\"\"\n    missing_answers = set()\n    failed_answers = collections.defaultdict(lambda: {})\n    missing_errors = [\"No such file or directory\", \"There is no old answer available\"]\n    tree = ET.parse(nose_xml)\n    testsuite = tree.getroot()\n\n    for testcase in testsuite:\n        for error in testcase.iter(\"error\"):\n            handle_error(\n                error, testcase, missing_errors, missing_answers, failed_answers\n            )\n        for error in testcase.iter(\"failure\"):\n            handle_error(\n                error, testcase, missing_errors, missing_answers, failed_answers\n            )\n    return failed_answers, missing_answers\n\n\ndef handle_error(error, testcase, missing_errors, missing_answers, failed_answers):\n    attribs = [\"classname\", \"name\"]\n    test_name = \":\".join(testcase.attrib[a] for a in attribs)\n    message = error.attrib[\"message\"]\n    if (\n        missing_errors[0] in error.attrib[\"message\"]\n        or missing_errors[1] in error.attrib[\"message\"]\n    ):\n        missing_answers.add(test_name)\n    elif \"Items are not equal\" in error.attrib[\"message\"]:\n        img_path = extract_image_locations(error.attrib[\"message\"])\n        if img_path:\n            failed_answers[test_name][message] = img_path\n\n\nif __name__ == \"__main__\":\n    \"\"\"Report failed answer tests of cloud platforms like Travis, Appveyor\n\n    This script parses the nosetests xml file generated after answer tests are\n    executed. If the test fail due to difference in actual and expected images,\n    this function uploads a html page having all the plots which got failed\n    (if executed with `-f` command line argument).\n    In case, answer store does not has a golden answer and if executed with\n    `-m` argument, it uploads missing answers zip file to yt's curldrop server.\n\n    \"\"\"\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\n        \"-f\",\n        \"--upload-failed-tests\",\n        action=\"store_true\",\n        help=\"Upload a comparison report of failed answer tests\"\n        \" to yt's curldrop server.\",\n    )\n    parser.add_argument(\n        \"-m\",\n        \"--upload-missing-answers\",\n        action=\"store_true\",\n        help=\"Upload tests' answers that are not found in answer-store.\",\n    )\n    parser.add_argument(\n        \"--xunit-file\",\n        action=\"store\",\n        dest=\"nosetest_xml\",\n        required=True,\n        help=\"Name of the nosetests xml file to parse for failed answer tests.\",\n    )\n    args = parser.parse_args()\n\n    # ANSI color codes\n    COLOR_PURPLE = \"\\x1b[35;1m\"\n    COLOR_CYAN = \"\\x1b[36;1m\"\n    COLOR_RESET = \"\\x1b[0m\"\n    FLAG_EMOJI = \" \\U0001f6a9 \"\n\n    failed_answers = missing_answers = None\n    if args.upload_failed_tests or args.upload_missing_answers:\n        failed_answers, missing_answers = parse_nose_xml(args.nosetest_xml)\n\n    if args.upload_failed_tests and failed_answers:\n        response = upload_failed_answers(failed_answers)\n        msg = \"\"\n        if response.ok:\n            msg += (\n                \"\\n\"\n                + FLAG_EMOJI\n                + COLOR_PURPLE\n                + \"Successfully uploaded failed answer test(s) result.\"\n                \" More details about the test failure can be found at the\"\n                \" URL: \"\n                + response.text.split(\"\\n\")[1]\n                + COLOR_RESET\n                + FLAG_EMOJI\n                + \"\\n\"\n            )\n        response = upload_answers(failed_answers)\n        if response is None:\n            log.error(\"Failed to upload answers for failed tests !\")\n            sys.exit(1)\n        if response.ok:\n            msg += (\n                FLAG_EMOJI\n                + COLOR_CYAN\n                + \"Successfully uploaded answer(s) for failed test at URL: \"\n                + response.text.split(\"\\n\")[1]\n                + \" . Please commit these \"\n                \"answers in the repository's answer-store.\" + COLOR_RESET + FLAG_EMOJI\n            )\n            log.info(msg)\n\n    if args.upload_missing_answers and missing_answers:\n        response = upload_answers(missing_answers)\n        if response is None:\n            log.error(\"Failed to upload missing answers !\")\n            sys.exit(1)\n        if response.ok:\n            msg = (\n                FLAG_EMOJI\n                + COLOR_CYAN\n                + \"Successfully uploaded missing answer(s) at URL: \"\n                + response.text.split(\"\\n\")[1]\n                + \" . Please commit these \"\n                \"answers in the repository's answer-store.\" + COLOR_RESET + FLAG_EMOJI\n            )\n            log.info(msg)\n"
  },
  {
    "path": "tests/tests.yaml",
    "content": "answer_tests:\n\n  local_art_004: # PR 3081, 3101\n    - yt/frontends/art/tests/test_outputs.py:test_d9p\n\n#copied from boxlib frontend\n  local_amrex_012:\n    - yt/frontends/amrex/tests/test_outputs.py:test_radadvect\n    - yt/frontends/amrex/tests/test_outputs.py:test_radtube\n    - yt/frontends/amrex/tests/test_outputs.py:test_star\n    - yt/frontends/amrex/tests/test_outputs.py:test_OrionDataset\n    - yt/frontends/amrex/tests/test_outputs.py:test_CastroDataset\n    - yt/frontends/amrex/tests/test_outputs.py:test_RT_particles\n    - yt/frontends/amrex/tests/test_outputs.py:test_units_override\n    - yt/frontends/amrex/tests/test_outputs.py:test_raw_fields\n\n  local_amrex_particles_010:\n    - yt/frontends/amrex/tests/test_outputs.py:test_LyA\n    - yt/frontends/amrex/tests/test_outputs.py:test_nyx_particle_io\n    - yt/frontends/amrex/tests/test_outputs.py:test_castro_particle_io\n    - yt/frontends/amrex/tests/test_outputs.py:test_langmuir\n    - yt/frontends/amrex/tests/test_outputs.py:test_plasma\n    - yt/frontends/amrex/tests/test_outputs.py:test_beam\n    - yt/frontends/amrex/tests/test_outputs.py:test_warpx_particle_io\n    - yt/frontends/amrex/tests/test_outputs.py:test_NyxDataset\n    - yt/frontends/amrex/tests/test_outputs.py:test_WarpXDataset\n\n  local_amrvac_009: # PR 2945\n    - yt/frontends/amrvac/tests/test_outputs.py:test_domain_size\n    - yt/frontends/amrvac/tests/test_outputs.py:test_bw_polar_2d\n    - yt/frontends/amrvac/tests/test_outputs.py:test_blastwave_cartesian_3D\n    - yt/frontends/amrvac/tests/test_outputs.py:test_blastwave_spherical_2D\n    - yt/frontends/amrvac/tests/test_outputs.py:test_blastwave_cylindrical_3D\n    - yt/frontends/amrvac/tests/test_outputs.py:test_khi_cartesian_2D\n    - yt/frontends/amrvac/tests/test_outputs.py:test_khi_cartesian_3D\n    - yt/frontends/amrvac/tests/test_outputs.py:test_jet_cylindrical_25D\n    - yt/frontends/amrvac/tests/test_outputs.py:test_riemann_cartesian_175D\n    - yt/frontends/amrvac/tests/test_outputs.py:test_rmi_cartesian_dust_2D\n\n  local_arepo_013:  # PR 4939\n    - yt/frontends/arepo/tests/test_outputs.py:test_arepo_bullet\n    - yt/frontends/arepo/tests/test_outputs.py:test_arepo_tng59\n    - yt/frontends/arepo/tests/test_outputs.py:test_arepo_cr\n\n  local_artio_005:\n    - yt/frontends/artio/tests/test_outputs.py:test_sizmbhloz\n\n  local_athena_011:  # PR 3971\n    - yt/frontends/athena/tests/test_outputs.py:test_cloud\n    - yt/frontends/athena/tests/test_outputs.py:test_blast\n    - yt/frontends/athena/tests/test_outputs.py:test_stripping\n\n  local_athena_pp_008:\n    - yt/frontends/athena_pp/tests/test_outputs.py:test_disk\n    - yt/frontends/athena_pp/tests/test_outputs.py:test_AM06\n\n  local_chimera_002: #PR 3638\n    - yt/frontends/chimera/tests/test_outputs.py:test_multimesh\n    - yt/frontends/chimera/tests/test_outputs.py:test_2D\n    - yt/frontends/chimera/tests/test_outputs.py:test_3D\n\n  local_cholla_001:\n    - yt/frontends/cholla/tests/test_outputs.py:test_cholla_data\n\n  local_chombo_006:\n    - yt/frontends/chombo/tests/test_outputs.py:test_gc\n    - yt/frontends/chombo/tests/test_outputs.py:test_tb\n    - yt/frontends/chombo/tests/test_outputs.py:test_iso\n    - yt/frontends/chombo/tests/test_outputs.py:test_zp\n    - yt/frontends/chombo/tests/test_outputs.py:test_kho\n\n  local_enzo_011:  # PR 4930\n    - yt/frontends/enzo/tests/test_outputs.py:test_moving7\n    - yt/frontends/enzo/tests/test_outputs.py:test_galaxy0030\n    - yt/frontends/enzo/tests/test_outputs.py:test_toro1d\n    - yt/frontends/enzo/tests/test_outputs.py:test_kh2d\n    - yt/frontends/enzo/tests/test_outputs.py:test_ecp\n    - yt/frontends/enzo/tests/test_outputs.py:test_nuclei_density_fields\n\n  local_enzo_e_004:  # PR 3971\n    - yt/frontends/enzo_e/tests/test_outputs.py:test_hello_world\n    - yt/frontends/enzo_e/tests/test_outputs.py:test_particle_fields\n\n  local_fits_005:\n    - yt/frontends/fits/tests/test_outputs.py:test_grs\n    - yt/frontends/fits/tests/test_outputs.py:test_velocity_field\n    - yt/frontends/fits/tests/test_outputs.py:test_acis\n    - yt/frontends/fits/tests/test_outputs.py:test_A2052\n\n  local_flash_014:\n    - yt/frontends/flash/tests/test_outputs.py:test_sloshing\n    - yt/frontends/flash/tests/test_outputs.py:test_wind_tunnel\n    - yt/frontends/flash/tests/test_outputs.py:test_fid_1to3_b1\n\n  local_gadget_010:  # PR 4939\n    - yt/frontends/gadget/tests/test_outputs.py:test_iso_collapse\n    - yt/frontends/gadget/tests/test_outputs.py:test_pid_uniqueness\n    - yt/frontends/gadget/tests/test_outputs.py:test_bigendian_field_access\n    - yt/frontends/gadget/tests/test_outputs.py:test_magneticum\n\n  local_gamer_012:  # PR 4782\n    - yt/frontends/gamer/tests/test_outputs.py:test_jet\n    - yt/frontends/gamer/tests/test_outputs.py:test_psiDM\n    - yt/frontends/gamer/tests/test_outputs.py:test_plummer\n    - yt/frontends/gamer/tests/test_outputs.py:test_mhdvortex\n    - yt/frontends/gamer/tests/test_outputs.py:test_jiw\n\n  local_gdf_002:\n    - yt/frontends/gdf/tests/test_outputs_nose.py:test_sedov_tunnel\n\n  local_gizmo_009:  # PR 4939\n    - yt/frontends/gizmo/tests/test_outputs.py:test_gizmo_64\n\n  local_halos_012:  # PR 3325\n    - yt/frontends/ahf/tests/test_outputs.py:test_fields_ahf_halos\n    - yt/frontends/owls_subfind/tests/test_outputs.py:test_fields_g1\n    - yt/frontends/owls_subfind/tests/test_outputs.py:test_fields_g8\n    - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g5\n    - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g42\n\n  local_owls_009:  # PR 4939\n    - yt/frontends/owls/tests/test_outputs.py:test_snapshot_033\n    - yt/frontends/owls/tests/test_outputs.py:test_OWLS_particlefilter\n\n  local_pw_049:  # PR 4440\n    - yt/visualization/tests/test_plotwindow.py:test_attributes\n    - yt/visualization/tests/test_particle_plot.py:test_particle_projection_answers\n    - yt/visualization/tests/test_particle_plot.py:test_particle_offaxis_projection_answers\n    - yt/visualization/tests/test_particle_plot.py:test_particle_projection_filter\n    - yt/visualization/tests/test_particle_plot.py:test_particle_phase_answers\n    - yt/visualization/tests/test_callbacks.py:test_axis_manipulations\n\n  local_tipsy_010:  # PR 4939\n    - yt/frontends/tipsy/tests/test_outputs.py:test_pkdgrav\n    - yt/frontends/tipsy/tests/test_outputs.py:test_gasoline_dmonly\n    - yt/frontends/tipsy/tests/test_outputs.py:test_tipsy_galaxy\n\n  local_varia_017:\n    - yt/frontends/moab/tests/test_c5.py:test_cantor_5\n    - yt/fields/tests/test_xray_fields.py:test_sloshing_apec\n    - yt/fields/tests/test_xray_fields.py:test_d9p_cloudy\n    - yt/fields/tests/test_xray_fields.py:test_d9p_cloudy_local\n\n  local_boxlib_012:\n    - yt/frontends/boxlib/tests/test_outputs.py:test_radadvect\n\n  local_boxlib_particles_010:\n    - yt/frontends/boxlib/tests/test_outputs.py:test_WarpXDataset\n\n  local_ramses_005:  # PR 3856\n    - yt/frontends/ramses/tests/test_outputs.py:test_output_00080\n\n  local_ytdata_008:\n    - yt/frontends/ytdata/tests/test_outputs.py:test_datacontainer_data\n    - yt/frontends/ytdata/tests/test_outputs.py:test_grid_datacontainer_data\n    - yt/frontends/ytdata/tests/test_outputs.py:test_spatial_data\n    - yt/frontends/ytdata/tests/test_outputs.py:test_profile_data\n    - yt/frontends/ytdata/tests/test_outputs.py:test_nonspatial_data\n    - yt/frontends/ytdata/tests/test_old_outputs.py:test_old_datacontainer_data\n    - yt/frontends/ytdata/tests/test_old_outputs.py:test_old_grid_datacontainer_data\n    - yt/frontends/ytdata/tests/test_old_outputs.py:test_old_spatial_data\n    - yt/frontends/ytdata/tests/test_old_outputs.py:test_old_profile_data\n    - yt/frontends/ytdata/tests/test_old_outputs.py:test_old_nonspatial_data\n\n  local_axialpix_009: # PR 3818\n    - yt/geometry/coordinates/tests/test_axial_pixelization.py:test_axial_pixelization\n\n  #local_particle_trajectory_001:\n  #  - yt/data_objects/tests/test_particle_trajectories.py\n\n  local_nc4_cm1_002: # PR  2176, 2998\n    - yt/frontends/nc4_cm1/tests/test_outputs.py:test_cm1_mesh_fields\n\n  local_parthenon_001: # PR 4323\n    - yt/frontends/parthenon/tests/test_outputs.py:test_loading_data\n    - yt/frontends/parthenon/tests/test_outputs.py:test_cluster\n    - yt/frontends/parthenon/tests/test_outputs.py:test_derived_fields\n\nother_tests:\n  unittests:\n     # keep in sync with nose_ignores\n     - \"--exclude=test_mesh_slices\"  # disable randomly failing test\n     - \"--ignore=test_outputs_pytest\"\n     - \"--ignore-file=test_add_field\\\\.py\"\n     - \"--ignore-file=test_ambiguous_fields\\\\.py\"\n     - \"--ignore-file=test_base_plot_types\\\\.py\"\n     - \"--ignore-file=test_callable_grids\\\\.py\"\n     - \"--ignore-file=test_callbacks_geographic\\\\.py\"\n     - \"--ignore-file=test_commons\\\\.py\"\n     - \"--ignore-file=test_cython_fortran_utils\\\\.py\"\n     - \"--ignore-file=test_data_reload\\\\.py\"\n     - \"--ignore-file=test_eps_writer\\\\.py\"\n     - \"--ignore-file=test_ewah_write_load\\\\.py\"\n     - \"--ignore-file=test_external_frontends\\\\.py\"\n     - \"--ignore-file=test_field_access_pytest\\\\.py\"\n     - \"--ignore-file=test_field_parsing\\\\.py\"\n     - \"--ignore-file=test_file_sanitizer\\\\.py\"\n     - \"--ignore-file=test_firefly\\\\.py\"\n     - \"--ignore-file=test_geometries\\\\.py\"\n     - \"--ignore-file=test_geographic_coordinates\\\\.py\"\n     - \"--ignore-file=test_glue\\\\.py\"\n     - \"--ignore-file=test_image_comp_2D_plots\\\\.py\"\n     - \"--ignore-file=test_image_comp_geo\\\\.py\"\n     - \"--ignore-file=test_invalid_origin\\\\.py\"\n     - \"--ignore-file=test_line_annotation_unit\\\\.py\"\n     - \"--ignore-file=test_line_plots\\\\.py\"\n     - \"--ignore-file=test_load_archive\\\\.py\"\n     - \"--ignore-file=test_load_errors\\\\.py\"\n     - \"--ignore-file=test_load_sample\\\\.py\"\n     - \"--ignore-file=test_mesh_render\\\\.py\"\n     - \"--ignore-file=test_mesh_slices\\\\.py\"\n     - \"--ignore-file=test_normal_plot_api\\\\.py\"\n     - \"--ignore-file=test_on_demand_imports\\\\.py\"\n     - \"--ignore-file=test_outputs_pytest\\\\.py\"\n     - \"--ignore-file=test_profile_plots\\\\.py\"\n     - \"--ignore-file=test_raw_field_slices\\\\.py\"\n     - \"--ignore-file=test_registration\\\\.py\"\n     - \"--ignore-file=test_sanitize_center\\\\.py\"\n     - \"--ignore-file=test_save\\\\.py\"\n     - \"--ignore-file=test_set_zlim\\\\.py\"\n     - \"--ignore-file=test_stream_particles\\\\.py\"\n     - \"--ignore-file=test_stream_stretched\\\\.py\"\n     - \"--ignore-file=test_version\\\\.py\"\n     - \"--ignore-file=test_gadget_pytest\\\\.py\"\n     - \"--ignore-file=test_vr_orientation\\\\.py\"\n     - \"--ignore-file=test_particle_trajectories_pytest\\\\.py\"\n     - \"--ignore-file=test_time_series\\\\.py\"\n     - \"--exclude-test=yt.frontends.gdf.tests.test_outputs.TestGDF\"\n     - \"--exclude-test=yt.frontends.adaptahop.tests.test_outputs\"\n     - \"--exclude-test=yt.frontends.stream.tests.test_stream_particles.test_stream_non_cartesian_particles\"\n     - \"--ignore-file=test_offaxisprojection_pytestonly\\\\.py\"\n     - \"--ignore-file=test_sph_pixelization_pytestonly\\\\.py\"\n     - \"--ignore-file=test_cf_radial_pytest\\\\.py\"\n     - \"--ignore-file=test_fields_pytest\\\\.py\"\n  cookbook:\n     - 'doc/source/cookbook/tests/test_cookbook.py'\n"
  },
  {
    "path": "tests/unpin_requirements.py",
    "content": "# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#     \"tomli ; python_full_version < '3.11'\",\n#     \"tomli-w\",\n# ]\n# ///\nimport re\nimport sys\n\nimport tomli_w\n\nif sys.version_info >= (3, 11):\n    import tomllib\nelse:\n    import tomli as tomllib\n\nPINNED_VERSION_REGEXP = re.compile(r\",?(<|<=|==)([0-9a-z]+\\.?)+\")\n\n\ndef unpin_requirements(requirements: list[str]) -> list[str]:\n    return [re.sub(PINNED_VERSION_REGEXP, \"\", _) for _ in requirements]\n\n\nif __name__ == \"__main__\":\n    with open(\"pyproject.toml\", \"rb\") as fr:\n        config = tomllib.load(fr)\n\n    config[\"project\"][\"dependencies\"] = unpin_requirements(\n        config[\"project\"][\"dependencies\"]\n    )\n    for key, reqs in config[\"project\"][\"optional-dependencies\"].items():\n        config[\"project\"][\"optional-dependencies\"][key] = unpin_requirements(reqs)\n\n    with open(\"pyproject_out.toml\", \"wb\") as fw:\n        tomli_w.dump(config, fw)\n"
  },
  {
    "path": "yt/__init__.py",
    "content": "\"\"\"\nyt is a toolkit for analyzing and visualizing volumetric data.\n\n* Website: https://yt-project.org\n* Documentation: https://yt-project.org/doc\n* Data hub: https://girder.hub.yt\n* Contribute: https://github.com/yt-project/yt\n\n\"\"\"\n\nfrom ._version import __version__, version_info  # isort: skip\nimport yt.units as units\nimport yt.utilities.physical_constants as physical_constants\nfrom yt.data_objects.api import (\n    DatasetSeries,\n    ImageArray,\n    ParticleProfile,\n    Profile1D,\n    Profile2D,\n    Profile3D,\n    add_particle_filter,\n    create_profile,\n    particle_filter,\n)\nfrom yt.fields.api import (\n    DerivedField,\n    FieldDetector,\n    FieldInfoContainer,\n    ValidateDataField,\n    ValidateGridType,\n    ValidateParameter,\n    ValidateProperty,\n    ValidateSpatial,\n    add_field,\n    add_xray_emissivity_field,\n    derived_field,\n    field_plugins,\n)\nfrom yt.funcs import (\n    enable_plugins,\n    get_memory_usage,\n    get_pbar,\n    get_version_stack,\n    get_yt_version,\n    insert_ipython,\n    is_root,\n    is_sequence,\n    memory_checker,\n    only_on_root,\n    parallel_profile,\n    print_tb,\n    rootonly,\n    toggle_interactivity,\n)\nfrom yt.units import (\n    YTArray,\n    YTQuantity,\n    display_ytarray,\n    loadtxt,\n    savetxt,\n    uconcatenate,\n    ucross,\n    udot,\n    uhstack,\n    uintersect1d,\n    unorm,\n    ustack,\n    uunion1d,\n    uvstack,\n)\nfrom yt.units.unit_object import define_unit  # type: ignore\nfrom yt.utilities.logger import set_log_level, ytLogger as mylog\n\nfrom yt import frontends\n\nimport yt.visualization.volume_rendering.api as volume_rendering\nfrom yt.frontends.stream.api import hexahedral_connectivity\nfrom yt.frontends.ytdata.api import save_as_dataset\nfrom yt.loaders import (\n    load,\n    load_amr_grids,\n    load_archive,\n    load_hdf5_file,\n    load_hexahedral_mesh,\n    load_octree,\n    load_particles,\n    load_sample,\n    load_simulation,\n    load_uniform_grid,\n    load_unstructured_mesh,\n)\n\n\ndef run_nose(*args, **kwargs):\n    # we hide this function behind a closure so we\n    # don't make pytest a hard dependency for end users\n    # see https://github.com/yt-project/yt/issues/3771\n    from yt.testing import run_nose\n\n    return run_nose(*args, **kwargs)\n\n\nfrom yt.config import _setup_postinit_configuration\nfrom yt.units.unit_systems import UnitSystem, unit_system_registry  # type: ignore\n\n# Import some helpful math utilities\nfrom yt.utilities.math_utils import ortho_find, periodic_position, quartiles\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    communication_system,\n    enable_parallelism,\n    parallel_objects,\n)\n\n# Now individual component imports from the visualization API\nfrom yt.visualization.api import (\n    AxisAlignedProjectionPlot,\n    AxisAlignedSlicePlot,\n    FITSImageData,\n    FITSOffAxisProjection,\n    FITSOffAxisSlice,\n    FITSParticleOffAxisProjection,\n    FITSParticleProjection,\n    FITSProjection,\n    FITSSlice,\n    FixedResolutionBuffer,\n    LineBuffer,\n    LinePlot,\n    OffAxisProjectionPlot,\n    OffAxisSlicePlot,\n    ParticleImageBuffer,\n    ParticlePhasePlot,\n    ParticlePlot,\n    ParticleProjectionPlot,\n    PhasePlot,\n    ProfilePlot,\n    ProjectionPlot,\n    SlicePlot,\n    add_colormap,\n    apply_colormap,\n    make_colormap,\n    plot_2d,\n    scale_image,\n    show_colormaps,\n    write_bitmap,\n    write_image,\n    write_projection,\n)\nfrom yt.visualization.volume_rendering.api import (\n    ColorTransferFunction,\n    TransferFunction,\n    create_scene,\n    off_axis_projection,\n    volume_render,\n)\n\n#    TransferFunctionHelper, MultiVariateTransferFunction\n#    off_axis_projection\n\n\n# run configuration callbacks\n_setup_postinit_configuration()\ndel _setup_postinit_configuration\n"
  },
  {
    "path": "yt/_maintenance/__init__.py",
    "content": ""
  },
  {
    "path": "yt/_maintenance/backports.py",
    "content": "# A namespace to store simple backports from most recent Python versions\n# Backports should be contained in (if/else) blocks, checking on the runtime\n# Python version, e.g.\n#\n# if sys.version_info < (3, 8):\n#     ... # insert backported definitions\n# else:\n#     pass\n#\n# while the else part if meant as a no-op, it is required to allow automated\n# cleanups with pyupgrade, when our minimal supported Python version reaches\n# the requirement for backports to be unneeded.\n#\n# Likewise, imports from this module should be guarded by a similar condition,\n# e.g.,\n#\n# if sys.version_info >= (3, 8):\n#     from functools import cached_property\n# else:\n#     from yt._maintenance.backports import cached_property\nimport sys\n\nif sys.version_info >= (3, 11):\n    pass\nelse:\n    from enum import Enum\n\n    # backported from Python 3.11.0\n    class ReprEnum(Enum):\n        \"\"\"\n        Only changes the repr(), leaving str() and format() to the mixed-in type.\n        \"\"\"\n\n    # backported from Python 3.11.0\n    class StrEnum(str, ReprEnum):\n        \"\"\"\n        Enum where members are also (and must be) strings\n        \"\"\"\n\n        def __new__(cls, *values):\n            \"values must already be of type `str`\"\n            if len(values) > 3:\n                raise TypeError(f\"too many arguments for str(): {values!r}\")\n            if len(values) == 1:\n                # it must be a string\n                if not isinstance(values[0], str):\n                    raise TypeError(f\"{values[0]!r} is not a string\")\n            if len(values) >= 2:\n                # check that encoding argument is a string\n                if not isinstance(values[1], str):\n                    raise TypeError(f\"encoding must be a string, not {values[1]!r}\")\n            if len(values) == 3:\n                # check that errors argument is a string\n                if not isinstance(values[2], str):\n                    raise TypeError(\"errors must be a string, not %r\" % (values[2]))  # noqa: UP031\n            value = str(*values)\n            member = str.__new__(cls, value)\n            member._value_ = value\n            return member\n\n        def _generate_next_value_(name, start, count, last_values):  # noqa B902\n            \"\"\"\n            Return the lower-cased version of the member name.\n            \"\"\"\n            return name.lower()\n"
  },
  {
    "path": "yt/_maintenance/deprecation.py",
    "content": "import warnings\nfrom functools import wraps\nfrom types import FunctionType\n\n\ndef issue_deprecation_warning(\n    msg: str,\n    *,\n    stacklevel: int,\n    since: str,\n    removal: str | None = None,\n):\n    \"\"\"\n    Parameters\n    ----------\n    msg : str\n        A text message explaining that the code surrounding the call to this function is\n        deprecated, and what should be changed on the user side to avoid it.\n\n    stacklevel: int\n        Number of stack frames to be skipped when pointing at caller code, starting from\n        *this* function's frame. In general 3 is a minimum.\n\n    since and removal: str version numbers, indicating the anticipated removal date\n\n    Notes\n    -----\n\n    removal can be left empty if it is not clear how many minor releases are expected to\n    happen before the next major.\n\n    removal and since arguments are keyword-only to forbid accidentally swapping them.\n\n    Examples\n    --------\n    >>> issue_deprecation_warning(\n    ...     \"This code is deprecated.\", stacklevel=3, since=\"4.0\"\n    ... )\n    \"\"\"\n\n    msg += f\"\\nDeprecated since yt {since}\"\n    if removal is not None:\n        msg += f\"\\nThis feature is planned for removal in yt {removal}\"\n    warnings.warn(msg, DeprecationWarning, stacklevel=stacklevel)\n\n\ndef future_positional_only(positions2names: dict[int, str], /, **depr_kwargs):\n    \"\"\"Warn users when using a future positional-only argument as keyword.\n    Note that positional-only arguments are available from Python 3.8\n    See https://www.python.org/dev/peps/pep-0570/\n    \"\"\"\n\n    def outer(func: FunctionType):\n        @wraps(func)\n        def inner(*args, **kwargs):\n            for no, name in sorted(positions2names.items()):\n                if name not in kwargs:\n                    continue\n                value = kwargs[name]\n                issue_deprecation_warning(\n                    f\"Using the {name!r} argument as keyword (on position {no}) \"\n                    \"is deprecated. \"\n                    \"Pass the argument as positional to suppress this warning, \"\n                    f\"i.e., use {func.__name__}({value!r}, ...)\",\n                    stacklevel=3,\n                    **depr_kwargs,\n                )\n            return func(*args, **kwargs)\n\n        return inner\n\n    return outer\n"
  },
  {
    "path": "yt/_maintenance/ipython_compat.py",
    "content": "from importlib.metadata import version\nfrom importlib.util import find_spec\n\nfrom packaging.version import Version\n\n__all__ = [\n    \"IS_IPYTHON\",\n    \"IPYWIDGETS_ENABLED\",\n]\n\nIS_IPYTHON: bool\nHAS_IPYWIDGETS_GE_8: bool\nIPYWIDGETS_ENABLED: bool\n\n\ntry:\n    # this name is only defined if running within ipython/jupyter\n    __IPYTHON__  # type: ignore [name-defined] # noqa: B018\nexcept NameError:\n    IS_IPYTHON = False\nelse:\n    IS_IPYTHON = True\n\n\nHAS_IPYWIDGETS_GE_8 = (\n    Version(version(\"ipywidgets\")) >= Version(\"8.0.0\")\n    if find_spec(\"ipywidgets\") is not None\n    else False\n)\n\nIPYWIDGETS_ENABLED = IS_IPYTHON and HAS_IPYWIDGETS_GE_8\n"
  },
  {
    "path": "yt/_maintenance/numpy2_compat.py",
    "content": "# avoid deprecation warnings in numpy >= 2.0\n\nimport numpy as np\n\nif hasattr(np, \"trapezoid\"):\n    # np.trapz is deprecated in numpy 2.0 in favor of np.trapezoid\n    trapezoid = np.trapezoid\nelse:\n    trapezoid = np.trapz  # type: ignore # noqa: NPY201\n"
  },
  {
    "path": "yt/_typing.py",
    "content": "from typing import Any, Optional, TypeAlias\n\nimport numpy as np\nimport unyt as un\n\nFieldDescT = tuple[str, tuple[str, list[str], str | None]]\nKnownFieldsT = tuple[FieldDescT, ...]\n\nParticleType = str\nFieldType = str\nFieldName = str\nFieldKey = tuple[FieldType, FieldName]\nImplicitFieldKey = FieldName\nAnyFieldKey = FieldKey | ImplicitFieldKey\nDomainDimensions = tuple[int, ...] | list[int] | np.ndarray\n\nParticleCoordinateTuple = tuple[\n    str,  # particle type\n    tuple[np.ndarray, np.ndarray, np.ndarray],  # xyz\n    float | np.ndarray,  # hsml\n]\n\n# Geometry specific types\nAxisName = str\nAxisOrder = tuple[AxisName, AxisName, AxisName]\n\n# types that can be converted to un.Unit\nUnit: TypeAlias = un.Unit | str\n\n# types that can be converted to un.unyt_quantity\nQuantity = un.unyt_quantity | tuple[float, Unit]\n\n# np.ndarray[...] syntax is runtime-valid from numpy 1.22, we quote it until our minimal\n# runtime requirement is bumped to, or beyond this version\n\nMaskT = Optional[\"np.ndarray[Any, np.dtype[np.bool_]]\"]\nAlphaT = Optional[\"np.ndarray[Any, np.dtype[np.float64]]\"]\n"
  },
  {
    "path": "yt/_version.py",
    "content": "from typing import NamedTuple\n\nfrom packaging.version import Version\n\n__all__ = [\n    \"__version__\",\n    \"version_info\",\n]\n\n__version__ = \"4.5.dev0\"  # keep in sync with pyproject.toml\n\n\nclass VersionTuple(NamedTuple):\n    \"\"\"\n    A minimal representation of the current version number\n    that can be used downstream to check the runtime version\n    simply by comparing with builtin tuples, as can be done with\n    the runtime Python version using sys.version_info\n\n    https://docs.python.org/3/library/sys.html#sys.version_info\n    \"\"\"\n\n    major: int\n    minor: int\n    micro: int\n    releaselevel: str\n    serial: int\n\n\ndef _parse_to_version_info(version_str: str) -> VersionTuple:\n    # adapted from matplotlib 3.5\n    \"\"\"\n    Parse a version string to a namedtuple analogous to sys.version_info.\n    See:\n    https://packaging.pypa.io/en/latest/version.html#packaging.version.parse\n    https://docs.python.org/3/library/sys.html#sys.version_info\n    \"\"\"\n    v = Version(version_str)\n    if v.pre is None and v.post is None and v.dev is None:\n        return VersionTuple(v.major, v.minor, v.micro, \"final\", 0)\n    elif v.dev is not None:\n        return VersionTuple(v.major, v.minor, v.micro, \"alpha\", v.dev)\n    elif v.pre is not None:\n        releaselevel = {\"a\": \"alpha\", \"b\": \"beta\", \"rc\": \"candidate\"}.get(\n            v.pre[0], \"alpha\"\n        )\n        return VersionTuple(v.major, v.minor, v.micro, releaselevel, v.pre[1])\n    elif v.post is not None:\n        # fallback for v.post: guess-next-dev scheme from setuptools_scm\n        return VersionTuple(v.major, v.minor, v.micro + 1, \"alpha\", v.post)\n    else:\n        return VersionTuple(v.major, v.minor, v.micro + 1, \"alpha\", 0)\n\n\nversion_info = _parse_to_version_info(__version__)\n"
  },
  {
    "path": "yt/analysis_modules/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/absorption_spectrum/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/absorption_spectrum/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"AbsorptionSpectrum\",\n    \"https://github.com/trident-project/trident\",\n    \"https://trident.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/cosmological_observation/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/cosmological_observation/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"CosmologySplice and LightCone\",\n    \"https://github.com/yt-project/yt_astro_analysis\",\n    \"https://yt-astro-analysis.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/halo_analysis/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/halo_analysis/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"halo_analysis\",\n    \"https://github.com/yt-project/yt_astro_analysis\",\n    \"https://yt-astro-analysis.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/halo_finding/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/halo_finding/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"halo_finding\",\n    \"https://github.com/yt-project/yt_astro_analysis\",\n    \"https://yt-astro-analysis.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/halo_mass_function/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/halo_mass_function/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"halo_mass_function\",\n    \"https://github.com/yt-project/yt_attic\",\n    \"https://yt-attic.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/level_sets/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/level_sets/api.py",
    "content": "raise RuntimeError(\n    \"The level_sets module has been moved to yt.data_objects.level_sets.\"\n    \"Please, change the import in your scripts from \"\n    \"'from yt.analysis_modules.level_sets' to \"\n    \"'from yt.data_objects.level_sets.'.\"\n)\n"
  },
  {
    "path": "yt/analysis_modules/particle_trajectories/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/particle_trajectories/api.py",
    "content": "raise RuntimeError(\n    \"Particle trajectories are now available from DatasetSeries \"\n    \"objects as ts.particle_trajectories. The ParticleTrajectories \"\n    \"analysis module has been removed.\"\n)\n"
  },
  {
    "path": "yt/analysis_modules/photon_simulator/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/photon_simulator/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"photon_simulator\", \"pyXSIM\", \"http://hea-www.cfa.harvard.edu/~jzuhone/pyxsim\"\n)\n"
  },
  {
    "path": "yt/analysis_modules/ppv_cube/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/ppv_cube/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"PPVCube\",\n    \"https://github.com/yt-project/yt_astro_analysis\",\n    \"https://yt-astro-analysis.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/radmc3d_export/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/radmc3d_export/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"radmc3d_export\",\n    \"https://github.com/yt-project/yt_astro_analysis\",\n    \"https://yt-astro-analysis.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/spectral_integrator/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/spectral_integrator/api.py",
    "content": "raise RuntimeError(\n    \"The spectral_integrator module as been moved to yt.fields. \"\n    \"'add_xray_emissivity_field' can now be imported \"\n    \"from the yt module.\"\n)\n"
  },
  {
    "path": "yt/analysis_modules/star_analysis/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/star_analysis/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"star_analysis\",\n    \"https://github.com/yt-project/yt_attic\",\n    \"https://yt-attic.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/sunrise_export/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/sunrise_export/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"sunrise_export\",\n    \"https://github.com/yt-project/yt_attic\",\n    \"https://yt-attic.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/sunyaev_zeldovich/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/sunyaev_zeldovich/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"sunyaev_zeldovich\",\n    \"https://github.com/yt-project/yt_astro_analysis\",\n    \"https://yt-astro-analysis.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/analysis_modules/two_point_functions/__init__.py",
    "content": ""
  },
  {
    "path": "yt/analysis_modules/two_point_functions/api.py",
    "content": "from yt.utilities.exceptions import YTModuleRemoved\n\nraise YTModuleRemoved(\n    \"two_point_functions\",\n    \"https://github.com/yt-project/yt_attic\",\n    \"https://yt-attic.readthedocs.io/\",\n)\n"
  },
  {
    "path": "yt/api.py",
    "content": "\"\"\"\nAPI for yt\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/arraytypes.py",
    "content": "import numpy as np\n\n# Now define convenience functions\n\n\ndef blankRecordArray(desc, elements):\n    \"\"\"\n    Accept a descriptor describing a recordarray, and return one that's full of\n    zeros\n\n    This seems like it should be in the numpy distribution...\n    \"\"\"\n    blanks = []\n    for atype in desc[\"formats\"]:\n        blanks.append(np.zeros(elements, dtype=atype))\n    return np.rec.fromarrays(blanks, **desc)\n"
  },
  {
    "path": "yt/config.py",
    "content": "import os\n\nfrom yt.utilities.configure import YTConfig, config_dir, configuration_callbacks\n\nytcfg_defaults = {}\n\nytcfg_defaults[\"yt\"] = {\n    \"serialize\": False,\n    \"only_deserialize\": False,\n    \"time_functions\": False,\n    \"colored_logs\": False,\n    \"suppress_stream_logging\": False,\n    \"stdout_stream_logging\": False,\n    \"log_level\": 20,\n    \"inline\": False,\n    \"num_threads\": -1,\n    \"store_parameter_files\": False,\n    \"parameter_file_store\": \"parameter_files.csv\",\n    \"maximum_stored_datasets\": 500,\n    \"skip_dataset_cache\": True,\n    \"load_field_plugins\": False,\n    \"plugin_filename\": \"my_plugins.py\",\n    \"parallel_traceback\": False,\n    \"pasteboard_repo\": \"\",\n    \"reconstruct_index\": True,\n    \"test_storage_dir\": \"/does/not/exist\",\n    \"test_data_dir\": \"/does/not/exist\",\n    \"enzo_db\": \"\",\n    \"answer_testing_tolerance\": 3,\n    \"answer_testing_bitwise\": False,\n    \"gold_standard_filename\": \"gold311\",\n    \"local_standard_filename\": \"local001\",\n    \"answer_tests_url\": \"http://answers.yt-project.org/{1}_{2}\",\n    \"sketchfab_api_key\": \"None\",\n    \"imagebin_api_key\": \"e1977d9195fe39e\",\n    \"imagebin_upload_url\": \"https://api.imgur.com/3/image\",\n    \"imagebin_delete_url\": \"https://api.imgur.com/3/image/{delete_hash}\",\n    \"curldrop_upload_url\": \"http://use.yt/upload\",\n    \"thread_field_detection\": False,\n    \"ignore_invalid_unit_operation_errors\": False,\n    \"chunk_size\": 1000,\n    \"xray_data_dir\": \"/does/not/exist\",\n    \"supp_data_dir\": \"/does/not/exist\",\n    \"default_colormap\": \"cmyt.arbre\",\n    \"ray_tracing_engine\": \"yt\",\n    \"internals\": {\n        \"within_testing\": False,\n        \"parallel\": False,\n        \"strict_requires\": False,\n        \"global_parallel_rank\": 0,\n        \"global_parallel_size\": 1,\n        \"topcomm_parallel_rank\": 0,\n        \"topcomm_parallel_size\": 1,\n        \"command_line\": False,\n    },\n}\n\n\n# For backward compatibility, do not use these vars internally in yt\nCONFIG_DIR = config_dir()\n\n\n_global_config_file = YTConfig.get_global_config_file()\n_local_config_file = YTConfig.get_local_config_file()\n\n# Load the config\nytcfg = YTConfig()\nytcfg.update(ytcfg_defaults, metadata={\"source\": \"defaults\"})\n\n# Try loading the local config first, otherwise fall back to global config\nif os.path.exists(_local_config_file):\n    ytcfg.read(_local_config_file)\nelif os.path.exists(_global_config_file):\n    ytcfg.read(_global_config_file)\n\n\ndef _setup_postinit_configuration():\n    \"\"\"This is meant to be run last in yt.__init__\"\"\"\n    for callback in configuration_callbacks:\n        callback(ytcfg)\n"
  },
  {
    "path": "yt/data_objects/__init__.py",
    "content": ""
  },
  {
    "path": "yt/data_objects/analyzer_objects.py",
    "content": "import inspect\n\nfrom yt.utilities.object_registries import analysis_task_registry\n\n\nclass AnalysisTask:\n    def __init_subclass__(cls, *args, **kwargs):\n        if hasattr(cls, \"skip\") and not cls.skip:\n            return\n        analysis_task_registry[cls.__name__] = cls\n\n    def __init__(self, *args, **kwargs):\n        # This should only get called if the subclassed object\n        # does not override\n        if len(args) + len(kwargs) != len(self._params):\n            raise RuntimeError\n        self.__dict__.update(zip(self._params, args, strict=False))\n        self.__dict__.update(kwargs)\n\n    def __repr__(self):\n        # Stolen from YTDataContainer.__repr__\n        s = f\"{self.__class__.__name__}: \"\n        s += \", \".join(f\"{i}={getattr(self, i)}\" for i in self._params)\n        return s\n\n\ndef analysis_task(params=None):\n    if params is None:\n        params = ()\n\n    def create_new_class(func):\n        cls = type(func.__name__, (AnalysisTask,), {\"eval\": func, \"_params\": params})\n        return cls\n\n    return create_new_class\n\n\n@analysis_task((\"field\",))\ndef MaximumValue(params, data_object):\n    v = data_object.quantities[\"MaxLocation\"](params.field)[0]\n    return v\n\n\n@analysis_task()\ndef CurrentTimeYears(params, ds):\n    return ds.current_time * ds[\"years\"]\n\n\nclass SlicePlotDataset(AnalysisTask):\n    _params = [\"field\", \"axis\", \"center\"]\n\n    def __init__(self, *args, **kwargs):\n        from yt.visualization.api import SlicePlot\n\n        self.SlicePlot = SlicePlot\n        AnalysisTask.__init__(self, *args, **kwargs)\n\n    def eval(self, ds):\n        slc = self.SlicePlot(ds, self.axis, self.field, center=self.center)\n        return slc.save()\n\n\nclass QuantityProxy(AnalysisTask):\n    _params = None\n    quantity_name = None\n\n    def __repr__(self):\n        # Stolen from YTDataContainer.__repr__\n        s = f\"{self.__class__.__name__}: \"\n        s += \", \".join([str(list(self.args))])\n        s += \", \".join(f\"{k}={v}\" for k, v in self.kwargs.items())\n        return s\n\n    def __init__(self, *args, **kwargs):\n        self.args = args\n        self.kwargs = kwargs\n\n    def eval(self, data_object):\n        rv = data_object.quantities[self.quantity_name](*self.args, **self.kwargs)\n        return rv\n\n\nclass ParameterValue(AnalysisTask):\n    _params = [\"parameter\"]\n\n    def __init__(self, parameter, cast=None):\n        self.parameter = parameter\n        if cast is None:\n\n            def _identity(x):\n                return x\n\n            cast = _identity\n        self.cast = cast\n\n    def eval(self, ds):\n        return self.cast(ds.get_parameter(self.parameter))\n\n\ndef create_quantity_proxy(quantity_object):\n    args, varargs, kwargs, defaults = inspect.getargspec(quantity_object[1])\n    # Strip off 'data' which is on every quantity function\n    params = args[1:]\n    if kwargs is not None:\n        params += kwargs\n    dd = {\"_params\": params, \"quantity_name\": quantity_object[0]}\n    cls = type(quantity_object[0], (QuantityProxy,), dd)\n    return cls\n"
  },
  {
    "path": "yt/data_objects/api.py",
    "content": "from . import construction_data_containers as __cdc, selection_objects as __sdc\nfrom .analyzer_objects import AnalysisTask, analysis_task\nfrom .image_array import ImageArray\nfrom .index_subobjects import AMRGridPatch, OctreeSubset\nfrom .particle_filters import add_particle_filter, particle_filter\nfrom .profiles import ParticleProfile, Profile1D, Profile2D, Profile3D, create_profile\nfrom .static_output import Dataset\nfrom .time_series import DatasetSeries, DatasetSeriesObject\n"
  },
  {
    "path": "yt/data_objects/construction_data_containers.py",
    "content": "import fileinput\nimport io\nimport os\nimport warnings\nimport zipfile\nfrom functools import partial, wraps\nfrom re import finditer\nfrom tempfile import NamedTemporaryFile, TemporaryFile\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._typing import FieldKey\nfrom yt.config import ytcfg\nfrom yt.data_objects.field_data import YTFieldData\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer1D,\n    YTSelectionContainer2D,\n    YTSelectionContainer3D,\n)\nfrom yt.fields.field_exceptions import NeedsGridType, NeedsOriginalGrid\nfrom yt.frontends.sph.data_structures import ParticleDataset\nfrom yt.funcs import (\n    get_memory_usage,\n    is_sequence,\n    iter_fields,\n    mylog,\n    only_on_root,\n    validate_moment,\n)\nfrom yt.geometry import particle_deposit as particle_deposit\nfrom yt.geometry.coordinates.cartesian_coordinates import all_data\nfrom yt.loaders import load_uniform_grid\nfrom yt.units._numpy_wrapper_functions import uconcatenate\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.exceptions import (\n    YTNoAPIKey,\n    YTNotInsideNotebook,\n    YTParticleDepositionNotImplemented,\n    YTTooManyVertices,\n)\nfrom yt.utilities.grid_data_format.writer import write_to_gdf\nfrom yt.utilities.lib.cyoctree import CyOctree\nfrom yt.utilities.lib.interpolators import ghost_zone_interpolate\nfrom yt.utilities.lib.marching_cubes import march_cubes_grid, march_cubes_grid_flux\nfrom yt.utilities.lib.misc_utilities import fill_region, fill_region_float\nfrom yt.utilities.lib.pixelization_routines import (\n    interpolate_sph_grid_gather,\n    interpolate_sph_positions_gather,\n    normalization_1d_utility,\n    normalization_3d_utility,\n    pixelize_sph_kernel_arbitrary_grid,\n)\nfrom yt.utilities.lib.quad_tree import QuadTree\nfrom yt.utilities.math_utils import compute_stddev_image\nfrom yt.utilities.minimal_representation import MinimalProjectionData\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    communication_system,\n    parallel_objects,\n    parallel_root_only,\n)\nfrom yt.visualization.color_maps import get_colormap_lut\n\n\nclass YTStreamline(YTSelectionContainer1D):\n    \"\"\"\n    This is a streamline, which is a set of points defined as\n    being parallel to some vector field.\n\n    This object is typically accessed through the Streamlines.path\n    function.  The resulting arrays have their dimensionality\n    reduced to one, and an ordered list of points at an (x,y)\n    tuple along `axis` are available, as is the `t` field, which\n    corresponds to a unitless measurement along the ray from start\n    to end.\n\n    Parameters\n    ----------\n    positions : array-like\n        List of streamline positions\n    length : float\n        The magnitude of the distance; dts will be divided by this\n    fields : list of strings, optional\n        If you want the object to pre-retrieve a set of fields, supply them\n        here.  This is not necessary.\n    ds : dataset object\n        Passed in to access the index\n    kwargs : dict of items\n        Any additional values are passed as field parameters that can be\n        accessed by generated fields.\n\n    Examples\n    --------\n\n    >>> from yt.visualization.api import Streamlines\n    >>> streamlines = Streamlines(ds, [0.5] * 3)\n    >>> streamlines.integrate_through_volume()\n    >>> stream = streamlines.path(0)\n    >>> fig, ax = plt.subplots()\n    >>> ax.set_yscale(\"log\")\n    >>> ax.plot(stream[\"t\"], stream[\"gas\", \"density\"], \"-x\")\n    \"\"\"\n\n    _type_name = \"streamline\"\n    _con_args = (\"positions\",)\n    sort_by = \"t\"\n\n    def __init__(self, positions, length=1.0, fields=None, ds=None, **kwargs):\n        YTSelectionContainer1D.__init__(self, ds, fields, **kwargs)\n        self.positions = positions\n        self.dts = np.empty_like(positions[:, 0])\n        self.dts[:-1] = np.sqrt(\n            np.sum((self.positions[1:] - self.positions[:-1]) ** 2, axis=1)\n        )\n        self.dts[-1] = self.dts[-1]\n        self.length = length\n        self.dts /= length\n        self.ts = np.add.accumulate(self.dts)\n        self._set_center(self.positions[0])\n        self.set_field_parameter(\"center\", self.positions[0])\n        self._dts, self._ts = {}, {}\n        # self._refresh_data()\n\n    def _get_list_of_grids(self):\n        # Get the value of the line at each LeftEdge and RightEdge\n        LE = self.ds.grid_left_edge\n        RE = self.ds.grid_right_edge\n        # Check left faces first\n        min_streampoint = np.min(self.positions, axis=0)\n        max_streampoint = np.max(self.positions, axis=0)\n        p = np.all((min_streampoint <= RE) & (max_streampoint > LE), axis=1)\n        self._grids = self.index.grids[p]\n\n    def _get_data_from_grid(self, grid, field):\n        # No child masking here; it happens inside the mask cut\n        mask = self._get_cut_mask(grid)\n        if field == \"dts\":\n            return self._dts[grid.id]\n        if field == \"t\":\n            return self._ts[grid.id]\n        return grid[field].flat[mask]\n\n    def _get_cut_mask(self, grid):\n        points_in_grid = np.all(self.positions > grid.LeftEdge, axis=1) & np.all(\n            self.positions <= grid.RightEdge, axis=1\n        )\n        pids = np.where(points_in_grid)[0]\n        mask = np.zeros(points_in_grid.sum(), dtype=\"int64\")\n        dts = np.zeros(points_in_grid.sum(), dtype=\"float64\")\n        ts = np.zeros(points_in_grid.sum(), dtype=\"float64\")\n        for mi, (i, pos) in enumerate(\n            zip(pids, self.positions[points_in_grid], strict=True)\n        ):\n            if not points_in_grid[i]:\n                continue\n            ci = ((pos - grid.LeftEdge) / grid.dds).astype(\"int64\")\n            if grid.child_mask[ci[0], ci[1], ci[2]] == 0:\n                continue\n            for j in range(3):\n                ci[j] = min(ci[j], grid.ActiveDimensions[j] - 1)\n            mask[mi] = np.ravel_multi_index(ci, grid.ActiveDimensions)\n            dts[mi] = self.dts[i]\n            ts[mi] = self.ts[i]\n        self._dts[grid.id] = dts\n        self._ts[grid.id] = ts\n        return mask\n\n\nclass YTProj(YTSelectionContainer2D):\n    _key_fields = YTSelectionContainer2D._key_fields + [\"weight_field\"]\n    _con_args = (\"axis\", \"field\", \"weight_field\")\n    _container_fields = (\"px\", \"py\", \"pdx\", \"pdy\", \"weight_field\")\n\n    def __init__(\n        self,\n        field,\n        axis,\n        weight_field=None,\n        center=None,\n        ds=None,\n        data_source=None,\n        method=\"integrate\",\n        field_parameters=None,\n        max_level=None,\n        *,\n        moment=1,\n    ):\n        super().__init__(axis, ds, field_parameters)\n\n        if method == \"mip\":\n            issue_deprecation_warning(\n                \"The 'mip' method value is a deprecated alias for 'max'. \"\n                \"Please use max directly.\",\n                since=\"4.1\",\n                stacklevel=4,\n            )\n            method = \"max\"\n        if method == \"sum\":\n            self.method = \"integrate\"\n            self._sum_only = True\n        else:\n            self.method = method\n            self._sum_only = False\n        if self.method in [\"max\", \"mip\"]:\n            self.func = np.max\n        elif self.method == \"min\":\n            self.func = np.min\n        elif self.method == \"integrate\":\n            self.func = np.sum  # for the future\n        else:\n            raise NotImplementedError(self.method)\n        validate_moment(moment, weight_field)\n        self.moment = moment\n        self._set_center(center)\n        self._projected_units = {}\n        if data_source is None:\n            data_source = self.ds.all_data()\n        if max_level is not None:\n            data_source.max_level = max_level\n        for k, v in data_source.field_parameters.items():\n            if k not in self.field_parameters or self._is_default_field_parameter(k):\n                self.set_field_parameter(k, v)\n        self.data_source = data_source\n        if weight_field is None:\n            self.weight_field = weight_field\n        else:\n            self.weight_field = self._determine_fields(weight_field)[0]\n\n        for f in self._determine_fields(field):\n            nodal_flag = self.ds._get_field_info(f).nodal_flag\n            if any(nodal_flag):\n                raise RuntimeError(\n                    \"Nodal fields are currently not supported for projections.\"\n                )\n\n    @property\n    def blocks(self):\n        return self.data_source.blocks\n\n    @property\n    def field(self):\n        return [k for k in self.field_data.keys() if k not in self._container_fields]\n\n    def get_data(self, fields=None):\n        fields = self._determine_fields(fields)\n        sfields = []\n        if self.moment == 2:\n\n            def _sq_field(field, data, fname: FieldKey):\n                return data[fname] ** 2\n\n            for field in fields:\n                fd = self.ds._get_field_info(field)\n                ftype, fname = field\n                self.ds.add_field(\n                    (ftype, f\"tmp_{fname}_squared\"),\n                    partial(_sq_field, fname=field),\n                    sampling_type=fd.sampling_type,\n                    units=f\"({fd.units})*({fd.units})\",\n                )\n                sfields.append((ftype, f\"tmp_{fname}_squared\"))\n        nfields = len(fields)\n        nsfields = len(sfields)\n        # We need a new tree for every single set of fields we add\n        if nfields == 0:\n            return\n        if isinstance(self.ds, ParticleDataset):\n            return\n        tree = self._get_tree(nfields + nsfields)\n        # This only needs to be done if we are in parallel; otherwise, we can\n        # safely build the mesh as we go.\n        if communication_system.communicators[-1].size > 1:\n            for chunk in self.data_source.chunks([], \"io\", local_only=False):\n                self._initialize_chunk(chunk, tree)\n        _units_initialized = False\n        with self.data_source._field_parameter_state(self.field_parameters):\n            for chunk in parallel_objects(\n                self.data_source.chunks([], \"io\", local_only=True)\n            ):\n                if not _units_initialized:\n                    self._initialize_projected_units(fields, chunk)\n                    _units_initialized = True\n                self._handle_chunk(chunk, fields + sfields, tree)\n        # if there's less than nprocs chunks, units won't be initialized\n        # on all processors, so sync with _projected_units on rank 0\n        projected_units = self.comm.mpi_bcast(self._projected_units)\n        self._projected_units = projected_units\n        # Note that this will briefly double RAM usage\n        if self.method in [\"max\", \"mip\"]:\n            merge_style = -1\n            op = \"max\"\n        elif self.method == \"min\":\n            merge_style = -2\n            op = \"min\"\n        elif self.method == \"integrate\":\n            merge_style = 1\n            op = \"sum\"\n        else:\n            raise NotImplementedError\n        # TODO: Add the combine operation\n        xax = self.ds.coordinates.x_axis[self.axis]\n        yax = self.ds.coordinates.y_axis[self.axis]\n\n        ix, iy, ires, nvals, nwvals = tree.get_all(False, merge_style)\n        px, pdx = self.ds.index._icoords_to_fcoords(\n            ix[:, None], ires // self.ds.ires_factor, axes=(xax,)\n        )\n        py, pdy = self.ds.index._icoords_to_fcoords(\n            iy[:, None], ires // self.ds.ires_factor, axes=(yax,)\n        )\n        px = px.ravel()\n        py = py.ravel()\n        pdx = pdx.ravel()\n        pdy = pdy.ravel()\n        np.multiply(pdx, 0.5, pdx)\n        np.multiply(pdy, 0.5, pdy)\n\n        nvals = self.comm.mpi_allreduce(nvals, op=op)\n        nwvals = self.comm.mpi_allreduce(nwvals, op=op)\n        if self.weight_field is not None:\n            # If there are 0s remaining in the weight vals\n            # this will not throw an error, but silently\n            # return nans for vals where dividing by 0\n            # Leave as NaNs to be auto-masked by Matplotlib\n            with np.errstate(invalid=\"ignore\"):\n                np.divide(nvals, nwvals[:, None], nvals)\n        # We now convert to half-widths and center-points\n        data = {}\n        code_length = self.ds.domain_width.units\n        data[\"px\"] = self.ds.arr(px, code_length)\n        data[\"py\"] = self.ds.arr(py, code_length)\n        data[\"weight_field\"] = nwvals\n        data[\"pdx\"] = self.ds.arr(pdx, code_length)\n        data[\"pdy\"] = self.ds.arr(pdy, code_length)\n        data[\"fields\"] = nvals\n        # Now we run the finalizer, which is ignored if we don't need it\n        field_data = np.hsplit(data.pop(\"fields\"), nfields + nsfields)\n        for fi, field in enumerate(fields):\n            mylog.debug(\"Setting field %s\", field)\n            input_units = self._projected_units[field]\n            fvals = field_data[fi].ravel()\n            if self.moment == 2:\n                fvals = compute_stddev_image(field_data[fi + nfields].ravel(), fvals)\n            self[field] = self.ds.arr(fvals, input_units)\n        for i in list(data.keys()):\n            self[i] = data.pop(i)\n        mylog.info(\"Projection completed\")\n        if self.moment == 2:\n            for field in sfields:\n                self.ds.field_info.pop(field)\n        self.tree = tree\n\n    def to_pw(self, fields=None, center=\"center\", width=None, origin=\"center-window\"):\n        r\"\"\"Create a :class:`~yt.visualization.plot_window.PWViewerMPL` from this\n        object.\n\n        This is a bare-bones mechanism of creating a plot window from this\n        object, which can then be moved around, zoomed, and on and on.  All\n        behavior of the plot window is relegated to that routine.\n        \"\"\"\n        pw = self._get_pw(fields, center, width, origin, \"Projection\")\n        return pw\n\n    def plot(self, fields=None):\n        if hasattr(self.data_source, \"left_edge\") and hasattr(\n            self.data_source, \"right_edge\"\n        ):\n            left_edge = self.data_source.left_edge\n            right_edge = self.data_source.right_edge\n            center = (left_edge + right_edge) / 2.0\n            width = right_edge - left_edge\n            xax = self.ds.coordinates.x_axis[self.axis]\n            yax = self.ds.coordinates.y_axis[self.axis]\n            lx, rx = left_edge[xax], right_edge[xax]\n            ly, ry = left_edge[yax], right_edge[yax]\n            width = (rx - lx), (ry - ly)\n        else:\n            width = self.ds.domain_width\n            center = self.ds.domain_center\n        pw = self._get_pw(fields, center, width, \"native\", \"Projection\")\n        try:\n            pw.show()\n        except YTNotInsideNotebook:\n            pass\n        return pw\n\n    def _initialize_projected_units(self, fields, chunk):\n        for field in self.data_source._determine_fields(fields):\n            if field in self._projected_units:\n                continue\n            finfo = self.ds._get_field_info(field)\n            if finfo.units is None:\n                # First time calling a units=\"auto\" field, infer units and cache\n                # for future field accesses.\n                finfo.units = str(chunk[field].units)\n            field_unit = Unit(finfo.output_units, registry=self.ds.unit_registry)\n            if self.method in (\"min\", \"max\") or self._sum_only:\n                path_length_unit = Unit(registry=self.ds.unit_registry)\n            else:\n                ax_name = self.ds.coordinates.axis_name[self.axis]\n                path_element_name = (\"index\", f\"path_element_{ax_name}\")\n                path_length_unit = self.ds.field_info[path_element_name].units\n                path_length_unit = Unit(\n                    path_length_unit, registry=self.ds.unit_registry\n                )\n                # Only convert to appropriate unit system for path\n                # elements that aren't angles\n                if not path_length_unit.is_dimensionless:\n                    path_length_unit = path_length_unit.get_base_equivalent(\n                        unit_system=self.ds.unit_system\n                    )\n            if self.weight_field is None:\n                self._projected_units[field] = field_unit * path_length_unit\n            else:\n                self._projected_units[field] = field_unit\n\n\nclass YTParticleProj(YTProj):\n    \"\"\"\n    A projection operation optimized for SPH particle data.\n    \"\"\"\n\n    _type_name = \"particle_proj\"\n\n    def __init__(\n        self,\n        field,\n        axis,\n        weight_field=None,\n        center=None,\n        ds=None,\n        data_source=None,\n        method=\"integrate\",\n        field_parameters=None,\n        max_level=None,\n        *,\n        moment=1,\n    ):\n        super().__init__(\n            field,\n            axis,\n            weight_field,\n            center,\n            ds,\n            data_source,\n            method,\n            field_parameters,\n            max_level,\n            moment=moment,\n        )\n\n    def _handle_chunk(self, chunk, fields, tree):\n        raise NotImplementedError(\"Particle projections have not yet been implemented\")\n\n\nclass YTQuadTreeProj(YTProj):\n    \"\"\"\n    This is a data object corresponding to a line integral through the\n    simulation domain.\n\n    This object is typically accessed through the `proj` object that\n    hangs off of index objects.  YTQuadTreeProj is a projection of a\n    `field` along an `axis`.  The field can have an associated\n    `weight_field`, in which case the values are multiplied by a weight\n    before being summed, and then divided by the sum of that weight; the\n    two fundamental modes of operating are direct line integral (no\n    weighting) and average along a line of sight (weighting.)  What makes\n    `proj` different from the standard projection mechanism is that it\n    utilizes a quadtree data structure, rather than the old mechanism for\n    projections.  It will not run in parallel, but serial runs should be\n    substantially faster.  Note also that lines of sight are integrated at\n    every projected finest-level cell.\n\n    Parameters\n    ----------\n    field : string\n        This is the field which will be \"projected\" along the axis.  If\n        multiple are specified (in a list) they will all be projected in\n        the first pass.\n    axis : int\n        The axis along which to slice.  Can be 0, 1, or 2 for x, y, z.\n    weight_field : string\n        If supplied, the field being projected will be multiplied by this\n        weight value before being integrated, and at the conclusion of the\n        projection the resultant values will be divided by the projected\n        `weight_field`.\n    center : array_like, optional\n        The 'center' supplied to fields that use it.  Note that this does\n        not have to have `coord` as one value.  Strictly optional.\n    data_source : `yt.data_objects.data_containers.YTSelectionContainer`, optional\n        If specified, this will be the data source used for selecting\n        regions to project.\n    method : string, optional\n        The method of projection to be performed.\n        \"integrate\" : integration along the axis\n        \"mip\" : maximum intensity projection (deprecated)\n        \"max\" : maximum intensity projection\n        \"min\" : minimum intensity projection\n        \"sum\" : same as \"integrate\", except that we don't multiply by the path length\n        WARNING: The \"sum\" option should only be used for uniform resolution grid\n        datasets, as other datasets may result in unphysical images.\n    style : string, optional\n        The same as the method keyword.  Deprecated as of version 3.0.2.\n        Please use method keyword instead.\n    field_parameters : dict of items\n        Values to be passed as field parameters that can be\n        accessed by generated fields.\n    moment : integer, optional\n        for a weighted projection, moment = 1 (the default) corresponds to a\n        weighted average. moment = 2 corresponds to a weighted standard\n        deviation.\n\n    Examples\n    --------\n\n    >>> ds = load(\"RedshiftOutput0005\")\n    >>> prj = ds.proj((\"gas\", \"density\"), 0)\n    >>> print(proj[\"gas\", \"density\"])\n    \"\"\"\n\n    _type_name = \"quad_proj\"\n\n    def __init__(\n        self,\n        field,\n        axis,\n        weight_field=None,\n        center=None,\n        ds=None,\n        data_source=None,\n        method=\"integrate\",\n        field_parameters=None,\n        max_level=None,\n        *,\n        moment=1,\n    ):\n        super().__init__(\n            field,\n            axis,\n            weight_field,\n            center,\n            ds,\n            data_source,\n            method,\n            field_parameters,\n            max_level,\n            moment=moment,\n        )\n\n        if not self.deserialize(field):\n            self.get_data(field)\n            self.serialize()\n\n    @property\n    def _mrep(self):\n        return MinimalProjectionData(self)\n\n    def deserialize(self, fields):\n        if not ytcfg.get(\"yt\", \"serialize\"):\n            return False\n        for field in fields:\n            self[field] = None\n        deserialized_successfully = False\n        store_file = self.ds.parameter_filename + \".yt\"\n        if os.path.isfile(store_file):\n            deserialized_successfully = self._mrep.restore(store_file, self.ds)\n\n            if deserialized_successfully:\n                mylog.info(\"Using previous projection data from %s\", store_file)\n                for field, field_data in self._mrep.field_data.items():\n                    self[field] = field_data\n        if not deserialized_successfully:\n            for field in fields:\n                del self[field]\n        return deserialized_successfully\n\n    def serialize(self):\n        if not ytcfg.get(\"yt\", \"serialize\"):\n            return\n        self._mrep.store(self.ds.parameter_filename + \".yt\")\n\n    def _get_tree(self, nvals):\n        xax = self.ds.coordinates.x_axis[self.axis]\n        yax = self.ds.coordinates.y_axis[self.axis]\n        xd = self.ds.domain_dimensions[xax]\n        yd = self.ds.domain_dimensions[yax]\n        bounds = (\n            self.ds.domain_left_edge[xax],\n            self.ds.domain_right_edge[xax],\n            self.ds.domain_left_edge[yax],\n            self.ds.domain_right_edge[yax],\n        )\n        return QuadTree(\n            np.array([xd, yd], dtype=\"int64\"), nvals, bounds, method=self.method\n        )\n\n    def _initialize_chunk(self, chunk, tree):\n        icoords = chunk.icoords\n        xax = self.ds.coordinates.x_axis[self.axis]\n        yax = self.ds.coordinates.y_axis[self.axis]\n        i1 = icoords[:, xax]\n        i2 = icoords[:, yax]\n        ilevel = chunk.ires * self.ds.ires_factor\n        tree.initialize_chunk(i1, i2, ilevel)\n\n    def _handle_chunk(self, chunk, fields, tree):\n        mylog.debug(\n            \"Adding chunk (%s) to tree (%0.3e GB RAM)\",\n            chunk.ires.size,\n            get_memory_usage() / 1024.0,\n        )\n        if self.method in (\"min\", \"max\") or self._sum_only:\n            dl = self.ds.quan(1.0, \"\")\n        else:\n            ax_name = self.ds.coordinates.axis_name[self.axis]\n            dl = chunk[\"index\", f\"path_element_{ax_name}\"]\n            # This is done for cases where our path element does not have a CGS\n            # equivalent.  Once \"preferred units\" have been implemented, this\n            # will not be necessary at all, as the final conversion will occur\n            # at the display layer.\n            if not dl.units.is_dimensionless:\n                dl.convert_to_units(self.ds.unit_system[\"length\"])\n        v = np.empty((chunk.ires.size, len(fields)), dtype=\"float64\")\n        for i, field in enumerate(fields):\n            d = chunk[field] * dl\n            v[:, i] = d\n        if self.weight_field is not None:\n            w = chunk[self.weight_field]\n            np.multiply(v, w[:, None], v)\n            np.multiply(w, dl, w)\n        else:\n            w = np.ones(chunk.ires.size, dtype=\"float64\")\n        icoords = chunk.icoords\n        xax = self.ds.coordinates.x_axis[self.axis]\n        yax = self.ds.coordinates.y_axis[self.axis]\n        i1 = icoords[:, xax]\n        i2 = icoords[:, yax]\n        ilevel = chunk.ires * self.ds.ires_factor\n        tree.add_chunk_to_tree(i1, i2, ilevel, v, w)\n\n\nclass YTCoveringGrid(YTSelectionContainer3D):\n    \"\"\"A 3D region with all data extracted to a single, specified\n    resolution.  Left edge should align with a cell boundary, but\n    defaults to the closest cell boundary.\n\n    Parameters\n    ----------\n    level : int\n        The resolution level data to which data will be gridded. Level\n        0 is the root grid dx for that dataset.\n        (The grid resolution will be simulation size / 2**level along\n         each grid axis.)\n    left_edge : array_like\n        The left edge of the region to be extracted.  Specify units by supplying\n        a YTArray, otherwise code length units are assumed.\n    dims : array_like\n        Number of cells along each axis of resulting covering_grid\n    fields : array_like, optional\n        A list of fields that you'd like pre-generated for your object\n    num_ghost_zones : integer, optional\n        The number of padding ghost zones used when accessing fields.\n    data_source :\n        An existing data object to intersect with the covering grid. Grid points\n        outside the data_source will exist as empty values.\n\n    Examples\n    --------\n    >>> cube = ds.covering_grid(2, left_edge=[0.0, 0.0, 0.0], dims=[128, 128, 128])\n    \"\"\"\n\n    _spatial = True\n    _type_name = \"covering_grid\"\n    _con_args = (\"level\", \"left_edge\", \"ActiveDimensions\")\n    _container_fields = (\n        (\"index\", \"dx\"),\n        (\"index\", \"dy\"),\n        (\"index\", \"dz\"),\n        (\"index\", \"x\"),\n        (\"index\", \"y\"),\n        (\"index\", \"z\"),\n    )\n    _base_grid = None\n\n    def __init__(\n        self,\n        level,\n        left_edge,\n        dims,\n        fields=None,\n        ds=None,\n        num_ghost_zones=0,\n        use_pbar=True,\n        field_parameters=None,\n        *,\n        data_source=None,\n    ):\n        if field_parameters is None:\n            center = None\n        else:\n            center = field_parameters.get(\"center\", None)\n        super().__init__(center, ds, field_parameters, data_source=data_source)\n\n        self.level = level\n        self.left_edge = self._sanitize_edge(left_edge)\n        self.ActiveDimensions = self._sanitize_dims(dims)\n\n        rdx = self.ds.domain_dimensions * self.ds.relative_refinement(0, level)\n\n        # normalize dims as a non-zero dim array\n        dims = np.array(list(always_iterable(dims)))\n        rdx[np.where(dims - 2 * num_ghost_zones <= 1)] = 1  # issue 602\n        self.base_dds = self.ds.domain_width / self.ds.domain_dimensions\n        self.dds = self.ds.domain_width / rdx.astype(\"float64\")\n        self.right_edge = self.left_edge + self.ActiveDimensions * self.dds\n        self._num_ghost_zones = num_ghost_zones\n        self._use_pbar = use_pbar\n        self.global_startindex = (\n            np.rint((self.left_edge - self.ds.domain_left_edge) / self.dds).astype(\n                \"int64\"\n            )\n            + self.ds.domain_offset\n        )\n        self._setup_data_source()\n        self.get_data(fields)\n\n    def get_global_startindex(self):\n        r\"\"\"Get the global start index of the covering grid.\"\"\"\n        return self.global_startindex\n\n    def to_xarray(self, fields=None):\n        r\"\"\"Export this fixed-resolution object to an xarray Dataset\n\n        This function will take a regularized grid and optionally a list of\n        fields and return an xarray Dataset object.  If xarray is not\n        importable, this will raise ImportError.\n\n        Parameters\n        ----------\n        fields : list of strings or tuple field names, default None\n            If this is supplied, it is the list of fields to be exported into\n            the data frame.  If not supplied, whatever fields presently exist\n            will be used.\n\n        Returns\n        -------\n        arr : Dataset\n            The data contained in the object.\n\n        Examples\n        --------\n\n        >>> dd = ds.r[::256j, ::256j, ::256j]\n        >>> xf1 = dd.to_xarray([(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n        >>> dd[\"gas\", \"velocity_magnitude\"]\n        >>> xf2 = dd.to_xarray()\n        \"\"\"\n        import xarray as xr\n\n        data = {}\n        coords = {}\n        for f in fields or self.field_data.keys():\n            data[f] = {\n                \"dims\": (\n                    \"x\",\n                    \"y\",\n                    \"z\",\n                ),\n                \"data\": self[f],\n                \"attrs\": {\"units\": str(self[f].uq)},\n            }\n        # We have our data, so now we generate both our coordinates and our metadata.\n        LE = self.LeftEdge + self.dds / 2.0\n        RE = self.RightEdge - self.dds / 2.0\n        N = self.ActiveDimensions\n        u = str(LE.uq)\n        for i, ax in enumerate(\"xyz\"):\n            coords[ax] = {\n                \"dims\": (ax,),\n                \"data\": np.mgrid[LE[i] : RE[i] : N[i] * 1j],\n                \"attrs\": {\"units\": u},\n            }\n        return xr.Dataset.from_dict({\"data_vars\": data, \"coords\": coords})\n\n    @property\n    def icoords(self):\n        ic = np.indices(self.ActiveDimensions).astype(\"int64\")\n        return np.column_stack(\n            [\n                i.ravel() + gi\n                for i, gi in zip(ic, self.get_global_startindex(), strict=True)\n            ]\n        )\n\n    @property\n    def fwidth(self):\n        fw = np.ones((self.ActiveDimensions.prod(), 3), dtype=\"float64\")\n        fw *= self.dds\n        return fw\n\n    @property\n    def fcoords(self):\n        LE = self.LeftEdge + self.dds / 2.0\n        RE = self.RightEdge - self.dds / 2.0\n        N = self.ActiveDimensions\n        fc = np.mgrid[\n            LE[0] : RE[0] : N[0] * 1j,\n            LE[1] : RE[1] : N[1] * 1j,\n            LE[2] : RE[2] : N[2] * 1j,\n        ]\n        return np.column_stack([f.ravel() for f in fc])\n\n    @property\n    def ires(self):\n        tr = np.ones(self.ActiveDimensions.prod(), dtype=\"int64\")\n        tr *= self.level\n        return tr\n\n    def set_field_parameter(self, name, val):\n        super().set_field_parameter(name, val)\n        if self._data_source is not None:\n            self._data_source.set_field_parameter(name, val)\n\n    def _sanitize_dims(self, dims):\n        if not is_sequence(dims):\n            dims = [dims] * len(self.ds.domain_left_edge)\n        if len(dims) != len(self.ds.domain_left_edge):\n            raise RuntimeError(\n                \"Length of dims must match the dimensionality of the dataset\"\n            )\n        return np.array(dims, dtype=\"int32\")\n\n    def _sanitize_edge(self, edge):\n        if not is_sequence(edge):\n            edge = [edge] * len(self.ds.domain_left_edge)\n        if len(edge) != len(self.ds.domain_left_edge):\n            raise RuntimeError(\n                \"Length of edges must match the dimensionality of the dataset\"\n            )\n        if hasattr(edge, \"units\"):\n            if edge.units.registry is self.ds.unit_registry:\n                return edge\n            edge_units = edge.units.copy()\n            edge_units.registry = self.ds.unit_registry\n        else:\n            edge_units = \"code_length\"\n        return self.ds.arr(edge, edge_units, dtype=\"float64\")\n\n    def _reshape_vals(self, arr):\n        if len(arr.shape) == 3:\n            return arr\n        return arr.reshape(self.ActiveDimensions, order=\"C\")\n\n    @property\n    def shape(self):\n        return tuple(self.ActiveDimensions.tolist())\n\n    def _setup_data_source(self):\n        reg = self.ds.region(self.center, self.left_edge, self.right_edge)\n        if self._data_source is None:\n            # note: https://github.com/yt-project/yt/pull/4063 implemented\n            # a data_source kwarg for YTCoveringGrid, but not YTArbitraryGrid\n            # so as of 4063, this will always be True for YTArbitraryGrid\n            # instances.\n            self._data_source = reg\n        else:\n            self._data_source = self.ds.intersection([self._data_source, reg])\n\n        self._data_source.min_level = 0\n        self._data_source.max_level = self.level\n        # This triggers \"special\" behavior in the RegionSelector to ensure we\n        # select *cells* whose bounding boxes overlap with our region, not just\n        # their cell centers.\n        self._data_source.loose_selection = True\n\n    def get_bbox(self):\n        return (self.left_edge, self.right_edge)\n\n    def get_data(self, fields=None):\n        if fields is None:\n            return\n        fields = self._determine_fields(fields)\n        fields_to_get = [f for f in fields if f not in self.field_data]\n        fields_to_get = self._identify_dependencies(fields_to_get)\n        if len(fields_to_get) == 0:\n            return\n        try:\n            fill, gen, part, alias = self._split_fields(fields_to_get)\n        except NeedsGridType as e:\n            if self._num_ghost_zones == 0:\n                num_ghost_zones = self._num_ghost_zones\n                raise RuntimeError(\n                    \"Attempting to access a field that needs ghost zones, but \"\n                    f\"{num_ghost_zones = }. You should create the covering grid \"\n                    \"with nonzero num_ghost_zones.\"\n                ) from e\n            else:\n                raise\n\n        # checking if we have a sph particles\n        if len(part) == 0:\n            is_sph_field = False\n        else:\n            is_sph_field = self.ds.field_info[part[0]].is_sph_field\n\n        if len(part) > 0 and len(alias) == 0:\n            if is_sph_field:\n                self._fill_sph_particles(fields)\n                for field in fields:\n                    if field in gen:\n                        gen.remove(field)\n            else:\n                self._fill_particles(part)\n\n        if len(fill) > 0:\n            self._fill_fields(fill)\n        for a, f in sorted(alias.items()):\n            if f.sampling_type == \"particle\" and not is_sph_field:\n                self[a] = self._data_source[f]\n            else:\n                self[a] = f(self)\n            self.field_data[a].convert_to_units(f.output_units)\n\n        if len(gen) > 0:\n            part_gen = []\n            cell_gen = []\n            for field in gen:\n                finfo = self.ds.field_info[field]\n                if finfo.sampling_type == \"particle\":\n                    part_gen.append(field)\n                else:\n                    cell_gen.append(field)\n            self._generate_fields(cell_gen)\n            for p in part_gen:\n                self[p] = self._data_source[p]\n\n    def _split_fields(self, fields_to_get):\n        fill, gen = self.index._split_fields(fields_to_get)\n        particles = []\n        alias = {}\n        for field in gen:\n            finfo = self.ds._get_field_info(field)\n            if finfo.is_alias:\n                alias[field] = finfo\n                continue\n            try:\n                finfo.check_available(self)\n            except NeedsOriginalGrid:\n                fill.append(field)\n        for field in fill:\n            finfo = self.ds._get_field_info(field)\n            if finfo.sampling_type == \"particle\":\n                particles.append(field)\n        gen = [f for f in gen if f not in fill and f not in alias]\n        fill = [f for f in fill if f not in particles]\n        return fill, gen, particles, alias\n\n    def _fill_particles(self, part):\n        for p in part:\n            self[p] = self._data_source[p]\n\n    def _check_sph_type(self, finfo):\n        \"\"\"\n        Check if a particle field has an SPH type.\n        There are several ways that this can happen,\n        checked in this order:\n        1. If the field type is a known particle filter, and\n           is in the list of SPH ptypes, use this type\n        2. If the field is an alias of an SPH field, but its\n           type is not \"gas\", use this type\n        3. Otherwise, if the field type is not in the SPH\n           types list and it is not \"gas\", we fail\n        If we get through without erroring out, we either have\n        a known SPH particle filter, an alias of an SPH field,\n        the default SPH ptype, or \"gas\" for an SPH field. Then\n        we return the particle type.\n        \"\"\"\n        ftype, fname = finfo.name\n        sph_ptypes = self.ds._sph_ptypes\n        ptype = sph_ptypes[0]\n        err = KeyError(f\"{ftype} is not a SPH particle type!\")\n        if ftype in self.ds.known_filters:\n            if ftype not in sph_ptypes:\n                raise err\n            else:\n                ptype = ftype\n        elif finfo.is_alias:\n            if finfo.alias_name[0] not in sph_ptypes:\n                raise err\n            elif ftype != \"gas\":\n                ptype = ftype\n        elif ftype not in sph_ptypes and ftype != \"gas\":\n            raise err\n        return ptype\n\n    def _fill_sph_particles(self, fields):\n        from tqdm import tqdm\n\n        # checks that we have the field and gets information\n        fields = [f for f in fields if f not in self.field_data]\n        if len(fields) == 0:\n            return\n\n        smoothing_style = getattr(self.ds, \"sph_smoothing_style\", \"scatter\")\n        normalize = getattr(self.ds, \"use_sph_normalization\", True)\n        kernel_name = getattr(self.ds, \"kernel_name\", \"cubic\")\n\n        bounds, size = self._get_grid_bounds_size()\n\n        period = self.ds.coordinates.period.copy()\n        if hasattr(period, \"in_units\"):\n            period = period.in_units(\"code_length\").d\n        # check periodicity per dimension\n        is_periodic = self.ds.periodicity\n\n        if smoothing_style == \"scatter\":\n            for field in fields:\n                fi = self.ds._get_field_info(field)\n                ptype = self._check_sph_type(fi)\n\n                buff = np.zeros(size, dtype=\"float64\")\n                if normalize:\n                    buff_den = np.zeros(size, dtype=\"float64\")\n\n                pbar = tqdm(desc=f\"Interpolating SPH field {field}\")\n                for chunk in self._data_source.chunks([field], \"io\"):\n                    px = chunk[ptype, \"particle_position_x\"].in_base(\"code\").d\n                    py = chunk[ptype, \"particle_position_y\"].in_base(\"code\").d\n                    pz = chunk[ptype, \"particle_position_z\"].in_base(\"code\").d\n                    hsml = chunk[ptype, \"smoothing_length\"].in_base(\"code\").d\n                    mass = chunk[ptype, \"particle_mass\"].in_base(\"code\").d\n                    dens = chunk[ptype, \"density\"].in_base(\"code\").d\n                    field_quantity = chunk[field].d\n\n                    pixelize_sph_kernel_arbitrary_grid(\n                        buff,\n                        px,\n                        py,\n                        pz,\n                        hsml,\n                        mass,\n                        dens,\n                        field_quantity,\n                        bounds,\n                        pbar=pbar,\n                        check_period=is_periodic,\n                        period=period,\n                        kernel_name=kernel_name,\n                    )\n                    if normalize:\n                        pixelize_sph_kernel_arbitrary_grid(\n                            buff_den,\n                            px,\n                            py,\n                            pz,\n                            hsml,\n                            mass,\n                            dens,\n                            np.ones(dens.shape[0]),\n                            bounds,\n                            pbar=pbar,\n                            check_period=is_periodic,\n                            period=period,\n                            kernel_name=kernel_name,\n                        )\n\n                if normalize:\n                    normalization_3d_utility(buff, buff_den)\n\n                self[field] = self.ds.arr(buff, fi.units)\n                pbar.close()\n\n        if smoothing_style == \"gather\":\n            num_neighbors = getattr(self.ds, \"num_neighbors\", 32)\n            for field in fields:\n                fi = self.ds._get_field_info(field)\n                ptype = self._check_sph_type(fi)\n\n                buff = np.zeros(size, dtype=\"float64\")\n\n                fields_to_get = [\n                    \"particle_position\",\n                    \"density\",\n                    \"particle_mass\",\n                    \"smoothing_length\",\n                    field[1],\n                ]\n                all_fields = all_data(self.ds, ptype, fields_to_get, kdtree=True)\n\n                interpolate_sph_grid_gather(\n                    buff,\n                    all_fields[\"particle_position\"],\n                    bounds,\n                    all_fields[\"smoothing_length\"],\n                    all_fields[\"particle_mass\"],\n                    all_fields[\"density\"],\n                    all_fields[field[1]].in_units(fi.units),\n                    self.ds.index.kdtree,\n                    use_normalization=normalize,\n                    num_neigh=num_neighbors,\n                )\n\n                self[field] = self.ds.arr(buff, fi.units)\n\n    def _fill_fields(self, fields):\n        fields = [f for f in fields if f not in self.field_data]\n        if len(fields) == 0:\n            return\n        output_fields = [\n            np.zeros(self.ActiveDimensions, dtype=\"float64\") for field in fields\n        ]\n        domain_dims = self.ds.domain_dimensions.astype(\n            \"int64\"\n        ) * self.ds.relative_refinement(0, self.level)\n        refine_by = self.ds.refine_by\n        if not is_sequence(self.ds.refine_by):\n            refine_by = [refine_by, refine_by, refine_by]\n        refine_by = np.array(refine_by, dtype=\"i8\")\n        for chunk in parallel_objects(self._data_source.chunks(fields, \"io\")):\n            input_fields = [chunk[field] for field in fields]\n            # NOTE: This usage of \"refine_by\" is actually *okay*, because it's\n            # being used with respect to iref, which is *already* scaled!\n            fill_region(\n                input_fields,\n                output_fields,\n                self.level,\n                self.global_startindex,\n                chunk.icoords,\n                chunk.ires,\n                domain_dims,\n                refine_by,\n            )\n        if self.comm.size > 1:\n            for i in range(len(fields)):\n                output_fields[i] = self.comm.mpi_allreduce(output_fields[i], op=\"sum\")\n        for field, v in zip(fields, output_fields, strict=True):\n            fi = self.ds._get_field_info(field)\n            self[field] = self.ds.arr(v, fi.units)\n\n    def _generate_container_field(self, field):\n        rv = self.ds.arr(np.ones(self.ActiveDimensions, dtype=\"float64\"), \"\")\n        axis_name = self.ds.coordinates.axis_name\n        if field == (\"index\", f\"d{axis_name[0]}\"):\n            np.multiply(rv, self.dds[0], rv)\n        elif field == (\"index\", f\"d{axis_name[1]}\"):\n            np.multiply(rv, self.dds[1], rv)\n        elif field == (\"index\", f\"d{axis_name[2]}\"):\n            np.multiply(rv, self.dds[2], rv)\n        elif field == (\"index\", axis_name[0]):\n            x = np.mgrid[\n                self.left_edge[0] + 0.5 * self.dds[0] : self.right_edge[0]\n                - 0.5 * self.dds[0] : self.ActiveDimensions[0] * 1j\n            ]\n            np.multiply(rv, x[:, None, None], rv)\n        elif field == (\"index\", axis_name[1]):\n            y = np.mgrid[\n                self.left_edge[1] + 0.5 * self.dds[1] : self.right_edge[1]\n                - 0.5 * self.dds[1] : self.ActiveDimensions[1] * 1j\n            ]\n            np.multiply(rv, y[None, :, None], rv)\n        elif field == (\"index\", axis_name[2]):\n            z = np.mgrid[\n                self.left_edge[2] + 0.5 * self.dds[2] : self.right_edge[2]\n                - 0.5 * self.dds[2] : self.ActiveDimensions[2] * 1j\n            ]\n            np.multiply(rv, z[None, None, :], rv)\n        else:\n            raise KeyError(field)\n        return rv\n\n    @property\n    def LeftEdge(self):\n        return self.left_edge\n\n    @property\n    def RightEdge(self):\n        return self.right_edge\n\n    def deposit(self, positions, fields=None, method=None, kernel_name=\"cubic\"):\n        cls = getattr(particle_deposit, f\"deposit_{method}\", None)\n        if cls is None:\n            raise YTParticleDepositionNotImplemented(method)\n        # We allocate number of zones, not number of octs. Everything\n        # inside this is Fortran ordered because of the ordering in the\n        # octree deposit routines, so we reverse it here to match the\n        # convention there\n        nvals = tuple(self.ActiveDimensions[::-1])\n        # append a dummy dimension because we are only depositing onto\n        # one grid\n        op = cls(nvals + (1,), kernel_name)\n        op.initialize()\n        op.process_grid(self, positions, fields)\n        # Fortran-ordered, so transpose.\n        vals = op.finalize().transpose()\n        # squeeze dummy dimension we appended above\n        return np.squeeze(vals, axis=0)\n\n    def write_to_gdf(self, gdf_path, fields, nprocs=1, field_units=None, **kwargs):\n        r\"\"\"\n        Write the covering grid data to a GDF file.\n\n        Parameters\n        ----------\n        gdf_path : string\n            Pathname of the GDF file to write.\n        fields : list of strings\n            Fields to write to the GDF file.\n        nprocs : integer, optional\n            Split the covering grid into *nprocs* subgrids before\n            writing to the GDF file. Default: 1\n        field_units : dictionary, optional\n            Dictionary of units to convert fields to. If not set, fields are\n            in their default units.\n        All remaining keyword arguments are passed to\n        yt.utilities.grid_data_format.writer.write_to_gdf.\n\n        Examples\n        --------\n        >>> cube.write_to_gdf(\n        ...     \"clumps.h5\",\n        ...     [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        ...     nprocs=16,\n        ...     overwrite=True,\n        ... )\n        \"\"\"\n        data = {}\n        for field in fields:\n            if field in field_units:\n                units = field_units[field]\n            else:\n                units = str(self[field].units)\n            data[field] = (self[field].in_units(units).v, units)\n        le = self.left_edge.v\n        re = self.right_edge.v\n        bbox = np.array([[l, r] for l, r in zip(le, re, strict=True)])\n        ds = load_uniform_grid(\n            data,\n            self.ActiveDimensions,\n            bbox=bbox,\n            length_unit=self.ds.length_unit,\n            time_unit=self.ds.time_unit,\n            mass_unit=self.ds.mass_unit,\n            nprocs=nprocs,\n            sim_time=self.ds.current_time.v,\n        )\n        write_to_gdf(ds, gdf_path, **kwargs)\n\n    def _get_grid_bounds_size(self):\n        dd = self.ds.domain_width / 2**self.level\n        bounds = np.zeros(6, dtype=\"float64\")\n\n        bounds[0] = self.left_edge[0].in_base(\"code\")\n        bounds[1] = bounds[0] + dd[0].d * self.ActiveDimensions[0]\n        bounds[2] = self.left_edge[1].in_base(\"code\")\n        bounds[3] = bounds[2] + dd[1].d * self.ActiveDimensions[1]\n        bounds[4] = self.left_edge[2].in_base(\"code\")\n        bounds[5] = bounds[4] + dd[2].d * self.ActiveDimensions[2]\n        size = np.ones(3, dtype=\"int64\") * 2**self.level\n\n        return bounds, size\n\n    def to_fits_data(self, fields, length_unit=None):\n        r\"\"\"Export a set of gridded fields to a FITS file.\n\n        This will export a set of FITS images of either the fields specified\n        or all the fields already in the object.\n\n        Parameters\n        ----------\n        fields : list of strings\n            These fields will be pixelized and output. If \"None\", the keys of the\n            FRB will be used.\n        length_unit : string, optional\n            the length units that the coordinates are written in. The default\n            is to use the default length unit of the dataset.\n        \"\"\"\n        from yt.visualization.fits_image import FITSImageData\n\n        if length_unit is None:\n            length_unit = self.ds.length_unit\n        fields = list(iter_fields(fields))\n        fid = FITSImageData(self, fields, length_unit=length_unit)\n        return fid\n\n\nclass YTArbitraryGrid(YTCoveringGrid):\n    \"\"\"A 3D region with arbitrary bounds and dimensions.\n\n    In contrast to the Covering Grid, this object accepts a left edge, a right\n    edge, and dimensions.  This allows it to be used for creating 3D particle\n    deposition fields that are independent of the underlying mesh, whether that\n    is yt-generated or from the simulation data.  For example, arbitrary boxes\n    around particles can be drawn and particle deposition fields can be\n    created.  This object will refuse to generate any fluid fields.\n\n    Parameters\n    ----------\n    left_edge : array_like\n        The left edge of the region to be extracted\n    right_edge : array_like\n        The left edge of the region to be extracted\n    dims : array_like\n        Number of cells along each axis of resulting grid.\n\n    Examples\n    --------\n    >>> obj = ds.arbitrary_grid(\n    ...     [0.0, 0.0, 0.0], [0.99, 0.99, 0.99], dims=[128, 128, 128]\n    ... )\n    \"\"\"\n\n    _spatial = True\n    _type_name = \"arbitrary_grid\"\n    _con_args = (\"left_edge\", \"right_edge\", \"ActiveDimensions\")\n    _container_fields = (\n        (\"index\", \"dx\"),\n        (\"index\", \"dy\"),\n        (\"index\", \"dz\"),\n        (\"index\", \"x\"),\n        (\"index\", \"y\"),\n        (\"index\", \"z\"),\n    )\n\n    def __init__(self, left_edge, right_edge, dims, ds=None, field_parameters=None):\n        if field_parameters is None:\n            center = None\n        else:\n            center = field_parameters.get(\"center\", None)\n        YTSelectionContainer3D.__init__(self, center, ds, field_parameters)\n        self.left_edge = self._sanitize_edge(left_edge)\n        self.right_edge = self._sanitize_edge(right_edge)\n        self.ActiveDimensions = self._sanitize_dims(dims)\n        self.dds = self.base_dds = (\n            self.right_edge - self.left_edge\n        ) / self.ActiveDimensions\n        self.level = 99\n        self._setup_data_source()\n\n    def _fill_fields(self, fields):\n        fields = [f for f in fields if f not in self.field_data]\n        if len(fields) == 0:\n            return\n        # It may be faster to adapt fill_region_float to fill multiple fields\n        # instead of looping here\n        for field in fields:\n            dest = np.zeros(self.ActiveDimensions, dtype=\"float64\")\n            for chunk in self._data_source.chunks(fields, \"io\"):\n                fill_region_float(\n                    chunk.fcoords,\n                    chunk.fwidth,\n                    chunk[field],\n                    self.left_edge,\n                    self.right_edge,\n                    dest,\n                    1,\n                    self.ds.domain_width,\n                    int(any(self.ds.periodicity)),\n                )\n            fi = self.ds._get_field_info(field)\n            self[field] = self.ds.arr(dest, fi.units)\n\n    def _get_grid_bounds_size(self):\n        bounds = np.empty(6, dtype=\"float64\")\n        bounds[0] = self.left_edge[0].in_base(\"code\")\n        bounds[2] = self.left_edge[1].in_base(\"code\")\n        bounds[4] = self.left_edge[2].in_base(\"code\")\n        bounds[1] = self.right_edge[0].in_base(\"code\")\n        bounds[3] = self.right_edge[1].in_base(\"code\")\n        bounds[5] = self.right_edge[2].in_base(\"code\")\n        size = self.ActiveDimensions\n\n        return bounds, size\n\n\nclass LevelState:\n    current_dx = None\n    current_dims = None\n    current_level = None\n    global_startindex = None\n    old_global_startindex = None\n    fields = None\n    data_source = None\n\n    # These are all cached here as numpy arrays, without units, in\n    # code_lengths.\n    domain_width = None\n    domain_left_edge = None\n    domain_right_edge = None\n    left_edge = None\n    right_edge = None\n    base_dx = None\n    dds = None\n\n\nclass YTSmoothedCoveringGrid(YTCoveringGrid):\n    \"\"\"A 3D region with all data extracted and interpolated to a\n    single, specified resolution. (Identical to covering_grid,\n    except that it interpolates.)\n\n    Smoothed covering grids start at level 0, interpolating to\n    fill the region to level 1, replacing any cells actually\n    covered by level 1 data, and then recursively repeating this\n    process until it reaches the specified `level`.\n\n    Parameters\n    ----------\n    level : int\n        The resolution level data is uniformly gridded at\n    left_edge : array_like\n        The left edge of the region to be extracted\n    dims : array_like\n        Number of cells along each axis of resulting covering_grid.\n    fields : array_like, optional\n        A list of fields that you'd like pre-generated for your object\n\n    Example\n    -------\n    cube = ds.smoothed_covering_grid(2, left_edge=[0.0, 0.0, 0.0], \\\n                              dims=[128, 128, 128])\n    \"\"\"\n\n    _type_name = \"smoothed_covering_grid\"\n    filename = None\n    _min_level = None\n\n    @wraps(YTCoveringGrid.__init__)  # type: ignore [misc]\n    def __init__(self, *args, **kwargs):\n        ds = kwargs[\"ds\"]\n        self._base_dx = (\n            ds.domain_right_edge - ds.domain_left_edge\n        ) / ds.domain_dimensions.astype(\"float64\")\n        self.global_endindex = None\n        YTCoveringGrid.__init__(self, *args, **kwargs)\n        self._final_start_index = self.global_startindex\n\n    def _setup_data_source(self, level_state=None):\n        if level_state is None:\n            super()._setup_data_source()\n            return\n        # We need a buffer region to allow for zones that contribute to the\n        # interpolation but are not directly inside our bounds\n        level_state.data_source = self.ds.region(\n            self.center,\n            level_state.left_edge - level_state.current_dx,\n            level_state.right_edge + level_state.current_dx,\n        )\n        level_state.data_source.min_level = level_state.current_level\n        level_state.data_source.max_level = level_state.current_level\n        self._pdata_source = self.ds.region(\n            self.center,\n            level_state.left_edge - level_state.current_dx,\n            level_state.right_edge + level_state.current_dx,\n        )\n        self._pdata_source.min_level = level_state.current_level\n        self._pdata_source.max_level = level_state.current_level\n\n    def _compute_minimum_level(self):\n        # This attempts to determine the minimum level that we should be\n        # starting on for this box.  It does this by identifying the minimum\n        # level that could contribute to the minimum bounding box at that\n        # level; that means that all cells from coarser levels will be replaced.\n        if self._min_level is not None:\n            return self._min_level\n        ils = LevelState()\n        min_level = 0\n        for l in range(self.level, 0, -1):\n            dx = self._base_dx / self.ds.relative_refinement(0, l)\n            start_index, end_index, dims = self._minimal_box(dx)\n            ils.left_edge = start_index * dx + self.ds.domain_left_edge\n            ils.right_edge = ils.left_edge + dx * dims\n            ils.current_dx = dx\n            ils.current_level = l\n            self._setup_data_source(ils)\n            # Reset the max_level\n            ils.data_source.min_level = 0\n            ils.data_source.max_level = l\n            ils.data_source.loose_selection = False\n            min_level = self.level\n            for chunk in ils.data_source.chunks([], \"io\"):\n                # With our odd selection methods, we can sometimes get no-sized ires.\n                ir = chunk.ires\n                if ir.size == 0:\n                    continue\n                min_level = min(ir.min(), min_level)\n            if min_level >= l:\n                break\n        self._min_level = min_level\n        return min_level\n\n    def _fill_fields(self, fields):\n        fields = [f for f in fields if f not in self.field_data]\n        if len(fields) == 0:\n            return\n        ls = self._initialize_level_state(fields)\n        min_level = self._compute_minimum_level()\n        # NOTE: This usage of \"refine_by\" is actually *okay*, because it's\n        # being used with respect to iref, which is *already* scaled!\n        refine_by = self.ds.refine_by\n        if not is_sequence(self.ds.refine_by):\n            refine_by = [refine_by, refine_by, refine_by]\n        refine_by = np.array(refine_by, dtype=\"i8\")\n\n        runtime_errors_count = 0\n        for level in range(self.level + 1):\n            if level < min_level:\n                self._update_level_state(ls)\n                continue\n            nd = self.ds.dimensionality\n            refinement = np.zeros_like(ls.base_dx)\n            refinement += self.ds.relative_refinement(0, ls.current_level)\n            refinement[nd:] = 1\n            domain_dims = self.ds.domain_dimensions * refinement\n            domain_dims = domain_dims.astype(\"int64\")\n            tot = ls.current_dims.prod()\n            for chunk in ls.data_source.chunks(fields, \"io\"):\n                chunk[fields[0]]\n                input_fields = [chunk[field] for field in fields]\n                tot -= fill_region(\n                    input_fields,\n                    ls.fields,\n                    ls.current_level,\n                    ls.global_startindex,\n                    chunk.icoords,\n                    chunk.ires,\n                    domain_dims,\n                    refine_by,\n                )\n            if level == 0 and tot != 0:\n                runtime_errors_count += 1\n            self._update_level_state(ls)\n        if runtime_errors_count:\n            warnings.warn(\n                \"Something went wrong during field computation. \"\n                \"This is likely due to missing ghost-zones support \"\n                f\"in class {type(self.ds)}\",\n                category=RuntimeWarning,\n                stacklevel=1,\n            )\n            mylog.debug(\"Caught %d runtime errors.\", runtime_errors_count)\n        for field, v in zip(fields, ls.fields, strict=True):\n            if self.level > 0:\n                v = v[1:-1, 1:-1, 1:-1]\n            fi = self.ds._get_field_info(field)\n            self[field] = self.ds.arr(v, fi.units)\n\n    def _initialize_level_state(self, fields):\n        ls = LevelState()\n        ls.domain_width = self.ds.domain_width\n        ls.domain_left_edge = self.ds.domain_left_edge\n        ls.domain_right_edge = self.ds.domain_right_edge\n        ls.base_dx = self._base_dx\n        ls.dds = self.dds\n        ls.left_edge = self.left_edge\n        ls.right_edge = self.right_edge\n        for att in (\n            \"domain_width\",\n            \"domain_left_edge\",\n            \"domain_right_edge\",\n            \"left_edge\",\n            \"right_edge\",\n            \"base_dx\",\n            \"dds\",\n        ):\n            setattr(ls, att, getattr(ls, att).in_units(\"code_length\").d)\n        ls.current_dx = ls.base_dx\n        ls.current_level = 0\n        ls.global_startindex, end_index, idims = self._minimal_box(ls.current_dx)\n        ls.current_dims = idims.astype(\"int32\")\n        ls.left_edge = ls.global_startindex * ls.current_dx + self.ds.domain_left_edge.d\n        ls.right_edge = ls.left_edge + ls.current_dims * ls.current_dx\n        ls.fields = [np.zeros(idims, dtype=\"float64\") - 999 for field in fields]\n        self._setup_data_source(ls)\n        return ls\n\n    def _minimal_box(self, dds):\n        LL = self.left_edge.d - self.ds.domain_left_edge.d\n        # Nudge in case we're on the edge\n        LL += np.finfo(np.float64).eps\n        LS = self.right_edge.d - self.ds.domain_left_edge.d\n        LS += np.finfo(np.float64).eps\n        cell_start = LL / dds  # This is the cell we're inside\n        cell_end = LS / dds\n        if self.level == 0:\n            start_index = np.array(np.floor(cell_start), dtype=\"int64\")\n            end_index = np.array(np.ceil(cell_end), dtype=\"int64\")\n            dims = np.rint((self.ActiveDimensions * self.dds.d) / dds).astype(\"int64\")\n        else:\n            # Give us one buffer\n            start_index = np.rint(cell_start).astype(\"int64\") - 1\n            # How many root cells do we occupy?\n            end_index = np.rint(cell_end).astype(\"int64\")\n            dims = end_index - start_index + 1\n        return start_index, end_index, dims.astype(\"int32\")\n\n    def _update_level_state(self, level_state):\n        ls = level_state\n        if ls.current_level >= self.level:\n            return\n        rf = float(self.ds.relative_refinement(ls.current_level, ls.current_level + 1))\n        ls.current_level += 1\n        nd = self.ds.dimensionality\n        refinement = np.zeros_like(ls.base_dx)\n        refinement += self.ds.relative_refinement(0, ls.current_level)\n        refinement[nd:] = 1\n        ls.current_dx = ls.base_dx / refinement\n        ls.old_global_startindex = ls.global_startindex\n        ls.global_startindex, end_index, ls.current_dims = self._minimal_box(\n            ls.current_dx\n        )\n        ls.left_edge = ls.global_startindex * ls.current_dx + self.ds.domain_left_edge.d\n        ls.right_edge = ls.left_edge + ls.current_dims * ls.current_dx\n        input_left = (level_state.old_global_startindex) * rf + 1\n        new_fields = []\n        for input_field in level_state.fields:\n            output_field = np.zeros(ls.current_dims, dtype=\"float64\")\n            output_left = level_state.global_startindex + 0.5\n            ghost_zone_interpolate(\n                rf, input_field, input_left, output_field, output_left\n            )\n            new_fields.append(output_field)\n        level_state.fields = new_fields\n        self._setup_data_source(ls)\n\n\nclass YTSurface(YTSelectionContainer3D):\n    r\"\"\"This surface object identifies isocontours on a cell-by-cell basis,\n    with no consideration of global connectedness, and returns the vertices\n    of the Triangles in that isocontour.\n\n    This object simply returns the vertices of all the triangles calculated by\n    the `marching cubes <https://en.wikipedia.org/wiki/Marching_cubes>`_\n    algorithm; for more complex operations, such as identifying connected sets\n    of cells above a given threshold, see the extract_connected_sets function.\n    This is more useful for calculating, for instance, total isocontour area, or\n    visualizing in an external program (such as `MeshLab\n    <http://www.meshlab.net>`_.)  The object has the properties .vertices and\n    will sample values if a field is requested.  The values are interpolated to\n    the center of a given face.\n\n    Parameters\n    ----------\n    data_source : YTSelectionContainer\n        This is the object which will used as a source\n    surface_field : string\n        Any field that can be obtained in a data object.  This is the field\n        which will be isocontoured.\n    field_value : float, YTQuantity, or unit tuple\n        The value at which the isocontour should be calculated.\n\n    Examples\n    --------\n    This will create a data object, find a nice value in the center, and\n    output the vertices to \"triangles.obj\" after rescaling them.\n\n    >>> from yt.units import kpc\n    >>> sp = ds.sphere(\"max\", (10, \"kpc\"))\n    >>> surf = ds.surface(sp, (\"gas\", \"density\"), 5e-27)\n    >>> print(surf[\"gas\", \"temperature\"])\n    >>> print(surf.vertices)\n    >>> bounds = [\n    ...     (sp.center[i] - 5.0 * kpc, sp.center[i] + 5.0 * kpc) for i in range(3)\n    ... ]\n    >>> surf.export_ply(\"my_galaxy.ply\", bounds=bounds)\n    \"\"\"\n\n    _type_name = \"surface\"\n    _con_args = (\"data_source\", \"surface_field\", \"field_value\")\n    _container_fields = (\n        (\"index\", \"dx\"),\n        (\"index\", \"dy\"),\n        (\"index\", \"dz\"),\n        (\"index\", \"x\"),\n        (\"index\", \"y\"),\n        (\"index\", \"z\"),\n    )\n\n    def __init__(self, data_source, surface_field, field_value, ds=None):\n        self.data_source = data_source\n        self.surface_field = data_source._determine_fields(surface_field)[0]\n        finfo = data_source.ds.field_info[self.surface_field]\n        try:\n            self.field_value = field_value.to(finfo.units)\n        except AttributeError:\n            if isinstance(field_value, tuple):\n                self.field_value = data_source.ds.quan(*field_value)\n                self.field_value = self.field_value.to(finfo.units)\n            else:\n                self.field_value = data_source.ds.quan(field_value, finfo.units)\n        self.vertex_samples = YTFieldData()\n        center = data_source.get_field_parameter(\"center\")\n        super().__init__(center=center, ds=ds)\n\n    def _generate_container_field(self, field):\n        self.get_data(field)\n        return self[field]\n\n    def get_data(self, fields=None, sample_type=\"face\", no_ghost=False):\n        if isinstance(fields, list) and len(fields) > 1:\n            for field in fields:\n                self.get_data(field)\n            return\n        elif isinstance(fields, list):\n            fields = fields[0]\n        # Now we have a \"fields\" value that is either a string or None\n        if fields is not None:\n            mylog.info(\"Extracting (sampling: %s)\", fields)\n        verts = []\n        samples = []\n        for _io_chunk in parallel_objects(self.data_source.chunks([], \"io\")):\n            for block, mask in self.data_source.blocks:\n                my_verts = self._extract_isocontours_from_grid(\n                    block,\n                    self.surface_field,\n                    self.field_value,\n                    mask,\n                    fields,\n                    sample_type,\n                    no_ghost=no_ghost,\n                )\n                if fields is not None:\n                    my_verts, svals = my_verts\n                    samples.append(svals)\n                verts.append(my_verts)\n        verts = np.concatenate(verts).transpose()\n        verts = self.comm.par_combine_object(verts, op=\"cat\", datatype=\"array\")\n        # verts is an ndarray here and will always be in code units, so we\n        # expose it in the public API as a YTArray\n        self._vertices = self.ds.arr(verts, \"code_length\")\n        if fields is not None:\n            samples = uconcatenate(samples)\n            samples = self.comm.par_combine_object(samples, op=\"cat\", datatype=\"array\")\n            if sample_type == \"face\":\n                self[fields] = samples\n            elif sample_type == \"vertex\":\n                self.vertex_samples[fields] = samples\n\n    def _extract_isocontours_from_grid(\n        self,\n        grid,\n        field,\n        value,\n        mask,\n        sample_values=None,\n        sample_type=\"face\",\n        no_ghost=False,\n    ):\n        # TODO: check if multiple fields can be passed here\n        vals = grid.get_vertex_centered_data([field], no_ghost=no_ghost)[field]\n        if sample_values is not None:\n            # TODO: is no_ghost=False correct here?\n            svals = grid.get_vertex_centered_data([sample_values])[sample_values]\n        else:\n            svals = None\n\n        sample_type = {\"face\": 1, \"vertex\": 2}[sample_type]\n        my_verts = march_cubes_grid(\n            value, vals, mask, grid.LeftEdge, grid.dds, svals, sample_type\n        )\n        return my_verts\n\n    def calculate_flux(self, field_x, field_y, field_z, fluxing_field=None):\n        r\"\"\"This calculates the flux over the surface.\n\n        This function will conduct `Marching Cubes`_ on all the cells in a\n        given data container (grid-by-grid), and then for each identified\n        triangular segment of an isocontour in a given cell, calculate the\n        gradient (i.e., normal) in the isocontoured field, interpolate the local\n        value of the \"fluxing\" field, the area of the triangle, and then return:\n\n        area * local_flux_value * (n dot v)\n\n        Where area, local_value, and the vector v are interpolated at the barycenter\n        (weighted by the vertex values) of the triangle.  Note that this\n        specifically allows for the field fluxing across the surface to be\n        *different* from the field being contoured.  If the fluxing_field is\n        not specified, it is assumed to be 1.0 everywhere, and the raw flux\n        with no local-weighting is returned.\n\n        Additionally, the returned flux is defined as flux *into* the surface,\n        not flux *out of* the surface.\n\n        Parameters\n        ----------\n        field_x : string\n            The x-component field\n        field_y : string\n            The y-component field\n        field_z : string\n            The z-component field\n        fluxing_field : string, optional\n            The field whose passage over the surface is of interest.  If not\n            specified, assumed to be 1.0 everywhere.\n\n        Returns\n        -------\n        flux : YTQuantity\n            The summed flux.\n\n        References\n        ----------\n\n        .. _Marching Cubes:\n            https://en.wikipedia.org/wiki/Marching_cubes\n\n        Examples\n        --------\n\n        This will create a data object, find a nice value in the center, and\n        calculate the metal flux over it.\n\n        >>> sp = ds.sphere(\"max\", (10, \"kpc\"))\n        >>> surf = ds.surface(sp, (\"gas\", \"density\"), 5e-27)\n        >>> flux = surf.calculate_flux(\n        ...     (\"gas\", \"velocity_x\"),\n        ...     (\"gas\", \"velocity_y\"),\n        ...     (\"gas\", \"velocity_z\"),\n        ...     (\"gas\", \"metal_density\"),\n        ... )\n        \"\"\"\n        flux = 0.0\n        mylog.info(\"Fluxing %s\", fluxing_field)\n        for _io_chunk in parallel_objects(self.data_source.chunks([], \"io\")):\n            for block, mask in self.data_source.blocks:\n                flux += self._calculate_flux_in_grid(\n                    block, mask, field_x, field_y, field_z, fluxing_field\n                )\n        flux = self.comm.mpi_allreduce(flux, op=\"sum\")\n        return flux\n\n    def _calculate_flux_in_grid(\n        self, grid, mask, field_x, field_y, field_z, fluxing_field=None\n    ):\n        vc_fields = [self.surface_field, field_x, field_y, field_z]\n        if fluxing_field is not None:\n            vc_fields.append(fluxing_field)\n\n        vc_data = grid.get_vertex_centered_data(vc_fields)\n        if fluxing_field is None:\n            ff = self.ds.arr(\n                np.ones_like(vc_data[self.surface_field].d, dtype=\"float64\"),\n                \"dimensionless\",\n            )\n        else:\n            ff = vc_data[fluxing_field]\n        surf_vals = vc_data[self.surface_field]\n        field_x_vals = vc_data[field_x]\n        field_y_vals = vc_data[field_y]\n        field_z_vals = vc_data[field_z]\n        ret = march_cubes_grid_flux(\n            self.field_value,\n            surf_vals,\n            field_x_vals,\n            field_y_vals,\n            field_z_vals,\n            ff,\n            mask,\n            grid.LeftEdge,\n            grid.dds,\n        )\n        # assumes all the fluxing fields have the same units\n        ret_units = field_x_vals.units * ff.units * grid.dds.units**2\n        ret = self.ds.arr(ret, ret_units)\n        ret.convert_to_units(self.ds.unit_system[ret_units.dimensions])\n        return ret\n\n    _vertices = None\n\n    @property\n    def vertices(self):\n        if self._vertices is None:\n            self.get_data()\n        return self._vertices\n\n    @property\n    def triangles(self):\n        vv = np.empty((self.vertices.shape[1] // 3, 3, 3), dtype=\"float64\")\n        vv = self.ds.arr(vv, self.vertices.units)\n        for i in range(3):\n            for j in range(3):\n                vv[:, i, j] = self.vertices[j, i::3]\n        return vv\n\n    _surface_area = None\n\n    @property\n    def surface_area(self):\n        if self._surface_area is not None:\n            return self._surface_area\n        tris = self.triangles\n        x = tris[:, 1, :] - tris[:, 0, :]\n        y = tris[:, 2, :] - tris[:, 0, :]\n        areas = (x[:, 1] * y[:, 2] - x[:, 2] * y[:, 1]) ** 2\n        np.add(areas, (x[:, 2] * y[:, 0] - x[:, 0] * y[:, 2]) ** 2, out=areas)\n        np.add(areas, (x[:, 0] * y[:, 1] - x[:, 1] * y[:, 0]) ** 2, out=areas)\n        np.sqrt(areas, out=areas)\n        self._surface_area = 0.5 * areas.sum()\n        return self._surface_area\n\n    def export_obj(\n        self,\n        filename,\n        transparency=1.0,\n        dist_fac=None,\n        color_field=None,\n        emit_field=None,\n        color_map=None,\n        color_log=True,\n        emit_log=True,\n        plot_index=None,\n        color_field_max=None,\n        color_field_min=None,\n        emit_field_max=None,\n        emit_field_min=None,\n    ):\n        r\"\"\"Export the surface to the OBJ format\n\n        Suitable for visualization in many different programs (e.g., Blender).\n        NOTE: this exports an .obj file and an .mtl file, both with the general\n        'filename' as a prefix.  The .obj file points to the .mtl file in its\n        header, so if you move the 2 files, make sure you change the .obj header\n        to account for this. ALSO NOTE: the emit_field needs to be a combination\n        of the other 2 fields used to have the emissivity track with the color.\n\n        Parameters\n        ----------\n\n        filename : string\n            The file this will be exported to.  This cannot be a file-like\n            object. If there are no file extensions included - both obj & mtl\n            files are created.\n        transparency : float\n            This gives the transparency of the output surface plot.  Values\n            from 0.0 (invisible) to 1.0 (opaque).\n        dist_fac : float\n            Divide the axes distances by this amount.\n        color_field : string\n            Should a field be sample and colormapped?\n        emit_field : string\n            Should we track the emissivity of a field? This should be a\n            combination of the other 2 fields being used.\n        color_map : string\n            Which color map should be applied?\n        color_log : bool\n            Should the color field be logged before being mapped?\n        emit_log : bool\n            Should the emitting field be logged before being mapped?\n        plot_index : integer\n            Index of plot for multiple plots.  If none, then only 1 plot.\n        color_field_max : float\n            Maximum value of the color field across all surfaces.\n        color_field_min : float\n            Minimum value of the color field across all surfaces.\n        emit_field_max : float\n            Maximum value of the emitting field across all surfaces.\n        emit_field_min : float\n            Minimum value of the emitting field across all surfaces.\n\n        Examples\n        --------\n\n        >>> sp = ds.sphere(\"max\", (10, \"kpc\"))\n        >>> trans = 1.0\n        >>> surf = ds.surface(sp, (\"gas\", \"density\"), 5e-27)\n        >>> surf.export_obj(\"my_galaxy\", transparency=trans)\n\n        >>> sp = ds.sphere(\"max\", (10, \"kpc\"))\n        >>> mi, ma = sp.quantities.extrema(\"temperature\")\n        >>> rhos = [1e-24, 1e-25]\n        >>> trans = [0.5, 1.0]\n        >>> for i, r in enumerate(rhos):\n        ...     surf = ds.surface(sp, \"density\", r)\n        ...     surf.export_obj(\n        ...         \"my_galaxy\",\n        ...         transparency=trans[i],\n        ...         color_field=\"temperature\",\n        ...         plot_index=i,\n        ...         color_field_max=ma,\n        ...         color_field_min=mi,\n        ...     )\n\n        >>> sp = ds.sphere(\"max\", (10, \"kpc\"))\n        >>> rhos = [1e-24, 1e-25]\n        >>> trans = [0.5, 1.0]\n        >>> def _Emissivity(data):\n        ...     return (\n        ...         data[\"gas\", \"density\"]\n        ...         * data[\"gas\", \"density\"]\n        ...         * np.sqrt(data[\"gas\", \"temperature\"])\n        ...     )\n        >>> ds.add_field(\n        ...     (\"gas\", \"emissivity\"),\n        ...     function=_Emissivity,\n        ...     sampling_type=\"cell\",\n        ...     units=r\"g**2*sqrt(K)/cm**6\",\n        ... )\n        >>> for i, r in enumerate(rhos):\n        ...     surf = ds.surface(sp, \"density\", r)\n        ...     surf.export_obj(\n        ...         \"my_galaxy\",\n        ...         transparency=trans[i],\n        ...         color_field=\"temperature\",\n        ...         emit_field=\"emissivity\",\n        ...         plot_index=i,\n        ...     )\n\n        \"\"\"\n        if color_map is None:\n            color_map = ytcfg.get(\"yt\", \"default_colormap\")\n        if self.vertices is None:\n            if color_field is not None:\n                self.get_data(color_field, \"face\")\n        elif color_field is not None:\n            if color_field not in self.field_data:\n                self[color_field]\n        if color_field is None:\n            self.get_data(self.surface_field, \"face\")\n        if emit_field is not None:\n            if color_field not in self.field_data:\n                self[emit_field]\n        only_on_root(\n            self._export_obj,\n            filename,\n            transparency,\n            dist_fac,\n            color_field,\n            emit_field,\n            color_map,\n            color_log,\n            emit_log,\n            plot_index,\n            color_field_max,\n            color_field_min,\n            emit_field_max,\n            emit_field_min,\n        )\n\n    def _color_samples_obj(\n        self,\n        cs,\n        em,\n        color_log,\n        emit_log,\n        color_map,\n        arr,\n        color_field_max,\n        color_field_min,\n        color_field,\n        emit_field_max,\n        emit_field_min,\n        emit_field,\n    ):  # this now holds for obj files\n        if color_field is not None:\n            if color_log:\n                cs = np.log10(cs)\n        if emit_field is not None:\n            if emit_log:\n                em = np.log10(em)\n        if color_field is not None:\n            if color_field_min is None:\n                cs = [float(field) for field in cs]\n                cs = np.array(cs)\n                mi = cs.min()\n            else:\n                mi = color_field_min\n                if color_log:\n                    mi = np.log10(mi)\n            if color_field_max is None:\n                cs = [float(field) for field in cs]\n                cs = np.array(cs)\n                ma = cs.max()\n            else:\n                ma = color_field_max\n                if color_log:\n                    ma = np.log10(ma)\n            cs = (cs - mi) / (ma - mi)\n        else:\n            cs[:] = 1.0\n        # to get color indices for OBJ formatting\n        lut = get_colormap_lut(color_map)\n\n        x = np.mgrid[0.0 : 1.0 : lut[0].shape[0] * 1j]\n        arr[\"cind\"][:] = (np.interp(cs, x, x) * (lut[0].shape[0] - 1)).astype(\"uint8\")\n        # now, get emission\n        if emit_field is not None:\n            if emit_field_min is None:\n                em = [float(field) for field in em]\n                em = np.array(em)\n                emi = em.min()\n            else:\n                emi = emit_field_min\n                if emit_log:\n                    emi = np.log10(emi)\n            if emit_field_max is None:\n                em = [float(field) for field in em]\n                em = np.array(em)\n                ema = em.max()\n            else:\n                ema = emit_field_max\n                if emit_log:\n                    ema = np.log10(ema)\n            em = (em - emi) / (ema - emi)\n            x = np.mgrid[0.0:255.0:2j]  # assume 1 emissivity per color\n            arr[\"emit\"][:] = (\n                np.interp(em, x, x)\n            ) * 2.0  # for some reason, max emiss = 2\n        else:\n            arr[\"emit\"][:] = 0.0\n\n    @parallel_root_only\n    def _export_obj(\n        self,\n        filename,\n        transparency,\n        dist_fac=None,\n        color_field=None,\n        emit_field=None,\n        color_map=None,\n        color_log=True,\n        emit_log=True,\n        plot_index=None,\n        color_field_max=None,\n        color_field_min=None,\n        emit_field_max=None,\n        emit_field_min=None,\n    ):\n        if color_map is None:\n            color_map = ytcfg.get(\"yt\", \"default_colormap\")\n        if plot_index is None:\n            plot_index = 0\n        if isinstance(filename, io.IOBase):\n            fobj = filename + \".obj\"\n            fmtl = filename + \".mtl\"\n        else:\n            if plot_index == 0:\n                fobj = open(filename + \".obj\", \"w\")\n                fmtl = open(filename + \".mtl\", \"w\")\n                cc = 1\n            else:\n                # read in last vertex\n                linesave = \"\"\n                for line in fileinput.input(filename + \".obj\"):\n                    if line[0] == \"f\":\n                        linesave = line\n                p = [m.start() for m in finditer(\" \", linesave)]\n                cc = int(linesave[p[len(p) - 1] :]) + 1\n                fobj = open(filename + \".obj\", \"a\")\n                fmtl = open(filename + \".mtl\", \"a\")\n        ftype = [(\"cind\", \"uint8\"), (\"emit\", \"float\")]\n        vtype = [(\"x\", \"float\"), (\"y\", \"float\"), (\"z\", \"float\")]\n        if plot_index == 0:\n            fobj.write(\"# yt OBJ file\\n\")\n            fobj.write(\"# www.yt-project.org\\n\")\n            fobj.write(\n                f\"mtllib {filename}.mtl\\n\\n\"\n            )  # use this material file for the faces\n            fmtl.write(\"# yt MLT file\\n\")\n            fmtl.write(\"# www.yt-project.org\\n\\n\")\n        # (0) formulate vertices\n        nv = self.vertices.shape[1]  # number of groups of vertices\n        f = np.empty(\n            nv // self.vertices.shape[0], dtype=ftype\n        )  # store sets of face colors\n        v = np.empty(nv, dtype=vtype)  # stores vertices\n        if color_field is not None:\n            cs = self[color_field]\n        else:\n            cs = np.empty(self.vertices.shape[1] // self.vertices.shape[0])\n        if emit_field is not None:\n            em = self[emit_field]\n        else:\n            em = np.empty(self.vertices.shape[1] // self.vertices.shape[0])\n        self._color_samples_obj(\n            cs,\n            em,\n            color_log,\n            emit_log,\n            color_map,\n            f,\n            color_field_max,\n            color_field_min,\n            color_field,\n            emit_field_max,\n            emit_field_min,\n            emit_field,\n        )  # map color values to color scheme\n\n        lut = get_colormap_lut(color_map)\n\n        # interpolate emissivity to enumerated colors\n        emiss = np.interp(\n            np.mgrid[0 : lut[0].shape[0]], np.mgrid[0 : len(cs)], f[\"emit\"][:]\n        )\n        if dist_fac is None:  # then normalize by bounds\n            DLE = self.pf.domain_left_edge\n            DRE = self.pf.domain_right_edge\n            bounds = [(DLE[i], DRE[i]) for i in range(3)]\n            for i, ax in enumerate(\"xyz\"):\n                # Do the bounds first since we cast to f32\n                tmp = self.vertices[i, :]\n                np.subtract(tmp, bounds[i][0], tmp)\n                w = bounds[i][1] - bounds[i][0]\n                np.divide(tmp, w, tmp)\n                np.subtract(tmp, 0.5, tmp)  # Center at origin.\n                v[ax][:] = tmp\n        else:\n            for i, ax in enumerate(\"xyz\"):\n                tmp = self.vertices[i, :]\n                np.divide(tmp, dist_fac, tmp)\n                v[ax][:] = tmp\n        # (1) write all colors per surface to mtl file\n        for i in range(0, lut[0].shape[0]):\n            omname = f\"material_{i}_{plot_index}\"  # name of the material\n            fmtl.write(\n                f\"newmtl {omname}\\n\"\n            )  # the specific material (color) for this face\n            fmtl.write(f\"Ka {0.0:.6f} {0.0:.6f} {0.0:.6f}\\n\")  # ambient color, keep off\n            fmtl.write(\n                f\"Kd {lut[0][i]:.6f} {lut[1][i]:.6f} {lut[2][i]:.6f}\\n\"\n            )  # color of face\n            fmtl.write(\n                f\"Ks {0.0:.6f} {0.0:.6f} {0.0:.6f}\\n\"\n            )  # specular color, keep off\n            fmtl.write(f\"d {transparency:.6f}\\n\")  # transparency\n            fmtl.write(f\"em {emiss[i]:.6f}\\n\")  # emissivity per color\n            fmtl.write(\"illum 2\\n\")  # not relevant, 2 means highlights on?\n            fmtl.write(f\"Ns {0:.6f}\\n\\n\")  # keep off, some other specular thing\n        # (2) write vertices\n        for i in range(0, self.vertices.shape[1]):\n            fobj.write(f\"v {v['x'][i]:.6f} {v['y'][i]:.6f} {v['z'][i]:.6f}\\n\")\n        fobj.write(\"#done defining vertices\\n\\n\")\n        # (3) define faces and materials for each face\n        for i in range(0, self.triangles.shape[0]):\n            omname = f\"material_{f['cind'][i]}_{plot_index}\"  # which color to use\n            fobj.write(\n                f\"usemtl {omname}\\n\"\n            )  # which material to use for this face (color)\n            fobj.write(f\"f {cc} {cc + 1} {cc + 2}\\n\\n\")  # vertices to color\n            cc = cc + 3\n        fmtl.close()\n        fobj.close()\n\n    def export_blender(\n        self,\n        transparency=1.0,\n        dist_fac=None,\n        color_field=None,\n        emit_field=None,\n        color_map=None,\n        color_log=True,\n        emit_log=True,\n        plot_index=None,\n        color_field_max=None,\n        color_field_min=None,\n        emit_field_max=None,\n        emit_field_min=None,\n    ):\n        r\"\"\"This exports the surface to the OBJ format, suitable for visualization\n        in many different programs (e.g., Blender).  NOTE: this exports an .obj file\n        and an .mtl file, both with the general 'filename' as a prefix.\n        The .obj file points to the .mtl file in its header, so if you move the 2\n        files, make sure you change the .obj header to account for this. ALSO NOTE:\n        the emit_field needs to be a combination of the other 2 fields used to\n        have the emissivity track with the color.\n\n        Parameters\n        ----------\n        transparency : float\n            This gives the transparency of the output surface plot.  Values\n            from 0.0 (invisible) to 1.0 (opaque).\n        dist_fac : float\n            Divide the axes distances by this amount.\n        color_field : string\n            Should a field be sample and colormapped?\n        emit_field : string\n            Should we track the emissivity of a field?\n              NOTE: this should be a combination of the other 2 fields being used.\n        color_map : string\n            Which color map should be applied?\n        color_log : bool\n            Should the color field be logged before being mapped?\n        emit_log : bool\n            Should the emitting field be logged before being mapped?\n        plot_index : integer\n            Index of plot for multiple plots.  If none, then only 1 plot.\n        color_field_max : float\n            Maximum value of the color field across all surfaces.\n        color_field_min : float\n            Minimum value of the color field across all surfaces.\n        emit_field_max : float\n            Maximum value of the emitting field across all surfaces.\n        emit_field_min : float\n            Minimum value of the emitting field across all surfaces.\n\n        Examples\n        --------\n\n        >>> sp = ds.sphere(\"max\", (10, \"kpc\"))\n        >>> trans = 1.0\n        >>> surf = ds.surface(sp, (\"gas\", \"density\"), 5e-27)\n        >>> surf.export_obj(\"my_galaxy\", transparency=trans)\n\n        >>> sp = ds.sphere(\"max\", (10, \"kpc\"))\n        >>> mi, ma = sp.quantities.extrema(\"temperature\")[0]\n        >>> rhos = [1e-24, 1e-25]\n        >>> trans = [0.5, 1.0]\n        >>> for i, r in enumerate(rhos):\n        ...     surf = ds.surface(sp, \"density\", r)\n        ...     surf.export_obj(\n        ...         \"my_galaxy\",\n        ...         transparency=trans[i],\n        ...         color_field=\"temperature\",\n        ...         plot_index=i,\n        ...         color_field_max=ma,\n        ...         color_field_min=mi,\n        ...     )\n\n        >>> sp = ds.sphere(\"max\", (10, \"kpc\"))\n        >>> rhos = [1e-24, 1e-25]\n        >>> trans = [0.5, 1.0]\n        >>> def _Emissivity(data):\n        ...     return (\n        ...         data[\"gas\", \"density\"]\n        ...         * data[\"gas\", \"density\"]\n        ...         * np.sqrt(data[\"gas\", \"temperature\"])\n        ...     )\n        >>> ds.add_field((\"gas\", \"emissivity\"), function=_Emissivity, units=\"g / cm**6\")\n        >>> for i, r in enumerate(rhos):\n        ...     surf = ds.surface(sp, \"density\", r)\n        ...     surf.export_obj(\n        ...         \"my_galaxy\",\n        ...         transparency=trans[i],\n        ...         color_field=\"temperature\",\n        ...         emit_field=\"emissivity\",\n        ...         plot_index=i,\n        ...     )\n\n        \"\"\"\n        if color_map is None:\n            color_map = ytcfg.get(\"yt\", \"default_colormap\")\n        if self.vertices is None:\n            if color_field is not None:\n                self.get_data(color_field, \"face\")\n        elif color_field is not None:\n            if color_field not in self.field_data:\n                self[color_field]\n        if color_field is None:\n            self.get_data(self.surface_field, \"face\")\n        if emit_field is not None:\n            if color_field not in self.field_data:\n                self[emit_field]\n        fullverts, colors, alpha, emisses, colorindex = only_on_root(\n            self._export_blender,\n            transparency,\n            dist_fac,\n            color_field,\n            emit_field,\n            color_map,\n            color_log,\n            emit_log,\n            plot_index,\n            color_field_max,\n            color_field_min,\n            emit_field_max,\n            emit_field_min,\n        )\n        return fullverts, colors, alpha, emisses, colorindex\n\n    def _export_blender(\n        self,\n        transparency,\n        dist_fac=None,\n        color_field=None,\n        emit_field=None,\n        color_map=None,\n        color_log=True,\n        emit_log=True,\n        plot_index=None,\n        color_field_max=None,\n        color_field_min=None,\n        emit_field_max=None,\n        emit_field_min=None,\n    ):\n        if color_map is None:\n            color_map = ytcfg.get(\"yt\", \"default_colormap\")\n        if plot_index is None:\n            plot_index = 0\n        ftype = [(\"cind\", \"uint8\"), (\"emit\", \"float\")]\n        vtype = [(\"x\", \"float\"), (\"y\", \"float\"), (\"z\", \"float\")]\n        # (0) formulate vertices\n        nv = self.vertices.shape[1]  # number of groups of vertices\n        f = np.empty(\n            nv / self.vertices.shape[0], dtype=ftype\n        )  # store sets of face colors\n        v = np.empty(nv, dtype=vtype)  # stores vertices\n        if color_field is not None:\n            cs = self[color_field]\n        else:\n            cs = np.empty(self.vertices.shape[1] / self.vertices.shape[0])\n        if emit_field is not None:\n            em = self[emit_field]\n        else:\n            em = np.empty(self.vertices.shape[1] / self.vertices.shape[0])\n        self._color_samples_obj(\n            cs,\n            em,\n            color_log,\n            emit_log,\n            color_map,\n            f,\n            color_field_max,\n            color_field_min,\n            color_field,\n            emit_field_max,\n            emit_field_min,\n            emit_field,\n        )  # map color values to color scheme\n\n        lut = get_colormap_lut(color_map)\n\n        # interpolate emissivity to enumerated colors\n        emiss = np.interp(\n            np.mgrid[0 : lut[0].shape[0]], np.mgrid[0 : len(cs)], f[\"emit\"][:]\n        )\n        if dist_fac is None:  # then normalize by bounds\n            DLE = self.ds.domain_left_edge\n            DRE = self.ds.domain_right_edge\n            bounds = [(DLE[i], DRE[i]) for i in range(3)]\n            for i, ax in enumerate(\"xyz\"):\n                # Do the bounds first since we cast to f32\n                tmp = self.vertices[i, :]\n                np.subtract(tmp, bounds[i][0], tmp)\n                w = bounds[i][1] - bounds[i][0]\n                np.divide(tmp, w, tmp)\n                np.subtract(tmp, 0.5, tmp)  # Center at origin.\n                v[ax][:] = tmp\n        else:\n            for i, ax in enumerate(\"xyz\"):\n                tmp = self.vertices[i, :]\n                np.divide(tmp, dist_fac, tmp)\n                v[ax][:] = tmp\n        return v, lut, transparency, emiss, f[\"cind\"]\n\n    def export_ply(\n        self,\n        filename,\n        bounds=None,\n        color_field=None,\n        color_map=None,\n        color_log=True,\n        sample_type=\"face\",\n        no_ghost=False,\n        *,\n        color_field_max=None,\n        color_field_min=None,\n    ):\n        r\"\"\"This exports the surface to the PLY format, suitable for visualization\n        in many different programs (e.g., MeshLab).\n\n        Parameters\n        ----------\n        filename : string\n            The file this will be exported to.  This cannot be a file-like object.\n        bounds : list of tuples\n            The bounds the vertices will be normalized to.  This is of the format:\n            [(xmin, xmax), (ymin, ymax), (zmin, zmax)].  Defaults to the full\n            domain.\n        color_field : string\n            Should a field be sample and colormapped?\n        color_map : string\n            Which color map should be applied?\n        color_log : bool\n            Should the color field be logged before being mapped?\n        color_field_max : float\n            Maximum value of the color field across all surfaces.\n        color_field_min : float\n            Minimum value of the color field across all surfaces.\n\n        Examples\n        --------\n\n        >>> from yt.units import kpc\n        >>> sp = ds.sphere(\"max\", (10, \"kpc\"))\n        >>> surf = ds.surface(sp, (\"gas\", \"density\"), 5e-27)\n        >>> print(surf[\"gas\", \"temperature\"])\n        >>> print(surf.vertices)\n        >>> bounds = [\n        ...     (sp.center[i] - 5.0 * kpc, sp.center[i] + 5.0 * kpc) for i in range(3)\n        ... ]\n        >>> surf.export_ply(\"my_galaxy.ply\", bounds=bounds)\n        \"\"\"\n        if color_map is None:\n            color_map = ytcfg.get(\"yt\", \"default_colormap\")\n        if self.vertices is None:\n            self.get_data(color_field, sample_type, no_ghost=no_ghost)\n        elif color_field is not None:\n            if sample_type == \"face\" and color_field not in self.field_data:\n                self[color_field]\n            elif sample_type == \"vertex\" and color_field not in self.vertex_samples:\n                self.get_data(color_field, sample_type, no_ghost=no_ghost)\n        self._export_ply(\n            filename,\n            bounds,\n            color_field,\n            color_map,\n            color_log,\n            sample_type,\n            color_field_max=color_field_max,\n            color_field_min=color_field_min,\n        )\n\n    def _color_samples(\n        self,\n        cs,\n        color_log,\n        color_map,\n        arr,\n        *,\n        color_field_max=None,\n        color_field_min=None,\n    ):\n        cs = np.asarray(cs)\n        if color_log:\n            cs = np.log10(cs)\n        if color_field_min is None:\n            mi = cs.min()\n        else:\n            mi = color_field_min\n            if color_log:\n                mi = np.log10(mi)\n        if color_field_max is None:\n            ma = cs.max()\n        else:\n            ma = color_field_max\n            if color_log:\n                ma = np.log10(ma)\n        cs = (cs - mi) / (ma - mi)\n        from yt.visualization.image_writer import map_to_colors\n\n        cs = map_to_colors(cs, color_map)\n        arr[\"red\"][:] = cs[0, :, 0]\n        arr[\"green\"][:] = cs[0, :, 1]\n        arr[\"blue\"][:] = cs[0, :, 2]\n\n    @parallel_root_only\n    def _export_ply(\n        self,\n        filename,\n        bounds=None,\n        color_field=None,\n        color_map=None,\n        color_log=True,\n        sample_type=\"face\",\n        *,\n        color_field_max=None,\n        color_field_min=None,\n    ):\n        if color_map is None:\n            color_map = ytcfg.get(\"yt\", \"default_colormap\")\n        if hasattr(filename, \"read\"):\n            f = filename\n        else:\n            f = open(filename, \"wb\")\n        if bounds is None:\n            DLE = self.ds.domain_left_edge\n            DRE = self.ds.domain_right_edge\n            bounds = [(DLE[i], DRE[i]) for i in range(3)]\n        elif any(not all(isinstance(be, YTArray) for be in b) for b in bounds):\n            bounds = [\n                tuple(\n                    be if isinstance(be, YTArray) else self.ds.quan(be, \"code_length\")\n                    for be in b\n                )\n                for b in bounds\n            ]\n        nv = self.vertices.shape[1]\n        vs = [\n            (\"x\", \"<f\"),\n            (\"y\", \"<f\"),\n            (\"z\", \"<f\"),\n            (\"red\", \"uint8\"),\n            (\"green\", \"uint8\"),\n            (\"blue\", \"uint8\"),\n        ]\n        fs = [\n            (\"ni\", \"uint8\"),\n            (\"v1\", \"<i4\"),\n            (\"v2\", \"<i4\"),\n            (\"v3\", \"<i4\"),\n            (\"red\", \"uint8\"),\n            (\"green\", \"uint8\"),\n            (\"blue\", \"uint8\"),\n        ]\n        f.write(b\"ply\\n\")\n        f.write(b\"format binary_little_endian 1.0\\n\")\n        line = f\"element vertex {nv}\\n\"\n        f.write(line.encode(\"latin-1\"))\n        f.write(b\"property float x\\n\")\n        f.write(b\"property float y\\n\")\n        f.write(b\"property float z\\n\")\n        if color_field is not None and sample_type == \"vertex\":\n            f.write(b\"property uchar red\\n\")\n            f.write(b\"property uchar green\\n\")\n            f.write(b\"property uchar blue\\n\")\n            v = np.empty(self.vertices.shape[1], dtype=vs)\n            cs = self.vertex_samples[color_field]\n            self._color_samples(\n                cs,\n                color_log,\n                color_map,\n                v,\n                color_field_max=color_field_max,\n                color_field_min=color_field_min,\n            )\n        else:\n            v = np.empty(self.vertices.shape[1], dtype=vs[:3])\n        line = f\"element face {int(nv / 3)}\\n\"\n        f.write(line.encode(\"latin-1\"))\n        f.write(b\"property list uchar int vertex_indices\\n\")\n        if color_field is not None and sample_type == \"face\":\n            f.write(b\"property uchar red\\n\")\n            f.write(b\"property uchar green\\n\")\n            f.write(b\"property uchar blue\\n\")\n            # Now we get our samples\n            cs = self[color_field]\n            arr = np.empty(cs.shape[0], dtype=np.dtype(fs))\n            self._color_samples(\n                cs,\n                color_log,\n                color_map,\n                arr,\n                color_field_max=color_field_max,\n                color_field_min=color_field_min,\n            )\n        else:\n            arr = np.empty(nv // 3, np.dtype(fs[:-3]))\n        for i, ax in enumerate(\"xyz\"):\n            # Do the bounds first since we cast to f32\n            tmp = self.vertices[i, :]\n            np.subtract(tmp, bounds[i][0], tmp)\n            w = bounds[i][1] - bounds[i][0]\n            np.divide(tmp, w, tmp)\n            np.subtract(tmp, 0.5, tmp)  # Center at origin.\n            v[ax][:] = tmp\n        f.write(b\"end_header\\n\")\n        v.tofile(f)\n        arr[\"ni\"][:] = 3\n        vi = np.arange(nv, dtype=\"<i\").reshape(nv // 3, 3)\n        arr[\"v1\"][:] = vi[:, 0]\n        arr[\"v2\"][:] = vi[:, 1]\n        arr[\"v3\"][:] = vi[:, 2]\n        arr.tofile(f)\n        if filename is not f:\n            f.close()\n\n    def export_sketchfab(\n        self,\n        title,\n        description,\n        api_key=None,\n        color_field=None,\n        color_map=None,\n        color_log=True,\n        bounds=None,\n        no_ghost=False,\n        *,\n        color_field_max=None,\n        color_field_min=None,\n    ):\n        r\"\"\"This exports Surfaces to SketchFab.com, where they can be viewed\n        interactively in a web browser.\n\n        SketchFab.com is a proprietary web service that provides WebGL\n        rendering of models.  This routine will use temporary files to\n        construct a compressed binary representation (in .PLY format) of the\n        Surface and any optional fields you specify and upload it to\n        SketchFab.com.  It requires an API key, which can be found on your\n        SketchFab.com dashboard.  You can either supply the API key to this\n        routine directly or you can place it in the variable\n        \"sketchfab_api_key\" in your ~/.config/yt/yt.toml file.  This function is\n        parallel-safe.\n\n        Parameters\n        ----------\n        title : string\n            The title for the model on the website\n        description : string\n            How you want the model to be described on the website\n        api_key : string\n            Optional; defaults to using the one in the config file\n        color_field : string\n            If specified, the field by which the surface will be colored\n        color_map : string\n            The name of the color map to use to map the color field\n        color_log : bool\n            Should the field be logged before being mapped to RGB?\n        bounds : list of tuples\n            [ (xmin, xmax), (ymin, ymax), (zmin, zmax) ] within which the model\n            will be scaled and centered.  Defaults to the full domain.\n        color_field_max : float\n            Maximum value of the color field across all surfaces.\n        color_field_min : float\n            Minimum value of the color field across all surfaces.\n\n        Returns\n        -------\n        URL : string\n            The URL at which your model can be viewed.\n\n        Examples\n        --------\n\n        >>> from yt.units import kpc\n        >>> dd = ds.sphere(\"max\", (200, \"kpc\"))\n        >>> rho = 5e-27\n        >>> bounds = [\n        ...     (dd.center[i] - 100.0 * kpc, dd.center[i] + 100.0 * kpc)\n        ...     for i in range(3)\n        ... ]\n        ...\n        >>> surf = ds.surface(dd, (\"gas\", \"density\"), rho)\n        >>> rv = surf.export_sketchfab(\n        ...     title=\"Testing Upload\",\n        ...     description=\"A simple test of the uploader\",\n        ...     color_field=\"temperature\",\n        ...     color_map=\"hot\",\n        ...     color_log=True,\n        ...     bounds=bounds,\n        ... )\n        ...\n        \"\"\"\n        if color_map is None:\n            color_map = ytcfg.get(\"yt\", \"default_colormap\")\n        api_key = api_key or ytcfg.get(\"yt\", \"sketchfab_api_key\")\n        if api_key in (None, \"None\"):\n            raise YTNoAPIKey(\"SketchFab.com\", \"sketchfab_api_key\")\n\n        ply_file = TemporaryFile()\n        self.export_ply(\n            ply_file,\n            bounds,\n            color_field,\n            color_map,\n            color_log,\n            sample_type=\"vertex\",\n            no_ghost=no_ghost,\n            color_field_max=color_field_max,\n            color_field_min=color_field_min,\n        )\n        ply_file.seek(0)\n        # Greater than ten million vertices and we throw an error but dump\n        # to a file.\n        if self.vertices.shape[1] > 1e7:\n            tfi = 0\n            fn = f\"temp_model_{tfi:03}.ply\"\n            while os.path.exists(fn):\n                fn = f\"temp_model_{tfi:03}.ply\"\n                tfi += 1\n            open(fn, \"wb\").write(ply_file.read())\n            raise YTTooManyVertices(self.vertices.shape[1], fn)\n\n        zfs = NamedTemporaryFile(suffix=\".zip\")\n        with zipfile.ZipFile(zfs, \"w\", zipfile.ZIP_DEFLATED) as zf:\n            zf.writestr(\"yt_export.ply\", ply_file.read())\n        zfs.seek(0)\n\n        zfs.seek(0)\n        data = {\n            \"token\": api_key,\n            \"name\": title,\n            \"description\": description,\n            \"tags\": \"yt\",\n        }\n        files = {\"modelFile\": zfs}\n        upload_id = self._upload_to_sketchfab(data, files)\n        upload_id = self.comm.mpi_bcast(upload_id, root=0)\n        return upload_id\n\n    @parallel_root_only\n    def _upload_to_sketchfab(self, data, files):\n        from yt.utilities.on_demand_imports import _requests as requests\n\n        SKETCHFAB_DOMAIN = \"sketchfab.com\"\n        SKETCHFAB_API_URL = f\"https://api.{SKETCHFAB_DOMAIN}/v2/models\"\n        SKETCHFAB_MODEL_URL = f\"https://{SKETCHFAB_DOMAIN}/models/\"\n\n        try:\n            r = requests.post(SKETCHFAB_API_URL, data=data, files=files, verify=False)\n        except requests.exceptions.RequestException:\n            mylog.exception(\"An error has occurred\")\n            return\n\n        result = r.json()\n\n        if r.status_code != requests.codes.created:\n            mylog.error(\"Upload to SketchFab failed with error: %s\", result)\n            return\n\n        model_uid = result[\"uid\"]\n        model_url = SKETCHFAB_MODEL_URL + model_uid\n        if model_uid:\n            mylog.info(\"Model uploaded to: %s\", model_url)\n        else:\n            mylog.error(\"Problem uploading.\")\n\n        return model_uid\n\n\nclass YTOctree(YTSelectionContainer3D):\n    \"\"\"A 3D region with all the data filled into an octree. This container\n    will deposit particle fields onto octs using a kernel and SPH smoothing.\n    The octree is built in a depth-first fashion. Depth-first search (DFS)\n    means that tree starts refining at the root node (this is the largest node\n    which contains every particles) and refines as far as possible along each\n    branch before backtracking.\n\n    Parameters\n    ----------\n    right_edge : array_like\n        The right edge of the region to be extracted.  Specify units by supplying\n        a YTArray, otherwise code length units are assumed.\n    left_edge : array_like\n        The left edge of the region to be extracted.  Specify units by supplying\n        a YTArray, otherwise code length units are assumed.\n    n_ref: int\n        This is the maximum number of particles per leaf in the resulting\n        octree.\n    ptypes: list\n        This is the type of particles to include when building the tree. This\n        will default to all particles.\n\n    Examples\n    --------\n\n    octree = ds.octree(n_ref=64)\n    x_positions_of_cells = octree['index', 'x']\n    y_positions_of_cells = octree['index', 'y']\n    z_positions_of_cells = octree['index', 'z']\n    density_of_gas_in_cells = octree['gas', 'density']\n    \"\"\"\n\n    _spatial = True\n    _type_name = \"octree\"\n\n    _con_args = (\"left_edge\", \"right_edge\", \"n_ref\")\n    _container_fields = (\n        (\"index\", \"dx\"),\n        (\"index\", \"dy\"),\n        (\"index\", \"dz\"),\n        (\"index\", \"x\"),\n        (\"index\", \"y\"),\n        (\"index\", \"z\"),\n        (\"index\", \"depth\"),\n        (\"index\", \"refined\"),\n        (\"index\", \"sizes\"),\n        (\"index\", \"positions\"),\n    )\n\n    def __init__(\n        self,\n        left_edge=None,\n        right_edge=None,\n        n_ref=32,\n        ptypes=None,\n        ds=None,\n        field_parameters=None,\n    ):\n        if field_parameters is None:\n            center = None\n        else:\n            center = field_parameters.get(\"center\", None)\n        YTSelectionContainer3D.__init__(self, center, ds, field_parameters)\n\n        self.left_edge = self._sanitize_edge(left_edge, ds.domain_left_edge)\n        self.right_edge = self._sanitize_edge(right_edge, ds.domain_right_edge)\n        self.n_ref = n_ref\n        self.ptypes = self._sanitize_ptypes(ptypes)\n\n        self._setup_data_source()\n        self.tree\n\n    def _generate_tree(self):\n        positions = []\n        for ptype in self.ptypes:\n            positions.append(\n                self._data_source[ptype, \"particle_position\"].in_units(\"code_length\").d\n            )\n\n        positions = (\n            np.concatenate(positions) if len(positions) > 0 else np.array(positions)\n        )\n        if not positions.size:\n            mylog.info(\"No particles found!\")\n            self._octree = None\n            return\n\n        mylog.info(\"Allocating Octree for %s particles\", positions.shape[0])\n        self._octree = CyOctree(\n            positions,\n            left_edge=self.left_edge.to(\"code_length\").d,\n            right_edge=self.right_edge.to(\"code_length\").d,\n            n_ref=self.n_ref,\n        )\n        mylog.info(\"Allocated %s nodes in octree\", self._octree.num_nodes)\n        mylog.info(\"Octree bound %s particles\", self._octree.bound_particles)\n\n        # Now we store the index data about the octree in the python container\n        ds = self.ds\n        pos = ds.arr(self._octree.node_positions, \"code_length\")\n        self[\"index\", \"positions\"] = pos\n        self[\"index\", \"x\"] = pos[:, 0]\n        self[\"index\", \"y\"] = pos[:, 1]\n        self[\"index\", \"z\"] = pos[:, 2]\n        self[\"index\", \"refined\"] = self._octree.node_refined\n\n        sizes = ds.arr(self._octree.node_sizes, \"code_length\")\n        self[\"index\", \"sizes\"] = sizes\n        self[\"index\", \"dx\"] = sizes[:, 0]\n        self[\"index\", \"dy\"] = sizes[:, 1]\n        self[\"index\", \"dz\"] = sizes[:, 2]\n        self[\"index\", \"depth\"] = self._octree.node_depth\n\n    @property\n    def tree(self):\n        \"\"\"\n        The Cython+Python octree instance\n        \"\"\"\n        if hasattr(self, \"_octree\"):\n            return self._octree\n\n        self._generate_tree()\n        return self._octree\n\n    @property\n    def sph_smoothing_style(self):\n        smoothing_style = getattr(self.ds, \"sph_smoothing_style\", \"scatter\")\n        return smoothing_style\n\n    @property\n    def sph_normalize(self):\n        normalize = getattr(self.ds, \"use_sph_normalization\", \"normalize\")\n        return normalize\n\n    @property\n    def sph_num_neighbors(self):\n        num_neighbors = getattr(self.ds, \"num_neighbors\", 32)\n        return num_neighbors\n\n    def _sanitize_ptypes(self, ptypes):\n        if ptypes is None:\n            return [\"all\"]\n\n        if not isinstance(ptypes, list):\n            ptypes = [ptypes]\n\n        for ptype in ptypes:\n            if ptype not in self.ds.particle_types:\n                mess = f\"{ptype} not found. Particle type must \"\n                mess += \"be in the dataset!\"\n                raise TypeError(mess)\n\n        return ptypes\n\n    def _setup_data_source(self):\n        mylog.info(\n            (\n                \"Allocating octree with spatial range \"\n                \"[%.4e, %.4e, %.4e] code_length to \"\n                \"[%.4e, %.4e, %.4e] code_length\"\n            ),\n            *self.left_edge.to(\"code_length\").d,\n            *self.right_edge.to(\"code_length\").d,\n        )\n        self._data_source = self.ds.region(self.center, self.left_edge, self.right_edge)\n\n    def _sanitize_edge(self, edge, default):\n        if edge is None:\n            return default.copy()\n        if not is_sequence(edge):\n            edge = [edge] * len(self.ds.domain_left_edge)\n        if len(edge) != len(self.ds.domain_left_edge):\n            raise RuntimeError(\n                \"Length of edges must match the dimensionality of the dataset\"\n            )\n        if hasattr(edge, \"units\"):\n            if edge.units.registry is self.ds.unit_registry:\n                return edge\n            edge_units = edge.units\n        else:\n            edge_units = \"code_length\"\n        return self.ds.arr(edge, edge_units)\n\n    def get_data(self, fields=None):\n        if fields is None:\n            return\n\n        if hasattr(self.ds, \"_sph_ptypes\"):\n            sph_ptypes = self.ds._sph_ptypes\n        else:\n            sph_ptypes = tuple(\n                value\n                for value in self.ds.particle_types_raw\n                if value in [\"PartType0\", \"Gas\", \"gas\", \"io\"]\n            )\n\n        if len(sph_ptypes) == 0:\n            raise RuntimeError\n        smoothing_style = self.sph_smoothing_style\n        normalize = self.sph_normalize\n\n        if fields[0] in sph_ptypes:\n            units = self.ds._get_field_info(fields).units\n            if smoothing_style == \"scatter\":\n                self._scatter_smooth(fields, units, normalize)\n            else:\n                self._gather_smooth(fields, units, normalize)\n        elif fields[0] == \"index\":\n            return self[fields]\n        else:\n            raise NotImplementedError\n\n    def _gather_smooth(self, fields, units, normalize):\n        buff = np.zeros(self.tree.num_nodes, dtype=\"float64\")\n\n        # Again, attempt to load num_neighbors from the octree\n        num_neighbors = self.sph_num_neighbors\n\n        # For the gather approach we load up all of the data, this like other\n        # gather approaches is not memory conservative but with spatial chunking\n        # this can be fixed\n        fields_to_get = [\n            \"particle_position\",\n            \"density\",\n            \"particle_mass\",\n            \"smoothing_length\",\n            fields[1],\n        ]\n        all_fields = all_data(self.ds, fields[0], fields_to_get, kdtree=True)\n\n        interpolate_sph_positions_gather(\n            buff,\n            all_fields[\"particle_position\"],\n            self[\"index\", \"positions\"],\n            all_fields[\"smoothing_length\"],\n            all_fields[\"particle_mass\"],\n            all_fields[\"density\"],\n            all_fields[fields[1]].in_units(units),\n            self.ds.index.kdtree,\n            use_normalization=normalize,\n            num_neigh=num_neighbors,\n        )\n\n        self[fields] = self.ds.arr(buff[~self[\"index\", \"refined\"]], units)\n\n    def _scatter_smooth(self, fields, units, normalize):\n        from tqdm import tqdm\n\n        buff = np.zeros(self.tree.num_nodes, dtype=\"float64\")\n\n        if normalize:\n            buff_den = np.zeros(buff.shape[0], dtype=\"float64\")\n        else:\n            buff_den = np.empty(0)\n\n        ptype = fields[0]\n        pbar = tqdm(desc=f\"Interpolating (scatter) SPH field {fields[0]}\")\n        for chunk in self._data_source.chunks([fields], \"io\"):\n            px = chunk[ptype, \"particle_position_x\"].to(\"code_length\").d\n            py = chunk[ptype, \"particle_position_y\"].to(\"code_length\").d\n            pz = chunk[ptype, \"particle_position_z\"].to(\"code_length\").d\n            hsml = chunk[ptype, \"smoothing_length\"].to(\"code_length\").d\n            pmass = chunk[ptype, \"particle_mass\"].to(\"code_mass\").d\n            pdens = chunk[ptype, \"density\"].to(\"code_mass/code_length**3\").d\n            field_quantity = chunk[fields].to(units).d\n\n            if px.shape[0] > 0:\n                self.tree.interpolate_sph_cells(\n                    buff,\n                    buff_den,\n                    px,\n                    py,\n                    pz,\n                    pmass,\n                    pdens,\n                    hsml,\n                    field_quantity,\n                    use_normalization=normalize,\n                )\n\n            pbar.update(1)\n        pbar.close()\n\n        if normalize:\n            normalization_1d_utility(buff, buff_den)\n\n        self[fields] = self.ds.arr(buff[~self[\"index\", \"refined\"]], units)\n"
  },
  {
    "path": "yt/data_objects/data_containers.py",
    "content": "import abc\nimport weakref\nfrom collections import defaultdict\nfrom contextlib import contextmanager\nfrom typing import TYPE_CHECKING, Optional\n\nimport numpy as np\nfrom unyt import unyt_array\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._typing import AnyFieldKey, FieldKey, FieldName\nfrom yt.data_objects.field_data import YTFieldData\nfrom yt.data_objects.profiles import create_profile\nfrom yt.fields.field_exceptions import NeedsGridType\nfrom yt.frontends.ytdata.utilities import save_as_dataset\nfrom yt.funcs import get_output_filename, iter_fields, mylog, parse_center_array\nfrom yt.units._numpy_wrapper_functions import uconcatenate\nfrom yt.utilities.amr_kdtree.api import AMRKDTree\nfrom yt.utilities.exceptions import (\n    YTCouldNotGenerateField,\n    YTException,\n    YTFieldNotFound,\n    YTFieldTypeNotFound,\n    YTNonIndexedDataContainer,\n    YTSpatialFieldUnitError,\n)\nfrom yt.utilities.object_registries import data_object_registry\nfrom yt.utilities.on_demand_imports import _firefly as firefly\nfrom yt.utilities.parameter_file_storage import ParameterFileStore\n\nif TYPE_CHECKING:\n    from yt.data_objects.static_output import Dataset\n\n\ndef sanitize_weight_field(ds, field, weight):\n    if weight is None:\n        field_object = ds._get_field_info(field)\n        if field_object.sampling_type == \"particle\":\n            if field_object.name[0] == \"gas\":\n                ptype = ds._sph_ptypes[0]\n            else:\n                ptype = field_object.name[0]\n            weight_field = (ptype, \"particle_ones\")\n        else:\n            weight_field = (\"index\", \"ones\")\n    else:\n        weight_field = weight\n    return weight_field\n\n\ndef _get_ipython_key_completion(ds):\n    # tuple-completion (ftype, fname) was added in IPython 8.0.0\n    # with earlier versions, completion works with fname only\n    # this implementation should work transparently with all IPython versions\n    tuple_keys = ds.field_list + ds.derived_field_list\n    fnames = list({k[1] for k in tuple_keys})\n    return tuple_keys + fnames\n\n\nclass YTDataContainer(abc.ABC):\n    \"\"\"\n    Generic YTDataContainer container.  By itself, will attempt to\n    generate field, read fields (method defined by derived classes)\n    and deal with passing back and forth field parameters.\n    \"\"\"\n\n    _chunk_info = None\n    _num_ghost_zones = 0\n    _con_args: tuple[str, ...] = ()\n    _skip_add = False\n    _container_fields: tuple[AnyFieldKey, ...] = ()\n    _tds_attrs: tuple[str, ...] = ()\n    _tds_fields: tuple[str, ...] = ()\n    _field_cache = None\n    _index = None\n    _key_fields: list[str]\n\n    def __init__(self, ds: Optional[\"Dataset\"], field_parameters) -> None:\n        \"\"\"\n        Typically this is never called directly, but only due to inheritance.\n        It associates a :class:`~yt.data_objects.static_output.Dataset` with the class,\n        sets its initial set of fields, and the remainder of the arguments\n        are passed as field_parameters.\n        \"\"\"\n        # ds is typically set in the new object type created in\n        # Dataset._add_object_class but it can also be passed as a parameter to the\n        # constructor, in which case it will override the default.\n        # This code ensures it is never not set.\n\n        self.ds: Dataset\n        if ds is not None:\n            self.ds = ds\n        else:\n            if not hasattr(self, \"ds\"):\n                raise RuntimeError(\n                    \"Error: ds must be set either through class type \"\n                    \"or parameter to the constructor\"\n                )\n\n        self._current_particle_type = \"all\"\n        self._current_fluid_type = self.ds.default_fluid_type\n        self.ds.objects.append(weakref.proxy(self))\n        mylog.debug(\"Appending object to %s (type: %s)\", self.ds, type(self))\n        self.field_data = YTFieldData()\n        if self.ds.unit_system.has_current_mks:\n            mag_unit = \"T\"\n        else:\n            mag_unit = \"G\"\n        self._default_field_parameters = {\n            \"center\": self.ds.arr(np.zeros(3, dtype=\"float64\"), \"cm\"),\n            \"bulk_velocity\": self.ds.arr(np.zeros(3, dtype=\"float64\"), \"cm/s\"),\n            \"bulk_magnetic_field\": self.ds.arr(np.zeros(3, dtype=\"float64\"), mag_unit),\n            \"normal\": self.ds.arr([0.0, 0.0, 1.0], \"\"),\n        }\n        if field_parameters is None:\n            field_parameters = {}\n        self._set_default_field_parameters()\n        for key, val in field_parameters.items():\n            self.set_field_parameter(key, val)\n\n    def __init_subclass__(cls, *args, **kwargs):\n        super().__init_subclass__(*args, **kwargs)\n        if hasattr(cls, \"_type_name\") and not cls._skip_add:\n            name = getattr(cls, \"_override_selector_name\", cls._type_name)\n            data_object_registry[name] = cls\n\n    @property\n    def pf(self):\n        return getattr(self, \"ds\", None)\n\n    @property\n    def index(self):\n        if self._index is not None:\n            return self._index\n        self._index = self.ds.index\n        return self._index\n\n    def _debug(self):\n        \"\"\"\n        When called from within a derived field, this will run pdb.  However,\n        during field detection, it will not.  This allows you to more easily\n        debug fields that are being called on actual objects.\n        \"\"\"\n        import pdb\n\n        pdb.set_trace()\n\n    def _set_default_field_parameters(self):\n        self.field_parameters = {}\n        for k, v in self._default_field_parameters.items():\n            self.set_field_parameter(k, v)\n\n    def _is_default_field_parameter(self, parameter):\n        if parameter not in self._default_field_parameters:\n            return False\n        return (\n            self._default_field_parameters[parameter]\n            is self.field_parameters[parameter]\n        )\n\n    def apply_units(self, arr, units):\n        try:\n            arr.units.registry = self.ds.unit_registry\n            return arr.to(units)\n        except AttributeError:\n            return self.ds.arr(arr, units=units)\n\n    def _first_matching_field(self, field: FieldName) -> FieldKey:\n        for ftype, fname in self.ds.derived_field_list:\n            if fname == field:\n                return (ftype, fname)\n\n        raise YTFieldNotFound(field, self.ds)\n\n    def _set_center(self, center):\n        if center is None:\n            self.center = None\n            return\n        else:\n            axis = getattr(self, \"axis\", None)\n            self.center = parse_center_array(center, ds=self.ds, axis=axis)\n            self.set_field_parameter(\"center\", self.center)\n\n    def get_field_parameter(self, name, default=None):\n        \"\"\"\n        This is typically only used by derived field functions, but\n        it returns parameters used to generate fields.\n        \"\"\"\n        if name in self.field_parameters:\n            return self.field_parameters[name]\n        else:\n            return default\n\n    def set_field_parameter(self, name, val):\n        \"\"\"\n        Here we set up dictionaries that get passed up and down and ultimately\n        to derived fields.\n        \"\"\"\n        self.field_parameters[name] = val\n\n    def has_field_parameter(self, name):\n        \"\"\"\n        Checks if a field parameter is set.\n        \"\"\"\n        return name in self.field_parameters\n\n    def clear_data(self, fields: list[AnyFieldKey] | AnyFieldKey | None = None):\n        \"\"\"\n        Clears out data from the YTDataContainer instance, freeing memory.\n\n        Parameters\n        ----------\n        fields : list[str] | str | None\n            The fields to clear. If None, all fields are cleared.\n        \"\"\"\n        if fields is None:\n            self.field_data.clear()\n            return\n\n        if isinstance(fields, (str, tuple)):\n            fields = [fields]\n\n        for field in fields:\n            self.field_data.pop(field, None)\n\n    def has_key(self, key):\n        \"\"\"\n        Checks if a data field already exists.\n        \"\"\"\n        return key in self.field_data\n\n    def keys(self):\n        return self.field_data.keys()\n\n    def _reshape_vals(self, arr):\n        return arr\n\n    def __getitem__(self, key):\n        \"\"\"\n        Returns a single field.  Will add if necessary.\n        \"\"\"\n        f = self._determine_fields([key])[0]\n        if f not in self.field_data and key not in self.field_data:\n            if f in self._container_fields:\n                self.field_data[f] = self.ds.arr(self._generate_container_field(f))\n                return self.field_data[f]\n            else:\n                self.get_data(f)\n        # fi.units is the unit expression string. We depend on the registry\n        # hanging off the dataset to define this unit object.\n        # Note that this is less succinct so that we can account for the case\n        # when there are, for example, no elements in the object.\n        try:\n            rv = self.field_data[f]\n        except KeyError:\n            fi = self.ds._get_field_info(f)\n            rv = self.ds.arr(self.field_data[key], fi.units)\n        return rv\n\n    def _ipython_key_completions_(self):\n        return _get_ipython_key_completion(self.ds)\n\n    def __setitem__(self, key, val):\n        \"\"\"\n        Sets a field to be some other value.\n        \"\"\"\n        self.field_data[key] = val\n\n    def __delitem__(self, key):\n        \"\"\"\n        Deletes a field\n        \"\"\"\n        if key not in self.field_data:\n            key = self._determine_fields(key)[0]\n        del self.field_data[key]\n\n    @abc.abstractmethod\n    def get_bbox(self) -> tuple[unyt_array, unyt_array]:\n        \"\"\"\n        Return the bounding box for this data container.\n        \"\"\"\n        pass\n\n    def _generate_field(self, field):\n        ftype, fname = field\n        finfo = self.ds._get_field_info(field)\n        with self._field_type_state(ftype, finfo):\n            if fname in self._container_fields:\n                tr = self._generate_container_field(field)\n            if finfo.sampling_type == \"particle\":\n                tr = self._generate_particle_field(field)\n            else:\n                tr = self._generate_fluid_field(field)\n            if tr is None:\n                raise YTCouldNotGenerateField(field, self.ds)\n            return tr\n\n    def _generate_fluid_field(self, field):\n        # First we check the validator\n        finfo = self.ds._get_field_info(field)\n        if self._current_chunk is None or self._current_chunk.chunk_type != \"spatial\":\n            gen_obj = self\n        else:\n            gen_obj = self._current_chunk.objs[0]\n            gen_obj.field_parameters = self.field_parameters\n        try:\n            finfo.check_available(gen_obj)\n        except NeedsGridType as ngt_exception:\n            rv = self._generate_spatial_fluid(field, ngt_exception.ghost_zones)\n        else:\n            rv = finfo(gen_obj)\n        return rv\n\n    def _generate_spatial_fluid(self, field, ngz):\n        finfo = self.ds._get_field_info(field)\n        if finfo.units is None:\n            raise YTSpatialFieldUnitError(field)\n        units = finfo.units\n        try:\n            rv = self.ds.arr(np.zeros(self.ires.size, dtype=\"float64\"), units)\n            accumulate = False\n        except YTNonIndexedDataContainer:\n            # In this case, we'll generate many tiny arrays of unknown size and\n            # then concatenate them.\n            outputs = []\n            accumulate = True\n        ind = 0\n        if ngz == 0:\n            deps = self._identify_dependencies([field], spatial=True)\n            deps = self._determine_fields(deps)\n            for _io_chunk in self.chunks([], \"io\", cache=False):\n                for _chunk in self.chunks([], \"spatial\", ngz=0, preload_fields=deps):\n                    o = self._current_chunk.objs[0]\n                    if accumulate:\n                        rv = self.ds.arr(np.empty(o.ires.size, dtype=\"float64\"), units)\n                        outputs.append(rv)\n                        ind = 0  # Does this work with mesh?\n                    with o._activate_cache():\n                        ind += o.select(\n                            self.selector, source=self[field], dest=rv, offset=ind\n                        )\n        else:\n            chunks = self.index._chunk(self, \"spatial\", ngz=ngz)\n            for chunk in chunks:\n                with self._chunked_read(chunk):\n                    gz = self._current_chunk.objs[0]\n                    gz.field_parameters = self.field_parameters\n                    wogz = gz._base_grid\n                    if accumulate:\n                        rv = self.ds.arr(\n                            np.empty(wogz.ires.size, dtype=\"float64\"), units\n                        )\n                        outputs.append(rv)\n                    ind += wogz.select(\n                        self.selector,\n                        source=gz[field][ngz:-ngz, ngz:-ngz, ngz:-ngz],\n                        dest=rv,\n                        offset=ind,\n                    )\n        if accumulate:\n            rv = uconcatenate(outputs)\n        return rv\n\n    def _generate_particle_field(self, field):\n        # First we check the validator\n        ftype, fname = field\n        if self._current_chunk is None or self._current_chunk.chunk_type != \"spatial\":\n            gen_obj = self\n        else:\n            gen_obj = self._current_chunk.objs[0]\n        try:\n            finfo = self.ds._get_field_info(field)\n            finfo.check_available(gen_obj)\n        except NeedsGridType as ngt_exception:\n            if ngt_exception.ghost_zones != 0:\n                raise NotImplementedError from ngt_exception\n            size = self._count_particles(ftype)\n            rv = self.ds.arr(np.empty(size, dtype=\"float64\"), finfo.units)\n            ind = 0\n            for _io_chunk in self.chunks([], \"io\", cache=False):\n                for _chunk in self.chunks(field, \"spatial\"):\n                    x, y, z = (self[ftype, f\"particle_position_{ax}\"] for ax in \"xyz\")\n                    if x.size == 0:\n                        continue\n                    mask = self._current_chunk.objs[0].select_particles(\n                        self.selector, x, y, z\n                    )\n                    if mask is None:\n                        continue\n                    # This requests it from the grid and does NOT mask it\n                    data = self[field][mask]\n                    rv[ind : ind + data.size] = data\n                    ind += data.size\n        else:\n            with self._field_type_state(ftype, finfo, gen_obj):\n                rv = self.ds._get_field_info(field)(gen_obj)\n        return rv\n\n    def _count_particles(self, ftype):\n        for (f1, _f2), val in self.field_data.items():\n            if f1 == ftype:\n                return val.size\n        size = 0\n        for _io_chunk in self.chunks([], \"io\", cache=False):\n            for _chunk in self.chunks([], \"spatial\"):\n                x, y, z = (self[ftype, f\"particle_position_{ax}\"] for ax in \"xyz\")\n                if x.size == 0:\n                    continue\n                size += self._current_chunk.objs[0].count_particles(\n                    self.selector, x, y, z\n                )\n        return size\n\n    def _generate_container_field(self, field):\n        raise NotImplementedError\n\n    def _parameter_iterate(self, seq):\n        for obj in seq:\n            old_fp = obj.field_parameters\n            obj.field_parameters = self.field_parameters\n            yield obj\n            obj.field_parameters = old_fp\n\n    def write_out(self, filename, fields=None, format=\"%0.16e\"):\n        \"\"\"Write out the YTDataContainer object in a text file.\n\n        This function will take a data object and produce a tab delimited text\n        file containing the fields presently existing and the fields given in\n        the ``fields`` list.\n\n        Parameters\n        ----------\n        filename : String\n            The name of the file to write to.\n\n        fields : List of string, Default = None\n            If this is supplied, these fields will be added to the list of\n            fields to be saved to disk. If not supplied, whatever fields\n            presently exist will be used.\n\n        format : String, Default = \"%0.16e\"\n            Format of numbers to be written in the file.\n\n        Raises\n        ------\n        ValueError\n            Raised when there is no existing field.\n\n        YTException\n            Raised when field_type of supplied fields is inconsistent with the\n            field_type of existing fields.\n\n        Examples\n        --------\n        >>> ds = fake_particle_ds()\n        >>> sp = ds.sphere(ds.domain_center, 0.25)\n        >>> sp.write_out(\"sphere_1.txt\")\n        >>> sp.write_out(\"sphere_2.txt\", fields=[\"cell_volume\"])\n        \"\"\"\n        if fields is None:\n            fields = sorted(self.field_data.keys())\n\n        field_order = [(\"index\", k) for k in self._key_fields]\n        diff_fields = [field for field in fields if field not in field_order]\n        field_order += diff_fields\n        field_order = sorted(self._determine_fields(field_order))\n\n        field_shapes = defaultdict(list)\n        for field in field_order:\n            shape = self[field].shape\n            field_shapes[shape].append(field)\n\n        # Check all fields have the same shape\n        if len(field_shapes) != 1:\n            err_msg = [\"Got fields with different number of elements:\\n\"]\n            for shape, these_fields in field_shapes.items():\n                err_msg.append(f\"\\t {these_fields} with shape {shape}\")\n            raise YTException(\"\\n\".join(err_msg))\n\n        with open(filename, \"w\") as fid:\n            field_header = [str(f) for f in field_order]\n            fid.write(\"\\t\".join([\"#\"] + field_header + [\"\\n\"]))\n            field_data = np.array([self.field_data[field] for field in field_order])\n            for line in range(field_data.shape[1]):\n                field_data[:, line].tofile(fid, sep=\"\\t\", format=format)\n                fid.write(\"\\n\")\n\n    def to_dataframe(self, fields):\n        r\"\"\"Export a data object to a :class:`~pandas.DataFrame`.\n\n        This function will take a data object and an optional list of fields\n        and export them to a :class:`~pandas.DataFrame` object.\n        If pandas is not importable, this will raise ImportError.\n\n        Parameters\n        ----------\n        fields : list of strings or tuple field names\n            This is the list of fields to be exported into\n            the DataFrame.\n\n        Returns\n        -------\n        df : :class:`~pandas.DataFrame`\n            The data contained in the object.\n\n        Examples\n        --------\n        >>> dd = ds.all_data()\n        >>> df = dd.to_dataframe([(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n        \"\"\"\n        from yt.utilities.on_demand_imports import _pandas as pd\n\n        data = {}\n        fields = self._determine_fields(fields)\n        for field in fields:\n            data[field[-1]] = self[field]\n        df = pd.DataFrame(data)\n        return df\n\n    def to_astropy_table(self, fields):\n        \"\"\"\n        Export region data to a :class:~astropy.table.table.QTable,\n        which is a Table object which is unit-aware. The QTable can then\n        be exported to an ASCII file, FITS file, etc.\n\n        See the AstroPy Table docs for more details:\n        http://docs.astropy.org/en/stable/table/\n\n        Parameters\n        ----------\n        fields : list of strings or tuple field names\n            This is the list of fields to be exported into\n            the QTable.\n\n        Examples\n        --------\n        >>> sp = ds.sphere(\"c\", (1.0, \"Mpc\"))\n        >>> t = sp.to_astropy_table([(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n        \"\"\"\n        from astropy.table import QTable\n\n        t = QTable()\n        fields = self._determine_fields(fields)\n        for field in fields:\n            t[field[-1]] = self[field].to_astropy()\n        return t\n\n    def save_as_dataset(self, filename=None, fields=None):\n        r\"\"\"Export a data object to a reloadable yt dataset.\n\n        This function will take a data object and output a dataset\n        containing either the fields presently existing or fields\n        given in the ``fields`` list.  The resulting dataset can be\n        reloaded as a yt dataset.\n\n        Parameters\n        ----------\n        filename : str, optional\n            The name of the file to be written.  If None, the name\n            will be a combination of the original dataset and the type\n            of data container.\n        fields : list of string or tuple field names, optional\n            If this is supplied, it is the list of fields to be saved to\n            disk.  If not supplied, all the fields that have been queried\n            will be saved.\n\n        Returns\n        -------\n        filename : str\n            The name of the file that has been created.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n        >>> sp = ds.sphere(ds.domain_center, (10, \"Mpc\"))\n        >>> fn = sp.save_as_dataset(fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n        >>> sphere_ds = yt.load(fn)\n        >>> # the original data container is available as the data attribute\n        >>> print(sds.data[\"gas\", \"density\"])\n        [  4.46237613e-32   4.86830178e-32   4.46335118e-32 ...,   6.43956165e-30\n           3.57339907e-30   2.83150720e-30] g/cm**3\n        >>> ad = sphere_ds.all_data()\n        >>> print(ad[\"gas\", \"temperature\"])\n        [  1.00000000e+00   1.00000000e+00   1.00000000e+00 ...,   4.40108359e+04\n           4.54380547e+04   4.72560117e+04] K\n\n        \"\"\"\n\n        keyword = f\"{str(self.ds)}_{self._type_name}\"\n        filename = get_output_filename(filename, keyword, \".h5\")\n\n        data = {}\n        if fields is not None:\n            for f in self._determine_fields(fields):\n                data[f] = self[f]\n        else:\n            data.update(self.field_data)\n        # get the extra fields needed to reconstruct the container\n        tds_fields = tuple((\"index\", t) for t in self._tds_fields)\n        for f in [f for f in self._container_fields + tds_fields if f not in data]:\n            data[f] = self[f]\n        data_fields = list(data.keys())\n\n        need_grid_positions = False\n        need_particle_positions = False\n        ptypes = []\n        ftypes = {}\n        for field in data_fields:\n            if field in self._container_fields:\n                ftypes[field] = \"grid\"\n                need_grid_positions = True\n            elif self.ds.field_info[field].sampling_type == \"particle\":\n                if field[0] not in ptypes:\n                    ptypes.append(field[0])\n                ftypes[field] = field[0]\n                need_particle_positions = True\n            else:\n                ftypes[field] = \"grid\"\n                need_grid_positions = True\n        # projections and slices use px and py, so don't need positions\n        if self._type_name in [\"cutting\", \"proj\", \"slice\", \"quad_proj\"]:\n            need_grid_positions = False\n\n        if need_particle_positions:\n            for ax in self.ds.coordinates.axis_order:\n                for ptype in ptypes:\n                    p_field = (ptype, f\"particle_position_{ax}\")\n                    if p_field in self.ds.field_info and p_field not in data:\n                        data_fields.append(field)\n                        ftypes[p_field] = p_field[0]\n                        data[p_field] = self[p_field]\n        if need_grid_positions:\n            for ax in self.ds.coordinates.axis_order:\n                g_field = (\"index\", ax)\n                if g_field in self.ds.field_info and g_field not in data:\n                    data_fields.append(g_field)\n                    ftypes[g_field] = \"grid\"\n                    data[g_field] = self[g_field]\n                g_field = (\"index\", \"d\" + ax)\n                if g_field in self.ds.field_info and g_field not in data:\n                    data_fields.append(g_field)\n                    ftypes[g_field] = \"grid\"\n                    data[g_field] = self[g_field]\n\n        extra_attrs = {\n            arg: getattr(self, arg, None) for arg in self._con_args + self._tds_attrs\n        }\n        extra_attrs[\"con_args\"] = repr(self._con_args)\n        extra_attrs[\"data_type\"] = \"yt_data_container\"\n        extra_attrs[\"container_type\"] = self._type_name\n        extra_attrs[\"dimensionality\"] = self._dimensionality\n        save_as_dataset(\n            self.ds, filename, data, field_types=ftypes, extra_attrs=extra_attrs\n        )\n\n        return filename\n\n    def create_firefly_object(\n        self,\n        datadir=None,\n        fields_to_include=None,\n        fields_units=None,\n        default_decimation_factor=100,\n        velocity_units=\"km/s\",\n        coordinate_units=\"kpc\",\n        show_unused_fields=0,\n        *,\n        JSONdir=None,\n        match_any_particle_types=True,\n        **kwargs,\n    ):\n        r\"\"\"This function links a region of data stored in a yt dataset\n        to the Python frontend API for [Firefly](http://github.com/ageller/Firefly),\n        a browser-based particle visualization tool.\n\n        Parameters\n        ----------\n\n        datadir : string\n            Path to where any `.json` files should be saved. If a relative\n            path will assume relative to `${HOME}`. A value of `None` will default to `${HOME}/Data`.\n\n        fields_to_include : array_like of strings or field tuples\n            A list of fields that you want to include in your\n            Firefly visualization for on-the-fly filtering and\n            colormapping.\n\n        default_decimation_factor : integer\n            The factor by which you want to decimate each particle group\n            by (e.g. if there are 1e7 total particles in your simulation\n            you might want to set this to 100 at first). Randomly samples\n            your data like `shuffled_data[::decimation_factor]` so as to\n            not overtax a system. This is adjustable on a per particle group\n            basis by changing the returned reader's\n            `reader.particleGroup[i].decimation_factor` before calling\n            `reader.writeToDisk()`.\n\n        velocity_units : string\n            The units that the velocity should be converted to in order to\n            show streamlines in Firefly. Defaults to km/s.\n\n        coordinate_units : string\n            The units that the coordinates should be converted to. Defaults to\n            kpc.\n\n        show_unused_fields : boolean\n            A flag to optionally print the fields that are available, in the\n            dataset but were not explicitly requested to be tracked.\n\n        match_any_particle_types : boolean\n            If True, when any of the fields_to_include match multiple particle\n            groups then the field will be added for all matching particle\n            groups. If False, an error is raised when encountering an ambiguous\n            field. Default is True.\n\n        Any additional keyword arguments are passed to\n        firefly.data_reader.Reader.__init__\n\n        Returns\n        -------\n        reader : Firefly.data_reader.Reader object\n            A reader object from the Firefly, configured\n            to output the current region selected\n\n        Examples\n        --------\n\n            >>> ramses_ds = yt.load(\n            ...     \"/Users/agurvich/Desktop/yt_workshop/\"\n            ...     + \"DICEGalaxyDisk_nonCosmological/output_00002/info_00002.txt\"\n            ... )\n\n            >>> region = ramses_ds.sphere(ramses_ds.domain_center, (1000, \"kpc\"))\n\n            >>> reader = region.create_firefly_object(\n            ...     \"IsoGalaxyRamses\",\n            ...     fields_to_include=[\n            ...         \"particle_extra_field_1\",\n            ...         \"particle_extra_field_2\",\n            ...     ],\n            ...     fields_units=[\"dimensionless\", \"dimensionless\"],\n            ... )\n\n            >>> reader.settings[\"color\"][\"io\"] = [1, 1, 0, 1]\n            >>> reader.particleGroups[0].decimation_factor = 100\n            >>> reader.writeToDisk()\n        \"\"\"\n\n        ## handle default arguments\n        if fields_to_include is None:\n            fields_to_include = []\n        if fields_units is None:\n            fields_units = []\n\n        ## handle input validation, if any\n        if len(fields_units) != len(fields_to_include):\n            raise RuntimeError(\"Each requested field must have units.\")\n\n        ## for safety, in case someone passes a float just cast it\n        default_decimation_factor = int(default_decimation_factor)\n\n        if JSONdir is not None:\n            issue_deprecation_warning(\n                \"The 'JSONdir' keyword argument is a deprecated alias for 'datadir'.\"\n                \"Please use 'datadir' directly.\",\n                stacklevel=3,\n                since=\"4.1\",\n            )\n            datadir = JSONdir\n\n        ## initialize a firefly reader instance\n        reader = firefly.data_reader.Reader(\n            datadir=datadir, clean_datadir=True, **kwargs\n        )\n\n        ## Ensure at least one field type contains every field requested\n        if match_any_particle_types:\n            # Need to keep previous behavior: single string field names that\n            # are ambiguous should bring in any matching ParticleGroups instead\n            # of raising an error\n            # This can be expanded/changed in the future to include field\n            # tuples containing some sort of special \"any\" ParticleGroup\n            unambiguous_fields_to_include = []\n            unambiguous_fields_units = []\n            for field, field_unit in zip(fields_to_include, fields_units, strict=True):\n                if isinstance(field, tuple):\n                    # skip tuples, they'll be checked with _determine_fields\n                    unambiguous_fields_to_include.append(field)\n                    unambiguous_fields_units.append(field_unit)\n                    continue\n                _, candidates = self.ds._get_field_info_helper(field)\n                if len(candidates) == 1:\n                    # Field is unambiguous, add in tuple form\n                    # This should be equivalent to _tupleize_field\n                    unambiguous_fields_to_include.append(candidates[0])\n                    unambiguous_fields_units.append(field_unit)\n                else:\n                    # Field has multiple candidates, add all of them instead\n                    # of original field. Note this may bring in aliases and\n                    # equivalent particle fields\n                    for c in candidates:\n                        unambiguous_fields_to_include.append(c)\n                        unambiguous_fields_units.append(field_unit)\n            fields_to_include = unambiguous_fields_to_include\n            fields_units = unambiguous_fields_units\n        # error if any requested field is unknown or (still) ambiguous\n        # This is also sufficient if match_any_particle_types=False\n        fields_to_include = self._determine_fields(fields_to_include)\n        ## Also generate equivalent of particle_fields_by_type including\n        ## derived fields\n        kysd = defaultdict(list)\n        for k, v in self.ds.derived_field_list:\n            kysd[k].append(v)\n\n        ## create a ParticleGroup object that contains *every* field\n        for ptype in sorted(self.ds.particle_types_raw):\n            ## skip this particle type if it has no particles in this dataset\n            if self[ptype, \"relative_particle_position\"].shape[0] == 0:\n                continue\n\n            ## loop through the fields and print them to the screen\n            if show_unused_fields:\n                ## read the available extra fields from yt\n                this_ptype_fields = self.ds.particle_fields_by_type[ptype]\n\n                ## load the extra fields and print them\n                for field in this_ptype_fields:\n                    if field not in fields_to_include:\n                        mylog.warning(\n                            \"detected (but did not request) %s %s\", ptype, field\n                        )\n\n            field_arrays = []\n            field_names = []\n\n            ## explicitly go after the fields we want\n            for field, units in zip(fields_to_include, fields_units, strict=True):\n                ## Only interested in fields with the current particle type,\n                ## whether that means general fields or field tuples\n                ftype, fname = field\n                if ftype not in (ptype, \"all\"):\n                    continue\n\n                ## determine if you want to take the log of the field for Firefly\n                log_flag = \"log(\" in units\n\n                ## read the field array from the dataset\n                this_field_array = self[ptype, fname]\n\n                ## fix the units string and prepend 'log' to the field for\n                ##  the UI name\n                if log_flag:\n                    units = units[len(\"log(\") : -1]\n                    fname = f\"log{fname}\"\n\n                ## perform the unit conversion and take the log if\n                ##  necessary.\n                this_field_array.in_units(units)\n                if log_flag:\n                    this_field_array = np.log10(this_field_array)\n\n                ## add this array to the tracked arrays\n                field_arrays += [this_field_array]\n                field_names = np.append(field_names, [fname], axis=0)\n\n            ## flag whether we want to filter and/or color by these fields\n            ##  we'll assume yes for both cases, this can be changed after\n            ##  the reader object is returned to the user.\n            field_filter_flags = np.ones(len(field_names))\n            field_colormap_flags = np.ones(len(field_names))\n\n            ## field_* needs to be explicitly set None if empty\n            ## so that Firefly will correctly compute the binary\n            ## headers\n            if len(field_arrays) == 0:\n                if len(fields_to_include) > 0:\n                    mylog.warning(\"No additional fields specified for %s\", ptype)\n                field_arrays = None\n                field_names = None\n                field_filter_flags = None\n                field_colormap_flags = None\n\n            ## Check if particles have velocities\n            if \"relative_particle_velocity\" in kysd[ptype]:\n                velocities = self[ptype, \"relative_particle_velocity\"].in_units(\n                    velocity_units\n                )\n            else:\n                velocities = None\n\n            ## create a firefly ParticleGroup for this particle type\n            pg = firefly.data_reader.ParticleGroup(\n                UIname=ptype,\n                coordinates=self[ptype, \"relative_particle_position\"].in_units(\n                    coordinate_units\n                ),\n                velocities=velocities,\n                field_arrays=field_arrays,\n                field_names=field_names,\n                field_filter_flags=field_filter_flags,\n                field_colormap_flags=field_colormap_flags,\n                decimation_factor=default_decimation_factor,\n            )\n\n            ## bind this particle group to the firefly reader object\n            reader.addParticleGroup(pg)\n\n        return reader\n\n    # Numpy-like Operations\n    def argmax(self, field, axis=None):\n        r\"\"\"Return the values at which the field is maximized.\n\n        This will, in a parallel-aware fashion, find the maximum value and then\n        return to you the values at that maximum location that are requested\n        for \"axis\".  By default it will return the spatial positions (in the\n        natural coordinate system), but it can be any field\n\n        Parameters\n        ----------\n        field : string or tuple field name\n            The field to maximize.\n        axis : string or list of strings, optional\n            If supplied, the fields to sample along; if not supplied, defaults\n            to the coordinate fields.  This can be the name of the coordinate\n            fields (i.e., 'x', 'y', 'z') or a list of fields, but cannot be 0,\n            1, 2.\n\n        Returns\n        -------\n        A list of YTQuantities as specified by the axis argument.\n\n        Examples\n        --------\n\n        >>> temp_at_max_rho = reg.argmax(\n        ...     (\"gas\", \"density\"), axis=(\"gas\", \"temperature\")\n        ... )\n        >>> max_rho_xyz = reg.argmax((\"gas\", \"density\"))\n        >>> t_mrho, v_mrho = reg.argmax(\n        ...     (\"gas\", \"density\"),\n        ...     axis=[(\"gas\", \"temperature\"), (\"gas\", \"velocity_magnitude\")],\n        ... )\n        >>> x, y, z = reg.argmax((\"gas\", \"density\"))\n\n        \"\"\"\n        if axis is None:\n            mv, pos0, pos1, pos2 = self.quantities.max_location(field)\n            return pos0, pos1, pos2\n        if isinstance(axis, str):\n            axis = [axis]\n        rv = self.quantities.sample_at_max_field_values(field, axis)\n        if len(rv) == 2:\n            return rv[1]\n        return rv[1:]\n\n    def argmin(self, field, axis=None):\n        r\"\"\"Return the values at which the field is minimized.\n\n        This will, in a parallel-aware fashion, find the minimum value and then\n        return to you the values at that minimum location that are requested\n        for \"axis\".  By default it will return the spatial positions (in the\n        natural coordinate system), but it can be any field\n\n        Parameters\n        ----------\n        field : string or tuple field name\n            The field to minimize.\n        axis : string or list of strings, optional\n            If supplied, the fields to sample along; if not supplied, defaults\n            to the coordinate fields.  This can be the name of the coordinate\n            fields (i.e., 'x', 'y', 'z') or a list of fields, but cannot be 0,\n            1, 2.\n\n        Returns\n        -------\n        A list of YTQuantities as specified by the axis argument.\n\n        Examples\n        --------\n\n        >>> temp_at_min_rho = reg.argmin(\n        ...     (\"gas\", \"density\"), axis=(\"gas\", \"temperature\")\n        ... )\n        >>> min_rho_xyz = reg.argmin((\"gas\", \"density\"))\n        >>> t_mrho, v_mrho = reg.argmin(\n        ...     (\"gas\", \"density\"),\n        ...     axis=[(\"gas\", \"temperature\"), (\"gas\", \"velocity_magnitude\")],\n        ... )\n        >>> x, y, z = reg.argmin((\"gas\", \"density\"))\n\n        \"\"\"\n        if axis is None:\n            mv, pos0, pos1, pos2 = self.quantities.min_location(field)\n            return pos0, pos1, pos2\n        if isinstance(axis, str):\n            axis = [axis]\n        rv = self.quantities.sample_at_min_field_values(field, axis)\n        if len(rv) == 2:\n            return rv[1]\n        return rv[1:]\n\n    def _compute_extrema(self, field):\n        if self._extrema_cache is None:\n            self._extrema_cache = {}\n        if field not in self._extrema_cache:\n            # Note we still need to call extrema for each field, as of right\n            # now\n            mi, ma = self.quantities.extrema(field)\n            self._extrema_cache[field] = (mi, ma)\n        return self._extrema_cache[field]\n\n    _extrema_cache = None\n\n    def max(self, field, axis=None):\n        r\"\"\"Compute the maximum of a field, optionally along an axis.\n\n        This will, in a parallel-aware fashion, compute the maximum of the\n        given field.  Supplying an axis will result in a return value of a\n        YTProjection, with method 'max' for maximum intensity.  If the max has\n        already been requested, it will use the cached extrema value.\n\n        Parameters\n        ----------\n        field : string or tuple field name\n            The field to maximize.\n        axis : string, optional\n            If supplied, the axis to project the maximum along.\n\n        Returns\n        -------\n        Either a scalar or a YTProjection.\n\n        Examples\n        --------\n\n        >>> max_temp = reg.max((\"gas\", \"temperature\"))\n        >>> max_temp_proj = reg.max((\"gas\", \"temperature\"), axis=(\"index\", \"x\"))\n        \"\"\"\n        if axis is None:\n            rv = tuple(self._compute_extrema(f)[1] for f in iter_fields(field))\n            if len(rv) == 1:\n                return rv[0]\n            return rv\n        elif axis in self.ds.coordinates.axis_name:\n            return self.ds.proj(field, axis, data_source=self, method=\"max\")\n        else:\n            raise NotImplementedError(f\"Unknown axis {axis}\")\n\n    def min(self, field, axis=None):\n        r\"\"\"Compute the minimum of a field.\n\n        This will, in a parallel-aware fashion, compute the minimum of the\n        given field. Supplying an axis will result in a return value of a\n        YTProjection, with method 'min' for minimum intensity.  If the min\n        has already been requested, it will use the cached extrema value.\n\n        Parameters\n        ----------\n        field : string or tuple field name\n            The field to minimize.\n        axis : string, optional\n            If supplied, the axis to compute the minimum along.\n\n        Returns\n        -------\n        Either a scalar or a YTProjection.\n\n        Examples\n        --------\n\n        >>> min_temp = reg.min((\"gas\", \"temperature\"))\n        >>> min_temp_proj = reg.min((\"gas\", \"temperature\"), axis=(\"index\", \"x\"))\n        \"\"\"\n        if axis is None:\n            rv = tuple(self._compute_extrema(f)[0] for f in iter_fields(field))\n            if len(rv) == 1:\n                return rv[0]\n            return rv\n        elif axis in self.ds.coordinates.axis_name:\n            return self.ds.proj(field, axis, data_source=self, method=\"min\")\n        else:\n            raise NotImplementedError(f\"Unknown axis {axis}\")\n\n    def std(self, field, axis=None, weight=None):\n        \"\"\"Compute the standard deviation of a field, optionally along\n        an axis, with a weight.\n\n        This will, in a parallel-ware fashion, compute the standard\n        deviation of the given field. If an axis is supplied, it\n        will return a projection, where the weight is also supplied.\n\n        By default the weight field will be \"ones\" or \"particle_ones\",\n        depending on the field, resulting in an unweighted standard\n        deviation.\n\n        Parameters\n        ----------\n        field : string or tuple field name\n            The field to calculate the standard deviation of\n        axis : string, optional\n            If supplied, the axis to compute the standard deviation\n            along (i.e., to project along)\n        weight : string, optional\n            The field to use as a weight.\n\n        Returns\n        -------\n        Scalar or YTProjection.\n        \"\"\"\n        weight_field = sanitize_weight_field(self.ds, field, weight)\n        if axis in self.ds.coordinates.axis_name:\n            r = self.ds.proj(\n                field, axis, data_source=self, weight_field=weight_field, moment=2\n            )\n        elif axis is None:\n            r = self.quantities.weighted_standard_deviation(field, weight_field)[0]\n        else:\n            raise NotImplementedError(f\"Unknown axis {axis}\")\n        return r\n\n    def ptp(self, field):\n        r\"\"\"Compute the range of values (maximum - minimum) of a field.\n\n        This will, in a parallel-aware fashion, compute the \"peak-to-peak\" of\n        the given field.\n\n        Parameters\n        ----------\n        field : string or tuple field name\n            The field to average.\n\n        Returns\n        -------\n        Scalar\n\n        Examples\n        --------\n\n        >>> rho_range = reg.ptp((\"gas\", \"density\"))\n        \"\"\"\n        ex = self._compute_extrema(field)\n        return ex[1] - ex[0]\n\n    def profile(\n        self,\n        bin_fields,\n        fields,\n        n_bins=64,\n        extrema=None,\n        logs=None,\n        units=None,\n        weight_field=(\"gas\", \"mass\"),\n        accumulation=False,\n        fractional=False,\n        deposition=\"ngp\",\n        *,\n        override_bins=None,\n    ):\n        r\"\"\"\n        Create a 1, 2, or 3D profile object from this data_source.\n\n        The dimensionality of the profile object is chosen by the number of\n        fields given in the bin_fields argument.  This simply calls\n        :func:`yt.data_objects.profiles.create_profile`.\n\n        Parameters\n        ----------\n        bin_fields : list of strings\n            List of the binning fields for profiling.\n        fields : list of strings\n            The fields to be profiled.\n        n_bins : int or list of ints\n            The number of bins in each dimension.  If None, 64 bins for\n            each bin are used for each bin field.\n            Default: 64.\n        extrema : dict of min, max tuples\n            Minimum and maximum values of the bin_fields for the profiles.\n            The keys correspond to the field names. Defaults to the extrema\n            of the bin_fields of the dataset. If a units dict is provided, extrema\n            are understood to be in the units specified in the dictionary.\n        logs : dict of boolean values\n            Whether or not to log the bin_fields for the profiles.\n            The keys correspond to the field names. Defaults to the take_log\n            attribute of the field.\n        units : dict of strings\n            The units of the fields in the profiles, including the bin_fields.\n        weight_field : str or tuple field identifier\n            The weight field for computing weighted average for the profile\n            values.  If None, the profile values are sums of the data in\n            each bin.\n        accumulation : bool or list of bools\n            If True, the profile values for a bin n are the cumulative sum of\n            all the values from bin 0 to n.  If -True, the sum is reversed so\n            that the value for bin n is the cumulative sum from bin N (total bins)\n            to n.  If the profile is 2D or 3D, a list of values can be given to\n            control the summation in each dimension independently.\n            Default: False.\n        fractional : If True the profile values are divided by the sum of all\n            the profile data such that the profile represents a probability\n            distribution function.\n        deposition : Controls the type of deposition used for ParticlePhasePlots.\n            Valid choices are 'ngp' and 'cic'. Default is 'ngp'. This parameter is\n            ignored if the input fields are not of particle type.\n        override_bins : dict of bins to profile plot with\n            If set, ignores n_bins and extrema settings and uses the\n            supplied bins to profile the field. If a units dict is provided,\n            bins are understood to be in the units specified in the dictionary.\n\n        Examples\n        --------\n\n        Create a 1d profile.  Access bin field from profile.x and field\n        data from profile[<field_name>].\n\n        >>> ds = load(\"DD0046/DD0046\")\n        >>> ad = ds.all_data()\n        >>> profile = ad.profile(\n        ...     ad,\n        ...     [(\"gas\", \"density\")],\n        ...     [(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")],\n        ... )\n        >>> print(profile.x)\n        >>> print(profile[\"gas\", \"temperature\"])\n        >>> plot = profile.plot()\n        \"\"\"\n        p = create_profile(\n            self,\n            bin_fields,\n            fields,\n            n_bins,\n            extrema,\n            logs,\n            units,\n            weight_field,\n            accumulation,\n            fractional,\n            deposition,\n            override_bins=override_bins,\n        )\n        return p\n\n    def mean(self, field, axis=None, weight=None):\n        r\"\"\"Compute the mean of a field, optionally along an axis, with a\n        weight.\n\n        This will, in a parallel-aware fashion, compute the mean of the\n        given field.  If an axis is supplied, it will return a projection,\n        where the weight is also supplied.  By default the weight field will be\n        \"ones\" or \"particle_ones\", depending on the field being averaged,\n        resulting in an unweighted average.\n\n        Parameters\n        ----------\n        field : string or tuple field name\n            The field to average.\n        axis : string, optional\n            If supplied, the axis to compute the mean along (i.e., to project\n            along)\n        weight : string, optional\n            The field to use as a weight.\n\n        Returns\n        -------\n        Scalar or YTProjection.\n\n        Examples\n        --------\n\n        >>> avg_rho = reg.mean((\"gas\", \"density\"), weight=\"cell_volume\")\n        >>> rho_weighted_T = reg.mean(\n        ...     (\"gas\", \"temperature\"), axis=(\"index\", \"y\"), weight=(\"gas\", \"density\")\n        ... )\n        \"\"\"\n        weight_field = sanitize_weight_field(self.ds, field, weight)\n        if axis in self.ds.coordinates.axis_name:\n            r = self.ds.proj(field, axis, data_source=self, weight_field=weight_field)\n        elif axis is None:\n            r = self.quantities.weighted_average_quantity(field, weight_field)\n        else:\n            raise NotImplementedError(f\"Unknown axis {axis}\")\n        return r\n\n    def sum(self, field, axis=None):\n        r\"\"\"Compute the sum of a field, optionally along an axis.\n\n        This will, in a parallel-aware fashion, compute the sum of the given\n        field.  If an axis is specified, it will return a projection (using\n        method type \"sum\", which does not take into account path length) along\n        that axis.\n\n        Parameters\n        ----------\n        field : string or tuple field name\n            The field to sum.\n        axis : string, optional\n            If supplied, the axis to sum along.\n\n        Returns\n        -------\n        Either a scalar or a YTProjection.\n\n        Examples\n        --------\n\n        >>> total_vol = reg.sum(\"cell_volume\")\n        >>> cell_count = reg.sum((\"index\", \"ones\"), axis=(\"index\", \"x\"))\n        \"\"\"\n        # Because we're using ``sum`` to specifically mean a sum or a\n        # projection with the method=\"sum\", we do not utilize the ``mean``\n        # function.\n        if axis in self.ds.coordinates.axis_name:\n            with self._field_parameter_state({\"axis\": axis}):\n                r = self.ds.proj(field, axis, data_source=self, method=\"sum\")\n        elif axis is None:\n            r = self.quantities.total_quantity(field)\n        else:\n            raise NotImplementedError(f\"Unknown axis {axis}\")\n        return r\n\n    def integrate(self, field, weight=None, axis=None, *, moment=1):\n        r\"\"\"Compute the integral (projection) of a field along an axis.\n\n        This projects a field along an axis.\n\n        Parameters\n        ----------\n        field : string or tuple field name\n            The field to project.\n        weight : string or tuple field name\n            The field to weight the projection by\n        axis : string\n            The axis to project along.\n        moment : integer, optional\n            for a weighted projection, moment = 1 (the default) corresponds to a\n            weighted average. moment = 2 corresponds to a weighted standard\n            deviation.\n\n        Returns\n        -------\n        YTProjection\n\n        Examples\n        --------\n\n        >>> column_density = reg.integrate((\"gas\", \"density\"), axis=(\"index\", \"z\"))\n        \"\"\"\n        if weight is not None:\n            weight_field = sanitize_weight_field(self.ds, field, weight)\n        else:\n            weight_field = None\n        if axis in self.ds.coordinates.axis_name:\n            r = self.ds.proj(\n                field, axis, data_source=self, weight_field=weight_field, moment=moment\n            )\n        else:\n            raise NotImplementedError(f\"Unknown axis {axis}\")\n        return r\n\n    @property\n    def _hash(self):\n        s = f\"{self}\"\n        try:\n            import hashlib\n\n            return hashlib.md5(s.encode(\"utf-8\")).hexdigest()\n        except ImportError:\n            return s\n\n    def __reduce__(self):\n        args = tuple(\n            [self.ds._hash(), self._type_name]\n            + [getattr(self, n) for n in self._con_args]\n            + [self.field_parameters]\n        )\n        return (_reconstruct_object, args)\n\n    def clone(self):\n        r\"\"\"Clone a data object.\n\n        This will make a duplicate of a data object; note that the\n        `field_parameters` may not necessarily be deeply-copied.  If you modify\n        the field parameters in-place, it may or may not be shared between the\n        objects, depending on the type of object that that particular field\n        parameter is.\n\n        Notes\n        -----\n        One use case for this is to have multiple identical data objects that\n        are being chunked over in different orders.\n\n        Examples\n        --------\n\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> sp = ds.sphere(\"c\", 0.1)\n        >>> sp_clone = sp.clone()\n        >>> sp[\"gas\", \"density\"]\n        >>> print(sp.field_data.keys())\n        [(\"gas\", \"density\")]\n        >>> print(sp_clone.field_data.keys())\n        []\n        \"\"\"\n        args = self.__reduce__()\n        return args[0](self.ds, *args[1][1:])\n\n    def __repr__(self):\n        # We'll do this the slow way to be clear what's going on\n        s = f\"{self.__class__.__name__} ({self.ds}): \"\n        for i in self._con_args:\n            try:\n                s += (\n                    f\", {i}={getattr(self, i).in_base(unit_system=self.ds.unit_system)}\"\n                )\n            except AttributeError:\n                s += f\", {i}={getattr(self, i)}\"\n        return s\n\n    @contextmanager\n    def _field_parameter_state(self, field_parameters):\n        # What we're doing here is making a copy of the incoming field\n        # parameters, and then updating it with our own.  This means that we'll\n        # be using our own center, if set, rather than the supplied one.  But\n        # it also means that any additionally set values can override it.\n        old_field_parameters = self.field_parameters\n        new_field_parameters = field_parameters.copy()\n        new_field_parameters.update(old_field_parameters)\n        self.field_parameters = new_field_parameters\n        yield\n        self.field_parameters = old_field_parameters\n\n    @contextmanager\n    def _field_type_state(self, ftype, finfo, obj=None):\n        if obj is None:\n            obj = self\n        old_particle_type = obj._current_particle_type\n        old_fluid_type = obj._current_fluid_type\n        fluid_types = self.ds.fluid_types\n        if finfo.sampling_type == \"particle\" and ftype not in fluid_types:\n            obj._current_particle_type = ftype\n        else:\n            obj._current_fluid_type = ftype\n        yield\n        obj._current_particle_type = old_particle_type\n        obj._current_fluid_type = old_fluid_type\n\n    def _determine_fields(self, fields):\n        if str(fields) in self.ds._determined_fields:\n            return self.ds._determined_fields[str(fields)]\n        explicit_fields = []\n        for field in iter_fields(fields):\n            if field in self._container_fields:\n                if not isinstance(field, (str, tuple)):\n                    # NOTE: Container fields must be strings or tuples\n                    # otherwise, we end up filling _determined_fields\n                    # with DerivedFields (typing error, and causes\n                    # bugs down the line).\n                    raise RuntimeError(\n                        f\"Container fields must be tuples, got {field!r}\"\n                    )\n                explicit_fields.append(field)\n                continue\n\n            finfo = self.ds._get_field_info(field)\n            ftype, fname = finfo.name\n            # really ugly check to ensure that this field really does exist somewhere,\n            # in some naming convention, before returning it as a possible field type\n            if (\n                (ftype, fname) not in self.ds.field_info\n                and (ftype, fname) not in self.ds.field_list\n                and fname not in self.ds.field_list\n                and (ftype, fname) not in self.ds.derived_field_list\n                and fname not in self.ds.derived_field_list\n                and (ftype, fname) not in self._container_fields\n            ):\n                raise YTFieldNotFound((ftype, fname), self.ds)\n\n            # these tests are really insufficient as a field type may be valid, and the\n            # field name may be valid, but not the combination (field type, field name)\n            particle_field = finfo.sampling_type == \"particle\"\n            local_field = finfo.local_sampling\n            if local_field:\n                pass\n            elif particle_field and ftype not in self.ds.particle_types:\n                raise YTFieldTypeNotFound(ftype, ds=self.ds)\n            elif not particle_field and ftype not in self.ds.fluid_types:\n                raise YTFieldTypeNotFound(ftype, ds=self.ds)\n            explicit_fields.append((ftype, fname))\n\n        self.ds._determined_fields[str(fields)] = explicit_fields\n        return explicit_fields\n\n    _tree = None\n\n    @property\n    def tiles(self):\n        if self._tree is not None:\n            return self._tree\n        self._tree = AMRKDTree(self.ds, data_source=self)\n        return self._tree\n\n    @property\n    def blocks(self):\n        for _io_chunk in self.chunks([], \"io\"):\n            for _chunk in self.chunks([], \"spatial\", ngz=0):\n                # For grids this will be a grid object, and for octrees it will\n                # be an OctreeSubset.  Note that we delegate to the sub-object.\n                o = self._current_chunk.objs[0]\n                cache_fp = o.field_parameters.copy()\n                o.field_parameters.update(self.field_parameters)\n                for b, m in o.select_blocks(self.selector):\n                    if m is None:\n                        continue\n                    yield b, m\n                o.field_parameters = cache_fp\n\n\n# PR3124: Given that save_as_dataset is now the recommended method for saving\n# objects (see Issue 2021 and references therein), the following has been re-written.\n#\n# Original comments (still true):\n#\n# In the future, this would be better off being set up to more directly\n# reference objects or retain state, perhaps with a context manager.\n#\n# One final detail: time series or multiple datasets in a single pickle\n# seems problematic.\n\n\ndef _get_ds_by_hash(hash):\n    from yt.data_objects.static_output import Dataset\n\n    if isinstance(hash, Dataset):\n        return hash\n    from yt.data_objects.static_output import _cached_datasets\n\n    for ds in _cached_datasets.values():\n        if ds._hash() == hash:\n            return ds\n    return None\n\n\ndef _reconstruct_object(*args, **kwargs):\n    # returns a reconstructed YTDataContainer. As of PR 3124, we now return\n    # the actual YTDataContainer rather than a (ds, YTDataContainer) tuple.\n\n    # pull out some arguments\n    dsid = args[0]  # the hash id\n    dtype = args[1]  # DataContainer type (e.g., 'region')\n    field_parameters = args[-1]  # the field parameters\n\n    # re-instantiate the base dataset from the hash and ParameterFileStore\n    ds = _get_ds_by_hash(dsid)\n    override_weakref = False\n    if not ds:\n        override_weakref = True\n        datasets = ParameterFileStore()\n        ds = datasets.get_ds_hash(dsid)\n\n    # instantiate the class with remainder of the args and adjust the state\n    cls = getattr(ds, dtype)\n    obj = cls(*args[2:-1])\n    obj.field_parameters.update(field_parameters)\n\n    # any nested ds references are weakref.proxy(ds), so need to ensure the ds\n    # we just loaded persists when we leave this function (nosetests fail without\n    # this) if we did not have an actual dataset as an argument.\n    if hasattr(obj, \"ds\") and override_weakref:\n        obj.ds = ds\n\n    return obj\n"
  },
  {
    "path": "yt/data_objects/derived_quantities.py",
    "content": "import numpy as np\n\nfrom yt.funcs import camelcase_to_underscore, iter_fields\nfrom yt.units.yt_array import array_like_field\nfrom yt.utilities.exceptions import YTParticleTypeNotFound\nfrom yt.utilities.object_registries import derived_quantity_registry\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    ParallelAnalysisInterface,\n    parallel_objects,\n)\nfrom yt.utilities.physical_constants import gravitational_constant_cgs\nfrom yt.utilities.physical_ratios import HUGE\n\n\ndef get_position_fields(field, data):\n    axis_names = [data.ds.coordinates.axis_name[num] for num in [0, 1, 2]]\n    field = data._determine_fields(field)[0]\n    finfo = data.ds.field_info[field]\n    if finfo.sampling_type == \"particle\":\n        if finfo.is_alias:\n            ftype = finfo.alias_name[0]\n        else:\n            ftype = finfo.name[0]\n        position_fields = [(ftype, f\"particle_position_{d}\") for d in axis_names]\n    else:\n        position_fields = [(\"index\", ax_name) for ax_name in axis_names]\n\n    return position_fields\n\n\nclass DerivedQuantity(ParallelAnalysisInterface):\n    num_vals = -1\n\n    def __init__(self, data_source):\n        self.data_source = data_source\n\n    def __init_subclass__(cls, *args, **kwargs):\n        super().__init_subclass__(*args, **kwargs)\n        if cls.__name__ != \"DerivedQuantity\":\n            derived_quantity_registry[cls.__name__] = cls\n\n    def count_values(self, *args, **kwargs):\n        return\n\n    def __call__(self, *args, **kwargs):\n        \"\"\"Calculate results for the derived quantity\"\"\"\n        # create the index if it doesn't exist yet\n        self.data_source.ds.index\n        self.count_values(*args, **kwargs)\n        chunks = self.data_source.chunks(\n            [], chunking_style=self.data_source._derived_quantity_chunking\n        )\n        storage = {}\n        for sto, ds in parallel_objects(chunks, -1, storage=storage):\n            sto.result = self.process_chunk(ds, *args, **kwargs)\n        # Now storage will have everything, and will be done via pickling, so\n        # the units will be preserved.  (Credit to Nathan for this\n        # idea/implementation.)\n        values = [[] for i in range(self.num_vals)]\n        for key in sorted(storage):\n            for i in range(self.num_vals):\n                values[i].append(storage[key][i])\n        # These will be YTArrays\n        values = [self.data_source.ds.arr(values[i]) for i in range(self.num_vals)]\n        values = self.reduce_intermediate(values)\n        return values\n\n    def process_chunk(self, data, *args, **kwargs):\n        raise NotImplementedError\n\n    def reduce_intermediate(self, values):\n        raise NotImplementedError\n\n\nclass DerivedQuantityCollection:\n    def __new__(cls, data_source, *args, **kwargs):\n        inst = object.__new__(cls)\n        inst.data_source = data_source\n        for f in inst.keys():\n            setattr(inst, camelcase_to_underscore(f), inst[f])\n        return inst\n\n    def __getitem__(self, key):\n        dq = derived_quantity_registry[key]\n        # Instantiate here, so we can pass it the data object\n        # Note that this means we instantiate every time we run help, etc\n        # I have made my peace with this.\n        return dq(self.data_source)\n\n    def keys(self):\n        return derived_quantity_registry.keys()\n\n\nclass WeightedAverageQuantity(DerivedQuantity):\n    r\"\"\"\n    Calculates the weight average of a field or fields.\n\n    Returns a YTQuantity for each field requested; if one,\n    it returns a single YTQuantity, if many, it returns a list of YTQuantities\n    in order of the listed fields.\n\n    Where f is the field and w is the weight, the weighted average is\n    Sum_i(f_i \\* w_i) / Sum_i(w_i).\n\n    Parameters\n    ----------\n\n    fields : string / tuple, or list of strings / tuples\n        The field or fields of which the average value is to be calculated.\n    weight : string or tuple\n        The weight field.\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(\n    ...     ad.quantities.weighted_average_quantity(\n    ...         [(\"gas\", \"density\"), (\"gas\", \"temperature\")], (\"gas\", \"mass\")\n    ...     )\n    ... )\n\n    \"\"\"\n\n    def count_values(self, fields, weight):\n        # This is a list now\n        self.num_vals = len(fields) + 1\n\n    def __call__(self, fields, weight):\n        fields = list(iter_fields(fields))\n        rv = super().__call__(fields, weight)\n        if len(rv) == 1:\n            rv = rv[0]\n        return rv\n\n    def process_chunk(self, data, fields, weight):\n        vals = [(data[field] * data[weight]).sum(dtype=np.float64) for field in fields]\n        wv = data[weight].sum(dtype=np.float64)\n        return vals + [wv]\n\n    def reduce_intermediate(self, values):\n        w = values.pop(-1).sum(dtype=np.float64)\n        return [v.sum(dtype=np.float64) / w for v in values]\n\n\nclass TotalQuantity(DerivedQuantity):\n    r\"\"\"\n    Calculates the sum of the field or fields.\n\n    Parameters\n    ----------\n    fields\n        The field or list of fields to be summed.\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.total_quantity([(\"gas\", \"mass\")]))\n\n    \"\"\"\n\n    def count_values(self, fields):\n        # This is a list now\n        self.num_vals = len(fields)\n\n    def __call__(self, fields):\n        fields = list(iter_fields(fields))\n        rv = super().__call__(fields)\n        if len(rv) == 1:\n            rv = rv[0]\n        return rv\n\n    def process_chunk(self, data, fields):\n        vals = [data[field].sum(dtype=np.float64) for field in fields]\n        return vals\n\n    def reduce_intermediate(self, values):\n        return [v.sum(dtype=np.float64) for v in values]\n\n\nclass TotalMass(TotalQuantity):\n    r\"\"\"\n    Calculates the total mass of the object. Returns a YTArray where the\n    first element is total gas mass and the second element is total particle\n    mass.\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.total_mass())\n\n    \"\"\"\n\n    def __call__(self):\n        self.data_source.ds.index\n        fi = self.data_source.ds.field_info\n        if (\"gas\", \"mass\") in fi:\n            gas = super().__call__([(\"gas\", \"mass\")])\n        else:\n            gas = self.data_source.ds.quan(0.0, \"g\")\n        if (\"nbody\", \"particle_mass\") in fi:\n            part = super().__call__([(\"nbody\", \"particle_mass\")])\n        else:\n            part = self.data_source.ds.quan(0.0, \"g\")\n        return self.data_source.ds.arr([gas, part])\n\n\nclass CenterOfMass(DerivedQuantity):\n    r\"\"\"\n    Calculates the center of mass, using gas and/or particles.\n\n    The center of mass is the mass-weighted mean position.\n\n    Parameters\n    ----------\n    use_gas : bool\n        Flag to include gas in the calculation.  Gas is ignored if not\n        present.\n        Default: True\n    use_particles : bool\n        Flag to include particles in the calculation.  Particles are ignored\n        if not present.\n        Default: False\n    particle_type: string\n        Flag to specify the field type of the particles to use. Useful for\n        particle-based codes where you don't want to use all of the particles\n        in your calculation.\n        Default: 'all'\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.center_of_mass())\n\n    \"\"\"\n\n    def count_values(self, use_gas=True, use_particles=False, particle_type=\"nbody\"):\n        finfo = self.data_source.ds.field_info\n        includes_gas = (\"gas\", \"mass\") in finfo\n        includes_particles = (particle_type, \"particle_mass\") in finfo\n\n        self.use_gas = use_gas & includes_gas\n        self.use_particles = use_particles & includes_particles\n\n        self.num_vals = 0\n        if self.use_gas:\n            self.num_vals += 4\n        if self.use_particles:\n            self.num_vals += 4\n\n    def process_chunk(\n        self, data, use_gas=True, use_particles=False, particle_type=\"nbody\"\n    ):\n        vals = []\n        if self.use_gas:\n            vals += [\n                (data[\"gas\", ax] * data[\"gas\", \"mass\"]).sum(dtype=np.float64)\n                for ax in \"xyz\"\n            ]\n            vals.append(data[\"gas\", \"mass\"].sum(dtype=np.float64))\n        if self.use_particles:\n            vals += [\n                (\n                    data[particle_type, f\"particle_position_{ax}\"]\n                    * data[particle_type, \"particle_mass\"]\n                ).sum(dtype=np.float64)\n                for ax in \"xyz\"\n            ]\n            vals.append(data[particle_type, \"particle_mass\"].sum(dtype=np.float64))\n        return vals\n\n    def reduce_intermediate(self, values):\n        if len(values) not in (4, 8):\n            raise RuntimeError\n        x = values.pop(0).sum(dtype=np.float64)\n        y = values.pop(0).sum(dtype=np.float64)\n        z = values.pop(0).sum(dtype=np.float64)\n        w = values.pop(0).sum(dtype=np.float64)\n        if len(values) > 0:\n            # Note that this could be shorter if we pre-initialized our x,y,z,w\n            # values as YTQuantity objects.\n            x += values.pop(0).sum(dtype=np.float64)\n            y += values.pop(0).sum(dtype=np.float64)\n            z += values.pop(0).sum(dtype=np.float64)\n            w += values.pop(0).sum(dtype=np.float64)\n        return self.data_source.ds.arr([v / w for v in [x, y, z]])\n\n\nclass BulkVelocity(DerivedQuantity):\n    r\"\"\"\n    Calculates the bulk velocity, using gas and/or particles.\n\n    The bulk velocity is the mass-weighted mean velocity.\n\n    Parameters\n    ----------\n    use_gas : bool\n        Flag to include gas in the calculation.  Gas is ignored if not\n        present.\n        Default: True\n    use_particles : bool\n        Flag to include particles in the calculation.  Particles are ignored\n        if not present.\n        Default: True\n    particle_type: string\n        Flag to specify the field type of the particles to use. Useful for\n        particle-based codes where you don't want to use all of the particles\n        in your calculation.\n        Default: 'all'\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.bulk_velocity())\n\n    \"\"\"\n\n    def count_values(self, use_gas=True, use_particles=False, particle_type=\"nbody\"):\n        if use_particles and particle_type not in self.data_source.ds.particle_types:\n            raise YTParticleTypeNotFound(particle_type, self.data_source.ds)\n        # This is a list now\n        self.num_vals = 0\n        if use_gas:\n            self.num_vals += 4\n        if use_particles and \"nbody\" in self.data_source.ds.particle_types:\n            self.num_vals += 4\n\n    def process_chunk(\n        self, data, use_gas=True, use_particles=False, particle_type=\"nbody\"\n    ):\n        vals = []\n        if use_gas:\n            vals += [\n                (data[\"gas\", f\"velocity_{ax}\"] * data[\"gas\", \"mass\"]).sum(\n                    dtype=np.float64\n                )\n                for ax in \"xyz\"\n            ]\n            vals.append(data[\"gas\", \"mass\"].sum(dtype=np.float64))\n        if use_particles and \"nbody\" in data.ds.particle_types:\n            vals += [\n                (\n                    data[particle_type, f\"particle_velocity_{ax}\"]\n                    * data[particle_type, \"particle_mass\"]\n                ).sum(dtype=np.float64)\n                for ax in \"xyz\"\n            ]\n            vals.append(data[particle_type, \"particle_mass\"].sum(dtype=np.float64))\n        return vals\n\n    def reduce_intermediate(self, values):\n        if len(values) not in (4, 8):\n            raise RuntimeError\n        x = values.pop(0).sum(dtype=np.float64)\n        y = values.pop(0).sum(dtype=np.float64)\n        z = values.pop(0).sum(dtype=np.float64)\n        w = values.pop(0).sum(dtype=np.float64)\n        if len(values) > 0:\n            # Note that this could be shorter if we pre-initialized our x,y,z,w\n            # values as YTQuantity objects.\n            x += values.pop(0).sum(dtype=np.float64)\n            y += values.pop(0).sum(dtype=np.float64)\n            z += values.pop(0).sum(dtype=np.float64)\n            w += values.pop(0).sum(dtype=np.float64)\n        return self.data_source.ds.arr([v / w for v in [x, y, z]])\n\n\nclass WeightedStandardDeviation(DerivedQuantity):\n    r\"\"\"\n    Calculates the weighted standard deviation and weighted mean for a field\n    or list of fields. Returns a YTArray for each field requested; if one,\n    it returns a single YTArray, if many, it returns a list of YTArrays\n    in order of the listed fields.  The first element of each YTArray is\n    the weighted standard deviation, and the second element is the weighted mean.\n\n    Where f is the field, w is the weight, and <f_w> is the weighted mean,\n    the weighted standard deviation is\n    sqrt( Sum_i( (f_i - <f_w>)^2 \\* w_i ) / Sum_i(w_i) ).\n\n    Parameters\n    ----------\n\n    fields : string / tuple, or list of strings / tuples\n        The field or fields of which the average value is to be calculated.\n    weight : string or tuple\n        The weight field.\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(\n    ...     ad.quantities.weighted_standard_deviation(\n    ...         [(\"gas\", \"density\"), (\"gas\", \"temperature\")], (\"gas\", \"mass\")\n    ...     )\n    ... )\n\n    \"\"\"\n\n    def count_values(self, fields, weight):\n        # This is a list now\n        self.num_vals = 2 * len(fields) + 1\n\n    def __call__(self, fields, weight):\n        fields = list(iter_fields(fields))\n        units = [self.data_source.ds._get_field_info(field).units for field in fields]\n        rv = super().__call__(fields, weight)\n        rv = [self.data_source.ds.arr(v, u) for v, u in zip(rv, units, strict=True)]\n        if len(rv) == 1:\n            rv = rv[0]\n        return rv\n\n    def process_chunk(self, data, fields, weight):\n        my_weight = data[weight].d.sum(dtype=np.float64)\n        if my_weight == 0:\n            return [0.0 for field in fields] + [0.0 for field in fields] + [0.0]\n        my_means = [\n            (data[field].d * data[weight].d).sum(dtype=np.float64) / my_weight\n            for field in fields\n        ]\n        my_var2s = [\n            (data[weight].d * (data[field].d - my_mean) ** 2).sum(dtype=np.float64)\n            / my_weight\n            for field, my_mean in zip(fields, my_means, strict=True)\n        ]\n        return my_means + my_var2s + [my_weight]\n\n    def reduce_intermediate(self, values):\n        my_weight = values.pop(-1)\n        all_weight = my_weight.sum(dtype=np.float64)\n        rvals = []\n        for i in range(int(len(values) / 2)):\n            my_mean = values[i]\n            my_var2 = values[i + int(len(values) / 2)]\n            all_mean = (my_weight * my_mean).sum(dtype=np.float64) / all_weight\n            ret = [\n                (\n                    np.sqrt(\n                        (my_weight * (my_var2 + (my_mean - all_mean) ** 2)).sum(\n                            dtype=np.float64\n                        )\n                        / all_weight\n                    )\n                ),\n                all_mean,\n            ]\n            rvals.append(np.array(ret))\n        return rvals\n\n\nclass AngularMomentumVector(DerivedQuantity):\n    r\"\"\"\n    Calculates the angular momentum vector, using gas (grid-based) and/or particles.\n\n    The angular momentum vector is the mass-weighted mean specific angular momentum.\n    Returns a YTArray of the vector.\n\n    Parameters\n    ----------\n    use_gas : bool\n        Flag to include grid-based gas in the calculation. Gas is ignored if not\n        present.\n        Default: True\n    use_particles : bool\n        Flag to include particles in the calculation.  Particles are ignored\n        if not present.\n        Default: True\n    particle_type: string\n        Flag to specify the field type of the particles to use. Useful for\n        particle-based codes where you don't want to use all of the particles\n        in your calculation.\n        Default: 'all'\n\n    Examples\n    --------\n\n    Find angular momentum vector of galaxy in grid-based isolated galaxy dataset\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    ... ad = ds.all_data()\n    ... print(ad.quantities.angular_momentum_vector())\n    [-7.50868209e+26  1.06032596e+27  2.19274002e+29] cm**2/s\n    >>> # Find angular momentum vector of gas disk in particle-based dataset\n    >>> ds = yt.load(\"FIRE_M12i_ref11/snapshot_600.hdf5\")\n    ... _, c = ds.find_max((\"gas\", \"density\"))\n    ... sp = ds.sphere(c, (10, \"kpc\"))\n    ... search_args = dict(use_gas=False, use_particles=True, particle_type=\"PartType0\")\n    ... print(sp.quantities.angular_momentum_vector(**search_args))\n    [4.88104442e+28 7.38463362e+28 6.20030135e+28] cm**2/s\n\n    \"\"\"\n\n    def count_values(self, use_gas=True, use_particles=True, particle_type=\"all\"):\n        if use_particles and particle_type not in self.data_source.ds.particle_types:\n            raise YTParticleTypeNotFound(particle_type, self.data_source.ds)\n        num_vals = 0\n        # create the index if it doesn't exist yet\n        self.data_source.ds.index\n        self.particle_type = particle_type\n        self.use_gas = use_gas & ((\"gas\", \"mass\") in self.data_source.ds.field_info)\n        self.use_particles = use_particles & (\n            (self.particle_type, \"particle_mass\") in self.data_source.ds.field_info\n        )\n        if self.use_gas:\n            num_vals += 4\n        if self.use_particles:\n            num_vals += 4\n        self.num_vals = num_vals\n\n    def process_chunk(\n        self, data, use_gas=True, use_particles=False, particle_type=\"all\"\n    ):\n        rvals = []\n        if self.use_gas:\n            rvals.extend(\n                [\n                    (\n                        data[\"gas\", f\"specific_angular_momentum_{axis}\"]\n                        * data[\"gas\", \"mass\"]\n                    ).sum(dtype=np.float64)\n                    for axis in \"xyz\"\n                ]\n            )\n            rvals.append(data[\"gas\", \"mass\"].sum(dtype=np.float64))\n        if self.use_particles:\n            rvals.extend(\n                [\n                    (\n                        data[\n                            self.particle_type,\n                            f\"particle_specific_angular_momentum_{axis}\",\n                        ]\n                        * data[self.particle_type, \"particle_mass\"]\n                    ).sum(dtype=np.float64)\n                    for axis in \"xyz\"\n                ]\n            )\n            rvals.append(\n                data[self.particle_type, \"particle_mass\"].sum(dtype=np.float64)\n            )\n        return rvals\n\n    def reduce_intermediate(self, values):\n        jx = values.pop(0).sum(dtype=np.float64)\n        jy = values.pop(0).sum(dtype=np.float64)\n        jz = values.pop(0).sum(dtype=np.float64)\n        m = values.pop(0).sum(dtype=np.float64)\n        if values:\n            jx += values.pop(0).sum(dtype=np.float64)\n            jy += values.pop(0).sum(dtype=np.float64)\n            jz += values.pop(0).sum(dtype=np.float64)\n            m += values.pop(0).sum(dtype=np.float64)\n        return self.data_source.ds.arr([jx / m, jy / m, jz / m])\n\n\nclass Extrema(DerivedQuantity):\n    r\"\"\"\n    Calculates the min and max value of a field or list of fields.\n    Returns a YTArray for each field requested.  If one, a single YTArray\n    is returned, if many, a list of YTArrays in order of field list is\n    returned.  The first element of each YTArray is the minimum of the\n    field and the second is the maximum of the field.\n\n    Parameters\n    ----------\n    fields\n        The field or list of fields over which the extrema are to be\n        calculated.\n    non_zero : bool\n        If True, only positive values are considered in the calculation.\n        Default: False\n    check_finite : bool\n        If True, non-finite values will be explicitly excluded.\n        Default: False\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.extrema([(\"gas\", \"density\"), (\"gas\", \"temperature\")]))\n\n    \"\"\"\n\n    def count_values(self, fields, non_zero, *, check_finite=False):\n        self.num_vals = len(fields) * 2\n\n    def __call__(self, fields, non_zero=False, *, check_finite=False):\n        fields = list(iter_fields(fields))\n        rv = super().__call__(fields, non_zero, check_finite=check_finite)\n        if len(rv) == 1:\n            rv = rv[0]\n        return rv\n\n    def process_chunk(self, data, fields, non_zero, *, check_finite=False):\n        vals = []\n        for field in fields:\n            field = data._determine_fields(field)[0]\n            fd = data[field]\n            if non_zero:\n                fd = fd[fd > 0.0]\n            if check_finite:\n                fd = fd[np.isfinite(fd)]\n            if fd.size > 0:\n                vals += [fd.min(), fd.max()]\n            else:\n                vals += [\n                    array_like_field(data, HUGE, field),\n                    array_like_field(data, -HUGE, field),\n                ]\n        return vals\n\n    def reduce_intermediate(self, values):\n        # The values get turned into arrays here.\n        return [\n            self.data_source.ds.arr([mis.min(), mas.max()])\n            for mis, mas in zip(values[::2], values[1::2], strict=True)\n        ]\n\n\nclass SampleAtMaxFieldValues(DerivedQuantity):\n    _sign = -1\n    r\"\"\"\n    Calculates the maximum value and returns whichever fields are asked to be\n    sampled.\n\n    Parameters\n    ----------\n    field : tuple or string\n        The field over which the extrema are to be calculated.\n    sample_fields : list of fields\n        The fields to sample and return at the minimum value.\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.sample_at_max_field_values((\"gas\", \"density\"),\n    ...         [(\"gas\", \"temperature\"), (\"gas\", \"velocity_magnitude\")]))\n\n    \"\"\"\n\n    def count_values(self, field, sample_fields):\n        # field itself, then index, then the number of sample fields\n        self.num_vals = 1 + len(sample_fields)\n\n    def __call__(self, field, sample_fields):\n        rv = super().__call__(field, sample_fields)\n        if len(rv) == 1:\n            rv = rv[0]\n        return rv\n\n    def process_chunk(self, data, field, sample_fields):\n        field = data._determine_fields(field)[0]\n        ma = array_like_field(data, self._sign * HUGE, field)\n        vals = [array_like_field(data, -1, sf) for sf in sample_fields]\n        maxi = -1\n        if data[field].size > 0:\n            maxi = self._func(data[field])\n            ma = data[field][maxi]\n            vals = [data[sf][maxi] for sf in sample_fields]\n        return (ma,) + tuple(vals)\n\n    def reduce_intermediate(self, values):\n        i = self._func(values[0])  # ma is values[0]\n        return [val[i] for val in values]\n\n    def _func(self, arr):\n        return np.argmax(arr)\n\n\nclass MaxLocation(SampleAtMaxFieldValues):\n    r\"\"\"\n    Calculates the maximum value plus the x, y, and z position of the maximum.\n\n    Parameters\n    ----------\n\n    field : tuple or string\n        The field over which the extrema are to be calculated.\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.max_location((\"gas\", \"density\")))\n\n    \"\"\"\n\n    def __call__(self, field):\n        # Make sure we have an index\n        self.data_source.index\n        sample_fields = get_position_fields(field, self.data_source)\n        rv = super().__call__(field, sample_fields)\n        if len(rv) == 1:\n            rv = rv[0]\n        return rv\n\n\nclass SampleAtMinFieldValues(SampleAtMaxFieldValues):\n    _sign = 1\n    r\"\"\"\n    Calculates the minimum value and returns whichever fields are asked to be\n    sampled.\n\n    Parameters\n    ----------\n    field : tuple or string\n        The field over which the extrema are to be calculated.\n    sample_fields : list of fields\n        The fields to sample and return at the minimum value.\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.sample_at_min_field_values((\"gas\", \"density\"),\n    ...         [(\"gas\", \"temperature\"), (\"gas\", \"velocity_magnitude\")]))\n\n    \"\"\"\n\n    def _func(self, arr):\n        return np.argmin(arr)\n\n\nclass MinLocation(SampleAtMinFieldValues):\n    r\"\"\"\n    Calculates the minimum value plus the x, y, and z position of the minimum.\n\n    Parameters\n    ----------\n\n    field : tuple or string\n        The field over which the extrema are to be calculated.\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.min_location((\"gas\", \"density\")))\n\n    \"\"\"\n\n    def __call__(self, field):\n        # Make sure we have an index\n        self.data_source.index\n        sample_fields = get_position_fields(field, self.data_source)\n        rv = super().__call__(field, sample_fields)\n        if len(rv) == 1:\n            rv = rv[0]\n        return rv\n\n\nclass SpinParameter(DerivedQuantity):\n    r\"\"\"\n    Calculates the dimensionless spin parameter.\n\n    Given by Equation 3 of Peebles (1971, A&A, 11, 377), the spin parameter\n    is defined as\n\n    .. math::\n\n      \\lambda = (L * |E|^(1/2)) / (G * M^5/2),\n\n    where L is the total angular momentum, E is the total energy (kinetic and\n    potential), G is the gravitational constant, and M is the total mass.\n\n    Parameters\n    ----------\n    use_gas : bool\n        Flag to include gas in the calculation.  Gas is ignored if not\n        present.\n        Default: True\n    use_particles : bool\n        Flag to include particles in the calculation.  Particles are ignored\n        if not present.\n        Default: True\n    particle_type : str\n        Particle type to be used for Center of mass calculation when use_particle\n        = True.\n        Default: all\n\n    Examples\n    --------\n\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> print(ad.quantities.spin_parameter())\n\n    \"\"\"\n\n    def count_values(self, **kwargs):\n        self.num_vals = 3\n\n    def process_chunk(\n        self, data, use_gas=True, use_particles=True, particle_type=\"nbody\"\n    ):\n        if use_particles and particle_type not in self.data_source.ds.particle_types:\n            raise YTParticleTypeNotFound(particle_type, self.data_source.ds)\n        use_gas &= (\"gas\", \"mass\") in self.data_source.ds.field_info\n        use_particles &= (\n            particle_type,\n            \"particle_mass\",\n        ) in self.data_source.ds.field_info\n        e = data.ds.quan(0.0, \"erg\")\n        j = data.ds.quan(0.0, \"g*cm**2/s\")\n        m = data.ds.quan(0.0, \"g\")\n        if use_gas:\n            e += (data[\"gas\", \"kinetic_energy_density\"] * data[\"gas\", \"volume\"]).sum(\n                dtype=np.float64\n            )\n            j += data[\"gas\", \"angular_momentum_magnitude\"].sum(dtype=np.float64)\n            m += data[\"gas\", \"mass\"].sum(dtype=np.float64)\n        if use_particles:\n            e += (\n                data[particle_type, \"particle_velocity_magnitude\"] ** 2\n                * data[particle_type, \"particle_mass\"]\n            ).sum(dtype=np.float64)\n            j += data[particle_type, \"particle_angular_momentum_magnitude\"].sum(\n                dtype=np.float64\n            )\n            m += data[particle_type, \"particle_mass\"].sum(dtype=np.float64)\n        return (e, j, m)\n\n    def reduce_intermediate(self, values):\n        e = values.pop(0).sum(dtype=np.float64)\n        j = values.pop(0).sum(dtype=np.float64)\n        m = values.pop(0).sum(dtype=np.float64)\n        return j * np.sqrt(np.abs(e)) / m**2.5 / gravitational_constant_cgs\n"
  },
  {
    "path": "yt/data_objects/field_data.py",
    "content": "class YTFieldData(dict):\n    \"\"\"\n    A Container object for field data, instead of just having it be a dict.\n    \"\"\"\n\n    pass\n"
  },
  {
    "path": "yt/data_objects/image_array.py",
    "content": "import numpy as np\nfrom unyt import unyt_array\n\nfrom yt.config import ytcfg\nfrom yt.visualization.image_writer import write_bitmap, write_image\n\n\nclass ImageArray(unyt_array):\n    r\"\"\"A custom Numpy ndarray used for images.\n\n    This differs from ndarray in that you can optionally specify an\n    info dictionary which is used later in saving, and can be accessed with\n    ImageArray.info.\n\n    Parameters\n    ----------\n    input_array: array_like\n        A numpy ndarray, or list.\n\n    Other Parameters\n    ----------------\n    info: dictionary\n        Contains information to be stored with image.\n\n    Returns\n    -------\n    obj: ImageArray object\n\n    Raises\n    ------\n    None\n\n    See Also\n    --------\n    numpy.ndarray : Inherits\n\n    Notes\n    -----\n\n    References\n    ----------\n\n    Examples\n    --------\n    These are written in doctest format, and should illustrate how to\n    use the function.  Use the variables 'ds' for the dataset, 'pc' for\n    a plot collection, 'c' for a center, and 'L' for a vector.\n\n    >>> im = np.zeros([64, 128, 3])\n    >>> for i in range(im.shape[0]):\n    ...     for k in range(im.shape[2]):\n    ...         im[i, :, k] = np.linspace(0.0, 0.3 * k, im.shape[1])\n\n    >>> myinfo = {\n    ...     \"field\": \"dinosaurs\",\n    ...     \"east_vector\": np.array([1.0, 0.0, 0.0]),\n    ...     \"north_vector\": np.array([0.0, 0.0, 1.0]),\n    ...     \"normal_vector\": np.array([0.0, 1.0, 0.0]),\n    ...     \"width\": 0.245,\n    ...     \"units\": \"cm\",\n    ...     \"type\": \"rendering\",\n    ... }\n\n    >>> im_arr = ImageArray(im, info=myinfo)\n    >>> im_arr.save(\"test_ImageArray\")\n\n    Numpy ndarray documentation appended:\n\n    \"\"\"\n\n    def __new__(\n        cls,\n        input_array,\n        units=None,\n        registry=None,\n        info=None,\n        bypass_validation=False,\n    ):\n        obj = super().__new__(\n            cls, input_array, units, registry, bypass_validation=bypass_validation\n        )\n        if info is None:\n            info = {}\n        obj.info = info\n        return obj\n\n    def __array_finalize__(self, obj):\n        # see InfoArray.__array_finalize__ for comments\n        super().__array_finalize__(obj)\n        self.info = getattr(obj, \"info\", None)\n\n    def write_hdf5(self, filename, dataset_name=None):\n        r\"\"\"Writes ImageArray to hdf5 file.\n\n        Parameters\n        ----------\n        filename : string\n            The filename to create and write a dataset to\n        dataset_name : string\n            The name of the dataset to create in the file.\n\n        Examples\n        --------\n        >>> im = np.zeros([64, 128, 3])\n        >>> for i in range(im.shape[0]):\n        ...     for k in range(im.shape[2]):\n        ...         im[i, :, k] = np.linspace(0.0, 0.3 * k, im.shape[1])\n\n        >>> myinfo = {\n        ...     \"field\": \"dinosaurs\",\n        ...     \"east_vector\": np.array([1.0, 0.0, 0.0]),\n        ...     \"north_vector\": np.array([0.0, 0.0, 1.0]),\n        ...     \"normal_vector\": np.array([0.0, 1.0, 0.0]),\n        ...     \"width\": 0.245,\n        ...     \"units\": \"cm\",\n        ...     \"type\": \"rendering\",\n        ... }\n\n        >>> im_arr = ImageArray(im, info=myinfo)\n        >>> im_arr.write_hdf5(\"test_ImageArray.h5\")\n\n        \"\"\"\n        if dataset_name is None:\n            dataset_name = self.info.get(\"name\", \"image\")\n        super().write_hdf5(filename, dataset_name=dataset_name, info=self.info)\n\n    def add_background_color(self, background=\"black\", inline=True):\n        r\"\"\"Adds a background color to a 4-channel ImageArray\n\n        This adds a background color to a 4-channel ImageArray, by default\n        doing so inline.  The ImageArray must already be normalized to the\n        [0,1] range.\n\n        Parameters\n        ----------\n        background:\n            This can be used to set a background color for the image, and can\n            take several types of values:\n\n               * ``white``: white background, opaque\n               * ``black``: black background, opaque\n               * ``None``: transparent background\n               * 4-element array [r,g,b,a]: arbitrary rgba setting.\n\n            Default: 'black'\n\n        inline : boolean, optional\n            If True, original ImageArray is modified. If False, a copy is first\n            created, then modified. Default: True\n\n        Returns\n        -------\n        out: ImageArray\n            The modified ImageArray with a background color added.\n\n        Examples\n        --------\n        >>> im = np.zeros([64, 128, 4])\n        >>> for i in range(im.shape[0]):\n        ...     for k in range(im.shape[2]):\n        ...         im[i, :, k] = np.linspace(0.0, 10.0 * k, im.shape[1])\n\n        >>> im_arr = ImageArray(im)\n        >>> im_arr.rescale()\n        >>> new_im = im_arr.add_background_color([1.0, 0.0, 0.0, 1.0], inline=False)\n        >>> new_im.write_png(\"red_bg.png\")\n        >>> im_arr.add_background_color(\"black\")\n        >>> im_arr.write_png(\"black_bg.png\")\n        \"\"\"\n        assert self.shape[-1] == 4\n\n        if background is None:\n            background = (0.0, 0.0, 0.0, 0.0)\n        elif background == \"white\":\n            background = (1.0, 1.0, 1.0, 1.0)\n        elif background == \"black\":\n            background = (0.0, 0.0, 0.0, 1.0)\n\n        # Alpha blending to background\n        if inline:\n            out = self\n        else:\n            out = self.copy()\n\n        for i in range(3):\n            out[:, :, i] = self[:, :, i] * self[:, :, 3]\n            out[:, :, i] += background[i] * background[3] * (1.0 - self[:, :, 3])\n        out[:, :, 3] = self[:, :, 3] + background[3] * (1.0 - self[:, :, 3])\n        return out\n\n    def rescale(self, cmax=None, amax=None, inline=True):\n        r\"\"\"Rescales the image to be in [0,1] range.\n\n        Parameters\n        ----------\n        cmax : float, optional\n            Normalization value to use for rgb channels. Defaults to None,\n            corresponding to using the maximum value in the rgb channels.\n        amax : float, optional\n            Normalization value to use for alpha channel. Defaults to None,\n            corresponding to using the maximum value in the alpha channel.\n        inline : boolean, optional\n            Specifies whether or not the rescaling is done inline. If false,\n            a new copy of the ImageArray will be created, returned.\n            Default:True.\n\n        Returns\n        -------\n        out: ImageArray\n            The rescaled ImageArray, clipped to the [0,1] range.\n\n        Notes\n        -----\n        This requires that the shape of the ImageArray to have a length of 3,\n        and for the third dimension to be >= 3.  If the third dimension has\n        a shape of 4, the alpha channel will also be rescaled.\n\n        Examples\n        --------\n        >>> im = np.zeros([64, 128, 4])\n        >>> for i in range(im.shape[0]):\n        ...     for k in range(im.shape[2]):\n        ...         im[i, :, k] = np.linspace(0.0, 0.3 * k, im.shape[1])\n\n        >>> im = ImageArray(im)\n        >>> im.write_png(\"original.png\")\n        >>> im.rescale()\n        >>> im.write_png(\"normalized.png\")\n\n        \"\"\"\n        assert len(self.shape) == 3\n        assert self.shape[2] >= 3\n        if inline:\n            out = self\n        else:\n            out = self.copy()\n\n        if cmax is None:\n            cmax = self[:, :, :3].sum(axis=2).max()\n        if cmax > 0.0:\n            np.multiply(self[:, :, :3], 1.0 / cmax, out[:, :, :3])\n\n        if self.shape[2] == 4:\n            if amax is None:\n                amax = self[:, :, 3].max()\n            if amax > 0.0:\n                np.multiply(self[:, :, 3], 1.0 / amax, out[:, :, 3])\n\n        np.clip(out, 0.0, 1.0, out)\n        return out\n\n    def write_png(\n        self,\n        filename,\n        sigma_clip=None,\n        background=\"black\",\n        rescale=True,\n    ):\n        r\"\"\"Writes ImageArray to png file.\n\n        Parameters\n        ----------\n        filename : string\n            Filename to save to.  If None, PNG contents will be returned as a\n            string.\n\n        sigma_clip : float, optional\n            Image will be clipped before saving to the standard deviation\n            of the image multiplied by this value.  Useful for enhancing\n            images. Default: None\n        background:\n            This can be used to set a background color for the image, and can\n            take several types of values:\n\n               * ``white``: white background, opaque\n               * ``black``: black background, opaque\n               * ``None``: transparent background\n               * 4-element array [r,g,b,a]: arbitrary rgba setting.\n\n            Default: 'black'\n\n        rescale : boolean, optional\n            If True, will write out a rescaled image (without modifying the\n            original image). Default: True\n\n        Examples\n        --------\n        >>> im = np.zeros([64, 128, 4])\n        >>> for i in range(im.shape[0]):\n        ...     for k in range(im.shape[2]):\n        ...         im[i, :, k] = np.linspace(0.0, 10.0 * k, im.shape[1])\n\n        >>> im_arr = ImageArray(im)\n        >>> im_arr.write_png(\"standard.png\")\n        >>> im_arr.write_png(\"non-scaled.png\", rescale=False)\n        >>> im_arr.write_png(\"black_bg.png\", background=\"black\")\n        >>> im_arr.write_png(\"white_bg.png\", background=\"white\")\n        >>> im_arr.write_png(\"green_bg.png\", background=[0, 1, 0, 1])\n        >>> im_arr.write_png(\"transparent_bg.png\", background=None)\n\n        \"\"\"\n        if rescale:\n            scaled = self.rescale(inline=False)\n        else:\n            scaled = self\n\n        if self.shape[-1] == 4:\n            out = scaled.add_background_color(background, inline=False)\n        else:\n            out = scaled\n\n        if filename is not None and filename[-4:] != \".png\":\n            filename += \".png\"\n\n        if sigma_clip is not None:\n            clip_value = self._clipping_value(sigma_clip, im=out)\n            return write_bitmap(out.swapaxes(0, 1), filename, clip_value)\n        else:\n            return write_bitmap(out.swapaxes(0, 1), filename)\n\n    def write_image(\n        self,\n        filename,\n        color_bounds=None,\n        channel=None,\n        cmap_name=None,\n        func=lambda x: x,\n    ):\n        r\"\"\"Writes a single channel of the ImageArray to a png file.\n\n        Parameters\n        ----------\n        filename : string\n            Note filename not be modified.\n\n        Other Parameters\n        ----------------\n        channel: int\n            Which channel to write out as an image. Defaults to 0\n        cmap_name: string\n            Name of the colormap to be used.\n        color_bounds : tuple of floats, optional\n            The min and max to scale between.  Outlying values will be clipped.\n        cmap_name : string, optional\n            An acceptable colormap.  See either yt.visualization.color_maps or\n            https://scipy-cookbook.readthedocs.io/items/Matplotlib_Show_colormaps.html .\n        func : function, optional\n            A function to transform the buffer before applying a colormap.\n\n        Returns\n        -------\n        scaled_image : uint8 image that has been saved\n\n        Examples\n        --------\n\n        >>> im = np.zeros([64, 128])\n        >>> for i in range(im.shape[0]):\n        ...     im[i, :] = np.linspace(0.0, 0.3 * i, im.shape[1])\n\n        >>> myinfo = {\n        ...     \"field\": \"dinosaurs\",\n        ...     \"east_vector\": np.array([1.0, 0.0, 0.0]),\n        ...     \"north_vector\": np.array([0.0, 0.0, 1.0]),\n        ...     \"normal_vector\": np.array([0.0, 1.0, 0.0]),\n        ...     \"width\": 0.245,\n        ...     \"units\": \"cm\",\n        ...     \"type\": \"rendering\",\n        ... }\n\n        >>> im_arr = ImageArray(im, info=myinfo)\n        >>> im_arr.write_image(\"test_ImageArray.png\")\n\n        \"\"\"\n        if cmap_name is None:\n            cmap_name = ytcfg.get(\"yt\", \"default_colormap\")\n        if filename is not None and filename[-4:] != \".png\":\n            filename += \".png\"\n\n        # TODO: Write info dict as png metadata\n        if channel is None:\n            return write_image(\n                self.swapaxes(0, 1).to_ndarray(),\n                filename,\n                color_bounds=color_bounds,\n                cmap_name=cmap_name,\n                func=func,\n            )\n        else:\n            return write_image(\n                self.swapaxes(0, 1)[:, :, channel].to_ndarray(),\n                filename,\n                color_bounds=color_bounds,\n                cmap_name=cmap_name,\n                func=func,\n            )\n\n    def save(self, filename, png=True, hdf5=True, dataset_name=None):\n        \"\"\"\n        Saves ImageArray.\n\n        Arguments:\n          filename: string\n            This should not contain the extension type (.png, .h5, ...)\n\n        Optional Arguments:\n          png: boolean, default True\n            Save to a png\n\n          hdf5: boolean, default True\n            Save to hdf5 file, including info dictionary as attributes.\n\n        \"\"\"\n        if png:\n            if not filename.endswith(\".png\"):\n                filename = filename + \".png\"\n            if len(self.shape) > 2:\n                self.write_png(filename)\n            else:\n                self.write_image(filename)\n        if hdf5:\n            if not filename.endswith(\".h5\"):\n                filename = filename + \".h5\"\n            self.write_hdf5(filename, dataset_name)\n\n    def _clipping_value(self, sigma_clip, im=None):\n        # return the max value to clip with given a sigma_clip value. If im\n        # is None, the current instance is used\n        if im is None:\n            im = self\n        nz = im[:, :, :3][im[:, :, :3].nonzero()]\n        return nz.mean() + sigma_clip * nz.std()\n"
  },
  {
    "path": "yt/data_objects/index_subobjects/__init__.py",
    "content": "from .grid_patch import AMRGridPatch\nfrom .octree_subset import OctreeSubset\n"
  },
  {
    "path": "yt/data_objects/index_subobjects/grid_patch.py",
    "content": "import weakref\n\nimport numpy as np\n\nimport yt.geometry.particle_deposit as particle_deposit\nfrom yt._typing import FieldKey\nfrom yt.config import ytcfg\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n)\nfrom yt.funcs import is_sequence\nfrom yt.geometry.selection_routines import convert_mask_to_indices\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.exceptions import (\n    YTFieldTypeNotFound,\n    YTParticleDepositionNotImplemented,\n)\nfrom yt.utilities.lib.interpolators import ghost_zone_interpolate\nfrom yt.utilities.lib.mesh_utilities import clamp_edges\nfrom yt.utilities.nodal_data_utils import get_nodal_slices\n\n\nclass AMRGridPatch(YTSelectionContainer):\n    _spatial = True\n    _num_ghost_zones = 0\n    _grids = None\n    _id_offset = 1\n    _cache_mask = True\n\n    _type_name = \"grid\"\n    _skip_add = True\n    _con_args = (\"id\", \"filename\")\n    _container_fields = (\n        (\"index\", \"dx\"),\n        (\"index\", \"dy\"),\n        (\"index\", \"dz\"),\n        (\"index\", \"x\"),\n        (\"index\", \"y\"),\n        (\"index\", \"z\"),\n    )\n    OverlappingSiblings = None\n\n    def __init__(self, id, filename=None, index=None):\n        super().__init__(index.dataset, None)\n        self.id = id\n        self._child_mask = self._child_indices = self._child_index_mask = None\n        self.ds = index.dataset\n        self._index = weakref.proxy(index)\n        self.start_index = None\n        self.filename = filename\n        self._last_mask = None\n        self._last_count = -1\n        self._last_selector_id = None\n\n    def get_global_startindex(self):\n        \"\"\"\n        Return the integer starting index for each dimension at the current\n        level.\n\n        \"\"\"\n        if self.start_index is not None:\n            return self.start_index\n        if self.Parent is None:\n            left = self.LeftEdge.d - self.ds.domain_left_edge.d\n            start_index = left / self.dds.d\n            return np.rint(start_index).astype(\"int64\").ravel()\n\n        pdx = self.Parent.dds.d\n        di = np.rint((self.LeftEdge.d - self.Parent.LeftEdge.d) / pdx)\n        start_index = self.Parent.get_global_startindex() + di\n        self.start_index = (start_index * self.ds.refine_by).astype(\"int64\").ravel()\n        return self.start_index\n\n    def __getitem__(self, key):\n        tr = super().__getitem__(key)\n        try:\n            fields = self._determine_fields(key)\n        except YTFieldTypeNotFound:\n            return tr\n        finfo = self.ds._get_field_info(fields[0])\n        if not finfo.sampling_type == \"particle\":\n            num_nodes = 2 ** sum(finfo.nodal_flag)\n            new_shape = list(self.ActiveDimensions)\n            if num_nodes > 1:\n                new_shape += [num_nodes]\n            return tr.reshape(new_shape)\n        return tr\n\n    def convert(self, datatype):\n        \"\"\"\n        This will attempt to convert a given unit to cgs from code units. It\n        either returns the multiplicative factor or throws a KeyError.\n\n        \"\"\"\n        return self.ds[datatype]\n\n    @property\n    def shape(self):\n        return self.ActiveDimensions\n\n    def _reshape_vals(self, arr):\n        if len(arr.shape) == 3:\n            return arr\n        return arr.reshape(self.ActiveDimensions, order=\"C\")\n\n    def _generate_container_field(self, field):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        if field == (\"index\", \"dx\"):\n            tr = self._current_chunk.fwidth[:, 0]\n        elif field == (\"index\", \"dy\"):\n            tr = self._current_chunk.fwidth[:, 1]\n        elif field == (\"index\", \"dz\"):\n            tr = self._current_chunk.fwidth[:, 2]\n        elif field == (\"index\", \"x\"):\n            tr = self._current_chunk.fcoords[:, 0]\n        elif field == (\"index\", \"y\"):\n            tr = self._current_chunk.fcoords[:, 1]\n        elif field == (\"index\", \"z\"):\n            tr = self._current_chunk.fcoords[:, 2]\n        return self._reshape_vals(tr)\n\n    def _setup_dx(self):\n        # So first we figure out what the index is.  We don't assume\n        # that dx=dy=dz, at least here.  We probably do elsewhere.\n        id = self.id - self._id_offset\n        ds = self.ds\n        index = self.index\n        if self.Parent is not None:\n            if not hasattr(self.Parent, \"dds\"):\n                self.Parent._setup_dx()\n            self.dds = self.Parent.dds.d / self.ds.refine_by\n        else:\n            LE, RE = (index.grid_left_edge[id, :].d, index.grid_right_edge[id, :].d)\n            self.dds = (RE - LE) / self.ActiveDimensions\n        if self.ds.dimensionality < 3:\n            self.dds[2] = ds.domain_right_edge[2] - ds.domain_left_edge[2]\n        elif self.ds.dimensionality < 2:\n            self.dds[1] = ds.domain_right_edge[1] - ds.domain_left_edge[1]\n        self.dds = self.dds.view(YTArray)\n        self.dds.units = self.index.grid_left_edge.units\n\n    def __repr__(self):\n        cls_name = self.__class__.__name__\n        return f\"{cls_name}_{self.id:04d} ({self.ActiveDimensions})\"\n\n    def __int__(self):\n        return self.id\n\n    def clear_data(self):\n        \"\"\"\n        Clear out the following things: child_mask, child_indices, all fields,\n        all field parameters.\n\n        \"\"\"\n        super().clear_data()\n        self._setup_dx()\n\n    def _prepare_grid(self):\n        \"\"\"Copies all the appropriate attributes from the index.\"\"\"\n        # This is definitely the slowest part of generating the index\n        # Now we give it pointers to all of its attributes\n        # Note that to keep in line with Enzo, we have broken PEP-8\n        h = self.index  # cache it\n        my_ind = self.id - self._id_offset\n        self.ActiveDimensions = h.grid_dimensions[my_ind]\n        self.LeftEdge = h.grid_left_edge[my_ind]\n        self.RightEdge = h.grid_right_edge[my_ind]\n        # This can be expensive so we allow people to disable this behavior\n        # via a config option\n        if ytcfg.get(\"yt\", \"reconstruct_index\"):\n            if is_sequence(self.Parent) and len(self.Parent) > 0:\n                p = self.Parent[0]\n            else:\n                p = self.Parent\n            if p is not None and p != []:\n                # clamp grid edges to an integer multiple of the parent cell\n                # width\n                clamp_edges(self.LeftEdge, p.LeftEdge, p.dds)\n                clamp_edges(self.RightEdge, p.RightEdge, p.dds)\n        h.grid_levels[my_ind, 0] = self.Level\n        # This might be needed for streaming formats\n        # self.Time = h.gridTimes[my_ind,0]\n        self.NumberOfParticles = h.grid_particle_count[my_ind, 0]\n\n    def get_position(self, index):\n        \"\"\"Returns center position of an *index*.\"\"\"\n        pos = (index + 0.5) * self.dds + self.LeftEdge\n        return pos\n\n    def _fill_child_mask(self, child, mask, tofill, dlevel=1):\n        rf = self.ds.refine_by\n        if dlevel != 1:\n            rf = rf**dlevel\n        gi, cgi = self.get_global_startindex(), child.get_global_startindex()\n        startIndex = np.maximum(0, cgi // rf - gi)\n        endIndex = np.minimum(\n            (cgi + child.ActiveDimensions) // rf - gi, self.ActiveDimensions\n        )\n        endIndex += startIndex == endIndex\n        mask[\n            startIndex[0] : endIndex[0],\n            startIndex[1] : endIndex[1],\n            startIndex[2] : endIndex[2],\n        ] = tofill\n\n    @property\n    def child_mask(self):\n        \"\"\"\n        Generates self.child_mask, which is zero where child grids exist (and\n        thus, where higher resolution data is available).\n\n        \"\"\"\n        child_mask = np.ones(self.ActiveDimensions, \"bool\")\n        for child in self.Children:\n            self._fill_child_mask(child, child_mask, 0)\n        for sibling in self.OverlappingSiblings or []:\n            self._fill_child_mask(sibling, child_mask, 0, dlevel=0)\n        return child_mask\n\n    @property\n    def child_indices(self):\n        return self.child_mask == 0\n\n    @property\n    def child_index_mask(self):\n        \"\"\"\n        Generates self.child_index_mask, which is -1 where there is no child,\n        and otherwise has the ID of the grid that resides there.\n\n        \"\"\"\n        child_index_mask = np.zeros(self.ActiveDimensions, \"int32\") - 1\n        for child in self.Children:\n            self._fill_child_mask(child, child_index_mask, child.id)\n        for sibling in self.OverlappingSiblings or []:\n            self._fill_child_mask(sibling, child_index_mask, sibling.id, dlevel=0)\n        return child_index_mask\n\n    def retrieve_ghost_zones(self, n_zones, fields, all_levels=False, smoothed=False):\n        # We will attempt this by creating a datacube that is exactly bigger\n        # than the grid by nZones*dx in each direction\n        nl = self.get_global_startindex() - n_zones\n        new_left_edge = nl * self.dds + self.ds.domain_left_edge\n\n        # Something different needs to be done for the root grid, though\n        level = self.Level\n        if all_levels:\n            level = self.index.max_level + 1\n        kwargs = {\n            \"dims\": self.ActiveDimensions + 2 * n_zones,\n            \"num_ghost_zones\": n_zones,\n            \"use_pbar\": False,\n            \"fields\": fields,\n        }\n        # This should update the arguments to set the field parameters to be\n        # those of this grid.\n        field_parameters = {}\n        field_parameters.update(self.field_parameters)\n        if smoothed:\n            cube = self.ds.smoothed_covering_grid(\n                level, new_left_edge, field_parameters=field_parameters, **kwargs\n            )\n        else:\n            cube = self.ds.covering_grid(\n                level, new_left_edge, field_parameters=field_parameters, **kwargs\n            )\n        cube._base_grid = self\n        return cube\n\n    def get_vertex_centered_data(\n        self,\n        fields: list[FieldKey],\n        smoothed: bool = True,\n        no_ghost: bool = False,\n    ):\n        # Make sure the field list has only unique entries\n        fields = list(set(fields))\n        new_fields = {}\n        for field in fields:\n            finfo = self.ds._get_field_info(field)\n            new_fields[field] = self.ds.arr(\n                np.zeros(self.ActiveDimensions + 1), finfo.output_units\n            )\n        if no_ghost:\n            for field in fields:\n                # Ensure we have the native endianness in this array.  Avoid making\n                # a copy if possible.\n                old_field = np.asarray(self[field], dtype=\"=f8\")\n                # We'll use the ghost zone routine, which will naturally\n                # extrapolate here.\n                input_left = np.array([0.5, 0.5, 0.5], dtype=\"float64\")\n                output_left = np.array([0.0, 0.0, 0.0], dtype=\"float64\")\n                # rf = 1 here\n                ghost_zone_interpolate(\n                    1, old_field, input_left, new_fields[field], output_left\n                )\n        else:\n            cg = self.retrieve_ghost_zones(1, fields, smoothed=smoothed)\n            for field in fields:\n                src = cg[field].in_units(new_fields[field].units).d\n                dest = new_fields[field].d\n                np.add(dest, src[1:, 1:, 1:], dest)\n                np.add(dest, src[:-1, 1:, 1:], dest)\n                np.add(dest, src[1:, :-1, 1:], dest)\n                np.add(dest, src[1:, 1:, :-1], dest)\n                np.add(dest, src[:-1, 1:, :-1], dest)\n                np.add(dest, src[1:, :-1, :-1], dest)\n                np.add(dest, src[:-1, :-1, 1:], dest)\n                np.add(dest, src[:-1, :-1, :-1], dest)\n                np.multiply(dest, 0.125, dest)\n\n        return new_fields\n\n    def select_icoords(self, dobj):\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty((0, 3), dtype=\"int64\")\n        coords = convert_mask_to_indices(mask, self._last_count)\n        coords += self.get_global_startindex()[None, :]\n        return coords\n\n    def select_fcoords(self, dobj):\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty((0, 3), dtype=\"float64\")\n        coords = convert_mask_to_indices(mask, self._last_count).astype(\"float64\")\n        coords += 0.5\n        coords *= self.dds.d[None, :]\n        coords += self.LeftEdge.d[None, :]\n        return coords\n\n    def select_fwidth(self, dobj):\n        count = self.count(dobj.selector)\n        if count == 0:\n            return np.empty((0, 3), dtype=\"float64\")\n        coords = np.empty((count, 3), dtype=\"float64\")\n        for axis in range(3):\n            coords[:, axis] = self.dds.d[axis]\n        return coords\n\n    def select_ires(self, dobj):\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty(0, dtype=\"int64\")\n        coords = np.empty(self._last_count, dtype=\"int64\")\n        coords[:] = self.Level\n        return coords\n\n    def select_tcoords(self, dobj):\n        dt, t = dobj.selector.get_dt(self)\n        return dt, t\n\n    def smooth(self, *args, **kwargs):\n        raise NotImplementedError\n\n    def particle_operation(self, *args, **kwargs):\n        raise NotImplementedError\n\n    def deposit(self, positions, fields=None, method=None, kernel_name=\"cubic\"):\n        # Here we perform our particle deposition.\n        cls = getattr(particle_deposit, f\"deposit_{method}\", None)\n        if cls is None:\n            raise YTParticleDepositionNotImplemented(method)\n        # We allocate number of zones, not number of octs. Everything\n        # inside this is Fortran ordered because of the ordering in the\n        # octree deposit routines, so we reverse it here to match the\n        # convention there\n        nvals = tuple(self.ActiveDimensions[::-1])\n        # append a dummy dimension because we are only depositing onto\n        # one grid\n        op = cls(nvals + (1,), kernel_name)\n        op.initialize()\n        if positions.size > 0:\n            op.process_grid(self, positions, fields)\n        vals = op.finalize()\n        if vals is None:\n            return\n        # Fortran-ordered, so transpose.\n        vals = vals.transpose()\n        # squeeze dummy dimension we appended above\n        return np.squeeze(vals, axis=0)\n\n    def select_blocks(self, selector):\n        mask = self._get_selector_mask(selector)\n        yield self, mask\n\n    def _get_selector_mask(self, selector):\n        if self._cache_mask and hash(selector) == self._last_selector_id:\n            mask = self._last_mask\n        else:\n            mask, count = selector.fill_mask_regular_grid(self)\n            if self._cache_mask:\n                self._last_mask = mask\n            self._last_selector_id = hash(selector)\n            if mask is None:\n                self._last_count = 0\n            else:\n                self._last_count = count\n        return mask\n\n    def select(self, selector, source, dest, offset):\n        mask = self._get_selector_mask(selector)\n        count = self.count(selector)\n        if count == 0:\n            return 0\n        dim = np.squeeze(self.ds.dimensionality)\n        nodal_flag = source.shape[:dim] - self.ActiveDimensions[:dim]\n        if sum(nodal_flag) == 0:\n            dest[offset : offset + count] = source[mask]\n        else:\n            slices = get_nodal_slices(source.shape, nodal_flag, dim)\n            for i, sl in enumerate(slices):\n                dest[offset : offset + count, i] = source[tuple(sl)][np.squeeze(mask)]\n        return count\n\n    def count(self, selector):\n        mask = self._get_selector_mask(selector)\n        if mask is None:\n            return 0\n        return self._last_count\n\n    def count_particles(self, selector, x, y, z):\n        # We don't cache the selector results\n        count = selector.count_points(x, y, z, 0.0)\n        return count\n\n    def select_particles(self, selector, x, y, z):\n        mask = selector.select_points(x, y, z, 0.0)\n        return mask\n"
  },
  {
    "path": "yt/data_objects/index_subobjects/octree_subset.py",
    "content": "import abc\nfrom contextlib import contextmanager\nfrom functools import cached_property\nfrom itertools import product, repeat\n\nimport numpy as np\nfrom unyt import unyt_array\n\nimport yt.geometry.particle_deposit as particle_deposit\nimport yt.geometry.particle_smooth as particle_smooth\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n)\nfrom yt.geometry.particle_oct_container import ParticleOctreeContainer\nfrom yt.units.dimensions import length  # type: ignore\nfrom yt.utilities.exceptions import (\n    YTFieldTypeNotFound,\n    YTInvalidPositionArray,\n    YTParticleDepositionNotImplemented,\n)\nfrom yt.utilities.lib.geometry_utils import compute_morton\nfrom yt.utilities.logger import ytLogger as mylog\n\n\ndef cell_count_cache(func):\n    def cc_cache_func(self, dobj):\n        if hash(dobj.selector) != self._last_selector_id:\n            self._cell_count = -1\n        rv = func(self, dobj)\n        self._cell_count = rv.shape[0]\n        self._last_selector_id = hash(dobj.selector)\n        return rv\n\n    return cc_cache_func\n\n\nclass OctreeSubset(YTSelectionContainer, abc.ABC):\n    _spatial = True\n    _num_ghost_zones = 0\n    _type_name = \"octree_subset\"\n    _skip_add = True\n    _con_args: tuple[str, ...] = (\"base_region\", \"domain\", \"ds\")\n    _domain_offset = 0\n    _cell_count = -1\n    _block_order = \"C\"\n\n    def __init__(self, base_region, domain, ds, num_zones=2, num_ghost_zones=0):\n        super().__init__(ds, None)\n        self._num_zones = num_zones\n        self._num_ghost_zones = num_ghost_zones\n        self.domain = domain\n        self.domain_id = domain.domain_id\n        self.ds = domain.ds\n        self._index = self.ds.index\n        self._last_mask = None\n        self._last_selector_id = None\n        self.base_region = base_region\n        self.base_selector = base_region.selector\n\n    @property\n    @abc.abstractmethod\n    def oct_handler(self):\n        # In charge of returning the oct_handler\n        pass\n\n    def __getitem__(self, key):\n        tr = super().__getitem__(key)\n        try:\n            fields = self._determine_fields(key)\n        except YTFieldTypeNotFound:\n            return tr\n        finfo = self.ds._get_field_info(fields[0])\n        if not finfo.sampling_type == \"particle\":\n            # We may need to reshape the field, if it is being queried from\n            # field_data.  If it's already cached, it just passes through.\n            if len(tr.shape) < 4:\n                tr = self._reshape_vals(tr)\n            return tr\n        return tr\n\n    @property\n    def nz(self):\n        return self._num_zones + 2 * self._num_ghost_zones\n\n    def get_bbox(self):\n        return self.base_region.get_bbox()\n\n    def _reshape_vals(self, arr):\n        nz = self.nz\n        if len(arr.shape) <= 2:\n            n_oct = arr.shape[0] // (nz**3)\n        elif arr.shape[-1] == 3:\n            n_oct = arr.shape[-2]\n        else:\n            n_oct = arr.shape[-1]\n        if arr.size == nz * nz * nz * n_oct:\n            new_shape = (nz, nz, nz, n_oct)\n        elif arr.size == nz * nz * nz * n_oct * 3:\n            new_shape = (nz, nz, nz, n_oct, 3)\n        else:\n            raise RuntimeError\n        # Note that if arr is already F-contiguous, this *shouldn't* copy the\n        # data.  But, it might.  However, I can't seem to figure out how to\n        # make the assignment to .shape, which *won't* copy the data, make the\n        # resultant array viewed in Fortran order.\n        arr = arr.reshape(new_shape, order=\"F\")\n        return arr\n\n    def mask_refinement(self, selector):\n        mask = self.oct_handler.mask(selector, domain_id=self.domain_id)\n        return mask\n\n    def select_blocks(self, selector):\n        mask = self.oct_handler.mask(selector, domain_id=self.domain_id)\n        slicer = OctreeSubsetBlockSlice(self, self.ds)\n        for i, sl in slicer:\n            yield sl, np.atleast_3d(mask[i, ...])\n\n    def select_tcoords(self, dobj):\n        # These will not be pre-allocated, which can be a problem for speed and\n        # memory usage.\n        dts, ts = [], []\n        for sl, mask in self.select_blocks(dobj.selector):\n            sl.child_mask = np.asfortranarray(mask)\n            dt, t = dobj.selector.get_dt(sl)\n            dts.append(dt)\n            ts.append(t)\n        if len(dts) == len(ts) == 0:\n            return np.empty(0, \"f8\"), np.empty(0, \"f8\")\n        return np.concatenate(dts), np.concatenate(ts)\n\n    @cached_property\n    def domain_ind(self):\n        return self.oct_handler.domain_ind(self.selector)\n\n    def deposit(self, positions, fields=None, method=None, kernel_name=\"cubic\"):\n        r\"\"\"Operate on the mesh, in a particle-against-mesh fashion, with\n        exclusively local input.\n\n        This uses the octree indexing system to call a \"deposition\" operation\n        (defined in yt/geometry/particle_deposit.pyx) that can take input from\n        several particles (local to the mesh) and construct some value on the\n        mesh.  The canonical example is to sum the total mass in a mesh cell\n        and then divide by its volume.\n\n        Parameters\n        ----------\n        positions : array_like (Nx3)\n            The positions of all of the particles to be examined.  A new\n            indexed octree will be constructed on these particles.\n        fields : list of arrays\n            All the necessary fields for computing the particle operation.  For\n            instance, this might include mass, velocity, etc.\n        method : string\n            This is the \"method name\" which will be looked up in the\n            `particle_deposit` namespace as `methodname_deposit`.  Current\n            methods include `count`, `simple_smooth`, `sum`, `std`, `cic`,\n            `weighted_mean`, `mesh_id`, and `nearest`.\n        kernel_name : string, default 'cubic'\n            This is the name of the smoothing kernel to use. Current supported\n            kernel names include `cubic`, `quartic`, `quintic`, `wendland2`,\n            `wendland4`, and `wendland6`.\n\n        Returns\n        -------\n        List of fortran-ordered, mesh-like arrays.\n        \"\"\"\n        # Here we perform our particle deposition.\n        if fields is None:\n            fields = []\n        cls = getattr(particle_deposit, f\"deposit_{method}\", None)\n        if cls is None:\n            raise YTParticleDepositionNotImplemented(method)\n        nz = self.nz\n        nvals = (nz, nz, nz, (self.domain_ind >= 0).sum())\n        if np.max(self.domain_ind) >= nvals[-1]:\n            print(\n                f\"nocts, domain_ind >= 0, max {self.oct_handler.nocts} {nvals[-1]} {np.max(self.domain_ind)}\"\n            )\n            raise Exception()\n        # We allocate number of zones, not number of octs\n        op = cls(nvals, kernel_name)\n        op.initialize()\n        mylog.debug(\n            \"Depositing %s (%s^3) particles into %s Octs\",\n            positions.shape[0],\n            positions.shape[0] ** 0.3333333,\n            nvals[-1],\n        )\n        positions.convert_to_units(\"code_length\")\n        pos = positions.d\n        # We should not need the following if we know in advance all our fields\n        # need no casting.\n        fields = [np.ascontiguousarray(f, dtype=\"float64\") for f in fields]\n        op.process_octree(\n            self.oct_handler,\n            self.domain_ind,\n            pos,\n            fields,\n            self.domain_id,\n            self._domain_offset,\n        )\n        vals = op.finalize()\n        if vals is None:\n            return\n        return np.asfortranarray(vals)\n\n    def mesh_sampling_particle_field(self, positions, mesh_field, lvlmax=None):\n        r\"\"\"Operate on the particles, in a mesh-against-particle\n        fashion, with exclusively local input.\n\n        This uses the octree indexing system to call a \"mesh\n        sampling\" operation (defined in yt/geometry/particle_deposit.pyx).\n        For each particle, the function returns the value of the cell\n        containing the particle.\n\n        Parameters\n        ----------\n        positions : array_like (Nx3)\n            The positions of all of the particles to be examined.\n        mesh_field : array_like (M,)\n            The value of the field to deposit.\n        lvlmax : array_like (N), optional\n            If provided, the maximum level where to look for cells\n\n        Returns\n        -------\n        List of fortran-ordered, particle-like arrays containing the\n        value of the mesh at the location of the particles.\n        \"\"\"\n        # Here we perform our particle deposition.\n        npart = positions.shape[0]\n        nocts = (self.domain_ind >= 0).sum()\n        # We allocate number of zones, not number of octs\n        op = particle_deposit.CellIdentifier(npart, \"none\")\n        op.initialize(npart)\n        mylog.debug(\n            \"Depositing %s Octs onto %s (%s^3) particles\",\n            nocts,\n            positions.shape[0],\n            positions.shape[0] ** 0.3333333,\n        )\n        pos = positions.to_value(\"code_length\").astype(\"float64\", copy=False)\n\n        op.process_octree(\n            self.oct_handler,\n            self.domain_ind,\n            pos,\n            None,\n            self.domain_id,\n            self._domain_offset,\n            lvlmax=lvlmax,\n        )\n\n        igrid, icell = op.finalize()\n\n        igrid = np.asfortranarray(igrid)\n        icell = np.asfortranarray(icell)\n\n        # Some particle may fall outside of the local domain, so we\n        # need to be careful here\n        ids = igrid * 8 + icell\n        mask = ids >= 0\n\n        # Fill the return array\n        ret = np.empty(npart)\n        ret[mask] = mesh_field[ids[mask]]\n        ret[~mask] = np.nan\n\n        return ret\n\n    def smooth(\n        self,\n        positions,\n        fields=None,\n        index_fields=None,\n        method=None,\n        create_octree=False,\n        nneighbors=64,\n        kernel_name=\"cubic\",\n    ):\n        r\"\"\"Operate on the mesh, in a particle-against-mesh fashion, with\n        non-local input.\n\n        This uses the octree indexing system to call a \"smoothing\" operation\n        (defined in yt/geometry/particle_smooth.pyx) that can take input from\n        several (non-local) particles and construct some value on the mesh.\n        The canonical example is to conduct a smoothing kernel operation on the\n        mesh.\n\n        Parameters\n        ----------\n        positions : array_like (Nx3)\n            The positions of all of the particles to be examined.  A new\n            indexed octree will be constructed on these particles.\n        fields : list of arrays\n            All the necessary fields for computing the particle operation.  For\n            instance, this might include mass, velocity, etc.\n        index_fields : list of arrays\n            All of the fields defined on the mesh that may be used as input to\n            the operation.\n        method : string\n            This is the \"method name\" which will be looked up in the\n            `particle_smooth` namespace as `methodname_smooth`.  Current\n            methods include `volume_weighted`, `nearest`, `idw`,\n            `nth_neighbor`, and `density`.\n        create_octree : bool\n            Should we construct a new octree for indexing the particles?  In\n            cases where we are applying an operation on a subset of the\n            particles used to construct the mesh octree, this will ensure that\n            we are able to find and identify all relevant particles.\n        nneighbors : int, default 64\n            The number of neighbors to examine during the process.\n        kernel_name : string, default 'cubic'\n            This is the name of the smoothing kernel to use. Current supported\n            kernel names include `cubic`, `quartic`, `quintic`, `wendland2`,\n            `wendland4`, and `wendland6`.\n\n        Returns\n        -------\n        List of fortran-ordered, mesh-like arrays.\n        \"\"\"\n        # Here we perform our particle deposition.\n        positions.convert_to_units(\"code_length\")\n        if create_octree:\n            morton = compute_morton(\n                positions[:, 0],\n                positions[:, 1],\n                positions[:, 2],\n                self.ds.domain_left_edge,\n                self.ds.domain_right_edge,\n            )\n            morton.sort()\n            particle_octree = ParticleOctreeContainer(\n                [1, 1, 1],\n                self.ds.domain_left_edge,\n                self.ds.domain_right_edge,\n                num_zones=self._nz,\n            )\n            # This should ensure we get everything within one neighbor of home.\n            particle_octree.n_ref = nneighbors * 2\n            particle_octree.add(morton)\n            particle_octree.finalize(self.domain_id)\n            pdom_ind = particle_octree.domain_ind(self.selector)\n        else:\n            particle_octree = self.oct_handler\n            pdom_ind = self.domain_ind\n        if fields is None:\n            fields = []\n        if index_fields is None:\n            index_fields = []\n        cls = getattr(particle_smooth, f\"{method}_smooth\", None)\n        if cls is None:\n            raise YTParticleDepositionNotImplemented(method)\n        nz = self.nz\n        mdom_ind = self.domain_ind\n        nvals = (nz, nz, nz, (mdom_ind >= 0).sum())\n        op = cls(nvals, len(fields), nneighbors, kernel_name)\n        op.initialize()\n        mylog.debug(\n            \"Smoothing %s particles into %s Octs\", positions.shape[0], nvals[-1]\n        )\n        # Pointer operations within 'process_octree' require arrays to be\n        # contiguous cf. https://bitbucket.org/yt_analysis/yt/issues/1079\n        fields = [np.ascontiguousarray(f, dtype=\"float64\") for f in fields]\n        op.process_octree(\n            self.oct_handler,\n            mdom_ind,\n            positions,\n            self.fcoords,\n            fields,\n            self.domain_id,\n            self._domain_offset,\n            self.ds.periodicity,\n            index_fields,\n            particle_octree,\n            pdom_ind,\n            self.ds.geometry,\n        )\n        # If there are 0s in the smoothing field this will not throw an error,\n        # but silently return nans for vals where dividing by 0\n        # Same as what is currently occurring, but suppressing the div by zero\n        # error.\n        with np.errstate(invalid=\"ignore\"):\n            vals = op.finalize()\n        if isinstance(vals, list):\n            vals = [np.asfortranarray(v) for v in vals]\n        else:\n            vals = np.asfortranarray(vals)\n        return vals\n\n    def particle_operation(\n        self, positions, fields=None, method=None, nneighbors=64, kernel_name=\"cubic\"\n    ):\n        r\"\"\"Operate on particles, in a particle-against-particle fashion.\n\n        This uses the octree indexing system to call a \"smoothing\" operation\n        (defined in yt/geometry/particle_smooth.pyx) that expects to be called\n        in a particle-by-particle fashion.  For instance, the canonical example\n        of this would be to compute the Nth nearest neighbor, or to compute the\n        density for a given particle based on some kernel operation.\n\n        Many of the arguments to this are identical to those used in the smooth\n        and deposit functions.  Note that the `fields` argument must not be\n        empty, as these fields will be modified in place.\n\n        Parameters\n        ----------\n        positions : array_like (Nx3)\n            The positions of all of the particles to be examined.  A new\n            indexed octree will be constructed on these particles.\n        fields : list of arrays\n            All the necessary fields for computing the particle operation.  For\n            instance, this might include mass, velocity, etc.  One of these\n            will likely be modified in place.\n        method : string\n            This is the \"method name\" which will be looked up in the\n            `particle_smooth` namespace as `methodname_smooth`.\n        nneighbors : int, default 64\n            The number of neighbors to examine during the process.\n        kernel_name : string, default 'cubic'\n            This is the name of the smoothing kernel to use. Current supported\n            kernel names include `cubic`, `quartic`, `quintic`, `wendland2`,\n            `wendland4`, and `wendland6`.\n\n        Returns\n        -------\n        Nothing.\n\n        \"\"\"\n        # Here we perform our particle deposition.\n        positions.convert_to_units(\"code_length\")\n        morton = compute_morton(\n            positions[:, 0],\n            positions[:, 1],\n            positions[:, 2],\n            self.ds.domain_left_edge,\n            self.ds.domain_right_edge,\n        )\n        morton.sort()\n        particle_octree = ParticleOctreeContainer(\n            [1, 1, 1],\n            self.ds.domain_left_edge,\n            self.ds.domain_right_edge,\n            num_zones=2,\n        )\n        particle_octree.n_ref = nneighbors * 2\n        particle_octree.add(morton)\n        particle_octree.finalize()\n        pdom_ind = particle_octree.domain_ind(self.selector)\n        if fields is None:\n            fields = []\n        cls = getattr(particle_smooth, f\"{method}_smooth\", None)\n        if cls is None:\n            raise YTParticleDepositionNotImplemented(method)\n        nz = self.nz\n        mdom_ind = self.domain_ind\n        nvals = (nz, nz, nz, (mdom_ind >= 0).sum())\n        op = cls(nvals, len(fields), nneighbors, kernel_name)\n        op.initialize()\n        mylog.debug(\n            \"Smoothing %s particles into %s Octs\", positions.shape[0], nvals[-1]\n        )\n        op.process_particles(\n            particle_octree,\n            pdom_ind,\n            positions,\n            fields,\n            self.domain_id,\n            self._domain_offset,\n            self.ds.periodicity,\n            self.ds.geometry,\n        )\n        vals = op.finalize()\n        if vals is None:\n            return\n        if isinstance(vals, list):\n            vals = [np.asfortranarray(v) for v in vals]\n        else:\n            vals = np.asfortranarray(vals)\n        return vals\n\n    @cell_count_cache\n    def select_icoords(self, dobj):\n        return self.oct_handler.icoords(\n            dobj.selector, domain_id=self.domain_id, num_cells=self._cell_count\n        )\n\n    @cell_count_cache\n    def select_fcoords(self, dobj):\n        fcoords = self.oct_handler.fcoords(\n            dobj.selector, domain_id=self.domain_id, num_cells=self._cell_count\n        )\n        return self.ds.arr(fcoords, \"code_length\")\n\n    @cell_count_cache\n    def select_fwidth(self, dobj):\n        fwidth = self.oct_handler.fwidth(\n            dobj.selector, domain_id=self.domain_id, num_cells=self._cell_count\n        )\n        return self.ds.arr(fwidth, \"code_length\")\n\n    @cell_count_cache\n    def select_ires(self, dobj):\n        return self.oct_handler.ires(\n            dobj.selector, domain_id=self.domain_id, num_cells=self._cell_count\n        )\n\n    def select(self, selector, source, dest, offset):\n        n = self.oct_handler.selector_fill(\n            selector, source, dest, offset, domain_id=self.domain_id\n        )\n        return n\n\n    def count(self, selector):\n        return -1\n\n    def count_particles(self, selector, x, y, z):\n        # We don't cache the selector results\n        count = selector.count_points(x, y, z, 0.0)\n        return count\n\n    def select_particles(self, selector, x, y, z):\n        mask = selector.select_points(x, y, z, 0.0)\n        return mask\n\n    def get_vertex_centered_data(self, fields):\n        # Make sure the field list has only unique entries\n        fields = list(set(fields))\n        new_fields = {}\n        cg = self.retrieve_ghost_zones(1, fields)\n        for field in fields:\n            new_fields[field] = cg[field][1:, 1:, 1:].copy()\n            np.add(new_fields[field], cg[field][:-1, 1:, 1:], new_fields[field])\n            np.add(new_fields[field], cg[field][1:, :-1, 1:], new_fields[field])\n            np.add(new_fields[field], cg[field][1:, 1:, :-1], new_fields[field])\n            np.add(new_fields[field], cg[field][:-1, 1:, :-1], new_fields[field])\n            np.add(new_fields[field], cg[field][1:, :-1, :-1], new_fields[field])\n            np.add(new_fields[field], cg[field][:-1, :-1, 1:], new_fields[field])\n            np.add(new_fields[field], cg[field][:-1, :-1, :-1], new_fields[field])\n            np.multiply(new_fields[field], 0.125, new_fields[field])\n\n        return new_fields\n\n\nclass OctreeSubsetBlockSlicePosition:\n    def __init__(self, ind, block_slice):\n        self.ind = ind\n        self.block_slice = block_slice\n        nz = self.block_slice.octree_subset.nz\n        self.ActiveDimensions = np.array([nz, nz, nz], dtype=\"int64\")\n        self.ds = block_slice.ds\n\n    def __getitem__(self, key):\n        bs = self.block_slice\n        rv = np.require(\n            bs.octree_subset[key][:, :, :, self.ind].T,\n            requirements=bs.octree_subset._block_order,\n        )\n        return rv\n\n    @property\n    def id(self):\n        return self.ind\n\n    @property\n    def Level(self):\n        return self.block_slice._ires[0, 0, 0, self.ind]\n\n    @property\n    def LeftEdge(self):\n        LE = (\n            self.block_slice._fcoords[0, 0, 0, self.ind, :].d\n            - self.block_slice._fwidth[0, 0, 0, self.ind, :].d * 0.5\n        )\n        return self.block_slice.octree_subset.ds.arr(\n            LE, self.block_slice._fcoords.units\n        )\n\n    @property\n    def RightEdge(self):\n        RE = (\n            self.block_slice._fcoords[-1, -1, -1, self.ind, :].d\n            + self.block_slice._fwidth[-1, -1, -1, self.ind, :].d * 0.5\n        )\n        return self.block_slice.octree_subset.ds.arr(\n            RE, self.block_slice._fcoords.units\n        )\n\n    @property\n    def dds(self):\n        return self.block_slice._fwidth[0, 0, 0, self.ind, :]\n\n    def clear_data(self):\n        pass\n\n    def get_vertex_centered_data(self, fields, smoothed=False, no_ghost=False):\n        field = fields[0]\n        new_field = self.block_slice.get_vertex_centered_data(fields)[field]\n        return {field: new_field[..., self.ind]}\n\n    @contextmanager\n    def _field_parameter_state(self, field_parameters):\n        yield self.block_slice.octree_subset._field_parameter_state(field_parameters)\n\n\nclass OctreeSubsetBlockSlice:\n    def __init__(self, octree_subset, ds):\n        self.octree_subset = octree_subset\n        self.ds = ds\n        self._vertex_centered_data = {}\n        # Cache some attributes\n        for attr in [\"ires\", \"icoords\", \"fcoords\", \"fwidth\"]:\n            v = getattr(octree_subset, attr)\n            setattr(self, f\"_{attr}\", octree_subset._reshape_vals(v))\n\n    @property\n    def octree_subset_with_gz(self):\n        subset_with_gz = getattr(self, \"_octree_subset_with_gz\", None)\n        if not subset_with_gz:\n            self._octree_subset_with_gz = self.octree_subset.retrieve_ghost_zones(1, [])\n        return self._octree_subset_with_gz\n\n    def get_vertex_centered_data(self, fields, smoothed=False, no_ghost=False):\n        if no_ghost is True:\n            raise NotImplementedError(\n                \"get_vertex_centered_data without ghost zones for \"\n                \"oct-based datasets has not been implemented.\"\n            )\n\n        # Make sure the field list has only unique entries\n        fields = list(set(fields))\n        new_fields = {}\n        cg = self.octree_subset_with_gz\n        for field in fields:\n            if field in self._vertex_centered_data:\n                new_fields[field] = self._vertex_centered_data[field]\n            else:\n                finfo = self.ds._get_field_info(field)\n                orig_field = cg[field]\n                nocts = orig_field.shape[-1]\n                new_field = np.zeros((3, 3, 3, nocts), order=\"F\")\n\n                # Compute vertex-centred data as mean of 8 neighbours cell data\n                slices = (slice(1, None), slice(None, -1))\n                for slx, sly, slz in product(*repeat(slices, 3)):\n                    new_field += orig_field[slx, sly, slz]\n                new_field *= 0.125\n\n                new_fields[field] = self.ds.arr(new_field, finfo.output_units)\n\n                self._vertex_centered_data[field] = new_fields[field]\n\n        return new_fields\n\n    def __iter__(self):\n        for i in range(self._ires.shape[-1]):\n            yield i, OctreeSubsetBlockSlicePosition(i, self)\n\n\nclass YTPositionArray(unyt_array):\n    @property\n    def morton(self):\n        self.validate()\n        eps = np.finfo(self.dtype).eps\n        LE = self.min(axis=0)\n        LE -= np.abs(LE) * eps\n        RE = self.max(axis=0)\n        RE += np.abs(RE) * eps\n        morton = compute_morton(self[:, 0], self[:, 1], self[:, 2], LE, RE)\n        return morton\n\n    def to_octree(self, num_zones=2, dims=(1, 1, 1), n_ref=64):\n        mi = self.morton\n        mi.sort()\n        eps = np.finfo(self.dtype).eps\n        LE = self.min(axis=0)\n        LE -= np.abs(LE) * eps\n        RE = self.max(axis=0)\n        RE += np.abs(RE) * eps\n        octree = ParticleOctreeContainer(dims, LE, RE, num_zones=num_zones)\n        octree.n_ref = n_ref\n        octree.add(mi)\n        octree.finalize()\n        return octree\n\n    def validate(self):\n        if (\n            len(self.shape) != 2\n            or self.shape[1] != 3\n            or self.units.dimensions != length\n        ):\n            raise YTInvalidPositionArray(self.shape, self.units.dimensions)\n"
  },
  {
    "path": "yt/data_objects/index_subobjects/particle_container.py",
    "content": "import contextlib\n\nfrom more_itertools import always_iterable\n\nfrom yt.data_objects.data_containers import YTFieldData\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n)\nfrom yt.utilities.exceptions import (\n    YTDataSelectorNotImplemented,\n    YTNonIndexedDataContainer,\n)\n\n\ndef _non_indexed(name):\n    def _func_non_indexed(self, *args, **kwargs):\n        raise YTNonIndexedDataContainer(self)\n\n    return _func_non_indexed\n\n\nclass ParticleContainer(YTSelectionContainer):\n    _spatial = False\n    _type_name = \"particle_container\"\n    _skip_add = True\n    _con_args = (\"base_region\", \"base_selector\", \"data_files\", \"overlap_files\")\n\n    def __init__(\n        self, base_region, base_selector, data_files, overlap_files=None, domain_id=-1\n    ):\n        if overlap_files is None:\n            overlap_files = []\n        self.field_data = YTFieldData()\n        self.field_parameters = {}\n        self.data_files = list(always_iterable(data_files))\n        self.overlap_files = list(always_iterable(overlap_files))\n        self.ds = self.data_files[0].ds\n        self._last_mask = None\n        self._last_selector_id = None\n        self._current_particle_type = \"all\"\n        # self._current_fluid_type = self.ds.default_fluid_type\n\n        self.base_region = base_region\n        self.base_selector = base_selector\n        self._octree = None\n        self._temp_spatial = False\n        if isinstance(base_region, ParticleContainer):\n            self._temp_spatial = base_region._temp_spatial\n            self._octree = base_region._octree\n        # To ensure there are not domains if global octree not used\n        self.domain_id = -1\n\n    def __reduce__(self):\n        # we need to override the __reduce__ from data_containers as this method is not\n        # a registered dataset method (i.e., ds.particle_container does not exist)\n        arg_tuple = tuple(getattr(self, attr) for attr in self._con_args)\n        return (self.__class__, arg_tuple)\n\n    @property\n    def selector(self):\n        raise YTDataSelectorNotImplemented(self.oc_type_name)\n\n    def select_particles(self, selector, x, y, z):\n        mask = selector.select_points(x, y, z)\n        return mask\n\n    @contextlib.contextmanager\n    def _expand_data_files(self):\n        old_data_files = self.data_files\n        old_overlap_files = self.overlap_files\n        self.data_files = list(set(self.data_files + self.overlap_files))\n        self.data_files.sort()\n        self.overlap_files = []\n        yield self\n        self.data_files = old_data_files\n        self.overlap_files = old_overlap_files\n\n    def retrieve_ghost_zones(self, ngz, coarse_ghosts=False):\n        gz_oct = self.octree.retrieve_ghost_zones(ngz, coarse_ghosts=coarse_ghosts)\n        gz = ParticleContainer(\n            gz_oct.base_region,\n            gz_oct.base_selector,\n            gz_oct.data_files,\n            overlap_files=gz_oct.overlap_files,\n            selector_mask=gz_oct.selector_mask,\n            domain_id=gz_oct.domain_id,\n        )\n        gz._octree = gz_oct\n        return gz\n\n    select_blocks = _non_indexed(\"select_blocks\")\n    deposit = _non_indexed(\"deposit\")\n    smooth = _non_indexed(\"smooth\")\n    select_icoords = _non_indexed(\"select_icoords\")\n    select_fcoords = _non_indexed(\"select_fcoords\")\n    select_fwidth = _non_indexed(\"select_fwidth\")\n    select_ires = _non_indexed(\"select_ires\")\n    select = _non_indexed(\"select\")\n    count = _non_indexed(\"count\")\n    count_particles = _non_indexed(\"count_particles\")\n"
  },
  {
    "path": "yt/data_objects/index_subobjects/stretched_grid.py",
    "content": "import numpy as np\n\nfrom yt.geometry.selection_routines import convert_mask_to_indices\n\nfrom .grid_patch import AMRGridPatch\n\n\nclass StretchedGrid(AMRGridPatch):\n    def __init__(self, id, cell_widths, *, filename=None, index=None):\n        self.cell_widths = [np.array(_) for _ in cell_widths]\n        super().__init__(id, filename, index)\n\n    def _check_consistency(self):\n        computed_right_edge = self.LeftEdge + [\n            _.sum() for _ in self.cell_widths * self.LeftEdge.uq\n        ]\n        assert (computed_right_edge == self.RightEdge).all()\n\n    def _get_selector_mask(self, selector):\n        if self._cache_mask and hash(selector) == self._last_selector_id:\n            mask = self._last_mask\n        else:\n            mask = selector.fill_mask(self)\n            if self._cache_mask:\n                self._last_mask = mask\n            self._last_selector_id = hash(selector)\n            if mask is None:\n                self._last_count = 0\n            else:\n                self._last_count = mask.sum()\n        return mask\n\n    def select_fwidth(self, dobj):\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty((0, 3), dtype=\"float64\")\n        indices = convert_mask_to_indices(mask, self._last_count)\n\n        coords = np.array(\n            [\n                self.cell_widths[0][indices[:, 0]],\n                self.cell_widths[1][indices[:, 1]],\n                self.cell_widths[2][indices[:, 2]],\n            ]\n        ).T\n        return coords\n\n    def select_fcoords(self, dobj):\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty((0, 3), dtype=\"float64\")\n        cell_centers = [\n            self.LeftEdge[i].d\n            + np.add.accumulate(self.cell_widths[i])\n            - 0.5 * self.cell_widths[i]\n            for i in range(3)\n        ]\n        indices = convert_mask_to_indices(mask, self._last_count)\n        coords = np.array(\n            [\n                cell_centers[0][indices[:, 0]],\n                cell_centers[1][indices[:, 1]],\n                cell_centers[2][indices[:, 2]],\n            ]\n        ).T\n        return coords\n"
  },
  {
    "path": "yt/data_objects/index_subobjects/unstructured_mesh.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n)\nfrom yt.funcs import mylog\nfrom yt.utilities.lib.mesh_utilities import fill_fcoords, fill_fwidths\n\n\nclass UnstructuredMesh(YTSelectionContainer):\n    # This is a base class, not meant to be used directly.\n    _spatial = False\n    _connectivity_length = -1\n    _type_name = \"unstructured_mesh\"\n    _skip_add = True\n    _index_offset = 0\n    _con_args = (\"mesh_id\", \"filename\", \"connectivity_indices\", \"connectivity_coords\")\n\n    def __init__(\n        self, mesh_id, filename, connectivity_indices, connectivity_coords, index\n    ):\n        super().__init__(index.dataset, None)\n        self.filename = filename\n        self.mesh_id = mesh_id\n        # This is where we set up the connectivity information\n        self.connectivity_indices = connectivity_indices\n        if connectivity_indices.shape[1] != self._connectivity_length:\n            if self._connectivity_length == -1:\n                self._connectivity_length = connectivity_indices.shape[1]\n            else:\n                raise RuntimeError\n        self.connectivity_coords = connectivity_coords\n        self.ds = index.dataset\n        self._index = index\n        self._last_mask = None\n        self._last_count = -1\n        self._last_selector_id = None\n\n    def _check_consistency(self):\n        if self.connectivity_indices.shape[1] != self._connectivity_length:\n            raise RuntimeError\n\n        for gi in range(self.connectivity_indices.shape[0]):\n            ind = self.connectivity_indices[gi, :] - self._index_offset\n            coords = self.connectivity_coords[ind, :]\n            for i in range(3):\n                assert np.unique(coords[:, i]).size == 2\n        mylog.debug(\"Connectivity is consistent.\")\n\n    def __repr__(self):\n        return f\"UnstructuredMesh_{self.mesh_id:04}\"\n\n    def get_global_startindex(self):\n        \"\"\"\n        Return the integer starting index for each dimension at the current\n        level.\n\n        \"\"\"\n        raise NotImplementedError\n\n    def convert(self, datatype):\n        \"\"\"\n        This will attempt to convert a given unit to cgs from code units. It\n        either returns the multiplicative factor or throws a KeyError.\n\n        \"\"\"\n        return self.ds[datatype]\n\n    @property\n    def shape(self):\n        raise NotImplementedError\n\n    def _generate_container_field(self, field):\n        raise NotImplementedError\n\n    def select_fcoords(self, dobj=None):\n        # This computes centroids!\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty((0, 3), dtype=\"float64\")\n        centers = fill_fcoords(\n            self.connectivity_coords, self.connectivity_indices, self._index_offset\n        )\n        return centers[mask, :]\n\n    def select_fwidth(self, dobj):\n        raise NotImplementedError\n\n    def select_icoords(self, dobj):\n        raise NotImplementedError\n\n    def select_ires(self, dobj):\n        raise NotImplementedError\n\n    def select_tcoords(self, dobj):\n        raise NotImplementedError\n\n    def deposit(self, positions, fields=None, method=None, kernel_name=\"cubic\"):\n        raise NotImplementedError\n\n    def select_blocks(self, selector):\n        mask = self._get_selector_mask(selector)\n        yield self, mask\n\n    def select(self, selector, source, dest, offset):\n        mask = self._get_selector_mask(selector)\n        count = self.count(selector)\n        if count == 0:\n            return 0\n        dest[offset : offset + count] = source[mask, ...]\n        return count\n\n    def count(self, selector):\n        mask = self._get_selector_mask(selector)\n        if mask is None:\n            return 0\n        return self._last_count\n\n    def count_particles(self, selector, x, y, z):\n        # We don't cache the selector results\n        count = selector.count_points(x, y, z, 0.0)\n        return count\n\n    def select_particles(self, selector, x, y, z):\n        mask = selector.select_points(x, y, z, 0.0)\n        return mask\n\n    def _get_selector_mask(self, selector):\n        if hash(selector) == self._last_selector_id:\n            mask = self._last_mask\n        else:\n            self._last_mask = mask = selector.fill_mesh_cell_mask(self)\n            self._last_selector_id = hash(selector)\n            if mask is None:\n                self._last_count = 0\n            else:\n                self._last_count = mask.sum()\n        return mask\n\n    def select_fcoords_vertex(self, dobj=None):\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty((0, self._connectivity_length, 3), dtype=\"float64\")\n        vertices = self.connectivity_coords[self.connectivity_indices - 1]\n        return vertices[mask, :, :]\n\n\nclass SemiStructuredMesh(UnstructuredMesh):\n    _connectivity_length = 8\n    _type_name = \"semi_structured_mesh\"\n    _container_fields = (\"dx\", \"dy\", \"dz\")\n\n    def __repr__(self):\n        return f\"SemiStructuredMesh_{self.mesh_id:04}\"\n\n    def _generate_container_field(self, field):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        if field == \"dx\":\n            return self._current_chunk.fwidth[:, 0]\n        elif field == \"dy\":\n            return self._current_chunk.fwidth[:, 1]\n        elif field == \"dz\":\n            return self._current_chunk.fwidth[:, 2]\n\n    def select_fwidth(self, dobj):\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty((0, 3), dtype=\"float64\")\n        widths = fill_fwidths(\n            self.connectivity_coords, self.connectivity_indices, self._index_offset\n        )\n        return widths[mask, :]\n\n    def select_ires(self, dobj):\n        ind = np.zeros(self.connectivity_indices.shape[0])\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty(0, dtype=\"int32\")\n        return ind[mask]\n\n    def select_tcoords(self, dobj):\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty(0, dtype=\"float64\")\n        dt, t = dobj.selector.get_dt_mesh(self, mask.sum(), self._index_offset)\n        return dt, t\n\n    def _get_selector_mask(self, selector):\n        if hash(selector) == self._last_selector_id:\n            mask = self._last_mask\n        else:\n            self._last_mask = mask = selector.fill_mesh_cell_mask(self)\n            self._last_selector_id = hash(selector)\n            if mask is None:\n                self._last_count = 0\n            else:\n                self._last_count = mask.sum()\n        return mask\n\n    def select(self, selector, source, dest, offset):\n        mask = self._get_selector_mask(selector)\n        count = self.count(selector)\n        if count == 0:\n            return 0\n        # Note: this likely will not work with vector fields.\n        dest[offset : offset + count] = source.flat[mask]\n        return count\n"
  },
  {
    "path": "yt/data_objects/level_sets/__init__.py",
    "content": ""
  },
  {
    "path": "yt/data_objects/level_sets/api.py",
    "content": "from .clump_handling import Clump, find_clumps\nfrom .clump_info_items import add_clump_info\nfrom .clump_tools import (\n    clump_list_sort,\n    recursive_all_clumps,\n    recursive_bottom_clumps,\n    return_all_clumps,\n    return_bottom_clumps,\n)\nfrom .clump_validators import add_validator\nfrom .contour_finder import identify_contours\n"
  },
  {
    "path": "yt/data_objects/level_sets/clump_handling.py",
    "content": "import uuid\n\nimport numpy as np\n\nfrom yt.fields.derived_field import ValidateSpatial\nfrom yt.frontends.ytdata.utilities import save_as_dataset\nfrom yt.funcs import get_output_filename, mylog\nfrom yt.utilities.tree_container import TreeContainer\n\nfrom .clump_info_items import clump_info_registry\nfrom .clump_validators import clump_validator_registry\nfrom .contour_finder import identify_contours\n\n\ndef add_contour_field(ds, contour_key):\n    def _contours(data):\n        fd = data.get_field_parameter(f\"contour_slices_{contour_key}\")\n        vals = data[\"index\", \"ones\"] * -1\n        if fd is None or fd == 0.0:\n            return vals\n        for sl, v in fd.get(data.id, []):\n            vals[sl] = v\n        return vals\n\n    ds.add_field(\n        (\"index\", f\"contours_{contour_key}\"),\n        function=_contours,\n        validators=[ValidateSpatial(0)],\n        take_log=False,\n        display_field=False,\n        sampling_type=\"cell\",\n        units=\"\",\n    )\n\n\nclass Clump(TreeContainer):\n    def __init__(\n        self,\n        data,\n        field,\n        parent=None,\n        clump_info=None,\n        validators=None,\n        base=None,\n        contour_key=None,\n        contour_id=None,\n    ):\n        self.data = data\n        self.field = field\n        self.parent = parent\n        self.quantities = data.quantities\n        self.min_val = self.data[field].min()\n        self.max_val = self.data[field].max()\n        self.info = {}\n        self.children = []\n\n        # is this the parent clump?\n        if base is None:\n            base = self\n            self.total_clumps = 0\n\n        if clump_info is None:\n            self.set_default_clump_info()\n        else:\n            self.clump_info = clump_info\n\n        for ci in self.clump_info:\n            ci(self)\n\n        self.base = base\n        self.clump_id = self.base.total_clumps\n        self.base.total_clumps += 1\n        self.contour_key = contour_key\n        self.contour_id = contour_id\n\n        if parent is not None:\n            self.data.parent = self.parent.data\n\n        if validators is None:\n            validators = []\n        self.validators = validators\n        # Return value of validity function.\n        self.valid = None\n\n    _leaves = None\n\n    @property\n    def leaves(self):\n        if self._leaves is not None:\n            return self._leaves\n\n        self._leaves = []\n        for clump in self:\n            if not clump.children:\n                self._leaves.append(clump)\n        return self._leaves\n\n    def add_validator(self, validator, *args, **kwargs):\n        \"\"\"\n        Add a validating function to determine whether the clump should\n        be kept.\n        \"\"\"\n        callback = clump_validator_registry.find(validator, *args, **kwargs)\n        self.validators.append(callback)\n        for child in self.children:\n            child.add_validator(validator)\n\n    def add_info_item(self, info_item, *args, **kwargs):\n        \"Adds an entry to clump_info list and tells children to do the same.\"\n\n        callback = clump_info_registry.find(info_item, *args, **kwargs)\n        callback(self)\n        self.clump_info.append(callback)\n        for child in self.children:\n            child.add_info_item(info_item)\n\n    def set_default_clump_info(self):\n        \"Defines default entries in the clump_info array.\"\n\n        # add_info_item is recursive so this function does not need to be.\n        self.clump_info = []\n\n        self.add_info_item(\"total_cells\")\n        self.add_info_item(\"cell_mass\")\n\n        if any(\"jeans\" in f for f in self.data.pf.field_list):\n            self.add_info_item(\"mass_weighted_jeans_mass\")\n            self.add_info_item(\"volume_weighted_jeans_mass\")\n\n        self.add_info_item(\"max_grid_level\")\n\n        if any(\"number_density\" in f for f in self.data.pf.field_list):\n            self.add_info_item(\"min_number_density\")\n            self.add_info_item(\"max_number_density\")\n\n    def clear_clump_info(self):\n        \"\"\"\n        Clears the clump_info array and passes the instruction to its\n        children.\n        \"\"\"\n\n        self.clump_info = []\n        for child in self.children:\n            child.clear_clump_info()\n\n    def find_children(self, min_val, max_val=None):\n        if self.children:\n            mylog.info(\"Wiping out existing children clumps: %d.\", len(self.children))\n        self.children = []\n        if max_val is None:\n            max_val = self.max_val\n        nj, cids = identify_contours(self.data, self.field, min_val, max_val)\n        # Here, cids is the set of slices and values, keyed by the\n        # parent_grid_id, that defines the contours.  So we can figure out all\n        # the unique values of the contours by examining the list here.\n        unique_contours = set()\n        for sl_list in cids.values():\n            for _sl, ff in sl_list:\n                unique_contours.update(np.unique(ff))\n        contour_key = uuid.uuid4().hex\n        base_object = getattr(self.data, \"base_object\", self.data)\n        add_contour_field(base_object.ds, contour_key)\n        for cid in sorted(unique_contours):\n            if cid == -1:\n                continue\n            new_clump = base_object.cut_region(\n                [f\"obj['contours_{contour_key}'] == {cid}\"],\n                {(f\"contour_slices_{contour_key}\"): cids},\n            )\n            if new_clump[\"index\", \"ones\"].size == 0:\n                # This is to skip possibly duplicate clumps.\n                # Using \"ones\" here will speed things up.\n                continue\n            self.children.append(\n                Clump(\n                    new_clump,\n                    self.field,\n                    parent=self,\n                    validators=self.validators,\n                    base=self.base,\n                    clump_info=self.clump_info,\n                    contour_key=contour_key,\n                    contour_id=cid,\n                )\n            )\n\n    def __iter__(self):\n        yield self\n        for child in self.children:\n            yield from child\n\n    def save_as_dataset(self, filename=None, fields=None):\n        r\"\"\"Export clump tree to a reloadable yt dataset.\n        This function will take a clump object and output a dataset\n        containing the fields given in the ``fields`` list and all info\n        items.  The resulting dataset can be reloaded as a yt dataset.\n\n        Parameters\n        ----------\n        filename : str, optional\n            The name of the file to be written.  If None, the name\n            will be a combination of the original dataset and the clump\n            index.\n        fields : list of strings or tuples, optional\n            If this is supplied, it is the list of fields to be saved to\n            disk.\n\n        Returns\n        -------\n        filename : str\n            The name of the file that has been created.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> from yt.data_objects.level_sets.api import Clump, find_clumps\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> data_source = ds.disk(\n        ...     [0.5, 0.5, 0.5], [0.0, 0.0, 1.0], (8, \"kpc\"), (1, \"kpc\")\n        ... )\n        >>> field = (\"gas\", \"density\")\n        >>> step = 2.0\n        >>> c_min = 10 ** np.floor(np.log10(data_source[field]).min())\n        >>> c_max = 10 ** np.floor(np.log10(data_source[field]).max() + 1)\n        >>> master_clump = Clump(data_source, field)\n        >>> master_clump.add_info_item(\"center_of_mass\")\n        >>> master_clump.add_validator(\"min_cells\", 20)\n        >>> find_clumps(master_clump, c_min, c_max, step)\n        >>> fn = master_clump.save_as_dataset(\n        ...     fields=[(\"gas\", \"density\"), (\"all\", \"particle_mass\")]\n        ... )\n        >>> new_ds = yt.load(fn)\n        >>> print(ds.tree[\"clump\", \"cell_mass\"])\n        1296926163.91 Msun\n        >>> print(ds.tree[\"grid\", \"density\"])\n        [  2.54398434e-26   2.46620353e-26   2.25120154e-26 ...,   1.12879234e-25\n           1.59561490e-25   1.09824903e-24] g/cm**3\n        >>> print(ds.tree[\"all\", \"particle_mass\"])\n        [  4.25472446e+38   4.25472446e+38   4.25472446e+38 ...,   2.04238266e+38\n           2.04523901e+38   2.04770938e+38] g\n        >>> print(ds.tree.children[0][\"clump\", \"cell_mass\"])\n        909636495.312 Msun\n        >>> print(ds.leaves[0][\"clump\", \"cell_mass\"])\n        3756566.99809 Msun\n        >>> print(ds.leaves[0][\"grid\", \"density\"])\n        [  6.97820274e-24   6.58117370e-24   7.32046082e-24   6.76202430e-24\n           7.41184837e-24   6.76981480e-24   6.94287213e-24   6.56149658e-24\n           6.76584569e-24   6.94073710e-24   7.06713082e-24   7.22556526e-24\n           7.08338898e-24   6.78684331e-24   7.40647040e-24   7.03050456e-24\n           7.12438678e-24   6.56310217e-24   7.23201662e-24   7.17314333e-24] g/cm**3\n\n        \"\"\"\n\n        ds = self.data.ds\n        keyword = f\"{ds}_clump_{self.clump_id}\"\n        filename = get_output_filename(filename, keyword, \".h5\")\n\n        # collect clump info fields\n        clump_info = {ci.name: [] for ci in self.base.clump_info}\n        clump_info.update(\n            {\n                field: []\n                for field in [\"clump_id\", \"parent_id\", \"contour_key\", \"contour_id\"]\n            }\n        )\n        for clump in self:\n            clump_info[\"clump_id\"].append(clump.clump_id)\n            if clump.parent is None:\n                parent_id = -1\n            else:\n                parent_id = clump.parent.clump_id\n            clump_info[\"parent_id\"].append(parent_id)\n\n            contour_key = clump.contour_key\n            if contour_key is None:\n                contour_key = -1\n            clump_info[\"contour_key\"].append(contour_key)\n            contour_id = clump.contour_id\n            if contour_id is None:\n                contour_id = -1\n            clump_info[\"contour_id\"].append(contour_id)\n\n            for ci in self.base.clump_info:\n                clump_info[ci.name].append(clump.info[ci.name][1])\n        for ci in clump_info:\n            if hasattr(clump_info[ci][0], \"units\"):\n                clump_info[ci] = ds.arr(clump_info[ci])\n            else:\n                clump_info[ci] = np.array(clump_info[ci])\n\n        ftypes = dict.fromkeys(clump_info, \"clump\")\n\n        # collect data fields\n        if fields is not None:\n            contour_fields = [\n                (\"index\", f\"contours_{ckey}\")\n                for ckey in np.unique(clump_info[\"contour_key\"])\n                if str(ckey) != \"-1\"\n            ]\n\n            ptypes = []\n            field_data = {}\n            need_grid_positions = False\n            for f in self.base.data._determine_fields(fields) + contour_fields:\n                if ds.field_info[f].sampling_type == \"particle\":\n                    if f[0] not in ptypes:\n                        ptypes.append(f[0])\n                    ftypes[f] = f[0]\n                else:\n                    need_grid_positions = True\n                    if f[1] in (\"x\", \"y\", \"z\", \"dx\", \"dy\", \"dz\"):\n                        # skip 'xyz' if a user passes that in because they\n                        # will be added to ftypes below\n                        continue\n                    ftypes[f] = \"grid\"\n                field_data[f] = self.base[f]\n\n            if len(ptypes) > 0:\n                for ax in \"xyz\":\n                    for ptype in ptypes:\n                        p_field = (ptype, f\"particle_position_{ax}\")\n                        if p_field in ds.field_info and p_field not in field_data:\n                            ftypes[p_field] = p_field[0]\n                            field_data[p_field] = self.base[p_field]\n\n                for clump in self:\n                    if clump.contour_key is None:\n                        continue\n                    for ptype in ptypes:\n                        cfield = (ptype, f\"contours_{clump.contour_key}\")\n                        if cfield not in field_data:\n                            field_data[cfield] = clump.data._part_ind(ptype).astype(\n                                np.int64\n                            )\n                            ftypes[cfield] = ptype\n                        field_data[cfield][clump.data._part_ind(ptype)] = (\n                            clump.contour_id\n                        )\n\n            if need_grid_positions:\n                for ax in \"xyz\":\n                    g_field = (\"index\", ax)\n                    if g_field in ds.field_info and g_field not in field_data:\n                        field_data[g_field] = self.base[g_field]\n                        ftypes[g_field] = \"grid\"\n                    g_field = (\"index\", \"d\" + ax)\n                    if g_field in ds.field_info and g_field not in field_data:\n                        ftypes[g_field] = \"grid\"\n                        field_data[g_field] = self.base[g_field]\n\n            if self.contour_key is not None:\n                cfilters = {}\n                for field in field_data:\n                    if ftypes[field] == \"grid\":\n                        ftype = \"index\"\n                    else:\n                        ftype = field[0]\n                    cfield = (ftype, f\"contours_{self.contour_key}\")\n                    if cfield not in cfilters:\n                        cfilters[cfield] = field_data[cfield] == self.contour_id\n                    field_data[field] = field_data[field][cfilters[cfield]]\n\n            clump_info.update(field_data)\n        extra_attrs = {\"data_type\": \"yt_clump_tree\", \"container_type\": \"yt_clump_tree\"}\n        save_as_dataset(\n            ds, filename, clump_info, field_types=ftypes, extra_attrs=extra_attrs\n        )\n\n        return filename\n\n    def pass_down(self, operation):\n        \"\"\"\n        Performs an operation on a clump with an exec and passes the\n        instruction down to clump children.\n        \"\"\"\n\n        # Call if callable, otherwise do an exec.\n        if callable(operation):\n            operation()\n        else:\n            exec(operation)\n\n        for child in self.children:\n            child.pass_down(operation)\n\n    def _validate(self):\n        \"Apply all user specified validator functions.\"\n\n        # Only call functions if not done already.\n        if self.valid is not None:\n            return self.valid\n\n        self.valid = True\n        for validator in self.validators:\n            self.valid &= validator(self)\n            if not self.valid:\n                break\n\n        return self.valid\n\n    def __reduce__(self):\n        raise RuntimeError(\n            \"Pickling Clump instances is not supported. Please use \"\n            \"Clump.save_as_dataset instead\"\n        )\n\n    def __getitem__(self, request):\n        return self.data[request]\n\n\ndef find_clumps(clump, min_val, max_val, d_clump):\n    mylog.info(\"Finding clumps: min: %e, max: %e, step: %f\", min_val, max_val, d_clump)\n    if min_val >= max_val:\n        return\n    clump.find_children(min_val, max_val=max_val)\n\n    if len(clump.children) == 1:\n        find_clumps(clump, min_val * d_clump, max_val, d_clump)\n\n    elif len(clump.children) > 0:\n        these_children = []\n        mylog.info(\"Investigating %d children.\", len(clump.children))\n        for child in clump.children:\n            find_clumps(child, min_val * d_clump, max_val, d_clump)\n            if len(child.children) > 0:\n                these_children.append(child)\n            elif child._validate():\n                these_children.append(child)\n            else:\n                mylog.info(\n                    \"Eliminating invalid, childless clump with %d cells.\",\n                    len(child.data[\"index\", \"ones\"]),\n                )\n        if len(these_children) > 1:\n            mylog.info(\n                \"%d of %d children survived.\", len(these_children), len(clump.children)\n            )\n            clump.children = these_children\n        elif len(these_children) == 1:\n            mylog.info(\n                \"%d of %d children survived, linking its children to parent.\",\n                len(these_children),\n                len(clump.children),\n            )\n            clump.children = these_children[0].children\n            for child in clump.children:\n                child.parent = clump\n                child.data.parent = clump.data\n        else:\n            mylog.info(\n                \"%d of %d children survived, erasing children.\",\n                len(these_children),\n                len(clump.children),\n            )\n            clump.children = []\n"
  },
  {
    "path": "yt/data_objects/level_sets/clump_info_items.py",
    "content": "import numpy as np\n\nfrom yt.utilities.operator_registry import OperatorRegistry\n\nclump_info_registry = OperatorRegistry()\n\n\ndef add_clump_info(name, function):\n    clump_info_registry[name] = ClumpInfoCallback(name, function)\n\n\nclass ClumpInfoCallback:\n    r\"\"\"\n    A ClumpInfoCallback is a function that takes a clump, computes a\n    quantity, and returns a string to be printed out for writing clump info.\n    \"\"\"\n\n    def __init__(self, name, function, args=None, kwargs=None):\n        self.name = name\n        self.function = function\n        self.args = args\n        if self.args is None:\n            self.args = []\n        self.kwargs = kwargs\n        if self.kwargs is None:\n            self.kwargs = {}\n\n    def __call__(self, clump):\n        if self.name not in clump.info:\n            clump.info[self.name] = self.function(clump, *self.args, **self.kwargs)\n        rv = clump.info[self.name]\n        return rv[0] % rv[1]\n\n\ndef _center_of_mass(clump, units=\"code_length\", **kwargs):\n    p = clump.quantities.center_of_mass(**kwargs)\n    return \"Center of mass: %s.\", p.to(units)\n\n\nadd_clump_info(\"center_of_mass\", _center_of_mass)\n\n\ndef _total_cells(clump):\n    n_cells = clump.data[\"index\", \"ones\"].size\n    return \"Cells: %d.\", n_cells\n\n\nadd_clump_info(\"total_cells\", _total_cells)\n\n\ndef _cell_mass(clump):\n    cell_mass = clump.data[\"gas\", \"cell_mass\"].sum().in_units(\"Msun\")\n    return \"Mass: %e Msun.\", cell_mass\n\n\nadd_clump_info(\"cell_mass\", _cell_mass)\n\n\ndef _mass_weighted_jeans_mass(clump):\n    jeans_mass = clump.data.quantities.weighted_average_quantity(\n        \"jeans_mass\", (\"gas\", \"cell_mass\")\n    ).in_units(\"Msun\")\n    return \"Jeans Mass (mass-weighted): %.6e Msolar.\", jeans_mass\n\n\nadd_clump_info(\"mass_weighted_jeans_mass\", _mass_weighted_jeans_mass)\n\n\ndef _volume_weighted_jeans_mass(clump):\n    jeans_mass = clump.data.quantities.weighted_average_quantity(\n        \"jeans_mass\", (\"index\", \"cell_volume\")\n    ).in_units(\"Msun\")\n    return \"Jeans Mass (volume-weighted): %.6e Msolar.\", jeans_mass\n\n\nadd_clump_info(\"volume_weighted_jeans_mass\", _volume_weighted_jeans_mass)\n\n\ndef _max_grid_level(clump):\n    max_level = clump.data[\"index\", \"grid_level\"].max()\n    return \"Max grid level: %d.\", max_level\n\n\nadd_clump_info(\"max_grid_level\", _max_grid_level)\n\n\ndef _min_number_density(clump):\n    min_n = clump.data[\"gas\", \"number_density\"].min().in_units(\"cm**-3\")\n    return \"Min number density: %.6e cm^-3.\", min_n\n\n\nadd_clump_info(\"min_number_density\", _min_number_density)\n\n\ndef _max_number_density(clump):\n    max_n = clump.data[\"gas\", \"number_density\"].max().in_units(\"cm**-3\")\n    return \"Max number density: %.6e cm^-3.\", max_n\n\n\nadd_clump_info(\"max_number_density\", _max_number_density)\n\n\ndef _distance_to_main_clump(clump, units=\"pc\"):\n    master = clump\n    while master.parent is not None:\n        master = master.parent\n    master_com = clump.data.ds.arr(master.data.quantities.center_of_mass())\n    my_com = clump.data.ds.arr(clump.data.quantities.center_of_mass())\n    distance = np.sqrt(((master_com - my_com) ** 2).sum())\n    distance.convert_to_units(\"pc\")\n    return (\n        f\"Distance from master center of mass: %.6e {units}.\",\n        distance.in_units(units),\n    )\n\n\nadd_clump_info(\"distance_to_main_clump\", _distance_to_main_clump)\n"
  },
  {
    "path": "yt/data_objects/level_sets/clump_tools.py",
    "content": "import numpy as np\n\nnar = np.array\n\ncounter = 0\n\n\ndef recursive_all_clumps(clump, list, level, parentnumber):\n    \"\"\"A recursive function to flatten the index in *clump*.\n    Not to be called directly: please call return_all_clumps, below.\"\"\"\n    global counter\n    counter += 1\n    clump.number = counter\n    clump.parentnumber = parentnumber\n    counter += 1\n    list.append(clump)\n    clump.level = level\n    if clump.children is not None:\n        for child in clump.children:\n            recursive_all_clumps(child, list, level + 1, clump.number)\n    return list\n\n\ndef return_all_clumps(clump):\n    \"\"\"Flatten the index defined by *clump*.\n    Additionally adds three variables to the clump:\n    level        = depth of index\n    number       = index of clump in the final array\n    parentnumber = index of this clumps parent\n\n    \"\"\"\n    global counter\n    counter = 0\n    list = []\n    level = 0\n    clump.level = level\n    parentnumber = -1\n    recursive_all_clumps(clump, list, level, parentnumber)\n    return list\n\n\ndef return_bottom_clumps(clump, dbg=0):\n    \"\"\"\n    Recursively return clumps at the bottom of the index.\n    This gives a list of clumps similar to what one would get from a CLUMPFIND routine\n    \"\"\"\n    global counter\n    counter = 0\n    list = []\n    level = 0\n    recursive_bottom_clumps(clump, list, dbg, level)\n    return list\n\n\ndef recursive_bottom_clumps(clump, clump_list, dbg=0, level=0):\n    \"\"\"Loops over a list of clumps (clumps) and fills clump_list with the bottom most.\n    Recursive. Prints the level and the number of cores to the screen.\"\"\"\n\n    global counter\n\n    if (clump.children is None) or (len(clump.children) == 0):\n        counter += 1\n        clump_list.append(clump)\n    else:\n        for child in clump.children:\n            recursive_bottom_clumps(child, clump_list, dbg=dbg, level=level + 1)\n\n\ndef clump_list_sort(clump_list):\n    \"\"\"Returns a copy of clump_list, sorted by ascending minimum density.  This\n    eliminates overlap when passing to\n    yt.visualization.plot_modification.ClumpContourCallback\"\"\"\n    minDensity = [c[\"gas\", \"density\"].min() for c in clump_list]\n\n    args = np.argsort(minDensity)\n    list = nar(clump_list)[args]\n    reverse = range(list.size - 1, -1, -1)\n    return list[reverse]\n"
  },
  {
    "path": "yt/data_objects/level_sets/clump_validators.py",
    "content": "import numpy as np\n\nfrom yt.utilities.lib.misc_utilities import gravitational_binding_energy\nfrom yt.utilities.operator_registry import OperatorRegistry\nfrom yt.utilities.physical_constants import gravitational_constant_cgs as G\n\nclump_validator_registry = OperatorRegistry()\n\n\ndef add_validator(name, function):\n    clump_validator_registry[name] = ClumpValidator(function)\n\n\nclass ClumpValidator:\n    r\"\"\"\n    A ClumpValidator is a function that takes a clump and returns\n    True or False as to whether the clump is valid and shall be kept.\n    \"\"\"\n\n    def __init__(self, function, args=None, kwargs=None):\n        self.function = function\n        self.args = args\n        if self.args is None:\n            self.args = []\n        self.kwargs = kwargs\n        if self.kwargs is None:\n            self.kwargs = {}\n\n    def __call__(self, clump):\n        return self.function(clump, *self.args, **self.kwargs)\n\n\ndef _gravitationally_bound(\n    clump, use_thermal_energy=True, use_particles=True, truncate=True, num_threads=0\n):\n    \"True if clump is gravitationally bound.\"\n\n    use_particles &= (\"all\", \"particle_mass\") in clump.data.ds.field_info\n\n    bulk_velocity = clump.quantities.bulk_velocity(use_particles=use_particles)\n\n    kinetic = (\n        0.5\n        * (\n            clump[\"gas\", \"mass\"]\n            * (\n                (bulk_velocity[0] - clump[\"gas\", \"velocity_x\"]) ** 2\n                + (bulk_velocity[1] - clump[\"gas\", \"velocity_y\"]) ** 2\n                + (bulk_velocity[2] - clump[\"gas\", \"velocity_z\"]) ** 2\n            )\n        ).sum()\n    )\n\n    if use_thermal_energy:\n        kinetic += (\n            clump[\"gas\", \"mass\"] * clump[\"gas\", \"specific_thermal_energy\"]\n        ).sum()\n\n    if use_particles:\n        kinetic += (\n            0.5\n            * (\n                clump[\"all\", \"particle_mass\"]\n                * (\n                    (bulk_velocity[0] - clump[\"all\", \"particle_velocity_x\"]) ** 2\n                    + (bulk_velocity[1] - clump[\"all\", \"particle_velocity_y\"]) ** 2\n                    + (bulk_velocity[2] - clump[\"all\", \"particle_velocity_z\"]) ** 2\n                )\n            ).sum()\n        )\n\n    if use_particles:\n        m = np.concatenate(\n            [clump[\"gas\", \"mass\"].in_cgs(), clump[\"all\", \"particle_mass\"].in_cgs()]\n        )\n        px = np.concatenate(\n            [clump[\"index\", \"x\"].in_cgs(), clump[\"all\", \"particle_position_x\"].in_cgs()]\n        )\n        py = np.concatenate(\n            [clump[\"index\", \"y\"].in_cgs(), clump[\"all\", \"particle_position_y\"].in_cgs()]\n        )\n        pz = np.concatenate(\n            [clump[\"index\", \"z\"].in_cgs(), clump[\"all\", \"particle_position_z\"].in_cgs()]\n        )\n    else:\n        m = clump[\"gas\", \"mass\"].in_cgs()\n        px = clump[\"index\", \"x\"].in_cgs()\n        py = clump[\"index\", \"y\"].in_cgs()\n        pz = clump[\"index\", \"z\"].in_cgs()\n\n    potential = clump.data.ds.quan(\n        G\n        * gravitational_binding_energy(\n            m, px, py, pz, truncate, (kinetic / G).in_cgs(), num_threads=num_threads\n        ),\n        kinetic.in_cgs().units,\n    )\n\n    if truncate and potential >= kinetic:\n        return True\n\n    return potential >= kinetic\n\n\nadd_validator(\"gravitationally_bound\", _gravitationally_bound)\n\n\ndef _min_cells(clump, n_cells):\n    \"True if clump has a minimum number of cells.\"\n    return clump[\"index\", \"ones\"].size >= n_cells\n\n\nadd_validator(\"min_cells\", _min_cells)\n"
  },
  {
    "path": "yt/data_objects/level_sets/contour_finder.py",
    "content": "from collections import defaultdict\n\nimport numpy as np\n\nfrom yt.funcs import get_pbar, mylog\nfrom yt.utilities.lib.contour_finding import (\n    ContourTree,\n    TileContourTree,\n    link_node_contours,\n    update_joins,\n)\nfrom yt.utilities.lib.partitioned_grid import PartitionedGrid\n\n\ndef identify_contours(data_source, field, min_val, max_val, cached_fields=None):\n    tree = ContourTree()\n    gct = TileContourTree(min_val, max_val)\n    total_contours = 0\n    contours = {}\n    node_ids = []\n    DLE = data_source.ds.domain_left_edge\n    masks = {g.id: m for g, m in data_source.blocks}\n    for g, node, (sl, dims, gi) in data_source.tiles.slice_traverse():\n        g.field_parameters.update(data_source.field_parameters)\n        node.node_ind = len(node_ids)\n        nid = node.node_id\n        node_ids.append(nid)\n        values = g[field][sl].astype(\"float64\")\n        contour_ids = np.zeros(dims, \"int64\") - 1\n        mask = masks[g.id][sl].astype(\"uint8\")\n        total_contours += gct.identify_contours(\n            values, contour_ids, mask, total_contours\n        )\n        new_contours = tree.cull_candidates(contour_ids)\n        tree.add_contours(new_contours)\n        # Now we can create a partitioned grid with the contours.\n        LE = (DLE + g.dds * gi).in_units(\"code_length\").ndarray_view()\n        RE = LE + (dims * g.dds).in_units(\"code_length\").ndarray_view()\n        pg = PartitionedGrid(\n            g.id, [contour_ids.view(\"float64\")], mask, LE, RE, dims.astype(\"int64\")\n        )\n        contours[nid] = (g.Level, node.node_ind, pg, sl)\n    node_ids = np.array(node_ids, dtype=\"int64\")\n    if node_ids.size == 0:\n        return 0, {}\n    trunk = data_source.tiles.tree.trunk\n    mylog.info(\"Linking node (%s) contours.\", len(contours))\n    link_node_contours(trunk, contours, tree, node_ids)\n    mylog.info(\"Linked.\")\n    # joins = tree.cull_joins(bt)\n    # tree.add_joins(joins)\n    joins = tree.export()\n    contour_ids = defaultdict(list)\n    pbar = get_pbar(\"Updating joins ... \", len(contours))\n    final_joins = np.unique(joins[:, 1])\n    for i, nid in enumerate(sorted(contours)):\n        level, node_ind, pg, sl = contours[nid]\n        ff = pg.my_data[0].view(\"int64\")\n        update_joins(joins, ff, final_joins)\n        contour_ids[pg.parent_grid_id].append((sl, ff))\n        pbar.update(i + 1)\n    pbar.finish()\n    rv = {}\n    rv.update(contour_ids)\n    # NOTE: Because joins can appear in both a \"final join\" and a subsequent\n    # \"join\", we can't know for sure how many unique joins there are without\n    # checking if no cells match or doing an expensive operation checking for\n    # the unique set of final join values.\n    return final_joins.size, rv\n"
  },
  {
    "path": "yt/data_objects/level_sets/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/data_objects/level_sets/tests/test_clump_finding.py",
    "content": "import os\nimport shutil\nimport tempfile\n\nimport numpy as np\nfrom numpy.testing import assert_array_equal, assert_equal\n\nfrom yt.data_objects.level_sets.api import Clump, add_clump_info, find_clumps\nfrom yt.data_objects.level_sets.clump_info_items import clump_info_registry\nfrom yt.fields.derived_field import ValidateParameter\nfrom yt.loaders import load, load_uniform_grid\nfrom yt.testing import requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import data_dir_load\n\n\ndef test_clump_finding():\n    n_c = 8\n    n_p = 1\n    dims = (n_c, n_c, n_c)\n\n    density = np.ones(dims)\n    high_rho = 10.0\n    # add a couple disconnected density enhancements\n    density[2, 2, 2] = high_rho\n    density[6, 6, 6] = high_rho\n\n    # put a particle at the center of one of them\n    dx = 1.0 / n_c\n    px = 2.5 * dx * np.ones(n_p)\n\n    data = {\n        \"density\": density,\n        \"particle_mass\": np.ones(n_p),\n        \"particle_position_x\": px,\n        \"particle_position_y\": px,\n        \"particle_position_z\": px,\n    }\n\n    ds = load_uniform_grid(data, dims)\n\n    ad = ds.all_data()\n    master_clump = Clump(ad, (\"gas\", \"density\"))\n    master_clump.add_validator(\"min_cells\", 1)\n\n    def _total_volume(clump):\n        total_vol = clump.data.quantities.total_quantity(\n            [(\"index\", \"cell_volume\")]\n        ).in_units(\"cm**3\")\n        return \"Cell Volume: %6e cm**3.\", total_vol\n\n    add_clump_info(\"total_volume\", _total_volume)\n    master_clump.add_info_item(\"total_volume\")\n\n    find_clumps(master_clump, 0.5, 2.0 * high_rho, 10.0)\n\n    # there should be two children\n    assert_equal(len(master_clump.children), 2)\n\n    leaf_clumps = master_clump.leaves\n\n    for l in leaf_clumps:\n        keys = l.info.keys()\n        assert \"total_cells\" in keys\n        assert \"cell_mass\" in keys\n        assert \"max_grid_level\" in keys\n        assert \"total_volume\" in keys\n\n    # two leaf clumps\n    assert_equal(len(leaf_clumps), 2)\n\n    # check some clump fields\n    assert_equal(master_clump.children[0][\"gas\", \"density\"][0].size, 1)\n    assert_equal(\n        master_clump.children[0][\"gas\", \"density\"][0], ad[\"gas\", \"density\"].max()\n    )\n    assert_equal(master_clump.children[0][\"all\", \"particle_mass\"].size, 1)\n    assert_array_equal(\n        master_clump.children[0][\"all\", \"particle_mass\"], ad[\"all\", \"particle_mass\"]\n    )\n    assert_equal(master_clump.children[1][\"gas\", \"density\"][0].size, 1)\n    assert_equal(\n        master_clump.children[1][\"gas\", \"density\"][0], ad[\"gas\", \"density\"].max()\n    )\n    assert_equal(master_clump.children[1][\"all\", \"particle_mass\"].size, 0)\n\n    # clean up global registry to avoid polluting other tests\n    del clump_info_registry[\"total_volume\"]\n\n\ni30 = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\n\n@requires_module(\"h5py\")\n@requires_file(i30)\ndef test_clump_tree_save():\n    tmpdir = tempfile.mkdtemp()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n\n    ds = data_dir_load(i30)\n    data_source = ds.disk([0.5, 0.5, 0.5], [0.0, 0.0, 1.0], (8, \"kpc\"), (1, \"kpc\"))\n\n    field = (\"gas\", \"density\")\n    step = 2.0\n    c_min = 10 ** np.floor(np.log10(data_source[field]).min())\n    c_max = 10 ** np.floor(np.log10(data_source[field]).max() + 1)\n\n    master_clump = Clump(data_source, field)\n    master_clump.add_info_item(\"center_of_mass\")\n    master_clump.add_validator(\"min_cells\", 20)\n\n    find_clumps(master_clump, c_min, c_max, step)\n    leaf_clumps = master_clump.leaves\n\n    fn = master_clump.save_as_dataset(\n        fields=[\n            (\"gas\", \"density\"),\n            (\"index\", \"x\"),\n            (\"index\", \"y\"),\n            (\"index\", \"z\"),\n            (\"all\", \"particle_mass\"),\n        ]\n    )\n    ds2 = load(fn)\n\n    # compare clumps in the tree\n    t1 = list(master_clump)\n    t2 = list(ds2.tree)\n    mt1 = ds.arr([c.info[\"cell_mass\"][1] for c in t1])\n    mt2 = ds2.arr([c[\"clump\", \"cell_mass\"] for c in t2])\n    it1 = np.argsort(mt1).astype(\"int64\")\n    it2 = np.argsort(mt2).astype(\"int64\")\n    assert_array_equal(mt1[it1], mt2[it2])\n\n    for i1, i2 in zip(it1, it2, strict=True):\n        ct1 = t1[i1]\n        ct2 = t2[i2]\n        assert_array_equal(ct1[\"gas\", \"density\"], ct2[\"grid\", \"density\"])\n        assert_array_equal(ct1[\"all\", \"particle_mass\"], ct2[\"all\", \"particle_mass\"])\n\n    # compare leaf clumps\n    c1 = list(leaf_clumps)\n    c2 = list(ds2.leaves)\n    mc1 = ds.arr([c.info[\"cell_mass\"][1] for c in c1])\n    mc2 = ds2.arr([c[\"clump\", \"cell_mass\"] for c in c2])\n    ic1 = np.argsort(mc1).astype(\"int64\")\n    ic2 = np.argsort(mc2).astype(\"int64\")\n    assert_array_equal(mc1[ic1], mc2[ic2])\n\n    os.chdir(curdir)\n    shutil.rmtree(tmpdir)\n\n\ni30 = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\n\n@requires_module(\"h5py\")\n@requires_file(i30)\ndef test_clump_field_parameters():\n    \"\"\"\n    Make sure clump finding on fields with field parameters works.\n    \"\"\"\n\n    def _also_density(data):\n        factor = data.get_field_parameter(\"factor\")\n        return factor * data[\"gas\", \"density\"]\n\n    ds = data_dir_load(i30)\n    ds.add_field(\n        (\"gas\", \"also_density\"),\n        function=_also_density,\n        units=ds.fields.gas.density.units,\n        sampling_type=\"cell\",\n        validators=[ValidateParameter(\"factor\")],\n    )\n    data_source = ds.disk([0.5, 0.5, 0.5], [0.0, 0.0, 1.0], (8, \"kpc\"), (1, \"kpc\"))\n    data_source.set_field_parameter(\"factor\", 1)\n\n    step = 2.0\n    field = (\"gas\", \"density\")\n    c_min = 10 ** np.floor(np.log10(data_source[field]).min())\n    c_max = 10 ** np.floor(np.log10(data_source[field]).max() + 1)\n\n    master_clump_1 = Clump(data_source, (\"gas\", \"density\"))\n    master_clump_1.add_validator(\"min_cells\", 20)\n    master_clump_2 = Clump(data_source, (\"gas\", \"also_density\"))\n    master_clump_2.add_validator(\"min_cells\", 20)\n\n    find_clumps(master_clump_1, c_min, c_max, step)\n    find_clumps(master_clump_2, c_min, c_max, step)\n    leaf_clumps_1 = master_clump_1.leaves\n    leaf_clumps_2 = master_clump_2.leaves\n\n    for c1, c2 in zip(leaf_clumps_1, leaf_clumps_2, strict=True):\n        assert_array_equal(c1[\"gas\", \"density\"], c2[\"gas\", \"density\"])\n"
  },
  {
    "path": "yt/data_objects/particle_filters.py",
    "content": "import copy\nfrom contextlib import contextmanager\n\nfrom yt.fields.field_info_container import NullFunc, TranslationFunc\nfrom yt.funcs import mylog\nfrom yt.utilities.exceptions import YTIllDefinedFilter\n\n# One to one mapping\nfilter_registry: dict[str, \"ParticleFilter\"] = {}\n\n\nclass DummyFieldInfo:\n    particle_type = True\n    sampling_type = \"particle\"\n\n\ndfi = DummyFieldInfo()\n\n\nclass ParticleFilter:\n    def __init__(self, name, function, requires, filtered_type):\n        self.name = name\n        self.function = function\n        self.requires = requires[:]\n        self.filtered_type = filtered_type\n\n    @contextmanager\n    def apply(self, dobj):\n        with dobj._chunked_read(dobj._current_chunk):\n            with dobj._field_type_state(self.filtered_type, dfi):\n                # We won't be storing the field data from the whole read, so we\n                # start by filtering now.\n                filter = self.function(self, dobj)\n                yield\n                # Retain a reference here, and we'll filter all appropriate fields\n                # later.\n                fd = dobj.field_data\n        for f, tr in fd.items():\n            if f[0] != self.filtered_type:\n                continue\n            if tr.shape != filter.shape and tr.shape[0] != filter.shape[0]:\n                raise YTIllDefinedFilter(self, tr.shape, filter.shape)\n            else:\n                d = tr[filter]\n            dobj.field_data[self.name, f[1]] = d\n\n    def available(self, field_list):\n        # Note that this assumes that all the fields in field_list have the\n        # same form as the 'requires' attributes.  This won't be true if the\n        # fields are implicitly \"all\" or something.\n        return all((self.filtered_type, field) in field_list for field in self.requires)\n\n    def missing(self, field_list):\n        return [\n            (self.filtered_type, field)\n            for field in self.requires\n            if (self.filtered_type, field) not in field_list\n        ]\n\n    def wrap_func(self, field_name, old_fi):\n        new_fi = copy.copy(old_fi)\n        new_fi.name = (self.name, field_name[1])\n        if old_fi._function == NullFunc:\n            new_fi._function = TranslationFunc(old_fi.name)\n        # Marking the field as inherited\n        new_fi._inherited_particle_filter = True\n        return new_fi\n\n\ndef add_particle_filter(name, function, requires=None, filtered_type=\"all\"):\n    r\"\"\"Create a new particle filter in the global namespace of filters\n\n    A particle filter is a short name that corresponds to an algorithm for\n    filtering a set of particles into a subset.  This is useful for creating new\n    particle types based on a cut on a particle field, such as particle mass, ID\n    or type. After defining a new filter, it still needs to be added to the\n    dataset by calling\n    :func:`~yt.data_objects.static_output.add_particle_filter`.\n\n    .. note::\n       Alternatively, you can make use of the\n       :func:`~yt.data_objects.particle_filters.particle_filter` decorator to\n       define a new particle filter.\n\n    Parameters\n    ----------\n    name : string\n        The name of the particle filter.  New particle fields with particle type\n        set by this name will be added to any dataset that enables this particle\n        filter.\n    function : reference to a function\n        The function that defines the particle filter.  The function should\n        accept two arguments: a reference to a particle filter object and a\n        reference to an abstract yt data object.  See the example below.\n    requires : a list of field names\n        A list of field names required by the particle filter definition.\n    filtered_type : string\n        The name of the particle type to be filtered.\n\n    Examples\n    --------\n    >>> import yt\n\n    >>> def _stars(pfilter, data):\n    ...     return data[pfilter.filtered_type, \"particle_type\"] == 2\n\n    >>> yt.add_particle_filter(\n    ...     \"stars\", function=_stars, filtered_type=\"all\", requires=[\"particle_type\"]\n    ... )\n\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ds.add_particle_filter(\"stars\")\n    >>> ad = ds.all_data()\n    >>> print(ad[\"stars\", \"particle_mass\"])\n    [  1.68243760e+38   1.65690882e+38   1.65813321e+38 ...,   2.04238266e+38\n       2.04523901e+38   2.04770938e+38] g\n\n    \"\"\"\n    if requires is None:\n        requires = []\n    filter = ParticleFilter(name, function, requires, filtered_type)\n    if filter_registry.get(name, None) is not None:\n        mylog.warning(\"The %s particle filter already exists. Overriding.\", name)\n    filter_registry[name] = filter\n\n\ndef particle_filter(name=None, requires=None, filtered_type=\"all\"):\n    r\"\"\"A decorator that adds a new particle filter\n\n    A particle filter is a short name that corresponds to an algorithm for\n    filtering a set of particles into a subset.  This is useful for creating new\n    particle types based on a cut on a particle field, such as particle mass, ID\n    or type.\n\n    .. note::\n       Alternatively, you can make use of the\n       :func:`~yt.data_objects.particle_filters.add_particle_filter` function\n       to define a new particle filter using a more declarative syntax.\n\n    Parameters\n    ----------\n    name : string\n        The name of the particle filter.  New particle fields with particle type\n        set by this name will be added to any dataset that enables this particle\n        filter.  If not set, the name will be inferred from the name of the\n        filter function.\n    requires : a list of field names\n        A list of field names required by the particle filter definition.\n    filtered_type : string\n        The name of the particle type to be filtered.\n\n    Examples\n    --------\n    >>> import yt\n\n    >>> # define a filter named \"stars\"\n    >>> @yt.particle_filter(requires=[\"particle_type\"], filtered_type=\"all\")\n    ... def stars(pfilter, data):\n    ...     return data[pfilter.filtered_type, \"particle_type\"] == 2\n\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ds.add_particle_filter(\"stars\")\n    >>> ad = ds.all_data()\n    >>> print(ad[\"stars\", \"particle_mass\"])\n    [  1.68243760e+38   1.65690882e+38   1.65813321e+38 ...,   2.04238266e+38\n       2.04523901e+38   2.04770938e+38] g\n\n    \"\"\"\n\n    def wrapper(function):\n        if name is None:\n            used_name = function.__name__\n        else:\n            used_name = name\n        return add_particle_filter(used_name, function, requires, filtered_type)\n\n    return wrapper\n"
  },
  {
    "path": "yt/data_objects/particle_trajectories.py",
    "content": "import numpy as np\n\nfrom yt.config import ytcfg\nfrom yt.data_objects.field_data import YTFieldData\nfrom yt.funcs import get_pbar, mylog\nfrom yt.units.yt_array import array_like_field\nfrom yt.utilities.exceptions import YTIllDefinedParticleData\nfrom yt.utilities.lib.particle_mesh_operations import CICSample_3\nfrom yt.utilities.on_demand_imports import _h5py as h5py\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_root_only\n\n\nclass ParticleTrajectories:\n    r\"\"\"A collection of particle trajectories in time over a series of\n    datasets.\n\n    Parameters\n    ----------\n    outputs : ~yt.data_objects.time_series.DatasetSeries\n        DatasetSeries object from which to draw the particles.\n    indices : array_like\n        An integer array of particle indices whose trajectories we\n        want to track. If they are not sorted they will be sorted.\n    fields : list of strings, optional\n        A set of fields that is retrieved when the trajectory\n        collection is instantiated. Default: None (will default\n        to the fields 'particle_position_x', 'particle_position_y',\n        'particle_position_z')\n    suppress_logging : boolean\n        Suppress yt's logging when iterating over the simulation time\n        series. Default: False\n    ptype : str, optional\n        Only use this particle type. Default: None, which uses all particle type.\n\n    Examples\n    --------\n    >>> my_fns = glob.glob(\"orbit_hdf5_chk_00[0-9][0-9]\")\n    >>> my_fns.sort()\n    >>> fields = [\n    ...     (\"all\", \"particle_position_x\"),\n    ...     (\"all\", \"particle_position_y\"),\n    ...     (\"all\", \"particle_position_z\"),\n    ...     (\"all\", \"particle_velocity_x\"),\n    ...     (\"all\", \"particle_velocity_y\"),\n    ...     (\"all\", \"particle_velocity_z\"),\n    ... ]\n    >>> ds = load(my_fns[0])\n    >>> init_sphere = ds.sphere(ds.domain_center, (0.5, \"unitary\"))\n    >>> indices = init_sphere[\"all\", \"particle_index\"].astype(\"int64\")\n    >>> ts = DatasetSeries(my_fns)\n    >>> trajs = ts.particle_trajectories(indices, fields=fields)\n    >>> for t in trajs:\n    ...     print(\n    ...         t[\"all\", \"particle_velocity_x\"].max(),\n    ...         t[\"all\", \"particle_velocity_x\"].min(),\n    ...     )\n    \"\"\"\n\n    def __init__(\n        self, outputs, indices, fields=None, suppress_logging=False, ptype=None\n    ):\n        indices.sort()  # Just in case the caller wasn't careful\n        self.field_data = YTFieldData()\n        self.data_series = outputs\n        self.masks = []\n        self.sorts = []\n        self.array_indices = []\n        self.indices = indices\n        self.num_indices = len(indices)\n        self.num_steps = len(outputs)\n        self.times = []\n        self.suppress_logging = suppress_logging\n        self.ptype = ptype if ptype else \"all\"\n\n        if fields is None:\n            fields = []\n\n        if self.suppress_logging:\n            old_level = int(ytcfg.get(\"yt\", \"log_level\"))\n            mylog.setLevel(40)\n        ds_first = self.data_series[0]\n        dd_first = ds_first.all_data()\n\n        fds = {}\n        for field in (\n            \"particle_index\",\n            \"particle_position_x\",\n            \"particle_position_y\",\n            \"particle_position_z\",\n        ):\n            fds[field] = dd_first._determine_fields((self.ptype, field))[0]\n\n        # Note: we explicitly pass dynamic=False to prevent any change in piter from\n        # breaking the assumption that the same processors load the same datasets\n        my_storage = {}\n        pbar = get_pbar(\"Constructing trajectory information\", len(self.data_series))\n        for i, (sto, ds) in enumerate(\n            self.data_series.piter(storage=my_storage, dynamic=False)\n        ):\n            dd = ds.all_data()\n            newtags = dd[fds[\"particle_index\"]].d.astype(\"int64\")\n            mask = np.isin(newtags, indices, assume_unique=True)\n            sort = np.argsort(newtags[mask])\n            array_indices = np.where(np.isin(indices, newtags, assume_unique=True))[0]\n            self.array_indices.append(array_indices)\n            self.masks.append(mask)\n            self.sorts.append(sort)\n\n            pfields = {}\n            for field in (f\"particle_position_{ax}\" for ax in \"xyz\"):\n                pfields[field] = dd[fds[field]].ndarray_view()[mask][sort]\n\n            sto.result_id = ds.parameter_filename\n            sto.result = (ds.current_time, array_indices, pfields)\n            pbar.update(i + 1)\n        pbar.finish()\n\n        if self.suppress_logging:\n            mylog.setLevel(old_level)\n\n        sorted_storage = sorted(my_storage.items())\n        _fn, (time, *_) = sorted_storage[0]\n        time_units = time.units\n        times = [time.to(time_units) for _fn, (time, *_) in sorted_storage]\n        self.times = self.data_series[0].arr([time.value for time in times], time_units)\n\n        self.particle_fields = []\n        output_field = np.empty((self.num_indices, self.num_steps))\n        output_field.fill(np.nan)\n        for field in (f\"particle_position_{ax}\" for ax in \"xyz\"):\n            for i, (_fn, (_time, indices, pfields)) in enumerate(sorted_storage):\n                try:\n                    # This will fail if particles ids are\n                    # duplicate. This is due to the fact that the rhs\n                    # would then have a different shape as the lhs\n                    output_field[indices, i] = pfields[field]\n                except ValueError as e:\n                    raise YTIllDefinedParticleData(\n                        \"This dataset contains duplicate particle indices!\"\n                    ) from e\n            self.field_data[fds[field]] = array_like_field(\n                dd_first, output_field.copy(), fds[field]\n            )\n            self.particle_fields.append(field)\n\n        # Instantiate fields the caller requested\n        self._get_data(fields)\n\n    def has_key(self, key):\n        return key in self.field_data\n\n    def keys(self):\n        return self.field_data.keys()\n\n    def __getitem__(self, key):\n        \"\"\"\n        Get the field associated with key.\n        \"\"\"\n        if key == \"particle_time\":\n            return self.times\n        if key not in self.field_data:\n            self._get_data([key])\n        return self.field_data[key]\n\n    def __setitem__(self, key, val):\n        \"\"\"\n        Sets a field to be some other value.\n        \"\"\"\n        self.field_data[key] = val\n\n    def __delitem__(self, key):\n        \"\"\"\n        Delete the field from the trajectory\n        \"\"\"\n        del self.field_data[key]\n\n    def __iter__(self):\n        \"\"\"\n        This iterates over the trajectories for\n        the different particles, returning dicts\n        of fields for each trajectory\n        \"\"\"\n        for idx in range(self.num_indices):\n            traj = {}\n            traj[\"particle_index\"] = self.indices[idx]\n            traj[\"particle_time\"] = self.times\n            for field in self.field_data.keys():\n                traj[field] = self[field][idx, :]\n            yield traj\n\n    def __len__(self):\n        \"\"\"\n        The number of individual trajectories\n        \"\"\"\n        return self.num_indices\n\n    def add_fields(self, fields):\n        \"\"\"\n        Add a list of fields to an existing trajectory\n\n        Parameters\n        ----------\n        fields : list of strings\n            A list of fields to be added to the current trajectory\n            collection.\n\n        Examples\n        ________\n        >>> trajs = ParticleTrajectories(my_fns, indices)\n        >>> trajs.add_fields([(\"all\", \"particle_mass\"), (\"all\", \"particle_gpot\")])\n        \"\"\"\n        self._get_data(fields)\n\n    def _get_data(self, fields):\n        \"\"\"\n        Get a list of fields to include in the trajectory collection.\n        The trajectory collection itself is a dict of 2D numpy arrays,\n        with shape (num_indices, num_steps)\n        \"\"\"\n\n        missing_fields = [field for field in fields if field not in self.field_data]\n        if not missing_fields:\n            return\n\n        if self.suppress_logging:\n            old_level = int(ytcfg.get(\"yt\", \"log_level\"))\n            mylog.setLevel(40)\n        ds_first = self.data_series[0]\n        dd_first = ds_first.all_data()\n\n        fds = {}\n        new_particle_fields = []\n        for field in missing_fields:\n            fds[field] = dd_first._determine_fields(field)[0]\n            if field not in self.particle_fields:\n                ftype = fds[field][0]\n                if ftype in ds_first.particle_types:\n                    self.particle_fields.append(field)\n                    new_particle_fields.append(field)\n\n        grid_fields = [\n            field for field in missing_fields if field not in self.particle_fields\n        ]\n        step = 0\n        fields_str = \", \".join(str(f) for f in missing_fields)\n        pbar = get_pbar(\n            f\"Generating [{fields_str}] fields in trajectories\",\n            self.num_steps,\n        )\n\n        # Note: we explicitly pass dynamic=False to prevent any change in piter from\n        # breaking the assumption that the same processors load the same datasets\n        my_storage = {}\n        for i, (sto, ds) in enumerate(\n            self.data_series.piter(storage=my_storage, dynamic=False)\n        ):\n            mask = self.masks[i]\n            sort = self.sorts[i]\n            pfield = {}\n\n            if new_particle_fields:  # there's at least one particle field\n                dd = ds.all_data()\n                for field in new_particle_fields:\n                    # This is easy... just get the particle fields\n                    pfield[field] = dd[fds[field]].d[mask][sort]\n\n            if grid_fields:\n                # This is hard... must loop over grids\n                for field in grid_fields:\n                    pfield[field] = np.zeros(self.num_indices)\n                x = self[\"particle_position_x\"][:, step].d\n                y = self[\"particle_position_y\"][:, step].d\n                z = self[\"particle_position_z\"][:, step].d\n                particle_grids, particle_grid_inds = ds.index._find_points(x, y, z)\n\n                # This will fail for non-grid index objects\n                for grid in particle_grids:\n                    cube = grid.retrieve_ghost_zones(1, grid_fields)\n                    for field in grid_fields:\n                        CICSample_3(\n                            x,\n                            y,\n                            z,\n                            pfield[field],\n                            self.num_indices,\n                            cube[fds[field]],\n                            np.array(grid.LeftEdge, dtype=\"float64\"),\n                            np.array(grid.ActiveDimensions, dtype=\"int32\"),\n                            grid.dds[0],\n                        )\n            sto.result_id = ds.parameter_filename\n            sto.result = (self.array_indices[i], pfield)\n            pbar.update(step)\n            step += 1\n        pbar.finish()\n\n        output_field = np.empty((self.num_indices, self.num_steps))\n        output_field.fill(np.nan)\n        for field in missing_fields:\n            fd = fds[field]\n            for i, (_fn, (indices, pfield)) in enumerate(sorted(my_storage.items())):\n                output_field[indices, i] = pfield[field]\n            self.field_data[field] = array_like_field(dd_first, output_field.copy(), fd)\n\n        if self.suppress_logging:\n            mylog.setLevel(old_level)\n\n    def trajectory_from_index(self, index):\n        \"\"\"\n        Retrieve a single trajectory corresponding to a specific particle\n        index\n\n        Parameters\n        ----------\n        index : int\n            This defines which particle trajectory from the\n            ParticleTrajectories object will be returned.\n\n        Returns\n        -------\n        A dictionary corresponding to the particle's trajectory and the\n        fields along that trajectory\n\n        Examples\n        --------\n        >>> import matplotlib.pyplot as plt\n        >>> trajs = ParticleTrajectories(my_fns, indices)\n        >>> traj = trajs.trajectory_from_index(indices[0])\n        >>> plt.plot(\n        ...     traj[\"all\", \"particle_time\"],\n        ...     traj[\"all\", \"particle_position_x\"],\n        ...     \"-x\",\n        ... )\n        >>> plt.savefig(\"orbit\")\n        \"\"\"\n        mask = np.isin(self.indices, (index,), assume_unique=True)\n        if not np.any(mask):\n            print(f\"The particle index {index} is not in the list!\")\n            raise IndexError\n        fields = sorted(self.field_data.keys())\n        traj = {}\n        traj[self.ptype, \"particle_time\"] = self.times\n        traj[self.ptype, \"particle_index\"] = index\n        for field in fields:\n            traj[field] = self[field][mask, :][0]\n        return traj\n\n    @parallel_root_only\n    def write_out(self, filename_base):\n        \"\"\"\n        Write out particle trajectories to tab-separated ASCII files (one\n        for each trajectory) with the field names in the file header. Each\n        file is named with a basename and the index number.\n\n        Parameters\n        ----------\n        filename_base : string\n            The prefix for the outputted ASCII files.\n\n        Examples\n        --------\n        >>> trajs = ParticleTrajectories(my_fns, indices)\n        >>> trajs.write_out(\"orbit_trajectory\")\n        \"\"\"\n        fields = sorted(self.field_data.keys())\n        num_fields = len(fields)\n        first_str = \"# particle_time\\t\" + \"\\t\".join(fields) + \"\\n\"\n        template_str = \"%g\\t\" * num_fields + \"%g\\n\"\n        for ix in range(self.num_indices):\n            outlines = [first_str]\n            for it in range(self.num_steps):\n                outlines.append(\n                    template_str\n                    % tuple(\n                        [self.times[it]] + [self[field][ix, it] for field in fields]\n                    )\n                )\n            fid = open(f\"{filename_base}_{self.indices[ix]}.dat\", \"w\")\n            fid.writelines(outlines)\n            fid.close()\n            del fid\n\n    @parallel_root_only\n    def write_out_h5(self, filename):\n        \"\"\"\n        Write out all the particle trajectories to a single HDF5 file\n        that contains the indices, the times, and the 2D array for each\n        field individually\n\n        Parameters\n        ----------\n\n        filename : string\n            The output filename for the HDF5 file\n\n        Examples\n        --------\n        >>> trajs = ParticleTrajectories(my_fns, indices)\n        >>> trajs.write_out_h5(\"orbit_trajectories\")\n        \"\"\"\n        fid = h5py.File(filename, mode=\"w\")\n        fid.create_dataset(\"particle_indices\", dtype=np.int64, data=self.indices)\n        fid.close()\n        self.times.write_hdf5(filename, dataset_name=\"particle_times\")\n        fields = sorted(self.field_data.keys())\n        for field in fields:\n            self[field].write_hdf5(filename, dataset_name=f\"{field}\")\n"
  },
  {
    "path": "yt/data_objects/particle_unions.py",
    "content": "from yt._maintenance.deprecation import issue_deprecation_warning\n\nfrom .unions import ParticleUnion  # noqa: F401\n\nissue_deprecation_warning(\n    \"Importing ParticleUnion from yt.data_objects.particle_unions is deprecated. \"\n    \"Please import this class from yt.data_objects.unions instead\",\n    stacklevel=3,\n    since=\"4.2\",\n)\n"
  },
  {
    "path": "yt/data_objects/profiles.py",
    "content": "import numpy as np\nfrom more_itertools import collapse\n\nfrom yt.data_objects.field_data import YTFieldData\nfrom yt.fields.derived_field import DerivedField\nfrom yt.frontends.ytdata.utilities import save_as_dataset\nfrom yt.funcs import get_output_filename, is_sequence, iter_fields, mylog\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.units.yt_array import YTQuantity, array_like_field\nfrom yt.utilities.exceptions import (\n    YTIllDefinedBounds,\n    YTIllDefinedProfile,\n    YTProfileDataShape,\n)\nfrom yt.utilities.lib.misc_utilities import (\n    new_bin_profile1d,\n    new_bin_profile2d,\n    new_bin_profile3d,\n)\nfrom yt.utilities.lib.particle_mesh_operations import CICDeposit_2, NGPDeposit_2\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    ParallelAnalysisInterface,\n    parallel_objects,\n)\n\n\ndef _sanitize_min_max_units(amin, amax, finfo, registry):\n    # returns a copy of amin and amax, converted to finfo's output units\n    umin = getattr(amin, \"units\", None)\n    umax = getattr(amax, \"units\", None)\n    if umin is None:\n        umin = Unit(finfo.output_units, registry=registry)\n        rmin = YTQuantity(amin, umin)\n    else:\n        rmin = amin.in_units(finfo.output_units)\n    if umax is None:\n        umax = Unit(finfo.output_units, registry=registry)\n        rmax = YTQuantity(amax, umax)\n    else:\n        rmax = amax.in_units(finfo.output_units)\n    return rmin, rmax\n\n\ndef preserve_source_parameters(func):\n    def save_state(*args, **kwargs):\n        # Temporarily replace the 'field_parameters' for a\n        # grid with the 'field_parameters' for the data source\n        prof = args[0]\n        source = args[1]\n        if hasattr(source, \"field_parameters\"):\n            old_params = source.field_parameters\n            source.field_parameters = prof._data_source.field_parameters\n            try:\n                return func(*args, **kwargs)\n            finally:\n                # restore field_parameter state even if func raises an exception\n                # see https://github.com/yt-project/yt/pull/5377\n                source.field_parameters = old_params\n        return func(*args, **kwargs)\n\n    return save_state\n\n\nclass ProfileFieldAccumulator:\n    def __init__(self, n_fields, size):\n        shape = size + (n_fields,)\n        self.values = np.zeros(shape, dtype=\"float64\")\n        self.mvalues = np.zeros(shape, dtype=\"float64\")\n        self.qvalues = np.zeros(shape, dtype=\"float64\")\n        self.used = np.zeros(size, dtype=\"bool\")\n        self.weight_values = np.zeros(size, dtype=\"float64\")\n\n\nclass ProfileND(ParallelAnalysisInterface):\n    \"\"\"The profile object class\"\"\"\n\n    def __init__(self, data_source, weight_field=None):\n        self.data_source = data_source\n        self.ds = data_source.ds\n        self.field_map = {}\n        self.field_info = {}\n        self.field_data = YTFieldData()\n        if weight_field is not None:\n            self.standard_deviation = YTFieldData()\n            weight_field = self.data_source._determine_fields(weight_field)[0]\n        else:\n            self.standard_deviation = None\n        self.weight_field = weight_field\n        self.field_units = {}\n        ParallelAnalysisInterface.__init__(self, comm=data_source.comm)\n\n    def add_fields(self, fields):\n        \"\"\"Add fields to profile\n\n        Parameters\n        ----------\n        fields : list of field names\n            A list of fields to create profile histograms for\n\n        \"\"\"\n        fields = self.data_source._determine_fields(fields)\n        for f in fields:\n            self.field_info[f] = self.data_source.ds.field_info[f]\n        temp_storage = ProfileFieldAccumulator(len(fields), self.size)\n        citer = self.data_source.chunks([], \"io\")\n        for chunk in parallel_objects(citer):\n            self._bin_chunk(chunk, fields, temp_storage)\n        self._finalize_storage(fields, temp_storage)\n\n    def set_field_unit(self, field, new_unit):\n        \"\"\"Sets a new unit for the requested field\n\n        Parameters\n        ----------\n        field : string or field tuple\n           The name of the field that is to be changed.\n\n        new_unit : string or Unit object\n           The name of the new unit.\n        \"\"\"\n        if field in self.field_units:\n            self.field_units[field] = Unit(new_unit, registry=self.ds.unit_registry)\n        else:\n            fd = self.field_map[field]\n            if fd in self.field_units:\n                self.field_units[fd] = Unit(new_unit, registry=self.ds.unit_registry)\n            else:\n                raise KeyError(f\"{field} not in profile!\")\n\n    def _finalize_storage(self, fields, temp_storage):\n        # We use our main comm here\n        # This also will fill _field_data\n\n        for i, _field in enumerate(fields):\n            # q values are returned as q * weight but we want just q\n            temp_storage.qvalues[..., i][temp_storage.used] /= (\n                temp_storage.weight_values[temp_storage.used]\n            )\n\n        # get the profile data from all procs\n        all_store = {self.comm.rank: temp_storage}\n        all_store = self.comm.par_combine_object(all_store, \"join\", datatype=\"dict\")\n\n        all_val = np.zeros_like(temp_storage.values)\n        all_mean = np.zeros_like(temp_storage.mvalues)\n        all_std = np.zeros_like(temp_storage.qvalues)\n        all_weight = np.zeros_like(temp_storage.weight_values)\n        all_used = np.zeros_like(temp_storage.used, dtype=\"bool\")\n\n        # Combine the weighted mean and standard deviation from each processor.\n        # For two samples with total weight, mean, and standard deviation\n        # given by w, m, and s, their combined mean and standard deviation are:\n        # m12 = (m1 * w1 + m2 * w2) / (w1 + w2)\n        # s12 = (m1 * (s1**2 + (m1 - m12)**2) +\n        #        m2 * (s2**2 + (m2 - m12)**2)) / (w1 + w2)\n        # Here, the mvalues are m and the qvalues are s**2.\n        for p in sorted(all_store.keys()):\n            all_used += all_store[p].used\n            old_mean = all_mean.copy()\n            old_weight = all_weight.copy()\n            all_weight[all_store[p].used] += all_store[p].weight_values[\n                all_store[p].used\n            ]\n            for i, _field in enumerate(fields):\n                all_val[..., i][all_store[p].used] += all_store[p].values[..., i][\n                    all_store[p].used\n                ]\n\n                all_mean[..., i][all_store[p].used] = (\n                    all_mean[..., i] * old_weight\n                    + all_store[p].mvalues[..., i] * all_store[p].weight_values\n                )[all_store[p].used] / all_weight[all_store[p].used]\n\n                all_std[..., i][all_store[p].used] = (\n                    old_weight\n                    * (all_std[..., i] + (old_mean[..., i] - all_mean[..., i]) ** 2)\n                    + all_store[p].weight_values\n                    * (\n                        all_store[p].qvalues[..., i]\n                        + (all_store[p].mvalues[..., i] - all_mean[..., i]) ** 2\n                    )\n                )[all_store[p].used] / all_weight[all_store[p].used]\n\n        all_std = np.sqrt(all_std)\n        del all_store\n        self.used = all_used\n        blank = ~all_used\n\n        self.weight = all_weight\n        self.weight[blank] = 0.0\n\n        for i, field in enumerate(fields):\n            if self.weight_field is None:\n                self.field_data[field] = array_like_field(\n                    self.data_source, all_val[..., i], field\n                )\n            else:\n                self.field_data[field] = array_like_field(\n                    self.data_source, all_mean[..., i], field\n                )\n                self.standard_deviation[field] = array_like_field(\n                    self.data_source, all_std[..., i], field\n                )\n                self.standard_deviation[field][blank] = 0.0\n                self.weight = array_like_field(\n                    self.data_source, self.weight, self.weight_field\n                )\n            self.field_data[field][blank] = 0.0\n            self.field_units[field] = self.field_data[field].units\n            if isinstance(field, tuple):\n                self.field_map[field[1]] = field\n            else:\n                self.field_map[field] = field\n\n    def _bin_chunk(self, chunk, fields, storage):\n        raise NotImplementedError\n\n    def _filter(self, bin_fields):\n        # cut_points is set to be everything initially, but\n        # we also want to apply a filtering based on min/max\n        pfilter = np.ones(bin_fields[0].shape, dtype=\"bool\")\n        for (mi, ma), data in zip(self.bounds, bin_fields, strict=True):\n            pfilter &= data > mi\n            pfilter &= data < ma\n        return pfilter, [data[pfilter] for data in bin_fields]\n\n    def _get_data(self, chunk, fields):\n        # We are using chunks now, which will manage the field parameters and\n        # the like.\n        bin_fields = [chunk[bf] for bf in self.bin_fields]\n        for i in range(1, len(bin_fields)):\n            if bin_fields[0].shape != bin_fields[i].shape:\n                raise YTProfileDataShape(\n                    self.bin_fields[0],\n                    bin_fields[0].shape,\n                    self.bin_fields[i],\n                    bin_fields[i].shape,\n                )\n        # We want to make sure that our fields are within the bounds of the\n        # binning\n        pfilter, bin_fields = self._filter(bin_fields)\n        if not np.any(pfilter):\n            return None\n        arr = np.zeros((bin_fields[0].size, len(fields)), dtype=\"float64\")\n        for i, field in enumerate(fields):\n            if pfilter.shape != chunk[field].shape:\n                raise YTProfileDataShape(\n                    self.bin_fields[0], bin_fields[0].shape, field, chunk[field].shape\n                )\n            units = chunk.ds.field_info[field].output_units\n            arr[:, i] = chunk[field][pfilter].in_units(units)\n        if self.weight_field is not None:\n            if pfilter.shape != chunk[self.weight_field].shape:\n                raise YTProfileDataShape(\n                    self.bin_fields[0],\n                    bin_fields[0].shape,\n                    self.weight_field,\n                    chunk[self.weight_field].shape,\n                )\n            units = chunk.ds.field_info[self.weight_field].output_units\n            weight_data = chunk[self.weight_field].in_units(units)\n        else:\n            weight_data = np.ones(pfilter.shape, dtype=\"float64\")\n        weight_data = weight_data[pfilter]\n        # So that we can pass these into\n        return arr, weight_data, bin_fields\n\n    def __getitem__(self, field):\n        if field in self.field_data:\n            fname = field\n        else:\n            # deal with string vs tuple field names and attempt to guess which field\n            # we are supposed to be talking about\n            fname = self.field_map.get(field, None)\n            if isinstance(field, tuple):\n                fname = self.field_map.get(field[1], None)\n                if fname != field:\n                    raise KeyError(\n                        f\"Asked for field '{field}' but only have data for \"\n                        f\"fields '{list(self.field_data.keys())}'\"\n                    )\n            elif isinstance(field, DerivedField):\n                fname = self.field_map.get(field.name[1], None)\n            if fname is None:\n                raise KeyError(field)\n        if getattr(self, \"fractional\", False):\n            return self.field_data[fname]\n        else:\n            return self.field_data[fname].in_units(self.field_units[fname])\n\n    def items(self):\n        return [(k, self[k]) for k in self.field_data.keys()]\n\n    def keys(self):\n        return self.field_data.keys()\n\n    def __iter__(self):\n        return sorted(self.items())\n\n    def _get_bins(self, mi, ma, n, take_log):\n        if take_log:\n            ret = np.logspace(np.log10(mi), np.log10(ma), n + 1)\n            # at this point ret[0] and ret[-1] are not exactly equal to\n            # mi and ma due to round-off error. Let's force them to be\n            # mi and ma exactly to avoid incorrectly discarding cells near\n            # the edges. See Issue #1300.\n            ret[0], ret[-1] = mi, ma\n            return ret\n        else:\n            return np.linspace(mi, ma, n + 1)\n\n    def save_as_dataset(self, filename=None):\n        r\"\"\"Export a profile to a reloadable yt dataset.\n\n        This function will take a profile and output a dataset\n        containing all relevant fields.  The resulting dataset\n        can be reloaded as a yt dataset.\n\n        Parameters\n        ----------\n        filename : str, optional\n            The name of the file to be written.  If None, the name\n            will be a combination of the original dataset plus\n            the type of object, e.g., Profile1D.\n\n        Returns\n        -------\n        filename : str\n            The name of the file that has been created.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n        >>> ad = ds.all_data()\n        >>> profile = yt.create_profile(\n        ...     ad,\n        ...     [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        ...     (\"gas\", \"mass\"),\n        ...     weight_field=None,\n        ...     n_bins=(128, 128),\n        ... )\n        >>> fn = profile.save_as_dataset()\n        >>> prof_ds = yt.load(fn)\n        >>> print(prof_ds.data[\"gas\", \"mass\"])\n        (128, 128)\n        >>> print(prof_ds.data[\"index\", \"x\"].shape)  # x bins as 1D array\n        (128,)\n        >>> print(prof_ds.data[\"gas\", \"density\"])  # x bins as 2D array\n        (128, 128)\n        >>> p = yt.PhasePlot(\n        ...     prof_ds.data,\n        ...     (\"gas\", \"density\"),\n        ...     (\"gas\", \"temperature\"),\n        ...     (\"gas\", \"mass\"),\n        ...     weight_field=None,\n        ... )\n        >>> p.save()\n\n        \"\"\"\n\n        keyword = f\"{str(self.ds)}_{self.__class__.__name__}\"\n        filename = get_output_filename(filename, keyword, \".h5\")\n\n        args = (\"field\", \"log\")\n        extra_attrs = {\n            \"data_type\": \"yt_profile\",\n            \"profile_dimensions\": self.size,\n            \"weight_field\": self.weight_field,\n            \"fractional\": self.fractional,\n            \"accumulation\": self.accumulation,\n        }\n        data = {}\n        data.update(self.field_data)\n        data[\"weight\"] = self.weight\n        data[\"used\"] = self.used.astype(\"float64\")\n        std = \"standard_deviation\"\n        if self.weight_field is not None:\n            std_data = getattr(self, std)\n            data.update({(std, field[1]): std_data[field] for field in self.field_data})\n\n        dimensionality = 0\n        bin_data = []\n        for ax in \"xyz\":\n            if hasattr(self, ax):\n                dimensionality += 1\n                data[ax] = getattr(self, ax)\n                bin_data.append(data[ax])\n                bin_field_name = f\"{ax}_bins\"\n                data[bin_field_name] = getattr(self, bin_field_name)\n                extra_attrs[f\"{ax}_range\"] = self.ds.arr(\n                    [data[bin_field_name][0], data[bin_field_name][-1]]\n                )\n                for arg in args:\n                    key = f\"{ax}_{arg}\"\n                    extra_attrs[key] = getattr(self, key)\n\n        bin_fields = np.meshgrid(*bin_data)\n        for i, ax in enumerate(\"xyz\"[:dimensionality]):\n            data[getattr(self, f\"{ax}_field\")] = bin_fields[i]\n\n        extra_attrs[\"dimensionality\"] = dimensionality\n        ftypes = {field: \"data\" for field in data if field[0] != std}\n        if self.weight_field is not None:\n            ftypes.update({(std, field[1]): std for field in self.field_data})\n        save_as_dataset(\n            self.ds, filename, data, field_types=ftypes, extra_attrs=extra_attrs\n        )\n\n        return filename\n\n\nclass ProfileNDFromDataset(ProfileND):\n    \"\"\"\n    An ND profile object loaded from a ytdata dataset.\n    \"\"\"\n\n    def __init__(self, ds):\n        ProfileND.__init__(self, ds.data, ds.parameters.get(\"weight_field\", None))\n        self.fractional = ds.parameters.get(\"fractional\", False)\n        self.accumulation = ds.parameters.get(\"accumulation\", False)\n        exclude_fields = [\"used\", \"weight\"]\n        for ax in \"xyz\"[: ds.dimensionality]:\n            setattr(self, ax, ds.data[\"data\", ax])\n            ax_bins = f\"{ax}_bins\"\n            ax_field = f\"{ax}_field\"\n            ax_log = f\"{ax}_log\"\n            setattr(self, ax_bins, ds.data[\"data\", ax_bins])\n            field_name = tuple(ds.parameters.get(ax_field, (None, None)))\n            setattr(self, ax_field, field_name)\n            self.field_info[field_name] = ds.field_info[field_name]\n            setattr(self, ax_log, ds.parameters.get(ax_log, False))\n            exclude_fields.extend([ax, ax_bins, field_name[1]])\n        self.weight = ds.data[\"data\", \"weight\"]\n        self.used = ds.data[\"data\", \"used\"].d.astype(bool)\n        profile_fields = [\n            f\n            for f in ds.field_list\n            if f[1] not in exclude_fields and f[0] != \"standard_deviation\"\n        ]\n        for field in profile_fields:\n            self.field_map[field[1]] = field\n            self.field_data[field] = ds.data[field]\n            self.field_info[field] = ds.field_info[field]\n            self.field_units[field] = ds.data[field].units\n            if (\"standard_deviation\", field[1]) in ds.field_list:\n                self.standard_deviation[field] = ds.data[\"standard_deviation\", field[1]]\n\n\nclass Profile1D(ProfileND):\n    \"\"\"An object that represents a 1D profile.\n\n    Parameters\n    ----------\n\n    data_source : AMD3DData object\n        The data object to be profiled\n    x_field : string field name\n        The field to profile as a function of\n    x_n : integer\n        The number of bins along the x direction.\n    x_min : float\n        The minimum value of the x profile field. If supplied without units,\n        assumed to be in the output units for x_field.\n    x_max : float\n        The maximum value of the x profile field. If supplied without units,\n        assumed to be in the output units for x_field.\n    x_log : boolean\n        Controls whether or not the bins for the x field are evenly\n        spaced in linear (False) or log (True) space.\n    weight_field : string field name\n        The field to weight the profiled fields by.\n    override_bins_x : array\n        Array to set as xbins and ignore other parameters if set\n\n    \"\"\"\n\n    def __init__(\n        self,\n        data_source,\n        x_field,\n        x_n,\n        x_min,\n        x_max,\n        x_log,\n        weight_field=None,\n        override_bins_x=None,\n    ):\n        super().__init__(data_source, weight_field)\n        self.x_field = data_source._determine_fields(x_field)[0]\n        self.field_info[self.x_field] = self.data_source.ds.field_info[self.x_field]\n        self.x_log = x_log\n        x_min, x_max = _sanitize_min_max_units(\n            x_min, x_max, self.field_info[self.x_field], self.ds.unit_registry\n        )\n        self.x_bins = array_like_field(\n            data_source, self._get_bins(x_min, x_max, x_n, x_log), self.x_field\n        )\n\n        if override_bins_x is not None:\n            self.x_bins = array_like_field(data_source, override_bins_x, self.x_field)\n\n        self.size = (self.x_bins.size - 1,)\n        self.bin_fields = (self.x_field,)\n        self.x = 0.5 * (self.x_bins[1:] + self.x_bins[:-1])\n\n    def _bin_chunk(self, chunk, fields, storage):\n        rv = self._get_data(chunk, fields)\n        if rv is None:\n            return\n        fdata, wdata, (bf_x,) = rv\n        bf_x.convert_to_units(self.field_info[self.x_field].output_units)\n        bin_ind = np.digitize(bf_x, self.x_bins) - 1\n        new_bin_profile1d(\n            bin_ind,\n            wdata,\n            fdata,\n            storage.weight_values,\n            storage.values,\n            storage.mvalues,\n            storage.qvalues,\n            storage.used,\n        )\n\n        # We've binned it!\n\n    def set_x_unit(self, new_unit):\n        \"\"\"Sets a new unit for the x field\n\n        Parameters\n        ----------\n        new_unit : string or Unit object\n            The name of the new unit.\n        \"\"\"\n        self.x_bins.convert_to_units(new_unit)\n        self.x = 0.5 * (self.x_bins[1:] + self.x_bins[:-1])\n\n    @property\n    def bounds(self):\n        return ((self.x_bins[0], self.x_bins[-1]),)\n\n    def plot(self):\n        r\"\"\"\n        This returns a :class:`~yt.visualization.profile_plotter.ProfilePlot`\n        with the fields that have been added to this object.\n        \"\"\"\n        from yt.visualization.profile_plotter import ProfilePlot\n\n        return ProfilePlot.from_profiles(self)\n\n    def _export_prep(self, fields, only_used):\n        if only_used:\n            idxs = self.used\n        else:\n            idxs = slice(None, None, None)\n        if not only_used and not np.all(self.used):\n            masked = True\n        else:\n            masked = False\n        if fields is None:\n            fields = self.field_data.keys()\n        else:\n            fields = self.data_source._determine_fields(fields)\n        return idxs, masked, fields\n\n    def to_dataframe(self, fields=None, only_used=False, include_std=False):\n        r\"\"\"Export a profile object to a pandas DataFrame.\n\n        This function will take a data object and construct from it and\n        optionally a list of fields a pandas DataFrame object.  If pandas is\n        not importable, this will raise ImportError.\n\n        Parameters\n        ----------\n        fields : list of strings or tuple field names, default None\n            If this is supplied, it is the list of fields to be exported into\n            the DataFrame. If not supplied, whatever fields exist in the\n            profile, along with the bin field, will be exported.\n        only_used : boolean, default False\n            If True, only the bins which have data will be exported. If False,\n            all the bins will be exported, but the elements for those bins\n            in the data arrays will be filled with NaNs.\n        include_std : boolean, optional\n            If True, include the standard deviation of the profile\n            in the pandas DataFrame. It will appear in the table as the\n            field name with \"_stddev\" appended, e.g. \"velocity_x_stddev\".\n            Default: False\n\n        Returns\n        -------\n        df : :class:`~pandas.DataFrame`\n            The data contained in the profile.\n\n        Examples\n        --------\n        >>> sp = ds.sphere(\"c\", (0.1, \"unitary\"))\n        >>> p = sp.profile(\n        ...     (\"index\", \"radius\"), [(\"gas\", \"density\"), (\"gas\", \"temperature\")]\n        ... )\n        >>> df1 = p.to_dataframe()\n        >>> df2 = p.to_dataframe(fields=(\"gas\", \"density\"), only_used=True)\n        \"\"\"\n        from yt.utilities.on_demand_imports import _pandas as pd\n\n        idxs, masked, fields = self._export_prep(fields, only_used)\n        pdata = {self.x_field[-1]: self.x[idxs]}\n        for field in fields:\n            pdata[field[-1]] = self[field][idxs]\n            if include_std:\n                pdata[f\"{field[-1]}_stddev\"] = self.standard_deviation[field][idxs]\n        df = pd.DataFrame(pdata)\n        if masked:\n            mask = np.zeros(df.shape, dtype=\"bool\")\n            mask[~self.used, 1:] = True\n            df.mask(mask, inplace=True)\n        return df\n\n    def to_astropy_table(self, fields=None, only_used=False, include_std=False):\n        \"\"\"\n        Export the profile data to a :class:`~astropy.table.table.QTable`,\n        which is a Table object which is unit-aware. The QTable can then\n        be exported to an ASCII file, FITS file, etc.\n\n        See the AstroPy Table docs for more details:\n        http://docs.astropy.org/en/stable/table/\n\n        Parameters\n        ----------\n        fields : list of strings or tuple field names, default None\n            If this is supplied, it is the list of fields to be exported into\n            the DataFrame. If not supplied, whatever fields exist in the\n            profile, along with the bin field, will be exported.\n        only_used : boolean, optional\n            If True, only the bins which are used are copied\n            to the QTable as rows. If False, all bins are\n            copied, but the bins which are not used are masked.\n            Default: False\n        include_std : boolean, optional\n            If True, include the standard deviation of the profile\n            in the AstroPy QTable. It will appear in the table as the\n            field name with \"_stddev\" appended, e.g. \"velocity_x_stddev\".\n            Default: False\n\n        Returns\n        -------\n        qt : :class:`~astropy.table.QTable`\n            The data contained in the profile.\n\n        Examples\n        --------\n        >>> sp = ds.sphere(\"c\", (0.1, \"unitary\"))\n        >>> p = sp.profile(\n        ...     (\"index\", \"radius\"), [(\"gas\", \"density\"), (\"gas\", \"temperature\")]\n        ... )\n        >>> qt1 = p.to_astropy_table()\n        >>> qt2 = p.to_astropy_table(fields=(\"gas\", \"density\"), only_used=True)\n        \"\"\"\n        from astropy.table import QTable\n\n        idxs, masked, fields = self._export_prep(fields, only_used)\n        qt = QTable(masked=masked)\n        qt[self.x_field[-1]] = self.x[idxs].to_astropy()\n        if masked:\n            qt[self.x_field[-1]].mask = self.used\n        for field in fields:\n            qt[field[-1]] = self[field][idxs].to_astropy()\n            if masked:\n                qt[field[-1]].mask = self.used\n            if include_std:\n                qt[f\"{field[-1]}_stddev\"] = self.standard_deviation[field][\n                    idxs\n                ].to_astropy()\n                if masked:\n                    qt[f\"{field[-1]}_stddev\"].mask = self.used\n        return qt\n\n\nclass Profile1DFromDataset(ProfileNDFromDataset, Profile1D):\n    \"\"\"\n    A 1D profile object loaded from a ytdata dataset.\n    \"\"\"\n\n    def __init(self, ds):\n        ProfileNDFromDataset.__init__(self, ds)\n\n\nclass Profile2D(ProfileND):\n    \"\"\"An object that represents a 2D profile.\n\n    Parameters\n    ----------\n\n    data_source : AMD3DData object\n        The data object to be profiled\n    x_field : string field name\n        The field to profile as a function of along the x axis.\n    x_n : integer\n        The number of bins along the x direction.\n    x_min : float\n        The minimum value of the x profile field. If supplied without units,\n        assumed to be in the output units for x_field.\n    x_max : float\n        The maximum value of the x profile field. If supplied without units,\n        assumed to be in the output units for x_field.\n    x_log : boolean\n        Controls whether or not the bins for the x field are evenly\n        spaced in linear (False) or log (True) space.\n    y_field : string field name\n        The field to profile as a function of along the y axis\n    y_n : integer\n        The number of bins along the y direction.\n    y_min : float\n        The minimum value of the y profile field. If supplied without units,\n        assumed to be in the output units for y_field.\n    y_max : float\n        The maximum value of the y profile field. If supplied without units,\n        assumed to be in the output units for y_field.\n    y_log : boolean\n        Controls whether or not the bins for the y field are evenly\n        spaced in linear (False) or log (True) space.\n    weight_field : string field name\n        The field to weight the profiled fields by.\n    override_bins_x : array\n        Array to set as xbins and ignore other parameters if set\n    override_bins_y : array\n        Array to set as ybins and ignore other parameters if set\n\n    \"\"\"\n\n    def __init__(\n        self,\n        data_source,\n        x_field,\n        x_n,\n        x_min,\n        x_max,\n        x_log,\n        y_field,\n        y_n,\n        y_min,\n        y_max,\n        y_log,\n        weight_field=None,\n        override_bins_x=None,\n        override_bins_y=None,\n    ):\n        super().__init__(data_source, weight_field)\n        # X\n        self.x_field = data_source._determine_fields(x_field)[0]\n        self.x_log = x_log\n        self.field_info[self.x_field] = self.data_source.ds.field_info[self.x_field]\n        x_min, x_max = _sanitize_min_max_units(\n            x_min, x_max, self.field_info[self.x_field], self.ds.unit_registry\n        )\n        self.x_bins = array_like_field(\n            data_source, self._get_bins(x_min, x_max, x_n, x_log), self.x_field\n        )\n        if override_bins_x is not None:\n            self.x_bins = array_like_field(data_source, override_bins_x, self.x_field)\n\n        # Y\n        self.y_field = data_source._determine_fields(y_field)[0]\n        self.y_log = y_log\n        self.field_info[self.y_field] = self.data_source.ds.field_info[self.y_field]\n        y_min, y_max = _sanitize_min_max_units(\n            y_min, y_max, self.field_info[self.y_field], self.ds.unit_registry\n        )\n        self.y_bins = array_like_field(\n            data_source, self._get_bins(y_min, y_max, y_n, y_log), self.y_field\n        )\n        if override_bins_y is not None:\n            self.y_bins = array_like_field(data_source, override_bins_y, self.y_field)\n\n        self.size = (self.x_bins.size - 1, self.y_bins.size - 1)\n\n        self.bin_fields = (self.x_field, self.y_field)\n        self.x = 0.5 * (self.x_bins[1:] + self.x_bins[:-1])\n        self.y = 0.5 * (self.y_bins[1:] + self.y_bins[:-1])\n\n    def _bin_chunk(self, chunk, fields, storage):\n        rv = self._get_data(chunk, fields)\n        if rv is None:\n            return\n        fdata, wdata, (bf_x, bf_y) = rv\n        bf_x.convert_to_units(self.field_info[self.x_field].output_units)\n        bin_ind_x = np.digitize(bf_x, self.x_bins) - 1\n        bf_y.convert_to_units(self.field_info[self.y_field].output_units)\n        bin_ind_y = np.digitize(bf_y, self.y_bins) - 1\n        new_bin_profile2d(\n            bin_ind_x,\n            bin_ind_y,\n            wdata,\n            fdata,\n            storage.weight_values,\n            storage.values,\n            storage.mvalues,\n            storage.qvalues,\n            storage.used,\n        )\n        # We've binned it!\n\n    def set_x_unit(self, new_unit):\n        \"\"\"Sets a new unit for the x field\n\n        Parameters\n        ----------\n        new_unit : string or Unit object\n            The name of the new unit.\n        \"\"\"\n        self.x_bins.convert_to_units(new_unit)\n        self.x = 0.5 * (self.x_bins[1:] + self.x_bins[:-1])\n\n    def set_y_unit(self, new_unit):\n        \"\"\"Sets a new unit for the y field\n\n        Parameters\n        ----------\n        new_unit : string or Unit object\n            The name of the new unit.\n        \"\"\"\n        self.y_bins.convert_to_units(new_unit)\n        self.y = 0.5 * (self.y_bins[1:] + self.y_bins[:-1])\n\n    @property\n    def bounds(self):\n        return ((self.x_bins[0], self.x_bins[-1]), (self.y_bins[0], self.y_bins[-1]))\n\n    def plot(self):\n        r\"\"\"\n        This returns a :class:~yt.visualization.profile_plotter.PhasePlot with\n        the fields that have been added to this object.\n        \"\"\"\n        from yt.visualization.profile_plotter import PhasePlot\n\n        return PhasePlot.from_profile(self)\n\n\nclass Profile2DFromDataset(ProfileNDFromDataset, Profile2D):\n    \"\"\"\n    A 2D profile object loaded from a ytdata dataset.\n    \"\"\"\n\n    def __init(self, ds):\n        ProfileNDFromDataset.__init__(self, ds)\n\n\nclass ParticleProfile(Profile2D):\n    \"\"\"An object that represents a *deposited* 2D profile. This is like a\n    Profile2D, except that it is intended for particle data. Instead of just\n    binning the particles, the added fields will be deposited onto the mesh\n    using either the nearest-grid-point or cloud-in-cell interpolation kernels.\n\n    Parameters\n    ----------\n\n    data_source : AMD3DData object\n        The data object to be profiled\n    x_field : string field name\n        The field to profile as a function of along the x axis.\n    x_n : integer\n        The number of bins along the x direction.\n    x_min : float\n        The minimum value of the x profile field. If supplied without units,\n        assumed to be in the output units for x_field.\n    x_max : float\n        The maximum value of the x profile field. If supplied without units,\n        assumed to be in the output units for x_field.\n    y_field : string field name\n        The field to profile as a function of along the y axis\n    y_n : integer\n        The number of bins along the y direction.\n    y_min : float\n        The minimum value of the y profile field. If supplied without units,\n        assumed to be in the output units for y_field.\n    y_max : float\n        The maximum value of the y profile field. If supplied without units,\n        assumed to be in the output units for y_field.\n    weight_field : string field name\n        The field to use for weighting. Default is None.\n    deposition : string, optional\n        The interpolation kernel to be used for\n        deposition. Valid choices:\n        \"ngp\" : nearest grid point interpolation\n        \"cic\" : cloud-in-cell interpolation\n\n    \"\"\"\n\n    accumulation = False\n    fractional = False\n\n    def __init__(\n        self,\n        data_source,\n        x_field,\n        x_n,\n        x_min,\n        x_max,\n        x_log,\n        y_field,\n        y_n,\n        y_min,\n        y_max,\n        y_log,\n        weight_field=None,\n        deposition=\"ngp\",\n    ):\n        x_field = data_source._determine_fields(x_field)[0]\n        y_field = data_source._determine_fields(y_field)[0]\n\n        if deposition not in [\"ngp\", \"cic\"]:\n            raise NotImplementedError(deposition)\n        elif (x_log or y_log) and deposition != \"ngp\":\n            mylog.warning(\n                \"cic deposition is only supported for linear axis \"\n                \"scales, falling back to ngp deposition\"\n            )\n            deposition = \"ngp\"\n\n        self.deposition = deposition\n\n        # set the log parameters to False (since that doesn't make much sense\n        # for deposited data) and also turn off the weight field.\n        super().__init__(\n            data_source,\n            x_field,\n            x_n,\n            x_min,\n            x_max,\n            x_log,\n            y_field,\n            y_n,\n            y_min,\n            y_max,\n            y_log,\n            weight_field=weight_field,\n        )\n\n    # Either stick the particle field in the nearest bin,\n    # or spread it out using the 2D CIC deposition function\n    def _bin_chunk(self, chunk, fields, storage):\n        rv = self._get_data(chunk, fields)\n        if rv is None:\n            return\n        fdata, wdata, (bf_x, bf_y) = rv\n        # make sure everything has the same units before deposition.\n        # the units will be scaled to the correct values later.\n\n        if self.deposition == \"ngp\":\n            func = NGPDeposit_2\n        elif self.deposition == \"cic\":\n            func = CICDeposit_2\n\n        for fi, _field in enumerate(fields):\n            if self.weight_field is None:\n                deposit_vals = fdata[:, fi]\n            else:\n                deposit_vals = wdata * fdata[:, fi]\n\n            field_mask = np.zeros(self.size, dtype=\"uint8\")\n\n            func(\n                bf_x,\n                bf_y,\n                deposit_vals,\n                fdata[:, fi].size,\n                storage.values[:, :, fi],\n                field_mask,\n                self.x_bins,\n                self.y_bins,\n            )\n\n            locs = field_mask > 0\n            storage.used[locs] = True\n\n            if self.weight_field is not None:\n                func(\n                    bf_x,\n                    bf_y,\n                    wdata,\n                    fdata[:, fi].size,\n                    storage.weight_values,\n                    field_mask,\n                    self.x_bins,\n                    self.y_bins,\n                )\n            else:\n                storage.weight_values[locs] = 1.0\n            storage.mvalues[locs, fi] = (\n                storage.values[locs, fi] / storage.weight_values[locs]\n            )\n        # We've binned it!\n\n\nclass Profile3D(ProfileND):\n    \"\"\"An object that represents a 2D profile.\n\n    Parameters\n    ----------\n\n    data_source : AMD3DData object\n        The data object to be profiled\n    x_field : string field name\n        The field to profile as a function of along the x axis.\n    x_n : integer\n        The number of bins along the x direction.\n    x_min : float\n        The minimum value of the x profile field. If supplied without units,\n        assumed to be in the output units for x_field.\n    x_max : float\n        The maximum value of the x profile field. If supplied without units,\n        assumed to be in the output units for x_field.\n    x_log : boolean\n        Controls whether or not the bins for the x field are evenly\n        spaced in linear (False) or log (True) space.\n    y_field : string field name\n        The field to profile as a function of along the y axis\n    y_n : integer\n        The number of bins along the y direction.\n    y_min : float\n        The minimum value of the y profile field. If supplied without units,\n        assumed to be in the output units for y_field.\n    y_max : float\n        The maximum value of the y profile field. If supplied without units,\n        assumed to be in the output units for y_field.\n    y_log : boolean\n        Controls whether or not the bins for the y field are evenly\n        spaced in linear (False) or log (True) space.\n    z_field : string field name\n        The field to profile as a function of along the z axis\n    z_n : integer\n        The number of bins along the z direction.\n    z_min : float\n        The minimum value of the z profile field. If supplied without units,\n        assumed to be in the output units for z_field.\n    z_max : float\n        The maximum value of thee z profile field. If supplied without units,\n        assumed to be in the output units for z_field.\n    z_log : boolean\n        Controls whether or not the bins for the z field are evenly\n        spaced in linear (False) or log (True) space.\n    weight_field : string field name\n        The field to weight the profiled fields by.\n    override_bins_x : array\n        Array to set as xbins and ignore other parameters if set\n    override_bins_y : array\n        Array to set as xbins and ignore other parameters if set\n    override_bins_z : array\n        Array to set as xbins and ignore other parameters if set\n\n    \"\"\"\n\n    def __init__(\n        self,\n        data_source,\n        x_field,\n        x_n,\n        x_min,\n        x_max,\n        x_log,\n        y_field,\n        y_n,\n        y_min,\n        y_max,\n        y_log,\n        z_field,\n        z_n,\n        z_min,\n        z_max,\n        z_log,\n        weight_field=None,\n        override_bins_x=None,\n        override_bins_y=None,\n        override_bins_z=None,\n    ):\n        super().__init__(data_source, weight_field)\n        # X\n        self.x_field = data_source._determine_fields(x_field)[0]\n        self.x_log = x_log\n        self.field_info[self.x_field] = self.data_source.ds.field_info[self.x_field]\n        x_min, x_max = _sanitize_min_max_units(\n            x_min, x_max, self.field_info[self.x_field], self.ds.unit_registry\n        )\n        self.x_bins = array_like_field(\n            data_source, self._get_bins(x_min, x_max, x_n, x_log), self.x_field\n        )\n        if override_bins_x is not None:\n            self.x_bins = array_like_field(data_source, override_bins_x, self.x_field)\n        # Y\n        self.y_field = data_source._determine_fields(y_field)[0]\n        self.y_log = y_log\n        self.field_info[self.y_field] = self.data_source.ds.field_info[self.y_field]\n        y_min, y_max = _sanitize_min_max_units(\n            y_min, y_max, self.field_info[self.y_field], self.ds.unit_registry\n        )\n        self.y_bins = array_like_field(\n            data_source, self._get_bins(y_min, y_max, y_n, y_log), self.y_field\n        )\n        if override_bins_y is not None:\n            self.y_bins = array_like_field(data_source, override_bins_y, self.y_field)\n        # Z\n        self.z_field = data_source._determine_fields(z_field)[0]\n        self.z_log = z_log\n        self.field_info[self.z_field] = self.data_source.ds.field_info[self.z_field]\n        z_min, z_max = _sanitize_min_max_units(\n            z_min, z_max, self.field_info[self.z_field], self.ds.unit_registry\n        )\n        self.z_bins = array_like_field(\n            data_source, self._get_bins(z_min, z_max, z_n, z_log), self.z_field\n        )\n        if override_bins_z is not None:\n            self.z_bins = array_like_field(data_source, override_bins_z, self.z_field)\n\n        self.size = (self.x_bins.size - 1, self.y_bins.size - 1, self.z_bins.size - 1)\n\n        self.bin_fields = (self.x_field, self.y_field, self.z_field)\n        self.x = 0.5 * (self.x_bins[1:] + self.x_bins[:-1])\n        self.y = 0.5 * (self.y_bins[1:] + self.y_bins[:-1])\n        self.z = 0.5 * (self.z_bins[1:] + self.z_bins[:-1])\n\n    def _bin_chunk(self, chunk, fields, storage):\n        rv = self._get_data(chunk, fields)\n        if rv is None:\n            return\n        fdata, wdata, (bf_x, bf_y, bf_z) = rv\n        bf_x.convert_to_units(self.field_info[self.x_field].output_units)\n        bin_ind_x = np.digitize(bf_x, self.x_bins) - 1\n        bf_y.convert_to_units(self.field_info[self.y_field].output_units)\n        bin_ind_y = np.digitize(bf_y, self.y_bins) - 1\n        bf_z.convert_to_units(self.field_info[self.z_field].output_units)\n        bin_ind_z = np.digitize(bf_z, self.z_bins) - 1\n        new_bin_profile3d(\n            bin_ind_x,\n            bin_ind_y,\n            bin_ind_z,\n            wdata,\n            fdata,\n            storage.weight_values,\n            storage.values,\n            storage.mvalues,\n            storage.qvalues,\n            storage.used,\n        )\n        # We've binned it!\n\n    @property\n    def bounds(self):\n        return (\n            (self.x_bins[0], self.x_bins[-1]),\n            (self.y_bins[0], self.y_bins[-1]),\n            (self.z_bins[0], self.z_bins[-1]),\n        )\n\n    def set_x_unit(self, new_unit):\n        \"\"\"Sets a new unit for the x field\n\n        Parameters\n        ----------\n        new_unit : string or Unit object\n            The name of the new unit.\n        \"\"\"\n        self.x_bins.convert_to_units(new_unit)\n        self.x = 0.5 * (self.x_bins[1:] + self.x_bins[:-1])\n\n    def set_y_unit(self, new_unit):\n        \"\"\"Sets a new unit for the y field\n\n        Parameters\n        ----------\n        new_unit : string or Unit object\n            The name of the new unit.\n        \"\"\"\n        self.y_bins.convert_to_units(new_unit)\n        self.y = 0.5 * (self.y_bins[1:] + self.y_bins[:-1])\n\n    def set_z_unit(self, new_unit):\n        \"\"\"Sets a new unit for the z field\n\n        Parameters\n        ----------\n        new_unit : string or Unit object\n            The name of the new unit.\n        \"\"\"\n        self.z_bins.convert_to_units(new_unit)\n        self.z = 0.5 * (self.z_bins[1:] + self.z_bins[:-1])\n\n\nclass Profile3DFromDataset(ProfileNDFromDataset, Profile3D):\n    \"\"\"\n    A 2D profile object loaded from a ytdata dataset.\n    \"\"\"\n\n    def __init(self, ds):\n        ProfileNDFromDataset.__init__(self, ds)\n\n\ndef sanitize_field_tuple_keys(input_dict, data_source):\n    if input_dict is not None:\n        dummy = {}\n        for item in input_dict:\n            dummy[data_source._determine_fields(item)[0]] = input_dict[item]\n        return dummy\n    else:\n        return input_dict\n\n\ndef create_profile(\n    data_source,\n    bin_fields,\n    fields,\n    n_bins=64,\n    extrema=None,\n    logs=None,\n    units=None,\n    weight_field=(\"gas\", \"mass\"),\n    accumulation=False,\n    fractional=False,\n    deposition=\"ngp\",\n    override_bins=None,\n):\n    r\"\"\"\n    Create a 1, 2, or 3D profile object.\n\n    The dimensionality of the profile object is chosen by the number of\n    fields given in the bin_fields argument.\n\n    Parameters\n    ----------\n    data_source : YTSelectionContainer Object\n        The data object to be profiled.\n    bin_fields : list of strings\n        List of the binning fields for profiling.\n    fields : list of strings\n        The fields to be profiled.\n    n_bins : int or list of ints\n        The number of bins in each dimension.  If None, 64 bins for\n        each bin are used for each bin field.\n        Default: 64.\n    extrema : dict of min, max tuples\n        Minimum and maximum values of the bin_fields for the profiles.\n        The keys correspond to the field names. Defaults to the extrema\n        of the bin_fields of the dataset. If a units dict is provided, extrema\n        are understood to be in the units specified in the dictionary.\n    logs : dict of boolean values\n        Whether or not to log the bin_fields for the profiles.\n        The keys correspond to the field names. Defaults to the take_log\n        attribute of the field.\n    units : dict of strings\n        The units of the fields in the profiles, including the bin_fields.\n    weight_field : str or tuple field identifier\n        The weight field for computing weighted average for the profile\n        values.  If None, the profile values are sums of the data in\n        each bin. Defaults to (\"gas\", \"mass\").\n    accumulation : bool or list of bools\n        If True, the profile values for a bin n are the cumulative sum of\n        all the values from bin 0 to n.  If -True, the sum is reversed so\n        that the value for bin n is the cumulative sum from bin N (total bins)\n        to n.  If the profile is 2D or 3D, a list of values can be given to\n        control the summation in each dimension independently.\n        Default: False.\n    fractional : bool\n        If True the profile values are divided by the sum of all\n        the profile data such that the profile represents a probability\n        distribution function.\n    deposition : strings\n        Controls the type of deposition used for ParticlePhasePlots.\n        Valid choices are 'ngp' and 'cic'. Default is 'ngp'. This parameter is\n        ignored if the input fields are not of particle type.\n    override_bins : dict of bins to profile plot with\n        If set, ignores n_bins and extrema settings and uses the\n        supplied bins to profile the field. If a units dict is provided,\n        bins are understood to be in the units specified in the dictionary.\n\n\n    Examples\n    --------\n\n    Create a 1d profile.  Access bin field from profile.x and field\n    data from profile[<field_name>].\n\n    >>> ds = load(\"DD0046/DD0046\")\n    >>> ad = ds.all_data()\n    >>> profile = create_profile(\n    ...     ad, [(\"gas\", \"density\")], [(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")]\n    ... )\n    >>> print(profile.x)\n    >>> print(profile[\"gas\", \"temperature\"])\n\n    \"\"\"\n    bin_fields = data_source._determine_fields(bin_fields)\n    fields = list(iter_fields(fields))\n    is_pfield = [\n        data_source.ds._get_field_info(f).sampling_type == \"particle\"\n        for f in bin_fields + fields\n    ]\n    wf = None\n    if weight_field is not None:\n        wf = data_source.ds._get_field_info(weight_field)\n        is_pfield.append(wf.sampling_type == \"particle\")\n        wf = wf.name\n\n    if len(bin_fields) > 1 and isinstance(accumulation, bool):\n        accumulation = [accumulation for _ in range(len(bin_fields))]\n\n    bin_fields = data_source._determine_fields(bin_fields)\n    fields = data_source._determine_fields(fields)\n    units = sanitize_field_tuple_keys(units, data_source)\n    extrema = sanitize_field_tuple_keys(extrema, data_source)\n    logs = sanitize_field_tuple_keys(logs, data_source)\n    override_bins = sanitize_field_tuple_keys(override_bins, data_source)\n\n    if any(is_pfield) and not all(is_pfield):\n        if hasattr(data_source.ds, \"_sph_ptypes\"):\n            is_local = [\n                data_source.ds.field_info[f].sampling_type == \"local\"\n                for f in bin_fields + fields\n            ]\n            if wf is not None:\n                is_local.append(data_source.ds.field_info[wf].sampling_type == \"local\")\n            is_local_or_pfield = [\n                pf or lf for (pf, lf) in zip(is_pfield, is_local, strict=True)\n            ]\n            if not all(is_local_or_pfield):\n                raise YTIllDefinedProfile(\n                    bin_fields, data_source._determine_fields(fields), wf, is_pfield\n                )\n        else:\n            raise YTIllDefinedProfile(\n                bin_fields, data_source._determine_fields(fields), wf, is_pfield\n            )\n    if len(bin_fields) == 1:\n        cls = Profile1D\n    elif len(bin_fields) == 2 and all(is_pfield):\n        if deposition == \"cic\":\n            if logs is not None:\n                if (bin_fields[0] in logs and logs[bin_fields[0]]) or (\n                    bin_fields[1] in logs and logs[bin_fields[1]]\n                ):\n                    raise RuntimeError(\n                        \"CIC deposition is only implemented for linear-scaled axes\"\n                    )\n            else:\n                logs = {bin_fields[0]: False, bin_fields[1]: False}\n            if any(accumulation) or fractional:\n                raise RuntimeError(\n                    \"The accumulation and fractional keyword arguments must be \"\n                    \"False for CIC deposition\"\n                )\n        cls = ParticleProfile\n    elif len(bin_fields) == 2:\n        cls = Profile2D\n    elif len(bin_fields) == 3:\n        cls = Profile3D\n    else:\n        raise NotImplementedError\n    if weight_field is not None and cls == ParticleProfile:\n        (weight_field,) = data_source._determine_fields([weight_field])\n        wf = data_source.ds._get_field_info(weight_field)\n        if not wf.sampling_type == \"particle\":\n            weight_field = None\n    if not is_sequence(n_bins):\n        n_bins = [n_bins] * len(bin_fields)\n    if not is_sequence(accumulation):\n        accumulation = [accumulation] * len(bin_fields)\n    if logs is None:\n        logs = {}\n    logs_list = []\n    for bin_field in bin_fields:\n        if bin_field in logs:\n            logs_list.append(logs[bin_field])\n        else:\n            logs_list.append(data_source.ds.field_info[bin_field].take_log)\n    logs = logs_list\n\n    # Are the extrema all Nones? Then treat them as though extrema was set as None\n    if extrema is None or not any(collapse(extrema.values())):\n        ex = [\n            data_source.quantities[\"Extrema\"](f, non_zero=l)\n            for f, l in zip(bin_fields, logs, strict=True)\n        ]\n        # pad extrema by epsilon so cells at bin edges are not excluded\n        for i, (mi, ma) in enumerate(ex):\n            mi = mi - np.spacing(mi)\n            ma = ma + np.spacing(ma)\n            ex[i][0], ex[i][1] = mi, ma\n    else:\n        ex = []\n        for bin_field in bin_fields:\n            bf_units = data_source.ds.field_info[bin_field].output_units\n            try:\n                field_ex = list(extrema[bin_field[-1]])\n            except KeyError as e:\n                try:\n                    field_ex = list(extrema[bin_field])\n                except KeyError:\n                    raise RuntimeError(\n                        f\"Could not find field {bin_field[-1]} or {bin_field} in extrema\"\n                    ) from e\n\n            if isinstance(field_ex[0], tuple):\n                field_ex = [data_source.ds.quan(*f) for f in field_ex]\n            if any(exi is None for exi in field_ex):\n                try:\n                    ds_extrema = data_source.quantities.extrema(bin_field)\n                except AttributeError:\n                    # ytdata profile datasets don't have data_source.quantities\n                    bf_vals = data_source[bin_field]\n                    ds_extrema = data_source.ds.arr([bf_vals.min(), bf_vals.max()])\n                for i, exi in enumerate(field_ex):\n                    if exi is None:\n                        field_ex[i] = ds_extrema[i]\n                        # pad extrema by epsilon so cells at bin edges are\n                        # not excluded\n                        field_ex[i] -= (-1) ** i * np.spacing(field_ex[i])\n            if units is not None and bin_field in units:\n                for i, exi in enumerate(field_ex):\n                    if hasattr(exi, \"units\"):\n                        field_ex[i] = exi.to(units[bin_field])\n                    else:\n                        field_ex[i] = data_source.ds.quan(exi, units[bin_field])\n                fe = data_source.ds.arr(field_ex)\n            else:\n                if hasattr(field_ex, \"units\"):\n                    fe = field_ex.to(bf_units)\n                else:\n                    fe = data_source.ds.arr(field_ex, bf_units)\n            fe.convert_to_units(bf_units)\n            field_ex = [fe[0].v, fe[1].v]\n            if is_sequence(field_ex[0]):\n                field_ex[0] = data_source.ds.quan(field_ex[0][0], field_ex[0][1])\n                field_ex[0] = field_ex[0].in_units(bf_units)\n            if is_sequence(field_ex[1]):\n                field_ex[1] = data_source.ds.quan(field_ex[1][0], field_ex[1][1])\n                field_ex[1] = field_ex[1].in_units(bf_units)\n            ex.append(field_ex)\n\n    if override_bins is not None:\n        o_bins = []\n        for bin_field in bin_fields:\n            bf_units = data_source.ds.field_info[bin_field].output_units\n            try:\n                field_obin = override_bins[bin_field[-1]]\n            except KeyError:\n                field_obin = override_bins[bin_field]\n\n            if field_obin is None:\n                o_bins.append(None)\n                continue\n\n            if isinstance(field_obin, tuple):\n                field_obin = data_source.ds.arr(*field_obin)\n\n            if units is not None and bin_field in units:\n                fe = data_source.ds.arr(field_obin, units[bin_field])\n            else:\n                if hasattr(field_obin, \"units\"):\n                    fe = field_obin.to(bf_units)\n                else:\n                    fe = data_source.ds.arr(field_obin, bf_units)\n            fe.convert_to_units(bf_units)\n            field_obin = fe.d\n            o_bins.append(field_obin)\n\n    args = [data_source]\n    for f, n, (mi, ma), l in zip(bin_fields, n_bins, ex, logs, strict=True):\n        if mi <= 0 and l:\n            raise YTIllDefinedBounds(mi, ma)\n        args += [f, n, mi, ma, l]\n    kwargs = {\"weight_field\": weight_field}\n    if cls is ParticleProfile:\n        kwargs[\"deposition\"] = deposition\n    if override_bins is not None:\n        for o_bin, ax in zip(o_bins, [\"x\", \"y\", \"z\"], strict=False):\n            kwargs[f\"override_bins_{ax}\"] = o_bin\n    obj = cls(*args, **kwargs)\n    obj.accumulation = accumulation\n    obj.fractional = fractional\n    if fields is not None:\n        obj.add_fields(list(fields))\n    for field in fields:\n        if fractional:\n            obj.field_data[field] /= obj.field_data[field].sum()\n        for axis, acc in enumerate(accumulation):\n            if not acc:\n                continue\n            temp = obj.field_data[field]\n            temp = np.rollaxis(temp, axis)\n            if weight_field is not None:\n                temp_weight = obj.weight\n                temp_weight = np.rollaxis(temp_weight, axis)\n            if acc < 0:\n                temp = temp[::-1]\n                if weight_field is not None:\n                    temp_weight = temp_weight[::-1]\n            if weight_field is None:\n                temp = temp.cumsum(axis=0)\n            else:\n                # avoid 0-division warnings by nan-masking\n                _denom = temp_weight.cumsum(axis=0)\n                _denom[_denom == 0.0] = np.nan\n                temp = (temp * temp_weight).cumsum(axis=0) / _denom\n            if acc < 0:\n                temp = temp[::-1]\n                if weight_field is not None:\n                    temp_weight = temp_weight[::-1]\n            temp = np.rollaxis(temp, axis)\n            obj.field_data[field] = temp\n            if weight_field is not None:\n                temp_weight = np.rollaxis(temp_weight, axis)\n                obj.weight = temp_weight\n    if units is not None:\n        for field, unit in units.items():\n            field = data_source._determine_fields(field)[0]\n            if field == obj.x_field:\n                obj.set_x_unit(unit)\n            elif field == getattr(obj, \"y_field\", None):\n                obj.set_y_unit(unit)\n            elif field == getattr(obj, \"z_field\", None):\n                obj.set_z_unit(unit)\n            else:\n                obj.set_field_unit(field, unit)\n    return obj\n"
  },
  {
    "path": "yt/data_objects/region_expression.py",
    "content": "import weakref\nfrom functools import cached_property\n\nfrom yt.funcs import obj_length\nfrom yt.units.yt_array import YTQuantity\nfrom yt.utilities.exceptions import (\n    YTDimensionalityError,\n    YTFieldNotFound,\n    YTFieldNotParseable,\n)\nfrom yt.visualization.line_plot import LineBuffer\n\nfrom .data_containers import _get_ipython_key_completion\n\n\nclass RegionExpression:\n    def __init__(self, ds):\n        self.ds = weakref.proxy(ds)\n\n    @cached_property\n    def all_data(self):\n        return self.ds.all_data()\n\n    def __getitem__(self, item):\n        # At first, we will only implement this as accepting a slice that is\n        # (optionally) unitful corresponding to a specific set of coordinates\n        # that result in a rectangular prism or a slice.\n        try:\n            return self.all_data[item]\n        except (YTFieldNotParseable, YTFieldNotFound):\n            # any error raised by self.ds._get_field_info\n            # signals a type error (not a field), however we don't want to\n            # catch plain TypeErrors as this may create subtle bugs very hard\n            # to decipher, like broken internal function calls.\n            pass\n\n        if isinstance(item, slice):\n            if obj_length(item.start) == 3 and obj_length(item.stop) == 3:\n                # This is for a ray that is not orthogonal to an axis.\n                # it's straightforward to do this, so we create a ray\n                # and drop out here.\n                return self._create_ray(item)\n            else:\n                # This is for the case where we give a slice as an index; one\n                # possible use case of this would be where we supply something\n                # like ds.r[::256j] .  This would be expanded, implicitly into\n                # ds.r[::256j, ::256j, ::256j].  Other cases would be if we do\n                # ds.r[0.1:0.9] where it will be expanded along all dimensions.\n                item = tuple(item for _ in range(self.ds.dimensionality))\n\n        if item is Ellipsis:\n            item = (Ellipsis,)\n\n        # from this point, item is implicitly assumed to be iterable\n        if Ellipsis in item:\n            # expand \"...\" into the appropriate number of \":\"\n            item = list(item)\n            idx = item.index(Ellipsis)\n            item.pop(idx)\n            if Ellipsis in item:\n                # this error mimics numpy's\n                raise IndexError(\"an index can only have a single ellipsis ('...')\")\n            while len(item) < self.ds.dimensionality:\n                item.insert(idx, slice(None))\n\n        if len(item) != self.ds.dimensionality:\n            # Not the right specification, and we don't want to do anything\n            # implicitly.  Note that this happens *after* the implicit expansion\n            # of a single slice.\n            raise YTDimensionalityError(len(item), self.ds.dimensionality)\n\n        # OK, now we need to look at our slices.  How many are a specific\n        # coordinate?\n\n        nslices = sum(isinstance(v, slice) for v in item)\n        if nslices == 0:\n            return self._create_point(item)\n        elif nslices == 1:\n            return self._create_ortho_ray(item)\n        elif nslices == 2:\n            return self._create_slice(item)\n        else:\n            if all(s.start is s.stop is s.step is None for s in item):\n                return self.all_data\n            return self._create_region(item)\n\n    def _ipython_key_completions_(self):\n        return _get_ipython_key_completion(self.ds)\n\n    def _spec_to_value(self, input):\n        if isinstance(input, tuple):\n            v = self.ds.quan(input[0], input[1]).to(\"code_length\")\n        elif isinstance(input, YTQuantity):\n            v = self.ds.quan(input).to(\"code_length\")\n        else:\n            v = self.ds.quan(input, \"code_length\")\n        return v\n\n    def _create_slice(self, slice_tuple):\n        # This is somewhat more complex because we want to allow for slicing\n        # in one dimension but also *not* using the entire domain; for instance\n        # this means we allow something like ds.r[0.5, 0.1:0.4, 0.1:0.4].\n        axis = None\n        new_slice = []\n        for ax, v in enumerate(slice_tuple):\n            if not isinstance(v, slice):\n                if axis is not None:\n                    raise RuntimeError\n                axis = ax\n                coord = self._spec_to_value(v)\n                new_slice.append(slice(None, None, None))\n            else:\n                new_slice.append(v)\n        # This new slice doesn't need to be a tuple\n        dim = self.ds.dimensionality\n        if dim < 2:\n            raise ValueError(\n                f\"Can not create a slice from data with dimensionality '{dim}'\"\n            )\n        if dim == 2:\n            coord = self.ds.domain_center[2]\n            axis = 2\n        source = self._create_region(new_slice)\n        sl = self.ds.slice(axis, coord, data_source=source)\n        # Now, there's the possibility that what we're also seeing here\n        # includes some steps, which would be for getting back a fixed\n        # resolution buffer.  We check for that by checking if we have\n        # exactly two imaginary steps.\n        xax = self.ds.coordinates.x_axis[axis]\n        yax = self.ds.coordinates.y_axis[axis]\n        if (\n            getattr(new_slice[xax].step, \"imag\", 0.0) != 0.0\n            and getattr(new_slice[yax].step, \"imag\", 0.0) != 0.0\n        ):\n            # We now need to convert to a fixed res buffer.\n            # We'll do this by getting the x/y axes, and then using that.\n            width = source.right_edge[xax] - source.left_edge[xax]\n            height = source.right_edge[yax] - source.left_edge[yax]\n            # Make a resolution tuple with\n            resolution = (int(new_slice[xax].step.imag), int(new_slice[yax].step.imag))\n            # Use the center of the slice, not the entire domain\n            center = source.center\n            sl = sl.to_frb(\n                width=width, resolution=resolution, height=height, center=center\n            )\n        return sl\n\n    def _slice_to_edges(self, ax, val):\n        if val.start is None:\n            l = self.ds.domain_left_edge[ax]\n        else:\n            l = self._spec_to_value(val.start)\n        if val.stop is None:\n            r = self.ds.domain_right_edge[ax]\n        else:\n            r = self._spec_to_value(val.stop)\n        if r < l:\n            raise RuntimeError\n        return l, r\n\n    def _create_region(self, bounds_tuple):\n        left_edge = self.ds.domain_left_edge.copy()\n        right_edge = self.ds.domain_right_edge.copy()\n        dims = [1, 1, 1]\n        for ax, b in enumerate(bounds_tuple):\n            l, r = self._slice_to_edges(ax, b)\n            left_edge[ax] = l\n            right_edge[ax] = r\n            d = getattr(b.step, \"imag\", None)\n            if d is not None:\n                d = int(d)\n            dims[ax] = d\n        center = (left_edge + right_edge) / 2.0\n        if None not in dims:\n            return self.ds.arbitrary_grid(left_edge, right_edge, dims)\n        return self.ds.region(center, left_edge, right_edge)\n\n    def _create_point(self, point_tuple):\n        coord = [self._spec_to_value(p) for p in point_tuple]\n        return self.ds.point(coord)\n\n    def _create_ray(self, ray_slice):\n        start_point = [self._spec_to_value(v) for v in ray_slice.start]\n        end_point = [self._spec_to_value(v) for v in ray_slice.stop]\n        if getattr(ray_slice.step, \"imag\", 0.0) != 0.0:\n            return LineBuffer(self.ds, start_point, end_point, int(ray_slice.step.imag))\n        else:\n            return self.ds.ray(start_point, end_point)\n\n    def _create_ortho_ray(self, ray_tuple):\n        axis = None\n        new_slice = []\n        coord = []\n        npoints = 0\n        start_point = []\n        end_point = []\n        for ax, v in enumerate(ray_tuple):\n            if not isinstance(v, slice):\n                val = self._spec_to_value(v)\n                coord.append(val)\n                new_slice.append(slice(None, None, None))\n                start_point.append(val)\n                end_point.append(val)\n            else:\n                if axis is not None:\n                    raise RuntimeError\n                if getattr(v.step, \"imag\", 0.0) != 0.0:\n                    npoints = int(v.step.imag)\n                    xi = self._spec_to_value(v.start)\n                    xf = self._spec_to_value(v.stop)\n                    dx = (xf - xi) / npoints\n                    start_point.append(xi + 0.5 * dx)\n                    end_point.append(xf - 0.5 * dx)\n                else:\n                    axis = ax\n                    new_slice.append(v)\n        if npoints > 0:\n            ray = LineBuffer(self.ds, start_point, end_point, npoints)\n        else:\n            if axis == 1:\n                coord = [coord[1], coord[0]]\n            source = self._create_region(new_slice)\n            ray = self.ds.ortho_ray(axis, coord, data_source=source)\n        return ray\n"
  },
  {
    "path": "yt/data_objects/selection_objects/__init__.py",
    "content": "from .boolean_operations import (\n    YTBooleanContainer,\n    YTDataObjectUnion,\n    YTIntersectionContainer3D,\n)\nfrom .cut_region import YTCutRegion\nfrom .disk import YTDisk\nfrom .object_collection import YTDataCollection\nfrom .point import YTPoint\nfrom .ray import YTOrthoRay, YTRay\nfrom .region import YTRegion\nfrom .slices import YTCuttingPlane, YTSlice\nfrom .spheroids import YTEllipsoid, YTMinimalSphere, YTSphere\n"
  },
  {
    "path": "yt/data_objects/selection_objects/boolean_operations.py",
    "content": "import numpy as np\nfrom more_itertools import always_iterable\n\nimport yt.geometry\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n    YTSelectionContainer3D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import validate_object, validate_sequence\n\n\nclass YTBooleanContainer(YTSelectionContainer3D):\n    r\"\"\"\n    This is a boolean operation, accepting AND, OR, XOR, and NOT for combining\n    multiple data objects.\n\n    This object is not designed to be created directly; it is designed to be\n    created implicitly by using one of the bitwise operations (&, \\|, ^, \\~) on\n    one or two other data objects.  These correspond to the appropriate boolean\n    operations, and the resultant object can be nested.\n\n    Parameters\n    ----------\n    op : string\n        Can be AND, OR, XOR, NOT or NEG.\n    dobj1 : yt.data_objects.selection_objects.data_selection_objects.\n            YTSelectionContainer\n        The first selection object\n    dobj2 : yt.data_objects.selection_objects.base_objects.YTSelectionContainer\n        The second object\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> sp = ds.sphere(\"c\", 0.1)\n    >>> dd = ds.r[:, :, :]\n    >>> new_obj = sp ^ dd\n    >>> print(new_obj.sum(\"cell_volume\"), dd.sum(\"cell_volume\") - sp.sum(\"cell_volume\"))\n    \"\"\"\n\n    _type_name = \"bool\"\n    _con_args = (\"op\", \"dobj1\", \"dobj2\")\n\n    def __init__(\n        self, op, dobj1, dobj2, ds=None, field_parameters=None, data_source=None\n    ):\n        YTSelectionContainer3D.__init__(self, None, ds, field_parameters, data_source)\n        self.op = op.upper()\n        self.dobj1 = dobj1\n        self.dobj2 = dobj2\n        name = f\"Boolean{self.op}Selector\"\n        sel_cls = getattr(yt.geometry.selection_routines, name)\n        self._selector = sel_cls(self)\n\n    def _get_bbox(self):\n        le1, re1 = self.dobj1._get_bbox()\n        if self.op == \"NOT\":\n            return le1, re1\n        else:\n            le2, re2 = self.dobj2._get_bbox()\n            return np.minimum(le1, le2), np.maximum(re1, re2)\n\n\nclass YTIntersectionContainer3D(YTSelectionContainer3D):\n    \"\"\"\n    This is a more efficient method of selecting the intersection of multiple\n    data selection objects.\n\n    Creating one of these objects returns the intersection of all of the\n    sub-objects; it is designed to be a faster method than chaining & (\"and\")\n    operations to create a single, large intersection.\n\n    Parameters\n    ----------\n    data_objects : Iterable of YTSelectionContainer\n        The data objects to intersect\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> sp1 = ds.sphere((0.4, 0.5, 0.6), 0.15)\n    >>> sp2 = ds.sphere((0.38, 0.51, 0.55), 0.1)\n    >>> sp3 = ds.sphere((0.35, 0.5, 0.6), 0.15)\n    >>> new_obj = ds.intersection((sp1, sp2, sp3))\n    >>> print(new_obj.sum(\"cell_volume\"))\n    \"\"\"\n\n    _type_name = \"intersection\"\n    _con_args = (\"data_objects\",)\n\n    def __init__(self, data_objects, ds=None, field_parameters=None, data_source=None):\n        validate_sequence(data_objects)\n        for obj in data_objects:\n            validate_object(obj, YTSelectionContainer)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        YTSelectionContainer3D.__init__(self, None, ds, field_parameters, data_source)\n        self.data_objects = list(always_iterable(data_objects))\n\n    def get_bbox(self):\n        # Get the bounding box of the intersection\n        bbox = self.data_objects[0].get_bbox()\n        for obj in self.data_objects[1:]:\n            cur_bbox = obj._get_bbox()\n            bbox = (\n                np.maximum(bbox[0], cur_bbox[0]),\n                np.minimum(bbox[1], cur_bbox[1]),\n            )\n        return bbox\n\n\nclass YTDataObjectUnion(YTSelectionContainer3D):\n    \"\"\"\n    This is a more efficient method of selecting the union of multiple\n    data selection objects.\n\n    Creating one of these objects returns the union of all of the sub-objects;\n    it is designed to be a faster method than chaining | (or) operations to\n    create a single, large union.\n\n    Parameters\n    ----------\n    data_objects : Iterable of YTSelectionContainer\n        The data objects to union\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> sp1 = ds.sphere((0.4, 0.5, 0.6), 0.1)\n    >>> sp2 = ds.sphere((0.3, 0.5, 0.15), 0.1)\n    >>> sp3 = ds.sphere((0.5, 0.5, 0.9), 0.1)\n    >>> new_obj = ds.union((sp1, sp2, sp3))\n    >>> print(new_obj.sum(\"cell_volume\"))\n    \"\"\"\n\n    _type_name = \"union\"\n    _con_args = (\"data_objects\",)\n\n    def __init__(self, data_objects, ds=None, field_parameters=None, data_source=None):\n        validate_sequence(data_objects)\n        for obj in data_objects:\n            validate_object(obj, YTSelectionContainer)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        YTSelectionContainer3D.__init__(self, None, ds, field_parameters, data_source)\n        self.data_objects = list(always_iterable(data_objects))\n"
  },
  {
    "path": "yt/data_objects/selection_objects/cut_region.py",
    "content": "import re\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n    YTSelectionContainer3D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.fields.derived_field import DerivedFieldCombination\nfrom yt.funcs import iter_fields, validate_object\nfrom yt.geometry.selection_routines import points_in_cells\nfrom yt.utilities.exceptions import YTIllDefinedCutRegion\nfrom yt.utilities.on_demand_imports import _scipy\n\n\nclass YTCutRegion(YTSelectionContainer3D):\n    \"\"\"\n    This is a data object designed to allow individuals to apply logical\n    operations to fields and filter as a result of those cuts.\n\n    Parameters\n    ----------\n    data_source : YTSelectionContainer3D\n        The object to which cuts will be applied.\n    conditionals : list of strings\n        A list of conditionals that will be evaluated.  In the namespace\n        available, these conditionals will have access to 'obj' which is a data\n        object of unknown shape, and they must generate a boolean array.  For\n        instance, conditionals = [\"obj['gas', 'temperature'] < 1e3\"]\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> sp = ds.sphere(\"max\", (1.0, \"Mpc\"))\n    >>> cr = ds.cut_region(sp, [\"obj['gas', 'temperature'] < 1e3\"])\n    \"\"\"\n\n    _type_name = \"cut_region\"\n    _con_args = (\"base_object\", \"conditionals\")\n    _derived_quantity_chunking = \"all\"\n\n    def __init__(\n        self,\n        data_source,\n        conditionals,\n        ds=None,\n        field_parameters=None,\n        base_object=None,\n        locals=None,\n    ):\n        if locals is None:\n            locals = {}\n        validate_object(data_source, YTSelectionContainer)\n        conditionals = list(always_iterable(conditionals))\n        for condition in conditionals:\n            validate_object(condition, (str, DerivedFieldCombination))\n\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(base_object, YTSelectionContainer)\n\n        self.conditionals = conditionals\n        if isinstance(data_source, YTCutRegion):\n            # If the source is also a cut region, add its conditionals\n            # and set the source to be its source.\n            # Preserve order of conditionals.\n            self.conditionals = data_source.conditionals + self.conditionals\n            new_locals = data_source.locals.copy()\n            new_locals.update(locals)\n            locals = new_locals\n            data_source = data_source.base_object\n\n        super().__init__(\n            data_source.center, ds, field_parameters, data_source=data_source\n        )\n        self.filter_fields = self._check_filter_fields()\n        self.base_object = data_source\n        self.locals = locals\n        self._selector = None\n        # Need to interpose for __getitem__, fwidth, fcoords, icoords, iwidth,\n        # ires and get_data\n\n    def _check_filter_fields(self):\n        fields = []\n        for cond in self.conditionals:\n            if isinstance(cond, DerivedFieldCombination):\n                fields.extend(cond.getDependentFields())\n                continue\n\n            for field in re.findall(r\"\\[([A-Za-z0-9_,.'\\\"\\(\\)]+)\\]\", cond):\n                fd = field.replace('\"', \"\").replace(\"'\", \"\")\n                if \",\" in fd:\n                    fd = tuple(fd.strip(\"()\").split(\",\"))\n                fd = self.ds._get_field_info(fd)\n                if fd.sampling_type == \"particle\" or fd.is_sph_field:\n                    raise RuntimeError(\n                        f\"cut_region requires a mesh-based field, \"\n                        f\"but {fd.name} is a particle field! Use \"\n                        f\"a particle filter instead. \"\n                    )\n                fields.append(fd.name)\n        return fields\n\n    def chunks(self, fields, chunking_style, **kwargs):\n        # We actually want to chunk the sub-chunk, not ourselves.  We have no\n        # chunks to speak of, as we do not data IO.\n        for chunk in self.index._chunk(self.base_object, chunking_style, **kwargs):\n            with self.base_object._chunked_read(chunk):\n                with self._chunked_read(chunk):\n                    self.get_data(fields)\n                    yield self\n\n    def get_data(self, fields=None):\n        fields = list(iter_fields(fields))\n        self.base_object.get_data(fields)\n        ind = self._cond_ind\n        for field in fields:\n            f = self.base_object[field]\n            if f.shape != ind.shape:\n                parent = getattr(self, \"parent\", self.base_object)\n                self.field_data[field] = parent[field][self._part_ind(field[0])]\n            else:\n                self.field_data[field] = self.base_object[field][ind]\n\n    @property\n    def blocks(self):\n        # We have to take a slightly different approach here.  Note that all\n        # that .blocks has to yield is a 3D array and a mask.\n        for obj, m in self.base_object.blocks:\n            m = m.copy()\n            with obj._field_parameter_state(self.field_parameters):\n                for cond in self.conditionals:\n                    if isinstance(cond, DerivedFieldCombination):\n                        ss = cond(None, obj)\n                    else:\n                        ss = eval(cond)\n                    m &= ss\n            if not np.any(m):\n                continue\n            yield obj, m\n\n    @property\n    def _cond_ind(self):\n        ind = None\n        obj = self.base_object\n        locals = self.locals.copy()\n        if \"obj\" in locals:\n            raise RuntimeError(\n                '\"obj\" has been defined in the \"locals\" ; '\n                \"this is not supported, please rename the variable.\"\n            )\n        locals[\"obj\"] = obj\n        with obj._field_parameter_state(self.field_parameters):\n            for cond in self.conditionals:\n                if isinstance(cond, DerivedFieldCombination):\n                    res = cond(None, obj)\n                else:\n                    res = eval(cond, locals)\n                if ind is None:\n                    ind = res\n                if ind.shape != res.shape:\n                    raise YTIllDefinedCutRegion(self.conditionals)\n                ind &= res\n        return ind\n\n    def _part_ind_KDTree(self, ptype):\n        \"\"\"Find the particles in cells using a KDTree approach.\"\"\"\n        parent = getattr(self, \"parent\", self.base_object)\n        units = \"code_length\"\n\n        pos = np.stack(\n            [\n                self[\"index\", \"x\"].to(units),\n                self[\"index\", \"y\"].to(units),\n                self[\"index\", \"z\"].to(units),\n            ],\n            axis=1,\n        ).value\n        dx = np.stack(\n            [\n                self[\"index\", \"dx\"].to(units),\n                self[\"index\", \"dy\"].to(units),\n                self[\"index\", \"dz\"].to(units),\n            ],\n            axis=1,\n        ).value\n        ppos = np.stack(\n            [\n                parent[ptype, \"particle_position_x\"],\n                parent[ptype, \"particle_position_y\"],\n                parent[ptype, \"particle_position_z\"],\n            ],\n            axis=1,\n        ).value\n\n        mask = np.zeros(ppos.shape[0], dtype=bool)\n        levels = self[\"index\", \"grid_level\"].astype(\"int32\").value\n        if levels.size == 0:\n            return mask\n\n        levelmin = levels.min()\n        levelmax = levels.max()\n\n        for lvl in range(levelmax, levelmin - 1, -1):\n            # Filter out cells not in the current level\n            lvl_mask = levels == lvl\n            dx_loc = dx[lvl_mask]\n            pos_loc = pos[lvl_mask]\n\n            grid_tree = _scipy.spatial.cKDTree(pos_loc, boxsize=1)\n\n            # Compute closest cell for all remaining particles\n            dist, icell = grid_tree.query(\n                ppos[~mask], distance_upper_bound=dx_loc.max(), p=np.inf\n            )\n            mask_loc = np.isfinite(dist[:])\n\n            # Check that particles within dx of a cell are in it\n            i = icell[mask_loc]\n            dist = np.abs(ppos[~mask][mask_loc, :] - pos_loc[i])\n            tmp_mask = np.all(dist <= (dx_loc[i] / 2), axis=1)\n\n            mask_loc[mask_loc] = tmp_mask\n\n            # Update the particle mask with particles found at this level\n            mask[~mask] |= mask_loc\n\n        return mask\n\n    def _part_ind_brute_force(self, ptype):\n        parent = getattr(self, \"parent\", self.base_object)\n        units = \"code_length\"\n        mask = points_in_cells(\n            self[\"index\", \"x\"].to(units),\n            self[\"index\", \"y\"].to(units),\n            self[\"index\", \"z\"].to(units),\n            self[\"index\", \"dx\"].to(units),\n            self[\"index\", \"dy\"].to(units),\n            self[\"index\", \"dz\"].to(units),\n            parent[ptype, \"particle_position_x\"].to(units),\n            parent[ptype, \"particle_position_y\"].to(units),\n            parent[ptype, \"particle_position_z\"].to(units),\n        )\n\n        return mask\n\n    def _part_ind(self, ptype):\n        # If scipy is installed, use the fast KD tree\n        # implementation. Else, fall back onto the direct\n        # brute-force algorithm.\n        try:\n            _scipy.spatial.KDTree\n            return self._part_ind_KDTree(ptype)\n        except ImportError:\n            return self._part_ind_brute_force(ptype)\n\n    @property\n    def icoords(self):\n        return self.base_object.icoords[self._cond_ind, :]\n\n    @property\n    def fcoords(self):\n        return self.base_object.fcoords[self._cond_ind, :]\n\n    @property\n    def ires(self):\n        return self.base_object.ires[self._cond_ind]\n\n    @property\n    def fwidth(self):\n        return self.base_object.fwidth[self._cond_ind, :]\n\n    def _get_bbox(self):\n        \"\"\"\n        Get the bounding box for the cut region. Here we just use\n        the bounding box for the source region.\n        \"\"\"\n        return self.base_object._get_bbox()\n"
  },
  {
    "path": "yt/data_objects/selection_objects/data_selection_objects.py",
    "content": "import abc\nimport itertools\nimport sys\nimport uuid\nfrom collections import defaultdict\nfrom contextlib import contextmanager\nfrom typing import Literal\n\nimport numpy as np\nfrom more_itertools import always_iterable\nfrom unyt import unyt_array\nfrom unyt.exceptions import UnitConversionError, UnitParseError\n\nimport yt.geometry\nfrom yt.data_objects.data_containers import YTDataContainer\nfrom yt.data_objects.derived_quantities import DerivedQuantityCollection\nfrom yt.data_objects.field_data import YTFieldData\nfrom yt.fields.field_exceptions import NeedsGridType\nfrom yt.funcs import fix_axis, is_sequence, iter_fields, validate_width_tuple\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.selection_routines import SelectorObject, compose_selector\nfrom yt.units import YTArray\nfrom yt.utilities.exceptions import (\n    GenerationInProgress,\n    YTBooleanObjectError,\n    YTBooleanObjectsWrongDataset,\n    YTDataSelectorNotImplemented,\n    YTDimensionalityError,\n    YTFieldUnitError,\n    YTFieldUnitParseError,\n)\nfrom yt.utilities.lib.marching_cubes import march_cubes_grid, march_cubes_grid_flux\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    ParallelAnalysisInterface,\n)\n\nif sys.version_info >= (3, 11):\n    from typing import assert_never\nelse:\n    from typing_extensions import assert_never\n\n\nclass YTSelectionContainer(YTDataContainer, ParallelAnalysisInterface, abc.ABC):\n    _locked: bool = False\n    _selector: SelectorObject | None = None\n    _current_chunk: YTDataContainer | None = None\n    _data_source: YTDataContainer | None = None\n    _dimensionality: int\n    _max_level: int | None = None\n    _min_level: int | None = None\n    _derived_quantity_chunking: Literal[\"io\", \"all\"] = \"io\"\n\n    def __init__(self, ds, field_parameters, data_source=None):\n        ParallelAnalysisInterface.__init__(self)\n        super().__init__(ds, field_parameters)\n        self._data_source = data_source\n        if data_source is not None:\n            if data_source.ds != self.ds:\n                raise RuntimeError(\n                    \"Attempted to construct a DataContainer with a data_source \"\n                    \"from a different Dataset\",\n                    ds,\n                    data_source.ds,\n                )\n            if data_source._dimensionality < self._dimensionality:\n                raise RuntimeError(\n                    \"Attempted to construct a DataContainer with a data_source \"\n                    \"of lower dimensionality \"\n                    f\"({data_source._dimensionality} vs {self._dimensionality})\"\n                )\n            self.field_parameters.update(data_source.field_parameters)\n        self.quantities = DerivedQuantityCollection(self)\n\n    @property\n    def selector(self):\n        if self._selector is not None:\n            return self._selector\n        s_module = getattr(self, \"_selector_module\", yt.geometry.selection_routines)\n        sclass = getattr(s_module, f\"{self._type_name}_selector\", None)\n        if sclass is None:\n            raise YTDataSelectorNotImplemented(self._type_name)\n\n        if self._data_source is not None:\n            self._selector = compose_selector(\n                self, self._data_source.selector, sclass(self)\n            )\n        else:\n            self._selector = sclass(self)\n        return self._selector\n\n    def chunks(self, fields, chunking_style, **kwargs):\n        # This is an iterator that will yield the necessary chunks.\n        self.get_data()  # Ensure we have built ourselves\n        if fields is None:\n            fields = []\n        # chunk_ind can be supplied in the keyword arguments.  If it's a\n        # scalar, that'll be the only chunk that gets returned; if it's a list,\n        # those are the ones that will be.\n        chunk_ind = kwargs.pop(\"chunk_ind\", None)\n        if chunk_ind is not None:\n            chunk_ind = list(always_iterable(chunk_ind))\n        for ci, chunk in enumerate(self.index._chunk(self, chunking_style, **kwargs)):\n            if chunk_ind is not None and ci not in chunk_ind:\n                continue\n            with self._chunked_read(chunk):\n                self.get_data(fields)\n                # NOTE: we yield before releasing the context\n                yield self\n\n    def _identify_dependencies(self, fields_to_get, spatial=False):\n        inspected = 0\n        fields_to_get = fields_to_get[:]\n        for field in itertools.cycle(fields_to_get):\n            if inspected >= len(fields_to_get):\n                break\n            inspected += 1\n            fi = self.ds._get_field_info(field)\n            fd = self.ds.field_dependencies.get(\n                field, None\n            ) or self.ds.field_dependencies.get(field[1], None)\n            # This is long overdue.  Any time we *can't* find a field\n            # dependency -- for instance, if the derived field has been added\n            # after dataset instantiation -- let's just try to\n            # recalculate it.\n            if fd is None:\n                try:\n                    fd = fi.get_dependencies(ds=self.ds)\n                    self.ds.field_dependencies[field] = fd\n                except Exception:\n                    continue\n            requested = self._determine_fields(list(set(fd.requested)))\n            deps = [d for d in requested if d not in fields_to_get]\n            fields_to_get += deps\n        return sorted(fields_to_get)\n\n    def get_data(self, fields=None):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        if fields is None:\n            return\n        nfields = []\n        apply_fields = defaultdict(list)\n        for field in self._determine_fields(fields):\n            # We need to create the field on the raw particle types\n            # for particles types (when the field is not directly\n            # defined for the derived particle type only)\n            finfo = self.ds.field_info[field]\n\n            if (\n                field[0] in self.ds.filtered_particle_types\n                and finfo._inherited_particle_filter\n            ):\n                f = self.ds.known_filters[field[0]]\n                apply_fields[field[0]].append((f.filtered_type, field[1]))\n            else:\n                nfields.append(field)\n        for filter_type in apply_fields:\n            f = self.ds.known_filters[filter_type]\n            with f.apply(self):\n                self.get_data(apply_fields[filter_type])\n        fields = nfields\n        if len(fields) == 0:\n            return\n        # Now we collect all our fields\n        # Here is where we need to perform a validation step, so that if we\n        # have a field requested that we actually *can't* yet get, we put it\n        # off until the end.  This prevents double-reading fields that will\n        # need to be used in spatial fields later on.\n        fields_to_get = []\n        # This will be pre-populated with spatial fields\n        fields_to_generate = []\n        for field in self._determine_fields(fields):\n            if field in self.field_data:\n                continue\n            finfo = self.ds._get_field_info(field)\n            try:\n                finfo.check_available(self)\n            except NeedsGridType:\n                fields_to_generate.append(field)\n                continue\n            fields_to_get.append(field)\n        if len(fields_to_get) == 0 and len(fields_to_generate) == 0:\n            return\n        elif self._locked:\n            raise GenerationInProgress(fields)\n        # Track which ones we want in the end\n        ofields = set(list(self.field_data.keys()) + fields_to_get + fields_to_generate)\n        # At this point, we want to figure out *all* our dependencies.\n        fields_to_get = self._identify_dependencies(fields_to_get, self._spatial)\n        # We now split up into readers for the types of fields\n        fluids, particles = [], []\n        finfos = {}\n        for field_key in fields_to_get:\n            finfo = self.ds._get_field_info(field_key)\n            finfos[field_key] = finfo\n            if finfo.sampling_type == \"particle\":\n                particles.append(field_key)\n            elif field_key not in fluids:\n                fluids.append(field_key)\n        # The _read method will figure out which fields it needs to get from\n        # disk, and return a dict of those fields along with the fields that\n        # need to be generated.\n        read_fluids, gen_fluids = self.index._read_fluid_fields(\n            fluids, self, self._current_chunk\n        )\n        for f, v in read_fluids.items():\n            self.field_data[f] = self.ds.arr(v, units=finfos[f].units)\n            self.field_data[f].convert_to_units(finfos[f].output_units)\n\n        read_particles, gen_particles = self.index._read_particle_fields(\n            particles, self, self._current_chunk\n        )\n\n        for f, v in read_particles.items():\n            self.field_data[f] = self.ds.arr(v, units=finfos[f].units)\n            self.field_data[f].convert_to_units(finfos[f].output_units)\n\n        fields_to_generate += gen_fluids + gen_particles\n        self._generate_fields(fields_to_generate)\n        for field in list(self.field_data.keys()):\n            if field not in ofields:\n                self.field_data.pop(field)\n\n    def _get_bbox(self):\n        \"\"\"\n        Return the bounding box for this data container.\n        This generic version will return the bounds of the entire domain.\n        \"\"\"\n        return self.ds.domain_left_edge, self.ds.domain_right_edge\n\n    def get_bbox(self) -> tuple[unyt_array, unyt_array]:\n        \"\"\"\n        Return the bounding box for this data container.\n        \"\"\"\n        match self.ds.geometry:\n            case Geometry.CARTESIAN if self._data_source is None:\n                le, re = self._get_bbox()\n                return (le.to(\"code_length\"), re.to(\"code_length\"))\n            case Geometry.CARTESIAN:\n                # mypy does not understand that here, _data_source cannot be None\n                return self._data_source.get_bbox()  # type: ignore [union-attr]\n            case (\n                Geometry.CYLINDRICAL\n                | Geometry.POLAR\n                | Geometry.SPHERICAL\n                | Geometry.GEOGRAPHIC\n                | Geometry.INTERNAL_GEOGRAPHIC\n                | Geometry.SPECTRAL_CUBE\n            ):\n                geometry = self.ds.geometry\n                raise NotImplementedError(\n                    f\"get_bbox is currently not implemented for {geometry=}!\"\n                )\n            case _:\n                assert_never(self.ds.geometry)\n\n    def _generate_fields(self, fields_to_generate):\n        index = 0\n\n        def dimensions_compare_equal(a, b, /) -> bool:\n            if a == b:\n                return True\n            try:\n                if (a == 1 and b.is_dimensionless) or (a.is_dimensionless and b == 1):\n                    return True\n            except AttributeError:\n                return False\n            return False\n\n        with self._field_lock():\n            # At this point, we assume that any fields that are necessary to\n            # *generate* a field are in fact already available to us.  Note\n            # that we do not make any assumption about whether or not the\n            # fields have a spatial requirement.  This will be checked inside\n            # _generate_field, at which point additional dependencies may\n            # actually be noted.\n            while any(f not in self.field_data for f in fields_to_generate):\n                field = fields_to_generate[index % len(fields_to_generate)]\n                index += 1\n                if field in self.field_data:\n                    continue\n                fi = self.ds._get_field_info(field)\n                try:\n                    fd = self._generate_field(field)\n                    if hasattr(fd, \"units\"):\n                        fd.units.registry = self.ds.unit_registry\n                    if fd is None:\n                        raise RuntimeError\n                    if fi.units is None:\n                        # first time calling a field with units='auto', so we\n                        # infer the units from the units of the data we get back\n                        # from the field function and use these units for future\n                        # field accesses\n                        units = getattr(fd, \"units\", \"\")\n                        if units == \"\":\n                            sunits = \"\"\n                            dimensions = 1\n                        else:\n                            sunits = str(\n                                units.get_base_equivalent(self.ds.unit_system.name)\n                            )\n                            dimensions = units.dimensions\n\n                        if fi.dimensions is None:\n                            mylog.warning(\n                                \"Field %s was added without specifying units or dimensions, \"\n                                \"auto setting units to %r\",\n                                fi.name,\n                                sunits or \"dimensionless\",\n                            )\n                        elif not dimensions_compare_equal(fi.dimensions, dimensions):\n                            raise YTDimensionalityError(fi.dimensions, dimensions)\n                        fi.units = sunits\n                        fi.dimensions = dimensions\n                        self.field_data[field] = self.ds.arr(fd, units)\n                    if fi.output_units is None:\n                        fi.output_units = fi.units\n\n                    try:\n                        fd.convert_to_units(fi.units)\n                    except AttributeError:\n                        # If the field returns an ndarray, coerce to a\n                        # dimensionless YTArray and verify that field is\n                        # supposed to be unitless\n                        fd = self.ds.arr(fd, \"\")\n                        if fi.units != \"\":\n                            raise YTFieldUnitError(fi, fd.units) from None\n                    except UnitConversionError as e:\n                        raise YTFieldUnitError(fi, fd.units) from e\n                    except UnitParseError as e:\n                        raise YTFieldUnitParseError(fi) from e\n                    self.field_data[field] = fd\n                except GenerationInProgress as gip:\n                    for f in gip.fields:\n                        if f not in fields_to_generate:\n                            fields_to_generate.append(f)\n\n    def __or__(self, other):\n        if not isinstance(other, YTSelectionContainer):\n            raise YTBooleanObjectError(other)\n        if self.ds is not other.ds:\n            raise YTBooleanObjectsWrongDataset()\n        # Should maybe do something with field parameters here\n        from yt.data_objects.selection_objects.boolean_operations import (\n            YTBooleanContainer,\n        )\n\n        return YTBooleanContainer(\"OR\", self, other, ds=self.ds)\n\n    def __invert__(self):\n        # ~obj\n        asel = yt.geometry.selection_routines.AlwaysSelector(self.ds)\n        from yt.data_objects.selection_objects.boolean_operations import (\n            YTBooleanContainer,\n        )\n\n        return YTBooleanContainer(\"NOT\", self, asel, ds=self.ds)\n\n    def __xor__(self, other):\n        if not isinstance(other, YTSelectionContainer):\n            raise YTBooleanObjectError(other)\n        if self.ds is not other.ds:\n            raise YTBooleanObjectsWrongDataset()\n        from yt.data_objects.selection_objects.boolean_operations import (\n            YTBooleanContainer,\n        )\n\n        return YTBooleanContainer(\"XOR\", self, other, ds=self.ds)\n\n    def __and__(self, other):\n        if not isinstance(other, YTSelectionContainer):\n            raise YTBooleanObjectError(other)\n        if self.ds is not other.ds:\n            raise YTBooleanObjectsWrongDataset()\n        from yt.data_objects.selection_objects.boolean_operations import (\n            YTBooleanContainer,\n        )\n\n        return YTBooleanContainer(\"AND\", self, other, ds=self.ds)\n\n    def __add__(self, other):\n        return self.__or__(other)\n\n    def __sub__(self, other):\n        if not isinstance(other, YTSelectionContainer):\n            raise YTBooleanObjectError(other)\n        if self.ds is not other.ds:\n            raise YTBooleanObjectsWrongDataset()\n        from yt.data_objects.selection_objects.boolean_operations import (\n            YTBooleanContainer,\n        )\n\n        return YTBooleanContainer(\"NEG\", self, other, ds=self.ds)\n\n    @contextmanager\n    def _field_lock(self):\n        self._locked = True\n        yield\n        self._locked = False\n\n    @contextmanager\n    def _ds_hold(self, new_ds):\n        \"\"\"\n        This contextmanager is used to take a data object and preserve its\n        attributes but allow the dataset that underlies it to be swapped out.\n        This is typically only used internally, and differences in unit systems\n        may present interesting possibilities.\n        \"\"\"\n        old_ds = self.ds\n        old_index = self._index\n        self.ds = new_ds\n        self._index = new_ds.index\n        old_chunk_info = self._chunk_info\n        old_chunk = self._current_chunk\n        old_size = self.size\n        self._chunk_info = None\n        self._current_chunk = None\n        self.size = None\n        self._index._identify_base_chunk(self)\n        with self._chunked_read(None):\n            yield\n        self._index = old_index\n        self.ds = old_ds\n        self._chunk_info = old_chunk_info\n        self._current_chunk = old_chunk\n        self.size = old_size\n\n    @contextmanager\n    def _chunked_read(self, chunk):\n        # There are several items that need to be swapped out\n        # field_data, size, shape\n        obj_field_data = []\n        if hasattr(chunk, \"objs\"):\n            for obj in chunk.objs:\n                obj_field_data.append(obj.field_data)\n                obj.field_data = YTFieldData()\n        old_field_data, self.field_data = self.field_data, YTFieldData()\n        old_chunk, self._current_chunk = self._current_chunk, chunk\n        old_locked, self._locked = self._locked, False\n        yield\n        self.field_data = old_field_data\n        self._current_chunk = old_chunk\n        self._locked = old_locked\n        if hasattr(chunk, \"objs\"):\n            for obj in chunk.objs:\n                obj.field_data = obj_field_data.pop(0)\n\n    @contextmanager\n    def _activate_cache(self):\n        cache = self._field_cache or {}\n        old_fields = {}\n        for field in (f for f in cache if f in self.field_data):\n            old_fields[field] = self.field_data[field]\n        self.field_data.update(cache)\n        yield\n        for field in cache:\n            self.field_data.pop(field)\n            if field in old_fields:\n                self.field_data[field] = old_fields.pop(field)\n        self._field_cache = None\n\n    def _initialize_cache(self, cache):\n        # Wipe out what came before\n        self._field_cache = {}\n        self._field_cache.update(cache)\n\n    @property\n    def icoords(self):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        return self._current_chunk.icoords\n\n    @property\n    def fcoords(self):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        return self._current_chunk.fcoords\n\n    @property\n    def ires(self):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        return self._current_chunk.ires\n\n    @property\n    def fwidth(self):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        return self._current_chunk.fwidth\n\n    @property\n    def fcoords_vertex(self):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        return self._current_chunk.fcoords_vertex\n\n    @property\n    def max_level(self):\n        if self._max_level is None:\n            try:\n                return self.ds.max_level\n            except AttributeError:\n                return None\n        return self._max_level\n\n    @max_level.setter\n    def max_level(self, value):\n        if self._selector is not None:\n            del self._selector\n            self._selector = None\n        self._current_chunk = None\n        self.size = None\n        self.shape = None\n        self.field_data.clear()\n        self._max_level = value\n\n    @property\n    def min_level(self):\n        if self._min_level is None:\n            try:\n                return 0\n            except AttributeError:\n                return None\n        return self._min_level\n\n    @min_level.setter\n    def min_level(self, value):\n        if self._selector is not None:\n            del self._selector\n            self._selector = None\n        self.field_data.clear()\n        self.size = None\n        self.shape = None\n        self._current_chunk = None\n        self._min_level = value\n\n\nclass YTSelectionContainer0D(YTSelectionContainer):\n    _spatial = False\n    _dimensionality = 0\n\n    def __init__(self, ds, field_parameters=None, data_source=None):\n        super().__init__(ds, field_parameters, data_source)\n\n\nclass YTSelectionContainer1D(YTSelectionContainer):\n    _spatial = False\n    _dimensionality = 1\n\n    def __init__(self, ds, field_parameters=None, data_source=None):\n        super().__init__(ds, field_parameters, data_source)\n        self._grids = None\n        self._sortkey = None\n        self._sorted = {}\n\n\nclass YTSelectionContainer2D(YTSelectionContainer):\n    _key_fields = [\"px\", \"py\", \"pdx\", \"pdy\"]\n    _dimensionality = 2\n    \"\"\"\n    Prepares the YTSelectionContainer2D, normal to *axis*.  If *axis* is 4, we are not\n    aligned with any axis.\n    \"\"\"\n    _spatial = False\n\n    def __init__(self, axis, ds, field_parameters=None, data_source=None):\n        super().__init__(ds, field_parameters, data_source)\n        # We need the ds, which will exist by now, for fix_axis.\n        self.axis = fix_axis(axis, self.ds)\n        self.set_field_parameter(\"axis\", axis)\n\n    def _convert_field_name(self, field):\n        return field\n\n    def _get_pw(self, fields, center, width, origin, plot_type):\n        from yt.visualization.fixed_resolution import FixedResolutionBuffer as frb\n        from yt.visualization.plot_window import PWViewerMPL, get_window_parameters\n\n        axis = self.axis\n        skip = self._key_fields\n        skip += list(set(frb._exclude_fields).difference(set(self._key_fields)))\n        self.fields = [k for k in self.field_data if k not in skip]\n        if fields is not None:\n            self.fields = list(iter_fields(fields)) + self.fields\n        if len(self.fields) == 0:\n            raise ValueError(\"No fields found to plot in get_pw\")\n        (bounds, center, display_center) = get_window_parameters(\n            axis, center, width, self.ds\n        )\n        pw = PWViewerMPL(\n            self,\n            bounds,\n            fields=self.fields,\n            origin=origin,\n            frb_generator=frb,\n            plot_type=plot_type,\n            geometry=self.ds.geometry,\n        )\n        pw._setup_plots()\n        return pw\n\n    def to_frb(self, width, resolution, center=None, height=None, periodic=False):\n        r\"\"\"This function returns a FixedResolutionBuffer generated from this\n        object.\n\n        A FixedResolutionBuffer is an object that accepts a variable-resolution\n        2D object and transforms it into an NxM bitmap that can be plotted,\n        examined or processed.  This is a convenience function to return an FRB\n        directly from an existing 2D data object.\n\n        Parameters\n        ----------\n        width : width specifier\n            This can either be a floating point value, in the native domain\n            units of the simulation, or a tuple of the (value, unit) style.\n            This will be the width of the FRB.\n        height : height specifier\n            This will be the physical height of the FRB, by default it is equal\n            to width.  Note that this will not make any corrections to\n            resolution for the aspect ratio.\n        resolution : int or tuple of ints\n            The number of pixels on a side of the final FRB.  If iterable, this\n            will be the width then the height.\n        center : array-like of floats, optional\n            The center of the FRB.  If not specified, defaults to the center of\n            the current object.\n        periodic : bool\n            Should the returned Fixed Resolution Buffer be periodic?  (default:\n            False).\n\n        Returns\n        -------\n        frb : :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer`\n            A fixed resolution buffer, which can be queried for fields.\n\n        Examples\n        --------\n\n        >>> proj = ds.proj((\"gas\", \"density\"), 0)\n        >>> frb = proj.to_frb((100.0, \"kpc\"), 1024)\n        >>> write_image(np.log10(frb[\"gas\", \"density\"]), \"density_100kpc.png\")\n        \"\"\"\n\n        match (self.ds.geometry, self.axis):\n            case (Geometry.CYLINDRICAL, 1) | (Geometry.POLAR, 2):\n                if center is not None and center != (0.0, 0.0):\n                    raise NotImplementedError(\n                        \"Currently we only support images centered at R=0. \"\n                        + \"We plan to generalize this in the near future\"\n                    )\n                from yt.visualization.fixed_resolution import (\n                    CylindricalFixedResolutionBuffer,\n                )\n\n                validate_width_tuple(width)\n                if is_sequence(resolution):\n                    resolution = max(resolution)\n                frb = CylindricalFixedResolutionBuffer(self, width, resolution)\n                return frb\n\n        if center is None:\n            center = self.center\n            if center is None:\n                center = (self.ds.domain_right_edge + self.ds.domain_left_edge) / 2.0\n        elif is_sequence(center) and not isinstance(center, YTArray):\n            center = self.ds.arr(center, \"code_length\")\n        if is_sequence(width):\n            w, u = width\n            if isinstance(w, tuple) and isinstance(u, tuple):\n                height = u\n                w, u = w\n            width = self.ds.quan(w, units=u)\n        elif not isinstance(width, YTArray):\n            width = self.ds.quan(width, \"code_length\")\n        if height is None:\n            height = width\n        elif is_sequence(height):\n            h, u = height\n            height = self.ds.quan(h, units=u)\n        elif not isinstance(height, YTArray):\n            height = self.ds.quan(height, \"code_length\")\n        if not is_sequence(resolution):\n            resolution = (resolution, resolution)\n        from yt.visualization.fixed_resolution import FixedResolutionBuffer\n\n        xax = self.ds.coordinates.x_axis[self.axis]\n        yax = self.ds.coordinates.y_axis[self.axis]\n        bounds = (\n            center[xax] - width * 0.5,\n            center[xax] + width * 0.5,\n            center[yax] - height * 0.5,\n            center[yax] + height * 0.5,\n        )\n        frb = FixedResolutionBuffer(self, bounds, resolution, periodic=periodic)\n        return frb\n\n\nclass YTSelectionContainer3D(YTSelectionContainer):\n    \"\"\"\n    Returns an instance of YTSelectionContainer3D, or prepares one.  Usually only\n    used as a base class.  Note that *center* is supplied, but only used\n    for fields and quantities that require it.\n    \"\"\"\n\n    _key_fields = [\"x\", \"y\", \"z\", \"dx\", \"dy\", \"dz\"]\n    _spatial = False\n    _num_ghost_zones = 0\n    _dimensionality = 3\n\n    def __init__(self, center, ds, field_parameters=None, data_source=None):\n        super().__init__(ds, field_parameters, data_source)\n        self._set_center(center)\n        self.coords = None\n        self._grids = None\n\n    def cut_region(self, field_cuts, field_parameters=None, locals=None):\n        \"\"\"\n        Return a YTCutRegion, where the a cell is identified as being inside\n        the cut region based on the value of one or more fields.  Note that in\n        previous versions of yt the name 'grid' was used to represent the data\n        object used to construct the field cut, as of yt 3.0, this has been\n        changed to 'obj'.\n\n        Parameters\n        ----------\n        field_cuts : list of strings\n           A list of conditionals that will be evaluated. In the namespace\n           available, these conditionals will have access to 'obj' which is a\n           data object of unknown shape, and they must generate a boolean array.\n           For instance, conditionals = [\"obj['gas', 'temperature'] < 1e3\"]\n        field_parameters : dictionary\n           A dictionary of field parameters to be used when applying the field\n           cuts.\n        locals : dictionary\n            A dictionary of local variables to use when defining the cut region.\n\n        Examples\n        --------\n        To find the total mass of hot gas with temperature greater than 10^6 K\n        in your volume:\n\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.cut_region([\"obj['gas', 'temperature'] > 1e6\"])\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        if locals is None:\n            locals = {}\n        cr = self.ds.cut_region(\n            self, field_cuts, field_parameters=field_parameters, locals=locals\n        )\n        return cr\n\n    def _build_operator_cut(self, operation, field, value, units=None):\n        \"\"\"\n        Given an operation (>, >=, etc.), a field and a value,\n        return the cut_region implementing it.\n\n        This is only meant to be used internally.\n\n        Examples\n        --------\n        >>> ds._build_operator_cut(\">\", (\"gas\", \"density\"), 1e-24)\n        ... # is equivalent to\n        ... ds.cut_region(['obj[\"gas\", \"density\"] > 1e-24'])\n        \"\"\"\n        ftype, fname = self._determine_fields(field)[0]\n        if units is None:\n            field_cuts = f'obj[\"{ftype}\", \"{fname}\"] {operation} {value}'\n        else:\n            field_cuts = (\n                f'obj[\"{ftype}\", \"{fname}\"].in_units(\"{units}\") {operation} {value}'\n            )\n        return self.cut_region(field_cuts)\n\n    def _build_function_cut(self, function, field, units=None, **kwargs):\n        \"\"\"\n        Given a function (np.abs, np.all) and a field,\n        return the cut_region implementing it.\n\n        This is only meant to be used internally.\n\n        Examples\n        --------\n        >>> ds._build_function_cut(\"np.isnan\", (\"gas\", \"density\"), locals={\"np\": np})\n        ... # is equivalent to\n        ... ds.cut_region(['np.isnan(obj[\"gas\", \"density\"])'], locals={\"np\": np})\n        \"\"\"\n        ftype, fname = self._determine_fields(field)[0]\n        if units is None:\n            field_cuts = f'{function}(obj[\"{ftype}\", \"{fname}\"])'\n        else:\n            field_cuts = f'{function}(obj[\"{ftype}\", \"{fname}\"].in_units(\"{units}\"))'\n        return self.cut_region(field_cuts, **kwargs)\n\n    def exclude_above(self, field, value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where all of the regions\n        whose field is above a given value are masked.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        value : float\n            The minimum value that will not be masked in the output\n            YTCutRegion.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the field above the given value masked.\n\n        Examples\n        --------\n        To find the total mass of hot gas with temperature colder than 10^6 K\n        in your volume:\n\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.exclude_above((\"gas\", \"temperature\"), 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n\n        \"\"\"\n        return self._build_operator_cut(\"<=\", field, value, units)\n\n    def include_above(self, field, value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where only the regions\n        whose field is above a given value are included.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        value : float\n            The minimum value that will not be masked in the output\n            YTCutRegion.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the field above the given value masked.\n\n        Examples\n        --------\n        To find the total mass of hot gas with temperature warmer than 10^6 K\n        in your volume:\n\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.include_above((\"gas\", \"temperature\"), 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n\n        return self._build_operator_cut(\">\", field, value, units)\n\n    def exclude_equal(self, field, value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where all of the regions\n        whose field are equal to given value are masked.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        value : float\n            The minimum value that will not be masked in the output\n            YTCutRegion.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the field equal to the given value masked.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.exclude_equal((\"gas\", \"temperature\"), 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        return self._build_operator_cut(\"!=\", field, value, units)\n\n    def include_equal(self, field, value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where only the regions\n        whose field are equal to given value are included.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        value : float\n            The minimum value that will not be masked in the output\n            YTCutRegion.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the field equal to the given value included.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.include_equal((\"gas\", \"temperature\"), 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        return self._build_operator_cut(\"==\", field, value, units)\n\n    def exclude_inside(self, field, min_value, max_value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where all of the regions\n        whose field are inside the interval from min_value to max_value.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        min_value : float\n            The minimum value inside the interval to be excluded.\n        max_value : float\n            The maximum value inside the interval to be excluded.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the field inside the given interval excluded.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.exclude_inside((\"gas\", \"temperature\"), 1e5, 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        ftype, fname = self._determine_fields(field)[0]\n        if units is None:\n            field_cuts = (\n                f'(obj[\"{ftype}\", \"{fname}\"] <= {min_value}) | '\n                f'(obj[\"{ftype}\", \"{fname}\"] >= {max_value})'\n            )\n        else:\n            field_cuts = (\n                f'(obj[\"{ftype}\", \"{fname}\"].in_units(\"{units}\") <= {min_value}) | '\n                f'(obj[\"{ftype}\", \"{fname}\"].in_units(\"{units}\") >= {max_value})'\n            )\n        cr = self.cut_region(field_cuts)\n        return cr\n\n    def include_inside(self, field, min_value, max_value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where only the regions\n        whose field are inside the interval from min_value to max_value are\n        included.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        min_value : float\n            The minimum value inside the interval to be excluded.\n        max_value : float\n            The maximum value inside the interval to be excluded.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the field inside the given interval excluded.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.include_inside((\"gas\", \"temperature\"), 1e5, 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        ftype, fname = self._determine_fields(field)[0]\n        if units is None:\n            field_cuts = (\n                f'(obj[\"{ftype}\", \"{fname}\"] > {min_value}) & '\n                f'(obj[\"{ftype}\", \"{fname}\"] < {max_value})'\n            )\n        else:\n            field_cuts = (\n                f'(obj[\"{ftype}\", \"{fname}\"].in_units(\"{units}\") > {min_value}) & '\n                f'(obj[\"{ftype}\", \"{fname}\"].in_units(\"{units}\") < {max_value})'\n            )\n        cr = self.cut_region(field_cuts)\n        return cr\n\n    def exclude_outside(self, field, min_value, max_value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where all of the regions\n        whose field are outside the interval from min_value to max_value.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        min_value : float\n            The minimum value inside the interval to be excluded.\n        max_value : float\n            The maximum value inside the interval to be excluded.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the field outside the given interval excluded.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.exclude_outside((\"gas\", \"temperature\"), 1e5, 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        cr = self.exclude_below(field, min_value, units)\n        cr = cr.exclude_above(field, max_value, units)\n        return cr\n\n    def include_outside(self, field, min_value, max_value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where only the regions\n        whose field are outside the interval from min_value to max_value are\n        included.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        min_value : float\n            The minimum value inside the interval to be excluded.\n        max_value : float\n            The maximum value inside the interval to be excluded.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the field outside the given interval excluded.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.exclude_outside((\"gas\", \"temperature\"), 1e5, 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        cr = self.exclude_inside(field, min_value, max_value, units)\n        return cr\n\n    def exclude_below(self, field, value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where all of the regions\n        whose field is below a given value are masked.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        value : float\n            The minimum value that will not be masked in the output\n            YTCutRegion.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the field below the given value masked.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.exclude_below((\"gas\", \"temperature\"), 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        return self._build_operator_cut(\">=\", field, value, units)\n\n    def exclude_nan(self, field, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where all of the regions\n        whose field is NaN are masked.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with the NaN entries of the field masked.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.exclude_nan((\"gas\", \"temperature\"))\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        return self._build_function_cut(\"~np.isnan\", field, units, locals={\"np\": np})\n\n    def include_below(self, field, value, units=None):\n        \"\"\"\n        This function will return a YTCutRegion where only the regions\n        whose field is below a given value are included.\n\n        Parameters\n        ----------\n        field : string\n            The field in which the conditional will be applied.\n        value : float\n            The minimum value that will not be masked in the output\n            YTCutRegion.\n        units : string or None\n            The units of the value threshold. None will use the default units\n            given in the field.\n\n        Returns\n        -------\n        cut_region : YTCutRegion\n            The YTCutRegion with only regions with the field below the given\n            value included.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"RedshiftOutput0005\")\n        >>> ad = ds.all_data()\n        >>> cr = ad.include_below((\"gas\", \"temperature\"), 1e5, 1e6)\n        >>> print(cr.quantities.total_quantity((\"gas\", \"cell_mass\")).in_units(\"Msun\"))\n        \"\"\"\n        return self._build_operator_cut(\"<\", field, value, units)\n\n    def extract_isocontours(\n        self, field, value, filename=None, rescale=False, sample_values=None\n    ):\n        r\"\"\"This identifies isocontours on a cell-by-cell basis, with no\n        consideration of global connectedness, and returns the vertices of the\n        Triangles in that isocontour.\n\n        This function simply returns the vertices of all the triangles\n        calculated by the `marching cubes\n        <https://en.wikipedia.org/wiki/Marching_cubes>`_ algorithm; for more\n        complex operations, such as identifying connected sets of cells above a\n        given threshold, see the extract_connected_sets function.  This is more\n        useful for calculating, for instance, total isocontour area, or\n        visualizing in an external program (such as `MeshLab\n        <http://www.meshlab.net>`_.)\n\n        Parameters\n        ----------\n        field : string\n            Any field that can be obtained in a data object.  This is the field\n            which will be isocontoured.\n        value : float\n            The value at which the isocontour should be calculated.\n        filename : string, optional\n            If supplied, this file will be filled with the vertices in .obj\n            format.  Suitable for loading into meshlab.\n        rescale : bool, optional\n            If true, the vertices will be rescaled within their min/max.\n        sample_values : string, optional\n            Any field whose value should be extracted at the center of each\n            triangle.\n\n        Returns\n        -------\n        verts : array of floats\n            The array of vertices, x,y,z.  Taken in threes, these are the\n            triangle vertices.\n        samples : array of floats\n            If `sample_values` is specified, this will be returned and will\n            contain the values of the field specified at the center of each\n            triangle.\n\n        Examples\n        --------\n        This will create a data object, find a nice value in the center, and\n        output the vertices to \"triangles.obj\" after rescaling them.\n\n        >>> dd = ds.all_data()\n        >>> rho = dd.quantities[\"WeightedAverageQuantity\"](\n        ...     (\"gas\", \"density\"), weight=(\"gas\", \"cell_mass\")\n        ... )\n        >>> verts = dd.extract_isocontours(\n        ...     (\"gas\", \"density\"), rho, \"triangles.obj\", True\n        ... )\n        \"\"\"\n        from yt.data_objects.static_output import ParticleDataset\n        from yt.frontends.stream.data_structures import StreamParticlesDataset\n\n        verts = []\n        samples = []\n        if isinstance(self.ds, (ParticleDataset, StreamParticlesDataset)):\n            raise NotImplementedError\n        for block, mask in self.blocks:\n            my_verts = self._extract_isocontours_from_grid(\n                block, mask, field, value, sample_values\n            )\n            if sample_values is not None:\n                my_verts, svals = my_verts\n                samples.append(svals)\n            verts.append(my_verts)\n        verts = np.concatenate(verts).transpose()\n        verts = self.comm.par_combine_object(verts, op=\"cat\", datatype=\"array\")\n        verts = verts.transpose()\n        if sample_values is not None:\n            samples = np.concatenate(samples)\n            samples = self.comm.par_combine_object(samples, op=\"cat\", datatype=\"array\")\n        if rescale:\n            mi = np.min(verts, axis=0)\n            ma = np.max(verts, axis=0)\n            verts = (verts - mi) / (ma - mi).max()\n        if filename is not None and self.comm.rank == 0:\n            if hasattr(filename, \"write\"):\n                f = filename\n            else:\n                f = open(filename, \"w\")\n            for v1 in verts:\n                f.write(f\"v {v1[0]:0.16e} {v1[1]:0.16e} {v1[2]:0.16e}\\n\")\n            for i in range(len(verts) // 3):\n                f.write(f\"f {i * 3 + 1} {i * 3 + 2} {i * 3 + 3}\\n\")\n            if not hasattr(filename, \"write\"):\n                f.close()\n        if sample_values is not None:\n            return verts, samples\n        return verts\n\n    def _extract_isocontours_from_grid(\n        self, grid, mask, field, value, sample_values=None\n    ):\n        vc_fields = [field]\n        if sample_values is not None:\n            vc_fields.append(sample_values)\n\n        vc_data = grid.get_vertex_centered_data(vc_fields, no_ghost=False)\n        try:\n            svals = vc_data[sample_values]\n        except KeyError:\n            svals = None\n\n        my_verts = march_cubes_grid(\n            value, vc_data[field], mask, grid.LeftEdge, grid.dds, svals\n        )\n        return my_verts\n\n    def calculate_isocontour_flux(\n        self, field, value, field_x, field_y, field_z, fluxing_field=None\n    ):\n        r\"\"\"This identifies isocontours on a cell-by-cell basis, with no\n        consideration of global connectedness, and calculates the flux over\n        those contours.\n\n        This function will conduct `marching cubes\n        <https://en.wikipedia.org/wiki/Marching_cubes>`_ on all the cells in a\n        given data container (grid-by-grid), and then for each identified\n        triangular segment of an isocontour in a given cell, calculate the\n        gradient (i.e., normal) in the isocontoured field, interpolate the local\n        value of the \"fluxing\" field, the area of the triangle, and then return:\n\n        area * local_flux_value * (n dot v)\n\n        Where area, local_value, and the vector v are interpolated at the barycenter\n        (weighted by the vertex values) of the triangle.  Note that this\n        specifically allows for the field fluxing across the surface to be\n        *different* from the field being contoured.  If the fluxing_field is\n        not specified, it is assumed to be 1.0 everywhere, and the raw flux\n        with no local-weighting is returned.\n\n        Additionally, the returned flux is defined as flux *into* the surface,\n        not flux *out of* the surface.\n\n        Parameters\n        ----------\n        field : string\n            Any field that can be obtained in a data object.  This is the field\n            which will be isocontoured and used as the \"local_value\" in the\n            flux equation.\n        value : float\n            The value at which the isocontour should be calculated.\n        field_x : string\n            The x-component field\n        field_y : string\n            The y-component field\n        field_z : string\n            The z-component field\n        fluxing_field : string, optional\n            The field whose passage over the surface is of interest.  If not\n            specified, assumed to be 1.0 everywhere.\n\n        Returns\n        -------\n        flux : float\n            The summed flux.  Note that it is not currently scaled; this is\n            simply the code-unit area times the fields.\n\n        Examples\n        --------\n        This will create a data object, find a nice value in the center, and\n        calculate the metal flux over it.\n\n        >>> dd = ds.all_data()\n        >>> rho = dd.quantities[\"WeightedAverageQuantity\"](\n        ...     (\"gas\", \"density\"), weight=(\"gas\", \"cell_mass\")\n        ... )\n        >>> flux = dd.calculate_isocontour_flux(\n        ...     (\"gas\", \"density\"),\n        ...     rho,\n        ...     (\"gas\", \"velocity_x\"),\n        ...     (\"gas\", \"velocity_y\"),\n        ...     (\"gas\", \"velocity_z\"),\n        ...     (\"gas\", \"metallicity\"),\n        ... )\n        \"\"\"\n        flux = 0.0\n        for block, mask in self.blocks:\n            flux += self._calculate_flux_in_grid(\n                block, mask, field, value, field_x, field_y, field_z, fluxing_field\n            )\n        flux = self.comm.mpi_allreduce(flux, op=\"sum\")\n        return flux\n\n    def _calculate_flux_in_grid(\n        self, grid, mask, field, value, field_x, field_y, field_z, fluxing_field=None\n    ):\n        vc_fields = [field, field_x, field_y, field_z]\n        if fluxing_field is not None:\n            vc_fields.append(fluxing_field)\n\n        vc_data = grid.get_vertex_centered_data(vc_fields)\n\n        if fluxing_field is None:\n            ff = np.ones_like(vc_data[field], dtype=\"float64\")\n        else:\n            ff = vc_data[fluxing_field]\n\n        return march_cubes_grid_flux(\n            value,\n            vc_data[field],\n            vc_data[field_x],\n            vc_data[field_y],\n            vc_data[field_z],\n            ff,\n            mask,\n            grid.LeftEdge,\n            grid.dds,\n        )\n\n    def extract_connected_sets(\n        self, field, num_levels, min_val, max_val, log_space=True, cumulative=True\n    ):\n        \"\"\"\n        This function will create a set of contour objects, defined\n        by having connected cell structures, which can then be\n        studied and used to 'paint' their source grids, thus enabling\n        them to be plotted.\n\n        Note that this function *can* return a connected set object that has no\n        member values.\n        \"\"\"\n        if log_space:\n            cons = np.logspace(np.log10(min_val), np.log10(max_val), num_levels + 1)\n        else:\n            cons = np.linspace(min_val, max_val, num_levels + 1)\n        contours = {}\n        for level in range(num_levels):\n            contours[level] = {}\n            if cumulative:\n                mv = max_val\n            else:\n                mv = cons[level + 1]\n            from yt.data_objects.level_sets.api import identify_contours\n            from yt.data_objects.level_sets.clump_handling import add_contour_field\n\n            nj, cids = identify_contours(self, field, cons[level], mv)\n            unique_contours = set()\n            for sl_list in cids.values():\n                for _sl, ff in sl_list:\n                    unique_contours.update(np.unique(ff))\n            contour_key = uuid.uuid4().hex\n            # In case we're a cut region already...\n            base_object = getattr(self, \"base_object\", self)\n            add_contour_field(base_object.ds, contour_key)\n            for cid in sorted(unique_contours):\n                if cid == -1:\n                    continue\n                contours[level][cid] = base_object.cut_region(\n                    [f\"obj['contours_{contour_key}'] == {cid}\"],\n                    {f\"contour_slices_{contour_key}\": cids},\n                )\n        return cons, contours\n\n    def volume(self):\n        \"\"\"\n        Return the volume of the data container.\n        This is found by adding up the volume of the cells with centers\n        in the container, rather than using the geometric shape of\n        the container, so this may vary very slightly\n        from what might be expected from the geometric volume.\n        \"\"\"\n        return self.quantities.total_quantity((\"index\", \"cell_volume\"))\n"
  },
  {
    "path": "yt/data_objects/selection_objects/disk.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n    YTSelectionContainer3D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import (\n    fix_length,\n    validate_3d_array,\n    validate_center,\n    validate_float,\n    validate_object,\n    validate_sequence,\n)\n\n\nclass YTDisk(YTSelectionContainer3D):\n    \"\"\"\n    By providing a *center*, a *normal*, a *radius* and a *height* we\n    can define a cylinder of any proportion.  Only cells whose centers are\n    within the cylinder will be selected.\n\n    Parameters\n    ----------\n    center : array_like\n        coordinate to which the normal, radius, and height all reference\n    normal : array_like\n        the normal vector defining the direction of lengthwise part of the\n        cylinder\n    radius : float\n        the radius of the cylinder\n    height : float\n        the distance from the midplane of the cylinder to the top and\n        bottom planes\n    fields : array of fields, optional\n        any fields to be pre-loaded in the cylinder object\n    ds: ~yt.data_objects.static_output.Dataset, optional\n        An optional dataset to use rather than self.ds\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source: optional\n        Draw the selection from the provided data source rather than\n        all data associated with the data_set\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> c = [0.5, 0.5, 0.5]\n    >>> disk = ds.disk(c, [1, 0, 0], (1, \"kpc\"), (10, \"kpc\"))\n    \"\"\"\n\n    _type_name = \"disk\"\n    _con_args = (\"center\", \"_norm_vec\", \"radius\", \"height\")\n\n    def __init__(\n        self,\n        center,\n        normal,\n        radius,\n        height,\n        fields=None,\n        ds=None,\n        field_parameters=None,\n        data_source=None,\n    ):\n        validate_center(center)\n        validate_3d_array(normal)\n        validate_float(radius)\n        validate_float(height)\n        validate_sequence(fields)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        YTSelectionContainer3D.__init__(self, center, ds, field_parameters, data_source)\n        self._norm_vec = np.array(normal) / np.sqrt(np.dot(normal, normal))\n        self.set_field_parameter(\"normal\", self._norm_vec)\n        self.set_field_parameter(\"center\", self.center)\n        self.height = fix_length(height, self.ds)\n        self.radius = fix_length(radius, self.ds)\n        self._d = -1.0 * np.dot(self._norm_vec, self.center)\n\n    def _get_bbox(self):\n        \"\"\"\n        Return the minimum bounding box for the disk.\n        \"\"\"\n        # http://www.iquilezles.org/www/articles/diskbbox/diskbbox.htm\n        pa = self.center + self._norm_vec * self.height\n        pb = self.center - self._norm_vec * self.height\n        a = pa - pb\n        db = self.radius * np.sqrt(1.0 - a.d * a.d / np.dot(a.d, a.d))\n        return np.minimum(pa - db, pb - db), np.maximum(pa + db, pb + db)\n"
  },
  {
    "path": "yt/data_objects/selection_objects/object_collection.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n    YTSelectionContainer3D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import validate_center, validate_object, validate_sequence\n\n\nclass YTDataCollection(YTSelectionContainer3D):\n    \"\"\"\n    By selecting an arbitrary *object_list*, we can act on those grids.\n    Child cells are not returned.\n    \"\"\"\n\n    _type_name = \"data_collection\"\n    _con_args = (\"_obj_list\",)\n\n    def __init__(\n        self, obj_list, ds=None, field_parameters=None, data_source=None, center=None\n    ):\n        validate_sequence(obj_list)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        if center is not None:\n            validate_center(center)\n        YTSelectionContainer3D.__init__(self, center, ds, field_parameters, data_source)\n        self._obj_ids = np.array([o.id - o._id_offset for o in obj_list], dtype=\"int64\")\n        self._obj_list = obj_list\n"
  },
  {
    "path": "yt/data_objects/selection_objects/point.py",
    "content": "from yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n    YTSelectionContainer0D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import validate_3d_array, validate_object\nfrom yt.units import YTArray\n\n\nclass YTPoint(YTSelectionContainer0D):\n    \"\"\"\n    A 0-dimensional object defined by a single point\n\n    Parameters\n    ----------\n    p: array_like\n        A points defined within the domain.  If the domain is\n        periodic its position will be corrected to lie inside\n        the range [DLE,DRE) to ensure one and only one cell may\n        match that point\n    ds: ~yt.data_objects.static_output.Dataset, optional\n        An optional dataset to use rather than self.ds\n    field_parameters : dictionary\n        A dictionary of field parameters than can be accessed by derived\n        fields.\n    data_source: optional\n        Draw the selection from the provided data source rather than\n        all data associated with the data_set\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> c = [0.5, 0.5, 0.5]\n    >>> point = ds.point(c)\n    \"\"\"\n\n    _type_name = \"point\"\n    _con_args = (\"p\",)\n\n    def __init__(self, p, ds=None, field_parameters=None, data_source=None):\n        validate_3d_array(p)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        super().__init__(ds, field_parameters, data_source)\n        if isinstance(p, YTArray):\n            # we pass p through ds.arr to ensure code units are attached\n            self.p = self.ds.arr(p)\n        else:\n            self.p = self.ds.arr(p, \"code_length\")\n"
  },
  {
    "path": "yt/data_objects/selection_objects/ray.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n    YTSelectionContainer1D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.frontends.sph.data_structures import SPHDataset\nfrom yt.funcs import (\n    fix_axis,\n    validate_3d_array,\n    validate_axis,\n    validate_float,\n    validate_object,\n    validate_sequence,\n)\nfrom yt.units import YTArray, YTQuantity\nfrom yt.units._numpy_wrapper_functions import udot, unorm\nfrom yt.utilities.lib.pixelization_routines import SPHKernelInterpolationTable\nfrom yt.utilities.logger import ytLogger as mylog\n\n\nclass YTOrthoRay(YTSelectionContainer1D):\n    \"\"\"\n    This is an orthogonal ray cast through the entire domain, at a specific\n    coordinate.\n\n    This object is typically accessed through the `ortho_ray` object that\n    hangs off of index objects.  The resulting arrays have their\n    dimensionality reduced to one, and an ordered list of points at an\n    (x,y) tuple along `axis` are available.\n\n    Parameters\n    ----------\n    axis : int or char\n        The axis along which to slice.  Can be 0, 1, or 2 for x, y, z.\n    coords : tuple of floats\n        The (plane_x, plane_y) coordinates at which to cast the ray.  Note\n        that this is in the plane coordinates: so if you are casting along\n        x, this will be (y, z).  If you are casting along y, this will be\n        (z, x).  If you are casting along z, this will be (x, y).\n    ds: ~yt.data_objects.static_output.Dataset, optional\n        An optional dataset to use rather than self.ds\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source: optional\n        Draw the selection from the provided data source rather than\n        all data associated with the data_set\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> oray = ds.ortho_ray(0, (0.2, 0.74))\n    >>> print(oray[\"gas\", \"density\"])\n\n    Note: The low-level data representation for rays are not guaranteed to be\n    spatially ordered.  In particular, with AMR datasets, higher resolution\n    data is tagged on to the end of the ray.  If you want this data\n    represented in a spatially ordered manner, manually sort it by the \"t\"\n    field, which is the value of the parametric variable that goes from 0 at\n    the start of the ray to 1 at the end:\n\n    >>> my_ray = ds.ortho_ray(...)\n    >>> ray_sort = np.argsort(my_ray[\"t\"])\n    >>> density = my_ray[\"gas\", \"density\"][ray_sort]\n    \"\"\"\n\n    _key_fields = [\"x\", \"y\", \"z\", \"dx\", \"dy\", \"dz\"]\n    _type_name = \"ortho_ray\"\n    _con_args = (\"axis\", \"coords\")\n\n    def __init__(self, axis, coords, ds=None, field_parameters=None, data_source=None):\n        validate_axis(ds, axis)\n        validate_sequence(coords)\n        for c in coords:\n            validate_float(c)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        super().__init__(ds, field_parameters, data_source)\n        self.axis = fix_axis(axis, self.ds)\n        xax = self.ds.coordinates.x_axis[self.axis]\n        yax = self.ds.coordinates.y_axis[self.axis]\n        self.px_ax = xax\n        self.py_ax = yax\n        # Even though we may not be using x,y,z we use them here.\n        self.px_dx = f\"d{'xyz'[self.px_ax]}\"\n        self.py_dx = f\"d{'xyz'[self.py_ax]}\"\n        # Convert coordinates to code length.\n        if isinstance(coords[0], YTQuantity):\n            self.px = self.ds.quan(coords[0]).to(\"code_length\")\n        else:\n            self.px = self.ds.quan(coords[0], \"code_length\")\n        if isinstance(coords[1], YTQuantity):\n            self.py = self.ds.quan(coords[1]).to(\"code_length\")\n        else:\n            self.py = self.ds.quan(coords[1], \"code_length\")\n        self.sort_by = \"xyz\"[self.axis]\n\n    @property\n    def coords(self):\n        return (self.px, self.py)\n\n\nclass YTRay(YTSelectionContainer1D):\n    \"\"\"\n    This is an arbitrarily-aligned ray cast through the entire domain, at a\n    specific coordinate.\n\n    This object is typically accessed through the `ray` object that hangs\n    off of index objects.  The resulting arrays have their\n    dimensionality reduced to one, and an ordered list of points at an\n    (x,y) tuple along `axis` are available, as is the `t` field, which\n    corresponds to a unitless measurement along the ray from start to\n    end.\n\n    Parameters\n    ----------\n    start_point : array-like set of 3 floats\n        The place where the ray starts.\n    end_point : array-like set of 3 floats\n        The place where the ray ends.\n    ds: ~yt.data_objects.static_output.Dataset, optional\n        An optional dataset to use rather than self.ds\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source: optional\n        Draw the selection from the provided data source rather than\n        all data associated with the data_set\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> ray = ds.ray((0.2, 0.74, 0.11), (0.4, 0.91, 0.31))\n    >>> print(ray[\"gas\", \"density\"], ray[\"t\"], ray[\"dts\"])\n\n    Note: The low-level data representation for rays are not guaranteed to be\n    spatially ordered.  In particular, with AMR datasets, higher resolution\n    data is tagged on to the end of the ray.  If you want this data\n    represented in a spatially ordered manner, manually sort it by the \"t\"\n    field, which is the value of the parametric variable that goes from 0 at\n    the start of the ray to 1 at the end:\n\n    >>> my_ray = ds.ray(...)\n    >>> ray_sort = np.argsort(my_ray[\"t\"])\n    >>> density = my_ray[\"gas\", \"density\"][ray_sort]\n    \"\"\"\n\n    _type_name = \"ray\"\n    _con_args = (\"start_point\", \"end_point\")\n    _container_fields = (\"t\", \"dts\")\n\n    def __init__(\n        self, start_point, end_point, ds=None, field_parameters=None, data_source=None\n    ):\n        validate_3d_array(start_point)\n        validate_3d_array(end_point)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        super().__init__(ds, field_parameters, data_source)\n        if isinstance(start_point, YTArray):\n            self.start_point = self.ds.arr(start_point).to(\"code_length\")\n        else:\n            self.start_point = self.ds.arr(start_point, \"code_length\", dtype=\"float64\")\n        if isinstance(end_point, YTArray):\n            self.end_point = self.ds.arr(end_point).to(\"code_length\")\n        else:\n            self.end_point = self.ds.arr(end_point, \"code_length\", dtype=\"float64\")\n        if (self.start_point < self.ds.domain_left_edge).any() or (\n            self.end_point > self.ds.domain_right_edge\n        ).any():\n            mylog.warning(\n                \"Ray start or end is outside the domain. \"\n                \"Returned data will only be for the ray section inside the domain.\"\n            )\n        self.vec = self.end_point - self.start_point\n        self._set_center(self.start_point)\n        self.set_field_parameter(\"center\", self.start_point)\n        self._dts, self._ts = None, None\n\n    def _generate_container_field(self, field):\n        # What should we do with `ParticleDataset`?\n        if isinstance(self.ds, SPHDataset):\n            return self._generate_container_field_sph(field)\n        else:\n            return self._generate_container_field_grid(field)\n\n    def _generate_container_field_grid(self, field):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        if field == \"dts\":\n            return self._current_chunk.dtcoords\n        elif field == \"t\":\n            return self._current_chunk.tcoords\n        else:\n            raise KeyError(field)\n\n    def _generate_container_field_sph(self, field):\n        if field not in [\"dts\", \"t\"]:\n            raise KeyError(field)\n\n        length = unorm(self.vec)\n        pos = self[self.ds._sph_ptypes[0], \"particle_position\"]\n        r = pos - self.start_point\n        l = udot(r, self.vec / length)\n\n        if field == \"t\":\n            return l / length\n\n        hsml = self[self.ds._sph_ptypes[0], \"smoothing_length\"]\n        mass = self[self.ds._sph_ptypes[0], \"particle_mass\"]\n        dens = self[self.ds._sph_ptypes[0], \"density\"]\n        # impact parameter from particle to ray\n        b = np.sqrt(np.sum(r**2, axis=1) - l**2)\n\n        # Use an interpolation table to evaluate the integrated 2D\n        # kernel from the dimensionless impact parameter b/hsml.\n        # The table tabulates integrals for values of (b/hsml)**2\n        itab = SPHKernelInterpolationTable(self.ds.kernel_name)\n        dl = itab.interpolate_array((b / hsml) ** 2) * mass / dens / hsml**2\n        return dl / length\n"
  },
  {
    "path": "yt/data_objects/selection_objects/region.py",
    "content": "from yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n    YTSelectionContainer3D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import (\n    validate_3d_array,\n    validate_center,\n    validate_object,\n    validate_sequence,\n)\nfrom yt.units import YTArray\n\n\nclass YTRegion(YTSelectionContainer3D):\n    \"\"\"A 3D region of data with an arbitrary center.\n\n    Takes an array of three *left_edge* coordinates, three\n    *right_edge* coordinates, and a *center* that can be anywhere\n    in the domain. If the selected region extends past the edges\n    of the domain, no data will be found there, though the\n    object's `left_edge` or `right_edge` are not modified.\n\n    Parameters\n    ----------\n    center : array_like\n        The center of the region\n    left_edge : array_like\n        The left edge of the region\n    right_edge : array_like\n        The right edge of the region\n    \"\"\"\n\n    _type_name = \"region\"\n    _con_args = (\"center\", \"left_edge\", \"right_edge\")\n\n    def __init__(\n        self,\n        center,\n        left_edge,\n        right_edge,\n        fields=None,\n        ds=None,\n        field_parameters=None,\n        data_source=None,\n    ):\n        if center is not None:\n            validate_center(center)\n        validate_3d_array(left_edge)\n        validate_3d_array(right_edge)\n        validate_sequence(fields)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        YTSelectionContainer3D.__init__(self, center, ds, field_parameters, data_source)\n        if not isinstance(left_edge, YTArray):\n            self.left_edge = self.ds.arr(left_edge, \"code_length\", dtype=\"float64\")\n        else:\n            # need to assign this dataset's unit registry to the YTArray\n            self.left_edge = self.ds.arr(left_edge.copy(), dtype=\"float64\")\n        if not isinstance(right_edge, YTArray):\n            self.right_edge = self.ds.arr(right_edge, \"code_length\", dtype=\"float64\")\n        else:\n            # need to assign this dataset's unit registry to the YTArray\n            self.right_edge = self.ds.arr(right_edge.copy(), dtype=\"float64\")\n\n    def _get_bbox(self):\n        \"\"\"\n        Return the minimum bounding box for the region.\n        \"\"\"\n        return self.left_edge.copy(), self.right_edge.copy()\n"
  },
  {
    "path": "yt/data_objects/selection_objects/slices.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n    YTSelectionContainer2D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import (\n    fix_length,\n    is_sequence,\n    iter_fields,\n    validate_3d_array,\n    validate_axis,\n    validate_center,\n    validate_float,\n    validate_object,\n    validate_width_tuple,\n)\nfrom yt.utilities.exceptions import YTNotInsideNotebook\nfrom yt.utilities.minimal_representation import MinimalSliceData\nfrom yt.utilities.orientation import Orientation\n\n\nclass YTSlice(YTSelectionContainer2D):\n    \"\"\"\n    This is a data object corresponding to a slice through the simulation\n    domain.\n\n    This object is typically accessed through the `slice` object that hangs\n    off of index objects.  Slice is an orthogonal slice through the\n    data, taking all the points at the finest resolution available and then\n    indexing them.  It is more appropriately thought of as a slice\n    'operator' than an object, however, as its field and coordinate can\n    both change.\n\n    Parameters\n    ----------\n    axis : int or char\n        The axis along which to slice.  Can be 0, 1, or 2 for x, y, z.\n    coord : float\n        The coordinate along the axis at which to slice.  This is in\n        \"domain\" coordinates.\n    center : array_like, optional\n        The 'center' supplied to fields that use it.  Note that this does\n        not have to have `coord` as one value.  optional.\n    ds: ~yt.data_objects.static_output.Dataset, optional\n        An optional dataset to use rather than self.ds\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source: optional\n        Draw the selection from the provided data source rather than\n        all data associated with the data_set\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> slice = ds.slice(0, 0.25)\n    >>> print(slice[\"gas\", \"density\"])\n    \"\"\"\n\n    _top_node = \"/Slices\"\n    _type_name = \"slice\"\n    _con_args = (\"axis\", \"coord\")\n    _container_fields = (\"px\", \"py\", \"pz\", \"pdx\", \"pdy\", \"pdz\")\n\n    def __init__(\n        self, axis, coord, center=None, ds=None, field_parameters=None, data_source=None\n    ):\n        validate_axis(ds, axis)\n        validate_float(coord)\n        # center is an optional parameter\n        if center is not None:\n            validate_center(center)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        YTSelectionContainer2D.__init__(self, axis, ds, field_parameters, data_source)\n        self._set_center(center)\n        self.coord = fix_length(coord, ds)\n\n    def _generate_container_field(self, field):\n        xax = self.ds.coordinates.x_axis[self.axis]\n        yax = self.ds.coordinates.y_axis[self.axis]\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        if field == \"px\":\n            return self._current_chunk.fcoords[:, xax]\n        elif field == \"py\":\n            return self._current_chunk.fcoords[:, yax]\n        elif field == \"pz\":\n            return self._current_chunk.fcoords[:, self.axis]\n        elif field == \"pdx\":\n            return self._current_chunk.fwidth[:, xax] * 0.5\n        elif field == \"pdy\":\n            return self._current_chunk.fwidth[:, yax] * 0.5\n        elif field == \"pdz\":\n            return self._current_chunk.fwidth[:, self.axis] * 0.5\n        else:\n            raise KeyError(field)\n\n    @property\n    def _mrep(self):\n        return MinimalSliceData(self)\n\n    def to_pw(self, fields=None, center=\"center\", width=None, origin=\"center-window\"):\n        r\"\"\"Create a :class:`~yt.visualization.plot_window.PWViewerMPL` from this\n        object.\n\n        This is a bare-bones mechanism of creating a plot window from this\n        object, which can then be moved around, zoomed, and on and on.  All\n        behavior of the plot window is relegated to that routine.\n        \"\"\"\n        pw = self._get_pw(fields, center, width, origin, \"Slice\")\n        return pw\n\n    def plot(self, fields=None):\n        if hasattr(self._data_source, \"left_edge\") and hasattr(\n            self._data_source, \"right_edge\"\n        ):\n            left_edge = self._data_source.left_edge\n            right_edge = self._data_source.right_edge\n            center = (left_edge + right_edge) / 2.0\n            width = right_edge - left_edge\n            xax = self.ds.coordinates.x_axis[self.axis]\n            yax = self.ds.coordinates.y_axis[self.axis]\n            lx, rx = left_edge[xax], right_edge[xax]\n            ly, ry = left_edge[yax], right_edge[yax]\n            width = (rx - lx), (ry - ly)\n        else:\n            width = self.ds.domain_width\n            center = self.ds.domain_center\n        pw = self._get_pw(fields, center, width, \"native\", \"Slice\")\n        try:\n            pw.show()\n        except YTNotInsideNotebook:\n            pass\n        return pw\n\n\nclass YTCuttingPlane(YTSelectionContainer2D):\n    \"\"\"\n    This is a data object corresponding to an oblique slice through the\n    simulation domain.\n\n    This object is typically accessed through the `cutting` object\n    that hangs off of index objects.  A cutting plane is an oblique\n    plane through the data, defined by a normal vector and a coordinate.\n    It attempts to guess an 'north' vector, which can be overridden, and\n    then it pixelizes the appropriate data onto the plane without\n    interpolation.\n\n    Parameters\n    ----------\n    normal : array_like\n        The vector that defines the desired plane.  For instance, the\n        angular momentum of a sphere.\n    center : array_like\n        The center of the cutting plane, where the normal vector is anchored.\n    north_vector: array_like, optional\n        An optional vector to describe the north-facing direction in the resulting\n        plane.\n    ds: ~yt.data_objects.static_output.Dataset, optional\n        An optional dataset to use rather than self.ds\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source: optional\n        Draw the selection from the provided data source rather than\n        all data associated with the dataset\n\n    Notes\n    -----\n\n    This data object in particular can be somewhat expensive to create.\n    It's also important to note that unlike the other 2D data objects, this\n    object provides px, py, pz, as some cells may have a height from the\n    plane.\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> cp = ds.cutting([0.1, 0.2, -0.9], [0.5, 0.42, 0.6])\n    >>> print(cp[\"gas\", \"density\"])\n    \"\"\"\n\n    _plane = None\n    _top_node = \"/CuttingPlanes\"\n    _key_fields = YTSelectionContainer2D._key_fields + [\"pz\", \"pdz\"]\n    _type_name = \"cutting\"\n    _con_args = (\"normal\", \"center\")\n    _tds_attrs = (\"_inv_mat\",)\n    _tds_fields = (\"x\", \"y\", \"z\", \"dx\")\n    _container_fields = (\"px\", \"py\", \"pz\", \"pdx\", \"pdy\", \"pdz\")\n\n    def __init__(\n        self,\n        normal,\n        center,\n        north_vector=None,\n        ds=None,\n        field_parameters=None,\n        data_source=None,\n    ):\n        validate_3d_array(normal)\n        validate_center(center)\n        if north_vector is not None:\n            validate_3d_array(north_vector)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        YTSelectionContainer2D.__init__(self, None, ds, field_parameters, data_source)\n        self._set_center(center)\n        self.set_field_parameter(\"center\", self.center)\n        # Let's set up our plane equation\n        # ax + by + cz + d = 0\n        self.orienter = Orientation(normal, north_vector=north_vector)\n        self._norm_vec = self.orienter.normal_vector\n        self._d = -1.0 * np.dot(self._norm_vec, self.center)\n        self._x_vec = self.orienter.unit_vectors[0]\n        self._y_vec = self.orienter.unit_vectors[1]\n        # First we try all three, see which has the best result:\n        self._rot_mat = np.array([self._x_vec, self._y_vec, self._norm_vec])\n        self._inv_mat = np.linalg.pinv(self._rot_mat)\n        self.set_field_parameter(\"cp_x_vec\", self._x_vec)\n        self.set_field_parameter(\"cp_y_vec\", self._y_vec)\n        self.set_field_parameter(\"cp_z_vec\", self._norm_vec)\n\n    @property\n    def normal(self):\n        return self._norm_vec\n\n    def _generate_container_field(self, field):\n        if self._current_chunk is None:\n            self.index._identify_base_chunk(self)\n        if field == \"px\":\n            x = self._current_chunk.fcoords[:, 0] - self.center[0]\n            y = self._current_chunk.fcoords[:, 1] - self.center[1]\n            z = self._current_chunk.fcoords[:, 2] - self.center[2]\n            tr = np.zeros(x.size, dtype=\"float64\")\n            tr = self.ds.arr(tr, \"code_length\")\n            tr += x * self._x_vec[0]\n            tr += y * self._x_vec[1]\n            tr += z * self._x_vec[2]\n            return tr\n        elif field == \"py\":\n            x = self._current_chunk.fcoords[:, 0] - self.center[0]\n            y = self._current_chunk.fcoords[:, 1] - self.center[1]\n            z = self._current_chunk.fcoords[:, 2] - self.center[2]\n            tr = np.zeros(x.size, dtype=\"float64\")\n            tr = self.ds.arr(tr, \"code_length\")\n            tr += x * self._y_vec[0]\n            tr += y * self._y_vec[1]\n            tr += z * self._y_vec[2]\n            return tr\n        elif field == \"pz\":\n            x = self._current_chunk.fcoords[:, 0] - self.center[0]\n            y = self._current_chunk.fcoords[:, 1] - self.center[1]\n            z = self._current_chunk.fcoords[:, 2] - self.center[2]\n            tr = np.zeros(x.size, dtype=\"float64\")\n            tr = self.ds.arr(tr, \"code_length\")\n            tr += x * self._norm_vec[0]\n            tr += y * self._norm_vec[1]\n            tr += z * self._norm_vec[2]\n            return tr\n        elif field == \"pdx\":\n            return self._current_chunk.fwidth[:, 0] * 0.5\n        elif field == \"pdy\":\n            return self._current_chunk.fwidth[:, 1] * 0.5\n        elif field == \"pdz\":\n            return self._current_chunk.fwidth[:, 2] * 0.5\n        else:\n            raise KeyError(field)\n\n    def to_pw(self, fields=None, center=\"center\", width=None, axes_unit=None):\n        r\"\"\"Create a :class:`~yt.visualization.plot_window.PWViewerMPL` from this\n        object.\n\n        This is a bare-bones mechanism of creating a plot window from this\n        object, which can then be moved around, zoomed, and on and on.  All\n        behavior of the plot window is relegated to that routine.\n        \"\"\"\n        normal = self.normal\n        center = self.center\n        self.fields = list(iter_fields(fields)) + [\n            k for k in self.field_data.keys() if k not in self._key_fields\n        ]\n        from yt.visualization.fixed_resolution import FixedResolutionBuffer\n        from yt.visualization.plot_window import (\n            PWViewerMPL,\n            get_oblique_window_parameters,\n        )\n\n        (bounds, center_rot) = get_oblique_window_parameters(\n            normal, center, width, self.ds\n        )\n        pw = PWViewerMPL(\n            self,\n            bounds,\n            fields=self.fields,\n            origin=\"center-window\",\n            periodic=False,\n            oblique=True,\n            frb_generator=FixedResolutionBuffer,\n            plot_type=\"OffAxisSlice\",\n        )\n        if axes_unit is not None:\n            pw.set_axes_unit(axes_unit)\n        pw._setup_plots()\n        return pw\n\n    def to_frb(self, width, resolution, height=None, periodic=False):\n        r\"\"\"This function returns a FixedResolutionBuffer generated from this\n        object.\n\n        An FixedResolutionBuffer is an object that accepts a\n        variable-resolution 2D object and transforms it into an NxM bitmap that\n        can be plotted, examined or processed.  This is a convenience function\n        to return an FRB directly from an existing 2D data object.  Unlike the\n        corresponding to_frb function for other YTSelectionContainer2D objects,\n        this does not accept a 'center' parameter as it is assumed to be\n        centered at the center of the cutting plane.\n\n        Parameters\n        ----------\n        width : width specifier\n            This can either be a floating point value, in the native domain\n            units of the simulation, or a tuple of the (value, unit) style.\n            This will be the width of the FRB.\n        height : height specifier, optional\n            This will be the height of the FRB, by default it is equal to width.\n        resolution : int or tuple of ints\n            The number of pixels on a side of the final FRB.\n        periodic : boolean\n            This can be true or false, and governs whether the pixelization\n            will span the domain boundaries.\n\n        Returns\n        -------\n        frb : :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer`\n            A fixed resolution buffer, which can be queried for fields.\n\n        Examples\n        --------\n\n        >>> v, c = ds.find_max((\"gas\", \"density\"))\n        >>> sp = ds.sphere(c, (100.0, \"au\"))\n        >>> L = sp.quantities.angular_momentum_vector()\n        >>> cutting = ds.cutting(L, c)\n        >>> frb = cutting.to_frb((1.0, \"pc\"), 1024)\n        >>> write_image(np.log10(frb[\"gas\", \"density\"]), \"density_1pc.png\")\n        \"\"\"\n        if is_sequence(width):\n            validate_width_tuple(width)\n            width = self.ds.quan(width[0], width[1])\n        if height is None:\n            height = width\n        elif is_sequence(height):\n            validate_width_tuple(height)\n            height = self.ds.quan(height[0], height[1])\n        if not is_sequence(resolution):\n            resolution = (resolution, resolution)\n        from yt.visualization.fixed_resolution import FixedResolutionBuffer\n\n        bounds = (-width / 2.0, width / 2.0, -height / 2.0, height / 2.0)\n        frb = FixedResolutionBuffer(self, bounds, resolution, periodic=periodic)\n        return frb\n"
  },
  {
    "path": "yt/data_objects/selection_objects/spheroids.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n    YTSelectionContainer3D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import (\n    fix_length,\n    validate_3d_array,\n    validate_center,\n    validate_float,\n    validate_object,\n    validate_sequence,\n)\nfrom yt.units import YTArray\nfrom yt.utilities.exceptions import YTEllipsoidOrdering, YTException\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.math_utils import get_rotation_matrix\nfrom yt.utilities.on_demand_imports import _miniball\n\n\nclass YTSphere(YTSelectionContainer3D):\n    \"\"\"\n    A sphere of points defined by a *center* and a *radius*.\n\n    Parameters\n    ----------\n    center : array_like\n        The center of the sphere.\n    radius : float, width specifier, or YTQuantity\n        The radius of the sphere. If passed a float,\n        that will be interpreted in code units. Also\n        accepts a (radius, unit) tuple or YTQuantity\n        instance with units attached.\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> c = [0.5, 0.5, 0.5]\n    >>> sphere = ds.sphere(c, (1.0, \"kpc\"))\n    \"\"\"\n\n    _type_name = \"sphere\"\n    _con_args = (\"center\", \"radius\")\n\n    def __init__(\n        self, center, radius, ds=None, field_parameters=None, data_source=None\n    ):\n        validate_center(center)\n        validate_float(radius)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        super().__init__(center, ds, field_parameters, data_source)\n        # Unpack the radius, if necessary\n        radius = fix_length(radius, self.ds)\n        self.set_field_parameter(\"radius\", radius)\n        self.set_field_parameter(\"center\", self.center)\n        self.radius = radius\n\n    def _get_bbox(self):\n        \"\"\"\n        Return the minimum bounding box for the sphere.\n        \"\"\"\n        return -self.radius + self.center, self.radius + self.center\n\n\nclass YTMinimalSphere(YTSelectionContainer3D):\n    \"\"\"\n    Build the smallest sphere that encompasses a set of points.\n\n    Parameters\n    ----------\n    points : YTArray\n        The points that the sphere will contain.\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"output_00080/info_00080.txt\")\n    >>> points = ds.r[\"particle_position\"]\n    >>> sphere = ds.minimal_sphere(points)\n    \"\"\"\n\n    _type_name = \"sphere\"\n    _override_selector_name = \"minimal_sphere\"\n    _con_args = (\"center\", \"radius\")\n\n    def __init__(self, points, ds=None, field_parameters=None, data_source=None):\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        validate_object(points, YTArray)\n\n        points = fix_length(points, ds)\n        if len(points) < 2:\n            raise YTException(\n                f\"Not enough points. Expected at least 2, got {len(points)}\"\n            )\n        mylog.debug(\"Building minimal sphere around points.\")\n        mb = _miniball.Miniball(points)\n        if not mb.is_valid():\n            raise YTException(\"Could not build valid sphere around points.\")\n\n        center = ds.arr(mb.center(), points.units)\n        radius = ds.quan(np.sqrt(mb.squared_radius()), points.units)\n        super().__init__(center, ds, field_parameters, data_source)\n        self.set_field_parameter(\"radius\", radius)\n        self.set_field_parameter(\"center\", self.center)\n        self.radius = radius\n\n\nclass YTEllipsoid(YTSelectionContainer3D):\n    \"\"\"\n    By providing a *center*,*A*,*B*,*C*,*e0*,*tilt* we\n    can define a ellipsoid of any proportion.  Only cells whose\n    centers are within the ellipsoid will be selected.\n\n    Parameters\n    ----------\n    center : array_like\n        The center of the ellipsoid.\n    A : float\n        The magnitude of the largest axis (semi-major) of the ellipsoid.\n    B : float\n        The magnitude of the medium axis (semi-medium) of the ellipsoid.\n    C : float\n        The magnitude of the smallest axis (semi-minor) of the ellipsoid.\n    e0 : array_like (automatically normalized)\n        the direction of the largest semi-major axis of the ellipsoid\n    tilt : float\n        After the rotation about the z-axis to align e0 to x in the x-y\n        plane, and then rotating about the y-axis to align e0 completely\n        to the x-axis, tilt is the angle in radians remaining to\n        rotate about the x-axis to align both e1 to the y-axis and e2 to\n        the z-axis.\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"RedshiftOutput0005\")\n    >>> c = [0.5, 0.5, 0.5]\n    >>> ell = ds.ellipsoid(c, 0.1, 0.1, 0.1, np.array([0.1, 0.1, 0.1]), 0.2)\n    \"\"\"\n\n    _type_name = \"ellipsoid\"\n    _con_args = (\"center\", \"_A\", \"_B\", \"_C\", \"_e0\", \"_tilt\")\n\n    def __init__(\n        self,\n        center,\n        A,\n        B,\n        C,\n        e0,\n        tilt,\n        fields=None,\n        ds=None,\n        field_parameters=None,\n        data_source=None,\n    ):\n        validate_center(center)\n        validate_float(A)\n        validate_float(B)\n        validate_float(C)\n        validate_3d_array(e0)\n        validate_float(tilt)\n        validate_sequence(fields)\n        validate_object(ds, Dataset)\n        validate_object(field_parameters, dict)\n        validate_object(data_source, YTSelectionContainer)\n        YTSelectionContainer3D.__init__(self, center, ds, field_parameters, data_source)\n        # make sure the magnitudes of semi-major axes are in order\n        if A < B or B < C:\n            raise YTEllipsoidOrdering(ds, A, B, C)\n        # make sure the smallest side is not smaller than dx\n        self._A = self.ds.quan(A, \"code_length\")\n        self._B = self.ds.quan(B, \"code_length\")\n        self._C = self.ds.quan(C, \"code_length\")\n\n        self._e0 = e0 = e0 / (e0**2.0).sum() ** 0.5\n        self._tilt = tilt\n\n        # find the t1 angle needed to rotate about z axis to align e0 to x\n        t1 = np.arctan(e0[1] / e0[0])\n        # rotate e0 by -t1\n        RZ = get_rotation_matrix(t1, (0, 0, 1)).transpose()\n        r1 = (e0 * RZ).sum(axis=1)\n        # find the t2 angle needed to rotate about y axis to align e0 to x\n        t2 = np.arctan(-r1[2] / r1[0])\n        \"\"\"\n        calculate the original e1\n        given the tilt about the x axis when e0 was aligned\n        to x after t1, t2 rotations about z, y\n        \"\"\"\n        RX = get_rotation_matrix(-tilt, (1, 0, 0)).transpose()\n        RY = get_rotation_matrix(-t2, (0, 1, 0)).transpose()\n        RZ = get_rotation_matrix(-t1, (0, 0, 1)).transpose()\n        e1 = ((0, 1, 0) * RX).sum(axis=1)\n        e1 = (e1 * RY).sum(axis=1)\n        e1 = (e1 * RZ).sum(axis=1)\n        e2 = np.cross(e0, e1)\n\n        self._e1 = e1\n        self._e2 = e2\n\n        self.set_field_parameter(\"A\", A)\n        self.set_field_parameter(\"B\", B)\n        self.set_field_parameter(\"C\", C)\n        self.set_field_parameter(\"e0\", e0)\n        self.set_field_parameter(\"e1\", e1)\n        self.set_field_parameter(\"e2\", e2)\n\n    def _get_bbox(self):\n        \"\"\"\n        Get the bounding box for the ellipsoid. NOTE that in this case\n        it is not the *minimum* bounding box.\n        \"\"\"\n        radius = self.ds.arr(np.max([self._A, self._B, self._C]), \"code_length\")\n        return -radius + self.center, radius + self.center\n"
  },
  {
    "path": "yt/data_objects/static_output.py",
    "content": "import abc\nimport functools\nimport hashlib\nimport itertools\nimport os\nimport pickle\nimport sys\nimport time\nimport warnings\nimport weakref\nfrom collections import defaultdict\nfrom collections.abc import MutableMapping\nfrom functools import cached_property\nfrom importlib.util import find_spec\nfrom stat import ST_CTIME\nfrom typing import TYPE_CHECKING, Any, Literal, Optional\n\nimport numpy as np\nimport unyt as un\nfrom more_itertools import unzip\nfrom unyt import Unit, UnitSystem, unyt_quantity\nfrom unyt.exceptions import UnitConversionError, UnitParseError\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._maintenance.ipython_compat import IPYWIDGETS_ENABLED\nfrom yt._typing import (\n    AnyFieldKey,\n    AxisOrder,\n    FieldKey,\n    FieldType,\n    ImplicitFieldKey,\n    ParticleType,\n)\nfrom yt.config import ytcfg\nfrom yt.data_objects.particle_filters import (\n    ParticleFilter,\n    add_particle_filter,\n    filter_registry,\n)\nfrom yt.data_objects.region_expression import RegionExpression\nfrom yt.data_objects.unions import ParticleUnion\nfrom yt.fields.derived_field import (\n    DerivedField,\n    DerivedFieldCombination,\n    ValidateSpatial,\n)\nfrom yt.fields.field_type_container import FieldTypeContainer\nfrom yt.fields.fluid_fields import setup_gradient_fields\nfrom yt.funcs import iter_fields, mylog, set_intersection, setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.coordinates.api import (\n    CartesianCoordinateHandler,\n    CoordinateHandler,\n    CylindricalCoordinateHandler,\n    GeographicCoordinateHandler,\n    InternalGeographicCoordinateHandler,\n    PolarCoordinateHandler,\n    SpectralCubeCoordinateHandler,\n    SphericalCoordinateHandler,\n)\nfrom yt.geometry.geometry_handler import Index\nfrom yt.units import UnitContainer, _wrap_display_ytarray, dimensions\nfrom yt.units.dimensions import current_mks\nfrom yt.units.unit_object import define_unit  # type: ignore\nfrom yt.units.unit_registry import UnitRegistry  # type: ignore\nfrom yt.units.unit_systems import (  # type: ignore\n    create_code_unit_system,\n    unit_system_registry,\n)\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.configure import YTConfig, configuration_callbacks\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.exceptions import (\n    YTFieldNotFound,\n    YTFieldNotParseable,\n    YTIllDefinedParticleFilter,\n    YTObjectNotImplemented,\n)\nfrom yt.utilities.lib.fnv_hash import fnv_hash\nfrom yt.utilities.minimal_representation import MinimalDataset\nfrom yt.utilities.object_registries import data_object_registry, output_type_registry\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_root_only\nfrom yt.utilities.parameter_file_storage import NoParameterShelf, ParameterFileStore\n\nif TYPE_CHECKING:\n    from sympy import Symbol\n\nif sys.version_info >= (3, 11):\n    from typing import assert_never\nelse:\n    from typing_extensions import assert_never\n\n\n# We want to support the movie format in the future.\n# When such a thing comes to pass, I'll move all the stuff that is constant up\n# to here, and then have it instantiate EnzoDatasets as appropriate.\n\n\n_cached_datasets: MutableMapping[int | str, \"Dataset\"] = weakref.WeakValueDictionary()\n\n# we set this global to None as a place holder\n# its actual instantiation is delayed until after yt.__init__\n# is completed because we need yt.config.ytcfg to be instantiated first\n\n_ds_store: ParameterFileStore | None = None\n\n\ndef _setup_ds_store(ytcfg: YTConfig) -> None:\n    global _ds_store\n    _ds_store = ParameterFileStore()\n\n\nconfiguration_callbacks.append(_setup_ds_store)\n\n\ndef _unsupported_object(ds, obj_name):\n    def _raise_unsupp(*args, **kwargs):\n        raise YTObjectNotImplemented(ds, obj_name)\n\n    return _raise_unsupp\n\n\nclass MutableAttribute:\n    \"\"\"A descriptor for mutable data\"\"\"\n\n    def __init__(self, display_array=False):\n        self.data = weakref.WeakKeyDictionary()\n        # We can assume that ipywidgets will not be *added* to the system\n        # during the course of execution, and if it is, we will not wrap the\n        # array.\n        self.display_array = display_array and IPYWIDGETS_ENABLED\n\n    def __get__(self, instance, owner):\n        return self.data.get(instance, None)\n\n    def __set__(self, instance, value):\n        if self.display_array:\n            try:\n                value._ipython_display_ = functools.partial(\n                    _wrap_display_ytarray, value\n                )\n            except AttributeError:\n                # If they have slots, we can't assign a new item.  So, let's catch\n                # the error!\n                pass\n        if isinstance(value, np.ndarray):\n            value.flags.writeable = False\n        self.data[instance] = value\n\n\ndef requires_index(attr_name):\n    @property\n    def ireq(self):\n        self.index\n        # By now it should have been set\n        attr = self.__dict__[attr_name]\n        return attr\n\n    @ireq.setter\n    def ireq(self, value):\n        self.__dict__[attr_name] = value\n\n    return ireq\n\n\nclass Dataset(abc.ABC):\n    _load_requirements: list[str] = []\n    default_fluid_type = \"gas\"\n    default_field = (\"gas\", \"density\")\n    fluid_types: tuple[FieldType, ...] = (\"gas\", \"deposit\", \"index\")\n    particle_types: tuple[ParticleType, ...] = (\"io\",)  # By default we have an 'all'\n    particle_types_raw: tuple[ParticleType, ...] | None = (\"io\",)\n    geometry: Geometry = Geometry.CARTESIAN\n    coordinates = None\n    storage_filename = None\n    particle_unions: dict[ParticleType, ParticleUnion] | None = None\n    known_filters: dict[ParticleType, ParticleFilter] | None = None\n    _index_class: type[Index]\n    field_units: dict[AnyFieldKey, Unit] | None = None\n    derived_field_list = requires_index(\"derived_field_list\")\n    fields = requires_index(\"fields\")\n    _field_info = None\n    conversion_factors: dict[str, float] | None = None\n    # _instantiated represents an instantiation time (since Epoch)\n    # the default is a place holder sentinel, falsy value\n    _instantiated: float = 0\n    _particle_type_counts = None\n    _proj_type = \"quad_proj\"\n    _ionization_label_format = \"roman_numeral\"\n    _determined_fields: dict[str, list[FieldKey]] | None = None\n    fields_detected = False\n\n    # these are set in self._parse_parameter_file()\n    domain_left_edge = MutableAttribute(True)\n    domain_right_edge = MutableAttribute(True)\n    domain_dimensions = MutableAttribute(True)\n    # the point in index space \"domain_left_edge\" doesn't necessarily\n    # map to (0, 0, 0)\n    domain_offset = np.zeros(3, dtype=\"int64\")\n    _periodicity = MutableAttribute()\n    _force_periodicity = False\n\n    # these are set in self._set_derived_attrs()\n    domain_width = MutableAttribute(True)\n    domain_center = MutableAttribute(True)\n\n    def __new__(cls, filename=None, *args, **kwargs):\n        if not isinstance(filename, str):\n            obj = object.__new__(cls)\n            # The Stream frontend uses a StreamHandler object to pass metadata\n            # to __init__.\n            is_stream = hasattr(filename, \"get_fields\") and hasattr(\n                filename, \"get_particle_type\"\n            )\n            if not is_stream:\n                obj.__init__(filename, *args, **kwargs)\n            return obj\n        apath = os.path.abspath(os.path.expanduser(filename))\n        cache_key = (apath, pickle.dumps(args), pickle.dumps(kwargs))\n        if ytcfg.get(\"yt\", \"skip_dataset_cache\"):\n            obj = object.__new__(cls)\n        elif cache_key not in _cached_datasets:\n            obj = object.__new__(cls)\n            if not obj._skip_cache:\n                _cached_datasets[cache_key] = obj\n        else:\n            obj = _cached_datasets[cache_key]\n        return obj\n\n    def __init_subclass__(cls, *args, **kwargs):\n        super().__init_subclass__(*args, **kwargs)\n        if cls.__name__ in output_type_registry:\n            warnings.warn(\n                f\"Overwriting {cls.__name__}, which was previously registered. \"\n                \"This is expected if you're importing a yt extension with a \"\n                \"frontend that was already migrated to the main code base.\",\n                stacklevel=2,\n            )\n        output_type_registry[cls.__name__] = cls\n        mylog.debug(\"Registering: %s as %s\", cls.__name__, cls)\n\n    def __init__(\n        self,\n        filename: str,\n        dataset_type: str | None = None,\n        units_override: dict[str, str] | None = None,\n        # valid unit_system values include all keys from unyt.unit_systems.unit_systems_registry + \"code\"\n        unit_system: Literal[\n            \"cgs\",\n            \"mks\",\n            \"imperial\",\n            \"galactic\",\n            \"solar\",\n            \"geometrized\",\n            \"planck\",\n            \"code\",\n        ] = \"cgs\",\n        default_species_fields: Optional[\n            \"Any\"\n        ] = None,  # Any used as a placeholder here\n        *,\n        axis_order: AxisOrder | None = None,\n    ) -> None:\n        \"\"\"\n        Base class for generating new output types.  Principally consists of\n        a *filename* and a *dataset_type* which will be passed on to children.\n        \"\"\"\n        # We return early and do NOT initialize a second time if this file has\n        # already been initialized.\n        if self._instantiated != 0:\n            return\n        self.dataset_type = dataset_type\n        self.conversion_factors = {}\n        self.parameters: dict[str, Any] = {}\n        self.region_expression = self.r = RegionExpression(self)\n        self.known_filters = self.known_filters or {}\n        self.particle_unions = self.particle_unions or {}\n        self.field_units = self.field_units or {}\n        self._determined_fields = {}\n        self.units_override = self.__class__._sanitize_units_override(units_override)\n        self.default_species_fields = default_species_fields\n\n        self._input_filename: str = os.fspath(filename)\n\n        # to get the timing right, do this before the heavy lifting\n        self._instantiated = time.time()\n\n        self.no_cgs_equiv_length = False\n\n        if unit_system == \"code\":\n            # create a fake MKS unit system which we will override later to\n            # avoid chicken/egg issue of the unit registry needing a unit system\n            # but code units need a unit registry to define the code units on\n            used_unit_system = \"mks\"\n        else:\n            used_unit_system = unit_system\n\n        self._create_unit_registry(used_unit_system)\n\n        self._parse_parameter_file()\n        self.set_units()\n        self.setup_cosmology()\n        self._assign_unit_system(unit_system)\n        self._setup_coordinate_handler(axis_order)\n        self.print_key_parameters()\n        self._set_derived_attrs()\n        # Because we need an instantiated class to check the ds's existence in\n        # the cache, we move that check to here from __new__.  This avoids\n        # double-instantiation.\n        # PR 3124: _set_derived_attrs() can change the hash, check store here\n        if _ds_store is None:\n            raise RuntimeError(\n                \"Something went wrong during yt's initialization: \"\n                \"dataset cache isn't properly initialized\"\n            )\n        try:\n            _ds_store.check_ds(self)\n        except NoParameterShelf:\n            pass\n        self._setup_classes()\n\n    @property\n    def filename(self):\n        if self._input_filename.startswith(\"http\"):\n            return self._input_filename\n        else:\n            return os.path.abspath(os.path.expanduser(self._input_filename))\n\n    @property\n    def parameter_filename(self):\n        # historic alias\n        return self.filename\n\n    @property\n    def basename(self):\n        return os.path.basename(self.filename)\n\n    @property\n    def directory(self):\n        return os.path.dirname(self.filename)\n\n    @property\n    def fullpath(self):\n        issue_deprecation_warning(\n            \"the Dataset.fullpath attribute is now aliased to Dataset.directory, \"\n            \"and all path attributes are now absolute. \"\n            \"Please use the directory attribute instead\",\n            stacklevel=3,\n            since=\"4.1\",\n        )\n        return self.directory\n\n    @property\n    def backup_filename(self):\n        name, _ext = os.path.splitext(self.filename)\n        return name + \"_backup.gdf\"\n\n    @cached_property\n    def unique_identifier(self) -> str:\n        retv = int(os.stat(self.parameter_filename)[ST_CTIME])\n        name_as_bytes = bytearray(self.parameter_filename.encode(\"utf-8\"))\n        retv += fnv_hash(name_as_bytes)\n        return str(retv)\n\n    @property\n    def periodicity(self):\n        if self._force_periodicity:\n            return (True, True, True)\n        elif getattr(self, \"_domain_override\", False):\n            # dataset loaded with a bounding box\n            return (False, False, False)\n        return self._periodicity\n\n    def force_periodicity(self, val=True):\n        \"\"\"\n        Override box periodicity to (True, True, True).\n        Use ds.force_periodicty(False) to use the actual box periodicity.\n        \"\"\"\n        # This is a user-facing method that embrace a long-standing\n        # workaround in yt user codes.\n        if not isinstance(val, bool):\n            raise TypeError(\"force_periodicity expected a boolean.\")\n        self._force_periodicity = val\n\n    @classmethod\n    def _missing_load_requirements(cls) -> list[str]:\n        # return a list of optional dependencies that are\n        # needed for the present class to function, but currently missing.\n        # returning an empty list means that all requirements are met\n        return [name for name in cls._load_requirements if not find_spec(name)]\n\n    # abstract methods require implementation in subclasses\n    @classmethod\n    @abc.abstractmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        # A heuristic test to determine if the data format can be interpreted\n        # with the present frontend\n        return False\n\n    @abc.abstractmethod\n    def _parse_parameter_file(self):\n        # set up various attributes from self.parameter_filename\n        # for a full description of what is required here see\n        # yt.frontends._skeleton.SkeletonDataset\n        pass\n\n    @abc.abstractmethod\n    def _set_code_unit_attributes(self):\n        # set up code-units to physical units normalization factors\n        # for a full description of what is required here see\n        # yt.frontends._skeleton.SkeletonDataset\n        pass\n\n    def _set_derived_attrs(self):\n        if self.domain_left_edge is None or self.domain_right_edge is None:\n            self.domain_center = np.zeros(3)\n            self.domain_width = np.zeros(3)\n        else:\n            self.domain_center = 0.5 * (self.domain_right_edge + self.domain_left_edge)\n            self.domain_width = self.domain_right_edge - self.domain_left_edge\n        if not isinstance(self.current_time, YTQuantity):\n            self.current_time = self.quan(self.current_time, \"code_time\")\n        for attr in (\"center\", \"width\", \"left_edge\", \"right_edge\"):\n            n = f\"domain_{attr}\"\n            v = getattr(self, n)\n            if not isinstance(v, YTArray) and v is not None:\n                # Note that we don't add on _ipython_display_ here because\n                # everything is stored inside a MutableAttribute.\n                v = self.arr(v, \"code_length\")\n                setattr(self, n, v)\n\n    def __reduce__(self):\n        args = (self._hash(),)\n        return (_reconstruct_ds, args)\n\n    def __repr__(self):\n        return f\"{self.__class__.__name__}: {self.parameter_filename}\"\n\n    def __str__(self):\n        return self.basename\n\n    def _hash(self):\n        s = f\"{self.basename};{self.current_time};{self.unique_identifier}\"\n        return hashlib.md5(s.encode(\"utf-8\")).hexdigest()\n\n    @cached_property\n    def checksum(self):\n        \"\"\"\n        Computes md5 sum of a dataset.\n\n        Note: Currently this property is unable to determine a complete set of\n        files that are a part of a given dataset. As a first approximation, the\n        checksum of :py:attr:`~parameter_file` is calculated. In case\n        :py:attr:`~parameter_file` is a directory, checksum of all files inside\n        the directory is calculated.\n        \"\"\"\n\n        def generate_file_md5(m, filename, blocksize=2**20):\n            with open(filename, \"rb\") as f:\n                while True:\n                    buf = f.read(blocksize)\n                    if not buf:\n                        break\n                    m.update(buf)\n\n        m = hashlib.md5()\n        if os.path.isdir(self.parameter_filename):\n            for root, _, files in os.walk(self.parameter_filename):\n                for fname in files:\n                    fname = os.path.join(root, fname)\n                    generate_file_md5(m, fname)\n        elif os.path.isfile(self.parameter_filename):\n            generate_file_md5(m, self.parameter_filename)\n        else:\n            m = \"notafile\"\n\n        if hasattr(m, \"hexdigest\"):\n            m = m.hexdigest()\n        return m\n\n    @property\n    def _mrep(self):\n        return MinimalDataset(self)\n\n    @property\n    def _skip_cache(self):\n        return False\n\n    @classmethod\n    def _guess_candidates(cls, base, directories, files):\n        \"\"\"\n        This is a class method that accepts a directory (base), a list of files\n        in that directory, and a list of subdirectories.  It should return a\n        list of filenames (defined relative to the supplied directory) and a\n        boolean as to whether or not further directories should be recursed.\n\n        This function doesn't need to catch all possibilities, nor does it need\n        to filter possibilities.\n        \"\"\"\n        return [], True\n\n    def close(self):  # noqa: B027\n        pass\n\n    def __getitem__(self, key):\n        \"\"\"Returns units, parameters, or conversion_factors in that order.\"\"\"\n        return self.parameters[key]\n\n    def __iter__(self):\n        yield from self.parameters\n\n    def get_smallest_appropriate_unit(\n        self, v, quantity=\"distance\", return_quantity=False\n    ):\n        \"\"\"\n        Returns the largest whole unit smaller than the YTQuantity passed to\n        it as a string.\n\n        The quantity keyword can be equal to `distance` or `time`.  In the\n        case of distance, the units are: 'Mpc', 'kpc', 'pc', 'au', 'rsun',\n        'km', etc.  For time, the units are: 'Myr', 'kyr', 'yr', 'day', 'hr',\n        's', 'ms', etc.\n\n        If return_quantity is set to True, it finds the largest YTQuantity\n        object with a whole unit and a power of ten as the coefficient, and it\n        returns this YTQuantity.\n        \"\"\"\n        good_u = None\n        if quantity == \"distance\":\n            unit_list = [\n                \"Ppc\",\n                \"Tpc\",\n                \"Gpc\",\n                \"Mpc\",\n                \"kpc\",\n                \"pc\",\n                \"au\",\n                \"rsun\",\n                \"km\",\n                \"m\",\n                \"cm\",\n                \"mm\",\n                \"um\",\n                \"nm\",\n                \"pm\",\n            ]\n        elif quantity == \"time\":\n            unit_list = [\n                \"Yyr\",\n                \"Zyr\",\n                \"Eyr\",\n                \"Pyr\",\n                \"Tyr\",\n                \"Gyr\",\n                \"Myr\",\n                \"kyr\",\n                \"yr\",\n                \"day\",\n                \"hr\",\n                \"s\",\n                \"ms\",\n                \"us\",\n                \"ns\",\n                \"ps\",\n                \"fs\",\n            ]\n        else:\n            raise ValueError(\n                \"Specified quantity must be equal to 'distance' or 'time'.\"\n            )\n        for unit in unit_list:\n            uq = self.quan(1.0, unit)\n            if uq <= v:\n                good_u = unit\n                break\n        if good_u is None and quantity == \"distance\":\n            good_u = \"cm\"\n        if good_u is None and quantity == \"time\":\n            good_u = \"s\"\n        if return_quantity:\n            unit_index = unit_list.index(good_u)\n            # This avoids indexing errors\n            if unit_index == 0:\n                return self.quan(1, unit_list[0])\n            # Number of orders of magnitude between unit and next one up\n            OOMs = np.ceil(\n                np.log10(\n                    self.quan(1, unit_list[unit_index - 1])\n                    / self.quan(1, unit_list[unit_index])\n                )\n            )\n            # Backwards order of coefficients (e.g. [100, 10, 1])\n            coeffs = 10 ** np.arange(OOMs)[::-1]\n            for j in coeffs:\n                uq = self.quan(j, good_u)\n                if uq <= v:\n                    return uq\n        else:\n            return good_u\n\n    def has_key(self, key):\n        \"\"\"\n        Checks units, parameters, and conversion factors. Returns a boolean.\n\n        \"\"\"\n        return key in self.parameters\n\n    _instantiated_index = None\n\n    @property\n    def index(self):\n        if self._instantiated_index is None:\n            self._instantiated_index = self._index_class(\n                self, dataset_type=self.dataset_type\n            )\n            # Now we do things that we need an instantiated index for\n            # ...first off, we create our field_info now.\n            oldsettings = np.geterr()\n            np.seterr(all=\"ignore\")\n            self.create_field_info()\n            np.seterr(**oldsettings)\n        return self._instantiated_index\n\n    @parallel_root_only\n    def print_key_parameters(self):\n        for a in [\n            \"current_time\",\n            \"domain_dimensions\",\n            \"domain_left_edge\",\n            \"domain_right_edge\",\n            \"cosmological_simulation\",\n        ]:\n            if not hasattr(self, a):\n                mylog.error(\"Missing %s in parameter file definition!\", a)\n                continue\n            v = getattr(self, a)\n            mylog.info(\"Parameters: %-25s = %s\", a, v)\n        if hasattr(self, \"cosmological_simulation\") and self.cosmological_simulation:\n            for a in [\n                \"current_redshift\",\n                \"omega_lambda\",\n                \"omega_matter\",\n                \"omega_radiation\",\n                \"hubble_constant\",\n            ]:\n                if not hasattr(self, a):\n                    mylog.error(\"Missing %s in parameter file definition!\", a)\n                    continue\n                v = getattr(self, a)\n                mylog.info(\"Parameters: %-25s = %s\", a, v)\n        if getattr(self, \"_domain_override\", False):\n            if any(self._periodicity):\n                mylog.warning(\n                    \"A bounding box was explicitly specified, so we \"\n                    \"are disabling periodicity.\"\n                )\n\n    @parallel_root_only\n    def print_stats(self):\n        self.index.print_stats()\n\n    @property\n    def field_list(self):\n        return self.index.field_list\n\n    @property\n    def field_info(self):\n        if self._field_info is None:\n            self.index\n        return self._field_info\n\n    @field_info.setter\n    def field_info(self, value):\n        self._field_info = value\n\n    def create_field_info(self):\n        # create_field_info will be called at the end of instantiating\n        # the index object. This will trigger index creation, which will\n        # call this function again.\n        if self._instantiated_index is None:\n            self.index\n            return\n\n        self.field_dependencies = {}\n        self.derived_field_list = []\n        self.filtered_particle_types = []\n        self.field_info = self._field_info_class(self, self.field_list)\n        self.coordinates.setup_fields(self.field_info)\n        self.field_info.setup_fluid_fields()\n        for ptype in self.particle_types:\n            self.field_info.setup_particle_fields(ptype)\n        self.field_info.setup_fluid_index_fields()\n        if \"all\" not in self.particle_types:\n            mylog.debug(\"Creating Particle Union 'all'\")\n            pu = ParticleUnion(\"all\", list(self.particle_types_raw))\n            nfields = self.add_particle_union(pu)\n            if nfields == 0:\n                mylog.debug(\"zero common fields: skipping particle union 'all'\")\n        if \"nbody\" not in self.particle_types:\n            mylog.debug(\"Creating Particle Union 'nbody'\")\n            ptypes = list(self.particle_types_raw)\n            if hasattr(self, \"_sph_ptypes\"):\n                for sph_ptype in self._sph_ptypes:\n                    if sph_ptype in ptypes:\n                        ptypes.remove(sph_ptype)\n            if ptypes:\n                nbody_ptypes = []\n                for ptype in ptypes:\n                    if (ptype, \"particle_mass\") in self.field_info:\n                        nbody_ptypes.append(ptype)\n                pu = ParticleUnion(\"nbody\", nbody_ptypes)\n                nfields = self.add_particle_union(pu)\n                if nfields == 0:\n                    mylog.debug(\"zero common fields, skipping particle union 'nbody'\")\n        self.field_info.setup_extra_union_fields()\n        self.field_info.load_all_plugins(self.default_fluid_type)\n        deps, unloaded = self.field_info.check_derived_fields()\n        self.field_dependencies.update(deps)\n        self.fields = FieldTypeContainer(self)\n        self.index.field_list = sorted(self.field_list)\n        # Now that we've detected the fields, set this flag so that\n        # deprecated fields will be logged if they are used\n        self.fields_detected = True\n\n    def set_field_label_format(self, format_property, value):\n        \"\"\"\n        Set format properties for how fields will be written\n        out. Accepts\n\n        format_property : string indicating what property to set\n        value: the value to set for that format_property\n        \"\"\"\n        available_formats = {\"ionization_label\": (\"plus_minus\", \"roman_numeral\")}\n        if format_property in available_formats:\n            if value in available_formats[format_property]:\n                setattr(self, f\"_{format_property}_format\", value)\n            else:\n                raise ValueError(\n                    f\"{value} not an acceptable value for format_property \"\n                    f\"{format_property}. Choices are {available_formats[format_property]}.\"\n                )\n        else:\n            raise ValueError(\n                f\"{format_property} not a recognized format_property. Available \"\n                f\"properties are: {list(available_formats.keys())}\"\n            )\n\n    def setup_deprecated_fields(self):\n        from yt.fields.field_aliases import _field_name_aliases\n\n        added = []\n        for old_name, new_name in _field_name_aliases:\n            try:\n                fi = self._get_field_info(new_name)\n            except YTFieldNotFound:\n                continue\n            self.field_info.alias((\"gas\", old_name), fi.name)\n            added.append((\"gas\", old_name))\n        self.field_info.find_dependencies(added)\n\n    def _setup_coordinate_handler(self, axis_order: AxisOrder | None) -> None:\n        # backward compatibility layer:\n        # turning off type-checker on a per-line basis\n        cls: type[CoordinateHandler]\n\n        if isinstance(self.geometry, tuple):  # type: ignore [unreachable]\n            issue_deprecation_warning(  # type: ignore [unreachable]\n                f\"Dataset object {self} has a tuple for its geometry attribute. \"\n                \"This is interpreted as meaning the first element is the actual geometry string, \"\n                \"and the second represents an arbitrary axis order. \"\n                \"This will stop working in a future version of yt.\\n\"\n                \"If you're loading data using yt.load_* functions, \"\n                \"you should be able to clear this warning by using the axis_order keyword argument.\\n\"\n                \"Otherwise, if your code relies on this behaviour, please reach out and open an issue:\\n\"\n                \"https://github.com/yt-project/yt/issues/new\\n\"\n                \"Also see https://github.com/yt-project/yt/pull/4244#discussion_r1063486520 for reference\",\n                since=\"4.2\",\n                stacklevel=2,\n            )\n            self.geometry, axis_order = self.geometry\n        elif callable(self.geometry):\n            issue_deprecation_warning(\n                f\"Dataset object {self} has a class for its geometry attribute. \"\n                \"This was accepted in previous versions of yt but leads to undefined behaviour. \"\n                \"This will stop working in a future version of yt.\\n\"\n                \"If you are relying on this behaviour, please reach out and open an issue:\\n\"\n                \"https://github.com/yt-project/yt/issues/new\",\n                since=\"4.2\",\n                stacklevel=2,\n            )\n            cls = self.geometry  # type: ignore [assignment]\n\n        if type(self.geometry) is str:  # noqa: E721\n            issue_deprecation_warning(\n                f\"Dataset object {self} has a raw string for its geometry attribute. \"\n                \"In yt>=4.2, a yt.geometry.geometry_enum.Geometry member is expected instead. \"\n                \"This will stop working in a future version of yt.\\n\",\n                since=\"4.2\",\n                stacklevel=2,\n            )\n            self.geometry = Geometry(self.geometry.lower())\n        if isinstance(self.geometry, CoordinateHandler):\n            issue_deprecation_warning(\n                f\"Dataset object {self} has a CoordinateHandler object for its geometry attribute. \"\n                \"In yt>=4.2, a yt.geometry.geometry_enum.Geometry member is expected instead. \"\n                \"This will stop working in a future version of yt.\\n\",\n                since=\"4.2\",\n                stacklevel=2,\n            )\n            _class_name = type(self.geometry).__name__\n            if not _class_name.endswith(\"CoordinateHandler\"):\n                raise RuntimeError(\n                    \"Expected CoordinateHandler child class name to end with CoordinateHandler\"\n                )\n            _geom_str = _class_name[: -len(\"CoordinateHandler\")]\n            self.geometry = Geometry(_geom_str.lower())\n            del _class_name, _geom_str\n\n        # end compatibility layer\n        if not isinstance(self.geometry, Geometry):\n            raise TypeError(\n                \"Expected dataset.geometry attribute to be of \"\n                \"type yt.geometry.geometry_enum.Geometry\\n\"\n                f\"Got {self.geometry=} with type {type(self.geometry)}\"\n            )\n\n        match self.geometry:\n            case Geometry.CARTESIAN:\n                cls = CartesianCoordinateHandler\n            case Geometry.CYLINDRICAL:\n                cls = CylindricalCoordinateHandler\n            case Geometry.POLAR:\n                cls = PolarCoordinateHandler\n            case Geometry.SPHERICAL:\n                cls = SphericalCoordinateHandler\n                # It shouldn't be required to reset self.no_cgs_equiv_length\n                # to the default value (False) here, but it's still necessary\n                # see https://github.com/yt-project/yt/pull/3618\n                self.no_cgs_equiv_length = False\n            case Geometry.GEOGRAPHIC:\n                cls = GeographicCoordinateHandler\n                self.no_cgs_equiv_length = True\n            case Geometry.INTERNAL_GEOGRAPHIC:\n                cls = InternalGeographicCoordinateHandler\n                self.no_cgs_equiv_length = True\n            case Geometry.SPECTRAL_CUBE:\n                cls = SpectralCubeCoordinateHandler\n            case _:\n                assert_never(self.geometry)\n\n        self.coordinates = cls(self, ordering=axis_order)\n\n    def add_particle_union(self, union):\n        # No string lookups here, we need an actual union.\n        f = self.particle_fields_by_type\n\n        # find fields common to all particle types in the union\n        fields = set_intersection([f[s] for s in union if s in self.particle_types_raw])\n\n        if len(fields) == 0:\n            # don't create this union if no fields are common to all\n            # particle types\n            return len(fields)\n\n        for field in fields:\n            units = set()\n            for s in union:\n                # First we check our existing fields for units\n                funits = self._get_field_info((s, field)).units\n                # Then we override with field_units settings.\n                funits = self.field_units.get((s, field), funits)\n                units.add(funits)\n            if len(units) == 1:\n                self.field_units[union.name, field] = list(units)[0]\n        self.particle_types += (union.name,)\n        self.particle_unions[union.name] = union\n        fields = [(union.name, field) for field in fields]\n        new_fields = [_ for _ in fields if _ not in self.field_list]\n        self.field_list.extend(new_fields)\n        new_field_info_fields = [\n            _ for _ in fields if _ not in self.field_info.field_list\n        ]\n        self.field_info.field_list.extend(new_field_info_fields)\n        self.index.field_list = sorted(self.field_list)\n        # Give ourselves a chance to add them here, first, then...\n        # ...if we can't find them, we set them up as defaults.\n        new_fields = self._setup_particle_types([union.name])\n        self.field_info.find_dependencies(new_fields)\n        return len(new_fields)\n\n    def add_particle_filter(self, filter):\n        \"\"\"Add particle filter to the dataset.\n\n        Add ``filter`` to the dataset and set up relevant derived_field.\n        It will also add any ``filtered_type`` that the ``filter`` depends on.\n\n        \"\"\"\n        # This requires an index\n        self.index\n        # This is a dummy, which we set up to enable passthrough of \"all\"\n        # concatenation fields.\n        n = getattr(filter, \"name\", filter)\n        self.known_filters[n] = None\n        if isinstance(filter, DerivedFieldCombination):\n            add_particle_filter(filter.name, filter)\n            filter = filter.name\n        if isinstance(filter, str):\n            used = False\n            f = filter_registry.get(filter, None)\n            if f is None:\n                return False\n            used = self._setup_filtered_type(f)\n            if used:\n                filter = f\n        else:\n            used = self._setup_filtered_type(filter)\n        if not used:\n            self.known_filters.pop(n, None)\n            return False\n        self.known_filters[filter.name] = filter\n        return True\n\n    def _setup_filtered_type(self, filter):\n        # Check if the filtered_type of this filter is known,\n        # otherwise add it first if it is in the filter_registry\n        if filter.filtered_type not in self.known_filters.keys():\n            if filter.filtered_type in filter_registry:\n                add_success = self.add_particle_filter(filter.filtered_type)\n                if add_success:\n                    mylog.info(\n                        \"Added filter dependency '%s' for '%s'\",\n                        filter.filtered_type,\n                        filter.name,\n                    )\n\n        if not filter.available(self.derived_field_list):\n            raise YTIllDefinedParticleFilter(\n                filter, filter.missing(self.derived_field_list)\n            )\n        fi = self.field_info\n        fd = self.field_dependencies\n        available = False\n        for fn in self.derived_field_list:\n            if fn[0] == filter.filtered_type:\n                # Now we can add this\n                available = True\n                self.derived_field_list.append((filter.name, fn[1]))\n                fi[filter.name, fn[1]] = filter.wrap_func(fn, fi[fn])\n                # Now we append the dependencies\n                fd[filter.name, fn[1]] = fd[fn]\n        if available:\n            if filter.name not in self.particle_types:\n                self.particle_types += (filter.name,)\n            if filter.name not in self.filtered_particle_types:\n                self.filtered_particle_types.append(filter.name)\n            if hasattr(self, \"_sph_ptypes\"):\n                if filter.filtered_type == self._sph_ptypes[0]:\n                    mylog.warning(\n                        \"It appears that you are filtering on an SPH field \"\n                        \"type. It is recommended to use 'gas' as the \"\n                        \"filtered particle type in this case instead.\"\n                    )\n                if filter.filtered_type in (self._sph_ptypes + (\"gas\",)):\n                    self._sph_ptypes = self._sph_ptypes + (filter.name,)\n            new_fields = self._setup_particle_types([filter.name])\n            deps, _ = self.field_info.check_derived_fields(new_fields)\n            self.field_dependencies.update(deps)\n        return available\n\n    def _setup_particle_types(self, ptypes=None):\n        df = []\n        if ptypes is None:\n            ptypes = self.ds.particle_types_raw\n        for ptype in set(ptypes):\n            df += self._setup_particle_type(ptype)\n        return df\n\n    def _get_field_info(\n        self,\n        field: FieldKey | ImplicitFieldKey | DerivedField,\n        /,\n    ) -> DerivedField:\n        field_info, candidates = self._get_field_info_helper(field)\n\n        if field_info.name[1] in (\"px\", \"py\", \"pz\", \"pdx\", \"pdy\", \"pdz\"):\n            # escape early as a bandaid solution to\n            # https://github.com/yt-project/yt/issues/3381\n            return field_info\n\n        def _are_ambiguous(candidates: list[FieldKey]) -> bool:\n            if len(candidates) < 2:\n                return False\n\n            ftypes, fnames = (list(_) for _ in unzip(candidates))\n            assert all(name == fnames[0] for name in fnames)\n\n            fi = self.field_info\n\n            all_aliases: bool = all(\n                fi[c].is_alias_to(fi[candidates[0]]) for c in candidates\n            )\n\n            all_equivalent_particle_fields: bool\n            if (\n                not self.particle_types\n                or not self.particle_unions\n                or not self.particle_types_raw\n            ):\n                all_equivalent_particle_fields = False\n            elif all(ft in self.particle_types for ft in ftypes):\n                ptypes = ftypes\n\n                sub_types_list: list[set[str]] = []\n                for pt in ptypes:\n                    if pt in self.particle_types_raw:\n                        sub_types_list.append({pt})\n                    elif pt in self.particle_unions:\n                        sub_types_list.append(set(self.particle_unions[pt].sub_types))\n                all_equivalent_particle_fields = all(\n                    st == sub_types_list[0] for st in sub_types_list\n                )\n            else:\n                all_equivalent_particle_fields = False\n\n            return not (all_aliases or all_equivalent_particle_fields)\n\n        if _are_ambiguous(candidates):\n            ft, fn = field_info.name\n            possible_ftypes = [c[0] for c in candidates]\n            raise ValueError(\n                f\"The requested field name {fn!r} \"\n                \"is ambiguous and corresponds to any one of \"\n                f\"the following field types:\\n {possible_ftypes}\\n\"\n                \"Please specify the requested field as an explicit \"\n                \"tuple (<ftype>, <fname>).\\n\"\n            )\n        return field_info\n\n    def _get_field_info_helper(\n        self,\n        field: FieldKey | ImplicitFieldKey | DerivedField,\n        /,\n    ) -> tuple[DerivedField, list[FieldKey]]:\n        self.index\n\n        ftype: str\n        fname: str\n        if isinstance(field, str):\n            ftype, fname = \"unknown\", field\n        elif isinstance(field, tuple) and len(field) == 2:\n            ftype, fname = field\n        elif isinstance(field, DerivedField):\n            ftype, fname = field.name\n        else:\n            raise YTFieldNotParseable(field)\n\n        if ftype == \"unknown\":\n            candidates: list[FieldKey] = [\n                (ft, fn) for ft, fn in self.field_info if fn == fname\n            ]\n\n            # We also should check \"all\" for particles, which can show up if you're\n            # mixing deposition/gas fields with particle fields.\n            if hasattr(self, \"_sph_ptype\"):\n                to_guess = [self.default_fluid_type, \"all\"]\n            else:\n                to_guess = [\"all\", self.default_fluid_type]\n            to_guess += list(self.fluid_types) + list(self.particle_types)\n            for ftype in to_guess:\n                if (ftype, fname) in self.field_info:\n                    return self.field_info[ftype, fname], candidates\n\n        elif (ftype, fname) in self.field_info:\n            return self.field_info[ftype, fname], []\n\n        raise YTFieldNotFound(field, ds=self)\n\n    def _setup_classes(self):\n        # Called by subclass\n        self.object_types = []\n        self.objects = []\n        self.plots = []\n        for name, cls in sorted(data_object_registry.items()):\n            if name in self._index_class._unsupported_objects:\n                setattr(self, name, _unsupported_object(self, name))\n                continue\n            self._add_object_class(name, cls)\n        self.object_types.sort()\n\n    def _add_object_class(self, name, base):\n        # skip projection data objects that don't make sense\n        # for this type of data\n        if \"proj\" in name and name != self._proj_type:\n            return\n        elif \"proj\" in name:\n            name = \"proj\"\n        self.object_types.append(name)\n        obj = functools.partial(base, ds=weakref.proxy(self))\n        obj.__doc__ = base.__doc__\n        setattr(self, name, obj)\n\n    def _find_extremum(self, field, ext, source=None, to_array=True):\n        \"\"\"\n        Find the extremum value of a field in a data object (source) and its position.\n\n        Parameters\n        ----------\n        field : str or tuple(str, str)\n        ext : str\n            'min' or 'max', select an extremum\n        source : a Yt data object\n        to_array : bool\n            select the return type.\n\n        Returns\n        -------\n        val, coords\n\n        val: unyt.unyt_quantity\n            extremum value detected\n\n        coords: unyt.unyt_array or list(unyt.unyt_quantity)\n            Conversion to a single unyt_array object is only possible for coordinate\n            systems with homogeneous dimensions across axes (i.e. cartesian).\n        \"\"\"\n        ext = ext.lower()\n        if source is None:\n            source = self.all_data()\n        method = {\n            \"min\": source.quantities.min_location,\n            \"max\": source.quantities.max_location,\n        }[ext]\n        val, x1, x2, x3 = method(field)\n        coords = [x1, x2, x3]\n        mylog.info(\"%s value is %0.5e at %0.16f %0.16f %0.16f\", ext, val, *coords)\n        if to_array:\n            if any(x.units.is_dimensionless for x in coords):\n                mylog.warning(\n                    \"dataset `%s` has angular coordinates. \"\n                    \"Use 'to_array=False' to preserve \"\n                    \"dimensionality in each coordinate.\",\n                    str(self),\n                )\n\n            # force conversion to length\n            alt_coords = []\n            for x in coords:\n                alt_coords.append(\n                    self.quan(x.v, \"code_length\")\n                    if x.units.is_dimensionless\n                    else x.to(\"code_length\")\n                )\n            coords = self.arr(alt_coords, dtype=\"float64\").to(\"code_length\")\n        return val, coords\n\n    def find_max(self, field, source=None, to_array=True):\n        \"\"\"\n        Returns (value, location) of the maximum of a given field.\n\n        This is a wrapper around _find_extremum\n        \"\"\"\n        mylog.debug(\"Searching for maximum value of %s\", field)\n        return self._find_extremum(field, \"max\", source=source, to_array=to_array)\n\n    def find_min(self, field, source=None, to_array=True):\n        \"\"\"\n        Returns (value, location) for the minimum of a given field.\n\n        This is a wrapper around _find_extremum\n        \"\"\"\n        mylog.debug(\"Searching for minimum value of %s\", field)\n        return self._find_extremum(field, \"min\", source=source, to_array=to_array)\n\n    def find_field_values_at_point(self, fields, coords):\n        \"\"\"\n        Returns the values [field1, field2,...] of the fields at the given\n        coordinates. Returns a list of field values in the same order as\n        the input *fields*.\n        \"\"\"\n        point = self.point(coords)\n        ret = [point[f] for f in iter_fields(fields)]\n        if len(ret) == 1:\n            return ret[0]\n        else:\n            return ret\n\n    def find_field_values_at_points(self, fields, coords):\n        \"\"\"\n        Returns the values [field1, field2,...] of the fields at the given\n        [(x1, y1, z2), (x2, y2, z2),...] points.  Returns a list of field\n        values in the same order as the input *fields*.\n\n        \"\"\"\n        # If an optimized version exists on the Index object we'll use that\n        try:\n            return self.index._find_field_values_at_points(fields, coords)\n        except AttributeError:\n            pass\n\n        fields = list(iter_fields(fields))\n        out = []\n\n        # This may be slow because it creates a data object for each point\n        for field_index, field in enumerate(fields):\n            funit = self._get_field_info(field).units\n            out.append(self.arr(np.empty((len(coords),)), funit))\n            for coord_index, coord in enumerate(coords):\n                out[field_index][coord_index] = self.point(coord)[field]\n        if len(fields) == 1:\n            return out[0]\n        else:\n            return out\n\n    # Now all the object related stuff\n    def all_data(self, find_max=False, **kwargs):\n        \"\"\"\n        all_data is a wrapper to the Region object for creating a region\n        which covers the entire simulation domain.\n        \"\"\"\n        self.index\n        if find_max:\n            c = self.find_max(\"density\")[1]\n        else:\n            c = (self.domain_right_edge + self.domain_left_edge) / 2.0\n        return self.region(c, self.domain_left_edge, self.domain_right_edge, **kwargs)\n\n    def box(self, left_edge, right_edge, **kwargs):\n        \"\"\"\n        box is a wrapper to the Region object for creating a region\n        without having to specify a *center* value.  It assumes the center\n        is the midpoint between the left_edge and right_edge.\n\n        Keyword arguments are passed to the initializer of the YTRegion object\n        (e.g. ds.region).\n        \"\"\"\n        # we handle units in the region data object\n        # but need to check if left_edge or right_edge is a\n        # list or other non-array iterable before calculating\n        # the center\n        if isinstance(left_edge[0], YTQuantity):\n            left_edge = YTArray(left_edge)\n            right_edge = YTArray(right_edge)\n\n        left_edge = np.asanyarray(left_edge, dtype=\"float64\")\n        right_edge = np.asanyarray(right_edge, dtype=\"float64\")\n        c = (left_edge + right_edge) / 2.0\n        return self.region(c, left_edge, right_edge, **kwargs)\n\n    def _setup_particle_type(self, ptype):\n        orig = set(self.field_info.items())\n        self.field_info.setup_particle_fields(ptype)\n        return [n for n, v in set(self.field_info.items()).difference(orig)]\n\n    @property\n    def particle_fields_by_type(self):\n        fields = defaultdict(list)\n        for field in self.field_list:\n            if field[0] in self.particle_types_raw:\n                fields[field[0]].append(field[1])\n        return fields\n\n    @property\n    def particles_exist(self):\n        for pt, f in itertools.product(self.particle_types_raw, self.field_list):\n            if pt == f[0]:\n                return True\n        return False\n\n    @property\n    def particle_type_counts(self):\n        self.index\n        if not self.particles_exist:\n            return {}\n\n        # frontends or index implementation can populate this dict while\n        # creating the index if they know particle counts at that time\n        if self._particle_type_counts is not None:\n            return self._particle_type_counts\n\n        self._particle_type_counts = self.index._get_particle_type_counts()\n        return self._particle_type_counts\n\n    @property\n    def ires_factor(self):\n        o2 = np.log2(self.refine_by)\n        if o2 != int(o2):\n            raise RuntimeError\n        # In the case that refine_by is 1 or 0 or something, we just\n        # want to make it a non-operative number, so we set to 1.\n        return max(1, int(o2))\n\n    def relative_refinement(self, l0, l1):\n        return self.refine_by ** (l1 - l0)\n\n    def _assign_unit_system(\n        self,\n        # valid unit_system values include all keys from unyt.unit_systems.unit_systems_registry + \"code\"\n        unit_system: Literal[\n            \"cgs\",\n            \"mks\",\n            \"imperial\",\n            \"galactic\",\n            \"solar\",\n            \"geometrized\",\n            \"planck\",\n            \"code\",\n        ],\n    ) -> None:\n        # we need to determine if the requested unit system\n        # is mks-like: i.e., it has a current with the same\n        # dimensions as amperes.\n        mks_system = False\n        mag_unit: unyt_quantity | None = getattr(self, \"magnetic_unit\", None)\n        mag_dims: set[Symbol] | None\n        if mag_unit is not None:\n            mag_dims = mag_unit.units.dimensions.free_symbols\n        else:\n            mag_dims = None\n        if unit_system != \"code\":\n            # if the unit system is known, we can check if it\n            # has a \"current_mks\" unit\n            us = unit_system_registry[str(unit_system).lower()]\n            mks_system = us.base_units[current_mks] is not None\n        elif mag_dims and current_mks in mag_dims:\n            # if we're using the not-yet defined code unit system,\n            # then we check if the magnetic field unit has a SI\n            # current dimension in it\n            mks_system = True\n        # Now we get to the tricky part. If we have an MKS-like system but\n        # we asked for a conversion to something CGS-like, or vice-versa,\n        # we have to convert the magnetic field\n        if mag_dims is not None:\n            self.magnetic_unit: unyt_quantity\n            if mks_system and current_mks not in mag_dims:\n                self.magnetic_unit = self.quan(\n                    self.magnetic_unit.to_value(\"gauss\") * 1.0e-4, \"T\"\n                )\n                # The following modification ensures that we get the conversion to\n                # mks correct\n                self.unit_registry.modify(\n                    \"code_magnetic\", self.magnetic_unit.value * 1.0e3 * 0.1**-0.5\n                )\n            elif not mks_system and current_mks in mag_dims:\n                self.magnetic_unit = self.quan(\n                    self.magnetic_unit.to_value(\"T\") * 1.0e4, \"gauss\"\n                )\n                # The following modification ensures that we get the conversion to\n                # cgs correct\n                self.unit_registry.modify(\n                    \"code_magnetic\", self.magnetic_unit.value * 1.0e-4\n                )\n        current_mks_unit = \"A\" if mks_system else None\n        us = create_code_unit_system(\n            self.unit_registry, current_mks_unit=current_mks_unit\n        )\n        if unit_system != \"code\":\n            us = unit_system_registry[str(unit_system).lower()]\n\n        self._unit_system_name: str = unit_system\n\n        self.unit_system: UnitSystem = us\n        self.unit_registry.unit_system = self.unit_system\n\n    @property\n    def _uses_code_length_unit(self) -> bool:\n        return self._unit_system_name == \"code\" or self.no_cgs_equiv_length\n\n    @property\n    def _uses_code_time_unit(self) -> bool:\n        return self._unit_system_name == \"code\"\n\n    def _create_unit_registry(self, unit_system):\n        from yt.units import dimensions\n\n        # yt assumes a CGS unit system by default (for back compat reasons).\n        # Since unyt is MKS by default we specify the MKS values of the base\n        # units in the CGS system. So, for length, 1 cm = .01 m. And so on.\n        # Note that the values associated with the code units here will be\n        # modified once we actually determine what the code units are from\n        # the dataset\n        # NOTE that magnetic fields are not done here yet, see set_code_units\n        self.unit_registry = UnitRegistry(unit_system=unit_system)\n        # 1 cm = 0.01 m\n        self.unit_registry.add(\"code_length\", 0.01, dimensions.length)\n        # 1 g = 0.001 kg\n        self.unit_registry.add(\"code_mass\", 0.001, dimensions.mass)\n        # 1 g/cm**3 = 1000 kg/m**3\n        self.unit_registry.add(\"code_density\", 1000.0, dimensions.density)\n        # 1 erg/g = 1.0e-4 J/kg\n        self.unit_registry.add(\n            \"code_specific_energy\", 1.0e-4, dimensions.energy / dimensions.mass\n        )\n        # 1 s = 1 s\n        self.unit_registry.add(\"code_time\", 1.0, dimensions.time)\n        # 1 K = 1 K\n        self.unit_registry.add(\"code_temperature\", 1.0, dimensions.temperature)\n        # 1 dyn/cm**2 = 0.1 N/m**2\n        self.unit_registry.add(\"code_pressure\", 0.1, dimensions.pressure)\n        # 1 cm/s = 0.01 m/s\n        self.unit_registry.add(\"code_velocity\", 0.01, dimensions.velocity)\n        # metallicity\n        self.unit_registry.add(\"code_metallicity\", 1.0, dimensions.dimensionless)\n        # dimensionless hubble parameter\n        self.unit_registry.add(\"h\", 1.0, dimensions.dimensionless, r\"h\")\n        # cosmological scale factor\n        self.unit_registry.add(\"a\", 1.0, dimensions.dimensionless)\n\n    def set_units(self):\n        \"\"\"\n        Creates the unit registry for this dataset.\n\n        \"\"\"\n\n        if getattr(self, \"cosmological_simulation\", False):\n            # this dataset is cosmological, so add cosmological units.\n            self.unit_registry.modify(\"h\", self.hubble_constant)\n            if getattr(self, \"current_redshift\", None) is not None:\n                # Comoving lengths\n                for my_unit in [\"m\", \"pc\", \"AU\", \"au\"]:\n                    new_unit = f\"{my_unit}cm\"\n                    my_u = Unit(my_unit, registry=self.unit_registry)\n                    self.unit_registry.add(\n                        new_unit,\n                        my_u.base_value / (1 + self.current_redshift),\n                        dimensions.length,\n                        f\"\\\\rm{{c{my_unit}}}\",\n                        prefixable=True,\n                    )\n                self.unit_registry.modify(\"a\", 1 / (1 + self.current_redshift))\n\n        self.set_code_units()\n\n    def setup_cosmology(self):\n        \"\"\"\n        If this dataset is cosmological, add a cosmology object.\n        \"\"\"\n        if not getattr(self, \"cosmological_simulation\", False):\n            return\n\n        # Set dynamical dark energy parameters\n        use_dark_factor = getattr(self, \"use_dark_factor\", False)\n        w_0 = getattr(self, \"w_0\", -1.0)\n        w_a = getattr(self, \"w_a\", 0.0)\n\n        # many frontends do not set this\n        setdefaultattr(self, \"omega_radiation\", 0.0)\n\n        self.cosmology = Cosmology(\n            hubble_constant=self.hubble_constant,\n            omega_matter=self.omega_matter,\n            omega_lambda=self.omega_lambda,\n            omega_radiation=self.omega_radiation,\n            use_dark_factor=use_dark_factor,\n            w_0=w_0,\n            w_a=w_a,\n        )\n\n        if not hasattr(self, \"current_time\"):\n            self.current_time = self.cosmology.t_from_z(self.current_redshift)\n\n        if getattr(self, \"current_redshift\", None) is not None:\n            self.critical_density = self.cosmology.critical_density(\n                self.current_redshift\n            )\n            self.scale_factor = 1.0 / (1.0 + self.current_redshift)\n\n    def get_unit_from_registry(self, unit_str):\n        \"\"\"\n        Creates a unit object matching the string expression, using this\n        dataset's unit registry.\n\n        Parameters\n        ----------\n        unit_str : str\n            string that we can parse for a sympy Expr.\n\n        \"\"\"\n        new_unit = Unit(unit_str, registry=self.unit_registry)\n        return new_unit\n\n    def set_code_units(self):\n        # here we override units, if overrides have been provided.\n        self._override_code_units()\n\n        # set attributes like ds.length_unit\n        self._set_code_unit_attributes()\n\n        self.unit_registry.modify(\"code_length\", self.length_unit)\n        self.unit_registry.modify(\"code_mass\", self.mass_unit)\n        self.unit_registry.modify(\"code_time\", self.time_unit)\n        vel_unit = getattr(self, \"velocity_unit\", self.length_unit / self.time_unit)\n        pressure_unit = getattr(\n            self,\n            \"pressure_unit\",\n            self.mass_unit / (self.length_unit * (self.time_unit) ** 2),\n        )\n        temperature_unit = getattr(self, \"temperature_unit\", 1.0)\n        density_unit = getattr(\n            self, \"density_unit\", self.mass_unit / self.length_unit**3\n        )\n        specific_energy_unit = getattr(self, \"specific_energy_unit\", vel_unit**2)\n        self.unit_registry.modify(\"code_velocity\", vel_unit)\n        self.unit_registry.modify(\"code_temperature\", temperature_unit)\n        self.unit_registry.modify(\"code_pressure\", pressure_unit)\n        self.unit_registry.modify(\"code_density\", density_unit)\n        self.unit_registry.modify(\"code_specific_energy\", specific_energy_unit)\n        # Defining code units for magnetic fields are tricky because\n        # they have different dimensions in different unit systems, so we have\n        # to handle them carefully\n        if hasattr(self, \"magnetic_unit\"):\n            if self.magnetic_unit.units.dimensions == dimensions.magnetic_field_cgs:\n                # We have to cast this explicitly to MKS base units, otherwise\n                # unyt will convert it automatically to Tesla\n                value = self.magnetic_unit.to_value(\"sqrt(kg)/(sqrt(m)*s)\")\n                dims = dimensions.magnetic_field_cgs\n            else:\n                value = self.magnetic_unit.to_value(\"T\")\n                dims = dimensions.magnetic_field_mks\n        else:\n            # Fallback to gauss if no magnetic unit is specified\n            # 1 gauss = 1 sqrt(g)/(sqrt(cm)*s) = 0.1**0.5 sqrt(kg)/(sqrt(m)*s)\n            value = 0.1**0.5\n            dims = dimensions.magnetic_field_cgs\n        self.unit_registry.add(\"code_magnetic\", value, dims)\n        # domain_width does not yet exist\n        if self.domain_left_edge is not None and self.domain_right_edge is not None:\n            DW = self.arr(self.domain_right_edge - self.domain_left_edge, \"code_length\")\n            self.unit_registry.add(\n                \"unitary\", float(DW.max() * DW.units.base_value), DW.units.dimensions\n            )\n\n    @classmethod\n    def _validate_units_override_keys(cls, units_override):\n        valid_keys = set(cls.default_units.keys())\n        invalid_keys_found = set(units_override.keys()) - valid_keys\n        if invalid_keys_found:\n            raise ValueError(\n                f\"units_override contains invalid keys: {invalid_keys_found}\"\n            )\n\n    default_units = {\n        \"length_unit\": \"cm\",\n        \"time_unit\": \"s\",\n        \"mass_unit\": \"g\",\n        \"velocity_unit\": \"cm/s\",\n        \"magnetic_unit\": \"gauss\",\n        \"temperature_unit\": \"K\",\n    }\n\n    @classmethod\n    def _sanitize_units_override(cls, units_override):\n        \"\"\"\n        Convert units_override values to valid input types for unyt.\n        Throw meaningful errors early if units_override is ill-formed.\n\n        Parameters\n        ----------\n        units_override : dict\n\n            keys should be strings with format  \"<dim>_unit\" (e.g. \"mass_unit\"), and\n            need to match a key in cls.default_units\n\n            values should be mappable to unyt.unyt_quantity objects, and can be any\n            combinations of:\n                - unyt.unyt_quantity\n                - 2-long sequence (tuples, list, ...) with types (number, str)\n                  e.g. (10, \"km\"), (0.1, \"s\")\n                - number (in which case the associated is taken from cls.default_unit)\n\n\n        Raises\n        ------\n        TypeError\n            If unit_override has invalid types\n\n        ValueError\n            If provided units do not match the intended dimensionality,\n            or in case of a zero scaling factor.\n\n        \"\"\"\n        uo = {}\n        if units_override is None:\n            return uo\n\n        cls._validate_units_override_keys(units_override)\n\n        for key in cls.default_units:\n            try:\n                val = units_override[key]\n            except KeyError:\n                continue\n\n            # Now attempt to instantiate a unyt.unyt_quantity from val ...\n            try:\n                # ... directly (valid if val is a number, or a unyt_quantity)\n                uo[key] = YTQuantity(val)\n                continue\n            except RuntimeError:\n                # note that unyt.unyt_quantity throws RuntimeError in lieu of TypeError\n                pass\n            try:\n                # ... with tuple unpacking (valid if val is a sequence)\n                uo[key] = YTQuantity(*val)\n                continue\n            except (RuntimeError, TypeError, UnitParseError):\n                pass\n            raise TypeError(\n                \"units_override values should be 2-sequence (float, str), \"\n                \"YTQuantity objects or real numbers; \"\n                f\"received {val} with type {type(val)}.\"\n            )\n        for key, q in uo.items():\n            if q.units.is_dimensionless:\n                uo[key] = YTQuantity(q, cls.default_units[key])\n            try:\n                uo[key].to(cls.default_units[key])\n            except UnitConversionError as err:\n                raise ValueError(\n                    \"Inconsistent dimensionality in units_override. \"\n                    f\"Received {key} = {uo[key]}\"\n                ) from err\n            if uo[key].value == 0.0:\n                raise ValueError(\n                    f\"Invalid 0 normalisation factor in units_override for {key}.\"\n                )\n        return uo\n\n    def _override_code_units(self):\n        if not self.units_override:\n            return\n\n        mylog.warning(\n            \"Overriding code units: Use this option only if you know that the \"\n            \"dataset doesn't define the units correctly or at all.\"\n        )\n        for ukey, val in self.units_override.items():\n            mylog.info(\"Overriding %s: %s.\", ukey, val)\n            setattr(self, ukey, self.quan(val))\n\n    _units = None\n    _unit_system_id = None\n\n    @property\n    def units(self):\n        current_uid = self.unit_registry.unit_system_id\n        if self._units is not None and self._unit_system_id == current_uid:\n            return self._units\n        self._unit_system_id = current_uid\n        self._units = UnitContainer(self.unit_registry)\n        return self._units\n\n    _arr = None\n\n    @property\n    def arr(self):\n        \"\"\"Converts an array into a :class:`yt.units.yt_array.YTArray`\n\n        The returned YTArray will be dimensionless by default, but can be\n        cast to arbitrary units using the ``units`` keyword argument.\n\n        Parameters\n        ----------\n\n        input_array : Iterable\n            A tuple, list, or array to attach units to\n        units: String unit specification, unit symbol or astropy object\n            The units of the array. Powers must be specified using python syntax\n            (cm**3, not cm^3).\n        input_units : Deprecated in favor of 'units'\n        dtype : string or NumPy dtype object\n            The dtype of the returned array data\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> import numpy as np\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> a = ds.arr([1, 2, 3], \"cm\")\n        >>> b = ds.arr([4, 5, 6], \"m\")\n        >>> a + b\n        YTArray([ 401.,  502.,  603.]) cm\n        >>> b + a\n        YTArray([ 4.01,  5.02,  6.03]) m\n\n        Arrays returned by this function know about the dataset's unit system\n\n        >>> a = ds.arr(np.ones(5), \"code_length\")\n        >>> a.in_units(\"Mpccm/h\")\n        YTArray([ 1.00010449,  1.00010449,  1.00010449,  1.00010449,\n                 1.00010449]) Mpc\n\n        \"\"\"\n\n        if self._arr is not None:\n            return self._arr\n        self._arr = functools.partial(YTArray, registry=self.unit_registry)\n        return self._arr\n\n    _quan = None\n\n    @property\n    def quan(self):\n        \"\"\"Converts an scalar into a :class:`yt.units.yt_array.YTQuantity`\n\n        The returned YTQuantity will be dimensionless by default, but can be\n        cast to arbitrary units using the ``units`` keyword argument.\n\n        Parameters\n        ----------\n\n        input_scalar : an integer or floating point scalar\n            The scalar to attach units to\n        units: String unit specification, unit symbol or astropy object\n            The units of the quantity. Powers must be specified using python\n            syntax (cm**3, not cm^3).\n        input_units : Deprecated in favor of 'units'\n        dtype : string or NumPy dtype object\n            The dtype of the array data.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> a = ds.quan(1, \"cm\")\n        >>> b = ds.quan(2, \"m\")\n        >>> a + b\n        201.0 cm\n        >>> b + a\n        2.01 m\n\n        Quantities created this way automatically know about the unit system\n        of the dataset.\n\n        >>> a = ds.quan(5, \"code_length\")\n        >>> a.in_cgs()\n        1.543e+25 cm\n\n        \"\"\"\n\n        if self._quan is not None:\n            return self._quan\n        self._quan = functools.partial(YTQuantity, registry=self.unit_registry)\n        return self._quan\n\n    def add_field(\n        self, name, function, *, sampling_type=None, force_override=False, **kwargs\n    ):\n        \"\"\"\n        Dataset-specific call to add_field\n\n        Add a new field, along with supplemental metadata, to the list of\n        available fields.  This respects a number of arguments, all of which\n        are passed on to the constructor for\n        :class:`~yt.data_objects.api.DerivedField`.\n\n        Parameters\n        ----------\n\n        name : str\n           is the name of the field.\n        function : callable\n           A function handle that defines the field.  Should accept\n           arguments (data)\n        sampling_type: str\n           \"cell\" or \"particle\" or \"local\"\n        force_override: bool\n           If False (default), an error will be raised if a field of the same name already exists.\n        units : str\n           A plain text string encoding the unit.  Powers must be in\n           python syntax (** instead of ^).\n        take_log : bool\n           Describes whether the field should be logged\n        validators : list\n           A list of :class:`FieldValidator` objects\n        vector_field : bool\n           Describes the dimensionality of the field.  Currently unused.\n        display_name : str\n           A name used in the plots\n        force_override : bool\n           Whether to override an existing derived field. Does not work with\n           on-disk fields.\n\n        \"\"\"\n        from yt.fields.field_functions import validate_field_function\n\n        if not isinstance(function, DerivedFieldCombination):\n            if sampling_type is None:\n                raise ValueError(\"You must specify a sampling_type for the field.\")\n            validate_field_function(function)\n        else:\n            sampling_type = function.sampling_type\n\n        self.index\n        if force_override and name in self.index.field_list:\n            raise RuntimeError(\n                \"force_override is only meant to be used with \"\n                \"derived fields, not on-disk fields.\"\n            )\n        if not force_override and name in self.field_info:\n            mylog.warning(\n                \"Field %s already exists. To override use `force_override=True`.\",\n                name,\n            )\n\n        self.field_info.add_field(\n            name, function, sampling_type, force_override=force_override, **kwargs\n        )\n        self.field_info._show_field_errors.append(name)\n        deps, _ = self.field_info.check_derived_fields([name])\n        self.field_dependencies.update(deps)\n\n    def add_mesh_sampling_particle_field(self, sample_field, ptype=\"all\"):\n        \"\"\"Add a new mesh sampling particle field\n\n        Creates a new particle field which has the value of the\n        *deposit_field* at the location of each particle of type\n        *ptype*.\n\n        Parameters\n        ----------\n\n        sample_field : tuple\n           The field name tuple of the mesh field to be deposited onto\n           the particles. This must be a field name tuple so yt can\n           appropriately infer the correct particle type.\n        ptype : string, default 'all'\n           The particle type onto which the deposition will occur.\n\n        Returns\n        -------\n\n        The field name tuple for the newly created field.\n\n        Examples\n        --------\n        >>> ds = yt.load(\"output_00080/info_00080.txt\")\n        ... ds.add_mesh_sampling_particle_field((\"gas\", \"density\"), ptype=\"all\")\n\n        >>> print(\"The density at the location of the particle is:\")\n        ... print(ds.r[\"all\", \"cell_gas_density\"])\n        The density at the location of the particle is:\n        [9.33886124e-30 1.22174333e-28 1.20402333e-28 ... 2.77410331e-30\n         8.79467609e-31 3.50665136e-30] g/cm**3\n\n        >>> len(ds.r[\"all\", \"cell_gas_density\"]) == len(ds.r[\"all\", \"particle_ones\"])\n        True\n\n        \"\"\"\n        if isinstance(sample_field, tuple):\n            ftype, sample_field = sample_field[0], sample_field[1]\n        else:\n            raise RuntimeError\n\n        return self.index._add_mesh_sampling_particle_field(sample_field, ftype, ptype)\n\n    def add_deposited_particle_field(\n        self, deposit_field, method, kernel_name=\"cubic\", weight_field=None\n    ):\n        \"\"\"Add a new deposited particle field\n\n        Creates a new deposited field based on the particle *deposit_field*.\n\n        Parameters\n        ----------\n\n        deposit_field : tuple\n           The field name tuple of the particle field the deposited field will\n           be created from.  This must be a field name tuple so yt can\n           appropriately infer the correct particle type.\n        method : string\n           This is the \"method name\" which will be looked up in the\n           `particle_deposit` namespace as `methodname_deposit`.  Current\n           methods include `simple_smooth`, `sum`, `std`, `cic`, `weighted_mean`,\n           `nearest` and `count`.\n        kernel_name : string, default 'cubic'\n           This is the name of the smoothing kernel to use. It is only used for\n           the `simple_smooth` method and is otherwise ignored. Current\n           supported kernel names include `cubic`, `quartic`, `quintic`,\n           `wendland2`, `wendland4`, and `wendland6`.\n        weight_field : (field_type, field_name) or None\n           Weighting field name for deposition method `weighted_mean`.\n           If None, use the particle mass.\n\n        Returns\n        -------\n\n        The field name tuple for the newly created field.\n        \"\"\"\n        self.index\n        if isinstance(deposit_field, tuple):\n            ptype, deposit_field = deposit_field[0], deposit_field[1]\n        else:\n            raise RuntimeError\n\n        if weight_field is None:\n            weight_field = (ptype, \"particle_mass\")\n        units = self.field_info[ptype, deposit_field].output_units\n        take_log = self.field_info[ptype, deposit_field].take_log\n        name_map = {\n            \"sum\": \"sum\",\n            \"std\": \"std\",\n            \"cic\": \"cic\",\n            \"weighted_mean\": \"avg\",\n            \"nearest\": \"nn\",\n            \"simple_smooth\": \"ss\",\n            \"count\": \"count\",\n        }\n        field_name = \"%s_\" + name_map[method] + \"_%s\"\n        field_name = field_name % (ptype, deposit_field.replace(\"particle_\", \"\"))\n\n        if method == \"count\":\n            field_name = f\"{ptype}_count\"\n            if (\"deposit\", field_name) in self.field_info:\n                mylog.warning(\"The deposited field %s already exists\", field_name)\n                return (\"deposit\", field_name)\n            else:\n                units = \"dimensionless\"\n                take_log = False\n\n        def _deposit_field(data):\n            \"\"\"\n            Create a grid field for particle quantities using given method.\n            \"\"\"\n            pos = data[ptype, \"particle_position\"]\n            fields = [data[ptype, deposit_field]]\n            if method == \"weighted_mean\":\n                fields.append(data[ptype, weight_field])\n            fields = [np.ascontiguousarray(f) for f in fields]\n            d = data.deposit(pos, fields, method=method, kernel_name=kernel_name)\n            d = data.ds.arr(d, units=units)\n            if method == \"weighted_mean\":\n                d[np.isnan(d)] = 0.0\n            return d\n\n        self.add_field(\n            (\"deposit\", field_name),\n            function=_deposit_field,\n            sampling_type=\"cell\",\n            units=units,\n            take_log=take_log,\n            validators=[ValidateSpatial()],\n        )\n        return (\"deposit\", field_name)\n\n    def add_gradient_fields(self, fields=None):\n        \"\"\"Add gradient fields.\n\n        Creates four new grid-based fields that represent the components of the gradient\n        of an existing field, plus an extra field for the magnitude of the gradient. The\n        gradient is computed using second-order centered differences.\n\n        Parameters\n        ----------\n        fields : str or tuple(str, str), or a list of the previous\n            Label(s) for at least one field. Can either represent a tuple\n            (<field type>, <field fname>) or simply the field name.\n            Warning: several field types may match the provided field name,\n            in which case the first one discovered internally is used.\n\n        Returns\n        -------\n        A list of field name tuples for the newly created fields.\n\n        Raises\n        ------\n        YTFieldNotParsable\n            If fields are not parsable to yt field keys.\n\n        YTFieldNotFound :\n            If at least one field can not be identified.\n\n        Examples\n        --------\n\n        >>> grad_fields = ds.add_gradient_fields((\"gas\", \"density\"))\n        >>> print(grad_fields)\n        ... [\n        ...     (\"gas\", \"density_gradient_x\"),\n        ...     (\"gas\", \"density_gradient_y\"),\n        ...     (\"gas\", \"density_gradient_z\"),\n        ...     (\"gas\", \"density_gradient_magnitude\"),\n        ... ]\n\n        Note that the above example assumes ds.geometry is Geometry.CARTESIAN.\n        In general, the function will create gradient components along the axes\n        of the dataset coordinate system.\n        For instance, with cylindrical data, one gets 'density_gradient_<r,theta,z>'\n\n        \"\"\"\n        if fields is None:\n            raise TypeError(\"Missing required positional argument: fields\")\n\n        self.index\n        data_obj = self.all_data()\n        explicit_fields = data_obj._determine_fields(fields)\n        grad_fields = []\n        for ftype, fname in explicit_fields:\n            units = self.field_info[ftype, fname].units\n            setup_gradient_fields(self.field_info, (ftype, fname), units)\n            # Now we make a list of the fields that were just made, to check them\n            # and to return them\n            grad_fields += [\n                (ftype, f\"{fname}_gradient_{suffix}\")\n                for suffix in self.coordinates.axis_order\n            ]\n            grad_fields.append((ftype, f\"{fname}_gradient_magnitude\"))\n            deps, _ = self.field_info.check_derived_fields(grad_fields)\n            self.field_dependencies.update(deps)\n        return grad_fields\n\n    _max_level = None\n\n    @property\n    def max_level(self):\n        if self._max_level is None:\n            self._max_level = self.index.max_level\n        return self._max_level\n\n    @max_level.setter\n    def max_level(self, value):\n        self._max_level = value\n\n    _min_level = None\n\n    @property\n    def min_level(self):\n        if self._min_level is None:\n            self._min_level = self.index.min_level\n        return self._min_level\n\n    @min_level.setter\n    def min_level(self, value):\n        self._min_level = value\n\n    def define_unit(self, symbol, value, tex_repr=None, offset=None, prefixable=False):\n        \"\"\"\n        Define a new unit and add it to the dataset's unit registry.\n\n        Parameters\n        ----------\n        symbol : string\n            The symbol for the new unit.\n        value : tuple or ~yt.units.yt_array.YTQuantity\n            The definition of the new unit in terms of some other units. For example,\n            one would define a new \"mph\" unit with (1.0, \"mile/hr\")\n        tex_repr : string, optional\n            The LaTeX representation of the new unit. If one is not supplied, it will\n            be generated automatically based on the symbol string.\n        offset : float, optional\n            The default offset for the unit. If not set, an offset of 0 is assumed.\n        prefixable : bool, optional\n            Whether or not the new unit can use SI prefixes. Default: False\n\n        Examples\n        --------\n        >>> ds.define_unit(\"mph\", (1.0, \"mile/hr\"))\n        >>> two_weeks = YTQuantity(14.0, \"days\")\n        >>> ds.define_unit(\"fortnight\", two_weeks)\n        \"\"\"\n        define_unit(\n            symbol,\n            value,\n            tex_repr=tex_repr,\n            offset=offset,\n            prefixable=prefixable,\n            registry=self.unit_registry,\n        )\n\n    def _is_within_domain(self, point) -> bool:\n        assert len(point) == len(self.domain_left_edge)\n        assert point.units.dimensions == un.dimensions.length\n        for i, x in enumerate(point):\n            if self.periodicity[i]:\n                continue\n            if x < self.domain_left_edge[i]:\n                return False\n            if x > self.domain_right_edge[i]:\n                return False\n        return True\n\n\ndef _reconstruct_ds(*args, **kwargs):\n    datasets = ParameterFileStore()\n    ds = datasets.get_ds_hash(*args)\n    return ds\n\n\n@functools.total_ordering\nclass ParticleFile:\n    filename: str\n    file_id: int\n\n    start: int | None = None\n    end: int | None = None\n    total_particles: defaultdict[str, int] | None = None\n\n    def __init__(self, ds, io, filename, file_id, range=None):\n        self.ds = ds\n        self.io = weakref.proxy(io)\n        self.filename = filename\n        self.file_id = file_id\n        if range is None:\n            range = (None, None)\n        self.start, self.end = range\n        self.total_particles = self.io._count_particles(self)\n        # Now we adjust our start/end, in case there are fewer particles than\n        # we realized\n        if self.start is None:\n            self.start = 0\n        self.end = max(self.total_particles.values()) + self.start\n\n    def select(self, selector):  # noqa: B027\n        pass\n\n    def count(self, selector):  # noqa: B027\n        pass\n\n    def _calculate_offsets(self, fields, pcounts):  # noqa: B027\n        pass\n\n    def __lt__(self, other):\n        if self.filename != other.filename:\n            return self.filename < other.filename\n        return self.start < other.start\n\n    def __eq__(self, other):\n        if self.filename != other.filename:\n            return False\n        return self.start == other.start\n\n    def __hash__(self):\n        return hash((self.filename, self.file_id, self.start, self.end))\n\n\nclass ParticleDataset(Dataset):\n    _unit_base = None\n    filter_bbox = False\n    _proj_type = \"particle_proj\"\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        index_order=None,\n        index_filename=None,\n        default_species_fields=None,\n    ):\n        self.index_order = index_order\n        self.index_filename = index_filename\n        super().__init__(\n            filename,\n            dataset_type=dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n"
  },
  {
    "path": "yt/data_objects/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/data_objects/tests/test_add_field.py",
    "content": "import logging\nfrom functools import partial\n\nimport numpy as np\nimport pytest\nimport unyt\n\nimport yt\nfrom yt import derived_field\nfrom yt.fields import local_fields\nfrom yt.testing import fake_random_ds\nfrom yt.utilities.logger import ytLogger\n\n\ndef test_add_field_lambda():\n    ds = fake_random_ds(16)\n\n    ds.add_field(\n        (\"gas\", \"spam\"),\n        lambda field, data: data[\"gas\", \"density\"],\n        sampling_type=\"cell\",\n    )\n\n    # check access\n    ds.all_data()[\"gas\", \"spam\"]\n\n\ndef test_add_field_partial():\n    ds = fake_random_ds(16)\n\n    def _spam(field, data, factor):\n        return factor * data[\"gas\", \"density\"]\n\n    ds.add_field(\n        (\"gas\", \"spam\"),\n        partial(_spam, factor=1),\n        sampling_type=\"cell\",\n    )\n\n    # check access\n    ds.all_data()[\"gas\", \"spam\"]\n\n\ndef test_add_field_arbitrary_callable():\n    ds = fake_random_ds(16)\n\n    class Spam:\n        def __call__(self, field, data):\n            return data[\"gas\", \"density\"]\n\n    ds.add_field((\"gas\", \"spam\"), Spam(), sampling_type=\"cell\")\n\n    # check access\n    ds.all_data()[\"gas\", \"spam\"]\n\n\ndef test_add_field_uncallable():\n    ds = fake_random_ds(16)\n\n    class Spam:\n        pass\n\n    with pytest.raises(TypeError, match=r\"(is not a callable object)$\"):\n        ds.add_field((\"bacon\", \"spam\"), Spam(), sampling_type=\"cell\")\n\n\ndef test_add_field_flipped_signature():\n    # before yt 4.5, the only valid signature was\n    # `function(data)`, but now we allow `function(data, field)`\n    ds = fake_random_ds(16)\n\n    def _spam(data, field):\n        return data[\"gas\", \"density\"]\n\n    ds.add_field((\"bacon\", \"spam\"), _spam, sampling_type=\"cell\")\n\n\ndef test_add_field_signature_v2():\n    # before yt 4.5, the only valid signature was\n    # `function(data)`, but now we allow `function(data)`\n    ds = fake_random_ds(16)\n\n    def _spam(data):\n        return data[\"gas\", \"density\"]\n\n    ds.add_field((\"bacon\", \"spam\"), _spam, sampling_type=\"cell\")\n\n\ndef test_add_field_keyword_only():\n    ds = fake_random_ds(16)\n\n    def _spam(field, *, data):\n        return data[\"gas\", \"density\"]\n\n    ds.add_field(\n        (\"bacon\", \"spam\"),\n        _spam,\n        sampling_type=\"cell\",\n    )\n\n\ndef test_derived_field(monkeypatch):\n    tmp_field_info = local_fields.LocalFieldInfoContainer(None, [], None)\n    monkeypatch.setattr(local_fields, \"local_fields\", tmp_field_info)\n\n    @derived_field(name=\"pressure\", sampling_type=\"cell\", units=\"dyne/cm**2\")\n    def _pressure(data):\n        return (\n            (data.ds.gamma - 1.0)\n            * data[\"gas\", \"density\"]\n            * data[\"gas\", \"specific_thermal_energy\"]\n        )\n\n\n@pytest.mark.parametrize(\n    \"add_field_kwargs\",\n    [\n        # full default: auto unit detection, no (in)validation\n        {},\n        # explicit \"auto\", should be identical to default behaviour\n        {\"units\": \"auto\"},\n        # explicitly requesting dimensionless units\n        {\"units\": \"dimensionless\"},\n        # explicitly requesting dimensionless units (short hand)\n        {\"units\": \"\"},\n        # explictly requesting no dimensions\n        {\"dimensions\": yt.units.dimensionless},\n        # should work with unyt.dimensionless too\n        {\"dimensions\": unyt.dimensionless},\n        # supported short hand\n        {\"dimensions\": \"dimensionless\"},\n    ],\n)\ndef test_dimensionless_field(add_field_kwargs):\n    ds = fake_random_ds(16)\n\n    def _dimensionless_field(data):\n        return data[\"gas\", \"density\"] / data[\"gas\", \"density\"].units\n\n    ds.add_field(\n        name=(\"gas\", \"dimensionless_density\"),\n        function=_dimensionless_field,\n        sampling_type=\"local\",\n        **add_field_kwargs,\n    )\n    # check access\n    ds.all_data()[\"gas\", \"dimensionless_density\"]\n\n\ndef test_add_field_quick():\n    ds = fake_random_ds(16)\n\n    # Test subtractions\n    for field in (\n        -ds.fields.gas.density,\n        -1 * ds.fields.gas.density,\n        ds.fields.gas.density - 2 * ds.fields.gas.density,\n        ds.fields.gas.density * (-1),\n    ):\n        ds.add_field((\"gas\", \"-density\"), field, force_override=True)\n        np.testing.assert_allclose(ds.r[\"gas\", \"-density\"], -ds.r[\"gas\", \"density\"])\n\n    # Test additions\n    for field in (\n        ds.fields.index.ones + 1,\n        1 + ds.fields.index.ones,\n    ):\n        ds.add_field((\"index\", \"two\"), field, force_override=True)\n        np.testing.assert_allclose(ds.r[\"index\", \"two\"], 2)\n\n    # Test multiplications\n    for field in (ds.fields.gas.density * 2, 2 * ds.fields.gas.density):\n        ds.add_field((\"gas\", \"twodensity\"), field, force_override=True)\n        np.testing.assert_allclose(\n            ds.r[\"gas\", \"twodensity\"], ds.r[\"gas\", \"density\"] * 2\n        )\n\n    # Test divisions\n    for field in (ds.fields.gas.density / 2, 0.5 * ds.fields.gas.density):\n        ds.add_field((\"gas\", \"halfdensity\"), field, force_override=True)\n        np.testing.assert_allclose(\n            ds.r[\"gas\", \"halfdensity\"], ds.r[\"gas\", \"density\"] / 2\n        )\n\n    # Test right division\n    for field in (1 / ds.fields.gas.density,):\n        ds.add_field((\"gas\", \"one_over_density\"), field, force_override=True)\n        np.testing.assert_allclose(\n            ds.r[\"gas\", \"one_over_density\"], 1 / ds.r[\"gas\", \"density\"]\n        )\n\n\ndef test_add_field_quick_syntax2():\n    fields = (\"density\", \"temperature\")\n    units = (\"g/cm**3\", \"K\")\n    ds = fake_random_ds(16, fields=fields, units=units)\n\n    # Left multiplication\n    ds.fields.gas.density_two = ds.fields.gas.density * 2\n    np.testing.assert_allclose(ds.r[\"gas\", \"density_two\"], ds.r[\"gas\", \"density\"] * 2)\n    ds.fields.gas.density_two2 = ds.fields.gas.density + ds.fields.gas.density\n    np.testing.assert_allclose(ds.r[\"gas\", \"density_two2\"], ds.r[\"gas\", \"density\"] * 2)\n\n    # Right multiplication\n    ds.fields.gas.two_density = 2 * ds.fields.gas.density\n    np.testing.assert_allclose(ds.r[\"gas\", \"two_density\"], ds.r[\"gas\", \"density\"] * 2)\n\n    # Left division\n    ds.fields.gas.half_density = ds.fields.gas.density / 2\n    np.testing.assert_allclose(ds.r[\"gas\", \"half_density\"], ds.r[\"gas\", \"density\"] / 2)\n    ds.fields.gas.density_over_temperature = (\n        ds.fields.gas.density / ds.fields.gas.temperature\n    )\n    np.testing.assert_allclose(\n        ds.r[\"gas\", \"density_over_temperature\"],\n        ds.r[\"gas\", \"density\"] / ds.r[\"gas\", \"temperature\"],\n    )\n\n    # Right division\n    ds.fields.gas.one_over_density = 1 / ds.fields.gas.density\n    np.testing.assert_allclose(\n        ds.r[\"gas\", \"one_over_density\"], 1 / ds.r[\"gas\", \"density\"]\n    )\n\n    # Subtraction\n    ds.fields.gas.neg_density = -ds.fields.gas.density\n    np.testing.assert_allclose(ds.r[\"gas\", \"neg_density\"], -ds.r[\"gas\", \"density\"])\n    ds.fields.gas.density_minus_twice_density = (\n        ds.fields.gas.density - 2 * ds.fields.gas.density\n    )\n    np.testing.assert_allclose(\n        ds.r[\"gas\", \"density_minus_twice_density\"],\n        -ds.r[\"gas\", \"density\"],\n    )\n\n    # Complex expression\n    ds.fields.gas.kbT_per_V = (\n        ds.fields.gas.temperature * ds.units.kb / ds.fields.gas.volume\n    )\n    np.testing.assert_allclose(\n        ds.r[\"gas\", \"kbT_per_V\"],\n        ds.r[\"gas\", \"temperature\"] * ds.units.kb / ds.r[\"gas\", \"volume\"],\n    )\n\n    # Returning boolean\n    dx_min = ds.r[\"index\", \"dx\"].min()\n    ds.fields.gas.smallest_cells = ds.fields.gas.dx == dx_min\n    np.testing.assert_allclose(\n        ds.r[\"gas\", \"smallest_cells\"].value, (dx_min == ds.r[\"gas\", \"dx\"])\n    )\n\n\n@pytest.fixture()\ndef capturable_logger(caplog):\n    \"\"\"\n    This set the minimal conditions to make pytest's caplog fixture usable.\n    \"\"\"\n\n    propagate = ytLogger.propagate\n    ytLogger.propagate = True\n\n    with caplog.at_level(logging.WARNING, \"yt\"):\n        yield\n\n    ytLogger.propagate = propagate\n\n\n@pytest.mark.usefixtures(\"capturable_logger\")\ndef test_add_field_quick_syntax_warnings(caplog):\n    # Make sure we get a warning when overriding\n    # an existing field with a different function\n    ds = fake_random_ds(16)\n\n    warn_str = \"Field ('gas', 'density2') already exists. To override use `force_override=True`\"\n    # First time, no warning\n    ds.add_field((\"gas\", \"density2\"), ds.fields.gas.density * 2)\n    assert warn_str not in caplog.text\n\n    # Second time, warning\n    caplog.clear()\n    ds.add_field((\"gas\", \"density2\"), ds.fields.gas.density * 2)\n    assert warn_str in caplog.text\n\n    warn_str = \"Field ('gas', 'density3') already exists. To override use `force_override=True`\"\n    # Third time, new definition, no warning\n    caplog.clear()\n    ds.fields.gas.density3 = ds.fields.gas.density * 3\n    assert warn_str not in caplog.text\n    caplog.clear()\n\n    # Fourth time: new definition, warning!\n    caplog.clear()\n    ds.fields.gas.density3 = ds.fields.gas.density * 3\n    assert warn_str in caplog.text\n"
  },
  {
    "path": "yt/data_objects/tests/test_bbox.py",
    "content": "# Some tests for finding bounding boxes\n\nimport numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import assert_allclose_units, fake_amr_ds, fake_octree_ds\n\n\ndef test_object_bbox():\n    ds = fake_amr_ds()\n    reg = ds.box(\n        ds.domain_left_edge + 0.5 * ds.domain_width,\n        ds.domain_right_edge - 0.5 * ds.domain_width,\n    )\n    le, re = reg.get_bbox()\n    assert_equal(le, ds.domain_left_edge + 0.5 * ds.domain_width)\n    assert_equal(re, ds.domain_right_edge - 0.5 * ds.domain_width)\n    sp = ds.sphere(\"c\", (0.1, \"unitary\"))\n    le, re = sp.get_bbox()\n    assert_equal(le, -sp.radius + sp.center)\n    assert_equal(re, sp.radius + sp.center)\n    dk = ds.disk(\"c\", [1, 1, 0], (0.25, \"unitary\"), (0.25, \"unitary\"))\n    le, re = dk.get_bbox()\n    le0 = ds.arr(\n        [0.5 - 0.25 * np.sqrt(2.0), 0.5 - 0.25 * np.sqrt(2.0), 0.25], \"code_length\"\n    )\n    re0 = ds.arr(\n        [0.5 + 0.25 * np.sqrt(2.0), 0.5 + 0.25 * np.sqrt(2.0), 0.75], \"code_length\"\n    )\n    assert_allclose_units(le, le0)\n    assert_allclose_units(re, re0)\n    ep = ds.ellipsoid(\"c\", 0.3, 0.2, 0.1, np.array([0.1, 0.1, 0.1]), 0.2)\n    le, re = ep.get_bbox()\n    assert_equal(le, -ds.quan(0.3, \"code_length\") + sp.center)\n    assert_equal(re, ds.quan(0.3, \"code_length\") + sp.center)\n    spb = ds.sphere(\n        ds.domain_center - ds.quan(0.1, \"code_length\"), (0.1, \"code_length\")\n    )\n    regb = ds.box(ds.domain_center, ds.domain_center + ds.quan(0.2, \"code_length\"))\n    br1 = spb & regb\n    br2 = spb | regb\n    br3 = spb ^ regb\n    br4 = ~regb\n    le1, re1 = br1.get_bbox()\n    le2, re2 = br2.get_bbox()\n    le3, re3 = br3.get_bbox()\n    le4, re4 = br4.get_bbox()\n    le0 = ds.arr([0.3, 0.3, 0.3], \"code_length\")\n    re0 = ds.arr([0.7, 0.7, 0.7], \"code_length\")\n    assert_allclose_units(le1, le0)\n    assert_allclose_units(re1, re0)\n    assert_allclose_units(le2, le0)\n    assert_allclose_units(re2, re0)\n    assert_allclose_units(le3, le0)\n    assert_allclose_units(re3, re0)\n    assert_equal(le4, regb.left_edge)\n    assert_equal(re4, regb.right_edge)\n\n\ndef test_covering_grid_bbox():\n    ds = fake_octree_ds(num_zones=32)\n\n    cg = ds.covering_grid(level=0, left_edge=[0.3, 0.3, 0.3], dims=[8, 8, 8])\n\n    # Make a covering grid with a data source\n    sp = ds.sphere(\n        [0.5, 0.5, 0.5],\n        0.15,\n    )\n    cgds = ds.covering_grid(\n        level=0,\n        left_edge=[0.3, 0.3, 0.3],\n        dims=[8, 8, 8],\n        data_source=sp,\n    )\n\n    # The bounding boxes of the two covering grids should be the same\n    cg_bbox = cg.get_bbox()\n    cgds_bbox = cgds.get_bbox()\n    assert_equal(cg_bbox, cgds_bbox)\n\n    # The bounding box of the cg's data source should be the left edge of sp and right edge of cgds\n    cgds_ds_bbox = cgds._data_source.get_bbox()\n    le_sp, re_sp = sp.get_bbox()\n\n    assert_equal(le_sp, cgds_ds_bbox[0])\n    assert_equal(cgds_bbox[1], cgds_ds_bbox[1])\n\n\ndef test_intersection_bbox():\n    ds = fake_octree_ds(num_zones=32)\n\n    # intersect a region and a sphere smaller than the region, get back\n    # the bbox of the sphere\n    sp1 = ds.sphere(ds.domain_center, (0.1, \"unitary\"))\n    reg = ds.region(ds.domain_center, ds.domain_left_edge, ds.domain_right_edge)\n\n    le, re = ds.intersection((sp1, reg)).get_bbox()\n    le_sp, re_sp = sp1.get_bbox()\n    assert_equal(le_sp, le)\n    assert_equal(re_sp, re)\n\n    # check for no error with a single data source\n    le, re = ds.intersection((reg,)).get_bbox()\n    assert_equal(le, ds.domain_left_edge)\n    assert_equal(re, ds.domain_right_edge)\n\n    # check some overlapping regions shifted along one dimension\n    c = ds.domain_center - ds.arr((0.3, 0, 0), \"code_length\")\n    le = c - ds.quan(0.2, \"code_length\")\n    re = c + ds.quan(0.2, \"code_length\")\n    r1 = ds.region(c, le, re)\n    offset = ds.arr((0.1, 0, 0), \"code_length\")\n    r2 = ds.region(c + offset, le + offset, re + offset)\n    r3 = ds.region(c + 2 * offset, le + 2 * offset, re + 2 * offset)\n\n    r_int = ds.intersection((r1, r2, r3))\n    le, re = r_int.get_bbox()\n    assert_equal(le, ds.arr((0.2, 0.3, 0.3), \"code_length\"))\n    assert_equal(re, ds.arr((0.4, 0.7, 0.7), \"code_length\"))\n"
  },
  {
    "path": "yt/data_objects/tests/test_boolean_regions.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_equal\n\nfrom yt.testing import fake_amr_ds\n\n# We use morton indices in this test because they are single floating point\n# values that uniquely identify each cell.  That's a convenient way to compare\n# inclusion in set operations, since there are no duplicates.\n\n\ndef test_boolean_spheres_no_overlap():\n    r\"\"\"Test to make sure that boolean objects (spheres, no overlap)\n    behave the way we expect.\n\n    Test non-overlapping spheres. This also checks that the original spheres\n    don't change as part of constructing the booleans.\n    \"\"\"\n    ds = fake_amr_ds()\n    sp1 = ds.sphere([0.25, 0.25, 0.25], 0.15)\n    sp2 = ds.sphere([0.75, 0.75, 0.75], 0.15)\n    # Store the original indices\n    i1 = sp1[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = sp2[\"index\", \"morton_index\"]\n    i2.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = sp1 & sp2\n    bo2 = sp1 - sp2\n    bo3 = sp1 | sp2  # also works with +\n    bo4 = ds.union([sp1, sp2])\n    bo5 = ds.intersection([sp1, sp2])\n    # This makes sure the original containers didn't change.\n    new_i1 = sp1[\"index\", \"morton_index\"]\n    new_i1.sort()\n    new_i2 = sp2[\"index\", \"morton_index\"]\n    new_i2.sort()\n    assert_array_equal(new_i1, i1)\n    assert_array_equal(new_i2, i2)\n    # Now make sure the indices also behave as we expect.\n    empty = np.array([])\n    assert_array_equal(bo1[\"index\", \"morton_index\"], empty)\n    assert_array_equal(bo5[\"index\", \"morton_index\"], empty)\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    assert_array_equal(b2, i1)\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b3, ii)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    assert_array_equal(b4, ii)\n    bo6 = sp1 ^ sp2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_spheres_overlap():\n    r\"\"\"Test to make sure that boolean objects (spheres, overlap)\n    behave the way we expect.\n\n    Test overlapping spheres.\n    \"\"\"\n    ds = fake_amr_ds()\n    sp1 = ds.sphere([0.45, 0.45, 0.45], 0.15)\n    sp2 = ds.sphere([0.55, 0.55, 0.55], 0.15)\n    # Get indices of both.\n    i1 = sp1[\"index\", \"morton_index\"]\n    i2 = sp2[\"index\", \"morton_index\"]\n    # Make some booleans\n    bo1 = sp1 & sp2\n    bo2 = sp1 - sp2\n    bo3 = sp1 | sp2\n    bo4 = ds.union([sp1, sp2])\n    bo5 = ds.intersection([sp1, sp2])\n    # Now make sure the indices also behave as we expect.\n    lens = np.intersect1d(i1, i2)\n    apple = np.setdiff1d(i1, i2)\n    both = np.union1d(i1, i2)\n    b1 = bo1[\"index\", \"morton_index\"]\n    b1.sort()\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b1, lens)\n    assert_array_equal(b2, apple)\n    assert_array_equal(b3, both)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    assert_array_equal(b1, b5)\n    bo6 = sp1 ^ sp2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_regions_no_overlap():\n    r\"\"\"Test to make sure that boolean objects (regions, no overlap)\n    behave the way we expect.\n\n    Test non-overlapping regions. This also checks that the original regions\n    don't change as part of constructing the booleans.\n    \"\"\"\n    ds = fake_amr_ds()\n    re1 = ds.region([0.25] * 3, [0.2] * 3, [0.3] * 3)\n    re2 = ds.region([0.65] * 3, [0.6] * 3, [0.7] * 3)\n    # Store the original indices\n    i1 = re1[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = re2[\"index\", \"morton_index\"]\n    i2.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = re1 & re2\n    bo2 = re1 - re2\n    bo3 = re1 | re2\n    bo4 = ds.union([re1, re2])\n    bo5 = ds.intersection([re1, re2])\n    # This makes sure the original containers didn't change.\n    new_i1 = re1[\"index\", \"morton_index\"]\n    new_i1.sort()\n    new_i2 = re2[\"index\", \"morton_index\"]\n    new_i2.sort()\n    assert_array_equal(new_i1, i1)\n    assert_array_equal(new_i2, i2)\n    # Now make sure the indices also behave as we expect.\n    empty = np.array([])\n    assert_array_equal(bo1[\"index\", \"morton_index\"], empty)\n    assert_array_equal(bo5[\"index\", \"morton_index\"], empty)\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    assert_array_equal(b2, i1)\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b3, ii)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    bo6 = re1 ^ re2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_regions_overlap():\n    r\"\"\"Test to make sure that boolean objects (regions, overlap)\n    behave the way we expect.\n\n    Test overlapping regions.\n    \"\"\"\n    ds = fake_amr_ds()\n    re1 = ds.region([0.55] * 3, [0.5] * 3, [0.6] * 3)\n    re2 = ds.region([0.6] * 3, [0.55] * 3, [0.65] * 3)\n    # Get indices of both.\n    i1 = re1[\"index\", \"morton_index\"]\n    i2 = re2[\"index\", \"morton_index\"]\n    # Make some booleans\n    bo1 = re1 & re2\n    bo2 = re1 - re2\n    bo3 = re1 | re2\n    bo4 = ds.union([re1, re2])\n    bo5 = ds.intersection([re1, re2])\n    # Now make sure the indices also behave as we expect.\n    cube = np.intersect1d(i1, i2)\n    bite_cube = np.setdiff1d(i1, i2)\n    both = np.union1d(i1, i2)\n    b1 = bo1[\"index\", \"morton_index\"]\n    b1.sort()\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b1, cube)\n    assert_array_equal(b2, bite_cube)\n    assert_array_equal(b3, both)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    assert_array_equal(b1, b5)\n    bo6 = re1 ^ re2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_cylinders_no_overlap():\n    r\"\"\"Test to make sure that boolean objects (cylinders, no overlap)\n    behave the way we expect.\n\n    Test non-overlapping cylinders. This also checks that the original cylinders\n    don't change as part of constructing the booleans.\n    \"\"\"\n    ds = fake_amr_ds()\n    cyl1 = ds.disk([0.25] * 3, [1, 0, 0], 0.1, 0.1)\n    cyl2 = ds.disk([0.75] * 3, [1, 0, 0], 0.1, 0.1)\n    # Store the original indices\n    i1 = cyl1[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = cyl2[\"index\", \"morton_index\"]\n    i2.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = cyl1 & cyl2\n    bo2 = cyl1 - cyl2\n    bo3 = cyl1 | cyl2\n    bo4 = ds.union([cyl1, cyl2])\n    bo5 = ds.intersection([cyl1, cyl2])\n    # This makes sure the original containers didn't change.\n    new_i1 = cyl1[\"index\", \"morton_index\"]\n    new_i1.sort()\n    new_i2 = cyl2[\"index\", \"morton_index\"]\n    new_i2.sort()\n    assert_array_equal(new_i1, i1)\n    assert_array_equal(new_i2, i2)\n    # Now make sure the indices also behave as we expect.\n    empty = np.array([])\n    assert_array_equal(bo1[\"index\", \"morton_index\"], empty)\n    assert_array_equal(bo5[\"index\", \"morton_index\"], empty)\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    assert_array_equal(b2, i1)\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b3, ii)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    bo6 = cyl1 ^ cyl2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_cylinders_overlap():\n    r\"\"\"Test to make sure that boolean objects (cylinders, overlap)\n    behave the way we expect.\n\n    Test overlapping cylinders.\n    \"\"\"\n    ds = fake_amr_ds()\n    cyl1 = ds.disk([0.45] * 3, [1, 0, 0], 0.2, 0.2)\n    cyl2 = ds.disk([0.55] * 3, [1, 0, 0], 0.2, 0.2)\n    # Get indices of both.\n    i1 = cyl1[\"index\", \"morton_index\"]\n    i2 = cyl2[\"index\", \"morton_index\"]\n    # Make some booleans\n    bo1 = cyl1 & cyl2\n    bo2 = cyl1 - cyl2\n    bo3 = cyl1 | cyl2\n    bo4 = ds.union([cyl1, cyl2])\n    bo5 = ds.intersection([cyl1, cyl2])\n    # Now make sure the indices also behave as we expect.\n    vlens = np.intersect1d(i1, i2)\n    bite_disk = np.setdiff1d(i1, i2)\n    both = np.union1d(i1, i2)\n    b1 = bo1[\"index\", \"morton_index\"]\n    b1.sort()\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b1, vlens)\n    assert_array_equal(b2, bite_disk)\n    assert_array_equal(b3, both)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    assert_array_equal(b1, b5)\n    bo6 = cyl1 ^ cyl2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n    del ds\n\n\ndef test_boolean_ellipsoids_no_overlap():\n    r\"\"\"Test to make sure that boolean objects (ellipsoids, no overlap)\n    behave the way we expect.\n\n    Test non-overlapping ellipsoids. This also checks that the original\n    ellipsoids don't change as part of constructing the booleans.\n    \"\"\"\n    ds = fake_amr_ds()\n    ell1 = ds.ellipsoid([0.25] * 3, 0.05, 0.05, 0.05, np.array([0.1] * 3), 0.1)\n    ell2 = ds.ellipsoid([0.75] * 3, 0.05, 0.05, 0.05, np.array([0.1] * 3), 0.1)\n    # Store the original indices\n    i1 = ell1[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = ell2[\"index\", \"morton_index\"]\n    i2.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = ell1 & ell2\n    bo2 = ell1 - ell2\n    bo3 = ell1 | ell2\n    bo4 = ds.union([ell1, ell2])\n    bo5 = ds.intersection([ell1, ell2])\n    # This makes sure the original containers didn't change.\n    new_i1 = ell1[\"index\", \"morton_index\"]\n    new_i1.sort()\n    new_i2 = ell2[\"index\", \"morton_index\"]\n    new_i2.sort()\n    assert_array_equal(new_i1, i1)\n    assert_array_equal(new_i2, i2)\n    # Now make sure the indices also behave as we expect.\n    empty = np.array([])\n    assert_array_equal(bo1[\"index\", \"morton_index\"], empty)\n    assert_array_equal(bo5[\"index\", \"morton_index\"], empty)\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    assert_array_equal(b2, i1)\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b3, ii)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    bo6 = ell1 ^ ell2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_ellipsoids_overlap():\n    r\"\"\"Test to make sure that boolean objects (ellipsoids, overlap)\n    behave the way we expect.\n\n    Test overlapping ellipsoids.\n    \"\"\"\n    ds = fake_amr_ds()\n    ell1 = ds.ellipsoid([0.45] * 3, 0.05, 0.05, 0.05, np.array([0.1] * 3), 0.1)\n    ell2 = ds.ellipsoid([0.55] * 3, 0.05, 0.05, 0.05, np.array([0.1] * 3), 0.1)\n    # Get indices of both.\n    i1 = ell1[\"index\", \"morton_index\"]\n    i2 = ell2[\"index\", \"morton_index\"]\n    # Make some booleans\n    bo1 = ell1 & ell2\n    bo2 = ell1 - ell2\n    bo3 = ell1 | ell2\n    bo4 = ds.union([ell1, ell2])\n    bo5 = ds.intersection([ell1, ell2])\n    # Now make sure the indices also behave as we expect.\n    overlap = np.intersect1d(i1, i2)\n    diff = np.setdiff1d(i1, i2)\n    both = np.union1d(i1, i2)\n    b1 = bo1[\"index\", \"morton_index\"]\n    b1.sort()\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b1, overlap)\n    assert_array_equal(b2, diff)\n    assert_array_equal(b3, both)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    assert_array_equal(b1, b5)\n    bo6 = ell1 ^ ell2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_mix_periodicity():\n    r\"\"\"Test that a hybrid boolean region behaves as we expect.\n\n    This also tests nested logic and that periodicity works.\n    \"\"\"\n    ds = fake_amr_ds()\n    re = ds.region([0.5] * 3, [0.0] * 3, [1] * 3)  # whole thing\n    sp = ds.sphere([0.95] * 3, 0.3)  # wraps around\n    cyl = ds.disk([0.05] * 3, [1, 1, 1], 0.1, 0.4)  # wraps around\n    # Get original indices\n    rei = re[\"index\", \"morton_index\"]\n    spi = sp[\"index\", \"morton_index\"]\n    cyli = cyl[\"index\", \"morton_index\"]\n    # Make some booleans\n    # whole box minux spherical bites at corners\n    bo1 = re - sp\n    # sphere plus cylinder\n    bo2 = sp | cyl\n    # a jumble, the region minus the sp+cyl\n    bo3 = re - (sp | cyl)\n    # Now make sure the indices also behave as we expect.\n    bo4 = ds.union([re, sp, cyl])\n    bo5 = ds.intersection([re, sp, cyl])\n    expect = np.setdiff1d(rei, spi)\n    ii = bo1[\"index\", \"morton_index\"]\n    ii.sort()\n    assert_array_equal(expect, ii)\n    #\n    expect = np.union1d(spi, cyli)\n    ii = bo2[\"index\", \"morton_index\"]\n    ii.sort()\n    assert_array_equal(expect, ii)\n    #\n    expect = np.union1d(spi, cyli)\n    expect = np.setdiff1d(rei, expect)\n    ii = bo3[\"index\", \"morton_index\"]\n    ii.sort()\n    assert_array_equal(expect, ii)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    ii = np.union1d(np.union1d(rei, cyli), spi)\n    ii.sort()\n    assert_array_equal(ii, b4)\n    ii = np.intersect1d(np.intersect1d(rei, cyli), spi)\n    ii.sort()\n    assert_array_equal(ii, b5)\n\n    bo6 = (re ^ sp) ^ cyl\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(np.setxor1d(rei, spi), cyli))\n\n\ndef test_boolean_ray_region_no_overlap():\n    r\"\"\"Test to make sure that boolean objects (ray, region, no overlap)\n    behave the way we expect.\n\n    Test non-overlapping ray and region. This also checks that the original\n    objects don't change as part of constructing the booleans.\n    \"\"\"\n    ds = fake_amr_ds()\n    re = ds.box([0.25] * 3, [0.75] * 3)\n    ra = ds.ray([0.1] * 3, [0.1, 0.1, 0.9])\n    # Store the original indices\n    i1 = re[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = ra[\"index\", \"morton_index\"]\n    i2.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = re & ra\n    bo2 = re - ra\n    bo3 = re | ra\n    bo4 = ds.union([re, ra])\n    bo5 = ds.intersection([re, ra])\n    # This makes sure the original containers didn't change.\n    new_i1 = re[\"index\", \"morton_index\"]\n    new_i1.sort()\n    new_i2 = ra[\"index\", \"morton_index\"]\n    new_i2.sort()\n    assert_array_equal(new_i1, i1)\n    assert_array_equal(new_i2, i2)\n    # Now make sure the indices also behave as we expect.\n    empty = np.array([])\n    assert_array_equal(bo1[\"index\", \"morton_index\"], empty)\n    assert_array_equal(bo5[\"index\", \"morton_index\"], empty)\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    assert_array_equal(b2, i1)\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b3, ii)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    bo6 = re ^ ra\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_ray_region_overlap():\n    r\"\"\"Test to make sure that boolean objects (ray, region, overlap)\n    behave the way we expect.\n\n    Test overlapping ray and region. This also checks that the original\n    objects don't change as part of constructing the booleans.\n    \"\"\"\n    ds = fake_amr_ds()\n    re = ds.box([0.25] * 3, [0.75] * 3)\n    ra = ds.ray([0] * 3, [1] * 3)\n    # Get indices of both.\n    i1 = re[\"index\", \"morton_index\"]\n    i2 = ra[\"index\", \"morton_index\"]\n    # Make some booleans\n    bo1 = re & ra\n    bo2 = re - ra\n    bo3 = re | ra\n    bo4 = ds.union([re, ra])\n    bo5 = ds.intersection([re, ra])\n    # Now make sure the indices also behave as we expect.\n    short_line = np.intersect1d(i1, i2)\n    cube_minus_line = np.setdiff1d(i1, i2)\n    both = np.union1d(i1, i2)\n    b1 = bo1[\"index\", \"morton_index\"]\n    b1.sort()\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b1, short_line)\n    assert_array_equal(b2, cube_minus_line)\n    assert_array_equal(b3, both)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    assert_array_equal(b1, b5)\n    bo6 = re ^ ra\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_rays_no_overlap():\n    r\"\"\"Test to make sure that boolean objects (rays, no overlap)\n    behave the way we expect.\n\n    Test non-overlapping rays.\n    \"\"\"\n    ds = fake_amr_ds()\n    ra1 = ds.ray([0, 0, 0], [0, 0, 1])\n    ra2 = ds.ray([1, 0, 0], [1, 0, 1])\n    # Store the original indices\n    i1 = ra1[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = ra2[\"index\", \"morton_index\"]\n    i2.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = ra1 & ra2\n    bo2 = ra1 - ra2\n    bo3 = ra1 | ra2\n    bo4 = ds.union([ra1, ra2])\n    bo5 = ds.intersection([ra1, ra2])\n    # This makes sure the original containers didn't change.\n    new_i1 = ra1[\"index\", \"morton_index\"]\n    new_i1.sort()\n    new_i2 = ra2[\"index\", \"morton_index\"]\n    new_i2.sort()\n    assert_array_equal(new_i1, i1)\n    assert_array_equal(new_i2, i2)\n    # Now make sure the indices also behave as we expect.\n    empty = np.array([])\n    assert_array_equal(bo1[\"index\", \"morton_index\"], empty)\n    assert_array_equal(bo5[\"index\", \"morton_index\"], empty)\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    assert_array_equal(b2, i1)\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b3, ii)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    bo6 = ra1 ^ ra2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_rays_overlap():\n    r\"\"\"Test to make sure that boolean objects (rays, overlap)\n    behave the way we expect.\n\n    Test non-overlapping rays.\n    \"\"\"\n    ds = fake_amr_ds()\n    ra1 = ds.ray([0] * 3, [1] * 3)\n    ra2 = ds.ray([0] * 3, [0.5] * 3)\n    # Get indices of both.\n    i1 = ra1[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = ra2[\"index\", \"morton_index\"]\n    i2.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = ra1 & ra2\n    bo2 = ra1 - ra2\n    bo3 = ra1 | ra2\n    bo4 = ds.union([ra1, ra2])\n    bo5 = ds.intersection([ra1, ra2])\n    # Now make sure the indices also behave as we expect.\n    short_line = np.intersect1d(i1, i2)\n    short_line_b = np.setdiff1d(i1, i2)\n    full_line = np.union1d(i1, i2)\n    b1 = bo1[\"index\", \"morton_index\"]\n    b1.sort()\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b1, short_line)\n    assert_array_equal(b2, short_line_b)\n    assert_array_equal(b3, full_line)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, i1)\n    assert_array_equal(b3, b4)\n    assert_array_equal(b1, b5)\n    bo6 = ra1 ^ ra2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_slices_no_overlap():\n    r\"\"\"Test to make sure that boolean objects (slices, no overlap)\n    behave the way we expect.\n\n    Test non-overlapping slices. This also checks that the original regions\n    don't change as part of constructing the booleans.\n    \"\"\"\n    ds = fake_amr_ds()\n    sl1 = ds.r[:, :, 0.25]\n    sl2 = ds.r[:, :, 0.75]\n    # Store the original indices\n    i1 = sl1[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = sl2[\"index\", \"morton_index\"]\n    i2.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = sl1 & sl2\n    bo2 = sl1 - sl2\n    bo3 = sl1 | sl2\n    bo4 = ds.union([sl1, sl2])\n    bo5 = ds.intersection([sl1, sl2])\n    # This makes sure the original containers didn't change.\n    new_i1 = sl1[\"index\", \"morton_index\"]\n    new_i1.sort()\n    new_i2 = sl2[\"index\", \"morton_index\"]\n    new_i2.sort()\n    assert_array_equal(new_i1, i1)\n    assert_array_equal(new_i2, i2)\n    # Now make sure the indices also behave as we expect.\n    empty = np.array([])\n    assert_array_equal(bo1[\"index\", \"morton_index\"], empty)\n    assert_array_equal(bo5[\"index\", \"morton_index\"], empty)\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    assert_array_equal(b2, i1)\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b3, ii)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    bo6 = sl1 ^ sl2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_slices_overlap():\n    r\"\"\"Test to make sure that boolean objects (slices, overlap)\n    behave the way we expect.\n\n    Test overlapping slices.\n    \"\"\"\n    ds = fake_amr_ds()\n    sl1 = ds.r[:, :, 0.25]\n    sl2 = ds.r[:, 0.75, :]\n    # Get indices of both.\n    i1 = sl1[\"index\", \"morton_index\"]\n    i2 = sl2[\"index\", \"morton_index\"]\n    # Make some booleans\n    bo1 = sl1 & sl2\n    bo2 = sl1 - sl2\n    bo3 = sl1 | sl2\n    bo4 = ds.union([sl1, sl2])\n    bo5 = ds.intersection([sl1, sl2])\n    # Now make sure the indices also behave as we expect.\n    line = np.intersect1d(i1, i2)\n    orig = np.setdiff1d(i1, i2)\n    both = np.union1d(i1, i2)\n    b1 = bo1[\"index\", \"morton_index\"]\n    b1.sort()\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b1, line)\n    assert_array_equal(b2, orig)\n    assert_array_equal(b3, both)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    assert_array_equal(b1, b5)\n    bo6 = sl1 ^ sl2\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_ray_slice_no_overlap():\n    r\"\"\"Test to make sure that boolean objects (ray, slice, no overlap)\n    behave the way we expect.\n\n    Test non-overlapping ray and slice. This also checks that the original\n    regions don't change as part of constructing the booleans.\n    \"\"\"\n    ds = fake_amr_ds()\n    sl = ds.r[:, :, 0.25]\n    ra = ds.ray([0] * 3, [0, 1, 0])\n    # Store the original indices\n    i1 = sl[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = ra[\"index\", \"morton_index\"]\n    i2.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = sl & ra\n    bo2 = sl - ra\n    bo3 = sl | ra\n    bo4 = ds.union([sl, ra])\n    bo5 = ds.intersection([sl, ra])\n    # This makes sure the original containers didn't change.\n    new_i1 = sl[\"index\", \"morton_index\"]\n    new_i1.sort()\n    new_i2 = ra[\"index\", \"morton_index\"]\n    new_i2.sort()\n    assert_array_equal(new_i1, i1)\n    assert_array_equal(new_i2, i2)\n    # Now make sure the indices also behave as we expect.\n    empty = np.array([])\n    assert_array_equal(bo1[\"index\", \"morton_index\"], empty)\n    assert_array_equal(bo5[\"index\", \"morton_index\"], empty)\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    assert_array_equal(b2, i1)\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b3, ii)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, b4)\n    bo6 = sl ^ ra\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n\n\ndef test_boolean_ray_slice_overlap():\n    r\"\"\"Test to make sure that boolean objects (rays and slices, overlap)\n    behave the way we expect.\n\n    Test overlapping rays and slices.\n    \"\"\"\n    ds = fake_amr_ds()\n    sl = ds.r[:, :, 0.25]\n    ra = ds.ray([0, 0, 0.25], [0, 1, 0.25])\n    # Get indices of both.\n    i1 = sl[\"index\", \"morton_index\"]\n    i1.sort()\n    i2 = ra[\"index\", \"morton_index\"]\n    i1.sort()\n    ii = np.concatenate((i1, i2))\n    ii.sort()\n    # Make some booleans\n    bo1 = sl & ra\n    bo2 = sl - ra\n    bo3 = sl | ra\n    bo4 = ds.union([sl, ra])\n    bo5 = ds.intersection([sl, ra])\n    # Now make sure the indices also behave as we expect.\n    line = np.intersect1d(i1, i2)\n    sheet_minus_line = np.setdiff1d(i1, i2)\n    sheet = np.union1d(i1, i2)\n    b1 = bo1[\"index\", \"morton_index\"]\n    b1.sort()\n    b2 = bo2[\"index\", \"morton_index\"]\n    b2.sort()\n    b3 = bo3[\"index\", \"morton_index\"]\n    b3.sort()\n    assert_array_equal(b1, line)\n    assert_array_equal(b2, sheet_minus_line)\n    assert_array_equal(b3, sheet)\n    b4 = bo4[\"index\", \"morton_index\"]\n    b4.sort()\n    b5 = bo5[\"index\", \"morton_index\"]\n    b5.sort()\n    assert_array_equal(b3, i1)\n    assert_array_equal(b3, b4)\n    assert_array_equal(b1, b5)\n    bo6 = sl ^ ra\n    b6 = bo6[\"index\", \"morton_index\"]\n    b6.sort()\n    assert_array_equal(b6, np.setxor1d(i1, i2))\n"
  },
  {
    "path": "yt/data_objects/tests/test_center_squeeze.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.testing import fake_amr_ds, fake_particle_ds, fake_random_ds\n\n\ndef test_center_squeeze():\n    # checks that the center is reshaped correctly\n\n    # create and test amr, random and particle data\n    check_single_ds(fake_amr_ds(fields=(\"Density\",), units=(\"g/cm**3\",)))\n    check_single_ds(fake_random_ds(16, fields=(\"Density\",), units=(\"g/cm**3\",)))\n    check_single_ds(fake_particle_ds(npart=100))\n\n\ndef check_single_ds(ds):\n    # checks that the center\n    center = ds.domain_center  # reference center value\n    for test_shape in [(1, 3), (1, 1, 3)]:\n        new_center = center.reshape(test_shape)\n        assert_equal(ds.sphere(new_center, 0.25).center, center)\n        assert_equal(ds.slice(0, 0.25, center=new_center).center, center)\n        assert_equal(\n            ds.region(new_center, [-0.25, -0.25, -0.25], [0.25, 0.25, 0.25]).center,\n            center,\n        )\n"
  },
  {
    "path": "yt/data_objects/tests/test_chunking.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.testing import fake_random_ds\nfrom yt.units._numpy_wrapper_functions import uconcatenate\n\n\ndef _get_dobjs(c):\n    dobjs = [\n        (\"sphere\", (\"center\", (1.0, \"unitary\"))),\n        (\"sphere\", (\"center\", (0.1, \"unitary\"))),\n        (\"ortho_ray\", (0, (c[1], c[2]))),\n        (\"slice\", (0, c[0])),\n        # (\"disk\", (\"center\", [0.1, 0.3, 0.6],\n        #           (0.2, 'unitary'), (0.1, 'unitary'))),\n        (\"cutting\", ([0.1, 0.3, 0.6], \"center\")),\n        (\"all_data\", ()),\n    ]\n    return dobjs\n\n\ndef test_chunking():\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(64, nprocs=nprocs)\n        c = (ds.domain_right_edge + ds.domain_left_edge) / 2.0\n        c += ds.arr(0.5 / ds.domain_dimensions, \"code_length\")\n        for dobj in _get_dobjs(c):\n            obj = getattr(ds, dobj[0])(*dobj[1])\n            coords = {\"f\": {}, \"i\": {}}\n            for t in [\"io\", \"all\", \"spatial\"]:\n                coords[\"i\"][t] = []\n                coords[\"f\"][t] = []\n                for chunk in obj.chunks(None, t):\n                    coords[\"f\"][t].append(chunk.fcoords[:, :])\n                    coords[\"i\"][t].append(chunk.icoords[:, :])\n                coords[\"f\"][t] = uconcatenate(coords[\"f\"][t])\n                coords[\"i\"][t] = uconcatenate(coords[\"i\"][t])\n                coords[\"f\"][t].sort()\n                coords[\"i\"][t].sort()\n            assert_equal(coords[\"f\"][\"io\"], coords[\"f\"][\"all\"])\n            assert_equal(coords[\"f\"][\"io\"], coords[\"f\"][\"spatial\"])\n            assert_equal(coords[\"i\"][\"io\"], coords[\"i\"][\"all\"])\n            assert_equal(coords[\"i\"][\"io\"], coords[\"i\"][\"spatial\"])\n\n\ndef test_ds_hold():\n    ds1 = fake_random_ds(64)\n    ds2 = fake_random_ds(128)\n    dd = ds1.all_data()\n    # dd.ds is a weakref, so can't use \"is\"\n    assert dd.ds.__hash__() == ds1.__hash__()\n\n    assert dd.index is ds1.index\n    assert_equal(dd[\"index\", \"ones\"].size, 64**3)\n    with dd._ds_hold(ds2):\n        assert dd.ds.__hash__() == ds2.__hash__()\n        assert dd.index is ds2.index\n        assert_equal(dd[\"index\", \"ones\"].size, 128**3)\n    assert dd.ds.__hash__() == ds1.__hash__()\n    assert dd.index is ds1.index\n    assert_equal(dd[\"index\", \"ones\"].size, 64**3)\n"
  },
  {
    "path": "yt/data_objects/tests/test_clone.py",
    "content": "from numpy.testing import assert_array_equal, assert_equal\n\nfrom yt.testing import fake_random_ds\n\n\ndef test_clone_sphere():\n    # Now we test that we can get different radial velocities based on field\n    # parameters.\n    fields = (\"density\", \"velocity_x\", \"velocity_y\", \"velocity_z\")\n    units = (\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\")\n    # Get the first sphere\n    ds = fake_random_ds(16, fields=fields, units=units)\n    sp0 = ds.sphere(ds.domain_center, 0.25)\n\n    assert_equal(list(sp0.keys()), [])\n\n    sp1 = sp0.clone()\n    sp0[\"gas\", \"density\"]\n    assert_equal(list(sp0.keys()), ((\"gas\", \"density\"),))\n    assert_equal(list(sp1.keys()), [])\n\n    sp1[\"gas\", \"density\"]\n\n    assert_array_equal(sp0[\"gas\", \"density\"], sp1[\"gas\", \"density\"])\n\n\ndef test_clone_cut_region():\n    fields = (\"density\", \"temperature\")\n    units = (\"g/cm**3\", \"K\")\n    ds = fake_random_ds(64, nprocs=4, fields=fields, units=units)\n    dd = ds.all_data()\n    reg1 = dd.cut_region(\n        [\"obj['gas', 'temperature'] > 0.5\", \"obj['gas', 'density'] < 0.75\"]\n    )\n    reg2 = reg1.clone()\n    assert_array_equal(reg1[\"gas\", \"density\"], reg2[\"gas\", \"density\"])\n"
  },
  {
    "path": "yt/data_objects/tests/test_compose.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_equal\n\nfrom yt.testing import fake_amr_ds, fake_random_ds\nfrom yt.units._numpy_wrapper_functions import uintersect1d\nfrom yt.units.yt_array import YTArray\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\n# Copied from test_boolean for computing a unique identifier for\n# each cell from cell positions\ndef _IDFIELD(data):\n    width = data.ds.domain_right_edge - data.ds.domain_left_edge\n    min_dx = YTArray(1.0 / 8192, units=\"code_length\", registry=data.ds.unit_registry)\n    delta = width / min_dx\n    x = data[\"index\", \"x\"] - min_dx / 2.0\n    y = data[\"index\", \"y\"] - min_dx / 2.0\n    z = data[\"index\", \"z\"] - min_dx / 2.0\n    xi = x / min_dx\n    yi = y / min_dx\n    zi = z / min_dx\n    index = xi + delta[0] * (yi + delta[1] * zi)\n    return index\n\n\ndef test_compose_no_overlap():\n    r\"\"\"Test to make sure that composed data objects that don't\n    overlap behave the way we expect (return empty collections)\n    \"\"\"\n    empty = np.array([])\n    for n in [1, 2, 4, 8]:\n        ds = fake_random_ds(64, nprocs=n)\n        ds.add_field((\"index\", \"ID\"), sampling_type=\"cell\", function=_IDFIELD)\n\n        # position parameters for initial region\n        center = [0.25] * 3\n        left_edge = [0.1] * 3\n        right_edge = [0.4] * 3\n        normal = [1, 0, 0]\n        radius = height = 0.15\n\n        # initial 3D regions\n        sources = [\n            ds.sphere(center, radius),\n            ds.region(center, left_edge, right_edge),\n            ds.disk(center, normal, radius, height),\n        ]\n\n        # position parameters for non-overlapping regions\n        center = [0.75] * 3\n        left_edge = [0.6] * 3\n        right_edge = [0.9] * 3\n\n        # subselect non-overlapping 0, 1, 2, 3D regions\n        for data1 in sources:\n            data2 = ds.sphere(center, radius, data_source=data1)\n            assert_array_equal(data2[\"index\", \"ID\"], empty)\n\n            data2 = ds.region(center, left_edge, right_edge, data_source=data1)\n            assert_array_equal(data2[\"index\", \"ID\"], empty)\n\n            data2 = ds.disk(center, normal, radius, height, data_source=data1)\n            assert_array_equal(data2[\"index\", \"ID\"], empty)\n\n            for d in range(3):\n                data2 = ds.slice(d, center[d], data_source=data1)\n                assert_array_equal(data2[\"index\", \"ID\"], empty)\n\n            for d in range(3):\n                data2 = ds.ortho_ray(\n                    d, center[0:d] + center[d + 1 :], data_source=data1\n                )\n                assert_array_equal(data2[\"index\", \"ID\"], empty)\n\n            data2 = ds.point(center, data_source=data1)\n            assert_array_equal(data2[\"index\", \"ID\"], empty)\n\n\ndef test_compose_overlap():\n    r\"\"\"Test to make sure that composed data objects that do\n    overlap behave the way we expect\n    \"\"\"\n    for n in [1, 2, 4, 8]:\n        ds = fake_random_ds(64, nprocs=n)\n        ds.add_field((\"index\", \"ID\"), sampling_type=\"cell\", function=_IDFIELD)\n\n        # position parameters for initial region\n        center = [0.4, 0.5, 0.5]\n        left_edge = [0.1] * 3\n        right_edge = [0.7] * 3\n        normal = [1, 0, 0]\n        radius = height = 0.15\n\n        # initial 3D regions\n        sources = [\n            ds.sphere(center, radius),\n            ds.region(center, left_edge, right_edge),\n            ds.disk(center, normal, radius, height),\n        ]\n\n        # position parameters for overlapping regions\n        center = [0.6, 0.5, 0.5]\n        left_edge = [0.3] * 3\n        right_edge = [0.9] * 3\n\n        # subselect non-overlapping 0, 1, 2, 3D regions\n        for data1 in sources:\n            id1 = data1[\"index\", \"ID\"]\n\n            data2 = ds.sphere(center, radius)\n            data3 = ds.sphere(center, radius, data_source=data1)\n            id2 = data2[\"index\", \"ID\"]\n            id3 = data3[\"index\", \"ID\"]\n            id3.sort()\n            assert_array_equal(uintersect1d(id1, id2), id3)\n\n            data2 = ds.region(center, left_edge, right_edge)\n            data3 = ds.region(center, left_edge, right_edge, data_source=data1)\n            id2 = data2[\"index\", \"ID\"]\n            id3 = data3[\"index\", \"ID\"]\n            id3.sort()\n            assert_array_equal(uintersect1d(id1, id2), id3)\n\n            data2 = ds.disk(center, normal, radius, height)\n            data3 = ds.disk(center, normal, radius, height, data_source=data1)\n            id2 = data2[\"index\", \"ID\"]\n            id3 = data3[\"index\", \"ID\"]\n            id3.sort()\n            assert_array_equal(uintersect1d(id1, id2), id3)\n\n            for d in range(3):\n                data2 = ds.slice(d, center[d])\n                data3 = ds.slice(d, center[d], data_source=data1)\n                id2 = data2[\"index\", \"ID\"]\n                id3 = data3[\"index\", \"ID\"]\n                id3.sort()\n                assert_array_equal(uintersect1d(id1, id2), id3)\n\n            for d in range(3):\n                data2 = ds.ortho_ray(d, center[0:d] + center[d + 1 :])\n                data3 = ds.ortho_ray(\n                    d, center[0:d] + center[d + 1 :], data_source=data1\n                )\n                id2 = data2[\"index\", \"ID\"]\n                id3 = data3[\"index\", \"ID\"]\n                id3.sort()\n                assert_array_equal(uintersect1d(id1, id2), id3)\n\n            data2 = ds.point(center)\n            data3 = ds.point(center, data_source=data1)\n            id2 = data2[\"index\", \"ID\"]\n            id3 = data3[\"index\", \"ID\"]\n            id3.sort()\n            assert_array_equal(uintersect1d(id1, id2), id3)\n\n\ndef test_compose_max_level_min_level():\n    ds = fake_amr_ds()\n    ad = ds.all_data()\n    ad.max_level = 2\n    slc = ds.slice(\"x\", 0.5, data_source=ad)\n    assert slc[\"index\", \"grid_level\"].max() == 2\n    frb = slc.to_frb(1.0, 128)\n    assert np.all(frb[\"stream\", \"Density\"] > 0)\n    assert frb[\"index\", \"grid_level\"].max() == 2\n"
  },
  {
    "path": "yt/data_objects/tests/test_connected_sets.py",
    "content": "from yt.testing import fake_random_ds\nfrom yt.utilities.answer_testing.level_sets_tests import ExtractConnectedSetsTest\n\n\ndef test_connected_sets():\n    ds = fake_random_ds(16, nprocs=8, particles=16**3)\n    data_source = ds.disk([0.5, 0.5, 0.5], [0.0, 0.0, 1.0], (8, \"kpc\"), (1, \"kpc\"))\n    field = (\"gas\", \"density\")\n    min_val, max_val = data_source[field].min() / 2, data_source[field].max() / 2\n    data_source.extract_connected_sets(\n        field, 5, min_val, max_val, log_space=True, cumulative=True\n    )\n    yield ExtractConnectedSetsTest(ds, data_source, field, 5, min_val, max_val)\n"
  },
  {
    "path": "yt/data_objects/tests/test_covering_grid.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_array_equal, assert_equal\n\nfrom yt.fields.derived_field import ValidateParameter\nfrom yt.loaders import load, load_particles\nfrom yt.testing import (\n    fake_octree_ds,\n    fake_random_ds,\n    requires_file,\n    requires_module,\n)\nfrom yt.units import kpc\n\n# cylindrical data for covering_grid test\ncyl_2d = \"WDMerger_hdf5_chk_1000/WDMerger_hdf5_chk_1000.hdf5\"\ncyl_3d = \"MHD_Cyl3d_hdf5_plt_cnt_0100/MHD_Cyl3d_hdf5_plt_cnt_0100.hdf5\"\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\n@requires_file(cyl_3d)\ndef test_covering_grid():\n    # We decompose in different ways\n    for level in [0, 1, 2]:\n        for nprocs in [1, 2, 4, 8]:\n            ds = fake_random_ds(16, nprocs=nprocs)\n            axis_name = ds.coordinates.axis_name\n            dn = ds.refine_by**level\n            cg = ds.covering_grid(level, [0.0, 0.0, 0.0], dn * ds.domain_dimensions)\n            # Test coordinate generation\n            assert_equal(np.unique(cg[\"index\", f\"d{axis_name[0]}\"]).size, 1)\n            xmi = cg[\"index\", axis_name[0]].min()\n            xma = cg[\"index\", axis_name[0]].max()\n            dx = cg[\"index\", f\"d{axis_name[0]}\"].flat[0:1]\n            edges = ds.arr([[0, 1], [0, 1], [0, 1]], \"code_length\")\n            assert_equal(xmi, edges[0, 0] + dx / 2.0)\n            assert_equal(xmi, cg[\"index\", axis_name[0]][0, 0, 0])\n            assert_equal(xmi, cg[\"index\", axis_name[0]][0, 1, 1])\n            assert_equal(xma, edges[0, 1] - dx / 2.0)\n            assert_equal(xma, cg[\"index\", axis_name[0]][-1, 0, 0])\n            assert_equal(xma, cg[\"index\", axis_name[0]][-1, 1, 1])\n            assert_equal(np.unique(cg[\"index\", f\"d{axis_name[1]}\"]).size, 1)\n            ymi = cg[\"index\", axis_name[1]].min()\n            yma = cg[\"index\", axis_name[1]].max()\n            dy = cg[\"index\", f\"d{axis_name[1]}\"][0]\n            assert_equal(ymi, edges[1, 0] + dy / 2.0)\n            assert_equal(ymi, cg[\"index\", axis_name[1]][0, 0, 0])\n            assert_equal(ymi, cg[\"index\", axis_name[1]][1, 0, 1])\n            assert_equal(yma, edges[1, 1] - dy / 2.0)\n            assert_equal(yma, cg[\"index\", axis_name[1]][0, -1, 0])\n            assert_equal(yma, cg[\"index\", axis_name[1]][1, -1, 1])\n            assert_equal(np.unique(cg[\"index\", f\"d{axis_name[2]}\"]).size, 1)\n            zmi = cg[\"index\", axis_name[2]].min()\n            zma = cg[\"index\", axis_name[2]].max()\n            dz = cg[\"index\", f\"d{axis_name[2]}\"][0]\n            assert_equal(zmi, edges[2, 0] + dz / 2.0)\n            assert_equal(zmi, cg[\"index\", axis_name[2]][0, 0, 0])\n            assert_equal(zmi, cg[\"index\", axis_name[2]][1, 1, 0])\n            assert_equal(zma, edges[2, 1] - dz / 2.0)\n            assert_equal(zma, cg[\"index\", axis_name[2]][0, 0, -1])\n            assert_equal(zma, cg[\"index\", axis_name[2]][1, 1, -1])\n            # Now we test other attributes\n            assert_equal(cg[\"index\", \"ones\"].max(), 1.0)\n            assert_equal(cg[\"index\", \"ones\"].min(), 1.0)\n            assert_equal(cg[\"index\", \"grid_level\"], level)\n            assert_equal(cg[\"index\", \"cell_volume\"].sum(), ds.domain_width.prod())\n            for g in ds.index.grids:\n                di = g.get_global_startindex()\n                dd = g.ActiveDimensions\n                for i in range(dn):\n                    f = cg[\"gas\", \"density\"][\n                        dn * di[0] + i : dn * (di[0] + dd[0]) + i : dn,\n                        dn * di[1] + i : dn * (di[1] + dd[1]) + i : dn,\n                        dn * di[2] + i : dn * (di[2] + dd[2]) + i : dn,\n                    ]\n                    assert_equal(f, g[\"gas\", \"density\"])\n\n    # More tests for cylindrical geometry\n    for fn in [cyl_2d, cyl_3d]:\n        ds = load(fn)\n        ad = ds.all_data()\n        upper_ad = ad.cut_region([\"obj['index', 'z'] > 0\"])\n        sp = ds.sphere((0, 0, 0), 0.5 * ds.domain_width[0], data_source=upper_ad)\n        sp.quantities.total_mass()\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\n@requires_file(cyl_3d)\ndef test_covering_grid_data_source():\n    # test the data_source kwarg (new with PR 4063)\n\n    for level in [0, 1, 2]:\n        for nprocs in [1, 2, 4, 8]:\n            ds = fake_random_ds(16, nprocs=nprocs)\n            dn = ds.refine_by**level\n            cg = ds.covering_grid(level, [0.0, 0.0, 0.0], dn * ds.domain_dimensions)\n\n            # check that the regularized sphere is centered where it should be,\n            # to within the tolerance of the grid. Use a center offsect from\n            # the domain center by a bit\n            center = ds.domain_center + np.min(ds.domain_width) * 0.15\n            sp = ds.sphere(center, (0.1, \"code_length\"))\n            dims = dn * ds.domain_dimensions\n            cg_sp = ds.covering_grid(level, [0.0, 0.0, 0.0], dims, data_source=sp)\n\n            # find the discrete center of the sphere by averaging the position\n            # along each dimension where values are non-zero\n            cg_mask = cg_sp[\"gas\", \"density\"] != 0\n            discrete_c = ds.arr(\n                [\n                    np.mean(cg_sp[\"index\", dim][cg_mask])\n                    for dim in ds.coordinates.axis_order\n                ]\n            )\n\n            # should be centered to within the tolerance of the grid at least\n            grid_tol = ds.arr(\n                [cg_sp[\"index\", dim][0, 0, 0] for dim in ds.coordinates.axis_order]\n            )\n            assert np.all(np.abs(discrete_c - center) <= grid_tol)\n\n            # check that a region covering the whole domain matches no data_source\n            reg = ds.region(ds.domain_center, ds.domain_left_edge, ds.domain_right_edge)\n            cg_reg = ds.covering_grid(level, [0.0, 0.0, 0.0], dims, data_source=reg)\n            assert np.all(cg[\"gas\", \"density\"] == cg_reg[\"gas\", \"density\"])\n\n            # check that a box covering a subset of the domain is the right volume\n            right_edge = ds.domain_left_edge + ds.domain_width * 0.5\n            c = (right_edge + ds.domain_left_edge) / 2\n            reg = ds.region(c, ds.domain_left_edge, right_edge)\n            cg_reg = ds.covering_grid(level, [0.0, 0.0, 0.0], dims, data_source=reg)\n            box_vol = cg_reg[\"index\", \"cell_volume\"][\n                cg_reg[\"gas\", \"density\"] != 0\n            ].sum()\n            actual_vol = np.prod(right_edge - ds.domain_left_edge)\n            assert box_vol == actual_vol\n\n\n@requires_module(\"xarray\")\ndef test_xarray_export():\n    def _run_tests(cg):\n        xarr = cg.to_xarray(fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n        assert (\"gas\", \"density\") in xarr.variables\n        assert (\"gas\", \"temperature\") in xarr.variables\n        assert (\"gas\", \"specific_thermal_energy\") not in xarr.variables\n        assert \"x\" in xarr.coords\n        assert \"y\" in xarr.coords\n        assert \"z\" in xarr.coords\n        assert xarr.sizes[\"x\"] == dn * ds.domain_dimensions[0]\n        assert xarr.sizes[\"y\"] == dn * ds.domain_dimensions[1]\n        assert xarr.sizes[\"z\"] == dn * ds.domain_dimensions[2]\n        assert_equal(xarr.x, cg[\"index\", \"x\"][:, 0, 0])\n        assert_equal(xarr.y, cg[\"index\", \"y\"][0, :, 0])\n        assert_equal(xarr.z, cg[\"index\", \"z\"][0, 0, :])\n\n    fields = (\"density\", \"temperature\", \"specific_thermal_energy\")\n    units = (\"g/cm**3\", \"K\", \"erg/g\")\n    for level in [0, 1, 2]:\n        ds = fake_random_ds(16, fields=fields, units=units)\n        dn = ds.refine_by**level\n        rcg = ds.covering_grid(level, [0.0, 0.0, 0.0], dn * ds.domain_dimensions)\n        _run_tests(rcg)\n        scg = ds.smoothed_covering_grid(\n            level, [0.0, 0.0, 0.0], dn * ds.domain_dimensions\n        )\n        _run_tests(scg)\n        ag1 = ds.arbitrary_grid(\n            [0.0, 0.0, 0.0], [1.0, 1.0, 1.0], dn * ds.domain_dimensions\n        )\n        _run_tests(ag1)\n        ag2 = ds.arbitrary_grid(\n            [0.1, 0.3, 0.2], [0.4, 1.0, 0.9], dn * ds.domain_dimensions\n        )\n        _run_tests(ag2)\n\n\ndef test_smoothed_covering_grid():\n    # We decompose in different ways\n    for level in [0, 1, 2]:\n        for nprocs in [1, 2, 4, 8]:\n            ds = fake_random_ds(16, nprocs=nprocs)\n            dn = ds.refine_by**level\n            cg = ds.smoothed_covering_grid(\n                level, [0.0, 0.0, 0.0], dn * ds.domain_dimensions\n            )\n            assert_equal(cg[\"index\", \"ones\"].max(), 1.0)\n            assert_equal(cg[\"index\", \"ones\"].min(), 1.0)\n            assert_equal(cg[\"index\", \"cell_volume\"].sum(), ds.domain_width.prod())\n            for g in ds.index.grids:\n                if level != g.Level:\n                    continue\n                di = g.get_global_startindex()\n                dd = g.ActiveDimensions\n                for i in range(dn):\n                    f = cg[\"gas\", \"density\"][\n                        dn * di[0] + i : dn * (di[0] + dd[0]) + i : dn,\n                        dn * di[1] + i : dn * (di[1] + dd[1]) + i : dn,\n                        dn * di[2] + i : dn * (di[2] + dd[2]) + i : dn,\n                    ]\n                    assert_equal(f, g[\"gas\", \"density\"])\n\n\ndef test_arbitrary_grid():\n    for ncells in [32, 64]:\n        for px in [0.125, 0.25, 0.55519]:\n            particle_data = {\n                \"particle_position_x\": np.array([px]),\n                \"particle_position_y\": np.array([0.5]),\n                \"particle_position_z\": np.array([0.5]),\n                \"particle_mass\": np.array([1.0]),\n            }\n\n            ds = load_particles(particle_data)\n\n            for dims in ([ncells] * 3, [ncells, ncells / 2, ncells / 4]):\n                LE = np.array([0.05, 0.05, 0.05])\n                RE = np.array([0.95, 0.95, 0.95])\n                dims = np.array(dims)\n\n                dds = (RE - LE) / dims\n                volume = ds.quan(np.prod(dds), \"cm**3\")\n\n                obj = ds.arbitrary_grid(LE, RE, dims)\n                deposited_mass = obj[\"deposit\", \"all_density\"].sum() * volume\n\n                assert_equal(deposited_mass, ds.quan(1.0, \"g\"))\n\n                LE = np.array([0.00, 0.00, 0.00])\n                RE = np.array([0.05, 0.05, 0.05])\n\n                obj = ds.arbitrary_grid(LE, RE, dims)\n\n                deposited_mass = obj[\"deposit\", \"all_density\"].sum()\n\n                assert_equal(deposited_mass, 0)\n\n    # Test that we get identical results to the covering grid for unigrid data.\n    # Testing AMR data is much harder.\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(32, nprocs=nprocs)\n        for ref_level in [0, 1, 2]:\n            cg = ds.covering_grid(\n                ref_level, [0.0, 0.0, 0.0], 2**ref_level * ds.domain_dimensions\n            )\n            ag = ds.arbitrary_grid(\n                [0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 2**ref_level * ds.domain_dimensions\n            )\n            assert_almost_equal(cg[\"gas\", \"density\"], ag[\"gas\", \"density\"])\n\n\ndef test_octree_cg():\n    ds = fake_octree_ds(num_zones=1, partial_coverage=0)\n    cgrid = ds.covering_grid(\n        0, left_edge=ds.domain_left_edge, dims=ds.domain_dimensions\n    )\n    density_field = cgrid[\"gas\", \"density\"]\n    assert_equal((density_field == 0.0).sum(), 0)\n\n\ndef test_smoothed_covering_grid_2d_dataset():\n    ds = fake_random_ds([32, 32, 1], nprocs=4)\n    ds.force_periodicity()\n    scg = ds.smoothed_covering_grid(1, [0.0, 0.0, 0.0], [32, 32, 1])\n    assert_equal(scg[\"gas\", \"density\"].shape, [32, 32, 1])\n\n\ndef test_arbitrary_grid_derived_field():\n    def custom_metal_density(data):\n        # Calculating some random value\n        return data[\"gas\", \"density\"] * np.random.random_sample()\n\n    ds = fake_random_ds(64, nprocs=8, particles=16**2)\n    ds.add_field(\n        (\"gas\", \"Metal_Density\"),\n        units=\"g/cm**3\",\n        function=custom_metal_density,\n        sampling_type=\"cell\",\n    )\n\n    def _tracerf(data):\n        return data[\"gas\", \"Metal_Density\"] / data[\"gas\", \"density\"]\n\n    ds.add_field(\n        (\"gas\", \"tracerf\"),\n        function=_tracerf,\n        units=\"dimensionless\",\n        sampling_type=\"cell\",\n        take_log=False,\n    )\n\n    galgas = ds.arbitrary_grid([0.4, 0.4, 0.4], [0.99, 0.99, 0.99], dims=[32, 32, 32])\n    galgas[\"gas\", \"tracerf\"]\n\n\ndef test_arbitrary_field_parameters():\n    def _test_field(data):\n        par = data.get_field_parameter(\"test_parameter\")\n        return par * data[\"all\", \"particle_mass\"]\n\n    ds = fake_random_ds(64, nprocs=8, particles=16**2)\n    ds.add_field(\n        (\"all\", \"test_field\"),\n        units=\"g\",\n        function=_test_field,\n        sampling_type=\"particle\",\n        validators=[ValidateParameter(\"test_parameter\")],\n    )\n\n    agrid = ds.arbitrary_grid([0.4, 0.4, 0.4], [0.99, 0.99, 0.99], dims=[32, 32, 32])\n    agrid.set_field_parameter(\"test_parameter\", 2)\n    assert_array_equal(2 * agrid[\"all\", \"particle_mass\"], agrid[\"all\", \"test_field\"])\n\n\ndef test_arbitrary_grid_edge():\n    # Tests bug fix for issue #2087\n    # Regardless of how left_edge and right_edge are passed, the result should be\n    # a YTArray with a unit registry that matches that of the dataset.\n    dims = [32, 32, 32]\n    ds = fake_random_ds(dims)\n    # Test when edge is a list, numpy array, YTArray with dataset units, and\n    # YTArray with non-dataset units\n    ledge = [\n        [0.0, 0.0, 0.0],\n        np.array([0.0, 0.0, 0.0]),\n        [0.0, 0.0, 0.0] * ds.length_unit,\n        [0.0, 0.0, 0.0] * kpc,\n    ]\n\n    redge = [\n        [1.0, 1.0, 1.0],\n        np.array([1.0, 1.0, 1.0]),\n        [1.0, 1.0, 1.0] * ds.length_unit,\n        [1.0, 1.0, 1.0] * kpc,\n    ]\n\n    ledge_ans = [\n        [0.0, 0.0, 0.0] * ds.length_unit.to(\"code_length\"),\n        np.array([0.0, 0.0, 0.0]) * ds.length_unit.to(\"code_length\"),\n        [0.0, 0.0, 0.0] * ds.length_unit,\n        [0.0, 0.0, 0.0] * kpc,\n    ]\n\n    redge_ans = [\n        [1.0, 1.0, 1.0] * ds.length_unit.to(\"code_length\"),\n        np.array([1.0, 1.0, 1.0]) * ds.length_unit.to(\"code_length\"),\n        [1.0, 1.0, 1.0] * ds.length_unit,\n        [1.0, 1.0, 1.0] * kpc,\n    ]\n\n    for le, re, le_ans, re_ans in zip(ledge, redge, ledge_ans, redge_ans, strict=True):\n        ag = ds.arbitrary_grid(left_edge=le, right_edge=re, dims=dims)\n        assert np.array_equal(ag.left_edge, le_ans)\n        assert np.array_equal(ag.right_edge, re_ans)\n        assert ag.left_edge.units.registry == ds.unit_registry\n        assert ag.right_edge.units.registry == ds.unit_registry\n        ag[\"gas\", \"density\"]\n"
  },
  {
    "path": "yt/data_objects/tests/test_cut_region_chaining.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import fake_random_ds\n\n\ndef test_chained_cut_region_with_locals():\n    ds = fake_random_ds(16, fields=(\"density\",), units=(\"g/cm**3\",))\n    ad = ds.all_data()\n    cut_1 = ad.cut_region(\"obj['density'] > density_min\", locals={\"density_min\": 0.2})\n\n    # This chained cut region should inherit 'density_min' from cut_1's locals\n    cut_2 = cut_1.cut_region(\n        \"obj['density'] < density_max\", locals={\"density_max\": 0.8}\n    )\n\n    # Verify locals are merged\n    assert \"density_min\" in cut_2.locals\n    assert \"density_max\" in cut_2.locals\n    assert cut_2.locals[\"density_min\"] == 0.2\n    assert cut_2.locals[\"density_max\"] == 0.8\n\n    mask = (ad[\"density\"] > 0.2) & (ad[\"density\"] < 0.8)\n    expected = np.sort(ad[\"density\"][mask])\n\n    # This access triggers evaluation involving locals\n    result = np.sort(cut_2[\"density\"])\n\n    assert_equal(expected, result)\n"
  },
  {
    "path": "yt/data_objects/tests/test_cutting_plane.py",
    "content": "import os\nimport tempfile\n\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import fake_random_ds\nfrom yt.units.unit_object import Unit\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef teardown_func(fns):\n    for fn in fns:\n        try:\n            os.remove(fn)\n        except OSError:\n            pass\n\n\ndef test_cutting_plane():\n    fns = []\n    for nprocs in [8, 1]:\n        # We want to test both 1 proc and 8 procs, to make sure that\n        # parallelism isn't broken\n        ds = fake_random_ds(64, nprocs=nprocs)\n        center = [0.5, 0.5, 0.5]\n        normal = [1, 1, 1]\n        cut = ds.cutting(normal, center)\n        assert_equal(cut[\"index\", \"ones\"].sum(), cut[\"index\", \"ones\"].size)\n        assert_equal(cut[\"index\", \"ones\"].min(), 1.0)\n        assert_equal(cut[\"index\", \"ones\"].max(), 1.0)\n        pw = cut.to_pw(fields=(\"gas\", \"density\"))\n        for p in pw.plots.values():\n            tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n            os.close(tmpfd)\n            p.save(name=tmpname)\n            fns.append(tmpname)\n        for width in [(1.0, \"unitary\"), 1.0, ds.quan(0.5, \"code_length\")]:\n            frb = cut.to_frb(width, 64)\n            for cut_field in [(\"index\", \"ones\"), (\"gas\", \"density\")]:\n                fi = ds._get_field_info(cut_field)\n                data = frb[cut_field]\n                assert_equal(data.info[\"data_source\"], cut.__str__())\n                assert_equal(data.info[\"axis\"], None)\n                assert_equal(data.info[\"field\"], str(cut_field))\n                assert_equal(data.units, Unit(fi.units))\n                assert_equal(data.info[\"xlim\"], frb.bounds[:2])\n                assert_equal(data.info[\"ylim\"], frb.bounds[2:])\n                assert_equal(data.info[\"length_to_cm\"], ds.length_unit.in_cgs())\n                assert_equal(data.info[\"center\"], cut.center)\n    teardown_func(fns)\n"
  },
  {
    "path": "yt/data_objects/tests/test_data_collection.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import assert_rel_equal, fake_random_ds\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_data_collection():\n    # We decompose in different ways\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(16, nprocs=nprocs)\n        coll = ds.data_collection(ds.index.grids)\n        crho = coll[\"gas\", \"density\"].sum(dtype=\"float64\").to_ndarray()\n        grho = np.sum(\n            [g[\"gas\", \"density\"].sum(dtype=\"float64\") for g in ds.index.grids],\n            dtype=\"float64\",\n        )\n        assert_rel_equal(np.array([crho]), np.array([grho]), 12)\n        assert_equal(coll.size, ds.domain_dimensions.prod())\n        for gi in range(ds.index.num_grids):\n            grids = ds.index.grids[: gi + 1]\n            coll = ds.data_collection(grids)\n            crho = coll[\"gas\", \"density\"].sum(dtype=\"float64\")\n            grho = np.sum(\n                [g[\"gas\", \"density\"].sum(dtype=\"float64\") for g in grids],\n                dtype=\"float64\",\n            )\n            assert_rel_equal(np.array([crho]), np.array([grho]), 12)\n            assert_equal(coll.size, sum(g.ActiveDimensions.prod() for g in grids))\n"
  },
  {
    "path": "yt/data_objects/tests/test_data_containers.py",
    "content": "import os\n\nimport numpy as np\nimport pytest\nfrom numpy.testing import assert_array_equal, assert_equal\n\nfrom yt.data_objects.particle_filters import particle_filter\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n)\nfrom yt.testing import (\n    fake_amr_ds,\n    fake_particle_ds,\n    fake_random_ds,\n    requires_module_pytest as requires_module,\n)\nfrom yt.utilities.exceptions import YTException, YTFieldNotFound\n\n\n@pytest.fixture\ndef temp_workdir(tmp_path):\n    curdir = os.getcwd()\n    os.chdir(tmp_path)\n    yield tmp_path\n    os.chdir(curdir)\n\n\n@pytest.mark.usefixtures(\"temp_workdir\")\ndef test_yt_data_container():\n    # Test if ds could be None\n    with pytest.raises(\n        RuntimeError,\n        match=(\n            \"Error: ds must be set either through class\"\n            \" type or parameter to the constructor\"\n        ),\n    ):\n        YTSelectionContainer(None, None)\n\n    # Test if field_data key exists\n    ds = fake_random_ds(5)\n    proj = ds.proj((\"gas\", \"density\"), 0, data_source=ds.all_data())\n    assert \"px\" in proj.keys()\n    assert \"pz\" not in proj.keys()\n\n    # Delete the key and check if exits\n    del proj[\"px\"]\n    assert \"px\" not in proj.keys()\n    del proj[\"gas\", \"density\"]\n    assert \"density\" not in proj.keys()\n\n    # Delete a non-existent field\n    with pytest.raises(\n        YTFieldNotFound, match=\"Could not find field 'p_mass' in UniformGridData.\"\n    ):\n        del proj[\"p_mass\"]\n\n\n@pytest.mark.usefixtures(\"temp_workdir\")\ndef test_write_out():\n    filename = \"sphere.txt\"\n    ds = fake_random_ds(16, particles=10)\n    sp = ds.sphere(ds.domain_center, 0.25)\n\n    sp.write_out(filename, fields=[(\"gas\", \"cell_volume\")])\n\n    with open(filename) as file:\n        file_row_1 = file.readline()\n        file_row_2 = file.readline()\n        file_row_2 = np.array(file_row_2.split(\"\\t\"), dtype=np.float64)\n    sorted_keys = sorted(sp.field_data.keys())\n    keys = [str(k) for k in sorted_keys]\n    keys = \"\\t\".join([\"#\"] + keys + [\"\\n\"])\n    data = [sp.field_data[k][0] for k in sorted_keys]\n\n    assert_equal(keys, file_row_1)\n    assert_array_equal(data, file_row_2)\n\n\n@pytest.mark.usefixtures(\"temp_workdir\")\ndef test_invalid_write_out():\n    filename = \"sphere.txt\"\n    ds = fake_random_ds(16, particles=10)\n    sp = ds.sphere(ds.domain_center, 0.25)\n\n    with pytest.raises(YTException):\n        sp.write_out(filename, fields=[(\"all\", \"particle_ones\")])\n\n\n@pytest.mark.usefixtures(\"temp_workdir\")\n@requires_module(\"pandas\")\ndef test_to_dataframe():\n    fields = [(\"gas\", \"density\"), (\"gas\", \"velocity_z\")]\n    ds = fake_random_ds(6)\n    dd = ds.all_data()\n    df = dd.to_dataframe(fields)\n    assert_array_equal(dd[fields[0]], df[fields[0][1]])\n    assert_array_equal(dd[fields[1]], df[fields[1][1]])\n\n\n@pytest.mark.usefixtures(\"temp_workdir\")\n@requires_module(\"astropy\")\ndef test_to_astropy_table():\n    from yt.units.yt_array import YTArray\n\n    fields = [(\"gas\", \"density\"), (\"gas\", \"velocity_z\")]\n    ds = fake_random_ds(6)\n    dd = ds.all_data()\n    at1 = dd.to_astropy_table(fields)\n    assert_array_equal(dd[fields[0]].d, at1[fields[0][1]].value)\n    assert_array_equal(dd[fields[1]].d, at1[fields[1][1]].value)\n    assert dd[fields[0]].units == YTArray.from_astropy(at1[fields[0][1]]).units\n    assert dd[fields[1]].units == YTArray.from_astropy(at1[fields[1][1]]).units\n\n\ndef test_std():\n    ds = fake_random_ds(3)\n    ds.all_data().std((\"gas\", \"density\"), weight=(\"gas\", \"velocity_z\"))\n\n\ndef test_to_frb():\n    # Test cylindrical geometry\n    fields = [\"density\", \"cell_mass\"]\n    units = [\"g/cm**3\", \"g\"]\n    ds = fake_amr_ds(\n        fields=fields, units=units, geometry=\"cylindrical\", particles=16**3\n    )\n    dd = ds.all_data()\n    proj = ds.proj(\n        (\"gas\", \"density\"),\n        weight_field=(\"gas\", \"cell_mass\"),\n        axis=1,\n        data_source=dd,\n    )\n    frb = proj.to_frb((1.0, \"unitary\"), 64)\n    assert frb.radius == (1.0, \"unitary\")\n    assert frb.buff_size == 64\n\n\n@pytest.mark.usefixtures(\"temp_workdir\")\ndef test_extract_isocontours():\n    # Test isocontour properties for AMRGridData\n    fields = [\"density\", \"cell_mass\"]\n    units = [\"g/cm**3\", \"g\"]\n    ds = fake_amr_ds(fields=fields, units=units, particles=16**3)\n    dd = ds.all_data()\n    q = dd.quantities[\"WeightedAverageQuantity\"]\n    rho = q((\"gas\", \"density\"), weight=(\"gas\", \"cell_mass\"))\n    dd.extract_isocontours((\"gas\", \"density\"), rho, \"triangles.obj\", True)\n    dd.calculate_isocontour_flux(\n        (\"gas\", \"density\"),\n        rho,\n        (\"index\", \"x\"),\n        (\"index\", \"y\"),\n        (\"index\", \"z\"),\n        (\"index\", \"dx\"),\n    )\n\n    # Test error in case of ParticleData\n    ds = fake_particle_ds()\n    dd = ds.all_data()\n    q = dd.quantities[\"WeightedAverageQuantity\"]\n    rho = q((\"all\", \"particle_velocity_x\"), weight=(\"all\", \"particle_mass\"))\n    with pytest.raises(NotImplementedError):\n        dd.extract_isocontours(\"density\", rho, sample_values=\"x\")\n\n\ndef test_derived_field():\n    # Test that derived field on filtered particles do not require\n    # their parent field to be created\n    ds = fake_particle_ds()\n    dd = ds.all_data()\n    dd.set_field_parameter(\"axis\", 0)\n\n    @particle_filter(requires=[\"particle_mass\"], filtered_type=\"io\")\n    def massive(pfilter, data):\n        return data[pfilter.filtered_type, \"particle_mass\"].to(\"code_mass\") > 0.5\n\n    ds.add_particle_filter(\"massive\")\n\n    def fun(field, data):\n        return data[field.name[0], \"particle_mass\"]\n\n    # Add the field to the massive particles\n    ds.add_field(\n        (\"massive\", \"test\"),\n        function=fun,\n        sampling_type=\"particle\",\n        units=\"code_mass\",\n    )\n\n    expected_size = (dd[\"io\", \"particle_mass\"].to(\"code_mass\") > 0.5).sum()\n\n    fields_to_test = [f for f in ds.derived_field_list if f[0] == \"massive\"]\n\n    for fname in fields_to_test:\n        data = dd[fname]\n        assert_equal(data.shape[0], expected_size)\n"
  },
  {
    "path": "yt/data_objects/tests/test_dataset_access.py",
    "content": "import numpy as np\nfrom nose.tools import assert_raises\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.testing import (\n    fake_amr_ds,\n    fake_particle_ds,\n    fake_random_ds,\n    requires_file,\n    requires_module,\n)\nfrom yt.utilities.answer_testing.framework import data_dir_load\nfrom yt.utilities.exceptions import YTDimensionalityError\nfrom yt.visualization.line_plot import LineBuffer\n\n# This will test the \"dataset access\" method.\n\n\ndef test_box_creation():\n    ds = fake_random_ds(32, length_unit=2)\n    left_edge = ds.arr([0.2, 0.2, 0.2], \"cm\")\n    right_edge = ds.arr([0.6, 0.6, 0.6], \"cm\")\n    center = (left_edge + right_edge) / 2\n\n    boxes = [\n        ds.box(left_edge, right_edge),\n        ds.box(0.5 * np.array(left_edge), 0.5 * np.array(right_edge)),\n        ds.box((0.5 * left_edge).tolist(), (0.5 * right_edge).tolist()),\n    ]\n\n    region = ds.region(center, left_edge, right_edge)\n\n    for b in boxes:\n        assert_almost_equal(b.left_edge, region.left_edge)\n        assert_almost_equal(b.right_edge, region.right_edge)\n        assert_almost_equal(b.center, region.center)\n\n\ndef test_region_from_d():\n    ds = fake_amr_ds(fields=[\"density\"], units=[\"g/cm**3\"])\n    # We'll do a couple here\n\n    # First, no string units\n    reg1 = ds.r[0.2:0.3, 0.4:0.6, :]\n    reg2 = ds.region([0.25, 0.5, 0.5], [0.2, 0.4, 0.0], [0.3, 0.6, 1.0])\n    assert_equal(reg1[\"gas\", \"density\"], reg2[\"gas\", \"density\"])\n\n    # Now, string units in some -- 1.0 == cm\n    reg1 = ds.r[(0.1, \"cm\") : (0.5, \"cm\"), :, (0.25, \"cm\") : (0.35, \"cm\")]\n    reg2 = ds.region([0.3, 0.5, 0.3], [0.1, 0.0, 0.25], [0.5, 1.0, 0.35])\n    assert_equal(reg1[\"gas\", \"density\"], reg2[\"gas\", \"density\"])\n\n    # Now, string units in some -- 1.0 == cm\n    reg1 = ds.r[(0.1, \"cm\") : (0.5, \"cm\"), :, 0.25:0.35]\n    reg2 = ds.region([0.3, 0.5, 0.3], [0.1, 0.0, 0.25], [0.5, 1.0, 0.35])\n    assert_equal(reg1[\"gas\", \"density\"], reg2[\"gas\", \"density\"])\n\n    # And, lots of : usage!\n    reg1 = ds.r[:, :, :]\n    reg2 = ds.all_data()\n    assert_equal(reg1[\"gas\", \"density\"], reg2[\"gas\", \"density\"])\n\n    # Test slice as an index\n    reg1 = ds.r[0.1:0.8]\n    reg2 = ds.region([0.45, 0.45, 0.45], [0.1, 0.1, 0.1], [0.8, 0.8, 0.8])\n    assert_equal(reg1[\"gas\", \"density\"], reg2[\"gas\", \"density\"])\n\n    # Test with bad boundary initialization\n    with assert_raises(RuntimeError):\n        ds.r[0.3:0.1, 0.4:0.6, :]\n\n    # Test region by creating an arbitrary grid\n    reg1 = ds.r[0.15:0.55:16j, 0.25:0.65:32j, 0.35:0.75:64j]\n    left_edge = np.array([0.15, 0.25, 0.35])\n    right_edge = np.array([0.55, 0.65, 0.75])\n    dims = np.array([16.0, 32.0, 64.0])\n    reg2 = ds.arbitrary_grid(left_edge, right_edge, dims)\n    assert_equal(reg1[\"gas\", \"density\"], reg2[\"gas\", \"density\"])\n\n\ndef test_accessing_all_data():\n    # This will test first that we can access all_data, and next that we can\n    # access it multiple times and get the *same object*.\n    ds = fake_amr_ds(fields=[\"density\"], units=[\"g/cm**3\"])\n    dd = ds.all_data()\n    assert_equal(ds.r[\"gas\", \"density\"], dd[\"gas\", \"density\"])\n    # Now let's assert that it's the same object\n    rho = ds.r[\"gas\", \"density\"]\n    rho *= 2.0\n    assert_equal(dd[\"gas\", \"density\"] * 2.0, ds.r[\"gas\", \"density\"])\n    assert_equal(dd[\"gas\", \"density\"] * 2.0, ds.r[\"gas\", \"density\"])\n\n\ndef test_slice_from_r():\n    ds = fake_amr_ds(fields=[\"density\"], units=[\"g/cm**3\"])\n    sl1 = ds.r[0.5, :, :]\n    sl2 = ds.slice(\"x\", 0.5)\n    assert_equal(sl1[\"gas\", \"density\"], sl2[\"gas\", \"density\"])\n\n    frb1 = sl1.to_frb(width=1.0, height=1.0, resolution=(1024, 512))\n    frb2 = ds.r[0.5, ::1024j, ::512j]\n    assert_equal(frb1[\"gas\", \"density\"], frb2[\"gas\", \"density\"])\n\n    # Test slice which doesn't cover the whole domain\n    box = ds.box([0.0, 0.25, 0.25], [1.0, 0.75, 0.75])\n\n    sl3 = ds.r[0.5, 0.25:0.75, 0.25:0.75]\n    sl4 = ds.slice(\"x\", 0.5, data_source=box)\n    assert_equal(sl3[\"gas\", \"density\"], sl4[\"gas\", \"density\"])\n\n    frb3 = sl3.to_frb(width=0.5, height=0.5, resolution=(1024, 512))\n    frb4 = ds.r[0.5, 0.25:0.75:1024j, 0.25:0.75:512j]\n    assert_equal(frb3[\"gas\", \"density\"], frb4[\"gas\", \"density\"])\n\n    # Test off-center slice\n    offset_box = ds.box([0.0, 0.0, 0.4], [1.0, 0.5, 0.9])\n\n    sl5 = ds.r[0.5, 0:0.5, 0.4:0.9]\n    sl6 = ds.slice(\"x\", 0.5, data_source=offset_box)\n    assert_equal(sl5[\"gas\", \"density\"], sl6[\"gas\", \"density\"])\n\n    frb5 = sl5.to_frb(\n        width=0.5, height=0.5, resolution=(1024, 512), center=(0.5, 0.25, 0.65)\n    )\n    frb6 = ds.r[0.5, 0.0:0.5:1024j, 0.4:0.9:512j]\n    assert_equal(frb5[\"gas\", \"density\"], frb6[\"gas\", \"density\"])\n\n\ndef test_point_from_r():\n    ds = fake_amr_ds(fields=[\"density\"], units=[\"g/cm**3\"])\n    pt1 = ds.r[0.5, 0.3, 0.1]\n    pt2 = ds.point([0.5, 0.3, 0.1])\n    assert_equal(pt1[\"gas\", \"density\"], pt2[\"gas\", \"density\"])\n\n    # Test YTDimensionalityError\n    with assert_raises(YTDimensionalityError) as ex:\n        ds.r[0.5, 0.1]\n    assert_equal(str(ex.exception), \"Dimensionality specified was 2 but we need 3\")\n\n\ndef test_ray_from_r():\n    ds = fake_amr_ds(fields=[\"density\"], units=[\"g/cm**3\"])\n    ray1 = ds.r[(0.1, 0.2, 0.3) : (0.4, 0.5, 0.6)]\n    ray2 = ds.ray((0.1, 0.2, 0.3), (0.4, 0.5, 0.6))\n    assert_equal(ray1[\"gas\", \"density\"], ray2[\"gas\", \"density\"])\n\n    ray3 = ds.r[0.5 * ds.domain_left_edge : 0.5 * ds.domain_right_edge]\n    ray4 = ds.ray(0.5 * ds.domain_left_edge, 0.5 * ds.domain_right_edge)\n    assert_equal(ray3[\"gas\", \"density\"], ray4[\"gas\", \"density\"])\n\n    start = [(0.1, \"cm\"), 0.2, (0.3, \"cm\")]\n    end = [(0.5, \"cm\"), (0.4, \"cm\"), 0.6]\n    ray5 = ds.r[start:end]\n    start_arr = [ds.quan(0.1, \"cm\"), ds.quan(0.2, \"cm\"), ds.quan(0.3, \"cm\")]\n    end_arr = [ds.quan(0.5, \"cm\"), ds.quan(0.4, \"cm\"), ds.quan(0.6, \"cm\")]\n    ray6 = ds.ray(start_arr, end_arr)\n    assert_equal(ray5[\"gas\", \"density\"], ray6[\"gas\", \"density\"])\n\n    ray7 = ds.r[start:end:500j]\n    ray8 = LineBuffer(ds, [0.1, 0.2, 0.3], [0.5, 0.4, 0.6], 500)\n    assert_equal(ray7[\"gas\", \"density\"], ray8[\"gas\", \"density\"])\n\n\ndef test_ortho_ray_from_r():\n    ds = fake_amr_ds(fields=[\"density\"], units=[\"g/cm**3\"])\n    ray1 = ds.r[:, 0.3, 0.2]\n    ray2 = ds.ortho_ray(\"x\", [0.3, 0.2])\n    assert_equal(ray1[\"gas\", \"density\"], ray2[\"gas\", \"density\"])\n\n    # the y-coord is funny so test it too\n    ray3 = ds.r[0.3, :, 0.2]\n    ray4 = ds.ortho_ray(\"y\", [0.2, 0.3])\n    assert_equal(ray3[\"gas\", \"density\"], ray4[\"gas\", \"density\"])\n\n    # Test ray which doesn't cover the whole domain\n    box = ds.box([0.25, 0.0, 0.0], [0.75, 1.0, 1.0])\n    ray5 = ds.r[0.25:0.75, 0.3, 0.2]\n    ray6 = ds.ortho_ray(\"x\", [0.3, 0.2], data_source=box)\n    assert_equal(ray5[\"gas\", \"density\"], ray6[\"gas\", \"density\"])\n\n    # Test fixed-resolution rays\n    ray7 = ds.r[0.25:0.75:100j, 0.3, 0.2]\n    ray8 = LineBuffer(ds, [0.2525, 0.3, 0.2], [0.7475, 0.3, 0.2], 100)\n    assert_equal(ray7[\"gas\", \"density\"], ray8[\"gas\", \"density\"])\n\n\ndef test_particle_counts():\n    ds = fake_random_ds(16, particles=100)\n    assert ds.particle_type_counts == {\"io\": 100}\n\n    pds = fake_particle_ds(npart=128)\n    assert pds.particle_type_counts == {\"io\": 128}\n\n\ng30 = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\n\n@requires_module(\"h5py\")\n@requires_file(g30)\ndef test_checksum():\n    assert fake_random_ds(16).checksum == \"notafile\"\n    assert data_dir_load(g30).checksum == \"6169536e4b9f737ce3d3ad440df44c58\"\n"
  },
  {
    "path": "yt/data_objects/tests/test_derived_quantities.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nimport yt\nfrom yt import particle_filter\nfrom yt.testing import (\n    assert_rel_equal,\n    fake_particle_ds,\n    fake_random_ds,\n    fake_sph_orientation_ds,\n    requires_file,\n)\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_extrema():\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(\n            16,\n            nprocs=nprocs,\n            fields=(\"density\", \"velocity_x\", \"velocity_y\", \"velocity_z\"),\n            units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n        )\n        for sp in [ds.sphere(\"c\", (0.25, \"unitary\")), ds.r[0.5, :, :]]:\n            mi, ma = sp.quantities[\"Extrema\"]((\"gas\", \"density\"))\n            assert_equal(mi, np.nanmin(sp[\"gas\", \"density\"]))\n            assert_equal(ma, np.nanmax(sp[\"gas\", \"density\"]))\n            dd = ds.all_data()\n            mi, ma = dd.quantities[\"Extrema\"]((\"gas\", \"density\"))\n            assert_equal(mi, np.nanmin(dd[\"gas\", \"density\"]))\n            assert_equal(ma, np.nanmax(dd[\"gas\", \"density\"]))\n            sp = ds.sphere(\"max\", (0.25, \"unitary\"))\n            assert_equal(np.any(np.isnan(sp[\"gas\", \"radial_velocity\"])), False)\n            mi, ma = dd.quantities[\"Extrema\"]((\"gas\", \"radial_velocity\"))\n            assert_equal(mi, np.nanmin(dd[\"gas\", \"radial_velocity\"]))\n            assert_equal(ma, np.nanmax(dd[\"gas\", \"radial_velocity\"]))\n\n\ndef test_extrema_with_nan():\n    dens = np.ones((16, 16, 16))\n    dens[0, 0, 0] = np.nan\n    data = {\"density\": dens}\n    ds = yt.load_uniform_grid(data, data[\"density\"].shape)\n    ad = ds.all_data()\n    mi, ma = ad.quantities.extrema((\"stream\", \"density\"))\n    assert np.isnan(mi)\n    assert np.isnan(ma)\n    mi, ma = ad.quantities.extrema((\"stream\", \"density\"), check_finite=True)\n    assert mi == 1.0\n    assert ma == 1.0\n\n\ndef test_average():\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(16, nprocs=nprocs, fields=(\"density\",), units=(\"g/cm**3\",))\n        for ad in [ds.all_data(), ds.r[0.5, :, :]]:\n            my_mean = ad.quantities[\"WeightedAverageQuantity\"](\n                (\"gas\", \"density\"), (\"index\", \"ones\")\n            )\n            assert_rel_equal(my_mean, ad[\"gas\", \"density\"].mean(), 12)\n\n            my_mean = ad.quantities[\"WeightedAverageQuantity\"](\n                (\"gas\", \"density\"), (\"gas\", \"cell_mass\")\n            )\n            a_mean = (ad[\"gas\", \"density\"] * ad[\"gas\", \"cell_mass\"]).sum() / ad[\n                \"gas\", \"cell_mass\"\n            ].sum()\n            assert_rel_equal(my_mean, a_mean, 12)\n\n\ndef test_standard_deviation():\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(16, nprocs=nprocs, fields=(\"density\",), units=(\"g/cm**3\",))\n        for ad in [ds.all_data(), ds.r[0.5, :, :]]:\n            my_std, my_mean = ad.quantities[\"WeightedStandardDeviation\"](\n                (\"gas\", \"density\"), (\"index\", \"ones\")\n            )\n            assert_rel_equal(my_mean, ad[\"gas\", \"density\"].mean(), 12)\n            assert_rel_equal(my_std, ad[\"gas\", \"density\"].std(), 12)\n\n            my_std, my_mean = ad.quantities[\"WeightedStandardDeviation\"](\n                (\"gas\", \"density\"), (\"gas\", \"cell_mass\")\n            )\n            a_mean = (ad[\"gas\", \"density\"] * ad[\"gas\", \"cell_mass\"]).sum() / ad[\n                \"gas\", \"cell_mass\"\n            ].sum()\n            assert_rel_equal(my_mean, a_mean, 12)\n            a_std = np.sqrt(\n                (ad[\"gas\", \"cell_mass\"] * (ad[\"gas\", \"density\"] - a_mean) ** 2).sum()\n                / ad[\"gas\", \"cell_mass\"].sum()\n            )\n            assert_rel_equal(my_std, a_std, 12)\n\n\ndef test_max_location():\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(16, nprocs=nprocs, fields=(\"density\",), units=(\"g/cm**3\",))\n        for ad in [ds.all_data(), ds.r[0.5, :, :]]:\n            mv, x, y, z = ad.quantities.max_location((\"gas\", \"density\"))\n\n            assert_equal(mv, ad[\"gas\", \"density\"].max())\n\n            mi = np.argmax(ad[\"gas\", \"density\"])\n\n            assert_equal(ad[\"index\", \"x\"][mi], x)\n            assert_equal(ad[\"index\", \"y\"][mi], y)\n            assert_equal(ad[\"index\", \"z\"][mi], z)\n\n\ndef test_min_location():\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(16, nprocs=nprocs, fields=(\"density\",), units=(\"g/cm**3\",))\n        for ad in [ds.all_data(), ds.r[0.5, :, :]]:\n            mv, x, y, z = ad.quantities.min_location((\"gas\", \"density\"))\n\n            assert_equal(mv, ad[\"gas\", \"density\"].min())\n\n            mi = np.argmin(ad[\"gas\", \"density\"])\n\n            assert_equal(ad[\"index\", \"x\"][mi], x)\n            assert_equal(ad[\"index\", \"y\"][mi], y)\n            assert_equal(ad[\"index\", \"z\"][mi], z)\n\n\ndef test_sample_at_min_field_values():\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(\n            16,\n            nprocs=nprocs,\n            fields=(\"density\", \"temperature\", \"velocity_x\"),\n            units=(\"g/cm**3\", \"K\", \"cm/s\"),\n        )\n        for ad in [ds.all_data(), ds.r[0.5, :, :]]:\n            mv, temp, vm = ad.quantities.sample_at_min_field_values(\n                (\"gas\", \"density\"), [(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")]\n            )\n\n            assert_equal(mv, ad[\"gas\", \"density\"].min())\n\n            mi = np.argmin(ad[\"gas\", \"density\"])\n\n            assert_equal(ad[\"gas\", \"temperature\"][mi], temp)\n            assert_equal(ad[\"gas\", \"velocity_x\"][mi], vm)\n\n\ndef test_sample_at_max_field_values():\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(\n            16,\n            nprocs=nprocs,\n            fields=(\"density\", \"temperature\", \"velocity_x\"),\n            units=(\"g/cm**3\", \"K\", \"cm/s\"),\n        )\n        for ad in [ds.all_data(), ds.r[0.5, :, :]]:\n            mv, temp, vm = ad.quantities.sample_at_max_field_values(\n                (\"gas\", \"density\"), [(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")]\n            )\n\n            assert_equal(mv, ad[\"gas\", \"density\"].max())\n\n            mi = np.argmax(ad[\"gas\", \"density\"])\n\n            assert_equal(ad[\"gas\", \"temperature\"][mi], temp)\n            assert_equal(ad[\"gas\", \"velocity_x\"][mi], vm)\n\n\ndef test_in_memory_sph_derived_quantities():\n    ds = fake_sph_orientation_ds()\n    ad = ds.all_data()\n\n    ang_mom = ad.quantities.angular_momentum_vector()\n    assert_equal(ang_mom, [0, 0, 0])\n\n    bv = ad.quantities.bulk_velocity()\n    assert_equal(bv, [0, 0, 0])\n\n    com = ad.quantities.center_of_mass()\n    assert_equal(com, [1 / 7, (1 + 2) / 7, (1 + 2 + 3) / 7])\n\n    ex = ad.quantities.extrema([(\"io\", \"x\"), (\"io\", \"y\"), (\"io\", \"z\")])\n    for fex, ans in zip(ex, [[0, 1], [0, 2], [0, 3]], strict=True):\n        assert_equal(fex, ans)\n\n    for d, v, l in [\n        (\"x\", 1, [1, 0, 0]),\n        (\"y\", 2, [0, 2, 0]),\n        (\"z\", 3, [0, 0, 3]),\n    ]:\n        max_d, x, y, z = ad.quantities.max_location((\"io\", d))\n        assert_equal(max_d, v)\n        assert_equal([x, y, z], l)\n\n    for d in \"xyz\":\n        min_d, x, y, z = ad.quantities.min_location((\"io\", d))\n        assert_equal(min_d, 0)\n        assert_equal([x, y, z], [0, 0, 0])\n\n    tot_m = ad.quantities.total_mass()\n    assert_equal(tot_m, [7, 0])\n\n    weighted_av_z = ad.quantities.weighted_average_quantity((\"io\", \"z\"), (\"io\", \"z\"))\n    assert_equal(weighted_av_z, 7 / 3)\n\n\niso_collapse = \"IsothermalCollapse/snap_505\"\ntipsy_gal = \"TipsyGalaxy/galaxy.00300\"\n\n\n@requires_file(iso_collapse)\n@requires_file(tipsy_gal)\ndef test_sph_datasets_derived_quantities():\n    for fname in [tipsy_gal, iso_collapse]:\n        ds = yt.load(fname)\n        ad = ds.all_data()\n        use_particles = \"nbody\" in ds.particle_types\n        ad.quantities.angular_momentum_vector()\n        ad.quantities.bulk_velocity(True, use_particles)\n        ad.quantities.center_of_mass(True, use_particles)\n        ad.quantities.extrema([(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n        ad.quantities.min_location((\"gas\", \"density\"))\n        ad.quantities.max_location((\"gas\", \"density\"))\n        ad.quantities.total_mass()\n        ad.quantities.weighted_average_quantity((\"gas\", \"density\"), (\"gas\", \"mass\"))\n\n\ndef test_derived_quantities_with_particle_types():\n    ds = fake_particle_ds()\n\n    @particle_filter(requires=[\"particle_position_x\"], filtered_type=\"all\")\n    def low_x(pfilter, data):\n        return (\n            data[pfilter.filtered_type, \"particle_position_x\"].in_units(\"code_length\")\n            < 0.5\n        )\n\n    ds.add_particle_filter(\"low_x\")\n\n    ad = ds.all_data()\n\n    for ptype in [\"all\", \"low_x\"]:\n        # Check bulk velocity\n        bulk_vx = (\n            ad[ptype, \"particle_mass\"]\n            * ad[ptype, \"particle_velocity_x\"]\n            / ad[ptype, \"particle_mass\"].sum()\n        ).sum()\n        assert_almost_equal(\n            ad.quantities.bulk_velocity(\n                use_gas=False, use_particles=True, particle_type=ptype\n            )[0],\n            bulk_vx,\n            5,\n        )\n\n        # Check center of mass\n        com_x = (\n            ad[ptype, \"particle_mass\"]\n            * ad[ptype, \"particle_position_x\"]\n            / ad[ptype, \"particle_mass\"].sum()\n        ).sum()\n        assert_almost_equal(\n            ad.quantities.center_of_mass(\n                use_gas=False, use_particles=True, particle_type=ptype\n            )[0],\n            com_x,\n            5,\n        )\n\n        # Check angular momentum vector\n        l_x = (\n            ad[ptype, \"particle_specific_angular_momentum_x\"]\n            * ad[ptype, \"particle_mass\"]\n            / ad[ptype, \"particle_mass\"].sum()\n        ).sum()\n        assert_almost_equal(\n            ad.quantities.angular_momentum_vector(\n                use_gas=False, use_particles=True, particle_type=ptype\n            )[0],\n            l_x,\n            5,\n        )\n\n    # Check spin parameter values\n    assert_almost_equal(\n        ad.quantities.spin_parameter(use_gas=False, use_particles=True),\n        655.7311454765503,\n    )\n    assert_almost_equal(\n        ad.quantities.spin_parameter(\n            use_gas=False, use_particles=True, particle_type=\"low_x\"\n        ),\n        1309.164886405665,\n    )\n"
  },
  {
    "path": "yt/data_objects/tests/test_disks.py",
    "content": "import pytest\n\nfrom yt import YTQuantity\nfrom yt.testing import fake_random_ds\n\n\ndef test_bad_disk_input():\n    # Fixes 1768\n    ds = fake_random_ds(16)\n\n    # Test invalid 3d array\n    with pytest.raises(\n        TypeError,\n        match=r\"^Expected an array of size \\(3,\\), received 'list' of length 4$\",\n    ):\n        ds.disk(ds.domain_center, [0, 0, 1, 1], (10, \"kpc\"), (20, \"kpc\"))\n\n    # Test invalid float\n    with pytest.raises(\n        TypeError,\n        match=(\n            r\"^Expected a numeric value \\(or size-1 array\\), \"\n            r\"received 'unyt.array.unyt_array' of length 3$\"\n        ),\n    ):\n        ds.disk(ds.domain_center, [0, 0, 1], ds.domain_center, (20, \"kpc\"))\n\n    # Test invalid float\n    with pytest.raises(\n        TypeError,\n        match=(\n            r\"^Expected a numeric value \\(or tuple of format \\(float, String\\)\\), \"\n            r\"received an inconsistent tuple '\\(10, 10\\)'.$\"\n        ),\n    ):\n        ds.disk(ds.domain_center, [0, 0, 1], (10, 10), (20, \"kpc\"))\n\n    # Test invalid iterable\n    with pytest.raises(\n        TypeError,\n        match=r\"^Expected an iterable object, received 'unyt\\.array\\.unyt_quantity'$\",\n    ):\n        ds.disk(\n            ds.domain_center,\n            [0, 0, 1],\n            (10, \"kpc\"),\n            (20, \"kpc\"),\n            fields=YTQuantity(1, \"kpc\"),\n        )\n\n    # Test invalid object\n    with pytest.raises(\n        TypeError,\n        match=(\n            r\"^Expected an object of 'yt\\.data_objects\\.static_output\\.Dataset' type, \"\n            r\"received 'yt\\.data_objects\\.selection_objects\\.region\\.YTRegion'$\"\n        ),\n    ):\n        ds.disk(ds.domain_center, [0, 0, 1], (10, \"kpc\"), (20, \"kpc\"), ds=ds.all_data())\n\n    # Test valid disk\n    ds.disk(ds.domain_center, [0, 0, 1], (10, \"kpc\"), (20, \"kpc\"))\n    ds.disk(ds.domain_center, [0, 0, 1], 10, (20, \"kpc\"))\n"
  },
  {
    "path": "yt/data_objects/tests/test_ellipsoid.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_less\n\nfrom yt.testing import fake_random_ds\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"log_level\"] = 50\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef _difference(x1, x2, dw):\n    rel = x1 - x2\n    rel[rel > dw / 2.0] -= dw\n    rel[rel < -dw / 2.0] += dw\n    return rel\n\n\ndef test_ellipsoid():\n    # We decompose in different ways\n    cs = [\n        np.array([0.5, 0.5, 0.5]),\n        np.array([0.1, 0.2, 0.3]),\n        np.array([0.8, 0.8, 0.8]),\n    ]\n    np.random.seed(0x4D3D3D3)\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(64, nprocs=nprocs)\n        DW = ds.domain_right_edge - ds.domain_left_edge\n        min_dx = 2.0 / ds.domain_dimensions\n        ABC = np.random.random((3, 12)) * 0.1\n        e0s = np.random.random((3, 12))\n        tilts = np.random.random(12)\n        ABC[:, 0] = 0.1\n        for i in range(12):\n            for c in cs:\n                A, B, C = sorted(ABC[:, i], reverse=True)\n                A = max(A, min_dx[0])\n                B = max(B, min_dx[1])\n                C = max(C, min_dx[2])\n                e0 = e0s[:, i]\n                tilt = tilts[i]\n                ell = ds.ellipsoid(c, A, B, C, e0, tilt)\n                assert_array_less(ell[\"index\", \"radius\"], A)\n                p = np.array([ell[\"index\", ax] for ax in \"xyz\"])\n                dot_evec = [np.zeros_like(ell[\"index\", \"radius\"]) for i in range(3)]\n                vecs = [ell._e0, ell._e1, ell._e2]\n                mags = [ell._A, ell._B, ell._C]\n                my_c = np.array([c] * p.shape[1]).transpose()\n                dot_evec = [de.to_ndarray() for de in dot_evec]\n                mags = [m.to_ndarray() for m in mags]\n                for ax_i in range(3):\n                    dist = _difference(p[ax_i, :], my_c[ax_i, :], DW[ax_i])\n                    for ax_j in range(3):\n                        dot_evec[ax_j] += dist * vecs[ax_j][ax_i]\n                dist = 0\n                for ax_i in range(3):\n                    dist += dot_evec[ax_i] ** 2.0 / mags[ax_i] ** 2.0\n                assert_array_less(dist, 1.0)\n"
  },
  {
    "path": "yt/data_objects/tests/test_exclude_functions.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.loaders import load_uniform_grid\nfrom yt.testing import fake_random_ds\n\n\ndef test_exclude_above():\n    the_ds = fake_random_ds(ndims=3)\n    all_data = the_ds.all_data()\n    new_ds = all_data.exclude_above((\"gas\", \"density\"), 1)\n    assert_equal(new_ds[\"gas\", \"density\"], all_data[\"gas\", \"density\"])\n    new_ds = all_data.exclude_above((\"gas\", \"density\"), 1e6, \"g/m**3\")\n    assert_equal(new_ds[\"gas\", \"density\"], all_data[\"gas\", \"density\"])\n    new_ds = all_data.exclude_above((\"gas\", \"density\"), 0)\n    assert_equal(new_ds[\"gas\", \"density\"], [])\n\n\ndef test_exclude_below():\n    the_ds = fake_random_ds(ndims=3)\n    all_data = the_ds.all_data()\n    new_ds = all_data.exclude_below((\"gas\", \"density\"), 1)\n    assert_equal(new_ds[\"gas\", \"density\"], [])\n    new_ds = all_data.exclude_below((\"gas\", \"density\"), 1e6, \"g/m**3\")\n    assert_equal(new_ds[\"gas\", \"density\"], [])\n    new_ds = all_data.exclude_below((\"gas\", \"density\"), 0)\n    assert_equal(new_ds[\"gas\", \"density\"], all_data[\"gas\", \"density\"])\n\n\ndef test_exclude_nan():\n    test_array = np.nan * np.ones((10, 10, 10))\n    test_array[1, 1, :] = 1\n    data = {\"density\": test_array}\n    ds = load_uniform_grid(data, test_array.shape, length_unit=\"cm\", nprocs=1)\n    ad = ds.all_data()\n    no_nan_ds = ad.exclude_nan((\"gas\", \"density\"))\n    assert_equal(no_nan_ds[\"gas\", \"density\"], np.array(np.ones(10)))\n\n\ndef test_equal():\n    test_array = np.ones((10, 10, 10))\n    test_array[1, 1, :] = 2.0\n    test_array[2, 1, :] = 3.0\n    data = {\"density\": test_array}\n    ds = load_uniform_grid(data, test_array.shape, length_unit=\"cm\", nprocs=1)\n    ad = ds.all_data()\n    no_ones = ad.exclude_equal((\"gas\", \"density\"), 1.0)\n    assert np.all(no_ones[\"gas\", \"density\"] != 1.0)\n    only_ones = ad.include_equal((\"gas\", \"density\"), 1.0)\n    assert np.all(only_ones[\"gas\", \"density\"] == 1.0)\n\n\ndef test_inside_outside():\n    test_array = np.ones((10, 10, 10))\n    test_array[1, 1, :] = 2.0\n    test_array[2, 1, :] = 3.0\n    data = {\"density\": test_array}\n    ds = load_uniform_grid(data, test_array.shape, length_unit=\"cm\", nprocs=1)\n    ad = ds.all_data()\n\n    only_ones_and_twos = ad.include_inside((\"gas\", \"density\"), 0.9, 2.1)\n    assert np.all(only_ones_and_twos[\"gas\", \"density\"] != 3.0)\n    assert len(only_ones_and_twos[\"gas\", \"density\"]) == 990\n\n    only_ones_and_twos = ad.exclude_outside((\"gas\", \"density\"), 0.9, 2.1)\n    assert len(only_ones_and_twos[\"gas\", \"density\"]) == 990\n    assert np.all(only_ones_and_twos[\"gas\", \"density\"] != 3.0)\n\n    only_threes = ad.include_outside((\"gas\", \"density\"), 0.9, 2.1)\n    assert np.all(only_threes[\"gas\", \"density\"] == 3)\n    assert len(only_threes[\"gas\", \"density\"]) == 10\n\n    only_threes = ad.include_outside((\"gas\", \"density\"), 0.9, 2.1)\n    assert np.all(only_threes[\"gas\", \"density\"] == 3)\n    assert len(only_threes[\"gas\", \"density\"]) == 10\n\n    # Repeat, but convert units to g/m**3\n    only_ones_and_twos = ad.include_inside((\"gas\", \"density\"), 0.9e6, 2.1e6, \"g/m**3\")\n    assert np.all(only_ones_and_twos[\"gas\", \"density\"] != 3.0)\n    assert len(only_ones_and_twos[\"gas\", \"density\"]) == 990\n\n    only_ones_and_twos = ad.exclude_outside((\"gas\", \"density\"), 0.9e6, 2.1e6, \"g/m**3\")\n    assert len(only_ones_and_twos[\"gas\", \"density\"]) == 990\n    assert np.all(only_ones_and_twos[\"gas\", \"density\"] != 3.0)\n\n    only_threes = ad.include_outside((\"gas\", \"density\"), 0.9e6, 2.1e6, \"g/m**3\")\n    assert np.all(only_threes[\"gas\", \"density\"] == 3)\n    assert len(only_threes[\"gas\", \"density\"]) == 10\n\n    only_threes = ad.include_outside((\"gas\", \"density\"), 0.9e6, 2.1e6, \"g/m**3\")\n    assert np.all(only_threes[\"gas\", \"density\"] == 3)\n    assert len(only_threes[\"gas\", \"density\"]) == 10\n"
  },
  {
    "path": "yt/data_objects/tests/test_extract_regions.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_array_less, assert_equal\n\nfrom yt.loaders import load\nfrom yt.testing import (\n    fake_amr_ds,\n    fake_random_ds,\n    requires_file,\n    requires_module,\n)\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_cut_region():\n    # We decompose in different ways\n    for nprocs in [1, 2, 4, 8]:\n        ds = fake_random_ds(\n            64,\n            nprocs=nprocs,\n            fields=(\"density\", \"temperature\", \"velocity_x\"),\n            units=(\"g/cm**3\", \"K\", \"cm/s\"),\n        )\n        # We'll test two objects\n        dd = ds.all_data()\n        r = dd.cut_region(\n            [\n                \"obj['gas', 'temperature'] > 0.5\",\n                \"obj['gas', 'density'] < 0.75\",\n                \"obj['gas', 'velocity_x'] > 0.25\",\n            ]\n        )\n        t = (\n            (dd[\"gas\", \"temperature\"] > 0.5)\n            & (dd[\"gas\", \"density\"] < 0.75)\n            & (dd[\"gas\", \"velocity_x\"] > 0.25)\n        )\n        assert_equal(np.all(r[\"gas\", \"temperature\"] > 0.5), True)\n        assert_equal(np.all(r[\"gas\", \"density\"] < 0.75), True)\n        assert_equal(np.all(r[\"gas\", \"velocity_x\"] > 0.25), True)\n        assert_equal(np.sort(dd[\"gas\", \"density\"][t]), np.sort(r[\"gas\", \"density\"]))\n        assert_equal(np.sort(dd[\"index\", \"x\"][t]), np.sort(r[\"index\", \"x\"]))\n        r2 = r.cut_region([\"obj['gas', 'temperature'] < 0.75\"])\n        t2 = r[\"gas\", \"temperature\"] < 0.75\n        assert_equal(\n            np.sort(r2[\"gas\", \"temperature\"]), np.sort(r[\"gas\", \"temperature\"][t2])\n        )\n        assert_equal(np.all(r2[\"gas\", \"temperature\"] < 0.75), True)\n\n        # Now we can test some projections\n        dd = ds.all_data()\n        cr = dd.cut_region([\"obj['index', 'ones'] > 0\"])\n        for weight in [None, (\"gas\", \"density\")]:\n            p1 = ds.proj((\"gas\", \"density\"), 0, data_source=dd, weight_field=weight)\n            p2 = ds.proj((\"gas\", \"density\"), 0, data_source=cr, weight_field=weight)\n            for f in p1.field_data:\n                assert_almost_equal(p1[f], p2[f])\n        cr = dd.cut_region([\"obj['gas', 'density'] > 0.25\"])\n        p2 = ds.proj((\"gas\", \"density\"), 2, data_source=cr)\n        assert_equal(p2[\"gas\", \"density\"].max() > 0.25, True)\n        p2 = ds.proj(\n            (\"gas\", \"density\"), 2, data_source=cr, weight_field=(\"gas\", \"density\")\n        )\n        assert_equal(p2[\"gas\", \"density\"].max() > 0.25, True)\n\n\ndef test_cut_region_simple_syntax():\n    ds = fake_random_ds(\n        64,\n        fields=(\"density\", \"temperature\", \"velocity_x\"),\n        units=(\"g/cm**3\", \"K\", \"cm/s\"),\n    )\n    ad = ds.all_data()\n    rho_thresh = ds.quan(0.5, \"g/cm**3\")\n    T_thresh = ds.quan(0.5, \"K\")\n    low_density = ad.cut_region(ds.fields.gas.density < rho_thresh)\n    low_density_high_T = ad.cut_region(\n        [\n            ds.fields.gas.density < rho_thresh,\n            ds.fields.gas.temperature > T_thresh,\n        ]\n    )\n    reg = ad.cut_region(ds.fields.gas.density / ds.fields.gas.temperature > 1)\n\n    # Make sure the low density only contains low density, but should\n    # also contain low temperature\n    assert_array_less(low_density[\"gas\", \"density\"], rho_thresh)\n    assert low_density[\"gas\", \"temperature\"].min() < T_thresh\n\n    # Make sure the low density, high-T is correctly selected\n    assert_array_less(low_density_high_T[\"gas\", \"density\"], rho_thresh)\n    assert_array_less(T_thresh, low_density_high_T[\"gas\", \"temperature\"])\n\n    # Make sure the ratio of density to temperature is larger than one\n    assert_array_less(1, reg[\"gas\", \"density\"] / reg[\"gas\", \"temperature\"])\n\n\ndef test_region_and_particles():\n    ds = fake_amr_ds(particles=10000)\n\n    ad = ds.all_data()\n    reg = ad.cut_region('obj[\"index\", \"x\"] < .5')\n\n    mask = ad[\"all\", \"particle_position_x\"] < 0.5\n    expected = np.sort(ad[\"all\", \"particle_position_x\"][mask].value)\n    result = np.sort(reg[\"all\", \"particle_position_x\"])\n\n    assert_equal(expected.shape, result.shape)\n    assert_equal(expected, result)\n\n\nISOGAL = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\n\n@requires_module(\"h5py\")\n@requires_file(ISOGAL)\ndef test_region_chunked_read():\n    # see #2104\n    ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    sp = ds.sphere((0.5, 0.5, 0.5), (2, \"kpc\"))\n    dense_sp = sp.cut_region(['obj[\"gas\", \"H_p0_number_density\"]>= 1e-2'])\n    dense_sp.quantities.angular_momentum_vector()\n\n\n@requires_module(\"h5py\")\n@requires_file(ISOGAL)\ndef test_chained_cut_region():\n    # see Issue #2233\n    ds = load(ISOGAL)\n    base = ds.disk([0.5, 0.5, 0.5], [0, 0, 1], (4, \"kpc\"), (10, \"kpc\"))\n    c1 = \"(obj['index', 'cylindrical_radius'].in_units('kpc') > 2.0)\"\n    c2 = \"(obj['gas', 'density'].to('g/cm**3') > 1e-26)\"\n\n    cr12 = base.cut_region([c1, c2])\n    cr1 = base.cut_region([c1])\n    cr12c = cr1.cut_region([c2])\n\n    field = (\"index\", \"cell_volume\")\n    assert_equal(\n        cr12.quantities.total_quantity(field), cr12c.quantities.total_quantity(field)\n    )\n"
  },
  {
    "path": "yt/data_objects/tests/test_firefly.py",
    "content": "import numpy as np\nimport pytest\nfrom numpy.testing import assert_array_equal\n\nfrom yt.testing import fake_particle_ds, requires_module\nfrom yt.utilities.exceptions import YTFieldNotFound\n\n\n@requires_module(\"firefly\")\ndef test_firefly_JSON_string():\n    ds = fake_particle_ds()\n    ad = ds.all_data()\n    reader = ad.create_firefly_object(\n        None,\n        velocity_units=\"cm/s\",\n        coordinate_units=\"cm\",\n    )\n\n    reader.writeToDisk(write_to_disk=False, file_extension=\".json\")\n\n    ## reader.JSON was not output to string correctly\n    ##  either Firefly is damaged or needs a hotfix-- try reinstalling.\n    ##  if that doesn't work contact the developers\n    ##  at github.com/ageller/Firefly/issues.\n    assert len(reader.JSON) > 0\n\n\n@requires_module(\"firefly\")\ndef test_firefly_write_to_disk(tmp_path):\n    tmpdir = str(tmp_path)  # create_firefly_object needs a str, not PosixPath\n\n    ds = fake_particle_ds()\n    ad = ds.all_data()\n    reader = ad.create_firefly_object(\n        tmpdir,\n        velocity_units=\"cm/s\",\n        coordinate_units=\"cm\",\n        match_any_particle_types=True,  # Explicitly specifying to avoid deprecation warning\n    )\n\n    reader.writeToDisk()\n\n\n@pytest.fixture\ndef firefly_test_dataset():\n    # create dataset\n    ds_fields = [\n        # Assumed present\n        (\"pt1\", \"particle_position_x\"),\n        (\"pt1\", \"particle_position_y\"),\n        (\"pt1\", \"particle_position_z\"),\n        (\"pt2\", \"particle_position_x\"),\n        (\"pt2\", \"particle_position_y\"),\n        (\"pt2\", \"particle_position_z\"),\n        # User input\n        (\"pt1\", \"common_field\"),\n        (\"pt2\", \"common_field\"),\n        (\"pt2\", \"pt2only_field\"),\n    ]\n    ds_field_units = [\"code_length\"] * 9\n    ds_negative = [0] * 9\n    ds = fake_particle_ds(\n        fields=ds_fields,\n        units=ds_field_units,\n        negative=ds_negative,\n    )\n    return ds\n\n\n@requires_module(\"firefly\")\n@pytest.mark.parametrize(\n    \"fields_to_include,fields_units\",\n    [\n        (None, None),  # Test default values\n        ([], []),  # Test empty fields\n    ],\n)\ndef test_field_empty_specification(\n    firefly_test_dataset, fields_to_include, fields_units\n):\n    dd = firefly_test_dataset.all_data()\n    reader = dd.create_firefly_object(\n        fields_to_include=fields_to_include,\n        fields_units=fields_units,\n        coordinate_units=\"code_length\",\n    )\n    assert_array_equal(\n        dd[\"pt1\", \"relative_particle_position\"].d,\n        reader.particleGroups[0].coordinates,\n    )\n    assert_array_equal(\n        dd[\"pt2\", \"relative_particle_position\"].d,\n        reader.particleGroups[1].coordinates,\n    )\n\n\n@requires_module(\"firefly\")\ndef test_field_unique_string_specification(firefly_test_dataset):\n    # Test unique field (pt2only_field)\n    dd = firefly_test_dataset.all_data()\n    # Unique field string will fallback to \"all\" field type and fail\n    # as nonexistent (\"all\", \"pt2only_field\") unless we set\n    # match_any_particle_types=True\n    reader = dd.create_firefly_object(\n        fields_to_include=[\"pt2only_field\"],\n        fields_units=[\"code_length\"],\n        coordinate_units=\"code_length\",\n        match_any_particle_types=True,\n    )\n\n    pt1 = reader.particleGroups[0]\n    pt2 = reader.particleGroups[1]\n    assert_array_equal(\n        dd[\"pt1\", \"relative_particle_position\"].d,\n        pt1.coordinates,\n    )\n    assert_array_equal(\n        dd[\"pt2\", \"relative_particle_position\"].d,\n        pt2.coordinates,\n    )\n    assert \"pt2only_field\" not in pt1.field_names\n    assert \"pt2only_field\" in pt2.field_names\n    arrind = np.flatnonzero(pt2.field_names == \"pt2only_field\")[0]\n    assert_array_equal(dd[\"pt2\", \"pt2only_field\"].d, pt2.field_arrays[arrind])\n\n\n@requires_module(\"firefly\")\ndef test_field_common_string_specification(firefly_test_dataset):\n    # Test common field (common_field)\n    dd = firefly_test_dataset.all_data()\n    # Common field string will be ambiguous and fail\n    # unless we set match_any_particle_types=True\n    reader = dd.create_firefly_object(\n        fields_to_include=[\"common_field\"],\n        fields_units=[\"code_length\"],\n        coordinate_units=\"code_length\",\n        match_any_particle_types=True,\n    )\n\n    pt1 = reader.particleGroups[0]\n    pt2 = reader.particleGroups[1]\n    assert_array_equal(\n        dd[\"pt1\", \"relative_particle_position\"].d,\n        pt1.coordinates,\n    )\n    assert_array_equal(\n        dd[\"pt2\", \"relative_particle_position\"].d,\n        pt2.coordinates,\n    )\n    assert \"common_field\" in pt1.field_names\n    assert \"common_field\" in pt2.field_names\n    arrind = np.flatnonzero(pt1.field_names == \"common_field\")[0]\n    assert_array_equal(dd[\"pt1\", \"common_field\"].d, pt1.field_arrays[arrind])\n    arrind = np.flatnonzero(pt2.field_names == \"common_field\")[0]\n    assert_array_equal(dd[\"pt2\", \"common_field\"].d, pt2.field_arrays[arrind])\n\n\n@requires_module(\"firefly\")\n@pytest.mark.parametrize(\n    \"fields_to_include,fields_units\",\n    [\n        (\n            [(\"pt2\", \"pt2only_field\")],\n            [\"code_length\"],\n        ),  # Test existing field tuple (pt2, pt2only_field)\n        (\n            [(\"pt1\", \"common_field\")],\n            [\"code_length\"],\n        ),  # Test that tuples only bring in referenced particleGroup\n        (\n            [(\"all\", \"common_field\")],\n            [\"code_length\"],\n        ),  # Test that \"all\" brings in all particleGroups\n    ],\n)\ndef test_field_tuple_specification(\n    firefly_test_dataset,\n    fields_to_include,\n    fields_units,\n):\n    dd = firefly_test_dataset.all_data()\n    reader = dd.create_firefly_object(\n        fields_to_include=fields_to_include,\n        fields_units=fields_units,\n        coordinate_units=\"code_length\",\n    )\n    assert_array_equal(\n        dd[\"pt1\", \"relative_particle_position\"].d,\n        reader.particleGroups[0].coordinates,\n    )\n    assert_array_equal(\n        dd[\"pt2\", \"relative_particle_position\"].d,\n        reader.particleGroups[1].coordinates,\n    )\n    all_pgs = reader.particleGroups\n    all_pgs_names = [\"pt1\", \"pt2\"]\n    for field in fields_to_include:\n        ftype, fname = field\n        for pgi in range(2):\n            pg = all_pgs[pgi]\n            if ftype == all_pgs_names[pgi]:\n                assert fname in pg.field_names\n                arrind = np.flatnonzero(pg.field_names == fname)[0]\n                assert_array_equal(dd[field].d, pg.field_arrays[arrind])\n            elif ftype == \"all\":\n                assert fname in pg.field_names\n                this_pg_name = all_pgs_names[pgi]\n                arrind = np.flatnonzero(pg.field_names == fname)[0]\n                assert_array_equal(dd[this_pg_name, fname].d, pg.field_arrays[arrind])\n            else:\n                assert fname not in pg.field_names\n\n\n@requires_module(\"firefly\")\n@pytest.mark.parametrize(\n    \"fields_to_include,fields_units,ErrorType\",\n    [\n        (\n            [\"dinos\"],\n            [\"code_length\"],\n            YTFieldNotFound,\n        ),  # Test nonexistent field (dinos)\n        (\n            [\"common_field\"],\n            [\"code_length\"],\n            ValueError,\n        ),  # Test ambiguous field (match_any_particle_types=False)\n        (\n            [(\"pt1\", \"pt2only_field\")],\n            [\"code_length\"],\n            YTFieldNotFound,\n        ),  # Test nonexistent field tuple (pt1, pt2only_field)\n    ],\n)\ndef test_field_invalid_specification(\n    firefly_test_dataset, fields_to_include, fields_units, ErrorType\n):\n    dd = firefly_test_dataset.all_data()\n    # Note that we have specified match_any_particle_types as False since\n    # that is the behavior expected in the future\n    with pytest.raises(ErrorType):\n        dd.create_firefly_object(\n            fields_to_include=fields_to_include,\n            fields_units=fields_units,\n            coordinate_units=\"code_length\",\n            match_any_particle_types=False,\n        )\n\n\n@requires_module(\"firefly\")\ndef test_field_mixed_specification(firefly_test_dataset):\n    dd = firefly_test_dataset.all_data()\n\n    reader = dd.create_firefly_object(\n        fields_to_include=[\"pt2only_field\", (\"pt1\", \"common_field\")],\n        fields_units=[\"code_length\", \"code_length\"],\n    )\n\n    pt1 = reader.particleGroups[0]\n    pt2 = reader.particleGroups[1]\n    assert \"common_field\" in pt1.field_names\n    assert \"common_field\" not in pt2.field_names\n    arrind = np.flatnonzero(pt1.field_names == \"common_field\")[0]\n    assert_array_equal(dd[\"pt1\", \"common_field\"].d, pt1.field_arrays[arrind])\n\n    assert \"pt2only_field\" not in pt1.field_names\n    assert \"pt2only_field\" in pt2.field_names\n    arrind = np.flatnonzero(pt2.field_names == \"pt2only_field\")[0]\n    assert_array_equal(dd[\"pt2\", \"pt2only_field\"].d, pt2.field_arrays[arrind])\n"
  },
  {
    "path": "yt/data_objects/tests/test_fluxes.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.testing import fake_random_ds\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_flux_calculation():\n    ds = fake_random_ds(64, nprocs=4)\n    dd = ds.all_data()\n    surf = ds.surface(dd, (\"index\", \"x\"), 0.51)\n    assert_equal(surf[\"index\", \"x\"], 0.51)\n    flux = surf.calculate_flux(\n        (\"index\", \"ones\"), (\"index\", \"zeros\"), (\"index\", \"zeros\"), (\"index\", \"ones\")\n    )\n    assert_almost_equal(flux.value, 1.0, 12)\n    assert_equal(str(flux.units), \"cm**2\")\n    flux2 = surf.calculate_flux(\n        (\"index\", \"ones\"), (\"index\", \"zeros\"), (\"index\", \"zeros\")\n    )\n    assert_almost_equal(flux2.value, 1.0, 12)\n    assert_equal(str(flux2.units), \"cm**2\")\n\n\ndef test_sampling():\n    ds = fake_random_ds(64, nprocs=4)\n    dd = ds.all_data()\n    for i, ax in enumerate([(\"index\", \"x\"), (\"index\", \"y\"), (\"index\", \"z\")]):\n        surf = ds.surface(dd, ax, 0.51)\n        surf.get_data(ax, sample_type=\"vertex\")\n        assert_equal(surf.vertex_samples[ax], surf.vertices[i, :])\n        assert_equal(str(surf.vertices.units), \"code_length\")\n        dens = surf[\"gas\", \"density\"]\n        vert_shape = surf.vertices.shape\n        assert_equal(dens.shape[0], vert_shape[1] // vert_shape[0])\n        assert_equal(str(dens.units), \"g/cm**3\")\n\n\nclass ExporterTests(TestCase):\n    def setUp(self):\n        self.curdir = os.getcwd()\n        self.tmpdir = tempfile.mkdtemp()\n        os.chdir(self.tmpdir)\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        shutil.rmtree(self.tmpdir)\n\n    def test_export_ply(self):\n        ds = fake_random_ds(64, nprocs=4)\n        dd = ds.all_data()\n        surf = ds.surface(dd, (\"index\", \"x\"), 0.51)\n        surf.export_ply(\"my_ply.ply\", bounds=[(0, 1), (0, 1), (0, 1)])\n        assert os.path.exists(\"my_ply.ply\")\n        surf.export_ply(\n            \"my_ply2.ply\",\n            bounds=[(0, 1), (0, 1), (0, 1)],\n            sample_type=\"vertex\",\n            color_field=(\"gas\", \"density\"),\n        )\n        assert os.path.exists(\"my_ply2.ply\")\n\n    def test_export_obj(self):\n        ds = fake_random_ds(\n            16,\n            nprocs=4,\n            particles=16**3,\n            fields=(\"density\", \"temperature\"),\n            units=(\"g/cm**3\", \"K\"),\n        )\n        sp = ds.sphere(\"max\", (1.0, \"cm\"))\n        surf = ds.surface(sp, (\"gas\", \"density\"), 0.5)\n        surf.export_obj(\"my_galaxy\", transparency=1.0, dist_fac=1.0)\n        assert os.path.exists(\"my_galaxy.obj\")\n        assert os.path.exists(\"my_galaxy.mtl\")\n\n        mi, ma = sp.quantities.extrema((\"gas\", \"temperature\"))\n        rhos = [0.5, 0.25]\n        trans = [0.5, 1.0]\n        for i, r in enumerate(rhos):\n            basename = \"my_galaxy_color\"\n            surf = ds.surface(sp, (\"gas\", \"density\"), r)\n            surf.export_obj(\n                basename,\n                transparency=trans[i],\n                color_field=(\"gas\", \"temperature\"),\n                dist_fac=1.0,\n                plot_index=i,\n                color_field_max=ma,\n                color_field_min=mi,\n            )\n\n            assert os.path.exists(f\"{basename}.obj\")\n            assert os.path.exists(f\"{basename}.mtl\")\n\n        def _Emissivity(data):\n            return (\n                data[\"gas\", \"density\"]\n                * data[\"gas\", \"density\"]\n                * np.sqrt(data[\"gas\", \"temperature\"])\n            )\n\n        ds.add_field(\n            (\"gas\", \"emissivity\"),\n            sampling_type=\"cell\",\n            function=_Emissivity,\n            units=r\"g**2*sqrt(K)/cm**6\",\n        )\n        for i, r in enumerate(rhos):\n            basename = \"my_galaxy_emis\"\n            surf = ds.surface(sp, (\"gas\", \"density\"), r)\n            surf.export_obj(\n                basename,\n                transparency=trans[i],\n                color_field=(\"gas\", \"temperature\"),\n                emit_field=(\"gas\", \"emissivity\"),\n                dist_fac=1.0,\n                plot_index=i,\n            )\n\n            basename = \"my_galaxy_emis\"\n            assert os.path.exists(f\"{basename}.obj\")\n            assert os.path.exists(f\"{basename}.mtl\")\n\n\ndef test_correct_output_unit_fake_ds():\n    # see issue #1368\n    ds = fake_random_ds(64, nprocs=4, particles=16**3)\n    x = y = z = 0.5\n    sp1 = ds.sphere((x, y, z), (300, \"kpc\"))\n    Nmax = sp1.max((\"gas\", \"density\"))\n    sur = ds.surface(sp1, (\"gas\", \"density\"), 0.5 * Nmax)\n    sur[\"index\", \"x\"][0]\n\n\ndef test_radius_surface():\n    # see #1407\n    ds = fake_random_ds(64, nprocs=4, particles=16**3, length_unit=10.0)\n    reg = ds.all_data()\n    sp = ds.sphere(ds.domain_center, (0.5, \"code_length\"))\n    for obj in [reg, sp]:\n        for rad in [0.05, 0.1, 0.4]:\n            surface = ds.surface(obj, (\"index\", \"radius\"), (rad, \"code_length\"))\n            assert_almost_equal(surface.surface_area.v, 4 * np.pi * rad**2, decimal=2)\n            verts = surface.vertices\n            for i in range(3):\n                assert_almost_equal(verts[i, :].min().v, 0.5 - rad, decimal=2)\n                assert_almost_equal(verts[i, :].max().v, 0.5 + rad, decimal=2)\n"
  },
  {
    "path": "yt/data_objects/tests/test_image_array.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\n\nimport numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.data_objects.image_array import ImageArray\nfrom yt.testing import requires_module\n\nold_settings = None\n\n\ndef setup_module():\n    global old_settings\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n    old_settings = np.geterr()\n    np.seterr(all=\"ignore\")\n\n\ndef teardown_module():\n    np.seterr(**old_settings)\n\n\ndef dummy_image(kstep, nlayers):\n    im = np.zeros([64, 128, nlayers])\n    for i in range(im.shape[0]):\n        for k in range(im.shape[2]):\n            im[i, :, k] = np.linspace(0.0, kstep * k, im.shape[1])\n    return im\n\n\ndef test_rgba_rescale():\n    im_arr = ImageArray(dummy_image(10.0, 4))\n\n    new_im = im_arr.rescale(inline=False)\n    assert_equal(im_arr[:, :, :3].max(), 2 * 10.0)\n    assert_equal(im_arr[:, :, 3].max(), 3 * 10.0)\n    assert_equal(new_im[:, :, :3].sum(axis=2).max(), 1.0)\n    assert_equal(new_im[:, :, 3].max(), 1.0)\n\n    im_arr.rescale()\n    assert_equal(im_arr[:, :, :3].sum(axis=2).max(), 1.0)\n    assert_equal(im_arr[:, :, 3].max(), 1.0)\n\n    im_arr.rescale(cmax=0.0, amax=0.0)\n    assert_equal(im_arr[:, :, :3].sum(axis=2).max(), 1.0)\n    assert_equal(im_arr[:, :, 3].max(), 1.0)\n\n\nclass TestImageArray(unittest.TestCase):\n    tmpdir = None\n    curdir = None\n\n    def setUp(self):\n        self.tmpdir = tempfile.mkdtemp()\n        self.curdir = os.getcwd()\n        os.chdir(self.tmpdir)\n\n    def test_image_arry_units(self):\n        im_arr = ImageArray(dummy_image(0.3, 3), units=\"cm\")\n\n        assert str(im_arr.units) == \"cm\"\n\n        new_im = im_arr.in_units(\"km\")\n\n        assert str(new_im.units) == \"km\"\n\n    @requires_module(\"h5py\")\n    def test_image_array_hdf5(self):\n        myinfo = {\n            \"field\": \"dinosaurs\",\n            \"east_vector\": np.array([1.0, 0.0, 0.0]),\n            \"north_vector\": np.array([0.0, 0.0, 1.0]),\n            \"normal_vector\": np.array([0.0, 1.0, 0.0]),\n            \"width\": 0.245,\n            \"type\": \"rendering\",\n        }\n\n        im_arr = ImageArray(dummy_image(0.3, 3), units=\"cm\", info=myinfo)\n        im_arr.save(\"test_3d_ImageArray\", png=False)\n\n        im = np.zeros([64, 128])\n        for i in range(im.shape[0]):\n            im[i, :] = np.linspace(0.0, 0.3 * 2, im.shape[1])\n\n        myinfo = {\n            \"field\": \"dinosaurs\",\n            \"east_vector\": np.array([1.0, 0.0, 0.0]),\n            \"north_vector\": np.array([0.0, 0.0, 1.0]),\n            \"normal_vector\": np.array([0.0, 1.0, 0.0]),\n            \"width\": 0.245,\n            \"type\": \"rendering\",\n        }\n\n        im_arr = ImageArray(im, info=myinfo, units=\"cm\")\n        im_arr.save(\"test_2d_ImageArray\", png=False)\n\n        im_arr.save(\"test_2d_ImageArray_ds\", png=False, dataset_name=\"Random_DS\")\n\n    def test_image_array_rgb_png(self):\n        im = np.zeros([64, 128])\n        for i in range(im.shape[0]):\n            im[i, :] = np.linspace(0.0, 0.3 * 2, im.shape[1])\n        im_arr = ImageArray(im)\n        im_arr.save(\"standard-image\", hdf5=False)\n\n        im_arr = ImageArray(dummy_image(10.0, 3))\n        im_arr.save(\"standard-png\", hdf5=False)\n\n    def test_image_array_rgba_png(self):\n        im_arr = ImageArray(dummy_image(10.0, 4))\n        im_arr.write_png(\"standard\")\n        im_arr.write_png(\"non-scaled.png\", rescale=False)\n        im_arr.write_png(\"black_bg.png\", background=\"black\")\n        im_arr.write_png(\"white_bg.png\", background=\"white\")\n        im_arr.write_png(\"green_bg.png\", background=[0.0, 1.0, 0.0, 1.0])\n        im_arr.write_png(\"transparent_bg.png\", background=None)\n\n    def test_image_array_background(self):\n        im_arr = ImageArray(dummy_image(10.0, 4))\n        im_arr.rescale()\n        new_im = im_arr.add_background_color([1.0, 0.0, 0.0, 1.0], inline=False)\n        new_im.write_png(\"red_bg.png\")\n        im_arr.add_background_color(\"black\")\n        im_arr.write_png(\"black_bg2.png\")\n\n    def test_write_image(self):\n        im_arr = ImageArray(dummy_image(10.0, 4))\n        im_arr.write_image(\"with_cmap\", cmap_name=\"hot\")\n        im_arr.write_image(\"channel_1.png\", channel=1)\n\n    def test_clipping_value(self):\n        im_arr = ImageArray(dummy_image(10.0, 4))\n        clip_val1 = im_arr._clipping_value(1)\n        clip_val2 = im_arr._clipping_value(1, im=im_arr)\n        assert clip_val2 == clip_val1\n\n        clip_val3 = im_arr._clipping_value(6)\n        assert clip_val3 > clip_val2\n\n        im_arr[:] = 1.0  # std will be 0, mean will be 1, so clip value will be 1\n        assert im_arr._clipping_value(1) == 1.0\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        # clean up\n        shutil.rmtree(self.tmpdir)\n"
  },
  {
    "path": "yt/data_objects/tests/test_io_geometry.py",
    "content": "import os\nfrom tempfile import TemporaryDirectory\n\nimport numpy as np\n\nfrom yt.frontends.ytdata.api import save_as_dataset\nfrom yt.frontends.ytdata.data_structures import YTDataContainerDataset\nfrom yt.loaders import load\nfrom yt.testing import fake_amr_ds, requires_module\nfrom yt.units import YTQuantity\n\n\n@requires_module(\"h5py\")\ndef test_preserve_geometric_properties():\n    for geom in (\"cartesian\", \"cylindrical\", \"spherical\"):\n        ds1 = fake_amr_ds(fields=[(\"gas\", \"density\")], units=[\"g/cm**3\"], geometry=geom)\n        ad = ds1.all_data()\n        with TemporaryDirectory() as tmpdir:\n            tmpf = os.path.join(tmpdir, \"savefile.h5\")\n            fn = ad.save_as_dataset(tmpf, fields=[(\"gas\", \"density\")])\n            ds2 = load(fn)\n            assert isinstance(ds2, YTDataContainerDataset)\n            dfl = ds2.derived_field_list\n        assert ds1.geometry == ds2.geometry == geom\n\n        expected = set(ds1.coordinates.axis_order)\n        actual = {fname for ftype, fname in dfl}\n        assert expected.difference(actual) == set()\n\n\n@requires_module(\"h5py\")\ndef test_default_to_cartesian():\n    data = {\"density\": np.random.random(128)}\n    ds_attrs = {\"current_time\": YTQuantity(10, \"Myr\")}\n    with TemporaryDirectory() as tmpdir:\n        tmpf = os.path.join(tmpdir, \"savefile.h5\")\n        fn = save_as_dataset(ds_attrs, tmpf, data)\n        ds2 = load(fn)\n    assert ds2.geometry == \"cartesian\"\n"
  },
  {
    "path": "yt/data_objects/tests/test_numpy_ops.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import fake_amr_ds, fake_random_ds\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_mean_sum_integrate():\n    for nprocs in [-1, 1, 2, 16]:\n        if nprocs == -1:\n            ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), particles=20)\n        else:\n            ds = fake_random_ds(\n                32, nprocs=nprocs, fields=(\"density\",), units=(\"g/cm**3\",), particles=20\n            )\n        ad = ds.all_data()\n\n        # Sums\n        q = ad.sum((\"gas\", \"density\"))\n\n        q1 = ad.quantities.total_quantity((\"gas\", \"density\"))\n\n        assert_equal(q, q1)\n\n        q = ad.sum((\"all\", \"particle_ones\"))\n\n        q1 = ad.quantities.total_quantity((\"all\", \"particle_ones\"))\n\n        assert_equal(q, q1)\n\n        # Weighted Averages\n        w = ad.mean((\"gas\", \"density\"))\n\n        w1 = ad.quantities.weighted_average_quantity(\n            (\"gas\", \"density\"), (\"index\", \"ones\")\n        )\n\n        assert_equal(w, w1)\n\n        w = ad.mean((\"gas\", \"density\"), weight=(\"gas\", \"density\"))\n\n        w1 = ad.quantities.weighted_average_quantity(\n            (\"gas\", \"density\"), (\"gas\", \"density\")\n        )\n\n        assert_equal(w, w1)\n\n        w = ad.mean((\"all\", \"particle_mass\"))\n\n        w1 = ad.quantities.weighted_average_quantity(\n            (\"all\", \"particle_mass\"), (\"all\", \"particle_ones\")\n        )\n\n        assert_equal(w, w1)\n\n        w = ad.mean((\"all\", \"particle_mass\"), weight=(\"all\", \"particle_mass\"))\n\n        w1 = ad.quantities.weighted_average_quantity(\n            (\"all\", \"particle_mass\"), (\"all\", \"particle_mass\")\n        )\n\n        assert_equal(w, w1)\n\n        # Projections\n        p = ad.sum((\"gas\", \"density\"), axis=0)\n\n        p1 = ds.proj((\"gas\", \"density\"), 0, data_source=ad, method=\"sum\")\n\n        assert_equal(p[\"gas\", \"density\"], p1[\"gas\", \"density\"])\n\n        # Check by axis-name\n        p = ad.sum((\"gas\", \"density\"), axis=\"x\")\n\n        assert_equal(p[\"gas\", \"density\"], p1[\"gas\", \"density\"])\n\n        # Now we check proper projections\n        p = ad.integrate((\"gas\", \"density\"), axis=0)\n        p1 = ds.proj((\"gas\", \"density\"), 0, data_source=ad)\n\n        assert_equal(p[\"gas\", \"density\"], p1[\"gas\", \"density\"])\n\n        # Check by axis-name\n        p = ad.integrate((\"gas\", \"density\"), axis=\"x\")\n\n        assert_equal(p[\"gas\", \"density\"], p1[\"gas\", \"density\"])\n\n\ndef test_min_max():\n    for nprocs in [-1, 1, 2, 16]:\n        fields = [\"density\", \"temperature\"]\n        units = [\"g/cm**3\", \"K\"]\n        if nprocs == -1:\n            ds = fake_amr_ds(fields=fields, units=units, particles=20)\n        else:\n            ds = fake_random_ds(\n                32, nprocs=nprocs, fields=fields, units=units, particles=20\n            )\n\n        ad = ds.all_data()\n\n        q = ad.min((\"gas\", \"density\")).v\n        assert_equal(q, ad[\"gas\", \"density\"].min())\n\n        q = ad.max((\"gas\", \"density\")).v\n        assert_equal(q, ad[\"gas\", \"density\"].max())\n\n        q = ad.min((\"all\", \"particle_mass\")).v\n        assert_equal(q, ad[\"all\", \"particle_mass\"].min())\n\n        q = ad.max((\"all\", \"particle_mass\")).v\n        assert_equal(q, ad[\"all\", \"particle_mass\"].max())\n\n        ptp = ad.ptp((\"gas\", \"density\")).v\n        assert_equal(ptp, ad[\"gas\", \"density\"].max() - ad[\"gas\", \"density\"].min())\n\n        ptp = ad.ptp((\"all\", \"particle_mass\")).v\n        assert_equal(\n            ptp, ad[\"all\", \"particle_mass\"].max() - ad[\"all\", \"particle_mass\"].min()\n        )\n\n        p = ad.max((\"gas\", \"density\"), axis=1)\n        p1 = ds.proj((\"gas\", \"density\"), 1, data_source=ad, method=\"max\")\n        assert_equal(p[\"gas\", \"density\"], p1[\"gas\", \"density\"])\n\n        p = ad.min((\"gas\", \"density\"), axis=1)\n        p1 = ds.proj((\"gas\", \"density\"), 1, data_source=ad, method=\"min\")\n        assert_equal(p[\"gas\", \"density\"], p1[\"gas\", \"density\"])\n\n        p = ad.max((\"gas\", \"density\"), axis=\"y\")\n        p1 = ds.proj((\"gas\", \"density\"), 1, data_source=ad, method=\"max\")\n        assert_equal(p[\"gas\", \"density\"], p1[\"gas\", \"density\"])\n\n        p = ad.min((\"gas\", \"density\"), axis=\"y\")\n        p1 = ds.proj((\"gas\", \"density\"), 1, data_source=ad, method=\"min\")\n        assert_equal(p[\"gas\", \"density\"], p1[\"gas\", \"density\"])\n\n        # Test that we can get multiple in a single pass\n\n        qrho, qtemp = ad.max([(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n        assert_equal(qrho, ad[\"gas\", \"density\"].max())\n        assert_equal(qtemp, ad[\"gas\", \"temperature\"].max())\n\n        qrho, qtemp = ad.min([(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n        assert_equal(qrho, ad[\"gas\", \"density\"].min())\n        assert_equal(qtemp, ad[\"gas\", \"temperature\"].min())\n\n\ndef test_argmin():\n    fields = [\"density\", \"temperature\"]\n    units = [\"g/cm**3\", \"K\"]\n    for nprocs in [-1, 1, 2, 16]:\n        if nprocs == -1:\n            ds = fake_amr_ds(fields=fields, units=units)\n        else:\n            ds = fake_random_ds(\n                32,\n                nprocs=nprocs,\n                fields=fields,\n                units=units,\n            )\n\n        ad = ds.all_data()\n\n        q = ad.argmin((\"gas\", \"density\"), axis=[(\"gas\", \"density\")])\n        assert_equal(q, ad[\"gas\", \"density\"].min())\n\n        q1, q2 = ad.argmin(\n            (\"gas\", \"density\"), axis=[(\"gas\", \"density\"), (\"gas\", \"temperature\")]\n        )\n        mi = np.argmin(ad[\"gas\", \"density\"])\n        assert_equal(q1, ad[\"gas\", \"density\"].min())\n        assert_equal(q2, ad[\"gas\", \"temperature\"][mi])\n\n        pos = ad.argmin((\"gas\", \"density\"))\n        mi = np.argmin(ad[\"gas\", \"density\"])\n        assert_equal(pos[0], ad[\"index\", \"x\"][mi])\n        assert_equal(pos[1], ad[\"index\", \"y\"][mi])\n        assert_equal(pos[2], ad[\"index\", \"z\"][mi])\n\n\ndef test_argmax():\n    fields = [\"density\", \"temperature\"]\n    units = [\"g/cm**3\", \"K\"]\n    for nprocs in [-1, 1, 2, 16]:\n        if nprocs == -1:\n            ds = fake_amr_ds(fields=fields, units=units)\n        else:\n            ds = fake_random_ds(\n                32,\n                nprocs=nprocs,\n                fields=fields,\n                units=units,\n            )\n\n        ad = ds.all_data()\n\n        q = ad.argmax((\"gas\", \"density\"), axis=[(\"gas\", \"density\")])\n        assert_equal(q, ad[\"gas\", \"density\"].max())\n\n        q1, q2 = ad.argmax(\n            (\"gas\", \"density\"), axis=[(\"gas\", \"density\"), (\"gas\", \"temperature\")]\n        )\n        mi = np.argmax(ad[\"gas\", \"density\"])\n        assert_equal(q1, ad[\"gas\", \"density\"].max())\n        assert_equal(q2, ad[\"gas\", \"temperature\"][mi])\n\n        pos = ad.argmax((\"gas\", \"density\"))\n        mi = np.argmax(ad[\"gas\", \"density\"])\n        assert_equal(pos[0], ad[\"index\", \"x\"][mi])\n        assert_equal(pos[1], ad[\"index\", \"y\"][mi])\n        assert_equal(pos[2], ad[\"index\", \"z\"][mi])\n"
  },
  {
    "path": "yt/data_objects/tests/test_octree.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.testing import fake_sph_grid_ds\n\nn_ref = 4\n\n\ndef test_building_tree():\n    \"\"\"\n    Build an octree and make sure correct number of particles\n    \"\"\"\n    ds = fake_sph_grid_ds()\n    octree = ds.octree(n_ref=n_ref)\n    assert octree[\"index\", \"x\"].shape[0] == 17\n\n\ndef test_sph_interpolation_scatter():\n    \"\"\"\n    Generate an octree, perform some SPH interpolation and check with some\n    answer testing\n    \"\"\"\n\n    ds = fake_sph_grid_ds(hsml_factor=26.0)\n    ds._sph_ptypes = (\"io\",)\n\n    ds.use_sph_normalization = False\n\n    octree = ds.octree(n_ref=n_ref)\n\n    density = octree[\"io\", \"density\"]\n    answers = np.array(\n        [\n            1.00434706,\n            1.00434706,\n            1.00434706,\n            1.00434706,\n            1.00434706,\n            1.00434706,\n            1.00434706,\n            0.7762907,\n            0.89250848,\n            0.89250848,\n            0.97039088,\n            0.89250848,\n            0.97039088,\n            0.97039088,\n            1.01156175,\n        ]\n    )\n\n    assert_almost_equal(density.d, answers)\n\n\ndef test_sph_interpolation_gather():\n    \"\"\"\n    Generate an octree, perform some SPH interpolation and check with some\n    answer testing\n    \"\"\"\n    ds = fake_sph_grid_ds(hsml_factor=26.0)\n    ds.index\n    ds._sph_ptypes = (\"io\",)\n\n    ds.sph_smoothing_style = \"gather\"\n    ds.num_neighbors = 5\n    ds.use_sph_normalization = False\n\n    octree = ds.octree(n_ref=n_ref)\n\n    density = octree[\"io\", \"density\"]\n    answers = np.array(\n        [\n            0.59240874,\n            0.59240874,\n            0.59240874,\n            0.59240874,\n            0.59240874,\n            0.59240874,\n            0.59240874,\n            0.10026846,\n            0.77014968,\n            0.77014968,\n            0.96127825,\n            0.77014968,\n            0.96127825,\n            0.96127825,\n            1.21183996,\n        ]\n    )\n\n    assert_almost_equal(density.d, answers)\n\n\ndef test_octree_properties():\n    \"\"\"\n    Generate an octree, and test the refinement, depth and sizes of the cells.\n    \"\"\"\n    ds = fake_sph_grid_ds()\n    octree = ds.octree(n_ref=n_ref)\n\n    depth = octree[\"index\", \"depth\"]\n    depth_ans = np.array([0] + [1] * 8 + [2] * 8, dtype=np.int64)\n    assert_equal(depth, depth_ans)\n\n    size_ans = np.zeros((depth.shape[0], 3), dtype=np.float64)\n    for i in range(size_ans.shape[0]):\n        size_ans[i, :] = (ds.domain_right_edge - ds.domain_left_edge) / 2.0 ** depth[i]\n\n    dx = octree[\"index\", \"dx\"].d\n    assert_almost_equal(dx, size_ans[:, 0])\n\n    dy = octree[\"index\", \"dy\"].d\n    assert_almost_equal(dy, size_ans[:, 1])\n\n    dz = octree[\"index\", \"dz\"].d\n    assert_almost_equal(dz, size_ans[:, 2])\n\n    refined = octree[\"index\", \"refined\"]\n    refined_ans = np.array([True] + [False] * 7 + [True] + [False] * 8, dtype=np.bool_)\n    assert_equal(refined, refined_ans)\n"
  },
  {
    "path": "yt/data_objects/tests/test_ortho_rays.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import fake_random_ds\n\n\ndef test_ortho_ray():\n    ds = fake_random_ds(64, nprocs=8)\n    dx = (ds.domain_right_edge - ds.domain_left_edge) / ds.domain_dimensions\n\n    axes = [\"x\", \"y\", \"z\"]\n    for ax in range(3):\n        ocoord = ds.arr(np.random.random(2), \"code_length\")\n\n        my_oray = ds.ortho_ray(ax, ocoord)\n\n        my_axes = ds.coordinates.x_axis[ax], ds.coordinates.y_axis[ax]\n\n        # find the cells intersected by the ortho ray\n        my_all = ds.all_data()\n        my_cells = (\n            np.abs(my_all[\"index\", axes[my_axes[0]]] - ocoord[0])\n            <= 0.5 * dx[my_axes[0]]\n        ) & (\n            np.abs(my_all[\"index\", axes[my_axes[1]]] - ocoord[1])\n            <= 0.5 * dx[my_axes[1]]\n        )\n\n        assert_equal(\n            my_oray[\"gas\", \"density\"].sum(),\n            my_all[\"gas\", \"density\"][my_cells].sum(),\n        )\n"
  },
  {
    "path": "yt/data_objects/tests/test_particle_filter.py",
    "content": "import os\nimport shutil\nimport tempfile\n\nimport numpy as np\nfrom nose.tools import assert_raises\nfrom numpy.testing import assert_equal\n\nfrom yt.data_objects.particle_filters import add_particle_filter, particle_filter\nfrom yt.testing import fake_random_ds, fake_sph_grid_ds\nfrom yt.utilities.exceptions import YTIllDefinedFilter, YTIllDefinedParticleFilter\nfrom yt.visualization.plot_window import ProjectionPlot\n\n\ndef test_add_particle_filter():\n    \"\"\"Test particle filters created via add_particle_filter\n\n    This accesses a deposition field using the particle filter, which was a\n    problem in previous versions on this dataset because there are chunks with\n    no stars in them.\n\n    \"\"\"\n\n    def stars(pfilter, data):\n        filter_field = (pfilter.filtered_type, \"particle_mass\")\n        return data[filter_field] > 0.5\n\n    add_particle_filter(\n        \"stars1\", function=stars, filtered_type=\"all\", requires=[\"particle_mass\"]\n    )\n    ds = fake_random_ds(16, nprocs=8, particles=16)\n    ds.add_particle_filter(\"stars1\")\n    assert (\"deposit\", \"stars1_cic\") in ds.derived_field_list\n\n    # Test without requires field\n    add_particle_filter(\"stars2\", function=stars)\n    ds = fake_random_ds(16, nprocs=8, particles=16)\n    ds.add_particle_filter(\"stars2\")\n    assert (\"deposit\", \"stars2_cic\") in ds.derived_field_list\n\n    # Test adding filter with fields not defined on the ds\n    with assert_raises(YTIllDefinedParticleFilter) as ex:\n        add_particle_filter(\n            \"bad_stars\", function=stars, filtered_type=\"all\", requires=[\"wrong_field\"]\n        )\n        ds.add_particle_filter(\"bad_stars\")\n    actual = str(ex.exception)\n    desired = (\n        \"\\nThe fields\\n\\t('all', 'wrong_field'),\\nrequired by the\"\n        ' \"bad_stars\" particle filter, are not defined for this dataset.'\n    )\n    assert_equal(actual, desired)\n\n\ndef test_add_particle_filter_overriding():\n    \"\"\"Test the add_particle_filter overriding\"\"\"\n    from yt.data_objects.particle_filters import filter_registry\n    from yt.funcs import mylog\n\n    def star_0(pfilter, data):\n        pass\n\n    def star_1(pfilter, data):\n        pass\n\n    # Use a closure to store whether the warning was called\n    def closure(status):\n        def warning_patch(*args, **kwargs):\n            status[0] = True\n\n        def was_called():\n            return status[0]\n\n        return warning_patch, was_called\n\n    ## Test 1: we add a dummy particle filter\n    add_particle_filter(\n        \"dummy\", function=star_0, filtered_type=\"all\", requires=[\"creation_time\"]\n    )\n    assert \"dummy\" in filter_registry\n    assert_equal(filter_registry[\"dummy\"].function, star_0)\n\n    ## Test 2: we add another dummy particle filter.\n    ##         a warning is expected. We use the above closure to\n    ##         check that.\n    # Store the original warning function\n    warning = mylog.warning\n    monkey_warning, monkey_patch_was_called = closure([False])\n    mylog.warning = monkey_warning\n    add_particle_filter(\n        \"dummy\", function=star_1, filtered_type=\"all\", requires=[\"creation_time\"]\n    )\n    assert_equal(filter_registry[\"dummy\"].function, star_1)\n    assert_equal(monkey_patch_was_called(), True)\n\n    # Restore the original warning function\n    mylog.warning = warning\n\n\ndef test_particle_filter_decorator():\n    \"\"\"Test the particle_filter decorator\"\"\"\n\n    @particle_filter(filtered_type=\"all\", requires=[\"particle_mass\"])\n    def heavy_stars(pfilter, data):\n        filter_field = (pfilter.filtered_type, \"particle_mass\")\n        return data[filter_field] > 0.5\n\n    ds = fake_random_ds(16, nprocs=8, particles=16)\n    ds.add_particle_filter(\"heavy_stars\")\n    assert \"heavy_stars\" in ds.particle_types\n    assert (\"deposit\", \"heavy_stars_cic\") in ds.derived_field_list\n\n    # Test name of particle filter\n    @particle_filter(name=\"my_stars\", filtered_type=\"all\", requires=[\"particle_mass\"])\n    def custom_stars(pfilter, data):\n        filter_field = (pfilter.filtered_type, \"particle_mass\")\n        return data[filter_field] == 0.5\n\n    ds = fake_random_ds(16, nprocs=8, particles=16)\n    ds.add_particle_filter(\"my_stars\")\n    assert \"my_stars\" in ds.particle_types\n    assert (\"deposit\", \"my_stars_cic\") in ds.derived_field_list\n\n\ndef test_particle_filter_exceptions():\n    @particle_filter(filtered_type=\"all\", requires=[\"particle_mass\"])\n    def filter1(pfilter, data):\n        return data\n\n    ds = fake_random_ds(16, nprocs=8, particles=16)\n    ds.add_particle_filter(\"filter1\")\n\n    ad = ds.all_data()\n    with assert_raises(YTIllDefinedFilter):\n        ad[\"filter1\", \"particle_mass\"].shape[0]\n\n    @particle_filter(filtered_type=\"all\", requires=[\"particle_mass\"])\n    def filter2(pfilter, data):\n        filter_field = (\"io\", \"particle_mass\")\n        return data[filter_field] > 0.5\n\n    ds.add_particle_filter(\"filter2\")\n    ad = ds.all_data()\n    ad[\"filter2\", \"particle_mass\"].min()\n\n\ndef test_particle_filter_dependency():\n    \"\"\"\n    Test dataset add_particle_filter which should automatically add\n    the dependency of the filter.\n    \"\"\"\n\n    @particle_filter(filtered_type=\"all\", requires=[\"particle_mass\"])\n    def h_stars(pfilter, data):\n        filter_field = (pfilter.filtered_type, \"particle_mass\")\n        return data[filter_field] > 0.5\n\n    @particle_filter(filtered_type=\"h_stars\", requires=[\"particle_mass\"])\n    def hh_stars(pfilter, data):\n        filter_field = (pfilter.filtered_type, \"particle_mass\")\n        return data[filter_field] > 0.9\n\n    ds = fake_random_ds(16, nprocs=8, particles=16)\n    ds.add_particle_filter(\"hh_stars\")\n    assert \"hh_stars\" in ds.particle_types\n    assert \"h_stars\" in ds.particle_types\n    assert (\"deposit\", \"hh_stars_cic\") in ds.derived_field_list\n    assert (\"deposit\", \"h_stars_cic\") in ds.derived_field_list\n\n\ndef test_covering_grid_particle_filter():\n    @particle_filter(filtered_type=\"all\", requires=[\"particle_mass\"])\n    def heavy_stars(pfilter, data):\n        filter_field = (pfilter.filtered_type, \"particle_mass\")\n        return data[filter_field] > 0.5\n\n    ds = fake_random_ds(16, nprocs=8, particles=16)\n    ds.add_particle_filter(\"heavy_stars\")\n\n    for grid in ds.index.grids:\n        cg = ds.covering_grid(grid.Level, grid.LeftEdge, grid.ActiveDimensions)\n\n        assert_equal(\n            cg[\"heavy_stars\", \"particle_mass\"].shape[0],\n            grid[\"heavy_stars\", \"particle_mass\"].shape[0],\n        )\n        assert_equal(\n            cg[\"heavy_stars\", \"particle_mass\"].shape[0],\n            grid[\"heavy_stars\", \"particle_mass\"].shape[0],\n        )\n\n\ndef test_sph_particle_filter_plotting():\n    ds = fake_sph_grid_ds()\n\n    @particle_filter(\"central_gas\", requires=[\"particle_position\"], filtered_type=\"io\")\n    def _filter(pfilter, data):\n        coords = np.abs(data[pfilter.filtered_type, \"particle_position\"])\n        return (coords[:, 0] < 1.6) & (coords[:, 1] < 1.6) & (coords[:, 2] < 1.6)\n\n    ds.add_particle_filter(\"central_gas\")\n\n    plot = ProjectionPlot(ds, \"z\", (\"central_gas\", \"density\"))\n    tmpdir = tempfile.mkdtemp()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n\n    plot.save()\n\n    os.chdir(curdir)\n    shutil.rmtree(tmpdir)\n"
  },
  {
    "path": "yt/data_objects/tests/test_particle_trajectories.py",
    "content": "import glob\nimport os\n\nfrom yt.config import ytcfg\nfrom yt.data_objects.time_series import DatasetSeries\nfrom yt.utilities.answer_testing.framework import GenericArrayTest, requires_ds\n\n\ndef setup_module():\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndata_path = ytcfg.get(\"yt\", \"test_data_dir\")\n\npfields = [\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_position_y\"),\n    (\"all\", \"particle_position_z\"),\n]\nvfields = [\n    (\"all\", \"particle_velocity_x\"),\n    (\"all\", \"particle_velocity_y\"),\n    (\"all\", \"particle_velocity_z\"),\n]\n\n\n@requires_ds(\"Orbit/orbit_hdf5_chk_0000\")\ndef test_orbit_traj():\n    fields = [\"particle_velocity_x\", \"particle_velocity_y\", \"particle_velocity_z\"]\n    my_fns = glob.glob(os.path.join(data_path, \"Orbit/orbit_hdf5_chk_00[0-9][0-9]\"))\n    my_fns.sort()\n    ts = DatasetSeries(my_fns)\n    ds = ts[0]\n    traj = ts.particle_trajectories([1, 2], fields=fields, suppress_logging=True)\n    for field in pfields + vfields:\n\n        def field_func(name):\n            return traj[field]  # noqa: B023\n\n        yield GenericArrayTest(ds, field_func, args=[field])\n\n\n@requires_ds(\"enzo_tiny_cosmology/DD0000/DD0000\")\ndef test_etc_traj():\n    fields = [\n        (\"all\", \"particle_velocity_x\"),\n        (\"all\", \"particle_velocity_y\"),\n        (\"all\", \"particle_velocity_z\"),\n    ]\n    my_fns = glob.glob(\n        os.path.join(data_path, \"enzo_tiny_cosmology/DD000[0-9]/*.hierarchy\")\n    )\n    my_fns.sort()\n    ts = DatasetSeries(my_fns)\n    ds = ts[0]\n    sp = ds.sphere(\"max\", (0.5, \"Mpc\"))\n    indices = sp[\"particle_index\"][sp[\"particle_type\"] == 1][:5]\n    traj = ts.particle_trajectories(indices, fields=fields, suppress_logging=True)\n    traj.add_fields([(\"gas\", \"density\")])\n    for field in pfields + vfields + [(\"gas\", \"density\")]:\n\n        def field_func(name):\n            return traj[field]  # noqa: B023\n\n        yield GenericArrayTest(ds, field_func, args=[field])\n"
  },
  {
    "path": "yt/data_objects/tests/test_particle_trajectories_pytest.py",
    "content": "import numpy as np\nimport pytest\nfrom numpy.testing import assert_raises\n\nfrom yt.data_objects.particle_filters import particle_filter\nfrom yt.data_objects.time_series import DatasetSeries\nfrom yt.testing import fake_particle_ds\nfrom yt.utilities.exceptions import YTIllDefinedParticleData\n\npfields = [\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_position_y\"),\n    (\"all\", \"particle_position_z\"),\n]\nvfields = [\n    (\"all\", \"particle_velocity_x\"),\n    (\"all\", \"particle_velocity_y\"),\n    (\"all\", \"particle_velocity_z\"),\n]\n\n\n@pytest.fixture\ndef particle_trajectories_test_dataset():\n    n_particles = 2\n    n_steps = 2\n    ids = np.arange(n_particles, dtype=\"int64\")\n    data = {\"particle_index\": ids}\n    fields = [\n        \"particle_position_x\",\n        \"particle_position_y\",\n        \"particle_position_z\",\n        \"particle_velocity_x\",  # adding a non-default field\n        \"particle_index\",\n    ]\n    negative = [False, False, False, True, False]\n    units = [\"cm\", \"cm\", \"cm\", \"cm/s\", \"1\"]\n\n    ts = DatasetSeries(\n        [\n            fake_particle_ds(\n                fields=fields,\n                negative=negative,\n                units=units,\n                npart=n_particles,\n                data=data,\n            )\n            for i in range(n_steps)\n        ]\n    )\n    return ts\n\n\ndef test_uniqueness():\n    n_particles = 2\n    n_steps = 2\n    ids = np.arange(n_particles, dtype=\"int64\") % (n_particles // 2)\n    data = {\"particle_index\": ids}\n    fields = [\n        \"particle_position_x\",\n        \"particle_position_y\",\n        \"particle_position_z\",\n        \"particle_index\",\n    ]\n    negative = [False, False, False, False]\n    units = [\"cm\", \"cm\", \"cm\", \"1\"]\n\n    ts = DatasetSeries(\n        [\n            fake_particle_ds(\n                fields=fields,\n                negative=negative,\n                units=units,\n                npart=n_particles,\n                data=data,\n            )\n            for i in range(n_steps)\n        ]\n    )\n\n    assert_raises(YTIllDefinedParticleData, ts.particle_trajectories, [0])\n\n\ndef test_ptype():\n    n_particles = 100\n    fields = [\n        \"particle_position_x\",\n        \"particle_position_y\",\n        \"particle_position_z\",\n        \"particle_index\",\n        \"particle_dummy\",\n    ]\n    negative = [False, False, False, False, False]\n    units = [\"cm\", \"cm\", \"cm\", \"1\", \"1\"]\n\n    # Setup filters on the 'particle_dummy' field, keeping only the first 50\n    @particle_filter(name=\"dummy\", requires=[\"particle_dummy\"])\n    def dummy(pfilter, data):\n        return data[pfilter.filtered_type, \"particle_dummy\"] <= n_particles // 2\n\n    # Setup fake particle datasets with repeated ids. This should work because\n    # the ids are unique among `dummy_particles` so let's test this\n    data = {\n        \"particle_index\": np.arange(n_particles) % (n_particles // 2),\n        \"particle_dummy\": np.arange(n_particles),\n    }\n    all_ds = [\n        fake_particle_ds(\n            fields=fields, negative=negative, units=units, npart=n_particles, data=data\n        )\n    ]\n    for ds in all_ds:\n        ds.add_particle_filter(\"dummy\")\n    ts = DatasetSeries(all_ds)\n\n    # Select all dummy particles\n    ids = ts[0].all_data()[\"dummy\", \"particle_index\"]\n\n    # Build trajectories\n    ts.particle_trajectories(ids, ptype=\"dummy\")\n\n\n@pytest.mark.parametrize(\"ptype\", [None, \"io\"])\ndef test_default_field_tuple(particle_trajectories_test_dataset, ptype):\n    ds = particle_trajectories_test_dataset[0]\n    ids = ds.all_data()[\"all\", \"particle_index\"]\n    trajs = particle_trajectories_test_dataset.particle_trajectories(\n        ids, ptype=ptype, suppress_logging=True\n    )\n    ptype = ptype if ptype else \"all\"  # ptype defaults to \"all\"\n    for k in trajs.field_data.keys():\n        assert isinstance(k, tuple), f\"Expected key to be tuple, received {type(k)}\"\n        assert k[0] == ptype, (\n            f\"Default field type ({k[0]}) does not match expected ({ptype})\"\n        )\n        assert (\"all\", k[1]) in pfields, f\"Unexpected field: {k[1]}\"\n\n\n@pytest.mark.parametrize(\"ptype\", [None, \"io\"])\ndef test_time_and_index(particle_trajectories_test_dataset, ptype):\n    ds = particle_trajectories_test_dataset[0]\n    ids = ds.all_data()[\"all\", \"particle_index\"]\n    trajs = particle_trajectories_test_dataset.particle_trajectories(\n        ids, ptype=ptype, suppress_logging=True\n    )\n    ptype = ptype if ptype else \"all\"  # ptype defaults to \"all\"\n    traj = trajs.trajectory_from_index(1)\n    for field in [\"particle_time\", \"particle_index\"]:\n        assert (ptype, field) in traj.keys(), f\"Missing ({ptype},{field})\"\n        assert (field) not in traj.keys(), f\"{field} present as bare string\"\n"
  },
  {
    "path": "yt/data_objects/tests/test_pickling.py",
    "content": "import pickle\n\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import data_dir_load\n\ntipsy_ds = \"TipsyGalaxy/galaxy.00300\"\nenzo_ds = \"enzo_tiny_cosmology/DD0000/DD0000\"\n\n\n@requires_module(\"h5py\")\n@requires_file(enzo_ds)\ndef test_grid_pickles():\n    ds = data_dir_load(enzo_ds)\n    ad = ds.all_data()\n    # just test ad since there is a nested ds that will get (un)pickled\n    ad_pickle = pickle.loads(pickle.dumps(ad))\n    assert_equal(ad.ds.basename, ad_pickle.ds.basename)\n\n\n@requires_file(tipsy_ds)\ndef test_particle_pickles():\n    ds = data_dir_load(tipsy_ds)\n    ad = ds.all_data()\n    ds.index._identify_base_chunk(ad)\n    ch0 = list(ds.index._chunk_io(ad, cache=False))[0]\n    # just test one chunk since there is a nested ds that will get (un)pickled\n    ch_pickle = pickle.loads(pickle.dumps(ch0))\n    assert_equal(ch0.dobj.ds.basename, ch_pickle.dobj.ds.basename)\n"
  },
  {
    "path": "yt/data_objects/tests/test_points.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nimport yt\nfrom yt.testing import fake_random_ds\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_point_creation():\n    ds = fake_random_ds(16)\n    p1 = ds.point(ds.domain_center)\n    p2 = ds.point([0.5, 0.5, 0.5])\n    p3 = ds.point([0.5, 0.5, 0.5] * yt.units.cm)\n\n    # ensure all three points are really at the same position\n    for fname in \"xyz\":\n        assert_equal(p1[\"index\", fname], p2[\"index\", fname])\n        assert_equal(p1[\"index\", fname], p3[\"index\", fname])\n\n\ndef test_domain_point():\n    nparticles = 3\n    ds = fake_random_ds(16, particles=nparticles)\n    p = ds.point(ds.domain_center)\n\n    # ensure accessing one field works, store for comparison later\n    point_den = p[\"gas\", \"density\"]\n    point_vel = p[\"gas\", \"velocity_x\"]\n\n    ad = ds.all_data()\n    ppos = ad[\"all\", \"particle_position\"]\n\n    fpoint_den = ds.find_field_values_at_point((\"gas\", \"density\"), ds.domain_center)\n\n    fpoint_den_vel = ds.find_field_values_at_point(\n        [(\"gas\", \"density\"), (\"gas\", \"velocity_x\")], ds.domain_center\n    )\n\n    assert_equal(point_den, fpoint_den)\n    assert_equal(point_den, fpoint_den_vel[0])\n    assert_equal(point_vel, fpoint_den_vel[1])\n\n    ppos_den = ds.find_field_values_at_points((\"gas\", \"density\"), ppos)\n    ppos_vel = ds.find_field_values_at_points((\"gas\", \"velocity_x\"), ppos)\n    ppos_den_vel = ds.find_field_values_at_points(\n        [(\"gas\", \"density\"), (\"gas\", \"velocity_x\")], ppos\n    )\n\n    assert_equal(ppos_den.shape, (nparticles,))\n    assert_equal(ppos_vel.shape, (nparticles,))\n    assert_equal(len(ppos_den_vel), 2)\n    assert_equal(ppos_den_vel[0], ppos_den)\n    assert_equal(ppos_den_vel[1], ppos_vel)\n\n\ndef test_fast_find_field_values_at_points():\n    ds = fake_random_ds(64, nprocs=8, particles=16**3)\n    ad = ds.all_data()\n    # right now this is slow for large numbers of particles, so randomly\n    # sample 100 particles\n    nparticles = 100\n    ppos = ad[\"all\", \"particle_position\"]\n    ppos = ppos[np.random.randint(low=0, high=len(ppos), size=nparticles)]\n\n    ppos_den = ds.find_field_values_at_points((\"gas\", \"density\"), ppos)\n    ppos_vel = ds.find_field_values_at_points((\"gas\", \"velocity_x\"), ppos)\n    ppos_den_vel = ds.find_field_values_at_points(\n        [(\"gas\", \"density\"), (\"gas\", \"velocity_x\")], ppos\n    )\n\n    assert_equal(ppos_den.shape, (nparticles,))\n    assert_equal(ppos_vel.shape, (nparticles,))\n    assert_equal(len(ppos_den_vel), 2)\n    assert_equal(ppos_den_vel[0], ppos_den)\n    assert_equal(ppos_den_vel[1], ppos_vel)\n"
  },
  {
    "path": "yt/data_objects/tests/test_print_stats.py",
    "content": "import pytest\n\nfrom yt.testing import (\n    fake_amr_ds,\n    fake_particle_ds,\n)\n\n\ndef test_no_print_stats():\n    ds = fake_particle_ds()\n    with pytest.raises(NotImplementedError):\n        ds.print_stats()\n\n\ndef test_print_stats():\n    ds = fake_amr_ds()\n    ds.print_stats()\n"
  },
  {
    "path": "yt/data_objects/tests/test_profiles.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\n\nimport numpy as np\nfrom numpy.testing import assert_equal, assert_raises\n\nimport yt\nfrom yt.data_objects.particle_filters import add_particle_filter\nfrom yt.data_objects.profiles import Profile1D, Profile2D, Profile3D, create_profile\nfrom yt.testing import (\n    assert_rel_equal,\n    fake_random_ds,\n    fake_sph_orientation_ds,\n    requires_module,\n)\nfrom yt.utilities.exceptions import YTIllDefinedProfile, YTProfileDataShape\nfrom yt.visualization.profile_plotter import PhasePlot, ProfilePlot\n\n_fields = (\"density\", \"temperature\", \"dinosaurs\", \"tribbles\")\n_units = (\"g/cm**3\", \"K\", \"dyne\", \"erg\")\n\n\ndef test_profiles():\n    ds = fake_random_ds(64, nprocs=8, fields=_fields, units=_units)\n    nv = ds.domain_dimensions.prod()\n    dd = ds.all_data()\n    rt, tt, dt = dd.quantities[\"TotalQuantity\"](\n        [(\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"stream\", \"dinosaurs\")]\n    )\n\n    e1, e2 = 0.9, 1.1\n    for nb in [8, 16, 32, 64]:\n        for input_units in [\"mks\", \"cgs\"]:\n            (rmi, rma), (tmi, tma), (dmi, dma) = (\n                getattr(ex, f\"in_{input_units}\")()\n                for ex in dd.quantities[\"Extrema\"](\n                    [\n                        (\"gas\", \"density\"),\n                        (\"gas\", \"temperature\"),\n                        (\"stream\", \"dinosaurs\"),\n                    ]\n                )\n            )\n            # We log all the fields or don't log 'em all.  No need to do them\n            # individually.\n            for lf in [True, False]:\n                direct_profile = Profile1D(\n                    dd,\n                    (\"gas\", \"density\"),\n                    nb,\n                    rmi * e1,\n                    rma * e2,\n                    lf,\n                    weight_field=None,\n                )\n                direct_profile.add_fields([(\"index\", \"ones\"), (\"gas\", \"temperature\")])\n\n                indirect_profile_s = create_profile(\n                    dd,\n                    (\"gas\", \"density\"),\n                    [(\"index\", \"ones\"), (\"gas\", \"temperature\")],\n                    n_bins=nb,\n                    extrema={(\"gas\", \"density\"): (rmi * e1, rma * e2)},\n                    logs={(\"gas\", \"density\"): lf},\n                    weight_field=None,\n                )\n\n                indirect_profile_t = create_profile(\n                    dd,\n                    (\"gas\", \"density\"),\n                    [(\"index\", \"ones\"), (\"gas\", \"temperature\")],\n                    n_bins=nb,\n                    extrema={(\"gas\", \"density\"): (rmi * e1, rma * e2)},\n                    logs={(\"gas\", \"density\"): lf},\n                    weight_field=None,\n                )\n\n                for p1d in [direct_profile, indirect_profile_s, indirect_profile_t]:\n                    assert_equal(p1d[\"index\", \"ones\"].sum(), nv)\n                    assert_rel_equal(tt, p1d[\"gas\", \"temperature\"].sum(), 7)\n\n                p2d = Profile2D(\n                    dd,\n                    (\"gas\", \"density\"),\n                    nb,\n                    rmi * e1,\n                    rma * e2,\n                    lf,\n                    (\"gas\", \"temperature\"),\n                    nb,\n                    tmi * e1,\n                    tma * e2,\n                    lf,\n                    weight_field=None,\n                )\n                p2d.add_fields([(\"index\", \"ones\"), (\"gas\", \"temperature\")])\n                assert_equal(p2d[\"index\", \"ones\"].sum(), nv)\n                assert_rel_equal(tt, p2d[\"gas\", \"temperature\"].sum(), 7)\n\n                p3d = Profile3D(\n                    dd,\n                    (\"gas\", \"density\"),\n                    nb,\n                    rmi * e1,\n                    rma * e2,\n                    lf,\n                    (\"gas\", \"temperature\"),\n                    nb,\n                    tmi * e1,\n                    tma * e2,\n                    lf,\n                    (\"stream\", \"dinosaurs\"),\n                    nb,\n                    dmi * e1,\n                    dma * e2,\n                    lf,\n                    weight_field=None,\n                )\n                p3d.add_fields([(\"index\", \"ones\"), (\"gas\", \"temperature\")])\n                assert_equal(p3d[\"index\", \"ones\"].sum(), nv)\n                assert_rel_equal(tt, p3d[\"gas\", \"temperature\"].sum(), 7)\n\n        p1d = Profile1D(dd, (\"index\", \"x\"), nb, 0.0, 1.0, False, weight_field=None)\n        p1d.add_fields((\"index\", \"ones\"))\n        av = nv / nb\n        assert_equal(p1d[\"index\", \"ones\"], np.ones(nb) * av)\n\n        # We re-bin ones with a weight now\n        p1d = Profile1D(\n            dd, (\"index\", \"x\"), nb, 0.0, 1.0, False, weight_field=(\"gas\", \"temperature\")\n        )\n        p1d.add_fields([(\"index\", \"ones\")])\n        assert_equal(p1d[\"index\", \"ones\"], np.ones(nb))\n\n        # Verify we can access \"ones\" after adding a new field\n        # See issue 988\n        p1d.add_fields([(\"gas\", \"density\")])\n        assert_equal(p1d[\"index\", \"ones\"], np.ones(nb))\n\n        p2d = Profile2D(\n            dd,\n            (\"index\", \"x\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            (\"index\", \"y\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            weight_field=None,\n        )\n        p2d.add_fields((\"index\", \"ones\"))\n        av = nv / nb**2\n        assert_equal(p2d[\"index\", \"ones\"], np.ones((nb, nb)) * av)\n\n        # We re-bin ones with a weight now\n        p2d = Profile2D(\n            dd,\n            (\"index\", \"x\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            (\"index\", \"y\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            weight_field=(\"gas\", \"temperature\"),\n        )\n        p2d.add_fields([(\"index\", \"ones\")])\n        assert_equal(p2d[\"index\", \"ones\"], np.ones((nb, nb)))\n\n        p3d = Profile3D(\n            dd,\n            (\"index\", \"x\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            (\"index\", \"y\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            (\"index\", \"z\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            weight_field=None,\n        )\n        p3d.add_fields((\"index\", \"ones\"))\n        av = nv / nb**3\n        assert_equal(p3d[\"index\", \"ones\"], np.ones((nb, nb, nb)) * av)\n\n        # We re-bin ones with a weight now\n        p3d = Profile3D(\n            dd,\n            (\"index\", \"x\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            (\"index\", \"y\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            (\"index\", \"z\"),\n            nb,\n            0.0,\n            1.0,\n            False,\n            weight_field=(\"gas\", \"temperature\"),\n        )\n        p3d.add_fields([(\"index\", \"ones\")])\n        assert_equal(p3d[\"index\", \"ones\"], np.ones((nb, nb, nb)))\n\n        p2d = create_profile(\n            dd,\n            (\"gas\", \"density\"),\n            (\"gas\", \"temperature\"),\n            weight_field=(\"gas\", \"cell_mass\"),\n            extrema={(\"gas\", \"density\"): (None, rma * e2)},\n        )\n        assert_equal(p2d.x_bins[0], rmi - np.spacing(rmi))\n        assert_equal(p2d.x_bins[-1], rma * e2)\n        assert str(ds.field_info[\"gas\", \"cell_mass\"].units) == str(p2d.weight.units)\n\n        p2d = create_profile(\n            dd,\n            (\"gas\", \"density\"),\n            (\"gas\", \"temperature\"),\n            weight_field=(\"gas\", \"cell_mass\"),\n            extrema={(\"gas\", \"density\"): (rmi * e2, None)},\n        )\n        assert_equal(p2d.x_bins[0], rmi * e2)\n        assert_equal(p2d.x_bins[-1], rma + np.spacing(rma))\n\n\nextrema_s = {(\"all\", \"particle_position_x\"): (0, 1)}\nlogs_s = {(\"all\", \"particle_position_x\"): False}\n\nextrema_t = {(\"all\", \"particle_position_x\"): (0, 1)}\nlogs_t = {(\"all\", \"particle_position_x\"): False}\n\n\ndef test_particle_profiles():\n    for nproc in [1, 2, 4, 8]:\n        ds = fake_random_ds(32, nprocs=nproc, particles=32**3)\n        dd = ds.all_data()\n\n        p1d = Profile1D(\n            dd, (\"all\", \"particle_position_x\"), 128, 0.0, 1.0, False, weight_field=None\n        )\n        p1d.add_fields([(\"all\", \"particle_ones\")])\n        assert_equal(p1d[\"all\", \"particle_ones\"].sum(), 32**3)\n\n        p1d = create_profile(\n            dd,\n            [(\"all\", \"particle_position_x\")],\n            [(\"all\", \"particle_ones\")],\n            weight_field=None,\n            n_bins=128,\n            extrema=extrema_s,\n            logs=logs_s,\n        )\n        assert_equal(p1d[\"all\", \"particle_ones\"].sum(), 32**3)\n\n        p1d = create_profile(\n            dd,\n            [(\"all\", \"particle_position_x\")],\n            [(\"all\", \"particle_ones\")],\n            weight_field=None,\n            n_bins=128,\n            extrema=extrema_t,\n            logs=logs_t,\n        )\n        assert_equal(p1d[\"all\", \"particle_ones\"].sum(), 32**3)\n\n        p2d = Profile2D(\n            dd,\n            (\"all\", \"particle_position_x\"),\n            128,\n            0.0,\n            1.0,\n            False,\n            (\"all\", \"particle_position_y\"),\n            128,\n            0.0,\n            1.0,\n            False,\n            weight_field=None,\n        )\n        p2d.add_fields([(\"all\", \"particle_ones\")])\n        assert_equal(p2d[\"all\", \"particle_ones\"].sum(), 32**3)\n\n        p3d = Profile3D(\n            dd,\n            (\"all\", \"particle_position_x\"),\n            128,\n            0.0,\n            1.0,\n            False,\n            (\"all\", \"particle_position_y\"),\n            128,\n            0.0,\n            1.0,\n            False,\n            (\"all\", \"particle_position_z\"),\n            128,\n            0.0,\n            1.0,\n            False,\n            weight_field=None,\n        )\n        p3d.add_fields([(\"all\", \"particle_ones\")])\n        assert_equal(p3d[\"all\", \"particle_ones\"].sum(), 32**3)\n\n\ndef test_mixed_particle_mesh_profiles():\n    ds = fake_random_ds(32, particles=10)\n    ad = ds.all_data()\n    assert_raises(\n        YTIllDefinedProfile,\n        ProfilePlot,\n        ad,\n        (\"index\", \"radius\"),\n        (\"all\", \"particle_mass\"),\n    )\n    assert_raises(\n        YTIllDefinedProfile,\n        ProfilePlot,\n        ad,\n        \"radius\",\n        [(\"all\", \"particle_mass\"), (\"all\", \"particle_ones\")],\n    )\n    assert_raises(\n        YTIllDefinedProfile,\n        ProfilePlot,\n        ad,\n        (\"index\", \"radius\"),\n        [(\"all\", \"particle_mass\"), (\"index\", \"ones\")],\n    )\n    assert_raises(\n        YTIllDefinedProfile,\n        ProfilePlot,\n        ad,\n        (\"all\", \"particle_radius\"),\n        (\"all\", \"particle_mass\"),\n        (\"gas\", \"cell_mass\"),\n    )\n    assert_raises(\n        YTIllDefinedProfile,\n        ProfilePlot,\n        ad,\n        (\"index\", \"radius\"),\n        (\"gas\", \"cell_mass\"),\n        (\"all\", \"particle_ones\"),\n    )\n\n    assert_raises(\n        YTIllDefinedProfile,\n        PhasePlot,\n        ad,\n        (\"index\", \"radius\"),\n        (\"all\", \"particle_mass\"),\n        (\"gas\", \"velocity_x\"),\n    )\n    assert_raises(\n        YTIllDefinedProfile,\n        PhasePlot,\n        ad,\n        (\"all\", \"particle_radius\"),\n        (\"all\", \"particle_mass\"),\n        (\"gas\", \"cell_mass\"),\n    )\n    assert_raises(\n        YTIllDefinedProfile,\n        PhasePlot,\n        ad,\n        (\"index\", \"radius\"),\n        (\"gas\", \"cell_mass\"),\n        (\"all\", \"particle_ones\"),\n    )\n    assert_raises(\n        YTIllDefinedProfile,\n        PhasePlot,\n        ad,\n        (\"all\", \"particle_radius\"),\n        (\"all\", \"particle_mass\"),\n        (\"all\", \"particle_ones\"),\n    )\n\n\ndef test_particle_profile_negative_field():\n    # see Issue #1340\n    n_particles = int(1e4)\n\n    ppx, ppy, ppz = np.random.normal(size=[3, n_particles])\n    pvx, pvy, pvz = -np.ones((3, n_particles))\n\n    data = {\n        \"particle_position_x\": ppx,\n        \"particle_position_y\": ppy,\n        \"particle_position_z\": ppz,\n        \"particle_velocity_x\": pvx,\n        \"particle_velocity_y\": pvy,\n        \"particle_velocity_z\": pvz,\n    }\n\n    bbox = 1.1 * np.array(\n        [[min(ppx), max(ppx)], [min(ppy), max(ppy)], [min(ppz), max(ppz)]]\n    )\n    ds = yt.load_particles(data, bbox=bbox)\n    ad = ds.all_data()\n\n    profile = yt.create_profile(\n        ad,\n        [(\"all\", \"particle_position_x\"), (\"all\", \"particle_position_y\")],\n        (\"all\", \"particle_velocity_x\"),\n        logs={\n            (\"all\", \"particle_position_x\"): True,\n            (\"all\", \"particle_position_y\"): True,\n            (\"all\", \"particle_position_z\"): True,\n        },\n        weight_field=None,\n    )\n    assert profile[\"all\", \"particle_velocity_x\"].min() < 0\n    assert profile.x_bins.min() > 0\n    assert profile.y_bins.min() > 0\n\n    profile = yt.create_profile(\n        ad,\n        [(\"all\", \"particle_position_x\"), (\"all\", \"particle_position_y\")],\n        (\"all\", \"particle_velocity_x\"),\n        weight_field=None,\n    )\n    assert profile[\"all\", \"particle_velocity_x\"].min() < 0\n    assert profile.x_bins.min() < 0\n    assert profile.y_bins.min() < 0\n\n    # can't use CIC deposition with log-scaled bin fields\n    with assert_raises(RuntimeError):\n        yt.create_profile(\n            ad,\n            [(\"all\", \"particle_position_x\"), (\"all\", \"particle_position_y\")],\n            (\"all\", \"particle_velocity_x\"),\n            logs={\n                (\"all\", \"particle_position_x\"): True,\n                (\"all\", \"particle_position_y\"): False,\n                (\"all\", \"particle_position_z\"): False,\n            },\n            weight_field=None,\n            deposition=\"cic\",\n        )\n\n    # can't use CIC deposition with accumulation or fractional\n    with assert_raises(RuntimeError):\n        yt.create_profile(\n            ad,\n            [(\"all\", \"particle_position_x\"), (\"all\", \"particle_position_y\")],\n            (\"all\", \"particle_velocity_x\"),\n            logs={\n                (\"all\", \"particle_position_x\"): False,\n                (\"all\", \"particle_position_y\"): False,\n                (\"all\", \"particle_position_z\"): False,\n            },\n            weight_field=None,\n            deposition=\"cic\",\n            accumulation=True,\n            fractional=True,\n        )\n\n\ndef test_profile_zero_weight():\n    def DMparticles(pfilter, data):\n        filter = data[pfilter.filtered_type, \"particle_type\"] == 1\n        return filter\n\n    def DM_in_cell_mass(data):\n        return data[\"deposit\", \"DM_density\"] * data[\"index\", \"cell_volume\"]\n\n    add_particle_filter(\n        \"DM\", function=DMparticles, filtered_type=\"io\", requires=[\"particle_type\"]\n    )\n\n    _fields = (\n        \"particle_position_x\",\n        \"particle_position_y\",\n        \"particle_position_z\",\n        \"particle_mass\",\n        \"particle_velocity_x\",\n        \"particle_velocity_y\",\n        \"particle_velocity_z\",\n        \"particle_type\",\n    )\n    _units = (\"cm\", \"cm\", \"cm\", \"g\", \"cm/s\", \"cm/s\", \"cm/s\", \"dimensionless\")\n    ds = fake_random_ds(\n        32, particle_fields=_fields, particle_field_units=_units, particles=16\n    )\n\n    ds.add_particle_filter(\"DM\")\n    ds.add_field(\n        (\"gas\", \"DM_cell_mass\"),\n        units=\"g\",\n        function=DM_in_cell_mass,\n        sampling_type=\"cell\",\n    )\n\n    sp = ds.sphere(ds.domain_center, (10, \"kpc\"))\n\n    profile = yt.create_profile(\n        sp,\n        [(\"gas\", \"density\")],\n        [(\"gas\", \"radial_velocity\")],\n        weight_field=(\"gas\", \"DM_cell_mass\"),\n    )\n\n    assert not np.any(np.isnan(profile[\"gas\", \"radial_velocity\"]))\n\n\ndef test_profile_sph_data():\n    ds = fake_sph_orientation_ds()\n    # test we create a profile without raising YTIllDefinedProfile\n    yt.create_profile(\n        ds.all_data(),\n        [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        [(\"gas\", \"kinetic_energy_density\")],\n        weight_field=None,\n    )\n    yt.create_profile(\n        ds.all_data(),\n        [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        [(\"gas\", \"kinetic_energy_density\")],\n        weight_field=(\"gas\", \"density\"),\n    )\n\n\ndef test_profile_override_limits():\n    ds = fake_random_ds(64, nprocs=8, fields=_fields, units=_units)\n\n    sp = ds.sphere(ds.domain_center, (10, \"kpc\"))\n    obins = np.linspace(-5, 5, 10)\n    profile = yt.create_profile(\n        sp,\n        [(\"gas\", \"density\")],\n        [(\"gas\", \"temperature\")],\n        override_bins={(\"gas\", \"density\"): (obins, \"g/cm**3\")},\n    )\n    assert_equal(ds.arr(obins, \"g/cm**3\"), profile.x_bins)\n\n    profile = yt.create_profile(\n        sp,\n        [(\"gas\", \"density\"), (\"stream\", \"dinosaurs\")],\n        [(\"gas\", \"temperature\")],\n        override_bins={\n            (\"gas\", \"density\"): (obins, \"g/cm**3\"),\n            (\"stream\", \"dinosaurs\"): obins,\n        },\n    )\n    assert_equal(ds.arr(obins, \"g/cm**3\"), profile.x_bins)\n    assert_equal(ds.arr(obins, \"dyne\"), profile.y_bins)\n\n    profile = yt.create_profile(\n        sp,\n        [(\"gas\", \"density\"), ((\"stream\", \"dinosaurs\")), (\"stream\", \"tribbles\")],\n        [(\"gas\", \"temperature\")],\n        override_bins={\n            (\"gas\", \"density\"): (obins, \"g/cm**3\"),\n            ((\"stream\", \"dinosaurs\")): obins,\n            (\"stream\", \"tribbles\"): (obins, \"erg\"),\n        },\n    )\n    assert_equal(ds.arr(obins, \"g/cm**3\"), profile.x_bins)\n    assert_equal(ds.arr(obins, \"dyne\"), profile.y_bins)\n    assert_equal(ds.arr(obins, \"erg\"), profile.z_bins)\n\n\nclass TestBadProfiles(unittest.TestCase):\n    tmpdir = None\n    curdir = None\n\n    def setUp(self):\n        self.tmpdir = tempfile.mkdtemp()\n        self.curdir = os.getcwd()\n        os.chdir(self.tmpdir)\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        # clean up\n        shutil.rmtree(self.tmpdir)\n\n    @requires_module(\"h5py\")\n    def test_unequal_data_shape_profile(self):\n        density = np.random.random(128)\n        temperature = np.random.random(128)\n        mass = np.random.random((128, 128))\n\n        my_data = {\n            (\"gas\", \"density\"): density,\n            (\"gas\", \"temperature\"): temperature,\n            (\"gas\", \"mass\"): mass,\n        }\n        fake_ds_med = {\"current_time\": yt.YTQuantity(10, \"Myr\")}\n        field_types = dict.fromkeys(my_data.keys(), \"gas\")\n        yt.save_as_dataset(fake_ds_med, \"mydata.h5\", my_data, field_types=field_types)\n\n        ds = yt.load(\"mydata.h5\")\n\n        with assert_raises(YTProfileDataShape):\n            yt.PhasePlot(\n                ds.data,\n                (\"gas\", \"temperature\"),\n                (\"gas\", \"density\"),\n                (\"gas\", \"mass\"),\n            )\n\n    @requires_module(\"h5py\")\n    def test_unequal_bin_field_profile(self):\n        density = np.random.random(128)\n        temperature = np.random.random(127)\n        mass = np.random.random((128, 128))\n\n        my_data = {\n            (\"gas\", \"density\"): density,\n            (\"gas\", \"temperature\"): temperature,\n            (\"gas\", \"mass\"): mass,\n        }\n        fake_ds_med = {\"current_time\": yt.YTQuantity(10, \"Myr\")}\n        field_types = dict.fromkeys(my_data.keys(), \"gas\")\n        yt.save_as_dataset(fake_ds_med, \"mydata.h5\", my_data, field_types=field_types)\n\n        ds = yt.load(\"mydata.h5\")\n\n        with assert_raises(YTProfileDataShape):\n            yt.PhasePlot(\n                ds.data,\n                (\"gas\", \"temperature\"),\n                (\"gas\", \"density\"),\n                (\"gas\", \"mass\"),\n            )\n\n    def test_set_linear_scaling_for_none_extrema(self):\n        # See Issue #3431\n        # Ensures that extrema are calculated in the same way on subsequent passes\n        # through the PhasePlot machinery.\n        ds = fake_sph_orientation_ds()\n        p = yt.PhasePlot(\n            ds,\n            (\"all\", \"particle_position_spherical_theta\"),\n            (\"all\", \"particle_position_spherical_radius\"),\n            (\"all\", \"particle_mass\"),\n            weight_field=None,\n        )\n        p.set_log((\"all\", \"particle_position_spherical_theta\"), False)\n        p.save()\n\n\ndef test_index_field_units():\n    # see #1849\n    ds = fake_random_ds(16, length_unit=2)\n    ad = ds.all_data()\n    icv_units = ad[\"index\", \"cell_volume\"].units\n    assert str(icv_units) == \"code_length**3\"\n    gcv_units = ad[\"gas\", \"cell_volume\"].units\n    assert str(gcv_units) == \"cm**3\"\n    prof = ad.profile(\n        [(\"gas\", \"density\"), (\"gas\", \"velocity_x\")],\n        [(\"gas\", \"cell_volume\"), (\"index\", \"cell_volume\")],\n        weight_field=None,\n    )\n    assert str(prof[\"index\", \"cell_volume\"].units) == \"code_length**3\"\n    assert str(prof[\"gas\", \"cell_volume\"].units) == \"cm**3\"\n\n\n@requires_module(\"astropy\")\ndef test_export_astropy():\n    from yt.units.yt_array import YTArray\n\n    ds = fake_random_ds(64)\n    ad = ds.all_data()\n    prof = ad.profile(\n        (\"index\", \"radius\"),\n        [(\"gas\", \"density\"), (\"gas\", \"velocity_x\")],\n        weight_field=(\"index\", \"ones\"),\n        n_bins=32,\n    )\n    # export to AstroPy table\n    at1 = prof.to_astropy_table()\n    assert \"radius\" in at1.colnames\n    assert \"density\" in at1.colnames\n    assert \"velocity_x\" in at1.colnames\n    assert_equal(prof.x.d, at1[\"radius\"].value)\n    assert_equal(prof[\"gas\", \"density\"].d, at1[\"density\"].value)\n    assert_equal(prof[\"gas\", \"velocity_x\"].d, at1[\"velocity_x\"].value)\n    assert prof.x.units == YTArray.from_astropy(at1[\"radius\"]).units\n    assert prof[\"gas\", \"density\"].units == YTArray.from_astropy(at1[\"density\"]).units\n    assert (\n        prof[\"gas\", \"velocity_x\"].units == YTArray.from_astropy(at1[\"velocity_x\"]).units\n    )\n    assert np.all(at1.mask[\"density\"] == prof.used)\n    at2 = prof.to_astropy_table(fields=(\"gas\", \"density\"), only_used=True)\n    assert \"radius\" in at2.colnames\n    assert \"velocity_x\" not in at2.colnames\n    assert_equal(prof.x.d[prof.used], at2[\"radius\"].value)\n    assert_equal(prof[\"gas\", \"density\"].d[prof.used], at2[\"density\"].value)\n    at3 = prof.to_astropy_table(fields=(\"gas\", \"density\"), include_std=True)\n    assert_equal(prof[\"gas\", \"density\"].d, at3[\"density\"].value)\n    assert_equal(\n        prof.standard_deviation[\"gas\", \"density\"].d, at3[\"density_stddev\"].value\n    )\n    assert (\n        prof.standard_deviation[\"gas\", \"density\"].units\n        == YTArray.from_astropy(at3[\"density_stddev\"]).units\n    )\n\n\n@requires_module(\"pandas\")\ndef test_export_pandas():\n    ds = fake_random_ds(64)\n    ad = ds.all_data()\n    prof = ad.profile(\n        \"radius\",\n        [(\"gas\", \"density\"), (\"gas\", \"velocity_x\")],\n        weight_field=(\"index\", \"ones\"),\n        n_bins=32,\n    )\n    # export to pandas DataFrame\n    df1 = prof.to_dataframe()\n    assert \"radius\" in df1.columns\n    assert \"density\" in df1.columns\n    assert \"velocity_x\" in df1.columns\n    assert_equal(prof.x.d, df1[\"radius\"])\n    assert_equal(prof[\"gas\", \"density\"].d, np.nan_to_num(df1[\"density\"]))\n    assert_equal(prof[\"velocity_x\"].d, np.nan_to_num(df1[\"velocity_x\"]))\n    df2 = prof.to_dataframe(fields=(\"gas\", \"density\"), only_used=True)\n    assert \"radius\" in df2.columns\n    assert \"velocity_x\" not in df2.columns\n    assert_equal(prof.x.d[prof.used], df2[\"radius\"])\n    assert_equal(prof[\"gas\", \"density\"].d[prof.used], df2[\"density\"])\n    df3 = prof.to_dataframe(fields=(\"gas\", \"density\"), include_std=True)\n    assert_equal(\n        prof.standard_deviation[\"gas\", \"density\"].d,\n        np.nan_to_num(df3[\"density_stddev\"]),\n    )\n\n\ndef test_preserve_source_parameters_restores_field_parameters_on_exception():\n    # Regression test for preserve_source_parameters:\n    # the decorator must restore source.field_parameters even if the wrapped\n    # function raises.\n    from yt.data_objects.profiles import preserve_source_parameters\n\n    class Dummy:\n        pass\n\n    prof = Dummy()\n    prof._data_source = Dummy()\n    prof._data_source.field_parameters = {\"from_data_source\": 1}\n\n    source = Dummy()\n    original_params = {\"original\": True}\n    source.field_parameters = original_params\n\n    @preserve_source_parameters\n    def boom(_prof, _source):\n        # While executing, field_parameters should be temporarily redirected\n        assert _source.field_parameters is _prof._data_source.field_parameters\n        raise RuntimeError(\"boom\")\n\n    with assert_raises(RuntimeError):\n        boom(prof, source)\n\n    # The key: even after an exception, state should be restored\n    assert source.field_parameters is original_params\n    assert_equal(source.field_parameters, {\"original\": True})\n"
  },
  {
    "path": "yt/data_objects/tests/test_projection.py",
    "content": "import os\nimport tempfile\nfrom unittest import mock\n\nimport numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import assert_rel_equal, fake_amr_ds, fake_random_ds\nfrom yt.units.unit_object import Unit\n\nLENGTH_UNIT = 2.0\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef teardown_func(fns):\n    for fn in fns:\n        try:\n            os.remove(fn)\n        except OSError:\n            pass\n\n\n@mock.patch(\"matplotlib.backends.backend_agg.FigureCanvasAgg.print_figure\")\ndef test_projection(pf):\n    fns = []\n    for nprocs in [8, 1]:\n        # We want to test both 1 proc and 8 procs, to make sure that\n        # parallelism isn't broken\n        fields = (\"density\", \"temperature\", \"velocity_x\", \"velocity_y\", \"velocity_z\")\n        units = (\"g/cm**3\", \"K\", \"cm/s\", \"cm/s\", \"cm/s\")\n        ds = fake_random_ds(\n            64, fields=fields, units=units, nprocs=nprocs, length_unit=LENGTH_UNIT\n        )\n        dims = ds.domain_dimensions\n        xn, yn, zn = ds.domain_dimensions\n        xi, yi, zi = ds.domain_left_edge.to_ndarray() + 1.0 / (ds.domain_dimensions * 2)\n        xf, yf, zf = ds.domain_right_edge.to_ndarray() - 1.0 / (\n            ds.domain_dimensions * 2\n        )\n        dd = ds.all_data()\n        coords = np.mgrid[xi : xf : xn * 1j, yi : yf : yn * 1j, zi : zf : zn * 1j]\n        uc = [np.unique(c) for c in coords]\n        # test if projections inherit the field parameters of their data sources\n        dd.set_field_parameter(\"bulk_velocity\", np.array([0, 1, 2]))\n        proj = ds.proj((\"gas\", \"density\"), 0, data_source=dd)\n        assert_equal(\n            dd.field_parameters[\"bulk_velocity\"], proj.field_parameters[\"bulk_velocity\"]\n        )\n\n        # Some simple projection tests with single grids\n        for ax, an in enumerate(\"xyz\"):\n            xax = ds.coordinates.x_axis[ax]\n            yax = ds.coordinates.y_axis[ax]\n            for wf in [(\"gas\", \"density\"), None]:\n                proj = ds.proj(\n                    [(\"index\", \"ones\"), (\"gas\", \"density\")], ax, weight_field=wf\n                )\n                if wf is None:\n                    assert_equal(\n                        proj[\"index\", \"ones\"].sum(),\n                        LENGTH_UNIT * proj[\"index\", \"ones\"].size,\n                    )\n                    assert_equal(proj[\"index\", \"ones\"].min(), LENGTH_UNIT)\n                    assert_equal(proj[\"index\", \"ones\"].max(), LENGTH_UNIT)\n                else:\n                    assert_equal(\n                        proj[\"index\", \"ones\"].sum(), proj[\"index\", \"ones\"].size\n                    )\n                    assert_equal(proj[\"index\", \"ones\"].min(), 1.0)\n                    assert_equal(proj[\"index\", \"ones\"].max(), 1.0)\n                assert_equal(np.unique(proj[\"px\"]), uc[xax])\n                assert_equal(np.unique(proj[\"py\"]), uc[yax])\n                assert_equal(np.unique(proj[\"pdx\"]), 1.0 / (dims[xax] * 2.0))\n                assert_equal(np.unique(proj[\"pdy\"]), 1.0 / (dims[yax] * 2.0))\n                plots = [proj.to_pw(fields=(\"gas\", \"density\")), proj.to_pw()]\n                for pw in plots:\n                    for p in pw.plots.values():\n                        tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n                        os.close(tmpfd)\n                        p.save(name=tmpname)\n                        fns.append(tmpname)\n                frb = proj.to_frb((1.0, \"unitary\"), 64)\n                for proj_field in [\n                    (\"index\", \"ones\"),\n                    (\"gas\", \"density\"),\n                    (\"gas\", \"temperature\"),\n                ]:\n                    fi = ds._get_field_info(proj_field)\n                    assert_equal(frb[proj_field].info[\"data_source\"], proj.__str__())\n                    assert_equal(frb[proj_field].info[\"axis\"], ax)\n                    assert_equal(frb[proj_field].info[\"field\"], str(proj_field))\n                    field_unit = Unit(fi.units)\n                    if wf is not None:\n                        assert_equal(\n                            frb[proj_field].units,\n                            Unit(field_unit, registry=ds.unit_registry),\n                        )\n                    else:\n                        if frb[proj_field].units.is_code_unit:\n                            proj_unit = \"code_length\"\n                        else:\n                            proj_unit = \"cm\"\n                        if field_unit != \"\" and field_unit != Unit():\n                            proj_unit = f\"({field_unit}) * {proj_unit}\"\n                        assert_equal(\n                            frb[proj_field].units,\n                            Unit(proj_unit, registry=ds.unit_registry),\n                        )\n                    assert_equal(frb[proj_field].info[\"xlim\"], frb.bounds[:2])\n                    assert_equal(frb[proj_field].info[\"ylim\"], frb.bounds[2:])\n                    assert_equal(frb[proj_field].info[\"center\"], proj.center)\n                    if wf is None:\n                        assert_equal(frb[proj_field].info[\"weight_field\"], wf)\n                    else:\n                        assert_equal(\n                            frb[proj_field].info[\"weight_field\"],\n                            proj.data_source._determine_fields(wf)[0],\n                        )\n            # wf == None\n            assert_equal(wf, None)\n            v1 = proj[\"gas\", \"density\"].sum()\n            v2 = (dd[\"gas\", \"density\"] * dd[\"index\", f\"d{an}\"]).sum()\n            assert_rel_equal(v1, v2.in_units(v1.units), 10)\n\n        # Test moment projections\n        def make_vsq_field(aname):\n            def _vsquared(data):\n                return data[\"gas\", f\"velocity_{aname}\"] ** 2\n\n            return _vsquared\n\n        for ax, an in enumerate(\"xyz\"):\n            ds.add_field(\n                (\"gas\", f\"velocity_{an}_squared\"),\n                make_vsq_field(an),\n                sampling_type=\"local\",\n                units=\"cm**2/s**2\",\n            )\n            proj1 = ds.proj(\n                [(\"gas\", f\"velocity_{an}\"), (\"gas\", f\"velocity_{an}_squared\")],\n                ax,\n                weight_field=(\"gas\", \"density\"),\n                moment=1,\n            )\n            proj2 = ds.proj(\n                (\"gas\", f\"velocity_{an}\"), ax, weight_field=(\"gas\", \"density\"), moment=2\n            )\n            assert_rel_equal(\n                np.sqrt(\n                    proj1[\"gas\", f\"velocity_{an}_squared\"]\n                    - proj1[\"gas\", f\"velocity_{an}\"] ** 2\n                ),\n                proj2[\"gas\", f\"velocity_{an}\"],\n                10,\n            )\n    teardown_func(fns)\n\n\ndef test_max_level():\n    ds = fake_amr_ds(fields=[(\"gas\", \"density\")], units=[\"mp/cm**3\"])\n    proj = ds.proj((\"gas\", \"density\"), 2, method=\"max\", max_level=2)\n    assert proj[\"index\", \"grid_level\"].max() == 2\n\n    proj = ds.proj((\"gas\", \"density\"), 2, method=\"max\")\n    assert proj[\"index\", \"grid_level\"].max() == ds.index.max_level\n\n\ndef test_min_level():\n    ds = fake_amr_ds(fields=[(\"gas\", \"density\")], units=[\"mp/cm**3\"])\n    proj = ds.proj((\"gas\", \"density\"), 2, method=\"min\")\n    assert proj[\"index\", \"grid_level\"].min() == 0\n\n    proj = ds.proj((\"gas\", \"density\"), 2, method=\"max\")\n    assert proj[\"index\", \"grid_level\"].min() == ds.index.min_level\n"
  },
  {
    "path": "yt/data_objects/tests/test_rays.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt import load\nfrom yt.testing import (\n    assert_rel_equal,\n    cubicspline_python,\n    fake_random_ds,\n    fake_sph_grid_ds,\n    integrate_kernel,\n    requires_file,\n    requires_module,\n)\nfrom yt.units._numpy_wrapper_functions import uconcatenate\n\n\ndef test_ray():\n    for nproc in [1, 2, 4, 8]:\n        ds = fake_random_ds(64, nprocs=nproc)\n        dx = (ds.domain_right_edge - ds.domain_left_edge) / ds.domain_dimensions\n        # Three we choose, to get varying vectors, and ten random\n        pp1 = np.random.random((3, 13))\n        pp2 = np.random.random((3, 13))\n        pp1[:, 0] = [0.1, 0.2, 0.3]\n        pp2[:, 0] = [0.8, 0.1, 0.4]\n        pp1[:, 1] = [0.9, 0.2, 0.3]\n        pp2[:, 1] = [0.8, 0.1, 0.4]\n        pp1[:, 2] = [0.9, 0.2, 0.9]\n        pp2[:, 2] = [0.8, 0.1, 0.4]\n        unitary = ds.arr(1.0, \"\")\n        for i in range(pp1.shape[1]):\n            p1 = ds.arr(pp1[:, i] + 1e-8 * np.random.random(3), \"code_length\")\n            p2 = ds.arr(pp2[:, i] + 1e-8 * np.random.random(3), \"code_length\")\n\n            my_ray = ds.ray(p1, p2)\n            assert_rel_equal(my_ray[\"dts\"].sum(), unitary, 14)\n            ray_cells = my_ray[\"dts\"] > 0\n\n            # find cells intersected by the ray\n            my_all = ds.all_data()\n\n            dt = np.abs(dx / (p2 - p1))\n            tin = uconcatenate(\n                [\n                    [(my_all[\"index\", \"x\"] - p1[0]) / (p2 - p1)[0] - 0.5 * dt[0]],\n                    [(my_all[\"index\", \"y\"] - p1[1]) / (p2 - p1)[1] - 0.5 * dt[1]],\n                    [(my_all[\"index\", \"z\"] - p1[2]) / (p2 - p1)[2] - 0.5 * dt[2]],\n                ]\n            )\n            tout = uconcatenate(\n                [\n                    [(my_all[\"index\", \"x\"] - p1[0]) / (p2 - p1)[0] + 0.5 * dt[0]],\n                    [(my_all[\"index\", \"y\"] - p1[1]) / (p2 - p1)[1] + 0.5 * dt[1]],\n                    [(my_all[\"index\", \"z\"] - p1[2]) / (p2 - p1)[2] + 0.5 * dt[2]],\n                ]\n            )\n            tin = tin.max(axis=0)\n            tout = tout.min(axis=0)\n            my_cells = (tin < tout) & (tin < 1) & (tout > 0)\n\n            assert_equal(ray_cells.sum(), my_cells.sum())\n            assert_rel_equal(\n                my_ray[\"gas\", \"density\"][ray_cells].sum(),\n                my_all[\"gas\", \"density\"][my_cells].sum(),\n                14,\n            )\n            assert_rel_equal(my_ray[\"dts\"].sum(), unitary, 14)\n\n\n@requires_module(\"h5py\")\n@requires_file(\"GadgetDiskGalaxy/snapshot_200.hdf5\")\ndef test_ray_particle():\n    ds = load(\"GadgetDiskGalaxy/snapshot_200.hdf5\")\n    ray = ds.ray(ds.domain_left_edge, ds.domain_right_edge)\n    assert_equal(ray[\"t\"].shape, (1451,))\n    assert ray[\"dts\"].sum(dtype=\"f8\") > 0\n\n\n## test that kernels integrate correctly\n# (1) including the right particles\n# (2) correct t and dts values for those particles\n## fake_sph_grid_ds:\n# This dataset should have 27 particles with the particles arranged\n# uniformly on a 3D grid. The bottom left corner is (0.5,0.5,0.5) and\n# the top right corner is (2.5,2.5,2.5). All particles will have\n# non-overlapping smoothing regions with a radius of 0.05, masses of\n# 1, and densities of 1, and zero velocity.\n\n\ndef test_ray_particle2():\n    kernelfunc = cubicspline_python\n    ds = fake_sph_grid_ds(hsml_factor=1.0)\n    ds.kernel_name = \"cubic\"\n\n    ## Ray through the one particle at (0.5, 0.5, 0.5):\n    ## test basic kernel integration\n    eps = 0.0  # 1e-7\n    start0 = np.array((1.0 + eps, 0.0, 0.5))\n    end0 = np.array((0.0, 1.0 + eps, 0.5))\n    ray0 = ds.ray(start0, end0)\n    b0 = np.array([np.sqrt(2.0) * eps])\n    hsml0 = np.array([0.05])\n    len0 = np.sqrt(np.sum((end0 - start0) ** 2))\n    # for a ParticleDataset like this one, the Ray object attempts\n    # to generate the 't' and 'dts' fields using the grid method\n    ray0.field_data[\"t\"] = ray0.ds.arr(ray0._generate_container_field_sph(\"t\"))\n    ray0.field_data[\"dts\"] = ray0.ds.arr(ray0._generate_container_field_sph(\"dts\"))\n    # not demanding too much precision;\n    # from kernel volume integrals, the linear interpolation\n    # restricts you to 4 -- 5 digits precision\n    assert_equal(ray0[\"t\"].shape, (1,))\n    assert_rel_equal(ray0[\"t\"], np.array([0.5]), 5)\n    assert_rel_equal(ray0[\"gas\", \"position\"].v, np.array([[0.5, 0.5, 0.5]]), 5)\n    dl0 = integrate_kernel(kernelfunc, b0, hsml0)\n    dl0 *= ray0[\"gas\", \"mass\"].v / ray0[\"gas\", \"density\"].v\n    assert_rel_equal(ray0[(\"dts\")].v, dl0 / len0, 4)\n\n    ## Ray in the middle of the box:\n    ## test end points, >1 particle\n    start1 = np.array((1.53, 0.53, 1.0))\n    end1 = np.array((1.53, 0.53, 3.0))\n    ray1 = ds.ray(start1, end1)\n    b1 = np.array([np.sqrt(2.0) * 0.03] * 2)\n    hsml1 = np.array([0.05] * 2)\n    len1 = np.sqrt(np.sum((end1 - start1) ** 2))\n    # for a ParticleDataset like this one, the Ray object attempts\n    # to generate the 't' and 'dts' fields using the grid method\n    ray1.field_data[\"t\"] = ray1.ds.arr(ray1._generate_container_field_sph(\"t\"))\n    ray1.field_data[\"dts\"] = ray1.ds.arr(ray1._generate_container_field_sph(\"dts\"))\n    # not demanding too much precision;\n    # from kernel volume integrals, the linear interpolation\n    # restricts you to 4 -- 5 digits precision\n    assert_equal(ray1[\"t\"].shape, (2,))\n    assert_rel_equal(ray1[\"t\"], np.array([0.25, 0.75]), 5)\n    assert_rel_equal(\n        ray1[\"gas\", \"position\"].v, np.array([[1.5, 0.5, 1.5], [1.5, 0.5, 2.5]]), 5\n    )\n    dl1 = integrate_kernel(kernelfunc, b1, hsml1)\n    dl1 *= ray1[\"gas\", \"mass\"].v / ray1[\"gas\", \"density\"].v\n    assert_rel_equal(ray1[(\"dts\")].v, dl1 / len1, 4)\n\n    ## Ray missing all particles:\n    ## test handling of size-0 selections\n    start2 = np.array((1.0, 2.0, 0.0))\n    end2 = np.array((1.0, 2.0, 3.0))\n    ray2 = ds.ray(start2, end2)\n    # for a ParticleDataset like this one, the Ray object attempts\n    # to generate the 't' and 'dts' fields using the grid method\n    ray2.field_data[\"t\"] = ray2.ds.arr(ray2._generate_container_field_sph(\"t\"))\n    ray2.field_data[\"dts\"] = ray2.ds.arr(ray2._generate_container_field_sph(\"dts\"))\n    assert_equal(ray2[\"t\"].shape, (0,))\n    assert_equal(ray2[\"dts\"].shape, (0,))\n    assert_equal(ray2[\"gas\", \"position\"].v.shape, (0, 3))\n"
  },
  {
    "path": "yt/data_objects/tests/test_refinement.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_equal, assert_equal\n\nimport yt\n\n\ndef setup_fake_refby():\n    refine_by = np.array([5, 1, 1])\n    top_grid_dim = [100, 10, 2]\n    n1 = 100\n    n2 = 10\n    n3 = 2\n\n    grid_data = [\n        {\n            \"left_edge\": [0.0, 0.0, 0.0],\n            \"right_edge\": [1.0, np.pi, np.pi * 2.0],\n            \"level\": 0,\n            \"dimensions\": np.array([n1, n2, n3]),\n        },\n        {\n            \"left_edge\": [0.0, 0.0, 0.0],\n            \"right_edge\": [0.5, np.pi, np.pi * 2.0],\n            \"level\": 1,\n            \"dimensions\": refine_by * [n1 / 2.0, n2, n3],\n        },\n    ]\n\n    for g in grid_data:\n        g[\"density\"] = (np.random.random(g[\"dimensions\"].astype(\"i8\")), \"g/cm**3\")\n    bbox = np.array([[0.0, 1.0], [0.0, np.pi], [0.0, np.pi * 2]])\n\n    ds = yt.load_amr_grids(\n        grid_data,\n        top_grid_dim,\n        bbox=bbox,\n        geometry=\"spherical\",\n        refine_by=refine_by,\n        length_unit=\"kpc\",\n    )\n    return ds\n\n\ndef test_refine_by():\n    ds = setup_fake_refby()\n    dd = ds.all_data()\n    # This checks that we always refine_by 1 in dimensions 2 and 3\n    dims = ds.domain_dimensions * ds.refine_by**ds.max_level\n    for i in range(1, 3):\n        # Check the refine_by == 1\n        ncoords = np.unique(dd.icoords[:, i]).size\n        assert_equal(ncoords, dims[i])\n    for g in ds.index.grids:\n        dims = ds.domain_dimensions * ds.refine_by**g.Level\n        # Now we can check converting back to the reference space\n        v = ((g.icoords + 1) / dims.astype(\"f8\")).max(axis=0)\n        v *= ds.domain_width\n        assert_array_equal(v, g.RightEdge.d)\n"
  },
  {
    "path": "yt/data_objects/tests/test_regions.py",
    "content": "from numpy.testing import assert_array_equal, assert_raises\n\nfrom yt.testing import fake_amr_ds, fake_random_ds\nfrom yt.units import cm\n\n\ndef test_box_creation():\n    # test that creating a region with left and right edge\n    # with units works\n    ds = fake_random_ds(32, length_unit=2)\n    reg = ds.box([0, 0, 0] * cm, [2, 2, 2] * cm)\n    dens_units = reg[\"gas\", \"density\"]\n\n    reg = ds.box([0, 0, 0], [1, 1, 1])\n    dens_no_units = reg[\"gas\", \"density\"]\n\n    assert_array_equal(dens_units, dens_no_units)\n\n\ndef test_max_level_min_level_semantics():\n    ds = fake_amr_ds()\n    ad = ds.all_data()\n    assert ad[\"index\", \"grid_level\"].max() == 4\n    ad.max_level = 2\n    assert ad[\"index\", \"grid_level\"].max() == 2\n    ad.max_level = 8\n    assert ad[\"index\", \"grid_level\"].max() == 4\n    ad.min_level = 2\n    assert ad[\"index\", \"grid_level\"].min() == 2\n    ad.min_level = 0\n    assert ad[\"index\", \"grid_level\"].min() == 0\n\n\ndef test_ellipsis_selection():\n    ds = fake_amr_ds()\n    reg = ds.r[:, :, :]\n    ereg = ds.r[...]\n    assert_array_equal(reg.fwidth, ereg.fwidth)\n\n    reg = ds.r[(0.5, \"cm\"), :, :]\n    ereg = ds.r[(0.5, \"cm\"), ...]\n    assert_array_equal(reg.fwidth, ereg.fwidth)\n\n    reg = ds.r[:, :, (0.5, \"cm\")]\n    ereg = ds.r[..., (0.5, \"cm\")]\n    assert_array_equal(reg.fwidth, ereg.fwidth)\n\n    reg = ds.r[:, :, (0.5, \"cm\")]\n    ereg = ds.r[..., (0.5, \"cm\")]\n    assert_array_equal(reg.fwidth, ereg.fwidth)\n\n    assert_raises(IndexError, ds.r.__getitem__, (..., (0.5, \"cm\"), ...))\n\n\n# this test will fail until \"arbitrary_grid\" selector is implemented for 2D datasets\n# see issue https://github.com/yt-project/yt/issues/3437\n\"\"\"\nfrom yt.utilities.answer_testing.framework import data_dir_load, requires_ds\n\n@requires_ds(\"castro_sedov_2d_cyl_in_cart_plt00150\")\ndef test_complex_slicing_2D_consistency():\n\n    # see https://github.com/yt-project/yt/issues/3429\n    ds = data_dir_load(\"castro_sedov_2d_cyl_in_cart_plt00150\")\n\n    reg = ds.r[0.1:0.2:8j, :]\n    reg[\"gas\", \"density\"]\n    reg = ds.r[:, 1:2:8j]\n    reg[\"gas\", \"density\"]\n    reg = ds.r[0.1:0.2:8j, 1:2:8j]\n    reg[\"gas\", \"density\"]\n\"\"\"\n"
  },
  {
    "path": "yt/data_objects/tests/test_registration.py",
    "content": "import pytest\n\nfrom yt.data_objects.static_output import Dataset\nfrom yt.utilities.object_registries import output_type_registry\n\n\ndef test_reregistration_warning():\n    from yt.frontends import enzo  # noqa\n\n    true_EnzoDataset = output_type_registry[\"EnzoDataset\"]\n    try:\n        with pytest.warns(\n            UserWarning,\n            match=(\n                \"Overwriting EnzoDataset, which was previously registered. \"\n                \"This is expected if you're importing a yt extension with a \"\n                \"frontend that was already migrated to the main code base.\"\n            ),\n        ):\n\n            class EnzoDataset(Dataset):\n                pass\n\n    finally:\n        output_type_registry[\"EnzoDataset\"] = true_EnzoDataset\n"
  },
  {
    "path": "yt/data_objects/tests/test_slice.py",
    "content": "import os\nimport tempfile\nfrom unittest import mock\n\nimport numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import fake_random_ds\nfrom yt.units.unit_object import Unit\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef teardown_func(fns):\n    for fn in fns:\n        try:\n            os.remove(fn)\n        except OSError:\n            pass\n\n\n@mock.patch(\"matplotlib.backends.backend_agg.FigureCanvasAgg.print_figure\")\ndef test_slice(pf):\n    fns = []\n    grid_eps = np.finfo(np.float64).eps\n    for nprocs in [8, 1]:\n        # We want to test both 1 proc and 8 procs, to make sure that\n        # parallelism isn't broken\n        ds = fake_random_ds(64, nprocs=nprocs)\n        dims = ds.domain_dimensions\n        xn, yn, zn = ds.domain_dimensions\n        dx = ds.arr(1.0 / (ds.domain_dimensions * 2), \"code_length\")\n        xi, yi, zi = ds.domain_left_edge + dx\n        xf, yf, zf = ds.domain_right_edge - dx\n        coords = np.mgrid[xi : xf : xn * 1j, yi : yf : yn * 1j, zi : zf : zn * 1j]\n        uc = [np.unique(c) for c in coords]\n        slc_pos = 0.5\n        # Some simple slice tests with single grids\n        for ax in range(3):\n            xax = ds.coordinates.x_axis[ax]\n            yax = ds.coordinates.y_axis[ax]\n            slc = ds.slice(ax, slc_pos)\n            shifted_slc = ds.slice(ax, slc_pos + grid_eps)\n            assert_equal(slc[\"index\", \"ones\"].sum(), slc[\"index\", \"ones\"].size)\n            assert_equal(slc[\"index\", \"ones\"].min(), 1.0)\n            assert_equal(slc[\"index\", \"ones\"].max(), 1.0)\n            assert_equal(np.unique(slc[\"px\"]), uc[xax])\n            assert_equal(np.unique(slc[\"py\"]), uc[yax])\n            assert_equal(np.unique(slc[\"pdx\"]), 0.5 / dims[xax])\n            assert_equal(np.unique(slc[\"pdy\"]), 0.5 / dims[yax])\n            pw = slc.to_pw(fields=(\"gas\", \"density\"))\n            for p in pw.plots.values():\n                tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n                os.close(tmpfd)\n                p.save(name=tmpname)\n                fns.append(tmpname)\n            for width in [(1.0, \"unitary\"), 1.0, ds.quan(0.5, \"code_length\")]:\n                frb = slc.to_frb(width, 64)\n                shifted_frb = shifted_slc.to_frb(width, 64)\n                for slc_field in [(\"index\", \"ones\"), (\"gas\", \"density\")]:\n                    fi = ds._get_field_info(slc_field)\n                    assert_equal(frb[slc_field].info[\"data_source\"], slc.__str__())\n                    assert_equal(frb[slc_field].info[\"axis\"], ax)\n                    assert_equal(frb[slc_field].info[\"field\"], str(slc_field))\n                    assert_equal(frb[slc_field].units, Unit(fi.units))\n                    assert_equal(frb[slc_field].info[\"xlim\"], frb.bounds[:2])\n                    assert_equal(frb[slc_field].info[\"ylim\"], frb.bounds[2:])\n                    assert_equal(frb[slc_field].info[\"center\"], slc.center)\n                    assert_equal(frb[slc_field].info[\"coord\"], slc_pos)\n                    assert_equal(frb[slc_field], shifted_frb[slc_field])\n    teardown_func(fns)\n\n\ndef test_slice_over_edges():\n    ds = fake_random_ds(\n        64, nprocs=8, fields=(\"density\",), units=(\"g/cm**3\",), negative=[False]\n    )\n    slc = ds.slice(0, 0.0)\n    slc[\"gas\", \"density\"]\n    slc = ds.slice(1, 0.5)\n    slc[\"gas\", \"density\"]\n\n\ndef test_slice_over_outer_boundary():\n    ds = fake_random_ds(\n        64, nprocs=8, fields=(\"density\",), units=(\"g/cm**3\",), negative=[False]\n    )\n    slc = ds.slice(2, 1.0)\n    slc[\"gas\", \"density\"]\n    assert_equal(slc[\"gas\", \"density\"].size, 0)\n"
  },
  {
    "path": "yt/data_objects/tests/test_sph_data_objects.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_allclose, assert_almost_equal, assert_equal\n\nfrom yt import SlicePlot, add_particle_filter\nfrom yt.loaders import load\nfrom yt.testing import fake_sph_grid_ds, fake_sph_orientation_ds, requires_file\n\n\ndef test_point():\n    ds = fake_sph_orientation_ds()\n    field_data = ds.stream_handler.fields[\"stream_file\"]\n    ppos = [field_data[\"io\", f\"particle_position_{d}\"] for d in \"xyz\"]\n    ppos = np.array(ppos).T\n    for pos in ppos:\n        for i in range(-1, 2):\n            offset = 0.1 * np.array([i, 0, 0])\n            pt = ds.point(pos + offset)\n            assert_equal(pt[\"gas\", \"density\"].shape[0], 1)\n        for j in range(-1, 2):\n            offset = 0.1 * np.array([0, j, 0])\n            pt = ds.point(pos + offset)\n            assert_equal(pt[\"gas\", \"density\"].shape[0], 1)\n        for k in range(-1, 2):\n            offset = 0.1 * np.array([0, 0, k])\n            pt = ds.point(pos + offset)\n            assert_equal(pt[\"gas\", \"density\"].shape[0], 1)\n\n\n# The number of particles along each slice axis at that coordinate\nSLICE_ANSWERS = {\n    (\"x\", 0): 6,\n    (\"x\", 0.5): 0,\n    (\"x\", 1): 1,\n    (\"y\", 0): 5,\n    (\"y\", 1): 1,\n    (\"y\", 2): 1,\n    (\"z\", 0): 4,\n    (\"z\", 1): 1,\n    (\"z\", 2): 1,\n    (\"z\", 3): 1,\n}\n\n\ndef test_slice():\n    ds = fake_sph_orientation_ds()\n    for (ax, coord), answer in SLICE_ANSWERS.items():\n        # test that we can still select particles even if we offset the slice\n        # within each particle's smoothing volume\n        for i in range(-1, 2):\n            sl = ds.slice(ax, coord + i * 0.1)\n            assert_equal(sl[\"gas\", \"density\"].shape[0], answer)\n\n\nREGION_ANSWERS = {\n    ((-4, -4, -4), (4, 4, 4)): 7,\n    ((0, 0, 0), (4, 4, 4)): 7,\n    ((1, 0, 0), (4, 4, 4)): 1,\n    ((0, 1, 0), (4, 4, 4)): 2,\n    ((0, 0, 1), (4, 4, 4)): 3,\n    ((0, 0, 0), (4, 4, 2)): 6,\n    ((0, 0, 0), (4, 4, 1)): 5,\n    ((0, 0, 0), (4, 1, 4)): 6,\n    ((0, 0, 0), (1, 1, 4)): 6,\n}\n\n\ndef test_slice_to_frb():\n    ds = fake_sph_orientation_ds()\n    frb = ds.slice(0, 0.5).to_frb(ds.domain_width[0], (64, 64))\n    ref_vals = frb[\"gas\", \"density\"]\n    for center in ((0.5, \"code_length\"), (0.5, \"cm\"), ds.quan(0.5, \"code_length\")):\n        frb = ds.slice(0, center).to_frb(ds.domain_width[0], (64, 64))\n        vals = frb[\"gas\", \"density\"]\n        assert_equal(vals, ref_vals)\n\n\ndef test_region():\n    ds = fake_sph_orientation_ds()\n    for (left_edge, right_edge), answer in REGION_ANSWERS.items():\n        # test that regions enclosing a particle's smoothing region\n        # correctly select SPH particles\n        for i in range(-1, 2):\n            for j in range(-1, 2):\n                le = np.array([le + i * 0.1 for le in left_edge])\n                re = np.array([re + j * 0.1 for re in right_edge])\n\n                # check if we went off the edge of the domain\n                whl = le < ds.domain_left_edge\n                le[whl] = ds.domain_left_edge[whl]\n                whr = re > ds.domain_right_edge\n                re[whr] = ds.domain_right_edge[whr]\n\n                reg = ds.box(le, re)\n                assert_equal(reg[\"gas\", \"density\"].shape[0], answer)\n\n\ndef test_periodic_region():\n    ds = fake_sph_grid_ds(10.0)\n    coords = [0.7, 1.4, 2.8]\n\n    for x in coords:\n        for y in coords:\n            for z in coords:\n                center = np.array([x, y, z])\n                for n, w in [(8, 1.0), (27, 2.0)]:\n                    le = center - 0.5 * w\n                    re = center + 0.5 * w\n                    box = ds.box(le, re)\n                    assert box[\"io\", \"particle_ones\"].size == n\n\n\nSPHERE_ANSWERS = {\n    ((0, 0, 0), 4): 7,\n    ((0, 0, 0), 3): 7,\n    ((0, 0, 0), 2): 6,\n    ((0, 0, 0), 1): 4,\n    ((0, 0, 0), 0.5): 1,\n    ((1, 0, 0), 0.5): 1,\n    ((1, 0, 0), 1.0): 2,\n    ((0, 1, 0), 1.0): 3,\n    ((0, 0, 1), 1.0): 3,\n}\n\n\ndef test_sphere():\n    ds = fake_sph_orientation_ds()\n    for (center, radius), answer in SPHERE_ANSWERS.items():\n        # test that spheres enclosing a particle's smoothing region\n        # correctly select SPH particles\n        for i in range(-1, 2):\n            for j in range(-1, 2):\n                cent = np.array([c + i * 0.1 for c in center])\n                rad = radius + 0.1 * j\n                sph = ds.sphere(cent, rad)\n                assert_equal(sph[\"gas\", \"density\"].shape[0], answer)\n\n\nDISK_ANSWERS = {\n    ((0, 0, 0), (0, 0, 1), 4, 3): 7,\n    ((0, 0, 0), (0, 0, 1), 4, 2): 6,\n    ((0, 0, 0), (0, 0, 1), 4, 1): 5,\n    ((0, 0, 0), (0, 0, 1), 4, 0.5): 4,\n    ((0, 0, 0), (0, 1, 0), 4, 3): 7,\n    ((0, 0, 0), (0, 1, 0), 4, 2): 7,\n    ((0, 0, 0), (0, 1, 0), 4, 1): 6,\n    ((0, 0, 0), (0, 1, 0), 4, 0.5): 5,\n    ((0, 0, 0), (1, 0, 0), 4, 3): 7,\n    ((0, 0, 0), (1, 0, 0), 4, 2): 7,\n    ((0, 0, 0), (1, 0, 0), 4, 1): 7,\n    ((0, 0, 0), (1, 0, 0), 4, 0.5): 6,\n    ((0, 0, 0), (1, 1, 1), 1, 1): 4,\n    ((-0.5, -0.5, -0.5), (1, 1, 1), 4, 4): 7,\n}\n\n\ndef test_disk():\n    ds = fake_sph_orientation_ds()\n    for (center, normal, radius, height), answer in DISK_ANSWERS.items():\n        # test that disks enclosing a particle's smoothing region\n        # correctly select SPH particles\n        for i in range(-1, 2):\n            cent = np.array([c + i * 0.1 for c in center])\n            disk = ds.disk(cent, normal, radius, height)\n            assert_equal(disk[\"gas\", \"density\"].shape[0], answer)\n\n\nRAY_ANSWERS = {\n    ((0, 0, 0), (3, 0, 0)): 2,\n    ((0, 0, 0), (0, 3, 0)): 3,\n    ((0, 0, 0), (0, 0, 3)): 4,\n    ((0, 1, 0), (0, 2, 0)): 2,\n    ((1, 0, 0), (0, 2, 0)): 2,\n    ((0.5, 0.5, 0.5), (0.5, 0.5, 3.5)): 0,\n}\n\n\ndef test_ray():\n    ds = fake_sph_orientation_ds()\n    for (start_point, end_point), answer in RAY_ANSWERS.items():\n        for i in range(-1, 2):\n            start = np.array([s + i * 0.1 for s in start_point])\n            end = np.array([e + i * 0.1 for e in end_point])\n            ray = ds.ray(start, end)\n            assert_equal(ray[\"gas\", \"density\"].shape[0], answer)\n\n\nCUTTING_ANSWERS = {\n    ((1, 0, 0), (0, 0, 0)): 6,\n    ((0, 1, 0), (0, 0, 0)): 5,\n    ((0, 0, 1), (0, 0, 0)): 4,\n    ((1, 1, 1), (1.0 / 3, 1.0 / 3, 1.0 / 3)): 3,\n    ((1, 1, 1), (2.0 / 3, 2.0 / 3, 2.0 / 3)): 2,\n    ((1, 1, 1), (1, 1, 1)): 1,\n}\n\n\ndef test_cutting():\n    ds = fake_sph_orientation_ds()\n    for (normal, center), answer in CUTTING_ANSWERS.items():\n        for i in range(-1, 2):\n            cen = [c + 0.1 * i for c in center]\n            cut = ds.cutting(normal, cen)\n            assert_equal(cut[\"gas\", \"density\"].shape[0], answer)\n\n\ndef test_chained_selection():\n    ds = fake_sph_orientation_ds()\n\n    for (center, radius), answer in SPHERE_ANSWERS.items():\n        sph = ds.sphere(center, radius)\n        region = ds.box(ds.domain_left_edge, ds.domain_right_edge, data_source=sph)\n        assert_equal(region[\"gas\", \"density\"].shape[0], answer)\n\n\ndef test_boolean_selection():\n    ds = fake_sph_orientation_ds()\n\n    sph = ds.sphere([0, 0, 0], 0.5)\n\n    sph2 = ds.sphere([1, 0, 0], 0.5)\n\n    reg = ds.all_data()\n\n    neg = reg - sph\n\n    assert_equal(neg[\"gas\", \"density\"].shape[0], 6)\n\n    plus = sph + sph2\n\n    assert_equal(plus[\"gas\", \"density\"].shape[0], 2)\n\n    intersect = sph & sph2\n\n    assert_equal(intersect[\"gas\", \"density\"].shape[0], 0)\n\n    intersect = reg & sph2\n\n    assert_equal(intersect[\"gas\", \"density\"].shape[0], 1)\n\n    exclusive = sph ^ sph2\n\n    assert_equal(exclusive[\"gas\", \"density\"].shape[0], 2)\n\n    exclusive = sph ^ reg\n\n    assert_equal(exclusive[\"gas\", \"density\"].shape[0], 6)\n\n    intersect = ds.intersection([sph, sph2])\n\n    assert_equal(intersect[\"gas\", \"density\"].shape[0], 0)\n\n    intersect = ds.intersection([reg, sph2])\n\n    assert_equal(intersect[\"gas\", \"density\"].shape[0], 1)\n\n    union = ds.union([sph, sph2])\n\n    assert_equal(union[\"gas\", \"density\"].shape[0], 2)\n\n    union = ds.union([sph, reg])\n\n    assert_equal(union[\"gas\", \"density\"].shape[0], 7)\n\n\ndef test_arbitrary_grid():\n    ds = fake_sph_grid_ds()\n\n    # this loads up some sph data in a test grid\n    agrid = ds.arbitrary_grid([0, 0, 0], [3, 3, 3], dims=[3, 3, 3])\n\n    # the field should be equal to the density of a particle in every voxel\n    # which is 1.\n    dens = agrid[\"gas\", \"density\"]\n    answers = np.ones(shape=(3, 3, 3))\n\n    assert_equal(dens, answers)\n\n\ndef test_compare_arbitrary_grid_slice():\n    ds = fake_sph_orientation_ds()\n    c = np.array([0.0, 0.0, 0.0])\n    width = 1.5\n    buff_size = 51\n    field = (\"gas\", \"density\")\n\n    # buffer from arbitrary grid\n    ag = ds.arbitrary_grid(c - width / 2, c + width / 2, [buff_size] * 3)\n    buff_ag = ag[field][:, :, int(np.floor(buff_size / 2))].d.T\n\n    # buffer from slice\n    p = SlicePlot(ds, \"z\", field, center=c, width=width)\n    p.set_buff_size(51)\n    buff_slc = p.frb.data[field].d\n\n    assert_equal(buff_slc, buff_ag)\n\n\ndef test_gather_slice():\n    ds = fake_sph_grid_ds()\n    ds.num_neighbors = 5\n    field = (\"gas\", \"density\")\n\n    c = np.array([1.5, 1.5, 0.5])\n    width = 3.0\n\n    p = SlicePlot(ds, \"z\", field, center=c, width=width)\n    p.set_buff_size(3)\n    buff_scatter = p.frb.data[field].d\n\n    ds.sph_smoothing_style = \"gather\"\n\n    p = SlicePlot(ds, \"z\", field, center=c, width=width)\n    p.set_buff_size(3)\n    buff_gather = p.frb.data[field].d\n\n    assert_allclose(buff_scatter, buff_gather, rtol=3e-16)\n\n\ndef test_gather_grid():\n    ds = fake_sph_grid_ds()\n    ds.num_neighbors = 5\n    field = (\"gas\", \"density\")\n\n    ag = ds.arbitrary_grid([0, 0, 0], [3, 3, 3], dims=[3, 3, 3])\n    scatter = ag[field]\n\n    ds.sph_smoothing_style = \"gather\"\n    ag = ds.arbitrary_grid([0, 0, 0], [3, 3, 3], dims=[3, 3, 3])\n    gather = ag[field]\n\n    assert_allclose(gather, scatter, rtol=3e-16)\n\n\ndef test_covering_grid_scatter():\n    ds = fake_sph_grid_ds()\n    field = (\"gas\", \"density\")\n    buff_size = 8\n\n    ag = ds.arbitrary_grid(0, 3, [buff_size] * 3)\n    ag_dens = ag[field].to(\"g*cm**-3\").d\n\n    cg = ds.covering_grid(3, 0, 8)\n    cg_dens = cg[field].to(\"g*cm**-3\").d\n\n    assert_equal(ag_dens, cg_dens)\n\n\ndef test_covering_grid_gather():\n    ds = fake_sph_grid_ds()\n    ds.sph_smoothing_style = \"gather\"\n    ds.num_neighbors = 5\n    field = (\"gas\", \"density\")\n    buff_size = 8\n\n    ag = ds.arbitrary_grid(0, 3, [buff_size] * 3)\n    ag_dens = ag[field].to(\"g*cm**-3\").d\n\n    cg = ds.covering_grid(3, 0, 8)\n    cg_dens = cg[field].to(\"g*cm**-3\").d\n\n    assert_equal(ag_dens, cg_dens)\n\n\n@requires_file(\"TNGHalo/halo_59.hdf5\")\ndef test_covering_grid_derived_fields():\n    def hot_gas(pfilter, data):\n        return data[pfilter.filtered_type, \"temperature\"] > 1.0e6\n\n    add_particle_filter(\n        \"hot_gas\",\n        function=hot_gas,\n        filtered_type=\"gas\",\n        requires=[\"temperature\"],\n    )\n    bbox = [[40669.34, 56669.34], [45984.04, 61984.04], [54114.9, 70114.9]]\n    ds = load(\"TNGHalo/halo_59.hdf5\", bounding_box=bbox)\n    ds.add_particle_filter(\"hot_gas\")\n    w = ds.quan(0.2, \"Mpc\")\n    le = ds.domain_center - 0.5 * w\n    re = ds.domain_center + 0.5 * w\n    g = ds.r[le[0] : re[0] : 128j, le[1] : re[1] : 128j, le[2] : re[2] : 128j]\n    T1 = g[\"gas\", \"temperature\"].to(\"keV\", \"thermal\")\n    T2 = g[\"gas\", \"kT\"]\n    assert_almost_equal(T1, T2)\n    T3 = g[\"hot_gas\", \"temperature\"].to(\"keV\", \"thermal\")\n    T4 = g[\"hot_gas\", \"kT\"]\n    assert_almost_equal(T3, T4)\n"
  },
  {
    "path": "yt/data_objects/tests/test_spheres.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_equal, assert_equal, assert_raises\n\nfrom yt.data_objects.profiles import create_profile\nfrom yt.testing import fake_random_ds, periodicity_cases, requires_module\nfrom yt.utilities.exceptions import YTException, YTFieldNotFound\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\n_fields_to_compare = (\n    \"spherical_radius\",\n    \"cylindrical_radius\",\n    \"spherical_theta\",\n    \"cylindrical_theta\",\n    \"spherical_phi\",\n    \"cylindrical_z\",\n)\n\n\ndef test_domain_sphere():\n    # Now we test that we can get different radial velocities based on field\n    # parameters.\n\n    # Get the first sphere\n    ds = fake_random_ds(\n        16,\n        fields=(\"density\", \"velocity_x\", \"velocity_y\", \"velocity_z\"),\n        units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n    )\n    sp0 = ds.sphere(ds.domain_center, 0.25)\n\n    # Compute the bulk velocity from the cells in this sphere\n    bulk_vel = sp0.quantities.bulk_velocity()\n\n    # Get the second sphere\n    sp1 = ds.sphere(ds.domain_center, 0.25)\n\n    # Set the bulk velocity field parameter\n    sp1.set_field_parameter(\"bulk_velocity\", bulk_vel)\n\n    assert_equal(\n        np.any(sp0[\"gas\", \"radial_velocity\"] == sp1[\"gas\", \"radial_velocity\"]),\n        False,\n    )\n\n    # Radial profile without correction\n    # Note we set n_bins = 8 here.\n\n    rp0 = create_profile(\n        sp0,\n        \"radius\",\n        \"radial_velocity\",\n        units={\"radius\": \"kpc\"},\n        logs={\"radius\": False},\n        n_bins=8,\n    )\n\n    # Radial profile with correction for bulk velocity\n\n    rp1 = create_profile(\n        sp1,\n        \"radius\",\n        \"radial_velocity\",\n        units={\"radius\": \"kpc\"},\n        logs={\"radius\": False},\n        n_bins=8,\n    )\n\n    assert_equal(rp0.x_bins, rp1.x_bins)\n    assert_equal(rp0.used, rp1.used)\n    assert_equal(rp0.used.sum() > rp0.used.size / 2.0, True)\n    assert_equal(\n        np.any(rp0[\"radial_velocity\"][rp0.used] == rp1[\"radial_velocity\"][rp1.used]),\n        False,\n    )\n\n    ref_sp = ds.sphere(\"c\", 0.25)\n    for f in _fields_to_compare:\n        ref_sp[f].sort()\n    for center in periodicity_cases(ds):\n        sp = ds.sphere(center, 0.25)\n        for f in _fields_to_compare:\n            sp[f].sort()\n            assert_equal(sp[f], ref_sp[f])\n\n\ndef test_sphere_center():\n    ds = fake_random_ds(\n        16,\n        nprocs=8,\n        fields=(\"density\", \"temperature\", \"velocity_x\"),\n        units=(\"g/cm**3\", \"K\", \"cm/s\"),\n    )\n\n    # Test if we obtain same center in different ways\n    sp1 = ds.sphere(\"max\", (0.25, \"unitary\"))\n    sp2 = ds.sphere(\"max_density\", (0.25, \"unitary\"))\n    assert_array_equal(sp1.center, sp2.center)\n\n    sp1 = ds.sphere(\"min\", (0.25, \"unitary\"))\n    sp2 = ds.sphere(\"min_density\", (0.25, \"unitary\"))\n    assert_array_equal(sp1.center, sp2.center)\n\n\ndef test_center_error():\n    ds = fake_random_ds(16, nprocs=16)\n\n    with assert_raises(YTFieldNotFound):\n        ds.sphere(\"min_non_existing_field_name\", (0.25, \"unitary\"))\n\n    with assert_raises(YTFieldNotFound):\n        ds.sphere(\"max_non_existing_field_name\", (0.25, \"unitary\"))\n\n\n@requires_module(\"miniball\")\ndef test_minimal_sphere():\n    ds = fake_random_ds(16, nprocs=8, particles=100)\n\n    pos = ds.r[\"all\", \"particle_position\"]\n    sp1 = ds.minimal_sphere(pos)\n\n    N0 = len(pos)\n\n    # Check all particles have been found\n    N1 = len(sp1[\"all\", \"particle_ones\"])\n    assert_equal(N0, N1)\n\n    # Check that any smaller sphere is missing some particles\n    sp2 = ds.sphere(sp1.center, sp1.radius * 0.9)\n    N2 = len(sp2[\"all\", \"particle_ones\"])\n    assert N2 < N0\n\n\n@requires_module(\"miniball\")\ndef test_minimal_sphere_bad_inputs():\n    ds = fake_random_ds(16, nprocs=8, particles=100)\n    pos = ds.r[\"all\", \"particle_position\"]\n\n    ## Check number of points >= 2\n    # -> should fail\n    assert_raises(YTException, ds.minimal_sphere, pos[:1, :])\n\n    # -> should not fail\n    ds.minimal_sphere(pos[:2, :])\n"
  },
  {
    "path": "yt/data_objects/tests/test_time_series.py",
    "content": "import tempfile\nfrom pathlib import Path\n\nimport pytest\n\nfrom yt.data_objects.static_output import Dataset\nfrom yt.data_objects.time_series import DatasetSeries\nfrom yt.utilities.exceptions import YTUnidentifiedDataType\nfrom yt.utilities.object_registries import output_type_registry\n\n\ndef test_pattern_expansion():\n    file_list = [f\"fake_data_file_{str(i).zfill(4)}\" for i in range(10)]\n\n    with tempfile.TemporaryDirectory() as tmpdir:\n        tmp_path = Path(tmpdir)\n        for file in file_list:\n            (tmp_path / file).touch()\n\n        pattern = tmp_path / \"fake_data_file_*\"\n        expected = [str(tmp_path / file) for file in file_list]\n        found = DatasetSeries._get_filenames_from_glob_pattern(pattern)\n        assert found == expected\n\n        found2 = DatasetSeries._get_filenames_from_glob_pattern(Path(pattern))\n        assert found2 == expected\n\n\ndef test_no_match_pattern():\n    with tempfile.TemporaryDirectory() as tmpdir:\n        pattern = Path(tmpdir).joinpath(\"fake_data_file_*\")\n        with pytest.raises(FileNotFoundError):\n            DatasetSeries._get_filenames_from_glob_pattern(pattern)\n\n\n@pytest.fixture\ndef FakeDataset():\n    class _FakeDataset(Dataset):\n        \"\"\"A minimal loadable fake dataset subclass\"\"\"\n\n        def __init__(self, *args, **kwargs):\n            super().__init__(*args, **kwargs)\n\n        @classmethod\n        def _is_valid(cls, *args, **kwargs):\n            return True\n\n        def _parse_parameter_file(self):\n            return\n\n        def _set_code_unit_attributes(self):\n            return\n\n        def set_code_units(self):\n            i = int(Path(self.filename).name.split(\"_\")[-1])\n            self.current_time = i\n            self.current_redshift = 1 / (i + 1)\n            return\n\n        def _hash(self):\n            return\n\n        def _setup_classes(self):\n            return\n\n    try:\n        yield _FakeDataset\n    finally:\n        output_type_registry.pop(\"_FakeDataset\")\n\n\n@pytest.fixture\ndef fake_datasets():\n    file_list = [f\"fake_data_file_{i:04d}\" for i in range(10)]\n    with tempfile.TemporaryDirectory() as tmpdir:\n        pfile_list = [Path(tmpdir) / file for file in file_list]\n        sfile_list = [str(file) for file in pfile_list]\n        for file in pfile_list:\n            file.touch()\n        pattern = Path(tmpdir) / \"fake_data_file_*\"\n\n        yield file_list, pfile_list, sfile_list, pattern\n\n\ndef test_init_fake_dataseries(fake_datasets):\n    file_list, pfile_list, sfile_list, pattern = fake_datasets\n\n    # init from str pattern\n    ts = DatasetSeries(pattern)\n    assert ts._pre_outputs == sfile_list\n\n    # init from Path pattern\n    ppattern = Path(pattern)\n    ts = DatasetSeries(ppattern)\n    assert ts._pre_outputs == sfile_list\n\n    # init form str list\n    ts = DatasetSeries(sfile_list)\n    assert ts._pre_outputs == sfile_list\n\n    # init form Path list\n    ts = DatasetSeries(pfile_list)\n    assert ts._pre_outputs == pfile_list\n\n    # rejected input type (str repr of a list) \"[file1, file2, ...]\"\n    with pytest.raises(FileNotFoundError):\n        DatasetSeries(str(file_list))\n\n    # finally, check that ts[0] fails to actually load\n    with pytest.raises(YTUnidentifiedDataType):\n        ts[0]\n\n\ndef test_init_fake_dataseries2(FakeDataset, fake_datasets):\n    _file_list, _pfile_list, _sfile_list, pattern = fake_datasets\n    ds = DatasetSeries(pattern)[0]\n    assert isinstance(ds, FakeDataset)\n\n    ts = DatasetSeries(pattern, my_unsupported_kwarg=None)\n\n    with pytest.raises(TypeError):\n        ts[0]\n\n\ndef test_get_by_key(FakeDataset, fake_datasets):\n    _file_list, _pfile_list, sfile_list, pattern = fake_datasets\n    ts = DatasetSeries(pattern)\n\n    Ntot = len(sfile_list)\n\n    t = ts[0].quan(1, \"code_time\")\n\n    assert sfile_list[0] == ts.get_by_time(-t).filename\n    assert sfile_list[0] == ts.get_by_time(t - t).filename\n    assert sfile_list[1] == ts.get_by_time((0.8, \"code_time\")).filename\n    assert sfile_list[1] == ts.get_by_time((1.2, \"code_time\")).filename\n    assert sfile_list[Ntot - 1] == ts.get_by_time(t * (Ntot - 1)).filename\n    assert sfile_list[Ntot - 1] == ts.get_by_time(t * Ntot).filename\n\n    with pytest.raises(ValueError):\n        ts.get_by_time(-2 * t, tolerance=0.1 * t)\n    with pytest.raises(ValueError):\n        ts.get_by_time(1000 * t, tolerance=0.1 * t)\n\n    assert sfile_list[1] == ts.get_by_redshift(1 / 2.2).filename\n    assert sfile_list[1] == ts.get_by_redshift(1 / 2).filename\n    assert sfile_list[1] == ts.get_by_redshift(1 / 1.6).filename\n\n    with pytest.raises(ValueError):\n        ts.get_by_redshift(1000, tolerance=0.1)\n\n    zmid = (ts[0].current_redshift + ts[1].current_redshift) / 2\n\n    assert sfile_list[1] == ts.get_by_redshift(zmid, prefer=\"smaller\").filename\n    assert sfile_list[0] == ts.get_by_redshift(zmid, prefer=\"larger\").filename\n"
  },
  {
    "path": "yt/data_objects/tests/test_units_override.py",
    "content": "from functools import partial\n\nfrom numpy.testing import assert_raises\n\nfrom yt.data_objects.static_output import Dataset\nfrom yt.units import YTQuantity\nfrom yt.units.unit_registry import UnitRegistry\n\nmock_quan = partial(YTQuantity, registry=UnitRegistry())\n\n\ndef test_schema_validation():\n    valid_schemas = [\n        {\"length_unit\": 1.0},\n        {\"length_unit\": [1.0]},\n        {\"length_unit\": (1.0,)},\n        {\"length_unit\": int(1.0)},\n        {\"length_unit\": (1.0, \"m\")},\n        {\"length_unit\": [1.0, \"m\"]},\n        {\"length_unit\": YTQuantity(1.0, \"m\")},\n    ]\n\n    for schema in valid_schemas:\n        uo = Dataset._sanitize_units_override(schema)\n        for v in uo.values():\n            q = mock_quan(v)  # check that no error (TypeError) is raised\n            q.to(\"pc\")  # check that q is a length\n\n\ndef test_invalid_schema_detection():\n    invalid_key_schemas = [\n        {\"len_unit\": 1.0},  # plain invalid key\n        {\"lenght_unit\": 1.0},  # typo\n    ]\n    for invalid_schema in invalid_key_schemas:\n        assert_raises(ValueError, Dataset._sanitize_units_override, invalid_schema)\n\n    invalid_val_schemas = [\n        {\"length_unit\": [1, 1, 1]},  # len(val) > 2\n        {\"length_unit\": [1, 1, 1, 1, 1]},  # \"data type not understood\" in unyt\n    ]\n\n    for invalid_schema in invalid_val_schemas:\n        assert_raises(TypeError, Dataset._sanitize_units_override, invalid_schema)\n\n    # 0 shouldn't make sense\n    invalid_number_schemas = [\n        {\"length_unit\": 0},\n        {\"length_unit\": [0]},\n        {\"length_unit\": (0,)},\n        {\"length_unit\": (0, \"cm\")},\n    ]\n    for invalid_schema in invalid_number_schemas:\n        assert_raises(ValueError, Dataset._sanitize_units_override, invalid_schema)\n\n\ndef test_typing_error_detection():\n    invalid_schema = {\"length_unit\": \"1m\"}\n\n    # this is the error that is raised by unyt on bad input\n    assert_raises(RuntimeError, mock_quan, invalid_schema[\"length_unit\"])\n\n    # check that the sanitizer function is able to catch the\n    # type issue before passing down to unyt\n    assert_raises(TypeError, Dataset._sanitize_units_override, invalid_schema)\n\n\ndef test_dimensionality_error_detection():\n    invalid_schema = {\"length_unit\": YTQuantity(1.0, \"s\")}\n    assert_raises(ValueError, Dataset._sanitize_units_override, invalid_schema)\n"
  },
  {
    "path": "yt/data_objects/time_series.py",
    "content": "import functools\nimport glob\nimport inspect\nimport os\nimport weakref\nfrom abc import ABC, abstractmethod\nfrom functools import wraps\nfrom typing import TYPE_CHECKING, Literal\n\nimport numpy as np\nfrom more_itertools import always_iterable\nfrom unyt import Unit, unyt_quantity\n\nfrom yt.config import ytcfg\nfrom yt.data_objects.analyzer_objects import AnalysisTask, create_quantity_proxy\nfrom yt.data_objects.particle_trajectories import ParticleTrajectories\nfrom yt.funcs import is_sequence, mylog\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.exceptions import YTException\nfrom yt.utilities.object_registries import (\n    analysis_task_registry,\n    data_object_registry,\n    derived_quantity_registry,\n    simulation_time_series_registry,\n)\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    communication_system,\n    parallel_objects,\n    parallel_root_only,\n)\n\nif TYPE_CHECKING:\n    from yt.data_objects.static_output import Dataset\n\n\nclass AnalysisTaskProxy:\n    def __init__(self, time_series):\n        self.time_series = time_series\n\n    def __getitem__(self, key):\n        task_cls = analysis_task_registry[key]\n\n        @wraps(task_cls.__init__)\n        def func(*args, **kwargs):\n            task = task_cls(*args, **kwargs)\n            return self.time_series.eval(task)\n\n        return func\n\n    def keys(self):\n        return analysis_task_registry.keys()\n\n    def __contains__(self, key):\n        return key in analysis_task_registry\n\n\ndef get_ds_prop(propname):\n    def _eval(params, ds):\n        return getattr(ds, propname)\n\n    cls = type(propname, (AnalysisTask,), {\"eval\": _eval, \"_params\": ()})\n    return cls\n\n\nattrs = (\n    \"refine_by\",\n    \"dimensionality\",\n    \"current_time\",\n    \"domain_dimensions\",\n    \"domain_left_edge\",\n    \"domain_right_edge\",\n    \"unique_identifier\",\n    \"current_redshift\",\n    \"cosmological_simulation\",\n    \"omega_matter\",\n    \"omega_lambda\",\n    \"omega_radiation\",\n    \"hubble_constant\",\n)\n\n\nclass TimeSeriesParametersContainer:\n    def __init__(self, data_object):\n        self.data_object = data_object\n\n    def __getattr__(self, attr):\n        if attr in attrs:\n            return self.data_object.eval(get_ds_prop(attr)())\n        raise AttributeError(attr)\n\n\nclass DatasetSeries:\n    r\"\"\"The DatasetSeries object is a container of multiple datasets,\n    allowing easy iteration and computation on them.\n\n    DatasetSeries objects are designed to provide easy ways to access,\n    analyze, parallelize and visualize multiple datasets sequentially.  This is\n    primarily expressed through iteration, but can also be constructed via\n    analysis tasks (see :ref:`time-series-analysis`).\n\n    Note that contained datasets are lazily loaded and weakly referenced. This means\n    that in order to perform follow-up operations on data it's best to define handles on\n    these datasets during iteration.\n\n    Parameters\n    ----------\n    outputs : list of filenames, or pattern\n        A list of filenames, for instance [\"DD0001/DD0001\", \"DD0002/DD0002\"],\n        or a glob pattern (i.e. containing wildcards '[]?!*') such as \"DD*/DD*.index\".\n        In the latter case, results are sorted automatically.\n        Filenames and patterns can be of type str, os.Pathlike or bytes.\n    parallel : True, False or int\n        This parameter governs the behavior when .piter() is called on the\n        resultant DatasetSeries object.  If this is set to False, the time\n        series will not iterate in parallel when .piter() is called.  If\n        this is set to either True, one processor will be allocated for\n        each iteration of the loop. If this is set to an integer, the loop\n        will be parallelized over this many workgroups. It the integer\n        value is less than the total number of available processors,\n        more than one processor will be allocated to a given loop iteration,\n        causing the functionality within the loop to be run in parallel.\n    setup_function : callable, accepts a ds\n        This function will be called whenever a dataset is loaded.\n    mixed_dataset_types : True or False, default False\n        Set to True if the DatasetSeries will load different dataset types, set\n        to False if loading dataset of a single type as this will result in a\n        considerable speed up from not having to figure out the dataset type.\n\n    Examples\n    --------\n\n    >>> ts = DatasetSeries(\n    ...     \"GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0[0-6][0-9]0\"\n    ... )\n    >>> for ds in ts:\n    ...     SlicePlot(ds, \"x\", (\"gas\", \"density\")).save()\n    ...\n    >>> def print_time(ds):\n    ...     print(ds.current_time)\n    ...\n    >>> ts = DatasetSeries(\n    ...     \"GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0[0-6][0-9]0\",\n    ...     setup_function=print_time,\n    ... )\n    ...\n    >>> for ds in ts:\n    ...     SlicePlot(ds, \"x\", (\"gas\", \"density\")).save()\n\n    \"\"\"\n\n    _dataset_cls: type[\"Dataset\"] | None = None\n\n    def __init_subclass__(cls, *args, **kwargs):\n        super().__init_subclass__(*args, **kwargs)\n        code_name = cls.__name__[: cls.__name__.find(\"Simulation\")]\n        if code_name:\n            simulation_time_series_registry[code_name] = cls\n            mylog.debug(\"Registering simulation: %s as %s\", code_name, cls)\n\n    def __new__(cls, outputs, *args, **kwargs):\n        try:\n            outputs = cls._get_filenames_from_glob_pattern(outputs)\n        except TypeError:\n            pass\n        ret = super().__new__(cls)\n        ret._pre_outputs = outputs[:]\n        ret.kwargs = {}\n        return ret\n\n    def __init__(\n        self,\n        outputs,\n        parallel=True,\n        setup_function=None,\n        mixed_dataset_types=False,\n        **kwargs,\n    ):\n        # This is needed to properly set _pre_outputs for Simulation subclasses.\n        self._mixed_dataset_types = mixed_dataset_types\n        if is_sequence(outputs) and not isinstance(outputs, str):\n            self._pre_outputs = outputs[:]\n        self.tasks = AnalysisTaskProxy(self)\n        self.params = TimeSeriesParametersContainer(self)\n        if setup_function is None:\n\n            def _null(x):\n                return None\n\n            setup_function = _null\n        self._setup_function = setup_function\n        for type_name in data_object_registry:\n            setattr(\n                self, type_name, functools.partial(DatasetSeriesObject, self, type_name)\n            )\n        self.parallel = parallel\n        self.kwargs = kwargs\n\n    @staticmethod\n    def _get_filenames_from_glob_pattern(outputs):\n        \"\"\"\n        Helper function to DatasetSeries.__new__\n        handle a special case where \"outputs\" is assumed to be really a pattern string\n        \"\"\"\n        pattern = outputs\n        epattern = os.path.expanduser(pattern)\n        data_dir = ytcfg.get(\"yt\", \"test_data_dir\")\n        # if no match if found from the current work dir,\n        # we try to match the pattern from the test data dir\n        file_list = glob.glob(epattern) or glob.glob(os.path.join(data_dir, epattern))\n        if not file_list:\n            raise FileNotFoundError(f\"No match found for pattern : {pattern}\")\n        return sorted(file_list)\n\n    def __getitem__(self, key):\n        if isinstance(key, slice):\n            if isinstance(key.start, float):\n                return self.get_range(key.start, key.stop)\n            # This will return a sliced up object!\n            return DatasetSeries(\n                self._pre_outputs[key], parallel=self.parallel, **self.kwargs\n            )\n        o = self._pre_outputs[key]\n        if isinstance(o, (str, os.PathLike)):\n            o = self._load(o, **self.kwargs)\n            self._setup_function(o)\n        return o\n\n    def __len__(self):\n        return len(self._pre_outputs)\n\n    @property\n    def outputs(self):\n        return self._pre_outputs\n\n    def piter(self, storage=None, dynamic=False):\n        r\"\"\"Iterate over time series components in parallel.\n\n        This allows you to iterate over a time series while dispatching\n        individual components of that time series to different processors or\n        processor groups.  If the parallelism strategy was set to be\n        multi-processor (by \"parallel = N\" where N is an integer when the\n        DatasetSeries was created) this will issue each dataset to an\n        N-processor group.  For instance, this would allow you to start a 1024\n        processor job, loading up 100 datasets in a time series and creating 8\n        processor groups of 128 processors each, each of which would be\n        assigned a different dataset.  This could be accomplished as shown in\n        the examples below.  The *storage* option is as seen in\n        :func:`~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_objects`\n        which is a mechanism for storing results of analysis on an individual\n        dataset and then combining the results at the end, so that the entire\n        set of processors have access to those results.\n\n        Note that supplying a *store* changes the iteration mechanism; see\n        below.\n\n        Parameters\n        ----------\n        storage : dict\n            This is a dictionary, which will be filled with results during the\n            course of the iteration.  The keys will be the dataset\n            indices and the values will be whatever is assigned to the *result*\n            attribute on the storage during iteration.\n        dynamic : boolean\n            This governs whether or not dynamic load balancing will be\n            enabled.  This requires one dedicated processor; if this\n            is enabled with a set of 128 processors available, only\n            127 will be available to iterate over objects as one will\n            be load balancing the rest.\n\n\n        Examples\n        --------\n        Here is an example of iteration when the results do not need to be\n        stored.  One processor will be assigned to each dataset.\n\n        >>> ts = DatasetSeries(\"DD*/DD*.index\")\n        >>> for ds in ts.piter():\n        ...     SlicePlot(ds, \"x\", (\"gas\", \"density\")).save()\n        ...\n\n        This demonstrates how one might store results:\n\n        >>> def print_time(ds):\n        ...     print(ds.current_time)\n        ...\n        >>> ts = DatasetSeries(\"DD*/DD*.index\", setup_function=print_time)\n        ...\n        >>> my_storage = {}\n        >>> for sto, ds in ts.piter(storage=my_storage):\n        ...     v, c = ds.find_max((\"gas\", \"density\"))\n        ...     sto.result = (v, c)\n        ...\n        >>> for i, (v, c) in sorted(my_storage.items()):\n        ...     print(\"% 4i  %0.3e\" % (i, v))\n        ...\n\n        This shows how to dispatch 4 processors to each dataset:\n\n        >>> ts = DatasetSeries(\"DD*/DD*.index\", parallel=4)\n        >>> for ds in ts.piter():\n        ...     ProjectionPlot(ds, \"x\", (\"gas\", \"density\")).save()\n        ...\n\n        \"\"\"\n        if not self.parallel:\n            njobs = 1\n        elif not dynamic:\n            if self.parallel:\n                njobs = -1\n            else:\n                njobs = self.parallel\n        else:\n            my_communicator = communication_system.communicators[-1]\n            nsize = my_communicator.size\n            if nsize == 1:\n                self.parallel = False\n                dynamic = False\n                njobs = 1\n            else:\n                njobs = nsize - 1\n\n        for output in parallel_objects(\n            self._pre_outputs, njobs=njobs, storage=storage, dynamic=dynamic\n        ):\n            if storage is not None:\n                sto, output = output\n\n            if isinstance(output, str):\n                ds = self._load(output, **self.kwargs)\n                self._setup_function(ds)\n            else:\n                ds = output\n\n            if storage is not None:\n                next_ret = (sto, ds)\n            else:\n                next_ret = ds\n\n            yield next_ret\n\n    def eval(self, tasks, obj=None):\n        return_values = {}\n        for store, ds in self.piter(return_values):\n            store.result = []\n            for task in always_iterable(tasks):\n                try:\n                    style = inspect.getargspec(task.eval)[0][1]\n                    if style == \"ds\":\n                        arg = ds\n                    elif style == \"data_object\":\n                        if obj is None:\n                            obj = DatasetSeriesObject(self, \"all_data\")\n                        arg = obj.get(ds)\n                    rv = task.eval(arg)\n                # We catch and store YT-originating exceptions\n                # This fixes the standard problem of having a sphere that's too\n                # small.\n                except YTException:\n                    pass\n                store.result.append(rv)\n        return [v for k, v in sorted(return_values.items())]\n\n    @classmethod\n    def from_output_log(cls, output_log, line_prefix=\"DATASET WRITTEN\", parallel=True):\n        filenames = []\n        for line in open(output_log):\n            if not line.startswith(line_prefix):\n                continue\n            cut_line = line[len(line_prefix) :].strip()\n            fn = cut_line.split()[0]\n            filenames.append(fn)\n        obj = cls(filenames, parallel=parallel)\n        return obj\n\n    def _load(self, output_fn, *, hint: str | None = None, **kwargs):\n        from yt.loaders import load\n\n        if self._dataset_cls is not None:\n            return self._dataset_cls(output_fn, **kwargs)\n        elif self._mixed_dataset_types:\n            return load(output_fn, hint=hint, **kwargs)\n        ds = load(output_fn, hint=hint, **kwargs)\n        self._dataset_cls = ds.__class__\n        return ds\n\n    def particle_trajectories(\n        self, indices, fields=None, suppress_logging=False, ptype=None\n    ):\n        r\"\"\"Create a collection of particle trajectories in time over a series of\n        datasets.\n\n        Parameters\n        ----------\n        indices : array_like\n            An integer array of particle indices whose trajectories we\n            want to track. If they are not sorted they will be sorted.\n        fields : list of strings, optional\n            A set of fields that is retrieved when the trajectory\n            collection is instantiated. Default: None (will default\n            to the fields 'particle_position_x', 'particle_position_y',\n            'particle_position_z')\n        suppress_logging : boolean\n            Suppress yt's logging when iterating over the simulation time\n            series. Default: False\n        ptype : str, optional\n            Only use this particle type. Default: None, which uses all particle type.\n\n        Examples\n        --------\n        >>> my_fns = glob.glob(\"orbit_hdf5_chk_00[0-9][0-9]\")\n        >>> my_fns.sort()\n        >>> fields = [\n        ...     (\"all\", \"particle_position_x\"),\n        ...     (\"all\", \"particle_position_y\"),\n        ...     (\"all\", \"particle_position_z\"),\n        ...     (\"all\", \"particle_velocity_x\"),\n        ...     (\"all\", \"particle_velocity_y\"),\n        ...     (\"all\", \"particle_velocity_z\"),\n        ... ]\n        >>> ds = load(my_fns[0])\n        >>> init_sphere = ds.sphere(ds.domain_center, (0.5, \"unitary\"))\n        >>> indices = init_sphere[\"all\", \"particle_index\"].astype(\"int64\")\n        >>> ts = DatasetSeries(my_fns)\n        >>> trajs = ts.particle_trajectories(indices, fields=fields)\n        >>> for t in trajs:\n        ...     print(\n        ...         t[\"all\", \"particle_velocity_x\"].max(),\n        ...         t[\"all\", \"particle_velocity_x\"].min(),\n        ...     )\n\n        Notes\n        -----\n        This function will fail if there are duplicate particle ids or if some of the\n        particle disappear.\n        \"\"\"\n        return ParticleTrajectories(\n            self, indices, fields=fields, suppress_logging=suppress_logging, ptype=ptype\n        )\n\n    def _get_by_attribute(\n        self,\n        attribute: str,\n        value: unyt_quantity | tuple[float, Unit | str],\n        tolerance: None | unyt_quantity | tuple[float, Unit | str] = None,\n        prefer: Literal[\"nearest\", \"smaller\", \"larger\"] = \"nearest\",\n    ) -> \"Dataset\":\n        r\"\"\"\n        Get a dataset at or near to a given value.\n\n        Parameters\n        ----------\n        attribute : str\n            The key by which to retrieve an output, usually 'current_time' or\n            'current_redshift'. The key must be an attribute of the dataset\n            and monotonic.\n        value : unyt_quantity or (value, unit)\n            The value to search for.\n        tolerance : unyt_quantity or (value, unit), optional\n            If not None, do not return a dataset unless the value is\n            within the tolerance value. If None, simply return the\n            nearest dataset.\n            Default: None.\n        prefer : str\n            The side of the value to return. Can be 'nearest', 'smaller' or 'larger'.\n            Default: 'nearest'.\n        \"\"\"\n\n        if prefer not in (\"nearest\", \"smaller\", \"larger\"):\n            raise ValueError(\n                f\"Side must be 'nearest', 'smaller' or 'larger', got {prefer}.\"\n            )\n\n        # Use a binary search to find the closest value\n        iL = 0\n        iR = len(self._pre_outputs) - 1\n\n        if iL == iR:\n            ds = self[0]\n            if (\n                tolerance is not None\n                and abs(getattr(ds, attribute) - value) > tolerance\n            ):\n                raise ValueError(\n                    f\"No dataset found with {attribute} within {tolerance} of {value}.\"\n                )\n            return ds\n\n        # Check signedness\n        dsL = self[iL]\n        dsR = self[iR]\n        vL = getattr(dsL, attribute)\n        vR = getattr(dsR, attribute)\n\n        if vL < vR:\n            sign = 1\n        elif vL > vR:\n            sign = -1\n        else:\n            raise ValueError(\n                f\"{dsL} and {dsR} have both {attribute}={vL}, cannot perform search.\"\n            )\n\n        if isinstance(value, tuple):\n            value = dsL.quan(*value)\n        if isinstance(tolerance, tuple):\n            tolerance = dsL.quan(*tolerance)\n\n        # Short-circuit if value is out-of-range\n        if not (vL * sign < value * sign < vR * sign):\n            iL = iR = 0\n\n        while iR - iL > 1:\n            iM = (iR + iL) // 2\n            dsM = self[iM]\n            vM = getattr(dsM, attribute)\n\n            if sign * value < sign * vM:\n                iR = iM\n                dsR = dsM\n            elif sign * value > sign * vM:\n                iL = iM\n                dsL = dsM\n            else:  # Exact match\n                dsL = dsR = dsM\n                break\n\n        if prefer == \"smaller\":\n            ds_best = dsL if sign > 0 else dsR\n        elif prefer == \"larger\":\n            ds_best = dsR if sign > 0 else dsL\n        elif abs(value - getattr(dsL, attribute)) < abs(\n            value - getattr(dsR, attribute)\n        ):\n            ds_best = dsL\n        else:\n            ds_best = dsR\n\n        if tolerance is not None:\n            if abs(value - getattr(ds_best, attribute)) > tolerance:\n                raise ValueError(\n                    f\"No dataset found with {attribute} within {tolerance} of {value}.\"\n                )\n        return ds_best\n\n    def get_by_time(\n        self,\n        time: unyt_quantity | tuple[float, Unit | str],\n        tolerance: None | unyt_quantity | tuple[float, Unit | str] = None,\n        prefer: Literal[\"nearest\", \"smaller\", \"larger\"] = \"nearest\",\n    ) -> \"Dataset\":\n        \"\"\"\n        Get a dataset at or near to a given time.\n\n        Parameters\n        ----------\n        time : unyt_quantity or (value, unit)\n            The time to search for.\n        tolerance : unyt_quantity or (value, unit)\n            If not None, do not return a dataset unless the time is\n            within the tolerance value. If None, simply return the\n            nearest dataset.\n            Default: None.\n        prefer : str\n            The side of the value to return. Can be 'nearest', 'smaller' or 'larger'.\n            Default: 'nearest'.\n\n        Examples\n        --------\n        >>> ds = ts.get_by_time((12, \"Gyr\"))\n        >>> t = ts[0].quan(12, \"Gyr\")\n        ... ds = ts.get_by_time(t, tolerance=(100, \"Myr\"))\n        \"\"\"\n        return self._get_by_attribute(\n            \"current_time\", time, tolerance=tolerance, prefer=prefer\n        )\n\n    def get_by_redshift(\n        self,\n        redshift: float,\n        tolerance: float | None = None,\n        prefer: Literal[\"nearest\", \"smaller\", \"larger\"] = \"nearest\",\n    ) -> \"Dataset\":\n        \"\"\"\n        Get a dataset at or near to a given time.\n\n        Parameters\n        ----------\n        redshift : float\n            The redshift to search for.\n        tolerance : float\n            If not None, do not return a dataset unless the redshift is\n            within the tolerance value. If None, simply return the\n            nearest dataset.\n            Default: None.\n        prefer : str\n            The side of the value to return. Can be 'nearest', 'smaller' or 'larger'.\n            Default: 'nearest'.\n\n        Examples\n        --------\n        >>> ds = ts.get_by_redshift(0.0)\n        \"\"\"\n        return self._get_by_attribute(\n            \"current_redshift\", redshift, tolerance=tolerance, prefer=prefer\n        )\n\n\nclass TimeSeriesQuantitiesContainer:\n    def __init__(self, data_object, quantities):\n        self.data_object = data_object\n        self.quantities = quantities\n\n    def __getitem__(self, key):\n        if key not in self.quantities:\n            raise KeyError(key)\n        q = self.quantities[key]\n\n        def run_quantity_wrapper(quantity, quantity_name):\n            @wraps(derived_quantity_registry[quantity_name][1])\n            def run_quantity(*args, **kwargs):\n                to_run = quantity(*args, **kwargs)\n                return self.data_object.eval(to_run)\n\n            return run_quantity\n\n        return run_quantity_wrapper(q, key)\n\n\nclass DatasetSeriesObject:\n    def __init__(self, time_series, data_object_name, *args, **kwargs):\n        self.time_series = weakref.proxy(time_series)\n        self.data_object_name = data_object_name\n        self._args = args\n        self._kwargs = kwargs\n        qs = {\n            qn: create_quantity_proxy(qv)\n            for qn, qv in derived_quantity_registry.items()\n        }\n        self.quantities = TimeSeriesQuantitiesContainer(self, qs)\n\n    def eval(self, tasks):\n        return self.time_series.eval(tasks, self)\n\n    def get(self, ds):\n        # We get the type name, which corresponds to an attribute of the\n        # index\n        cls = getattr(ds, self.data_object_name)\n        return cls(*self._args, **self._kwargs)\n\n\nclass SimulationTimeSeries(DatasetSeries, ABC):\n    def __init__(self, parameter_filename, find_outputs=False):\n        \"\"\"\n        Base class for generating simulation time series types.\n        Principally consists of a *parameter_filename*.\n        \"\"\"\n\n        if not os.path.exists(parameter_filename):\n            raise FileNotFoundError(parameter_filename)\n        self.parameter_filename = parameter_filename\n        self.basename = os.path.basename(parameter_filename)\n        self.directory = os.path.dirname(parameter_filename)\n        self.parameters = {}\n        self.key_parameters = []\n\n        # Set some parameter defaults.\n        self._set_parameter_defaults()\n        # Read the simulation dataset.\n        self._parse_parameter_file()\n        # Set units\n        self._set_units()\n        # Figure out the starting and stopping times and redshift.\n        self._calculate_simulation_bounds()\n        # Get all possible datasets.\n        self._get_all_outputs(find_outputs=find_outputs)\n\n        self.print_key_parameters()\n\n    def _set_parameter_defaults(self):  # noqa: B027\n        pass\n\n    @abstractmethod\n    def _parse_parameter_file(self):\n        pass\n\n    @abstractmethod\n    def _set_units(self):\n        pass\n\n    @abstractmethod\n    def _calculate_simulation_bounds(self):\n        pass\n\n    @abstractmethod\n    def _get_all_outputs(self, *, find_outputs=False):\n        pass\n\n    def __repr__(self):\n        return self.parameter_filename\n\n    _arr = None\n\n    @property\n    def arr(self):\n        if self._arr is not None:\n            return self._arr\n        self._arr = functools.partial(YTArray, registry=self.unit_registry)\n        return self._arr\n\n    _quan = None\n\n    @property\n    def quan(self):\n        if self._quan is not None:\n            return self._quan\n        self._quan = functools.partial(YTQuantity, registry=self.unit_registry)\n        return self._quan\n\n    @parallel_root_only\n    def print_key_parameters(self):\n        \"\"\"\n        Print out some key parameters for the simulation.\n        \"\"\"\n        if self.simulation_type == \"grid\":\n            for a in [\"domain_dimensions\", \"domain_left_edge\", \"domain_right_edge\"]:\n                self._print_attr(a)\n        for a in [\"initial_time\", \"final_time\", \"cosmological_simulation\"]:\n            self._print_attr(a)\n        if getattr(self, \"cosmological_simulation\", False):\n            for a in [\n                \"box_size\",\n                \"omega_matter\",\n                \"omega_lambda\",\n                \"omega_radiation\",\n                \"hubble_constant\",\n                \"initial_redshift\",\n                \"final_redshift\",\n            ]:\n                self._print_attr(a)\n        for a in self.key_parameters:\n            self._print_attr(a)\n        mylog.info(\"Total datasets: %d.\", len(self.all_outputs))\n\n    def _print_attr(self, a):\n        \"\"\"\n        Print the attribute or warn about it missing.\n        \"\"\"\n        if not hasattr(self, a):\n            mylog.error(\"Missing %s in dataset definition!\", a)\n            return\n        v = getattr(self, a)\n        mylog.info(\"Parameters: %-25s = %s\", a, v)\n\n    def _get_outputs_by_key(self, key, values, tolerance=None, outputs=None):\n        r\"\"\"\n        Get datasets at or near to given values.\n\n        Parameters\n        ----------\n        key : str\n            The key by which to retrieve outputs, usually 'time' or\n            'redshift'.\n        values : array_like\n            A list of values, given as floats.\n        tolerance : float\n            If not None, do not return a dataset unless the value is\n            within the tolerance value.  If None, simply return the\n            nearest dataset.\n            Default: None.\n        outputs : list\n            The list of outputs from which to choose.  If None,\n            self.all_outputs is used.\n            Default: None.\n\n        Examples\n        --------\n        >>> datasets = es.get_outputs_by_key(\"redshift\", [0, 1, 2], tolerance=0.1)\n\n        \"\"\"\n\n        if not isinstance(values, YTArray):\n            if isinstance(values, tuple) and len(values) == 2:\n                values = self.arr(*values)\n            else:\n                values = self.arr(values)\n        values = values.in_base()\n\n        if outputs is None:\n            outputs = self.all_outputs\n        my_outputs = []\n        if not outputs:\n            return my_outputs\n        for value in values:\n            outputs.sort(key=lambda obj, value=value: np.abs(value - obj[key]))\n            if (\n                tolerance is None or np.abs(value - outputs[0][key]) <= tolerance\n            ) and outputs[0] not in my_outputs:\n                my_outputs.append(outputs[0])\n            else:\n                mylog.error(\"No dataset added for %s = %f.\", key, value)\n\n        outputs.sort(key=lambda obj: obj[\"time\"])\n        return my_outputs\n"
  },
  {
    "path": "yt/data_objects/unions.py",
    "content": "from abc import ABC, abstractmethod\n\nfrom more_itertools import always_iterable\n\n\nclass Union(ABC):\n    @property\n    @abstractmethod\n    def _union_type(self) -> str: ...\n\n    def __init__(self, name, sub_types):\n        self.name = name\n        self.sub_types = list(always_iterable(sub_types))\n\n    def __iter__(self):\n        yield from self.sub_types\n\n    def __repr__(self):\n        return f\"{self._union_type.capitalize()} Union: '{self.name}' composed of: {self.sub_types}\"\n\n\nclass MeshUnion(Union):\n    _union_type = \"mesh\"\n\n\nclass ParticleUnion(Union):\n    _union_type = \"particle\"\n"
  },
  {
    "path": "yt/default.mplstyle",
    "content": "# basic usage\n# >>> import matplotlib as mpl\n# >>> mpl.style.use(\"yt.default\")\n\nxtick.top: True\nytick.right: True\nxtick.minor.visible: True\nytick.minor.visible: True\nxtick.direction: in\nytick.direction: in\n\nfont.family: stixgeneral\n\nmathtext.fontset: cm\n"
  },
  {
    "path": "yt/extensions/__init__.py",
    "content": "\"\"\"\n    yt.extensions\n    ~~~~~~~~~~~~~\n\n    Redirect imports for extensions.  This module basically makes it possible\n    for us to transition from ytext.foo to ytext_foo without having to\n    force all extensions to upgrade at the same time.\n\n    When a user does ``from yt.extensions.foo import bar`` it will attempt to\n    import ``from yt_foo import bar`` first and when that fails it will\n    try to import ``from ytext.foo import bar``.\n\n    We're switching from namespace packages because it was just too painful for\n    everybody involved.\n\n    :copyright: (c) 2015 by Armin Ronacher.\n    :license: BSD, see LICENSE for more details.\n\"\"\"\n\n# This source code is originally from flask, in the flask/ext/__init__.py file.\n\n\ndef setup():\n    from ..exthook import ExtensionImporter\n\n    importer = ExtensionImporter([\"yt_%s\", \"ytext.%s\"], __name__)\n    importer.install()\n\n\nsetup()\ndel setup\n"
  },
  {
    "path": "yt/exthook.py",
    "content": "\"\"\"\n    yt.exthook\n    ~~~~~~~~~~\n\n    Redirect imports for extensions.  This module basically makes it possible\n    for us to transition from ytext.foo to yt_foo without having to\n    force all extensions to upgrade at the same time.\n\n    When a user does ``from yt.extensions.foo import bar`` it will attempt to\n    import ``from yt_foo import bar`` first and when that fails it will\n    try to import ``from ytext.foo import bar``.\n\n    We're switching from namespace packages because it was just too painful for\n    everybody involved.\n\n    This is used by `yt.extensions`.\n\n    :copyright: (c) 2015 by Armin Ronacher.\n    :license: BSD, see LICENSE for more details.\n\"\"\"\n\n# This source code was originally in flask/exthook.py\nimport os\nimport sys\n\n\nclass ExtensionImporter:\n    \"\"\"This importer redirects imports from this submodule to other locations.\n    This makes it possible to transition from the old flaskext.name to the\n    newer flask_name without people having a hard time.\n    \"\"\"\n\n    def __init__(self, module_choices, wrapper_module):\n        self.module_choices = module_choices\n        self.wrapper_module = wrapper_module\n        self.prefix = wrapper_module + \".\"\n        self.prefix_cutoff = wrapper_module.count(\".\") + 1\n\n    def __eq__(self, other):\n        return (\n            self.__class__.__module__ == other.__class__.__module__\n            and self.__class__.__name__ == other.__class__.__name__\n            and self.wrapper_module == other.wrapper_module\n            and self.module_choices == other.module_choices\n        )\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def install(self):\n        sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]\n\n    def find_module(self, fullname, path=None):\n        if fullname.startswith(self.prefix):\n            return self\n\n    def load_module(self, fullname):\n        if fullname in sys.modules:\n            return sys.modules[fullname]\n        modname = fullname.split(\".\", self.prefix_cutoff)[self.prefix_cutoff]\n        for path in self.module_choices:\n            realname = path % modname\n            try:\n                __import__(realname)\n            except ImportError:\n                exc_type, exc_value, tb = sys.exc_info()\n                # since we only establish the entry in sys.modules at the\n                # very this seems to be redundant, but if recursive imports\n                # happen we will call into the move import a second time.\n                # On the second invocation we still don't have an entry for\n                # fullname in sys.modules, but we will end up with the same\n                # fake module name and that import will succeed since this\n                # one already has a temporary entry in the modules dict.\n                # Since this one \"succeeded\" temporarily that second\n                # invocation now will have created a fullname entry in\n                # sys.modules which we have to kill.\n                sys.modules.pop(fullname, None)\n\n                # If it's an important traceback we reraise it, otherwise\n                # we swallow it and try the next choice.  The skipped frame\n                # is the one from __import__ above which we don't care about\n                if self.is_important_traceback(realname, tb):\n                    raise exc_value.with_traceback(tb.tb_next)  # noqa: B904\n                continue\n            module = sys.modules[fullname] = sys.modules[realname]\n            if \".\" not in modname:\n                setattr(sys.modules[self.wrapper_module], modname, module)\n            return module\n        raise ImportError(f\"No module named {fullname}\")\n\n    def is_important_traceback(self, important_module, tb):\n        \"\"\"Walks a traceback's frames and checks if any of the frames\n        originated in the given important module.  If that is the case then we\n        were able to import the module itself but apparently something went\n        wrong when the module was imported.  (Eg: import of an import failed).\n        \"\"\"\n        while tb is not None:\n            if self.is_important_frame(important_module, tb):\n                return True\n            tb = tb.tb_next\n        return False\n\n    def is_important_frame(self, important_module, tb):\n        \"\"\"Checks a single frame if it's important.\"\"\"\n        g = tb.tb_frame.f_globals\n        if \"__name__\" not in g:\n            return False\n\n        module_name = g[\"__name__\"]\n\n        # Python 2.7 Behavior.  Modules are cleaned up late so the\n        # name shows up properly here.  Success!\n        if module_name == important_module:\n            return True\n\n        # Some python versions will clean up modules so early that the\n        # module name at that point is no longer set.  Try guessing from\n        # the filename then.\n        filename = os.path.abspath(tb.tb_frame.f_code.co_filename)\n        test_string = os.path.sep + important_module.replace(\".\", os.path.sep)\n        return (\n            test_string + \".py\" in filename\n            or test_string + os.path.sep + \"__init__.py\" in filename\n        )\n"
  },
  {
    "path": "yt/fields/__init__.py",
    "content": ""
  },
  {
    "path": "yt/fields/angular_momentum.py",
    "content": "import numpy as np\n\nfrom yt.utilities.lib.misc_utilities import (\n    obtain_position_vector,\n    obtain_relative_velocity_vector,\n)\n\nfrom .derived_field import ValidateParameter\nfrom .field_plugin_registry import register_field_plugin\nfrom .vector_operations import create_magnitude_field\n\n\n@register_field_plugin\ndef setup_angular_momentum(registry, ftype=\"gas\", slice_info=None):\n    # Angular momentum defined here needs to be consistent with\n    # _particle_specific_angular_momentum in particle_fields.py\n    unit_system = registry.ds.unit_system\n\n    def _specific_angular_momentum_x(data):\n        xv, yv, zv = obtain_relative_velocity_vector(data)\n        rv = obtain_position_vector(data)\n        units = rv.units\n        rv = np.rollaxis(rv, 0, len(rv.shape))\n        rv = data.ds.arr(rv, units=units)\n        return rv[..., 1] * zv - rv[..., 2] * yv\n\n    def _specific_angular_momentum_y(data):\n        xv, yv, zv = obtain_relative_velocity_vector(data)\n        rv = obtain_position_vector(data)\n        units = rv.units\n        rv = np.rollaxis(rv, 0, len(rv.shape))\n        rv = data.ds.arr(rv, units=units)\n        return rv[..., 2] * xv - rv[..., 0] * zv\n\n    def _specific_angular_momentum_z(data):\n        xv, yv, zv = obtain_relative_velocity_vector(data)\n        rv = obtain_position_vector(data)\n        units = rv.units\n        rv = np.rollaxis(rv, 0, len(rv.shape))\n        rv = data.ds.arr(rv, units=units)\n        return rv[..., 0] * yv - rv[..., 1] * xv\n\n    registry.add_field(\n        (ftype, \"specific_angular_momentum_x\"),\n        sampling_type=\"local\",\n        function=_specific_angular_momentum_x,\n        units=unit_system[\"specific_angular_momentum\"],\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"bulk_velocity\")],\n    )\n\n    registry.add_field(\n        (ftype, \"specific_angular_momentum_y\"),\n        sampling_type=\"local\",\n        function=_specific_angular_momentum_y,\n        units=unit_system[\"specific_angular_momentum\"],\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"bulk_velocity\")],\n    )\n\n    registry.add_field(\n        (ftype, \"specific_angular_momentum_z\"),\n        sampling_type=\"local\",\n        function=_specific_angular_momentum_z,\n        units=unit_system[\"specific_angular_momentum\"],\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"bulk_velocity\")],\n    )\n\n    create_magnitude_field(\n        registry,\n        \"specific_angular_momentum\",\n        unit_system[\"specific_angular_momentum\"],\n        ftype=ftype,\n    )\n\n    def _angular_momentum_x(data):\n        return data[ftype, \"mass\"] * data[ftype, \"specific_angular_momentum_x\"]\n\n    registry.add_field(\n        (ftype, \"angular_momentum_x\"),\n        sampling_type=\"local\",\n        function=_angular_momentum_x,\n        units=unit_system[\"angular_momentum\"],\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"bulk_velocity\")],\n    )\n\n    def _angular_momentum_y(data):\n        return data[ftype, \"mass\"] * data[ftype, \"specific_angular_momentum_y\"]\n\n    registry.add_field(\n        (ftype, \"angular_momentum_y\"),\n        sampling_type=\"local\",\n        function=_angular_momentum_y,\n        units=unit_system[\"angular_momentum\"],\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"bulk_velocity\")],\n    )\n\n    def _angular_momentum_z(data):\n        return data[ftype, \"mass\"] * data[ftype, \"specific_angular_momentum_z\"]\n\n    registry.add_field(\n        (ftype, \"angular_momentum_z\"),\n        sampling_type=\"local\",\n        function=_angular_momentum_z,\n        units=unit_system[\"angular_momentum\"],\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"bulk_velocity\")],\n    )\n\n    create_magnitude_field(\n        registry, \"angular_momentum\", unit_system[\"angular_momentum\"], ftype=ftype\n    )\n"
  },
  {
    "path": "yt/fields/api.py",
    "content": "# from . import species_fields\nfrom . import (\n    angular_momentum,\n    astro_fields,\n    cosmology_fields,\n    fluid_fields,\n    fluid_vector_fields,\n    geometric_fields,\n    local_fields,\n    magnetic_field,\n    my_plugin_fields,\n    particle_fields,\n    vector_operations,\n)\nfrom .derived_field import (\n    DerivedField,\n    ValidateDataField,\n    ValidateGridType,\n    ValidateParameter,\n    ValidateProperty,\n    ValidateSpatial,\n)\nfrom .field_detector import FieldDetector\nfrom .field_info_container import FieldInfoContainer\nfrom .field_plugin_registry import field_plugins, register_field_plugin\nfrom .local_fields import add_field, derived_field\nfrom .xray_emission_fields import add_xray_emissivity_field\n"
  },
  {
    "path": "yt/fields/astro_fields.py",
    "content": "import numpy as np\n\nfrom .derived_field import ValidateParameter\nfrom .field_plugin_registry import register_field_plugin\nfrom .vector_operations import create_magnitude_field\n\n\n@register_field_plugin\ndef setup_astro_fields(registry, ftype=\"gas\", slice_info=None):\n    unit_system = registry.ds.unit_system\n    pc = registry.ds.units.physical_constants\n    # slice_info would be the left, the right, and the factor.\n    # For example, with the old Enzo-ZEUS fields, this would be:\n    # slice(None, -2, None)\n    # slice(1, -1, None)\n    # 1.0\n    # Otherwise, we default to a centered difference.\n    if slice_info is None:\n        sl_left = slice(None, -2, None)\n        sl_right = slice(2, None, None)\n        div_fac = 2.0\n    else:\n        sl_left, sl_right, div_fac = slice_info\n\n    def _dynamical_time(data):\n        \"\"\"\n        sqrt(3 pi / (16 G rho))\n        \"\"\"\n        return np.sqrt(3.0 * np.pi / (16.0 * pc.G * data[ftype, \"density\"]))\n\n    registry.add_field(\n        (ftype, \"dynamical_time\"),\n        sampling_type=\"local\",\n        function=_dynamical_time,\n        units=unit_system[\"time\"],\n    )\n\n    def _jeans_mass(data):\n        MJ_constant = (((5.0 * pc.kboltz) / (pc.G * pc.mh)) ** 1.5) * (\n            3.0 / (4.0 * np.pi)\n        ) ** 0.5\n        u = (\n            MJ_constant\n            * (\n                (data[ftype, \"temperature\"] / data[ftype, \"mean_molecular_weight\"])\n                ** 1.5\n            )\n            * (data[ftype, \"density\"] ** (-0.5))\n        )\n        return u\n\n    registry.add_field(\n        (ftype, \"jeans_mass\"),\n        sampling_type=\"local\",\n        function=_jeans_mass,\n        units=unit_system[\"mass\"],\n    )\n\n    def _emission_measure(data):\n        dV = data[ftype, \"mass\"] / data[ftype, \"density\"]\n        nenhdV = data[ftype, \"H_nuclei_density\"] * dV\n        nenhdV *= data[ftype, \"El_number_density\"]\n        return nenhdV\n\n    registry.add_field(\n        (ftype, \"emission_measure\"),\n        sampling_type=\"local\",\n        function=_emission_measure,\n        units=unit_system[\"number_density\"],\n    )\n\n    def _mazzotta_weighting(data):\n        # Spectroscopic-like weighting field for galaxy clusters\n        # Only useful as a weight_field for temperature, metallicity, velocity\n        ret = data[ftype, \"El_number_density\"].d ** 2\n        ret *= data[ftype, \"kT\"].d ** -0.75\n        return ret\n\n    registry.add_field(\n        (ftype, \"mazzotta_weighting\"),\n        sampling_type=\"local\",\n        function=_mazzotta_weighting,\n        units=\"\",\n    )\n\n    def _optical_depth(data):\n        return data[ftype, \"El_number_density\"] * pc.sigma_thompson\n\n    registry.add_field(\n        (ftype, \"optical_depth\"),\n        sampling_type=\"local\",\n        function=_optical_depth,\n        units=unit_system[\"length\"] ** -1,\n    )\n\n    def _sz_kinetic(data):\n        # minus sign is because radial velocity is WRT viewer\n        # See issue #1225\n        return -data[ftype, \"velocity_los\"] * data[ftype, \"optical_depth\"] / pc.clight\n\n    registry.add_field(\n        (ftype, \"sz_kinetic\"),\n        sampling_type=\"local\",\n        function=_sz_kinetic,\n        units=unit_system[\"length\"] ** -1,\n        validators=[ValidateParameter(\"axis\", {\"axis\": [0, 1, 2]})],\n    )\n\n    def _szy(data):\n        kT = data[ftype, \"kT\"] / (pc.me * pc.clight * pc.clight)\n        return data[ftype, \"optical_depth\"] * kT\n\n    registry.add_field(\n        (ftype, \"szy\"),\n        sampling_type=\"local\",\n        function=_szy,\n        units=unit_system[\"length\"] ** -1,\n    )\n\n    def _entropy(field, data):\n        mgammam1 = -2.0 / 3.0\n        tr = data[ftype, \"kT\"] * data[ftype, \"El_number_density\"] ** mgammam1\n        return data.apply_units(tr, field.units)\n\n    registry.add_field(\n        (ftype, \"entropy\"), sampling_type=\"local\", units=\"keV*cm**2\", function=_entropy\n    )\n\n    def _lorentz_factor(data):\n        b2 = data[ftype, \"velocity_magnitude\"].to_value(\"c\")\n        b2 *= b2\n        return 1.0 / np.sqrt(1.0 - b2)\n\n    registry.add_field(\n        (ftype, \"lorentz_factor\"),\n        sampling_type=\"local\",\n        units=\"\",\n        function=_lorentz_factor,\n    )\n\n    # 4-velocity spatial components\n    def four_velocity_xyz(u):\n        def _four_velocity(data):\n            return data[\"gas\", f\"velocity_{u}\"] * data[\"gas\", \"lorentz_factor\"]\n\n        return _four_velocity\n\n    for u in registry.ds.coordinates.axis_order:\n        registry.add_field(\n            (\"gas\", f\"four_velocity_{u}\"),\n            sampling_type=\"local\",\n            function=four_velocity_xyz(u),\n            units=unit_system[\"velocity\"],\n        )\n\n    # 4-velocity t-component\n    def _four_velocity_t(data):\n        return data[\"gas\", \"lorentz_factor\"] * pc.clight\n\n    registry.add_field(\n        (\"gas\", \"four_velocity_t\"),\n        sampling_type=\"local\",\n        function=_four_velocity_t,\n        units=unit_system[\"velocity\"],\n    )\n\n    create_magnitude_field(\n        registry,\n        \"four_velocity\",\n        unit_system[\"velocity\"],\n        ftype=ftype,\n    )\n"
  },
  {
    "path": "yt/fields/astro_simulations.py",
    "content": "from .domain_context import DomainContext\n\n# Here's how this all works:\n#\n#   1. We have a mapping here that defines fields we might expect to find in an\n#      astrophysical simulation to units (not necessarily definitive) that we\n#      may want to use them in.\n#   2. Simulations and frontends will register aliases from fields (which can\n#      utilize units) to the fields enumerated here.\n#   3. This plugin can call additional plugins on the registry.\n\n\nclass AstroSimulation(DomainContext):\n    # This is an immutable of immutables.  Note that we do not specify the\n    # fluid type here, although in most cases we expect it to be \"gas\".\n    _known_fluid_fields = (\n        (\"density\", \"g/cm**3\"),\n        (\"number_density\", \"1/cm**3\"),\n        (\"pressure\", \"dyne / cm**2\"),\n        (\"specific_thermal_energy\", \"erg / g\"),\n        (\"temperature\", \"K\"),\n        (\"velocity_x\", \"cm / s\"),\n        (\"velocity_y\", \"cm / s\"),\n        (\"velocity_z\", \"cm / s\"),\n        (\"magnetic_field_x\", \"gauss\"),\n        (\"magnetic_field_y\", \"gauss\"),\n        (\"magnetic_field_z\", \"gauss\"),\n        (\"radiation_acceleration_x\", \"cm / s**2\"),\n        (\"radiation_acceleration_y\", \"cm / s**2\"),\n        (\"radiation_acceleration_z\", \"cm / s**2\"),\n    )\n\n    # This set of fields can be applied to any particle type.\n    _known_particle_fields = (\n        (\"particle_position_x\", \"cm\"),\n        (\"particle_position_y\", \"cm\"),\n        (\"particle_position_z\", \"cm\"),\n        (\"particle_velocity_x\", \"cm / s\"),\n        (\"particle_velocity_y\", \"cm / s\"),\n        (\"particle_velocity_z\", \"cm / s\"),\n        (\"particle_mass\", \"g\"),\n        (\"particle_index\", \"\"),\n    )\n"
  },
  {
    "path": "yt/fields/cosmology_fields.py",
    "content": "from .derived_field import ValidateParameter\nfrom .field_exceptions import NeedsConfiguration, NeedsParameter\nfrom .field_plugin_registry import register_field_plugin\n\n\n@register_field_plugin\ndef setup_cosmology_fields(registry, ftype=\"gas\", slice_info=None):\n    unit_system = registry.ds.unit_system\n    # slice_info would be the left, the right, and the factor.\n    # For example, with the old Enzo-ZEUS fields, this would be:\n    # slice(None, -2, None)\n    # slice(1, -1, None)\n    # 1.0\n    # Otherwise, we default to a centered difference.\n    if slice_info is None:\n        sl_left = slice(None, -2, None)\n        sl_right = slice(2, None, None)\n        div_fac = 2.0\n    else:\n        sl_left, sl_right, div_fac = slice_info\n\n    def _matter_density(data):\n        return data[ftype, \"density\"] + data[ftype, \"dark_matter_density\"]\n\n    registry.add_field(\n        (ftype, \"matter_density\"),\n        sampling_type=\"local\",\n        function=_matter_density,\n        units=unit_system[\"density\"],\n    )\n\n    def _matter_mass(data):\n        return data[ftype, \"matter_density\"] * data[\"index\", \"cell_volume\"]\n\n    registry.add_field(\n        (ftype, \"matter_mass\"),\n        sampling_type=\"local\",\n        function=_matter_mass,\n        units=unit_system[\"mass\"],\n    )\n\n    # rho_total / rho_cr(z).\n    def _overdensity(data):\n        if (\n            not hasattr(data.ds, \"cosmological_simulation\")\n            or not data.ds.cosmological_simulation\n        ):\n            raise NeedsConfiguration(\"cosmological_simulation\", 1)\n        co = data.ds.cosmology\n        return data[ftype, \"matter_density\"] / co.critical_density(\n            data.ds.current_redshift\n        )\n\n    registry.add_field(\n        (ftype, \"overdensity\"), sampling_type=\"local\", function=_overdensity, units=\"\"\n    )\n\n    # rho_baryon / <rho_baryon>\n    def _baryon_overdensity(data):\n        if (\n            not hasattr(data.ds, \"cosmological_simulation\")\n            or not data.ds.cosmological_simulation\n        ):\n            raise NeedsConfiguration(\"cosmological_simulation\", 1)\n        omega_baryon = data.get_field_parameter(\"omega_baryon\")\n        if omega_baryon is None:\n            raise NeedsParameter(\"omega_baryon\")\n        co = data.ds.cosmology\n        # critical_density(z) ~ omega_lambda + omega_matter * (1 + z)^3\n        # mean matter density(z) ~ omega_matter * (1 + z)^3\n        return (\n            data[ftype, \"density\"]\n            / omega_baryon\n            / co.critical_density(0.0)\n            / (1.0 + data.ds.current_redshift) ** 3\n        )\n\n    registry.add_field(\n        (ftype, \"baryon_overdensity\"),\n        sampling_type=\"local\",\n        function=_baryon_overdensity,\n        units=\"\",\n        validators=[ValidateParameter(\"omega_baryon\")],\n    )\n\n    # rho_matter / <rho_matter>\n    def _matter_overdensity(data):\n        if (\n            not hasattr(data.ds, \"cosmological_simulation\")\n            or not data.ds.cosmological_simulation\n        ):\n            raise NeedsConfiguration(\"cosmological_simulation\", 1)\n        co = data.ds.cosmology\n        # critical_density(z) ~ omega_lambda + omega_matter * (1 + z)^3\n        # mean density(z) ~ omega_matter * (1 + z)^3\n        return (\n            data[ftype, \"matter_density\"]\n            / data.ds.omega_matter\n            / co.critical_density(0.0)\n            / (1.0 + data.ds.current_redshift) ** 3\n        )\n\n    registry.add_field(\n        (ftype, \"matter_overdensity\"),\n        sampling_type=\"local\",\n        function=_matter_overdensity,\n        units=\"\",\n    )\n\n    # r / r_vir\n    def _virial_radius_fraction(data):\n        virial_radius = data.get_field_parameter(\"virial_radius\")\n        if virial_radius == 0.0:\n            ret = 0.0\n        else:\n            ret = data[\"index\", \"radius\"] / virial_radius\n        return ret\n\n    registry.add_field(\n        (\"index\", \"virial_radius_fraction\"),\n        sampling_type=\"local\",\n        function=_virial_radius_fraction,\n        validators=[ValidateParameter(\"virial_radius\")],\n        units=\"\",\n    )\n\n    # Weak lensing convergence.\n    # Eqn 4 of Metzler, White, & Loken (2001, ApJ, 547, 560).\n    # This needs to be checked for accuracy.\n    def _weak_lensing_convergence(data):\n        if (\n            not hasattr(data.ds, \"cosmological_simulation\")\n            or not data.ds.cosmological_simulation\n        ):\n            raise NeedsConfiguration(\"cosmological_simulation\", 1)\n        co = data.ds.cosmology\n        pc = data.ds.units.physical_constants\n        observer_redshift = data.get_field_parameter(\"observer_redshift\")\n        source_redshift = data.get_field_parameter(\"source_redshift\")\n\n        # observer to lens\n        dl = co.angular_diameter_distance(observer_redshift, data.ds.current_redshift)\n        # observer to source\n        ds = co.angular_diameter_distance(observer_redshift, source_redshift)\n        # lens to source\n        dls = co.angular_diameter_distance(data.ds.current_redshift, source_redshift)\n\n        # removed the factor of 1 / a to account for the fact that we are projecting\n        # with a proper distance.\n        return (\n            1.5\n            * (co.hubble_constant / pc.clight) ** 2\n            * (dl * dls / ds)\n            * data[ftype, \"matter_overdensity\"]\n        ).in_units(\"1/cm\")\n\n    registry.add_field(\n        (ftype, \"weak_lensing_convergence\"),\n        sampling_type=\"local\",\n        function=_weak_lensing_convergence,\n        units=unit_system[\"length\"] ** -1,\n        validators=[\n            ValidateParameter(\"observer_redshift\"),\n            ValidateParameter(\"source_redshift\"),\n        ],\n    )\n"
  },
  {
    "path": "yt/fields/derived_field.py",
    "content": "import abc\nimport contextlib\nimport enum\nimport inspect\nimport operator\nimport re\nimport sys\nfrom collections.abc import Callable, Iterable\nfrom functools import cached_property, reduce\nfrom typing import Optional\n\nfrom more_itertools import always_iterable\n\nimport yt.units.dimensions as ytdims\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._typing import FieldKey\nfrom yt.funcs import iter_fields, validate_field_key\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.utilities.exceptions import YTFieldNotFound\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.visualization._commons import _get_units_label\n\nfrom .field_detector import FieldDetector\nfrom .field_exceptions import (\n    FieldUnitsError,\n    NeedsDataField,\n    NeedsGridType,\n    NeedsOriginalGrid,\n    NeedsParameter,\n    NeedsProperty,\n)\n\nif sys.version_info >= (3, 11):\n    from typing import assert_never\nelse:\n    from typing_extensions import assert_never\n\n\nclass _FieldFuncSignature(enum.Enum):\n    V1 = enum.auto()  # has arguments field, data\n    V2 = enum.auto()  # doesn't support field argument\n\n\ndef TranslationFunc(field_name):\n    def _TranslationFunc(data, *, field=None):\n        # We do a bunch of in-place modifications, so we will copy this.\n        return data[field_name].copy()\n\n    _TranslationFunc.alias_name = field_name\n    return _TranslationFunc\n\n\ndef NullFunc(field, data):\n    raise YTFieldNotFound(field.name)\n\n\ndef DeprecatedFieldFunc(ret_field, func, since, removal):\n    def _DeprecatedFieldFunc(field, data):\n        # Only log a warning if we've already done\n        # field detection\n        if data.ds.fields_detected:\n            args = [field.name, since]\n            msg = \"The Derived Field %s is deprecated as of yt v%s \"\n            if removal is not None:\n                msg += \"and will be removed in yt v%s \"\n                args.append(removal)\n            if ret_field != field.name:\n                msg += \", use %s instead\"\n                args.append(ret_field)\n            mylog.warning(msg, *args)\n        return func(data)\n\n    return _DeprecatedFieldFunc\n\n\nclass DerivedFieldBase(abc.ABC):\n    @abc.abstractmethod\n    def __call__(self, field, data):\n        pass\n\n    @abc.abstractmethod\n    def __repr__(self) -> str:\n        pass\n\n    # Multiplication (left and right side)\n    def __mul__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.mul)\n\n    def __rmul__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.mul)\n\n    # Division (left side)\n    def __truediv__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.truediv)\n\n    def __rtruediv__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([other, self], op=operator.truediv)\n\n    # Addition (left and right side)\n    def __add__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.add)\n\n    def __radd__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.add)\n\n    # Subtraction (left and right side)\n    def __sub__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.sub)\n\n    def __rsub__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([other, self], op=operator.sub)\n\n    # Unary minus\n    def __neg__(self) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self], op=operator.neg)\n\n    # Comparison operators\n    def __leq__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.le)\n\n    def __lt__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.lt)\n\n    def __geq__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.ge)\n\n    def __gt__(self, other) -> \"DerivedFieldCombination\":\n        return DerivedFieldCombination([self, other], op=operator.gt)\n\n    # NOTE: we need to ignore type checking for these methods\n    # since mypy expects the following two to return `bool`\n    def __eq__(self, other) -> \"DerivedFieldCombination\":  # type: ignore[override]\n        return DerivedFieldCombination([self, other], op=operator.eq)\n\n    def __ne__(self, other) -> \"DerivedFieldCombination\":  # type: ignore[override]\n        return DerivedFieldCombination([self, other], op=operator.ne)\n\n    @abc.abstractmethod\n    def __hash__(self) -> int:\n        pass\n\n\nclass DerivedFieldCombination(DerivedFieldBase):\n    sampling_type: str | None\n    terms: list\n    op: Callable\n\n    def __init__(self, terms: list, op: Callable):\n        if not terms:\n            raise ValueError(\"DerivedFieldCombination requires at least one term.\")\n\n        # Make sure all terms have the same sampling type\n        sampling_types = set()\n        for term in terms:\n            if isinstance(term, DerivedField):\n                sampling_types.add(term.sampling_type)\n\n        if len(sampling_types) > 1:\n            raise ValueError(\n                \"All terms in a DerivedFieldCombination must \"\n                \"have the same sampling type.\"\n            )\n        self.sampling_type = sampling_types.pop() if sampling_types else None\n        self.terms = terms\n        self.op = op\n\n    def __hash__(self) -> int:\n        return hash((self.sampling_type, tuple(self.terms), self.op))\n\n    def __call__(self, field, data):\n        \"\"\"\n        Return the value of the field in a given data object.\n        \"\"\"\n        qties = []\n        for term in self.terms:\n            if isinstance(term, DerivedField):\n                qties.append(data[term.name])\n            elif isinstance(term, DerivedFieldCombination):\n                qties.append(term(field, data))\n            else:\n                qties.append(term)\n\n        if len(qties) == 1:\n            return self.op(qties[0])\n        else:\n            return reduce(self.op, qties)\n\n    def __repr__(self):\n        return f\"DerivedFieldCombination(terms={self.terms!r}, op={self.op!r})\"\n\n    def getDependentFields(self):\n        fields = []\n        for term in self.terms:\n            if isinstance(term, DerivedField):\n                fields.append(term.name)\n            elif isinstance(term, DerivedFieldCombination):\n                fields.extend(term.getDependentFields())\n            else:\n                continue\n        return fields\n\n    @property\n    def name(self):\n        return f\"{self!r}\"\n\n    def __bool__(self):\n        match self.op:\n            # Special case for equality, check terms are aliases of each other\n            case operator.eq if len(self.terms) == 2:\n                return self.terms[0] is self.terms[1]\n            case operator.eq:\n                raise ValueError(\n                    \"The truth value of a DerivedFieldCombination with more \"\n                    \"than two terms is ambiguous.\"\n                )\n            case _:\n                return True\n\n\nclass DerivedField(DerivedFieldBase):\n    \"\"\"\n    This is the base class used to describe a cell-by-cell derived field.\n\n    Parameters\n    ----------\n\n    name : str\n       is the name of the field.\n    function : callable\n       A function handle that defines the field.  Should accept\n       arguments (data)\n    units : str\n       A plain text string encoding the unit, or a query to a unit system of\n       a dataset. Powers must be in Python syntax (** instead of ^). If set\n       to 'auto' or None (default), units will be inferred from the return value\n       of the field function.\n    take_log : bool\n       Describes whether the field should be logged\n    validators : list\n       A list of :class:`FieldValidator` objects\n    sampling_type : string, default = \"cell\"\n        How is the field sampled?  This can be one of the following options at\n        present: \"cell\" (cell-centered), \"discrete\" (or \"particle\") for\n        discretely sampled data.\n    vector_field : bool\n       Describes the dimensionality of the field.  Currently unused.\n    display_field : bool\n       Governs its appearance in the dropdowns in Reason\n    not_in_all : bool\n       Used for baryon fields from the data that are not in all the grids\n    display_name : str\n       A name used in the plots\n    output_units : str\n       For fields that exist on disk, which we may want to convert to other\n       fields or that get aliased to themselves, we can specify a different\n       desired output unit than the unit found on disk.\n    dimensions : str or object from yt.units.dimensions\n       The dimensions of the field, only used for error checking with units='auto'.\n    nodal_flag : array-like with three components\n       This describes how the field is centered within a cell. If nodal_flag\n       is [0, 0, 0], then the field is cell-centered. If any of the components\n       of nodal_flag are 1, then the field is nodal in that direction, meaning\n       it is defined at the lo and hi sides of the cell rather than at the center.\n       For example, a field with nodal_flag = [1, 0, 0] would be defined at the\n       middle of the 2 x-faces of each cell. nodal_flag = [0, 1, 1] would mean the\n       that the field defined at the centers of the 4 edges that are normal to the\n       x axis, while nodal_flag = [1, 1, 1] would be defined at the 8 cell corners.\n    \"\"\"\n\n    _inherited_particle_filter = False\n\n    def __init__(\n        self,\n        name: FieldKey,\n        sampling_type,\n        function,\n        units: str | bytes | Unit | None = None,\n        take_log=True,\n        validators=None,\n        vector_field=False,\n        display_field=True,\n        not_in_all=False,\n        display_name=None,\n        output_units=None,\n        dimensions=None,\n        ds=None,\n        nodal_flag=None,\n        *,\n        alias: Optional[\"DerivedField\"] = None,\n    ):\n        validate_field_key(name)\n        self.name = name\n        self.take_log = take_log\n        self.display_name = display_name\n        self.not_in_all = not_in_all\n        self.display_field = display_field\n        self.sampling_type = sampling_type\n        self.vector_field = vector_field\n        self.ds = ds\n\n        if self.ds is not None:\n            self._ionization_label_format = self.ds._ionization_label_format\n        else:\n            self._ionization_label_format = \"roman_numeral\"\n\n        if nodal_flag is None:\n            self.nodal_flag = [0, 0, 0]\n        else:\n            self.nodal_flag = nodal_flag\n\n        self._function = function\n\n        self.validators = list(always_iterable(validators))\n\n        # handle units\n        self.units: str | bytes | Unit | None\n        if units in (None, \"auto\"):\n            self.units = None\n        elif isinstance(units, str):\n            self.units = units\n        elif isinstance(units, Unit):\n            self.units = str(units)\n        elif isinstance(units, bytes):\n            self.units = units.decode(\"utf-8\")\n        else:\n            raise FieldUnitsError(\n                f\"Cannot handle units {units!r} (type {type(units)}). \"\n                \"Please provide a string or Unit object.\"\n            )\n        if output_units is None:\n            output_units = self.units\n        self.output_units = output_units\n\n        if isinstance(dimensions, str):\n            dimensions = getattr(ytdims, dimensions)\n        self.dimensions = dimensions\n\n        if alias is None:\n            self._shared_aliases_list = [self]\n        else:\n            self._shared_aliases_list = alias._shared_aliases_list\n            self._shared_aliases_list.append(self)\n\n    def __hash__(self) -> int:\n        return hash(\n            (\n                self.name,\n                self.sampling_type,\n                self._function,\n                self.units,\n                self.take_log,\n                tuple(self.validators),\n                self.vector_field,\n                self.display_field,\n                self.not_in_all,\n                self.display_name,\n                self.output_units,\n                self.dimensions,\n                self.ds,\n                tuple(self.nodal_flag),\n            )\n        )\n\n    def _copy_def(self):\n        dd = {}\n        dd[\"name\"] = self.name\n        dd[\"units\"] = self.units\n        dd[\"take_log\"] = self.take_log\n        dd[\"validators\"] = list(self.validators)\n        dd[\"sampling_type\"] = self.sampling_type\n        dd[\"vector_field\"] = self.vector_field\n        dd[\"display_field\"] = True\n        dd[\"not_in_all\"] = self.not_in_all\n        dd[\"display_name\"] = self.display_name\n        return dd\n\n    @property\n    def is_sph_field(self):\n        if self.sampling_type == \"cell\":\n            return False\n        is_sph_field = False\n        if self.is_alias:\n            name = self.alias_name\n        else:\n            name = self.name\n        if hasattr(self.ds, \"_sph_ptypes\"):\n            is_sph_field |= name[0] in (self.ds._sph_ptypes + (\"gas\",))\n        return is_sph_field\n\n    @property\n    def local_sampling(self):\n        return self.sampling_type in (\"discrete\", \"particle\", \"local\")\n\n    def get_units(self):\n        if self.ds is not None:\n            u = Unit(self.units, registry=self.ds.unit_registry)\n        else:\n            u = Unit(self.units)\n        return u.latex_representation()\n\n    def get_projected_units(self):\n        if self.ds is not None:\n            u = Unit(self.units, registry=self.ds.unit_registry)\n        else:\n            u = Unit(self.units)\n        return (u * Unit(\"cm\")).latex_representation()\n\n    def check_available(self, data):\n        \"\"\"\n        This raises an exception of the appropriate type if the set of\n        validation mechanisms are not met, and otherwise returns True.\n        \"\"\"\n        for validator in self.validators:\n            validator(data)\n        # If we don't get an exception, we're good to go\n        return True\n\n    def get_dependencies(self, *args, **kwargs):\n        \"\"\"\n        This returns a list of names of fields that this field depends on.\n        \"\"\"\n        e = FieldDetector(*args, **kwargs)\n        e[self.name]\n        return e\n\n    def _get_needed_parameters(self, fd):\n        params = []\n        values = []\n        permute_params = {}\n        vals = [v for v in self.validators if isinstance(v, ValidateParameter)]\n        for val in vals:\n            if val.parameter_values is not None:\n                permute_params.update(val.parameter_values)\n            else:\n                params.extend(val.parameters)\n                values.extend([fd.get_field_parameter(fp) for fp in val.parameters])\n        return dict(zip(params, values, strict=True)), permute_params\n\n    _unit_registry = None\n\n    @contextlib.contextmanager\n    def unit_registry(self, data):\n        old_registry = self._unit_registry\n        if hasattr(data, \"unit_registry\"):\n            ur = data.unit_registry\n        elif hasattr(data, \"ds\"):\n            ur = data.ds.unit_registry\n        else:\n            ur = None\n        self._unit_registry = ur\n        yield\n        self._unit_registry = old_registry\n\n    def __call__(self, data):\n        \"\"\"Return the value of the field in a given *data* object.\"\"\"\n        self.check_available(data)\n        original_fields = data.keys()  # Copy\n        if self._function is NullFunc:\n            raise RuntimeError(\n                \"Something has gone terribly wrong, _function is NullFunc \"\n                + f\"for {self.name}\"\n            )\n        with self.unit_registry(data):\n            dd = self._eval(data)\n        for field_name in data.keys():\n            if field_name not in original_fields:\n                del data[field_name]\n        return dd\n\n    @cached_property\n    def _func_signature_type(self) -> _FieldFuncSignature:\n        signature = inspect.signature(self._function)\n        if \"field\" in signature.parameters:\n            return _FieldFuncSignature.V1\n        else:\n            return _FieldFuncSignature.V2\n\n    def _eval(self, data):\n        match self._func_signature_type:\n            case _FieldFuncSignature.V1:\n                return self._function(data=data, field=self)\n            case _FieldFuncSignature.V2:\n                return self._function(data)\n            case _:\n                assert_never(self._sig)\n\n    def get_source(self):\n        \"\"\"\n        Return a string containing the source of the function (if possible.)\n        \"\"\"\n        return inspect.getsource(self._function)\n\n    def get_label(self, projected=False):\n        \"\"\"\n        Return a data label for the given field, including units.\n        \"\"\"\n        name = self.name[1]\n        if self.display_name is not None:\n            name = self.display_name\n\n        # Start with the field name\n        data_label = rf\"$\\rm{{{name}}}\"\n\n        # Grab the correct units\n        if projected:\n            raise NotImplementedError\n        else:\n            if self.ds is not None:\n                units = Unit(self.units, registry=self.ds.unit_registry)\n            else:\n                units = Unit(self.units)\n        # Add unit label\n        if not units.is_dimensionless:\n            data_label += _get_units_label(units.latex_representation()).strip(\"$\")\n\n        data_label += r\"$\"\n        return data_label\n\n    @property\n    def alias_field(self) -> bool:\n        issue_deprecation_warning(\n            \"DerivedField.alias_field is a deprecated equivalent to DerivedField.is_alias \",\n            stacklevel=3,\n            since=\"4.1\",\n        )\n        return self.is_alias\n\n    @property\n    def is_alias(self) -> bool:\n        return self._shared_aliases_list.index(self) > 0\n\n    def is_alias_to(self, other: \"DerivedField\") -> bool:\n        return self._shared_aliases_list is other._shared_aliases_list\n\n    @property\n    def alias_name(self) -> FieldKey | None:\n        if self.is_alias:\n            return self._shared_aliases_list[0].name\n        return None\n\n    def __repr__(self):\n        if self._function is NullFunc:\n            s = \"On-Disk Field \"\n        elif self.is_alias:\n            s = f\"Alias Field for {self.alias_name!r} \"\n        else:\n            s = \"Derived Field \"\n        s += f\"{self.name!r}: (units: {self.units!r}\"\n        if self.display_name is not None:\n            s += f\", display_name: {self.display_name!r}\"\n        if self.sampling_type == \"particle\":\n            s += \", particle field\"\n        s += \")\"\n        return s\n\n    def _is_ion(self):\n        p = re.compile(\"_p[0-9]+_\")\n        result = False\n        if p.search(self.name[1]) is not None:\n            result = True\n        return result\n\n    def _ion_to_label(self):\n        # check to see if the output format has changed\n        if self.ds is not None:\n            self._ionization_label_format = self.ds._ionization_label_format\n\n        pnum2rom = {\n            \"0\": \"I\",\n            \"1\": \"II\",\n            \"2\": \"III\",\n            \"3\": \"IV\",\n            \"4\": \"V\",\n            \"5\": \"VI\",\n            \"6\": \"VII\",\n            \"7\": \"VIII\",\n            \"8\": \"IX\",\n            \"9\": \"X\",\n            \"10\": \"XI\",\n            \"11\": \"XII\",\n            \"12\": \"XIII\",\n            \"13\": \"XIV\",\n            \"14\": \"XV\",\n            \"15\": \"XVI\",\n            \"16\": \"XVII\",\n            \"17\": \"XVIII\",\n            \"18\": \"XIX\",\n            \"19\": \"XX\",\n            \"20\": \"XXI\",\n            \"21\": \"XXII\",\n            \"22\": \"XXIII\",\n            \"23\": \"XXIV\",\n            \"24\": \"XXV\",\n            \"25\": \"XXVI\",\n            \"26\": \"XXVII\",\n            \"27\": \"XXVIII\",\n            \"28\": \"XXIX\",\n            \"29\": \"XXX\",\n        }\n\n        # first look for charge to decide if it is an ion\n        p = re.compile(\"_p[0-9]+_\")\n        m = p.search(self.name[1])\n        if m is not None:\n            # Find the ionization state\n            pstr = m.string[m.start() + 1 : m.end() - 1]\n            segments = self.name[1].split(\"_\")\n\n            # find the ionization index\n            for i, s in enumerate(segments):\n                if s == pstr:\n                    ipstr = i\n\n            for i, s in enumerate(segments):\n                # If its the species we don't want to change the capitalization\n                if i == ipstr - 1:\n                    continue\n                segments[i] = s.capitalize()\n\n            species = segments[ipstr - 1]\n\n            # If there is a number in the species part of the label\n            # that indicates part of a molecule\n            symbols = []\n            for symb in species:\n                # can't just use underscore b/c gets replaced later with space\n                if symb.isdigit():\n                    symbols.append(\"latexsub{\" + symb + \"}\")\n                else:\n                    symbols.append(symb)\n            species_label = \"\".join(symbols)\n\n            # Use roman numerals for ionization\n\n            if self._ionization_label_format == \"roman_numeral\":\n                roman = pnum2rom[pstr[1:]]\n                label = (\n                    species_label\n                    + r\"\\ \"\n                    + roman\n                    + r\"\\ \"\n                    + r\"\\ \".join(segments[ipstr + 1 :])\n                )\n\n            # use +/- for ionization\n            else:\n                sign = \"+\" * int(pstr[1:])\n                label = (\n                    \"{\"\n                    + species_label\n                    + \"}\"\n                    + \"^{\"\n                    + sign\n                    + \"}\"\n                    + r\"\\ \"\n                    + r\"\\ \".join(segments[ipstr + 1 :])\n                )\n\n        else:\n            label = self.name[1]\n        return label\n\n    def get_latex_display_name(self):\n        label = self.display_name\n        if label is None:\n            if self._is_ion():\n                fname = self._ion_to_label()\n                label = r\"$\\rm{\" + fname.replace(\"_\", r\"\\ \") + r\"}$\"\n                label = label.replace(\"latexsub\", \"_\")\n            else:\n                label = r\"$\\rm{\" + self.name[1].replace(\"_\", r\"\\ \").title() + r\"}$\"\n        elif label.find(\"$\") == -1:\n            label = label.replace(\" \", r\"\\ \")\n            label = r\"$\\rm{\" + label + r\"}$\"\n        return label\n\n    def __copy__(self):\n        # a shallow copy doesn't copy the _shared_alias_list attr\n        # This method is implemented in support to ParticleFilter.wrap_func\n        return type(self)(\n            name=self.name,\n            sampling_type=self.sampling_type,\n            function=self._function,\n            units=self.units,\n            take_log=self.take_log,\n            validators=self.validators,\n            vector_field=self.vector_field,\n            display_field=self.display_field,\n            not_in_all=self.not_in_all,\n            display_name=self.display_name,\n            output_units=self.output_units,\n            dimensions=self.dimensions,\n            ds=self.ds,\n            nodal_flag=self.nodal_flag,\n        )\n\n\nclass FieldValidator:\n    \"\"\"\n    Base class for FieldValidator objects. Available subclasses include:\n    \"\"\"\n\n    def __init_subclass__(cls, **kwargs):\n        # add the new subclass to the list of subclasses in the docstring\n        class_str = f\":class:`{cls.__name__}`\"\n        if \":class:\" in FieldValidator.__doc__:\n            class_str = \", \" + class_str\n        FieldValidator.__doc__ += class_str\n\n\nclass ValidateParameter(FieldValidator):\n    \"\"\"\n    A :class:`FieldValidator` that ensures the dataset has a given parameter.\n\n    Parameters\n    ----------\n    parameters: str, iterable[str]\n        a single parameter or list of parameters to require\n    parameter_values: dict\n        If *parameter_values* is supplied, this dict should map from field\n        parameter to a value or list of values. It will ensure that the field\n        is available for all permutations of the field parameter.\n    \"\"\"\n\n    def __init__(\n        self,\n        parameters: str | Iterable[str],\n        parameter_values: dict | None = None,\n    ):\n        FieldValidator.__init__(self)\n        self.parameters = list(always_iterable(parameters))\n        self.parameter_values = parameter_values\n\n    def __call__(self, data):\n        doesnt_have = []\n        for p in self.parameters:\n            if not data.has_field_parameter(p):\n                doesnt_have.append(p)\n        if len(doesnt_have) > 0:\n            raise NeedsParameter(doesnt_have)\n        return True\n\n\nclass ValidateDataField(FieldValidator):\n    \"\"\"\n    A :class:`FieldValidator` that ensures the output file has a given data field stored\n    in it.\n\n    Parameters\n    ----------\n    field: str, tuple[str, str], or any iterable of the previous types.\n        the field or fields to require\n    \"\"\"\n\n    def __init__(self, field):\n        FieldValidator.__init__(self)\n        self.fields = list(iter_fields(field))\n\n    def __call__(self, data):\n        doesnt_have = []\n        if isinstance(data, FieldDetector):\n            return True\n        for f in self.fields:\n            if f not in data.index.field_list:\n                doesnt_have.append(f)\n        if len(doesnt_have) > 0:\n            raise NeedsDataField(doesnt_have)\n        return True\n\n\nclass ValidateProperty(FieldValidator):\n    \"\"\"\n    A :class:`FieldValidator` that ensures the data object has a given python attribute.\n\n    Parameters\n    ----------\n    prop: str, iterable[str]\n        the required property or properties to require\n    \"\"\"\n\n    def __init__(self, prop: str | Iterable[str]):\n        FieldValidator.__init__(self)\n        self.prop = list(always_iterable(prop))\n\n    def __call__(self, data):\n        doesnt_have = [p for p in self.prop if not hasattr(data, p)]\n        if len(doesnt_have) > 0:\n            raise NeedsProperty(doesnt_have)\n        return True\n\n\nclass ValidateSpatial(FieldValidator):\n    \"\"\"\n    A :class:`FieldValidator` that ensures the data handed to the field is of spatial\n    nature -- that is to say, 3-D.\n\n    Parameters\n    ----------\n    ghost_zones: int\n        If supplied, will validate that the number of ghost zones required\n        for the field is <= the available ghost zones. Default is 0.\n    fields: Optional str, tuple[str, str], or any iterable of the previous types.\n        The field or fields to validate.\n\n    \"\"\"\n\n    def __init__(self, ghost_zones: int | None = 0, fields=None):\n        FieldValidator.__init__(self)\n        self.ghost_zones = ghost_zones\n        self.fields = fields\n\n    def __call__(self, data):\n        # When we say spatial information, we really mean\n        # that it has a three-dimensional data structure\n        if not getattr(data, \"_spatial\", False):\n            raise NeedsGridType(self.ghost_zones, self.fields)\n        if self.ghost_zones <= data._num_ghost_zones:\n            return True\n        raise NeedsGridType(self.ghost_zones, self.fields)\n\n\nclass ValidateGridType(FieldValidator):\n    \"\"\"\n    A :class:`FieldValidator` that ensures the data handed to the field is an actual\n    grid patch, not a covering grid of any kind. Does not accept parameters.\n    \"\"\"\n\n    def __init__(self):\n        FieldValidator.__init__(self)\n\n    def __call__(self, data):\n        # We need to make sure that it's an actual AMR grid\n        if isinstance(data, FieldDetector):\n            return True\n        if getattr(data, \"_type_name\", None) == \"grid\":\n            return True\n        raise NeedsOriginalGrid()\n"
  },
  {
    "path": "yt/fields/domain_context.py",
    "content": "import abc\n\nfrom yt._typing import FieldKey\n\ndomain_context_registry = {}\n\n\nclass DomainContext(abc.ABC):\n    class __metaclass__(type):\n        def __init__(cls, name, b, d):\n            type.__init__(cls, name, b, d)\n            domain_context_registry[name] = cls\n\n    _known_fluid_fields: tuple[FieldKey, ...]\n    _known_particle_fields: tuple[FieldKey, ...]\n\n    def __init__(self, ds):\n        self.ds = ds\n"
  },
  {
    "path": "yt/fields/field_aliases.py",
    "content": "_field_name_aliases = [\n    (\"GridLevel\", \"grid_level\"),\n    (\"GridIndices\", \"grid_indices\"),\n    (\"OnesOverDx\", \"ones_over_dx\"),\n    (\"Ones\", \"ones\"),\n    # (\"CellsPerBin\",                      \"cells_per_bin\"),\n    (\"SoundSpeed\", \"sound_speed\"),\n    (\"RadialMachNumber\", \"radial_mach_number\"),\n    (\"MachNumber\", \"mach_number\"),\n    (\"CourantTimeStep\", \"courant_time_step\"),\n    # (\"ParticleVelocityMagnitude\",        \"particle_velocity_magnitude\"),\n    (\"VelocityMagnitude\", \"velocity_magnitude\"),\n    (\"TangentialOverVelocityMagnitude\", \"tangential_over_velocity_magnitude\"),\n    (\"Pressure\", \"pressure\"),\n    (\"Entropy\", \"entropy\"),\n    (\"sph_r\", \"spherical_r\"),\n    (\"sph_theta\", \"spherical_theta\"),\n    (\"sph_phi\", \"spherical_phi\"),\n    (\"cyl_R\", \"cylindrical_radius\"),\n    (\"cyl_z\", \"cylindrical_z\"),\n    (\"cyl_theta\", \"cylindrical_theta\"),\n    (\"cyl_RadialVelocity\", \"cylindrical_radial_velocity\"),\n    (\"cyl_RadialVelocityABS\", \"cylindrical_radial_velocity_absolute\"),\n    (\"cyl_TangentialVelocity\", \"velocity_cylindrical_theta\"),\n    (\"cyl_TangentialVelocityABS\", \"velocity_cylindrical_theta\"),\n    (\"DynamicalTime\", \"dynamical_time\"),\n    (\"JeansMassMsun\", \"jeans_mass\"),\n    (\"CellMass\", \"cell_mass\"),\n    (\"TotalMass\", \"total_mass\"),\n    (\"StarMassMsun\", \"star_mass\"),\n    (\"Matter_Density\", \"matter_density\"),\n    (\"ComovingDensity\", \"comoving_density\"),\n    (\"Overdensity\", \"overdensity\"),\n    (\"DensityPerturbation\", \"density_perturbation\"),\n    (\"Baryon_Overdensity\", \"baryon_overdensity\"),\n    (\"WeakLensingConvergence\", \"weak_lensing_convergence\"),\n    (\"CellVolume\", \"cell_volume\"),\n    (\"ChandraEmissivity\", \"chandra_emissivity\"),\n    (\"XRayEmissivity\", \"xray_emissivity\"),\n    (\"SZKinetic\", \"sz_kinetic\"),\n    (\"SZY\", \"szy\"),\n    (\"AveragedDensity\", \"averaged_density\"),\n    (\"DivV\", \"div_v\"),\n    (\"AbsDivV\", \"div_v_absolute\"),\n    (\"Contours\", \"contours\"),\n    (\"tempContours\", \"temp_contours\"),\n    (\"SpecificAngularMomentumX\", \"specific_angular_momentum_x\"),\n    (\"SpecificAngularMomentumY\", \"specific_angular_momentum_y\"),\n    (\"SpecificAngularMomentumZ\", \"specific_angular_momentum_z\"),\n    (\"AngularMomentumX\", \"angular_momentum_x\"),\n    (\"AngularMomentumY\", \"angular_momentum_y\"),\n    (\"AngularMomentumZ\", \"angular_momentum_z\"),\n    # (\"ParticleSpecificAngularMomentumX\", \"particle_specific_angular_momentum_x\"),\n    # (\"ParticleSpecificAngularMomentumY\", \"particle_specific_angular_momentum_y\"),\n    # (\"ParticleSpecificAngularMomentumZ\", \"particle_specific_angular_momentum_z\"),\n    # (\"ParticleAngularMomentumX\",         \"particle_angular_momentum_x\"),\n    # (\"ParticleAngularMomentumY\",         \"particle_angular_momentum_y\"),\n    # (\"ParticleAngularMomentumZ\",         \"particle_angular_momentum_z\"),\n    # (\"ParticleRadius\",                   \"particle_radius\"),\n    (\"Radius\", \"radius\"),\n    (\"RadialVelocity\", \"radial_velocity\"),\n    (\"RadialVelocityABS\", \"radial_velocity_absolute\"),\n    (\"TangentialVelocity\", \"tangential_velocity\"),\n    (\"CuttingPlaneVelocityX\", \"cutting_plane_velocity_x\"),\n    (\"CuttingPlaneVelocityY\", \"cutting_plane_velocity_y\"),\n    (\"CuttingPlaneBX\", \"cutting_plane_magnetic_field_x\"),\n    (\"CuttingPlaneBy\", \"cutting_plane_magnetic_field_y\"),\n    (\"MeanMolecularWeight\", \"mean_molecular_weight\"),\n    (\"particle_density\", \"particle_density\"),\n    (\"ThermalEnergy\", \"specific_thermal_energy\"),\n    (\"TotalEnergy\", \"specific_total_energy\"),\n    (\"MagneticEnergy\", \"magnetic_energy_density\"),\n    (\"GasEnergy\", \"specific_thermal_energy\"),\n    (\"Gas_Energy\", \"specific_thermal_energy\"),\n    (\"BMagnitude\", \"b_magnitude\"),\n    (\"PlasmaBeta\", \"plasma_beta\"),\n    (\"MagneticPressure\", \"magnetic_pressure\"),\n    (\"BPoloidal\", \"b_poloidal\"),\n    (\"BToroidal\", \"b_toroidal\"),\n    (\"BRadial\", \"b_radial\"),\n    (\"VorticitySquared\", \"vorticity_squared\"),\n    (\"gradPressureX\", \"grad_pressure_x\"),\n    (\"gradPressureY\", \"grad_pressure_y\"),\n    (\"gradPressureZ\", \"grad_pressure_z\"),\n    (\"gradPressureMagnitude\", \"grad_pressure_magnitude\"),\n    (\"gradDensityX\", \"grad_density_x\"),\n    (\"gradDensityY\", \"grad_density_y\"),\n    (\"gradDensityZ\", \"grad_density_z\"),\n    (\"gradDensityMagnitude\", \"grad_density_magnitude\"),\n    (\"BaroclinicVorticityX\", \"baroclinic_vorticity_x\"),\n    (\"BaroclinicVorticityY\", \"baroclinic_vorticity_y\"),\n    (\"BaroclinicVorticityZ\", \"baroclinic_vorticity_z\"),\n    (\"BaroclinicVorticityMagnitude\", \"baroclinic_vorticity_magnitude\"),\n    (\"VorticityX\", \"vorticity_x\"),\n    (\"VorticityY\", \"vorticity_y\"),\n    (\"VorticityZ\", \"vorticity_z\"),\n    (\"VorticityMagnitude\", \"vorticity_magnitude\"),\n    (\"VorticityStretchingX\", \"vorticity_stretching_x\"),\n    (\"VorticityStretchingY\", \"vorticity_stretching_y\"),\n    (\"VorticityStretchingZ\", \"vorticity_stretching_z\"),\n    (\"VorticityStretchingMagnitude\", \"vorticity_stretching_magnitude\"),\n    (\"VorticityGrowthX\", \"vorticity_growth_x\"),\n    (\"VorticityGrowthY\", \"vorticity_growth_y\"),\n    (\"VorticityGrowthZ\", \"vorticity_growth_z\"),\n    (\"VorticityGrowthMagnitude\", \"vorticity_growth_magnitude\"),\n    (\"VorticityGrowthMagnitudeABS\", \"vorticity_growth_magnitude_absolute\"),\n    (\"VorticityGrowthTimescale\", \"vorticity_growth_timescale\"),\n    (\"VorticityRadPressureX\", \"vorticity_radiation_pressure_x\"),\n    (\"VorticityRadPressureY\", \"vorticity_radiation_pressure_y\"),\n    (\"VorticityRadPressureZ\", \"vorticity_radiation_pressure_z\"),\n    (\"VorticityRadPressureMagnitude\", \"vorticity_radiation_pressure_magnitude\"),\n    (\"VorticityRPGrowthX\", \"vorticity_radiation_pressure_growth_x\"),\n    (\"VorticityRPGrowthY\", \"vorticity_radiation_pressure_growth_y\"),\n    (\"VorticityRPGrowthZ\", \"vorticity_radiation_pressure_growth_z\"),\n    (\"VorticityRPGrowthMagnitude\", \"vorticity_radiation_pressure_growth_magnitude\"),\n    (\"VorticityRPGrowthTimescale\", \"vorticity_radiation_pressure_growth_timescale\"),\n    (\"DiskAngle\", \"theta\"),\n    (\"Height\", \"height\"),\n    (\"HI density\", \"H_density\"),\n    (\"HII density\", \"H_p1_density\"),\n    (\"HeI density\", \"He_density\"),\n    (\"HeII density\", \"He_p1_density\"),\n    (\"HeIII density\", \"He_p2_density\"),\n]\n\n_field_units_aliases = [\n    (\"cyl_RCode\", \"code_length\"),\n    (\"HeightAU\", \"au\"),\n    (\"cyl_RadialVelocityKMS\", \"km/s\"),\n    (\"cyl_RadialVelocityKMSABS\", \"km/s\"),\n    (\"cyl_TangentialVelocityKMS\", \"km/s\"),\n    (\"cyl_TangentialVelocityKMSABS\", \"km/s\"),\n    (\"CellMassMsun\", \"msun\"),\n    (\"CellMassCode\", \"code_mass\"),\n    (\"TotalMassMsun\", \"msun\"),\n    (\"CellVolumeCode\", \"code_length\"),\n    (\"CellVolumeMpc\", \"Mpc**3\"),\n    (\"ParticleSpecificAngularMomentumXKMSMPC\", \"km/s/Mpc\"),\n    (\"ParticleSpecificAngularMomentumYKMSMPC\", \"km/s/Mpc\"),\n    (\"ParticleSpecificAngularMomentumZKMSMPC\", \"km/s/Mpc\"),\n    (\"RadiusMpc\", \"Mpc\"),\n    (\"ParticleRadiusMpc\", \"Mpc\"),\n    (\"ParticleRadiuskpc\", \"kpc\"),\n    (\"Radiuskpc\", \"kpc\"),\n    (\"ParticleRadiuskpch\", \"kpc\"),\n    (\"Radiuskpch\", \"kpc\"),\n    (\"ParticleRadiuspc\", \"pc\"),\n    (\"Radiuspc\", \"pc\"),\n    (\"ParticleRadiusAU\", \"au\"),\n    (\"RadiusAU\", \"au\"),\n    (\"ParticleRadiusCode\", \"code_length\"),\n    (\"RadiusCode\", \"code_length\"),\n    (\"RadialVelocityKMS\", \"km/s\"),\n    (\"RadialVelocityKMSABS\", \"km/s\"),\n    (\"JeansMassMsun\", \"msun\"),\n]\n"
  },
  {
    "path": "yt/fields/field_detector.py",
    "content": "from collections import defaultdict\n\nimport numpy as np\n\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.io_handler import io_registry\n\nfrom .field_exceptions import NeedsGridType\n\nfp_units = {\n    \"bulk_velocity\": \"cm/s\",\n    \"center\": \"cm\",\n    \"normal\": \"\",\n    \"cp_x_vec\": \"\",\n    \"cp_y_vec\": \"\",\n    \"cp_z_vec\": \"\",\n    \"x_hat\": \"\",\n    \"y_hat\": \"\",\n    \"z_hat\": \"\",\n    \"omega_baryon\": \"\",\n    \"virial_radius\": \"cm\",\n    \"observer_redshift\": \"\",\n    \"source_redshift\": \"\",\n}\n\n\nclass FieldDetector(defaultdict):\n    Level = 1\n    NumberOfParticles = 1\n    _read_exception = None\n    _id_offset = 0\n    domain_id = 0\n\n    def __init__(self, nd=16, ds=None, flat=False, field_parameters=None):\n        self.nd = nd\n        self.flat = flat\n        self._spatial = not flat\n        self.ActiveDimensions = [nd, nd, nd]\n        self.shape = tuple(self.ActiveDimensions)\n        self.size = np.prod(self.ActiveDimensions)\n        self.LeftEdge = [0.0, 0.0, 0.0]\n        self.RightEdge = [1.0, 1.0, 1.0]\n        self.dds = np.ones(3, \"float64\")\n        if field_parameters is None:\n            self.field_parameters = {}\n        else:\n            self.field_parameters = field_parameters\n\n        class fake_dataset(defaultdict):\n            pass\n\n        if ds is None:\n            # required attrs\n            ds = fake_dataset(lambda: 1)\n            ds[\"Massarr\"] = np.ones(6)\n            ds.current_redshift = 0.0\n            ds.omega_lambda = 0.0\n            ds.omega_matter = 0.0\n            ds.cosmological_simulation = 0\n            ds.gamma = 5.0 / 3.0\n            ds.hubble_constant = 0.7\n            ds.domain_left_edge = np.zeros(3, \"float64\")\n            ds.domain_right_edge = np.ones(3, \"float64\")\n            ds.dimensionality = 3\n            ds.periodicity = (True, True, True)\n        self.ds = ds\n\n        class fake_index:\n            class fake_io:\n                def _read_data_set(io_self, data, field):  # noqa: B902\n                    return self._read_data(field)\n\n                _read_exception = RuntimeError\n\n            io = fake_io()\n\n            def get_smallest_dx(self):\n                return 1.0\n\n        self.index = fake_index()\n        self.requested = []\n        self.requested_parameters = []\n        rng = np.random.default_rng()\n        if not self.flat:\n            defaultdict.__init__(\n                self,\n                lambda: (\n                    np.ones((nd, nd, nd), dtype=\"float64\")\n                    + 1e-4 * rng.random((nd, nd, nd))\n                ),\n            )\n        else:\n            defaultdict.__init__(\n                self,\n                lambda: (\n                    np.ones((nd * nd * nd), dtype=\"float64\")\n                    + 1e-4 * rng.random(nd * nd * nd)\n                ),\n            )\n\n    def _reshape_vals(self, arr):\n        if not self._spatial:\n            return arr\n        if len(arr.shape) == 3:\n            return arr\n        return arr.reshape(self.ActiveDimensions, order=\"C\")\n\n    def __missing__(self, item: tuple[str, str] | str):\n        from yt.fields.derived_field import NullFunc\n\n        if not isinstance(item, tuple):\n            field = (\"unknown\", item)\n        else:\n            field = item\n        finfo = self.ds._get_field_info(field)\n        params, permute_params = finfo._get_needed_parameters(self)\n        self.field_parameters.update(params)\n        # For those cases where we are guessing the field type, we will\n        # need to re-update -- otherwise, our item will always not have the\n        # field type.  This can lead to, for instance, \"unknown\" particle\n        # types not getting correctly identified.\n        # Note that the *only* way this works is if we also fix our field\n        # dependencies during checking.  Bug #627 talks about this.\n        _item: tuple[str, str] = finfo.name\n        if finfo is not None and finfo._function is not NullFunc:\n            try:\n                for param, param_v in permute_params.items():\n                    for v in param_v:\n                        self.field_parameters[param] = v\n                        vv = finfo(self)\n                if not permute_params:\n                    vv = finfo(self)\n            except NeedsGridType as exc:\n                ngz = exc.ghost_zones\n                nfd = FieldDetector(\n                    self.nd + ngz * 2,\n                    ds=self.ds,\n                    field_parameters=self.field_parameters.copy(),\n                )\n                nfd._num_ghost_zones = ngz\n                vv = finfo(nfd)\n                if ngz > 0:\n                    vv = vv[ngz:-ngz, ngz:-ngz, ngz:-ngz]\n                for i in nfd.requested:\n                    if i not in self.requested:\n                        self.requested.append(i)\n                for i in nfd.requested_parameters:\n                    if i not in self.requested_parameters:\n                        self.requested_parameters.append(i)\n            if vv is not None:\n                if not self.flat:\n                    self[_item] = vv\n                else:\n                    self[_item] = vv.ravel()\n                return self[_item]\n        elif finfo is not None and finfo.sampling_type == \"particle\":\n            io = io_registry[self.ds.dataset_type](self.ds)\n            if hasattr(io, \"_vector_fields\") and (\n                _item in io._vector_fields or _item[1] in io._vector_fields\n            ):\n                try:\n                    cols = io._vector_fields[_item]\n                except KeyError:\n                    cols = io._vector_fields[_item[1]]\n                # A vector\n                self[_item] = YTArray(\n                    np.ones((self.NumberOfParticles, cols)),\n                    finfo.units,\n                    registry=self.ds.unit_registry,\n                )\n            else:\n                # Not a vector\n                self[_item] = YTArray(\n                    np.ones(self.NumberOfParticles),\n                    finfo.units,\n                    registry=self.ds.unit_registry,\n                )\n            if _item == (\"STAR\", \"BIRTH_TIME\"):\n                # hack for the artio frontend so we pass valid times to\n                # the artio functions for calculating physical times\n                # from internal times\n                self[_item] *= -0.1\n            self.requested.append(_item)\n            return self[_item]\n        self.requested.append(_item)\n        if _item not in self:\n            self[_item] = self._read_data(_item)\n        return self[_item]\n\n    def _debug(self):\n        # We allow this to pass through.\n        return\n\n    def deposit(self, *args, **kwargs):\n        from yt.data_objects.static_output import ParticleDataset\n        from yt.frontends.stream.data_structures import StreamParticlesDataset\n\n        if kwargs[\"method\"] == \"mesh_id\":\n            if isinstance(self.ds, (StreamParticlesDataset, ParticleDataset)):\n                raise ValueError\n        rng = np.random.default_rng()\n        return rng.random((self.nd, self.nd, self.nd))\n\n    def mesh_sampling_particle_field(self, *args, **kwargs):\n        pos = args[0]\n        npart = len(pos)\n        rng = np.random.default_rng()\n        return rng.random(npart)\n\n    def smooth(self, *args, **kwargs):\n        rng = np.random.default_rng()\n        tr = rng.random((self.nd, self.nd, self.nd))\n        if kwargs[\"method\"] == \"volume_weighted\":\n            return [tr]\n\n        return tr\n\n    def particle_operation(self, *args, **kwargs):\n        return None\n\n    def _read_data(self, field_name):\n        self.requested.append(field_name)\n        finfo = self.ds._get_field_info(field_name)\n        if finfo.sampling_type == \"particle\":\n            self.requested.append(field_name)\n            return np.ones(self.NumberOfParticles)\n        return YTArray(\n            defaultdict.__missing__(self, field_name),\n            units=finfo.units,\n            registry=self.ds.unit_registry,\n        )\n\n    def get_field_parameter(self, param, default=0.0):\n        if self.field_parameters and param in self.field_parameters:\n            return self.field_parameters[param]\n        self.requested_parameters.append(param)\n        if param in [\"center\", \"normal\"] or param.startswith(\"bulk\"):\n            if param == \"bulk_magnetic_field\":\n                if self.ds.unit_system.has_current_mks:\n                    unit = \"T\"\n                else:\n                    unit = \"G\"\n            else:\n                unit = fp_units[param]\n            rng = np.random.default_rng()\n            return self.ds.arr(rng.random(3) * 1e-2, unit)\n        elif param in [\"surface_height\"]:\n            return self.ds.quan(0.0, \"code_length\")\n        elif param in [\"axis\"]:\n            return 0\n        elif param.startswith(\"cp_\"):\n            ax = param[3]\n            rv = self.ds.arr((0.0, 0.0, 0.0), fp_units[param])\n            rv[\"xyz\".index(ax)] = 1.0\n            return rv\n        elif param.endswith(\"_hat\"):\n            ax = param[0]\n            rv = YTArray((0.0, 0.0, 0.0), fp_units[param])\n            rv[\"xyz\".index(ax)] = 1.0\n            return rv\n        elif param == \"fof_groups\":\n            return None\n        elif param == \"mu\":\n            return 1.0\n        else:\n            return default\n\n    _num_ghost_zones = 0\n    id = 1\n\n    def apply_units(self, arr, units):\n        return self.ds.arr(arr, units=units)\n\n    def has_field_parameter(self, param):\n        return param in self.field_parameters\n\n    @property\n    def fcoords(self):\n        fc = np.array(\n            np.mgrid[0 : 1 : self.nd * 1j, 0 : 1 : self.nd * 1j, 0 : 1 : self.nd * 1j]\n        )\n        if self.flat:\n            fc = fc.reshape(self.nd * self.nd * self.nd, 3)\n        else:\n            fc = fc.transpose()\n        return self.ds.arr(fc, units=\"code_length\")\n\n    @property\n    def fcoords_vertex(self):\n        if self.flat:\n            shape = (self.nd * self.nd * self.nd, 8, 3)\n        else:\n            shape = (self.nd, self.nd, self.nd, 8, 3)\n        rng = np.random.default_rng()\n        return self.ds.arr(rng.random(shape), units=\"code_length\")\n\n    @property\n    def icoords(self):\n        ic = np.mgrid[\n            0 : self.nd - 1 : self.nd * 1j,\n            0 : self.nd - 1 : self.nd * 1j,\n            0 : self.nd - 1 : self.nd * 1j,\n        ]\n        if self.flat:\n            return ic.reshape(self.nd * self.nd * self.nd, 3)\n        else:\n            return ic.transpose()\n\n    @property\n    def ires(self):\n        if self.flat:\n            shape = (self.nd**3,)\n        else:\n            shape = (self.nd, self.nd, self.nd)\n        return np.ones(shape, dtype=\"int64\")\n\n    @property\n    def fwidth(self):\n        if self.flat:\n            shape = (self.nd**3, 3)\n        else:\n            shape = (self.nd, self.nd, self.nd, 3)\n        fw = np.full(shape, 1 / self.nd, dtype=\"float64\")\n        return self.ds.arr(fw, units=\"code_length\")\n"
  },
  {
    "path": "yt/fields/field_exceptions.py",
    "content": "class ValidationException(Exception):\n    pass\n\n\nclass NeedsGridType(ValidationException):\n    def __init__(self, ghost_zones=0, fields=None):\n        self.ghost_zones = ghost_zones\n        self.fields = fields\n\n    def __str__(self):\n        s = \"s\" if self.ghost_zones != 1 else \"\"\n        return f\"fields {self.fields} require {self.ghost_zones} ghost zone{s}.\"\n\n\nclass NeedsOriginalGrid(NeedsGridType):\n    def __init__(self):\n        self.ghost_zones = 0\n\n\nclass NeedsDataField(ValidationException):\n    def __init__(self, missing_fields):\n        self.missing_fields = missing_fields\n\n    def __str__(self):\n        return f\"({self.missing_fields})\"\n\n\nclass NeedsProperty(ValidationException):\n    def __init__(self, missing_properties):\n        self.missing_properties = missing_properties\n\n    def __str__(self):\n        return f\"({self.missing_properties})\"\n\n\nclass NeedsParameter(ValidationException):\n    def __init__(self, missing_parameters):\n        self.missing_parameters = missing_parameters\n\n    def __str__(self):\n        return f\"({self.missing_parameters})\"\n\n\nclass NeedsConfiguration(ValidationException):\n    def __init__(self, parameter, value):\n        self.parameter = parameter\n        self.value = value\n\n    def __str__(self):\n        return f\"(Needs {self.parameter} = {self.value})\"\n\n\nclass FieldUnitsError(Exception):\n    pass\n"
  },
  {
    "path": "yt/fields/field_functions.py",
    "content": "import warnings\nfrom collections.abc import Callable\nfrom inspect import Parameter, signature\n\nimport numpy as np\n\nfrom yt.utilities.lib.misc_utilities import obtain_position_vector\n\n\ndef get_radius(data, field_prefix, ftype):\n    center = data.get_field_parameter(\"center\").to(\"code_length\")\n    DW = (data.ds.domain_right_edge - data.ds.domain_left_edge).to(\"code_length\")\n    # This is in code_length so it can be the destination for our r later.\n    radius2 = data.ds.arr(\n        np.zeros(data[ftype, field_prefix + \"x\"].shape, dtype=\"float64\"), \"code_length\"\n    )\n\n    r = np.empty_like(radius2, subok=False)\n    if any(data.ds.periodicity):\n        rdw = radius2.v\n    for i, ax in enumerate(\"xyz\"):\n        pos = data[ftype, f\"{field_prefix}{ax}\"]\n        if str(pos.units) != \"code_length\":\n            pos = pos.to(\"code_length\")\n        np.subtract(\n            pos.d,\n            center[i].d,\n            r,\n        )\n        if data.ds.periodicity[i]:\n            np.abs(r, r)\n            np.subtract(r, DW.d[i], rdw)\n            np.abs(rdw, rdw)\n            np.minimum(r, rdw, out=r)\n        np.multiply(r, r, r)\n        np.add(radius2.d, r, radius2.d)\n        if data.ds.dimensionality < i + 1:\n            break\n    # Using the views into the array is not changing units and as such keeps\n    # from having to do symbolic manipulations\n    np.sqrt(radius2.d, radius2.d)\n    # Alias it, just for clarity.\n    radius = radius2\n    return radius\n\n\ndef get_periodic_rvec(data):\n    coords = obtain_position_vector(data).d\n    if sum(data.ds.periodicity) == 0:\n        return coords\n    le = data.ds.domain_left_edge.in_units(\"code_length\").d\n    dw = data.ds.domain_width.in_units(\"code_length\").d\n    for i in range(coords.shape[0]):\n        if not data.ds.periodicity[i]:\n            continue\n        coords[i, ...] -= le[i]\n        # figure out which measure is less\n        mins = np.argmin(\n            [\n                np.abs(np.mod(coords[i, ...], dw[i])),\n                np.abs(np.mod(coords[i, ...], -dw[i])),\n            ],\n            axis=0,\n        )\n        temp_coords = np.mod(coords[i, ...], dw[i])\n\n        # Where second measure is better, updating temporary coords\n        ii = mins == 1\n        temp_coords[ii] = np.mod(coords[i, ...], -dw[i])[ii]\n\n        # Putting the temporary coords into the actual storage\n        coords[i, ...] = temp_coords\n\n        coords[i, ...] + le[i]\n\n    return coords\n\n\ndef validate_field_function(function: Callable) -> None:\n    \"\"\"\n    Inspect signature, raise a TypeError if invalid, return None otherwise.\n    \"\"\"\n    # This is a helper function to user-intended field registration methods\n    # (e.g. Dataset.add_field and yt.derived_field)\n    # it is not used in FieldInfoContainer.add_field to optimize performance\n    # (inspect.signature is quite expensive and we don't want to validate yt's\n    # internal code every time a dataset's fields are defined).\n\n    # lookup parameters that do not have default values\n    fparams = signature(function).parameters\n    if \"data\" not in fparams:\n        raise TypeError(\n            f\"Received field function {function} with invalid signature. \"\n            f\"Expected a 'data' parameter, found {tuple(p.name for p in fparams.values())!r}\"\n        )\n\n    nodefaults = tuple(p.name for p in fparams.values() if p.default is Parameter.empty)\n    if not set(nodefaults).issubset({\"field\", \"data\"}):\n        raise TypeError(\n            f\"Received field function {function} with invalid signature. \"\n            \"Expected parameters without default values to be 'data' \"\n            f\"(and optionally, 'field'). Found {nodefaults!r}\"\n        )\n\n    if any(\n        nokeyword := tuple(\n            name\n            for name in nodefaults\n            if fparams[name].kind\n            not in (Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD)\n        )\n    ):\n        raise TypeError(\n            f\"Received field function {function} with invalid signature. \"\n            f\"Expected {nokeyword} to be allowed as a keyword argument.\"\n        )\n\n    defaults = tuple(\n        name\n        for name in (\"data\", \"field\")\n        if name in fparams and fparams[name].default is not Parameter.empty\n    )\n    if defaults:\n        warnings.warn(\n            f\"Received field function {function} with default values for parameters {defaults!r}. \"\n            \"These default values will never be used. Drop them to avoid this warning.\",\n            UserWarning,\n            stacklevel=2,\n        )\n"
  },
  {
    "path": "yt/fields/field_info_container.py",
    "content": "import sys\nfrom collections import UserDict\nfrom collections.abc import Callable\n\nfrom unyt.exceptions import UnitConversionError\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._typing import FieldKey, FieldName, FieldType, KnownFieldsT\nfrom yt.config import ytcfg\nfrom yt.fields.field_exceptions import NeedsConfiguration\nfrom yt.funcs import mylog, obj_length, only_on_root\nfrom yt.geometry.api import Geometry\nfrom yt.units.dimensions import dimensionless  # type: ignore\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.utilities.exceptions import (\n    YTCoordinateNotImplemented,\n    YTDomainOverflow,\n    YTFieldNotFound,\n)\n\nfrom .derived_field import DeprecatedFieldFunc, DerivedField, NullFunc, TranslationFunc\nfrom .field_plugin_registry import FunctionName, field_plugins\nfrom .particle_fields import (\n    add_union_field,\n    particle_deposition_functions,\n    particle_scalar_functions,\n    particle_vector_functions,\n    sph_whitelist_fields,\n    standard_particle_fields,\n)\n\nif sys.version_info >= (3, 11):\n    from typing import assert_never\nelse:\n    from typing_extensions import assert_never\n\n\nclass FieldInfoContainer(UserDict):\n    \"\"\"\n    This is a generic field container.  It contains a list of potential derived\n    fields, all of which know how to act on a data object and return a value.\n    This object handles converting units as well as validating the availability\n    of a given field.\n\n    \"\"\"\n\n    fallback = None\n    known_other_fields: KnownFieldsT = ()\n    known_particle_fields: KnownFieldsT = ()\n    extra_union_fields: tuple[FieldKey, ...] = ()\n\n    def __init__(self, ds, field_list: list[FieldKey], slice_info=None):\n        super().__init__()\n        self._show_field_errors: list[Exception] = []\n        self.ds = ds\n        # Now we start setting things up.\n        self.field_list = field_list\n        self.slice_info = slice_info\n        self.field_aliases: dict[FieldKey, FieldKey] = {}\n        self.species_names: list[FieldName] = []\n        self.setup_fluid_aliases()\n\n    @property\n    def curvilinear(self) -> bool:\n        issue_deprecation_warning(\n            \"FieldInfoContainer.curvilinear attribute is deprecated. \"\n            \"Please compare the internal dataset geometry directly to known Geometry enum members instead. \",\n            stacklevel=3,\n            since=\"4.2\",\n        )\n        geometry = self.ds.geometry\n        return (\n            geometry is Geometry.POLAR\n            or geometry is Geometry.CYLINDRICAL\n            or geometry is Geometry.SPHERICAL\n        )\n\n    def setup_fluid_fields(self):\n        pass\n\n    def setup_fluid_index_fields(self):\n        # Now we get all our index types and set up aliases to them\n        if self.ds is None:\n            return\n        index_fields = {f for _, f in self if _ == \"index\"}\n        for ftype in self.ds.fluid_types:\n            if ftype in (\"index\", \"deposit\"):\n                continue\n            for f in index_fields:\n                if (ftype, f) in self:\n                    continue\n                self.alias((ftype, f), (\"index\", f))\n\n    def setup_particle_fields(self, ptype, ftype=\"gas\", num_neighbors=64):\n        skip_output_units = (\"code_length\",)\n        for f, (units, aliases, dn) in sorted(self.known_particle_fields):\n            units = self.ds.field_units.get((ptype, f), units)\n            output_units = units\n            if (\n                f in aliases or ptype not in self.ds.particle_types_raw\n            ) and units not in skip_output_units:\n                u = Unit(units, registry=self.ds.unit_registry)\n                if u.dimensions is not dimensionless:\n                    output_units = str(self.ds.unit_system[u.dimensions])\n            if (ptype, f) not in self.field_list:\n                continue\n            self.add_output_field(\n                (ptype, f),\n                sampling_type=\"particle\",\n                units=units,\n                display_name=dn,\n                output_units=output_units,\n            )\n            for alias in aliases:\n                self.alias((ptype, alias), (ptype, f), units=output_units)\n\n        # We'll either have particle_position or particle_position_[xyz]\n        if (ptype, \"particle_position\") in self.field_list or (\n            ptype,\n            \"particle_position\",\n        ) in self.field_aliases:\n            particle_scalar_functions(\n                ptype, \"particle_position\", \"particle_velocity\", self\n            )\n        else:\n            # We need to check to make sure that there's a \"known field\" that\n            # overlaps with one of the vector fields.  For instance, if we are\n            # in the Stream frontend, and we have a set of scalar position\n            # fields, they will overlap with -- and be overridden by -- the\n            # \"known\" vector field that the frontend creates.  So the easiest\n            # thing to do is to simply remove the on-disk field (which doesn't\n            # exist) and replace it with a derived field.\n            if (ptype, \"particle_position\") in self and self[\n                ptype, \"particle_position\"\n            ]._function == NullFunc:\n                self.pop((ptype, \"particle_position\"))\n            particle_vector_functions(\n                ptype,\n                [f\"particle_position_{ax}\" for ax in \"xyz\"],\n                [f\"particle_velocity_{ax}\" for ax in \"xyz\"],\n                self,\n            )\n        particle_deposition_functions(ptype, \"particle_position\", \"particle_mass\", self)\n        standard_particle_fields(self, ptype)\n        # Now we check for any leftover particle fields\n        for field in sorted(self.field_list):\n            if field in self:\n                continue\n            if not isinstance(field, tuple):\n                raise RuntimeError\n            if field[0] not in self.ds.particle_types:\n                continue\n            units = self.ds.field_units.get(field, None)\n            if units is None:\n                try:\n                    units = ytcfg.get(\"fields\", *field, \"units\")\n                except KeyError:\n                    units = \"\"\n            self.add_output_field(\n                field,\n                sampling_type=\"particle\",\n                units=units,\n            )\n        self.setup_smoothed_fields(ptype, num_neighbors=num_neighbors, ftype=ftype)\n\n    def setup_extra_union_fields(self, ptype=\"all\"):\n        if ptype != \"all\":\n            raise RuntimeError(\n                \"setup_extra_union_fields is currently\"\n                + 'only enabled for particle type \"all\".'\n            )\n        for units, field in self.extra_union_fields:\n            add_union_field(self, ptype, field, units)\n\n    def setup_smoothed_fields(self, ptype, num_neighbors=64, ftype=\"gas\"):\n        # We can in principle compute this, but it is not yet implemented.\n        if (ptype, \"density\") not in self or not hasattr(self.ds, \"_sph_ptypes\"):\n            return\n        new_aliases = []\n        for ptype2, alias_name in list(self):\n            if ptype2 != ptype:\n                continue\n            if alias_name not in sph_whitelist_fields:\n                if alias_name.startswith(\"particle_\"):\n                    pass\n                else:\n                    continue\n            uni_alias_name = alias_name\n            if \"particle_position_\" in alias_name:\n                uni_alias_name = alias_name.replace(\"particle_position_\", \"\")\n            elif \"particle_\" in alias_name:\n                uni_alias_name = alias_name.replace(\"particle_\", \"\")\n            new_aliases.append(\n                (\n                    (ftype, uni_alias_name),\n                    (ptype, alias_name),\n                )\n            )\n            if \"particle_position_\" in alias_name:\n                new_aliases.append(\n                    (\n                        (ftype, alias_name),\n                        (ptype, alias_name),\n                    )\n                )\n            new_aliases.append(\n                (\n                    (ptype, uni_alias_name),\n                    (ptype, alias_name),\n                )\n            )\n            for alias, source in new_aliases:\n                self.alias(alias, source)\n        self.alias((ftype, \"particle_position\"), (ptype, \"particle_position\"))\n        self.alias((ftype, \"particle_mass\"), (ptype, \"particle_mass\"))\n\n    # Collect the names for all aliases if geometry is curvilinear\n    def get_aliases_gallery(self) -> list[FieldName]:\n        aliases_gallery: list[FieldName] = []\n        known_other_fields = dict(self.known_other_fields)\n\n        if self.ds is None:\n            return aliases_gallery\n\n        match self.ds.geometry:\n            case Geometry.POLAR | Geometry.CYLINDRICAL | Geometry.SPHERICAL:\n                aliases: list[FieldName]\n                for field in sorted(self.field_list):\n                    if field[0] in self.ds.particle_types:\n                        continue\n                    args = known_other_fields.get(field[1], (\"\", [], None))\n                    units, aliases, display_name = args\n                    aliases_gallery.extend(aliases)\n            case (\n                Geometry.CARTESIAN\n                | Geometry.GEOGRAPHIC\n                | Geometry.INTERNAL_GEOGRAPHIC\n                | Geometry.SPECTRAL_CUBE\n            ):\n                # nothing to do\n                pass\n            case _:\n                assert_never(self.ds.geometry)\n\n        return aliases_gallery\n\n    def setup_fluid_aliases(self, ftype: FieldType = \"gas\") -> None:\n        known_other_fields = dict(self.known_other_fields)\n\n        # For non-Cartesian geometry, convert alias of vector fields to\n        # curvilinear coordinates\n        aliases_gallery = self.get_aliases_gallery()\n\n        for field in sorted(self.field_list):\n            if not isinstance(field, tuple) or len(field) != 2:\n                raise RuntimeError\n            if field[0] in self.ds.particle_types:\n                continue\n            args = known_other_fields.get(field[1], None)\n            if args is not None:\n                units, aliases, display_name = args\n            else:\n                try:\n                    node = ytcfg.get(\"fields\", *field).as_dict()\n                except KeyError:\n                    node = {}\n\n                units = node.get(\"units\", \"\")\n                aliases = node.get(\"aliases\", [])\n                display_name = node.get(\"display_name\", None)\n\n            # We allow field_units to override this.  First we check if the\n            # field *name* is in there, then the field *tuple*.\n            units = self.ds.field_units.get(field[1], units)\n            units = self.ds.field_units.get(field, units)\n            self.add_output_field(\n                field, sampling_type=\"cell\", units=units, display_name=display_name\n            )\n            axis_names = self.ds.coordinates.axis_order\n            for alias in aliases:\n                match self.ds.geometry:\n                    case Geometry.POLAR | Geometry.CYLINDRICAL | Geometry.SPHERICAL:\n                        KNOWN_SUFFIXES = (\"x\", \"y\", \"z\")\n                        stem, _, suffix = alias.rpartition(\"_\")\n                        if suffix in KNOWN_SUFFIXES and all(\n                            f\"{stem}_{s}\" in aliases_gallery for s in KNOWN_SUFFIXES\n                        ):\n                            new_suffix = axis_names[KNOWN_SUFFIXES.index(suffix)]\n                            alias = f\"{stem}_{new_suffix}\"\n                    case (\n                        Geometry.CARTESIAN\n                        | Geometry.GEOGRAPHIC\n                        | Geometry.INTERNAL_GEOGRAPHIC\n                        | Geometry.SPECTRAL_CUBE\n                    ):\n                        # nothing to do\n                        pass\n                    case _:\n                        assert_never(self.ds.geometry)\n                self.alias((ftype, alias), field)\n\n    @staticmethod\n    def _sanitize_sampling_type(sampling_type: str) -> str:\n        \"\"\"Detect conflicts between deprecated and new parameters to specify the\n        sampling type in a new field.\n\n        This is a helper function to add_field methods.\n\n        Parameters\n        ----------\n        sampling_type : str\n            One of \"cell\", \"particle\" or \"local\" (case insensitive)\n\n        Raises\n        ------\n        ValueError\n            For unsupported values in sampling_type\n        \"\"\"\n        if not isinstance(sampling_type, str):\n            raise TypeError(\"sampling_type should be a string.\")\n\n        sampling_type = sampling_type.lower()\n        acceptable_samplings = (\"cell\", \"particle\", \"local\")\n        if sampling_type not in acceptable_samplings:\n            raise ValueError(\n                f\"Received invalid sampling type {sampling_type!r}. \"\n                f\"Expected any of {acceptable_samplings}\"\n            )\n        return sampling_type\n\n    def add_field(\n        self,\n        name: FieldKey,\n        function: Callable,\n        sampling_type: str,\n        *,\n        alias: DerivedField | None = None,\n        force_override: bool = False,\n        **kwargs,\n    ) -> None:\n        \"\"\"\n        Add a new field, along with supplemental metadata, to the list of\n        available fields.  This respects a number of arguments, all of which\n        are passed on to the constructor for\n        :class:`~yt.data_objects.api.DerivedField`.\n\n        Parameters\n        ----------\n\n        name : tuple[str, str]\n           field (or particle) type, field name\n        function : callable\n           A function handle that defines the field.  Should accept\n           arguments (data)\n        sampling_type: str\n           \"cell\" or \"particle\" or \"local\"\n        force_override: bool\n           If False (default), an error will be raised if a field of the same name already exists.\n        alias: DerivedField (optional):\n           existing field to be aliased\n        units : str\n           A plain text string encoding the unit.  Powers must be in\n           python syntax (** instead of ^). If set to \"auto\" the units\n           will be inferred from the return value of the field function.\n        take_log : bool\n           Describes whether the field should be logged\n        validators : list\n           A list of :class:`FieldValidator` objects\n        vector_field : bool\n           Describes the dimensionality of the field.  Currently unused.\n        display_name : str\n           A name used in the plots\n\n        \"\"\"\n        # Handle the case where the field has already been added.\n        if not force_override and name in self:\n            return\n\n        kwargs.setdefault(\"ds\", self.ds)\n\n        sampling_type = self._sanitize_sampling_type(sampling_type)\n\n        if (\n            not isinstance(name, str)\n            and obj_length(name) == 2\n            and all(isinstance(e, str) for e in name)\n        ):\n            self[name] = DerivedField(\n                name, sampling_type, function, alias=alias, **kwargs\n            )\n        else:\n            raise ValueError(f\"Expected name to be a tuple[str, str], got {name}\")\n\n    def load_all_plugins(self, ftype: str | None = \"gas\") -> None:\n        if ftype is None:\n            return\n        mylog.debug(\"Loading field plugins for field type: %s.\", ftype)\n        loaded = []\n        for n in sorted(field_plugins):\n            loaded += self.load_plugin(n, ftype)\n            only_on_root(mylog.debug, \"Loaded %s (%s new fields)\", n, len(loaded))\n        self.find_dependencies(loaded)\n\n    def load_plugin(\n        self,\n        plugin_name: FunctionName,\n        ftype: FieldType = \"gas\",\n        skip_check: bool = False,\n    ):\n        f = field_plugins[plugin_name]\n        orig = set(self.items())\n        f(self, ftype, slice_info=self.slice_info)\n        loaded = [n for n, v in set(self.items()).difference(orig)]\n        return loaded\n\n    def find_dependencies(self, loaded):\n        deps, unavailable = self.check_derived_fields(loaded)\n        self.ds.field_dependencies.update(deps)\n        # Note we may have duplicated\n        dfl = set(self.ds.derived_field_list).union(deps.keys())\n        self.ds.derived_field_list = sorted(dfl)\n        return loaded, unavailable\n\n    def add_output_field(self, name, sampling_type, **kwargs):\n        if name[1] == \"density\":\n            if name in self:\n                # this should not happen, but it does\n                # it'd be best to raise an error here but\n                # it may take a while to cleanup internal issues\n                return\n        kwargs.setdefault(\"ds\", self.ds)\n        self[name] = DerivedField(name, sampling_type, NullFunc, **kwargs)\n\n    def alias(\n        self,\n        alias_name: FieldKey,\n        original_name: FieldKey,\n        units: str | None = None,\n        deprecate: tuple[str, str | None] | None = None,\n    ):\n        \"\"\"\n        Alias one field to another field.\n\n        Parameters\n        ----------\n        alias_name : tuple[str, str]\n            The new field name.\n        original_name : tuple[str, str]\n            The field to be aliased.\n        units : str\n           A plain text string encoding the unit.  Powers must be in\n           python syntax (** instead of ^). If set to \"auto\" the units\n           will be inferred from the return value of the field function.\n        deprecate : tuple[str, str | None] | None\n            If this is set, then the tuple contains two string version\n            numbers: the first marking the version when the field was\n            deprecated, and the second marking when the field will be\n            removed.\n        \"\"\"\n        if original_name not in self:\n            return\n        if units is None:\n            # We default to CGS here, but in principle, this can be pluggable\n            # as well.\n\n            # self[original_name].units may be set to `None` at this point\n            # to signal that units should be autoset later\n            oru = self[original_name].units\n            if oru is None:\n                units = None\n            else:\n                u = Unit(oru, registry=self.ds.unit_registry)\n                if u.dimensions is not dimensionless:\n                    units = str(self.ds.unit_system[u.dimensions])\n                else:\n                    units = oru\n\n        self.field_aliases[alias_name] = original_name\n        function = TranslationFunc(original_name)\n        if deprecate is not None:\n            self.add_deprecated_field(\n                alias_name,\n                function=function,\n                sampling_type=self[original_name].sampling_type,\n                display_name=self[original_name].display_name,\n                units=units,\n                since=deprecate[0],\n                removal=deprecate[1],\n                ret_name=original_name,\n            )\n        else:\n            self.add_field(\n                alias_name,\n                function=function,\n                sampling_type=self[original_name].sampling_type,\n                display_name=self[original_name].display_name,\n                units=units,\n                alias=self[original_name],\n            )\n\n    def add_deprecated_field(\n        self,\n        name,\n        function,\n        sampling_type,\n        since,\n        removal=None,\n        ret_name=None,\n        **kwargs,\n    ):\n        \"\"\"\n        Add a new field which is deprecated, along with supplemental metadata,\n        to the list of available fields.  This respects a number of arguments,\n        all of which are passed on to the constructor for\n        :class:`~yt.data_objects.api.DerivedField`.\n\n        Parameters\n        ----------\n        name : str\n           is the name of the field.\n        function : callable\n           A function handle that defines the field.  Should accept\n           arguments (data)\n        sampling_type : str\n           \"cell\" or \"particle\" or \"local\"\n        since : str\n            The version string marking when this field was deprecated.\n        removal : str\n            The version string marking when this field will be removed.\n        ret_name : str\n            The name of the field which will actually be returned, used\n            only by :meth:`~yt.fields.field_info_container.FieldInfoContainer.alias`.\n        units : str\n           A plain text string encoding the unit.  Powers must be in\n           python syntax (** instead of ^). If set to \"auto\" the units\n           will be inferred from the return value of the field function.\n        take_log : bool\n           Describes whether the field should be logged\n        validators : list\n           A list of :class:`FieldValidator` objects\n        vector_field : bool\n           Describes the dimensionality of the field.  Currently unused.\n        display_name : str\n           A name used in the plots\n        \"\"\"\n        if ret_name is None:\n            ret_name = name\n        self.add_field(\n            name,\n            function=DeprecatedFieldFunc(ret_name, function, since, removal),\n            sampling_type=sampling_type,\n            **kwargs,\n        )\n\n    def has_key(self, key):\n        # This gets used a lot\n        if key in self:\n            return True\n        if self.fallback is None:\n            return False\n        return key in self.fallback\n\n    def __missing__(self, key):\n        if self.fallback is None:\n            raise KeyError(f\"No field named {key}\")\n        return self.fallback[key]\n\n    @classmethod\n    def create_with_fallback(cls, fallback, name=\"\"):\n        obj = cls()\n        obj.fallback = fallback\n        obj.name = name\n        return obj\n\n    def __contains__(self, key):\n        if super().__contains__(key):\n            return True\n        if self.fallback is None:\n            return False\n        return key in self.fallback\n\n    def __iter__(self):\n        yield from super().__iter__()\n        if self.fallback is not None:\n            yield from self.fallback\n\n    def keys(self):\n        keys = super().keys()\n        if self.fallback:\n            keys += list(self.fallback.keys())\n        return keys\n\n    def check_derived_fields(self, fields_to_check=None):\n        # The following exceptions lists were obtained by expanding an\n        # all-catching `except Exception`.\n        # We define\n        # - a blacklist (exceptions that we know should be caught)\n        # - a whitelist (exceptions that should be handled)\n        # - a greylist (exceptions that may be covering bugs but should be checked)\n        # See https://github.com/yt-project/yt/issues/2853\n        # in the long run, the greylist should be removed\n        blacklist = ()\n        whitelist = (NotImplementedError,)\n        greylist = (\n            YTFieldNotFound,\n            YTDomainOverflow,\n            YTCoordinateNotImplemented,\n            NeedsConfiguration,\n            TypeError,\n            ValueError,\n            IndexError,\n            AttributeError,\n            KeyError,\n            # code smells -> those are very likely bugs\n            UnitConversionError,  # solved in GH PR 2897 ?\n            # RecursionError is clearly a bug, and was already solved once\n            # in GH PR 2851\n            RecursionError,\n        )\n\n        deps = {}\n        unavailable = []\n        fields_to_check = fields_to_check or list(self.keys())\n        for field in fields_to_check:\n            fi = self[field]\n            try:\n                # fd: field detector\n                fd = fi.get_dependencies(ds=self.ds)\n            except blacklist as err:\n                print(f\"{err.__class__} raised for field {field}\")\n                raise SystemExit(1) from err\n            except (*whitelist, *greylist) as e:\n                if field in self._show_field_errors:\n                    raise\n                if not isinstance(e, YTFieldNotFound):\n                    # if we're doing field tests, raise an error\n                    # see yt.fields.tests.test_fields\n                    if hasattr(self.ds, \"_field_test_dataset\"):\n                        raise\n                    mylog.debug(\n                        \"Raises %s during field %s detection.\", str(type(e)), field\n                    )\n                self.pop(field)\n                continue\n            # This next bit checks that we can't somehow generate everything.\n            # We also manually update the 'requested' attribute\n            missing = not all(f in self.field_list for f in fd.requested)\n            if missing:\n                self.pop(field)\n                unavailable.append(field)\n                continue\n            fd.requested = set(fd.requested)\n            deps[field] = fd\n            mylog.debug(\"Succeeded with %s (needs %s)\", field, fd.requested)\n\n        # now populate the derived field list with results\n        # this violates isolation principles and should be refactored\n        dfl = set(self.ds.derived_field_list).union(deps.keys())\n        dfl = sorted(dfl)\n\n        if not hasattr(self.ds.index, \"meshes\"):\n            # the meshes attribute characterizes an unstructured-mesh data structure\n\n            # ideally this filtering should not be required\n            # and this could maybe be handled in fi.get_dependencies\n            # but it's a lot easier to do here\n\n            filtered_dfl = []\n            for field in dfl:\n                try:\n                    ftype, fname = field\n                    if \"vertex\" in fname:\n                        continue\n                except ValueError:\n                    # in very rare cases, there can a field represented by a single\n                    # string, like \"emissivity\"\n                    # this try block _should_ be removed and the error fixed upstream\n                    # for reference, a test that would break is\n                    # yt/data_objects/tests/test_fluxes.py::ExporterTests\n                    pass\n                filtered_dfl.append(field)\n            dfl = filtered_dfl\n\n        self.ds.derived_field_list = dfl\n        self._set_linear_fields()\n        return deps, unavailable\n\n    def _set_linear_fields(self):\n        \"\"\"\n        Sets which fields use linear as their default scaling in Profiles and\n        PhasePlots. Default for all fields is set to log, so this sets which\n        are linear.  For now, set linear to geometric fields: position and\n        velocity coordinates.\n        \"\"\"\n        non_log_prefixes = (\"\", \"velocity_\", \"particle_position_\", \"particle_velocity_\")\n        coords = (\"x\", \"y\", \"z\")\n        non_log_fields = [\n            prefix + coord for prefix in non_log_prefixes for coord in coords\n        ]\n        for field in self.ds.derived_field_list:\n            if field[1] in non_log_fields:\n                self[field].take_log = False\n"
  },
  {
    "path": "yt/fields/field_plugin_registry.py",
    "content": "from collections.abc import Callable\n\nFunctionName = str\nFieldPluginMap = dict[FunctionName, Callable]\nfield_plugins: FieldPluginMap = {}\n\n\ndef register_field_plugin(func: Callable) -> Callable:\n    name = func.__name__\n    if name.startswith(\"setup_\"):\n        name = name[len(\"setup_\") :]\n    if name.endswith(\"_fields\"):\n        name = name[: -len(\"_fields\")]\n    field_plugins[name] = func\n    # And, we return it, too\n    return func\n"
  },
  {
    "path": "yt/fields/field_type_container.py",
    "content": "\"\"\"\nA proxy object for field descriptors, usually living as ds.fields.\n\"\"\"\n\nimport inspect\nimport textwrap\nimport weakref\nfrom functools import cached_property\n\nfrom yt._maintenance.ipython_compat import IPYWIDGETS_ENABLED\nfrom yt.fields.derived_field import DerivedField, DerivedFieldCombination\n\n\ndef _fill_values(values):\n    value = (\n        '<div class=\"rendered_html jp-RenderedHTMLCommon\">'\n        + \"<table><thead><tr><th>Name</th><th>Type</th>\"\n        + \"<th>Value</th></tr></thead><tr><td>\"\n        + \"</td></tr><tr><td>\".join(\n            f\"{v}</td><td>{type(values[v]).__name__}</td><td>{str(values[v])}\"\n            for v in sorted(values)\n        )\n        + \"</td></tr></table></div>\"\n    )\n    return value\n\n\nclass FieldTypeContainer:\n    def __init__(self, ds):\n        self.ds = weakref.proxy(ds)\n\n    def __getattr__(self, attr):\n        ds = self.__getattribute__(\"ds\")\n        fnc = FieldNameContainer(ds, attr)\n        if len(dir(fnc)) == 0:\n            return self.__getattribute__(attr)\n        return fnc\n\n    @cached_property\n    def field_types(self):\n        return {t for t, n in self.ds.field_info}\n\n    def __dir__(self):\n        return list(self.field_types)\n\n    def __iter__(self):\n        for ft in self.field_types:\n            fnc = FieldNameContainer(self.ds, ft)\n            if len(dir(fnc)) == 0:\n                yield self.__getattribute__(ft)\n            else:\n                yield fnc\n\n    def __contains__(self, obj):\n        ob = None\n        if isinstance(obj, FieldNameContainer):\n            ob = obj.field_type\n        elif isinstance(obj, str):\n            ob = obj\n\n        return ob in self.field_types\n\n    if IPYWIDGETS_ENABLED:\n\n        def _ipython_display_(self):\n            import ipywidgets\n            from IPython.display import display\n\n            fnames = []\n            children = []\n            for ftype in sorted(self.field_types):\n                fnc = getattr(self, ftype)\n                children.append(ipywidgets.Output())\n                with children[-1]:\n                    display(fnc)\n                fnames.append(ftype)\n            tabs = ipywidgets.Tab(children=children)\n            for i, n in enumerate(fnames):\n                tabs.set_title(i, n)\n            display(tabs)\n\n\nclass FieldNameContainer:\n    def __init__(self, ds, field_type):\n        self.ds = ds\n        self.field_type = field_type\n\n    def __getattr__(self, attr):\n        ft = self.__getattribute__(\"field_type\")\n        ds = self.__getattribute__(\"ds\")\n        if (ft, attr) not in ds.field_info:\n            return self.__getattribute__(attr)\n        return ds.field_info[ft, attr]\n\n    def __setattr__(self, attr, value):\n        if isinstance(value, DerivedFieldCombination):\n            self.ds.add_field((self.field_type, attr), value, units=\"auto\")\n        else:\n            super().__setattr__(attr, value)\n\n    def __dir__(self):\n        return [n for t, n in self.ds.field_info if t == self.field_type]\n\n    def __iter__(self):\n        for t, n in self.ds.field_info:\n            if t == self.field_type:\n                yield self.ds.field_info[t, n]\n\n    def __contains__(self, obj):\n        if isinstance(obj, DerivedField):\n            if self.field_type == obj.name[0] and obj.name in self.ds.field_info:\n                # e.g. from a completely different dataset\n                if self.ds.field_info[obj.name] is not obj:\n                    return False\n                return True\n        elif isinstance(obj, tuple):\n            if self.field_type == obj[0] and obj in self.ds.field_info:\n                return True\n        elif isinstance(obj, str):\n            if (self.field_type, obj) in self.ds.field_info:\n                return True\n        return False\n\n    if IPYWIDGETS_ENABLED:\n        # for discussion of this class-level conditional: https://github.com/yt-project/yt/pull/4941\n\n        def _ipython_display_(self):\n            import ipywidgets\n            from IPython.display import Markdown, display\n\n            names = dir(self)\n            names.sort()\n\n            def change_field(_ftype, _box, _var_window):\n                def _change_field(event):\n                    fobj = getattr(_ftype, event[\"new\"])\n                    _box.clear_output()\n                    with _box:\n                        display(\n                            Markdown(\n                                data=\"```python\\n\"\n                                + textwrap.dedent(fobj.get_source())\n                                + \"\\n```\"\n                            )\n                        )\n                    values = inspect.getclosurevars(fobj._function).nonlocals\n                    _var_window.value = _fill_values(values)\n\n                return _change_field\n\n            flist = ipywidgets.Select(\n                options=names, layout=ipywidgets.Layout(height=\"95%\")\n            )\n            source = ipywidgets.Output(\n                layout=ipywidgets.Layout(width=\"100%\", height=\"9em\")\n            )\n            var_window = ipywidgets.HTML(value=\"Empty\")\n            var_box = ipywidgets.Box(\n                layout=ipywidgets.Layout(\n                    width=\"100%\", height=\"100%\", overflow_y=\"scroll\"\n                )\n            )\n            var_box.children = [var_window]\n            ftype_tabs = ipywidgets.Tab(\n                children=[source, var_box],\n                layout=ipywidgets.Layout(flex=\"2 1 auto\", width=\"auto\", height=\"95%\"),\n            )\n            ftype_tabs.set_title(0, \"Source\")\n            ftype_tabs.set_title(1, \"Variables\")\n            flist.observe(change_field(self, source, var_window), \"value\")\n            display(\n                ipywidgets.HBox(\n                    [flist, ftype_tabs], layout=ipywidgets.Layout(height=\"14em\")\n                )\n            )\n"
  },
  {
    "path": "yt/fields/fluid_fields.py",
    "content": "import numpy as np\n\nfrom yt.units.dimensions import current_mks\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.utilities.chemical_formulas import compute_mu\nfrom yt.utilities.lib.misc_utilities import obtain_relative_velocity_vector\n\nfrom .derived_field import ValidateParameter, ValidateSpatial\nfrom .field_plugin_registry import register_field_plugin\nfrom .vector_operations import (\n    create_averaged_field,\n    create_magnitude_field,\n    create_vector_fields,\n)\n\n\n@register_field_plugin\ndef setup_fluid_fields(registry, ftype=\"gas\", slice_info=None):\n    pc = registry.ds.units.physical_constants\n    # slice_info would be the left, the right, and the factor.\n    # For example, with the old Enzo-ZEUS fields, this would be:\n    # slice(None, -2, None)\n    # slice(1, -1, None)\n    # 1.0\n    # Otherwise, we default to a centered difference.\n    if slice_info is None:\n        sl_left = slice(None, -2, None)\n        sl_right = slice(2, None, None)\n        div_fac = 2.0\n    else:\n        sl_left, sl_right, div_fac = slice_info\n\n    unit_system = registry.ds.unit_system\n\n    if unit_system.base_units[current_mks] is None:\n        mag_units = \"magnetic_field_cgs\"\n    else:\n        mag_units = \"magnetic_field_mks\"\n\n    create_vector_fields(\n        registry, \"velocity\", unit_system[\"velocity\"], ftype, slice_info\n    )\n\n    create_vector_fields(\n        registry, \"magnetic_field\", unit_system[mag_units], ftype, slice_info\n    )\n\n    def _cell_mass(data):\n        return data[ftype, \"density\"] * data[ftype, \"cell_volume\"]\n\n    registry.add_field(\n        (ftype, \"cell_mass\"),\n        sampling_type=\"cell\",\n        function=_cell_mass,\n        units=unit_system[\"mass\"],\n    )\n    registry.alias((ftype, \"mass\"), (ftype, \"cell_mass\"))\n\n    # momentum\n    def momentum_xyz(v):\n        def _momm(data):\n            return data[\"gas\", \"mass\"] * data[\"gas\", f\"velocity_{v}\"]\n\n        def _momd(data):\n            return data[\"gas\", \"density\"] * data[\"gas\", f\"velocity_{v}\"]\n\n        return _momm, _momd\n\n    for v in registry.ds.coordinates.axis_order:\n        _momm, _momd = momentum_xyz(v)\n        registry.add_field(\n            (\"gas\", f\"momentum_{v}\"),\n            sampling_type=\"local\",\n            function=_momm,\n            units=unit_system[\"momentum\"],\n        )\n        registry.add_field(\n            (\"gas\", f\"momentum_density_{v}\"),\n            sampling_type=\"local\",\n            function=_momd,\n            units=unit_system[\"density\"] * unit_system[\"velocity\"],\n        )\n\n    def _sound_speed(data):\n        tr = data.ds.gamma * data[ftype, \"pressure\"] / data[ftype, \"density\"]\n        return np.sqrt(tr)\n\n    registry.add_field(\n        (ftype, \"sound_speed\"),\n        sampling_type=\"local\",\n        function=_sound_speed,\n        units=unit_system[\"velocity\"],\n    )\n\n    def _radial_mach_number(data):\n        \"\"\"Radial component of M{|v|/c_sound}\"\"\"\n        tr = data[ftype, \"radial_velocity\"] / data[ftype, \"sound_speed\"]\n        return np.abs(tr)\n\n    registry.add_field(\n        (ftype, \"radial_mach_number\"),\n        sampling_type=\"local\",\n        function=_radial_mach_number,\n        units=\"\",\n    )\n\n    def _kinetic_energy_density(data):\n        v = obtain_relative_velocity_vector(data)\n        return 0.5 * data[ftype, \"density\"] * (v**2).sum(axis=0)\n\n    registry.add_field(\n        (ftype, \"kinetic_energy_density\"),\n        sampling_type=\"local\",\n        function=_kinetic_energy_density,\n        units=unit_system[\"pressure\"],\n        validators=[ValidateParameter(\"bulk_velocity\")],\n    )\n\n    def _mach_number(data):\n        \"\"\"M{|v|/c_sound}\"\"\"\n        return data[ftype, \"velocity_magnitude\"] / data[ftype, \"sound_speed\"]\n\n    registry.add_field(\n        (ftype, \"mach_number\"), sampling_type=\"local\", function=_mach_number, units=\"\"\n    )\n\n    def _courant_time_step(data):\n        t1 = data[ftype, \"dx\"] / (\n            data[ftype, \"sound_speed\"] + np.abs(data[ftype, \"velocity_x\"])\n        )\n        t2 = data[ftype, \"dy\"] / (\n            data[ftype, \"sound_speed\"] + np.abs(data[ftype, \"velocity_y\"])\n        )\n        t3 = data[ftype, \"dz\"] / (\n            data[ftype, \"sound_speed\"] + np.abs(data[ftype, \"velocity_z\"])\n        )\n        tr = np.minimum(np.minimum(t1, t2), t3)\n        return tr\n\n    registry.add_field(\n        (ftype, \"courant_time_step\"),\n        sampling_type=\"cell\",\n        function=_courant_time_step,\n        units=unit_system[\"time\"],\n    )\n\n    def _pressure(data):\n        \"\"\"M{(Gamma-1.0)*rho*E}\"\"\"\n        tr = (data.ds.gamma - 1.0) * (\n            data[ftype, \"density\"] * data[ftype, \"specific_thermal_energy\"]\n        )\n        return tr\n\n    registry.add_field(\n        (ftype, \"pressure\"),\n        sampling_type=\"local\",\n        function=_pressure,\n        units=unit_system[\"pressure\"],\n    )\n\n    def _kT(data):\n        return (pc.kboltz * data[ftype, \"temperature\"]).in_units(\"keV\")\n\n    registry.add_field(\n        (ftype, \"kT\"),\n        sampling_type=\"local\",\n        function=_kT,\n        units=\"keV\",\n        display_name=\"Temperature\",\n    )\n\n    def _metallicity(data):\n        return data[ftype, \"metal_density\"] / data[ftype, \"density\"]\n\n    registry.add_field(\n        (ftype, \"metallicity\"),\n        sampling_type=\"local\",\n        function=_metallicity,\n        units=\"Zsun\",\n    )\n\n    def _metal_mass(data):\n        Z = data[ftype, \"metallicity\"].to(\"dimensionless\")\n        return Z * data[ftype, \"mass\"]\n\n    registry.add_field(\n        (ftype, \"metal_mass\"),\n        sampling_type=\"local\",\n        function=_metal_mass,\n        units=unit_system[\"mass\"],\n    )\n\n    if len(registry.ds.field_info.species_names) > 0:\n\n        def _number_density(data):\n            field_data = np.zeros_like(\n                data[\"gas\", f\"{data.ds.field_info.species_names[0]}_number_density\"]\n            )\n            for species in data.ds.field_info.species_names:\n                field_data += data[\"gas\", f\"{species}_number_density\"]\n            return field_data\n\n    else:\n\n        def _number_density(data):\n            mu = getattr(data.ds, \"mu\", compute_mu(data.ds.default_species_fields))\n            return data[ftype, \"density\"] / (pc.mh * mu)\n\n    registry.add_field(\n        (ftype, \"number_density\"),\n        sampling_type=\"local\",\n        function=_number_density,\n        units=unit_system[\"number_density\"],\n    )\n\n    def _mean_molecular_weight(data):\n        return data[ftype, \"density\"] / (pc.mh * data[ftype, \"number_density\"])\n\n    registry.add_field(\n        (ftype, \"mean_molecular_weight\"),\n        sampling_type=\"local\",\n        function=_mean_molecular_weight,\n        units=\"\",\n    )\n\n    setup_gradient_fields(\n        registry, (ftype, \"pressure\"), unit_system[\"pressure\"], slice_info\n    )\n\n    setup_gradient_fields(\n        registry, (ftype, \"density\"), unit_system[\"density\"], slice_info\n    )\n\n    create_averaged_field(\n        registry,\n        \"density\",\n        unit_system[\"density\"],\n        ftype=ftype,\n        slice_info=slice_info,\n        weight=\"mass\",\n    )\n\n\ndef setup_gradient_fields(registry, grad_field, field_units, slice_info=None):\n    assert isinstance(grad_field, tuple)\n    ftype, fname = grad_field\n    if slice_info is None:\n        sl_left = slice(None, -2, None)\n        sl_right = slice(2, None, None)\n        div_fac = 2.0\n    else:\n        sl_left, sl_right, div_fac = slice_info\n    slice_3d = (slice(1, -1), slice(1, -1), slice(1, -1))\n\n    def grad_func(axi, ax):\n        slice_3dl = slice_3d[:axi] + (sl_left,) + slice_3d[axi + 1 :]\n        slice_3dr = slice_3d[:axi] + (sl_right,) + slice_3d[axi + 1 :]\n\n        def func(data):\n            block_order = getattr(data, \"_block_order\", \"C\")\n            if block_order == \"F\":\n                # Fortran-ordering: we need to swap axes here and\n                # reswap below\n                field_data = data[grad_field].swapaxes(0, 2)\n            else:\n                field_data = data[grad_field]\n            dx = div_fac * data[ftype, f\"d{ax}\"]\n            if ax == \"theta\":\n                dx *= data[ftype, \"r\"]\n            if ax == \"phi\":\n                dx *= data[ftype, \"r\"] * np.sin(data[ftype, \"theta\"])\n            f = field_data[slice_3dr] / dx[slice_3d]\n            f -= field_data[slice_3dl] / dx[slice_3d]\n            new_field = np.zeros_like(data[grad_field], dtype=np.float64)\n            new_field = data.ds.arr(new_field, field_data.units / dx.units)\n            new_field[slice_3d] = f\n\n            if block_order == \"F\":\n                new_field = new_field.swapaxes(0, 2)\n\n            return new_field\n\n        return func\n\n    field_units = Unit(field_units, registry=registry.ds.unit_registry)\n    grad_units = field_units / registry.ds.unit_system[\"length\"]\n\n    for axi, ax in enumerate(registry.ds.coordinates.axis_order):\n        f = grad_func(axi, ax)\n        registry.add_field(\n            (ftype, f\"{fname}_gradient_{ax}\"),\n            sampling_type=\"local\",\n            function=f,\n            validators=[ValidateSpatial(1, [grad_field])],\n            units=grad_units,\n        )\n\n    create_magnitude_field(\n        registry,\n        f\"{fname}_gradient\",\n        grad_units,\n        ftype=ftype,\n        validators=[ValidateSpatial(1, [grad_field])],\n    )\n"
  },
  {
    "path": "yt/fields/fluid_vector_fields.py",
    "content": "import numpy as np\n\nfrom yt._typing import FieldType\nfrom yt.fields.derived_field import ValidateParameter, ValidateSpatial\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.funcs import just_one\nfrom yt.geometry.api import Geometry\nfrom yt.utilities.exceptions import YTDimensionalityError, YTFieldNotFound\n\nfrom .field_plugin_registry import register_field_plugin\nfrom .vector_operations import create_magnitude_field, create_squared_field\n\n\n@register_field_plugin\ndef setup_fluid_vector_fields(\n    registry: FieldInfoContainer, ftype: FieldType = \"gas\", slice_info=None\n) -> None:\n    # Current implementation for gradient is not valid for curvilinear geometries\n    geometry: Geometry = registry.ds.geometry\n    if geometry is not Geometry.CARTESIAN:\n        return\n\n    unit_system = registry.ds.unit_system\n    # slice_info would be the left, the right, and the factor.\n    # For example, with the old Enzo-ZEUS fields, this would be:\n    # slice(None, -2, None)\n    # slice(1, -1, None)\n    # 1.0\n    # Otherwise, we default to a centered difference.\n    if slice_info is None:\n        sl_left = slice(None, -2, None)\n        sl_right = slice(2, None, None)\n        div_fac = 2.0\n    else:\n        sl_left, sl_right, div_fac = slice_info\n    sl_center = slice(1, -1, None)\n\n    def _baroclinic_vorticity_x(data):\n        rho2 = data[ftype, \"density\"].astype(\"float64\", copy=False) ** 2\n        return (\n            data[ftype, \"pressure_gradient_y\"] * data[ftype, \"density_gradient_z\"]\n            - data[ftype, \"pressure_gradient_z\"] * data[ftype, \"density_gradient_y\"]\n        ) / rho2\n\n    def _baroclinic_vorticity_y(data):\n        rho2 = data[ftype, \"density\"].astype(\"float64\", copy=False) ** 2\n        return (\n            data[ftype, \"pressure_gradient_z\"] * data[ftype, \"density_gradient_x\"]\n            - data[ftype, \"pressure_gradient_x\"] * data[ftype, \"density_gradient_z\"]\n        ) / rho2\n\n    def _baroclinic_vorticity_z(data):\n        rho2 = data[ftype, \"density\"].astype(\"float64\", copy=False) ** 2\n        return (\n            data[ftype, \"pressure_gradient_x\"] * data[ftype, \"density_gradient_y\"]\n            - data[ftype, \"pressure_gradient_y\"] * data[ftype, \"density_gradient_x\"]\n        ) / rho2\n\n    bv_validators = [ValidateSpatial(1, [(ftype, \"density\"), (ftype, \"pressure\")])]\n    for ax in \"xyz\":\n        n = f\"baroclinic_vorticity_{ax}\"\n        registry.add_field(\n            (ftype, n),\n            sampling_type=\"cell\",\n            function=eval(f\"_{n}\"),\n            validators=bv_validators,\n            units=unit_system[\"frequency\"] ** 2,\n        )\n\n    create_magnitude_field(\n        registry,\n        \"baroclinic_vorticity\",\n        unit_system[\"frequency\"] ** 2,\n        ftype=ftype,\n        slice_info=slice_info,\n        validators=bv_validators,\n    )\n\n    def _vorticity_x(data):\n        vz = data[ftype, \"relative_velocity_z\"]\n        vy = data[ftype, \"relative_velocity_y\"]\n        f = (vz[sl_center, sl_right, sl_center] - vz[sl_center, sl_left, sl_center]) / (\n            div_fac * just_one(data[\"index\", \"dy\"])\n        )\n        f -= (\n            vy[sl_center, sl_center, sl_right] - vy[sl_center, sl_center, sl_left]\n        ) / (div_fac * just_one(data[\"index\", \"dz\"]))\n        new_field = data.ds.arr(np.zeros_like(vz, dtype=np.float64), f.units)\n        new_field[sl_center, sl_center, sl_center] = f\n        return new_field\n\n    def _vorticity_y(data):\n        vx = data[ftype, \"relative_velocity_x\"]\n        vz = data[ftype, \"relative_velocity_z\"]\n        f = (vx[sl_center, sl_center, sl_right] - vx[sl_center, sl_center, sl_left]) / (\n            div_fac * just_one(data[\"index\", \"dz\"])\n        )\n        f -= (\n            vz[sl_right, sl_center, sl_center] - vz[sl_left, sl_center, sl_center]\n        ) / (div_fac * just_one(data[\"index\", \"dx\"]))\n        new_field = data.ds.arr(np.zeros_like(vz, dtype=np.float64), f.units)\n        new_field[sl_center, sl_center, sl_center] = f\n        return new_field\n\n    def _vorticity_z(data):\n        vx = data[ftype, \"relative_velocity_x\"]\n        vy = data[ftype, \"relative_velocity_y\"]\n        f = (vy[sl_right, sl_center, sl_center] - vy[sl_left, sl_center, sl_center]) / (\n            div_fac * just_one(data[\"index\", \"dx\"])\n        )\n        f -= (\n            vx[sl_center, sl_right, sl_center] - vx[sl_center, sl_left, sl_center]\n        ) / (div_fac * just_one(data[\"index\", \"dy\"]))\n        new_field = data.ds.arr(np.zeros_like(vy, dtype=np.float64), f.units)\n        new_field[sl_center, sl_center, sl_center] = f\n        return new_field\n\n    vort_validators = [\n        ValidateSpatial(1, [(ftype, f\"velocity_{d}\") for d in \"xyz\"]),\n        ValidateParameter(\"bulk_velocity\"),\n    ]\n\n    for ax in \"xyz\":\n        n = f\"vorticity_{ax}\"\n        registry.add_field(\n            (ftype, n),\n            sampling_type=\"cell\",\n            function=eval(f\"_{n}\"),\n            units=unit_system[\"frequency\"],\n            validators=vort_validators,\n        )\n\n    create_magnitude_field(\n        registry,\n        \"vorticity\",\n        unit_system[\"frequency\"],\n        ftype=ftype,\n        slice_info=slice_info,\n        validators=vort_validators,\n    )\n    create_squared_field(\n        registry,\n        \"vorticity\",\n        unit_system[\"frequency\"] ** 2,\n        ftype=ftype,\n        slice_info=slice_info,\n        validators=vort_validators,\n    )\n\n    def _vorticity_stretching_x(data):\n        return data[ftype, \"velocity_divergence\"] * data[ftype, \"vorticity_x\"]\n\n    def _vorticity_stretching_y(data):\n        return data[ftype, \"velocity_divergence\"] * data[ftype, \"vorticity_y\"]\n\n    def _vorticity_stretching_z(data):\n        return data[ftype, \"velocity_divergence\"] * data[ftype, \"vorticity_z\"]\n\n    for ax in \"xyz\":\n        n = f\"vorticity_stretching_{ax}\"\n        registry.add_field(\n            (ftype, n),\n            sampling_type=\"cell\",\n            function=eval(f\"_{n}\"),\n            units=unit_system[\"frequency\"] ** 2,\n            validators=vort_validators,\n        )\n\n    create_magnitude_field(\n        registry,\n        \"vorticity_stretching\",\n        unit_system[\"frequency\"] ** 2,\n        ftype=ftype,\n        slice_info=slice_info,\n        validators=vort_validators,\n    )\n\n    def _vorticity_growth_x(data):\n        return (\n            -data[ftype, \"vorticity_stretching_x\"]\n            - data[ftype, \"baroclinic_vorticity_x\"]\n        )\n\n    def _vorticity_growth_y(data):\n        return (\n            -data[ftype, \"vorticity_stretching_y\"]\n            - data[ftype, \"baroclinic_vorticity_y\"]\n        )\n\n    def _vorticity_growth_z(data):\n        return (\n            -data[ftype, \"vorticity_stretching_z\"]\n            - data[ftype, \"baroclinic_vorticity_z\"]\n        )\n\n    for ax in \"xyz\":\n        n = f\"vorticity_growth_{ax}\"\n        registry.add_field(\n            (ftype, n),\n            sampling_type=\"cell\",\n            function=eval(f\"_{n}\"),\n            units=unit_system[\"frequency\"] ** 2,\n            validators=vort_validators,\n        )\n\n    def _vorticity_growth_magnitude(data):\n        result = np.sqrt(\n            data[ftype, \"vorticity_growth_x\"] ** 2\n            + data[ftype, \"vorticity_growth_y\"] ** 2\n            + data[ftype, \"vorticity_growth_z\"] ** 2\n        )\n        dot = data.ds.arr(np.zeros(result.shape), \"\")\n        for ax in \"xyz\":\n            dot += (\n                data[ftype, f\"vorticity_{ax}\"] * data[ftype, f\"vorticity_growth_{ax}\"]\n            ).to_ndarray()\n        result = np.sign(dot) * result\n        return result\n\n    registry.add_field(\n        (ftype, \"vorticity_growth_magnitude\"),\n        sampling_type=\"cell\",\n        function=_vorticity_growth_magnitude,\n        units=unit_system[\"frequency\"] ** 2,\n        validators=vort_validators,\n        take_log=False,\n    )\n\n    def _vorticity_growth_magnitude_absolute(data):\n        return np.sqrt(\n            data[ftype, \"vorticity_growth_x\"] ** 2\n            + data[ftype, \"vorticity_growth_y\"] ** 2\n            + data[ftype, \"vorticity_growth_z\"] ** 2\n        )\n\n    registry.add_field(\n        (ftype, \"vorticity_growth_magnitude_absolute\"),\n        sampling_type=\"cell\",\n        function=_vorticity_growth_magnitude_absolute,\n        units=unit_system[\"frequency\"] ** 2,\n        validators=vort_validators,\n    )\n\n    def _vorticity_growth_timescale(data):\n        domegax_dt = data[ftype, \"vorticity_x\"] / data[ftype, \"vorticity_growth_x\"]\n        domegay_dt = data[ftype, \"vorticity_y\"] / data[ftype, \"vorticity_growth_y\"]\n        domegaz_dt = data[ftype, \"vorticity_z\"] / data[ftype, \"vorticity_growth_z\"]\n        return np.sqrt(domegax_dt**2 + domegay_dt**2 + domegaz_dt**2)\n\n    registry.add_field(\n        (ftype, \"vorticity_growth_timescale\"),\n        sampling_type=\"cell\",\n        function=_vorticity_growth_timescale,\n        units=unit_system[\"time\"],\n        validators=vort_validators,\n    )\n\n    ########################################################################\n    # With radiation pressure\n    ########################################################################\n\n    def _vorticity_radiation_pressure_x(data):\n        rho = data[ftype, \"density\"].astype(\"float64\", copy=False)\n        return (\n            data[ftype, \"radiation_acceleration_y\"] * data[ftype, \"density_gradient_z\"]\n            - data[ftype, \"radiation_acceleration_z\"]\n            * data[ftype, \"density_gradient_y\"]\n        ) / rho\n\n    def _vorticity_radiation_pressure_y(data):\n        rho = data[ftype, \"density\"].astype(\"float64\", copy=False)\n        return (\n            data[ftype, \"radiation_acceleration_z\"] * data[ftype, \"density_gradient_x\"]\n            - data[ftype, \"radiation_acceleration_x\"]\n            * data[ftype, \"density_gradient_z\"]\n        ) / rho\n\n    def _vorticity_radiation_pressure_z(data):\n        rho = data[ftype, \"density\"].astype(\"float64\", copy=False)\n        return (\n            data[ftype, \"radiation_acceleration_x\"] * data[ftype, \"density_gradient_y\"]\n            - data[ftype, \"radiation_acceleration_y\"]\n            * data[ftype, \"density_gradient_x\"]\n        ) / rho\n\n    vrp_validators = [\n        ValidateSpatial(\n            1,\n            [\n                (ftype, \"density\"),\n                (ftype, \"radiation_acceleration_x\"),\n                (ftype, \"radiation_acceleration_y\"),\n                (ftype, \"radiation_acceleration_z\"),\n            ],\n        )\n    ]\n    for ax in \"xyz\":\n        n = f\"vorticity_radiation_pressure_{ax}\"\n        registry.add_field(\n            (ftype, n),\n            sampling_type=\"cell\",\n            function=eval(f\"_{n}\"),\n            units=unit_system[\"frequency\"] ** 2,\n            validators=vrp_validators,\n        )\n\n    create_magnitude_field(\n        registry,\n        \"vorticity_radiation_pressure\",\n        unit_system[\"frequency\"] ** 2,\n        ftype=ftype,\n        slice_info=slice_info,\n        validators=vrp_validators,\n    )\n\n    def _vorticity_radiation_pressure_growth_x(data):\n        return (\n            -data[ftype, \"vorticity_stretching_x\"]\n            - data[ftype, \"baroclinic_vorticity_x\"]\n            - data[ftype, \"vorticity_radiation_pressure_x\"]\n        )\n\n    def _vorticity_radiation_pressure_growth_y(data):\n        return (\n            -data[ftype, \"vorticity_stretching_y\"]\n            - data[ftype, \"baroclinic_vorticity_y\"]\n            - data[ftype, \"vorticity_radiation_pressure_y\"]\n        )\n\n    def _vorticity_radiation_pressure_growth_z(data):\n        return (\n            -data[ftype, \"vorticity_stretching_z\"]\n            - data[ftype, \"baroclinic_vorticity_z\"]\n            - data[ftype, \"vorticity_radiation_pressure_z\"]\n        )\n\n    for ax in \"xyz\":\n        n = f\"vorticity_radiation_pressure_growth_{ax}\"\n        registry.add_field(\n            (ftype, n),\n            sampling_type=\"cell\",\n            function=eval(f\"_{n}\"),\n            units=unit_system[\"frequency\"] ** 2,\n            validators=vrp_validators,\n        )\n\n    def _vorticity_radiation_pressure_growth_magnitude(data):\n        result = np.sqrt(\n            data[ftype, \"vorticity_radiation_pressure_growth_x\"] ** 2\n            + data[ftype, \"vorticity_radiation_pressure_growth_y\"] ** 2\n            + data[ftype, \"vorticity_radiation_pressure_growth_z\"] ** 2\n        )\n        dot = data.ds.arr(np.zeros(result.shape), \"\")\n        for ax in \"xyz\":\n            dot += (\n                data[ftype, f\"vorticity_{ax}\"] * data[ftype, f\"vorticity_growth_{ax}\"]\n            ).to_ndarray()\n        result = np.sign(dot) * result\n        return result\n\n    registry.add_field(\n        (ftype, \"vorticity_radiation_pressure_growth_magnitude\"),\n        sampling_type=\"cell\",\n        function=_vorticity_radiation_pressure_growth_magnitude,\n        units=unit_system[\"frequency\"] ** 2,\n        validators=vrp_validators,\n        take_log=False,\n    )\n\n    def _vorticity_radiation_pressure_growth_magnitude_absolute(data):\n        return np.sqrt(\n            data[ftype, \"vorticity_radiation_pressure_growth_x\"] ** 2\n            + data[ftype, \"vorticity_radiation_pressure_growth_y\"] ** 2\n            + data[ftype, \"vorticity_radiation_pressure_growth_z\"] ** 2\n        )\n\n    registry.add_field(\n        (ftype, \"vorticity_radiation_pressure_growth_magnitude_absolute\"),\n        sampling_type=\"cell\",\n        function=_vorticity_radiation_pressure_growth_magnitude_absolute,\n        units=\"s**(-2)\",\n        validators=vrp_validators,\n    )\n\n    def _vorticity_radiation_pressure_growth_timescale(data):\n        domegax_dt = (\n            data[ftype, \"vorticity_x\"]\n            / data[ftype, \"vorticity_radiation_pressure_growth_x\"]\n        )\n        domegay_dt = (\n            data[ftype, \"vorticity_y\"]\n            / data[ftype, \"vorticity_radiation_pressure_growth_y\"]\n        )\n        domegaz_dt = (\n            data[ftype, \"vorticity_z\"]\n            / data[ftype, \"vorticity_radiation_pressure_growth_z\"]\n        )\n        return np.sqrt(domegax_dt**2 + domegay_dt**2 + domegaz_dt**2)\n\n    registry.add_field(\n        (ftype, \"vorticity_radiation_pressure_growth_timescale\"),\n        sampling_type=\"cell\",\n        function=_vorticity_radiation_pressure_growth_timescale,\n        units=unit_system[\"time\"],\n        validators=vrp_validators,\n    )\n\n    def _shear(data):\n        \"\"\"\n        Shear is defined as [(dvx/dy + dvy/dx)^2 + (dvz/dy + dvy/dz)^2 +\n                             (dvx/dz + dvz/dx)^2 ]^(0.5)\n        where dvx/dy = [vx(j-1) - vx(j+1)]/[2dy]\n        and is in units of s^(-1)\n        (it's just like vorticity except add the derivative pairs instead\n         of subtracting them)\n        \"\"\"\n\n        if data.ds.geometry != \"cartesian\":\n            raise NotImplementedError(\"shear is only supported in cartesian geometries\")\n\n        try:\n            vx = data[ftype, \"relative_velocity_x\"]\n            vy = data[ftype, \"relative_velocity_y\"]\n        except YTFieldNotFound as e:\n            raise YTDimensionalityError(\n                \"shear computation requires 2 velocity components\"\n            ) from e\n\n        dvydx = (\n            vy[sl_right, sl_center, sl_center] - vy[sl_left, sl_center, sl_center]\n        ) / (div_fac * just_one(data[\"index\", \"dx\"]))\n        dvxdy = (\n            vx[sl_center, sl_right, sl_center] - vx[sl_center, sl_left, sl_center]\n        ) / (div_fac * just_one(data[\"index\", \"dy\"]))\n        f = (dvydx + dvxdy) ** 2.0\n        del dvydx, dvxdy\n\n        try:\n            vz = data[ftype, \"relative_velocity_z\"]\n            dvzdy = (\n                vz[sl_center, sl_right, sl_center] - vz[sl_center, sl_left, sl_center]\n            ) / (div_fac * just_one(data[\"index\", \"dy\"]))\n            dvydz = (\n                vy[sl_center, sl_center, sl_right] - vy[sl_center, sl_center, sl_left]\n            ) / (div_fac * just_one(data[\"index\", \"dz\"]))\n            f += (dvzdy + dvydz) ** 2.0\n            del dvzdy, dvydz\n            dvxdz = (\n                vx[sl_center, sl_center, sl_right] - vx[sl_center, sl_center, sl_left]\n            ) / (div_fac * just_one(data[\"index\", \"dz\"]))\n            dvzdx = (\n                vz[sl_right, sl_center, sl_center] - vz[sl_left, sl_center, sl_center]\n            ) / (div_fac * just_one(data[\"index\", \"dx\"]))\n            f += (dvxdz + dvzdx) ** 2.0\n            del dvxdz, dvzdx\n        except YTFieldNotFound:\n            # the absence of a z velocity component is not blocking\n            pass\n\n        np.sqrt(f, out=f)\n        new_field = data.ds.arr(np.zeros_like(data[ftype, \"velocity_x\"]), f.units)\n        new_field[sl_center, sl_center, sl_center] = f\n        return new_field\n\n    registry.add_field(\n        (ftype, \"shear\"),\n        sampling_type=\"cell\",\n        function=_shear,\n        validators=[\n            ValidateSpatial(\n                1, [(ftype, \"velocity_x\"), (ftype, \"velocity_y\"), (ftype, \"velocity_z\")]\n            ),\n            ValidateParameter(\"bulk_velocity\"),\n        ],\n        units=unit_system[\"frequency\"],\n    )\n\n    def _shear_criterion(data):\n        \"\"\"\n        Divide by c_s to leave shear in units of length**-1, which\n        can be compared against the inverse of the local cell size (1/dx)\n        to determine if refinement should occur.\n        \"\"\"\n        return data[ftype, \"shear\"] / data[ftype, \"sound_speed\"]\n\n    registry.add_field(\n        (ftype, \"shear_criterion\"),\n        sampling_type=\"cell\",\n        function=_shear_criterion,\n        units=unit_system[\"length\"] ** -1,\n        validators=[\n            ValidateSpatial(\n                1,\n                [\n                    (ftype, \"sound_speed\"),\n                    (ftype, \"velocity_x\"),\n                    (ftype, \"velocity_y\"),\n                    (ftype, \"velocity_z\"),\n                ],\n            )\n        ],\n    )\n\n    def _shear_mach(data):\n        \"\"\"\n        Dimensionless shear (shear_mach) is defined nearly the same as shear,\n        except that it is scaled by the local dx/dy/dz and the local sound speed.\n        So it results in a unitless quantity that is effectively measuring\n        shear in mach number.\n\n        In order to avoid discontinuities created by multiplying by dx/dy/dz at\n        grid refinement boundaries, we also multiply by 2**GridLevel.\n\n        Shear (Mach) = [(dvx + dvy)^2 + (dvz + dvy)^2 +\n                        (dvx + dvz)^2  ]^(0.5) / c_sound\n        \"\"\"\n\n        if data.ds.geometry != \"cartesian\":\n            raise NotImplementedError(\n                \"shear_mach is only supported in cartesian geometries\"\n            )\n\n        try:\n            vx = data[ftype, \"relative_velocity_x\"]\n            vy = data[ftype, \"relative_velocity_y\"]\n        except YTFieldNotFound as e:\n            raise YTDimensionalityError(\n                \"shear_mach computation requires 2 velocity components\"\n            ) from e\n        dvydx = (\n            vy[sl_right, sl_center, sl_center] - vy[sl_left, sl_center, sl_center]\n        ) / div_fac\n        dvxdy = (\n            vx[sl_center, sl_right, sl_center] - vx[sl_center, sl_left, sl_center]\n        ) / div_fac\n        f = (dvydx + dvxdy) ** 2.0\n        del dvydx, dvxdy\n        try:\n            vz = data[ftype, \"relative_velocity_z\"]\n            dvzdy = (\n                vz[sl_center, sl_right, sl_center] - vz[sl_center, sl_left, sl_center]\n            ) / div_fac\n            dvydz = (\n                vy[sl_center, sl_center, sl_right] - vy[sl_center, sl_center, sl_left]\n            ) / div_fac\n            f += (dvzdy + dvydz) ** 2.0\n            del dvzdy, dvydz\n            dvxdz = (\n                vx[sl_center, sl_center, sl_right] - vx[sl_center, sl_center, sl_left]\n            ) / div_fac\n            dvzdx = (\n                vz[sl_right, sl_center, sl_center] - vz[sl_left, sl_center, sl_center]\n            ) / div_fac\n            f += (dvxdz + dvzdx) ** 2.0\n            del dvxdz, dvzdx\n        except YTFieldNotFound:\n            # the absence of a z velocity component is not blocking\n            pass\n        f *= (\n            2.0 ** data[\"index\", \"grid_level\"][sl_center, sl_center, sl_center]\n            / data[ftype, \"sound_speed\"][sl_center, sl_center, sl_center]\n        ) ** 2.0\n        np.sqrt(f, out=f)\n        new_field = data.ds.arr(np.zeros_like(vx), f.units)\n        new_field[sl_center, sl_center, sl_center] = f\n        return new_field\n\n    vs_fields = [\n        (ftype, \"sound_speed\"),\n        (ftype, \"velocity_x\"),\n        (ftype, \"velocity_y\"),\n        (ftype, \"velocity_z\"),\n    ]\n    registry.add_field(\n        (ftype, \"shear_mach\"),\n        sampling_type=\"cell\",\n        function=_shear_mach,\n        units=\"\",\n        validators=[ValidateSpatial(1, vs_fields), ValidateParameter(\"bulk_velocity\")],\n    )\n"
  },
  {
    "path": "yt/fields/geometric_fields.py",
    "content": "import numpy as np\n\nfrom yt.utilities.lib.geometry_utils import compute_morton\nfrom yt.utilities.math_utils import (\n    get_cyl_r,\n    get_cyl_theta,\n    get_cyl_z,\n    get_sph_phi,\n    get_sph_r,\n    get_sph_theta,\n)\n\nfrom .derived_field import ValidateParameter, ValidateSpatial\nfrom .field_functions import get_periodic_rvec, get_radius\nfrom .field_plugin_registry import register_field_plugin\n\n\n@register_field_plugin\ndef setup_geometric_fields(registry, ftype=\"gas\", slice_info=None):\n    unit_system = registry.ds.unit_system\n\n    def _radius(field, data):\n        \"\"\"The spherical radius component of the mesh cells.\n\n        Relative to the coordinate system defined by the *center* field\n        parameter.\n        \"\"\"\n        return get_radius(data, \"\", field.name[0])\n\n    registry.add_field(\n        (\"index\", \"radius\"),\n        sampling_type=\"cell\",\n        function=_radius,\n        validators=[ValidateParameter(\"center\")],\n        units=unit_system[\"length\"],\n    )\n\n    def _grid_level(data):\n        \"\"\"The AMR refinement level\"\"\"\n        arr = np.ones(data.ires.shape, dtype=\"float64\")\n        arr *= data.ires\n        if data._spatial:\n            return data._reshape_vals(arr)\n        return arr\n\n    registry.add_field(\n        (\"index\", \"grid_level\"),\n        sampling_type=\"cell\",\n        function=_grid_level,\n        units=\"\",\n        validators=[ValidateSpatial(0)],\n    )\n\n    def _grid_indices(data):\n        \"\"\"The index of the leaf grid the mesh cells exist on\"\"\"\n        if hasattr(data, \"domain_id\"):\n            this_id = data.domain_id\n        else:\n            this_id = data.id - data._id_offset\n        arr = np.ones(data[\"index\", \"ones\"].shape)\n        arr *= this_id\n        if data._spatial:\n            return data._reshape_vals(arr)\n        return arr\n\n    registry.add_field(\n        (\"index\", \"grid_indices\"),\n        sampling_type=\"cell\",\n        function=_grid_indices,\n        units=\"\",\n        validators=[ValidateSpatial(0)],\n        take_log=False,\n    )\n\n    def _ones_over_dx(data):\n        \"\"\"The inverse of the local cell spacing\"\"\"\n        return (\n            np.ones(data[\"index\", \"ones\"].shape, dtype=\"float64\") / data[\"index\", \"dx\"]\n        )\n\n    registry.add_field(\n        (\"index\", \"ones_over_dx\"),\n        sampling_type=\"cell\",\n        function=_ones_over_dx,\n        units=unit_system[\"length\"] ** -1,\n        display_field=False,\n    )\n\n    def _zeros(field, data):\n        \"\"\"Returns zero for all cells\"\"\"\n        arr = np.zeros(data[\"index\", \"ones\"].shape, dtype=\"float64\")\n        return data.apply_units(arr, field.units)\n\n    registry.add_field(\n        (\"index\", \"zeros\"),\n        sampling_type=\"cell\",\n        function=_zeros,\n        units=\"\",\n        display_field=False,\n    )\n\n    def _ones(field, data):\n        \"\"\"Returns one for all cells\"\"\"\n        tmp = np.ones(data.ires.shape, dtype=\"float64\")\n        arr = data.apply_units(tmp, field.units)\n        if data._spatial:\n            return data._reshape_vals(arr)\n        return arr\n\n    registry.add_field(\n        (\"index\", \"ones\"),\n        sampling_type=\"cell\",\n        function=_ones,\n        units=\"\",\n        display_field=False,\n    )\n\n    def _morton_index(data):\n        \"\"\"This is the morton index, which is properly a uint64 field.  Because\n        we make some assumptions that the fields returned by derived fields are\n        float64, this returns a \"view\" on the data that is float64.  To get\n        back the original uint64, you need to call .view(\"uint64\") on it;\n        however, it should be true that if you sort the uint64, you will get\n        the same order as if you sort the float64 view.\n        \"\"\"\n        eps = np.finfo(\"f8\").eps\n        uq = data.ds.domain_left_edge.uq\n        LE = data.ds.domain_left_edge - eps * uq\n        RE = data.ds.domain_right_edge + eps * uq\n        # .ravel() only copies if it needs to\n        morton = compute_morton(\n            data[\"index\", \"x\"].ravel(),\n            data[\"index\", \"y\"].ravel(),\n            data[\"index\", \"z\"].ravel(),\n            LE,\n            RE,\n        ).reshape(data[\"index\", \"x\"].shape)\n        return morton.view(\"f8\")\n\n    registry.add_field(\n        (\"index\", \"morton_index\"),\n        sampling_type=\"cell\",\n        function=_morton_index,\n        units=\"\",\n    )\n\n    def _spherical_radius(data):\n        \"\"\"The spherical radius component of the positions of the mesh cells.\n\n        Relative to the coordinate system defined by the *center* field\n        parameter.\n        \"\"\"\n        coords = get_periodic_rvec(data)\n        return data.ds.arr(get_sph_r(coords), \"code_length\").in_base(unit_system.name)\n\n    registry.add_field(\n        (\"index\", \"spherical_radius\"),\n        sampling_type=\"cell\",\n        function=_spherical_radius,\n        validators=[ValidateParameter(\"center\")],\n        units=unit_system[\"length\"],\n    )\n\n    def _spherical_theta(data):\n        \"\"\"The spherical theta component of the positions of the mesh cells.\n\n        theta is the poloidal position angle in the plane parallel to the\n        *normal* vector\n\n        Relative to the coordinate system defined by the *center* and *normal*\n        field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        coords = get_periodic_rvec(data)\n        return get_sph_theta(coords, normal)\n\n    registry.add_field(\n        (\"index\", \"spherical_theta\"),\n        sampling_type=\"cell\",\n        function=_spherical_theta,\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"normal\")],\n        units=\"\",\n    )\n\n    def _spherical_phi(data):\n        \"\"\"The spherical phi component of the positions of the mesh cells.\n\n        phi is the azimuthal position angle in the plane perpendicular to the\n        *normal* vector\n\n        Relative to the coordinate system defined by the *center* and *normal*\n        field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        coords = get_periodic_rvec(data)\n        return get_sph_phi(coords, normal)\n\n    registry.add_field(\n        (\"index\", \"spherical_phi\"),\n        sampling_type=\"cell\",\n        function=_spherical_phi,\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"normal\")],\n        units=\"\",\n    )\n\n    def _cylindrical_radius(data):\n        \"\"\"The cylindrical radius component of the positions of the mesh cells.\n\n        Relative to the coordinate system defined by the *normal* vector and\n        *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        coords = get_periodic_rvec(data)\n        return data.ds.arr(get_cyl_r(coords, normal), \"code_length\").in_base(\n            unit_system.name\n        )\n\n    registry.add_field(\n        (\"index\", \"cylindrical_radius\"),\n        sampling_type=\"cell\",\n        function=_cylindrical_radius,\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"normal\")],\n        units=unit_system[\"length\"],\n    )\n\n    def _cylindrical_z(data):\n        \"\"\"The cylindrical z component of the positions of the mesh cells.\n\n        Relative to the coordinate system defined by the *normal* vector and\n        *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        coords = get_periodic_rvec(data)\n        return data.ds.arr(get_cyl_z(coords, normal), \"code_length\").in_base(\n            unit_system.name\n        )\n\n    registry.add_field(\n        (\"index\", \"cylindrical_z\"),\n        sampling_type=\"cell\",\n        function=_cylindrical_z,\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"normal\")],\n        units=unit_system[\"length\"],\n    )\n\n    def _cylindrical_theta(data):\n        \"\"\"The cylindrical z component of the positions of the mesh cells.\n\n        theta is the azimuthal position angle in the plane perpendicular to the\n        *normal* vector.\n\n        Relative to the coordinate system defined by the *normal* vector and\n        *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        coords = get_periodic_rvec(data)\n        return get_cyl_theta(coords, normal)\n\n    registry.add_field(\n        (\"index\", \"cylindrical_theta\"),\n        sampling_type=\"cell\",\n        function=_cylindrical_theta,\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"normal\")],\n        units=\"\",\n    )\n"
  },
  {
    "path": "yt/fields/interpolated_fields.py",
    "content": "from yt.fields.local_fields import add_field\nfrom yt.utilities.linear_interpolators import (\n    BilinearFieldInterpolator,\n    TrilinearFieldInterpolator,\n    UnilinearFieldInterpolator,\n)\n\n_int_class = {\n    1: UnilinearFieldInterpolator,\n    2: BilinearFieldInterpolator,\n    3: TrilinearFieldInterpolator,\n}\n\n\ndef add_interpolated_field(\n    name,\n    units,\n    table_data,\n    axes_data,\n    axes_fields,\n    ftype=\"gas\",\n    particle_type=False,\n    validators=None,\n    truncate=True,\n):\n    if len(table_data.shape) not in _int_class:\n        raise RuntimeError(\n            \"Interpolated field can only be created from 1d, 2d, or 3d data.\"\n        )\n\n    if len(axes_fields) != len(axes_data) or len(axes_fields) != len(table_data.shape):\n        raise RuntimeError(\n            f\"Data dimension mismatch: data is {len(table_data.shape)}, \"\n            f\"{len(axes_data)} axes data provided, \"\n            f\"and {len(axes_fields)} axes fields provided.\"\n        )\n\n    int_class = _int_class[len(table_data.shape)]\n    my_interpolator = int_class(table_data, axes_data, axes_fields, truncate=truncate)\n\n    def _interpolated_field(data):\n        return my_interpolator(data)\n\n    add_field(\n        (ftype, name),\n        function=_interpolated_field,\n        units=units,\n        validators=validators,\n        particle_type=particle_type,\n    )\n"
  },
  {
    "path": "yt/fields/local_fields.py",
    "content": "from collections.abc import Callable\nfrom functools import partial\nfrom typing import Any, TypeVar\n\nfrom yt.funcs import is_sequence\nfrom yt.utilities.logger import ytLogger as mylog\n\nfrom .field_info_container import FieldInfoContainer\nfrom .field_plugin_registry import register_field_plugin\n\n# workaround mypy not being comfortable around decorator preserving signatures\n# adapted from\n# https://github.com/python/mypy/issues/1551#issuecomment-253978622\nTFun = TypeVar(\"TFun\", bound=Callable[..., Any])\n\n\nclass LocalFieldInfoContainer(FieldInfoContainer):\n    def add_field(\n        self, name, function, sampling_type, *, force_override=False, **kwargs\n    ):\n        from yt.fields.field_functions import validate_field_function\n\n        validate_field_function(function)\n        if isinstance(name, str) or not is_sequence(name):\n            # the base method only accepts proper tuple field keys\n            # and is only used internally, while this method is exposed to users\n            # and is documented as usable with single strings as name\n            if sampling_type == \"particle\":\n                ftype = \"all\"\n            else:\n                ftype = \"gas\"\n            name = (ftype, name)\n\n        # Handle the case where the field has already been added.\n        if not force_override and name in self:\n            mylog.warning(\n                \"Field %s already exists. To override use `force_override=True`.\",\n                name,\n            )\n\n        return super().add_field(\n            name, function, sampling_type, force_override=force_override, **kwargs\n        )\n\n\n# Empty FieldInfoContainer\nlocal_fields = LocalFieldInfoContainer(None, [], None)\n\n# we define two handles, essentially pointing to the same function but documented differently\n# yt.add_field() is meant to be used directly, while yt.derived_field is documented\n# as a decorator.\nadd_field = local_fields.add_field\n\n\nclass derived_field:\n    # implement a decorator accepting keyword arguments to be passed down to add_field\n\n    def __init__(self, **kwargs) -> None:\n        self._kwargs = kwargs\n\n    def __call__(self, f: Callable) -> Callable:\n        partial(local_fields.add_field, function=f)(**self._kwargs)\n        return f\n\n\n@register_field_plugin\ndef setup_local_fields(registry, ftype=\"gas\", slice_info=None):\n    # This is easy.  We just update with the contents of the local_fields field\n    # info container, and since they are not mutable in any real way, we are\n    # fine.\n    # Note that we actually don't care about the ftype here.\n    for f in local_fields:\n        registry._show_field_errors.append(f)\n    registry.update(local_fields)\n"
  },
  {
    "path": "yt/fields/magnetic_field.py",
    "content": "import sys\n\nimport numpy as np\n\nfrom yt._typing import FieldType\nfrom yt.fields.derived_field import ValidateParameter\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.geometry.api import Geometry\nfrom yt.units import dimensions\n\nfrom .field_plugin_registry import register_field_plugin\n\nif sys.version_info >= (3, 11):\n    from typing import assert_never\nelse:\n    from typing_extensions import assert_never\n\ncgs_normalizations = {\"gaussian\": 4.0 * np.pi, \"lorentz_heaviside\": 1.0}\n\n\ndef get_magnetic_normalization(key: str) -> float:\n    if key not in cgs_normalizations:\n        raise ValueError(\n            \"Unknown magnetic normalization convention. \"\n            f\"Got {key!r}, expected one of {tuple(cgs_normalizations)}\"\n        )\n    return cgs_normalizations[key]\n\n\n@register_field_plugin\ndef setup_magnetic_field_fields(\n    registry: FieldInfoContainer, ftype: FieldType = \"gas\", slice_info=None\n) -> None:\n    ds = registry.ds\n\n    unit_system = ds.unit_system\n    pc = registry.ds.units.physical_constants\n\n    axis_names = registry.ds.coordinates.axis_order\n\n    if (ftype, f\"magnetic_field_{axis_names[0]}\") not in registry:\n        return\n\n    u = registry[ftype, f\"magnetic_field_{axis_names[0]}\"].units\n\n    def mag_factors(dims):\n        if dims == dimensions.magnetic_field_cgs:\n            return getattr(ds, \"_magnetic_factor\", 4.0 * np.pi)\n        elif dims == dimensions.magnetic_field_mks:\n            return ds.units.physical_constants.mu_0\n\n    def _magnetic_field_strength(data):\n        xm = f\"relative_magnetic_field_{axis_names[0]}\"\n        ym = f\"relative_magnetic_field_{axis_names[1]}\"\n        zm = f\"relative_magnetic_field_{axis_names[2]}\"\n\n        B2 = (data[ftype, xm]) ** 2 + (data[ftype, ym]) ** 2 + (data[ftype, zm]) ** 2\n\n        return np.sqrt(B2)\n\n    registry.add_field(\n        (ftype, \"magnetic_field_strength\"),\n        sampling_type=\"local\",\n        function=_magnetic_field_strength,\n        validators=[ValidateParameter(\"bulk_magnetic_field\")],\n        units=u,\n    )\n\n    def _magnetic_energy_density(data):\n        B = data[ftype, \"magnetic_field_strength\"]\n        return 0.5 * B * B / mag_factors(B.units.dimensions)\n\n    registry.add_field(\n        (ftype, \"magnetic_energy_density\"),\n        sampling_type=\"local\",\n        function=_magnetic_energy_density,\n        units=unit_system[\"pressure\"],\n    )\n\n    def _plasma_beta(data):\n        return data[ftype, \"pressure\"] / data[ftype, \"magnetic_energy_density\"]\n\n    registry.add_field(\n        (ftype, \"plasma_beta\"), sampling_type=\"local\", function=_plasma_beta, units=\"\"\n    )\n\n    def _magnetic_pressure(data):\n        return data[ftype, \"magnetic_energy_density\"]\n\n    registry.add_field(\n        (ftype, \"magnetic_pressure\"),\n        sampling_type=\"local\",\n        function=_magnetic_pressure,\n        units=unit_system[\"pressure\"],\n    )\n\n    _magnetic_field_poloidal_magnitude = None\n    _magnetic_field_toroidal_magnitude = None\n\n    geometry: Geometry = registry.ds.geometry\n    match geometry:\n        case Geometry.CARTESIAN:\n\n            def _magnetic_field_poloidal_magnitude(data):\n                B2 = (\n                    data[ftype, \"relative_magnetic_field_x\"]\n                    * data[ftype, \"relative_magnetic_field_x\"]\n                    + data[ftype, \"relative_magnetic_field_y\"]\n                    * data[ftype, \"relative_magnetic_field_y\"]\n                    + data[ftype, \"relative_magnetic_field_z\"]\n                    * data[ftype, \"relative_magnetic_field_z\"]\n                )\n                Bt2 = (\n                    data[ftype, \"magnetic_field_spherical_phi\"]\n                    * data[ftype, \"magnetic_field_spherical_phi\"]\n                )\n                return np.sqrt(B2 - Bt2)\n\n        case Geometry.CYLINDRICAL | Geometry.POLAR:\n\n            def _magnetic_field_poloidal_magnitude(data):\n                bm = data.get_field_parameter(\"bulk_magnetic_field\")\n                rax = axis_names.index(\"r\")\n                zax = axis_names.index(\"z\")\n\n                return np.sqrt(\n                    (data[ftype, \"magnetic_field_r\"] - bm[rax]) ** 2\n                    + (data[ftype, \"magnetic_field_z\"] - bm[zax]) ** 2\n                )\n\n            def _magnetic_field_toroidal_magnitude(data):\n                ax = axis_names.find(\"theta\")\n                bm = data.get_field_parameter(\"bulk_magnetic_field\")\n                return data[ftype, \"magnetic_field_theta\"] - bm[ax]\n\n        case Geometry.SPHERICAL:\n\n            def _magnetic_field_poloidal_magnitude(data):\n                bm = data.get_field_parameter(\"bulk_magnetic_field\")\n                rax = axis_names.index(\"r\")\n                tax = axis_names.index(\"theta\")\n\n                return np.sqrt(\n                    (data[ftype, \"magnetic_field_r\"] - bm[rax]) ** 2\n                    + (data[ftype, \"magnetic_field_theta\"] - bm[tax]) ** 2\n                )\n\n            def _magnetic_field_toroidal_magnitude(data):\n                ax = axis_names.find(\"phi\")\n                bm = data.get_field_parameter(\"bulk_magnetic_field\")\n                return data[ftype, \"magnetic_field_phi\"] - bm[ax]\n\n        case Geometry.GEOGRAPHIC | Geometry.INTERNAL_GEOGRAPHIC:\n            # not implemented\n            pass\n        case Geometry.SPECTRAL_CUBE:\n            # nothing to be done\n            pass\n        case _:\n            assert_never(geometry)\n\n    if _magnetic_field_poloidal_magnitude is not None:\n        registry.add_field(\n            (ftype, \"magnetic_field_poloidal_magnitude\"),\n            sampling_type=\"local\",\n            function=_magnetic_field_poloidal_magnitude,\n            units=u,\n            validators=[\n                ValidateParameter(\"normal\"),\n                ValidateParameter(\"bulk_magnetic_field\"),\n            ],\n        )\n\n    if _magnetic_field_toroidal_magnitude is not None:\n        registry.add_field(\n            (ftype, \"magnetic_field_toroidal_magnitude\"),\n            sampling_type=\"local\",\n            function=_magnetic_field_toroidal_magnitude,\n            units=u,\n            validators=[\n                ValidateParameter(\"normal\"),\n                ValidateParameter(\"bulk_magnetic_field\"),\n            ],\n        )\n\n    match geometry:\n        case Geometry.CARTESIAN:\n            registry.alias(\n                (ftype, \"magnetic_field_toroidal_magnitude\"),\n                (ftype, \"magnetic_field_spherical_phi\"),\n                units=u,\n            )\n            registry.alias(\n                (ftype, \"magnetic_field_toroidal\"),\n                (ftype, \"magnetic_field_spherical_phi\"),\n                units=u,\n                deprecate=(\"4.1.0\", None),\n            )\n            registry.alias(\n                (ftype, \"magnetic_field_poloidal\"),\n                (ftype, \"magnetic_field_spherical_theta\"),\n                units=u,\n                deprecate=(\"4.1.0\", None),\n            )\n        case Geometry.CYLINDRICAL | Geometry.POLAR | Geometry.SPHERICAL:\n            # These cases should be covered already, just check that they are\n            assert (ftype, \"magnetic_field_toroidal_magnitude\") in registry\n            assert (ftype, \"magnetic_field_poloidal_magnitude\") in registry\n        case Geometry.GEOGRAPHIC | Geometry.INTERNAL_GEOGRAPHIC:\n            # not implemented\n            pass\n        case Geometry.SPECTRAL_CUBE:\n            # nothing to be done\n            pass\n        case _:\n            assert_never(Geometry)\n\n    def _alfven_speed(data):\n        B = data[ftype, \"magnetic_field_strength\"]\n        return B / np.sqrt(mag_factors(B.units.dimensions) * data[ftype, \"density\"])\n\n    registry.add_field(\n        (ftype, \"alfven_speed\"),\n        sampling_type=\"local\",\n        function=_alfven_speed,\n        units=unit_system[\"velocity\"],\n    )\n\n    def _mach_alfven(data):\n        return data[ftype, \"velocity_magnitude\"] / data[ftype, \"alfven_speed\"]\n\n    registry.add_field(\n        (ftype, \"mach_alfven\"),\n        sampling_type=\"local\",\n        function=_mach_alfven,\n        units=\"dimensionless\",\n    )\n\n    b_units = registry.ds.quan(1.0, u).units\n    if dimensions.current_mks in b_units.dimensions.free_symbols:\n        rm_scale = pc.qp.to(\"C\", \"SI\") ** 3 / (4.0 * np.pi * pc.eps_0)\n    else:\n        rm_scale = pc.qp**3 / pc.clight\n    rm_scale *= registry.ds.quan(1.0, \"rad\") / (2.0 * np.pi * pc.me**2 * pc.clight**3)\n    rm_units = registry.ds.quan(1.0, \"rad/m**2\").units / unit_system[\"length\"]\n\n    def _rotation_measure(data):\n        return (\n            rm_scale\n            * data[ftype, \"magnetic_field_los\"]\n            * data[ftype, \"El_number_density\"]\n        )\n\n    registry.add_field(\n        (ftype, \"rotation_measure\"),\n        sampling_type=\"local\",\n        function=_rotation_measure,\n        units=rm_units,\n        validators=[ValidateParameter(\"axis\", {\"axis\": [0, 1, 2]})],\n    )\n\n\ndef setup_magnetic_field_aliases(registry, ds_ftype, ds_fields, ftype=\"gas\"):\n    r\"\"\"\n    This routine sets up special aliases between dataset-specific magnetic\n    fields and the default magnetic fields in yt so that unit conversions\n    between different unit systems can be handled properly. This is only called\n    from the `setup_fluid_fields` method (for grid dataset) or the\n    `setup_gas_particle_fields` method (for particle dataset) of a frontend's\n    :class:`FieldInfoContainer` instance.\n\n    Parameters\n    ----------\n    registry : :class:`FieldInfoContainer`\n        The field registry that these definitions will be installed into.\n    ds_ftype : string\n        The field type for the fields we're going to alias, e.g. \"flash\",\n        \"enzo\", \"athena\", \"PartType0\", etc.\n    ds_fields : list of strings or string\n        The fields that will be aliased. For grid dataset, this should be a\n        list of strings corresponding to the components of magnetic field. For\n        particle dataset, this should be a single string corresponding to the\n        vector magnetic field.\n    ftype : string, optional\n        The resulting field type of the fields. Default \"gas\".\n\n    Examples\n    --------\n    >>> from yt.fields.magnetic_field import setup_magnetic_field_aliases\n    >>> class PlutoFieldInfo(ChomboFieldInfo):\n    ...     def setup_fluid_fields(self):\n    ...         setup_magnetic_field_aliases(\n    ...             self, \"chombo\", [\"bx%s\" % ax for ax in [1, 2, 3]]\n    ...         )\n    >>> class GizmoFieldInfo(GadgetFieldInfo):\n    ...     def setup_gas_particle_fields(self):\n    ...         setup_magnetic_field_aliases(\n    ...             self, \"PartType0\", \"MagneticField\", ftype=\"PartType0\"\n    ...         )\n    \"\"\"\n    unit_system = registry.ds.unit_system\n    if isinstance(ds_fields, list):\n        # If ds_fields is a list, we assume a grid dataset\n        sampling_type = \"local\"\n        ds_fields = [(ds_ftype, fd) for fd in ds_fields]\n        ds_field = ds_fields[0]\n    else:\n        # Otherwise, we assume a particle dataset\n        sampling_type = \"particle\"\n        ds_field = (ds_ftype, ds_fields)\n    if ds_field not in registry:\n        return\n\n    # Figure out the unit conversion to use\n    if unit_system.base_units[dimensions.current_mks] is not None:\n        to_units = unit_system[\"magnetic_field_mks\"]\n    else:\n        to_units = unit_system[\"magnetic_field_cgs\"]\n    units = unit_system[to_units.dimensions]\n\n    # Add fields\n    if sampling_type in [\"cell\", \"local\"]:\n        # Grid dataset case\n        def mag_field_from_field(fd):\n            def _mag_field(field, data):\n                return data[fd].to(field.units)\n\n            return _mag_field\n\n        for ax, fd in zip(registry.ds.coordinates.axis_order, ds_fields, strict=False):\n            registry.add_field(\n                (ftype, f\"magnetic_field_{ax}\"),\n                sampling_type=sampling_type,\n                function=mag_field_from_field(fd),\n                units=units,\n            )\n    else:\n        # Particle dataset case\n        def mag_field_from_ax(ax):\n            def _mag_field(data):\n                return data[ds_field][:, \"xyz\".index(ax)]\n\n            return _mag_field\n\n        for ax in registry.ds.coordinates.axis_order:\n            fname = f\"particle_magnetic_field_{ax}\"\n            registry.add_field(\n                (ds_ftype, fname),\n                sampling_type=sampling_type,\n                function=mag_field_from_ax(ax),\n                units=units,\n            )\n            sph_ptypes = getattr(registry.ds, \"_sph_ptypes\", ())\n            if ds_ftype in sph_ptypes:\n                registry.alias((ftype, f\"magnetic_field_{ax}\"), (ds_ftype, fname))\n"
  },
  {
    "path": "yt/fields/my_plugin_fields.py",
    "content": "from .field_plugin_registry import register_field_plugin\nfrom .local_fields import LocalFieldInfoContainer\n\n# Empty FieldInfoContainer\nmy_plugins_fields = LocalFieldInfoContainer(None, [], None)\n\n\n@register_field_plugin\ndef setup_my_plugins_fields(registry, ftype=\"gas\", slice_info=None):\n    # fields end up inside this container when added via add_field in\n    # my_plugins.py. See yt.funcs.enable_plugins to see how this is set up.\n    registry.update(my_plugins_fields)\n"
  },
  {
    "path": "yt/fields/particle_fields.py",
    "content": "import numpy as np\n\nfrom yt.fields.derived_field import ValidateParameter, ValidateSpatial\nfrom yt.units._numpy_wrapper_functions import uconcatenate, ucross\nfrom yt.utilities.lib.misc_utilities import (\n    obtain_position_vector,\n    obtain_relative_velocity_vector,\n)\nfrom yt.utilities.math_utils import (\n    get_cyl_r,\n    get_cyl_r_component,\n    get_cyl_theta,\n    get_cyl_theta_component,\n    get_cyl_z,\n    get_cyl_z_component,\n    get_sph_phi,\n    get_sph_phi_component,\n    get_sph_r_component,\n    get_sph_theta,\n    get_sph_theta_component,\n    modify_reference_frame,\n)\n\nfrom .field_functions import get_radius\nfrom .vector_operations import create_los_field, create_magnitude_field\n\nsph_whitelist_fields = (\n    \"density\",\n    \"temperature\",\n    \"metallicity\",\n    \"thermal_energy\",\n    \"smoothing_length\",\n    \"star_formation_rate\",\n    \"cold_fraction\",\n    \"hot_temperature\",\n    \"H_fraction\",\n    \"He_fraction\",\n    \"C_fraction\",\n    \"Ca_fraction\",\n    \"N_fraction\",\n    \"O_fraction\",\n    \"S_fraction\",\n    \"Ne_fraction\",\n    \"Mg_fraction\",\n    \"Si_fraction\",\n    \"Fe_fraction\",\n    \"Na_fraction\",\n    \"Al_fraction\",\n    \"Ar_fraction\",\n    \"Ni_fraction\",\n    \"Ej_fraction\",\n    \"H_density\",\n    \"He_density\",\n    \"C_density\",\n    \"Ca_density\",\n    \"N_density\",\n    \"O_density\",\n    \"S_density\",\n    \"Ne_density\",\n    \"Mg_density\",\n    \"Si_density\",\n    \"Fe_density\",\n    \"Na_density\",\n    \"Al_density\",\n    \"Ar_density\",\n    \"Ni_density\",\n    \"Ej_density\",\n)\n\n\ndef _field_concat(fname):\n    def _AllFields(data):\n        v = []\n        for ptype in data.ds.particle_types:\n            if ptype == \"all\" or ptype in data.ds.known_filters:\n                continue\n            v.append(data[ptype, fname].copy())\n        rv = uconcatenate(v, axis=0)\n        return rv\n\n    return _AllFields\n\n\ndef _field_concat_slice(fname, axi):\n    def _AllFields(data):\n        v = []\n        for ptype in data.ds.particle_types:\n            if ptype == \"all\" or ptype in data.ds.known_filters:\n                continue\n            v.append(data[ptype, fname][:, axi])\n        rv = uconcatenate(v, axis=0)\n        return rv\n\n    return _AllFields\n\n\ndef particle_deposition_functions(ptype, coord_name, mass_name, registry):\n    unit_system = registry.ds.unit_system\n    orig = set(registry.keys())\n    ptype_dn = ptype.replace(\"_\", \" \").title()\n\n    def particle_count(field, data):\n        pos = data[ptype, coord_name]\n        d = data.deposit(pos, method=\"count\")\n        return data.apply_units(d, field.units)\n\n    registry.add_field(\n        (\"deposit\", f\"{ptype}_count\"),\n        sampling_type=\"cell\",\n        function=particle_count,\n        validators=[ValidateSpatial()],\n        units=\"\",\n        display_name=rf\"\\mathrm{{{ptype_dn} Count}}\",\n    )\n\n    def particle_mass(field, data):\n        pos = data[ptype, coord_name]\n        pmass = data[ptype, mass_name]\n        pmass.convert_to_units(field.units)\n        d = data.deposit(pos, [pmass], method=\"sum\")\n        return data.apply_units(d, field.units)\n\n    registry.add_field(\n        (\"deposit\", f\"{ptype}_mass\"),\n        sampling_type=\"cell\",\n        function=particle_mass,\n        validators=[ValidateSpatial()],\n        display_name=rf\"\\mathrm{{{ptype_dn} Mass}}\",\n        units=unit_system[\"mass\"],\n    )\n\n    def particle_density(data):\n        pos = data[ptype, coord_name]\n        pos.convert_to_units(\"code_length\")\n        mass = data[ptype, mass_name]\n        mass.convert_to_units(\"code_mass\")\n        d = data.deposit(pos, [mass], method=\"sum\")\n        d = data.ds.arr(d, \"code_mass\")\n        d /= data[\"index\", \"cell_volume\"]\n        return d\n\n    registry.add_field(\n        (\"deposit\", f\"{ptype}_density\"),\n        sampling_type=\"cell\",\n        function=particle_density,\n        validators=[ValidateSpatial()],\n        display_name=rf\"\\mathrm{{{ptype_dn} Density}}\",\n        units=unit_system[\"density\"],\n    )\n\n    def particle_cic(data):\n        pos = data[ptype, coord_name]\n        d = data.deposit(pos, [data[ptype, mass_name]], method=\"cic\")\n        d = data.apply_units(d, data[ptype, mass_name].units)\n        d /= data[\"index\", \"cell_volume\"]\n        return d\n\n    registry.add_field(\n        (\"deposit\", f\"{ptype}_cic\"),\n        sampling_type=\"cell\",\n        function=particle_cic,\n        validators=[ValidateSpatial()],\n        display_name=rf\"\\mathrm{{{ptype_dn} CIC Density}}\",\n        units=unit_system[\"density\"],\n    )\n\n    def _get_density_weighted_deposit_field(fname, units, method):\n        def _deposit_field(data):\n            \"\"\"\n            Create a grid field for particle quantities weighted by particle\n            mass, using cloud-in-cell deposit.\n            \"\"\"\n            pos = data[ptype, \"particle_position\"]\n            # Get back into density\n            pden = data[ptype, \"particle_mass\"]\n            top = data.deposit(pos, [pden * data[ptype, fname]], method=method)\n            bottom = data.deposit(pos, [pden], method=method)\n            top[bottom == 0] = 0.0\n            bnz = bottom.nonzero()\n            top[bnz] /= bottom[bnz]\n            d = data.ds.arr(top, units=units)\n            return d\n\n        return _deposit_field\n\n    for ax in \"xyz\":\n        for method, name in [(\"cic\", \"cic\"), (\"sum\", \"nn\")]:\n            function = _get_density_weighted_deposit_field(\n                f\"particle_velocity_{ax}\", \"code_velocity\", method\n            )\n            registry.add_field(\n                (\"deposit\", (\"%s_\" + name + \"_velocity_%s\") % (ptype, ax)),\n                sampling_type=\"cell\",\n                function=function,\n                units=unit_system[\"velocity\"],\n                take_log=False,\n                validators=[ValidateSpatial(0)],\n            )\n\n    for method, name in [(\"cic\", \"cic\"), (\"sum\", \"nn\")]:\n        function = _get_density_weighted_deposit_field(\"age\", \"code_time\", method)\n        registry.add_field(\n            (\"deposit\", (\"%s_\" + name + \"_age\") % (ptype)),\n            sampling_type=\"cell\",\n            function=function,\n            units=unit_system[\"time\"],\n            take_log=False,\n            validators=[ValidateSpatial(0)],\n        )\n\n    # Now some translation functions.\n\n    def particle_ones(field, data):\n        v = np.ones(data[ptype, coord_name].shape[0], dtype=\"float64\")\n        return data.apply_units(v, field.units)\n\n    registry.add_field(\n        (ptype, \"particle_ones\"),\n        sampling_type=\"particle\",\n        function=particle_ones,\n        units=\"\",\n        display_name=r\"Particle Count\",\n    )\n\n    def particle_mesh_ids(data):\n        pos = data[ptype, coord_name]\n        ids = np.zeros(pos.shape[0], dtype=\"float64\") - 1\n        # This is float64 in name only.  It will be properly cast inside the\n        # deposit operation.\n        # _ids = ids.view(\"float64\")\n        data.deposit(pos, [ids], method=\"mesh_id\")\n        return data.apply_units(ids, \"\")\n\n    registry.add_field(\n        (ptype, \"mesh_id\"),\n        sampling_type=\"particle\",\n        function=particle_mesh_ids,\n        validators=[ValidateSpatial()],\n        units=\"\",\n    )\n\n    return list(set(registry.keys()).difference(orig))\n\n\ndef particle_scalar_functions(ptype, coord_name, vel_name, registry):\n    # Now we have to set up the various velocity and coordinate things.  In the\n    # future, we'll actually invert this and use the 3-component items\n    # elsewhere, and stop using these.\n\n    # Note that we pass in _ptype here so that it's defined inside the closure.\n\n    def _get_coord_funcs(axi, _ptype):\n        def _particle_velocity(data):\n            return data[_ptype, vel_name][:, axi]\n\n        def _particle_position(data):\n            return data[_ptype, coord_name][:, axi]\n\n        return _particle_velocity, _particle_position\n\n    for axi, ax in enumerate(\"xyz\"):\n        v, p = _get_coord_funcs(axi, ptype)\n        registry.add_field(\n            (ptype, f\"particle_velocity_{ax}\"),\n            sampling_type=\"particle\",\n            function=v,\n            units=\"code_velocity\",\n        )\n        registry.add_field(\n            (ptype, f\"particle_position_{ax}\"),\n            sampling_type=\"particle\",\n            function=p,\n            units=\"code_length\",\n        )\n\n\ndef particle_vector_functions(ptype, coord_names, vel_names, registry):\n    unit_system = registry.ds.unit_system\n\n    # This will column_stack a set of scalars to create vector fields.\n\n    def _get_vec_func(_ptype, names):\n        def particle_vectors(field, data):\n            v = [data[_ptype, name].in_units(field.units) for name in names]\n            return data.ds.arr(np.column_stack(v), v[0].units)\n\n        return particle_vectors\n\n    registry.add_field(\n        (ptype, \"particle_position\"),\n        sampling_type=\"particle\",\n        function=_get_vec_func(ptype, coord_names),\n        units=\"code_length\",\n    )\n\n    registry.add_field(\n        (ptype, \"particle_velocity\"),\n        sampling_type=\"particle\",\n        function=_get_vec_func(ptype, vel_names),\n        units=unit_system[\"velocity\"],\n    )\n\n\ndef get_angular_momentum_components(ptype, data, spos, svel):\n    normal = data.ds.arr([0.0, 0.0, 1.0], \"code_length\")  # default to simulation axis\n    pos = data.ds.arr([data[ptype, spos % ax] for ax in \"xyz\"]).T\n    vel = data.ds.arr([data[ptype, f\"relative_{svel % ax}\"] for ax in \"xyz\"]).T\n    return pos, vel, normal\n\n\ndef standard_particle_fields(\n    registry, ptype, spos=\"particle_position_%s\", svel=\"particle_velocity_%s\"\n):\n    unit_system = registry.ds.unit_system\n\n    def _particle_velocity_magnitude(data):\n        \"\"\"M{|v|}\"\"\"\n        return np.sqrt(\n            data[ptype, f\"relative_{svel % 'x'}\"] ** 2\n            + data[ptype, f\"relative_{svel % 'y'}\"] ** 2\n            + data[ptype, f\"relative_{svel % 'z'}\"] ** 2\n        )\n\n    registry.add_field(\n        (ptype, \"particle_velocity_magnitude\"),\n        sampling_type=\"particle\",\n        function=_particle_velocity_magnitude,\n        take_log=False,\n        units=unit_system[\"velocity\"],\n    )\n\n    create_los_field(\n        registry,\n        \"particle_velocity\",\n        unit_system[\"velocity\"],\n        ftype=ptype,\n        sampling_type=\"particle\",\n    )\n\n    def _particle_specific_angular_momentum(data):\n        \"\"\"Calculate the angular of a particle velocity.\n\n        Returns a vector for each particle.\n        \"\"\"\n        center = data.get_field_parameter(\"center\")\n        pos, vel, normal = get_angular_momentum_components(ptype, data, spos, svel)\n        L, r_vec, v_vec = modify_reference_frame(center, normal, P=pos, V=vel)\n        # adding in the unit registry allows us to have a reference to the\n        # dataset and thus we will always get the correct units after applying\n        # the cross product.\n        return ucross(r_vec, v_vec, registry=data.ds.unit_registry)\n\n    registry.add_field(\n        (ptype, \"particle_specific_angular_momentum\"),\n        sampling_type=\"particle\",\n        function=_particle_specific_angular_momentum,\n        units=unit_system[\"specific_angular_momentum\"],\n        validators=[ValidateParameter(\"center\")],\n    )\n\n    def _get_spec_ang_mom_comp(axi, ax, _ptype):\n        def _particle_specific_angular_momentum_component(data):\n            return data[_ptype, \"particle_specific_angular_momentum\"][:, axi]\n\n        def _particle_angular_momentum_component(data):\n            return (\n                data[_ptype, \"particle_mass\"]\n                * data[ptype, f\"particle_specific_angular_momentum_{ax}\"]\n            )\n\n        return (\n            _particle_specific_angular_momentum_component,\n            _particle_angular_momentum_component,\n        )\n\n    for axi, ax in enumerate(\"xyz\"):\n        f, v = _get_spec_ang_mom_comp(axi, ax, ptype)\n        registry.add_field(\n            (ptype, f\"particle_specific_angular_momentum_{ax}\"),\n            sampling_type=\"particle\",\n            function=f,\n            units=unit_system[\"specific_angular_momentum\"],\n            validators=[ValidateParameter(\"center\")],\n        )\n        registry.add_field(\n            (ptype, f\"particle_angular_momentum_{ax}\"),\n            sampling_type=\"particle\",\n            function=v,\n            units=unit_system[\"angular_momentum\"],\n            validators=[ValidateParameter(\"center\")],\n        )\n\n    def _particle_angular_momentum(data):\n        am = (\n            data[ptype, \"particle_mass\"]\n            * data[ptype, \"particle_specific_angular_momentum\"].T\n        )\n        return am.T\n\n    registry.add_field(\n        (ptype, \"particle_angular_momentum\"),\n        sampling_type=\"particle\",\n        function=_particle_angular_momentum,\n        units=unit_system[\"angular_momentum\"],\n        validators=[ValidateParameter(\"center\")],\n    )\n\n    create_magnitude_field(\n        registry,\n        \"particle_angular_momentum\",\n        unit_system[\"angular_momentum\"],\n        sampling_type=\"particle\",\n        ftype=ptype,\n    )\n\n    def _particle_radius(field, data):\n        \"\"\"The spherical radius component of the particle positions\n\n        Relative to the coordinate system defined by the *normal* vector,\n        and *center* field parameters.\n        \"\"\"\n        return get_radius(data, \"particle_position_\", field.name[0])\n\n    registry.add_field(\n        (ptype, \"particle_radius\"),\n        sampling_type=\"particle\",\n        function=_particle_radius,\n        units=unit_system[\"length\"],\n        validators=[ValidateParameter(\"center\")],\n    )\n\n    def _relative_particle_position(data):\n        \"\"\"The cartesian particle positions in a rotated reference frame\n\n        Relative to the coordinate system defined by *center* field parameter.\n\n        Note that the orientation of the x and y axes are arbitrary.\n        \"\"\"\n        field_names = [(ptype, f\"particle_position_{ax}\") for ax in \"xyz\"]\n        return obtain_position_vector(data, field_names=field_names).T\n\n    registry.add_field(\n        (ptype, \"relative_particle_position\"),\n        sampling_type=\"particle\",\n        function=_relative_particle_position,\n        units=unit_system[\"length\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _relative_particle_velocity(data):\n        \"\"\"The vector particle velocities in an arbitrary coordinate system\n\n        Relative to the coordinate system defined by the *bulk_velocity*\n        vector field parameter.\n\n        Note that the orientation of the x and y axes are arbitrary.\n        \"\"\"\n        field_names = [(ptype, f\"particle_velocity_{ax}\") for ax in \"xyz\"]\n        return obtain_relative_velocity_vector(data, field_names=field_names).T\n\n    registry.add_field(\n        (ptype, \"relative_particle_velocity\"),\n        sampling_type=\"particle\",\n        function=_relative_particle_velocity,\n        units=unit_system[\"velocity\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _get_coord_funcs_relative(axi, _ptype):\n        def _particle_pos_rel(data):\n            return data[_ptype, \"relative_particle_position\"][:, axi]\n\n        def _particle_vel_rel(data):\n            return data[_ptype, \"relative_particle_velocity\"][:, axi]\n\n        return _particle_vel_rel, _particle_pos_rel\n\n    for axi, ax in enumerate(\"xyz\"):\n        v, p = _get_coord_funcs_relative(axi, ptype)\n        registry.add_field(\n            (ptype, f\"particle_velocity_relative_{ax}\"),\n            sampling_type=\"particle\",\n            function=v,\n            units=\"code_velocity\",\n        )\n        registry.add_field(\n            (ptype, f\"particle_position_relative_{ax}\"),\n            sampling_type=\"particle\",\n            function=p,\n            units=\"code_length\",\n        )\n        registry.add_field(\n            (ptype, f\"relative_particle_velocity_{ax}\"),\n            sampling_type=\"particle\",\n            function=v,\n            units=\"code_velocity\",\n        )\n        registry.add_field(\n            (ptype, f\"relative_particle_position_{ax}\"),\n            sampling_type=\"particle\",\n            function=p,\n            units=\"code_length\",\n        )\n\n    # this is just particle radius but we add it with an alias for the sake of\n    # consistent naming\n    registry.add_field(\n        (ptype, \"particle_position_spherical_radius\"),\n        sampling_type=\"particle\",\n        function=_particle_radius,\n        units=unit_system[\"length\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _particle_position_spherical_theta(data):\n        \"\"\"The spherical theta coordinate of the particle positions.\n\n        Relative to the coordinate system defined by the *normal* vector\n        and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        return data.ds.arr(get_sph_theta(pos, normal), \"\")\n\n    registry.add_field(\n        (ptype, \"particle_position_spherical_theta\"),\n        sampling_type=\"particle\",\n        function=_particle_position_spherical_theta,\n        units=\"\",\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"normal\")],\n    )\n\n    def _particle_position_spherical_phi(data):\n        \"\"\"The spherical phi component of the particle positions\n\n        Relative to the coordinate system defined by the *normal* vector\n        and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        return data.ds.arr(get_sph_phi(pos, normal), \"\")\n\n    registry.add_field(\n        (ptype, \"particle_position_spherical_phi\"),\n        sampling_type=\"particle\",\n        function=_particle_position_spherical_phi,\n        units=\"\",\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _particle_velocity_spherical_radius(data):\n        \"\"\"The spherical radius component of the particle velocities in an\n         arbitrary coordinate system\n\n        Relative to the coordinate system defined by the *normal* vector,\n        *bulk_velocity* vector and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        vel = data[ptype, \"relative_particle_velocity\"].T\n        theta = get_sph_theta(pos, normal)\n        phi = get_sph_phi(pos, normal)\n        sphr = get_sph_r_component(vel, theta, phi, normal)\n        return sphr\n\n    registry.add_field(\n        (ptype, \"particle_velocity_spherical_radius\"),\n        sampling_type=\"particle\",\n        function=_particle_velocity_spherical_radius,\n        units=unit_system[\"velocity\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    registry.alias(\n        (ptype, \"particle_radial_velocity\"),\n        (ptype, \"particle_velocity_spherical_radius\"),\n    )\n\n    def _particle_velocity_spherical_theta(data):\n        \"\"\"The spherical theta component of the particle velocities in an\n         arbitrary coordinate system\n\n        Relative to the coordinate system defined by the *normal* vector,\n        *bulk_velocity* vector and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        vel = data[ptype, \"relative_particle_velocity\"].T\n        theta = get_sph_theta(pos, normal)\n        phi = get_sph_phi(pos, normal)\n        spht = get_sph_theta_component(vel, theta, phi, normal)\n        return spht\n\n    registry.add_field(\n        (ptype, \"particle_velocity_spherical_theta\"),\n        sampling_type=\"particle\",\n        function=_particle_velocity_spherical_theta,\n        units=unit_system[\"velocity\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _particle_velocity_spherical_phi(data):\n        \"\"\"The spherical phi component of the particle velocities\n\n        Relative to the coordinate system defined by the *normal* vector,\n        *bulk_velocity* vector and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        vel = data[ptype, \"relative_particle_velocity\"].T\n        phi = get_sph_phi(pos, normal)\n        sphp = get_sph_phi_component(vel, phi, normal)\n        return sphp\n\n    registry.add_field(\n        (ptype, \"particle_velocity_spherical_phi\"),\n        sampling_type=\"particle\",\n        function=_particle_velocity_spherical_phi,\n        units=unit_system[\"velocity\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _particle_position_cylindrical_radius(data):\n        \"\"\"The cylindrical radius component of the particle positions\n\n        Relative to the coordinate system defined by the *normal* vector\n        and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        pos.convert_to_units(\"code_length\")\n        return data.ds.arr(get_cyl_r(pos, normal), \"code_length\")\n\n    registry.add_field(\n        (ptype, \"particle_position_cylindrical_radius\"),\n        sampling_type=\"particle\",\n        function=_particle_position_cylindrical_radius,\n        units=unit_system[\"length\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _particle_position_cylindrical_theta(data):\n        \"\"\"The cylindrical theta component of the particle positions\n\n        Relative to the coordinate system defined by the *normal* vector\n        and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        return data.ds.arr(get_cyl_theta(pos, normal), \"\")\n\n    registry.add_field(\n        (ptype, \"particle_position_cylindrical_theta\"),\n        sampling_type=\"particle\",\n        function=_particle_position_cylindrical_theta,\n        units=\"\",\n        validators=[ValidateParameter(\"center\"), ValidateParameter(\"normal\")],\n    )\n\n    def _particle_position_cylindrical_z(data):\n        \"\"\"The cylindrical z component of the particle positions\n\n        Relative to the coordinate system defined by the *normal* vector\n        and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        pos.convert_to_units(\"code_length\")\n        return data.ds.arr(get_cyl_z(pos, normal), \"code_length\")\n\n    registry.add_field(\n        (ptype, \"particle_position_cylindrical_z\"),\n        sampling_type=\"particle\",\n        function=_particle_position_cylindrical_z,\n        units=unit_system[\"length\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _particle_velocity_cylindrical_radius(data):\n        \"\"\"The cylindrical radius component of the particle velocities\n\n        Relative to the coordinate system defined by the *normal* vector,\n        *bulk_velocity* vector and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        vel = data[ptype, \"relative_particle_velocity\"].T\n        theta = get_cyl_theta(pos, normal)\n        cylr = get_cyl_r_component(vel, theta, normal)\n        return cylr\n\n    registry.add_field(\n        (ptype, \"particle_velocity_cylindrical_radius\"),\n        sampling_type=\"particle\",\n        function=_particle_velocity_cylindrical_radius,\n        units=unit_system[\"velocity\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _particle_velocity_cylindrical_theta(data):\n        \"\"\"The cylindrical theta component of the particle velocities\n\n        Relative to the coordinate system defined by the *normal* vector,\n        *bulk_velocity* vector and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        pos = data[ptype, \"relative_particle_position\"].T\n        vel = data[ptype, \"relative_particle_velocity\"].T\n        theta = get_cyl_theta(pos, normal)\n        cylt = get_cyl_theta_component(vel, theta, normal)\n        return cylt\n\n    registry.add_field(\n        (ptype, \"particle_velocity_cylindrical_theta\"),\n        sampling_type=\"particle\",\n        function=_particle_velocity_cylindrical_theta,\n        units=unit_system[\"velocity\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n    def _particle_velocity_cylindrical_z(data):\n        \"\"\"The cylindrical z component of the particle velocities\n\n        Relative to the coordinate system defined by the *normal* vector,\n        *bulk_velocity* vector and *center* field parameters.\n        \"\"\"\n        normal = data.get_field_parameter(\"normal\")\n        vel = data[ptype, \"relative_particle_velocity\"].T\n        cylz = get_cyl_z_component(vel, normal)\n        return cylz\n\n    registry.add_field(\n        (ptype, \"particle_velocity_cylindrical_z\"),\n        sampling_type=\"particle\",\n        function=_particle_velocity_cylindrical_z,\n        units=unit_system[\"velocity\"],\n        validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n    )\n\n\ndef add_particle_average(registry, ptype, field_name, weight=None, density=True):\n    if weight is None:\n        weight = (ptype, \"particle_mass\")\n    field_units = registry[ptype, field_name].units\n\n    def _pfunc_avg(data):\n        pos = data[ptype, \"particle_position\"]\n        f = data[ptype, field_name]\n        wf = data[ptype, weight]\n        f *= wf\n        v = data.deposit(pos, [f], method=\"sum\")\n        w = data.deposit(pos, [wf], method=\"sum\")\n        v /= w\n        if density:\n            v /= data[\"index\", \"cell_volume\"]\n        v[np.isnan(v)] = 0.0\n        return v\n\n    fn = (\"deposit\", f\"{ptype}_avg_{field_name}\")\n    registry.add_field(\n        fn,\n        sampling_type=\"cell\",\n        function=_pfunc_avg,\n        validators=[ValidateSpatial(0)],\n        units=field_units,\n    )\n    return fn\n\n\ndef add_nearest_neighbor_field(ptype, coord_name, registry, nneighbors=64):\n    field_name = (ptype, f\"nearest_neighbor_distance_{nneighbors}\")\n\n    def _nth_neighbor(data):\n        pos = data[ptype, coord_name]\n        pos.convert_to_units(\"code_length\")\n        distances = 0.0 * pos[:, 0]\n        data.particle_operation(\n            pos, [distances], method=\"nth_neighbor\", nneighbors=nneighbors\n        )\n        # Now some quick unit conversions.\n        return distances\n\n    registry.add_field(\n        field_name,\n        sampling_type=\"particle\",\n        function=_nth_neighbor,\n        validators=[ValidateSpatial(0)],\n        units=\"code_length\",\n    )\n    return [field_name]\n\n\ndef add_nearest_neighbor_value_field(ptype, coord_name, sampled_field, registry):\n    \"\"\"\n    This adds a nearest-neighbor field, where values on the mesh are assigned\n    based on the nearest particle value found.  This is useful, for instance,\n    with voronoi-tesselations.\n    \"\"\"\n    field_name = (\"deposit\", f\"{ptype}_nearest_{sampled_field}\")\n    field_units = registry[ptype, sampled_field].units\n    unit_system = registry.ds.unit_system\n\n    def _nearest_value(data):\n        pos = data[ptype, coord_name]\n        pos = pos.convert_to_units(\"code_length\")\n        value = data[ptype, sampled_field].in_base(unit_system.name)\n        rv = data.smooth(\n            pos, [value], method=\"nearest\", create_octree=True, nneighbors=1\n        )\n        rv = data.apply_units(rv, field_units)\n        return rv\n\n    registry.add_field(\n        field_name,\n        sampling_type=\"cell\",\n        function=_nearest_value,\n        validators=[ValidateSpatial(0)],\n        units=field_units,\n    )\n    return [field_name]\n\n\ndef add_union_field(registry, ptype, field_name, units):\n    \"\"\"\n    Create a field that is the concatenation of multiple particle types.\n    This allows us to create fields for particle unions using alias names.\n    \"\"\"\n\n    def _cat_field(data):\n        return uconcatenate(\n            [data[dep_type, field_name] for dep_type in data.ds.particle_types_raw]\n        )\n\n    registry.add_field(\n        (ptype, field_name), sampling_type=\"particle\", function=_cat_field, units=units\n    )\n"
  },
  {
    "path": "yt/fields/species_fields.py",
    "content": "import re\n\nimport numpy as np\n\nfrom yt.frontends.sph.data_structures import ParticleDataset\nfrom yt.utilities.chemical_formulas import ChemicalFormula\nfrom yt.utilities.physical_ratios import _primordial_mass_fraction\n\nfrom .field_plugin_registry import register_field_plugin\n\n# See YTEP-0003 for details, but we want to ensure these fields are all\n# populated:\n#\n#   * _mass\n#   * _density\n#   * _fraction\n#   * _number_density\n#\n\n\ndef _create_fraction_func(ftype, species):\n    def _frac(data):\n        return data[ftype, f\"{species}_density\"] / data[ftype, \"density\"]\n\n    return _frac\n\n\ndef _mass_from_cell_volume_and_density(ftype, species):\n    def _mass(data):\n        return data[ftype, f\"{species}_density\"] * data[\"index\", \"cell_volume\"]\n\n    return _mass\n\n\ndef _mass_from_particle_mass_and_fraction(ftype, species):\n    def _mass(data):\n        return data[ftype, f\"{species}_fraction\"] * data[ftype, \"particle_mass\"]\n\n    return _mass\n\n\ndef _create_number_density_func(ftype, species):\n    formula = ChemicalFormula(species)\n\n    def _number_density(data):\n        weight = formula.weight  # This is in AMU\n        weight *= data.ds.quan(1.0, \"amu\").in_cgs()\n        return data[ftype, f\"{species}_density\"] / weight\n\n    return _number_density\n\n\ndef _create_density_func(ftype, species):\n    def _density(data):\n        return data[ftype, f\"{species}_fraction\"] * data[ftype, \"density\"]\n\n    return _density\n\n\ndef add_species_field_by_density(registry, ftype, species):\n    \"\"\"\n    This takes a field registry, a fluid type, and a species name and then\n    adds the other fluids based on that.  This assumes that the field\n    \"SPECIES_density\" already exists and refers to mass density.\n    \"\"\"\n    unit_system = registry.ds.unit_system\n\n    registry.add_field(\n        (ftype, f\"{species}_fraction\"),\n        sampling_type=\"local\",\n        function=_create_fraction_func(ftype, species),\n        units=\"\",\n    )\n\n    if isinstance(registry.ds, ParticleDataset):\n        _create_mass_func = _mass_from_particle_mass_and_fraction\n    else:\n        _create_mass_func = _mass_from_cell_volume_and_density\n    registry.add_field(\n        (ftype, f\"{species}_mass\"),\n        sampling_type=\"local\",\n        function=_create_mass_func(ftype, species),\n        units=unit_system[\"mass\"],\n    )\n\n    registry.add_field(\n        (ftype, f\"{species}_number_density\"),\n        sampling_type=\"local\",\n        function=_create_number_density_func(ftype, species),\n        units=unit_system[\"number_density\"],\n    )\n\n    return [\n        (ftype, f\"{species}_number_density\"),\n        (ftype, f\"{species}_density\"),\n        (ftype, f\"{species}_mass\"),\n    ]\n\n\ndef add_species_field_by_fraction(registry, ftype, species):\n    \"\"\"\n    This takes a field registry, a fluid type, and a species name and then\n    adds the other fluids based on that.  This assumes that the field\n    \"SPECIES_fraction\" already exists and refers to mass fraction.\n    \"\"\"\n    unit_system = registry.ds.unit_system\n\n    registry.add_field(\n        (ftype, f\"{species}_density\"),\n        sampling_type=\"local\",\n        function=_create_density_func(ftype, species),\n        units=unit_system[\"density\"],\n    )\n\n    if isinstance(registry.ds, ParticleDataset):\n        _create_mass_func = _mass_from_particle_mass_and_fraction\n    else:\n        _create_mass_func = _mass_from_cell_volume_and_density\n    registry.add_field(\n        (ftype, f\"{species}_mass\"),\n        sampling_type=\"local\",\n        function=_create_mass_func(ftype, species),\n        units=unit_system[\"mass\"],\n    )\n\n    registry.add_field(\n        (ftype, f\"{species}_number_density\"),\n        sampling_type=\"local\",\n        function=_create_number_density_func(ftype, species),\n        units=unit_system[\"number_density\"],\n    )\n\n    return [\n        (ftype, f\"{species}_number_density\"),\n        (ftype, f\"{species}_density\"),\n        (ftype, f\"{species}_mass\"),\n    ]\n\n\ndef add_species_aliases(registry, ftype, alias_species, species):\n    r\"\"\"\n    This takes a field registry, a fluid type, and two species names.\n    The first species name is one you wish to alias to an existing species\n    name.  For instance you might alias all \"H_p0\" fields to \"H\\_\" fields\n    to indicate that \"H\\_\" fields are really just neutral hydrogen fields.\n    This function registers field aliases for the density, number_density,\n    mass, and fraction fields between the two species given in the arguments.\n    \"\"\"\n    registry.alias((ftype, f\"{alias_species}_density\"), (ftype, f\"{species}_density\"))\n    registry.alias((ftype, f\"{alias_species}_fraction\"), (ftype, f\"{species}_fraction\"))\n    registry.alias(\n        (ftype, f\"{alias_species}_number_density\"),\n        (ftype, f\"{species}_number_density\"),\n    )\n    registry.alias((ftype, f\"{alias_species}_mass\"), (ftype, f\"{species}_mass\"))\n\n\ndef add_deprecated_species_aliases(registry, ftype, alias_species, species):\n    \"\"\"\n    Add the species aliases but with deprecation warnings.\n    \"\"\"\n\n    for suffix in [\"density\", \"fraction\", \"number_density\", \"mass\"]:\n        add_deprecated_species_alias(registry, ftype, alias_species, species, suffix)\n\n\ndef add_deprecated_species_alias(registry, ftype, alias_species, species, suffix):\n    \"\"\"\n    Add a deprecated species alias field.\n    \"\"\"\n\n    unit_system = registry.ds.unit_system\n    if suffix == \"fraction\":\n        my_units = \"\"\n    else:\n        my_units = unit_system[suffix]\n\n    def _dep_field(data):\n        return data[ftype, f\"{species}_{suffix}\"]\n\n    registry.add_field(\n        (ftype, f\"{alias_species}_{suffix}\"),\n        sampling_type=\"local\",\n        function=_dep_field,\n        units=my_units,\n    )\n\n\ndef add_nuclei_density_fields(registry, ftype):\n    unit_system = registry.ds.unit_system\n    elements = _get_all_elements(registry.species_names)\n    for element in elements:\n        registry.add_field(\n            (ftype, f\"{element}_nuclei_density\"),\n            sampling_type=\"local\",\n            function=_nuclei_density,\n            units=unit_system[\"number_density\"],\n        )\n\n    # Here, we add default nuclei and number density fields for H and\n    # He if they are not defined above, and if it was requested by\n    # setting \"default_species_fields\"\n    if registry.ds.default_species_fields is None:\n        return\n\n    dsf = registry.ds.default_species_fields\n    # Right now, this only handles default fields for H and He\n    for element in [\"H\", \"He\"]:\n        # If these elements are already present in the dataset,\n        # DO NOT set them\n        if element in elements:\n            continue\n        # First add the default nuclei density fields\n        registry.add_field(\n            (ftype, f\"{element}_nuclei_density\"),\n            sampling_type=\"local\",\n            function=_default_nuclei_density,\n            units=unit_system[\"number_density\"],\n        )\n        # Set up number density fields for hydrogen, either fully ionized or neutral.\n        if element == \"H\":\n            if dsf == \"ionized\":\n                state = \"p1\"\n            elif dsf == \"neutral\":\n                state = \"p0\"\n            else:\n                raise NotImplementedError(\n                    f\"'default_species_fields' option '{dsf}' is not implemented!\"\n                )\n            registry.alias(\n                (ftype, f\"H_{state}_number_density\"), (ftype, \"H_nuclei_density\")\n            )\n        # Set up number density fields for helium, either fully ionized or neutral.\n        if element == \"He\":\n            if dsf == \"ionized\":\n                state = \"p2\"\n            elif dsf == \"neutral\":\n                state = \"p0\"\n            registry.alias(\n                (ftype, f\"He_{state}_number_density\"), (ftype, \"He_nuclei_density\")\n            )\n    # If we're fully ionized, we need to setup the electron number density field\n    if (ftype, \"El_number_density\") not in registry and dsf == \"ionized\":\n        registry.add_field(\n            (ftype, \"El_number_density\"),\n            sampling_type=\"local\",\n            function=_default_nuclei_density,\n            units=unit_system[\"number_density\"],\n        )\n\n\ndef _default_nuclei_density(field, data):\n    ftype = field.name[0]\n    element = field.name[1][: field.name[1].find(\"_\")]\n    amu_cgs = data.ds.quan(1.0, \"amu\").in_cgs()\n    if element == \"El\":\n        # This is for determining the electron number density.\n        # If we got here, this assumes full ionization!\n        muinv = 1.0 * _primordial_mass_fraction[\"H\"] / ChemicalFormula(\"H\").weight\n        muinv += 2.0 * _primordial_mass_fraction[\"He\"] / ChemicalFormula(\"He\").weight\n    else:\n        # This is for anything else besides electrons\n        muinv = _primordial_mass_fraction[element] / ChemicalFormula(element).weight\n    return data[ftype, \"density\"] * muinv / amu_cgs\n\n\ndef _nuclei_density(field, data):\n    ftype = field.name[0]\n    element = field.name[1][: field.name[1].find(\"_\")]\n\n    nuclei_mass_field = f\"{element}_nuclei_mass_density\"\n    if (ftype, nuclei_mass_field) in data.ds.field_info:\n        return (\n            data[ftype, nuclei_mass_field]\n            / ChemicalFormula(element).weight\n            / data.ds.quan(1.0, \"amu\").in_cgs()\n        )\n    metal_field = f\"{element}_metallicity\"\n    if (ftype, metal_field) in data.ds.field_info:\n        return (\n            data[ftype, \"density\"]\n            * data[ftype, metal_field]\n            / ChemicalFormula(element).weight\n            / data.ds.quan(1.0, \"amu\").in_cgs()\n        )\n\n    field_data = np.zeros_like(\n        data[ftype, f\"{data.ds.field_info.species_names[0]}_number_density\"]\n    )\n    for species in data.ds.field_info.species_names:\n        nucleus = species\n        if \"_\" in species:\n            nucleus = species[: species.find(\"_\")]\n        # num is the number of nuclei contributed by this species.\n        num = _get_element_multiple(nucleus, element)\n        # Since this is a loop over all species existing in this dataset,\n        # we will encounter species that contribute nothing, so we skip them.\n        if num == 0:\n            continue\n        field_data += num * data[ftype, f\"{species}_number_density\"]\n    return field_data\n\n\ndef _get_all_elements(species_list):\n    elements = []\n    for species in species_list:\n        for item in re.findall(\"[A-Z][a-z]?|[0-9]+\", species):\n            if not item.isdigit() and item not in elements and item != \"El\":\n                elements.append(item)\n    return elements\n\n\ndef _get_element_multiple(compound, element):\n    my_split = re.findall(\"[A-Z][a-z]?|[0-9]+\", compound)\n    if element not in my_split:\n        return 0\n    loc = my_split.index(element)\n    if loc == len(my_split) - 1 or not my_split[loc + 1].isdigit():\n        return 1\n    return int(my_split[loc + 1])\n\n\n@register_field_plugin\ndef setup_species_fields(registry, ftype=\"gas\", slice_info=None):\n    for species in registry.species_names:\n        # These are all the species we should be looking for fractions or\n        # densities of.\n        if (ftype, f\"{species}_density\") in registry:\n            func = add_species_field_by_density\n        elif (ftype, f\"{species}_fraction\") in registry:\n            func = add_species_field_by_fraction\n        else:\n            # Skip it\n            continue\n        func(registry, ftype, species)\n\n        # Add aliases of X_p0_<field> to X_<field>.\n        # These are deprecated and will be removed soon.\n        if ChemicalFormula(species).charge == 0:\n            alias_species = species.split(\"_\")[0]\n            if (ftype, f\"{alias_species}_density\") in registry:\n                continue\n            add_deprecated_species_aliases(registry, \"gas\", alias_species, species)\n\n    add_nuclei_density_fields(registry, ftype)\n"
  },
  {
    "path": "yt/fields/tensor_fields.py",
    "content": "from functools import partial\n\n\n# This is the metric for Minkowski spacetime in SR\ndef metric(mu: int, nu: int):\n    # This assumes the -+++ signature\n    if (mu, nu) == (0, 0):\n        return -1\n    elif mu == nu:\n        return 1\n    else:\n        return 0\n\n\ndef setup_stress_energy_ideal(registry, ftype=\"gas\"):\n    ax = (\"t\",) + registry.ds.coordinates.axis_order\n    pc = registry.ds.units.physical_constants\n    inv_c2 = 1.0 / (pc.clight * pc.clight)\n\n    def _T(field, data, mu: int, nu: int):\n        Umu = data[ftype, f\"four_velocity_{ax[mu]}\"]\n        Unu = data[ftype, f\"four_velocity_{ax[nu]}\"]\n        p = data[ftype, \"pressure\"]\n        e = data[ftype, \"thermal_energy_density\"]\n        rho = data[ftype, \"density\"]\n        return (rho + (e + p) * inv_c2) * Umu * Unu + metric(mu, nu) * p\n\n    for mu in range(5):\n        for nu in range(5):\n            registry.add_field(\n                (ftype, f\"T{mu}{nu}\"),\n                sampling_type=\"local\",\n                function=partial(_T, mu=mu, nu=nu),\n                units=registry.ds.unit_system[\"pressure\"],\n            )\n"
  },
  {
    "path": "yt/fields/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/fields/tests/test_ambiguous_fields.py",
    "content": "import pytest\n\nfrom yt.testing import fake_amr_ds\n\n\ndef test_ambiguous_fails():\n    ds = fake_amr_ds(particles=10)\n    msg = \"The requested field name '{}' is ambiguous\"\n\n    def _mock_field(data):\n        return data[\"ones\"]\n\n    # create a pair of ambiguous field\n    ds.add_field((\"io\", \"mock_field\"), _mock_field, sampling_type=\"particle\")\n    ds.add_field((\"gas\", \"mock_field\"), _mock_field, sampling_type=\"cell\")\n\n    # Test errors are raised for ambiguous fields\n    with pytest.raises(ValueError, match=msg.format(\"mock_field\")):\n        ds.r[\"mock_field\"]\n\n    # check that explicit name tuples don't raise a warning\n    ds.r[\"io\", \"mock_field\"]\n    ds.r[\"gas\", \"mock_field\"]\n\n\ndef test_nameonly_field_with_all_aliases_candidates():\n    # see https://github.com/yt-project/yt/issues/3839\n    ds = fake_amr_ds(fields=[\"density\"], units=[\"g/cm**3\"])\n\n    # here we rely on implementations details from fake_amr_ds,\n    # so we verify that it provides the appropriate conditions\n    # for the actual test.\n    candidates = [f for f in ds.derived_field_list if f[1] == \"density\"]\n    assert len(candidates) == 2\n    fi = ds.field_info\n    assert fi[candidates[0]].is_alias_to(fi[candidates[1]])\n\n    # this is the actual test (check that no error or warning is raised)\n    ds.all_data()[\"density\"]\n"
  },
  {
    "path": "yt/fields/tests/test_angular_momentum.py",
    "content": "import numpy as np\n\nfrom yt.testing import assert_allclose_units, fake_amr_ds\n\n\ndef test_AM_value():\n    ds = fake_amr_ds(\n        fields=(\"Density\", \"velocity_x\", \"velocity_y\", \"velocity_z\"),\n        units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n        length_unit=0.5,\n    )\n\n    sp = ds.sphere([0.5] * 3, (0.1, \"code_length\"))\n\n    x0 = sp.center\n    v0 = ds.arr([1, 2, 3], \"km/s\")\n\n    sp.set_field_parameter(\"bulk_velocity\", v0)\n\n    X = (ds.arr([sp[\"index\", k] for k in \"xyz\"]) - x0[:, None]).T\n    V = (ds.arr([sp[\"gas\", f\"velocity_{k}\"] for k in \"xyz\"]) - v0[:, None]).T\n\n    sAM_manual = ds.arr(np.cross(X, V), X.units * V.units)\n    sAM = ds.arr([sp[\"gas\", f\"specific_angular_momentum_{k}\"] for k in \"xyz\"]).T\n\n    assert_allclose_units(sAM_manual, sAM)\n"
  },
  {
    "path": "yt/fields/tests/test_field_access.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.data_objects.profiles import create_profile\nfrom yt.testing import fake_random_ds\nfrom yt.visualization.plot_window import ProjectionPlot, SlicePlot\nfrom yt.visualization.profile_plotter import PhasePlot, ProfilePlot\n\n\ndef test_field_access():\n    ds = fake_random_ds(16)\n\n    ad = ds.all_data()\n    sp = ds.sphere(ds.domain_center, 0.25)\n    cg = ds.covering_grid(0, ds.domain_left_edge, ds.domain_dimensions)\n    scg = ds.smoothed_covering_grid(0, ds.domain_left_edge, ds.domain_dimensions)\n    sl = ds.slice(0, ds.domain_center[0])\n    proj = ds.proj((\"gas\", \"density\"), 0)\n    prof = create_profile(ad, (\"index\", \"radius\"), (\"gas\", \"density\"))\n\n    for data_object in [ad, sp, cg, scg, sl, proj, prof]:\n        assert_equal(data_object[\"gas\", \"density\"], data_object[ds.fields.gas.density])\n\n    for field in [(\"gas\", \"density\"), ds.fields.gas.density]:\n        ad = ds.all_data()\n        prof = ProfilePlot(ad, (\"index\", \"radius\"), field)\n        phase = PhasePlot(ad, (\"index\", \"radius\"), field, (\"gas\", \"cell_mass\"))\n        s = SlicePlot(ds, 2, field)\n        oas = SlicePlot(ds, [1, 1, 1], field)\n        p = ProjectionPlot(ds, 2, field)\n        oap = ProjectionPlot(ds, [1, 1, 1], field)\n\n        for plot_object in [s, oas, p, oap, prof, phase]:\n            plot_object.render()\n            if hasattr(plot_object, \"_frb\"):\n                plot_object._frb[field]\n"
  },
  {
    "path": "yt/fields/tests/test_field_access_pytest.py",
    "content": "from collections import defaultdict\nfrom itertools import product\n\nimport pytest\n\nfrom yt.testing import fake_random_ds\nfrom yt.utilities.exceptions import YTFieldNotFound\n\n\ndef test_unexisting_field_access():\n    ds = fake_random_ds(16, particles=10)\n\n    fname2ftype = defaultdict(list)\n\n    for ft, fn in ds.derived_field_list:\n        fname2ftype[fn].append(ft)\n\n    ad = ds.all_data()\n\n    ftypes = (\"gas\", \"io\")\n    fnames = (\n        \"density\",\n        \"particle_position_x\",\n        \"particle_position_y\",\n        \"particle_position_z\",\n    )\n\n    # Try invalid ftypes, fnames combinations\n    for ft, fn in product(ftypes, fnames):\n        if (ft, fn) in ds.derived_field_list:\n            continue\n\n        with pytest.raises(YTFieldNotFound) as excinfo:\n            ad[ft, fn]\n\n        # Make sure the existing field has been suggested\n        for possible_ft in fname2ftype[fn]:\n            assert str((possible_ft, fn)) in str(excinfo.value)\n\n    # Try typos\n    for bad_field, good_field in (\n        ((\"gas\", \"densi_y\"), (\"gas\", \"density\")),\n        ((\"oi\", \"particle_mass\"), (\"io\", \"particle_mass\")),\n        ((\"gas\", \"DENSITY\"), (\"gas\", \"density\")),\n    ):\n        with pytest.raises(YTFieldNotFound) as excinfo:\n            ad[bad_field]\n\n        assert str(good_field) in str(excinfo.value)\n"
  },
  {
    "path": "yt/fields/tests/test_field_name_container.py",
    "content": "from yt import load\nfrom yt.testing import fake_amr_ds, fake_hexahedral_ds, requires_file, requires_module\n\n\ndef do_field_type(ft):\n    assert dir(ft) == sorted(dir(ft))\n    assert sorted(dir(ft)) == sorted(f.name[1] for f in ft)\n    for field_name in dir(ft):\n        f = getattr(ft, field_name)\n        assert (ft.field_type, field_name) == f.name\n    for field in ft:\n        f = getattr(ft, field.name[1])\n        assert f == field\n        assert f in ft\n        assert f.name in ft\n        assert f.name[1] in ft\n\n\nenzotiny = \"enzo_tiny_cosmology/DD0046/DD0046\"\n\n\n@requires_module(\"h5py\")\n@requires_file(enzotiny)\ndef test_field_name_container():\n    ds = load(enzotiny)\n    assert dir(ds.fields) == sorted(dir(ds.fields))\n    assert sorted(ft.field_type for ft in ds.fields) == sorted(dir(ds.fields))\n    for field_type in dir(ds.fields):\n        assert field_type in ds.fields\n        ft = getattr(ds.fields, field_type)\n        do_field_type(ft)\n    for field_type in ds.fields:\n        assert field_type in ds.fields\n        do_field_type(field_type)\n\n\ndef test_vertex_fields_only_in_unstructured_ds():\n    def get_vertex_fields(ds):\n        return [(ft, fn) for ft, fn in ds.derived_field_list if \"vertex\" in fn]\n\n    ds = fake_amr_ds()\n    vertex_fields = get_vertex_fields(ds)\n    assert not vertex_fields\n\n    ds = fake_hexahedral_ds()\n    actual = get_vertex_fields(ds)\n    expected = [\n        (\"all\", \"vertex_x\"),\n        (\"all\", \"vertex_y\"),\n        (\"all\", \"vertex_z\"),\n        (\"connect1\", \"vertex_x\"),\n        (\"connect1\", \"vertex_y\"),\n        (\"connect1\", \"vertex_z\"),\n        (\"index\", \"vertex_x\"),\n        (\"index\", \"vertex_y\"),\n        (\"index\", \"vertex_z\"),\n    ]\n    assert actual == expected\n"
  },
  {
    "path": "yt/fields/tests/test_fields.py",
    "content": "from dataclasses import dataclass\n\nimport numpy as np\nfrom numpy.testing import (\n    assert_almost_equal,\n    assert_array_almost_equal_nulp,\n    assert_array_equal,\n    assert_equal,\n    assert_raises,\n)\n\nfrom yt import load\nfrom yt.data_objects.static_output import Dataset\nfrom yt.frontends.stream.fields import StreamFieldInfo\nfrom yt.testing import (\n    assert_allclose_units,\n    fake_amr_ds,\n    fake_particle_ds,\n    fake_random_ds,\n    requires_file,\n    requires_module,\n)\nfrom yt.units.yt_array import YTArray, YTQuantity, array_like_field\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.exceptions import (\n    YTDimensionalityError,\n    YTFieldUnitError,\n    YTFieldUnitParseError,\n)\n\n\ndef get_params(ds):\n    return {\n        \"axis\": 0,\n        \"center\": YTArray((0.0, 0.0, 0.0), \"cm\", registry=ds.unit_registry),\n        \"bulk_velocity\": YTArray((0.0, 0.0, 0.0), \"cm/s\", registry=ds.unit_registry),\n        \"bulk_magnetic_field\": YTArray((0.0, 0.0, 0.0), \"G\", registry=ds.unit_registry),\n        \"normal\": YTArray((0.0, 0.0, 1.0), \"\", registry=ds.unit_registry),\n        \"cp_x_vec\": YTArray((1.0, 0.0, 0.0), \"\", registry=ds.unit_registry),\n        \"cp_y_vec\": YTArray((0.0, 1.0, 0.0), \"\", registry=ds.unit_registry),\n        \"cp_z_vec\": YTArray((0.0, 0.0, 1.0), \"\", registry=ds.unit_registry),\n        \"omega_baryon\": 0.04,\n        \"observer_redshift\": 0.0,\n        \"source_redshift\": 3.0,\n        \"virial_radius\": YTQuantity(1.0, \"cm\"),\n    }\n\n\n_base_fields = (\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_x\"),\n    (\"gas\", \"velocity_y\"),\n    (\"gas\", \"velocity_z\"),\n)\n\n\ndef _strip_ftype(field):\n    if not isinstance(field, tuple):\n        return field\n    elif field[0] in (\"all\", \"io\"):\n        return field\n    return field[1]\n\n\n@dataclass(slots=True, frozen=True)\nclass FieldAccessTestCase:\n    field_name: str\n    ds: Dataset\n    nprocs: int\n\n    @property\n    def description(self) -> str:\n        return f\"Accessing_{self.field_name}_{self.nprocs}\"\n\n\ndef get_base_ds(nprocs):\n    fields, units = [], []\n\n    for fname, (code_units, *_) in StreamFieldInfo.known_other_fields:\n        fields.append((\"gas\", fname))\n        units.append(code_units)\n\n    pfields, punits = [], []\n\n    for fname, (code_units, _aliases, _dn) in StreamFieldInfo.known_particle_fields:\n        if fname == \"smoothing_lenth\":\n            # we test SPH fields elsewhere\n            continue\n        pfields.append(fname)\n        punits.append(code_units)\n\n    ds = fake_random_ds(\n        4,\n        fields=fields,\n        units=units,\n        particles=20,\n        nprocs=nprocs,\n        particle_fields=pfields,\n        particle_field_units=punits,\n    )\n    ds.parameters[\"HydroMethod\"] = \"streaming\"\n    ds.parameters[\"EOSType\"] = 1.0\n    ds.parameters[\"EOSSoundSpeed\"] = 1.0\n    ds.conversion_factors[\"Time\"] = 1.0\n    ds.conversion_factors.update(dict.fromkeys(fields, 1.0))\n    ds.gamma = 5.0 / 3.0\n    ds.current_redshift = 0.0001\n    ds.cosmological_simulation = 1\n    ds.hubble_constant = 0.7\n    ds.omega_matter = 0.27\n    ds.omega_lambda = 0.73\n    ds.cosmology = Cosmology(\n        hubble_constant=ds.hubble_constant,\n        omega_matter=ds.omega_matter,\n        omega_lambda=ds.omega_lambda,\n        unit_registry=ds.unit_registry,\n    )\n    # ensures field errors are raised during testing\n    # see FieldInfoContainer.check_derived_fields\n    ds._field_test_dataset = True\n    ds.index\n    return ds\n\n\ndef test_all_fields():\n    datasets = {}\n\n    for nprocs in [1, 4, 8]:\n        ds = get_base_ds(nprocs)\n        datasets[nprocs] = ds\n\n    for field in sorted(ds.field_info):\n        if field[1].find(\"beta_p\") > -1:\n            continue\n        if field[1].find(\"vertex\") > -1:\n            # don't test the vertex fields for now\n            continue\n        if field[1].find(\"smoothed\") > -1:\n            # smoothed fields aren't implemented for grid data\n            continue\n        if field in ds.field_list:\n            # Don't know how to test this.  We need some way of having fields\n            # that are fallbacks be tested, but we don't have that now.\n            continue\n\n        for nprocs in [1, 4, 8]:\n            test_all_fields.__name__ = f\"{field}_{nprocs}\"\n\n            tc = FieldAccessTestCase(field, datasets[nprocs], nprocs)\n\n            field = tc.ds._get_field_info(tc.field_name)\n            skip_grids = False\n            needs_spatial = False\n            for v in field.validators:\n                if getattr(v, \"ghost_zones\", 0) > 0:\n                    skip_grids = True\n                if hasattr(v, \"ghost_zones\"):\n                    needs_spatial = True\n\n            ds = tc.ds\n\n            # This gives unequal sized grids as well as subgrids\n            dd1 = ds.all_data()\n            dd2 = ds.all_data()\n            sp = get_params(ds)\n            dd1.field_parameters.update(sp)\n            dd2.field_parameters.update(sp)\n            with np.errstate(all=\"ignore\"):\n                v1 = dd1[tc.field_name]\n                # No more conversion checking\n                assert_equal(v1, dd1[tc.field_name])\n                if not needs_spatial:\n                    with field.unit_registry(dd2):\n                        res = field._eval(dd2)\n                        res = dd2.apply_units(res, field.units)\n                    assert_array_almost_equal_nulp(v1, res, 4)\n                if not skip_grids:\n                    for g in ds.index.grids:\n                        g.field_parameters.update(sp)\n                        v1 = g[tc.field_name]\n                        g.clear_data()\n                        g.field_parameters.update(sp)\n                        r1 = field._eval(g)\n                        if field.sampling_type == \"particle\":\n                            assert_equal(v1.shape[0], g.NumberOfParticles)\n                        else:\n                            assert_array_equal(r1.shape, v1.shape)\n                            for ax in \"xyz\":\n                                assert_array_equal(g[\"index\", ax].shape, v1.shape)\n                        with field.unit_registry(g):\n                            res = field._eval(g)\n                            assert_array_equal(v1.shape, res.shape)\n                            res = g.apply_units(res, field.units)\n                        assert_array_almost_equal_nulp(v1, res, 4)\n\n\ndef test_add_deposited_particle_field():\n    # NOT tested: \"std\", \"mesh_id\", \"nearest\" and \"simple_smooth\"\n    base_ds = get_base_ds(1)\n    ad = base_ds.all_data()\n\n    # Test \"count\", \"sum\" and \"cic\" method\n    for method in [\"count\", \"sum\", \"cic\"]:\n        fn = base_ds.add_deposited_particle_field((\"io\", \"particle_mass\"), method)\n        expected_fn = \"io_%s\" if method == \"count\" else \"io_%s_mass\"\n        assert_equal(fn, (\"deposit\", expected_fn % method))\n        ret = ad[fn]\n        if method == \"count\":\n            assert_equal(ret.sum(), ad[\"io\", \"particle_ones\"].sum())\n        else:\n            assert_almost_equal(ret.sum(), ad[\"io\", \"particle_mass\"].sum())\n\n    # Test \"weighted_mean\" method\n    fn = base_ds.add_deposited_particle_field(\n        (\"io\", \"particle_ones\"), \"weighted_mean\", weight_field=\"particle_ones\"\n    )\n    assert_equal(fn, (\"deposit\", \"io_avg_ones\"))\n    ret = ad[fn]\n    # The sum should equal the number of cells that have particles\n    assert_equal(ret.sum(), np.count_nonzero(ad[\"deposit\", \"io_count\"]))\n\n\ndef test_add_gradient_fields():\n    ds = get_base_ds(1)\n    gfields = ds.add_gradient_fields((\"gas\", \"density\"))\n    gfields += ds.add_gradient_fields((\"index\", \"ones\"))\n    field_list = [\n        (\"gas\", \"density_gradient_x\"),\n        (\"gas\", \"density_gradient_y\"),\n        (\"gas\", \"density_gradient_z\"),\n        (\"gas\", \"density_gradient_magnitude\"),\n        (\"index\", \"ones_gradient_x\"),\n        (\"index\", \"ones_gradient_y\"),\n        (\"index\", \"ones_gradient_z\"),\n        (\"index\", \"ones_gradient_magnitude\"),\n    ]\n    assert_equal(gfields, field_list)\n    ad = ds.all_data()\n    for field in field_list:\n        ret = ad[field]\n        if field[0] == \"gas\":\n            assert str(ret.units) == \"g/cm**4\"\n        else:\n            assert str(ret.units) == \"1/cm\"\n\n\ndef test_add_gradient_fields_by_fname():\n    ds = fake_amr_ds(fields=(\"density\", \"temperature\"), units=(\"g/cm**3\", \"K\"))\n    actual = ds.add_gradient_fields((\"gas\", \"density\"))\n    expected = [\n        (\"gas\", \"density_gradient_x\"),\n        (\"gas\", \"density_gradient_y\"),\n        (\"gas\", \"density_gradient_z\"),\n        (\"gas\", \"density_gradient_magnitude\"),\n    ]\n    assert_equal(actual, expected)\n\n\ndef test_add_gradient_multiple_fields():\n    ds = fake_amr_ds(fields=(\"density\", \"temperature\"), units=(\"g/cm**3\", \"K\"))\n    actual = ds.add_gradient_fields([(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n    expected = [\n        (\"gas\", \"density_gradient_x\"),\n        (\"gas\", \"density_gradient_y\"),\n        (\"gas\", \"density_gradient_z\"),\n        (\"gas\", \"density_gradient_magnitude\"),\n        (\"gas\", \"temperature_gradient_x\"),\n        (\"gas\", \"temperature_gradient_y\"),\n        (\"gas\", \"temperature_gradient_z\"),\n        (\"gas\", \"temperature_gradient_magnitude\"),\n    ]\n    assert_equal(actual, expected)\n\n    ds = fake_amr_ds(fields=(\"density\", \"temperature\"), units=(\"g/cm**3\", \"K\"))\n    actual = ds.add_gradient_fields([(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n    assert_equal(actual, expected)\n\n\ndef test_add_gradient_fields_curvilinear():\n    ds = fake_amr_ds(fields=[\"density\"], units=[\"g/cm**3\"], geometry=\"spherical\")\n    gfields = ds.add_gradient_fields((\"gas\", \"density\"))\n    gfields += ds.add_gradient_fields((\"index\", \"ones\"))\n    field_list = [\n        (\"gas\", \"density_gradient_r\"),\n        (\"gas\", \"density_gradient_theta\"),\n        (\"gas\", \"density_gradient_phi\"),\n        (\"gas\", \"density_gradient_magnitude\"),\n        (\"index\", \"ones_gradient_r\"),\n        (\"index\", \"ones_gradient_theta\"),\n        (\"index\", \"ones_gradient_phi\"),\n        (\"index\", \"ones_gradient_magnitude\"),\n    ]\n    assert_equal(gfields, field_list)\n    ad = ds.all_data()\n    for field in field_list:\n        ret = ad[field]\n        if field[0] == \"gas\":\n            assert str(ret.units) == \"g/cm**4\"\n        else:\n            assert str(ret.units) == \"1/cm\"\n\n\ndef get_data(ds, field_name):\n    # Need to create a new data object otherwise the errors we are\n    # intentionally raising lead to spurious GenerationInProgress errors\n    ad = ds.all_data()\n    return ad[field_name]\n\n\ndef test_add_field_unit_semantics():\n    ds = fake_random_ds(16)\n    ad = ds.all_data()\n\n    def density_alias(data):\n        return data[\"gas\", \"density\"].in_cgs()\n\n    def unitless_data(data):\n        return np.ones(data[\"gas\", \"density\"].shape)\n\n    ds.add_field(\n        (\"gas\", \"density_alias_auto\"),\n        sampling_type=\"cell\",\n        function=density_alias,\n        units=\"auto\",\n        dimensions=\"density\",\n    )\n    ds.add_field(\n        (\"gas\", \"density_alias_wrong_units\"),\n        function=density_alias,\n        sampling_type=\"cell\",\n        units=\"m/s\",\n    )\n    ds.add_field(\n        (\"gas\", \"density_alias_unparseable_units\"),\n        sampling_type=\"cell\",\n        function=density_alias,\n        units=\"dragons\",\n    )\n    ds.add_field(\n        (\"gas\", \"density_alias_auto_wrong_dims\"),\n        function=density_alias,\n        sampling_type=\"cell\",\n        units=\"auto\",\n        dimensions=\"temperature\",\n    )\n    assert_raises(YTFieldUnitError, get_data, ds, (\"gas\", \"density_alias_wrong_units\"))\n    assert_raises(\n        YTFieldUnitParseError, get_data, ds, (\"gas\", \"density_alias_unparseable_units\")\n    )\n    assert_raises(\n        YTDimensionalityError, get_data, ds, (\"gas\", \"density_alias_auto_wrong_dims\")\n    )\n\n    dens = ad[\"gas\", \"density_alias_auto\"]\n    assert_equal(str(dens.units), \"g/cm**3\")\n\n    ds.add_field((\"gas\", \"dimensionless\"), sampling_type=\"cell\", function=unitless_data)\n    ds.add_field(\n        (\"gas\", \"dimensionless_auto\"),\n        function=unitless_data,\n        sampling_type=\"cell\",\n        units=\"auto\",\n        dimensions=\"dimensionless\",\n    )\n    ds.add_field(\n        (\"gas\", \"dimensionless_explicit\"),\n        function=unitless_data,\n        sampling_type=\"cell\",\n        units=\"\",\n    )\n    ds.add_field(\n        (\"gas\", \"dimensionful\"),\n        sampling_type=\"cell\",\n        function=unitless_data,\n        units=\"g/cm**3\",\n    )\n\n    assert_equal(str(ad[\"gas\", \"dimensionless\"].units), \"dimensionless\")\n    assert_equal(str(ad[\"gas\", \"dimensionless_auto\"].units), \"dimensionless\")\n    assert_equal(str(ad[\"gas\", \"dimensionless_explicit\"].units), \"dimensionless\")\n    assert_raises(YTFieldUnitError, get_data, ds, (\"gas\", \"dimensionful\"))\n\n\ndef test_add_field_from_lambda():\n    ds = fake_amr_ds(fields=[\"density\"], units=[\"g/cm**3\"])\n\n    def _function_density(data):\n        return data[\"gas\", \"density\"]\n\n    ds.add_field(\n        (\"gas\", \"function_density\"),\n        function=_function_density,\n        sampling_type=\"cell\",\n        units=\"g/cm**3\",\n    )\n\n    ds.add_field(\n        (\"gas\", \"lambda_density\"),\n        function=lambda field, data: data[\"gas\", \"density\"],\n        sampling_type=\"cell\",\n        units=\"g/cm**3\",\n    )\n\n    ad = ds.all_data()\n    # check that the fields are accessible\n    ad[\"gas\", \"function_density\"]\n    ad[\"gas\", \"lambda_density\"]\n\n\ndef test_array_like_field():\n    ds = fake_random_ds(4, particles=64)\n    ad = ds.all_data()\n    u1 = ad[\"all\", \"particle_mass\"].units\n    u2 = array_like_field(ad, 1.0, (\"all\", \"particle_mass\")).units\n    assert u1 == u2\n\n\nISOGAL = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\n\n@requires_module(\"h5py\")\n@requires_file(ISOGAL)\ndef test_array_like_field_output_units():\n    ds = load(ISOGAL)\n    ad = ds.all_data()\n    u1 = ad[\"all\", \"particle_mass\"].units\n    u2 = array_like_field(ad, 1.0, (\"all\", \"particle_mass\")).units\n    assert u1 == u2\n    assert str(u1) == ds.fields.all.particle_mass.output_units\n    u1 = ad[\"gas\", \"x\"].units\n    u2 = array_like_field(ad, 1.0, (\"gas\", \"x\")).units\n    assert u1 == u2\n    assert str(u1) == ds.fields.gas.x.units\n\n\ndef test_add_field_string():\n    ds = fake_random_ds(16)\n    ad = ds.all_data()\n\n    def density_alias(data):\n        return data[\"gas\", \"density\"]\n\n    ds.add_field(\n        (\"gas\", \"density_alias\"),\n        sampling_type=\"cell\",\n        function=density_alias,\n        units=\"g/cm**3\",\n    )\n\n    ad[\"gas\", \"density_alias\"]\n\n    assert (\"gas\", \"density_alias\") in ds.derived_field_list\n\n\ndef test_add_field_string_aliasing():\n    ds = fake_random_ds(16)\n\n    def density_alias(data):\n        return data[\"gas\", \"density\"]\n\n    ds.add_field(\n        (\"gas\", \"density_alias\"),\n        sampling_type=\"cell\",\n        function=density_alias,\n        units=\"g/cm**3\",\n    )\n\n    ds.field_info[\"gas\", \"density_alias\"]\n    ds.field_info[\"gas\", \"density_alias\"]\n\n    ds = fake_particle_ds()\n\n    def pmass_alias(data):\n        return data[\"all\", \"particle_mass\"]\n\n    ds.add_field(\n        (\"all\", \"particle_mass_alias\"),\n        function=pmass_alias,\n        units=\"g\",\n        sampling_type=\"particle\",\n    )\n\n    ds.field_info[\"all\", \"particle_mass_alias\"]\n    ds.field_info[\"all\", \"particle_mass_alias\"]\n\n\ndef test_morton_index():\n    ds = fake_amr_ds()\n    mi = ds.r[\"index\", \"morton_index\"]\n    mi2 = mi.view(\"uint64\")\n    assert_equal(np.unique(mi2).size, mi2.size)\n    a1 = np.argsort(mi)\n    a2 = np.argsort(mi2)\n    assert_array_equal(a1, a2)\n\n\n@requires_module(\"h5py\")\n@requires_file(ISOGAL)\ndef test_deposit_amr():\n    ds = load(ISOGAL)\n    for g in ds.index.grids:\n        gpm = g[\"all\", \"particle_mass\"].sum()\n        dpm = g[\"deposit\", \"all_mass\"].sum()\n        assert_allclose_units(gpm, dpm)\n\n\ndef test_ion_field_labels():\n    fields = [\n        \"O_p1_number_density\",\n        \"O2_p1_number_density\",\n        \"CO2_p1_number_density\",\n        \"Co_p1_number_density\",\n        \"O2_p2_number_density\",\n        \"H2O_p1_number_density\",\n    ]\n    units = [\"cm**-3\" for f in fields]\n    ds = fake_random_ds(16, fields=fields, units=units)\n\n    # by default labels should use roman numerals\n    default_labels = {\n        \"O_p1_number_density\": \"$\\\\rm{O\\\\ II\\\\ Number\\\\ Density}$\",\n        \"O2_p1_number_density\": \"$\\\\rm{O_{2}\\\\ II\\\\ Number\\\\ Density}$\",\n        \"CO2_p1_number_density\": \"$\\\\rm{CO_{2}\\\\ II\\\\ Number\\\\ Density}$\",\n        \"Co_p1_number_density\": \"$\\\\rm{Co\\\\ II\\\\ Number\\\\ Density}$\",\n        \"O2_p2_number_density\": \"$\\\\rm{O_{2}\\\\ III\\\\ Number\\\\ Density}$\",\n        \"H2O_p1_number_density\": \"$\\\\rm{H_{2}O\\\\ II\\\\ Number\\\\ Density}$\",\n    }\n\n    pm_labels = {\n        \"O_p1_number_density\": \"$\\\\rm{{O}^{+}\\\\ Number\\\\ Density}$\",\n        \"O2_p1_number_density\": \"$\\\\rm{{O_{2}}^{+}\\\\ Number\\\\ Density}$\",\n        \"CO2_p1_number_density\": \"$\\\\rm{{CO_{2}}^{+}\\\\ Number\\\\ Density}$\",\n        \"Co_p1_number_density\": \"$\\\\rm{{Co}^{+}\\\\ Number\\\\ Density}$\",\n        \"O2_p2_number_density\": \"$\\\\rm{{O_{2}}^{++}\\\\ Number\\\\ Density}$\",\n        \"H2O_p1_number_density\": \"$\\\\rm{{H_{2}O}^{+}\\\\ Number\\\\ Density}$\",\n    }\n\n    fobj = ds.fields.stream\n\n    for f in fields:\n        label = getattr(fobj, f).get_latex_display_name()\n        assert_equal(label, default_labels[f])\n\n    ds.set_field_label_format(\"ionization_label\", \"plus_minus\")\n    fobj = ds.fields.stream\n\n    for f in fields:\n        label = getattr(fobj, f).get_latex_display_name()\n        assert_equal(label, pm_labels[f])\n\n\ndef test_default_fluid_type_None():\n    \"\"\"\n    Check for bad behavior when default_fluid_type is None.\n    See PR #3710.\n    \"\"\"\n    ds = fake_amr_ds()\n    ds.default_fluid_type = None\n    ds.field_list\n"
  },
  {
    "path": "yt/fields/tests/test_fields_plugins.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\n\nfrom numpy.testing import assert_raises\n\nimport yt\nfrom yt.config import ytcfg\nfrom yt.testing import fake_random_ds\nfrom yt.utilities.configure import YTConfig, config_dir\n\n_TEST_PLUGIN = \"_test_plugin.py\"\n\n_DUMMY_CFG_TOML = f\"\"\"[yt]\nlog_level = 49\nplugin_filename = \"{_TEST_PLUGIN}\"\nboolean_stuff = true\nchunk_size = 3\n\"\"\"\n\nTEST_PLUGIN_FILE = \"\"\"\nimport numpy as np\n\ndef _myfunc(data):\n    return np.random.random(data['gas', 'density'].shape)\nadd_field(('gas', 'random'), dimensions='dimensionless',\n          function=_myfunc, units='auto', sampling_type='local')\nconstant = 3\ndef myfunc():\n    return constant*4\nfoobar = 17\n\"\"\"\n\n\nclass TestPluginFile(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        cls.xdg_config_home = os.environ.get(\"XDG_CONFIG_HOME\")\n        cls.tmpdir = tempfile.mkdtemp()\n        os.mkdir(os.path.join(cls.tmpdir, \"yt\"))\n        os.environ[\"XDG_CONFIG_HOME\"] = cls.tmpdir\n        with open(YTConfig.get_global_config_file(), mode=\"w\") as fh:\n            fh.write(_DUMMY_CFG_TOML)\n        cls.plugin_path = os.path.join(config_dir(), ytcfg.get(\"yt\", \"plugin_filename\"))\n        with open(cls.plugin_path, mode=\"w\") as fh:\n            fh.write(TEST_PLUGIN_FILE)\n\n    @classmethod\n    def tearDownClass(cls):\n        shutil.rmtree(cls.tmpdir)\n        if cls.xdg_config_home:\n            os.environ[\"XDG_CONFIG_HOME\"] = cls.xdg_config_home\n        else:\n            os.environ.pop(\"XDG_CONFIG_HOME\")\n\n    def testCustomField(self):\n        msg = f\"INFO:yt:Loading plugins from {self.plugin_path}\"\n        with self.assertLogs(\"yt\", level=\"INFO\") as cm:\n            yt.enable_plugins()\n            self.assertEqual(cm.output, [msg])\n\n        ds = fake_random_ds(16)\n        dd = ds.all_data()\n        self.assertEqual(str(dd[\"gas\", \"random\"].units), \"dimensionless\")\n        self.assertEqual(dd[\"gas\", \"random\"].shape, dd[\"gas\", \"density\"].shape)\n        assert yt.myfunc() == 12\n        assert_raises(AttributeError, getattr, yt, \"foobar\")\n"
  },
  {
    "path": "yt/fields/tests/test_fields_pytest.py",
    "content": "import pytest\n\nfrom yt.fields.field_functions import validate_field_function\n\n\n@pytest.mark.parametrize(\n    \"function\",\n    [\n        pytest.param(lambda field, data: ..., id=\"field-data\"),\n        pytest.param(lambda data, field: ..., id=\"data-field\"),\n        pytest.param(lambda *, data, field: ..., id=\"data_kw-field_kw\"),\n        pytest.param(lambda data, *, field: ..., id=\"data-field_kw\"),\n        pytest.param(lambda field, *, data: ..., id=\"field-data_kw\"),\n        pytest.param(\n            lambda data, field, extra=None: ..., id=\"data-field-extra_default\"\n        ),\n    ],\n)\ndef test_validate_field_function(function):\n    assert validate_field_function(function) is None\n\n\n@pytest.mark.parametrize(\n    \"function\",\n    [\n        pytest.param(lambda data, field=None: ..., id=\"data-field_default\"),\n        pytest.param(lambda field, data=None: ..., id=\"field-data_default\"),\n    ],\n)\ndef test_validate_field_function_default(function):\n    with pytest.warns(\n        UserWarning,\n        match=r\"These default values will never be used\\. Drop them to avoid this warning\\.$\",\n    ):\n        validate_field_function(function)\n\n\n@pytest.mark.parametrize(\n    \"function\",\n    [\n        pytest.param(lambda: ..., id=\"no_args\"),\n        pytest.param(lambda dataa: ..., id=\"no-data\"),\n        pytest.param(lambda field, data, extra: ..., id=\"field-data-extra_nodefault\"),\n        pytest.param(lambda field, /, data: ..., id=\"field_pos-data\"),\n        pytest.param(lambda data, /, field: ..., id=\"data_pos-field\"),\n        pytest.param(lambda field, data, /: ..., id=\"field_pos-data_pos\"),\n        pytest.param(lambda *, data, field, extra: ..., id=\"data_kw-field_kw-extra_kw\"),\n        pytest.param(lambda data, field, *, extra: ..., id=\"data-field-extra_kw\"),\n        pytest.param(lambda data, *field: ..., id=\"data-field_varargs\"),\n        pytest.param(lambda field, *data: ..., id=\"field-data_varargs\"),\n        pytest.param(lambda data, **field: ..., id=\"data-field_kwargs\"),\n        pytest.param(lambda field, **data: ..., id=\"field-data_kwargs\"),\n    ],\n)\ndef test_invalidate_field_function(function):\n    with pytest.raises(\n        TypeError,\n        match=r\"^Received field function .* with invalid signature\",\n    ):\n        validate_field_function(function)\n"
  },
  {
    "path": "yt/fields/tests/test_magnetic_fields.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal\n\nfrom yt.loaders import load_uniform_grid\nfrom yt.utilities.physical_constants import mu_0\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_magnetic_fields():\n    ddims = (16, 16, 16)\n    data1 = {\n        \"magnetic_field_x\": (np.random.random(size=ddims), \"T\"),\n        \"magnetic_field_y\": (np.random.random(size=ddims), \"T\"),\n        \"magnetic_field_z\": (np.random.random(size=ddims), \"T\"),\n    }\n    data2 = {}\n    for field in data1:\n        data2[field] = (data1[field][0] * 1.0e4, \"gauss\")\n\n    ds0 = load_uniform_grid(data1, ddims, unit_system=\"cgs\")\n    ds1 = load_uniform_grid(data1, ddims, magnetic_unit=(1.0, \"T\"), unit_system=\"cgs\")\n    ds2 = load_uniform_grid(data2, ddims, unit_system=\"mks\")\n    # For this test dataset, code units are cgs units\n    ds3 = load_uniform_grid(data2, ddims, unit_system=\"code\")\n    # For this test dataset, code units are SI units\n    ds4 = load_uniform_grid(data1, ddims, magnetic_unit=(1.0, \"T\"), unit_system=\"code\")\n\n    ds0.index\n    ds1.index\n    ds2.index\n    ds3.index\n    ds4.index\n\n    dd0 = ds0.all_data()\n    dd1 = ds1.all_data()\n    dd2 = ds2.all_data()\n    dd3 = ds3.all_data()\n    dd4 = ds4.all_data()\n\n    assert ds0.fields.gas.magnetic_field_strength.units == \"G\"\n    assert ds1.fields.gas.magnetic_field_strength.units == \"G\"\n    assert ds1.fields.gas.magnetic_field_poloidal.units == \"G\"\n    assert ds1.fields.gas.magnetic_field_toroidal.units == \"G\"\n    assert ds2.fields.gas.magnetic_field_strength.units == \"T\"\n    assert ds2.fields.gas.magnetic_field_poloidal.units == \"T\"\n    assert ds2.fields.gas.magnetic_field_toroidal.units == \"T\"\n    assert ds3.fields.gas.magnetic_field_strength.units == \"code_magnetic\"\n    assert ds3.fields.gas.magnetic_field_poloidal.units == \"code_magnetic\"\n    assert ds3.fields.gas.magnetic_field_toroidal.units == \"code_magnetic\"\n    assert ds4.fields.gas.magnetic_field_strength.units == \"code_magnetic\"\n    assert ds4.fields.gas.magnetic_field_poloidal.units == \"code_magnetic\"\n    assert ds4.fields.gas.magnetic_field_toroidal.units == \"code_magnetic\"\n\n    emag0 = (\n        dd0[\"gas\", \"magnetic_field_x\"] ** 2\n        + dd0[\"gas\", \"magnetic_field_y\"] ** 2\n        + dd0[\"gas\", \"magnetic_field_z\"] ** 2\n    ) / (8.0 * np.pi)\n    emag0.convert_to_units(\"dyne/cm**2\")\n\n    emag1 = (\n        dd1[\"gas\", \"magnetic_field_x\"] ** 2\n        + dd1[\"gas\", \"magnetic_field_y\"] ** 2\n        + dd1[\"gas\", \"magnetic_field_z\"] ** 2\n    ) / (8.0 * np.pi)\n    emag1.convert_to_units(\"dyne/cm**2\")\n\n    emag2 = (\n        dd2[\"gas\", \"magnetic_field_x\"] ** 2\n        + dd2[\"gas\", \"magnetic_field_y\"] ** 2\n        + dd2[\"gas\", \"magnetic_field_z\"] ** 2\n    ) / (2.0 * mu_0)\n    emag2.convert_to_units(\"Pa\")\n\n    emag3 = (\n        dd3[\"gas\", \"magnetic_field_x\"] ** 2\n        + dd3[\"gas\", \"magnetic_field_y\"] ** 2\n        + dd3[\"gas\", \"magnetic_field_z\"] ** 2\n    ) / (8.0 * np.pi)\n    emag3.convert_to_units(\"code_pressure\")\n\n    emag4 = (\n        dd4[\"gas\", \"magnetic_field_x\"] ** 2\n        + dd4[\"gas\", \"magnetic_field_y\"] ** 2\n        + dd4[\"gas\", \"magnetic_field_z\"] ** 2\n    ) / (2.0 * mu_0)\n    emag4.convert_to_units(\"code_pressure\")\n\n    # note that \"magnetic_energy_density\" and \"magnetic_pressure\" are aliased\n\n    assert_almost_equal(emag0, dd0[\"gas\", \"magnetic_energy_density\"])\n    assert_almost_equal(emag1, dd1[\"gas\", \"magnetic_energy_density\"])\n    assert_almost_equal(emag2, dd2[\"gas\", \"magnetic_energy_density\"])\n    assert_almost_equal(emag3, dd3[\"gas\", \"magnetic_energy_density\"])\n    assert_almost_equal(emag4, dd4[\"gas\", \"magnetic_energy_density\"])\n\n    assert str(emag0.units) == str(dd0[\"gas\", \"magnetic_energy_density\"].units)\n    assert str(emag1.units) == str(dd1[\"gas\", \"magnetic_energy_density\"].units)\n    assert str(emag2.units) == str(dd2[\"gas\", \"magnetic_energy_density\"].units)\n    assert str(emag3.units) == str(dd3[\"gas\", \"magnetic_energy_density\"].units)\n    assert str(emag4.units) == str(dd4[\"gas\", \"magnetic_energy_density\"].units)\n\n    assert_almost_equal(emag1.in_cgs(), emag0.in_cgs())\n    assert_almost_equal(emag2.in_cgs(), emag0.in_cgs())\n    assert_almost_equal(emag1.in_cgs(), emag2.in_cgs())\n    assert_almost_equal(emag1.in_cgs(), emag3.in_cgs())\n    assert_almost_equal(emag1.in_cgs(), emag4.in_cgs())\n"
  },
  {
    "path": "yt/fields/tests/test_particle_fields.py",
    "content": "import numpy as np\n\nfrom yt.testing import assert_allclose_units, requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import data_dir_load\n\ng30 = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\n\n@requires_module(\"h5py\")\n@requires_file(g30)\ndef test_relative_particle_fields():\n    ds = data_dir_load(g30)\n    offset = ds.arr([0.1, -0.2, 0.3], \"code_length\")\n    c = ds.domain_center + offset\n    sp = ds.sphere(c, (10, \"kpc\"))\n    bv = ds.arr([1.0, 2.0, 3.0], \"code_velocity\")\n    sp.set_field_parameter(\"bulk_velocity\", bv)\n    assert_allclose_units(\n        sp[\"all\", \"relative_particle_position\"], sp[\"all\", \"particle_position\"] - c\n    )\n    assert_allclose_units(\n        sp[\"all\", \"relative_particle_velocity\"], sp[\"all\", \"particle_velocity\"] - bv\n    )\n\n\n@requires_module(\"h5py\")\n@requires_file(g30)\ndef test_los_particle_fields():\n    ds = data_dir_load(g30)\n    offset = ds.arr([0.1, -0.2, 0.3], \"code_length\")\n    c = ds.domain_center + offset\n    sp = ds.sphere(c, (10, \"kpc\"))\n    bv = ds.arr([1.0, 2.0, 3.0], \"code_velocity\")\n    sp.set_field_parameter(\"bulk_velocity\", bv)\n    ax = [0.1, 0.2, -0.3]\n    sp.set_field_parameter(\"axis\", ax)\n    ax /= np.linalg.norm(ax)\n    vlos = (\n        sp[\"all\", \"relative_particle_velocity_x\"] * ax[0]\n        + sp[\"all\", \"relative_particle_velocity_y\"] * ax[1]\n        + sp[\"all\", \"relative_particle_velocity_z\"] * ax[2]\n    )\n    assert_allclose_units(sp[\"all\", \"particle_velocity_los\"], vlos)\n    sp.clear_data()\n    ax = 2\n    sp.set_field_parameter(\"axis\", ax)\n    vlos = sp[\"all\", \"relative_particle_velocity_z\"]\n    assert_allclose_units(sp[\"all\", \"particle_velocity_los\"], vlos)\n"
  },
  {
    "path": "yt/fields/tests/test_species_fields.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.testing import assert_allclose_units, fake_random_ds\nfrom yt.utilities.chemical_formulas import ChemicalFormula\nfrom yt.utilities.physical_ratios import _primordial_mass_fraction\n\n\ndef test_default_species_fields():\n    # Test default case (no species fields)\n\n    ds = fake_random_ds(32)\n    sp = ds.sphere(\"c\", (0.2, \"unitary\"))\n\n    mu = 0.5924489101195808\n\n    assert_allclose_units(mu * sp[\"index\", \"ones\"], sp[\"gas\", \"mean_molecular_weight\"])\n\n    assert (\"gas\", \"H_nuclei_density\") not in ds.derived_field_list\n    assert (\"gas\", \"He_nuclei_density\") not in ds.derived_field_list\n    assert (\"gas\", \"El_number_density\") not in ds.derived_field_list\n    assert (\"gas\", \"H_p1_number_density\") not in ds.derived_field_list\n    assert (\"gas\", \"He_p2_number_density\") not in ds.derived_field_list\n    assert (\"gas\", \"H_p0_number_density\") not in ds.derived_field_list\n    assert (\"gas\", \"He_p0_number_density\") not in ds.derived_field_list\n\n    # Test fully ionized case\n    dsi = fake_random_ds(32, default_species_fields=\"ionized\")\n    spi = dsi.sphere(\"c\", (0.2, \"unitary\"))\n    amu_cgs = dsi.quan(1.0, \"amu\").in_cgs()\n\n    mueinv = 1.0 * _primordial_mass_fraction[\"H\"] / ChemicalFormula(\"H\").weight\n    mueinv *= spi[\"index\", \"ones\"]\n    mueinv += 2.0 * _primordial_mass_fraction[\"He\"] / ChemicalFormula(\"He\").weight\n    mupinv = _primordial_mass_fraction[\"H\"] / ChemicalFormula(\"H\").weight\n    mupinv *= spi[\"index\", \"ones\"]\n    muainv = _primordial_mass_fraction[\"He\"] / ChemicalFormula(\"He\").weight\n    muainv *= spi[\"index\", \"ones\"]\n    mueinv2 = spi[\"gas\", \"El_number_density\"] * amu_cgs / spi[\"gas\", \"density\"]\n    mupinv2 = spi[\"gas\", \"H_p1_number_density\"] * amu_cgs / spi[\"gas\", \"density\"]\n    muainv2 = spi[\"gas\", \"He_p2_number_density\"] * amu_cgs / spi[\"gas\", \"density\"]\n\n    assert_allclose_units(mueinv, mueinv2)\n    assert_allclose_units(mupinv, mupinv2)\n    assert_allclose_units(muainv, muainv2)\n\n    assert_equal(spi[\"gas\", \"H_p1_number_density\"], spi[\"gas\", \"H_nuclei_density\"])\n    assert_equal(spi[\"gas\", \"He_p2_number_density\"], spi[\"gas\", \"He_nuclei_density\"])\n\n    mu = 0.5924489101195808\n\n    assert_allclose_units(\n        mu * spi[\"index\", \"ones\"], spi[\"gas\", \"mean_molecular_weight\"]\n    )\n\n    # Test fully neutral case\n\n    dsn = fake_random_ds(32, default_species_fields=\"neutral\")\n    spn = dsn.sphere(\"c\", (0.2, \"unitary\"))\n    amu_cgs = dsn.quan(1.0, \"amu\").in_cgs()\n\n    assert (\"gas\", \"El_number_density\") not in ds.derived_field_list\n\n    mupinv = _primordial_mass_fraction[\"H\"] / ChemicalFormula(\"H\").weight\n    mupinv *= spn[\"index\", \"ones\"]\n    muainv = _primordial_mass_fraction[\"He\"] / ChemicalFormula(\"He\").weight\n    muainv *= spn[\"index\", \"ones\"]\n    mupinv2 = spn[\"gas\", \"H_p0_number_density\"] * amu_cgs / spn[\"gas\", \"density\"]\n    muainv2 = spn[\"gas\", \"He_p0_number_density\"] * amu_cgs / spn[\"gas\", \"density\"]\n\n    assert_allclose_units(mueinv, mueinv2)\n    assert_allclose_units(mupinv, mupinv2)\n    assert_allclose_units(muainv, muainv2)\n\n    assert_equal(spn[\"gas\", \"H_p0_number_density\"], spn[\"gas\", \"H_nuclei_density\"])\n    assert_equal(spn[\"gas\", \"He_p0_number_density\"], spn[\"gas\", \"He_nuclei_density\"])\n\n    mu = 1.2285402715185552\n\n    assert_allclose_units(\n        mu * spn[\"index\", \"ones\"], spn[\"gas\", \"mean_molecular_weight\"]\n    )\n"
  },
  {
    "path": "yt/fields/tests/test_sph_fields.py",
    "content": "from collections import defaultdict\n\nfrom numpy.testing import assert_array_almost_equal, assert_equal\n\nimport yt\nfrom yt.testing import requires_file, skip\n\nisothermal_h5 = \"IsothermalCollapse/snap_505.hdf5\"\nisothermal_bin = \"IsothermalCollapse/snap_505\"\nsnap_33 = \"snapshot_033/snap_033.0.hdf5\"\ntipsy_gal = \"TipsyGalaxy/galaxy.00300\"\nFIRE_m12i = \"FIRE_M12i_ref11/snapshot_600.hdf5\"\n\niso_kwargs = {\n    \"bounding_box\": [[-3, 3], [-3, 3], [-3, 3]],\n    \"unit_base\": {\n        \"UnitLength_in_cm\": 5.0e16,\n        \"UnitMass_in_g\": 1.98992e33,\n        \"UnitVelocity_in_cm_per_s\": 46385.190,\n    },\n}\n\nload_kwargs = defaultdict(dict)\nload_kwargs.update(\n    {\n        isothermal_h5: iso_kwargs,\n        isothermal_bin: iso_kwargs,\n    }\n)\n\ngas_fields_to_particle_fields = {\n    \"temperature\": \"Temperature\",\n    \"density\": \"Density\",\n    \"velocity_x\": \"particle_velocity_x\",\n    \"velocity_magnitude\": \"particle_velocity_magnitude\",\n}\n\n\n@skip(reason=\"See https://github.com/yt-project/yt/issues/3909\")\n@requires_file(isothermal_bin)\n@requires_file(isothermal_h5)\n@requires_file(snap_33)\n@requires_file(tipsy_gal)\n@requires_file(FIRE_m12i)\ndef test_sph_field_semantics():\n    for ds_fn in [tipsy_gal, isothermal_h5, isothermal_bin, snap_33, FIRE_m12i]:\n        yield sph_fields_validate, ds_fn\n\n\ndef sph_fields_validate(ds_fn):\n    ds = yt.load(ds_fn, **(load_kwargs[ds_fn]))\n    ad = ds.all_data()\n    for gf, pf in gas_fields_to_particle_fields.items():\n        gas_field = ad[\"gas\", gf]\n        part_field = ad[ds._sph_ptypes[0], pf]\n\n        assert_array_almost_equal(gas_field, part_field)\n\n        npart = ds.particle_type_counts[ds._sph_ptypes[0]]\n        err_msg = f\"Field {gf} is not the correct shape\"\n        assert_equal(npart, gas_field.shape[0], err_msg=err_msg)\n\n    dd = ds.r[0.4:0.6, 0.4:0.6, 0.4:0.6]\n\n    for i, ax in enumerate(\"xyz\"):\n        dd.set_field_parameter(f\"cp_{ax}_vec\", yt.YTArray([1, 1, 1]))\n        dd.set_field_parameter(\"axis\", i)\n    dd.set_field_parameter(\"omega_baryon\", 0.3)\n\n    for f in ds.fields.gas:\n        gas_field = dd[f]\n        assert f.is_sph_field\n"
  },
  {
    "path": "yt/fields/tests/test_vector_fields.py",
    "content": "import numpy as np\n\nfrom yt.testing import (\n    assert_allclose_units,\n    fake_random_ds,\n    requires_file,\n    requires_module,\n)\nfrom yt.units import cm, s  # type: ignore\nfrom yt.utilities.answer_testing.framework import data_dir_load\nfrom yt.visualization.volume_rendering.off_axis_projection import off_axis_projection\n\n\ndef random_unit_vector(prng):\n    v = prng.random_sample(3)\n    while (v == 0).all():\n        v = prng.random_sample(3)\n    return v / np.sqrt((v**2).sum())\n\n\ndef random_velocity_vector(prng):\n    return 2e5 * prng.random_sample(3) - 1e5\n\n\ndef compare_vector_conversions(data_source):\n    prng = np.random.RandomState(8675309)\n    normals = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + [\n        random_unit_vector(prng) for i in range(2)\n    ]\n    bulk_velocities = [random_velocity_vector(prng) for i in range(2)]\n\n    for bv in bulk_velocities:\n        bulk_velocity = bv * cm / s\n        data_source.set_field_parameter(\"bulk_velocity\", bulk_velocity)\n        data_source.clear_data()\n\n        vmag = data_source[\"gas\", \"velocity_magnitude\"]\n        vrad = data_source[\"gas\", \"velocity_spherical_radius\"]\n\n        for normal in normals:\n            data_source.set_field_parameter(\"normal\", normal)\n            data_source.clear_data()\n\n            assert_allclose_units(vrad, data_source[\"gas\", \"velocity_spherical_radius\"])\n\n            vmag_new = data_source[\"gas\", \"velocity_magnitude\"]\n            assert_allclose_units(vmag, vmag_new)\n\n            vmag_cart = np.sqrt(\n                (data_source[\"gas\", \"velocity_x\"] - bulk_velocity[0]) ** 2\n                + (data_source[\"gas\", \"velocity_y\"] - bulk_velocity[1]) ** 2\n                + (data_source[\"gas\", \"velocity_z\"] - bulk_velocity[2]) ** 2\n            )\n            assert_allclose_units(vmag, vmag_cart)\n\n            vmag_cyl = np.sqrt(\n                data_source[\"gas\", \"velocity_cylindrical_radius\"] ** 2\n                + data_source[\"gas\", \"velocity_cylindrical_theta\"] ** 2\n                + data_source[\"gas\", \"velocity_cylindrical_z\"] ** 2\n            )\n            assert_allclose_units(vmag, vmag_cyl)\n\n            vmag_sph = np.sqrt(\n                data_source[\"gas\", \"velocity_spherical_radius\"] ** 2\n                + data_source[\"gas\", \"velocity_spherical_theta\"] ** 2\n                + data_source[\"gas\", \"velocity_spherical_phi\"] ** 2\n            )\n            assert_allclose_units(vmag, vmag_sph)\n\n            for i, d in enumerate(\"xyz\"):\n                assert_allclose_units(\n                    data_source[\"gas\", f\"velocity_{d}\"] - bulk_velocity[i],\n                    data_source[\"gas\", f\"relative_velocity_{d}\"],\n                )\n\n        for i, ax in enumerate(\"xyz\"):\n            data_source.set_field_parameter(\"axis\", i)\n            data_source.clear_data()\n            assert_allclose_units(\n                data_source[\"gas\", \"velocity_los\"],\n                data_source[\"gas\", f\"relative_velocity_{ax}\"],\n            )\n\n        for i, ax in enumerate(\"xyz\"):\n            prj = data_source.ds.proj(\n                (\"gas\", \"velocity_los\"), i, weight_field=(\"gas\", \"density\")\n            )\n            assert_allclose_units(\n                prj[\"gas\", \"velocity_los\"], prj[\"gas\", f\"velocity_{ax}\"]\n            )\n\n        data_source.clear_data()\n        ax = [0.1, 0.2, -0.3]\n        data_source.set_field_parameter(\"axis\", ax)\n        ax /= np.sqrt(np.dot(ax, ax))\n        vlos = data_source[\"gas\", \"relative_velocity_x\"] * ax[0]\n        vlos += data_source[\"gas\", \"relative_velocity_y\"] * ax[1]\n        vlos += data_source[\"gas\", \"relative_velocity_z\"] * ax[2]\n        assert_allclose_units(data_source[\"gas\", \"velocity_los\"], vlos)\n\n        buf_los = off_axis_projection(\n            data_source,\n            data_source.ds.domain_center,\n            ax,\n            0.5,\n            128,\n            (\"gas\", \"velocity_los\"),\n            weight=(\"gas\", \"density\"),\n        )\n\n        buf_x = off_axis_projection(\n            data_source,\n            data_source.ds.domain_center,\n            ax,\n            0.5,\n            128,\n            (\"gas\", \"relative_velocity_x\"),\n            weight=(\"gas\", \"density\"),\n        )\n\n        buf_y = off_axis_projection(\n            data_source,\n            data_source.ds.domain_center,\n            ax,\n            0.5,\n            128,\n            (\"gas\", \"relative_velocity_y\"),\n            weight=(\"gas\", \"density\"),\n        )\n\n        buf_z = off_axis_projection(\n            data_source,\n            data_source.ds.domain_center,\n            ax,\n            0.5,\n            128,\n            (\"gas\", \"relative_velocity_z\"),\n            weight=(\"gas\", \"density\"),\n        )\n\n        vlos = buf_x * ax[0] + buf_y * ax[1] + buf_z * ax[2]\n\n        assert_allclose_units(buf_los, vlos, rtol=1.0e-6)\n\n\ndef test_vector_component_conversions_fake():\n    ds = fake_random_ds(16)\n    ad = ds.all_data()\n    compare_vector_conversions(ad)\n\n\ng30 = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\n\n@requires_module(\"h5py\")\n@requires_file(g30)\ndef test_vector_component_conversions_real():\n    ds = data_dir_load(g30)\n    sp = ds.sphere(ds.domain_center, (10, \"kpc\"))\n    compare_vector_conversions(sp)\n"
  },
  {
    "path": "yt/fields/tests/test_xray_fields.py",
    "content": "from yt.fields.xray_emission_fields import add_xray_emissivity_field\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    ProjectionValuesTest,\n    can_run_ds,\n    data_dir_load,\n    requires_ds,\n)\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef check_xray_fields(ds_fn, fields):\n    if not can_run_ds(ds_fn):\n        return\n    dso = [None, (\"sphere\", (\"m\", (0.1, \"unitary\")))]\n    for field in fields:\n        for dobj_name in dso:\n            for axis in [0, 1, 2]:\n                yield ProjectionValuesTest(ds_fn, axis, field, None, dobj_name)\n            yield FieldValuesTest(ds_fn, field, dobj_name)\n\n\nsloshing = \"GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0300\"\nd9p = \"D9p_500/10MpcBox_HartGal_csf_a0.500.d\"\n\n\n@requires_ds(sloshing, big_data=True)\ndef test_sloshing_apec():\n    ds = data_dir_load(sloshing, kwargs={\"default_species_fields\": \"ionized\"})\n    fields = add_xray_emissivity_field(ds, 0.5, 7.0, table_type=\"apec\", metallicity=0.3)\n    for test in check_xray_fields(ds, fields):\n        test_sloshing_apec.__name__ = test.description\n        yield test\n\n\n@requires_ds(d9p, big_data=True)\ndef test_d9p_cloudy():\n    ds = data_dir_load(d9p, kwargs={\"default_species_fields\": \"ionized\"})\n    fields = add_xray_emissivity_field(\n        ds,\n        0.5,\n        2.0,\n        redshift=ds.current_redshift,\n        table_type=\"cloudy\",\n        cosmology=ds.cosmology,\n        metallicity=(\"gas\", \"metallicity\"),\n    )\n    for test in check_xray_fields(ds, fields):\n        test.suffix = \"current_redshift\"\n        test_d9p_cloudy.__name__ = test.description + test.suffix\n        yield test\n\n\n@requires_ds(d9p, big_data=True)\ndef test_d9p_cloudy_local():\n    ds = data_dir_load(d9p, kwargs={\"default_species_fields\": \"ionized\"})\n    fields = add_xray_emissivity_field(\n        ds,\n        0.5,\n        2.0,\n        dist=(1.0, \"Mpc\"),\n        table_type=\"cloudy\",\n        metallicity=(\"gas\", \"metallicity\"),\n    )\n    for test in check_xray_fields(ds, fields):\n        test.suffix = \"dist_1Mpc\"\n        test_d9p_cloudy_local.__name__ = test.description + test.suffix\n        yield test\n"
  },
  {
    "path": "yt/fields/vector_operations.py",
    "content": "import sys\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\nfrom unyt import Unit\n\nfrom yt._typing import FieldName, FieldType\nfrom yt.funcs import is_sequence, just_one\nfrom yt.geometry.api import Geometry\nfrom yt.utilities.lib.misc_utilities import obtain_relative_velocity_vector\nfrom yt.utilities.math_utils import (\n    get_cyl_r_component,\n    get_cyl_theta_component,\n    get_cyl_z_component,\n    get_sph_phi_component,\n    get_sph_r_component,\n    get_sph_theta_component,\n)\n\nfrom .derived_field import NeedsParameter, ValidateParameter, ValidateSpatial\n\nif sys.version_info >= (3, 11):\n    from typing import assert_never\nelse:\n    from typing_extensions import assert_never\n\nif TYPE_CHECKING:\n    # avoid circular imports\n    from yt.fields.field_info_container import FieldInfoContainer\n\n\ndef get_bulk(data, basename, unit):\n    if data.has_field_parameter(f\"bulk_{basename}\"):\n        bulk = data.get_field_parameter(f\"bulk_{basename}\")\n    else:\n        bulk = [0, 0, 0] * unit\n    return bulk\n\n\ndef create_magnitude_field(\n    registry,\n    basename,\n    field_units,\n    ftype=\"gas\",\n    slice_info=None,\n    validators=None,\n    sampling_type=None,\n):\n    axis_order = registry.ds.coordinates.axis_order\n\n    field_components = [(ftype, f\"{basename}_{ax}\") for ax in axis_order]\n\n    if sampling_type is None:\n        sampling_type = \"local\"\n\n    def _magnitude(data):\n        fn = field_components[0]\n        if data.has_field_parameter(f\"bulk_{basename}\"):\n            fn = (fn[0], f\"relative_{fn[1]}\")\n        d = data[fn]\n        mag = (d) ** 2\n        for idim in range(1, registry.ds.dimensionality):\n            fn = field_components[idim]\n            if data.has_field_parameter(f\"bulk_{basename}\"):\n                fn = (fn[0], f\"relative_{fn[1]}\")\n            mag += (data[fn]) ** 2\n        return np.sqrt(mag)\n\n    registry.add_field(\n        (ftype, f\"{basename}_magnitude\"),\n        sampling_type=sampling_type,\n        function=_magnitude,\n        units=field_units,\n        validators=validators,\n    )\n\n\ndef create_relative_field(\n    registry, basename, field_units, ftype=\"gas\", slice_info=None, validators=None\n):\n    axis_order = registry.ds.coordinates.axis_order\n\n    field_components = [(ftype, f\"{basename}_{ax}\") for ax in axis_order]\n\n    def relative_vector(ax):\n        def _relative_vector(data):\n            iax = axis_order.index(ax)\n            d = data[field_components[iax]]\n            bulk = get_bulk(data, basename, d.unit_quantity)\n            return d - bulk[iax]\n\n        return _relative_vector\n\n    for d in axis_order:\n        registry.add_field(\n            (ftype, f\"relative_{basename}_{d}\"),\n            sampling_type=\"local\",\n            function=relative_vector(d),\n            units=field_units,\n            validators=validators,\n        )\n\n\ndef create_los_field(\n    registry,\n    basename,\n    field_units,\n    ftype=\"gas\",\n    slice_info=None,\n    *,\n    sampling_type=\"local\",\n):\n    axis_order = registry.ds.coordinates.axis_order\n\n    # Here we need to check if we are a particle field, so that we can\n    # correctly identify the \"bulk\" field parameter corresponding to\n    # this vector field.\n    if sampling_type == \"particle\":\n        basenm = basename.removeprefix(\"particle_\")\n    else:\n        basenm = basename\n\n    validators = [\n        ValidateParameter(f\"bulk_{basenm}\"),\n        ValidateParameter(\"axis\", {\"axis\": [0, 1, 2]}),\n    ]\n\n    field_comps = [(ftype, f\"{basename}_{ax}\") for ax in axis_order]\n\n    def _los_field(data):\n        if data.has_field_parameter(f\"bulk_{basenm}\"):\n            fns = [(fc[0], f\"relative_{fc[1]}\") for fc in field_comps]\n        else:\n            fns = field_comps\n        ax = data.get_field_parameter(\"axis\")\n        if is_sequence(ax):\n            # Make sure this is a unit vector\n            ax /= np.sqrt(np.dot(ax, ax))\n            ret = data[fns[0]] * ax[0] + data[fns[1]] * ax[1] + data[fns[2]] * ax[2]\n        elif ax in [0, 1, 2]:\n            ret = data[fns[ax]]\n        else:\n            raise NeedsParameter([\"axis\"])\n        return ret\n\n    registry.add_field(\n        (ftype, f\"{basename}_los\"),\n        sampling_type=sampling_type,\n        function=_los_field,\n        units=field_units,\n        validators=validators,\n        display_name=rf\"\\mathrm{{Line of Sight {basename.capitalize()}}}\",\n    )\n\n\ndef create_squared_field(\n    registry, basename, field_units, ftype=\"gas\", slice_info=None, validators=None\n):\n    axis_order = registry.ds.coordinates.axis_order\n\n    field_components = [(ftype, f\"{basename}_{ax}\") for ax in axis_order]\n\n    def _squared(data):\n        fn = field_components[0]\n        if data.has_field_parameter(f\"bulk_{basename}\"):\n            fn = (fn[0], f\"relative_{fn[1]}\")\n        squared = data[fn] * data[fn]\n        for idim in range(1, registry.ds.dimensionality):\n            fn = field_components[idim]\n            squared += data[fn] * data[fn]\n        return squared\n\n    registry.add_field(\n        (ftype, f\"{basename}_squared\"),\n        sampling_type=\"local\",\n        function=_squared,\n        units=field_units,\n        validators=validators,\n    )\n\n\ndef create_vector_fields(\n    registry: \"FieldInfoContainer\",\n    basename: FieldName,\n    field_units,\n    ftype: FieldType = \"gas\",\n    slice_info=None,\n) -> None:\n    # slice_info would be the left, the right, and the factor.\n    # For example, with the old Enzo-ZEUS fields, this would be:\n    # slice(None, -2, None)\n    # slice(1, -1, None)\n    # 1.0\n    # Otherwise, we default to a centered difference.\n    if slice_info is None:\n        sl_left = slice(None, -2, None)\n        sl_right = slice(2, None, None)\n        div_fac = 2.0\n    else:\n        sl_left, sl_right, div_fac = slice_info\n\n    axis_order = registry.ds.coordinates.axis_order\n\n    xn, yn, zn = ((ftype, f\"{basename}_{ax}\") for ax in axis_order)\n\n    # Is this safe?\n    if registry.ds.dimensionality < 3:\n        zn = (\"index\", \"zeros\")\n    if registry.ds.dimensionality < 2:\n        yn = (\"index\", \"zeros\")\n\n    create_relative_field(\n        registry,\n        basename,\n        field_units,\n        ftype=ftype,\n        slice_info=slice_info,\n        validators=[ValidateParameter(f\"bulk_{basename}\")],\n    )\n\n    create_magnitude_field(\n        registry,\n        basename,\n        field_units,\n        ftype=ftype,\n        slice_info=slice_info,\n        validators=[ValidateParameter(f\"bulk_{basename}\")],\n    )\n\n    axis_names = registry.ds.coordinates.axis_order\n\n    geometry: Geometry = registry.ds.geometry\n    if geometry is Geometry.CARTESIAN:\n        # The following fields are invalid for curvilinear geometries\n        def _spherical_radius_component(data):\n            \"\"\"The spherical radius component of the vector field\n\n            Relative to the coordinate system defined by the *normal* vector,\n            *center*, and *bulk_* field parameters.\n            \"\"\"\n            normal = data.get_field_parameter(\"normal\")\n            vectors = obtain_relative_velocity_vector(\n                data, (xn, yn, zn), f\"bulk_{basename}\"\n            )\n            theta = data[\"index\", \"spherical_theta\"]\n            phi = data[\"index\", \"spherical_phi\"]\n            rv = get_sph_r_component(vectors, theta, phi, normal)\n            # Now, anywhere that radius is in fact zero, we want to zero out our\n            # return values.\n            rv[np.isnan(theta)] = 0.0\n            return rv\n\n        registry.add_field(\n            (ftype, f\"{basename}_spherical_radius\"),\n            sampling_type=\"local\",\n            function=_spherical_radius_component,\n            units=field_units,\n            validators=[\n                ValidateParameter(\"normal\"),\n                ValidateParameter(\"center\"),\n                ValidateParameter(f\"bulk_{basename}\"),\n            ],\n        )\n        create_los_field(\n            registry, basename, field_units, ftype=ftype, slice_info=slice_info\n        )\n\n        def _radial(data):\n            return data[ftype, f\"{basename}_spherical_radius\"]\n\n        def _radial_absolute(data):\n            return np.abs(data[ftype, f\"{basename}_spherical_radius\"])\n\n        def _tangential(data):\n            return np.sqrt(\n                data[ftype, f\"{basename}_spherical_theta\"] ** 2.0\n                + data[ftype, f\"{basename}_spherical_phi\"] ** 2.0\n            )\n\n        registry.add_field(\n            (ftype, f\"radial_{basename}\"),\n            sampling_type=\"local\",\n            function=_radial,\n            units=field_units,\n            validators=[ValidateParameter(\"normal\"), ValidateParameter(\"center\")],\n        )\n\n        registry.add_field(\n            (ftype, f\"radial_{basename}_absolute\"),\n            sampling_type=\"local\",\n            function=_radial_absolute,\n            units=field_units,\n        )\n\n        registry.add_field(\n            (ftype, f\"tangential_{basename}\"),\n            sampling_type=\"local\",\n            function=_tangential,\n            units=field_units,\n        )\n\n        def _spherical_theta_component(data):\n            \"\"\"The spherical theta component of the vector field\n\n            Relative to the coordinate system defined by the *normal* vector,\n            *center*, and *bulk_* field parameters.\n            \"\"\"\n            normal = data.get_field_parameter(\"normal\")\n            vectors = obtain_relative_velocity_vector(\n                data, (xn, yn, zn), f\"bulk_{basename}\"\n            )\n            theta = data[\"index\", \"spherical_theta\"]\n            phi = data[\"index\", \"spherical_phi\"]\n            return get_sph_theta_component(vectors, theta, phi, normal)\n\n        registry.add_field(\n            (ftype, f\"{basename}_spherical_theta\"),\n            sampling_type=\"local\",\n            function=_spherical_theta_component,\n            units=field_units,\n            validators=[\n                ValidateParameter(\"normal\"),\n                ValidateParameter(\"center\"),\n                ValidateParameter(f\"bulk_{basename}\"),\n            ],\n        )\n\n        def _spherical_phi_component(data):\n            \"\"\"The spherical phi component of the vector field\n\n            Relative to the coordinate system defined by the *normal* vector,\n            *center*, and *bulk_* field parameters.\n            \"\"\"\n            normal = data.get_field_parameter(\"normal\")\n            vectors = obtain_relative_velocity_vector(\n                data, (xn, yn, zn), f\"bulk_{basename}\"\n            )\n            phi = data[\"index\", \"spherical_phi\"]\n            return get_sph_phi_component(vectors, phi, normal)\n\n        registry.add_field(\n            (ftype, f\"{basename}_spherical_phi\"),\n            sampling_type=\"local\",\n            function=_spherical_phi_component,\n            units=field_units,\n            validators=[\n                ValidateParameter(\"normal\"),\n                ValidateParameter(\"center\"),\n                ValidateParameter(f\"bulk_{basename}\"),\n            ],\n        )\n\n        def _cp_vectors(ax):\n            def _cp_val(data):\n                vec = data.get_field_parameter(f\"cp_{ax}_vec\")\n                tr = data[xn[0], f\"relative_{xn[1]}\"] * vec.d[0]\n                tr += data[yn[0], f\"relative_{yn[1]}\"] * vec.d[1]\n                tr += data[zn[0], f\"relative_{zn[1]}\"] * vec.d[2]\n                return tr\n\n            return _cp_val\n\n        for ax in \"xyz\":\n            registry.add_field(\n                (ftype, f\"cutting_plane_{basename}_{ax}\"),\n                sampling_type=\"local\",\n                function=_cp_vectors(ax),\n                units=field_units,\n            )\n\n        def _divergence(data):\n            ds = div_fac * just_one(data[\"index\", \"dx\"])\n            f = data[xn[0], f\"relative_{xn[1]}\"][sl_right, 1:-1, 1:-1] / ds\n            f -= data[xn[0], f\"relative_{xn[1]}\"][sl_left, 1:-1, 1:-1] / ds\n            ds = div_fac * just_one(data[\"index\", \"dy\"])\n            f += data[yn[0], f\"relative_{yn[1]}\"][1:-1, sl_right, 1:-1] / ds\n            f -= data[yn[0], f\"relative_{yn[1]}\"][1:-1, sl_left, 1:-1] / ds\n            ds = div_fac * just_one(data[\"index\", \"dz\"])\n            f += data[zn[0], f\"relative_{zn[1]}\"][1:-1, 1:-1, sl_right] / ds\n            f -= data[zn[0], f\"relative_{zn[1]}\"][1:-1, 1:-1, sl_left] / ds\n            new_field = data.ds.arr(np.zeros(data[xn].shape, dtype=\"f8\"), str(f.units))\n            new_field[1:-1, 1:-1, 1:-1] = f\n            return new_field\n\n        def _divergence_abs(data):\n            return np.abs(data[ftype, f\"{basename}_divergence\"])\n\n        field_units = Unit(field_units, registry=registry.ds.unit_registry)\n        div_units = field_units / registry.ds.unit_system[\"length\"]\n\n        registry.add_field(\n            (ftype, f\"{basename}_divergence\"),\n            sampling_type=\"local\",\n            function=_divergence,\n            units=div_units,\n            validators=[ValidateSpatial(1), ValidateParameter(f\"bulk_{basename}\")],\n        )\n\n        registry.add_field(\n            (ftype, f\"{basename}_divergence_absolute\"),\n            sampling_type=\"local\",\n            function=_divergence_abs,\n            units=div_units,\n        )\n\n        def _tangential_over_magnitude(data):\n            tr = (\n                data[ftype, f\"tangential_{basename}\"]\n                / data[ftype, f\"{basename}_magnitude\"]\n            )\n            return np.abs(tr)\n\n        registry.add_field(\n            (ftype, f\"tangential_over_{basename}_magnitude\"),\n            sampling_type=\"local\",\n            function=_tangential_over_magnitude,\n            take_log=False,\n        )\n\n        def _cylindrical_radius_component(data):\n            \"\"\"The cylindrical radius component of the vector field\n\n            Relative to the coordinate system defined by the *normal* vector,\n            *center*, and *bulk_* field parameters.\n            \"\"\"\n            normal = data.get_field_parameter(\"normal\")\n            vectors = obtain_relative_velocity_vector(\n                data, (xn, yn, zn), f\"bulk_{basename}\"\n            )\n            theta = data[\"index\", \"cylindrical_theta\"]\n            return get_cyl_r_component(vectors, theta, normal)\n\n        registry.add_field(\n            (ftype, f\"{basename}_cylindrical_radius\"),\n            sampling_type=\"local\",\n            function=_cylindrical_radius_component,\n            units=field_units,\n            validators=[ValidateParameter(\"normal\")],\n        )\n\n        def _cylindrical_theta_component(data):\n            \"\"\"The cylindrical theta component of the vector field\n\n            Relative to the coordinate system defined by the *normal* vector,\n            *center*, and *bulk_* field parameters.\n            \"\"\"\n            normal = data.get_field_parameter(\"normal\")\n            vectors = obtain_relative_velocity_vector(\n                data, (xn, yn, zn), f\"bulk_{basename}\"\n            )\n            theta = data[\"index\", \"cylindrical_theta\"].copy()\n            theta = np.tile(theta, (3,) + (1,) * len(theta.shape))\n            return get_cyl_theta_component(vectors, theta, normal)\n\n        registry.add_field(\n            (ftype, f\"{basename}_cylindrical_theta\"),\n            sampling_type=\"local\",\n            function=_cylindrical_theta_component,\n            units=field_units,\n            validators=[\n                ValidateParameter(\"normal\"),\n                ValidateParameter(\"center\"),\n                ValidateParameter(f\"bulk_{basename}\"),\n            ],\n        )\n\n        def _cylindrical_z_component(data):\n            \"\"\"The cylindrical z component of the vector field\n\n            Relative to the coordinate system defined by the *normal* vector,\n            *center*, and *bulk_* field parameters.\n            \"\"\"\n            normal = data.get_field_parameter(\"normal\")\n            vectors = obtain_relative_velocity_vector(\n                data, (xn, yn, zn), f\"bulk_{basename}\"\n            )\n            return get_cyl_z_component(vectors, normal)\n\n        registry.add_field(\n            (ftype, f\"{basename}_cylindrical_z\"),\n            sampling_type=\"local\",\n            function=_cylindrical_z_component,\n            units=field_units,\n            validators=[\n                ValidateParameter(\"normal\"),\n                ValidateParameter(\"center\"),\n                ValidateParameter(f\"bulk_{basename}\"),\n            ],\n        )\n\n    elif (\n        geometry is Geometry.POLAR\n        or geometry is Geometry.CYLINDRICAL\n        or geometry is Geometry.SPHERICAL\n    ):  # Create Cartesian fields for curvilinear coordinates\n        if geometry is Geometry.POLAR:\n\n            def _cartesian_x(data):\n                return data[ftype, f\"{basename}_r\"] * np.cos(data[ftype, \"theta\"])\n\n            def _cartesian_y(data):\n                return data[ftype, f\"{basename}_r\"] * np.sin(data[ftype, \"theta\"])\n\n            def _cartesian_z(data):\n                return data[ftype, f\"{basename}_z\"]\n\n        elif geometry is Geometry.CYLINDRICAL:\n\n            def _cartesian_x(data):\n                if data.ds.dimensionality == 2:\n                    return data[ftype, f\"{basename}_r\"]\n                elif data.ds.dimensionality == 3:\n                    return data[ftype, f\"{basename}_r\"] * np.cos(\n                        data[ftype, \"theta\"]\n                    ) - data[ftype, f\"{basename}_theta\"] * np.sin(data[ftype, \"theta\"])\n\n            def _cartesian_y(data):\n                if data.ds.dimensionality == 2:\n                    return data[ftype, f\"{basename}_z\"]\n                elif data.ds.dimensionality == 3:\n                    return data[ftype, f\"{basename}_r\"] * np.sin(\n                        data[ftype, \"theta\"]\n                    ) + data[ftype, f\"{basename}_theta\"] * np.cos(data[ftype, \"theta\"])\n\n            def _cartesian_z(data):\n                return data[ftype, f\"{basename}_z\"]\n\n        elif geometry is Geometry.SPHERICAL:\n\n            def _cartesian_x(data):\n                if data.ds.dimensionality == 2:\n                    return data[ftype, f\"{basename}_r\"] * np.sin(\n                        data[ftype, \"theta\"]\n                    ) + data[ftype, f\"{basename}_theta\"] * np.cos(data[ftype, \"theta\"])\n                elif data.ds.dimensionality == 3:\n                    return (\n                        data[ftype, f\"{basename}_r\"]\n                        * np.sin(data[ftype, \"theta\"])\n                        * np.cos(data[ftype, \"phi\"])\n                        + data[ftype, f\"{basename}_theta\"]\n                        * np.cos(data[ftype, \"theta\"])\n                        * np.cos(data[ftype, \"phi\"])\n                        - data[ftype, f\"{basename}_phi\"] * np.sin(data[ftype, \"phi\"])\n                    )\n\n            def _cartesian_y(data):\n                if data.ds.dimensionality == 2:\n                    return data[ftype, f\"{basename}_r\"] * np.cos(\n                        data[ftype, \"theta\"]\n                    ) - data[f\"{basename}_theta\"] * np.sin(data[ftype, \"theta\"])\n                elif data.ds.dimensionality == 3:\n                    return (\n                        data[ftype, f\"{basename}_r\"]\n                        * np.sin(data[ftype, \"theta\"])\n                        * np.sin(data[ftype, \"phi\"])\n                        + data[ftype, f\"{basename}_theta\"]\n                        * np.cos(data[ftype, \"theta\"])\n                        * np.sin(data[ftype, \"phi\"])\n                        + data[ftype, f\"{basename}_phi\"] * np.cos(data[ftype, \"phi\"])\n                    )\n\n            def _cartesian_z(data):\n                return data[ftype, f\"{basename}_r\"] * np.cos(\n                    data[ftype, \"theta\"]\n                ) - data[ftype, f\"{basename}_theta\"] * np.sin(data[ftype, \"theta\"])\n\n        else:\n            assert_never(geometry)\n\n        # it's redundant to define a cartesian x field for 1D data\n        if registry.ds.dimensionality >= 2:\n            registry.add_field(\n                (ftype, f\"{basename}_cartesian_x\"),\n                sampling_type=\"local\",\n                function=_cartesian_x,\n                units=field_units,\n                display_field=True,\n            )\n\n            registry.add_field(\n                (ftype, f\"{basename}_cartesian_y\"),\n                sampling_type=\"local\",\n                function=_cartesian_y,\n                units=field_units,\n                display_field=True,\n            )\n\n            registry.add_field(\n                (ftype, f\"{basename}_cartesian_z\"),\n                sampling_type=\"local\",\n                function=_cartesian_z,\n                units=field_units,\n                display_field=True,\n            )\n    elif (\n        geometry is Geometry.GEOGRAPHIC\n        or geometry is Geometry.INTERNAL_GEOGRAPHIC\n        or geometry is Geometry.SPECTRAL_CUBE\n    ):\n        # nothing to do\n        pass\n    else:\n        assert_never(geometry)\n\n    if registry.ds.geometry is Geometry.SPHERICAL:\n\n        def _cylindrical_radius_component(data):\n            return (\n                np.sin(data[ftype, \"theta\"]) * data[ftype, f\"{basename}_r\"]\n                + np.cos(data[ftype, \"theta\"]) * data[ftype, f\"{basename}_theta\"]\n            )\n\n        registry.add_field(\n            (ftype, f\"{basename}_cylindrical_radius\"),\n            sampling_type=\"local\",\n            function=_cylindrical_radius_component,\n            units=field_units,\n            display_field=True,\n        )\n\n        registry.alias(\n            (ftype, f\"{basename}_cylindrical_z\"),\n            (ftype, f\"{basename}_cartesian_z\"),\n        )\n\n        # define vector components appropriate for 'theta'-normal plots.\n        # The projection plane is called 'conic plane' in the code base as well as docs.\n        # Contrary to 'poloidal' and 'toroidal', this isn't a widely spread\n        # naming convention, but here it is exposed to users as part of dedicated\n        # field names, so it needs to be stable.\n        def _conic_x(data):\n            rax = axis_names.index(\"r\")\n            pax = axis_names.index(\"phi\")\n            bc = data.get_field_parameter(f\"bulk_{basename}\")\n            return np.cos(data[ftype, \"phi\"]) * (\n                data[ftype, f\"{basename}_r\"] - bc[rax]\n            ) - np.sin(data[ftype, \"phi\"]) * (data[ftype, f\"{basename}_phi\"] - bc[pax])\n\n        def _conic_y(data):\n            rax = axis_names.index(\"r\")\n            pax = axis_names.index(\"phi\")\n            bc = data.get_field_parameter(f\"bulk_{basename}\")\n            return np.sin(data[ftype, \"phi\"]) * (\n                data[ftype, f\"{basename}_r\"] - bc[rax]\n            ) + np.cos(data[ftype, \"phi\"]) * (data[ftype, f\"{basename}_phi\"] - bc[pax])\n\n        if registry.ds.dimensionality == 3:\n            registry.add_field(\n                (ftype, f\"{basename}_conic_x\"),\n                sampling_type=\"local\",\n                function=_conic_x,\n                units=field_units,\n                display_field=True,\n            )\n            registry.add_field(\n                (ftype, f\"{basename}_conic_y\"),\n                sampling_type=\"local\",\n                function=_conic_y,\n                units=field_units,\n                display_field=True,\n            )\n\n\ndef create_averaged_field(\n    registry,\n    basename,\n    field_units,\n    ftype=\"gas\",\n    slice_info=None,\n    validators=None,\n    weight=\"mass\",\n):\n    if validators is None:\n        validators = []\n    validators += [ValidateSpatial(1, [(ftype, basename)])]\n\n    def _averaged_field(data):\n        def atleast_4d(array):\n            if array.ndim == 3:\n                return array[..., None]\n            else:\n                return array\n\n        nx, ny, nz, ngrids = atleast_4d(data[ftype, basename]).shape\n        new_field = data.ds.arr(\n            np.zeros((nx - 2, ny - 2, nz - 2, ngrids), dtype=np.float64),\n            (just_one(data[ftype, basename]) * just_one(data[ftype, weight])).units,\n        )\n        weight_field = data.ds.arr(\n            np.zeros((nx - 2, ny - 2, nz - 2, ngrids), dtype=np.float64),\n            data[ftype, weight].units,\n        )\n        i_i, j_i, k_i = np.mgrid[0:3, 0:3, 0:3]\n\n        for i, j, k in zip(i_i.ravel(), j_i.ravel(), k_i.ravel(), strict=True):\n            sl = (\n                slice(i, nx - (2 - i)),\n                slice(j, ny - (2 - j)),\n                slice(k, nz - (2 - k)),\n            )\n            new_field += (\n                atleast_4d(data[ftype, basename])[sl]\n                * atleast_4d(data[ftype, weight])[sl]\n            )\n            weight_field += atleast_4d(data[ftype, weight])[sl]\n\n        # Now some fancy footwork\n        new_field2 = data.ds.arr(\n            np.zeros((nx, ny, nz, ngrids)), data[ftype, basename].units\n        )\n        new_field2[1:-1, 1:-1, 1:-1] = new_field / weight_field\n\n        if data[ftype, basename].ndim == 3:\n            return new_field2[..., 0]\n        else:\n            return new_field2\n\n    registry.add_field(\n        (ftype, f\"averaged_{basename}\"),\n        sampling_type=\"cell\",\n        function=_averaged_field,\n        units=field_units,\n        validators=validators,\n    )\n"
  },
  {
    "path": "yt/fields/xray_emission_fields.py",
    "content": "import os\n\nimport numpy as np\n\nfrom yt.config import ytcfg\nfrom yt.fields.derived_field import DerivedField\nfrom yt.funcs import mylog, only_on_root, parse_h5_attr\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.exceptions import YTException, YTFieldNotFound\nfrom yt.utilities.linear_interpolators import (\n    BilinearFieldInterpolator,\n    UnilinearFieldInterpolator,\n)\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\ndata_version = {\"cloudy\": 2, \"apec\": 3}\n\ndata_url = \"http://yt-project.org/data\"\n\n\ndef _get_data_file(table_type, data_dir=None):\n    data_file = f\"{table_type}_emissivity_v{data_version[table_type]}.h5\"\n    if data_dir is None:\n        supp_data_dir = ytcfg.get(\"yt\", \"supp_data_dir\")\n        data_dir = supp_data_dir if os.path.exists(supp_data_dir) else \".\"\n    data_path = os.path.join(data_dir, data_file)\n    if not os.path.exists(data_path):\n        msg = f\"Failed to find emissivity data file {data_file}! Please download from {data_url}\"\n        mylog.error(msg)\n        raise OSError(msg)\n    return data_path\n\n\nclass EnergyBoundsException(YTException):\n    def __init__(self, lower, upper):\n        self.lower = lower\n        self.upper = upper\n\n    def __str__(self):\n        return f\"Energy bounds are {self.lower:e} to {self.upper:e} keV.\"\n\n\nclass ObsoleteDataException(YTException):\n    def __init__(self, table_type):\n        data_file = f\"{table_type}_emissivity_v{data_version[table_type]}.h5\"\n        self.msg = \"X-ray emissivity data is out of date.\\n\"\n        self.msg += f\"Download the latest data from {data_url}/{data_file}.\"\n\n    def __str__(self):\n        return self.msg\n\n\nclass XrayEmissivityIntegrator:\n    r\"\"\"Class for making X-ray emissivity fields. Uses hdf5 data tables\n    generated from Cloudy and AtomDB/APEC.\n\n    Initialize an XrayEmissivityIntegrator object.\n\n    Parameters\n    ----------\n    table_type : string\n        The type of data to use when computing the emissivity values. If \"cloudy\",\n        a file called \"cloudy_emissivity.h5\" is used, for photoionized\n        plasmas. If, \"apec\", a file called \"apec_emissivity.h5\" is used for\n        collisionally ionized plasmas. These files contain emissivity tables\n        for primordial elements and for metals at solar metallicity for the\n        energy range 0.1 to 100 keV.\n    redshift : float, optional\n        The cosmological redshift of the source of the field. Default: 0.0.\n    data_dir : string, optional\n        The location to look for the data table in. If not supplied, the file\n        will be looked for in the location of the YT_DEST environment variable\n        or in the current working directory.\n    use_metals : boolean, optional\n        If set to True, the emissivity will include contributions from metals.\n        Default: True\n    \"\"\"\n\n    def __init__(self, table_type, redshift=0.0, data_dir=None, use_metals=True):\n        filename = _get_data_file(table_type, data_dir=data_dir)\n        only_on_root(mylog.info, \"Loading emissivity data from %s\", filename)\n        in_file = h5py.File(filename, mode=\"r\")\n        if \"info\" in in_file.attrs:\n            only_on_root(mylog.info, parse_h5_attr(in_file, \"info\"))\n        if parse_h5_attr(in_file, \"version\") != data_version[table_type]:\n            raise ObsoleteDataException(table_type)\n        else:\n            only_on_root(\n                mylog.info,\n                f\"X-ray '{table_type}' emissivity data version: \"\n                f\"{parse_h5_attr(in_file, 'version')}.\",\n            )\n\n        self.log_T = in_file[\"log_T\"][:]\n        self.emissivity_primordial = in_file[\"emissivity_primordial\"][:]\n        if \"log_nH\" in in_file:\n            self.log_nH = in_file[\"log_nH\"][:]\n        if use_metals:\n            self.emissivity_metals = in_file[\"emissivity_metals\"][:]\n        self.ebin = YTArray(in_file[\"E\"], \"keV\")\n        in_file.close()\n        self.dE = np.diff(self.ebin)\n        self.emid = 0.5 * (self.ebin[1:] + self.ebin[:-1]).to(\"erg\")\n        self.redshift = redshift\n\n    def get_interpolator(self, data_type, e_min, e_max, energy=True):\n        data = getattr(self, f\"emissivity_{data_type}\")\n        if not energy:\n            data = data[..., :] / self.emid.v\n        e_min = YTQuantity(e_min, \"keV\") * (1.0 + self.redshift)\n        e_max = YTQuantity(e_max, \"keV\") * (1.0 + self.redshift)\n        if (e_min - self.ebin[0]) / e_min < -1e-3 or (\n            e_max - self.ebin[-1]\n        ) / e_max > 1e-3:\n            raise EnergyBoundsException(self.ebin[0], self.ebin[-1])\n        e_is, e_ie = np.digitize([e_min, e_max], self.ebin)\n        e_is = np.clip(e_is - 1, 0, self.ebin.size - 1)\n        e_ie = np.clip(e_ie, 0, self.ebin.size - 1)\n\n        my_dE = self.dE[e_is:e_ie].copy()\n        # clip edge bins if the requested range is smaller\n        my_dE[0] -= e_min - self.ebin[e_is]\n        my_dE[-1] -= self.ebin[e_ie] - e_max\n\n        interp_data = (data[..., e_is:e_ie] * my_dE).sum(axis=-1)\n        if data.ndim == 2:\n            emiss = UnilinearFieldInterpolator(\n                np.log10(interp_data),\n                [self.log_T[0], self.log_T[-1]],\n                \"log_T\",\n                truncate=True,\n            )\n        else:\n            emiss = BilinearFieldInterpolator(\n                np.log10(interp_data),\n                [self.log_nH[0], self.log_nH[-1], self.log_T[0], self.log_T[-1]],\n                [\"log_nH\", \"log_T\"],\n                truncate=True,\n            )\n\n        return emiss\n\n\ndef add_xray_emissivity_field(\n    ds,\n    e_min,\n    e_max,\n    redshift=0.0,\n    metallicity=(\"gas\", \"metallicity\"),\n    table_type=\"cloudy\",\n    data_dir=None,\n    cosmology=None,\n    dist=None,\n    ftype=\"gas\",\n):\n    r\"\"\"Create X-ray emissivity fields for a given energy range.\n\n    Parameters\n    ----------\n    e_min : float\n        The minimum energy in keV for the energy band.\n    e_min : float\n        The maximum energy in keV for the energy band.\n    redshift : float, optional\n        The cosmological redshift of the source of the field. Default: 0.0.\n    metallicity : str or tuple of str or float, optional\n        Either the name of a metallicity field or a single floating-point\n        number specifying a spatially constant metallicity. Must be in\n        solar units. If set to None, no metals will be assumed. Default:\n        (\"gas\", \"metallicity\")\n    table_type : string, optional\n        The type of emissivity table to be used when creating the fields.\n        Options are \"cloudy\" or \"apec\". Default: \"cloudy\"\n    data_dir : string, optional\n        The location to look for the data table in. If not supplied, the file\n        will be looked for in the location of the YT_DEST environment variable\n        or in the current working directory.\n    cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional\n        If set and redshift > 0.0, this cosmology will be used when computing the\n        cosmological dependence of the emission fields. If not set, yt's default\n        LCDM cosmology will be used.\n    dist : (value, unit) tuple or :class:`~yt.units.yt_array.YTQuantity`, optional\n        The distance to the source, used for making intensity fields. You should\n        only use this if your source is nearby (not cosmological). Default: None\n    ftype : string, optional\n        The field type to use when creating the fields, default \"gas\"\n\n    This will create at least three fields:\n\n    \"xray_emissivity_{e_min}_{e_max}_keV\" (erg s^-1 cm^-3)\n    \"xray_luminosity_{e_min}_{e_max}_keV\" (erg s^-1)\n    \"xray_photon_emissivity_{e_min}_{e_max}_keV\" (photons s^-1 cm^-3)\n\n    and if a redshift or distance is specified it will create two others:\n\n    \"xray_intensity_{e_min}_{e_max}_keV\" (erg s^-1 cm^-3 arcsec^-2)\n    \"xray_photon_intensity_{e_min}_{e_max}_keV\" (photons s^-1 cm^-3 arcsec^-2)\n\n    These latter two are really only useful when making projections.\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"sloshing_nomag2_hdf5_plt_cnt_0100\")\n    >>> yt.add_xray_emissivity_field(ds, 0.5, 2)\n    >>> p = yt.ProjectionPlot(\n    ...     ds, \"x\", (\"gas\", \"xray_emissivity_0.5_2_keV\"), table_type=\"apec\"\n    ... )\n    >>> p.save()\n    \"\"\"\n    if not isinstance(metallicity, float) and metallicity is not None:\n        try:\n            metallicity = ds._get_field_info(metallicity)\n        except YTFieldNotFound as e:\n            raise RuntimeError(\n                f\"Your dataset does not have a {metallicity} field! \"\n                + \"Perhaps you should specify a constant metallicity instead?\"\n            ) from e\n\n    if table_type == \"cloudy\":\n        # Cloudy wants to scale by nH**2\n        other_n = \"H_nuclei_density\"\n    else:\n        # APEC wants to scale by nH*ne\n        other_n = \"El_number_density\"\n\n    def _norm_field(data):\n        return data[ftype, \"H_nuclei_density\"] * data[ftype, other_n]\n\n    ds.add_field(\n        (ftype, \"norm_field\"), _norm_field, units=\"cm**-6\", sampling_type=\"local\"\n    )\n\n    my_si = XrayEmissivityIntegrator(table_type, data_dir=data_dir, redshift=redshift)\n\n    em_0 = my_si.get_interpolator(\"primordial\", e_min, e_max)\n    emp_0 = my_si.get_interpolator(\"primordial\", e_min, e_max, energy=False)\n    if metallicity is not None:\n        em_Z = my_si.get_interpolator(\"metals\", e_min, e_max)\n        emp_Z = my_si.get_interpolator(\"metals\", e_min, e_max, energy=False)\n\n    def _emissivity_field(data):\n        with np.errstate(all=\"ignore\"):\n            dd = {\n                \"log_nH\": np.log10(data[ftype, \"H_nuclei_density\"]),\n                \"log_T\": np.log10(data[ftype, \"temperature\"]),\n            }\n\n        my_emissivity = np.power(10, em_0(dd))\n        if metallicity is not None:\n            if isinstance(metallicity, DerivedField):\n                my_Z = data[metallicity.name].to_value(\"Zsun\")\n            else:\n                my_Z = metallicity\n            my_emissivity += my_Z * np.power(10, em_Z(dd))\n\n        my_emissivity[np.isnan(my_emissivity)] = 0\n\n        return data[ftype, \"norm_field\"] * YTArray(my_emissivity, \"erg*cm**3/s\")\n\n    emiss_name = (ftype, f\"xray_emissivity_{e_min}_{e_max}_keV\")\n    ds.add_field(\n        emiss_name,\n        function=_emissivity_field,\n        display_name=rf\"\\epsilon_{{X}} ({e_min}-{e_max} keV)\",\n        sampling_type=\"local\",\n        units=\"erg/cm**3/s\",\n    )\n\n    def _luminosity_field(data):\n        return data[emiss_name] * data[ftype, \"mass\"] / data[ftype, \"density\"]\n\n    lum_name = (ftype, f\"xray_luminosity_{e_min}_{e_max}_keV\")\n    ds.add_field(\n        lum_name,\n        function=_luminosity_field,\n        display_name=rf\"\\rm{{L}}_{{X}} ({e_min}-{e_max} keV)\",\n        sampling_type=\"local\",\n        units=\"erg/s\",\n    )\n\n    def _photon_emissivity_field(data):\n        dd = {\n            \"log_nH\": np.log10(data[ftype, \"H_nuclei_density\"]),\n            \"log_T\": np.log10(data[ftype, \"temperature\"]),\n        }\n\n        my_emissivity = np.power(10, emp_0(dd))\n        if metallicity is not None:\n            if isinstance(metallicity, DerivedField):\n                my_Z = data[metallicity.name].to_value(\"Zsun\")\n            else:\n                my_Z = metallicity\n            my_emissivity += my_Z * np.power(10, emp_Z(dd))\n\n        return data[ftype, \"norm_field\"] * YTArray(my_emissivity, \"photons*cm**3/s\")\n\n    phot_name = (ftype, f\"xray_photon_emissivity_{e_min}_{e_max}_keV\")\n    ds.add_field(\n        phot_name,\n        function=_photon_emissivity_field,\n        display_name=rf\"\\epsilon_{{X}} ({e_min}-{e_max} keV)\",\n        sampling_type=\"local\",\n        units=\"photons/cm**3/s\",\n    )\n\n    fields = [emiss_name, lum_name, phot_name]\n\n    if redshift > 0.0 or dist is not None:\n        if dist is None:\n            if cosmology is None:\n                if hasattr(ds, \"cosmology\"):\n                    cosmology = ds.cosmology\n                else:\n                    cosmology = Cosmology()\n            D_L = cosmology.luminosity_distance(0.0, redshift)\n            angular_scale = 1.0 / cosmology.angular_scale(0.0, redshift)\n            dist_fac = ds.quan(\n                1.0 / (4.0 * np.pi * D_L * D_L * angular_scale * angular_scale).v,\n                \"rad**-2\",\n            )\n        else:\n            redshift = 0.0  # Only for local sources!\n            try:\n                # normal behaviour, if dist is a YTQuantity\n                dist = ds.quan(dist.value, dist.units)\n            except AttributeError as e:\n                try:\n                    dist = ds.quan(*dist)\n                except (RuntimeError, TypeError):\n                    raise TypeError(\n                        \"dist should be a YTQuantity or a (value, unit) tuple!\"\n                    ) from e\n\n            angular_scale = dist / ds.quan(1.0, \"radian\")\n            dist_fac = ds.quan(\n                1.0 / (4.0 * np.pi * dist * dist * angular_scale * angular_scale).v,\n                \"rad**-2\",\n            )\n\n        ei_name = (ftype, f\"xray_intensity_{e_min}_{e_max}_keV\")\n\n        def _intensity_field(data):\n            I = dist_fac * data[emiss_name]\n            return I.in_units(\"erg/cm**3/s/arcsec**2\")\n\n        ds.add_field(\n            ei_name,\n            function=_intensity_field,\n            display_name=rf\"I_{{X}} ({e_min}-{e_max} keV)\",\n            sampling_type=\"local\",\n            units=\"erg/cm**3/s/arcsec**2\",\n        )\n\n        i_name = (ftype, f\"xray_photon_intensity_{e_min}_{e_max}_keV\")\n\n        def _photon_intensity_field(data):\n            I = (1.0 + redshift) * dist_fac * data[phot_name]\n            return I.in_units(\"photons/cm**3/s/arcsec**2\")\n\n        ds.add_field(\n            i_name,\n            function=_photon_intensity_field,\n            display_name=rf\"I_{{X}} ({e_min}-{e_max} keV)\",\n            sampling_type=\"local\",\n            units=\"photons/cm**3/s/arcsec**2\",\n        )\n\n        fields += [ei_name, i_name]\n\n    for field in fields:\n        mylog.info(\"Adding ('%s','%s') field.\", field[0], field[1])\n\n    return fields\n"
  },
  {
    "path": "yt/frontends/__init__.py",
    "content": "__all__ = [\n    \"adaptahop\",\n    \"ahf\",\n    \"amrex\",\n    \"amrvac\",\n    \"art\",\n    \"artio\",\n    \"athena\",\n    \"athena_pp\",\n    \"boxlib\",  # the boxlib frontend is deprecated, use 'amrex'\n    \"cf_radial\",\n    \"chimera\",\n    \"chombo\",\n    \"cholla\",\n    \"enzo_e\",\n    \"enzo\",\n    \"exodus_ii\",\n    \"fits\",\n    \"flash\",\n    \"gadget\",\n    # breaking alphabetical order intentionnally here:\n    # arepo and eagle depend on gadget. Importing them first causes\n    # unintended side effects. See\n    # https://github.com/yt-project/yt/issues/4563\n    \"arepo\",\n    \"eagle\",\n    \"gadget_fof\",\n    \"gamer\",\n    \"gdf\",\n    \"gizmo\",\n    \"halo_catalog\",\n    \"http_stream\",\n    \"moab\",\n    \"nc4_cm1\",\n    \"open_pmd\",\n    \"owls\",\n    \"owls_subfind\",\n    \"parthenon\",\n    \"ramses\",\n    \"rockstar\",\n    \"sdf\",\n    \"stream\",\n    \"swift\",\n    \"tipsy\",\n    \"ytdata\",\n]\n\nfrom functools import lru_cache\n\n\n@lru_cache(maxsize=None)\ndef __getattr__(value):\n    import importlib\n\n    if value == \"_all\":\n        # recursively import all frontends\n        for _ in __all__:\n            __getattr__(_)\n        return\n\n    if value not in __all__:\n        raise AttributeError(f\"yt.frontends has no attribute {value!r}\")\n\n    return importlib.import_module(f\"yt.frontends.{value}.api\")\n"
  },
  {
    "path": "yt/frontends/_skeleton/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/_skeleton/api.py",
    "content": "from .data_structures import SkeletonDataset, SkeletonGrid, SkeletonHierarchy\nfrom .fields import SkeletonFieldInfo\nfrom .io import SkeletonIOHandler\n"
  },
  {
    "path": "yt/frontends/_skeleton/data_structures.py",
    "content": "import os\nimport weakref\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import setdefaultattr\nfrom yt.geometry.grid_geometry_handler import GridIndex\n\nfrom .fields import SkeletonFieldInfo\n\n\nclass SkeletonGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level):\n        super().__init__(id, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n\n\nclass SkeletonHierarchy(GridIndex):\n    grid = SkeletonGrid\n\n    def __init__(self, ds, dataset_type=\"skeleton\"):\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        # for now, the index file is the dataset!\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        # float type for the simulation edges and must be float64 now\n        self.float_type = np.float64\n        super().__init__(ds, dataset_type)\n\n    def _detect_output_fields(self):\n        # This needs to set a self.field_list that contains all the available,\n        # on-disk fields. No derived fields should be defined here.\n        # NOTE: Each should be a tuple, where the first element is the on-disk\n        # fluid type or particle type.  Convention suggests that the on-disk\n        # fluid type is usually the dataset_type and the on-disk particle type\n        # (for a single population of particles) is \"io\".\n        pass\n\n    def _count_grids(self):\n        # This needs to set self.num_grids (int)\n        pass\n\n    def _parse_index(self):\n        # This needs to fill the following arrays, where N is self.num_grids:\n        #   self.grid_left_edge         (N, 3) <= float64\n        #   self.grid_right_edge        (N, 3) <= float64\n        #   self.grid_dimensions        (N, 3) <= int\n        #   self.grid_particle_count    (N, 1) <= int\n        #   self.grid_levels            (N, 1) <= int\n        #   self.grids                  (N, 1) <= grid objects\n        #   self.max_level = self.grid_levels.max()\n        pass\n\n    def _populate_grid_objects(self):\n        # the minimal form of this method is\n        #\n        # for g in self.grids:\n        #     g._prepare_grid()\n        #     g._setup_dx()\n        #\n        # This must also set:\n        #   g.Children <= list of child grids\n        #   g.Parent   <= parent grid\n        # This is handled by the frontend because often the children must be identified.\n        pass\n\n\nclass SkeletonDataset(Dataset):\n    _index_class = SkeletonHierarchy\n    _field_info_class = SkeletonFieldInfo\n\n    # names of additional modules that may be required to load data from disk\n    _load_requirements: list[str] = []\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"skeleton\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        self.fluid_types += (\"skeleton\",)\n        super().__init__(\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        self.storage_filename = storage_filename\n        # refinement factor between a grid and its subgrid\n        # self.refine_by = 2\n\n    def _set_code_unit_attributes(self):\n        # This is where quantities are created that represent the various\n        # on-disk units.  These are the currently available quantities which\n        # should be set, along with examples of how to set them to standard\n        # values.\n        #\n        # self.length_unit = self.quan(1.0, \"cm\")\n        # self.mass_unit = self.quan(1.0, \"g\")\n        # self.time_unit = self.quan(1.0, \"s\")\n        # self.time_unit = self.quan(1.0, \"s\")\n        #\n        # These can also be set:\n        # self.velocity_unit = self.quan(1.0, \"cm/s\")\n        # self.magnetic_unit = self.quan(1.0, \"gauss\")\n        #\n        # If your frontend uses SI EM units, set magnetic units like this\n        # instead:\n        # self.magnetic_unit = self.quan(1.0, \"T\")\n\n        # this minimalistic implementation fills the requirements for\n        # this frontend to run, change it to make it run _correctly_ !\n        for key, unit in self.__class__.default_units.items():\n            setdefaultattr(self, key, self.quan(1, unit))\n\n    def _parse_parameter_file(self):\n        # This needs to set up the following items.  Note that these are all\n        # assumed to be in code units; domain_left_edge and domain_right_edge\n        # will be converted to YTArray automatically at a later time.\n        # This includes the cosmological parameters.\n        #\n        #   self.unique_identifier      <= unique identifier for the dataset\n        #                                  being read (e.g., UUID or ST_CTIME)\n        #   self.parameters             <= dict full of code-specific items of use\n        #   self.domain_left_edge       <= three-element array of float64\n        #   self.domain_right_edge      <= three-element array of float64\n        #   self.dimensionality         <= int\n        #   self.domain_dimensions      <= three-element array of int64\n        #   self.periodicity            <= three-element tuple of booleans\n        #   self.current_time           <= simulation time in code units (float)\n        #\n        # We also set up cosmological information.  Set these to zero if\n        # non-cosmological.\n        #\n        #   self.cosmological_simulation    <= int, 0 or 1\n        #   self.current_redshift           <= float\n        #   self.omega_lambda               <= float\n        #   self.omega_matter               <= float\n        #   self.hubble_constant            <= float\n\n        # optional (the following have default implementations)\n        #\n        #   self.geometry: any yt.geometry.api.Geometry enum member\n        #                  defaults to Geometry.CARTESIAN\n\n        # this attribute is required.\n        # Change this value to a constant 0 if time is not relevant to your dataset.\n        # Otherwise, parse its value in any appropriate fashion.\n        self.current_time = -1\n\n        # required. Change this if need be.\n        self.cosmological_simulation = 0\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        # This accepts a filename or a set of arguments and returns True or\n        # False depending on if the file is of the type requested.\n        #\n        # The functionality in this method should be unique enough that it can\n        # differentiate the frontend from others. Sometimes this means looking\n        # for specific fields or attributes in the dataset in addition to\n        # looking at the file name or extension.\n        return False\n"
  },
  {
    "path": "yt/frontends/_skeleton/definitions.py",
    "content": "# This file is often empty.  It can hold definitions related to a frontend.\n"
  },
  {
    "path": "yt/frontends/_skeleton/fields.py",
    "content": "from yt.fields.field_info_container import FieldInfoContainer\n\n# We need to specify which fields we might have in our dataset.  The field info\n# container subclass here will define which fields it knows about.  There are\n# optionally methods on it that get called which can be subclassed.\n\n\nclass SkeletonFieldInfo(FieldInfoContainer):\n    known_other_fields = (\n        # Each entry here is of the form\n        # ( \"name\", (\"units\", [\"fields\", \"to\", \"alias\"], # \"display_name\")),\n    )\n\n    known_particle_fields = (\n        # Identical form to above\n        # ( \"name\", (\"units\", [\"fields\", \"to\", \"alias\"], # \"display_name\")),\n    )\n\n    def __init__(self, ds, field_list):\n        super().__init__(ds, field_list)\n        # If you want, you can check self.field_list\n\n    def setup_fluid_fields(self):\n        # Here we do anything that might need info about the dataset.\n        # You can use self.alias, self.add_output_field (for on-disk fields)\n        # and self.add_field (for derived fields).\n        pass\n\n    def setup_particle_fields(self, ptype):\n        super().setup_particle_fields(ptype)\n        # This will get called for every particle type.\n"
  },
  {
    "path": "yt/frontends/_skeleton/io.py",
    "content": "from yt.utilities.io_handler import BaseIOHandler\n\n\nclass SkeletonIOHandler(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"skeleton\"\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This needs to *yield* a series of tuples of (ptype, (x, y, z)).\n        # chunks is a list of chunks, and ptf is a dict where the keys are\n        # ptypes and the values are lists of fields.\n        pass\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # This gets called after the arrays have been allocated.  It needs to\n        # yield ((ptype, field), data) where data is the masked results of\n        # reading ptype, field and applying the selector to the data read in.\n        # Selector objects have a .select_points(x,y,z) that returns a mask, so\n        # you need to do your masking here.\n        pass\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        # This needs to allocate a set of arrays inside a dictionary, where the\n        # keys are the (ftype, fname) tuples and the values are arrays that\n        # have been masked using whatever selector method is appropriate.  The\n        # dict gets returned at the end and it should be flat, with selected\n        # data.  Note that if you're reading grid data, you might need to\n        # special-case a grid selector object.\n        # Also note that \"chunks\" is a generator for multiple chunks, each of\n        # which contains a list of grids. The returned numpy arrays should be\n        # in 64-bit float and contiguous along the z direction. Therefore, for\n        # a C-like input array with the dimension [x][y][z] or a\n        # Fortran-like input array with the dimension (z,y,x), a matrix\n        # transpose is required (e.g., using np_array.transpose() or\n        # np_array.swapaxes(0,2)).\n\n        # This method is not abstract, and has a default implementation\n        # in the base class.However, the default implementation requires that the method\n        # io_iter be defined\n        pass\n\n    def _read_chunk_data(self, chunk, fields):\n        # This reads the data from a single chunk without doing any selection,\n        # and is only used for caching data that might be used by multiple\n        # different selectors later. For instance, this can speed up ghost zone\n        # computation.\n        pass\n"
  },
  {
    "path": "yt/frontends/_skeleton/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/adaptahop/__init__.py",
    "content": "\"\"\"\nAPI for AdaptaHOP frontend.\n\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/adaptahop/api.py",
    "content": "\"\"\"\nAPI for AdaptaHOP frontend\n\n\n\n\n\"\"\"\n\nfrom . import tests\nfrom .data_structures import AdaptaHOPDataset\nfrom .fields import AdaptaHOPFieldInfo\nfrom .io import IOHandlerAdaptaHOPBinary\n"
  },
  {
    "path": "yt/frontends/adaptahop/data_structures.py",
    "content": "\"\"\"\nData structures for AdaptaHOP frontend.\n\n\n\n\n\"\"\"\n\nimport os\nimport re\nfrom itertools import product\n\nimport numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.frontends.halo_catalog.data_structures import HaloCatalogFile\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.units import Mpc  # type: ignore\nfrom yt.utilities.cython_fortran_utils import FortranFile\n\nfrom .definitions import ADAPTAHOP_TEMPLATES, ATTR_T, HEADER_ATTRIBUTES\nfrom .fields import AdaptaHOPFieldInfo\n\n\nclass AdaptaHOPParticleIndex(ParticleIndex):\n    def _setup_filenames(self):\n        template = self.dataset.filename_template\n        ndoms = self.dataset.file_count\n        cls = self.dataset._file_class\n        if ndoms > 1:\n            self.data_files = [\n                cls(self.dataset, self.io, template % {\"num\": i}, i, None)\n                for i in range(ndoms)\n            ]\n        else:\n            self.data_files = [\n                cls(\n                    self.dataset,\n                    self.io,\n                    self.dataset.parameter_filename,\n                    0,\n                    None,\n                )\n            ]\n\n\nclass AdaptaHOPDataset(Dataset):\n    _index_class = AdaptaHOPParticleIndex\n    _file_class = HaloCatalogFile\n    _field_info_class = AdaptaHOPFieldInfo\n\n    # AdaptaHOP internally assumes 1Mpc == 3.0824cm\n    _code_length_to_Mpc = (1.0 * Mpc).to_value(\"cm\") / 3.08e24\n    _header_attributes: ATTR_T | None = None\n    _halo_attributes: ATTR_T | None = None\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"adaptahop_binary\",\n        n_ref=16,\n        num_zones=2,\n        units_override=None,\n        unit_system=\"cgs\",\n        parent_ds=None,\n    ):\n        self.n_ref = n_ref\n        self.num_zones = num_zones\n        if parent_ds is None:\n            raise RuntimeError(\n                \"The AdaptaHOP frontend requires a parent dataset \"\n                \"to be passed as `parent_ds`.\"\n            )\n        self.parent_ds = parent_ds\n\n        self._guess_headers_from_file(filename)\n\n        super().__init__(\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n\n    def _set_code_unit_attributes(self):\n        setdefaultattr(self, \"length_unit\", self.quan(self._code_length_to_Mpc, \"Mpc\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1e11, \"Msun\"))\n        setdefaultattr(self, \"velocity_unit\", self.quan(1.0, \"km / s\"))\n        setdefaultattr(self, \"time_unit\", self.length_unit / self.velocity_unit)\n\n    def _guess_headers_from_file(self, filename) -> None:\n        with FortranFile(filename) as fpu:\n            ok = False\n            for dp, longint in product((True, False), (True, False)):\n                fpu.seek(0)\n                try:\n                    header_attributes = HEADER_ATTRIBUTES(double=dp, longint=longint)\n                    fpu.read_attrs(header_attributes)\n                    ok = True\n                    break\n                except (ValueError, OSError):\n                    pass\n\n            if not ok:\n                raise OSError(f\"Could not read headers from file {filename}\")\n\n            istart = fpu.tell()\n            fpu.seek(0, 2)\n            iend = fpu.tell()\n\n            # Try different templates\n            ok = False\n            for name, cls in ADAPTAHOP_TEMPLATES.items():\n                fpu.seek(istart)\n                attributes = cls(longint, dp).HALO_ATTRIBUTES\n                mylog.debug(\"Trying %s(longint=%s, dp=%s)\", name, longint, dp)\n                try:\n                    # Try to read two halos to be sure\n                    fpu.read_attrs(attributes)\n                    if fpu.tell() < iend:\n                        fpu.read_attrs(attributes)\n                    ok = True\n                    break\n                except (ValueError, OSError):\n                    continue\n\n        if not ok:\n            raise OSError(f\"Could not guess fields from file {filename}\")\n\n        self._header_attributes = header_attributes\n        self._halo_attributes = attributes\n\n    def _parse_parameter_file(self):\n        with FortranFile(self.parameter_filename) as fpu:\n            params = fpu.read_attrs(self._header_attributes)\n        self.dimensionality = 3\n        # Domain related things\n        self.filename_template = self.parameter_filename\n        self.file_count = 1\n        nz = self.num_zones\n        self.domain_dimensions = np.ones(3, \"int32\") * nz\n\n        # Set things up\n        self.cosmological_simulation = 1\n        self.current_redshift = (1.0 / params[\"aexp\"]) - 1.0\n        self.omega_matter = params[\"omega_t\"]\n        self.current_time = self.quan(params[\"age\"], \"Gyr\")\n        self.omega_lambda = 0.724  # hard coded if not inferred from parent ds\n        self.hubble_constant = 0.7  # hard coded if not inferred from parent ds\n        self._periodicity = (True, True, True)\n        self.particle_types = \"halos\"\n        self.particle_types_raw = \"halos\"\n\n        # Inherit stuff from parent ds -- if they exist\n        for k in (\"omega_lambda\", \"hubble_constant\", \"omega_matter\", \"omega_radiation\"):\n            v = getattr(self.parent_ds, k, None)\n            if v is not None:\n                setattr(self, k, v)\n\n        self.domain_left_edge = np.array([0.0, 0.0, 0.0])\n        self.domain_right_edge = (\n            self.parent_ds.domain_right_edge.to_value(\"Mpc\") * self._code_length_to_Mpc\n        )\n\n        self.parameters.update(params)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        fname = os.path.split(filename)[1]\n        if not fname.startswith(\"tree_bricks\") or not re.match(\n            r\"^tree_bricks\\d{3}$\", fname\n        ):\n            return False\n        return True\n\n    def halo(self, halo_id, ptype=\"DM\"):\n        \"\"\"\n        Create a data container to get member particles and individual\n        values from halos. Halo mass, position, and velocity are set as attributes.\n        Halo IDs are accessible through the field, \"member_ids\".  Other fields that\n        are one value per halo are accessible as normal.  The field list for\n        halo objects can be seen in `ds.halos_field_list`.\n\n        Parameters\n        ----------\n        halo_id : int\n            The id of the halo or group\n        ptype : str, default DM\n            The type of particles the halo is made of.\n        \"\"\"\n        return AdaptaHOPHaloContainer(\n            ptype, halo_id, parent_ds=self.parent_ds, halo_ds=self\n        )\n\n\nclass AdaptaHOPHaloContainer(YTSelectionContainer):\n    \"\"\"\n    Create a data container to get member particles and individual\n    values from halos. Halo mass, position, and velocity are set as attributes.\n    Halo IDs are accessible through the field, \"member_ids\".  Other fields that\n    are one value per halo are accessible as normal.  The field list for\n    halo objects can be seen in `ds.halos_field_list`.\n\n    Parameters\n    ----------\n    ptype : string\n        The type of halo, either \"Group\" for the main halo or\n        \"Subhalo\" for subhalos.\n    particle_identifier : int or tuple of ints\n        The halo or subhalo id.  If requesting a subhalo, the id\n        can also be given as a tuple of the main halo id and\n        subgroup id, such as (1, 4) for subgroup 4 of halo 1.\n\n    Attributes\n    ----------\n    particle_identifier : int\n        The id of the halo or subhalo.\n    group_identifier : int\n        For subhalos, the id of the enclosing halo.\n    subgroup_identifier : int\n        For subhalos, the relative id of the subhalo within\n        the enclosing halo.\n    particle_number : int\n        Number of particles in the halo.\n    mass : float\n        Halo mass.\n    position : array of floats\n        Halo position.\n    velocity : array of floats\n        Halo velocity.\n\n    Note\n    ----\n    Relevant Fields:\n\n     * particle_number - number of particles\n     * subhalo_number - number of subhalos\n     * group_identifier - id of parent group for subhalos\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\n    ...     \"output_00080_halos/tree_bricks080\",\n    ...     parent_ds=yt.load(\"output_00080/info_00080.txt\"),\n    ... )\n    >>> ds.halo(1, ptype=\"io\")\n    >>> print(halo.mass)\n    119.22804260253906 100000000000.0*Msun\n    >>> print(halo.position)\n    [26.80901299 24.35978484  5.45388672] code_length\n    >>> print(halo.velocity)\n    [3306394.95849609 8584366.60766602 9982682.80029297] cm/s\n    >>> print(halo[\"io\", \"particle_mass\"])\n    [3.19273578e-06 3.19273578e-06 ... 3.19273578e-06 3.19273578e-06] code_mass\n\n    >>> # particle ids for this halo\n    >>> print(halo.member_ids)\n    [     48      64     176 ... 999947 1005471 1006779]\n\n    \"\"\"\n\n    _type_name = \"halo\"\n    _con_args = (\"ptype\", \"particle_identifier\", \"parent_ds\", \"halo_ds\")\n    _spatial = False\n    # Do not register it to prevent .halo from being attached to all datasets\n    _skip_add = True\n\n    def __init__(self, ptype, particle_identifier, parent_ds, halo_ds):\n        if ptype not in parent_ds.particle_types_raw:\n            raise RuntimeError(\n                f'Possible halo types are {parent_ds.particle_types_raw}, supplied \"{ptype}\".'\n            )\n\n        # Setup required fields\n        self._dimensionality = 3\n        self.ds = parent_ds\n\n        # Halo-specific\n        self.halo_ds = halo_ds\n        self.ptype = ptype\n        self.particle_identifier = particle_identifier\n\n        # Set halo particles data\n        self._set_halo_properties()\n        self._set_halo_member_data()\n\n        # Call constructor\n        super().__init__(parent_ds, {})\n\n    def __repr__(self):\n        return f\"{self.ds}_{self.ptype}_{self.particle_identifier:09}\"\n\n    def __getitem__(self, key):\n        return self.region[key]\n\n    @property\n    def ihalo(self):\n        \"\"\"The index in the catalog of the halo\"\"\"\n        ihalo = getattr(self, \"_ihalo\", None)\n        if ihalo:\n            return ihalo\n        else:\n            halo_id = self.particle_identifier\n            halo_ids = self.halo_ds.r[\"halos\", \"particle_identifier\"].astype(\"int64\")\n            ihalo = np.searchsorted(halo_ids, halo_id)\n\n            assert halo_ids[ihalo] == halo_id\n\n            self._ihalo = ihalo\n            return self._ihalo\n\n    def _set_halo_member_data(self):\n        ptype = self.ptype\n        halo_ds = self.halo_ds\n        parent_ds = self.ds\n        ihalo = self.ihalo\n\n        # Note: convert to physical units to prevent errors when jumping\n        # from halo_ds to parent_ds\n        halo_pos = halo_ds.r[\"halos\", \"particle_position\"][ihalo, :].to_value(\"Mpc\")\n        halo_vel = halo_ds.r[\"halos\", \"particle_velocity\"][ihalo, :].to_value(\"km/s\")\n        halo_radius = halo_ds.r[\"halos\", \"r\"][ihalo].to_value(\"Mpc\")\n\n        members = self.member_ids\n        ok = False\n        f = 1 / 1.1\n        center = parent_ds.arr(halo_pos, \"Mpc\")\n        radius = parent_ds.arr(halo_radius, \"Mpc\")\n        # Find smallest sphere containing all particles\n        while not ok:\n            f *= 1.1\n            sph = parent_ds.sphere(center, f * radius)\n\n            part_ids = sph[ptype, \"particle_identity\"].astype(\"int64\")\n\n            ok = len(np.lib.arraysetops.setdiff1d(members, part_ids)) == 0\n\n        # Set bulk velocity\n        sph.set_field_parameter(\"bulk_velocity\", (halo_vel, \"km/s\"))\n\n        # Build subregion that only contains halo particles\n        reg = sph.cut_region(\n            ['np.isin(obj[\"io\", \"particle_identity\"].astype(\"int64\"), members)'],\n            locals={\"members\": members, \"np\": np},\n        )\n\n        self.sphere = sph\n        self.region = reg\n\n    def _set_halo_properties(self):\n        ihalo = self.ihalo\n        ds = self.halo_ds\n        # Add position, mass, velocity member functions\n        for attr_name in (\"mass\", \"position\", \"velocity\"):\n            setattr(self, attr_name, ds.r[\"halos\", f\"particle_{attr_name}\"][ihalo])\n        # Add members\n        self.member_ids = self.halo_ds.index.io.members(ihalo).astype(np.int64)\n"
  },
  {
    "path": "yt/frontends/adaptahop/definitions.py",
    "content": "\"\"\"\nData structures for AdaptaHOP\n\n\n\n\"\"\"\n\nfrom yt.funcs import mylog\n\nATTR_T = tuple[tuple[tuple[str, ...] | str, int, str], ...]\n\n\ndef HEADER_ATTRIBUTES(*, double: bool, longint: bool) -> ATTR_T:\n    int_type = \"l\" if longint else \"i\"\n    float_type = \"d\" if double else \"f\"\n    return (\n        (\"npart\", 1, int_type),\n        (\"massp\", 1, float_type),\n        (\"aexp\", 1, float_type),\n        (\"omega_t\", 1, float_type),\n        (\"age\", 1, float_type),\n        ((\"nhalos\", \"nsubs\"), 2, \"i\"),\n    )\n\n\nADAPTAHOP_TEMPLATES = {}\n\n\nclass AdaptaHOPDefTemplate:\n    # this is a mixin class\n    def __init_subclass__(cls, *args, **kwargs):\n        super().__init_subclass__(*args, **kwargs)\n        mylog.debug(\"Registering AdaptaHOP template class %s\", cls.__name__)\n        ADAPTAHOP_TEMPLATES[cls.__name__] = cls\n\n    def __init__(self, longint, double_precision):\n        self.longint = longint\n        self.double_precision = double_precision\n\n\nclass AdaptaHOPOld(AdaptaHOPDefTemplate):\n    @property\n    def HALO_ATTRIBUTES(self) -> ATTR_T:\n        int_type = \"l\" if self.longint else \"i\"\n        float_type = \"d\" if self.double_precision else \"f\"\n        return (\n            (\"npart\", 1, int_type),\n            (\"particle_identities\", -1, int_type),\n            (\"particle_identifier\", 1, \"i\"),  # this is the halo id, always an int32\n            (\"timestep\", 1, \"i\"),\n            (\n                (\n                    \"level\",\n                    \"host_id\",\n                    \"first_subhalo_id\",\n                    \"n_subhalos\",\n                    \"next_subhalo_id\",\n                ),\n                5,\n                \"i\",\n            ),\n            (\"particle_mass\", 1, float_type),\n            ((\"raw_position_x\", \"raw_position_y\", \"raw_position_z\"), 3, float_type),\n            (\n                (\"particle_velocity_x\", \"particle_velocity_y\", \"particle_velocity_z\"),\n                3,\n                float_type,\n            ),\n            (\n                (\n                    \"particle_angular_momentum_x\",\n                    \"particle_angular_momentum_y\",\n                    \"particle_angular_momentum_z\",\n                ),\n                3,\n                float_type,\n            ),\n            ((\"r\", \"a\", \"b\", \"c\"), 4, float_type),\n            ((\"ek\", \"ep\", \"etot\"), 3, float_type),\n            (\"spin\", 1, float_type),\n            (\n                (\n                    \"virial_radius\",\n                    \"virial_mass\",\n                    \"virial_temperature\",\n                    \"virial_velocity\",\n                ),\n                4,\n                float_type,\n            ),\n            ((\"rho0\", \"R_c\"), 2, float_type),\n        )\n\n\nclass AdaptaHOPNewNoContam(AdaptaHOPDefTemplate):\n    @property\n    def HALO_ATTRIBUTES(self) -> ATTR_T:\n        int_type = \"l\" if self.longint else \"i\"\n        float_type = \"d\" if self.double_precision else \"f\"\n        return (\n            (\"npart\", 1, int_type),\n            (\"particle_identities\", -1, int_type),\n            (\"particle_identifier\", 1, \"i\"),  # this is the halo id, always an int32\n            (\"timestep\", 1, \"i\"),\n            (\n                (\n                    \"level\",\n                    \"host_id\",\n                    \"first_subhalo_id\",\n                    \"n_subhalos\",\n                    \"next_subhalo_id\",\n                ),\n                5,\n                \"i\",\n            ),\n            (\"particle_mass\", 1, float_type),\n            (\"npart_tot\", 1, int_type),\n            (\"particle_mass_tot\", 1, float_type),\n            ((\"raw_position_x\", \"raw_position_y\", \"raw_position_z\"), 3, float_type),\n            (\n                (\"particle_velocity_x\", \"particle_velocity_y\", \"particle_velocity_z\"),\n                3,\n                float_type,\n            ),\n            (\n                (\n                    \"particle_angular_momentum_x\",\n                    \"particle_angular_momentum_y\",\n                    \"particle_angular_momentum_z\",\n                ),\n                3,\n                float_type,\n            ),\n            ((\"r\", \"a\", \"b\", \"c\"), 4, float_type),\n            ((\"ek\", \"ep\", \"etot\"), 3, float_type),\n            (\"spin\", 1, float_type),\n            (\"velocity_dispersion\", 1, float_type),\n            (\n                (\n                    \"virial_radius\",\n                    \"virial_mass\",\n                    \"virial_temperature\",\n                    \"virial_velocity\",\n                ),\n                4,\n                float_type,\n            ),\n            ((\"rmax\", \"vmax\"), 2, float_type),\n            (\"concentration\", 1, float_type),\n            ((\"radius_200\", \"mass_200\"), 2, float_type),\n            ((\"radius_50\", \"mass_50\"), 2, float_type),\n            (\"radius_profile\", -1, float_type),\n            (\"rho_profile\", -1, float_type),\n            ((\"rho0\", \"R_c\"), 2, float_type),\n        )\n\n\nclass AdaptaHOPNewContam(AdaptaHOPNewNoContam):\n    @property\n    def HALO_ATTRIBUTES(self) -> ATTR_T:\n        attrs = list(super().HALO_ATTRIBUTES)\n        int_type = \"l\" if self.longint else \"i\"\n        float_type = \"d\" if self.double_precision else \"f\"\n        return tuple(\n            attrs\n            + [\n                (\"contaminated\", 1, \"i\"),\n                ((\"m_contam\", \"mtot_contam\"), 2, float_type),\n                ((\"n_contam\", \"ntot_contam\"), 2, int_type),\n            ]\n        )\n"
  },
  {
    "path": "yt/frontends/adaptahop/fields.py",
    "content": "\"\"\"\nAdaptaHOP-specific fields\n\n\n\n\n\"\"\"\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\nm_units = \"1e11 * Msun\"\nr_units = \"Mpc\"\nv_units = \"km / s\"\nl_units = \"1e11 * Msun * Mpc * km / s\"\ne_units = \"1e11 * Msun * km**2 / s**2\"\ndens_units = \"1e11 * Msun / Mpc**3\"\n\n\nclass AdaptaHOPFieldInfo(FieldInfoContainer):\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_identifier\", (\"\", [], \"Halo Identity\")),\n        (\"raw_position_x\", (r_units, [], None)),\n        (\"raw_position_y\", (r_units, [], None)),\n        (\"raw_position_z\", (r_units, [], None)),\n        (\"r\", (r_units, [], \"Halo radius\")),\n        (\"a\", (r_units, [], \"Halo semi-major axis\")),\n        (\"b\", (r_units, [], \"Halo semi-medium axis\")),\n        (\"c\", (r_units, [], \"Halo semi-minor axis\")),\n        (\"particle_velocity_x\", (v_units, [], \"Halo velocity x\")),\n        (\"particle_velocity_y\", (v_units, [], \"Halo velocity y\")),\n        (\"particle_velocity_z\", (v_units, [], \"Halo velocity z\")),\n        (\"particle_angular_momentum_x\", (l_units, [], \"Halo Angular Momentum x\")),\n        (\"particle_angular_momentum_y\", (l_units, [], \"Halo Angular Momentum y\")),\n        (\"particle_angular_momentum_z\", (l_units, [], \"Halo Angular Momentum z\")),\n        (\"particle_mass\", (m_units, [], \"Halo mass\")),\n        (\"ek\", (e_units, [], \"Halo Kinetic Energy\")),\n        (\"ep\", (e_units, [], \"Halo Gravitational Energy\")),\n        (\"ek\", (e_units, [], \"Halo Total Energy\")),\n        (\"spin\", (\"\", [], \"Halo Spin Parameter\")),\n        # Virial parameters\n        (\"virial_radius\", (r_units, [], \"Halo Virial Radius\")),\n        (\"virial_mass\", (m_units, [], \"Halo Virial Mass\")),\n        (\"virial_temperature\", (\"K\", [], \"Halo Virial Temperature\")),\n        (\"virial_velocity\", (v_units, [], \"Halo Virial Velocity\")),\n        # NFW parameters\n        (\"rho0\", (dens_units, [], \"Halo NFW Density\")),\n        (\"R_c\", (r_units, [], \"Halo NFW Scale Radius\")),\n        (\"velocity_dispersion\", (\"km/s\", [], \"Velocity Dispersion\")),\n        (\"radius_200\", (r_units, [], r\"$R_\\mathrm{200}$\")),\n        (\"radius_50\", (r_units, [], r\"$R_\\mathrm{50}$\")),\n        (\"mass_200\", (m_units, [], r\"$M_\\mathrm{200}$\")),\n        (\"mass_50\", (m_units, [], r\"$M_\\mathrm{50}$\")),\n        # Contamination\n        (\"contaminated\", (\"\", [], \"Contaminated\")),\n        (\"m_contam\", (m_units, [], \"Contaminated Mass\")),\n    )\n\n    def setup_particle_fields(self, ptype):\n        super().setup_particle_fields(ptype)\n\n        # Add particle position\n        def generate_pos_field(d):\n            shift = self.ds.domain_width[0] / 2\n\n            def closure(data):\n                return data[\"halos\", f\"raw_position_{d}\"] + shift\n\n            return closure\n\n        for k in \"xyz\":\n            fun = generate_pos_field(k)\n            self.add_field(\n                (\"halos\", f\"particle_position_{k}\"),\n                sampling_type=\"particle\",\n                function=fun,\n                units=\"Mpc\",\n            )\n"
  },
  {
    "path": "yt/frontends/adaptahop/io.py",
    "content": "\"\"\"\nAdaptaHOP data-file handling function\n\n\n\n\n\"\"\"\n\nfrom functools import partial\nfrom operator import attrgetter\n\nimport numpy as np\n\nfrom yt.utilities.cython_fortran_utils import FortranFile\nfrom yt.utilities.io_handler import BaseParticleIOHandler\n\nfrom .definitions import ATTR_T\n\n\nclass IOHandlerAdaptaHOPBinary(BaseParticleIOHandler):\n    _dataset_type = \"adaptahop_binary\"\n\n    _offsets = None  # Location of halos in the file\n    _particle_positions = None  # Buffer of halo position\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _read_chunk_data(self, chunk, fields):\n        raise NotImplementedError\n\n    def _yield_coordinates(self, data_file):\n        yield \"halos\", self._get_particle_positions()\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This will read chunks and yield the results.\n        # Only support halo reading for now.\n        assert len(ptf) == 1\n        assert list(ptf.keys())[0] == \"halos\"\n        ptype = \"halos\"\n        for data_file in self._sorted_chunk_iterator(chunks):\n            pcount = (\n                data_file.ds.parameters[\"nhalos\"] + data_file.ds.parameters[\"nsubs\"]\n            )\n            if pcount == 0:\n                continue\n            pos = self._get_particle_positions()\n            yield ptype, [pos[:, i] for i in range(3)], 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # Now we have all the sizes, and we can allocate\n\n        # Only support halo reading for now.\n        assert len(ptf) == 1\n        assert list(ptf.keys())[0] == \"halos\"\n\n        def iterate_over_attributes(attr_list):\n            for attr, *_ in attr_list:\n                if isinstance(attr, tuple):\n                    yield from attr\n                else:\n                    yield attr\n\n        halo_attributes = self.ds._halo_attributes\n\n        attr_pos = partial(_find_attr_position, halo_attributes=halo_attributes)\n\n        for data_file in self._sorted_chunk_iterator(chunks):\n            pcount = (\n                data_file.ds.parameters[\"nhalos\"] + data_file.ds.parameters[\"nsubs\"]\n            )\n            if pcount == 0:\n                continue\n            ptype = \"halos\"\n            field_list0 = sorted(ptf[ptype], key=attr_pos)\n            field_list_pos = [f\"raw_position_{k}\" for k in \"xyz\"]\n            field_list = sorted(set(field_list0 + field_list_pos), key=attr_pos)\n\n            with FortranFile(self.ds.parameter_filename) as fpu:\n                params = fpu.read_attrs(self.ds._header_attributes)\n\n                todo = _todo_from_attributes(field_list, self.ds._halo_attributes)\n\n                nhalos = params[\"nhalos\"] + params[\"nsubs\"]\n                data = np.zeros((nhalos, len(field_list)))\n                for ihalo in range(nhalos):\n                    jj = 0\n                    for it in todo:\n                        if isinstance(it, int):\n                            fpu.skip(it)\n                        else:\n                            tmp = fpu.read_attrs(it)\n                            for key in iterate_over_attributes(it):\n                                v = tmp[key]\n                                if key not in field_list:\n                                    continue\n                                data[ihalo, jj] = v\n                                jj += 1\n            ipos = [field_list.index(k) for k in field_list_pos]\n            w = self.ds.domain_width.to(\"code_length\")[0].value / 2\n            x, y, z = (data[:, i] + w for i in ipos)\n            mask = selector.select_points(x, y, z, 0.0)\n            del x, y, z\n\n            if mask is None:\n                continue\n            for field in field_list0:\n                i = field_list.index(field)\n                yield (ptype, field), data[mask, i]\n\n    def _count_particles(self, data_file):\n        nhalos = data_file.ds.parameters[\"nhalos\"] + data_file.ds.parameters[\"nsubs\"]\n        return {\"halos\": nhalos}\n\n    def _identify_fields(self, data_file):\n        fields = []\n\n        for attr, _1, _2 in self.ds._halo_attributes:\n            if isinstance(attr, str):\n                fields.append((\"halos\", attr))\n            else:\n                for a in attr:\n                    fields.append((\"halos\", a))\n        return fields, {}\n\n    # -----------------------------------------------------\n    # Specific to AdaptaHOP\n    def _get_particle_positions(self):\n        \"\"\"Read the particles and return them in code_units\"\"\"\n        data = getattr(self, \"_particle_positions\", None)\n        if data is not None:\n            return data\n\n        with FortranFile(self.ds.parameter_filename) as fpu:\n            params = fpu.read_attrs(self.ds._header_attributes)\n\n            todo = _todo_from_attributes(\n                (\n                    \"particle_identifier\",\n                    \"raw_position_x\",\n                    \"raw_position_y\",\n                    \"raw_position_z\",\n                ),\n                self.ds._halo_attributes,\n            )\n\n            nhalos = params[\"nhalos\"] + params[\"nsubs\"]\n            data = np.zeros((nhalos, 3))\n            offset_map = np.zeros((nhalos, 2), dtype=\"int64\")\n            for ihalo in range(nhalos):\n                ipos = fpu.tell()\n                for it in todo:\n                    if isinstance(it, int):\n                        fpu.skip(it)\n                    elif it[0][0] != \"particle_identifier\":\n                        # Small optimisation here: we can read as vector\n                        # dt = fpu.read_attrs(it)\n                        # data[ihalo, 0] = dt['particle_position_x']\n                        # data[ihalo, 1] = dt['particle_position_y']\n                        # data[ihalo, 2] = dt['particle_position_z']\n                        data[ihalo, :] = fpu.read_vector(it[0][-1])\n                    else:\n                        halo_id = fpu.read_int()\n                        offset_map[ihalo, 0] = halo_id\n                        offset_map[ihalo, 1] = ipos\n        data = self.ds.arr(data, \"code_length\") + self.ds.domain_width / 2\n\n        # Make sure halos are loaded in increasing halo_id order\n        assert np.all(np.diff(offset_map[:, 0]) > 0)\n\n        # Cache particle positions as one do not expect a large number of halos anyway\n        self._particle_positions = data\n        self._offsets = offset_map\n        return data\n\n    def members(self, ihalo):\n        offset = self._offsets[ihalo, 1]\n        todo = _todo_from_attributes((\"particle_identities\",), self.ds._halo_attributes)\n        with FortranFile(self.ds.parameter_filename) as fpu:\n            fpu.seek(offset)\n            if isinstance(todo[0], int):\n                fpu.skip(todo.pop(0))\n            members = fpu.read_attrs(todo.pop(0))[\"particle_identities\"]\n        return members\n\n    def _sorted_chunk_iterator(self, chunks):\n        data_files = self._get_data_files(chunks)\n        yield from sorted(data_files, key=attrgetter(\"filename\"))\n\n\ndef _todo_from_attributes(attributes: ATTR_T, halo_attributes: ATTR_T):\n    # Helper function to generate a list of read-skip instructions given a list of\n    # attributes. This is used to skip fields most of the fields when reading\n    # the tree_brick files.\n    iskip = 0\n    todo: list[int | list[tuple[tuple[str, ...] | str, int, str]]] = []\n\n    attributes = tuple(set(attributes))\n\n    for i, (attrs, l, k) in enumerate(halo_attributes):\n        attrs_list: tuple[str, ...]\n        if isinstance(attrs, tuple):\n            if not all(isinstance(a, str) for a in attrs):\n                raise TypeError(f\"Expected a single str or a tuple of str, got {attrs}\")\n            attrs_list = attrs\n        else:\n            attrs_list = (attrs,)\n        ok = False\n        for attr in attrs_list:\n            if attr in attributes:\n                ok = True\n                break\n\n        if i == 0:\n            if ok:\n                state = \"read\"\n                todo.append([])\n            else:\n                state = \"skip\"\n\n        if ok:\n            if state == \"skip\":\n                # Switched from skip to read, store skip information and start\n                # new read list\n                todo.append(iskip)\n                todo.append([])\n                iskip = 0\n            if not isinstance(todo[-1], list):\n                raise TypeError\n            todo[-1].append((attrs, l, k))\n            state = \"read\"\n        else:\n            iskip += 1\n            state = \"skip\"\n\n    if state == \"skip\" and iskip > 0:\n        todo.append(iskip)\n\n    return todo\n\n\ndef _find_attr_position(key, halo_attributes: ATTR_T) -> int:\n    j = 0\n    for attrs, *_ in halo_attributes:\n        if not isinstance(attrs, tuple):\n            attrs = (attrs,)\n        for a in attrs:\n            if key == a:\n                return j\n            j += 1\n    raise KeyError\n"
  },
  {
    "path": "yt/frontends/adaptahop/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/adaptahop/tests/test_outputs.py",
    "content": "\"\"\"\nAdaptaHOP frontend tests\n\n\n\n\"\"\"\n\nimport numpy as np\nimport pytest\n\nfrom yt.frontends.adaptahop.data_structures import AdaptaHOPDataset\nfrom yt.testing import requires_file\nfrom yt.utilities.answer_testing.framework import data_dir_load\n\nr0 = \"output_00080/info_00080.txt\"\nbrick_old = \"output_00080_halos/tree_bricks080\"\nbrick_new = \"output_00080_new_halos/tree_bricks080\"\n\n\n@requires_file(r0)\n@requires_file(brick_old)\n@requires_file(brick_new)\n@pytest.mark.parametrize(\"brick\", [brick_old, brick_new])\ndef test_opening(brick):\n    ds_parent = data_dir_load(r0)\n    ds = data_dir_load(brick, kwargs={\"parent_ds\": ds_parent})\n\n    ds.index\n\n    assert isinstance(ds, AdaptaHOPDataset)\n\n\n@requires_file(r0)\n@requires_file(brick_old)\n@requires_file(brick_new)\n@pytest.mark.parametrize(\"brick\", [brick_old, brick_new])\ndef test_field_access(brick):\n    ds_parent = data_dir_load(r0)\n    ds = data_dir_load(brick, kwargs={\"parent_ds\": ds_parent})\n\n    skip_list = (\"particle_identities\", \"mesh_id\", \"radius_profile\", \"rho_profile\")\n    fields = [\n        (ptype, field)\n        for (ptype, field) in ds.derived_field_list\n        if (ptype == \"halos\") and (field not in skip_list)\n    ]\n\n    ad = ds.all_data()\n\n    for ptype, field in fields:\n        ad[ptype, field]\n\n\n@requires_file(r0)\n@requires_file(brick_old)\n@requires_file(brick_new)\n@pytest.mark.parametrize(\"brick\", [brick_old, brick_new])\ndef test_get_halo(brick):\n    ds_parent = data_dir_load(r0)\n    ds = data_dir_load(brick, kwargs={\"parent_ds\": ds_parent})\n\n    halo = ds.halo(1, ptype=\"io\")\n\n    # Check halo object has position, velocity, mass and members attributes\n    for attr_name in (\"mass\", \"position\", \"velocity\", \"member_ids\"):\n        getattr(halo, attr_name)\n\n    members = np.sort(halo.member_ids)\n\n    # Check sphere contains all the members\n    id_sphere = halo.sphere[\"io\", \"particle_identity\"].astype(\"int64\")\n    assert len(np.lib.arraysetops.setdiff1d(members, id_sphere)) == 0\n\n    # Check region contains *only* halo particles\n    id_reg = np.sort(halo[\"io\", \"particle_identity\"].astype(\"int64\"))\n\n    np.testing.assert_equal(members, id_reg)\n"
  },
  {
    "path": "yt/frontends/ahf/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/ahf/api.py",
    "content": "from .data_structures import AHFHalosDataset\nfrom .fields import AHFHalosFieldInfo\nfrom .io import IOHandlerAHFHalos\n"
  },
  {
    "path": "yt/frontends/ahf/data_structures.py",
    "content": "import glob\nimport os\n\nimport numpy as np\n\nfrom yt.data_objects.static_output import Dataset\nfrom yt.frontends.halo_catalog.data_structures import HaloCatalogFile\nfrom yt.funcs import setdefaultattr\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.utilities.cosmology import Cosmology\n\nfrom .fields import AHFHalosFieldInfo\n\n\nclass AHFHalosFile(HaloCatalogFile):\n    def __init__(self, ds, io, filename, file_id, range=None):\n        root, _ = os.path.splitext(filename)\n        candidates = glob.glob(root + \"*.AHF_halos\")\n        if len(candidates) == 1:\n            filename = candidates[0]\n        else:\n            raise ValueError(\"Too many AHF_halos files.\")\n        self.col_names = self._read_column_names(filename)\n        super().__init__(ds, io, filename, file_id, range)\n\n    def read_data(self, usecols=None):\n        return np.genfromtxt(self.filename, names=self.col_names, usecols=usecols)\n\n    def _read_column_names(self, filename):\n        with open(filename) as f:\n            line = f.readline()\n            # Remove leading '#'\n            line = line[1:]\n            names = line.split()\n            # Remove trailing '()'\n            names = [name.split(\"(\")[0] for name in names]\n            return names\n\n    def _read_particle_positions(self, ptype, f=None):\n        \"\"\"\n        Read all particle positions in this file.\n        \"\"\"\n\n        halos = self.read_data(usecols=[\"Xc\", \"Yc\", \"Zc\"])\n        pos = np.empty((halos.size, 3), dtype=\"float64\")\n        for i, ax in enumerate(\"XYZ\"):\n            pos[:, i] = halos[f\"{ax}c\"].astype(\"float64\")\n\n        return pos\n\n\nclass AHFHalosDataset(Dataset):\n    _index_class = ParticleIndex\n    _file_class = AHFHalosFile\n    _field_info_class = AHFHalosFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"ahf\",\n        n_ref=16,\n        num_zones=2,\n        units_override=None,\n        unit_system=\"cgs\",\n        hubble_constant=1.0,\n    ):\n        root, _ = os.path.splitext(filename)\n        self.log_filename = root + \".log\"\n        self.hubble_constant = hubble_constant\n\n        self.n_ref = n_ref\n        self.num_zones = num_zones\n        super().__init__(\n            filename,\n            dataset_type=dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n\n    def _set_code_unit_attributes(self):\n        setdefaultattr(self, \"length_unit\", self.quan(1.0, \"kpccm/h\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"Msun/h\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n        setdefaultattr(self, \"velocity_unit\", self.quan(1.0, \"km/s\"))\n\n    def _parse_parameter_file(self):\n        # Read all parameters.\n        simu = self._read_log_simu()\n        param = self._read_parameter()\n\n        # Set up general information.\n        self.filename_template = self.parameter_filename\n        self.file_count = 1\n        self.parameters.update(param)\n        self.particle_types = \"halos\"\n        self.particle_types_raw = \"halos\"\n\n        # Set up geometrical information.\n        self.refine_by = 2\n        self.dimensionality = 3\n        nz = self.num_zones\n        self.domain_dimensions = np.ones(self.dimensionality, \"int32\") * nz\n        self.domain_left_edge = np.array([0.0, 0.0, 0.0])\n        # Note that boxsize is in Mpc but particle positions are in kpc.\n        self.domain_right_edge = np.array([simu[\"boxsize\"]] * 3) * 1000\n        self._periodicity = (True, True, True)\n\n        # Set up cosmological information.\n        self.cosmological_simulation = 1\n        self.current_redshift = param[\"z\"]\n        self.omega_lambda = simu[\"lambda0\"]\n        self.omega_matter = simu[\"omega0\"]\n        cosmo = Cosmology(\n            hubble_constant=self.hubble_constant,\n            omega_matter=self.omega_matter,\n            omega_lambda=self.omega_lambda,\n        )\n        self.current_time = cosmo.lookback_time(param[\"z\"], 1e6).in_units(\"s\")\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".parameter\"):\n            return False\n        with open(filename) as f:\n            return f.readlines()[11].startswith(\"AHF\")\n\n    # Helper methods\n\n    def _read_log_simu(self):\n        simu = {}\n        with open(self.log_filename) as f:\n            for l in f:\n                if l.startswith(\"simu.\"):\n                    name, val = l.split(\":\")\n                    key = name.strip().split(\".\")[1]\n                    try:\n                        val = float(val)\n                    except Exception:\n                        val = float.fromhex(val)\n                    simu[key] = val\n        return simu\n\n    def _read_parameter(self):\n        param = {}\n        with open(self.parameter_filename) as f:\n            for l in f:\n                words = l.split()\n                if len(words) == 2:\n                    key, val = words\n                    try:\n                        val = float(val)\n                        param[key] = val\n                    except Exception:\n                        pass\n        return param\n\n    @property\n    def _skip_cache(self):\n        return True\n"
  },
  {
    "path": "yt/frontends/ahf/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\nm_units = \"Msun/h\"\np_units = \"kpccm/h\"\nr_units = \"kpccm/h\"\nv_units = \"km/s\"\n\n\nclass AHFHalosFieldInfo(FieldInfoContainer):\n    # See http://popia.ft.uam.es/AHF/files/AHF.pdf\n    # and search for '*.AHF_halos'.\n    known_particle_fields: KnownFieldsT = (\n        (\"ID\", (\"\", [\"particle_identifier\"], None)),\n        (\"hostHalo\", (\"\", [], None)),\n        (\"numSubStruct\", (\"\", [], None)),\n        (\"Mvir\", (m_units, [\"particle_mass\"], \"Virial Mass\")),\n        (\"npart\", (\"\", [], None)),\n        (\"Xc\", (p_units, [\"particle_position_x\"], None)),\n        (\"Yc\", (p_units, [\"particle_position_y\"], None)),\n        (\"Zc\", (p_units, [\"particle_position_z\"], None)),\n        (\"VXc\", (v_units, [\"particle_velocity_x\"], None)),\n        (\"VYc\", (v_units, [\"particle_velocity_y\"], None)),\n        (\"VZc\", (v_units, [\"particle_velocity_z\"], None)),\n        (\"Rvir\", (r_units, [\"virial_radius\"], \"Virial Radius\")),\n        (\"Rmax\", (r_units, [], None)),\n        (\"r2\", (r_units, [], None)),\n        (\"mbp_offset\", (r_units, [], None)),\n        (\"com_offset\", (r_units, [], None)),\n        (\"Vmax\", (v_units, [], None)),\n        (\"v_sec\", (v_units, [], None)),\n        (\"sigV\", (v_units, [], None)),\n        (\"lambda\", (\"\", [], None)),\n        (\"lambdaE\", (\"\", [], None)),\n        (\"Lx\", (\"\", [], None)),\n        (\"Ly\", (\"\", [], None)),\n        (\"Lz\", (\"\", [], None)),\n        (\"b\", (\"\", [], None)),\n        (\"c\", (\"\", [], None)),\n        (\"Eax\", (\"\", [], None)),\n        (\"Eay\", (\"\", [], None)),\n        (\"Eaz\", (\"\", [], None)),\n        (\"Ebx\", (\"\", [], None)),\n        (\"Eby\", (\"\", [], None)),\n        (\"Ebz\", (\"\", [], None)),\n        (\"Ecx\", (\"\", [], None)),\n        (\"Ecy\", (\"\", [], None)),\n        (\"Ecz\", (\"\", [], None)),\n        (\"ovdens\", (\"\", [], None)),\n        (\"nbins\", (\"\", [], None)),\n        (\"fMhires\", (\"\", [], None)),\n        (\"Ekin\", (\"Msun/h*(km/s)**2\", [], None)),\n        (\"Epot\", (\"Msun/h*(km/s)**2\", [], None)),\n        (\"SurfP\", (\"Msun/h*(km/s)**2\", [], None)),\n        (\"Phi0\", (\"(km/s)**2\", [], None)),\n        (\"cNFW\", (\"\", [], None)),\n    )\n"
  },
  {
    "path": "yt/frontends/ahf/io.py",
    "content": "from operator import attrgetter\n\nimport numpy as np\n\nfrom yt.utilities.io_handler import BaseParticleIOHandler\n\n\nclass IOHandlerAHFHalos(BaseParticleIOHandler):\n    _particle_reader = False\n    _dataset_type = \"ahf\"\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This needs to *yield* a series of tuples of (ptype, (x, y, z), hsml).\n        # chunks is a list of chunks, and ptf is a dict where the keys are\n        # ptypes and the values are lists of fields.\n\n        # Only support halo reading for now.\n        assert len(ptf) == 1\n        assert list(ptf.keys())[0] == \"halos\"\n        for data_file in self._sorted_chunk_iterator(chunks):\n            pos = data_file._get_particle_positions(\"halos\")\n            x, y, z = (pos[:, i] for i in range(3))\n            yield \"halos\", (x, y, z), 0.0\n\n    def _yield_coordinates(self, data_file):\n        halos = data_file.read_data(usecols=[\"Xc\", \"Yc\", \"Zc\"])\n        x = halos[\"Xc\"].astype(\"float64\")\n        y = halos[\"Yc\"].astype(\"float64\")\n        z = halos[\"Zc\"].astype(\"float64\")\n        yield \"halos\", np.asarray((x, y, z)).T\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # This gets called after the arrays have been allocated.  It needs to\n        # yield ((ptype, field), data) where data is the masked results of\n        # reading ptype, field and applying the selector to the data read in.\n        # Selector objects have a .select_points(x,y,z) that returns a mask, so\n        # you need to do your masking here.\n        # Only support halo reading for now.\n        assert len(ptf) == 1\n        assert list(ptf.keys())[0] == \"halos\"\n        for data_file in self._sorted_chunk_iterator(chunks):\n            si, ei = data_file.start, data_file.end\n            cols = []\n            for field_list in ptf.values():\n                cols.extend(field_list)\n            cols = list(set(cols))\n            halos = data_file.read_data(usecols=cols)\n            pos = data_file._get_particle_positions(\"halos\")\n            x, y, z = (pos[:, i] for i in range(3))\n            yield \"halos\", (x, y, z)\n            mask = selector.select_points(x, y, z, 0.0)\n            del x, y, z\n            if mask is None:\n                continue\n            for ptype, field_list in sorted(ptf.items()):\n                for field in field_list:\n                    data = halos[field][si:ei][mask].astype(\"float64\")\n                    yield (ptype, field), data\n\n    def _count_particles(self, data_file):\n        halos = data_file.read_data(usecols=[\"ID\"])\n        nhalos = len(halos[\"ID\"])\n        si, ei = data_file.start, data_file.end\n        if None not in (si, ei):\n            nhalos = np.clip(nhalos - si, 0, ei - si)\n        return {\"halos\": nhalos}\n\n    def _identify_fields(self, data_file):\n        fields = [(\"halos\", f) for f in data_file.col_names]\n        return fields, {}\n\n    def _sorted_chunk_iterator(self, chunks):\n        # yield from sorted list of data_files\n        data_files = self._get_data_files(chunks)\n        yield from sorted(data_files, key=attrgetter(\"filename\"))\n"
  },
  {
    "path": "yt/frontends/ahf/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/ahf/tests/test_outputs.py",
    "content": "import os.path\n\nfrom numpy.testing import assert_equal\n\nfrom yt.frontends.ahf.api import AHFHalosDataset\nfrom yt.testing import ParticleSelectionComparison, requires_file\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    data_dir_load,\n    requires_ds,\n)\n\n_fields = (\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_position_y\"),\n    (\"all\", \"particle_position_z\"),\n    (\"all\", \"particle_mass\"),\n)\n\nahf_halos = \"ahf_halos/snap_N64L16_135.parameter\"\n\n\ndef load(filename):\n    return data_dir_load(filename, kwargs={\"hubble_constant\": 0.7})\n\n\n@requires_ds(ahf_halos)\ndef test_fields_ahf_halos():\n    ds = load(ahf_halos)\n    assert_equal(str(ds), os.path.basename(ahf_halos))\n    for field in _fields:\n        yield FieldValuesTest(ds, field, particle_type=True)\n\n\n@requires_file(ahf_halos)\ndef test_AHFHalosDataset():\n    ds = load(ahf_halos)\n    assert isinstance(ds, AHFHalosDataset)\n    ad = ds.all_data()\n    ad[\"all\", \"particle_mass\"]\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n"
  },
  {
    "path": "yt/frontends/amrex/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/amrex/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    AMReXDataset,\n    AMReXHierarchy,\n    BoxlibDataset,\n    BoxlibGrid,\n    BoxlibHierarchy,\n    CastroDataset,\n    MaestroDataset,\n    NyxDataset,\n    NyxHierarchy,\n    OrionDataset,\n    OrionHierarchy,\n    WarpXDataset,\n    WarpXHierarchy,\n)\nfrom .fields import (\n    BoxlibFieldInfo,\n    CastroFieldInfo,\n    MaestroFieldInfo,\n    NyxFieldInfo,\n    WarpXFieldInfo,\n)\nfrom .io import IOHandlerBoxlib\n"
  },
  {
    "path": "yt/frontends/amrex/data_structures.py",
    "content": "import glob\nimport os\nimport re\nfrom collections import namedtuple\nfrom functools import cached_property\nfrom stat import ST_CTIME\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.io_handler import io_registry\nfrom yt.utilities.lib.misc_utilities import get_box_grids_level\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_root_only\n\nfrom .fields import (\n    BoxlibFieldInfo,\n    CastroFieldInfo,\n    MaestroFieldInfo,\n    NyxFieldInfo,\n    WarpXFieldInfo,\n)\n\n# This is what we use to find scientific notation that might include d's\n# instead of e's.\n_scinot_finder = re.compile(r\"[-+]?[0-9]*\\.?[0-9]+([eEdD][-+]?[0-9]+)?\")\n# This is the dimensions in the Cell_H file for each level\n# It is different for different dimensionalities, so we make a list\n_1dregx = r\"-?\\d+\"\n_2dregx = r\"-?\\d+,-?\\d+\"\n_3dregx = r\"-?\\d+,-?\\d+,-?\\d+\"\n_dim_finder = [\n    re.compile(rf\"\\(\\(({ndregx})\\) \\(({ndregx})\\) \\({ndregx}\\)\\)$\")\n    for ndregx in (_1dregx, _2dregx, _3dregx)\n]\n# This is the line that prefixes each set of data for a FAB in the FAB file\n# It is different for different dimensionalities, so we make a list\n_endian_regex = r\"^FAB\\ \\(\\(\\d+,\\ \\([\\d\\ ]+\\)\\),\\((\\d+),\\ \\(([\\d\\ ]+)\\)\\)\\)\"\n_header_pattern = [\n    re.compile(\n        rf\"\"\"{_endian_regex}            # match `endianness`\n        \\(\n              \\(( {ndregx} )\\)          # match `start`\n            \\ \\(( {ndregx} )\\)          # match `end`\n            \\ \\(( {ndregx} )\\)          # match `centering`\n        \\)\n        \\ (-?\\d+)                       # match `nc`\n        $ # end of line\n        \"\"\",\n        re.VERBOSE,\n    )\n    for ndregx in (_1dregx, _2dregx, _3dregx)\n]\n\n\nclass BoxlibGrid(AMRGridPatch):\n    _id_offset = 0\n    _offset = -1\n\n    def __init__(self, grid_id, offset, filename=None, index=None):\n        super().__init__(grid_id, filename, index)\n        self._base_offset = offset\n        self._parent_id = []\n        self._children_ids = []\n        self._pdata = {}\n\n    def _prepare_grid(self):\n        super()._prepare_grid()\n        my_ind = self.id - self._id_offset\n        self.start_index = self.index.grid_start_index[my_ind]\n\n    def get_global_startindex(self):\n        return self.start_index\n\n    def _setup_dx(self):\n        # has already been read in and stored in index\n        self.dds = self.index.ds.arr(self.index.level_dds[self.Level, :], \"code_length\")\n        self.field_data[\"dx\"], self.field_data[\"dy\"], self.field_data[\"dz\"] = self.dds\n\n    @property\n    def Parent(self):\n        if len(self._parent_id) == 0:\n            return None\n        return [self.index.grids[pid - self._id_offset] for pid in self._parent_id]\n\n    @property\n    def Children(self):\n        return [self.index.grids[cid - self._id_offset] for cid in self._children_ids]\n\n    def _get_offset(self, f):\n        # This will either seek to the _offset or figure out the correct\n        # _offset.\n        if self._offset == -1:\n            f.seek(self._base_offset, os.SEEK_SET)\n            f.readline()\n            self._offset = f.tell()\n        return self._offset\n\n    # We override here because we can have varying refinement levels\n    def select_ires(self, dobj):\n        mask = self._get_selector_mask(dobj.selector)\n        if mask is None:\n            return np.empty(0, dtype=\"int64\")\n        coords = np.empty(self._last_count, dtype=\"int64\")\n        coords[:] = self.Level + self.ds.level_offsets[self.Level]\n        return coords\n\n    # Override this as well, since refine_by can vary\n    def _fill_child_mask(self, child, mask, tofill, dlevel=1):\n        rf = self.ds.ref_factors[self.Level]\n        if dlevel != 1:\n            raise NotImplementedError\n        gi, cgi = self.get_global_startindex(), child.get_global_startindex()\n        startIndex = np.maximum(0, cgi // rf - gi)\n        endIndex = np.minimum(\n            (cgi + child.ActiveDimensions) // rf - gi, self.ActiveDimensions\n        )\n        endIndex += startIndex == endIndex\n        mask[\n            startIndex[0] : endIndex[0],\n            startIndex[1] : endIndex[1],\n            startIndex[2] : endIndex[2],\n        ] = tofill\n\n\nclass BoxLibParticleHeader:\n    def __init__(self, ds, directory_name, is_checkpoint, extra_field_names=None):\n        self.particle_type = directory_name\n        header_filename = os.path.join(ds.output_dir, directory_name, \"Header\")\n        with open(header_filename) as f:\n            self.version_string = f.readline().strip()\n\n            particle_real_type = self.version_string.split(\"_\")[-1]\n            known_real_types = {\"double\": np.float64, \"single\": np.float32}\n            try:\n                self.real_type = known_real_types[particle_real_type]\n            except KeyError:\n                mylog.warning(\n                    \"yt did not recognize particle real type '%s'. Assuming 'double'.\",\n                    particle_real_type,\n                )\n                self.real_type = known_real_types[\"double\"]\n\n            self.int_type = np.int32\n\n            self.dim = int(f.readline().strip())\n            self.num_int_base = 2 + self.dim\n            self.num_real_base = self.dim\n            self.num_int_extra = 0  # this should be written by Boxlib, but isn't\n            self.num_real_extra = int(f.readline().strip())\n            self.num_int = self.num_int_base + self.num_int_extra\n            self.num_real = self.num_real_base + self.num_real_extra\n            self.num_particles = int(f.readline().strip())\n            self.max_next_id = int(f.readline().strip())\n            self.finest_level = int(f.readline().strip())\n            self.num_levels = self.finest_level + 1\n\n            # Boxlib particles can be written in checkpoint or plotfile mode\n            # The base integer fields are only there for checkpoints, but some\n            # codes use the checkpoint format for plotting\n            if not is_checkpoint:\n                self.num_int_base = 0\n                self.num_int_extra = 0\n                self.num_int = 0\n\n            self.grids_per_level = np.zeros(self.num_levels, dtype=\"int64\")\n            self.data_map = {}\n            for level_num in range(self.num_levels):\n                self.grids_per_level[level_num] = int(f.readline().strip())\n                self.data_map[level_num] = {}\n\n            pfd = namedtuple(\n                \"ParticleFileDescriptor\", [\"file_number\", \"num_particles\", \"offset\"]\n            )\n\n            for level_num in range(self.num_levels):\n                for grid_num in range(self.grids_per_level[level_num]):\n                    entry = [int(val) for val in f.readline().strip().split()]\n                    self.data_map[level_num][grid_num] = pfd(*entry)\n\n        self._generate_particle_fields(extra_field_names)\n\n    def _generate_particle_fields(self, extra_field_names):\n        # these are the 'base' integer fields\n        self.known_int_fields = [\n            (self.particle_type, \"particle_id\"),\n            (self.particle_type, \"particle_cpu\"),\n            (self.particle_type, \"particle_cell_x\"),\n            (self.particle_type, \"particle_cell_y\"),\n            (self.particle_type, \"particle_cell_z\"),\n        ]\n        self.known_int_fields = self.known_int_fields[0 : self.num_int_base]\n\n        # these are extra integer fields\n        extra_int_fields = [f\"particle_int_comp{i}\" for i in range(self.num_int_extra)]\n        self.known_int_fields.extend(\n            [(self.particle_type, field) for field in extra_int_fields]\n        )\n\n        # these are the base real fields\n        self.known_real_fields = [\n            (self.particle_type, \"particle_position_x\"),\n            (self.particle_type, \"particle_position_y\"),\n            (self.particle_type, \"particle_position_z\"),\n        ]\n        self.known_real_fields = self.known_real_fields[0 : self.num_real_base]\n\n        # these are the extras\n        if extra_field_names is not None:\n            assert len(extra_field_names) == self.num_real_extra\n        else:\n            extra_field_names = [\n                f\"particle_real_comp{i}\" for i in range(self.num_real_extra)\n            ]\n\n        self.known_real_fields.extend(\n            [(self.particle_type, field) for field in extra_field_names]\n        )\n\n        self.known_fields = self.known_int_fields + self.known_real_fields\n\n        self.particle_int_dtype = np.dtype(\n            [(t[1], self.int_type) for t in self.known_int_fields]\n        )\n\n        self.particle_real_dtype = np.dtype(\n            [(t[1], self.real_type) for t in self.known_real_fields]\n        )\n\n\nclass AMReXParticleHeader:\n    def __init__(self, ds, directory_name, is_checkpoint, extra_field_names=None):\n        self.particle_type = directory_name\n        header_filename = os.path.join(ds.output_dir, directory_name, \"Header\")\n        self.real_component_names = []\n        self.int_component_names = []\n        with open(header_filename) as f:\n            self.version_string = f.readline().strip()\n\n            particle_real_type = self.version_string.split(\"_\")[-1]\n\n            if particle_real_type == \"double\":\n                self.real_type = np.float64\n            elif particle_real_type == \"single\":\n                self.real_type = np.float32\n            else:\n                raise RuntimeError(\"yt did not recognize particle real type.\")\n            self.int_type = np.int32\n\n            self.dim = int(f.readline().strip())\n            self.num_int_base = 2\n            self.num_real_base = self.dim\n            self.num_real_extra = int(f.readline().strip())\n            for _ in range(self.num_real_extra):\n                self.real_component_names.append(f.readline().strip())\n            self.num_int_extra = int(f.readline().strip())\n            for _ in range(self.num_int_extra):\n                self.int_component_names.append(f.readline().strip())\n            self.num_int = self.num_int_base + self.num_int_extra\n            self.num_real = self.num_real_base + self.num_real_extra\n            self.is_checkpoint = bool(int(f.readline().strip()))\n            self.num_particles = int(f.readline().strip())\n            self.max_next_id = int(f.readline().strip())\n            self.finest_level = int(f.readline().strip())\n            self.num_levels = self.finest_level + 1\n\n            if not self.is_checkpoint:\n                self.num_int_base = 0\n                self.num_int_extra = 0\n                self.num_int = 0\n\n            self.grids_per_level = np.zeros(self.num_levels, dtype=\"int64\")\n            self.data_map = {}\n            for level_num in range(self.num_levels):\n                self.grids_per_level[level_num] = int(f.readline().strip())\n                self.data_map[level_num] = {}\n\n            pfd = namedtuple(\n                \"ParticleFileDescriptor\", [\"file_number\", \"num_particles\", \"offset\"]\n            )\n\n            for level_num in range(self.num_levels):\n                for grid_num in range(self.grids_per_level[level_num]):\n                    entry = [int(val) for val in f.readline().strip().split()]\n                    self.data_map[level_num][grid_num] = pfd(*entry)\n\n        self._generate_particle_fields()\n\n    def _generate_particle_fields(self):\n        # these are the 'base' integer fields\n        self.known_int_fields = [\n            (self.particle_type, \"particle_id\"),\n            (self.particle_type, \"particle_cpu\"),\n        ]\n        self.known_int_fields = self.known_int_fields[0 : self.num_int_base]\n\n        self.known_int_fields.extend(\n            [\n                (self.particle_type, \"particle_\" + field)\n                for field in self.int_component_names\n            ]\n        )\n\n        # these are the base real fields\n        self.known_real_fields = [\n            (self.particle_type, \"particle_position_x\"),\n            (self.particle_type, \"particle_position_y\"),\n            (self.particle_type, \"particle_position_z\"),\n        ]\n        self.known_real_fields = self.known_real_fields[0 : self.num_real_base]\n\n        self.known_real_fields.extend(\n            [\n                (self.particle_type, \"particle_\" + field)\n                for field in self.real_component_names\n            ]\n        )\n\n        self.known_fields = self.known_int_fields + self.known_real_fields\n\n        self.particle_int_dtype = np.dtype(\n            [(t[1], self.int_type) for t in self.known_int_fields]\n        )\n\n        self.particle_real_dtype = np.dtype(\n            [(t[1], self.real_type) for t in self.known_real_fields]\n        )\n\n\nclass BoxlibHierarchy(GridIndex):\n    grid = BoxlibGrid\n\n    def __init__(self, ds, dataset_type=\"boxlib_native\"):\n        self.dataset_type = dataset_type\n        self.header_filename = os.path.join(ds.output_dir, \"Header\")\n        self.directory = ds.output_dir\n        self.particle_headers = {}\n\n        GridIndex.__init__(self, ds, dataset_type)\n        self._cache_endianness(self.grids[-1])\n\n    def _parse_index(self):\n        \"\"\"\n        read the global header file for an Boxlib plotfile output.\n        \"\"\"\n        self.max_level = self.dataset._max_level\n        header_file = open(self.header_filename)\n\n        self.dimensionality = self.dataset.dimensionality\n        _our_dim_finder = _dim_finder[self.dimensionality - 1]\n        DRE = self.dataset.domain_right_edge  # shortcut\n        DLE = self.dataset.domain_left_edge  # shortcut\n\n        # We can now skip to the point in the file we want to start parsing.\n        header_file.seek(self.dataset._header_mesh_start)\n\n        dx = []\n        for i in range(self.max_level + 1):\n            dx.append([float(v) for v in next(header_file).split()])\n            # account for non-3d data sets\n            if self.dimensionality < 2:\n                dx[i].append(DRE[1] - DLE[1])\n            if self.dimensionality < 3:\n                dx[i].append(DRE[2] - DLE[2])\n        self.level_dds = np.array(dx, dtype=\"float64\")\n        next(header_file)\n        match self.ds.geometry:\n            case Geometry.CARTESIAN:\n                default_ybounds = (0.0, 1.0)\n                default_zbounds = (0.0, 1.0)\n            case Geometry.CYLINDRICAL:\n                default_ybounds = (0.0, 1.0)\n                default_zbounds = (0.0, 2 * np.pi)\n            case Geometry.SPHERICAL:\n                default_ybounds = (0.0, np.pi)\n                default_zbounds = (0.0, 2 * np.pi)\n            case _:\n                header_file.close()\n                raise RuntimeError(\"Unknown BoxLib coordinate system.\")\n        if int(next(header_file)) != 0:\n            header_file.close()\n            raise RuntimeError(\"INTERNAL ERROR! This should be a zero.\")\n\n        # each level is one group with ngrids on it.\n        # each grid has self.dimensionality number of lines of 2 reals\n        self.grids = []\n        grid_counter = 0\n        for level in range(self.max_level + 1):\n            vals = next(header_file).split()\n            lev, ngrids = int(vals[0]), int(vals[1])\n            assert lev == level\n            nsteps = int(next(header_file))  # NOQA\n            for gi in range(ngrids):\n                xlo, xhi = (float(v) for v in next(header_file).split())\n                if self.dimensionality > 1:\n                    ylo, yhi = (float(v) for v in next(header_file).split())\n                else:\n                    ylo, yhi = default_ybounds\n                if self.dimensionality > 2:\n                    zlo, zhi = (float(v) for v in next(header_file).split())\n                else:\n                    zlo, zhi = default_zbounds\n                self.grid_left_edge[grid_counter + gi, :] = [xlo, ylo, zlo]\n                self.grid_right_edge[grid_counter + gi, :] = [xhi, yhi, zhi]\n            # Now we get to the level header filename, which we open and parse.\n            fn = os.path.join(self.dataset.output_dir, next(header_file).strip())\n            level_header_file = open(fn + \"_H\")\n            level_dir = os.path.dirname(fn)\n            # We skip the first two lines, which contain BoxLib header file\n            # version and 'how' the data was written\n            next(level_header_file)\n            next(level_header_file)\n            # Now we get the number of components\n            ncomp_this_file = int(next(level_header_file))  # NOQA\n            # Skip the next line, which contains the number of ghost zones\n            next(level_header_file)\n            # To decipher this next line, we expect something like:\n            # (8 0\n            # where the first is the number of FABs in this level.\n            ngrids = int(next(level_header_file).split()[0][1:])\n            # Now we can iterate over each and get the indices.\n            for gi in range(ngrids):\n                # components within it\n                start, stop = _our_dim_finder.match(next(level_header_file)).groups()\n                # fix for non-3d data\n                # note we append '0' to both ends b/c of the '+1' in dims below\n                start += \",0\" * (3 - self.dimensionality)\n                stop += \",0\" * (3 - self.dimensionality)\n                start = np.array(start.split(\",\"), dtype=\"int64\")\n                stop = np.array(stop.split(\",\"), dtype=\"int64\")\n                dims = stop - start + 1\n                self.grid_dimensions[grid_counter + gi, :] = dims\n                self.grid_start_index[grid_counter + gi, :] = start\n            # Now we read two more lines.  The first of these is a close\n            # parenthesis.\n            next(level_header_file)\n            # The next is again the number of grids\n            next(level_header_file)\n            # Now we iterate over grids to find their offsets in each file.\n            for gi in range(ngrids):\n                # Now we get the data file, at which point we're ready to\n                # create the grid.\n                dummy, filename, offset = next(level_header_file).split()\n                filename = os.path.join(level_dir, filename)\n                go = self.grid(grid_counter + gi, int(offset), filename, self)\n                go.Level = self.grid_levels[grid_counter + gi, :] = level\n                self.grids.append(go)\n            level_header_file.close()\n            grid_counter += ngrids\n            # already read the filenames above...\n        self.float_type = \"float64\"\n        header_file.close()\n\n    def _cache_endianness(self, test_grid):\n        \"\"\"\n        Cache the endianness and bytes perreal of the grids by using a\n        test grid and assuming that all grids have the same\n        endianness. This is a pretty safe assumption since Boxlib uses\n        one file per processor, and if you're running on a cluster\n        with different endian processors, then you're on your own!\n        \"\"\"\n        # open the test file & grab the header\n        with open(os.path.expanduser(test_grid.filename), \"rb\") as f:\n            header = f.readline().decode(\"ascii\", \"ignore\")\n\n        bpr, endian, start, stop, centering, nc = (\n            _header_pattern[self.dimensionality - 1].search(header).groups()\n        )\n        # Note that previously we were using a different value for BPR than we\n        # use now.  Here is an example set of information directly from BoxLib\n        \"\"\"\n        * DOUBLE data\n        * FAB ((8, (64 11 52 0 1 12 0 1023)),(8, (1 2 3 4 5 6 7 8)))((0,0) (63,63) (0,0)) 27  # NOQA: E501\n        * FLOAT data\n        * FAB ((8, (32 8 23 0 1 9 0 127)),(4, (1 2 3 4)))((0,0) (63,63) (0,0)) 27\n        \"\"\"\n        if bpr == endian[0]:\n            dtype = f\"<f{bpr}\"\n        elif bpr == endian[-1]:\n            dtype = f\">f{bpr}\"\n        else:\n            raise ValueError(\n                \"FAB header is neither big nor little endian. \"\n                \"Perhaps the file is corrupt?\"\n            )\n\n        mylog.debug(\"FAB header suggests dtype of %s\", dtype)\n        self._dtype = np.dtype(dtype)\n\n    def _populate_grid_objects(self):\n        mylog.debug(\"Creating grid objects\")\n        self.grids = np.array(self.grids, dtype=\"object\")\n        self._reconstruct_parent_child()\n        for i, grid in enumerate(self.grids):\n            if (i % 1e4) == 0:\n                mylog.debug(\"Prepared % 7i / % 7i grids\", i, self.num_grids)\n            grid._prepare_grid()\n            grid._setup_dx()\n        mylog.debug(\"Done creating grid objects\")\n\n    def _reconstruct_parent_child(self):\n        if self.max_level == 0:\n            return\n        mask = np.empty(len(self.grids), dtype=\"int32\")\n        mylog.debug(\"First pass; identifying child grids\")\n        for i, grid in enumerate(self.grids):\n            get_box_grids_level(\n                self.grid_left_edge[i, :],\n                self.grid_right_edge[i, :],\n                self.grid_levels[i].item() + 1,\n                self.grid_left_edge,\n                self.grid_right_edge,\n                self.grid_levels,\n                mask,\n            )\n            ids = np.where(mask.astype(\"bool\"))  # where is a tuple\n            grid._children_ids = ids[0] + grid._id_offset\n        mylog.debug(\"Second pass; identifying parents\")\n        for i, grid in enumerate(self.grids):  # Second pass\n            for child in grid.Children:\n                child._parent_id.append(i + grid._id_offset)\n\n    def _count_grids(self):\n        # We can get everything from the Header file, but note that we're\n        # duplicating some work done elsewhere.  In a future where we don't\n        # pre-allocate grid arrays, this becomes unnecessary.\n        header_file = open(self.header_filename)\n        header_file.seek(self.dataset._header_mesh_start)\n        # Skip over the level dxs, geometry and the zero:\n        [next(header_file) for i in range(self.dataset._max_level + 3)]\n        # Now we need to be very careful, as we've seeked, and now we iterate.\n        # Does this work?  We are going to count the number of places that we\n        # have a three-item line.  The three items would be level, number of\n        # grids, and then grid time.\n        self.num_grids = 0\n        for line in header_file:\n            if len(line.split()) != 3:\n                continue\n            self.num_grids += int(line.split()[1])\n        header_file.close()\n\n    def _initialize_grid_arrays(self):\n        super()._initialize_grid_arrays()\n        self.grid_start_index = np.zeros((self.num_grids, 3), \"int64\")\n\n    def _initialize_state_variables(self):\n        \"\"\"override to not re-initialize num_grids in AMRHierarchy.__init__\"\"\"\n        self._parallel_locking = False\n        self._data_file = None\n        self._data_mode = None\n\n    def _detect_output_fields(self):\n        # This is all done in _parse_header_file\n        self.field_list = [(\"boxlib\", f) for f in self.dataset._field_list]\n        self.field_indexes = {f[1]: i for i, f in enumerate(self.field_list)}\n        # There are times when field_list may change.  We copy it here to\n        # avoid that possibility.\n        self.field_order = list(self.field_list)\n\n    def _setup_data_io(self):\n        self.io = io_registry[self.dataset_type](self.dataset)\n\n    def _determine_particle_output_type(self, directory_name):\n        header_filename = os.path.join(self.ds.output_dir, directory_name, \"Header\")\n        with open(header_filename) as f:\n            version_string = f.readline().strip()\n            if version_string.startswith(\"Version_Two\"):\n                return AMReXParticleHeader\n            else:\n                return BoxLibParticleHeader\n\n    def _read_particles(self, directory_name, is_checkpoint, extra_field_names=None):\n        pheader = self._determine_particle_output_type(directory_name)\n        self.particle_headers[directory_name] = pheader(\n            self.ds, directory_name, is_checkpoint, extra_field_names\n        )\n\n        num_parts = self.particle_headers[directory_name].num_particles\n        if self.ds._particle_type_counts is None:\n            self.ds._particle_type_counts = {}\n        self.ds._particle_type_counts[directory_name] = num_parts\n\n        base = os.path.join(self.ds.output_dir, directory_name)\n        if len(glob.glob(os.path.join(base, \"Level_?\", \"DATA_????\"))) > 0:\n            base_particle_fn = os.path.join(base, \"Level_%d\", \"DATA_%.4d\")\n        elif len(glob.glob(os.path.join(base, \"Level_?\", \"DATA_?????\"))) > 0:\n            base_particle_fn = os.path.join(base, \"Level_%d\", \"DATA_%.5d\")\n        else:\n            return\n\n        gid = 0\n        for lev, data in self.particle_headers[directory_name].data_map.items():\n            for pdf in data.values():\n                pdict = self.grids[gid]._pdata\n                pdict[directory_name] = {}\n                pdict[directory_name][\"particle_filename\"] = base_particle_fn % (\n                    lev,\n                    pdf.file_number,\n                )\n                pdict[directory_name][\"offset\"] = pdf.offset\n                pdict[directory_name][\"NumberOfParticles\"] = pdf.num_particles\n                self.grid_particle_count[gid] += pdf.num_particles\n                self.grids[gid].NumberOfParticles += pdf.num_particles\n                gid += 1\n\n        # add particle fields to field_list\n        pfield_list = self.particle_headers[directory_name].known_fields\n        self.field_list.extend(pfield_list)\n\n\nclass BoxlibDataset(Dataset):\n    \"\"\"\n    This class is a stripped down class that simply reads and parses\n    *filename*, without looking at the Boxlib index.\n    \"\"\"\n\n    _index_class = BoxlibHierarchy\n    _field_info_class: type[FieldInfoContainer] = BoxlibFieldInfo\n    _output_prefix = None\n    _default_cparam_filename = \"job_info\"\n\n    def __init__(\n        self,\n        output_dir,\n        cparam_filename=None,\n        fparam_filename=None,\n        dataset_type=\"boxlib_native\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        \"\"\"\n        The paramfile is usually called \"inputs\"\n        and there may be a fortran inputs file usually called \"probin\"\n        plotname here will be a directory name\n        as per BoxLib, dataset_type will be Native (implemented here), IEEE (not\n        yet implemented) or ASCII (not yet implemented.)\n        \"\"\"\n        self.fluid_types += (\"boxlib\",)\n        self.output_dir = os.path.abspath(os.path.expanduser(output_dir))\n\n        cparam_filename = cparam_filename or self.__class__._default_cparam_filename\n        self.cparam_filename = self._lookup_cparam_filepath(\n            self.output_dir, cparam_filename=cparam_filename\n        )\n        self.fparam_filename = self._localize_check(fparam_filename)\n        self.storage_filename = storage_filename\n\n        Dataset.__init__(\n            self,\n            output_dir,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n        # These are still used in a few places.\n        if \"HydroMethod\" not in self.parameters.keys():\n            self.parameters[\"HydroMethod\"] = \"boxlib\"\n        self.parameters[\"Time\"] = 1.0  # default unit is 1...\n        self.parameters[\"EOSType\"] = -1  # default\n        self.parameters[\"gamma\"] = self.parameters.get(\"materials.gamma\", 1.6667)\n\n    def _localize_check(self, fn):\n        if fn is None:\n            return None\n        # If the file exists, use it.  If not, set it to None.\n        root_dir = os.path.dirname(self.output_dir)\n        full_fn = os.path.join(root_dir, fn)\n        if os.path.exists(full_fn):\n            return full_fn\n        return None\n\n    @classmethod\n    def _is_valid(cls, filename, *args, cparam_filename=None, **kwargs):\n        output_dir = filename\n        header_filename = os.path.join(output_dir, \"Header\")\n        # boxlib datasets are always directories, and\n        # We *know* it's not boxlib if Header doesn't exist.\n        if not os.path.exists(header_filename):\n            return False\n\n        if cls is BoxlibDataset:\n            # Stop checks here for the boxlib base class.\n            # Further checks are performed on subclasses.\n            return True\n\n        cparam_filename = cparam_filename or cls._default_cparam_filename\n        cparam_filepath = cls._lookup_cparam_filepath(output_dir, cparam_filename)\n\n        if cparam_filepath is None:\n            return False\n\n        with open(cparam_filepath) as f:\n            lines = [line.lower() for line in f]\n        return any(cls._subtype_keyword in line for line in lines)\n\n    @classmethod\n    def _lookup_cparam_filepath(cls, output_dir, cparam_filename):\n        lookup_table = [\n            os.path.abspath(os.path.join(p, cparam_filename))\n            for p in (output_dir, os.path.dirname(output_dir))\n        ]\n        found = [os.path.exists(file) for file in lookup_table]\n\n        if not any(found):\n            return None\n\n        return lookup_table[found.index(True)]\n\n    @cached_property\n    def unique_identifier(self) -> str:\n        hfn = os.path.join(self.output_dir, \"Header\")\n        return str(int(os.stat(hfn)[ST_CTIME]))\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Parses the parameter file and establishes the various\n        dictionaries.\n        \"\"\"\n        self._periodicity = (False, False, False)\n        self._parse_header_file()\n        # Let's read the file\n        # the 'inputs' file is now optional\n        self._parse_cparams()\n        self._parse_fparams()\n\n    def _parse_cparams(self):\n        if self.cparam_filename is None:\n            return\n        with open(self.cparam_filename) as param_file:\n            for line in (line.split(\"#\")[0].strip() for line in param_file):\n                try:\n                    param, vals = (s.strip() for s in line.split(\"=\"))\n                except ValueError:\n                    continue\n                # Castro and Maestro mark overridden defaults with a \"[*]\"\n                # before the parameter name\n                param = param.removeprefix(\"[*]\").strip()\n                if param == \"amr.ref_ratio\":\n                    vals = self.refine_by = int(vals[0])\n                elif param == \"Prob.lo_bc\":\n                    vals = tuple(p == \"1\" for p in vals.split())\n                    assert len(vals) == self.dimensionality\n                    # default to non periodic\n                    periodicity = [False, False, False]\n                    # fill in ndim parsed values\n                    periodicity[: self.dimensionality] = vals\n                    self._periodicity = tuple(periodicity)\n                elif param == \"castro.use_comoving\":\n                    vals = self.cosmological_simulation = int(vals)\n                else:\n                    try:\n                        vals = _guess_pcast(vals)\n                    except (IndexError, ValueError):\n                        # hitting an empty string or a comment\n                        vals = None\n                self.parameters[param] = vals\n\n        if getattr(self, \"cosmological_simulation\", 0) == 1:\n            self.omega_lambda = self.parameters[\"comoving_OmL\"]\n            self.omega_matter = self.parameters[\"comoving_OmM\"]\n            self.hubble_constant = self.parameters[\"comoving_h\"]\n            with open(os.path.join(self.output_dir, \"comoving_a\")) as a_file:\n                line = a_file.readline().strip()\n            self.current_redshift = 1 / float(line) - 1\n        else:\n            self.current_redshift = 0.0\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0\n            self.hubble_constant = 0.0\n            self.cosmological_simulation = 0\n\n    def _parse_fparams(self):\n        \"\"\"\n        Parses the fortran parameter file for Orion. Most of this will\n        be useless, but this is where it keeps mu = mass per\n        particle/m_hydrogen.\n        \"\"\"\n        if self.fparam_filename is None:\n            return\n        param_file = open(self.fparam_filename)\n        for line in (l for l in param_file if \"=\" in l):\n            param, vals = (v.strip() for v in line.split(\"=\"))\n            # Now, there are a couple different types of parameters.\n            # Some will be where you only have floating point values, others\n            # will be where things are specified as string literals.\n            # Unfortunately, we're also using Fortran values, which will have\n            # things like 1.d-2 which is pathologically difficult to parse if\n            # your C library doesn't include 'd' in its locale for strtod.\n            # So we'll try to determine this.\n            vals = vals.split()\n            if any(_scinot_finder.match(v) for v in vals):\n                vals = [float(v.replace(\"D\", \"e\").replace(\"d\", \"e\")) for v in vals]\n            if len(vals) == 1:\n                vals = vals[0]\n            self.parameters[param] = vals\n        param_file.close()\n\n    def _parse_header_file(self):\n        \"\"\"\n        We parse the Boxlib header, which we use as our basis.  Anything in the\n        inputs file will override this, but the inputs file is not strictly\n        necessary for orientation of the data in space.\n        \"\"\"\n\n        # Note: Python uses a read-ahead buffer, so using next(), which would\n        # be my preferred solution, won't work here.  We have to explicitly\n        # call readline() if we want to end up with an offset at the very end.\n        # Fortunately, elsewhere we don't care about the offset, so we're fine\n        # everywhere else using iteration exclusively.\n        header_file = open(os.path.join(self.output_dir, \"Header\"))\n        self.orion_version = header_file.readline().rstrip()\n        n_fields = int(header_file.readline())\n\n        self._field_list = [header_file.readline().strip() for i in range(n_fields)]\n\n        self.dimensionality = int(header_file.readline())\n        self.current_time = float(header_file.readline())\n        # This is traditionally a index attribute, so we will set it, but\n        # in a slightly hidden variable.\n        self._max_level = int(header_file.readline())\n\n        for side, init in [(\"left\", np.zeros), (\"right\", np.ones)]:\n            domain_edge = init(3, dtype=\"float64\")\n            domain_edge[: self.dimensionality] = header_file.readline().split()\n            setattr(self, f\"domain_{side}_edge\", domain_edge)\n\n        ref_factors = np.array(header_file.readline().split(), dtype=\"int64\")\n        if ref_factors.size == 0:\n            # We use a default of two, as Nyx doesn't always output this value\n            ref_factors = [2] * (self._max_level + 1)\n        # We can't vary refinement factors based on dimension, or whatever else\n        # they are varied on.  In one curious thing, I found that some Castro 3D\n        # data has only two refinement factors, which I don't know how to\n        # understand.\n        self.ref_factors = ref_factors\n        if np.unique(ref_factors).size > 1:\n            # We want everything to be a multiple of this.\n            self.refine_by = min(ref_factors)\n            # Check that they're all multiples of the minimum.\n            if not all(\n                float(rf) / self.refine_by == int(float(rf) / self.refine_by)\n                for rf in ref_factors\n            ):\n                header_file.close()\n                raise RuntimeError\n            base_log = np.log2(self.refine_by)\n            self.level_offsets = [0]  # level 0 has to have 0 offset\n            lo = 0\n            for rf in self.ref_factors:\n                lo += int(np.log2(rf) / base_log) - 1\n                self.level_offsets.append(lo)\n        # assert(np.unique(ref_factors).size == 1)\n        else:\n            self.refine_by = ref_factors[0]\n            self.level_offsets = [0 for l in range(self._max_level + 1)]\n        # Now we read the global index space, to get\n        index_space = header_file.readline()\n        # This will be of the form:\n        #  ((0,0,0) (255,255,255) (0,0,0)) ((0,0,0) (511,511,511) (0,0,0))\n        # So note that if we split it all up based on spaces, we should be\n        # fine, as long as we take the first two entries, which correspond to\n        # the root level.  I'm not 100% pleased with this solution.\n        root_space = index_space.replace(\"(\", \"\").replace(\")\", \"\").split()[:2]\n        start = np.array(root_space[0].split(\",\"), dtype=\"int64\")\n        stop = np.array(root_space[1].split(\",\"), dtype=\"int64\")\n        dd = np.ones(3, dtype=\"int64\")\n        dd[: self.dimensionality] = stop - start + 1\n        self.domain_offset[: self.dimensionality] = start\n        self.domain_dimensions = dd\n\n        # Skip timesteps per level\n        header_file.readline()\n        self._header_mesh_start = header_file.tell()\n        # Skip the cell size information per level - we'll get this later\n        for _ in range(self._max_level + 1):\n            header_file.readline()\n        # Get the geometry\n        next_line = header_file.readline()\n        if len(next_line.split()) == 1:\n            coordinate_type = int(next_line)\n        else:\n            coordinate_type = 0\n\n        known_types = {0: \"cartesian\", 1: \"cylindrical\", 2: \"spherical\"}\n        try:\n            geom_str = known_types[coordinate_type]\n        except KeyError as err:\n            header_file.close()\n            raise ValueError(f\"Unknown BoxLib coord_type `{coordinate_type}`.\") from err\n        else:\n            self.geometry = Geometry(geom_str)\n\n        if self.geometry is Geometry.CYLINDRICAL:\n            dre = self.domain_right_edge.copy()\n            dre[2] = 2.0 * np.pi\n            self.domain_right_edge = dre\n        if self.geometry is Geometry.SPHERICAL and self.dimensionality < 3:\n            dre = self.domain_right_edge.copy()\n            dre[2] = 2.0 * np.pi\n            if self.dimensionality < 2:\n                dre[1] = np.pi\n            self.domain_right_edge = dre\n\n        header_file.close()\n\n    def _set_code_unit_attributes(self):\n        setdefaultattr(self, \"length_unit\", self.quan(1.0, \"cm\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"g\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n        setdefaultattr(self, \"velocity_unit\", self.quan(1.0, \"cm/s\"))\n\n    @parallel_root_only\n    def print_key_parameters(self):\n        for a in [\n            \"current_time\",\n            \"domain_dimensions\",\n            \"domain_left_edge\",\n            \"domain_right_edge\",\n        ]:\n            if not hasattr(self, a):\n                mylog.error(\"Missing %s in parameter file definition!\", a)\n                continue\n            v = getattr(self, a)\n            mylog.info(\"Parameters: %-25s = %s\", a, v)\n\n    def relative_refinement(self, l0, l1):\n        offset = self.level_offsets[l1] - self.level_offsets[l0]\n        return self.refine_by ** (l1 - l0 + offset)\n\n\nclass AMReXHierarchy(BoxlibHierarchy):\n    def __init__(self, ds, dataset_type=\"boxlib_native\"):\n        super().__init__(ds, dataset_type)\n\n        if \"particles\" in self.ds.parameters:\n            is_checkpoint = True\n            for ptype in self.ds.particle_types:\n                self._read_particles(ptype, is_checkpoint)\n\n\nclass AMReXDataset(BoxlibDataset):\n    _index_class: type[BoxlibHierarchy] = AMReXHierarchy\n    _subtype_keyword = \"amrex\"\n    _default_cparam_filename = \"job_info\"\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n        particle_types = glob.glob(os.path.join(self.output_dir, \"*\", \"Header\"))\n        particle_types = [cpt.split(os.sep)[-2] for cpt in particle_types]\n        if len(particle_types) > 0:\n            self.parameters[\"particles\"] = 1\n            self.particle_types = tuple(particle_types)\n            self.particle_types_raw = self.particle_types\n\n\nclass OrionHierarchy(BoxlibHierarchy):\n    def __init__(self, ds, dataset_type=\"orion_native\"):\n        BoxlibHierarchy.__init__(self, ds, dataset_type)\n        self._read_particles()\n        # self.io = IOHandlerOrion\n\n    def _detect_output_fields(self):\n        # This is all done in _parse_header_file\n        self.field_list = [(\"boxlib\", f) for f in self.dataset._field_list]\n        self.field_indexes = {f[1]: i for i, f in enumerate(self.field_list)}\n        # There are times when field_list may change.  We copy it here to\n        # avoid that possibility.\n        self.field_order = list(self.field_list)\n\n        # look for particle fields\n        self.particle_filename = None\n        for particle_filename in [\"StarParticles\", \"SinkParticles\"]:\n            fn = os.path.join(self.ds.output_dir, particle_filename)\n            if os.path.exists(fn):\n                self.particle_filename = fn\n\n        if self.particle_filename is None:\n            return\n\n        pfield_list = [(\"io\", c) for c in self.io.particle_field_index.keys()]\n        self.field_list.extend(pfield_list)\n\n    def _read_particles(self):\n        \"\"\"\n        Reads in particles and assigns them to grids. Will search for\n        Star particles, then sink particles if no star particle file\n        is found, and finally will simply note that no particles are\n        found if neither works. To add a new Orion particle type,\n        simply add it to the if/elif/else block.\n\n        \"\"\"\n        self.grid_particle_count = np.zeros(len(self.grids))\n\n        if self.particle_filename is not None:\n            self._read_particle_file(self.particle_filename)\n\n    def _read_particle_file(self, fn):\n        \"\"\"actually reads the orion particle data file itself.\"\"\"\n        if not os.path.exists(fn):\n            return\n        with open(fn) as f:\n            lines = f.readlines()\n            self.num_stars = int(lines[0].strip()[0])\n            for num, line in enumerate(lines[1:]):\n                particle_position_x = float(line.split(\" \")[1])\n                particle_position_y = float(line.split(\" \")[2])\n                particle_position_z = float(line.split(\" \")[3])\n                coord = [particle_position_x, particle_position_y, particle_position_z]\n                # for each particle, determine which grids contain it\n                # copied from object_finding_mixin.py\n                mask = np.ones(self.num_grids)\n                for i in range(len(coord)):\n                    np.choose(\n                        np.greater(self.grid_left_edge.d[:, i], coord[i]),\n                        (mask, 0),\n                        mask,\n                    )\n                    np.choose(\n                        np.greater(self.grid_right_edge.d[:, i], coord[i]),\n                        (0, mask),\n                        mask,\n                    )\n                ind = np.where(mask == 1)\n                selected_grids = self.grids[ind]\n                # in orion, particles always live on the finest level.\n                # so, we want to assign the particle to the finest of\n                # the grids we just found\n                if len(selected_grids) != 0:\n                    grid = sorted(selected_grids, key=lambda grid: grid.Level)[-1]\n                    ind = np.where(self.grids == grid)[0][0]\n                    self.grid_particle_count[ind] += 1\n                    self.grids[ind].NumberOfParticles += 1\n\n                    # store the position in the particle file for fast access.\n                    try:\n                        self.grids[ind]._particle_line_numbers.append(num + 1)\n                    except AttributeError:\n                        self.grids[ind]._particle_line_numbers = [num + 1]\n        return True\n\n\nclass OrionDataset(BoxlibDataset):\n    _index_class = OrionHierarchy\n    _subtype_keyword = \"hyp.\"\n    _default_cparam_filename = \"inputs\"\n\n    def __init__(\n        self,\n        output_dir,\n        cparam_filename=None,\n        fparam_filename=\"probin\",\n        dataset_type=\"orion_native\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        BoxlibDataset.__init__(\n            self,\n            output_dir,\n            cparam_filename,\n            fparam_filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n\nclass CastroHierarchy(BoxlibHierarchy):\n    def __init__(self, ds, dataset_type=\"castro_native\"):\n        super().__init__(ds, dataset_type)\n\n        if \"particles\" in self.ds.parameters:\n            # extra beyond the base real fields that all Boxlib\n            # particles have, i.e. the xyz positions\n            castro_extra_real_fields = [\n                \"particle_velocity_x\",\n                \"particle_velocity_y\",\n                \"particle_velocity_z\",\n            ]\n\n            is_checkpoint = True\n\n            self._read_particles(\n                \"Tracer\",\n                is_checkpoint,\n                castro_extra_real_fields[0 : self.ds.dimensionality],\n            )\n\n\nclass CastroDataset(AMReXDataset):\n    _index_class = CastroHierarchy\n    _field_info_class = CastroFieldInfo\n    _subtype_keyword = \"castro\"\n    _default_cparam_filename = \"job_info\"\n\n    def __init__(\n        self,\n        output_dir,\n        cparam_filename=None,\n        fparam_filename=None,\n        dataset_type=\"boxlib_native\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        super().__init__(\n            output_dir,\n            cparam_filename,\n            fparam_filename,\n            dataset_type,\n            storage_filename,\n            units_override,\n            unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n        jobinfo_filename = os.path.join(self.output_dir, self.cparam_filename)\n        line = \"\"\n        with open(jobinfo_filename) as f:\n            while not line.startswith(\" Inputs File Parameters\"):\n                # boundary condition info starts with -x:, etc.\n                bcs = [\"-x:\", \"+x:\", \"-y:\", \"+y:\", \"-z:\", \"+z:\"]\n                if any(b in line for b in bcs):\n                    p, v = line.strip().split(\":\")\n                    self.parameters[p] = v.strip()\n                if \"git describe\" in line or \"git hash\" in line:\n                    # Castro release 17.02 and later\n                    #    line format: codename git describe:  the-hash\n                    # Castro before release 17.02\n                    #    line format: codename git hash:  the-hash\n                    fields = line.split(\":\")\n                    self.parameters[fields[0]] = fields[1].strip()\n                line = next(f)\n\n        # hydro method is set by the base class -- override it here\n        self.parameters[\"HydroMethod\"] = \"Castro\"\n\n        # set the periodicity based on the runtime parameters\n        # https://amrex-astro.github.io/Castro/docs/inputs.html?highlight=periodicity\n        periodicity = [False, False, False]\n        for i, axis in enumerate(\"xyz\"):\n            try:\n                periodicity[i] = self.parameters[f\"-{axis}\"] == \"interior\"\n            except KeyError:\n                break\n\n        self._periodicity = tuple(periodicity)\n\n        if os.path.isdir(os.path.join(self.output_dir, \"Tracer\")):\n            # we have particles\n            self.parameters[\"particles\"] = 1\n            self.particle_types = (\"Tracer\",)\n            self.particle_types_raw = self.particle_types\n\n\nclass MaestroDataset(AMReXDataset):\n    _index_class = BoxlibHierarchy\n    _field_info_class = MaestroFieldInfo\n    _subtype_keyword = \"maestro\"\n    _default_cparam_filename = \"job_info\"\n\n    def __init__(\n        self,\n        output_dir,\n        cparam_filename=None,\n        fparam_filename=None,\n        dataset_type=\"boxlib_native\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        super().__init__(\n            output_dir,\n            cparam_filename,\n            fparam_filename,\n            dataset_type,\n            storage_filename,\n            units_override,\n            unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n        jobinfo_filename = os.path.join(self.output_dir, self.cparam_filename)\n\n        with open(jobinfo_filename) as f:\n            for line in f:\n                # get the code git hashes\n                if \"git hash\" in line:\n                    # line format: codename git hash:  the-hash\n                    fields = line.split(\":\")\n                    self.parameters[fields[0]] = fields[1].strip()\n\n        # hydro method is set by the base class -- override it here\n        self.parameters[\"HydroMethod\"] = \"Maestro\"\n\n        # set the periodicity based on the integer BC runtime parameters\n        periodicity = [False, False, False]\n        for i, ax in enumerate(\"xyz\"):\n            try:\n                periodicity[i] = self.parameters[f\"bc{ax}_lo\"] != -1\n            except KeyError:\n                pass\n\n        self._periodicity = tuple(periodicity)\n\n\nclass NyxHierarchy(BoxlibHierarchy):\n    def __init__(self, ds, dataset_type=\"nyx_native\"):\n        super().__init__(ds, dataset_type)\n\n        if \"particles\" in self.ds.parameters:\n            # extra beyond the base real fields that all Boxlib\n            # particles have, i.e. the xyz positions\n            nyx_extra_real_fields = [\n                \"particle_mass\",\n                \"particle_velocity_x\",\n                \"particle_velocity_y\",\n                \"particle_velocity_z\",\n            ]\n\n            is_checkpoint = False\n\n            self._read_particles(\n                \"DM\",\n                is_checkpoint,\n                nyx_extra_real_fields[0 : self.ds.dimensionality + 1],\n            )\n\n\nclass NyxDataset(BoxlibDataset):\n    _index_class = NyxHierarchy\n    _field_info_class = NyxFieldInfo\n    _subtype_keyword = \"nyx\"\n    _default_cparam_filename = \"job_info\"\n\n    def __init__(\n        self,\n        output_dir,\n        cparam_filename=None,\n        fparam_filename=None,\n        dataset_type=\"boxlib_native\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        super().__init__(\n            output_dir,\n            cparam_filename,\n            fparam_filename,\n            dataset_type,\n            storage_filename,\n            units_override,\n            unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n\n        jobinfo_filename = os.path.join(self.output_dir, self.cparam_filename)\n\n        with open(jobinfo_filename) as f:\n            for line in f:\n                # get the code git hashes\n                if \"git hash\" in line:\n                    # line format: codename git hash:  the-hash\n                    fields = line.split(\":\")\n                    self.parameters[fields[0]] = fields[1].strip()\n\n                if line.startswith(\" Cosmology Information\"):\n                    self.cosmological_simulation = 1\n                    break\n            else:\n                self.cosmological_simulation = 0\n\n            if self.cosmological_simulation:\n                # note that modern Nyx is always cosmological, but there are some old\n                # files without these parameters so we want to special-case them\n                for line in f:\n                    if \"Omega_m (comoving)\" in line:\n                        self.omega_matter = float(line.split(\":\")[1])\n                    elif \"Omega_lambda (comoving)\" in line:\n                        self.omega_lambda = float(line.split(\":\")[1])\n                    elif \"h (comoving)\" in line:\n                        self.hubble_constant = float(line.split(\":\")[1])\n\n        # Read in the `comoving_a` file and parse the value. We should fix this\n        # in the new Nyx output format...\n        with open(os.path.join(self.output_dir, \"comoving_a\")) as a_file:\n            a_string = a_file.readline().strip()\n\n        # Set the scale factor and redshift\n        self.cosmological_scale_factor = float(a_string)\n        self.parameters[\"CosmologyCurrentRedshift\"] = 1 / float(a_string) - 1\n\n        # alias\n        self.current_redshift = self.parameters[\"CosmologyCurrentRedshift\"]\n        if os.path.isfile(os.path.join(self.output_dir, \"DM/Header\")):\n            # we have particles\n            self.parameters[\"particles\"] = 1\n            self.particle_types = (\"DM\",)\n            self.particle_types_raw = self.particle_types\n\n    def _set_code_unit_attributes(self):\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"Msun\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0 / 3.08568025e19, \"s\"))\n        setdefaultattr(\n            self, \"length_unit\", self.quan(1.0 / (1 + self.current_redshift), \"Mpc\")\n        )\n        setdefaultattr(self, \"velocity_unit\", self.length_unit / self.time_unit)\n\n\nclass QuokkaDataset(AMReXDataset):\n    # match any plotfiles that have a metadata.yaml file in the root\n    _subtype_keyword = \"\"\n    _default_cparam_filename = \"metadata.yaml\"\n\n\ndef _guess_pcast(vals):\n    # Now we guess some things about the parameter and its type\n    # Just in case there are multiple; we'll go\n    # back afterward to using vals.\n    v = vals.split()[0]\n    try:\n        float(v.upper().replace(\"D\", \"E\"))\n    except Exception:\n        pcast = str\n        if v in (\"F\", \"T\"):\n            pcast = bool\n    else:\n        syms = (\".\", \"D+\", \"D-\", \"E+\", \"E-\", \"E\", \"D\")\n        if any(sym in v.upper() for sym in syms for v in vals.split()):\n            pcast = float\n        else:\n            pcast = int\n    if pcast is bool:\n        vals = [value == \"T\" for value in vals.split()]\n    else:\n        vals = [pcast(value) for value in vals.split()]\n    if len(vals) == 1:\n        vals = vals[0]\n    return vals\n\n\ndef _read_raw_field_names(raw_file):\n    header_files = glob.glob(os.path.join(raw_file, \"*_H\"))\n    return [hf.split(os.sep)[-1][:-2] for hf in header_files]\n\n\ndef _string_to_numpy_array(s):\n    return np.array([int(v) for v in s[1:-1].split(\",\")], dtype=np.int64)\n\n\ndef _line_to_numpy_arrays(line):\n    lo_corner = _string_to_numpy_array(line[0][1:])\n    hi_corner = _string_to_numpy_array(line[1][:])\n    node_type = _string_to_numpy_array(line[2][:-1])\n    return lo_corner, hi_corner, node_type\n\n\ndef _get_active_dimensions(box):\n    return box[1] - box[2] - box[0] + 1\n\n\ndef _read_header(raw_file, field):\n    level_files = glob.glob(os.path.join(raw_file, \"Level_*\"))\n    level_files.sort()\n\n    all_boxes = []\n    all_file_names = []\n    all_offsets = []\n\n    for level_file in level_files:\n        header_file = os.path.join(level_file, field + \"_H\")\n        with open(header_file) as f:\n            f.readline()  # version\n            f.readline()  # how\n            f.readline()  # ncomp\n\n            # nghost_line will be parsed below after the number of dimensions\n            # is determined when the boxes are read in\n            nghost_line = f.readline().strip().split()\n\n            f.readline()  # num boxes\n\n            # read boxes\n            boxes = []\n            for line in f:\n                clean_line = line.strip().split()\n                if clean_line == [\")\"]:\n                    break\n                lo_corner, hi_corner, node_type = _line_to_numpy_arrays(clean_line)\n                boxes.append((lo_corner, hi_corner, node_type))\n\n            try:\n                # nghost_line[0] is a single number\n                ng = int(nghost_line[0])\n                ndims = len(lo_corner)\n                nghost = np.array(ndims * [ng])\n            except ValueError:\n                # nghost_line[0] is (#,#,#)\n                nghost_list = nghost_line[0].strip(\"()\").split(\",\")\n                nghost = np.array(nghost_list, dtype=\"int64\")\n\n            # read the file and offset position for the corresponding box\n            file_names = []\n            offsets = []\n            for line in f:\n                if line.startswith(\"FabOnDisk:\"):\n                    clean_line = line.strip().split()\n                    file_names.append(clean_line[1])\n                    offsets.append(int(clean_line[2]))\n\n            all_boxes += boxes\n            all_file_names += file_names\n            all_offsets += offsets\n\n    return nghost, all_boxes, all_file_names, all_offsets\n\n\nclass WarpXHeader:\n    def __init__(self, header_fn):\n        self.data = {}\n        with open(header_fn) as f:\n            self.data[\"Checkpoint_version\"] = int(f.readline().strip().split()[-1])\n\n            self.data[\"num_levels\"] = int(f.readline().strip().split()[-1])\n            self.data[\"istep\"] = [int(num) for num in f.readline().strip().split()]\n            self.data[\"nsubsteps\"] = [int(num) for num in f.readline().strip().split()]\n\n            self.data[\"t_new\"] = [float(num) for num in f.readline().strip().split()]\n            self.data[\"t_old\"] = [float(num) for num in f.readline().strip().split()]\n            self.data[\"dt\"] = [float(num) for num in f.readline().strip().split()]\n\n            self.data[\"moving_window_x\"] = float(f.readline().strip().split()[-1])\n\n            #  not all datasets will have is_synchronized\n            line = f.readline().strip().split()\n            if len(line) == 1:\n                self.data[\"is_synchronized\"] = bool(line[-1])\n                self.data[\"prob_lo\"] = [\n                    float(num) for num in f.readline().strip().split()\n                ]\n            else:\n                self.data[\"is_synchronized\"] = True\n                self.data[\"prob_lo\"] = [float(num) for num in line]\n\n            self.data[\"prob_hi\"] = [float(num) for num in f.readline().strip().split()]\n\n            for _ in range(self.data[\"num_levels\"]):\n                num_boxes = int(f.readline().strip().split()[0][1:])\n                for __ in range(num_boxes):\n                    f.readline()\n                f.readline()\n\n            i = 0\n            line = f.readline()\n            while line:\n                line = line.strip().split()\n                if len(line) == 1:\n                    line = f.readline()\n                    continue\n                self.data[f\"species_{i}\"] = [float(val) for val in line]\n                i = i + 1\n                line = f.readline()\n\n\nclass WarpXHierarchy(BoxlibHierarchy):\n    def __init__(self, ds, dataset_type=\"boxlib_native\"):\n        super().__init__(ds, dataset_type)\n\n        is_checkpoint = True\n        for ptype in self.ds.particle_types:\n            self._read_particles(ptype, is_checkpoint)\n\n        # Additional WarpX particle information (used to set up species)\n        self.warpx_header = WarpXHeader(os.path.join(self.ds.output_dir, \"WarpXHeader\"))\n\n        for key, val in self.warpx_header.data.items():\n            if key.startswith(\"species_\"):\n                i = int(key.split(\"_\")[-1])\n                charge_name = f\"particle{i}_charge\"\n                mass_name = f\"particle{i}_mass\"\n                self.parameters[charge_name] = val[0]\n                self.parameters[mass_name] = val[1]\n\n    def _detect_output_fields(self):\n        super()._detect_output_fields()\n\n        # now detect the optional, non-cell-centered fields\n        self.raw_file = os.path.join(self.ds.output_dir, \"raw_fields\")\n        self.raw_fields = _read_raw_field_names(os.path.join(self.raw_file, \"Level_0\"))\n        self.field_list += [(\"raw\", f) for f in self.raw_fields]\n        self.raw_field_map = {}\n        self.ds.nodal_flags = {}\n        self.raw_field_nghost = {}\n        for field_name in self.raw_fields:\n            nghost, boxes, file_names, offsets = _read_header(self.raw_file, field_name)\n            self.raw_field_map[field_name] = (boxes, file_names, offsets)\n            self.raw_field_nghost[field_name] = nghost\n            self.ds.nodal_flags[field_name] = np.array(boxes[0][2])\n\n\ndef _skip_line(line):\n    if len(line) == 0:\n        return True\n    if line[0] == \"\\n\":\n        return True\n    if line[0] == \"=\":\n        return True\n    if line[0] == \" \":\n        return True\n\n\nclass WarpXDataset(BoxlibDataset):\n    _index_class = WarpXHierarchy\n    _field_info_class = WarpXFieldInfo\n    _subtype_keyword = \"warpx\"\n    _default_cparam_filename = \"warpx_job_info\"\n\n    def __init__(\n        self,\n        output_dir,\n        cparam_filename=None,\n        fparam_filename=None,\n        dataset_type=\"boxlib_native\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"mks\",\n    ):\n        self.default_fluid_type = \"mesh\"\n        self.default_field = (\"mesh\", \"density\")\n        self.fluid_types = (\"mesh\", \"index\", \"raw\")\n\n        super().__init__(\n            output_dir,\n            cparam_filename,\n            fparam_filename,\n            dataset_type,\n            storage_filename,\n            units_override,\n            unit_system,\n        )\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n        jobinfo_filename = os.path.join(self.output_dir, self.cparam_filename)\n        with open(jobinfo_filename) as f:\n            for line in f.readlines():\n                if _skip_line(line):\n                    continue\n                l = line.strip().split(\":\")\n                if len(l) == 2:\n                    self.parameters[l[0].strip()] = l[1].strip()\n                l = line.strip().split(\"=\")\n                if len(l) == 2:\n                    self.parameters[l[0].strip()] = l[1].strip()\n\n        # set the periodicity based on the integer BC runtime parameters\n        # https://amrex-codes.github.io/amrex/docs_html/InputsProblemDefinition.html\n        periodicity = [False, False, False]\n        try:\n            is_periodic = self.parameters[\"geometry.is_periodic\"].split()\n            periodicity[: len(is_periodic)] = [p == \"1\" for p in is_periodic]\n        except KeyError:\n            pass\n        self._periodicity = tuple(periodicity)\n\n        particle_types = glob.glob(os.path.join(self.output_dir, \"*\", \"Header\"))\n        particle_types = [cpt.split(os.sep)[-2] for cpt in particle_types]\n        if len(particle_types) > 0:\n            self.parameters[\"particles\"] = 1\n            self.particle_types = tuple(particle_types)\n            self.particle_types_raw = self.particle_types\n        else:\n            self.particle_types = ()\n            self.particle_types_raw = ()\n\n    def _set_code_unit_attributes(self):\n        setdefaultattr(self, \"length_unit\", self.quan(1.0, \"m\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"kg\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n        setdefaultattr(self, \"velocity_unit\", self.quan(1.0, \"m/s\"))\n        setdefaultattr(self, \"magnetic_unit\", self.quan(1.0, \"T\"))\n"
  },
  {
    "path": "yt/frontends/amrex/definitions.py",
    "content": ""
  },
  {
    "path": "yt/frontends/amrex/fields.py",
    "content": "import re\nfrom typing import TypeAlias\n\nimport numpy as np\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.units import YTQuantity\nfrom yt.utilities.physical_constants import amu_cgs, boltzmann_constant_cgs, c\n\nrho_units = \"code_mass / code_length**3\"\nmom_units = \"code_mass / (code_time * code_length**2)\"\neden_units = \"code_mass / (code_time**2 * code_length)\"  # erg / cm^3\n\n\ndef _thermal_energy_density(data):\n    # What we've got here is UEINT:\n    # u here is velocity\n    # E is energy density from the file\n    #   rho e = rho E - rho * u * u / 2\n    ke = (\n        0.5\n        * (\n            data[\"gas\", \"momentum_density_x\"] ** 2\n            + data[\"gas\", \"momentum_density_y\"] ** 2\n            + data[\"gas\", \"momentum_density_z\"] ** 2\n        )\n        / data[\"gas\", \"density\"]\n    )\n    return data[\"boxlib\", \"eden\"] - ke\n\n\ndef _specific_thermal_energy(data):\n    # This is little e, so we take thermal_energy_density and divide by density\n    return data[\"gas\", \"thermal_energy_density\"] / data[\"gas\", \"density\"]\n\n\ndef _temperature(data):\n    mu = data.ds.parameters[\"mu\"]\n    gamma = data.ds.parameters[\"gamma\"]\n    tr = data[\"gas\", \"thermal_energy_density\"] / data[\"gas\", \"density\"]\n    tr *= mu * amu_cgs / boltzmann_constant_cgs\n    tr *= gamma - 1.0\n    return tr\n\n\nclass WarpXFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"Bx\", (\"T\", [\"magnetic_field_x\", \"B_x\"], None)),\n        (\"By\", (\"T\", [\"magnetic_field_y\", \"B_y\"], None)),\n        (\"Bz\", (\"T\", [\"magnetic_field_z\", \"B_z\"], None)),\n        (\"Ex\", (\"V/m\", [\"electric_field_x\", \"E_x\"], None)),\n        (\"Ey\", (\"V/m\", [\"electric_field_y\", \"E_y\"], None)),\n        (\"Ez\", (\"V/m\", [\"electric_field_z\", \"E_z\"], None)),\n        (\"jx\", (\"A\", [\"current_x\", \"Jx\", \"J_x\"], None)),\n        (\"jy\", (\"A\", [\"current_y\", \"Jy\", \"J_y\"], None)),\n        (\"jz\", (\"A\", [\"current_z\", \"Jz\", \"J_z\"], None)),\n    )\n\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_weight\", (\"\", [\"particle_weighting\"], None)),\n        (\"particle_position_x\", (\"m\", [], None)),\n        (\"particle_position_y\", (\"m\", [], None)),\n        (\"particle_position_z\", (\"m\", [], None)),\n        (\"particle_velocity_x\", (\"m/s\", [], None)),\n        (\"particle_velocity_y\", (\"m/s\", [], None)),\n        (\"particle_velocity_z\", (\"m/s\", [], None)),\n        (\"particle_momentum_x\", (\"kg*m/s\", [], None)),\n        (\"particle_momentum_y\", (\"kg*m/s\", [], None)),\n        (\"particle_momentum_z\", (\"kg*m/s\", [], None)),\n    )\n\n    extra_union_fields = (\n        (\"kg\", \"particle_mass\"),\n        (\"C\", \"particle_charge\"),\n        (\"\", \"particle_ones\"),\n    )\n\n    def __init__(self, ds, field_list):\n        super().__init__(ds, field_list)\n\n        # setup nodal flag information\n        for field in ds.index.raw_fields:\n            finfo = self.__getitem__((\"raw\", field))\n            finfo.nodal_flag = ds.nodal_flags[field]\n\n    def setup_fluid_fields(self):\n        for field in self.known_other_fields:\n            fname = field[0]\n            self.alias((\"mesh\", fname), (\"boxlib\", fname))\n\n    def setup_fluid_aliases(self):\n        super().setup_fluid_aliases(\"mesh\")\n\n    def setup_particle_fields(self, ptype):\n        def get_mass(data):\n            species_mass = data.ds.index.parameters[ptype + \"_mass\"]\n            return data[ptype, \"particle_weight\"] * YTQuantity(species_mass, \"kg\")\n\n        self.add_field(\n            (ptype, \"particle_mass\"),\n            sampling_type=\"particle\",\n            function=get_mass,\n            units=\"kg\",\n        )\n\n        def get_charge(data):\n            species_charge = data.ds.index.parameters[ptype + \"_charge\"]\n            return data[ptype, \"particle_weight\"] * YTQuantity(species_charge, \"C\")\n\n        self.add_field(\n            (ptype, \"particle_charge\"),\n            sampling_type=\"particle\",\n            function=get_charge,\n            units=\"C\",\n        )\n\n        def get_energy(data):\n            p2 = (\n                data[ptype, \"particle_momentum_x\"] ** 2\n                + data[ptype, \"particle_momentum_y\"] ** 2\n                + data[ptype, \"particle_momentum_z\"] ** 2\n            )\n            return np.sqrt(p2 * c**2 + data[ptype, \"particle_mass\"] ** 2 * c**4)\n\n        self.add_field(\n            (ptype, \"particle_energy\"),\n            sampling_type=\"particle\",\n            function=get_energy,\n            units=\"J\",\n        )\n\n        def get_velocity_x(data):\n            return (\n                c**2\n                * data[ptype, \"particle_momentum_x\"]\n                / data[ptype, \"particle_energy\"]\n            )\n\n        def get_velocity_y(data):\n            return (\n                c**2\n                * data[ptype, \"particle_momentum_y\"]\n                / data[ptype, \"particle_energy\"]\n            )\n\n        def get_velocity_z(data):\n            return (\n                c**2\n                * data[ptype, \"particle_momentum_z\"]\n                / data[ptype, \"particle_energy\"]\n            )\n\n        self.add_field(\n            (ptype, \"particle_velocity_x\"),\n            sampling_type=\"particle\",\n            function=get_velocity_x,\n            units=\"m/s\",\n        )\n\n        self.add_field(\n            (ptype, \"particle_velocity_y\"),\n            sampling_type=\"particle\",\n            function=get_velocity_y,\n            units=\"m/s\",\n        )\n\n        self.add_field(\n            (ptype, \"particle_velocity_z\"),\n            sampling_type=\"particle\",\n            function=get_velocity_z,\n            units=\"m/s\",\n        )\n\n        super().setup_particle_fields(ptype)\n\n\nclass NyxFieldInfo(FieldInfoContainer):\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_position_z\", (\"code_length\", [], None)),\n    )\n\n\nclass BoxlibFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (rho_units, [\"density\"], None)),\n        (\"eden\", (eden_units, [\"total_energy_density\"], None)),\n        (\"xmom\", (mom_units, [\"momentum_density_x\"], None)),\n        (\"ymom\", (mom_units, [\"momentum_density_y\"], None)),\n        (\"zmom\", (mom_units, [\"momentum_density_z\"], None)),\n        (\"temperature\", (\"K\", [\"temperature\"], None)),\n        (\"Temp\", (\"K\", [\"temperature\"], None)),\n        (\"x_velocity\", (\"cm/s\", [\"velocity_x\"], None)),\n        (\"y_velocity\", (\"cm/s\", [\"velocity_y\"], None)),\n        (\"z_velocity\", (\"cm/s\", [\"velocity_z\"], None)),\n        (\"xvel\", (\"cm/s\", [\"velocity_x\"], None)),\n        (\"yvel\", (\"cm/s\", [\"velocity_y\"], None)),\n        (\"zvel\", (\"cm/s\", [\"velocity_z\"], None)),\n    )\n\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_position_z\", (\"code_length\", [], None)),\n        (\"particle_momentum_x\", (\"code_mass*code_length/code_time\", [], None)),\n        (\"particle_momentum_y\", (\"code_mass*code_length/code_time\", [], None)),\n        (\"particle_momentum_z\", (\"code_mass*code_length/code_time\", [], None)),\n        # Note that these are *internal* agmomen\n        (\"particle_angmomen_x\", (\"code_length**2/code_time\", [], None)),\n        (\"particle_angmomen_y\", (\"code_length**2/code_time\", [], None)),\n        (\"particle_angmomen_z\", (\"code_length**2/code_time\", [], None)),\n        (\"particle_id\", (\"\", [\"particle_index\"], None)),\n        (\"particle_mdot\", (\"code_mass/code_time\", [], None)),\n        # \"mlast\",\n        # \"r\",\n        # \"mdeut\",\n        # \"n\",\n        # \"burnstate\",\n        # \"luminosity\",\n    )\n\n    def setup_particle_fields(self, ptype):\n        def _get_vel(axis):\n            def velocity(data):\n                return (\n                    data[ptype, f\"particle_momentum_{axis}\"]\n                    / data[ptype, \"particle_mass\"]\n                )\n\n            return velocity\n\n        for ax in \"xyz\":\n            self.add_field(\n                (ptype, f\"particle_velocity_{ax}\"),\n                sampling_type=\"particle\",\n                function=_get_vel(ax),\n                units=\"code_length/code_time\",\n            )\n\n        super().setup_particle_fields(ptype)\n\n    def setup_fluid_fields(self):\n        unit_system = self.ds.unit_system\n        # Now, let's figure out what fields are included.\n        if any(f[1] == \"xmom\" for f in self.field_list):\n            self.setup_momentum_to_velocity()\n        elif any(f[1] == \"xvel\" for f in self.field_list):\n            self.setup_velocity_to_momentum()\n        self.add_field(\n            (\"gas\", \"specific_thermal_energy\"),\n            sampling_type=\"cell\",\n            function=_specific_thermal_energy,\n            units=unit_system[\"specific_energy\"],\n        )\n        self.add_field(\n            (\"gas\", \"thermal_energy_density\"),\n            sampling_type=\"cell\",\n            function=_thermal_energy_density,\n            units=unit_system[\"pressure\"],\n        )\n        if (\"gas\", \"temperature\") not in self.field_aliases:\n            self.add_field(\n                (\"gas\", \"temperature\"),\n                sampling_type=\"cell\",\n                function=_temperature,\n                units=unit_system[\"temperature\"],\n            )\n\n    def setup_momentum_to_velocity(self):\n        def _get_vel(axis):\n            def velocity(data):\n                return data[\"boxlib\", f\"{axis}mom\"] / data[\"boxlib\", \"density\"]\n\n            return velocity\n\n        for ax in \"xyz\":\n            self.add_field(\n                (\"gas\", f\"velocity_{ax}\"),\n                sampling_type=\"cell\",\n                function=_get_vel(ax),\n                units=self.ds.unit_system[\"velocity\"],\n            )\n\n    def setup_velocity_to_momentum(self):\n        def _get_mom(axis):\n            def momentum(data):\n                return data[\"boxlib\", f\"{axis}vel\"] * data[\"boxlib\", \"density\"]\n\n            return momentum\n\n        for ax in \"xyz\":\n            self.add_field(\n                (\"gas\", f\"momentum_density_{ax}\"),\n                sampling_type=\"cell\",\n                function=_get_mom(ax),\n                units=mom_units,\n            )\n\n\nclass CastroFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (\"g/cm**3\", [\"density\"], r\"\\rho\")),\n        (\"xmom\", (\"g/(cm**2 * s)\", [\"momentum_density_x\"], r\"\\rho u\")),\n        (\"ymom\", (\"g/(cm**2 * s)\", [\"momentum_density_y\"], r\"\\rho v\")),\n        (\"zmom\", (\"g/(cm**2 * s)\", [\"momentum_density_z\"], r\"\\rho w\")),\n        # velocity components are not always present\n        (\"x_velocity\", (\"cm/s\", [\"velocity_x\"], r\"u\")),\n        (\"y_velocity\", (\"cm/s\", [\"velocity_y\"], r\"v\")),\n        (\"z_velocity\", (\"cm/s\", [\"velocity_z\"], r\"w\")),\n        (\"rho_E\", (\"erg/cm**3\", [\"total_energy_density\"], r\"\\rho E\")),\n        # internal energy density (not just thermal)\n        (\"rho_e\", (\"erg/cm**3\", [], r\"\\rho e\")),\n        (\"Temp\", (\"K\", [\"temperature\"], r\"T\")),\n        (\"grav_x\", (\"cm/s**2\", [], r\"\\mathbf{g} \\cdot \\mathbf{e}_x\")),\n        (\"grav_y\", (\"cm/s**2\", [], r\"\\mathbf{g} \\cdot \\mathbf{e}_y\")),\n        (\"grav_z\", (\"cm/s**2\", [], r\"\\mathbf{g} \\cdot \\mathbf{e}_z\")),\n        (\"pressure\", (\"dyne/cm**2\", [], r\"p\")),\n        (\n            \"kineng\",\n            (\"erg/cm**3\", [\"kinetic_energy_density\"], r\"\\frac{1}{2}\\rho|\\mathbf{U}|^2\"),\n        ),\n        (\"soundspeed\", (\"cm/s\", [\"sound_speed\"], \"Sound Speed\")),\n        (\"MachNumber\", (\"\", [\"mach_number\"], \"Mach Number\")),\n        (\"abar\", (\"\", [], r\"$\\bar{A}$\")),\n        (\"Ye\", (\"\", [], r\"$Y_e$\")),\n        (\"entropy\", (\"erg/(g*K)\", [\"entropy\"], r\"s\")),\n        (\"magvort\", (\"1/s\", [\"vorticity_magnitude\"], r\"|\\nabla \\times \\mathbf{U}|\")),\n        (\"divu\", (\"1/s\", [\"velocity_divergence\"], r\"\\nabla \\cdot \\mathbf{U}\")),\n        (\"eint_E\", (\"erg/g\", [], r\"e(E,U)\")),\n        (\"eint_e\", (\"erg/g\", [], r\"e\")),\n        (\"magvel\", (\"cm/s\", [\"velocity_magnitude\"], r\"|\\mathbf{U}|\")),\n        (\"radvel\", (\"cm/s\", [\"radial_velocity\"], r\"\\mathbf{U} \\cdot \\mathbf{e}_r\")),\n        (\"magmom\", (\"g*cm/s\", [\"momentum_magnitude\"], r\"\\rho |\\mathbf{U}|\")),\n        (\"maggrav\", (\"cm/s**2\", [], r\"|\\mathbf{g}|\")),\n        (\"phiGrav\", (\"erg/g\", [], r\"\\Phi\")),\n        (\"enuc\", (\"erg/(g*s)\", [], r\"\\dot{e}_{\\rm{nuc}}\")),\n        (\"rho_enuc\", (\"erg/(cm**3*s)\", [], r\"\\rho \\dot{e}_{\\rm{nuc}}\")),\n        (\"angular_momentum_x\", (\"g/(cm*s)\", [], r\"\\ell_x\")),\n        (\"angular_momentum_y\", (\"g/(cm*s)\", [], r\"\\ell_y\")),\n        (\"angular_momentum_z\", (\"g/(cm*s)\", [], r\"\\ell_z\")),\n        (\"phiRot\", (\"erg/g\", [], r\"\\Phi_{\\rm{rot}}\")),\n        (\"rot_x\", (\"cm/s**2\", [], r\"\\mathbf{f}_{\\rm{rot}} \\cdot \\mathbf{e}_x\")),\n        (\"rot_y\", (\"cm/s**2\", [], r\"\\mathbf{f}_{\\rm{rot}} \\cdot \\mathbf{e}_y\")),\n        (\"rot_z\", (\"cm/s**2\", [], r\"\\mathbf{f}_{\\rm{rot}} \\cdot \\mathbf{e}_z\")),\n    )\n\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_position_z\", (\"code_length\", [], None)),\n    )\n\n    def setup_fluid_fields(self):\n        # add X's\n        for _, field in self.ds.field_list:\n            if field.startswith(\"X(\"):\n                # We have a fraction\n                sub = Substance(field)\n                # Overwrite field to use nicer tex_label display_name\n                self.add_output_field(\n                    (\"boxlib\", field),\n                    sampling_type=\"cell\",\n                    units=\"\",\n                    display_name=rf\"X\\left({sub.to_tex()}\\right)\",\n                )\n                self.alias((\"gas\", f\"{sub}_fraction\"), (\"boxlib\", field), units=\"\")\n                func = _create_density_func((\"gas\", f\"{sub}_fraction\"))\n                self.add_field(\n                    name=(\"gas\", f\"{sub}_density\"),\n                    sampling_type=\"cell\",\n                    function=func,\n                    units=self.ds.unit_system[\"density\"],\n                    display_name=rf\"\\rho {sub.to_tex()}\",\n                )\n\n\nclass MaestroFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (\"g/cm**3\", [\"density\"], None)),\n        (\"x_vel\", (\"cm/s\", [\"velocity_x\"], r\"\\tilde{u}\")),\n        (\"y_vel\", (\"cm/s\", [\"velocity_y\"], r\"\\tilde{v}\")),\n        (\"z_vel\", (\"cm/s\", [\"velocity_z\"], r\"\\tilde{w}\")),\n        (\n            \"magvel\",\n            (\n                \"cm/s\",\n                [\"velocity_magnitude\"],\n                r\"|\\tilde{\\mathbf{U}} + w_0 \\mathbf{e}_r|\",\n            ),\n        ),\n        (\n            \"radial_velocity\",\n            (\"cm/s\", [\"radial_velocity\"], r\"\\mathbf{U}\\cdot \\mathbf{e}_r\"),\n        ),\n        (\"circum_velocity\", (\"cm/s\", [\"tangential_velocity\"], r\"U - U\\cdot e_r\")),\n        (\"tfromp\", (\"K\", [], \"T(\\\\rho,p,X)\")),\n        (\"tfromh\", (\"K\", [], \"T(\\\\rho,h,X)\")),\n        (\"Machnumber\", (\"\", [\"mach_number\"], \"M\")),\n        (\"S\", (\"1/s\", [], None)),\n        (\"ad_excess\", (\"\", [], r\"\\nabla - \\nabla_\\mathrm{ad}\")),\n        (\"deltaT\", (\"\", [], \"[T(\\\\rho,h,X) - T(\\\\rho,p,X)]/T(\\\\rho,h,X)\")),\n        (\"deltagamma\", (\"\", [], r\"\\Gamma_1 - \\overline{\\Gamma_1}\")),\n        (\"deltap\", (\"\", [], \"[p(\\\\rho,h,X) - p_0] / p_0\")),\n        (\"divw0\", (\"1/s\", [], r\"\\nabla \\cdot \\mathbf{w}_0\")),\n        # Specific entropy\n        (\"entropy\", (\"erg/(g*K)\", [\"entropy\"], \"s\")),\n        (\"entropypert\", (\"\", [], r\"[s - \\overline{s}] / \\overline{s}\")),\n        (\"enucdot\", (\"erg/(g*s)\", [], r\"\\dot{\\epsilon}_{nuc}\")),\n        (\"Hext\", (\"erg/(g*s)\", [], \"H_{ext}\")),\n        # Perturbational pressure grad\n        (\"gpi_x\", (\"dyne/cm**3\", [], r\"\\left(\\nabla\\pi\\right)_x\")),\n        (\"gpi_y\", (\"dyne/cm**3\", [], r\"\\left(\\nabla\\pi\\right)_y\")),\n        (\"gpi_z\", (\"dyne/cm**3\", [], r\"\\left(\\nabla\\pi\\right)_z\")),\n        (\"h\", (\"erg/g\", [], \"h\")),\n        (\"h0\", (\"erg/g\", [], \"h_0\")),\n        # Momentum cannot be computed because we need to include base and\n        # full state.\n        (\"momentum\", (\"g*cm/s\", [\"momentum_magnitude\"], r\"\\rho |\\mathbf{U}|\")),\n        (\"p0\", (\"erg/cm**3\", [], \"p_0\")),\n        (\"p0pluspi\", (\"erg/cm**3\", [], r\"p_0 + \\pi\")),\n        (\"pi\", (\"erg/cm**3\", [], r\"\\pi\")),\n        (\"pioverp0\", (\"\", [], r\"\\pi/p_0\")),\n        # Base state density\n        (\"rho0\", (\"g/cm**3\", [], \"\\\\rho_0\")),\n        (\"rhoh\", (\"erg/cm**3\", [\"enthalpy_density\"], \"(\\\\rho h)\")),\n        # Base state enthalpy density\n        (\"rhoh0\", (\"erg/cm**3\", [], \"(\\\\rho h)_0\")),\n        (\"rhohpert\", (\"erg/cm**3\", [], \"(\\\\rho h)^\\\\prime\")),\n        (\"rhopert\", (\"g/cm**3\", [], \"\\\\rho^\\\\prime\")),\n        (\"soundspeed\", (\"cm/s\", [\"sound_speed\"], None)),\n        (\"sponge\", (\"\", [], None)),\n        (\"tpert\", (\"K\", [], r\"T - \\overline{T}\")),\n        # Again, base state -- so we can't compute ourselves.\n        (\"vort\", (\"1/s\", [\"vorticity_magnitude\"], r\"|\\nabla\\times\\tilde{U}|\")),\n        # Base state\n        (\"w0_x\", (\"cm/s\", [], \"(w_0)_x\")),\n        (\"w0_y\", (\"cm/s\", [], \"(w_0)_y\")),\n        (\"w0_z\", (\"cm/s\", [], \"(w_0)_z\")),\n    )\n\n    def setup_fluid_fields(self):\n        unit_system = self.ds.unit_system\n        # pick the correct temperature field\n        tfromp = False\n        if \"use_tfromp\" in self.ds.parameters:\n            # original MAESTRO (F90) code\n            tfromp = self.ds.parameters[\"use_tfromp\"]\n        elif \"maestro.use_tfromp\" in self.ds.parameters:\n            # new MAESTROeX (C++) code\n            tfromp = self.ds.parameters[\"maestro.use_tfromp\"]\n\n        if tfromp:\n            self.alias(\n                (\"gas\", \"temperature\"),\n                (\"boxlib\", \"tfromp\"),\n                units=unit_system[\"temperature\"],\n            )\n        else:\n            self.alias(\n                (\"gas\", \"temperature\"),\n                (\"boxlib\", \"tfromh\"),\n                units=unit_system[\"temperature\"],\n            )\n\n        # Add X's and omegadots, units of 1/s\n        for _, field in self.ds.field_list:\n            if field.startswith(\"X(\"):\n                # We have a mass fraction\n                sub = Substance(field)\n                # Overwrite field to use nicer tex_label display_name\n                self.add_output_field(\n                    (\"boxlib\", field),\n                    sampling_type=\"cell\",\n                    units=\"\",\n                    display_name=rf\"X\\left({sub.to_tex()}\\right)\",\n                )\n                self.alias((\"gas\", f\"{sub}_fraction\"), (\"boxlib\", field), units=\"\")\n                func = _create_density_func((\"gas\", f\"{sub}_fraction\"))\n                self.add_field(\n                    name=(\"gas\", f\"{sub}_density\"),\n                    sampling_type=\"cell\",\n                    function=func,\n                    units=unit_system[\"density\"],\n                    display_name=rf\"\\rho {sub.to_tex()}\",\n                )\n\n            elif field.startswith(\"omegadot(\"):\n                sub = Substance(field)\n                display_name = rf\"\\dot{{\\omega}}\\left[{sub.to_tex()}\\right]\"\n                # Overwrite field to use nicer tex_label'ed display_name\n                self.add_output_field(\n                    (\"boxlib\", field),\n                    sampling_type=\"cell\",\n                    units=unit_system[\"frequency\"],\n                    display_name=display_name,\n                )\n                self.alias(\n                    (\"gas\", f\"{sub}_creation_rate\"),\n                    (\"boxlib\", field),\n                    units=unit_system[\"frequency\"],\n                )\n\n\nsubstance_expr_re = re.compile(r\"\\(([a-zA-Z][a-zA-Z0-9]*)\\)\")\nsubstance_elements_re = re.compile(r\"(?P<element>[a-zA-Z]+)(?P<digits>\\d*)\")\nSubstanceSpec: TypeAlias = list[tuple[str, int]]\n\n\nclass Substance:\n    def __init__(self, data: str) -> None:\n        if (m := substance_expr_re.search(data)) is None:\n            raise ValueError(f\"{data!r} doesn't match expected regular expression\")\n        sub_str = m.group()\n        constituents = substance_elements_re.findall(sub_str)\n\n        # 0 is used as a sentinel value to mark descriptive names\n        default_value = 1 if len(constituents) > 1 else 0\n        self._spec: SubstanceSpec = [\n            (name, int(count or default_value)) for (name, count) in constituents\n        ]\n\n    def get_spec(self) -> SubstanceSpec:\n        return self._spec.copy()\n\n    def is_isotope(self) -> bool:\n        return len(self._spec) == 1 and self._spec[0][1] > 0\n\n    def is_molecule(self) -> bool:\n        return len(self._spec) != 1\n\n    def is_descriptive_name(self) -> bool:\n        return len(self._spec) == 1 and self._spec[0][1] == 0\n\n    def __str__(self) -> str:\n        return \"\".join(\n            f\"{element}{count if count > 1 else ''}\" for element, count in self._spec\n        )\n\n    def _to_tex_isotope(self) -> str:\n        element, count = self._spec[0]\n        return rf\"^{{{count}}}{element}\"\n\n    def _to_tex_molecule(self) -> str:\n        return \"\".join(\n            rf\"{element}_{{{count if count > 1 else ''}}}\"\n            for element, count in self._spec\n        )\n\n    def _to_tex_descriptive(self) -> str:\n        return str(self)\n\n    def to_tex(self) -> str:\n        if self.is_isotope():\n            return self._to_tex_isotope()\n        elif self.is_molecule():\n            return self._to_tex_molecule()\n        elif self.is_descriptive_name():\n            return self._to_tex_descriptive()\n        else:\n            # should only be reachable in case of a regular expression defect\n            raise RuntimeError\n\n\ndef _create_density_func(field_name):\n    def _func(data):\n        return data[field_name] * data[\"gas\", \"density\"]\n\n    return _func\n"
  },
  {
    "path": "yt/frontends/amrex/io.py",
    "content": "import os\nfrom collections import defaultdict\n\nimport numpy as np\n\nfrom yt.frontends.chombo.io import parse_orion_sinks\nfrom yt.funcs import mylog\nfrom yt.geometry.selection_routines import GridSelector\nfrom yt.utilities.io_handler import BaseIOHandler\n\n\ndef _remove_raw(all_fields, raw_fields):\n    centered_fields = set(all_fields)\n    for raw in raw_fields:\n        centered_fields.discard(raw)\n    return list(centered_fields)\n\n\nclass IOHandlerBoxlib(BaseIOHandler):\n    _dataset_type = \"boxlib_native\"\n\n    def __init__(self, ds, *args, **kwargs):\n        super().__init__(ds)\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        if any((not (ftype == \"boxlib\" or ftype == \"raw\") for ftype, fname in fields)):\n            raise NotImplementedError\n        rv = {}\n        raw_fields = []\n        for field in fields:\n            if field[0] == \"raw\":\n                nodal_flag = self.ds.nodal_flags[field[1]]\n                num_nodes = 2 ** sum(nodal_flag)\n                rv[field] = np.empty((size, num_nodes), dtype=\"float64\")\n                raw_fields.append(field)\n            else:\n                rv[field] = np.empty(size, dtype=\"float64\")\n        centered_fields = _remove_raw(fields, raw_fields)\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s grids\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n        ind = 0\n        for chunk in chunks:\n            data = self._read_chunk_data(chunk, centered_fields)\n            for g in chunk.objs:\n                for field in fields:\n                    if field in centered_fields:\n                        ds = data[g.id].pop(field)\n                    else:\n                        ds = self._read_raw_field(g, field)\n                    nd = g.select(selector, ds, rv[field], ind)\n                ind += nd\n                data.pop(g.id)\n        return rv\n\n    def _read_raw_field(self, grid, field):\n        field_name = field[1]\n        base_dir = self.ds.index.raw_file\n\n        nghost = self.ds.index.raw_field_nghost[field_name]\n        box_list = self.ds.index.raw_field_map[field_name][0]\n        fn_list = self.ds.index.raw_field_map[field_name][1]\n        offset_list = self.ds.index.raw_field_map[field_name][2]\n\n        lev = grid.Level\n        filename = os.path.join(base_dir, f\"Level_{lev}\", fn_list[grid.id])\n        offset = offset_list[grid.id]\n        box = box_list[grid.id]\n\n        lo = box[0] - nghost\n        hi = box[1] + nghost\n        shape = hi - lo + 1\n        with open(filename, \"rb\") as f:\n            f.seek(offset)\n            f.readline()  # always skip the first line\n            arr = np.fromfile(f, \"float64\", np.prod(shape))\n            arr = arr.reshape(shape, order=\"F\")\n        return arr[\n            tuple(\n                slice(None) if (nghost[dim] == 0) else slice(nghost[dim], -nghost[dim])\n                for dim in range(self.ds.dimensionality)\n            )\n        ]\n\n    def _read_chunk_data(self, chunk, fields):\n        data = {}\n        grids_by_file = defaultdict(list)\n        if len(chunk.objs) == 0:\n            return data\n        for g in chunk.objs:\n            if g.filename is None:\n                continue\n            grids_by_file[g.filename].append(g)\n        dtype = self.ds.index._dtype\n        bpr = dtype.itemsize\n        for filename in grids_by_file:\n            grids = grids_by_file[filename]\n            grids.sort(key=lambda a: a._offset)\n            f = open(filename, \"rb\")\n            for grid in grids:\n                data[grid.id] = {}\n                local_offset = grid._get_offset(f) - f.tell()\n                count = grid.ActiveDimensions.prod()\n                size = count * bpr\n                for field in self.ds.index.field_order:\n                    if field in fields:\n                        # We read it ...\n                        f.seek(local_offset, os.SEEK_CUR)\n                        v = np.fromfile(f, dtype=dtype, count=count)\n                        v = v.reshape(grid.ActiveDimensions, order=\"F\")\n                        data[grid.id][field] = v\n                        local_offset = 0\n                    else:\n                        local_offset += size\n            f.close()\n        return data\n\n    def _read_particle_coords(self, chunks, ptf):\n        yield from (\n            (ptype, xyz, 0.0)\n            for ptype, xyz in self._read_particle_fields(chunks, ptf, None)\n        )\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        for chunk in chunks:  # These should be organized by grid filename\n            for g in chunk.objs:\n                for ptype, field_list in sorted(ptf.items()):\n                    npart = g._pdata[ptype][\"NumberOfParticles\"]\n                    if npart == 0:\n                        continue\n\n                    fn = g._pdata[ptype][\"particle_filename\"]\n                    offset = g._pdata[ptype][\"offset\"]\n                    pheader = self.ds.index.particle_headers[ptype]\n\n                    with open(fn, \"rb\") as f:\n                        # read in the position fields for selection\n                        f.seek(offset + pheader.particle_int_dtype.itemsize * npart)\n                        rdata = np.fromfile(\n                            f, pheader.real_type, pheader.num_real * npart\n                        )\n\n                        # Allow reading particles in 1, 2, and 3 dimensions,\n                        # setting the appropriate default for unused dimensions.\n                        pos = []\n                        for idim in [1, 2, 3]:\n                            if g.ds.dimensionality >= idim:\n                                pos.append(\n                                    np.asarray(\n                                        rdata[idim - 1 :: pheader.num_real],\n                                        dtype=np.float64,\n                                    )\n                                )\n                            else:\n                                center = 0.5 * (\n                                    g.LeftEdge[idim - 1] + g.RightEdge[idim - 1]\n                                )\n                                pos.append(np.full(npart, center, dtype=np.float64))\n                        x, y, z = pos\n\n                        if selector is None:\n                            # This only ever happens if the call is made from\n                            # _read_particle_coords.\n                            yield ptype, (x, y, z)\n                            continue\n                        mask = selector.select_points(x, y, z, 0.0)\n                        if mask is None:\n                            continue\n                        for field in field_list:\n                            # handle the case that this is an integer field\n                            int_fnames = [\n                                fname for _, fname in pheader.known_int_fields\n                            ]\n                            if field in int_fnames:\n                                ind = int_fnames.index(field)\n                                f.seek(offset)\n                                idata = np.fromfile(\n                                    f, pheader.int_type, pheader.num_int * npart\n                                )\n                                data = np.asarray(\n                                    idata[ind :: pheader.num_int], dtype=np.float64\n                                )\n                                yield (ptype, field), data[mask].flatten()\n\n                            # handle case that this is a real field\n                            real_fnames = [\n                                fname for _, fname in pheader.known_real_fields\n                            ]\n                            if field in real_fnames:\n                                ind = real_fnames.index(field)\n                                data = np.asarray(\n                                    rdata[ind :: pheader.num_real], dtype=np.float64\n                                )\n                                yield (ptype, field), data[mask].flatten()\n\n\nclass IOHandlerOrion(IOHandlerBoxlib):\n    _dataset_type = \"orion_native\"\n\n    _particle_filename = None\n\n    @property\n    def particle_filename(self):\n        fn = os.path.join(self.ds.output_dir, \"StarParticles\")\n        if not os.path.exists(fn):\n            fn = os.path.join(self.ds.output_dir, \"SinkParticles\")\n        self._particle_filename = fn\n        return self._particle_filename\n\n    _particle_field_index = None\n\n    @property\n    def particle_field_index(self):\n        index = parse_orion_sinks(self.particle_filename)\n\n        self._particle_field_index = index\n        return self._particle_field_index\n\n    def _read_particle_selection(self, chunks, selector, fields):\n        rv = {}\n        chunks = list(chunks)\n\n        if isinstance(selector, GridSelector):\n            if not (len(chunks) == len(chunks[0].objs) == 1):\n                raise RuntimeError\n\n            grid = chunks[0].objs[0]\n\n            for ftype, fname in fields:\n                rv[ftype, fname] = self._read_particles(grid, fname)\n\n            return rv\n\n        rv = {f: np.array([]) for f in fields}\n        for chunk in chunks:\n            for grid in chunk.objs:\n                for ftype, fname in fields:\n                    data = self._read_particles(grid, fname)\n                    rv[ftype, fname] = np.concatenate((data, rv[ftype, fname]))\n        return rv\n\n    def _read_particles(self, grid, field):\n        \"\"\"\n        parses the Orion Star Particle text files\n\n        \"\"\"\n\n        particles = []\n\n        if grid.NumberOfParticles == 0:\n            return np.array(particles)\n\n        def read(line, field):\n            entry = line.strip().split(\" \")[self.particle_field_index[field]]\n            return float(entry)\n\n        try:\n            lines = self._cached_lines\n            for num in grid._particle_line_numbers:\n                line = lines[num]\n                particles.append(read(line, field))\n            return np.array(particles)\n        except AttributeError:\n            fn = self.particle_filename\n            with open(fn) as f:\n                lines = f.readlines()\n                self._cached_lines = lines\n                for num in grid._particle_line_numbers:\n                    line = lines[num]\n                    particles.append(read(line, field))\n            return np.array(particles)\n"
  },
  {
    "path": "yt/frontends/amrex/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/amrex/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/amrex/tests/test_field_parsing.py",
    "content": "import pytest\n\nfrom yt.frontends.amrex.fields import Substance\n\n\n@pytest.mark.parametrize(\n    \"data, expected\",\n    [\n        pytest.param(\"X(He5)\", [(\"He\", 5)], id=\"isotope_1\"),\n        pytest.param(\"X(C12)\", [(\"C\", 12)], id=\"isotope_2\"),\n        pytest.param(\"X(A1B2C3)\", [(\"A\", 1), (\"B\", 2), (\"C\", 3)], id=\"molecule_1\"),\n        pytest.param(\"X(C12H24)\", [(\"C\", 12), (\"H\", 24)], id=\"molecule_2\"),\n        pytest.param(\"X(H2O)\", [(\"H\", 2), (\"O\", 1)], id=\"molecule_3\"),\n        pytest.param(\"X(ash)\", [(\"ash\", 0)], id=\"descriptive_name\"),\n    ],\n)\ndef test_Substance_spec(data, expected):\n    assert Substance(data)._spec == expected\n\n\n@pytest.mark.parametrize(\n    \"data, expected_type\",\n    [\n        pytest.param(\"X(He5)\", \"isotope\", id=\"isotope_1\"),\n        pytest.param(\"X(C12)\", \"isotope\", id=\"isotope_2\"),\n        pytest.param(\"X(A1B2C3)\", \"molecule\", id=\"molecule_1\"),\n        pytest.param(\"X(C12H24)\", \"molecule\", id=\"molecule_2\"),\n        pytest.param(\"X(H2O)\", \"molecule\", id=\"molecule_3\"),\n        pytest.param(\"X(ash)\", \"descriptive_name\", id=\"descriptive_name\"),\n    ],\n)\ndef test_Substance_type(data, expected_type):\n    sub = Substance(data)\n    assert getattr(sub, f\"is_{expected_type}\")()\n\n\n@pytest.mark.parametrize(\n    \"data, expected_str, expected_tex\",\n    [\n        pytest.param(\"X(He5)\", \"He5\", \"^{5}He\", id=\"isotope_1\"),\n        pytest.param(\"X(C12)\", \"C12\", \"^{12}C\", id=\"isotope_2\"),\n        pytest.param(\"X(A1B2C3)\", \"AB2C3\", \"A_{}B_{2}C_{3}\", id=\"molecule_1\"),\n        pytest.param(\"X(C12H24)\", \"C12H24\", \"C_{12}H_{24}\", id=\"molecule_2\"),\n        pytest.param(\"X(H2O)\", \"H2O\", \"H_{2}O_{}\", id=\"molecule_2\"),\n        pytest.param(\"X(ash)\", \"ash\", \"ash\", id=\"descriptive_name\"),\n    ],\n)\ndef test_Substance_to_str(data, expected_str, expected_tex):\n    sub = Substance(data)\n    assert str(sub) == expected_str\n    assert sub.to_tex() == expected_tex\n"
  },
  {
    "path": "yt/frontends/amrex/tests/test_outputs.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_allclose, assert_equal\n\nfrom yt.frontends.amrex.api import (\n    AMReXDataset,\n    CastroDataset,\n    MaestroDataset,\n    NyxDataset,\n    OrionDataset,\n    WarpXDataset,\n)\nfrom yt.loaders import load\nfrom yt.testing import (\n    disable_dataset_cache,\n    requires_file,\n    units_override_check,\n)\nfrom yt.utilities.answer_testing.framework import (\n    GridValuesTest,\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\n# We don't do anything needing ghost zone generation right now, because these\n# are non-periodic datasets.\n_orion_fields = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_magnitude\"),\n)\n_nyx_fields = (\n    (\"boxlib\", \"Ne\"),\n    (\"boxlib\", \"Temp\"),\n    (\"boxlib\", \"particle_mass_density\"),\n)\n_warpx_fields = ((\"mesh\", \"Ex\"), (\"mesh\", \"By\"), (\"mesh\", \"jz\"))\n_castro_fields = (\n    (\"boxlib\", \"Temp\"),\n    (\"gas\", \"density\"),\n    (\"boxlib\", \"particle_count\"),\n)\n\nradadvect = \"RadAdvect/plt00000\"\n\n\n@requires_ds(radadvect)\ndef test_radadvect():\n    ds = data_dir_load(radadvect)\n    assert_equal(str(ds), \"plt00000\")\n    for test in small_patch_amr(ds, _orion_fields):\n        test_radadvect.__name__ = test.description\n        yield test\n\n\nrt = \"RadTube/plt00500\"\n\n\n@requires_ds(rt)\ndef test_radtube():\n    ds = data_dir_load(rt)\n    assert_equal(str(ds), \"plt00500\")\n    for test in small_patch_amr(ds, _orion_fields):\n        test_radtube.__name__ = test.description\n        yield test\n\n\nstar = \"StarParticles/plrd01000\"\n\n\n@requires_ds(star)\ndef test_star():\n    ds = data_dir_load(star)\n    assert_equal(str(ds), \"plrd01000\")\n    for test in small_patch_amr(ds, _orion_fields):\n        test_star.__name__ = test.description\n        yield test\n\n\nLyA = \"Nyx_LyA/plt00000\"\n\n\n@requires_ds(LyA)\ndef test_LyA():\n    ds = data_dir_load(LyA)\n    assert_equal(str(ds), \"plt00000\")\n    for test in small_patch_amr(\n        ds, _nyx_fields, input_center=\"c\", input_weight=(\"boxlib\", \"Ne\")\n    ):\n        test_LyA.__name__ = test.description\n        yield test\n\n\n@requires_file(LyA)\ndef test_nyx_particle_io():\n    ds = data_dir_load(LyA)\n\n    grid = ds.index.grids[0]\n    npart_grid_0 = 7908  # read directly from the header\n    assert_equal(grid[\"all\", \"particle_position_x\"].size, npart_grid_0)\n    assert_equal(grid[\"DM\", \"particle_position_y\"].size, npart_grid_0)\n    assert_equal(grid[\"all\", \"particle_position_z\"].size, npart_grid_0)\n\n    ad = ds.all_data()\n    npart = 32768  # read directly from the header\n    assert_equal(ad[\"all\", \"particle_velocity_x\"].size, npart)\n    assert_equal(ad[\"DM\", \"particle_velocity_y\"].size, npart)\n    assert_equal(ad[\"all\", \"particle_velocity_z\"].size, npart)\n\n    assert np.all(ad[\"all\", \"particle_mass\"] == ad[\"all\", \"particle_mass\"][0])\n\n    left_edge = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    right_edge = ds.arr([4.0, 4.0, 4.0], \"code_length\")\n    center = 0.5 * (left_edge + right_edge)\n\n    reg = ds.region(center, left_edge, right_edge)\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_x\"] <= right_edge[0],\n            reg[\"all\", \"particle_position_x\"] >= left_edge[0],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_y\"] <= right_edge[1],\n            reg[\"all\", \"particle_position_y\"] >= left_edge[1],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_z\"] <= right_edge[2],\n            reg[\"all\", \"particle_position_z\"] >= left_edge[2],\n        )\n    )\n\n\nRT_particles = \"RT_particles/plt00050\"\n\n\n@requires_ds(RT_particles)\ndef test_RT_particles():\n    ds = data_dir_load(RT_particles)\n    assert_equal(str(ds), \"plt00050\")\n    for test in small_patch_amr(ds, _castro_fields):\n        test_RT_particles.__name__ = test.description\n        yield test\n\n\n@requires_file(RT_particles)\ndef test_castro_particle_io():\n    ds = data_dir_load(RT_particles)\n\n    grid = ds.index.grids[2]\n    npart_grid_2 = 49  # read directly from the header\n    assert_equal(grid[\"all\", \"particle_position_x\"].size, npart_grid_2)\n    assert_equal(grid[\"Tracer\", \"particle_position_y\"].size, npart_grid_2)\n    assert_equal(grid[\"all\", \"particle_position_y\"].size, npart_grid_2)\n\n    ad = ds.all_data()\n    npart = 49  # read directly from the header\n    assert_equal(ad[\"all\", \"particle_velocity_x\"].size, npart)\n    assert_equal(ad[\"Tracer\", \"particle_velocity_y\"].size, npart)\n    assert_equal(ad[\"all\", \"particle_velocity_y\"].size, npart)\n\n    left_edge = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    right_edge = ds.arr([0.25, 1.0, 1.0], \"code_length\")\n    center = 0.5 * (left_edge + right_edge)\n\n    reg = ds.region(center, left_edge, right_edge)\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_x\"] <= right_edge[0],\n            reg[\"all\", \"particle_position_x\"] >= left_edge[0],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_y\"] <= right_edge[1],\n            reg[\"all\", \"particle_position_y\"] >= left_edge[1],\n        )\n    )\n\n\nlangmuir = \"LangmuirWave/plt00020_v2\"\n\n\n@requires_ds(langmuir)\ndef test_langmuir():\n    ds = data_dir_load(langmuir)\n    assert_equal(str(ds), \"plt00020_v2\")\n    for test in small_patch_amr(\n        ds, _warpx_fields, input_center=\"c\", input_weight=(\"mesh\", \"Ex\")\n    ):\n        test_langmuir.__name__ = test.description\n        yield test\n\n\nplasma = \"PlasmaAcceleration/plt00030_v2\"\n\n\n@requires_ds(plasma)\ndef test_plasma():\n    ds = data_dir_load(plasma)\n    assert_equal(str(ds), \"plt00030_v2\")\n    for test in small_patch_amr(\n        ds, _warpx_fields, input_center=\"c\", input_weight=(\"mesh\", \"Ex\")\n    ):\n        test_plasma.__name__ = test.description\n        yield test\n\n\nbeam = \"GaussianBeam/plt03008\"\n\n\n@requires_ds(beam)\ndef test_beam():\n    ds = data_dir_load(beam)\n    assert_equal(str(ds), \"plt03008\")\n    for param in (\"number of boxes\", \"maximum zones\"):\n        # PR 2807\n        # these parameters are only populated if the config file attached to this\n        # dataset is read correctly\n        assert param in ds.parameters\n    for test in small_patch_amr(\n        ds, _warpx_fields, input_center=\"c\", input_weight=(\"mesh\", \"Ex\")\n    ):\n        test_beam.__name__ = test.description\n        yield test\n\n\n@requires_file(plasma)\ndef test_warpx_particle_io():\n    ds = data_dir_load(plasma)\n    grid = ds.index.grids[0]\n\n    # read directly from the header\n    npart0_grid_0 = 344\n    npart1_grid_0 = 69632\n\n    assert_equal(grid[\"particle0\", \"particle_position_x\"].size, npart0_grid_0)\n    assert_equal(grid[\"particle1\", \"particle_position_y\"].size, npart1_grid_0)\n    assert_equal(grid[\"all\", \"particle_position_z\"].size, npart0_grid_0 + npart1_grid_0)\n\n    # read directly from the header\n    npart0 = 1360\n    npart1 = 802816\n    ad = ds.all_data()\n    assert_equal(ad[\"particle0\", \"particle_velocity_x\"].size, npart0)\n    assert_equal(ad[\"particle1\", \"particle_velocity_y\"].size, npart1)\n    assert_equal(ad[\"all\", \"particle_velocity_z\"].size, npart0 + npart1)\n\n    np.all(ad[\"particle1\", \"particle_mass\"] == ad[\"particle1\", \"particle_mass\"][0])\n    np.all(ad[\"particle0\", \"particle_mass\"] == ad[\"particle0\", \"particle_mass\"][0])\n\n    left_edge = ds.arr([-7.5e-5, -7.5e-5, -7.5e-5], \"code_length\")\n    right_edge = ds.arr([2.5e-5, 2.5e-5, 2.5e-5], \"code_length\")\n    center = 0.5 * (left_edge + right_edge)\n\n    reg = ds.region(center, left_edge, right_edge)\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_x\"] <= right_edge[0],\n            reg[\"all\", \"particle_position_x\"] >= left_edge[0],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_y\"] <= right_edge[1],\n            reg[\"all\", \"particle_position_y\"] >= left_edge[1],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_z\"] <= right_edge[2],\n            reg[\"all\", \"particle_position_z\"] >= left_edge[2],\n        )\n    )\n\n\n_raw_fields = [(\"raw\", \"Bx\"), (\"raw\", \"Ey\"), (\"raw\", \"jz\")]\n\nlaser = \"Laser/plt00015\"\n\n\n@requires_ds(laser)\ndef test_raw_fields():\n    for field in _raw_fields:\n        yield GridValuesTest(laser, field)\n\n\n@requires_file(rt)\ndef test_OrionDataset():\n    assert isinstance(data_dir_load(rt), OrionDataset)\n\n\n@requires_file(LyA)\ndef test_NyxDataset():\n    assert isinstance(data_dir_load(LyA), NyxDataset)\n\n\n@requires_file(\"nyx_small/nyx_small_00000\")\ndef test_NyxDataset_2():\n    assert isinstance(data_dir_load(\"nyx_small/nyx_small_00000\"), NyxDataset)\n\n\n@requires_file(RT_particles)\ndef test_CastroDataset():\n    assert isinstance(data_dir_load(RT_particles), CastroDataset)\n\n\n@requires_file(\"castro_sod_x_plt00036\")\ndef test_CastroDataset_2():\n    assert isinstance(data_dir_load(\"castro_sod_x_plt00036\"), CastroDataset)\n\n\n@requires_file(\"castro_sedov_1d_cyl_plt00150\")\ndef test_CastroDataset_3():\n    assert isinstance(data_dir_load(\"castro_sedov_1d_cyl_plt00150\"), CastroDataset)\n\n\n@requires_file(plasma)\ndef test_WarpXDataset():\n    assert isinstance(data_dir_load(plasma), WarpXDataset)\n\n\n@disable_dataset_cache\n@requires_file(plasma)\ndef test_magnetic_units():\n    ds1 = load(plasma)\n    assert_allclose(ds1.magnetic_unit.value, 1.0)\n    assert str(ds1.magnetic_unit.units) == \"T\"\n    mag_unit1 = ds1.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mag_unit1.value, 1.0)\n    assert str(mag_unit1.units) == \"code_magnetic\"\n    ds2 = load(plasma, unit_system=\"cgs\")\n    assert_allclose(ds2.magnetic_unit.value, 1.0e4)\n    assert str(ds2.magnetic_unit.units) == \"G\"\n    mag_unit2 = ds2.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mag_unit2.value, 1.0)\n    assert str(mag_unit2.units) == \"code_magnetic\"\n\n\n@requires_ds(laser)\ndef test_WarpXDataset_2():\n    assert isinstance(data_dir_load(laser), WarpXDataset)\n\n\n@requires_file(\"plt.Cavity00010\")\ndef test_AMReXDataset():\n    ds = data_dir_load(\"plt.Cavity00010\", kwargs={\"cparam_filename\": \"inputs\"})\n    assert isinstance(ds, AMReXDataset)\n\n\n@requires_file(rt)\ndef test_units_override():\n    units_override_check(rt)\n\n\nnyx_no_particles = \"nyx_sedov_plt00086\"\n\n\n@requires_file(nyx_no_particles)\ndef test_nyx_no_part():\n    assert isinstance(data_dir_load(nyx_no_particles), NyxDataset)\n\n    fields = sorted(\n        [\n            (\"boxlib\", \"H\"),\n            (\"boxlib\", \"He\"),\n            (\"boxlib\", \"MachNumber\"),\n            (\"boxlib\", \"Ne\"),\n            (\"boxlib\", \"Rank\"),\n            (\"boxlib\", \"StateErr\"),\n            (\"boxlib\", \"Temp\"),\n            (\"boxlib\", \"X(H)\"),\n            (\"boxlib\", \"X(He)\"),\n            (\"boxlib\", \"density\"),\n            (\"boxlib\", \"divu\"),\n            (\"boxlib\", \"eint_E\"),\n            (\"boxlib\", \"eint_e\"),\n            (\"boxlib\", \"entropy\"),\n            (\"boxlib\", \"forcex\"),\n            (\"boxlib\", \"forcey\"),\n            (\"boxlib\", \"forcez\"),\n            (\"boxlib\", \"kineng\"),\n            (\"boxlib\", \"logden\"),\n            (\"boxlib\", \"magmom\"),\n            (\"boxlib\", \"magvel\"),\n            (\"boxlib\", \"magvort\"),\n            (\"boxlib\", \"pressure\"),\n            (\"boxlib\", \"rho_E\"),\n            (\"boxlib\", \"rho_H\"),\n            (\"boxlib\", \"rho_He\"),\n            (\"boxlib\", \"rho_e\"),\n            (\"boxlib\", \"soundspeed\"),\n            (\"boxlib\", \"x_velocity\"),\n            (\"boxlib\", \"xmom\"),\n            (\"boxlib\", \"y_velocity\"),\n            (\"boxlib\", \"ymom\"),\n            (\"boxlib\", \"z_velocity\"),\n            (\"boxlib\", \"zmom\"),\n        ]\n    )\n\n    ds = data_dir_load(nyx_no_particles)\n    assert_equal(sorted(ds.field_list), fields)\n\n\nmsubch = \"maestro_subCh_plt00248\"\n\n\n@requires_file(msubch)\ndef test_maestro_parameters():\n    assert isinstance(data_dir_load(msubch), MaestroDataset)\n    ds = data_dir_load(msubch)\n\n    # Check a string parameter\n    assert ds.parameters[\"plot_base_name\"] == \"subCh_hot_baserun_plt\"\n    assert type(ds.parameters[\"plot_base_name\"]) is str  # noqa: E721\n\n    # Check boolean parameters: T or F\n    assert not ds.parameters[\"use_thermal_diffusion\"]\n    assert type(ds.parameters[\"use_thermal_diffusion\"]) is bool  # noqa: E721\n\n    assert ds.parameters[\"do_burning\"]\n    assert type(ds.parameters[\"do_burning\"]) is bool  # noqa: E721\n\n    # Check a float parameter with a decimal point\n    assert ds.parameters[\"sponge_kappa\"] == float(\"10.00000000\")\n    assert type(ds.parameters[\"sponge_kappa\"]) is float  # noqa: E721\n\n    # Check a float parameter with E exponent notation\n    assert ds.parameters[\"small_dt\"] == float(\"0.1000000000E-09\")\n\n    # Check an int parameter\n    assert ds.parameters[\"s0_interp_type\"] == 3\n    assert type(ds.parameters[\"s0_interp_type\"]) is int  # noqa: E721\n\n\n# test loading non-Cartesian coordinate systems in different dimensionalities\n\n\ndef check_coordsys_data(ds):\n    # check that level_dds is consistent with domain_width\n    assert_allclose(\n        ds.index.level_dds[0] * ds.domain_dimensions,\n        ds.domain_width.to_value(\"code_length\"),\n        rtol=1e-12,\n        atol=0.0,\n    )\n\n    # check that we get the expected number of data points when selecting the\n    # entire domain\n    expected_size = sum(np.count_nonzero(g.child_mask) for g in ds.index.grids)\n    ad = ds.all_data()\n    assert ad[\"boxlib\", \"Temp\"].size == expected_size\n\n\ncyl_1d = \"castro_sedov_1d_cyl_plt00150\"\ncyl_2d = \"castro_sedov_2d_sph_in_cyl_plt00130\"\nsph_1d = \"sedov_1d_sph_plt00120\"\nsph_2d = \"xrb_spherical_smallplt00010\"\n\n\n@requires_file(cyl_1d)\ndef test_coordsys_1d_cylindrical():\n    ds = data_dir_load(cyl_1d)\n    assert ds.geometry == \"cylindrical\"\n    assert ds.dimensionality == 1\n    check_coordsys_data(ds)\n\n\n@requires_file(cyl_2d)\ndef test_coordsys_2d_cylindrical():\n    ds = data_dir_load(cyl_2d)\n    assert ds.geometry == \"cylindrical\"\n    assert ds.dimensionality == 2\n    check_coordsys_data(ds)\n\n\n@requires_file(sph_1d)\ndef test_coordsys_1d_spherical():\n    ds = data_dir_load(sph_1d)\n    assert ds.geometry == \"spherical\"\n    assert ds.dimensionality == 1\n    check_coordsys_data(ds)\n\n\n@requires_file(sph_2d)\ndef test_coordsys_2d_spherical():\n    ds = data_dir_load(sph_2d)\n    assert ds.geometry == \"spherical\"\n    assert ds.dimensionality == 2\n    check_coordsys_data(ds)\n"
  },
  {
    "path": "yt/frontends/amrvac/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/amrvac/api.py",
    "content": "\"\"\"\nfrontend API: a submodule that exposes user-facing defs and classes\n\"\"\"\n\nfrom .data_structures import AMRVACDataset, AMRVACGrid, AMRVACHierarchy\nfrom .fields import AMRVACFieldInfo\nfrom .io import AMRVACIOHandler, read_amrvac_namelist\n"
  },
  {
    "path": "yt/frontends/amrvac/data_structures.py",
    "content": "\"\"\"\nAMRVAC data structures\n\n\n\n\"\"\"\n\nimport os\nimport struct\nimport warnings\nimport weakref\nfrom pathlib import Path\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt.config import ytcfg\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.physical_constants import boltzmann_constant_cgs as kb_cgs\n\nfrom .datfile_utils import get_header, get_tree_info\nfrom .fields import AMRVACFieldInfo\nfrom .io import read_amrvac_namelist\n\n\ndef _parse_geometry(geometry_tag: str) -> Geometry:\n    \"\"\"Translate AMRVAC's geometry tag to yt's format.\n\n    Parameters\n    ----------\n    geometry_tag : str\n        A geometry tag as read from AMRVAC's datfile from v5.\n\n    Returns\n    -------\n    geometry_yt : Geometry\n        An enum member of the yt.geometry.geometry_enum.Geometry class\n\n    Examples\n    --------\n    >>> _parse_geometry(\"Polar_2.5D\")\n     <Geometry.POLAR: 'polar'>\n    >>> _parse_geometry(\"Cartesian_2.5D\")\n    <Geometry.CARTESIAN: 'cartesian'>\n\n    \"\"\"\n    geometry_str, _, _dimension_str = geometry_tag.partition(\"_\")\n    return Geometry(geometry_str.lower())\n\n\nclass AMRVACGrid(AMRGridPatch):\n    \"\"\"A class to populate AMRVACHierarchy.grids, setting parent/children relations.\"\"\"\n\n    _id_offset = 0\n\n    def __init__(self, id, index, level):\n        # <level> should use yt's convention (start from 0)\n        super().__init__(id, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n\n    def get_global_startindex(self):\n        \"\"\"Refresh and retrieve the starting index for each dimension at current level.\n\n        Returns\n        -------\n        self.start_index : int\n        \"\"\"\n        start_index = (self.LeftEdge - self.ds.domain_left_edge) / self.dds\n        self.start_index = np.rint(start_index).astype(\"int64\").ravel()\n        return self.start_index\n\n    def retrieve_ghost_zones(self, n_zones, fields, all_levels=False, smoothed=False):\n        if smoothed:\n            warnings.warn(\n                \"ghost-zones interpolation/smoothing is not \"\n                \"currently supported for AMRVAC data.\",\n                category=RuntimeWarning,\n                stacklevel=2,\n            )\n            smoothed = False\n        return super().retrieve_ghost_zones(\n            n_zones, fields, all_levels=all_levels, smoothed=smoothed\n        )\n\n\nclass AMRVACHierarchy(GridIndex):\n    grid = AMRVACGrid\n\n    def __init__(self, ds, dataset_type=\"amrvac\"):\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        # the index file *is* the datfile\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        self.float_type = np.float64\n\n        super().__init__(ds, dataset_type)\n\n    def _detect_output_fields(self):\n        \"\"\"Parse field names from the header, as stored in self.dataset.parameters\"\"\"\n        self.field_list = [\n            (self.dataset_type, f) for f in self.dataset.parameters[\"w_names\"]\n        ]\n\n    def _count_grids(self):\n        \"\"\"Set self.num_grids from datfile header.\"\"\"\n        self.num_grids = self.dataset.parameters[\"nleafs\"]\n\n    def _parse_index(self):\n        \"\"\"Populate self.grid_* attributes from tree info from datfile header.\"\"\"\n        with open(self.index_filename, \"rb\") as istream:\n            vaclevels, morton_indices, block_offsets = get_tree_info(istream)\n            assert (\n                len(vaclevels)\n                == len(morton_indices)\n                == len(block_offsets)\n                == self.num_grids\n            )\n\n        self.block_offsets = block_offsets\n        # YT uses 0-based grid indexing:\n        # lowest level = 0, while AMRVAC uses 1 for lowest level\n        ytlevels = np.array(vaclevels, dtype=\"int32\") - 1\n        self.grid_levels.flat[:] = ytlevels\n        self.min_level = np.min(ytlevels)\n        self.max_level = np.max(ytlevels)\n        assert self.max_level == self.dataset.parameters[\"levmax\"] - 1\n\n        # some aliases for left/right edges computation in the coming loop\n        domain_width = self.dataset.parameters[\"xmax\"] - self.dataset.parameters[\"xmin\"]\n        block_nx = self.dataset.parameters[\"block_nx\"]\n        xmin = self.dataset.parameters[\"xmin\"]\n        dx0 = (\n            domain_width / self.dataset.parameters[\"domain_nx\"]\n        )  # dx at coarsest grid level (YT level 0)\n        dim = self.dataset.dimensionality\n\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n        for igrid, (ytlevel, morton_index) in enumerate(\n            zip(ytlevels, morton_indices, strict=True)\n        ):\n            dx = dx0 / self.dataset.refine_by**ytlevel\n            left_edge = xmin + (morton_index - 1) * block_nx * dx\n\n            # edges and dimensions are filled in a dimensionality-agnostic way\n            self.grid_left_edge[igrid, :dim] = left_edge\n            self.grid_right_edge[igrid, :dim] = left_edge + block_nx * dx\n            self.grid_dimensions[igrid, :dim] = block_nx\n            self.grids[igrid] = self.grid(igrid, self, ytlevels[igrid])\n\n    def _populate_grid_objects(self):\n        # required method\n        for g in self.grids:\n            g._prepare_grid()\n            g._setup_dx()\n\n\nclass AMRVACDataset(Dataset):\n    _index_class = AMRVACHierarchy\n    _field_info_class = AMRVACFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"amrvac\",\n        units_override=None,\n        unit_system=\"cgs\",\n        geometry_override=None,\n        parfiles=None,\n        default_species_fields=None,\n    ):\n        \"\"\"Instantiate AMRVACDataset.\n\n        Parameters\n        ----------\n        filename : str\n            Path to a datfile.\n\n        dataset_type : str, optional\n            This should always be 'amrvac'.\n\n        units_override : dict, optional\n            A dictionary of physical normalisation factors to interpret on disk data.\n\n        unit_system : str, optional\n            Either \"cgs\" (default), \"mks\" or \"code\"\n\n        geometry_override : str, optional\n            A geometry flag formatted either according to either\n            AMRVAC or yt standards.\n            When this parameter is passed along with v5 or more newer datfiles,\n            will precede over their internal \"geometry\" tag.\n\n        parfiles : str or list, optional\n            One or more parfiles to be passed to\n            yt.frontends.amrvac.read_amrvac_parfiles()\n\n        \"\"\"\n        # note: geometry_override and parfiles are specific to this frontend\n\n        self._geometry_override = geometry_override\n        self._parfiles = []\n\n        if parfiles is not None:\n            parfiles = list(always_iterable(parfiles))\n            ppf = Path(parfiles[0])\n            if not ppf.is_absolute() and Path(filename).resolve().is_relative_to(\n                ytcfg[\"yt\", \"test_data_dir\"]\n            ):\n                mylog.debug(\n                    \"Looks like %s is relative to your test_data_dir. Assuming this is also true for parfiles.\",\n                    filename,\n                )\n                parfiles = [Path(ytcfg[\"yt\", \"test_data_dir\"]) / pf for pf in parfiles]\n            self._parfiles = parfiles\n\n        super().__init__(\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n        namelist = None\n        namelist_gamma = None\n        c_adiab = None\n        e_is_internal = None\n\n        if parfiles is not None:\n            namelist = read_amrvac_namelist(parfiles)\n            if \"hd_list\" in namelist:\n                c_adiab = namelist[\"hd_list\"].get(\"hd_adiab\", 1.0)\n                namelist_gamma = namelist[\"hd_list\"].get(\"hd_gamma\")\n            elif \"mhd_list\" in namelist:\n                c_adiab = namelist[\"mhd_list\"].get(\"mhd_adiab\", 1.0)\n                namelist_gamma = namelist[\"mhd_list\"].get(\"mhd_gamma\")\n\n            if namelist_gamma is not None and self.gamma != namelist_gamma:\n                mylog.error(\n                    \"Inconsistent values in gamma: datfile %s, parfiles %s\",\n                    self.gamma,\n                    namelist_gamma,\n                )\n\n            if \"method_list\" in namelist:\n                e_is_internal = namelist[\"method_list\"].get(\"solve_internal_e\", False)\n\n        if c_adiab is not None:\n            # this complicated unit is required for the adiabatic equation\n            # of state to make physical sense\n            c_adiab *= (\n                self.mass_unit ** (1 - self.gamma)\n                * self.length_unit ** (2 + 3 * (self.gamma - 1))\n                / self.time_unit**2\n            )\n\n        self.namelist = namelist\n        self._c_adiab = c_adiab\n        self._e_is_internal = e_is_internal\n\n        self.fluid_types += (\"amrvac\",)\n        # refinement factor between a grid and its subgrid\n        self.refine_by = 2\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        \"\"\"At load time, check whether data is recognized as AMRVAC formatted.\"\"\"\n        validation = False\n        if filename.endswith(\".dat\"):\n            try:\n                with open(filename, mode=\"rb\") as istream:\n                    fmt = \"=i\"\n                    [datfile_version] = struct.unpack(\n                        fmt, istream.read(struct.calcsize(fmt))\n                    )\n                    if 3 <= datfile_version < 6:\n                        fmt = \"=ii\"\n                        offset_tree, offset_blocks = struct.unpack(\n                            fmt, istream.read(struct.calcsize(fmt))\n                        )\n                        istream.seek(0, 2)\n                        file_size = istream.tell()\n                        validation = (\n                            offset_tree < file_size and offset_blocks < file_size\n                        )\n            except Exception:\n                pass\n        return validation\n\n    def _parse_parameter_file(self):\n        \"\"\"Parse input datfile's header. Apply geometry_override if specified.\"\"\"\n        # required method\n\n        # populate self.parameters with header data\n        with open(self.parameter_filename, \"rb\") as istream:\n            self.parameters.update(get_header(istream))\n\n        self.current_time = self.parameters[\"time\"]\n        self.dimensionality = self.parameters[\"ndim\"]\n\n        # force 3D for this definition\n        dd = np.ones(3, dtype=\"int64\")\n        dd[: self.dimensionality] = self.parameters[\"domain_nx\"]\n        self.domain_dimensions = dd\n\n        if self.parameters.get(\"staggered\", False):\n            mylog.warning(\n                \"'staggered' flag was found, but is currently ignored (unsupported)\"\n            )\n\n        # parse geometry\n        # by order of decreasing priority, we use\n        # - geometry_override\n        # - \"geometry\" parameter from datfile\n        # - if all fails, default to \"cartesian\"\n        self.geometry = Geometry.CARTESIAN\n\n        amrvac_geom = self.parameters.get(\"geometry\", None)\n        if amrvac_geom is not None:\n            self.geometry = _parse_geometry(amrvac_geom)\n        elif self.parameters[\"datfile_version\"] > 4:\n            mylog.error(\n                \"No 'geometry' flag found in datfile with version %d >4.\",\n                self.parameters[\"datfile_version\"],\n            )\n\n        if self._geometry_override is not None:\n            try:\n                new_geometry = _parse_geometry(self._geometry_override)\n                if new_geometry == self.geometry:\n                    mylog.info(\"geometry_override is identical to datfile parameter.\")\n                else:\n                    self.geometry = new_geometry\n                    mylog.warning(\n                        \"Overriding geometry, this may lead to surprising results.\"\n                    )\n            except ValueError:\n                mylog.error(\n                    \"Unable to parse geometry_override '%s' (will be ignored).\",\n                    self._geometry_override,\n                )\n\n        # parse peridiocity\n        periodicity = self.parameters.get(\"periodic\", ())\n        missing_dim = 3 - len(periodicity)\n        self._periodicity = (*periodicity, *(missing_dim * (False,)))\n\n        self.gamma = self.parameters.get(\"gamma\", 5.0 / 3.0)\n\n        # parse domain edges\n        dle = np.zeros(3)\n        dre = np.ones(3)\n        dle[: self.dimensionality] = self.parameters[\"xmin\"]\n        dre[: self.dimensionality] = self.parameters[\"xmax\"]\n        self.domain_left_edge = dle\n        self.domain_right_edge = dre\n\n        # defaulting to non-cosmological\n        self.cosmological_simulation = 0\n        self.current_redshift = 0.0\n        self.omega_matter = 0.0\n        self.omega_lambda = 0.0\n        self.hubble_constant = 0.0\n\n    # units stuff ======================================================================\n    def _set_code_unit_attributes(self):\n        \"\"\"Reproduce how AMRVAC internally set up physical normalisation factors.\"\"\"\n        # This gets called later than Dataset._override_code_units()\n        # This is the reason why it uses setdefaultattr: it will only fill in the gaps\n        # left by the \"override\", instead of overriding them again.\n\n        # note: yt sets hydrogen mass equal to proton mass, amrvac doesn't.\n        mp_cgs = self.quan(1.672621898e-24, \"g\")  # This value is taken from AstroPy\n\n        # get self.length_unit if overrides are supplied, otherwise use default\n        length_unit = getattr(self, \"length_unit\", self.quan(1, \"cm\"))\n        namelist = read_amrvac_namelist(self._parfiles)\n        He_abundance = namelist.get(\"mhd_list\", {}).get(\"he_abundance\", 0.1)\n\n        # 1. calculations for mass, density, numberdensity\n        if \"mass_unit\" in self.units_override:\n            # in this case unit_mass is supplied (and has been set as attribute)\n            mass_unit = self.mass_unit\n            density_unit = mass_unit / length_unit**3\n            nd_unit = density_unit / ((1.0 + 4.0 * He_abundance) * mp_cgs)\n        else:\n            # other case: numberdensity is supplied.\n            # Fall back to one (default) if no overrides supplied\n            try:\n                nd_unit = self.quan(self.units_override[\"numberdensity_unit\"])\n            except KeyError:\n                nd_unit = self.quan(\n                    1.0, self.__class__.default_units[\"numberdensity_unit\"]\n                )\n            density_unit = (1.0 + 4.0 * He_abundance) * mp_cgs * nd_unit\n            mass_unit = density_unit * length_unit**3\n\n        # 2. calculations for velocity\n        if \"time_unit\" in self.units_override:\n            # in this case time was supplied\n            velocity_unit = length_unit / self.time_unit\n        else:\n            # other case: velocity was supplied.\n            # Fall back to None if no overrides supplied\n            velocity_unit = getattr(self, \"velocity_unit\", None)\n\n        # 3. calculations for pressure and temperature\n        if velocity_unit is None:\n            # velocity and time not given, see if temperature is given.\n            # Fall back to one (default) if not\n            temperature_unit = getattr(self, \"temperature_unit\", self.quan(1, \"K\"))\n            pressure_unit = (\n                (2.0 + 3.0 * He_abundance) * nd_unit * kb_cgs * temperature_unit\n            ).in_cgs()\n            velocity_unit = (np.sqrt(pressure_unit / density_unit)).in_cgs()\n        else:\n            # velocity is not zero if either time was given OR velocity was given\n            pressure_unit = (density_unit * velocity_unit**2).in_cgs()\n            temperature_unit = (\n                pressure_unit / ((2.0 + 3.0 * He_abundance) * nd_unit * kb_cgs)\n            ).in_cgs()\n\n        # 4. calculations for magnetic unit and time\n        time_unit = getattr(\n            self, \"time_unit\", length_unit / velocity_unit\n        )  # if time given use it, else calculate\n        magnetic_unit = (np.sqrt(4 * np.pi * pressure_unit)).to(\"gauss\")\n\n        setdefaultattr(self, \"mass_unit\", mass_unit)\n        setdefaultattr(self, \"density_unit\", density_unit)\n\n        setdefaultattr(self, \"length_unit\", length_unit)\n        setdefaultattr(self, \"velocity_unit\", velocity_unit)\n        setdefaultattr(self, \"time_unit\", time_unit)\n\n        setdefaultattr(self, \"temperature_unit\", temperature_unit)\n        setdefaultattr(self, \"pressure_unit\", pressure_unit)\n        setdefaultattr(self, \"magnetic_unit\", magnetic_unit)\n\n    allowed_unit_combinations = [\n        {\"numberdensity_unit\", \"temperature_unit\", \"length_unit\"},\n        {\"mass_unit\", \"temperature_unit\", \"length_unit\"},\n        {\"mass_unit\", \"time_unit\", \"length_unit\"},\n        {\"numberdensity_unit\", \"velocity_unit\", \"length_unit\"},\n        {\"mass_unit\", \"velocity_unit\", \"length_unit\"},\n    ]\n\n    default_units = {\n        \"length_unit\": \"cm\",\n        \"time_unit\": \"s\",\n        \"mass_unit\": \"g\",\n        \"velocity_unit\": \"cm/s\",\n        \"magnetic_unit\": \"gauss\",\n        \"temperature_unit\": \"K\",\n        # this is the one difference with Dataset.default_units:\n        # we accept numberdensity_unit as a valid override\n        \"numberdensity_unit\": \"cm**-3\",\n    }\n\n    @classmethod\n    def _validate_units_override_keys(cls, units_override):\n        \"\"\"Check that keys in units_override are consistent with AMRVAC's internal\n        normalisations factors.\n        \"\"\"\n        # YT supports overriding other normalisations, this method ensures consistency\n        # between supplied 'units_override' items and those used by AMRVAC.\n\n        # AMRVAC's normalisations/units have 3 degrees of freedom.\n        # Moreover, if temperature unit is specified then velocity unit will be\n        # calculated accordingly, and vice-versa.\n        # We replicate this by allowing a finite set of combinations.\n\n        # there are only three degrees of freedom, so explicitly check for this\n        if len(units_override) > 3:\n            raise ValueError(\n                \"More than 3 degrees of freedom were specified \"\n                f\"in units_override ({len(units_override)} given)\"\n            )\n        # temperature and velocity cannot both be specified\n        if \"temperature_unit\" in units_override and \"velocity_unit\" in units_override:\n            raise ValueError(\n                \"Either temperature or velocity is allowed in units_override, not both.\"\n            )\n        # check if provided overrides are allowed\n        suo = set(units_override)\n        for allowed_combo in cls.allowed_unit_combinations:\n            if suo.issubset(allowed_combo):\n                break\n        else:\n            raise ValueError(\n                f\"Combination {suo} passed to units_override \"\n                \"is not consistent with AMRVAC.\\n\"\n                f\"Allowed combinations are {cls.allowed_unit_combinations}\"\n            )\n\n        # syntax for mixing super with classmethod is weird...\n        super(cls, cls)._validate_units_override_keys(units_override)\n"
  },
  {
    "path": "yt/frontends/amrvac/datfile_utils.py",
    "content": "import struct\n\nimport numpy as np\n\n# Size of basic types (in bytes)\nSIZE_LOGICAL = 4\nSIZE_INT = 4\nSIZE_DOUBLE = 8\nNAME_LEN = 16\n\n# For un-aligned data, use '=' (for aligned data set to '')\nALIGN = \"=\"\n\n\ndef get_header(istream):\n    \"\"\"Read header from an MPI-AMRVAC 2.0 snapshot.\n    istream' should be a file\n    opened in binary mode.\n    \"\"\"\n    istream.seek(0)\n    h = {}\n\n    fmt = ALIGN + \"i\"\n    [h[\"datfile_version\"]] = struct.unpack(fmt, istream.read(struct.calcsize(fmt)))\n\n    if h[\"datfile_version\"] < 3:\n        raise OSError(\"Unsupported AMRVAC .dat file version: %d\", h[\"datfile_version\"])\n\n    # Read scalar data at beginning of file\n    fmt = ALIGN + 9 * \"i\" + \"d\"\n    hdr = struct.unpack(fmt, istream.read(struct.calcsize(fmt)))\n    [\n        h[\"offset_tree\"],\n        h[\"offset_blocks\"],\n        h[\"nw\"],\n        h[\"ndir\"],\n        h[\"ndim\"],\n        h[\"levmax\"],\n        h[\"nleafs\"],\n        h[\"nparents\"],\n        h[\"it\"],\n        h[\"time\"],\n    ] = hdr\n\n    # Read min/max coordinates\n    fmt = ALIGN + h[\"ndim\"] * \"d\"\n    h[\"xmin\"] = np.array(struct.unpack(fmt, istream.read(struct.calcsize(fmt))))\n    h[\"xmax\"] = np.array(struct.unpack(fmt, istream.read(struct.calcsize(fmt))))\n\n    # Read domain and block size (in number of cells)\n    fmt = ALIGN + h[\"ndim\"] * \"i\"\n    h[\"domain_nx\"] = np.array(struct.unpack(fmt, istream.read(struct.calcsize(fmt))))\n    h[\"block_nx\"] = np.array(struct.unpack(fmt, istream.read(struct.calcsize(fmt))))\n\n    if h[\"datfile_version\"] >= 5:\n        # Read periodicity\n        fmt = ALIGN + h[\"ndim\"] * \"i\"  # Fortran logical is 4 byte int\n        h[\"periodic\"] = np.array(\n            struct.unpack(fmt, istream.read(struct.calcsize(fmt))), dtype=bool\n        )\n\n        # Read geometry name\n        fmt = ALIGN + NAME_LEN * \"c\"\n        hdr = struct.unpack(fmt, istream.read(struct.calcsize(fmt)))\n        h[\"geometry\"] = b\"\".join(hdr).strip().decode()\n\n        # Read staggered flag\n        fmt = ALIGN + \"i\"  # Fortran logical is 4 byte int\n        h[\"staggered\"] = bool(struct.unpack(fmt, istream.read(struct.calcsize(fmt)))[0])\n\n    # Read w_names\n    w_names = []\n    for _ in range(h[\"nw\"]):\n        fmt = ALIGN + NAME_LEN * \"c\"\n        hdr = struct.unpack(fmt, istream.read(struct.calcsize(fmt)))\n        w_names.append(b\"\".join(hdr).strip().decode())\n    h[\"w_names\"] = w_names\n\n    # Read physics type\n    fmt = ALIGN + NAME_LEN * \"c\"\n    hdr = struct.unpack(fmt, istream.read(struct.calcsize(fmt)))\n    h[\"physics_type\"] = b\"\".join(hdr).strip().decode()\n\n    # Read number of physics-defined parameters\n    fmt = ALIGN + \"i\"\n    [n_pars] = struct.unpack(fmt, istream.read(struct.calcsize(fmt)))\n\n    # First physics-parameter values are given, then their names\n    fmt = ALIGN + n_pars * \"d\"\n    vals = struct.unpack(fmt, istream.read(struct.calcsize(fmt)))\n\n    fmt = ALIGN + n_pars * NAME_LEN * \"c\"\n    names = struct.unpack(fmt, istream.read(struct.calcsize(fmt)))\n    # Split and join the name strings (from one character array)\n    names = [\n        b\"\".join(names[i : i + NAME_LEN]).strip().decode()\n        for i in range(0, len(names), NAME_LEN)\n    ]\n\n    # Store the values corresponding to the names\n    for val, name in zip(vals, names, strict=True):\n        h[name] = val\n    return h\n\n\ndef get_tree_info(istream):\n    \"\"\"\n    Read levels, morton-curve indices, and byte offsets for each block as stored in the\n    datfile istream is an open datfile buffer with 'rb' mode\n    This can be used as the \"first pass\" data reading required by YT's interface.\n    \"\"\"\n    istream.seek(0)\n    header = get_header(istream)\n    nleafs = header[\"nleafs\"]\n    nparents = header[\"nparents\"]\n\n    # Read tree info. Skip 'leaf' array\n    istream.seek(header[\"offset_tree\"] + (nleafs + nparents) * SIZE_LOGICAL)\n\n    # Read block levels\n    fmt = ALIGN + nleafs * \"i\"\n    block_lvls = np.array(struct.unpack(fmt, istream.read(struct.calcsize(fmt))))\n\n    # Read block indices\n    fmt = ALIGN + nleafs * header[\"ndim\"] * \"i\"\n    block_ixs = np.reshape(\n        struct.unpack(fmt, istream.read(struct.calcsize(fmt))), [nleafs, header[\"ndim\"]]\n    )\n\n    # Read block offsets (skip ghost cells !)\n    bcfmt = ALIGN + header[\"ndim\"] * \"i\"\n    bcsize = struct.calcsize(bcfmt) * 2\n\n    fmt = ALIGN + nleafs * \"q\"\n    block_offsets = (\n        np.array(struct.unpack(fmt, istream.read(struct.calcsize(fmt)))) + bcsize\n    )\n    return block_lvls, block_ixs, block_offsets\n\n\ndef get_single_block_data(istream, byte_offset, block_shape):\n    \"\"\"retrieve a specific block (all fields) from a datfile\"\"\"\n    istream.seek(byte_offset)\n    # Read actual data\n    fmt = ALIGN + np.prod(block_shape) * \"d\"\n    d = struct.unpack(fmt, istream.read(struct.calcsize(fmt)))\n    # Fortran ordering\n    block_data = np.reshape(d, block_shape, order=\"F\")\n    return block_data\n\n\ndef get_single_block_field_data(istream, byte_offset, block_shape, field_idx):\n    \"\"\"retrieve a specific block (ONE field) from a datfile\"\"\"\n    # compute byte size of a single field\n    field_shape = block_shape[:-1][::-1]\n    count = np.prod(field_shape)\n    fmt = ALIGN + count * \"d\"\n    byte_size_field = struct.calcsize(fmt)\n\n    istream.seek(byte_offset + byte_size_field * field_idx)\n    return np.fromfile(istream, dtype=\"=f8\", count=count).reshape(field_shape).T\n"
  },
  {
    "path": "yt/frontends/amrvac/definitions.py",
    "content": "# This file is often empty.  It can hold definitions related to a frontend.\n"
  },
  {
    "path": "yt/frontends/amrvac/fields.py",
    "content": "\"\"\"\nAMRVAC-specific fields\n\n\"\"\"\n\nimport functools\n\nimport numpy as np\n\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.units import dimensions\nfrom yt.utilities.logger import ytLogger as mylog\n\n# We need to specify which fields we might have in our dataset.  The field info\n# container subclass here will define which fields it knows about.  There are\n# optionally methods on it that get called which can be subclassed.\n\ndirection_aliases = {\n    \"cartesian\": (\"x\", \"y\", \"z\"),\n    \"polar\": (\"r\", \"theta\", \"z\"),\n    \"cylindrical\": (\"r\", \"z\", \"theta\"),\n    \"spherical\": (\"r\", \"theta\", \"phi\"),\n}\n\n\ndef _velocity(field, data, idir, prefix=None):\n    \"\"\"Velocity = linear momentum / density\"\"\"\n    # This is meant to be used with functools.partial to produce\n    # functions with only 2 arguments (data)\n    # idir : int\n    #    the direction index (1, 2 or 3)\n    # prefix : str\n    #    used to generalize to dust fields\n    if prefix is None:\n        prefix = \"\"\n    moment = data[\"gas\", f\"{prefix}moment_{idir}\"]\n    rho = data[\"gas\", f\"{prefix}density\"]\n\n    mask1 = rho == 0\n    if mask1.any():\n        mylog.info(\n            \"zeros found in %sdensity, \"\n            \"patching them to compute corresponding velocity field.\",\n            prefix,\n        )\n        mask2 = moment == 0\n        if not ((mask1 & mask2) == mask1).all():\n            raise RuntimeError\n        rho[mask1] = 1\n    return moment / rho\n\n\ncode_density = \"code_mass / code_length**3\"\ncode_moment = \"code_mass / code_length**2 / code_time\"\ncode_pressure = \"code_mass / code_length / code_time**2\"\n\n\nclass AMRVACFieldInfo(FieldInfoContainer):\n    # for now, define a finite family of dust fields (up to 100 species)\n    MAXN_DUST_SPECIES = 100\n    known_dust_fields = [\n        (f\"rhod{idust}\", (code_density, [f\"dust{idust}_density\"], None))\n        for idust in range(1, MAXN_DUST_SPECIES + 1)\n    ] + [\n        (\n            f\"m{idir}d{idust}\",\n            (code_moment, [f\"dust{idust}_moment_{idir}\"], None),\n        )\n        for idust in range(1, MAXN_DUST_SPECIES + 1)\n        for idir in (1, 2, 3)\n    ]\n    # format: (native(?) field, (units, [aliases], display_name))\n    # note: aliases will correspond to \"gas\" typed fields\n    # whereas the native ones are \"amrvac\" typed\n    known_other_fields = (\n        (\"rho\", (code_density, [\"density\"], None)),\n        (\"m1\", (code_moment, [\"moment_1\"], None)),\n        (\"m2\", (code_moment, [\"moment_2\"], None)),\n        (\"m3\", (code_moment, [\"moment_3\"], None)),\n        (\"e\", (code_pressure, [\"energy_density\"], None)),\n        (\"b1\", (\"code_magnetic\", [\"magnetic_1\"], None)),\n        (\"b2\", (\"code_magnetic\", [\"magnetic_2\"], None)),\n        (\"b3\", (\"code_magnetic\", [\"magnetic_3\"], None)),\n        (\"Te\", (\"code_temperature\", [\"temperature\"], None)),\n        *known_dust_fields,\n    )\n\n    known_particle_fields = ()\n\n    def _setup_velocity_fields(self, idust=None):\n        if idust is None:\n            dust_flag = dust_label = \"\"\n        else:\n            dust_flag = f\"d{idust}\"\n            dust_label = f\"dust{idust}_\"\n\n        us = self.ds.unit_system\n        for idir, alias in enumerate(direction_aliases[self.ds.geometry], start=1):\n            if (\"amrvac\", f\"m{idir}{dust_flag}\") not in self.field_list:\n                break\n            velocity_fn = functools.partial(_velocity, idir=idir, prefix=dust_label)\n            self.add_field(\n                (\"gas\", f\"{dust_label}velocity_{alias}\"),\n                function=velocity_fn,\n                units=us[\"velocity\"],\n                dimensions=dimensions.velocity,\n                sampling_type=\"cell\",\n            )\n            self.alias(\n                (\"gas\", f\"{dust_label}velocity_{idir}\"),\n                (\"gas\", f\"{dust_label}velocity_{alias}\"),\n                units=us[\"velocity\"],\n            )\n            self.alias(\n                (\"gas\", f\"{dust_label}moment_{alias}\"),\n                (\"gas\", f\"{dust_label}moment_{idir}\"),\n                units=us[\"density\"] * us[\"velocity\"],\n            )\n\n    def _setup_dust_fields(self):\n        idust = 1\n        imax = self.__class__.MAXN_DUST_SPECIES\n        while (\"amrvac\", f\"rhod{idust}\") in self.field_list:\n            if idust > imax:\n                mylog.error(\n                    \"Only the first %d dust species are currently read by yt. \"\n                    \"If you read this, please consider issuing a ticket. \",\n                    imax,\n                )\n                break\n            self._setup_velocity_fields(idust)\n            idust += 1\n        n_dust_found = idust - 1\n\n        us = self.ds.unit_system\n        if n_dust_found > 0:\n\n            def _total_dust_density(data):\n                tot = np.zeros_like(data[\"gas\", \"density\"])\n                for idust in range(1, n_dust_found + 1):\n                    tot += data[f\"dust{idust}_density\"]\n                return tot\n\n            self.add_field(\n                (\"gas\", \"total_dust_density\"),\n                function=_total_dust_density,\n                dimensions=dimensions.density,\n                units=us[\"density\"],\n                sampling_type=\"cell\",\n            )\n\n            def dust_to_gas_ratio(data):\n                return data[\"gas\", \"total_dust_density\"] / data[\"gas\", \"density\"]\n\n            self.add_field(\n                (\"gas\", \"dust_to_gas_ratio\"),\n                function=dust_to_gas_ratio,\n                dimensions=dimensions.dimensionless,\n                sampling_type=\"cell\",\n            )\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        setup_magnetic_field_aliases(self, \"amrvac\", [f\"mag{ax}\" for ax in \"xyz\"])\n        self._setup_velocity_fields()  # gas velocities\n        self._setup_dust_fields()  # dust derived fields (including velocities)\n\n        # fields with nested dependencies are defined thereafter\n        # by increasing level of complexity\n        us = self.ds.unit_system\n\n        def _kinetic_energy_density(data):\n            # devnote : have a look at issue 1301\n            return 0.5 * data[\"gas\", \"density\"] * data[\"gas\", \"velocity_magnitude\"] ** 2\n\n        self.add_field(\n            (\"gas\", \"kinetic_energy_density\"),\n            function=_kinetic_energy_density,\n            units=us[\"density\"] * us[\"velocity\"] ** 2,\n            dimensions=dimensions.density * dimensions.velocity**2,\n            sampling_type=\"cell\",\n        )\n\n        # magnetic energy density\n        if (\"amrvac\", \"b1\") in self.field_list:\n\n            def _magnetic_energy_density(data):\n                emag = 0.5 * data[\"gas\", \"magnetic_1\"] ** 2\n                for idim in \"23\":\n                    if (\"amrvac\", f\"b{idim}\") not in self.field_list:\n                        break\n                    emag += 0.5 * data[\"gas\", f\"magnetic_{idim}\"] ** 2\n                # in AMRVAC the magnetic field is defined in units where mu0 = 1,\n                # such that\n                # Emag = 0.5*B**2 instead of Emag = 0.5*B**2 / mu0\n                # To correctly transform the dimensionality from gauss**2 -> rho*v**2,\n                # we have to take mu0 into account. If we divide here, units when adding\n                # the field should be us[\"density\"]*us[\"velocity\"]**2.\n                # If not, they should be us[\"magnetic_field\"]**2 and division should\n                # happen elsewhere.\n                emag /= 4 * np.pi\n                # divided by mu0 = 4pi in cgs,\n                # yt handles 'mks' and 'code' unit systems internally.\n                return emag\n\n            self.add_field(\n                (\"gas\", \"magnetic_energy_density\"),\n                function=_magnetic_energy_density,\n                units=us[\"density\"] * us[\"velocity\"] ** 2,\n                dimensions=dimensions.density * dimensions.velocity**2,\n                sampling_type=\"cell\",\n            )\n\n        # Adding the thermal pressure field.\n        # In AMRVAC we have multiple physics possibilities:\n        # - if HD/MHD + energy equation P = (gamma-1)*(e - ekin (- emag)) for (M)HD\n        # - if HD/MHD but solve_internal_e is true in parfile, P = (gamma-1)*e for both\n        # - if (m)hd_energy is false in parfile (isothermal), P = c_adiab * rho**gamma\n\n        def _full_thermal_pressure_HD(data):\n            # energy density and pressure are actually expressed in the same unit\n            pthermal = (data.ds.gamma - 1) * (\n                data[\"gas\", \"energy_density\"] - data[\"gas\", \"kinetic_energy_density\"]\n            )\n            return pthermal\n\n        def _full_thermal_pressure_MHD(data):\n            pthermal = (\n                _full_thermal_pressure_HD(data)\n                - (data.ds.gamma - 1) * data[\"gas\", \"magnetic_energy_density\"]\n            )\n            return pthermal\n\n        def _polytropic_thermal_pressure(data):\n            return (data.ds.gamma - 1) * data[\"gas\", \"energy_density\"]\n\n        def _adiabatic_thermal_pressure(data):\n            return data.ds._c_adiab * data[\"gas\", \"density\"] ** data.ds.gamma\n\n        pressure_recipe = None\n        if (\"amrvac\", \"e\") in self.field_list:\n            if self.ds._e_is_internal:\n                pressure_recipe = _polytropic_thermal_pressure\n                mylog.info(\"Using polytropic EoS for thermal pressure.\")\n            elif (\"amrvac\", \"b1\") in self.field_list:\n                pressure_recipe = _full_thermal_pressure_MHD\n                mylog.info(\"Using full MHD energy for thermal pressure.\")\n            else:\n                pressure_recipe = _full_thermal_pressure_HD\n                mylog.info(\"Using full HD energy for thermal pressure.\")\n        elif self.ds._c_adiab is not None:\n            pressure_recipe = _adiabatic_thermal_pressure\n            mylog.info(\"Using adiabatic EoS for thermal pressure (isothermal).\")\n            mylog.warning(\n                \"If you used usr_set_pthermal you should \"\n                \"redefine the thermal_pressure field.\"\n            )\n\n        if pressure_recipe is not None:\n            self.add_field(\n                (\"gas\", \"thermal_pressure\"),\n                function=pressure_recipe,\n                units=us[\"density\"] * us[\"velocity\"] ** 2,\n                dimensions=dimensions.density * dimensions.velocity**2,\n                sampling_type=\"cell\",\n            )\n\n            # sound speed and temperature depend on thermal pressure\n            def _sound_speed(data):\n                return np.sqrt(\n                    data.ds.gamma\n                    * data[\"gas\", \"thermal_pressure\"]\n                    / data[\"gas\", \"density\"]\n                )\n\n            self.add_field(\n                (\"gas\", \"sound_speed\"),\n                function=_sound_speed,\n                units=us[\"velocity\"],\n                dimensions=dimensions.velocity,\n                sampling_type=\"cell\",\n            )\n        else:\n            mylog.warning(\n                \"e not found and no parfile passed, can not set thermal_pressure.\"\n            )\n"
  },
  {
    "path": "yt/frontends/amrvac/io.py",
    "content": "\"\"\"\nAMRVAC-specific IO functions\n\n\n\n\"\"\"\n\nimport os\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt.geometry.selection_routines import GridSelector\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.on_demand_imports import _f90nml as f90nml\n\n\ndef read_amrvac_namelist(parfiles):\n    \"\"\"Read one or more parfiles, and return a unified f90nml.Namelist object.\n\n    This function replicates the patching logic of MPI-AMRVAC where redundant parameters\n    only retain last-in-line values, with the exception of `&filelist:base_filename`,\n    which is accumulated. When passed a single file, this function acts as a mere\n    wrapper of f90nml.read().\n\n    Parameters\n    ----------\n    parfiles : str, os.Pathlike, byte, or an iterable returning those types\n        A file path, or a list of file paths to MPI-AMRVAC configuration parfiles.\n\n    Returns\n    -------\n    unified_namelist : f90nml.Namelist\n        A single namelist object. The class inherits from ordereddict.\n\n    \"\"\"\n    parfiles = (os.path.expanduser(pf) for pf in always_iterable(parfiles))\n\n    # first merge the namelists\n    namelists = [f90nml.read(parfile) for parfile in parfiles]\n    unified_namelist = f90nml.Namelist()\n    for nml in namelists:\n        unified_namelist.patch(nml)\n\n    if \"filelist\" not in unified_namelist:\n        return unified_namelist\n\n    # accumulate `&filelist:base_filename`\n    base_filename = \"\".join(\n        nml.get(\"filelist\", {}).get(\"base_filename\", \"\") for nml in namelists\n    )\n    unified_namelist[\"filelist\"][\"base_filename\"] = base_filename\n\n    return unified_namelist\n\n\nclass AMRVACIOHandler(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"amrvac\"\n\n    def __init__(self, ds):\n        BaseIOHandler.__init__(self, ds)\n        self.ds = ds\n        self.datfile = ds.parameter_filename\n        header = self.ds.parameters\n        self.block_shape = np.append(header[\"block_nx\"], header[\"nw\"])\n\n    def _read_particle_coords(self, chunks, ptf):\n        \"\"\"Not implemented yet.\"\"\"\n        # This needs to *yield* a series of tuples of (ptype, (x, y, z)).\n        # chunks is a list of chunks, and ptf is a dict where the keys are\n        # ptypes and the values are lists of fields.\n        raise NotImplementedError\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        \"\"\"Not implemented yet.\"\"\"\n        # This gets called after the arrays have been allocated.  It needs to\n        # yield ((ptype, field), data) where data is the masked results of\n        # reading ptype, field and applying the selector to the data read in.\n        # Selector objects have a .select_points(x,y,z) that returns a mask, so\n        # you need to do your masking here.\n        raise NotImplementedError\n\n    def _read_data(self, fid, grid, field):\n        \"\"\"Retrieve field data from a grid.\n\n        Parameters\n        ----------\n        fid: file descriptor (open binary file with read access)\n\n        grid : yt.frontends.amrvac.data_structures.AMRVACGrid\n            The grid from which data is to be read.\n        field : str\n            A field name.\n\n        Returns\n        -------\n        data : np.ndarray\n            A 3D array of float64 type representing grid data.\n\n        \"\"\"\n        ileaf = grid.id\n        offset = grid._index.block_offsets[ileaf]\n        field_idx = self.ds.parameters[\"w_names\"].index(field)\n\n        field_shape = self.block_shape[:-1][::-1]\n        count = np.prod(field_shape)\n        byte_size_field = count * 8  # size of a double\n\n        fid.seek(offset + byte_size_field * field_idx)\n        data = np.fromfile(fid, dtype=\"=f8\", count=count).reshape(field_shape).T\n        # Always convert data to 3D, as grid.ActiveDimensions is always 3D\n        while len(data.shape) < 3:\n            data = data[..., np.newaxis]\n        return data\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        \"\"\"Retrieve field(s) data in a selected region of space.\n\n        Parameters\n        ----------\n        chunks : generator\n            A generator for multiple chunks, each of which contains a list of grids.\n\n        selector : yt.geometry.selection_routines.SelectorObject\n            A spatial region selector.\n\n        fields : list\n            A list of tuples (ftype, fname).\n\n        size : np.int64\n            The cumulative number of objs contained in all chunks.\n\n        Returns\n        -------\n        data_dict : dict\n            keys are the (ftype, fname) tuples, values are arrays that have been masked\n            using whatever selector method is appropriate. Arrays have dtype float64.\n        \"\"\"\n\n        # @Notes from Niels:\n        # The chunks list has YTDataChunk objects containing the different grids.\n        # The list of grids can be obtained by doing eg.\n        # grids_list = chunks[0].objs or chunks[1].objs etc.\n        # Every element in \"grids_list\" is then an AMRVACGrid object,\n        # and has hence all attributes of a grid :\n        # (Level, ActiveDimensions, LeftEdge, etc.)\n\n        chunks = list(chunks)\n        data_dict = {}  # <- return variable\n\n        if isinstance(selector, GridSelector):\n            if not len(chunks) == len(chunks[0].objs) == 1:\n                raise RuntimeError\n            grid = chunks[0].objs[0]\n\n            with open(self.datfile, \"rb\") as fh:\n                for ftype, fname in fields:\n                    data_dict[ftype, fname] = self._read_data(fh, grid, fname)\n        else:\n            if size is None:\n                size = sum(g.count(selector) for chunk in chunks for g in chunk.objs)\n\n            for field in fields:\n                data_dict[field] = np.empty(size, dtype=\"float64\")\n\n            # nb_grids = sum(len(chunk.objs) for chunk in chunks)\n            with open(self.datfile, \"rb\") as fh:\n                ind = 0\n                for chunk in chunks:\n                    for grid in chunk.objs:\n                        nd = 0\n                        for field in fields:\n                            ftype, fname = field\n                            data = self._read_data(fh, grid, fname)\n                            nd = grid.select(selector, data, data_dict[field], ind)\n                        ind += nd\n\n        return data_dict\n\n    def _read_chunk_data(self, chunk, fields):\n        \"\"\"Not implemented yet.\"\"\"\n        # This reads the data from a single chunk without doing any selection,\n        # and is only used for caching data that might be used by multiple\n        # different selectors later. For instance, this can speed up ghost zone\n        # computation.\n        # it should be used by _read_fluid_selection instead of _read_data\n        raise NotImplementedError\n"
  },
  {
    "path": "yt/frontends/amrvac/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/amrvac/tests/sample_parfiles/bw_3d.par",
    "content": "!setup.pl -d=3\n &filelist\n        typefilelog='regression_test'\n        base_filename='bw_3d'\n        saveprim=.true.\n        convert_type='vtuBCCmpi'\n        autoconvert=.true.\n        nwauxio=1\n /\n\n &savelist\n        dtsave_log = 1.d-3\n /\n\n &stoplist\n        time_max = 2.d-2\n/\n\n &methodlist\n        time_integrator= 'threestep'\n        flux_scheme= 20*'hllc'\n        limiter= 20*'koren'\n /\n\n &boundlist\n        typeboundary_min1=5*'cont'\n        typeboundary_max1=5*'cont'\n        typeboundary_min2=5*'cont'\n        typeboundary_max2=5*'cont'\n        typeboundary_min3=5*'cont'\n        typeboundary_max3=5*'cont'\n /\n\n &meshlist\n        refine_criterion=3\n        refine_max_level=3\n        w_refine_weight(1)=0.5d0\n        w_refine_weight(5)=0.5d0\n        block_nx1=8\n        block_nx2=8\n        block_nx3=8\n        domain_nx1=16\n        domain_nx2=16\n        domain_nx3=16\n        iprob=1\n        xprobmin1=0.d0\n        xprobmax1=2.d0\n        xprobmin2=0.d0\n        xprobmax2=2.d0\n        xprobmin3=0.d0\n        xprobmax3=2.d0\n /\n\n &paramlist\n        typecourant='maxsum'\n        courantpar=0.5d0\n /\n"
  },
  {
    "path": "yt/frontends/amrvac/tests/sample_parfiles/tvdlf_scheme.par",
    "content": "! This is a fake example modifier parfile that can be used as an\n! extension to bw_3d.par in order to change the integrator scheme.\n! This file is here for testing.\n\n&methodlist\n    flux_scheme= 20*'tvdlf'\n/\n&filelist\n    base_filename='_tvdlf'\n/\n"
  },
  {
    "path": "yt/frontends/amrvac/tests/test_outputs.py",
    "content": "import numpy as np\n\nimport yt  # NOQA\nfrom yt.frontends.amrvac.api import AMRVACDataset, AMRVACGrid\nfrom yt.testing import requires_file\nfrom yt.units import YTArray\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\nblastwave_spherical_2D = \"amrvac/bw_2d0000.dat\"\nkhi_cartesian_2D = \"amrvac/kh_2d0000.dat\"\nkhi_cartesian_3D = \"amrvac/kh_3D0000.dat\"\njet_cylindrical_25D = \"amrvac/Jet0003.dat\"\nriemann_cartesian_175D = \"amrvac/R_1d0005.dat\"\nblastwave_cartesian_3D = \"amrvac/bw_3d0000.dat\"\nblastwave_polar_2D = \"amrvac/bw_polar_2D0000.dat\"\nblastwave_cylindrical_3D = \"amrvac/bw_cylindrical_3D0000.dat\"\nrmi_cartesian_dust_2D = \"amrvac/Richtmyer_Meshkov_dust_2D/RM2D_dust_Kwok0000.dat\"\n\n\ndef _get_fields_to_check(ds):\n    fields = [\"density\", \"velocity_magnitude\"]\n    raw_fields_labels = [fname for ftype, fname in ds.field_list]\n    if \"b1\" in raw_fields_labels:\n        fields.append(\"magnetic_energy_density\")\n    if \"e\" in raw_fields_labels:\n        fields.append(\"energy_density\")\n    if \"rhod1\" in raw_fields_labels:\n        fields.append(\"total_dust_density\")\n        # note : not hitting dust velocity fields\n    return fields\n\n\n@requires_file(khi_cartesian_2D)\ndef test_AMRVACDataset():\n    assert isinstance(data_dir_load(khi_cartesian_2D), AMRVACDataset)\n\n\n@requires_ds(blastwave_cartesian_3D)\ndef test_domain_size():\n    # \"Check for correct box size, see bw_3d.par\"\n    ds = data_dir_load(blastwave_cartesian_3D)\n    for lb in ds.domain_left_edge:\n        assert int(lb) == 0\n    for rb in ds.domain_right_edge:\n        assert int(rb) == 2\n    for w in ds.domain_width:\n        assert int(w) == 2\n\n\n@requires_file(blastwave_cartesian_3D)\ndef test_grid_attributes():\n    # \"Check various grid attributes\"\n    ds = data_dir_load(blastwave_cartesian_3D)\n    grids = ds.index.grids\n    assert ds.index.max_level == 2\n    for g in grids:\n        assert isinstance(g, AMRVACGrid)\n        assert isinstance(g.LeftEdge, YTArray)\n        assert isinstance(g.RightEdge, YTArray)\n        assert isinstance(g.ActiveDimensions, np.ndarray)\n        assert isinstance(g.Level, (np.int32, np.int64, int))\n\n\n@requires_ds(blastwave_polar_2D)\ndef test_bw_polar_2d():\n    ds = data_dir_load(blastwave_polar_2D)\n    for test in small_patch_amr(ds, _get_fields_to_check(ds)):\n        test_bw_polar_2d.__name__ = test.description\n        yield test\n\n\n@requires_ds(blastwave_cartesian_3D)\ndef test_blastwave_cartesian_3D():\n    ds = data_dir_load(blastwave_cartesian_3D)\n    for test in small_patch_amr(ds, _get_fields_to_check(ds)):\n        test_blastwave_cartesian_3D.__name__ = test.description\n        yield test\n\n\n@requires_ds(blastwave_spherical_2D)\ndef test_blastwave_spherical_2D():\n    ds = data_dir_load(blastwave_spherical_2D)\n    for test in small_patch_amr(ds, _get_fields_to_check(ds)):\n        test_blastwave_spherical_2D.__name__ = test.description\n        yield test\n\n\n@requires_ds(blastwave_cylindrical_3D)\ndef test_blastwave_cylindrical_3D():\n    ds = data_dir_load(blastwave_cylindrical_3D)\n    for test in small_patch_amr(ds, _get_fields_to_check(ds)):\n        test_blastwave_cylindrical_3D.__name__ = test.description\n        yield test\n\n\n@requires_ds(khi_cartesian_2D)\ndef test_khi_cartesian_2D():\n    ds = data_dir_load(khi_cartesian_2D)\n    for test in small_patch_amr(ds, _get_fields_to_check(ds)):\n        test_khi_cartesian_2D.__name__ = test.description\n        yield test\n\n\n@requires_ds(khi_cartesian_3D)\ndef test_khi_cartesian_3D():\n    ds = data_dir_load(khi_cartesian_3D)\n    for test in small_patch_amr(ds, _get_fields_to_check(ds)):\n        test_khi_cartesian_3D.__name__ = test.description\n        yield test\n\n\n@requires_ds(jet_cylindrical_25D)\ndef test_jet_cylindrical_25D():\n    ds = data_dir_load(jet_cylindrical_25D)\n    for test in small_patch_amr(ds, _get_fields_to_check(ds)):\n        test_jet_cylindrical_25D.__name__ = test.description\n        yield test\n\n\n@requires_ds(riemann_cartesian_175D)\ndef test_riemann_cartesian_175D():\n    ds = data_dir_load(riemann_cartesian_175D)\n    for test in small_patch_amr(ds, _get_fields_to_check(ds)):\n        test_riemann_cartesian_175D.__name__ = test.description\n        yield test\n\n\n@requires_ds(rmi_cartesian_dust_2D)\ndef test_rmi_cartesian_dust_2D():\n    # dataset with dust fields\n    ds = data_dir_load(rmi_cartesian_dust_2D)\n    for test in small_patch_amr(ds, _get_fields_to_check(ds)):\n        test_rmi_cartesian_dust_2D.__name__ = test.description\n        yield test\n"
  },
  {
    "path": "yt/frontends/amrvac/tests/test_read_amrvac_namelist.py",
    "content": "import os\nfrom copy import deepcopy\nfrom pathlib import Path\n\nfrom yt.frontends.amrvac.api import read_amrvac_namelist\nfrom yt.testing import requires_module\nfrom yt.utilities.on_demand_imports import _f90nml as f90nml\n\ntest_dir = os.path.dirname(os.path.abspath(__file__))\nblast_wave_parfile = os.path.join(test_dir, \"sample_parfiles\", \"bw_3d.par\")\nmodifier_parfile = os.path.join(test_dir, \"sample_parfiles\", \"tvdlf_scheme.par\")\n\n\n@requires_module(\"f90nml\")\ndef test_read_pathlike():\n    read_amrvac_namelist(Path(blast_wave_parfile))\n\n\n@requires_module(\"f90nml\")\ndef test_read_one_file():\n    \"\"\"when provided a single file, the function should merely act\n    as a wrapper for f90nml.read()\"\"\"\n    namelist1 = read_amrvac_namelist(blast_wave_parfile)\n    namelist2 = f90nml.read(blast_wave_parfile)\n    assert namelist1 == namelist2\n\n\n@requires_module(\"f90nml\")\ndef test_accumulate_basename():\n    \"\"\"When two (or more) parfiles are passed,\n    the filelist:base_filename should be special-cased\"\"\"\n    namelist_base = f90nml.read(blast_wave_parfile)\n    namelist_update = f90nml.read(modifier_parfile)\n\n    namelist_tot1 = read_amrvac_namelist([blast_wave_parfile, modifier_parfile])\n    namelist_tot2 = deepcopy(namelist_base)\n    namelist_tot2.patch(namelist_update)\n\n    # remove and store the special-case value\n    name1 = namelist_tot1[\"filelist\"].pop(\"base_filename\")\n    name2 = namelist_tot2[\"filelist\"].pop(\"base_filename\")\n\n    assert (\n        name1\n        == namelist_base[\"filelist\"][\"base_filename\"]\n        + namelist_update[\"filelist\"][\"base_filename\"]\n    )\n    assert name2 == namelist_update[\"filelist\"][\"base_filename\"]\n    assert name1 != name2\n\n    # test equality for the rest of the namelist\n    assert namelist_tot1 == namelist_tot2\n"
  },
  {
    "path": "yt/frontends/amrvac/tests/test_units_override.py",
    "content": "from numpy.testing import assert_raises\n\nfrom yt.testing import assert_allclose_units, requires_file\nfrom yt.units import YTQuantity\nfrom yt.utilities.answer_testing.framework import data_dir_load\n\nkhi_cartesian_2D = \"amrvac/kh_2d0000.dat\"\n\n# Tests for units: check that overriding certain units yields the correct derived units.\n# The following are the correct normalisations\n# based on length, numberdensity and temperature\nlength_unit = (1e9, \"cm\")\nnumberdensity_unit = (1e9, \"cm**-3\")\ntemperature_unit = (1e6, \"K\")\ndensity_unit = (2.341670657200000e-15, \"g*cm**-3\")\nmass_unit = (2.341670657200000e12, \"g\")\nvelocity_unit = (1.164508387441102e07, \"cm*s**-1\")\npressure_unit = (3.175492240000000e-01, \"dyn*cm**-2\")\ntime_unit = (8.587314705370271e01, \"s\")\nmagnetic_unit = (1.997608879907716, \"gauss\")\n\n\ndef _assert_normalisations_equal(ds):\n    assert_allclose_units(ds.length_unit, YTQuantity(*length_unit))\n    assert_allclose_units(ds.temperature_unit, YTQuantity(*temperature_unit))\n    assert_allclose_units(ds.density_unit, YTQuantity(*density_unit))\n    assert_allclose_units(ds.mass_unit, YTQuantity(*mass_unit))\n    assert_allclose_units(ds.velocity_unit, YTQuantity(*velocity_unit))\n    assert_allclose_units(ds.pressure_unit, YTQuantity(*pressure_unit))\n    assert_allclose_units(ds.time_unit, YTQuantity(*time_unit))\n    assert_allclose_units(ds.magnetic_unit, YTQuantity(*magnetic_unit))\n\n\n@requires_file(khi_cartesian_2D)\ndef test_normalisations_length_temp_nb():\n    # overriding length, temperature, numberdensity\n    overrides = {\n        \"length_unit\": length_unit,\n        \"temperature_unit\": temperature_unit,\n        \"numberdensity_unit\": numberdensity_unit,\n    }\n    ds = data_dir_load(khi_cartesian_2D, kwargs={\"units_override\": overrides})\n    _assert_normalisations_equal(ds)\n\n\n@requires_file(khi_cartesian_2D)\ndef test_normalisations_length_temp_mass():\n    # overriding length, temperature, mass\n    overrides = {\n        \"length_unit\": length_unit,\n        \"temperature_unit\": temperature_unit,\n        \"mass_unit\": mass_unit,\n    }\n    ds = data_dir_load(khi_cartesian_2D, kwargs={\"units_override\": overrides})\n    _assert_normalisations_equal(ds)\n\n\n@requires_file(khi_cartesian_2D)\ndef test_normalisations_length_time_mass():\n    # overriding length, time, mass\n    overrides = {\n        \"length_unit\": length_unit,\n        \"time_unit\": time_unit,\n        \"mass_unit\": mass_unit,\n    }\n    ds = data_dir_load(khi_cartesian_2D, kwargs={\"units_override\": overrides})\n    _assert_normalisations_equal(ds)\n\n\n@requires_file(khi_cartesian_2D)\ndef test_normalisations_length_vel_nb():\n    # overriding length, velocity, numberdensity\n    overrides = {\n        \"length_unit\": length_unit,\n        \"velocity_unit\": velocity_unit,\n        \"numberdensity_unit\": numberdensity_unit,\n    }\n    ds = data_dir_load(khi_cartesian_2D, kwargs={\"units_override\": overrides})\n    _assert_normalisations_equal(ds)\n\n\n@requires_file(khi_cartesian_2D)\ndef test_normalisations_length_vel_mass():\n    # overriding length, velocity, mass\n    overrides = {\n        \"length_unit\": length_unit,\n        \"velocity_unit\": velocity_unit,\n        \"mass_unit\": mass_unit,\n    }\n    ds = data_dir_load(khi_cartesian_2D, kwargs={\"units_override\": overrides})\n    _assert_normalisations_equal(ds)\n\n\n@requires_file(khi_cartesian_2D)\ndef test_normalisations_default():\n    # test default normalisations, without overrides\n    ds = data_dir_load(khi_cartesian_2D)\n    assert_allclose_units(ds.length_unit, YTQuantity(1, \"cm\"))\n    assert_allclose_units(ds.temperature_unit, YTQuantity(1, \"K\"))\n    assert_allclose_units(\n        ds.density_unit, YTQuantity(2.341670657200000e-24, \"g*cm**-3\")\n    )\n    assert_allclose_units(ds.mass_unit, YTQuantity(2.341670657200000e-24, \"g\"))\n    assert_allclose_units(\n        ds.velocity_unit, YTQuantity(1.164508387441102e04, \"cm*s**-1\")\n    )\n    assert_allclose_units(\n        ds.pressure_unit, YTQuantity(3.175492240000000e-16, \"dyn*cm**-2\")\n    )\n    assert_allclose_units(ds.time_unit, YTQuantity(8.587314705370271e-05, \"s\"))\n    assert_allclose_units(ds.magnetic_unit, YTQuantity(6.316993934686148e-08, \"gauss\"))\n\n\n@requires_file(khi_cartesian_2D)\ndef test_normalisations_too_many_args():\n    # test forbidden case: too many arguments (max 3 are allowed)\n    overrides = {\n        \"length_unit\": length_unit,\n        \"numberdensity_unit\": numberdensity_unit,\n        \"temperature_unit\": temperature_unit,\n        \"time_unit\": time_unit,\n    }\n    with assert_raises(ValueError):\n        data_dir_load(khi_cartesian_2D, kwargs={\"units_override\": overrides})\n\n\n@requires_file(khi_cartesian_2D)\ndef test_normalisations_vel_and_length():\n    # test forbidden case: both velocity and temperature are specified as overrides\n    overrides = {\n        \"length_unit\": length_unit,\n        \"velocity_unit\": velocity_unit,\n        \"temperature_unit\": temperature_unit,\n    }\n    with assert_raises(ValueError):\n        data_dir_load(khi_cartesian_2D, kwargs={\"units_override\": overrides})\n"
  },
  {
    "path": "yt/frontends/api.py",
    "content": "from . import __all__ as _frontends  # backward compat\n"
  },
  {
    "path": "yt/frontends/arepo/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/arepo/api.py",
    "content": "from . import tests\nfrom .data_structures import ArepoFieldInfo, ArepoHDF5Dataset\nfrom .io import IOHandlerArepoHDF5\n"
  },
  {
    "path": "yt/frontends/arepo/data_structures.py",
    "content": "import numpy as np\n\nfrom yt.frontends.gadget.api import GadgetHDF5Dataset\nfrom yt.funcs import mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import ArepoFieldInfo\n\n\nclass ArepoHDF5Dataset(GadgetHDF5Dataset):\n    _load_requirements = [\"h5py\"]\n    _field_info_class = ArepoFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"arepo_hdf5\",\n        unit_base=None,\n        smoothing_factor=2.0,\n        index_order=None,\n        index_filename=None,\n        kernel_name=None,\n        bounding_box=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        super().__init__(\n            filename,\n            dataset_type=dataset_type,\n            unit_base=unit_base,\n            index_order=index_order,\n            index_filename=index_filename,\n            kernel_name=kernel_name,\n            bounding_box=bounding_box,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        # The \"smoothing_factor\" is a user-configurable parameter which\n        # is multiplied by the radius of the sphere with a volume equal\n        # to that of the Voronoi cell to create smoothing lengths.\n        self.smoothing_factor = smoothing_factor\n        self.gamma = 5.0 / 3.0\n        self.gamma_cr = self.parameters.get(\"GammaCR\", 4.0 / 3.0)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        need_groups = [\"Header\", \"Config\"]\n        veto_groups = [\"FOF\", \"Group\", \"Subhalo\"]\n        valid = True\n        try:\n            fh = h5py.File(filename, mode=\"r\")\n            valid = (\n                all(ng in fh[\"/\"] for ng in need_groups)\n                and not any(vg in fh[\"/\"] for vg in veto_groups)\n                and (\n                    \"VORONOI\" in fh[\"/Config\"].attrs.keys()\n                    or \"AMR\" in fh[\"/Config\"].attrs.keys()\n                )\n                # Datasets with GFM_ fields present are AREPO\n                or any(field.startswith(\"GFM_\") for field in fh[\"/PartType0\"])\n            )\n            fh.close()\n        except Exception:\n            valid = False\n        return valid\n\n    def _get_uvals(self):\n        handle = h5py.File(self.parameter_filename, mode=\"r\")\n        uvals = {}\n        missing = [True] * 3\n        for i, unit in enumerate(\n            [\"UnitLength_in_cm\", \"UnitMass_in_g\", \"UnitVelocity_in_cm_per_s\"]\n        ):\n            for grp in [\"Header\", \"Parameters\", \"Units\"]:\n                if grp in handle and unit in handle[grp].attrs:\n                    uvals[unit] = handle[grp].attrs[unit]\n                    missing[i] = False\n                    break\n        if \"UnitLength_in_cm\" in uvals:\n            # We assume this is comoving, because in the absence of comoving\n            # integration the redshift will be zero.\n            uvals[\"cmcm\"] = 1.0 / uvals[\"UnitLength_in_cm\"]\n        handle.close()\n        if all(missing):\n            uvals = None\n        return uvals\n\n    def _set_code_unit_attributes(self):\n        arepo_unit_base = self._get_uvals()\n        # This rather convoluted logic is required to ensure that\n        # units which are present in the Arepo dataset will be used\n        # no matter what but that the user gets warned\n        if arepo_unit_base is not None:\n            if self._unit_base is None:\n                self._unit_base = arepo_unit_base\n            else:\n                for unit in arepo_unit_base:\n                    if unit == \"cmcm\":\n                        continue\n                    short_unit = unit.split(\"_\")[0][4:].lower()\n                    if short_unit in self._unit_base:\n                        which_unit = short_unit\n                        self._unit_base.pop(short_unit, None)\n                    elif unit in self._unit_base:\n                        which_unit = unit\n                    else:\n                        which_unit = None\n                    if which_unit is not None:\n                        msg = f\"Overwriting '{which_unit}' in unit_base with what we found in the dataset.\"\n                        mylog.warning(msg)\n                    self._unit_base[unit] = arepo_unit_base[unit]\n                if \"cmcm\" in arepo_unit_base:\n                    self._unit_base[\"cmcm\"] = arepo_unit_base[\"cmcm\"]\n        super()._set_code_unit_attributes()\n        munit = np.sqrt(self.mass_unit / (self.time_unit**2 * self.length_unit)).to(\n            \"gauss\"\n        )\n        if self.cosmological_simulation:\n            self.magnetic_unit = self.quan(munit.value, f\"{munit.units}/a**2\")\n        else:\n            self.magnetic_unit = munit\n"
  },
  {
    "path": "yt/frontends/arepo/fields.py",
    "content": "from yt.fields.field_info_container import FieldInfoContainer\nfrom yt.fields.species_fields import add_species_field_by_fraction, setup_species_fields\nfrom yt.frontends.gadget.api import GadgetFieldInfo\nfrom yt.utilities.chemical_formulas import ChemicalFormula\nfrom yt.utilities.physical_ratios import _primordial_mass_fraction\n\nmetal_elements = [\"He\", \"C\", \"N\", \"O\", \"Ne\", \"Mg\", \"Si\", \"Fe\"]\n\n\nclass ArepoFieldInfo(GadgetFieldInfo):\n    def __init__(self, ds, field_list, slice_info=None):\n        if ds.cosmological_simulation:\n            GFM_SFT_units = \"dimensionless\"\n        else:\n            GFM_SFT_units = \"code_length/code_velocity\"\n        self.known_particle_fields += (\n            (\"GFM_StellarFormationTime\", (GFM_SFT_units, [\"stellar_age\"], None)),\n            (\"MagneticField\", (\"code_magnetic\", [\"particle_magnetic_field\"], None)),\n            (\n                \"MagneticFieldDivergence\",\n                (\"code_magnetic/code_length\", [\"magnetic_field_divergence\"], None),\n            ),\n            (\"GFM_CoolingRate\", (\"erg*cm**3/s\", [\"cooling_rate\"], None)),\n            (\"GFM_Metallicity\", (\"\", [\"metallicity\"], None)),\n            (\"GFM_Metals_00\", (\"\", [\"H_fraction\"], None)),\n            (\"GFM_Metals_01\", (\"\", [\"He_fraction\"], None)),\n            (\"GFM_Metals_02\", (\"\", [\"C_fraction\"], None)),\n            (\"GFM_Metals_03\", (\"\", [\"N_fraction\"], None)),\n            (\"GFM_Metals_04\", (\"\", [\"O_fraction\"], None)),\n            (\"GFM_Metals_05\", (\"\", [\"Ne_fraction\"], None)),\n            (\"GFM_Metals_06\", (\"\", [\"Mg_fraction\"], None)),\n            (\"GFM_Metals_07\", (\"\", [\"Si_fraction\"], None)),\n            (\"GFM_Metals_08\", (\"\", [\"Fe_fraction\"], None)),\n            (\"GFM_StellarPhotometrics_00\", (\"\", [\"U_magnitude\"], None)),\n            (\"GFM_StellarPhotometrics_01\", (\"\", [\"B_magnitude\"], None)),\n            (\"GFM_StellarPhotometrics_02\", (\"\", [\"V_magnitude\"], None)),\n            (\"GFM_StellarPhotometrics_03\", (\"\", [\"K_magnitude\"], None)),\n            (\"GFM_StellarPhotometrics_04\", (\"\", [\"g_magnitude\"], None)),\n            (\"GFM_StellarPhotometrics_05\", (\"\", [\"r_magnitude\"], None)),\n            (\"GFM_StellarPhotometrics_06\", (\"\", [\"i_magnitude\"], None)),\n            (\"GFM_StellarPhotometrics_07\", (\"\", [\"z_magnitude\"], None)),\n            (\n                \"CosmicRaySpecificEnergy\",\n                (\"code_specific_energy\", [\"specific_cosmic_ray_energy\"], None),\n            ),\n        )\n        super().__init__(ds, field_list, slice_info=slice_info)\n\n    def setup_particle_fields(self, ptype, *args, **kwargs):\n        FieldInfoContainer.setup_particle_fields(self, ptype)\n        if ptype == \"PartType0\":\n            self.setup_gas_particle_fields(ptype)\n            setup_species_fields(self, ptype)\n\n    def setup_gas_particle_fields(self, ptype):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        super().setup_gas_particle_fields(ptype)\n\n        # Since the AREPO gas \"particles\" are Voronoi cells, we can\n        # define a volume here\n        def _volume(data):\n            return data[\"gas\", \"mass\"] / data[\"gas\", \"density\"]\n\n        self.add_field(\n            (\"gas\", \"cell_volume\"),\n            function=_volume,\n            sampling_type=\"local\",\n            units=self.ds.unit_system[\"volume\"],\n        )\n\n        if (ptype, \"InternalEnergy\") in self.field_list:\n\n            def _pressure(data):\n                return (\n                    (data.ds.gamma - 1.0)\n                    * data[ptype, \"density\"]\n                    * data[ptype, \"InternalEnergy\"]\n                )\n\n            self.add_field(\n                (\"gas\", \"pressure\"),\n                function=_pressure,\n                sampling_type=\"local\",\n                units=self.ds.unit_system[\"pressure\"],\n            )\n\n        if (ptype, \"GFM_Metals_00\") in self.field_list:\n            self.nuclei_names = metal_elements\n            self.species_names = [\"H\"] + metal_elements\n\n        if (ptype, \"MagneticField\") in self.field_list:\n            setup_magnetic_field_aliases(self, ptype, \"MagneticField\")\n\n        if (ptype, \"NeutralHydrogenAbundance\") in self.field_list:\n\n            def _h_p0_fraction(data):\n                return (\n                    data[ptype, \"GFM_Metals_00\"]\n                    * data[ptype, \"NeutralHydrogenAbundance\"]\n                )\n\n            self.add_field(\n                (ptype, \"H_p0_fraction\"),\n                sampling_type=\"particle\",\n                function=_h_p0_fraction,\n                units=\"\",\n            )\n\n            def _h_p1_fraction(data):\n                return data[ptype, \"GFM_Metals_00\"] * (\n                    1.0 - data[ptype, \"NeutralHydrogenAbundance\"]\n                )\n\n            self.add_field(\n                (ptype, \"H_p1_fraction\"),\n                sampling_type=\"particle\",\n                function=_h_p1_fraction,\n                units=\"\",\n            )\n\n            add_species_field_by_fraction(self, ptype, \"H_p0\")\n            add_species_field_by_fraction(self, ptype, \"H_p1\")\n\n            for species in [\"H\", \"H_p0\", \"H_p1\"]:\n                for suf in [\"_density\", \"_number_density\"]:\n                    field = f\"{species}{suf}\"\n                    self.alias((\"gas\", field), (ptype, field))\n\n        if (ptype, \"ElectronAbundance\") in self.field_list:\n            # If we have ElectronAbundance but not NeutralHydrogenAbundance,\n            # try first to use the H_fraction, but otherwise we assume the\n            # cosmic value for hydrogen to generate the H_number_density\n            if (ptype, \"NeutralHydrogenAbundance\") not in self.field_list:\n                m_u = self.ds.quan(1.0, \"amu\").in_cgs()\n                A_H = ChemicalFormula(\"H\").weight\n                if (ptype, \"GFM_Metals_00\") in self.field_list:\n\n                    def _h_number_density(data):\n                        return (\n                            data[\"gas\", \"density\"]\n                            * data[\"gas\", \"H_fraction\"]\n                            / (A_H * m_u)\n                        )\n\n                else:\n                    X_H = _primordial_mass_fraction[\"H\"]\n\n                    def _h_number_density(data):\n                        return data[\"gas\", \"density\"] * X_H / (A_H * m_u)\n\n                self.add_field(\n                    (ptype, \"H_number_density\"),\n                    sampling_type=\"particle\",\n                    function=_h_number_density,\n                    units=self.ds.unit_system[\"number_density\"],\n                )\n                self.alias((\"gas\", \"H_number_density\"), (ptype, \"H_number_density\"))\n                self.alias((\"gas\", \"H_nuclei_density\"), (\"gas\", \"H_number_density\"))\n\n            def _el_number_density(data):\n                return (\n                    data[ptype, \"ElectronAbundance\"] * data[ptype, \"H_number_density\"]\n                )\n\n            self.add_field(\n                (ptype, \"El_number_density\"),\n                sampling_type=\"particle\",\n                function=_el_number_density,\n                units=self.ds.unit_system[\"number_density\"],\n            )\n            self.alias((\"gas\", \"El_number_density\"), (ptype, \"El_number_density\"))\n\n        if (ptype, \"GFM_CoolingRate\") in self.field_list:\n            self.alias((\"gas\", \"cooling_rate\"), (\"PartType0\", \"cooling_rate\"))\n\n            def _cooling_time(data):\n                nH = data[\"gas\", \"H_nuclei_density\"]\n                dedt = -data[\"gas\", \"cooling_rate\"] * nH * nH\n                e = 1.5 * data[\"gas\", \"pressure\"]\n                return e / dedt\n\n            self.add_field(\n                (\"gas\", \"cooling_time\"), _cooling_time, sampling_type=\"local\", units=\"s\"\n            )\n\n        if (ptype, \"CosmicRaySpecificEnergy\") in self.field_list:\n            self.alias(\n                (ptype, \"specific_cosmic_ray_energy\"),\n                (\"gas\", \"specific_cosmic_ray_energy\"),\n            )\n\n            def _cr_energy_density(data):\n                return (\n                    data[\"PartType0\", \"specific_cosmic_ray_energy\"]\n                    * data[\"gas\", \"density\"]\n                )\n\n            self.add_field(\n                (\"gas\", \"cosmic_ray_energy_density\"),\n                _cr_energy_density,\n                sampling_type=\"local\",\n                units=self.ds.unit_system[\"pressure\"],\n            )\n\n            def _cr_pressure(data):\n                return (data.ds.gamma_cr - 1.0) * data[\n                    \"gas\", \"cosmic_ray_energy_density\"\n                ]\n\n            self.add_field(\n                (\"gas\", \"cosmic_ray_pressure\"),\n                _cr_pressure,\n                sampling_type=\"local\",\n                units=self.ds.unit_system[\"pressure\"],\n            )\n"
  },
  {
    "path": "yt/frontends/arepo/io.py",
    "content": "import numpy as np\n\nfrom yt.frontends.gadget.api import IOHandlerGadgetHDF5\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\nclass IOHandlerArepoHDF5(IOHandlerGadgetHDF5):\n    _dataset_type = \"arepo_hdf5\"\n\n    def _generate_smoothing_length(self, index):\n        # This is handled below in _get_smoothing_length\n        return\n\n    def _get_smoothing_length(self, data_file, position_dtype, position_shape):\n        ptype = self.ds._sph_ptypes[0]\n        ind = int(ptype[-1])\n        si, ei = data_file.start, data_file.end\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            pcount = f[\"/Header\"].attrs[\"NumPart_ThisFile\"][ind].astype(\"int64\")\n            pcount = np.clip(pcount - si, 0, ei - si)\n            # Arepo cells do not have \"smoothing lengths\" by definition, so\n            # we compute one here by finding the radius of the sphere\n            # corresponding to the volume of the Voroni cell and multiplying\n            # by a user-configurable smoothing factor.\n            hsml = f[ptype][\"Masses\"][si:ei, ...] / f[ptype][\"Density\"][si:ei, ...]\n            hsml *= 3.0 / (4.0 * np.pi)\n            hsml **= 1.0 / 3.0\n            hsml *= self.ds.smoothing_factor\n            dt = hsml.dtype.newbyteorder(\"N\")  # Native\n            if position_dtype is not None and dt < position_dtype:\n                dt = position_dtype\n            return hsml.astype(dt)\n"
  },
  {
    "path": "yt/frontends/arepo/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/arepo/tests/test_outputs.py",
    "content": "from collections import OrderedDict\n\nfrom yt.frontends.arepo.api import ArepoHDF5Dataset\nfrom yt.testing import (\n    ParticleSelectionComparison,\n    assert_allclose_units,\n    requires_file,\n    requires_module,\n)\nfrom yt.utilities.answer_testing.framework import data_dir_load, requires_ds, sph_answer\n\nbullet_h5 = \"ArepoBullet/snapshot_150.hdf5\"\ntng59_h5 = \"TNGHalo/halo_59.hdf5\"\n_tng59_bbox = [[40669.34, 56669.34], [45984.04, 61984.04], [54114.9, 70114.9]]\ncr_h5 = \"ArepoCosmicRays/snapshot_039.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_file(bullet_h5)\ndef test_arepo_hdf5_selection():\n    ds = data_dir_load(bullet_h5)\n    assert isinstance(ds, ArepoHDF5Dataset)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\nbullet_fields = OrderedDict(\n    [\n        ((\"gas\", \"density\"), None),\n        ((\"gas\", \"temperature\"), None),\n        ((\"gas\", \"temperature\"), (\"gas\", \"density\")),\n        ((\"gas\", \"velocity_magnitude\"), None),\n    ]\n)\n\n\n@requires_module(\"h5py\")\n@requires_ds(bullet_h5)\ndef test_arepo_bullet():\n    ds = data_dir_load(bullet_h5)\n    for test in sph_answer(ds, \"snapshot_150\", 26529600, bullet_fields):\n        test_arepo_bullet.__name__ = test.description\n        yield test\n\n\ntng59_fields = OrderedDict(\n    [\n        ((\"gas\", \"density\"), None),\n        ((\"gas\", \"temperature\"), None),\n        ((\"gas\", \"temperature\"), (\"gas\", \"density\")),\n        ((\"gas\", \"H_number_density\"), None),\n        ((\"gas\", \"H_p0_number_density\"), None),\n        ((\"gas\", \"H_p1_number_density\"), None),\n        ((\"gas\", \"El_number_density\"), None),\n        ((\"gas\", \"C_number_density\"), None),\n        ((\"gas\", \"velocity_magnitude\"), None),\n        ((\"gas\", \"magnetic_field_strength\"), None),\n    ]\n)\n\n\n@requires_module(\"h5py\")\n@requires_ds(tng59_h5)\ndef test_arepo_tng59():\n    ds = data_dir_load(tng59_h5, kwargs={\"bounding_box\": _tng59_bbox})\n    for test in sph_answer(ds, \"halo_59\", 10107142, tng59_fields):\n        test_arepo_tng59.__name__ = test.description\n        yield test\n\n\n@requires_module(\"h5py\")\n@requires_ds(tng59_h5)\ndef test_arepo_tng59_periodicity():\n    ds1 = data_dir_load(tng59_h5)\n    assert ds1.periodicity == (True, True, True)\n    ds2 = data_dir_load(tng59_h5, kwargs={\"bounding_box\": _tng59_bbox})\n    assert ds2.periodicity == (False, False, False)\n\n\n@requires_module(\"h5py\")\n@requires_file(tng59_h5)\ndef test_nh_density():\n    ds = data_dir_load(tng59_h5, kwargs={\"bounding_box\": _tng59_bbox})\n    ad = ds.all_data()\n    assert_allclose_units(\n        ad[\"gas\", \"H_number_density\"], (ad[\"gas\", \"H_nuclei_density\"])\n    )\n\n\n@requires_module(\"h5py\")\n@requires_file(tng59_h5)\ndef test_arepo_tng59_selection():\n    ds = data_dir_load(tng59_h5, kwargs={\"bounding_box\": _tng59_bbox})\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\ncr_fields = OrderedDict(\n    [\n        ((\"gas\", \"density\"), None),\n        ((\"gas\", \"cosmic_ray_energy_density\"), None),\n        ((\"gas\", \"cosmic_ray_pressure\"), None),\n    ]\n)\n\n\n@requires_module(\"h5py\")\n@requires_ds(cr_h5)\ndef test_arepo_cr():\n    ds = data_dir_load(cr_h5)\n    assert ds.gamma_cr == ds.parameters[\"GammaCR\"]\n    for test in sph_answer(ds, \"snapshot_039\", 28313510, cr_fields):\n        test_arepo_cr.__name__ = test.description\n        yield test\n"
  },
  {
    "path": "yt/frontends/art/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/art/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    ARTDataset,\n    ARTDomainFile,\n    ARTDomainSubset,\n    ARTIndex,\n    DarkMatterARTDataset,\n)\nfrom .fields import ARTFieldInfo\nfrom .io import IOHandlerART\n"
  },
  {
    "path": "yt/frontends/art/data_structures.py",
    "content": "import glob\nimport os\nimport struct\nimport weakref\n\nimport numpy as np\n\nimport yt.utilities.fortran_utils as fpu\nfrom yt.data_objects.index_subobjects.octree_subset import OctreeSubset\nfrom yt.data_objects.static_output import Dataset, ParticleFile\nfrom yt.data_objects.unions import ParticleUnion\nfrom yt.frontends.art.definitions import (\n    amr_header_struct,\n    constants,\n    dmparticle_header_struct,\n    filename_pattern,\n    fluid_fields,\n    particle_fields,\n    particle_header_struct,\n    seek_extras,\n)\nfrom yt.frontends.art.fields import ARTFieldInfo\nfrom yt.frontends.art.io import (\n    _read_art_level_info,\n    _read_child_level,\n    _read_root_level,\n    a2b,\n    b2t,\n)\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.geometry_handler import Index, YTDataChunk\nfrom yt.geometry.oct_container import ARTOctreeContainer\nfrom yt.geometry.oct_geometry_handler import OctreeIndex\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\n\n\nclass ARTIndex(OctreeIndex):\n    def __init__(self, ds, dataset_type=\"art\"):\n        self.fluid_field_list = fluid_fields\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        self.max_level = ds.max_level\n        self.float_type = np.float64\n        super().__init__(ds, dataset_type)\n\n    def get_smallest_dx(self):\n        \"\"\"\n        Returns (in code units) the smallest cell size in the simulation.\n        \"\"\"\n        # Overloaded\n        ds = self.dataset\n        return (ds.domain_width / ds.domain_dimensions / (2**self.max_level)).min()\n\n    def _initialize_oct_handler(self):\n        \"\"\"\n        Just count the number of octs per domain and\n        allocate the requisite memory in the oct tree\n        \"\"\"\n        nv = len(self.fluid_field_list)\n        self.oct_handler = ARTOctreeContainer(\n            self.dataset.domain_dimensions / 2,  # dd is # of root cells\n            self.dataset.domain_left_edge,\n            self.dataset.domain_right_edge,\n            1,\n        )\n        # The 1 here refers to domain_id == 1 always for ARTIO.\n        self.domains = [ARTDomainFile(self.dataset, nv, self.oct_handler, 1)]\n        self.octs_per_domain = [dom.level_count.sum() for dom in self.domains]\n\n        self.total_octs = sum(self.octs_per_domain)\n        mylog.debug(\"Allocating %s octs\", self.total_octs)\n        self.oct_handler.allocate_domains(self.octs_per_domain)\n        domain = self.domains[0]\n        domain._read_amr_root(self.oct_handler)\n        domain._read_amr_level(self.oct_handler)\n        self.oct_handler.finalize()\n\n    def _detect_output_fields(self):\n        self.particle_field_list = list(particle_fields)\n        self.field_list = [(\"art\", f) for f in fluid_fields]\n        # now generate all of the possible particle fields\n        for ptype in self.dataset.particle_types_raw:\n            for pfield in self.particle_field_list:\n                pfn = (ptype, pfield)\n                self.field_list.append(pfn)\n\n    def _identify_base_chunk(self, dobj):\n        \"\"\"\n        Take the passed in data source dobj, and use its embedded selector\n        to calculate the domain mask, build the reduced domain\n        subsets and oct counts. Attach this information to dobj.\n        \"\"\"\n        if getattr(dobj, \"_chunk_info\", None) is None:\n            # Get all octs within this oct handler\n            domains = [dom for dom in self.domains if dom.included(dobj.selector)]\n            base_region = getattr(dobj, \"base_region\", dobj)\n            if len(domains) > 1:\n                mylog.debug(\"Identified %s intersecting domains\", len(domains))\n            subsets = [\n                ARTDomainSubset(base_region, domain, self.dataset) for domain in domains\n            ]\n            dobj._chunk_info = subsets\n        dobj._current_chunk = list(self._chunk_all(dobj))[0]\n\n    def _chunk_all(self, dobj):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        # We pass the chunk both the current chunk and list of chunks,\n        # as well as the referring data source\n        yield YTDataChunk(dobj, \"all\", oobjs, None)\n\n    def _chunk_spatial(self, dobj, ngz, sort=None, preload_fields=None):\n        sobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for og in sobjs:\n            if ngz > 0:\n                g = og.retrieve_ghost_zones(ngz, [], smoothed=True)\n            else:\n                g = og\n            yield YTDataChunk(dobj, \"spatial\", [g], None)\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):\n        \"\"\"\n        Since subsets are calculated per domain,\n        i.e. per file, yield each domain at a time to\n        organize by IO. We will eventually chunk out NMSU ART\n        to be level-by-level.\n        \"\"\"\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for subset in oobjs:\n            yield YTDataChunk(dobj, \"io\", [subset], None, cache=cache)\n\n\nclass ARTDataset(Dataset):\n    _index_class: type[Index] = ARTIndex\n    _field_info_class = ARTFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"art\",\n        fields=None,\n        storage_filename=None,\n        skip_particles=False,\n        skip_stars=False,\n        limit_level=None,\n        spread_age=True,\n        force_max_level=None,\n        file_particle_header=None,\n        file_particle_data=None,\n        file_particle_stars=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        self.fluid_types += (\"art\",)\n        if fields is None:\n            fields = fluid_fields\n        filename = os.path.abspath(filename)\n        self._fields_in_file = fields\n        self._file_amr = filename\n        self._file_particle_header = file_particle_header\n        self._file_particle_data = file_particle_data\n        self._file_particle_stars = file_particle_stars\n        self._find_files(filename)\n        self.skip_particles = skip_particles\n        self.skip_stars = skip_stars\n        self.limit_level = limit_level\n        self.max_level = limit_level\n        self.force_max_level = force_max_level\n        self.spread_age = spread_age\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        self.storage_filename = storage_filename\n\n    def _find_files(self, file_amr):\n        \"\"\"\n        Given the AMR base filename, attempt to find the\n        particle header, star files, etc.\n        \"\"\"\n        base_prefix, base_suffix = filename_pattern[\"amr\"]\n        numericstr = file_amr.rsplit(\"_\", 1)[1].replace(base_suffix, \"\")\n        possibles = glob.glob(\n            os.path.join(os.path.dirname(os.path.abspath(file_amr)), \"*\")\n        )\n        for filetype, (prefix, suffix) in filename_pattern.items():\n            # if this attribute is already set skip it\n            if getattr(self, \"_file_\" + filetype, None) is not None:\n                continue\n            match = None\n            for possible in possibles:\n                if possible.endswith(numericstr + suffix):\n                    if os.path.basename(possible).startswith(prefix):\n                        match = possible\n            if match is not None:\n                mylog.info(\"discovered %s:%s\", filetype, match)\n                setattr(self, \"_file_\" + filetype, match)\n            else:\n                setattr(self, \"_file_\" + filetype, None)\n\n    def __str__(self):\n        return self._file_amr.split(\"/\")[-1]\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Generates the conversion to various physical units based\n                on the parameters from the header\n        \"\"\"\n\n        # spatial units\n        z = self.current_redshift\n        h = self.hubble_constant\n        boxcm_cal = self.parameters[\"boxh\"]\n        boxcm_uncal = boxcm_cal / h\n        box_proper = boxcm_uncal / (1 + z)\n        aexpn = self.parameters[\"aexpn\"]\n\n        # all other units\n        Om0 = self.parameters[\"Om0\"]\n        ng = self.parameters[\"ng\"]\n        boxh = self.parameters[\"boxh\"]\n        aexpn = self.parameters[\"aexpn\"]\n        hubble = self.parameters[\"hubble\"]\n\n        r0 = boxh / ng\n        v0 = 50.0 * r0 * np.sqrt(Om0)\n        rho0 = 2.776e11 * hubble**2.0 * Om0\n        aM0 = rho0 * (boxh / hubble) ** 3.0 / ng**3.0\n        velocity = v0 / aexpn * 1.0e5  # proper cm/s\n        mass = aM0 * 1.98892e33\n\n        self.cosmological_simulation = True\n        setdefaultattr(self, \"mass_unit\", self.quan(mass, f\"g*{ng**3}\"))\n        setdefaultattr(self, \"length_unit\", self.quan(box_proper, \"Mpc\"))\n        setdefaultattr(self, \"velocity_unit\", self.quan(velocity, \"cm/s\"))\n        setdefaultattr(self, \"time_unit\", self.length_unit / self.velocity_unit)\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Get the various simulation parameters & constants.\n        \"\"\"\n        self.domain_left_edge = np.zeros(3, dtype=\"float\")\n        self.domain_right_edge = np.zeros(3, dtype=\"float\") + 1.0\n        self.dimensionality = 3\n        self.refine_by = 2\n        self._periodicity = (True, True, True)\n        self.cosmological_simulation = True\n        self.parameters = {}\n        self.parameters.update(constants)\n        self.parameters[\"Time\"] = 1.0\n        # read the amr header\n        with open(self._file_amr, \"rb\") as f:\n            amr_header_vals = fpu.read_attrs(f, amr_header_struct, \">\")\n            n_to_skip = len((\"tl\", \"dtl\", \"tlold\", \"dtlold\", \"iSO\"))\n            fpu.skip(f, n_to_skip, endian=\">\")\n            (self.ncell) = fpu.read_vector(f, \"i\", \">\")[0]\n            # Try to figure out the root grid dimensions\n            est = int(np.rint(self.ncell ** (1.0 / 3.0)))\n            # Note here: this is the number of *cells* on the root grid.\n            # This is not the same as the number of Octs.\n            # domain dimensions is the number of root *cells*\n            self.domain_dimensions = np.ones(3, dtype=\"int64\") * est\n            self.root_grid_mask_offset = f.tell()\n            self.root_nocts = self.domain_dimensions.prod() // 8\n            self.root_ncells = self.root_nocts * 8\n            mylog.debug(\n                \"Estimating %i cells on a root grid side, %i root octs\",\n                est,\n                self.root_nocts,\n            )\n            self.root_iOctCh = fpu.read_vector(f, \"i\", \">\")[: self.root_ncells]\n            self.root_iOctCh = self.root_iOctCh.reshape(\n                self.domain_dimensions, order=\"F\"\n            )\n            self.root_grid_offset = f.tell()\n            self.root_nhvar = fpu.skip(f, endian=\">\")\n            self.root_nvar = fpu.skip(f, endian=\">\")\n            # make sure that the number of root variables is a multiple of\n            # rootcells\n            assert self.root_nhvar % self.root_ncells == 0\n            assert self.root_nvar % self.root_ncells == 0\n            self.nhydro_variables = (\n                self.root_nhvar + self.root_nvar\n            ) / self.root_ncells\n            self.iOctFree, self.nOct = fpu.read_vector(f, \"i\", \">\")\n            self.child_grid_offset = f.tell()\n            # lextra needs to be loaded as a string, but it's actually\n            # array values.  So pop it off here, and then re-insert.\n            lextra = amr_header_vals.pop(\"lextra\")\n            amr_header_vals[\"lextra\"] = np.frombuffer(lextra, \">f4\")\n            self.parameters.update(amr_header_vals)\n            amr_header_vals = None\n            # estimate the root level\n            float_center, fl, iocts, nocts, root_level = _read_art_level_info(\n                f, [0, self.child_grid_offset], 1, coarse_grid=self.domain_dimensions[0]\n            )\n            del float_center, fl, iocts, nocts\n            self.root_level = root_level\n            mylog.info(\"Using root level of %02i\", self.root_level)\n        # read the particle header\n        self.particle_types = []\n        self.particle_types_raw = ()\n        if not self.skip_particles and self._file_particle_header:\n            with open(self._file_particle_header, \"rb\") as fh:\n                particle_header_vals = fpu.read_attrs(fh, particle_header_struct, \">\")\n                fh.seek(seek_extras)\n                n = particle_header_vals[\"Nspecies\"]\n                wspecies = np.fromfile(fh, dtype=\">f\", count=10)\n                lspecies = np.fromfile(fh, dtype=\">i\", count=10)\n                # extras needs to be loaded as a string, but it's actually\n                # array values.  So pop it off here, and then re-insert.\n                extras = particle_header_vals.pop(\"extras\")\n                particle_header_vals[\"extras\"] = np.frombuffer(extras, \">f4\")\n            self.parameters[\"wspecies\"] = wspecies[:n]\n            self.parameters[\"lspecies\"] = lspecies[:n]\n            for specie in range(n):\n                self.particle_types.append(f\"specie{specie}\")\n            self.particle_types_raw = tuple(self.particle_types)\n            ls_nonzero = np.diff(lspecies)[: n - 1]\n            ls_nonzero = np.append(lspecies[0], ls_nonzero)\n            self.star_type = len(ls_nonzero)\n            mylog.info(\"Discovered %i species of particles\", len(ls_nonzero))\n            info_str = \"Particle populations: \" + \"%9i \" * len(ls_nonzero)\n            mylog.info(info_str, *ls_nonzero)\n            self._particle_type_counts = dict(\n                zip(self.particle_types_raw, ls_nonzero, strict=True)\n            )\n            for k, v in particle_header_vals.items():\n                if k in self.parameters.keys():\n                    if not self.parameters[k] == v:\n                        mylog.info(\n                            \"Inconsistent parameter %s %1.1e  %1.1e\",\n                            k,\n                            v,\n                            self.parameters[k],\n                        )\n                else:\n                    self.parameters[k] = v\n            self.parameters_particles = particle_header_vals\n            self.parameters.update(particle_header_vals)\n            self.parameters[\"ng\"] = self.parameters[\"Ngridc\"]\n            self.parameters[\"ncell0\"] = self.parameters[\"ng\"] ** 3\n\n        # setup standard simulation params yt expects to see\n        self.current_redshift = self.parameters[\"aexpn\"] ** -1.0 - 1.0\n        self.omega_lambda = self.parameters[\"Oml0\"]\n        self.omega_matter = self.parameters[\"Om0\"]\n        self.hubble_constant = self.parameters[\"hubble\"]\n        self.min_level = self.parameters[\"min_level\"]\n        self.max_level = self.parameters[\"max_level\"]\n        if self.limit_level is not None:\n            self.max_level = min(self.limit_level, self.parameters[\"max_level\"])\n        if self.force_max_level is not None:\n            self.max_level = self.force_max_level\n        self.hubble_time = 1.0 / (self.hubble_constant * 100 / 3.08568025e19)\n        self.current_time = self.quan(b2t(self.parameters[\"t\"]), \"Gyr\")\n        self.gamma = self.parameters[\"gamma\"]\n        mylog.info(\"Max level is %02i\", self.max_level)\n\n    def create_field_info(self):\n        super().create_field_info()\n        if \"wspecies\" in self.parameters:\n            # We create dark_matter and stars unions.\n            ptr = self.particle_types_raw\n            pu = ParticleUnion(\"darkmatter\", list(ptr[:-1]))\n            self.add_particle_union(pu)\n            pu = ParticleUnion(\"stars\", list(ptr[-1:]))\n            self.add_particle_union(pu)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        \"\"\"\n        Defined for the NMSU file naming scheme.\n        This could differ for other formats.\n        \"\"\"\n        f = str(filename)\n        prefix, suffix = filename_pattern[\"amr\"]\n        if not os.path.isfile(f):\n            return False\n        if not f.endswith(suffix):\n            return False\n        with open(f, \"rb\") as fh:\n            try:\n                fpu.read_attrs(fh, amr_header_struct, \">\")\n                return True\n            except Exception:\n                return False\n\n\nclass ARTParticleFile(ParticleFile):\n    def __init__(self, ds, io, filename, file_id):\n        super().__init__(ds, io, filename, file_id, range=None)\n        self.total_particles = {}\n        for ptype, count in zip(\n            ds.particle_types_raw,\n            ds.parameters[\"total_particles\"],\n            strict=True,\n        ):\n            self.total_particles[ptype] = count\n        with open(filename, \"rb\") as f:\n            f.seek(0, os.SEEK_END)\n            self._file_size = f.tell()\n\n\nclass ARTParticleIndex(ParticleIndex):\n    def _setup_filenames(self):\n        # no need for template, all data in one file\n        template = self.dataset.filename_template\n        ndoms = self.dataset.file_count\n        cls = self.dataset._file_class\n        self.data_files = []\n        fi = 0\n        for i in range(int(ndoms)):\n            df = cls(self.dataset, self.io, template % {\"num\": i}, fi)\n            fi += 1\n            self.data_files.append(df)\n\n\nclass DarkMatterARTDataset(ARTDataset):\n    _index_class = ARTParticleIndex\n    _file_class = ARTParticleFile\n    filter_bbox = False\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"dm_art\",\n        fields=None,\n        storage_filename=None,\n        skip_particles=False,\n        skip_stars=False,\n        limit_level=None,\n        spread_age=True,\n        force_max_level=None,\n        file_particle_header=None,\n        file_particle_stars=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        self.num_zones = 2\n        self.n_ref = 64\n        self.particle_types += (\"all\",)\n        if fields is None:\n            fields = particle_fields\n            filename = os.path.abspath(filename)\n        self._fields_in_file = fields\n        self._file_particle = filename\n        self._file_particle_header = file_particle_header\n        self._find_files(filename)\n        self.skip_stars = skip_stars\n        self.spread_age = spread_age\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n        self.storage_filename = storage_filename\n\n    def _find_files(self, file_particle):\n        \"\"\"\n        Given the particle base filename, attempt to find the\n        particle header and star files.\n        \"\"\"\n        base_prefix, base_suffix = filename_pattern[\"particle_data\"]\n        aexpstr = file_particle.rsplit(\"s0\", 1)[1].replace(base_suffix, \"\")\n        possibles = glob.glob(\n            os.path.join(os.path.dirname(os.path.abspath(file_particle)), \"*\")\n        )\n        for filetype, (prefix, suffix) in filename_pattern.items():\n            # if this attribute is already set skip it\n            if getattr(self, \"_file_\" + filetype, None) is not None:\n                continue\n            match = None\n            for possible in possibles:\n                if possible.endswith(aexpstr + suffix):\n                    if os.path.basename(possible).startswith(prefix):\n                        match = possible\n            if match is not None:\n                mylog.info(\"discovered %s:%s\", filetype, match)\n                setattr(self, \"_file_\" + filetype, match)\n            else:\n                setattr(self, \"_file_\" + filetype, None)\n\n    def __str__(self):\n        return self._file_particle.split(\"/\")[-1]\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Generates the conversion to various physical units based\n                on the parameters from the header\n        \"\"\"\n        # spatial units\n        z = self.current_redshift\n        h = self.hubble_constant\n        boxcm_cal = self.parameters[\"boxh\"]\n        boxcm_uncal = boxcm_cal / h\n        box_proper = boxcm_uncal / (1 + z)\n        aexpn = self.parameters[\"aexpn\"]\n\n        # all other units\n        Om0 = self.parameters[\"Om0\"]\n        ng = self.parameters[\"ng\"]\n        boxh = self.parameters[\"boxh\"]\n        aexpn = self.parameters[\"aexpn\"]\n        hubble = self.parameters[\"hubble\"]\n\n        r0 = boxh / ng\n        rho0 = 2.776e11 * hubble**2.0 * Om0\n        aM0 = rho0 * (boxh / hubble) ** 3.0 / ng**3.0\n        velocity = 100.0 * r0 / aexpn * 1.0e5  # proper cm/s\n        mass = aM0 * 1.98892e33\n\n        self.cosmological_simulation = True\n        self.mass_unit = self.quan(mass, f\"g*{ng**3}\")\n        self.length_unit = self.quan(box_proper, \"Mpc\")\n        self.velocity_unit = self.quan(velocity, \"cm/s\")\n        self.time_unit = self.length_unit / self.velocity_unit\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Get the various simulation parameters & constants.\n        \"\"\"\n        self.domain_left_edge = np.zeros(3, dtype=\"float\")\n        self.domain_right_edge = np.zeros(3, dtype=\"float\") + 1.0\n        self.dimensionality = 3\n        self.refine_by = 2\n        self._periodicity = (True, True, True)\n        self.cosmological_simulation = True\n        self.parameters = {}\n        self.parameters.update(constants)\n        self.parameters[\"Time\"] = 1.0\n        self.file_count = 1\n        self.filename_template = self.parameter_filename\n\n        # read the particle header\n        self.particle_types = []\n        self.particle_types_raw = ()\n        assert self._file_particle_header\n        with open(self._file_particle_header, \"rb\") as fh:\n            seek = 4\n            fh.seek(seek)\n            headerstr = fh.read(45).decode(\"ascii\")\n            aexpn = np.fromfile(fh, count=1, dtype=\">f4\")\n            aexp0 = np.fromfile(fh, count=1, dtype=\">f4\")\n            amplt = np.fromfile(fh, count=1, dtype=\">f4\")\n            astep = np.fromfile(fh, count=1, dtype=\">f4\")\n            istep = np.fromfile(fh, count=1, dtype=\">i4\")\n            partw = np.fromfile(fh, count=1, dtype=\">f4\")\n            tintg = np.fromfile(fh, count=1, dtype=\">f4\")\n            ekin = np.fromfile(fh, count=1, dtype=\">f4\")\n            ekin1 = np.fromfile(fh, count=1, dtype=\">f4\")\n            ekin2 = np.fromfile(fh, count=1, dtype=\">f4\")\n            au0 = np.fromfile(fh, count=1, dtype=\">f4\")\n            aeu0 = np.fromfile(fh, count=1, dtype=\">f4\")\n            nrowc = np.fromfile(fh, count=1, dtype=\">i4\")\n            ngridc = np.fromfile(fh, count=1, dtype=\">i4\")\n            nspecs = np.fromfile(fh, count=1, dtype=\">i4\")\n            nseed = np.fromfile(fh, count=1, dtype=\">i4\")\n            Om0 = np.fromfile(fh, count=1, dtype=\">f4\")\n            Oml0 = np.fromfile(fh, count=1, dtype=\">f4\")\n            hubble = np.fromfile(fh, count=1, dtype=\">f4\")\n            Wp5 = np.fromfile(fh, count=1, dtype=\">f4\")\n            Ocurv = np.fromfile(fh, count=1, dtype=\">f4\")\n            wspecies = np.fromfile(fh, count=10, dtype=\">f4\")\n            lspecies = np.fromfile(fh, count=10, dtype=\">i4\")\n            extras = np.fromfile(fh, count=79, dtype=\">f4\")\n            boxsize = np.fromfile(fh, count=1, dtype=\">f4\")\n        n = nspecs[0]\n        particle_header_vals = {}\n        tmp = [\n            headerstr,\n            aexpn,\n            aexp0,\n            amplt,\n            astep,\n            istep,\n            partw,\n            tintg,\n            ekin,\n            ekin1,\n            ekin2,\n            au0,\n            aeu0,\n            nrowc,\n            ngridc,\n            nspecs,\n            nseed,\n            Om0,\n            Oml0,\n            hubble,\n            Wp5,\n            Ocurv,\n            wspecies,\n            lspecies,\n            extras,\n            boxsize,\n        ]\n        for i, arr in enumerate(tmp):\n            a1 = dmparticle_header_struct[0][i]\n            a2 = dmparticle_header_struct[1][i]\n            if a2 == 1:\n                particle_header_vals[a1] = arr[0]\n            else:\n                particle_header_vals[a1] = arr[:a2]\n        for specie in range(n):\n            self.particle_types.append(f\"specie{specie}\")\n        self.particle_types_raw = tuple(self.particle_types)\n        ls_nonzero = np.diff(lspecies)[: n - 1]\n        ls_nonzero = np.append(lspecies[0], ls_nonzero)\n        self.star_type = len(ls_nonzero)\n        mylog.info(\"Discovered %i species of particles\", len(ls_nonzero))\n        info_str = \"Particle populations: \" + \"%9i \" * len(ls_nonzero)\n        mylog.info(info_str, *ls_nonzero)\n        for k, v in particle_header_vals.items():\n            if k in self.parameters.keys():\n                if not self.parameters[k] == v:\n                    mylog.info(\n                        \"Inconsistent parameter %s %1.1e  %1.1e\",\n                        k,\n                        v,\n                        self.parameters[k],\n                    )\n            else:\n                self.parameters[k] = v\n        self.parameters_particles = particle_header_vals\n        self.parameters.update(particle_header_vals)\n        self.parameters[\"wspecies\"] = wspecies[:n]\n        self.parameters[\"lspecies\"] = lspecies[:n]\n        self.parameters[\"ng\"] = self.parameters[\"Ngridc\"]\n        self.parameters[\"ncell0\"] = self.parameters[\"ng\"] ** 3\n        self.parameters[\"boxh\"] = self.parameters[\"boxsize\"]\n        self.parameters[\"total_particles\"] = ls_nonzero\n        self.domain_dimensions = np.ones(3, dtype=\"int64\") * 2  # NOT ng\n\n        # setup standard simulation params yt expects to see\n        # Convert to float to please unyt\n        self.current_redshift = float(self.parameters[\"aexpn\"] ** -1.0 - 1.0)\n        self.omega_lambda = float(particle_header_vals[\"Oml0\"])\n        self.omega_matter = float(particle_header_vals[\"Om0\"])\n        self.hubble_constant = float(particle_header_vals[\"hubble\"])\n        self.min_level = 0\n        self.max_level = 0\n        #        self.min_level = particle_header_vals['min_level']\n        #        self.max_level = particle_header_vals['max_level']\n        #        if self.limit_level is not None:\n        #            self.max_level = min(\n        #                self.limit_level, particle_header_vals['max_level'])\n        #        if self.force_max_level is not None:\n        #            self.max_level = self.force_max_level\n        self.hubble_time = 1.0 / (self.hubble_constant * 100 / 3.08568025e19)\n        self.parameters[\"t\"] = a2b(self.parameters[\"aexpn\"])\n        self.current_time = self.quan(b2t(self.parameters[\"t\"]), \"Gyr\")\n        self.gamma = self.parameters[\"gamma\"]\n        mylog.info(\"Max level is %02i\", self.max_level)\n\n    def create_field_info(self):\n        super(ARTDataset, self).create_field_info()\n        ptr = self.particle_types_raw\n        pu = ParticleUnion(\"darkmatter\", list(ptr))\n        self.add_particle_union(pu)\n        pass\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        \"\"\"\n        Defined for the NMSU file naming scheme.\n        This could differ for other formats.\n        \"\"\"\n        f = str(filename)\n        prefix, suffix = filename_pattern[\"particle_data\"]\n        if not os.path.isfile(f):\n            return False\n        if not f.endswith(suffix):\n            return False\n        if \"s0\" not in f:\n            # ATOMIC.DAT, for instance, passes the other tests, but then dies\n            # during _find_files because it can't be split.\n            return False\n        with open(f, \"rb\") as fh:\n            try:\n                amr_prefix, amr_suffix = filename_pattern[\"amr\"]\n                possibles = glob.glob(\n                    os.path.join(os.path.dirname(os.path.abspath(f)), \"*\")\n                )\n                for possible in possibles:\n                    if possible.endswith(amr_suffix):\n                        if os.path.basename(possible).startswith(amr_prefix):\n                            return False\n            except Exception:\n                pass\n            try:\n                seek = 4\n                fh.seek(seek)\n                headerstr = np.fromfile(fh, count=1, dtype=(str, 45))  # NOQA\n                aexpn = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                aexp0 = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                amplt = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                astep = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                istep = np.fromfile(fh, count=1, dtype=\">i4\")  # NOQA\n                partw = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                tintg = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                ekin = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                ekin1 = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                ekin2 = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                au0 = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                aeu0 = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                nrowc = np.fromfile(fh, count=1, dtype=\">i4\")  # NOQA\n                ngridc = np.fromfile(fh, count=1, dtype=\">i4\")  # NOQA\n                nspecs = np.fromfile(fh, count=1, dtype=\">i4\")  # NOQA\n                nseed = np.fromfile(fh, count=1, dtype=\">i4\")  # NOQA\n                Om0 = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                Oml0 = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                hubble = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                Wp5 = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                Ocurv = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                wspecies = np.fromfile(fh, count=10, dtype=\">f4\")  # NOQA\n                lspecies = np.fromfile(fh, count=10, dtype=\">i4\")  # NOQA\n                extras = np.fromfile(fh, count=79, dtype=\">f4\")  # NOQA\n                boxsize = np.fromfile(fh, count=1, dtype=\">f4\")  # NOQA\n                return True\n            except Exception:\n                return False\n\n\nclass ARTDomainSubset(OctreeSubset):\n    @property\n    def oct_handler(self):\n        return self.domain.oct_handler\n\n    def fill(self, content, ftfields, selector):\n        \"\"\"\n        This is called from IOHandler. It takes content\n        which is a binary stream, reads the requested field\n        over this while domain. It then uses oct_handler fill\n        to reorganize values from IO read index order to\n        the order they are in in the octhandler.\n        \"\"\"\n        oct_handler = self.oct_handler\n        all_fields = self.domain.ds.index.fluid_field_list\n        fields = [f for ft, f in ftfields]\n        field_idxs = [all_fields.index(f) for f in fields]\n        source, tr = {}, {}\n        cell_count = selector.count_oct_cells(self.oct_handler, self.domain_id)\n        levels, cell_inds, file_inds = self.oct_handler.file_index_octs(\n            selector, self.domain_id, cell_count\n        )\n        for field in fields:\n            tr[field] = np.zeros(cell_count, \"float64\")\n        data = _read_root_level(\n            content, self.domain.level_child_offsets, self.domain.level_count\n        )\n        ns = (self.domain.ds.domain_dimensions.prod() // 8, 8)\n        for field, fi in zip(fields, field_idxs, strict=True):\n            source[field] = np.empty(ns, dtype=\"float64\", order=\"C\")\n            dt = data[fi, :].reshape(self.domain.ds.domain_dimensions, order=\"F\")\n            for i in range(2):\n                for j in range(2):\n                    for k in range(2):\n                        ii = ((k * 2) + j) * 2 + i\n                        # Note: C order because our index converts C to F.\n                        source[field][:, ii] = dt[i::2, j::2, k::2].ravel(order=\"C\")\n        oct_handler.fill_level(0, levels, cell_inds, file_inds, tr, source)\n        del source\n        # Now we continue with the additional levels.\n        for level in range(1, self.ds.index.max_level + 1):\n            no = self.domain.level_count[level]\n            noct_range = [0, no]\n            source = _read_child_level(\n                content,\n                self.domain.level_child_offsets,\n                self.domain.level_offsets,\n                self.domain.level_count,\n                level,\n                fields,\n                self.domain.ds.domain_dimensions,\n                self.domain.ds.parameters[\"ncell0\"],\n                noct_range=noct_range,\n            )\n            oct_handler.fill_level(level, levels, cell_inds, file_inds, tr, source)\n        return tr\n\n\nclass ARTDomainFile:\n    \"\"\"\n    Read in the AMR, left/right edges, fill out the octhandler\n    \"\"\"\n\n    # We already read in the header in static output,\n    # and since these headers are defined in only a single file it's\n    # best to leave them in the static output\n    _last_mask = None\n    _last_selector_id = None\n\n    def __init__(self, ds, nvar, oct_handler, domain_id):\n        self.nvar = nvar\n        self.ds = ds\n        self.domain_id = domain_id\n        self._level_count = None\n        self._level_oct_offsets = None\n        self._level_child_offsets = None\n        self._oct_handler = oct_handler\n\n    @property\n    def oct_handler(self):\n        return self._oct_handler\n\n    @property\n    def level_count(self):\n        # this is number of *octs*\n        if self._level_count is not None:\n            return self._level_count\n        self.level_offsets\n        return self._level_count\n\n    @property\n    def level_child_offsets(self):\n        if self._level_count is not None:\n            return self._level_child_offsets\n        self.level_offsets\n        return self._level_child_offsets\n\n    @property\n    def level_offsets(self):\n        # this is used by the IO operations to find the file offset,\n        # and then start reading to fill values\n        # note that this is called hydro_offset in ramses\n        if self._level_oct_offsets is not None:\n            return self._level_oct_offsets\n        # We now have to open the file and calculate it\n        f = open(self.ds._file_amr, \"rb\")\n        (\n            nhydrovars,\n            inoll,\n            _level_oct_offsets,\n            _level_child_offsets,\n        ) = self._count_art_octs(\n            f, self.ds.child_grid_offset, self.ds.min_level, self.ds.max_level\n        )\n        # remember that the root grid is by itself; manually add it back in\n        inoll[0] = self.ds.domain_dimensions.prod() // 8\n        _level_child_offsets[0] = self.ds.root_grid_offset\n        self.nhydrovars = nhydrovars\n        self.inoll = inoll  # number of octs\n        self._level_oct_offsets = _level_oct_offsets\n        self._level_child_offsets = _level_child_offsets\n        self._level_count = inoll\n        return self._level_oct_offsets\n\n    def _count_art_octs(self, f, offset, MinLev, MaxLevelNow):\n        level_oct_offsets = [\n            0,\n        ]\n        level_child_offsets = [\n            0,\n        ]\n        f.seek(offset)\n        nchild, ntot = 8, 0\n        Level = np.zeros(MaxLevelNow + 1 - MinLev, dtype=\"int64\")\n        iNOLL = np.zeros(MaxLevelNow + 1 - MinLev, dtype=\"int64\")\n        iHOLL = np.zeros(MaxLevelNow + 1 - MinLev, dtype=\"int64\")\n        for Lev in range(MinLev + 1, MaxLevelNow + 1):\n            level_oct_offsets.append(f.tell())\n\n            # Get the info for this level, skip the rest\n            # print(\"Reading oct tree data for level\", Lev)\n            # print('offset:',f.tell())\n            Level[Lev], iNOLL[Lev], iHOLL[Lev] = fpu.read_vector(f, \"i\", \">\")\n            # print('Level %i : '%Lev, iNOLL)\n            # print('offset after level record:',f.tell())\n            nLevel = iNOLL[Lev]\n            ntot = ntot + nLevel\n\n            # Skip all the oct hierarchy data\n            ns = fpu.peek_record_size(f, endian=\">\")\n            size = struct.calcsize(\">i\") + ns + struct.calcsize(\">i\")\n            f.seek(f.tell() + size * nLevel)\n\n            level_child_offsets.append(f.tell())\n            # Skip the child vars data\n            ns = fpu.peek_record_size(f, endian=\">\")\n            size = struct.calcsize(\">i\") + ns + struct.calcsize(\">i\")\n            f.seek(f.tell() + size * nLevel * nchild)\n\n            # find nhydrovars\n            nhydrovars = 8 + 2\n        f.seek(offset)\n        return nhydrovars, iNOLL, level_oct_offsets, level_child_offsets\n\n    def _read_amr_level(self, oct_handler):\n        \"\"\"Open the oct file, read in octs level-by-level.\n        For each oct, only the position, index, level and domain\n        are needed - its position in the octree is found automatically.\n        The most important is finding all the information to feed\n        oct_handler.add\n        \"\"\"\n        self.level_offsets\n        f = open(self.ds._file_amr, \"rb\")\n        for level in range(1, self.ds.max_level + 1):\n            unitary_center, fl, iocts, nocts, root_level = _read_art_level_info(\n                f,\n                self._level_oct_offsets,\n                level,\n                coarse_grid=self.ds.domain_dimensions[0],\n                root_level=self.ds.root_level,\n            )\n            nocts_check = oct_handler.add(self.domain_id, level, unitary_center)\n            assert nocts_check == nocts\n            mylog.debug(\n                \"Added %07i octs on level %02i, cumulative is %07i\",\n                nocts,\n                level,\n                oct_handler.nocts,\n            )\n\n    def _read_amr_root(self, oct_handler):\n        self.level_offsets\n        # add the root *cell* not *oct* mesh\n        root_octs_side = self.ds.domain_dimensions[0] / 2\n        NX = np.ones(3) * root_octs_side\n        LE = np.array([0.0, 0.0, 0.0], dtype=\"float64\")\n        RE = np.array([1.0, 1.0, 1.0], dtype=\"float64\")\n        root_dx = (RE - LE) / NX\n        LL = LE + root_dx / 2.0\n        RL = RE - root_dx / 2.0\n        # compute floating point centers of root octs\n        root_fc = np.mgrid[\n            LL[0] : RL[0] : NX[0] * 1j,\n            LL[1] : RL[1] : NX[1] * 1j,\n            LL[2] : RL[2] : NX[2] * 1j,\n        ]\n        root_fc = np.vstack([p.ravel() for p in root_fc]).T\n        oct_handler.add(self.domain_id, 0, root_fc)\n        assert oct_handler.nocts == root_fc.shape[0]\n        mylog.debug(\n            \"Added %07i octs on level %02i, cumulative is %07i\",\n            root_octs_side**3,\n            0,\n            oct_handler.nocts,\n        )\n\n    def included(self, selector):\n        return True\n"
  },
  {
    "path": "yt/frontends/art/definitions.py",
    "content": "# If not otherwise specified, we are big endian\nendian = \">\"\n\nfluid_fields = [\n    \"Density\",\n    \"TotalEnergy\",\n    \"XMomentumDensity\",\n    \"YMomentumDensity\",\n    \"ZMomentumDensity\",\n    \"Pressure\",\n    \"Gamma\",\n    \"GasEnergy\",\n    \"MetalDensitySNII\",\n    \"MetalDensitySNIa\",\n    \"PotentialNew\",\n    \"PotentialOld\",\n]\n\nhydro_struct = [(\"pad1\", \">i\"), (\"idc\", \">i\"), (\"iOctCh\", \">i\")]\nfor field in fluid_fields:\n    hydro_struct += ((field, \">f\"),)\nhydro_struct += ((\"pad2\", \">i\"),)\n\nparticle_fields = [\n    \"particle_mass\",  # stars have variable mass\n    \"particle_index\",\n    \"particle_type\",\n    \"particle_position_x\",\n    \"particle_position_y\",\n    \"particle_position_z\",\n    \"particle_velocity_x\",\n    \"particle_velocity_y\",\n    \"particle_velocity_z\",\n    \"particle_mass_initial\",\n    \"particle_creation_time\",\n    \"particle_metallicity1\",\n    \"particle_metallicity2\",\n    \"particle_metallicity\",\n]\n\nparticle_star_fields = [\n    \"particle_mass\",\n    \"particle_mass_initial\",\n    \"particle_creation_time\",\n    \"particle_metallicity1\",\n    \"particle_metallicity2\",\n    \"particle_metallicity\",\n]\n\n\nfilename_pattern = {\n    \"amr\": [\"10MpcBox_\", \".d\"],\n    \"particle_header\": [\"PMcrd\", \".DAT\"],\n    \"particle_data\": [\"PMcrs\", \".DAT\"],\n    \"particle_stars\": [\"stars\", \".dat\"],\n}\n\namr_header_struct = [\n    (\"jname\", 1, \"256s\"),\n    ((\"istep\", \"t\", \"dt\", \"aexpn\", \"ainit\"), 1, \"iddff\"),\n    ((\"boxh\", \"Om0\", \"Oml0\", \"Omb0\", \"hubble\"), 5, \"f\"),\n    (\"nextras\", 1, \"i\"),\n    ((\"extra1\", \"extra2\"), 2, \"f\"),\n    (\"lextra\", 1, \"512s\"),\n    ((\"min_level\", \"max_level\"), 2, \"i\"),\n]\n\nparticle_header_struct = [\n    (\n        (\n            \"header\",\n            \"aexpn\",\n            \"aexp0\",\n            \"amplt\",\n            \"astep\",\n            \"istep\",\n            \"partw\",\n            \"tintg\",\n            \"Ekin\",\n            \"Ekin1\",\n            \"Ekin2\",\n            \"au0\",\n            \"aeu0\",\n            \"Nrow\",\n            \"Ngridc\",\n            \"Nspecies\",\n            \"Nseed\",\n            \"Om0\",\n            \"Oml0\",\n            \"hubble\",\n            \"Wp5\",\n            \"Ocurv\",\n            \"Omb0\",\n            \"extras\",\n            \"unknown\",\n        ),\n        1,\n        \"45sffffi\" + \"fffffff\" + \"iiii\" + \"ffffff\" + \"396s\" + \"f\",\n    )\n]\n\ndmparticle_header_struct = [\n    (\n        \"header\",\n        \"aexpn\",\n        \"aexp0\",\n        \"amplt\",\n        \"astep\",\n        \"istep\",\n        \"partw\",\n        \"tintg\",\n        \"Ekin\",\n        \"Ekin1\",\n        \"Ekin2\",\n        \"au0\",\n        \"aeu0\",\n        \"Nrow\",\n        \"Ngridc\",\n        \"Nspecies\",\n        \"Nseed\",\n        \"Om0\",\n        \"Oml0\",\n        \"hubble\",\n        \"Wp5\",\n        \"Ocurv\",\n        \"wspecies\",\n        \"lspecies\",\n        \"extras\",\n        \"boxsize\",\n    ),\n    (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 10, 79, 1),\n]\n\nstar_struct = [\n    (\">d\", (\"t_stars\", \"a_stars\")),\n    (\">i\", \"nstars\"),\n    (\">d\", (\"ws_old\", \"ws_oldi\")),\n    (\">f\", \"particle_mass\"),\n    (\">f\", \"particle_mass_initial\"),\n    (\">f\", \"particle_creation_time\"),\n    (\">f\", \"particle_metallicity1\"),\n    (\">f\", \"particle_metallicity2\"),\n]\n\nstar_name_map = {\n    \"particle_mass\": \"mass\",\n    \"particle_mass_initial\": \"imass\",\n    \"particle_creation_time\": \"tbirth\",\n    \"particle_metallicity1\": \"metallicity1\",\n    \"particle_metallicity2\": \"metallicity2\",\n    \"particle_metallicity\": \"metallicity\",\n}\n\nconstants = {\n    \"Y_p\": 0.245,\n    \"gamma\": 5.0 / 3.0,\n    \"T_CMB0\": 2.726,\n    \"T_min\": 300.0,\n    \"wmu\": 4.0 / (8.0 - 5.0 * 0.245),\n}\n\nseek_extras = 137\n"
  },
  {
    "path": "yt/frontends/art/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\nb_units = \"code_magnetic\"\nra_units = \"code_length / code_time**2\"\nrho_units = \"code_mass / code_length**3\"\nvel_units = \"code_velocity\"\n# NOTE: ARTIO uses momentum density.\nmom_units = \"code_mass / (code_length**2 * code_time)\"\nen_units = \"code_mass*code_velocity**2/code_length**3\"\n\n\nclass ARTFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"Density\", (rho_units, [\"density\"], None)),\n        (\"TotalEnergy\", (en_units, [\"total_energy_density\"], None)),\n        (\"XMomentumDensity\", (mom_units, [\"momentum_density_x\"], None)),\n        (\"YMomentumDensity\", (mom_units, [\"momentum_density_y\"], None)),\n        (\"ZMomentumDensity\", (mom_units, [\"momentum_density_z\"], None)),\n        (\"Pressure\", (\"\", [\"pressure\"], None)),  # Unused\n        (\"Gamma\", (\"\", [\"gamma\"], None)),\n        (\"GasEnergy\", (en_units, [\"thermal_energy_density\"], None)),\n        (\"MetalDensitySNII\", (rho_units, [\"metal_ii_density\"], None)),\n        (\"MetalDensitySNIa\", (rho_units, [\"metal_ia_density\"], None)),\n        (\"PotentialNew\", (\"\", [\"potential\"], None)),\n        (\"PotentialOld\", (\"\", [\"gas_potential\"], None)),\n    )\n\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_position_z\", (\"code_length\", [], None)),\n        (\"particle_velocity_x\", (vel_units, [], None)),\n        (\"particle_velocity_y\", (vel_units, [], None)),\n        (\"particle_velocity_z\", (vel_units, [], None)),\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"particle_index\", (\"\", [], None)),\n        (\"particle_species\", (\"\", [\"particle_type\"], None)),\n        (\"particle_creation_time\", (\"Gyr\", [], None)),\n        (\"particle_mass_initial\", (\"code_mass\", [], None)),\n        (\"particle_metallicity1\", (\"\", [], None)),\n        (\"particle_metallicity2\", (\"\", [], None)),\n    )\n\n    def setup_fluid_fields(self):\n        unit_system = self.ds.unit_system\n\n        def _temperature(data):\n            r0 = data.ds.parameters[\"boxh\"] / data.ds.parameters[\"ng\"]\n            tr = data.ds.quan(3.03e5 * r0**2, \"K/code_velocity**2\")\n            tr *= data.ds.parameters[\"wmu\"] * data.ds.parameters[\"Om0\"]\n            tr *= data.ds.parameters[\"gamma\"] - 1.0\n            tr /= data.ds.parameters[\"aexpn\"] ** 2\n            return tr * data[\"art\", \"GasEnergy\"] / data[\"art\", \"Density\"]\n\n        self.add_field(\n            (\"gas\", \"temperature\"),\n            sampling_type=\"cell\",\n            function=_temperature,\n            units=unit_system[\"temperature\"],\n        )\n\n        def _get_vel(axis):\n            def velocity(data):\n                return data[\"gas\", f\"momentum_density_{axis}\"] / data[\"gas\", \"density\"]\n\n            return velocity\n\n        for ax in \"xyz\":\n            self.add_field(\n                (\"gas\", f\"velocity_{ax}\"),\n                sampling_type=\"cell\",\n                function=_get_vel(ax),\n                units=unit_system[\"velocity\"],\n            )\n\n        def _momentum_magnitude(data):\n            tr = (\n                data[\"gas\", \"momentum_density_x\"] ** 2\n                + data[\"gas\", \"momentum_density_y\"] ** 2\n                + data[\"gas\", \"momentum_density_z\"] ** 2\n            ) ** 0.5\n            tr *= data[\"index\", \"cell_volume\"].in_units(\"cm**3\")\n            return tr\n\n        self.add_field(\n            (\"gas\", \"momentum_magnitude\"),\n            sampling_type=\"cell\",\n            function=_momentum_magnitude,\n            units=unit_system[\"momentum\"],\n        )\n\n        def _velocity_magnitude(data):\n            tr = data[\"gas\", \"momentum_magnitude\"]\n            tr /= data[\"gas\", \"cell_mass\"]\n            return tr\n\n        self.add_field(\n            (\"gas\", \"velocity_magnitude\"),\n            sampling_type=\"cell\",\n            function=_velocity_magnitude,\n            units=unit_system[\"velocity\"],\n        )\n\n        def _metal_density(data):\n            tr = data[\"gas\", \"metal_ia_density\"]\n            tr += data[\"gas\", \"metal_ii_density\"]\n            return tr\n\n        self.add_field(\n            (\"gas\", \"metal_density\"),\n            sampling_type=\"cell\",\n            function=_metal_density,\n            units=unit_system[\"density\"],\n        )\n\n        def _metal_mass_fraction(data):\n            tr = data[\"gas\", \"metal_density\"]\n            tr /= data[\"gas\", \"density\"]\n            return tr\n\n        self.add_field(\n            (\"gas\", \"metal_mass_fraction\"),\n            sampling_type=\"cell\",\n            function=_metal_mass_fraction,\n            units=\"\",\n        )\n\n        def _H_mass_fraction(data):\n            tr = 1.0 - data.ds.parameters[\"Y_p\"] - data[\"gas\", \"metal_mass_fraction\"]\n            return tr\n\n        self.add_field(\n            (\"gas\", \"H_mass_fraction\"),\n            sampling_type=\"cell\",\n            function=_H_mass_fraction,\n            units=\"\",\n        )\n\n        def _metallicity(data):\n            tr = data[\"gas\", \"metal_mass_fraction\"]\n            tr /= data[\"gas\", \"H_mass_fraction\"]\n            return tr\n\n        self.add_field(\n            (\"gas\", \"metallicity\"),\n            sampling_type=\"cell\",\n            function=_metallicity,\n            units=\"\",\n        )\n\n        atoms = [\n            \"C\",\n            \"N\",\n            \"O\",\n            \"F\",\n            \"Ne\",\n            \"Na\",\n            \"Mg\",\n            \"Al\",\n            \"Si\",\n            \"P\",\n            \"S\",\n            \"Cl\",\n            \"Ar\",\n            \"K\",\n            \"Ca\",\n            \"Sc\",\n            \"Ti\",\n            \"V\",\n            \"Cr\",\n            \"Mn\",\n            \"Fe\",\n            \"Co\",\n            \"Ni\",\n            \"Cu\",\n            \"Zn\",\n        ]\n\n        def _specific_metal_density_function(atom):\n            def _specific_metal_density(data):\n                nucleus_densityIa = (\n                    data[\"gas\", \"metal_ia_density\"] * SNIa_abundance[atom]\n                )\n                nucleus_densityII = (\n                    data[\"gas\", \"metal_ii_density\"] * SNII_abundance[atom]\n                )\n                return nucleus_densityIa + nucleus_densityII\n\n            return _specific_metal_density\n\n        for atom in atoms:\n            self.add_field(\n                (\"gas\", f\"{atom}_nuclei_mass_density\"),\n                sampling_type=\"cell\",\n                function=_specific_metal_density_function(atom),\n                units=unit_system[\"density\"],\n            )\n\n\n# based on Iwamoto et al 1999\n# mass fraction of each atom in SNIa metal\nSNIa_abundance = {\n    \"H\": 0.00e00,\n    \"He\": 0.00e00,\n    \"C\": 3.52e-02,\n    \"N\": 8.47e-07,\n    \"O\": 1.04e-01,\n    \"F\": 4.14e-10,\n    \"Ne\": 3.30e-03,\n    \"Na\": 4.61e-05,\n    \"Mg\": 6.25e-03,\n    \"Al\": 7.19e-04,\n    \"Si\": 1.14e-01,\n    \"P\": 2.60e-04,\n    \"S\": 6.35e-02,\n    \"Cl\": 1.27e-04,\n    \"Ar\": 1.14e-02,\n    \"K\": 5.72e-05,\n    \"Ca\": 8.71e-03,\n    \"Sc\": 1.61e-07,\n    \"Ti\": 2.50e-04,\n    \"V\": 5.46e-05,\n    \"Cr\": 6.19e-03,\n    \"Mn\": 6.47e-03,\n    \"Fe\": 5.46e-01,\n    \"Co\": 7.59e-04,\n    \"Ni\": 9.17e-02,\n    \"Cu\": 2.19e-06,\n    \"Zn\": 2.06e-05,\n}\n\n# mass fraction of each atom in SNII metal\nSNII_abundance = {\n    \"H\": 0.00e00,\n    \"He\": 0.00e00,\n    \"C\": 3.12e-02,\n    \"N\": 6.15e-04,\n    \"O\": 7.11e-01,\n    \"F\": 4.57e-10,\n    \"Ne\": 9.12e-02,\n    \"Na\": 2.56e-03,\n    \"Mg\": 4.84e-02,\n    \"Al\": 5.83e-03,\n    \"Si\": 4.81e-02,\n    \"P\": 4.77e-04,\n    \"S\": 1.62e-02,\n    \"Cl\": 4.72e-05,\n    \"Ar\": 3.15e-03,\n    \"K\": 2.65e-05,\n    \"Ca\": 2.31e-03,\n    \"Sc\": 9.02e-08,\n    \"Ti\": 5.18e-05,\n    \"V\": 3.94e-06,\n    \"Cr\": 5.18e-04,\n    \"Mn\": 1.52e-04,\n    \"Fe\": 3.58e-02,\n    \"Co\": 2.86e-05,\n    \"Ni\": 2.35e-03,\n    \"Cu\": 4.90e-07,\n    \"Zn\": 7.46e-06,\n}\n"
  },
  {
    "path": "yt/frontends/art/io.py",
    "content": "import os.path\nfrom collections import defaultdict\nfrom functools import partial\n\nimport numpy as np\n\nfrom yt.frontends.art.definitions import (\n    hydro_struct,\n    particle_fields,\n    particle_star_fields,\n    star_struct,\n)\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.fortran_utils import read_vector, skip\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\n\n\nclass IOHandlerART(BaseIOHandler):\n    _dataset_type = \"art\"\n    tb, ages = None, None\n    cache = None\n    masks = None\n    caching = True\n\n    def __init__(self, *args, **kwargs):\n        self.cache = {}\n        self.masks = {}\n        super().__init__(*args, **kwargs)\n        self.ws = self.ds.parameters[\"wspecies\"]\n        self.ls = self.ds.parameters[\"lspecies\"]\n        self.file_particle = self.ds._file_particle_data\n        self.file_stars = self.ds._file_particle_stars\n        self.Nrow = self.ds.parameters[\"Nrow\"]\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        # Chunks in this case will have affiliated domain subset objects\n        # Each domain subset will contain a hydro_offset array, which gives\n        # pointers to level-by-level hydro information\n        tr = defaultdict(list)\n        cp = 0\n        for chunk in chunks:\n            for subset in chunk.objs:\n                # Now we read the entire thing\n                f = open(subset.domain.ds._file_amr, \"rb\")\n                # This contains the boundary information, so we skim through\n                # and pick off the right vectors\n                rv = subset.fill(f, fields, selector)\n                for ft, f in fields:\n                    d = rv.pop(f)\n                    mylog.debug(\n                        \"Filling %s with %s (%0.3e %0.3e) (%s:%s)\",\n                        f,\n                        d.size,\n                        d.min(),\n                        d.max(),\n                        cp,\n                        cp + d.size,\n                    )\n                    tr[ft, f].append(d)\n                cp += d.size\n        d = {}\n        for field in fields:\n            d[field] = np.concatenate(tr.pop(field))\n        return d\n\n    def _get_mask(self, selector, ftype):\n        key = (selector, ftype)\n        if key in self.masks.keys() and self.caching:\n            return self.masks[key]\n        pstr = \"particle_position_%s\"\n        x, y, z = (self._get_field((ftype, pstr % ax)) for ax in \"xyz\")\n        mask = selector.select_points(x, y, z, 0.0)\n        if self.caching:\n            self.masks[key] = mask\n            return self.masks[key]\n        else:\n            return mask\n\n    def _read_particle_coords(self, chunks, ptf):\n        chunks = list(chunks)\n        for _chunk in chunks:\n            for ptype in sorted(ptf):\n                x = self._get_field((ptype, \"particle_position_x\"))\n                y = self._get_field((ptype, \"particle_position_y\"))\n                z = self._get_field((ptype, \"particle_position_z\"))\n                yield ptype, (x, y, z), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        chunks = list(chunks)\n        for _chunk in chunks:\n            for ptype, field_list in sorted(ptf.items()):\n                x = self._get_field((ptype, \"particle_position_x\"))\n                y = self._get_field((ptype, \"particle_position_y\"))\n                z = self._get_field((ptype, \"particle_position_z\"))\n                mask = selector.select_points(x, y, z, 0.0)\n                if mask is None:\n                    continue\n                for field in field_list:\n                    data = self._get_field((ptype, field))\n                    yield (ptype, field), data[mask]\n\n    def _get_field(self, field):\n        if field in self.cache.keys() and self.caching:\n            mylog.debug(\"Cached %s\", str(field))\n            return self.cache[field]\n        mylog.debug(\"Reading %s\", str(field))\n        tr = {}\n        ftype, fname = field\n        ptmax = self.ws[-1]\n        pbool, idxa, idxb = _determine_field_size(self.ds, ftype, self.ls, ptmax)\n        npa = idxb - idxa\n        sizes = np.diff(np.concatenate(([0], self.ls)))\n        rp = partial(\n            read_particles, self.file_particle, self.Nrow, idxa=idxa, idxb=idxb\n        )\n        for ax in \"xyz\":\n            if fname.startswith(f\"particle_position_{ax}\"):\n                dd = self.ds.domain_dimensions[0]\n                off = 1.0 / dd\n                tr[field] = rp(fields=[ax])[0] / dd - off\n            if fname.startswith(f\"particle_velocity_{ax}\"):\n                (tr[field],) = rp(fields=[\"v\" + ax])\n        if fname.startswith(\"particle_mass\"):\n            a = 0\n            data = np.zeros(npa, dtype=\"f8\")\n            for ptb, size, m in zip(pbool, sizes, self.ws, strict=True):\n                if ptb:\n                    data[a : a + size] = m\n                    a += size\n            tr[field] = data\n        elif fname == \"particle_index\":\n            tr[field] = np.arange(idxa, idxb)\n        elif fname == \"particle_type\":\n            a = 0\n            data = np.zeros(npa, dtype=\"int64\")\n            for i, (ptb, size) in enumerate(zip(pbool, sizes, strict=True)):\n                if ptb:\n                    data[a : a + size] = i\n                    a += size\n            tr[field] = data\n        if pbool[-1] and fname in particle_star_fields:\n            data = read_star_field(self.file_stars, field=fname)\n            temp = tr.get(field, np.zeros(npa, \"f8\"))\n            nstars = self.ls[-1] - self.ls[-2]\n            if nstars > 0:\n                temp[-nstars:] = data\n            tr[field] = temp\n        if fname == \"particle_creation_time\":\n            self.tb, self.ages, data = interpolate_ages(\n                tr[field][-nstars:],\n                self.file_stars,\n                self.tb,\n                self.ages,\n                self.ds.current_time,\n            )\n            temp = tr.get(field, np.zeros(npa, \"f8\"))\n            temp[-nstars:] = data\n            tr[field] = temp\n            del data\n        # We check again, after it's been filled\n        if fname.startswith(\"particle_mass\"):\n            # We now divide by NGrid in order to make this match up.  Note that\n            # this means that even when requested in *code units*, we are\n            # giving them as modified by the ng value.  This only works for\n            # dark_matter -- stars are regular matter.\n            tr[field] /= self.ds.domain_dimensions.prod()\n        if tr == {}:\n            tr = {f: np.array([]) for f in [field]}\n        if self.caching:\n            self.cache[field] = tr[field]\n            return self.cache[field]\n        else:\n            return tr[field]\n\n\nclass IOHandlerDarkMatterART(IOHandlerART):\n    _dataset_type = \"dm_art\"\n\n    def _count_particles(self, data_file):\n        return {\n            k: self.ds.parameters[\"lspecies\"][i]\n            for i, k in enumerate(self.ds.particle_types_raw)\n        }\n\n    def _identify_fields(self, domain):\n        field_list = []\n        self.particle_field_list = list(particle_fields)\n        for ptype in self.ds.particle_types_raw:\n            for pfield in self.particle_field_list:\n                pfn = (ptype, pfield)\n                field_list.append(pfn)\n        return field_list, {}\n\n    def _get_field(self, field):\n        if field in self.cache.keys() and self.caching:\n            mylog.debug(\"Cached %s\", str(field))\n            return self.cache[field]\n        mylog.debug(\"Reading %s\", str(field))\n        tr = {}\n        ftype, fname = field\n        ptmax = self.ws[-1]\n        pbool, idxa, idxb = _determine_field_size(self.ds, ftype, self.ls, ptmax)\n        npa = idxb - idxa\n        sizes = np.diff(np.concatenate(([0], self.ls)))\n        rp = partial(\n            read_particles, self.file_particle, self.Nrow, idxa=idxa, idxb=idxb\n        )\n        for ax in \"xyz\":\n            if fname.startswith(f\"particle_position_{ax}\"):\n                # This is not the same as domain_dimensions\n                dd = self.ds.parameters[\"ng\"]\n                off = 1.0 / dd\n                tr[field] = rp(fields=[ax])[0] / dd - off\n            if fname.startswith(f\"particle_velocity_{ax}\"):\n                (tr[field],) = rp([\"v\" + ax])\n        if fname.startswith(\"particle_mass\"):\n            a = 0\n            data = np.zeros(npa, dtype=\"f8\")\n            for ptb, size, m in zip(pbool, sizes, self.ws, strict=True):\n                if ptb:\n                    data[a : a + size] = m\n                    a += size\n            tr[field] = data\n        elif fname == \"particle_index\":\n            tr[field] = np.arange(idxa, idxb)\n        elif fname == \"particle_type\":\n            a = 0\n            data = np.zeros(npa, dtype=\"int64\")\n            for i, (ptb, size) in enumerate(zip(pbool, sizes, strict=True)):\n                if ptb:\n                    data[a : a + size] = i\n                    a += size\n            tr[field] = data\n        # We check again, after it's been filled\n        if fname.startswith(\"particle_mass\"):\n            # We now divide by NGrid in order to make this match up.  Note that\n            # this means that even when requested in *code units*, we are\n            # giving them as modified by the ng value.  This only works for\n            # dark_matter -- stars are regular matter.\n            tr[field] /= self.ds.domain_dimensions.prod()\n        if tr == {}:\n            tr[field] = np.array([])\n        if self.caching:\n            self.cache[field] = tr[field]\n            return self.cache[field]\n        else:\n            return tr[field]\n\n    def _yield_coordinates(self, data_file):\n        for ptype in self.ds.particle_types_raw:\n            x = self._get_field((ptype, \"particle_position_x\"))\n            y = self._get_field((ptype, \"particle_position_y\"))\n            z = self._get_field((ptype, \"particle_position_z\"))\n\n            yield ptype, np.stack((x, y, z), axis=-1)\n\n\ndef _determine_field_size(pf, field, lspecies, ptmax):\n    pbool = np.zeros(len(lspecies), dtype=\"bool\")\n    idxas = np.concatenate(\n        (\n            [\n                0,\n            ],\n            lspecies[:-1],\n        )\n    )\n    idxbs = lspecies\n    if \"specie\" in field:\n        index = int(field.replace(\"specie\", \"\"))\n        pbool[index] = True\n    else:\n        raise RuntimeError\n    idxa, idxb = idxas[pbool][0], idxbs[pbool][-1]\n    return pbool, idxa, idxb\n\n\ndef interpolate_ages(\n    data, file_stars, interp_tb=None, interp_ages=None, current_time=None\n):\n    if interp_tb is None:\n        t_stars, a_stars = read_star_field(file_stars, field=\"t_stars\")\n        # timestamp of file should match amr timestamp\n        if current_time:\n            tdiff = YTQuantity(b2t(t_stars), \"Gyr\") - current_time.in_units(\"Gyr\")\n            if np.abs(tdiff) > 1e-4:\n                mylog.info(\"Timestamp mismatch in star particle header: %s\", tdiff)\n        mylog.info(\"Interpolating ages\")\n        interp_tb, interp_ages = b2t(data)\n        interp_tb = YTArray(interp_tb, \"Gyr\")\n        interp_ages = YTArray(interp_ages, \"Gyr\")\n    temp = np.interp(data, interp_tb, interp_ages)\n    return interp_tb, interp_ages, temp\n\n\ndef _read_art_level_info(\n    f, level_oct_offsets, level, coarse_grid=128, ncell0=None, root_level=None\n):\n    pos = f.tell()\n    f.seek(level_oct_offsets[level])\n    # Get the info for this level, skip the rest\n    junk, nLevel, iOct = read_vector(f, \"i\", \">\")\n\n    # fortran indices start at 1\n\n    # Skip all the oct index data\n    le = np.zeros((nLevel, 3), dtype=\"int64\")\n    fl = np.ones((nLevel, 6), dtype=\"int64\")\n    iocts = np.zeros(nLevel + 1, dtype=\"int64\")\n    idxa, idxb = 0, 0\n    chunk = int(1e6)  # this is ~111MB for 15 dimensional 64 bit arrays\n    left = nLevel\n    while left > 0:\n        this_chunk = min(chunk, left)\n        idxb = idxa + this_chunk\n        data = np.fromfile(f, dtype=\">i\", count=this_chunk * 15)\n        data = data.reshape(this_chunk, 15)\n        left -= this_chunk\n        le[idxa:idxb, :] = data[:, 1:4]\n        fl[idxa:idxb, 1] = np.arange(idxa, idxb)\n        # pad byte is last, LL2, then ioct right before it\n        iocts[idxa:idxb] = data[:, -3]\n        idxa = idxa + this_chunk\n    del data\n\n    # emulate fortran code\n    #     do ic1 = 1 , nLevel\n    #       read(19) (iOctPs(i,iOct),i=1,3),(iOctNb(i,iOct),i=1,6),\n    # &                iOctPr(iOct), iOctLv(iOct), iOctLL1(iOct),\n    # &                iOctLL2(iOct)\n    #       iOct = iOctLL1(iOct)\n\n    # ioct always represents the index of the next variable\n    # not the current, so shift forward one index\n    # the last index isn't used\n    iocts[1:] = iocts[:-1]  # shift\n    iocts = iocts[:nLevel]  # chop off the last, unused, index\n    iocts[0] = iOct  # starting value\n\n    # now correct iocts for fortran indices start @ 1\n    iocts = iocts - 1\n\n    assert np.unique(iocts).shape[0] == nLevel\n\n    # left edges are expressed as if they were on\n    # level 15, so no matter what level max(le)=2**15\n    # correct to the yt convention\n    # le = le/2**(root_level-1-level)-1\n\n    # try to find the root_level first\n    def cfc(root_level, level, le):\n        d_x = 1.0 / (2.0 ** (root_level - level + 1))\n        fc = (d_x * le) - 2 ** (level - 1)\n        return fc\n\n    if root_level is None:\n        root_level = np.floor(np.log2(le.max() * 1.0 / coarse_grid))\n        root_level = root_level.astype(\"int64\")\n        for _ in range(10):\n            fc = cfc(root_level, level, le)\n            go = np.diff(np.unique(fc)).min() < 1.1\n            if go:\n                break\n            root_level += 1\n    else:\n        fc = cfc(root_level, level, le)\n    unitary_center = fc / (coarse_grid * 2.0 ** (level - 1))\n    assert np.all(unitary_center < 1.0)\n\n    # again emulate the fortran code\n    # This is all for calculating child oct locations\n    # iC_ = iC + nbshift\n    # iO = ishft ( iC_ , - ndim )\n    # id = ishft ( 1, MaxLevel - iOctLv(iO) )\n    # j  = iC_ + 1 - ishft( iO , ndim )\n    # Posx   = d_x * (iOctPs(1,iO) + sign ( id , idelta(j,1) ))\n    # Posy   = d_x * (iOctPs(2,iO) + sign ( id , idelta(j,2) ))\n    # Posz   = d_x * (iOctPs(3,iO) + sign ( id , idelta(j,3) ))\n    # idelta = [[-1,  1, -1,  1, -1,  1, -1,  1],\n    #           [-1, -1,  1,  1, -1, -1,  1,  1],\n    #           [-1, -1, -1, -1,  1,  1,  1,  1]]\n    # idelta = np.array(idelta)\n    # if ncell0 is None:\n    #     ncell0 = coarse_grid**3\n    # nchild = 8\n    # ndim = 3\n    # nshift = nchild -1\n    # nbshift = nshift - ncell0\n    # iC = iocts #+ nbshift\n    # iO = iC >> ndim #possibly >>\n    # id = 1 << (root_level - level)\n    # j = iC + 1 - ( iO << 3)\n    # delta = np.abs(id)*idelta[:,j-1]\n\n    # try without the -1\n    # le = le/2**(root_level+1-level)\n    # now read the hvars and vars arrays\n    # we are looking for iOctCh\n    # we record if iOctCh is >0, in which it is subdivided\n    # iOctCh  = np.zeros((nLevel+1,8),dtype='bool')\n    f.seek(pos)\n    return unitary_center, fl, iocts, nLevel, root_level\n\n\ndef get_ranges(\n    skip, count, field, words=6, real_size=4, np_per_page=4096**2, num_pages=1\n):\n    # translate every particle index into a file position ranges\n    ranges = []\n    arr_size = np_per_page * real_size\n    idxa, idxb = 0, 0\n    posa, posb = 0, 0\n    for _page in range(num_pages):\n        idxb += np_per_page\n        for i, fname in enumerate([\"x\", \"y\", \"z\", \"vx\", \"vy\", \"vz\"]):\n            posb += arr_size\n            if i == field or fname == field:\n                if skip < np_per_page and count > 0:\n                    left_in_page = np_per_page - skip\n                    this_count = min(left_in_page, count)\n                    count -= this_count\n                    start = posa + skip * real_size\n                    end = posa + this_count * real_size\n                    ranges.append((start, this_count))\n                    skip = 0\n                    assert end <= posb\n                else:\n                    skip -= np_per_page\n            posa += arr_size\n        idxa += np_per_page\n    assert count == 0\n    return ranges\n\n\ndef read_particles(file, Nrow, idxa, idxb, fields):\n    words = 6  # words (reals) per particle: x,y,z,vx,vy,vz\n    real_size = 4  # for file_particle_data; not always true?\n    np_per_page = Nrow**2  # defined in ART a_setup.h, # of particles/page\n    num_pages = os.path.getsize(file) // (real_size * words * np_per_page)\n    fh = open(file)\n    skip, count = idxa, idxb - idxa\n    kwargs = {\n        \"words\": words,\n        \"real_size\": real_size,\n        \"np_per_page\": np_per_page,\n        \"num_pages\": num_pages,\n    }\n    arrs = []\n    for field in fields:\n        ranges = get_ranges(skip, count, field, **kwargs)\n        data = None\n        for seek, this_count in ranges:\n            fh.seek(seek)\n            temp = np.fromfile(fh, count=this_count, dtype=\">f4\")\n            if data is None:\n                data = temp\n            else:\n                data = np.concatenate((data, temp))\n        arrs.append(data.astype(\"f8\"))\n    fh.close()\n    return arrs\n\n\ndef read_star_field(file, field=None):\n    data = {}\n    with open(file, \"rb\") as fh:\n        for dtype, variables in star_struct:\n            found = (\n                isinstance(variables, tuple) and field in variables\n            ) or field == variables\n            if found:\n                data[field] = read_vector(fh, dtype[1], dtype[0])\n            else:\n                skip(fh, endian=\">\")\n    return data.pop(field)\n\n\ndef _read_child_mask_level(f, level_child_offsets, level, nLevel, nhydro_vars):\n    f.seek(level_child_offsets[level])\n    ioctch = np.zeros(nLevel, dtype=\"uint8\")\n    idc = np.zeros(nLevel, dtype=\"int32\")\n\n    chunk = int(1e6)\n    left = nLevel\n    width = nhydro_vars + 6\n    a, b = 0, 0\n    while left > 0:\n        chunk = min(chunk, left)\n        b += chunk\n        arr = np.fromfile(f, dtype=\">i\", count=chunk * width)\n        arr = arr.reshape((width, chunk), order=\"F\")\n        assert np.all(arr[0, :] == arr[-1, :])  # pads must be equal\n        idc[a:b] = arr[1, :] - 1  # fix fortran indexing\n        ioctch[a:b] = arr[2, :] == 0  # if it is above zero, then refined available\n        # zero in the mask means there is refinement available\n        a = b\n        left -= chunk\n    assert left == 0\n    return idc, ioctch\n\n\nnchem = 8 + 2\ndtyp = np.dtype(f\">i4,>i8,>i8,>{nchem}f4,>2f4,>i4\")\n\n\ndef _read_child_level(\n    f,\n    level_child_offsets,\n    level_oct_offsets,\n    level_info,\n    level,\n    fields,\n    domain_dimensions,\n    ncell0,\n    nhydro_vars=10,\n    nchild=8,\n    noct_range=None,\n):\n    # emulate the fortran code for reading cell data\n    # read ( 19 ) idc, iOctCh(idc), (hvar(i,idc),i=1,nhvar),\n    #    &                 (var(i,idc), i=2,3)\n    # contiguous 8-cell sections are for the same oct;\n    # ie, we don't write out just the 0 cells, then the 1 cells\n    # optionally, we only read noct_range to save memory\n    left_index, fl, octs, nocts, root_level = _read_art_level_info(\n        f, level_oct_offsets, level, coarse_grid=domain_dimensions[0]\n    )\n    if noct_range is None:\n        nocts = level_info[level]\n        ncells = nocts * 8\n        f.seek(level_child_offsets[level])\n        arr = np.fromfile(f, dtype=hydro_struct, count=ncells)\n        assert np.all(arr[\"pad1\"] == arr[\"pad2\"])  # pads must be equal\n        # idc = np.argsort(arr['idc']) #correct fortran indices\n        # translate idc into icell, and then to iOct\n        icell = (arr[\"idc\"] >> 3) << 3\n        iocts = (icell - ncell0) / nchild  # without a F correction, there's a +1\n        # assert that the children are read in the same order as the octs\n        assert np.all(octs == iocts[::nchild])\n    else:\n        start, end = noct_range\n        nocts = min(end - start, level_info[level])\n        end = start + nocts\n        ncells = nocts * 8\n        skip = np.dtype(hydro_struct).itemsize * start * 8\n        f.seek(level_child_offsets[level] + skip)\n        arr = np.fromfile(f, dtype=hydro_struct, count=ncells)\n        assert np.all(arr[\"pad1\"] == arr[\"pad2\"])  # pads must be equal\n    source = {}\n    for field in fields:\n        sh = (nocts, 8)\n        source[field] = np.reshape(arr[field], sh, order=\"C\").astype(\"float64\")\n    return source\n\n\ndef _read_root_level(f, level_offsets, level_info, nhydro_vars=10):\n    nocts = level_info[0]\n    f.seek(level_offsets[0])  # Ditch the header\n    hvar = read_vector(f, \"f\", \">\")\n    var = read_vector(f, \"f\", \">\")\n    hvar = hvar.reshape((nhydro_vars, nocts * 8), order=\"F\")\n    var = var.reshape((2, nocts * 8), order=\"F\")\n    arr = np.concatenate((hvar, var))\n    return arr\n\n\n# All of these functions are to convert from hydro time var to\n# proper time\nsqrt = np.sqrt\nsign = np.sign\n\n\ndef find_root(f, a, b, tol=1e-6):\n    c = (a + b) / 2.0\n    last = -np.inf\n    assert sign(f(a)) != sign(f(b))\n    while np.abs(f(c) - last) > tol:\n        last = f(c)\n        if sign(last) == sign(f(b)):\n            b = c\n        else:\n            a = c\n        c = (a + b) / 2.0\n    return c\n\n\ndef quad(fintegrand, xmin, xmax, n=1e4):\n    from yt._maintenance.numpy2_compat import trapezoid\n\n    spacings = np.logspace(np.log10(xmin), np.log10(xmax), num=int(n))\n    integrand_arr = fintegrand(spacings)\n    val = trapezoid(integrand_arr, dx=np.diff(spacings))\n    return val\n\n\ndef a2b(at, Om0=0.27, Oml0=0.73, h=0.700):\n    def f_a2b(x):\n        val = 0.5 * sqrt(Om0) / x**3.0\n        val /= sqrt(Om0 / x**3.0 + Oml0 + (1.0 - Om0 - Oml0) / x**2.0)\n        return val\n\n    # val, err = si.quad(f_a2b,1,at)\n    val = quad(f_a2b, 1, at)\n    return val\n\n\ndef b2a(bt, **kwargs):\n    # converts code time into expansion factor\n    # if Om0 ==1and OmL == 0 then b2a is (1 / (1-td))**2\n    # if bt < -190.0 or bt > -.10:  raise 'bt outside of range'\n    def f_b2a(at):\n        return a2b(at, **kwargs) - bt\n\n    return find_root(f_b2a, 1e-4, 1.1)\n    # return so.brenth(f_b2a,1e-4,1.1)\n    # return brent.brent(f_b2a)\n\n\ndef a2t(at, Om0=0.27, Oml0=0.73, h=0.700):\n    def integrand(x):\n        return 1.0 / (x * sqrt(Oml0 + Om0 * x**-3.0))\n\n    current_time = quad(integrand, 1e-4, at)\n    current_time *= 9.779 / h\n    return current_time\n\n\ndef b2t(tb, n=1e2, logger=None, **kwargs):\n    tb = np.array(tb)\n    if isinstance(tb, float):\n        return a2t(b2a(tb))\n    if tb.shape == ():\n        return a2t(b2a(tb))\n    if len(tb) < n:\n        n = len(tb)\n    tbs = -1.0 * np.logspace(np.log10(-tb.min()), np.log10(-tb.max()), int(n))\n    ages = []\n    for i, tbi in enumerate(tbs):\n        ages += (a2t(b2a(tbi)),)\n        if logger:\n            logger(i)\n    ages = np.array(ages)\n    return tbs, ages\n"
  },
  {
    "path": "yt/frontends/art/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/art/tests/test_outputs.py",
    "content": "from numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.frontends.art.api import ARTDataset\nfrom yt.testing import (\n    ParticleSelectionComparison,\n    requires_file,\n    units_override_check,\n)\nfrom yt.units.yt_array import YTQuantity\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    PixelizedProjectionValuesTest,\n    data_dir_load,\n    requires_ds,\n)\n\n_fields = (\n    (\"gas\", \"density\"),\n    (\"gas\", \"temperature\"),\n    (\"all\", \"particle_mass\"),\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_velocity_y\"),\n)\n\nd9p = \"D9p_500/10MpcBox_HartGal_csf_a0.500.d\"\ndmonly = \"DMonly/PMcrs0.0100.DAT\"\n\n\n@requires_ds(d9p, big_data=True)\ndef test_d9p():\n    ds = data_dir_load(d9p)\n    ds.index\n    assert_equal(str(ds), \"10MpcBox_HartGal_csf_a0.500.d\")\n    dso = [None, (\"sphere\", (\"max\", (0.1, \"unitary\")))]\n    for field in _fields:\n        for axis in [0, 1]:\n            for dobj_name in dso:\n                for weight_field in [None, (\"gas\", \"density\")]:\n                    if field[0] not in ds.particle_types:\n                        yield PixelizedProjectionValuesTest(\n                            d9p, axis, field, weight_field, dobj_name\n                        )\n                yield FieldValuesTest(\n                    d9p,\n                    field,\n                    obj_type=dobj_name,\n                    particle_type=field[0] in ds.particle_types,\n                )\n\n\n@requires_ds(d9p, big_data=True)\ndef test_d9p_global_values():\n    ds = data_dir_load(d9p)\n    ad = ds.all_data()\n    # 'Ana' variable values output from the ART Fortran 'ANA' analysis code\n    AnaNStars = 6255\n    assert_equal(ad[\"stars\", \"particle_type\"].size, AnaNStars)\n    assert_equal(ad[\"specie4\", \"particle_type\"].size, AnaNStars)\n\n    # The *real* answer is 2833405, but yt misses one particle since it lives\n    # on a domain boundary. See issue 814. When that is fixed, this test\n    # will need to be updated\n    AnaNDM = 2833404\n    assert_equal(ad[\"darkmatter\", \"particle_type\"].size, AnaNDM)\n    assert_equal(\n        (\n            ad[\"specie0\", \"particle_type\"].size\n            + ad[\"specie1\", \"particle_type\"].size\n            + ad[\"specie2\", \"particle_type\"].size\n            + ad[\"specie3\", \"particle_type\"].size\n        ),\n        AnaNDM,\n    )\n\n    for spnum in range(5):\n        npart_read = ad[f\"specie{spnum}\", \"particle_type\"].size\n        npart_header = ds.particle_type_counts[f\"specie{spnum}\"]\n        if spnum == 3:\n            # see issue 814\n            npart_read += 1\n        assert_equal(npart_read, npart_header)\n\n    AnaBoxSize = YTQuantity(7.1442196564, \"Mpc\")\n    AnaVolume = YTQuantity(364.640074656, \"Mpc**3\")\n    Volume = 1\n    for i in ds.domain_width.in_units(\"Mpc\"):\n        assert_almost_equal(i, AnaBoxSize)\n        Volume *= i\n    assert_almost_equal(Volume, AnaVolume)\n\n    AnaNCells = 4087490\n    assert_equal(len(ad[\"index\", \"cell_volume\"]), AnaNCells)\n\n    AnaTotDMMass = YTQuantity(1.01191786808255e14, \"Msun\")\n    assert_almost_equal(\n        ad[\"darkmatter\", \"particle_mass\"].sum().in_units(\"Msun\"), AnaTotDMMass\n    )\n\n    AnaTotStarMass = YTQuantity(1776701.3990607238, \"Msun\")\n    assert_almost_equal(\n        ad[\"stars\", \"particle_mass\"].sum().in_units(\"Msun\"), AnaTotStarMass\n    )\n\n    AnaTotStarMassInitial = YTQuantity(2423468.2801332865, \"Msun\")\n    assert_almost_equal(\n        ad[\"stars\", \"particle_mass_initial\"].sum().in_units(\"Msun\"),\n        AnaTotStarMassInitial,\n    )\n\n    AnaTotGasMass = YTQuantity(1.7826982029216785e13, \"Msun\")\n    assert_almost_equal(ad[\"gas\", \"cell_mass\"].sum().in_units(\"Msun\"), AnaTotGasMass)\n\n    AnaTotTemp = YTQuantity(150219844793.3907, \"K\")  # just leaves\n    assert_almost_equal(ad[\"gas\", \"temperature\"].sum().in_units(\"K\"), AnaTotTemp)\n\n\n@requires_file(d9p)\ndef test_ARTDataset():\n    assert isinstance(data_dir_load(d9p), ARTDataset)\n\n\n@requires_file(d9p)\ndef test_units_override():\n    units_override_check(d9p)\n\n\n@requires_file(dmonly)\ndef test_particle_selection():\n    ds = data_dir_load(dmonly)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n"
  },
  {
    "path": "yt/frontends/artio/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/artio/_artio_caller.pyx",
    "content": "# distutils: sources = ARTIO_SOURCE\n# distutils: include_dirs = LIB_DIR_GEOM_ARTIO\n# distutils: libraries = STD_LIBS\ncimport cython\n\nimport numpy as np\n\ncimport numpy as np\n\nimport sys\n\nfrom libc.stdlib cimport free, malloc\nfrom libc.string cimport memcpy\n\nfrom yt.geometry.oct_container cimport OctObjectPool, SparseOctreeContainer\nfrom yt.geometry.oct_visitors cimport Oct\nfrom yt.geometry.particle_deposit cimport ParticleDepositOperation\nfrom yt.geometry.selection_routines cimport AlwaysSelector, SelectorObject\nfrom yt.utilities.lib.fp_utils cimport imax\n\n\nfrom yt.utilities.lib.misc_utilities import OnceIndirect\n\n\ncdef extern from \"platform_dep.h\":\n    ctypedef int int32_t\n    ctypedef long long int64_t\n    void *alloca(int)\n\ncdef extern from \"cosmology.h\":\n    ctypedef struct CosmologyParameters \"CosmologyParameters\" :\n        pass\n    CosmologyParameters *cosmology_allocate()\n    void cosmology_free(CosmologyParameters *c)\n    void cosmology_set_fixed(CosmologyParameters *c)\n\n    void cosmology_set_OmegaM(CosmologyParameters *c, double value)\n    void cosmology_set_OmegaB(CosmologyParameters *c, double value)\n    void cosmology_set_OmegaL(CosmologyParameters *c, double value)\n    void cosmology_set_h(CosmologyParameters *c, double value)\n    void cosmology_set_DeltaDC(CosmologyParameters *c, double value)\n\n    double abox_from_auni(CosmologyParameters *c, double a) noexcept nogil\n    double tcode_from_auni(CosmologyParameters *c, double a) noexcept nogil\n    double tphys_from_auni(CosmologyParameters *c, double a) noexcept nogil\n\n    double auni_from_abox(CosmologyParameters *c, double v) noexcept nogil\n    double auni_from_tcode(CosmologyParameters *c, double v) noexcept nogil\n    double auni_from_tphys(CosmologyParameters *c, double v) noexcept nogil\n\n    double abox_from_tcode(CosmologyParameters *c, double tcode) noexcept nogil\n    double tcode_from_abox(CosmologyParameters *c, double abox) noexcept nogil\n\n    double tphys_from_abox(CosmologyParameters *c, double abox) noexcept nogil\n    double tphys_from_tcode(CosmologyParameters *c, double tcode) noexcept nogil\n\ncdef extern from \"artio.h\":\n    ctypedef struct artio_fileset_handle \"artio_fileset\" :\n        pass\n    ctypedef struct artio_selection \"artio_selection\" :\n        pass\n    ctypedef struct artio_context :\n        pass\n    cdef extern artio_context *artio_context_global\n\n    # open modes\n    cdef int ARTIO_OPEN_HEADER \"ARTIO_OPEN_HEADER\"\n    cdef int ARTIO_OPEN_GRID \"ARTIO_OPEN_GRID\"\n    cdef int ARTIO_OPEN_PARTICLES \"ARTIO_OPEN_PARTICLES\"\n\n    # parameter constants\n    cdef int ARTIO_TYPE_STRING \"ARTIO_TYPE_STRING\"\n    cdef int ARTIO_TYPE_CHAR \"ARTIO_TYPE_CHAR\"\n    cdef int ARTIO_TYPE_INT \"ARTIO_TYPE_INT\"\n    cdef int ARTIO_TYPE_FLOAT \"ARTIO_TYPE_FLOAT\"\n    cdef int ARTIO_TYPE_DOUBLE \"ARTIO_TYPE_DOUBLE\"\n    cdef int ARTIO_TYPE_LONG \"ARTIO_TYPE_LONG\"\n\n    cdef int ARTIO_MAX_STRING_LENGTH \"ARTIO_MAX_STRING_LENGTH\"\n\n    cdef int ARTIO_PARAMETER_EXHAUSTED \"ARTIO_PARAMETER_EXHAUSTED\"\n\n    # grid read options\n    cdef int ARTIO_READ_LEAFS \"ARTIO_READ_LEAFS\"\n    cdef int ARTIO_READ_REFINED \"ARTIO_READ_REFINED\"\n    cdef int ARTIO_READ_ALL \"ARTIO_READ_ALL\"\n    cdef int ARTIO_READ_REFINED_NOT_ROOT \"ARTIO_READ_REFINED_NOT_ROOT\"\n    cdef int ARTIO_RETURN_CELLS \"ARTIO_RETURN_CELLS\"\n    cdef int ARTIO_RETURN_OCTS \"ARTIO_RETURN_OCTS\"\n\n    # errors\n    cdef int ARTIO_SUCCESS \"ARTIO_SUCCESS\"\n    cdef int ARTIO_ERR_MEMORY_ALLOCATION \"ARTIO_ERR_MEMORY_ALLOCATION\"\n\n    artio_fileset_handle *artio_fileset_open(char *file_prefix, int type, artio_context *context )\n    int artio_fileset_close( artio_fileset_handle *handle )\n    int artio_fileset_open_particle( artio_fileset_handle *handle )\n    int artio_fileset_open_grid(artio_fileset_handle *handle)\n    int artio_fileset_close_grid(artio_fileset_handle *handle)\n\n    int artio_fileset_has_grid( artio_fileset_handle *handle )\n    int artio_fileset_has_particles( artio_fileset_handle *handle )\n\n    # selection functions\n    artio_selection *artio_selection_allocate( artio_fileset_handle *handle )\n    artio_selection *artio_select_all( artio_fileset_handle *handle )\n    artio_selection *artio_select_volume( artio_fileset_handle *handle, double lpos[3], double rpos[3] )\n    int artio_selection_add_root_cell( artio_selection *selection, int coords[3] )\n    int artio_selection_destroy( artio_selection *selection )\n    int artio_selection_iterator( artio_selection *selection,\n            int64_t max_range_size, int64_t *start, int64_t *end )\n    int64_t artio_selection_size( artio_selection *selection )\n    void artio_selection_print( artio_selection *selection )\n\n    # parameter functions\n    int artio_parameter_iterate( artio_fileset_handle *handle, char *key, int *type, int *length )\n    int artio_parameter_get_int_array(artio_fileset_handle *handle, char * key, int length, int32_t *values)\n    int artio_parameter_get_float_array(artio_fileset_handle *handle, char * key, int length, float *values)\n    int artio_parameter_get_long_array(artio_fileset_handle *handle, char * key, int length, int64_t *values)\n    int artio_parameter_get_double_array(artio_fileset_handle *handle, char * key, int length, double *values)\n    int artio_parameter_get_string_array(artio_fileset_handle *handle, char * key, int length, char **values )\n\n    # grid functions\n    int artio_grid_cache_sfc_range(artio_fileset_handle *handle, int64_t start, int64_t end)\n    int artio_grid_clear_sfc_cache( artio_fileset_handle *handle )\n\n    int artio_grid_read_root_cell_begin(artio_fileset_handle *handle, int64_t sfc,\n        double *pos, float *variables,\n        int *num_tree_levels, int *num_octs_per_level)\n    int artio_grid_read_root_cell_end(artio_fileset_handle *handle)\n\n    int artio_grid_read_level_begin(artio_fileset_handle *handle, int level )\n    int artio_grid_read_level_end(artio_fileset_handle *handle)\n\n    int artio_grid_read_oct(artio_fileset_handle *handle, double *pos,\n            float *variables, int *refined)\n\n    int artio_grid_count_octs_in_sfc_range(artio_fileset_handle *handle,\n            int64_t start, int64_t end, int64_t *num_octs)\n\n    #particle functions\n    int artio_fileset_open_particles(artio_fileset_handle *handle)\n    int artio_particle_read_root_cell_begin(artio_fileset_handle *handle, int64_t sfc,\n                        int * num_particle_per_species)\n    int artio_particle_read_root_cell_end(artio_fileset_handle *handle)\n    int artio_particle_read_particle(artio_fileset_handle *handle, int64_t *pid, int *subspecies,\n                        double *primary_variables, float *secondary_variables)\n    int artio_particle_cache_sfc_range(artio_fileset_handle *handle, int64_t sfc_start, int64_t sfc_end)\n    int artio_particle_clear_sfc_cache(artio_fileset_handle *handle)\n    int artio_particle_read_species_begin(artio_fileset_handle *handle, int species)\n    int artio_particle_read_species_end(artio_fileset_handle *handle)\n\n\ncdef extern from \"artio_internal.h\":\n    np.int64_t artio_sfc_index( artio_fileset_handle *handle, int coords[3] ) noexcept nogil\n    void artio_sfc_coords( artio_fileset_handle *handle, int64_t index, int coords[3] ) noexcept nogil\n\ncdef void check_artio_status(int status, char *fname=\"[unknown]\"):\n    if status != ARTIO_SUCCESS:\n        import traceback\n        traceback.print_stack()\n        callername = sys._getframe().f_code.co_name\n        nline = sys._getframe().f_lineno\n        raise RuntimeError('failure with status', status, 'in function',fname,'from caller', callername, nline)\n\ncdef class artio_fileset :\n    cdef public object parameters\n    cdef artio_fileset_handle *handle\n\n    # cosmology parameter for time unit conversion\n    cdef CosmologyParameters *cosmology\n    cdef float tcode_to_years\n\n    # common attributes\n    cdef public int num_grid\n    cdef int64_t num_root_cells\n    cdef int64_t sfc_min, sfc_max\n\n    # grid attributes\n    cdef public int has_grid\n    cdef public int min_level, max_level\n    cdef public int num_grid_variables\n    cdef int *num_octs_per_level\n    cdef float *grid_variables\n\n    # particle attributes\n    cdef public int has_particles\n    cdef public int num_species\n    cdef int *particle_position_index\n    cdef int *num_particles_per_species\n    cdef double *primary_variables\n    cdef float *secondary_variables\n\n    def __init__(self, char *file_prefix) :\n        cdef int artio_type = ARTIO_OPEN_HEADER\n        cdef int64_t num_root\n\n        self.handle = artio_fileset_open( file_prefix, artio_type, artio_context_global )\n        if not self.handle :\n            raise RuntimeError\n\n        self.read_parameters()\n\n        self.num_root_cells = self.parameters['num_root_cells'][0]\n        self.num_grid = 1\n        num_root = self.num_root_cells\n        while num_root > 1 :\n            self.num_grid <<= 1\n            num_root >>= 3\n\n        self.sfc_min = 0\n        self.sfc_max = self.num_root_cells-1\n\n        # initialize cosmology module\n        if \"abox\" in self.parameters:\n            self.cosmology = cosmology_allocate()\n            cosmology_set_OmegaM(self.cosmology, self.parameters['OmegaM'][0])\n            cosmology_set_OmegaL(self.cosmology, self.parameters['OmegaL'][0])\n            cosmology_set_OmegaB(self.cosmology, self.parameters['OmegaB'][0])\n            cosmology_set_h(self.cosmology, self.parameters['hubble'][0])\n            cosmology_set_DeltaDC(self.cosmology, self.parameters['DeltaDC'][0])\n            cosmology_set_fixed(self.cosmology)\n        else:\n            self.cosmology = NULL\n            self.tcode_to_years = self.parameters['time_unit'][0]/(365.25*86400)\n\n        # grid detection\n        self.min_level = 0\n        self.max_level = self.parameters['grid_max_level'][0]\n        self.num_grid_variables = self.parameters['num_grid_variables'][0]\n\n        self.num_octs_per_level = <int *>malloc(self.max_level*sizeof(int))\n        self.grid_variables = <float *>malloc(8*self.num_grid_variables*sizeof(float))\n        if (not self.num_octs_per_level) or (not self.grid_variables) :\n            raise MemoryError\n\n        if artio_fileset_has_grid(self.handle):\n            status = artio_fileset_open_grid(self.handle)\n            check_artio_status(status)\n            self.has_grid = 1\n        else:\n            self.has_grid = 0\n\n        # particle detection\n        if ( artio_fileset_has_particles(self.handle) ):\n            status = artio_fileset_open_particles(self.handle)\n            check_artio_status(status)\n            self.has_particles = 1\n\n            for v in [\"num_particle_species\",\"num_primary_variables\",\"num_secondary_variables\"]:\n                if v not in self.parameters:\n                    raise RuntimeError(\"Unable to locate particle header information in artio header: key=\", v)\n\n            self.num_species = self.parameters['num_particle_species'][0]\n            self.particle_position_index = <int *>malloc(3*sizeof(int)*self.num_species)\n            if not self.particle_position_index :\n                raise MemoryError\n            for ispec in range(self.num_species) :\n                species_labels = \"species_%02d_primary_variable_labels\"% (ispec,)\n                if species_labels not in self.parameters:\n                    raise RuntimeError(\"Unable to locate variable labels for species\",ispec)\n\n                labels = self.parameters[species_labels]\n                try :\n                    self.particle_position_index[3*ispec+0] = labels.index('POSITION_X')\n                    self.particle_position_index[3*ispec+1] = labels.index('POSITION_Y')\n                    self.particle_position_index[3*ispec+2] = labels.index('POSITION_Z')\n                except ValueError :\n                    raise RuntimeError(\"Unable to locate position information for particle species\", ispec)\n\n            self.num_particles_per_species =  <int *>malloc(sizeof(int)*self.num_species)\n            self.primary_variables = <double *>malloc(sizeof(double)*max(self.parameters['num_primary_variables']))\n            self.secondary_variables = <float *>malloc(sizeof(float)*max(self.parameters['num_secondary_variables']))\n            if (not self.num_particles_per_species) or (not self.primary_variables) or (not self.secondary_variables) :\n                raise MemoryError\n        else:\n            self.has_particles = 0\n\n    def __dealloc__(self) :\n        if self.num_octs_per_level : free(self.num_octs_per_level)\n        if self.grid_variables : free(self.grid_variables)\n\n        if self.particle_position_index : free(self.particle_position_index)\n        if self.num_particles_per_species : free(self.num_particles_per_species)\n        if self.primary_variables : free(self.primary_variables)\n        if self.secondary_variables : free(self.secondary_variables)\n\n        if self.cosmology : cosmology_free(self.cosmology)\n\n        if self.handle : artio_fileset_close(self.handle)\n\n    def read_parameters(self) :\n        cdef char key[64]\n        cdef int type\n        cdef int length\n        cdef char ** char_values\n        cdef int32_t *int_values\n        cdef int64_t *long_values\n        cdef float *float_values\n        cdef double *double_values\n\n        self.parameters = {}\n\n        while artio_parameter_iterate( self.handle, key, &type, &length ) == ARTIO_SUCCESS :\n\n            if type == ARTIO_TYPE_STRING :\n                char_values = <char **>malloc(length*sizeof(char *))\n                for i in range(length) :\n                    char_values[i] = <char *>malloc( ARTIO_MAX_STRING_LENGTH*sizeof(char) )\n                artio_parameter_get_string_array( self.handle, key, length, char_values )\n                parameter = [ char_values[i] for i in range(length) ]\n                for i in range(length) :\n                    free(char_values[i])\n                free(char_values)\n                for i in range(len(parameter)):\n                    parameter[i] = parameter[i].decode('utf-8')\n            elif type == ARTIO_TYPE_INT :\n                int_values = <int32_t *>malloc(length*sizeof(int32_t))\n                artio_parameter_get_int_array( self.handle, key, length, int_values )\n                parameter = [ int_values[i] for i in range(length) ]\n                free(int_values)\n            elif type == ARTIO_TYPE_LONG :\n                long_values = <int64_t *>malloc(length*sizeof(int64_t))\n                artio_parameter_get_long_array( self.handle, key, length, long_values )\n                parameter = [ long_values[i] for i in range(length) ]\n                free(long_values)\n            elif type == ARTIO_TYPE_FLOAT :\n                float_values = <float *>malloc(length*sizeof(float))\n                artio_parameter_get_float_array( self.handle, key, length, float_values )\n                parameter = [ float_values[i] for i in range(length) ]\n                free(float_values)\n            elif type == ARTIO_TYPE_DOUBLE :\n                double_values = <double *>malloc(length*sizeof(double))\n                artio_parameter_get_double_array( self.handle, key, length, double_values )\n                parameter = [ double_values[i] for i in range(length) ]\n                free(double_values)\n            else :\n                raise RuntimeError(\"ARTIO file corruption detected: invalid type!\")\n\n            self.parameters[key.decode('utf-8')] = parameter\n\n    def abox_from_auni(self, np.float64_t a):\n        if self.cosmology:\n            return abox_from_auni(self.cosmology, a)\n        else:\n            raise RuntimeError(\"abox_from_auni called for non-cosmological ARTIO fileset!\")\n\n    def tcode_from_auni(self, np.float64_t a):\n        if self.cosmology:\n            return tcode_from_auni(self.cosmology, a)\n        else:\n            raise RuntimeError(\"tcode_from_auni called for non-cosmological ARTIO fileset!\")\n\n    def tphys_from_auni(self, np.float64_t a):\n        if self.cosmology:\n            return tphys_from_auni(self.cosmology, a)\n        else:\n            raise RuntimeError(\"tphys_from_auni called for non-cosmological ARTIO fileset!\")\n\n    def auni_from_abox(self, np.float64_t v):\n        if self.cosmology:\n            return auni_from_abox(self.cosmology, v)\n        else:\n            raise RuntimeError(\"auni_from_abox called for non-cosmological ARTIO fileset!\")\n\n    def auni_from_tcode(self, np.float64_t v):\n        if self.cosmology:\n            return auni_from_tcode(self.cosmology, v)\n        else:\n            raise RuntimeError(\"auni_from_tcode called for non-cosmological ARTIO fileset!\")\n\n    @cython.wraparound(False)\n    @cython.boundscheck(False)\n    def auni_from_tcode_array(self, np.ndarray[np.float64_t] tcode):\n        cdef int i, nauni\n        cdef np.ndarray[np.float64_t, ndim=1] auni\n        cdef CosmologyParameters *cosmology = self.cosmology\n        if not cosmology:\n            raise RuntimeError(\"auni_from_tcode_array called for non-cosmological ARTIO fileset!\")\n        auni = np.empty_like(tcode)\n        nauni = auni.shape[0]\n        with nogil:\n            for i in range(nauni):\n                auni[i] = auni_from_tcode(self.cosmology, tcode[i])\n        return auni\n\n    def auni_from_tphys(self, np.float64_t v):\n        if self.cosmology:\n            return auni_from_tphys(self.cosmology, v)\n        else:\n            raise RuntimeError(\"auni_from_tphys called for non-cosmological ARTIO fileset!\")\n\n    def abox_from_tcode(self, np.float64_t abox):\n        if self.cosmology:\n            return abox_from_tcode(self.cosmology, abox)\n        else:\n            raise RuntimeError(\"abox_from_tcode called for non-cosmological ARTIO fileset!\")\n\n    def tcode_from_abox(self, np.float64_t abox):\n        if self.cosmology:\n            return tcode_from_abox(self.cosmology, abox)\n        else:\n            raise RuntimeError(\"tcode_from_abox called for non-cosmological ARTIO fileset!\")\n\n    def tphys_from_abox(self, np.float64_t abox):\n        if self.cosmology:\n            return tphys_from_abox(self.cosmology, abox)\n        else:\n            raise RuntimeError(\"tphys_from_abox called for non-cosmological ARTIO fileset!\")\n\n    def tphys_from_tcode(self, np.float64_t tcode):\n        if self.cosmology:\n            return tphys_from_tcode(self.cosmology, tcode)\n        else:\n            return self.tcode_to_years*tcode\n\n    @cython.wraparound(False)\n    @cython.boundscheck(False)\n    def tphys_from_tcode_array(self, np.ndarray[np.float64_t] tcode):\n        cdef int i, ntphys\n        cdef np.ndarray[np.float64_t, ndim=1] tphys\n        cdef CosmologyParameters *cosmology = self.cosmology\n        tphys = np.empty_like(tcode)\n        ntphys = tcode.shape[0]\n\n        if cosmology:\n            tphys = np.empty_like(tcode)\n            ntphys = tcode.shape[0]\n            with nogil:\n                for i in range(ntphys):\n                    tphys[i] = tphys_from_tcode(cosmology, tcode[i])\n            return tphys\n        else:\n            return tcode*self.tcode_to_years\n\n#    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def read_particle_chunk(self, SelectorObject selector, int64_t sfc_start, int64_t sfc_end, fields) :\n        cdef int i\n        cdef int status\n        cdef int subspecies\n        cdef int64_t pid\n\n        cdef np.float64_t pos[3]\n\n        # since RuntimeErrors are not fatal, ensure no artio_particles* functions\n        # called if fileset lacks particles\n        if not self.has_particles: return\n\n        data = {}\n        accessed_species = np.zeros( self.num_species, dtype=\"int64\")\n        selected_mass = [ None for i in range(self.num_species)]\n        selected_pid = [ None for i in range(self.num_species)]\n        selected_species = [ None for i in range(self.num_species)]\n        selected_primary = [ [] for i in range(self.num_species)]\n        selected_secondary = [ [] for i in range(self.num_species)]\n\n        for species,field in fields :\n            if species < 0 or species > self.num_species :\n                raise RuntimeError(\"Invalid species provided to read_particle_chunk\")\n            accessed_species[species] = 1\n\n            if self.parameters[\"num_primary_variables\"][species] > 0 and \\\n                    field in self.parameters[\"species_%02u_primary_variable_labels\"%(species,)] :\n                selected_primary[species].append((self.parameters[\"species_%02u_primary_variable_labels\"%(species,)].index(field),(species,field)))\n                data[species,field] = np.empty(0,dtype=\"float64\")\n            elif self.parameters[\"num_secondary_variables\"][species] > 0 and \\\n                    field in self.parameters[\"species_%02u_secondary_variable_labels\"%(species,)] :\n                selected_secondary[species].append((self.parameters[\"species_%02u_secondary_variable_labels\"%(species,)].index(field),(species,field)))\n                data[species,field] = np.empty(0,dtype=\"float64\")\n            elif field == \"MASS\" :\n                selected_mass[species] = (species,field)\n                data[species,field] = np.empty(0,dtype=\"float64\")\n            elif field == \"PID\" :\n                selected_pid[species] = (species,field)\n                data[species,field] = np.empty(0,dtype=\"int64\")\n            elif field == \"SPECIES\" :\n                selected_species[species] = (species,field)\n                data[species,field] = np.empty(0,dtype=\"int8\")\n            else :\n                raise RuntimeError(\"invalid field name provided to read_particle_chunk\")\n\n        # cache the range\n        status = artio_particle_cache_sfc_range( self.handle, self.sfc_min, self.sfc_max )\n        check_artio_status(status)\n\n        for sfc in range( sfc_start, sfc_end+1 ) :\n            status = artio_particle_read_root_cell_begin( self.handle, sfc,\n                    self.num_particles_per_species )\n            check_artio_status(status)\n\n            for ispec in range(self.num_species) :\n                if accessed_species[ispec] :\n                    status = artio_particle_read_species_begin(self.handle, ispec)\n                    check_artio_status(status)\n\n                    for particle in range( self.num_particles_per_species[ispec] ) :\n                        status = artio_particle_read_particle(self.handle,\n                                &pid, &subspecies, self.primary_variables,\n                                self.secondary_variables)\n                        check_artio_status(status)\n\n                        for i in range(3) :\n                            pos[i] = self.primary_variables[self.particle_position_index[3*ispec+i]]\n\n                        if selector.select_point(pos) :\n                            # loop over primary variables\n                            for i,field in selected_primary[ispec] :\n                                count = len(data[field])\n                                data[field].resize(count+1)\n                                data[field][count] = self.primary_variables[i]\n\n                            # loop over secondary variables\n                            for i,field in selected_secondary[ispec] :\n                                count = len(data[field])\n                                data[field].resize(count+1)\n                                data[field][count] = self.secondary_variables[i]\n\n                            # add particle id\n                            if selected_pid[ispec] :\n                                count = len(data[selected_pid[ispec]])\n                                data[selected_pid[ispec]].resize(count+1)\n                                data[selected_pid[ispec]][count] = pid\n\n                            # add mass if requested\n                            if selected_mass[ispec] :\n                                count = len(data[selected_mass[ispec]])\n                                data[selected_mass[ispec]].resize(count+1)\n                                data[selected_mass[ispec]][count] = self.parameters[\"particle_species_mass\"][ispec]\n\n                    status = artio_particle_read_species_end( self.handle )\n                    check_artio_status(status)\n\n            status = artio_particle_read_root_cell_end( self.handle )\n            check_artio_status(status)\n\n        return data\n\n    #@cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def read_grid_chunk(self, SelectorObject selector, int64_t sfc_start, int64_t sfc_end, fields ):\n        cdef int i\n        cdef int level\n        cdef int num_oct_levels\n        cdef int refined[8]\n        cdef int status\n        cdef int64_t count\n        cdef double dpos[3]\n        cdef np.float64_t left[3]\n        cdef np.float64_t right[3]\n        cdef np.float64_t dds[3]\n\n        cdef int *field_order\n        cdef int num_fields  = len(fields)\n        field_order = <int*>malloc(sizeof(int)*num_fields)\n\n        # translate fields from ARTIO names to indices\n        var_labels = self.parameters['grid_variable_labels']\n        for i, f in enumerate(fields):\n            if f not in var_labels:\n                raise RuntimeError(\"Field\",f,\"is not known to ARTIO\")\n            field_order[i] = var_labels.index(f)\n\n        status = artio_grid_cache_sfc_range( self.handle, self.sfc_min, self.sfc_max )\n        check_artio_status(status)\n\n        # determine max number of cells we could hit (optimize later)\n        #status = artio_grid_count_octs_in_sfc_range( self.handle,\n        #        sfc_start, sfc_end, &max_octs )\n        #check_artio_status(status)\n        #max_cells = sfc_end-sfc_start+1 + max_octs*8\n\n        # allocate space for _fcoords, _icoords, _fwidth, _ires\n        #fcoords = np.empty((max_cells, 3), dtype=\"float64\")\n        #ires = np.empty(max_cells, dtype=\"int64\")\n        fcoords = np.empty((0, 3), dtype=\"float64\")\n        ires = np.empty(0, dtype=\"int64\")\n\n        #data = [ np.empty(max_cells, dtype=\"float32\") for i in range(num_fields) ]\n        data = [ np.empty(0,dtype=\"float64\") for i in range(num_fields)]\n\n        count = 0\n        for sfc in range( sfc_start, sfc_end+1 ) :\n            status = artio_grid_read_root_cell_begin( self.handle, sfc,\n                    dpos, self.grid_variables, &num_oct_levels, self.num_octs_per_level )\n            check_artio_status(status)\n\n            if num_oct_levels == 0 :\n                for i in range(num_fields) :\n                    data[i].resize(count+1)\n                    data[i][count] = self.grid_variables[field_order[i]]\n                fcoords.resize((count+1,3))\n                for i in range(3) :\n                    fcoords[count][i] = dpos[i]\n                ires.resize(count+1)\n                ires[count] = 0\n                count += 1\n\n            for level in range(1,num_oct_levels+1) :\n                status = artio_grid_read_level_begin( self.handle, level )\n                check_artio_status(status)\n\n                for i in range(3) :\n                    dds[i] = 2.**-level\n\n                for oct in range(self.num_octs_per_level[level-1]) :\n                    status = artio_grid_read_oct( self.handle, dpos, self.grid_variables, refined )\n                    check_artio_status(status)\n\n                    for child in range(8) :\n                        if not refined[child] :\n                            for i in range(3) :\n                                left[i] = (dpos[i]-dds[i]) if (child & (i<<1)) else dpos[i]\n                                right[i] = left[i] + dds[i]\n\n                            if selector.select_bbox(left,right) :\n                                fcoords.resize((count+1, 3))\n                                for i in range(3) :\n                                    fcoords[count][i] = left[i]+0.5*dds[i]\n                                ires.resize(count+1)\n                                ires[count] = level\n                                for i in range(num_fields) :\n                                    data[i].resize(count+1)\n                                    data[i][count] = self.grid_variables[self.num_grid_variables*child+field_order[i]]\n                                count += 1\n                status = artio_grid_read_level_end( self.handle )\n                check_artio_status(status)\n\n            status = artio_grid_read_root_cell_end( self.handle )\n            check_artio_status(status)\n\n        free(field_order)\n\n        #fcoords.resize((count,3))\n        #ires.resize(count)\n        #\n        #for i in range(num_fields) :\n        #    data[i].resize(count)\n\n        return (fcoords, ires, data)\n\n    def root_sfc_ranges_all(self, int max_range_size = 1024) :\n        cdef int64_t sfc_start, sfc_end\n        cdef artio_selection *selection\n\n        selection = artio_select_all( self.handle )\n        if selection == NULL :\n            raise RuntimeError\n        sfc_ranges = []\n        while artio_selection_iterator(selection, max_range_size,\n                &sfc_start, &sfc_end) == ARTIO_SUCCESS :\n            sfc_ranges.append([sfc_start, sfc_end])\n        artio_selection_destroy(selection)\n        return sfc_ranges\n\n    def root_sfc_ranges(self, SelectorObject selector,\n                        int max_range_size = 1024):\n        cdef int coords[3]\n        cdef int64_t sfc_start, sfc_end\n        cdef np.float64_t left[3]\n        cdef np.float64_t right[3]\n        cdef artio_selection *selection\n        cdef int i, j, k\n\n        sfc_ranges=[]\n        selection = artio_selection_allocate(self.handle)\n        for i in range(self.num_grid) :\n            # stupid cython\n            coords[0] = i\n            left[0] = coords[0]\n            right[0] = left[0] + 1.0\n            for j in range(self.num_grid) :\n                coords[1] = j\n                left[1] = coords[1]\n                right[1] = left[1] + 1.0\n                for k in range(self.num_grid) :\n                    coords[2] = k\n                    left[2] = coords[2]\n                    right[2] = left[2] + 1.0\n                    if selector.select_bbox(left,right) :\n                        status = artio_selection_add_root_cell(selection, coords)\n                        check_artio_status(status)\n\n        while artio_selection_iterator(selection, max_range_size,\n                &sfc_start, &sfc_end) == ARTIO_SUCCESS :\n            sfc_ranges.append([sfc_start, sfc_end])\n\n        artio_selection_destroy(selection)\n        return sfc_ranges\n\n###################################################\ndef artio_is_valid( char *file_prefix ) :\n    cdef artio_fileset_handle *handle = artio_fileset_open( file_prefix,\n            ARTIO_OPEN_HEADER, artio_context_global )\n    if handle == NULL :\n        return False\n    else :\n        artio_fileset_close(handle)\n    return True\n\ncdef class ARTIOSFCRangeHandler:\n    cdef public np.int64_t sfc_start\n    cdef public np.int64_t sfc_end\n    cdef public artio_fileset artio_handle\n    cdef public object root_mesh_handler\n    cdef public object oct_count\n    cdef public object octree_handler\n    cdef artio_fileset_handle *handle\n    cdef np.float64_t DLE[3]\n    cdef np.float64_t DRE[3]\n    cdef np.float64_t dds[3]\n    cdef np.int64_t dims[3]\n    cdef public np.int64_t total_octs\n    cdef np.int64_t *doct_count\n    cdef np.int64_t **pcount\n    cdef float **root_mesh_data\n    cdef np.int64_t nvars[2]\n    cdef int cache_root_mesh\n\n    def __init__(self, domain_dimensions, # cells\n                 domain_left_edge,\n                 domain_right_edge,\n                 artio_fileset artio_handle,\n                 sfc_start, sfc_end, int cache_root_mesh = 0):\n        cdef int i\n        cdef np.int64_t sfc\n        self.sfc_start = sfc_start\n        self.sfc_end = sfc_end\n        self.artio_handle = artio_handle\n        self.root_mesh_handler = None\n        self.octree_handler = None\n        self.handle = artio_handle.handle\n        self.oct_count = None\n        self.root_mesh_data = NULL\n        self.pcount = NULL\n        self.cache_root_mesh = cache_root_mesh\n\n        if artio_handle.has_particles:\n            self.pcount = <np.int64_t **> malloc(sizeof(np.int64_t*)\n                * artio_handle.num_species)\n            self.nvars[0] = artio_handle.num_species\n            for i in range(artio_handle.num_species):\n                self.pcount[i] = <np.int64_t*> malloc(sizeof(np.int64_t)\n                    * (self.sfc_end - self.sfc_start + 1))\n                for sfc in range(self.sfc_end - self.sfc_start + 1):\n                    self.pcount[i][sfc] = 0\n        else:\n            self.nvars[0] = 0\n\n        if artio_handle.has_grid:\n            self.nvars[1] = artio_handle.num_grid_variables\n        else:\n            self.nvars[1] = 0\n\n        for i in range(3):\n            self.dims[i] = domain_dimensions[i]\n            self.DLE[i] = domain_left_edge[i]\n            self.DRE[i] = domain_right_edge[i]\n            self.dds[i] = (self.DRE[i] - self.DLE[i])/self.dims[i]\n\n    def __dealloc__(self):\n        cdef int i\n        if self.artio_handle.has_particles:\n            for i in range(self.nvars[0]):\n                free(self.pcount[i])\n            free(self.pcount)\n        if self.artio_handle.has_grid:\n            if self.root_mesh_data != NULL:\n                for i in range(self.nvars[1]):\n                    free(self.root_mesh_data[i])\n                free(self.root_mesh_data)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def construct_mesh(self):\n        cdef int status, level, ngv\n        cdef np.int64_t sfc, oc, i\n        cdef double dpos[3]\n        cdef int num_oct_levels\n        cdef int max_level = self.artio_handle.max_level\n        cdef int *num_octs_per_level = <int *>malloc(\n            (max_level + 1)*sizeof(int))\n        cdef int num_species = self.artio_handle.num_species\n        cdef int *num_particles_per_species\n        cdef ARTIOOctreeContainer octree\n        ngv = self.nvars[1]\n        cdef float *grid_variables = <float *>malloc(\n            ngv * sizeof(float))\n        self.octree_handler = octree = ARTIOOctreeContainer(self)\n        if self.cache_root_mesh == 1:\n            self.root_mesh_data = <float **>malloc(sizeof(float *) * ngv)\n            for i in range(ngv):\n                self.root_mesh_data[i] = <float *>malloc(sizeof(float) * \\\n                    (self.sfc_end - self.sfc_start + 1))\n        # We want to pre-allocate an array of root pointers.  In the future,\n        # this will be pre-determined by the ARTIO library.  However, because\n        # realloc plays havoc with our tree searching, we can't utilize an\n        # expanding array at the present time.\n        octree.allocate_domains([], self.sfc_end - self.sfc_start + 1)\n        cdef np.ndarray[np.int64_t, ndim=1] oct_count\n        oct_count = np.zeros(self.sfc_end - self.sfc_start + 1, dtype=\"int64\")\n        status = artio_grid_cache_sfc_range(self.handle, self.sfc_start,\n                                            self.sfc_end)\n        check_artio_status(status)\n        for sfc in range(self.sfc_start, self.sfc_end + 1):\n            status = artio_grid_read_root_cell_begin( self.handle,\n                sfc, dpos, grid_variables, &num_oct_levels,\n                num_octs_per_level)\n            check_artio_status(status)\n            for i in range(ngv * self.cache_root_mesh):\n                self.root_mesh_data[i][sfc - self.sfc_start] = \\\n                    grid_variables[i]\n            if num_oct_levels > 0:\n                oc = 0\n                for level in range(num_oct_levels):\n                    oc += num_octs_per_level[level]\n                self.total_octs += oc\n                oct_count[sfc - self.sfc_start] = oc\n                octree.initialize_local_mesh(oc, num_oct_levels,\n                    num_octs_per_level, sfc)\n            status = artio_grid_read_root_cell_end(self.handle)\n            check_artio_status(status)\n        status = artio_grid_clear_sfc_cache(self.handle)\n        check_artio_status(status)\n        if self.artio_handle.has_particles:\n            num_particles_per_species =  <int *>malloc(\n                    sizeof(int)*num_species)\n\n            # Now particles\n            status = artio_particle_cache_sfc_range(self.handle, self.sfc_start,\n                                            self.sfc_end)\n            check_artio_status(status)\n            for sfc in range(self.sfc_start, self.sfc_end + 1):\n                # Now particles\n                status = artio_particle_read_root_cell_begin(self.handle,\n                        sfc, num_particles_per_species)\n                check_artio_status(status)\n\n                for i in range(num_species):\n                    self.pcount[i][sfc - self.sfc_start] = \\\n                        num_particles_per_species[i]\n\n                status = artio_particle_read_root_cell_end(self.handle)\n                check_artio_status(status)\n\n            status = artio_particle_clear_sfc_cache(self.handle)\n            check_artio_status(status)\n\n            free(num_particles_per_species)\n\n        free(grid_variables)\n        free(num_octs_per_level)\n        self.oct_count = oct_count\n        self.doct_count = <np.int64_t *> oct_count.data\n        self.root_mesh_handler = ARTIORootMeshContainer(self)\n\n    def free_mesh(self):\n        self.octree_handler = None\n        self.root_mesh_handler = None\n        self.doct_count = NULL\n        self.oct_count = None\n\ndef get_coords(artio_fileset handle, np.int64_t s):\n    cdef int coords[3]\n    artio_sfc_coords(handle.handle, s, coords)\n    return (coords[0], coords[1], coords[2])\n\ncdef struct particle_var_pointers:\n    # The number of particles we have filled\n    np.int64_t count\n    # Number of primary variables and pointers to their indices\n    int n_p\n    int p_ind[16] # Max of 16 vars\n    # Number of secondary variables and pointers to their indices\n    int n_s\n    int s_ind[16] # Max of 16 vars\n    # Pointers to the bools and data arrays for mass, pid and species\n    int n_mass\n    np.float64_t *mass\n    int n_pid\n    np.int64_t *pid\n    int n_species\n    np.int8_t *species\n    # Pointers to the pointers to primary and secondary vars\n    np.float64_t *pvars[16]\n    np.float64_t *svars[16]\n\ncdef class ARTIOOctreeContainer(SparseOctreeContainer):\n    # This is a transitory, created-on-demand OctreeContainer.  It should not\n    # be considered to be long-lasting, and during its creation it will read\n    # the index file.  This means that when created it will then be able to\n    # provide coordinates, but then on subsequent IO accesses it will pass over\n    # the file again, despite knowing the indexing system already.  Because of\n    # this, we will avoid creating it as long as possible.\n\n    cdef public artio_fileset artio_handle\n    cdef ARTIOSFCRangeHandler range_handler\n    cdef np.int64_t level_indices[32]\n    cdef np.int64_t sfc_start\n    cdef np.int64_t sfc_end\n\n    def __init__(self, ARTIOSFCRangeHandler range_handler):\n        self.range_handler = range_handler\n        self.sfc_start = range_handler.sfc_start\n        self.sfc_end = range_handler.sfc_end\n        self.artio_handle = range_handler.artio_handle\n        # Note the final argument is partial_coverage, which indicates whether\n        # or not an Oct can be partially refined.\n        dims, DLE, DRE = [], [], []\n        for i in range(32):\n            self.level_indices[i] = 0\n        for i in range(3):\n            # range_handler has dims in cells, which is the same as the number\n            # of possible octs.  This is because we have a forest of octrees.\n            dims.append(range_handler.dims[i])\n            DLE.append(range_handler.DLE[i])\n            DRE.append(range_handler.DRE[i])\n        super(ARTIOOctreeContainer, self).__init__(dims, DLE, DRE)\n        self.artio_handle = range_handler.artio_handle\n        self.level_offset = 1\n        self.domains = OctObjectPool()\n        self.root_nodes = NULL\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void initialize_local_mesh(self, np.int64_t oct_count,\n                              int num_oct_levels, int *num_octs_per_level,\n                              np.int64_t sfc):\n        # We actually will not be initializing the root mesh here, we will be\n        # initializing the entire mesh between sfc_start and sfc_end.\n        cdef np.int64_t oct_ind, ipos, nadded\n        cdef int i, status, level\n        cdef artio_fileset_handle *handle = self.artio_handle.handle\n        cdef double dpos[3]\n        cdef np.ndarray[np.float64_t, ndim=2] pos\n        # NOTE: We do not cache any SFC ranges here, as we should only ever be\n        # called from within a pre-cached operation in the SFC handler.\n\n        # We only allow one root oct.\n        self.append_domain(oct_count)\n        self.domains.containers[self.num_domains - 1].con_id = sfc\n\n        oct_ind = -1\n        ipos = 0\n        for level in range(num_oct_levels):\n            oct_ind = imax(oct_ind, num_octs_per_level[level])\n            self.level_indices[level] = ipos\n            ipos += num_octs_per_level[level]\n        pos = np.empty((oct_ind, 3), dtype=\"float64\")\n\n        # Now we initialize\n        # Note that we also assume we have already started reading the level.\n        ipos = 0\n        for level in range(num_oct_levels):\n            status = artio_grid_read_level_begin(handle, level + 1)\n            check_artio_status(status)\n            for oct_ind in range(num_octs_per_level[level]):\n                status = artio_grid_read_oct(handle, dpos, NULL, NULL)\n                for i in range(3):\n                    pos[oct_ind, i] = dpos[i]\n                check_artio_status(status)\n            status = artio_grid_read_level_end(handle)\n            check_artio_status(status)\n            nadded = self.add(self.num_domains, level, pos[:num_octs_per_level[level],:])\n            if nadded != num_octs_per_level[level]:\n                raise RuntimeError\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_sfc(self,\n             np.ndarray[np.uint8_t, ndim=1] levels,\n             np.ndarray[np.uint8_t, ndim=1] cell_inds,\n             np.ndarray[np.int64_t, ndim=1] file_inds,\n             np.ndarray[np.int64_t, ndim=1] domain_counts,\n             field_indices, dest_fields):\n        cdef np.ndarray[np.float32_t, ndim=2] source\n        cdef np.ndarray[np.float64_t, ndim=1] dest\n        cdef int status, i, num_oct_levels, nf, ngv, max_level\n        cdef int level, j, oct_ind, si\n        cdef np.int64_t sfc, ipos\n        cdef artio_fileset_handle *handle = self.artio_handle.handle\n        cdef double dpos[3]\n        # We duplicate some of the grid_variables stuff here so that we can\n        # potentially release the GIL\n        nf = len(field_indices)\n        ngv = self.artio_handle.num_grid_variables\n        max_level = self.artio_handle.max_level\n        cdef int *num_octs_per_level = <int *>malloc(\n            (max_level + 1)*sizeof(int))\n        cdef float *grid_variables = <float *>malloc(\n            8 * ngv * sizeof(float))\n        cdef int* field_ind = <int*> malloc(\n            nf * sizeof(int))\n        cdef np.float32_t **field_vals = <np.float32_t**> malloc(\n            nf * sizeof(np.float32_t*))\n        source_arrays = []\n        ipos = -1\n        for i in range(self.num_domains):\n            ipos = imax(ipos, self.domains.containers[i].n)\n        for i in range(nf):\n            field_ind[i] = field_indices[i]\n            # Note that we subtract one, because we're not using the root mesh.\n            source = np.zeros((ipos, 8), dtype=\"float32\")\n            source_arrays.append(source)\n            field_vals[i] = <np.float32_t*> source.data\n        cdef np.int64_t level_position[32]\n        cdef np.int64_t lp\n        # First we need to walk the mesh in the file.  Then we fill in the dest\n        # location based on the file index.\n        # A few ways this could be improved:\n        #   * Create a new visitor function that actually queried the data,\n        #     rather than our somewhat hokey double-loop over SFC arrays.\n        #   * Go to pointers for the dest arrays.\n        #   * Enable preloading during mesh initialization\n        #   * Calculate domain indices on the fly rather than with a\n        #     double-loop to calculate domain_counts\n        # The cons should be in order\n        cdef np.int64_t sfc_start, sfc_end\n        sfc_start = self.domains.containers[0].con_id\n        sfc_end = self.domains.containers[self.num_domains - 1].con_id\n        status = artio_grid_cache_sfc_range(handle, sfc_start, sfc_end)\n        check_artio_status(status)\n        cdef np.int64_t offset = 0\n        for si in range(self.num_domains):\n            sfc = self.domains.containers[si].con_id\n            status = artio_grid_read_root_cell_begin( handle, sfc,\n                    dpos, NULL, &num_oct_levels, num_octs_per_level)\n            check_artio_status(status)\n            lp = 0\n            for level in range(num_oct_levels):\n                status = artio_grid_read_level_begin(handle, level + 1)\n                check_artio_status(status)\n                level_position[level] = lp\n                for oct_ind in range(num_octs_per_level[level]):\n                    status = artio_grid_read_oct(handle, dpos, grid_variables, NULL)\n                    check_artio_status(status)\n                    for j in range(8):\n                        for i in range(nf):\n                            field_vals[i][(oct_ind + lp)*8+j] = \\\n                                grid_variables[ngv*j+field_ind[i]]\n                status = artio_grid_read_level_end(handle)\n                check_artio_status(status)\n                lp += num_octs_per_level[level]\n            status = artio_grid_read_root_cell_end(handle)\n            check_artio_status(status)\n            # Now we have all our sources.\n            for j in range(nf):\n                dest = dest_fields[j]\n                source = source_arrays[j]\n                for i in range(domain_counts[si]):\n                    level = levels[i + offset]\n                    oct_ind = file_inds[i + offset] + level_position[level]\n                    dest[i + offset] = source[oct_ind, cell_inds[i + offset]]\n            # Now, we offset by the actual number filled here.\n            offset += domain_counts[si]\n        status = artio_grid_clear_sfc_cache(handle)\n        check_artio_status(status)\n        free(field_ind)\n        free(field_vals)\n        free(grid_variables)\n        free(num_octs_per_level)\n\n    def fill_sfc_particles(self, fields):\n        # This handles not getting particles for refined sfc values.\n        rv = read_sfc_particles(self.artio_handle, self.sfc_start,\n                                self.sfc_end, 0, fields,\n                                self.range_handler.doct_count,\n                                self.range_handler.pcount)\n        return rv\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef read_sfc_particles(artio_fileset artio_handle,\n                        np.int64_t sfc_start, np.int64_t sfc_end,\n                        int read_unrefined, fields,\n                        np.int64_t *doct_count,\n                        np.int64_t **pcount):\n    cdef int status, ispec, subspecies\n    cdef np.int64_t sfc, particle, pid, ind, vind\n    cdef int num_species = artio_handle.num_species\n    cdef artio_fileset_handle *handle = artio_handle.handle\n    cdef int *num_particles_per_species =  <int *>malloc(\n        sizeof(int)*num_species)\n    cdef int *accessed_species =  <int *>malloc(\n        sizeof(int)*num_species)\n    cdef int *total_particles = <int *>malloc(\n        sizeof(int)*num_species)\n    cdef particle_var_pointers *vpoints = <particle_var_pointers *> malloc(\n        sizeof(particle_var_pointers)*num_species)\n    cdef double *primary_variables\n    cdef float *secondary_variables\n    cdef np.int64_t tp\n    cdef int max_level = artio_handle.max_level\n    cdef int *num_octs_per_level = <int *>malloc(\n        (max_level + 1)*sizeof(int))\n\n    cdef np.ndarray[np.int8_t, ndim=1] npi8arr\n    cdef np.ndarray[np.int64_t, ndim=1] npi64arr\n    cdef np.ndarray[np.float64_t, ndim=1] npf64arr\n\n    if not artio_handle.has_particles:\n        raise RuntimeError(\"Attempted to read non-existent particles in ARTIO\")\n\n    # Now we set up our field pointers\n    params = artio_handle.parameters\n\n    npri_vars = params[\"num_primary_variables\"]\n    nsec_vars = params[\"num_secondary_variables\"]\n    primary_variables = <double *>malloc(sizeof(double) * max(npri_vars))\n    secondary_variables = <float *>malloc(sizeof(float) * max(nsec_vars))\n\n    cdef particle_var_pointers *vp\n\n    for ispec in range(num_species):\n        total_particles[ispec] = 0\n        accessed_species[ispec] = 0\n        # Initialize our vpoints array\n        vpoints[ispec].count = 0\n        vpoints[ispec].n_mass = 0\n        vpoints[ispec].n_pid = 0\n        vpoints[ispec].n_species = 0\n        vpoints[ispec].n_p = 0\n        vpoints[ispec].n_s = 0\n\n    # Pass through once.  We want every single particle.\n    tp = 0\n    cdef np.int64_t c\n    for sfc in range(sfc_start, sfc_end + 1):\n        c = doct_count[sfc - sfc_start]\n        if read_unrefined == 1 and c > 0: continue\n        if read_unrefined == 0 and c == 0: continue\n\n        for ispec in range(num_species):\n            total_particles[ispec] += pcount[ispec][sfc - sfc_start]\n\n    # Now we allocate our final fields, which will be filled\n    #for ispec in range(num_species):\n    #    print \"In SFC %s to %s reading %s of species %s\" % (\n    #        sfc_start, sfc_end + 1, total_particles[ispec], ispec)\n    data = {}\n    for species, field in sorted(fields):\n        accessed_species[species] = 1\n        pri_vars = params.get(\n            \"species_%02u_primary_variable_labels\" % (species,), [])\n        sec_vars = params.get(\n            \"species_%02u_secondary_variable_labels\" % (species,), [])\n        tp = total_particles[species]\n        vp = &vpoints[species]\n        if field == \"PID\":\n            vp.n_pid = 1\n            data[species, field] = np.zeros(tp, dtype=\"int64\")\n            npi64arr = data[species, field]\n            vp.pid = <np.int64_t*> npi64arr.data\n        elif field == \"SPECIES\":\n            vp.n_species = 1\n            data[species, field] = np.zeros(tp, dtype=\"int8\")\n            npi8arr = data[species, field]\n            # We fill this *now*\n            npi8arr += species\n            vp.species = <np.int8_t*> npi8arr.data\n        elif npri_vars[species] > 0 and field in pri_vars :\n            data[species, field] = np.zeros(tp, dtype=\"float64\")\n            npf64arr = data[species, field]\n            vp.p_ind[vp.n_p] = pri_vars.index(field)\n            vp.pvars[vp.n_p] = <np.float64_t *> npf64arr.data\n            vp.n_p += 1\n        elif nsec_vars[species] > 0 and field in sec_vars :\n            data[species, field] = np.zeros(tp, dtype=\"float64\")\n            npf64arr = data[species, field]\n            vp.s_ind[vp.n_s] = sec_vars.index(field)\n            vp.svars[vp.n_s] = <np.float64_t *> npf64arr.data\n            vp.n_s += 1\n        elif field == \"MASS\":\n            vp.n_mass = 1\n            data[species, field] = np.zeros(tp, dtype=\"float64\")\n            npf64arr = data[species, field]\n            # We fill this *now*\n            npf64arr += params[\"particle_species_mass\"][species]\n            vp.mass = <np.float64_t*> npf64arr.data\n\n    status = artio_particle_cache_sfc_range( handle,\n            sfc_start, sfc_end )\n    check_artio_status(status)\n\n    for sfc in range(sfc_start, sfc_end + 1):\n        c = doct_count[sfc - sfc_start]\n        check_artio_status(status)\n        if read_unrefined == 1 and c > 0: continue\n        if read_unrefined == 0 and c == 0: continue\n        c = 0\n        for ispec in range(num_species) :\n            if accessed_species[ispec] == 0: continue\n            c += pcount[ispec][sfc - sfc_start]\n        if c == 0: continue\n        status = artio_particle_read_root_cell_begin( handle, sfc,\n                num_particles_per_species )\n        check_artio_status(status)\n        for ispec in range(num_species) :\n            if accessed_species[ispec] == 0: continue\n            status = artio_particle_read_species_begin(handle, ispec)\n            check_artio_status(status)\n            vp = &vpoints[ispec]\n\n            for particle in range(num_particles_per_species[ispec]) :\n                status = artio_particle_read_particle(handle,\n                        &pid, &subspecies, primary_variables,\n                        secondary_variables)\n                check_artio_status(status)\n                ind = vp.count\n\n                for i in range(vp.n_p):\n                    vind = vp.p_ind[i]\n                    vp.pvars[i][ind] = primary_variables[vind]\n\n                for i in range(vp.n_s):\n                    vind = vp.s_ind[i]\n                    vp.svars[i][ind] = secondary_variables[vind]\n\n                if vp.n_pid:\n                    vp.pid[ind] = pid\n\n                vp.count += 1\n\n            status = artio_particle_read_species_end( handle )\n            check_artio_status(status)\n\n        status = artio_particle_read_root_cell_end( handle )\n        check_artio_status(status)\n\n    status = artio_particle_clear_sfc_cache(handle)\n    check_artio_status(status)\n\n    free(num_octs_per_level)\n    free(num_particles_per_species)\n    free(total_particles)\n    free(accessed_species)\n    free(vpoints)\n    free(primary_variables)\n    free(secondary_variables)\n    return data\n\ncdef class ARTIORootMeshContainer:\n    cdef public artio_fileset artio_handle\n    cdef np.float64_t DLE[3]\n    cdef np.float64_t DRE[3]\n    cdef np.float64_t dds[3]\n    cdef np.int64_t dims[3]\n    cdef artio_fileset_handle *handle\n    cdef np.uint64_t sfc_start\n    cdef np.uint64_t sfc_end\n    cdef public object _last_mask\n    cdef public np.int64_t _last_selector_id\n    cdef np.int64_t _last_mask_sum\n    cdef ARTIOSFCRangeHandler range_handler\n    cdef np.uint8_t *sfc_mask\n    cdef np.int64_t nsfc\n\n    def __init__(self, ARTIOSFCRangeHandler range_handler):\n        cdef int i\n        cdef np.int64_t sfci\n        for i in range(3):\n            self.DLE[i] = range_handler.DLE[i]\n            self.DRE[i] = range_handler.DRE[i]\n            self.dims[i] = range_handler.dims[i]\n            self.dds[i] = range_handler.dds[i]\n        self.handle = range_handler.handle\n        self.artio_handle = range_handler.artio_handle\n        self._last_mask = None\n        self._last_selector_id = -1\n        self.sfc_start = range_handler.sfc_start\n        self.sfc_end = range_handler.sfc_end\n        self.range_handler = range_handler\n        # We assume that the number of octs has been created and filled\n        # already.  We no longer care about ANY of the SFCs that have octs\n        # inside them -- this goes for every operation that this object\n        # performs.\n        self.sfc_mask = <np.uint8_t *>malloc(sizeof(np.uint8_t) *\n          self.sfc_end - self.sfc_start + 1)\n        self.nsfc = 0\n        for sfci in range(self.sfc_end - self.sfc_start + 1):\n            if self.range_handler.oct_count[sfci] > 0:\n                self.sfc_mask[sfci] = 0\n            else:\n                self.sfc_mask[sfci] = 1\n                self.nsfc += 1\n\n    def __dealloc__(self):\n        free(self.sfc_mask)\n\n    @cython.cdivision(True)\n    cdef np.int64_t pos_to_sfc(self, np.float64_t pos[3]) noexcept nogil:\n        # Calculate the index\n        cdef int coords[3]\n        cdef int i\n        cdef np.int64_t sfc\n        for i in range(3):\n            coords[i] = <int>((pos[i] - self.DLE[i])/self.dds[i])\n        sfc = artio_sfc_index(self.handle, coords)\n        return sfc\n\n    @cython.cdivision(True)\n    cdef void sfc_to_pos(self, np.int64_t sfc, np.float64_t pos[3]) noexcept nogil:\n        cdef int coords[3]\n        cdef int i\n        artio_sfc_coords(self.handle, sfc, coords)\n        for i in range(3):\n            pos[i] = self.DLE[i] + (coords[i] + 0.5) * self.dds[i]\n\n    cdef np.int64_t count_cells(self, SelectorObject selector):\n        # We visit each cell if it is not refined and determine whether it is\n        # included or not.\n        return self.mask(selector).sum()\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def icoords(self, SelectorObject selector, np.int64_t num_cells = -1,\n                int domain_id = -1):\n        # Note that num_octs does not have to equal sfc_end - sfc_start + 1.\n        cdef np.int64_t sfc, sfci = -1\n        cdef int acoords[3]\n        cdef int i\n        cdef np.ndarray[np.uint8_t, ndim=1, cast=True] mask\n        mask = self.mask(selector)\n        num_cells = self._last_mask_sum\n        cdef np.ndarray[np.int64_t, ndim=2] coords\n        coords = np.empty((num_cells, 3), dtype=\"int64\")\n        cdef int filled = 0\n        for sfc in range(self.sfc_start, self.sfc_end + 1):\n            if self.sfc_mask[sfc - self.sfc_start] == 0: continue\n            sfci += 1\n            if mask[sfci] == 0: continue\n            # Note that we do *no* checks on refinement here.  In fact, this\n            # entire setup should not need to touch the disk except if the\n            # artio sfc calculators need to.\n            artio_sfc_coords(self.handle, sfc, acoords)\n            for i in range(3):\n                coords[filled, i] = acoords[i]\n            filled += 1\n        return coords\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fcoords(self, SelectorObject selector, np.int64_t num_cells = -1,\n                int domain_id = -1):\n        # Note that num_cells does not have to equal sfc_end - sfc_start + 1.\n        cdef np.int64_t sfc, sfci = -1\n        cdef np.float64_t pos[3]\n        cdef int i\n        cdef np.ndarray[np.uint8_t, ndim=1, cast=True] mask\n        mask = self.mask(selector)\n        num_cells = self._last_mask_sum\n        cdef np.ndarray[np.float64_t, ndim=2] coords\n        coords = np.empty((num_cells, 3), dtype=\"float64\")\n        cdef int filled = 0\n        for sfc in range(self.sfc_start, self.sfc_end + 1):\n            if self.sfc_mask[sfc - self.sfc_start] == 0: continue\n            sfci += 1\n            if mask[sfci] == 0: continue\n            # Note that we do *no* checks on refinement here.  In fact, this\n            # entire setup should not need to touch the disk except if the\n            # artio sfc calculators need to.\n            self.sfc_to_pos(sfc, pos)\n            for i in range(3):\n                coords[filled, i] = pos[i]\n            filled += 1\n        return coords\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fwidth(self, SelectorObject selector, np.int64_t num_cells = -1,\n                int domain_id = -1):\n        cdef int i\n        num_cells = self._last_mask_sum\n        cdef np.ndarray[np.float64_t, ndim=2] width\n        width = np.zeros((num_cells, 3), dtype=\"float64\")\n        for i in range(3):\n            width[:,i] = self.dds[i]\n        return width\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def ires(self, SelectorObject selector, np.int64_t num_cells = -1,\n                int domain_id = -1):\n        # Note: self.mask has a side effect of setting self._last_mask_sum\n        self.mask(selector)\n        num_cells = self._last_mask_sum\n        cdef np.ndarray[np.int64_t, ndim=1] res\n        res = np.zeros(num_cells, dtype=\"int64\")\n        return res\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def selector_fill(self, SelectorObject selector,\n                      np.ndarray source,\n                      np.ndarray dest = None,\n                      np.int64_t offset = 0, int dims = 1,\n                      int domain_id = -1):\n        # This is where we use the selector to transplant from one to the\n        # other.  Note that we *do* apply the selector here.\n        cdef np.int64_t num_cells = -1\n        cdef np.int64_t ind\n        cdef np.int64_t sfc, sfci = -1\n        cdef int filled = 0\n        cdef char *sdata = <char*> source.data\n        cdef char *ddata\n        cdef int ss = source.dtype.itemsize\n        cdef np.ndarray[np.uint8_t, ndim=1, cast=True] mask\n        mask = self.mask(selector)\n        if dest is None:\n            # Note that RAMSES can have partial refinement inside an Oct.  This\n            # means we actually do want the number of Octs, not the number of\n            # cells.\n            num_cells = self._last_mask_sum\n            if dims > 1:\n                dest = np.zeros((num_cells, dims), dtype=source.dtype,\n                    order='C')\n            else:\n                dest = np.zeros(num_cells, dtype=source.dtype, order='C')\n        ddata = (<char*>dest.data) + offset*ss*dims\n        ind = 0\n        for sfc in range(self.sfc_start, self.sfc_end + 1):\n            if self.sfc_mask[sfc - self.sfc_start] == 0: continue\n            sfci += 1\n            if mask[sfci] == 0: continue\n            memcpy(ddata, sdata + ind, dims * ss)\n            ddata += dims * ss\n            filled += 1\n            ind += ss * dims\n        if num_cells >= 0:\n            return dest\n        return filled\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def mask(self, SelectorObject selector, np.int64_t num_cells = -1,\n             int domain_id = -1):\n        # We take a domain_id here to avoid subclassing\n        cdef np.float64_t pos[3]\n        cdef np.int64_t sfc, sfci = -1\n        if self._last_selector_id == hash(selector):\n            return self._last_mask\n        cdef np.ndarray[np.uint8_t, ndim=1] mask\n        mask = np.zeros((self.nsfc), dtype=\"uint8\")\n        self._last_mask_sum = 0\n        for sfc in range(self.sfc_start, self.sfc_end + 1):\n            if self.sfc_mask[sfc - self.sfc_start] == 0: continue\n            sfci += 1\n            self.sfc_to_pos(sfc, pos)\n            if selector.select_cell(pos, self.dds) == 0: continue\n            mask[sfci] = 1\n            self._last_mask_sum += 1\n        self._last_mask = mask.astype(\"bool\")\n        self._last_selector_id = hash(selector)\n        return self._last_mask\n\n\n    def fill_sfc_particles(self, fields):\n        rv = read_sfc_particles(self.artio_handle,\n                                self.sfc_start, self.sfc_end,\n                                1, fields, self.range_handler.doct_count,\n                                self.range_handler.pcount)\n        return rv\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_sfc(self, SelectorObject selector, field_indices):\n        cdef np.ndarray[np.float64_t, ndim=1] dest\n        cdef int status, i, num_oct_levels, nf, ngv, max_level\n        cdef np.int64_t sfc, num_cells, sfci = -1\n        cdef double dpos[3]\n        max_level = self.artio_handle.max_level\n        cdef int *num_octs_per_level = <int *>malloc(\n            (max_level + 1)*sizeof(int))\n        # We duplicate some of the grid_variables stuff here so that we can\n        # potentially release the GIL\n        nf = len(field_indices)\n        ngv = self.artio_handle.num_grid_variables\n        cdef float *grid_variables = <float *>malloc(\n            ngv * sizeof(float))\n        cdef np.ndarray[np.uint8_t, ndim=1, cast=True] mask\n        mask = self.mask(selector, -1)\n        num_cells = self._last_mask_sum\n        tr = []\n        for i in range(nf):\n            tr.append(np.zeros(num_cells, dtype=\"float64\"))\n        cdef int* field_ind = <int*> malloc(\n            nf * sizeof(int))\n        cdef np.float64_t **field_vals = <np.float64_t**> malloc(\n            nf * sizeof(np.float64_t*))\n        for i in range(nf):\n            field_ind[i] = field_indices[i]\n            # This zeros should be an empty once we handle the root grid\n            dest = tr[i]\n            field_vals[i] = <np.float64_t*> dest.data\n        # First we need to walk the mesh in the file.  Then we fill in the dest\n        # location based on the file index.\n        cdef int filled = 0\n        cdef float **mesh_data = self.range_handler.root_mesh_data\n        if mesh_data == NULL:\n            status = artio_grid_cache_sfc_range(self.handle, self.sfc_start,\n                                                self.sfc_end)\n            check_artio_status(status)\n            for sfc in range(self.sfc_start, self.sfc_end + 1):\n                if self.sfc_mask[sfc - self.sfc_start] == 0: continue\n                sfci += 1\n                if mask[sfci] == 0: continue\n                status = artio_grid_read_root_cell_begin( self.handle,\n                    sfc, dpos, grid_variables, &num_oct_levels,\n                    num_octs_per_level)\n                check_artio_status(status)\n                for i in range(nf):\n                    field_vals[i][filled] = grid_variables[field_ind[i]]\n                filled += 1\n                status = artio_grid_read_root_cell_end(self.handle)\n                check_artio_status(status)\n            status = artio_grid_clear_sfc_cache(self.handle)\n            check_artio_status(status)\n        else:\n            for sfc in range(self.sfc_start, self.sfc_end + 1):\n                if self.sfc_mask[sfc - self.sfc_start] == 0: continue\n                sfci += 1\n                if mask[sfci] == 0: continue\n                for i in range(nf):\n                    field_vals[i][filled] = mesh_data[field_ind[i]][\n                        sfc - self.sfc_start]\n                filled += 1\n        # Now we have all our sources.\n        free(field_ind)\n        free(field_vals)\n        free(grid_variables)\n        free(num_octs_per_level)\n        return tr\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def deposit(self, ParticleDepositOperation pdeposit,\n                SelectorObject selector,\n                np.ndarray[np.float64_t, ndim=2] positions,\n                fields):\n        # This implements the necessary calls to enable particle deposition to\n        # occur as needed.\n        cdef int nf, i, j\n        cdef np.int64_t sfc, sfci\n        if fields is None:\n            fields = []\n        nf = len(fields)\n        cdef np.float64_t[::cython.view.indirect, ::1] field_pointers\n        if nf > 0: field_pointers = OnceIndirect(fields)\n        cdef np.float64_t[:] field_vals = np.empty(nf, dtype=\"float64\")\n        cdef np.ndarray[np.uint8_t, ndim=1, cast=True] mask\n        mask = self.mask(selector, -1)\n        cdef np.ndarray[np.int64_t, ndim=1] domain_ind\n        domain_ind = np.zeros(self.sfc_end - self.sfc_start + 1,\n                              dtype=\"int64\") - 1\n        j = 0\n        sfci = -1\n        for sfc in range(self.sfc_start, self.sfc_end + 1):\n            if self.sfc_mask[sfc - self.sfc_start] == 0: continue\n            sfci += 1\n            if mask[sfci] == 0:\n                continue\n            domain_ind[sfc - self.sfc_start] = j\n            j += 1\n        cdef np.float64_t pos[3]\n        cdef np.float64_t left_edge[3]\n        cdef int coords[3]\n        cdef int dims[3]\n        dims[0] = dims[1] = dims[2] = 1\n        cdef np.int64_t offset\n        for i in range(positions.shape[0]):\n            for j in range(nf):\n                field_vals[j] = field_pointers[j][i]\n            for j in range(3):\n                pos[j] = positions[i, j]\n                coords[j] = <int>((pos[j] - self.DLE[j])/self.dds[j])\n            sfc = artio_sfc_index(self.artio_handle.handle, coords)\n            if sfc < self.sfc_start or sfc > self.sfc_end: continue\n            offset = domain_ind[sfc - self.sfc_start]\n            if offset < 0: continue\n            # Check that we found the oct ...\n            for j in range(3):\n                left_edge[j] = coords[j] * self.dds[j] + self.DLE[j]\n            pdeposit.process(dims, i, left_edge, self.dds,\n                         offset, pos, field_vals, sfc)\n            if pdeposit.update_values == 1:\n                for j in range(nf):\n                    field_pointers[j][i] = field_vals[j]\n\ncdef class SFCRangeSelector(SelectorObject):\n\n    cdef SelectorObject base_selector\n    cdef ARTIOSFCRangeHandler range_handler\n    cdef ARTIORootMeshContainer mesh_container\n    cdef np.int64_t sfc_start, sfc_end\n\n    def __init__(self, dobj):\n        self.base_selector = dobj.base_selector\n        self.mesh_container = dobj.oct_handler\n        self.range_handler = self.mesh_container.range_handler\n        self.sfc_start = self.mesh_container.sfc_start\n        self.sfc_end = self.mesh_container.sfc_end\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def select_grids(self,\n                     np.ndarray[np.float64_t, ndim=2] left_edges,\n                     np.ndarray[np.float64_t, ndim=2] right_edges,\n                     np.ndarray[np.int32_t, ndim=2] levels):\n        raise RuntimeError\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        return self.select_point(pos)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef np.int64_t sfc = self.mesh_container.pos_to_sfc(pos)\n        if sfc > self.sfc_end: return 0\n        cdef np.int64_t oc = self.range_handler.doct_count[\n            sfc - self.sfc_start]\n        if oc > 0: return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        return self.base_selector.select_bbox(left_edge, right_edge)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        # Because visitors now use select_grid, we should be explicitly\n        # checking this.\n        return self.base_selector.select_grid(left_edge, right_edge, level, o)\n\n    def _hash_vals(self):\n        return (hash(self.base_selector), self.sfc_start, self.sfc_end)\n\nsfc_subset_selector = AlwaysSelector\n#sfc_subset_selector = SFCRangeSelector\n"
  },
  {
    "path": "yt/frontends/artio/api.py",
    "content": "from . import tests\nfrom .data_structures import ARTIODataset\nfrom .fields import ARTIOFieldInfo\nfrom .io import IOHandlerARTIO\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/LICENSE",
    "content": "ARTIO is licensed under the GNU Lesser General Public License (LGPL) version 3,\nwhich is an extension of the GNU General Public License (GPL).  The text of both\nlicenses are included here.\n\n===============================================================================\n\n                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n\n===============================================================================\n\n                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n\n===============================================================================\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n#include \"artio.h\"\n#include \"artio_internal.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <math.h>\n\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\nartio_fileset *artio_fileset_allocate( char *file_prefix, int mode,\n\t\tconst artio_context *context );\nvoid artio_fileset_destroy( artio_fileset *handle );\n\nint artio_fh_buffer_size = ARTIO_DEFAULT_BUFFER_SIZE;\n\nint artio_fileset_set_buffer_size( int buffer_size ) {\n\tif ( buffer_size < 0 ) {\n\t\treturn ARTIO_ERR_INVALID_BUFFER_SIZE;\n\t}\n\n\tartio_fh_buffer_size = buffer_size;\n\treturn ARTIO_SUCCESS;\n}\n\nartio_fileset *artio_fileset_open(char * file_prefix, int type, const artio_context *context) {\n\tartio_fh *head_fh;\n\tchar filename[256];\n\tint ret;\n\tint64_t tmp;\n\tint artio_major, artio_minor;\n\n\tartio_fileset *handle =\n\t\tartio_fileset_allocate( file_prefix, ARTIO_FILESET_READ, context );\n\tif ( handle == NULL ) {\n\t\treturn NULL;\n\t}\n\n\t/* open header file */\n\tsprintf(filename, \"%s.art\", handle->file_prefix);\n\thead_fh = artio_file_fopen(filename,\n\t\t\tARTIO_MODE_READ | ARTIO_MODE_ACCESS, context);\n\n\tif ( head_fh == NULL ) {\n\t\tartio_fileset_destroy(handle);\n\t\treturn NULL;\n\t}\n\n\tret = artio_parameter_read(head_fh, handle->parameters );\n\tif ( ret != ARTIO_SUCCESS ) {\n\t\tartio_fileset_destroy(handle);\n\t\treturn NULL;\n\t}\n\n\tartio_file_fclose(head_fh);\n\n\t/* check versions */\n\tif ( artio_parameter_get_int(handle, \"ARTIO_MAJOR_VERSION\", &artio_major ) ==\n\t\t\tARTIO_ERR_PARAM_NOT_FOUND ) {\n\t\t/* version pre 1.0 */\n\t\tartio_major = 0;\n\t\tartio_minor = 9;\n\t} else {\n\t\tartio_parameter_get_int(handle, \"ARTIO_MINOR_VERSION\", &artio_minor );\n\t}\n\n\tif ( artio_major > ARTIO_MAJOR_VERSION ) {\n\t\tfprintf(stderr,\"ERROR: artio file version newer than library (%u.%u vs %u.%u).\\n\",\n\t\t\tartio_major, artio_minor, ARTIO_MAJOR_VERSION, ARTIO_MINOR_VERSION );\n\t\tartio_fileset_destroy(handle);\n\t\treturn NULL;\n\t}\n\n\tartio_parameter_get_long(handle, \"num_root_cells\", &handle->num_root_cells);\n\n\tif ( artio_parameter_get_int(handle, \"sfc_type\", &handle->sfc_type ) != ARTIO_SUCCESS ) {\n\t\thandle->sfc_type = ARTIO_SFC_HILBERT;\n\t}\n\n\thandle->nBitsPerDim = 0;\n\ttmp = handle->num_root_cells >> 3;\n\twhile ( tmp ) {\n\t\thandle->nBitsPerDim++;\n\t\ttmp >>= 3;\n\t}\n\thandle->num_grid = 1<<handle->nBitsPerDim;\n\n\t/* default to accessing all sfc indices */\n\thandle->proc_sfc_begin = 0;\n\thandle->proc_sfc_end = handle->num_root_cells-1;\n\n\t/* open data files */\n\tif (type & ARTIO_OPEN_PARTICLES) {\n\t\tret = artio_fileset_open_particles(handle);\n\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\tartio_fileset_destroy(handle);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tif (type & ARTIO_OPEN_GRID) {\n\t\tret = artio_fileset_open_grid(handle);\n\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\tartio_fileset_destroy(handle);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\treturn handle;\n}\n\nartio_fileset *artio_fileset_create(char * file_prefix, int64_t root_cells,\n\t\tint64_t proc_sfc_begin, int64_t proc_sfc_end, const artio_context *context) {\n    artio_fileset *handle =\n\t\tartio_fileset_allocate( file_prefix, ARTIO_FILESET_WRITE, context );\n    if ( handle == NULL ) {\n        return NULL;\n    }\n\n\thandle->proc_sfc_index =\n\t\t(int64_t*)malloc((handle->num_procs+1)*sizeof(int64_t));\n\tif ( handle->proc_sfc_index == NULL ) {\n\t\tartio_fileset_destroy(handle);\n\t\treturn NULL;\n\t}\n\n#ifdef ARTIO_MPI\n\tMPI_Allgather( &proc_sfc_begin, 1, MPI_LONG_LONG,\n\t\t\thandle->proc_sfc_index, 1, MPI_LONG_LONG, handle->context->comm );\n#else\n\thandle->proc_sfc_index[0] = 0;\n#endif /* ARTIO_MPI */\n\thandle->proc_sfc_index[handle->num_procs] = root_cells;\n\n\thandle->proc_sfc_begin = proc_sfc_begin;\n\thandle->proc_sfc_end = proc_sfc_end;\n\thandle->num_root_cells = root_cells;\n\n\tartio_parameter_set_long(handle, \"num_root_cells\", root_cells);\n\n\tartio_parameter_set_int(handle, \"ARTIO_MAJOR_VERSION\", ARTIO_MAJOR_VERSION );\n\tartio_parameter_set_int(handle, \"ARTIO_MINOR_VERSION\", ARTIO_MINOR_VERSION );\n\n\treturn handle;\n}\n\nint artio_fileset_close(artio_fileset *handle) {\n\tchar header_filename[256];\n\tartio_fh *head_fh;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode == ARTIO_FILESET_WRITE) {\n\t\t/* ensure we've flushed open particle and\n\t\t * grid files before writing header */\n\t\tif ( handle->grid != NULL ) {\n\t\t\tartio_fileset_close_grid(handle);\n\t\t}\n\n\t\tif ( handle->particle != NULL ) {\n\t\t\tartio_fileset_close_particles(handle);\n\t\t}\n\n\t\tsprintf(header_filename, \"%s.art\", handle->file_prefix);\n\t\thead_fh = artio_file_fopen(header_filename,\n\t\t\t\tARTIO_MODE_WRITE | ((handle->rank == 0) ? ARTIO_MODE_ACCESS : 0),\n\t\t\t\thandle->context);\n\n\t\tif (head_fh == NULL) {\n\t\t\treturn ARTIO_ERR_FILE_CREATE;\n\t\t}\n\n\t\tif (0 == handle->rank) {\n\t\t\tartio_parameter_write(head_fh, handle->parameters );\n\t\t}\n\n\t\tartio_file_fclose(head_fh);\n\t}\n\n\tartio_fileset_destroy(handle);\n\n\treturn ARTIO_SUCCESS;\n}\n\nartio_fileset *artio_fileset_allocate( char *file_prefix, int mode,\n\t\tconst artio_context *context ) {\n\tint my_rank;\n\tint num_procs;\n\n    artio_fileset *handle = (artio_fileset *)malloc(sizeof(artio_fileset));\n\tif ( handle != NULL ) {\n\t\thandle->parameters = artio_parameter_list_init();\n\n#ifdef ARTIO_MPI\n\t\thandle->context = (artio_context *)malloc(sizeof(artio_context));\n\t\tif ( handle->context == NULL ) {\n\t\t\treturn NULL;\n\t\t}\n\t\tmemcpy( handle->context, context, sizeof(artio_context) );\n\n\t\tMPI_Comm_size(handle->context->comm, &num_procs);\n\t\tMPI_Comm_rank(handle->context->comm, &my_rank);\n#else\n\t\thandle->context = NULL;\n\n\t\tnum_procs = 1;\n\t\tmy_rank = 0;\n#endif /* MPI */\n\n\t\tstrncpy(handle->file_prefix, file_prefix, 250);\n\n\t\thandle->open_mode = mode;\n\t\thandle->open_type = ARTIO_OPEN_HEADER;\n\n\t\thandle->rank = my_rank;\n\t\thandle->num_procs = num_procs;\n\t\thandle->endian_swap = 0;\n\n\t\thandle->proc_sfc_index = NULL;\n\t\thandle->proc_sfc_begin = -1;\n\t\thandle->proc_sfc_end = -1;\n\t\thandle->num_root_cells = -1;\n\n\t\thandle->grid = NULL;\n\t\thandle->particle = NULL;\n\t}\n\treturn handle;\n}\n\nvoid artio_fileset_destroy( artio_fileset *handle ) {\n\tif ( handle == NULL ) return;\n\n\tif ( handle->proc_sfc_index != NULL ) free( handle->proc_sfc_index );\n\n\tif ( handle->grid != NULL ) {\n        artio_fileset_close_grid(handle);\n    }\n\n    if ( handle->particle != NULL ) {\n        artio_fileset_close_particles(handle);\n    }\n\n\tif ( handle->context != NULL ) free( handle->context );\n\n\tartio_parameter_list_free(handle->parameters);\n\n\tfree(handle);\n}\n\nint artio_fileset_has_grid( artio_fileset *handle ) {\n\tint num_grid_files = 0;\n\treturn ( handle->grid != NULL ||\n\t\t( artio_parameter_get_int( handle, \"num_grid_files\", &num_grid_files ) == ARTIO_SUCCESS &&\n\t\t  num_grid_files > 0 ) );\n}\n\nint artio_fileset_has_particles( artio_fileset *handle ) {\n\tint num_particle_files = 0;\n\treturn ( handle->particle != NULL ||\n\t\t\t( artio_parameter_get_int( handle, \"num_particle_files\", &num_particle_files ) == ARTIO_SUCCESS &&\n\t\t\t  num_particle_files > 0 ) );\n}\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio.h",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#ifndef __ARTIO_H__\n#define __ARTIO_H__\n\n#define ARTIO_MAJOR_VERSION     1\n#define ARTIO_MINOR_VERSION     2\n\n#ifdef ARTIO_MPI\n#include <mpi.h>\n#endif\n\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\n#define ARTIO_OPEN_HEADER\t\t\t\t\t0\n#define ARTIO_OPEN_PARTICLES                1\n#define ARTIO_OPEN_GRID                     2\n\n#define ARTIO_READ_LEAFS                    1\n#define ARTIO_READ_REFINED                  2\n#define\tARTIO_READ_ALL                      3\n\n#define ARTIO_RETURN_OCTS\t\t\t\t\t4\n#define ARTIO_RETURN_CELLS\t\t\t\t\t0\n\n/* allocation strategy */\n#define ARTIO_ALLOC_EQUAL_SFC               0\n#define ARTIO_ALLOC_EQUAL_PROC              1\n#define ARTIO_ALLOC_MAX_FILE_SIZE  \t        2\n\n#define ARTIO_TYPE_STRING                   0\n#define ARTIO_TYPE_CHAR                     1\n#define ARTIO_TYPE_INT                      2\n#define ARTIO_TYPE_FLOAT                    3\n#define ARTIO_TYPE_DOUBLE                   4\n#define ARTIO_TYPE_LONG                     5\n\n/* error codes */\n#define ARTIO_SUCCESS                       0\n\n#define ARTIO_ERR_PARAM_NOT_FOUND           1\n#define ARTIO_PARAMETER_EXHAUSTED\t\t\t2\n#define ARTIO_ERR_PARAM_INVALID_LENGTH      3\n#define ARTIO_ERR_PARAM_TYPE_MISMATCH       4\n#define ARTIO_ERR_PARAM_LENGTH_MISMATCH     5\n#define ARTIO_ERR_PARAM_LENGTH_INVALID      6\n#define ARTIO_ERR_PARAM_DUPLICATE           7\n#define ARTIO_ERR_PARAM_CORRUPTED           8\n#define ARTIO_ERR_PARAM_CORRUPTED_MAGIC     9\n#define ARTIO_ERR_STRING_LENGTH             10\n\n#define ARTIO_ERR_INVALID_FILESET_MODE      100\n#define\tARTIO_ERR_INVALID_FILE_NUMBER       101\n#define ARTIO_ERR_INVALID_FILE_MODE         102\n#define ARTIO_ERR_INVALID_SFC_RANGE         103\n#define ARTIO_ERR_INVALID_SFC               104\n#define ARTIO_ERR_INVALID_STATE             105\n#define ARTIO_ERR_INVALID_SEEK              106\n#define ARTIO_ERR_INVALID_OCT_LEVELS        107\n#define ARTIO_ERR_INVALID_SPECIES           108\n#define ARTIO_ERR_INVALID_ALLOC_STRATEGY    109\n#define ARTIO_ERR_INVALID_LEVEL             110\n#define ARTIO_ERR_INVALID_PARAMETER_LIST    111\n#define ARTIO_ERR_INVALID_DATATYPE          112\n#define ARTIO_ERR_INVALID_OCT_REFINED       113\n#define ARTIO_ERR_INVALID_HANDLE            114\n#define ARTIO_ERR_INVALID_CELL_TYPES        115\n#define ARTIO_ERR_INVALID_BUFFER_SIZE\t\t116\n#define ARTIO_ERR_INVALID_INDEX\t\t\t\t117\n\n#define ARTIO_ERR_DATA_EXISTS               200\n#define ARTIO_ERR_INSUFFICIENT_DATA         201\n#define ARTIO_ERR_FILE_CREATE               202\n#define ARTIO_ERR_GRID_DATA_NOT_FOUND       203\n#define ARTIO_ERR_GRID_FILE_NOT_FOUND       204\n#define ARTIO_ERR_PARTICLE_DATA_NOT_FOUND   205\n#define ARTIO_ERR_PARTICLE_FILE_NOT_FOUND   206\n#define ARTIO_ERR_IO_OVERFLOW               207\n#define ARTIO_ERR_IO_WRITE                  208\n#define ARTIO_ERR_IO_READ                   209\n#define ARTIO_ERR_BUFFER_EXISTS             210\n\n#define ARTIO_SELECTION_EXHAUSTED           300\n#define ARTIO_ERR_INVALID_SELECTION         301\n#define ARTIO_ERR_INVALID_COORDINATES       302\n\n#define ARTIO_ERR_MEMORY_ALLOCATION         400\n\n#define ARTIO_ERR_VERSION_MISMATCH\t\t\t500\n\n#ifdef ARTIO_MPI\ntypedef struct {\n    MPI_Comm comm;\n} artio_context;\n#else\ntypedef struct {\n    int comm;\n} artio_context;\n#endif\n\n#define ARTIO_MAX_STRING_LENGTH\t\t\t\t256\n\ntypedef struct artio_fileset_struct artio_fileset;\ntypedef struct artio_selection_struct artio_selection;\n\nextern const artio_context *artio_context_global;\n\n/*\n * Description: Open the file\n *\n *  filename\t\tThe file prefix\n *  type\t\t\tcombination of ARTIO_OPEN_PARTICLES and ARTIO_OPEN_GRID flags\n */\nartio_fileset *artio_fileset_open( char * file_name, int type, const artio_context *context);\n\n/**\n * Description: Create fileset and begin populating header information\n *\n *  file_name\t\t\tfile name of refined cells\n *  root_cells\t\t\tthe number of root level cells\n *  proc_sfc_begin-end\t\tthe range of local space-filling-curve indices\n *  handle\t\t\tthe artio file handle\n *\n */\nartio_fileset *artio_fileset_create(char * file_prefix,\n        int64_t root_cells, int64_t proc_sfc_begin, int64_t proc_sfc_end, const artio_context *context);\n\n/*\n * Description\tClose the file\n */\nint artio_fileset_close(artio_fileset *handle);\nint artio_fileset_set_buffer_size( int buffer_size );\nint artio_fileset_has_grid( artio_fileset *handle );\nint artio_fileset_has_particles( artio_fileset *handle );\n\n/* public parameter interface */\nint artio_parameter_iterate( artio_fileset *handle, char *key, int *type, int *length );\nint artio_parameter_get_array_length(artio_fileset *handle, const char * key, int *length);\n\nint artio_parameter_set_int(artio_fileset *handle, const char * key, int32_t value);\nint artio_parameter_get_int(artio_fileset *handle, const char * key, int32_t * value);\n\nint artio_parameter_set_int_array(artio_fileset *handle, const char * key, int length,\n\t\tint32_t *values);\nint artio_parameter_get_int_array(artio_fileset *handle, const char * key, int length,\n\t\tint32_t *values);\nint artio_parameter_get_int_array_index(artio_fileset *handle, const char * key,\n\t\tint index, int32_t *values);\n\nint artio_parameter_set_string(artio_fileset *handle, const char * key, char * value);\nint artio_parameter_get_string(artio_fileset *handle, const char * key, char * value );\n\nint artio_parameter_set_string_array(artio_fileset *handle, const char * key,\n\t\tint length, char ** values);\nint artio_parameter_get_string_array(artio_fileset *handle, const char * key,\n\t\tint length, char ** values );\nint artio_parameter_get_string_array_index(artio_fileset *handle, const char * key,\n\t\tint index, char * values );\n\nint artio_parameter_set_float(artio_fileset *handle, const char * key, float value);\nint artio_parameter_get_float(artio_fileset *handle, const char * key, float * value);\n\nint artio_parameter_set_float_array(artio_fileset *handle, const char * key,\n\t\tint length, float *values);\nint artio_parameter_get_float_array(artio_fileset *handle, const char * key,\n\t\tint length, float * values);\nint artio_parameter_get_float_array_index(artio_fileset *handle, const char * key,\n\t\tint index, float * values);\n\nint artio_parameter_set_double(artio_fileset *handle, const char * key, double value);\nint  artio_parameter_get_double(artio_fileset *handle, const char * key, double * value);\n\nint artio_parameter_set_double_array(artio_fileset *handle, const char * key,\n\t\tint length, double * values);\nint artio_parameter_get_double_array(artio_fileset *handle, const char * key,\n        int length, double *values);\nint artio_parameter_get_double_array_index(artio_fileset *handle, const char * key,\n\t\tint index, double *values);\n\nint artio_parameter_set_long(artio_fileset *handle, const char * key, int64_t value);\nint artio_parameter_get_long(artio_fileset *handle, const char * key, int64_t *value);\n\nint artio_parameter_set_long_array(artio_fileset *handle, const char * key,\n        int length, int64_t *values);\nint artio_parameter_get_long_array(artio_fileset *handle, const char * key,\n        int length, int64_t *values);\nint artio_parameter_get_long_array_index(artio_fileset *handle, const char * key,\n\t\tint index, int64_t *values);\n\n/* public grid interface */\ntypedef void (* artio_grid_callback)( int64_t sfc_index, int level,\n\t\tdouble *pos, float * variables, int *refined, void *params );\n\n/*\n * Description:\tAdd a grid component to a fileset open for writing\n *\n *  handle\t\t\tThe fileset handle\n *  num_grid_files\t\tThe number of grid files to create\n *  allocation_strategy\t\tHow to apportion sfc indices to each grid file\n *  num_grid_variables\t\tThe number of variables per cell\n *  grid_variable_labels\tIdentifying labels for each variable\n *  num_levels_per_root_tree\tMaximum tree depth for each oct tree\n *  num_octs_per_root_tree\tTotal octs in each oct tree\n */\nint artio_fileset_add_grid(artio_fileset *handle,\n        int num_grid_files, int allocation_strategy,\n        int num_grid_variables,\n        char ** grid_variable_labels,\n        int * num_levels_per_root_tree,\n        int * num_octs_per_root_tree );\n\nint artio_fileset_open_grid(artio_fileset *handle);\nint artio_fileset_close_grid(artio_fileset *handle);\n\n/*\n * Description:\tOutput the variables of the root level cell and the index of the Oct tree correlated with this root level cell\n *\n *  handle\t\t\tThe File handle\n *  sfc\t\t\t\tThe sfc index of root cell\n *  variables\t\t\tThe variables of the root level cell\n *  level\t\t\tThe depth of the Oct tree correlated to the root level cell\n *  num_level_octs\t\tThe array store the number of Oct nodes each level\n */\nint artio_grid_write_root_cell_begin(artio_fileset *handle, int64_t sfc,\n\t\tfloat * variables, int level, int * num_octs_per_level);\n\n/*\n * Description:\tDo something at the end of writing the root level cell\n */\nint artio_grid_write_root_cell_end(artio_fileset *handle);\n\n/*\n * Description:\tDo something at the beginning of each level\n */\nint artio_grid_write_level_begin(artio_fileset *handle, int level );\n\n/*\n * Description:\tDo something at the end of each level\n */\nint artio_grid_write_level_end(artio_fileset *handle);\n\n/*\n * Description:\tOutput the data of a special oct tree node to the file\n *\n *  handle\t\t\tThe handle of the file\n *  variables \t\t\tThe array recording the variables of the eight cells belonging to this Octree node.\n */\nint artio_grid_write_oct(artio_fileset *handle, float *variables, int *refined);\n\n/*\n * Description:\tRead the variables of the root level cell and the index of the Octree\n *              correlated with this root level cell\n *\n *  handle\t\t\tThe File handle\n *  variables\t\t\tThe variables of the root level cell\n *  level \t\t\tThe depth of the OCT tree\n *  num_octs_per_level\t\tThe number of node of each oct level\n *\n */\nint artio_grid_read_root_cell_begin(artio_fileset *handle, int64_t sfc,\n\t\tdouble *pos, float *variables,\n\t\tint *num_tree_levels, int *num_octs_per_level);\n\n/*\n * Description:\tDo something at the end of reading the root level cell\n */\nint artio_grid_read_root_cell_end(artio_fileset *handle);\n\n/*\n * Description:\tDo something at the beginning of each level\n */\nint artio_grid_read_level_begin(artio_fileset *handle, int level );\n\n/*\n * Description:\tDo something at the end of each level\n */\nint artio_grid_read_level_end(artio_fileset *handle);\n\n/*\n * Description:\tRead the data of a special oct tree node from the file\n */\nint artio_grid_read_oct(artio_fileset *handle, double *pos,\n\t\tfloat *variables, int *refined);\n\nint artio_grid_cache_sfc_range(artio_fileset *handle, int64_t sfc_start, int64_t sfc_end);\nint artio_grid_clear_sfc_cache(artio_fileset *handle );\n\nint artio_grid_count_octs_in_sfc_range(artio_fileset *handle,\n        int64_t start, int64_t end, int64_t *num_octs_in_range );\n\n/*\n * Description:\tRead a segment of oct nodes\n *\n *  handle\t\t\tfile pointer\n *  sfc1\t\t\tthe start sfc index\n *  sfc2\t\t\tthe end sfc index\n *  max_level_to_read\t\tmax level to read for each oct tree\n *  option\t\t\t1. refined nodes; 2 leaf nodes; 3 all nodes\n *  callback        callback function\n *  params          a pointer to user-defined data passed to the callback\n */\nint artio_grid_read_sfc_range_levels(artio_fileset *handle,\n\t\tint64_t sfc1, int64_t sfc2,\n\t\tint min_level_to_read, int max_level_to_read,\n\t\tint options, artio_grid_callback callback,\n\t\tvoid *params );\n\nint artio_grid_read_sfc_range(artio_fileset *handle,\n        int64_t sfc1, int64_t sfc2, int options,\n        artio_grid_callback callback,\n\t\tvoid *params );\n\nint artio_grid_read_selection(artio_fileset *handle,\n\t\tartio_selection *selection, int options,\n\t\tartio_grid_callback callback,\n\t\tvoid *params );\n\nint artio_grid_read_selection_levels( artio_fileset *handle,\n\t\tartio_selection *selection,\n\t\tint min_level_to_read, int max_level_to_read,\n\t\tint options,\n\t\tartio_grid_callback callback,\n\t\tvoid *params );\n\n/**\n *  header\t\t\thead file name\n *  num_particle_files\t\tthe number of files to record refined cells\n *  allocation_strategy\n *  num_species\t\t\tnumber of particle species\n *  species_labels\t\tstring identifier for each species\n *  handle\t\t\tthe artio file handle\n *\n */\nint artio_fileset_add_particles(artio_fileset *handle,\n        int num_particle_files, int allocation_strategy,\n        int num_species, char **species_labels,\n        int *num_primary_variables,\n        int *num_secondary_variables,\n        char ***primary_variable_labels_per_species,\n        char ***secondary_variable_labels_per_species,\n        int *num_particles_per_species_per_root_tree );\n\nint artio_fileset_open_particles(artio_fileset *handle);\nint artio_fileset_close_particles(artio_fileset *handle);\n\n/*\n * Description:\tOutput the variables of the root level cell and the index of\n *                  the oct-tree correlated with this root level cell\n *\n *  handle\t\t\tThe File handle\n *  sfc\t\t\t\tThe sfc index of root cell\n *  variables\t\t\tThe variables of the root level cell\n *  level\t\t\tThe depth of the Oct tree correlated to the root level cell\n *  num_level_octs\t\tThe array store the number of Oct nodes each level\n */\nint artio_particle_write_root_cell_begin(artio_fileset *handle, int64_t sfc,\n\t\tint *num_particles_per_species);\n\n/*\n * Description:\tDo something at the end of writing the root level cell\n */\nint artio_particle_write_root_cell_end(artio_fileset *handle);\n\n/*\n * Description:\tDo something at the beginning of each level\n */\nint artio_particle_write_species_begin(artio_fileset *handle, int species );\n\n/*\n * Description:\tDo something at the end of each level\n */\nint artio_particle_write_species_end(artio_fileset *handle);\n\n/*\n * Description: Output the data of a special oct tree node to the file\n *\n *  handle\t\t\tThe handle of the file\n *  variables \t\t\tThe array recording the variables of the eight cells belonging to this Octree node.\n */\nint artio_particle_write_particle(artio_fileset *handle, int64_t pid, int subspecies,\n\t\t\tdouble* primary_variables, float *secondary_variables);\n\n/*\n * Description:\tRead the variables of the root level cell and the index of the Octree\n *              correlated with this root level cell\n *\n *  handle\t\t\tThe File handle\n *  variables\t\t\tThe variables of the root level cell\n *  level \t\t\tThe depth of the OCT tree\n *  num_octs_per_level\t\tThe number of node of each oct level\n *\n */\nint artio_particle_read_root_cell_begin(artio_fileset *handle, int64_t sfc,\n\t\t\tint * num_particle_per_species);\n\n/*\n * Description:\tDo something at the end of reading the root level cell\n */\nint artio_particle_read_root_cell_end(artio_fileset *handle);\n\n/*\n * Description:\tDo something at the beginning of each level\n */\nint artio_particle_read_species_begin(artio_fileset *handle, int species );\n\n/*\n * Description:  Do something at the end of each level\n */\nint artio_particle_read_species_end(artio_fileset *handle);\n\n/*\n * Description:\tRead the data of a single particle from the file\n */\nint artio_particle_read_particle(artio_fileset *handle, int64_t *pid, int *subspecies,\n\t\t\tdouble *primary_variables, float *secondary_variables);\n\nint artio_particle_cache_sfc_range(artio_fileset *handle, int64_t sfc_start, int64_t sfc_end);\nint artio_particle_clear_sfc_cache(artio_fileset *handle );\n\ntypedef void (* artio_particle_callback)(int64_t sfc_index,\n\t\tint species, int subspecies, int64_t pid,\n\t\tdouble *primary_variables, float *secondary_variables, void *params );\n\n/*\n * Description: Read a segment of particles\n *\n *  handle\t\t\tfile pointer\n *  sfc1\t\t\tthe start sfc index\n *  sfc2\t\t\tthe end sfc index\n *  start_species   the first particle species to read\n *  end_species     the last particle species to read\n *  callback        callback function\n *  params          user defined data passed to the callback function\n */\nint artio_particle_read_sfc_range(artio_fileset *handle,\n\t\tint64_t sfc1, int64_t sfc2,\n\t\tartio_particle_callback callback,\n\t\tvoid *params);\n\nint artio_particle_read_sfc_range_species( artio_fileset *handle,\n        int64_t sfc1, int64_t sfc2,\n        int start_species, int end_species,\n        artio_particle_callback callback,\n\t\tvoid *params);\n\nint artio_particle_read_selection(artio_fileset *handle,\n        artio_selection *selection, artio_particle_callback callback,\n\t\tvoid *params );\n\nint artio_particle_read_selection_species( artio_fileset *handle,\n        artio_selection *selection, int start_species, int end_species,\n        artio_particle_callback callback,\n\t\tvoid *params );\n\nartio_selection *artio_selection_allocate( artio_fileset *handle );\nartio_selection *artio_select_all( artio_fileset *handle );\nartio_selection *artio_select_volume( artio_fileset *handle, double lpos[3], double rpos[3] );\nartio_selection *artio_select_cube( artio_fileset *handle, double center[3], double size );\nint artio_selection_add_root_cell( artio_selection *selection, int coords[3] );\nint artio_selection_destroy( artio_selection *selection );\nvoid artio_selection_print( artio_selection *selection );\nint artio_selection_iterator( artio_selection *selection,\n         int64_t max_range_size, int64_t *start, int64_t *end );\nint artio_selection_iterator_reset( artio_selection *selection );\nint64_t artio_selection_size( artio_selection *selection );\n\n#endif /* __ARTIO_H__ */\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_endian.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#include \"artio_endian.h\"\n\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\nvoid artio_int_swap(int32_t *src, int count) {\n\tint i;\n\tunion {\n\t\tint32_t f;\n\t\tunsigned char c[4];\n\t} d1, d2;\n\n\tfor ( i = 0; i < count; i++ ) {\n\t\td1.f = src[i];\n\t\td2.c[0] = d1.c[3];\n\t\td2.c[1] = d1.c[2];\n\t\td2.c[2] = d1.c[1];\n\t\td2.c[3] = d1.c[0];\n\t\tsrc[i] = d2.f;\n\t}\n}\n\nvoid artio_float_swap(float *src, int count) {\n\tint i;\n\tunion {\n\t\tfloat f;\n\t\tunsigned char c[4];\n\t} d1, d2;\n\n\tfor ( i = 0; i < count; i++ ) {\n\t\td1.f = src[i];\n\t\td2.c[0] = d1.c[3];\n\t\td2.c[1] = d1.c[2];\n\t\td2.c[2] = d1.c[1];\n\t\td2.c[3] = d1.c[0];\n\t\tsrc[i] = d2.f;\n\t}\n}\n\nvoid artio_double_swap(double *src, int count) {\n\tint i;\n\tunion\n\t{\n\t\tdouble d;\n\t\tunsigned char c[8];\n\t} d1, d2;\n\n\tfor ( i = 0; i < count; i++ ) {\n\t\td1.d = src[i];\n\t\td2.c[0] = d1.c[7];\n\t\td2.c[1] = d1.c[6];\n\t\td2.c[2] = d1.c[5];\n\t\td2.c[3] = d1.c[4];\n\t\td2.c[4] = d1.c[3];\n\t\td2.c[5] = d1.c[2];\n\t\td2.c[6] = d1.c[1];\n\t\td2.c[7] = d1.c[0];\n\t\tsrc[i] = d2.d;\n\t}\n}\n\nvoid artio_long_swap(int64_t *src, int count) {\n\tint i;\n\tunion\n\t{\n\t\tint64_t d;\n\t\tunsigned char c[8];\n\t} d1, d2;\n\n\tfor ( i = 0; i < count; i++ ) {\n\t\td1.d = src[i];\n\t\td2.c[0] = d1.c[7];\n\t\td2.c[1] = d1.c[6];\n\t\td2.c[2] = d1.c[5];\n\t\td2.c[3] = d1.c[4];\n\t\td2.c[4] = d1.c[3];\n\t\td2.c[5] = d1.c[2];\n\t\td2.c[6] = d1.c[1];\n\t\td2.c[7] = d1.c[0];\n\t\tsrc[i] = d2.d;\n\t}\n}\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_endian.h",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#ifndef __ARTIO_EDIAN_H__\n#define __ARTIO_EDIAN_H__\n\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\nvoid artio_int_swap(int32_t *src, int count);\nvoid artio_float_swap(float *src, int count);\nvoid artio_double_swap(double *src, int count);\nvoid artio_long_swap(int64_t *src, int count);\n\n#endif /* __ARTIO_ENDIAN_H__ */\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_file.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#include \"artio.h\"\n#include \"artio_internal.h\"\n\n#include <stdlib.h>\n#include <stdio.h>\n\nartio_fh *artio_file_fopen( char * filename, int mode, const artio_context *context) {\n\tartio_fh *fh;\n#ifdef ARTIO_DEBUG\n\tprintf( \"artio_file_fopen( filename=%s, mode=%d, context=%p )\\n\",\n\t\t\tfilename, mode, context ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n\tfh = artio_file_fopen_i(filename,mode,context);\n#ifdef ARTIO_DEBUG\n\tprintf(\" artio_file_fopen = %p\\n\", fh ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n\treturn fh;\n}\n\nint artio_file_attach_buffer( artio_fh *handle, void *buf, int buf_size ) {\n    int status;\n#ifdef ARTIO_DEBUG\n\tprintf( \"artio_file_attach_buffer( handle=%p, buf=%p, buf_size = %d )\\n\",\n\t\t\thandle, buf, buf_size ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n    status = artio_file_attach_buffer_i(handle,buf,buf_size);\n#ifdef ARTIO_DEBUG\n\tif ( status != ARTIO_SUCCESS ) {\n\t\tprintf(\" artio_file_attach_buffer(%p) = %d\\n\", handle, status ); fflush(stdout);\n\t}\n#endif /* ARTIO_DEBUG */\n    return status;\n}\n\nint artio_file_detach_buffer( artio_fh *handle ) {\n\tint status;\n#ifdef ARTIO_DEBUG\n\tprintf( \"artio_file_detach_buffer( handle=%p )\\n\", handle ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n\tstatus = artio_file_detach_buffer_i(handle);\n#ifdef ARTIO_DEBUG\n\tif ( status != ARTIO_SUCCESS ) {\n\t\tprintf( \"artio_file_detach_buffer(%p) = %d\\n\", handle, status ); fflush(stdout);\n\t}\n#endif /* ARTIO_DEBUG */\n\treturn status;\n}\n\nint artio_file_fwrite( artio_fh *handle, const void *buf, int64_t count, int type ) {\n\tint status;\n#ifdef ARTIO_DEBUG\n\tprintf( \"artio_file_fwrite( handle=%p, buf=%p, count=%ld, type=%d )\\n\",\n\t\t\thandle, buf, count, type ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n\tstatus = artio_file_fwrite_i(handle,buf,count,type);\n#ifdef ARTIO_DEBUG\n\tif ( status != ARTIO_SUCCESS ) {\n\t\tprintf( \"artio_file_fwrite(%p) = %d\", handle, status ); fflush(stdout);\n\t}\n#endif /* ARTIO_DEBUG */\n\treturn status;\n}\n\nint artio_file_fflush(artio_fh *handle) {\n\tint status;\n#ifdef ARTIO_DEBUG\n\tprintf( \"artio_file_fflush( handle=%p )\\n\", handle ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n\tstatus = artio_file_fflush_i(handle);\n#ifdef ARTIO_DEBUG\n\tif ( status != ARTIO_SUCCESS ) {\n\t\tprintf( \"artio_file_fflush(%p) = %d\\n\", handle, status ); fflush(stdout);\n\t}\n#endif /* ARTIO_DEBUG */\n\treturn status;\n}\n\nint artio_file_fread(artio_fh *handle, void *buf, int64_t count, int type ) {\n\tint status;\n#ifdef ARTIO_DEBUG\n\tprintf( \"artio_file_fread( handle=%p, buf=%p, count=%ld, type=%d )\\n\",\n\t\t\thandle, buf, count, type ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n\tstatus = artio_file_fread_i(handle,buf,count,type);\n#ifdef ARTIO_DEBUG\n\tif ( status != ARTIO_SUCCESS ) {\n\t\tprintf( \"artio_file_fread(%p) = %d\", handle, status );\n\t}\n#endif /* ARTIO_DEBUG */\n\treturn status;\n}\n\nint artio_file_ftell(artio_fh *handle, int64_t *offset) {\n\tint status;\n#ifdef ARTIO_DEBUG\n\tprintf( \"artio_file_ftell( handle=%p, offset=%p )\\n\",\n\t\thandle, offset ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n\tstatus = artio_file_ftell_i(handle,offset);\n#ifdef ARTIO_DEBUG\n\tif ( status != ARTIO_SUCCESS ) {\n\t\tprintf(\"artio_file_ftell(%p) = %d\\n\", handle, status ); fflush(stdout);\n\t}\n#endif /* ARTIO_DEBUG */\n\treturn status;\n}\n\nint artio_file_fseek(artio_fh *handle, int64_t offset, int whence ) {\n\tint status;\n#ifdef ARTIO_DEBUG\n    printf( \"artio_file_fseek( handle=%p, offset=%ld, whence=%d )\\n\",\n        handle, offset, whence ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n\tstatus = artio_file_fseek_i(handle,offset,whence);\n#ifdef ARTIO_DEBUG\n\tif ( status != ARTIO_SUCCESS ) {\n\t\tprintf( \"artio_file_fseek(%p) = %d\\n\", handle, status ); fflush(stdout);\n\t}\n#endif /* ARTIO_DEBUG */\n\treturn status;\n}\n\nint artio_file_fclose(artio_fh *handle) {\n\tint status;\n#ifdef ARTIO_DEBUG\n\tprintf( \"artio_file_fclose( handle=%p )\\n\", handle ); fflush(stdout);\n#endif /* ARTIO_DEBUG */\n\tstatus = artio_file_fclose_i(handle);\n#ifdef ARTIO_DEBUG\n\tif ( status != ARTIO_SUCCESS ) {\n\t\tprintf( \"artio_file_fclose(%p) = %d\\n\", handle, status ); fflush(stdout);\n\t}\n#endif /* ARTIO_DEBUG */\n\treturn status;\n}\n\nvoid artio_file_set_endian_swap_tag(artio_fh *handle) {\n\tartio_file_set_endian_swap_tag_i(handle);\n}\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_grid.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#include \"artio.h\"\n#include \"artio_internal.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <math.h>\n\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\nint artio_grid_find_file(artio_grid_file *ghandle, int start, int end, int64_t sfc);\nartio_grid_file *artio_grid_file_allocate(void);\nvoid artio_grid_file_destroy(artio_grid_file *ghandle);\n\nconst double oct_pos_offsets[8][3] = {\n\t{ -0.5, -0.5, -0.5 }, {  0.5, -0.5, -0.5 },\n\t{ -0.5,  0.5, -0.5 }, {  0.5,  0.5, -0.5 },\n\t{ -0.5, -0.5,  0.5 }, {  0.5, -0.5,  0.5 },\n\t{ -0.5,  0.5,  0.5 }, {  0.5,  0.5,  0.5 }\n};\n\n/*\n * Open grid component of the fileset\n */\nint artio_fileset_open_grid(artio_fileset *handle) {\n\tint i;\n\tchar filename[256];\n\tint first_file, last_file;\n\tint mode;\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\t/* check that the fileset doesn't already contain a grid component */\n\tif ( handle->open_type & ARTIO_OPEN_GRID ||\n\t\t\thandle->open_mode != ARTIO_FILESET_READ ||\n\t\t\thandle->grid != NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\thandle->open_type |= ARTIO_OPEN_GRID;\n\n\tghandle = artio_grid_file_allocate();\n\tif ( ghandle == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\t/* load grid parameters from header file */\n\tif ( artio_parameter_get_int(handle, \"num_grid_files\", &ghandle->num_grid_files) != ARTIO_SUCCESS ||\n\t\t\tartio_parameter_get_int( handle, \"num_grid_variables\", &ghandle->num_grid_variables ) != ARTIO_SUCCESS ) {\n\t\treturn ARTIO_ERR_GRID_DATA_NOT_FOUND;\n\t}\n\n\tghandle->file_sfc_index = (int64_t *)malloc(sizeof(int64_t) * (ghandle->num_grid_files + 1));\n\tif ( ghandle->file_sfc_index == NULL ) {\n\t\tartio_grid_file_destroy(ghandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tartio_parameter_get_long_array(handle, \"grid_file_sfc_index\",\n\t\t\t\t       ghandle->num_grid_files + 1, ghandle->file_sfc_index);\n\tartio_parameter_get_int(handle, \"grid_max_level\",\n\t\t\t\t&ghandle->file_max_level);\n\n\tghandle->octs_per_level = (int *)malloc(ghandle->file_max_level * sizeof(int));\n\tif ( ghandle->octs_per_level == NULL ) {\n\t\tartio_grid_file_destroy(ghandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tghandle->ffh = (artio_fh **)malloc(ghandle->num_grid_files * sizeof(artio_fh *));\n\tif ( ghandle->ffh == NULL ) {\n\t\tartio_grid_file_destroy(ghandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tfor ( i = 0; i < ghandle->num_grid_files; i++ ) {\n\t\tghandle->ffh[i] = NULL;\n\t}\n\n\tfirst_file = artio_grid_find_file(ghandle, 0,\n\t\t\tghandle->num_grid_files, handle->proc_sfc_begin);\n\tlast_file = artio_grid_find_file(ghandle, first_file,\n\t\t\tghandle->num_grid_files, handle->proc_sfc_end);\n\n\t/* open files on all processes */\n\tfor (i = 0; i < ghandle->num_grid_files; i++) {\n\t\tsprintf(filename, \"%s.g%03d\", handle->file_prefix, i);\n\n\t\tmode = ARTIO_MODE_READ;\n\t\tif (i >= first_file && i <= last_file) {\n\t\t\tmode |= ARTIO_MODE_ACCESS;\n\t\t}\n\n\t\tif (handle->endian_swap) {\n\t\t\tmode |= ARTIO_MODE_ENDIAN_SWAP;\n\t\t}\n\n\t\tghandle->ffh[i] = artio_file_fopen(filename, mode, handle->context);\n\t\tif ( ghandle->ffh[i] == NULL ) {\n\t\t\tartio_grid_file_destroy(ghandle);\n\t\t\treturn ARTIO_ERR_GRID_FILE_NOT_FOUND;\n\t\t}\n\t}\n\n\thandle->grid = ghandle;\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_fileset_add_grid(artio_fileset *handle,\n\t\tint num_grid_files, int allocation_strategy,\n\t\tint num_grid_variables,\n\t\tchar ** grid_variable_labels,\n\t\tint * num_levels_per_root_tree,\n\t\tint * num_octs_per_root_tree ) {\n\n\tint i;\n\tint file_max_level, local_max_level;\n\tint64_t cur, sfc, l;\n\tint64_t first_file_sfc, last_file_sfc;\n\tint first_file, last_file;\n\tchar filename[256];\n\tint mode;\n\tint ret;\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif ( handle->open_mode != ARTIO_FILESET_WRITE ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tif ( handle->open_type & ARTIO_OPEN_GRID) {\n\t\treturn ARTIO_ERR_DATA_EXISTS;\n\t}\n\thandle->open_type |= ARTIO_OPEN_GRID;\n\n\tartio_parameter_set_int(handle, \"num_grid_files\", num_grid_files);\n\tartio_parameter_set_int(handle, \"num_grid_variables\", num_grid_variables);\n\tartio_parameter_set_string_array(handle, \"grid_variable_labels\",\n\t\t\tnum_grid_variables, grid_variable_labels);\n\n\tghandle = artio_grid_file_allocate();\n\tif ( ghandle == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tghandle->file_sfc_index = (int64_t *)malloc(sizeof(int64_t) * (num_grid_files + 1));\n\tif ( ghandle->file_sfc_index == NULL ) {\n\t\tartio_grid_file_destroy(ghandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\t/* compute global maximum level */\n\tlocal_max_level = 0;\n\tfor (sfc = 0; sfc < handle->proc_sfc_end - handle->proc_sfc_begin + 1; sfc++) {\n\t\tif (num_levels_per_root_tree[sfc] > local_max_level) {\n\t\t\tlocal_max_level = num_levels_per_root_tree[sfc];\n\t\t}\n\t}\n\n#ifdef ARTIO_MPI\n\tMPI_Allreduce( &local_max_level, &file_max_level,\n\t\t\t1, MPI_INT, MPI_MAX, handle->context->comm );\n#else\n\tfile_max_level = local_max_level;\n#endif /* ARTIO_MPI */\n\n\tswitch (allocation_strategy) {\n\t\tcase ARTIO_ALLOC_EQUAL_PROC:\n\t\t\tif (num_grid_files > handle->num_procs) {\n\t\t\t\treturn ARTIO_ERR_INVALID_FILE_NUMBER;\n\t\t\t}\n\n\t\t\tfor (i = 0; i < num_grid_files; i++) {\n\t\t\t\tghandle->file_sfc_index[i] =\n\t\t\t\t\thandle->proc_sfc_index[(handle->num_procs*i+num_grid_files-1) / num_grid_files];\n\t\t\t}\n\t\t\tghandle->file_sfc_index[num_grid_files] =\n\t\t\t\thandle->proc_sfc_index[handle->num_procs];\n\t\t\tbreak;\n\t\tcase ARTIO_ALLOC_EQUAL_SFC:\n\t\t\tif ( num_grid_files > handle->num_root_cells ) {\n\t\t\t\treturn ARTIO_ERR_INVALID_FILE_NUMBER;\n\t\t\t}\n\n\t\t\tfor (i = 0; i < num_grid_files; i++) {\n\t\t\t\tghandle->file_sfc_index[i] =\n\t\t\t\t\t(handle->num_root_cells*i+num_grid_files-1) / num_grid_files;\n\t\t\t}\n\t\t\tghandle->file_sfc_index[num_grid_files] = handle->num_root_cells;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tartio_grid_file_destroy(ghandle);\n\t\t\treturn ARTIO_ERR_INVALID_ALLOC_STRATEGY;\n\t}\n\n\tghandle->num_grid_files = num_grid_files;\n\tghandle->num_grid_variables = num_grid_variables;\n\tghandle->file_max_level = file_max_level;\n\n\t/* allocate space for sfc offset cache */\n\tghandle->cache_sfc_begin = handle->proc_sfc_begin;\n\tghandle->cache_sfc_end = handle->proc_sfc_end;\n\tghandle->sfc_offset_table =\n\t\t(int64_t *)malloc((size_t)(ghandle->cache_sfc_end -\n\t\t\t\t\tghandle->cache_sfc_begin + 1) * sizeof(int64_t));\n\tif ( ghandle->sfc_offset_table == NULL ) {\n\t\tartio_grid_file_destroy(ghandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tghandle->octs_per_level = (int *)malloc(ghandle->file_max_level * sizeof(int));\n\tif ( ghandle->octs_per_level == NULL ) {\n\t\tartio_grid_file_destroy(ghandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\t/* allocate file handles */\n\tghandle->ffh = (artio_fh **)malloc(num_grid_files * sizeof(artio_fh *));\n\tif ( ghandle->ffh == NULL ) {\n\t\tartio_grid_file_destroy(ghandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\tfor ( i = 0; i < num_grid_files; i++ ) {\n\t\tghandle->ffh[i] = NULL;\n\t}\n\n\t/* open file handles */\n\tfirst_file = artio_grid_find_file(ghandle, 0, num_grid_files,\n\t\t\t\t\thandle->proc_sfc_begin);\n\tlast_file = artio_grid_find_file(ghandle, first_file, num_grid_files,\n\t\t\t\t\thandle->proc_sfc_end);\n\n\tif ( first_file < 0 || first_file >= num_grid_files ||\n\t\t\tlast_file < first_file || last_file >= num_grid_files ) {\n\t\treturn ARTIO_ERR_INVALID_FILE_NUMBER;\n\t}\n\n\tfor (i = 0; i < num_grid_files; i++) {\n\t\tsprintf(filename, \"%s.g%03d\", handle->file_prefix, i);\n\n\t\tmode = ARTIO_MODE_WRITE;\n\t\tif (i >= first_file && i <= last_file) {\n\t\t\tmode |= ARTIO_MODE_ACCESS;\n\t\t}\n\n\t\tghandle->ffh[i] = artio_file_fopen(filename, mode, handle->context);\n\t\tif ( ghandle->ffh[i] == NULL ) {\n\t\t\tartio_grid_file_destroy(ghandle);\n\t\t\treturn ARTIO_ERR_FILE_CREATE;\n\t\t}\n\n\t\t/* write sfc offset header if we contribute to this file */\n\t\tif (i >= first_file && i <= last_file) {\n#ifdef ARTIO_MPI\n\t\t\tif (ghandle->file_sfc_index[i] >= handle->proc_sfc_index[ handle->rank ] &&\n\t\t\t\t\tghandle->file_sfc_index[i] < handle->proc_sfc_index[ handle->rank + 1] ) {\n\t\t\t\tcur = (ghandle->file_sfc_index[i + 1] - ghandle->file_sfc_index[i]) * sizeof(int64_t);\n\t\t\t} else {\n\t\t\t\t/* obtain offset from previous process */\n\t\t\t\tMPI_Recv( &cur, 1, MPI_LONG_LONG_INT, handle->rank - 1, i,\n\t\t\t\t\t\thandle->context->comm, MPI_STATUS_IGNORE );\n\t\t\t}\n#else\n\t\t\tcur = (ghandle->file_sfc_index[i + 1] - ghandle->file_sfc_index[i]) * sizeof(int64_t);\n#endif /* ARTIO_MPI */\n\n\t\t\tfirst_file_sfc = MAX( handle->proc_sfc_begin, ghandle->file_sfc_index[i] );\n\t\t\tlast_file_sfc = MIN( handle->proc_sfc_end, ghandle->file_sfc_index[i+1]-1 );\n\n\t\t\tfor (l = first_file_sfc - ghandle->cache_sfc_begin;\n\t\t\t\t\tl < last_file_sfc - ghandle->cache_sfc_begin + 1; l++) {\n\t\t\t\tghandle->sfc_offset_table[l] = cur;\n\t\t\t\tcur += sizeof(float) * ghandle->num_grid_variables + sizeof(int) * (1\n\t\t\t\t\t\t+ num_levels_per_root_tree[l])\n\t\t\t\t\t\t+ num_octs_per_root_tree[l] * 8 * (sizeof(float)\n\t\t\t\t\t\t\t\t* ghandle->num_grid_variables + sizeof(int));\n\t\t\t}\n\n#ifdef ARTIO_MPI\n\t\t\tif ( ghandle->file_sfc_index[i+1] > handle->proc_sfc_end+1 ) {\n\t\t\t\tMPI_Send( &cur, 1, MPI_LONG_LONG_INT, handle->rank + 1, i, handle->context->comm );\n\t\t\t}\n#endif /* ARTIO_MPI */\n\n\t\t\t/* seek and write our portion of sfc table */\n\t\t\tret = artio_file_fseek(ghandle->ffh[i],\n\t\t\t\t\t(first_file_sfc - ghandle->file_sfc_index[i]) * sizeof(int64_t),\n\t\t\t\t\tARTIO_SEEK_SET);\n\t\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\t\tartio_grid_file_destroy(ghandle);\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\tret = artio_file_fwrite(ghandle->ffh[i],\n\t\t\t\t\t&ghandle->sfc_offset_table[first_file_sfc - ghandle->cache_sfc_begin],\n\t\t\t\t\tlast_file_sfc - first_file_sfc + 1, ARTIO_TYPE_LONG);\n\t\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\t\tartio_grid_file_destroy(ghandle);\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\t}\n\n\thandle->grid = ghandle;\n\n\tartio_parameter_set_long_array(handle, \"grid_file_sfc_index\",\n\t\t\tghandle->num_grid_files + 1, ghandle->file_sfc_index);\n\tartio_parameter_set_int(handle, \"grid_max_level\", ghandle->file_max_level);\n\n\treturn ARTIO_SUCCESS;\n}\n\nartio_grid_file *artio_grid_file_allocate(void) {\n\tartio_grid_file *ghandle =\n\t\t\t(artio_grid_file *)malloc(sizeof(struct artio_grid_file_struct));\n\tif ( ghandle != NULL ) {\n\t\tghandle->ffh = NULL;\n\t\tghandle->num_grid_variables = -1;\n\t\tghandle->num_grid_files = -1;\n\t\tghandle->file_sfc_index = NULL;\n\t\tghandle->cache_sfc_begin = -1;\n\t\tghandle->cache_sfc_end = -1;\n\t\tghandle->sfc_offset_table = NULL;\n\t\tghandle->file_max_level = -1;\n\t\tghandle->cur_file = -1;\n\t\tghandle->cur_num_levels = -1;\n\t\tghandle->cur_level = -1;\n\t\tghandle->cur_octs = -1;\n\t\tghandle->cur_sfc = -1;\n\t\tghandle->octs_per_level = NULL;\n\n\t\tghandle->pos_flag = 0;\n\t\tghandle->pos_cur_level = -1;\n\t\tghandle->next_level_size = -1;\n\t\tghandle->cur_level_size = -1;\n\t\tghandle->cell_size_level = 1e20;\n\t\tghandle->next_level_pos = NULL;\n\t\tghandle->cur_level_pos = NULL;\n\t\tghandle->next_level_oct = -1;\n\n\t\tghandle->buffer_size = artio_fh_buffer_size;\n\t\tghandle->buffer = malloc(ghandle->buffer_size);\n\t\tif ( ghandle->buffer == NULL ) {\n\t\t\tfree(ghandle);\n\t\t\treturn NULL;\n\t\t}\n    }\n\treturn ghandle;\n}\n\nvoid artio_grid_file_destroy(artio_grid_file *ghandle) {\n\tint i;\n\tif ( ghandle == NULL ) return;\n\n\tif ( ghandle->ffh != NULL ) {\n\t\tfor (i = 0; i < ghandle->num_grid_files; i++) {\n\t\t\tif ( ghandle->ffh[i] != NULL ) {\n\t\t\t\tartio_file_fclose(ghandle->ffh[i]);\n\t\t\t}\n\t\t}\n\t\tfree(ghandle->ffh);\n\t}\n\n\tif ( ghandle->sfc_offset_table != NULL ) free(ghandle->sfc_offset_table);\n\tif ( ghandle->octs_per_level != NULL ) free(ghandle->octs_per_level);\n\tif ( ghandle->file_sfc_index != NULL ) free(ghandle->file_sfc_index);\n\tif ( ghandle->next_level_pos != NULL ) free(ghandle->next_level_pos);\n\tif ( ghandle->cur_level_pos != NULL ) free(ghandle->cur_level_pos);\n\tif ( ghandle->buffer != NULL ) free( ghandle->buffer );\n\n\tfree(ghandle);\n}\n\nint artio_fileset_close_grid(artio_fileset *handle) {\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif ( !(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tartio_grid_file_destroy(handle->grid);\n\thandle->grid = NULL;\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_count_octs_in_sfc_range(artio_fileset *handle,\n\t\tint64_t start, int64_t end, int64_t *num_octs_in_range ) {\n    int i;\n\tint ret;\n\tint file, first;\n\tint64_t sfc;\n\tint64_t offset, next_offset, size_offset;\n\tint num_oct_levels;\n\tint *num_octs_per_level;\n    artio_grid_file *ghandle;\n\n    if ( handle == NULL ) {\n        return ARTIO_ERR_INVALID_HANDLE;\n    }\n\n    if (handle->open_mode != ARTIO_FILESET_READ ||\n            !(handle->open_type & ARTIO_OPEN_GRID) ||\n            handle->grid == NULL ) {\n        return ARTIO_ERR_INVALID_FILESET_MODE;\n    }\n\n\tif ( start > end || start < handle->proc_sfc_begin ||\n\t\t\tend > handle->proc_sfc_end ) {\n\t\treturn ARTIO_ERR_INVALID_SFC_RANGE;\n\t}\n\n\tghandle = handle->grid;\n\n\t/* check that we're not in the middle of a read */\n\tif ( ghandle->cur_sfc != -1 ) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\t*num_octs_in_range = 0;\n\n\tif ( 8*ghandle->num_grid_variables <= ghandle->file_max_level ) {\n\t\t/* we can't compute the number of octs through the offset table */\n\t\tret = artio_grid_cache_sfc_range( handle, start, end );\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\t\tnum_octs_per_level = (int *)malloc(ghandle->file_max_level*sizeof(int) );\n\t\tif ( num_octs_per_level == NULL ) {\n\t\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t\t}\n\n\t\tfor ( sfc = start; sfc <= end; sfc++ ) {\n\t\t\tret = artio_grid_read_root_cell_begin( handle, sfc, NULL, NULL,\n\t\t\t\t\t&num_oct_levels, num_octs_per_level );\n\t\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\t\t\tfor ( i = 0; i < num_oct_levels; i++ ) {\n\t\t\t\t*num_octs_in_range += num_octs_per_level[i];\n\t\t\t}\n\n\t\t\tret = artio_grid_read_root_cell_end( handle );\n\t\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t\t}\n\n\t\tfree( num_octs_per_level );\n\t} else {\n\t\t/* TODO: add optimization if sfc range already cached */\n\t\tfile = artio_grid_find_file(ghandle, 0, ghandle->num_grid_files, start);\n\t\tfirst = MAX( 0, start - ghandle->file_sfc_index[file] );\n\n\t\tret = artio_file_fseek(ghandle->ffh[file],\n\t\t\t\tsizeof(int64_t) * first, ARTIO_SEEK_SET);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\t\tret = artio_file_fread(ghandle->ffh[file], &offset, 1, ARTIO_TYPE_LONG );\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\t\tsfc = start;\n\t\twhile ( sfc <= end ) {\n\t\t/* read next offset or compute end of file*/\n\t\t\tif ( sfc < ghandle->file_sfc_index[file+1] - 1 ) {\n\t\t\t\tret = artio_file_fread(ghandle->ffh[file], &size_offset, 1, ARTIO_TYPE_LONG );\n\t\t\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t\t\t\tnext_offset = size_offset;\n\t\t\t} else {\n\t\t\t\t/* need to seek and ftell */\n\t\t\t\tartio_file_fseek( ghandle->ffh[file], 0, ARTIO_SEEK_END );\n\t\t\t\tartio_file_ftell( ghandle->ffh[file], &size_offset );\n\t\t\t\tfile++;\n\n\t\t\t\tif ( sfc < end && file < ghandle->num_grid_files ) {\n\t\t\t\t\tartio_file_fseek( ghandle->ffh[file], 0, ARTIO_SEEK_SET );\n\t\t\t\t\tret = artio_file_fread(ghandle->ffh[file], &next_offset,\n\t\t\t\t\t\t\t1, ARTIO_TYPE_LONG );\n\t\t\t\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* this assumes (num_levels_per_root_tree)*sizeof(int) <\n\t\t\t *   size of an oct, or 8*num_variables > max_level so the\n\t\t\t *   number of levels drops off in rounding to int */\n\t\t\t*num_octs_in_range += (size_offset - offset -\n\t\t\t\tsizeof(float)*ghandle->num_grid_variables - sizeof(int) ) /\n\t\t\t\t(8*(sizeof(float)*ghandle->num_grid_variables + sizeof(int) ));\n\t\t\toffset = next_offset;\n\t\t\tsfc++;\n\t\t}\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_cache_sfc_range(artio_fileset *handle, int64_t start, int64_t end) {\n\tint i;\n\tint ret;\n\tint first_file, last_file;\n\tint64_t first, count, cur;\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tif ( start > end || start < handle->proc_sfc_begin ||\n\t\t\tend > handle->proc_sfc_end ) {\n\t\treturn ARTIO_ERR_INVALID_SFC_RANGE;\n\t}\n\n\tghandle = handle->grid;\n\n\t/* check if we've already cached the range */\n\tif ( start >= ghandle->cache_sfc_begin &&\n\t\t\tend <= ghandle->cache_sfc_end ) {\n\t\treturn ARTIO_SUCCESS;\n\t}\n\n\tartio_grid_clear_sfc_cache(handle);\n\n\tfirst_file = artio_grid_find_file(ghandle, 0, ghandle->num_grid_files, start);\n\tlast_file = artio_grid_find_file(ghandle, first_file, ghandle->num_grid_files, end);\n\n\tghandle->cache_sfc_begin = start;\n\tghandle->cache_sfc_end = end;\n\tghandle->sfc_offset_table = (int64_t *)malloc(sizeof(int64_t) * (size_t)(end - start + 1));\n\tif ( ghandle->sfc_offset_table == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tif ( ghandle->cur_file != -1 ) {\n\t\tartio_file_detach_buffer( ghandle->ffh[ghandle->cur_file]);\n\t\tghandle->cur_file = -1;\n\t}\n\n\tcur = 0;\n\tfor (i = first_file; i <= last_file; i++) {\n\t\tfirst = MAX( 0, start - ghandle->file_sfc_index[i] );\n\t\tcount = MIN( ghandle->file_sfc_index[i+1], end+1 )\n\t\t\t\t- MAX( start, ghandle->file_sfc_index[i]);\n\n\t\tartio_file_attach_buffer( ghandle->ffh[i],\n\t\t\tghandle->buffer, ghandle->buffer_size );\n\n\t\tret = artio_file_fseek(ghandle->ffh[i],\n\t\t\t\tsizeof(int64_t) * first, ARTIO_SEEK_SET);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\t\tret = artio_file_fread(ghandle->ffh[i],\n\t\t\t\t&ghandle->sfc_offset_table[cur],\n\t\t\t\tcount, ARTIO_TYPE_LONG);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\t\tartio_file_detach_buffer( ghandle->ffh[i] );\n\t\tcur += count;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_clear_sfc_cache( artio_fileset *handle ) {\n    artio_grid_file *ghandle;\n\n    if ( handle == NULL ) {\n        return ARTIO_ERR_INVALID_HANDLE;\n    }\n\n    if (handle->open_mode != ARTIO_FILESET_READ ||\n            !(handle->open_type & ARTIO_OPEN_GRID) ||\n            handle->grid == NULL ) {\n        return ARTIO_ERR_INVALID_FILESET_MODE;\n    }\n\n    ghandle = handle->grid;\n\n\tif ( ghandle->sfc_offset_table != NULL ) {\n\t\tfree(ghandle->sfc_offset_table);\n\t\tghandle->sfc_offset_table = NULL;\n\t}\n\n    ghandle->cache_sfc_begin = -1;\n    ghandle->cache_sfc_end = -1;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_find_file(artio_grid_file *ghandle, int start, int end, int64_t sfc) {\n\tint j;\n\n\tif ( start < 0 || start > ghandle->num_grid_files ||\n\t\t\tend < 0 || end > ghandle->num_grid_files ||\n\t\t\tsfc < ghandle->file_sfc_index[start] ||\n\t\t\tsfc >= ghandle->file_sfc_index[end] ) {\n\t\treturn -1;\n\t}\n\n\tif (start == end || sfc == ghandle->file_sfc_index[start]) {\n\t\treturn start;\n\t}\n\n\tif (1 == end - start) {\n\t\tif (sfc < ghandle->file_sfc_index[end]) {\n\t\t\treturn start;\n\t\t} else {\n\t\t\treturn end;\n\t\t}\n\t}\n\n\tj = start + (end - start) / 2;\n\tif (sfc > ghandle->file_sfc_index[j]) {\n\t\treturn artio_grid_find_file(ghandle, j, end, sfc);\n\t} else if (sfc < ghandle->file_sfc_index[j]) {\n\t\treturn artio_grid_find_file(ghandle, start, j, sfc);\n\t} else {\n\t\treturn j;\n\t}\n}\n\nint artio_grid_seek_to_sfc(artio_fileset *handle, int64_t sfc) {\n\tint64_t offset;\n\tartio_grid_file *ghandle;\n\tint file;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif ( !(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tghandle = handle->grid;\n\n\tif (ghandle->cache_sfc_begin == -1 ||\n\t\t\tsfc < ghandle->cache_sfc_begin ||\n\t\t\tsfc > ghandle->cache_sfc_end) {\n\t\treturn ARTIO_ERR_INVALID_SFC;\n\t}\n\n\tfile = artio_grid_find_file(ghandle, 0, ghandle->num_grid_files, sfc );\n\tif ( file != ghandle->cur_file ) {\n\t\tif ( ghandle->cur_file != -1 ) {\n\t\t\tartio_file_detach_buffer( ghandle->ffh[ghandle->cur_file] );\n\t\t}\n\t\tif ( ghandle->buffer_size > 0 ) {\n\t\t\tartio_file_attach_buffer( ghandle->ffh[file],\n\t\t\t\t\tghandle->buffer, ghandle->buffer_size );\n\t\t}\n\t\tghandle->cur_file = file;\n\t}\n\toffset = ghandle->sfc_offset_table[sfc - ghandle->cache_sfc_begin];\n\treturn artio_file_fseek(ghandle->ffh[ghandle->cur_file],\n\t\t\toffset, ARTIO_SEEK_SET);\n}\n\nint artio_grid_write_root_cell_begin(artio_fileset *handle, int64_t sfc,\n\t\tfloat *variables, int num_oct_levels, int *num_octs_per_level) {\n\tint i;\n\tint ret;\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tghandle = handle->grid;\n\n\tif (num_oct_levels < 0 || num_oct_levels > ghandle->file_max_level) {\n\t\treturn ARTIO_ERR_INVALID_OCT_LEVELS;\n\t}\n\n\tret = artio_grid_seek_to_sfc(handle, sfc);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fwrite(ghandle->ffh[ghandle->cur_file], variables,\n\t\t\tghandle->num_grid_variables, ARTIO_TYPE_FLOAT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fwrite(ghandle->ffh[ghandle->cur_file],\n\t\t\t&num_oct_levels, 1, ARTIO_TYPE_INT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fwrite(ghandle->ffh[ghandle->cur_file],\n\t\t\tnum_octs_per_level, num_oct_levels, ARTIO_TYPE_INT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tfor (i = 0; i < num_oct_levels; i++) {\n\t\tghandle->octs_per_level[i] = num_octs_per_level[i];\n\t}\n\n\tghandle->cur_sfc = sfc;\n\tghandle->cur_num_levels = num_oct_levels;\n\tghandle->cur_level = -1;\n\tghandle->cur_octs = 0;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_write_root_cell_end(artio_fileset *handle) {\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\thandle->grid->cur_sfc = -1;\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_write_level_begin(artio_fileset *handle, int level) {\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tghandle = handle->grid;\n\n\tif (ghandle->cur_sfc == -1 ||\n\t\t\tlevel <= 0 || level > ghandle->cur_num_levels) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tghandle->cur_level = level;\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_write_level_end(artio_fileset *handle) {\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tghandle = handle->grid;\n\n\tif (ghandle->cur_level == -1 ||\n\t\t\tghandle->cur_octs != ghandle->octs_per_level[ghandle->cur_level - 1] ) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tghandle->cur_level = -1;\n\tghandle->cur_octs = 0;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_write_oct(artio_fileset *handle, float *variables,\n\t\tint *cellrefined) {\n\tint i;\n\tint ret;\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tghandle = handle->grid;\n\n\tif (ghandle->cur_level == -1 ||\n\t\t\tghandle->cur_octs >= ghandle->octs_per_level[ghandle->cur_level - 1]) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\t/* check that no last-level octs have refined cells */\n\tif ( ghandle->cur_level == ghandle->cur_num_levels ) {\n\t\tfor ( i = 0; i < 8; i++ ) {\n\t\t\tif ( cellrefined[i] > 0) {\n\t\t\t\treturn ARTIO_ERR_INVALID_OCT_REFINED;\n\t\t\t}\n\t\t}\n\t}\n\n\tret = artio_file_fwrite(ghandle->ffh[ghandle->cur_file],\n\t\t\tvariables, 8 * ghandle->num_grid_variables,\n\t\t\tARTIO_TYPE_FLOAT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fwrite(ghandle->ffh[ghandle->cur_file],\n\t\t\tcellrefined, 8, ARTIO_TYPE_INT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tghandle->cur_octs++;\n\treturn ARTIO_SUCCESS;\n}\n\n/*\n *\n */\nint artio_grid_read_root_cell_begin(artio_fileset *handle, int64_t sfc,\n\t\tdouble *pos, float *variables, int *num_oct_levels,\n\t\tint *num_octs_per_level) {\n\tint i;\n\tint ret;\n\tint coords[3];\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tghandle = handle->grid;\n\n\tret = artio_grid_seek_to_sfc(handle, sfc);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tif ( variables == NULL ) {\n\t\tret = artio_file_fseek( ghandle->ffh[ghandle->cur_file],\n\t\t\t\tghandle->num_grid_variables*sizeof(float),\n\t\t\t\tARTIO_SEEK_CUR );\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t} else {\n\t\tret = artio_file_fread(ghandle->ffh[ghandle->cur_file],\n\t\t\t\tvariables, ghandle->num_grid_variables,\n\t\t\t\tARTIO_TYPE_FLOAT);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t}\n\n\tret = artio_file_fread(ghandle->ffh[ghandle->cur_file],\n\t\t\tnum_oct_levels, 1, ARTIO_TYPE_INT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tif ( *num_oct_levels > ghandle->file_max_level || *num_oct_levels < 0 ) {\n\t\tprintf(\"*num_oct_levels = %d\\n\", *num_oct_levels );\n\t\treturn ARTIO_ERR_INVALID_OCT_LEVELS;\n\t}\n\n\tif ( pos != NULL ) {\n\t\tghandle->pos_flag = 1;\n\n\t\t/* compute position from sfc */\n\t\tartio_sfc_coords( handle, sfc, coords );\n\t\tfor ( i = 0; i < 3; i++ ) {\n\t\t\tpos[i] = (double)coords[i] + 0.5;\n\t\t}\n\n\t\tif ( *num_oct_levels > 0 ) {\n\t\t\t/* compute next level position */\n\t\t\tif ( ghandle->next_level_pos == NULL ) {\n\t\t\t\tghandle->next_level_pos = (double *)malloc( 3*sizeof(double) );\n\t\t\t\tif ( ghandle->next_level_pos == NULL ) {\n\t\t\t\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t\t\t\t}\n\t\t\t\tghandle->next_level_size = 1;\n\t\t\t}\n\n\t\t\tfor ( i = 0; i < 3; i++ ) {\n\t\t\t\tghandle->next_level_pos[i] = pos[i];\n\t\t\t}\n\t\t\tghandle->pos_cur_level = 0;\n\t\t} else {\n\t\t\tghandle->pos_cur_level = -1;\n\t\t}\n\t} else {\n\t\tghandle->pos_flag = 0;\n\t}\n\n\tif (*num_oct_levels > 0) {\n\t\tret = artio_file_fread(ghandle->ffh[ghandle->cur_file],\n\t\t\t\tnum_octs_per_level, *num_oct_levels,\n\t\t\t\tARTIO_TYPE_INT);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\t\tfor (i = 0; i < *num_oct_levels; i++) {\n\t\t\tghandle->octs_per_level[i] = num_octs_per_level[i];\n\t\t}\n\t}\n\n\tghandle->cur_sfc = sfc;\n\tghandle->cur_num_levels = *num_oct_levels;\n\tghandle->cur_level = -1;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_read_oct(artio_fileset *handle,\n\t\tdouble *pos,\n\t\tfloat *variables,\n\t\tint *refined) {\n\tint i, j;\n\tint ret;\n\tint local_refined[8];\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tghandle = handle->grid;\n\n\tif (ghandle->cur_level == -1 ||\n\t\t\tghandle->cur_octs > ghandle->octs_per_level[ghandle->cur_level - 1] ||\n\t\t\t(pos != NULL && !ghandle->pos_flag )) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tif ( variables == NULL ) {\n\t\tret = artio_file_fseek(ghandle->ffh[ghandle->cur_file],\n\t\t\t\t8*ghandle->num_grid_variables*sizeof(float),\n\t\t\t\tARTIO_SEEK_CUR );\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t} else {\n\t\tret = artio_file_fread(ghandle->ffh[ghandle->cur_file],\n\t\t\t\tvariables, 8 * ghandle->num_grid_variables,\n\t\t\t\tARTIO_TYPE_FLOAT);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t}\n\n\tif ( !ghandle->pos_flag && refined == NULL ) {\n\t\tret = artio_file_fseek(ghandle->ffh[ghandle->cur_file],\n\t\t\t\t8*sizeof(int), ARTIO_SEEK_CUR );\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t} else {\n\t\tret = artio_file_fread(ghandle->ffh[ghandle->cur_file],\n\t\t\t\tlocal_refined, 8, ARTIO_TYPE_INT);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t}\n\n\tif ( refined != NULL ) {\n\t\tfor ( i = 0; i < 8; i++ ) {\n\t\t\trefined[i] = (local_refined[i] > 0);\n\t\t}\n\t}\n\n\tif ( ghandle->pos_flag ) {\n\t\tif ( pos != NULL ) {\n\t\t\tfor ( i = 0; i < 3; i++ ) {\n\t\t\t\tpos[i] = ghandle->cur_level_pos[3*ghandle->cur_octs + i];\n\t\t\t}\n\t\t}\n\n\t\tfor ( i = 0; i < 8; i++ ) {\n\t\t\tif ( local_refined[i] > 0) {\n\t\t\t\tif ( ghandle->next_level_oct >= ghandle->next_level_size ) {\n\t\t\t\t\treturn ARTIO_ERR_INVALID_STATE;\n\t\t\t\t}\n\t\t\t\tfor ( j = 0; j < 3; j++ ) {\n\t\t\t\t\tghandle->next_level_pos[3*ghandle->next_level_oct+j] =\n\t\t\t\t\t\tghandle->cur_level_pos[3*ghandle->cur_octs + j] +\n\t\t\t\t\t\tghandle->cell_size_level*oct_pos_offsets[i][j];\n\t\t\t\t}\n\t\t\t\tghandle->next_level_oct++;\n\t\t\t}\n\t\t}\n\t}\n\n\tghandle->cur_octs++;\n\n\treturn ARTIO_SUCCESS;\n}\n\n/*\n * Description        Obtain data from an appointed level tree node\n */\nint artio_grid_read_level_begin(artio_fileset *handle, int level) {\n\tint i;\n\tint ret;\n\tint64_t offset = 0;\n\tartio_grid_file *ghandle;\n\tint tmp_size;\n\tdouble *tmp_pos;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tghandle = handle->grid;\n\n\tif ( ghandle->cur_sfc == -1 || level <= 0 ||\n\t\t\tlevel > ghandle->cur_num_levels ||\n\t\t\t(ghandle->pos_flag && ghandle->pos_cur_level != level - 1) ) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tif ( ghandle->pos_flag ) {\n\t\tghandle->cell_size_level = 1.0 / (double)(1<<level);\n\n\t\ttmp_pos = ghandle->cur_level_pos;\n\t\ttmp_size = ghandle->cur_level_size;\n\n\t\tghandle->cur_level_pos = ghandle->next_level_pos;\n\t\tghandle->cur_level_size = ghandle->next_level_size;\n\n\t\tghandle->next_level_pos = tmp_pos;\n\t\tghandle->next_level_size = tmp_size;\n\n\t\tghandle->pos_cur_level = level;\n\n\t\tif ( level < ghandle->cur_num_levels ) {\n\t\t\t/* ensure the buffer for the next level positions is large enough */\n\t\t\tif ( ghandle->octs_per_level[level] > ghandle->next_level_size ) {\n\t\t\t\tif ( ghandle->next_level_pos != NULL ) {\n\t\t\t\t\tfree( ghandle->next_level_pos );\n\t\t\t\t}\n\t\t\t\tghandle->next_level_pos = (double *)malloc( 3*ghandle->octs_per_level[level]*sizeof(double) );\n\t\t\t\tif ( ghandle->next_level_pos == NULL ) {\n\t\t\t\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t\t\t\t}\n\t\t\t\tghandle->next_level_size = ghandle->octs_per_level[level];\n\t\t\t}\n\n\t\t\tghandle->next_level_oct = 0;\n\t\t}\n\t}\n\n\toffset = ghandle->sfc_offset_table[ghandle->cur_sfc - ghandle->cache_sfc_begin];\n\toffset += sizeof(float) * ghandle->num_grid_variables + sizeof(int)\n\t\t\t* (ghandle->cur_num_levels + 1);\n\tfor (i = 0; i < level - 1; i++) {\n\t\toffset += 8 * (sizeof(float) * ghandle->num_grid_variables + sizeof(int))\n\t\t\t\t* ghandle->octs_per_level[i];\n\t}\n\n\tret = artio_file_fseek(ghandle->ffh[ghandle->cur_file],\n\t\t\toffset, ARTIO_SEEK_SET);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tghandle->cur_level = level;\n\tghandle->cur_octs = 0;\n\n\treturn ARTIO_SUCCESS;\n}\n\n/*\n * Description        Do something at the end of each kind of read operation\n */\nint artio_grid_read_level_end(artio_fileset *handle) {\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tghandle = handle->grid;\n\n\tif (ghandle->cur_level == -1 &&\n\t\t\t( ghandle->cur_level < ghandle->cur_num_levels - 1 ||\n\t\t\t\tghandle->next_level_oct != ghandle->octs_per_level[ghandle->cur_level] ) ) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tghandle->cur_level = -1;\n\tghandle->cur_octs = -1;\n\tghandle->next_level_oct = -1;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_read_root_cell_end(artio_fileset *handle) {\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\thandle->grid->cur_sfc = -1;\n\thandle->grid->cur_level = -1;\n\thandle->grid->pos_flag = 0;\n\thandle->grid->pos_cur_level = -1;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_read_sfc_range_levels(artio_fileset *handle,\n\t\tint64_t sfc1, int64_t sfc2,\n\t\tint min_level_to_read, int max_level_to_read,\n\t\tint options,\n\t\tartio_grid_callback callback,\n\t\tvoid *params ) {\n\tint i, j;\n\tint64_t sfc;\n\tint oct, level;\n\tint ret;\n\tint *octs_per_level = NULL;\n\tint refined;\n\tint oct_refined[8];\n\tint root_tree_levels;\n\tfloat *variables = NULL;\n\tdouble pos[3], cell_pos[3];\n\n\tartio_grid_file *ghandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tif ( ( (options & ARTIO_RETURN_CELLS) &&\n\t\t\t\t!(options & ARTIO_READ_LEAFS) &&\n\t\t\t\t!(options & ARTIO_READ_REFINED)) ||\n\t\t\t( (options & ARTIO_RETURN_OCTS) &&\n\t\t\t\t((options & ARTIO_READ_LEAFS) ||\n\t\t\t\t(options & ARTIO_READ_REFINED) ) &&\n\t\t\t\t!((options & ARTIO_READ_ALL) == ARTIO_READ_ALL ) ) ) {\n\t\treturn ARTIO_ERR_INVALID_CELL_TYPES;\n\t}\n\n\tghandle = handle->grid;\n\n\tif ((min_level_to_read < 0) || (min_level_to_read > max_level_to_read)) {\n\t\treturn ARTIO_ERR_INVALID_LEVEL;\n\t}\n\n\tocts_per_level = (int *)malloc(ghandle->file_max_level * sizeof(int));\n\tvariables = (float *)malloc(8*ghandle->num_grid_variables * sizeof(float));\n\n\tif ( octs_per_level == NULL || variables == NULL ) {\n\t\tif ( octs_per_level != NULL ) free(octs_per_level);\n\t\tif ( variables != NULL ) free(variables);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tret = artio_grid_cache_sfc_range(handle, sfc1, sfc2);\n\tif ( ret != ARTIO_SUCCESS ) {\n\t\tfree(octs_per_level);\n\t\tfree(variables);\n\t\treturn ret;\n\t}\n\n\tfor (sfc = sfc1; sfc <= sfc2; sfc++) {\n\t\tret = artio_grid_read_root_cell_begin(handle, sfc, pos,\n\t\t\t\tvariables, &root_tree_levels, octs_per_level);\n\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\tfree(octs_per_level);\n\t\t\tfree(variables);\n\t\t\treturn ret;\n\t\t}\n\n\t\tif (min_level_to_read == 0 &&\n\t\t\t\t((options & ARTIO_READ_REFINED && root_tree_levels > 0) ||\n\t\t\t\t(options & ARTIO_READ_LEAFS && root_tree_levels == 0)) ) {\n\t\t\trefined = (root_tree_levels > 0) ? 1 : 0;\n\t\t\tcallback( sfc, 0, pos, variables, &refined, params );\n\t\t}\n\n\t\tfor (level = MAX(min_level_to_read,1);\n\t\t\t\tlevel <= MIN(root_tree_levels,max_level_to_read); level++) {\n\t\t\tret = artio_grid_read_level_begin(handle, level);\n\t\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\t\tfree(octs_per_level);\n\t\t\t\tfree(variables);\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\tfor (oct = 0; oct < octs_per_level[level - 1]; oct++) {\n\t\t\t\tret = artio_grid_read_oct(handle, pos, variables, oct_refined);\n\t\t\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\t\t\tfree(octs_per_level);\n\t\t\t\t\tfree(variables);\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tif ( options & ARTIO_RETURN_OCTS ) {\n\t\t\t\t\tcallback( sfc, level, pos, variables, oct_refined, params );\n\t\t\t\t} else {\n\t\t\t\t\tfor (i = 0; i < 8; i++) {\n\t\t\t\t\t\tif ( (options & ARTIO_READ_REFINED && oct_refined[i]>0) ||\n\t\t\t\t\t\t\t\t(options & ARTIO_READ_LEAFS && oct_refined[i]<=0) ) {\n\t\t\t\t\t\t\tfor ( j = 0; j < 3; j++ ) {\n\t\t\t\t\t\t\t\tcell_pos[j] = pos[j] + ghandle->cell_size_level*oct_pos_offsets[i][j];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallback( sfc, level, cell_pos,\n\t\t\t\t\t\t\t\t\t&variables[i * ghandle->num_grid_variables],\n\t\t\t\t\t\t\t\t\t&oct_refined[i], params );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tartio_grid_read_level_end(handle);\n\t\t}\n\t\tartio_grid_read_root_cell_end(handle);\n\t}\n\n\tfree(variables);\n\tfree(octs_per_level);\n\n\tartio_grid_clear_sfc_cache(handle);\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_grid_read_sfc_range(artio_fileset *handle,\n        int64_t sfc1, int64_t sfc2,\n\t\tint options,\n\t\tartio_grid_callback callback,\n\t\tvoid *params) {\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\treturn artio_grid_read_sfc_range_levels( handle, sfc1, sfc2,\n\t\t\t0, handle->grid->file_max_level, options, callback, params );\n}\n\nint artio_grid_read_selection(artio_fileset *handle,\n        artio_selection *selection, int options, artio_grid_callback callback,\n\t\tvoid *params ) {\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\treturn artio_grid_read_selection_levels( handle, selection,\n\t\t\t0, handle->grid->file_max_level, options, callback, params );\n}\n\nint artio_grid_read_selection_levels( artio_fileset *handle,\n        artio_selection *selection,\n        int min_level_to_read, int max_level_to_read,\n\t\tint options,\n        artio_grid_callback callback, void *params ) {\n\tint ret;\n\tint64_t start, end;\n\n\t/* loop over selected ranges */\n\tartio_selection_iterator_reset( selection );\n\twhile ( artio_selection_iterator( selection,\n\t\t\thandle->num_root_cells,\n\t\t\t&start, &end ) == ARTIO_SUCCESS ) {\n\t\tret = artio_grid_read_sfc_range_levels( handle, start, end,\n\t\t\t\tmin_level_to_read, max_level_to_read, options,\n\t\t\t\tcallback, params);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_internal.h",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#ifndef __ARTIO_INTERNAL_H__\n#define __ARTIO_INTERNAL_H__\n\n#ifdef ARTIO_MPI\n#include <mpi.h>\n#endif\n\n#include <stdlib.h>\n#include <limits.h>\n\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\n#include \"artio_endian.h\"\n\n#ifndef ARTIO_DEFAULT_BUFFER_SIZE\n#define ARTIO_DEFAULT_BUFFER_SIZE\t65536\n#endif\n\nextern int artio_fh_buffer_size;\n\n#define nDim\t\t\t3\n\n#ifndef MIN\n#define MIN(x,y)        (((x) < (y)) ? (x): (y))\n#endif\n#ifndef MAX\n#define MAX(x,y)        (((x) > (y)) ? (x): (y))\n#endif\n\n/* limit individual writes to 32-bit safe quantity */\n#define ARTIO_IO_MAX\t(1<<30)\n\n#ifdef INT64_MAX\n#define ARTIO_INT64_MAX\tINT64_MAX\n#else\n#define ARTIO_INT64_MAX 0x7fffffffffffffffLL\n#endif\n\ntypedef struct ARTIO_FH artio_fh;\n\ntypedef struct artio_particle_file_struct {\n\tartio_fh **ffh;\n\n\tvoid *buffer;\n\tint buffer_size;\n\n\tint num_particle_files;\n\tint64_t *file_sfc_index;\n\tint64_t cache_sfc_begin;\n\tint64_t cache_sfc_end;\n\tint64_t *sfc_offset_table;\n\n\t/* maintained for consistency and user-error detection */\n\tint num_species;\n\tint cur_file;\n\tint cur_species;\n\tint cur_particle;\n\tint64_t cur_sfc;\n\tint *num_primary_variables;\n\tint *num_secondary_variables;\n\tint *num_particles_per_species;\n} artio_particle_file;\n\ntypedef struct artio_grid_file_struct {\n\tartio_fh **ffh;\n\n\tvoid *buffer;\n\tint buffer_size;\n\n\tint num_grid_variables;\n\tint num_grid_files;\n\tint64_t *file_sfc_index;\n\tint64_t cache_sfc_begin;\n\tint64_t cache_sfc_end;\n\tint64_t *sfc_offset_table;\n\n\tint file_max_level;\n\t/* maintained for consistency and user-error detection */\n\tint cur_file;\n\tint cur_num_levels;\n\tint cur_level;\n\tint cur_octs;\n\tint64_t cur_sfc;\n\tint *octs_per_level;\n\n\tint pos_flag;\n\tint pos_cur_level;\n\tint next_level_size;\n\tint cur_level_size;\n\tdouble cell_size_level;\n\tdouble *next_level_pos;\n\tdouble *cur_level_pos;\n\tint next_level_oct;\n\n} artio_grid_file;\n\ntypedef struct parameter_struct {\n\tint key_length;\n\tchar key[64];\n\tint val_length;\n\tint type;\n\tchar *value;\n\tstruct parameter_struct * next;\n} parameter;\n\ntypedef struct parameter_list_struct {\n\tparameter * head;\n\tparameter * tail;\n\tparameter * cursor;\n\tint iterate_flag;\n} parameter_list;\n\nstruct artio_fileset_struct {\n\tchar file_prefix[256];\n\tint endian_swap;\n\tint open_type;\n\tint open_mode;\n\tint rank;\n\tint num_procs;\n\tartio_context *context;\n\n\tint64_t *proc_sfc_index;\n\tint64_t proc_sfc_begin;\n\tint64_t proc_sfc_end;\n\tint64_t num_root_cells;\n\tint sfc_type;\n\tint nBitsPerDim;\n\tint num_grid;\n\n\tparameter_list *parameters;\n\tartio_grid_file *grid;\n\tartio_particle_file *particle;\n};\n\nstruct artio_selection_struct {\n    int64_t *list;\n    int size;\n    int num_ranges;\n\tint cursor;\n\tint64_t subcycle;\n\tartio_fileset *fileset;\n};\n\n#define ARTIO_FILESET_READ      0\n#define ARTIO_FILESET_WRITE     1\n\n#define ARTIO_MODE_READ         1\n#define ARTIO_MODE_WRITE        2\n#define ARTIO_MODE_ACCESS       4\n#define ARTIO_MODE_ENDIAN_SWAP  8\n\n#define ARTIO_SEEK_SET          0\n#define ARTIO_SEEK_CUR          1\n#define ARTIO_SEEK_END\t\t\t2\n\n/* wrapper functions for profiling and debugging */\nartio_fh *artio_file_fopen( char * filename, int amode, const artio_context *context );\nint artio_file_attach_buffer( artio_fh *handle, void *buf, int buf_size );\nint artio_file_detach_buffer( artio_fh *handle );\nint artio_file_fwrite(artio_fh *handle, const void *buf, int64_t count, int type );\nint artio_file_ftell( artio_fh *handle, int64_t *offset );\nint artio_file_fflush(artio_fh *handle);\nint artio_file_fseek(artio_fh *ffh, int64_t offset, int whence);\nint artio_file_fread(artio_fh *handle, void *buf, int64_t count, int type );\nint artio_file_fclose(artio_fh *handle);\nvoid artio_file_set_endian_swap_tag(artio_fh *handle);\n\n/* internal versions */\nartio_fh *artio_file_fopen_i( char * filename, int amode, const artio_context *context );\nint artio_file_attach_buffer_i( artio_fh *handle, void *buf, int buf_size );\nint artio_file_detach_buffer_i( artio_fh *handle );\nint artio_file_fwrite_i(artio_fh *handle, const void *buf, int64_t count, int type );\nint artio_file_ftell_i( artio_fh *handle, int64_t *offset );\nint artio_file_fflush_i(artio_fh *handle);\nint artio_file_fseek_i(artio_fh *ffh, int64_t offset, int whence);\nint artio_file_fread_i(artio_fh *handle, void *buf, int64_t count, int type );\nint artio_file_fclose_i(artio_fh *handle);\nvoid artio_file_set_endian_swap_tag_i(artio_fh *handle);\n\n#define ARTIO_ENDIAN_MAGIC\t0x1234\n\nparameter_list *artio_parameter_list_init(void);\n\nparameter *artio_parameter_list_search(parameter_list *parameters, const char *key);\n\nint artio_parameter_array_length( parameter *item );\n\nint artio_parameter_list_insert(parameter_list *parameters, const char *key, int length,\n\t\tvoid * value, int type);\n\nint artio_parameter_read(artio_fh *handle, parameter_list *parameters);\n\nint artio_parameter_write(artio_fh *handle, parameter_list *parameters);\n\nint artio_parameter_list_print(parameter_list *parameters);\nint artio_parameter_list_free(parameter_list *parameters);\nint artio_parameter_list_print(parameter_list *parameters);\n\nsize_t artio_type_size( int type );\nint artio_parameter_list_unpack(parameter_list *parameters, const char *key, int length, void *value, int type );\n\n#define ARTIO_SFC_SLAB_X\t0\n#define ARTIO_SFC_MORTION\t1\n#define ARTIO_SFC_HILBERT\t2\n#define ARTIO_SFC_SLAB_Y\t3\n#define ARTIO_SFC_SLAB_Z\t4\n\nint64_t artio_sfc_index_position( artio_fileset *handle, double position[nDim] );\nint64_t artio_sfc_index( artio_fileset *handle, int coords[nDim] );\nvoid artio_sfc_coords( artio_fileset *handle, int64_t index, int coords[nDim] );\n\n#endif /* __ARTIO_INTERNAL_H__ */\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_mpi.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#include \"artio.h\"\n#include \"artio_internal.h\"\n\n#ifdef ARTIO_MPI\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\nartio_context artio_context_global_struct = { MPI_COMM_WORLD };\nconst artio_context *artio_context_global = &artio_context_global_struct;\n\nstruct ARTIO_FH {\n\tMPI_File fh;\n\tMPI_Comm comm;\n\tint mode;\n\tchar *data;\n\tint bfsize;\n\tint bfptr;\n\tint bfend;\n};\n\nartio_fh *artio_file_fopen_i( char * filename, int mode, const artio_context *context) {\n\tint status;\n\tint flag;\n\tint rank;\n\tint amode;\n\n\tif ( mode & ARTIO_MODE_WRITE &&\n\t\t\tmode & ARTIO_MODE_READ ) {\n\t\treturn NULL;\n\t} else if ( mode & ARTIO_MODE_WRITE ) {\n\t\tamode = MPI_MODE_CREATE | MPI_MODE_WRONLY;\n\t} else if ( mode & ARTIO_MODE_READ ) {\n\t\tamode = MPI_MODE_RDONLY;\n\t} else {\n\t\treturn NULL;\n\t}\n\n\tartio_fh *ffh = (artio_fh *)malloc(sizeof(artio_fh));\n\tif ( ffh == NULL ) {\n\t\treturn NULL;\n\t}\n\n\tffh->mode = mode;\n\tffh->data = NULL;\n\tffh->bfsize = -1;\n\tffh->bfend = -1;\n\tffh->bfptr = -1;\n\n\tflag = mode & ARTIO_MODE_ACCESS;\n\n\tMPI_Comm_rank( context->comm, &rank );\n\tMPI_Comm_split( context->comm, flag, rank, &ffh->comm );\n\n\tif ( flag ) {\n\t\tstatus = MPI_File_open( ffh->comm, filename, amode, MPI_INFO_NULL, &ffh->fh);\n\t\tif (status != MPI_SUCCESS) {\n\t\t\tMPI_Comm_free(&ffh->comm);\n\t\t\tfree( ffh );\n\t\t\treturn NULL;\n\t\t}\n\n\t\t/* truncate the file on write */\n\t\tif ( mode & ARTIO_MODE_WRITE ) {\n\t\t\tMPI_File_set_size(ffh->fh, 0);\n\t\t}\n\t}\n\n\treturn ffh;\n}\n\nint artio_file_attach_buffer_i( artio_fh *handle, void *buf, int buf_size ) {\n\tif ( !(handle->mode & ARTIO_MODE_ACCESS ) ) {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\tif ( handle->data != NULL ) {\n\t\treturn ARTIO_ERR_BUFFER_EXISTS;\n\t}\n\n\thandle->bfsize = buf_size;\n\thandle->bfend = -1;\n\thandle->bfptr = 0;\n\thandle->data = (char *)buf;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_detach_buffer_i( artio_fh *handle ) {\n\tint ret;\n\tret = artio_file_fflush(handle);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\thandle->data = NULL;\n\thandle->bfsize = -1;\n\thandle->bfend = -1;\n\thandle->bfptr = -1;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_fwrite_i( artio_fh *handle, const void *buf, int64_t count, int type ) {\n\tsize_t size;\n\tint64_t remain;\n\tint size32;\n\tchar *p;\n\n\tif ( !(handle->mode & ARTIO_MODE_WRITE) ||\n\t\t\t!(handle->mode & ARTIO_MODE_ACCESS) ) {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\tsize = artio_type_size( type );\n\tif ( size == (size_t)-1 ) {\n\t\treturn ARTIO_ERR_INVALID_DATATYPE;\n\t}\n\n\tif ( count > ARTIO_INT64_MAX / size ) {\n\t\treturn ARTIO_ERR_IO_OVERFLOW;\n\t}\n\n\tremain = size*count;\n\tp = (char *)buf;\n\n\tif ( handle->data == NULL ) {\n\t\twhile ( remain > 0 ) {\n\t\t\tsize32 = MIN( ARTIO_IO_MAX, remain );\n\t\t\tif ( MPI_File_write( handle->fh, p, size32,\n\t\t\t\t\tMPI_BYTE, MPI_STATUS_IGNORE ) != MPI_SUCCESS ) {\n\t\t\t\treturn ARTIO_ERR_IO_WRITE;\n\t\t\t}\n\t\t\tremain -= size32;\n\t\t\tp += size32;\n\t\t}\n\t} else if ( remain < handle->bfsize - handle->bfptr ) {\n\t\tmemcpy( handle->data + handle->bfptr, p, (size_t)remain );\n\t\thandle->bfptr += remain;\n\t} else {\n\t\t/* complete buffer */\n\t\tsize32 = handle->bfsize - handle->bfptr;\n\t\tmemcpy( handle->data + handle->bfptr, p, size32 );\n\t\tif ( MPI_File_write(handle->fh, handle->data, handle->bfsize,\n\t\t\t\t\tMPI_BYTE, MPI_STATUS_IGNORE ) != MPI_SUCCESS ) {\n\t\t\treturn ARTIO_ERR_IO_WRITE;\n\t\t}\n\t\tp += size32;\n\t\tremain -= size32;\n\n\t\twhile ( remain > handle->bfsize ) {\n\t\t\tif ( MPI_File_write(handle->fh, p, handle->bfsize,\n\t\t\t\t\t\tMPI_BYTE, MPI_STATUS_IGNORE ) != MPI_SUCCESS ) {\n\t\t\t\treturn ARTIO_ERR_IO_WRITE;\n\t\t\t}\n\t\t\tremain -= handle->bfsize;\n\t\t\tp += handle->bfsize;\n\t\t}\n\n\t\tmemcpy( handle->data, p, (size_t)remain);\n\t\thandle->bfptr = remain;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_fflush_i(artio_fh *handle) {\n\tif ( !(handle->mode & ARTIO_MODE_ACCESS) ) {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\tif ( handle->mode & ARTIO_MODE_WRITE ) {\n\t\tif ( handle->bfptr > 0 ) {\n\t\t\tif ( MPI_File_write(handle->fh, handle->data, handle->bfptr,\n\t\t\t\t\tMPI_BYTE, MPI_STATUS_IGNORE ) != MPI_SUCCESS ) {\n\t\t\t\treturn ARTIO_ERR_IO_WRITE;\n\t\t\t}\n\t\t\thandle->bfptr = 0;\n\t\t}\n\t} else if ( handle->mode & ARTIO_MODE_READ ) {\n\t\thandle->bfend = -1;\n\t\thandle->bfptr = 0;\n\t} else {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_fread_i(artio_fh *handle, void *buf, int64_t count, int type ) {\n\tMPI_Status status;\n\tsize_t size, avail, remain;\n\tint size_read, size32;\n\tchar *p;\n\n\tif ( !(handle->mode & ARTIO_MODE_READ) ) {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\tsize = artio_type_size( type );\n\tif ( size == (size_t)-1 ) {\n\t\treturn ARTIO_ERR_INVALID_DATATYPE;\n\t}\n\n\tif ( count > ARTIO_INT64_MAX / size ) {\n\t\treturn ARTIO_ERR_IO_OVERFLOW;\n\t}\n\n\tremain = size*count;\n\tp = (char *)buf;\n\n\tif ( handle->data == NULL ) {\n\t\twhile ( remain > 0 ) {\n\t\t\tsize32 = MIN( ARTIO_IO_MAX, remain );\n\t\t\tif ( MPI_File_read(handle->fh, p, size32,\n\t\t\t\t\tMPI_BYTE, &status ) != MPI_SUCCESS ) {\n\t\t\t\treturn ARTIO_ERR_IO_READ;\n\t\t\t}\n\t\t\tMPI_Get_count( &status, MPI_BYTE, &size_read );\n\t\t\tif ( size_read != size32 ) {\n\t\t\t\treturn ARTIO_ERR_INSUFFICIENT_DATA;\n\t\t\t}\n\t\t\tremain -= size32;\n\t\t\tp += size32;\n\t\t}\n\t} else {\n\t\tif ( handle->bfend == -1 ) {\n\t\t\t/* load initial data into buffer */\n\t\t\tif ( MPI_File_read(handle->fh, handle->data,\n\t\t\t\t\thandle->bfsize, MPI_BYTE, &status) != MPI_SUCCESS ) {\n\t\t\t\treturn ARTIO_ERR_IO_READ;\n\t\t\t}\n\t\t\tMPI_Get_count(&status, MPI_BYTE, &handle->bfend);\n\t\t\thandle->bfptr = 0;\n\t\t}\n\n\t\t/* read from buffer */\n\t\twhile ( remain > 0 &&\n\t\t\t\thandle->bfend > 0 &&\n\t\t\t\thandle->bfptr + remain >= handle->bfend ) {\n\t\t\tavail = handle->bfend - handle->bfptr;\n\t\t\tmemcpy( p, handle->data + handle->bfptr, avail );\n\t\t\tp += avail;\n\t\t\tremain -= avail;\n\n\t\t\t/* refill buffer */\n\t\t\tif ( MPI_File_read(handle->fh, handle->data, handle->bfsize,\n\t\t\t\t\tMPI_BYTE, &status ) != MPI_SUCCESS ) {\n\t\t\t\treturn ARTIO_ERR_IO_READ;\n\t\t\t}\n\t\t\tMPI_Get_count(&status, MPI_BYTE, &handle->bfend );\n\t\t\thandle->bfptr = 0;\n\t\t}\n\n\t\tif ( remain > 0 ) {\n\t\t\tif ( handle->bfend == 0 ) {\n\t\t\t\t/* ran out of data, eof */\n\t\t\t\treturn ARTIO_ERR_INSUFFICIENT_DATA;\n\t\t\t}\n\n\t\t\tmemcpy( p, handle->data + handle->bfptr, (size_t)remain );\n\t\t\thandle->bfptr += (int)remain;\n\t\t}\n\t}\n\n\tif(handle->mode & ARTIO_MODE_ENDIAN_SWAP){\n\t\tswitch (type) {\n\t\t\tcase ARTIO_TYPE_INT :\n\t\t\t\tartio_int_swap( (int32_t *)buf, count );\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_FLOAT :\n\t\t\t\tartio_float_swap( (float *)buf, count );\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_DOUBLE :\n\t\t\t\tartio_double_swap( (double *)buf, count );\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_LONG :\n\t\t\t\tartio_long_swap( (int64_t *)buf, count );\n\t\t\t\tbreak;\n\t\t\tdefault :\n\t\t\t\treturn ARTIO_ERR_INVALID_DATATYPE;\n\t\t}\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_ftell_i(artio_fh *handle, int64_t *offset) {\n\tMPI_Offset current;\n\tMPI_File_get_position( handle->fh, &current );\n\n\tif ( handle->bfend > 0 ) {\n\t\tcurrent -= handle->bfend;\n\t}\n\tif ( handle->bfptr > 0 ) {\n\t\tcurrent += handle->bfptr;\n\t}\n\t*offset = (int64_t)current;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_fseek_i(artio_fh *handle, int64_t offset, int whence ) {\n\tMPI_Offset current;\n\n\tif ( handle->mode & ARTIO_MODE_ACCESS ) {\n\t\tif ( whence == ARTIO_SEEK_CUR ) {\n\t\t\tif ( offset == 0 ) {\n\t\t\t\treturn ARTIO_SUCCESS;\n\t\t\t} else if ( handle->mode & ARTIO_MODE_READ &&\n\t\t\t\t\thandle->bfend > 0 &&\n                \thandle->bfptr + offset >= 0 &&\n\t\t\t\t\thandle->bfptr + offset < handle->bfend ) {\n\t\t\t\thandle->bfptr += offset;\n\t\t\t\treturn ARTIO_SUCCESS;\n\t\t\t} else {\n\t\t\t\tif ( handle->bfptr > 0 ) {\n\t\t\t\t\tcurrent = (MPI_Offset)offset - handle->bfend + handle->bfptr;\n\t\t\t\t} else {\n\t\t\t\t\tcurrent = (MPI_Offset)offset;\n\t\t\t\t}\n\n\t\t\t\tartio_file_fflush( handle );\n\t\t\t\tMPI_File_seek( handle->fh, current, MPI_SEEK_CUR );\n\t\t\t}\n\t\t} else if ( whence == ARTIO_SEEK_SET ) {\n\t\t\tMPI_File_get_position( handle->fh, &current );\n\t\t\tif (handle->mode & ARTIO_MODE_WRITE &&\n\t\t\t\t\tcurrent <= offset &&\n\t\t\t\t\toffset < current + handle->bfsize &&\n\t\t\t\t\thandle->bfptr == offset - current ) {\n\t\t\t\treturn ARTIO_SUCCESS;\n\t\t\t} else if ( handle->mode & ARTIO_MODE_READ &&\n\t\t\t\t\thandle->bfptr > 0 &&\n\t\t\t\t\thandle->bfend > 0 &&\n\t\t\t\t\thandle->bfptr < handle->bfend &&\n\t\t\t\t\toffset >= current - handle->bfend &&\n\t\t\t\t\toffset < current ) {\n\t\t\t\thandle->bfptr = offset - current + handle->bfend;\n\t\t\t} else {\n\t\t\t\tartio_file_fflush( handle );\n\t\t\t\tMPI_File_seek( handle->fh, (MPI_Offset)offset, MPI_SEEK_SET );\n\t\t\t}\n\t\t} else if ( whence == ARTIO_SEEK_END ) {\n\t\t\tartio_file_fflush(handle);\n\t\t\tMPI_File_seek( handle->fh, (MPI_Offset)offset, MPI_SEEK_END );\n\t\t} else {\n\t\t\t/* unknown whence */\n\t\t\treturn ARTIO_ERR_INVALID_SEEK;\n\t\t}\n\t} else {\n\t\t/* seek on non-active file handle */\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_fclose_i(artio_fh *handle) {\n\tif ( handle->mode & ARTIO_MODE_ACCESS ) {\n\t\tartio_file_fflush(handle);\n\t\tMPI_File_close(&handle->fh);\n\t}\n\tMPI_Comm_free(&handle->comm);\n\tfree(handle);\n\treturn ARTIO_SUCCESS;\n}\n\nvoid artio_file_set_endian_swap_tag_i(artio_fh *handle) {\n\thandle->mode |= ARTIO_MODE_ENDIAN_SWAP;\n}\n\n#endif /* MPI */\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_mpi.h",
    "content": "/*\n * artio_mpi.h\n *\n *  Created on: Mar 6, 2012\n *      Author: Nick Gnedin\n */\n\n#ifndef __ARTIO_MPI_H__\n#define __ARTIO_MPI_H__\n\n#include <mpi.h>\n\nstruct artio_context_struct {\n\tMPI_Comm comm;\n};\n\n#endif /* __ARTIO_MPI_H__ */\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_parameter.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#include \"artio.h\"\n#include \"artio_internal.h\"\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\nsize_t artio_type_size(int type) {\n\tsize_t t_len=0;\n\n\tswitch (type) {\n\t\tcase ARTIO_TYPE_STRING:\n\t\tcase ARTIO_TYPE_CHAR:\n\t\t\tt_len = sizeof(char);\n\t\t\tbreak;\n\t\tcase ARTIO_TYPE_INT:\n\t\t\tt_len = sizeof(int32_t);\n\t\t\tbreak;\n\t\tcase ARTIO_TYPE_FLOAT:\n\t\t\tt_len = sizeof(float);\n\t\t\tbreak;\n\t\tcase ARTIO_TYPE_DOUBLE:\n\t\t\tt_len = sizeof(double);\n\t\t\tbreak;\n\t\tcase ARTIO_TYPE_LONG:\n\t\t\tt_len = sizeof(int64_t);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tt_len = (size_t)-1;\n\t\t\tbreak;\n\t}\n\n\treturn t_len;\n}\n\nparameter_list *artio_parameter_list_init() {\n\tparameter_list *parameters = (parameter_list *)malloc(sizeof(parameter_list));\n\tif ( parameters != NULL ) {\n\t\tparameters->head = NULL;\n\t\tparameters->tail = NULL;\n\t\tparameters->cursor = NULL;\n\t\tparameters->iterate_flag = 0;\n\t}\n\treturn parameters;\n}\n\nint artio_parameter_read(artio_fh *handle, parameter_list *parameters) {\n\tparameter * item;\n\tint i;\n\tint length, re;\n\tint t_len;\n\tint32_t endian_tag;\n\n    /* endian check */\n\tre = artio_file_fread(handle, &endian_tag, 1, ARTIO_TYPE_INT);\n\tif ( re != ARTIO_SUCCESS ) {\n\t\treturn ARTIO_ERR_PARAM_CORRUPTED;\n\t}\n\n\tif ( endian_tag != ARTIO_ENDIAN_MAGIC ) {\n\t\tartio_int_swap( &endian_tag, 1 );\n\t\tif ( endian_tag == ARTIO_ENDIAN_MAGIC ) {\n\t\t\tartio_file_set_endian_swap_tag(handle);\n\t\t} else {\n\t\t\treturn ARTIO_ERR_PARAM_CORRUPTED_MAGIC;\n\t\t}\n\t}\n\n\tre = artio_file_fread(handle, &length, 1, ARTIO_TYPE_INT);\n\tif ( re != ARTIO_SUCCESS ) {\n\t\treturn ARTIO_ERR_PARAM_CORRUPTED;\n\t}\n\n\tfor ( i = 0; i < length; i++ ) {\n\t\titem = (parameter *)malloc(sizeof(parameter));\n\t\tif ( item == NULL ) {\n\t\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t\t}\n\n\t\tartio_file_fread(handle, &item->key_length, 1, ARTIO_TYPE_INT);\n\t\tartio_file_fread(handle, item->key, item->key_length, ARTIO_TYPE_CHAR);\n\t\titem->key[item->key_length] = 0;\n\n\t\tartio_file_fread(handle, &item->val_length, 1, ARTIO_TYPE_INT);\n\t\tartio_file_fread(handle, &item->type, 1, ARTIO_TYPE_INT);\n\n\t\tt_len = artio_type_size(item->type);\n\t\titem->value = (char *)malloc(item->val_length * t_len);\n\n\t\tre = artio_file_fread(handle, item->value,\n\t\t\t\titem->val_length, item->type);\n\t\tif ( re != ARTIO_SUCCESS ) {\n\t\t\treturn ARTIO_ERR_PARAM_CORRUPTED;\n\t\t}\n\n\t\titem->next = NULL;\n\t\tif (NULL == parameters->tail) {\n\t\t\tparameters->tail = item;\n\t\t\tparameters->head = item;\n\t\t} else {\n\t\t\tparameters->tail->next = item;\n\t\t\tparameters->tail = item;\n\t\t}\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_parameter_write(artio_fh *handle, parameter_list *parameters) {\n\tparameter * item;\n\n\t/* retain a number for endian check */\n\tint32_t endian_tag = ARTIO_ENDIAN_MAGIC;\n\tint32_t length = 0;\n\n\titem = parameters->head;\n\twhile (NULL != item) {\n\t\tlength++;\n\t\titem = item->next;\n\t}\n\n\tartio_file_fwrite(handle, &endian_tag,\n\t\t\t1, ARTIO_TYPE_INT);\n\tartio_file_fwrite(handle, &length,\n\t\t\t1, ARTIO_TYPE_INT);\n\n\titem = parameters->head;\n\twhile (NULL != item) {\n\t\tartio_file_fwrite(handle, &item->key_length,\n\t\t\t\t1, ARTIO_TYPE_INT);\n\t\tartio_file_fwrite(handle, item->key,\n\t\t\t\titem->key_length, ARTIO_TYPE_CHAR);\n\t\tartio_file_fwrite(handle, &item->val_length,\n\t\t\t\t1, ARTIO_TYPE_INT);\n\t\tartio_file_fwrite(handle, &item->type,\n\t\t\t\t1, ARTIO_TYPE_INT);\n\t\tartio_file_fwrite(handle, item->value,\n\t\t\t\titem->val_length, item->type);\n\t\titem = item->next;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_parameter_iterate( artio_fileset *handle,\n\t\tchar *key, int *type, int *length ) {\n\tparameter *item;\n\tparameter_list *parameters = handle->parameters;\n\n\tif ( parameters->iterate_flag == 0 ) {\n\t\tparameters->cursor = parameters->head;\n\t\tparameters->iterate_flag = 1;\n\t}\n\n\tif ( parameters->cursor == NULL ) {\n\t\tparameters->iterate_flag = 0;\n\t\treturn ARTIO_PARAMETER_EXHAUSTED;\n\t}\n\n\titem = parameters->cursor;\n\tstrncpy( key, item->key, 64 );\n\t*type = item->type;\n\t*length = artio_parameter_array_length(item);\n\n\tparameters->cursor = item->next;\n\treturn ARTIO_SUCCESS;\n}\n\nparameter *artio_parameter_list_search(parameter_list * parameters, const char *key ) {\n\tparameter * item = parameters->head;\n\twhile ( NULL != item && strcmp(item->key, key) ) {\n\t\titem = item->next;\n\t}\n\treturn item;\n}\n\nint artio_parameter_list_insert(parameter_list * parameters, const char * key,\n\t\tint length, void *value, int type) {\n\tint key_len;\n\tsize_t val_len = 0;\n\tparameter * item;\n\n\tif ( length <= 0 ) {\n\t\treturn ARTIO_ERR_PARAM_LENGTH_INVALID;\n\t}\n\n\titem = artio_parameter_list_search(parameters, key);\n\tif (NULL != item) {\n\t\treturn ARTIO_ERR_PARAM_DUPLICATE;\n\t}\n\n\t/* create the list node */\n\titem = (parameter *)malloc(sizeof(parameter));\n\tif ( item == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tkey_len = strlen(key);\n\titem->key_length = key_len;\n\tstrcpy(item->key, key);\n\titem->val_length = length;\n\titem->type = type;\n\tval_len = artio_type_size(type);\n\titem->value = (char *)malloc(length * val_len);\n\tif ( item->value == NULL ) {\n\t\tfree(item);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tmemcpy(item->value, value, length * val_len);\n\n\titem->next = NULL;\n\t/* add to the list */\n\tif (NULL == parameters->tail) {\n\t\tparameters->tail = item;\n\t\tparameters->head = item;\n\t} else {\n\t\tparameters->tail->next = item;\n\t\tparameters->tail = item;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_parameter_list_unpack(parameter_list *parameters,\n\t\tconst char *key, int length,\n\t\tvoid *value, int type) {\n\tsize_t t_len;\n\tparameter *item = artio_parameter_list_search(parameters, key);\n\n\tif (item != NULL) {\n\t\tif (length != item->val_length ) {\n\t\t\treturn ARTIO_ERR_PARAM_LENGTH_MISMATCH;\n\t\t} else if ( type != item->type ) {\n\t\t\treturn ARTIO_ERR_PARAM_TYPE_MISMATCH;\n\t\t} else {\n\t\t\tt_len = artio_type_size(type);\n\t\t\tmemcpy(value, item->value, item->val_length * t_len);\n\t\t}\n\t} else {\n\t\treturn ARTIO_ERR_PARAM_NOT_FOUND;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_parameter_list_unpack_index(parameter_list *parameters,\n        const char *key, int index,\n\t\tvoid *value, int type) {\n\tsize_t t_len;\n\tparameter *item;\n\n\tif ( index < 0 ) {\n\t\treturn ARTIO_ERR_INVALID_INDEX;\n\t}\n\n\titem = artio_parameter_list_search(parameters, key);\n\tif (item != NULL) {\n\t\tif (index >= item->val_length ) {\n\t\t\treturn ARTIO_ERR_PARAM_LENGTH_MISMATCH;\n\t\t} else if ( type != item->type ) {\n\t\t\treturn ARTIO_ERR_PARAM_TYPE_MISMATCH;\n\t\t} else {\n\t\t\tt_len = artio_type_size(type);\n\t\t\tmemcpy(value, item->value+index*t_len, t_len);\n\t\t}\n\t} else {\n\t\treturn ARTIO_ERR_PARAM_NOT_FOUND;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_parameter_array_length( parameter *item ) {\n\tint i, length;\n\n\tif ( item->type == ARTIO_TYPE_STRING ) {\n\t\tlength = 0;\n\t\tfor ( i = 0; i < item->val_length; i++ ) {\n\t\t\tif ( item->value[i] == '\\0' ) {\n\t\t\t\tlength++;\n\t\t\t}\n\t\t}\n\t} else {\n\t\tlength = item->val_length;\n\t}\n\n\treturn length;\n}\n\nint artio_parameter_get_array_length(artio_fileset *handle, const char * key, int *length) {\n\tparameter *item = artio_parameter_list_search(handle->parameters, key);\n\n\tif (item != NULL) {\n\t\t*length = artio_parameter_array_length(item);\n\t} else {\n\t\treturn ARTIO_ERR_PARAM_NOT_FOUND;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_parameter_list_free(parameter_list * parameters) {\n\tparameter * tmp;\n\tparameter * item;\n\n\tif ( parameters != NULL ) {\n\t\titem  = parameters->head;\n\t\twhile (NULL != item) {\n\t\t\ttmp = item;\n\t\t\titem = item->next;\n\t\t\tfree(tmp->value);\n\t\t\tfree(tmp);\n\t\t}\n\n\t\tparameters->head = NULL;\n\t\tparameters->tail = NULL;\n\n\t\tfree( parameters );\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_parameter_list_print(parameter_list * parameters) {\n\tint32_t a;\n\tfloat b;\n\tdouble c;\n\tint64_t d;\n\n\tparameter * item = parameters->head;\n\twhile (NULL != item) {\n\t\tswitch ( item->type ) {\n\t\t\tcase ARTIO_TYPE_STRING:\n\t\t\t\tprintf(\"string: key %s %s\\n\", item->key, item->value);\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_CHAR:\n\t\t\t\tprintf(\"char: key %s %c\\n\", item->key, *item->value);\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_INT:\n\t\t\t\tmemcpy(&a, item->value, sizeof(int32_t));\n\t\t\t\tprintf(\"int: key %s %d\\n\", item->key, a);\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_FLOAT:\n\t\t\t\tmemcpy(&b, item->value, sizeof(float));\n\t\t\t\tprintf(\"float: key %s %f\\n\", item->key, b);\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_DOUBLE:\n\t\t\t\tmemcpy(&c, item->value, sizeof(double));\n\t\t\t\tprintf(\"double: key %s %f\\n\", item->key, c);\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_LONG:\n\t\t\t\tmemcpy(&d, item->value, sizeof(int64_t));\n\t\t\t\tprintf(\"long: %ld\\n\", d);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tprintf(\"unrecognized type %d\\n\", item->type);\n\t\t}\n\t\titem = item->next;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_parameter_set_int(artio_fileset *handle, const char * key, int32_t value) {\n\tint32_t tmp = value;\n\treturn artio_parameter_set_int_array(handle, key, 1, &tmp);\n}\n\nint artio_parameter_get_int(artio_fileset *handle, const char * key, int32_t * value) {\n\treturn artio_parameter_get_int_array(handle, key, 1, value);\n}\n\nint artio_parameter_set_int_array(artio_fileset *handle, const char * key, int length,\n\t\tint32_t * value) {\n\treturn artio_parameter_list_insert(handle->parameters, key, length, value,\n\t\t\tARTIO_TYPE_INT);\n}\n\nint artio_parameter_get_int_array(artio_fileset *handle, const char * key, int length,\n\t\tint32_t * value) {\n\treturn artio_parameter_list_unpack(handle->parameters, key, length,\n\t\t\tvalue, ARTIO_TYPE_INT);\n}\n\nint artio_parameter_get_int_array_index( artio_fileset *handle, const char *key, int index,\n\t\tint32_t * value ) {\n\treturn artio_parameter_list_unpack_index(handle->parameters, key,\n\t\t\tindex, value, ARTIO_TYPE_INT );\n}\n\nint artio_parameter_set_float(artio_fileset *handle, const char * key, float value) {\n\tfloat tmp = value;\n\treturn artio_parameter_set_float_array(handle, key, 1, &tmp);\n}\n\nint artio_parameter_get_float(artio_fileset *handle, const char * key, float * value) {\n\treturn artio_parameter_get_float_array(handle, key, 1, value);\n}\n\nint artio_parameter_set_float_array(artio_fileset *handle, const char * key,\n\t\tint length, float * value) {\n\treturn artio_parameter_list_insert(handle->parameters, key, length, value,\n\t\t\tARTIO_TYPE_FLOAT);\n}\n\nint artio_parameter_get_float_array(artio_fileset *handle, const char * key,\n\t\tint length, float * value) {\n\treturn artio_parameter_list_unpack(handle->parameters, key, length,\n\t\t\tvalue, ARTIO_TYPE_FLOAT);\n}\n\nint artio_parameter_get_float_array_index(artio_fileset *handle, const char * key,\n\t\tint index, float * value) {\n\treturn artio_parameter_list_unpack_index(handle->parameters, key, index,\n\t\t\tvalue, ARTIO_TYPE_FLOAT);\n}\n\nint artio_parameter_set_double(artio_fileset *handle, const char * key, double value) {\n\tdouble tmp = value;\n\treturn artio_parameter_set_double_array(handle, key, 1, &tmp);\n}\n\nint artio_parameter_get_double(artio_fileset *handle, const char * key, double * value) {\n\treturn artio_parameter_get_double_array(handle, key, 1, value);\n}\n\nint artio_parameter_set_double_array(artio_fileset *handle, const char * key,\n\t\tint length, double * value) {\n\treturn artio_parameter_list_insert(handle->parameters, key, length, value,\n\t\t\tARTIO_TYPE_DOUBLE);\n}\n\nint artio_parameter_get_double_array(artio_fileset *handle, const char * key,\n\t\tint length, double * value) {\n\treturn artio_parameter_list_unpack(handle->parameters, key, length,\n\t\t\tvalue, ARTIO_TYPE_DOUBLE);\n}\n\nint artio_parameter_get_double_array_index(artio_fileset *handle, const char * key,\n\t\tint index, double * value) {\n\treturn artio_parameter_list_unpack_index(handle->parameters, key, index,\n\t\t\tvalue, ARTIO_TYPE_DOUBLE);\n}\n\nint artio_parameter_set_long(artio_fileset *handle, const char * key, int64_t value) {\n\tint64_t tmp = value;\n\treturn artio_parameter_set_long_array(handle, key, 1, &tmp);\n}\n\nint artio_parameter_get_long(artio_fileset *handle, const char * key, int64_t * value) {\n\treturn artio_parameter_get_long_array(handle, key, 1, value);\n}\n\nint artio_parameter_set_long_array(artio_fileset *handle, const char * key,\n\t\tint length, int64_t * value) {\n\treturn artio_parameter_list_insert(handle->parameters, key, length, value,\n\t\t\tARTIO_TYPE_LONG);\n}\n\nint artio_parameter_get_long_array(artio_fileset *handle, const char * key,\n\t\tint length, int64_t * value) {\n\treturn artio_parameter_list_unpack(handle->parameters, key, length,\n\t\t\tvalue, ARTIO_TYPE_LONG);\n}\n\nint artio_parameter_get_long_array_index(artio_fileset *handle, const char * key,\n        int index, int64_t * value) {\n    return artio_parameter_list_unpack_index(handle->parameters, key, index,\n            value, ARTIO_TYPE_LONG);\n}\n\nint artio_parameter_set_string(artio_fileset *handle, const char *key, char *value) {\n\treturn artio_parameter_set_string_array(handle, key, 1, &value);\n}\n\nint artio_parameter_set_string_array(artio_fileset *handle, const char *key,\n\t\tint length, char **value) {\n\tint i;\n\tint len, loc_length;\n\tchar *loc_value;\n\tchar *p;\n\tint ret;\n\n\tfor (i = 0, loc_length = 0; i < length; i++) {\n\t\tlen = strlen(value[i]) + 1;\n\t\tif ( len > ARTIO_MAX_STRING_LENGTH ) {\n\t\t\treturn ARTIO_ERR_STRING_LENGTH;\n\t\t}\n\t\tloc_length += len;\n\t}\n\n\tloc_value = (char *)malloc(loc_length * sizeof(char));\n\tif ( loc_value == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tfor (i = 0, p = loc_value; i < length; i++) {\n\t\tstrcpy(p, value[i]);\n\t\tp += strlen(value[i]) + 1;\n\t}\n\n\tret = artio_parameter_list_insert(handle->parameters, key,\n\t\t\t\tloc_length, loc_value, ARTIO_TYPE_STRING);\n\tfree(loc_value);\n\n\treturn ret;\n}\n\nint artio_parameter_get_string(artio_fileset *handle, const char *key, char *value ) {\n\treturn artio_parameter_get_string_array(handle, key, 1, &value );\n}\n\nint artio_parameter_get_string_array_index( artio_fileset *handle, const char *key,\n\t\tint index, char *value ) {\n\tint count;\n\tchar *p;\n\tparameter *item = artio_parameter_list_search(handle->parameters, key);\n\n\tif ( item != NULL ) {\n\t\tcount = 0;\n\t\tp = item->value;\n\t\twhile ( count < index && p < item->value + item->val_length) {\n\t\t\tp += strlen(p) + 1;\n\t\t\tcount++;\n\t\t}\n\n\t\tif ( count != index ) {\n\t\t\treturn ARTIO_ERR_INVALID_INDEX;\n\t\t}\n\n\t\tstrncpy(value, p, ARTIO_MAX_STRING_LENGTH-1);\n\t\tvalue[ARTIO_MAX_STRING_LENGTH-1] = 0;\n\t} else {\n\t\treturn ARTIO_ERR_PARAM_NOT_FOUND;\n    }\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_parameter_get_string_array(artio_fileset *handle, const char *key,\n\t\tint length, char **value ) {\n\tint i;\n\tchar *p;\n\tint count;\n\n\tparameter *item = artio_parameter_list_search(handle->parameters, key);\n\n\tif (item != NULL) {\n\t\t/* count string items in item->value */\n\t\tcount = 0;\n\t\tp = item->value;\n\t\twhile (p < item->value + item->val_length) {\n\t\t\tp += strlen(p) + 1;\n\t\t\tcount++;\n\t\t}\n\n\t\tif (count != length) {\n\t\t\treturn ARTIO_ERR_PARAM_LENGTH_MISMATCH;\n\t\t}\n\n\t\tfor (i = 0, p = item->value; i < length; i++) {\n\t\t\tstrncpy(value[i], p, ARTIO_MAX_STRING_LENGTH-1);\n\t\t\tvalue[i][ARTIO_MAX_STRING_LENGTH-1] = 0;\n\t\t\tp += strlen(p) + 1;\n\t\t}\n\t} else {\n\t\treturn ARTIO_ERR_PARAM_NOT_FOUND;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_particle.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#include \"artio.h\"\n#include \"artio_internal.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\nint artio_particle_find_file(artio_particle_file *phandle, int start, int end, int64_t sfc);\nartio_particle_file *artio_particle_file_allocate(void);\nvoid artio_particle_file_destroy( artio_particle_file *phandle );\n\n/*\n * Open existing particle files and add to fileset\n */\nint artio_fileset_open_particles(artio_fileset *handle) {\n\tint i;\n\tchar filename[256];\n\tint first_file, last_file;\n\tint mode;\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif ( handle->open_type & ARTIO_OPEN_PARTICLES ||\n\t\t\thandle->open_mode != ARTIO_FILESET_READ ||\n\t\t\thandle->particle != NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\thandle->open_type |= ARTIO_OPEN_PARTICLES;\n\n\tphandle = artio_particle_file_allocate();\n\tif ( phandle == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tif ( artio_parameter_get_int( handle, \"num_particle_files\", &phandle->num_particle_files ) != ARTIO_SUCCESS ||\n\t\t\tartio_parameter_get_int(handle, \"num_particle_species\", &phandle->num_species) != ARTIO_SUCCESS ) {\n\t\treturn ARTIO_ERR_PARTICLE_DATA_NOT_FOUND;\n\t}\n\n\tphandle->num_primary_variables = (int *)malloc(sizeof(int) * phandle->num_species);\n\tif ( phandle->num_primary_variables == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tphandle->num_secondary_variables = (int *)malloc(sizeof(int) * phandle->num_species);\n\tif ( phandle->num_primary_variables == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tphandle->num_particles_per_species =\n\t\t(int *)malloc(phandle->num_species * sizeof(int));\n\tif ( phandle->num_particles_per_species == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tartio_parameter_get_int_array(handle, \"num_primary_variables\",\n\t\t\tphandle->num_species, phandle->num_primary_variables);\n\tartio_parameter_get_int_array(handle, \"num_secondary_variables\",\n\t\t\tphandle->num_species, phandle->num_secondary_variables);\n\n\tphandle->file_sfc_index = (int64_t *)malloc(sizeof(int64_t) * (phandle->num_particle_files + 1));\n\tif ( phandle->file_sfc_index == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tartio_parameter_get_long_array(handle, \"particle_file_sfc_index\",\n\t\t\tphandle->num_particle_files + 1, phandle->file_sfc_index);\n\n\tfirst_file = artio_particle_find_file(phandle, 0,\n\t\t\tphandle->num_particle_files, handle->proc_sfc_begin);\n\tlast_file = artio_particle_find_file(phandle, first_file,\n\t\t\tphandle->num_particle_files, handle->proc_sfc_end);\n\n\t/* allocate file handles */\n\tphandle->ffh = (artio_fh **)malloc(phandle->num_particle_files * sizeof(artio_fh *));\n\tif ( phandle->ffh == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tfor ( i = 0; i < phandle->num_particle_files; i++ ) {\n\t\tphandle->ffh[i] = NULL;\n\t}\n\n\t/* open files on all processes */\n\tfor (i = 0; i < phandle->num_particle_files; i++) {\n\t\tsprintf(filename, \"%s.p%03d\", handle->file_prefix, i);\n\n\t\tmode = ARTIO_MODE_READ;\n\t\tif (i >= first_file && i <= last_file) {\n\t\t\tmode |= ARTIO_MODE_ACCESS;\n\t\t}\n\t\tif (handle->endian_swap) {\n\t\t\tmode |= ARTIO_MODE_ENDIAN_SWAP;\n\t\t}\n\n\t\tphandle->ffh[i] = artio_file_fopen(filename, mode, handle->context);\n\t\tif ( phandle->ffh[i] == NULL ) {\n\t\t\tartio_particle_file_destroy(phandle);\n\t\t\treturn ARTIO_ERR_PARTICLE_FILE_NOT_FOUND;\n\t\t}\n\t}\n\n\thandle->particle = phandle;\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_fileset_add_particles( artio_fileset *handle,\n        int num_particle_files, int allocation_strategy,\n        int num_species, char ** species_labels,\n        int * num_primary_variables,\n        int * num_secondary_variables,\n        char *** primary_variable_labels_per_species,\n        char *** secondary_variable_labels_per_species,\n\t\tint * num_particles_per_species_per_root_tree ) {\n\tint i, k;\n\tint ret;\n\tint64_t l, cur;\n\tint64_t first_file_sfc, last_file_sfc;\n\n\tint first_file, last_file;\n\tchar filename[256];\n\tchar species_label[64];\n\tint mode;\n\tint64_t *local_particles_per_species, *total_particles_per_species;\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif ( handle->open_mode != ARTIO_FILESET_WRITE ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tif ( handle->open_type & ARTIO_OPEN_PARTICLES ||\n\t\t\thandle->particle != NULL ) {\n\t\treturn ARTIO_ERR_DATA_EXISTS;\n\t}\n\thandle->open_type |= ARTIO_OPEN_PARTICLES;\n\n\t/* compute total number of particles per species */\n\tlocal_particles_per_species = (int64_t *)malloc( num_species * sizeof(int64_t));\n\tif ( local_particles_per_species == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\ttotal_particles_per_species = (int64_t *)malloc( num_species * sizeof(int64_t));\n\tif ( total_particles_per_species == NULL ) {\n\t\tfree( local_particles_per_species );\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tfor ( i = 0; i < num_species; i++ ) {\n\t\tlocal_particles_per_species[i] = 0;\n\t}\n\n\tfor ( l = 0; l < handle->proc_sfc_end-handle->proc_sfc_begin+1; l++ ) {\n\t\tfor ( i = 0; i < num_species; i++ ) {\n\t\t\tlocal_particles_per_species[i] +=\n\t\t\t\t\tnum_particles_per_species_per_root_tree[num_species*l+i];\n\t\t}\n\t}\n\n#ifdef ARTIO_MPI\n\tMPI_Allreduce( local_particles_per_species, total_particles_per_species,\n\t\t\tnum_species, MPI_LONG_LONG_INT, MPI_SUM, handle->context->comm );\n#else\n\tfor ( i = 0; i < num_species; i++ ) {\n\t\ttotal_particles_per_species[i] = local_particles_per_species[i];\n\t}\n#endif\n\n\tartio_parameter_set_long_array(handle,\n\t\t\t\"num_particles_per_species\", num_species,\n\t\t\ttotal_particles_per_species );\n\n\tfree( local_particles_per_species );\n\tfree( total_particles_per_species );\n\n\tartio_parameter_set_int(handle, \"num_particle_files\", num_particle_files);\n\tartio_parameter_set_int(handle, \"num_particle_species\", num_species);\n\tartio_parameter_set_string_array(handle, \"particle_species_labels\",\n\t\t\tnum_species, species_labels);\n\tartio_parameter_set_int_array(handle, \"num_primary_variables\", num_species,\n\t\t\tnum_primary_variables);\n\tartio_parameter_set_int_array(handle, \"num_secondary_variables\",\n\t\t\tnum_species, num_secondary_variables);\n\n\tfor(i=0;i<num_species;i++) {\n\t\tsprintf( species_label, \"species_%02u_primary_variable_labels\", i );\n\t\tartio_parameter_set_string_array( handle, species_label,\n\t\t\t\tnum_primary_variables[i], primary_variable_labels_per_species[i] );\n\n\t\tsprintf( species_label, \"species_%02u_secondary_variable_labels\", i );\n\t\tartio_parameter_set_string_array( handle, species_label,\n\t\t\t\tnum_secondary_variables[i], secondary_variable_labels_per_species[i] );\n\t}\n\n\tphandle = artio_particle_file_allocate();\n\tif ( phandle == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tphandle->file_sfc_index = (int64_t *)malloc(sizeof(int64_t) * (num_particle_files + 1));\n\tif ( phandle->file_sfc_index == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tswitch (allocation_strategy) {\n\t\tcase ARTIO_ALLOC_EQUAL_PROC:\n\t\t\tif (num_particle_files > handle->num_procs) {\n\t\t\t\treturn ARTIO_ERR_INVALID_FILE_NUMBER;\n\t\t\t}\n\n\t\t\tfor (i = 0; i < num_particle_files; i++) {\n\t\t\t\tphandle->file_sfc_index[i] = handle->proc_sfc_index[\n\t\t\t\t\t(handle->num_procs*i+num_particle_files-1) / num_particle_files ];\n\t\t\t}\n\t\t\tphandle->file_sfc_index[num_particle_files] = handle->proc_sfc_index[handle->num_procs];\n\t\t\tbreak;\n\t\tcase ARTIO_ALLOC_EQUAL_SFC:\n\t\t\tfor (i = 0; i < num_particle_files; i++) {\n\t\t\t\tphandle->file_sfc_index[i] = (handle->num_root_cells*i+num_particle_files-1) /\n\t\t\t\t\t\t\t\t\t\t\t\t\tnum_particle_files;\n\t\t\t}\n\t\t\tphandle->file_sfc_index[num_particle_files] = handle->num_root_cells;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tartio_particle_file_destroy(phandle);\n\t\t\treturn ARTIO_ERR_INVALID_ALLOC_STRATEGY;\n\t}\n\n\tphandle->num_particle_files = num_particle_files;\n\tphandle->num_species = num_species;\n\n\tphandle->num_primary_variables = (int *)malloc(sizeof(int) * num_species);\n\tif ( phandle->num_primary_variables == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tphandle->num_secondary_variables = (int *)malloc(sizeof(int) * num_species);\n\tif ( phandle->num_secondary_variables == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tphandle->num_particles_per_species =\n\t\t(int *)malloc(phandle->num_species * sizeof(int));\n\tif ( phandle->num_particles_per_species == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tfor (i = 0; i < num_species; i++) {\n\t\tphandle->num_primary_variables[i] = num_primary_variables[i];\n\t\tphandle->num_secondary_variables[i] = num_secondary_variables[i];\n\t}\n\n\t/* allocate space for sfc offset cache */\n\tphandle->cache_sfc_begin = handle->proc_sfc_begin;\n\tphandle->cache_sfc_end = handle->proc_sfc_end;\n\tphandle->sfc_offset_table =\n\t\t(int64_t *)malloc( (size_t)(handle->proc_sfc_end - handle->proc_sfc_begin + 1) * sizeof(int64_t));\n\tif ( phandle->sfc_offset_table == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\t/* allocate file handles */\n\tphandle->ffh = (artio_fh **)malloc(num_particle_files * sizeof(artio_fh *));\n\tif ( phandle->ffh == NULL ) {\n\t\tartio_particle_file_destroy(phandle);\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tfor ( i = 0; i < num_particle_files; i++ ) {\n\t\tphandle->ffh[i] = NULL;\n\t}\n\n\t/* open file handles */\n\tfirst_file = artio_particle_find_file(phandle, 0,\n\t\t\t\t\tnum_particle_files, handle->proc_sfc_begin);\n\tlast_file = artio_particle_find_file(phandle, first_file,\n\t\t\t\t\tnum_particle_files, handle->proc_sfc_end);\n\n\tfor (i = 0; i < num_particle_files; i++) {\n\t\tsprintf(filename, \"%s.p%03d\", handle->file_prefix, i);\n\t\tmode = ARTIO_MODE_WRITE;\n\t\tif (i >= first_file && i <= last_file) {\n\t\t\tmode |= ARTIO_MODE_ACCESS;\n\t\t}\n\n\t\tphandle->ffh[i] = artio_file_fopen(filename, mode, handle->context);\n\t\tif ( phandle->ffh[i] == NULL ) {\n\t\t\tartio_particle_file_destroy(phandle);\n\t\t\treturn ARTIO_ERR_FILE_CREATE;\n\t\t}\n\n\t\t/* write sfc offset header if we contribute to this file */\n\t\tif (i >= first_file && i <= last_file) {\n#ifdef ARTIO_MPI\n\t\t\tif ( phandle->file_sfc_index[i] >= handle->proc_sfc_index[ handle->rank ] &&\n\t\t\t\t\tphandle->file_sfc_index[i] < handle->proc_sfc_index[ handle->rank + 1 ] ) {\n\t\t\t\tcur = (phandle->file_sfc_index[i+1] - phandle->file_sfc_index[i]) * sizeof(int64_t);\n\t\t\t} else {\n\t\t\t\t/* obtain offset from previous process */\n\t\t\t\tMPI_Recv( &cur, 1, MPI_LONG_LONG_INT, handle->rank - 1, i,\n\t\t\t\t\t\thandle->context->comm, MPI_STATUS_IGNORE );\n\t\t\t}\n#else\n\t\t\tcur = (phandle->file_sfc_index[i+1] - phandle->file_sfc_index[i]) * sizeof(int64_t);\n#endif /* ARTIO_MPI */\n\n\t\t\tfirst_file_sfc = MAX( phandle->cache_sfc_begin, phandle->file_sfc_index[i] );\n\t\t\tlast_file_sfc = MIN( phandle->cache_sfc_end, phandle->file_sfc_index[i+1]-1 );\n\n\t\t\tfor (l = first_file_sfc - phandle->cache_sfc_begin;\n\t\t\t\t\tl < last_file_sfc - phandle->cache_sfc_begin + 1; l++) {\n\n\t\t\t\tphandle->sfc_offset_table[l] = cur;\n\t\t\t\tcur += sizeof(int) * num_species;\n\n\t\t\t\tfor (k = 0; k < num_species; k++) {\n\t\t\t\t\tcur += num_particles_per_species_per_root_tree[l*num_species+k]\n\t\t\t\t\t\t\t* (sizeof(int64_t) + sizeof(int) +\n\t\t\t\t\t\t\t\t\tnum_primary_variables[k] * sizeof(double) +\n\t\t\t\t\t\t\t\t\tnum_secondary_variables[k] * sizeof(float));\n\t\t\t\t}\n\t\t\t}\n\n#ifdef ARTIO_MPI\n\t\t\tif ( phandle->file_sfc_index[i+1] > handle->proc_sfc_end+1 ) {\n\t\t\t\tMPI_Send( &cur, 1, MPI_LONG_LONG_INT, handle->rank+1, i, handle->context->comm );\n\t\t\t}\n#endif /* ARTIO_MPI */\n\n\t\t\t/* seek and write our portion of sfc table */\n\t\t\tret = artio_file_fseek(phandle->ffh[i],\n\t\t\t\t\t(first_file_sfc - phandle->file_sfc_index[i]) * sizeof(int64_t), ARTIO_SEEK_SET);\n\t\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\t\tartio_particle_file_destroy(phandle);\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\tret = artio_file_fwrite(phandle->ffh[i],\n\t\t\t\t\t&phandle->sfc_offset_table[first_file_sfc - phandle->cache_sfc_begin],\n\t\t\t\t\tlast_file_sfc - first_file_sfc + 1, ARTIO_TYPE_LONG);\n\t\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\t\tartio_particle_file_destroy(phandle);\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\t}\n\n\tartio_parameter_set_long_array(handle, \"particle_file_sfc_index\",\n\t\t\tphandle->num_particle_files + 1,\n\t\t\tphandle->file_sfc_index);\n\n\thandle->particle = phandle;\n\treturn ARTIO_SUCCESS;\n}\n\nartio_particle_file *artio_particle_file_allocate(void) {\n\tartio_particle_file *phandle =\n\t\t(artio_particle_file *)malloc(sizeof(artio_particle_file));\n\tif ( phandle != NULL ) {\n\t\tphandle->ffh = NULL;\n\t\tphandle->num_particle_files = -1;\n\t\tphandle->file_sfc_index = NULL;\n\t\tphandle->cache_sfc_begin = -1;\n\t\tphandle->cache_sfc_end = -1;\n\t\tphandle->sfc_offset_table = NULL;\n\t\tphandle->num_species = -1;\n\t\tphandle->cur_particle = -1;\n\t\tphandle->cur_sfc = -1;\n\t\tphandle->num_primary_variables = NULL;\n\t\tphandle->num_secondary_variables = NULL;\n\t\tphandle->num_particles_per_species = NULL;\n\t\tphandle->cur_file = -1;\n\t\tphandle->buffer_size = artio_fh_buffer_size;\n\t\tphandle->buffer = malloc(phandle->buffer_size);\n\t\tif ( phandle->buffer == NULL ) {\n\t\t\tfree(phandle);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\treturn phandle;\n}\n\nvoid artio_particle_file_destroy( artio_particle_file *phandle ) {\n\tint i;\n\n\tif ( phandle == NULL ) return;\n\n\tif ( phandle->ffh != NULL ) {\n\t    for (i = 0; i < phandle->num_particle_files; i++) {\n\t\t\tif ( phandle->ffh[i] != NULL ) {\n\t\t        artio_file_fclose(phandle->ffh[i]);\n\t\t\t}\n    \t}\n\t    free(phandle->ffh);\n\t}\n\n    if (phandle->sfc_offset_table != NULL) free(phandle->sfc_offset_table);\n    if (phandle->num_particles_per_species != NULL) free(phandle->num_particles_per_species);\n\tif (phandle->num_primary_variables != NULL) free(phandle->num_primary_variables);\n\tif (phandle->num_secondary_variables != NULL) free(phandle->num_secondary_variables);\n\tif (phandle->file_sfc_index != NULL) free(phandle->file_sfc_index);\n\tif (phandle->buffer != NULL) free(phandle->buffer);\n\n\tfree(phandle);\n}\n\nint artio_fileset_close_particles(artio_fileset *handle) {\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif ( !(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n            handle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\tartio_particle_file_destroy(handle->particle);\n\thandle->particle = NULL;\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_particle_cache_sfc_range(artio_fileset *handle,\n\t\tint64_t start, int64_t end) {\n\tint i;\n\tint ret;\n\tint first_file, last_file;\n\tint64_t min, count, cur;\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif ( start > end || start < handle->proc_sfc_begin ||\n\t\t\tend > handle->proc_sfc_end) {\n\t\treturn ARTIO_ERR_INVALID_SFC_RANGE;\n\t}\n\n\t/* check if we've already cached the range */\n\tif ( start >= phandle->cache_sfc_begin &&\n\t\t\tend <= phandle->cache_sfc_end ) {\n\t\treturn ARTIO_SUCCESS;\n\t}\n\n\tartio_grid_clear_sfc_cache(handle);\n\n\tfirst_file = artio_particle_find_file(phandle, 0,\n\t\t\tphandle->num_particle_files, start);\n\tlast_file = artio_particle_find_file(phandle, first_file,\n\t\t\tphandle->num_particle_files, end);\n\n\tphandle->cache_sfc_begin = start;\n\tphandle->cache_sfc_end = end;\n\tphandle->sfc_offset_table = (int64_t *)malloc(sizeof(int64_t) * (size_t)(end - start + 1));\n\tif ( phandle->sfc_offset_table == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tif ( phandle->cur_file != -1 ) {\n\t\tartio_file_detach_buffer( phandle->ffh[phandle->cur_file]);\n\t\tphandle->cur_file = -1;\n\t}\n\n\tcur = 0;\n\tfor (i = first_file; i <= last_file; i++) {\n\t\tmin = MAX( 0, start - phandle->file_sfc_index[i] );\n\t\tcount = MIN( phandle->file_sfc_index[i+1], end+1 )\n\t\t\t\t- MAX( start, phandle->file_sfc_index[i] );\n\n\t\tartio_file_attach_buffer( phandle->ffh[i],\n\t\t\t\tphandle->buffer, phandle->buffer_size );\n\n\t\tret = artio_file_fseek(phandle->ffh[i],\n\t\t\t\tsizeof(int64_t) * min,\n\t\t\t\tARTIO_SEEK_SET);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\t\tret = artio_file_fread(phandle->ffh[i],\n\t\t\t\t&phandle->sfc_offset_table[cur],\n\t\t\t\tcount, ARTIO_TYPE_LONG);\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\t\tartio_file_detach_buffer( phandle->ffh[i] );\n\t\tcur += count;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_particle_clear_sfc_cache( artio_fileset *handle ) {\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_GRID) ||\n\t\t\thandle->grid == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif ( phandle->sfc_offset_table != NULL ) {\n\t\tfree(phandle->sfc_offset_table);\n\t\tphandle->sfc_offset_table = NULL;\n\t}\n\n\tphandle->cache_sfc_begin = -1;\n\tphandle->cache_sfc_end = -1;\n\n\treturn ARTIO_SUCCESS;\n}\n\n\nint artio_particle_find_file(artio_particle_file *phandle,\n\t\tint start, int end, int64_t sfc) {\n\tint j;\n\n\tif ( start < 0 || start > phandle->num_particle_files ||\n\t\t\tend < 0 || end > phandle->num_particle_files ||\n\t\t\tsfc < phandle->file_sfc_index[start] ||\n            sfc >= phandle->file_sfc_index[end] ) {\n\t\treturn -1;\n\t}\n\n\tif (start == end ||\n\t\t\tsfc == phandle->file_sfc_index[start] ) {\n\t\treturn start;\n\t}\n\n\tif (1 == end - start) {\n\t\tif (sfc < phandle->file_sfc_index[end]) {\n\t\t\treturn start;\n\t\t} else {\n\t\t\treturn end;\n\t\t}\n\t}\n\n\tj = start + (end - start) / 2;\n\tif (sfc > phandle->file_sfc_index[j]) {\n\t\treturn artio_particle_find_file(phandle, j, end, sfc);\n\t} else if (sfc < phandle->file_sfc_index[j]) {\n\t\treturn artio_particle_find_file(phandle, start, j, sfc);\n\t} else {\n\t\treturn j;\n\t}\n}\n\nint artio_particle_seek_to_sfc(artio_fileset *handle, int64_t sfc) {\n\tint64_t offset;\n\tartio_particle_file *phandle;\n\tint file;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif ( !(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif (phandle->cache_sfc_begin == -1 ||\n\t\t\tsfc < phandle->cache_sfc_begin ||\n\t\t\tsfc > phandle->cache_sfc_end) {\n\t\treturn ARTIO_ERR_INVALID_SFC;\n\t}\n\n\tfile = artio_particle_find_file(phandle, 0, phandle->num_particle_files, sfc);\n\tif ( file != phandle->cur_file ) {\n\t\tif ( phandle->cur_file != -1 ) {\n\t\t\tartio_file_detach_buffer( phandle->ffh[phandle->cur_file] );\n\t\t}\n\t\tif ( phandle->buffer_size > 0 ) {\n\t\t\tartio_file_attach_buffer( phandle->ffh[file],\n\t\t\t\t\tphandle->buffer, phandle->buffer_size );\n\t\t}\n\t\tphandle->cur_file = file;\n\t}\n\toffset = phandle->sfc_offset_table[sfc - phandle->cache_sfc_begin];\n\treturn artio_file_fseek(phandle->ffh[phandle->cur_file], offset, ARTIO_SEEK_SET);\n}\n\nint artio_particle_write_root_cell_begin(artio_fileset *handle, int64_t sfc,\n\t\tint * num_particles_per_species) {\n\tint i;\n\tint ret;\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif ( phandle->cur_sfc != -1 ) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tret = artio_particle_seek_to_sfc(handle, sfc);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fwrite(phandle->ffh[phandle->cur_file], num_particles_per_species,\n\t\t\tphandle->num_species, ARTIO_TYPE_INT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tfor (i = 0; i < phandle->num_species; i++) {\n\t\tphandle->num_particles_per_species[i] = num_particles_per_species[i];\n\t}\n\n\tphandle->cur_sfc = sfc;\n\tphandle->cur_species = -1;\n\tphandle->cur_particle = -1;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_particle_write_root_cell_end(artio_fileset *handle) {\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tif ( handle->particle->cur_sfc == -1 ||\n\t\t\thandle->particle->cur_species != -1 ) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\thandle->particle->cur_sfc = -1;\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_particle_write_species_begin(artio_fileset *handle,\n\t\tint species) {\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif (phandle->cur_sfc == -1 || phandle->cur_species != -1 ) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tif ( species < 0 || species >= phandle->num_species) {\n\t\treturn ARTIO_ERR_INVALID_SPECIES;\n\t}\n\n\tphandle->cur_species = species;\n\tphandle->cur_particle = 0;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_particle_write_species_end(artio_fileset *handle) {\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif (phandle->cur_species == -1 ||\n\t\t\tphandle->cur_particle !=\n\t\t\t\tphandle->num_particles_per_species[phandle->cur_species]) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tphandle->cur_species = -1;\n\tphandle->cur_particle = -1;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_particle_write_particle(artio_fileset *handle, int64_t pid, int subspecies,\n\t\tdouble * primary_variables, float *secondary_variables) {\n\n\tint ret;\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_WRITE ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif (phandle->cur_species == -1 ||\n\t\t\tphandle->cur_particle >= phandle->num_particles_per_species[phandle->cur_species]) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tret = artio_file_fwrite(phandle->ffh[phandle->cur_file], &pid, 1, ARTIO_TYPE_LONG);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fwrite(phandle->ffh[phandle->cur_file], &subspecies, 1, ARTIO_TYPE_INT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fwrite(phandle->ffh[phandle->cur_file], primary_variables,\n\t\t\tphandle->num_primary_variables[phandle->cur_species],\n\t\t\tARTIO_TYPE_DOUBLE);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fwrite(phandle->ffh[phandle->cur_file], secondary_variables,\n\t\t\tphandle->num_secondary_variables[phandle->cur_species],\n\t\t\tARTIO_TYPE_FLOAT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tphandle->cur_particle++;\n\treturn ARTIO_SUCCESS;\n}\n\n/*\n *\n */\nint artio_particle_read_root_cell_begin(artio_fileset *handle, int64_t sfc,\n\t\tint * num_particles_per_species) {\n\tint i;\n\tint ret;\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tret = artio_particle_seek_to_sfc(handle, sfc);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fread(phandle->ffh[phandle->cur_file], num_particles_per_species,\n\t\t\tphandle->num_species, ARTIO_TYPE_INT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tfor (i = 0; i < phandle->num_species; i++) {\n\t\tphandle->num_particles_per_species[i] = num_particles_per_species[i];\n\t}\n\n\tphandle->cur_sfc = sfc;\n\tphandle->cur_species = -1;\n\tphandle->cur_particle = 0;\n\treturn ARTIO_SUCCESS;\n}\n\n/* Description  */\nint artio_particle_read_particle(artio_fileset *handle, int64_t * pid, int *subspecies,\n\t\tdouble * primary_variables, float * secondary_variables) {\n\tint ret;\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif (phandle->cur_species == -1 ||\n\t\t\tphandle->cur_particle >= phandle->num_particles_per_species[phandle->cur_species]) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tret = artio_file_fread(phandle->ffh[phandle->cur_file], pid, 1, ARTIO_TYPE_LONG);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fread(phandle->ffh[phandle->cur_file], subspecies, 1, ARTIO_TYPE_INT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fread(phandle->ffh[phandle->cur_file], primary_variables,\n\t\t\tphandle->num_primary_variables[phandle->cur_species],\n\t\t\tARTIO_TYPE_DOUBLE);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tret = artio_file_fread(phandle->ffh[phandle->cur_file], secondary_variables,\n\t\t\tphandle->num_secondary_variables[phandle->cur_species],\n\t\t\tARTIO_TYPE_FLOAT);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tphandle->cur_particle++;\n\treturn ARTIO_SUCCESS;\n}\n\n/*\n * Description        Start reading particle species\n */\nint artio_particle_read_species_begin(artio_fileset *handle, int species) {\n\tint i;\n\tint ret;\n\tint64_t offset = 0;\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif (phandle->cur_sfc == -1) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tif (species < 0 || species >= phandle->num_species) {\n\t\treturn ARTIO_ERR_INVALID_SPECIES;\n\t}\n\n\toffset = phandle->sfc_offset_table[phandle->cur_sfc - phandle->cache_sfc_begin];\n\toffset += sizeof(int32_t) * (phandle->num_species);\n\n\tfor (i = 0; i < species; i++) {\n\t\toffset += ( sizeof(int64_t) + sizeof(int) +\n\t\t\t\t\tphandle->num_primary_variables[i] * sizeof(double) +\n\t\t\t\t\tphandle->num_secondary_variables[i] * sizeof(float) ) *\n\t\t\t\t\t\tphandle->num_particles_per_species[i];\n\t}\n\n\tret = artio_file_fseek(phandle->ffh[phandle->cur_file], offset, ARTIO_SEEK_SET);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\tphandle->cur_species = species;\n    phandle->cur_particle = 0;\n\n\treturn ARTIO_SUCCESS;\n}\n\n/*\n * Description        Do something at the end of each kind of read operation\n */\nint artio_particle_read_species_end(artio_fileset *handle) {\n\tartio_particle_file *phandle;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif (phandle->cur_species == -1) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\tphandle->cur_species = -1;\n\tphandle->cur_particle = 0;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_particle_read_root_cell_end(artio_fileset *handle) {\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tif ( handle->particle->cur_sfc == -1 ) {\n\t\treturn ARTIO_ERR_INVALID_STATE;\n\t}\n\n\thandle->particle->cur_sfc = -1;\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_particle_read_selection(artio_fileset *handle,\n\t\tartio_selection *selection, artio_particle_callback callback,\n\t\tvoid *params ) {\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\treturn artio_particle_read_selection_species( handle,\n\t\tselection, 0, handle->particle->num_species-1,\n\t\tcallback, params );\n}\n\nint artio_particle_read_selection_species( artio_fileset *handle,\n        artio_selection *selection, int start_species, int end_species,\n\t\tartio_particle_callback callback, void *params ) {\n\tint ret;\n\tint64_t start, end;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n    }\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tartio_selection_iterator_reset( selection );\n\twhile ( artio_selection_iterator( selection,\n\t\t\t\thandle->num_root_cells, &start, &end ) == ARTIO_SUCCESS ) {\n\t\tret = artio_particle_read_sfc_range_species( handle,\n\t\t\tstart, end, start_species, end_species, callback, params );\n\t\tif ( ret != ARTIO_SUCCESS ) return ret;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_particle_read_sfc_range(artio_fileset *handle,\n\t\tint64_t sfc1, int64_t sfc2,\n\t\tartio_particle_callback callback,\n\t\tvoid *params ) {\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n            !(handle->open_type & ARTIO_OPEN_PARTICLES) ||\n\t\t\thandle->particle == NULL ) {\n        return ARTIO_ERR_INVALID_FILESET_MODE;\n    }\n\n\treturn artio_particle_read_sfc_range_species( handle,\n\t\tsfc1, sfc2, 0, handle->particle->num_species-1,\n\t\tcallback, params );\n}\n\nint artio_particle_read_sfc_range_species(artio_fileset *handle,\n        int64_t sfc1, int64_t sfc2,\n        int start_species, int end_species,\n        artio_particle_callback callback,\n\t\tvoid *params ) {\n\tint64_t sfc;\n\tint particle, species;\n\tint *num_particles_per_species;\n\tartio_particle_file *phandle;\n\tint64_t pid = 0l;\n\tint subspecies;\n\tdouble * primary_variables = NULL;\n\tfloat * secondary_variables = NULL;\n\tint num_primary, num_secondary;\n\tint ret;\n\n\tif ( handle == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_HANDLE;\n\t}\n\n\tif (handle->open_mode != ARTIO_FILESET_READ ||\n\t\t\t!(handle->open_type & ARTIO_OPEN_PARTICLES) ) {\n\t\treturn ARTIO_ERR_INVALID_FILESET_MODE;\n\t}\n\n\tphandle = handle->particle;\n\n\tif ( start_species < 0 || start_species > end_species ||\n\t\t\tend_species > phandle->num_species-1 ) {\n\t\treturn ARTIO_ERR_INVALID_SPECIES;\n\t}\n\n\tnum_particles_per_species = (int *)malloc(phandle->num_species * sizeof(int));\n\tif ( num_particles_per_species == NULL ) {\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tret = artio_particle_cache_sfc_range(handle, sfc1, sfc2);\n\tif ( ret != ARTIO_SUCCESS ) {\n\t\tfree( num_particles_per_species );\n\t\treturn ret;\n\t}\n\n\tnum_primary = num_secondary = 0;\n\tfor ( species = start_species; species <= end_species; species++ ) {\n\t\tnum_primary = MAX( phandle->num_primary_variables[species], num_primary );\n\t\tnum_secondary = MAX( phandle->num_secondary_variables[species], num_secondary );\n\t}\n\n\tprimary_variables = (double *)malloc(num_primary * sizeof(double));\n\tif ( primary_variables == NULL ) {\n\t\tfree( num_particles_per_species );\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n    }\n\n\tsecondary_variables = (float *)malloc(num_secondary * sizeof(float));\n\tif ( secondary_variables == NULL ) {\n\t\tfree( num_particles_per_species );\n\t\tfree( primary_variables );\n\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t}\n\n\tfor ( sfc = sfc1; sfc <= sfc2; sfc++ ) {\n\t\tret = artio_particle_read_root_cell_begin(handle, sfc,\n\t\t\t\tnum_particles_per_species);\n\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\tfree( num_particles_per_species );\n\t\t\tfree( primary_variables );\n\t\t\tfree( secondary_variables );\n\t\t\treturn ret;\n\t\t}\n\n\t\tfor ( species = start_species; species <= end_species; species++) {\n\t\t\tret = artio_particle_read_species_begin(handle, species);\n\t\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\t\tfree( num_particles_per_species );\n\t\t\t\tfree( primary_variables );\n\t\t\t\tfree( secondary_variables );\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\tfor (particle = 0; particle < num_particles_per_species[species]; particle++) {\n\t\t\t\tret = artio_particle_read_particle(handle,\n\t\t\t\t\t\t&pid,\n\t\t\t\t\t\t&subspecies,\n\t\t\t\t\t\tprimary_variables,\n\t\t\t\t\t\tsecondary_variables);\n\t\t\t\tif ( ret != ARTIO_SUCCESS ) {\n\t\t\t\t\tfree( num_particles_per_species );\n\t\t\t\t\tfree( primary_variables );\n\t\t\t\t\tfree( secondary_variables );\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tcallback(sfc, species, subspecies,\n\t\t\t\t\t\tpid,\n\t\t\t\t\t\tprimary_variables,\n\t\t\t\t\t\tsecondary_variables,\n\t\t\t\t\t\tparams  );\n\t\t\t}\n\t\t\tartio_particle_read_species_end(handle);\n\t\t}\n\t\tartio_particle_read_root_cell_end(handle);\n\t}\n\n\tfree(primary_variables);\n\tfree(secondary_variables);\n\tfree(num_particles_per_species);\n\n\treturn ARTIO_SUCCESS;\n}\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_posix.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#include \"artio.h\"\n#include \"artio_internal.h\"\n\n#ifndef ARTIO_MPI\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\nstruct ARTIO_FH {\n\tFILE *fh;\n\tint mode;\n\tchar *data;\n\tint bfptr;\n\tint bfsize;\n\tint bfend;\n};\n\n#ifdef _WIN32\n#define FOPEN_FLAGS \"b\"\n#define fseek _fseeki64\n#else\n#define FOPEN_FLAGS \"\"\n#endif\n\nartio_context artio_context_global_struct = { 0 };\nconst artio_context *artio_context_global = &artio_context_global_struct;\n\nartio_fh *artio_file_fopen_i( char * filename, int mode, const artio_context *not_used ) {\n\tartio_fh *ffh;\n\t/* check for invalid combination of mode parameter */\n\tif ( ( mode & ARTIO_MODE_READ && mode & ARTIO_MODE_WRITE ) ||\n\t\t\t!( mode & ARTIO_MODE_READ || mode & ARTIO_MODE_WRITE ) ) {\n\t\treturn NULL;\n\t}\n\n\tffh = (artio_fh *)malloc(sizeof(artio_fh));\n\tif ( ffh == NULL ) {\n\t\treturn NULL;\n\t}\n\n\tffh->mode = mode;\n\tffh->bfsize = -1;\n\tffh->bfend = -1;\n\tffh->bfptr = -1;\n\tffh->data = NULL;\n\n\tif ( mode & ARTIO_MODE_ACCESS ) {\n\t\tffh->fh = fopen( filename, ( mode & ARTIO_MODE_WRITE ) ? \"w\"FOPEN_FLAGS : \"r\"FOPEN_FLAGS );\n\t\tif ( ffh->fh == NULL ) {\n\t\t\tfree( ffh );\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\treturn ffh;\n}\n\nint artio_file_attach_buffer_i( artio_fh *handle, void *buf, int buf_size ) {\n\tif ( !(handle->mode & ARTIO_MODE_ACCESS ) ) {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\tif ( handle->data != NULL ) {\n\t\treturn ARTIO_ERR_BUFFER_EXISTS;\n\t}\n\n\thandle->bfsize = buf_size;\n\thandle->bfend = -1;\n\thandle->bfptr = 0;\n\thandle->data = (char *)buf;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_detach_buffer_i( artio_fh *handle ) {\n\tint ret;\n\tret = artio_file_fflush(handle);\n\tif ( ret != ARTIO_SUCCESS ) return ret;\n\n\thandle->data = NULL;\n\thandle->bfsize = -1;\n    handle->bfend = -1;\n    handle->bfptr = -1;\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_fwrite_i(artio_fh *handle, const void *buf, int64_t count, int type ) {\n\tsize_t size;\n\tint64_t remain;\n\tchar *p;\n\tint size32;\n\n\tif ( !(handle->mode & ARTIO_MODE_WRITE) ||\n\t\t\t!(handle->mode & ARTIO_MODE_ACCESS) ) {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\tsize = artio_type_size( type );\n\tif ( size == (size_t)-1 ) {\n\t\treturn ARTIO_ERR_INVALID_DATATYPE;\n\t}\n\n\tif ( count > ARTIO_INT64_MAX / size ) {\n\t\treturn ARTIO_ERR_IO_OVERFLOW;\n\t}\n\n\tremain = count*size;\n\tp = (char *)buf;\n\n\tif ( handle->data == NULL ) {\n\t\t/* force writes to 32-bit sizes */\n\t\twhile ( remain > 0 ) {\n\t\t\tsize32 = MIN( ARTIO_IO_MAX, remain );\n\t\t\tif ( fwrite( p, 1, size32, handle->fh ) != size32 ) {\n\t\t\t\treturn ARTIO_ERR_IO_WRITE;\n\t\t\t}\n\t\t\tremain -= size32;\n\t\t\tp += size32;\n\t\t}\n\t} else if ( remain < handle->bfsize - handle->bfptr ) {\n\t\tmemcpy( handle->data + handle->bfptr, p, (size_t)remain );\n\t\thandle->bfptr += remain;\n\t} else {\n\t\tsize32 = handle->bfsize - handle->bfptr;\n\t\tmemcpy( handle->data + handle->bfptr, p, size32 );\n\t\tif ( fwrite( handle->data, 1, handle->bfsize,\n\t\t\t\thandle->fh ) != handle->bfsize ) {\n\t\t\treturn ARTIO_ERR_IO_WRITE;\n\t\t}\n\t\tp += size32;\n\t\tremain -= size32;\n\n\t\twhile ( remain > handle->bfsize ) {\n\t\t\t/* write directly to file-handle in unbuffered case */\n\t\t\tif ( fwrite( p, 1, handle->bfsize,\n\t\t\t\t\thandle->fh ) != handle->bfsize ) {\n\t\t\t\treturn ARTIO_ERR_IO_WRITE;\n\t\t\t}\n\t\t\tremain -= handle->bfsize;\n\t\t\tp += handle->bfsize;\n\t\t}\n\n\t\tmemcpy( handle->data, p, (size_t)remain);\n\t\thandle->bfptr = remain;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_fflush_i(artio_fh *handle) {\n\tif ( !(handle->mode & ARTIO_MODE_ACCESS) ) {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n    if ( handle->mode & ARTIO_MODE_WRITE ) {\n\t\tif ( handle->bfptr > 0 ) {\n\t\t\tif ( fwrite( handle->data, 1, handle->bfptr,\n\t\t\t\t\thandle->fh ) != handle->bfptr ) {\n\t\t\t\treturn ARTIO_ERR_IO_WRITE;\n\t\t\t}\n\t\t\thandle->bfptr = 0;\n\t\t}\n\t} else if ( handle->mode & ARTIO_MODE_READ ) {\n\t\thandle->bfend = -1;\n\t\thandle->bfptr = 0;\n\t} else {\n        return ARTIO_ERR_INVALID_FILE_MODE;\n    }\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_fread_i(artio_fh *handle, void *buf, int64_t count, int type ) {\n\tsize_t size, avail, remain;\n\tint size32;\n\tchar *p;\n\n\tif ( !(handle->mode & ARTIO_MODE_READ) ) {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\tsize = artio_type_size( type );\n\tif ( size == (size_t)-1 ) {\n\t\treturn ARTIO_ERR_INVALID_DATATYPE;\n\t}\n\n\tif ( count > ARTIO_INT64_MAX / size ) {\n\t\treturn ARTIO_ERR_IO_OVERFLOW;\n\t}\n\n\tremain = size*count;\n\tp = (char *)buf;\n\n\tif ( handle->data == NULL ) {\n\t\twhile ( remain > 0 ) {\n\t\t\tsize32 = MIN( ARTIO_IO_MAX, remain );\n\t\t\tif ( fread( p, 1, size32, handle->fh) != size32 ) {\n\t\t\t\treturn ARTIO_ERR_INSUFFICIENT_DATA;\n\t\t\t}\n\t\t\tremain -= size32;\n\t\t\tp += size32;\n\t\t}\n\t} else {\n\t\tif ( handle->bfend == -1 ) {\n\t\t\t/* load initial data into buffer */\n\t\t\thandle->bfend = fread( handle->data, 1, handle->bfsize, handle->fh );\n\t\t\thandle->bfptr = 0;\n\t\t}\n\n\t\t/* read from buffer */\n\t\twhile ( remain > 0 &&\n\t\t\t\thandle->bfend > 0 &&\n\t\t\t\thandle->bfptr + remain >= handle->bfend ) {\n\t\t\tavail = handle->bfend - handle->bfptr;\n\t\t\tmemcpy( p, handle->data + handle->bfptr, avail );\n\t\t\tp += avail;\n\t\t\tremain -= avail;\n\n\t\t\t/* refill buffer */\n\t\t\thandle->bfend = fread( handle->data, 1, handle->bfsize, handle->fh );\n            handle->bfptr = 0;\n\t\t}\n\n\t\tif ( remain > 0 ) {\n\t\t\tif ( handle->bfend == 0 ) {\n\t\t\t\t/* ran out of data, eof */\n\t\t\t\treturn ARTIO_ERR_INSUFFICIENT_DATA;\n\t\t\t}\n\n\t\t\tmemcpy( p, handle->data + handle->bfptr, (size_t)remain );\n\t\t\thandle->bfptr += (int)remain;\n\t\t}\n\t}\n\n\tif(handle->mode & ARTIO_MODE_ENDIAN_SWAP) {\n\t\tswitch (type) {\n\t\t\tcase ARTIO_TYPE_INT :\n\t\t\t\tartio_int_swap( (int32_t *)buf, count );\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_FLOAT :\n\t\t\t\tartio_float_swap( (float *)buf, count );\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_DOUBLE :\n\t\t\t\tartio_double_swap( (double *)buf, count );\n\t\t\t\tbreak;\n\t\t\tcase ARTIO_TYPE_LONG :\n\t\t\t\tartio_long_swap( (int64_t *)buf, count );\n\t\t\t\tbreak;\n\t\t\tdefault :\n\t\t\t\treturn ARTIO_ERR_INVALID_DATATYPE;\n\t\t}\n\t}\n\n    return ARTIO_SUCCESS;\n}\n\nint artio_file_ftell_i( artio_fh *handle, int64_t *offset ) {\n\tsize_t current = ftell( handle->fh );\n\n\tif ( handle->bfend > 0 ) {\n\t\tcurrent -= handle->bfend;\n\t}\n\tif ( handle->bfptr > 0 ) {\n\t\tcurrent += handle->bfptr;\n\t}\n\t*offset = (int64_t)current;\n\n    return ARTIO_SUCCESS;\n}\n\nint artio_file_fseek_i(artio_fh *handle, int64_t offset, int whence ) {\n\tsize_t current;\n\n\tif ( handle->mode & ARTIO_MODE_ACCESS ) {\n\t\tif ( whence == ARTIO_SEEK_CUR ) {\n\t\t\tif ( offset == 0 ) {\n\t\t\t\treturn ARTIO_SUCCESS;\n\t\t\t} else if ( handle->mode & ARTIO_MODE_READ &&\n\t\t\t\t\thandle->bfend > 0 &&\n\t\t\t\t\thandle->bfptr + offset >= 0 &&\n\t\t\t\t\thandle->bfptr + offset < handle->bfend ) {\n\t\t\t\thandle->bfptr += offset;\n\t\t\t\treturn ARTIO_SUCCESS;\n            } else {\n\t\t\t\t/* modify offset due to offset in buffer */\n\t\t\t\tif ( handle->bfptr > 0 ) {\n\t\t\t\t\tcurrent = offset - handle->bfend + handle->bfptr;\n\t\t\t\t} else {\n\t\t\t\t\tcurrent = offset;\n\t\t\t\t}\n                artio_file_fflush( handle );\n\t\t\t\tfseek( handle->fh, (size_t)current, SEEK_CUR );\n\t\t\t}\n\t\t} else if ( whence == ARTIO_SEEK_SET ) {\n\t\t\tcurrent = ftell( handle->fh );\n\t\t\tif ( handle->mode & ARTIO_MODE_WRITE &&\n\t\t\t\t\tcurrent <= offset &&\n\t\t\t\t\toffset < current + handle->bfsize &&\n\t\t\t\t\thandle->bfptr == offset - current ) {\n\t\t\t\treturn ARTIO_SUCCESS;\n\t\t\t} else if ( handle->mode & ARTIO_MODE_READ &&\n\t\t\t\t\thandle->bfptr > 0 &&\n\t\t\t\t\thandle->bfend > 0 &&\n\t\t\t\t\thandle->bfptr < handle->bfend &&\n\t\t\t\t\toffset >= current - handle->bfend &&\n\t\t\t\t\toffset < current ) {\n\t\t\t\thandle->bfptr = offset - current + handle->bfend;\n\t\t\t} else {\n\t\t\t\tartio_file_fflush( handle );\n\t\t\t\tfseek( handle->fh, (size_t)offset, SEEK_SET );\n\t\t\t}\n\t\t} else if ( whence == ARTIO_SEEK_END ) {\n\t\t\tartio_file_fflush( handle );\n\t\t\tfseek( handle->fh, (size_t)offset, SEEK_END );\n\t\t} else {\n\t\t\t/* unknown whence */\n\t\t\treturn ARTIO_ERR_INVALID_SEEK;\n\t\t}\n\t} else {\n\t\treturn ARTIO_ERR_INVALID_FILE_MODE;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_file_fclose_i(artio_fh *handle) {\n\tif ( handle->mode & ARTIO_MODE_ACCESS ) {\n\t\tartio_file_fflush(handle);\n\t\tfclose(handle->fh);\n\t}\n\tfree(handle);\n\n\treturn ARTIO_SUCCESS;\n}\n\nvoid artio_file_set_endian_swap_tag_i(artio_fh *handle) {\n\thandle->mode |= ARTIO_MODE_ENDIAN_SWAP;\n}\n\n#endif /* ifndef ARTIO_MPI */\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_selector.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#include \"artio.h\"\n#include \"artio_internal.h\"\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <math.h>\n#ifdef _WIN32\ntypedef __int64 int64_t;\ntypedef __int32 int32_t;\n#else\n#include <stdint.h>\n#endif\n\n#define ARTIO_SELECTION_LIST_SIZE\t\t1024\n#define ARTIO_SELECTION_VOLUME_LIMIT\t(1L<<60)\n\nint artio_add_volume_to_selection( artio_fileset *handle, int lcoords[3], int rcoords[3],\n            int64_t sfcs[8], artio_selection *selection );\n\nint artio_selection_iterator( artio_selection *selection,\n\t\t int64_t max_range_size, int64_t *start, int64_t *end ) {\n\n\tif ( selection->cursor < 0 ) {\n\t\tselection->cursor = 0;\n\t}\n\n\tif ( selection->cursor == selection->num_ranges ) {\n\t\tselection->cursor = -1;\n\t\treturn ARTIO_SELECTION_EXHAUSTED;\n\t}\n\n\tif ( selection->subcycle > 0 ) {\n\t\t*start = selection->subcycle+1;\n\t} else {\n\t\t*start = selection->list[2*selection->cursor];\n\t}\n\n\t*end = selection->list[2*selection->cursor+1];\n\tif ( *end - *start > max_range_size ) {\n\t\t*end = *start + max_range_size-1;\n\t\tselection->subcycle = *end;\n\t} else {\n\t\tselection->subcycle = -1;\n\t\tselection->cursor++;\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_selection_iterator_reset( artio_selection *selection ) {\n\tselection->cursor = -1;\n\tselection->subcycle = -1;\n\treturn ARTIO_SUCCESS;\n}\n\nint64_t artio_selection_size( artio_selection *selection ) {\n\tint i;\n\tint64_t count = 0;\n\tfor ( i = 0; i < selection->num_ranges; i++ ) {\n\t\tcount += selection->list[2*i+1] - selection->list[2*i] + 1;\n\t}\n\treturn count;\n}\n\nartio_selection *artio_selection_allocate( artio_fileset *handle ) {\n\tartio_selection *selection = (artio_selection *)malloc(sizeof(artio_selection));\n\tif ( selection != NULL ) {\n\t\tselection->list = (int64_t *)malloc(2*ARTIO_SELECTION_LIST_SIZE*sizeof(int64_t));\n\t\tif ( selection->list == NULL ) {\n\t\t\tfree(selection);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\tselection->subcycle = -1;\n\tselection->cursor = -1;\n\tselection->size = ARTIO_SELECTION_LIST_SIZE;\n\tselection->num_ranges = 0;\n\tselection->fileset = handle;\n\treturn selection;\n}\n\nint artio_selection_destroy( artio_selection *selection ) {\n\tif ( selection == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_SELECTION;\n\t}\n\n\tif ( selection->list != NULL ) {\n\t\tfree( selection->list );\n\t}\n\tfree(selection);\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_selection_add_range( artio_selection *selection,\n\t\tint64_t start, int64_t end ) {\n\tint i, j;\n\tint64_t *new_list;\n\n\tif ( selection == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_SELECTION;\n\t}\n\n\tif ( start < 0 || end >= selection->fileset->num_root_cells || start > end ) {\n\t\treturn ARTIO_ERR_INVALID_SFC_RANGE;\n\t}\n\n\tfor ( i = 0; i < selection->num_ranges; i++ ) {\n\t\tif ( (start >= selection->list[2*i] && start <= selection->list[2*i+1] ) ||\n\t\t\t (end >= selection->list[2*i] && end <= selection->list[2*i+1] ) ) {\n\t\t\treturn ARTIO_ERR_INVALID_STATE;\n\t\t}\n\t}\n\n\t/* locate page */\n\tif ( selection->num_ranges == 0 ) {\n\t\tselection->list[0] = start;\n\t\tselection->list[1] = end;\n\t\tselection->num_ranges = 1;\n\t\treturn ARTIO_SUCCESS;\n\t} else {\n\t\t/* eventually replace with binary search */\n\t\tfor ( i = 0; i < selection->num_ranges; i++ ) {\n\t\t\tif ( end < selection->list[2*i] ) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif ( ( i == 0 && end < selection->list[2*i]-1 ) ||\n\t\t\t\t( i == selection->num_ranges && start > selection->list[2*i-1]+1 ) ||\n\t\t\t\t( end < selection->list[2*i]-1 && start > selection->list[2*i-1]+1 ) ) {\n\t\t\tif ( selection->num_ranges == selection->size ) {\n\t\t\t\tnew_list = (int64_t *)malloc(4*selection->size*sizeof(int64_t));\n\t\t\t\tif ( new_list == NULL ) {\n\t\t\t\t\treturn ARTIO_ERR_MEMORY_ALLOCATION;\n\t\t\t\t}\n\n\t\t\t\tfor ( j = 0; j < i; j++ ) {\n\t\t\t\t\tnew_list[2*j] = selection->list[2*j];\n\t\t\t\t\tnew_list[2*j+1] = selection->list[2*j+1];\n\t\t\t\t}\n\t\t\t\tfor ( ; j < selection->num_ranges; j++ ) {\n\t\t\t\t\tnew_list[2*j+2] = selection->list[2*j];\n\t\t\t\t\tnew_list[2*j+3] = selection->list[2*j+1];\n\t\t\t\t}\n\t\t\t\tselection->size *= 2;\n\t\t\t\tfree( selection->list );\n\t\t\t\tselection->list = new_list;\n\t\t\t} else {\n\t\t\t\tfor ( j = selection->num_ranges-1; j >= i; j-- ) {\n\t\t\t\t\tselection->list[2*j+2] = selection->list[2*j];\n\t\t\t\t\tselection->list[2*j+3] = selection->list[2*j+1];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tselection->list[2*i] = start;\n\t\t\tselection->list[2*i+1] = end;\n\t\t\tselection->num_ranges++;\n\t\t} else {\n\t\t\tif ( end == selection->list[2*i]-1 ) {\n\t\t\t\tselection->list[2*i] = start;\n\t\t\t} else if ( start == selection->list[2*i-1]+1 ) {\n\t\t\t\tselection->list[2*i-1] = end;\n\t\t\t}\n\n\t\t\t/* merge 2 ranges if necessary */\n\t\t\tif ( selection->list[2*i] == selection->list[2*i-1]+1 ) {\n\t\t\t\tselection->list[2*i-1] = selection->list[2*i+1];\n\t\t\t\tselection->num_ranges--;\n\t\t\t\tfor ( ; i < selection->num_ranges; i++ ) {\n\t\t\t\t\tselection->list[2*i] = selection->list[2*i+2];\n\t\t\t\t\tselection->list[2*i+1] = selection->list[2*i+3];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ARTIO_SUCCESS;\n}\n\nint artio_selection_add_root_cell( artio_selection *selection, int coords[3] ) {\n\tint i;\n\tint64_t sfc;\n\n\tif ( selection == NULL ) {\n\t\treturn ARTIO_ERR_INVALID_SELECTION;\n\t}\n\n\tfor ( i = 0; i < 3; i++ ) {\n\t\tif ( coords[i] < 0 || coords[i] >= selection->fileset->num_grid ) {\n\t\t\treturn ARTIO_ERR_INVALID_COORDINATES;\n\t\t}\n\t}\n\n\tsfc = artio_sfc_index( selection->fileset, coords );\n\treturn artio_selection_add_range( selection, sfc, sfc );\n}\n\nvoid artio_selection_print( artio_selection *selection ) {\n\tint i;\n\n\tfor ( i = 0; i < selection->num_ranges; i++ ) {\n\t\tprintf(\"%u: %ld %ld\\n\", i, selection->list[2*i], selection->list[2*i+1] );\n\t}\n}\n\nartio_selection *artio_select_all( artio_fileset *handle ) {\n\tartio_selection *selection;\n\n\tif ( handle == NULL ) {\n\t\treturn NULL;\n\t}\n\n\tselection = artio_selection_allocate(handle);\n\tif ( selection == NULL ) {\n\t\treturn NULL;\n\t}\n\n\tif ( artio_selection_add_range( selection, 0, handle->num_root_cells-1 ) != ARTIO_SUCCESS ) {\n\t\tartio_selection_destroy(selection);\n\t\treturn NULL;\n\t}\n\n\treturn selection;\n}\n\nartio_selection *artio_select_volume( artio_fileset *handle, double lpos[3], double rpos[3] ) {\n\tint i;\n\tint64_t sfc;\n\tint coords[3];\n\tint lcoords[3];\n\tint rcoords[3];\n\tartio_selection *selection;\n\n\tif ( handle == NULL ) {\n\t\treturn NULL;\n\t}\n\n\tfor ( i = 0; i < 3; i++ ) {\n\t\tif ( lpos[i] < 0.0 || lpos[i] >= rpos[i] ) {\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tfor ( i = 0; i < 3; i++ ) {\n\t\tlcoords[i] = (int)lpos[i];\n\t\trcoords[i] = (int)rpos[i];\n\t}\n\n\tselection = artio_selection_allocate( handle );\n\tif ( selection == NULL ) {\n\t\treturn NULL;\n\t}\n\n\tfor ( coords[0] = lcoords[0]; coords[0] <= rcoords[0]; coords[0]++ ) {\n\t\tfor ( coords[1] = lcoords[1]; coords[1] <= rcoords[1]; coords[1]++ ) {\n\t\t\tfor ( coords[2] = lcoords[2]; coords[2] <= rcoords[2]; coords[2]++ ) {\n\t\t\t\tsfc = artio_sfc_index( handle, coords );\n\t\t\t\tif ( artio_selection_add_range( selection, sfc, sfc ) != ARTIO_SUCCESS ) {\n\t\t\t\t\tartio_selection_destroy(selection);\n\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn selection;\n}\n\nartio_selection *artio_select_cube( artio_fileset *handle, double center[3], double size ) {\n\tint i, j, k, dx;\n    int64_t sfc;\n    int coords[3], coords2[3];\n\tartio_selection *selection;\n\n\tif ( handle == NULL ) {\n\t\treturn NULL;\n\t}\n\n\tif ( size <= 0.0 || size > handle->num_grid/2 ) {\n\t\treturn NULL;\n\t}\n\tdx = (int)(center[0] + 0.5*size) - (int)(center[0] - 0.5*size) + 1;\n\n\tfor ( i = 0; i < 3; i++ ) {\n\t\tif ( center[i] < 0.0 || center[i] >= handle->num_grid ) {\n\t\t\treturn NULL;\n\t\t}\n\t\tcoords[i] = (int)(center[i] - 0.5*size + handle->num_grid) % handle->num_grid;\n\t}\n\n\tselection = artio_selection_allocate( handle );\n\tif ( selection == NULL ) {\n\t\treturn NULL;\n\t}\n\n\tfor ( i = coords[0]-dx; i <= coords[0]+dx; i++ ) {\n\t\tcoords2[0] = (i + handle->num_grid) % handle->num_grid;\n\t\tfor ( j = coords[1]-dx; j <= coords[1]+dx; j++ ) {\n\t\t\tcoords2[1] = (j + handle->num_grid) % handle->num_grid;\n\t\t\tfor ( k = coords[2]-dx; k <= coords[2]+dx; k++ ) {\n\t\t\t\tcoords2[2] = (k + handle->num_grid) % handle->num_grid;\n\t\t\t\tsfc = artio_sfc_index( handle, coords2 );\n\t\t\t\tif ( artio_selection_add_range( selection, sfc, sfc ) != ARTIO_SUCCESS ) {\n\t\t\t\t\tartio_selection_destroy(selection);\n\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn selection;\n}\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/artio_sfc.c",
    "content": "/**********************************************************************\n * Copyright (c) 2012-2013, Douglas H. Rudd\n * All rights reserved.\n *\n * This file is part of the artio library.\n *\n * artio is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * artio is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public License for more details.\n *\n * Copies of the GNU Lesser General Public License and the GNU General\n * Public License are available in the file LICENSE, included with this\n * distribution.  If you failed to receive a copy of this file, see\n * <http://www.gnu.org/licenses/>\n **********************************************************************/\n\n#include \"artio.h\"\n#include \"artio_internal.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#define rollLeft(x,y,mask) ((x<<y) | (x>>(nDim-y))) & mask\n#define rollRight(x,y,mask) ((x>>y) | (x<<(nDim-y))) & mask\n\n/*******************************************************\n * morton_index\n ******************************************************/\nint64_t artio_morton_index( artio_fileset *handle, int coords[nDim] )\n/* purpose: interleaves the bits of the nDim integer\n * \tcoordinates, normally called Morton or z-ordering\n *\n * \tUsed by the hilbert curve algorithm\n */\n{\n\tint i, d;\n\tint64_t mortonnumber = 0;\n\tint64_t bitMask = 1L << (handle->nBitsPerDim - 1);\n\n\t/* interleave bits of coordinates */\n\tfor ( i = handle->nBitsPerDim; i > 0; i-- ) {\n\t\tfor ( d = 0; d < nDim; d++ ) {\n\t\t\tmortonnumber |= ( coords[d] & bitMask ) << (((nDim - 1) * i ) - d );\n\t\t}\n\t\tbitMask >>= 1;\n\t}\n\n\treturn mortonnumber;\n}\n\n/*******************************************************\n * hilbert_index\n ******************************************************/\nint64_t artio_hilbert_index( artio_fileset *handle, int coords[nDim] )\n/* purpose: calculates the 1-d space-filling-curve index\n * \tcorresponding to the nDim set of coordinates\n *\n * \tUses the Hilbert curve algorithm given in\n * \tAlternative Algorithm for Hilbert's Space-\n * \tFilling Curve, A.R. Butz, IEEE Trans on Comp.,\n * \tp. 424, 1971\n */\n{\n\tint i, j;\n\tint64_t hilbertnumber;\n\tint64_t singleMask;\n\tint64_t dimMask;\n\tint64_t numberShifts;\n\tint principal;\n\tint64_t o;\n\tint64_t rho;\n\tint64_t w;\n\tint64_t interleaved;\n\n\t/* begin by transposing bits */\n\tinterleaved = artio_morton_index( handle, coords );\n\n\t/* mask out nDim and 1 bit blocks starting\n\t * at highest order bits */\n\tsingleMask = 1L << ((handle->nBitsPerDim - 1) * nDim);\n\n\tdimMask = singleMask;\n\tfor ( i = 1; i < nDim; i++ ) {\n\t\tdimMask |= singleMask << i;\n\t}\n\n\tw = 0;\n\tnumberShifts = 0;\n\thilbertnumber = 0;\n\n\twhile (singleMask) {\n\t\to = (interleaved ^ w) & dimMask;\n\t\to = rollLeft( o, numberShifts, dimMask );\n\n\t\trho = o;\n\t\tfor ( j = 1; j < nDim; j++ ) {\n\t\t\trho ^= (o>>j) & dimMask;\n\t\t}\n\n\t\thilbertnumber |= rho;\n\n\t\t/* break out early (we already have complete number\n\t\t * no need to calculate other numbers) */\n\t\tif ( singleMask == 1 ) {\n\t\t\tbreak;\n\t\t}\n\n\t\t/* calculate principal position */\n\t\tprincipal = 0;\n\t\tfor ( i = 1; i < nDim; i++ ) {\n\t\t\tif ( (hilbertnumber & singleMask) != ((hilbertnumber>>i) & singleMask)) {\n\t\t\t\tprincipal = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t/* complement lowest bit position */\n\t\to ^= singleMask;\n\n\t\t/* force even parity by complementing at principal position if necessary\n\t\t * Note: lowest order bit of hilbertnumber gives you parity of o at this\n\t\t * point due to xor operations of previous steps */\n\t\tif ( !(hilbertnumber & singleMask) ) {\n\t\t\to ^= singleMask << principal;\n\t\t}\n\n\t\t/* rotate o right by numberShifts */\n\t\to = rollRight( o, numberShifts, dimMask );\n\n\t\t/* find next numberShifts */\n\t\tnumberShifts += (nDim - 1) - principal;\n\t\tnumberShifts %= nDim;\n\n\t\tw ^= o;\n\t\tw >>= nDim;\n\n\t\tsingleMask >>= nDim;\n\t\tdimMask >>= nDim;\n\t}\n\n\treturn hilbertnumber;\n}\n\n/*******************************************************\n * hilbert_coords\n ******************************************************/\nvoid artio_hilbert_coords( artio_fileset *handle, int64_t index, int coords[nDim] )\n/* purpose: performs the inverse of sfc_index,\n * \ttaking a 1-d space-filling-curve index\n * \tand transforming it into nDim coordinates\n *\n * returns: the coordinates in coords\n */\n{\n\tint i, j;\n\tint64_t dimMask;\n\tint64_t singleMask;\n\tint64_t sigma;\n\tint64_t sigma_;\n\tint64_t tau;\n\tint64_t tau_;\n\tint num_shifts;\n\tint principal;\n\tint64_t w;\n\tint64_t x = 0;\n\n\tw = 0;\n\tsigma_ = 0;\n\tnum_shifts = 0;\n\n\tsingleMask = 1L << ((handle->nBitsPerDim - 1) * nDim);\n\n\tdimMask = singleMask;\n\tfor ( i = 1; i < nDim; i++ ) {\n\t\tdimMask |= singleMask << i;\n\t}\n\n\tfor ( i = 0; i < handle->nBitsPerDim; i++ ) {\n\t\tsigma = ((index & dimMask) ^ ( (index & dimMask) >> 1 )) & dimMask;\n\t\tsigma_ |= rollRight( sigma, num_shifts, dimMask );\n\n\t\tprincipal = nDim - 1;\n\t\tfor ( j = 1; j < nDim; j++ ) {\n\t\t\tif ( (index & singleMask) != ((index >> j) & singleMask) ) {\n\t\t\t\tprincipal = nDim - j - 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t/* complement nth bit */\n\t\ttau = sigma ^ singleMask;\n\n\t\t/* if even parity, complement in principal bit position */\n\t\tif ( !(index & singleMask) ) {\n\t\t\ttau ^= singleMask << ( nDim - principal - 1 );\n\t\t}\n\n\t\ttau_ = rollRight( tau, num_shifts, dimMask );\n\n\t\tnum_shifts += principal;\n\t\tnum_shifts %= nDim;\n\n\t\tw |= ((w & dimMask) ^ tau_) >> nDim;\n\n\t\tdimMask >>= nDim;\n\t\tsingleMask >>= nDim;\n\t}\n\n\tx = w ^ sigma_;\n\n\t/* undo bit interleaving to get coordinates */\n\tfor ( i = 0; i < nDim; i++ ) {\n\t\tcoords[i] = 0;\n\n\t\tsingleMask = 1L << (nDim*handle->nBitsPerDim - 1 - i);\n\n\t\tfor ( j = 0; j < handle->nBitsPerDim; j++ ) {\n\t\t\tif ( x & singleMask ) {\n\t\t\t\tcoords[i] |= 1 << (handle->nBitsPerDim-j-1);\n\t\t\t}\n\t\t\tsingleMask >>= nDim;\n\t\t}\n\t}\n}\n\nint64_t artio_slab_index( artio_fileset *handle, int coords[nDim], int slab_dim ) {\n\tint64_t num_grid = 1L << handle->nBitsPerDim;\n\tint64_t index;\n\n\tswitch ( slab_dim ) {\n\t\tcase 0:\n\t\t\tindex = num_grid*num_grid*coords[0] + num_grid*coords[1] + coords[2];\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tindex = num_grid*num_grid*coords[1] + num_grid*coords[0] + coords[2];\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tindex = num_grid*num_grid*coords[2] + num_grid*coords[0] + coords[1];\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tindex = -1;\n\t}\n\treturn index;\n}\n\nvoid artio_slab_coords( artio_fileset *handle, int64_t index, int coords[nDim], int slab_dim ) {\n\tint64_t num_grid = 1L << handle->nBitsPerDim;\n\tswitch ( slab_dim ) {\n\t\tcase 0:\n\t\t\tcoords[2] = index % num_grid;\n\t\t\tcoords[1] = ((index - coords[2] )/num_grid) % num_grid;\n\t\t\tcoords[0] = (index - coords[2] - num_grid*coords[1])/(num_grid*num_grid);\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tcoords[2] = index % num_grid;\n\t\t\tcoords[0] = ((index - coords[2] )/num_grid) % num_grid;\n\t\t\tcoords[1] = (index - coords[2] - num_grid*coords[0])/(num_grid*num_grid);\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tcoords[1] = index % num_grid;\n\t\t\tcoords[0] = ((index - coords[1] )/num_grid) % num_grid;\n\t\t\tcoords[2] = (index - coords[1] - num_grid*coords[0])/(num_grid*num_grid);\n\t\t\tbreak;\n\t}\n}\n\nint64_t artio_sfc_index_position( artio_fileset *handle, double position[nDim] ) {\n\tint i;\n\tint coords[nDim];\n\n\tfor ( i = 0; i < nDim; i++ ) {\n\t\tcoords[i] = (int)position[i];\n\t}\n\treturn artio_sfc_index(handle, coords);\n}\n\nint64_t artio_sfc_index( artio_fileset *handle, int coords[nDim] ) {\n\tswitch ( handle->sfc_type ) {\n\t\tcase ARTIO_SFC_SLAB_X: return artio_slab_index(handle, coords, 0);\n\t\tcase ARTIO_SFC_SLAB_Y: return artio_slab_index(handle, coords, 1);\n\t\tcase ARTIO_SFC_SLAB_Z: return artio_slab_index(handle, coords, 2);\n\t\tcase ARTIO_SFC_HILBERT: return artio_hilbert_index( handle, coords );\n\t\tdefault: return -1;\n\t}\n}\n\nvoid artio_sfc_coords( artio_fileset *handle, int64_t index, int coords[nDim] ) {\n\tint i;\n\n\tswitch ( handle->sfc_type ) {\n\t\tcase ARTIO_SFC_SLAB_X:\n\t\t\tartio_slab_coords( handle, index, coords, 0 );\n\t\t\tbreak;\n\t\tcase ARTIO_SFC_SLAB_Y:\n\t\t\tartio_slab_coords( handle, index, coords, 1 );\n\t\t\tbreak;\n\t\tcase ARTIO_SFC_SLAB_Z:\n\t\t\tartio_slab_coords( handle, index, coords, 2 );\n\t\t\tbreak;\n\t\tcase ARTIO_SFC_HILBERT:\n\t\t\tartio_hilbert_coords( handle, index, coords );\n\t\t\tbreak;\n\t\tdefault :\n\t\t\tfor ( i = 0; i < nDim; i++ ) {\n\t\t\t\tcoords[i] = -1;\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/cosmology.c",
    "content": "#include <math.h>\n#include <stdio.h>\n#include <string.h>\n\n\n#ifndef ERROR\n#include <stdio.h>\n#define ERROR(msg) { fprintf(stderr,\"%s\\n\",msg); exit(1); }\n#endif\n\n#ifndef ASSERT\n#include <stdio.h>\n#define ASSERT(exp) { if(!(exp)) { fprintf(stderr,\"Failed assertion %s, line: %d\\n\",#exp,__LINE__); } }\n#endif\n\n#ifndef HEAPALLOC\n#include <stdlib.h>\n#define HEAPALLOC(type,size)\t(type *)malloc((size)*sizeof(type))\n#endif\n\n#ifndef NEWARR\n#include <stdlib.h>\n#define NEWARR(size)   HEAPALLOC(double,size)\n#endif\n\n#ifndef DELETE\n#include <stdlib.h>\n#define DELETE(ptr)    free(ptr)\n#endif\n\n\n#include \"cosmology.h\"\n\n\nstruct CosmologyParametersStruct\n{\n  int set;\n  int ndex;\n  int size;\n  double *la;\n  double *aUni;\n  double *aBox;\n  double *tCode;\n  double *tPhys;\n  double *dPlus;\n  double *qPlus;\n  double aLow;\n  double tCodeOffset;\n\n  double OmegaM;\n  double OmegaD;\n  double OmegaB;\n  double OmegaL;\n  double OmegaK;\n  double OmegaR;\n  double h;\n  double DeltaDC;\n  int flat;\n  double Omh2;\n  double Obh2;\n};\n\nvoid cosmology_clear_table(CosmologyParameters *c);\nvoid cosmology_fill_table(CosmologyParameters *c, double amin, double amax);\nvoid cosmology_fill_table_abox(CosmologyParameters *c, int istart, int n);\n\nCosmologyParameters *cosmology_allocate() {\n\tCosmologyParameters *c = HEAPALLOC(CosmologyParameters,1);\n\tif ( c != NULL ) {\n\t\tmemset(c, 0, sizeof(CosmologyParameters));\n\n\t\tc->ndex = 200;\n\t\tc->aLow = 1.0e-2;\n\t}\n\treturn c;\n}\n\nvoid cosmology_free(CosmologyParameters *c) {\n\tcosmology_clear_table(c);\n\tDELETE(c);\n}\n\nint cosmology_is_set(CosmologyParameters *c)\n{\n  return (c->OmegaM>0.0 && c->OmegaB>0.0 && c->h>0.0);\n}\n\n\nvoid cosmology_fail_on_reset(const char *name, double old_value, double new_value)\n{\n  char str[150];\n  sprintf(str,\"Trying to change %s from %lg to %lg...\\nCosmology has been fixed and cannot be changed.\\n\",name,old_value,new_value);\n  ERROR(str);\n}\n\n\nvoid cosmology_set_OmegaM(CosmologyParameters *c, double v)\n{\n  if(v < 1.0e-3) v = 1.0e-3;\n  if(fabs(c->OmegaM-v) > 1.0e-5)\n    {\n      if(c->set) cosmology_fail_on_reset(\"OmegaM\",c->OmegaM,v);\n      c->OmegaM = v;\n      c->flat = (fabs(c->OmegaM+c->OmegaL-1.0) > 1.0e-5) ? 0 : 1;\n      cosmology_clear_table(c);\n    }\n}\n\n\nvoid cosmology_set_OmegaL(CosmologyParameters *c, double v)\n{\n  if(fabs(c->OmegaL-v) > 1.0e-5)\n    {\n      if(c->set) cosmology_fail_on_reset(\"OmegaL\",c->OmegaL,v);\n      c->OmegaL = v;\n      c->flat = (fabs(c->OmegaM+c->OmegaL-1.0) > 1.0e-5) ? 0 : 1;\n      cosmology_clear_table(c);\n    }\n}\n\n\nvoid cosmology_set_OmegaB(CosmologyParameters *c, double v)\n{\n  if(v < 0.0) v = 0.0;\n  if(fabs(c->OmegaB-v) > 1.0e-5)\n    {\n      if(c->set) cosmology_fail_on_reset(\"OmegaB\",c->OmegaB,v);\n      c->OmegaB = v;\n      cosmology_clear_table(c);\n    }\n}\n\n\nvoid cosmology_set_h(CosmologyParameters *c, double v)\n{\n  if(fabs(c->h-v) > 1.0e-5)\n    {\n      if(c->set) cosmology_fail_on_reset(\"h\",c->h,v);\n      c->h = v;\n      cosmology_clear_table(c);\n    }\n}\n\n\nvoid cosmology_set_DeltaDC(CosmologyParameters *c, double v)\n{\n  if(fabs(c->DeltaDC-v) > 1.0e-3)\n    {\n      if(c->set) cosmology_fail_on_reset(\"DeltaDC\",c->DeltaDC,v);\n      c->DeltaDC = v;\n      cosmology_clear_table(c);\n    }\n}\n\n\nvoid cosmology_init(CosmologyParameters *c)\n{\n  if(c->size == 0) /* reset only if the state is dirty */\n    {\n      if(!cosmology_is_set(c)) ERROR(\"Not all of the required cosmological parameters have been set; the minimum required set is (OmegaM,OmegaB,h).\");\n\n      if(c->OmegaB > c->OmegaM) c->OmegaB = c->OmegaM;\n      c->OmegaD = c->OmegaM - c->OmegaB;\n      if(c->flat)\n\t{\n\t  c->OmegaK = 0.0;\n\t  c->OmegaL = 1.0 - c->OmegaM;\n\t}\n      else\n\t{\n\t  c->OmegaK = 1.0 - (c->OmegaM+c->OmegaL);\n\t}\n      c->OmegaR = 4.166e-5/(c->h*c->h);\n\n      c->Omh2 = c->OmegaM*c->h*c->h;\n      c->Obh2 = c->OmegaB*c->h*c->h;\n\n      cosmology_fill_table(c,c->aLow,1.0);\n\n      c->tCodeOffset = 0.0;  /*  Do need to set it to zero first */\n#ifndef NATIVE_TCODE_NORMALIZATION\n      c->tCodeOffset = 0.0 - tCode(c,inv_aBox(c,1.0));\n#endif\n    }\n}\n\n\nvoid cosmology_set_fixed(CosmologyParameters *c)\n{\n  cosmology_init(c);\n  c->set = 1;\n}\n\n\ndouble cosmology_mu(CosmologyParameters *c, double a)\n{\n  return sqrt(((a*a*c->OmegaL+c->OmegaK)*a+c->OmegaM)*a+c->OmegaR);\n}\n\n\ndouble cosmology_dc_factor(CosmologyParameters *c, double dPlus)\n{\n  double dc = 1.0 + dPlus*c->DeltaDC;\n  return 1.0/pow((dc>0.001)?dc:0.001,1.0/3.0);\n}\n\n\nvoid cosmology_fill_table_integrate(CosmologyParameters *c, double a, double y[], double f[])\n{\n  double mu = cosmology_mu(c, a);\n  double abox = a*cosmology_dc_factor(c, y[2]);\n\n  f[0] = a/(abox*abox*mu);\n  f[1] = a/mu;\n  f[2] = y[3]/(a*mu);\n  f[3] = 1.5*c->OmegaM*y[2]/mu;\n}\n\n#ifdef _WIN32\ndouble asinh(double x){\n    return log(x + sqrt((x * x) + 1.0));\n}\n#endif\n\nvoid cosmology_fill_table_piece(CosmologyParameters *c, int istart, int n)\n{\n  int i, j;\n  double tPhysUnit = (3.0856775813e17/(365.25*86400))/c->h;  /* 1/H0 in Julian years */\n\n  double x, aeq = c->OmegaR/c->OmegaM;\n  double tCodeFac = 1.0/sqrt(aeq);\n  double tPhysFac = tPhysUnit*aeq*sqrt(aeq)/sqrt(c->OmegaM);\n\n  double da, a0, y0[4], y1[4];\n  double f1[4], f2[4], f3[4], f4[4];\n\n  for(i=istart; i<n; i++)\n    {\n      c->aUni[i] = pow(10.0,c->la[i]);\n    }\n\n  /*\n  //  Small a regime, use analytical formulae for matter + radiation model\n  */\n  for(i=istart; c->aUni[i]<(c->aLow+1.0e-9) && i<n; i++)\n    {\n      x = c->aUni[i]/aeq;\n\n      c->tPhys[i] = tPhysFac*2*x*x*(2+sqrt(x+1))/(3*pow(1+sqrt(x+1),2.0));\n      c->dPlus[i] = aeq*(x + 2.0/3.0 + (6*sqrt(1+x)+(2+3*x)*log(x)-2*(2+3*x)*log(1+sqrt(1+x)))/(log(64.0)-9));  /* long last term is the decaying mode generated after equality; it is very small for x > 10, I keep ot just for completeness; */\n      c->qPlus[i] = c->aUni[i]*cosmology_mu(c,c->aUni[i])*(1 + ((2+6*x)/(x*sqrt(1+x))+3*log(x)-6*log(1+sqrt(1+x)))/(log(64)-9)); /* this is a^2*dDPlus/dt/H0 */\n\n      c->aBox[i] = c->aUni[i]*cosmology_dc_factor(c,c->dPlus[i]);\n      c->tCode[i] = 1.0 - tCodeFac*asinh(sqrt(aeq/c->aBox[i]));\n    }\n\n  /*\n  //  Large a regime, solve ODEs\n  */\n  ASSERT(i > 0);\n\n  tCodeFac = 0.5*sqrt(c->OmegaM);\n  tPhysFac = tPhysUnit;\n\n  y1[0] = c->tCode[i-1]/tCodeFac;\n  y1[1] = c->tPhys[i-1]/tPhysFac;\n  y1[2] = c->dPlus[i-1];\n  y1[3] = c->qPlus[i-1];\n\n  for(; i<n; i++)\n    {\n      a0 = c->aUni[i-1];\n      da = c->aUni[i] - a0;\n\n      /*  RK4 integration */\n      for(j=0; j<4; j++) y0[j] = y1[j];\n      cosmology_fill_table_integrate(c, a0,y1,f1);\n\n      for(j=0; j<4; j++) y1[j] = y0[j] + 0.5*da*f1[j];\n      cosmology_fill_table_integrate(c, a0+0.5*da,y1,f2);\n\n      for(j=0; j<4; j++) y1[j] = y0[j] + 0.5*da*f2[j];\n      cosmology_fill_table_integrate(c, a0+0.5*da,y1,f3);\n\n      for(j=0; j<4; j++) y1[j] = y0[j] + da*f3[j];\n      cosmology_fill_table_integrate(c, a0+da,y1,f4);\n\n      for(j=0; j<4; j++) y1[j] = y0[j] + da*(f1[j]+2*f2[j]+2*f3[j]+f4[j])/6.0;\n\n      c->tCode[i] = tCodeFac*y1[0];\n      c->tPhys[i] = tPhysFac*y1[1];\n      c->dPlus[i] = y1[2];\n      c->qPlus[i] = y1[3];\n\n      c->aBox[i] = c->aUni[i]*cosmology_dc_factor(c,c->dPlus[i]);\n    }\n}\n\n\nvoid cosmology_fill_table(CosmologyParameters *c, double amin, double amax)\n{\n  int i, imin, imax, iold;\n  double dla = 1.0/c->ndex;\n  double lamin, lamax;\n  double *old_la = c->la;\n  double *old_aUni = c->aUni;\n  double *old_aBox = c->aBox;\n  double *old_tCode = c->tCode;\n  double *old_tPhys = c->tPhys;\n  double *old_dPlus = c->dPlus;\n  double *old_qPlus = c->qPlus;\n  int old_size = c->size;\n\n  if(amin > c->aLow) amin = c->aLow;\n  lamin = dla*floor(c->ndex*log10(amin));\n  lamax = dla*ceil(c->ndex*log10(amax));\n\n  c->size = 1 + (int)(0.5+c->ndex*(lamax-lamin));\n  ASSERT(fabs(lamax-lamin-dla*(c->size-1)) < 1.0e-14);\n\n  c->la = NEWARR(c->size);     ASSERT(c->la != NULL);\n  c->aUni = NEWARR(c->size);   ASSERT(c->aUni != NULL);\n  c->aBox = NEWARR(c->size);   ASSERT(c->aBox != NULL);\n  c->tCode = NEWARR(c->size);  ASSERT(c->tCode != NULL);\n  c->tPhys = NEWARR(c->size);  ASSERT(c->tPhys != NULL);\n  c->dPlus = NEWARR(c->size);  ASSERT(c->dPlus != NULL);\n  c->qPlus = NEWARR(c->size);  ASSERT(c->qPlus != NULL);\n\n  /*\n  //  New log10(aUni) table\n  */\n  for(i=0; i<c->size; i++)\n    {\n      c->la[i] = lamin + dla*i;\n    }\n\n  if(old_size == 0)\n    {\n      /*\n      //  Filling the table for the first time\n      */\n      cosmology_fill_table_piece(c,0,c->size);\n    }\n  else\n    {\n      /*\n      //  Find if we need to expand the lower end\n      */\n      if(lamin < old_la[0])\n\t{\n\t  imin = (int)(0.5+c->ndex*(old_la[0]-lamin));\n\t  ASSERT(fabs(old_la[0]-lamin-dla*imin) < 1.0e-14);\n\t}\n      else imin = 0;\n\n      /*\n      //  Find if we need to expand the upper end\n      */\n      if(lamax > old_la[old_size-1])\n\t{\n\t  imax = (int)(0.5+c->ndex*(old_la[old_size-1]-lamin));\n\t  ASSERT(fabs(old_la[old_size-1]-lamin-dla*imax) < 1.0e-14);\n\t}\n      else imax = c->size - 1;\n\n      /*\n      //  Re-use the rest\n      */\n      if(lamin > old_la[0])\n\t{\n\t  iold = (int)(0.5+c->ndex*(lamin-old_la[0]));\n\t  ASSERT(fabs(lamin-old_la[0]-dla*iold) < 1.0e-14);\n\t}\n      else iold = 0;\n\n      memcpy(c->aUni+imin,old_aUni+iold,sizeof(double)*(imax-imin+1));\n      memcpy(c->aBox+imin,old_aBox+iold,sizeof(double)*(imax-imin+1));\n      memcpy(c->tCode+imin,old_tCode+iold,sizeof(double)*(imax-imin+1));\n      memcpy(c->tPhys+imin,old_tPhys+iold,sizeof(double)*(imax-imin+1));\n      memcpy(c->dPlus+imin,old_dPlus+iold,sizeof(double)*(imax-imin+1));\n      memcpy(c->qPlus+imin,old_qPlus+iold,sizeof(double)*(imax-imin+1));\n\n      DELETE(old_la);\n      DELETE(old_aUni);\n      DELETE(old_aBox);\n      DELETE(old_tCode);\n      DELETE(old_tPhys);\n      DELETE(old_dPlus);\n      DELETE(old_qPlus);\n\n      /*\n      //  Fill in additional pieces\n      */\n      if(imin > 0) cosmology_fill_table_piece(c,0,imin);\n      if(imax < c->size-1) cosmology_fill_table_piece(c,imax,c->size);\n    }\n}\n\n\nvoid cosmology_clear_table(CosmologyParameters *c)\n{\n  if(c->size > 0)\n    {\n      DELETE(c->la);\n      DELETE(c->aUni);\n      DELETE(c->aBox);\n      DELETE(c->tCode);\n      DELETE(c->tPhys);\n      DELETE(c->dPlus);\n      DELETE(c->qPlus);\n\n      c->size = 0;\n      c->la = NULL;\n      c->aUni = NULL;\n      c->aBox = NULL;\n      c->tCode = NULL;\n      c->tPhys = NULL;\n      c->dPlus = NULL;\n      c->qPlus = NULL;\n    }\n}\n\n\nvoid cosmology_check_range(CosmologyParameters *c, double a)\n{\n  ASSERT((a > 1.0e-9) && (a < 1.0e9));\n\n  if(c->size == 0) cosmology_init(c);\n\n  if(a < c->aUni[0])\n    {\n      cosmology_fill_table(c,a,c->aUni[c->size-1]);\n    }\n\n  if(a > c->aUni[c->size-1])\n    {\n      cosmology_fill_table(c,c->aUni[0],a);\n    }\n}\n\n\nvoid cosmology_set_thread_safe_range(CosmologyParameters *c, double amin, double amax)\n{\n  cosmology_check_range(c, amin);\n  cosmology_check_range(c, amax);\n}\n\n\ndouble cosmology_get_value_from_table(CosmologyParameters *c, double a, double table[])\n{\n  // This is special case code for boundary conditions\n  int idx;\n  double la = log10(a);\n  if (fabs(la - c->la[c->size-1]) < 1.0e-14) {\n    return table[c->size-1];\n  } else if (fabs(la - c->la[0]) < 1.0e-14) {\n    return table[0];\n  }\n\n  idx = (int)(c->ndex*(la-c->la[0]));\n\n  // Note that because we do idx+1 below, we need -1 here.\n  ASSERT(idx>=0 && (idx<c->size-1));\n\n  /*\n  //  Do it as a function of aUni rather than la to ensure exact inversion\n  */\n  return table[idx] + (table[idx+1]-table[idx])/(c->aUni[idx+1]-c->aUni[idx])*(a-c->aUni[idx]);\n}\n\n\nint cosmology_find_index(CosmologyParameters *c, double v, double table[])\n{\n  int ic, il = 0;\n  int ih = c->size - 1;\n\n  if(v < table[0])\n    {\n      return -1;\n    }\n  if(v > table[c->size-1])\n    {\n      return c->size + 1;\n    }\n\n  while((ih-il) > 1)\n    {\n      ic = (il+ih)/2;\n      if(v > table[ic]) /* special, not fully optimal form to avoid checking that il < c->size-1 */\n\til = ic;\n      else\n\tih = ic;\n    }\n\n  ASSERT(il+1 < c->size);\n\n  return il;\n}\n\n\n/*\n//  Direct and inverse functions\n*/\n#define DEFINE_FUN(name,offset)\t\t\t\\\ndouble name(CosmologyParameters *c, double a) \\\n{ \\\n  cosmology_check_range(c,a); \\\n  return cosmology_get_value_from_table(c,a,c->name) + offset; \\\n} \\\ndouble inv_##name(CosmologyParameters *c, double v) \\\n{ \\\n  int idx; \\\n  double *table; \\\n  if(c->size == 0) cosmology_init(c); \\\n  v -= offset; \\\n  table = c->name; \\\n  idx = cosmology_find_index(c,v,table); \\\n  while(idx < 0) \\\n    { \\\n      cosmology_check_range(c,0.5*c->aUni[0]); \\\n      table = c->name; \\\n      idx = cosmology_find_index(c,v,table); \\\n    } \\\n  while(idx > c->size) \\\n    { \\\n      cosmology_check_range(c,2.0*c->aUni[c->size-1]); \\\n      table = c->name; \\\n      idx = cosmology_find_index(c,v,table); \\\n    } \\\n  return c->aUni[idx] + (c->aUni[idx+1]-c->aUni[idx])/(table[idx+1]-table[idx])*(v-table[idx]); \\\n}\n\nDEFINE_FUN(aBox,0.0);\nDEFINE_FUN(tCode,c->tCodeOffset);\nDEFINE_FUN(tPhys,0.0);\nDEFINE_FUN(dPlus,0.0);\nDEFINE_FUN(qPlus,0.0);\n\n#undef DEFINE_FUN\n"
  },
  {
    "path": "yt/frontends/artio/artio_headers/cosmology.h",
    "content": "#ifndef __COSMOLOGY_H__\n#define __COSMOLOGY_H__\n\ntypedef struct CosmologyParametersStruct CosmologyParameters;\n\n#define COSMOLOGY_DECLARE_PRIMARY_PARAMETER(name) \\\nvoid cosmology_set_##name(CosmologyParameters *c, double value)\n\n#define cosmology_set(c,name,value)\t\\\ncosmology_set_##name(c,value)\n\nCOSMOLOGY_DECLARE_PRIMARY_PARAMETER(OmegaM);\nCOSMOLOGY_DECLARE_PRIMARY_PARAMETER(OmegaB);\nCOSMOLOGY_DECLARE_PRIMARY_PARAMETER(OmegaL);\nCOSMOLOGY_DECLARE_PRIMARY_PARAMETER(h);\nCOSMOLOGY_DECLARE_PRIMARY_PARAMETER(DeltaDC);\n\n#undef COSMOLOGY_DECLARE_PRIMARY_PARAMETER\n\nCosmologyParameters *cosmology_allocate();\nvoid cosmology_free(CosmologyParameters *c);\n\n/*\n//  Check that all required cosmological parameters have been set.\n//  The minimum set is OmegaM, OmegaB, and h. By default, zero OmegaL,\n//  OmegaK, and the DC mode are assumed.\n*/\nint cosmology_is_set(CosmologyParameters *c);\n\n\n/*\n//  Freeze the cosmology and forbid any further changes to it.\n//  In codes that include user-customizable segments (like plugins),\n//  this function van be used for insuring that a user does not\n//  change the cosmology in mid-run.\n*/\nvoid cosmology_set_fixed(CosmologyParameters *c);\n\n\n/*\n//  Manual initialization. This does not need to be called,\n//  the initialization is done automatically on the first call\n//  to a relevant function.\n*/\nvoid cosmology_init(CosmologyParameters *c);\n\n\n/*\n//  Set the range of global scale factors for thread-safe\n//  calls to direct functions until the argument leaves the range.\n*/\nvoid cosmology_set_thread_safe_range(CosmologyParameters *c, double amin, double amax);\n\n/*\n//  Direct functions take the global cosmological scale factor as the argument.\n//  These functionsare are thread-safe if called with the argument in the\n//  range set by a prior call to cosmology_set_thread_safe_range(...).\n//  Calling them with the argument outside that range is ok, but breaks\n//  thread-safety assurance.\n*/\n\n#define DEFINE_FUN(name)         \\\ndouble name(CosmologyParameters *c, double a); \\\ndouble inv_##name(CosmologyParameters *c, double v);\n\nDEFINE_FUN(aBox);\nDEFINE_FUN(tCode);\nDEFINE_FUN(tPhys);\nDEFINE_FUN(dPlus);\nDEFINE_FUN(qPlus); /* Q+ = a^2 dD+/(H0 dt) */\n\n#undef DEFINE_FUN\n\n/*\n//  Conversion macros\n*/\n#define  abox_from_auni(c,a)   aBox(c,a)\n#define tcode_from_auni(c,a)  tCode(c,a)\n#define tphys_from_auni(c,a)  tPhys(c,a)\n#define dplus_from_auni(c,a)  dPlus(c,a)\n\n#define auni_from_abox(c,v)   inv_aBox(c,v)\n#define auni_from_tcode(c,v)  inv_tCode(c,v)\n#define auni_from_tphys(c,v)  inv_tPhys(c,v)\n#define auni_from_dplus(c,v)  inv_dPlus(c,v)\n\n#define abox_from_tcode(c,tcode)   aBox(c,inv_tCode(c,tcode))\n#define tcode_from_abox(c,abox)    tCode(c,inv_aBox(c,abox))\n\n#define tphys_from_abox(c,abox)    tPhys(c,inv_aBox(c,abox))\n#define tphys_from_tcode(c,tcode)  tPhys(c,inv_tCode(c,tcode))\n#define dplus_from_tcode(c,tcode)  dPlus(c,inv_tCode(c,tcode))\n\n/*\n//  Hubble parameter in km/s/Mpc; defined as macro so that it can be\n//  undefined if needed to avoid the name clash.\n*/\ndouble cosmology_mu(CosmologyParameters *c, double a);\n#define Hubble(c,a) (100*c->h*cosmology_mu(c,a)/(a*a))\n\n#endif /* __COSMOLOGY_H__ */\n"
  },
  {
    "path": "yt/frontends/artio/data_structures.py",
    "content": "import os\nimport weakref\nfrom collections import defaultdict\n\nimport numpy as np\n\nfrom yt.data_objects.field_data import YTFieldData\nfrom yt.data_objects.index_subobjects.octree_subset import OctreeSubset\nfrom yt.data_objects.static_output import Dataset\nfrom yt.data_objects.unions import ParticleUnion\nfrom yt.frontends.artio import _artio_caller\nfrom yt.frontends.artio._artio_caller import (\n    ARTIOSFCRangeHandler,\n    artio_fileset,\n    artio_is_valid,\n)\nfrom yt.frontends.artio.fields import ARTIOFieldInfo\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry import particle_deposit as particle_deposit\nfrom yt.geometry.geometry_handler import Index, YTDataChunk\nfrom yt.utilities.exceptions import YTParticleDepositionNotImplemented\n\n\nclass ARTIOOctreeSubset(OctreeSubset):\n    _domain_offset = 0\n    domain_id = -1\n    _con_args = (\"base_region\", \"sfc_start\", \"sfc_end\", \"oct_handler\", \"ds\")\n    _type_name = \"octree_subset\"\n    _num_zones = 2\n\n    def __init__(self, base_region, sfc_start, sfc_end, oct_handler, ds):\n        self.field_data = YTFieldData()\n        self.field_parameters = {}\n        self.sfc_start = sfc_start\n        self.sfc_end = sfc_end\n        self._oct_handler = oct_handler\n        self.ds = ds\n        self._last_mask = None\n        self._last_selector_id = None\n        self._current_particle_type = \"all\"\n        self._current_fluid_type = self.ds.default_fluid_type\n        self.base_region = base_region\n        self.base_selector = base_region.selector\n\n    @property\n    def oct_handler(self):\n        return self._oct_handler\n\n    @property\n    def min_ind(self):\n        return self.sfc_start\n\n    @property\n    def max_ind(self):\n        return self.sfc_end\n\n    def fill(self, fields, selector):\n        if len(fields) == 0:\n            return []\n        handle = self.oct_handler.artio_handle\n        field_indices = [\n            handle.parameters[\"grid_variable_labels\"].index(f) for (ft, f) in fields\n        ]\n        cell_count = selector.count_oct_cells(self.oct_handler, self.domain_id)\n        self.data_size = cell_count\n        levels, cell_inds, file_inds = self.oct_handler.file_index_octs(\n            selector, self.domain_id, cell_count\n        )\n        domain_counts = self.oct_handler.domain_count(selector)\n        tr = [np.zeros(cell_count, dtype=\"float64\") for field in fields]\n        self.oct_handler.fill_sfc(\n            levels, cell_inds, file_inds, domain_counts, field_indices, tr\n        )\n        tr = dict(zip(fields, tr, strict=True))\n        return tr\n\n    def fill_particles(self, fields):\n        if len(fields) == 0:\n            return {}\n        ptype_indices = self.ds.particle_types\n        art_fields = [(ptype_indices.index(ptype), fname) for ptype, fname in fields]\n        species_data = self.oct_handler.fill_sfc_particles(art_fields)\n        tr = defaultdict(dict)\n        # Now we need to sum things up and then fill\n        for s, f in fields:\n            count = 0\n            dt = \"float64\"  # default\n            i = ptype_indices.index(s)\n            # No vector fields in ARTIO\n            count += species_data[i, f].size\n            dt = species_data[i, f].dtype\n            tr[s][f] = np.zeros(count, dtype=dt)\n            cp = 0\n            v = species_data.pop((i, f))\n            tr[s][f][cp : cp + v.size] = v\n            cp += v.size\n        return tr\n\n\n# We create something of a fake octree here.  This is primarily to enable us to\n# reuse code for things like __getitem__ and the like.  We will also create a\n# new oct_handler type that is functionally equivalent, except that it will\n# only manage the root mesh.\nclass ARTIORootMeshSubset(ARTIOOctreeSubset):\n    _num_zones = 1\n    _type_name = \"sfc_subset\"\n    _selector_module = _artio_caller\n    domain_id = -1\n\n    def fill(self, fields, selector):\n        # We know how big these will be.\n        if len(fields) == 0:\n            return []\n        handle = self.ds._handle\n        field_indices = [\n            handle.parameters[\"grid_variable_labels\"].index(f) for (ft, f) in fields\n        ]\n        tr = self.oct_handler.fill_sfc(selector, field_indices)\n        self.data_size = tr[0].size\n        tr = dict(zip(fields, tr, strict=True))\n        return tr\n\n    def deposit(self, positions, fields=None, method=None, kernel_name=\"cubic\"):\n        # Here we perform our particle deposition.\n        if fields is None:\n            fields = []\n        cls = getattr(particle_deposit, f\"deposit_{method}\", None)\n        if cls is None:\n            raise YTParticleDepositionNotImplemented(method)\n        nz = self.nz\n        nvals = (nz, nz, nz, self.ires.size)\n        # We allocate number of zones, not number of octs\n        op = cls(nvals, kernel_name)\n        op.initialize()\n        mylog.debug(\n            \"Depositing %s (%s^3) particles into %s Root Mesh\",\n            positions.shape[0],\n            positions.shape[0] ** 0.3333333,\n            nvals[-1],\n        )\n        pos = np.array(positions, dtype=\"float64\")\n        f64 = [np.array(f, dtype=\"float64\") for f in fields]\n        self.oct_handler.deposit(op, self.base_selector, pos, f64)\n        vals = op.finalize()\n        if vals is None:\n            return\n        return np.asfortranarray(vals)\n\n\nclass ARTIOIndex(Index):\n    def __init__(self, ds, dataset_type=\"artio\"):\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        # for now, the index file is the dataset!\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n\n        self.max_level = ds.max_level\n        self.range_handlers = {}\n        self.float_type = np.float64\n        super().__init__(ds, dataset_type)\n\n    @property\n    def max_range(self):\n        return self.dataset.max_range\n\n    def _setup_geometry(self):\n        mylog.debug(\"Initializing Geometry Handler empty for now.\")\n\n    def get_smallest_dx(self):\n        \"\"\"\n        Returns (in code units) the smallest cell size in the simulation.\n        \"\"\"\n        return (\n            self.dataset.domain_width\n            / (self.dataset.domain_dimensions * 2 ** (self.max_level))\n        ).min()\n\n    def _get_particle_type_counts(self):\n        # this could be done in the artio C interface without creating temporary\n        # arrays but I don't want to touch that code\n        # if a future brave soul wants to try, take a look at\n        # `read_sfc_particles` in _artio_caller.pyx\n        result = {}\n        ad = self.ds.all_data()\n        for ptype in self.ds.particle_types_raw:\n            result[ptype] = ad[ptype, \"PID\"].size\n        return result\n\n    def convert(self, unit):\n        return self.dataset.conversion_factors[unit]\n\n    def find_max(self, field, finest_levels=3):\n        \"\"\"\n        Returns (value, center) of location of maximum for a given field.\n        \"\"\"\n        if (field, finest_levels) in self._max_locations:\n            return self._max_locations[field, finest_levels]\n        mv, pos = self.find_max_cell_location(field, finest_levels)\n        self._max_locations[field, finest_levels] = (mv, pos)\n        return mv, pos\n\n    def find_max_cell_location(self, field, finest_levels=3):\n        source = self.all_data()\n        if finest_levels is not False:\n            source.min_level = self.max_level - finest_levels\n        mylog.debug(\"Searching for maximum value of %s\", field)\n        max_val, mx, my, mz = source.quantities[\"MaxLocation\"](field)\n        mylog.info(\"Max Value is %0.5e at %0.16f %0.16f %0.16f\", max_val, mx, my, mz)\n        self.ds.parameters[f\"Max{field}Value\"] = max_val\n        self.ds.parameters[f\"Max{field}Pos\"] = f\"{mx, my, mz}\"\n        return max_val, np.array((mx, my, mz), dtype=\"float64\")\n\n    def _detect_output_fields(self):\n        self.fluid_field_list = self._detect_fluid_fields()\n        self.particle_field_list = self._detect_particle_fields()\n        self.field_list = self.fluid_field_list + self.particle_field_list\n        mylog.debug(\"Detected fields: %s\", (self.field_list,))\n\n    def _detect_fluid_fields(self):\n        return [(\"artio\", f) for f in self.ds.artio_parameters[\"grid_variable_labels\"]]\n\n    def _detect_particle_fields(self):\n        fields = set()\n        for i, ptype in enumerate(self.ds.particle_types):\n            if ptype == \"all\":\n                break  # This will always be after all intrinsic\n            for fname in self.ds.particle_variables[i]:\n                fields.add((ptype, fname))\n        return list(fields)\n\n    def _identify_base_chunk(self, dobj):\n        if getattr(dobj, \"_chunk_info\", None) is None:\n            try:\n                all_data = all(dobj.left_edge == self.ds.domain_left_edge) and all(\n                    dobj.right_edge == self.ds.domain_right_edge\n                )\n            except Exception:\n                all_data = False\n            base_region = getattr(dobj, \"base_region\", dobj)\n            sfc_start = getattr(dobj, \"sfc_start\", None)\n            sfc_end = getattr(dobj, \"sfc_end\", None)\n            nz = getattr(dobj, \"_num_zones\", 0)\n            if all_data:\n                mylog.debug(\"Selecting entire artio domain\")\n                list_sfc_ranges = self.ds._handle.root_sfc_ranges_all(\n                    max_range_size=self.max_range\n                )\n            elif sfc_start is not None and sfc_end is not None:\n                mylog.debug(\"Restricting to %s .. %s\", sfc_start, sfc_end)\n                list_sfc_ranges = [(sfc_start, sfc_end)]\n            else:\n                mylog.debug(\"Running selector on artio base grid\")\n                list_sfc_ranges = self.ds._handle.root_sfc_ranges(\n                    dobj.selector, max_range_size=self.max_range\n                )\n            ci = []\n            # v = np.array(list_sfc_ranges)\n            # list_sfc_ranges = [ (v.min(), v.max()) ]\n            for start, end in list_sfc_ranges:\n                if (start, end) in self.range_handlers.keys():\n                    range_handler = self.range_handlers[start, end]\n                else:\n                    range_handler = ARTIOSFCRangeHandler(\n                        self.ds.domain_dimensions,\n                        self.ds.domain_left_edge,\n                        self.ds.domain_right_edge,\n                        self.ds._handle,\n                        start,\n                        end,\n                    )\n                    range_handler.construct_mesh()\n                    self.range_handlers[start, end] = range_handler\n                if nz != 2:\n                    ci.append(\n                        ARTIORootMeshSubset(\n                            base_region,\n                            start,\n                            end,\n                            range_handler.root_mesh_handler,\n                            self.ds,\n                        )\n                    )\n                if nz != 1 and range_handler.total_octs > 0:\n                    ci.append(\n                        ARTIOOctreeSubset(\n                            base_region,\n                            start,\n                            end,\n                            range_handler.octree_handler,\n                            self.ds,\n                        )\n                    )\n            dobj._chunk_info = ci\n            if len(list_sfc_ranges) > 1:\n                mylog.info(\"Created %d chunks for ARTIO\", len(list_sfc_ranges))\n        dobj._current_chunk = list(self._chunk_all(dobj))[0]\n\n    def _data_size(self, dobj, dobjs):\n        size = 0\n        for d in dobjs:\n            size += d.data_size\n        return size\n\n    def _chunk_all(self, dobj):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        yield YTDataChunk(dobj, \"all\", oobjs, None, cache=True)\n\n    def _chunk_spatial(self, dobj, ngz, preload_fields=None):\n        if ngz > 0:\n            raise NotImplementedError\n        sobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for og in sobjs:\n            if ngz > 0:\n                g = og.retrieve_ghost_zones(ngz, [], smoothed=True)\n            else:\n                g = og\n            yield YTDataChunk(dobj, \"spatial\", [g], None, cache=True)\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):\n        # _current_chunk is made from identify_base_chunk\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for chunk in oobjs:\n            yield YTDataChunk(dobj, \"io\", [chunk], None, cache=cache)\n\n    def _read_fluid_fields(self, fields, dobj, chunk=None):\n        if len(fields) == 0:\n            return {}, []\n        if chunk is None:\n            self._identify_base_chunk(dobj)\n        fields_to_return = {}\n        fields_to_read, fields_to_generate = self._split_fields(fields)\n        if len(fields_to_read) == 0:\n            return {}, fields_to_generate\n        fields_to_return = self.io._read_fluid_selection(\n            self._chunk_io(dobj), dobj.selector, fields_to_read\n        )\n        return fields_to_return, fields_to_generate\n\n    def _icoords_to_fcoords(\n        self,\n        icoords: np.ndarray,\n        ires: np.ndarray,\n        axes: tuple[int, ...] | None = None,\n    ) -> tuple[np.ndarray, np.ndarray]:\n        \"\"\"\n        Accepts icoords and ires and returns appropriate fcoords and fwidth.\n        Mostly useful for cases where we have irregularly spaced or structured\n        grids.\n        \"\"\"\n        dds = self.ds.domain_width[axes,] / (\n            self.ds.domain_dimensions[axes,] * self.ds.refine_by ** ires[:, None]\n        )\n        pos = (0.5 + icoords) * dds + self.ds.domain_left_edge[axes,]\n        return pos, dds\n\n\nclass ARTIODataset(Dataset):\n    _handle = None\n    _index_class = ARTIOIndex\n    _field_info_class = ARTIOFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"artio\",\n        storage_filename=None,\n        max_range=1024,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        if self._handle is not None:\n            return\n        self.max_range = max_range\n        self.fluid_types += (\"artio\",)\n        self._filename = filename\n        self._fileset_prefix = filename[:-4]\n        self._handle = artio_fileset(bytes(self._fileset_prefix, \"utf-8\"))\n        self.artio_parameters = self._handle.parameters\n        # Here we want to initiate a traceback, if the reader is not built.\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        self.storage_filename = storage_filename\n\n    def _set_code_unit_attributes(self):\n        setdefaultattr(self, \"mass_unit\", self.quan(self.parameters[\"unit_m\"], \"g\"))\n        setdefaultattr(self, \"length_unit\", self.quan(self.parameters[\"unit_l\"], \"cm\"))\n        setdefaultattr(self, \"time_unit\", self.quan(self.parameters[\"unit_t\"], \"s\"))\n        setdefaultattr(self, \"velocity_unit\", self.length_unit / self.time_unit)\n\n    def _parse_parameter_file(self):\n        # hard-coded -- not provided by headers\n        self.dimensionality = 3\n        self.refine_by = 2\n        self.parameters[\"HydroMethod\"] = \"artio\"\n        self.parameters[\"Time\"] = 1.0  # default unit is 1...\n\n        # read header\n\n        self.num_grid = self._handle.num_grid\n        self.domain_dimensions = np.ones(3, dtype=\"int32\") * self.num_grid\n        self.domain_left_edge = np.zeros(3, dtype=\"float64\")\n        self.domain_right_edge = np.ones(3, dtype=\"float64\") * self.num_grid\n\n        # TODO: detect if grid exists\n        self.min_level = 0  # ART has min_level=0\n        self.max_level = self.artio_parameters[\"grid_max_level\"][0]\n\n        # TODO: detect if particles exist\n        if self._handle.has_particles:\n            self.num_species = self.artio_parameters[\"num_particle_species\"][0]\n            self.particle_variables = [\n                [\"PID\", \"SPECIES\"] for i in range(self.num_species)\n            ]\n\n            # If multiple N-BODY species exist, they all have the same name,\n            # which can lead to conflict if not renamed\n            # A particle union will be created later to hold all N-BODY\n            # particles and will take the name \"N-BODY\"\n            labels = self.artio_parameters[\"particle_species_labels\"]\n            if labels.count(\"N-BODY\") > 1:\n                for species, label in enumerate(labels):\n                    if label == \"N-BODY\":\n                        labels[species] = f\"N-BODY_{species}\"\n\n            self.particle_types_raw = self.artio_parameters[\"particle_species_labels\"]\n            self.particle_types = tuple(self.particle_types_raw)\n\n            for species in range(self.num_species):\n                # Mass would be best as a derived field,\n                # but wouldn't detect under 'all'\n                label = self.artio_parameters[\"particle_species_labels\"][species]\n                if \"N-BODY\" in label:\n                    self.particle_variables[species].append(\"MASS\")\n\n                if self.artio_parameters[\"num_primary_variables\"][species] > 0:\n                    self.particle_variables[species].extend(\n                        self.artio_parameters[\n                            f\"species_{species:02}_primary_variable_labels\"\n                        ]\n                    )\n                if self.artio_parameters[\"num_secondary_variables\"][species] > 0:\n                    self.particle_variables[species].extend(\n                        self.artio_parameters[\n                            f\"species_{species:02}_secondary_variable_labels\"\n                        ]\n                    )\n\n        else:\n            self.num_species = 0\n            self.particle_variables = []\n            self.particle_types = ()\n        self.particle_types_raw = self.particle_types\n\n        self.current_time = self.quan(\n            self._handle.tphys_from_tcode(self.artio_parameters[\"tl\"][0]), \"yr\"\n        )\n\n        # detect cosmology\n        if \"abox\" in self.artio_parameters:\n            self.cosmological_simulation = True\n\n            abox = self.artio_parameters[\"abox\"][0]\n            self.omega_lambda = self.artio_parameters[\"OmegaL\"][0]\n            self.omega_matter = self.artio_parameters[\"OmegaM\"][0]\n            self.hubble_constant = self.artio_parameters[\"hubble\"][0]\n            self.current_redshift = 1.0 / self.artio_parameters[\"auni\"][0] - 1.0\n            self.current_redshift_box = 1.0 / abox - 1.0\n\n            self.parameters[\"initial_redshift\"] = (\n                1.0 / self.artio_parameters[\"auni_init\"][0] - 1.0\n            )\n            self.parameters[\"CosmologyInitialRedshift\"] = self.parameters[\n                \"initial_redshift\"\n            ]\n\n            self.parameters[\"unit_m\"] = self.artio_parameters[\"mass_unit\"][0]\n            self.parameters[\"unit_t\"] = self.artio_parameters[\"time_unit\"][0] * abox**2\n            self.parameters[\"unit_l\"] = self.artio_parameters[\"length_unit\"][0] * abox\n\n            if self.artio_parameters[\"DeltaDC\"][0] != 0:\n                mylog.warning(\n                    \"DeltaDC != 0, which implies auni != abox. \"\n                    \"Be sure you understand which expansion parameter \"\n                    \"is appropriate for your use! (Gnedin, Kravtsov, & Rudd 2011)\"\n                )\n        else:\n            self.cosmological_simulation = False\n\n            self.parameters[\"unit_l\"] = self.artio_parameters[\"length_unit\"][0]\n            self.parameters[\"unit_t\"] = self.artio_parameters[\"time_unit\"][0]\n            self.parameters[\"unit_m\"] = self.artio_parameters[\"mass_unit\"][0]\n\n        # hard coded assumption of 3D periodicity\n        self._periodicity = (True, True, True)\n\n    def create_field_info(self):\n        super().create_field_info()\n        # only make the particle union if there are multiple DM species.\n        # If there are multiple, \"N-BODY_0\" will be the first species. If there\n        # are not multiple, they will be all under \"N-BODY\"\n        if \"N-BODY_0\" in self.particle_types_raw:\n            dm_labels = [\n                label for label in self.particle_types_raw if \"N-BODY\" in label\n            ]\n            # Use the N-BODY label for the union to be consistent with the\n            # previous single mass N-BODY case, where this label was used for\n            # all N-BODY particles by default\n            pu = ParticleUnion(\"N-BODY\", dm_labels)\n            self.add_particle_union(pu)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        # a valid artio header file starts with a prefix and ends with .art\n        name, _, ext = filename.rpartition(\".\")\n        if ext != \"art\":\n            return False\n        return artio_is_valid(bytes(name, \"utf-8\"))\n"
  },
  {
    "path": "yt/frontends/artio/definitions.py",
    "content": "yt_to_art = {\n    \"Density\": \"HVAR_GAS_DENSITY\",\n    \"TotalEnergy\": \"HVAR_GAS_ENERGY\",\n    \"GasEnergy\": \"HVAR_INTERNAL_ENERGY\",\n    \"Pressure\": \"HVAR_PRESSURE\",\n    \"XMomentumDensity\": \"HVAR_MOMENTUM_X\",\n    \"YMomentumDensity\": \"HVAR_MOMENTUM_Y\",\n    \"ZMomentumDensity\": \"HVAR_MOMENTUM_Z\",\n    \"Gamma\": \"HVAR_GAMMA\",\n    \"MetalDensitySNIa\": \"HVAR_METAL_DENSITY_Ia\",\n    \"MetalDensitySNII\": \"HVAR_METAL_DENSITY_II\",\n    \"Potential\": \"VAR_POTENTIAL\",\n    \"PotentialHydro\": \"VAR_POTENTIAL_HYDRO\",\n    \"particle_position_x\": \"POSITION_X\",\n    \"particle_position_y\": \"POSITION_Y\",\n    \"particle_position_z\": \"POSITION_Z\",\n    \"particle_velocity_x\": \"VELOCITY_X\",\n    \"particle_velocity_y\": \"VELOCITY_Y\",\n    \"particle_velocity_z\": \"VELOCITY_Z\",\n    \"particle_mass\": \"MASS\",\n    \"particle_index\": \"PID\",\n    \"particle_species\": \"SPECIES\",\n    \"creation_time\": \"BIRTH_TIME\",\n    \"particle_mass_initial\": \"INITIAL_MASS\",\n    \"particle_metallicity1\": \"METALLICITY_SNIa\",\n    \"particle_metallicity2\": \"METALLICITY_SNII\",\n    \"stars\": \"STAR\",\n    \"nbody\": \"N-BODY\",\n}\nart_to_yt = dict(zip(yt_to_art.values(), yt_to_art.keys(), strict=True))\n\n\nclass ARTIOconstants:\n    def __init__(self):\n        self.yr = 365.25 * 86400\n        self.Myr = 1.0e6 * self.yr\n        self.Gyr = 1.0e9 * self.yr\n\n        self.pc = 3.0856775813e18\n        self.kpc = 1.0e3 * self.pc\n        self.Mpc = 1.0e6 * self.pc\n\n        self.kms = 1.0e5\n\n        self.mp = 1.672621637e-24\n        self.k = 1.3806504e-16\n        self.G = 6.67428e-8\n        self.c = 2.99792458e10\n\n        self.eV = 1.602176487e-12\n        self.amu = 1.660538782e-24\n        self.mH = 1.007825 * self.amu\n        self.mHe = 4.002602 * self.amu\n\n        self.Msun = 1.32712440018e26 / self.G\n        self.Zsun = 0.0199\n\n        self.Yp = 0.24\n        self.wmu = 4.0 / (8.0 - 5.0 * self.Yp)\n        self.wmu_e = 1.0 / (1.0 - 0.5 * self.Yp)\n        self.XH = 1.0 - self.Yp\n        self.XHe = 0.25 * self.Yp\n        self.gamma = 5.0 / 3.0\n\n        self.sigmaT = 6.6524e-25\n"
  },
  {
    "path": "yt/frontends/artio/fields.py",
    "content": "import numpy as np\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.physical_constants import amu_cgs, boltzmann_constant_cgs\n\nb_units = \"code_magnetic\"\nra_units = \"code_length / code_time**2\"\nrho_units = \"code_mass / code_length**3\"\nvel_units = \"code_velocity\"\n# NOTE: ARTIO uses momentum density.\nmom_units = \"code_mass / (code_length**2 * code_time)\"\nen_units = \"code_mass*code_velocity**2/code_length**3\"\np_units = \"code_mass / (code_length * code_time**2)\"\n\n\nclass ARTIOFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"HVAR_GAS_DENSITY\", (rho_units, [\"density\"], None)),\n        (\"HVAR_GAS_ENERGY\", (en_units, [\"total_energy_density\"], None)),\n        (\"HVAR_INTERNAL_ENERGY\", (en_units, [\"thermal_energy_density\"], None)),\n        (\"HVAR_PRESSURE\", (p_units, [\"pressure\"], None)),\n        (\"HVAR_MOMENTUM_X\", (mom_units, [\"momentum_density_x\"], None)),\n        (\"HVAR_MOMENTUM_Y\", (mom_units, [\"momentum_density_y\"], None)),\n        (\"HVAR_MOMENTUM_Z\", (mom_units, [\"momentum_density_z\"], None)),\n        (\"HVAR_GAMMA\", (\"\", [\"gamma\"], None)),\n        (\"HVAR_METAL_DENSITY_Ia\", (rho_units, [\"metal_ia_density\"], None)),\n        (\"HVAR_METAL_DENSITY_II\", (rho_units, [\"metal_ii_density\"], None)),\n        (\"VAR_POTENTIAL\", (\"\", [\"potential\"], None)),\n        (\"VAR_POTENTIAL_HYDRO\", (\"\", [\"gas_potential\"], None)),\n        (\"RT_HVAR_HI\", (rho_units, [\"H_density\"], None)),\n        (\"RT_HVAR_HII\", (rho_units, [\"H_p1_density\"], None)),\n        (\"RT_HVAR_H2\", (rho_units, [\"H2_density\"], None)),\n        (\"RT_HVAR_HeI\", (rho_units, [\"He_density\"], None)),\n        (\"RT_HVAR_HeII\", (rho_units, [\"He_p1_density\"], None)),\n        (\"RT_HVAR_HeIII\", (rho_units, [\"He_p2_density\"], None)),\n    )\n\n    known_particle_fields: KnownFieldsT = (\n        (\"POSITION_X\", (\"code_length\", [\"particle_position_x\"], None)),\n        (\"POSITION_Y\", (\"code_length\", [\"particle_position_y\"], None)),\n        (\"POSITION_Z\", (\"code_length\", [\"particle_position_z\"], None)),\n        (\"VELOCITY_X\", (vel_units, [\"particle_velocity_x\"], None)),\n        (\"VELOCITY_Y\", (vel_units, [\"particle_velocity_y\"], None)),\n        (\"VELOCITY_Z\", (vel_units, [\"particle_velocity_z\"], None)),\n        (\"MASS\", (\"code_mass\", [\"particle_mass\"], None)),\n        (\"PID\", (\"\", [\"particle_index\"], None)),\n        (\"SPECIES\", (\"\", [\"particle_type\"], None)),\n        (\"BIRTH_TIME\", (\"\", [], None)),  # code-units defined as dimensionless to\n        # avoid incorrect conversion\n        (\"INITIAL_MASS\", (\"code_mass\", [\"initial_mass\"], None)),\n        (\"METALLICITY_SNIa\", (\"\", [\"metallicity_snia\"], None)),\n        (\"METALLICITY_SNII\", (\"\", [\"metallicity_snii\"], None)),\n    )\n\n    def setup_fluid_fields(self):\n        unit_system = self.ds.unit_system\n\n        def _get_vel(axis):\n            def velocity(data):\n                return data[\"gas\", f\"momentum_density_{axis}\"] / data[\"gas\", \"density\"]\n\n            return velocity\n\n        for ax in \"xyz\":\n            self.add_field(\n                (\"gas\", f\"velocity_{ax}\"),\n                sampling_type=\"cell\",\n                function=_get_vel(ax),\n                units=unit_system[\"velocity\"],\n            )\n\n        def _temperature(data):\n            tr = data[\"gas\", \"thermal_energy_density\"] / data[\"gas\", \"density\"]\n            # We want this to match *exactly* what ARTIO would compute\n            # internally.  We therefore use the exact values that are internal\n            # to ARTIO, rather than yt's own internal constants.\n            mH = 1.007825 * amu_cgs\n            mHe = 4.002602 * amu_cgs\n            Yp = 0.24\n            XH = 1.0 - Yp\n            XHe = 0.25 * Yp\n            mb = XH * mH + XHe * mHe\n            wmu = 4.0 / (8.0 - 5.0 * Yp)\n            # Note that we have gamma = 5.0/3.0 here\n            tr *= data[\"gas\", \"gamma\"] - 1.0\n            tr *= wmu\n            tr *= mb / boltzmann_constant_cgs\n            return tr\n\n        # TODO: The conversion factor here needs to be addressed, as previously\n        # it was set as:\n        # unit_T = unit_v**2.0*mb / constants.k\n        self.add_field(\n            (\"gas\", \"temperature\"),\n            sampling_type=\"cell\",\n            function=_temperature,\n            units=unit_system[\"temperature\"],\n        )\n\n        # Create a metal_density field as sum of existing metal fields.\n        flag1 = (\"artio\", \"HVAR_METAL_DENSITY_Ia\") in self.field_list\n        flag2 = (\"artio\", \"HVAR_METAL_DENSITY_II\") in self.field_list\n        if flag1 or flag2:\n            if flag1 and flag2:\n\n                def _metal_density(data):\n                    tr = data[\"gas\", \"metal_ia_density\"].copy()\n                    np.add(tr, data[\"gas\", \"metal_ii_density\"], out=tr)\n                    return tr\n\n            elif flag1 and not flag2:\n\n                def _metal_density(data):\n                    tr = data[\"metal_ia_density\"]\n                    return tr\n\n            else:\n\n                def _metal_density(data):\n                    tr = data[\"metal_ii_density\"]\n                    return tr\n\n            self.add_field(\n                (\"gas\", \"metal_density\"),\n                sampling_type=\"cell\",\n                function=_metal_density,\n                units=unit_system[\"density\"],\n                take_log=True,\n            )\n\n    def setup_particle_fields(self, ptype):\n        if ptype == \"STAR\":\n\n            def _creation_time(data):\n                return YTArray(\n                    data.ds._handle.tphys_from_tcode_array(data[\"STAR\", \"BIRTH_TIME\"]),\n                    \"yr\",\n                )\n\n            def _age(data):\n                return data.ds.current_time - data[\"STAR\", \"creation_time\"]\n\n            self.add_field(\n                (ptype, \"creation_time\"),\n                sampling_type=\"particle\",\n                function=_creation_time,\n                units=\"yr\",\n            )\n            self.add_field(\n                (ptype, \"age\"), sampling_type=\"particle\", function=_age, units=\"yr\"\n            )\n\n            if self.ds.cosmological_simulation:\n\n                def _creation_redshift(data):\n                    return (\n                        1.0\n                        / data.ds._handle.auni_from_tcode_array(\n                            data[\"STAR\", \"BIRTH_TIME\"]\n                        )\n                        - 1.0\n                    )\n\n                self.add_field(\n                    (ptype, \"creation_redshift\"),\n                    sampling_type=\"particle\",\n                    function=_creation_redshift,\n                )\n\n        super().setup_particle_fields(ptype)\n"
  },
  {
    "path": "yt/frontends/artio/io.py",
    "content": "import numpy as np\n\nfrom yt.utilities.io_handler import BaseIOHandler\n\n\nclass IOHandlerARTIO(BaseIOHandler):\n    _dataset_type = \"artio\"\n\n    def _read_fluid_selection(self, chunks, selector, fields):\n        tr = {ftuple: np.empty(0, dtype=\"float64\") for ftuple in fields}\n        cp = 0\n        for onechunk in chunks:\n            for artchunk in onechunk.objs:\n                rv = artchunk.fill(fields, selector)\n                for f in fields:\n                    tr[f].resize(cp + artchunk.data_size)\n                    tr[f][cp : cp + artchunk.data_size] = rv.pop(f)\n                cp += artchunk.data_size\n        return tr\n\n    def _read_particle_coords(self, chunks, ptf):\n        pn = \"POSITION_%s\"\n        chunks = list(chunks)\n        fields = [(ptype, pn % ax) for ptype, field_list in ptf.items() for ax in \"XYZ\"]\n        for chunk in chunks:  # These should be organized by grid filename\n            for subset in chunk.objs:\n                rv = dict(**subset.fill_particles(fields))\n                for ptype in sorted(ptf):\n                    x, y, z = (\n                        np.asarray(rv[ptype][pn % ax], dtype=\"=f8\") for ax in \"XYZ\"\n                    )\n                    yield ptype, (x, y, z), 0.0\n                    rv.pop(ptype)\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        pn = \"POSITION_%s\"\n        chunks = list(chunks)\n        fields = [\n            (ptype, fname) for ptype, field_list in ptf.items() for fname in field_list\n        ]\n        for ptype, field_list in sorted(ptf.items()):\n            for ax in \"XYZ\":\n                if pn % ax not in field_list:\n                    fields.append((ptype, pn % ax))\n        for chunk in chunks:  # These should be organized by grid filename\n            for subset in chunk.objs:\n                rv = dict(**subset.fill_particles(fields))\n                for ptype, field_list in sorted(ptf.items()):\n                    x, y, z = (\n                        np.asarray(rv[ptype][pn % ax], dtype=\"=f8\") for ax in \"XYZ\"\n                    )\n                    mask = selector.select_points(x, y, z, 0.0)\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        data = np.asarray(rv[ptype][field], \"=f8\")\n                        yield (ptype, field), data[mask]\n                    rv.pop(ptype)\n"
  },
  {
    "path": "yt/frontends/artio/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/artio/tests/test_outputs.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.frontends.artio.api import ARTIODataset\nfrom yt.loaders import load\nfrom yt.testing import (\n    assert_allclose_units,\n    requires_file,\n    units_override_check,\n)\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    PixelizedProjectionValuesTest,\n    create_obj,\n    data_dir_load,\n    requires_ds,\n)\n\n_fields = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_magnitude\"),\n    (\"deposit\", \"all_density\"),\n    (\"deposit\", \"all_count\"),\n)\n\nsizmbhloz = \"sizmbhloz-clref04SNth-rs9_a0.9011/sizmbhloz-clref04SNth-rs9_a0.9011.art\"\n\n\n@requires_ds(sizmbhloz)\ndef test_sizmbhloz():\n    ds = data_dir_load(sizmbhloz)\n    ds.max_range = 1024 * 1024\n    assert_equal(str(ds), \"sizmbhloz-clref04SNth-rs9_a0.9011.art\")\n    dso = [None, (\"sphere\", (\"max\", (0.1, \"unitary\")))]\n    for dobj_name in dso:\n        for field in _fields:\n            for axis in [0, 1, 2]:\n                for weight_field in [None, (\"gas\", \"density\")]:\n                    yield PixelizedProjectionValuesTest(\n                        ds, axis, field, weight_field, dobj_name\n                    )\n            yield FieldValuesTest(ds, field, dobj_name)\n        dobj = create_obj(ds, dobj_name)\n        s1 = dobj[\"index\", \"ones\"].sum()\n        s2 = sum(mask.sum() for block, mask in dobj.blocks)\n        assert_equal(s1, s2)\n    assert_equal(ds.particle_type_counts, {\"N-BODY\": 100000, \"STAR\": 110650})\n\n\n@requires_file(sizmbhloz)\ndef test_ARTIODataset():\n    assert isinstance(data_dir_load(sizmbhloz), ARTIODataset)\n\n\n@requires_file(sizmbhloz)\ndef test_units_override():\n    units_override_check(sizmbhloz)\n\n\n@requires_file(sizmbhloz)\ndef test_particle_derived_field():\n    def star_age_alias(data):\n        # test to make sure we get back data in the correct units\n        # during field detection\n        return data[\"STAR\", \"age\"].in_units(\"Myr\")\n\n    ds = load(sizmbhloz)\n\n    ds.add_field(\n        (\"STAR\", \"new_field\"),\n        function=star_age_alias,\n        units=\"Myr\",\n        sampling_type=\"particle\",\n    )\n\n    ad = ds.all_data()\n\n    assert_allclose_units(ad[\"STAR\", \"age\"].in_units(\"Myr\"), ad[\"STAR\", \"new_field\"])\n"
  },
  {
    "path": "yt/frontends/athena/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/athena/api.py",
    "content": "from . import tests\nfrom .data_structures import AthenaDataset, AthenaGrid, AthenaHierarchy\nfrom .fields import AthenaFieldInfo\nfrom .io import IOHandlerAthena\n"
  },
  {
    "path": "yt/frontends/athena/data_structures.py",
    "content": "import os\nimport re\nimport weakref\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.fields.magnetic_field import get_magnetic_normalization\nfrom yt.funcs import mylog, sglob\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.geometry_handler import YTDataChunk\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.chemical_formulas import compute_mu\nfrom yt.utilities.decompose import decompose_array, get_psize\nfrom yt.utilities.lib.misc_utilities import get_box_grids_level\n\nfrom .fields import AthenaFieldInfo\n\n\ndef chk23(strin):\n    return strin.encode(\"utf-8\")\n\n\ndef str23(strin):\n    if isinstance(strin, list):\n        return [s.decode(\"utf-8\") for s in strin]\n    else:\n        return strin.decode(\"utf-8\")\n\n\ndef check_readline(fl):\n    line = fl.readline()\n    chk = chk23(\"SCALARS\")\n    if chk in line and not line.startswith(chk):\n        line = line[line.find(chk) :]\n    chk = chk23(\"VECTORS\")\n    if chk in line and not line.startswith(chk):\n        line = line[line.find(chk) :]\n    return line\n\n\ndef check_break(line):\n    splitup = line.strip().split()\n    do_break = chk23(\"SCALAR\") in splitup\n    do_break = (chk23(\"VECTOR\") in splitup) & do_break\n    do_break = (chk23(\"TABLE\") in splitup) & do_break\n    do_break = (len(line) == 0) & do_break\n    return do_break\n\n\ndef _get_convert(fname):\n    def _conv(data):\n        return data.convert(fname)\n\n    return _conv\n\n\nclass AthenaGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level, start, dimensions, file_offset, read_dims):\n        gname = index.grid_filenames[id]\n        AMRGridPatch.__init__(self, id, filename=gname, index=index)\n        self.filename = gname\n        self.Parent = []\n        self.Children = []\n        self.Level = level\n        self.start_index = start.copy()\n        self.stop_index = self.start_index + dimensions\n        self.ActiveDimensions = dimensions.copy()\n        self.file_offset = file_offset\n        self.read_dims = read_dims\n\n    def _setup_dx(self):\n        # So first we figure out what the index is.  We don't assume\n        # that dx=dy=dz , at least here.  We probably do elsewhere.\n        id = self.id - self._id_offset\n        if len(self.Parent) > 0:\n            self.dds = self.Parent[0].dds / self.ds.refine_by\n        else:\n            LE, RE = self.index.grid_left_edge[id, :], self.index.grid_right_edge[id, :]\n            self.dds = self.ds.arr((RE - LE) / self.ActiveDimensions, \"code_length\")\n        if self.ds.dimensionality < 2:\n            self.dds[1] = 1.0\n        if self.ds.dimensionality < 3:\n            self.dds[2] = 1.0\n        self.field_data[\"dx\"], self.field_data[\"dy\"], self.field_data[\"dz\"] = self.dds\n\n\ndef parse_line(line, grid):\n    # grid is a dictionary\n    splitup = line.strip().split()\n    if chk23(\"vtk\") in splitup:\n        grid[\"vtk_version\"] = str23(splitup[-1])\n    elif chk23(\"time=\") in splitup:\n        time_index = splitup.index(chk23(\"time=\"))\n        grid[\"time\"] = float(str23(splitup[time_index + 1]).rstrip(\",\"))\n        grid[\"level\"] = int(str23(splitup[time_index + 3]).rstrip(\",\"))\n        grid[\"domain\"] = int(str23(splitup[time_index + 5]).rstrip(\",\"))\n    elif chk23(\"DIMENSIONS\") in splitup:\n        grid[\"dimensions\"] = np.array(str23(splitup[-3:]), dtype=\"int\")\n    elif chk23(\"ORIGIN\") in splitup:\n        grid[\"left_edge\"] = np.array(str23(splitup[-3:]), dtype=\"float64\")\n    elif chk23(\"SPACING\") in splitup:\n        grid[\"dds\"] = np.array(str23(splitup[-3:]), dtype=\"float64\")\n    elif chk23(\"CELL_DATA\") in splitup or chk23(\"POINT_DATA\") in splitup:\n        grid[\"ncells\"] = int(str23(splitup[-1]))\n    elif chk23(\"SCALARS\") in splitup:\n        field = str23(splitup[1])\n        grid[\"read_field\"] = field\n        grid[\"read_type\"] = \"scalar\"\n    elif chk23(\"VECTORS\") in splitup:\n        field = str23(splitup[1])\n        grid[\"read_field\"] = field\n        grid[\"read_type\"] = \"vector\"\n    elif chk23(\"time\") in splitup:\n        time_index = splitup.index(chk23(\"time\"))\n        grid[\"time\"] = float(str23(splitup[time_index + 1]))\n\n\nclass AthenaHierarchy(GridIndex):\n    grid = AthenaGrid\n    _dataset_type = \"athena\"\n    _data_file = None\n\n    def __init__(self, ds, dataset_type=\"athena\"):\n        self.dataset = weakref.proxy(ds)\n        self.directory = os.path.dirname(self.dataset.filename)\n        self.dataset_type = dataset_type\n        # for now, the index file is the dataset!\n        self.index_filename = os.path.join(os.getcwd(), self.dataset.filename)\n        self._fhandle = open(self.index_filename, \"rb\")\n        GridIndex.__init__(self, ds, dataset_type)\n\n        self._fhandle.close()\n\n    def _detect_output_fields(self):\n        field_map = {}\n        f = open(self.index_filename, \"rb\")\n        line = check_readline(f)\n        chkwhile = chk23(\"\")\n        while line != chkwhile:\n            splitup = line.strip().split()\n            chkd = chk23(\"DIMENSIONS\")\n            chkc = chk23(\"CELL_DATA\")\n            chkp = chk23(\"POINT_DATA\")\n            if chkd in splitup:\n                field = str23(splitup[-3:])\n                grid_dims = np.array(field, dtype=\"int64\")\n                line = check_readline(f)\n            elif chkc in splitup or chkp in splitup:\n                grid_ncells = int(str23(splitup[-1]))\n                line = check_readline(f)\n                if np.prod(grid_dims) != grid_ncells:\n                    grid_dims -= 1\n                    grid_dims[grid_dims == 0] = 1\n                if np.prod(grid_dims) != grid_ncells:\n                    mylog.error(\n                        \"product of dimensions %i not equal to number of cells %i\",\n                        np.prod(grid_dims),\n                        grid_ncells,\n                    )\n                    raise TypeError\n                break\n            else:\n                line = check_readline(f)\n        read_table = False\n        read_table_offset = f.tell()\n        while line != chkwhile:\n            splitup = line.strip().split()\n            chks = chk23(\"SCALARS\")\n            chkv = chk23(\"VECTORS\")\n            if chks in line and chks not in splitup:\n                splitup = str23(line[line.find(chks) :].strip().split())\n            if chkv in line and chkv not in splitup:\n                splitup = str23(line[line.find(chkv) :].strip().split())\n            if chks in splitup:\n                field = (\"athena\", str23(splitup[1]))\n                dtype = str23(splitup[-1]).lower()\n                if not read_table:\n                    line = check_readline(f)  # Read the lookup table line\n                    read_table = True\n                field_map[field] = (\"scalar\", f.tell() - read_table_offset, dtype)\n                read_table = False\n            elif chkv in splitup:\n                field = str23(splitup[1])\n                dtype = str23(splitup[-1]).lower()\n                for ax in \"xyz\":\n                    field_map[\"athena\", f\"{field}_{ax}\"] = (\n                        \"vector\",\n                        f.tell() - read_table_offset,\n                        dtype,\n                    )\n            line = check_readline(f)\n\n        f.close()\n\n        self.field_list = list(field_map.keys())\n        self._field_map = field_map\n\n    def _count_grids(self):\n        self.num_grids = self.dataset.nvtk * self.dataset.nprocs\n\n    def _parse_index(self):\n        f = open(self.index_filename, \"rb\")\n        grid = {}\n        grid[\"read_field\"] = None\n        grid[\"read_type\"] = None\n        line = f.readline()\n        while grid[\"read_field\"] is None:\n            parse_line(line, grid)\n            if check_break(line):\n                break\n            line = f.readline()\n        f.close()\n\n        # It seems some datasets have a mismatch between ncells and\n        # the actual grid dimensions.\n        if np.prod(grid[\"dimensions\"]) != grid[\"ncells\"]:\n            grid[\"dimensions\"] -= 1\n            grid[\"dimensions\"][grid[\"dimensions\"] == 0] = 1\n        if np.prod(grid[\"dimensions\"]) != grid[\"ncells\"]:\n            mylog.error(\n                \"product of dimensions %i not equal to number of cells %i\",\n                np.prod(grid[\"dimensions\"]),\n                grid[\"ncells\"],\n            )\n            raise TypeError\n\n        # Need to determine how many grids: self.num_grids\n        dataset_dir = os.path.dirname(self.index_filename)\n        dname = os.path.split(self.index_filename)[-1]\n        if dataset_dir.endswith(\"id0\"):\n            dname = \"id0/\" + dname\n            dataset_dir = dataset_dir[:-3]\n\n        gridlistread = sglob(\n            os.path.join(dataset_dir, f\"id*/{dname[4:-9]}-id*{dname[-9:]}\")\n        )\n        gridlistread.insert(0, self.index_filename)\n        if \"id0\" in dname:\n            gridlistread += sglob(\n                os.path.join(dataset_dir, f\"id*/lev*/{dname[4:-9]}*-lev*{dname[-9:]}\")\n            )\n        else:\n            gridlistread += sglob(\n                os.path.join(dataset_dir, f\"lev*/{dname[:-9]}*-lev*{dname[-9:]}\")\n            )\n        ndots = dname.count(\".\")\n        gridlistread = [\n            fn for fn in gridlistread if os.path.basename(fn).count(\".\") == ndots\n        ]\n        self.num_grids = len(gridlistread)\n        dxs = []\n        levels = np.zeros(self.num_grids, dtype=\"int32\")\n        glis = np.empty((self.num_grids, 3), dtype=\"float64\")\n        gdds = np.empty((self.num_grids, 3), dtype=\"float64\")\n        gdims = np.ones_like(glis)\n        j = 0\n        self.grid_filenames = gridlistread\n        while j < (self.num_grids):\n            f = open(gridlistread[j], \"rb\")\n            gridread = {}\n            gridread[\"read_field\"] = None\n            gridread[\"read_type\"] = None\n            line = f.readline()\n            while gridread[\"read_field\"] is None:\n                parse_line(line, gridread)\n                splitup = line.strip().split()\n                if chk23(\"X_COORDINATES\") in splitup:\n                    gridread[\"left_edge\"] = np.zeros(3)\n                    gridread[\"dds\"] = np.zeros(3)\n                    v = np.fromfile(f, dtype=\">f8\", count=2)\n                    gridread[\"left_edge\"][0] = v[0] - 0.5 * (v[1] - v[0])\n                    gridread[\"dds\"][0] = v[1] - v[0]\n                if chk23(\"Y_COORDINATES\") in splitup:\n                    v = np.fromfile(f, dtype=\">f8\", count=2)\n                    gridread[\"left_edge\"][1] = v[0] - 0.5 * (v[1] - v[0])\n                    gridread[\"dds\"][1] = v[1] - v[0]\n                if chk23(\"Z_COORDINATES\") in splitup:\n                    v = np.fromfile(f, dtype=\">f8\", count=2)\n                    gridread[\"left_edge\"][2] = v[0] - 0.5 * (v[1] - v[0])\n                    gridread[\"dds\"][2] = v[1] - v[0]\n                if check_break(line):\n                    break\n                line = f.readline()\n            f.close()\n            levels[j] = gridread.get(\"level\", 0)\n            glis[j, 0] = gridread[\"left_edge\"][0]\n            glis[j, 1] = gridread[\"left_edge\"][1]\n            glis[j, 2] = gridread[\"left_edge\"][2]\n            # It seems some datasets have a mismatch between ncells and\n            # the actual grid dimensions.\n            if np.prod(gridread[\"dimensions\"]) != gridread[\"ncells\"]:\n                gridread[\"dimensions\"] -= 1\n                gridread[\"dimensions\"][gridread[\"dimensions\"] == 0] = 1\n            if np.prod(gridread[\"dimensions\"]) != gridread[\"ncells\"]:\n                mylog.error(\n                    \"product of dimensions %i not equal to number of cells %i\",\n                    np.prod(gridread[\"dimensions\"]),\n                    gridread[\"ncells\"],\n                )\n                raise TypeError\n            gdims[j, 0] = gridread[\"dimensions\"][0]\n            gdims[j, 1] = gridread[\"dimensions\"][1]\n            gdims[j, 2] = gridread[\"dimensions\"][2]\n            # Setting dds=1 for non-active dimensions in 1D/2D datasets\n            gridread[\"dds\"][gridread[\"dimensions\"] == 1] = 1.0\n            gdds[j, :] = gridread[\"dds\"]\n\n            j = j + 1\n\n        gres = glis + gdims * gdds\n        # Now we convert the glis, which were left edges (floats), to indices\n        # from the domain left edge.  Then we do a bunch of fixing now that we\n        # know the extent of all the grids.\n        glis = np.round(\n            (glis - self.dataset.domain_left_edge.ndarray_view()) / gdds\n        ).astype(\"int64\")\n        new_dre = np.max(gres, axis=0)\n        dre_units = self.dataset.domain_right_edge.uq\n        self.dataset.domain_right_edge = np.round(new_dre, decimals=12) * dre_units\n        self.dataset.domain_width = (\n            self.dataset.domain_right_edge - self.dataset.domain_left_edge\n        )\n        self.dataset.domain_center = 0.5 * (\n            self.dataset.domain_left_edge + self.dataset.domain_right_edge\n        )\n        domain_dimensions = np.round(self.dataset.domain_width / gdds[0]).astype(\n            \"int64\"\n        )\n\n        if self.dataset.dimensionality <= 2:\n            domain_dimensions[2] = 1\n        if self.dataset.dimensionality == 1:\n            domain_dimensions[1] = 1\n        self.dataset.domain_dimensions = domain_dimensions\n\n        dle = self.dataset.domain_left_edge\n        dre = self.dataset.domain_right_edge\n        dx_root = (\n            self.dataset.domain_right_edge - self.dataset.domain_left_edge\n        ) / self.dataset.domain_dimensions\n\n        if self.dataset.nprocs > 1:\n            gle_all = []\n            gre_all = []\n            shapes_all = []\n            levels_all = []\n            new_gridfilenames = []\n            file_offsets = []\n            read_dims = []\n            for i in range(levels.shape[0]):\n                dx = dx_root / self.dataset.refine_by ** (levels[i])\n                gle_orig = self.ds.arr(\n                    np.round(dle + dx * glis[i], decimals=12), \"code_length\"\n                )\n                gre_orig = self.ds.arr(\n                    np.round(gle_orig + dx * gdims[i], decimals=12), \"code_length\"\n                )\n                bbox = np.array(\n                    [[le, re] for le, re in zip(gle_orig, gre_orig, strict=True)]\n                )\n                psize = get_psize(self.ds.domain_dimensions, self.ds.nprocs)\n                gle, gre, shapes, slices, _ = decompose_array(gdims[i], psize, bbox)\n                gle_all += gle\n                gre_all += gre\n                shapes_all += shapes\n                levels_all += [levels[i]] * self.dataset.nprocs\n                new_gridfilenames += [self.grid_filenames[i]] * self.dataset.nprocs\n                file_offsets += [\n                    [slc[0].start, slc[1].start, slc[2].start] for slc in slices\n                ]\n                read_dims += [\n                    np.array([gdims[i][0], gdims[i][1], shape[2]], dtype=\"int64\")\n                    for shape in shapes\n                ]\n            self.num_grids *= self.dataset.nprocs\n            self.grids = np.empty(self.num_grids, dtype=\"object\")\n            self.grid_filenames = new_gridfilenames\n            self.grid_left_edge = self.ds.arr(gle_all, \"code_length\")\n            self.grid_right_edge = self.ds.arr(gre_all, \"code_length\")\n            self.grid_dimensions = np.array(list(shapes_all), dtype=\"int32\")\n            gdds = (self.grid_right_edge - self.grid_left_edge) / self.grid_dimensions\n            glis = np.round(\n                (self.grid_left_edge - self.ds.domain_left_edge) / gdds\n            ).astype(\"int64\")\n            for i in range(self.num_grids):\n                self.grids[i] = self.grid(\n                    i,\n                    self,\n                    levels_all[i],\n                    glis[i],\n                    shapes_all[i],\n                    file_offsets[i],\n                    read_dims[i],\n                )\n        else:\n            self.grids = np.empty(self.num_grids, dtype=\"object\")\n            for i in range(levels.shape[0]):\n                self.grids[i] = self.grid(\n                    i, self, levels[i], glis[i], gdims[i], [0] * 3, gdims[i]\n                )\n                dx = dx_root / self.dataset.refine_by ** (levels[i])\n                dxs.append(dx)\n\n            dx = self.ds.arr(dxs, \"code_length\")\n            self.grid_left_edge = self.ds.arr(\n                np.round(dle + dx * glis, decimals=12), \"code_length\"\n            )\n            self.grid_dimensions = gdims.astype(\"int32\")\n            self.grid_right_edge = self.ds.arr(\n                np.round(self.grid_left_edge + dx * self.grid_dimensions, decimals=12),\n                \"code_length\",\n            )\n        if self.dataset.dimensionality <= 2:\n            self.grid_right_edge[:, 2] = dre[2]\n        if self.dataset.dimensionality == 1:\n            self.grid_right_edge[:, 1:] = dre[1:]\n        self.grid_particle_count = np.zeros([self.num_grids, 1], dtype=\"int64\")\n\n    def _populate_grid_objects(self):\n        for g in self.grids:\n            g._prepare_grid()\n            g._setup_dx()\n        self._reconstruct_parent_child()\n        self.max_level = self.grid_levels.max()\n\n    def _reconstruct_parent_child(self):\n        mask = np.empty(len(self.grids), dtype=\"int32\")\n        mylog.debug(\"First pass; identifying child grids\")\n        for i, grid in enumerate(self.grids):\n            get_box_grids_level(\n                self.grid_left_edge[i, :],\n                self.grid_right_edge[i, :],\n                self.grid_levels[i].item() + 1,\n                self.grid_left_edge,\n                self.grid_right_edge,\n                self.grid_levels,\n                mask,\n            )\n            grid.Children = [\n                g for g in self.grids[mask.astype(\"bool\")] if g.Level == grid.Level + 1\n            ]\n        mylog.debug(\"Second pass; identifying parents\")\n        for grid in self.grids:  # Second pass\n            for child in grid.Children:\n                child.Parent.append(grid)\n\n    def _get_grid_children(self, grid):\n        mask = np.zeros(self.num_grids, dtype=\"bool\")\n        grids, grid_ind = self.get_box_grids(grid.LeftEdge, grid.RightEdge)\n        mask[grid_ind] = True\n        return [g for g in self.grids[mask] if g.Level == grid.Level + 1]\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):\n        gobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for subset in gobjs:\n            yield YTDataChunk(\n                dobj, \"io\", [subset], self._count_selection(dobj, [subset]), cache=cache\n            )\n\n\nclass AthenaDataset(Dataset):\n    _index_class = AthenaHierarchy\n    _field_info_class = AthenaFieldInfo\n    _dataset_type = \"athena\"\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"athena\",\n        storage_filename=None,\n        parameters=None,\n        units_override=None,\n        nprocs=1,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n        magnetic_normalization=\"gaussian\",\n    ):\n        self.fluid_types += (\"athena\",)\n        self.nprocs = nprocs\n        if parameters is None:\n            parameters = {}\n        self.specified_parameters = parameters.copy()\n        if units_override is None:\n            units_override = {}\n        self._magnetic_factor = get_magnetic_normalization(magnetic_normalization)\n\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        if storage_filename is None:\n            storage_filename = self.basename + \".yt\"\n        self.storage_filename = storage_filename\n        # Unfortunately we now have to mandate that the index gets\n        # instantiated so that we can make sure we have the correct left\n        # and right domain edges.\n        self.index\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Generates the conversion to various physical _units based on the\n        parameter file\n        \"\"\"\n        if \"length_unit\" not in self.units_override:\n            self.no_cgs_equiv_length = True\n        for unit, cgs in [(\"length\", \"cm\"), (\"time\", \"s\"), (\"mass\", \"g\")]:\n            # We set these to cgs for now, but they may have been overridden\n            if getattr(self, unit + \"_unit\", None) is not None:\n                continue\n            mylog.warning(\"Assuming 1.0 = 1.0 %s\", cgs)\n            setattr(self, f\"{unit}_unit\", self.quan(1.0, cgs))\n        self.magnetic_unit = np.sqrt(\n            self._magnetic_factor\n            * self.mass_unit\n            / (self.time_unit**2 * self.length_unit)\n        )\n        self.magnetic_unit.convert_to_units(\"gauss\")\n        self.velocity_unit = self.length_unit / self.time_unit\n\n    def _parse_parameter_file(self):\n        self._handle = open(self.parameter_filename, \"rb\")\n        # Read the start of a grid to get simulation parameters.\n        grid = {}\n        grid[\"read_field\"] = None\n        line = self._handle.readline()\n        while grid[\"read_field\"] is None:\n            parse_line(line, grid)\n            splitup = line.strip().split()\n            if chk23(\"X_COORDINATES\") in splitup:\n                grid[\"left_edge\"] = np.zeros(3)\n                grid[\"dds\"] = np.zeros(3)\n                v = np.fromfile(self._handle, dtype=\">f8\", count=2)\n                grid[\"left_edge\"][0] = v[0] - 0.5 * (v[1] - v[0])\n                grid[\"dds\"][0] = v[1] - v[0]\n            if chk23(\"Y_COORDINATES\") in splitup:\n                v = np.fromfile(self._handle, dtype=\">f8\", count=2)\n                grid[\"left_edge\"][1] = v[0] - 0.5 * (v[1] - v[0])\n                grid[\"dds\"][1] = v[1] - v[0]\n            if chk23(\"Z_COORDINATES\") in splitup:\n                v = np.fromfile(self._handle, dtype=\">f8\", count=2)\n                grid[\"left_edge\"][2] = v[0] - 0.5 * (v[1] - v[0])\n                grid[\"dds\"][2] = v[1] - v[0]\n            if check_break(line):\n                break\n            line = self._handle.readline()\n\n        self.domain_left_edge = grid[\"left_edge\"]\n        mylog.info(\n            \"Temporarily setting domain_right_edge = -domain_left_edge. \"\n            \"This will be corrected automatically if it is not the case.\"\n        )\n        self.domain_right_edge = -self.domain_left_edge\n        self.domain_width = self.domain_right_edge - self.domain_left_edge\n        domain_dimensions = np.round(self.domain_width / grid[\"dds\"]).astype(\"int32\")\n        refine_by = None\n        if refine_by is None:\n            refine_by = 2\n        self.refine_by = refine_by\n        dimensionality = 3\n        if grid[\"dimensions\"][2] == 1:\n            dimensionality = 2\n        if grid[\"dimensions\"][1] == 1:\n            dimensionality = 1\n        if dimensionality <= 2:\n            domain_dimensions[2] = np.int32(1)\n        if dimensionality == 1:\n            domain_dimensions[1] = np.int32(1)\n        if dimensionality != 3 and self.nprocs > 1:\n            raise RuntimeError(\"Virtual grids are only supported for 3D outputs!\")\n        self.domain_dimensions = domain_dimensions\n        self.dimensionality = dimensionality\n        self.current_time = grid[\"time\"]\n        self.cosmological_simulation = False\n        self.num_ghost_zones = 0\n        self.field_ordering = \"fortran\"\n        self.boundary_conditions = [1] * 6\n        self._periodicity = tuple(\n            self.specified_parameters.get(\"periodicity\", (True, True, True))\n        )\n        if \"gamma\" in self.specified_parameters:\n            self.gamma = float(self.specified_parameters[\"gamma\"])\n        else:\n            self.gamma = 5.0 / 3.0\n        dataset_dir = os.path.dirname(self.parameter_filename)\n        dname = os.path.split(self.parameter_filename)[-1]\n        if dataset_dir.endswith(\"id0\"):\n            dname = \"id0/\" + dname\n            dataset_dir = dataset_dir[:-3]\n\n        gridlistread = sglob(\n            os.path.join(dataset_dir, f\"id*/{dname[4:-9]}-id*{dname[-9:]}\")\n        )\n        if \"id0\" in dname:\n            gridlistread += sglob(\n                os.path.join(dataset_dir, f\"id*/lev*/{dname[4:-9]}*-lev*{dname[-9:]}\")\n            )\n        else:\n            gridlistread += sglob(\n                os.path.join(dataset_dir, f\"lev*/{dname[:-9]}*-lev*{dname[-9:]}\")\n            )\n        ndots = dname.count(\".\")\n        gridlistread = [\n            fn for fn in gridlistread if os.path.basename(fn).count(\".\") == ndots\n        ]\n        self.nvtk = len(gridlistread) + 1\n\n        self.current_redshift = 0.0\n        self.omega_lambda = 0.0\n        self.omega_matter = 0.0\n        self.hubble_constant = 0.0\n        self.cosmological_simulation = 0\n        # Hardcode time conversion for now.\n        self.parameters[\"Time\"] = self.current_time\n        # Hardcode for now until field staggering is supported.\n        self.parameters[\"HydroMethod\"] = 0\n        if \"gamma\" in self.specified_parameters:\n            self.parameters[\"Gamma\"] = self.specified_parameters[\"gamma\"]\n        else:\n            self.parameters[\"Gamma\"] = 5.0 / 3.0\n        self.geometry = Geometry(self.specified_parameters.get(\"geometry\", \"cartesian\"))\n        self._handle.close()\n        self.mu = self.specified_parameters.get(\n            \"mu\", compute_mu(self.default_species_fields)\n        )\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".vtk\"):\n            return False\n\n        with open(filename, \"rb\") as fh:\n            if not re.match(b\"# vtk DataFile Version \\\\d\\\\.\\\\d\\n\", fh.readline(256)):\n                return False\n            if (\n                re.search(\n                    b\"at time= .*, level= \\\\d, domain= \\\\d\\n\",\n                    fh.readline(256),\n                )\n                is None\n            ):\n                # vtk Dumps headers start with either \"CONSERVED vars\" or \"PRIMITIVE vars\",\n                # while vtk output headers start with \"Really cool Athena data\", but\n                # we will consider this an implementation detail and not attempt to\n                # match it exactly here.\n                # See Athena's user guide for reference\n                # https://princetonuniversity.github.io/Athena-Cversion/AthenaDocsUGbtk\n                return False\n        return True\n\n    @property\n    def _skip_cache(self):\n        return True\n\n    def __str__(self):\n        return self.basename.rsplit(\".\", 1)[0]\n"
  },
  {
    "path": "yt/frontends/athena/definitions.py",
    "content": "\"\"\"\nVarious definitions for various other modules and routines\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/athena/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.utilities.physical_constants import kboltz, mh\n\nb_units = \"code_magnetic\"\npres_units = \"code_pressure\"\nerg_units = \"code_mass * (code_length/code_time)**2\"\nrho_units = \"code_mass / code_length**3\"\n\n\ndef velocity_field(comp):\n    def _velocity(data):\n        return data[\"athena\", f\"momentum_{comp}\"] / data[\"athena\", \"density\"]\n\n    return _velocity\n\n\nclass AthenaFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (\"code_mass/code_length**3\", [\"density\"], None)),\n        (\"cell_centered_B_x\", (b_units, [], None)),\n        (\"cell_centered_B_y\", (b_units, [], None)),\n        (\"cell_centered_B_z\", (b_units, [], None)),\n        (\"total_energy\", (\"code_pressure\", [\"total_energy_density\"], None)),\n        (\n            \"gravitational_potential\",\n            (\"code_velocity**2\", [\"gravitational_potential\"], None),\n        ),\n    )\n\n    # In Athena, conservative or primitive variables may be written out.\n    # By default, yt concerns itself with primitive variables. The following\n    # field definitions allow for conversions to primitive variables in the\n    # case that the file contains the conservative ones.\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        unit_system = self.ds.unit_system\n        # Add velocity fields\n        for comp in \"xyz\":\n            vel_field = (\"athena\", f\"velocity_{comp}\")\n            mom_field = (\"athena\", f\"momentum_{comp}\")\n            if vel_field in self.field_list:\n                self.add_output_field(\n                    vel_field, sampling_type=\"cell\", units=\"code_length/code_time\"\n                )\n                self.alias(\n                    (\"gas\", f\"velocity_{comp}\"),\n                    vel_field,\n                    units=unit_system[\"velocity\"],\n                )\n            elif mom_field in self.field_list:\n                self.add_output_field(\n                    mom_field,\n                    sampling_type=\"cell\",\n                    units=\"code_mass/code_time/code_length**2\",\n                )\n                self.alias(\n                    (\"gas\", f\"momentum_density_{comp}\"),\n                    mom_field,\n                    units=unit_system[\"density\"] * unit_system[\"velocity\"],\n                )\n                self.add_field(\n                    (\"gas\", f\"velocity_{comp}\"),\n                    sampling_type=\"cell\",\n                    function=velocity_field(comp),\n                    units=unit_system[\"velocity\"],\n                )\n\n        # Add pressure, energy, and temperature fields\n        def eint_from_etot(data):\n            eint = (\n                data[\"athena\", \"total_energy\"] - data[\"gas\", \"kinetic_energy_density\"]\n            )\n            if (\"athena\", \"cell_centered_B_x\") in self.field_list:\n                eint -= data[\"gas\", \"magnetic_energy_density\"]\n            return eint\n\n        def etot_from_pres(data):\n            etot = data[\"athena\", \"pressure\"] / (data.ds.gamma - 1.0)\n            etot += data[\"gas\", \"kinetic_energy_density\"]\n            if (\"athena\", \"cell_centered_B_x\") in self.field_list:\n                etot += data[\"gas\", \"magnetic_energy_density\"]\n            return etot\n\n        if (\"athena\", \"pressure\") in self.field_list:\n            self.add_output_field(\n                (\"athena\", \"pressure\"), sampling_type=\"cell\", units=pres_units\n            )\n            self.alias(\n                (\"gas\", \"pressure\"),\n                (\"athena\", \"pressure\"),\n                units=unit_system[\"pressure\"],\n            )\n\n            def _specific_thermal_energy(data):\n                return (\n                    data[\"athena\", \"pressure\"]\n                    / (data.ds.gamma - 1.0)\n                    / data[\"athena\", \"density\"]\n                )\n\n            self.add_field(\n                (\"gas\", \"specific_thermal_energy\"),\n                sampling_type=\"cell\",\n                function=_specific_thermal_energy,\n                units=unit_system[\"specific_energy\"],\n            )\n\n            def _specific_total_energy(data):\n                return etot_from_pres(data) / data[\"athena\", \"density\"]\n\n            self.add_field(\n                (\"gas\", \"specific_total_energy\"),\n                sampling_type=\"cell\",\n                function=_specific_total_energy,\n                units=unit_system[\"specific_energy\"],\n            )\n        elif (\"athena\", \"total_energy\") in self.field_list:\n            self.add_output_field(\n                (\"athena\", \"total_energy\"), sampling_type=\"cell\", units=pres_units\n            )\n\n            def _specific_thermal_energy(data):\n                return eint_from_etot(data) / data[\"athena\", \"density\"]\n\n            self.add_field(\n                (\"gas\", \"specific_thermal_energy\"),\n                sampling_type=\"cell\",\n                function=_specific_thermal_energy,\n                units=unit_system[\"specific_energy\"],\n            )\n\n            def _specific_total_energy(data):\n                return data[\"athena\", \"total_energy\"] / data[\"athena\", \"density\"]\n\n            self.add_field(\n                (\"gas\", \"specific_total_energy\"),\n                sampling_type=\"cell\",\n                function=_specific_total_energy,\n                units=unit_system[\"specific_energy\"],\n            )\n\n        # Add temperature field\n        def _temperature(data):\n            return (\n                data.ds.mu\n                * data[\"gas\", \"pressure\"]\n                / data[\"gas\", \"density\"]\n                * mh\n                / kboltz\n            )\n\n        self.add_field(\n            (\"gas\", \"temperature\"),\n            sampling_type=\"cell\",\n            function=_temperature,\n            units=unit_system[\"temperature\"],\n        )\n\n        setup_magnetic_field_aliases(\n            self, \"athena\", [f\"cell_centered_B_{ax}\" for ax in \"xyz\"]\n        )\n"
  },
  {
    "path": "yt/frontends/athena/io.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog\nfrom yt.utilities.io_handler import BaseIOHandler\n\nfrom .data_structures import chk23\n\nfloat_size = {\"float\": np.dtype(\">f4\").itemsize, \"double\": np.dtype(\">f8\").itemsize}\n\naxis_list = [\"_x\", \"_y\", \"_z\"]\n\n\nclass IOHandlerAthena(BaseIOHandler):\n    _dataset_type = \"athena\"\n    _offset_string = \"data:offsets=0\"\n    _data_string = \"data:datatype=0\"\n    _read_table_offset = None\n\n    def _read_field_names(self, grid):\n        pass\n\n    def _read_chunk_data(self, chunk, fields):\n        data = {}\n        if len(chunk.objs) == 0:\n            return data\n        for grid in chunk.objs:\n            if grid.filename is None:\n                continue\n            f = open(grid.filename, \"rb\")\n            data[grid.id] = {}\n            grid_dims = grid.ActiveDimensions\n            read_dims = grid.read_dims.astype(\"int64\")\n            grid_ncells = np.prod(read_dims)\n            grid0_ncells = np.prod(grid.index.grids[0].read_dims)\n            read_table_offset = get_read_table_offset(f)\n            for field in fields:\n                ftype, offsetr, dtype = grid.index._field_map[field]\n                if grid_ncells != grid0_ncells:\n                    offset = offsetr + (\n                        (grid_ncells - grid0_ncells) * (offsetr // grid0_ncells)\n                    )\n                if grid_ncells == grid0_ncells:\n                    offset = offsetr\n                offset = int(offset)  # Casting to be certain.\n                file_offset = (\n                    grid.file_offset[2]\n                    * read_dims[0]\n                    * read_dims[1]\n                    * float_size[dtype]\n                )\n                xread = slice(grid.file_offset[0], grid.file_offset[0] + grid_dims[0])\n                yread = slice(grid.file_offset[1], grid.file_offset[1] + grid_dims[1])\n                f.seek(read_table_offset + offset + file_offset)\n                if dtype == \"float\":\n                    dt = \">f4\"\n                elif dtype == \"double\":\n                    dt = \">f8\"\n                if ftype == \"scalar\":\n                    f.seek(read_table_offset + offset + file_offset)\n                    v = np.fromfile(f, dtype=dt, count=grid_ncells).reshape(\n                        read_dims, order=\"F\"\n                    )\n                if ftype == \"vector\":\n                    vec_offset = axis_list.index(field[-1][-2:])\n                    f.seek(read_table_offset + offset + 3 * file_offset)\n                    v = np.fromfile(f, dtype=dt, count=3 * grid_ncells)\n                    v = v[vec_offset::3].reshape(read_dims, order=\"F\")\n                if grid.ds.field_ordering == 1:\n                    data[grid.id][field] = v[xread, yread, :].T.astype(\"float64\")\n                else:\n                    data[grid.id][field] = v[xread, yread, :].astype(\"float64\")\n            f.close()\n        return data\n\n    def _read_data_slice(self, grid, field, axis, coord):\n        sl = [slice(None), slice(None), slice(None)]\n        sl[axis] = slice(coord, coord + 1)\n        if grid.ds.field_ordering == 1:\n            sl.reverse()\n        return self._read_data_set(grid, field)[tuple(sl)]\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        if any((ftype != \"athena\" for ftype, fname in fields)):\n            raise NotImplementedError\n        rv = {}\n        for field in fields:\n            rv[field] = np.empty(size, dtype=\"float64\")\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s grids\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n        ind = 0\n        for chunk in chunks:\n            data = self._read_chunk_data(chunk, fields)\n            for g in chunk.objs:\n                for field in fields:\n                    ftype, fname = field\n                    ds = data[g.id].pop(field)\n                    nd = g.select(selector, ds, rv[field], ind)  # caches\n                ind += nd\n                data.pop(g.id)\n        return rv\n\n\ndef get_read_table_offset(f):\n    line = f.readline()\n    while True:\n        splitup = line.strip().split()\n        chkc = chk23(\"CELL_DATA\")\n        chkp = chk23(\"POINT_DATA\")\n        if chkc in splitup or chkp in splitup:\n            f.readline()\n            read_table_offset = f.tell()\n            break\n        line = f.readline()\n    return read_table_offset\n"
  },
  {
    "path": "yt/frontends/athena/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/athena/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/athena/tests/test_outputs.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.frontends.athena.api import AthenaDataset\nfrom yt.loaders import load\nfrom yt.testing import (\n    assert_allclose_units,\n    disable_dataset_cache,\n    requires_file,\n)\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\n_fields_cloud = (\n    (\"athena\", \"scalar[0]\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"total_energy_density\"),\n)\n\ncloud = \"ShockCloud/id0/Cloud.0050.vtk\"\n\n\n@requires_ds(cloud)\ndef test_cloud():\n    ds = data_dir_load(cloud)\n    assert_equal(str(ds), \"Cloud.0050\")\n    for test in small_patch_amr(ds, _fields_cloud):\n        test_cloud.__name__ = test.description\n        yield test\n\n\n_fields_blast = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_magnitude\"),\n)\n\nblast = \"MHDBlast/id0/Blast.0100.vtk\"\n\n\n@requires_ds(blast)\ndef test_blast():\n    ds = data_dir_load(blast)\n    assert_equal(str(ds), \"Blast.0100\")\n    for test in small_patch_amr(ds, _fields_blast):\n        test_blast.__name__ = test.description\n        yield test\n\n\nuo_blast = {\n    \"length_unit\": (1.0, \"pc\"),\n    \"mass_unit\": (2.38858753789e-24, \"g/cm**3*pc**3\"),\n    \"time_unit\": (1.0, \"s*pc/km\"),\n}\n\n\n@requires_file(blast)\ndef test_blast_override():\n    # verify that overriding units causes derived unit values to be updated.\n    # see issue #1259\n    ds = load(blast, units_override=uo_blast)\n    assert_equal(float(ds.magnetic_unit.in_units(\"gauss\")), 5.47867467969813e-07)\n\n\nuo_stripping = {\n    \"time_unit\": 3.086e14,\n    \"length_unit\": 8.0236e22,\n    \"mass_unit\": 9.999e-30 * 8.0236e22**3,\n}\n\n_fields_stripping = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n    (\"athena\", \"specific_scalar[0]\"),\n)\n\nstripping = \"RamPressureStripping/id0/rps.0062.vtk\"\n\n\n@requires_ds(stripping, big_data=True)\ndef test_stripping():\n    ds = data_dir_load(stripping, kwargs={\"units_override\": uo_stripping})\n    assert_equal(str(ds), \"rps.0062\")\n    for test in small_patch_amr(ds, _fields_stripping):\n        test_stripping.__name__ = test.description\n        yield test\n\n\nsloshing = \"MHDSloshing/virgo_low_res.0054.vtk\"\n\nuo_sloshing = {\n    \"length_unit\": (1.0, \"Mpc\"),\n    \"time_unit\": (1.0, \"Myr\"),\n    \"mass_unit\": (1.0e14, \"Msun\"),\n}\n\n\n@requires_file(sloshing)\n@disable_dataset_cache\ndef test_nprocs():\n    ds1 = load(sloshing, units_override=uo_sloshing)\n    sp1 = ds1.sphere(\"c\", (100.0, \"kpc\"))\n    prj1 = ds1.proj((\"gas\", \"density\"), 0)\n    ds2 = load(sloshing, units_override=uo_sloshing, nprocs=8)\n    sp2 = ds2.sphere(\"c\", (100.0, \"kpc\"))\n    prj2 = ds1.proj((\"gas\", \"density\"), 0)\n\n    assert_equal(\n        sp1.quantities.extrema((\"gas\", \"pressure\")),\n        sp2.quantities.extrema((\"gas\", \"pressure\")),\n    )\n    assert_allclose_units(\n        sp1.quantities.total_quantity((\"gas\", \"pressure\")),\n        sp2.quantities.total_quantity((\"gas\", \"pressure\")),\n    )\n    for ax in \"xyz\":\n        assert_equal(\n            sp1.quantities.extrema((\"gas\", f\"velocity_{ax}\")),\n            sp2.quantities.extrema((\"gas\", f\"velocity_{ax}\")),\n        )\n    assert_allclose_units(\n        sp1.quantities.bulk_velocity(), sp2.quantities.bulk_velocity()\n    )\n    assert_equal(prj1[\"gas\", \"density\"], prj2[\"gas\", \"density\"])\n\n\n@requires_file(cloud)\ndef test_AthenaDataset():\n    assert isinstance(data_dir_load(cloud), AthenaDataset)\n\n\n@requires_file(sloshing)\n@disable_dataset_cache\ndef test_mag_factor():\n    ds1 = load(sloshing, units_override=uo_sloshing, magnetic_normalization=\"gaussian\")\n    assert ds1.magnetic_unit == np.sqrt(\n        4.0 * np.pi * ds1.mass_unit / (ds1.time_unit**2 * ds1.length_unit)\n    )\n    sp1 = ds1.sphere(\"c\", (100.0, \"kpc\"))\n    pB1a = (\n        sp1[\"athena\", \"cell_centered_B_x\"] ** 2\n        + sp1[\"athena\", \"cell_centered_B_y\"] ** 2\n        + sp1[\"athena\", \"cell_centered_B_z\"] ** 2\n    ) / (8.0 * np.pi)\n    pB1b = (\n        sp1[\"gas\", \"magnetic_field_x\"] ** 2\n        + sp1[\"gas\", \"magnetic_field_y\"] ** 2\n        + sp1[\"gas\", \"magnetic_field_z\"] ** 2\n    ) / (8.0 * np.pi)\n    pB1a.convert_to_units(\"dyn/cm**2\")\n    pB1b.convert_to_units(\"dyn/cm**2\")\n    assert_allclose_units(pB1a, pB1b)\n    assert_allclose_units(pB1a, sp1[\"magnetic_pressure\"])\n    ds2 = load(\n        sloshing, units_override=uo_sloshing, magnetic_normalization=\"lorentz_heaviside\"\n    )\n    assert ds2.magnetic_unit == np.sqrt(\n        ds2.mass_unit / (ds2.time_unit**2 * ds2.length_unit)\n    )\n    sp2 = ds2.sphere(\"c\", (100.0, \"kpc\"))\n    pB2a = (\n        sp2[\"athena\", \"cell_centered_B_x\"] ** 2\n        + sp2[\"athena\", \"cell_centered_B_y\"] ** 2\n        + sp2[\"athena\", \"cell_centered_B_z\"] ** 2\n    ) / 2.0\n    pB2b = (\n        sp2[\"gas\", \"magnetic_field_x\"] ** 2\n        + sp2[\"gas\", \"magnetic_field_y\"] ** 2\n        + sp2[\"gas\", \"magnetic_field_z\"] ** 2\n    ) / 2.0\n    pB2a.convert_to_units(\"dyn/cm**2\")\n    pB2b.convert_to_units(\"dyn/cm**2\")\n    assert_allclose_units(pB2a, pB2b)\n    assert_allclose_units(pB2a, sp2[\"magnetic_pressure\"])\n    assert_allclose_units(pB1a, pB2a)\n"
  },
  {
    "path": "yt/frontends/athena_pp/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/athena_pp/api.py",
    "content": "from . import tests\nfrom .data_structures import AthenaPPDataset, AthenaPPGrid, AthenaPPHierarchy\nfrom .fields import AthenaPPFieldInfo\nfrom .io import IOHandlerAthenaPP\n"
  },
  {
    "path": "yt/frontends/athena_pp/data_structures.py",
    "content": "import os\nimport weakref\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.index_subobjects.stretched_grid import StretchedGrid\nfrom yt.data_objects.static_output import Dataset\nfrom yt.fields.magnetic_field import get_magnetic_normalization\nfrom yt.funcs import mylog\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.chemical_formulas import compute_mu\nfrom yt.utilities.file_handler import HDF5FileHandler\n\nfrom .fields import AthenaPPFieldInfo\n\ngeom_map = {\n    \"cartesian\": \"cartesian\",\n    \"cylindrical\": \"cylindrical\",\n    \"spherical_polar\": \"spherical\",\n    \"minkowski\": \"cartesian\",\n    \"tilted\": \"cartesian\",\n    \"sinusoidal\": \"cartesian\",\n    \"schwarzschild\": \"spherical\",\n    \"kerr-schild\": \"spherical\",\n}\n\n\nclass AthenaPPGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level):\n        AMRGridPatch.__init__(self, id, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n\n    def _setup_dx(self):\n        # So first we figure out what the index is.  We don't assume\n        # that dx=dy=dz , at least here.  We probably do elsewhere.\n        id = self.id - self._id_offset\n        LE, RE = self.index.grid_left_edge[id, :], self.index.grid_right_edge[id, :]\n        self.dds = self.ds.arr((RE - LE) / self.ActiveDimensions, \"code_length\")\n        if self.ds.dimensionality < 2:\n            self.dds[1] = 1.0\n        if self.ds.dimensionality < 3:\n            self.dds[2] = 1.0\n        self.field_data[\"dx\"], self.field_data[\"dy\"], self.field_data[\"dz\"] = self.dds\n\n\nclass AthenaPPStretchedGrid(StretchedGrid):\n    _id_offset = 0\n\n    def __init__(self, id, cell_widths, index, level):\n        super().__init__(id, cell_widths, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n\n\nclass AthenaPPHierarchy(GridIndex):\n    _dataset_type = \"athena_pp\"\n    _data_file = None\n\n    def __init__(self, ds, dataset_type=\"athena_pp\"):\n        self.dataset = weakref.proxy(ds)\n        self.grid = AthenaPPStretchedGrid if self.dataset._nonuniform else AthenaPPGrid\n        self.directory = os.path.dirname(self.dataset.filename)\n        self.dataset_type = dataset_type\n        # for now, the index file is the dataset!\n        self.index_filename = self.dataset.filename\n        self._handle = ds._handle\n        GridIndex.__init__(self, ds, dataset_type)\n\n    def _detect_output_fields(self):\n        self.field_list = [(\"athena_pp\", k) for k in self.dataset._field_map]\n\n    def _count_grids(self):\n        self.num_grids = self._handle.attrs[\"NumMeshBlocks\"]\n\n    def _parse_index(self):\n        num_grids = self._handle.attrs[\"NumMeshBlocks\"]\n\n        self.grid_left_edge = np.zeros((num_grids, 3), dtype=\"float64\")\n        self.grid_right_edge = np.zeros((num_grids, 3), dtype=\"float64\")\n        self.grid_dimensions = np.zeros((num_grids, 3), dtype=\"int32\")\n\n        # TODO: In an unlikely case this would use too much memory, implement\n        #       chunked read along 1 dim\n        x = self._handle[\"x1f\"][:, :].astype(\"float64\")\n        y = self._handle[\"x2f\"][:, :].astype(\"float64\")\n        z = self._handle[\"x3f\"][:, :].astype(\"float64\")\n        dx = np.diff(x, axis=1)\n        dy = np.diff(y, axis=1)\n        dz = np.diff(z, axis=1)\n        mesh_block_size = self._handle.attrs[\"MeshBlockSize\"]\n\n        for i in range(num_grids):\n            self.grid_left_edge[i] = np.array([x[i, 0], y[i, 0], z[i, 0]])\n            self.grid_right_edge[i] = np.array([x[i, -1], y[i, -1], z[i, -1]])\n            self.grid_dimensions[i] = mesh_block_size\n        levels = self._handle[\"Levels\"][:]\n\n        self.grid_left_edge = self.ds.arr(self.grid_left_edge, \"code_length\")\n        self.grid_right_edge = self.ds.arr(self.grid_right_edge, \"code_length\")\n\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n        for i in range(num_grids):\n            if self.dataset._nonuniform:\n                self.grids[i] = self.grid(i, [dx[i], dy[i], dz[i]], self, levels[i])\n            else:\n                self.grids[i] = self.grid(i, self, levels[i])\n\n        if self.dataset.dimensionality <= 2:\n            self.grid_right_edge[:, 2] = self.dataset.domain_right_edge[2]\n        if self.dataset.dimensionality == 1:\n            self.grid_right_edge[:, 1:] = self.dataset.domain_right_edge[1:]\n        self.grid_particle_count = np.zeros([self.num_grids, 1], dtype=\"int64\")\n\n    def _populate_grid_objects(self):\n        for g in self.grids:\n            g._prepare_grid()\n            g._setup_dx()\n        self.max_level = self._handle.attrs[\"MaxLevel\"]\n\n\nclass AthenaPPDataset(Dataset):\n    _field_info_class = AthenaPPFieldInfo\n    _dataset_type = \"athena_pp\"\n    _index_class = AthenaPPHierarchy\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"athena_pp\",\n        storage_filename=None,\n        parameters=None,\n        units_override=None,\n        unit_system=\"code\",\n        default_species_fields=None,\n        magnetic_normalization=\"gaussian\",\n    ):\n        self.fluid_types += (\"athena_pp\",)\n        if parameters is None:\n            parameters = {}\n        self.specified_parameters = parameters\n        if units_override is None:\n            units_override = {}\n        self._handle = HDF5FileHandler(filename)\n        xrat = self._handle.attrs[\"RootGridX1\"][2]\n        yrat = self._handle.attrs[\"RootGridX2\"][2]\n        zrat = self._handle.attrs[\"RootGridX3\"][2]\n        self._nonuniform = xrat != 1.0 or yrat != 1.0 or zrat != 1.0\n        self._magnetic_factor = get_magnetic_normalization(magnetic_normalization)\n\n        geom = self._handle.attrs[\"Coordinates\"].decode(\"utf-8\")\n        self.geometry = Geometry(geom_map[geom])\n        if self.geometry is Geometry.CYLINDRICAL:\n            axis_order = (\"r\", \"theta\", \"z\")\n        else:\n            axis_order = None\n\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n            axis_order=axis_order,\n        )\n        if storage_filename is None:\n            storage_filename = self.basename + \".yt\"\n        self.storage_filename = storage_filename\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Generates the conversion to various physical _units based on the\n        parameter file\n        \"\"\"\n        if \"length_unit\" not in self.units_override:\n            self.no_cgs_equiv_length = True\n        for unit, cgs in [\n            (\"length\", \"cm\"),\n            (\"time\", \"s\"),\n            (\"mass\", \"g\"),\n            (\"temperature\", \"K\"),\n        ]:\n            # We set these to cgs for now, but they may have been overridden\n            if getattr(self, unit + \"_unit\", None) is not None:\n                continue\n            mylog.warning(\"Assuming 1.0 = 1.0 %s\", cgs)\n            setattr(self, f\"{unit}_unit\", self.quan(1.0, cgs))\n\n        self.magnetic_unit = np.sqrt(\n            self._magnetic_factor\n            * self.mass_unit\n            / (self.time_unit**2 * self.length_unit)\n        )\n        self.magnetic_unit.convert_to_units(\"gauss\")\n        self.velocity_unit = self.length_unit / self.time_unit\n\n    def _parse_parameter_file(self):\n        xmin, xmax = self._handle.attrs[\"RootGridX1\"][:2]\n        ymin, ymax = self._handle.attrs[\"RootGridX2\"][:2]\n        zmin, zmax = self._handle.attrs[\"RootGridX3\"][:2]\n\n        self.domain_left_edge = np.array([xmin, ymin, zmin], dtype=\"float64\")\n        self.domain_right_edge = np.array([xmax, ymax, zmax], dtype=\"float64\")\n\n        self.domain_width = self.domain_right_edge - self.domain_left_edge\n        self.domain_dimensions = self._handle.attrs[\"RootGridSize\"]\n\n        self._field_map = {}\n        k = 0\n        for dname, num_var in zip(\n            self._handle.attrs[\"DatasetNames\"],\n            self._handle.attrs[\"NumVariables\"],\n            strict=True,\n        ):\n            for j in range(num_var):\n                fname = self._handle.attrs[\"VariableNames\"][k].decode(\"ascii\", \"ignore\")\n                self._field_map[fname] = (dname.decode(\"ascii\", \"ignore\"), j)\n                k += 1\n\n        self.refine_by = 2\n        dimensionality = 3\n        if self.domain_dimensions[2] == 1:\n            dimensionality = 2\n        if self.domain_dimensions[1] == 1:\n            dimensionality = 1\n        self.dimensionality = dimensionality\n        self.current_time = self._handle.attrs[\"Time\"]\n        self.cosmological_simulation = False\n        self.num_ghost_zones = 0\n        self.field_ordering = \"fortran\"\n        self.boundary_conditions = [1] * 6\n        self._periodicity = tuple(\n            self.specified_parameters.get(\"periodicity\", (True, True, True))\n        )\n        if \"gamma\" in self.specified_parameters:\n            self.gamma = float(self.specified_parameters[\"gamma\"])\n        else:\n            self.gamma = 5.0 / 3.0\n\n        self.current_redshift = 0.0\n        self.omega_lambda = 0.0\n        self.omega_matter = 0.0\n        self.hubble_constant = 0.0\n        self.cosmological_simulation = 0\n        # Hardcode time conversion for now.\n        self.parameters[\"Time\"] = self.current_time\n        # Hardcode for now until field staggering is supported.\n        self.parameters[\"HydroMethod\"] = 0\n        if \"gamma\" in self.specified_parameters:\n            self.parameters[\"Gamma\"] = self.specified_parameters[\"gamma\"]\n        else:\n            self.parameters[\"Gamma\"] = 5.0 / 3.0\n        self.mu = self.specified_parameters.get(\n            \"mu\", compute_mu(self.default_species_fields)\n        )\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        return filename.endswith(\".athdf\")\n\n    @property\n    def _skip_cache(self):\n        return True\n\n    def __str__(self):\n        return self.basename.rsplit(\".\", 1)[0]\n"
  },
  {
    "path": "yt/frontends/athena_pp/definitions.py",
    "content": "\"\"\"\nVarious definitions for various other modules and routines\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/athena_pp/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.utilities.physical_constants import kboltz, mh\n\nb_units = \"code_magnetic\"\npres_units = \"code_mass/(code_length*code_time**2)\"\nrho_units = \"code_mass / code_length**3\"\nvel_units = \"code_length / code_time\"\n\n\ndef velocity_field(j):\n    def _velocity(data):\n        return data[\"athena_pp\", f\"mom{j}\"] / data[\"athena_pp\", \"dens\"]\n\n    return _velocity\n\n\nclass AthenaPPFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"rho\", (rho_units, [\"density\"], None)),\n        (\"dens\", (rho_units, [\"density\"], None)),\n        (\"Bcc1\", (b_units, [], None)),\n        (\"Bcc2\", (b_units, [], None)),\n        (\"Bcc3\", (b_units, [], None)),\n    )\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        unit_system = self.ds.unit_system\n        # Add velocity fields\n        vel_prefix = \"velocity\"\n        for i, comp in enumerate(self.ds.coordinates.axis_order):\n            vel_field = (\"athena_pp\", f\"vel{i + 1}\")\n            mom_field = (\"athena_pp\", f\"mom{i + 1}\")\n            if vel_field in self.field_list:\n                self.add_output_field(\n                    vel_field, sampling_type=\"cell\", units=\"code_length/code_time\"\n                )\n                self.alias(\n                    (\"gas\", f\"{vel_prefix}_{comp}\"),\n                    vel_field,\n                    units=unit_system[\"velocity\"],\n                )\n            elif mom_field in self.field_list:\n                self.add_output_field(\n                    mom_field,\n                    sampling_type=\"cell\",\n                    units=\"code_mass/code_time/code_length**2\",\n                )\n                self.add_field(\n                    (\"gas\", f\"{vel_prefix}_{comp}\"),\n                    sampling_type=\"cell\",\n                    function=velocity_field(i + 1),\n                    units=unit_system[\"velocity\"],\n                )\n        # Figure out thermal energy field\n        if (\"athena_pp\", \"press\") in self.field_list:\n            self.add_output_field(\n                (\"athena_pp\", \"press\"), sampling_type=\"cell\", units=pres_units\n            )\n            self.alias(\n                (\"gas\", \"pressure\"),\n                (\"athena_pp\", \"press\"),\n                units=unit_system[\"pressure\"],\n            )\n\n            def _specific_thermal_energy(data):\n                return (\n                    data[\"athena_pp\", \"press\"]\n                    / (data.ds.gamma - 1.0)\n                    / data[\"athena_pp\", \"rho\"]\n                )\n\n            self.add_field(\n                (\"gas\", \"specific_thermal_energy\"),\n                sampling_type=\"cell\",\n                function=_specific_thermal_energy,\n                units=unit_system[\"specific_energy\"],\n            )\n        elif (\"athena_pp\", \"Etot\") in self.field_list:\n            self.add_output_field(\n                (\"athena_pp\", \"Etot\"), sampling_type=\"cell\", units=pres_units\n            )\n\n            def _specific_thermal_energy(data):\n                eint = data[\"athena_pp\", \"Etot\"] - data[\"gas\", \"kinetic_energy_density\"]\n                if (\"athena_pp\", \"B1\") in self.field_list:\n                    eint -= data[\"gas\", \"magnetic_energy_density\"]\n                return eint / data[\"athena_pp\", \"dens\"]\n\n            self.add_field(\n                (\"gas\", \"specific_thermal_energy\"),\n                sampling_type=\"cell\",\n                function=_specific_thermal_energy,\n                units=unit_system[\"specific_energy\"],\n            )\n\n        # Add temperature field\n        def _temperature(data):\n            return (\n                (data[\"gas\", \"pressure\"] / data[\"gas\", \"density\"])\n                * data.ds.mu\n                * mh\n                / kboltz\n            )\n\n        self.add_field(\n            (\"gas\", \"temperature\"),\n            sampling_type=\"cell\",\n            function=_temperature,\n            units=unit_system[\"temperature\"],\n        )\n\n        setup_magnetic_field_aliases(\n            self, \"athena_pp\", [f\"Bcc{ax}\" for ax in (1, 2, 3)]\n        )\n"
  },
  {
    "path": "yt/frontends/athena_pp/io.py",
    "content": "from itertools import groupby\n\nimport numpy as np\n\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\n\n\n# http://stackoverflow.com/questions/2361945/detecting-consecutive-integers-in-a-list\ndef grid_sequences(grids):\n    g_iter = sorted(grids, key=lambda g: g.id)\n    for _, g in groupby(enumerate(g_iter), lambda i_x1: i_x1[0] - i_x1[1].id):\n        seq = [v[1] for v in g]\n        yield seq\n\n\nclass IOHandlerAthenaPP(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"athena_pp\"\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        self._handle = ds._handle\n\n    def _read_particles(\n        self, fields_to_read, type, args, grid_list, count_list, conv_factors\n    ):\n        pass\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        if any((ftype != \"athena_pp\" for ftype, fname in fields)):\n            raise NotImplementedError\n        f = self._handle\n        rv = {}\n        for field in fields:\n            # Always use *native* 64-bit float.\n            rv[field] = np.empty(size, dtype=\"=f8\")\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s blocks\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n        last_dname = None\n        for field in fields:\n            ftype, fname = field\n            dname, fdi = self.ds._field_map[fname]\n            if dname != last_dname:\n                ds = f[f\"/{dname}\"]\n            ind = 0\n            for chunk in chunks:\n                for gs in grid_sequences(chunk.objs):\n                    start = gs[0].id - gs[0]._id_offset\n                    end = gs[-1].id - gs[-1]._id_offset + 1\n                    data = ds[fdi, start:end, :, :, :].transpose()\n                    for i, g in enumerate(gs):\n                        ind += g.select(selector, data[..., i], rv[field], ind)\n            last_dname = dname\n        return rv\n\n    def _read_chunk_data(self, chunk, fields):\n        f = self._handle\n        rv = {}\n        for g in chunk.objs:\n            rv[g.id] = {}\n        if len(fields) == 0:\n            return rv\n        for field in fields:\n            ftype, fname = field\n            dname, fdi = self.ds._field_map[fname]\n            ds = f[f\"/{dname}\"]\n            for gs in grid_sequences(chunk.objs):\n                start = gs[0].id - gs[0]._id_offset\n                end = gs[-1].id - gs[-1]._id_offset + 1\n                data = ds[fdi, start:end, :, :, :].transpose()\n                for i, g in enumerate(gs):\n                    rv[g.id][field] = np.asarray(data[..., i], \"=f8\")\n        return rv\n"
  },
  {
    "path": "yt/frontends/athena_pp/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/athena_pp/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/athena_pp/tests/test_outputs.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_allclose, assert_equal\n\nfrom yt.frontends.athena_pp.api import AthenaPPDataset\nfrom yt.loaders import load\nfrom yt.testing import (\n    assert_allclose_units,\n    disable_dataset_cache,\n    requires_file,\n    units_override_check,\n)\nfrom yt.units import dimensions\nfrom yt.utilities.answer_testing.framework import (\n    GenericArrayTest,\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\n_fields_disk = (\"density\", \"velocity_r\")\n\ndisk = \"KeplerianDisk/disk.out1.00000.athdf\"\n\n\n@requires_ds(disk)\ndef test_disk():\n    ds = data_dir_load(disk)\n    assert_equal(str(ds), \"disk.out1.00000\")\n    dd = ds.all_data()\n    vol = (ds.domain_right_edge[0] ** 3 - ds.domain_left_edge[0] ** 3) / 3.0\n    vol *= np.cos(ds.domain_left_edge[1]) - np.cos(ds.domain_right_edge[1])\n    vol *= ds.domain_right_edge[2].v - ds.domain_left_edge[2].v\n    assert_allclose(dd.quantities.total_quantity((\"gas\", \"cell_volume\")), vol)\n\n    def field_func(field):\n        return dd[field]\n\n    for field in _fields_disk:\n        yield GenericArrayTest(ds, field_func, args=[field])\n\n\n_fields_AM06 = (\"temperature\", \"density\", \"velocity_magnitude\", \"magnetic_field_x\")\n\nAM06 = \"AM06/AM06.out1.00400.athdf\"\n\n\n@requires_ds(AM06)\ndef test_AM06():\n    ds = data_dir_load(AM06)\n    assert_equal(str(ds), \"AM06.out1.00400\")\n    for test in small_patch_amr(ds, _fields_AM06):\n        test_AM06.__name__ = test.description\n        yield test\n\n\nuo_AM06 = {\n    \"length_unit\": (1.0, \"kpc\"),\n    \"mass_unit\": (1.0, \"Msun\"),\n    \"time_unit\": (1.0, \"Myr\"),\n}\n\n\n@requires_file(AM06)\ndef test_AM06_override():\n    # verify that overriding units causes derived unit values to be updated.\n    # see issue #1259\n    ds = load(AM06, units_override=uo_AM06)\n    assert_equal(float(ds.magnetic_unit.in_units(\"gauss\")), 9.01735778342523e-08)\n\n\n@requires_file(AM06)\ndef test_units_override():\n    units_override_check(AM06)\n\n\n@requires_file(AM06)\ndef test_AthenaPPDataset():\n    assert isinstance(data_dir_load(AM06), AthenaPPDataset)\n\n\n@requires_file(AM06)\ndef test_magnetic_units():\n    ds = load(AM06, unit_system=\"code\")\n    assert ds.magnetic_unit.units.dimensions == dimensions.magnetic_field_cgs\n    assert (ds.magnetic_unit**2).units.dimensions == dimensions.pressure\n\n\n@requires_file(AM06)\n@disable_dataset_cache\ndef test_mag_factor():\n    ds1 = load(AM06, units_override=uo_AM06, magnetic_normalization=\"gaussian\")\n    assert ds1.magnetic_unit == np.sqrt(\n        4.0 * np.pi * ds1.mass_unit / (ds1.time_unit**2 * ds1.length_unit)\n    )\n    sp1 = ds1.sphere(\"c\", (100.0, \"kpc\"))\n    pB1a = (\n        sp1[\"athena_pp\", \"Bcc1\"] ** 2\n        + sp1[\"athena_pp\", \"Bcc2\"] ** 2\n        + sp1[\"athena_pp\", \"Bcc3\"] ** 2\n    ) / (8.0 * np.pi)\n    pB1b = (\n        sp1[\"gas\", \"magnetic_field_x\"] ** 2\n        + sp1[\"gas\", \"magnetic_field_y\"] ** 2\n        + sp1[\"gas\", \"magnetic_field_z\"] ** 2\n    ) / (8.0 * np.pi)\n    pB1a.convert_to_units(\"dyn/cm**2\")\n    pB1b.convert_to_units(\"dyn/cm**2\")\n    assert_allclose_units(pB1a, pB1b)\n    assert_allclose_units(pB1a, sp1[\"magnetic_pressure\"])\n    ds2 = load(AM06, units_override=uo_AM06, magnetic_normalization=\"lorentz_heaviside\")\n    assert ds2.magnetic_unit == np.sqrt(\n        ds2.mass_unit / (ds2.time_unit**2 * ds2.length_unit)\n    )\n    sp2 = ds2.sphere(\"c\", (100.0, \"kpc\"))\n    pB2a = (\n        sp2[\"athena_pp\", \"Bcc1\"] ** 2\n        + sp2[\"athena_pp\", \"Bcc2\"] ** 2\n        + sp2[\"athena_pp\", \"Bcc3\"] ** 2\n    ) / 2.0\n    pB2b = (\n        sp2[\"gas\", \"magnetic_field_x\"] ** 2\n        + sp2[\"gas\", \"magnetic_field_y\"] ** 2\n        + sp2[\"gas\", \"magnetic_field_z\"] ** 2\n    ) / 2.0\n    pB2a.convert_to_units(\"dyn/cm**2\")\n    pB2b.convert_to_units(\"dyn/cm**2\")\n    assert_allclose_units(pB2a, pB2b)\n    assert_allclose_units(pB2a, sp2[\"magnetic_pressure\"])\n    assert_allclose_units(pB1a, pB2a)\n"
  },
  {
    "path": "yt/frontends/boxlib/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/boxlib/_deprecation.py",
    "content": "from yt._maintenance.deprecation import issue_deprecation_warning, warnings\n\n\ndef boxlib_deprecation():\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"always\")\n        issue_deprecation_warning(\n            \"The historic 'boxlib' frontend is \\n\"\n            \"deprecated as it has been renamed 'amrex'. \"\n            \"Existing and future work should instead reference the 'amrex' frontend.\",\n            stacklevel=4,\n            since=\"4.4.0\",\n        )\n"
  },
  {
    "path": "yt/frontends/boxlib/api.py",
    "content": "from ..amrex import tests\nfrom ..amrex import data_structures\nfrom ..amrex.data_structures import (\n    AMReXDataset,\n    AMReXHierarchy,\n    BoxlibDataset,\n    BoxlibGrid,\n    BoxlibHierarchy,\n    CastroDataset,\n    MaestroDataset,\n    NyxDataset,\n    NyxHierarchy,\n    OrionDataset,\n    OrionHierarchy,\n    WarpXDataset,\n    WarpXHierarchy,\n)\nfrom ..amrex.fields import (\n    BoxlibFieldInfo,\n    CastroFieldInfo,\n    MaestroFieldInfo,\n    NyxFieldInfo,\n    WarpXFieldInfo,\n)\nfrom ..amrex.io import IOHandlerBoxlib\n"
  },
  {
    "path": "yt/frontends/boxlib/data_structures/__init__.py",
    "content": "from ...amrex.data_structures import (\n    AMReXDataset,\n    AMReXHierarchy,\n    BoxlibDataset,\n    BoxlibGrid,\n    BoxlibHierarchy,\n    CastroDataset,\n    MaestroDataset,\n    NyxDataset,\n    NyxHierarchy,\n    OrionDataset,\n    OrionHierarchy,\n    WarpXDataset,\n    WarpXHierarchy,\n)\nfrom .._deprecation import boxlib_deprecation\n\nboxlib_deprecation()\n"
  },
  {
    "path": "yt/frontends/boxlib/fields/__init__.py",
    "content": "from ...amrex.fields import (\n    BoxlibFieldInfo,\n    CastroFieldInfo,\n    MaestroFieldInfo,\n    NyxFieldInfo,\n    WarpXFieldInfo,\n)\nfrom .._deprecation import boxlib_deprecation\n\nboxlib_deprecation()\n"
  },
  {
    "path": "yt/frontends/boxlib/io/__init__.py",
    "content": "from ...amrex.io import IOHandlerBoxlib\nfrom .._deprecation import boxlib_deprecation\n\nboxlib_deprecation()\n"
  },
  {
    "path": "yt/frontends/boxlib/tests/__init__.py",
    "content": "from ...amrex import tests\n"
  },
  {
    "path": "yt/frontends/boxlib/tests/test_boxlib_deprecation.py",
    "content": "from importlib import import_module, reload\n\nfrom yt._maintenance.deprecation import warnings\n\n\ndef test_imports():\n    with warnings.catch_warnings(record=True) as w:\n        warnings.simplefilter(\"always\")\n        for index, mname in enumerate([\"data_structures\", \"fields\", \"io\"]):\n            mod_name = import_module(\"yt.frontends.boxlib.\" + mname)\n            if len(w) != index + 1:\n                reload(mod_name)\n\n        assert len(w) == 3 and all(\n            [\n                issubclass(w[0].category, DeprecationWarning),\n                issubclass(w[1].category, DeprecationWarning),\n                issubclass(w[2].category, DeprecationWarning),\n            ]\n        )\n"
  },
  {
    "path": "yt/frontends/boxlib/tests/test_outputs.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_allclose, assert_equal\n\nfrom yt.frontends.boxlib.api import (\n    AMReXDataset,\n    CastroDataset,\n    MaestroDataset,\n    NyxDataset,\n    OrionDataset,\n    WarpXDataset,\n)\nfrom yt.loaders import load\nfrom yt.testing import (\n    disable_dataset_cache,\n    requires_file,\n    units_override_check,\n)\nfrom yt.utilities.answer_testing.framework import (\n    GridValuesTest,\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\n# We don't do anything needing ghost zone generation right now, because these\n# are non-periodic datasets.\n_orion_fields = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_magnitude\"),\n)\n_nyx_fields = (\n    (\"boxlib\", \"Ne\"),\n    (\"boxlib\", \"Temp\"),\n    (\"boxlib\", \"particle_mass_density\"),\n)\n_warpx_fields = ((\"mesh\", \"Ex\"), (\"mesh\", \"By\"), (\"mesh\", \"jz\"))\n_castro_fields = (\n    (\"boxlib\", \"Temp\"),\n    (\"gas\", \"density\"),\n    (\"boxlib\", \"particle_count\"),\n)\n\nradadvect = \"RadAdvect/plt00000\"\n\n\n@requires_ds(radadvect)\ndef test_radadvect():\n    ds = data_dir_load(radadvect)\n    assert_equal(str(ds), \"plt00000\")\n    for test in small_patch_amr(ds, _orion_fields):\n        test_radadvect.__name__ = test.description\n        yield test\n\n\nrt = \"RadTube/plt00500\"\n\n\n@requires_ds(rt)\ndef test_radtube():\n    ds = data_dir_load(rt)\n    assert_equal(str(ds), \"plt00500\")\n    for test in small_patch_amr(ds, _orion_fields):\n        test_radtube.__name__ = test.description\n        yield test\n\n\nstar = \"StarParticles/plrd01000\"\n\n\n@requires_ds(star)\ndef test_star():\n    ds = data_dir_load(star)\n    assert_equal(str(ds), \"plrd01000\")\n    for test in small_patch_amr(ds, _orion_fields):\n        test_star.__name__ = test.description\n        yield test\n\n\nLyA = \"Nyx_LyA/plt00000\"\n\n\n@requires_ds(LyA)\ndef test_LyA():\n    ds = data_dir_load(LyA)\n    assert_equal(str(ds), \"plt00000\")\n    for test in small_patch_amr(\n        ds, _nyx_fields, input_center=\"c\", input_weight=(\"boxlib\", \"Ne\")\n    ):\n        test_LyA.__name__ = test.description\n        yield test\n\n\n@requires_file(LyA)\ndef test_nyx_particle_io():\n    ds = data_dir_load(LyA)\n\n    grid = ds.index.grids[0]\n    npart_grid_0 = 7908  # read directly from the header\n    assert_equal(grid[\"all\", \"particle_position_x\"].size, npart_grid_0)\n    assert_equal(grid[\"DM\", \"particle_position_y\"].size, npart_grid_0)\n    assert_equal(grid[\"all\", \"particle_position_z\"].size, npart_grid_0)\n\n    ad = ds.all_data()\n    npart = 32768  # read directly from the header\n    assert_equal(ad[\"all\", \"particle_velocity_x\"].size, npart)\n    assert_equal(ad[\"DM\", \"particle_velocity_y\"].size, npart)\n    assert_equal(ad[\"all\", \"particle_velocity_z\"].size, npart)\n\n    assert np.all(ad[\"all\", \"particle_mass\"] == ad[\"all\", \"particle_mass\"][0])\n\n    left_edge = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    right_edge = ds.arr([4.0, 4.0, 4.0], \"code_length\")\n    center = 0.5 * (left_edge + right_edge)\n\n    reg = ds.region(center, left_edge, right_edge)\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_x\"] <= right_edge[0],\n            reg[\"all\", \"particle_position_x\"] >= left_edge[0],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_y\"] <= right_edge[1],\n            reg[\"all\", \"particle_position_y\"] >= left_edge[1],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_z\"] <= right_edge[2],\n            reg[\"all\", \"particle_position_z\"] >= left_edge[2],\n        )\n    )\n\n\nRT_particles = \"RT_particles/plt00050\"\n\n\n@requires_ds(RT_particles)\ndef test_RT_particles():\n    ds = data_dir_load(RT_particles)\n    assert_equal(str(ds), \"plt00050\")\n    for test in small_patch_amr(ds, _castro_fields):\n        test_RT_particles.__name__ = test.description\n        yield test\n\n\n@requires_file(RT_particles)\ndef test_castro_particle_io():\n    ds = data_dir_load(RT_particles)\n\n    grid = ds.index.grids[2]\n    npart_grid_2 = 49  # read directly from the header\n    assert_equal(grid[\"all\", \"particle_position_x\"].size, npart_grid_2)\n    assert_equal(grid[\"Tracer\", \"particle_position_y\"].size, npart_grid_2)\n    assert_equal(grid[\"all\", \"particle_position_y\"].size, npart_grid_2)\n\n    ad = ds.all_data()\n    npart = 49  # read directly from the header\n    assert_equal(ad[\"all\", \"particle_velocity_x\"].size, npart)\n    assert_equal(ad[\"Tracer\", \"particle_velocity_y\"].size, npart)\n    assert_equal(ad[\"all\", \"particle_velocity_y\"].size, npart)\n\n    left_edge = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    right_edge = ds.arr([0.25, 1.0, 1.0], \"code_length\")\n    center = 0.5 * (left_edge + right_edge)\n\n    reg = ds.region(center, left_edge, right_edge)\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_x\"] <= right_edge[0],\n            reg[\"all\", \"particle_position_x\"] >= left_edge[0],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_y\"] <= right_edge[1],\n            reg[\"all\", \"particle_position_y\"] >= left_edge[1],\n        )\n    )\n\n\nlangmuir = \"LangmuirWave/plt00020_v2\"\n\n\n@requires_ds(langmuir)\ndef test_langmuir():\n    ds = data_dir_load(langmuir)\n    assert_equal(str(ds), \"plt00020_v2\")\n    for test in small_patch_amr(\n        ds, _warpx_fields, input_center=\"c\", input_weight=(\"mesh\", \"Ex\")\n    ):\n        test_langmuir.__name__ = test.description\n        yield test\n\n\nplasma = \"PlasmaAcceleration/plt00030_v2\"\n\n\n@requires_ds(plasma)\ndef test_plasma():\n    ds = data_dir_load(plasma)\n    assert_equal(str(ds), \"plt00030_v2\")\n    for test in small_patch_amr(\n        ds, _warpx_fields, input_center=\"c\", input_weight=(\"mesh\", \"Ex\")\n    ):\n        test_plasma.__name__ = test.description\n        yield test\n\n\nbeam = \"GaussianBeam/plt03008\"\n\n\n@requires_ds(beam)\ndef test_beam():\n    ds = data_dir_load(beam)\n    assert_equal(str(ds), \"plt03008\")\n    for param in (\"number of boxes\", \"maximum zones\"):\n        # PR 2807\n        # these parameters are only populated if the config file attached to this\n        # dataset is read correctly\n        assert param in ds.parameters\n    for test in small_patch_amr(\n        ds, _warpx_fields, input_center=\"c\", input_weight=(\"mesh\", \"Ex\")\n    ):\n        test_beam.__name__ = test.description\n        yield test\n\n\n@requires_file(plasma)\ndef test_warpx_particle_io():\n    ds = data_dir_load(plasma)\n    grid = ds.index.grids[0]\n\n    # read directly from the header\n    npart0_grid_0 = 344\n    npart1_grid_0 = 69632\n\n    assert_equal(grid[\"particle0\", \"particle_position_x\"].size, npart0_grid_0)\n    assert_equal(grid[\"particle1\", \"particle_position_y\"].size, npart1_grid_0)\n    assert_equal(grid[\"all\", \"particle_position_z\"].size, npart0_grid_0 + npart1_grid_0)\n\n    # read directly from the header\n    npart0 = 1360\n    npart1 = 802816\n    ad = ds.all_data()\n    assert_equal(ad[\"particle0\", \"particle_velocity_x\"].size, npart0)\n    assert_equal(ad[\"particle1\", \"particle_velocity_y\"].size, npart1)\n    assert_equal(ad[\"all\", \"particle_velocity_z\"].size, npart0 + npart1)\n\n    np.all(ad[\"particle1\", \"particle_mass\"] == ad[\"particle1\", \"particle_mass\"][0])\n    np.all(ad[\"particle0\", \"particle_mass\"] == ad[\"particle0\", \"particle_mass\"][0])\n\n    left_edge = ds.arr([-7.5e-5, -7.5e-5, -7.5e-5], \"code_length\")\n    right_edge = ds.arr([2.5e-5, 2.5e-5, 2.5e-5], \"code_length\")\n    center = 0.5 * (left_edge + right_edge)\n\n    reg = ds.region(center, left_edge, right_edge)\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_x\"] <= right_edge[0],\n            reg[\"all\", \"particle_position_x\"] >= left_edge[0],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_y\"] <= right_edge[1],\n            reg[\"all\", \"particle_position_y\"] >= left_edge[1],\n        )\n    )\n\n    assert np.all(\n        np.logical_and(\n            reg[\"all\", \"particle_position_z\"] <= right_edge[2],\n            reg[\"all\", \"particle_position_z\"] >= left_edge[2],\n        )\n    )\n\n\n_raw_fields = [(\"raw\", \"Bx\"), (\"raw\", \"Ey\"), (\"raw\", \"jz\")]\n\nlaser = \"Laser/plt00015\"\n\n\n@requires_ds(laser)\ndef test_raw_fields():\n    for field in _raw_fields:\n        yield GridValuesTest(laser, field)\n\n\n@requires_file(rt)\ndef test_OrionDataset():\n    assert isinstance(data_dir_load(rt), OrionDataset)\n\n\n@requires_file(LyA)\ndef test_NyxDataset():\n    assert isinstance(data_dir_load(LyA), NyxDataset)\n\n\n@requires_file(\"nyx_small/nyx_small_00000\")\ndef test_NyxDataset_2():\n    assert isinstance(data_dir_load(\"nyx_small/nyx_small_00000\"), NyxDataset)\n\n\n@requires_file(RT_particles)\ndef test_CastroDataset():\n    assert isinstance(data_dir_load(RT_particles), CastroDataset)\n\n\n@requires_file(\"castro_sod_x_plt00036\")\ndef test_CastroDataset_2():\n    assert isinstance(data_dir_load(\"castro_sod_x_plt00036\"), CastroDataset)\n\n\n@requires_file(\"castro_sedov_1d_cyl_plt00150\")\ndef test_CastroDataset_3():\n    assert isinstance(data_dir_load(\"castro_sedov_1d_cyl_plt00150\"), CastroDataset)\n\n\n@requires_file(plasma)\ndef test_WarpXDataset():\n    assert isinstance(data_dir_load(plasma), WarpXDataset)\n\n\n@disable_dataset_cache\n@requires_file(plasma)\ndef test_magnetic_units():\n    ds1 = load(plasma)\n    assert_allclose(ds1.magnetic_unit.value, 1.0)\n    assert str(ds1.magnetic_unit.units) == \"T\"\n    mag_unit1 = ds1.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mag_unit1.value, 1.0)\n    assert str(mag_unit1.units) == \"code_magnetic\"\n    ds2 = load(plasma, unit_system=\"cgs\")\n    assert_allclose(ds2.magnetic_unit.value, 1.0e4)\n    assert str(ds2.magnetic_unit.units) == \"G\"\n    mag_unit2 = ds2.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mag_unit2.value, 1.0)\n    assert str(mag_unit2.units) == \"code_magnetic\"\n\n\n@requires_ds(laser)\ndef test_WarpXDataset_2():\n    assert isinstance(data_dir_load(laser), WarpXDataset)\n\n\n@requires_file(\"plt.Cavity00010\")\ndef test_AMReXDataset():\n    ds = data_dir_load(\"plt.Cavity00010\", kwargs={\"cparam_filename\": \"inputs\"})\n    assert isinstance(ds, AMReXDataset)\n\n\n@requires_file(rt)\ndef test_units_override():\n    units_override_check(rt)\n\n\nnyx_no_particles = \"nyx_sedov_plt00086\"\n\n\n@requires_file(nyx_no_particles)\ndef test_nyx_no_part():\n    assert isinstance(data_dir_load(nyx_no_particles), NyxDataset)\n\n    fields = sorted(\n        [\n            (\"boxlib\", \"H\"),\n            (\"boxlib\", \"He\"),\n            (\"boxlib\", \"MachNumber\"),\n            (\"boxlib\", \"Ne\"),\n            (\"boxlib\", \"Rank\"),\n            (\"boxlib\", \"StateErr\"),\n            (\"boxlib\", \"Temp\"),\n            (\"boxlib\", \"X(H)\"),\n            (\"boxlib\", \"X(He)\"),\n            (\"boxlib\", \"density\"),\n            (\"boxlib\", \"divu\"),\n            (\"boxlib\", \"eint_E\"),\n            (\"boxlib\", \"eint_e\"),\n            (\"boxlib\", \"entropy\"),\n            (\"boxlib\", \"forcex\"),\n            (\"boxlib\", \"forcey\"),\n            (\"boxlib\", \"forcez\"),\n            (\"boxlib\", \"kineng\"),\n            (\"boxlib\", \"logden\"),\n            (\"boxlib\", \"magmom\"),\n            (\"boxlib\", \"magvel\"),\n            (\"boxlib\", \"magvort\"),\n            (\"boxlib\", \"pressure\"),\n            (\"boxlib\", \"rho_E\"),\n            (\"boxlib\", \"rho_H\"),\n            (\"boxlib\", \"rho_He\"),\n            (\"boxlib\", \"rho_e\"),\n            (\"boxlib\", \"soundspeed\"),\n            (\"boxlib\", \"x_velocity\"),\n            (\"boxlib\", \"xmom\"),\n            (\"boxlib\", \"y_velocity\"),\n            (\"boxlib\", \"ymom\"),\n            (\"boxlib\", \"z_velocity\"),\n            (\"boxlib\", \"zmom\"),\n        ]\n    )\n\n    ds = data_dir_load(nyx_no_particles)\n    assert_equal(sorted(ds.field_list), fields)\n\n\nmsubch = \"maestro_subCh_plt00248\"\n\n\n@requires_file(msubch)\ndef test_maestro_parameters():\n    assert isinstance(data_dir_load(msubch), MaestroDataset)\n    ds = data_dir_load(msubch)\n\n    # Check a string parameter\n    assert ds.parameters[\"plot_base_name\"] == \"subCh_hot_baserun_plt\"\n    assert type(ds.parameters[\"plot_base_name\"]) is str  # noqa: E721\n\n    # Check boolean parameters: T or F\n    assert not ds.parameters[\"use_thermal_diffusion\"]\n    assert type(ds.parameters[\"use_thermal_diffusion\"]) is bool  # noqa: E721\n\n    assert ds.parameters[\"do_burning\"]\n    assert type(ds.parameters[\"do_burning\"]) is bool  # noqa: E721\n\n    # Check a float parameter with a decimal point\n    assert ds.parameters[\"sponge_kappa\"] == float(\"10.00000000\")\n    assert type(ds.parameters[\"sponge_kappa\"]) is float  # noqa: E721\n\n    # Check a float parameter with E exponent notation\n    assert ds.parameters[\"small_dt\"] == float(\"0.1000000000E-09\")\n\n    # Check an int parameter\n    assert ds.parameters[\"s0_interp_type\"] == 3\n    assert type(ds.parameters[\"s0_interp_type\"]) is int  # noqa: E721\n\n\ncastro_1d_cyl = \"castro_sedov_1d_cyl_plt00150\"\n\n\n@requires_file(castro_1d_cyl)\ndef test_castro_parameters():\n    ds = data_dir_load(castro_1d_cyl)\n    assert isinstance(ds, CastroDataset)\n\n    # Modified from default (leading [*])\n    assert ds.parameters[\"castro.do_hydro\"] == 1\n    assert ds.parameters[\"castro.cfl\"] == 0.5\n    assert ds.parameters[\"problem.p_ambient\"] == float(\"1e-06\")\n    # Leading [*] should be removed from the parameter name\n    assert \"[*] castro.do_hydro\" not in ds.parameters\n\n    # Not modified from default\n    assert ds.parameters[\"castro.pslope_cutoff_density\"] == float(\"-1e+20\")\n    assert ds.parameters[\"castro.do_sponge\"] == 0\n    assert ds.parameters[\"problem.dens_ambient\"] == 1\n    assert ds.parameters[\"eos.eos_assume_neutral\"] == 1\n\n    # Empty string value\n    assert ds.parameters[\"castro.stopping_criterion_field\"] is None\n"
  },
  {
    "path": "yt/frontends/cf_radial/__init__.py",
    "content": "\"\"\"\nAPI for yt.frontends.cf_radial\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/cf_radial/api.py",
    "content": "\"\"\"\nAPI for yt.frontends.cf_radial\n\n\n\n\"\"\"\n\nfrom .data_structures import CFRadialDataset, CFRadialGrid, CFRadialHierarchy\nfrom .fields import CFRadialFieldInfo\nfrom .io import CFRadialIOHandler\n"
  },
  {
    "path": "yt/frontends/cf_radial/data_structures.py",
    "content": "\"\"\"\nCF Radial data structures\n\n\n\n\"\"\"\n\nimport contextlib\nimport os\nimport weakref\n\nimport numpy as np\nfrom unyt import unyt_array\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import mylog\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.file_handler import NetCDF4FileHandler, valid_netcdf_signature\nfrom yt.utilities.on_demand_imports import _xarray as xr\n\nfrom .fields import CFRadialFieldInfo\n\n\nclass CFRadialGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level, dimensions):\n        super().__init__(id, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n        self.ActiveDimensions = dimensions\n\n\nclass CFRadialHierarchy(GridIndex):\n    grid = CFRadialGrid\n\n    def __init__(self, ds, dataset_type=\"cf_radial\"):\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        # our index file is the dataset itself:\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        # float type for the simulation edges and must be float64 now\n        self.float_type = np.float64\n        super().__init__(ds, dataset_type)\n\n    def _detect_output_fields(self):\n        # This sets self.field_list, containing all the available on-disk fields and\n        # records the units for each field.\n        self.field_list = []\n        units = {}\n        with self.ds._handle() as xr_ds_handle:\n            for key in xr_ds_handle.variables.keys():\n                if all(x in xr_ds_handle[key].dims for x in [\"time\", \"z\", \"y\", \"x\"]):\n                    fld = (\"cf_radial\", key)\n                    self.field_list.append(fld)\n                    units[fld] = xr_ds_handle[key].units\n\n        self.ds.field_units.update(units)\n\n    def _count_grids(self):\n        self.num_grids = 1\n\n    def _parse_index(self):\n        self.grid_left_edge[0][:] = self.ds.domain_left_edge[:]\n        self.grid_right_edge[0][:] = self.ds.domain_right_edge[:]\n        self.grid_dimensions[0][:] = self.ds.domain_dimensions[:]\n        self.grid_particle_count[0][0] = 0\n        self.grid_levels[0][0] = 0\n        self.max_level = 0\n\n    def _populate_grid_objects(self):\n        # only a single grid, no need to loop\n        g = self.grid(0, self, self.grid_levels.flat[0], self.grid_dimensions[0])\n        g._prepare_grid()\n        g._setup_dx()\n        self.grids = np.array([g], dtype=\"object\")\n\n\nclass CFRadialDataset(Dataset):\n    _load_requirements = [\"xarray\", \"pyart\"]\n    _index_class = CFRadialHierarchy\n    _field_info_class = CFRadialFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"cf_radial\",\n        storage_filename=None,\n        storage_overwrite: bool = False,\n        grid_shape: tuple[int, int, int] | None = None,\n        grid_limit_x: tuple[float, float] | None = None,\n        grid_limit_y: tuple[float, float] | None = None,\n        grid_limit_z: tuple[float, float] | None = None,\n        units_override=None,\n    ):\n        \"\"\"\n\n        Parameters\n        ----------\n        filename\n        dataset_type\n        storage_filename: Optional[str]\n            the filename to store gridded file to if necessary. If not provided,\n            the string \"_yt_grid\" will be appended to the dataset filename.\n        storage_overwrite: bool\n            if True and if any gridding parameters are set, then the\n            storage_filename will be over-written if it exists. Default is False.\n        grid_shape : Optional[Tuple[int, int, int]]\n            when gridding to cartesian, grid_shape is the number of cells in the\n            z, y, x coordinates. If not provided, yt attempts to calculate a\n            reasonable shape based on the resolution of the original cfradial grid\n        grid_limit_x : Optional[Tuple[float, float]]\n            The x range of the cartesian-gridded data in the form (xmin, xmax) with\n            x in the native radar range units\n        grid_limit_y : Optional[Tuple[float, float]]\n            The y range of the cartesian-gridded data in the form (ymin, ymax) with\n            y in the native radar range units\n        grid_limit_z : Optional[Tuple[float, float]]\n            The z range of the cartesian-gridded data in the form (zmin, zmax) with\n            z in the native radar range units\n        units_override\n        \"\"\"\n        self.fluid_types += (\"cf_radial\",)\n\n        with self._handle(filename=filename) as xr_ds_handle:\n            if \"x\" not in xr_ds_handle.coords:\n                if storage_filename is None:\n                    f_base, f_ext = os.path.splitext(filename)\n                    storage_filename = f_base + \"_yt_grid\" + f_ext\n\n                regrid = True\n                if os.path.exists(storage_filename):\n                    # pyart grid.write will error if the filename exists, so this logic\n                    # forces some explicit behavior to minimize confusion and avoid\n                    # overwriting or deleting without explicit user consent.\n                    if storage_overwrite:\n                        os.remove(storage_filename)\n                    elif any([grid_shape, grid_limit_x, grid_limit_y, grid_limit_z]):\n                        mylog.warning(\n                            \"Ignoring provided grid parameters because %s exists.\",\n                            storage_filename,\n                        )\n                        mylog.warning(\n                            \"To re-grid, either provide a unique storage_filename or set \"\n                            \"storage_overwrite to True to overwrite %s.\",\n                            storage_filename,\n                        )\n                        regrid = False\n                    else:\n                        mylog.info(\n                            \"loading existing re-gridded file: %s\", storage_filename\n                        )\n                        regrid = False\n\n                if regrid:\n                    mylog.info(\"Building cfradial grid\")\n                    from yt.utilities.on_demand_imports import _pyart as pyart\n\n                    radar = pyart.io.read_cfradial(filename)\n\n                    grid_limit_z = self._validate_grid_dim(radar, \"z\", grid_limit_z)\n                    grid_limit_x = self._validate_grid_dim(radar, \"x\", grid_limit_x)\n                    grid_limit_y = self._validate_grid_dim(radar, \"y\", grid_limit_y)\n                    grid_limits = (grid_limit_z, grid_limit_y, grid_limit_x)\n                    grid_shape = self._validate_grid_shape(grid_shape)\n                    # note: grid_shape must be in (z, y, x) order.\n\n                    self.grid_shape = grid_shape\n                    self.grid_limits = grid_limits\n                    mylog.info(\"Calling pyart.map.grid_from_radars ... \")\n                    # this is fairly slow\n                    grid = pyart.map.grid_from_radars(\n                        (radar,),\n                        grid_shape=self.grid_shape,\n                        grid_limits=self.grid_limits,\n                    )\n                    mylog.info(\n                        \"Successfully built cfradial grid, writing to %s\",\n                        storage_filename,\n                    )\n                    mylog.info(\n                        \"Subsequent loads of %s will load the gridded file by default\",\n                        filename,\n                    )\n                    grid.write(storage_filename)\n\n                filename = storage_filename\n\n        super().__init__(filename, dataset_type, units_override=units_override)\n        self.storage_filename = storage_filename\n        self.refine_by = 2  # refinement factor between a grid and its subgrid\n\n    @contextlib.contextmanager\n    def _handle(self, filename: str | None = None):\n        if filename is None:\n            if hasattr(self, \"filename\"):\n                filename = self.filename\n            else:\n                raise RuntimeError(\"Dataset has no filename yet.\")\n\n        with xr.open_dataset(filename) as xrds:\n            yield xrds\n\n    def _validate_grid_dim(\n        self, radar, dim: str, grid_limit: tuple[float, float] | None = None\n    ) -> tuple[float, float]:\n        if grid_limit is None:\n            if dim.lower() == \"z\":\n                gate_alt = radar.gate_altitude[\"data\"]\n                gate_alt_units = radar.gate_altitude[\"units\"]\n                grid_limit = (gate_alt.min(), gate_alt.max())\n                grid_limit = self._round_grid_guess(grid_limit, gate_alt_units)\n                mylog.info(\n                    \"grid_limit_z not provided, using max height range in data: (%f, %f)\",\n                    *grid_limit,\n                )\n            else:\n                max_range = radar.range[\"data\"].max()\n                grid_limit = self._round_grid_guess(\n                    (-max_range, max_range), radar.range[\"units\"]\n                )\n                mylog.info(\n                    \"grid_limit_%s not provided, using max horizontal range in data: (%f, %f)\",\n                    dim,\n                    *grid_limit,\n                )\n        if len(grid_limit) != 2:\n            raise ValueError(\n                f\"grid_limit_{dim} must have 2 dimensions, but it has {len(grid_limit)}\"\n            )\n\n        return grid_limit\n\n    def _validate_grid_shape(\n        self, grid_shape: tuple[int, int, int] | None = None\n    ) -> tuple[int, int, int]:\n        if grid_shape is None:\n            grid_shape = (100, 100, 100)\n            mylog.info(\n                \"grid_shape not provided, using (nz, ny, nx) = (%i, %i, %i)\",\n                *grid_shape,\n            )\n        if len(grid_shape) != 3:\n            raise ValueError(\n                f\"grid_shape must have 3 dimensions, but it has {len(grid_shape)}\"\n            )\n        return grid_shape\n\n    def _round_grid_guess(self, bounds: tuple[float, float], unit_str: str):\n        # rounds the bounds to the closest 10 km increment that still contains\n        # the grid_limit\n        for findstr, repstr in self._field_info_class.unit_subs:\n            unit_str = unit_str.replace(findstr, repstr)\n        limits = unyt_array(bounds, unit_str).to(\"km\")\n        limits[0] = np.floor(limits[0] / 10.0) * 10.0\n        limits[1] = np.ceil(limits[1] / 10.0) * 10.0\n        return tuple(limits.to(unit_str).tolist())\n\n    def _set_code_unit_attributes(self):\n        with self._handle() as xr_ds_handle:\n            length_unit = xr_ds_handle.variables[\"x\"].attrs[\"units\"]\n        self.length_unit = self.quan(1.0, length_unit)\n        self.mass_unit = self.quan(1.0, \"kg\")\n        self.time_unit = self.quan(1.0, \"s\")\n\n    def _parse_parameter_file(self):\n        self.parameters = {}\n\n        with self._handle() as xr_ds_handle:\n            x, y, z = (xr_ds_handle.coords[d] for d in \"xyz\")\n\n            self.domain_left_edge = np.array([x.min(), y.min(), z.min()])\n            self.domain_right_edge = np.array([x.max(), y.max(), z.max()])\n            self.dimensionality = 3\n            dims = [xr_ds_handle.sizes[d] for d in \"xyz\"]\n            self.domain_dimensions = np.array(dims, dtype=\"int64\")\n            self._periodicity = (False, False, False)\n\n            # note: origin_latitude and origin_longitude arrays will have time\n            # as a dimension and the initial implementation here only handles\n            # the first index. Also, the time array may have a datetime dtype,\n            # so cast to float.\n            self.origin_latitude = xr_ds_handle.origin_latitude[0]\n            self.origin_longitude = xr_ds_handle.origin_longitude[0]\n            self.current_time = float(xr_ds_handle.time.values[0])\n\n        # Cosmological information set to zero (not in space).\n        self.cosmological_simulation = 0\n        self.current_redshift = 0.0\n        self.omega_lambda = 0.0\n        self.omega_matter = 0.0\n        self.hubble_constant = 0.0\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        # This accepts a filename or a set of arguments and returns True or\n        # False depending on if the file is of the type requested.\n        if not valid_netcdf_signature(filename):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        is_cfrad = False\n        try:\n            # note that we use the NetCDF4FileHandler here to avoid some\n            # issues with xarray opening datasets it cannot handle. Once\n            # a dataset is as identified as a CFRadialDataset, xarray is used\n            # for opening. See https://github.com/yt-project/yt/issues/3987\n            nc4_file = NetCDF4FileHandler(filename)\n            with nc4_file.open_ds(keepweakref=True) as ds:\n                con = \"Conventions\"  # the attribute to check for file conventions\n                # note that the attributes here are potentially space- or\n                # comma-delimited strings, so we concatenate a single string\n                # to search for a substring.\n                cons = \"\"  # the value of the Conventions attribute\n                for c in [con, con.lower(), \"Sub_\" + con.lower()]:\n                    if hasattr(ds, c):\n                        cons += getattr(ds, c)\n                is_cfrad = \"CF/Radial\" in cons or \"CF-Radial\" in cons\n        except (OSError, AttributeError, ImportError):\n            return False\n\n        return is_cfrad\n"
  },
  {
    "path": "yt/frontends/cf_radial/fields.py",
    "content": "\"\"\"\nCF-radial-specific fields\n\n\n\n\"\"\"\n\nfrom yt.fields.field_info_container import FieldInfoContainer\n\n\nclass CFRadialFieldInfo(FieldInfoContainer):\n    known_other_fields = ()  # fields are set dynamically\n    known_particle_fields = ()\n    units_to_ignore = (\"dBz\", \"dBZ\", \"ratio\")  # set as nondimensional if found\n    field_units_ignored: list[str] = []  # fields for which units have been ignored\n\n    # (find, replace) pairs for sanitizing:\n    unit_subs = ((\"degrees\", \"degree\"), (\"meters\", \"m\"), (\"_per_\", \"/\"))\n\n    def setup_fluid_fields(self):\n        # Here we dynamically add fields available in our netcdf file for to the\n        # FieldInfoContainer with sanitized units.\n\n        for field in self.field_list:  # field here is ('fluid_type', 'field') tuple\n            units = self.ds.field_units.get(field, \"\")\n\n            # sanitization of the units\n            if units in self.units_to_ignore:\n                self.field_units_ignored.append(field)\n                units = \"\"\n\n            for findstr, repstr in self.unit_subs:\n                units = units.replace(findstr, repstr)\n\n            self.add_output_field(field, \"cell\", units=units)\n"
  },
  {
    "path": "yt/frontends/cf_radial/io.py",
    "content": "\"\"\"\nCF-Radial-specific IO functions\n\n\n\n\"\"\"\n\nimport numpy as np\n\nfrom yt.utilities.io_handler import BaseIOHandler\n\n\nclass CFRadialIOHandler(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"cf_radial\"\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        # This needs to allocate a set of arrays inside a dictionary, where the\n        # keys are the (ftype, fname) tuples and the values are arrays that\n        # have been masked using whatever selector method is appropriate.  The\n        # dict gets returned at the end and it should be flat, with selected\n        # data.\n\n        rv = {field: np.empty(size, dtype=\"float64\") for field in fields}\n\n        offset = 0\n\n        with self.ds._handle() as xr_ds_handle:\n            for field in fields:\n                for chunk in chunks:\n                    for grid in chunk.objs:\n                        variable = xr_ds_handle.variables[field[1]]\n                        data = variable.values[0, ...].T\n                        offset += grid.select(selector, data, rv[field], offset)\n        return rv\n"
  },
  {
    "path": "yt/frontends/cf_radial/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/cf_radial/tests/test_cf_radial_pytest.py",
    "content": "from pathlib import Path\n\nimport numpy as np\n\nimport yt\nfrom yt.frontends.cf_radial.api import CFRadialDataset\nfrom yt.testing import requires_module_pytest as requires_module\n\n\ndef create_cf_radial_mock_gridded_ds(savedir: Path) -> Path:\n    # a minimal dataset that yt should pick up as a CfRadial dataset\n    import xarray as xr\n\n    file_to_save = savedir / \"mock_gridded_cfradial.nc\"\n\n    shp = (16, 16, 16)\n    xyz = {dim: np.linspace(0.0, 1.0, shp[idim]) for idim, dim in enumerate(\"xyz\")}\n    da = xr.DataArray(data=np.ones(shp), coords=xyz, name=\"reflectivity\")\n    ds_xr = da.to_dataset()\n    ds_xr.attrs[\"conventions\"] = \"CF/Radial\"\n    ds_xr.x.attrs[\"units\"] = \"m\"\n    ds_xr.y.attrs[\"units\"] = \"m\"\n    ds_xr.z.attrs[\"units\"] = \"m\"\n    ds_xr.reflectivity.attrs[\"units\"] = \"\"\n\n    times = np.array([\"2017-05-19T01\", \"2017-05-19T01:01\"], dtype=\"datetime64[ns]\")\n    ds_xr = ds_xr.assign_coords({\"time\": times})\n    ds_xr[\"origin_latitude\"] = xr.DataArray(np.zeros_like(times), dims=(\"time\",))\n    ds_xr[\"origin_longitude\"] = xr.DataArray(np.zeros_like(times), dims=(\"time\",))\n\n    ds_xr.to_netcdf(file_to_save, engine=\"netcdf4\")\n\n    return file_to_save\n\n\n@requires_module(\"xarray\", \"netCDF4\", \"pyart\")\ndef test_load_mock_gridded_cf_radial(tmp_path):\n    import xarray as xr\n\n    test_file = create_cf_radial_mock_gridded_ds(tmp_path)\n    assert test_file.exists()\n\n    # make sure that the mock dataset is valid and can be re-loaded with xarray\n    with xr.open_dataset(test_file) as ds_xr:\n        assert \"CF/Radial\" in ds_xr.conventions\n\n    test_file = create_cf_radial_mock_gridded_ds(tmp_path)\n\n    ds_yt = yt.load(test_file)\n    assert isinstance(ds_yt, CFRadialDataset)\n"
  },
  {
    "path": "yt/frontends/cf_radial/tests/test_outputs.py",
    "content": "\"\"\"\nCF-Radial frontend tests\n\n\n\n\"\"\"\n\nimport os\nimport shutil\nimport tempfile\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.frontends.cf_radial.data_structures import CFRadialDataset\nfrom yt.testing import (\n    requires_file,\n    requires_module,\n    units_override_check,\n)\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\ncf = \"CfRadialGrid/grid1.nc\"  # an already gridded cfradial file\ncf_nongridded = (\n    \"CfRadialGrid/swx_20120520_0641.nc\"  # cfradial file without cartesian grid\n)\n_fields_cfradial = [\"reflectivity\", \"velocity\", \"gate_id\", \"differential_phase\", \"ROI\"]\n\n_fields_units = {\n    \"reflectivity\": \"dimensionless\",\n    \"velocity\": \"m/s\",\n    \"differential_phase\": \"degree\",\n    \"gate_id\": \"dimensionless\",\n    \"ROI\": \"m\",\n}\n\n\n@requires_module(\"xarray\")\n@requires_file(cf)\ndef test_units_override():\n    units_override_check(cf)\n\n\n@requires_module(\"xarray\")\n@requires_file(cf)\ndef test_cf_radial_gridded():\n    ds = data_dir_load(cf)\n    assert isinstance(ds, CFRadialDataset)\n\n    check_domain(ds)\n    check_origin_latitude_longitude(ds)\n    ad = ds.all_data()\n\n    for field in _fields_cfradial:\n        check_fields(ds, field)\n        check_field_units(ad, field, _fields_units[field])\n\n\ndef check_fields(ds, field):\n    assert (\"cf_radial\", field) in ds.field_list\n    with ds._handle() as xr_ds_handle:\n        assert field in xr_ds_handle.variables.keys()\n\n\ndef check_field_units(ad, field, value):\n    assert str(ad[\"cf_radial\", field].units) == value\n\n\ndef check_origin_latitude_longitude(ds):\n    assert_almost_equal(ds.origin_latitude.values, 36.49120001)\n    assert_almost_equal(ds.origin_longitude.values, -97.5939)\n\n\ndef check_domain(ds):\n    domain_dim_array = [251, 251, 46]\n    assert_equal(ds.domain_dimensions, domain_dim_array)\n\n    domain_center_array = [0.0, 0.0, 7500.0]\n    assert_equal(ds.domain_center, domain_center_array)\n\n    domain_left_array = [-50000.0, -50000.0, 0.0]\n    assert_equal(ds.domain_left_edge, domain_left_array)\n\n    domain_right_array = [50000.0, 50000.0, 15000.0]\n    assert_equal(ds.domain_right_edge, domain_right_array)\n\n\n@requires_module(\"xarray\")\n@requires_file(cf_nongridded)\ndef test_auto_gridding():\n    # loads up a radial dataset, which triggers the gridding.\n\n    # create temporary directory and grid file\n    tempdir = tempfile.mkdtemp()\n    grid_file = os.path.join(tempdir, \"temp_grid.nc\")\n\n    # this load will trigger the re-gridding and write out the gridded file\n    # from which data will be loaded. With default gridding params, this takes\n    # on the order of 10s, but since we are not testing actual output here, we\n    # can decrease the resolution to speed it up.\n    grid_shape = (10, 10, 10)\n    ds = data_dir_load(\n        cf_nongridded, kwargs={\"storage_filename\": grid_file, \"grid_shape\": grid_shape}\n    )\n    assert os.path.exists(grid_file)\n\n    # check that the cartesian fields exist now\n    with ds._handle() as xr_ds_handle:\n        on_disk_fields = xr_ds_handle.variables.keys()\n        for field in [\"x\", \"y\", \"z\"]:\n            assert field in on_disk_fields\n\n    assert all(ds.domain_dimensions == grid_shape)\n\n    # check that we can load the gridded file too\n    ds = data_dir_load(grid_file)\n    assert isinstance(ds, CFRadialDataset)\n\n    shutil.rmtree(tempdir)\n\n\n@requires_module(\"xarray\")\n@requires_file(cf_nongridded)\ndef test_grid_parameters():\n    # checks that the gridding parameters are used and that conflicts in parameters\n    # are resolved as expected.\n    tempdir = tempfile.mkdtemp()\n    grid_file = os.path.join(tempdir, \"temp_grid_params.nc\")\n\n    # check that the grid parameters work\n    cfkwargs = {\n        \"storage_filename\": grid_file,\n        \"grid_shape\": (10, 10, 10),\n        \"grid_limit_x\": (-10000, 10000),\n        \"grid_limit_y\": (-10000, 10000),\n        \"grid_limit_z\": (500, 20000),\n    }\n    ds = data_dir_load(cf_nongridded, kwargs=cfkwargs)\n\n    expected_width = []\n    for dim in \"xyz\":\n        minval, maxval = cfkwargs[f\"grid_limit_{dim}\"]\n        expected_width.append(maxval - minval)\n    expected_width = np.array(expected_width)\n\n    actual_width = ds.domain_width.to_value(\"m\")\n    assert all(expected_width == actual_width)\n    assert all(ds.domain_dimensions == cfkwargs[\"grid_shape\"])\n\n    # check the grid parameter conflicts\n\n    # on re-load with default grid params it will reload storage_filename if\n    # it exists. Just checking that this runs...\n    _ = data_dir_load(cf_nongridded, kwargs={\"storage_filename\": grid_file})\n\n    # if storage_filename exists, grid parameters are ignored (with a warning)\n    # and the domain_dimensions will match the original\n    new_kwargs = {\"storage_filename\": grid_file, \"grid_shape\": (15, 15, 15)}\n    ds = data_dir_load(cf_nongridded, kwargs=new_kwargs)\n    assert all(ds.domain_dimensions == cfkwargs[\"grid_shape\"])\n\n    # if we overwrite, the regridding should run and the dimensions should match\n    # the desired dimensions\n    new_kwargs[\"storage_overwrite\"] = True\n    ds = data_dir_load(cf_nongridded, kwargs=new_kwargs)\n    assert all(ds.domain_dimensions == new_kwargs[\"grid_shape\"])\n\n    shutil.rmtree(tempdir)\n\n\n@requires_module(\"xarray\")\n@requires_ds(cf)\ndef test_cfradial_grid_field_values():\n    ds = data_dir_load(cf)\n    fields_to_check = [(\"cf_radial\", field) for field in _fields_cfradial]\n    wtfield = (\"cf_radial\", \"reflectivity\")\n    for test in small_patch_amr(\n        ds, fields_to_check, input_center=ds.domain_center, input_weight=wtfield\n    ):\n        test_cfradial_grid_field_values.__name__ = test.description\n        yield test\n"
  },
  {
    "path": "yt/frontends/chimera/__init__.py",
    "content": "\"\"\"\nAPI for yt.frontends.chimera\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/chimera/api.py",
    "content": "\"\"\"\nAPI for yt.frontends.chimera\n\n\n\n\"\"\"\n\nfrom .data_structures import ChimeraDataset\nfrom .fields import ChimeraFieldInfo\nfrom .io import ChimeraIOHandler\n"
  },
  {
    "path": "yt/frontends/chimera/data_structures.py",
    "content": "\"\"\"\nChimera data structures\n\n\n\n\"\"\"\n\nimport os\nimport re\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.unstructured_mesh import SemiStructuredMesh\nfrom yt.data_objects.static_output import Dataset\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.geometry_handler import YTDataChunk\nfrom yt.geometry.unstructured_mesh_handler import UnstructuredIndex\nfrom yt.utilities.file_handler import HDF5FileHandler\nfrom yt.utilities.io_handler import io_registry\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import ChimeraFieldInfo\n\n\nclass ChimeraMesh(SemiStructuredMesh):\n    _index_offset = 0\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\ndef _find_files(filename_c):\n    # Returns a list of all files that share a frame number with the input\n    dirname, file = os.path.split(filename_c)\n    match = re.match(r\"chimera_\\d+_grid\", file)\n    if match is None:\n        raise RuntimeError(\n            rf\"Expected filename to be of form 'chimera_\\d+_grid_*', got {file!r}\"\n        )\n    prefix = match.group()\n    frames = [f for f in os.listdir(dirname) if f.startswith(prefix)]\n    index_filenames = [os.path.join(dirname, f) for f in sorted(frames)]\n    return index_filenames\n\n\nclass ChimeraUNSIndex(UnstructuredIndex):\n    def __init__(self, ds, dataset_type=\"chimera\"):\n        self._handle = ds._handle\n        super().__init__(ds, dataset_type)\n        self.directory = os.path.dirname(self.dataset.filename)\n        self.dataset_type = dataset_type\n\n    def _initialize_mesh(self):\n        self.meshes = []\n        index_filenames = _find_files(\n            self.dataset.filename\n        )  # Retrieves list of all datafiles with the same frame number\n        # Detects Yin-Yang data format\n        yy = any(\"grid_2\" in file for file in index_filenames)\n        for n, file in enumerate(index_filenames):\n            with h5py.File(file, \"r\") as f:\n                nmx, nmy, nmz = tuple(f[\"mesh\"][\"array_dimensions\"][:])\n                l = (\n                    int(file[-5:-3]) - 1\n                )  # Pulls the subgrid number from the data file name\n                if nmz > 2:\n                    k = f[\"fluid\"][\"entropy\"].shape[0]\n                    r = f[\"mesh\"][\"x_ef\"][:-2]\n                    theta = f[\"mesh\"][\"y_ef\"][:]\n                    phi = f[\"mesh\"][\"z_ef\"][\n                        k * l : k * (l + 1) + 1\n                    ]  # Pulls only the individual subgrid's band of phi values\n                elif f[\"mesh\"][\"z_ef\"][-1] == f[\"mesh\"][\"z_ef\"][0]:\n                    r = f[\"mesh\"][\"x_ef\"][\n                        : f[\"mesh\"][\"radial_index_bound\"][1]\n                        - f[\"mesh\"][\"x_ef\"].shape[0]\n                    ]\n                    theta = f[\"mesh\"][\"y_ef\"][:]\n                    phi = np.array([f[\"mesh\"][\"z_ef\"][0], 2 * np.pi])\n                else:\n                    r = f[\"mesh\"][\"x_ef\"][\n                        : f[\"mesh\"][\"radial_index_bound\"][1]\n                        - f[\"mesh\"][\"x_ef\"].shape[0]\n                    ]\n                    theta = f[\"mesh\"][\"y_ef\"][:]\n                    phi = f[\"mesh\"][\"z_ef\"][:]\n\n                # Creates variables to hold the size of dimensions\n                nxd = r.size\n                nyd = theta.size\n                nzd = phi.size\n                nyzd = nyd * nzd\n                nyzd_ = (nyd - 1) * (nzd - 1)\n\n                # Generates and fills coordinate array\n                coords = np.zeros((nxd, nyd, nzd, 3), dtype=\"float64\", order=\"C\")\n                coords[:, :, :, 0] = r[:, None, None]\n                coords[:, :, :, 1] = theta[None, :, None]\n                coords[:, :, :, 2] = phi[None, None, :]\n\n                if yy:\n                    mylog.warning(\n                        \"Yin-Yang File Detected; This data is not currently supported.\"\n                    )\n                coords = coords.reshape(nxd * nyd * nzd, 3)\n                # Connectivity is an array of rows, each of which corresponds to a grid cell.\n                # The 8 elements of each row are integers representing the cell vertices.\n                # These integers reference the numerical index of the element of the\n                # \"coords\" array which corresponds to the spatial coordinate.\n\n                connectivity = np.zeros(\n                    ((nyd - 1) * (nxd - 1) * (nzd - 1), 8), dtype=\"int64\", order=\"C\"\n                )  # Creates scaffold array\n                connectivity[0] = [\n                    0,\n                    1,\n                    nzd,\n                    (nzd + 1),\n                    (nyzd),\n                    (nyzd + 1),\n                    (nyzd + nzd),\n                    (nyzd + nzd + 1),\n                ]  # Manually defines first coordinate set\n\n                for p in range(\n                    nzd - 1\n                ):  # Increments first row around phi to define an arc of cells\n                    if p > 0:\n                        connectivity[p] = connectivity[p - 1] + 1\n                for t in range(\n                    nyd - 1\n                ):  # Increments this arc around theta to define a shell\n                    if t > 0:\n                        connectivity[t * (nzd - 1) : (t + 1) * (nzd - 1)] = (\n                            connectivity[(t - 1) * (nzd - 1) : t * (nzd - 1)] + nzd\n                        )\n                for r in range(\n                    nxd - 1\n                ):  # Increments this shell along r to define a sphere\n                    if r > 0:\n                        connectivity[r * (nyzd_) : (r + 1) * (nyzd_)] = (\n                            connectivity[(r - 1) * (nyzd_) : r * (nyzd_)] + nyzd\n                        )\n\n                mesh = ChimeraMesh(\n                    n, self.index_filename, connectivity, coords, self\n                )  # Creates a mesh object\n\n                if \"grid_\" in file:\n                    mylog.info(\"Mesh %s generated\", (n + 1) / len(index_filenames))\n                    self.meshes.append(\n                        mesh\n                    )  # Adds new mesh to the list of generated meshes\n\n    def _detect_output_fields(self):  # Reads in the available data fields\n        with h5py.File(self.index_filename, \"r\") as f:\n            fluids = [\n                (\"chimera\", i)\n                for i in f[\"fluid\"]\n                if np.shape(f[\"fluid\"][i]) == np.shape(f[\"fluid\"][\"rho_c\"])\n            ]\n            abundance = [\n                (\"chimera\", i)\n                for i in f[\"abundance\"]\n                if np.shape(f[\"abundance\"][i]) == np.shape(f[\"fluid\"][\"rho_c\"])\n            ]\n            e_rms = [(\"chimera\", f\"e_rms_{i + 1}\") for i in range(4)]\n            lumin = [(\"chimera\", f\"lumin_{i + 1}\") for i in range(4)]\n            num_lumin = [(\"chimera\", f\"num_lumin_{i + 1}\") for i in range(4)]\n            a_name = [\n                (\"chimera\", i.decode(\"utf-8\").strip()) for i in f[\"abundance\"][\"a_name\"]\n            ]\n            self.field_list = (\n                fluids\n                + abundance\n                + e_rms\n                + lumin\n                + num_lumin\n                + [(\"chimera\", \"abar\")]\n                + a_name\n            )\n            if np.shape(f[\"abundance\"][\"nse_c\"]) != np.shape(f[\"fluid\"][\"rho_c\"]):\n                self.field_list += [(\"chimera\", \"nse_c\")]\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):  # Creates Data chunk\n        gobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for subset in gobjs:\n            yield YTDataChunk(\n                dobj, \"io\", [subset], self._count_selection(dobj, [subset]), cache=cache\n            )\n\n    def _setup_data_io(self):\n        self.io = io_registry[self.dataset_type](self.dataset)\n\n\nclass ChimeraDataset(Dataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = ChimeraUNSIndex  # ChimeraHierarchy\n    _field_info_class = ChimeraFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"chimera\",\n        storage_filename=None,\n        units_override=None,\n    ):\n        # refinement factor between a grid and its subgrid\n        self.refine_by = 1  # Somewhat superfluous for Chimera, but left to avoid errors\n\n        self.fluid_types += (\"chimera\",)\n        super().__init__(filename, dataset_type, units_override=units_override)\n        self.storage_filename = storage_filename\n        self._handle = HDF5FileHandler(filename)\n\n    def _set_code_unit_attributes(self):\n        # This is where quantities are created that represent the various\n        # on-disk units.  These are the currently available quantities which\n        # should be set, along with examples of how to set them to standard\n        # values.\n        #\n        self.length_unit = self.quan(1.0, \"cm\")\n        self.mass_unit = self.quan(1.0, \"g\")\n        self.time_unit = self.quan(1.0, \"s\")\n        self.time_unit = self.quan(1.0, \"s\")\n        self.velocity_unit = self.quan(1.0, \"cm/s\")\n        self.magnetic_unit = self.quan(1.0, \"gauss\")\n\n    def _parse_parameter_file(self):\n        with h5py.File(self.parameter_filename, \"r\") as f:\n            # Reads in simulation time, number of dimensions and shape\n            self.current_time = f[\"mesh\"][\"time\"][()]\n            self.dimensionality = 3\n            self.domain_dimensions = f[\"mesh\"][\"array_dimensions\"][()]\n\n            self.geometry = Geometry.SPHERICAL  # Uses default spherical geometry\n            self._periodicity = (False, False, True)\n            dle = [\n                f[\"mesh\"][\"x_ef\"][0],\n                f[\"mesh\"][\"y_ef\"][0],\n                f[\"mesh\"][\"z_ef\"][0],\n            ]\n\n            if (\n                self.domain_dimensions[2] <= 2\n                and f[\"mesh\"][\"z_ef\"][-1] == f[\"mesh\"][\"z_ef\"][0]\n            ):\n                dre = [\n                    f[\"mesh\"][\"x_ef\"][\n                        f[\"mesh\"][\"radial_index_bound\"][1] - f[\"mesh\"][\"x_ef\"].shape[0]\n                    ],\n                    f[\"mesh\"][\"y_ef\"][-1],\n                    2 * np.pi,\n                ]\n            else:\n                dre = [\n                    f[\"mesh\"][\"x_ef\"][\n                        f[\"mesh\"][\"radial_index_bound\"][1] - f[\"mesh\"][\"x_ef\"].shape[0]\n                    ],\n                    f[\"mesh\"][\"y_ef\"][-1],\n                    f[\"mesh\"][\"z_ef\"][-1],\n                ]\n            # Sets left and right bounds based on earlier definitions\n            self.domain_right_edge = np.array(dre)\n            self.domain_left_edge = np.array(dle)\n\n        self.cosmological_simulation = 0  # Chimera is not a cosmological simulation\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        # This accepts a filename or a set of arguments and returns True or\n        # False depending on if the file is of the type requested.\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            fileh = HDF5FileHandler(filename)\n            if (\n                \"fluid\" in fileh\n                and \"agr_c\" in fileh[\"fluid\"].keys()\n                and \"grav_x_c\" in fileh[\"fluid\"].keys()\n            ):\n                return True  # Numpy bless\n        except OSError:\n            pass\n        return False\n"
  },
  {
    "path": "yt/frontends/chimera/definitions.py",
    "content": "# This file is often empty.  It can hold definitions related to a frontend.\n"
  },
  {
    "path": "yt/frontends/chimera/fields.py",
    "content": "\"\"\"\nChimera-specific fields\n\n\n\n\"\"\"\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\n# We need to specify which fields we might have in our dataset.  The field info\n# container subclass here will define which fields it knows about.  There are\n# optionally methods on it that get called which can be subclassed.\n\n\nclass ChimeraFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"e_int\", (\"erg\", [\"Internal Energy\"], \"Internal Energy\")),\n        (\"entropy\", (\"\", [\"Entropy\"], None)),\n        (\"rho_c\", (\"g/cm**3\", [\"density\", \"Density\"], \"Density\")),\n        (\"dudt_nu\", (\"erg/s\", [], None)),\n        (\"dudt_nuc\", (\"erg/s\", [], None)),\n        (\"grav_x_c\", (\"cm/s**2\", [], None)),\n        (\"grav_y_c\", (\"cm/s**2\", [], None)),\n        (\"grav_z_c\", (\"cm/s**2\", [], None)),\n        (\"press\", (\"erg/cm**3\", [\"pressure\"], \"Pressure\")),\n        (\"t_c\", (\"K\", [\"temperature\"], \"Temperature\")),\n        (\"u_c\", (\"cm/s\", [\"v_radial\"], \"Radial Velocity\")),\n        (\"v_c\", (\"cm/s\", [\"v_theta\"], \"Theta Velocity\")),\n        (\"v_csound\", (\"\", [], None)),\n        (\"wBVMD\", (\"1/s\", [], \"BruntViasala_freq\")),\n        (\"w_c\", (\"cm/s\", [\"v_phi\"], \"Phi Velocity\")),\n        (\"ye_c\", (\"\", [], None)),\n        (\"ylep\", (\"\", [], None)),\n        (\"a_nuc_rep_c\", (\"\", [], None)),\n        (\"be_nuc_rep_c\", (\"\", [], None)),\n        (\"e_book\", (\"\", [], None)),\n        (\"nse_c\", (\"\", [], None)),\n        (\"z_nuc_rep_c\", (\"\", [], None)),\n    )\n    # Each entry here is of the form\n    # ( \"name\", (\"units\", [\"fields\", \"to\", \"alias\"], # \"display_name\")),\n\n    known_particle_fields = (\n        # Identical form to above\n    )\n\n    def __init__(self, ds, field_list):\n        super().__init__(ds, field_list)\n        # If you want, you can check self.field_list\n\n    def setup_fluid_fields(self):\n        # Here we do anything that might need info about the dataset.\n        # You can use self.alias, self.add_output_field (for on-disk fields)\n        # and self.add_field (for derived fields).\n\n        def _test(data):\n            return data[\"chimera\", \"rho_c\"]\n\n        self.add_field(\n            (\"chimera\", \"test\"), sampling_type=\"cell\", function=_test, units=\"g/cm**3\"\n        )\n\n    def setup_particle_fields(self, ptype):\n        super().setup_particle_fields(ptype)\n        # This will get called for every particle type.\n"
  },
  {
    "path": "yt/frontends/chimera/io.py",
    "content": "\"\"\"\nChimera-specific IO functions\n\n\n\n\"\"\"\n\nimport numpy as np\nimport unyt as un\n\nfrom yt.frontends.chimera.data_structures import _find_files\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\nclass ChimeraIOHandler(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"chimera\"\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        self._handle = ds._handle\n        self.filename = ds.filename\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This needs to *yield* a series of tuples of (ptype, (x, y, z)).\n        # chunks is a list of chunks, and ptf is a dict where the keys are\n        # ptypes and the values are lists of fields.\n        pass\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # This gets called after the arrays have been allocated.  It needs to\n        # yield ((ptype, field), data) where data is the masked results of\n        # reading ptype, field and applying the selector to the data read in.\n        # Selector objects have a .select_points(x,y,z) that returns a mask, so\n        # you need to do your masking here.\n        pass\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        rv = {}\n        nodal_fields = []\n        for field in fields:\n            finfo = self.ds.field_info[field]\n            nodal_flag = finfo.nodal_flag\n            if np.any(nodal_flag):\n                num_nodes = 2 ** sum(nodal_flag)\n                rv[field] = np.empty((size, num_nodes), dtype=\"=f8\")\n                nodal_fields.append(field)\n            else:\n                rv[field] = np.empty(size, dtype=\"=f8\")\n        ind = 0\n        for field, mesh, data in self.io_iter(chunks, fields):\n            if data is None:\n                continue\n            else:\n                ind += mesh.select(selector, data.flatten(), rv[field], ind)  # caches\n        return rv\n\n    def io_iter(self, chunks, fields):\n        for n, chunk in enumerate(chunks):\n            file = _find_files(self.filename)\n            with h5py.File(file[n], \"r\") as f:\n                # Generates mask according to the \"ongrid_mask\" variable\n                m = int(file[n][-5:-3]) - 1\n                k = f[\"fluid\"][\"entropy\"].shape[0]\n                mask_0 = f[\"mesh\"][\"ongrid_mask\"][k * m : k * (m + 1), :]\n\n                if f[\"mesh\"][\"array_dimensions\"][2] > 1:\n                    nrd = f[\"mesh\"][\"array_dimensions\"][0] - 2\n                else:\n                    nrd = f[\"mesh\"][\"array_dimensions\"][0]\n\n                mask = np.repeat(mask_0[:, :, np.newaxis], nrd, axis=2).transpose()\n                specials = (\n                    \"abar\",\n                    \"e_rms_1\",\n                    \"e_rms_2\",\n                    \"e_rms_3\",\n                    \"e_rms_4\",\n                    \"lumin_1\",\n                    \"lumin_2\",\n                    \"lumin_3\",\n                    \"lumin_4\",\n                    \"num_lumin_1\",\n                    \"num_lumin_2\",\n                    \"num_lumin_3\",\n                    \"num_lumin_4\",\n                    \"shock\",\n                    \"nse_c\",\n                )\n                for field in fields:  # Reads data by locating subheading\n                    ftype, fname = field\n                    a_name_2 = [i.decode(\"utf-8\") for i in f[\"abundance\"][\"a_name\"]]\n                    a_name_dict = {name.strip(): name for name in a_name_2}\n                    if fname not in specials:\n                        if fname in f[\"fluid\"]:\n                            ds = f[\"fluid\"][f\"{fname}\"]\n                        elif fname in f[\"abundance\"]:\n                            ds = f[\"abundance\"][f\"{fname}\"]\n                        elif fname in a_name_dict:\n                            ind_xn = a_name_2.index(a_name_dict[fname])\n                            ds = f[\"abundance\"][\"xn_c\"][:, :, :, ind_xn]\n                        else:\n                            mylog.warning(\"Invalid field name %s\", fname)\n                        dat_1 = ds[:, :, :].transpose()\n\n                    elif fname == \"nse_c\":\n                        if np.shape(f[\"abundance\"][\"nse_c\"]) != np.shape(\n                            f[\"fluid\"][\"rho_c\"]\n                        ):\n                            ds = f[\"abundance\"][\"nse_c\"][:, :, 1:]\n                        else:\n                            ds = f[\"abundance\"][\"nse_c\"]\n                        dat_1 = ds[:, :, :].transpose()\n                    elif fname == \"abar\":\n                        xn_c = np.array(f[\"abundance\"][\"xn_c\"])\n                        a_nuc_rep_c = np.array(f[\"abundance\"][\"a_nuc_rep_c\"])\n                        a_nuc = np.array(f[\"abundance\"][\"a_nuc\"])\n                        a_nuc_tile = np.tile(\n                            a_nuc, (xn_c.shape[0], xn_c.shape[1], xn_c.shape[2], 1)\n                        )\n                        yn_c = np.empty(xn_c.shape)\n                        yn_c[:, :, :, :-1] = xn_c[:, :, :, :-1] / a_nuc_tile[:, :, :, :]\n                        yn_c[:, :, :, -1] = xn_c[:, :, :, -1] / a_nuc_rep_c[:, :, :]\n                        ytot = np.sum(yn_c, axis=3)\n                        atot = np.sum(xn_c, axis=3)\n                        abar = np.divide(atot, ytot)\n                        dat_1 = abar[:, :, :].transpose()\n                    elif fname in (\"e_rms_1\", \"e_rms_2\", \"e_rms_3\", \"e_rms_4\"):\n                        dims = f[\"mesh\"][\"array_dimensions\"]\n                        n_groups = f[\"radiation\"][\"raddim\"][0]\n                        n_species = f[\"radiation\"][\"raddim\"][1]\n                        n_hyperslabs = f[\"mesh\"][\"nz_hyperslabs\"][()]\n                        energy_edge = f[\"radiation\"][\"unubi\"][()]\n                        energy_center = f[\"radiation\"][\"unui\"][()]\n                        d_energy = []\n                        for i in range(0, n_groups):\n                            d_energy.append(energy_edge[i + 1] - energy_edge[i])\n                        d_energy = np.array(d_energy)\n                        e3de = energy_center**3 * d_energy\n                        e5de = energy_center**5 * d_energy\n\n                        psi0_c = f[\"radiation\"][\"psi0_c\"][:]\n                        row = np.empty(\n                            (n_species, int(dims[2] / n_hyperslabs), dims[1], dims[0])\n                        )\n                        for n in range(0, n_species):\n                            numerator = np.sum(psi0_c[:, :, :, n] * e5de, axis=3)\n                            denominator = np.sum(psi0_c[:, :, :, n] * e3de, axis=3)\n                            row[n][:][:][:] = np.sqrt(\n                                numerator / (denominator + 1e-100)\n                            )\n                            species = int(fname[-1]) - 1\n                            dat_1 = row[species, :, :, :].transpose()\n                    elif fname in (\n                        \"lumin_1\",\n                        \"lumin_2\",\n                        \"lumin_3\",\n                        \"lumin_4\",\n                        \"num_lumin_1\",\n                        \"num_lumin_2\",\n                        \"num_lumin_3\",\n                        \"num_lumin_4\",\n                    ):\n                        dims = f[\"mesh\"][\"array_dimensions\"]\n                        n_groups = f[\"radiation\"][\"raddim\"][0]\n                        n_hyperslabs = f[\"mesh\"][\"nz_hyperslabs\"][()]\n                        ergmev = float((1 * un.MeV) / (1 * un.erg))\n                        cvel = float(un.c.to(\"cm/s\"))\n                        h = float(un.h.to(\"MeV * s\"))\n                        ecoef = 4.0 * np.pi * ergmev / (h * cvel) ** 3\n                        radius = f[\"mesh\"][\"x_ef\"][()]\n                        agr_e = f[\"fluid\"][\"agr_e\"][()]\n                        cell_area_GRcorrected = 4 * np.pi * radius**2 / agr_e**4\n                        psi1_e = f[\"radiation\"][\"psi1_e\"]\n                        energy_edge = f[\"radiation\"][\"unubi\"][()]\n                        energy_center = f[\"radiation\"][\"unui\"][()]\n                        d_energy = []\n                        for i in range(0, n_groups):\n                            d_energy.append(energy_edge[i + 1] - energy_edge[i])\n                        d_energy = np.array(d_energy)\n                        species = int(fname[-1]) - 1\n                        if fname in (\"lumin_1\", \"lumin_2\", \"lumin_3\", \"lumin_4\"):\n                            eNde = energy_center**3 * d_energy\n                        else:\n                            eNde = energy_center**2 * d_energy\n\n                        lumin = (\n                            np.sum(psi1_e[:, :, :, species] * eNde, axis=3)\n                            * np.tile(\n                                cell_area_GRcorrected[1 : dims[0] + 1],\n                                (int(dims[2] / n_hyperslabs), dims[1], 1),\n                            )\n                            * (cvel * ecoef * 1e-51)\n                        )\n                        dat_1 = lumin[:, :, :].transpose()\n\n                    if f[\"mesh\"][\"array_dimensions\"][2] > 1:\n                        data = dat_1[:-2, :, :]  # Clips off ghost zones for 3D\n                    else:\n                        data = dat_1[:, :, :]\n                        data = np.ma.masked_where(mask == 0.0, data)  # Masks\n                        data = np.ma.filled(\n                            data, fill_value=0.0\n                        )  # Replaces masked value with 0\n\n                    yield field, chunk.objs[0], data\n\n        pass\n\n    def _read_chunk_data(self, chunk, fields):\n        # This reads the data from a single chunk without doing any selection,\n        # and is only used for caching data that might be used by multiple\n        # different selectors later. For instance, this can speed up ghost zone\n        # computation.\n        pass\n"
  },
  {
    "path": "yt/frontends/chimera/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/chimera/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/chimera/tests/test_outputs.py",
    "content": "\"\"\"\nChimera frontend tests\n\n\"\"\"\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal, assert_array_equal, assert_equal\n\nfrom yt.testing import requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import (\n    GenericArrayTest,\n    data_dir_load,\n    requires_ds,\n)\n\nTwo_D = \"F37_80/chimera_00001_grid_1_01.h5\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(Two_D)\ndef test_2D():\n    ds = data_dir_load(Two_D)\n    _fields = [\n        (\"chimera\", \"a_nuc_rep_c\"),\n        (\"chimera\", \"abar\"),\n        (\"chimera\", \"ar36\"),\n        (\"chimera\", \"be_nuc_rep_c\"),\n        (\"chimera\", \"c12\"),\n        (\"chimera\", \"ca40\"),\n        (\"chimera\", \"cr48\"),\n        (\"chimera\", \"dudt_nu\"),\n        (\"chimera\", \"dudt_nuc\"),\n        (\"chimera\", \"e_book\"),\n        (\"chimera\", \"e_int\"),\n        (\"chimera\", \"e_rms_1\"),\n        (\"chimera\", \"e_rms_2\"),\n        (\"chimera\", \"e_rms_3\"),\n        (\"chimera\", \"e_rms_4\"),\n        (\"chimera\", \"entropy\"),\n        (\"chimera\", \"fe52\"),\n        (\"chimera\", \"fe56\"),\n        (\"chimera\", \"grav_x_c\"),\n        (\"chimera\", \"grav_y_c\"),\n        (\"chimera\", \"grav_z_c\"),\n        (\"chimera\", \"he4\"),\n        (\"chimera\", \"lumin_1\"),\n        (\"chimera\", \"lumin_2\"),\n        (\"chimera\", \"lumin_3\"),\n        (\"chimera\", \"lumin_4\"),\n        (\"chimera\", \"mg24\"),\n        (\"chimera\", \"n\"),\n        (\"chimera\", \"ne20\"),\n        (\"chimera\", \"ni56\"),\n        (\"chimera\", \"nse_c\"),\n        (\"chimera\", \"num_lumin_1\"),\n        (\"chimera\", \"num_lumin_2\"),\n        (\"chimera\", \"num_lumin_3\"),\n        (\"chimera\", \"num_lumin_4\"),\n        (\"chimera\", \"o16\"),\n        (\"chimera\", \"p\"),\n        (\"chimera\", \"press\"),\n        (\"chimera\", \"rho_c\"),\n        (\"chimera\", \"s32\"),\n        (\"chimera\", \"si28\"),\n        (\"chimera\", \"t_c\"),\n        (\"chimera\", \"ti44\"),\n        (\"chimera\", \"u_c\"),\n        (\"chimera\", \"v_c\"),\n        (\"chimera\", \"v_csound\"),\n        (\"chimera\", \"wBVMD\"),\n        (\"chimera\", \"w_c\"),\n        (\"chimera\", \"ye_c\"),\n        (\"chimera\", \"ylep\"),\n        (\"chimera\", \"z_nuc_rep_c\"),\n        (\"chimera\", \"zn60\"),\n    ]\n    assert_equal(str(ds), \"chimera_00001_grid_1_01.h5\")\n    assert_equal(str(ds.geometry), \"spherical\")  # Geometry\n    assert_almost_equal(\n        ds.domain_right_edge,\n        ds.arr([1.0116509e10 + 100, 3.14159265e00, 6.28318531e00], \"code_length\"),\n    )  # domain edge\n    assert_array_equal(\n        ds.domain_left_edge, ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    )  # domain edge\n    assert_array_equal(ds.domain_dimensions, np.array([722, 240, 1]))  # Dimensions\n    assert_array_equal(ds.field_list, _fields)\n\n    def field_func(field):\n        min = dd[field].min()\n        max = dd[field].max()\n        avg = np.mean(dd[field])\n        size = dd[field].size\n        return [min, max, avg, size]\n\n    dd = ds.all_data()\n    for field in _fields:\n        if field != (\"chimera\", \"shock\"):\n            yield GenericArrayTest(ds, field_func, args=[field])\n\n\nThree_D = \"C15-3D-3deg/chimera_002715000_grid_1_01.h5\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(Three_D)\ndef test_3D():\n    ds = data_dir_load(Three_D)\n    _fields = [\n        (\"chimera\", \"a_nuc_rep_c\"),\n        (\"chimera\", \"abar\"),\n        (\"chimera\", \"ar36\"),\n        (\"chimera\", \"be_nuc_rep_c\"),\n        (\"chimera\", \"c12\"),\n        (\"chimera\", \"ca40\"),\n        (\"chimera\", \"cr48\"),\n        (\"chimera\", \"dudt_nu\"),\n        (\"chimera\", \"dudt_nuc\"),\n        (\"chimera\", \"e_book\"),\n        (\"chimera\", \"e_int\"),\n        (\"chimera\", \"e_rms_1\"),\n        (\"chimera\", \"e_rms_2\"),\n        (\"chimera\", \"e_rms_3\"),\n        (\"chimera\", \"e_rms_4\"),\n        (\"chimera\", \"entropy\"),\n        (\"chimera\", \"fe52\"),\n        (\"chimera\", \"grav_x_c\"),\n        (\"chimera\", \"grav_y_c\"),\n        (\"chimera\", \"grav_z_c\"),\n        (\"chimera\", \"he4\"),\n        (\"chimera\", \"lumin_1\"),\n        (\"chimera\", \"lumin_2\"),\n        (\"chimera\", \"lumin_3\"),\n        (\"chimera\", \"lumin_4\"),\n        (\"chimera\", \"mg24\"),\n        (\"chimera\", \"n\"),\n        (\"chimera\", \"ne20\"),\n        (\"chimera\", \"ni56\"),\n        (\"chimera\", \"nse_c\"),\n        (\"chimera\", \"num_lumin_1\"),\n        (\"chimera\", \"num_lumin_2\"),\n        (\"chimera\", \"num_lumin_3\"),\n        (\"chimera\", \"num_lumin_4\"),\n        (\"chimera\", \"o16\"),\n        (\"chimera\", \"p\"),\n        (\"chimera\", \"press\"),\n        (\"chimera\", \"rho_c\"),\n        (\"chimera\", \"s32\"),\n        (\"chimera\", \"si28\"),\n        (\"chimera\", \"t_c\"),\n        (\"chimera\", \"ti44\"),\n        (\"chimera\", \"u_c\"),\n        (\"chimera\", \"v_c\"),\n        (\"chimera\", \"v_csound\"),\n        (\"chimera\", \"wBVMD\"),\n        (\"chimera\", \"w_c\"),\n        (\"chimera\", \"ye_c\"),\n        (\"chimera\", \"ylep\"),\n        (\"chimera\", \"z_nuc_rep_c\"),\n        (\"chimera\", \"zn60\"),\n    ]\n    assert_equal(str(ds), \"chimera_002715000_grid_1_01.h5\")\n    assert_equal(str(ds.geometry), \"spherical\")  # Geometry\n    assert_almost_equal(\n        ds.domain_right_edge,\n        ds.arr(\n            [1.06500257e09 - 1.03818333, 3.14159265e00, 6.2831853e00], \"code_length\"\n        ),\n    )  # Domain edge\n    assert_array_equal(ds.domain_left_edge, [0.0, 0.0, 0.0])  # Domain edge\n    assert_array_equal(ds.domain_dimensions, [542, 60, 135])  # Dimensions\n    assert_array_equal(ds.field_list, _fields)\n\n    def field_func(field):\n        min = dd[field].min()\n        max = dd[field].max()\n        avg = np.mean(dd[field])\n        size = dd[field].size\n        return [min, max, avg, size]\n\n    dd = ds.all_data()\n    for field in _fields:\n        if field != (\"chimera\", \"shock\"):\n            yield GenericArrayTest(ds, field_func, args=[field])\n\n\n@requires_file(Three_D)\ndef test_multimesh():  # Tests that the multimesh system for 3D data has been created correctly\n    ds = data_dir_load(Three_D)\n    assert_equal(len(ds.index.meshes), 45)\n    for i in range(44):\n        assert_almost_equal(\n            ds.index.meshes[i + 1].connectivity_coords\n            - ds.index.meshes[i].connectivity_coords,\n            np.tile([0.0, 0.0, 0.13962634015954636], (132004, 1)),\n        )  # Tests that each mesh is an identically shaped wedge, incrememnted in Phi.\n        assert_array_equal(\n            ds.index.meshes[i + 1].connectivity_indices,\n            ds.index.meshes[i].connectivity_indices,\n        )  # Checks Connectivity array is identical for all meshes.\n"
  },
  {
    "path": "yt/frontends/cholla/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/cholla/api.py",
    "content": "from .data_structures import ChollaDataset, ChollaGrid, ChollaHierarchy\nfrom .fields import ChollaFieldInfo\nfrom .io import ChollaIOHandler\n"
  },
  {
    "path": "yt/frontends/cholla/data_structures.py",
    "content": "import os\nimport weakref\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import ChollaFieldInfo\n\n\nclass ChollaGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level, dims):\n        super().__init__(id, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n        self.ActiveDimensions = dims\n\n\nclass ChollaHierarchy(GridIndex):\n    grid = ChollaGrid\n\n    def __init__(self, ds, dataset_type=\"cholla\"):\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        # for now, the index file is the dataset!\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        # float type for the simulation edges and must be float64 now\n        self.float_type = np.float64\n        super().__init__(ds, dataset_type)\n\n    def _detect_output_fields(self):\n        with h5py.File(self.index_filename, mode=\"r\") as h5f:\n            self.field_list = [(\"cholla\", k) for k in h5f.keys()]\n\n    def _count_grids(self):\n        self.num_grids = 1\n\n    def _parse_index(self):\n        self.grid_left_edge[0][:] = self.ds.domain_left_edge[:]\n        self.grid_right_edge[0][:] = self.ds.domain_right_edge[:]\n        self.grid_dimensions[0][:] = self.ds.domain_dimensions[:]\n        self.grid_particle_count[0][0] = 0\n        self.grid_levels[0][0] = 0\n        self.max_level = 0\n\n    def _populate_grid_objects(self):\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n        for i in range(self.num_grids):\n            g = self.grid(i, self, self.grid_levels.flat[i], self.grid_dimensions[i])\n            g._prepare_grid()\n            g._setup_dx()\n            self.grids[i] = g\n\n\nclass ChollaDataset(Dataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = ChollaHierarchy\n    _field_info_class = ChollaFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"cholla\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        self.fluid_types += (\"cholla\",)\n        super().__init__(filename, dataset_type, units_override=units_override)\n        self.storage_filename = storage_filename\n\n    def _set_code_unit_attributes(self):\n        # This is where quantities are created that represent the various\n        # on-disk units.  These are the defaults, but if they are listed\n        # in the HDF5 attributes for a file, which is loaded first, then those are\n        # used instead.\n        #\n        if not self.length_unit:\n            self.length_unit = self.quan(1.0, \"pc\")\n        if not self.mass_unit:\n            self.mass_unit = self.quan(1.0, \"Msun\")\n        if not self.time_unit:\n            self.time_unit = self.quan(1000, \"yr\")\n        if not self.velocity_unit:\n            self.velocity_unit = self.quan(1.0, \"cm/s\")\n        if not self.magnetic_unit:\n            self.magnetic_unit = self.quan(1.0, \"gauss\")\n\n        for key, unit in self.__class__.default_units.items():\n            setdefaultattr(self, key, self.quan(1, unit))\n\n    def _parse_parameter_file(self):\n        with h5py.File(self.parameter_filename, mode=\"r\") as h5f:\n            attrs = h5f.attrs\n            self.parameters = dict(attrs.items())\n            self.domain_left_edge = attrs[\"bounds\"][:].astype(\"=f8\")\n            self.domain_right_edge = self.domain_left_edge + attrs[\"domain\"][:].astype(\n                \"=f8\"\n            )\n            self.dimensionality = len(attrs[\"dims\"][:])\n            self.domain_dimensions = attrs[\"dims\"][:].astype(\"=f8\")\n            self.current_time = attrs[\"t\"][:]\n            self._periodicity = tuple(attrs.get(\"periodicity\", (False, False, False)))\n            self.gamma = attrs.get(\"gamma\", 5.0 / 3.0)\n            if (self.default_species_fields is not None) and \"mu\" in attrs:\n                raise ValueError(\n                    'default_species_fields must be None when \"mu\" is an hdf5 attribute'\n                )\n            elif \"mu\" in attrs:\n                self.mu = attrs[\"mu\"]\n            elif self.default_species_fields is None:\n                # other yt-machinery can't handle ds.mu == None, so we simply\n                # avoid defining the mu attribute if we don't know its value\n                mylog.info(\n                    'add the \"mu\" hdf5 attribute OR use the default_species_fields kwarg '\n                    \"to compute temperature\"\n                )\n            self.refine_by = 1\n\n            # If header specifies code units, default to those (in CGS)\n            length_unit = attrs.get(\"length_unit\", None)\n            mass_unit = attrs.get(\"mass_unit\", None)\n            time_unit = attrs.get(\"time_unit\", None)\n            velocity_unit = attrs.get(\"velocity_unit\", None)\n            magnetic_unit = attrs.get(\"magnetic_unit\", None)\n            if length_unit:\n                self.length_unit = self.quan(length_unit[0], \"cm\")\n            if mass_unit:\n                self.mass_unit = self.quan(mass_unit[0], \"g\")\n            if time_unit:\n                self.time_unit = self.quan(time_unit[0], \"s\")\n            if velocity_unit:\n                self.velocity_unit = self.quan(velocity_unit[0], \"cm/s\")\n            if magnetic_unit:\n                self.magnetic_unit = self.quan(magnetic_unit[0], \"gauss\")\n\n            # this minimalistic implementation fills the requirements for\n            # this frontend to run, change it to make it run _correctly_ !\n            for key, unit in self.__class__.default_units.items():\n                setdefaultattr(self, key, self.quan(1, unit))\n\n        # CHOLLA cannot yet be run as a cosmological simulation\n        self.cosmological_simulation = 0\n        self.current_redshift = 0.0\n        self.omega_lambda = 0.0\n        self.omega_matter = 0.0\n        self.hubble_constant = 0.0\n\n        # CHOLLA datasets are always unigrid cartesian\n        self.geometry = Geometry.CARTESIAN\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        # This accepts a filename or a set of arguments and returns True or\n        # False depending on if the file is of the type requested.\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            fileh = h5py.File(filename, mode=\"r\")\n        except OSError:\n            return False\n\n        try:\n            attrs = fileh.attrs\n        except AttributeError:\n            return False\n        else:\n            return (\n                \"bounds\" in attrs\n                and \"domain\" in attrs\n                and attrs.get(\"data_type\") != \"yt_light_ray\"\n            )\n        finally:\n            fileh.close()\n"
  },
  {
    "path": "yt/frontends/cholla/definitions.py",
    "content": "# This file is often empty.  It can hold definitions related to a frontend.\n"
  },
  {
    "path": "yt/frontends/cholla/fields.py",
    "content": "import numpy as np\nfrom unyt import Zsun\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.utilities.physical_constants import kboltz, mh\n\n# Copied from Athena frontend\npres_units = \"code_pressure\"\nerg_units = \"code_mass * (code_length/code_time)**2\"\nrho_units = \"code_mass / code_length**3\"\nmom_units = \"code_mass / code_length**2 / code_time\"\n\n\ndef velocity_field(comp):\n    def _velocity(data):\n        return data[\"cholla\", f\"momentum_{comp}\"] / data[\"cholla\", \"density\"]\n\n    return _velocity\n\n\nclass ChollaFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        # Each entry here is of the form\n        # ( \"name\", (\"units\", [\"fields\", \"to\", \"alias\"], # \"display_name\")),\n        (\"density\", (rho_units, [\"density\"], None)),\n        (\"momentum_x\", (mom_units, [\"momentum_x\"], None)),\n        (\"momentum_y\", (mom_units, [\"momentum_y\"], None)),\n        (\"momentum_z\", (mom_units, [\"momentum_z\"], None)),\n        (\"Energy\", (\"code_pressure\", [\"total_energy_density\"], None)),\n        (\"scalar0\", (rho_units, [], None)),\n        (\"metal_density\", (rho_units, [\"metal_density\"], None)),\n    )\n\n    known_particle_fields = ()\n\n    # In Cholla, conservative variables are written out.\n\n    def setup_fluid_fields(self):\n        unit_system = self.ds.unit_system\n\n        # Add velocity fields\n        for comp in \"xyz\":\n            self.add_field(\n                (\"gas\", f\"velocity_{comp}\"),\n                sampling_type=\"cell\",\n                function=velocity_field(comp),\n                units=unit_system[\"velocity\"],\n            )\n\n        # Add pressure field\n        if (\"cholla\", \"GasEnergy\") in self.field_list:\n            self.add_output_field(\n                (\"cholla\", \"GasEnergy\"), sampling_type=\"cell\", units=pres_units\n            )\n            self.alias(\n                (\"gas\", \"thermal_energy\"),\n                (\"cholla\", \"GasEnergy\"),\n                units=unit_system[\"pressure\"],\n            )\n\n            def _pressure(data):\n                return (data.ds.gamma - 1.0) * data[\"cholla\", \"GasEnergy\"]\n\n        else:\n\n            def _pressure(data):\n                return (data.ds.gamma - 1.0) * (\n                    data[\"cholla\", \"Energy\"] - data[\"gas\", \"kinetic_energy_density\"]\n                )\n\n        self.add_field(\n            (\"gas\", \"pressure\"),\n            sampling_type=\"cell\",\n            function=_pressure,\n            units=unit_system[\"pressure\"],\n        )\n\n        def _specific_total_energy(data):\n            return data[\"cholla\", \"Energy\"] / data[\"cholla\", \"density\"]\n\n        self.add_field(\n            (\"gas\", \"specific_total_energy\"),\n            sampling_type=\"cell\",\n            function=_specific_total_energy,\n            units=unit_system[\"specific_energy\"],\n        )\n\n        # Add temperature field\n        if hasattr(self.ds, \"mu\"):\n\n            def _temperature(data):\n                return (\n                    data.ds.mu\n                    * data[\"gas\", \"pressure\"]\n                    / data[\"gas\", \"density\"]\n                    * mh\n                    / kboltz\n                )\n\n            self.add_field(\n                (\"gas\", \"temperature\"),\n                sampling_type=\"cell\",\n                function=_temperature,\n                units=unit_system[\"temperature\"],\n            )\n\n        # Add color field if present (scalar0 / density)\n        if (\"cholla\", \"scalar0\") in self.field_list:\n            self.add_output_field(\n                (\"cholla\", \"scalar0\"),\n                sampling_type=\"cell\",\n                units=rho_units,\n            )\n\n            def _color(data):\n                return data[\"cholla\", \"scalar0\"] / data[\"cholla\", \"density\"]\n\n            self.add_field(\n                (\"cholla\", \"color\"),\n                sampling_type=\"cell\",\n                function=_color,\n                units=\"\",\n            )\n\n            self.alias(\n                (\"gas\", \"color\"),\n                (\"cholla\", \"color\"),\n                units=\"\",\n            )\n\n            # Using color field to define metallicity field, where a color of 1\n            # indicates solar metallicity\n\n            def _metallicity(data):\n                # Ensuring that there are no negative metallicities\n                return np.clip(data[\"cholla\", \"color\"], 0, np.inf) * Zsun\n\n            self.add_field(\n                (\"cholla\", \"metallicity\"),\n                sampling_type=\"cell\",\n                function=_metallicity,\n                units=\"Zsun\",\n            )\n\n            self.alias(\n                (\"gas\", \"metallicity\"),\n                (\"cholla\", \"metallicity\"),\n                units=\"Zsun\",\n            )\n\n    def setup_particle_fields(self, ptype):\n        super().setup_particle_fields(ptype)\n        # This will get called for every particle type.\n"
  },
  {
    "path": "yt/frontends/cholla/io.py",
    "content": "import numpy as np\n\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\nclass ChollaIOHandler(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"cholla\"\n\n    def _read_particle_coords(self, chunks, ptf):\n        raise NotImplementedError\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        raise NotImplementedError\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        data = {}\n        for field in fields:\n            data[field] = np.empty(size, dtype=\"float64\")\n\n        with h5py.File(self.ds.parameter_filename, \"r\") as fh:\n            ind = 0\n            for chunk in chunks:\n                for grid in chunk.objs:\n                    nd = 0\n                    for field in fields:\n                        ftype, fname = field\n                        values = fh[fname][:].astype(\"=f8\")\n                        nd = grid.select(selector, values, data[field], ind)\n                    ind += nd\n        return data\n\n    def _read_chunk_data(self, chunk, fields):\n        raise NotImplementedError\n"
  },
  {
    "path": "yt/frontends/cholla/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/cholla/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/cholla/tests/test_outputs.py",
    "content": "from numpy.testing import assert_equal\n\nimport yt\nfrom yt.frontends.cholla.api import ChollaDataset\nfrom yt.testing import requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\n_fields = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n)\n\nChollaSimple = \"ChollaSimple/0.h5\"\n\n\n@requires_module(\"h5py\")\n@requires_file(ChollaSimple)\ndef test_ChollaDataset():\n    assert isinstance(data_dir_load(ChollaSimple), ChollaDataset)\n\n\n@requires_module(\"h5py\")\n@requires_file(ChollaSimple)\ndef test_ChollaSimple_fields():\n    expected_fields = [\n        \"Energy\",\n        \"GasEnergy\",\n        \"density\",\n        \"momentum_x\",\n        \"momentum_y\",\n        \"momentum_z\",\n        \"scalar0\",\n    ]\n\n    ds = yt.load(ChollaSimple)\n    assert_equal(str(ds), \"0.h5\")\n    ad = ds.all_data()\n\n    # Check all the expected fields exist and can be accessed\n    for field in expected_fields:\n        assert (\"cholla\", field) in ds.field_list\n\n        # test that field access works\n        ad[\"cholla\", field]\n\n\n@requires_module(\"h5py\")\n@requires_file(ChollaSimple)\ndef test_ChollaSimple_derived_fields():\n    expected_derived_fields = [\n        \"density\",\n        \"momentum_x\",\n        \"momentum_y\",\n        \"momentum_z\",\n        \"metallicity\",\n    ]\n\n    ds = yt.load(ChollaSimple)\n    ad = ds.all_data()\n\n    # Check all the expected fields exist and can be accessed\n    for field in expected_derived_fields:\n        assert (\"gas\", field) in ds.derived_field_list\n\n        # test that field access works\n        ad[\"gas\", field]\n\n\n_fields_chollasimple = (\n    (\"cholla\", \"GasEnergy\"),\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"metallicity\"),\n)\n\n\n@requires_module(\"h5py\")\n@requires_ds(ChollaSimple)\ndef test_cholla_data():\n    ds = data_dir_load(ChollaSimple)\n    assert_equal(str(ds), \"0.h5\")\n    for test in small_patch_amr(\n        ds, _fields_chollasimple, input_center=\"c\", input_weight=\"ones\"\n    ):\n        test_cholla_data.__name__ = test.description\n        yield test\n"
  },
  {
    "path": "yt/frontends/chombo/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/chombo/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    ChomboDataset,\n    ChomboGrid,\n    ChomboHierarchy,\n    ChomboPICDataset,\n    ChomboPICHierarchy,\n    Orion2Dataset,\n    Orion2Hierarchy,\n    PlutoDataset,\n    PlutoHierarchy,\n)\nfrom .fields import (\n    ChomboFieldInfo,\n    ChomboPICFieldInfo1D,\n    ChomboPICFieldInfo2D,\n    ChomboPICFieldInfo3D,\n    Orion2FieldInfo,\n    PlutoFieldInfo,\n)\nfrom .io import IOHandlerChomboHDF5\n"
  },
  {
    "path": "yt/frontends/chombo/data_structures.py",
    "content": "import os\nimport re\nimport weakref\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.file_handler import HDF5FileHandler, valid_hdf5_signature\nfrom yt.utilities.lib.misc_utilities import get_box_grids_level\nfrom yt.utilities.on_demand_imports import _h5py as h5py\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_root_only\n\nfrom .fields import (\n    ChomboFieldInfo,\n    ChomboPICFieldInfo1D,\n    ChomboPICFieldInfo2D,\n    ChomboPICFieldInfo3D,\n    Orion2FieldInfo,\n    PlutoFieldInfo,\n)\n\n\ndef is_chombo_hdf5(fn):\n    try:\n        with h5py.File(fn, mode=\"r\") as fileh:\n            valid = \"Chombo_global\" in fileh[\"/\"]\n    except (KeyError, OSError, ImportError):\n        return False\n    return valid\n\n\nclass ChomboGrid(AMRGridPatch):\n    _id_offset = 0\n    __slots__ = [\"_level_id\", \"stop_index\"]\n\n    def __init__(self, id, index, level, start, stop):\n        AMRGridPatch.__init__(self, id, filename=index.index_filename, index=index)\n        self._parent_id = []\n        self._children_ids = []\n        self.Level = level\n        self.ActiveDimensions = stop - start + 1\n\n    def get_global_startindex(self):\n        \"\"\"\n        Return the integer starting index for each dimension at the current\n        level.\n\n        \"\"\"\n        if self.start_index is not None:\n            return self.start_index\n        if self.Parent is None:\n            iLE = self.LeftEdge - self.ds.domain_left_edge\n            start_index = iLE / self.dds\n            return np.rint(start_index).astype(\"int64\").ravel()\n        pdx = self.Parent[0].dds\n        start_index = (self.Parent[0].get_global_startindex()) + np.rint(\n            (self.LeftEdge - self.Parent[0].LeftEdge) / pdx\n        )\n        self.start_index = (start_index * self.ds.refine_by).astype(\"int64\").ravel()\n        return self.start_index\n\n    def _setup_dx(self):\n        # has already been read in and stored in index\n        self.dds = self.ds.arr(self.index.dds_list[self.Level], \"code_length\")\n\n    @property\n    def Parent(self):\n        if len(self._parent_id) == 0:\n            return None\n        return [self.index.grids[pid - self._id_offset] for pid in self._parent_id]\n\n    @property\n    def Children(self):\n        return [self.index.grids[cid - self._id_offset] for cid in self._children_ids]\n\n\nclass ChomboHierarchy(GridIndex):\n    grid = ChomboGrid\n    _data_file = None\n\n    def __init__(self, ds, dataset_type=\"chombo_hdf5\"):\n        self.domain_left_edge = ds.domain_left_edge\n        self.domain_right_edge = ds.domain_right_edge\n        self.dataset_type = dataset_type\n        self.field_indexes = {}\n        self.dataset = weakref.proxy(ds)\n        # for now, the index file is the dataset!\n        self.index_filename = os.path.abspath(self.dataset.parameter_filename)\n        self.directory = ds.directory\n        self._handle = ds._handle\n\n        self._levels = [key for key in self._handle.keys() if key.startswith(\"level\")]\n\n        GridIndex.__init__(self, ds, dataset_type)\n\n        self._read_particles()\n\n    def _read_particles(self):\n        # only do anything if the dataset contains particles\n        if not any(f[1].startswith(\"particle_\") for f in self.field_list):\n            return\n\n        self.num_particles = 0\n        particles_per_grid = []\n        for key, val in self._handle.items():\n            if key.startswith(\"level\"):\n                level_particles = val[\"particles:offsets\"][:]\n                self.num_particles += level_particles.sum()\n                particles_per_grid = np.concatenate(\n                    (particles_per_grid, level_particles)\n                )\n\n        for i, _grid in enumerate(self.grids):\n            self.grids[i].NumberOfParticles = particles_per_grid[i]\n            self.grid_particle_count[i] = particles_per_grid[i]\n\n        assert self.num_particles == self.grid_particle_count.sum()\n\n    # Chombo datasets, by themselves, have no \"known\" fields. However,\n    # we will look for \"fluid\" fields by finding the string \"component\" in\n    # the output file, and \"particle\" fields by finding the string \"particle\".\n    def _detect_output_fields(self):\n        # look for fluid fields\n        output_fields = []\n        for key, val in self._handle.attrs.items():\n            if key.startswith(\"component\"):\n                output_fields.append(val.decode(\"ascii\"))\n        self.field_list = [(\"chombo\", c) for c in output_fields]\n\n        # look for particle fields\n        particle_fields = []\n        for key, val in self._handle.attrs.items():\n            if key.startswith(\"particle\"):\n                particle_fields.append(val.decode(\"ascii\"))\n        self.field_list.extend([(\"io\", c) for c in particle_fields])\n\n    def _count_grids(self):\n        self.num_grids = 0\n        for lev in self._levels:\n            d = self._handle[lev]\n            if \"Processors\" in d:\n                self.num_grids += d[\"Processors\"].len()\n            elif \"boxes\" in d:\n                self.num_grids += d[\"boxes\"].len()\n            else:\n                raise RuntimeError(\"Unknown file specification\")\n\n    def _parse_index(self):\n        f = self._handle  # shortcut\n        self.max_level = f.attrs[\"num_levels\"] - 1\n\n        grids = []\n        self.dds_list = []\n        i = 0\n        D = self.dataset.dimensionality\n        for lev_index, lev in enumerate(self._levels):\n            level_number = int(re.match(r\"level_(\\d+)\", lev).groups()[0])\n            try:\n                boxes = f[lev][\"boxes\"][()]\n            except KeyError:\n                boxes = f[lev][\"particles:boxes\"][()]\n            dx = f[lev].attrs[\"dx\"]\n            self.dds_list.append(dx * np.ones(3))\n\n            if D == 1:\n                self.dds_list[lev_index][1] = 1.0\n                self.dds_list[lev_index][2] = 1.0\n\n            if D == 2:\n                self.dds_list[lev_index][2] = 1.0\n\n            for level_id, box in enumerate(boxes):\n                si = np.array([box[f\"lo_{ax}\"] for ax in \"ijk\"[:D]])\n                ei = np.array([box[f\"hi_{ax}\"] for ax in \"ijk\"[:D]])\n\n                if D == 1:\n                    si = np.concatenate((si, [0.0, 0.0]))\n                    ei = np.concatenate((ei, [0.0, 0.0]))\n\n                if D == 2:\n                    si = np.concatenate((si, [0.0]))\n                    ei = np.concatenate((ei, [0.0]))\n\n                pg = self.grid(len(grids), self, level=level_number, start=si, stop=ei)\n                grids.append(pg)\n                grids[-1]._level_id = level_id\n                self.grid_levels[i] = level_number\n                self.grid_left_edge[i] = self.dds_list[lev_index] * si.astype(\n                    self.float_type\n                )\n                self.grid_right_edge[i] = self.dds_list[lev_index] * (\n                    ei.astype(self.float_type) + 1\n                )\n                self.grid_particle_count[i] = 0\n                self.grid_dimensions[i] = ei - si + 1\n                i += 1\n        self.grids = np.empty(len(grids), dtype=\"object\")\n        for gi, g in enumerate(grids):\n            self.grids[gi] = g\n\n    def _populate_grid_objects(self):\n        self._reconstruct_parent_child()\n        for g in self.grids:\n            g._prepare_grid()\n            g._setup_dx()\n\n    def _setup_derived_fields(self):\n        self.derived_field_list = []\n\n    def _reconstruct_parent_child(self):\n        mask = np.empty(len(self.grids), dtype=\"int32\")\n        mylog.debug(\"First pass; identifying child grids\")\n        for i, grid in enumerate(self.grids):\n            get_box_grids_level(\n                self.grid_left_edge[i, :],\n                self.grid_right_edge[i, :],\n                self.grid_levels[i].item() + 1,\n                self.grid_left_edge,\n                self.grid_right_edge,\n                self.grid_levels,\n                mask,\n            )\n            ids = np.where(mask.astype(\"bool\"))  # where is a tuple\n            grid._children_ids = ids[0] + grid._id_offset\n        mylog.debug(\"Second pass; identifying parents\")\n        for i, grid in enumerate(self.grids):  # Second pass\n            for child in grid.Children:\n                child._parent_id.append(i + grid._id_offset)\n\n\nclass ChomboDataset(Dataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = ChomboHierarchy\n    _field_info_class: type[FieldInfoContainer] = ChomboFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"chombo_hdf5\",\n        storage_filename=None,\n        ini_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        self.fluid_types += (\"chombo\",)\n        self._handle = HDF5FileHandler(filename)\n        self.dataset_type = dataset_type\n\n        self.geometry = Geometry.CARTESIAN\n        self.ini_filename = ini_filename\n        self.fullplotdir = os.path.abspath(filename)\n        Dataset.__init__(\n            self,\n            filename,\n            self.dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        self.storage_filename = storage_filename\n        self.cosmological_simulation = False\n\n        # These are parameters that I very much wish to get rid of.\n        self.parameters[\"HydroMethod\"] = \"chombo\"\n        self.parameters[\"DualEnergyFormalism\"] = 0\n        self.parameters[\"EOSType\"] = -1  # default\n\n    def _set_code_unit_attributes(self):\n        if not hasattr(self, \"length_unit\"):\n            mylog.warning(\"Setting code length unit to be 1.0 cm\")\n        if not hasattr(self, \"mass_unit\"):\n            mylog.warning(\"Setting code mass unit to be 1.0 g\")\n        if not hasattr(self, \"time_unit\"):\n            mylog.warning(\"Setting code time unit to be 1.0 s\")\n        setdefaultattr(self, \"length_unit\", self.quan(1.0, \"cm\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"g\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n        setdefaultattr(self, \"magnetic_unit\", self.quan(np.sqrt(4.0 * np.pi), \"gauss\"))\n        setdefaultattr(self, \"velocity_unit\", self.length_unit / self.time_unit)\n\n    def _localize(self, f, default):\n        if f is None:\n            return os.path.join(self.directory, default)\n        return f\n\n    def _parse_parameter_file(self):\n        self.dimensionality = self._handle[\"Chombo_global/\"].attrs[\"SpaceDim\"]\n        self.domain_left_edge = self._calc_left_edge()\n        self.domain_right_edge = self._calc_right_edge()\n        self.domain_dimensions = self._calc_domain_dimensions()\n\n        # if a lower-dimensional dataset, set up pseudo-3D stuff here.\n        if self.dimensionality == 1:\n            self.domain_left_edge = np.concatenate((self.domain_left_edge, [0.0, 0.0]))\n            self.domain_right_edge = np.concatenate(\n                (self.domain_right_edge, [1.0, 1.0])\n            )\n            self.domain_dimensions = np.concatenate((self.domain_dimensions, [1, 1]))\n\n        if self.dimensionality == 2:\n            self.domain_left_edge = np.concatenate((self.domain_left_edge, [0.0]))\n            self.domain_right_edge = np.concatenate((self.domain_right_edge, [1.0]))\n            self.domain_dimensions = np.concatenate((self.domain_dimensions, [1]))\n\n        self.refine_by = self._handle[\"/level_0\"].attrs[\"ref_ratio\"]\n        self._determine_periodic()\n        self._determine_current_time()\n\n    def _determine_current_time(self):\n        # some datasets will not be time-dependent, and to make\n        # matters worse, the simulation time is not always\n        # stored in the same place in the hdf file! Make\n        # sure we handle that here.\n        try:\n            self.current_time = self._handle.attrs[\"time\"]\n        except KeyError:\n            try:\n                self.current_time = self._handle[\"/level_0\"].attrs[\"time\"]\n            except KeyError:\n                self.current_time = 0.0\n\n    def _determine_periodic(self):\n        # we default to true unless the HDF5 file says otherwise\n        is_periodic = np.array([True, True, True])\n        for dir in range(self.dimensionality):\n            try:\n                is_periodic[dir] = self._handle[\"/level_0\"].attrs[f\"is_periodic_{dir}\"]\n            except KeyError:\n                is_periodic[dir] = True\n        self._periodicity = tuple(is_periodic)\n\n    def _calc_left_edge(self):\n        fileh = self._handle\n        dx0 = fileh[\"/level_0\"].attrs[\"dx\"]\n        D = self.dimensionality\n        LE = dx0 * ((np.array(list(fileh[\"/level_0\"].attrs[\"prob_domain\"])))[0:D])\n        return LE\n\n    def _calc_right_edge(self):\n        fileh = self._handle\n        dx0 = fileh[\"/level_0\"].attrs[\"dx\"]\n        D = self.dimensionality\n        RE = dx0 * ((np.array(list(fileh[\"/level_0\"].attrs[\"prob_domain\"])))[D:] + 1)\n        return RE\n\n    def _calc_domain_dimensions(self):\n        fileh = self._handle\n        D = self.dimensionality\n        L_index = (np.array(list(fileh[\"/level_0\"].attrs[\"prob_domain\"])))[0:D]\n        R_index = (np.array(list(fileh[\"/level_0\"].attrs[\"prob_domain\"])))[D:] + 1\n        return R_index - L_index\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        if not is_chombo_hdf5(filename):\n            return False\n\n        pluto_ini_file_exists = False\n        orion2_ini_file_exists = False\n\n        if isinstance(filename, str):\n            dir_name = os.path.dirname(os.path.abspath(filename))\n            pluto_ini_filename = os.path.join(dir_name, \"pluto.ini\")\n            orion2_ini_filename = os.path.join(dir_name, \"orion2.ini\")\n            pluto_ini_file_exists = os.path.isfile(pluto_ini_filename)\n            orion2_ini_file_exists = os.path.isfile(orion2_ini_filename)\n\n        if not (pluto_ini_file_exists or orion2_ini_file_exists):\n            try:\n                fileh = h5py.File(filename, mode=\"r\")\n                valid = \"Chombo_global\" in fileh[\"/\"]\n                # ORION2 simulations should always have this:\n                valid = valid and \"CeilVA_mass\" not in fileh.attrs.keys()\n                valid = valid and \"Charm_global\" not in fileh.keys()\n                fileh.close()\n                return valid\n            except Exception:\n                pass\n        return False\n\n    @parallel_root_only\n    def print_key_parameters(self):\n        for a in [\n            \"current_time\",\n            \"domain_dimensions\",\n            \"domain_left_edge\",\n            \"domain_right_edge\",\n        ]:\n            if not hasattr(self, a):\n                mylog.error(\"Missing %s in parameter file definition!\", a)\n                continue\n            v = getattr(self, a)\n            mylog.info(\"Parameters: %-25s = %s\", a, v)\n\n\nclass PlutoHierarchy(ChomboHierarchy):\n    def __init__(self, ds, dataset_type=\"chombo_hdf5\"):\n        ChomboHierarchy.__init__(self, ds, dataset_type)\n\n    def _parse_index(self):\n        f = self._handle  # shortcut\n        self.max_level = f.attrs[\"num_levels\"] - 1\n\n        grids = []\n        self.dds_list = []\n        i = 0\n        D = self.dataset.dimensionality\n        for lev_index, lev in enumerate(self._levels):\n            level_number = int(re.match(r\"level_(\\d+)\", lev).groups()[0])\n            try:\n                boxes = f[lev][\"boxes\"][()]\n            except KeyError:\n                boxes = f[lev][\"particles:boxes\"][()]\n            dx = f[lev].attrs[\"dx\"]\n            self.dds_list.append(dx * np.ones(3))\n\n            if D == 1:\n                self.dds_list[lev_index][1] = 1.0\n                self.dds_list[lev_index][2] = 1.0\n\n            if D == 2:\n                self.dds_list[lev_index][2] = 1.0\n\n            for level_id, box in enumerate(boxes):\n                si = np.array([box[f\"lo_{ax}\"] for ax in \"ijk\"[:D]])\n                ei = np.array([box[f\"hi_{ax}\"] for ax in \"ijk\"[:D]])\n\n                if D == 1:\n                    si = np.concatenate((si, [0.0, 0.0]))\n                    ei = np.concatenate((ei, [0.0, 0.0]))\n\n                if D == 2:\n                    si = np.concatenate((si, [0.0]))\n                    ei = np.concatenate((ei, [0.0]))\n\n                pg = self.grid(len(grids), self, level=level_number, start=si, stop=ei)\n                grids.append(pg)\n                grids[-1]._level_id = level_id\n                self.grid_levels[i] = level_number\n                self.grid_left_edge[i] = (\n                    self.dds_list[lev_index] * si.astype(self.float_type)\n                    + self.domain_left_edge.value\n                )\n                self.grid_right_edge[i] = (\n                    self.dds_list[lev_index] * (ei.astype(self.float_type) + 1)\n                    + self.domain_left_edge.value\n                )\n                self.grid_particle_count[i] = 0\n                self.grid_dimensions[i] = ei - si + 1\n                i += 1\n        self.grids = np.empty(len(grids), dtype=\"object\")\n        for gi, g in enumerate(grids):\n            self.grids[gi] = g\n\n\nclass PlutoDataset(ChomboDataset):\n    _index_class = PlutoHierarchy\n    _field_info_class = PlutoFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"chombo_hdf5\",\n        storage_filename=None,\n        ini_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        ChomboDataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            storage_filename,\n            ini_filename,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Check to see whether a 'pluto.ini' file\n        exists in the plot file directory. If one does, attempt to parse it.\n        Otherwise grab the dimensions from the hdf5 file.\n        \"\"\"\n\n        pluto_ini_file_exists = False\n        dir_name = os.path.dirname(os.path.abspath(self.fullplotdir))\n        pluto_ini_filename = os.path.join(dir_name, \"pluto.ini\")\n        pluto_ini_file_exists = os.path.isfile(pluto_ini_filename)\n\n        self.dimensionality = self._handle[\"Chombo_global/\"].attrs[\"SpaceDim\"]\n        self.domain_dimensions = self._calc_domain_dimensions()\n        self.refine_by = self._handle[\"/level_0\"].attrs[\"ref_ratio\"]\n\n        if pluto_ini_file_exists:\n            lines = [line.strip() for line in open(pluto_ini_filename)]\n            domain_left_edge = np.zeros(self.dimensionality)\n            domain_right_edge = np.zeros(self.dimensionality)\n            for il, ll in enumerate(\n                lines[\n                    lines.index(\"[Grid]\") + 2 : lines.index(\"[Grid]\")\n                    + 2\n                    + self.dimensionality\n                ]\n            ):\n                domain_left_edge[il] = float(ll.split()[2])\n                domain_right_edge[il] = float(ll.split()[-1])\n            self._periodicity = [0] * 3\n            for il, ll in enumerate(\n                lines[\n                    lines.index(\"[Boundary]\") + 2 : lines.index(\"[Boundary]\")\n                    + 2\n                    + 6 : 2\n                ]\n            ):\n                self.periodicity[il] = ll.split()[1] == \"periodic\"\n            self._periodicity = tuple(self.periodicity)\n            for ll in lines[lines.index(\"[Parameters]\") + 2 :]:\n                if ll.split()[0] == \"GAMMA\":\n                    self.gamma = float(ll.split()[1])\n            self.domain_left_edge = domain_left_edge\n            self.domain_right_edge = domain_right_edge\n        else:\n            self.domain_left_edge = self._calc_left_edge()\n            self.domain_right_edge = self._calc_right_edge()\n            self._periodicity = (True, True, True)\n\n        # if a lower-dimensional dataset, set up pseudo-3D stuff here.\n        if self.dimensionality == 1:\n            self.domain_left_edge = np.concatenate((self.domain_left_edge, [0.0, 0.0]))\n            self.domain_right_edge = np.concatenate(\n                (self.domain_right_edge, [1.0, 1.0])\n            )\n            self.domain_dimensions = np.concatenate((self.domain_dimensions, [1, 1]))\n\n        if self.dimensionality == 2:\n            self.domain_left_edge = np.concatenate((self.domain_left_edge, [0.0]))\n            self.domain_right_edge = np.concatenate((self.domain_right_edge, [1.0]))\n            self.domain_dimensions = np.concatenate((self.domain_dimensions, [1]))\n\n        self._determine_current_time()\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        if not is_chombo_hdf5(filename):\n            return False\n\n        pluto_ini_file_exists = False\n\n        if isinstance(filename, str):\n            dir_name = os.path.dirname(os.path.abspath(filename))\n            pluto_ini_filename = os.path.join(dir_name, \"pluto.ini\")\n            pluto_ini_file_exists = os.path.isfile(pluto_ini_filename)\n\n        if pluto_ini_file_exists:\n            return True\n\n        return False\n\n\nclass Orion2Hierarchy(ChomboHierarchy):\n    def __init__(self, ds, dataset_type=\"orion_chombo_native\"):\n        ChomboHierarchy.__init__(self, ds, dataset_type)\n\n    def _detect_output_fields(self):\n        # look for fluid fields\n        output_fields = []\n        for key, val in self._handle.attrs.items():\n            if key.startswith(\"component\"):\n                output_fields.append(val.decode(\"ascii\"))\n        self.field_list = [(\"chombo\", c) for c in output_fields]\n\n        # look for particle fields\n        self.particle_filename = self.index_filename[:-4] + \"sink\"\n        if not os.path.exists(self.particle_filename):\n            return\n        pfield_list = [(\"io\", str(c)) for c in self.io.particle_field_index.keys()]\n        self.field_list.extend(pfield_list)\n\n    def _read_particles(self):\n        if not os.path.exists(self.particle_filename):\n            return\n        with open(self.particle_filename) as f:\n            lines = f.readlines()\n            self.num_stars = int(lines[0].strip().split(\" \")[0])\n            for num, line in enumerate(lines[1:]):\n                particle_position_x = float(line.split(\" \")[1])\n                particle_position_y = float(line.split(\" \")[2])\n                particle_position_z = float(line.split(\" \")[3])\n                coord = [particle_position_x, particle_position_y, particle_position_z]\n                # for each particle, determine which grids contain it\n                # copied from object_finding_mixin.py\n                mask = np.ones(self.num_grids)\n                for i in range(len(coord)):\n                    np.choose(\n                        np.greater(self.grid_left_edge.d[:, i], coord[i]),\n                        (mask, 0),\n                        mask,\n                    )\n                    np.choose(\n                        np.greater(self.grid_right_edge.d[:, i], coord[i]),\n                        (0, mask),\n                        mask,\n                    )\n                ind = np.where(mask == 1)\n                selected_grids = self.grids[ind]\n                # in orion, particles always live on the finest level.\n                # so, we want to assign the particle to the finest of\n                # the grids we just found\n                if len(selected_grids) != 0:\n                    grid = sorted(selected_grids, key=lambda grid: grid.Level)[-1]\n                    ind = np.where(self.grids == grid)[0][0]\n                    self.grid_particle_count[ind] += 1\n                    self.grids[ind].NumberOfParticles += 1\n\n                    # store the position in the *.sink file for fast access.\n                    try:\n                        self.grids[ind]._particle_line_numbers.append(num + 1)\n                    except AttributeError:\n                        self.grids[ind]._particle_line_numbers = [num + 1]\n\n\nclass Orion2Dataset(ChomboDataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = Orion2Hierarchy\n    _field_info_class = Orion2FieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"orion_chombo_native\",\n        storage_filename=None,\n        ini_filename=None,\n        units_override=None,\n        default_species_fields=None,\n    ):\n        ChomboDataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            storage_filename,\n            ini_filename,\n            units_override=units_override,\n            default_species_fields=default_species_fields,\n        )\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Check to see whether an 'orion2.ini' file\n        exists in the plot file directory. If one does, attempt to parse it.\n        Otherwise grab the dimensions from the hdf5 file.\n        \"\"\"\n\n        orion2_ini_file_exists = False\n        dir_name = os.path.dirname(os.path.abspath(self.fullplotdir))\n        orion2_ini_filename = os.path.join(dir_name, \"orion2.ini\")\n        orion2_ini_file_exists = os.path.isfile(orion2_ini_filename)\n\n        if orion2_ini_file_exists:\n            self._parse_inputs_file(\"orion2.ini\")\n        self.dimensionality = 3\n        self.domain_left_edge = self._calc_left_edge()\n        self.domain_right_edge = self._calc_right_edge()\n        self.domain_dimensions = self._calc_domain_dimensions()\n        self.refine_by = self._handle[\"/level_0\"].attrs[\"ref_ratio\"]\n        self._determine_periodic()\n        self._determine_current_time()\n\n    def _parse_inputs_file(self, ini_filename):\n        self.fullplotdir = os.path.abspath(self.parameter_filename)\n        self.ini_filename = self._localize(self.ini_filename, ini_filename)\n        lines = open(self.ini_filename).readlines()\n        # read the file line by line, storing important parameters\n        for line in lines:\n            try:\n                param, sep, vals = line.partition(\"=\")\n                if not sep:\n                    # No = sign present, so split by space instead\n                    param, sep, vals = line.partition(\" \")\n                param = param.strip()\n                vals = vals.strip()\n                if not param:  # skip blank lines\n                    continue\n                if param[0] == \"#\":  # skip comment lines\n                    continue\n                if param[0] == \"[\":  # skip stanza headers\n                    continue\n                vals = vals.partition(\"#\")[0]  # strip trailing comments\n                try:\n                    self.parameters[param] = np.int64(vals)\n                except ValueError:\n                    try:\n                        self.parameters[param] = np.float64(vals)\n                    except ValueError:\n                        self.parameters[param] = vals\n            except ValueError:\n                mylog.error(\"ValueError: '%s'\", line)\n            if param == \"GAMMA\":\n                self.gamma = np.float64(vals)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        if not is_chombo_hdf5(filename):\n            return False\n\n        pluto_ini_file_exists = False\n        orion2_ini_file_exists = False\n\n        if isinstance(filename, str):\n            dir_name = os.path.dirname(os.path.abspath(filename))\n            pluto_ini_filename = os.path.join(dir_name, \"pluto.ini\")\n            orion2_ini_filename = os.path.join(dir_name, \"orion2.ini\")\n            pluto_ini_file_exists = os.path.isfile(pluto_ini_filename)\n            orion2_ini_file_exists = os.path.isfile(orion2_ini_filename)\n\n        if orion2_ini_file_exists:\n            return True\n\n        if not pluto_ini_file_exists:\n            try:\n                fileh = h5py.File(filename, mode=\"r\")\n                valid = \"CeilVA_mass\" in fileh.attrs.keys()\n                valid = (\n                    \"Chombo_global\" in fileh[\"/\"] and \"Charm_global\" not in fileh[\"/\"]\n                )\n                valid = valid and \"CeilVA_mass\" in fileh.attrs.keys()\n                fileh.close()\n                return valid\n            except Exception:\n                pass\n        return False\n\n\nclass ChomboPICHierarchy(ChomboHierarchy):\n    def __init__(self, ds, dataset_type=\"chombo_hdf5\"):\n        ChomboHierarchy.__init__(self, ds, dataset_type)\n\n\nclass ChomboPICDataset(ChomboDataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = ChomboPICHierarchy\n    _field_info_class = ChomboPICFieldInfo3D\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"chombo_hdf5\",\n        storage_filename=None,\n        ini_filename=None,\n        units_override=None,\n    ):\n        ChomboDataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            storage_filename,\n            ini_filename,\n            units_override=units_override,\n        )\n\n        if self.dimensionality == 1:\n            self._field_info_class = ChomboPICFieldInfo1D\n\n        if self.dimensionality == 2:\n            self._field_info_class = ChomboPICFieldInfo2D\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not valid_hdf5_signature(filename):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        if not is_chombo_hdf5(filename):\n            return False\n\n        pluto_ini_file_exists = False\n        orion2_ini_file_exists = False\n\n        if isinstance(filename, str):\n            dir_name = os.path.dirname(os.path.abspath(filename))\n            pluto_ini_filename = os.path.join(dir_name, \"pluto.ini\")\n            orion2_ini_filename = os.path.join(dir_name, \"orion2.ini\")\n            pluto_ini_file_exists = os.path.isfile(pluto_ini_filename)\n            orion2_ini_file_exists = os.path.isfile(orion2_ini_filename)\n\n        if orion2_ini_file_exists:\n            return False\n\n        if pluto_ini_file_exists:\n            return False\n\n        try:\n            fileh = h5py.File(filename, mode=\"r\")\n            valid = \"Charm_global\" in fileh[\"/\"]\n            fileh.close()\n            return valid\n        except Exception:\n            pass\n        return False\n"
  },
  {
    "path": "yt/frontends/chombo/definitions.py",
    "content": ""
  },
  {
    "path": "yt/frontends/chombo/fields.py",
    "content": "import numpy as np\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import (\n    FieldInfoContainer,\n    particle_deposition_functions,\n    particle_vector_functions,\n    standard_particle_fields,\n)\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.utilities.exceptions import YTFieldNotFound\n\nrho_units = \"code_mass / code_length**3\"\nmom_units = \"code_mass / (code_time * code_length**2)\"\neden_units = \"code_mass / (code_time**2 * code_length)\"  # erg / cm^3\nvel_units = \"code_length / code_time\"\nb_units = \"code_magnetic\"\n\n\nclass ChomboFieldInfo(FieldInfoContainer):\n    # no custom behaviour is needed yet\n    pass\n\n\n# Orion 2 Fields\n# We duplicate everything here from Boxlib, because we want to be able to\n# subclass it and that can be somewhat tricky.\nclass Orion2FieldInfo(ChomboFieldInfo):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (rho_units, [\"density\"], None)),\n        (\"energy-density\", (eden_units, [\"total_energy_density\"], None)),\n        (\"radiation-energy-density\", (eden_units, [\"radiation_energy_density\"], None)),\n        (\"X-momentum\", (mom_units, [\"momentum_density_x\"], None)),\n        (\"Y-momentum\", (mom_units, [\"momentum_density_y\"], None)),\n        (\"Z-momentum\", (mom_units, [\"momentum_density_z\"], None)),\n        (\"temperature\", (\"K\", [\"temperature\"], None)),\n        (\"X-magnfield\", (b_units, [], None)),\n        (\"Y-magnfield\", (b_units, [], None)),\n        (\"Z-magnfield\", (b_units, [], None)),\n        (\"directrad-dedt-density\", (eden_units, [\"directrad-dedt-density\"], None)),\n        (\"directrad-dpxdt-density\", (mom_units, [\"directrad-dpxdt-density\"], None)),\n        (\"directrad-dpydt-density\", (mom_units, [\"directrad-dpydt-density\"], None)),\n        (\"directrad-dpzdt-density\", (mom_units, [\"directrad-dpzdt-density\"], None)),\n    )\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_position_z\", (\"code_length\", [], None)),\n        (\"particle_momentum_x\", (\"code_mass*code_length/code_time\", [], None)),\n        (\"particle_momentum_y\", (\"code_mass*code_length/code_time\", [], None)),\n        (\"particle_momentum_z\", (\"code_mass*code_length/code_time\", [], None)),\n        # Note that these are *internal* agmomen\n        (\"particle_angmomen_x\", (\"code_length**2/code_time\", [], None)),\n        (\"particle_angmomen_y\", (\"code_length**2/code_time\", [], None)),\n        (\"particle_angmomen_z\", (\"code_length**2/code_time\", [], None)),\n        (\"particle_mlast\", (\"code_mass\", [], None)),\n        (\"particle_r\", (\"code_length\", [], None)),\n        (\"particle_mdeut\", (\"code_mass\", [], None)),\n        (\"particle_n\", (\"\", [], None)),\n        (\"particle_mdot\", (\"code_mass/code_time\", [], None)),\n        (\"particle_burnstate\", (\"\", [], None)),\n        (\"particle_luminosity\", (\"\", [], None)),\n        (\"particle_id\", (\"\", [\"particle_index\"], None)),\n    )\n\n    def setup_particle_fields(self, ptype):\n        def _get_vel(axis):\n            def velocity(data):\n                return (\n                    data[ptype, f\"particle_momentum_{axis}\"]\n                    / data[ptype, \"particle_mass\"]\n                )\n\n            return velocity\n\n        for ax in \"xyz\":\n            self.add_field(\n                (ptype, f\"particle_velocity_{ax}\"),\n                sampling_type=\"particle\",\n                function=_get_vel(ax),\n                units=\"code_length/code_time\",\n            )\n\n        super().setup_particle_fields(ptype)\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        unit_system = self.ds.unit_system\n\n        def _thermal_energy_density(data):\n            try:\n                return (\n                    data[\"chombo\", \"energy-density\"]\n                    - data[\"gas\", \"kinetic_energy_density\"]\n                    - data[\"gas\", \"magnetic_energy_density\"]\n                )\n            except YTFieldNotFound:\n                return (\n                    data[\"chombo\", \"energy-density\"]\n                    - data[\"gas\", \"kinetic_energy_density\"]\n                )\n\n        def _specific_thermal_energy(data):\n            return data[\"gas\", \"thermal_energy_density\"] / data[\"gas\", \"density\"]\n\n        def _magnetic_energy_density(data):\n            ret = data[\"chombo\", \"X-magnfield\"] ** 2\n            if data.ds.dimensionality > 1:\n                ret = ret + data[\"chombo\", \"Y-magnfield\"] ** 2\n            if data.ds.dimensionality > 2:\n                ret = ret + data[\"chombo\", \"Z-magnfield\"] ** 2\n            return ret / 8.0 / np.pi\n\n        def _specific_magnetic_energy(data):\n            return data[\"gas\", \"specific_magnetic_energy\"] / data[\"gas\", \"density\"]\n\n        def _kinetic_energy_density(data):\n            p2 = data[\"chombo\", \"X-momentum\"] ** 2\n            if data.ds.dimensionality > 1:\n                p2 = p2 + data[\"chombo\", \"Y-momentum\"] ** 2\n            if data.ds.dimensionality > 2:\n                p2 = p2 + data[\"chombo\", \"Z-momentum\"] ** 2\n            return 0.5 * p2 / data[\"gas\", \"density\"]\n\n        def _specific_kinetic_energy(data):\n            return data[\"gas\", \"kinetic_energy_density\"] / data[\"gas\", \"density\"]\n\n        def _temperature(data):\n            c_v = data.ds.quan(data.ds.parameters[\"radiation.const_cv\"], \"erg/g/K\")\n            return data[\"gas\", \"specific_thermal_energy\"] / c_v\n\n        def _get_vel(axis):\n            def velocity(data):\n                return data[\"gas\", f\"momentum_density_{axis}\"] / data[\"gas\", \"density\"]\n\n            return velocity\n\n        for ax in \"xyz\":\n            self.add_field(\n                (\"gas\", f\"velocity_{ax}\"),\n                sampling_type=\"cell\",\n                function=_get_vel(ax),\n                units=unit_system[\"velocity\"],\n            )\n        self.add_field(\n            (\"gas\", \"specific_thermal_energy\"),\n            sampling_type=\"cell\",\n            function=_specific_thermal_energy,\n            units=unit_system[\"specific_energy\"],\n        )\n        self.add_field(\n            (\"gas\", \"thermal_energy_density\"),\n            sampling_type=\"cell\",\n            function=_thermal_energy_density,\n            units=unit_system[\"pressure\"],\n        )\n        self.add_field(\n            (\"gas\", \"kinetic_energy_density\"),\n            sampling_type=\"cell\",\n            function=_kinetic_energy_density,\n            units=unit_system[\"pressure\"],\n        )\n        self.add_field(\n            (\"gas\", \"specific_kinetic_energy\"),\n            sampling_type=\"cell\",\n            function=_specific_kinetic_energy,\n            units=unit_system[\"specific_energy\"],\n        )\n        self.add_field(\n            (\"gas\", \"magnetic_energy_density\"),\n            sampling_type=\"cell\",\n            function=_magnetic_energy_density,\n            units=unit_system[\"pressure\"],\n        )\n        self.add_field(\n            (\"gas\", \"specific_magnetic_energy\"),\n            sampling_type=\"cell\",\n            function=_specific_magnetic_energy,\n            units=unit_system[\"specific_energy\"],\n        )\n        self.add_field(\n            (\"gas\", \"temperature\"),\n            sampling_type=\"cell\",\n            function=_temperature,\n            units=unit_system[\"temperature\"],\n        )\n\n        setup_magnetic_field_aliases(\n            self, \"chombo\", [f\"{ax}-magnfield\" for ax in \"XYZ\"]\n        )\n\n\nclass ChomboPICFieldInfo3D(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (rho_units, [\"density\", \"Density\"], None)),\n        (\n            \"potential\",\n            (\"code_length**2 / code_time**2\", [\"potential\", \"Potential\"], None),\n        ),\n        (\"gravitational_field_x\", (\"code_length / code_time**2\", [], None)),\n        (\"gravitational_field_y\", (\"code_length / code_time**2\", [], None)),\n        (\"gravitational_field_z\", (\"code_length / code_time**2\", [], None)),\n    )\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_position_z\", (\"code_length\", [], None)),\n        (\"particle_velocity_x\", (\"code_length / code_time\", [], None)),\n        (\"particle_velocity_y\", (\"code_length / code_time\", [], None)),\n        (\"particle_velocity_z\", (\"code_length / code_time\", [], None)),\n    )\n\n    # I am re-implementing this here to override a few default behaviors:\n    # I don't want to skip output units for code_length and I do want\n    # particle_fields to default to take_log = False.\n    def setup_particle_fields(self, ptype, ftype=\"gas\", num_neighbors=64):\n        skip_output_units = ()\n        for f, (units, aliases, dn) in sorted(self.known_particle_fields):\n            units = self.ds.field_units.get((ptype, f), units)\n            if (\n                f in aliases or ptype not in self.ds.particle_types_raw\n            ) and units not in skip_output_units:\n                u = Unit(units, registry=self.ds.unit_registry)\n                output_units = str(u.get_cgs_equivalent())\n            else:\n                output_units = units\n            if (ptype, f) not in self.field_list:\n                continue\n            self.add_output_field(\n                (ptype, f),\n                sampling_type=\"particle\",\n                units=units,\n                display_name=dn,\n                output_units=output_units,\n                take_log=False,\n            )\n            for alias in aliases:\n                self.alias((ptype, alias), (ptype, f), units=output_units)\n\n        ppos_fields = [f\"particle_position_{ax}\" for ax in \"xyz\"]\n        pvel_fields = [f\"particle_velocity_{ax}\" for ax in \"xyz\"]\n        particle_vector_functions(ptype, ppos_fields, pvel_fields, self)\n\n        particle_deposition_functions(ptype, \"particle_position\", \"particle_mass\", self)\n        standard_particle_fields(self, ptype)\n        # Now we check for any leftover particle fields\n        for field in sorted(self.field_list):\n            if field in self:\n                continue\n            if not isinstance(field, tuple):\n                raise RuntimeError\n            if field[0] not in self.ds.particle_types:\n                continue\n            self.add_output_field(\n                field,\n                sampling_type=\"particle\",\n                units=self.ds.field_units.get(field, \"\"),\n            )\n        self.setup_smoothed_fields(ptype, num_neighbors=num_neighbors, ftype=ftype)\n\n\ndef _dummy_position(data):\n    return 0.5 * np.ones_like(data[\"all\", \"particle_position_x\"])\n\n\ndef _dummy_velocity(data):\n    return np.zeros_like(data[\"all\", \"particle_velocity_x\"])\n\n\ndef _dummy_field(data):\n    return 0.0 * data[\"chombo\", \"gravitational_field_x\"]\n\n\nfluid_field_types = [\"chombo\", \"gas\"]\nparticle_field_types = [\"io\", \"all\"]\n\n\nclass ChomboPICFieldInfo2D(ChomboPICFieldInfo3D):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (rho_units, [\"density\", \"Density\"], None)),\n        (\n            \"potential\",\n            (\"code_length**2 / code_time**2\", [\"potential\", \"Potential\"], None),\n        ),\n        (\"gravitational_field_x\", (\"code_length / code_time**2\", [], None)),\n        (\"gravitational_field_y\", (\"code_length / code_time**2\", [], None)),\n    )\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_velocity_x\", (\"code_length / code_time\", [], None)),\n        (\"particle_velocity_y\", (\"code_length / code_time\", [], None)),\n    )\n\n    def __init__(self, ds, field_list):\n        super().__init__(ds, field_list)\n\n        for ftype in fluid_field_types:\n            self.add_field(\n                (ftype, \"gravitational_field_z\"),\n                sampling_type=\"cell\",\n                function=_dummy_field,\n                units=\"code_length / code_time**2\",\n            )\n\n        for ptype in particle_field_types:\n            self.add_field(\n                (ptype, \"particle_position_z\"),\n                sampling_type=\"particle\",\n                function=_dummy_position,\n                units=\"code_length\",\n            )\n\n            self.add_field(\n                (ptype, \"particle_velocity_z\"),\n                sampling_type=\"particle\",\n                function=_dummy_velocity,\n                units=\"code_length / code_time\",\n            )\n\n\nclass ChomboPICFieldInfo1D(ChomboPICFieldInfo3D):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (rho_units, [\"density\", \"Density\"], None)),\n        (\n            \"potential\",\n            (\"code_length**2 / code_time**2\", [\"potential\", \"Potential\"], None),\n        ),\n        (\"gravitational_field_x\", (\"code_length / code_time**2\", [], None)),\n    )\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_velocity_x\", (\"code_length / code_time\", [], None)),\n    )\n\n    def __init__(self, ds, field_list):\n        super().__init__(ds, field_list)\n\n        for ftype in fluid_field_types:\n            self.add_field(\n                (ftype, \"gravitational_field_y\"),\n                sampling_type=\"cell\",\n                function=_dummy_field,\n                units=\"code_length / code_time**2\",\n            )\n\n            self.add_field(\n                (ftype, \"gravitational_field_z\"),\n                sampling_type=\"cell\",\n                function=_dummy_field,\n                units=\"code_length / code_time**2\",\n            )\n\n        for ptype in particle_field_types:\n            self.add_field(\n                (ptype, \"particle_position_y\"),\n                sampling_type=\"particle\",\n                function=_dummy_position,\n                units=\"code_length\",\n            )\n            self.add_field(\n                (ptype, \"particle_position_z\"),\n                sampling_type=\"particle\",\n                function=_dummy_position,\n                units=\"code_length\",\n            )\n            self.add_field(\n                (ptype, \"particle_velocity_y\"),\n                sampling_type=\"particle\",\n                function=_dummy_velocity,\n                units=\"code_length / code_time\",\n            )\n            self.add_field(\n                (ptype, \"particle_velocity_z\"),\n                sampling_type=\"particle\",\n                function=_dummy_velocity,\n                units=\"code_length / code_time\",\n            )\n\n\nclass PlutoFieldInfo(ChomboFieldInfo):\n    known_other_fields: KnownFieldsT = (\n        (\"rho\", (rho_units, [\"density\"], None)),\n        (\"prs\", (\"code_mass / (code_length * code_time**2)\", [\"pressure\"], None)),\n        (\"vx1\", (vel_units, [\"velocity_x\"], None)),\n        (\"vx2\", (vel_units, [\"velocity_y\"], None)),\n        (\"vx3\", (vel_units, [\"velocity_z\"], None)),\n        (\"bx1\", (b_units, [], None)),\n        (\"bx2\", (b_units, [], None)),\n        (\"bx3\", (b_units, [], None)),\n    )\n\n    known_particle_fields = ()\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        setup_magnetic_field_aliases(self, \"chombo\", [f\"bx{ax}\" for ax in [1, 2, 3]])\n"
  },
  {
    "path": "yt/frontends/chombo/io.py",
    "content": "import re\n\nimport numpy as np\n\nfrom yt.geometry.selection_routines import GridSelector\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\n\n\nclass IOHandlerChomboHDF5(BaseIOHandler):\n    _dataset_type = \"chombo_hdf5\"\n    _offset_string = \"data:offsets=0\"\n    _data_string = \"data:datatype=0\"\n    _offsets = None\n\n    def __init__(self, ds, *args, **kwargs):\n        BaseIOHandler.__init__(self, ds, *args, **kwargs)\n        self.ds = ds\n        self._handle = ds._handle\n        self.dim = self._handle[\"Chombo_global/\"].attrs[\"SpaceDim\"]\n        self._read_ghost_info()\n        if self._offset_string not in self._handle[\"level_0\"]:\n            self._calculate_offsets()\n\n    def _calculate_offsets(self):\n        def box_size(corners):\n            size = 1\n            for idim in range(self.dim):\n                size *= corners[idim + self.dim] - corners[idim] + 1\n            return size\n\n        self._offsets = {}\n        num_comp = self._handle.attrs[\"num_components\"]\n        level = 0\n        while True:\n            lname = f\"level_{level}\"\n            if lname not in self._handle:\n                break\n            boxes = self._handle[\"level_0\"][\"boxes\"][()]\n            box_sizes = np.array([box_size(box) for box in boxes])\n\n            offsets = np.cumsum(box_sizes * num_comp, dtype=\"int64\")\n            offsets -= offsets[0]\n            self._offsets[level] = offsets\n            level += 1\n\n    def _read_ghost_info(self):\n        try:\n            self.ghost = tuple(\n                self._handle[\"level_0/data_attributes\"].attrs[\"outputGhost\"]\n            )\n            # pad with zeros if the dataset is low-dimensional\n            self.ghost += (3 - self.dim) * (0,)\n            self.ghost = np.array(self.ghost)\n        except KeyError:\n            # assume zero ghosts if outputGhosts not present\n            self.ghost = np.zeros(self.dim, \"int64\")\n\n    _field_dict = None\n\n    @property\n    def field_dict(self):\n        if self._field_dict is not None:\n            return self._field_dict\n        field_dict = {}\n        for key, val in self._handle.attrs.items():\n            if key.startswith(\"component_\"):\n                comp_number = int(re.match(r\"component_(\\d+)\", key).groups()[0])\n                field_dict[val.decode(\"utf-8\")] = comp_number\n        self._field_dict = field_dict\n        return self._field_dict\n\n    _particle_field_index = None\n\n    @property\n    def particle_field_index(self):\n        if self._particle_field_index is not None:\n            return self._particle_field_index\n        field_dict = {}\n        for key, val in self._handle.attrs.items():\n            if key.startswith(\"particle_\"):\n                comp_number = int(\n                    re.match(r\"particle_component_(\\d+)\", key).groups()[0]\n                )\n                field_dict[val.decode(\"ascii\")] = comp_number\n        self._particle_field_index = field_dict\n        return self._particle_field_index\n\n    def _read_data(self, grid, field):\n        lstring = f\"level_{grid.Level}\"\n        lev = self._handle[lstring]\n        dims = grid.ActiveDimensions\n        shape = dims + 2 * self.ghost\n        boxsize = shape.prod()\n\n        if self._offsets is not None:\n            grid_offset = self._offsets[grid.Level][grid._level_id]\n        else:\n            grid_offset = lev[self._offset_string][grid._level_id]\n        start = grid_offset + self.field_dict[field] * boxsize\n        stop = start + boxsize\n        data = lev[self._data_string][start:stop]\n        data_no_ghost = data.reshape(shape, order=\"F\")\n        ghost_slice = tuple(\n            slice(g, g + d) for g, d in zip(self.ghost, dims, strict=True)\n        )\n        ghost_slice = ghost_slice[0 : self.dim]\n        return data_no_ghost[ghost_slice]\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        rv = {}\n        chunks = list(chunks)\n        fields.sort(key=lambda a: self.field_dict[a[1]])\n        if isinstance(selector, GridSelector):\n            if not (len(chunks) == len(chunks[0].objs) == 1):\n                raise RuntimeError\n            grid = chunks[0].objs[0]\n            for ftype, fname in fields:\n                rv[ftype, fname] = self._read_data(grid, fname)\n            return rv\n        if size is None:\n            size = sum(g.count(selector) for chunk in chunks for g in chunk.objs)\n        for field in fields:\n            ftype, fname = field\n            fsize = size\n            rv[field] = np.empty(fsize, dtype=\"float64\")\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s grids\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n\n        ind = 0\n        for chunk in chunks:\n            for g in chunk.objs:\n                nd = 0\n                for field in fields:\n                    ftype, fname = field\n                    data = self._read_data(g, fname)\n                    nd = g.select(selector, data, rv[field], ind)  # caches\n                ind += nd\n        return rv\n\n    def _read_particle_selection(self, chunks, selector, fields):\n        rv = {}\n        chunks = list(chunks)\n\n        if isinstance(selector, GridSelector):\n            if not (len(chunks) == len(chunks[0].objs) == 1):\n                raise RuntimeError\n\n            grid = chunks[0].objs[0]\n\n            for ftype, fname in fields:\n                rv[ftype, fname] = self._read_particles(grid, fname)\n\n            return rv\n\n        rv = {f: np.array([]) for f in fields}\n        for chunk in chunks:\n            for grid in chunk.objs:\n                for ftype, fname in fields:\n                    data = self._read_particles(grid, fname)\n                    rv[ftype, fname] = np.concatenate((data, rv[ftype, fname]))\n        return rv\n\n    def _read_particles(self, grid, name):\n        field_index = self.particle_field_index[name]\n        lev = f\"level_{grid.Level}\"\n\n        particles_per_grid = self._handle[lev][\"particles:offsets\"][()]\n        items_per_particle = len(self._particle_field_index)\n\n        # compute global offset position\n        offsets = items_per_particle * np.cumsum(particles_per_grid)\n        offsets = np.append(np.array([0]), offsets)\n        offsets = np.array(offsets, dtype=np.int64)\n\n        # convert between the global grid id and the id on this level\n        grid_levels = np.array([g.Level for g in self.ds.index.grids])\n        grid_ids = np.array([g.id for g in self.ds.index.grids])\n        grid_level_offset = grid_ids[np.where(grid_levels == grid.Level)[0][0]]\n        lo = grid.id - grid_level_offset\n        hi = lo + 1\n\n        # handle the case where this grid has no particles\n        if offsets[lo] == offsets[hi]:\n            return np.array([], dtype=np.float64)\n\n        data = self._handle[lev][\"particles:data\"][offsets[lo] : offsets[hi]]\n        return np.asarray(\n            data[field_index::items_per_particle], dtype=np.float64, order=\"F\"\n        )\n\n\ndef parse_orion_sinks(fn):\n    r\"\"\"\n    Orion sink particles are stored in text files. This function\n    is for figuring what particle fields are present based on the\n    number of entries per line in the \\*.sink file.\n    \"\"\"\n\n    # Figure out the format of the particle file\n    with open(fn) as f:\n        lines = f.readlines()\n\n    try:\n        line = lines[1]\n    except IndexError:\n        # a particle file exists, but there is only one line,\n        # so no sinks have been created yet.\n        index = {}\n        return index\n\n    # The basic fields that all sink particles have\n    index = {\n        \"particle_mass\": 0,\n        \"particle_position_x\": 1,\n        \"particle_position_y\": 2,\n        \"particle_position_z\": 3,\n        \"particle_momentum_x\": 4,\n        \"particle_momentum_y\": 5,\n        \"particle_momentum_z\": 6,\n        \"particle_angmomen_x\": 7,\n        \"particle_angmomen_y\": 8,\n        \"particle_angmomen_z\": 9,\n        \"particle_id\": -1,\n    }\n\n    if len(line.strip().split()) == 11:\n        # these are vanilla sinks, do nothing\n        pass\n\n    elif len(line.strip().split()) == 17:\n        # these are old-style stars, add stellar model parameters\n        index[\"particle_mlast\"] = 10\n        index[\"particle_r\"] = 11\n        index[\"particle_mdeut\"] = 12\n        index[\"particle_n\"] = 13\n        index[\"particle_mdot\"] = 14\n        index[\"particle_burnstate\"] = 15\n\n    elif len(line.strip().split()) == 18 or len(line.strip().split()) == 19:\n        # these are the newer style, add luminosity as well\n        index[\"particle_mlast\"] = 10\n        index[\"particle_r\"] = 11\n        index[\"particle_mdeut\"] = 12\n        index[\"particle_n\"] = 13\n        index[\"particle_mdot\"] = 14\n        index[\"particle_burnstate\"] = 15\n        index[\"particle_luminosity\"] = 16\n    else:\n        # give a warning if none of the above apply:\n        mylog.warning(\"Warning - could not figure out particle output file\")\n        mylog.warning(\"These results could be nonsense!\")\n\n    return index\n\n\nclass IOHandlerOrion2HDF5(IOHandlerChomboHDF5):\n    _dataset_type = \"orion_chombo_native\"\n\n    _particle_field_index = None\n\n    @property\n    def particle_field_index(self):\n        fn = self.ds.fullplotdir[:-4] + \"sink\"\n\n        index = parse_orion_sinks(fn)\n\n        self._particle_field_index = index\n        return self._particle_field_index\n\n    def _read_particles(self, grid, field):\n        \"\"\"\n        parses the Orion Star Particle text files\n\n        \"\"\"\n\n        particles = []\n\n        if grid.NumberOfParticles == 0:\n            return np.array(particles)\n\n        def read(line, field):\n            entry = line.strip().split(\" \")[self.particle_field_index[field]]\n            return float(entry)\n\n        try:\n            lines = self._cached_lines\n            for num in grid._particle_line_numbers:\n                line = lines[num]\n                particles.append(read(line, field))\n            return np.array(particles)\n        except AttributeError:\n            fn = grid.ds.fullplotdir[:-4] + \"sink\"\n            with open(fn) as f:\n                lines = f.readlines()\n                self._cached_lines = lines\n                for num in grid._particle_line_numbers:\n                    line = lines[num]\n                    particles.append(read(line, field))\n            return np.array(particles)\n"
  },
  {
    "path": "yt/frontends/chombo/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/chombo/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/chombo/tests/test_outputs.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.frontends.chombo.api import ChomboDataset, Orion2Dataset, PlutoDataset\nfrom yt.testing import requires_file, requires_module, units_override_check\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\n_fields = (\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_magnitude\"),\n    (\"gas\", \"magnetic_field_x\"),\n)\n\ngc = \"GaussianCloud/data.0077.3d.hdf5\"\n\n\n@requires_ds(gc)\ndef test_gc():\n    ds = data_dir_load(gc)\n    assert_equal(str(ds), \"data.0077.3d.hdf5\")\n    for test in small_patch_amr(ds, _fields):\n        test_gc.__name__ = test.description\n        yield test\n\n\ntb = \"TurbBoxLowRes/data.0005.3d.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(tb)\ndef test_tb():\n    ds = data_dir_load(tb)\n    assert_equal(str(ds), \"data.0005.3d.hdf5\")\n    for test in small_patch_amr(ds, _fields):\n        test_tb.__name__ = test.description\n        yield test\n\n\niso = \"IsothermalSphere/data.0000.3d.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(iso)\ndef test_iso():\n    ds = data_dir_load(iso)\n    assert_equal(str(ds), \"data.0000.3d.hdf5\")\n    for test in small_patch_amr(ds, _fields):\n        test_iso.__name__ = test.description\n        yield test\n\n\n_zp_fields = ((\"chombo\", \"rhs\"), (\"chombo\", \"phi\"))\nzp = \"ZeldovichPancake/plt32.2d.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(zp)\ndef test_zp():\n    ds = data_dir_load(zp)\n    assert_equal(str(ds), \"plt32.2d.hdf5\")\n    for test in small_patch_amr(ds, _zp_fields, input_center=\"c\", input_weight=\"rhs\"):\n        test_zp.__name__ = test.description\n        yield test\n\n\nkho = \"KelvinHelmholtz/data.0004.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(kho)\ndef test_kho():\n    ds = data_dir_load(kho)\n    assert_equal(str(ds), \"data.0004.hdf5\")\n    for test in small_patch_amr(ds, _fields):\n        test_kho.__name__ = test.description\n        yield test\n\n\n@requires_module(\"h5py\")\n@requires_file(zp)\ndef test_ChomboDataset():\n    assert isinstance(data_dir_load(zp), ChomboDataset)\n\n\n@requires_module(\"h5py\")\n@requires_file(gc)\ndef test_Orion2Dataset():\n    assert isinstance(data_dir_load(gc), Orion2Dataset)\n\n\n@requires_module(\"h5py\")\n@requires_file(kho)\ndef test_PlutoDataset():\n    assert isinstance(data_dir_load(kho), PlutoDataset)\n\n\n@requires_module(\"h5py\")\n@requires_file(zp)\ndef test_units_override_zp():\n    units_override_check(zp)\n\n\n@requires_module(\"h5py\")\n@requires_file(gc)\ndef test_units_override_gc():\n    units_override_check(gc)\n\n\n@requires_module(\"h5py\")\n@requires_file(kho)\ndef test_units_override_kho():\n    units_override_check(kho)\n"
  },
  {
    "path": "yt/frontends/eagle/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/eagle/api.py",
    "content": "from . import tests\nfrom .data_structures import EagleDataset, EagleNetworkDataset\nfrom .fields import EagleNetworkFieldInfo\nfrom .io import IOHandlerEagleNetwork\n"
  },
  {
    "path": "yt/frontends/eagle/data_structures.py",
    "content": "import numpy as np\n\nimport yt.units\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.frontends.gadget.data_structures import GadgetHDF5Dataset\nfrom yt.frontends.owls.fields import OWLSFieldInfo\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import EagleNetworkFieldInfo\n\n\nclass EagleDataset(GadgetHDF5Dataset):\n    _load_requirements = [\"h5py\"]\n    _particle_mass_name = \"Mass\"\n    _field_info_class: type[FieldInfoContainer] = OWLSFieldInfo\n    _time_readin_ = \"Time\"\n\n    def _parse_parameter_file(self):\n        # read values from header\n        hvals = self._get_hvals()\n        self.parameters = hvals\n\n        # set features common to OWLS and Eagle\n        self._set_owls_eagle()\n\n        # Set time from analytic solution for flat LCDM universe\n        a = hvals[\"ExpansionFactor\"]\n        H0 = hvals[\"H(z)\"] / hvals[\"E(z)\"]\n        a_eq = (self.omega_matter / self.omega_lambda) ** (1.0 / 3)\n        t1 = 2.0 / (3.0 * np.sqrt(self.omega_lambda))\n        t2 = (a / a_eq) ** (3.0 / 2)\n        t3 = np.sqrt(1.0 + (a / a_eq) ** 3)\n        t = t1 * np.log(t2 + t3) / H0\n        self.current_time = t * yt.units.s\n\n    def _set_code_unit_attributes(self):\n        self._set_owls_eagle_units()\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        need_groups = [\n            \"Config\",\n            \"Constants\",\n            \"HashTable\",\n            \"Header\",\n            \"Parameters\",\n            \"RuntimePars\",\n            \"Units\",\n        ]\n        veto_groups = [\n            \"SUBFIND\",\n            \"PartType0/ChemistryAbundances\",\n            \"PartType0/ChemicalAbundances\",\n        ]\n        valid = True\n        try:\n            fileh = h5py.File(filename, mode=\"r\")\n            for ng in need_groups:\n                if ng not in fileh[\"/\"]:\n                    valid = False\n            for vg in veto_groups:\n                if vg in fileh[\"/\"]:\n                    valid = False\n            fileh.close()\n        except Exception:\n            valid = False\n            pass\n        return valid\n\n\nclass EagleNetworkDataset(EagleDataset):\n    _load_requirements = [\"h5py\"]\n    _particle_mass_name = \"Mass\"\n    _field_info_class = EagleNetworkFieldInfo\n    _time_readin = \"Time\"\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            fileh = h5py.File(filename, mode=\"r\")\n            if (\n                \"Constants\" in fileh[\"/\"].keys()\n                and \"Header\" in fileh[\"/\"].keys()\n                and \"SUBFIND\" not in fileh[\"/\"].keys()\n                and (\n                    \"ChemistryAbundances\" in fileh[\"PartType0\"].keys()\n                    or \"ChemicalAbundances\" in fileh[\"PartType0\"].keys()\n                )\n            ):\n                fileh.close()\n                return True\n            fileh.close()\n        except Exception:\n            pass\n        return False\n"
  },
  {
    "path": "yt/frontends/eagle/definitions.py",
    "content": "eaglenetwork_ions = (\n    \"electron\",\n    \"H1\",\n    \"H2\",\n    \"H_m\",\n    \"He1\",\n    \"He2\",\n    \"He3\",\n    \"C1\",\n    \"C2\",\n    \"C3\",\n    \"C4\",\n    \"C5\",\n    \"C6\",\n    \"C7\",\n    \"C_m\",\n    \"N1\",\n    \"N2\",\n    \"N3\",\n    \"N4\",\n    \"N5\",\n    \"N6\",\n    \"N7\",\n    \"N8\",\n    \"O1\",\n    \"O2\",\n    \"O3\",\n    \"O4\",\n    \"O5\",\n    \"O6\",\n    \"O7\",\n    \"O8\",\n    \"O9\",\n    \"O_m\",\n    \"Ne1\",\n    \"Ne2\",\n    \"Ne3\",\n    \"Ne4\",\n    \"Ne5\",\n    \"Ne6\",\n    \"Ne7\",\n    \"Ne8\",\n    \"Ne9\",\n    \"Ne10\",\n    \"Ne11\",\n    \"Mg1\",\n    \"Mg2\",\n    \"Mg3\",\n    \"Mg4\",\n    \"Mg5\",\n    \"Mg6\",\n    \"Mg7\",\n    \"Mg8\",\n    \"Mg9\",\n    \"Mg10\",\n    \"Mg11\",\n    \"Mg12\",\n    \"Mg13\",\n    \"Si1\",\n    \"Si2\",\n    \"Si3\",\n    \"Si4\",\n    \"Si5\",\n    \"Si6\",\n    \"Si7\",\n    \"Si8\",\n    \"Si9\",\n    \"Si10\",\n    \"Si11\",\n    \"Si12\",\n    \"Si13\",\n    \"Si14\",\n    \"Si15\",\n    \"Si16\",\n    \"Si17\",\n    \"Ca1\",\n    \"Ca2\",\n    \"Ca3\",\n    \"Ca4\",\n    \"Ca5\",\n    \"Ca6\",\n    \"Ca7\",\n    \"Ca8\",\n    \"Ca9\",\n    \"Ca10\",\n    \"Ca11\",\n    \"Ca12\",\n    \"Ca13\",\n    \"Ca14\",\n    \"Ca15\",\n    \"Ca16\",\n    \"Ca17\",\n    \"Ca18\",\n    \"Ca19\",\n    \"Ca20\",\n    \"Ca21\",\n    \"Fe1\",\n    \"Fe2\",\n    \"Fe3\",\n    \"Fe4\",\n    \"Fe5\",\n    \"Fe6\",\n    \"Fe7\",\n    \"Fe8\",\n    \"Fe9\",\n    \"Fe10\",\n    \"Fe11\",\n    \"Fe12\",\n    \"Fe13\",\n    \"Fe14\",\n    \"Fe15\",\n    \"Fe16\",\n    \"Fe17\",\n    \"Fe18\",\n    \"Fe19\",\n    \"Fe20\",\n    \"Fe21\",\n    \"Fe22\",\n    \"Fe23\",\n    \"Fe24\",\n    \"Fe25\",\n    \"Fe25\",\n    \"Fe27\",\n)\n\neaglenetwork_ion_lookup = {ion: index for index, ion in enumerate(eaglenetwork_ions)}\n"
  },
  {
    "path": "yt/frontends/eagle/fields.py",
    "content": "from yt.frontends.eagle.definitions import eaglenetwork_ion_lookup\nfrom yt.frontends.owls.fields import OWLSFieldInfo\nfrom yt.units.yt_array import YTQuantity\nfrom yt.utilities.periodic_table import periodic_table\n\n\nclass EagleNetworkFieldInfo(OWLSFieldInfo):\n    _ions = (\n        \"H1\",\n        \"H2\",\n        \"He1\",\n        \"He2\",\n        \"He3\",\n        \"C1\",\n        \"C2\",\n        \"C3\",\n        \"C4\",\n        \"C5\",\n        \"C6\",\n        \"C7\",\n        \"N1\",\n        \"N2\",\n        \"N3\",\n        \"N4\",\n        \"N5\",\n        \"N6\",\n        \"N7\",\n        \"N8\",\n        \"O1\",\n        \"O2\",\n        \"O3\",\n        \"O4\",\n        \"O5\",\n        \"O6\",\n        \"O7\",\n        \"O8\",\n        \"O9\",\n        \"Ne1\",\n        \"Ne2\",\n        \"Ne3\",\n        \"Ne4\",\n        \"Ne5\",\n        \"Ne6\",\n        \"Ne7\",\n        \"Ne8\",\n        \"Ne9\",\n        \"Ne10\",\n        \"Ne11\",\n        \"Mg1\",\n        \"Mg2\",\n        \"Mg3\",\n        \"Mg4\",\n        \"Mg5\",\n        \"Mg6\",\n        \"Mg7\",\n        \"Mg8\",\n        \"Mg9\",\n        \"Mg10\",\n        \"Mg11\",\n        \"Mg12\",\n        \"Mg13\",\n        \"Si1\",\n        \"Si2\",\n        \"Si3\",\n        \"Si4\",\n        \"Si5\",\n        \"Si6\",\n        \"Si7\",\n        \"Si8\",\n        \"Si9\",\n        \"Si10\",\n        \"Si11\",\n        \"Si12\",\n        \"Si13\",\n        \"Si14\",\n        \"Si15\",\n        \"Si16\",\n        \"Si17\",\n        \"Ca1\",\n        \"Ca2\",\n        \"Ca3\",\n        \"Ca4\",\n        \"Ca5\",\n        \"Ca6\",\n        \"Ca7\",\n        \"Ca8\",\n        \"Ca9\",\n        \"Ca10\",\n        \"Ca11\",\n        \"Ca12\",\n        \"Ca13\",\n        \"Ca14\",\n        \"Ca15\",\n        \"Ca16\",\n        \"Ca17\",\n        \"Ca18\",\n        \"Ca19\",\n        \"Ca20\",\n        \"Ca21\",\n        \"Fe1\",\n        \"Fe2\",\n        \"Fe3\",\n        \"Fe4\",\n        \"Fe5\",\n        \"Fe6\",\n        \"Fe7\",\n        \"Fe8\",\n        \"Fe9\",\n        \"Fe10\",\n        \"Fe11\",\n        \"Fe12\",\n        \"Fe13\",\n        \"Fe14\",\n        \"Fe15\",\n        \"Fe16\",\n        \"Fe17\",\n        \"Fe18\",\n        \"Fe19\",\n        \"Fe20\",\n        \"Fe21\",\n        \"Fe22\",\n        \"Fe23\",\n        \"Fe24\",\n        \"Fe25\",\n        \"Fe25\",\n        \"Fe27\",\n    )\n\n    def __init__(self, ds, field_list, slice_info=None):\n        super().__init__(ds, field_list, slice_info=slice_info)\n\n    def _create_ion_density_func(self, ftype, ion):\n        \"\"\"returns a function that calculates the ion density of a particle.\"\"\"\n\n        def _ion_density(data):\n            # Lookup the index of the ion\n            index = eaglenetwork_ion_lookup[ion]\n\n            # Ion to hydrogen number density ratio\n            ion_chem = data[ftype, f\"Chemistry_{index:03}\"]\n\n            # Mass of a single ion\n            if ion[0:2].isalpha():\n                symbol = ion[0:2].capitalize()\n            else:\n                symbol = ion[0:1].capitalize()\n            m_ion = YTQuantity(periodic_table.elements_by_symbol[symbol].weight, \"amu\")\n\n            # hydrogen number density\n            n_H = data[\"PartType0\", \"H_number_density\"]\n\n            return m_ion * ion_chem * n_H\n\n        return _ion_density\n"
  },
  {
    "path": "yt/frontends/eagle/io.py",
    "content": "from yt.frontends.owls.io import IOHandlerOWLS\n\n\nclass IOHandlerEagleNetwork(IOHandlerOWLS):\n    _dataset_type = \"eagle_network\"\n"
  },
  {
    "path": "yt/frontends/eagle/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/eagle/tests/test_outputs.py",
    "content": "from yt.frontends.eagle.api import EagleDataset\nfrom yt.testing import ParticleSelectionComparison, requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import data_dir_load\n\ns28 = \"snapshot_028_z000p000/snap_028_z000p000.0.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_file(s28)\ndef test_EagleDataset():\n    ds = data_dir_load(s28)\n    assert isinstance(ds, EagleDataset)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\ns399 = \"snipshot_399_z000p000/snip_399_z000p000.0.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_file(s399)\ndef test_Snipshot():\n    ds = data_dir_load(s399)\n    assert isinstance(ds, EagleDataset)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n"
  },
  {
    "path": "yt/frontends/enzo/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/enzo/answer_testing_support.py",
    "content": "import os\nfrom functools import wraps\n\nimport numpy as np\n\nfrom yt.config import ytcfg\nfrom yt.loaders import load\nfrom yt.testing import assert_allclose\nfrom yt.utilities.answer_testing.framework import (\n    AnswerTestingTest,\n    FieldValuesTest,\n    GridValuesTest,\n    ProjectionValuesTest,\n    can_run_ds,\n    temp_cwd,\n)\n\n\nclass AssertWrapper:\n    \"\"\"\n    Used to wrap a numpy testing assertion, in order to provide a useful name\n    for a given assertion test.\n    \"\"\"\n\n    def __init__(self, description, *args):\n        # The key here is to add a description attribute, which nose will pick\n        # up.\n        self.args = args\n        self.description = description\n\n    def __call__(self):\n        self.args[0](*self.args[1:])\n\n\ndef requires_outputlog(path=\".\", prefix=\"\"):\n    from nose import SkipTest\n\n    def ffalse(func):\n        @wraps(func)\n        def fskip(*args, **kwargs):\n            raise SkipTest\n\n        return fskip\n\n    def ftrue(func):\n        @wraps(func)\n        def fyielder(*args, **kwargs):\n            with temp_cwd(path):\n                for t in func(*args, **kwargs):\n                    if isinstance(t, AnswerTestingTest):\n                        t.prefix = prefix\n                    yield t\n\n        return fyielder\n\n    if os.path.exists(\"OutputLog\"):\n        return ftrue\n    with temp_cwd(path):\n        if os.path.exists(\"OutputLog\"):\n            return ftrue\n    return ffalse\n\n\ndef standard_small_simulation(ds_fn, fields):\n    if not can_run_ds(ds_fn):\n        return\n    dso = [None]\n    tolerance = ytcfg.get(\"yt\", \"answer_testing_tolerance\")\n    bitwise = ytcfg.get(\"yt\", \"answer_testing_bitwise\")\n    for field in fields:\n        if bitwise:\n            yield GridValuesTest(ds_fn, field)\n        if \"particle\" in field:\n            continue\n        for dobj_name in dso:\n            for axis in [0, 1, 2]:\n                for weight_field in [None, (\"gas\", \"density\")]:\n                    yield ProjectionValuesTest(\n                        ds_fn, axis, field, weight_field, dobj_name, decimals=tolerance\n                    )\n            yield FieldValuesTest(ds_fn, field, dobj_name, decimals=tolerance)\n\n\nclass ShockTubeTest:\n    def __init__(\n        self, data_file, solution_file, fields, left_edges, right_edges, rtol, atol\n    ):\n        self.solution_file = solution_file\n        self.data_file = data_file\n        self.fields = fields\n        self.left_edges = left_edges\n        self.right_edges = right_edges\n        self.rtol = rtol\n        self.atol = atol\n\n    def __call__(self):\n        # Read in the ds\n        ds = load(self.data_file)\n        exact = self.get_analytical_solution()\n\n        ad = ds.all_data()\n        position = ad[\"index\", \"x\"]\n        for k in self.fields:\n            field = ad[k].d\n            for xmin, xmax in zip(self.left_edges, self.right_edges, strict=True):\n                mask = (position >= xmin) * (position <= xmax)\n                exact_field = np.interp(position[mask].ndview, exact[\"pos\"], exact[k])\n                myname = f\"ShockTubeTest_{k}\"\n                # yield test vs analytical solution\n                yield AssertWrapper(\n                    myname,\n                    assert_allclose,\n                    field[mask],\n                    exact_field,\n                    self.rtol,\n                    self.atol,\n                )\n\n    def get_analytical_solution(self):\n        # Reads in from file\n        pos, dens, vel, pres, inte = np.loadtxt(self.solution_file, unpack=True)\n        exact = {}\n        exact[\"pos\"] = pos\n        exact[\"gas\", \"density\"] = dens\n        exact[\"gas\", \"velocity_x\"] = vel\n        exact[\"gas\", \"pressure\"] = pres\n        exact[\"gas\", \"specific_thermal_energy\"] = inte\n        return exact\n"
  },
  {
    "path": "yt/frontends/enzo/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    EnzoDataset,\n    EnzoDatasetInMemory,\n    EnzoGrid,\n    EnzoGridInMemory,\n    EnzoHierarchy,\n    EnzoHierarchy1D,\n    EnzoHierarchy2D,\n    EnzoHierarchyInMemory,\n)\nfrom .fields import EnzoFieldInfo\nfrom .io import (\n    IOHandlerInMemory,\n    IOHandlerPacked1D,\n    IOHandlerPacked2D,\n    IOHandlerPackedHDF5,\n)\nfrom .simulation_handling import EnzoSimulation\n\nadd_enzo_field = EnzoFieldInfo.add_field\n"
  },
  {
    "path": "yt/frontends/enzo/data_structures.py",
    "content": "import os\nimport re\nimport string\nimport time\nimport weakref\nfrom collections import defaultdict\nfrom functools import cached_property\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.fields.field_info_container import NullFunc\nfrom yt.frontends.enzo.misc import cosmology_get_units\nfrom yt.funcs import get_pbar, iter_fields, setdefaultattr\nfrom yt.geometry.geometry_handler import YTDataChunk\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py, _libconf as libconf\n\nfrom .fields import EnzoFieldInfo\n\n\nclass EnzoGrid(AMRGridPatch):\n    \"\"\"\n    Class representing a single Enzo Grid instance.\n    \"\"\"\n\n    def __init__(self, id, index):\n        \"\"\"\n        Returns an instance of EnzoGrid with *id*, associated with\n        *filename* and *index*.\n        \"\"\"\n        # All of the field parameters will be passed to us as needed.\n        AMRGridPatch.__init__(self, id, filename=None, index=index)\n        self._children_ids = []\n        self._parent_id = -1\n        self.Level = -1\n\n    def set_filename(self, filename):\n        \"\"\"\n        Intelligently set the filename.\n        \"\"\"\n        if filename is None:\n            self.filename = filename\n            return\n        if self.index._strip_path:\n            self.filename = os.path.join(\n                self.index.directory, os.path.basename(filename)\n            )\n        elif filename[0] == os.path.sep:\n            self.filename = filename\n        else:\n            self.filename = os.path.join(self.index.directory, filename)\n        return\n\n    @property\n    def Parent(self):\n        if self._parent_id == -1:\n            return None\n        return self.index.grids[self._parent_id - self._id_offset]\n\n    @property\n    def Children(self):\n        return [self.index.grids[cid - self._id_offset] for cid in self._children_ids]\n\n    @property\n    def NumberOfActiveParticles(self):\n        if not hasattr(self.index, \"grid_active_particle_count\"):\n            return {}\n        id = self.id - self._id_offset\n        nap = {\n            ptype: self.index.grid_active_particle_count[ptype][id]\n            for ptype in self.index.grid_active_particle_count\n        }\n        return nap\n\n\nclass EnzoGridInMemory(EnzoGrid):\n    __slots__ = [\"proc_num\"]\n\n    def set_filename(self, filename):\n        pass\n\n\nclass EnzoGridGZ(EnzoGrid):\n    __slots__ = ()\n\n    def retrieve_ghost_zones(self, n_zones, fields, all_levels=False, smoothed=False):\n        NGZ = self.ds.parameters.get(\"NumberOfGhostZones\", 3)\n        if n_zones > NGZ:\n            return EnzoGrid.retrieve_ghost_zones(\n                self, n_zones, fields, all_levels, smoothed\n            )\n\n        # ----- Below is mostly the original code, except we remove the field\n        # ----- access section\n        # We will attempt this by creating a datacube that is exactly bigger\n        # than the grid by nZones*dx in each direction\n        nl = self.get_global_startindex() - n_zones\n        new_left_edge = nl * self.dds + self.ds.domain_left_edge\n        # Something different needs to be done for the root grid, though\n        level = self.Level\n        kwargs = {\n            \"dims\": self.ActiveDimensions + 2 * n_zones,\n            \"num_ghost_zones\": n_zones,\n            \"use_pbar\": False,\n        }\n        # This should update the arguments to set the field parameters to be\n        # those of this grid.\n        kwargs.update(self.field_parameters)\n        if smoothed:\n            # cube = self.index.smoothed_covering_grid(\n            #    level, new_left_edge, new_right_edge, **kwargs)\n            cube = self.index.smoothed_covering_grid(level, new_left_edge, **kwargs)\n        else:\n            cube = self.index.covering_grid(level, new_left_edge, **kwargs)\n        # ----- This is EnzoGrid.get_data, duplicated here mostly for\n        # ----  efficiency's sake.\n        start_zone = NGZ - n_zones\n        if start_zone == 0:\n            end_zone = None\n        else:\n            end_zone = -(NGZ - n_zones)\n        sl = tuple(slice(start_zone, end_zone) for i in range(3))\n        if fields is None:\n            return cube\n        for field in iter_fields(fields):\n            if field in self.field_list:\n                conv_factor = 1.0\n                if field in self.ds.field_info:\n                    conv_factor = self.ds.field_info[field]._convert_function(self)\n                if self.ds.field_info[field].sampling_type == \"particle\":\n                    continue\n                temp = self.index.io._read_raw_data_set(self, field)\n                temp = temp.swapaxes(0, 2)\n                cube.field_data[field] = np.multiply(temp, conv_factor, temp)[sl]\n        return cube\n\n\nclass EnzoHierarchy(GridIndex):\n    _strip_path = False\n    grid = EnzoGrid\n    _preload_implemented = True\n\n    def __init__(self, ds, dataset_type):\n        self.dataset_type = dataset_type\n        self.index_filename = os.path.abspath(f\"{ds.parameter_filename}.hierarchy\")\n        if os.path.getsize(self.index_filename) == 0:\n            raise OSError(-1, \"File empty\", self.index_filename)\n        self.directory = os.path.dirname(self.index_filename)\n\n        # For some reason, r8 seems to want Float64\n        if \"CompilerPrecision\" in ds and ds[\"CompilerPrecision\"] == \"r4\":\n            self.float_type = \"float32\"\n        else:\n            self.float_type = \"float64\"\n\n        GridIndex.__init__(self, ds, dataset_type)\n        # sync it back\n        self.dataset.dataset_type = self.dataset_type\n\n    def _count_grids(self):\n        self.num_grids = None\n        test_grid = test_grid_id = None\n        self.num_stars = 0\n        for line in rlines(open(self.index_filename, \"rb\")):\n            if (\n                line.startswith(\"BaryonFileName\")\n                or line.startswith(\"ParticleFileName\")\n                or line.startswith(\"FileName \")\n            ):\n                test_grid = line.split(\"=\")[-1].strip().rstrip()\n            if line.startswith(\"NumberOfStarParticles\"):\n                self.num_stars = int(line.split(\"=\")[-1])\n            if line.startswith(\"Grid \"):\n                if self.num_grids is None:\n                    self.num_grids = int(line.split(\"=\")[-1])\n                test_grid_id = int(line.split(\"=\")[-1])\n                if test_grid is not None:\n                    break\n        self._guess_dataset_type(self.ds.dimensionality, test_grid, test_grid_id)\n\n    def _guess_dataset_type(self, rank, test_grid, test_grid_id):\n        if test_grid[0] != os.path.sep:\n            test_grid = os.path.join(self.directory, test_grid)\n        if not os.path.exists(test_grid):\n            test_grid = os.path.join(self.directory, os.path.basename(test_grid))\n            mylog.debug(\"Your data uses the annoying hardcoded path.\")\n            self._strip_path = True\n        if self.dataset_type is not None:\n            return\n        if rank == 3:\n            mylog.debug(\"Detected packed HDF5\")\n            if self.parameters.get(\"WriteGhostZones\", 0) == 1:\n                self.dataset_type = \"enzo_packed_3d_gz\"\n                self.grid = EnzoGridGZ\n            else:\n                self.dataset_type = \"enzo_packed_3d\"\n        elif rank == 2:\n            mylog.debug(\"Detect packed 2D\")\n            self.dataset_type = \"enzo_packed_2d\"\n        elif rank == 1:\n            mylog.debug(\"Detect packed 1D\")\n            self.dataset_type = \"enzo_packed_1d\"\n        else:\n            raise NotImplementedError\n\n    # Sets are sorted, so that won't work!\n    def _parse_index(self):\n        def _next_token_line(token, f):\n            for line in f:\n                if line.startswith(token):\n                    return line.split()[2:]\n\n        pattern = r\"Pointer: Grid\\[(\\d*)\\]->NextGrid(Next|This)Level = (\\d*)\\s+$\"\n        patt = re.compile(pattern)\n        f = open(self.index_filename)\n        self.grids = [self.grid(1, self)]\n        self.grids[0].Level = 0\n        si, ei, LE, RE, fn, npart = [], [], [], [], [], []\n        pbar = get_pbar(\"Parsing Hierarchy \", self.num_grids)\n        version = self.dataset.parameters.get(\"VersionNumber\", None)\n        params = self.dataset.parameters\n        if version is None and \"Internal\" in params:\n            version = float(params[\"Internal\"][\"Provenance\"][\"VersionNumber\"])\n        if version >= 3.0:\n            active_particles = True\n            nap = {\n                ap_type: []\n                for ap_type in params[\"Physics\"][\"ActiveParticles\"][\n                    \"ActiveParticlesEnabled\"\n                ]\n            }\n        else:\n            if \"AppendActiveParticleType\" in self.parameters:\n                nap = {}\n                active_particles = True\n                for type in self.parameters.get(\"AppendActiveParticleType\", []):\n                    nap[type] = []\n            else:\n                nap = None\n                active_particles = False\n        for grid_id in range(self.num_grids):\n            pbar.update(grid_id + 1)\n            # We will unroll this list\n            si.append(_next_token_line(\"GridStartIndex\", f))\n            ei.append(_next_token_line(\"GridEndIndex\", f))\n            LE.append(_next_token_line(\"GridLeftEdge\", f))\n            RE.append(_next_token_line(\"GridRightEdge\", f))\n            nb = int(_next_token_line(\"NumberOfBaryonFields\", f)[0])\n            fn.append([None])\n            if nb > 0:\n                fn[-1] = _next_token_line(\"BaryonFileName\", f)\n            npart.append(int(_next_token_line(\"NumberOfParticles\", f)[0]))\n            # Below we find out what active particles exist in this grid,\n            # and add their counts individually.\n            if active_particles:\n                ptypes = _next_token_line(\"PresentParticleTypes\", f)\n                counts = [int(c) for c in _next_token_line(\"ParticleTypeCounts\", f)]\n                for ptype in self.parameters.get(\"AppendActiveParticleType\", []):\n                    if ptype in ptypes:\n                        nap[ptype].append(counts[ptypes.index(ptype)])\n                    else:\n                        nap[ptype].append(0)\n            if nb == 0 and npart[-1] > 0:\n                fn[-1] = _next_token_line(\"ParticleFileName\", f)\n            for line in f:\n                if len(line) < 2:\n                    break\n                if line.startswith(\"Pointer:\"):\n                    vv = patt.findall(line)[0]\n                    self.__pointer_handler(vv)\n        pbar.finish()\n        self._fill_arrays(ei, si, LE, RE, npart, nap)\n        temp_grids = np.empty(self.num_grids, dtype=\"object\")\n        temp_grids[:] = self.grids\n        self.grids = temp_grids\n        self.filenames = fn\n\n    def _initialize_grid_arrays(self):\n        super()._initialize_grid_arrays()\n        if \"AppendActiveParticleType\" in self.parameters.keys() and len(\n            self.parameters[\"AppendActiveParticleType\"]\n        ):\n            gac = {\n                ptype: np.zeros(self.num_grids, dtype=\"i4\")\n                for ptype in self.parameters[\"AppendActiveParticleType\"]\n            }\n            self.grid_active_particle_count = gac\n\n    def _fill_arrays(self, ei, si, LE, RE, npart, nap):\n        self.grid_dimensions.flat[:] = ei\n        self.grid_dimensions -= np.array(si, dtype=\"i4\")\n        self.grid_dimensions += 1\n        self.grid_left_edge.flat[:] = LE\n        self.grid_right_edge.flat[:] = RE\n        self.grid_particle_count.flat[:] = npart\n        if nap is not None:\n            for ptype in nap:\n                self.grid_active_particle_count[ptype].flat[:] = nap[ptype]\n\n    def __pointer_handler(self, m):\n        sgi = int(m[2]) - 1\n        if sgi == -1:\n            return  # if it's 0, then we're done with that lineage\n        # Okay, so, we have a pointer.  We make a new grid, with an id of the length+1\n        # (recall, Enzo grids are 1-indexed)\n        self.grids.append(self.grid(len(self.grids) + 1, self))\n        # We'll just go ahead and make a weakref to cache\n        second_grid = self.grids[sgi]  # zero-indexed already\n        first_grid = self.grids[int(m[0]) - 1]\n        if m[1] == \"Next\":\n            first_grid._children_ids.append(second_grid.id)\n            second_grid._parent_id = first_grid.id\n            second_grid.Level = first_grid.Level + 1\n        elif m[1] == \"This\":\n            if first_grid.Parent is not None:\n                first_grid.Parent._children_ids.append(second_grid.id)\n                second_grid._parent_id = first_grid._parent_id\n            second_grid.Level = first_grid.Level\n        self.grid_levels[sgi] = second_grid.Level\n\n    def _rebuild_top_grids(self, level=0):\n        mylog.info(\"Rebuilding grids on level %s\", level)\n        cmask = self.grid_levels.flat == (level + 1)\n        cmsum = cmask.sum()\n        mask = np.zeros(self.num_grids, dtype=\"bool\")\n        for grid in self.select_grids(level):\n            mask[:] = 0\n            LE = self.grid_left_edge[grid.id - grid._id_offset]\n            RE = self.grid_right_edge[grid.id - grid._id_offset]\n            grids, grid_i = self.get_box_grids(LE, RE)\n            mask[grid_i] = 1\n            grid._children_ids = []\n            cgrids = self.grids[(mask * cmask).astype(\"bool\")]\n            mylog.info(\"%s: %s / %s\", grid, len(cgrids), cmsum)\n            for cgrid in cgrids:\n                grid._children_ids.append(cgrid.id)\n                cgrid._parent_id = grid.id\n        mylog.info(\"Finished rebuilding\")\n\n    def _populate_grid_objects(self):\n        for g, f in zip(self.grids, self.filenames, strict=True):\n            g._prepare_grid()\n            g._setup_dx()\n            g.set_filename(f[0])\n        del self.filenames  # No longer needed.\n        self.max_level = self.grid_levels.max()\n\n    def _detect_active_particle_fields(self):\n        ap_list = self.dataset[\"AppendActiveParticleType\"]\n        _fields = {ap: [] for ap in ap_list}\n        fields = []\n        for ptype in self.dataset[\"AppendActiveParticleType\"]:\n            select_grids = self.grid_active_particle_count[ptype].flat\n            if not np.any(select_grids):\n                current_ptypes = self.dataset.particle_types\n                new_ptypes = [p for p in current_ptypes if p != ptype]\n                self.dataset.particle_types = new_ptypes\n                self.dataset.particle_types_raw = new_ptypes\n                continue\n            if ptype != \"DarkMatter\":\n                gs = self.grids[select_grids > 0]\n                g = gs[0]\n                handle = h5py.File(g.filename, \"r\")\n                grid_group = handle[f\"/Grid{g.id:08d}\"]\n                for pname in [\"Active Particles\", \"Particles\"]:\n                    if pname in grid_group:\n                        break\n                else:\n                    raise RuntimeError(\"Could not find active particle group in data.\")\n                node = grid_group[pname]\n                for ptype in (str(p) for p in node):\n                    if ptype not in _fields:\n                        continue\n                    for field in (str(f) for f in node[ptype]):\n                        _fields[ptype].append(field)\n                        if node[ptype][field].ndim > 1:\n                            self.io._array_fields[field] = (\n                                node[ptype][field].shape[1:],\n                            )\n                    fields += [(ptype, field) for field in _fields.pop(ptype)]\n                handle.close()\n        return set(fields)\n\n    def _setup_derived_fields(self):\n        super()._setup_derived_fields()\n        aps = self.dataset.parameters.get(\"AppendActiveParticleType\", [])\n        for fname, field in self.ds.field_info.items():\n            if not field.sampling_type == \"particle\":\n                continue\n            if isinstance(fname, tuple):\n                continue\n            if field._function is NullFunc:\n                continue\n            for apt in aps:\n                dd = field._copy_def()\n                dd.pop(\"name\")\n                self.ds.field_info.add_field((apt, fname), sampling_type=\"cell\", **dd)\n\n    def _detect_output_fields(self):\n        self.field_list = []\n        # Do this only on the root processor to save disk work.\n        if self.comm.rank in (0, None):\n            mylog.info(\"Gathering a field list (this may take a moment.)\")\n            field_list = set()\n            random_sample = self._generate_random_grids()\n            for grid in random_sample:\n                if not hasattr(grid, \"filename\"):\n                    continue\n                try:\n                    gf = self.io._read_field_names(grid)\n                except self.io._read_exception as e:\n                    raise OSError(\"Grid %s is a bit funky?\", grid.id) from e\n                mylog.debug(\"Grid %s has: %s\", grid.id, gf)\n                field_list = field_list.union(gf)\n            if \"AppendActiveParticleType\" in self.dataset.parameters:\n                ap_fields = self._detect_active_particle_fields()\n                field_list = list(set(field_list).union(ap_fields))\n                if not any(f[0] == \"io\" for f in field_list):\n                    if \"io\" in self.dataset.particle_types_raw:\n                        ptypes_raw = list(self.dataset.particle_types_raw)\n                        ptypes_raw.remove(\"io\")\n                        self.dataset.particle_types_raw = tuple(ptypes_raw)\n\n                    if \"io\" in self.dataset.particle_types:\n                        ptypes = list(self.dataset.particle_types)\n                        ptypes.remove(\"io\")\n                        self.dataset.particle_types = tuple(ptypes)\n            ptypes = self.dataset.particle_types\n            ptypes_raw = self.dataset.particle_types_raw\n        else:\n            field_list = None\n            ptypes = None\n            ptypes_raw = None\n        self.field_list = list(self.comm.mpi_bcast(field_list))\n        self.dataset.particle_types = list(self.comm.mpi_bcast(ptypes))\n        self.dataset.particle_types_raw = list(self.comm.mpi_bcast(ptypes_raw))\n\n    def _generate_random_grids(self):\n        if self.num_grids > 40:\n            rng = np.random.default_rng()\n            starter = rng.integers(0, 20)\n            random_sample = np.mgrid[starter : len(self.grids) - 1 : 20j].astype(\n                \"int32\"\n            )\n            # We also add in a bit to make sure that some of the grids have\n            # particles\n            gwp = self.grid_particle_count > 0\n            if np.any(gwp) and not np.any(gwp[random_sample,]):\n                # We just add one grid.  This is not terribly efficient.\n                first_grid = np.where(gwp)[0][0]\n                random_sample.resize((21,))\n                random_sample[-1] = first_grid\n                mylog.debug(\"Added additional grid %s\", first_grid)\n            mylog.debug(\"Checking grids: %s\", random_sample.tolist())\n        else:\n            random_sample = np.mgrid[0 : max(len(self.grids), 1)].astype(\"int32\")\n        return self.grids[random_sample,]\n\n    def _get_particle_type_counts(self):\n        try:\n            ret = {}\n            for ptype in self.grid_active_particle_count:\n                ret[ptype] = self.grid_active_particle_count[ptype].sum()\n            return ret\n        except AttributeError:\n            return super()._get_particle_type_counts()\n\n    def find_particles_by_type(self, ptype, max_num=None, additional_fields=None):\n        \"\"\"\n        Returns a structure of arrays with all of the particles'\n        positions, velocities, masses, types, IDs, and attributes for\n        a particle type **ptype** for a maximum of **max_num**\n        particles.  If non-default particle fields are used, provide\n        them in **additional_fields**.\n        \"\"\"\n        # Not sure whether this routine should be in the general HierarchyType.\n        if self.grid_particle_count.sum() == 0:\n            mylog.info(\"Data contains no particles.\")\n            return None\n        if additional_fields is None:\n            additional_fields = [\n                \"metallicity_fraction\",\n                \"creation_time\",\n                \"dynamical_time\",\n            ]\n        pfields = [f for f in self.field_list if f.startswith(\"particle_\")]\n        nattr = self.dataset[\"NumberOfParticleAttributes\"]\n        if nattr > 0:\n            pfields += additional_fields[:nattr]\n        # Find where the particles reside and count them\n        if max_num is None:\n            max_num = 1e100\n        total = 0\n        pstore = []\n        for level in range(self.max_level, -1, -1):\n            for grid in self.select_grids(level):\n                index = np.where(grid[\"particle_type\"] == ptype)[0]\n                total += len(index)\n                pstore.append(index)\n                if total >= max_num:\n                    break\n            if total >= max_num:\n                break\n        result = None\n        if total > 0:\n            result = {}\n            for p in pfields:\n                result[p] = np.zeros(total, \"float64\")\n            # Now we retrieve data for each field\n            ig = count = 0\n            for level in range(self.max_level, -1, -1):\n                for grid in self.select_grids(level):\n                    nidx = len(pstore[ig])\n                    if nidx > 0:\n                        for p in pfields:\n                            result[p][count : count + nidx] = grid[p][pstore[ig]]\n                        count += nidx\n                    ig += 1\n                    if count >= total:\n                        break\n                if count >= total:\n                    break\n            # Crop data if retrieved more than max_num\n            if count > max_num:\n                for p in pfields:\n                    result[p] = result[p][0:max_num]\n        return result\n\n\nclass EnzoHierarchyInMemory(EnzoHierarchy):\n    grid = EnzoGridInMemory\n\n    @cached_property\n    def enzo(self):\n        import enzo\n\n        return enzo\n\n    def __init__(self, ds, dataset_type=None):\n        self.dataset_type = dataset_type\n        self.float_type = \"float64\"\n        self.dataset = weakref.proxy(ds)  # for _obtain_enzo\n        self.float_type = self.enzo.hierarchy_information[\"GridLeftEdge\"].dtype\n        self.directory = os.getcwd()\n        GridIndex.__init__(self, ds, dataset_type)\n\n    def _initialize_data_storage(self):\n        pass\n\n    def _count_grids(self):\n        self.num_grids = self.enzo.hierarchy_information[\"GridDimensions\"].shape[0]\n\n    def _parse_index(self):\n        self._copy_index_structure()\n        mylog.debug(\"Copying reverse tree\")\n        reverse_tree = self.enzo.hierarchy_information[\"GridParentIDs\"].ravel().tolist()\n        # Initial setup:\n        mylog.debug(\"Reconstructing parent-child relationships\")\n        grids = []\n        # We enumerate, so it's 0-indexed id and 1-indexed pid\n        self.filenames = [\"-1\"] * self.num_grids\n        for id, pid in enumerate(reverse_tree):\n            grids.append(self.grid(id + 1, self))\n            grids[-1].Level = self.grid_levels[id, 0]\n            if pid > 0:\n                grids[-1]._parent_id = pid\n                grids[pid - 1]._children_ids.append(grids[-1].id)\n        self.max_level = self.grid_levels.max()\n        mylog.debug(\"Preparing grids\")\n        self.grids = np.empty(len(grids), dtype=\"object\")\n        for i, grid in enumerate(grids):\n            if (i % 1e4) == 0:\n                mylog.debug(\"Prepared % 7i / % 7i grids\", i, self.num_grids)\n            grid.filename = f\"Inline_processor_{self.grid_procs[i, 0]:07}\"\n            grid._prepare_grid()\n            grid._setup_dx()\n            grid.proc_num = self.grid_procs[i, 0]\n            self.grids[i] = grid\n        mylog.debug(\"Prepared\")\n\n    def _initialize_grid_arrays(self):\n        EnzoHierarchy._initialize_grid_arrays(self)\n        self.grid_procs = np.zeros((self.num_grids, 1), \"int32\")\n\n    def _copy_index_structure(self):\n        # Dimensions are important!\n        self.grid_dimensions[:] = self.enzo.hierarchy_information[\"GridEndIndices\"][:]\n        self.grid_dimensions -= self.enzo.hierarchy_information[\"GridStartIndices\"][:]\n        self.grid_dimensions += 1\n        self.grid_left_edge[:] = self.enzo.hierarchy_information[\"GridLeftEdge\"][:]\n        self.grid_right_edge[:] = self.enzo.hierarchy_information[\"GridRightEdge\"][:]\n        self.grid_levels[:] = self.enzo.hierarchy_information[\"GridLevels\"][:]\n        self.grid_procs = self.enzo.hierarchy_information[\"GridProcs\"].copy()\n        self.grid_particle_count[:] = self.enzo.hierarchy_information[\n            \"GridNumberOfParticles\"\n        ][:]\n\n    def save_data(self, *args, **kwargs):\n        pass\n\n    _cached_field_list = None\n    _cached_derived_field_list = None\n\n    def _generate_random_grids(self):\n        my_rank = self.comm.rank\n        my_grids = self.grids[self.grid_procs.ravel() == my_rank]\n        if len(my_grids) > 40:\n            rng = np.random.default_rng()\n            starter = rng.integers(0, 20)\n            random_sample = np.mgrid[starter : len(my_grids) - 1 : 20j].astype(\"int32\")\n            mylog.debug(\"Checking grids: %s\", random_sample.tolist())\n        else:\n            random_sample = np.mgrid[0 : max(len(my_grids) - 1, 1)].astype(\"int32\")\n        return my_grids[random_sample,]\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):\n        gfiles = defaultdict(list)\n        gobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for g in gobjs:\n            gfiles[g.filename].append(g)\n        for fn in sorted(gfiles):\n            if local_only:\n                gobjs = [g for g in gfiles[fn] if g.proc_num == self.comm.rank]\n                gfiles[fn] = gobjs\n            gs = gfiles[fn]\n            count = self._count_selection(dobj, gs)\n            yield YTDataChunk(dobj, \"io\", gs, count, cache=cache)\n\n\nclass EnzoHierarchy1D(EnzoHierarchy):\n    def _fill_arrays(self, ei, si, LE, RE, npart, nap):\n        self.grid_dimensions[:, :1] = ei\n        self.grid_dimensions[:, :1] -= np.array(si, dtype=\"i4\")\n        self.grid_dimensions += 1\n        self.grid_left_edge[:, :1] = LE\n        self.grid_right_edge[:, :1] = RE\n        self.grid_particle_count.flat[:] = npart\n        self.grid_left_edge[:, 1:] = 0.0\n        self.grid_right_edge[:, 1:] = 1.0\n        self.grid_dimensions[:, 1:] = 1\n        if nap is not None:\n            raise NotImplementedError\n\n\nclass EnzoHierarchy2D(EnzoHierarchy):\n    def _fill_arrays(self, ei, si, LE, RE, npart, nap):\n        self.grid_dimensions[:, :2] = ei\n        self.grid_dimensions[:, :2] -= np.array(si, dtype=\"i4\")\n        self.grid_dimensions += 1\n        self.grid_left_edge[:, :2] = LE\n        self.grid_right_edge[:, :2] = RE\n        self.grid_particle_count.flat[:] = npart\n        self.grid_left_edge[:, 2] = 0.0\n        self.grid_right_edge[:, 2] = 1.0\n        self.grid_dimensions[:, 2] = 1\n        if nap is not None:\n            raise NotImplementedError\n\n\nclass EnzoDataset(Dataset):\n    \"\"\"\n    Enzo-specific output, set at a fixed time.\n    \"\"\"\n\n    _load_requirements = [\"h5py\"]\n    _index_class = EnzoHierarchy\n    _field_info_class = EnzoFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=None,\n        parameter_override=None,\n        conversion_override=None,\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        \"\"\"\n        This class is a stripped down class that simply reads and parses\n        *filename* without looking at the index.  *dataset_type* gets passed\n        to the index to pre-determine the style of data-output.  However,\n        it is not strictly necessary.  Optionally you may specify a\n        *parameter_override* dictionary that will override anything in the\n        parameter file and a *conversion_override* dictionary that consists\n        of {fieldname : conversion_to_cgs} that will override the #DataCGS.\n        \"\"\"\n        self.fluid_types += (\"enzo\",)\n        if filename.endswith(\".hierarchy\"):\n            filename = filename[:-10]\n        if parameter_override is None:\n            parameter_override = {}\n        self._parameter_override = parameter_override\n        if conversion_override is None:\n            conversion_override = {}\n        self._conversion_override = conversion_override\n        self.storage_filename = storage_filename\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n    def _setup_1d(self):\n        self._index_class = EnzoHierarchy1D\n        self.domain_left_edge = np.concatenate([[self.domain_left_edge], [0.0, 0.0]])\n        self.domain_right_edge = np.concatenate([[self.domain_right_edge], [1.0, 1.0]])\n\n    def _setup_2d(self):\n        self._index_class = EnzoHierarchy2D\n        self.domain_left_edge = np.concatenate([self.domain_left_edge, [0.0]])\n        self.domain_right_edge = np.concatenate([self.domain_right_edge, [1.0]])\n\n    def get_parameter(self, parameter, type=None):\n        \"\"\"\n        Gets a parameter not in the parameterDict.\n        \"\"\"\n        if parameter in self.parameters:\n            return self.parameters[parameter]\n        for line in open(self.parameter_filename):\n            if line.find(\"#\") >= 1:  # Keep the commented lines\n                line = line[: line.find(\"#\")]\n            line = line.strip().rstrip()\n            if len(line) < 2:\n                continue\n            try:\n                param, vals = map(string.strip, map(string.rstrip, line.split(\"=\")))\n            except ValueError:\n                mylog.error(\"ValueError: '%s'\", line)\n            if parameter == param:\n                if type is None:\n                    t = vals.split()\n                else:\n                    t = map(type, vals.split())\n                if len(t) == 1:\n                    self.parameters[param] = t[0]\n                else:\n                    self.parameters[param] = t\n                if param.endswith(\"Units\") and not param.startswith(\"Temperature\"):\n                    dataType = param[:-5]\n                    self.conversion_factors[dataType] = self.parameters[param]\n                return self.parameters[parameter]\n\n        return \"\"\n\n    @cached_property\n    def unique_identifier(self) -> str:\n        if \"CurrentTimeIdentifier\" in self.parameters:\n            # enzo2\n            return str(self.parameters[\"CurrentTimeIdentifier\"])\n        elif \"MetaDataDatasetUUID\" in self.parameters:\n            # enzo2\n            return str(self.parameters[\"MetaDataDatasetUUID\"])\n        elif \"Internal\" in self.parameters:\n            # enzo3\n            return str(\n                self.parameters[\"Internal\"][\"Provenance\"][\"CurrentTimeIdentidier\"]\n            )\n        else:\n            return super().unique_identifier\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Parses the parameter file and establishes the various\n        dictionaries.\n        \"\"\"\n        # Let's read the file\n        with open(self.parameter_filename) as f:\n            line = f.readline().strip()\n            f.seek(0)\n            if line == \"Internal:\":\n                self._parse_enzo3_parameter_file(f)\n            else:\n                self._parse_enzo2_parameter_file(f)\n\n    def _parse_enzo3_parameter_file(self, f):\n        self.parameters = p = libconf.load(f)\n        sim = p[\"SimulationControl\"]\n        internal = p[\"Internal\"]\n        phys = p[\"Physics\"]\n        self.refine_by = sim[\"AMR\"][\"RefineBy\"]\n        self._periodicity = tuple(\n            a == 3 for a in sim[\"Domain\"][\"LeftFaceBoundaryCondition\"]\n        )\n        self.dimensionality = sim[\"Domain\"][\"TopGridRank\"]\n        self.domain_dimensions = np.array(\n            sim[\"Domain\"][\"TopGridDimensions\"], dtype=\"int64\"\n        )\n        self.domain_left_edge = np.array(\n            sim[\"Domain\"][\"DomainLeftEdge\"], dtype=\"float64\"\n        )\n        self.domain_right_edge = np.array(\n            sim[\"Domain\"][\"DomainRightEdge\"], dtype=\"float64\"\n        )\n        self.gamma = phys[\"Hydro\"][\"Gamma\"]\n        self.current_time = internal[\"InitialTime\"]\n        self.cosmological_simulation = phys[\"Cosmology\"][\"ComovingCoordinates\"]\n        if self.cosmological_simulation == 1:\n            cosmo = phys[\"Cosmology\"]\n            self.current_redshift = internal[\"CosmologyCurrentRedshift\"]\n            self.omega_lambda = cosmo[\"OmegaLambdaNow\"]\n            self.omega_matter = cosmo[\"OmegaMatterNow\"]\n            self.hubble_constant = cosmo[\"HubbleConstantNow\"]\n        else:\n            self.current_redshift = 0.0\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0\n            self.hubble_constant = 0.0\n            self.cosmological_simulation = 0\n        self.particle_types = [\"DarkMatter\"] + phys[\"ActiveParticles\"][\n            \"ActiveParticlesEnabled\"\n        ]\n        self.particle_types = tuple(self.particle_types)\n        self.particle_types_raw = self.particle_types\n        if self.dimensionality == 1:\n            self._setup_1d()\n        elif self.dimensionality == 2:\n            self._setup_2d()\n\n    def _parse_enzo2_parameter_file(self, f):\n        for line in (l.strip() for l in f):\n            if (len(line) < 2) or (\"=\" not in line):\n                continue\n            param, vals = (i.strip() for i in line.split(\"=\", 1))\n            # First we try to decipher what type of value it is.\n            vals = vals.split()\n            # Special case approaching.\n            if \"(do\" in vals:\n                vals = vals[:1]\n            if len(vals) == 0:\n                pcast = str  # Assume NULL output\n            else:\n                v = vals[0]\n                # Figure out if it's castable to floating point:\n                try:\n                    float(v)\n                except ValueError:\n                    pcast = str\n                else:\n                    if any(\".\" in v or \"e+\" in v or \"e-\" in v for v in vals):\n                        pcast = float\n                    elif v == \"inf\":\n                        pcast = str\n                    else:\n                        pcast = int\n            # Now we figure out what to do with it.\n            if len(vals) == 0:\n                vals = \"\"\n            elif len(vals) == 1:\n                vals = pcast(vals[0])\n            else:\n                vals = np.array([pcast(i) for i in vals if i != \"-99999\"])\n            if param.startswith(\"Append\"):\n                if param not in self.parameters:\n                    self.parameters[param] = []\n                self.parameters[param].append(vals)\n            else:\n                self.parameters[param] = vals\n        self.refine_by = self.parameters[\"RefineBy\"]\n        _periodicity = tuple(\n            always_iterable(self.parameters[\"LeftFaceBoundaryCondition\"] == 3)\n        )\n        self.dimensionality = self.parameters[\"TopGridRank\"]\n\n        if self.dimensionality > 1:\n            self.domain_dimensions = self.parameters[\"TopGridDimensions\"]\n            if len(self.domain_dimensions) < 3:\n                tmp = self.domain_dimensions.tolist()\n                tmp.append(1)\n                self.domain_dimensions = np.array(tmp)\n                _periodicity += (False,)\n            self.domain_left_edge = np.array(\n                self.parameters[\"DomainLeftEdge\"], \"float64\"\n            ).copy()\n            self.domain_right_edge = np.array(\n                self.parameters[\"DomainRightEdge\"], \"float64\"\n            ).copy()\n        else:\n            self.domain_left_edge = np.array(\n                self.parameters[\"DomainLeftEdge\"], \"float64\"\n            )\n            self.domain_right_edge = np.array(\n                self.parameters[\"DomainRightEdge\"], \"float64\"\n            )\n            self.domain_dimensions = np.array(\n                [self.parameters[\"TopGridDimensions\"], 1, 1]\n            )\n            _periodicity += (False, False)\n        assert len(_periodicity) == 3\n        self._periodicity = _periodicity\n\n        self.gamma = self.parameters[\"Gamma\"]\n        if self.parameters[\"ComovingCoordinates\"]:\n            self.cosmological_simulation = 1\n            self.current_redshift = self.parameters[\"CosmologyCurrentRedshift\"]\n            self.omega_lambda = self.parameters[\"CosmologyOmegaLambdaNow\"]\n            self.omega_matter = self.parameters[\"CosmologyOmegaMatterNow\"]\n            self.omega_radiation = self.parameters.get(\n                \"CosmologyOmegaRadiationNow\", 0.0\n            )\n            self.hubble_constant = self.parameters[\"CosmologyHubbleConstantNow\"]\n        else:\n            self.current_redshift = 0.0\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0\n            self.hubble_constant = 0.0\n            self.cosmological_simulation = 0\n        self.particle_types = []\n        self.current_time = self.parameters[\"InitialTime\"]\n        if (\n            self.parameters[\"NumberOfParticles\"] > 0\n            and \"AppendActiveParticleType\" in self.parameters.keys()\n        ):\n            # If this is the case, then we know we should have a DarkMatter\n            # particle type, and we don't need the \"io\" type.\n            self.parameters[\"AppendActiveParticleType\"].append(\"DarkMatter\")\n        else:\n            # We do not have an \"io\" type for Enzo particles if the\n            # ActiveParticle machinery is on, as we simply will ignore any of\n            # the non-DarkMatter particles in that case.  However, for older\n            # datasets, we call this particle type \"io\".\n            self.particle_types = [\"io\"]\n        for ptype in self.parameters.get(\"AppendActiveParticleType\", []):\n            self.particle_types.append(ptype)\n        self.particle_types = tuple(self.particle_types)\n        self.particle_types_raw = self.particle_types\n\n        if self.dimensionality == 1:\n            self._setup_1d()\n        elif self.dimensionality == 2:\n            self._setup_2d()\n\n    def _set_code_unit_attributes(self):\n        if self.cosmological_simulation:\n            k = cosmology_get_units(\n                self.hubble_constant,\n                self.omega_matter,\n                self.parameters[\"CosmologyComovingBoxSize\"],\n                self.parameters[\"CosmologyInitialRedshift\"],\n                self.current_redshift,\n            )\n            # Now some CGS values\n            box_size = self.parameters[\"CosmologyComovingBoxSize\"]\n            setdefaultattr(self, \"length_unit\", self.quan(box_size, \"Mpccm/h\"))\n            setdefaultattr(\n                self,\n                \"mass_unit\",\n                self.quan(k[\"urho\"], \"g/cm**3\") * (self.length_unit.in_cgs()) ** 3,\n            )\n            setdefaultattr(self, \"time_unit\", self.quan(k[\"utim\"], \"s\"))\n            setdefaultattr(self, \"velocity_unit\", self.quan(k[\"uvel\"], \"cm/s\"))\n        else:\n            if \"LengthUnits\" in self.parameters:\n                length_unit = self.parameters[\"LengthUnits\"]\n                mass_unit = self.parameters[\"DensityUnits\"] * length_unit**3\n                time_unit = self.parameters[\"TimeUnits\"]\n            elif \"SimulationControl\" in self.parameters:\n                units = self.parameters[\"SimulationControl\"][\"Units\"]\n                length_unit = units[\"Length\"]\n                mass_unit = units[\"Density\"] * length_unit**3\n                time_unit = units[\"Time\"]\n            else:\n                mylog.warning(\"Setting 1.0 in code units to be 1.0 cm\")\n                mylog.warning(\"Setting 1.0 in code units to be 1.0 s\")\n                length_unit = mass_unit = time_unit = 1.0\n\n            setdefaultattr(self, \"length_unit\", self.quan(length_unit, \"cm\"))\n            setdefaultattr(self, \"mass_unit\", self.quan(mass_unit, \"g\"))\n            setdefaultattr(self, \"time_unit\", self.quan(time_unit, \"s\"))\n            setdefaultattr(self, \"velocity_unit\", self.length_unit / self.time_unit)\n\n        density_unit = self.mass_unit / self.length_unit**3\n        magnetic_unit = np.sqrt(4 * np.pi * density_unit) * self.velocity_unit\n        magnetic_unit = np.float64(magnetic_unit.in_cgs())\n        setdefaultattr(self, \"magnetic_unit\", self.quan(magnetic_unit, \"gauss\"))\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        return filename.endswith(\".hierarchy\") or os.path.exists(\n            f\"{filename}.hierarchy\"\n        )\n\n    @classmethod\n    def _guess_candidates(cls, base, directories, files):\n        candidates = [\n            _\n            for _ in files\n            if _.endswith(\".hierarchy\")\n            and os.path.exists(os.path.join(base, _.rsplit(\".\", 1)[0]))\n        ]\n        # Typically, Enzo won't have nested outputs.\n        return candidates, (len(candidates) == 0)\n\n\nclass EnzoDatasetInMemory(EnzoDataset):\n    _index_class = EnzoHierarchyInMemory\n    _dataset_type = \"enzo_inline\"\n\n    def __init__(self, parameter_override=None, conversion_override=None):\n        self.fluid_types += (\"enzo\",)\n        if parameter_override is None:\n            parameter_override = {}\n        self._parameter_override = parameter_override\n        if conversion_override is None:\n            conversion_override = {}\n        self._conversion_override = conversion_override\n\n        Dataset.__init__(self, \"InMemoryParameterFile\", self._dataset_type)\n\n    def _parse_parameter_file(self):\n        enzo = self._obtain_enzo()\n        ncalls = enzo.yt_parameter_file[\"NumberOfPythonCalls\"]\n        self._input_filename = f\"cycle{ncalls:08d}\"\n        self.parameters[\"CurrentTimeIdentifier\"] = time.time()\n        self.parameters.update(enzo.yt_parameter_file)\n        self.conversion_factors.update(enzo.conversion_factors)\n        for i in self.parameters:\n            if isinstance(self.parameters[i], tuple):\n                self.parameters[i] = np.array(self.parameters[i])\n            if i.endswith(\"Units\") and not i.startswith(\"Temperature\"):\n                dataType = i[:-5]\n                self.conversion_factors[dataType] = self.parameters[i]\n        self.domain_left_edge = self.parameters[\"DomainLeftEdge\"].copy()\n        self.domain_right_edge = self.parameters[\"DomainRightEdge\"].copy()\n        for i in self.conversion_factors:\n            if isinstance(self.conversion_factors[i], tuple):\n                self.conversion_factors[i] = np.array(self.conversion_factors[i])\n        for p, v in self._parameter_override.items():\n            self.parameters[p] = v\n        for p, v in self._conversion_override.items():\n            self.conversion_factors[p] = v\n        self.refine_by = self.parameters[\"RefineBy\"]\n        self._periodicity = tuple(\n            always_iterable(self.parameters[\"LeftFaceBoundaryCondition\"] == 3)\n        )\n        self.dimensionality = self.parameters[\"TopGridRank\"]\n        self.domain_dimensions = self.parameters[\"TopGridDimensions\"]\n        self.current_time = self.parameters[\"InitialTime\"]\n        if self.parameters[\"ComovingCoordinates\"]:\n            self.cosmological_simulation = 1\n            self.current_redshift = self.parameters[\"CosmologyCurrentRedshift\"]\n            self.omega_lambda = self.parameters[\"CosmologyOmegaLambdaNow\"]\n            self.omega_matter = self.parameters[\"CosmologyOmegaMatterNow\"]\n            self.hubble_constant = self.parameters[\"CosmologyHubbleConstantNow\"]\n        else:\n            self.current_redshift = 0.0\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0\n            self.hubble_constant = 0.0\n            self.cosmological_simulation = 0\n\n    def _obtain_enzo(self):\n        import enzo\n\n        return enzo\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        return False\n\n\n# These next two functions are taken from\n# http://www.reddit.com/r/Python/comments/6hj75/reverse_file_iterator/c03vms4\n# Credit goes to \"Brian\" on Reddit\n\n\ndef rblocks(f, blocksize=4096):\n    \"\"\"Read file as series of blocks from end of file to start.\n\n    The data itself is in normal order, only the order of the blocks is reversed.\n    ie. \"hello world\" -> [\"ld\",\"wor\", \"lo \", \"hel\"]\n    Note that the file must be opened in binary mode.\n    \"\"\"\n    if \"b\" not in f.mode.lower():\n        raise Exception(\"File must be opened using binary mode.\")\n    size = os.stat(f.name).st_size\n    fullblocks, lastblock = divmod(size, blocksize)\n\n    # The first(end of file) block will be short, since this leaves\n    # the rest aligned on a blocksize boundary.  This may be more\n    # efficient than having the last (first in file) block be short\n    f.seek(-lastblock, 2)\n    yield f.read(lastblock).decode(\"ascii\")\n\n    for i in range(fullblocks - 1, -1, -1):\n        f.seek(i * blocksize)\n        yield f.read(blocksize).decode(\"ascii\")\n\n\ndef rlines(f, keepends=False):\n    \"\"\"Iterate through the lines of a file in reverse order.\n\n    If keepends is true, line endings are kept as part of the line.\n    \"\"\"\n    buf = \"\"\n    for block in rblocks(f):\n        buf = block + buf\n        lines = buf.splitlines(keepends)\n        # Return all lines except the first (since may be partial)\n        if lines:\n            lines.reverse()\n            buf = lines.pop()  # Last line becomes end of new first line.\n            yield from lines\n    yield buf  # First line.\n"
  },
  {
    "path": "yt/frontends/enzo/definitions.py",
    "content": ""
  },
  {
    "path": "yt/frontends/enzo/fields.py",
    "content": "import numpy as np\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.utilities.physical_constants import me, mp\n\nb_units = \"code_magnetic\"\ne_units = \"code_magnetic/c\"\nra_units = \"code_length / code_time**2\"\nrho_units = \"code_mass / code_length**3\"\nvel_units = \"code_velocity\"\n\nknown_species_names = {\n    \"HI\": \"H_p0\",\n    \"HII\": \"H_p1\",\n    \"HeI\": \"He_p0\",\n    \"HeII\": \"He_p1\",\n    \"HeIII\": \"He_p2\",\n    \"H2I\": \"H2_p0\",\n    \"H2II\": \"H2_p1\",\n    \"HM\": \"H_m1\",\n    \"HeH\": \"HeH_p0\",\n    \"DI\": \"D_p0\",\n    \"DII\": \"D_p1\",\n    \"HDI\": \"HD_p0\",\n    \"Electron\": \"El\",\n    \"OI\": \"O_p0\",\n    \"OII\": \"O_p1\",\n    \"OIII\": \"O_p2\",\n    \"OIV\": \"O_p3\",\n    \"OV\": \"O_p4\",\n    \"OVI\": \"O_p5\",\n    \"OVII\": \"O_p6\",\n    \"OVIII\": \"O_p7\",\n    \"OIX\": \"O_p8\",\n}\n\nNODAL_FLAGS = {\n    \"BxF\": [1, 0, 0],\n    \"ByF\": [0, 1, 0],\n    \"BzF\": [0, 0, 1],\n    \"Ex\": [0, 1, 1],\n    \"Ey\": [1, 0, 1],\n    \"Ez\": [1, 1, 0],\n    \"AvgElec0\": [0, 1, 1],\n    \"AvgElec1\": [1, 0, 1],\n    \"AvgElec2\": [1, 1, 0],\n}\n\n\nclass EnzoFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"Cooling_Time\", (\"s\", [\"cooling_time\"], None)),\n        (\"Dengo_Cooling_Rate\", (\"erg/g/s\", [], None)),\n        (\"Grackle_Cooling_Rate\", (\"erg/s/cm**3\", [], None)),\n        (\"HI_kph\", (\"1/code_time\", [\"H_p0_ionization_rate\"], None)),\n        (\"HeI_kph\", (\"1/code_time\", [\"He_p0_ionization_rate\"], None)),\n        (\"HeII_kph\", (\"1/code_time\", [\"He_p1_ionization_rate\"], None)),\n        (\"H2I_kdiss\", (\"1/code_time\", [\"H2_p0_dissociation_rate\"], None)),\n        (\"HM_kph\", (\"1/code_time\", [\"H_m1_ionization_rate\"], None)),\n        (\"H2II_kdiss\", (\"1/code_time\", [\"H2_p1_dissociation_rate\"], None)),\n        (\"Bx\", (b_units, [], None)),\n        (\"By\", (b_units, [], None)),\n        (\"Bz\", (b_units, [], None)),\n        (\"BxF\", (b_units, [], None)),\n        (\"ByF\", (b_units, [], None)),\n        (\"BzF\", (b_units, [], None)),\n        (\"Ex\", (e_units, [], None)),\n        (\"Ey\", (e_units, [], None)),\n        (\"Ez\", (e_units, [], None)),\n        (\"AvgElec0\", (e_units, [], None)),\n        (\"AvgElec1\", (e_units, [], None)),\n        (\"AvgElec2\", (e_units, [], None)),\n        (\"RadAccel1\", (ra_units, [\"radiation_acceleration_x\"], None)),\n        (\"RadAccel2\", (ra_units, [\"radiation_acceleration_y\"], None)),\n        (\"RadAccel3\", (ra_units, [\"radiation_acceleration_z\"], None)),\n        (\"Dark_Matter_Density\", (rho_units, [\"dark_matter_density\"], None)),\n        (\"Temperature\", (\"K\", [\"temperature\"], None)),\n        (\"Dust_Temperature\", (\"K\", [\"dust_temperature\"], None)),\n        (\"x-velocity\", (vel_units, [\"velocity_x\"], None)),\n        (\"y-velocity\", (vel_units, [\"velocity_y\"], None)),\n        (\"z-velocity\", (vel_units, [\"velocity_z\"], None)),\n        (\"RaySegments\", (\"\", [\"ray_segments\"], None)),\n        (\"PhotoGamma\", (\"eV/code_time\", [\"photo_gamma\"], None)),\n        (\"PotentialField\", (\"code_velocity**2\", [\"gravitational_potential\"], None)),\n        (\"Density\", (rho_units, [\"density\"], None)),\n        (\"Metal_Density\", (rho_units, [\"metal_density\"], None)),\n        (\"SN_Colour\", (rho_units, [], None)),\n        # Note: we do not alias Electron_Density to anything\n        (\"Electron_Density\", (rho_units, [], None)),\n    )\n\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_position_z\", (\"code_length\", [], None)),\n        (\"particle_velocity_x\", (vel_units, [], None)),\n        (\"particle_velocity_y\", (vel_units, [], None)),\n        (\"particle_velocity_z\", (vel_units, [], None)),\n        (\"creation_time\", (\"code_time\", [], None)),\n        (\"dynamical_time\", (\"code_time\", [], None)),\n        (\"metallicity_fraction\", (\"code_metallicity\", [], None)),\n        (\"metallicity\", (\"\", [], None)),\n        (\"particle_type\", (\"\", [], None)),\n        (\"particle_index\", (\"\", [], None)),\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"GridID\", (\"\", [], None)),\n        (\"identifier\", (\"\", [\"particle_index\"], None)),\n        (\"level\", (\"\", [], None)),\n        (\"AccretionRate\", (\"code_mass/code_time\", [], None)),\n        (\"AccretionRateTime\", (\"code_time\", [], None)),\n        (\"AccretionRadius\", (\"code_length\", [], None)),\n        (\"RadiationLifetime\", (\"code_time\", [], None)),\n    )\n\n    def __init__(self, ds, field_list):\n        hydro_method = ds.parameters.get(\"HydroMethod\", None)\n        if hydro_method is None:\n            hydro_method = ds.parameters[\"Physics\"][\"Hydro\"][\"HydroMethod\"]\n        if hydro_method == 2:\n            sl_left = slice(None, -2, None)\n            sl_right = slice(1, -1, None)\n            div_fac = 1.0\n        else:\n            sl_left = slice(None, -2, None)\n            sl_right = slice(2, None, None)\n            div_fac = 2.0\n        slice_info = (sl_left, sl_right, div_fac)\n        super().__init__(ds, field_list, slice_info)\n\n        # setup nodal flag information\n        for field in NODAL_FLAGS:\n            if (\"enzo\", field) in self:\n                finfo = self[\"enzo\", field]\n                finfo.nodal_flag = np.array(NODAL_FLAGS[field])\n\n    def add_species_field(self, species):\n        # This is currently specific to Enzo.  Hopefully in the future we will\n        # have deeper integration with other systems, such as Dengo, to provide\n        # better understanding of ionization and molecular states.\n        #\n        # We have several fields to add based on a given species field.  First\n        # off, we add the species field itself.  Then we'll add a few more\n        # items...\n        #\n        self.add_output_field(\n            (\"enzo\", f\"{species}_Density\"),\n            sampling_type=\"cell\",\n            take_log=True,\n            units=\"code_mass/code_length**3\",\n        )\n        yt_name = known_species_names[species]\n        # don't alias electron density since mass is wrong\n        if species != \"Electron\":\n            self.alias((\"gas\", f\"{yt_name}_density\"), (\"enzo\", f\"{species}_Density\"))\n\n    def setup_species_fields(self):\n        species_names = [\n            fn.rsplit(\"_Density\")[0]\n            for ft, fn in self.field_list\n            if fn.endswith(\"_Density\")\n        ]\n        species_names = [sp for sp in species_names if sp in known_species_names]\n\n        def _electron_density(data):\n            return data[\"enzo\", \"Electron_Density\"] * (me / mp)\n\n        self.add_field(\n            (\"gas\", \"El_density\"),\n            sampling_type=\"cell\",\n            function=_electron_density,\n            units=self.ds.unit_system[\"density\"],\n        )\n        for sp in species_names:\n            self.add_species_field(sp)\n            self.species_names.append(known_species_names[sp])\n        self.species_names.sort()  # bb #1059\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        # Now we conditionally load a few other things.\n        params = self.ds.parameters\n        multi_species = params.get(\"MultiSpecies\", None)\n        dengo = params.get(\"DengoChemistryModel\", 0)\n        if multi_species is None:\n            multi_species = params[\"Physics\"][\"AtomicPhysics\"][\"MultiSpecies\"]\n        if multi_species > 0 or dengo == 1:\n            self.setup_species_fields()\n        self.setup_energy_field()\n        setup_magnetic_field_aliases(self, \"enzo\", [f\"B{ax}\" for ax in \"xyz\"])\n\n    def setup_energy_field(self):\n        unit_system = self.ds.unit_system\n        # We check which type of field we need, and then we add it.\n        ge_name = None\n        te_name = None\n        params = self.ds.parameters\n        multi_species = params.get(\"MultiSpecies\", None)\n        if multi_species is None:\n            multi_species = params[\"Physics\"][\"AtomicPhysics\"][\"MultiSpecies\"]\n        hydro_method = params.get(\"HydroMethod\", None)\n        if hydro_method is None:\n            hydro_method = params[\"Physics\"][\"Hydro\"][\"HydroMethod\"]\n        dual_energy = params.get(\"DualEnergyFormalism\", None)\n        if dual_energy is None:\n            dual_energy = params[\"Physics\"][\"Hydro\"][\"DualEnergyFormalism\"]\n        if (\"enzo\", \"Gas_Energy\") in self.field_list:\n            ge_name = \"Gas_Energy\"\n        elif (\"enzo\", \"GasEnergy\") in self.field_list:\n            ge_name = \"GasEnergy\"\n        if (\"enzo\", \"Total_Energy\") in self.field_list:\n            te_name = \"Total_Energy\"\n        elif (\"enzo\", \"TotalEnergy\") in self.field_list:\n            te_name = \"TotalEnergy\"\n\n        if hydro_method == 2 and te_name is not None:\n            self.add_output_field(\n                (\"enzo\", te_name), sampling_type=\"cell\", units=\"code_velocity**2\"\n            )\n            self.alias((\"gas\", \"specific_thermal_energy\"), (\"enzo\", te_name))\n\n            def _ge_plus_kin(data):\n                ret = data[\"enzo\", te_name] + 0.5 * data[\"gas\", \"velocity_x\"] ** 2.0\n                if data.ds.dimensionality > 1:\n                    ret += 0.5 * data[\"gas\", \"velocity_y\"] ** 2.0\n                if data.ds.dimensionality > 2:\n                    ret += 0.5 * data[\"gas\", \"velocity_z\"] ** 2.0\n                return ret\n\n            self.add_field(\n                (\"gas\", \"specific_total_energy\"),\n                sampling_type=\"cell\",\n                function=_ge_plus_kin,\n                units=unit_system[\"specific_energy\"],\n            )\n        elif dual_energy == 1:\n            if te_name is not None:\n                self.add_output_field(\n                    (\"enzo\", te_name), sampling_type=\"cell\", units=\"code_velocity**2\"\n                )\n                self.alias(\n                    (\"gas\", \"specific_total_energy\"),\n                    (\"enzo\", te_name),\n                    units=unit_system[\"specific_energy\"],\n                )\n            if ge_name is not None:\n                self.add_output_field(\n                    (\"enzo\", ge_name), sampling_type=\"cell\", units=\"code_velocity**2\"\n                )\n                self.alias(\n                    (\"gas\", \"specific_thermal_energy\"),\n                    (\"enzo\", ge_name),\n                    units=unit_system[\"specific_energy\"],\n                )\n        elif hydro_method in (4, 6) and te_name is not None:\n            self.add_output_field(\n                (\"enzo\", te_name), sampling_type=\"cell\", units=\"code_velocity**2\"\n            )\n\n            # Subtract off B-field energy\n            def _sub_b(data):\n                ret = data[\"enzo\", te_name] - 0.5 * data[\"gas\", \"velocity_x\"] ** 2.0\n                if data.ds.dimensionality > 1:\n                    ret -= 0.5 * data[\"gas\", \"velocity_y\"] ** 2.0\n                if data.ds.dimensionality > 2:\n                    ret -= 0.5 * data[\"gas\", \"velocity_z\"] ** 2.0\n                ret -= data[\"gas\", \"magnetic_energy_density\"] / data[\"gas\", \"density\"]\n                return ret\n\n            self.add_field(\n                (\"gas\", \"specific_thermal_energy\"),\n                sampling_type=\"cell\",\n                function=_sub_b,\n                units=unit_system[\"specific_energy\"],\n            )\n        elif te_name is not None:  # Otherwise, we assume TotalEnergy is kinetic+thermal\n            self.add_output_field(\n                (\"enzo\", te_name), sampling_type=\"cell\", units=\"code_velocity**2\"\n            )\n            self.alias(\n                (\"gas\", \"specific_total_energy\"),\n                (\"enzo\", te_name),\n                units=unit_system[\"specific_energy\"],\n            )\n\n            def _tot_minus_kin(data):\n                ret = data[\"enzo\", te_name] - 0.5 * data[\"gas\", \"velocity_x\"] ** 2.0\n                if data.ds.dimensionality > 1:\n                    ret -= 0.5 * data[\"gas\", \"velocity_y\"] ** 2.0\n                if data.ds.dimensionality > 2:\n                    ret -= 0.5 * data[\"gas\", \"velocity_z\"] ** 2.0\n                return ret\n\n            self.add_field(\n                (\"gas\", \"specific_thermal_energy\"),\n                sampling_type=\"cell\",\n                function=_tot_minus_kin,\n                units=unit_system[\"specific_energy\"],\n            )\n        if multi_species == 0 and \"Mu\" in params:\n\n            def _mean_molecular_weight(data):\n                return params[\"Mu\"] * data[\"index\", \"ones\"]\n\n            self.add_field(\n                (\"gas\", \"mean_molecular_weight\"),\n                sampling_type=\"cell\",\n                function=_mean_molecular_weight,\n                units=\"\",\n            )\n\n            def _number_density(data):\n                return data[\"gas\", \"density\"] / (mp * params[\"Mu\"])\n\n            self.add_field(\n                (\"gas\", \"number_density\"),\n                sampling_type=\"cell\",\n                function=_number_density,\n                units=unit_system[\"number_density\"],\n            )\n\n    def setup_particle_fields(self, ptype):\n        def _age(data):\n            return data.ds.current_time - data[\"all\", \"creation_time\"]\n\n        self.add_field(\n            (ptype, \"age\"), sampling_type=\"particle\", function=_age, units=\"yr\"\n        )\n\n        super().setup_particle_fields(ptype)\n"
  },
  {
    "path": "yt/frontends/enzo/io.py",
    "content": "import numpy as np\n\nfrom yt.geometry.selection_routines import GridSelector\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n_convert_mass = (\"particle_mass\", \"mass\")\n\n_particle_position_names: dict[str, str] = {}\n\n\nclass IOHandlerPackedHDF5(BaseIOHandler):\n    _dataset_type = \"enzo_packed_3d\"\n    _base = slice(None)\n    _field_dtype = \"float64\"\n\n    def _read_field_names(self, grid):\n        if grid.filename is None:\n            return []\n        f = h5py.File(grid.filename, mode=\"r\")\n        try:\n            group = f[f\"/Grid{grid.id:08}\"]\n        except KeyError:\n            group = f\n        fields = []\n        dtypes = set()\n        add_io = \"io\" in grid.ds.particle_types\n        add_dm = \"DarkMatter\" in grid.ds.particle_types\n        for name, v in group.items():\n            # NOTE: This won't work with 1D datasets or references.\n            # For all versions of Enzo I know about, we can assume all floats\n            # are of the same size.  So, let's grab one.\n            if not hasattr(v, \"shape\") or v.dtype == \"O\":\n                continue\n            elif len(v.dims) == 1:\n                if grid.ds.dimensionality == 1:\n                    fields.append((\"enzo\", str(name)))\n                elif add_io:\n                    fields.append((\"io\", str(name)))\n                elif add_dm:\n                    fields.append((\"DarkMatter\", str(name)))\n            else:\n                fields.append((\"enzo\", str(name)))\n                dtypes.add(v.dtype)\n\n        if len(dtypes) == 1:\n            # Now, if everything we saw was the same dtype, we can go ahead and\n            # set it here.  We do this because it is a HUGE savings for 32 bit\n            # floats, since our numpy copying/casting is way faster than\n            # h5py's, for some reason I don't understand.  This does *not* need\n            # to be correct -- it will get fixed later -- it just needs to be\n            # okay for now.\n            self._field_dtype = list(dtypes)[0]\n        f.close()\n        return fields\n\n    @property\n    def _read_exception(self):\n        return (KeyError,)\n\n    def _read_particle_coords(self, chunks, ptf):\n        yield from (\n            (ptype, xyz, 0.0)\n            for ptype, xyz in self._read_particle_fields(chunks, ptf, None)\n        )\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        chunks = list(chunks)\n        for chunk in chunks:  # These should be organized by grid filename\n            f = None\n            for g in chunk.objs:\n                if g.filename is None:\n                    continue\n                if f is None:\n                    # print(\"Opening (read) %s\" % g.filename)\n                    f = h5py.File(g.filename, mode=\"r\")\n                nap = sum(g.NumberOfActiveParticles.values())\n                if g.NumberOfParticles == 0 and nap == 0:\n                    continue\n                ds = f.get(f\"/Grid{g.id:08}\")\n                for ptype, field_list in sorted(ptf.items()):\n                    if ptype == \"io\":\n                        if g.NumberOfParticles == 0:\n                            continue\n                        pds = ds\n                    elif ptype == \"DarkMatter\":\n                        if g.NumberOfActiveParticles[ptype] == 0:\n                            continue\n                        pds = ds\n                    elif not g.NumberOfActiveParticles[ptype]:\n                        continue\n                    else:\n                        for pname in [\"Active Particles\", \"Particles\"]:\n                            pds = ds.get(f\"{pname}/{ptype}\")\n                            if pds is not None:\n                                break\n                        else:\n                            raise RuntimeError(\n                                \"Could not find active particle group in data.\"\n                            )\n                    pn = _particle_position_names.get(ptype, r\"particle_position_%s\")\n                    x, y, z = (\n                        np.asarray(pds.get(pn % ax)[()], dtype=\"=f8\") for ax in \"xyz\"\n                    )\n                    if selector is None:\n                        # This only ever happens if the call is made from\n                        # _read_particle_coords.\n                        yield ptype, (x, y, z)\n                        continue\n                    mask = selector.select_points(x, y, z, 0.0)\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        data = np.asarray(pds.get(field)[()], \"=f8\")\n                        if field in _convert_mass:\n                            data *= g.dds.prod(dtype=\"f8\")\n                        yield (ptype, field), data[mask]\n            if f:\n                f.close()\n\n    def io_iter(self, chunks, fields):\n        h5_dtype = self._field_dtype\n        for chunk in chunks:\n            fid = None\n            filename = -1\n            for obj in chunk.objs:\n                if obj.filename is None:\n                    continue\n                if obj.filename != filename:\n                    # Note one really important thing here: even if we do\n                    # implement LRU caching in the _read_obj_field function,\n                    # we'll still be doing file opening and whatnot.  This is a\n                    # problem, but one we can return to.\n                    if fid is not None:\n                        fid.close()\n                    fid = h5py.h5f.open(\n                        obj.filename.encode(\"latin-1\"), h5py.h5f.ACC_RDONLY\n                    )\n                    filename = obj.filename\n                for field in fields:\n                    nodal_flag = self.ds.field_info[field].nodal_flag\n                    dims = obj.ActiveDimensions[::-1] + nodal_flag[::-1]\n                    data = np.empty(dims, dtype=h5_dtype)\n                    yield field, obj, self._read_obj_field(obj, field, (fid, data))\n        if fid is not None:\n            fid.close()\n\n    def _read_obj_field(self, obj, field, fid_data):\n        if fid_data is None:\n            fid_data = (None, None)\n        fid, data = fid_data\n        if fid is None:\n            close = True\n            fid = h5py.h5f.open(obj.filename.encode(\"latin-1\"), h5py.h5f.ACC_RDONLY)\n        else:\n            close = False\n        if data is None:\n            data = np.empty(obj.ActiveDimensions[::-1], dtype=self._field_dtype)\n        ftype, fname = field\n        try:\n            node = f\"/Grid{obj.id:08}/{fname}\"\n            dg = h5py.h5d.open(fid, node.encode(\"latin-1\"))\n        except KeyError:\n            if fname == \"Dark_Matter_Density\":\n                data[:] = 0\n                return data.T\n            raise\n        dg.read(h5py.h5s.ALL, h5py.h5s.ALL, data)\n        # I don't know why, but on some installations of h5py this works, but\n        # on others, nope.  Doesn't seem to be a version thing.\n        # dg.close()\n        if close:\n            fid.close()\n        return data.T\n\n\nclass IOHandlerPackedHDF5GhostZones(IOHandlerPackedHDF5):\n    _dataset_type = \"enzo_packed_3d_gz\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        NGZ = self.ds.parameters.get(\"NumberOfGhostZones\", 3)\n        self._base = (slice(NGZ, -NGZ), slice(NGZ, -NGZ), slice(NGZ, -NGZ))\n\n    def _read_obj_field(self, *args, **kwargs):\n        return super()._read_obj_field(*args, **kwargs)[self._base]\n\n\nclass IOHandlerInMemory(BaseIOHandler):\n    _dataset_type = \"enzo_inline\"\n\n    def __init__(self, ds, ghost_zones=3):\n        self.ds = ds\n        import enzo\n\n        self.enzo = enzo\n        self.grids_in_memory = enzo.grid_data\n        self.old_grids_in_memory = enzo.old_grid_data\n        self.my_slice = (\n            slice(ghost_zones, -ghost_zones),\n            slice(ghost_zones, -ghost_zones),\n            slice(ghost_zones, -ghost_zones),\n        )\n        BaseIOHandler.__init__(self, ds)\n\n    def _read_field_names(self, grid):\n        fields = []\n        add_io = \"io\" in grid.ds.particle_types\n        for name, v in self.grids_in_memory[grid.id].items():\n            # NOTE: This won't work with 1D datasets or references.\n            if not hasattr(v, \"shape\") or v.dtype == \"O\":\n                continue\n            elif v.ndim == 1:\n                if grid.ds.dimensionality == 1:\n                    fields.append((\"enzo\", str(name)))\n                elif add_io:\n                    fields.append((\"io\", str(name)))\n            else:\n                fields.append((\"enzo\", str(name)))\n        return fields\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        rv = {}\n        # Now we have to do something unpleasant\n        chunks = list(chunks)\n        if isinstance(selector, GridSelector):\n            if not (len(chunks) == len(chunks[0].objs) == 1):\n                raise RuntimeError\n            g = chunks[0].objs[0]\n            for ftype, fname in fields:\n                rv[ftype, fname] = self.grids_in_memory[g.id][fname].swapaxes(0, 2)\n            return rv\n        if size is None:\n            size = sum(g.count(selector) for chunk in chunks for g in chunk.objs)\n        for field in fields:\n            ftype, fname = field\n            fsize = size\n            rv[field] = np.empty(fsize, dtype=\"float64\")\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s grids\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n\n        ind = 0\n        for chunk in chunks:\n            for g in chunk.objs:\n                # We want a *hard error* here.\n                # if g.id not in self.grids_in_memory: continue\n                for field in fields:\n                    ftype, fname = field\n                    data_view = self.grids_in_memory[g.id][fname][\n                        self.my_slice\n                    ].swapaxes(0, 2)\n                    nd = g.select(selector, data_view, rv[field], ind)\n                ind += nd\n        assert ind == fsize\n        return rv\n\n    def _read_particle_coords(self, chunks, ptf):\n        chunks = list(chunks)\n        for chunk in chunks:  # These should be organized by grid filename\n            for g in chunk.objs:\n                if g.id not in self.grids_in_memory:\n                    continue\n                nap = sum(g.NumberOfActiveParticles.values())\n                if g.NumberOfParticles == 0 and nap == 0:\n                    continue\n                for ptype in sorted(ptf):\n                    x, y, z = (\n                        self.grids_in_memory[g.id][\"particle_position_x\"],\n                        self.grids_in_memory[g.id][\"particle_position_y\"],\n                        self.grids_in_memory[g.id][\"particle_position_z\"],\n                    )\n                    yield ptype, (x, y, z), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        chunks = list(chunks)\n        for chunk in chunks:  # These should be organized by grid filename\n            for g in chunk.objs:\n                if g.id not in self.grids_in_memory:\n                    continue\n                nap = sum(g.NumberOfActiveParticles.values())\n                if g.NumberOfParticles == 0 and nap == 0:\n                    continue\n                for ptype, field_list in sorted(ptf.items()):\n                    x, y, z = (\n                        self.grids_in_memory[g.id][\"particle_position_x\"],\n                        self.grids_in_memory[g.id][\"particle_position_y\"],\n                        self.grids_in_memory[g.id][\"particle_position_z\"],\n                    )\n                    mask = selector.select_points(x, y, z, 0.0)\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        data = self.grids_in_memory[g.id][field]\n                        if field in _convert_mass:\n                            data = data * g.dds.prod(dtype=\"f8\")\n                        yield (ptype, field), data[mask]\n\n\nclass IOHandlerPacked2D(IOHandlerPackedHDF5):\n    _dataset_type = \"enzo_packed_2d\"\n    _particle_reader = False\n\n    def _read_data_set(self, grid, field):\n        f = h5py.File(grid.filename, mode=\"r\")\n        ds = f[f\"/Grid{grid.id:08}/{field}\"][:]\n        f.close()\n        return ds.transpose()[:, :, None]\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        rv = {}\n        # Now we have to do something unpleasant\n        chunks = list(chunks)\n        if isinstance(selector, GridSelector):\n            if not (len(chunks) == len(chunks[0].objs) == 1):\n                raise RuntimeError\n            g = chunks[0].objs[0]\n            f = h5py.File(g.filename, mode=\"r\")\n            gds = f.get(f\"/Grid{g.id:08}\")\n            for ftype, fname in fields:\n                rv[ftype, fname] = np.atleast_3d(gds.get(fname)[()].transpose())\n            f.close()\n            return rv\n        if size is None:\n            size = sum(g.count(selector) for chunk in chunks for g in chunk.objs)\n        for field in fields:\n            ftype, fname = field\n            fsize = size\n            rv[field] = np.empty(fsize, dtype=\"float64\")\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s grids\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n        ind = 0\n        for chunk in chunks:\n            f = None\n            for g in chunk.objs:\n                if f is None:\n                    # print(\"Opening (count) %s\" % g.filename)\n                    f = h5py.File(g.filename, mode=\"r\")\n                gds = f.get(f\"/Grid{g.id:08}\")\n                if gds is None:\n                    gds = f\n                for field in fields:\n                    ftype, fname = field\n                    ds = np.atleast_3d(gds.get(fname)[()].transpose())\n                    nd = g.select(selector, ds, rv[field], ind)  # caches\n                ind += nd\n            f.close()\n        return rv\n\n\nclass IOHandlerPacked1D(IOHandlerPackedHDF5):\n    _dataset_type = \"enzo_packed_1d\"\n    _particle_reader = False\n\n    def _read_data_set(self, grid, field):\n        f = h5py.File(grid.filename, mode=\"r\")\n        ds = f[f\"/Grid{grid.id:08}/{field}\"][:]\n        f.close()\n        return ds.transpose()[:, None, None]\n"
  },
  {
    "path": "yt/frontends/enzo/misc.py",
    "content": "import numpy as np\n\nfrom yt.utilities.physical_ratios import (\n    boltzmann_constant_erg_per_K,\n    cm_per_mpc,\n    mass_hydrogen_grams,\n    newton_cgs,\n    rho_crit_g_cm3_h2,\n)\n\n\ndef cosmology_get_units(\n    hubble_constant, omega_matter, box_size, initial_redshift, current_redshift\n):\n    \"\"\"\n    Return a dict of Enzo cosmological unit conversions.\n    \"\"\"\n\n    zp1 = 1.0 + current_redshift\n    zip1 = 1.0 + initial_redshift\n\n    k = {}\n    # For better agreement with values calculated by Enzo,\n    # adopt the exact constants that are used there.\n\n    time_scaling = np.sqrt(1 / (4 * np.pi * newton_cgs * rho_crit_g_cm3_h2))\n    vel_scaling = cm_per_mpc / time_scaling\n    temp_scaling = mass_hydrogen_grams / boltzmann_constant_erg_per_K * vel_scaling**2\n\n    k[\"utim\"] = time_scaling / np.sqrt(omega_matter) / hubble_constant / zip1**1.5\n    k[\"urho\"] = rho_crit_g_cm3_h2 * omega_matter * hubble_constant**2 * zp1**3\n    k[\"uxyz\"] = cm_per_mpc * box_size / hubble_constant / zp1\n    k[\"uaye\"] = 1.0 / zip1\n    k[\"uvel\"] = vel_scaling * box_size * np.sqrt(omega_matter) * np.sqrt(zip1)\n    k[\"utem\"] = temp_scaling * (box_size**2) * omega_matter * zip1\n    k[\"aye\"] = zip1 / zp1\n    return k\n"
  },
  {
    "path": "yt/frontends/enzo/simulation_handling.py",
    "content": "import glob\nimport os\n\nimport numpy as np\nfrom unyt import dimensions, unyt_array\nfrom unyt.unit_registry import UnitRegistry\n\nfrom yt.data_objects.time_series import DatasetSeries, SimulationTimeSeries\nfrom yt.funcs import only_on_root\nfrom yt.loaders import load\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.exceptions import (\n    InvalidSimulationTimeSeries,\n    MissingParameter,\n    NoStoppingCondition,\n    YTUnidentifiedDataType,\n)\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_objects\n\n\nclass EnzoSimulation(SimulationTimeSeries):\n    r\"\"\"\n    Initialize an Enzo Simulation object.\n\n    Upon creation, the parameter file is parsed and the time and redshift\n    are calculated and stored in all_outputs.  A time units dictionary is\n    instantiated to allow for time outputs to be requested with physical\n    time units.  The get_time_series can be used to generate a\n    DatasetSeries object.\n\n    parameter_filename : str\n        The simulation parameter file.\n    find_outputs : bool\n        If True, subdirectories within the GlobalDir directory are\n        searched one by one for datasets.  Time and redshift\n        information are gathered by temporarily instantiating each\n        dataset.  This can be used when simulation data was created\n        in a non-standard way, making it difficult to guess the\n        corresponding time and redshift information.\n        Default: False.\n\n    Examples\n    --------\n    >>> import yt\n    >>> es = yt.load_simulation(\"enzo_tiny_cosmology/32Mpc_32.enzo\", \"Enzo\")\n    >>> es.get_time_series()\n    >>> for ds in es:\n    ...     print(ds.current_time)\n\n    \"\"\"\n\n    def __init__(self, parameter_filename, find_outputs=False):\n        self.simulation_type = \"grid\"\n        self.key_parameters = [\"stop_cycle\"]\n        SimulationTimeSeries.__init__(\n            self, parameter_filename, find_outputs=find_outputs\n        )\n\n    def _set_units(self):\n        self.unit_registry = UnitRegistry()\n        self.unit_registry.add(\"code_time\", 1.0, dimensions.time)\n        self.unit_registry.add(\"code_length\", 1.0, dimensions.length)\n        if self.cosmological_simulation:\n            # Instantiate EnzoCosmology object for units and time conversions.\n            self.cosmology = EnzoCosmology(\n                self.parameters[\"CosmologyHubbleConstantNow\"],\n                self.parameters[\"CosmologyOmegaMatterNow\"],\n                self.parameters[\"CosmologyOmegaLambdaNow\"],\n                self.parameters.get(\"CosmologyOmegaRadiationNow\", 0.0),\n                0.0,\n                self.parameters[\"CosmologyInitialRedshift\"],\n                unit_registry=self.unit_registry,\n            )\n\n            self.time_unit = self.cosmology.time_unit.in_units(\"s\")\n            if \"h\" in self.unit_registry:\n                self.unit_registry.modify(\"h\", self.hubble_constant)\n            else:\n                self.unit_registry.add(\n                    \"h\", self.hubble_constant, dimensions.dimensionless\n                )\n            # Comoving lengths\n            for my_unit in [\"m\", \"pc\", \"AU\"]:\n                new_unit = f\"{my_unit}cm\"\n                # technically not true, but should be ok\n                self.unit_registry.add(\n                    new_unit,\n                    self.unit_registry.lut[my_unit][0],\n                    dimensions.length,\n                    f\"\\\\rm{{{my_unit}}}/(1+z)\",\n                    prefixable=True,\n                )\n            self.length_unit = self.quan(\n                self.box_size, \"Mpccm / h\", registry=self.unit_registry\n            )\n        else:\n            self.time_unit = self.quan(self.parameters[\"TimeUnits\"], \"s\")\n            self.length_unit = self.quan(self.parameters[\"LengthUnits\"], \"cm\")\n        self.box_size = self.length_unit\n        self.domain_left_edge = self.domain_left_edge * self.length_unit\n        self.domain_right_edge = self.domain_right_edge * self.length_unit\n        self.unit_registry.modify(\"code_time\", self.time_unit)\n        self.unit_registry.modify(\"code_length\", self.length_unit)\n        self.unit_registry.add(\n            \"unitary\", float(self.box_size.in_base()), self.length_unit.units.dimensions\n        )\n\n    def get_time_series(\n        self,\n        time_data=True,\n        redshift_data=True,\n        initial_time=None,\n        final_time=None,\n        initial_redshift=None,\n        final_redshift=None,\n        initial_cycle=None,\n        final_cycle=None,\n        times=None,\n        redshifts=None,\n        tolerance=None,\n        parallel=True,\n        setup_function=None,\n    ):\n        \"\"\"\n        Instantiate a DatasetSeries object for a set of outputs.\n\n        If no additional keywords given, a DatasetSeries object will be\n        created with all potential datasets created by the simulation.\n\n        Outputs can be gather by specifying a time or redshift range\n        (or combination of time and redshift), with a specific list of\n        times or redshifts, a range of cycle numbers (for cycle based\n        output), or by simply searching all subdirectories within the\n        simulation directory.\n\n        time_data : bool\n            Whether or not to include time outputs when gathering\n            datasets for time series.\n            Default: True.\n        redshift_data : bool\n            Whether or not to include redshift outputs when gathering\n            datasets for time series.\n            Default: True.\n        initial_time : tuple of type (float, str)\n            The earliest time for outputs to be included.  This should be\n            given as the value and the string representation of the units.\n            For example, (5.0, \"Gyr\").  If None, the initial time of the\n            simulation is used.  This can be used in combination with\n            either final_time or final_redshift.\n            Default: None.\n        final_time : tuple of type (float, str)\n            The latest time for outputs to be included.  This should be\n            given as the value and the string representation of the units.\n            For example, (13.7, \"Gyr\"). If None, the final time of the\n            simulation is used.  This can be used in combination with either\n            initial_time or initial_redshift.\n            Default: None.\n        times : tuple of type (float array, str)\n            A list of times for which outputs will be found and the units\n            of those values.  For example, ([0, 1, 2, 3], \"s\").\n            Default: None.\n        initial_redshift : float\n            The earliest redshift for outputs to be included.  If None,\n            the initial redshift of the simulation is used.  This can be\n            used in combination with either final_time or\n            final_redshift.\n            Default: None.\n        final_redshift : float\n            The latest redshift for outputs to be included.  If None,\n            the final redshift of the simulation is used.  This can be\n            used in combination with either initial_time or\n            initial_redshift.\n            Default: None.\n        redshifts : array_like\n            A list of redshifts for which outputs will be found.\n            Default: None.\n        initial_cycle : float\n            The earliest cycle for outputs to be included.  If None,\n            the initial cycle of the simulation is used.  This can\n            only be used with final_cycle.\n            Default: None.\n        final_cycle : float\n            The latest cycle for outputs to be included.  If None,\n            the final cycle of the simulation is used.  This can\n            only be used in combination with initial_cycle.\n            Default: None.\n        tolerance : float\n            Used in combination with \"times\" or \"redshifts\" keywords,\n            this is the tolerance within which outputs are accepted\n            given the requested times or redshifts.  If None, the\n            nearest output is always taken.\n            Default: None.\n        parallel : bool/int\n            If True, the generated DatasetSeries will divide the work\n            such that a single processor works on each dataset.  If an\n            integer is supplied, the work will be divided into that\n            number of jobs.\n            Default: True.\n        setup_function : callable, accepts a ds\n            This function will be called whenever a dataset is loaded.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> es = yt.load_simulation(\"enzo_tiny_cosmology/32Mpc_32.enzo\", \"Enzo\")\n        >>> es.get_time_series(\n        ...     initial_redshift=10, final_time=(13.7, \"Gyr\"), redshift_data=False\n        ... )\n        >>> for ds in es:\n        ...     print(ds.current_time)\n        >>> es.get_time_series(redshifts=[3, 2, 1, 0])\n        >>> for ds in es:\n        ...     print(ds.current_time)\n\n        \"\"\"\n\n        if (\n            initial_redshift is not None or final_redshift is not None\n        ) and not self.cosmological_simulation:\n            raise InvalidSimulationTimeSeries(\n                \"An initial or final redshift has been given for a \"\n                + \"noncosmological simulation.\"\n            )\n\n        if time_data and redshift_data:\n            my_all_outputs = self.all_outputs\n        elif time_data:\n            my_all_outputs = self.all_time_outputs\n        elif redshift_data:\n            my_all_outputs = self.all_redshift_outputs\n        else:\n            raise InvalidSimulationTimeSeries(\n                \"Both time_data and redshift_data are False.\"\n            )\n\n        if not my_all_outputs:\n            DatasetSeries.__init__(self, outputs=[], parallel=parallel)\n            mylog.info(\"0 outputs loaded into time series.\")\n            return\n\n        # Apply selection criteria to the set.\n        if times is not None:\n            my_outputs = self._get_outputs_by_key(\n                \"time\", times, tolerance=tolerance, outputs=my_all_outputs\n            )\n\n        elif redshifts is not None:\n            my_outputs = self._get_outputs_by_key(\n                \"redshift\", redshifts, tolerance=tolerance, outputs=my_all_outputs\n            )\n\n        elif initial_cycle is not None or final_cycle is not None:\n            if initial_cycle is None:\n                initial_cycle = 0\n            else:\n                initial_cycle = max(initial_cycle, 0)\n            if final_cycle is None:\n                final_cycle = self.parameters[\"StopCycle\"]\n            else:\n                final_cycle = min(final_cycle, self.parameters[\"StopCycle\"])\n\n            my_outputs = my_all_outputs[\n                int(\n                    np.ceil(float(initial_cycle) / self.parameters[\"CycleSkipDataDump\"])\n                ) : (final_cycle / self.parameters[\"CycleSkipDataDump\"]) + 1\n            ]\n\n        else:\n            if initial_time is not None:\n                if isinstance(initial_time, float):\n                    my_initial_time = self.quan(initial_time, \"code_time\")\n                elif isinstance(initial_time, tuple) and len(initial_time) == 2:\n                    my_initial_time = self.quan(*initial_time)\n                elif not isinstance(initial_time, unyt_array):\n                    raise RuntimeError(\n                        \"Error: initial_time must be given as a float or \"\n                        + \"tuple of (value, units).\"\n                    )\n            elif initial_redshift is not None:\n                my_initial_time = self.cosmology.t_from_z(initial_redshift)\n            else:\n                my_initial_time = self.initial_time\n\n            if final_time is not None:\n                if isinstance(final_time, float):\n                    my_final_time = self.quan(final_time, \"code_time\")\n                elif isinstance(final_time, tuple) and len(final_time) == 2:\n                    my_final_time = self.quan(*final_time)\n                elif not isinstance(final_time, unyt_array):\n                    raise RuntimeError(\n                        \"Error: final_time must be given as a float or \"\n                        + \"tuple of (value, units).\"\n                    )\n            elif final_redshift is not None:\n                my_final_time = self.cosmology.t_from_z(final_redshift)\n            else:\n                my_final_time = self.final_time\n\n            my_initial_time.convert_to_units(\"s\")\n            my_final_time.convert_to_units(\"s\")\n            my_times = np.array([a[\"time\"] for a in my_all_outputs])\n            my_indices = np.digitize([my_initial_time, my_final_time], my_times)\n            if my_initial_time == my_times[my_indices[0] - 1]:\n                my_indices[0] -= 1\n            my_outputs = my_all_outputs[my_indices[0] : my_indices[1]]\n\n        init_outputs = []\n        for output in my_outputs:\n            if os.path.exists(output[\"filename\"]):\n                init_outputs.append(output[\"filename\"])\n\n        DatasetSeries.__init__(\n            self, outputs=init_outputs, parallel=parallel, setup_function=setup_function\n        )\n        mylog.info(\"%d outputs loaded into time series.\", len(init_outputs))\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Parses the parameter file and establishes the various\n        dictionaries.\n        \"\"\"\n\n        self.conversion_factors = {}\n        redshift_outputs = []\n\n        # Let's read the file\n        lines = open(self.parameter_filename).readlines()\n        for line in (l.strip() for l in lines):\n            if \"#\" in line:\n                line = line[0 : line.find(\"#\")]\n            if \"//\" in line:\n                line = line[0 : line.find(\"//\")]\n            if len(line) < 2:\n                continue\n            param, vals = (i.strip() for i in line.split(\"=\", 1))\n            # First we try to decipher what type of value it is.\n            vals = vals.split()\n            # Special case approaching.\n            if \"(do\" in vals:\n                vals = vals[:1]\n            if len(vals) == 0:\n                pcast = str  # Assume NULL output\n            else:\n                v = vals[0]\n                # Figure out if it's castable to floating point:\n                try:\n                    float(v)\n                except ValueError:\n                    pcast = str\n                else:\n                    if any(\".\" in v or \"e\" in v for v in vals):\n                        pcast = float\n                    elif v == \"inf\":\n                        pcast = str\n                    else:\n                        pcast = int\n            # Now we figure out what to do with it.\n            if param.endswith(\"Units\") and not param.startswith(\"Temperature\"):\n                dataType = param[:-5]\n                # This one better be a float.\n                self.conversion_factors[dataType] = float(vals[0])\n            if param.startswith(\"CosmologyOutputRedshift[\"):\n                index = param[param.find(\"[\") + 1 : param.find(\"]\")]\n                redshift_outputs.append(\n                    {\"index\": int(index), \"redshift\": float(vals[0])}\n                )\n            elif len(vals) == 0:\n                vals = \"\"\n            elif len(vals) == 1:\n                vals = pcast(vals[0])\n            else:\n                vals = np.array([pcast(i) for i in vals if i != \"-99999\"])\n            self.parameters[param] = vals\n        self.refine_by = self.parameters[\"RefineBy\"]\n        self.dimensionality = self.parameters[\"TopGridRank\"]\n        if self.dimensionality > 1:\n            self.domain_dimensions = self.parameters[\"TopGridDimensions\"]\n            if len(self.domain_dimensions) < 3:\n                tmp = self.domain_dimensions.tolist()\n                tmp.append(1)\n                self.domain_dimensions = np.array(tmp)\n            self.domain_left_edge = np.array(\n                self.parameters[\"DomainLeftEdge\"], \"float64\"\n            ).copy()\n            self.domain_right_edge = np.array(\n                self.parameters[\"DomainRightEdge\"], \"float64\"\n            ).copy()\n        else:\n            self.domain_left_edge = np.array(\n                self.parameters[\"DomainLeftEdge\"], \"float64\"\n            )\n            self.domain_right_edge = np.array(\n                self.parameters[\"DomainRightEdge\"], \"float64\"\n            )\n            self.domain_dimensions = np.array(\n                [self.parameters[\"TopGridDimensions\"], 1, 1]\n            )\n\n        if self.parameters[\"ComovingCoordinates\"]:\n            cosmo_attr = {\n                \"box_size\": \"CosmologyComovingBoxSize\",\n                \"omega_lambda\": \"CosmologyOmegaLambdaNow\",\n                \"omega_matter\": \"CosmologyOmegaMatterNow\",\n                \"omega_radiation\": \"CosmologyOmegaRadiationNow\",\n                \"hubble_constant\": \"CosmologyHubbleConstantNow\",\n                \"initial_redshift\": \"CosmologyInitialRedshift\",\n                \"final_redshift\": \"CosmologyFinalRedshift\",\n            }\n            self.cosmological_simulation = 1\n            for a, v in cosmo_attr.items():\n                if v not in self.parameters:\n                    raise MissingParameter(self.parameter_filename, v)\n                setattr(self, a, self.parameters[v])\n        else:\n            self.cosmological_simulation = 0\n            self.omega_lambda = self.omega_matter = self.hubble_constant = 0.0\n\n        # make list of redshift outputs\n        self.all_redshift_outputs = []\n        if not self.cosmological_simulation:\n            return\n        for output in redshift_outputs:\n            output[\"filename\"] = os.path.join(\n                self.parameters[\"GlobalDir\"],\n                f\"{self.parameters['RedshiftDumpDir']}{output['index']:04}\",\n                f\"{self.parameters['RedshiftDumpName']}{output['index']:04}\",\n            )\n            del output[\"index\"]\n        self.all_redshift_outputs = redshift_outputs\n\n    def _calculate_time_outputs(self):\n        \"\"\"\n        Calculate time outputs and their redshifts if cosmological.\n        \"\"\"\n\n        self.all_time_outputs = []\n        if (\n            self.final_time is None\n            or \"dtDataDump\" not in self.parameters\n            or self.parameters[\"dtDataDump\"] <= 0.0\n        ):\n            return []\n\n        index = 0\n        current_time = self.initial_time.copy()\n        dt_datadump = self.quan(self.parameters[\"dtDataDump\"], \"code_time\")\n        while current_time <= self.final_time + dt_datadump:\n            filename = os.path.join(\n                self.parameters[\"GlobalDir\"],\n                f\"{self.parameters['DataDumpDir']}{index:04}\",\n                f\"{self.parameters['DataDumpName']}{index:04}\",\n            )\n\n            output = {\"index\": index, \"filename\": filename, \"time\": current_time.copy()}\n            output[\"time\"] = min(output[\"time\"], self.final_time)\n            if self.cosmological_simulation:\n                output[\"redshift\"] = self.cosmology.z_from_t(current_time)\n\n            self.all_time_outputs.append(output)\n            if np.abs(self.final_time - current_time) / self.final_time < 1e-4:\n                break\n            current_time += dt_datadump\n            index += 1\n\n    def _calculate_cycle_outputs(self):\n        \"\"\"\n        Calculate cycle outputs.\n        \"\"\"\n\n        mylog.warning(\"Calculating cycle outputs.  Dataset times will be unavailable.\")\n\n        if (\n            self.stop_cycle is None\n            or \"CycleSkipDataDump\" not in self.parameters\n            or self.parameters[\"CycleSkipDataDump\"] <= 0.0\n        ):\n            return []\n\n        self.all_time_outputs = []\n        index = 0\n        for cycle in range(\n            0, self.stop_cycle + 1, self.parameters[\"CycleSkipDataDump\"]\n        ):\n            filename = os.path.join(\n                self.parameters[\"GlobalDir\"],\n                f\"{self.parameters['DataDumpDir']}{index:04}\",\n                f\"{self.parameters['DataDumpName']}{index:04}\",\n            )\n\n            output = {\"index\": index, \"filename\": filename, \"cycle\": cycle}\n            self.all_time_outputs.append(output)\n            index += 1\n\n    def _get_all_outputs(self, *, find_outputs=False):\n        \"\"\"\n        Get all potential datasets and combine into a time-sorted list.\n        \"\"\"\n\n        # Create the set of outputs from which further selection will be done.\n        if find_outputs:\n            self._find_outputs()\n\n        elif (\n            self.parameters[\"dtDataDump\"] > 0\n            and self.parameters[\"CycleSkipDataDump\"] > 0\n        ):\n            mylog.info(\n                \"Simulation %s has both dtDataDump and CycleSkipDataDump set.\",\n                self.parameter_filename,\n            )\n            mylog.info(\n                \"    Unable to calculate datasets.  \"\n                \"Attempting to search in the current directory\"\n            )\n            self._find_outputs()\n\n        else:\n            # Get all time or cycle outputs.\n            if self.parameters[\"CycleSkipDataDump\"] > 0:\n                self._calculate_cycle_outputs()\n            else:\n                self._calculate_time_outputs()\n\n            # Calculate times for redshift outputs.\n            if self.cosmological_simulation:\n                for output in self.all_redshift_outputs:\n                    output[\"time\"] = self.cosmology.t_from_z(output[\"redshift\"])\n                self.all_redshift_outputs.sort(key=lambda obj: obj[\"time\"])\n\n            self.all_outputs = self.all_time_outputs + self.all_redshift_outputs\n            if self.parameters[\"CycleSkipDataDump\"] <= 0:\n                self.all_outputs.sort(key=lambda obj: obj[\"time\"].to_ndarray())\n\n    def _calculate_simulation_bounds(self):\n        \"\"\"\n        Figure out the starting and stopping time and redshift for the simulation.\n        \"\"\"\n\n        if \"StopCycle\" in self.parameters:\n            self.stop_cycle = self.parameters[\"StopCycle\"]\n\n        # Convert initial/final redshifts to times.\n        if self.cosmological_simulation:\n            self.initial_time = self.cosmology.t_from_z(self.initial_redshift)\n            self.initial_time.units.registry = self.unit_registry\n            self.final_time = self.cosmology.t_from_z(self.final_redshift)\n            self.final_time.units.registry = self.unit_registry\n\n        # If not a cosmology simulation, figure out the stopping criteria.\n        else:\n            if \"InitialTime\" in self.parameters:\n                self.initial_time = self.quan(\n                    self.parameters[\"InitialTime\"], \"code_time\"\n                )\n            else:\n                self.initial_time = self.quan(0.0, \"code_time\")\n\n            if \"StopTime\" in self.parameters:\n                self.final_time = self.quan(self.parameters[\"StopTime\"], \"code_time\")\n            else:\n                self.final_time = None\n            if not (\"StopTime\" in self.parameters or \"StopCycle\" in self.parameters):\n                raise NoStoppingCondition(self.parameter_filename)\n            if self.final_time is None:\n                mylog.warning(\n                    \"Simulation %s has no stop time set, stopping condition \"\n                    \"will be based only on cycles.\",\n                    self.parameter_filename,\n                )\n\n    def _set_parameter_defaults(self):\n        \"\"\"\n        Set some default parameters to avoid problems\n        if they are not in the parameter file.\n        \"\"\"\n\n        self.parameters[\"GlobalDir\"] = self.directory\n        self.parameters[\"DataDumpName\"] = \"data\"\n        self.parameters[\"DataDumpDir\"] = \"DD\"\n        self.parameters[\"RedshiftDumpName\"] = \"RedshiftOutput\"\n        self.parameters[\"RedshiftDumpDir\"] = \"RD\"\n        self.parameters[\"ComovingCoordinates\"] = 0\n        self.parameters[\"TopGridRank\"] = 3\n        self.parameters[\"DomainLeftEdge\"] = np.zeros(self.parameters[\"TopGridRank\"])\n        self.parameters[\"DomainRightEdge\"] = np.ones(self.parameters[\"TopGridRank\"])\n        self.parameters[\"RefineBy\"] = 2  # technically not the enzo default\n        self.parameters[\"StopCycle\"] = 100000\n        self.parameters[\"dtDataDump\"] = 0.0\n        self.parameters[\"CycleSkipDataDump\"] = 0.0\n        self.parameters[\"LengthUnits\"] = 1.0\n        self.parameters[\"TimeUnits\"] = 1.0\n        self.parameters[\"CosmologyOmegaRadiationNow\"] = 0.0\n\n    def _find_outputs(self):\n        \"\"\"\n        Search for directories matching the data dump keywords.\n        If found, get dataset times py opening the ds.\n        \"\"\"\n\n        # look for time outputs.\n        potential_time_outputs = glob.glob(\n            os.path.join(\n                self.parameters[\"GlobalDir\"], f\"{self.parameters['DataDumpDir']}*\"\n            )\n        )\n        self.all_time_outputs = self._check_for_outputs(potential_time_outputs)\n        self.all_time_outputs.sort(key=lambda obj: obj[\"time\"])\n\n        # look for redshift outputs.\n        potential_redshift_outputs = glob.glob(\n            os.path.join(\n                self.parameters[\"GlobalDir\"], f\"{self.parameters['RedshiftDumpDir']}*\"\n            )\n        )\n        self.all_redshift_outputs = self._check_for_outputs(potential_redshift_outputs)\n        self.all_redshift_outputs.sort(key=lambda obj: obj[\"time\"])\n\n        self.all_outputs = self.all_time_outputs + self.all_redshift_outputs\n        self.all_outputs.sort(key=lambda obj: obj[\"time\"])\n        only_on_root(mylog.info, \"Located %d total outputs.\", len(self.all_outputs))\n\n        # manually set final time and redshift with last output\n        if self.all_outputs:\n            self.final_time = self.all_outputs[-1][\"time\"]\n            if self.cosmological_simulation:\n                self.final_redshift = self.all_outputs[-1][\"redshift\"]\n\n    def _check_for_outputs(self, potential_outputs):\n        \"\"\"\n        Check a list of files to see if they are valid datasets.\n        \"\"\"\n\n        only_on_root(\n            mylog.info, \"Checking %d potential outputs.\", len(potential_outputs)\n        )\n\n        my_outputs = {}\n        llevel = mylog.level\n        # suppress logging as we load every dataset, unless set to debug\n        if llevel > 10 and llevel < 40:\n            mylog.setLevel(40)\n        for my_storage, output in parallel_objects(\n            potential_outputs, storage=my_outputs\n        ):\n            if self.parameters[\"DataDumpDir\"] in output:\n                dir_key = self.parameters[\"DataDumpDir\"]\n                output_key = self.parameters[\"DataDumpName\"]\n            else:\n                dir_key = self.parameters[\"RedshiftDumpDir\"]\n                output_key = self.parameters[\"RedshiftDumpName\"]\n            index = output[output.find(dir_key) + len(dir_key) :]\n            filename = os.path.join(\n                self.parameters[\"GlobalDir\"],\n                f\"{dir_key}{index}\",\n                f\"{output_key}{index}\",\n            )\n            try:\n                ds = load(filename)\n            except (FileNotFoundError, YTUnidentifiedDataType):\n                mylog.error(\"Failed to load %s\", filename)\n                continue\n            my_storage.result = {\n                \"filename\": filename,\n                \"time\": ds.current_time.in_units(\"s\"),\n            }\n            if ds.cosmological_simulation:\n                my_storage.result[\"redshift\"] = ds.current_redshift\n        mylog.setLevel(llevel)\n        my_outputs = [\n            my_output for my_output in my_outputs.values() if my_output is not None\n        ]\n\n        return my_outputs\n\n    def _write_cosmology_outputs(self, filename, outputs, start_index, decimals=3):\n        \"\"\"\n        Write cosmology output parameters for a cosmology splice.\n        \"\"\"\n\n        mylog.info(\"Writing redshift output list to %s.\", filename)\n        f = open(filename, \"w\")\n        for q, output in enumerate(outputs):\n            f.write(\n                (f\"CosmologyOutputRedshift[%d] = %.{decimals}f\\n\")\n                % ((q + start_index), output[\"redshift\"])\n            )\n        f.close()\n\n\nclass EnzoCosmology(Cosmology):\n    def __init__(\n        self,\n        hubble_constant,\n        omega_matter,\n        omega_lambda,\n        omega_radiation,\n        omega_curvature,\n        initial_redshift,\n        unit_registry=None,\n    ):\n        Cosmology.__init__(\n            self,\n            hubble_constant=hubble_constant,\n            omega_matter=omega_matter,\n            omega_lambda=omega_lambda,\n            omega_radiation=omega_radiation,\n            omega_curvature=omega_curvature,\n            unit_registry=unit_registry,\n        )\n        self.initial_redshift = initial_redshift\n        self.initial_time = self.t_from_z(self.initial_redshift)\n        # time units = 1 / sqrt(4 * pi * G rho_0 * (1 + z_i)**3),\n        # rho_0 = (3 * Omega_m * h**2) / (8 * pi * G)\n        self.time_unit = (\n            (\n                1.5\n                * self.omega_matter\n                * self.hubble_constant**2\n                * (1 + self.initial_redshift) ** 3\n            )\n            ** -0.5\n        ).in_units(\"s\")\n        self.time_unit.units.registry = self.unit_registry\n"
  },
  {
    "path": "yt/frontends/enzo/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/enzo/tests/test_outputs.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_array_equal, assert_equal\n\nfrom yt.frontends.enzo.api import EnzoDataset\nfrom yt.frontends.enzo.fields import NODAL_FLAGS\nfrom yt.testing import (\n    assert_allclose_units,\n    requires_file,\n    requires_module,\n    units_override_check,\n)\nfrom yt.utilities.answer_testing.framework import (\n    big_patch_amr,\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\nfrom yt.visualization.plot_window import SlicePlot\n\n_fields = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_magnitude\"),\n    (\"gas\", \"velocity_divergence\"),\n)\n\ntwo_sphere_test = \"ActiveParticleTwoSphere/DD0011/DD0011\"\nactive_particle_cosmology = \"ActiveParticleCosmology/DD0046/DD0046\"\necp = \"enzo_cosmology_plus/DD0046/DD0046\"\ng30 = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\nenzotiny = \"enzo_tiny_cosmology/DD0046/DD0046\"\ntoro1d = \"ToroShockTube/DD0001/data0001\"\nkh2d = \"EnzoKelvinHelmholtz/DD0011/DD0011\"\nmhdctot = \"MHDCTOrszagTang/DD0004/data0004\"\ndnz = \"DeeplyNestedZoom/DD0025/data0025\"\np3mini = \"PopIII_mini/DD0034/DD0034\"\n\n\ndef color_conservation(ds):\n    species_names = ds.field_info.species_names\n    dd = ds.all_data()\n    dens_yt = dd[\"gas\", \"density\"].copy()\n    # Enumerate our species here\n    for s in sorted(species_names):\n        if s == \"El\":\n            continue\n        dens_yt -= dd[f\"{s}_density\"]\n    dens_yt -= dd[\"enzo\", \"Metal_Density\"]\n    delta_yt = np.abs(dens_yt / dd[\"gas\", \"density\"])\n    # Now we compare color conservation to Enzo's color conservation\n    dd = ds.all_data()\n    dens_enzo = dd[\"enzo\", \"Density\"].copy()\n    for f in sorted(ds.field_list):\n        ff = f[1]\n        if not ff.endswith(\"_Density\"):\n            continue\n        start_strings = [\n            \"Electron_\",\n            \"SFR_\",\n            \"Forming_Stellar_\",\n            \"Dark_Matter\",\n            \"Star_Particle_\",\n        ]\n        if any(ff.startswith(ss) for ss in start_strings):\n            continue\n        dens_enzo -= dd[f]\n    delta_enzo = np.abs(dens_enzo / dd[\"enzo\", \"Density\"])\n    np.testing.assert_almost_equal(delta_yt, delta_enzo)\n\n\ndef check_color_conservation(ds):\n    species_names = ds.field_info.species_names\n    dd = ds.all_data()\n    dens_yt = dd[\"gas\", \"density\"].copy()\n    # Enumerate our species here\n    for s in sorted(species_names):\n        if s == \"El\":\n            continue\n        dens_yt -= dd[f\"{s}_density\"]\n    dens_yt -= dd[\"enzo\", \"Metal_Density\"]\n    delta_yt = np.abs(dens_yt / dd[\"gas\", \"density\"])\n\n    # Now we compare color conservation to Enzo's color conservation\n    dd = ds.all_data()\n    dens_enzo = dd[\"enzo\", \"Density\"].copy()\n    for f in sorted(ds.field_list):\n        ff = f[1]\n        if not ff.endswith(\"_Density\"):\n            continue\n        start_strings = [\n            \"Electron_\",\n            \"SFR_\",\n            \"Forming_Stellar_\",\n            \"Dark_Matter\",\n            \"Star_Particle_\",\n        ]\n        if any(ff.startswith(ss) for ss in start_strings):\n            continue\n        dens_enzo -= dd[f]\n    delta_enzo = np.abs(dens_enzo / dd[\"enzo\", \"Density\"])\n    return assert_almost_equal, delta_yt, delta_enzo\n\n\nm7 = \"DD0010/moving7_0010\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(m7)\ndef test_moving7():\n    ds = data_dir_load(m7)\n    assert_equal(str(ds), \"moving7_0010\")\n    for test in small_patch_amr(m7, _fields):\n        test_moving7.__name__ = test.description\n        yield test\n\n\n@requires_module(\"h5py\")\n@requires_ds(g30, big_data=True)\ndef test_galaxy0030():\n    ds = data_dir_load(g30)\n    yield check_color_conservation(ds)\n    assert_equal(str(ds), \"galaxy0030\")\n    for test in big_patch_amr(ds, _fields):\n        test_galaxy0030.__name__ = test.description\n        yield test\n    assert_equal(ds.particle_type_counts, {\"io\": 1124453})\n\n\n@requires_module(\"h5py\")\n@requires_ds(toro1d)\ndef test_toro1d():\n    ds = data_dir_load(toro1d)\n    assert_equal(str(ds), \"data0001\")\n    for test in small_patch_amr(ds, ds.field_list):\n        test_toro1d.__name__ = test.description\n        yield test\n\n\n@requires_module(\"h5py\")\n@requires_ds(kh2d)\ndef test_kh2d():\n    ds = data_dir_load(kh2d)\n    assert_equal(str(ds), \"DD0011\")\n    for test in small_patch_amr(ds, ds.field_list):\n        test_kh2d.__name__ = test.description\n        yield test\n\n\n@requires_module(\"h5py\")\n@requires_ds(ecp, big_data=True)\ndef test_ecp():\n    ds = data_dir_load(ecp)\n    # Now we test our species fields\n    yield check_color_conservation(ds)\n\n\n@requires_module(\"h5py\")\n@requires_file(enzotiny)\ndef test_units_override():\n    units_override_check(enzotiny)\n\n\n@requires_module(\"h5py\")\n@requires_ds(ecp, big_data=True)\ndef test_nuclei_density_fields():\n    ds = data_dir_load(ecp)\n    ad = ds.all_data()\n    assert_array_equal(\n        ad[\"gas\", \"H_nuclei_density\"],\n        (ad[\"gas\", \"H_p0_number_density\"] + ad[\"gas\", \"H_p1_number_density\"]),\n    )\n    assert_array_equal(\n        ad[\"gas\", \"He_nuclei_density\"],\n        (\n            ad[\"gas\", \"He_p0_number_density\"]\n            + ad[\"gas\", \"He_p1_number_density\"]\n            + ad[\"gas\", \"He_p2_number_density\"]\n        ),\n    )\n\n\n@requires_module(\"h5py\")\n@requires_file(enzotiny)\ndef test_EnzoDataset():\n    assert isinstance(data_dir_load(enzotiny), EnzoDataset)\n\n\n@requires_module(\"h5py\")\n@requires_file(two_sphere_test)\n@requires_file(active_particle_cosmology)\ndef test_active_particle_datasets():\n    two_sph = data_dir_load(two_sphere_test)\n    assert \"AccretingParticle\" in two_sph.particle_types_raw\n    assert \"io\" not in two_sph.particle_types_raw\n    assert \"all\" in two_sph.particle_types\n    assert \"nbody\" in two_sph.particle_types\n    assert_equal(len(two_sph.particle_unions), 2)\n    pfields = [\n        \"GridID\",\n        \"creation_time\",\n        \"dynamical_time\",\n        \"identifier\",\n        \"level\",\n        \"metallicity\",\n        \"particle_mass\",\n    ]\n    pfields += [f\"particle_position_{d}\" for d in \"xyz\"]\n    pfields += [f\"particle_velocity_{d}\" for d in \"xyz\"]\n\n    acc_part_fields = [(\"AccretingParticle\", pf) for pf in [\"AccretionRate\"] + pfields]\n\n    real_acc_part_fields = sorted(\n        f for f in two_sph.field_list if f[0] == \"AccretingParticle\"\n    )\n    assert_equal(acc_part_fields, real_acc_part_fields)\n\n    apcos = data_dir_load(active_particle_cosmology)\n    assert_equal([\"CenOstriker\", \"DarkMatter\"], apcos.particle_types_raw)\n    assert \"all\" in apcos.particle_unions\n    assert \"nbody\" in apcos.particle_unions\n\n    apcos_fields = [(\"CenOstriker\", pf) for pf in pfields]\n\n    real_apcos_fields = sorted(f for f in apcos.field_list if f[0] == \"CenOstriker\")\n\n    assert_equal(apcos_fields, real_apcos_fields)\n\n    assert_equal(\n        apcos.particle_type_counts, {\"CenOstriker\": 899755, \"DarkMatter\": 32768}\n    )\n\n\n@requires_module(\"h5py\")\n@requires_file(mhdctot)\ndef test_face_centered_mhdct_fields():\n    ds = data_dir_load(mhdctot)\n\n    ad = ds.all_data()\n    grid = ds.index.grids[0]\n\n    for field, flag in NODAL_FLAGS.items():\n        dims = ds.domain_dimensions\n        assert_equal(ad[field].shape, (dims.prod(), 2 * sum(flag)))\n        assert_equal(grid[field].shape, tuple(dims) + (2 * sum(flag),))\n\n    # Average of face-centered fields should be the same as cell-centered field\n    assert (ad[\"enzo\", \"BxF\"].sum(axis=-1) / 2 == ad[\"enzo\", \"Bx\"]).all()\n    assert (ad[\"enzo\", \"ByF\"].sum(axis=-1) / 2 == ad[\"enzo\", \"By\"]).all()\n    assert (ad[\"enzo\", \"BzF\"].sum(axis=-1) / 2 == ad[\"enzo\", \"Bz\"]).all()\n\n\n@requires_module(\"h5py\")\n@requires_file(dnz)\ndef test_deeply_nested_zoom():\n    ds = data_dir_load(dnz)\n\n    # carefully chosen to just barely miss a grid in the middle of the image\n    center = [0.4915073260199302, 0.5052605316800006, 0.4905805557500548]\n\n    plot = SlicePlot(ds, \"z\", \"density\", width=(0.001, \"pc\"), center=center)\n\n    image = plot.frb[\"gas\", \"density\"]\n\n    assert (image > 0).all()\n\n    v, c = ds.find_max((\"gas\", \"density\"))\n\n    assert_allclose_units(v, ds.quan(0.005878286377124154, \"g/cm**3\"))\n\n    c_actual = [0.49150732540021, 0.505260532936791, 0.49058055816398]\n    c_actual = ds.arr(c_actual, \"code_length\")\n    assert_allclose_units(c, c_actual)\n\n    assert_equal(max(g[\"gas\", \"density\"].max() for g in ds.index.grids), v)\n\n\n@requires_module(\"h5py\")\n@requires_file(kh2d)\ndef test_2d_grid_shape():\n    # see issue #1601\n    # we want to make sure that accessing data on a grid object\n    # returns a 3D array with a dummy dimension.\n    ds = data_dir_load(kh2d)\n    g = ds.index.grids[1]\n    assert g[\"gas\", \"density\"].shape == (128, 100, 1)\n\n\n@requires_module(\"h5py\")\n@requires_file(p3mini)\ndef test_nonzero_omega_radiation():\n    \"\"\"\n    Test support for non-zero omega_radiation cosmologies.\n    \"\"\"\n    ds = data_dir_load(p3mini)\n\n    assert_equal(ds.omega_radiation, ds.cosmology.omega_radiation)\n\n    tratio = ds.current_time / ds.cosmology.t_from_z(ds.current_redshift)\n    assert_almost_equal(\n        tratio,\n        1,\n        4,\n        err_msg=\"Simulation time not consistent with cosmology calculator.\",\n    )\n"
  },
  {
    "path": "yt/frontends/enzo_e/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/enzo_e/api.py",
    "content": "from . import tests\nfrom .data_structures import EnzoEDataset, EnzoEGrid, EnzoEHierarchy\nfrom .fields import EnzoEFieldInfo\nfrom .io import EnzoEIOHandler\n\nadd_enzoe_field = EnzoEFieldInfo.add_field\n"
  },
  {
    "path": "yt/frontends/enzo_e/data_structures.py",
    "content": "import os\nfrom functools import cached_property\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.fields.field_info_container import NullFunc\nfrom yt.frontends.enzo.misc import cosmology_get_units\nfrom yt.frontends.enzo_e.fields import EnzoEFieldInfo\nfrom yt.frontends.enzo_e.misc import (\n    get_block_info,\n    get_child_index,\n    get_listed_subparam,\n    get_root_block_id,\n    get_root_blocks,\n    is_parent,\n    nested_dict_get,\n)\nfrom yt.funcs import get_pbar, setdefaultattr\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py, _libconf as libconf\n\n\nclass EnzoEGrid(AMRGridPatch):\n    \"\"\"\n    Class representing a single EnzoE Grid instance.\n    \"\"\"\n\n    _id_offset = 0\n    _refine_by = 2\n\n    def __init__(self, id, index, block_name, filename=None):\n        \"\"\"\n        Returns an instance of EnzoEGrid with *id*, associated with\n        *filename* and *index*.\n        \"\"\"\n        # All of the field parameters will be passed to us as needed.\n        AMRGridPatch.__init__(self, id, filename=filename, index=index)\n        self.block_name = block_name\n        self._children_ids = None\n        self._parent_id = -1\n        self.Level = -1\n\n    def __repr__(self):\n        return f\"EnzoEGrid_{self.id:04}\"\n\n    def _prepare_grid(self):\n        \"\"\"Copies all the appropriate attributes from the index.\"\"\"\n        h = self.index  # cache it\n        my_ind = self.id - self._id_offset\n        self.ActiveDimensions = h.grid_dimensions[my_ind]\n        self.LeftEdge = h.grid_left_edge[my_ind]\n        self.RightEdge = h.grid_right_edge[my_ind]\n\n    def get_parent_id(self, desc_block_name):\n        if self.block_name == desc_block_name:\n            raise RuntimeError(\"Child and parent are the same!\")\n        dim = self.ds.dimensionality\n        d_block = desc_block_name[1:].replace(\":\", \"\")\n        parent = self\n\n        while True:\n            a_block = parent.block_name[1:].replace(\":\", \"\")\n            gengap = (len(d_block) - len(a_block)) / dim\n            if gengap <= 1:\n                return parent.id\n            cid = get_child_index(a_block, d_block)\n            parent = self.index.grids[parent._children_ids[cid]]\n\n    def add_child(self, child):\n        if self._children_ids is None:\n            self._children_ids = -1 * np.ones(\n                self._refine_by**self.ds.dimensionality, dtype=np.int64\n            )\n\n        a_block = self.block_name[1:].replace(\":\", \"\")\n        d_block = child.block_name[1:].replace(\":\", \"\")\n        cid = get_child_index(a_block, d_block)\n        self._children_ids[cid] = child.id\n\n    @cached_property\n    def particle_count(self):\n        with h5py.File(self.filename, mode=\"r\") as f:\n            fnstr = \"{}/{}\".format(\n                self.block_name,\n                self.ds.index.io._sep.join([\"particle\", \"%s\", \"%s\"]),\n            )\n            return {\n                ptype: f.get(fnstr % (ptype, pfield)).size\n                for ptype, pfield in self.ds.index.io.sample_pfields.items()\n            }\n\n    @cached_property\n    def total_particles(self) -> int:\n        return sum(self.particle_count.values())\n\n    @property\n    def Parent(self):\n        if self._parent_id == -1:\n            return None\n        return self.index.grids[self._parent_id]\n\n    @property\n    def Children(self):\n        if self._children_ids is None:\n            return []\n        return [self.index.grids[cid] for cid in self._children_ids]\n\n\nclass EnzoEHierarchy(GridIndex):\n    _strip_path = False\n    grid = EnzoEGrid\n    _preload_implemented = True\n\n    def __init__(self, ds, dataset_type):\n        self.dataset_type = dataset_type\n        self.directory = os.path.dirname(ds.parameter_filename)\n        self.index_filename = ds.parameter_filename\n        if os.path.getsize(self.index_filename) == 0:\n            raise OSError(-1, \"File empty\", self.index_filename)\n\n        GridIndex.__init__(self, ds, dataset_type)\n        self.dataset.dataset_type = self.dataset_type\n\n    def _count_grids(self):\n        fblock_size = 32768\n        f = open(self.ds.parameter_filename)\n        f.seek(0, 2)\n        file_size = f.tell()\n        nblocks = np.ceil(float(file_size) / fblock_size).astype(np.int64)\n        f.seek(0)\n        offset = f.tell()\n        ngrids = 0\n        for _ in range(nblocks):\n            my_block = min(fblock_size, file_size - offset)\n            buff = f.read(my_block)\n            ngrids += buff.count(\"\\n\")\n            offset += my_block\n        f.close()\n        self.num_grids = ngrids\n        self.dataset_type = \"enzo_e\"\n\n    def _parse_index(self):\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n\n        c = 1\n        pbar = get_pbar(\"Parsing Hierarchy\", self.num_grids)\n        f = open(self.ds.parameter_filename)\n        fblock_size = 32768\n        f.seek(0, 2)\n        file_size = f.tell()\n        nblocks = np.ceil(float(file_size) / fblock_size).astype(np.int64)\n        f.seek(0)\n        offset = f.tell()\n        lstr = \"\"\n        # place child blocks after the root blocks\n        rbdim = self.ds.root_block_dimensions\n        nroot_blocks = rbdim.prod()\n        child_id = nroot_blocks\n\n        last_pid = None\n        for _ib in range(nblocks):\n            fblock = min(fblock_size, file_size - offset)\n            buff = lstr + f.read(fblock)\n            bnl = 0\n            for _inl in range(buff.count(\"\\n\")):\n                nnl = buff.find(\"\\n\", bnl)\n                line = buff[bnl:nnl]\n                block_name, block_file = line.split()\n\n                # Handling of the B, B_, and B__ blocks is consistent with\n                # other unrefined blocks\n                level, left, right = get_block_info(block_name)\n                rbindex = get_root_block_id(block_name)\n                rbid = (\n                    rbindex[0] * rbdim[1:].prod()\n                    + rbindex[1] * rbdim[2:].prod()\n                    + rbindex[2]\n                )\n\n                # There are also blocks at lower level than the\n                # real root blocks. These can be ignored.\n                if level == 0:\n                    check_root = get_root_blocks(block_name).prod()\n                    if check_root < nroot_blocks:\n                        level = -1\n\n                if level == -1:\n                    grid_id = child_id\n                    parent_id = -1\n                    child_id += 1\n                elif level == 0:\n                    grid_id = rbid\n                    parent_id = -1\n                else:\n                    grid_id = child_id\n                    # Try the last parent_id first\n                    if last_pid is not None and is_parent(\n                        self.grids[last_pid].block_name, block_name\n                    ):\n                        parent_id = last_pid\n                    else:\n                        parent_id = self.grids[rbid].get_parent_id(block_name)\n                    last_pid = parent_id\n                    child_id += 1\n\n                my_grid = self.grid(\n                    grid_id,\n                    self,\n                    block_name,\n                    filename=os.path.join(self.directory, block_file),\n                )\n                my_grid.Level = level\n                my_grid._parent_id = parent_id\n\n                self.grids[grid_id] = my_grid\n                self.grid_levels[grid_id] = level\n                self.grid_left_edge[grid_id] = left\n                self.grid_right_edge[grid_id] = right\n                self.grid_dimensions[grid_id] = self.ds.active_grid_dimensions\n\n                if level > 0:\n                    self.grids[parent_id].add_child(my_grid)\n\n                bnl = nnl + 1\n                pbar.update(c)\n                c += 1\n            lstr = buff[bnl:]\n            offset += fblock\n\n        f.close()\n        pbar.finish()\n\n        slope = self.ds.domain_width / self.ds.arr(np.ones(3), \"code_length\")\n        self.grid_left_edge = self.grid_left_edge * slope + self.ds.domain_left_edge\n        self.grid_right_edge = self.grid_right_edge * slope + self.ds.domain_left_edge\n\n    def _populate_grid_objects(self):\n        for g in self.grids:\n            g._prepare_grid()\n            g._setup_dx()\n        self.max_level = self.grid_levels.max()\n\n    def _setup_derived_fields(self):\n        super()._setup_derived_fields()\n        for fname, field in self.ds.field_info.items():\n            if not field.particle_type:\n                continue\n            if isinstance(fname, tuple):\n                continue\n            if field._function is NullFunc:\n                continue\n\n    def _get_particle_type_counts(self):\n        return {\n            ptype: sum(g.particle_count[ptype] for g in self.grids)\n            for ptype in self.ds.particle_types_raw\n        }\n\n    def _detect_output_fields(self):\n        self.field_list = []\n        # Do this only on the root processor to save disk work.\n        if self.comm.rank in (0, None):\n            # Just check the first grid.\n            grid = self.grids[0]\n            field_list, ptypes = self.io._read_field_names(grid)\n            mylog.debug(\"Grid %s has: %s\", grid.id, field_list)\n            sample_pfields = self.io.sample_pfields\n        else:\n            field_list = None\n            ptypes = None\n            sample_pfields = None\n        self.field_list = list(self.comm.mpi_bcast(field_list))\n        self.dataset.particle_types = list(self.comm.mpi_bcast(ptypes))\n        self.dataset.particle_types_raw = self.dataset.particle_types[:]\n        self.io.sample_pfields = self.comm.mpi_bcast(sample_pfields)\n\n\nclass EnzoEDataset(Dataset):\n    \"\"\"\n    Enzo-E-specific output, set at a fixed time.\n    \"\"\"\n\n    _load_requirements = [\"h5py\", \"libconf\"]\n    refine_by = 2\n    _index_class = EnzoEHierarchy\n    _field_info_class = EnzoEFieldInfo\n    _suffix = \".block_list\"\n    particle_types: tuple[str, ...] = ()\n    particle_types_raw = None\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=None,\n        parameter_override=None,\n        conversion_override=None,\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        \"\"\"\n        This class is a stripped down class that simply reads and parses\n        *filename* without looking at the index.  *dataset_type* gets passed\n        to the index to pre-determine the style of data-output.  However,\n        it is not strictly necessary.  Optionally you may specify a\n        *parameter_override* dictionary that will override anything in the\n        parameter file and a *conversion_override* dictionary that consists\n        of {fieldname : conversion_to_cgs} that will override the #DataCGS.\n        \"\"\"\n        self.fluid_types += (\"enzoe\",)\n        if parameter_override is None:\n            parameter_override = {}\n        self._parameter_override = parameter_override\n        if conversion_override is None:\n            conversion_override = {}\n        self._conversion_override = conversion_override\n        self.storage_filename = storage_filename\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Parses the parameter file and establishes the various\n        dictionaries.\n        \"\"\"\n\n        f = open(self.parameter_filename)\n        # get dimension from first block name\n        b0, fn0 = f.readline().strip().split()\n        level0, left0, right0 = get_block_info(b0, min_dim=0)\n        root_blocks = get_root_blocks(b0)\n        f.close()\n        self.dimensionality = left0.size\n        self._periodicity = tuple(np.ones(self.dimensionality, dtype=bool))\n\n        lcfn = self.parameter_filename[: -len(self._suffix)] + \".libconfig\"\n        if os.path.exists(lcfn):\n            with open(lcfn) as lf:\n                self.parameters = libconf.load(lf)\n\n            # Enzo-E ignores all cosmology parameters if \"cosmology\" is not in\n            # the Physics:list parameter\n            physics_list = nested_dict_get(\n                self.parameters, (\"Physics\", \"list\"), default=[]\n            )\n            if \"cosmology\" in physics_list:\n                self.cosmological_simulation = 1\n                co_pars = [\n                    \"hubble_constant_now\",\n                    \"omega_matter_now\",\n                    \"omega_lambda_now\",\n                    \"comoving_box_size\",\n                    \"initial_redshift\",\n                ]\n                co_dict = {\n                    attr: nested_dict_get(\n                        self.parameters, (\"Physics\", \"cosmology\", attr)\n                    )\n                    for attr in co_pars\n                }\n                for attr in [\"hubble_constant\", \"omega_matter\", \"omega_lambda\"]:\n                    setattr(self, attr, co_dict[f\"{attr}_now\"])\n\n                # Current redshift is not stored, so it's not possible\n                # to set all cosmological units yet.\n                # Get the time units and use that to figure out redshift.\n                k = cosmology_get_units(\n                    self.hubble_constant,\n                    self.omega_matter,\n                    co_dict[\"comoving_box_size\"],\n                    co_dict[\"initial_redshift\"],\n                    0,\n                )\n                setdefaultattr(self, \"time_unit\", self.quan(k[\"utim\"], \"s\"))\n                co = Cosmology(\n                    hubble_constant=self.hubble_constant,\n                    omega_matter=self.omega_matter,\n                    omega_lambda=self.omega_lambda,\n                )\n            else:\n                self.cosmological_simulation = 0\n        else:\n            self.cosmological_simulation = 0\n\n        fh = h5py.File(os.path.join(self.directory, fn0), \"r\")\n        self.domain_left_edge = fh.attrs[\"lower\"]\n        self.domain_right_edge = fh.attrs[\"upper\"]\n        if \"version\" in fh.attrs:\n            version = fh.attrs.get(\"version\").tobytes().decode(\"ascii\")\n        else:\n            version = None  # earliest recorded version is '0.9.0'\n        self.parameters[\"version\"] = version\n\n        # all blocks are the same size\n        ablock = fh[list(fh.keys())[0]]\n        self.current_time = ablock.attrs[\"time\"][0]\n        self.parameters[\"current_cycle\"] = ablock.attrs[\"cycle\"][0]\n        gsi = ablock.attrs[\"enzo_GridStartIndex\"]\n        gei = ablock.attrs[\"enzo_GridEndIndex\"]\n        assert len(gsi) == len(gei) == 3  # sanity check\n        # Enzo-E technically allows each axis to have different ghost zone\n        # depths (this feature is not really used in practice)\n        self.ghost_zones = gsi\n        assert (self.ghost_zones[self.dimensionality :] == 0).all()  # sanity check\n        self.root_block_dimensions = root_blocks\n        self.active_grid_dimensions = gei - gsi + 1\n        self.grid_dimensions = ablock.attrs[\"enzo_GridDimension\"]\n        self.domain_dimensions = root_blocks * self.active_grid_dimensions\n        fh.close()\n\n        if self.cosmological_simulation:\n            self.current_redshift = co.z_from_t(self.current_time * self.time_unit)\n\n        self._periodicity += (False,) * (3 - self.dimensionality)\n        self._parse_fluid_prop_params()\n\n    def _parse_fluid_prop_params(self):\n        \"\"\"\n        Parse the fluid properties.\n        \"\"\"\n\n        fp_params = nested_dict_get(\n            self.parameters, (\"Physics\", \"fluid_props\"), default=None\n        )\n\n        if fp_params is not None:\n            # in newer versions of enzo-e, this data is specified in a\n            # centralized parameter group called Physics:fluid_props\n            # -  for internal reasons related to backwards compatibility,\n            #    treatment of this physics-group is somewhat special (compared\n            #    to the cosmology group). The parameters in this group are\n            #    honored even if Physics:list does not include \"fluid_props\"\n            self.gamma = nested_dict_get(fp_params, (\"eos\", \"gamma\"))\n            de_type = nested_dict_get(\n                fp_params, (\"dual_energy\", \"type\"), default=\"disabled\"\n            )\n            uses_de = de_type != \"disabled\"\n        else:\n            # in older versions, these parameters were more scattered\n            self.gamma = nested_dict_get(self.parameters, (\"Field\", \"gamma\"))\n\n            uses_de = False\n            for method in (\"ppm\", \"mhd_vlct\"):\n                subparams = get_listed_subparam(\n                    self.parameters, \"Method\", method, default=None\n                )\n                if subparams is not None:\n                    uses_de = subparams.get(\"dual_energy\", False)\n        self.parameters[\"uses_dual_energy\"] = uses_de\n\n    def _set_code_unit_attributes(self):\n        if self.cosmological_simulation:\n            box_size = self.parameters[\"Physics\"][\"cosmology\"][\"comoving_box_size\"]\n            k = cosmology_get_units(\n                self.hubble_constant,\n                self.omega_matter,\n                box_size,\n                self.parameters[\"Physics\"][\"cosmology\"][\"initial_redshift\"],\n                self.current_redshift,\n            )\n            # Now some CGS values\n            setdefaultattr(self, \"length_unit\", self.quan(box_size, \"Mpccm/h\"))\n            setdefaultattr(\n                self,\n                \"mass_unit\",\n                self.quan(k[\"urho\"], \"g/cm**3\") * (self.length_unit.in_cgs()) ** 3,\n            )\n            setdefaultattr(self, \"velocity_unit\", self.quan(k[\"uvel\"], \"cm/s\"))\n        else:\n            p = self.parameters\n            for d, u in [(\"length\", \"cm\"), (\"time\", \"s\")]:\n                val = nested_dict_get(p, (\"Units\", d), default=1)\n                setdefaultattr(self, f\"{d}_unit\", self.quan(val, u))\n            mass = nested_dict_get(p, (\"Units\", \"mass\"))\n            if mass is None:\n                density = nested_dict_get(p, (\"Units\", \"density\"))\n                if density is not None:\n                    mass = density * self.length_unit**3\n                else:\n                    mass = 1\n            setdefaultattr(self, \"mass_unit\", self.quan(mass, \"g\"))\n            setdefaultattr(self, \"velocity_unit\", self.length_unit / self.time_unit)\n\n        magnetic_unit = np.sqrt(\n            4 * np.pi * self.mass_unit / (self.time_unit**2 * self.length_unit)\n        )\n        magnetic_unit = np.float64(magnetic_unit.in_cgs())\n        setdefaultattr(self, \"magnetic_unit\", self.quan(magnetic_unit, \"gauss\"))\n\n    def __str__(self):\n        return self.basename[: -len(self._suffix)]\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        ddir = os.path.dirname(filename)\n        if not filename.endswith(cls._suffix):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            with open(filename) as f:\n                block, block_file = f.readline().strip().split()\n                get_block_info(block)\n                if not os.path.exists(os.path.join(ddir, block_file)):\n                    return False\n        except Exception:\n            return False\n        return True\n"
  },
  {
    "path": "yt/frontends/enzo_e/definitions.py",
    "content": ""
  },
  {
    "path": "yt/frontends/enzo_e/fields.py",
    "content": "import numpy as np\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.fields.particle_fields import add_union_field\nfrom yt.frontends.enzo_e.misc import (\n    get_listed_subparam,\n    get_particle_mass_correction,\n    nested_dict_get,\n)\n\nrho_units = \"code_mass / code_length**3\"\nvel_units = \"code_velocity\"\nacc_units = \"code_velocity / code_time\"\nenergy_units = \"code_velocity**2\"\nb_units = \"code_magnetic\"\n\nNODAL_FLAGS = {\n    \"bfieldi_x\": [1, 0, 0],\n    \"bfieldi_y\": [0, 1, 0],\n    \"bfieldi_z\": [0, 0, 1],\n}\n\n\nclass EnzoEFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"velocity_x\", (vel_units, [\"velocity_x\"], None)),\n        (\"velocity_y\", (vel_units, [\"velocity_y\"], None)),\n        (\"velocity_z\", (vel_units, [\"velocity_z\"], None)),\n        (\"acceleration_x\", (acc_units, [\"acceleration_x\"], None)),\n        (\"acceleration_y\", (acc_units, [\"acceleration_y\"], None)),\n        (\"acceleration_z\", (acc_units, [\"acceleration_z\"], None)),\n        (\"density\", (rho_units, [\"density\"], None)),\n        (\"density_total\", (rho_units, [\"total_density\"], None)),\n        (\"total_energy\", (energy_units, [\"specific_total_energy\"], None)),\n        (\"internal_energy\", (energy_units, [\"specific_thermal_energy\"], None)),\n        (\"bfield_x\", (b_units, [], None)),\n        (\"bfield_y\", (b_units, [], None)),\n        (\"bfield_z\", (b_units, [], None)),\n        (\"bfieldi_x\", (b_units, [], None)),\n        (\"bfieldi_y\", (b_units, [], None)),\n        (\"bfieldi_z\", (b_units, [], None)),\n    )\n\n    known_particle_fields: KnownFieldsT = (\n        (\"x\", (\"code_length\", [\"particle_position_x\"], None)),\n        (\"y\", (\"code_length\", [\"particle_position_y\"], None)),\n        (\"z\", (\"code_length\", [\"particle_position_z\"], None)),\n        (\"vx\", (vel_units, [\"particle_velocity_x\"], None)),\n        (\"vy\", (vel_units, [\"particle_velocity_y\"], None)),\n        (\"vz\", (vel_units, [\"particle_velocity_z\"], None)),\n        (\"ax\", (acc_units, [\"particle_acceleration_x\"], None)),\n        (\"ay\", (acc_units, [\"particle_acceleration_y\"], None)),\n        (\"az\", (acc_units, [\"particle_acceleration_z\"], None)),\n        (\"mass\", (\"code_mass\", [\"particle_mass\"], None)),\n    )\n\n    def __init__(self, ds, field_list, slice_info=None):\n        super().__init__(ds, field_list, slice_info=slice_info)\n\n        # setup nodal flag information\n        for field, arr in NODAL_FLAGS.items():\n            if (\"enzoe\", field) in self:\n                finfo = self[\"enzoe\", field]\n                finfo.nodal_flag = np.array(arr)\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        self.setup_energy_field()\n        setup_magnetic_field_aliases(self, \"enzoe\", [f\"bfield_{ax}\" for ax in \"xyz\"])\n\n    def setup_energy_field(self):\n        unit_system = self.ds.unit_system\n        # check if we have a field for internal energy\n        has_ie_field = (\"enzoe\", \"internal_energy\") in self.field_list\n        # check if we need to account for magnetic energy\n        vlct_params = get_listed_subparam(self.ds.parameters, \"Method\", \"mhd_vlct\", {})\n        has_magnetic = \"no_bfield\" != vlct_params.get(\"mhd_choice\", \"no_bfield\")\n\n        # define the (\"gas\", \"specific_thermal_energy\") field\n        # - this is already done for us if the simulation used the dual-energy\n        #   formalism AND (\"enzoe\", \"internal_energy\") was saved to disk\n        if not (self.ds.parameters[\"uses_dual_energy\"] and has_ie_field):\n\n            def _tot_minus_kin(data):\n                return (\n                    data[\"enzoe\", \"total_energy\"]\n                    - 0.5 * data[\"gas\", \"velocity_magnitude\"] ** 2.0\n                )\n\n            if not has_magnetic:\n                # thermal energy = total energy - kinetic energy\n                self.add_field(\n                    (\"gas\", \"specific_thermal_energy\"),\n                    sampling_type=\"cell\",\n                    function=_tot_minus_kin,\n                    units=unit_system[\"specific_energy\"],\n                )\n            else:\n                # thermal energy = total energy - kinetic energy - magnetic energy\n                def _sub_b(data):\n                    return (\n                        _tot_minus_kin(data)\n                        - data[\"gas\", \"magnetic_energy_density\"]\n                        / data[\"gas\", \"density\"]\n                    )\n\n                self.add_field(\n                    (\"gas\", \"specific_thermal_energy\"),\n                    sampling_type=\"cell\",\n                    function=_sub_b,\n                    units=unit_system[\"specific_energy\"],\n                )\n\n    def setup_particle_fields(self, ptype, ftype=\"gas\", num_neighbors=64):\n        super().setup_particle_fields(ptype, ftype=ftype, num_neighbors=num_neighbors)\n        self.setup_particle_mass_field(ptype)\n\n    def setup_particle_mass_field(self, ptype):\n        fname = \"particle_mass\"\n        if ptype in self.ds.particle_unions:\n            add_union_field(self, ptype, fname, \"code_mass\")\n            return\n\n        pdict = self.ds.parameters.get(\"Particle\", None)\n        if pdict is None:\n            return\n\n        constants = nested_dict_get(pdict, (ptype, \"constants\"), default=())\n        if not constants:\n            return\n\n        # constants should be a tuple consisting of multiple tuples of (name, type, value).\n        # When there is only one entry, the enclosing tuple gets stripped, so we put it back.\n        if not isinstance(constants[0], tuple):\n            constants = (constants,)\n        names = [c[0] for c in constants]\n\n        if \"mass\" not in names:\n            return\n\n        val = constants[names.index(\"mass\")][2] * self.ds.mass_unit\n        if not self.ds.index.io._particle_mass_is_mass:\n            val = val * get_particle_mass_correction(self.ds)\n\n        def _pmass(data):\n            return val * data[ptype, \"particle_ones\"]\n\n        self.add_field(\n            (ptype, fname),\n            function=_pmass,\n            units=\"code_mass\",\n            sampling_type=\"particle\",\n        )\n"
  },
  {
    "path": "yt/frontends/enzo_e/io.py",
    "content": "import numpy as np\n\nfrom yt.frontends.enzo_e.misc import get_particle_mass_correction, nested_dict_get\nfrom yt.utilities.exceptions import YTException\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\nclass EnzoEIOHandler(BaseIOHandler):\n    _dataset_type = \"enzo_e\"\n    _base = slice(None)\n    _field_dtype = \"float64\"\n    _sep = \"_\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        # precompute the indexing specifying each field's active zone\n        # -> this assumes that each field in Enzo-E shares the same number of\n        #    ghost-zones. Technically, Enzo-E allows each field to have a\n        #    different number of ghost zones (but this feature isn't currently\n        #    used & it currently doesn't record this information)\n        # -> our usage of a negative stop value ensures compatability with\n        #    both cell-centered and face-centered fields\n        self._activezone_idx = tuple(\n            slice(num_zones, -num_zones) if num_zones > 0 else slice(None)\n            for num_zones in self.ds.ghost_zones[: self.ds.dimensionality]\n        )\n\n        # Determine if particle masses are actually masses or densities.\n        if self.ds.parameters[\"version\"] is not None:\n            # they're masses for enzo-e versions that record a version string\n            mass_flag = True\n        else:\n            # in earlier versions: query the existence of the \"mass_is_mass\"\n            # particle parameter\n            mass_flag = nested_dict_get(\n                self.ds.parameters, (\"Particle\", \"mass_is_mass\"), default=None\n            )\n        # the historic approach for initializing the value of \"mass_is_mass\"\n        # was unsound (and could yield a random value). Thus we should only\n        # check for the parameter's existence and not its value\n        self._particle_mass_is_mass = mass_flag is not None\n\n    def _read_field_names(self, grid):\n        if grid.filename is None:\n            return []\n        f = h5py.File(grid.filename, mode=\"r\")\n        try:\n            group = f[grid.block_name]\n        except KeyError as e:\n            raise YTException(\n                message=f\"Grid {grid.block_name} is missing from data file {grid.filename}.\",\n                ds=self.ds,\n            ) from e\n        fields = []\n        ptypes = set()\n        dtypes = set()\n        # keep one field for each particle type so we can count later\n        sample_pfields = {}\n        for name, v in group.items():\n            if not hasattr(v, \"shape\") or v.dtype == \"O\":\n                continue\n            # mesh fields are \"field <name>\"\n            if name.startswith(\"field\"):\n                _, fname = name.split(self._sep, 1)\n                fields.append((\"enzoe\", fname))\n                dtypes.add(v.dtype)\n            # particle fields are \"particle <type> <name>\"\n            else:\n                _, ftype, fname = name.split(self._sep, 2)\n                fields.append((ftype, fname))\n                ptypes.add(ftype)\n                dtypes.add(v.dtype)\n                if ftype not in sample_pfields:\n                    sample_pfields[ftype] = fname\n        self.sample_pfields = sample_pfields\n\n        if len(dtypes) == 1:\n            # Now, if everything we saw was the same dtype, we can go ahead and\n            # set it here.  We do this because it is a HUGE savings for 32 bit\n            # floats, since our numpy copying/casting is way faster than\n            # h5py's, for some reason I don't understand.  This does *not* need\n            # to be correct -- it will get fixed later -- it just needs to be\n            # okay for now.\n            self._field_dtype = list(dtypes)[0]\n        f.close()\n        return fields, ptypes\n\n    def _read_particle_coords(self, chunks, ptf):\n        yield from (\n            (ptype, xyz, 0.0)\n            for ptype, xyz in self._read_particle_fields(chunks, ptf, None)\n        )\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        chunks = list(chunks)\n        dc = self.ds.domain_center.in_units(\"code_length\").d\n        for chunk in chunks:  # These should be organized by grid filename\n            f = None\n            for g in chunk.objs:\n                if g.filename is None:\n                    continue\n                if f is None:\n                    f = h5py.File(g.filename, mode=\"r\")\n                if g.particle_count is None:\n                    fnstr = \"{}/{}\".format(\n                        g.block_name,\n                        self._sep.join([\"particle\", \"%s\", \"%s\"]),\n                    )\n                    g.particle_count = {\n                        ptype: f.get(fnstr % (ptype, self.sample_pfields[ptype])).size\n                        for ptype in self.sample_pfields\n                    }\n                    g.total_particles = sum(g.particle_count.values())\n                if g.total_particles == 0:\n                    continue\n                group = f.get(g.block_name)\n                for ptype, field_list in sorted(ptf.items()):\n                    pn = self._sep.join([\"particle\", ptype, \"%s\"])\n                    if g.particle_count[ptype] == 0:\n                        continue\n                    coords = tuple(\n                        np.asarray(group.get(pn % ax)[()], dtype=\"=f8\")\n                        for ax in \"xyz\"[: self.ds.dimensionality]\n                    )\n                    for i in range(self.ds.dimensionality, 3):\n                        coords += (\n                            dc[i] * np.ones(g.particle_count[ptype], dtype=\"f8\"),\n                        )\n                    if selector is None:\n                        # This only ever happens if the call is made from\n                        # _read_particle_coords.\n                        yield ptype, coords\n                        continue\n                    coords += (0.0,)\n                    mask = selector.select_points(*coords)\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        data = np.asarray(group.get(pn % field)[()], \"=f8\")\n                        if field == \"mass\" and not self._particle_mass_is_mass:\n                            data[mask] *= get_particle_mass_correction(self.ds)\n                        yield (ptype, field), data[mask]\n            if f:\n                f.close()\n\n    def io_iter(self, chunks, fields):\n        for chunk in chunks:\n            fid = None\n            filename = -1\n            for obj in chunk.objs:\n                if obj.filename is None:\n                    continue\n                if obj.filename != filename:\n                    # Note one really important thing here: even if we do\n                    # implement LRU caching in the _read_obj_field function,\n                    # we'll still be doing file opening and whatnot.  This is a\n                    # problem, but one we can return to.\n                    if fid is not None:\n                        fid.close()\n                    fid = h5py.h5f.open(\n                        obj.filename.encode(\"latin-1\"), h5py.h5f.ACC_RDONLY\n                    )\n                    filename = obj.filename\n                for field in fields:\n                    grid_dim = self.ds.grid_dimensions\n                    nodal_flag = self.ds.field_info[field].nodal_flag\n                    dims = (\n                        grid_dim[: self.ds.dimensionality][::-1]\n                        + nodal_flag[: self.ds.dimensionality][::-1]\n                    )\n                    data = np.empty(dims, dtype=self._field_dtype)\n                    yield field, obj, self._read_obj_field(obj, field, (fid, data))\n        if fid is not None:\n            fid.close()\n\n    def _read_obj_field(self, obj, field, fid_data):\n        if fid_data is None:\n            fid_data = (None, None)\n        fid, rdata = fid_data\n        if fid is None:\n            close = True\n            fid = h5py.h5f.open(obj.filename.encode(\"latin-1\"), h5py.h5f.ACC_RDONLY)\n        else:\n            close = False\n        ftype, fname = field\n        node = f\"/{obj.block_name}/field{self._sep}{fname}\"\n        dg = h5py.h5d.open(fid, node.encode(\"latin-1\"))\n        if rdata is None:\n            rdata = np.empty(\n                self.ds.grid_dimensions[: self.ds.dimensionality][::-1],\n                dtype=self._field_dtype,\n            )\n        dg.read(h5py.h5s.ALL, h5py.h5s.ALL, rdata)\n        if close:\n            fid.close()\n        data = rdata[self._activezone_idx].T\n        if self.ds.dimensionality < 3:\n            nshape = data.shape + (1,) * (3 - self.ds.dimensionality)\n            data = np.reshape(data, nshape)\n        return data\n"
  },
  {
    "path": "yt/frontends/enzo_e/misc.py",
    "content": "import numpy as np\nfrom more_itertools import always_iterable\n\n\ndef bdecode(block):\n    \"\"\"\n    Decode a block descriptor to get its left and right sides and level.\n\n    A block string consisting of (0, 1), with optionally one colon. The\n    number of digits after the colon is the refinement level. The combined\n    digits denote the binary representation of the left edge.\n    \"\"\"\n\n    if \":\" in block:\n        level = len(block) - block.find(\":\") - 1\n    else:\n        level = 0\n    bst = block.replace(\":\", \"\")\n    d = float(2 ** len(bst))\n    left = int(bst, 2)\n    right = left + 1\n    left /= d\n    right /= d\n    return level, left, right\n\n\ndef get_block_string_and_dim(block, min_dim=3):\n    mybs = block[1:].split(\"_\")\n    dim = max(len(mybs), min_dim)\n    return mybs, dim\n\n\ndef get_block_level(block):\n    if \":\" in block:\n        l = block.find(\":\")\n    else:\n        l = len(block)\n    return l\n\n\ndef get_block_info(block, min_dim=3):\n    \"\"\"Decode a block name to get its left and right sides and level.\n\n    Given a block name, this function returns the locations of the block's left\n    and right edges (measured as binary fractions of the domain along each\n    axis) and level.\n\n    Unrefined blocks in the root array (which can each hold an of octree) have\n    a refinement level of 0 while their ancestors (used internally by Enzo-E's\n    solvers - they don't actually hold meaningful data) have negative levels.\n    Because identification of negative refinement levels requires knowledge of\n    the root array shape (the 'root_blocks' value specified in the parameter\n    file), all unrefined blocks are assumed to have a level of 0.\n    \"\"\"\n    mybs, dim = get_block_string_and_dim(block, min_dim=min_dim)\n    left = np.zeros(dim)\n    right = np.ones(dim)\n    level = 0\n    for i, myb in enumerate(mybs):\n        if myb == \"\":\n            continue\n        level, left[i], right[i] = bdecode(myb)\n    return level, left, right\n\n\ndef get_root_blocks(block, min_dim=3):\n    mybs, dim = get_block_string_and_dim(block, min_dim=min_dim)\n    nb = np.ones(dim, dtype=\"int64\")\n    for i, myb in enumerate(mybs):\n        if myb == \"\":\n            continue\n        s = get_block_level(myb)\n        nb[i] = 2**s\n    return nb\n\n\ndef get_root_block_id(block, min_dim=3):\n    mybs, dim = get_block_string_and_dim(block, min_dim=min_dim)\n    rbid = np.zeros(dim, dtype=\"int64\")\n    for i, myb in enumerate(mybs):\n        if myb == \"\":\n            continue\n        s = get_block_level(myb)\n        if s == 0:\n            continue\n        rbid[i] = int(myb[:s], 2)\n    return rbid\n\n\ndef get_child_index(anc_id, desc_id):\n    cid = \"\"\n    for aind, dind in zip(anc_id.split(\"_\"), desc_id.split(\"_\"), strict=True):\n        cid += dind[len(aind)]\n    cid = int(cid, 2)\n    return cid\n\n\ndef is_parent(anc_block, desc_block):\n    dim = anc_block.count(\"_\") + 1\n    if (len(desc_block.replace(\":\", \"\")) - len(anc_block.replace(\":\", \"\"))) / dim != 1:\n        return False\n\n    for aind, dind in zip(anc_block.split(\"_\"), desc_block.split(\"_\"), strict=True):\n        if not dind.startswith(aind):\n            return False\n    return True\n\n\ndef nested_dict_get(pdict, keys, default=None):\n    \"\"\"\n    Retrieve a value from a nested dict using a tuple of keys.\n\n    If a is a dict, and a['b'] = {'c': 'd'},\n    then nested_dict_get(a, ('b', 'c')) returns 'd'.\n    \"\"\"\n\n    val = pdict\n    for key in always_iterable(keys):\n        try:\n            val = val[key]\n        except KeyError:\n            return default\n    return val\n\n\ndef get_listed_subparam(pdict, parent_param, subparam, default=None):\n    \"\"\"\n    Returns nested_dict_get(pdict, (parent_param,subparam), default) if\n    subparam is an entry in nested_dict_get(pdict, (parent_param, 'list'), [])\n\n    This is a common idiom in Enzo-E's parameter parsing\n    \"\"\"\n    if subparam in nested_dict_get(pdict, (parent_param, \"list\"), []):\n        return nested_dict_get(pdict, (parent_param, subparam), default)\n    return default\n\n\ndef get_particle_mass_correction(ds):\n    \"\"\"\n    Normalize particle masses by the root grid cell volume.\n\n    This correction is used for Enzo-E datasets where particle\n    masses are stored as densities.\n    \"\"\"\n\n    return (ds.domain_width / ds.domain_dimensions).prod() / ds.length_unit**3\n"
  },
  {
    "path": "yt/frontends/enzo_e/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/enzo_e/tests/test_misc.py",
    "content": "import numpy as np\n\nfrom yt.frontends.enzo_e.misc import (\n    get_block_info,\n    get_root_block_id,\n    get_root_blocks,\n    is_parent,\n    nested_dict_get,\n)\n\n\ndef get_random_block_string(max_n=64, random_state=None, level=None):\n    if max_n == 1:\n        assert level is None or level == 0\n        return 0, 0, \"B\"\n    elif max_n < 1:\n        raise ValueError(\"max_n must be a positive integer\")\n\n    if random_state is None:\n        random_state = np.random.RandomState()\n\n    max_l = int(np.log2(max_n))\n    form = f\"%0{max_l}d\"\n    num10 = random_state.randint(0, high=max_n)\n    num2 = form % int(bin(num10)[2:])  # the slice clips the '0b' prefix\n\n    if level is None:\n        level = random_state.randint(0, high=max_l)\n    if level > 0:\n        my_block = f\"{num2[:-level]}:{num2[-level:]}\"\n    else:\n        my_block = num2\n    my_block = \"B\" + my_block\n\n    return num10, level, my_block\n\n\ndef flip_random_block_bit(block, rs):\n    \"\"\"\n    Flips a bit string in one of the block descriptors in a given block name\n    \"\"\"\n    # split block into descriptors for each dimension\n    descriptors = block[1:].split(\"_\")\n\n    # choose which descriptor to modify\n    flippable = [i for i, descr in enumerate(descriptors) if len(descr) > 0]\n    if len(flippable) == 0:  # when block in ['B', 'B_', 'B__']\n        raise ValueError(f\"{block} has no bits that can be flipped\")\n    descr_index = flippable[rs.randint(0, len(flippable))]\n\n    # split block descriptor into left and right parts\n    parts = descriptors[descr_index].split(\":\")\n    # select the part to be modified\n    if len(parts) == 1:  # block is unrefined\n        part_index = 0\n    elif len(parts[0]) == 0:  # The root block index can't be modified\n        part_index = 1\n    else:\n        part_index = rs.randint(0, high=2)\n    modify_part = parts[part_index]\n\n    # flip a bit in modify_part, and return the new block name with this change\n    flip_index = rs.randint(0, high=len(modify_part))\n    parts[part_index] = (\n        f\"{modify_part[:flip_index]}\"\n        f\"{(int(modify_part[flip_index]) + 1) % 2}\"\n        f\"{modify_part[flip_index + 1 :]}\"\n    )\n    descriptors[descr_index] = \":\".join(parts)\n    return \"B\" + \"_\".join(descriptors)\n\n\ndef test_get_block_info():\n    rs = np.random.RandomState(45047)\n    max_n = 64\n    for _ in range(10):\n        n, l, b = get_random_block_string(max_n=max_n, random_state=rs)\n        level, left, right = get_block_info(b, min_dim=1)\n        assert level == l\n        assert left == float(n) / max_n\n        assert right == float(n + 1) / max_n\n\n    for block in [\"B\", \"B_\", \"B__\"]:\n        level, left, right = get_block_info(block)\n        assert level == 0\n        assert (left == 0.0).all()\n        assert (right == 1.0).all()\n\n\ndef test_root_blocks():\n    rs = np.random.RandomState(45652)\n    for i in range(6):\n        max_n = 2**i\n        n1, l1, b1 = get_random_block_string(max_n=max_n, random_state=rs, level=0)\n        n2, l2, b2 = get_random_block_string(max_n=32, random_state=rs, level=0)\n        block = f\"{b1}:{b2[1:]}\"\n\n        nrb = get_root_blocks(block, min_dim=1)\n        assert nrb == max_n\n        rbid = get_root_block_id(block, min_dim=1)\n        assert rbid == n1\n\n\ndef test_is_parent():\n    rs = np.random.RandomState(45652)\n    for dim in [1, 2, 3]:\n        for i in range(6):\n            max_n = 2**i\n\n            descriptors = []\n            for _ in range(dim):\n                n1, l1, b1 = get_random_block_string(\n                    max_n=max_n, random_state=rs, level=0\n                )\n                n2, l2, b2 = get_random_block_string(max_n=32, random_state=rs, level=0)\n                descriptors.append(f\"{b1[1:]}:{b2[1:]}\")\n            block = \"B\" + \"_\".join(descriptors)\n            # since b2 is computed with max_n=32 in the for-loop, block always\n            # has a refined great-great-grandparent\n            parent = \"B\" + \"_\".join(elem[:-1] for elem in descriptors)\n            grandparent = \"B\" + \"_\".join(elem[:-2] for elem in descriptors)\n\n            assert is_parent(parent, block)\n            assert is_parent(grandparent, parent)\n            assert not is_parent(grandparent, block)\n            assert not is_parent(block, parent)\n            assert not is_parent(flip_random_block_bit(parent, rs), block)\n\n\ndef test_nested_dict_get():\n    rs = np.random.RandomState(47988)\n    keys = []\n    my_dict = None\n    for _ in range(5):\n        k = str(rs.randint(0, high=1000000))\n        if my_dict is None:\n            v = str(rs.randint(0, high=1000000))\n            keys.append(v)\n            my_dict = {k: v}\n        else:\n            my_dict = {k: my_dict}\n        keys.append(k)\n    keys.reverse()\n    assert nested_dict_get(my_dict, keys[:-1]) == keys[-1]\n\n    my_def = \"devron\"\n    assert nested_dict_get(my_dict, \"system\", default=my_def) == my_def\n\n\ndef test_nested_dict_get_real_none():\n    my_dict = {\"a\": None}\n    response = nested_dict_get(my_dict, \"a\", default=\"fail\")\n    assert response is None\n"
  },
  {
    "path": "yt/frontends/enzo_e/tests/test_outputs.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_equal, assert_equal\n\nfrom yt.frontends.enzo_e.api import EnzoEDataset\nfrom yt.frontends.enzo_e.fields import NODAL_FLAGS\nfrom yt.testing import requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    PixelizedProjectionValuesTest,\n    create_obj,\n    data_dir_load,\n    requires_ds,\n)\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n_fields = (\n    (\"gas\", \"density\"),\n    (\"gas\", \"specific_total_energy\"),\n    (\"gas\", \"velocity_x\"),\n    (\"gas\", \"velocity_y\"),\n)\n\n_pfields = (\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_position_y\"),\n    (\"all\", \"particle_position_z\"),\n    (\"all\", \"particle_velocity_x\"),\n    (\"all\", \"particle_velocity_y\"),\n    (\"all\", \"particle_velocity_z\"),\n)\n\nhello_world = \"hello-0210/hello-0210.block_list\"\nep_cosmo = \"ENZOP_DD0140/ENZOP_DD0140.block_list\"\norszag_tang = \"ENZOE_orszag-tang_0.5/ENZOE_orszag-tang_0.5.block_list\"\n\n\n@requires_module(\"h5py\")\n@requires_file(hello_world)\ndef test_EnzoEDataset():\n    assert isinstance(data_dir_load(hello_world), EnzoEDataset)\n\n\n@requires_module(\"h5py\")\n@requires_ds(hello_world)\ndef test_hello_world():\n    ds = data_dir_load(hello_world)\n\n    dso = [None, (\"sphere\", (\"max\", (0.25, \"unitary\")))]\n    for dobj_name in dso:\n        for field in _fields:\n            for axis in [0, 1, 2]:\n                for weight_field in [None, (\"gas\", \"density\")]:\n                    yield PixelizedProjectionValuesTest(\n                        hello_world, axis, field, weight_field, dobj_name\n                    )\n            yield FieldValuesTest(hello_world, field, dobj_name)\n        dobj = create_obj(ds, dobj_name)\n        s1 = dobj[\"index\", \"ones\"].sum()\n        s2 = sum(mask.sum() for block, mask in dobj.blocks)\n        assert_equal(s1, s2)\n\n\n@requires_module(\"h5py\")\n@requires_ds(ep_cosmo)\ndef test_particle_fields():\n    ds = data_dir_load(ep_cosmo)\n\n    dso = [None, (\"sphere\", (\"max\", (0.1, \"unitary\")))]\n    for dobj_name in dso:\n        for field in _pfields:\n            yield FieldValuesTest(ep_cosmo, field, dobj_name, particle_type=True)\n        dobj = create_obj(ds, dobj_name)\n        s1 = dobj[\"index\", \"ones\"].sum()\n        s2 = sum(mask.sum() for block, mask in dobj.blocks)\n        assert_equal(s1, s2)\n\n\n@requires_module(\"h5py\")\n@requires_file(hello_world)\ndef test_hierarchy():\n    ds = data_dir_load(hello_world)\n\n    fh = h5py.File(ds.index.grids[0].filename, mode=\"r\")\n    for grid in ds.index.grids:\n        assert_array_equal(\n            grid.LeftEdge.d, fh[grid.block_name].attrs[\"enzo_GridLeftEdge\"]\n        )\n        assert_array_equal(ds.index.grid_left_edge[grid.id], grid.LeftEdge)\n        assert_array_equal(ds.index.grid_right_edge[grid.id], grid.RightEdge)\n        for child in grid.Children:\n            assert (child.LeftEdge >= grid.LeftEdge).all()\n            assert (child.RightEdge <= grid.RightEdge).all()\n            assert_equal(child.Parent.id, grid.id)\n    fh.close()\n\n\n@requires_module(\"h5py\")\n@requires_file(ep_cosmo)\ndef test_critical_density():\n    ds = data_dir_load(ep_cosmo)\n\n    c1 = (\n        (ds.r[\"dark\", \"particle_mass\"].sum() + ds.r[\"gas\", \"cell_mass\"].sum())\n        / ds.domain_width.prod()\n        / ds.critical_density\n    )\n    c2 = (\n        ds.omega_matter\n        * (1 + ds.current_redshift) ** 3\n        / (ds.omega_matter * (1 + ds.current_redshift) ** 3 + ds.omega_lambda)\n    )\n\n    assert np.abs(c1 - c2) / max(c1, c2) < 1e-3\n\n\n@requires_module(\"h5py\")\n@requires_file(orszag_tang)\ndef test_face_centered_bfields():\n    # this is based on the enzo frontend test, test_face_centered_mhdct_fields\n    ds = data_dir_load(orszag_tang)\n\n    ad = ds.all_data()\n    assert len(ds.index.grids) == 4\n\n    for field, flag in NODAL_FLAGS.items():\n        dims = ds.domain_dimensions\n        assert_equal(ad[field].shape, (dims.prod(), 2 * sum(flag)))\n\n        # the x and y domains are each split across 2 blocks. The z domain\n        # is all located on a single block\n        block_dims = (dims[0] // 2, dims[1] // 2, dims[2])\n        for grid in ds.index.grids:\n            assert_equal(grid[field].shape, block_dims + (2 * sum(flag),))\n\n    # Average of face-centered fields should be the same as cell-centered field\n    assert_array_equal(\n        ad[\"enzoe\", \"bfieldi_x\"].sum(axis=-1) / 2, ad[\"enzoe\", \"bfield_x\"]\n    )\n    assert_array_equal(\n        ad[\"enzoe\", \"bfieldi_y\"].sum(axis=-1) / 2, ad[\"enzoe\", \"bfield_y\"]\n    )\n    assert_array_equal(\n        ad[\"enzoe\", \"bfieldi_z\"].sum(axis=-1) / 2, ad[\"enzoe\", \"bfield_z\"]\n    )\n"
  },
  {
    "path": "yt/frontends/exodus_ii/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/exodus_ii/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    ExodusIIDataset,\n    ExodusIIUnstructuredIndex,\n    ExodusIIUnstructuredMesh,\n)\nfrom .fields import ExodusIIFieldInfo\nfrom .io import IOHandlerExodusII\nfrom .simulation_handling import ExodusIISimulation\n"
  },
  {
    "path": "yt/frontends/exodus_ii/data_structures.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.index_subobjects.unstructured_mesh import UnstructuredMesh\nfrom yt.data_objects.static_output import Dataset\nfrom yt.data_objects.unions import MeshUnion\nfrom yt.funcs import setdefaultattr\nfrom yt.geometry.unstructured_mesh_handler import UnstructuredIndex\nfrom yt.utilities.file_handler import NetCDF4FileHandler, valid_netcdf_signature\nfrom yt.utilities.logger import ytLogger as mylog\n\nfrom .fields import ExodusIIFieldInfo\nfrom .util import get_num_pseudo_dims, load_info_records, sanitize_string\n\n\nclass ExodusIIUnstructuredMesh(UnstructuredMesh):\n    _index_offset = 1\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n\nclass ExodusIIUnstructuredIndex(UnstructuredIndex):\n    def __init__(self, ds, dataset_type=\"exodus_ii\"):\n        super().__init__(ds, dataset_type)\n\n    def _initialize_mesh(self):\n        coords = self.ds._read_coordinates()\n        connectivity = self.ds._read_connectivity()\n        self.meshes = []\n        for mesh_id, conn_ind in enumerate(connectivity):\n            displaced_coords = self.ds._apply_displacement(coords, mesh_id)\n            mesh = ExodusIIUnstructuredMesh(\n                mesh_id, self.index_filename, conn_ind, displaced_coords, self\n            )\n            self.meshes.append(mesh)\n        self.mesh_union = MeshUnion(\"mesh_union\", self.meshes)\n\n    def _detect_output_fields(self):\n        elem_names = self.dataset.parameters[\"elem_names\"]\n        node_names = self.dataset.parameters[\"nod_names\"]\n        fnames = elem_names + node_names\n        self.field_list = []\n        for i in range(1, len(self.meshes) + 1):\n            self.field_list += [(f\"connect{i}\", fname) for fname in fnames]\n        self.field_list += [(\"all\", fname) for fname in fnames]\n\n\nclass ExodusIIDataset(Dataset):\n    _load_requirements = [\"netCDF4\"]\n    _index_class = ExodusIIUnstructuredIndex\n    _field_info_class = ExodusIIFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        step=0,\n        displacements=None,\n        dataset_type=\"exodus_ii\",\n        storage_filename=None,\n        units_override=None,\n    ):\n        \"\"\"\n\n        A class used to represent an on-disk ExodusII dataset. The initializer takes\n        two extra optional parameters, \"step\" and \"displacements.\"\n\n        Parameters\n        ----------\n\n        step : integer\n            The step tells which time index to slice at. It throws an Error if\n            the index is larger than the number of time outputs in the ExodusII\n            file. Passing step=-1 picks out the last dataframe.\n            Default is 0.\n\n        displacements : dictionary of tuples\n            This is a dictionary that controls whether or not displacement fields\n            will be used with the meshes in this dataset. The keys of the\n            displacements dictionary should the names of meshes in the file\n            (e.g., \"connect1\", \"connect2\", etc... ), while the values should be\n            tuples of the form (scale, offset), where \"scale\" is a floating point\n            value and \"offset\" is an array-like with one component for each spatial\n            dimension in the dataset. When the displacements for a given mesh are\n            turned on, the coordinates of the vertices in that mesh get transformed\n            as:\n\n                  vertex_x = vertex_x + disp_x*scale + offset_x\n                  vertex_y = vertex_y + disp_y*scale + offset_y\n                  vertex_z = vertex_z + disp_z*scale + offset_z\n\n            If no displacement\n            fields (assumed to be named 'disp_x', 'disp_y', etc... ) are detected in\n            the output file, then this dictionary is ignored.\n\n        Examples\n        --------\n\n        This will load the Dataset at time index '0' with displacements turned off.\n\n        >>> import yt\n        >>> ds = yt.load(\"MOOSE_sample_data/mps_out.e\")\n\n        This will load the Dataset at the final index with displacements turned off.\n\n        >>> import yt\n        >>> ds = yt.load(\"MOOSE_sample_data/mps_out.e\", step=-1)\n\n        This will load the Dataset at index 10, turning on displacement fields for\n        the 2nd mesh without applying any scale or offset:\n\n        >>> import yt\n        >>> ds = yt.load(\n        ...     \"MOOSE_sample_data/mps_out.e\",\n        ...     step=10,\n        ...     displacements={\"connect2\": (1.0, [0.0, 0.0, 0.0])},\n        ... )\n\n        This will load the Dataset at index 10, scaling the displacements\n        in the 2nd mesh by a factor of 5 while not applying an offset:\n\n        >>> import yt\n        >>> ds = yt.load(\n        ...     \"MOOSE_sample_data/mps_out.e\",\n        ...     step=10,\n        ...     displacements={\"connect2\": (5.0, [0.0, 0.0, 0.0])},\n        ... )\n\n        This will load the Dataset at index 10, scaling the displacements for\n        the 2nd mesh by a factor of 5.0 and shifting all the vertices in\n        the first mesh by 1.0 unit in the z direction.\n\n        >>> import yt\n        >>> ds = yt.load(\n        ...     \"MOOSE_sample_data/mps_out.e\",\n        ...     step=10,\n        ...     displacements={\n        ...         \"connect1\": (0.0, [0.0, 0.0, 1.0]),\n        ...         \"connect2\": (5.0, [0.0, 0.0, 0.0]),\n        ...     },\n        ... )\n\n        \"\"\"\n        self.step = step\n        if displacements is None:\n            self.displacements = {}\n        else:\n            self.displacements = displacements\n        self.storage_filename = storage_filename\n\n        super().__init__(filename, dataset_type, units_override=units_override)\n\n        self.fluid_types += self._get_fluid_types()\n        self.default_field = [f for f in self.field_list if f[0] == \"connect1\"][-1]\n\n    @property\n    def index_filename(self):\n        # historic alias\n        return self.filename\n\n    def _set_code_unit_attributes(self):\n        # This is where quantities are created that represent the various\n        # on-disk units.  These are the currently available quantities which\n        # should be set, along with examples of how to set them to standard\n        # values.\n        #\n        setdefaultattr(self, \"length_unit\", self.quan(1.0, \"cm\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"g\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n        #\n        # These can also be set:\n        # self.velocity_unit = self.quan(1.0, \"cm/s\")\n        # self.magnetic_unit = self.quan(1.0, \"gauss\")\n\n    def _parse_parameter_file(self):\n        self._handle = NetCDF4FileHandler(self.parameter_filename)\n        with self._handle.open_ds() as ds:\n            self._read_glo_var()\n            self.dimensionality = ds.variables[\"coor_names\"].shape[0]\n            self.parameters[\"info_records\"] = self._load_info_records()\n            self.num_steps = len(ds.variables[\"time_whole\"])\n            self.current_time = self._get_current_time()\n            self.parameters[\"num_meshes\"] = ds.variables[\"eb_status\"].shape[0]\n            self.parameters[\"elem_names\"] = self._get_elem_names()\n            self.parameters[\"nod_names\"] = self._get_nod_names()\n            self.domain_left_edge, self.domain_right_edge = self._load_domain_edge()\n            self._periodicity = (False, False, False)\n\n        # These attributes don't really make sense for unstructured\n        # mesh data, but yt warns if they are not present, so we set\n        # them to dummy values here.\n        self.domain_dimensions = np.ones(3, \"int32\")\n        self.cosmological_simulation = 0\n        self.current_redshift = 0\n        self.omega_lambda = 0\n        self.omega_matter = 0\n        self.hubble_constant = 0\n        self.refine_by = 0\n\n    def _get_fluid_types(self):\n        with NetCDF4FileHandler(self.parameter_filename).open_ds() as ds:\n            fluid_types = ()\n            i = 1\n            while True:\n                ftype = f\"connect{i}\"\n                if ftype in ds.variables:\n                    fluid_types += (ftype,)\n                    i += 1\n                else:\n                    break\n            fluid_types += (\"all\",)\n            return fluid_types\n\n    def _read_glo_var(self):\n        \"\"\"\n        Adds each global variable to the dict of parameters\n\n        \"\"\"\n        names = self._get_glo_names()\n        if not names:\n            return\n        with self._handle.open_ds() as ds:\n            values = ds.variables[\"vals_glo_var\"][:].transpose()\n            for name, value in zip(names, values, strict=True):\n                self.parameters[name] = value\n\n    def _load_info_records(self):\n        \"\"\"\n        Returns parsed version of the info_records.\n        \"\"\"\n        with self._handle.open_ds() as ds:\n            try:\n                return load_info_records(ds.variables[\"info_records\"])\n            except (KeyError, TypeError):\n                mylog.warning(\"No info_records found\")\n                return []\n\n    def _get_current_time(self):\n        with self._handle.open_ds() as ds:\n            try:\n                return ds.variables[\"time_whole\"][self.step]\n            except IndexError as e:\n                raise RuntimeError(\n                    f\"Invalid step number, max is {self.num_steps - 1}\"\n                ) from e\n            except (KeyError, TypeError):\n                return 0.0\n\n    def _get_glo_names(self):\n        \"\"\"\n\n        Returns the names of the global vars, if available.\n\n        \"\"\"\n\n        with self._handle.open_ds() as ds:\n            if \"name_glo_var\" not in ds.variables:\n                mylog.warning(\"name_glo_var not found\")\n                return []\n            else:\n                return [\n                    sanitize_string(v.tobytes()) for v in ds.variables[\"name_glo_var\"]\n                ]\n\n    def _get_elem_names(self):\n        \"\"\"\n\n        Returns the names of the element vars, if available.\n\n        \"\"\"\n\n        with self._handle.open_ds() as ds:\n            if \"name_elem_var\" not in ds.variables:\n                mylog.warning(\"name_elem_var not found\")\n                return []\n            else:\n                return [\n                    sanitize_string(v.tobytes()) for v in ds.variables[\"name_elem_var\"]\n                ]\n\n    def _get_nod_names(self):\n        \"\"\"\n\n        Returns the names of the node vars, if available\n\n        \"\"\"\n\n        with self._handle.open_ds() as ds:\n            if \"name_nod_var\" not in ds.variables:\n                mylog.warning(\"name_nod_var not found\")\n                return []\n            else:\n                return [\n                    sanitize_string(v.tobytes()) for v in ds.variables[\"name_nod_var\"]\n                ]\n\n    def _read_coordinates(self):\n        \"\"\"\n\n        Loads the coordinates for the mesh\n\n        \"\"\"\n\n        coord_axes = \"xyz\"[: self.dimensionality]\n\n        mylog.info(\"Loading coordinates\")\n        with self._handle.open_ds() as ds:\n            if \"coord\" not in ds.variables:\n                coords = (\n                    np.array([ds.variables[f\"coord{ax}\"][:] for ax in coord_axes])\n                    .transpose()\n                    .astype(\"f8\")\n                )\n            else:\n                coords = (\n                    np.array(list(ds.variables[\"coord\"][:])).transpose().astype(\"f8\")\n                )\n            return coords\n\n    def _apply_displacement(self, coords, mesh_id):\n        mesh_name = f\"connect{mesh_id + 1}\"\n        new_coords = coords.copy()\n        if mesh_name not in self.displacements:\n            return new_coords\n        fac = self.displacements[mesh_name][0]\n        offset = self.displacements[mesh_name][1]\n\n        coord_axes = \"xyz\"[: self.dimensionality]\n        with self._handle.open_ds() as ds:\n            for i, ax in enumerate(coord_axes):\n                if f\"disp_{ax}\" in self.parameters[\"nod_names\"]:\n                    ind = self.parameters[\"nod_names\"].index(f\"disp_{ax}\")\n                    disp = ds.variables[f\"vals_nod_var{ind + 1}\"][self.step]\n                    new_coords[:, i] = coords[:, i] + fac * disp + offset[i]\n\n            return new_coords\n\n    def _read_connectivity(self):\n        \"\"\"\n        Loads the connectivity data for the mesh\n        \"\"\"\n        mylog.info(\"Loading connectivity\")\n        connectivity = []\n        with self._handle.open_ds() as ds:\n            for i in range(self.parameters[\"num_meshes\"]):\n                var = ds.variables[f\"connect{i + 1}\"][:].astype(\"i8\")\n                try:\n                    elem_type = var.elem_type.lower()\n                    if elem_type == \"nfaced\":\n                        raise NotImplementedError(\n                            \"3D arbitrary polyhedra are not implemented yet\"\n                        )\n                    arbitrary_polyhedron = elem_type == \"nsided\"\n                except AttributeError:\n                    arbitrary_polyhedron = False\n\n                conn = var[:]\n                if arbitrary_polyhedron:\n                    nodes_per_element = ds.variables[f\"ebepecnt{i + 1}\"]\n                    npe = nodes_per_element[0]\n                    if np.any(nodes_per_element != npe):\n                        raise NotImplementedError(\"only equal-size polyhedra supported\")\n                    q, r = np.divmod(len(conn), npe)\n                    assert r == 0\n                    conn = conn.reshape(q, npe)\n                connectivity.append(conn)\n            return connectivity\n\n    def _load_domain_edge(self):\n        \"\"\"\n        Loads the boundaries for the domain edge\n\n        \"\"\"\n\n        coords = self._read_coordinates()\n        connectivity = self._read_connectivity()\n\n        mi = 1e300\n        ma = -1e300\n        for mesh_id, _ in enumerate(connectivity):\n            displaced_coords = self._apply_displacement(coords, mesh_id)\n            mi = np.minimum(displaced_coords.min(axis=0), mi)\n            ma = np.maximum(displaced_coords.max(axis=0), ma)\n\n        # pad domain boundaries\n        width = ma - mi\n        mi -= 0.1 * width\n        ma += 0.1 * width\n\n        # set up pseudo-3D for lodim datasets here\n        for _ in range(self.dimensionality, 3):\n            mi = np.append(mi, 0.0)\n            ma = np.append(ma, 1.0)\n\n        num_pseudo_dims = get_num_pseudo_dims(coords)\n        self.dimensionality -= num_pseudo_dims\n        for i in range(self.dimensionality, 3):\n            mi[i] = 0.0\n            ma[i] = 1.0\n\n        return mi, ma\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not valid_netcdf_signature(filename):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            from netCDF4 import Dataset\n\n            # We use keepweakref here to avoid holding onto the file handle\n            # which can interfere with other is_valid calls.\n            with Dataset(filename, keepweakref=True) as f:\n                f.variables[\"connect1\"]\n            return True\n        except Exception:\n            return False\n"
  },
  {
    "path": "yt/frontends/exodus_ii/definitions.py",
    "content": "# This file is often empty.  It can hold definitions related to a frontend.\n"
  },
  {
    "path": "yt/frontends/exodus_ii/fields.py",
    "content": "from yt.fields.field_info_container import FieldInfoContainer\n\n# We need to specify which fields we might have in our dataset.  The field info\n# container subclass here will define which fields it knows about.  There are\n# optionally methods on it that get called which can be subclassed.\n\n\nclass ExodusIIFieldInfo(FieldInfoContainer):\n    known_other_fields = (\n        # Each entry here is of the form\n        # ( \"name\", (\"units\", [\"fields\", \"to\", \"alias\"], # \"display_name\")),\n    )\n\n    known_particle_fields = (\n        # Identical form to above\n        # ( \"name\", (\"units\", [\"fields\", \"to\", \"alias\"], # \"display_name\")),\n    )\n\n    def __init__(self, ds, field_list):\n        super().__init__(ds, field_list)\n        for name in self:\n            self[name].take_log = False\n        # If you want, you can check self.field_list\n\n    def setup_fluid_fields(self):\n        # Here we do anything that might need info about the dataset.\n        # You can use self.alias, self.add_output_field and self.add_field .\n        pass\n\n    def setup_particle_fields(self, ptype):\n        # This will get called for every particle type.\n        pass\n"
  },
  {
    "path": "yt/frontends/exodus_ii/io.py",
    "content": "import numpy as np\n\nfrom yt.utilities.file_handler import NetCDF4FileHandler\nfrom yt.utilities.io_handler import BaseIOHandler\n\n\nclass IOHandlerExodusII(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"exodus_ii\"\n    _INDEX_OFFSET = 1\n\n    def __init__(self, ds):\n        self.filename = ds.index_filename\n        exodus_ii_handler = NetCDF4FileHandler(self.filename)\n        self.handler = exodus_ii_handler\n        super().__init__(ds)\n        self.node_fields = ds._get_nod_names()\n        self.elem_fields = ds._get_elem_names()\n\n    def _read_particle_coords(self, chunks, ptf):\n        pass\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        pass\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        # This needs to allocate a set of arrays inside a dictionary, where the\n        # keys are the (ftype, fname) tuples and the values are arrays that\n        # have been masked using whatever selector method is appropriate.  The\n        # dict gets returned at the end and it should be flat, with selected\n        # data.  Note that if you're reading grid data, you might need to\n        # special-case a grid selector object.\n        with self.handler.open_ds() as ds:\n            chunks = list(chunks)\n            rv = {}\n            for field in fields:\n                ftype, fname = field\n                if ftype == \"all\":\n                    ci = np.concatenate(\n                        [\n                            mesh.connectivity_indices - self._INDEX_OFFSET\n                            for mesh in self.ds.index.mesh_union\n                        ]\n                    )\n                else:\n                    ci = ds.variables[ftype][:] - self._INDEX_OFFSET\n                num_elem = ci.shape[0]\n                if fname in self.node_fields:\n                    nodes_per_element = ci.shape[1]\n                    rv[field] = np.zeros((num_elem, nodes_per_element), dtype=\"float64\")\n                elif fname in self.elem_fields:\n                    rv[field] = np.zeros(num_elem, dtype=\"float64\")\n            for field in fields:\n                ind = 0\n                ftype, fname = field\n                if ftype == \"all\":\n                    mesh_ids = [mesh.mesh_id + 1 for mesh in self.ds.index.mesh_union]\n                    objs = list(self.ds.index.mesh_union)\n                else:\n                    mesh_ids = [int(ftype.replace(\"connect\", \"\"))]\n                    chunk = chunks[mesh_ids[0] - 1]\n                    objs = chunk.objs\n                if fname in self.node_fields:\n                    field_ind = self.node_fields.index(fname)\n                    fdata = ds.variables[f\"vals_nod_var{field_ind + 1}\"]\n                    for g in objs:\n                        ci = g.connectivity_indices - self._INDEX_OFFSET\n                        data = fdata[self.ds.step][ci]\n                        ind += g.select(selector, data, rv[field], ind)  # caches\n                if fname in self.elem_fields:\n                    field_ind = self.elem_fields.index(fname)\n                    for g, mesh_id in zip(objs, mesh_ids, strict=True):\n                        varname = f\"vals_elem_var{field_ind + 1}eb{mesh_id}\"\n                        fdata = ds.variables[varname][:]\n                        data = fdata[self.ds.step, :]\n                        ind += g.select(selector, data, rv[field], ind)  # caches\n                rv[field] = rv[field][:ind]\n            return rv\n\n    def _read_chunk_data(self, chunk, fields):\n        # This reads the data from a single chunk, and is only used for\n        # caching.\n        pass\n"
  },
  {
    "path": "yt/frontends/exodus_ii/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/exodus_ii/simulation_handling.py",
    "content": "import glob\n\nfrom yt.data_objects.time_series import DatasetSeries\nfrom yt.funcs import only_on_root\nfrom yt.loaders import load\nfrom yt.utilities.exceptions import YTUnidentifiedDataType\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_objects\n\n\nclass ExodusIISimulation(DatasetSeries):\n    r\"\"\"\n    Initialize an ExodusII Simulation object.\n\n    Upon creation, the input directory is searched for valid ExodusIIDatasets.\n    The get_time_series can be used to generate a DatasetSeries object.\n\n    simulation_directory : str\n        The directory that contain the simulation data.\n\n    Examples\n    --------\n    >>> import yt\n    >>> sim = yt.load_simulation(\"demo_second\", \"ExodusII\")\n    >>> sim.get_time_series()\n    >>> for ds in sim:\n    ...     print(ds.current_time)\n\n    \"\"\"\n\n    def __init__(self, simulation_directory, find_outputs=False):\n        self.simulation_directory = simulation_directory\n        fn_pattern = f\"{self.simulation_directory}/*\"\n        potential_outputs = glob.glob(fn_pattern)\n        self.all_outputs = self._check_for_outputs(potential_outputs)\n        self.all_outputs.sort(key=lambda obj: obj[\"filename\"])\n\n    def __iter__(self):\n        for o in self._pre_outputs:\n            fn, step = o\n            ds = load(fn, step=step)\n            self._setup_function(ds)\n            yield ds\n\n    def __getitem__(self, key):\n        if isinstance(key, slice):\n            if isinstance(key.start, float):\n                return self.get_range(key.start, key.stop)\n            # This will return a sliced up object!\n            return DatasetSeries(self._pre_outputs[key], self.parallel)\n        o = self._pre_outputs[key]\n        fn, step = o\n        o = load(fn, step=step)\n        self._setup_function(o)\n        return o\n\n    def get_time_series(self, parallel=False, setup_function=None):\n        r\"\"\"\n        Instantiate a DatasetSeries object for a set of outputs.\n\n        If no additional keywords given, a DatasetSeries object will be\n        created with all potential datasets created by the simulation.\n\n        Fine-level filtering is currently not implemented.\n\n        \"\"\"\n\n        all_outputs = self.all_outputs\n        ds_list = []\n        for output in all_outputs:\n            num_steps = output[\"num_steps\"]\n            fn = output[\"filename\"]\n            for step in range(num_steps):\n                ds_list.append((fn, step))\n        super().__init__(ds_list, parallel=parallel, setup_function=setup_function)\n\n    def _check_for_outputs(self, potential_outputs):\n        r\"\"\"\n        Check a list of files to see if they are valid datasets.\n        \"\"\"\n\n        only_on_root(\n            mylog.info, \"Checking %d potential outputs.\", len(potential_outputs)\n        )\n\n        my_outputs = {}\n        for my_storage, output in parallel_objects(\n            potential_outputs, storage=my_outputs\n        ):\n            try:\n                ds = load(output)\n            except (FileNotFoundError, YTUnidentifiedDataType):\n                mylog.error(\"Failed to load %s\", output)\n                continue\n            my_storage.result = {\"filename\": output, \"num_steps\": ds.num_steps}\n\n        my_outputs = [\n            my_output for my_output in my_outputs.values() if my_output is not None\n        ]\n        return my_outputs\n"
  },
  {
    "path": "yt/frontends/exodus_ii/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/exodus_ii/tests/test_outputs.py",
    "content": "from numpy.testing import assert_array_equal, assert_equal\n\nfrom yt.testing import requires_file\nfrom yt.utilities.answer_testing.framework import (\n    GenericArrayTest,\n    data_dir_load,\n    requires_ds,\n)\n\nout = \"ExodusII/out.e\"\n\n\n@requires_file(out)\ndef test_out():\n    ds = data_dir_load(out)\n    field_list = [\n        (\"all\", \"conv_indicator\"),\n        (\"all\", \"conv_marker\"),\n        (\"all\", \"convected\"),\n        (\"all\", \"diffused\"),\n        (\"connect1\", \"conv_indicator\"),\n        (\"connect1\", \"conv_marker\"),\n        (\"connect1\", \"convected\"),\n        (\"connect1\", \"diffused\"),\n        (\"connect2\", \"conv_indicator\"),\n        (\"connect2\", \"conv_marker\"),\n        (\"connect2\", \"convected\"),\n        (\"connect2\", \"diffused\"),\n    ]\n    assert_equal(str(ds), \"out.e\")\n    assert_equal(ds.dimensionality, 3)\n    assert_equal(ds.current_time, 0.0)\n    assert_array_equal(ds.parameters[\"nod_names\"], [\"convected\", \"diffused\"])\n    assert_equal(ds.parameters[\"num_meshes\"], 2)\n    assert_array_equal(ds.field_list, field_list)\n\n\nout_s002 = \"ExodusII/out.e-s002\"\n\n\n@requires_file(out_s002)\ndef test_out002():\n    ds = data_dir_load(out_s002)\n    field_list = [\n        (\"all\", \"conv_indicator\"),\n        (\"all\", \"conv_marker\"),\n        (\"all\", \"convected\"),\n        (\"all\", \"diffused\"),\n        (\"connect1\", \"conv_indicator\"),\n        (\"connect1\", \"conv_marker\"),\n        (\"connect1\", \"convected\"),\n        (\"connect1\", \"diffused\"),\n        (\"connect2\", \"conv_indicator\"),\n        (\"connect2\", \"conv_marker\"),\n        (\"connect2\", \"convected\"),\n        (\"connect2\", \"diffused\"),\n    ]\n    assert_equal(str(ds), \"out.e-s002\")\n    assert_equal(ds.dimensionality, 3)\n    assert_equal(ds.current_time, 2.0)\n    assert_array_equal(ds.field_list, field_list)\n\n\ngold = \"ExodusII/gold.e\"\n\n\n@requires_file(gold)\ndef test_gold():\n    ds = data_dir_load(gold)\n    field_list = [(\"all\", \"forced\"), (\"connect1\", \"forced\")]\n    assert_equal(str(ds), \"gold.e\")\n    assert_array_equal(ds.field_list, field_list)\n\n\nbig_data = \"MOOSE_sample_data/mps_out.e\"\n\n\n@requires_ds(big_data)\ndef test_displacement_fields():\n    displacement_dicts = [\n        {\"connect2\": (5.0, [0.0, 0.0, 0.0])},\n        {\"connect1\": (1.0, [1.0, 2.0, 3.0]), \"connect2\": (0.0, [0.0, 0.0, 0.0])},\n    ]\n    for disp in displacement_dicts:\n        ds = data_dir_load(big_data, displacements=disp)\n        for mesh in ds.index.meshes:\n\n            def array_func():\n                return mesh.connectivity_coords  # noqa: B023\n\n            yield GenericArrayTest(ds, array_func, 12)\n"
  },
  {
    "path": "yt/frontends/exodus_ii/util.py",
    "content": "import re\nimport string\nfrom collections import OrderedDict\nfrom itertools import takewhile\n\nimport numpy as np\n\n\ndef get_num_pseudo_dims(coords):\n    D = coords.shape[1]\n    return sum(np.all(coords[:, dim] == 0.0) for dim in range(D))\n\n\ndef sanitize_string(s):\n    _printable = {ord(_) for _ in string.printable}\n    return \"\".join(chr(_) for _ in takewhile(lambda a: a in _printable, s))\n\n\ndef load_info_records(info_records):\n    info_records_parsed = [sanitize_string(line_chars) for line_chars in info_records]\n    return group_by_sections(info_records_parsed)\n\n\ndef group_by_sections(info_records):\n    # 1. Split by top groupings\n    top_levels = get_top_levels(info_records)\n    # 2. Determine if in section by index number\n    grouped = OrderedDict()\n    for tidx, top_level in enumerate(top_levels):\n        grouped[top_level[1]] = []\n\n        try:\n            next_idx = top_levels[tidx + 1][0]\n        except IndexError:\n            next_idx = len(info_records) - 1\n\n        for idx in range(top_level[0], next_idx):\n            if idx == top_level[0]:\n                continue\n\n            grouped[top_level[1]].append(info_records[idx])\n\n    if \"Version Info\" in grouped.keys():\n        version_info = OrderedDict()\n        for line in grouped[\"Version Info\"]:\n            split_line = line.split(\":\")\n            key = split_line[0]\n            val = \":\".join(split_line[1:]).lstrip().rstrip()\n            if key != \"\":\n                version_info[key] = val\n        grouped[\"Version Info\"] = version_info\n\n    return grouped\n\n\ndef get_top_levels(info_records):\n    top_levels = []\n    for idx, line in enumerate(info_records):\n        pattern = re.compile(r\"###[a-zA-Z\\s]+\")\n        if pattern.match(line):\n            clean_line = re.sub(r\"[^\\w\\s]\", \"\", line).lstrip().rstrip()\n            top_levels.append([idx, clean_line])\n\n    return top_levels\n"
  },
  {
    "path": "yt/frontends/fits/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/fits/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    EventsFITSDataset,\n    FITSDataset,\n    FITSGrid,\n    FITSHierarchy,\n    SkyDataFITSDataset,\n    SpectralCubeFITSDataset,\n    SpectralCubeFITSHierarchy,\n)\nfrom .fields import FITSFieldInfo\nfrom .io import IOHandlerFITS\nfrom .misc import setup_counts_fields\n"
  },
  {
    "path": "yt/frontends/fits/data_structures.py",
    "content": "import os\nimport time\nimport uuid\nimport warnings\nimport weakref\nfrom collections import defaultdict\nfrom functools import cached_property\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt.config import ytcfg\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.geometry_handler import YTDataChunk\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.units import dimensions\nfrom yt.units.unit_lookup_table import (  # type: ignore\n    default_unit_symbol_lut,\n    unit_prefixes,\n)\nfrom yt.units.unit_object import UnitParseError  # type: ignore\nfrom yt.units.yt_array import YTQuantity\nfrom yt.utilities.decompose import decompose_array, get_psize\nfrom yt.utilities.file_handler import FITSFileHandler\nfrom yt.utilities.io_handler import io_registry\nfrom yt.utilities.on_demand_imports import NotAModule, _astropy\n\nfrom .fields import FITSFieldInfo, WCSFITSFieldInfo, YTFITSFieldInfo\n\nlon_prefixes = [\"X\", \"RA\", \"GLON\", \"LINEAR\"]\nlat_prefixes = [\"Y\", \"DEC\", \"GLAT\", \"LINEAR\"]\n\nspec_names = {\"V\": \"Velocity\", \"F\": \"Frequency\", \"E\": \"Energy\", \"W\": \"Wavelength\"}\n\nspace_prefixes = list(set(lon_prefixes + lat_prefixes))\nunique_sky_prefixes = set(space_prefixes)\nunique_sky_prefixes.difference_update({\"X\", \"Y\", \"LINEAR\"})\nsky_prefixes = list(unique_sky_prefixes)\nspec_prefixes = list(spec_names.keys())\n\n\nclass FITSGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level):\n        AMRGridPatch.__init__(self, id, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = 0\n\n\nclass FITSHierarchy(GridIndex):\n    grid = FITSGrid\n\n    def __init__(self, ds, dataset_type=\"fits\"):\n        self.dataset_type = dataset_type\n        self.field_indexes = {}\n        self.dataset = weakref.proxy(ds)\n        # for now, the index file is the dataset\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        self._handle = ds._handle\n        self.float_type = np.float64\n        GridIndex.__init__(self, ds, dataset_type)\n\n    def _initialize_data_storage(self):\n        pass\n\n    def _guess_name_from_units(self, units):\n        field_from_unit = {\"Jy\": \"intensity\", \"K\": \"temperature\"}\n        for k, v in field_from_unit.items():\n            if k in units:\n                mylog.warning(\n                    \"Guessing this is a %s field based on its units of %s.\", v, k\n                )\n                return v\n        return None\n\n    def _determine_image_units(self, bunit):\n        try:\n            try:\n                # First let AstroPy attempt to figure the unit out\n                u = 1.0 * _astropy.units.Unit(bunit, format=\"fits\")\n                u = YTQuantity.from_astropy(u).units\n            except ValueError:\n                try:\n                    # Let yt try it by itself\n                    u = self.ds.quan(1.0, bunit).units\n                except UnitParseError:\n                    return \"dimensionless\"\n            return str(u)\n        except KeyError:\n            return \"dimensionless\"\n\n    def _ensure_same_dims(self, hdu):\n        ds = self.dataset\n        conditions = [hdu.header[\"naxis\"] != ds.primary_header[\"naxis\"]]\n        for i in range(ds.naxis):\n            nax = f\"naxis{i + 1}\"\n            conditions.append(hdu.header[nax] != ds.primary_header[nax])\n        if np.any(conditions):\n            return False\n        else:\n            return True\n\n    def _detect_output_fields(self):\n        self.field_list = []\n        self._axis_map = {}\n        self._file_map = {}\n        self._ext_map = {}\n        self._scale_map = {}\n        dup_field_index = {}\n        # Since FITS header keywords are case-insensitive, we only pick a subset of\n        # prefixes, ones that we expect to end up in headers.\n        known_units = {unit.lower(): unit for unit in self.ds.unit_registry.lut}\n        for unit in list(known_units.values()):\n            if unit in self.ds.unit_registry.prefixable_units:\n                for p in [\"n\", \"u\", \"m\", \"c\", \"k\"]:\n                    known_units[(p + unit).lower()] = p + unit\n        # We create a field from each slice on the 4th axis\n        if self.dataset.naxis == 4:\n            naxis4 = self.dataset.primary_header[\"naxis4\"]\n        else:\n            naxis4 = 1\n        for i, fits_file in enumerate(self.dataset._handle._fits_files):\n            for j, hdu in enumerate(fits_file):\n                if (\n                    isinstance(hdu, _astropy.pyfits.BinTableHDU)\n                    or hdu.header[\"naxis\"] == 0\n                ):\n                    continue\n                if self._ensure_same_dims(hdu):\n                    units = self._determine_image_units(hdu.header[\"bunit\"])\n                    try:\n                        # Grab field name from btype\n                        fname = hdu.header[\"btype\"]\n                    except KeyError:\n                        # Try to guess the name from the units\n                        fname = self._guess_name_from_units(units)\n                        # When all else fails\n                        if fname is None:\n                            fname = f\"image_{j}\"\n                    if self.ds.num_files > 1 and fname.startswith(\"image\"):\n                        fname += f\"_file_{i}\"\n                    if (\"fits\", fname) in self.field_list:\n                        if fname in dup_field_index:\n                            dup_field_index[fname] += 1\n                        else:\n                            dup_field_index[fname] = 1\n                        mylog.warning(\n                            \"This field has the same name as a previously loaded \"\n                            \"field. Changing the name from %s to %s_%d. To avoid \"\n                            \"this, change one of the BTYPE header keywords.\",\n                            fname,\n                            fname,\n                            dup_field_index[fname],\n                        )\n                        fname += f\"_{dup_field_index[fname]}\"\n                    for k in range(naxis4):\n                        if naxis4 > 1:\n                            fname += f\"_{hdu.header['CTYPE4']}_{k + 1}\"\n                        self._axis_map[fname] = k\n                        self._file_map[fname] = fits_file\n                        self._ext_map[fname] = j\n                        self._scale_map[fname] = [0.0, 1.0]\n                        if \"bzero\" in hdu.header:\n                            self._scale_map[fname][0] = hdu.header[\"bzero\"]\n                        if \"bscale\" in hdu.header:\n                            self._scale_map[fname][1] = hdu.header[\"bscale\"]\n                        self.field_list.append((\"fits\", fname))\n                        self.dataset.field_units[fname] = units\n                        mylog.info(\"Adding field %s to the list of fields.\", fname)\n                        if units == \"dimensionless\":\n                            mylog.warning(\n                                \"Could not determine dimensions for field %s, \"\n                                \"setting to dimensionless.\",\n                                fname,\n                            )\n                else:\n                    mylog.warning(\n                        \"Image block %s does not have the same dimensions \"\n                        \"as the primary and will not be available as a field.\",\n                        hdu.name.lower(),\n                    )\n\n    def _count_grids(self):\n        self.num_grids = self.ds.parameters[\"nprocs\"]\n\n    def _parse_index(self):\n        ds = self.dataset\n\n        # If nprocs > 1, decompose the domain into virtual grids\n        if self.num_grids > 1:\n            self._domain_decomp()\n        else:\n            self.grid_left_edge[0, :] = ds.domain_left_edge\n            self.grid_right_edge[0, :] = ds.domain_right_edge\n            self.grid_dimensions[0] = ds.domain_dimensions\n\n        self.grid_levels.flat[:] = 0\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n        for i in range(self.num_grids):\n            self.grids[i] = self.grid(i, self, self.grid_levels[i, 0])\n\n    def _domain_decomp(self):\n        bbox = np.array(\n            [self.ds.domain_left_edge, self.ds.domain_right_edge]\n        ).transpose()\n        dims = self.ds.domain_dimensions\n        psize = get_psize(dims, self.num_grids)\n        gle, gre, shapes, slices, _ = decompose_array(dims, psize, bbox)\n        self.grid_left_edge = self.ds.arr(gle, \"code_length\")\n        self.grid_right_edge = self.ds.arr(gre, \"code_length\")\n        self.grid_dimensions = np.array(shapes, dtype=\"int32\")\n\n    def _populate_grid_objects(self):\n        for i in range(self.num_grids):\n            self.grids[i]._prepare_grid()\n            self.grids[i]._setup_dx()\n        self.max_level = 0\n\n    def _setup_derived_fields(self):\n        super()._setup_derived_fields()\n        [self.dataset.conversion_factors[field] for field in self.field_list]\n        for field in self.field_list:\n            if field not in self.derived_field_list:\n                self.derived_field_list.append(field)\n\n        for field in self.derived_field_list:\n            f = self.dataset.field_info[field]\n            if f.is_alias:\n                # Translating an already-converted field\n                self.dataset.conversion_factors[field] = 1.0\n\n    def _setup_data_io(self):\n        self.io = io_registry[self.dataset_type](self.dataset)\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):\n        # local_only is only useful for inline datasets and requires\n        # implementation by subclasses.\n        gfiles = defaultdict(list)\n        gobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for g in gobjs:\n            gfiles[g.id].append(g)\n        for fn in sorted(gfiles):\n            gs = gfiles[fn]\n            yield YTDataChunk(\n                dobj, \"io\", gs, self._count_selection(dobj, gs), cache=cache\n            )\n\n\ndef find_primary_header(fileh):\n    # Sometimes the primary hdu doesn't have an image\n    if len(fileh) > 1 and fileh[0].header[\"naxis\"] == 0:\n        first_image = 1\n    else:\n        first_image = 0\n    header = fileh[first_image].header\n    return header, first_image\n\n\ndef check_fits_valid(filename):\n    ext = filename.rsplit(\".\", 1)[-1]\n    if ext.upper() in (\"GZ\", \"FZ\"):\n        # We don't know for sure that there will be > 1\n        ext = filename.rsplit(\".\", 1)[0].rsplit(\".\", 1)[-1]\n    if ext.upper() not in (\"FITS\", \"FTS\"):\n        return None\n    elif isinstance(_astropy.pyfits, NotAModule):\n        raise RuntimeError(\n            \"This appears to be a FITS file, but AstroPy is not installed.\"\n        )\n    try:\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=UserWarning, append=True)\n            fileh = _astropy.pyfits.open(filename)\n        header, _ = find_primary_header(fileh)\n        if header[\"naxis\"] >= 2:\n            return fileh\n        else:\n            fileh.close()\n    except Exception:\n        pass\n    return None\n\n\ndef check_sky_coords(filename, ndim):\n    fileh = check_fits_valid(filename)\n    if fileh is not None:\n        try:\n            if len(fileh) > 1 and fileh[1].name == \"EVENTS\" and ndim == 2:\n                fileh.close()\n                return True\n            else:\n                header, _ = find_primary_header(fileh)\n                if header[\"naxis\"] < ndim:\n                    return False\n                axis_names = [\n                    header.get(f\"ctype{i + 1}\", \"\") for i in range(header[\"naxis\"])\n                ]\n                if len(axis_names) == 3 and axis_names.count(\"LINEAR\") == 2:\n                    return any(a[0] in spec_prefixes for a in axis_names)\n                x = find_axes(axis_names, sky_prefixes + spec_prefixes)\n                fileh.close()\n                return x >= ndim\n        except Exception:\n            pass\n    return False\n\n\nclass FITSDataset(Dataset):\n    _load_requirements = [\"astropy\"]\n    _index_class = FITSHierarchy\n    _field_info_class: type[FieldInfoContainer] = FITSFieldInfo\n    _dataset_type = \"fits\"\n    _handle = None\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"fits\",\n        auxiliary_files=None,\n        nprocs=None,\n        storage_filename=None,\n        nan_mask=None,\n        suppress_astropy_warnings=True,\n        parameters=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        if parameters is None:\n            parameters = {}\n        parameters[\"nprocs\"] = nprocs\n        self.specified_parameters = parameters\n\n        if suppress_astropy_warnings:\n            warnings.filterwarnings(\"ignore\", module=\"astropy\", append=True)\n\n        self.filenames = [filename] + list(always_iterable(auxiliary_files))\n        self.num_files = len(self.filenames)\n        self.fluid_types += (\"fits\",)\n        if nan_mask is None:\n            self.nan_mask = {}\n        elif isinstance(nan_mask, float):\n            self.nan_mask = {\"all\": nan_mask}\n        elif isinstance(nan_mask, dict):\n            self.nan_mask = nan_mask\n        self._handle = FITSFileHandler(self.filenames[0])\n        if isinstance(\n            self.filenames[0], _astropy.pyfits.hdu.image._ImageBaseHDU\n        ) or isinstance(self.filenames[0], _astropy.pyfits.HDUList):\n            fn = f\"InMemoryFITSFile_{uuid.uuid4().hex}\"\n        else:\n            fn = self.filenames[0]\n        self._handle._fits_files.append(self._handle)\n        if self.num_files > 1:\n            for fits_file in auxiliary_files:\n                if isinstance(fits_file, _astropy.pyfits.hdu.image._ImageBaseHDU):\n                    f = _astropy.pyfits.HDUList([fits_file])\n                elif isinstance(fits_file, _astropy.pyfits.HDUList):\n                    f = fits_file\n                else:\n                    if os.path.exists(fits_file):\n                        fn = fits_file\n                    else:\n                        fn = os.path.join(ytcfg.get(\"yt\", \"test_data_dir\"), fits_file)\n                    f = _astropy.pyfits.open(\n                        fn, memmap=True, do_not_scale_image_data=True, ignore_blank=True\n                    )\n                self._handle._fits_files.append(f)\n\n        self.refine_by = 2\n\n        Dataset.__init__(\n            self,\n            fn,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n        self.storage_filename = storage_filename\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Generates the conversion to various physical _units based on the\n        parameter file\n        \"\"\"\n        if getattr(self, \"length_unit\", None) is None:\n            default_length_units = [\n                u for u, v in default_unit_symbol_lut.items() if str(v[1]) == \"(length)\"\n            ]\n            more_length_units = []\n            for unit in default_length_units:\n                if unit in self.unit_registry.prefixable_units:\n                    more_length_units += [prefix + unit for prefix in unit_prefixes]\n            default_length_units += more_length_units\n            file_units = []\n            cunits = [self.wcs.wcs.cunit[i] for i in range(self.dimensionality)]\n            for unit in (_.to_string() for _ in cunits):\n                if unit in default_length_units:\n                    file_units.append(unit)\n            if len(set(file_units)) == 1:\n                length_factor = self.wcs.wcs.cdelt[0]\n                length_unit = str(file_units[0])\n                mylog.info(\"Found length units of %s.\", length_unit)\n            else:\n                self.no_cgs_equiv_length = True\n                mylog.warning(\"No length conversion provided. Assuming 1 = 1 cm.\")\n                length_factor = 1.0\n                length_unit = \"cm\"\n            setdefaultattr(self, \"length_unit\", self.quan(length_factor, length_unit))\n        for unit, cgs in [(\"time\", \"s\"), (\"mass\", \"g\")]:\n            # We set these to cgs for now, but they may have been overridden\n            if getattr(self, unit + \"_unit\", None) is not None:\n                continue\n            mylog.warning(\"Assuming 1.0 = 1.0 %s\", cgs)\n            setdefaultattr(self, f\"{unit}_unit\", self.quan(1.0, cgs))\n        self.magnetic_unit = np.sqrt(\n            4 * np.pi * self.mass_unit / (self.time_unit**2 * self.length_unit)\n        )\n        self.magnetic_unit.convert_to_units(\"gauss\")\n        self.velocity_unit = self.length_unit / self.time_unit\n\n    @property\n    def filename(self) -> str:\n        if self._input_filename.startswith(\"InMemory\"):\n            return self._input_filename\n        else:\n            return super().filename\n\n    @cached_property\n    def unique_identifier(self) -> str:\n        if self.filename.startswith(\"InMemory\"):\n            return str(time.time())\n        else:\n            return super().unique_identifier\n\n    def _parse_parameter_file(self):\n        self._determine_structure()\n        self._determine_axes()\n\n        # Determine dimensionality\n\n        self.dimensionality = self.naxis\n        self.geometry = Geometry.CARTESIAN\n\n        # Sometimes a FITS file has a 4D datacube, in which case\n        # we take the 4th axis and assume it consists of different fields.\n        if self.dimensionality == 4:\n            self.dimensionality = 3\n\n        self._determine_wcs()\n\n        self.current_time = 0.0\n\n        self.domain_dimensions = np.array(self.dims)[: self.dimensionality]\n        if self.dimensionality == 2:\n            self.domain_dimensions = np.append(self.domain_dimensions, [1])\n        self._determine_bbox()\n\n        # Get the simulation time\n        try:\n            self.current_time = self.parameters[\"time\"]\n        except Exception:\n            mylog.warning(\"Cannot find time\")\n            self.current_time = 0.0\n            pass\n\n        # For now we'll ignore these\n        self._periodicity = (False,) * 3\n        self.current_redshift = 0.0\n        self.omega_lambda = 0.0\n        self.omega_matter = 0.0\n        self.hubble_constant = 0.0\n        self.cosmological_simulation = 0\n\n        self._determine_nprocs()\n\n        # Now we can set up some of our parameters for convenience.\n        for k, v in self.primary_header.items():\n            self.parameters[k] = v\n        # Remove potential default keys\n        self.parameters.pop(\"\", None)\n\n    def _determine_nprocs(self):\n        # If nprocs is None, do some automatic decomposition of the domain\n        if self.specified_parameters[\"nprocs\"] is None:\n            nprocs = np.around(\n                np.prod(self.domain_dimensions) / 32**self.dimensionality\n            ).astype(\"int64\")\n            self.parameters[\"nprocs\"] = max(min(nprocs, 512), 1)\n        else:\n            self.parameters[\"nprocs\"] = self.specified_parameters[\"nprocs\"]\n\n    def _determine_structure(self):\n        self.primary_header, self.first_image = find_primary_header(self._handle)\n        self.naxis = self.primary_header[\"naxis\"]\n        self.axis_names = [\n            self.primary_header.get(f\"ctype{i + 1}\", \"LINEAR\")\n            for i in range(self.naxis)\n        ]\n        self.dims = [self.primary_header[f\"naxis{i + 1}\"] for i in range(self.naxis)]\n\n    def _determine_wcs(self):\n        wcs = _astropy.pywcs.WCS(header=self.primary_header)\n        if self.naxis == 4:\n            self.wcs = _astropy.pywcs.WCS(naxis=3)\n            self.wcs.wcs.crpix = wcs.wcs.crpix[:3]\n            self.wcs.wcs.cdelt = wcs.wcs.cdelt[:3]\n            self.wcs.wcs.crval = wcs.wcs.crval[:3]\n            self.wcs.wcs.cunit = [str(unit) for unit in wcs.wcs.cunit][:3]\n            self.wcs.wcs.ctype = list(wcs.wcs.ctype)[:3]\n        else:\n            self.wcs = wcs\n\n    def _determine_bbox(self):\n        domain_left_edge = np.array([0.5] * 3)\n        domain_right_edge = np.array(\n            [float(dim) + 0.5 for dim in self.domain_dimensions]\n        )\n\n        if self.dimensionality == 2:\n            domain_left_edge[-1] = 0.5\n            domain_right_edge[-1] = 1.5\n\n        self.domain_left_edge = domain_left_edge\n        self.domain_right_edge = domain_right_edge\n\n    def _determine_axes(self):\n        self.lat_axis = 1\n        self.lon_axis = 0\n        self.lat_name = \"Y\"\n        self.lon_name = \"X\"\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            fileh = check_fits_valid(filename)\n        except Exception:\n            return False\n\n        if fileh is None:\n            return False\n        else:\n            fileh.close()\n            return True\n\n    @classmethod\n    def _guess_candidates(cls, base, directories, files):\n        candidates = []\n        for fn, fnl in ((_, _.lower()) for _ in files):\n            if (\n                fnl.endswith(\".fits\")\n                or fnl.endswith(\".fits.gz\")\n                or fnl.endswith(\".fits.fz\")\n            ):\n                candidates.append(fn)\n        # FITS files don't preclude subdirectories\n        return candidates, True\n\n    def close(self):\n        self._handle.close()\n\n\ndef find_axes(axis_names, prefixes):\n    x = 0\n    for p in prefixes:\n        y = np.char.startswith(axis_names, p)\n        x += np.any(y)\n    return x\n\n\nclass YTFITSDataset(FITSDataset):\n    _load_requirements = [\"astropy\"]\n    _field_info_class = YTFITSFieldInfo\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n        # Get the current time\n        if \"time\" in self.primary_header:\n            self.current_time = self.primary_header[\"time\"]\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Generates the conversion to various physical _units based on the parameter file\n        \"\"\"\n        for unit, cgs in [\n            (\"length\", \"cm\"),\n            (\"time\", \"s\"),\n            (\"mass\", \"g\"),\n            (\"velocity\", \"cm/s\"),\n            (\"magnetic\", \"gauss\"),\n        ]:\n            if unit == \"magnetic\":\n                short_unit = \"bfunit\"\n            else:\n                short_unit = f\"{unit[0]}unit\"\n            if short_unit in self.primary_header:\n                # units should now be in header\n                u = self.quan(\n                    self.primary_header[short_unit],\n                    self.primary_header.comments[short_unit].strip(\"[]\"),\n                )\n                mylog.info(\"Found %s units of %s.\", unit, u)\n            else:\n                if unit == \"length\":\n                    # Falling back to old way of getting units for length\n                    # in old files\n                    u = self.quan(1.0, str(self.wcs.wcs.cunit[0]))\n                    mylog.info(\"Found %s units of %s.\", unit, u)\n                else:\n                    # Give up otherwise\n                    u = self.quan(1.0, cgs)\n                    mylog.warning(\n                        \"No unit for %s found. Assuming 1.0 code_%s = 1.0 %s\",\n                        unit,\n                        unit,\n                        cgs,\n                    )\n            setdefaultattr(self, f\"{unit}_unit\", u)\n\n    def _determine_bbox(self):\n        dx = np.zeros(3)\n        dx[: self.dimensionality] = self.wcs.wcs.cdelt\n        domain_left_edge = np.zeros(3)\n        domain_left_edge[: self.dimensionality] = self.wcs.wcs.crval - dx[\n            : self.dimensionality\n        ] * (self.wcs.wcs.crpix - 0.5)\n        domain_right_edge = domain_left_edge + dx * self.domain_dimensions\n\n        if self.dimensionality == 2:\n            domain_left_edge[-1] = 0.0\n            domain_right_edge[-1] = dx[0]\n\n        self.domain_left_edge = domain_left_edge\n        self.domain_right_edge = domain_right_edge\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            fileh = check_fits_valid(filename)\n        except Exception:\n            return False\n\n        if fileh is None:\n            return False\n        else:\n            if \"WCSNAME\" in fileh[0].header:\n                isyt = fileh[0].header[\"WCSNAME\"].strip() == \"yt\"\n            else:\n                isyt = False\n            fileh.close()\n            return isyt\n\n\nclass SkyDataFITSDataset(FITSDataset):\n    _load_requirements = [\"astropy\"]\n    _field_info_class = WCSFITSFieldInfo\n\n    def _determine_wcs(self):\n        super()._determine_wcs()\n        end = min(self.dimensionality + 1, 4)\n        self.ctypes = np.array(\n            [self.primary_header[f\"CTYPE{i}\"] for i in range(1, end)]\n        )\n        self.wcs_2d = self.wcs\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n\n        end = min(self.dimensionality + 1, 4)\n\n        self.geometry = Geometry.SPECTRAL_CUBE\n\n        log_str = \"Detected these axes: \" + \"%s \" * len(self.ctypes)\n        mylog.info(log_str, *self.ctypes)\n\n        self.lat_axis = np.zeros((end - 1), dtype=\"bool\")\n        for p in lat_prefixes:\n            self.lat_axis += np.char.startswith(self.ctypes, p)\n        self.lat_axis = np.where(self.lat_axis)[0][0]\n        self.lat_name = self.ctypes[self.lat_axis].split(\"-\")[0].lower()\n\n        self.lon_axis = np.zeros((end - 1), dtype=\"bool\")\n        for p in lon_prefixes:\n            self.lon_axis += np.char.startswith(self.ctypes, p)\n        self.lon_axis = np.where(self.lon_axis)[0][0]\n        self.lon_name = self.ctypes[self.lon_axis].split(\"-\")[0].lower()\n\n        if self.lat_axis == self.lon_axis and self.lat_name == self.lon_name:\n            self.lat_axis = 1\n            self.lon_axis = 0\n            self.lat_name = \"Y\"\n            self.lon_name = \"X\"\n\n        self.spec_axis = 2\n        self.spec_name = \"z\"\n        self.spec_unit = \"\"\n\n    def _set_code_unit_attributes(self):\n        super()._set_code_unit_attributes()\n        units = self.wcs_2d.wcs.cunit[0]\n        if units == \"deg\":\n            units = \"degree\"\n        if units == \"rad\":\n            units = \"radian\"\n        pixel_area = np.prod(np.abs(self.wcs_2d.wcs.cdelt))\n        pixel_area = self.quan(pixel_area, f\"{units}**2\").in_cgs()\n        pixel_dims = pixel_area.units.dimensions\n        self.unit_registry.add(\"pixel\", float(pixel_area.value), dimensions=pixel_dims)\n        if \"beam_size\" in self.specified_parameters:\n            beam_size = self.specified_parameters[\"beam_size\"]\n            beam_size = self.quan(beam_size[0], beam_size[1]).in_cgs().value\n            self.unit_registry.add(\"beam\", beam_size, dimensions=dimensions.solid_angle)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            return check_sky_coords(filename, ndim=2)\n        except Exception:\n            return False\n\n\nclass SpectralCubeFITSHierarchy(FITSHierarchy):\n    def _domain_decomp(self):\n        dz = self.ds.quan(1.0, \"code_length\") * self.ds.spectral_factor\n        self.grid_dimensions[:, 2] = np.around(\n            float(self.ds.domain_dimensions[2]) / self.num_grids\n        ).astype(\"int64\")\n        self.grid_dimensions[-1, 2] += self.ds.domain_dimensions[2] % self.num_grids\n        self.grid_left_edge[0, 2] = self.ds.domain_left_edge[2]\n        self.grid_left_edge[1:, 2] = (\n            self.ds.domain_left_edge[2] + np.cumsum(self.grid_dimensions[:-1, 2]) * dz\n        )\n        self.grid_right_edge[:, 2] = (\n            self.grid_left_edge[:, 2] + self.grid_dimensions[:, 2] * dz\n        )\n        self.grid_left_edge[:, :2] = self.ds.domain_left_edge[:2]\n        self.grid_right_edge[:, :2] = self.ds.domain_right_edge[:2]\n        self.grid_dimensions[:, :2] = self.ds.domain_dimensions[:2]\n\n\nclass SpectralCubeFITSDataset(SkyDataFITSDataset):\n    _load_requirements = [\"astropy\"]\n    _index_class = SpectralCubeFITSHierarchy\n\n    def __init__(\n        self,\n        filename,\n        auxiliary_files=None,\n        nprocs=None,\n        storage_filename=None,\n        nan_mask=None,\n        spectral_factor=1.0,\n        suppress_astropy_warnings=True,\n        parameters=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        if auxiliary_files is None:\n            auxiliary_files = []\n        self.spectral_factor = spectral_factor\n        super().__init__(\n            filename,\n            nprocs=nprocs,\n            auxiliary_files=auxiliary_files,\n            storage_filename=storage_filename,\n            suppress_astropy_warnings=suppress_astropy_warnings,\n            nan_mask=nan_mask,\n            parameters=parameters,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n\n        self.geometry = Geometry.SPECTRAL_CUBE\n\n        end = min(self.dimensionality + 1, 4)\n\n        self.spec_axis = np.zeros(end - 1, dtype=\"bool\")\n        for p in spec_names.keys():\n            self.spec_axis += np.char.startswith(self.ctypes, p)\n        self.spec_axis = np.where(self.spec_axis)[0][0]\n        self.spec_name = spec_names[self.ctypes[self.spec_axis].split(\"-\")[0][0]]\n\n        # Extract a subimage from a WCS object\n        self.wcs_2d = self.wcs.sub([\"longitude\", \"latitude\"])\n\n        self._p0 = self.wcs.wcs.crpix[self.spec_axis]\n        self._dz = self.wcs.wcs.cdelt[self.spec_axis]\n        self._z0 = self.wcs.wcs.crval[self.spec_axis]\n        self.spec_unit = str(self.wcs.wcs.cunit[self.spec_axis])\n\n        if self.spectral_factor == \"auto\":\n            self.spectral_factor = float(\n                max(self.domain_dimensions[[self.lon_axis, self.lat_axis]])\n            )\n            self.spectral_factor /= self.domain_dimensions[self.spec_axis]\n            mylog.info(\"Setting the spectral factor to %f\", self.spectral_factor)\n        Dz = (\n            self.domain_right_edge[self.spec_axis]\n            - self.domain_left_edge[self.spec_axis]\n        )\n        dre = self.domain_right_edge.copy()\n        dre[self.spec_axis] = (\n            self.domain_left_edge[self.spec_axis] + self.spectral_factor * Dz\n        )\n        self.domain_right_edge = dre\n        self._dz /= self.spectral_factor\n        self._p0 = (self._p0 - 0.5) * self.spectral_factor + 0.5\n\n    def _determine_nprocs(self):\n        # If nprocs is None, do some automatic decomposition of the domain\n        if self.specified_parameters[\"nprocs\"] is None:\n            nprocs = np.around(self.domain_dimensions[2] / 8).astype(\"int64\")\n            self.parameters[\"nprocs\"] = max(min(nprocs, 512), 1)\n        else:\n            self.parameters[\"nprocs\"] = self.specified_parameters[\"nprocs\"]\n\n    def spec2pixel(self, spec_value):\n        sv = self.arr(spec_value).in_units(self.spec_unit)\n        return self.arr((sv.v - self._z0) / self._dz + self._p0, \"code_length\")\n\n    def pixel2spec(self, pixel_value):\n        pv = self.arr(pixel_value, \"code_length\")\n        return self.arr((pv.v - self._p0) * self._dz + self._z0, self.spec_unit)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            return check_sky_coords(filename, ndim=3)\n        except Exception:\n            return False\n\n\nclass EventsFITSHierarchy(FITSHierarchy):\n    def _detect_output_fields(self):\n        ds = self.dataset\n        self.field_list = []\n        for k, v in ds.events_info.items():\n            fname = \"event_\" + k\n            mylog.info(\"Adding field %s to the list of fields.\", fname)\n            self.field_list.append((\"io\", fname))\n            if k in [\"x\", \"y\"]:\n                field_unit = \"code_length\"\n            else:\n                field_unit = v\n            self.dataset.field_units[\"io\", fname] = field_unit\n        return\n\n    def _parse_index(self):\n        super()._parse_index()\n        try:\n            self.grid_particle_count[:] = self.dataset.primary_header[\"naxis2\"]\n        except KeyError:\n            self.grid_particle_count[:] = 0.0\n        self._particle_indices = np.zeros(self.num_grids + 1, dtype=\"int64\")\n        self._particle_indices[1] = self.grid_particle_count.squeeze()\n\n\nclass EventsFITSDataset(SkyDataFITSDataset):\n    _load_requirements = [\"astropy\"]\n    _index_class = EventsFITSHierarchy\n\n    def __init__(\n        self,\n        filename,\n        storage_filename=None,\n        suppress_astropy_warnings=True,\n        reblock=1,\n        parameters=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        self.reblock = reblock\n        super().__init__(\n            filename,\n            nprocs=1,\n            storage_filename=storage_filename,\n            parameters=parameters,\n            suppress_astropy_warnings=suppress_astropy_warnings,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n\n    def _determine_structure(self):\n        self.first_image = 1\n        self.primary_header = self._handle[self.first_image].header\n        self.naxis = 2\n\n    def _determine_wcs(self):\n        self.wcs = _astropy.pywcs.WCS(naxis=2)\n        self.events_info = {}\n        for k, v in self.primary_header.items():\n            if k.startswith(\"TTYP\"):\n                if v.lower() in [\"x\", \"y\"]:\n                    num = k.replace(\"TTYPE\", \"\")\n                    self.events_info[v.lower()] = (\n                        self.primary_header[\"TLMIN\" + num],\n                        self.primary_header[\"TLMAX\" + num],\n                        self.primary_header[\"TCTYP\" + num],\n                        self.primary_header[\"TCRVL\" + num],\n                        self.primary_header[\"TCDLT\" + num],\n                        self.primary_header[\"TCRPX\" + num],\n                    )\n                elif v.lower() in [\"energy\", \"time\"]:\n                    num = k.replace(\"TTYPE\", \"\")\n                    unit = self.primary_header[\"TUNIT\" + num].lower()\n                    if unit.endswith(\"ev\"):\n                        unit = unit.replace(\"ev\", \"eV\")\n                    self.events_info[v.lower()] = unit\n        self.axis_names = [self.events_info[ax][2] for ax in [\"x\", \"y\"]]\n        self.wcs.wcs.cdelt = [\n            self.events_info[\"x\"][4] * self.reblock,\n            self.events_info[\"y\"][4] * self.reblock,\n        ]\n        self.wcs.wcs.crpix = [\n            (self.events_info[\"x\"][5] - 0.5) / self.reblock + 0.5,\n            (self.events_info[\"y\"][5] - 0.5) / self.reblock + 0.5,\n        ]\n        self.wcs.wcs.ctype = [self.events_info[\"x\"][2], self.events_info[\"y\"][2]]\n        self.wcs.wcs.cunit = [\"deg\", \"deg\"]\n        self.wcs.wcs.crval = [self.events_info[\"x\"][3], self.events_info[\"y\"][3]]\n        self.dims = [\n            (self.events_info[\"x\"][1] - self.events_info[\"x\"][0]) / self.reblock,\n            (self.events_info[\"y\"][1] - self.events_info[\"y\"][0]) / self.reblock,\n        ]\n        self.ctypes = self.axis_names\n        self.wcs_2d = self.wcs\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            fileh = check_fits_valid(filename)\n        except Exception:\n            return False\n        if fileh is not None:\n            try:\n                valid = fileh[1].name == \"EVENTS\"\n                fileh.close()\n                return valid\n            except Exception:\n                pass\n        return False\n"
  },
  {
    "path": "yt/frontends/fits/definitions.py",
    "content": ""
  },
  {
    "path": "yt/frontends/fits/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\n\nclass FITSFieldInfo(FieldInfoContainer):\n    known_other_fields = ()\n\n    def __init__(self, ds, field_list, slice_info=None):\n        super().__init__(ds, field_list, slice_info=slice_info)\n        for field in ds.field_list:\n            if field[0] == \"fits\":\n                self[field].take_log = False\n\n\nclass YTFITSFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (\"code_mass/code_length**3\", [\"density\"], None)),\n        (\n            \"dark_matter_density\",\n            (\"code_mass/code_length**3\", [\"dark_matter_density\"], None),\n        ),\n        (\"number_density\", (\"1/code_length**3\", [\"number_density\"], None)),\n        (\"pressure\", (\"dyne/code_length**2\", [\"pressure\"], None)),\n        (\"thermal_energy\", (\"erg / g\", [\"specific_thermal_energy\"], None)),\n        (\"temperature\", (\"K\", [\"temperature\"], None)),\n        (\"velocity_x\", (\"code_length/code_time\", [\"velocity_x\"], None)),\n        (\"velocity_y\", (\"code_length/code_time\", [\"velocity_y\"], None)),\n        (\"velocity_z\", (\"code_length/code_time\", [\"velocity_z\"], None)),\n        (\"magnetic_field_x\", (\"gauss\", [], None)),\n        (\"magnetic_field_y\", (\"gauss\", [], None)),\n        (\"magnetic_field_z\", (\"gauss\", [], None)),\n        (\"metallicity\", (\"Zsun\", [\"metallicity\"], None)),\n        # We need to have a bunch of species fields here, too\n        (\"metal_density\", (\"code_mass/code_length**3\", [\"metal_density\"], None)),\n        (\"hi_density\", (\"code_mass/code_length**3\", [\"hi_density\"], None)),\n        (\"hii_density\", (\"code_mass/code_length**3\", [\"hii_density\"], None)),\n        (\"h2i_density\", (\"code_mass/code_length**3\", [\"h2i_density\"], None)),\n        (\"h2ii_density\", (\"code_mass/code_length**3\", [\"h2ii_density\"], None)),\n        (\"h2m_density\", (\"code_mass/code_length**3\", [\"h2m_density\"], None)),\n        (\"hei_density\", (\"code_mass/code_length**3\", [\"hei_density\"], None)),\n        (\"heii_density\", (\"code_mass/code_length**3\", [\"heii_density\"], None)),\n        (\"heiii_density\", (\"code_mass/code_length**3\", [\"heiii_density\"], None)),\n        (\"hdi_density\", (\"code_mass/code_length**3\", [\"hdi_density\"], None)),\n        (\"di_density\", (\"code_mass/code_length**3\", [\"di_density\"], None)),\n        (\"dii_density\", (\"code_mass/code_length**3\", [\"dii_density\"], None)),\n    )\n\n    def __init__(self, ds, field_list, slice_info=None):\n        super().__init__(ds, field_list, slice_info=slice_info)\n\n\nclass WCSFITSFieldInfo(FITSFieldInfo):\n    def setup_fluid_fields(self):\n        wcs_2d = getattr(self.ds, \"wcs_2d\", self.ds.wcs)\n\n        def _pixel(data):\n            return data.ds.arr(data[\"index\", \"ones\"], \"pixel\")\n\n        self.add_field(\n            (\"fits\", \"pixel\"), sampling_type=\"cell\", function=_pixel, units=\"pixel\"\n        )\n\n        def _get_2d_wcs(data, axis):\n            w_coords = wcs_2d.wcs_pix2world(data[\"index\", \"x\"], data[\"index\", \"y\"], 1)\n            return w_coords[axis]\n\n        def world_f(axis, unit):\n            def _world_f(data):\n                return data.ds.arr(_get_2d_wcs(data, axis), unit)\n\n            return _world_f\n\n        for i, axis, name in [\n            (0, self.ds.lon_axis, self.ds.lon_name),\n            (1, self.ds.lat_axis, self.ds.lat_name),\n        ]:\n            unit = str(wcs_2d.wcs.cunit[i])\n            if unit.lower() == \"deg\":\n                unit = \"degree\"\n            if unit.lower() == \"rad\":\n                unit = \"radian\"\n            self.add_field(\n                (\"fits\", name),\n                sampling_type=\"cell\",\n                function=world_f(axis, unit),\n                units=unit,\n            )\n\n        if self.ds.dimensionality == 3:\n\n            def _spec(data):\n                axis = \"xyz\"[data.ds.spec_axis]\n                sp = (\n                    data[\"fits\", axis].ndarray_view() - self.ds._p0\n                ) * self.ds._dz + self.ds._z0\n                return data.ds.arr(sp, data.ds.spec_unit)\n\n            self.add_field(\n                (\"fits\", \"spectral\"),\n                sampling_type=\"cell\",\n                function=_spec,\n                units=self.ds.spec_unit,\n                display_name=self.ds.spec_name,\n            )\n"
  },
  {
    "path": "yt/frontends/fits/io.py",
    "content": "import numpy as np\n\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\n\n\nclass IOHandlerFITS(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"fits\"\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        self.ds = ds\n        self._handle = ds._handle\n\n    def _read_particles(\n        self, fields_to_read, type, args, grid_list, count_list, conv_factors\n    ):\n        pass\n\n    def _read_particle_coords(self, chunks, ptf):\n        pdata = self.ds._handle[self.ds.first_image].data\n        assert len(ptf) == 1\n        ptype = list(ptf.keys())[0]\n        x = np.asarray(pdata.field(\"X\"), dtype=\"=f8\")\n        y = np.asarray(pdata.field(\"Y\"), dtype=\"=f8\")\n        z = np.ones(x.shape)\n        x = (x - 0.5) / self.ds.reblock + 0.5\n        y = (y - 0.5) / self.ds.reblock + 0.5\n        yield ptype, (x, y, z), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        pdata = self.ds._handle[self.ds.first_image].data\n        assert len(ptf) == 1\n        ptype = list(ptf.keys())[0]\n        field_list = ptf[ptype]\n        x = np.asarray(pdata.field(\"X\"), dtype=\"=f8\")\n        y = np.asarray(pdata.field(\"Y\"), dtype=\"=f8\")\n        z = np.ones(x.shape)\n        x = (x - 0.5) / self.ds.reblock + 0.5\n        y = (y - 0.5) / self.ds.reblock + 0.5\n        mask = selector.select_points(x, y, z, 0.0)\n        if mask is None:\n            return\n        for field in field_list:\n            fd = field.split(\"_\")[-1]\n            data = pdata.field(fd.upper())\n            if fd in [\"x\", \"y\"]:\n                data = (data.copy() - 0.5) / self.ds.reblock + 0.5\n            yield (ptype, field), data[mask]\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        if any((ftype != \"fits\" for ftype, fname in fields)):\n            raise NotImplementedError\n        rv = {}\n        dt = \"float64\"\n        for field in fields:\n            rv[field] = np.empty(size, dtype=dt)\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s grids\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n        dx = self.ds.domain_width / self.ds.domain_dimensions\n        for field in fields:\n            ftype, fname = field\n            f = self.ds.index._file_map[fname]\n            ds = f[self.ds.index._ext_map[fname]]\n            bzero, bscale = self.ds.index._scale_map[fname]\n            ind = 0\n            for chunk in chunks:\n                for g in chunk.objs:\n                    start = ((g.LeftEdge - self.ds.domain_left_edge) / dx).d.astype(\n                        \"int\"\n                    )\n                    end = start + g.ActiveDimensions\n                    slices = [slice(start[i], end[i]) for i in range(3)]\n                    if self.ds.dimensionality == 2:\n                        nx, ny = g.ActiveDimensions[:2]\n                        nz = 1\n                        data = np.zeros((nx, ny, nz))\n                        data[:, :, 0] = ds.data[slices[1], slices[0]].T\n                    elif self.ds.naxis == 4:\n                        idx = self.ds.index._axis_map[fname]\n                        data = ds.data[idx, slices[2], slices[1], slices[0]].T\n                    else:\n                        data = ds.data[slices[2], slices[1], slices[0]].T\n                    if fname in self.ds.nan_mask:\n                        data[np.isnan(data)] = self.ds.nan_mask[fname]\n                    elif \"all\" in self.ds.nan_mask:\n                        data[np.isnan(data)] = self.ds.nan_mask[\"all\"]\n                    data = bzero + bscale * data\n                    ind += g.select(selector, data.astype(\"float64\"), rv[field], ind)\n        return rv\n"
  },
  {
    "path": "yt/frontends/fits/misc.py",
    "content": "import base64\nimport os\nfrom io import BytesIO\n\nimport numpy as np\n\nfrom yt.fields.derived_field import ValidateSpatial\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _astropy\n\n\ndef _make_counts(emin, emax):\n    def _counts(data):\n        e = data[\"all\", \"event_energy\"].in_units(\"keV\")\n        mask = np.logical_and(e >= emin, e < emax)\n        x = data[\"all\", \"event_x\"][mask]\n        y = data[\"all\", \"event_y\"][mask]\n        z = np.ones(x.shape)\n        pos = np.array([x, y, z]).transpose()\n        img = data.deposit(pos, method=\"count\")\n        if data.has_field_parameter(\"sigma\"):\n            sigma = data.get_field_parameter(\"sigma\")\n        else:\n            sigma = None\n        if sigma is not None and sigma > 0.0:\n            kern = _astropy.conv.Gaussian2DKernel(x_stddev=sigma)\n            img[:, :, 0] = _astropy.conv.convolve(img[:, :, 0], kern)\n        return data.ds.arr(img, \"counts/pixel\")\n\n    return _counts\n\n\ndef setup_counts_fields(ds, ebounds, ftype=\"gas\"):\n    r\"\"\"\n    Create deposited image fields from X-ray count data in energy bands.\n\n    Parameters\n    ----------\n    ds : ~yt.data_objects.static_output.Dataset\n        The FITS events file dataset to add the counts fields to.\n    ebounds : list of tuples\n        A list of tuples, one for each field, with (emin, emax) as the\n        energy bounds for the image.\n    ftype : string, optional\n        The field type of the resulting field. Defaults to \"gas\".\n\n    Examples\n    --------\n    >>> ds = yt.load(\"evt.fits\")\n    >>> ebounds = [(0.1, 2.0), (2.0, 3.0)]\n    >>> setup_counts_fields(ds, ebounds)\n    \"\"\"\n    for emin, emax in ebounds:\n        cfunc = _make_counts(emin, emax)\n        fname = f\"counts_{emin}-{emax}\"\n        mylog.info(\"Creating counts field %s.\", fname)\n        ds.add_field(\n            (ftype, fname),\n            sampling_type=\"cell\",\n            function=cfunc,\n            units=\"counts/pixel\",\n            validators=[ValidateSpatial()],\n            display_name=f\"Counts ({emin}-{emax} keV)\",\n        )\n\n\ndef create_spectral_slabs(filename, slab_centers, slab_width, **kwargs):\n    r\"\"\"\n    Given a dictionary of spectral slab centers and a width in\n    spectral units, extract data from a spectral cube at these slab\n    centers and return a `FITSDataset` instance containing the different\n    slabs as separate yt fields. Useful for extracting individual\n    lines from a spectral cube and separating them out as different fields.\n\n    Requires the SpectralCube (https://spectral-cube.readthedocs.io/en/latest/)\n    library.\n\n    All keyword arguments will be passed on to the `FITSDataset` constructor.\n\n    Parameters\n    ----------\n    filename : string\n        The spectral cube FITS file to extract the data from.\n    slab_centers : dict of (float, string) tuples or YTQuantities\n        The centers of the slabs, where the keys are the names\n        of the new fields and the values are (float, string) tuples or\n        YTQuantities, specifying a value for each center and its unit.\n    slab_width : YTQuantity or (float, string) tuple\n        The width of the slab along the spectral axis.\n\n    Examples\n    --------\n    >>> slab_centers = {\n    ...     \"13CN\": (218.03117, \"GHz\"),\n    ...     \"CH3CH2CHO\": (218.284256, \"GHz\"),\n    ...     \"CH3NH2\": (218.40956, \"GHz\"),\n    ... }\n    >>> slab_width = (0.05, \"GHz\")\n    >>> ds = create_spectral_slabs(\n    ...     \"intensity_cube.fits\", slab_centers, slab_width, nan_mask=0.0\n    ... )\n    \"\"\"\n    from spectral_cube import SpectralCube\n\n    from yt.frontends.fits.api import FITSDataset\n    from yt.visualization.fits_image import FITSImageData\n\n    cube = SpectralCube.read(filename)\n    if not isinstance(slab_width, YTQuantity):\n        slab_width = YTQuantity(slab_width[0], slab_width[1])\n    slab_data = {}\n    field_units = cube.header.get(\"bunit\", \"dimensionless\")\n    for k, v in slab_centers.items():\n        if not isinstance(v, YTQuantity):\n            slab_center = YTQuantity(v[0], v[1])\n        else:\n            slab_center = v\n        mylog.info(\"Adding slab field %s at %g %s\", k, slab_center.v, slab_center.units)\n        slab_lo = (slab_center - 0.5 * slab_width).to_astropy()\n        slab_hi = (slab_center + 0.5 * slab_width).to_astropy()\n        subcube = cube.spectral_slab(slab_lo, slab_hi)\n        slab_data[k] = YTArray(subcube.filled_data[:, :, :], field_units)\n    width = subcube.header[\"naxis3\"] * cube.header[\"cdelt3\"]\n    w = subcube.wcs.copy()\n    w.wcs.crpix[-1] = 0.5\n    w.wcs.crval[-1] = -0.5 * width\n    fid = FITSImageData(slab_data, wcs=w)\n    for hdu in fid:\n        hdu.header.pop(\"RESTFREQ\", None)\n        hdu.header.pop(\"RESTFRQ\", None)\n    ds = FITSDataset(fid, **kwargs)\n    return ds\n\n\ndef ds9_region(ds, reg, obj=None, field_parameters=None):\n    r\"\"\"\n    Create a data container from a ds9 region file. Requires the regions\n    package (https://astropy-regions.readthedocs.io/) to be installed.\n\n    Parameters\n    ----------\n    ds : FITSDataset\n        The Dataset to create the region from.\n    reg : string\n        The filename of the ds9 region, or a region string to be parsed.\n    obj : data container, optional\n        The data container that will be used to create the new region.\n        Defaults to ds.all_data.\n    field_parameters : dictionary, optional\n        A set of field parameters to apply to the region.\n\n    Examples\n    --------\n\n    >>> ds = yt.load(\"m33_hi.fits\")\n    >>> circle_region = ds9_region(ds, \"circle.reg\")\n    >>> print(circle_region.quantities.extrema(\"flux\"))\n    \"\"\"\n    from yt.utilities.on_demand_imports import _astropy, _regions\n\n    Regions = _regions.Regions\n\n    WCS = _astropy.WCS\n\n    from yt.frontends.fits.api import EventsFITSDataset\n\n    if os.path.exists(reg):\n        method = Regions.read\n    else:\n        method = Regions.parse\n    r = method(reg, format=\"ds9\").regions[0]\n\n    reg_name = reg\n    header = ds.wcs_2d.to_header()\n    # The FITS header only contains WCS-related keywords\n    header[\"NAXIS1\"] = ds.domain_dimensions[ds.lon_axis]\n    header[\"NAXIS2\"] = ds.domain_dimensions[ds.lat_axis]\n\n    pixreg = r.to_pixel(WCS(header))\n    mask = pixreg.to_mask().to_image((header[\"NAXIS1\"], header[\"NAXIS2\"])).astype(bool)\n\n    if isinstance(ds, EventsFITSDataset):\n        prefix = \"event_\"\n    else:\n        prefix = \"\"\n\n    def _reg_field(data):\n        i = data[prefix + \"xyz\"[ds.lon_axis]].d.astype(\"int64\") - 1\n        j = data[prefix + \"xyz\"[ds.lat_axis]].d.astype(\"int64\") - 1\n        new_mask = mask[i, j]\n        ret = np.zeros(data[prefix + \"x\"].shape)\n        ret[new_mask] = 1.0\n        return ret\n\n    ds.add_field((\"gas\", reg_name), sampling_type=\"cell\", function=_reg_field)\n    if obj is None:\n        obj = ds.all_data()\n    if field_parameters is not None:\n        for k, v in field_parameters.items():\n            obj.set_field_parameter(k, v)\n    return obj.cut_region([f\"obj['{reg_name}'] > 0\"])\n\n\nclass PlotWindowWCS:\n    r\"\"\"\n    Use AstroPy's WCSAxes class to plot celestial coordinates on the axes of a\n    on-axis PlotWindow plot. See\n    http://docs.astropy.org/en/stable/visualization/wcsaxes/ for more details\n    on how it works under the hood. This functionality requires a version of\n    AstroPy >= 1.3.\n\n    Parameters\n    ----------\n    pw : on-axis PlotWindow instance\n        The PlotWindow instance to add celestial axes to.\n    \"\"\"\n\n    def __init__(self, pw):\n        WCSAxes = _astropy.wcsaxes.WCSAxes\n\n        if pw.oblique:\n            raise NotImplementedError(\"WCS axes are not implemented for oblique plots.\")\n        if not hasattr(pw.ds, \"wcs_2d\"):\n            raise NotImplementedError(\"WCS axes are not implemented for this dataset.\")\n        if pw.data_source.axis != pw.ds.spec_axis:\n            raise NotImplementedError(\"WCS axes are not implemented for this axis.\")\n        self.plots = {}\n        self.pw = pw\n        for f in pw.plots:\n            rect = pw.plots[f]._get_best_layout()[1]\n            fig = pw.plots[f].figure\n            ax = fig.axes[0]\n            wcs_ax = WCSAxes(fig, rect, wcs=pw.ds.wcs_2d, frameon=False)\n            fig.add_axes(wcs_ax)\n            wcs = pw.ds.wcs_2d.wcs\n            xax = pw.ds.coordinates.x_axis[pw.data_source.axis]\n            yax = pw.ds.coordinates.y_axis[pw.data_source.axis]\n            xlabel = f\"{wcs.ctype[xax].split('-')[0]} ({wcs.cunit[xax]})\"\n            ylabel = f\"{wcs.ctype[yax].split('-')[0]} ({wcs.cunit[yax]})\"\n            fp = pw._font_properties\n            wcs_ax.coords[0].set_axislabel(xlabel, fontproperties=fp, minpad=0.5)\n            wcs_ax.coords[1].set_axislabel(ylabel, fontproperties=fp, minpad=0.4)\n            wcs_ax.coords[0].ticklabels.set_fontproperties(fp)\n            wcs_ax.coords[1].ticklabels.set_fontproperties(fp)\n            ax.xaxis.set_visible(False)\n            ax.yaxis.set_visible(False)\n            wcs_ax.set_xlim(pw.xlim[0].value, pw.xlim[1].value)\n            wcs_ax.set_ylim(pw.ylim[0].value, pw.ylim[1].value)\n            wcs_ax.coords.frame._update_cache = []\n            ax.xaxis.set_visible(False)\n            ax.yaxis.set_visible(False)\n            self.plots[f] = fig\n\n    def keys(self):\n        return self.plots.keys()\n\n    def values(self):\n        return self.plots.values()\n\n    def items(self):\n        return self.plots.items()\n\n    def __getitem__(self, key):\n        for k in self.keys():\n            if k[1] == key:\n                return self.plots[k]\n\n    def show(self):\n        return self\n\n    def save(self, name=None, mpl_kwargs=None):\n        if mpl_kwargs is None:\n            mpl_kwargs = {}\n        mpl_kwargs[\"bbox_inches\"] = \"tight\"\n        self.pw.save(name=name, mpl_kwargs=mpl_kwargs)\n\n    def _repr_html_(self):\n        from matplotlib.backends.backend_agg import FigureCanvasAgg\n\n        ret = \"\"\n        for v in self.plots.values():\n            canvas = FigureCanvasAgg(v)\n            f = BytesIO()\n            canvas.print_figure(f)\n            f.seek(0)\n            img = base64.b64encode(f.read()).decode()\n            ret += (\n                r'<img style=\"max-width:100%%;max-height:100%%;\" '\n                rf'src=\"data:image/png;base64,{img}\"><br>'\n            )\n        return ret\n"
  },
  {
    "path": "yt/frontends/fits/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/fits/tests/test_outputs.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.testing import requires_file, requires_module, units_override_check\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\nfrom ..data_structures import (\n    EventsFITSDataset,\n    FITSDataset,\n    SkyDataFITSDataset,\n    SpectralCubeFITSDataset,\n)\n\n_fields_grs = ((\"fits\", \"temperature\"),)\n\ngrs = \"radio_fits/grs-50-cube.fits\"\n\n\n@requires_ds(grs)\ndef test_grs():\n    ds = data_dir_load(grs, cls=SpectralCubeFITSDataset, kwargs={\"nan_mask\": 0.0})\n    assert_equal(str(ds), \"grs-50-cube.fits\")\n    for test in small_patch_amr(ds, _fields_grs, input_center=\"c\", input_weight=\"ones\"):\n        test_grs.__name__ = test.description\n        yield test\n\n\n_fields_vels = ((\"fits\", \"velocity_x\"), (\"fits\", \"velocity_y\"), (\"fits\", \"velocity_z\"))\n\nvf = \"UnigridData/velocity_field_20.fits\"\n\n\n@requires_module(\"astropy\")\n@requires_ds(vf)\ndef test_velocity_field():\n    ds = data_dir_load(vf, cls=FITSDataset)\n    assert_equal(str(ds), \"velocity_field_20.fits\")\n    for test in small_patch_amr(\n        ds, _fields_vels, input_center=\"c\", input_weight=\"ones\"\n    ):\n        test_velocity_field.__name__ = test.description\n        yield test\n\n\nacis = \"xray_fits/acisf05356N003_evt2.fits.gz\"\n\n_fields_acis = ((\"gas\", \"counts_0.1-2.0\"), (\"gas\", \"counts_2.0-5.0\"))\n\n\n@requires_ds(acis)\ndef test_acis():\n    from yt.frontends.fits.misc import setup_counts_fields\n\n    ds = data_dir_load(acis, cls=EventsFITSDataset)\n    ebounds = [(0.1, 2.0), (2.0, 5.0)]\n    setup_counts_fields(ds, ebounds)\n    assert_equal(str(ds), \"acisf05356N003_evt2.fits.gz\")\n    for test in small_patch_amr(\n        ds, _fields_acis, input_center=\"c\", input_weight=\"ones\"\n    ):\n        test_acis.__name__ = test.description\n        yield test\n\n\nA2052 = \"xray_fits/A2052_merged_0.3-2_match-core_tmap_bgecorr.fits\"\n\n_fields_A2052 = ((\"fits\", \"flux\"),)\n\n\n@requires_ds(A2052)\ndef test_A2052():\n    ds = data_dir_load(A2052, cls=SkyDataFITSDataset)\n    assert_equal(str(ds), \"A2052_merged_0.3-2_match-core_tmap_bgecorr.fits\")\n    for test in small_patch_amr(\n        ds, _fields_A2052, input_center=\"c\", input_weight=\"ones\"\n    ):\n        test_A2052.__name__ = test.description\n        yield test\n\n\n@requires_file(vf)\ndef test_units_override():\n    units_override_check(vf)\n\n\n@requires_file(vf)\ndef test_FITSDataset():\n    assert isinstance(data_dir_load(vf), FITSDataset)\n\n\n@requires_file(grs)\ndef test_SpectralCubeFITSDataset():\n    assert isinstance(data_dir_load(grs), SpectralCubeFITSDataset)\n\n\n@requires_file(acis)\ndef test_EventsFITSDataset():\n    assert isinstance(data_dir_load(acis), EventsFITSDataset)\n\n\n@requires_file(A2052)\ndef test_SkyDataFITSDataset():\n    assert isinstance(data_dir_load(A2052), SkyDataFITSDataset)\n"
  },
  {
    "path": "yt/frontends/flash/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/flash/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    FLASHDataset,\n    FLASHGrid,\n    FLASHHierarchy,\n    FLASHParticleDataset,\n)\nfrom .fields import FLASHFieldInfo\nfrom .io import IOHandlerFLASH, IOHandlerFLASHParticle\n"
  },
  {
    "path": "yt/frontends/flash/data_structures.py",
    "content": "import os\nimport weakref\nfrom pathlib import Path\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset, ParticleFile\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.geometry_handler import Index\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.utilities.file_handler import HDF5FileHandler, valid_hdf5_signature\nfrom yt.utilities.physical_ratios import cm_per_mpc\n\nfrom .fields import FLASHFieldInfo\n\n\nclass FLASHGrid(AMRGridPatch):\n    _id_offset = 1\n    # __slots__ = [\"_level_id\", \"stop_index\"]\n\n    def __init__(self, id, index, level):\n        AMRGridPatch.__init__(self, id, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n\n\nclass FLASHHierarchy(GridIndex):\n    grid = FLASHGrid\n    _preload_implemented = True\n\n    def __init__(self, ds, dataset_type=\"flash_hdf5\"):\n        self.dataset_type = dataset_type\n        self.field_indexes = {}\n        self.dataset = weakref.proxy(ds)\n        # for now, the index file is the dataset!\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        self._handle = ds._handle\n        self._particle_handle = ds._particle_handle\n        self.float_type = np.float64\n        GridIndex.__init__(self, ds, dataset_type)\n\n    def _initialize_data_storage(self):\n        pass\n\n    def _detect_output_fields(self):\n        self.field_list = [\n            (\"flash\", s.decode(\"ascii\", \"ignore\"))\n            for s in self._handle[\"/unknown names\"][:].flat\n        ]\n        if \"/particle names\" in self._particle_handle:\n            self.field_list += [\n                (\"io\", \"particle_\" + s[0].decode(\"ascii\", \"ignore\").strip())\n                for s in self._particle_handle[\"/particle names\"][:]\n            ]\n\n    def _count_grids(self):\n        try:\n            self.num_grids = self.dataset._find_parameter(\n                \"integer\", \"globalnumblocks\", True\n            )\n        except KeyError:\n            try:\n                self.num_grids = self._handle[\"simulation parameters\"][\"total blocks\"][\n                    0\n                ]\n            except KeyError:\n                self.num_grids = self._handle[\"/simulation parameters\"][0][0]\n\n    def _parse_index(self):\n        f = self._handle  # shortcut\n        ds = self.dataset  # shortcut\n        f_part = self._particle_handle  # shortcut\n\n        # Initialize to the domain left / domain right\n        ND = self.dataset.dimensionality\n        DLE = self.dataset.domain_left_edge\n        DRE = self.dataset.domain_right_edge\n        for i in range(3):\n            self.grid_left_edge[:, i] = DLE[i]\n            self.grid_right_edge[:, i] = DRE[i]\n        # We only go up to ND for 2D datasets\n        self.grid_left_edge[:, :ND] = f[\"/bounding box\"][:, :ND, 0]\n        self.grid_right_edge[:, :ND] = f[\"/bounding box\"][:, :ND, 1]\n        # Move this to the parameter file\n        try:\n            nxb = ds.parameters[\"nxb\"]\n            nyb = ds.parameters[\"nyb\"]\n            nzb = ds.parameters[\"nzb\"]\n        except KeyError:\n            nxb, nyb, nzb = (\n                int(f[\"/simulation parameters\"][f\"n{ax}b\"]) for ax in \"xyz\"\n            )\n        self.grid_dimensions[:] *= (nxb, nyb, nzb)\n        try:\n            self.grid_particle_count[:] = f_part[\"/localnp\"][:][:, None]\n            self._blockless_particle_count = (\n                f_part[\"/tracer particles\"].shape[0] - self.grid_particle_count.sum()\n            )\n        except KeyError:\n            self.grid_particle_count[:] = 0.0\n        self._particle_indices = np.zeros(self.num_grids + 1, dtype=\"int64\")\n        if self.num_grids > 1:\n            np.add.accumulate(\n                self.grid_particle_count.squeeze(), out=self._particle_indices[1:]\n            )\n        else:\n            self._particle_indices[1] = self.grid_particle_count.squeeze()\n        # This will become redundant, as _prepare_grid will reset it to its\n        # current value.  Note that FLASH uses 1-based indexing for refinement\n        # levels, but we do not, so we reduce the level by 1.\n        self.grid_levels.flat[:] = f[\"/refine level\"][:][:] - 1\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n        for i in range(self.num_grids):\n            self.grids[i] = self.grid(i + 1, self, self.grid_levels[i, 0])\n\n        # This is a possibly slow and verbose fix, and should be re-examined!\n        rdx = self.dataset.domain_width / self.dataset.domain_dimensions\n        nlevels = self.grid_levels.max()\n        dxs = np.ones((nlevels + 1, 3), dtype=\"float64\")\n        for i in range(nlevels + 1):\n            dxs[i, :ND] = rdx[:ND] / self.dataset.refine_by**i\n\n        if ND < 3:\n            dxs[:, ND:] = rdx[ND:]\n\n        # Because we don't care about units, we're going to operate on views.\n        gle = self.grid_left_edge.ndarray_view()\n        gre = self.grid_right_edge.ndarray_view()\n        geom = self.dataset.geometry\n        if geom != \"cartesian\" and ND < 3:\n            if geom == \"spherical\" and ND < 2:\n                gle[:, 1] = 0.0\n                gre[:, 1] = np.pi\n            gle[:, 2] = 0.0\n            gre[:, 2] = 2.0 * np.pi\n            return\n\n    def _populate_grid_objects(self):\n        ii = np.argsort(self.grid_levels.flat)\n        gid = self._handle[\"/gid\"][:]\n        first_ind = -(self.dataset.refine_by**self.dataset.dimensionality)\n        for g in self.grids[ii].flat:\n            gi = g.id - g._id_offset\n            # FLASH uses 1-indexed group info\n            g.Children = [self.grids[i - 1] for i in gid[gi, first_ind:] if i > -1]\n            for g1 in g.Children:\n                g1.Parent = g\n            g._prepare_grid()\n            g._setup_dx()\n        if self.dataset.dimensionality < 3:\n            DD = self.dataset.domain_right_edge[2] - self.dataset.domain_left_edge[2]\n            for g in self.grids:\n                g.dds[2] = DD\n        if self.dataset.dimensionality < 2:\n            DD = self.dataset.domain_right_edge[1] - self.dataset.domain_left_edge[1]\n            for g in self.grids:\n                g.dds[1] = DD\n        self.max_level = self.grid_levels.max()\n\n\nclass FLASHDataset(Dataset):\n    _load_requirements = [\"h5py\"]\n    _index_class: type[Index] = FLASHHierarchy\n    _field_info_class = FLASHFieldInfo\n    _handle = None\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"flash_hdf5\",\n        storage_filename=None,\n        particle_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        self.fluid_types += (\"flash\",)\n        if self._handle is not None:\n            return\n        self._handle = HDF5FileHandler(filename)\n\n        self.particle_filename = particle_filename\n\n        filepath = Path(filename)\n\n        if self.particle_filename is None:\n            # try to guess the particle filename\n            if \"hdf5_plt_cnt\" in filepath.name:\n                # We have a plotfile, look for the particle file\n                try:\n                    pfn = str(\n                        filepath.parent.resolve()\n                        / filepath.name.replace(\"plt_cnt\", \"part\")\n                    )\n                    self._particle_handle = HDF5FileHandler(pfn)\n                    self.particle_filename = pfn\n                    mylog.info(\n                        \"Particle file found: %s\",\n                        os.path.basename(self.particle_filename),\n                    )\n                except OSError:\n                    self._particle_handle = self._handle\n            elif \"hdf5_chk\" in filepath.name:\n                # This is a checkpoint file, should have the particles in it\n                self._particle_handle = self._handle\n        else:\n            # particle_filename is specified by user\n            self._particle_handle = HDF5FileHandler(self.particle_filename)\n\n        # Check if the particle file has the same time\n        if self._particle_handle != self._handle:\n            plot_time = self._handle.handle.get(\"real scalars\")\n            if (part_time := self._particle_handle.handle.get(\"real scalars\")) is None:\n                raise RuntimeError(\"FLASH 2.x particle files are not supported!\")\n            if not np.isclose(part_time[0][1], plot_time[0][1]):\n                self._particle_handle = self._handle\n                mylog.warning(\n                    \"%s and %s are not at the same time. \"\n                    \"This particle file will not be used.\",\n                    self.particle_filename,\n                    filename,\n                )\n\n        # These should be explicitly obtained from the file, but for now that\n        # will wait until a reorganization of the source tree and better\n        # generalization.\n        self.refine_by = 2\n\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        self.storage_filename = storage_filename\n\n        self.parameters[\"HydroMethod\"] = \"flash\"  # always PPM DE\n        self.parameters[\"Time\"] = 1.0  # default unit is 1...\n\n    def _set_code_unit_attributes(self):\n        if \"unitsystem\" in self.parameters:\n            # Some versions of FLASH inject quotes in the runtime parameters\n            # See issue #1721\n            us = self[\"unitsystem\"].replace(\"'\", \"\").replace('\"', \"\").lower()\n            if us == \"cgs\":\n                b_factor = 1.0\n            elif us == \"si\":\n                b_factor = np.sqrt(4 * np.pi / 1e7)\n            elif us == \"none\":\n                b_factor = np.sqrt(4 * np.pi)\n            else:\n                raise RuntimeError(\n                    \"Runtime parameter unitsystem with \"\n                    f\"value {self['unitsystem']} is unrecognized\"\n                )\n        else:\n            b_factor = 1.0\n        if self.cosmological_simulation == 1:\n            length_factor = 1.0 / (1.0 + self.current_redshift)\n            temperature_factor = 1.0 / (1.0 + self.current_redshift) ** 2\n        else:\n            length_factor = 1.0\n            temperature_factor = 1.0\n\n        setdefaultattr(self, \"magnetic_unit\", self.quan(b_factor, \"gauss\"))\n        setdefaultattr(self, \"length_unit\", self.quan(length_factor, \"cm\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"g\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n        setdefaultattr(self, \"velocity_unit\", self.quan(1.0, \"cm/s\"))\n        setdefaultattr(self, \"temperature_unit\", self.quan(temperature_factor, \"K\"))\n\n    def set_code_units(self):\n        super().set_code_units()\n\n    def _find_parameter(self, ptype, pname, scalar=False):\n        nn = \"/{} {}\".format(\n            ptype, {False: \"runtime parameters\", True: \"scalars\"}[scalar]\n        )\n        if nn not in self._handle:\n            raise KeyError(nn)\n        for tpname, pval in zip(\n            self._handle[nn][:, \"name\"],\n            self._handle[nn][:, \"value\"],\n            strict=True,\n        ):\n            if tpname.decode(\"ascii\", \"ignore\").strip() == pname:\n                if hasattr(pval, \"decode\"):\n                    pval = pval.decode(\"ascii\", \"ignore\")\n                if ptype == \"string\":\n                    return pval.strip()\n                else:\n                    return pval\n        raise KeyError(pname)\n\n    def _parse_parameter_file(self):\n        if \"file format version\" in self._handle:\n            self._flash_version = self._handle[\"file format version\"][:].item()\n        elif \"sim info\" in self._handle:\n            self._flash_version = self._handle[\"sim info\"][:][\n                \"file format version\"\n            ].item()\n        else:\n            raise RuntimeError(\"Can't figure out FLASH file version.\")\n        # First we load all of the parameters\n        hns = [\"simulation parameters\"]\n        # note the ordering here is important: runtime parameters should\n        # overwrite scalars with the same name.\n        for ptype in [\"scalars\", \"runtime parameters\"]:\n            for vtype in [\"integer\", \"real\", \"logical\", \"string\"]:\n                hns.append(f\"{vtype} {ptype}\")\n        if self._flash_version > 7:\n            for hn in hns:\n                if hn not in self._handle:\n                    continue\n                for varname, val in zip(\n                    self._handle[hn][:, \"name\"],\n                    self._handle[hn][:, \"value\"],\n                    strict=True,\n                ):\n                    vn = varname.strip()\n                    if hn.startswith(\"string\"):\n                        pval = val.strip()\n                    else:\n                        pval = val\n                    if vn in self.parameters and self.parameters[vn] != pval:\n                        mylog.info(\n                            \"%s %s overwrites a simulation scalar of the same name\",\n                            hn[:-1],\n                            vn,\n                        )\n                    if hasattr(pval, \"decode\"):\n                        pval = pval.decode(\"ascii\", \"ignore\")\n                    self.parameters[vn.decode(\"ascii\", \"ignore\")] = pval\n        if self._flash_version == 7:\n            for hn in hns:\n                if hn not in self._handle:\n                    continue\n                if hn == \"simulation parameters\":\n                    zipover = (\n                        (name, self._handle[hn][name][0])\n                        for name in self._handle[hn].dtype.names\n                    )\n                else:\n                    zipover = zip(\n                        self._handle[hn][:, \"name\"],\n                        self._handle[hn][:, \"value\"],\n                        strict=True,\n                    )\n                for varname, val in zipover:\n                    vn = varname.strip()\n                    if hasattr(vn, \"decode\"):\n                        vn = vn.decode(\"ascii\", \"ignore\")\n                    if hn.startswith(\"string\"):\n                        pval = val.strip()\n                    else:\n                        pval = val\n                    if vn in self.parameters and self.parameters[vn] != pval:\n                        mylog.info(\n                            \"%s %s overwrites a simulation scalar of the same name\",\n                            hn[:-1],\n                            vn,\n                        )\n                    if hasattr(pval, \"decode\"):\n                        pval = pval.decode(\"ascii\", \"ignore\")\n                    self.parameters[vn] = pval\n\n        # Determine block size\n        try:\n            nxb = self.parameters[\"nxb\"]\n            nyb = self.parameters[\"nyb\"]\n            nzb = self.parameters[\"nzb\"]\n        except KeyError:\n            nxb, nyb, nzb = (\n                int(self._handle[\"/simulation parameters\"][f\"n{ax}b\"]) for ax in \"xyz\"\n            )  # FLASH2 only!\n\n        # Determine dimensionality\n        try:\n            dimensionality = self.parameters[\"dimensionality\"]\n        except KeyError:\n            dimensionality = 3\n            if nzb == 1:\n                dimensionality = 2\n            if nyb == 1:\n                dimensionality = 1\n            if dimensionality < 3:\n                mylog.warning(\"Guessing dimensionality as %s\", dimensionality)\n\n        self.dimensionality = dimensionality\n\n        self.geometry = Geometry(self.parameters[\"geometry\"])\n        # Determine base grid parameters\n        if \"lrefine_min\" in self.parameters.keys():  # PARAMESH\n            nblockx = self.parameters[\"nblockx\"]\n            nblocky = self.parameters[\"nblocky\"]\n            nblockz = self.parameters[\"nblockz\"]\n        elif self.parameters[\"globalnumblocks\"] == 1:  # non-fixed block size UG\n            nblockx = 1\n            nblocky = 1\n            nblockz = 1\n        else:  # Uniform Grid\n            nblockx = self.parameters[\"iprocs\"]\n            nblocky = self.parameters[\"jprocs\"]\n            nblockz = self.parameters[\"kprocs\"]\n\n        # In case the user wasn't careful\n        if self.dimensionality <= 2:\n            nblockz = 1\n        if self.dimensionality == 1:\n            nblocky = 1\n\n        # Determine domain boundaries\n        dle = np.array([self.parameters[f\"{ax}min\"] for ax in \"xyz\"]).astype(\"float64\")\n        dre = np.array([self.parameters[f\"{ax}max\"] for ax in \"xyz\"]).astype(\"float64\")\n        if self.dimensionality < 3:\n            for d in range(self.dimensionality, 3):\n                if dle[d] == dre[d]:\n                    mylog.warning(\n                        \"Identical domain left edge and right edges \"\n                        \"along dummy dimension (%i), attempting to read anyway\",\n                        d,\n                    )\n                    dre[d] = dle[d] + 1.0\n\n        if self.dimensionality < 3:\n            match self.geometry:\n                case Geometry.CYLINDRICAL:\n                    mylog.warning(\"Extending theta dimension to 2PI + left edge.\")\n                    dre[2] = dle[2] + 2 * np.pi\n                case Geometry.POLAR:\n                    mylog.warning(\"Extending theta dimension to 2PI + left edge.\")\n                    dre[1] = dle[1] + 2 * np.pi\n                case Geometry.SPHERICAL:\n                    mylog.warning(\"Extending phi dimension to 2PI + left edge.\")\n                    dre[2] = dle[2] + 2 * np.pi\n                    if self.dimensionality == 1:\n                        mylog.warning(\"Extending theta dimension to PI + left edge.\")\n                        dre[1] = dle[1] + np.pi\n\n        self.domain_left_edge = dle\n        self.domain_right_edge = dre\n        self.domain_dimensions = np.array([nblockx * nxb, nblocky * nyb, nblockz * nzb])\n\n        # Try to determine Gamma\n        try:\n            self.gamma = self.parameters[\"gamma\"]\n        except Exception:\n            mylog.info(\"Cannot find Gamma\")\n            pass\n\n        # Get the simulation time\n        self.current_time = self.parameters[\"time\"]\n\n        # Determine if this is a periodic box\n        p = [\n            self.parameters.get(f\"{ax}l_boundary_type\", None) == \"periodic\"\n            for ax in \"xyz\"\n        ]\n        self._periodicity = tuple(p)\n\n        # Determine cosmological parameters.\n        try:\n            self.parameters[\"usecosmology\"]\n            self.cosmological_simulation = 1\n            self.current_redshift = 1.0 / self.parameters[\"scalefactor\"] - 1.0\n            self.omega_lambda = self.parameters[\"cosmologicalconstant\"]\n            self.omega_matter = self.parameters[\"omegamatter\"]\n            self.hubble_constant = self.parameters[\"hubbleconstant\"]\n            self.hubble_constant *= cm_per_mpc * 1.0e-5 * 1.0e-2  # convert to 'h'\n        except Exception:\n            self.current_redshift = 0.0\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0\n            self.hubble_constant = 0.0\n            self.cosmological_simulation = 0\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            fileh = HDF5FileHandler(filename)\n            if \"bounding box\" in fileh[\"/\"].keys():\n                return True\n        except OSError:\n            pass\n        return False\n\n    @classmethod\n    def _guess_candidates(cls, base, directories, files):\n        candidates = [\n            _ for _ in files if (\"_hdf5_plt_cnt_\" in _) or (\"_hdf5_chk_\" in _)\n        ]\n        # Typically, Flash won't have nested outputs.\n        return candidates, (len(candidates) == 0)\n\n    def close(self):\n        self._handle.close()\n\n\nclass FLASHParticleFile(ParticleFile):\n    pass\n\n\nclass FLASHParticleDataset(FLASHDataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = ParticleIndex\n    filter_bbox = False\n    _file_class = FLASHParticleFile\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"flash_particle_hdf5\",\n        storage_filename=None,\n        units_override=None,\n        index_order=None,\n        index_filename=None,\n        unit_system=\"cgs\",\n    ):\n        self.index_order = index_order\n        self.index_filename = index_filename\n\n        if self._handle is not None:\n            return\n        self._handle = HDF5FileHandler(filename)\n        self.refine_by = 2\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n        self.storage_filename = storage_filename\n\n    def _parse_parameter_file(self):\n        # Let the superclass do all the work but then\n        # fix the domain dimensions\n        super()._parse_parameter_file()\n        domain_dimensions = np.zeros(3, \"int32\")\n        domain_dimensions[: self.dimensionality] = 1\n        self.domain_dimensions = domain_dimensions\n        self.filename_template = self.parameter_filename\n        self.file_count = 1\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not valid_hdf5_signature(filename):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            fileh = HDF5FileHandler(filename)\n            if (\n                \"bounding box\" not in fileh[\"/\"].keys()\n                and \"localnp\" in fileh[\"/\"].keys()\n            ):\n                return True\n        except OSError:\n            pass\n        return False\n\n    @classmethod\n    def _guess_candidates(cls, base, directories, files):\n        candidates = [_ for _ in files if \"_hdf5_part_\" in _]\n        # Typically, Flash won't have nested outputs.\n        return candidates, (len(candidates) == 0)\n"
  },
  {
    "path": "yt/frontends/flash/definitions.py",
    "content": ""
  },
  {
    "path": "yt/frontends/flash/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\n# Common fields in FLASH: (Thanks to John ZuHone for this list)\n#\n# dens gas mass density (g/cc) --\n# eint internal energy (ergs/g) --\n# ener total energy (ergs/g), with 0.5*v^2 --\n# gamc gamma defined as ratio of specific heats, no units\n# game gamma defined as in , no units\n# gpol gravitational potential from the last timestep (ergs/g)\n# gpot gravitational potential from the current timestep (ergs/g)\n# grac gravitational acceleration from the current timestep (cm s^-2)\n# pden particle mass density (usually dark matter) (g/cc)\n# pres pressure (erg/cc)\n# temp temperature (K) --\n# velx velocity x (cm/s) --\n# vely velocity y (cm/s) --\n# velz velocity z (cm/s) --\n\nb_units = \"code_magnetic\"\npres_units = \"code_mass/(code_length*code_time**2)\"\nen_units = \"code_mass * (code_length/code_time)**2\"\nrho_units = \"code_mass / code_length**3\"\n\n\nclass FLASHFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"velx\", (\"code_length/code_time\", [\"velocity_x\"], None)),\n        (\"vely\", (\"code_length/code_time\", [\"velocity_y\"], None)),\n        (\"velz\", (\"code_length/code_time\", [\"velocity_z\"], None)),\n        (\"dens\", (\"code_mass/code_length**3\", [\"density\"], None)),\n        (\"temp\", (\"code_temperature\", [\"temperature\"], None)),\n        (\"pres\", (pres_units, [\"pressure\"], None)),\n        (\"gpot\", (\"code_length**2/code_time**2\", [\"gravitational_potential\"], None)),\n        (\"gpol\", (\"code_length**2/code_time**2\", [], None)),\n        (\"tion\", (\"code_temperature\", [], None)),\n        (\"tele\", (\"code_temperature\", [], None)),\n        (\"trad\", (\"code_temperature\", [], None)),\n        (\"pion\", (pres_units, [], None)),\n        (\"pele\", (pres_units, [], \"Electron Pressure, P_e\")),\n        (\"prad\", (pres_units, [], \"Radiation Pressure\")),\n        (\"eion\", (en_units, [], \"Ion Internal Specific Energy\")),\n        (\"eele\", (en_units, [], \"Electron Internal Specific Energy\")),\n        (\"erad\", (en_units, [], \"Radiation Internal Specific Energy\")),\n        (\"pden\", (rho_units, [], \"Particle Mass Density\")),\n        (\"depo\", (\"code_length**2/code_time**2\", [], None)),\n        (\"ye\", (\"\", [], \"Y_e\")),\n        (\"magp\", (pres_units, [], None)),\n        (\"divb\", (\"code_magnetic/code_length\", [], None)),\n        (\"game\", (\"\", [], r\"\\gamma_e\\ \\rm{(ratio\\ of\\ specific\\ heats)}\")),\n        (\"gamc\", (\"\", [], r\"\\gamma_c\\ \\rm{(ratio\\ of\\ specific\\ heats)}\")),\n        (\"flam\", (\"\", [], None)),\n        (\"absr\", (\"\", [], \"Absorption Coefficient\")),\n        (\"emis\", (\"\", [], \"Emissivity\")),\n        (\"cond\", (\"\", [], \"Conductivity\")),\n        (\"dfcf\", (\"\", [], \"Diffusion Equation Scalar\")),\n        (\"fllm\", (\"\", [], \"Flux Limit\")),\n        (\"pipe\", (\"\", [], \"P_i/P_e\")),\n        (\"tite\", (\"\", [], \"T_i/T_e\")),\n        (\"dbgs\", (\"\", [], \"Debug for Shocks\")),\n        (\"cham\", (\"\", [], \"Chamber Material Fraction\")),\n        (\"targ\", (\"\", [], \"Target Material Fraction\")),\n        (\"sumy\", (\"\", [], None)),\n        (\"mgdc\", (\"\", [], \"Emission Minus Absorption Diffusion Terms\")),\n        (\"magx\", (b_units, [], \"B_x\")),\n        (\"magy\", (b_units, [], \"B_y\")),\n        (\"magz\", (b_units, [], \"B_z\")),\n    )\n\n    known_particle_fields = (\n        (\"particle_posx\", (\"code_length\", [\"particle_position_x\"], None)),\n        (\"particle_posy\", (\"code_length\", [\"particle_position_y\"], None)),\n        (\"particle_posz\", (\"code_length\", [\"particle_position_z\"], None)),\n        (\"particle_velx\", (\"code_length/code_time\", [\"particle_velocity_x\"], None)),\n        (\"particle_vely\", (\"code_length/code_time\", [\"particle_velocity_y\"], None)),\n        (\"particle_velz\", (\"code_length/code_time\", [\"particle_velocity_z\"], None)),\n        (\"particle_tag\", (\"\", [\"particle_index\"], None)),\n        (\"particle_mass\", (\"code_mass\", [\"particle_mass\"], None)),\n        (\n            \"particle_gpot\",\n            (\"code_length**2/code_time**2\", [\"particle_gravitational_potential\"], None),\n        ),\n    )\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        unit_system = self.ds.unit_system\n        # Adopt FLASH 4.6 value for Na\n        Na = self.ds.quan(6.022140857e23, \"g**-1\")\n        for i in range(1, 1000):\n            self.add_output_field(\n                (\"flash\", f\"r{i:03}\"),\n                sampling_type=\"cell\",\n                units=\"\",\n                display_name=f\"Energy Group {i}\",\n            )\n\n        # Add energy fields\n        def ekin(data):\n            ek = data[\"flash\", \"velx\"] ** 2\n            if data.ds.dimensionality >= 2:\n                ek += data[\"flash\", \"vely\"] ** 2\n            if data.ds.dimensionality == 3:\n                ek += data[\"flash\", \"velz\"] ** 2\n            return 0.5 * ek\n\n        if (\"flash\", \"ener\") in self.field_list:\n            self.add_output_field(\n                (\"flash\", \"ener\"),\n                sampling_type=\"cell\",\n                units=\"code_length**2/code_time**2\",\n            )\n            self.alias(\n                (\"gas\", \"specific_total_energy\"),\n                (\"flash\", \"ener\"),\n                units=unit_system[\"specific_energy\"],\n            )\n\n        else:\n\n            def _ener(data):\n                ener = data[\"flash\", \"eint\"] + ekin(data)\n                try:\n                    ener += data[\"flash\", \"magp\"] / data[\"flash\", \"dens\"]\n                except Exception:\n                    pass\n                return ener\n\n            self.add_field(\n                (\"gas\", \"specific_total_energy\"),\n                sampling_type=\"cell\",\n                function=_ener,\n                units=unit_system[\"specific_energy\"],\n            )\n        if (\"flash\", \"eint\") in self.field_list:\n            self.add_output_field(\n                (\"flash\", \"eint\"),\n                sampling_type=\"cell\",\n                units=\"code_length**2/code_time**2\",\n            )\n            self.alias(\n                (\"gas\", \"specific_thermal_energy\"),\n                (\"flash\", \"eint\"),\n                units=unit_system[\"specific_energy\"],\n            )\n        else:\n\n            def _eint(data):\n                eint = data[\"flash\", \"ener\"] - ekin(data)\n                try:\n                    eint -= data[\"flash\", \"magp\"] / data[\"flash\", \"dens\"]\n                except Exception:\n                    pass\n                return eint\n\n            self.add_field(\n                (\"gas\", \"specific_thermal_energy\"),\n                sampling_type=\"cell\",\n                function=_eint,\n                units=unit_system[\"specific_energy\"],\n            )\n\n        ## Derived FLASH Fields\n\n        if (\"flash\", \"abar\") in self.field_list:\n            self.alias((\"gas\", \"mean_molecular_weight\"), (\"flash\", \"abar\"))\n        elif (\"flash\", \"sumy\") in self.field_list:\n\n            def _abar(data):\n                return 1.0 / data[\"flash\", \"sumy\"]\n\n            self.add_field(\n                (\"gas\", \"mean_molecular_weight\"),\n                sampling_type=\"cell\",\n                function=_abar,\n                units=\"\",\n            )\n        elif \"eos_singlespeciesa\" in self.ds.parameters:\n\n            def _abar(data):\n                return data.ds.parameters[\"eos_singlespeciesa\"] * data[\"index\", \"ones\"]\n\n            self.add_field(\n                (\"gas\", \"mean_molecular_weight\"),\n                sampling_type=\"cell\",\n                function=_abar,\n                units=\"\",\n            )\n\n        if (\"flash\", \"sumy\") in self.field_list:\n\n            def _nele(data):\n                return data[\"flash\", \"dens\"] * data[\"flash\", \"ye\"] * Na\n\n            self.add_field(\n                (\"gas\", \"El_number_density\"),\n                sampling_type=\"cell\",\n                function=_nele,\n                units=unit_system[\"number_density\"],\n            )\n\n            def _nion(data):\n                return data[\"flash\", \"dens\"] * data[\"flash\", \"sumy\"] * Na\n\n            self.add_field(\n                (\"gas\", \"ion_number_density\"),\n                sampling_type=\"cell\",\n                function=_nion,\n                units=unit_system[\"number_density\"],\n            )\n\n            def _number_density(data):\n                return (\n                    data[\"gas\", \"El_number_density\"] + data[\"gas\", \"ion_number_density\"]\n                )\n\n        else:\n\n            def _number_density(data):\n                return data[\"flash\", \"dens\"] * Na / data[\"gas\", \"mean_molecular_weight\"]\n\n        self.add_field(\n            (\"gas\", \"number_density\"),\n            sampling_type=\"cell\",\n            function=_number_density,\n            units=unit_system[\"number_density\"],\n        )\n\n        setup_magnetic_field_aliases(self, \"flash\", [f\"mag{ax}\" for ax in \"xyz\"])\n"
  },
  {
    "path": "yt/frontends/flash/io.py",
    "content": "from functools import cached_property\nfrom itertools import groupby\n\nimport numpy as np\n\nfrom yt.geometry.selection_routines import AlwaysSelector\nfrom yt.utilities.io_handler import BaseIOHandler\n\n\n# http://stackoverflow.com/questions/2361945/detecting-consecutive-integers-in-a-list\ndef particle_sequences(grids):\n    g_iter = sorted(grids, key=lambda g: g.id)\n    for _k, g in groupby(enumerate(g_iter), lambda i_x: i_x[0] - i_x[1].id):\n        seq = [v[1] for v in g]\n        yield seq[0], seq[-1]\n\n\ndef grid_sequences(grids):\n    g_iter = sorted(grids, key=lambda g: g.id)\n    for _k, g in groupby(enumerate(g_iter), lambda i_x1: i_x1[0] - i_x1[1].id):\n        seq = [v[1] for v in g]\n        yield seq\n\n\ndef determine_particle_fields(handle):\n    try:\n        particle_fields = [\n            s[0].decode(\"ascii\", \"ignore\").strip() for s in handle[\"/particle names\"][:]\n        ]\n        _particle_fields = {\"particle_\" + s: i for i, s in enumerate(particle_fields)}\n    except KeyError:\n        _particle_fields = {}\n    return _particle_fields\n\n\ndef _conditionally_split_arrays(inp_arrays, condition):\n    output_true = []\n    output_false = []\n    for arr in inp_arrays:\n        output_true.append(arr[condition])\n        output_false.append(arr[~condition])\n    return output_true, output_false\n\n\nclass IOHandlerFLASH(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"flash_hdf5\"\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        # Now we cache the particle fields\n        self._handle = ds._handle\n        self._particle_handle = ds._particle_handle\n        self._particle_fields = determine_particle_fields(self._particle_handle)\n\n    def _read_particles(\n        self, fields_to_read, type, args, grid_list, count_list, conv_factors\n    ):\n        pass\n\n    def io_iter(self, chunks, fields):\n        f = self._handle\n        for chunk in chunks:\n            for field in fields:\n                # Note that we *prefer* to iterate over the fields on the\n                # outside; here, though, we're iterating over them on the\n                # inside because we may exhaust our chunks.\n                ftype, fname = field\n                ds = f[f\"/{fname}\"]\n                for gs in grid_sequences(chunk.objs):\n                    start = gs[0].id - gs[0]._id_offset\n                    end = gs[-1].id - gs[-1]._id_offset + 1\n                    data = ds[start:end, :, :, :]\n                    for i, g in enumerate(gs):\n                        yield field, g, self._read_obj_field(g, field, (data, i))\n\n    def _read_particle_coords(self, chunks, ptf):\n        chunks = list(chunks)\n        f_part = self._particle_handle\n        p_ind = self.ds.index._particle_indices\n        px, py, pz = (self._particle_fields[f\"particle_pos{ax}\"] for ax in \"xyz\")\n        pblk = self._particle_fields[\"particle_blk\"]\n        blockless_buffer = self.ds.index._blockless_particle_count\n        p_fields = f_part[\"/tracer particles\"]\n        assert len(ptf) == 1\n        ptype = list(ptf.keys())[0]\n        bxyz = []\n        # We need to track all the particles that don't have blocks and make\n        # sure they get yielded too.  But, we also want our particles to largely\n        # line up with the grids they are resident in.  So we keep a buffer of\n        # particles.  That buffer is checked for any \"blockless\" particles,\n        # which get yielded at the end.\n        for chunk in chunks:\n            start = end = None\n            for g1, g2 in particle_sequences(chunk.objs):\n                start = p_ind[g1.id - g1._id_offset]\n                end_nobuff = p_ind[g2.id - g2._id_offset + 1]\n                end = end_nobuff + blockless_buffer\n                maxlen = end_nobuff - start\n                blk = np.asarray(p_fields[start:end, pblk], dtype=\"=i8\") >= 0\n                # Can we use something like np.choose here?\n                xyz = [\n                    np.asarray(p_fields[start:end, _], dtype=\"=f8\")\n                    for _ in (px, py, pz)\n                ]\n                (x, y, z), _xyz = _conditionally_split_arrays(xyz, blk)\n                if _xyz[0].size > 0:\n                    bxyz.append(_xyz)\n                yield ptype, (x[:maxlen], y[:maxlen], z[:maxlen]), 0.0\n        if len(bxyz) == 0:\n            return\n        bxyz = np.concatenate(bxyz, axis=-1)\n        yield ptype, (bxyz[0, :], bxyz[1, :], bxyz[2, :]), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        chunks = list(chunks)\n        f_part = self._particle_handle\n        p_ind = self.ds.index._particle_indices\n        px, py, pz = (self._particle_fields[f\"particle_pos{ax}\"] for ax in \"xyz\")\n        pblk = self._particle_fields[\"particle_blk\"]\n        blockless_buffer = self.ds.index._blockless_particle_count\n        p_fields = f_part[\"/tracer particles\"]\n        assert len(ptf) == 1\n        ptype = list(ptf.keys())[0]\n        field_list = ptf[ptype]\n        bxyz = []\n        bfields = {(ptype, field): [] for field in field_list}\n        for chunk in chunks:\n            for g1, g2 in particle_sequences(chunk.objs):\n                start = p_ind[g1.id - g1._id_offset]\n                end_nobuff = p_ind[g2.id - g2._id_offset + 1]\n                end = end_nobuff + blockless_buffer\n                maxlen = end_nobuff - start\n                blk = np.asarray(p_fields[start:end, pblk], dtype=\"=i8\") >= 0\n                xyz = [\n                    np.asarray(p_fields[start:end, _], dtype=\"=f8\")\n                    for _ in (px, py, pz)\n                ]\n                (x, y, z), _xyz = _conditionally_split_arrays(xyz, blk)\n                x, y, z = (_[:maxlen] for _ in (x, y, z))\n                if _xyz[0].size > 0:\n                    bxyz.append(_xyz)\n                mask = selector.select_points(x, y, z, 0.0)\n                blockless = (_xyz[0] > 0).any()\n                # This checks if none of the particles within these blocks are\n                # included in the mask We need to also allow for blockless\n                # particles to be selected.\n                if mask is None and not blockless:\n                    continue\n                for field in field_list:\n                    fi = self._particle_fields[field]\n                    (data,), (bdata,) = _conditionally_split_arrays(\n                        [p_fields[start:end, fi]], blk\n                    )\n                    # Note that we have to apply mask to the split array, since\n                    # we select on the split array.\n                    if mask is not None:\n                        # Be sure to truncate off the buffer length!!\n                        yield (ptype, field), data[:maxlen][mask]\n                    if blockless:\n                        bfields[ptype, field].append(bdata)\n        if len(bxyz) == 0:\n            return\n        bxyz = np.concatenate(bxyz, axis=-1)\n        mask = selector.select_points(bxyz[0, :], bxyz[1, :], bxyz[2, :], 0.0)\n        if mask is None:\n            return\n        for field in field_list:\n            data_field = np.concatenate(bfields[ptype, field], axis=-1)\n            yield (ptype, field), data_field[mask]\n\n    def _read_obj_field(self, obj, field, ds_offset=None):\n        if ds_offset is None:\n            ds_offset = (None, -1)\n        ds, offset = ds_offset\n        # our context here includes datasets and whatnot that are opened in the\n        # hdf5 file\n        if ds is None:\n            ds = self._handle[f\"/{field[1]}\"]\n        if offset == -1:\n            data = ds[obj.id - obj._id_offset, :, :, :].transpose()\n        else:\n            data = ds[offset, :, :, :].transpose()\n        return data.astype(\"=f8\")\n\n    def _read_chunk_data(self, chunk, fields):\n        f = self._handle\n        rv = {}\n        for g in chunk.objs:\n            rv[g.id] = {}\n        # Split into particles and non-particles\n        fluid_fields, particle_fields = [], []\n        for ftype, fname in fields:\n            if ftype in self.ds.particle_types:\n                particle_fields.append((ftype, fname))\n            else:\n                fluid_fields.append((ftype, fname))\n        if len(particle_fields) > 0:\n            selector = AlwaysSelector(self.ds)\n            rv.update(self._read_particle_selection([chunk], selector, particle_fields))\n        if len(fluid_fields) == 0:\n            return rv\n        for field in fluid_fields:\n            ftype, fname = field\n            ds = f[f\"/{fname}\"]\n            for gs in grid_sequences(chunk.objs):\n                start = gs[0].id - gs[0]._id_offset\n                end = gs[-1].id - gs[-1]._id_offset + 1\n                data = ds[start:end, :, :, :].transpose()\n                for i, g in enumerate(gs):\n                    rv[g.id][field] = np.asarray(data[..., i], \"=f8\")\n        return rv\n\n\nclass IOHandlerFLASHParticle(BaseIOHandler):\n    _particle_reader = True\n    _dataset_type = \"flash_particle_hdf5\"\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        # Now we cache the particle fields\n        self._handle = ds._handle\n        self._particle_fields = determine_particle_fields(self._handle)\n        self._position_fields = [\n            self._particle_fields[f\"particle_pos{ax}\"] for ax in \"xyz\"\n        ]\n\n    @property\n    def chunksize(self):\n        return 32**3\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _read_particle_coords(self, chunks, ptf):\n        chunks = list(chunks)\n        data_files = set()\n        assert len(ptf) == 1\n        for chunk in chunks:\n            for obj in chunk.objs:\n                data_files.update(obj.data_files)\n        px, py, pz = self._position_fields\n        p_fields = self._handle[\"/tracer particles\"]\n        for data_file in sorted(data_files, key=lambda x: (x.filename, x.start)):\n            pxyz = np.asarray(\n                p_fields[data_file.start : data_file.end, [px, py, pz]], dtype=\"=f8\"\n            )\n            yield \"io\", pxyz.T, 0.0\n\n    def _yield_coordinates(self, data_file, needed_ptype=None):\n        px, py, pz = self._position_fields\n        p_fields = self._handle[\"/tracer particles\"]\n        pxyz = np.asarray(\n            p_fields[data_file.start : data_file.end, [px, py, pz]], dtype=\"=f8\"\n        )\n        yield (\"io\", pxyz)\n\n    def _read_particle_data_file(self, data_file, ptf, selector=None):\n        px, py, pz = self._position_fields\n        p_fields = self._handle[\"/tracer particles\"]\n        si, ei = data_file.start, data_file.end\n\n        data_return = {}\n        # This should just be a single item\n        for ptype, field_list in sorted(ptf.items()):\n            x = np.asarray(p_fields[si:ei, px], dtype=\"=f8\")\n            y = np.asarray(p_fields[si:ei, py], dtype=\"=f8\")\n            z = np.asarray(p_fields[si:ei, pz], dtype=\"=f8\")\n            if selector:\n                mask = selector.select_points(x, y, z, 0.0)\n            del x, y, z\n            if mask is None:\n                continue\n\n            for field in field_list:\n                fi = self._particle_fields[field]\n                data = p_fields[si:ei, fi]\n                if selector:\n                    data = data[mask]\n                data_return[ptype, field] = data\n\n        return data_return\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        assert len(ptf) == 1\n        yield from super()._read_particle_fields(chunks, ptf, selector)\n\n    @cached_property\n    def _pcount(self):\n        return self._handle[\"/localnp\"][:].sum()\n\n    def _count_particles(self, data_file):\n        si, ei = data_file.start, data_file.end\n        pcount = self._pcount\n        if None not in (si, ei):\n            pcount = np.clip(pcount - si, 0, ei - si)\n        return {\"io\": pcount}\n\n    def _identify_fields(self, data_file):\n        fields = [(\"io\", field) for field in self._particle_fields]\n        return fields, {}\n"
  },
  {
    "path": "yt/frontends/flash/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/flash/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/flash/tests/test_outputs.py",
    "content": "from collections import OrderedDict\n\nimport numpy as np\nfrom numpy.testing import assert_allclose, assert_equal\n\nfrom yt.frontends.flash.api import FLASHDataset, FLASHParticleDataset\nfrom yt.loaders import load\nfrom yt.testing import (\n    ParticleSelectionComparison,\n    disable_dataset_cache,\n    requires_file,\n    requires_module,\n    units_override_check,\n)\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    nbody_answer,\n    requires_ds,\n    small_patch_amr,\n)\n\n_fields = ((\"gas\", \"temperature\"), (\"gas\", \"density\"), (\"gas\", \"velocity_magnitude\"))\n\nsloshing = \"GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0300\"\n\n\n@requires_ds(sloshing, big_data=True)\ndef test_sloshing():\n    ds = data_dir_load(sloshing)\n    assert_equal(str(ds), \"sloshing_low_res_hdf5_plt_cnt_0300\")\n    for test in small_patch_amr(ds, _fields):\n        test_sloshing.__name__ = test.description\n        yield test\n\n\n_fields_2d = ((\"gas\", \"temperature\"), (\"gas\", \"density\"))\n\nwt = \"WindTunnel/windtunnel_4lev_hdf5_plt_cnt_0030\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(wt)\ndef test_wind_tunnel():\n    ds = data_dir_load(wt)\n    assert_equal(str(ds), \"windtunnel_4lev_hdf5_plt_cnt_0030\")\n    for test in small_patch_amr(ds, _fields_2d):\n        test_wind_tunnel.__name__ = test.description\n        yield test\n\n\n@requires_module(\"h5py\")\n@requires_file(wt)\ndef test_FLASHDataset():\n    assert isinstance(data_dir_load(wt), FLASHDataset)\n\n\n@requires_module(\"h5py\")\n@requires_file(sloshing)\ndef test_units_override():\n    units_override_check(sloshing)\n\n\n@disable_dataset_cache\n@requires_module(\"h5py\")\n@requires_file(sloshing)\ndef test_magnetic_units():\n    ds1 = load(sloshing)\n    assert_allclose(ds1.magnetic_unit.value, np.sqrt(4.0 * np.pi))\n    assert str(ds1.magnetic_unit.units) == \"G\"\n    mag_unit1 = ds1.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mag_unit1.value, 1.0)\n    assert str(mag_unit1.units) == \"code_magnetic\"\n    ds2 = load(sloshing, unit_system=\"mks\")\n    assert_allclose(ds2.magnetic_unit.value, np.sqrt(4.0 * np.pi) * 1.0e-4)\n    assert str(ds2.magnetic_unit.units) == \"T\"\n    mag_unit2 = ds2.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mag_unit2.value, 1.0)\n    assert str(mag_unit2.units) == \"code_magnetic\"\n\n\n@requires_module(\"h5py\")\n@requires_file(sloshing)\ndef test_mu():\n    ds = data_dir_load(sloshing)\n    sp = ds.sphere(\"c\", (0.1, \"unitary\"))\n    assert np.all(\n        sp[\"gas\", \"mean_molecular_weight\"] == ds.parameters[\"eos_singlespeciesa\"]\n    )\n\n\nfid_1to3_b1 = \"fiducial_1to3_b1/fiducial_1to3_b1_hdf5_part_0080\"\n\nfid_1to3_b1_fields = OrderedDict(\n    [\n        ((\"all\", \"particle_mass\"), None),\n        ((\"all\", \"particle_ones\"), None),\n        ((\"all\", \"particle_velocity_x\"), (\"all\", \"particle_mass\")),\n        ((\"all\", \"particle_velocity_y\"), (\"all\", \"particle_mass\")),\n        ((\"all\", \"particle_velocity_z\"), (\"all\", \"particle_mass\")),\n    ]\n)\n\n\n@requires_module(\"h5py\")\n@requires_file(fid_1to3_b1)\ndef test_FLASHParticleDataset():\n    assert isinstance(data_dir_load(fid_1to3_b1), FLASHParticleDataset)\n\n\n@requires_module(\"h5py\")\n@requires_file(fid_1to3_b1)\ndef test_FLASHParticleDataset_selection():\n    ds = data_dir_load(fid_1to3_b1)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\ndens_turb_mag = \"DensTurbMag/DensTurbMag_hdf5_plt_cnt_0015\"\n\n\n@requires_module(\"h5py\")\n@requires_file(dens_turb_mag)\ndef test_FLASH25_dataset():\n    ds = data_dir_load(dens_turb_mag)\n    assert_equal(ds.parameters[\"time\"], 751000000000.0)\n    assert_equal(ds.domain_dimensions, np.array([8, 8, 8]))\n    assert_equal(ds.domain_left_edge, ds.arr([-2e18, -2e18, -2e18], \"code_length\"))\n\n    assert_equal(ds.index.num_grids, 73)\n    dd = ds.all_data()\n    dd[\"gas\", \"density\"]\n\n\n@requires_module(\"h5py\")\n@requires_ds(fid_1to3_b1, big_data=True)\ndef test_fid_1to3_b1():\n    ds = data_dir_load(fid_1to3_b1)\n    for test in nbody_answer(\n        ds, \"fiducial_1to3_b1_hdf5_part_0080\", 6684119, fid_1to3_b1_fields\n    ):\n        test_fid_1to3_b1.__name__ = test.description\n        yield test\n\n\nloc_bub_dust = \"LocBub_dust/LocBub_dust_hdf5_plt_cnt_0220\"\n\n\n@requires_module(\"h5py\")\n@requires_file(loc_bub_dust)\ndef test_blockless_particles():\n    ds = data_dir_load(loc_bub_dust)\n    dd = ds.all_data()\n    pos = dd[\"all\", \"particle_position\"]\n    assert_equal(pos.shape, (2239, 3))\n"
  },
  {
    "path": "yt/frontends/gadget/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gadget/api.py",
    "content": "from . import tests\nfrom .data_structures import GadgetDataset, GadgetFieldInfo, GadgetHDF5Dataset\nfrom .io import IOHandlerGadgetBinary, IOHandlerGadgetHDF5\nfrom .simulation_handling import GadgetSimulation\n"
  },
  {
    "path": "yt/frontends/gadget/data_structures.py",
    "content": "import os\nimport struct\n\nimport numpy as np\n\nfrom yt.data_objects.static_output import ParticleFile\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.frontends.sph.data_structures import SPHDataset, SPHParticleIndex\nfrom yt.funcs import only_on_root\nfrom yt.geometry.geometry_handler import Index\nfrom yt.utilities.chemical_formulas import compute_mu\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.fortran_utils import read_record\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .definitions import (\n    SNAP_FORMAT_2_OFFSET,\n    gadget_field_specs,\n    gadget_header_specs,\n    gadget_ptype_specs,\n)\nfrom .fields import GadgetFieldInfo\n\n\ndef _fix_unit_ordering(unit):\n    if isinstance(unit[0], str):\n        unit = unit[1], unit[0]\n    return unit\n\n\ndef _byte_swap_32(x):\n    return struct.unpack(\">I\", struct.pack(\"<I\", x))[0]\n\n\nclass GadgetBinaryHeader:\n    \"\"\"A convenient interface to Gadget binary header.\n\n    This is a helper class to facilitate the main dataset and IO classes.\n    The main usage is through the GadgetDataset._header attribute. It is also\n    used stand-alone in GadgetDataset._is_valid method.\n    \"\"\"\n\n    _placeholder_keys = [\"unused\", \"empty\"]\n\n    def __init__(self, filename, header_spec):\n        self.filename = filename\n        if isinstance(header_spec, str):\n            header_spec = [header_spec]\n        self.spec = [\n            GadgetDataset._setup_binary_spec(hs, gadget_header_specs)\n            for hs in header_spec\n        ]\n\n    @property\n    def float_type(self):\n        \"\"\"Determine whether the floats are single or double precision.\"\"\"\n        gformat, endianswap = self.gadget_format\n        # We do not currently support double precision snapshot format 2\n        if gformat == 2:\n            return \"f4\"\n        else:\n            hvals = self.value\n            np0 = sum(hvals[\"Npart\"])\n            with self.open() as f:\n                f.seek(self.position_offset)\n                # Calculate particle number assuming single precision\n                np1 = struct.unpack(endianswap + \"I\", f.read(4))[0] / (4 * 3)\n            if np1 == np0:\n                return \"f4\"\n            elif np1 == 2 * np0:\n                return \"f8\"\n            else:\n                raise RuntimeError(\n                    \"Gadget snapshot file is likely corrupted! \"\n                    \"Cannot determine float type.\"\n                )\n\n    @property\n    def gadget_format(self):\n        \"\"\"Determine Gadget snapshot format and endianness.\n\n        The difference between Gadget snapshot format 1 and 2 can be told from\n        the first 4 bytes of the file. For format 1, it's the header size. For\n        format 2, it's always 8.\n        \"\"\"\n        first_header_size = self.size[0]\n        # Read the first 4 bytes assuming little endian int32\n        with self.open() as f:\n            (rhead,) = struct.unpack(\"<I\", f.read(4))\n        # Format 1?\n        if rhead == first_header_size:\n            return 1, \"<\"\n        elif rhead == _byte_swap_32(first_header_size):\n            return 1, \">\"\n        # Format 2?\n        elif rhead == 8:\n            return 2, \"<\"\n        elif rhead == _byte_swap_32(8):\n            return 2, \">\"\n        else:\n            raise RuntimeError(\n                \"Gadget snapshot file is likely corrupted! \"\n                f\"The first 4 bytes represent {rhead} (as little endian int32). \"\n                f\"But we are looking for {first_header_size} (for format 1) \"\n                \"or 8 (for format 2).\"\n            )\n\n    @property\n    def position_offset(self):\n        \"\"\"Offset to the position block.\"\"\"\n        n_header = len(self.size)\n        offset = 8 * n_header + sum(self.size)\n        if self.gadget_format[0] == 2:\n            offset += SNAP_FORMAT_2_OFFSET * (n_header + 1)\n        return offset\n\n    @property\n    def size(self):\n        \"\"\"Header size in bytes.\"\"\"\n\n        def single_header_size(single_header_spec):\n            return sum(\n                field[1] * np.dtype(field[2]).itemsize for field in single_header_spec\n            )\n\n        return [single_header_size(spec) for spec in self.spec]\n\n    @property\n    def value(self):\n        \"\"\"Header values as a dictionary.\"\"\"\n        # The entries in this header are capitalized and named to match Table 4\n        # in the GADGET-2 user guide.\n        gformat, endianswap = self.gadget_format\n        # Read header\n        with self.open() as f:\n            hvals = {}\n            for spec in self.spec:\n                if gformat == 2:\n                    f.seek(f.tell() + SNAP_FORMAT_2_OFFSET)\n                hvals_new = read_record(f, spec, endian=endianswap)\n                hvals.update(hvals_new)\n        # Remove placeholder keys\n        for key in self._placeholder_keys:\n            if key in hvals:\n                del hvals[key]\n        # Convert length 1 list to scalar value\n        for i in hvals:\n            if len(hvals[i]) == 1:\n                hvals[i] = hvals[i][0]\n        return hvals\n\n    def open(self):\n        \"\"\"Open snapshot file.\"\"\"\n        for filename in [self.filename, self.filename + \".0\"]:\n            if os.path.exists(filename):\n                return open(filename, \"rb\")\n        raise FileNotFoundError(f\"Snapshot file {self.filename} does not exist.\")\n\n    def validate(self):\n        \"\"\"Validate data integrity.\"\"\"\n        try:\n            self.open().close()\n            self.gadget_format\n            self.float_type\n        except Exception:\n            return False\n        return True\n\n\nclass GadgetBinaryFile(ParticleFile):\n    def __init__(self, ds, io, filename, file_id, range=None):\n        header = GadgetBinaryHeader(filename, ds._header.spec)\n        self.header = header.value\n        self._position_offset = header.position_offset\n        with header.open() as f:\n            self._file_size = f.seek(0, os.SEEK_END)\n\n        super().__init__(ds, io, filename, file_id, range)\n\n    def _calculate_offsets(self, field_list, pcounts):\n        # Note that we ignore pcounts here because it's the global count.  We\n        # just want the local count, which we store here.\n        self.field_offsets = self.io._calculate_field_offsets(\n            field_list,\n            self.header[\"Npart\"].copy(),\n            self._position_offset,\n            self.start,\n            self._file_size,\n        )\n\n\nclass GadgetBinaryIndex(SPHParticleIndex):\n    def __init__(self, ds, dataset_type):\n        super().__init__(ds, dataset_type)\n        self._initialize_index()\n\n    def _initialize_index(self):\n        # Normally this function is called during field detection. We call it\n        # here because we need to know which fields exist on-disk so that we can\n        # read in the smoothing lengths for SPH data before we construct the\n        # Morton bitmaps.\n        self._detect_output_fields()\n        super()._initialize_index()\n\n    def _initialize_frontend_specific(self):\n        super()._initialize_frontend_specific()\n        self.io._float_type = self.ds._header.float_type\n\n\nclass GadgetDataset(SPHDataset):\n    _index_class: type[Index] = GadgetBinaryIndex\n    _file_class: type[ParticleFile] = GadgetBinaryFile\n    _field_info_class: type[FieldInfoContainer] = GadgetFieldInfo\n    _particle_mass_name = \"Mass\"\n    _particle_coordinates_name = \"Coordinates\"\n    _particle_velocity_name = \"Velocities\"\n    _sph_ptypes = (\"Gas\",)\n    _suffix = \"\"\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"gadget_binary\",\n        additional_fields=(),\n        unit_base=None,\n        index_order=None,\n        index_filename=None,\n        kdtree_filename=None,\n        kernel_name=None,\n        bounding_box=None,\n        header_spec=\"default\",\n        field_spec=\"default\",\n        ptype_spec=\"default\",\n        long_ids=False,\n        units_override=None,\n        mean_molecular_weight=None,\n        header_offset=0,\n        unit_system=\"cgs\",\n        use_dark_factor=False,\n        w_0=-1.0,\n        w_a=0.0,\n        default_species_fields=None,\n    ):\n        if self._instantiated:\n            return\n        # Check if filename is a directory\n        if os.path.isdir(filename):\n            # Get the .0 snapshot file. We know there's only 1 and it's valid since we\n            # came through _is_valid in load()\n            for f in os.listdir(filename):\n                fname = os.path.join(filename, f)\n                fext = os.path.splitext(fname)[-1]\n                if (\n                    (\".0\" in f)\n                    and (fext not in {\".ewah\", \".kdtree\"})\n                    and os.path.isfile(fname)\n                ):\n                    filename = os.path.join(filename, f)\n                    break\n        self._header = GadgetBinaryHeader(filename, header_spec)\n        header_size = self._header.size\n        if header_size != [256]:\n            only_on_root(\n                mylog.warning,\n                \"Non-standard header size is detected! \"\n                \"Gadget-2 standard header is 256 bytes, but yours is %s. \"\n                \"Make sure a non-standard header is actually expected. \"\n                \"Otherwise something is wrong, \"\n                \"and you might want to check how the dataset is loaded. \"\n                \"Further information about header specification can be found in \"\n                \"https://yt-project.org/docs/dev/examining/loading_data.html#header-specification.\",\n                header_size,\n            )\n        self._field_spec = self._setup_binary_spec(field_spec, gadget_field_specs)\n        self._ptype_spec = self._setup_binary_spec(ptype_spec, gadget_ptype_specs)\n        self.storage_filename = None\n        if long_ids:\n            self._id_dtype = \"u8\"\n        else:\n            self._id_dtype = \"u4\"\n        self.long_ids = long_ids\n        self.header_offset = header_offset\n        if unit_base is not None and \"UnitLength_in_cm\" in unit_base:\n            # We assume this is comoving, because in the absence of comoving\n            # integration the redshift will be zero.\n            unit_base[\"cmcm\"] = 1.0 / unit_base[\"UnitLength_in_cm\"]\n        self._unit_base = unit_base\n        if bounding_box is not None:\n            # This ensures that we know a bounding box has been applied\n            self._domain_override = True\n            bbox = np.array(bounding_box, dtype=\"float64\")\n            if bbox.shape == (2, 3):\n                bbox = bbox.transpose()\n            self.domain_left_edge = bbox[:, 0]\n            self.domain_right_edge = bbox[:, 1]\n        else:\n            self.domain_left_edge = self.domain_right_edge = None\n        if units_override is not None:\n            raise RuntimeError(\n                \"units_override is not supported for GadgetDataset. \"\n                + \"Use unit_base instead.\"\n            )\n\n        # Set dark energy parameters before cosmology object is created\n        self.use_dark_factor = use_dark_factor\n        self.w_0 = w_0\n        self.w_a = w_a\n\n        super().__init__(\n            filename,\n            dataset_type=dataset_type,\n            unit_system=unit_system,\n            index_order=index_order,\n            index_filename=index_filename,\n            kdtree_filename=kdtree_filename,\n            kernel_name=kernel_name,\n            default_species_fields=default_species_fields,\n        )\n        if self.cosmological_simulation:\n            self.time_unit.convert_to_units(\"s/h\")\n            self.length_unit.convert_to_units(\"kpccm/h\")\n            self.mass_unit.convert_to_units(\"g/h\")\n        else:\n            self.time_unit.convert_to_units(\"s\")\n            self.length_unit.convert_to_units(\"kpc\")\n            self.mass_unit.convert_to_units(\"Msun\")\n        if mean_molecular_weight is None:\n            self.mu = compute_mu(self.default_species_fields)\n        else:\n            self.mu = mean_molecular_weight\n\n    @classmethod\n    def _setup_binary_spec(cls, spec, spec_dict):\n        if isinstance(spec, str):\n            _hs = ()\n            for hs in spec.split(\"+\"):\n                _hs += spec_dict[hs]\n            spec = _hs\n        return spec\n\n    def __str__(self):\n        return os.path.basename(self.parameter_filename).split(\".\")[0]\n\n    def _get_hvals(self):\n        self.gen_hsmls = False\n        return self._header.value\n\n    def _parse_parameter_file(self):\n        hvals = self._get_hvals()\n\n        self.dimensionality = 3\n        self.refine_by = 2\n        self.parameters[\"HydroMethod\"] = \"sph\"\n        # Set standard values\n\n        # We may have an overridden bounding box.\n        if self.domain_left_edge is None and hvals[\"BoxSize\"] != 0:\n            self.domain_left_edge = np.zeros(3, \"float64\")\n            self.domain_right_edge = np.ones(3, \"float64\") * hvals[\"BoxSize\"]\n\n        self.domain_dimensions = np.ones(3, \"int32\")\n        self._periodicity = (True, True, True)\n\n        self.current_redshift = hvals.get(\"Redshift\", 0.0)\n        if \"Redshift\" not in hvals:\n            mylog.info(\"Redshift is not set in Header. Assuming z=0.\")\n\n        if \"OmegaLambda\" in hvals:\n            self.omega_lambda = hvals[\"OmegaLambda\"]\n            self.omega_matter = hvals[\"Omega0\"]\n            self.hubble_constant = hvals[\"HubbleParam\"]\n            self.cosmological_simulation = self.omega_lambda != 0.0\n        else:\n            # If these are not set it is definitely not a cosmological dataset.\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0  # Just in case somebody asks for it.\n            # omega_matter has been changed to 0.0 for consistency with\n            # non-cosmological frontends\n            self.cosmological_simulation = 0\n            # Hubble is set below for Omega Lambda = 0.\n\n        # According to the Gadget manual, OmegaLambda will be zero for\n        # non-cosmological datasets.  However, it may be the case that\n        # individuals are running cosmological simulations *without* Lambda, in\n        # which case we may be doing something incorrect here.\n        # It may be possible to deduce whether ComovingIntegration is on\n        # somehow, but opinions on this vary.\n        if not self.cosmological_simulation:\n            mylog.info(\"Omega Lambda is 0.0, so we are turning off Cosmology.\")\n            self.hubble_constant = 1.0  # So that scaling comes out correct\n            self.current_redshift = 0.0\n            # This may not be correct.\n            self.current_time = hvals[\"Time\"]\n        else:\n            # Now we calculate our time based on the cosmology, because in\n            # ComovingIntegration hvals[\"Time\"] will in fact be the expansion\n            # factor, not the actual integration time, so we re-calculate\n            # global time from our Cosmology.\n            cosmo = Cosmology(\n                hubble_constant=self.hubble_constant,\n                omega_matter=self.omega_matter,\n                omega_lambda=self.omega_lambda,\n            )\n            self.current_time = cosmo.lookback_time(self.current_redshift, 1e6)\n            mylog.info(\n                \"Calculating time from %0.3e to be %0.3e seconds\",\n                hvals[\"Time\"],\n                self.current_time,\n            )\n        self.parameters = hvals\n\n        basename, _, _ = self.basename.partition(\".\")\n        prefix = os.path.join(self.directory, basename)\n\n        if hvals[\"NumFiles\"] > 1:\n            for t in (\n                f\"{prefix}.%(num)s{self._suffix}\",\n                f\"{prefix}.gad.%(num)s{self._suffix}\",\n            ):\n                if os.path.isfile(t % {\"num\": 0}):\n                    self.filename_template = t\n                    break\n            else:\n                raise RuntimeError(\"Could not determine correct data file template.\")\n        else:\n            self.filename_template = self.parameter_filename\n\n        self.file_count = hvals[\"NumFiles\"]\n\n    def _set_code_unit_attributes(self):\n        # If no units passed in by user, set a sane default (Gadget-2 users\n        # guide).\n        if self._unit_base is None:\n            if self.cosmological_simulation == 1:\n                only_on_root(\n                    mylog.info, \"Assuming length units are in kpc/h (comoving)\"\n                )\n                self._unit_base = {\"length\": (1.0, \"kpccm/h\")}\n            else:\n                only_on_root(mylog.info, \"Assuming length units are in kpc (physical)\")\n                self._unit_base = {\"length\": (1.0, \"kpc\")}\n\n        # If units passed in by user, decide what to do about\n        # co-moving and factors of h\n        unit_base = self._unit_base or {}\n        if \"length\" in unit_base:\n            length_unit = unit_base[\"length\"]\n        elif \"UnitLength_in_cm\" in unit_base:\n            if self.cosmological_simulation == 0:\n                length_unit = (unit_base[\"UnitLength_in_cm\"], \"cm\")\n            else:\n                length_unit = (unit_base[\"UnitLength_in_cm\"], \"cmcm/h\")\n        else:\n            raise RuntimeError\n        length_unit = _fix_unit_ordering(length_unit)\n        self.length_unit = self.quan(length_unit[0], length_unit[1])\n\n        unit_base = self._unit_base or {}\n\n        if self.cosmological_simulation:\n            # see http://www.mpa-garching.mpg.de/gadget/gadget-list/0113.html\n            # for why we need to include a factor of square root of the\n            # scale factor\n            vel_units = \"cm/s * sqrt(a)\"\n        else:\n            vel_units = \"cm/s\"\n\n        if \"velocity\" in unit_base:\n            velocity_unit = unit_base[\"velocity\"]\n        elif \"UnitVelocity_in_cm_per_s\" in unit_base:\n            velocity_unit = (unit_base[\"UnitVelocity_in_cm_per_s\"], vel_units)\n        else:\n            velocity_unit = (1e5, vel_units)\n        velocity_unit = _fix_unit_ordering(velocity_unit)\n        self.velocity_unit = self.quan(velocity_unit[0], velocity_unit[1])\n\n        # We set hubble_constant = 1.0 for non-cosmology, so this is safe.\n        # Default to 1e10 Msun/h if mass is not specified.\n        if \"mass\" in unit_base:\n            mass_unit = unit_base[\"mass\"]\n        elif \"UnitMass_in_g\" in unit_base:\n            if self.cosmological_simulation == 0:\n                mass_unit = (unit_base[\"UnitMass_in_g\"], \"g\")\n            else:\n                mass_unit = (unit_base[\"UnitMass_in_g\"], \"g/h\")\n        else:\n            # Sane default\n            mass_unit = (1e10, \"Msun/h\")\n        mass_unit = _fix_unit_ordering(mass_unit)\n        self.mass_unit = self.quan(mass_unit[0], mass_unit[1])\n        if self.cosmological_simulation:\n            # self.velocity_unit is the unit to rescale on-disk velocities, The\n            # actual internal velocity unit is really in comoving units\n            # since the time unit is derived from the internal velocity unit, we\n            # infer the internal velocity unit here and name it vel_unit\n            #\n            # see http://www.mpa-garching.mpg.de/gadget/gadget-list/0113.html\n            if \"velocity\" in unit_base:\n                vel_unit = unit_base[\"velocity\"]\n            elif \"UnitVelocity_in_cm_per_s\" in unit_base:\n                vel_unit = (unit_base[\"UnitVelocity_in_cm_per_s\"], \"cmcm/s\")\n            else:\n                vel_unit = (1, \"kmcm/s\")\n            vel_unit = self.quan(*vel_unit)\n        else:\n            vel_unit = self.velocity_unit\n        self.time_unit = self.length_unit / vel_unit\n\n        if \"specific_energy\" in unit_base:\n            specific_energy_unit = unit_base[\"specific_energy\"]\n        elif \"UnitEnergy_in_cgs\" in unit_base and \"UnitMass_in_g\" in unit_base:\n            specific_energy_unit = (\n                unit_base[\"UnitEnergy_in_cgs\"] / unit_base[\"UnitMass_in_g\"]\n            )\n            specific_energy_unit = (specific_energy_unit, \"(cm/s)**2\")\n        else:\n            # Sane default\n            specific_energy_unit = (1, \"(km/s) ** 2\")\n        specific_energy_unit = _fix_unit_ordering(specific_energy_unit)\n        self.specific_energy_unit = self.quan(*specific_energy_unit)\n\n        if \"magnetic\" in unit_base:\n            magnetic_unit = unit_base[\"magnetic\"]\n        elif \"UnitMagneticField_in_gauss\" in unit_base:\n            magnetic_unit = (unit_base[\"UnitMagneticField_in_gauss\"], \"gauss\")\n        else:\n            # Sane default\n            magnetic_unit = (1.0, \"gauss\")\n        magnetic_unit = _fix_unit_ordering(magnetic_unit)\n        self.magnetic_unit = self.quan(*magnetic_unit)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        header_spec = kwargs.get(\"header_spec\", \"default\")\n        # Check to see if passed filename is a directory. If so, use it to get\n        # the .0 snapshot file. Make sure there's only one such file, otherwise\n        # there's an ambiguity about which file the user wants. Ignore ewah files\n        if os.path.isdir(filename):\n            valid_files = []\n            for f in os.listdir(filename):\n                fname = os.path.join(filename, f)\n                if (\".0\" in f) and (\".ewah\" not in f) and os.path.isfile(fname):\n                    valid_files.append(f)\n            if len(valid_files) == 0:\n                return False\n            elif len(valid_files) > 1:\n                return False\n            else:\n                validated_file = os.path.join(filename, valid_files[0])\n        else:\n            validated_file = filename\n        header = GadgetBinaryHeader(validated_file, header_spec)\n        return header.validate()\n\n\nclass GadgetHDF5File(ParticleFile):\n    pass\n\n\nclass GadgetHDF5Dataset(GadgetDataset):\n    _load_requirements = [\"h5py\"]\n    _file_class = GadgetHDF5File\n    _index_class = SPHParticleIndex\n    _field_info_class: type[FieldInfoContainer] = GadgetFieldInfo\n    _particle_mass_name = \"Masses\"\n    _sph_ptypes = (\"PartType0\",)\n    _suffix = \".hdf5\"\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"gadget_hdf5\",\n        unit_base=None,\n        index_order=None,\n        index_filename=None,\n        kernel_name=None,\n        bounding_box=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        self.storage_filename = None\n        filename = os.path.abspath(filename)\n        if units_override is not None:\n            raise RuntimeError(\n                \"units_override is not supported for GadgetHDF5Dataset. \"\n                \"Use unit_base instead.\"\n            )\n        super().__init__(\n            filename,\n            dataset_type,\n            unit_base=unit_base,\n            index_order=index_order,\n            index_filename=index_filename,\n            kernel_name=kernel_name,\n            bounding_box=bounding_box,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n    def _get_hvals(self):\n        handle = h5py.File(self.parameter_filename, mode=\"r\")\n        hvals = {}\n        hvals.update((str(k), v) for k, v in handle[\"/Header\"].attrs.items())\n        # Compat reasons.\n        hvals[\"NumFiles\"] = hvals[\"NumFilesPerSnapshot\"]\n        hvals[\"Massarr\"] = hvals[\"MassTable\"]\n        sph_ptypes = [ptype for ptype in self._sph_ptypes if ptype in handle]\n        if sph_ptypes:\n            self.gen_hsmls = \"SmoothingLength\" not in handle[sph_ptypes[0]]\n        else:\n            self.gen_hsmls = False\n        # Later versions of Gadget and its derivatives have a \"Parameters\"\n        # group in the HDF5 file.\n        if \"Parameters\" in handle:\n            hvals.update((str(k), v) for k, v in handle[\"/Parameters\"].attrs.items())\n        handle.close()\n\n        # ensure that 1-element arrays are reduced to scalars\n        updated_hvals = {}\n        for hvalname, value in hvals.items():\n            if isinstance(value, np.ndarray) and value.size == 1:\n                mylog.info(\"Reducing single-element array %s to scalar.\", hvalname)\n                updated_hvals[hvalname] = value.item()\n        hvals.update(updated_hvals)\n\n        return hvals\n\n    def _get_uvals(self):\n        handle = h5py.File(self.parameter_filename, mode=\"r\")\n        uvals = {}\n        uvals.update((str(k), v) for k, v in handle[\"/Units\"].attrs.items())\n        handle.close()\n        return uvals\n\n    def _set_owls_eagle(self):\n        self.dimensionality = 3\n        self.refine_by = 2\n        self.parameters[\"HydroMethod\"] = \"sph\"\n\n        self._unit_base = self._get_uvals()\n        self._unit_base[\"cmcm\"] = 1.0 / self._unit_base[\"UnitLength_in_cm\"]\n\n        self.current_redshift = self.parameters[\"Redshift\"]\n        self.omega_lambda = self.parameters[\"OmegaLambda\"]\n        self.omega_matter = self.parameters[\"Omega0\"]\n        self.hubble_constant = self.parameters[\"HubbleParam\"]\n\n        if self.domain_left_edge is None and self.parameters[\"BoxSize\"] != 0:\n            self.domain_left_edge = np.zeros(3, \"float64\")\n            self.domain_right_edge = np.ones(3, \"float64\") * self.parameters[\"BoxSize\"]\n\n        self.domain_dimensions = np.ones(3, \"int32\")\n\n        self.cosmological_simulation = 1\n        self._periodicity = (True, True, True)\n\n        prefix = os.path.abspath(\n            os.path.join(\n                os.path.dirname(self.parameter_filename),\n                os.path.basename(self.parameter_filename).split(\".\", 1)[0],\n            )\n        )\n\n        suffix = self.parameter_filename.rsplit(\".\", 1)[-1]\n        if self.parameters[\"NumFiles\"] > 1:\n            self.filename_template = f\"{prefix}.%(num)i.{suffix}\"\n        else:\n            self.filename_template = self.parameter_filename\n\n        self.file_count = self.parameters[\"NumFilesPerSnapshot\"]\n\n    def _set_owls_eagle_units(self):\n        # note the contents of the HDF5 Units group are in _unit_base\n        # note the velocity stored on disk is sqrt(a) dx/dt\n        # physical velocity [cm/s] = a dx/dt\n        # = sqrt(a) * velocity_on_disk * UnitVelocity_in_cm_per_s\n        self.length_unit = self.quan(self._unit_base[\"UnitLength_in_cm\"], \"cmcm/h\")\n        self.mass_unit = self.quan(self._unit_base[\"UnitMass_in_g\"], \"g/h\")\n        self.velocity_unit = self.quan(\n            self._unit_base[\"UnitVelocity_in_cm_per_s\"], \"cm/s * sqrt(a)\"\n        )\n        self.time_unit = self.quan(self._unit_base[\"UnitTime_in_s\"], \"s/h\")\n\n        specific_energy_unit_cgs = (\n            self._unit_base[\"UnitEnergy_in_cgs\"] / self._unit_base[\"UnitMass_in_g\"]\n        )\n        self.specific_energy_unit = self.quan(specific_energy_unit_cgs, \"(cm/s)**2\")\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        need_groups = [\"Header\"]\n        veto_groups = [\"FOF\", \"Group\", \"Subhalo\"]\n        valid = True\n        try:\n            fh = h5py.File(filename, mode=\"r\")\n            valid = all(ng in fh[\"/\"] for ng in need_groups) and not any(\n                vg in fh[\"/\"] for vg in veto_groups\n            )\n            fh.close()\n        except Exception:\n            valid = False\n            pass\n\n        try:\n            fh = h5py.File(filename, mode=\"r\")\n            valid = fh[\"Header\"].attrs[\"Code\"].decode(\"utf-8\") != \"SWIFT\"\n            fh.close()\n        except (OSError, KeyError):\n            pass\n\n        return valid\n"
  },
  {
    "path": "yt/frontends/gadget/definitions.py",
    "content": "gadget_header_specs = {\n    \"default\": (\n        (\"Npart\", 6, \"i\"),\n        (\"Massarr\", 6, \"d\"),\n        (\"Time\", 1, \"d\"),\n        (\"Redshift\", 1, \"d\"),\n        (\"FlagSfr\", 1, \"i\"),\n        (\"FlagFeedback\", 1, \"i\"),\n        (\"Nall\", 6, \"i\"),\n        (\"FlagCooling\", 1, \"i\"),\n        (\"NumFiles\", 1, \"i\"),\n        (\"BoxSize\", 1, \"d\"),\n        (\"Omega0\", 1, \"d\"),\n        (\"OmegaLambda\", 1, \"d\"),\n        (\"HubbleParam\", 1, \"d\"),\n        (\"FlagAge\", 1, \"i\"),\n        (\"FlagMetals\", 1, \"i\"),\n        (\"NallHW\", 6, \"i\"),\n        (\"unused\", 16, \"i\"),\n    ),\n    \"pad32\": ((\"empty\", 32, \"c\"),),\n    \"pad64\": ((\"empty\", 64, \"c\"),),\n    \"pad128\": ((\"empty\", 128, \"c\"),),\n    \"pad256\": ((\"empty\", 256, \"c\"),),\n}\n\ngadget_ptype_specs = {\"default\": (\"Gas\", \"Halo\", \"Disk\", \"Bulge\", \"Stars\", \"Bndry\")}\n\ngadget_field_specs = {\n    \"default\": (\n        \"Coordinates\",\n        \"Velocities\",\n        \"ParticleIDs\",\n        \"Mass\",\n        (\"InternalEnergy\", \"Gas\"),\n        (\"Density\", \"Gas\"),\n        (\"SmoothingLength\", \"Gas\"),\n    ),\n    \"agora_unlv\": (\n        \"Coordinates\",\n        \"Velocities\",\n        \"ParticleIDs\",\n        \"Mass\",\n        (\"InternalEnergy\", \"Gas\"),\n        (\"Density\", \"Gas\"),\n        (\"Electron_Number_Density\", \"Gas\"),\n        (\"HI_NumberDensity\", \"Gas\"),\n        (\"SmoothingLength\", \"Gas\"),\n    ),\n    \"group0000\": (\n        \"Coordinates\",\n        \"Velocities\",\n        \"ParticleIDs\",\n        \"Mass\",\n        \"Potential\",\n        (\"Temperature\", \"Gas\"),\n        (\"Density\", \"Gas\"),\n        (\"ElectronNumberDensity\", \"Gas\"),\n        (\"HI_NumberDensity\", \"Gas\"),\n        (\"SmoothingLength\", \"Gas\"),\n        (\"StarFormationRate\", \"Gas\"),\n        (\"DelayTime\", \"Gas\"),\n        (\"FourMetalFractions\", (\"Gas\", \"Stars\")),\n        (\"MaxTemperature\", (\"Gas\", \"Stars\")),\n        (\"NStarsSpawned\", (\"Gas\", \"Stars\")),\n        (\"StellarAge\", \"Stars\"),\n    ),\n    \"magneticum_box2_hr\": (\n        \"Coordinates\",\n        \"Velocities\",\n        \"ParticleIDs\",\n        \"Mass\",\n        (\"InternalEnergy\", \"Gas\"),\n        (\"Density\", \"Gas\"),\n        (\"SmoothingLength\", \"Gas\"),\n        (\"ColdFraction\", \"Gas\"),\n        (\"Temperature\", \"Gas\"),\n        (\"StellarAge\", (\"Stars\", \"Bndry\")),\n        \"Potential\",\n        (\"InitialMass\", \"Stars\"),\n        (\"ElevenMetalMasses\", (\"Gas\", \"Stars\")),\n        (\"StarFormationRate\", \"Gas\"),\n        (\"TrueMass\", \"Bndry\"),\n        (\"AccretionRate\", \"Bndry\"),\n    ),\n}\n\ngadget_hdf5_ptypes = (\n    \"PartType0\",\n    \"PartType1\",\n    \"PartType2\",\n    \"PartType3\",\n    \"PartType4\",\n    \"PartType5\",\n)\n\nSNAP_FORMAT_2_OFFSET = 16\n\n\"\"\"\nHere we have a dictionary of possible element species defined in Gadget\ndatasets, keyed by the number of elements. In some cases, these are mass\nfractions, in others, they are metals--the context for the dataset will\ndetermine this. The \"Ej\" key is for the total mass of all elements that\nare not explicitly listed.\n\"\"\"\nelem_names_opts = {\n    4: [\"C\", \"O\", \"Si\", \"Fe\"],\n    7: [\"C\", \"N\", \"O\", \"Mg\", \"Si\", \"Fe\", \"Ej\"],\n    8: [\"He\", \"C\", \"O\", \"Mg\", \"S\", \"Si\", \"Fe\", \"Ej\"],\n    11: [\"He\", \"C\", \"Ca\", \"O\", \"N\", \"Ne\", \"Mg\", \"S\", \"Si\", \"Fe\", \"Ej\"],\n    15: [\n        \"He\",\n        \"C\",\n        \"Ca\",\n        \"O\",\n        \"N\",\n        \"Ne\",\n        \"Mg\",\n        \"S\",\n        \"Si\",\n        \"Fe\",\n        \"Na\",\n        \"Al\",\n        \"Ar\",\n        \"Ni\",\n        \"Ej\",\n    ],\n}\n"
  },
  {
    "path": "yt/frontends/gadget/fields.py",
    "content": "from functools import partial\n\nfrom yt.fields.particle_fields import sph_whitelist_fields\nfrom yt.frontends.gadget.definitions import elem_names_opts\nfrom yt.frontends.sph.fields import SPHFieldInfo\nfrom yt.utilities.periodic_table import periodic_table\nfrom yt.utilities.physical_constants import kb, mp\nfrom yt.utilities.physical_ratios import _primordial_mass_fraction\n\n\nclass GadgetFieldInfo(SPHFieldInfo):\n    def __init__(self, ds, field_list, slice_info=None):\n        if ds.gen_hsmls:\n            hsml = ((\"smoothing_length\", (\"code_length\", [], None)),)\n            self.known_particle_fields += hsml\n        for field in field_list:\n            if field[1].startswith(\"MetalMasses\"):\n                mm = ((field[1], (\"code_mass\", [], None)),)\n                self.known_particle_fields += mm\n        super().__init__(ds, field_list, slice_info=slice_info)\n\n    def setup_particle_fields(self, ptype, *args, **kwargs):\n        # setup some special fields that only make sense for SPH particles\n\n        if (ptype, \"FourMetalFractions\") in self.ds.field_list:\n            self.species_names = self._setup_four_metal_fractions(ptype)\n        elif (ptype, \"ElevenMetalMasses\") in self.ds.field_list:\n            self.species_names = self._setup_metal_masses(ptype, \"ElevenMetalMasses\")\n        elif (ptype, \"MetalMasses_00\") in self.ds.field_list:\n            self.species_names = self._setup_metal_masses(ptype, \"MetalMasses\")\n        if len(self.species_names) == 0:\n            self.species_names = self._check_whitelist_species_fields(ptype)\n\n        super().setup_particle_fields(ptype, *args, **kwargs)\n\n        if ptype in (\"PartType0\", \"Gas\"):\n            self.setup_gas_particle_fields(ptype)\n\n    def _setup_four_metal_fractions(self, ptype):\n        \"\"\"\n        This function breaks the FourMetalFractions field (if present)\n        into its four component metal fraction fields and adds\n        corresponding metal density fields which will later get smoothed\n\n        This gets used with the Gadget group0000 format\n        as defined in the gadget_field_specs in frontends/gadget/definitions.py\n        \"\"\"\n        metal_names = elem_names_opts[4]\n\n        def _fraction(field, data, i: int):\n            return data[ptype, \"FourMetalFractions\"][:, i]\n\n        def _metal_density(field, data, i: int):\n            return data[ptype, \"FourMetalFractions\"][:, i] * data[ptype, \"density\"]\n\n        for i, metal_name in enumerate(metal_names):\n            # add the metal fraction fields\n            self.add_field(\n                (ptype, metal_name + \"_fraction\"),\n                sampling_type=\"particle\",\n                function=partial(_fraction, i=i),\n                units=\"\",\n            )\n\n            # add the metal density fields\n            self.add_field(\n                (ptype, metal_name + \"_density\"),\n                sampling_type=\"particle\",\n                function=partial(_metal_density, i=i),\n                units=self.ds.unit_system[\"density\"],\n            )\n\n        return metal_names\n\n    def _make_fraction_functions(self, ptype, fname):\n        if fname == \"ElevenMetalMasses\":\n\n            def _fraction(field, data, i: int):\n                return (\n                    data[ptype, \"ElevenMetalMasses\"][:, i]\n                    / data[ptype, \"particle_mass\"]\n                )\n\n            def _metallicity(data):\n                ret = (\n                    data[ptype, \"ElevenMetalMasses\"][:, 1].sum(axis=1)\n                    / data[ptype, \"particle_mass\"]\n                )\n                return ret\n\n            def _h_fraction(data):\n                ret = (\n                    data[ptype, \"ElevenMetalMasses\"].sum(axis=1)\n                    / data[ptype, \"particle_mass\"]\n                )\n                return 1.0 - ret\n\n            elem_names = elem_names_opts[11]\n\n        elif fname == \"MetalMasses\":\n            n_elem = len(\n                [\n                    fd\n                    for fd in self.ds.field_list\n                    if fd[0] == ptype and fd[1].startswith(\"MetalMasses\")\n                ]\n            )\n            elem_names = elem_names_opts[n_elem]\n            no_He = \"He\" not in elem_names\n\n            def _fraction(field, data, i: int):\n                return (\n                    data[ptype, f\"MetalMasses_{i:02d}\"] / data[ptype, \"particle_mass\"]\n                )\n\n            def _metallicity(data):\n                mass = 0.0\n                start_idx = int(not no_He)\n                for i in range(start_idx, n_elem):\n                    mass += data[ptype, f\"MetalMasses_{i:02d}\"]\n                ret = mass / data[ptype, \"particle_mass\"]\n                return ret\n\n            if no_He:\n                _h_fraction = None\n\n            else:\n\n                def _h_fraction(data):\n                    mass = 0.0\n                    for i in range(n_elem):\n                        mass += data[ptype, f\"MetalMasses_{i:02d}\"]\n                    ret = mass / data[ptype, \"particle_mass\"]\n                    return 1.0 - ret\n\n        else:\n            raise KeyError(\n                f\"Making element fraction fields from '{ptype}','{fname}' not possible!\"\n            )\n        return _fraction, _h_fraction, _metallicity, elem_names\n\n    def _setup_metal_masses(self, ptype, fname):\n        \"\"\"\n        This function breaks the ElevenMetalMasses field (if present)\n        into its eleven component metal fraction fields and adds\n        corresponding metal density fields which will later get smoothed\n\n        This gets used with the magneticum_box2_hr format\n        as defined in the gadget_field_specs in frontends/gadget/definitions.py\n        \"\"\"\n        sampling_type = \"local\" if ptype in self.ds._sph_ptypes else \"particle\"\n        (\n            _fraction,\n            _h_fraction,\n            _metallicity,\n            elem_names,\n        ) = self._make_fraction_functions(ptype, fname)\n\n        def _metal_density(field, data, elem_name: str):\n            return data[ptype, f\"{elem_name}_fraction\"] * data[ptype, \"density\"]\n\n        for i, elem_name in enumerate(elem_names):\n            # add the element fraction fields\n            self.add_field(\n                (ptype, f\"{elem_name}_fraction\"),\n                sampling_type=sampling_type,\n                function=partial(_fraction, i=i),\n                units=\"\",\n            )\n\n            # add the element density fields\n            self.add_field(\n                (ptype, f\"{elem_name}_density\"),\n                sampling_type=sampling_type,\n                function=partial(_metal_density, elem_name=elem_name),\n                units=self.ds.unit_system[\"density\"],\n            )\n\n        # metallicity\n        self.add_field(\n            (ptype, \"metallicity\"),\n            sampling_type=sampling_type,\n            function=_metallicity,\n            units=\"\",\n        )\n\n        if _h_fraction is None:\n            # no helium, so can't compute hydrogen\n            species_names = elem_names[-1]\n\n        else:\n            # hydrogen fraction and density\n            self.add_field(\n                (ptype, \"H_fraction\"),\n                sampling_type=sampling_type,\n                function=_h_fraction,\n                units=\"\",\n            )\n\n            def _h_density(data):\n                return data[ptype, \"H_fraction\"] * data[ptype, \"density\"]\n\n            self.add_field(\n                (ptype, \"H_density\"),\n                sampling_type=sampling_type,\n                function=_h_density,\n                units=self.ds.unit_system[\"density\"],\n            )\n\n            species_names = [\"H\"] + elem_names[:-1]\n\n        if \"Ej\" in elem_names:\n\n            def _ej_mass(data):\n                return data[ptype, \"Ej_fraction\"] * data[ptype, \"particle_mass\"]\n\n            self.add_field(\n                (ptype, \"Ej_mass\"),\n                sampling_type=sampling_type,\n                function=_ej_mass,\n                units=self.ds.unit_system[\"mass\"],\n            )\n            if sampling_type == \"local\":\n                self.alias((\"gas\", \"Ej_mass\"), (ptype, \"Ej_mass\"))\n\n        return species_names\n\n    def _check_whitelist_species_fields(self, ptype):\n        species_names = []\n        for field in self.ds.field_list:\n            if (\n                field[0] == ptype\n                and field[1].endswith((\"_fraction\", \"_density\"))\n                and field[1] in sph_whitelist_fields\n            ):\n                symbol, _, _ = field[1].partition(\"_\")\n                species_names.append(symbol)\n        return sorted(species_names, key=lambda symbol: periodic_table[symbol].num)\n\n    def setup_gas_particle_fields(self, ptype):\n        if (ptype, \"Temperature\") not in self.ds.field_list:\n            if (ptype, \"ElectronAbundance\") in self.ds.field_list:\n\n                def _temperature(data):\n                    # Assume cosmic abundances\n                    x_H = _primordial_mass_fraction[\"H\"]\n                    gamma = 5.0 / 3.0\n                    a_e = data[ptype, \"ElectronAbundance\"]\n                    mu = 4.0 / (3.0 * x_H + 1.0 + 4.0 * x_H * a_e)\n                    ret = data[ptype, \"InternalEnergy\"] * (gamma - 1) * mu * mp / kb\n                    return ret.in_units(self.ds.unit_system[\"temperature\"])\n\n            else:\n\n                def _temperature(data):\n                    gamma = 5.0 / 3.0\n                    ret = (\n                        data[ptype, \"InternalEnergy\"]\n                        * (gamma - 1)\n                        * data.ds.mu\n                        * mp\n                        / kb\n                    )\n                    return ret.in_units(self.ds.unit_system[\"temperature\"])\n\n            self.add_field(\n                (ptype, \"Temperature\"),\n                sampling_type=\"particle\",\n                function=_temperature,\n                units=self.ds.unit_system[\"temperature\"],\n            )\n\n        self.alias((ptype, \"temperature\"), (ptype, \"Temperature\"))\n        # need to do this manually since that automatic aliasing that happens\n        # in the FieldInfoContainer base class has already happened at this\n        # point\n        self.alias((\"gas\", \"temperature\"), (ptype, \"Temperature\"))\n"
  },
  {
    "path": "yt/frontends/gadget/io.py",
    "content": "import os\nfrom collections import defaultdict\nfrom functools import cached_property\n\nimport numpy as np\n\nfrom yt.frontends.sph.io import IOHandlerSPH\nfrom yt.units._numpy_wrapper_functions import uconcatenate\nfrom yt.utilities.lib.particle_kdtree_tools import generate_smoothing_length\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .definitions import SNAP_FORMAT_2_OFFSET, gadget_hdf5_ptypes\n\n\nclass IOHandlerGadgetHDF5(IOHandlerSPH):\n    _dataset_type = \"gadget_hdf5\"\n    _vector_fields = {\n        \"Coordinates\": 3,\n        \"Velocity\": 3,\n        \"Velocities\": 3,\n        \"MagneticField\": 3,\n    }\n    _known_ptypes = gadget_hdf5_ptypes\n    _element_names = (\n        \"Hydrogen\",\n        \"Helium\",\n        \"Carbon\",\n        \"Nitrogen\",\n        \"Oxygen\",\n        \"Neon\",\n        \"Magnesium\",\n        \"Silicon\",\n        \"Iron\",\n    )\n\n    _coord_name = \"Coordinates\"\n\n    @cached_property\n    def var_mass(self) -> tuple[str, ...]:\n        vm = []\n        for i, v in enumerate(self.ds[\"Massarr\"]):\n            if v == 0:\n                vm.append(self._known_ptypes[i])\n        return tuple(vm)\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _read_particle_coords(self, chunks, ptf):\n        for data_file in self._sorted_chunk_iterator(chunks):\n            si, ei = data_file.start, data_file.end\n            f = h5py.File(data_file.filename, mode=\"r\")\n            # This double-reads\n            for ptype in sorted(ptf):\n                if data_file.total_particles[ptype] == 0:\n                    continue\n                c = f[f\"/{ptype}/{self._coord_name}\"][si:ei, :].astype(\"float64\")\n                x, y, z = (np.squeeze(_) for _ in np.split(c, 3, axis=1))\n                if ptype == self.ds._sph_ptypes[0]:\n                    pdtype = c.dtype\n                    pshape = c.shape\n                    hsml = self._get_smoothing_length(data_file, pdtype, pshape)\n                else:\n                    hsml = 0.0\n                yield ptype, (x, y, z), hsml\n            f.close()\n\n    def _yield_coordinates(self, data_file, needed_ptype=None):\n        si, ei = data_file.start, data_file.end\n        f = h5py.File(data_file.filename, mode=\"r\")\n        pcount = f[\"/Header\"].attrs[\"NumPart_ThisFile\"][:].astype(\"int64\")\n        np.clip(pcount - si, 0, ei - si, out=pcount)\n        pcount = pcount.sum()\n        for key in f.keys():\n            if not key.startswith(\"PartType\"):\n                continue\n            if \"Coordinates\" not in f[key]:\n                continue\n            if needed_ptype and key != needed_ptype:\n                continue\n            ds = f[key][\"Coordinates\"][si:ei, ...]\n            dt = ds.dtype.newbyteorder(\"N\")  # Native\n            pos = np.empty(ds.shape, dtype=dt)\n            pos[:] = ds\n            yield key, pos\n        f.close()\n\n    def _generate_smoothing_length(self, index):\n        data_files = index.data_files\n        if not self.ds.gen_hsmls:\n            return\n        hsml_fn = data_files[0].filename.replace(\".hdf5\", \".hsml.hdf5\")\n        if os.path.exists(hsml_fn):\n            with h5py.File(hsml_fn, mode=\"r\") as f:\n                file_hash = f.attrs[\"q\"]\n            if file_hash != self.ds._file_hash:\n                mylog.warning(\"Replacing hsml files.\")\n                for data_file in data_files:\n                    hfn = data_file.filename.replace(\".hdf5\", \".hsml.hdf5\")\n                    os.remove(hfn)\n            else:\n                return\n        positions = []\n        counts = defaultdict(int)\n        for data_file in data_files:\n            for _, ppos in self._yield_coordinates(\n                data_file, needed_ptype=self.ds._sph_ptypes[0]\n            ):\n                counts[data_file.filename] += ppos.shape[0]\n                positions.append(ppos)\n        if not positions:\n            return\n        offsets = {}\n        offset = 0\n        for fn, count in counts.items():\n            offsets[fn] = offset\n            offset += count\n        kdtree = index.kdtree\n        positions = uconcatenate(positions)[kdtree.idx]\n        hsml = generate_smoothing_length(\n            positions.astype(\"float64\"), kdtree, self.ds._num_neighbors\n        )\n        dtype = positions.dtype\n        hsml = hsml[np.argsort(kdtree.idx)].astype(dtype)\n        mylog.warning(\"Writing smoothing lengths to hsml files.\")\n        for i, data_file in enumerate(data_files):\n            si, ei = data_file.start, data_file.end\n            fn = data_file.filename\n            hsml_fn = data_file.filename.replace(\".hdf5\", \".hsml.hdf5\")\n            with h5py.File(hsml_fn, mode=\"a\") as f:\n                if i == 0:\n                    f.attrs[\"q\"] = self.ds._file_hash\n                g = f.require_group(self.ds._sph_ptypes[0])\n                d = g.require_dataset(\n                    \"SmoothingLength\", dtype=dtype, shape=(counts[fn],)\n                )\n                begin = si + offsets[fn]\n                end = min(ei, d.size) + offsets[fn]\n                d[si:ei] = hsml[begin:end]\n\n    def _get_smoothing_length(self, data_file, position_dtype, position_shape):\n        ptype = self.ds._sph_ptypes[0]\n        si, ei = data_file.start, data_file.end\n        if self.ds.gen_hsmls:\n            fn = data_file.filename.replace(\".hdf5\", \".hsml.hdf5\")\n        else:\n            fn = data_file.filename\n        with h5py.File(fn, mode=\"r\") as f:\n            ds = f[ptype][\"SmoothingLength\"][si:ei, ...]\n            dt = ds.dtype.newbyteorder(\"N\")  # Native\n            if position_dtype is not None and dt < position_dtype:\n                # Sometimes positions are stored in double precision\n                # but smoothing lengths are stored in single precision.\n                # In these cases upcast smoothing length to double precision\n                # to avoid ValueErrors when we pass these arrays to Cython.\n                dt = position_dtype\n            hsml = np.empty(ds.shape, dtype=dt)\n            hsml[:] = ds\n            return hsml\n\n    def _read_particle_data_file(self, data_file, ptf, selector=None):\n        si, ei = data_file.start, data_file.end\n\n        data_return = {}\n\n        f = h5py.File(data_file.filename, mode=\"r\")\n        for ptype, field_list in sorted(ptf.items()):\n            if data_file.total_particles[ptype] == 0:\n                continue\n            g = f[f\"/{ptype}\"]\n            if selector is None or getattr(selector, \"is_all_data\", False):\n                mask = slice(None, None, None)\n                mask_sum = data_file.total_particles[ptype]\n                hsmls = None\n            else:\n                coords = g[\"Coordinates\"][si:ei].astype(\"float64\")\n                if ptype == \"PartType0\":\n                    hsmls = self._get_smoothing_length(\n                        data_file, g[\"Coordinates\"].dtype, g[\"Coordinates\"].shape\n                    ).astype(\"float64\")\n                else:\n                    hsmls = 0.0\n                mask = selector.select_points(\n                    coords[:, 0], coords[:, 1], coords[:, 2], hsmls\n                )\n                if mask is not None:\n                    mask_sum = mask.sum()\n                del coords\n            if mask is None:\n                continue\n            for field in field_list:\n                if field in (\"Mass\", \"Masses\") and ptype not in self.var_mass:\n                    data = np.empty(mask_sum, dtype=\"float64\")\n                    ind = self._known_ptypes.index(ptype)\n                    data[:] = self.ds[\"Massarr\"][ind]\n                elif field in self._element_names:\n                    rfield = \"ElementAbundance/\" + field\n                    data = g[rfield][si:ei][mask, ...]\n                elif field.startswith(\"Metallicity_\"):\n                    col = int(field.rsplit(\"_\", 1)[-1])\n                    data = g[\"Metallicity\"][si:ei, col][mask]\n                elif field.startswith(\"GFM_Metals_\"):\n                    col = int(field.rsplit(\"_\", 1)[-1])\n                    data = g[\"GFM_Metals\"][si:ei, col][mask]\n                elif field.startswith(\"Chemistry_\"):\n                    col = int(field.rsplit(\"_\", 1)[-1])\n                    data = g[\"ChemistryAbundances\"][si:ei, col][mask]\n                elif field.startswith(\"PassiveScalars_\"):\n                    col = int(field.rsplit(\"_\", 1)[-1])\n                    data = g[\"PassiveScalars\"][si:ei, col][mask]\n                elif field.startswith(\"GFM_StellarPhotometrics_\"):\n                    col = int(field.rsplit(\"_\", 1)[-1])\n                    data = g[\"GFM_StellarPhotometrics\"][si:ei, col][mask]\n                elif field.startswith(\"MetalMasses_\"):\n                    col = int(field.rsplit(\"_\", 1)[-1])\n                    data = g[\"Mass of Metals\"][si:ei, col][mask]\n                elif field == \"smoothing_length\":\n                    # This is for frontends which do not store\n                    # the smoothing length on-disk, so we do not\n                    # attempt to read them, but instead assume\n                    # that they are calculated in _get_smoothing_length.\n                    if hsmls is None:\n                        hsmls = self._get_smoothing_length(\n                            data_file,\n                            g[\"Coordinates\"].dtype,\n                            g[\"Coordinates\"].shape,\n                        ).astype(\"float64\")\n                    data = hsmls[mask]\n                else:\n                    data = g[field][si:ei][mask, ...]\n\n                data_return[ptype, field] = data\n\n        f.close()\n        return data_return\n\n    def _count_particles(self, data_file):\n        si, ei = data_file.start, data_file.end\n        f = h5py.File(data_file.filename, mode=\"r\")\n        pcount = f[\"/Header\"].attrs[\"NumPart_ThisFile\"][:].astype(\"int64\")\n        f.close()\n        if None not in (si, ei):\n            np.clip(pcount - si, 0, ei - si, out=pcount)\n        npart = {f\"PartType{i}\": v for i, v in enumerate(pcount)}\n        return npart\n\n    def _identify_fields(self, data_file):\n        f = h5py.File(data_file.filename, mode=\"r\")\n        fields = []\n        cname = self.ds._particle_coordinates_name  # Coordinates\n        mname = self.ds._particle_mass_name  # Mass\n\n        # loop over all keys in OWLS hdf5 file\n        # --------------------------------------------------\n        for key in f.keys():\n            # only want particle data\n            # --------------------------------------\n            if not key.startswith(\"PartType\"):\n                continue\n\n            # particle data group\n            # --------------------------------------\n            g = f[key]\n            if cname not in g:\n                continue\n\n            # note str => not unicode!\n            ptype = str(key)\n            if ptype not in self.var_mass:\n                fields.append((ptype, mname))\n\n            # loop over all keys in PartTypeX group\n            # ----------------------------------------\n            for k in g.keys():\n                if k == \"ElementAbundance\":\n                    gp = g[k]\n                    for j in gp.keys():\n                        kk = j\n                        fields.append((ptype, str(kk)))\n                elif (\n                    k\n                    in (\n                        \"Metallicity\",\n                        \"GFM_Metals\",\n                        \"PassiveScalars\",\n                        \"GFM_StellarPhotometrics\",\n                        \"Mass of Metals\",\n                    )\n                    and len(g[k].shape) > 1\n                ):\n                    # Vector of metallicity or passive scalar\n                    for i in range(g[k].shape[1]):\n                        key = \"MetalMasses\" if k == \"Mass of Metals\" else k\n                        fields.append((ptype, f\"{key}_{i:02}\"))\n                elif k == \"ChemistryAbundances\" and len(g[k].shape) > 1:\n                    for i in range(g[k].shape[1]):\n                        fields.append((ptype, f\"Chemistry_{i:03}\"))\n                else:\n                    kk = k\n                    if not hasattr(g[kk], \"shape\"):\n                        continue\n                    if len(g[kk].shape) > 1:\n                        self._vector_fields[kk] = g[kk].shape[1]\n                    fields.append((ptype, str(kk)))\n\n        f.close()\n\n        if self.ds.gen_hsmls:\n            fields.append((\"PartType0\", \"smoothing_length\"))\n\n        return fields, {}\n\n\nZeroMass = object()\n\n\nclass IOHandlerGadgetBinary(IOHandlerSPH):\n    _dataset_type = \"gadget_binary\"\n    _vector_fields = {\n        \"Coordinates\": 3,\n        \"Velocity\": 3,\n        \"Velocities\": 3,\n        \"MagneticField\": 3,\n        \"FourMetalFractions\": 4,\n        \"ElevenMetalMasses\": 11,\n    }\n\n    # Particle types (Table 3 in GADGET-2 user guide)\n    #\n    # Blocks in the file:\n    #   HEAD\n    #   POS\n    #   VEL\n    #   ID\n    #   MASS    (variable mass only)\n    #   U       (gas only)\n    #   RHO     (gas only)\n    #   HSML    (gas only)\n    #   POT     (only if enabled in makefile)\n    #   ACCE    (only if enabled in makefile)\n    #   ENDT    (only if enabled in makefile)\n    #   TSTP    (only if enabled in makefile)\n\n    _format = None\n\n    def __init__(self, ds, *args, **kwargs):\n        self._fields = ds._field_spec\n        self._ptypes = ds._ptype_spec\n        self.data_files = set()\n        gformat, endianswap = ds._header.gadget_format\n        # gadget format 1 original, 2 with block name\n        self._format = gformat\n        self._endian = endianswap\n        super().__init__(ds, *args, **kwargs)\n\n    @cached_property\n    def var_mass(self) -> tuple[str, ...]:\n        vm = []\n        for i, v in enumerate(self.ds[\"Massarr\"]):\n            if v == 0:\n                vm.append(self._ptypes[i])\n        return tuple(vm)\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _read_particle_coords(self, chunks, ptf):\n        data_files = set()\n        for chunk in chunks:\n            for obj in chunk.objs:\n                data_files.update(obj.data_files)\n        for data_file in sorted(data_files, key=lambda x: (x.filename, x.start)):\n            poff = data_file.field_offsets\n            tp = data_file.total_particles\n            f = open(data_file.filename, \"rb\")\n            for ptype in ptf:\n                if tp[ptype] == 0:\n                    # skip if there are no particles\n                    continue\n                f.seek(poff[ptype, \"Coordinates\"], os.SEEK_SET)\n                pos = self._read_field_from_file(f, tp[ptype], \"Coordinates\")\n                if ptype == self.ds._sph_ptypes[0]:\n                    f.seek(poff[ptype, \"SmoothingLength\"], os.SEEK_SET)\n                    hsml = self._read_field_from_file(f, tp[ptype], \"SmoothingLength\")\n                else:\n                    hsml = 0.0\n                yield ptype, (pos[:, 0], pos[:, 1], pos[:, 2]), hsml\n            f.close()\n\n    def _read_particle_data_file(self, data_file, ptf, selector=None):\n        return_data = {}\n        poff = data_file.field_offsets\n        tp = data_file.total_particles\n        f = open(data_file.filename, \"rb\")\n        for ptype, field_list in sorted(ptf.items()):\n            if tp[ptype] == 0:\n                continue\n            if selector is None or getattr(selector, \"is_all_data\", False):\n                mask = slice(None, None, None)\n            else:\n                f.seek(poff[ptype, \"Coordinates\"], os.SEEK_SET)\n                pos = self._read_field_from_file(f, tp[ptype], \"Coordinates\")\n                if ptype == self.ds._sph_ptypes[0]:\n                    f.seek(poff[ptype, \"SmoothingLength\"], os.SEEK_SET)\n                    hsml = self._read_field_from_file(f, tp[ptype], \"SmoothingLength\")\n                else:\n                    hsml = 0.0\n                mask = selector.select_points(pos[:, 0], pos[:, 1], pos[:, 2], hsml)\n                del pos\n                del hsml\n            if mask is None:\n                continue\n            for field in field_list:\n                if field == \"Mass\" and ptype not in self.var_mass:\n                    if getattr(selector, \"is_all_data\", False):\n                        size = data_file.total_particles[ptype]\n                    else:\n                        size = mask.sum()\n                    data = np.empty(size, dtype=\"float64\")\n                    m = self.ds.parameters[\"Massarr\"][self._ptypes.index(ptype)]\n                    data[:] = m\n                else:\n                    f.seek(poff[ptype, field], os.SEEK_SET)\n                    data = self._read_field_from_file(f, tp[ptype], field)\n                    data = data[mask, ...]\n                return_data[ptype, field] = data\n        f.close()\n        return return_data\n\n    def _read_field_from_file(self, f, count, name):\n        if count == 0:\n            return\n        if name == \"ParticleIDs\":\n            dt = self._endian + self.ds._id_dtype\n        else:\n            dt = self._endian + self._float_type\n        dt = np.dtype(dt)\n        if name in self._vector_fields:\n            count *= self._vector_fields[name]\n        arr = np.fromfile(f, dtype=dt, count=count)\n        # ensure data are in native endianness to avoid errors\n        # when field data are passed to cython\n        dt = dt.newbyteorder(\"N\")\n        arr = arr.astype(dt)\n        if name in self._vector_fields:\n            factor = self._vector_fields[name]\n            arr = arr.reshape((count // factor, factor), order=\"C\")\n        return arr\n\n    def _yield_coordinates(self, data_file, needed_ptype=None):\n        self._float_type = data_file.ds._header.float_type\n        self._field_size = np.dtype(self._float_type).itemsize\n        dt = np.dtype(self._endian + self._float_type)\n        dt_native = dt.newbyteorder(\"N\")\n        with open(data_file.filename, \"rb\") as f:\n            # We add on an additionally 4 for the first record.\n            f.seek(data_file._position_offset + 4)\n            for ptype, count in data_file.total_particles.items():\n                if count == 0:\n                    continue\n                if needed_ptype is not None and ptype != needed_ptype:\n                    continue\n                # The first total_particles * 3 values are positions\n                pp = (\n                    np.fromfile(f, dtype=dt, count=count * 3)\n                    .reshape(count, 3)\n                    .astype(dt_native, copy=False)\n                )\n                yield ptype, pp\n\n    def _get_smoothing_length(self, data_file, position_dtype, position_shape):\n        ret = self._get_field(data_file, \"SmoothingLength\", \"Gas\")\n        if position_dtype is not None and ret.dtype != position_dtype:\n            # Sometimes positions are stored in double precision\n            # but smoothing lengths are stored in single precision.\n            # In these cases upcast smoothing length to double precision\n            # to avoid ValueErrors when we pass these arrays to Cython.\n            ret = ret.astype(position_dtype)\n        return ret\n\n    def _get_field(self, data_file, field, ptype):\n        poff = data_file.field_offsets\n        tp = data_file.total_particles\n        with open(data_file.filename, \"rb\") as f:\n            f.seek(poff[ptype, field], os.SEEK_SET)\n            pp = self._read_field_from_file(f, tp[ptype], field)\n        return pp\n\n    def _count_particles(self, data_file):\n        si, ei = data_file.start, data_file.end\n        pcount = np.array(data_file.header[\"Npart\"])\n        if None not in (si, ei):\n            np.clip(pcount - si, 0, ei - si, out=pcount)\n        npart = {self._ptypes[i]: v for i, v in enumerate(pcount)}\n        return npart\n\n    # header is 256, but we have 4 at beginning and end for ints\n    _field_size = 4\n\n    def _calculate_field_offsets(\n        self, field_list, pcount, offset, df_start, file_size=None\n    ):\n        # field_list is (ftype, fname) but the blocks are ordered\n        # (fname, ftype) in the file.\n        if self._format == 2:\n            # Need to subtract offset due to extra header block\n            pos = offset - SNAP_FORMAT_2_OFFSET\n        else:\n            pos = offset\n        fs = self._field_size\n        offsets = {}\n        pcount = dict(zip(self._ptypes, pcount, strict=True))\n\n        for field in self._fields:\n            if field == \"ParticleIDs\" and self.ds.long_ids:\n                fs = 8\n            else:\n                fs = 4\n            if not isinstance(field, str):\n                field = field[0]\n            if not any((ptype, field) in field_list for ptype in self._ptypes):\n                continue\n            if self._format == 2:\n                pos += 20  # skip block header\n            elif self._format == 1:\n                pos += 4\n            else:\n                raise RuntimeError(f\"incorrect Gadget format {str(self._format)}!\")\n            any_ptypes = False\n            for ptype in self._ptypes:\n                if field == \"Mass\" and ptype not in self.var_mass:\n                    continue\n                if (ptype, field) not in field_list:\n                    continue\n                start_offset = df_start * fs\n                if field in self._vector_fields:\n                    start_offset *= self._vector_fields[field]\n                pos += start_offset\n                offsets[ptype, field] = pos\n                any_ptypes = True\n                remain_offset = (pcount[ptype] - df_start) * fs\n                if field in self._vector_fields:\n                    remain_offset *= self._vector_fields[field]\n                pos += remain_offset\n            pos += 4\n            if not any_ptypes:\n                pos -= 8\n        if file_size is not None:\n            if (file_size != pos) & (self._format == 1):  # ignore the rest of format 2\n                diff = file_size - pos\n                possible = []\n                for ptype, psize in sorted(pcount.items()):\n                    if psize == 0:\n                        continue\n                    if float(diff) / psize == int(float(diff) / psize):\n                        possible.append(ptype)\n                mylog.warning(\n                    \"Your Gadget-2 file may have extra \"\n                    \"columns or different precision! \"\n                    \"(%s diff => %s?)\",\n                    diff,\n                    possible,\n                )\n        return offsets\n\n    def _identify_fields(self, domain):\n        # We can just look at the particle counts.\n        field_list = []\n        tp = domain.total_particles\n        for i, ptype in enumerate(self._ptypes):\n            count = tp[ptype]\n            if count == 0:\n                continue\n            m = domain.header[\"Massarr\"][i]\n            for field in self._fields:\n                if isinstance(field, tuple):\n                    field, req = field\n                    if req is ZeroMass:\n                        if m > 0.0:\n                            continue\n                    elif isinstance(req, tuple) and ptype in req:\n                        pass\n                    elif req != ptype:\n                        continue\n                field_list.append((ptype, field))\n        return field_list, {}\n"
  },
  {
    "path": "yt/frontends/gadget/simulation_handling.py",
    "content": "import glob\nimport os\n\nimport numpy as np\nfrom unyt import dimensions, unyt_array\nfrom unyt.unit_registry import UnitRegistry\n\nfrom yt.data_objects.time_series import DatasetSeries, SimulationTimeSeries\nfrom yt.funcs import only_on_root\nfrom yt.loaders import load\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.exceptions import (\n    InvalidSimulationTimeSeries,\n    MissingParameter,\n    NoStoppingCondition,\n    YTUnidentifiedDataType,\n)\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_objects\n\n\nclass GadgetSimulation(SimulationTimeSeries):\n    r\"\"\"\n    Initialize an Gadget Simulation object.\n\n    Upon creation, the parameter file is parsed and the time and redshift\n    are calculated and stored in all_outputs.  A time units dictionary is\n    instantiated to allow for time outputs to be requested with physical\n    time units.  The get_time_series can be used to generate a\n    DatasetSeries object.\n\n    parameter_filename : str\n        The simulation parameter file.\n    find_outputs : bool\n        If True, the OutputDir directory is searched for datasets.\n        Time and redshift information are gathered by temporarily\n        instantiating each dataset.  This can be used when simulation\n        data was created in a non-standard way, making it difficult\n        to guess the corresponding time and redshift information.\n        Default: False.\n\n    Examples\n    --------\n    >>> import yt\n    >>> gs = yt.load_simulation(\"my_simulation.par\", \"Gadget\")\n    >>> gs.get_time_series()\n    >>> for ds in gs:\n    ...     print(ds.current_time)\n\n    \"\"\"\n\n    def __init__(self, parameter_filename, find_outputs=False):\n        self.simulation_type = \"particle\"\n        self.dimensionality = 3\n        SimulationTimeSeries.__init__(\n            self, parameter_filename, find_outputs=find_outputs\n        )\n\n    def _set_units(self):\n        self.unit_registry = UnitRegistry()\n        self.time_unit = self.quan(1.0, \"s\")\n        if self.cosmological_simulation:\n            # Instantiate Cosmology object for units and time conversions.\n            self.cosmology = Cosmology(\n                hubble_constant=self.hubble_constant,\n                omega_matter=self.omega_matter,\n                omega_lambda=self.omega_lambda,\n                unit_registry=self.unit_registry,\n            )\n            if \"h\" in self.unit_registry:\n                self.unit_registry.modify(\"h\", self.hubble_constant)\n            else:\n                self.unit_registry.add(\n                    \"h\", self.hubble_constant, dimensions.dimensionless\n                )\n            # Comoving lengths\n            for my_unit in [\"m\", \"pc\", \"AU\"]:\n                new_unit = f\"{my_unit}cm\"\n                # technically not true, but should be ok\n                self.unit_registry.add(\n                    new_unit,\n                    self.unit_registry.lut[my_unit][0],\n                    dimensions.length,\n                    f\"\\\\rm{{{my_unit}}}/(1+z)\",\n                    prefixable=True,\n                )\n            self.length_unit = self.quan(\n                self.unit_base[\"UnitLength_in_cm\"],\n                \"cmcm / h\",\n                registry=self.unit_registry,\n            )\n            self.mass_unit = self.quan(\n                self.unit_base[\"UnitMass_in_g\"], \"g / h\", registry=self.unit_registry\n            )\n            self.box_size = self.box_size * self.length_unit\n            self.domain_left_edge = self.domain_left_edge * self.length_unit\n            self.domain_right_edge = self.domain_right_edge * self.length_unit\n            self.unit_registry.add(\n                \"unitary\",\n                float(self.box_size.in_base()),\n                self.length_unit.units.dimensions,\n            )\n        else:\n            # Read time from file for non-cosmological sim\n            self.time_unit = self.quan(\n                self.unit_base[\"UnitLength_in_cm\"]\n                / self.unit_base[\"UnitVelocity_in_cm_per_s\"],\n                \"s\",\n            )\n            self.unit_registry.add(\"code_time\", 1.0, dimensions.time)\n            self.unit_registry.modify(\"code_time\", self.time_unit)\n            # Length\n            self.length_unit = self.quan(self.unit_base[\"UnitLength_in_cm\"], \"cm\")\n            self.unit_registry.add(\"code_length\", 1.0, dimensions.length)\n            self.unit_registry.modify(\"code_length\", self.length_unit)\n\n    def get_time_series(\n        self,\n        initial_time=None,\n        final_time=None,\n        initial_redshift=None,\n        final_redshift=None,\n        times=None,\n        redshifts=None,\n        tolerance=None,\n        parallel=True,\n        setup_function=None,\n    ):\n        \"\"\"\n        Instantiate a DatasetSeries object for a set of outputs.\n\n        If no additional keywords given, a DatasetSeries object will be\n        created with all potential datasets created by the simulation.\n\n        Outputs can be gather by specifying a time or redshift range\n        (or combination of time and redshift), with a specific list of\n        times or redshifts), or by simply searching all subdirectories\n        within the simulation directory.\n\n        initial_time : tuple of type (float, str)\n            The earliest time for outputs to be included.  This should be\n            given as the value and the string representation of the units.\n            For example, (5.0, \"Gyr\").  If None, the initial time of the\n            simulation is used.  This can be used in combination with\n            either final_time or final_redshift.\n            Default: None.\n        final_time : tuple of type (float, str)\n            The latest time for outputs to be included.  This should be\n            given as the value and the string representation of the units.\n            For example, (13.7, \"Gyr\"). If None, the final time of the\n            simulation is used.  This can be used in combination with either\n            initial_time or initial_redshift.\n            Default: None.\n        times : tuple of type (float array, str)\n            A list of times for which outputs will be found and the units\n            of those values.  For example, ([0, 1, 2, 3], \"s\").\n            Default: None.\n        initial_redshift : float\n            The earliest redshift for outputs to be included.  If None,\n            the initial redshift of the simulation is used.  This can be\n            used in combination with either final_time or\n            final_redshift.\n            Default: None.\n        final_redshift : float\n            The latest redshift for outputs to be included.  If None,\n            the final redshift of the simulation is used.  This can be\n            used in combination with either initial_time or\n            initial_redshift.\n            Default: None.\n        redshifts : array_like\n            A list of redshifts for which outputs will be found.\n            Default: None.\n        tolerance : float\n            Used in combination with \"times\" or \"redshifts\" keywords,\n            this is the tolerance within which outputs are accepted\n            given the requested times or redshifts.  If None, the\n            nearest output is always taken.\n            Default: None.\n        parallel : bool/int\n            If True, the generated DatasetSeries will divide the work\n            such that a single processor works on each dataset.  If an\n            integer is supplied, the work will be divided into that\n            number of jobs.\n            Default: True.\n        setup_function : callable, accepts a ds\n            This function will be called whenever a dataset is loaded.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> gs = yt.load_simulation(\"my_simulation.par\", \"Gadget\")\n\n        >>> gs.get_time_series(initial_redshift=10, final_time=(13.7, \"Gyr\"))\n\n        >>> gs.get_time_series(redshifts=[3, 2, 1, 0])\n\n        >>> # after calling get_time_series\n        >>> for ds in gs.piter():\n        ...     p = ProjectionPlot(ds, \"x\", (\"gas\", \"density\"))\n        ...     p.save()\n\n        >>> # An example using the setup_function keyword\n        >>> def print_time(ds):\n        ...     print(ds.current_time)\n        >>> gs.get_time_series(setup_function=print_time)\n        >>> for ds in gs:\n        ...     SlicePlot(ds, \"x\", \"Density\").save()\n\n        \"\"\"\n\n        if (\n            initial_redshift is not None or final_redshift is not None\n        ) and not self.cosmological_simulation:\n            raise InvalidSimulationTimeSeries(\n                \"An initial or final redshift has been given for a \"\n                + \"noncosmological simulation.\"\n            )\n\n        my_all_outputs = self.all_outputs\n        if not my_all_outputs:\n            DatasetSeries.__init__(\n                self, outputs=[], parallel=parallel, unit_base=self.unit_base\n            )\n            mylog.info(\"0 outputs loaded into time series.\")\n            return\n\n        # Apply selection criteria to the set.\n        if times is not None:\n            my_outputs = self._get_outputs_by_key(\n                \"time\", times, tolerance=tolerance, outputs=my_all_outputs\n            )\n\n        elif redshifts is not None:\n            my_outputs = self._get_outputs_by_key(\n                \"redshift\", redshifts, tolerance=tolerance, outputs=my_all_outputs\n            )\n\n        else:\n            if initial_time is not None:\n                if isinstance(initial_time, float):\n                    initial_time = self.quan(initial_time, \"code_time\")\n                elif isinstance(initial_time, tuple) and len(initial_time) == 2:\n                    initial_time = self.quan(*initial_time)\n                elif not isinstance(initial_time, unyt_array):\n                    raise RuntimeError(\n                        \"Error: initial_time must be given as a float or \"\n                        + \"tuple of (value, units).\"\n                    )\n            elif initial_redshift is not None:\n                my_initial_time = self.cosmology.t_from_z(initial_redshift)\n            else:\n                my_initial_time = self.initial_time\n\n            if final_time is not None:\n                if isinstance(final_time, float):\n                    final_time = self.quan(final_time, \"code_time\")\n                elif isinstance(final_time, tuple) and len(final_time) == 2:\n                    final_time = self.quan(*final_time)\n                elif not isinstance(final_time, unyt_array):\n                    raise RuntimeError(\n                        \"Error: final_time must be given as a float or \"\n                        + \"tuple of (value, units).\"\n                    )\n                my_final_time = final_time.in_units(\"s\")\n            elif final_redshift is not None:\n                my_final_time = self.cosmology.t_from_z(final_redshift)\n            else:\n                my_final_time = self.final_time\n\n            my_initial_time.convert_to_units(\"s\")\n            my_final_time.convert_to_units(\"s\")\n            my_times = np.array([a[\"time\"] for a in my_all_outputs])\n            my_indices = np.digitize([my_initial_time, my_final_time], my_times)\n            if my_initial_time == my_times[my_indices[0] - 1]:\n                my_indices[0] -= 1\n            my_outputs = my_all_outputs[my_indices[0] : my_indices[1]]\n\n        init_outputs = []\n        for output in my_outputs:\n            if os.path.exists(output[\"filename\"]):\n                init_outputs.append(output[\"filename\"])\n        if len(init_outputs) == 0 and len(my_outputs) > 0:\n            mylog.warning(\n                \"Could not find any datasets.  \"\n                \"Check the value of OutputDir in your parameter file.\"\n            )\n\n        DatasetSeries.__init__(\n            self,\n            outputs=init_outputs,\n            parallel=parallel,\n            setup_function=setup_function,\n            unit_base=self.unit_base,\n        )\n        mylog.info(\"%d outputs loaded into time series.\", len(init_outputs))\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Parses the parameter file and establishes the various\n        dictionaries.\n        \"\"\"\n\n        self.unit_base = {}\n\n        # Let's read the file\n        lines = open(self.parameter_filename).readlines()\n        comments = [\"%\", \";\"]\n        for line in (l.strip() for l in lines):\n            for comment in comments:\n                if comment in line:\n                    line = line[0 : line.find(comment)]\n            if len(line) < 2:\n                continue\n            param, vals = (i.strip() for i in line.split(None, 1))\n            # First we try to decipher what type of value it is.\n            vals = vals.split()\n            # Special case approaching.\n            if \"(do\" in vals:\n                vals = vals[:1]\n            if len(vals) == 0:\n                pcast = str  # Assume NULL output\n            else:\n                v = vals[0]\n                # Figure out if it's castable to floating point:\n                try:\n                    float(v)\n                except ValueError:\n                    pcast = str\n                else:\n                    if any(\".\" in v or \"e\" in v for v in vals):\n                        pcast = float\n                    elif v == \"inf\":\n                        pcast = str\n                    else:\n                        pcast = int\n            # Now we figure out what to do with it.\n            if param.startswith(\"Unit\"):\n                self.unit_base[param] = float(vals[0])\n            if len(vals) == 0:\n                vals = \"\"\n            elif len(vals) == 1:\n                vals = pcast(vals[0])\n            else:\n                vals = np.array([pcast(i) for i in vals])\n\n            self.parameters[param] = vals\n\n        # Domain dimensions for Gadget datasets are always 2x2x2 for octree\n        self.domain_dimensions = np.array([2, 2, 2])\n\n        if self.parameters[\"ComovingIntegrationOn\"]:\n            cosmo_attr = {\n                \"box_size\": \"BoxSize\",\n                \"omega_lambda\": \"OmegaLambda\",\n                \"omega_matter\": \"Omega0\",\n                \"hubble_constant\": \"HubbleParam\",\n            }\n            self.initial_redshift = 1.0 / self.parameters[\"TimeBegin\"] - 1.0\n            self.final_redshift = 1.0 / self.parameters[\"TimeMax\"] - 1.0\n            self.cosmological_simulation = 1\n            for a, v in cosmo_attr.items():\n                if v not in self.parameters:\n                    raise MissingParameter(self.parameter_filename, v)\n                setattr(self, a, self.parameters[v])\n            self.domain_left_edge = np.array([0.0, 0.0, 0.0])\n            self.domain_right_edge = (\n                np.array([1.0, 1.0, 1.0]) * self.parameters[\"BoxSize\"]\n            )\n        else:\n            self.cosmological_simulation = 0\n            self.omega_lambda = self.omega_matter = self.hubble_constant = 0.0\n\n    def _find_data_dir(self):\n        \"\"\"\n        Find proper location for datasets.  First look where parameter file\n        points, but if this doesn't exist then default to the current\n        directory.\n        \"\"\"\n        if self.parameters[\"OutputDir\"].startswith(\"/\"):\n            data_dir = self.parameters[\"OutputDir\"]\n        else:\n            data_dir = os.path.join(self.directory, self.parameters[\"OutputDir\"])\n        if not os.path.exists(data_dir):\n            mylog.info(\n                \"OutputDir not found at %s, instead using %s.\", data_dir, self.directory\n            )\n            data_dir = self.directory\n        self.data_dir = data_dir\n\n    def _snapshot_format(self, index=None):\n        \"\"\"\n        The snapshot filename for a given index.  Modify this for different\n        naming conventions.\n        \"\"\"\n\n        if self.parameters[\"NumFilesPerSnapshot\"] > 1:\n            suffix = \".0\"\n        else:\n            suffix = \"\"\n        if self.parameters[\"SnapFormat\"] == 3:\n            suffix += \".hdf5\"\n        if index is None:\n            count = \"*\"\n        else:\n            count = f\"{index:03}\"\n        filename = f\"{self.parameters['SnapshotFileBase']}_{count}{suffix}\"\n        return os.path.join(self.data_dir, filename)\n\n    def _get_all_outputs(self, *, find_outputs=False):\n        \"\"\"\n        Get all potential datasets and combine into a time-sorted list.\n        \"\"\"\n\n        # Find the data directory where the outputs are\n        self._find_data_dir()\n\n        # Create the set of outputs from which further selection will be done.\n        if find_outputs:\n            self._find_outputs()\n        else:\n            if self.parameters[\"OutputListOn\"]:\n                a_values = [\n                    float(a)\n                    for a in open(\n                        os.path.join(\n                            self.data_dir, self.parameters[\"OutputListFilename\"]\n                        ),\n                    ).readlines()\n                ]\n            else:\n                a_values = [float(self.parameters[\"TimeOfFirstSnapshot\"])]\n                time_max = float(self.parameters[\"TimeMax\"])\n                while a_values[-1] < time_max:\n                    if self.cosmological_simulation:\n                        a_values.append(\n                            a_values[-1] * self.parameters[\"TimeBetSnapshot\"]\n                        )\n                    else:\n                        a_values.append(\n                            a_values[-1] + self.parameters[\"TimeBetSnapshot\"]\n                        )\n                if a_values[-1] > time_max:\n                    a_values[-1] = time_max\n\n            if self.cosmological_simulation:\n                self.all_outputs = [\n                    {\"filename\": self._snapshot_format(i), \"redshift\": (1.0 / a - 1)}\n                    for i, a in enumerate(a_values)\n                ]\n\n                # Calculate times for redshift outputs.\n                for output in self.all_outputs:\n                    output[\"time\"] = self.cosmology.t_from_z(output[\"redshift\"])\n            else:\n                self.all_outputs = [\n                    {\n                        \"filename\": self._snapshot_format(i),\n                        \"time\": self.quan(a, \"code_time\"),\n                    }\n                    for i, a in enumerate(a_values)\n                ]\n\n            self.all_outputs.sort(key=lambda obj: obj[\"time\"].to_ndarray())\n\n    def _calculate_simulation_bounds(self):\n        \"\"\"\n        Figure out the starting and stopping time and redshift for the simulation.\n        \"\"\"\n\n        # Convert initial/final redshifts to times.\n        if self.cosmological_simulation:\n            self.initial_time = self.cosmology.t_from_z(self.initial_redshift)\n            self.initial_time.units.registry = self.unit_registry\n            self.final_time = self.cosmology.t_from_z(self.final_redshift)\n            self.final_time.units.registry = self.unit_registry\n\n        # If not a cosmology simulation, figure out the stopping criteria.\n        else:\n            if \"TimeBegin\" in self.parameters:\n                self.initial_time = self.quan(self.parameters[\"TimeBegin\"], \"code_time\")\n            else:\n                self.initial_time = self.quan(0.0, \"code_time\")\n\n            if \"TimeMax\" in self.parameters:\n                self.final_time = self.quan(self.parameters[\"TimeMax\"], \"code_time\")\n            else:\n                self.final_time = None\n            if \"TimeMax\" not in self.parameters:\n                raise NoStoppingCondition(self.parameter_filename)\n\n    def _find_outputs(self):\n        \"\"\"\n        Search for directories matching the data dump keywords.\n        If found, get dataset times py opening the ds.\n        \"\"\"\n        potential_outputs = glob.glob(self._snapshot_format())\n        self.all_outputs = self._check_for_outputs(potential_outputs)\n        self.all_outputs.sort(key=lambda obj: obj[\"time\"])\n        only_on_root(mylog.info, \"Located %d total outputs.\", len(self.all_outputs))\n\n        # manually set final time and redshift with last output\n        if self.all_outputs:\n            self.final_time = self.all_outputs[-1][\"time\"]\n            if self.cosmological_simulation:\n                self.final_redshift = self.all_outputs[-1][\"redshift\"]\n\n    def _check_for_outputs(self, potential_outputs):\n        r\"\"\"\n        Check a list of files to see if they are valid datasets.\n        \"\"\"\n\n        only_on_root(\n            mylog.info, \"Checking %d potential outputs.\", len(potential_outputs)\n        )\n\n        my_outputs = {}\n        for my_storage, output in parallel_objects(\n            potential_outputs, storage=my_outputs\n        ):\n            try:\n                ds = load(output)\n            except (FileNotFoundError, YTUnidentifiedDataType):\n                mylog.error(\"Failed to load %s\", output)\n                continue\n            my_storage.result = {\n                \"filename\": output,\n                \"time\": ds.current_time.in_units(\"s\"),\n            }\n            if ds.cosmological_simulation:\n                my_storage.result[\"redshift\"] = ds.current_redshift\n\n        my_outputs = [\n            my_output for my_output in my_outputs.values() if my_output is not None\n        ]\n        return my_outputs\n\n    def _write_cosmology_outputs(self, filename, outputs, start_index, decimals=3):\n        r\"\"\"\n        Write cosmology output parameters for a cosmology splice.\n        \"\"\"\n\n        mylog.info(\"Writing redshift output list to %s.\", filename)\n        f = open(filename, \"w\")\n        for output in outputs:\n            f.write(f\"{1.0 / (1.0 + output['redshift']):f}\\n\")\n        f.close()\n"
  },
  {
    "path": "yt/frontends/gadget/testing.py",
    "content": "import numpy as np\n\nfrom .data_structures import GadgetBinaryHeader, GadgetDataset\nfrom .definitions import gadget_field_specs, gadget_ptype_specs\nfrom .io import IOHandlerGadgetBinary\n\nvector_fields = dict(IOHandlerGadgetBinary._vector_fields)\n\nblock_ids = {\n    \"Coordinates\": \"POS\",\n    \"Velocities\": \"VEL\",\n    \"ParticleIDs\": \"ID\",\n    \"Mass\": \"MASS\",\n    \"InternalEnergy\": \"U\",\n    \"Density\": \"RHO\",\n    \"SmoothingLength\": \"HSML\",\n}\n\n\ndef write_record(fp, data, endian):\n    dtype = endian + \"i4\"\n    size = np.array(data.nbytes, dtype=dtype)\n    fp.write(size.tobytes())\n    fp.write(data.tobytes())\n    fp.write(size.tobytes())\n\n\ndef write_block(fp, data, endian, fmt, block_id):\n    assert fmt in [1, 2]\n    block_id = f\"{block_id:<4}\"\n    if fmt == 2:\n        block_id_dtype = np.dtype([(\"id\", \"S\", 4), (\"offset\", endian + \"i4\")])\n        block_id_data = np.zeros(1, dtype=block_id_dtype)\n        block_id_data[\"id\"] = block_id\n        block_id_data[\"offset\"] = data.nbytes + 8\n        write_record(fp, block_id_data, endian)\n    write_record(fp, data, endian)\n\n\ndef fake_gadget_binary(\n    filename=\"fake_gadget_binary\",\n    npart=(100, 100, 100, 0, 100, 0),\n    header_spec=\"default\",\n    field_spec=\"default\",\n    ptype_spec=\"default\",\n    endian=\"\",\n    fmt=2,\n):\n    \"\"\"Generate a fake Gadget binary snapshot.\"\"\"\n    header = GadgetBinaryHeader(filename, header_spec)\n    field_spec = GadgetDataset._setup_binary_spec(field_spec, gadget_field_specs)\n    ptype_spec = GadgetDataset._setup_binary_spec(ptype_spec, gadget_ptype_specs)\n    with open(filename, \"wb\") as fp:\n        # Generate and write header blocks\n        for i_header, header_spec in enumerate(header.spec):\n            specs = []\n            for name, dim, dtype in header_spec:\n                # workaround a FutureWarning in numpy where np.dtype(name, type, 1)\n                # will change meaning in a future version so\n                name_dtype = [name, endian + dtype, dim]\n                if dim == 1:\n                    name_dtype.pop()\n                specs.append(tuple(name_dtype))\n\n            header_dtype = np.dtype(specs)\n            header = np.zeros(1, dtype=header_dtype)\n            if i_header == 0:\n                header[\"Npart\"] = npart\n                header[\"Nall\"] = npart\n                header[\"NumFiles\"] = 1\n                header[\"BoxSize\"] = 1\n                header[\"HubbleParam\"] = 1\n            write_block(fp, header, endian, fmt, \"HEAD\")\n\n        npart = dict(zip(ptype_spec, npart, strict=True))\n        for fs in field_spec:\n            # Parse field name and particle type\n            if isinstance(fs, str):\n                field = fs\n                ptype = ptype_spec\n            else:\n                field, ptype = fs\n                if isinstance(ptype, str):\n                    ptype = (ptype,)\n            # Determine field dimension\n            if field in vector_fields:\n                dim = vector_fields[field]\n            else:\n                dim = 1\n            # Determine dtype (in numpy convention)\n            if field == \"ParticleIDs\":\n                dtype = \"u4\"\n            else:\n                dtype = \"f4\"\n            dtype = endian + dtype\n            # Generate and write field block\n            data = []\n            rng = np.random.default_rng()\n            for pt in ptype:\n                data += [rng.random((npart[pt], dim))]\n            data = np.concatenate(data).astype(dtype)\n            if field in block_ids:\n                block_id = block_ids[field]\n            else:\n                block_id = \"\"\n            write_block(fp, data, endian, fmt, block_id)\n    return filename\n"
  },
  {
    "path": "yt/frontends/gadget/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gadget/tests/test_gadget_pytest.py",
    "content": "import numpy as np\n\nimport yt\nfrom yt.testing import requires_file, requires_module\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\n@requires_file(\"snapshot_033/snap_033.0.hdf5\")\n@requires_module(\"h5py\")\ndef test_gadget_header_array_reduction(tmp_path):\n    # first get a real header\n    ds = yt.load(\"snapshot_033/snap_033.0.hdf5\")\n    hvals = ds._get_hvals()\n    hvals_orig = hvals.copy()\n    # wrap some of the scalar values in nested arrays\n    hvals[\"Redshift\"] = np.array([hvals[\"Redshift\"]])\n    hvals[\"Omega0\"] = np.array([[hvals[\"Omega0\"]]])\n\n    # drop those header values into a fake header-only file\n    tmp_snpshot_dir = tmp_path / \"snapshot_033\"\n    tmp_snpshot_dir.mkdir()\n    tmp_header_only_file = str(tmp_snpshot_dir / \"fake_gadget_header.hdf5\")\n    with h5py.File(tmp_header_only_file, mode=\"w\") as f:\n        headergrp = f.create_group(\"Header\")\n        for field, val in hvals.items():\n            headergrp.attrs[field] = val\n\n    # trick the dataset into using the header file and make sure the\n    # arrays are reduced\n    ds._input_filename = tmp_header_only_file\n    hvals = ds._get_hvals()\n    for attr in (\"Redshift\", \"Omega0\"):\n        assert hvals[attr] == hvals_orig[attr]\n        assert isinstance(hvals[attr], np.ndarray) is False\n"
  },
  {
    "path": "yt/frontends/gadget/tests/test_outputs.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom collections import OrderedDict\nfrom itertools import product\n\nimport yt\nfrom yt.frontends.gadget.api import GadgetDataset, GadgetHDF5Dataset\nfrom yt.frontends.gadget.testing import fake_gadget_binary\nfrom yt.testing import (\n    ParticleSelectionComparison,\n    assert_allclose_units,\n    requires_file,\n    requires_module,\n)\nfrom yt.utilities.answer_testing.framework import data_dir_load, requires_ds, sph_answer\n\nisothermal_h5 = \"IsothermalCollapse/snap_505.hdf5\"\nisothermal_bin = \"IsothermalCollapse/snap_505\"\nBE_Gadget = \"BigEndianGadgetBinary/BigEndianGadgetBinary\"\nLE_SnapFormat2 = \"Gadget3-snap-format2/Gadget3-snap-format2\"\nkeplerian_ring = \"KeplerianRing/keplerian_ring_0020.hdf5\"\nsnap_33 = \"snapshot_033/snap_033.0.hdf5\"\nsnap_33_dir = \"snapshot_033/\"\nmagneticum = \"MagneticumCluster/snap_132\"\nmagneticum_camels = \"magneticum_camels/snap_small_086.hdf5\"\n\n\n# This maps from field names to weight field names to use for projections\niso_fields = OrderedDict(\n    [\n        ((\"gas\", \"density\"), None),\n        ((\"gas\", \"temperature\"), None),\n        ((\"gas\", \"temperature\"), (\"gas\", \"density\")),\n        ((\"gas\", \"velocity_magnitude\"), None),\n    ]\n)\niso_kwargs = {\"bounding_box\": [[-3, 3], [-3, 3], [-3, 3]]}\n\n\n@requires_module(\"h5py\")\ndef test_gadget_binary():\n    header_specs = [\"default\", \"default+pad32\", [\"default\", \"pad32\"]]\n    curdir = os.getcwd()\n    tmpdir = tempfile.mkdtemp()\n    for header_spec, endian, fmt in product(header_specs, \"<>\", [1, 2]):\n        try:\n            fake_snap = fake_gadget_binary(\n                header_spec=header_spec, endian=endian, fmt=fmt\n            )\n        except FileNotFoundError:\n            # sometimes this happens for mysterious reasons\n            pass\n        ds = yt.load(fake_snap, header_spec=header_spec)\n        assert isinstance(ds, GadgetDataset)\n        ds.field_list\n        try:\n            os.remove(fake_snap)\n        except FileNotFoundError:\n            # sometimes this happens for mysterious reasons\n            pass\n    os.chdir(curdir)\n    shutil.rmtree(tmpdir)\n\n\n@requires_module(\"h5py\")\n@requires_file(isothermal_h5)\ndef test_gadget_hdf5():\n    assert isinstance(\n        data_dir_load(isothermal_h5, kwargs=iso_kwargs), GadgetHDF5Dataset\n    )\n\n\n@requires_file(keplerian_ring)\ndef test_non_cosmo_dataset():\n    \"\"\"\n    Non-cosmological datasets may not have the cosmological parameters in the\n    Header. The code should fall back gracefully when they are not present,\n    with the Redshift set to 0.\n    \"\"\"\n    data = data_dir_load(keplerian_ring)\n    assert data.current_redshift == 0.0\n    assert data.cosmological_simulation == 0\n\n\n@requires_ds(isothermal_h5)\ndef test_iso_collapse():\n    ds = data_dir_load(isothermal_h5, kwargs=iso_kwargs)\n    for test in sph_answer(ds, \"snap_505\", 2**17, iso_fields):\n        test_iso_collapse.__name__ = test.description\n        yield test\n\n\n@requires_ds(LE_SnapFormat2)\ndef test_pid_uniqueness():\n    \"\"\"\n    ParticleIDs should be unique.\n    \"\"\"\n    ds = data_dir_load(LE_SnapFormat2)\n    ad = ds.all_data()\n    pid = ad[\"all\", \"ParticleIDs\"]\n    assert len(pid) == len(set(pid.v))\n\n\n@requires_file(snap_33)\n@requires_file(snap_33_dir)\ndef test_multifile_read():\n    \"\"\"\n    Tests to make sure multi-file gadget snapshot can be loaded by passing '.0' file\n    or by passing the directory containing the multi-file snapshot.\n    \"\"\"\n    assert isinstance(data_dir_load(snap_33), GadgetDataset)\n    assert isinstance(data_dir_load(snap_33_dir), GadgetDataset)\n\n\n@requires_file(snap_33)\ndef test_particle_subselection():\n    # This checks that we correctly subselect from a dataset, first by making\n    # sure we get all the particles, then by comparing manual selections against\n    # them.\n    ds = data_dir_load(snap_33)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\n@requires_ds(BE_Gadget)\ndef test_bigendian_field_access():\n    ds = data_dir_load(BE_Gadget)\n    data = ds.all_data()\n    data[\"Halo\", \"Velocities\"]\n\n\nmag_fields = OrderedDict(\n    [\n        ((\"gas\", \"density\"), None),\n        ((\"gas\", \"temperature\"), None),\n        ((\"gas\", \"temperature\"), (\"gas\", \"density\")),\n        ((\"gas\", \"velocity_magnitude\"), None),\n        ((\"gas\", \"H_fraction\"), None),\n        ((\"gas\", \"C_fraction\"), None),\n    ]\n)\n\n\nmag_kwargs = {\n    \"long_ids\": True,\n    \"field_spec\": \"magneticum_box2_hr\",\n}\n\n\n@requires_ds(magneticum)\ndef test_magneticum():\n    ds = data_dir_load(magneticum, kwargs=mag_kwargs)\n    for test in sph_answer(ds, \"snap_132\", 3718111, mag_fields, center=\"max\"):\n        test_magneticum.__name__ = test.description\n        yield test\n\n\ncamels_kwargs = {\n    \"bounding_box\": [[8126.0, 22126.0], [5140.0, 19140.0], [5500.0, 19500.0]]\n}\n\n\n@requires_module(\"h5py\")\n@requires_file(magneticum_camels)\ndef test_magneticum_camels():\n    # In this test, we're only checking the metal fields since this\n    # is a dataset with special metal handling\n    ds = data_dir_load(magneticum_camels, kwargs=camels_kwargs)\n    dd = ds.all_data()\n    elems = [\n        \"He\",\n        \"C\",\n        \"Ca\",\n        \"O\",\n        \"N\",\n        \"Ne\",\n        \"Mg\",\n        \"S\",\n        \"Si\",\n        \"Fe\",\n        \"Na\",\n        \"Al\",\n        \"Ar\",\n        \"Ni\",\n        \"Ej\",\n    ]\n    metl = 0.0\n    heavy_mass = 0.0\n    for i, elem in enumerate(elems):\n        assert_allclose_units(\n            dd[\"gas\", f\"{elem}_mass\"], dd[\"PartType0\", f\"MetalMasses_{i:02d}\"]\n        )\n        heavy_mass += dd[\"PartType0\", f\"MetalMasses_{i:02d}\"]\n        if i > 0:\n            metl += dd[\"PartType0\", f\"MetalMasses_{i:02d}\"] / dd[\"PartType0\", \"Masses\"]\n    assert_allclose_units(dd[\"gas\", \"metallicity\"], metl)\n    assert_allclose_units(dd[\"gas\", \"H_mass\"], dd[\"PartType0\", \"Masses\"] - heavy_mass)\n"
  },
  {
    "path": "yt/frontends/gadget_fof/__init__.py",
    "content": "\"\"\"\nAPI for HaloCatalog frontend.\n\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/gadget_fof/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    GadgetFOFDataset,\n    GadgetFOFHaloContainer,\n    GadgetFOFHaloDataset,\n    GadgetFOFHaloParticleIndex,\n    GadgetFOFHDF5File,\n    GadgetFOFParticleIndex,\n)\nfrom .fields import GadgetFOFFieldInfo, GadgetFOFHaloFieldInfo\nfrom .io import IOHandlerGadgetFOFHaloHDF5, IOHandlerGadgetFOFHDF5\n"
  },
  {
    "path": "yt/frontends/gadget_fof/data_structures.py",
    "content": "import os\nimport weakref\nfrom collections import defaultdict\nfrom functools import cached_property, partial\n\nimport numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n)\nfrom yt.data_objects.static_output import ParticleDataset\nfrom yt.frontends.gadget.data_structures import _fix_unit_ordering\nfrom yt.frontends.gadget_fof.fields import GadgetFOFFieldInfo, GadgetFOFHaloFieldInfo\nfrom yt.frontends.halo_catalog.data_structures import HaloCatalogFile, HaloDataset\nfrom yt.funcs import only_on_root, setdefaultattr\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\nclass GadgetFOFParticleIndex(ParticleIndex):\n    def _calculate_particle_count(self):\n        \"\"\"\n        Calculate the total number of each type of particle.\n        \"\"\"\n        self.particle_count = {\n            ptype: sum(d.total_particles[ptype] for d in self.data_files)\n            for ptype in self.ds.particle_types_raw\n        }\n\n    def _calculate_particle_index_starts(self):\n        # Halo indices are not saved in the file, so we must count by hand.\n        # File 0 has halos 0 to N_0 - 1, file 1 has halos N_0 to N_0 + N_1 - 1, etc.\n        particle_count = defaultdict(int)\n        offset_count = 0\n        for data_file in self.data_files:\n            data_file.index_start = {\n                ptype: particle_count[ptype] for ptype in data_file.total_particles\n            }\n            data_file.offset_start = offset_count\n            for ptype in data_file.total_particles:\n                particle_count[ptype] += data_file.total_particles[ptype]\n            offset_count += data_file.total_offset\n\n        self._halo_index_start = {\n            ptype: np.array(\n                [data_file.index_start[ptype] for data_file in self.data_files]\n            )\n            for ptype in self.ds.particle_types_raw\n        }\n\n    def _calculate_file_offset_map(self):\n        # After the FOF is performed, a load-balancing step redistributes halos\n        # and then writes more fields.  Here, for each file, we create a list of\n        # files which contain the rest of the redistributed particles.\n        ifof = np.array(\n            [data_file.total_particles[\"Group\"] for data_file in self.data_files]\n        )\n        isub = np.array([data_file.total_offset for data_file in self.data_files])\n        subend = isub.cumsum()\n        fofend = ifof.cumsum()\n        istart = np.digitize(fofend - ifof, subend - isub) - 1\n        iend = np.clip(np.digitize(fofend, subend), 0, ifof.size - 2)\n        for i, data_file in enumerate(self.data_files):\n            data_file.offset_files = self.data_files[istart[i] : iend[i] + 1]\n\n    def _detect_output_fields(self):\n        field_list = []\n        units = {}\n        found_fields = {\n            ptype: False for ptype, pnum in self.particle_count.items() if pnum > 0\n        }\n\n        for data_file in self.data_files:\n            fl, _units = self.io._identify_fields(data_file)\n            units.update(_units)\n            field_list.extend([f for f in fl if f not in field_list])\n            for ptype in found_fields:\n                found_fields[ptype] |= data_file.total_particles[ptype]\n            if all(found_fields.values()):\n                break\n\n        self.field_list = field_list\n        ds = self.dataset\n        ds.particle_types = tuple({pt for pt, ds in field_list})\n        ds.field_units.update(units)\n        ds.particle_types_raw = ds.particle_types\n\n    def _setup_filenames(self):\n        template = self.ds.filename_template\n        ndoms = self.ds.file_count\n        cls = self.ds._file_class\n        self.data_files = [\n            cls(self.ds, self.io, template % {\"num\": i}, i, frange=None)\n            for i in range(ndoms)\n        ]\n\n    def _setup_data_io(self):\n        super()._setup_data_io()\n        self._calculate_particle_count()\n        self._calculate_particle_index_starts()\n        self._calculate_file_offset_map()\n\n\nclass GadgetFOFHDF5File(HaloCatalogFile):\n    def __init__(self, ds, io, filename, file_id, frange):\n        with h5py.File(filename, mode=\"r\") as f:\n            self.header = {str(field): val for field, val in f[\"Header\"].attrs.items()}\n            self.group_length_sum = (\n                f[\"Group/GroupLen\"][()].sum() if \"Group/GroupLen\" in f else 0\n            )\n            self.group_subs_sum = (\n                f[\"Group/GroupNsubs\"][()].sum() if \"Group/GroupNsubs\" in f else 0\n            )\n        self.total_ids = self.header[\"Nids_ThisFile\"]\n        self.total_offset = 0\n        super().__init__(ds, io, filename, file_id, frange)\n\n    def _read_particle_positions(self, ptype, f=None):\n        \"\"\"\n        Read all particle positions in this file.\n        \"\"\"\n\n        if f is None:\n            close = True\n            f = h5py.File(self.filename, mode=\"r\")\n        else:\n            close = False\n\n        pos = f[ptype][f\"{ptype}Pos\"][()].astype(\"float64\")\n\n        if close:\n            f.close()\n\n        return pos\n\n\nclass GadgetFOFDataset(ParticleDataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = GadgetFOFParticleIndex\n    _file_class = GadgetFOFHDF5File\n    _field_info_class = GadgetFOFFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"gadget_fof_hdf5\",\n        index_order=None,\n        index_filename=None,\n        unit_base=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        if unit_base is not None and \"UnitLength_in_cm\" in unit_base:\n            # We assume this is comoving, because in the absence of comoving\n            # integration the redshift will be zero.\n            unit_base[\"cmcm\"] = 1.0 / unit_base[\"UnitLength_in_cm\"]\n        self._unit_base = unit_base\n        if units_override is not None:\n            raise RuntimeError(\n                \"units_override is not supported for GadgetFOFDataset. \"\n                + \"Use unit_base instead.\"\n            )\n        super().__init__(\n            filename,\n            dataset_type,\n            units_override=units_override,\n            index_order=index_order,\n            index_filename=index_filename,\n            unit_system=unit_system,\n        )\n\n    def add_field(self, *args, **kwargs):\n        super().add_field(*args, **kwargs)\n        self._halos_ds.add_field(*args, **kwargs)\n\n    @property\n    def halos_field_list(self):\n        return self._halos_ds.field_list\n\n    @property\n    def halos_derived_field_list(self):\n        return self._halos_ds.derived_field_list\n\n    @cached_property\n    def _halos_ds(self):\n        return GadgetFOFHaloDataset(self)\n\n    def _setup_classes(self):\n        super()._setup_classes()\n        self.halo = partial(GadgetFOFHaloContainer, ds=self._halos_ds)\n\n    def _parse_parameter_file(self):\n        with h5py.File(self.parameter_filename, mode=\"r\") as f:\n            self.parameters = {\n                str(field): val for field, val in f[\"Header\"].attrs.items()\n            }\n\n        self.dimensionality = 3\n        self.refine_by = 2\n\n        # Set standard values\n        self.domain_left_edge = np.zeros(3, \"float64\")\n        self.domain_right_edge = np.ones(3, \"float64\") * self.parameters[\"BoxSize\"]\n        self.domain_dimensions = np.ones(3, \"int32\")\n        self.cosmological_simulation = 1\n        self._periodicity = (True, True, True)\n        self.current_redshift = self.parameters[\"Redshift\"]\n        self.omega_lambda = self.parameters[\"OmegaLambda\"]\n        self.omega_matter = self.parameters[\"Omega0\"]\n        self.hubble_constant = self.parameters[\"HubbleParam\"]\n        cosmology = Cosmology(\n            hubble_constant=self.hubble_constant,\n            omega_matter=self.omega_matter,\n            omega_lambda=self.omega_lambda,\n        )\n        self.current_time = cosmology.t_from_z(self.current_redshift)\n\n        prefix = os.path.abspath(\n            os.path.join(\n                os.path.dirname(self.parameter_filename),\n                os.path.basename(self.parameter_filename).split(\".\", 1)[0],\n            )\n        )\n        suffix = self.parameter_filename.rsplit(\".\", 1)[-1]\n        self.filename_template = f\"{prefix}.%(num)i.{suffix}\"\n        self.file_count = self.parameters[\"NumFiles\"]\n        self.particle_types = (\"Group\", \"Subhalo\")\n        self.particle_types_raw = (\"Group\", \"Subhalo\")\n\n    def _set_code_unit_attributes(self):\n        # Set a sane default for cosmological simulations.\n        if self._unit_base is None and self.cosmological_simulation == 1:\n            only_on_root(mylog.info, \"Assuming length units are in Mpc/h (comoving)\")\n            self._unit_base = {\"length\": (1.0, \"Mpccm/h\")}\n        # The other same defaults we will use from the standard Gadget\n        # defaults.\n        unit_base = self._unit_base or {}\n\n        if \"length\" in unit_base:\n            length_unit = unit_base[\"length\"]\n        elif \"UnitLength_in_cm\" in unit_base:\n            if self.cosmological_simulation == 0:\n                length_unit = (unit_base[\"UnitLength_in_cm\"], \"cm\")\n            else:\n                length_unit = (unit_base[\"UnitLength_in_cm\"], \"cmcm/h\")\n        else:\n            raise RuntimeError\n        length_unit = _fix_unit_ordering(length_unit)\n        setdefaultattr(self, \"length_unit\", self.quan(length_unit[0], length_unit[1]))\n\n        if \"velocity\" in unit_base:\n            velocity_unit = unit_base[\"velocity\"]\n        elif \"UnitVelocity_in_cm_per_s\" in unit_base:\n            velocity_unit = (unit_base[\"UnitVelocity_in_cm_per_s\"], \"cm/s\")\n        else:\n            if self.cosmological_simulation == 0:\n                velocity_unit = (1e5, \"cm/s\")\n            else:\n                velocity_unit = (1e5, \"cm/s * sqrt(a)\")\n        velocity_unit = _fix_unit_ordering(velocity_unit)\n        setdefaultattr(\n            self, \"velocity_unit\", self.quan(velocity_unit[0], velocity_unit[1])\n        )\n\n        # We set hubble_constant = 1.0 for non-cosmology, so this is safe.\n        # Default to 1e10 Msun/h if mass is not specified.\n        if \"mass\" in unit_base:\n            mass_unit = unit_base[\"mass\"]\n        elif \"UnitMass_in_g\" in unit_base:\n            if self.cosmological_simulation == 0:\n                mass_unit = (unit_base[\"UnitMass_in_g\"], \"g\")\n            else:\n                mass_unit = (unit_base[\"UnitMass_in_g\"], \"g/h\")\n        else:\n            # Sane default\n            mass_unit = (1.0, \"1e10*Msun/h\")\n        mass_unit = _fix_unit_ordering(mass_unit)\n        setdefaultattr(self, \"mass_unit\", self.quan(mass_unit[0], mass_unit[1]))\n\n        if \"time\" in unit_base:\n            time_unit = unit_base[\"time\"]\n        elif \"UnitTime_in_s\" in unit_base:\n            time_unit = (unit_base[\"UnitTime_in_s\"], \"s\")\n        else:\n            tu = (self.length_unit / self.velocity_unit).to(\"yr/h\")\n            time_unit = (tu.d, tu.units)\n        setdefaultattr(self, \"time_unit\", self.quan(time_unit[0], time_unit[1]))\n\n    def __str__(self):\n        return self.basename.split(\".\", 1)[0]\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        need_groups = [\"Group\", \"Header\", \"Subhalo\"]\n        veto_groups = [\"FOF\"]\n        valid = True\n        try:\n            fh = h5py.File(filename, mode=\"r\")\n            valid = all(ng in fh[\"/\"] for ng in need_groups) and not any(\n                vg in fh[\"/\"] for vg in veto_groups\n            )\n            fh.close()\n        except Exception:\n            valid = False\n            pass\n        return valid\n\n\nclass GadgetFOFHaloParticleIndex(GadgetFOFParticleIndex):\n    def __init__(self, ds, dataset_type):\n        self.real_ds = weakref.proxy(ds.real_ds)\n        super().__init__(ds, dataset_type)\n\n    def _create_halo_id_table(self):\n        \"\"\"\n        Create a list of halo start ids so we know which file\n        contains particles for a given halo.  Note, the halo ids\n        are distributed over all files and so the ids for a given\n        halo are likely stored in a different file than the halo\n        itself.\n        \"\"\"\n\n        self._halo_id_number = np.array(\n            [data_file.total_ids for data_file in self.data_files]\n        )\n        self._halo_id_end = self._halo_id_number.cumsum()\n        self._halo_id_start = self._halo_id_end - self._halo_id_number\n\n        self._group_length_sum = np.array(\n            [data_file.group_length_sum for data_file in self.data_files]\n        )\n\n    def _detect_output_fields(self):\n        field_list = []\n        scalar_field_list = []\n        units = {}\n        found_fields = {\n            ptype: False for ptype, pnum in self.particle_count.items() if pnum > 0\n        }\n        has_ids = False\n\n        for data_file in self.data_files:\n            fl, sl, idl, _units = self.io._identify_fields(data_file)\n            units.update(_units)\n            field_list.extend([f for f in fl if f not in field_list])\n            scalar_field_list.extend([f for f in sl if f not in scalar_field_list])\n            for ptype in found_fields:\n                found_fields[ptype] |= data_file.total_particles[ptype]\n            has_ids |= len(idl) > 0\n            if all(found_fields.values()) and has_ids:\n                break\n\n        self.field_list = field_list\n        self.scalar_field_list = scalar_field_list\n        ds = self.dataset\n        ds.scalar_field_list = scalar_field_list\n        ds.particle_types = tuple({pt for pt, ds in field_list})\n        ds.field_units.update(units)\n        ds.particle_types_raw = ds.particle_types\n\n    def _identify_base_chunk(self, dobj):\n        pass\n\n    def _read_particle_fields(self, fields, dobj, chunk=None):\n        if len(fields) == 0:\n            return {}, []\n        fields_to_read, fields_to_generate = self._split_fields(fields)\n        if len(fields_to_read) == 0:\n            return {}, fields_to_generate\n        fields_to_return = self.io._read_particle_selection(dobj, fields_to_read)\n        return fields_to_return, fields_to_generate\n\n    def _get_halo_file_indices(self, ptype, identifiers):\n        return np.digitize(identifiers, self._halo_index_start[ptype], right=False) - 1\n\n    def _get_halo_scalar_index(self, ptype, identifier):\n        i_scalar = self._get_halo_file_indices(ptype, [identifier])[0]\n        scalar_index = identifier - self._halo_index_start[ptype][i_scalar]\n        return scalar_index\n\n    def _get_halo_values(self, ptype, identifiers, fields, f=None):\n        \"\"\"\n        Get field values for halos.  IDs are likely to be\n        sequential (or at least monotonic), but not necessarily\n        all within the same file.\n\n        This does not do much to minimize file i/o, but with\n        halos randomly distributed across files, there's not\n        much more we can do.\n        \"\"\"\n\n        # if a file is already open, don't open it again\n        filename = None if f is None else f.filename\n\n        data = defaultdict(lambda: np.empty(identifiers.size))\n        i_scalars = self._get_halo_file_indices(ptype, identifiers)\n        for i_scalar in np.unique(i_scalars):\n            target = i_scalars == i_scalar\n            scalar_indices = identifiers - self._halo_index_start[ptype][i_scalar]\n\n            # only open file if it's not already open\n            my_f = (\n                f\n                if self.data_files[i_scalar].filename == filename\n                else h5py.File(self.data_files[i_scalar].filename, mode=\"r\")\n            )\n\n            for field in fields:\n                data[field][target] = my_f[os.path.join(ptype, field)][()][\n                    scalar_indices[target]\n                ]\n\n            if self.data_files[i_scalar].filename != filename:\n                my_f.close()\n\n        return data\n\n    def _setup_data_io(self):\n        super()._setup_data_io()\n        self._create_halo_id_table()\n\n\nclass GadgetFOFHaloDataset(HaloDataset):\n    _index_class = GadgetFOFHaloParticleIndex\n    _file_class = GadgetFOFHDF5File\n    _field_info_class = GadgetFOFHaloFieldInfo\n\n    def __init__(self, ds, dataset_type=\"gadget_fof_halo_hdf5\"):\n        super().__init__(ds, dataset_type)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        # This class is not meant to be instantiated by yt.load()\n        return False\n\n\nclass GadgetFOFHaloContainer(YTSelectionContainer):\n    \"\"\"\n    Create a data container to get member particles and individual\n    values from halos and subhalos. Halo mass, position, and\n    velocity are set as attributes. Halo IDs are accessible\n    through the field, \"member_ids\".  Other fields that are one\n    value per halo are accessible as normal.  The field list for\n    halo objects can be seen in `ds.halos_field_list`.\n\n    Parameters\n    ----------\n    ptype : string\n        The type of halo, either \"Group\" for the main halo or\n        \"Subhalo\" for subhalos.\n    particle_identifier : int or tuple of ints\n        The halo or subhalo id.  If requesting a subhalo, the id\n        can also be given as a tuple of the main halo id and\n        subgroup id, such as (1, 4) for subgroup 4 of halo 1.\n\n    Attributes\n    ----------\n    particle_identifier : int\n        The id of the halo or subhalo.\n    group_identifier : int\n        For subhalos, the id of the enclosing halo.\n    subgroup_identifier : int\n        For subhalos, the relative id of the subhalo within\n        the enclosing halo.\n    particle_number : int\n        Number of particles in the halo.\n    mass : float\n        Halo mass.\n    position : array of floats\n        Halo position.\n    velocity : array of floats\n        Halo velocity.\n\n    Note\n    ----\n    Relevant Fields:\n\n     * particle_number - number of particles\n     * subhalo_number - number of subhalos\n     * group_identifier - id of parent group for subhalos\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"gadget_halos/data/groups_298/fof_subhalo_tab_298.0.hdf5\")\n\n    >>> halo = ds.halo(\"Group\", 0)\n    >>> print(halo.mass)\n    13256.5517578 code_mass\n    >>> print(halo.position)\n    [ 16.18603706   6.95965052  12.52694607] code_length\n    >>> print(halo.velocity)\n    [ 6943694.22793569  -762788.90647454  -794749.63819757] cm/s\n    >>> print(halo[\"Group_R_Crit200\"])\n    [ 0.79668683] code_length\n\n    >>> # particle ids for this halo\n    >>> print(halo[\"member_ids\"])\n    [  723631.   690744.   854212. ...,   608589.   905551.  1147449.] dimensionless\n\n    >>> # get the first subhalo of this halo\n    >>> subhalo = ds.halo(\"Subhalo\", (0, 0))\n    >>> print(subhalo[\"member_ids\"])\n    [  723631.   690744.   854212. ...,   808362.   956359.  1248821.] dimensionless\n\n    \"\"\"\n\n    _type_name = \"halo\"\n    _con_args = (\"ptype\", \"particle_identifier\")\n    _spatial = False\n    # Do not register it to prevent .halo from being attached to all datasets\n    _skip_add = True\n\n    def __init__(self, ptype, particle_identifier, ds=None):\n        if ptype not in ds.particle_types_raw:\n            raise RuntimeError(\n                f'Possible halo types are {ds.particle_types_raw}, supplied \"{ptype}\".'\n            )\n\n        self.ptype = ptype\n        self._current_particle_type = ptype\n        super().__init__(ds, {})\n\n        if ptype == \"Subhalo\" and isinstance(particle_identifier, tuple):\n            self.group_identifier, self.subgroup_identifier = particle_identifier\n            my_data = self.index._get_halo_values(\n                \"Group\", np.array([self.group_identifier]), [\"GroupFirstSub\"]\n            )\n            self.particle_identifier = np.int64(\n                my_data[\"GroupFirstSub\"][0] + self.subgroup_identifier\n            )\n        else:\n            self.particle_identifier = particle_identifier\n\n        if self.particle_identifier >= self.index.particle_count[ptype]:\n            raise RuntimeError(\n                f\"{ptype} {particle_identifier} requested, \"\n                f\"but only {self.index.particle_count[ptype]} {ptype} objects exist.\"\n            )\n\n        # Find the file that has the scalar values for this halo.\n        i_scalar = self.index._get_halo_file_indices(ptype, [self.particle_identifier])[\n            0\n        ]\n        self.scalar_data_file = self.index.data_files[i_scalar]\n\n        # index within halo arrays that corresponds to this halo\n        self.scalar_index = self.index._get_halo_scalar_index(\n            ptype, self.particle_identifier\n        )\n\n        halo_fields = [f\"{ptype}Len\"]\n        if ptype == \"Subhalo\":\n            halo_fields.append(\"SubhaloGrNr\")\n        my_data = self.index._get_halo_values(\n            ptype, np.array([self.particle_identifier]), halo_fields\n        )\n        self.particle_number = np.int64(my_data[f\"{ptype}Len\"][0])\n\n        if ptype == \"Group\":\n            self.group_identifier = self.particle_identifier\n            id_offset = 0\n            # index of file that has scalar values for the group\n            g_scalar = i_scalar\n            group_index = self.scalar_index\n\n        # If a subhalo, find the index of the parent.\n        elif ptype == \"Subhalo\":\n            self.group_identifier = np.int64(my_data[\"SubhaloGrNr\"][0])\n\n            # Find the file that has the scalar values for the parent group.\n            g_scalar = self.index._get_halo_file_indices(\n                \"Group\", [self.group_identifier]\n            )[0]\n\n            # index within halo arrays that corresponds to the paent group\n            group_index = self.index._get_halo_scalar_index(\n                \"Group\", self.group_identifier\n            )\n\n            my_data = self.index._get_halo_values(\n                \"Group\",\n                np.array([self.group_identifier]),\n                [\"GroupNsubs\", \"GroupFirstSub\"],\n            )\n            self.subgroup_identifier = self.particle_identifier - np.int64(\n                my_data[\"GroupFirstSub\"][0]\n            )\n            parent_subhalos = my_data[\"GroupNsubs\"][0]\n\n            mylog.debug(\n                \"Subhalo %d is subgroup %s of %d in group %d.\",\n                self.particle_identifier,\n                self.subgroup_identifier,\n                parent_subhalos,\n                self.group_identifier,\n            )\n\n            # ids of the sibling subhalos that come before this one\n            if self.subgroup_identifier > 0:\n                sub_ids = np.arange(\n                    self.particle_identifier - self.subgroup_identifier,\n                    self.particle_identifier,\n                )\n                my_data = self.index._get_halo_values(\n                    \"Subhalo\", sub_ids, [\"SubhaloLen\"]\n                )\n                id_offset = my_data[\"SubhaloLen\"].sum(dtype=np.int64)\n            else:\n                id_offset = 0\n\n        # Calculate the starting index for the member particles.\n        # First, add up all the particles in the earlier files.\n        all_id_start = self.index._group_length_sum[:g_scalar].sum(dtype=np.int64)\n\n        # Now add the halos in this file that come before.\n        with h5py.File(self.index.data_files[g_scalar].filename, mode=\"r\") as f:\n            all_id_start += f[\"Group\"][\"GroupLen\"][:group_index].sum(dtype=np.int64)\n\n        # Add the subhalo offset.\n        all_id_start += id_offset\n\n        # indices of first and last files containing member particles\n        i_start = (\n            np.digitize([all_id_start], self.index._halo_id_start, right=False)[0] - 1\n        )\n        i_end = np.digitize(\n            [all_id_start + self.particle_number], self.index._halo_id_end, right=True\n        )[0]\n        self.field_data_files = self.index.data_files[i_start : i_end + 1]\n\n        # starting and ending indices for each file containing particles\n        self.field_data_start = (\n            all_id_start - self.index._halo_id_start[i_start : i_end + 1]\n        ).clip(min=0)\n        self.field_data_start = self.field_data_start.astype(np.int64)\n        self.field_data_end = (\n            all_id_start\n            + self.particle_number\n            - self.index._halo_id_start[i_start : i_end + 1]\n        ).clip(max=self.index._halo_id_number[i_start : i_end + 1])\n        self.field_data_end = self.field_data_end.astype(np.int64)\n\n        for attr in [\"mass\", \"position\", \"velocity\"]:\n            setattr(self, attr, self[self.ptype, f\"particle_{attr}\"][0])\n\n    def __repr__(self):\n        return f\"{self.ds}_{self.ptype}_{self.particle_identifier:09}\"\n"
  },
  {
    "path": "yt/frontends/gadget_fof/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\nm_units = \"code_mass\"\np_units = \"code_length\"\nv_units = \"code_velocity\"\n\n_pnums = 6\n_type_fields: KnownFieldsT = tuple(\n    (f\"{ptype}{field}Type_{pnum}\", (units, [], None))\n    for pnum in range(_pnums)\n    for field, units in ((\"Mass\", m_units), (\"Len\", p_units))\n    for ptype in (\"Group\", \"Subhalo\")\n)\n_sub_type_fields: KnownFieldsT = tuple(\n    (f\"Subhalo{field}Type_{pnum}\", (units, [], None))\n    for pnum in range(_pnums)\n    for field, units in (\n        (\"HalfmassRad\", p_units),\n        (\"MassInHalfRad\", m_units),\n        (\"MassInMaxRad\", m_units),\n        (\"MassInRad\", m_units),\n    )\n)\n\n_particle_fields: KnownFieldsT = (\n    (\"GroupPos_0\", (p_units, [\"Group\", \"particle_position_x\"], None)),\n    (\"GroupPos_1\", (p_units, [\"Group\", \"particle_position_y\"], None)),\n    (\"GroupPos_2\", (p_units, [\"Group\", \"particle_position_z\"], None)),\n    (\"GroupVel_0\", (v_units, [\"Group\", \"particle_velocity_x\"], None)),\n    (\"GroupVel_1\", (v_units, [\"Group\", \"particle_velocity_y\"], None)),\n    (\"GroupVel_2\", (v_units, [\"Group\", \"particle_velocity_z\"], None)),\n    (\"GroupMass\", (m_units, [\"Group\", \"particle_mass\"], None)),\n    (\"GroupLen\", (\"\", [\"Group\", \"particle_number\"], None)),\n    (\"GroupNsubs\", (\"\", [\"Group\", \"subhalo_number\"], None)),\n    (\"GroupFirstSub\", (\"\", [], None)),\n    (\"Group_M_Crit200\", (m_units, [], None)),\n    (\"Group_M_Crit500\", (m_units, [], None)),\n    (\"Group_M_Mean200\", (m_units, [], None)),\n    (\"Group_M_TopHat200\", (m_units, [], None)),\n    (\"Group_R_Crit200\", (p_units, [], None)),\n    (\"Group_R_Crit500\", (p_units, [], None)),\n    (\"Group_R_Mean200\", (p_units, [], None)),\n    (\"Group_R_TopHat200\", (p_units, [], None)),\n    (\"SubhaloPos_0\", (p_units, [\"Subhalo\", \"particle_position_x\"], None)),\n    (\"SubhaloPos_1\", (p_units, [\"Subhalo\", \"particle_position_y\"], None)),\n    (\"SubhaloPos_2\", (p_units, [\"Subhalo\", \"particle_position_z\"], None)),\n    (\"SubhaloVel_0\", (v_units, [\"Subhalo\", \"particle_velocity_x\"], None)),\n    (\"SubhaloVel_1\", (v_units, [\"Subhalo\", \"particle_velocity_y\"], None)),\n    (\"SubhaloVel_2\", (v_units, [\"Subhalo\", \"particle_velocity_z\"], None)),\n    (\"SubhaloMass\", (m_units, [\"Subhalo\", \"particle_mass\"], None)),\n    (\"SubhaloLen\", (\"\", [\"Subhalo\", \"particle_number\"], None)),\n    (\"SubhaloCM_0\", (p_units, [\"Subhalo\", \"center_of_mass_x\"], None)),\n    (\"SubhaloCM_1\", (p_units, [\"Subhalo\", \"center_of_mass_y\"], None)),\n    (\"SubhaloCM_2\", (p_units, [\"Subhalo\", \"center_of_mass_z\"], None)),\n    (\"SubhaloSpin_0\", (\"\", [\"Subhalo\", \"spin_x\"], None)),\n    (\"SubhaloSpin_1\", (\"\", [\"Subhalo\", \"spin_y\"], None)),\n    (\"SubhaloSpin_2\", (\"\", [\"Subhalo\", \"spin_z\"], None)),\n    (\"SubhaloGrNr\", (\"\", [\"Subhalo\", \"group_identifier\"], None)),\n    (\"SubhaloHalfmassRad\", (p_units, [], None)),\n    (\"SubhaloIDMostbound\", (\"\", [], None)),\n    (\"SubhaloMassInHalfRad\", (m_units, [], None)),\n    (\"SubhaloMassInMaxRad\", (m_units, [], None)),\n    (\"SubhaloMassInRad\", (m_units, [], None)),\n    (\"SubhaloParent\", (\"\", [], None)),\n    (\"SubhaloVelDisp\", (v_units, [\"Subhalo\", \"velocity_dispersion\"], None)),\n    (\"SubhaloVmax\", (v_units, [], None)),\n    (\"SubhaloVmaxRad\", (p_units, [], None)),\n    *_type_fields,\n    *_sub_type_fields,\n)\n\n\nclass GadgetFOFFieldInfo(FieldInfoContainer):\n    known_particle_fields = _particle_fields\n\n    # these are extra fields to be created for the \"all\" particle type\n    extra_union_fields = (\n        (p_units, \"particle_position_x\"),\n        (p_units, \"particle_position_y\"),\n        (p_units, \"particle_position_z\"),\n        (v_units, \"particle_velocity_x\"),\n        (v_units, \"particle_velocity_y\"),\n        (v_units, \"particle_velocity_z\"),\n        (m_units, \"particle_mass\"),\n        (\"\", \"particle_number\"),\n        (\"\", \"particle_ones\"),\n    )\n\n\nclass GadgetFOFHaloFieldInfo(FieldInfoContainer):\n    known_particle_fields = _particle_fields + ((\"ID\", (\"\", [\"member_ids\"], None)),)\n"
  },
  {
    "path": "yt/frontends/gadget_fof/io.py",
    "content": "from collections import defaultdict\n\nimport numpy as np\n\nfrom yt.funcs import mylog\nfrom yt.utilities.io_handler import BaseParticleIOHandler\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\nclass IOHandlerGadgetFOFHDF5(BaseParticleIOHandler):\n    _dataset_type = \"gadget_fof_hdf5\"\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        self.offset_fields = set()\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError(\n            \"IOHandlerGadgetFOFHDF5 _read_fluid_selection not implemented yet\"\n        )\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This will read chunks and yield the results.\n        for data_file in self._sorted_chunk_iterator(chunks):\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype in sorted(ptf):\n                    coords = data_file._get_particle_positions(ptype, f=f)\n                    if coords is None:\n                        continue\n                    x = coords[:, 0]\n                    y = coords[:, 1]\n                    z = coords[:, 2]\n                    yield ptype, (x, y, z), 0.0\n\n    def _yield_coordinates(self, data_file):\n        ptypes = self.ds.particle_types_raw\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            for ptype in sorted(ptypes):\n                pcount = data_file.total_particles[ptype]\n                if pcount == 0:\n                    continue\n                coords = f[ptype][f\"{ptype}Pos\"][()].astype(\"float64\")\n                coords = np.resize(coords, (pcount, 3))\n                yield ptype, coords\n\n    def _read_offset_particle_field(self, field, data_file, fh):\n        field_data = np.empty(data_file.total_particles[\"Group\"], dtype=\"float64\")\n        fofindex = (\n            np.arange(data_file.total_particles[\"Group\"])\n            + data_file.index_start[\"Group\"]\n        )\n        for offset_file in data_file.offset_files:\n            if fh.filename == offset_file.filename:\n                ofh = fh\n            else:\n                ofh = h5py.File(offset_file.filename, mode=\"r\")\n            subindex = np.arange(offset_file.total_offset) + offset_file.offset_start\n            substart = max(fofindex[0] - subindex[0], 0)\n            subend = min(fofindex[-1] - subindex[0], subindex.size - 1)\n            fofstart = substart + subindex[0] - fofindex[0]\n            fofend = subend + subindex[0] - fofindex[0]\n            field_data[fofstart : fofend + 1] = ofh[\"Subhalo\"][field][\n                substart : subend + 1\n            ]\n        return field_data\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # Now we have all the sizes, and we can allocate\n        for data_file in self._sorted_chunk_iterator(chunks):\n            si, ei = data_file.start, data_file.end\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype, field_list in sorted(ptf.items()):\n                    pcount = data_file.total_particles[ptype]\n                    if pcount == 0:\n                        continue\n                    coords = data_file._get_particle_positions(ptype, f=f)\n                    x = coords[:, 0]\n                    y = coords[:, 1]\n                    z = coords[:, 2]\n                    mask = selector.select_points(x, y, z, 0.0)\n                    del x, y, z\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        if field in self.offset_fields:\n                            field_data = self._read_offset_particle_field(\n                                field, data_file, f\n                            )\n                        else:\n                            if field == \"particle_identifier\":\n                                field_data = (\n                                    np.arange(data_file.total_particles[ptype])\n                                    + data_file.index_start[ptype]\n                                )\n                            elif field in f[ptype]:\n                                field_data = f[ptype][field][()].astype(\"float64\")\n                            else:\n                                fname = field[: field.rfind(\"_\")]\n                                field_data = f[ptype][fname][()].astype(\"float64\")\n                                my_div = field_data.size / pcount\n                                if my_div > 1:\n                                    findex = int(field[field.rfind(\"_\") + 1 :])\n                                    field_data = field_data[:, findex]\n                        data = field_data[si:ei][mask]\n                        yield (ptype, field), data\n\n    def _count_particles(self, data_file):\n        si, ei = data_file.start, data_file.end\n        pcount = {\n            \"Group\": data_file.header[\"Ngroups_ThisFile\"],\n            \"Subhalo\": data_file.header[\"Nsubgroups_ThisFile\"],\n        }\n        if None not in (si, ei):\n            for ptype in pcount:\n                pcount[ptype] = np.clip(pcount[ptype] - si, 0, ei - si)\n        return pcount\n\n    def _identify_fields(self, data_file):\n        fields = []\n        pcount = data_file.total_particles\n        if sum(pcount.values()) == 0:\n            return fields, {}\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            for ptype in self.ds.particle_types_raw:\n                if data_file.total_particles[ptype] == 0:\n                    continue\n                fields.append((ptype, \"particle_identifier\"))\n                my_fields, my_offset_fields = subfind_field_list(\n                    f[ptype], ptype, data_file.total_particles\n                )\n                fields.extend(my_fields)\n                self.offset_fields = self.offset_fields.union(set(my_offset_fields))\n        return fields, {}\n\n\nclass IOHandlerGadgetFOFHaloHDF5(IOHandlerGadgetFOFHDF5):\n    _dataset_type = \"gadget_fof_halo_hdf5\"\n\n    def _read_particle_coords(self, chunks, ptf):\n        pass\n\n    def _read_particle_selection(self, dobj, fields):\n        rv = {}\n        ind = {}\n        # We first need a set of masks for each particle type\n        ptf = defaultdict(list)  # ON-DISK TO READ\n        fsize = defaultdict(lambda: 0)  # COUNT RV\n        field_maps = defaultdict(list)  # ptypes -> fields\n        unions = self.ds.particle_unions\n        # What we need is a mapping from particle types to return types\n        for field in fields:\n            ftype, fname = field\n            fsize[field] = 0\n            # We should add a check for p.fparticle_unions or something here\n            if ftype in unions:\n                for pt in unions[ftype]:\n                    ptf[pt].append(fname)\n                    field_maps[pt, fname].append(field)\n            else:\n                ptf[ftype].append(fname)\n                field_maps[field].append(field)\n\n        # Now we allocate\n        psize = {dobj.ptype: dobj.particle_number}\n        for field in fields:\n            if field[0] in unions:\n                for pt in unions[field[0]]:\n                    fsize[field] += psize.get(pt, 0)\n            else:\n                fsize[field] += psize.get(field[0], 0)\n        for field in fields:\n            if field[1] in self._vector_fields:\n                shape = (fsize[field], self._vector_fields[field[1]])\n            elif field[1] in self._array_fields:\n                shape = (fsize[field],) + self._array_fields[field[1]]\n            elif field in self.ds.scalar_field_list:\n                shape = (1,)\n            else:\n                shape = (fsize[field],)\n            rv[field] = np.empty(shape, dtype=\"float64\")\n            ind[field] = 0\n        # Now we read.\n        for field_r, vals in self._read_particle_fields(dobj, ptf):\n            # Note that we now need to check the mappings\n            for field_f in field_maps[field_r]:\n                my_ind = ind[field_f]\n                rv[field_f][my_ind : my_ind + vals.shape[0], ...] = vals\n                ind[field_f] += vals.shape[0]\n        # Now we need to truncate all our fields, since we allow for\n        # over-estimating.\n        for field_f in ind:\n            rv[field_f] = rv[field_f][: ind[field_f]]\n        return rv\n\n    def _read_scalar_fields(self, dobj, scalar_fields):\n        all_data = {}\n        if not scalar_fields:\n            return all_data\n        pcount = 1\n        with h5py.File(dobj.scalar_data_file.filename, mode=\"r\") as f:\n            for ptype, field_list in sorted(scalar_fields.items()):\n                for field in field_list:\n                    if field == \"particle_identifier\":\n                        field_data = (\n                            np.arange(dobj.scalar_data_file.total_particles[ptype])\n                            + dobj.scalar_data_file.index_start[ptype]\n                        )\n                    elif field in f[ptype]:\n                        field_data = f[ptype][field][()].astype(\"float64\")\n                    else:\n                        fname = field[: field.rfind(\"_\")]\n                        field_data = f[ptype][fname][()].astype(\"float64\")\n                        my_div = field_data.size / pcount\n                        if my_div > 1:\n                            findex = int(field[field.rfind(\"_\") + 1 :])\n                            field_data = field_data[:, findex]\n                    data = np.array([field_data[dobj.scalar_index]])\n                    all_data[ptype, field] = data\n        return all_data\n\n    def _read_member_fields(self, dobj, member_fields):\n        all_data = defaultdict(lambda: np.empty(dobj.particle_number, dtype=np.float64))\n        if not member_fields:\n            return all_data\n        field_start = 0\n        for i, data_file in enumerate(dobj.field_data_files):\n            start_index = dobj.field_data_start[i]\n            end_index = dobj.field_data_end[i]\n            pcount = end_index - start_index\n            if pcount == 0:\n                continue\n            field_end = field_start + end_index - start_index\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype, field_list in sorted(member_fields.items()):\n                    for field in field_list:\n                        field_data = all_data[ptype, field]\n                        if field in f[\"IDs\"]:\n                            my_data = f[\"IDs\"][field][start_index:end_index].astype(\n                                \"float64\"\n                            )\n                        else:\n                            fname = field[: field.rfind(\"_\")]\n                            my_data = f[\"IDs\"][fname][start_index:end_index].astype(\n                                \"float64\"\n                            )\n                            my_div = my_data.size / pcount\n                            if my_div > 1:\n                                findex = int(field[field.rfind(\"_\") + 1 :])\n                                my_data = my_data[:, findex]\n                        field_data[field_start:field_end] = my_data\n            field_start = field_end\n        return all_data\n\n    def _read_particle_fields(self, dobj, ptf):\n        # separate member particle fields from scalar fields\n        scalar_fields = defaultdict(list)\n        member_fields = defaultdict(list)\n        for ptype, field_list in sorted(ptf.items()):\n            for field in field_list:\n                if (ptype, field) in self.ds.scalar_field_list:\n                    scalar_fields[ptype].append(field)\n                else:\n                    member_fields[ptype].append(field)\n\n        all_data = self._read_scalar_fields(dobj, scalar_fields)\n        all_data.update(self._read_member_fields(dobj, member_fields))\n\n        for field, field_data in all_data.items():\n            yield field, field_data\n\n    def _identify_fields(self, data_file):\n        fields = []\n        scalar_fields = []\n        id_fields = {}\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            for ptype in self.ds.particle_types_raw:\n                fields.append((ptype, \"particle_identifier\"))\n                scalar_fields.append((ptype, \"particle_identifier\"))\n                my_fields, my_offset_fields = subfind_field_list(\n                    f[ptype], ptype, data_file.total_particles\n                )\n                fields.extend(my_fields)\n                scalar_fields.extend(my_fields)\n\n                if \"IDs\" not in f:\n                    continue\n                id_fields = [(ptype, field) for field in f[\"IDs\"]]\n                fields.extend(id_fields)\n        return fields, scalar_fields, id_fields, {}\n\n\ndef subfind_field_list(fh, ptype, pcount):\n    fields = []\n    offset_fields = []\n    for field in fh.keys():\n        if isinstance(fh[field], h5py.Group):\n            my_fields, my_offset_fields = subfind_field_list(fh[field], ptype, pcount)\n            fields.extend(my_fields)\n            my_offset_fields.extend(offset_fields)\n        else:\n            if not fh[field].size % pcount[ptype]:\n                my_div = fh[field].size / pcount[ptype]\n                fname = fh[field].name[fh[field].name.find(ptype) + len(ptype) + 1 :]\n                if my_div > 1:\n                    for i in range(int(my_div)):\n                        fields.append((ptype, f\"{fname}_{i}\"))\n                else:\n                    fields.append((ptype, fname))\n            elif (\n                ptype == \"Subhalo\"\n                and not fh[field].size % fh[\"/Subhalo\"].attrs[\"Number_of_groups\"]\n            ):\n                # These are actually Group fields, but they were written after\n                # a load balancing step moved halos around and thus they do not\n                # correspond to the halos stored in the Group group.\n                my_div = fh[field].size / fh[\"/Subhalo\"].attrs[\"Number_of_groups\"]\n                fname = fh[field].name[fh[field].name.find(ptype) + len(ptype) + 1 :]\n                if my_div > 1:\n                    for i in range(int(my_div)):\n                        fields.append((\"Group\", f\"{fname}_{i}\"))\n                else:\n                    fields.append((\"Group\", fname))\n                offset_fields.append(fname)\n            else:\n                mylog.warning(\n                    \"Cannot add field (%s, %s) with size %d.\",\n                    ptype,\n                    fh[field].name,\n                    fh[field].size,\n                )\n                continue\n    return fields, offset_fields\n"
  },
  {
    "path": "yt/frontends/gadget_fof/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gadget_fof/tests/test_outputs.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_equal, assert_equal\n\nfrom yt.frontends.gadget_fof.api import GadgetFOFDataset\nfrom yt.testing import ParticleSelectionComparison, requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    data_dir_load,\n    requires_ds,\n)\n\n_fields = (\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_position_y\"),\n    (\"all\", \"particle_position_z\"),\n    (\"all\", \"particle_velocity_x\"),\n    (\"all\", \"particle_velocity_y\"),\n    (\"all\", \"particle_velocity_z\"),\n    (\"all\", \"particle_mass\"),\n    (\"all\", \"particle_identifier\"),\n)\n\n# a dataset with empty files\ng5 = \"gadget_fof_halos/groups_005/fof_subhalo_tab_005.0.hdf5\"\ng42 = \"gadget_fof_halos/groups_042/fof_subhalo_tab_042.0.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(g5)\ndef test_fields_g5():\n    for field in _fields:\n        yield FieldValuesTest(g5, field, particle_type=True)\n\n\n@requires_module(\"h5py\")\n@requires_ds(g42)\ndef test_fields_g42():\n    for field in _fields:\n        yield FieldValuesTest(g42, field, particle_type=True)\n\n\n@requires_module(\"h5py\")\n@requires_file(g42)\ndef test_GadgetFOFDataset():\n    assert isinstance(data_dir_load(g42), GadgetFOFDataset)\n\n\n# fof/subhalo catalog with member particles\ng298 = \"gadget_halos/data/groups_298/fof_subhalo_tab_298.0.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_file(g298)\ndef test_particle_selection():\n    ds = data_dir_load(g298)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\n@requires_module(\"h5py\")\n@requires_file(g298)\ndef test_subhalos():\n    ds = data_dir_load(g298)\n    total_sub = 0\n    total_int = 0\n    for hid in range(0, ds.index.particle_count[\"Group\"]):\n        my_h = ds.halo(\"Group\", hid)\n        h_ids = my_h[\"Group\", \"ID\"]\n        for sid in range(int(my_h[\"Group\", \"subhalo_number\"][0])):\n            my_s = ds.halo(\"Subhalo\", (my_h.particle_identifier, sid))\n            total_sub += my_s[\"Subhalo\", \"ID\"].size\n            total_int += np.intersect1d(h_ids, my_s[\"Subhalo\", \"ID\"]).size\n\n    # Test that all subhalo particles are contained within\n    # their parent group.\n    assert_equal(total_sub, total_int)\n\n\n@requires_module(\"h5py\")\n@requires_file(g298)\ndef test_halo_masses():\n    ds = data_dir_load(g298)\n    ad = ds.all_data()\n    for ptype in [\"Group\", \"Subhalo\"]:\n        nhalos = ds.index.particle_count[ptype]\n        mass = ds.arr(np.zeros(nhalos), \"code_mass\")\n        for i in range(nhalos):\n            halo = ds.halo(ptype, i)\n            mass[i] = halo.mass\n\n        # Check that masses from halo containers are the same\n        # as the array of all masses.  This will test getting\n        # scalar fields for halos correctly.\n        assert_array_equal(ad[ptype, \"particle_mass\"], mass)\n\n\n# fof/subhalo catalog with no member ids in first file\ng56 = \"gadget_halos/data/groups_056/fof_subhalo_tab_056.0.hdf5\"\n\n\n# This dataset has halos in one file and ids in another,\n# which can confuse the field detection.\n@requires_module(\"h5py\")\n@requires_file(g56)\ndef test_unbalanced_dataset():\n    ds = data_dir_load(g56)\n    halo = ds.halo(\"Group\", 0)\n    assert_equal(len(halo[\"Group\", \"member_ids\"]), 33)\n    assert_equal(halo[\"Group\", \"member_ids\"].min().d, 723254.0)\n    assert_equal(halo[\"Group\", \"member_ids\"].max().d, 772662.0)\n\n\n# fof/subhalo catalog with no member ids in first file\ng76 = \"gadget_halos/data/groups_076/fof_subhalo_tab_076.0.hdf5\"\n\n\n# This dataset has one halo with particles distributed over 3 files\n# with the 2nd file being empty.\n@requires_module(\"h5py\")\n@requires_file(g76)\ndef test_3file_halo():\n    ds = data_dir_load(g76)\n    # this halo's particles are distributed over 3 files with the\n    # middle file being empty\n    halo = ds.halo(\"Group\", 6)\n    halo[\"Group\", \"member_ids\"]\n    assert True\n"
  },
  {
    "path": "yt/frontends/gamer/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gamer/api.py",
    "content": "from .data_structures import GAMERDataset, GAMERGrid, GAMERHierarchy\nfrom .fields import GAMERFieldInfo\nfrom .io import IOHandlerGAMER\n\n### NOT SUPPORTED YET\n# from . import tests\n"
  },
  {
    "path": "yt/frontends/gamer/cfields.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\ncimport cython\ncimport libc.math as math\ncimport numpy as np\n\nimport numpy as np\n\n\ncdef np.float64_t gamma_eos(np.float64_t kT, np.float64_t g) noexcept nogil:\n    return g\n\ncdef np.float64_t gamma_eos_tb(np.float64_t kT, np.float64_t g) noexcept nogil:\n    cdef np.float64_t x, c_p, c_v\n    x = 2.25 * kT / ( math.sqrt(2.25 * kT * kT + 1.0) + 1.0 )\n    c_p = 2.5 + x\n    c_v = 1.5 + x\n    return c_p / c_v\n\ncdef np.float64_t cs_eos_tb(np.float64_t kT, np.float64_t h, np.float64_t g) noexcept nogil:\n    cdef np.float64_t hp, cs2\n    hp = h + 1.0\n    cs2 = kT / (3.0 * hp)\n    cs2 *= (5.0 * hp - 8.0 * kT) / (hp - kT)\n    return math.sqrt(cs2)\n\ncdef np.float64_t cs_eos(np.float64_t kT, np.float64_t h, np.float64_t g) noexcept nogil:\n    cdef np.float64_t hp, cs2\n    hp = h + 1.0\n    cs2 = g / hp * kT\n    return math.sqrt(cs2)\n\n\nctypedef np.float64_t (*f2_type)(np.float64_t, np.float64_t) noexcept nogil\nctypedef np.float64_t (*f3_type)(np.float64_t, np.float64_t, np.float64_t) noexcept nogil\n\n\ncdef class SRHDFields:\n    cdef f2_type gamma\n    cdef f3_type cs\n    cdef np.float64_t _gamma\n\n    def __init__(self, int eos, np.float64_t gamma):\n        self._gamma = gamma\n        # Select aux functions based on eos no.\n        if eos == 1:\n            self.gamma = gamma_eos\n            self.cs = cs_eos\n        else:\n            self.gamma = gamma_eos_tb\n            self.cs = cs_eos_tb\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def gamma_field(self, temp):\n        cdef np.float64_t[:] kT = temp.ravel()\n\n        out = np.empty_like(kT)\n        cdef np.float64_t[:] outp = out.ravel()\n        cdef int i\n\n        for i in range(outp.shape[0]):\n            outp[i] = self.gamma(kT[i], self._gamma)\n        return out\n\n    cdef np.float64_t _lorentz_factor(\n            self,\n            np.float64_t rho,\n            np.float64_t mx,\n            np.float64_t my,\n            np.float64_t mz,\n            np.float64_t h,\n        ) noexcept nogil:\n        cdef np.float64_t u2\n        cdef np.float64_t fac\n\n        fac = (1.0 / (rho * (h + 1.0))) ** 2\n        u2 = (mx * mx + my * my + mz * mz) * fac\n        return math.sqrt(1.0 + u2)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def lorentz_factor(self, dens, momx, momy, momz, enth):\n        cdef np.float64_t[:] rho = dens.ravel()\n\n        cdef np.float64_t[:] mx = momx.ravel()\n        cdef np.float64_t[:] my = momy.ravel()\n        cdef np.float64_t[:] mz = momz.ravel()\n        cdef np.float64_t[:] h = enth.ravel()\n\n        out = np.empty_like(dens)\n        cdef np.float64_t[:] outp = out.ravel()\n        cdef int i\n\n        for i in range(outp.shape[0]):\n            outp[i] = self._lorentz_factor(rho[i], mx[i], my[i], mz[i], h[i])\n        return out\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def sound_speed(self, temp, enth):\n        cdef np.float64_t[:] kT = temp.ravel()\n        cdef np.float64_t[:] h = enth.ravel()\n        out = np.empty_like(kT)\n        cdef np.float64_t[:] outp = out.ravel()\n\n        cdef int i\n        for i in range(outp.shape[0]):\n            outp[i] = self.cs(kT[i], h[i], self._gamma)\n        return out\n\n    cdef np.float64_t _four_vel(\n            self,\n            np.float64_t rho,\n            np.float64_t mi,\n            np.float64_t h,\n        ) noexcept nogil:\n        return mi / (rho * (h + 1.0))\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def four_velocity_xyz(self, dens, mom, enth):\n        cdef np.float64_t[:] rho = dens.ravel()\n        cdef np.float64_t[:] mi = mom.ravel()\n        cdef np.float64_t[:] h = enth.ravel()\n\n        out = np.empty_like(dens)\n        cdef np.float64_t[:] outp = out.ravel()\n        cdef int i\n\n        for i in range(outp.shape[0]):\n            outp[i] = self._four_vel(rho[i], mi[i], h[i])\n        return out\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def kinetic_energy_density(self, dens, momx, momy, momz, temp, enth):\n        cdef np.float64_t[:] rho = dens.ravel()\n        cdef np.float64_t[:] mx = momx.ravel()\n        cdef np.float64_t[:] my = momy.ravel()\n        cdef np.float64_t[:] mz = momz.ravel()\n        cdef np.float64_t[:] kT = temp.ravel()\n        cdef np.float64_t[:] h = enth.ravel()\n\n        out = np.empty_like(dens)\n        cdef np.float64_t[:] outp = out.ravel()\n        cdef np.float64_t lf, u2, ux, uy, uz\n        cdef int i\n\n        for i in range(outp.shape[0]):\n            ux = self._four_vel(rho[i], mx[i], h[i])\n            uy = self._four_vel(rho[i], my[i], h[i])\n            uz = self._four_vel(rho[i], mz[i], h[i])\n            u2 = ux**2 + uy**2 + uz**2\n            lf = self._lorentz_factor(rho[i], mx[i], my[i], mz[i], h[i])\n            gm1 = u2 / (lf + 1.0)\n            p = rho[i] / lf * kT[i]\n            outp[i] = gm1 * (rho[i] * (h[i] + 1.0) + p)\n        return out\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def mach_number(self, dens, momx, momy, momz, temp, enth):\n        cdef np.float64_t[:] rho = dens.ravel()\n        cdef np.float64_t[:] mx = momx.ravel()\n        cdef np.float64_t[:] my = momy.ravel()\n        cdef np.float64_t[:] mz = momz.ravel()\n        cdef np.float64_t[:] kT = temp.ravel()\n        cdef np.float64_t[:] h = enth.ravel()\n\n        out = np.empty_like(dens)\n        cdef np.float64_t[:] outp = out.ravel()\n        cdef np.float64_t cs, us, u\n        cdef int i\n\n        for i in range(outp.shape[0]):\n            cs = self.cs(kT[i], h[i], self._gamma)\n            us = cs / math.sqrt(1.0 - cs**2)\n            u = math.sqrt(mx[i]**2 + my[i]**2 + mz[i]**2) / (rho[i] * (h[i] + 1.0))\n            outp[i] = u / us\n        return out\n"
  },
  {
    "path": "yt/frontends/gamer/data_structures.py",
    "content": "import os\nimport weakref\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.file_handler import HDF5FileHandler\n\nfrom .definitions import geometry_parameters\nfrom .fields import GAMERFieldInfo\n\n\nclass GAMERGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level):\n        AMRGridPatch.__init__(self, id, filename=index.index_filename, index=index)\n        self.Parent = None  # do NOT initialize Parent as []\n        self.Children = []\n        self.Level = level\n\n\nclass GAMERHierarchy(GridIndex):\n    grid = GAMERGrid\n    _preload_implemented = True  # since gamer defines \"_read_chunk_data\" in io.py\n\n    def __init__(self, ds, dataset_type=\"gamer\"):\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        self._handle = ds._handle\n        self._group_grid = ds._group_grid\n        self._group_particle = ds._group_particle\n        self.float_type = \"float64\"  # fixed even when FLOAT8 is off\n        self._particle_handle = ds._particle_handle\n        self.refine_by = ds.refine_by\n        self.pgroup = self.refine_by**3  # number of patches in a patch group\n        GridIndex.__init__(self, ds, dataset_type)\n\n    def _detect_output_fields(self):\n        # find all field names in the current dataset\n        # grid fields\n        self.field_list = [(\"gamer\", v) for v in self._group_grid.keys()]\n\n        # particle fields\n        if self._group_particle is not None:\n            self.field_list += [(\"io\", v) for v in self._group_particle.keys()]\n\n    def _count_grids(self):\n        # count the total number of patches at all levels\n        self.num_grids = self.dataset.parameters[\"NPatch\"].sum() // self.pgroup\n\n    def _parse_index(self):\n        parameters = self.dataset.parameters\n        gid0 = 0\n        grid_corner = self._handle[\"Tree/Corner\"][()][:: self.pgroup]\n        convert2physical = self._handle[\"Tree/Corner\"].attrs[\"Cvt2Phy\"]\n\n        self.grid_dimensions[:] = parameters[\"PatchSize\"] * self.refine_by\n\n        for lv in range(0, parameters[\"NLevel\"]):\n            num_grids_level = parameters[\"NPatch\"][lv] // self.pgroup\n            if num_grids_level == 0:\n                break\n\n            patch_scale = (\n                parameters[\"PatchSize\"] * parameters[\"CellScale\"][lv] * self.refine_by\n            )\n\n            # set the level and edge of each grid\n            # (left/right_edge are YT arrays in code units)\n            self.grid_levels.flat[gid0 : gid0 + num_grids_level] = lv\n            self.grid_left_edge[gid0 : gid0 + num_grids_level] = (\n                grid_corner[gid0 : gid0 + num_grids_level] * convert2physical\n            )\n            self.grid_right_edge[gid0 : gid0 + num_grids_level] = (\n                grid_corner[gid0 : gid0 + num_grids_level] + patch_scale\n            ) * convert2physical\n\n            gid0 += num_grids_level\n        self.grid_left_edge += self.dataset.domain_left_edge\n        self.grid_right_edge += self.dataset.domain_left_edge\n\n        # allocate all grid objects\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n        for i in range(self.num_grids):\n            self.grids[i] = self.grid(i, self, self.grid_levels.flat[i])\n\n        # maximum level with patches (which can be lower than MAX_LEVEL)\n        self.max_level = self.grid_levels.max()\n\n        # number of particles in each grid\n        try:\n            self.grid_particle_count[:] = np.sum(\n                self._handle[\"Tree/NPar\"][()].reshape(-1, self.pgroup), axis=1\n            )[:, None]\n        except KeyError:\n            self.grid_particle_count[:] = 0.0\n\n        # calculate the starting particle indices for each grid (starting from 0)\n        # --> note that the last element must store the total number of particles\n        #    (see _read_particle_coords and _read_particle_fields in io.py)\n        self._particle_indices = np.zeros(self.num_grids + 1, dtype=\"int64\")\n        np.add.accumulate(\n            self.grid_particle_count.reshape(self.num_grids),\n            out=self._particle_indices[1:],\n        )\n\n    def _populate_grid_objects(self):\n        son_list = self._handle[\"Tree/Son\"][()]\n\n        for gid in range(self.num_grids):\n            grid = self.grids[gid]\n            son_gid0 = (\n                son_list[gid * self.pgroup : (gid + 1) * self.pgroup] // self.pgroup\n            )\n\n            # set up the parent-children relationship\n            grid.Children = [self.grids[t] for t in son_gid0[son_gid0 >= 0]]\n\n            for son_grid in grid.Children:\n                son_grid.Parent = grid\n\n            # set up other grid attributes\n            grid._prepare_grid()\n            grid._setup_dx()\n\n        # validate the parent-children relationship in the debug mode\n        if self.dataset._debug:\n            self._validate_parent_children_relationship()\n\n    # for _debug mode only\n    def _validate_parent_children_relationship(self):\n        mylog.info(\"Validating the parent-children relationship ...\")\n\n        father_list = self._handle[\"Tree/Father\"][()]\n\n        for grid in self.grids:\n            # parent->children == itself\n            if grid.Parent is not None:\n                assert grid in grid.Parent.Children, (\n                    f\"Grid {grid.id}, Parent {grid.Parent.id}, \"\n                    f\"Parent->Children[0] {grid.Parent.Children[0].id}\"\n                )\n\n            # children->parent == itself\n            for c in grid.Children:\n                assert c.Parent is grid, (\n                    f\"Grid {grid.id}, Children {c.id}, Children->Parent {c.Parent.id}\"\n                )\n\n            # all refinement grids should have parent\n            if grid.Level > 0:\n                assert grid.Parent is not None and grid.Parent.id >= 0, (\n                    f\"Grid {grid.id}, Level {grid.Level}, \"\n                    f\"Parent {grid.Parent.id if grid.Parent is not None else -999}\"\n                )\n\n            # parent index is consistent with the loaded dataset\n            if grid.Level > 0:\n                father_gid = father_list[grid.id * self.pgroup] // self.pgroup\n                assert father_gid == grid.Parent.id, (\n                    f\"Grid {grid.id}, Level {grid.Level}, \"\n                    f\"Parent_Found {grid.Parent.id}, Parent_Expect {father_gid}\"\n                )\n\n            # edges between children and parent\n            for c in grid.Children:\n                for d in range(0, 3):\n                    if not grid.LeftEdge[d] <= c.LeftEdge[d]:\n                        raise ValueError(\n                            f\"Grid {grid.id}, Child {c.id}, \"\n                            f\"Grid->EdgeL {grid.LeftEdge[d]:14.7e}, \"\n                            f\"Children->EdgeL {c.LeftEdge[d]:14.7e}\"\n                        )\n\n                    if not grid.RightEdge[d] >= c.RightEdge[d]:\n                        raise ValueError(\n                            f\"Grid {grid.id}, Child {c.id}, \"\n                            f\"Grid->EdgeR {grid.RightEdge[d]:14.7e}, \"\n                            f\"Children->EdgeR {c.RightEdge[d]:14.7e}\"\n                        )\n\n        mylog.info(\"Check passed\")\n\n\nclass GAMERDataset(Dataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = GAMERHierarchy\n    _field_info_class = GAMERFieldInfo\n    _handle = None\n    _group_grid = None\n    _group_particle = None\n    _debug = False  # debug mode for the GAMER frontend\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"gamer\",\n        storage_filename=None,\n        particle_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        if self._handle is not None:\n            return\n\n        self.fluid_types += (\"gamer\",)\n        self._handle = HDF5FileHandler(filename)\n        self.particle_filename = particle_filename\n\n        # to catch both the new and old data formats for the grid data\n        try:\n            self._group_grid = self._handle[\"GridData\"]\n        except KeyError:\n            self._group_grid = self._handle[\"Data\"]\n\n        if \"Particle\" in self._handle:\n            self._group_particle = self._handle[\"Particle\"]\n\n        if self.particle_filename is None:\n            self._particle_handle = self._handle\n        else:\n            self._particle_handle = HDF5FileHandler(self.particle_filename)\n\n        # currently GAMER only supports refinement by a factor of 2\n        self.refine_by = 2\n\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        self.storage_filename = storage_filename\n\n    def _set_code_unit_attributes(self):\n        if self.opt_unit:\n            # GAMER units are always in CGS\n            setdefaultattr(\n                self, \"length_unit\", self.quan(self.parameters[\"Unit_L\"], \"cm\")\n            )\n            setdefaultattr(self, \"mass_unit\", self.quan(self.parameters[\"Unit_M\"], \"g\"))\n            setdefaultattr(self, \"time_unit\", self.quan(self.parameters[\"Unit_T\"], \"s\"))\n\n            if self.mhd:\n                setdefaultattr(\n                    self, \"magnetic_unit\", self.quan(self.parameters[\"Unit_B\"], \"gauss\")\n                )\n\n        else:\n            if len(self.units_override) == 0:\n                mylog.warning(\n                    \"Cannot determine code units ==> \"\n                    \"Use units_override to specify the units\"\n                )\n\n            for unit, value, cgs in [\n                (\"length\", 1.0, \"cm\"),\n                (\"time\", 1.0, \"s\"),\n                (\"mass\", 1.0, \"g\"),\n                (\"magnetic\", np.sqrt(4.0 * np.pi), \"gauss\"),\n            ]:\n                setdefaultattr(self, f\"{unit}_unit\", self.quan(value, cgs))\n\n                if len(self.units_override) == 0:\n                    mylog.warning(\"Assuming %8s unit = %f %s\", unit, value, cgs)\n\n    def _parse_parameter_file(self):\n        # code-specific parameters\n        for t in self._handle[\"Info\"]:\n            info_category = self._handle[\"Info\"][t]\n            for v in info_category.dtype.names:\n                self.parameters[v] = info_category[v]\n\n        # shortcut for self.parameters\n        parameters = self.parameters\n\n        # reset 'Model' to be more readable\n        # (no longer regard MHD as a separate model)\n        if parameters[\"Model\"] == 1:\n            parameters[\"Model\"] = \"Hydro\"\n        elif parameters[\"Model\"] == 3:\n            parameters[\"Model\"] = \"ELBDM\"\n        else:\n            parameters[\"Model\"] = \"Unknown\"\n\n        # simulation time and domain\n        self.dimensionality = 3  # always 3D\n        self.domain_left_edge = parameters.get(\n            \"BoxEdgeL\", np.array([0.0, 0.0, 0.0])\n        ).astype(\"f8\")\n        self.domain_right_edge = parameters.get(\n            \"BoxEdgeR\", parameters[\"BoxSize\"]\n        ).astype(\"f8\")\n        self.domain_dimensions = parameters[\"NX0\"].astype(\"int64\")\n\n        # periodicity\n        if parameters[\"FormatVersion\"] >= 2106:\n            periodic_bc = 1\n        else:\n            periodic_bc = 0\n        self._periodicity = (\n            bool(parameters[\"Opt__BC_Flu\"][0] == periodic_bc),\n            bool(parameters[\"Opt__BC_Flu\"][2] == periodic_bc),\n            bool(parameters[\"Opt__BC_Flu\"][4] == periodic_bc),\n        )\n\n        # cosmological parameters\n        if parameters[\"Comoving\"]:\n            self.cosmological_simulation = 1\n            # here parameters[\"Time\"][0] is the scale factor a at certain redshift\n            self.current_redshift = 1.0 / parameters[\"Time\"][0] - 1.0\n            self.omega_matter = parameters[\"OmegaM0\"]\n            self.omega_lambda = 1.0 - self.omega_matter\n            # default to 0.7 for old data format\n            self.hubble_constant = parameters.get(\"Hubble0\", 0.7)\n\n            # use the cosmological age computed by the given cosmological parameters as the current time when COMOVING is on; cosmological age is computed by subtracting the lookback time at self.current_redshift from that at z = 1e6 (i.e., very early universe)\n            cosmo = Cosmology(\n                hubble_constant=self.hubble_constant,\n                omega_matter=self.omega_matter,\n                omega_lambda=self.omega_lambda,\n            )\n            self.current_time = cosmo.lookback_time(self.current_redshift, 1e6)\n        else:\n            self.cosmological_simulation = 0\n            self.current_redshift = 0.0\n            self.omega_matter = 0.0\n            self.omega_lambda = 0.0\n            self.hubble_constant = 0.0\n\n            # use parameters[\"Time\"][0] as current time when COMOVING is off\n            self.current_time = parameters[\"Time\"][0]\n\n        # make aliases to some frequently used variables\n        if parameters[\"Model\"] == \"Hydro\":\n            self.gamma = parameters[\"Gamma\"]\n            self.gamma_cr = self.parameters.get(\"CR_Gamma\", None)\n            self.eos = parameters.get(\"EoS\", 1)  # Assume gamma-law by default\n            # default to 0.6 for old data format\n            self.mu = parameters.get(\n                \"MolecularWeight\", 0.6\n            )  # Assume ionized primordial by default\n            self.mhd = parameters.get(\"Magnetohydrodynamics\", 0)\n            self.srhd = parameters.get(\"SRHydrodynamics\", 0)\n        else:\n            self.mhd = 0\n            self.srhd = 0\n            # set dummy value of mu here to avoid more complicated workarounds later\n            self.mu = 1.0\n\n        # old data format (version < 2210) did not contain any information of code units\n        self.opt_unit = self.parameters.get(\"Opt__Unit\", 0)\n        self.geometry = Geometry(geometry_parameters[parameters.get(\"Coordinate\", 1)])\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            # define a unique way to identify GAMER datasets\n            f = HDF5FileHandler(filename)\n            if \"Info\" in f[\"/\"].keys() and \"KeyInfo\" in f[\"/Info\"].keys():\n                return True\n        except Exception:\n            pass\n        return False\n"
  },
  {
    "path": "yt/frontends/gamer/definitions.py",
    "content": "geometry_parameters = {\n    1: \"cartesian\",\n    2: (\"cylindrical\", (\"r\", \"theta\", \"z\")),\n    3: (\"spherical\", (\"r\", \"theta\", \"phi\")),\n}\n"
  },
  {
    "path": "yt/frontends/gamer/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.fields.tensor_fields import setup_stress_energy_ideal\nfrom yt.funcs import mylog\n\nfrom .cfields import SRHDFields\n\nb_units = \"code_magnetic\"\npre_units = \"code_mass / (code_length*code_time**2)\"\nerg_units = \"code_mass / (code_length*code_time**2)\"\nrho_units = \"code_mass / code_length**3\"\nmom_units = \"code_mass / (code_length**2*code_time)\"\nvel_units = \"code_velocity\"\npot_units = \"code_length**2/code_time**2\"\n\npsi_units = \"code_mass**(1/2) / code_length**(3/2)\"\n\n\nclass GAMERFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        # hydro fields on disk (GAMER outputs conservative variables)\n        (\"Dens\", (rho_units, [], None)),\n        (\"MomX\", (mom_units, [\"momentum_density_x\"], None)),\n        (\"MomY\", (mom_units, [\"momentum_density_y\"], None)),\n        (\"MomZ\", (mom_units, [\"momentum_density_z\"], None)),\n        (\"Engy\", (erg_units, [], None)),\n        (\"CRay\", (erg_units, [\"cosmic_ray_energy_density\"], None)),\n        (\"Pote\", (pot_units, [\"gravitational_potential\"], None)),\n        (\"Pres\", (pre_units, [\"pressure\"], None)),\n        (\"Temp\", (\"code_temperature\", [\"temperature\"], None)),\n        (\"Enth\", (pot_units, [\"specific_reduced_enthalpy\"], None)),\n        (\"Mach\", (\"dimensionless\", [\"mach_number\"], None)),\n        (\"Cs\", (vel_units, [\"sound_speed\"], None)),\n        (\"DivVel\", (\"1/code_time\", [\"velocity_divergence\"], None)),\n        # MHD fields on disk (CC=cell-centered)\n        (\"CCMagX\", (b_units, [], \"B_x\")),\n        (\"CCMagY\", (b_units, [], \"B_y\")),\n        (\"CCMagZ\", (b_units, [], \"B_z\")),\n        # psiDM fields on disk\n        (\"Real\", (psi_units, [\"psidm_real_part\"], None)),\n        (\"Imag\", (psi_units, [\"psidm_imaginary_part\"], None)),\n        # particle fields on disk (deposited onto grids)\n        (\"ParDens\", (rho_units, [\"particle_density_on_grid\"], None)),\n        (\"TotalDens\", (rho_units, [\"total_density_on_grid\"], None)),\n    )\n\n    known_particle_fields: KnownFieldsT = (\n        (\"ParMass\", (\"code_mass\", [\"particle_mass\"], None)),\n        (\"ParPosX\", (\"code_length\", [\"particle_position_x\"], None)),\n        (\"ParPosY\", (\"code_length\", [\"particle_position_y\"], None)),\n        (\"ParPosZ\", (\"code_length\", [\"particle_position_z\"], None)),\n        (\"ParVelX\", (\"code_velocity\", [\"particle_velocity_x\"], None)),\n        (\"ParVelY\", (\"code_velocity\", [\"particle_velocity_y\"], None)),\n        (\"ParVelZ\", (\"code_velocity\", [\"particle_velocity_z\"], None)),\n        (\"ParCreTime\", (\"code_time\", [\"particle_creation_time\"], None)),\n    )\n\n    def __init__(self, ds, field_list):\n        super().__init__(ds, field_list)\n\n    # add primitive and other derived variables\n    def setup_fluid_fields(self):\n        pc = self.ds.units.physical_constants\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        unit_system = self.ds.unit_system\n        unit_system.registry = self.ds.unit_registry  # TODO: Why do I need this?!\n\n        if self.ds.opt_unit:\n            temp_conv = pc.kb / (self.ds.mu * pc.mh)\n        else:\n            temp_conv = (\n                self.ds.arr(1.0, \"code_velocity**2/code_temperature\") / self.ds.mu\n            )\n\n        if self.ds.srhd:\n            if self.ds.opt_unit:\n                c2 = pc.clight * pc.clight\n            else:\n                c2 = self.ds.arr(1.0, \"code_velocity**2\")\n            invc2 = 1.0 / c2\n\n            if (\"gamer\", \"Temp\") not in self.field_list:\n                mylog.warning(\n                    'The temperature field \"Temp\" is not present in the dataset. Most '\n                    'SRHD fields will not be available!! Please set \"OPT__OUTPUT_TEMP '\n                    '= 1\" in Input__Parameter and re-run the simulation.'\n                )\n            if (\"gamer\", \"Enth\") not in self.field_list:\n                mylog.warning(\n                    'The reduced enthalpy field \"Enth\" is not present in the dataset. '\n                    \"Most SRHD fields will not be available!! Please set \"\n                    '\"OPT__OUTPUT_ENTHALPY = 1\" in Input__Parameter and re-run the '\n                    \"simulation.\"\n                )\n\n            # EOS functions\n            gamma = self.ds.gamma if self.ds.eos == 1 else 0.0\n            fgen = SRHDFields(self.ds.eos, gamma)\n\n            # temperature fraction (kT/mc^2)\n            def _temp_fraction(data):\n                return data[\"gamer\", \"Temp\"] * temp_conv * invc2\n\n            self.add_field(\n                (\"gas\", \"temp_fraction\"),\n                function=_temp_fraction,\n                sampling_type=\"cell\",\n                units=\"\",\n            )\n\n            # specific enthalpy\n            def _specific_enthalpy(data):\n                return data[\"gas\", \"specific_reduced_enthalpy\"] + c2\n\n            self.add_field(\n                (\"gas\", \"specific_enthalpy\"),\n                function=_specific_enthalpy,\n                sampling_type=\"cell\",\n                units=unit_system[\"specific_energy\"],\n            )\n\n            # sound speed\n            if (\"gamer\", \"Cs\") not in self.field_list:\n\n                def _sound_speed(data):\n                    out = fgen.sound_speed(\n                        data[\"gas\", \"temp_fraction\"].d,\n                        data[\"gamer\", \"Enth\"].d,\n                    )\n                    return data.ds.arr(out, \"code_velocity\").to(unit_system[\"velocity\"])\n\n                self.add_field(\n                    (\"gas\", \"sound_speed\"),\n                    sampling_type=\"cell\",\n                    function=_sound_speed,\n                    units=unit_system[\"velocity\"],\n                )\n\n            # ratio of specific heats (gamma)\n            def _gamma(data):\n                out = fgen.gamma_field(data[\"gas\", \"temp_fraction\"].d)\n                return data.ds.arr(out, \"dimensionless\")\n\n            self.add_field(\n                (\"gas\", \"gamma\"), sampling_type=\"cell\", function=_gamma, units=\"\"\n            )\n\n            # reduced total energy density\n            self.alias(\n                (\"gas\", \"reduced_total_energy_density\"),\n                (\"gamer\", \"Engy\"),\n                units=unit_system[\"pressure\"],\n            )\n\n            # total energy density\n            def _total_energy_density(data):\n                return data[\"gamer\", \"Engy\"] + data[\"gamer\", \"Dens\"] * c2\n\n            self.add_field(\n                (\"gas\", \"total_energy_density\"),\n                sampling_type=\"cell\",\n                function=_total_energy_density,\n                units=unit_system[\"pressure\"],\n            )\n\n            # coordinate frame density\n            self.alias(\n                (\"gas\", \"frame_density\"),\n                (\"gamer\", \"Dens\"),\n                units=unit_system[\"density\"],\n            )\n\n            # 4-velocity spatial components\n            def four_velocity_xyz(u):\n                def _four_velocity(data):\n                    out = fgen.four_velocity_xyz(\n                        data[\"gamer\", \"Dens\"].d,\n                        data[\"gamer\", f\"Mom{u.upper()}\"].d,\n                        data[\"gamer\", \"Enth\"].d,\n                    )\n                    return data.ds.arr(out, \"code_velocity\").to(unit_system[\"velocity\"])\n\n                return _four_velocity\n\n            for u in \"xyz\":\n                self.add_field(\n                    (\"gas\", f\"four_velocity_{u}\"),\n                    sampling_type=\"cell\",\n                    function=four_velocity_xyz(u),\n                    units=unit_system[\"velocity\"],\n                )\n\n            # lorentz factor\n            if (\"gamer\", \"Lrtz\") in self.field_list:\n\n                def _lorentz_factor(data):\n                    return data[\"gamer\", \"Lrtz\"]\n\n            else:\n\n                def _lorentz_factor(data):\n                    out = fgen.lorentz_factor(\n                        data[\"gamer\", \"Dens\"].d,\n                        data[\"gamer\", \"MomX\"].d,\n                        data[\"gamer\", \"MomY\"].d,\n                        data[\"gamer\", \"MomZ\"].d,\n                        data[\"gamer\", \"Enth\"].d,\n                    )\n                    return data.ds.arr(out, \"dimensionless\")\n\n            self.add_field(\n                (\"gas\", \"lorentz_factor\"),\n                sampling_type=\"cell\",\n                function=_lorentz_factor,\n                units=\"\",\n            )\n\n            # density\n            def _density(data):\n                return data[\"gamer\", \"Dens\"] / data[\"gas\", \"lorentz_factor\"]\n\n            self.add_field(\n                (\"gas\", \"density\"),\n                sampling_type=\"cell\",\n                function=_density,\n                units=unit_system[\"density\"],\n            )\n\n            # pressure\n            def _pressure(data):\n                p = data[\"gas\", \"density\"] * data[\"gas\", \"temp_fraction\"]\n                return p * c2\n\n            # thermal energy per mass (i.e., specific)\n            def _specific_thermal_energy(data):\n                return (\n                    data[\"gas\", \"specific_reduced_enthalpy\"]\n                    - c2 * data[\"gas\", \"temp_fraction\"]\n                )\n\n            # total energy per mass\n            def _specific_total_energy(data):\n                return data[\"gas\", \"total_energy_density\"] / data[\"gas\", \"density\"]\n\n            # kinetic energy density\n            def _kinetic_energy_density(data):\n                out = fgen.kinetic_energy_density(\n                    data[\"gamer\", \"Dens\"].d,\n                    data[\"gamer\", \"MomX\"].d,\n                    data[\"gamer\", \"MomY\"].d,\n                    data[\"gamer\", \"MomZ\"].d,\n                    data[\"gas\", \"temp_fraction\"].d,\n                    data[\"gamer\", \"Enth\"].d,\n                )\n                return data.ds.arr(out, erg_units).to(unit_system[\"pressure\"])\n\n            self.add_field(\n                (\"gas\", \"kinetic_energy_density\"),\n                sampling_type=\"cell\",\n                function=_kinetic_energy_density,\n                units=unit_system[\"pressure\"],\n            )\n\n            # Mach number\n            if (\"gamer\", \"Mach\") not in self.field_list:\n\n                def _mach_number(data):\n                    out = fgen.mach_number(\n                        data[\"gamer\", \"Dens\"].d,\n                        data[\"gamer\", \"MomX\"].d,\n                        data[\"gamer\", \"MomY\"].d,\n                        data[\"gamer\", \"MomZ\"].d,\n                        data[\"gas\", \"temp_fraction\"].d,\n                        data[\"gamer\", \"Enth\"].d,\n                    )\n                    return data.ds.arr(out, \"dimensionless\")\n\n                self.add_field(\n                    (\"gas\", \"mach_number\"),\n                    sampling_type=\"cell\",\n                    function=_mach_number,\n                    units=\"\",\n                )\n\n            setup_stress_energy_ideal(self)\n\n        else:  # not RHD\n            # density\n            self.alias(\n                (\"gas\", \"density\"), (\"gamer\", \"Dens\"), units=unit_system[\"density\"]\n            )\n\n            self.alias(\n                (\"gas\", \"total_energy_density\"),\n                (\"gamer\", \"Engy\"),\n                units=unit_system[\"pressure\"],\n            )\n\n            # ====================================================\n            # note that yt internal fields assume\n            #    [specific_thermal_energy]   = [energy per mass]\n            #    [kinetic_energy_density]    = [energy per volume]\n            #    [magnetic_energy_density]   = [energy per volume]\n            # and we further adopt\n            #    [specific_total_energy]     = [energy per mass]\n            #    [total_energy_density]      = [energy per volume]\n            # ====================================================\n\n            # thermal energy per volume\n            def et(data):\n                ek = (\n                    0.5\n                    * (\n                        data[\"gamer\", \"MomX\"] ** 2\n                        + data[\"gamer\", \"MomY\"] ** 2\n                        + data[\"gamer\", \"MomZ\"] ** 2\n                    )\n                    / data[\"gamer\", \"Dens\"]\n                )\n                Et = data[\"gamer\", \"Engy\"] - ek\n                if self.ds.mhd:\n                    # magnetic_energy is a yt internal field\n                    Et -= data[\"gas\", \"magnetic_energy_density\"]\n                if getattr(self.ds, \"gamma_cr\", None):\n                    # cosmic rays are included in this dataset\n                    Et -= data[\"gas\", \"cosmic_ray_energy_density\"]\n                return Et\n\n            # thermal energy per mass (i.e., specific)\n            def _specific_thermal_energy(data):\n                return et(data) / data[\"gamer\", \"Dens\"]\n\n            # total energy per mass\n            def _specific_total_energy(data):\n                return data[\"gamer\", \"Engy\"] / data[\"gamer\", \"Dens\"]\n\n            # pressure\n            def _pressure(data):\n                return et(data) * (data.ds.gamma - 1.0)\n\n        # velocity\n        def velocity_xyz(v):\n            if (\"gamer\", f\"Vel{v.upper()}\") in self.field_list:\n\n                def _velocity(data):\n                    return data.ds.arr(\n                        data[\"gamer\", f\"Vel{v.upper()}\"].d, \"code_velocity\"\n                    ).to(unit_system[\"velocity\"])\n\n            elif self.ds.srhd:\n\n                def _velocity(data):\n                    return (\n                        data[\"gas\", f\"four_velocity_{v}\"]\n                        / data[\"gas\", \"lorentz_factor\"]\n                    )\n\n            else:\n\n                def _velocity(data):\n                    return data[\"gas\", f\"momentum_density_{v}\"] / data[\"gas\", \"density\"]\n\n            return _velocity\n\n        for v in \"xyz\":\n            self.add_field(\n                (\"gas\", f\"velocity_{v}\"),\n                sampling_type=\"cell\",\n                function=velocity_xyz(v),\n                units=unit_system[\"velocity\"],\n            )\n\n        if (\"gamer\", \"Pres\") not in self.field_list:\n            self.add_field(\n                (\"gas\", \"pressure\"),\n                sampling_type=\"cell\",\n                function=_pressure,\n                units=unit_system[\"pressure\"],\n            )\n\n        self.add_field(\n            (\"gas\", \"specific_thermal_energy\"),\n            sampling_type=\"cell\",\n            function=_specific_thermal_energy,\n            units=unit_system[\"specific_energy\"],\n        )\n\n        def _thermal_energy_density(data):\n            return data[\"gas\", \"density\"] * data[\"gas\", \"specific_thermal_energy\"]\n\n        self.add_field(\n            (\"gas\", \"thermal_energy_density\"),\n            sampling_type=\"cell\",\n            function=_thermal_energy_density,\n            units=unit_system[\"pressure\"],\n        )\n\n        self.add_field(\n            (\"gas\", \"specific_total_energy\"),\n            sampling_type=\"cell\",\n            function=_specific_total_energy,\n            units=unit_system[\"specific_energy\"],\n        )\n\n        if getattr(self.ds, \"gamma_cr\", None):\n\n            def _cr_pressure(data):\n                return (data.ds.gamma_cr - 1.0) * data[\n                    \"gas\", \"cosmic_ray_energy_density\"\n                ]\n\n            self.add_field(\n                (\"gas\", \"cosmic_ray_pressure\"),\n                _cr_pressure,\n                sampling_type=\"cell\",\n                units=self.ds.unit_system[\"pressure\"],\n            )\n\n        # mean molecular weight\n        if hasattr(self.ds, \"mu\"):\n\n            def _mu(data):\n                return data.ds.mu * data[\"index\", \"ones\"]\n\n            self.add_field(\n                (\"gas\", \"mean_molecular_weight\"),\n                sampling_type=\"cell\",\n                function=_mu,\n                units=\"\",\n            )\n\n        # temperature\n        if (\"gamer\", \"Temp\") not in self.field_list:\n\n            def _temperature(data):\n                return data[\"gas\", \"pressure\"] / (data[\"gas\", \"density\"] * temp_conv)\n\n            self.add_field(\n                (\"gas\", \"temperature\"),\n                sampling_type=\"cell\",\n                function=_temperature,\n                units=unit_system[\"temperature\"],\n            )\n\n        # magnetic field aliases --> magnetic_field_x/y/z\n        if self.ds.mhd:\n            setup_magnetic_field_aliases(self, \"gamer\", [f\"CCMag{v}\" for v in \"XYZ\"])\n\n    def setup_particle_fields(self, ptype):\n        super().setup_particle_fields(ptype)\n"
  },
  {
    "path": "yt/frontends/gamer/io.py",
    "content": "from itertools import groupby\n\nimport numpy as np\n\nfrom yt.geometry.selection_routines import AlwaysSelector\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\n\n# -----------------------------------------------------------------------------\n# GAMER shares a similar HDF5 format, and thus io.py as well, with FLASH\n# -----------------------------------------------------------------------------\n\n\n# group grids with consecutive indices together to improve the I/O performance\n# --> grids are assumed to be sorted into ascending numerical order already\ndef grid_sequences(grids):\n    for _k, g in groupby(enumerate(grids), lambda i_x: i_x[0] - i_x[1].id):\n        seq = [v[1] for v in g]\n        yield seq\n\n\ndef particle_sequences(grids):\n    for _k, g in groupby(enumerate(grids), lambda i_x: i_x[0] - i_x[1].id):\n        seq = [v[1] for v in g]\n        yield seq[0], seq[-1]\n\n\nclass IOHandlerGAMER(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"gamer\"\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        self._handle = ds._handle\n        self._group_grid = ds._group_grid\n        self._group_particle = ds._group_particle\n        self._field_dtype = \"float64\"  # fixed even when FLOAT8 is off\n        self._particle_handle = ds._particle_handle\n        self.patch_size = ds.parameters[\"PatchSize\"] * ds.refine_by\n        self.pgroup = ds.refine_by**3  # number of patches in a patch group\n\n    def _read_particle_coords(self, chunks, ptf):\n        chunks = list(chunks)  # generator --> list\n        p_idx = self.ds.index._particle_indices\n\n        # shortcuts\n        par_posx = self._group_particle[\"ParPosX\"]\n        par_posy = self._group_particle[\"ParPosY\"]\n        par_posz = self._group_particle[\"ParPosZ\"]\n\n        # currently GAMER does not support multiple particle types\n        assert len(ptf) == 1\n        ptype = list(ptf.keys())[0]\n\n        for chunk in chunks:\n            for g1, g2 in particle_sequences(chunk.objs):\n                start = p_idx[g1.id]\n                end = p_idx[g2.id + 1]\n                x = np.asarray(par_posx[start:end], dtype=self._field_dtype)\n                y = np.asarray(par_posy[start:end], dtype=self._field_dtype)\n                z = np.asarray(par_posz[start:end], dtype=self._field_dtype)\n                yield ptype, (x, y, z), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        chunks = list(chunks)  # generator --> list\n        p_idx = self.ds.index._particle_indices\n\n        # shortcuts\n        par_posx = self._group_particle[\"ParPosX\"]\n        par_posy = self._group_particle[\"ParPosY\"]\n        par_posz = self._group_particle[\"ParPosZ\"]\n\n        # currently GAMER does not support multiple particle types\n        assert len(ptf) == 1\n        ptype = list(ptf.keys())[0]\n        pfields = ptf[ptype]\n\n        for chunk in chunks:\n            for g1, g2 in particle_sequences(chunk.objs):\n                start = p_idx[g1.id]\n                end = p_idx[g2.id + 1]\n                x = np.asarray(par_posx[start:end], dtype=self._field_dtype)\n                y = np.asarray(par_posy[start:end], dtype=self._field_dtype)\n                z = np.asarray(par_posz[start:end], dtype=self._field_dtype)\n\n                mask = selector.select_points(x, y, z, 0.0)\n                if mask is None:\n                    continue\n\n                for field in pfields:\n                    data = self._group_particle[field][start:end]\n                    yield (ptype, field), data[mask]\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)  # generator --> list\n\n        if any((ftype != \"gamer\" for ftype, fname in fields)):\n            raise NotImplementedError\n\n        rv = {}\n        for field in fields:\n            rv[field] = np.empty(size, dtype=self._field_dtype)\n\n        ng = sum(len(c.objs) for c in chunks)  # c.objs is a list of grids\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s grids\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n\n        # shortcuts\n        ps2 = self.patch_size\n        ps1 = ps2 // 2\n\n        for field in fields:\n            ds = self._group_grid[field[1]]\n            offset = 0\n            for chunk in chunks:\n                for gs in grid_sequences(chunk.objs):\n                    start = (gs[0].id) * self.pgroup\n                    end = (gs[-1].id + 1) * self.pgroup\n                    buf = ds[start:end, :, :, :]\n                    ngrid = len(gs)\n                    data = np.empty((ngrid, ps2, ps2, ps2), dtype=self._field_dtype)\n\n                    for g in range(ngrid):\n                        pid0 = g * self.pgroup\n                        data[g, 0:ps1, 0:ps1, 0:ps1] = buf[pid0 + 0, :, :, :]\n                        data[g, 0:ps1, 0:ps1, ps1:ps2] = buf[pid0 + 1, :, :, :]\n                        data[g, 0:ps1, ps1:ps2, 0:ps1] = buf[pid0 + 2, :, :, :]\n                        data[g, ps1:ps2, 0:ps1, 0:ps1] = buf[pid0 + 3, :, :, :]\n                        data[g, 0:ps1, ps1:ps2, ps1:ps2] = buf[pid0 + 4, :, :, :]\n                        data[g, ps1:ps2, ps1:ps2, 0:ps1] = buf[pid0 + 5, :, :, :]\n                        data[g, ps1:ps2, 0:ps1, ps1:ps2] = buf[pid0 + 6, :, :, :]\n                        data[g, ps1:ps2, ps1:ps2, ps1:ps2] = buf[pid0 + 7, :, :, :]\n\n                    data = data.transpose()\n\n                    for i, g in enumerate(gs):\n                        offset += g.select(selector, data[..., i], rv[field], offset)\n        return rv\n\n    def _read_chunk_data(self, chunk, fields):\n        rv = {}\n        if len(chunk.objs) == 0:\n            return rv\n\n        for g in chunk.objs:\n            rv[g.id] = {}\n\n        # Split into particles and non-particles\n        fluid_fields, particle_fields = [], []\n        for ftype, fname in fields:\n            if ftype in self.ds.particle_types:\n                particle_fields.append((ftype, fname))\n            else:\n                fluid_fields.append((ftype, fname))\n\n        # particles\n        if len(particle_fields) > 0:\n            selector = AlwaysSelector(self.ds)\n            rv.update(self._read_particle_selection([chunk], selector, particle_fields))\n\n        # fluid\n        if len(fluid_fields) == 0:\n            return rv\n\n        ps2 = self.patch_size\n        ps1 = ps2 // 2\n\n        for field in fluid_fields:\n            ds = self._group_grid[field[1]]\n\n            for gs in grid_sequences(chunk.objs):\n                start = (gs[0].id) * self.pgroup\n                end = (gs[-1].id + 1) * self.pgroup\n                buf = ds[start:end, :, :, :]\n                ngrid = len(gs)\n                data = np.empty((ngrid, ps2, ps2, ps2), dtype=self._field_dtype)\n\n                for g in range(ngrid):\n                    pid0 = g * self.pgroup\n                    data[g, 0:ps1, 0:ps1, 0:ps1] = buf[pid0 + 0, :, :, :]\n                    data[g, 0:ps1, 0:ps1, ps1:ps2] = buf[pid0 + 1, :, :, :]\n                    data[g, 0:ps1, ps1:ps2, 0:ps1] = buf[pid0 + 2, :, :, :]\n                    data[g, ps1:ps2, 0:ps1, 0:ps1] = buf[pid0 + 3, :, :, :]\n                    data[g, 0:ps1, ps1:ps2, ps1:ps2] = buf[pid0 + 4, :, :, :]\n                    data[g, ps1:ps2, ps1:ps2, 0:ps1] = buf[pid0 + 5, :, :, :]\n                    data[g, ps1:ps2, 0:ps1, ps1:ps2] = buf[pid0 + 6, :, :, :]\n                    data[g, ps1:ps2, ps1:ps2, ps1:ps2] = buf[pid0 + 7, :, :, :]\n\n                data = data.transpose()\n\n                for i, g in enumerate(gs):\n                    rv[g.id][field] = data[..., i]\n        return rv\n"
  },
  {
    "path": "yt/frontends/gamer/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gamer/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gamer/tests/test_outputs.py",
    "content": "from numpy.testing import assert_array_almost_equal, assert_equal\n\nfrom yt.frontends.gamer.api import GAMERDataset\nfrom yt.testing import requires_file, units_override_check\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\njet = \"InteractingJets/jet_000002\"\n_fields_jet = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_magnitude\"),\n)\njet_units = {\n    \"length_unit\": (1.0, \"kpc\"),\n    \"time_unit\": (3.08567758096e13, \"s\"),\n    \"mass_unit\": (1.4690033e36, \"g\"),\n}\n\n\n@requires_ds(jet, big_data=True)\ndef test_jet():\n    ds = data_dir_load(jet, kwargs={\"units_override\": jet_units})\n    assert_equal(str(ds), \"jet_000002\")\n    for test in small_patch_amr(ds, _fields_jet):\n        test_jet.__name__ = test.description\n        yield test\n\n\npsiDM = \"WaveDarkMatter/psiDM_000020\"\n_fields_psiDM = (\"Dens\", \"Real\", \"Imag\")\n\n\n@requires_ds(psiDM, big_data=True)\ndef test_psiDM():\n    ds = data_dir_load(psiDM)\n    assert_equal(str(ds), \"psiDM_000020\")\n    for test in small_patch_amr(ds, _fields_psiDM):\n        test_psiDM.__name__ = test.description\n        yield test\n\n\nplummer = \"Plummer/plummer_000000\"\n_fields_plummer = ((\"gamer\", \"ParDens\"), (\"deposit\", \"io_cic\"))\n\n\n@requires_ds(plummer, big_data=True)\ndef test_plummer():\n    ds = data_dir_load(plummer)\n    assert_equal(str(ds), \"plummer_000000\")\n    for test in small_patch_amr(ds, _fields_plummer):\n        test_plummer.__name__ = test.description\n        yield test\n\n\nmhdvortex = \"MHDOrszagTangVortex/Data_000018\"\n_fields_mhdvortex = (\n    (\"gamer\", \"CCMagX\"),\n    (\"gamer\", \"CCMagY\"),\n    (\"gas\", \"magnetic_energy_density\"),\n)\n\n\n@requires_ds(mhdvortex, big_data=True)\ndef test_mhdvortex():\n    ds = data_dir_load(mhdvortex)\n    assert_equal(str(ds), \"Data_000018\")\n    for test in small_patch_amr(ds, _fields_mhdvortex):\n        test_mhdvortex.__name__ = test.description\n        yield test\n\n\n@requires_file(psiDM)\ndef test_GAMERDataset():\n    assert isinstance(data_dir_load(psiDM), GAMERDataset)\n\n\n@requires_file(jet)\ndef test_units_override():\n    units_override_check(jet)\n\n\njiw = \"JetICMWall/Data_000060\"\n_fields_jiw = (\n    (\"gas\", \"four_velocity_magnitude\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"gamma\"),\n    (\"gas\", \"temperature\"),\n)\n\n\n@requires_ds(jiw, big_data=True)\ndef test_jiw():\n    ds = data_dir_load(jiw)\n    assert_equal(str(ds), \"Data_000060\")\n    for test in small_patch_amr(ds, _fields_jiw):\n        test_jiw.__name__ = test.description\n        yield test\n\n\n@requires_ds(jiw, big_data=True)\ndef test_stress_energy():\n    axes = \"txyz\"\n    ds = data_dir_load(jiw)\n    center = ds.arr([3.0, 10.0, 10.0], \"kpc\")\n    sp = ds.sphere(center, (1.0, \"kpc\"))\n    c2 = ds.units.clight**2\n    inv_c2 = 1.0 / c2\n    rho = sp[\"gas\", \"density\"]\n    p = sp[\"gas\", \"pressure\"]\n    e = sp[\"gas\", \"thermal_energy_density\"]\n    h = rho + (e + p) * inv_c2\n    for mu in range(4):\n        for nu in range(4):\n            # matrix is symmetric so only do the upper-right part\n            if nu >= mu:\n                Umu = sp[f\"four_velocity_{axes[mu]}\"]\n                Unu = sp[f\"four_velocity_{axes[nu]}\"]\n                Tmunu = h * Umu * Unu\n                if mu != nu:\n                    assert_array_almost_equal(sp[f\"T{mu}{nu}\"], sp[f\"T{nu}{mu}\"])\n                else:\n                    Tmunu += p\n                assert_array_almost_equal(sp[f\"T{mu}{nu}\"], Tmunu)\n\n\ncr_shock = \"CRShockTube/Data_000005\"\n\n\n@requires_ds(cr_shock)\ndef test_cosmic_rays():\n    ds = data_dir_load(cr_shock)\n    assert_array_almost_equal(ds.gamma_cr, 4.0 / 3.0)\n    ad = ds.all_data()\n    p_cr = ad[\"gas\", \"cosmic_ray_pressure\"]\n    e_cr = ad[\"gas\", \"cosmic_ray_energy_density\"]\n    assert_array_almost_equal(p_cr, e_cr / 3.0)\n    e_kin = ad[\"gas\", \"kinetic_energy_density\"]\n    e_int = ad[\"gas\", \"thermal_energy_density\"]\n    e_tot = ad[\"gas\", \"total_energy_density\"]\n    assert_array_almost_equal(e_tot, e_kin + e_int + e_cr)\n"
  },
  {
    "path": "yt/frontends/gdf/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gdf/api.py",
    "content": "from . import tests\nfrom .data_structures import GDFDataset, GDFGrid, GDFHierarchy\nfrom .fields import GDFFieldInfo\nfrom .io import IOHandlerGDFHDF5\n\nadd_gdf_field = GDFFieldInfo.add_field\n"
  },
  {
    "path": "yt/frontends/gdf/data_structures.py",
    "content": "import os\nimport weakref\nfrom functools import cached_property\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import just_one, setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.units.dimensions import dimensionless as sympy_one  # type: ignore\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.units.unit_systems import unit_system_registry  # type: ignore\nfrom yt.utilities.exceptions import YTGDFUnknownGeometry\nfrom yt.utilities.lib.misc_utilities import get_box_grids_level\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import GDFFieldInfo\n\nGEOMETRY_TRANS = {\n    0: \"cartesian\",\n    1: \"polar\",\n    2: \"cylindrical\",\n    3: \"spherical\",\n}\n\n\nclass GDFGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level, start, dimensions):\n        AMRGridPatch.__init__(self, id, filename=index.index_filename, index=index)\n        self.Parent = []\n        self.Children = []\n        self.Level = level\n        self.start_index = start.copy()\n        self.stop_index = self.start_index + dimensions\n        self.ActiveDimensions = dimensions.copy()\n\n    def _setup_dx(self):\n        # So first we figure out what the index is.  We don't assume\n        # that dx=dy=dz , at least here.  We probably do elsewhere.\n        id = self.id - self._id_offset\n        if len(self.Parent) > 0:\n            self.dds = self.Parent[0].dds / self.ds.refine_by\n        else:\n            LE, RE = self.index.grid_left_edge[id, :], self.index.grid_right_edge[id, :]\n            self.dds = np.array((RE - LE) / self.ActiveDimensions)\n        self.field_data[\"dx\"], self.field_data[\"dy\"], self.field_data[\"dz\"] = self.dds\n        self.dds = self.ds.arr(self.dds, \"code_length\")\n\n\nclass GDFHierarchy(GridIndex):\n    grid = GDFGrid\n\n    def __init__(self, ds, dataset_type=\"grid_data_format\"):\n        self.dataset = weakref.proxy(ds)\n        self.index_filename = self.dataset.parameter_filename\n        h5f = h5py.File(self.index_filename, mode=\"r\")\n        self.dataset_type = dataset_type\n        GridIndex.__init__(self, ds, dataset_type)\n        self.directory = os.path.dirname(self.index_filename)\n        h5f.close()\n\n    def _detect_output_fields(self):\n        h5f = h5py.File(self.index_filename, mode=\"r\")\n        self.field_list = [(\"gdf\", str(f)) for f in h5f[\"field_types\"].keys()]\n        h5f.close()\n\n    def _count_grids(self):\n        h5f = h5py.File(self.index_filename, mode=\"r\")\n        self.num_grids = h5f[\"/grid_parent_id\"].shape[0]\n        h5f.close()\n\n    def _parse_index(self):\n        h5f = h5py.File(self.index_filename, mode=\"r\")\n        dxs = []\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n        levels = (h5f[\"grid_level\"][:]).copy()\n        glis = (h5f[\"grid_left_index\"][:]).copy()\n        gdims = (h5f[\"grid_dimensions\"][:]).copy()\n        active_dims = ~(\n            (np.max(gdims, axis=0) == 1) & (self.dataset.domain_dimensions == 1)\n        )\n\n        for i in range(levels.shape[0]):\n            self.grids[i] = self.grid(i, self, levels[i], glis[i], gdims[i])\n            self.grids[i]._level_id = levels[i]\n\n            dx = (\n                self.dataset.domain_right_edge - self.dataset.domain_left_edge\n            ) / self.dataset.domain_dimensions\n            dx[active_dims] /= self.dataset.refine_by ** levels[i]\n            dxs.append(dx.in_units(\"code_length\"))\n        dx = self.dataset.arr(dxs, units=\"code_length\")\n        self.grid_left_edge = self.dataset.domain_left_edge + dx * glis\n        self.grid_dimensions = gdims.astype(\"int32\")\n        self.grid_right_edge = self.grid_left_edge + dx * self.grid_dimensions\n        self.grid_particle_count = h5f[\"grid_particle_count\"][:]\n        del levels, glis, gdims\n        h5f.close()\n\n    def _populate_grid_objects(self):\n        mask = np.empty(self.grids.size, dtype=\"int32\")\n        for g in self.grids:\n            g._prepare_grid()\n            g._setup_dx()\n\n        for gi, g in enumerate(self.grids):\n            g.Children = self._get_grid_children(g)\n            for g1 in g.Children:\n                g1.Parent.append(g)\n            get_box_grids_level(\n                self.grid_left_edge[gi, :],\n                self.grid_right_edge[gi, :],\n                self.grid_levels[gi].item(),\n                self.grid_left_edge,\n                self.grid_right_edge,\n                self.grid_levels,\n                mask,\n            )\n            m = mask.astype(\"bool\")\n            m[gi] = False\n            siblings = self.grids[gi:][m[gi:]]\n            if len(siblings) > 0:\n                g.OverlappingSiblings = siblings.tolist()\n        self.max_level = self.grid_levels.max()\n\n    def _get_box_grids(self, left_edge, right_edge):\n        \"\"\"\n        Gets back all the grids between a left edge and right edge\n        \"\"\"\n        eps = np.finfo(np.float64).eps\n        grid_i = np.where(\n            np.all((self.grid_right_edge - left_edge) > eps, axis=1)\n            & np.all((right_edge - self.grid_left_edge) > eps, axis=1)\n        )\n\n        return self.grids[grid_i], grid_i\n\n    def _get_grid_children(self, grid):\n        mask = np.zeros(self.num_grids, dtype=\"bool\")\n        grids, grid_ind = self._get_box_grids(grid.LeftEdge, grid.RightEdge)\n        mask[grid_ind] = True\n        return [g for g in self.grids[mask] if g.Level == grid.Level + 1]\n\n\nclass GDFDataset(Dataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = GDFHierarchy\n    _field_info_class = GDFFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"grid_data_format\",\n        storage_filename=None,\n        geometry=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        self.geometry = geometry\n        self.fluid_types += (\"gdf\",)\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        self.storage_filename = storage_filename\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Generates the conversion to various physical _units\n        based on the parameter file\n        \"\"\"\n\n        # This should be improved.\n        h5f = h5py.File(self.parameter_filename, mode=\"r\")\n        for field_name in h5f[\"/field_types\"]:\n            current_field = h5f[f\"/field_types/{field_name}\"]\n            if \"field_to_cgs\" in current_field.attrs:\n                field_conv = current_field.attrs[\"field_to_cgs\"]\n                self.field_units[field_name] = just_one(field_conv)\n            elif \"field_units\" in current_field.attrs:\n                field_units = current_field.attrs[\"field_units\"]\n                if isinstance(field_units, str):\n                    current_field_units = current_field.attrs[\"field_units\"]\n                else:\n                    current_field_units = just_one(current_field.attrs[\"field_units\"])\n                self.field_units[field_name] = current_field_units.decode(\"utf8\")\n            else:\n                self.field_units[field_name] = \"\"\n\n        if \"dataset_units\" in h5f:\n            for unit_name in h5f[\"/dataset_units\"]:\n                current_unit = h5f[f\"/dataset_units/{unit_name}\"]\n                value = current_unit[()]\n                unit = current_unit.attrs[\"unit\"]\n                # need to convert to a Unit object and check dimensions\n                # because unit can be things like\n                # 'dimensionless/dimensionless**3' so naive string\n                # comparisons are insufficient\n                unit = Unit(unit, registry=self.unit_registry)\n                if unit_name.endswith(\"_unit\") and unit.dimensions is sympy_one:\n                    # Catch code units and if they are dimensionless,\n                    # assign CGS units. setdefaultattr will catch code units\n                    # which have already been set via units_override.\n                    un = unit_name[:-5]\n                    un = un.replace(\"magnetic\", \"magnetic_field_cgs\", 1)\n                    unit = unit_system_registry[\"cgs\"][un]\n                    setdefaultattr(self, unit_name, self.quan(value, unit))\n                setdefaultattr(self, unit_name, self.quan(value, unit))\n                if unit_name in h5f[\"/field_types\"]:\n                    if unit_name in self.field_units:\n                        mylog.warning(\n                            \"'field_units' was overridden by 'dataset_units/%s'\",\n                            unit_name,\n                        )\n                    self.field_units[unit_name] = str(unit)\n        else:\n            setdefaultattr(self, \"length_unit\", self.quan(1.0, \"cm\"))\n            setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"g\"))\n            setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n\n        h5f.close()\n\n    @cached_property\n    def unique_identifier(self) -> str:\n        with h5py.File(self.parameter_filename, mode=\"r\") as handle:\n            return str(handle[\"/simulation_parameters\"].attrs[\"unique_identifier\"])\n\n    def _parse_parameter_file(self):\n        self._handle = h5py.File(self.parameter_filename, mode=\"r\")\n        if \"data_software\" in self._handle[\"gridded_data_format\"].attrs:\n            self.data_software = self._handle[\"gridded_data_format\"].attrs[\n                \"data_software\"\n            ]\n        else:\n            self.data_software = \"unknown\"\n        sp = self._handle[\"/simulation_parameters\"].attrs\n        if self.geometry is None:\n            geometry = just_one(sp.get(\"geometry\", 0))\n            try:\n                self.geometry = Geometry(GEOMETRY_TRANS[geometry])\n            except KeyError as e:\n                raise YTGDFUnknownGeometry(geometry) from e\n        self.parameters.update(sp)\n        self.domain_left_edge = sp[\"domain_left_edge\"][:]\n        self.domain_right_edge = sp[\"domain_right_edge\"][:]\n        self.domain_dimensions = sp[\"domain_dimensions\"][:]\n        refine_by = sp[\"refine_by\"]\n        if refine_by is None:\n            refine_by = 2\n        self.refine_by = refine_by\n        self.dimensionality = sp[\"dimensionality\"]\n        self.current_time = sp[\"current_time\"]\n        self.cosmological_simulation = sp[\"cosmological_simulation\"]\n        if sp[\"num_ghost_zones\"] != 0:\n            raise RuntimeError\n        self.num_ghost_zones = sp[\"num_ghost_zones\"]\n        self.field_ordering = sp[\"field_ordering\"]\n        self.boundary_conditions = sp[\"boundary_conditions\"][:]\n        self._periodicity = tuple(bnd == 0 for bnd in self.boundary_conditions[::2])\n        if self.cosmological_simulation:\n            self.current_redshift = sp[\"current_redshift\"]\n            self.omega_lambda = sp[\"omega_lambda\"]\n            self.omega_matter = sp[\"omega_matter\"]\n            self.hubble_constant = sp[\"hubble_constant\"]\n        else:\n            self.current_redshift = 0.0\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0\n            self.hubble_constant = 0.0\n            self.cosmological_simulation = 0\n        # Hardcode time conversion for now.\n        self.parameters[\"Time\"] = 1.0\n        # Hardcode for now until field staggering is supported.\n        self.parameters[\"HydroMethod\"] = 0\n        self._handle.close()\n        del self._handle\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            fileh = h5py.File(filename, mode=\"r\")\n            if \"gridded_data_format\" in fileh:\n                fileh.close()\n                return True\n            fileh.close()\n        except Exception:\n            pass\n        return False\n\n    def __str__(self):\n        return self.basename.rsplit(\".\", 1)[0]\n"
  },
  {
    "path": "yt/frontends/gdf/definitions.py",
    "content": "\"\"\"\nVarious definitions for various other modules and routines\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/gdf/fields.py",
    "content": "from yt.fields.field_info_container import FieldInfoContainer\n\n# The nice thing about GDF is that for the most part, everything is in CGS,\n# with potentially a scalar modification.\n\n\nclass GDFFieldInfo(FieldInfoContainer):\n    known_other_fields = (\n        (\"density\", (\"g/cm**3\", [\"density\"], None)),\n        (\"specific_energy\", (\"erg/g\", [\"specific_thermal_energy\"], None)),\n        (\"pressure\", (\"erg/cm**3\", [\"pressure\"], None)),\n        (\"temperature\", (\"K\", [\"temperature\"], None)),\n        (\"velocity_x\", (\"cm/s\", [\"velocity_x\"], None)),\n        (\"velocity_y\", (\"cm/s\", [\"velocity_y\"], None)),\n        (\"velocity_z\", (\"cm/s\", [\"velocity_z\"], None)),\n        (\"mag_field_x\", (\"gauss\", [\"magnetic_field_x\"], None)),\n        (\"mag_field_y\", (\"gauss\", [\"magnetic_field_y\"], None)),\n        (\"mag_field_z\", (\"gauss\", [\"magnetic_field_z\"], None)),\n    )\n    known_particle_fields = ()\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        setup_magnetic_field_aliases(\n            self, \"gdf\", [f\"magnetic_field_{ax}\" for ax in \"xyz\"]\n        )\n"
  },
  {
    "path": "yt/frontends/gdf/io.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog\nfrom yt.geometry.selection_routines import GridSelector\nfrom yt.utilities.io_handler import BaseParticleIOHandler\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\ndef _grid_dname(grid_id):\n    return f\"/data/grid_{grid_id:010}\"\n\n\ndef _field_dname(grid_id, field_name):\n    return f\"{_grid_dname(grid_id)}/{field_name}\"\n\n\n# TODO all particle bits were removed\nclass IOHandlerGDFHDF5(BaseParticleIOHandler):\n    _dataset_type = \"grid_data_format\"\n    _offset_string = \"data:offsets=0\"\n    _data_string = \"data:datatype=0\"\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        rv = {}\n        chunks = list(chunks)\n\n        if isinstance(selector, GridSelector):\n            if not (len(chunks) == len(chunks[0].objs) == 1):\n                raise RuntimeError\n            grid = chunks[0].objs[0]\n            h5f = h5py.File(grid.filename, mode=\"r\")\n            gds = h5f.get(_grid_dname(grid.id))\n            for ftype, fname in fields:\n                if self.ds.field_ordering == 1:\n                    rv[ftype, fname] = gds.get(fname)[()].swapaxes(0, 2)\n                else:\n                    rv[ftype, fname] = gds.get(fname)[()]\n            h5f.close()\n            return rv\n        if size is None:\n            size = sum(grid.count(selector) for chunk in chunks for grid in chunk.objs)\n\n        if any((ftype != \"gdf\" for ftype, fname in fields)):\n            raise NotImplementedError\n\n        for field in fields:\n            ftype, fname = field\n            fsize = size\n            # check the dtype instead\n            rv[field] = np.empty(fsize, dtype=\"float64\")\n        ngrids = sum(len(chunk.objs) for chunk in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s blocks\",\n            size,\n            [fn for ft, fn in fields],\n            ngrids,\n        )\n        ind = 0\n        for chunk in chunks:\n            fid = None\n            for grid in chunk.objs:\n                if grid.filename is None:\n                    continue\n                if fid is None:\n                    fid = h5py.h5f.open(\n                        bytes(grid.filename, \"utf-8\"), h5py.h5f.ACC_RDONLY\n                    )\n                if self.ds.field_ordering == 1:\n                    # check the dtype instead\n                    data = np.empty(grid.ActiveDimensions[::-1], dtype=\"float64\")\n                    data_view = data.swapaxes(0, 2)\n                else:\n                    # check the dtype instead\n                    data_view = data = np.empty(grid.ActiveDimensions, dtype=\"float64\")\n                for field in fields:\n                    ftype, fname = field\n                    dg = h5py.h5d.open(\n                        fid, bytes(_field_dname(grid.id, fname), \"utf-8\")\n                    )\n                    dg.read(h5py.h5s.ALL, h5py.h5s.ALL, data)\n                    # caches\n                    nd = grid.select(selector, data_view, rv[field], ind)\n                ind += nd  # I don't get that part, only last nd is added\n            if fid is not None:\n                fid.close()\n        return rv\n"
  },
  {
    "path": "yt/frontends/gdf/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gdf/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gdf/tests/conftest.py",
    "content": "\"\"\"\nTitle: conftest.py\nPurpose: Contains fixtures for loading data.\n\"\"\"\n\n# Test data\nsedov = \"sedov/sedov_tst_0004.h5\"\n\n\n# Test parameters. Format:\n# {test1: {param1 : [(val1, val2,...), (id1, id2,...)], param2 : ...}, test2: ...}\ntest_params = {\n    \"test_sedov_tunnel\": {\n        \"axis\": [(0, 1, 2), (\"0\", \"1\", \"2\")],\n        \"dobj\": [(None, (\"sphere\", (\"max\", (0.1, \"unitary\")))), (\"None\", \"sphere\")],\n        \"weight\": [(None, (\"gas\", \"density\")), (\"None\", \"density\")],\n        \"field\": [((\"gas\", \"density\"), (\"gas\", \"velocity_x\")), (\"density\", \"vx\")],\n    }\n}\n\n\ndef pytest_generate_tests(metafunc):\n    # Loop over each test in test_params\n    for test_name, params in test_params.items():\n        if metafunc.function.__name__ == test_name:\n            # Parametrize\n            for param_name, param_vals in params.items():\n                metafunc.parametrize(param_name, param_vals[0], ids=param_vals[1])\n"
  },
  {
    "path": "yt/frontends/gdf/tests/test_outputs.py",
    "content": "import pytest\n\nfrom yt.frontends.gdf.api import GDFDataset\nfrom yt.testing import requires_file, requires_module, units_override_check\nfrom yt.utilities.answer_testing.answer_tests import small_patch_amr\n\n# Test data\nsedov = \"sedov/sedov_tst_0004.h5\"\n\n\n@pytest.mark.answer_test\nclass TestGDF:\n    answer_file = None\n    saved_hashes = None\n    answer_version = \"000\"\n\n    @pytest.mark.usefixtures(\"hashing\")\n    @pytest.mark.parametrize(\"ds\", [sedov], indirect=True)\n    def test_sedov_tunnel(self, axis, dobj, weight, field, ds):\n        self.hashes.update(small_patch_amr(ds, field, weight, axis, dobj))\n\n    @pytest.mark.parametrize(\"ds\", [sedov], indirect=True)\n    def test_GDFDataset(self, ds):\n        assert isinstance(ds, GDFDataset)\n\n    @requires_module(\"h5py\")\n    @requires_file(sedov)\n    def test_units_override(self):\n        units_override_check(sedov)\n"
  },
  {
    "path": "yt/frontends/gdf/tests/test_outputs_nose.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.frontends.gdf.api import GDFDataset\nfrom yt.testing import requires_file, requires_module, units_override_check\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\n_fields = [(\"gas\", \"density\"), (\"gas\", \"velocity_x\")]\n\nsedov = \"sedov/sedov_tst_0004.h5\"\n\n\n@requires_ds(sedov)\ndef test_sedov_tunnel():\n    ds = data_dir_load(sedov)\n    assert_equal(str(ds), \"sedov_tst_0004\")\n    for test in small_patch_amr(ds, _fields):\n        test_sedov_tunnel.__name__ = test.description\n        yield test\n\n\n@requires_module(\"h5py\")\n@requires_file(sedov)\ndef test_GDFDataset():\n    assert isinstance(data_dir_load(sedov), GDFDataset)\n\n\n@requires_module(\"h5py\")\n@requires_file(sedov)\ndef test_units_override():\n    units_override_check(sedov)\n"
  },
  {
    "path": "yt/frontends/gizmo/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gizmo/api.py",
    "content": "from .data_structures import GizmoDataset\nfrom .fields import GizmoFieldInfo\n"
  },
  {
    "path": "yt/frontends/gizmo/data_structures.py",
    "content": "import os\n\nimport numpy as np\n\nfrom yt.frontends.gadget.data_structures import GadgetHDF5Dataset\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import GizmoFieldInfo\n\n\nclass GizmoDataset(GadgetHDF5Dataset):\n    _load_requirements = [\"h5py\"]\n    _field_info_class = GizmoFieldInfo\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        need_groups = [\"Header\"]\n        veto_groups = [\"Config\", \"Constants\", \"FOF\", \"Group\", \"Subhalo\"]\n        valid = True\n        valid_fname = filename\n        # If passed arg is a directory, look for the .0 file in that dir\n        if os.path.isdir(filename):\n            valid_files = []\n            for f in os.listdir(filename):\n                fname = os.path.join(filename, f)\n                fext = os.path.splitext(fname)[-1]\n                if (\n                    (\".0\" in f)\n                    and (fext not in {\".ewah\", \".kdtree\"})\n                    and os.path.isfile(fname)\n                ):\n                    valid_files.append(fname)\n            if len(valid_files) == 0:\n                valid = False\n            elif len(valid_files) > 1:\n                valid = False\n            else:\n                valid_fname = valid_files[0]\n        try:\n            fh = h5py.File(valid_fname, mode=\"r\")\n            valid = all(ng in fh[\"/\"] for ng in need_groups) and not any(\n                vg in fh[\"/\"] for vg in veto_groups\n            )\n            # From Apr 2021, 7f1f06f, public gizmo includes a header variable\n            # GIZMO_version, which is set to the year of the most recent commit\n            # We should prefer this to checking the metallicity, which might\n            # not exist\n            if \"GIZMO_version\" not in fh[\"/Header\"].attrs:\n                dmetal = \"/PartType0/Metallicity\"\n                if dmetal not in fh or (\n                    fh[dmetal].ndim > 1 and fh[dmetal].shape[1] < 11\n                ):\n                    valid = False\n            fh.close()\n        except Exception:\n            valid = False\n        return valid\n\n    def _set_code_unit_attributes(self):\n        super()._set_code_unit_attributes()\n\n    def _parse_parameter_file(self):\n        hvals = self._get_hvals()\n\n        self.dimensionality = 3\n        self.refine_by = 2\n        self.parameters[\"HydroMethod\"] = \"sph\"\n        # Set standard values\n\n        # We may have an overridden bounding box.\n        if self.domain_left_edge is None and hvals[\"BoxSize\"] != 0:\n            self.domain_left_edge = np.zeros(3, \"float64\")\n            self.domain_right_edge = np.ones(3, \"float64\") * hvals[\"BoxSize\"]\n\n        self.domain_dimensions = np.ones(3, \"int32\")\n        self._periodicity = (True, True, True)\n\n        self.cosmological_simulation = 1\n\n        self.current_redshift = hvals.get(\"Redshift\", 0.0)\n        if \"Redshift\" not in hvals:\n            mylog.info(\"Redshift is not set in Header. Assuming z=0.\")\n\n        if \"ComovingIntegrationOn\" in hvals:\n            # In 1d8479, Nov 2020, public GIZMO updated the names of the Omegas\n            # to include an _, added baryons and radiation and added the\n            # ComovingIntegrationOn field. ComovingIntegrationOn is always set,\n            # but the Omega's are only included if ComovingIntegrationOn is true\n            mylog.debug(\"Reading cosmological parameters using post-1d8479 format\")\n            self.cosmological_simulation = hvals[\"ComovingIntegrationOn\"]\n            if self.cosmological_simulation:\n                self.omega_lambda = hvals[\"Omega_Lambda\"]\n                self.omega_matter = hvals[\"Omega_Matter\"]\n                self.omega_baryon = hvals[\"Omega_Baryon\"]\n                self.omega_radiation = hvals[\"Omega_Radiation\"]\n            self.hubble_constant = hvals[\"HubbleParam\"]\n        elif \"OmegaLambda\" in hvals:\n            # Should still support GIZMO versions prior to 1d8479 too\n            mylog.info(\n                \"ComovingIntegrationOn does not exist, falling back to OmegaLambda\",\n            )\n            self.omega_lambda = hvals[\"OmegaLambda\"]\n            self.omega_matter = hvals[\"Omega0\"]\n            self.hubble_constant = hvals[\"HubbleParam\"]\n            self.cosmological_simulation = self.omega_lambda != 0.0\n        else:\n            # If these are not set it is definitely not a cosmological dataset.\n            mylog.debug(\"No cosmological information found, assuming defaults\")\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0  # Just in case somebody asks for it.\n            self.cosmological_simulation = 0\n            # Hubble is set below for Omega Lambda = 0.\n\n        if not self.cosmological_simulation:\n            mylog.info(\n                \"ComovingIntegrationOn != 1 or (not found \"\n                \"and OmegaLambda is 0.0), so we are turning off Cosmology.\",\n            )\n            self.hubble_constant = 1.0  # So that scaling comes out correct\n            self.current_redshift = 0.0\n            # This may not be correct.\n            self.current_time = hvals[\"Time\"]\n        else:\n            # Now we calculate our time based on the cosmology, because in\n            # ComovingIntegration hvals[\"Time\"] will in fact be the expansion\n            # factor, not the actual integration time, so we re-calculate\n            # global time from our Cosmology.\n            cosmo = Cosmology(\n                hubble_constant=self.hubble_constant,\n                omega_matter=self.omega_matter,\n                omega_lambda=self.omega_lambda,\n            )\n            self.current_time = cosmo.lookback_time(self.current_redshift, 1e6)\n            mylog.info(\n                \"Calculating time from %0.3e to be %0.3e seconds\",\n                hvals[\"Time\"],\n                self.current_time,\n            )\n        self.parameters = hvals\n\n        prefix = os.path.join(self.directory, self.basename.split(\".\", 1)[0])\n\n        if hvals[\"NumFiles\"] > 1:\n            self.filename_template = f\"{prefix}.%(num)s{self._suffix}\"\n        else:\n            self.filename_template = self.parameter_filename\n\n        self.file_count = hvals[\"NumFiles\"]\n"
  },
  {
    "path": "yt/frontends/gizmo/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.fields.species_fields import add_species_field_by_density, setup_species_fields\nfrom yt.frontends.gadget.fields import GadgetFieldInfo\nfrom yt.frontends.sph.fields import SPHFieldInfo\n\nmetal_elements = [\"He\", \"C\", \"N\", \"O\", \"Ne\", \"Mg\", \"Si\", \"S\", \"Ca\", \"Fe\"]\n\n\nclass GizmoFieldInfo(GadgetFieldInfo):\n    # The known fields list is according to the GIZMO User Guide. See\n    # http://www.tapir.caltech.edu/~phopkins/Site/GIZMO_files/gizmo_documentation.html#snaps-reading\n    known_particle_fields: KnownFieldsT = (\n        (\"Coordinates\", (\"code_length\", [\"particle_position\"], None)),\n        (\"Velocities\", (\"code_velocity\", [\"particle_velocity\"], None)),\n        (\"ParticleIDs\", (\"\", [\"particle_index\"], None)),\n        (\"Masses\", (\"code_mass\", [\"particle_mass\"], None)),\n        (\"InternalEnergy\", (\"code_specific_energy\", [\"specific_thermal_energy\"], None)),\n        (\"Density\", (\"code_mass / code_length**3\", [\"density\"], None)),\n        (\"SmoothingLength\", (\"code_length\", [\"smoothing_length\"], None)),\n        (\"ElectronAbundance\", (\"\", [], None)),\n        (\"NeutralHydrogenAbundance\", (\"\", [], None)),\n        (\"StarFormationRate\", (\"Msun / yr\", [\"star_formation_rate\"], None)),\n        (\"Metallicity\", (\"code_metallicity\", [\"metallicity\"], None)),\n        (\"Metallicity_00\", (\"\", [\"metallicity\"], None)),\n        (\"Metallicity_01\", (\"\", [\"He_metallicity\"], None)),\n        (\"Metallicity_02\", (\"\", [\"C_metallicity\"], None)),\n        (\"Metallicity_03\", (\"\", [\"N_metallicity\"], None)),\n        (\"Metallicity_04\", (\"\", [\"O_metallicity\"], None)),\n        (\"Metallicity_05\", (\"\", [\"Ne_metallicity\"], None)),\n        (\"Metallicity_06\", (\"\", [\"Mg_metallicity\"], None)),\n        (\"Metallicity_07\", (\"\", [\"Si_metallicity\"], None)),\n        (\"Metallicity_08\", (\"\", [\"S_metallicity\"], None)),\n        (\"Metallicity_09\", (\"\", [\"Ca_metallicity\"], None)),\n        (\"Metallicity_10\", (\"\", [\"Fe_metallicity\"], None)),\n        (\"ArtificialViscosity\", (\"\", [], None)),\n        (\"MagneticField\", (\"code_magnetic\", [\"particle_magnetic_field\"], None)),\n        (\"DivergenceOfMagneticField\", (\"code_magnetic / code_length\", [], None)),\n        (\"StellarFormationTime\", (\"\", [], None)),\n        # \"StellarFormationTime\" has different meanings in (non-)cosmological\n        # runs, so units are left blank here.\n        (\"BH_Mass\", (\"code_mass\", [], None)),\n        (\"BH_Mdot\", (\"code_mass / code_time\", [], None)),\n        (\"BH_Mass_AlphaDisk\", (\"code_mass\", [], None)),\n    )\n\n    def __init__(self, *args, **kwargs):\n        super(SPHFieldInfo, self).__init__(*args, **kwargs)\n        if (\"PartType0\", \"Metallicity_00\") in self.field_list:\n            self.nuclei_names = metal_elements\n            self.species_names = [\"H_p0\", \"H_p1\"] + metal_elements\n        else:\n            self.nuclei_names = []\n\n    def setup_particle_fields(self, ptype):\n        FieldInfoContainer.setup_particle_fields(self, ptype)\n        if ptype in (\"PartType0\",):\n            self.setup_gas_particle_fields(ptype)\n            setup_species_fields(self, ptype)\n        if ptype in (\"PartType4\",):\n            self.setup_star_particle_fields(ptype)\n\n    def setup_gas_particle_fields(self, ptype):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        super().setup_gas_particle_fields(ptype)\n\n        def _h_p0_density(data):\n            x_H = 1.0 - data[ptype, \"He_metallicity\"] - data[ptype, \"metallicity\"]\n            return (\n                x_H * data[ptype, \"density\"] * data[ptype, \"NeutralHydrogenAbundance\"]\n            )\n\n        self.add_field(\n            (ptype, \"H_p0_density\"),\n            sampling_type=\"particle\",\n            function=_h_p0_density,\n            units=self.ds.unit_system[\"density\"],\n        )\n        add_species_field_by_density(self, ptype, \"H\")\n\n        def _h_p1_density(data):\n            x_H = 1.0 - data[ptype, \"He_metallicity\"] - data[ptype, \"metallicity\"]\n            return (\n                x_H\n                * data[ptype, \"density\"]\n                * (1.0 - data[ptype, \"NeutralHydrogenAbundance\"])\n            )\n\n        self.add_field(\n            (ptype, \"H_p1_density\"),\n            sampling_type=\"particle\",\n            function=_h_p1_density,\n            units=self.ds.unit_system[\"density\"],\n        )\n        add_species_field_by_density(self, ptype, \"H_p1\")\n\n        def _nuclei_mass_density_field(data):\n            species = field.name[1][: field.name[1].find(\"_\")]\n            return data[ptype, \"density\"] * data[ptype, f\"{species}_metallicity\"]\n\n        for species in [\"H\", \"H_p0\", \"H_p1\"]:\n            for suf in [\"_density\", \"_number_density\"]:\n                field = f\"{species}{suf}\"\n                self.alias((\"gas\", field), (ptype, field))\n\n        if (ptype, \"ElectronAbundance\") in self.field_list:\n\n            def _el_number_density(data):\n                return (\n                    data[ptype, \"ElectronAbundance\"] * data[ptype, \"H_nuclei_density\"]\n                )\n\n            self.add_field(\n                (ptype, \"El_number_density\"),\n                sampling_type=\"particle\",\n                function=_el_number_density,\n                units=self.ds.unit_system[\"number_density\"],\n            )\n            self.alias((\"gas\", \"El_number_density\"), (ptype, \"El_number_density\"))\n\n        for species in self.nuclei_names:\n            self.add_field(\n                (ptype, f\"{species}_nuclei_mass_density\"),\n                sampling_type=\"particle\",\n                function=_nuclei_mass_density_field,\n                units=self.ds.unit_system[\"density\"],\n            )\n\n            for suf in [\"_nuclei_mass_density\", \"_metallicity\"]:\n                field = f\"{species}{suf}\"\n                self.alias((\"gas\", field), (ptype, field))\n\n        def _metal_density_field(data):\n            return data[ptype, \"metallicity\"] * data[ptype, \"density\"]\n\n        self.add_field(\n            (ptype, \"metal_density\"),\n            sampling_type=\"local\",\n            function=_metal_density_field,\n            units=self.ds.unit_system[\"density\"],\n        )\n        self.alias((\"gas\", \"metal_density\"), (ptype, \"metal_density\"))\n\n        magnetic_field = \"MagneticField\"\n        if (ptype, magnetic_field) in self.field_list:\n            setup_magnetic_field_aliases(self, ptype, magnetic_field)\n\n    def setup_star_particle_fields(self, ptype):\n        def _creation_time(data):\n            if data.ds.cosmological_simulation:\n                a_form = data[ptype, \"StellarFormationTime\"]\n                z_form = 1 / a_form - 1\n                creation_time = data.ds.cosmology.t_from_z(z_form)\n            else:\n                t_form = data[ptype, \"StellarFormationTime\"]\n                creation_time = data.ds.arr(t_form, \"code_time\")\n            return creation_time\n\n        self.add_field(\n            (ptype, \"creation_time\"),\n            sampling_type=\"particle\",\n            function=_creation_time,\n            units=self.ds.unit_system[\"time\"],\n        )\n\n        def _age(data):\n            return data.ds.current_time - data[ptype, \"creation_time\"]\n\n        self.add_field(\n            (ptype, \"age\"),\n            sampling_type=\"particle\",\n            function=_age,\n            units=self.ds.unit_system[\"time\"],\n        )\n"
  },
  {
    "path": "yt/frontends/gizmo/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/gizmo/tests/test_outputs.py",
    "content": "from collections import OrderedDict\n\nimport yt\nfrom yt.frontends.gizmo.api import GizmoDataset\nfrom yt.frontends.gizmo.fields import metal_elements\nfrom yt.testing import assert_allclose_units, requires_file, requires_module\nfrom yt.units import Myr\nfrom yt.utilities.answer_testing.framework import requires_ds, sph_answer\n\n# This maps from field names to weight field names to use for projections\nfields = OrderedDict(\n    [\n        ((\"gas\", \"density\"), None),\n        ((\"gas\", \"temperature\"), (\"gas\", \"density\")),\n        ((\"gas\", \"metallicity\"), (\"gas\", \"density\")),\n        ((\"gas\", \"O_metallicity\"), (\"gas\", \"density\")),\n        ((\"gas\", \"velocity_magnitude\"), None),\n    ]\n)\n\ng64 = \"gizmo_64/output/snap_N64L16_135.hdf5\"\ngmhd = \"gizmo_mhd_mwdisk/gizmo_mhd_mwdisk.hdf5\"\ngmhd_bbox = [[-400, 400]] * 3\nzeld_wg = \"gizmo_zeldovich/snapshot_076_wi_gizver.hdf5\"\nzeld_ng = \"gizmo_zeldovich/snapshot_076_no_gizver.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(g64, big_data=True)\ndef test_gizmo_64():\n    ds = yt.load(g64)\n    assert isinstance(ds, GizmoDataset)\n    for test in sph_answer(ds, \"snap_N64L16_135\", 524288, fields):\n        test_gizmo_64.__name__ = test.description\n        yield test\n\n\n@requires_module(\"h5py\")\n@requires_file(zeld_wg)\n@requires_file(zeld_ng)\ndef test_gizmo_zeldovich():\n    \"\"\"\n    Test loading a recent gizmo snapshot that doesn't have cooling/metallicity\n\n    The gizmo_zeldovich file has no metallicity field on the gas particles\n    but is a cosmological dataset run using GIZMO_version=2022. There are two\n    versions of the file, with GIZMO_version (_wg) and without GIZMO_version\n    (_ng). Check that both load as gizmo datasets and correctly pull the\n    cosmological variables. This test should get simpler when the file switches\n    to pytest.\n    \"\"\"\n    for fn in [zeld_wg, zeld_ng]:\n        ds = yt.load(fn)\n        assert isinstance(ds, GizmoDataset)\n\n        assert ds.cosmological_simulation\n        assert ds.omega_matter == 1.0\n        assert ds.omega_lambda == 0.0\n        # current_time is calculated from the cosmology so this checks if that\n        # was calculated correctly\n        assert_allclose_units(ds.current_time, 1672.0678 * Myr)\n\n\n@requires_module(\"h5py\")\n@requires_file(gmhd)\ndef test_gizmo_mhd():\n    \"\"\"\n    Magnetic fields should be loaded correctly when they are present.\n    \"\"\"\n    ds = yt.load(gmhd, bounding_box=gmhd_bbox, unit_system=\"code\")\n    ad = ds.all_data()\n    ptype = \"PartType0\"\n\n    # Test vector magnetic field\n    fmag = \"particle_magnetic_field\"\n    f = ad[ptype, fmag]\n    assert str(f.units) == \"code_magnetic\"\n    assert f.shape == (409013, 3)\n\n    # Test component magnetic fields\n    for axis in \"xyz\":\n        f = ad[ptype, fmag + \"_\" + axis]\n        assert str(f.units) == \"code_magnetic\"\n        assert f.shape == (409013,)\n\n\n@requires_module(\"h5py\")\n@requires_file(gmhd)\ndef test_gas_particle_fields():\n    \"\"\"\n    Test fields set up in GizmoFieldInfo.setup_gas_particle_fields.\n    \"\"\"\n    ds = yt.load(gmhd, bounding_box=gmhd_bbox)\n\n    ptype = \"PartType0\"\n    derived_fields = []\n    # Add species fields\n    for species in [\"H_p0\", \"H_p1\"]:\n        for suffix in [\"density\", \"fraction\", \"mass\", \"number_density\"]:\n            derived_fields += [f\"{species}_{suffix}\"]\n    for species in metal_elements:\n        derived_fields += [f\"{species}_nuclei_mass_density\"]\n    # Add magnetic fields\n    derived_fields += [f\"particle_magnetic_field_{axis}\" for axis in \"xyz\"]\n    # Check\n    for field in derived_fields:\n        assert (ptype, field) in ds.derived_field_list\n\n    ptype = \"gas\"\n    derived_fields = []\n    for species in [\"H_p0\", \"H_p1\"]:\n        for suffix in [\"density\", \"number_density\"]:\n            derived_fields += [f\"{species}_{suffix}\"]\n    for species in metal_elements:\n        for suffix in [\"nuclei_mass_density\", \"metallicity\"]:\n            derived_fields += [f\"{species}_{suffix}\"]\n    derived_fields += [f\"magnetic_field_{axis}\" for axis in \"xyz\"]\n    for field in derived_fields:\n        assert (ptype, field) in ds.derived_field_list\n\n\n@requires_module(\"h5py\")\n@requires_file(gmhd)\ndef test_star_particle_fields():\n    \"\"\"\n    Test fields set up in GizmoFieldInfo.setup_star_particle_fields.\n    \"\"\"\n    ds = yt.load(gmhd, bounding_box=gmhd_bbox)\n\n    ptype = \"PartType4\"\n    derived_fields = [\"creation_time\", \"age\"]\n    for field in derived_fields:\n        assert (ptype, field) in ds.derived_field_list\n"
  },
  {
    "path": "yt/frontends/halo_catalog/__init__.py",
    "content": "\"\"\"\nAPI for HaloCatalog frontend.\n\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/halo_catalog/api.py",
    "content": "from .data_structures import YTHaloCatalogDataset\nfrom .fields import YTHaloCatalogFieldInfo\nfrom .io import IOHandlerYTHaloCatalog\n"
  },
  {
    "path": "yt/frontends/halo_catalog/data_structures.py",
    "content": "import glob\nimport weakref\nfrom collections import defaultdict\nfrom functools import cached_property, partial\n\nimport numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer,\n)\nfrom yt.data_objects.static_output import (\n    ParticleDataset,\n    ParticleFile,\n)\nfrom yt.frontends.ytdata.data_structures import SavedDataset\nfrom yt.funcs import parse_h5_attr\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import YTHaloCatalogFieldInfo, YTHaloCatalogHaloFieldInfo\n\n\nclass HaloCatalogFile(ParticleFile):\n    \"\"\"\n    Base class for data files of halo catalog datasets.\n\n    This is mainly here to correct for periodicity when\n    reading particle positions.\n    \"\"\"\n\n    def __init__(self, ds, io, filename, file_id, frange):\n        super().__init__(ds, io, filename, file_id, frange)\n\n    def _read_particle_positions(self, ptype, f=None):\n        raise NotImplementedError\n\n    def _get_particle_positions(self, ptype, f=None):\n        pcount = self.total_particles[ptype]\n        if pcount == 0:\n            return None\n\n        # Correct for periodicity.\n        dle = self.ds.domain_left_edge.to(\"code_length\").v\n        dw = self.ds.domain_width.to(\"code_length\").v\n        pos = self._read_particle_positions(ptype, f=f)\n        si, ei = self.start, self.end\n        if None not in (si, ei):\n            pos = pos[si:ei]\n\n        np.subtract(pos, dle, out=pos)\n        np.mod(pos, dw, out=pos)\n        np.add(pos, dle, out=pos)\n\n        return pos\n\n\nclass YTHaloCatalogFile(HaloCatalogFile):\n    \"\"\"\n    Data file class for the YTHaloCatalogDataset.\n    \"\"\"\n\n    def __init__(self, ds, io, filename, file_id, frange):\n        with h5py.File(filename, mode=\"r\") as f:\n            self.header = {field: parse_h5_attr(f, field) for field in f.attrs.keys()}\n            pids = f.get(\"particles/ids\")\n            self.total_ids = 0 if pids is None else pids.size\n            self.group_length_sum = self.total_ids\n        super().__init__(ds, io, filename, file_id, frange)\n\n    def _read_particle_positions(self, ptype, f=None):\n        \"\"\"\n        Read all particle positions in this file.\n        \"\"\"\n\n        if f is None:\n            close = True\n            f = h5py.File(self.filename, mode=\"r\")\n        else:\n            close = False\n\n        pcount = self.header[\"num_halos\"]\n        pos = np.empty((pcount, 3), dtype=\"float64\")\n        for i, ax in enumerate(\"xyz\"):\n            pos[:, i] = f[f\"particle_position_{ax}\"][()]\n\n        if close:\n            f.close()\n\n        return pos\n\n\nclass YTHaloCatalogDataset(SavedDataset):\n    \"\"\"\n    Dataset class for halo catalogs made with yt.\n\n    This covers yt FoF/HoP halo finders and the halo analysis\n    in yt_astro_analysis.\n    \"\"\"\n\n    _load_requirements = [\"h5py\"]\n    _index_class = ParticleIndex\n    _file_class = YTHaloCatalogFile\n    _field_info_class = YTHaloCatalogFieldInfo\n    _suffix = \".h5\"\n    _con_attrs = (\n        \"cosmological_simulation\",\n        \"current_time\",\n        \"current_redshift\",\n        \"hubble_constant\",\n        \"omega_matter\",\n        \"omega_lambda\",\n        \"domain_left_edge\",\n        \"domain_right_edge\",\n    )\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"ythalocatalog\",\n        index_order=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        self.index_order = index_order\n        super().__init__(\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n\n    def add_field(self, *args, **kwargs):\n        super().add_field(*args, **kwargs)\n        self._halos_ds.add_field(*args, **kwargs)\n\n    @property\n    def halos_field_list(self):\n        return self._halos_ds.field_list\n\n    @property\n    def halos_derived_field_list(self):\n        return self._halos_ds.derived_field_list\n\n    @cached_property\n    def _halos_ds(self):\n        return YTHaloDataset(self)\n\n    def _setup_classes(self):\n        super()._setup_classes()\n        self.halo = partial(YTHaloCatalogHaloContainer, ds=self._halos_ds)\n        self.halo.__doc__ = YTHaloCatalogHaloContainer.__doc__\n\n    def _parse_parameter_file(self):\n        self.refine_by = 2\n        self.dimensionality = 3\n        self.domain_dimensions = np.ones(self.dimensionality, \"int32\")\n        self._periodicity = (True, True, True)\n        prefix = \".\".join(self.parameter_filename.rsplit(\".\", 2)[:-2])\n        self.filename_template = f\"{prefix}.%(num)s{self._suffix}\"\n        self.file_count = len(glob.glob(prefix + \"*\" + self._suffix))\n        self.particle_types = (\"halos\",)\n        self.particle_types_raw = (\"halos\",)\n        super()._parse_parameter_file()\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".h5\"):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        with h5py.File(filename, mode=\"r\") as f:\n            if (\n                \"data_type\" in f.attrs\n                and parse_h5_attr(f, \"data_type\") == \"halo_catalog\"\n            ):\n                return True\n        return False\n\n\nclass YTHaloParticleIndex(ParticleIndex):\n    \"\"\"\n    Particle index for getting halo particles from YTHaloCatalogDatasets.\n    \"\"\"\n\n    def __init__(self, ds, dataset_type):\n        self.real_ds = weakref.proxy(ds.real_ds)\n        super().__init__(ds, dataset_type)\n\n    def _calculate_particle_index_starts(self):\n        \"\"\"\n        Create a dict of halo id offsets for each file.\n        \"\"\"\n        particle_count = defaultdict(int)\n        offset_count = 0\n        for data_file in self.data_files:\n            data_file.index_start = {\n                ptype: particle_count[ptype] for ptype in data_file.total_particles\n            }\n            data_file.offset_start = offset_count\n            for ptype in data_file.total_particles:\n                particle_count[ptype] += data_file.total_particles[ptype]\n            offset_count += getattr(data_file, \"total_offset\", 0)\n\n        self._halo_index_start = {}\n        for ptype in self.ds.particle_types_raw:\n            d = [data_file.index_start[ptype] for data_file in self.data_files]\n            self._halo_index_start.update({ptype: np.array(d)})\n\n    def _detect_output_fields(self):\n        field_list = []\n        scalar_field_list = []\n        units = {}\n\n        pc = {}\n        for ptype in self.ds.particle_types_raw:\n            d = [df.total_particles[ptype] for df in self.data_files]\n            pc.update({ptype: sum(d)})\n        found_fields = {ptype: False for ptype, pnum in pc.items() if pnum > 0}\n        has_ids = False\n\n        for data_file in self.data_files:\n            fl, sl, idl, _units = self.io._identify_fields(data_file)\n            units.update(_units)\n            field_list.extend([f for f in fl if f not in field_list])\n            scalar_field_list.extend([f for f in sl if f not in scalar_field_list])\n            for ptype in found_fields:\n                found_fields[ptype] |= data_file.total_particles[ptype]\n            has_ids |= len(idl) > 0\n            if all(found_fields.values()) and has_ids:\n                break\n\n        self.field_list = field_list\n        self.scalar_field_list = scalar_field_list\n        ds = self.dataset\n        ds.scalar_field_list = scalar_field_list\n        ds.particle_types = tuple({pt for pt, ds in field_list})\n        ds.field_units.update(units)\n        ds.particle_types_raw = ds.particle_types\n\n    def _get_halo_file_indices(self, ptype, identifiers):\n        \"\"\"\n        Get the index of the data file list where this halo lives.\n\n        Digitize returns i such that bins[i-1] <= x < bins[i], so we subtract\n        one because we will open data file i.\n        \"\"\"\n        return np.digitize(identifiers, self._halo_index_start[ptype], right=False) - 1\n\n    def _get_halo_scalar_index(self, ptype, identifier):\n        i_scalar = self._get_halo_file_indices(ptype, [identifier])[0]\n        scalar_index = identifier - self._halo_index_start[ptype][i_scalar]\n        return scalar_index\n\n    def _get_halo_values(self, ptype, identifiers, fields, f=None):\n        \"\"\"\n        Get field values for halo data containers.\n        \"\"\"\n\n        # if a file is already open, don't open it again\n        filename = None if f is None else f.filename\n\n        data = defaultdict(lambda: np.empty(identifiers.size))\n        i_scalars = self._get_halo_file_indices(ptype, identifiers)\n        for i_scalar in np.unique(i_scalars):\n            # mask array to get field data for this halo\n            target = i_scalars == i_scalar\n            scalar_indices = identifiers - self._halo_index_start[ptype][i_scalar]\n\n            # only open file if it's not already open\n            my_f = (\n                f\n                if self.data_files[i_scalar].filename == filename\n                else h5py.File(self.data_files[i_scalar].filename, mode=\"r\")\n            )\n\n            for field in fields:\n                data[field][target] = self._read_halo_particle_field(\n                    my_f, ptype, field, scalar_indices[target]\n                )\n\n            if self.data_files[i_scalar].filename != filename:\n                my_f.close()\n\n        return data\n\n    def _identify_base_chunk(self, dobj):\n        pass\n\n    def _read_halo_particle_field(self, fh, ptype, field, indices):\n        return fh[field][indices]\n\n    def _read_particle_fields(self, fields, dobj, chunk=None):\n        if not fields:\n            return {}, []\n        fields_to_read, fields_to_generate = self._split_fields(fields)\n        if not fields_to_read:\n            return {}, fields_to_generate\n        fields_to_return = self.io._read_particle_selection(dobj, fields_to_read)\n        return fields_to_return, fields_to_generate\n\n    def _setup_data_io(self):\n        super()._setup_data_io()\n        if self.real_ds._instantiated_index is None:\n            self.real_ds.index\n        self.real_ds.index\n\n        # inherit some things from parent index\n        self._data_files = self.real_ds.index.data_files\n        self._total_particles = self.real_ds.index.total_particles\n\n        self._calculate_particle_index_starts()\n\n\nclass HaloDataset(ParticleDataset):\n    \"\"\"\n    Base class for dataset accessing particles from halo catalogs.\n    \"\"\"\n\n    def __init__(self, ds, dataset_type):\n        self.real_ds = ds\n        for attr in [\n            \"filename_template\",\n            \"file_count\",\n            \"particle_types_raw\",\n            \"particle_types\",\n            \"_periodicity\",\n        ]:\n            setattr(self, attr, getattr(self.real_ds, attr))\n\n        super().__init__(self.real_ds.parameter_filename, dataset_type)\n\n    def print_key_parameters(self):\n        pass\n\n    def _set_derived_attrs(self):\n        pass\n\n    def _parse_parameter_file(self):\n        for attr in [\n            \"cosmological_simulation\",\n            \"cosmology\",\n            \"current_redshift\",\n            \"current_time\",\n            \"dimensionality\",\n            \"domain_dimensions\",\n            \"domain_left_edge\",\n            \"domain_right_edge\",\n            \"domain_width\",\n            \"hubble_constant\",\n            \"omega_lambda\",\n            \"omega_matter\",\n            \"unique_identifier\",\n        ]:\n            setattr(self, attr, getattr(self.real_ds, attr, None))\n\n    def set_code_units(self):\n        self._set_code_unit_attributes()\n        self.unit_registry = self.real_ds.unit_registry\n\n    def _set_code_unit_attributes(self):\n        for unit in [\"length\", \"time\", \"mass\", \"velocity\", \"magnetic\", \"temperature\"]:\n            my_unit = f\"{unit}_unit\"\n            setattr(self, my_unit, getattr(self.real_ds, my_unit, None))\n\n    def __str__(self):\n        return f\"{self.real_ds}\"\n\n    def _setup_classes(self):\n        self.objects = []\n\n\nclass YTHaloDataset(HaloDataset):\n    \"\"\"\n    Dataset used for accessing member particles from YTHaloCatalogDatasets.\n    \"\"\"\n\n    _index_class = YTHaloParticleIndex\n    _file_class = YTHaloCatalogFile\n    _field_info_class = YTHaloCatalogHaloFieldInfo\n\n    def __init__(self, ds, dataset_type=\"ythalo\"):\n        super().__init__(ds, dataset_type)\n\n    def _set_code_unit_attributes(self):\n        pass\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        # We don't ever want this to be loaded by yt.load.\n        return False\n\n\nclass HaloContainer(YTSelectionContainer):\n    \"\"\"\n    Base class for data containers providing halo particles.\n    \"\"\"\n\n    _type_name = \"halo\"\n    _con_args = (\"ptype\", \"particle_identifier\")\n    _skip_add = True\n    _spatial = False\n\n    def __init__(self, ptype, particle_identifier, ds=None):\n        if ptype not in ds.particle_types_raw:\n            raise RuntimeError(\n                f'Possible halo types are {ds.particle_types_raw}, supplied \"{ptype}\".'\n            )\n\n        self.ptype = ptype\n        self._current_particle_type = ptype\n        super().__init__(ds, {})\n\n        self._set_identifiers(particle_identifier)\n\n        # Find the file that has the scalar values for this halo.\n        i_scalar = self.index._get_halo_file_indices(ptype, [self.particle_identifier])[\n            0\n        ]\n        self.i_scalar = i_scalar\n        self.scalar_data_file = self.index.data_files[i_scalar]\n\n        # Data files containing particles belonging to this halo.\n        self.field_data_files = [self.index.data_files[i_scalar]]\n\n        # index within halo arrays that corresponds to this halo\n        self.scalar_index = self.index._get_halo_scalar_index(\n            ptype, self.particle_identifier\n        )\n\n        self._set_io_data()\n        self.particle_number = self._get_particle_number()\n\n        # starting and ending indices for each file containing particles\n        self._set_field_indices()\n\n    @cached_property\n    def mass(self):\n        return self[self.ptype, \"particle_mass\"][0]\n\n    @cached_property\n    def radius(self):\n        return self[self.ptype, \"virial_radius\"][0]\n\n    @cached_property\n    def position(self):\n        return self[self.ptype, \"particle_position\"][0]\n\n    @cached_property\n    def velocity(self):\n        return self[self.ptype, \"particle_velocity\"][0]\n\n    def _set_io_data(self):\n        halo_fields = self._get_member_fieldnames()\n        my_data = self.index._get_halo_values(\n            self.ptype, np.array([self.particle_identifier]), halo_fields\n        )\n        self._io_data = {field: np.int64(val[0]) for field, val in my_data.items()}\n\n    def __repr__(self):\n        return f\"{self.ds}_{self.ptype}_{self.particle_identifier:09d}\"\n\n\nclass YTHaloCatalogHaloContainer(HaloContainer):\n    \"\"\"\n    Data container for accessing particles from a halo.\n\n    Create a data container to get member particles and individual\n    values from halos and subhalos. Halo mass, radius, position, and\n    velocity are set as attributes. Halo IDs are accessible\n    through the field, \"member_ids\".  Other fields that are one\n    value per halo are accessible as normal.  The field list for\n    halo objects can be seen in `ds.halos_field_list`.\n\n    Parameters\n    ----------\n    ptype : string\n        The type of halo. Possible options can be found by\n        inspecting the value of ds.particle_types_raw.\n    particle_identifier : int\n        The halo id.\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"tiny_fof_halos/DD0046/DD0046.0.h5\")\n\n    >>> halo = ds.halo(\"halos\", 0)\n    >>> print(halo.particle_identifier)\n    0\n    >>> print(halo.mass)\n    8724990744704.453 Msun\n    >>> print(halo.radius)\n    658.8140635766607 kpc\n    >>> print(halo.position)\n    [0.05496909 0.19451951 0.04056824] code_length\n    >>> print(halo.velocity)\n    [7034181.07118151 5323471.09102874 3234522.50495914] cm/s\n    >>> # particle ids for this halo\n    >>> print(halo[\"member_ids\"])\n    [ 1248.   129.   128. 31999. 31969. 31933. 31934.   159. 31903. 31841. ...\n      2241.  2240.  2239.  2177.  2209.  2207.  2208.] dimensionless\n\n    \"\"\"\n\n    def _get_member_fieldnames(self):\n        return [\"particle_number\", \"particle_index_start\"]\n\n    def _get_particle_number(self):\n        return self._io_data[\"particle_number\"]\n\n    def _set_field_indices(self):\n        self.field_data_start = [self._io_data[\"particle_index_start\"]]\n        self.field_data_end = [self.field_data_start[0] + self.particle_number]\n\n    def _set_identifiers(self, particle_identifier):\n        self.particle_identifier = particle_identifier\n        self.group_identifier = self.particle_identifier\n"
  },
  {
    "path": "yt/frontends/halo_catalog/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\nm_units = \"g\"\np_units = \"cm\"\nv_units = \"cm / s\"\nr_units = \"cm\"\n\n_particle_fields: KnownFieldsT = (\n    (\"particle_identifier\", (\"\", [], None)),\n    (\"particle_position_x\", (p_units, [], None)),\n    (\"particle_position_y\", (p_units, [], None)),\n    (\"particle_position_z\", (p_units, [], None)),\n    (\"particle_velocity_x\", (v_units, [], None)),\n    (\"particle_velocity_y\", (v_units, [], None)),\n    (\"particle_velocity_z\", (v_units, [], None)),\n    (\"particle_mass\", (m_units, [], \"Virial Mass\")),\n    (\"virial_radius\", (r_units, [], \"Virial Radius\")),\n)\n\n\nclass YTHaloCatalogFieldInfo(FieldInfoContainer):\n    known_other_fields = ()\n\n    known_particle_fields = _particle_fields\n\n\nclass YTHaloCatalogHaloFieldInfo(FieldInfoContainer):\n    known_other_fields = ()\n\n    known_particle_fields = _particle_fields + ((\"ids\", (\"\", [\"member_ids\"], None)),)\n"
  },
  {
    "path": "yt/frontends/halo_catalog/io.py",
    "content": "from collections import defaultdict\n\nimport numpy as np\n\nfrom yt.frontends.gadget_fof.io import IOHandlerGadgetFOFHaloHDF5\nfrom yt.funcs import parse_h5_attr\nfrom yt.units._numpy_wrapper_functions import uvstack\nfrom yt.utilities.io_handler import BaseParticleIOHandler\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\nclass IOHandlerYTHaloCatalog(BaseParticleIOHandler):\n    _dataset_type = \"ythalocatalog\"\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This will read chunks and yield the results.\n        # Only support halo reading for now.\n        assert len(ptf) == 1\n        assert list(ptf.keys())[0] == \"halos\"\n        ptype = \"halos\"\n        pn = \"particle_position_%s\"\n        for data_file in self._sorted_chunk_iterator(chunks):\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                units = parse_h5_attr(f[pn % \"x\"], \"units\")\n                pos = data_file._get_particle_positions(ptype, f=f)\n                x, y, z = (self.ds.arr(pos[:, i], units) for i in range(3))\n                yield \"halos\", (x, y, z), 0.0\n\n    def _yield_coordinates(self, data_file):\n        pn = \"particle_position_%s\"\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            units = parse_h5_attr(f[pn % \"x\"], \"units\")\n            x, y, z = (\n                self.ds.arr(f[pn % ax][()].astype(\"float64\"), units) for ax in \"xyz\"\n            )\n            pos = uvstack([x, y, z]).T\n            pos.convert_to_units(\"code_length\")\n            yield \"halos\", pos\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # Only support halo reading for now.\n        assert len(ptf) == 1\n        assert list(ptf.keys())[0] == \"halos\"\n        pn = \"particle_position_%s\"\n        for data_file in self._sorted_chunk_iterator(chunks):\n            si, ei = data_file.start, data_file.end\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype, field_list in sorted(ptf.items()):\n                    units = parse_h5_attr(f[pn % \"x\"], \"units\")\n                    pos = data_file._get_particle_positions(ptype, f=f)\n                    x, y, z = (self.ds.arr(pos[:, i], units) for i in range(3))\n                    mask = selector.select_points(x, y, z, 0.0)\n                    del x, y, z\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        data = f[field][si:ei][mask].astype(\"float64\")\n                        yield (ptype, field), data\n\n    def _count_particles(self, data_file):\n        si, ei = data_file.start, data_file.end\n        nhalos = data_file.header[\"num_halos\"]\n        if None not in (si, ei):\n            nhalos = np.clip(nhalos - si, 0, ei - si)\n        return {\"halos\": nhalos}\n\n    def _identify_fields(self, data_file):\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            fields = [\n                (\"halos\", field) for field in f if not isinstance(f[field], h5py.Group)\n            ]\n            units = {(\"halos\", field): parse_h5_attr(f[field], \"units\") for field in f}\n        return fields, units\n\n\nclass HaloDatasetIOHandler:\n    \"\"\"\n    Base class for io handlers to load halo member particles.\n    \"\"\"\n\n    def _read_particle_coords(self, chunks, ptf):\n        pass\n\n    def _read_particle_fields(self, dobj, ptf):\n        # separate member particle fields from scalar fields\n        scalar_fields = defaultdict(list)\n        member_fields = defaultdict(list)\n        for ptype, field_list in sorted(ptf.items()):\n            for field in field_list:\n                if (ptype, field) in self.ds.scalar_field_list:\n                    scalar_fields[ptype].append(field)\n                else:\n                    member_fields[ptype].append(field)\n\n        all_data = self._read_scalar_fields(dobj, scalar_fields)\n        all_data.update(self._read_member_fields(dobj, member_fields))\n\n        for field, field_data in all_data.items():\n            yield field, field_data\n\n    # This will be refactored.\n    _read_particle_selection = IOHandlerGadgetFOFHaloHDF5._read_particle_selection\n\n\n# ignoring type in this mixing to circumvent this error from mypy\n# Definition of \"_read_particle_fields\" in base class \"HaloDatasetIOHandler\"\n# is incompatible with definition in base class \"IOHandlerYTHaloCatalog\"\n#\n# it may not be possible to refactor out of this situation without breaking downstream\nclass IOHandlerYTHalo(HaloDatasetIOHandler, IOHandlerYTHaloCatalog):  # type: ignore\n    _dataset_type = \"ythalo\"\n\n    def _identify_fields(self, data_file):\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            scalar_fields = [\n                (\"halos\", field) for field in f if not isinstance(f[field], h5py.Group)\n            ]\n            units = {(\"halos\", field): parse_h5_attr(f[field], \"units\") for field in f}\n            if \"particles\" in f:\n                id_fields = [(\"halos\", field) for field in f[\"particles\"]]\n            else:\n                id_fields = []\n\n        return scalar_fields + id_fields, scalar_fields, id_fields, units\n\n    def _read_member_fields(self, dobj, member_fields):\n        all_data = defaultdict(lambda: np.empty(dobj.particle_number, dtype=np.float64))\n        if not member_fields:\n            return all_data\n        field_start = 0\n        for i, data_file in enumerate(dobj.field_data_files):\n            start_index = dobj.field_data_start[i]\n            end_index = dobj.field_data_end[i]\n            pcount = end_index - start_index\n            if pcount == 0:\n                continue\n            field_end = field_start + end_index - start_index\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype, field_list in sorted(member_fields.items()):\n                    for field in field_list:\n                        field_data = all_data[ptype, field]\n                        my_data = f[\"particles\"][field][start_index:end_index].astype(\n                            \"float64\"\n                        )\n                        field_data[field_start:field_end] = my_data\n            field_start = field_end\n        return all_data\n\n    def _read_scalar_fields(self, dobj, scalar_fields):\n        all_data = {}\n        if not scalar_fields:\n            return all_data\n        with h5py.File(dobj.scalar_data_file.filename, mode=\"r\") as f:\n            for ptype, field_list in sorted(scalar_fields.items()):\n                for field in field_list:\n                    data = np.array([f[field][dobj.scalar_index]]).astype(\"float64\")\n                    all_data[ptype, field] = data\n        return all_data\n"
  },
  {
    "path": "yt/frontends/halo_catalog/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/halo_catalog/tests/test_outputs.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_equal, assert_equal\n\nfrom yt.frontends.halo_catalog.data_structures import YTHaloCatalogDataset\nfrom yt.frontends.ytdata.utilities import save_as_dataset\nfrom yt.loaders import load as yt_load\nfrom yt.testing import TempDirTest, requires_file, requires_module\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.answer_testing.framework import data_dir_load\n\n\ndef fake_halo_catalog(data):\n    filename = \"catalog.0.h5\"\n\n    ftypes = dict.fromkeys(data, \".\")\n    extra_attrs = {\"data_type\": \"halo_catalog\", \"num_halos\": data[\"particle_mass\"].size}\n\n    ds = {\n        \"cosmological_simulation\": 1,\n        \"omega_lambda\": 0.7,\n        \"omega_matter\": 0.3,\n        \"hubble_constant\": 0.7,\n        \"current_redshift\": 0,\n        \"current_time\": YTQuantity(1, \"yr\"),\n        \"domain_left_edge\": YTArray(np.zeros(3), \"cm\"),\n        \"domain_right_edge\": YTArray(np.ones(3), \"cm\"),\n    }\n    save_as_dataset(ds, filename, data, field_types=ftypes, extra_attrs=extra_attrs)\n    return filename\n\n\nclass HaloCatalogTest(TempDirTest):\n    @requires_module(\"h5py\")\n    def test_halo_catalog(self):\n        rs = np.random.RandomState(3670474)\n        n_halos = 100\n        fields = [\"particle_mass\"] + [f\"particle_position_{ax}\" for ax in \"xyz\"]\n        units = [\"g\"] + [\"cm\"] * 3\n        data = {\n            field: YTArray(rs.random_sample(n_halos), unit)\n            for field, unit in zip(fields, units, strict=True)\n        }\n\n        fn = fake_halo_catalog(data)\n        ds = yt_load(fn)\n\n        assert type(ds) is YTHaloCatalogDataset\n\n        for field in fields:\n            f1 = data[field].in_base()\n            f1.sort()\n            f2 = ds.r[\"all\", field].in_base()\n            f2.sort()\n            assert_array_equal(f1, f2)\n\n    @requires_module(\"h5py\")\n    def test_halo_catalog_boundary_particles(self):\n        rs = np.random.RandomState(3670474)\n        n_halos = 100\n        fields = [\"particle_mass\"] + [f\"particle_position_{ax}\" for ax in \"xyz\"]\n        units = [\"g\"] + [\"cm\"] * 3\n        data = {\n            field: YTArray(rs.random_sample(n_halos), unit)\n            for field, unit in zip(fields, units, strict=True)\n        }\n\n        data[\"particle_position_x\"][0] = 1.0\n        data[\"particle_position_x\"][1] = 0.0\n        data[\"particle_position_y\"][2] = 1.0\n        data[\"particle_position_y\"][3] = 0.0\n        data[\"particle_position_z\"][4] = 1.0\n        data[\"particle_position_z\"][5] = 0.0\n\n        fn = fake_halo_catalog(data)\n        ds = yt_load(fn)\n\n        assert type(ds) is YTHaloCatalogDataset\n\n        for field in fields:\n            f1 = data[field].in_base()\n            f1.sort()\n            f2 = ds.r[\"all\", field].in_base()\n            f2.sort()\n            assert_array_equal(f1, f2)\n\n\nt46 = \"tiny_fof_halos/DD0046/DD0046.0.h5\"\n\n\n@requires_file(t46)\n@requires_module(\"h5py\")\ndef test_halo_quantities():\n    ds = data_dir_load(t46)\n    ad = ds.all_data()\n    for i in range(ds.index.total_particles):\n        hid = int(ad[\"halos\", \"particle_identifier\"][i])\n        halo = ds.halo(\"halos\", hid)\n        for field in [\"mass\", \"position\", \"velocity\"]:\n            v1 = ad[\"halos\", f\"particle_{field}\"][i]\n            v2 = getattr(halo, field)\n            assert_equal(v1, v2, err_msg=f\"Halo {hid} {field} field mismatch.\")\n\n\n@requires_file(t46)\n@requires_module(\"h5py\")\ndef test_halo_particles():\n    ds = data_dir_load(t46)\n    i = ds.r[\"halos\", \"particle_mass\"].argmax()\n    hid = int(ds.r[\"halos\", \"particle_identifier\"][i])\n    halo = ds.halo(\"halos\", hid)\n    ids = halo[\"halos\", \"member_ids\"]\n    assert_equal(ids.size, 420)\n    assert_equal(ids.min(), 19478.0)\n    assert_equal(ids.max(), 31669.0)\n"
  },
  {
    "path": "yt/frontends/http_stream/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/http_stream/api.py",
    "content": "from .data_structures import HTTPStreamDataset\nfrom .io import IOHandlerHTTPStream\n"
  },
  {
    "path": "yt/frontends/http_stream/data_structures.py",
    "content": "import json\nimport time\nfrom functools import cached_property\n\nimport numpy as np\n\nfrom yt.data_objects.static_output import ParticleDataset, ParticleFile\nfrom yt.frontends.sph.fields import SPHFieldInfo\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.utilities.on_demand_imports import _requests as requests\n\n\nclass HTTPParticleFile(ParticleFile):\n    pass\n\n\nclass HTTPStreamDataset(ParticleDataset):\n    _load_requirements = [\"requests\"]\n    _index_class = ParticleIndex\n    _file_class = HTTPParticleFile\n    _field_info_class = SPHFieldInfo\n    _particle_mass_name = \"Mass\"\n    _particle_coordinates_name = \"Coordinates\"\n    _particle_velocity_name = \"Velocities\"\n    filename_template = \"\"\n\n    def __init__(\n        self,\n        base_url,\n        dataset_type=\"http_particle_stream\",\n        unit_system=\"cgs\",\n        index_order=None,\n        index_filename=None,\n    ):\n        self.base_url = base_url\n        super().__init__(\n            \"\",\n            dataset_type=dataset_type,\n            unit_system=unit_system,\n            index_order=index_order,\n            index_filename=index_filename,\n        )\n\n    def __str__(self):\n        return self.base_url\n\n    @cached_property\n    def unique_identifier(self) -> str:\n        return str(self.parameters.get(\"unique_identifier\", time.time()))\n\n    def _parse_parameter_file(self):\n        self.dimensionality = 3\n        self.refine_by = 2\n        self.parameters[\"HydroMethod\"] = \"sph\"\n\n        # Here's where we're going to grab the JSON index file\n        hreq = requests.get(self.base_url + \"/yt_index.json\")\n        if hreq.status_code != 200:\n            raise RuntimeError\n        header = json.loads(hreq.content)\n        header[\"particle_count\"] = {\n            int(k): header[\"particle_count\"][k] for k in header[\"particle_count\"]\n        }\n        self.parameters = header\n\n        # Now we get what we need\n        self.domain_left_edge = np.array(header[\"domain_left_edge\"], \"float64\")\n        self.domain_right_edge = np.array(header[\"domain_right_edge\"], \"float64\")\n        self.domain_dimensions = np.ones(3, \"int32\")\n        self._periodicity = (True, True, True)\n\n        self.current_time = header[\"current_time\"]\n        self.cosmological_simulation = int(header[\"cosmological_simulation\"])\n        for attr in (\n            \"current_redshift\",\n            \"omega_lambda\",\n            \"omega_matter\",\n            \"hubble_constant\",\n        ):\n            setattr(self, attr, float(header[attr]))\n\n        self.file_count = header[\"num_files\"]\n\n    def _set_units(self):\n        length_unit = float(self.parameters[\"units\"][\"length\"])\n        time_unit = float(self.parameters[\"units\"][\"time\"])\n        mass_unit = float(self.parameters[\"units\"][\"mass\"])\n        density_unit = mass_unit / length_unit**3\n        velocity_unit = length_unit / time_unit\n        self._unit_base = {}\n        self._unit_base[\"cm\"] = 1.0 / length_unit\n        self._unit_base[\"s\"] = 1.0 / time_unit\n        super()._set_units()\n        self.conversion_factors[\"velocity\"] = velocity_unit\n        self.conversion_factors[\"mass\"] = mass_unit\n        self.conversion_factors[\"density\"] = density_unit\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.startswith(\"http://\"):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        return requests.get(filename + \"/yt_index.json\").status_code == 200\n"
  },
  {
    "path": "yt/frontends/http_stream/io.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog\nfrom yt.utilities.io_handler import BaseParticleIOHandler\nfrom yt.utilities.on_demand_imports import _requests as requests\n\n\nclass IOHandlerHTTPStream(BaseParticleIOHandler):\n    _dataset_type = \"http_particle_stream\"\n    _vector_fields = {\"Coordinates\": 3, \"Velocity\": 3, \"Velocities\": 3}\n\n    def __init__(self, ds):\n        self._url = ds.base_url\n        # This should eventually manage the IO and cache it\n        self.total_bytes = 0\n        super().__init__(ds)\n\n    def _open_stream(self, data_file, field):\n        # This does not actually stream yet!\n        ftype, fname = field\n        s = f\"{self._url}/{data_file.file_id}/{ftype}/{fname}\"\n        mylog.info(\"Loading URL %s\", s)\n        resp = requests.get(s)\n        if resp.status_code != 200:\n            raise RuntimeError\n        self.total_bytes += len(resp.content)\n        return resp.content\n\n    def _identify_fields(self, data_file):\n        f = []\n        for ftype, fname in self.ds.parameters[\"field_list\"]:\n            f.append((str(ftype), str(fname)))\n        return f, {}\n\n    def _read_particle_coords(self, chunks, ptf):\n        for data_file in self._sorted_chunk_iterator(chunks):\n            for ptype in ptf:\n                s = self._open_stream(data_file, (ptype, \"Coordinates\"))\n                c = np.frombuffer(s, dtype=\"float64\")\n                c = c.reshape(c.size // 3, 3)\n                yield ptype, (c[:, 0], c[:, 1], c[:, 2]), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # Now we have all the sizes, and we can allocate\n        for data_file in self._sorted_chunk_iterator(chunks):\n            for ptype, field_list in sorted(ptf.items()):\n                s = self._open_stream(data_file, (ptype, \"Coordinates\"))\n                c = np.frombuffer(s, dtype=\"float64\")\n                c = c.reshape(c.size // 3, 3)\n                mask = selector.select_points(c[:, 0], c[:, 1], c[:, 2], 0.0)\n                del c\n                if mask is None:\n                    continue\n                for field in field_list:\n                    s = self._open_stream(data_file, (ptype, field))\n                    c = np.frombuffer(s, dtype=\"float64\")\n                    if field in self._vector_fields:\n                        c = c.reshape(c.size // 3, 3)\n                    data = c[mask, ...]\n                    yield (ptype, field), data\n\n    def _count_particles(self, data_file):\n        return self.ds.parameters[\"particle_count\"][data_file.file_id]\n"
  },
  {
    "path": "yt/frontends/moab/__init__.py",
    "content": "\"\"\"\nEmpty __init__.py file.\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/moab/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    MoabHex8Dataset,\n    MoabHex8Hierarchy,\n    MoabHex8Mesh,\n    PyneMoabHex8Dataset,\n)\nfrom .fields import MoabFieldInfo, PyneFieldInfo\nfrom .io import IOHandlerMoabH5MHex8\n"
  },
  {
    "path": "yt/frontends/moab/data_structures.py",
    "content": "import os\nimport weakref\nfrom functools import cached_property\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.unstructured_mesh import SemiStructuredMesh\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import setdefaultattr\nfrom yt.geometry.unstructured_mesh_handler import UnstructuredIndex\nfrom yt.utilities.file_handler import HDF5FileHandler\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import MoabFieldInfo, PyneFieldInfo\n\n\nclass MoabHex8Mesh(SemiStructuredMesh):\n    _connectivity_length = 8\n    _index_offset = 1\n\n\nclass MoabHex8Hierarchy(UnstructuredIndex):\n    def __init__(self, ds, dataset_type=\"h5m\"):\n        self.dataset = weakref.proxy(ds)\n        self.dataset_type = dataset_type\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        self._fhandle = h5py.File(self.index_filename, mode=\"r\")\n\n        UnstructuredIndex.__init__(self, ds, dataset_type)\n\n        self._fhandle.close()\n\n    def _initialize_mesh(self):\n        con = self._fhandle[\"/tstt/elements/Hex8/connectivity\"][:]\n        con = np.asarray(con, dtype=\"int64\")\n        coords = self._fhandle[\"/tstt/nodes/coordinates\"][:]\n        coords = np.asarray(coords, dtype=\"float64\")\n        self.meshes = [MoabHex8Mesh(0, self.index_filename, con, coords, self)]\n\n    def _detect_output_fields(self):\n        self.field_list = [\n            (\"moab\", f) for f in self._fhandle[\"/tstt/elements/Hex8/tags\"].keys()\n        ]\n\n    def _count_grids(self):\n        self.num_grids = 1\n\n\nclass MoabHex8Dataset(Dataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = MoabHex8Hierarchy\n    _field_info_class = MoabFieldInfo\n    periodicity = (False, False, False)\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"moab_hex8\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        self.fluid_types += (\"moab\",)\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n        self.storage_filename = storage_filename\n        self._handle = HDF5FileHandler(filename)\n\n    def _set_code_unit_attributes(self):\n        # Almost everything is regarded as dimensionless in MOAB, so these will\n        # not be used very much or at all.\n        setdefaultattr(self, \"length_unit\", self.quan(1.0, \"cm\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"g\"))\n\n    def _parse_parameter_file(self):\n        self._handle = h5py.File(self.parameter_filename, mode=\"r\")\n        coords = self._handle[\"/tstt/nodes/coordinates\"]\n        self.domain_left_edge = coords[0]\n        self.domain_right_edge = coords[-1]\n        self.domain_dimensions = self.domain_right_edge - self.domain_left_edge\n        self.refine_by = 2\n        self.dimensionality = len(self.domain_dimensions)\n        self.current_time = 0.0\n        self.cosmological_simulation = False\n        self.num_ghost_zones = 0\n        self.current_redshift = 0.0\n        self.omega_lambda = 0.0\n        self.omega_matter = 0.0\n        self.hubble_constant = 0.0\n        self.cosmological_simulation = 0\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        return filename.endswith(\".h5m\") and not cls._missing_load_requirements()\n\n    def __str__(self):\n        return self.basename.rsplit(\".\", 1)[0]\n\n\nclass PyneHex8Mesh(SemiStructuredMesh):\n    _connectivity_length = 8\n    _index_offset = 0\n\n\nclass PyneMeshHex8Hierarchy(UnstructuredIndex):\n    def __init__(self, ds, dataset_type=\"moab_hex8_pyne\"):\n        self.dataset = weakref.proxy(ds)\n        self.dataset_type = dataset_type\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.getcwd()\n        self.pyne_mesh = ds.pyne_mesh\n\n        super().__init__(ds, dataset_type)\n\n    def _initialize_mesh(self):\n        from pymoab import types\n\n        ents = list(self.pyne_mesh.structured_iterate_vertex())\n        coords = self.pyne_mesh.mesh.get_coords(ents).astype(\"float64\")\n        coords = coords.reshape(len(coords) // 3, 3)\n        hexes = self.pyne_mesh.mesh.get_entities_by_type(0, types.MBHEX)\n        vind = []\n        for h in hexes:\n            vind.append(\n                self.pyne_mesh.mesh.get_adjacencies(\n                    h, 0, create_if_missing=True, op_type=types.UNION\n                )\n            )\n        vind = np.asarray(vind, dtype=np.int64)\n        if vind.ndim == 1:\n            vind = vind.reshape(len(vind) // 8, 8)\n        assert vind.ndim == 2 and vind.shape[1] == 8\n        self.meshes = [PyneHex8Mesh(0, self.index_filename, vind, coords, self)]\n\n    def _detect_output_fields(self):\n        self.field_list = [(\"pyne\", f) for f in self.pyne_mesh.tags.keys()]\n\n    def _count_grids(self):\n        self.num_grids = 1\n\n\nclass PyneMoabHex8Dataset(Dataset):\n    _index_class = PyneMeshHex8Hierarchy\n    _fieldinfo_fallback = MoabFieldInfo\n    _field_info_class = PyneFieldInfo\n    periodicity = (False, False, False)\n\n    def __init__(\n        self,\n        pyne_mesh,\n        dataset_type=\"moab_hex8_pyne\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        self.fluid_types += (\"pyne\",)\n        filename = f\"pyne_mesh_{id(pyne_mesh)}\"\n        self.pyne_mesh = pyne_mesh\n        Dataset.__init__(\n            self,\n            str(filename),\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n        self.storage_filename = storage_filename\n\n    @property\n    def filename(self) -> str:\n        return self._input_filename\n\n    @cached_property\n    def unique_identifier(self) -> str:\n        return self.filename\n\n    def _set_code_unit_attributes(self):\n        # Almost everything is regarded as dimensionless in MOAB, so these will\n        # not be used very much or at all.\n        setdefaultattr(self, \"length_unit\", self.quan(1.0, \"cm\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"g\"))\n\n    def _parse_parameter_file(self):\n        ents = list(self.pyne_mesh.structured_iterate_vertex())\n        coords = self.pyne_mesh.mesh.get_coords(ents)\n        self.domain_left_edge = coords[0:3]\n        self.domain_right_edge = coords[-3:]\n        self.domain_dimensions = self.domain_right_edge - self.domain_left_edge\n        self.refine_by = 2\n        self.dimensionality = len(self.domain_dimensions)\n        self.current_time = 0.0\n        self.cosmological_simulation = False\n        self.num_ghost_zones = 0\n        self.current_redshift = 0.0\n        self.omega_lambda = 0.0\n        self.omega_matter = 0.0\n        self.hubble_constant = 0.0\n        self.cosmological_simulation = 0\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        return False\n\n    def __str__(self):\n        return self.basename.rsplit(\".\", 1)[0]\n"
  },
  {
    "path": "yt/frontends/moab/definitions.py",
    "content": ""
  },
  {
    "path": "yt/frontends/moab/fields.py",
    "content": "from yt.fields.field_info_container import FieldInfoContainer\n\n\nclass MoabFieldInfo(FieldInfoContainer):\n    pass\n\n\nclass PyneFieldInfo(FieldInfoContainer):\n    pass\n"
  },
  {
    "path": "yt/frontends/moab/io.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog\nfrom yt.utilities.io_handler import BaseIOHandler\n\n\ndef field_dname(field_name):\n    return f\"/tstt/elements/Hex8/tags/{field_name}\"\n\n\n# TODO all particle bits were removed\nclass IOHandlerMoabH5MHex8(BaseIOHandler):\n    _dataset_type = \"moab_hex8\"\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        self._handle = ds._handle\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        assert len(chunks) == 1\n        fhandle = self._handle\n        rv = {}\n        for field in fields:\n            ftype, fname = field\n            rv[field] = np.empty(size, dtype=fhandle[field_dname(fname)].dtype)\n        ngrids = sum(len(chunk.objs) for chunk in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s blocks\",\n            size,\n            [fname for ft, fn in fields],\n            ngrids,\n        )\n        for field in fields:\n            ftype, fname = field\n            ds = np.array(fhandle[field_dname(fname)][:], dtype=\"float64\")\n            ind = 0\n            for chunk in chunks:\n                for g in chunk.objs:\n                    ind += g.select(selector, ds, rv[field], ind)  # caches\n        return rv\n\n\nclass IOHandlerMoabPyneHex8(BaseIOHandler):\n    _dataset_type = \"moab_hex8_pyne\"\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        assert len(chunks) == 1\n        rv = {}\n        pyne_mesh = self.ds.pyne_mesh\n        for field in fields:\n            rv[field] = np.empty(size, dtype=\"float64\")\n        ngrids = sum(len(chunk.objs) for chunk in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s blocks\",\n            size,\n            [fname for ftype, fname in fields],\n            ngrids,\n        )\n        for field in fields:\n            ftype, fname = field\n            if pyne_mesh.structured:\n                tag = pyne_mesh.mesh.tag_get_handle(\"idx\")\n                hex_list = list(pyne_mesh.structured_iterate_hex())\n                indices = pyne_mesh.mesh.tag_get_data(tag, hex_list).flatten()\n            else:\n                indices = slice(None)\n            ds = np.asarray(getattr(pyne_mesh, fname)[indices], \"float64\")\n\n            ind = 0\n            for chunk in chunks:\n                for g in chunk.objs:\n                    ind += g.select(selector, ds, rv[field], ind)  # caches\n        return rv\n"
  },
  {
    "path": "yt/frontends/moab/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/moab/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/moab/tests/test_c5.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.frontends.moab.api import MoabHex8Dataset\nfrom yt.testing import requires_file, requires_module, units_override_check\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    data_dir_load,\n    requires_ds,\n)\n\n_fields = ((\"moab\", \"flux\"),)\n\nc5 = \"c5/c5.h5m\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(c5)\ndef test_cantor_5():\n    np.random.seed(0x4D3D3D3)\n    ds = data_dir_load(c5)\n    assert_equal(str(ds), \"c5\")\n    dso = [\n        None,\n        (\"sphere\", (\"c\", (0.1, \"unitary\"))),\n        (\"sphere\", (\"c\", (0.2, \"unitary\"))),\n    ]\n    dd = ds.all_data()\n    assert_almost_equal(ds.index.get_smallest_dx(), 0.00411522633744843, 10)\n    assert_equal(dd[\"gas\", \"x\"].shape[0], 63 * 63 * 63)\n    assert_almost_equal(\n        dd[\"index\", \"cell_volume\"].in_units(\"code_length**3\").sum(dtype=\"float64\").d,\n        1.0,\n        10,\n    )\n    for offset_1 in [1e-9, 1e-4, 0.1]:\n        for offset_2 in [1e-9, 1e-4, 0.1]:\n            DLE = ds.domain_left_edge\n            DRE = ds.domain_right_edge\n            ray = ds.ray(DLE + offset_1 * DLE.uq, DRE - offset_2 * DRE.uq)\n            assert_almost_equal(ray[\"dts\"].sum(dtype=\"float64\"), 1.0, 8)\n    for p1 in np.random.random((5, 3)):\n        for p2 in np.random.random((5, 3)):\n            ray = ds.ray(p1, p2)\n            assert_almost_equal(ray[\"dts\"].sum(dtype=\"float64\"), 1.0, 8)\n    for field in _fields:\n        for dobj_name in dso:\n            yield FieldValuesTest(c5, field, dobj_name)\n\n\n@requires_module(\"h5py\")\n@requires_file(c5)\ndef test_MoabHex8Dataset():\n    assert isinstance(data_dir_load(c5), MoabHex8Dataset)\n\n\n@requires_file(c5)\ndef test_units_override():\n    units_override_check(c5)\n"
  },
  {
    "path": "yt/frontends/nc4_cm1/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/nc4_cm1/api.py",
    "content": "\"\"\"\nAPI for yt.frontends.nc4_cm1\n\n\n\n\"\"\"\n\nfrom .data_structures import CM1Dataset, CM1Grid, CM1Hierarchy\nfrom .fields import CM1FieldInfo\nfrom .io import CM1IOHandler\n"
  },
  {
    "path": "yt/frontends/nc4_cm1/data_structures.py",
    "content": "import os\nimport weakref\nfrom collections import OrderedDict\n\nimport numpy as np\n\nfrom yt._typing import AxisOrder\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.file_handler import NetCDF4FileHandler, valid_netcdf_signature\nfrom yt.utilities.logger import ytLogger as mylog\n\nfrom .fields import CM1FieldInfo\n\n\nclass CM1Grid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level, dimensions):\n        super().__init__(id, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n        self.ActiveDimensions = dimensions\n\n\nclass CM1Hierarchy(GridIndex):\n    grid = CM1Grid\n\n    def __init__(self, ds, dataset_type=\"cm1\"):\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        # for now, the index file is the dataset!\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        # float type for the simulation edges and must be float64 now\n        self.float_type = np.float64\n        super().__init__(ds, dataset_type)\n\n    def _detect_output_fields(self):\n        # build list of on-disk fields for dataset_type 'cm1'\n        vnames = self.dataset.parameters[\"variable_names\"]\n        self.field_list = [(\"cm1\", vname) for vname in vnames]\n\n    def _count_grids(self):\n        # This needs to set self.num_grids\n        self.num_grids = 1\n\n    def _parse_index(self):\n        self.grid_left_edge[0][:] = self.ds.domain_left_edge[:]\n        self.grid_right_edge[0][:] = self.ds.domain_right_edge[:]\n        self.grid_dimensions[0][:] = self.ds.domain_dimensions[:]\n        self.grid_particle_count[0][0] = 0\n        self.grid_levels[0][0] = 0\n        self.max_level = 0\n\n    def _populate_grid_objects(self):\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n        for i in range(self.num_grids):\n            g = self.grid(i, self, self.grid_levels.flat[i], self.grid_dimensions[i])\n            g._prepare_grid()\n            g._setup_dx()\n            self.grids[i] = g\n\n\nclass CM1Dataset(Dataset):\n    _load_requirements = [\"netCDF4\"]\n    _index_class = CM1Hierarchy\n    _field_info_class = CM1FieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"cm1\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"mks\",\n    ):\n        self.fluid_types += (\"cm1\",)\n        self._handle = NetCDF4FileHandler(filename)\n        # refinement factor between a grid and its subgrid.\n        self.refine_by = 1\n        super().__init__(\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n        self.storage_filename = storage_filename\n\n    def _setup_coordinate_handler(self, axis_order: AxisOrder | None) -> None:\n        # ensure correct ordering of axes so plots aren't rotated (z should always be\n        # on the vertical axis).\n        super()._setup_coordinate_handler(axis_order)\n\n        # type checking is deactivated in the following two lines because changing them is not\n        # within the scope of the PR that _enabled_ typechecking here (#4244), but it'd be worth\n        # having a careful look at *why* these warnings appear, as they may point to rotten code\n        self.coordinates._x_pairs = ((\"x\", \"y\"), (\"y\", \"x\"), (\"z\", \"x\"))  # type: ignore [union-attr]\n        self.coordinates._y_pairs = ((\"x\", \"z\"), (\"y\", \"z\"), (\"z\", \"y\"))  # type: ignore [union-attr]\n\n    def _set_code_unit_attributes(self):\n        # This is where quantities are created that represent the various\n        # on-disk units.  These are the currently available quantities which\n        # should be set, along with examples of how to set them to standard\n        # values.\n        with self._handle.open_ds() as _handle:\n            length_unit = _handle.variables[\"xh\"].units\n        self.length_unit = self.quan(1.0, length_unit)\n        self.mass_unit = self.quan(1.0, \"kg\")\n        self.time_unit = self.quan(1.0, \"s\")\n        self.velocity_unit = self.quan(1.0, \"m/s\")\n        self.time_unit = self.quan(1.0, \"s\")\n\n    def _parse_parameter_file(self):\n        # This needs to set up the following items.  Note that these are all\n        # assumed to be in code units; domain_left_edge and domain_right_edge\n        # will be converted to YTArray automatically at a later time.\n        # This includes the cosmological parameters.\n\n        self.parameters = {}  # code-specific items\n        with self._handle.open_ds() as _handle:\n            # _handle here is a netcdf Dataset object, we need to parse some metadata\n            # for constructing our yt ds.\n\n            # TO DO: generalize this to be coordinate variable name agnostic in order to\n            # make useful for WRF or climate data. For now, we're hard coding for CM1\n            # specifically and have named the classes appropriately. Additionally, we\n            # are only handling the cell-centered grid (\"xh\",\"yh\",\"zh\") at present.\n            # The cell-centered grid contains scalar fields and interpolated velocities.\n            dims = [_handle.dimensions[i].size for i in [\"xh\", \"yh\", \"zh\"]]\n            xh, yh, zh = (_handle.variables[i][:] for i in [\"xh\", \"yh\", \"zh\"])\n            self.domain_left_edge = np.array(\n                [xh.min(), yh.min(), zh.min()], dtype=\"float64\"\n            )\n            self.domain_right_edge = np.array(\n                [xh.max(), yh.max(), zh.max()], dtype=\"float64\"\n            )\n\n            # loop over the variable names in the netCDF file, record only those on the\n            # \"zh\",\"yh\",\"xh\" grid.\n            varnames = []\n            for key, var in _handle.variables.items():\n                if all(x in var.dimensions for x in [\"time\", \"zh\", \"yh\", \"xh\"]):\n                    varnames.append(key)\n            self.parameters[\"variable_names\"] = varnames\n            self.parameters[\"lofs_version\"] = _handle.cm1_lofs_version\n            self.parameters[\"is_uniform\"] = _handle.uniform_mesh\n            self.current_time = _handle.variables[\"time\"][:][0]\n\n            # record the dimension metadata: __handle.dimensions contains netcdf\n            # objects so we need to manually copy over attributes.\n            dim_info = OrderedDict()\n            for dim, meta in _handle.dimensions.items():\n                dim_info[dim] = meta.size\n            self.parameters[\"dimensions\"] = dim_info\n\n        self.dimensionality = 3\n        self.domain_dimensions = np.array(dims, dtype=\"int64\")\n        self._periodicity = (False, False, False)\n\n        # Set cosmological information to zero for non-cosmological.\n        self.cosmological_simulation = 0\n        self.current_redshift = 0.0\n        self.omega_lambda = 0.0\n        self.omega_matter = 0.0\n        self.hubble_constant = 0.0\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        # This accepts a filename or a set of arguments and returns True or\n        # False depending on if the file is of the type requested.\n        if not valid_netcdf_signature(filename):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            nc4_file = NetCDF4FileHandler(filename)\n            with nc4_file.open_ds(keepweakref=True) as _handle:\n                is_cm1_lofs = hasattr(_handle, \"cm1_lofs_version\")\n                is_cm1 = hasattr(_handle, \"cm1 version\")  # not a typo, it is a space...\n\n                # ensure coordinates of each variable array exists in the dataset\n                coords = _handle.dimensions  # get the dataset wide coordinates\n                failed_vars = []  # list of failed variables\n                for var in _handle.variables:  # iterate over the variables\n                    vcoords = _handle[var].dimensions  # get the dims for the variable\n                    ncoords = len(vcoords)  # number of coordinates in variable\n                    # number of coordinates that pass for a variable\n                    coordspassed = sum(vc in coords for vc in vcoords)\n                    if coordspassed != ncoords:\n                        failed_vars.append(var)\n\n                if failed_vars:\n                    mylog.warning(\n                        \"Trying to load a cm1_lofs netcdf file but the \"\n                        \"coordinates of the following fields do not match the \"\n                        \"coordinates of the dataset: %s\",\n                        failed_vars,\n                    )\n                    return False\n\n            if not is_cm1_lofs:\n                if is_cm1:\n                    mylog.warning(\n                        \"It looks like you are trying to load a cm1 netcdf file, \"\n                        \"but at present yt only supports cm1_lofs output. Until\"\n                        \" support is added, you can likely use\"\n                        \" yt.load_uniform_grid() to load your cm1 file manually.\"\n                    )\n                return False\n        except (OSError, AttributeError, ImportError):\n            return False\n\n        return True\n"
  },
  {
    "path": "yt/frontends/nc4_cm1/fields.py",
    "content": "from yt.fields.field_info_container import FieldInfoContainer\n\n# We need to specify which fields we might have in our dataset.  The field info\n# container subclass here will define which fields it knows about.  There are\n# optionally methods on it that get called which can be subclassed.\n\n\nclass CM1FieldInfo(FieldInfoContainer):\n    known_other_fields = (\n        # Each entry here is of the form\n        # ( \"name\", (\"units\", [\"fields\", \"to\", \"alias\"], # \"display_name\")),\n        (\"uinterp\", (\"m/s\", [\"velocity_x\"], None)),\n        (\"vinterp\", (\"m/s\", [\"velocity_y\"], None)),\n        (\"winterp\", (\"m/s\", [\"velocity_z\"], None)),\n        (\"u\", (\"m/s\", [\"velocity_x\"], None)),\n        (\"v\", (\"m/s\", [\"velocity_y\"], None)),\n        (\"w\", (\"m/s\", [\"velocity_z\"], None)),\n        (\"hwin_sr\", (\"m/s\", [\"storm_relative_horizontal_wind_speed\"], None)),\n        (\"windmag_sr\", (\"m/s\", [\"storm_relative_3D_wind_speed\"], None)),\n        (\"hwin_gr\", (\"m/s\", [\"ground_relative_horizontal_wind_speed\"], None)),\n        (\"thpert\", (\"K\", [\"potential_temperature_perturbation\"], None)),\n        (\"thrhopert\", (\"K\", [\"density_potential_temperature_perturbation\"], None)),\n        (\"prespert\", (\"hPa\", [\"presure_perturbation\"], None)),\n        (\"rhopert\", (\"kg/m**3\", [\"density_perturbation\"], None)),\n        (\"dbz\", (\"dB\", [\"simulated_reflectivity\"], None)),\n        (\"qvpert\", (\"g/kg\", [\"water_vapor_mixing_ratio_perturbation\"], None)),\n        (\"qc\", (\"g/kg\", [\"cloud_liquid_water_mixing_ratio\"], None)),\n        (\"qr\", (\"g/kg\", [\"rain_mixing_ratio\"], None)),\n        (\"qi\", (\"g/kg\", [\"cloud_ice_mixing_ratio\"], None)),\n        (\"qs\", (\"g/kg\", [\"snow_mixing_ratio\"], None)),\n        (\"qg\", (\"g/kg\", [\"graupel_or_hail_mixing_ratio\"], None)),\n        (\"qcloud\", (\"g/kg\", [\"sum_of_cloud_water_and_cloud_ice_mixing_ratios\"], None)),\n        (\"qprecip\", (\"g/kg\", [\"sum_of_rain_graupel_snow_mixing_ratios\"], None)),\n        (\"nci\", (\"1/cm**3\", [\"number_concerntration_of_cloud_ice\"], None)),\n        (\"ncr\", (\"1/cm**3\", [\"number_concentration_of_rain\"], None)),\n        (\"ncs\", (\"1/cm**3\", [\"number_concentration_of_snow\"], None)),\n        (\"ncg\", (\"1/cm**3\", [\"number_concentration_of_graupel_or_hail\"], None)),\n        (\"xvort\", (\"1/s\", [\"vorticity_x\"], None)),\n        (\"yvort\", (\"1/s\", [\"vorticity_y\"], None)),\n        (\"zvort\", (\"1/s\", [\"vorticity_z\"], None)),\n        (\"hvort\", (\"1/s\", [\"horizontal_vorticity_magnitude\"], None)),\n        (\"vortmag\", (\"1/s\", [\"vorticity_magnitude\"], None)),\n        (\"streamvort\", (\"1/s\", [\"streamwise_vorticity\"], None)),\n        (\"khh\", (\"m**2/s\", [\"khh\"], None)),\n        (\"khv\", (\"m**2/s\", [\"khv\"], None)),\n        (\"kmh\", (\"m**2/s\", [\"kmh\"], None)),\n        (\"kmv\", (\"m**2/s\", [\"kmv\"], None)),\n    )\n\n    known_particle_fields = (\n        # Identical form to above\n        # ( \"name\", (\"units\", [\"fields\", \"to\", \"alias\"], # \"display_name\")),\n    )\n\n    def setup_fluid_fields(self):\n        # Here we do anything that might need info about the dataset.\n        # You can use self.alias, self.add_output_field (for on-disk fields)\n        # and self.add_field (for derived fields).\n        pass\n\n    def setup_particle_fields(self, ptype):\n        super().setup_particle_fields(ptype)\n        # This will get called for every particle type.\n"
  },
  {
    "path": "yt/frontends/nc4_cm1/io.py",
    "content": "import numpy as np\n\nfrom yt.utilities.file_handler import NetCDF4FileHandler\nfrom yt.utilities.io_handler import BaseIOHandler\n\n\nclass CM1IOHandler(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"cm1\"\n\n    def __init__(self, ds):\n        self.filename = ds.filename\n        self._handle = NetCDF4FileHandler(self.filename)\n        super().__init__(ds)\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This needs to *yield* a series of tuples of (ptype, (x, y, z)).\n        # chunks is a list of chunks, and ptf is a dict where the keys are\n        # ptypes and the values are lists of fields.\n        raise NotImplementedError\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # This gets called after the arrays have been allocated.  It needs to\n        # yield ((ptype, field), data) where data is the masked results of\n        # reading ptype, field and applying the selector to the data read in.\n        # Selector objects have a .select_points(x,y,z) that returns a mask, so\n        # you need to do your masking here.\n        raise NotImplementedError\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        # This needs to allocate a set of arrays inside a dictionary, where the\n        # keys are the (ftype, fname) tuples and the values are arrays that\n        # have been masked using whatever selector method is appropriate.  The\n        # dict gets returned at the end and it should be flat, with selected\n        # data.  Note that if you're reading grid data, you might need to\n        # special-case a grid selector object.\n        # Also note that \"chunks\" is a generator for multiple chunks, each of\n        # which contains a list of grids. The returned numpy arrays should be\n        # in 64-bit float and contiguous along the z direction. Therefore, for\n        # a C-like input array with the dimension [x][y][z] or a\n        # Fortran-like input array with the dimension (z,y,x), a matrix\n        # transpose is required (e.g., using np_array.transpose() or\n        # np_array.swapaxes(0,2)).\n\n        data = {}\n        chunks = list(chunks)\n        with self._handle.open_ds() as ds:\n            for field in fields:\n                data[field] = np.empty(size, dtype=\"float64\")\n                offset = 0\n                for chunk in chunks:\n                    for grid in chunk.objs:\n                        variable = ds.variables[field[1]][:][0]\n                        values = np.squeeze(variable.T)\n                        offset += grid.select(selector, values, data[field], offset)\n        return data\n\n    def _read_chunk_data(self, chunk, fields):\n        # This reads the data from a single chunk without doing any selection,\n        # and is only used for caching data that might be used by multiple\n        # different selectors later. For instance, this can speed up ghost zone\n        # computation.\n        pass\n"
  },
  {
    "path": "yt/frontends/nc4_cm1/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/nc4_cm1/tests/test_outputs.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.frontends.nc4_cm1.api import CM1Dataset\nfrom yt.testing import requires_file, units_override_check\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    GridValuesTest,\n    can_run_ds,\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\n_fields = ((\"cm1\", \"thrhopert\"), (\"cm1\", \"zvort\"))\ncm1sim = \"cm1_tornado_lofs/nc4_cm1_lofs_tornado_test.nc\"\n\n\n@requires_ds(cm1sim)\ndef test_cm1_mesh_fields():\n    ds = data_dir_load(cm1sim)\n    assert_equal(str(ds), \"nc4_cm1_lofs_tornado_test.nc\")\n\n    # run the small_patch_amr tests on safe fields\n    ic = ds.domain_center\n    for test in small_patch_amr(ds, _fields, input_center=ic, input_weight=None):\n        test_cm1_mesh_fields.__name__ = test.description\n        yield test\n\n    # manually run the Grid and Field Values tests on dbz (do not want to run the\n    # ProjectionValuesTest for this field)\n    if can_run_ds(ds):\n        dso = [None, (\"sphere\", (ic, (0.1, \"unitary\")))]\n        for field in [(\"cm1\", \"dbz\")]:\n            yield GridValuesTest(ds, field)\n            for dobj_name in dso:\n                yield FieldValuesTest(ds, field, dobj_name)\n\n\n@requires_file(cm1sim)\ndef test_CM1Dataset():\n    assert isinstance(data_dir_load(cm1sim), CM1Dataset)\n\n\n@requires_file(cm1sim)\ndef test_units_override():\n    units_override_check(cm1sim)\n\n\n@requires_file(cm1sim)\ndef test_dims_and_meta():\n    ds = data_dir_load(cm1sim)\n\n    known_dims = [\"time\", \"zf\", \"zh\", \"yf\", \"yh\", \"xf\", \"xh\"]\n    dims = ds.parameters[\"dimensions\"]\n\n    ## check the file for 2 grids and a time dimension -\n    ## (time, xf, xh, yf, yh, zf, zh). The dimensions ending in\n    ## f are the staggered velocity grid components and the\n    ## dimensions ending in h are the scalar grid components\n    assert_equal(len(dims), len(known_dims))\n    for kdim in known_dims:\n        assert kdim in dims\n\n    ## check the simulation time\n    assert_equal(ds.current_time, 5500.0)\n\n\n@requires_file(cm1sim)\ndef test_if_cm1():\n    ds = data_dir_load(cm1sim)\n    assert float(ds.parameters[\"lofs_version\"]) >= 1.0\n"
  },
  {
    "path": "yt/frontends/open_pmd/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/open_pmd/api.py",
    "content": "from . import tests\nfrom .data_structures import OpenPMDDataset, OpenPMDGrid, OpenPMDHierarchy\nfrom .fields import OpenPMDFieldInfo\nfrom .io import IOHandlerOpenPMDHDF5\n"
  },
  {
    "path": "yt/frontends/open_pmd/data_structures.py",
    "content": "from functools import reduce\nfrom operator import mul\nfrom os import listdir, path\nfrom re import match\n\nimport numpy as np\nfrom packaging.version import Version\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.data_objects.time_series import DatasetSeries\nfrom yt.frontends.open_pmd.fields import OpenPMDFieldInfo\nfrom yt.frontends.open_pmd.misc import get_component, is_const_component\nfrom yt.funcs import setdefaultattr\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.file_handler import HDF5FileHandler, valid_hdf5_signature\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nompd_known_versions = [Version(_) for _ in (\"1.0.0\", \"1.0.1\", \"1.1.0\")]\nopmd_required_attributes = [\"openPMD\", \"basePath\"]\n\n\nclass OpenPMDGrid(AMRGridPatch):\n    \"\"\"Represents chunk of data on-disk.\n\n    This defines the index and offset for every mesh and particle type.\n    It also defines parents and children grids. Since openPMD does not have multiple\n    levels of refinement there are no parents or children for any grid.\n    \"\"\"\n\n    _id_offset = 0\n    __slots__ = [\"_level_id\"]\n    # Every particle species and mesh might have different hdf5-indices and offsets\n\n    ftypes: list[str] | None = []\n    ptypes: list[str] | None = []\n    findex = 0\n    foffset = 0\n    pindex = 0\n    poffset = 0\n\n    def __init__(self, gid, index, level=-1, fi=0, fo=0, pi=0, po=0, ft=None, pt=None):\n        AMRGridPatch.__init__(self, gid, filename=index.index_filename, index=index)\n        if ft is None:\n            ft = []\n        if pt is None:\n            pt = []\n        self.findex = fi\n        self.foffset = fo\n        self.pindex = pi\n        self.poffset = po\n        self.ftypes = ft\n        self.ptypes = pt\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n\n    def __str__(self):\n        return f\"OpenPMDGrid_{self.id:04} ({self.ActiveDimensions})\"\n\n\nclass OpenPMDHierarchy(GridIndex):\n    \"\"\"Defines which fields and particles are created and read from disk.\n\n    Furthermore it defines the characteristics of the grids.\n    \"\"\"\n\n    grid = OpenPMDGrid\n\n    def __init__(self, ds, dataset_type=\"openPMD\"):\n        self.dataset_type = dataset_type\n        self.dataset = ds\n        self.index_filename = ds.parameter_filename\n        self.directory = path.dirname(self.index_filename)\n        GridIndex.__init__(self, ds, dataset_type)\n\n    def _get_particle_type_counts(self):\n        \"\"\"Reads the active number of particles for every species.\n\n        Returns\n        -------\n        dict\n            keys are ptypes\n            values are integer counts of the ptype\n        \"\"\"\n        result = {}\n        f = self.dataset._handle\n        bp = self.dataset.base_path\n        pp = self.dataset.particles_path\n\n        try:\n            for ptype in self.ds.particle_types_raw:\n                if str(ptype) == \"io\":\n                    spec = list(f[bp + pp].keys())[0]\n                else:\n                    spec = ptype\n                axis = list(f[bp + pp + \"/\" + spec + \"/position\"].keys())[0]\n                pos = f[bp + pp + \"/\" + spec + \"/position/\" + axis]\n                if is_const_component(pos):\n                    result[ptype] = pos.attrs[\"shape\"]\n                else:\n                    result[ptype] = pos.len()\n        except KeyError:\n            result[\"io\"] = 0\n\n        return result\n\n    def _detect_output_fields(self):\n        \"\"\"Populates ``self.field_list`` with native fields (mesh and particle) on disk.\n\n        Each entry is a tuple of two strings. The first element is the on-disk fluid\n        type or particle type. The second element is the name of the field in yt.\n        This string is later used for accessing the data.\n        Convention suggests that the on-disk fluid type should be \"openPMD\",\n        the on-disk particle type (for a single species of particles) is \"io\"\n        or (for multiple species of particles) the particle name on-disk.\n        \"\"\"\n        f = self.dataset._handle\n        bp = self.dataset.base_path\n        mp = self.dataset.meshes_path\n        pp = self.dataset.particles_path\n\n        mesh_fields = []\n        try:\n            meshes = f[bp + mp]\n            for mname in meshes.keys():\n                try:\n                    mesh = meshes[mname]\n                    for axis in mesh.keys():\n                        mesh_fields.append(mname.replace(\"_\", \"-\") + \"_\" + axis)\n                except AttributeError:\n                    # This is a h5py.Dataset (i.e. no axes)\n                    mesh_fields.append(mname.replace(\"_\", \"-\"))\n        except (KeyError, TypeError, AttributeError):\n            pass\n        self.field_list = [(\"openPMD\", str(field)) for field in mesh_fields]\n\n        particle_fields = []\n        try:\n            particles = f[bp + pp]\n            for pname in particles.keys():\n                species = particles[pname]\n                for recname in species.keys():\n                    record = species[recname]\n                    if is_const_component(record):\n                        # Record itself (e.g. particle_mass) is constant\n                        particle_fields.append(\n                            pname.replace(\"_\", \"-\") + \"_\" + recname.replace(\"_\", \"-\")\n                        )\n                    elif \"particlePatches\" not in recname:\n                        try:\n                            # Create a field for every axis (x,y,z) of every\n                            # property (position) of every species (electrons)\n                            axes = list(record.keys())\n                            if str(recname) == \"position\":\n                                recname = \"positionCoarse\"\n                            for axis in axes:\n                                particle_fields.append(\n                                    pname.replace(\"_\", \"-\")\n                                    + \"_\"\n                                    + recname.replace(\"_\", \"-\")\n                                    + \"_\"\n                                    + axis\n                                )\n                        except AttributeError:\n                            # Record is a dataset, does not have axes (e.g. weighting)\n                            particle_fields.append(\n                                pname.replace(\"_\", \"-\")\n                                + \"_\"\n                                + recname.replace(\"_\", \"-\")\n                            )\n                            pass\n                    else:\n                        pass\n            if len(list(particles.keys())) > 1:\n                # There is more than one particle species,\n                # use the specific names as field types\n                self.field_list.extend(\n                    [\n                        (\n                            str(field).split(\"_\")[0],\n                            (\"particle_\" + \"_\".join(str(field).split(\"_\")[1:])),\n                        )\n                        for field in particle_fields\n                    ]\n                )\n            else:\n                # Only one particle species, fall back to \"io\"\n                self.field_list.extend(\n                    [\n                        (\"io\", (\"particle_\" + \"_\".join(str(field).split(\"_\")[1:])))\n                        for field in particle_fields\n                    ]\n                )\n        except (KeyError, TypeError, AttributeError):\n            pass\n\n    def _count_grids(self):\n        \"\"\"Sets ``self.num_grids`` to be the total number of grids in the simulation.\n\n        The number of grids is determined by their respective memory footprint.\n        \"\"\"\n        f = self.dataset._handle\n        bp = self.dataset.base_path\n        mp = self.dataset.meshes_path\n        pp = self.dataset.particles_path\n\n        self.meshshapes = {}\n        self.numparts = {}\n\n        self.num_grids = 0\n\n        try:\n            meshes = f[bp + mp]\n            for mname in meshes.keys():\n                mesh = meshes[mname]\n                if isinstance(mesh, h5py.Group):\n                    shape = mesh[list(mesh.keys())[0]].shape\n                else:\n                    shape = mesh.shape\n                spacing = tuple(mesh.attrs[\"gridSpacing\"])\n                offset = tuple(mesh.attrs[\"gridGlobalOffset\"])\n                unit_si = mesh.attrs[\"gridUnitSI\"]\n                self.meshshapes[mname] = (shape, spacing, offset, unit_si)\n        except (KeyError, TypeError, AttributeError):\n            pass\n        try:\n            particles = f[bp + pp]\n            for pname in particles.keys():\n                species = particles[pname]\n                if \"particlePatches\" in species.keys():\n                    for patch, size in enumerate(\n                        species[\"/particlePatches/numParticles\"]\n                    ):\n                        self.numparts[f\"{pname}#{patch}\"] = size\n                else:\n                    axis = list(species[\"/position\"].keys())[0]\n                    if is_const_component(species[\"/position/\" + axis]):\n                        self.numparts[pname] = species[\"/position/\" + axis].attrs[\n                            \"shape\"\n                        ]\n                    else:\n                        self.numparts[pname] = species[\"/position/\" + axis].len()\n        except (KeyError, TypeError, AttributeError):\n            pass\n\n        # Limit values per grid by resulting memory footprint\n        self.vpg = int(self.dataset.gridsize / 4)  # 4Byte per value (f32)\n\n        # Meshes of the same size do not need separate chunks\n        for shape, *_ in set(self.meshshapes.values()):\n            self.num_grids += min(\n                shape[0], int(np.ceil(reduce(mul, shape) * self.vpg**-1))\n            )\n\n        # Same goes for particle chunks if they are not inside particlePatches\n        patches = {}\n        no_patches = {}\n        for k, v in self.numparts.items():\n            if \"#\" in k:\n                patches[k] = v\n            else:\n                no_patches[k] = v\n        for size in set(no_patches.values()):\n            self.num_grids += int(np.ceil(size * self.vpg**-1))\n        for size in patches.values():\n            self.num_grids += int(np.ceil(size * self.vpg**-1))\n\n    def _parse_index(self):\n        \"\"\"Fills each grid with appropriate properties (extent, dimensions, ...)\n\n        This calculates the properties of every OpenPMDGrid based on the total number of\n        grids in the simulation. The domain is divided into ``self.num_grids`` (roughly)\n        equally sized chunks along the x-axis. ``grid_levels`` is always equal to 0\n        since we only have one level of refinement in openPMD.\n\n        Notes\n        -----\n        ``self.grid_dimensions`` is rounded to the nearest integer. Grid edges are\n        calculated from this dimension. Grids with dimensions [0, 0, 0] are particle\n        only. The others do not have any particles affiliated with them.\n        \"\"\"\n        f = self.dataset._handle\n        bp = self.dataset.base_path\n        pp = self.dataset.particles_path\n\n        self.grid_levels.flat[:] = 0\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n\n        grid_index_total = 0\n\n        # Mesh grids\n        for mesh in set(self.meshshapes.values()):\n            (shape, spacing, offset, unit_si) = mesh\n            shape = np.asarray(shape)\n            spacing = np.asarray(spacing)\n            offset = np.asarray(offset)\n            # Total dimension of this grid\n            domain_dimension = np.asarray(shape, dtype=np.int32)\n            domain_dimension = np.append(\n                domain_dimension, np.ones(3 - len(domain_dimension))\n            )\n            # Number of grids of this shape\n            num_grids = min(shape[0], int(np.ceil(reduce(mul, shape) * self.vpg**-1)))\n            gle = offset * unit_si  # self.dataset.domain_left_edge\n            gre = (\n                domain_dimension[: spacing.size] * unit_si * spacing + gle\n            )  # self.dataset.domain_right_edge\n            gle = np.append(gle, np.zeros(3 - len(gle)))\n            gre = np.append(gre, np.ones(3 - len(gre)))\n            grid_dim_offset = np.linspace(\n                0, domain_dimension[0], num_grids + 1, dtype=np.int32\n            )\n            grid_edge_offset = (\n                grid_dim_offset * float(domain_dimension[0]) ** -1 * (gre[0] - gle[0])\n                + gle[0]\n            )\n            mesh_names = []\n            for mname, mdata in self.meshshapes.items():\n                if mesh == mdata:\n                    mesh_names.append(str(mname))\n            prev = 0\n            for grid in np.arange(num_grids):\n                self.grid_dimensions[grid_index_total] = domain_dimension\n                self.grid_dimensions[grid_index_total][0] = (\n                    grid_dim_offset[grid + 1] - grid_dim_offset[grid]\n                )\n                self.grid_left_edge[grid_index_total] = gle\n                self.grid_left_edge[grid_index_total][0] = grid_edge_offset[grid]\n                self.grid_right_edge[grid_index_total] = gre\n                self.grid_right_edge[grid_index_total][0] = grid_edge_offset[grid + 1]\n                self.grid_particle_count[grid_index_total] = 0\n                self.grids[grid_index_total] = self.grid(\n                    grid_index_total,\n                    self,\n                    0,\n                    fi=prev,\n                    fo=self.grid_dimensions[grid_index_total][0],\n                    ft=mesh_names,\n                )\n                prev += self.grid_dimensions[grid_index_total][0]\n                grid_index_total += 1\n\n        handled_ptypes = []\n\n        # Particle grids\n        for species, count in self.numparts.items():\n            if \"#\" in species:\n                # This is a particlePatch\n                spec = species.split(\"#\")\n                patch = f[bp + pp + \"/\" + spec[0] + \"/particlePatches\"]\n                domain_dimension = np.ones(3, dtype=np.int32)\n                for ind, axis in enumerate(list(patch[\"extent\"].keys())):\n                    domain_dimension[ind] = patch[\"extent/\" + axis][()][int(spec[1])]\n                num_grids = int(np.ceil(count * self.vpg**-1))\n                gle = []\n                for axis in patch[\"offset\"].keys():\n                    gle.append(\n                        get_component(patch, \"offset/\" + axis, int(spec[1]), 1)[0]\n                    )\n                gle = np.asarray(gle)\n                gle = np.append(gle, np.zeros(3 - len(gle)))\n                gre = []\n                for axis in patch[\"extent\"].keys():\n                    gre.append(\n                        get_component(patch, \"extent/\" + axis, int(spec[1]), 1)[0]\n                    )\n                gre = np.asarray(gre)\n                gre = np.append(gre, np.ones(3 - len(gre)))\n                np.add(gle, gre, gre)\n                npo = patch[\"numParticlesOffset\"][()].item(int(spec[1]))\n                particle_count = np.linspace(\n                    npo, npo + count, num_grids + 1, dtype=np.int32\n                )\n                particle_names = [str(spec[0])]\n            elif str(species) not in handled_ptypes:\n                domain_dimension = self.dataset.domain_dimensions\n                num_grids = int(np.ceil(count * self.vpg**-1))\n                gle = self.dataset.domain_left_edge\n                gre = self.dataset.domain_right_edge\n                particle_count = np.linspace(0, count, num_grids + 1, dtype=np.int32)\n                particle_names = []\n                for pname, size in self.numparts.items():\n                    if size == count:\n                        # Since this is not part of a particlePatch,\n                        # we can include multiple same-sized ptypes\n                        particle_names.append(str(pname))\n                        handled_ptypes.append(str(pname))\n            else:\n                # A grid with this exact particle count has already been created\n                continue\n            for grid in np.arange(num_grids):\n                self.grid_dimensions[grid_index_total] = domain_dimension\n                self.grid_left_edge[grid_index_total] = gle\n                self.grid_right_edge[grid_index_total] = gre\n                self.grid_particle_count[grid_index_total] = (\n                    particle_count[grid + 1] - particle_count[grid]\n                ) * len(particle_names)\n                self.grids[grid_index_total] = self.grid(\n                    grid_index_total,\n                    self,\n                    0,\n                    pi=particle_count[grid],\n                    po=particle_count[grid + 1] - particle_count[grid],\n                    pt=particle_names,\n                )\n                grid_index_total += 1\n\n    def _populate_grid_objects(self):\n        \"\"\"This initializes all grids.\n\n        Additionally, it should set up Children and Parent lists on each grid object.\n        openPMD is not adaptive and thus there are no Children and Parents for any grid.\n        \"\"\"\n        for i in np.arange(self.num_grids):\n            self.grids[i]._prepare_grid()\n            self.grids[i]._setup_dx()\n        self.max_level = 0\n\n\nclass OpenPMDDataset(Dataset):\n    \"\"\"Contains all the required information of a single iteration of the simulation.\n\n    Notes\n    -----\n    It is assumed that\n    - all meshes cover the same region. Their resolution can be different.\n    - all particles reside in this same region exclusively.\n    - particle and mesh positions are *absolute* with respect to the simulation origin.\n    \"\"\"\n\n    _load_requirements = [\"h5py\"]\n    _index_class = OpenPMDHierarchy\n    _field_info_class = OpenPMDFieldInfo\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"openPMD\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"mks\",\n        **kwargs,\n    ):\n        self._handle = HDF5FileHandler(filename)\n        self.gridsize = kwargs.pop(\"open_pmd_virtual_gridsize\", 10**9)\n        self.standard_version = Version(self._handle.attrs[\"openPMD\"].decode())\n        self.iteration = kwargs.pop(\"iteration\", None)\n        self._set_paths(self._handle, path.dirname(filename), self.iteration)\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n        self.storage_filename = storage_filename\n        self.fluid_types += (\"openPMD\",)\n        try:\n            particles = tuple(\n                str(c)\n                for c in self._handle[self.base_path + self.particles_path].keys()\n            )\n            if len(particles) > 1:\n                # Only use on-disk particle names if there is more than one species\n                self.particle_types = particles\n            mylog.debug(\"self.particle_types: %s\", self.particle_types)\n            self.particle_types_raw = self.particle_types\n            self.particle_types = tuple(self.particle_types)\n        except (KeyError, TypeError, AttributeError):\n            pass\n\n    def _set_paths(self, handle, path, iteration):\n        \"\"\"Parses relevant hdf5-paths out of ``handle``.\n\n        Parameters\n        ----------\n        handle : h5py.File\n        path : str\n            (absolute) filepath for current hdf5 container\n        \"\"\"\n        iterations = []\n        if iteration is None:\n            iteration = list(handle[\"/data\"].keys())[0]\n        encoding = handle.attrs[\"iterationEncoding\"].decode()\n        if \"groupBased\" in encoding:\n            iterations = list(handle[\"/data\"].keys())\n            mylog.info(\"Found %s iterations in file\", len(iterations))\n        elif \"fileBased\" in encoding:\n            itformat = handle.attrs[\"iterationFormat\"].decode().split(\"/\")[-1]\n            regex = \"^\" + itformat.replace(\"%T\", \"[0-9]+\") + \"$\"\n            if path == \"\":\n                mylog.warning(\n                    \"For file based iterations, please use absolute file paths!\"\n                )\n                pass\n            for filename in listdir(path):\n                if match(regex, filename):\n                    iterations.append(filename)\n            mylog.info(\"Found %s iterations in directory\", len(iterations))\n\n        if len(iterations) == 0:\n            mylog.warning(\"No iterations found!\")\n        if \"groupBased\" in encoding and len(iterations) > 1:\n            mylog.warning(\"Only chose to load one iteration (%s)\", iteration)\n\n        self.base_path = f\"/data/{iteration}/\"\n        try:\n            self.meshes_path = self._handle[\"/\"].attrs[\"meshesPath\"].decode()\n            handle[self.base_path + self.meshes_path]\n        except KeyError:\n            if self.standard_version <= Version(\"1.1.0\"):\n                mylog.info(\n                    \"meshesPath not present in file. \"\n                    \"Assuming file contains no meshes and has a domain extent of 1m^3!\"\n                )\n                self.meshes_path = None\n            else:\n                raise\n        try:\n            self.particles_path = self._handle[\"/\"].attrs[\"particlesPath\"].decode()\n            handle[self.base_path + self.particles_path]\n        except KeyError:\n            if self.standard_version <= Version(\"1.1.0\"):\n                mylog.info(\n                    \"particlesPath not present in file.\"\n                    \" Assuming file contains no particles!\"\n                )\n                self.particles_path = None\n            else:\n                raise\n\n    def _set_code_unit_attributes(self):\n        \"\"\"Handle conversion between different physical units and the code units.\n\n        Every dataset in openPMD can have different code <-> physical scaling.\n        The individual factor is obtained by multiplying with \"unitSI\" reading getting\n        data from disk.\n        \"\"\"\n        setdefaultattr(self, \"length_unit\", self.quan(1.0, \"m\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"kg\"))\n        setdefaultattr(self, \"time_unit\", self.quan(1.0, \"s\"))\n        setdefaultattr(self, \"velocity_unit\", self.quan(1.0, \"m/s\"))\n        setdefaultattr(self, \"magnetic_unit\", self.quan(1.0, \"T\"))\n\n    def _parse_parameter_file(self):\n        \"\"\"Read in metadata describing the overall data on-disk.\"\"\"\n        f = self._handle\n        bp = self.base_path\n        mp = self.meshes_path\n\n        self.parameters = 0\n        self._periodicity = np.zeros(3, dtype=\"bool\")\n        self.refine_by = 1\n        self.cosmological_simulation = 0\n\n        try:\n            shapes = {}\n            left_edges = {}\n            right_edges = {}\n            meshes = f[bp + mp]\n            for mname in meshes.keys():\n                mesh = meshes[mname]\n                if isinstance(mesh, h5py.Group):\n                    shape = np.asarray(mesh[list(mesh.keys())[0]].shape)\n                else:\n                    shape = np.asarray(mesh.shape)\n                spacing = np.asarray(mesh.attrs[\"gridSpacing\"])\n                offset = np.asarray(mesh.attrs[\"gridGlobalOffset\"])\n                unit_si = np.asarray(mesh.attrs[\"gridUnitSI\"])\n                le = offset * unit_si\n                re = le + shape * unit_si * spacing\n                shapes[mname] = shape\n                left_edges[mname] = le\n                right_edges[mname] = re\n            lowest_dim = np.min([len(i) for i in shapes.values()])\n            shapes = np.asarray([i[:lowest_dim] for i in shapes.values()])\n            left_edges = np.asarray([i[:lowest_dim] for i in left_edges.values()])\n            right_edges = np.asarray([i[:lowest_dim] for i in right_edges.values()])\n            fs = []\n            dle = []\n            dre = []\n            for i in np.arange(lowest_dim):\n                fs.append(np.max(shapes.transpose()[i]))\n                dle.append(np.min(left_edges.transpose()[i]))\n                dre.append(np.min(right_edges.transpose()[i]))\n            self.dimensionality = len(fs)\n            self.domain_dimensions = np.append(fs, np.ones(3 - self.dimensionality))\n            self.domain_left_edge = np.append(dle, np.zeros(3 - len(dle)))\n            self.domain_right_edge = np.append(dre, np.ones(3 - len(dre)))\n        except (KeyError, TypeError, AttributeError):\n            if self.standard_version <= Version(\"1.1.0\"):\n                self.dimensionality = 3\n                self.domain_dimensions = np.ones(3, dtype=np.float64)\n                self.domain_left_edge = np.zeros(3, dtype=np.float64)\n                self.domain_right_edge = np.ones(3, dtype=np.float64)\n            else:\n                raise\n\n        self.current_time = f[bp].attrs[\"time\"] * f[bp].attrs[\"timeUnitSI\"]\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        \"\"\"Checks whether the supplied file can be read by this frontend.\"\"\"\n        if not valid_hdf5_signature(filename):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            with h5py.File(filename, mode=\"r\") as f:\n                attrs = list(f[\"/\"].attrs.keys())\n                for i in opmd_required_attributes:\n                    if i not in attrs:\n                        return False\n\n                if Version(f.attrs[\"openPMD\"].decode()) not in ompd_known_versions:\n                    return False\n\n                if f.attrs[\"iterationEncoding\"].decode() == \"fileBased\":\n                    return True\n\n                return False\n        except OSError:\n            return False\n\n\nclass OpenPMDDatasetSeries(DatasetSeries):\n    _pre_outputs = ()\n    _dataset_cls = OpenPMDDataset\n    parallel = True\n    setup_function = None\n    mixed_dataset_types = False\n\n    def __init__(self, filename):\n        super().__init__([])\n        self.handle = h5py.File(filename, mode=\"r\")\n        self.filename = filename\n        self._pre_outputs = sorted(\n            np.asarray(list(self.handle[\"/data\"].keys()), dtype=\"int64\")\n        )\n\n    def __iter__(self):\n        for it in self._pre_outputs:\n            ds = self._load(it, **self.kwargs)\n            self._setup_function(ds)\n            yield ds\n\n    def __getitem__(self, key):\n        if isinstance(key, int):\n            o = self._load(key)\n            self._setup_function(o)\n            return o\n        else:\n            raise KeyError(f\"Unknown iteration {key}\")\n\n    def _load(self, it, **kwargs):\n        return OpenPMDDataset(self.filename, iteration=it)\n\n\nclass OpenPMDGroupBasedDataset(Dataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = OpenPMDHierarchy\n    _field_info_class = OpenPMDFieldInfo\n\n    def __new__(cls, filename, *args, **kwargs):\n        ret = object.__new__(OpenPMDDatasetSeries)\n        ret.__init__(filename)\n        return ret\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not valid_hdf5_signature(filename):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        try:\n            with h5py.File(filename, mode=\"r\") as f:\n                attrs = list(f[\"/\"].attrs.keys())\n                for i in opmd_required_attributes:\n                    if i not in attrs:\n                        return False\n\n                if Version(f.attrs[\"openPMD\"].decode()) not in ompd_known_versions:\n                    return False\n\n                if f.attrs[\"iterationEncoding\"].decode() == \"groupBased\":\n                    return True\n\n                return False\n        except OSError:\n            return False\n"
  },
  {
    "path": "yt/frontends/open_pmd/definitions.py",
    "content": ""
  },
  {
    "path": "yt/frontends/open_pmd/fields.py",
    "content": "import numpy as np\n\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.fields.magnetic_field import setup_magnetic_field_aliases\nfrom yt.frontends.open_pmd.misc import is_const_component, parse_unit_dimension\nfrom yt.units.yt_array import YTQuantity\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\nfrom yt.utilities.physical_constants import mu_0, speed_of_light\n\n\ndef setup_poynting_vector(self):\n    def _get_poyn(axis):\n        def poynting(data):\n            u = mu_0**-1\n            if axis in \"x\":\n                return u * (\n                    data[\"openPMD\", \"E_y\"] * data[\"gas\", \"magnetic_field_z\"]\n                    - data[\"openPMD\", \"E_z\"] * data[\"gas\", \"magnetic_field_y\"]\n                )\n            elif axis in \"y\":\n                return u * (\n                    data[\"openPMD\", \"E_z\"] * data[\"gas\", \"magnetic_field_x\"]\n                    - data[\"openPMD\", \"E_x\"] * data[\"gas\", \"magnetic_field_z\"]\n                )\n            elif axis in \"z\":\n                return u * (\n                    data[\"openPMD\", \"E_x\"] * data[\"gas\", \"magnetic_field_y\"]\n                    - data[\"openPMD\", \"E_y\"] * data[\"gas\", \"magnetic_field_x\"]\n                )\n\n        return poynting\n\n    for ax in \"xyz\":\n        self.add_field(\n            (\"openPMD\", f\"poynting_vector_{ax}\"),\n            sampling_type=\"cell\",\n            function=_get_poyn(ax),\n            units=\"W/m**2\",\n        )\n\n\ndef setup_kinetic_energy(self, ptype):\n    def _kin_en(data):\n        p2 = (\n            data[ptype, \"particle_momentum_x\"] ** 2\n            + data[ptype, \"particle_momentum_y\"] ** 2\n            + data[ptype, \"particle_momentum_z\"] ** 2\n        )\n        mass = data[ptype, \"particle_mass\"] * data[ptype, \"particle_weighting\"]\n        return (\n            speed_of_light * np.sqrt(p2 + mass**2 * speed_of_light**2)\n            - mass * speed_of_light**2\n        )\n\n    self.add_field(\n        (ptype, \"particle_kinetic_energy\"),\n        sampling_type=\"particle\",\n        function=_kin_en,\n        units=\"kg*m**2/s**2\",\n    )\n\n\ndef setup_velocity(self, ptype):\n    def _get_vel(axis):\n        def velocity(data):\n            c = speed_of_light\n            momentum = data[ptype, f\"particle_momentum_{axis}\"]\n            mass = data[ptype, \"particle_mass\"]\n            weighting = data[ptype, \"particle_weighting\"]\n            return momentum / np.sqrt((mass * weighting) ** 2 + (momentum**2) / (c**2))\n\n        return velocity\n\n    for ax in \"xyz\":\n        self.add_field(\n            (ptype, f\"particle_velocity_{ax}\"),\n            sampling_type=\"particle\",\n            function=_get_vel(ax),\n            units=\"m/s\",\n        )\n\n\ndef setup_absolute_positions(self, ptype):\n    def _abs_pos(axis):\n        def ap(data):\n            return np.add(\n                data[ptype, f\"particle_positionCoarse_{axis}\"],\n                data[ptype, f\"particle_positionOffset_{axis}\"],\n            )\n\n        return ap\n\n    for ax in \"xyz\":\n        self.add_field(\n            (ptype, f\"particle_position_{ax}\"),\n            sampling_type=\"particle\",\n            function=_abs_pos(ax),\n            units=\"m\",\n        )\n\n\nclass OpenPMDFieldInfo(FieldInfoContainer):\n    r\"\"\"Specifies which fields from the dataset yt should know about.\n\n    ``self.known_other_fields`` and ``self.known_particle_fields`` must be populated.\n    Entries for both of these lists must be tuples of the form (\"name\", (\"units\",\n    [\"fields\", \"to\", \"alias\"], \"display_name\")) These fields will be represented and\n    handled in yt in the way you define them here. The fields defined in both\n    ``self.known_other_fields`` and ``self.known_particle_fields`` will only be added to\n    a dataset (with units, aliases, etc), if they match any entry in the\n    ``OpenPMDHierarchy``'s ``self.field_list``.\n\n    Notes\n    -----\n\n    Contrary to many other frontends, we dynamically obtain the known fields from the\n    simulation output. The openPMD markup is extremely flexible - names, dimensions and\n    the number of individual datasets can (and very likely will) vary.\n\n    openPMD states that record names and their components are only allowed to contain\n    * characters a-Z,\n    * the numbers 0-9\n    * and the underscore _\n    * (equivalently, the regex \\w).\n    Since yt widely uses the underscore in field names, openPMD's underscores (_) are\n    replaced by hyphen (-).\n\n    Derived fields will automatically be set up, if names and units of your known\n    on-disk (or manually derived) fields match the ones in [1].\n\n    References\n    ----------\n    * http://yt-project.org/docs/dev/analyzing/fields.html\n    * http://yt-project.org/docs/dev/developing/creating_frontend.html#data-meaning-structures\n    * https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md\n    * [1] http://yt-project.org/docs/dev/reference/field_list.html#universal-fields\n    \"\"\"\n\n    _mag_fields: list[str] = []\n\n    def __init__(self, ds, field_list):\n        f = ds._handle\n        bp = ds.base_path\n        mp = ds.meshes_path\n        pp = ds.particles_path\n\n        try:\n            fields = f[bp + mp]\n            for fname in fields.keys():\n                field = fields[fname]\n                if isinstance(field, h5py.Dataset) or is_const_component(field):\n                    # Don't consider axes.\n                    # This appears to be a vector field of single dimensionality\n                    ytname = str(\"_\".join([fname.replace(\"_\", \"-\")]))\n                    parsed = parse_unit_dimension(\n                        np.asarray(field.attrs[\"unitDimension\"], dtype=\"int64\")\n                    )\n                    unit = str(YTQuantity(1, parsed).units)\n                    aliases = []\n                    # Save a list of magnetic fields for aliasing later on\n                    # We can not reasonably infer field type/unit by name in openPMD\n                    if unit == \"T\" or unit == \"kg/(A*s**2)\":\n                        self._mag_fields.append(ytname)\n                    self.known_other_fields += ((ytname, (unit, aliases, None)),)\n                else:\n                    for axis in field.keys():\n                        ytname = str(\"_\".join([fname.replace(\"_\", \"-\"), axis]))\n                        parsed = parse_unit_dimension(\n                            np.asarray(field.attrs[\"unitDimension\"], dtype=\"int64\")\n                        )\n                        unit = str(YTQuantity(1, parsed).units)\n                        aliases = []\n                        # Save a list of magnetic fields for aliasing later on\n                        # We can not reasonably infer field type by name in openPMD\n                        if unit == \"T\" or unit == \"kg/(A*s**2)\":\n                            self._mag_fields.append(ytname)\n                        self.known_other_fields += ((ytname, (unit, aliases, None)),)\n            for i in self.known_other_fields:\n                mylog.debug(\"open_pmd - known_other_fields - %s\", i)\n        except (KeyError, TypeError, AttributeError):\n            pass\n\n        try:\n            particles = f[bp + pp]\n            for pname in particles.keys():\n                species = particles[pname]\n                for recname in species.keys():\n                    try:\n                        record = species[recname]\n                        parsed = parse_unit_dimension(record.attrs[\"unitDimension\"])\n                        unit = str(YTQuantity(1, parsed).units)\n                        ytattrib = str(recname).replace(\"_\", \"-\")\n                        if ytattrib == \"position\":\n                            # Symbolically rename position to preserve yt's\n                            # interpretation of the pfield particle_position is later\n                            # derived in setup_absolute_positions in the way yt expects\n                            ytattrib = \"positionCoarse\"\n                        if isinstance(record, h5py.Dataset) or is_const_component(\n                            record\n                        ):\n                            name = [\"particle\", ytattrib]\n                            self.known_particle_fields += (\n                                (str(\"_\".join(name)), (unit, [], None)),\n                            )\n                        else:\n                            for axis in record.keys():\n                                aliases = []\n                                name = [\"particle\", ytattrib, axis]\n                                ytname = str(\"_\".join(name))\n                                self.known_particle_fields += (\n                                    (ytname, (unit, aliases, None)),\n                                )\n                    except KeyError:\n                        if recname != \"particlePatches\":\n                            mylog.info(\n                                \"open_pmd - %s_%s does not seem to have unitDimension\",\n                                pname,\n                                recname,\n                            )\n            for i in self.known_particle_fields:\n                mylog.debug(\"open_pmd - known_particle_fields - %s\", i)\n        except (KeyError, TypeError, AttributeError):\n            pass\n\n        super().__init__(ds, field_list)\n\n    def setup_fluid_fields(self):\n        \"\"\"Defines which derived mesh fields to create.\n\n        If a field can not be calculated, it will simply be skipped.\n        \"\"\"\n        # Set up aliases first so the setup for poynting can use them\n        if len(self._mag_fields) > 0:\n            setup_magnetic_field_aliases(self, \"openPMD\", self._mag_fields)\n            setup_poynting_vector(self)\n\n    def setup_particle_fields(self, ptype):\n        \"\"\"Defines which derived particle fields to create.\n\n        This will be called for every entry in\n        `OpenPMDDataset``'s ``self.particle_types``.\n        If a field can not be calculated, it will simply be skipped.\n        \"\"\"\n        setup_absolute_positions(self, ptype)\n        setup_kinetic_energy(self, ptype)\n        setup_velocity(self, ptype)\n        super().setup_particle_fields(ptype)\n"
  },
  {
    "path": "yt/frontends/open_pmd/io.py",
    "content": "from collections import defaultdict\n\nimport numpy as np\n\nfrom yt.frontends.open_pmd.misc import get_component, is_const_component\nfrom yt.geometry.selection_routines import GridSelector\nfrom yt.utilities.io_handler import BaseIOHandler\n\n\nclass IOHandlerOpenPMDHDF5(BaseIOHandler):\n    _field_dtype = \"float32\"\n    _dataset_type = \"openPMD\"\n\n    def __init__(self, ds, *args, **kwargs):\n        self.ds = ds\n        self._handle = ds._handle\n        self.base_path = ds.base_path\n        self.meshes_path = ds.meshes_path\n        self.particles_path = ds.particles_path\n        self._array_fields = {}\n        self._cached_ptype = \"\"\n\n    def _fill_cache(self, ptype, index=0, offset=None):\n        \"\"\"Fills the particle position cache for the ``ptype``.\n\n        Parameters\n        ----------\n        ptype : str\n            The on-disk name of the particle species\n        index : int, optional\n        offset : int, optional\n        \"\"\"\n        if str((ptype, index, offset)) not in self._cached_ptype:\n            self._cached_ptype = str((ptype, index, offset))\n            pds = self._handle[self.base_path + self.particles_path + \"/\" + ptype]\n            axes = list(pds[\"position\"].keys())\n            if offset is None:\n                if is_const_component(pds[\"position/\" + axes[0]]):\n                    offset = pds[\"position/\" + axes[0]].attrs[\"shape\"]\n                else:\n                    offset = pds[\"position/\" + axes[0]].len()\n            self.cache = np.empty((3, offset), dtype=np.float64)\n            for i in np.arange(3):\n                ax = \"xyz\"[i]\n                if ax in axes:\n                    np.add(\n                        get_component(pds, \"position/\" + ax, index, offset),\n                        get_component(pds, \"positionOffset/\" + ax, index, offset),\n                        self.cache[i],\n                    )\n                else:\n                    # Pad accordingly with zeros to make 1D/2D datasets compatible\n                    # These have to be the same shape as the existing axes since that\n                    # equals the number of particles\n                    self.cache[i] = np.zeros(offset)\n\n    def _read_particle_selection(self, chunks, selector, fields):\n        \"\"\"Read particle fields for particle species masked by a selection.\n\n        Parameters\n        ----------\n        chunks\n            A list of chunks\n            A chunk is a list of grids\n        selector\n            A region (inside your domain) specifying which parts of the field\n            you want to read. See [1] and [2]\n        fields : array_like\n            Tuples (ptype, pfield) representing a field\n\n        Returns\n        -------\n        dict\n            keys are tuples (ptype, pfield) representing a field\n            values are (N,) ndarrays with data from that field\n        \"\"\"\n        f = self._handle\n        bp = self.base_path\n        pp = self.particles_path\n        ds = f[bp + pp]\n        unions = self.ds.particle_unions\n        chunks = list(chunks)  # chunks is a generator\n\n        rv = {}\n        ind = {}\n        particle_count = {}\n        ptf = defaultdict(list)  # ParticleTypes&Fields\n        rfm = defaultdict(list)  # RequestFieldMapping\n\n        for ptype, pname in fields:\n            pfield = (ptype, pname)\n            # Overestimate the size of all pfields so they include all particles\n            # and shrink it later\n            particle_count[pfield] = 0\n            if ptype in unions:\n                for pt in unions[ptype]:\n                    particle_count[pfield] += self.ds.particle_type_counts[pt]\n                    ptf[pt].append(pname)\n                    rfm[pt, pname].append(pfield)\n            else:\n                particle_count[pfield] = self.ds.particle_type_counts[ptype]\n                ptf[ptype].append(pname)\n                rfm[pfield].append(pfield)\n            rv[pfield] = np.empty((particle_count[pfield],), dtype=np.float64)\n            ind[pfield] = 0\n\n        for ptype in ptf:\n            for chunk in chunks:\n                for grid in chunk.objs:\n                    if str(ptype) == \"io\":\n                        species = list(ds.keys())[0]\n                    else:\n                        species = ptype\n                    if species not in grid.ptypes:\n                        continue\n                    # read particle coords into cache\n                    self._fill_cache(species, grid.pindex, grid.poffset)\n                    mask = selector.select_points(\n                        self.cache[0], self.cache[1], self.cache[2], 0.0\n                    )\n                    if mask is None:\n                        continue\n                    pds = ds[species]\n                    for field in ptf[ptype]:\n                        component = \"/\".join(field.split(\"_\")[1:])\n                        component = component.replace(\"positionCoarse\", \"position\")\n                        component = component.replace(\"-\", \"_\")\n                        data = get_component(pds, component, grid.pindex, grid.poffset)[\n                            mask\n                        ]\n                        for request_field in rfm[ptype, field]:\n                            rv[request_field][\n                                ind[request_field] : ind[request_field] + data.shape[0]\n                            ] = data\n                            ind[request_field] += data.shape[0]\n\n        for field in fields:\n            rv[field] = rv[field][: ind[field]]\n\n        return rv\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        \"\"\"Reads given fields masked by a given selection.\n\n        Parameters\n        ----------\n        chunks\n            A list of chunks\n            A chunk is a list of grids\n        selector\n            A region (inside your domain) specifying which parts of the field\n            you want to read. See [1] and [2]\n        fields : array_like\n            Tuples (fname, ftype) representing a field\n        size : int\n            Size of the data to read\n\n        Returns\n        -------\n        dict\n            keys are tuples (ftype, fname) representing a field\n            values are flat (``size``,) ndarrays with data from that field\n        \"\"\"\n        f = self._handle\n        bp = self.base_path\n        mp = self.meshes_path\n        ds = f[bp + mp]\n        chunks = list(chunks)\n\n        rv = {}\n        ind = {}\n\n        if isinstance(selector, GridSelector):\n            if not (len(chunks) == len(chunks[0].objs) == 1):\n                raise RuntimeError\n\n        if size is None:\n            size = sum(g.count(selector) for chunk in chunks for g in chunk.objs)\n        for field in fields:\n            rv[field] = np.empty(size, dtype=np.float64)\n            ind[field] = 0\n\n        for ftype, fname in fields:\n            field = (ftype, fname)\n            for chunk in chunks:\n                for grid in chunk.objs:\n                    mask = grid._get_selector_mask(selector)\n                    if mask is None:\n                        continue\n                    component = fname.replace(\"_\", \"/\").replace(\"-\", \"_\")\n                    if component.split(\"/\")[0] not in grid.ftypes:\n                        data = np.full_like(mask, 0)\n                    else:\n                        data = get_component(ds, component, grid.findex, grid.foffset)\n                        # Workaround - casts a 2D (x,y) array to 3D (x,y,1)\n                        data = data.reshape(mask.shape)\n\n                    # The following is a modified AMRGridPatch.select(...)\n                    count = grid.count(selector)\n                    rv[field][ind[field] : ind[field] + count] = data[mask]\n                    ind[field] += count\n\n        for field in fields:\n            rv[field] = rv[field][: ind[field]]\n            rv[field].flatten()\n\n        return rv\n"
  },
  {
    "path": "yt/frontends/open_pmd/misc.py",
    "content": "import numpy as np\n\nfrom yt.utilities.logger import ytLogger as mylog\n\n\ndef parse_unit_dimension(unit_dimension):\n    r\"\"\"Transforms an openPMD unitDimension into a string.\n\n    Parameters\n    ----------\n    unit_dimension : array_like\n        integer array of length 7 with one entry for the dimensional component of every\n        SI unit\n\n        [0] length L,\n        [1] mass M,\n        [2] time T,\n        [3] electric current I,\n        [4] thermodynamic temperature theta,\n        [5] amount of substance N,\n        [6] luminous intensity J\n\n    References\n    ----------\n\n    https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md#unit-systems-and-dimensionality\n\n\n    Returns\n    -------\n    str\n\n    Examples\n    --------\n    >>> velocity = [1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0]\n    >>> print(parse_unit_dimension(velocity))\n    'm**1*s**-1'\n\n    >>> magnetic_field = [0.0, 1.0, -2.0, -1.0, 0.0, 0.0, 0.0]\n    >>> print(parse_unit_dimension(magnetic_field))\n    'kg**1*s**-2*A**-1'\n    \"\"\"\n    if len(unit_dimension) != 7:\n        mylog.error(\"SI must have 7 base dimensions!\")\n    unit_dimension = np.asarray(unit_dimension, dtype=\"int64\")\n    dim = []\n    si = [\"m\", \"kg\", \"s\", \"A\", \"C\", \"mol\", \"cd\"]\n    for i in np.arange(7):\n        if unit_dimension[i] != 0:\n            dim.append(f\"{si[i]}**{unit_dimension[i]}\")\n    return \"*\".join(dim)\n\n\ndef is_const_component(record_component):\n    \"\"\"Determines whether a group or dataset in the HDF5 file is constant.\n\n    Parameters\n    ----------\n    record_component : h5py.Group or h5py.Dataset\n\n    Returns\n    -------\n    bool\n        True if constant, False otherwise\n\n    References\n    ----------\n    .. https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md,\n       section 'Constant Record Components'\n    \"\"\"\n    return \"value\" in record_component.attrs.keys()\n\n\ndef get_component(group, component_name, index=0, offset=None):\n    \"\"\"Grabs a dataset component from a group as a whole or sliced.\n\n    Parameters\n    ----------\n    group : h5py.Group\n    component_name : str\n        relative path of the component in the group\n    index : int, optional\n        first entry along the first axis to read\n    offset : int, optional\n        number of entries to read\n        if not supplied, every entry after index is returned\n\n    Notes\n    -----\n    This scales every entry of the component with the respective \"unitSI\".\n\n    Returns\n    -------\n    ndarray\n        (N,) 1D in case of particle data\n        (O,P,Q) 1D/2D/3D in case of mesh data\n    \"\"\"\n    record_component = group[component_name]\n    unit_si = record_component.attrs[\"unitSI\"]\n    if is_const_component(record_component):\n        shape = np.asarray(record_component.attrs[\"shape\"])\n        if offset is None:\n            shape[0] -= index\n        else:\n            shape[0] = offset\n        # component is constant, craft an array by hand\n        return np.full(shape, record_component.attrs[\"value\"] * unit_si)\n    else:\n        if offset is not None:\n            offset += index\n        # component is a dataset, return it (possibly masked)\n        return np.multiply(record_component[index:offset], unit_si)\n"
  },
  {
    "path": "yt/frontends/open_pmd/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/open_pmd/tests/test_outputs.py",
    "content": "from itertools import product\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal, assert_array_equal, assert_equal\n\nfrom yt.frontends.open_pmd.data_structures import OpenPMDDataset\nfrom yt.loaders import load\nfrom yt.testing import requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import data_dir_load\n\ntwoD = \"example-2d/hdf5/data00000100.h5\"\nthreeD = \"example-3d/hdf5/data00000100.h5\"\nnoFields = \"no_fields/data00000400.h5\"\nnoParticles = \"no_particles/data00000400.h5\"\ngroupBased = \"singleParticle/simData.h5\"\n\nparticle_fields = [\n    \"particle_charge\",\n    \"particle_mass\",\n    \"particle_momentum_x\",\n    \"particle_momentum_y\",\n    \"particle_momentum_z\",\n    \"particle_positionCoarse_x\",\n    \"particle_positionCoarse_y\",\n    \"particle_positionCoarse_z\",\n    \"particle_positionOffset_x\",\n    \"particle_positionOffset_y\",\n    \"particle_positionOffset_z\",\n    \"particle_weighting\",\n]\n\n\n@requires_module(\"h5py\")\n@requires_file(threeD)\ndef test_3d_out():\n    ds = data_dir_load(threeD)\n    particle_types = [\"all\", \"io\", \"nbody\"]\n    field_list = list(product(particle_types, particle_fields))\n    field_list += list(product((\"openPMD\",), (\"E_x\", \"E_y\", \"E_z\", \"rho\")))\n    domain_dimensions = [26, 26, 201] * np.ones_like(ds.domain_dimensions)\n    domain_width = [2.08e-05, 2.08e-05, 2.01e-05] * np.ones_like(ds.domain_left_edge)\n\n    assert isinstance(ds, OpenPMDDataset)\n    assert_equal(str(ds), \"data00000100.h5\")\n    assert_equal(ds.dimensionality, 3)\n    assert_equal(ds.particle_types_raw, (\"io\",))\n    assert \"all\" in ds.particle_unions\n    assert_array_equal(ds.field_list, field_list)\n    assert_array_equal(ds.domain_dimensions, domain_dimensions)\n    assert_almost_equal(\n        ds.current_time, 3.28471214521e-14 * np.ones_like(ds.current_time)\n    )\n    assert_almost_equal(ds.domain_right_edge - ds.domain_left_edge, domain_width)\n\n\n@requires_module(\"h5py\")\n@requires_file(twoD)\ndef test_2d_out():\n    ds = data_dir_load(twoD)\n    particle_types = (\"Hydrogen1+\", \"all\", \"electrons\", \"nbody\")\n    field_list = list(product(particle_types, particle_fields))\n    field_list += [\n        (\"openPMD\", \"B_x\"),\n        (\"openPMD\", \"B_y\"),\n        (\"openPMD\", \"B_z\"),\n        (\"openPMD\", \"E_x\"),\n        (\"openPMD\", \"E_y\"),\n        (\"openPMD\", \"E_z\"),\n        (\"openPMD\", \"J_x\"),\n        (\"openPMD\", \"J_y\"),\n        (\"openPMD\", \"J_z\"),\n        (\"openPMD\", \"rho\"),\n    ]\n    domain_dimensions = [51, 201, 1] * np.ones_like(ds.domain_dimensions)\n    domain_width = [3.06e-05, 2.01e-05, 1e0] * np.ones_like(ds.domain_left_edge)\n\n    assert isinstance(ds, OpenPMDDataset)\n    assert_equal(str(ds), \"data00000100.h5\")\n    assert_equal(ds.dimensionality, 2)\n    assert_equal(ds.particle_types_raw, (\"Hydrogen1+\", \"electrons\"))\n    assert \"all\" in ds.particle_unions\n    assert_array_equal(ds.field_list, field_list)\n    assert_array_equal(ds.domain_dimensions, domain_dimensions)\n    assert_almost_equal(\n        ds.current_time, 3.29025596712e-14 * np.ones_like(ds.current_time)\n    )\n    assert_almost_equal(ds.domain_right_edge - ds.domain_left_edge, domain_width)\n\n\n@requires_module(\"h5py\")\n@requires_file(noFields)\ndef test_no_fields_out():\n    ds = data_dir_load(noFields)\n    particle_types = (\"all\", \"io\", \"nbody\")\n    no_fields_pfields = sorted(particle_fields + [\"particle_id\"])\n    field_list = list(product(particle_types, no_fields_pfields))\n    domain_dimensions = [1, 1, 1] * np.ones_like(ds.domain_dimensions)\n    domain_width = [1, 1, 1] * np.ones_like(ds.domain_left_edge)\n\n    assert isinstance(ds, OpenPMDDataset)\n    assert_equal(str(ds), \"data00000400.h5\")\n    assert_equal(ds.dimensionality, 3)\n    assert_equal(ds.particle_types_raw, (\"io\",))\n    assert \"all\" in ds.particle_unions\n    assert_array_equal(ds.field_list, field_list)\n    assert_array_equal(ds.domain_dimensions, domain_dimensions)\n    assert_almost_equal(\n        ds.current_time, 1.3161023868481013e-13 * np.ones_like(ds.current_time)\n    )\n    assert_almost_equal(ds.domain_right_edge - ds.domain_left_edge, domain_width)\n\n\n@requires_module(\"h5py\")\n@requires_file(noParticles)\ndef test_no_particles_out():\n    ds = data_dir_load(noParticles)\n    field_list = [\n        (\"openPMD\", \"E_x\"),\n        (\"openPMD\", \"E_y\"),\n        (\"openPMD\", \"E_z\"),\n        (\"openPMD\", \"rho\"),\n    ]\n    domain_dimensions = [51, 201, 1] * np.ones_like(ds.domain_dimensions)\n    domain_width = [3.06e-05, 2.01e-05, 1e0] * np.ones_like(ds.domain_left_edge)\n\n    assert isinstance(ds, OpenPMDDataset)\n    assert_equal(str(ds), \"data00000400.h5\")\n    assert_equal(ds.dimensionality, 2)\n    assert_equal(ds.particle_types_raw, (\"io\",))\n    assert \"all\" not in ds.particle_unions\n    assert_array_equal(ds.field_list, field_list)\n    assert_array_equal(ds.domain_dimensions, domain_dimensions)\n    assert_almost_equal(\n        ds.current_time, 1.3161023868481013e-13 * np.ones_like(ds.current_time)\n    )\n    assert_almost_equal(ds.domain_right_edge - ds.domain_left_edge, domain_width)\n\n\n@requires_module(\"h5py\")\n@requires_file(groupBased)\ndef test_groupBased_out():\n    dss = load(groupBased)\n    particle_types = (\"all\", \"io\", \"nbody\")\n    field_list = list(product(particle_types, particle_fields))\n    field_list += [\n        (\"openPMD\", \"J_x\"),\n        (\"openPMD\", \"J_y\"),\n        (\"openPMD\", \"J_z\"),\n        (\"openPMD\", \"e-chargeDensity\"),\n    ]\n    domain_dimensions = [32, 64, 64] * np.ones_like(dss[0].domain_dimensions)\n    domain_width = [0.0002752, 0.0005504, 0.0005504] * np.ones_like(\n        dss[0].domain_left_edge\n    )\n\n    assert_equal(len(dss), 101)\n    for i in range(0, len(dss), 20):  # Test only every 20th ds out of the series\n        ds = dss[i]\n        assert_equal(str(ds), \"simData.h5\")\n        assert_equal(ds.dimensionality, 3)\n        assert_equal(ds.particle_types_raw, (\"io\",))\n        assert_array_equal(ds.field_list, field_list)\n        assert_array_equal(ds.domain_dimensions, domain_dimensions)\n        assert ds.current_time >= np.zeros_like(ds.current_time)\n        assert ds.current_time <= 1.6499999999999998e-12 * np.ones_like(ds.current_time)\n        assert_almost_equal(ds.domain_right_edge - ds.domain_left_edge, domain_width)\n"
  },
  {
    "path": "yt/frontends/owls/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/owls/api.py",
    "content": "from . import tests\nfrom .data_structures import OWLSDataset\nfrom .fields import OWLSFieldInfo\nfrom .io import IOHandlerOWLS\nfrom .simulation_handling import OWLSSimulation\n"
  },
  {
    "path": "yt/frontends/owls/data_structures.py",
    "content": "import os\n\nimport yt.units\nfrom yt.frontends.gadget.data_structures import GadgetHDF5Dataset\nfrom yt.utilities.definitions import sec_conversion\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import OWLSFieldInfo\n\n\nclass OWLSDataset(GadgetHDF5Dataset):\n    _load_requirements = [\"h5py\"]\n    _particle_mass_name = \"Mass\"\n    _field_info_class = OWLSFieldInfo\n    _time_readin = \"Time_GYR\"\n\n    def _parse_parameter_file(self):\n        # read values from header\n        hvals = self._get_hvals()\n        self.parameters = hvals\n\n        # set features common to OWLS and Eagle\n        self._set_owls_eagle()\n\n        # Set time from value in header\n        self.current_time = (\n            hvals[self._time_readin] * sec_conversion[\"Gyr\"] * yt.units.s\n        )\n\n    def _set_code_unit_attributes(self):\n        self._set_owls_eagle_units()\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        need_groups = [\"Constants\", \"Header\", \"Parameters\", \"Units\"]\n        veto_groups = [\n            \"SUBFIND\",\n            \"FOF\",\n            \"PartType0/ChemistryAbundances\",\n            \"PartType0/ChemicalAbundances\",\n            \"RuntimePars\",\n            \"HashTable\",\n        ]\n        valid = True\n        valid_fname = filename\n        # If passed arg is a directory, look for the .0 file in that dir\n        if os.path.isdir(filename):\n            valid_files = []\n            for f in os.listdir(filename):\n                fname = os.path.join(filename, f)\n                fext = os.path.splitext(fname)[-1]\n                if (\n                    (\".0\" in f)\n                    and (fext not in {\".ewah\", \".kdtree\"})\n                    and os.path.isfile(fname)\n                ):\n                    valid_files.append(fname)\n            if len(valid_files) == 0:\n                valid = False\n            elif len(valid_files) > 1:\n                valid = False\n            else:\n                valid_fname = valid_files[0]\n        try:\n            fileh = h5py.File(valid_fname, mode=\"r\")\n            for ng in need_groups:\n                if ng not in fileh[\"/\"]:\n                    valid = False\n            for vg in veto_groups:\n                if vg in fileh[\"/\"]:\n                    valid = False\n            fileh.close()\n        except Exception:\n            valid = False\n        return valid\n"
  },
  {
    "path": "yt/frontends/owls/fields.py",
    "content": "import os\n\nimport numpy as np\n\nfrom yt.config import ytcfg\nfrom yt.fields.species_fields import (\n    add_species_field_by_density,\n    add_species_field_by_fraction,\n)\nfrom yt.frontends.sph.fields import SPHFieldInfo\nfrom yt.funcs import download_file, mylog\n\nfrom . import owls_ion_tables as oit\n\n\ndef _get_ion_mass_frac(ion, ftype, itab, data):\n    # get element symbol from ion string. ion string will\n    # be a member of the tuple _ions (i.e. si13)\n    # --------------------------------------------------------\n    if ion[0:2].isalpha():\n        symbol = ion[0:2].capitalize()\n    else:\n        symbol = ion[0:1].capitalize()\n\n    # mass fraction for the element\n    # --------------------------------------------------------\n    m_frac = data[ftype, symbol + \"_fraction\"]\n\n    # get nH and T for lookup\n    # --------------------------------------------------------\n    log_nH = np.log10(data[\"PartType0\", \"H_number_density\"])\n    log_T = np.log10(data[\"PartType0\", \"Temperature\"])\n\n    # get name of owls_ion_file for given ion\n    # --------------------------------------------------------\n    itab.set_iz(data.ds.current_redshift)\n\n    # find ion balance using log nH and log T\n    # --------------------------------------------------------\n    i_frac = itab.interp(log_nH, log_T)\n\n    return i_frac, m_frac\n\n\nclass OWLSFieldInfo(SPHFieldInfo):\n    _ions: tuple[str, ...] = (\n        \"c1\",\n        \"c2\",\n        \"c3\",\n        \"c4\",\n        \"c5\",\n        \"c6\",\n        \"fe2\",\n        \"fe17\",\n        \"h1\",\n        \"he1\",\n        \"he2\",\n        \"mg1\",\n        \"mg2\",\n        \"n2\",\n        \"n3\",\n        \"n4\",\n        \"n5\",\n        \"n6\",\n        \"n7\",\n        \"ne8\",\n        \"ne9\",\n        \"ne10\",\n        \"o1\",\n        \"o6\",\n        \"o7\",\n        \"o8\",\n        \"si2\",\n        \"si3\",\n        \"si4\",\n        \"si13\",\n    )\n\n    _elements = (\"H\", \"He\", \"C\", \"N\", \"O\", \"Ne\", \"Mg\", \"Si\", \"Fe\")\n\n    _num_neighbors = 48\n\n    _add_elements = (\"PartType0\", \"PartType4\")\n\n    _add_ions = \"PartType0\"\n\n    def __init__(self, ds, field_list, slice_info=None):\n        new_particle_fields = (\n            (\"Hydrogen\", (\"\", [\"H_fraction\"], None)),\n            (\"Helium\", (\"\", [\"He_fraction\"], None)),\n            (\"Carbon\", (\"\", [\"C_fraction\"], None)),\n            (\"Nitrogen\", (\"\", [\"N_fraction\"], None)),\n            (\"Oxygen\", (\"\", [\"O_fraction\"], None)),\n            (\"Neon\", (\"\", [\"Ne_fraction\"], None)),\n            (\"Magnesium\", (\"\", [\"Mg_fraction\"], None)),\n            (\"Silicon\", (\"\", [\"Si_fraction\"], None)),\n            (\"Iron\", (\"\", [\"Fe_fraction\"], None)),\n        )\n\n        if ds.gen_hsmls:\n            new_particle_fields += ((\"smoothing_length\", (\"code_length\", [], None)),)\n\n        self.known_particle_fields += new_particle_fields\n\n        super().__init__(ds, field_list, slice_info=slice_info)\n\n        # This enables the machinery in yt.fields.species_fields\n        self.species_names += list(self._elements)\n\n    def setup_particle_fields(self, ptype):\n        \"\"\"additional particle fields derived from those in snapshot.\n        we also need to add the smoothed fields here b/c setup_fluid_fields\n        is called before setup_particle_fields.\"\"\"\n\n        smoothed_suffixes = (\"_number_density\", \"_density\", \"_mass\")\n\n        # we add particle element fields for stars and gas\n        # -----------------------------------------------------\n        if ptype in self._add_elements:\n            # this adds the particle element fields\n            # X_density, X_mass, and X_number_density\n            # where X is an item of self._elements.\n            # X_fraction are defined in snapshot\n            # -----------------------------------------------\n            for s in self._elements:\n                field_names = add_species_field_by_fraction(self, ptype, s)\n                if ptype == self.ds._sph_ptypes[0]:\n                    for fn in field_names:\n                        self.alias((\"gas\", fn[1]), fn)\n\n        # this needs to be called after the call to\n        # add_species_field_by_fraction for some reason ...\n        # not sure why yet.\n        # -------------------------------------------------------\n        if ptype == \"PartType0\":\n            ftype = \"gas\"\n        else:\n            ftype = ptype\n\n        super().setup_particle_fields(\n            ptype, num_neighbors=self._num_neighbors, ftype=ftype\n        )\n\n        # and now we add the smoothed versions for PartType0\n        # -----------------------------------------------------\n        if ptype == \"PartType0\":\n            # we only add ion fields for gas.  this takes some\n            # time as the ion abundances have to be interpolated\n            # from cloudy tables (optically thin)\n            # -----------------------------------------------------\n\n            # this defines the ion density on particles\n            # X_density for all items in self._ions\n            # -----------------------------------------------\n            self.setup_gas_ion_particle_fields(ptype)\n\n            # this adds the rest of the ion particle fields\n            # X_fraction, X_mass, X_number_density\n            # -----------------------------------------------\n            for ion in self._ions:\n                # construct yt name for ion\n                # ---------------------------------------------------\n                if ion[0:2].isalpha():\n                    symbol = ion[0:2].capitalize()\n                    roman = int(ion[2:])\n                else:\n                    symbol = ion[0:1].capitalize()\n                    roman = int(ion[1:])\n\n                if (ptype, symbol + \"_fraction\") not in self.field_aliases:\n                    continue\n\n                pstr = f\"_p{roman - 1}\"\n                yt_ion = symbol + pstr\n\n                # add particle field\n                # ---------------------------------------------------\n                add_species_field_by_density(self, ptype, yt_ion)\n\n            def _h_p1_density(data):\n                return data[ptype, \"H_density\"] - data[ptype, \"H_p0_density\"]\n\n            self.add_field(\n                (ptype, \"H_p1_density\"),\n                sampling_type=\"particle\",\n                function=_h_p1_density,\n                units=self.ds.unit_system[\"density\"],\n            )\n\n            add_species_field_by_density(self, ptype, \"H_p1\")\n            for sfx in [\"mass\", \"density\", \"number_density\"]:\n                fname = \"H_p1_\" + sfx\n                self.alias((\"gas\", fname), (ptype, fname))\n\n            def _el_number_density(data):\n                n_e = data[ptype, \"H_p1_number_density\"]\n                n_e += data[ptype, \"He_p1_number_density\"]\n                n_e += 2.0 * data[ptype, \"He_p2_number_density\"]\n                return n_e\n\n            self.add_field(\n                (ptype, \"El_number_density\"),\n                sampling_type=\"particle\",\n                function=_el_number_density,\n                units=self.ds.unit_system[\"number_density\"],\n            )\n            self.alias((\"gas\", \"El_number_density\"), (ptype, \"El_number_density\"))\n\n            # alias ion fields\n            # -----------------------------------------------\n            for ion in self._ions:\n                # construct yt name for ion\n                # ---------------------------------------------------\n                if ion[0:2].isalpha():\n                    symbol = ion[0:2].capitalize()\n                    roman = int(ion[2:])\n                else:\n                    symbol = ion[0:1].capitalize()\n                    roman = int(ion[1:])\n\n                if (ptype, symbol + \"_fraction\") not in self.field_aliases:\n                    continue\n\n                pstr = f\"_p{roman - 1}\"\n                yt_ion = symbol + pstr\n\n                for sfx in smoothed_suffixes:\n                    fname = yt_ion + sfx\n                    self.alias((\"gas\", fname), (ptype, fname))\n\n    def setup_gas_ion_particle_fields(self, ptype):\n        \"\"\"Sets up particle fields for gas ion densities.\"\"\"\n\n        # loop over all ions and make fields\n        # ----------------------------------------------\n        for ion in self._ions:\n            # construct yt name for ion\n            # ---------------------------------------------------\n            if ion[0:2].isalpha():\n                symbol = ion[0:2].capitalize()\n                roman = int(ion[2:])\n            else:\n                symbol = ion[0:1].capitalize()\n                roman = int(ion[1:])\n\n            if (ptype, symbol + \"_fraction\") not in self.field_aliases:\n                continue\n\n            pstr = f\"_p{roman - 1}\"\n            yt_ion = symbol + pstr\n            ftype = ptype\n\n            # add ion density and mass field for this species\n            # ------------------------------------------------\n            fname = yt_ion + \"_density\"\n            dens_func = self._create_ion_density_func(ftype, ion)\n            self.add_field(\n                (ftype, fname),\n                sampling_type=\"particle\",\n                function=dens_func,\n                units=self.ds.unit_system[\"density\"],\n            )\n            self._show_field_errors.append((ftype, fname))\n\n            fname = yt_ion + \"_mass\"\n            mass_func = self._create_ion_mass_func(ftype, ion)\n            self.add_field(\n                (ftype, fname),\n                sampling_type=\"particle\",\n                function=mass_func,\n                units=self.ds.unit_system[\"mass\"],\n            )\n            self._show_field_errors.append((ftype, fname))\n\n    def _create_ion_density_func(self, ftype, ion):\n        \"\"\"returns a function that calculates the ion density of a particle.\"\"\"\n\n        def get_owls_ion_density_field(ion, ftype, itab):\n            def _func(data):\n                m_frac, i_frac = _get_ion_mass_frac(ion, ftype, itab, data)\n                return data[ftype, \"Density\"] * m_frac * i_frac\n\n            return _func\n\n        ion_path = self._get_owls_ion_data_dir()\n        fname = os.path.join(ion_path, ion + \".hdf5\")\n        itab = oit.IonTableOWLS(fname)\n        return get_owls_ion_density_field(ion, ftype, itab)\n\n    def _create_ion_mass_func(self, ftype, ion):\n        \"\"\"returns a function that calculates the ion mass of a particle\"\"\"\n\n        def get_owls_ion_mass_field(ion, ftype, itab):\n            def _func(data):\n                m_frac, i_frac = _get_ion_mass_frac(ion, ftype, itab, data)\n                return data[ftype, \"particle_mass\"] * m_frac * i_frac\n\n            return _func\n\n        ion_path = self._get_owls_ion_data_dir()\n        fname = os.path.join(ion_path, ion + \".hdf5\")\n        itab = oit.IonTableOWLS(fname)\n        return get_owls_ion_mass_field(ion, ftype, itab)\n\n    # this function sets up the X_mass, X_density, X_fraction, and\n    # X_number_density fields where X is the name of an OWLS element.\n    # -------------------------------------------------------------\n    def setup_fluid_fields(self):\n        return\n\n    # this function returns the owls_ion_data directory. if it doesn't\n    # exist it will download the data from http://yt-project.org/data\n    # -------------------------------------------------------------\n    def _get_owls_ion_data_dir(self):\n        txt = \"Attempting to download ~ 30 Mb of owls ion data from %s to %s.\"\n        data_file = \"owls_ion_data.tar.gz\"\n        data_url = \"http://yt-project.org/data\"\n\n        # get test_data_dir from yt config (ytcgf)\n        # ----------------------------------------------\n        tdir = ytcfg.get(\"yt\", \"test_data_dir\")\n\n        # set download destination to tdir or ./ if tdir isn't defined\n        # ----------------------------------------------\n        if tdir == \"/does/not/exist\":\n            data_dir = \"./\"\n        else:\n            data_dir = tdir\n\n        # check for owls_ion_data directory in data_dir\n        # if not there download the tarball and untar it\n        # ----------------------------------------------\n        owls_ion_path = os.path.join(data_dir, \"owls_ion_data\")\n\n        if not os.path.exists(owls_ion_path):\n            mylog.info(txt, data_url, data_dir)\n            fname = os.path.join(data_dir, data_file)\n            download_file(os.path.join(data_url, data_file), fname)\n\n            cmnd = f\"cd {data_dir}; tar xf {data_file}\"\n            os.system(cmnd)\n\n        if not os.path.exists(owls_ion_path):\n            raise RuntimeError(\"Failed to download owls ion data.\")\n\n        return owls_ion_path\n"
  },
  {
    "path": "yt/frontends/owls/io.py",
    "content": "from yt.frontends.gadget.io import IOHandlerGadgetHDF5\n\n\nclass IOHandlerOWLS(IOHandlerGadgetHDF5):\n    _dataset_type = \"OWLS\"\n"
  },
  {
    "path": "yt/frontends/owls/owls_ion_tables.py",
    "content": "import numpy as np\n\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\ndef h5rd(fname, path, dtype=None):\n    \"\"\"Read Data. Return a dataset located at <path> in file <fname> as\n    a numpy array.\n    e.g. rd( fname, '/PartType0/Coordinates' ).\"\"\"\n\n    data = None\n    fid = h5py.h5f.open(fname.encode(\"latin-1\"), h5py.h5f.ACC_RDONLY)\n    dg = h5py.h5d.open(fid, path.encode(\"ascii\"))\n    if dtype is None:\n        dtype = dg.dtype\n    data = np.zeros(dg.shape, dtype=dtype)\n    dg.read(h5py.h5s.ALL, h5py.h5s.ALL, data)\n    fid.close()\n    return data\n\n\nclass IonTableSpectrum:\n    \"\"\"A class to handle the HM01 spectra in the OWLS ionization tables.\"\"\"\n\n    def __init__(self, ion_file):\n        where = \"/header/spectrum/gammahi\"\n        self.GH1 = h5rd(ion_file, where)  # GH1[1/s]\n\n        where = \"/header/spectrum/logenergy_ryd\"\n        self.logryd = h5rd(ion_file, where)  # E[ryd]\n\n        where = \"/header/spectrum/logflux\"\n        self.logflux = h5rd(ion_file, where)  # J[ergs/s/Hz/Sr/cm^2]\n\n        where = \"/header/spectrum/redshift\"\n        self.z = h5rd(ion_file, where)  # z\n\n    def return_table_GH1_at_z(self, z):\n        # find redshift indices\n        # -----------------------------------------------------------------\n        i_zlo = np.argmin(np.abs(self.z - z))\n        if self.z[i_zlo] < z:\n            i_zhi = i_zlo + 1\n        else:\n            i_zhi = i_zlo\n            i_zlo = i_zlo - 1\n\n        z_frac = (z - self.z[i_zlo]) / (self.z[i_zhi] - self.z[i_zlo])\n\n        # find GH1 from table\n        # -----------------------------------------------------------------\n        logGH1_all = np.log10(self.GH1)\n        dlog_GH1 = logGH1_all[i_zhi] - logGH1_all[i_zlo]\n\n        logGH1_table = logGH1_all[i_zlo] + z_frac * dlog_GH1\n        GH1_table = 10.0**logGH1_table\n\n        return GH1_table\n\n\nclass IonTableOWLS:\n    \"\"\"A class to handle OWLS ionization tables.\"\"\"\n\n    DELTA_nH = 0.25\n    DELTA_T = 0.1\n\n    def __init__(self, ion_file):\n        self.ion_file = ion_file\n\n        # ionbal is indexed like [nH, T, z]\n        # nH and T are log quantities\n        # ---------------------------------------------------------------\n        self.nH = h5rd(ion_file, \"/logd\")  # log nH [cm^-3]\n        self.T = h5rd(ion_file, \"/logt\")  # log T [K]\n        self.z = h5rd(ion_file, \"/redshift\")  # z\n\n        # read the ionization fractions\n        # linear values stored in file so take log here\n        # ionbal is the ionization balance (i.e. fraction)\n        # ---------------------------------------------------------------\n        self.ionbal = h5rd(ion_file, \"/ionbal\").astype(np.float64)\n        self.ionbal_orig = self.ionbal.copy()\n\n        ipositive = self.ionbal > 0.0\n        izero = np.logical_not(ipositive)\n        self.ionbal[izero] = self.ionbal[ipositive].min()\n\n        self.ionbal = np.log10(self.ionbal)\n\n        # load in background spectrum\n        # ---------------------------------------------------------------\n        self.spectrum = IonTableSpectrum(ion_file)\n\n        # calculate the spacing along each dimension\n        # ---------------------------------------------------------------\n        self.dnH = self.nH[1:] - self.nH[0:-1]\n        self.dT = self.T[1:] - self.T[0:-1]\n        self.dz = self.z[1:] - self.z[0:-1]\n\n        self.order_str = \"[log nH, log T, z]\"\n\n    # sets iz and fz\n    # -----------------------------------------------------\n    def set_iz(self, z):\n        if z <= self.z[0]:\n            self.iz = 0\n            self.fz = 0.0\n        elif z >= self.z[-1]:\n            self.iz = len(self.z) - 2\n            self.fz = 1.0\n        else:\n            for iz in range(len(self.z) - 1):\n                if z < self.z[iz + 1]:\n                    self.iz = iz\n                    self.fz = (z - self.z[iz]) / self.dz[iz]\n                    break\n\n    # interpolate the table at a fixed redshift for the input\n    # values of nH and T ( input should be log ).  A simple\n    # tri-linear interpolation is used.\n    # -----------------------------------------------------\n    def interp(self, nH, T):\n        nH = np.array(nH)\n        T = np.array(T)\n\n        if nH.size != T.size:\n            raise ValueError(\" owls_ion_tables: array size mismatch !!! \")\n\n        # field discovery will have nH.size == 1 and T.size == 1\n        # in that case we simply return 1.0\n\n        if nH.size == 1 and T.size == 1:\n            ionfrac = 1.0\n            return ionfrac\n\n        # find inH and fnH\n        # -----------------------------------------------------\n        x_nH = (nH - self.nH[0]) / self.DELTA_nH\n        x_nH_clip = np.clip(x_nH, 0.0, self.nH.size - 1.001)\n        fnH, inH = np.modf(x_nH_clip)\n        inH = inH.astype(np.int32)\n\n        # find iT and fT\n        # -----------------------------------------------------\n        x_T = (T - self.T[0]) / self.DELTA_T\n        x_T_clip = np.clip(x_T, 0.0, self.T.size - 1.001)\n        fT, iT = np.modf(x_T_clip)\n        iT = iT.astype(np.int32)\n\n        # short names for previously calculated iz and fz\n        # -----------------------------------------------------\n        iz = self.iz\n        fz = self.fz\n\n        # calculate interpolated value\n        # use tri-linear interpolation on the log values\n        # -----------------------------------------------------\n\n        ionfrac = (\n            self.ionbal[inH, iT, iz] * (1 - fnH) * (1 - fT) * (1 - fz)\n            + self.ionbal[inH + 1, iT, iz] * (fnH) * (1 - fT) * (1 - fz)\n            + self.ionbal[inH, iT + 1, iz] * (1 - fnH) * (fT) * (1 - fz)\n            + self.ionbal[inH, iT, iz + 1] * (1 - fnH) * (1 - fT) * (fz)\n            + self.ionbal[inH + 1, iT, iz + 1] * (fnH) * (1 - fT) * (fz)\n            + self.ionbal[inH, iT + 1, iz + 1] * (1 - fnH) * (fT) * (fz)\n            + self.ionbal[inH + 1, iT + 1, iz] * (fnH) * (fT) * (1 - fz)\n            + self.ionbal[inH + 1, iT + 1, iz + 1] * (fnH) * (fT) * (fz)\n        )\n\n        return 10**ionfrac\n"
  },
  {
    "path": "yt/frontends/owls/simulation_handling.py",
    "content": "import os\n\nfrom yt.frontends.gadget.simulation_handling import GadgetSimulation\n\n\nclass OWLSSimulation(GadgetSimulation):\n    r\"\"\"\n    Initialize an OWLS Simulation object.\n\n    Upon creation, the parameter file is parsed and the time and redshift\n    are calculated and stored in all_outputs.  A time units dictionary is\n    instantiated to allow for time outputs to be requested with physical\n    time units.  The get_time_series can be used to generate a\n    DatasetSeries object.\n\n    parameter_filename : str\n        The simulation parameter file.\n    find_outputs : bool\n        If True, the OutputDir directory is searched for datasets.\n        Time and redshift information are gathered by temporarily\n        instantiating each dataset.  This can be used when simulation\n        data was created in a non-standard way, making it difficult\n        to guess the corresponding time and redshift information.\n        Default: False.\n\n    Examples\n    --------\n    >>> import yt\n    >>> es = yt.load_simulation(\"my_simulation.par\", \"OWLS\")\n    >>> es.get_time_series()\n    >>> for ds in es:\n    ...     print(ds.current_time)\n\n    \"\"\"\n\n    def __init__(self, parameter_filename, find_outputs=False):\n        GadgetSimulation.__init__(self, parameter_filename, find_outputs=find_outputs)\n\n    def _snapshot_format(self, index=None):\n        \"\"\"\n        The snapshot filename for a given index.  Modify this for different\n        naming conventions.\n        \"\"\"\n\n        if self.parameters[\"OutputDir\"].startswith(\"/\"):\n            data_dir = self.parameters[\"OutputDir\"]\n        else:\n            data_dir = os.path.join(self.directory, self.parameters[\"OutputDir\"])\n        if self.parameters[\"NumFilesPerSnapshot\"] > 1:\n            suffix = \".0\"\n        else:\n            suffix = \"\"\n        if self.parameters[\"SnapFormat\"] == 3:\n            suffix += \".hdf5\"\n        if index is None:\n            count = \"*\"\n        else:\n            count = f\"{index:03}\"\n        keyword = f\"{self.parameters['SnapshotFileBase']}_{count}\"\n        filename = os.path.join(keyword, f\"{keyword}{suffix}\")\n        return os.path.join(data_dir, filename)\n"
  },
  {
    "path": "yt/frontends/owls/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/owls/tests/test_outputs.py",
    "content": "from collections import OrderedDict\n\nfrom yt.data_objects.particle_filters import add_particle_filter\nfrom yt.frontends.owls.api import OWLSDataset\nfrom yt.testing import ParticleSelectionComparison, requires_file, requires_module\nfrom yt.utilities.answer_testing.framework import data_dir_load, requires_ds, sph_answer\n\nos33 = \"snapshot_033/snap_033.0.hdf5\"\n\n# This maps from field names to weight field names to use for projections\n_fields = OrderedDict(\n    [\n        ((\"gas\", \"density\"), None),\n        ((\"gas\", \"temperature\"), None),\n        ((\"gas\", \"temperature\"), (\"gas\", \"density\")),\n        ((\"gas\", \"He_p0_number_density\"), None),\n        ((\"gas\", \"velocity_magnitude\"), None),\n    ]\n)\n\n\n@requires_module(\"h5py\")\n@requires_ds(os33, big_data=True)\ndef test_snapshot_033():\n    ds = data_dir_load(os33)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n    for test in sph_answer(ds, \"snap_033\", 2 * 128**3, _fields):\n        test_snapshot_033.__name__ = test.description\n        yield test\n\n\n@requires_module(\"h5py\")\n@requires_file(os33)\ndef test_OWLSDataset():\n    assert isinstance(data_dir_load(os33), OWLSDataset)\n\n\n@requires_module(\"h5py\")\n@requires_ds(os33)\ndef test_OWLS_particlefilter():\n    ds = data_dir_load(os33)\n    ad = ds.all_data()\n\n    def cold_gas(pfilter, data):\n        temperature = data[pfilter.filtered_type, \"Temperature\"]\n        filter = temperature.in_units(\"K\") <= 1e5\n        return filter\n\n    add_particle_filter(\n        \"gas_cold\",\n        function=cold_gas,\n        filtered_type=\"PartType0\",\n        requires=[\"Temperature\"],\n    )\n    ds.add_particle_filter(\"gas_cold\")\n\n    mask = ad[\"PartType0\", \"Temperature\"] <= 1e5\n    assert (\n        ad[\"PartType0\", \"Temperature\"][mask].shape\n        == ad[\"gas_cold\", \"Temperature\"].shape\n    )\n"
  },
  {
    "path": "yt/frontends/owls_subfind/__init__.py",
    "content": "\"\"\"\nAPI for HaloCatalog frontend.\n\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/owls_subfind/api.py",
    "content": "from . import tests\nfrom .data_structures import OWLSSubfindDataset\nfrom .fields import OWLSSubfindFieldInfo\nfrom .io import IOHandlerOWLSSubfindHDF5\n"
  },
  {
    "path": "yt/frontends/owls_subfind/data_structures.py",
    "content": "import glob\nimport os\nfrom collections import defaultdict\n\nimport numpy as np\n\nfrom yt.data_objects.static_output import ParticleDataset, ParticleFile\nfrom yt.frontends.gadget.data_structures import _fix_unit_ordering\nfrom yt.funcs import only_on_root, setdefaultattr\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.utilities.exceptions import YTException\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import OWLSSubfindFieldInfo\n\n\nclass OWLSSubfindParticleIndex(ParticleIndex):\n    chunksize = -1\n\n    def __init__(self, ds, dataset_type):\n        super().__init__(ds, dataset_type)\n\n    def _calculate_particle_index_starts(self):\n        # Halo indices are not saved in the file, so we must count by hand.\n        # File 0 has halos 0 to N_0 - 1, file 1 has halos N_0 to N_0 + N_1 - 1, etc.\n        particle_count = defaultdict(int)\n        offset_count = 0\n        for data_file in self.data_files:\n            data_file.index_start = {\n                ptype: particle_count[ptype] for ptype in data_file.total_particles\n            }\n            data_file.offset_start = offset_count\n            for ptype in data_file.total_particles:\n                particle_count[ptype] += data_file.total_particles[ptype]\n            offset_count += data_file.total_offset\n\n    def _calculate_file_offset_map(self):\n        # After the FOF  is performed, a load-balancing step redistributes halos\n        # and then writes more fields.  Here, for each file, we create a list of\n        # files which contain the rest of the redistributed particles.\n        ifof = np.array(\n            [data_file.total_particles[\"FOF\"] for data_file in self.data_files]\n        )\n        isub = np.array([data_file.total_offset for data_file in self.data_files])\n        subend = isub.cumsum()\n        fofend = ifof.cumsum()\n        istart = np.digitize(fofend - ifof, subend - isub) - 1\n        iend = np.clip(np.digitize(fofend, subend), 0, ifof.size - 2)\n        for i, data_file in enumerate(self.data_files):\n            data_file.offset_files = self.data_files[istart[i] : iend[i] + 1]\n\n    def _detect_output_fields(self):\n        # TODO: Add additional fields\n        self._calculate_particle_index_starts()\n        self._calculate_file_offset_map()\n        dsl = []\n        units = {}\n        for dom in self.data_files:\n            fl, _units = self.io._identify_fields(dom)\n            units.update(_units)\n            for f in fl:\n                if f not in dsl:\n                    dsl.append(f)\n        self.field_list = dsl\n        ds = self.dataset\n        ds.particle_types = tuple({pt for pt, ds in dsl})\n        # This is an attribute that means these particle types *actually*\n        # exist.  As in, they are real, in the dataset.\n        ds.field_units.update(units)\n        ds.particle_types_raw = ds.particle_types\n\n\nclass OWLSSubfindHDF5File(ParticleFile):\n    def __init__(self, ds, io, filename, file_id, bounds):\n        super().__init__(ds, io, filename, file_id, bounds)\n        with h5py.File(filename, mode=\"r\") as f:\n            self.header = {field: f.attrs[field] for field in f.attrs.keys()}\n\n\nclass OWLSSubfindDataset(ParticleDataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = OWLSSubfindParticleIndex\n    _file_class = OWLSSubfindHDF5File\n    _field_info_class = OWLSSubfindFieldInfo\n    _suffix = \".hdf5\"\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"subfind_hdf5\",\n        index_order=None,\n        index_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        super().__init__(\n            filename,\n            dataset_type,\n            index_order=index_order,\n            index_filename=index_filename,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n\n    def _parse_parameter_file(self):\n        handle = h5py.File(self.parameter_filename, mode=\"r\")\n        hvals = {}\n        hvals.update((str(k), v) for k, v in handle[\"/Header\"].attrs.items())\n        hvals[\"NumFiles\"] = hvals[\"NumFilesPerSnapshot\"]\n        hvals[\"Massarr\"] = hvals[\"MassTable\"]\n\n        self.dimensionality = 3\n        self.refine_by = 2\n\n        # Set standard values\n        if \"Time_GYR\" in hvals:\n            self.current_time = self.quan(hvals[\"Time_GYR\"], \"Gyr\")\n        self.domain_left_edge = np.zeros(3, \"float64\")\n        self.domain_right_edge = np.ones(3, \"float64\") * hvals[\"BoxSize\"]\n        self.domain_dimensions = np.ones(3, \"int32\")\n        self.cosmological_simulation = 1\n        self._periodicity = (True, True, True)\n        self.current_redshift = hvals[\"Redshift\"]\n        self.omega_lambda = hvals[\"OmegaLambda\"]\n        self.omega_matter = hvals[\"Omega0\"]\n        self.hubble_constant = hvals[\"HubbleParam\"]\n        self.parameters = hvals\n        prefix = os.path.abspath(\n            os.path.join(\n                os.path.dirname(self.parameter_filename),\n                os.path.basename(self.parameter_filename).split(\".\", 1)[0],\n            )\n        )\n\n        suffix = self.parameter_filename.rsplit(\".\", 1)[-1]\n        self.filename_template = f\"{prefix}.%(num)i.{suffix}\"\n        self.file_count = len(glob.glob(prefix + \"*\" + self._suffix))\n        if self.file_count == 0:\n            raise YTException(message=\"No data files found.\", ds=self)\n        self.particle_types = (\"FOF\", \"SUBFIND\")\n        self.particle_types_raw = (\"FOF\", \"SUBFIND\")\n\n        # To avoid having to open files twice\n        self._unit_base = {}\n        self._unit_base.update((str(k), v) for k, v in handle[\"/Units\"].attrs.items())\n        handle.close()\n\n    def _set_code_unit_attributes(self):\n        # Set a sane default for cosmological simulations.\n        if self._unit_base is None and self.cosmological_simulation == 1:\n            only_on_root(mylog.info, \"Assuming length units are in Mpc/h (comoving)\")\n            self._unit_base = {\"length\": (1.0, \"Mpccm/h\")}\n        # The other same defaults we will use from the standard Gadget\n        # defaults.\n        unit_base = self._unit_base or {}\n\n        if \"length\" in unit_base:\n            length_unit = unit_base[\"length\"]\n        elif \"UnitLength_in_cm\" in unit_base:\n            if self.cosmological_simulation == 0:\n                length_unit = (unit_base[\"UnitLength_in_cm\"], \"cm\")\n            else:\n                length_unit = (unit_base[\"UnitLength_in_cm\"], \"cmcm/h\")\n        else:\n            raise RuntimeError\n        length_unit = _fix_unit_ordering(length_unit)\n        setdefaultattr(self, \"length_unit\", self.quan(length_unit[0], length_unit[1]))\n\n        if \"velocity\" in unit_base:\n            velocity_unit = unit_base[\"velocity\"]\n        elif \"UnitVelocity_in_cm_per_s\" in unit_base:\n            velocity_unit = (unit_base[\"UnitVelocity_in_cm_per_s\"], \"cm/s\")\n        else:\n            velocity_unit = (1e5, \"cm/s  * sqrt(a)\")\n        velocity_unit = _fix_unit_ordering(velocity_unit)\n        setdefaultattr(\n            self, \"velocity_unit\", self.quan(velocity_unit[0], velocity_unit[1])\n        )\n\n        # We set hubble_constant = 1.0 for non-cosmology, so this is safe.\n        # Default to 1e10 Msun/h if mass is not specified.\n        if \"mass\" in unit_base:\n            mass_unit = unit_base[\"mass\"]\n        elif \"UnitMass_in_g\" in unit_base:\n            if self.cosmological_simulation == 0:\n                mass_unit = (unit_base[\"UnitMass_in_g\"], \"g\")\n            else:\n                mass_unit = (unit_base[\"UnitMass_in_g\"], \"g/h\")\n        else:\n            # Sane default\n            mass_unit = (1.0, \"1e10*Msun/h\")\n        mass_unit = _fix_unit_ordering(mass_unit)\n        setdefaultattr(self, \"mass_unit\", self.quan(mass_unit[0], mass_unit[1]))\n\n        if \"time\" in unit_base:\n            time_unit = unit_base[\"time\"]\n        elif \"UnitTime_in_s\" in unit_base:\n            time_unit = (unit_base[\"UnitTime_in_s\"], \"s\")\n        else:\n            tu = (self.length_unit / self.velocity_unit).to(\"yr/h\")\n            time_unit = (tu.d, tu.units)\n        setdefaultattr(self, \"time_unit\", self.quan(time_unit[0], time_unit[1]))\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        need_groups = [\"Constants\", \"Header\", \"Parameters\", \"Units\", \"FOF\"]\n        valid = True\n        try:\n            fh = h5py.File(filename, mode=\"r\")\n            valid = all(ng in fh[\"/\"] for ng in need_groups)\n            fh.close()\n        except Exception:\n            valid = False\n        return valid\n"
  },
  {
    "path": "yt/frontends/owls_subfind/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\nm_units = \"code_mass\"\nmdot_units = \"code_mass / code_time\"\np_units = \"Mpccm/h\"\nv_units = \"1e5 * cmcm / s\"\n\n\nclass OWLSSubfindFieldInfo(FieldInfoContainer):\n    known_particle_fields: KnownFieldsT = (\n        (\"CenterOfMass_0\", (p_units, [\"particle_position_x\"], None)),\n        (\"CenterOfMass_1\", (p_units, [\"particle_position_y\"], None)),\n        (\"CenterOfMass_2\", (p_units, [\"particle_position_z\"], None)),\n        (\"CentreOfMass_0\", (p_units, [\"particle_position_x\"], None)),\n        (\"CentreOfMass_1\", (p_units, [\"particle_position_y\"], None)),\n        (\"CentreOfMass_2\", (p_units, [\"particle_position_z\"], None)),\n        (\"CenterOfMassVelocity_0\", (v_units, [\"particle_velocity_x\"], None)),\n        (\"CenterOfMassVelocity_1\", (v_units, [\"particle_velocity_y\"], None)),\n        (\"CenterOfMassVelocity_2\", (v_units, [\"particle_velocity_z\"], None)),\n        (\"Velocity_0\", (v_units, [\"particle_velocity_x\"], None)),\n        (\"Velocity_1\", (v_units, [\"particle_velocity_y\"], None)),\n        (\"Velocity_2\", (v_units, [\"particle_velocity_z\"], None)),\n        (\"Mass\", (m_units, [\"particle_mass\"], None)),\n        (\"Halo_M_Crit200\", (m_units, [\"Virial Mass\"], None)),\n        (\"Halo_M_Crit2500\", (m_units, [], None)),\n        (\"Halo_M_Crit500\", (m_units, [], None)),\n        (\"Halo_M_Mean200\", (m_units, [], None)),\n        (\"Halo_M_Mean2500\", (m_units, [], None)),\n        (\"Halo_M_Mean500\", (m_units, [], None)),\n        (\"Halo_M_TopHat200\", (m_units, [], None)),\n        (\"Halo_R_Crit200\", (p_units, [\"Virial Radius\"], None)),\n        (\"Halo_R_Crit2500\", (p_units, [], None)),\n        (\"Halo_R_Crit500\", (p_units, [], None)),\n        (\"Halo_R_Mean200\", (p_units, [], None)),\n        (\"Halo_R_Mean2500\", (p_units, [], None)),\n        (\"Halo_R_Mean500\", (p_units, [], None)),\n        (\"Halo_R_TopHat200\", (p_units, [], None)),\n        (\"BH_Mass\", (m_units, [], None)),\n        (\"Stars/Mass\", (m_units, [], None)),\n        (\"BH_Mdot\", (mdot_units, [], None)),\n        (\"StarFormationRate\", (mdot_units, [], None)),\n    )\n"
  },
  {
    "path": "yt/frontends/owls_subfind/io.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog\nfrom yt.utilities.io_handler import BaseParticleIOHandler\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n_pos_names = [\"CenterOfMass\", \"CentreOfMass\"]\n\n\nclass IOHandlerOWLSSubfindHDF5(BaseParticleIOHandler):\n    _dataset_type = \"subfind_hdf5\"\n    _position_name = None\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        self.offset_fields = set()\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This will read chunks and yield the results.\n        for data_file in self._sorted_chunk_iterator(chunks):\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype in sorted(ptf):\n                    pcount = data_file.total_particles[ptype]\n                    coords = f[ptype][self._position_name][()].astype(\"float64\")\n                    coords = np.resize(coords, (pcount, 3))\n                    x = coords[:, 0]\n                    y = coords[:, 1]\n                    z = coords[:, 2]\n                    yield ptype, (x, y, z), 0.0\n\n    def _yield_coordinates(self, data_file):\n        ptypes = self.ds.particle_types_raw\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            for ptype in sorted(ptypes):\n                pcount = data_file.total_particles[ptype]\n                coords = f[ptype][self._position_name][()].astype(\"float64\")\n                coords = np.resize(coords, (pcount, 3))\n                yield ptype, coords\n\n    def _read_offset_particle_field(self, field, data_file, fh):\n        field_data = np.empty(data_file.total_particles[\"FOF\"], dtype=\"float64\")\n        fofindex = (\n            np.arange(data_file.total_particles[\"FOF\"]) + data_file.index_start[\"FOF\"]\n        )\n        for offset_file in data_file.offset_files:\n            if fh.filename == offset_file.filename:\n                ofh = fh\n            else:\n                ofh = h5py.File(offset_file.filename, mode=\"r\")\n            subindex = np.arange(offset_file.total_offset) + offset_file.offset_start\n            substart = max(fofindex[0] - subindex[0], 0)\n            subend = min(fofindex[-1] - subindex[0], subindex.size - 1)\n            fofstart = substart + subindex[0] - fofindex[0]\n            fofend = subend + subindex[0] - fofindex[0]\n            field_data[fofstart : fofend + 1] = ofh[\"SUBFIND\"][field][\n                substart : subend + 1\n            ]\n        return field_data\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # Now we have all the sizes, and we can allocate\n        for data_file in self._sorted_chunk_iterator(chunks):\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype, field_list in sorted(ptf.items()):\n                    pcount = data_file.total_particles[ptype]\n                    if pcount == 0:\n                        continue\n                    coords = f[ptype][self._position_name][()].astype(\"float64\")\n                    coords = np.resize(coords, (pcount, 3))\n                    x = coords[:, 0]\n                    y = coords[:, 1]\n                    z = coords[:, 2]\n                    mask = selector.select_points(x, y, z, 0.0)\n                    del x, y, z\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        if field in self.offset_fields:\n                            field_data = self._read_offset_particle_field(\n                                field, data_file, f\n                            )\n                        else:\n                            if field == \"particle_identifier\":\n                                field_data = (\n                                    np.arange(data_file.total_particles[ptype])\n                                    + data_file.index_start[ptype]\n                                )\n                            elif field in f[ptype]:\n                                field_data = f[ptype][field][()].astype(\"float64\")\n                            else:\n                                fname = field[: field.rfind(\"_\")]\n                                field_data = f[ptype][fname][()].astype(\"float64\")\n                                my_div = field_data.size / pcount\n                                if my_div > 1:\n                                    field_data = np.resize(\n                                        field_data, (int(pcount), int(my_div))\n                                    )\n                                    findex = int(field[field.rfind(\"_\") + 1 :])\n                                    field_data = field_data[:, findex]\n                        data = field_data[mask]\n                        yield (ptype, field), data\n\n    def _count_particles(self, data_file):\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            pcount = {\"FOF\": get_one_attr(f[\"FOF\"], [\"Number_of_groups\", \"Ngroups\"])}\n            if \"SUBFIND\" in f:\n                # We need this to figure out where the offset fields are stored.\n                data_file.total_offset = get_one_attr(\n                    f[\"SUBFIND\"], [\"Number_of_groups\", \"Ngroups\"]\n                )\n                pcount[\"SUBFIND\"] = get_one_attr(\n                    f[\"FOF\"], [\"Number_of_subgroups\", \"Nsubgroups\"]\n                )\n            else:\n                data_file.total_offset = 0\n                pcount[\"SUBFIND\"] = 0\n\n        return pcount\n\n    def _identify_fields(self, data_file):\n        fields = []\n        pcount = data_file.total_particles\n        if sum(pcount.values()) == 0:\n            return fields, {}\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            for ptype in self.ds.particle_types_raw:\n                if data_file.total_particles[ptype] == 0:\n                    continue\n                self._identify_position_name(f[ptype])\n                fields.append((ptype, \"particle_identifier\"))\n                my_fields, my_offset_fields = subfind_field_list(\n                    f[ptype], ptype, data_file.total_particles\n                )\n                fields.extend(my_fields)\n                self.offset_fields = self.offset_fields.union(set(my_offset_fields))\n        return fields, {}\n\n    def _identify_position_name(self, fh):\n        if self._position_name is not None:\n            return\n        for pname in _pos_names:\n            if pname in fh:\n                self._position_name = pname\n                return\n\n\ndef get_one_attr(fh, attrs, default=None, error=True):\n    \"\"\"\n    Try getting from a list of attrs. Return the first one that exists.\n    \"\"\"\n    for attr in attrs:\n        if attr in fh.attrs:\n            return fh.attrs[attr]\n    if error:\n        raise RuntimeError(\n            f\"Could not find any of these attributes: {attrs}. \"\n            f\"Available attributes: {fh.attrs.keys()}\"\n        )\n    return default\n\n\ndef subfind_field_list(fh, ptype, pcount):\n    fields = []\n    offset_fields = []\n    for field in fh.keys():\n        if \"PartType\" in field:\n            # These are halo member particles\n            continue\n        elif isinstance(fh[field], h5py.Group):\n            my_fields, my_offset_fields = subfind_field_list(fh[field], ptype, pcount)\n            fields.extend(my_fields)\n            my_offset_fields.extend(offset_fields)\n        else:\n            if not fh[field].size % pcount[ptype]:\n                my_div = fh[field].size / pcount[ptype]\n                fname = fh[field].name[fh[field].name.find(ptype) + len(ptype) + 1 :]\n                if my_div > 1:\n                    for i in range(int(my_div)):\n                        fields.append((ptype, f\"{fname}_{i}\"))\n                else:\n                    fields.append((ptype, fname))\n            elif (\n                ptype == \"SUBFIND\"\n                and not fh[field].size % fh[\"/SUBFIND\"].attrs[\"Number_of_groups\"]\n            ):\n                # These are actually FOF fields, but they were written after\n                # a load balancing step moved halos around and thus they do not\n                # correspond to the halos stored in the FOF group.\n                my_div = fh[field].size / fh[\"/SUBFIND\"].attrs[\"Number_of_groups\"]\n                fname = fh[field].name[fh[field].name.find(ptype) + len(ptype) + 1 :]\n                if my_div > 1:\n                    for i in range(int(my_div)):\n                        fields.append((\"FOF\", f\"{fname}_{i}\"))\n                else:\n                    fields.append((\"FOF\", fname))\n                offset_fields.append(fname)\n            else:\n                mylog.warning(\n                    \"Cannot add field (%s, %s) with size %d.\",\n                    ptype,\n                    fh[field].name,\n                    fh[field].size,\n                )\n                continue\n    return fields, offset_fields\n"
  },
  {
    "path": "yt/frontends/owls_subfind/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/owls_subfind/tests/test_outputs.py",
    "content": "import os.path\n\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import requires_module\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    data_dir_load,\n    requires_ds,\n)\n\n# from yt.frontends.owls_subfind.api import OWLSSubfindDataset\n\n_fields = (\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_position_y\"),\n    (\"all\", \"particle_position_z\"),\n    (\"all\", \"particle_mass\"),\n)\n\n# a dataset with empty files\ng1 = \"owls_fof_halos/groups_001/group_001.0.hdf5\"\ng8 = \"owls_fof_halos/groups_008/group_008.0.hdf5\"\n\n\n@requires_module(\"h5py\")\n@requires_ds(g8)\ndef test_fields_g8():\n    ds = data_dir_load(g8)\n    assert_equal(str(ds), os.path.basename(g8))\n    for field in _fields:\n        yield FieldValuesTest(g8, field, particle_type=True)\n\n\n@requires_module(\"h5py\")\n@requires_ds(g1)\ndef test_fields_g1():\n    ds = data_dir_load(g1)\n    assert_equal(str(ds), os.path.basename(g1))\n    for field in _fields:\n        yield FieldValuesTest(g1, field, particle_type=True)\n"
  },
  {
    "path": "yt/frontends/parthenon/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/parthenon/api.py",
    "content": "from . import tests\nfrom .data_structures import ParthenonDataset, ParthenonGrid, ParthenonHierarchy\nfrom .fields import ParthenonFieldInfo\nfrom .io import IOHandlerParthenon\n"
  },
  {
    "path": "yt/frontends/parthenon/data_structures.py",
    "content": "import os\nimport warnings\nimport weakref\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.static_output import Dataset\nfrom yt.fields.magnetic_field import get_magnetic_normalization\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.chemical_formulas import compute_mu\nfrom yt.utilities.file_handler import HDF5FileHandler\n\nfrom .fields import ParthenonFieldInfo\n\n_geom_map = {\n    \"UniformCartesian\": Geometry.CARTESIAN,\n    \"UniformCylindrical\": Geometry.CYLINDRICAL,\n    \"UniformSpherical\": Geometry.SPHERICAL,\n}\n\n# fmt: off\n_cis = np.array(\n    [\n        [0, 0, 0],\n        [0, 0, 1],\n        [0, 1, 0],\n        [0, 1, 1],\n        [1, 0, 0],\n        [1, 0, 1],\n        [1, 1, 0],\n        [1, 1, 1],\n    ],\n    dtype=\"int64\",\n)\n# fmt: on\n\n\nclass ParthenonGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, id, index, level):\n        AMRGridPatch.__init__(self, id, filename=index.index_filename, index=index)\n        self.Parent = None\n        self.Children = []\n        self.Level = level\n\n    def _setup_dx(self):\n        # So first we figure out what the index is.  We don't assume\n        # that dx=dy=dz , at least here.  We probably do elsewhere.\n        id = self.id - self._id_offset\n        LE, RE = self.index.grid_left_edge[id, :], self.index.grid_right_edge[id, :]\n        self.dds = self.ds.arr((RE - LE) / self.ActiveDimensions, \"code_length\")\n        self.field_data[\"dx\"], self.field_data[\"dy\"], self.field_data[\"dz\"] = self.dds\n\n    def retrieve_ghost_zones(self, n_zones, fields, all_levels=False, smoothed=False):\n        if smoothed:\n            warnings.warn(\n                \"ghost-zones interpolation/smoothing is not \"\n                \"currently supported for Parthenon data.\",\n                category=RuntimeWarning,\n                stacklevel=2,\n            )\n            smoothed = False\n        return super().retrieve_ghost_zones(\n            n_zones, fields, all_levels=all_levels, smoothed=smoothed\n        )\n\n\nclass ParthenonHierarchy(GridIndex):\n    grid = ParthenonGrid\n    _dataset_type = \"parthenon\"\n    _data_file = None\n\n    def __init__(self, ds, dataset_type=\"parthenon\"):\n        self.dataset = weakref.proxy(ds)\n        self.directory = os.path.dirname(self.dataset.filename)\n        self.dataset_type = dataset_type\n        # for now, the index file is the dataset!\n        self.index_filename = self.dataset.filename\n        self._handle = ds._handle\n        GridIndex.__init__(self, ds, dataset_type)\n\n    def _detect_output_fields(self):\n        self.field_list = [(\"parthenon\", k) for k in self.dataset._field_map]\n\n    def _count_grids(self):\n        self.num_grids = self._handle[\"Info\"].attrs[\"NumMeshBlocks\"]\n\n    def _parse_index(self):\n        num_grids = self._handle[\"Info\"].attrs[\"NumMeshBlocks\"]\n\n        # TODO: In an unlikely case this would use too much memory, implement\n        #       chunked read along 1 dim\n        x = self._handle[\"Locations\"][\"x\"][:, :]\n        y = self._handle[\"Locations\"][\"y\"][:, :]\n        z = self._handle[\"Locations\"][\"z\"][:, :]\n        mesh_block_size = self._handle[\"Info\"].attrs[\"MeshBlockSize\"]\n\n        self.grids = np.empty(self.num_grids, dtype=\"object\")\n        levels = self._handle[\"Levels\"][:]\n        for i in range(num_grids):\n            self.grid_left_edge[i] = np.array(\n                [x[i, 0], y[i, 0], z[i, 0]], dtype=\"float64\"\n            )\n            self.grid_right_edge[i] = np.array(\n                [x[i, -1], y[i, -1], z[i, -1]], dtype=\"float64\"\n            )\n            self.grid_dimensions[i] = mesh_block_size\n            self.grids[i] = self.grid(i, self, levels[i])\n\n        if self.dataset.dimensionality <= 2:\n            self.grid_right_edge[:, 2] = self.dataset.domain_right_edge[2]\n        if self.dataset.dimensionality == 1:\n            self.grid_right_edge[:, 1:] = self.dataset.domain_right_edge[1:]\n        self.grid_particle_count = np.zeros([self.num_grids, 1], dtype=\"int64\")\n\n    def _populate_grid_objects(self):\n        for g in self.grids:\n            g._prepare_grid()\n            g._setup_dx()\n        self.max_level = self._handle[\"Info\"].attrs[\"MaxLevel\"]\n\n\nclass ParthenonDataset(Dataset):\n    _load_requirements = [\"h5py\"]\n    _field_info_class = ParthenonFieldInfo\n    _dataset_type = \"parthenon\"\n    _index_class = ParthenonHierarchy\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"parthenon\",\n        storage_filename=None,\n        parameters=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n        magnetic_normalization=\"gaussian\",\n    ):\n        self.fluid_types += (\"parthenon\",)\n        if parameters is None:\n            parameters = {}\n        self.specified_parameters = parameters\n        if units_override is None:\n            units_override = {}\n        self._handle = HDF5FileHandler(filename)\n        xrat = self._handle[\"Info\"].attrs[\"RootGridDomain\"][2]\n        yrat = self._handle[\"Info\"].attrs[\"RootGridDomain\"][5]\n        zrat = self._handle[\"Info\"].attrs[\"RootGridDomain\"][8]\n        if xrat != 1.0 or yrat != 1.0 or zrat != 1.0:\n            raise NotImplementedError(\n                \"Logarithmic grids not yet supported/tested in Parthenon frontend.\"\n            )\n\n        self._magnetic_factor = get_magnetic_normalization(magnetic_normalization)\n\n        self.geometry = _geom_map[self._handle[\"Info\"].attrs[\"Coordinates\"]]\n\n        Dataset.__init__(\n            self,\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        if storage_filename is None:\n            storage_filename = self.basename + \".yt\"\n        self.storage_filename = storage_filename\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Generates the conversion to various physical _units based on the\n        parameter file\n        \"\"\"\n        for unit, cgs in [\n            (\"length\", \"cm\"),\n            (\"time\", \"s\"),\n            (\"mass\", \"g\"),\n        ]:\n            unit_param = f\"Hydro/code_{unit}_cgs\"\n            # Use units, if provided in output\n            if unit_param in self.parameters:\n                setdefaultattr(\n                    self, f\"{unit}_unit\", self.quan(self.parameters[unit_param], cgs)\n                )\n            # otherwise use code = cgs\n            else:\n                mylog.warning(f\"Assuming 1.0 code_{unit} = 1.0 {cgs}\")\n                setdefaultattr(self, f\"{unit}_unit\", self.quan(1.0, cgs))\n\n        self.magnetic_unit = np.sqrt(\n            self._magnetic_factor\n            * self.mass_unit\n            / (self.time_unit**2 * self.length_unit)\n        )\n        self.magnetic_unit.convert_to_units(\"gauss\")\n        self.velocity_unit = self.length_unit / self.time_unit\n\n    def _parse_parameter_file(self):\n        self.parameters.update(self.specified_parameters)\n        for key, val in self._handle[\"Params\"].attrs.items():\n            if key in self.parameters.keys():\n                mylog.warning(\n                    f\"Overriding existing {key!r} key in ds.parameters from data 'Params'\"\n                )\n            self.parameters[key] = val\n\n        xmin, xmax = self._handle[\"Info\"].attrs[\"RootGridDomain\"][0:2]\n        ymin, ymax = self._handle[\"Info\"].attrs[\"RootGridDomain\"][3:5]\n        zmin, zmax = self._handle[\"Info\"].attrs[\"RootGridDomain\"][6:8]\n\n        self.domain_left_edge = np.array([xmin, ymin, zmin], dtype=\"float64\")\n        self.domain_right_edge = np.array([xmax, ymax, zmax], dtype=\"float64\")\n\n        self.domain_width = self.domain_right_edge - self.domain_left_edge\n        self.domain_dimensions = self._handle[\"Info\"].attrs[\"RootGridSize\"]\n\n        self._field_map = {}\n        k = 0\n\n        dnames = self._handle[\"Info\"].attrs[\"OutputDatasetNames\"]\n        num_components = self._handle[\"Info\"].attrs[\"NumComponents\"]\n\n        if \"OutputFormatVersion\" in self._handle[\"Info\"].attrs.keys():\n            self.output_format_version = self._handle[\"Info\"].attrs[\n                \"OutputFormatVersion\"\n            ]\n        else:\n            raise NotImplementedError(\"Could not determine OutputFormatVersion.\")\n\n        # For a single variable, we need to convert it to a list for the following\n        # zip to work.\n        if isinstance(num_components, np.uint64):\n            dnames = (dnames,)\n            num_components = (num_components,)\n\n        component_name_offset = 0\n        for dname, num_component in zip(dnames, num_components, strict=False):\n            for j in range(num_component):\n                fname = self._handle[\"Info\"].attrs[\"ComponentNames\"][\n                    j + component_name_offset\n                ]\n                self._field_map[fname] = (dname, j)\n                k += 1\n            component_name_offset = int(component_name_offset + num_component)\n\n        self.refine_by = 2\n        dimensionality = 3\n        if self.domain_dimensions[2] == 1:\n            dimensionality = 2\n        if self.domain_dimensions[1] == 1:\n            dimensionality = 1\n        self.dimensionality = dimensionality\n        self.current_time = self._handle[\"Info\"].attrs[\"Time\"]\n        self.num_ghost_zones = 0\n        self.field_ordering = \"fortran\"\n        self.boundary_conditions = [1] * 6\n        self.cosmological_simulation = False\n\n        if \"periodicity\" in self.parameters:\n            self._periodicity = tuple(self.parameters[\"periodicity\"])\n        else:\n            boundary_conditions = self._handle[\"Info\"].attrs[\"BoundaryConditions\"]\n\n            inner_bcs = boundary_conditions[::2]\n            # outer_bcs = boundary_conditions[1::2]\n            ##Check self consistency\n            # for inner_bc,outer_bc in zip(inner_bcs,outer_bcs):\n            #    if( (inner_bc == \"periodicity\" or outer_bc == \"periodic\") and inner_bc != outer_bc ):\n            #        raise Exception(\"Inconsistent periodicity in boundary conditions\")\n\n            self._periodicity = tuple(bc == \"periodic\" for bc in inner_bcs)\n\n        if \"gamma\" in self.parameters:\n            self.gamma = float(self.parameters[\"gamma\"])\n        elif \"Hydro/AdiabaticIndex\" in self.parameters:\n            self.gamma = self.parameters[\"Hydro/AdiabaticIndex\"]\n        else:\n            mylog.warning(\n                \"Adiabatic index gamma could not be determined. Falling back to 5/3.\"\n            )\n            self.gamma = 5.0 / 3.0\n\n        if \"mu\" in self.parameters:\n            self.mu = self.parameters[\"mu\"]\n        elif \"Hydro/mu\" in self.parameters:\n            self.mu = self.parameters[\"Hydro/mu\"]\n        # Legacy He_mass_fraction parameter implemented in AthenaPK\n        elif \"Hydro/He_mass_fraction\" in self.parameters:\n            He_mass_fraction = self.parameters[\"Hydro/He_mass_fraction\"]\n            self.mu = 1 / (He_mass_fraction * 3.0 / 4.0 + (1 - He_mass_fraction) * 2)\n        # Fallback to primorial gas composition (and show warning)\n        else:\n            mylog.warning(\n                \"Plasma composition could not be determined in data file. Falling back to fully ionized primodial composition.\"\n            )\n            self.mu = self.parameters.get(\"mu\", compute_mu(self.default_species_fields))\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n        return filename.endswith((\".phdf\", \".rhdf\"))\n\n    @property\n    def _skip_cache(self):\n        return True\n\n    def __str__(self):\n        return self.basename.rsplit(\".\", 1)[0]\n"
  },
  {
    "path": "yt/frontends/parthenon/definitions.py",
    "content": "\"\"\"\nVarious definitions for various other modules and routines\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/parthenon/fields.py",
    "content": "import numpy as np\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.funcs import mylog\nfrom yt.utilities.physical_constants import kboltz, mh\n\nmag_units = \"code_magnetic\"\npres_units = \"code_mass/(code_length*code_time**2)\"\nrho_units = \"code_mass / code_length**3\"\nvel_units = \"code_length / code_time\"\nmom_units = \"code_mass / code_length**2 / code_time\"\neng_units = \"code_mass / code_length / code_time**2\"\n\n\ndef velocity_field(mom_field):\n    def _velocity(data):\n        return data[mom_field] / data[\"gas\", \"density\"]\n\n    return _velocity\n\n\ndef _cooling_time_field(data):\n    cooling_time = (\n        data[\"gas\", \"density\"]\n        * data[\"gas\", \"specific_thermal_energy\"]\n        / data[\"gas\", \"cooling_rate\"]\n    )\n\n    # Set cooling time where Cooling_Rate==0 to infinity\n    inf_ct_mask = data[\"cooling_rate\"] == 0\n    cooling_time[inf_ct_mask] = data.ds.quan(np.inf, \"s\")\n\n    return cooling_time\n\n\nclass ParthenonFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        # Need to provide info for both primitive and conserved variable as they\n        # can be written indepdendently (or even in the same output file).\n        # New field naming (i.e., \"variable_component\") of primitive variables\n        (\"prim_density\", (rho_units, [\"density\"], None)),\n        (\"prim_velocity_1\", (vel_units, [\"velocity_x\"], None)),\n        (\"prim_velocity_2\", (vel_units, [\"velocity_y\"], None)),\n        (\"prim_velocity_3\", (vel_units, [\"velocity_z\"], None)),\n        (\"prim_pressure\", (pres_units, [\"pressure\"], None)),\n        # Magnetic fields carry units of 1/sqrt(pi) so we cannot directly forward\n        # and need to setup aliases below.\n        (\"prim_magnetic_field_1\", (mag_units, [], None)),\n        (\"prim_magnetic_field_2\", (mag_units, [], None)),\n        (\"prim_magnetic_field_3\", (mag_units, [], None)),\n        # New field naming (i.e., \"variable_component\") of conserved variables\n        (\"cons_density\", (rho_units, [\"density\"], None)),\n        (\"cons_momentum_density_1\", (mom_units, [\"momentum_density_x\"], None)),\n        (\"cons_momentum_density_2\", (mom_units, [\"momentum_density_y\"], None)),\n        (\"cons_momentum_density_3\", (mom_units, [\"momentum_density_z\"], None)),\n        (\"cons_total_energy_density\", (eng_units, [\"total_energy_density\"], None)),\n        # Magnetic fields carry units of 1/sqrt(pi) so we cannot directly forward\n        # and need to setup aliases below.\n        (\"cons_magnetic_field_1\", (mag_units, [], None)),\n        (\"cons_magnetic_field_2\", (mag_units, [], None)),\n        (\"cons_magnetic_field_3\", (mag_units, [], None)),\n        # Legacy naming. Given that there's no conflict with the names above,\n        # we can just define those here so that the frontend works with older data.\n        (\"Density\", (rho_units, [\"density\"], None)),\n        (\"Velocity1\", (mom_units, [\"velocity_x\"], None)),\n        (\"Velocity2\", (mom_units, [\"velocity_y\"], None)),\n        (\"Velocity3\", (mom_units, [\"velocity_z\"], None)),\n        (\"Pressure\", (pres_units, [\"pressure\"], None)),\n        (\"MagneticField1\", (mag_units, [], None)),\n        (\"MagneticField2\", (mag_units, [], None)),\n        (\"MagneticField3\", (mag_units, [], None)),\n        (\"MomentumDensity1\", (mom_units, [\"momentum_density_x\"], None)),\n        (\"MomentumDensity2\", (mom_units, [\"momentum_density_y\"], None)),\n        (\"MomentumDensity3\", (mom_units, [\"momentum_density_z\"], None)),\n        (\"TotalEnergyDensity\", (eng_units, [\"total_energy_density\"], None)),\n    )\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n\n        unit_system = self.ds.unit_system\n        # Add velocity fields (if only momemtum densities are given)\n        for i, comp in enumerate(self.ds.coordinates.axis_order):\n            # Support both current and legacy scheme\n            for mom_field_name in [\"MomentumDensity\", \"cons_momentum_density_\"]:\n                mom_field = (\"parthenon\", f\"{mom_field_name}{i + 1}\")\n                if mom_field in self.field_list:\n                    self.add_field(\n                        (\"gas\", f\"velocity_{comp}\"),\n                        sampling_type=\"cell\",\n                        function=velocity_field(mom_field),\n                        units=unit_system[\"velocity\"],\n                    )\n        # Figure out thermal energy field\n        if (\"parthenon\", \"Pressure\") in self.field_list or (\n            \"parthenon\",\n            \"prim_pressure\",\n        ) in self.field_list:\n            # only show warning for non-AthenaPK codes\n            if \"Hydro/AdiabaticIndex\" not in self.ds.parameters:\n                mylog.warning(\n                    f\"Adding a specific thermal energy field assuming an ideal gas with an \"\n                    f\"adiabatic index of {self.ds.gamma}\"\n                )\n\n            def _specific_thermal_energy(data):\n                return (\n                    data[\"gas\", \"pressure\"]\n                    / (data.ds.gamma - 1.0)\n                    / data[\"gas\", \"density\"]\n                )\n\n            self.add_field(\n                (\"gas\", \"specific_thermal_energy\"),\n                sampling_type=\"cell\",\n                function=_specific_thermal_energy,\n                units=unit_system[\"specific_energy\"],\n            )\n        elif (\"parthenon\", \"TotalEnergyDensity\") in self.field_list or (\n            \"parthenon\",\n            \"cons_total_energy_density\",\n        ) in self.field_list:\n\n            def _specific_thermal_energy(data):\n                eint = (\n                    data[\"gas\", \"total_energy_density\"]\n                    - data[\"gas\", \"kinetic_energy_density\"]\n                )\n\n                if (\n                    (\"parthenon\", \"MagneticField1\") in self.field_list\n                    or (\"parthenon\", \"prim_magnetic_field_1\") in self.field_list\n                    or (\"parthenon\", \"cons_magnetic_field_1\") in self.field_list\n                ):\n                    eint -= data[\"gas\", \"magnetic_energy_density\"]\n                return eint / data[\"gas\", \"density\"]\n\n            self.add_field(\n                (\"gas\", \"specific_thermal_energy\"),\n                sampling_type=\"cell\",\n                function=_specific_thermal_energy,\n                units=unit_system[\"specific_energy\"],\n            )\n\n        # Add temperature field\n        def _temperature(data):\n            return (\n                (data[\"gas\", \"pressure\"] / data[\"gas\", \"density\"])\n                * data.ds.mu\n                * mh\n                / kboltz\n            )\n\n        self.add_field(\n            (\"gas\", \"temperature\"),\n            sampling_type=\"cell\",\n            function=_temperature,\n            units=unit_system[\"temperature\"],\n        )\n\n        # We can simply all all variants as only fields present will be added\n        setup_magnetic_field_aliases(\n            self, \"parthenon\", [f\"MagneticField{ax}\" for ax in (1, 2, 3)]\n        )\n        setup_magnetic_field_aliases(\n            self, \"parthenon\", [f\"prim_magnetic_field_{ax}\" for ax in (1, 2, 3)]\n        )\n        setup_magnetic_field_aliases(\n            self, \"parthenon\", [f\"cons_magnetic_field_{ax}\" for ax in (1, 2, 3)]\n        )\n"
  },
  {
    "path": "yt/frontends/parthenon/io.py",
    "content": "from itertools import groupby\n\nimport numpy as np\n\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\n\n\n# http://stackoverflow.com/questions/2361945/detecting-consecutive-integers-in-a-list\ndef grid_sequences(grids):\n    g_iter = sorted(grids, key=lambda g: g.id)\n    for _, g in groupby(enumerate(g_iter), lambda i_x1: i_x1[0] - i_x1[1].id):\n        seq = [v[1] for v in g]\n        yield seq\n\n\nii = [0, 1, 0, 1, 0, 1, 0, 1]\njj = [0, 0, 1, 1, 0, 0, 1, 1]\nkk = [0, 0, 0, 0, 1, 1, 1, 1]\n\n\nclass IOHandlerParthenon(BaseIOHandler):\n    _particle_reader = False\n    _dataset_type = \"parthenon\"\n\n    def __init__(self, ds):\n        super().__init__(ds)\n        self._handle = ds._handle\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        f = self._handle\n        rv = {}\n        for field in fields:\n            # Always use *native* 64-bit float.\n            rv[field] = np.empty(size, dtype=\"=f8\")\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s blocks\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n        last_dname = None\n        for field in fields:\n            ftype, fname = field\n            dname, fdi = self.ds._field_map[fname]\n            if dname != last_dname:\n                ds = f[f\"/{dname}\"]\n            ind = 0\n            for chunk in chunks:\n                for gs in grid_sequences(chunk.objs):\n                    start = gs[0].id - gs[0]._id_offset\n                    end = gs[-1].id - gs[-1]._id_offset + 1\n                    if len(ds.shape) == 4:\n                        data = ds[start:end, :, :, :].transpose()\n                    else:\n                        data = ds[start:end, fdi, :, :, :].transpose()\n                    for i, g in enumerate(gs):\n                        ind += g.select(selector, data[..., i], rv[field], ind)\n            last_dname = dname\n        return rv\n\n    def _read_chunk_data(self, chunk, fields):\n        f = self._handle\n        rv = {}\n        for g in chunk.objs:\n            rv[g.id] = {}\n        if len(fields) == 0:\n            return rv\n        for field in fields:\n            ftype, fname = field\n            dname, fdi = self.ds._field_map[fname]\n            ds = f[f\"/{dname}\"]\n            for gs in grid_sequences(chunk.objs):\n                start = gs[0].id - gs[0]._id_offset\n                end = gs[-1].id - gs[-1]._id_offset + 1\n                if len(ds.shape) == 4:\n                    data = ds[start:end, :, :, :].transpose()\n                else:\n                    data = ds[start:end, fdi, :, :, :].transpose()\n                for i, g in enumerate(gs):\n                    rv[g.id][field] = np.asarray(data[..., i], \"=f8\")\n        return rv\n"
  },
  {
    "path": "yt/frontends/parthenon/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/parthenon/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/parthenon/tests/test_outputs.py",
    "content": "import numpy as np\n\nfrom yt.frontends.parthenon.api import ParthenonDataset\nfrom yt.loaders import load\nfrom yt.testing import (\n    assert_allclose,\n    assert_equal,\n    assert_true,\n    requires_file,\n)\nfrom yt.utilities.answer_testing.framework import (\n    GenericArrayTest,\n    data_dir_load,\n    requires_ds,\n    small_patch_amr,\n)\n\n_fields_parthenon_advection = (\n    (\"parthenon\", \"advected_0_0\"),\n    (\"parthenon\", \"one_minus_advected\"),\n    (\"parthenon\", \"one_minus_advected_sq\"),\n    (\"parthenon\", \"one_minus_sqrt_one_minus_advected_sq_12\"),\n    (\"parthenon\", \"one_minus_sqrt_one_minus_advected_sq_37\"),\n)\n\n# Simple 2D test (advected spherical blob) with AMR from the main Parthenon test suite\n# adjusted so that x1 != x2.\n# Ran with `./example/advection/advection-example -i ../tst/regression/test_suites/output_hdf5/parthinput.advection parthenon/mesh/nx1=128 parthenon/mesh/x1min=-1.0 parthenon/mesh/x1max=1.0 Advection/vx=2`\n# on changeset e5059ad\nparthenon_advection = \"parthenon_advection/advection_2d.out0.final.phdf\"\n\n\n@requires_ds(parthenon_advection)\ndef test_loading_data():\n    ds = data_dir_load(parthenon_advection)\n    assert_equal(str(ds), \"advection_2d.out0.final\")\n    dd = ds.all_data()\n    # test mesh dims\n    vol = np.prod(ds.domain_right_edge - ds.domain_left_edge)\n    assert_equal(vol, ds.quan(2.0, \"code_length**3\"))\n    assert_allclose(dd.quantities.total_quantity(\"cell_volume\"), vol)\n    # test data\n    for field in _fields_parthenon_advection:\n\n        def field_func(name):\n            return dd[name]\n\n        yield GenericArrayTest(ds, field_func, args=[field])\n\n    # reading data of two fields and compare against each other (data is squared in output)\n    ad = ds.all_data()\n    assert_allclose(\n        ad[\"parthenon\", \"one_minus_advected\"] ** 2.0,\n        ad[\"parthenon\", \"one_minus_advected_sq\"],\n    )\n\n    # check if the peak is in the domain center (and at the highest refinement level)\n    dist_of_max_from_center = np.linalg.norm(\n        ad.quantities.max_location((\"parthenon\", \"advected_0_0\"))[1:] - ds.domain_center\n    )\n\n    dx_min, dx_max = ad.quantities.extrema((\"index\", \"dx\"))\n    dy_min, dy_max = ad.quantities.extrema((\"index\", \"dy\"))\n\n    assert_true(dist_of_max_from_center < np.min((dx_min, dy_min)))\n\n\n# 3D magnetized cluster center from downstream Parthenon code AthenaPK (Restart, Conserveds)\nathenapk_cluster = \"athenapk_cluster/athenapk_cluster.restart.00000.rhdf\"\n\n# Keplerian disk in 2D cylindrical from downstream Parthenon code AthenaPK (Data, Primitives)\nathenapk_disk = \"athenapk_disk/athenapk_disk.prim.00000.phdf\"\n\n\n@requires_file(athenapk_cluster)\ndef test_AthenaPK_rhdf():\n    # Test that a downstream AthenaPK data set can be loaded with this Parthenon\n    # frontend\n    ds = data_dir_load(athenapk_cluster)\n    assert isinstance(ds, ParthenonDataset)\n\n    assert_equal(ds.domain_left_edge.in_units(\"code_length\").v, (-0.15, -0.18, -0.2))\n    assert_equal(ds.domain_right_edge.in_units(\"code_length\").v, (0.15, 0.18, 0.2))\n\n\n@requires_file(athenapk_disk)\ndef test_AthenaPK_phdf():\n    # Test that a downstream AthenaPK data set can be loaded with this Parthenon\n    # frontend\n    assert isinstance(data_dir_load(athenapk_disk), ParthenonDataset)\n\n\n_fields_derived = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"specific_thermal_energy\"),\n)\n\n_fields_derived_cluster = ((\"gas\", \"magnetic_field_strength\"),)\n\n\n@requires_ds(athenapk_cluster)\ndef test_cluster():\n    ds = data_dir_load(athenapk_cluster)\n    assert_equal(str(ds), \"athenapk_cluster.restart.00000\")\n    for test in small_patch_amr(ds, _fields_derived + _fields_derived_cluster):\n        test_cluster.__name__ = test.description\n        yield test\n\n\n@requires_ds(athenapk_disk)\n@requires_ds(athenapk_cluster)\ndef test_derived_fields():\n    # Check that derived fields like temperature are present in downstream\n    # which define them\n\n    # Check temperature and specific thermal energy becomes defined from primitives\n    ds = data_dir_load(athenapk_disk)\n    dd = ds.all_data()\n\n    for field in _fields_derived:\n\n        def field_func(name):\n            return dd[name]\n\n        yield GenericArrayTest(ds, field_func, args=[field])\n\n    # Check hydro, magnetic, and cooling fields defined from conserveds\n    ds = data_dir_load(athenapk_cluster)\n    dd = ds.all_data()\n\n    for field in _fields_derived + _fields_derived_cluster:\n\n        def field_func(name):\n            return dd[name]\n\n        yield GenericArrayTest(ds, field_func, args=[field])\n\n\n@requires_file(athenapk_cluster)\n@requires_file(athenapk_disk)\ndef test_adiabatic_index():\n    # Read adiabiatic index from dataset parameters\n    ds = data_dir_load(athenapk_cluster)\n    assert_allclose(ds.gamma, 5.0 / 3.0, rtol=1e-12)\n\n    ds = data_dir_load(athenapk_disk)\n    assert_allclose(ds.gamma, 4.0 / 3.0, rtol=1e-12)\n\n    # Change adiabatic index from dataset parameters\n    ds = load(athenapk_disk, parameters={\"gamma\": 9.0 / 8.0})\n    assert_allclose(ds.gamma, 9.0 / 8.0, rtol=1e-12)\n\n\n@requires_file(athenapk_cluster)\ndef test_molecular_mass():\n    # Read mu from dataset parameters\n    ds = data_dir_load(athenapk_cluster)\n    assert_allclose(float(ds.mu), 0.5925925925925926, rtol=1e-12)\n\n    # Change He mass fraction from dataset parameters\n    ds = load(athenapk_disk, parameters={\"mu\": 137})\n    assert_equal(ds.mu, 137)\n\n\n@requires_file(athenapk_cluster)\ndef test_units():\n    # Check units in dataset are loaded correctly\n    ds = data_dir_load(athenapk_cluster)\n    assert_allclose(float(ds.quan(1, \"code_time\").in_units(\"Gyr\")), 1, rtol=1e-12)\n    assert_allclose(float(ds.quan(1, \"code_length\").in_units(\"Mpc\")), 1, rtol=1e-12)\n    assert_allclose(float(ds.quan(1, \"code_mass\").in_units(\"msun\")), 1e14, rtol=1e-12)\n\n\n@requires_file(athenapk_disk)\ndef test_load_cylindrical():\n    # Load a cylindrical dataset of a full disk\n    ds = data_dir_load(athenapk_disk)\n\n    # Check that the domain edges match r in [0.5,2.0], theta in [0, 2pi]\n    assert_equal(ds.domain_left_edge.in_units(\"code_length\").v[:2], (0.5, 0))\n    assert_equal(ds.domain_right_edge.in_units(\"code_length\").v[:2], (2.0, 2 * np.pi))\n\n\n# Sedov blast wave with curvlinear coords run with RIOT\nriot_sedov_curvlinear = \"riot_sedov_curvlinear/sedov.out1.final.phdf\"\n\n\n@requires_file(riot_sedov_curvlinear)\ndef test_load_riot_curvilinear():\n    # Load a cylindrical dataset of a full disk\n    ds = data_dir_load(riot_sedov_curvlinear)\n\n    assert (\"parthenon\", \"c.c.bulk.pressure\") in ds.field_list\n\n    ad = ds.all_data()\n    dth = ad[\"index\", \"dtheta\"]\n    vol = ad[\"index\", \"cell_volume\"]\n    rho = ad[\"parthenon\", \"c.c.bulk.rho\"]\n\n    assert np.all(np.abs(dth - 2 * np.pi) <= 1e-12)\n\n    total_mass = (rho * vol).sum()\n    assert np.all(np.abs(total_mass.value - 0.169646) <= 1e-8)\n"
  },
  {
    "path": "yt/frontends/ramses/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/ramses/api.py",
    "content": "from . import tests\nfrom .data_structures import RAMSESDataset\nfrom .definitions import field_aliases\nfrom .fields import RAMSESFieldInfo\nfrom .io import IOHandlerRAMSES\n"
  },
  {
    "path": "yt/frontends/ramses/data_structures.py",
    "content": "import os\nimport weakref\nfrom collections import defaultdict\nfrom functools import cached_property\nfrom itertools import product\nfrom pathlib import Path\n\nimport numpy as np\n\nfrom yt.arraytypes import blankRecordArray\nfrom yt.data_objects.index_subobjects.octree_subset import OctreeSubset\nfrom yt.data_objects.particle_filters import add_particle_filter\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import mylog, setdefaultattr\nfrom yt.geometry.geometry_handler import YTDataChunk\nfrom yt.geometry.oct_container import RAMSESOctreeContainer\nfrom yt.geometry.oct_geometry_handler import OctreeIndex\nfrom yt.utilities.cython_fortran_utils import FortranFile as fpu\nfrom yt.utilities.lib.cosmology_time import t_frw, tau_frw\nfrom yt.utilities.on_demand_imports import _f90nml as f90nml\nfrom yt.utilities.physical_constants import kb, mp\n\nfrom .definitions import (\n    OUTPUT_DIR_EXP,\n    OUTPUT_DIR_RE,\n    STANDARD_FILE_RE,\n    field_aliases,\n    particle_families,\n    ramses_header,\n)\nfrom .field_handlers import get_field_handlers\nfrom .fields import RAMSESFieldInfo\nfrom .hilbert import get_intersecting_cpus\nfrom .io_utils import fill_hydro, read_amr\nfrom .particle_handlers import get_particle_handlers\n\n\nclass RAMSESFileSanitizer:\n    \"\"\"A class to handle the different files that can be passed and associated\n    safely to a RAMSES output.\"\"\"\n\n    root_folder = None  # Path | None: path to the root folder\n    info_fname = None  # Path | None: path to the info file\n    group_name = None  # str | None: name of the first group folder (if any)\n\n    def __init__(self, filename):\n        # Make the resolve optional, so that it works with symlinks\n        paths_to_try = (Path(filename), Path(filename).resolve())\n\n        self.original_filename = filename\n\n        self.output_dir = None\n        self.info_fname = None\n\n        check_functions = (self.test_with_standard_file, self.test_with_folder_name)\n\n        # Loop on both the functions and the tested paths\n        for path, check_fun in product(paths_to_try, check_functions):\n            ok, output_dir, group_dir, info_fname = check_fun(path)\n            if ok:\n                break\n\n        # Early exit if the ok flag is False\n        if not ok:\n            return\n\n        self.root_folder = output_dir\n        self.group_name = group_dir.name if group_dir else None\n        self.info_fname = info_fname\n\n    def validate(self) -> None:\n        # raise a TypeError if self.original_filename is not a valid path\n        # we also want to expand '$USER' and '~' because os.path.exists('~') is always False\n        filename: str = os.path.expanduser(self.original_filename)\n\n        if not os.path.exists(filename):\n            raise FileNotFoundError(rf\"No such file or directory '{filename!s}'\")\n        if self.root_folder is None:\n            raise ValueError(\n                f\"Could not determine output directory from '{filename!s}'\\n\"\n                f\"Expected a directory name of form {OUTPUT_DIR_EXP!r} \"\n                \"containing an info_*.txt file and amr_* files.\"\n            )\n\n        # This last case is (erroneously ?) marked as unreachable by mypy\n        # If/when this bug is fixed upstream, mypy will warn that the unused\n        # 'type: ignore' comment can be removed\n        if self.info_fname is None:  # type: ignore [unreachable]\n            raise ValueError(f\"Failed to detect info file from '{filename!s}'\")\n\n    @property\n    def is_valid(self) -> bool:\n        try:\n            self.validate()\n        except (TypeError, FileNotFoundError, ValueError):\n            return False\n        else:\n            return True\n\n    @staticmethod\n    def check_standard_files(folder, iout):\n        \"\"\"Return True if the folder contains an amr file and the info file.\"\"\"\n        # Check that the \"amr_\" and \"info_\" files exist\n        ok = (folder / f\"amr_{iout}.out00001\").is_file()\n        ok &= (folder / f\"info_{iout}.txt\").is_file()\n        return ok\n\n    @staticmethod\n    def _match_output_and_group(\n        path: Path,\n    ) -> tuple[Path, Path | None, str | None]:\n        # Make sure we work with a directory of the form `output_XXXXX`\n        for p in (path, path.parent):\n            match = OUTPUT_DIR_RE.match(p.name)\n\n            if match:\n                path = p\n                break\n\n        if match is None:\n            return path, None, None\n\n        iout = match.group(1)\n\n        # See whether a folder named `group_YYYYY` exists\n        group_dir = path / \"group_00001\"\n        if group_dir.is_dir():\n            return path, group_dir, iout\n        else:\n            return path, None, iout\n\n    @classmethod\n    def test_with_folder_name(\n        cls, output_dir: Path\n    ) -> tuple[bool, Path | None, Path | None, Path | None]:\n        output_dir, group_dir, iout = cls._match_output_and_group(output_dir)\n        ok = output_dir.is_dir() and iout is not None\n\n        info_fname: Path | None\n\n        if ok:\n            parent_dir = group_dir or output_dir\n            ok &= cls.check_standard_files(parent_dir, iout)\n            info_fname = parent_dir / f\"info_{iout}.txt\"\n        else:\n            info_fname = None\n\n        return ok, output_dir, group_dir, info_fname\n\n    @classmethod\n    def test_with_standard_file(\n        cls, filename: Path\n    ) -> tuple[bool, Path | None, Path | None, Path | None]:\n        output_dir, group_dir, iout = cls._match_output_and_group(filename.parent)\n        ok = (\n            filename.is_file()\n            and STANDARD_FILE_RE.match(filename.name) is not None\n            and iout is not None\n        )\n\n        info_fname: Path | None\n\n        if ok:\n            parent_dir = group_dir or output_dir\n            ok &= cls.check_standard_files(parent_dir, iout)\n            info_fname = parent_dir / f\"info_{iout}.txt\"\n        else:\n            info_fname = None\n\n        return ok, output_dir, group_dir, info_fname\n\n\nclass RAMSESDomainFile:\n    _last_mask = None\n    _last_selector_id = None\n\n    _hydro_offset = None\n    _level_count = None\n\n    _oct_handler_initialized = False\n    _amr_header_initialized = False\n\n    def __init__(self, ds, domain_id):\n        self.ds = ds\n        self.domain_id = domain_id\n\n        num = ds.basename.split(\".\")[0].split(\"_\")[1]\n        basename = os.path.join(ds.directory, f\"%s_{num}.out{domain_id:05}\")\n        part_file_descriptor = os.path.join(ds.directory, \"part_file_descriptor.txt\")\n        if ds.num_groups > 0:\n            igroup = ((domain_id - 1) // ds.group_size) + 1\n            basename = os.path.join(\n                ds.root_folder, f\"group_{igroup:05}\", os.path.basename(basename)\n            )\n\n        for t in [\"grav\", \"amr\"]:\n            setattr(self, f\"{t}_fn\", basename % t)\n        self._part_file_descriptor = part_file_descriptor\n        self.max_level = self.ds.parameters[\"levelmax\"] - self.ds.parameters[\"levelmin\"]\n\n        # Autodetect field files\n        field_handlers = [FH(self) for FH in get_field_handlers() if FH.any_exist(ds)]\n        self.field_handlers = field_handlers\n        for fh in field_handlers:\n            mylog.debug(\"Detected fluid type %s in domain_id=%s\", fh.ftype, domain_id)\n            fh.detect_fields(ds)\n            # self._add_ftype(fh.ftype)\n\n        # Autodetect particle files\n        particle_handlers = [\n            PH(self) for PH in get_particle_handlers() if PH.any_exist(ds)\n        ]\n        self.particle_handlers = particle_handlers\n\n    def __repr__(self):\n        return f\"RAMSESDomainFile: {self.domain_id}\"\n\n    @property\n    def level_count(self):\n        lvl_count = None\n        for fh in self.field_handlers:\n            fh.offset\n            if lvl_count is None:\n                lvl_count = fh.level_count.copy()\n            else:\n                lvl_count += fh._level_count\n        return lvl_count\n\n    @property\n    def amr_file(self):\n        if hasattr(self, \"_amr_file\") and not self._amr_file.close:\n            self._amr_file.seek(0)\n            return self._amr_file\n\n        f = fpu(self.amr_fn)\n        self._amr_file = f\n        f.seek(0)\n        return f\n\n    def _read_amr_header(self):\n        if self._amr_header_initialized:\n            return\n        hvals = {}\n        with self.amr_file as f:\n            f.seek(0)\n\n            for header in ramses_header(hvals):\n                hvals.update(f.read_attrs(header))\n            # For speedup, skip reading of 'headl' and 'taill'\n            f.skip(2)\n            hvals[\"numbl\"] = f.read_vector(\"i\")\n\n            # That's the header, now we skip a few.\n            hvals[\"numbl\"] = np.array(hvals[\"numbl\"]).reshape(\n                (hvals[\"nlevelmax\"], hvals[\"ncpu\"])\n            )\n            f.skip()\n            if hvals[\"nboundary\"] > 0:\n                f.skip(2)\n                self._ngridbound = f.read_vector(\"i\").astype(\"int64\")\n            else:\n                self._ngridbound = np.zeros(hvals[\"nlevelmax\"], dtype=\"int64\")\n            _free_mem = f.read_attrs(((\"free_mem\", 5, \"i\"),))\n            _ordering = f.read_vector(\"c\")\n            f.skip(4)\n            # Now we're at the tree itself\n            # Now we iterate over each level and each CPU.\n            position = f.tell()\n\n        self._amr_header = hvals\n        self._amr_offset = position\n\n        # The maximum effective level is the deepest level\n        # that has a non-zero number of octs\n        nocts_to_this_level = hvals[\"numbl\"].sum(axis=1).cumsum()\n        self._max_level = (\n            np.argwhere(nocts_to_this_level == nocts_to_this_level[-1])[0][0]\n            - self.ds.parameters[\"levelmin\"]\n            + 1\n        )\n\n        # update levelmax\n        force_max_level, convention = self.ds._force_max_level\n        if convention == \"yt\":\n            force_max_level += self.ds.min_level + 1\n        self._amr_header[\"nlevelmax\"] = min(\n            force_max_level, self._amr_header[\"nlevelmax\"]\n        )\n        self._local_oct_count = hvals[\"numbl\"][\n            self.ds.min_level :, self.domain_id - 1\n        ].sum()\n        imin, imax = self.ds.min_level, self._amr_header[\"nlevelmax\"]\n        self._total_oct_count = hvals[\"numbl\"][imin:imax, :].sum(axis=0)\n\n        self._amr_header_initialized = True\n\n    @property\n    def ngridbound(self):\n        self._read_amr_header()\n        return self._ngridbound\n\n    @property\n    def amr_offset(self):\n        self._read_amr_header()\n        return self._amr_offset\n\n    @property\n    def max_level(self):\n        self._read_amr_header()\n        return self._max_level\n\n    @max_level.setter\n    def max_level(self, value):\n        self._max_level = value\n\n    @property\n    def total_oct_count(self):\n        self._read_amr_header()\n        return self._total_oct_count\n\n    @property\n    def local_oct_count(self):\n        self._read_amr_header()\n        return self._local_oct_count\n\n    @property\n    def amr_header(self):\n        self._read_amr_header()\n        return self._amr_header\n\n    @cached_property\n    def oct_handler(self):\n        \"\"\"Open the oct file, read in octs level-by-level.\n        For each oct, only the position, index, level and domain\n        are needed - its position in the octree is found automatically.\n        The most important is finding all the information to feed\n        oct_handler.add\n        \"\"\"\n        self._read_amr_header()\n        oct_handler = RAMSESOctreeContainer(\n            self.ds.domain_dimensions / 2,\n            self.ds.domain_left_edge,\n            self.ds.domain_right_edge,\n        )\n        root_nodes = self.amr_header[\"numbl\"][self.ds.min_level, :].sum()\n        oct_handler.allocate_domains(self.total_oct_count, root_nodes)\n        mylog.debug(\n            \"Reading domain AMR % 4i (%0.3e, %0.3e)\",\n            self.domain_id,\n            self.total_oct_count.sum(),\n            self.ngridbound.sum(),\n        )\n\n        with self.amr_file as f:\n            f.seek(self.amr_offset)\n\n            min_level = self.ds.min_level\n            max_level = read_amr(\n                f, self.amr_header, self.ngridbound, min_level, oct_handler\n            )\n\n            oct_handler.finalize()\n\n        new_max_level = max_level\n        if new_max_level > self.max_level:\n            raise RuntimeError(\n                f\"The maximum level detected in the AMR file ({new_max_level}) \"\n                f\" does not match the expected number {self.max_level}.\"\n            )\n        self.max_level = new_max_level\n        self._oct_handler_initialized = True\n\n        return oct_handler\n\n    def included(self, selector):\n        if getattr(selector, \"domain_id\", None) is not None:\n            return selector.domain_id == self.domain_id\n        domain_ids = self.oct_handler.domain_identify(selector)\n        return self.domain_id in domain_ids\n\n\nclass RAMSESDomainSubset(OctreeSubset):\n    _domain_offset = 1\n    _block_order = \"F\"\n\n    _base_domain = None\n\n    def __init__(\n        self,\n        base_region,\n        domain,\n        ds,\n        num_zones=2,\n        num_ghost_zones=0,\n        base_grid=None,\n    ):\n        super().__init__(base_region, domain, ds, num_zones, num_ghost_zones)\n\n        self._base_grid = base_grid\n\n        if num_ghost_zones > 0:\n            if not all(ds.periodicity):\n                mylog.warning(\n                    \"Ghost zones will wrongly assume the domain to be periodic.\"\n                )\n            # Create a base domain *with no self._base_domain.fwidth\n            base_domain = RAMSESDomainSubset(ds.all_data(), domain, ds, num_zones)\n            self._base_domain = base_domain\n        elif num_ghost_zones < 0:\n            raise RuntimeError(\n                \"Cannot initialize a domain subset with a negative number \"\n                f\"of ghost zones, was called with {num_ghost_zones=}\"\n            )\n\n    @property\n    def oct_handler(self):\n        return self.domain.oct_handler\n\n    def _fill_no_ghostzones(self, fd, fields, selector, file_handler):\n        ndim = self.ds.dimensionality\n        # Here we get a copy of the file, which we skip through and read the\n        # bits we want.\n        oct_handler = self.oct_handler\n        all_fields = [f for ft, f in file_handler.field_list]\n        fields = [f for ft, f in fields]\n        data = {}\n        cell_count = selector.count_oct_cells(self.oct_handler, self.domain_id)\n\n        # Initializing data container\n        for field in fields:\n            data[field] = np.zeros(cell_count, \"float64\")\n\n        # Do an early exit if the cell count is null\n        if cell_count == 0:\n            return data\n\n        level_inds, cell_inds, file_inds = self.oct_handler.file_index_octs(\n            selector, self.domain_id, cell_count\n        )\n\n        cpu_list = [self.domain_id - 1]\n        fill_hydro(\n            fd,\n            file_handler.offset,\n            file_handler.level_count,\n            cpu_list,\n            level_inds,\n            cell_inds,\n            file_inds,\n            ndim,\n            all_fields,\n            fields,\n            data,\n            oct_handler,\n            file_handler.single_precision,\n        )\n        return data\n\n    def _fill_with_ghostzones(\n        self, fd, fields, selector, file_handler, num_ghost_zones\n    ):\n        ndim = self.ds.dimensionality\n        ncpu = self.ds.parameters[\"ncpu\"]\n        # Here we get a copy of the file, which we skip through and read the\n        # bits we want.\n        oct_handler = self.oct_handler\n        all_fields = [f for ft, f in file_handler.field_list]\n        fields = [f for ft, f in fields]\n        tr = {}\n\n        cell_count = (\n            selector.count_octs(self.oct_handler, self.domain_id) * self.nz**ndim\n        )\n\n        # Initializing data container\n        for field in fields:\n            tr[field] = np.zeros(cell_count, \"float64\")\n\n        # Do an early exit if the cell count is null\n        if cell_count == 0:\n            return tr\n\n        gz_cache = getattr(self, \"_ghost_zone_cache\", None)\n        if gz_cache:\n            level_inds, cell_inds, file_inds, domain_inds = gz_cache\n        else:\n            gz_cache = (\n                level_inds,\n                cell_inds,\n                file_inds,\n                domain_inds,\n            ) = self.oct_handler.file_index_octs_with_ghost_zones(\n                selector, self.domain_id, cell_count, self._num_ghost_zones\n            )\n            self._ghost_zone_cache = gz_cache\n\n        cpu_list = list(range(ncpu))\n        fill_hydro(\n            fd,\n            file_handler.offset,\n            file_handler.level_count,\n            cpu_list,\n            level_inds,\n            cell_inds,\n            file_inds,\n            ndim,\n            all_fields,\n            fields,\n            tr,\n            oct_handler,\n            file_handler.single_precision,\n            domain_inds=domain_inds,\n        )\n        return tr\n\n    @property\n    def fwidth(self):\n        fwidth = super().fwidth\n        if self._num_ghost_zones > 0:\n            fwidth = fwidth.reshape(-1, 8, 3)\n            n_oct = fwidth.shape[0]\n            # new_fwidth contains the fwidth of the oct+ghost zones\n            # this is a constant array in each oct, so we simply copy\n            # the oct value using numpy fancy-indexing\n            new_fwidth = np.zeros((n_oct, self.nz**3, 3), dtype=fwidth.dtype)\n            new_fwidth[:, :, :] = fwidth[:, 0:1, :]\n            fwidth = new_fwidth.reshape(-1, 3)\n        return fwidth\n\n    @property\n    def fcoords(self):\n        num_ghost_zones = self._num_ghost_zones\n        if num_ghost_zones == 0:\n            return super().fcoords\n\n        oh = self.oct_handler\n\n        indices = oh.fill_index(self.selector).reshape(-1, 8)\n        oct_inds, cell_inds = oh.fill_octcellindex_neighbours(\n            self.selector, self._num_ghost_zones\n        )\n\n        N_per_oct = self.nz**3\n        oct_inds = oct_inds.reshape(-1, N_per_oct)\n        cell_inds = cell_inds.reshape(-1, N_per_oct)\n\n        inds = indices[oct_inds, cell_inds]\n\n        fcoords = self.ds.arr(oh.fcoords(self.selector)[inds].reshape(-1, 3), \"unitary\")\n\n        return fcoords\n\n    def fill(self, fd, fields, selector, file_handler):\n        if self._num_ghost_zones == 0:\n            return self._fill_no_ghostzones(fd, fields, selector, file_handler)\n        else:\n            return self._fill_with_ghostzones(\n                fd, fields, selector, file_handler, self._num_ghost_zones\n            )\n\n    def retrieve_ghost_zones(self, ngz, fields, smoothed=False):\n        if smoothed:\n            mylog.warning(\n                \"%s.retrieve_ghost_zones was called with the \"\n                \"`smoothed` argument set to True. This is not supported, \"\n                \"ignoring it.\",\n                self,\n            )\n            smoothed = False\n\n        _subset_with_gz = getattr(self, \"_subset_with_gz\", {})\n\n        try:\n            new_subset = _subset_with_gz[ngz]\n            mylog.debug(\n                \"Reusing previous subset with %s ghost zones for domain %s\",\n                ngz,\n                self.domain_id,\n            )\n        except KeyError:\n            new_subset = RAMSESDomainSubset(\n                self.base_region,\n                self.domain,\n                self.ds,\n                num_ghost_zones=ngz,\n                base_grid=self,\n            )\n            _subset_with_gz[ngz] = new_subset\n\n        # Cache the fields\n        new_subset.get_data(fields)\n        self._subset_with_gz = _subset_with_gz\n\n        return new_subset\n\n\nclass RAMSESIndex(OctreeIndex):\n    def __init__(self, ds, dataset_type=\"ramses\"):\n        self.fluid_field_list = ds._fields_in_file\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n\n        self.float_type = np.float64\n        super().__init__(ds, dataset_type)\n\n    def _initialize_oct_handler(self):\n        if self.ds._bbox is not None:\n            cpu_list = get_intersecting_cpus(self.dataset, self.dataset._bbox)\n        else:\n            cpu_list = range(self.dataset[\"ncpu\"])\n\n        self.domains = [RAMSESDomainFile(self.dataset, i + 1) for i in cpu_list]\n\n    @cached_property\n    def max_level(self):\n        force_max_level, convention = self.ds._force_max_level\n        if convention == \"yt\":\n            force_max_level += self.ds.min_level + 1\n        return min(force_max_level, max(dom.max_level for dom in self.domains))\n\n    @cached_property\n    def num_grids(self):\n        return sum(\n            dom.local_oct_count\n            for dom in self.domains  # + dom.ngridbound.sum()\n        )\n\n    def _detect_output_fields(self):\n        dsl = set()\n\n        # Get the detected particle fields\n        for ph in self.domains[0].particle_handlers:\n            dsl.update(set(ph.field_offsets.keys()))\n\n        self.particle_field_list = list(dsl)\n\n        # Get the detected fields\n        dsl = set()\n        for fh in self.domains[0].field_handlers:\n            dsl.update(set(fh.field_list))\n        self.fluid_field_list = list(dsl)\n\n        self.field_list = self.particle_field_list + self.fluid_field_list\n\n    def _identify_base_chunk(self, dobj):\n        use_fast_hilbert = (\n            hasattr(dobj, \"get_bbox\")\n            and self.ds.parameters[\"ordering type\"] == \"hilbert\"\n        )\n        if getattr(dobj, \"_chunk_info\", None) is None:\n            if use_fast_hilbert:\n                idoms = {\n                    idom + 1 for idom in get_intersecting_cpus(self.ds, dobj, factor=3)\n                }\n                # If the oct handler has been initialized, use it\n                domains = []\n                for dom in self.domains:\n                    # Hilbert indexing is conservative, so reject all those that\n                    # aren't in the bbox\n                    if dom.domain_id not in idoms:\n                        continue\n                    # If the domain has its oct handler, refine the selection\n                    if dom._oct_handler_initialized and not dom.included(dobj.selector):\n                        continue\n                    mylog.debug(\"Identified domain %s\", dom.domain_id)\n\n                    domains.append(dom)\n                if len(domains) >= 1:\n                    mylog.info(\n                        \"Identified % 5d/% 5d intersecting domains (% 5d through hilbert key indexing)\",\n                        len(domains),\n                        len(self.domains),\n                        len(idoms),\n                    )\n            else:\n                domains = [dom for dom in self.domains if dom.included(dobj.selector)]\n                if len(domains) >= 1:\n                    mylog.info(\"Identified %s intersecting domains\", len(domains))\n            base_region = getattr(dobj, \"base_region\", dobj)\n\n            subsets = [\n                RAMSESDomainSubset(\n                    base_region,\n                    domain,\n                    self.dataset,\n                    num_ghost_zones=dobj._num_ghost_zones,\n                )\n                for domain in domains\n            ]\n            dobj._chunk_info = subsets\n        dobj._current_chunk = list(self._chunk_all(dobj))[0]\n\n    def _chunk_all(self, dobj):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        yield YTDataChunk(dobj, \"all\", oobjs, None)\n\n    def _chunk_spatial(self, dobj, ngz, sort=None, preload_fields=None):\n        sobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for og in sobjs:\n            if ngz > 0:\n                g = og.retrieve_ghost_zones(ngz, [], smoothed=True)\n            else:\n                g = og\n            yield YTDataChunk(dobj, \"spatial\", [g], None)\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for subset in oobjs:\n            yield YTDataChunk(dobj, \"io\", [subset], None, cache=cache)\n\n    def _initialize_level_stats(self):\n        levels = sum(dom.level_count for dom in self.domains)\n        desc = {\"names\": [\"numcells\", \"level\"], \"formats\": [\"int64\"] * 2}\n        max_level = self.dataset.min_level + self.dataset.max_level + 2\n        self.level_stats = blankRecordArray(desc, max_level)\n        self.level_stats[\"level\"] = list(range(max_level))\n        self.level_stats[\"numcells\"] = [0 for i in range(max_level)]\n        for level in range(self.dataset.min_level + 1):\n            self.level_stats[level + 1][\"numcells\"] = 2 ** (\n                level * self.dataset.dimensionality\n            )\n        for level in range(levels.shape[1]):\n            ncell = levels[:, level].sum()\n            self.level_stats[level + self.dataset.min_level + 1][\"numcells\"] = ncell\n\n    def _get_particle_type_counts(self):\n        npart = 0\n        npart = {k: 0 for k in self.ds.particle_types if k != \"all\"}\n        for dom in self.domains:\n            for fh in dom.particle_handlers:\n                count = fh.local_particle_count\n                npart[fh.ptype] += count\n\n        return npart\n\n    def print_stats(self):\n        \"\"\"\n        Prints out (stdout) relevant information about the simulation\n\n        This function prints information based on the fluid on the grids,\n        and therefore does not work for DM only runs.\n        \"\"\"\n        if not self.fluid_field_list:\n            print(\"This function is not implemented for DM only runs\")\n            return\n\n        self._initialize_level_stats()\n\n        header = \"{:>3}\\t{:>14}\\t{:>14}\".format(\"level\", \"# cells\", \"# cells^3\")\n        print(header)\n        print(f\"{len(header.expandtabs()) * '-'}\")\n        for level in range(self.dataset.min_level + self.dataset.max_level + 2):\n            ncells = self.level_stats[\"numcells\"][level]\n            print(f\"{level:>3}\\t{ncells:>14}\\t{np.ceil(ncells ** (1.0 / 3)):>14}\")\n        print(\"-\" * 46)\n        print(f\"   \\t{self.level_stats['numcells'].sum():>14}\")\n        print(\"\\n\")\n\n        dx = self.get_smallest_dx()\n        try:\n            print(f\"z = {self.dataset.current_redshift:0.8f}\")\n        except Exception:\n            pass\n        print(\n            \"t = {:0.8e} = {:0.8e} = {:0.8e}\".format(\n                self.ds.current_time.in_units(\"code_time\"),\n                self.ds.current_time.in_units(\"s\"),\n                self.ds.current_time.in_units(\"yr\"),\n            )\n        )\n        print(\"\\nSmallest Cell:\")\n        for item in (\"Mpc\", \"pc\", \"AU\", \"cm\"):\n            print(f\"\\tWidth: {dx.in_units(item):0.3e}\")\n\n\nclass RAMSESDataset(Dataset):\n    _index_class = RAMSESIndex\n    _field_info_class = RAMSESFieldInfo\n    gamma = 1.4  # This will get replaced on hydro_fn open\n\n    # RAMSES-specific parameters\n    force_cosmological: bool | None\n    _force_max_level: tuple[int, str]\n    _bbox: list[list[float]] | None\n    _self_shielding: bool | None = None\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"ramses\",\n        fields=None,\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        extra_particle_fields=None,\n        cosmological=None,\n        bbox=None,\n        max_level=None,\n        max_level_convention=None,\n        default_species_fields=None,\n        self_shielding=None,\n        use_conformal_time=None,\n    ):\n        # Here we want to initiate a traceback, if the reader is not built.\n        if isinstance(fields, str):\n            fields = field_aliases[fields]\n        \"\"\"\n        fields: list[tuple[str, str]] | list[str] | None\n        An array of hydro variable fields in order of position in the\n        hydro_XXXXX.outYYYYY file. If set to None, will try a default set of fields.\n\n        extra_particle_fields:\n        An array of extra particle variables in order of position in the\n        particle_XXXXX.outYYYYY file.\n\n        cosmological:\n        If set to None, automatically detect cosmological simulation.\n        If a boolean, force its value.\n\n        self_shielding:\n        If set to True, assume gas is self-shielded above 0.01 mp/cm^3.\n        This affects the fields related to cooling and the mean molecular weight.\n        \"\"\"\n\n        self._fields_in_file: list[tuple[str, str]] = []\n        if fields:\n            for field in fields:\n                if isinstance(field, tuple):\n                    self._fields_in_file.append(field)\n                else:\n                    self._fields_in_file.append((field, \"d\"))\n        # By default, extra fields have not triggered a warning\n        self._warned_extra_fields = defaultdict(lambda: False)\n        self._extra_particle_fields = extra_particle_fields\n        self.force_cosmological = cosmological\n        self._bbox = bbox\n\n        self._force_max_level = self._sanitize_max_level(\n            max_level, max_level_convention\n        )\n\n        file_handler = RAMSESFileSanitizer(filename)\n\n        # ensure validation happens even if the class is instantiated\n        # directly rather than from yt.load\n        file_handler.validate()\n\n        # Sanitize the filename\n        info_fname = file_handler.info_fname\n\n        if file_handler.group_name is not None:\n            self.num_groups = len(\n                [_ for _ in file_handler.root_folder.glob(\"group_?????\") if _.is_dir()]\n            )\n        else:\n            self.num_groups = 0\n        self.root_folder = file_handler.root_folder\n\n        Dataset.__init__(\n            self,\n            info_fname,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n\n        # Add the particle types\n        ptypes = []\n        for PH in get_particle_handlers():\n            if PH.any_exist(self):\n                ptypes.append(PH.ptype)\n\n        ptypes = tuple(ptypes)\n        self.particle_types = self.particle_types_raw = ptypes\n\n        # Add the fluid types\n        for FH in get_field_handlers():\n            FH.purge_detected_fields(self)\n            if FH.any_exist(self):\n                self.fluid_types += (FH.ftype,)\n\n        if use_conformal_time is not None:\n            self.use_conformal_time = use_conformal_time\n        elif self.cosmological_simulation:\n            if \"rt\" in self.fluid_types:\n                self.use_conformal_time = False\n            else:\n                self.use_conformal_time = True\n        else:\n            self.use_conformal_time = False\n\n        self.storage_filename = storage_filename\n\n        self.self_shielding = self_shielding\n\n    @property\n    def self_shielding(self) -> bool:\n        if self._self_shielding is not None:\n            return self._self_shielding\n\n        # Read namelist.txt file (if any)\n        has_namelist = self.read_namelist()\n\n        if not has_namelist:\n            self._self_shielding = False\n            return self._self_shielding\n\n        nml = self.parameters[\"namelist\"]\n\n        # \"self_shielding\" is stored in physics_params in older versions of the code\n        physics_params = nml.get(\"physics_params\", default={})\n        # and in \"cooling_params\" in more recent ones\n        cooling_params = nml.get(\"cooling_params\", default={})\n\n        self_shielding = physics_params.get(\"self_shielding\", False)\n        self_shielding |= cooling_params.get(\"self_shielding\", False)\n\n        self._self_shielding = self_shielding\n        return self_shielding\n\n    @self_shielding.setter\n    def self_shielding(self, value):\n        self._self_shielding = value\n\n    @staticmethod\n    def _sanitize_max_level(max_level, max_level_convention):\n        # NOTE: the initialisation of the dataset class sets\n        #       self.min_level _and_ requires force_max_level\n        #       to be set, so we cannot convert from to yt/ramses\n        #       conventions\n        if max_level is None and max_level_convention is None:\n            return (2**999, \"yt\")\n\n        # Check max_level is a valid, positive integer\n        if not isinstance(max_level, (int, np.integer)):\n            raise TypeError(\n                f\"Expected `max_level` to be a positive integer, got {max_level} \"\n                f\"with type {type(max_level)} instead.\"\n            )\n        if max_level < 0:\n            raise ValueError(\n                f\"Expected `max_level` to be a positive integer, got {max_level} \"\n                \"instead.\"\n            )\n\n        # Check max_level_convention is set and acceptable\n        if max_level_convention is None:\n            raise ValueError(\n                f\"Received `max_level`={max_level}, but no `max_level_convention`. \"\n                \"Valid conventions are 'yt' and 'ramses'.\"\n            )\n        if max_level_convention not in (\"ramses\", \"yt\"):\n            raise ValueError(\n                f\"Invalid convention {max_level_convention}. \"\n                \"Valid choices are 'yt' and 'ramses'.\"\n            )\n        return (max_level, max_level_convention)\n\n    def create_field_info(self, *args, **kwa):\n        \"\"\"Extend create_field_info to add the particles types.\"\"\"\n        super().create_field_info(*args, **kwa)\n        # Register particle filters\n        if (\"io\", \"particle_family\") in self.field_list:\n            for fname, value in particle_families.items():\n\n                def loc(val):\n                    def closure(pfilter, data):\n                        filter = data[pfilter.filtered_type, \"particle_family\"] == val\n                        return filter\n\n                    return closure\n\n                add_particle_filter(\n                    fname, loc(value), filtered_type=\"io\", requires=[\"particle_family\"]\n                )\n\n            for k in particle_families.keys():\n                mylog.info(\"Adding particle_type: %s\", k)\n                self.add_particle_filter(f\"{k}\")\n\n    def __str__(self):\n        return self.basename.rsplit(\".\", 1)[0]\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Generates the conversion to various physical _units based on the parameter file\n        \"\"\"\n        # loading the units from the info file\n        boxlen = self.parameters[\"boxlen\"]\n        length_unit = self.parameters[\"unit_l\"]\n        density_unit = self.parameters[\"unit_d\"]\n        time_unit = self.parameters[\"unit_t\"]\n\n        # calculating derived units (except velocity and temperature, done below)\n        mass_unit = density_unit * length_unit**3\n        magnetic_unit = np.sqrt(4 * np.pi * mass_unit / (time_unit**2 * length_unit))\n        pressure_unit = density_unit * (length_unit / time_unit) ** 2\n\n        setdefaultattr(self, \"density_unit\", self.quan(density_unit, \"g/cm**3\"))\n        setdefaultattr(self, \"magnetic_unit\", self.quan(magnetic_unit, \"gauss\"))\n        setdefaultattr(self, \"pressure_unit\", self.quan(pressure_unit, \"dyne/cm**2\"))\n        setdefaultattr(self, \"time_unit\", self.quan(time_unit, \"s\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(mass_unit, \"g\"))\n        setdefaultattr(\n            self, \"velocity_unit\", self.quan(length_unit, \"cm\") / self.time_unit\n        )\n        temperature_unit = self.velocity_unit**2 * mp / kb\n        setdefaultattr(self, \"temperature_unit\", temperature_unit.in_units(\"K\"))\n\n        # Only the length unit get scales by a factor of boxlen\n        setdefaultattr(self, \"length_unit\", self.quan(length_unit * boxlen, \"cm\"))\n\n    def _parse_parameter_file(self):\n        # hardcoded for now\n        # These should be explicitly obtained from the file, but for now that\n        # will wait until a reorganization of the source tree and better\n        # generalization.\n        self.dimensionality = 3\n        self.refine_by = 2\n        self.parameters[\"HydroMethod\"] = \"ramses\"\n        self.parameters[\"Time\"] = 1.0  # default unit is 1...\n\n        # We now execute the same logic Oliver's code does\n        rheader = {}\n\n        def read_rhs(f, cast):\n            line = f.readline().strip()\n\n            if line and \"=\" in line:\n                key, val = (_.strip() for _ in line.split(\"=\"))\n                rheader[key] = cast(val)\n                return key\n            else:\n                return None\n\n        def cast_a_else_b(cast_a, cast_b):\n            def caster(val):\n                try:\n                    return cast_a(val)\n                except ValueError:\n                    return cast_b(val)\n\n            return caster\n\n        with open(self.parameter_filename) as f:\n            # Standard: first six are ncpu, ndim, levelmin, levelmax, ngridmax, nstep_coarse\n            for _ in range(6):\n                read_rhs(f, int)\n            f.readline()\n            # Standard: next 11 are boxlen, time, aexp, h0, omega_m, omega_l, omega_k, omega_b, unit_l, unit_d, unit_t\n            for _ in range(11):\n                key = read_rhs(f, float)\n\n            # Read non standard extra fields until hitting the ordering type\n            while key != \"ordering type\":\n                key = read_rhs(f, cast_a_else_b(float, str))\n\n            # This next line deserves some comment.  We specify a min_level that\n            # corresponds to the minimum level in the RAMSES simulation.  RAMSES is\n            # one-indexed, but it also does refer to the *oct* dimensions -- so\n            # this means that a levelmin of 1 would have *1* oct in it.  So a\n            # levelmin of 2 would have 8 octs at the root mesh level.\n            self.min_level = rheader[\"levelmin\"] - 1\n            # Now we read the hilbert indices\n            self.hilbert_indices = {}\n            if rheader[\"ordering type\"] == \"hilbert\":\n                f.readline()  # header\n                for _ in range(rheader[\"ncpu\"]):\n                    dom, mi, ma = f.readline().split()\n                    self.hilbert_indices[int(dom)] = (float(mi), float(ma))\n\n        if rheader[\"ordering type\"] != \"hilbert\" and self._bbox is not None:\n            raise NotImplementedError(\n                f\"The ordering {rheader['ordering type']} \"\n                \"is not compatible with the `bbox` argument.\"\n            )\n        self.parameters.update(rheader)\n        self.domain_left_edge = np.zeros(3, dtype=\"float64\")\n        self.domain_dimensions = np.ones(3, dtype=\"int32\") * 2 ** (self.min_level + 1)\n        self.domain_right_edge = np.ones(3, dtype=\"float64\")\n        # This is likely not true, but it's not clear\n        # how to determine the boundary conditions\n        self._periodicity = (True, True, True)\n\n        if self.force_cosmological is not None:\n            is_cosmological = self.force_cosmological\n        else:\n            # These conditions seem to always be true for non-cosmological datasets\n            is_cosmological = not (\n                rheader[\"time\"] >= 0 and rheader[\"H0\"] == 1 and rheader[\"aexp\"] == 1\n            )\n        if not is_cosmological:\n            self.cosmological_simulation = False\n            self.current_redshift = 0\n            self.hubble_constant = 0\n            self.omega_matter = 0\n            self.omega_lambda = 0\n        else:\n            self.cosmological_simulation = True\n            self.current_redshift = (1.0 / rheader[\"aexp\"]) - 1.0\n            self.omega_lambda = rheader[\"omega_l\"]\n            self.omega_matter = rheader[\"omega_m\"]\n            self.hubble_constant = rheader[\"H0\"] / 100.0  # This is H100\n\n        force_max_level, convention = self._force_max_level\n        if convention == \"yt\":\n            force_max_level += self.min_level + 1\n        self.max_level = min(force_max_level, rheader[\"levelmax\"]) - self.min_level - 1\n\n        if not self.cosmological_simulation:\n            self.current_time = self.parameters[\"time\"]\n        else:\n            aexp_grid = np.geomspace(1e-3, 1, 2_000, endpoint=False)\n            z_grid = 1 / aexp_grid - 1\n            self.tau_frw = tau_frw(self, z_grid)\n            self.t_frw = t_frw(self, z_grid)\n            self.current_time = t_frw(self, self.current_redshift).to(\"Gyr\")\n\n        if self.num_groups > 0:\n            self.group_size = rheader[\"ncpu\"] // self.num_groups\n\n        self.read_namelist()\n\n    def read_namelist(self) -> bool:\n        \"\"\"Read the namelist.txt file in the output folder, if present\"\"\"\n        namelist_file = os.path.join(self.root_folder, \"namelist.txt\")\n        if not os.path.exists(namelist_file):\n            return False\n\n        try:\n            with open(namelist_file) as f:\n                nml = f90nml.read(f)\n        except ImportError as err:\n            mylog.warning(\n                \"`namelist.txt` file found but missing package f90nml to read it:\",\n                exc_info=err,\n            )\n            return False\n        except (ValueError, StopIteration, AssertionError) as err:\n            # Note: f90nml may raise a StopIteration, a ValueError or an AssertionError if\n            # the namelist is not valid.\n            mylog.warning(\n                \"Could not parse `namelist.txt` file as it was malformed:\",\n                exc_info=err,\n            )\n            return False\n\n        self.parameters[\"namelist\"] = nml\n        return True\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        return RAMSESFileSanitizer(filename).is_valid\n"
  },
  {
    "path": "yt/frontends/ramses/definitions.py",
    "content": "# These functions are RAMSES-specific\nimport re\n\nfrom yt.funcs import mylog\nfrom yt.utilities.configure import YTConfig, configuration_callbacks\n\n\ndef ramses_header(hvals):\n    header = (\n        (\"ncpu\", 1, \"i\"),\n        (\"ndim\", 1, \"i\"),\n        (\"nx\", 3, \"i\"),\n        (\"nlevelmax\", 1, \"i\"),\n        (\"ngridmax\", 1, \"i\"),\n        (\"nboundary\", 1, \"i\"),\n        (\"ngrid_current\", 1, \"i\"),\n        (\"boxlen\", 1, \"d\"),\n        (\"nout\", 3, \"i\"),\n    )\n    yield header\n    # TODO: REMOVE\n    noutput, iout, ifout = hvals[\"nout\"]\n    next_set = (\n        (\"tout\", noutput, \"d\"),\n        (\"aout\", noutput, \"d\"),\n        (\"t\", 1, \"d\"),\n        (\"dtold\", hvals[\"nlevelmax\"], \"d\"),\n        (\"dtnew\", hvals[\"nlevelmax\"], \"d\"),\n        (\"nstep\", 2, \"i\"),\n        (\"stat\", 3, \"d\"),\n        (\"cosm\", 7, \"d\"),\n        (\"timing\", 5, \"d\"),\n        (\"mass_sph\", 1, \"d\", True),\n    )\n    yield next_set\n\n\nfield_aliases = {\n    \"standard_five\": (\"Density\", \"x-velocity\", \"y-velocity\", \"z-velocity\", \"Pressure\"),\n    \"standard_six\": (\n        \"Density\",\n        \"x-velocity\",\n        \"y-velocity\",\n        \"z-velocity\",\n        \"Pressure\",\n        \"Metallicity\",\n    ),\n}\n\n## Regular expressions used to parse file descriptors\nVERSION_RE = re.compile(r\"# version: *(\\d+)\")\n# This will match comma-separated strings, discarding whitespaces\n# on the left hand side\nVAR_DESC_RE = re.compile(r\"\\s*([^\\s]+),\\s*([^\\s]+),\\s*([^\\s]+)\")\n\nOUTPUT_DIR_EXP = r\"output_(\\d{5})\"\nOUTPUT_DIR_RE = re.compile(OUTPUT_DIR_EXP)\nSTANDARD_FILE_RE = re.compile(r\"((amr|hydro|part|grav)_\\d{5}\\.out\\d{5}|info_\\d{5}.txt)\")\n\n\n## Configure family mapping\nparticle_families = {\n    \"DM\": 1,\n    \"star\": 2,\n    \"cloud\": 3,\n    \"dust\": 4,\n    \"star_tracer\": -2,\n    \"cloud_tracer\": -3,\n    \"dust_tracer\": -4,\n    \"gas_tracer\": 0,\n}\n\n\ndef _setup_ramses_particle_families(ytcfg: YTConfig) -> None:\n    if not ytcfg.has_section(\"ramses-families\"):\n        return\n    for key in particle_families.keys():\n        val = ytcfg.get(\"ramses-families\", key, callback=None)\n        if val is not None:\n            mylog.info(\n                \"Changing family %s from %s to %s\", key, particle_families[key], val\n            )\n            particle_families[key] = val\n\n\nconfiguration_callbacks.append(_setup_ramses_particle_families)\n"
  },
  {
    "path": "yt/frontends/ramses/field_handlers.py",
    "content": "import abc\nimport glob\nimport os\nfrom functools import cached_property\n\nfrom yt.config import ytcfg\nfrom yt.funcs import mylog\nfrom yt.utilities.cython_fortran_utils import FortranFile\n\nfrom .io import _read_fluid_file_descriptor\nfrom .io_utils import read_offset\n\nFIELD_HANDLERS: set[type[\"FieldFileHandler\"]] = set()\n\n\ndef get_field_handlers():\n    return FIELD_HANDLERS\n\n\ndef register_field_handler(ph):\n    FIELD_HANDLERS.add(ph)\n\n\nDETECTED_FIELDS = {}  # type: ignore\n\n\ndef validate_field_precision(fields: list[tuple[str, str]]):\n    \"\"\"Check if all fields have the same precision.\n\n    Parameters\n    ----------\n    fields : list of (str, str)\n        List of tuples containing field names and their precision\n        ('f' for single, 'd' for double).\n    \"\"\"\n    if len(fields) == 0:\n        return\n\n    if not all(precision == fields[0][1] for _, precision in fields):\n        raise RuntimeError(\"Mixed precision in field list. This is not supported.\")\n\n    if fields[0][1] not in (\"f\", \"d\"):\n        raise ValueError(\n            f\"Unknown precision specifier '{fields[0][1]}'. \"\n            \"Only 'f' or 'd' are supported.\"\n        )\n\n\ndef get_fields_and_single_precision(\n    fields: list[tuple[str, str]],\n) -> tuple[list[str], bool]:\n    \"\"\"Get the common precision of fields.\n\n    Parameters\n    ----------\n    fields : list of (str, str)\n        List of tuples containing field names and their precision\n        ('f' for single, 'd' for double).\n\n    Returns\n    -------\n    tuple of (list of str, bool)\n        A tuple containing a list of field names and a boolean indicating\n        if all fields are single precision, False if double precision.\n    \"\"\"\n    field_names = [field for field, _ in fields]\n    validate_field_precision(fields)\n    if fields:\n        single_precision = fields[0][1] == \"f\"\n    else:\n        single_precision = False\n    return field_names, single_precision\n\n\nclass HandlerMixin:\n    \"\"\"This contains all the shared methods to handle RAMSES files.\n\n    This is not supposed to be user-facing.\n    \"\"\"\n\n    def setup_handler(self, domain):\n        \"\"\"\n        Initialize an instance of the class. This automatically sets\n        the full path to the file. This is not intended to be\n        overridden in most cases.\n\n        If you need more flexibility, rewrite this function to your\n        need in the inherited class.\n        \"\"\"\n        self.ds = ds = domain.ds\n        self.domain = domain\n        self.domain_id = domain.domain_id\n\n        basename = os.path.abspath(ds.root_folder)\n        iout = int(os.path.basename(ds.parameter_filename).split(\".\")[0].split(\"_\")[1])\n\n        if ds.num_groups > 0:\n            igroup = ((domain.domain_id - 1) // ds.group_size) + 1\n            full_path = os.path.join(\n                basename,\n                f\"group_{igroup:05d}\",\n                self.fname.format(iout=iout, icpu=domain.domain_id),\n            )\n        else:\n            full_path = os.path.join(\n                basename, self.fname.format(iout=iout, icpu=domain.domain_id)\n            )\n\n        if os.path.exists(full_path):\n            self.fname = full_path\n        else:\n            raise FileNotFoundError(\n                f\"Could not find {self._file_type} file (type: {self.ftype}). \"\n                f\"Tried {full_path}\"\n            )\n\n        if self.file_descriptor is not None:\n            if ds.num_groups > 0:\n                # The particle file descriptor is *only* in the first group\n                self.file_descriptor = os.path.join(\n                    basename, \"group_00001\", self.file_descriptor\n                )\n            else:\n                self.file_descriptor = os.path.join(basename, self.file_descriptor)\n\n    @property\n    def exists(self):\n        \"\"\"\n        This function should return True if the *file* the instance\n        exists. It is called for each file of the type found on the\n        disk.\n\n        By default, it just returns whether the file exists. Override\n        it for more complex cases.\n        \"\"\"\n        return os.path.exists(self.fname)\n\n    @property\n    def has_descriptor(self):\n        \"\"\"\n        This function should return True if a *file descriptor*\n        exists.\n\n        By default, it just returns whether the file exists. Override\n        it for more complex cases.\n        \"\"\"\n        return os.path.exists(self.file_descriptor)\n\n    @classmethod\n    def any_exist(cls, ds):\n        \"\"\"\n        This function should return True if the kind of particle\n        represented by the class exists in the dataset. It takes as\n        argument the class itself -not an instance- and a dataset.\n\n        Arguments\n        ---------\n        ds : a Ramses Dataset\n\n        Note\n        ----\n        This function is usually called once at the initialization of\n        the RAMSES Dataset structure to determine if the particle type\n        (e.g. regular particles) exists.\n        \"\"\"\n        if ds.unique_identifier in cls._unique_registry:\n            return cls._unique_registry[ds.unique_identifier]\n\n        iout = int(os.path.basename(ds.parameter_filename).split(\".\")[0].split(\"_\")[1])\n\n        fname = os.path.join(\n            os.path.split(ds.parameter_filename)[0], cls.fname.format(iout=iout, icpu=1)\n        )\n        exists = os.path.exists(fname)\n        cls._unique_registry[ds.unique_identifier] = exists\n\n        return exists\n\n\nclass FieldFileHandler(abc.ABC, HandlerMixin):\n    \"\"\"\n    Abstract class to handle particles in RAMSES. Each instance\n    represents a single file (one domain).\n\n    To add support to a new particle file, inherit from this class and\n    implement all functions containing a `NotImplementedError`.\n\n    See `SinkParticleFileHandler` for an example implementation.\"\"\"\n\n    _file_type = \"field\"\n\n    # These properties are static properties\n    ftype: str | None = None  # The name to give to the field type\n    fname: str | None = None  # The name of the file(s)\n    # The attributes of the header\n    attrs: tuple[tuple[str, int, str], ...] | None = None\n    known_fields = None  # A list of tuple containing the field name and its type\n    config_field: str | None = None  # Name of the config section (if any)\n\n    file_descriptor: str | None = None  # The name of the file descriptor (if any)\n\n    # These properties are computed dynamically\n    field_offsets = None  # Mapping from field to offset in file\n    field_types = (\n        None  # Mapping from field to the type of the data (float, integer, ...)\n    )\n    parameters: dict  # Parameters read from the header\n\n    def __init_subclass__(cls, *args, **kwargs):\n        \"\"\"\n        Registers subclasses at creation.\n        \"\"\"\n        super().__init_subclass__(*args, **kwargs)\n\n        if cls.ftype is not None:\n            register_field_handler(cls)\n\n        cls._unique_registry = {}\n        cls.parameters = {}\n        cls.rt_parameters = {}\n        return cls\n\n    def __init__(self, domain):\n        self.setup_handler(domain)\n\n    @classmethod\n    @abc.abstractmethod\n    def detect_fields(cls, ds) -> tuple[list[str], bool]:\n        \"\"\"\n        Called once to setup the fields of this type\n\n        It should set the following static variables:\n\n        * parameters: dictionary\n            Dictionary containing the variables. The keys should match\n            those of `cls.attrs`\n        * field_list: list of (ftype, fname)\n            The list of the field present in the file\n        \"\"\"\n        pass\n\n    @classmethod\n    def get_detected_fields(cls, ds) -> tuple[list[str], bool] | None:\n        \"\"\"\n        Get the detected fields from the registry.\n        \"\"\"\n        if ds.unique_identifier in DETECTED_FIELDS:\n            d = DETECTED_FIELDS[ds.unique_identifier]\n            if cls.ftype in d:\n                return d[cls.ftype]\n\n        return None\n\n    @classmethod\n    def set_detected_fields(cls, ds, fields, single_precision):\n        \"\"\"\n        Store the detected fields into the registry.\n        \"\"\"\n        if ds.unique_identifier not in DETECTED_FIELDS:\n            DETECTED_FIELDS[ds.unique_identifier] = {}\n\n        DETECTED_FIELDS[ds.unique_identifier].update(\n            {cls.ftype: (fields, single_precision)}\n        )\n\n    @classmethod\n    def purge_detected_fields(cls, ds):\n        \"\"\"\n        Purge the registry.\n\n        This should be called on dataset creation to force the field\n        detection to be called.\n        \"\"\"\n        if ds.unique_identifier in DETECTED_FIELDS:\n            DETECTED_FIELDS.pop(ds.unique_identifier)\n\n    @property\n    def level_count(self):\n        \"\"\"\n        Return the number of cells per level.\n        \"\"\"\n        if getattr(self, \"_level_count\", None) is not None:\n            return self._level_count\n        self.offset\n\n        return self._level_count\n\n    @property\n    def field_list(self):\n        field_list, _single_precision = self.detect_fields(self.ds)\n        return [(self.ftype, f) for f in field_list]\n\n    @property\n    def single_precision(self):\n        _field_list, single_precision = self.detect_fields(self.ds)\n        return single_precision\n\n    @cached_property\n    def offset(self):\n        \"\"\"\n        Compute the offsets of the fields.\n\n        By default, it skips the header (as defined by `cls.attrs`)\n        and computes the offset at each level.\n\n        It should be generic enough for most of the cases, but if the\n        *structure* of your fluid file is non-canonical, change this.\n        \"\"\"\n        nvars = len(self.field_list)\n\n        with FortranFile(self.fname) as fd:\n            # Skip headers\n            nskip = len(self.attrs)\n            fd.skip(nskip)\n            min_level = self.domain.ds.min_level\n\n            # The file is as follows:\n            # > headers\n            # loop over levels\n            #   loop over cpu domains\n            #     > <ilevel>: current level\n            #     > <nocts>: number of octs in level, domain\n            #     loop over <nvars> variables (positions, velocities, density, ...)\n            #       loop over <2*2*2> cells in each oct\n            #          > <data> with shape (nocts, )\n            #\n            # So there are 8 * nvars records each with length (nocts, )\n            # at each (level, cpus)\n\n            offset, level_count = read_offset(\n                fd,\n                min_level,\n                self.domain.domain_id,\n                self.parameters[self.ds.unique_identifier][\"nvar\"],\n                self.domain.amr_header,\n                Nskip=nvars * 8,\n                single_precision=self.single_precision,\n            )\n\n        self._level_count = level_count\n        return offset\n\n    @classmethod\n    def load_fields_from_yt_config(cls) -> tuple[list[str], bool]:\n        if cls.config_field and ytcfg.has_section(cls.config_field):\n            cfg = ytcfg.get(cls.config_field, \"fields\")\n            fields = []\n            for item in cfg:\n                item = item.strip()\n                if not item:\n                    continue\n                content = item.split(\",\")\n                if len(content) == 2:\n                    field_name, precision = content\n                elif len(content) == 1:\n                    field_name, precision = content[0], \"d\"\n                else:\n                    raise ValueError(\n                        f\"Invalid field specification '{item}' in config section \"\n                        f\"'{cls.config_field}'. Expected format: field_name[,precision]\"\n                    )\n                fields.append((field_name.strip(), precision.strip()))\n\n            return get_fields_and_single_precision(fields)\n\n        return ([], False)\n\n\nclass HydroFieldFileHandler(FieldFileHandler):\n    ftype = \"ramses\"\n    fname = \"hydro_{iout:05d}.out{icpu:05d}\"\n    file_descriptor = \"hydro_file_descriptor.txt\"\n    config_field = \"ramses-hydro\"\n\n    attrs = (\n        (\"ncpu\", 1, \"i\"),\n        (\"nvar\", 1, \"i\"),\n        (\"ndim\", 1, \"i\"),\n        (\"nlevelmax\", 1, \"i\"),\n        (\"nboundary\", 1, \"i\"),\n        (\"gamma\", 1, \"d\"),\n    )\n\n    @classmethod\n    def detect_fields(cls, ds) -> tuple[list[str], bool]:\n        # Try to get the detected fields\n        detected_fields = cls.get_detected_fields(ds)\n        if detected_fields:\n            return detected_fields\n\n        num = os.path.basename(ds.parameter_filename).split(\".\")[0].split(\"_\")[1]\n        testdomain = 1  # Just pick the first domain file to read\n        basename = os.path.join(ds.directory, f\"%s_{num}.out{testdomain:05}\")\n        fname = basename % \"hydro\"\n        fname_desc = os.path.join(ds.directory, cls.file_descriptor)\n\n        attrs = cls.attrs\n        with FortranFile(fname) as fd:\n            hvals = fd.read_attrs(attrs)\n        cls.parameters[ds.unique_identifier] = hvals\n\n        # Store some metadata\n        ds.gamma = hvals[\"gamma\"]\n        nvar = hvals[\"nvar\"]\n\n        ok = False\n        single_precision = False\n\n        if ds._fields_in_file:\n            # Case 1: fields are provided by users on construction of dataset\n            fields, single_precision = get_fields_and_single_precision(\n                ds._fields_in_file\n            )\n            ok = True\n        else:\n            # Case 2: fields are provided by users in the config\n            fields, single_precision = cls.load_fields_from_yt_config()\n\n            ok = len(fields) > 0\n\n        if not ok and os.path.exists(fname_desc):\n            # Case 3: there is a file descriptor\n            # Or there is an hydro file descriptor\n            mylog.debug(\"Reading hydro file descriptor.\")\n\n            field_with_precision = _read_fluid_file_descriptor(\n                fname_desc, prefix=\"hydro\"\n            )\n            fields, single_precision = get_fields_and_single_precision(\n                field_with_precision\n            )\n\n            # We get no fields for old-style hydro file descriptor\n            ok = len(fields) > 0\n\n        if not ok:\n            # Case 4: attempt autodetection with usual fields\n            foldername = os.path.abspath(os.path.dirname(ds.parameter_filename))\n            rt_flag = any(glob.glob(os.sep.join([foldername, \"info_rt_*.txt\"])))\n            if rt_flag:  # rt run\n                if nvar < 10:\n                    mylog.info(\"Detected RAMSES-RT file WITHOUT IR trapping.\")\n\n                    fields = [\n                        \"Density\",\n                        \"x-velocity\",\n                        \"y-velocity\",\n                        \"z-velocity\",\n                        \"Pressure\",\n                        \"Metallicity\",\n                        \"HII\",\n                        \"HeII\",\n                        \"HeIII\",\n                    ]\n                else:\n                    mylog.info(\"Detected RAMSES-RT file WITH IR trapping.\")\n\n                    fields = [\n                        \"Density\",\n                        \"x-velocity\",\n                        \"y-velocity\",\n                        \"z-velocity\",\n                        \"Pres_IR\",\n                        \"Pressure\",\n                        \"Metallicity\",\n                        \"HII\",\n                        \"HeII\",\n                        \"HeIII\",\n                    ]\n            else:\n                if nvar < 5:\n                    mylog.debug(\n                        \"nvar=%s is too small! YT doesn't currently \"\n                        \"support 1D/2D runs in RAMSES %s\"\n                    )\n                    raise ValueError\n                # Basic hydro runs\n                if nvar == 5:\n                    fields = [\n                        \"Density\",\n                        \"x-velocity\",\n                        \"y-velocity\",\n                        \"z-velocity\",\n                        \"Pressure\",\n                    ]\n                if nvar > 5 and nvar < 11:\n                    fields = [\n                        \"Density\",\n                        \"x-velocity\",\n                        \"y-velocity\",\n                        \"z-velocity\",\n                        \"Pressure\",\n                        \"Metallicity\",\n                    ]\n                # MHD runs - NOTE:\n                # THE MHD MODULE WILL SILENTLY ADD 3 TO THE NVAR IN THE MAKEFILE\n                if nvar == 11:\n                    fields = [\n                        \"Density\",\n                        \"x-velocity\",\n                        \"y-velocity\",\n                        \"z-velocity\",\n                        \"B_x_left\",\n                        \"B_y_left\",\n                        \"B_z_left\",\n                        \"B_x_right\",\n                        \"B_y_right\",\n                        \"B_z_right\",\n                        \"Pressure\",\n                    ]\n                if nvar > 11:\n                    fields = [\n                        \"Density\",\n                        \"x-velocity\",\n                        \"y-velocity\",\n                        \"z-velocity\",\n                        \"B_x_left\",\n                        \"B_y_left\",\n                        \"B_z_left\",\n                        \"B_x_right\",\n                        \"B_y_right\",\n                        \"B_z_right\",\n                        \"Pressure\",\n                        \"Metallicity\",\n                    ]\n            mylog.debug(\n                \"No fields specified by user; automatically setting fields array to %s\",\n                fields,\n            )\n\n        # Allow some wiggle room for users to add too many variables\n        count_extra = 0\n        while len(fields) < nvar:\n            fields.append(f\"var_{len(fields)}\")\n            count_extra += 1\n        if count_extra > 0:\n            mylog.debug(\"Detected %s extra fluid fields.\", count_extra)\n\n        cls.set_detected_fields(ds, fields, single_precision)\n\n        return (fields, single_precision)\n\n\nclass GravFieldFileHandler(FieldFileHandler):\n    ftype = \"gravity\"\n    fname = \"grav_{iout:05d}.out{icpu:05d}\"\n    config_field = \"ramses-grav\"\n\n    attrs = (\n        (\"ncpu\", 1, \"i\"),\n        (\"nvar\", 1, \"i\"),\n        (\"nlevelmax\", 1, \"i\"),\n        (\"nboundary\", 1, \"i\"),\n    )\n\n    @classmethod\n    def detect_fields(cls, ds) -> tuple[list[str], bool]:\n        # Try to get the detected fields\n        detected_fields = cls.get_detected_fields(ds)\n        if detected_fields:\n            return detected_fields\n\n        ndim = ds.dimensionality\n        iout = int(str(ds).split(\"_\")[1])\n        basedir = os.path.split(ds.parameter_filename)[0]\n        fname = os.path.join(basedir, cls.fname.format(iout=iout, icpu=1))\n        with FortranFile(fname) as fd:\n            cls.parameters[ds.unique_identifier] = fd.read_attrs(cls.attrs)\n\n        nvar = cls.parameters[ds.unique_identifier][\"nvar\"]\n        ndim = ds.dimensionality\n\n        fields, single_precision = cls.load_fields_from_yt_config()\n\n        if not fields:\n            if nvar == ndim + 1:\n                fields = [\"Potential\"] + [f\"{k}-acceleration\" for k in \"xyz\"[:ndim]]\n            else:\n                fields = [f\"{k}-acceleration\" for k in \"xyz\"[:ndim]]\n            ndetected = len(fields)\n\n            if ndetected != nvar and not ds._warned_extra_fields[\"gravity\"]:\n                mylog.info(\"Detected %s extra gravity fields.\", nvar - ndetected)\n                ds._warned_extra_fields[\"gravity\"] = True\n\n                for i in range(nvar - ndetected):\n                    fields.append(f\"var{i}\")\n\n        cls.set_detected_fields(ds, fields, single_precision)\n\n        return (fields, single_precision)\n\n\nclass RTFieldFileHandler(FieldFileHandler):\n    ftype = \"ramses-rt\"\n    fname = \"rt_{iout:05d}.out{icpu:05d}\"\n    file_descriptor = \"rt_file_descriptor.txt\"\n    config_field = \"ramses-rt\"\n\n    attrs = (\n        (\"ncpu\", 1, \"i\"),\n        (\"nvar\", 1, \"i\"),\n        (\"ndim\", 1, \"i\"),\n        (\"nlevelmax\", 1, \"i\"),\n        (\"nboundary\", 1, \"i\"),\n        (\"gamma\", 1, \"d\"),\n    )\n    rt_parameters: dict\n\n    @classmethod\n    def detect_fields(cls, ds):\n        # Try to get the detected fields\n        detected_fields = cls.get_detected_fields(ds)\n        if detected_fields:\n            return detected_fields\n\n        fname = ds.parameter_filename.replace(\"info_\", \"info_rt_\")\n\n        rheader = {}\n\n        def read_rhs(cast, group=None):\n            line = f.readline()\n            p, v = line.split(\"=\")\n            if group is not None:\n                rheader[f\"Group {group} {p.strip()}\"] = cast(v)\n            else:\n                rheader[p.strip()] = cast(v)\n\n        with open(fname) as f:\n            # Read nRTvar, nions, ngroups, iions\n            for _ in range(4):\n                read_rhs(int)\n\n            # Try to read rtprecision.\n            # Either it is present or the line is simply blank, so\n            # we try to parse the line as an int, and if it fails,\n            # we simply ignore it.\n            try:\n                read_rhs(int)\n                f.readline()\n            except ValueError:\n                pass\n\n            # Read X and Y fractions\n            for _ in range(2):\n                read_rhs(float)\n            f.readline()\n\n            # Read unit_np, unit_pf\n            for _ in range(2):\n                read_rhs(float)\n\n            # Read rt_c_frac\n            # Note: when using variable speed of light, this line will contain multiple\n            # values corresponding the the velocity at each level\n            read_rhs(lambda line: [float(e) for e in line.split()])\n            f.readline()\n\n            # Read n star, t2star, g_star\n            for _ in range(3):\n                read_rhs(float)\n            f.readline()\n            f.readline()\n\n            # Get global group properties (groupL0, groupL1, spec2group)\n            for _ in range(3):\n                read_rhs(lambda line: [float(e) for e in line.split()])\n\n            # get egy for each group (used to get proper energy densities)\n            line = f.readline().strip()\n            while line.startswith(\"---Group\"):\n                group = int(line.split()[1])\n                read_rhs(lambda line: [float(e) for e in line.split()], group)\n                # Skip cross sections weighted by number/energy\n                f.readline()\n                f.readline()\n                line = f.readline().strip()\n\n            cls.rt_parameters[ds.unique_identifier] = rheader\n\n        ngroups = rheader[\"nGroups\"]\n\n        iout = int(str(ds).split(\"_\")[1])\n        basedir = os.path.split(ds.parameter_filename)[0]\n        fname = os.path.join(basedir, cls.fname.format(iout=iout, icpu=1))\n        fname_desc = os.path.join(basedir, cls.file_descriptor)\n        with FortranFile(fname) as fd:\n            cls.parameters[ds.unique_identifier] = fd.read_attrs(cls.attrs)\n\n        ok = False\n\n        # Are fields provided by users in the config?\n        fields, single_precision = cls.load_fields_from_yt_config()\n        ok = len(fields) > 0\n\n        if not ok and os.path.exists(fname_desc):\n            # Case 3: there is a file descriptor\n            # Or there is an hydro file descriptor\n            mylog.debug(\"Reading rt file descriptor.\")\n\n            field_with_precision = _read_fluid_file_descriptor(fname_desc, prefix=\"rt\")\n            fields, single_precision = get_fields_and_single_precision(\n                field_with_precision\n            )\n\n            ok = len(fields) > 0\n\n        if not ok:\n            fields = []\n            tmp = [\n                \"Photon_density_%s\",\n                \"Photon_flux_x_%s\",\n                \"Photon_flux_y_%s\",\n                \"Photon_flux_z_%s\",\n            ]\n            for ng in range(ngroups):\n                fields.extend([t % (ng + 1) for t in tmp])\n\n        cls.set_detected_fields(ds, fields, single_precision)\n        return (fields, single_precision)\n\n    @classmethod\n    def get_rt_parameters(cls, ds):\n        if cls.rt_parameters[ds.unique_identifier]:\n            return cls.rt_parameters[ds.unique_identifier]\n\n        # Call detect fields to get the rt_parameters\n        cls.detect_fields(ds)\n        return cls.rt_parameters[ds.unique_identifier]\n"
  },
  {
    "path": "yt/frontends/ramses/fields.py",
    "content": "import os\nimport warnings\nfrom functools import partial\n\nimport numpy as np\n\nfrom yt import units\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_detector import FieldDetector\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.frontends.ramses.io import convert_ramses_conformal_time_to_physical_time\nfrom yt.utilities.cython_fortran_utils import FortranFile\nfrom yt.utilities.lib.cosmology_time import t_frw\nfrom yt.utilities.linear_interpolators import BilinearFieldInterpolator\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.physical_constants import (\n    boltzmann_constant_cgs,\n    mass_hydrogen_cgs,\n    mh,\n    mp,\n)\n\nfrom .field_handlers import RTFieldFileHandler\n\nb_units = \"code_magnetic\"\nra_units = \"code_length / code_time**2\"\nrho_units = \"code_density\"\nvel_units = \"code_velocity\"\npressure_units = \"code_pressure\"\nener_units = \"code_mass * code_velocity**2\"\nspecific_ener_units = \"code_velocity**2\"\nang_mom_units = \"code_mass * code_velocity * code_length\"\ncooling_function_units = \" erg * cm**3 /s\"\ncooling_function_prime_units = \" erg * cm**3 /s/K\"\nflux_unit = \"1 / code_length**2 / code_time\"\nnumber_density_unit = \"1 / code_length**3\"\n\nknown_species_masses = {\n    sp: mh * v\n    for sp, v in [\n        (\"HI\", 1.0),\n        (\"HII\", 1.0),\n        (\"Electron\", 1.0),\n        (\"HeI\", 4.0),\n        (\"HeII\", 4.0),\n        (\"HeIII\", 4.0),\n        (\"H2I\", 2.0),\n        (\"H2II\", 2.0),\n        (\"HM\", 1.0),\n        (\"DI\", 2.0),\n        (\"DII\", 2.0),\n        (\"HDI\", 3.0),\n    ]\n}\n\nknown_species_names = {\n    \"HI\": \"H_p0\",\n    \"HII\": \"H_p1\",\n    \"Electron\": \"El\",\n    \"HeI\": \"He_p0\",\n    \"HeII\": \"He_p1\",\n    \"HeIII\": \"He_p2\",\n    \"H2I\": \"H2_p0\",\n    \"H2II\": \"H2_p1\",\n    \"HM\": \"H_m1\",\n    \"DI\": \"D_p0\",\n    \"DII\": \"D_p1\",\n    \"HDI\": \"HD_p0\",\n}\n\n_cool_axes = (\"lognH\", \"logT\")  # , \"logTeq\")\n_cool_arrs = (\n    (\"cooling_primordial\", cooling_function_units),\n    (\"heating_primordial\", cooling_function_units),\n    (\"cooling_compton\", cooling_function_units),\n    (\"heating_compton\", cooling_function_units),\n    (\"cooling_metal\", cooling_function_units),\n    (\"cooling_primordial_prime\", cooling_function_prime_units),\n    (\"heating_primordial_prime\", cooling_function_prime_units),\n    (\"cooling_compton_prime\", cooling_function_prime_units),\n    (\"heating_compton_prime\", cooling_function_prime_units),\n    (\"cooling_metal_prime\", cooling_function_prime_units),\n    (\"mu\", None),\n    (\"abundances\", None),\n)\n_cool_species = (\n    \"Electron_number_density\",\n    \"HI_number_density\",\n    \"HII_number_density\",\n    \"HeI_number_density\",\n    \"HeII_number_density\",\n    \"HeIII_number_density\",\n)\n\n_X = 0.76  # H fraction, hardcoded\n_Y = 0.24  # He fraction, hardcoded\n\n\nclass RAMSESFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"Density\", (rho_units, [\"density\"], None)),\n        (\"x-velocity\", (vel_units, [\"velocity_x\"], None)),\n        (\"y-velocity\", (vel_units, [\"velocity_y\"], None)),\n        (\"z-velocity\", (vel_units, [\"velocity_z\"], None)),\n        (\"Pres_IR\", (pressure_units, [\"pres_IR\", \"pressure_IR\"], None)),\n        (\"Pressure\", (pressure_units, [\"pressure\"], None)),\n        (\"Metallicity\", (\"\", [\"metallicity\"], None)),\n        (\"HII\", (\"\", [\"H_p1_fraction\"], None)),\n        (\"HeII\", (\"\", [\"He_p1_fraction\"], None)),\n        (\"HeIII\", (\"\", [\"He_p2_fraction\"], None)),\n        (\"x-acceleration\", (ra_units, [\"acceleration_x\"], None)),\n        (\"y-acceleration\", (ra_units, [\"acceleration_y\"], None)),\n        (\"z-acceleration\", (ra_units, [\"acceleration_z\"], None)),\n        (\"Potential\", (specific_ener_units, [\"potential\"], None)),\n        (\"B_x_left\", (b_units, [\"magnetic_field_x_left\"], None)),\n        (\"B_x_right\", (b_units, [\"magnetic_field_x_right\"], None)),\n        (\"B_y_left\", (b_units, [\"magnetic_field_y_left\"], None)),\n        (\"B_y_right\", (b_units, [\"magnetic_field_y_right\"], None)),\n        (\"B_z_left\", (b_units, [\"magnetic_field_z_left\"], None)),\n        (\"B_z_right\", (b_units, [\"magnetic_field_z_right\"], None)),\n    )\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_position_z\", (\"code_length\", [], None)),\n        (\"particle_velocity_x\", (vel_units, [], None)),\n        (\"particle_velocity_y\", (vel_units, [], None)),\n        (\"particle_velocity_z\", (vel_units, [], None)),\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"particle_identity\", (\"\", [\"particle_index\"], None)),\n        (\"particle_refinement_level\", (\"\", [], None)),\n        (\"particle_birth_time\", (\"code_time\", [\"age\"], None)),\n        (\"conformal_birth_time\", (\"\", [], None)),\n        (\"particle_metallicity\", (\"\", [], None)),\n        (\"particle_family\", (\"\", [], None)),\n        (\"particle_tag\", (\"\", [], None)),\n        # sink field parameters\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"particle_angular_momentum_x\", (ang_mom_units, [], None)),\n        (\"particle_angular_momentum_y\", (ang_mom_units, [], None)),\n        (\"particle_angular_momentum_z\", (ang_mom_units, [], None)),\n        (\"particle_formation_time\", (\"code_time\", [], None)),\n        (\"particle_accretion_rate\", (\"code_mass/code_time\", [], None)),\n        (\"particle_delta_mass\", (\"code_mass\", [], None)),\n        (\"particle_rho_gas\", (rho_units, [], None)),\n        (\"particle_cs**2\", (vel_units, [], None)),\n        (\"particle_etherm\", (ener_units, [], None)),\n        (\"particle_velocity_x_gas\", (vel_units, [], None)),\n        (\"particle_velocity_y_gas\", (vel_units, [], None)),\n        (\"particle_velocity_z_gas\", (vel_units, [], None)),\n        (\"particle_mass_bh\", (\"code_mass\", [], None)),\n        (\"particle_level\", (\"\", [], None)),\n        (\"particle_radius_star\", (\"code_length\", [], None)),\n    )\n\n    known_sink_fields: KnownFieldsT = (\n        (\"particle_position_x\", (\"code_length\", [], None)),\n        (\"particle_position_y\", (\"code_length\", [], None)),\n        (\"particle_position_z\", (\"code_length\", [], None)),\n        (\"particle_velocity_x\", (vel_units, [], None)),\n        (\"particle_velocity_y\", (vel_units, [], None)),\n        (\"particle_velocity_z\", (vel_units, [], None)),\n        (\"particle_mass\", (\"code_mass\", [], None)),\n        (\"particle_identifier\", (\"\", [\"particle_index\"], None)),\n        (\"particle_birth_time\", (\"code_time\", [\"age\"], None)),\n        (\"BH_real_accretion\", (\"code_mass/code_time\", [], None)),\n        (\"BH_bondi_accretion\", (\"code_mass/code_time\", [], None)),\n        (\"BH_eddington_accretion\", (\"code_mass/code_time\", [], None)),\n        (\"BH_esave\", (ener_units, [], None)),\n        (\"gas_spin_x\", (ang_mom_units, [], None)),\n        (\"gas_spin_y\", (ang_mom_units, [], None)),\n        (\"gas_spin_z\", (ang_mom_units, [], None)),\n        (\"BH_spin_x\", (\"\", [], None)),\n        (\"BH_spin_y\", (\"\", [], None)),\n        (\"BH_spin_z\", (\"\", [], None)),\n        (\"BH_spin\", (ang_mom_units, [], None)),\n        (\"BH_efficiency\", (\"\", [], None)),\n    )\n\n    def setup_particle_fields(self, ptype):\n        super().setup_particle_fields(ptype)\n\n        def star_age_from_conformal_cosmo(data):\n            conformal_age = data[ptype, \"conformal_birth_time\"]\n            birth_time = convert_ramses_conformal_time_to_physical_time(\n                data.ds, conformal_age\n            )\n            return data.ds.current_time - birth_time\n\n        def star_age_from_physical_cosmo(data):\n            H0 = float(\n                data.ds.quan(data.ds.hubble_constant * 100, \"km/s/Mpc\").to(\"1/Gyr\")\n            )\n            times = data[ptype, \"conformal_birth_time\"].value\n            time_tot = float(t_frw(data.ds, 0) * H0)\n            birth_time = (time_tot + times) / H0\n            t_out = float(data.ds.current_time.to(\"Gyr\"))\n            return data.apply_units(t_out - birth_time, \"Gyr\")\n\n        def star_age(data):\n            formation_time = data[ptype, \"particle_birth_time\"]\n            return data.ds.current_time - formation_time\n\n        if self.ds.cosmological_simulation and self.ds.use_conformal_time:\n            fun = star_age_from_conformal_cosmo\n        elif self.ds.cosmological_simulation:\n            fun = star_age_from_physical_cosmo\n        else:\n            fun = star_age\n\n        self.add_field(\n            (ptype, \"star_age\"),\n            sampling_type=\"particle\",\n            function=fun,\n            units=self.ds.unit_system[\"time\"],\n        )\n\n    def setup_fluid_fields(self):\n        def _temperature_over_mu(data):\n            rv = data[\"gas\", \"pressure\"] / data[\"gas\", \"density\"]\n            rv *= mass_hydrogen_cgs / boltzmann_constant_cgs\n            return rv\n\n        self.add_field(\n            (\"gas\", \"temperature_over_mu\"),\n            sampling_type=\"cell\",\n            function=_temperature_over_mu,\n            units=self.ds.unit_system[\"temperature\"],\n        )\n        found_cooling_fields = self.create_cooling_fields()\n\n        if found_cooling_fields:\n\n            def _temperature(data):\n                return data[\"gas\", \"temperature_over_mu\"] * data[\"gas\", \"mu\"]\n\n        else:\n\n            def _temperature(data):\n                if not isinstance(data, FieldDetector):\n                    warnings.warn(\n                        \"Trying to calculate temperature but the cooling tables \"\n                        \"couldn't be found or read. yt will return T/µ instead of \"\n                        \"T — this is equivalent to assuming µ=1.0. To suppress this, \"\n                        \"derive the temperature from temperature_over_mu with \"\n                        \"some values for mu.\",\n                        category=RuntimeWarning,\n                        stacklevel=1,\n                    )\n                return data[\"gas\", \"temperature_over_mu\"]\n\n        self.add_field(\n            (\"gas\", \"temperature\"),\n            sampling_type=\"cell\",\n            function=_temperature,\n            units=self.ds.unit_system[\"temperature\"],\n        )\n\n        self.species_names = [\n            known_species_names[fn]\n            for ft, fn in self.field_list\n            if fn in known_species_names\n        ]\n\n        # See if we need to load the rt fields\n        rt_flag = RTFieldFileHandler.any_exist(self.ds)\n        if rt_flag:  # rt run\n            self.create_rt_fields()\n\n        # Load magnetic fields\n        if (\"gas\", \"magnetic_field_x_left\") in self:\n            self.create_magnetic_fields()\n\n        # Potential field\n        if (\"gravity\", \"Potential\") in self:\n            self.create_gravity_fields()\n\n    def create_gravity_fields(self):\n        def potential_energy(data):\n            return data[\"gas\", \"potential\"] * data[\"gas\", \"cell_mass\"]\n\n        self.add_field(\n            (\"gas\", \"potential_energy\"),\n            sampling_type=\"cell\",\n            function=potential_energy,\n            units=self.ds.unit_system[\"energy\"],\n        )\n\n    def create_magnetic_fields(self):\n        # Calculate cell-centred magnetic fields from face-centred\n        def mag_field(ax):\n            def _mag_field(data):\n                return (\n                    data[\"gas\", f\"magnetic_field_{ax}_left\"]\n                    + data[\"gas\", f\"magnetic_field_{ax}_right\"]\n                ) / 2\n\n            return _mag_field\n\n        for ax in self.ds.coordinates.axis_order:\n            self.add_field(\n                (\"gas\", f\"magnetic_field_{ax}\"),\n                sampling_type=\"cell\",\n                function=mag_field(ax),\n                units=self.ds.unit_system[\"magnetic_field_cgs\"],\n            )\n\n        def _divB(data):\n            \"\"\"Calculate magnetic field divergence\"\"\"\n            out = np.zeros_like(data[\"gas\", \"magnetic_field_x_right\"])\n            for ax in data.ds.coordinates.axis_order:\n                out += (\n                    data[\"gas\", f\"magnetic_field_{ax}_right\"]\n                    - data[\"gas\", f\"magnetic_field_{ax}_left\"]\n                )\n            return out / data[\"gas\", \"dx\"]\n\n        self.add_field(\n            (\"gas\", \"magnetic_field_divergence\"),\n            sampling_type=\"cell\",\n            function=_divB,\n            units=self.ds.unit_system[\"magnetic_field_cgs\"]\n            / self.ds.unit_system[\"length\"],\n        )\n\n    def create_rt_fields(self):\n        self.ds.fluid_types += (\"rt\",)\n        p = RTFieldFileHandler.get_rt_parameters(self.ds).copy()\n        p.update(self.ds.parameters)\n        ngroups = p[\"nGroups\"]\n        # Make sure rt_c_frac is at least as long as the number of levels in\n        # the simulation. Pad with either 1 (default) when using level-dependent\n        # reduced speed of light, otherwise pad with a constant value\n        if len(p[\"rt_c_frac\"]) == 1:\n            pad_value = p[\"rt_c_frac\"][0]\n        else:\n            pad_value = 1\n        rt_c_frac = np.pad(\n            p[\"rt_c_frac\"],\n            (0, max(0, self.ds.max_level - len([\"rt_c_frac\"]) + 1)),\n            constant_values=pad_value,\n        )\n\n        rt_c = rt_c_frac * units.c / (p[\"unit_l\"] / p[\"unit_t\"])\n        dens_conv = (p[\"unit_np\"] / rt_c).value / units.cm**3\n\n        ########################################\n        # Adding the fields in the hydro_* files\n        def _temp_IR(data):\n            rv = data[\"gas\", \"pres_IR\"] / data[\"gas\", \"density\"]\n            rv *= mass_hydrogen_cgs / boltzmann_constant_cgs\n            return rv\n\n        self.add_field(\n            (\"gas\", \"temp_IR\"),\n            sampling_type=\"cell\",\n            function=_temp_IR,\n            units=self.ds.unit_system[\"temperature\"],\n        )\n\n        def _species_density(field, data, species: str):\n            return data[\"gas\", f\"{species}_fraction\"] * data[\"gas\", \"density\"]\n\n        def _species_mass(field, data, species: str):\n            return data[\"gas\", f\"{species}_density\"] * data[\"index\", \"cell_volume\"]\n\n        for species in [\"H_p1\", \"He_p1\", \"He_p2\"]:\n            self.add_field(\n                (\"gas\", species + \"_density\"),\n                sampling_type=\"cell\",\n                function=partial(_species_density, species=species),\n                units=self.ds.unit_system[\"density\"],\n            )\n\n            self.add_field(\n                (\"gas\", species + \"_mass\"),\n                sampling_type=\"cell\",\n                function=partial(_species_mass, species=species),\n                units=self.ds.unit_system[\"mass\"],\n            )\n\n        ########################################\n        # Adding the fields in the rt_ files\n        def gen_pdens(igroup):\n            def _photon_density(data):\n                # The photon density depends on the possibly level-dependent conversion factor.\n                ilvl = data[\"index\", \"grid_level\"].astype(\"int64\")\n                dc = dens_conv[ilvl]\n                rv = data[\"ramses-rt\", f\"Photon_density_{igroup + 1}\"] * dc\n                return rv\n\n            return _photon_density\n\n        for igroup in range(ngroups):\n            self.add_field(\n                (\"rt\", f\"photon_density_{igroup + 1}\"),\n                sampling_type=\"cell\",\n                function=gen_pdens(igroup),\n                units=self.ds.unit_system[\"number_density\"],\n            )\n\n        flux_conv = p[\"unit_pf\"] / units.cm**2 / units.s\n        flux_unit = (\n            1 / self.ds.unit_system[\"time\"] / self.ds.unit_system[\"length\"] ** 2\n        ).units\n\n        def gen_flux(key, igroup):\n            def _photon_flux(data):\n                rv = data[\"ramses-rt\", f\"Photon_flux_{key}_{igroup + 1}\"] * flux_conv\n                return rv\n\n            return _photon_flux\n\n        for key in \"xyz\":\n            for igroup in range(ngroups):\n                self.add_field(\n                    (\"rt\", f\"photon_flux_{key}_{igroup + 1}\"),\n                    sampling_type=\"cell\",\n                    function=gen_flux(key, igroup),\n                    units=flux_unit,\n                )\n\n    def create_cooling_fields(self) -> bool:\n        \"Create cooling fields from the cooling files. Return True if successful.\"\n        num = int(self.ds.basename.split(\".\")[0].split(\"_\")[1])\n        filename = os.path.join(self.ds.directory, f\"cooling_{num:05}.out\")\n\n        if not os.path.exists(filename):\n            mylog.warning(\"This output has no cooling fields\")\n            return False\n\n        # Function to create the cooling fields\n        def _create_field(name, interp_object, unit):\n            def _func(data):\n                shape = data[\"gas\", \"temperature_over_mu\"].shape\n                # Ramses assumes a fraction X of Hydrogen within the non-metal gas.\n                # It has to be corrected by metallicity.\n                Z = data[\"gas\", \"metallicity\"]\n                nH = ((1 - _Y) * (1 - Z) * data[\"gas\", \"density\"] / mh).to(\"cm**-3\")\n                if data.ds.self_shielding:\n                    boost = np.maximum(np.exp(-nH / 0.01), 1e-20)\n                else:\n                    boost = 1\n                d = {\n                    \"lognH\": np.log10(nH / boost).ravel(),\n                    \"logT\": np.log10(data[\"gas\", \"temperature_over_mu\"]).ravel(),\n                }\n                rv = interp_object(d).reshape(shape)\n                if name[-1] != \"mu\":\n                    rv = 10 ** interp_object(d).reshape(shape)\n                cool = data.ds.arr(rv, unit)\n                if \"metal\" in name[-1].split(\"_\"):\n                    cool = (\n                        cool * data[\"gas\", \"metallicity\"] / 0.02\n                    )  # Ramses uses Zsolar=0.02\n                elif \"compton\" in name[-1].split(\"_\"):\n                    cool = data.ds.arr(rv, unit + \"/cm**3\")\n                    cool = (\n                        cool / data[\"gas\", \"number_density\"]\n                    )  # Compton cooling/heating is written to file in erg/s\n                return cool\n\n            self.add_field(name=name, sampling_type=\"cell\", function=_func, units=unit)\n\n        # Load cooling files\n        avals = {}\n        tvals = {}\n        with FortranFile(filename) as fd:\n            n1, n2 = fd.read_vector(\"i\")\n            for ax in _cool_axes:\n                avals[ax] = fd.read_vector(\"d\")\n            for i, (tname, unit) in enumerate(_cool_arrs):\n                var = fd.read_vector(\"d\")\n                if var.size == n1 and i == 0:\n                    # If this case occurs, the cooling files were produced pre-2010 in\n                    # a format that is no longer supported\n                    mylog.warning(\n                        \"This cooling file format is no longer supported. \"\n                        \"Cooling field loading skipped.\"\n                    )\n                    return False\n                if var.size == n1 * n2:\n                    tvals[tname] = {\n                        \"data\": var.reshape((n1, n2), order=\"F\"),\n                        \"unit\": unit,\n                    }\n                else:\n                    var = var.reshape((n1, n2, var.size // (n1 * n2)), order=\"F\")\n                    for i in range(var.shape[-1]):\n                        tvals[_cool_species[i]] = {\n                            \"data\": var[:, :, i],\n                            \"unit\": \"1/cm**3\",\n                        }\n\n        # Add the mu field first, as it is needed for the number density\n        interp = BilinearFieldInterpolator(\n            tvals[\"mu\"][\"data\"],\n            (avals[\"lognH\"], avals[\"logT\"]),\n            [\"lognH\", \"logT\"],\n            truncate=True,\n        )\n        _create_field((\"gas\", \"mu\"), interp, \"dimensionless\")\n\n        # Add the number density field, based on mu\n        def _number_density(data):\n            return data[\"gas\", \"density\"] / mp / data[\"gas\", \"mu\"]\n\n        self.add_field(\n            name=(\"gas\", \"number_density\"),\n            sampling_type=\"cell\",\n            function=_number_density,\n            units=number_density_unit,\n        )\n\n        # Add the cooling and heating fields, which need the number density field\n        for key in tvals:\n            if key != \"mu\":\n                interp = BilinearFieldInterpolator(\n                    tvals[key][\"data\"],\n                    (avals[\"lognH\"], avals[\"logT\"]),\n                    [\"lognH\", \"logT\"],\n                    truncate=True,\n                )\n                _create_field((\"gas\", key), interp, tvals[key][\"unit\"])\n\n        # Add total cooling and heating fields\n        def _all_cool(data):\n            return (\n                data[\"gas\", \"cooling_primordial\"]\n                + data[\"gas\", \"cooling_metal\"]\n                + data[\"gas\", \"cooling_compton\"]\n            )\n\n        def _all_heat(data):\n            return data[\"gas\", \"heating_primordial\"] + data[\"gas\", \"heating_compton\"]\n\n        self.add_field(\n            name=(\"gas\", \"cooling_total\"),\n            sampling_type=\"cell\",\n            function=_all_cool,\n            units=cooling_function_units,\n        )\n        self.add_field(\n            name=(\"gas\", \"heating_total\"),\n            sampling_type=\"cell\",\n            function=_all_heat,\n            units=cooling_function_units,\n        )\n\n        # Add net cooling fields\n        def _net_cool(data):\n            return data[\"gas\", \"cooling_total\"] - data[\"gas\", \"heating_total\"]\n\n        self.add_field(\n            name=(\"gas\", \"cooling_net\"),\n            sampling_type=\"cell\",\n            function=_net_cool,\n            units=cooling_function_units,\n        )\n\n        return True\n"
  },
  {
    "path": "yt/frontends/ramses/hilbert.py",
    "content": "from typing import Any, Optional\n\nimport numpy as np\n\nfrom yt.data_objects.selection_objects.region import YTRegion\nfrom yt.geometry.selection_routines import (\n    bbox_intersects,\n    fully_contains,\n)\nfrom yt.utilities.lib.geometry_utils import get_hilbert_indices\n\n# State diagram to compute the hilbert curve\n_STATE_DIAGRAM = np.array(\n    [\n        [\n            [1, 2, 0, 6, 11, 4, 5, 6, 10, 4, 7, 10],\n            [0, 0, 0, 2, 4, 6, 4, 6, 2, 2, 4, 6],\n        ],\n        [\n            [2, 6, 9, 0, 11, 4, 7, 1, 3, 4, 2, 3],\n            [1, 7, 3, 3, 3, 5, 7, 7, 5, 1, 5, 1],\n        ],\n        [\n            [3, 0, 10, 6, 0, 8, 5, 6, 1, 8, 11, 2],\n            [3, 1, 7, 1, 5, 1, 3, 5, 3, 5, 7, 7],\n        ],\n        [\n            [2, 7, 9, 11, 7, 8, 3, 10, 1, 8, 2, 6],\n            [2, 6, 4, 0, 2, 2, 0, 4, 4, 6, 6, 0],\n        ],\n        [\n            [4, 8, 1, 9, 5, 0, 1, 9, 10, 2, 7, 10],\n            [7, 3, 1, 5, 7, 7, 5, 1, 1, 3, 3, 5],\n        ],\n        [\n            [5, 8, 1, 0, 9, 6, 1, 4, 3, 7, 5, 3],\n            [6, 4, 2, 4, 0, 4, 6, 0, 6, 0, 2, 2],\n        ],\n        [\n            [3, 0, 11, 9, 0, 10, 11, 9, 5, 2, 8, 4],\n            [4, 2, 6, 6, 6, 0, 2, 2, 0, 4, 0, 4],\n        ],\n        [\n            [5, 7, 11, 8, 7, 6, 11, 10, 9, 3, 5, 4],\n            [5, 5, 5, 7, 1, 3, 1, 3, 7, 7, 1, 3],\n        ],\n    ]\n)\n\n\ndef hilbert3d(\n    ijk: \"np.ndarray[Any, np.dtype[np.int64]]\", bit_length: int\n) -> \"np.ndarray[Any, np.dtype[np.float64]]\":\n    \"\"\"Compute the order using Hilbert indexing.\n\n    Arguments\n    ---------\n    ijk : (N, ndim) integer array\n      The positions\n    bit_length : integer\n      The bit_length for the indexing.\n    \"\"\"\n    ijk = np.atleast_2d(ijk)\n    # A note here: there is a freedom in the way hilbert indices are\n    # being computed (should it be xyz or yzx or zxy etc.)\n    # and the yt convention is not the same as the RAMSES one.\n    return get_hilbert_indices(bit_length, ijk[:, [1, 2, 0]].astype(np.int64))\n\n\ndef get_intersecting_cpus(\n    ds,\n    region: YTRegion,\n    LE: Optional[\"np.ndarray[Any, np.dtype[np.float64]]\"] = None,\n    dx: float = 1.0,\n    dx_cond: float | None = None,\n    factor: float = 4.0,\n    bound_keys: Optional[\"np.ndarray[Any, np.dtype[np.float64]]\"] = None,\n    bbox: Any | None = None,\n) -> set[int]:\n    \"\"\"\n    Find the subset of CPUs that intersect the bbox in a recursive fashion.\n    \"\"\"\n    if bbox is None:\n        bbox = region.get_bbox()\n    if LE is None:\n        LE = np.array([0, 0, 0], dtype=\"d\")\n    if dx_cond is None:\n        dx_cond = float((bbox[1] - bbox[0]).min().to(\"code_length\"))\n    if bound_keys is None:\n        ncpu = ds.parameters[\"ncpu\"]\n        bound_keys = np.empty(ncpu + 1, dtype=\"float64\")\n        bound_keys[:ncpu] = [ds.hilbert_indices[icpu + 1][0] for icpu in range(ncpu)]\n        bound_keys[ncpu] = ds.hilbert_indices[ncpu][1]\n\n    # bbox rejection to avoid expensive selector checks. (greatly improves performance for small disks)\n    bbox_min = bbox[0].to(\"code_length\").d\n    bbox_max = bbox[1].to(\"code_length\").d\n    cube_min = LE\n    cube_max = LE + dx\n    if np.any(cube_max < bbox_min) or np.any(cube_min > bbox_max):\n        return set()\n\n    # If the current dx is smaller than the smallest size of the bbox\n    if dx < dx_cond / factor:\n        # Finish recursion\n        return get_cpu_list_cuboid(ds, np.asarray([LE, LE + dx]), bound_keys)\n\n    # If the current cell is fully within the selected region, stop recursion\n    if fully_contains(region.selector, LE, dx):\n        return get_cpu_list_cuboid(ds, np.asarray([LE, LE + dx]), bound_keys)\n\n    dx /= 2\n\n    ret = set()\n    # Compute intersection of the eight subcubes with the bbox and recurse.\n    for i in range(2):\n        for j in range(2):\n            for k in range(2):\n                LE_new = LE + np.array([i, j, k], dtype=\"d\") * dx\n\n                if bbox_intersects(region.selector, LE_new, dx):\n                    ret.update(\n                        get_intersecting_cpus(\n                            ds, region, LE_new, dx, dx_cond, factor, bound_keys, bbox\n                        )\n                    )\n    return ret\n\n\ndef get_cpu_list_cuboid(\n    ds,\n    X: \"np.ndarray[Any, np.dtype[np.float64]]\",\n    bound_keys: \"np.ndarray[Any, np.dtype[np.float64]]\",\n) -> set[int]:\n    \"\"\"\n    Return the list of the CPU intersecting with the cuboid containing the positions.\n    Note that it will be 0-indexed.\n\n    Parameters\n    ----------\n    ds : Dataset\n      The dataset containing the information\n    X : (N, ndim) float array\n      An array containing positions. They should be between 0 and 1.\n    \"\"\"\n    X = np.atleast_2d(X)\n    if X.shape[1] != 3:\n        raise NotImplementedError(\"This function is only implemented in 3D.\")\n\n    levelmax = ds.parameters[\"levelmax\"]\n    ndim = ds.parameters[\"ndim\"]\n\n    xmin, ymin, zmin = X.min(axis=0)\n    xmax, ymax, zmax = X.max(axis=0)\n\n    dmax = max(xmax - xmin, ymax - ymin, zmax - zmin)\n    ilevel = int(np.ceil(-np.log2(dmax)))\n\n    lmin = ilevel\n    bit_length = lmin - 1\n    maxdom = 2**bit_length\n\n    imin, imax, jmin, jmax, kmin, kmax = 0, 0, 0, 0, 0, 0\n    if bit_length > 0:\n        imin = int(xmin * maxdom)\n        imax = imin + 1\n        jmin = int(ymin * maxdom)\n        jmax = jmin + 1\n        kmin = int(zmin * maxdom)\n        kmax = kmin + 1\n\n    dkey = (2 ** (levelmax + 1) / maxdom) ** ndim\n    ndom = 1\n    if bit_length > 0:\n        ndom = 8\n\n    ijkdom = idom, jdom, kdom = np.empty((3, 8), dtype=np.int64)\n\n    idom[0], idom[1] = imin, imax\n    idom[2], idom[3] = imin, imax\n    idom[4], idom[5] = imin, imax\n    idom[6], idom[7] = imin, imax\n\n    jdom[0], jdom[1] = jmin, jmin\n    jdom[2], jdom[3] = jmax, jmax\n    jdom[4], jdom[5] = jmin, jmin\n    jdom[6], jdom[7] = jmax, jmax\n\n    kdom[0], kdom[1] = kmin, kmin\n    kdom[2], kdom[3] = kmin, kmin\n    kdom[4], kdom[5] = kmax, kmax\n    kdom[6], kdom[7] = kmax, kmax\n\n    bounding_min, bounding_max = np.zeros(ndom), np.zeros(ndom)\n    if bit_length > 0:\n        order_min = hilbert3d(ijkdom.T, bit_length)\n    for i in range(ndom):\n        if bit_length > 0:\n            omin = order_min[i]\n        else:\n            omin = 0\n        bounding_min[i] = omin * dkey\n        bounding_max[i] = (omin + 1) * dkey\n\n    cpu_min = np.searchsorted(bound_keys, bounding_min, side=\"right\") - 1\n    cpu_max = np.searchsorted(bound_keys, bounding_max, side=\"right\")\n\n    cpu_read: set[int] = set()\n\n    for i in range(ndom):\n        cpu_read.update(range(cpu_min[i], cpu_max[i]))\n\n    return cpu_read\n"
  },
  {
    "path": "yt/frontends/ramses/io.py",
    "content": "from collections import defaultdict\nfrom functools import lru_cache\nfrom typing import TYPE_CHECKING, Union\n\nimport numpy as np\nfrom unyt import unyt_array\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._typing import FieldKey\nfrom yt.frontends.ramses.definitions import VAR_DESC_RE, VERSION_RE\nfrom yt.utilities.cython_fortran_utils import FortranFile\nfrom yt.utilities.exceptions import (\n    YTFieldTypeNotFound,\n    YTFileNotParseable,\n    YTParticleOutputFormatNotImplemented,\n)\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\n\nif TYPE_CHECKING:\n    import os\n\n    from yt.frontends.ramses.data_structures import RAMSESDomainSubset\n    from yt.frontends.ramses.particle_handlers import ParticleFileHandler\n\n\ndef convert_ramses_ages(ds, conformal_ages):\n    issue_deprecation_warning(\n        msg=(\n            \"The `convert_ramses_ages' function is deprecated. It should be replaced \"\n            \"by the `convert_ramses_conformal_time_to_physical_age' function.\"\n        ),\n        stacklevel=3,\n        since=\"4.0.3\",\n    )\n    return convert_ramses_conformal_time_to_physical_time(ds, conformal_ages)\n\n\ndef convert_ramses_conformal_time_to_physical_time(\n    ds, conformal_time: np.ndarray\n) -> unyt_array:\n    \"\"\"\n    Convert conformal times (as defined in RAMSES) to physical times.\n\n    Arguments\n    ---------\n    ds : RAMSESDataset\n        The RAMSES dataset to use for the conversion\n    conformal_time : np.ndarray\n        The conformal time as read from disk\n\n    Returns\n    -------\n    physical_age : np.ndarray\n        The physical age in code units\n    \"\"\"\n    h0 = ds.hubble_constant\n    tau_bins = ds.tau_frw * h0\n    t_bins = ds.t_frw\n\n    min_time = 0\n    max_time = ds.current_time.to(t_bins.units)\n\n    return ds.arr(\n        np.clip(\n            np.interp(\n                conformal_time,\n                tau_bins,\n                t_bins.value,\n                right=max_time,\n                left=min_time,\n            ),\n            min_time,\n            max_time.value,\n        ),\n        t_bins.units,\n    )\n\n\ndef _ramses_particle_binary_file_handler(\n    particle_handler: \"ParticleFileHandler\",\n    subset: \"RAMSESDomainSubset\",\n    fields: list[FieldKey],\n    count: int,\n) -> dict[FieldKey, np.ndarray]:\n    \"\"\"General file handler for binary file, called by _read_particle_subset\n\n    Parameters\n    ----------\n    particle : ``ParticleFileHandler``\n        the particle class we want to read\n    subset: ``RAMSESDomainSubset``\n        A RAMSES domain subset object\n    fields: list of tuple\n        The fields to read\n    count: integer\n        The number of elements to count\n    \"\"\"\n    tr = {}\n    ds = subset.domain.ds\n    foffsets = particle_handler.field_offsets\n    fname = particle_handler.fname\n    data_types = particle_handler.field_types\n    with FortranFile(fname) as fd:\n        # We do *all* conversion into boxlen here.\n        # This means that no other conversions need to be applied to convert\n        # positions into the same domain as the octs themselves.\n        for field in sorted(fields, key=lambda a: foffsets[a]):\n            if count == 0:\n                tr[field] = np.empty(0, dtype=data_types[field])\n                continue\n            # Sentinel value: -1 means we don't have this field\n            if foffsets[field] == -1:\n                tr[field] = np.empty(count, dtype=data_types[field])\n            else:\n                fd.seek(foffsets[field])\n                dt = data_types[field]\n                tr[field] = fd.read_vector(dt)\n            if field[1].startswith(\"particle_position\"):\n                np.divide(tr[field], ds[\"boxlen\"], tr[field])\n\n            # Hand over to field handler for special cases, like particle_birth_times\n            particle_handler.handle_field(field, tr)\n\n    return tr\n\n\ndef _ramses_particle_csv_file_handler(\n    particle_handler: \"ParticleFileHandler\",\n    subset: \"RAMSESDomainSubset\",\n    fields: list[FieldKey],\n    count: int,\n) -> dict[FieldKey, np.ndarray]:\n    \"\"\"General file handler for csv file, called by _read_particle_subset\n\n    Parameters\n    ----------\n    particle: ``ParticleFileHandler``\n        the particle class we want to read\n    subset: ``RAMSESDomainSubset``\n        A RAMSES domain subset object\n    fields: list of tuple\n        The fields to read\n    count: integer\n        The number of elements to count\n    \"\"\"\n    from yt.utilities.on_demand_imports import _pandas as pd\n\n    tr = {}\n    ds = subset.domain.ds\n    foffsets = particle_handler.field_offsets\n    fname = particle_handler.fname\n\n    list_field_ind = [\n        (field, foffsets[field]) for field in sorted(fields, key=lambda a: foffsets[a])\n    ]\n\n    # read only selected fields\n    dat = pd.read_csv(\n        fname,\n        delimiter=r\"\\s*,\\s*\",\n        engine=\"python\",\n        usecols=[ind for _field, ind in list_field_ind],\n        skiprows=2,\n        header=None,\n    )\n\n    for field, ind in list_field_ind:\n        tr[field] = dat[ind].to_numpy()\n        if field[1].startswith(\"particle_position\"):\n            np.divide(tr[field], ds[\"boxlen\"], tr[field])\n\n        particle_handler.handle_field(field, tr)\n\n    return tr\n\n\nclass IOHandlerRAMSES(BaseIOHandler):\n    _dataset_type = \"ramses\"\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        tr = defaultdict(list)\n\n        # Set of field types\n        ftypes = {f[0] for f in fields}\n        for chunk in chunks:\n            # Gather fields by type to minimize i/o operations\n            for ft in ftypes:\n                # Get all the fields of the same type\n                field_subs = list(filter(lambda f, ft=ft: f[0] == ft, fields))\n\n                # Loop over subsets\n                for subset in chunk.objs:\n                    fname = None\n                    for fh in subset.domain.field_handlers:\n                        if fh.ftype == ft:\n                            file_handler = fh\n                            fname = fh.fname\n                            break\n\n                    if fname is None:\n                        raise YTFieldTypeNotFound(ft)\n\n                    # Now we read the entire thing\n                    with FortranFile(fname) as fd:\n                        # This contains the boundary information, so we skim through\n                        # and pick off the right vectors\n                        rv = subset.fill(fd, field_subs, selector, file_handler)\n                    for ft, f in field_subs:\n                        d = rv.pop(f)\n                        if d.size == 0:\n                            continue\n                        mylog.debug(\n                            \"Filling %s with %s (%0.3e %0.3e) (%s zones)\",\n                            f,\n                            d.size,\n                            d.min(),\n                            d.max(),\n                            d.size,\n                        )\n                        tr[ft, f].append(d)\n        d = {}\n        for field in fields:\n            tmp = tr.pop(field, None)\n            d[field] = np.concatenate(tmp) if tmp else np.empty(0, dtype=\"d\")\n\n        return d\n\n    def _read_particle_coords(self, chunks, ptf):\n        pn = \"particle_position_%s\"\n        fields = [\n            (ptype, f\"particle_position_{ax}\")\n            for ptype, field_list in ptf.items()\n            for ax in \"xyz\"\n        ]\n        for chunk in chunks:\n            for subset in chunk.objs:\n                rv = self._read_particle_subset(subset, fields)\n                for ptype in sorted(ptf):\n                    yield (\n                        ptype,\n                        (\n                            rv[ptype, pn % \"x\"],\n                            rv[ptype, pn % \"y\"],\n                            rv[ptype, pn % \"z\"],\n                        ),\n                        0.0,\n                    )\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        pn = \"particle_position_%s\"\n        chunks = list(chunks)\n        fields = [\n            (ptype, fname) for ptype, field_list in ptf.items() for fname in field_list\n        ]\n\n        for ptype, field_list in sorted(ptf.items()):\n            for ax in \"xyz\":\n                if pn % ax not in field_list:\n                    fields.append((ptype, pn % ax))\n\n            if ptype == \"sink_csv\":\n                subset = chunks[0].objs[0]\n                rv = self._read_particle_subset(subset, fields)\n                for ptype, field_list in sorted(ptf.items()):\n                    x, y, z = (np.asarray(rv[ptype, pn % ax], \"=f8\") for ax in \"xyz\")\n                    mask = selector.select_points(x, y, z, 0.0)\n                    if mask is None:\n                        mask = []\n                    for field in field_list:\n                        data = np.asarray(rv.pop((ptype, field))[mask], \"=f8\")\n                        yield (ptype, field), data\n\n            else:\n                for chunk in chunks:\n                    for subset in chunk.objs:\n                        rv = self._read_particle_subset(subset, fields)\n                        for ptype, field_list in sorted(ptf.items()):\n                            x, y, z = (\n                                np.asarray(rv[ptype, pn % ax], \"=f8\") for ax in \"xyz\"\n                            )\n                            mask = selector.select_points(x, y, z, 0.0)\n                            if mask is None:\n                                mask = []\n                            for field in field_list:\n                                data = np.asarray(rv.pop((ptype, field))[mask], \"=f8\")\n                                yield (ptype, field), data\n\n    def _read_particle_subset(self, subset, fields):\n        \"\"\"Read the particle files.\"\"\"\n        tr = {}\n\n        # Sequential read depending on particle type\n        for ptype in {f[0] for f in fields}:\n            # Select relevant files\n            subs_fields = filter(lambda f, ptype=ptype: f[0] == ptype, fields)\n\n            ok = False\n            for ph in subset.domain.particle_handlers:\n                if ph.ptype == ptype:\n                    ok = True\n                    count = ph.local_particle_count\n                    break\n            if not ok:\n                raise YTFieldTypeNotFound(ptype)\n\n            tr.update(ph.reader(subset, subs_fields, count))\n\n        return tr\n\n\n@lru_cache\ndef _read_part_binary_file_descriptor(fname: Union[str, \"os.PathLike[str]\"]):\n    \"\"\"\n    Read a file descriptor and returns the array of the fields found.\n    \"\"\"\n\n    # Mapping\n    mapping_list = [\n        (\"position_x\", \"particle_position_x\"),\n        (\"position_y\", \"particle_position_y\"),\n        (\"position_z\", \"particle_position_z\"),\n        (\"velocity_x\", \"particle_velocity_x\"),\n        (\"velocity_y\", \"particle_velocity_y\"),\n        (\"velocity_z\", \"particle_velocity_z\"),\n        (\"mass\", \"particle_mass\"),\n        (\"identity\", \"particle_identity\"),\n        (\"levelp\", \"particle_level\"),\n        (\"family\", \"particle_family\"),\n        (\"tag\", \"particle_tag\"),\n    ]\n    # Convert to dictionary\n    mapping = dict(mapping_list)\n\n    with open(fname) as f:\n        line = f.readline()\n        tmp = VERSION_RE.match(line)\n        mylog.debug(\"Reading part file descriptor %s.\", fname)\n        if not tmp:\n            raise YTParticleOutputFormatNotImplemented()\n\n        version = int(tmp.group(1))\n\n        if version == 1:\n            # Skip one line (containing the headers)\n            line = f.readline()\n            fields = []\n            for i, line in enumerate(f.readlines()):\n                tmp = VAR_DESC_RE.match(line)\n                if not tmp:\n                    raise YTFileNotParseable(fname, i + 1)\n\n                # ivar = tmp.group(1)\n                varname = tmp.group(2)\n                dtype = tmp.group(3)\n\n                if varname in mapping:\n                    varname = mapping[varname]\n                else:\n                    varname = f\"particle_{varname}\"\n\n                fields.append((varname, dtype))\n        else:\n            raise YTParticleOutputFormatNotImplemented()\n\n    return fields\n\n\n@lru_cache\ndef _read_part_csv_file_descriptor(fname: Union[str, \"os.PathLike[str]\"]):\n    \"\"\"\n    Read the file from the csv sink particles output.\n    \"\"\"\n    from yt.utilities.on_demand_imports import _pandas as pd\n\n    # Fields name from the default csv RAMSES sink algorithm in the yt default convention\n    mapping = {\n        \"# id\": \"particle_identifier\",\n        \"msink\": \"particle_mass\",\n        \"x\": \"particle_position_x\",\n        \"y\": \"particle_position_y\",\n        \"z\": \"particle_position_z\",\n        \"vx\": \"particle_velocity_x\",\n        \"vy\": \"particle_velocity_y\",\n        \"vz\": \"particle_velocity_z\",\n        \"lx\": \"particle_angular_momentum_x\",\n        \"ly\": \"particle_angular_momentum_y\",\n        \"lz\": \"particle_angular_momentum_z\",\n        \"tform\": \"particle_formation_time\",\n        \"acc_rate\": \"particle_accretion_rate\",\n        \"del_mass\": \"particle_delta_mass\",\n        \"rho_gas\": \"particle_rho_gas\",\n        \"cs**2\": \"particle_sound_speed\",\n        \"etherm\": \"particle_etherm\",\n        \"vx_gas\": \"particle_velocity_x_gas\",\n        \"vy_gas\": \"particle_velocity_y_gas\",\n        \"vz_gas\": \"particle_velocity_z_gas\",\n        \"mbh\": \"particle_mass_bh\",\n        \"level\": \"particle_level\",\n        \"rsink_star\": \"particle_radius_star\",\n    }\n\n    # read the all file to get the number of particle\n    dat = pd.read_csv(fname, delimiter=r\"\\s*,\\s*\", engine=\"python\")\n    fields = []\n    local_particle_count = len(dat)\n\n    for varname in dat.columns:\n        varname = varname.strip()\n        if varname in mapping:\n            varname = mapping[varname]\n        else:\n            varname = f\"particle_{varname}\"\n\n        fields.append(varname)\n\n    return fields, local_particle_count\n\n\n@lru_cache\ndef _read_fluid_file_descriptor(fname: Union[str, \"os.PathLike[str]\"], *, prefix: str):\n    \"\"\"\n    Read a file descriptor and returns the array of the fields found.\n    \"\"\"\n\n    # Mapping\n    mapping_list = [\n        (\"density\", \"Density\"),\n        (\"velocity_x\", \"x-velocity\"),\n        (\"velocity_y\", \"y-velocity\"),\n        (\"velocity_z\", \"z-velocity\"),\n        (\"pressure\", \"Pressure\"),\n        (\"metallicity\", \"Metallicity\"),\n        # Add mapping for ionized species\n        # Note: we expect internally that these names use the HII, HeII,\n        #       HeIII, ... convention for historical reasons. So we need to map\n        #       the names read from `hydro_file_descriptor.txt` to this\n        #       convention.\n        # This will create fields like (\"ramses\", \"HII\") which are mapped\n        # to (\"gas\", \"H_p1_fraction\") in fields.py\n        (\"H_p1_fraction\", \"HII\"),\n        (\"He_p1_fraction\", \"HeII\"),\n        (\"He_p2_fraction\", \"HeIII\"),\n        # Photon fluxes / densities are stored as `photon_density_XX`, so\n        # only 100 photon bands can be stored with this format. Let's be\n        # conservative and support up to 100 bands.\n        *[(f\"photon_density_{i:02d}\", f\"Photon_density_{i:d}\") for i in range(100)],\n        *[\n            (f\"photon_flux_{i:02d}_{dim}\", f\"Photon_flux_{dim}_{i:d}\")\n            for i in range(100)\n            for dim in \"xyz\"\n        ],\n    ]\n\n    # Add mapping for magnetic fields\n    mapping_list += [\n        (key, key)\n        for key in (\n            f\"B_{dim}_{side}\" for side in [\"left\", \"right\"] for dim in [\"x\", \"y\", \"z\"]\n        )\n    ]\n\n    # Convert to dictionary\n    mapping = dict(mapping_list)\n\n    with open(fname) as f:\n        line = f.readline()\n        tmp = VERSION_RE.match(line)\n        mylog.debug(\"Reading fluid file descriptor %s.\", fname)\n        if not tmp:\n            return []\n\n        version = int(tmp.group(1))\n\n        if version == 1:\n            # Skip one line (containing the headers)\n            line = f.readline()\n            fields = []\n            for i, line in enumerate(f.readlines()):\n                tmp = VAR_DESC_RE.match(line)\n                if not tmp:\n                    raise YTFileNotParseable(fname, i + 1)\n\n                # ivar = tmp.group(1)\n                varname = tmp.group(2)\n                dtype = tmp.group(3)\n\n                if varname in mapping:\n                    varname = mapping[varname]\n                else:\n                    varname = f\"{prefix}_{varname}\"\n\n                fields.append((varname, dtype))\n        else:\n            mylog.error(\"Version %s\", version)\n            raise YTParticleOutputFormatNotImplemented()\n\n    return fields\n"
  },
  {
    "path": "yt/frontends/ramses/io_utils.pyx",
    "content": "# distutils: libraries = STD_LIBS\n# distutils: include_dirs = LIB_DIR\ncimport cython\nfrom libc.stdio cimport SEEK_CUR, SEEK_SET\ncimport numpy as np\n\nimport numpy as np\n\nfrom yt.geometry.oct_container cimport RAMSESOctreeContainer\nfrom yt.utilities.cython_fortran_utils cimport FortranFile\n\nfrom yt.utilities.exceptions import YTIllDefinedAMRData\n\n\nctypedef np.int32_t INT32_t\nctypedef np.int64_t INT64_t\nctypedef np.float64_t DOUBLE_t\n\ncdef INT64_t INT32_SIZE = sizeof(np.int32_t)\ncdef INT64_t INT64_SIZE = sizeof(np.int64_t)\ncdef INT64_t DOUBLE_SIZE = sizeof(np.float64_t)\ncdef INT64_t SINGLE_SIZE = sizeof(np.float32_t)\n\n\ncdef inline int skip_len(int Nskip, int record_len, np.npy_bool single_precision) noexcept nogil:\n    # If the data is single precision, we need to skip 4 bytes\n    if single_precision:\n        return Nskip * (record_len * SINGLE_SIZE + INT64_SIZE)\n    else:\n        return Nskip * (record_len * DOUBLE_SIZE + INT64_SIZE)\n\n\n@cython.cpow(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\n@cython.nonecheck(False)\ndef read_amr(FortranFile f, dict headers,\n             np.ndarray[np.int64_t, ndim=1] ngridbound, INT64_t min_level,\n             RAMSESOctreeContainer oct_handler):\n\n    cdef INT64_t ncpu, nboundary, max_level, nlevelmax, ncpu_and_bound\n    cdef DOUBLE_t nx, ny, nz\n    cdef INT64_t ilevel, icpu, n, ndim, jump_len\n    cdef INT32_t ng\n    cdef np.ndarray[np.int32_t, ndim=2] numbl\n    cdef np.ndarray[np.float64_t, ndim=2] pos\n    cdef int i\n\n    # The ordering is very important here, as we'll write directly into the memory\n    # address the content of the files.\n    cdef np.float64_t[::1, :] pos_view\n\n    ndim = headers['ndim']\n    numbl = headers['numbl']\n    nboundary = headers['nboundary']\n    nx, ny, nz = (((i-1.0)/2.0) for i in headers['nx'])\n    nlevelmax = headers['nlevelmax']\n    ncpu = headers['ncpu']\n\n    ncpu_and_bound = nboundary + ncpu\n\n    # Allocate more memory if required\n    pos = np.empty((max(numbl.max(), ngridbound.max()), 3), dtype=\"d\", order=\"F\")\n    pos_view = pos\n\n    # Compute number of fields to skip. This should be 31 in 3 dimensions\n    jump_len = (1          # father index\n                + 2*ndim   # neighbor index\n                + 2**ndim  # son index\n                + 2**ndim  # cpu map\n                + 2**ndim  # refinement map\n    )\n    # Initialize values\n    max_level = 0\n    cdef INT64_t record_len\n\n    for ilevel in range(nlevelmax):\n        for icpu in range(ncpu_and_bound):\n            if icpu < ncpu:\n                ng = numbl[ilevel, icpu]\n            else:\n                ng = ngridbound[icpu - ncpu + nboundary*ilevel]\n\n            if ng == 0:\n                continue\n            # Skip grid index, 'next' and 'prev' arrays\n            record_len = (ng * INT32_SIZE + INT64_SIZE)\n            f.seek(record_len * 3, SEEK_CUR)\n\n            f.read_vector_inplace(\"d\", <void*> &pos_view[0, 0])\n            f.read_vector_inplace(\"d\", <void*> &pos_view[0, 1])\n            f.read_vector_inplace(\"d\", <void*> &pos_view[0, 2])\n\n            for i in range(ng):\n                pos_view[i, 0] -= nx\n            for i in range(ng):\n                pos_view[i, 1] -= ny\n            for i in range(ng):\n                pos_view[i, 2] -= nz\n\n            # Skip father, neighbor, son, cpu map and refinement map\n            f.seek(record_len * jump_len, SEEK_CUR)\n\n            # Note that we're adding *grids*, not individual cells.\n            if ilevel >= min_level:\n                n = oct_handler.add(icpu + 1, ilevel - min_level, pos[:ng, :],\n                                    count_boundary = 1)\n                if n > 0:\n                    max_level = max(ilevel - min_level, max_level)\n\n    return max_level\n\n@cython.cpow(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\n@cython.nonecheck(False)\ncpdef read_offset(\n    FortranFile f,\n    INT64_t min_level,\n    INT64_t domain_id,\n    INT64_t nvar,\n    dict headers,\n    int Nskip,\n    np.npy_bool single_precision\n):\n\n    cdef np.ndarray[np.int64_t, ndim=2] offset, level_count\n    cdef INT64_t ndim, twotondim, nlevelmax, n_levels, nboundary, ncpu, ncpu_and_bound\n    cdef INT64_t ilevel, icpu\n    cdef INT32_t file_ilevel, file_ncache\n\n    ndim = headers['ndim']\n    nboundary = headers['nboundary']\n    nlevelmax = headers['nlevelmax']\n    n_levels = nlevelmax - min_level\n    ncpu = headers['ncpu']\n\n    ncpu_and_bound = nboundary + ncpu\n    twotondim = 2**ndim\n\n    if Nskip == -1:\n        Nskip = twotondim * nvar\n\n    # It goes: level, CPU, 8-variable (1 oct)\n    offset = np.full((ncpu_and_bound, n_levels), -1, dtype=np.int64)\n    level_count = np.zeros((ncpu_and_bound, n_levels), dtype=np.int64)\n\n    cdef np.int64_t[:, ::1] level_count_view = level_count\n    cdef np.int64_t[:, ::1] offset_view = offset\n\n    for ilevel in range(nlevelmax):\n        for icpu in range(ncpu_and_bound):\n            file_ilevel = f.read_int()\n            file_ncache = f.read_int()\n            if file_ncache == 0:\n                continue\n\n            if file_ilevel != ilevel+1:\n                raise YTIllDefinedAMRData(\n                    'Cannot read offsets in file %s. The level read '\n                    'from data (%s) is not coherent with the expected (%s)',\n                    f.name, file_ilevel, ilevel)\n\n            if ilevel >= min_level:\n                offset_view[icpu, ilevel - min_level] = f.tell()\n                level_count_view[icpu, ilevel - min_level] = <INT64_t> file_ncache\n            f.seek(skip_len(Nskip, file_ncache, single_precision), SEEK_CUR)\n\n    return offset, level_count\n\n@cython.cpow(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\n@cython.nonecheck(False)\ndef fill_hydro(FortranFile f,\n               np.ndarray[np.int64_t, ndim=2] offsets,\n               np.ndarray[np.int64_t, ndim=2] level_count,\n               list cpu_enumerator,\n               np.ndarray[np.uint8_t, ndim=1] level_inds,\n               np.ndarray[np.uint8_t, ndim=1] cell_inds,\n               np.ndarray[np.int64_t, ndim=1] file_inds,\n               INT64_t ndim, list all_fields, list fields,\n               dict tr,\n               RAMSESOctreeContainer oct_handler,\n               np.npy_bool single_precision,\n               np.ndarray[np.int32_t, ndim=1] domain_inds=np.array([], dtype='int32'),\n               ):\n    cdef INT64_t offset\n    cdef dict tmp\n    cdef str field\n    cdef INT64_t twotondim\n    cdef int ilevel, icpu, nlevels, nc, ncpu_selected, nfields_selected\n    cdef int i, j, ii\n\n    twotondim = 2**ndim\n    nfields_selected = len(fields)\n\n    nlevels = offsets.shape[1]\n    ncpu_selected = len(cpu_enumerator)\n\n    cdef np.int64_t[::1] cpu_list = np.asarray(cpu_enumerator, dtype=np.int64)\n\n    cdef np.int64_t[::1] jumps = np.zeros(nfields_selected + 1, dtype=np.int64)\n    cdef int jump_len, Ncells\n    cdef np.uint8_t[::1] mask_level = np.zeros(nlevels, dtype=np.uint8)\n\n    # First, make sure fields are in the same order\n    fields = sorted(fields, key=lambda f: all_fields.index(f))\n\n    # The ordering is very important here, as we'll write directly into the memory\n    # address the content of the files.\n    cdef np.float32_t[::1, :, :] buffer_single\n    cdef np.float64_t[::1, :, :] buffer_double\n\n    jump_len = 0\n    j = 0\n    for i, field in enumerate(all_fields):\n        if field in fields:\n            jumps[j] = jump_len\n            j += 1\n            jump_len = 0\n        else:\n            jump_len += 1\n    jumps[j] = jump_len\n    cdef int first_field_index = jumps[0]\n\n    # The buffer is different depending on the size of the data\n    if single_precision:\n        buffer_single = np.empty((level_count.max(), twotondim, nfields_selected), dtype=\"float32\", order='F')\n    else:\n        buffer_double = np.empty((level_count.max(), twotondim, nfields_selected), dtype=\"float64\", order='F')\n\n    # Precompute which levels we need to read\n    Ncells = len(level_inds)\n    for i in range(Ncells):\n        mask_level[level_inds[i]] |= 1\n\n    # Loop over levels\n    for ilevel in range(nlevels):\n        if mask_level[ilevel] == 0:\n            continue\n        # Loop over cpu domains. In most cases, we'll only read the CPU corresponding to the domain.\n        for ii in range(ncpu_selected):\n            icpu = cpu_list[ii]\n            nc = level_count[icpu, ilevel]\n            if nc == 0:\n                continue\n            offset = offsets[icpu, ilevel]\n            if offset == -1:\n                continue\n            f.seek(offset + skip_len(first_field_index, nc, single_precision), SEEK_SET)\n\n            # We have already skipped the first fields (if any)\n            # so we \"rewind\" (this will cancel the first seek)\n            jump_len = -first_field_index\n            for i in range(twotondim):\n                # Read the selected fields\n                for j in range(nfields_selected):\n                    jump_len += jumps[j]\n                    if jump_len > 0:\n                        f.seek(skip_len(jump_len, nc, single_precision), SEEK_CUR)\n                        jump_len = 0\n                    if single_precision:\n                        f.read_vector_inplace('f', <void*> &buffer_single[0, i, j])\n                    else:\n                        f.read_vector_inplace('d', <void*> &buffer_double[0, i, j])\n\n                jump_len += jumps[nfields_selected]\n\n            # In principle, we may be left with some fields to skip\n            # but since we're doing an absolute seek at the beginning of\n            # the loop on CPUs, we can spare one seek here\n            ## if jump_len > 0:\n            ##     f.seek(skip_len(jump_len, nc, single_precision), SEEK_CUR)\n\n            # Alias buffer into dictionary\n            tmp = {}\n            for i, field in enumerate(fields):\n                if single_precision:\n                    tmp[field] = np.asarray(buffer_single[:, :, i], dtype=\"float64\")\n                else:\n                    tmp[field] = buffer_double[:, :, i]\n\n            if ncpu_selected > 1:\n                oct_handler.fill_level_with_domain(\n                    ilevel, level_inds, cell_inds, file_inds, domain_inds, tr, tmp, domain=icpu+1)\n            else:\n                oct_handler.fill_level(\n                    ilevel, level_inds, cell_inds, file_inds, tr, tmp)\n"
  },
  {
    "path": "yt/frontends/ramses/particle_handlers.py",
    "content": "import abc\nimport os\nimport struct\nfrom collections.abc import Callable\nfrom functools import cached_property\nfrom itertools import chain, count\nfrom typing import TYPE_CHECKING, Any\n\nimport numpy as np\n\nfrom yt._typing import FieldKey\nfrom yt.config import ytcfg\nfrom yt.funcs import mylog\nfrom yt.utilities.cython_fortran_utils import FortranFile\n\nfrom .field_handlers import HandlerMixin\nfrom .io import (\n    _ramses_particle_binary_file_handler,\n    _ramses_particle_csv_file_handler,\n    _read_part_binary_file_descriptor,\n    _read_part_csv_file_descriptor,\n    convert_ramses_conformal_time_to_physical_time,\n)\n\nif TYPE_CHECKING:\n    from yt.frontends.ramses.data_structures import RAMSESDomainSubset\n\n\nPARTICLE_HANDLERS: set[type[\"ParticleFileHandler\"]] = set()\n\n\ndef get_particle_handlers():\n    return PARTICLE_HANDLERS\n\n\ndef register_particle_handler(ph):\n    PARTICLE_HANDLERS.add(ph)\n\n\nclass ParticleFileHandler(abc.ABC, HandlerMixin):\n    \"\"\"\n    Abstract class to handle particles in RAMSES. Each instance\n    represents a single file (one domain).\n\n    To add support to a new particle file, inherit from this class and\n    implement all functions containing a `NotImplementedError`.\n\n    See `SinkParticleFileHandler` for an example implementation.\"\"\"\n\n    _file_type = \"particle\"\n\n    ## These properties are static\n    # The name to give to the particle type\n    ptype: str\n\n    # The name of the file(s).\n    fname: str\n\n    # The name of the file descriptor (if any)\n    file_descriptor: str | None = None\n\n    # The attributes of the header\n    attrs: tuple[tuple[str, int, str], ...]\n\n    # A list of tuple containing the field name and its type\n    known_fields: list[FieldKey]\n\n    # The function to employ to read the file\n    # NOTE: We omit the `ParticleFileHandler` argument since\n    #       it is accessed as a method (so the first argument is\n    #       assumed to be `self`).\n    reader: Callable[\n        [\"RAMSESDomainSubset\", list[FieldKey], int],\n        dict[FieldKey, np.ndarray],\n    ]\n\n    # Name of the config section (if any)\n    config_field: str | None = None\n\n    ## These properties are computed dynamically\n    # Mapping from field to offset in file\n    _field_offsets: dict[FieldKey, int]\n\n    # Mapping from field to the type of the data (float, integer, ...)\n    _field_types: dict[FieldKey, str]\n\n    # Number of particle in the domain\n    _local_particle_count: int\n\n    # The header of the file\n    _header: dict[str, Any]\n\n    def __init_subclass__(cls, *args, **kwargs):\n        \"\"\"\n        Registers subclasses at creation.\n        \"\"\"\n        super().__init_subclass__(*args, **kwargs)\n\n        if cls.ptype is not None:\n            register_particle_handler(cls)\n\n        cls._unique_registry = {}\n        return cls\n\n    def __init__(self, domain):\n        self.setup_handler(domain)\n\n        # Attempt to read the list of fields from the config file\n        if self.config_field and ytcfg.has_section(self.config_field):\n            cfg = ytcfg.get(self.config_field, \"fields\")\n            known_fields = []\n            for c in (_.strip() for _ in cfg.split(\"\\n\") if _.strip() != \"\"):\n                field, field_type = (_.strip() for _ in c.split(\",\"))\n                known_fields.append((field, field_type))\n            self.known_fields = known_fields\n\n    @abc.abstractmethod\n    def read_header(self):\n        \"\"\"\n        This function is called once per file. It should:\n\n        * read the header of the file and store any relevant information\n        * detect the fields in the file\n        * compute the offsets (location in the file) of each field\n\n        It is in charge of setting `self.field_offsets` and `self.field_types`.\n\n        * `_field_offsets`: dictionary: tuple -> integer\n            A dictionary that maps `(type, field_name)` to their\n            location in the file (integer)\n        * `_field_types`: dictionary: tuple -> character\n            A dictionary that maps `(type, field_name)` to their type\n            (character), following Python's struct convention.\n        \"\"\"\n        pass\n\n    @property\n    def field_offsets(self) -> dict[FieldKey, int]:\n        if hasattr(self, \"_field_offsets\"):\n            return self._field_offsets\n        self.read_header()\n        return self._field_offsets\n\n    @property\n    def field_types(self) -> dict[FieldKey, str]:\n        if hasattr(self, \"_field_types\"):\n            return self._field_types\n        self.read_header()\n        return self._field_types\n\n    @property\n    def local_particle_count(self) -> int:\n        if hasattr(self, \"_local_particle_count\"):\n            return self._local_particle_count\n        self.read_header()\n        return self._local_particle_count\n\n    @property\n    def header(self) -> dict[str, Any]:\n        if hasattr(self, \"_header\"):\n            return self._header\n        self.read_header()\n        return self._header\n\n    def handle_field(self, field: FieldKey, data_dict: dict[FieldKey, np.ndarray]):\n        \"\"\"\n        This function allows custom code to be called to handle special cases,\n        such as the particle birth time.\n\n        It updates the `data_dict` dictionary with the new data.\n\n        Parameters\n        ----------\n        field : FieldKey\n            The field name.\n        data_dict : dict[FieldKey, np.ndarray]\n            A dictionary containing the data.\n\n        By default, this function does nothing.\n        \"\"\"\n        pass\n\n\n_default_dtypes: dict[int, str] = {\n    1: \"c\",  # char\n    2: \"h\",  # short,\n    4: \"f\",  # float\n    8: \"d\",  # double\n}\n\n\nclass DefaultParticleFileHandler(ParticleFileHandler):\n    ptype = \"io\"\n    fname = \"part_{iout:05d}.out{icpu:05d}\"\n    file_descriptor = \"part_file_descriptor.txt\"\n    config_field = \"ramses-particles\"\n    reader = _ramses_particle_binary_file_handler\n\n    attrs = (\n        (\"ncpu\", 1, \"i\"),\n        (\"ndim\", 1, \"i\"),\n        (\"npart\", 1, \"i\"),\n        (\"localseed\", -1, \"i\"),\n        (\"nstar_tot\", 1, \"i\"),\n        (\"mstar_tot\", 1, \"d\"),\n        (\"mstar_lost\", 1, \"d\"),\n        (\"nsink\", 1, \"i\"),\n    )\n\n    known_fields = [\n        (\"particle_position_x\", \"d\"),\n        (\"particle_position_y\", \"d\"),\n        (\"particle_position_z\", \"d\"),\n        (\"particle_velocity_x\", \"d\"),\n        (\"particle_velocity_y\", \"d\"),\n        (\"particle_velocity_z\", \"d\"),\n        (\"particle_mass\", \"d\"),\n        (\"particle_identity\", \"i\"),\n        (\"particle_refinement_level\", \"i\"),\n    ]\n\n    def read_header(self):\n        if not self.exists:\n            self._field_offsets = {}\n            self._field_types = {}\n            self._local_particle_count = 0\n            self._header = {}\n            return\n\n        flen = os.path.getsize(self.fname)\n        with FortranFile(self.fname) as fd:\n            hvals = dict(fd.read_attrs(self.attrs))\n            particle_field_pos = fd.tell()\n\n        self._header = hvals\n        self._local_particle_count = hvals[\"npart\"]\n        extra_particle_fields = self.ds._extra_particle_fields\n\n        if self.has_descriptor:\n            particle_fields = _read_part_binary_file_descriptor(self.file_descriptor)\n        else:\n            particle_fields = list(self.known_fields)\n\n            if extra_particle_fields is not None:\n                particle_fields += extra_particle_fields\n\n        if (\n            hvals[\"nstar_tot\"] > 0\n            and extra_particle_fields is not None\n            and (\"particle_birth_time\", \"d\") not in particle_fields\n        ):\n            particle_fields += [\n                (\"particle_birth_time\", \"d\"),\n                (\"particle_metallicity\", \"d\"),\n            ]\n\n        def build_iterator():\n            return chain(\n                particle_fields,\n                ((f\"particle_extra_field_{i}\", \"d\") for i in count(1)),\n            )\n\n        field_offsets = {}\n        _pfields = {}\n        ptype = self.ptype\n        blockLen = struct.calcsize(\"i\") * 2\n\n        particle_fields_iterator = build_iterator()\n        ipos = particle_field_pos\n        while ipos < flen:\n            field, vtype = next(particle_fields_iterator)\n            field_offsets[ptype, field] = ipos\n            _pfields[ptype, field] = vtype\n            ipos += blockLen + struct.calcsize(vtype) * hvals[\"npart\"]\n\n        if ipos != flen:\n            particle_fields_iterator = build_iterator()\n            with FortranFile(self.fname) as fd:\n                fd.seek(particle_field_pos)\n                ipos = particle_field_pos\n                while ipos < flen:\n                    field, vtype = next(particle_fields_iterator)\n                    old_pos = fd.tell()\n                    field_offsets[ptype, field] = old_pos\n                    fd.skip(1)\n                    ipos = fd.tell()\n\n                    record_len = ipos - old_pos - blockLen\n                    exp_len = struct.calcsize(vtype) * hvals[\"npart\"]\n\n                    if record_len != exp_len:\n                        # Guess record vtype from length\n                        nbytes = record_len // hvals[\"npart\"]\n                        # NOTE: in some simulations (e.g. New Horizon), the record length is not\n                        # a multiple of 1, 2, 4 or 8. In this case, fallback onto assuming\n                        # double precision.\n                        vtype = _default_dtypes.get(nbytes, \"d\")\n\n                        mylog.warning(\n                            \"Supposed that `%s` has type %s given record size\",\n                            field,\n                            np.dtype(vtype),\n                        )\n\n                    _pfields[ptype, field] = vtype\n\n        if field.startswith(\"particle_extra_field_\"):\n            iextra = int(field.split(\"_\")[-1])\n        else:\n            iextra = 0\n        if iextra > 0 and not self.ds._warned_extra_fields[\"io\"]:\n            mylog.warning(\n                \"Detected %s extra particle fields assuming kind \"\n                \"`double`. Consider using the `extra_particle_fields` \"\n                \"keyword argument if you have unexpected behavior.\",\n                iextra,\n            )\n            self.ds._warned_extra_fields[\"io\"] = True\n\n        if (\n            self.ds.use_conformal_time\n            and (ptype, \"particle_birth_time\") in field_offsets\n        ):\n            field_offsets[ptype, \"conformal_birth_time\"] = field_offsets[\n                ptype, \"particle_birth_time\"\n            ]\n            _pfields[ptype, \"conformal_birth_time\"] = _pfields[\n                ptype, \"particle_birth_time\"\n            ]\n\n        self._field_offsets = field_offsets\n        self._field_types = _pfields\n\n    @property\n    def birth_file_fname(self):\n        basename = os.path.abspath(self.ds.root_folder)\n        iout = int(\n            os.path.basename(self.ds.parameter_filename).split(\".\")[0].split(\"_\")[1]\n        )\n        icpu = self.domain_id\n\n        fname = os.path.join(basename, f\"birth_{iout:05d}.out{icpu:05d}\")\n        return fname\n\n    @cached_property\n    def has_birth_file(self):\n        return os.path.exists(self.birth_file_fname)\n\n    def handle_field(self, field: FieldKey, data_dict: dict[FieldKey, np.ndarray]):\n        _ptype, fname = field\n        if not (fname == \"particle_birth_time\" and self.ds.cosmological_simulation):\n            return\n\n        # If the birth files exist, read from them\n        if self.has_birth_file:\n            with FortranFile(self.birth_file_fname) as fd:\n                # Note: values are written in Gyr, so we need to convert back to code_time\n                data_dict[field] = (\n                    self.ds.arr(fd.read_vector(\"d\"), \"Gyr\").to(\"code_time\").v\n                )\n\n            return\n\n        # Otherwise, convert conformal time to physical age\n        ds = self.ds\n        conformal_time = data_dict[field]\n        physical_time = (\n            convert_ramses_conformal_time_to_physical_time(ds, conformal_time)\n            .to(\"code_time\")\n            .v\n        )\n        # arbitrarily set particles with zero conformal_age to zero\n        # particle_age. This corresponds to DM particles.\n        data_dict[field] = np.where(conformal_time != 0, physical_time, 0)\n\n\nclass SinkParticleFileHandler(ParticleFileHandler):\n    \"\"\"Handle sink files\"\"\"\n\n    ptype = \"sink\"\n    fname = \"sink_{iout:05d}.out{icpu:05d}\"\n    file_descriptor = \"sink_file_descriptor.txt\"\n    config_field = \"ramses-sink-particles\"\n    reader = _ramses_particle_binary_file_handler\n\n    attrs = ((\"nsink\", 1, \"i\"), (\"nindsink\", 1, \"i\"))\n\n    known_fields = [\n        (\"particle_identifier\", \"i\"),\n        (\"particle_mass\", \"d\"),\n        (\"particle_position_x\", \"d\"),\n        (\"particle_position_y\", \"d\"),\n        (\"particle_position_z\", \"d\"),\n        (\"particle_velocity_x\", \"d\"),\n        (\"particle_velocity_y\", \"d\"),\n        (\"particle_velocity_z\", \"d\"),\n        (\"particle_birth_time\", \"d\"),\n        (\"BH_real_accretion\", \"d\"),\n        (\"BH_bondi_accretion\", \"d\"),\n        (\"BH_eddington_accretion\", \"d\"),\n        (\"BH_esave\", \"d\"),\n        (\"gas_spin_x\", \"d\"),\n        (\"gas_spin_y\", \"d\"),\n        (\"gas_spin_z\", \"d\"),\n        (\"BH_spin_x\", \"d\"),\n        (\"BH_spin_y\", \"d\"),\n        (\"BH_spin_z\", \"d\"),\n        (\"BH_spin\", \"d\"),\n        (\"BH_efficiency\", \"d\"),\n    ]\n\n    def read_header(self):\n        if not self.exists:\n            self._field_offsets = {}\n            self._field_types = {}\n            self._local_particle_count = 0\n            self._header = {}\n            return\n        fd = FortranFile(self.fname)\n        flen = os.path.getsize(self.fname)\n        hvals = {}\n        # Read the header of the file\n        attrs = self.attrs\n\n        hvals.update(fd.read_attrs(attrs))\n        self._header = hvals\n\n        # This is somehow a trick here: we only want one domain to\n        # be read, as ramses writes all the sinks in all the\n        # domains. Here, we set the local_particle_count to 0 except\n        # for the first domain to be red.\n        if getattr(self.ds, \"_sink_file_flag\", False):\n            self._local_particle_count = 0\n        else:\n            self.ds._sink_file_flag = True\n            self._local_particle_count = hvals[\"nsink\"]\n\n        # Read the fields + add the sink properties\n        if self.has_descriptor:\n            fields = _read_part_binary_file_descriptor(self.file_descriptor)\n        else:\n            fields = list(self.known_fields)\n\n        # Note: this follows RAMSES convention.\n        for i in range(self.ds.dimensionality * 2 + 1):\n            for ilvl in range(self.ds.max_level + 1):\n                fields.append((f\"particle_prop_{ilvl}_{i}\", \"d\"))\n\n        field_offsets = {}\n        _pfields = {}\n\n        # Fill the fields, offsets and types\n        self.fields = []\n        for field, vtype in fields:\n            self.fields.append(field)\n            if fd.tell() >= flen:\n                break\n            field_offsets[self.ptype, field] = fd.tell()\n            _pfields[self.ptype, field] = vtype\n            fd.skip(1)\n        self._field_offsets = field_offsets\n        self._field_types = _pfields\n        fd.close()\n\n\nclass SinkParticleFileHandlerCsv(ParticleFileHandler):\n    \"\"\"Handle sink files from a csv file, the format from the sink particle in ramses\"\"\"\n\n    ptype = \"sink_csv\"\n    fname = \"sink_{iout:05d}.csv\"\n    file_descriptor = None\n    config_field = \"ramses-sink-particles\"\n    reader = _ramses_particle_csv_file_handler\n    attrs = ((\"nsink\", 1, \"i\"), (\"nindsink\", 1, \"i\"))\n\n    def read_header(self):\n        if not self.exists:\n            self._field_offsets = {}\n            self._field_types = {}\n            self._local_particle_count = 0\n            self._header = {}\n            return\n        field_offsets = {}\n        _pfields = {}\n\n        fields, self._local_particle_count = _read_part_csv_file_descriptor(self.fname)\n\n        for ind, field in enumerate(fields):\n            field_offsets[self.ptype, field] = ind\n            _pfields[self.ptype, field] = \"d\"\n\n        self._field_offsets = field_offsets\n        self._field_types = _pfields\n\n    def handle_field(self, field: FieldKey, data_dict: dict[FieldKey, np.ndarray]):\n        _ptype, fname = field\n        if not (fname == \"particle_birth_time\" and self.ds.cosmological_simulation):\n            return\n\n        # convert conformal time to physical age\n        ds = self.ds\n        conformal_time = data_dict[field]\n        physical_time = convert_ramses_conformal_time_to_physical_time(\n            ds, conformal_time\n        )\n        # arbitrarily set particles with zero conformal_age to zero\n        # particle_age. This corresponds to DM particles.\n        data_dict[field] = np.where(conformal_time > 0, physical_time, 0)\n"
  },
  {
    "path": "yt/frontends/ramses/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/ramses/tests/test_file_sanitizer.py",
    "content": "import re\nimport tempfile\nfrom collections import namedtuple\nfrom itertools import chain\nfrom pathlib import Path\n\nimport pytest\n\nfrom yt.frontends.ramses.data_structures import RAMSESFileSanitizer\n\nPathTuple = namedtuple(\n    \"PathTuple\", (\"output_dir\", \"group_dir_name\", \"info_file\", \"paths_to_try\")\n)\n\n\ndef generate_paths(create):\n    with tempfile.TemporaryDirectory() as tmpdir:\n        output_dir = Path(tmpdir) / \"output_00123\"\n        output_dir.mkdir()\n        amr_file = output_dir / \"amr_00123.out00001\"\n        if create:\n            amr_file.touch()\n        info_file = output_dir / \"info_00123.txt\"\n        if create:\n            info_file.touch()\n\n        # Test with regular structure\n        output_dir2 = Path(tmpdir) / \"output_00124\"\n        output_dir2.mkdir()\n\n        group_dir2 = output_dir2 / \"group_00001\"\n        group_dir2.mkdir()\n        info_file2 = group_dir2 / \"info_00124.txt\"\n        if create:\n            info_file2.touch()\n        amr_file2 = group_dir2 / \"amr_00124.out00001\"\n        if create:\n            amr_file2.touch()\n\n        yield (\n            PathTuple(\n                output_dir=output_dir,\n                group_dir_name=None,\n                info_file=info_file,\n                paths_to_try=(output_dir, info_file, amr_file),\n            ),\n            PathTuple(\n                output_dir=output_dir2,\n                group_dir_name=group_dir2.name,\n                info_file=info_file2,\n                paths_to_try=(output_dir2, info_file2, group_dir2, amr_file2),\n            ),\n        )\n\n\n@pytest.fixture\ndef valid_path_tuples():\n    yield from generate_paths(create=True)\n\n\n@pytest.fixture\ndef invalid_path_tuples():\n    yield from generate_paths(create=False)\n\n\ndef test_valid_sanitizing(valid_path_tuples):\n    for answer in valid_path_tuples:\n        for path in answer.paths_to_try:\n            sanitizer = RAMSESFileSanitizer(path)\n            sanitizer.validate()\n\n            assert sanitizer.root_folder == answer.output_dir\n            assert sanitizer.group_name == answer.group_dir_name\n            assert sanitizer.info_fname == answer.info_file\n\n\ndef test_invalid_sanitizing(valid_path_tuples, invalid_path_tuples):\n    for path in chain(*(pt.paths_to_try for pt in invalid_path_tuples)):\n        sanitizer = RAMSESFileSanitizer(path)\n\n        if path.exists():\n            expected_error = ValueError\n            expected_error_message = (\n                \"Could not determine output directory from '.*'\\n\"\n                \"Expected a directory name of form .* \"\n                \"containing an info_\\\\*.txt file and amr_\\\\* files.\"\n            )\n        else:\n            expected_error = FileNotFoundError\n            expected_error_message = re.escape(\n                f\"No such file or directory '{str(path)}'\"\n            )\n\n        with pytest.raises(expected_error, match=expected_error_message):\n            sanitizer.validate()\n\n    for path in chain(*(pt.paths_to_try for pt in valid_path_tuples)):\n        expected_error_message = re.escape(\n            f\"No such file or directory '{str(path / 'does_not_exist.txt')}'\"\n        )\n        sanitizer = RAMSESFileSanitizer(path / \"does_not_exist.txt\")\n        with pytest.raises(FileNotFoundError, match=expected_error_message):\n            sanitizer.validate()\n\n    expected_error_message = \"No such file or directory '.*'\"\n    sanitizer = RAMSESFileSanitizer(Path(\"this\") / \"does\" / \"not\" / \"exist\")\n    with pytest.raises(FileNotFoundError, match=expected_error_message):\n        sanitizer.validate()\n"
  },
  {
    "path": "yt/frontends/ramses/tests/test_hilbert.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nimport yt\nfrom yt.frontends.ramses.hilbert import get_cpu_list_cuboid, hilbert3d\nfrom yt.testing import requires_file\n\n\ndef test_hilbert3d():\n    # 8 different cases, checked against RAMSES' own implementation\n    inputs = [\n        [0, 0, 0],\n        [1, 0, 0],\n        [0, 1, 0],\n        [1, 1, 0],\n        [0, 0, 1],\n        [1, 0, 1],\n        [0, 1, 1],\n        [1, 1, 1],\n    ]\n    outputs = [0, 1, 7, 6, 3, 2, 4, 5]\n\n    for i, o in zip(inputs, outputs, strict=True):\n        assert_equal(hilbert3d(i, 3).item(), o)\n\n\noutput_00080 = \"output_00080/info_00080.txt\"\n\n\n@requires_file(output_00080)\ndef test_get_cpu_list():\n    ds = yt.load(output_00080)\n\n    np.random.seed(16091992)\n    # These are randomly generated outputs, checked against RAMSES' own implementation\n    inputs = (\n        [[0.27747276, 0.30018937, 0.17916189], [0.42656026, 0.40509483, 0.29927838]],\n        [[0.90660856, 0.44201328, 0.22770587], [1.09175462, 0.58017918, 0.2836648]],\n        [[0.98542323, 0.58543376, 0.45858327], [1.04441105, 0.62079207, 0.58919283]],\n        [[0.42274841, 0.44887745, 0.87793679], [0.52066634, 0.58936331, 1.00666222]],\n        [[0.69964803, 0.65893669, 0.03660775], [0.80565696, 0.67409752, 0.11434604]],\n    )\n    outputs = ([0, 15], [0, 15], [0, 1, 15], [0, 13, 14, 15], [0])\n\n    ncpu = ds.parameters[\"ncpu\"]\n    bound_keys = np.array(\n        [ds.hilbert_indices[icpu][0] for icpu in range(1, ncpu + 1)]\n        + [ds.hilbert_indices[ds.parameters[\"ncpu\"]][1]],\n        dtype=\"float64\",\n    )\n    for i, o in zip(inputs, outputs, strict=True):\n        bbox = i\n        ls = list(get_cpu_list_cuboid(ds, bbox, bound_keys=bound_keys))\n        assert len(ls) > 0\n        assert all(np.array(o) == np.array(ls))\n"
  },
  {
    "path": "yt/frontends/ramses/tests/test_outputs.py",
    "content": "import os\nfrom contextlib import contextmanager\nfrom pathlib import Path\nfrom shutil import copytree\nfrom tempfile import TemporaryDirectory\n\nimport numpy as np\nfrom numpy.testing import assert_equal, assert_raises\n\nimport yt\nfrom yt.config import ytcfg\nfrom yt.fields.field_detector import FieldDetector\nfrom yt.frontends.ramses.api import RAMSESDataset\nfrom yt.frontends.ramses.field_handlers import DETECTED_FIELDS, HydroFieldFileHandler\nfrom yt.testing import requires_file, requires_module, units_override_check\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    PixelizedProjectionValuesTest,\n    create_obj,\n    data_dir_load,\n    requires_ds,\n)\nfrom yt.utilities.on_demand_imports import _f90nml as f90nml\n\n_fields = (\n    (\"gas\", \"temperature\"),\n    (\"gas\", \"density\"),\n    (\"gas\", \"velocity_magnitude\"),\n    (\"gas\", \"pressure_gradient_magnitude\"),\n)\n\noutput_00080 = \"output_00080/info_00080.txt\"\n\n\n@requires_ds(output_00080)\ndef test_output_00080():\n    ds = data_dir_load(output_00080)\n    assert_equal(str(ds), \"info_00080\")\n    assert_equal(ds.particle_type_counts, {\"io\": 1090895, \"nbody\": 0})\n    dso = [None, (\"sphere\", (\"max\", (0.1, \"unitary\")))]\n    for dobj_name in dso:\n        for field in _fields:\n            for axis in [0, 1, 2]:\n                for weight_field in [None, (\"gas\", \"density\")]:\n                    yield PixelizedProjectionValuesTest(\n                        output_00080, axis, field, weight_field, dobj_name\n                    )\n            yield FieldValuesTest(output_00080, field, dobj_name)\n        dobj = create_obj(ds, dobj_name)\n        s1 = dobj[\"index\", \"ones\"].sum()\n        s2 = sum(mask.sum() for block, mask in dobj.blocks)\n        assert_equal(s1, s2)\n\n\n@requires_file(output_00080)\ndef test_RAMSESDataset():\n    assert isinstance(data_dir_load(output_00080), RAMSESDataset)\n\n\n@requires_file(output_00080)\ndef test_RAMSES_alternative_load():\n    # Test that we can load a RAMSES dataset by giving it the folder name,\n    # the info file name or an amr file name\n    base_dir, info_file_fname = os.path.split(output_00080)\n    for fname in (base_dir, os.path.join(base_dir, \"amr_00080.out00001\")):\n        assert isinstance(data_dir_load(fname), RAMSESDataset)\n\n\n@requires_file(output_00080)\ndef test_units_override():\n    units_override_check(output_00080)\n\n\nramsesNonCosmo = \"DICEGalaxyDisk_nonCosmological/output_00002/info_00002.txt\"\n\n\n@requires_file(ramsesNonCosmo)\ndef test_non_cosmo_detection():\n    ds = yt.load(ramsesNonCosmo, cosmological=False)\n    assert_equal(ds.cosmological_simulation, 0)\n\n    ds = yt.load(ramsesNonCosmo, cosmological=None)\n    assert_equal(ds.cosmological_simulation, 0)\n\n    ds = yt.load(ramsesNonCosmo)\n    assert_equal(ds.cosmological_simulation, 0)\n\n\n@requires_file(ramsesNonCosmo)\ndef test_unit_non_cosmo():\n    for force_cosmo in [False, None]:\n        ds = yt.load(ramsesNonCosmo, cosmological=force_cosmo)\n\n        expected_raw_time = 0.0299468077820411  # in ramses unit\n        assert_equal(ds.current_time.value, expected_raw_time)\n\n        expected_time = 14087886140997.336  # in seconds\n        assert_equal(ds.current_time.in_units(\"s\").value, expected_time)\n\n\nramsesCosmo = \"output_00080/info_00080.txt\"\n\n\n@requires_file(ramsesCosmo)\ndef test_cosmo_detection():\n    ds = yt.load(ramsesCosmo, cosmological=True)\n    assert_equal(ds.cosmological_simulation, 1)\n\n    ds = yt.load(ramsesCosmo, cosmological=None)\n    assert_equal(ds.cosmological_simulation, 1)\n\n    ds = yt.load(ramsesCosmo)\n    assert_equal(ds.cosmological_simulation, 1)\n\n\n@requires_file(ramsesCosmo)\ndef test_unit_cosmo():\n    for force_cosmo in [True, None]:\n        ds = yt.load(ramsesCosmo, cosmological=force_cosmo)\n\n        # NOTE: these are the old test values, which used 3.08e24 as\n        # the Mpc to cm conversion factor\n        # expected_raw_time = 1.119216564055017 # in ramses unit\n        # expected_time = 3.756241729312462e17 # in seconds\n\n        expected_raw_time = 1.1213297131656712  # in ramses unit\n        np.testing.assert_equal(\n            ds.current_time.to(\"code_time\").value, expected_raw_time\n        )\n\n        expected_time = 3.7633337427123904e17  # in seconds\n        np.testing.assert_equal(ds.current_time.to(\"s\").value, expected_time)\n\n\nramsesExtraFieldsSmall = \"ramses_extra_fields_small/output_00001\"\n\n\n@requires_file(ramsesExtraFieldsSmall)\ndef test_extra_fields():\n    extra_fields = [(\"particle_family\", \"I\"), (\"particle_pointer\", \"I\")]\n    ds = yt.load(\n        os.path.join(ramsesExtraFieldsSmall, \"info_00001.txt\"),\n        extra_particle_fields=extra_fields,\n    )\n\n    # the dataset should contain the fields\n    for field, _ in extra_fields:\n        assert (\"all\", field) in ds.field_list\n\n    # Check the family (they should equal 100, for tracer particles)\n    dd = ds.all_data()\n    families = dd[\"all\", \"particle_family\"]\n    assert all(families == 100)\n\n\n@requires_file(ramsesExtraFieldsSmall)\ndef test_extra_fields_2():\n    extra_fields = [f\"particle_extra_field_{i + 1}\" for i in range(2)]\n    ds = yt.load(os.path.join(ramsesExtraFieldsSmall, \"info_00001.txt\"))\n\n    # When migrating to pytest, uncomment this\n    # with pytest.warns(\n    #     UserWarning,\n    #     match=r\"Field (.*) has a length \\d+, but expected a length of \\d+.()\",\n    # ):\n    #     ds.index\n\n    # the dataset should contain the fields\n    for field in extra_fields:\n        assert (\"io\", field) in ds.field_list\n\n    # In the dataset, the fields are integers, so we cannot test\n    # that they are accessed correctly.\n\n\nramses_rt = \"ramses_rt_00088/output_00088/info_00088.txt\"\n\n\n@requires_file(ramses_rt)\ndef test_ramses_rt():\n    ds = yt.load(ramses_rt)\n    ad = ds.all_data()\n\n    expected_fields = [\n        \"Density\",\n        \"x-velocity\",\n        \"y-velocity\",\n        \"z-velocity\",\n        \"Pres_IR\",\n        \"Pressure\",\n        \"Metallicity\",\n        \"HII\",\n        \"HeII\",\n        \"HeIII\",\n    ]\n\n    for field in expected_fields:\n        assert (\"ramses\", field) in ds.field_list\n\n        # test that field access works\n        ad[\"ramses\", field]\n\n    # test that special derived fields for RT datasets work\n    special_fields = [(\"gas\", \"temp_IR\")]\n    species = [\"H_p1\", \"He_p1\", \"He_p2\"]\n    for specie in species:\n        special_fields.extend(\n            [\n                (\"gas\", f\"{specie}_fraction\"),\n                (\"gas\", f\"{specie}_density\"),\n                (\"gas\", f\"{specie}_mass\"),\n            ]\n        )\n\n    for field in special_fields:\n        assert field in ds.derived_field_list\n        ad[field]\n\n    # Test the creation of rt fields\n    rt_fields = [(\"rt\", \"photon_density_1\")] + [\n        (\"rt\", f\"photon_flux_{d}_1\") for d in \"xyz\"\n    ]\n    for field in rt_fields:\n        assert field in ds.derived_field_list\n        ad[field]\n\n\nramses_sink = \"ramses_sink_00016/output_00016/info_00016.txt\"\n\n\n@requires_file(ramses_sink)\ndef test_ramses_sink():\n    expected_fields = [\n        \"BH_bondi_accretion\",\n        \"BH_eddington_accretion\",\n        \"BH_efficiency\",\n        \"BH_esave\",\n        \"BH_real_accretion\",\n        \"BH_spin\",\n        \"BH_spin_x\",\n        \"BH_spin_y\",\n        \"BH_spin_z\",\n        \"gas_spin_x\",\n        \"gas_spin_y\",\n        \"gas_spin_z\",\n        \"particle_birth_time\",\n        \"particle_identifier\",\n        \"particle_mass\",\n        \"particle_position_x\",\n        \"particle_position_y\",\n        \"particle_position_z\",\n        \"particle_prop_0_0\",\n        \"particle_prop_0_1\",\n        \"particle_prop_0_2\",\n        \"particle_prop_0_3\",\n        \"particle_prop_0_4\",\n        \"particle_prop_0_5\",\n        \"particle_prop_0_6\",\n        \"particle_velocity_x\",\n        \"particle_velocity_y\",\n        \"particle_velocity_z\",\n    ]\n\n    # Check that sinks are autodetected\n    ds = yt.load(ramses_sink)\n    ad = ds.all_data()\n\n    for field in expected_fields:\n        assert (\"sink\", field) in ds.field_list\n\n        # test that field access works\n        ad[\"sink\", field]\n\n    # Checking that sinks are autodetected\n    ds = yt.load(ramsesCosmo)\n    ad = ds.all_data()\n\n    for field in expected_fields:\n        assert (\"sink\", field) not in ds.field_list\n\n\nramses_new_format = \"ramses_new_format/output_00002/info_00002.txt\"\n\n\n@requires_file(ramses_new_format)\ndef test_new_format():\n    expected_particle_fields = [\n        (\"star\", \"particle_identity\"),\n        (\"star\", \"particle_level\"),\n        (\"star\", \"particle_mass\"),\n        (\"star\", \"particle_metallicity\"),\n        (\"star\", \"particle_position_x\"),\n        (\"star\", \"particle_position_y\"),\n        (\"star\", \"particle_position_z\"),\n        (\"star\", \"particle_tag\"),\n        (\"star\", \"particle_velocity_x\"),\n        (\"star\", \"particle_velocity_y\"),\n        (\"star\", \"particle_velocity_z\"),\n    ]\n\n    ds = yt.load(ramses_new_format)\n    ad = ds.all_data()\n\n    # Check all the expected fields exist and can be accessed\n    for f in expected_particle_fields:\n        assert f in ds.derived_field_list\n        ad[f]\n\n    # Check there is only stars with tag 0 (it should be right)\n    assert all(ad[\"star\", \"particle_family\"] == 2)\n    assert all(ad[\"star\", \"particle_tag\"] == 0)\n    assert len(ad[\"star\", \"particle_tag\"]) == 600\n\n\n@requires_file(ramses_sink)\ndef test_ramses_part_count():\n    ds = yt.load(ramses_sink)\n    pcount = ds.particle_type_counts\n\n    assert_equal(pcount[\"io\"], 17132, err_msg=\"Got wrong number of io particle\")\n    assert_equal(pcount[\"sink\"], 8, err_msg=\"Got wrong number of sink particle\")\n\n\n@requires_file(ramsesCosmo)\ndef test_custom_particle_def():\n    ytcfg.add_section(\"ramses-particles\")\n    ytcfg[\"ramses-particles\", \"fields\"] = \"\"\"particle_position_x, d\n         particle_position_y, d\n         particle_position_z, d\n         particle_velocity_x, d\n         particle_velocity_y, d\n         particle_velocity_z, d\n         particle_mass, d\n         particle_identifier, i\n         particle_refinement_level, I\n         particle_birth_time, d\n         particle_foobar, d\n    \"\"\"\n    ds = yt.load(ramsesCosmo)\n\n    def check_unit(array, unit):\n        assert str(array.in_cgs().units) == unit\n\n    try:\n        assert (\"io\", \"particle_birth_time\") in ds.derived_field_list\n        assert (\"io\", \"particle_foobar\") in ds.derived_field_list\n\n        check_unit(ds.r[\"io\", \"particle_birth_time\"], \"s\")\n        check_unit(ds.r[\"io\", \"particle_foobar\"], \"dimensionless\")\n    finally:\n        ytcfg.remove_section(\"ramses-particles\")\n\n\n@requires_file(output_00080)\n@requires_file(ramses_sink)\ndef test_grav_detection():\n    for path, has_potential in ((output_00080, False), (ramses_sink, True)):\n        ds = yt.load(path)\n\n        # Test detection\n        for k in \"xyz\":\n            assert (\"gravity\", f\"{k}-acceleration\") in ds.field_list\n            assert (\"gas\", f\"acceleration_{k}\") in ds.derived_field_list\n\n        if has_potential:\n            assert (\"gravity\", \"Potential\") in ds.field_list\n            assert (\"gas\", \"potential\") in ds.derived_field_list\n            assert (\"gas\", \"potential_energy\") in ds.derived_field_list\n\n        # Test access\n        for k in \"xyz\":\n            ds.r[\"gas\", f\"acceleration_{k}\"].to(\"m/s**2\")\n\n        if has_potential:\n            ds.r[\"gas\", \"potential\"].to(\"m**2/s**2\")\n            ds.r[\"gas\", \"potential_energy\"].to(\"kg*m**2/s**2\")\n\n\n@requires_file(ramses_sink)\n@requires_file(output_00080)\ndef test_ramses_field_detection():\n    ds1 = yt.load(ramses_rt)\n\n    assert \"ramses\" not in DETECTED_FIELDS\n\n    # Now they are detected !\n    ds1.index\n    P1 = HydroFieldFileHandler.parameters\n    assert DETECTED_FIELDS[ds1.unique_identifier][\"ramses\"] is not None\n    fields_1 = set(DETECTED_FIELDS[ds1.unique_identifier][\"ramses\"])\n\n    # Check the right number of variables has been loaded\n    assert P1[ds1.unique_identifier][\"nvar\"] == 10\n    assert len(fields_1) == P1[ds1.unique_identifier][\"nvar\"]\n\n    # Now load another dataset\n    ds2 = yt.load(output_00080)\n    ds2.index\n    P2 = HydroFieldFileHandler.parameters\n    fields_2 = set(DETECTED_FIELDS[ds2.unique_identifier][\"ramses\"])\n\n    # Check the right number of variables has been loaded\n    assert P2[ds2.unique_identifier][\"nvar\"] == 6\n    assert len(fields_2) == P2[ds2.unique_identifier][\"nvar\"]\n\n\n@requires_file(ramses_new_format)\n@requires_file(ramsesCosmo)\n@requires_file(ramsesNonCosmo)\ndef test_formation_time():\n    extra_particle_fields = [\n        (\"particle_birth_time\", \"d\"),\n        (\"particle_metallicity\", \"d\"),\n    ]\n\n    # test semantics for cosmological dataset\n    ds = yt.load(ramsesCosmo, extra_particle_fields=extra_particle_fields)\n    assert (\"io\", \"particle_birth_time\") in ds.field_list\n    assert (\"io\", \"conformal_birth_time\") in ds.field_list\n    assert (\"io\", \"particle_metallicity\") in ds.field_list\n\n    ad = ds.all_data()\n    whstars = ad[\"io\", \"conformal_birth_time\"] != 0\n    assert np.all(ad[\"io\", \"star_age\"][whstars] > 0)\n\n    # test semantics for non-cosmological new-style output format\n    ds = yt.load(ramses_new_format)\n    ad = ds.all_data()\n    assert (\"io\", \"particle_birth_time\") in ds.field_list\n    assert np.all(ad[\"io\", \"particle_birth_time\"] > 0)\n\n    # test semantics for non-cosmological old-style output format\n    ds = yt.load(ramsesNonCosmo, extra_particle_fields=extra_particle_fields)\n    ad = ds.all_data()\n    assert (\"io\", \"particle_birth_time\") in ds.field_list\n    # the dataset only includes particles with arbitrarily old ages\n    # and particles that formed in the very first timestep\n    assert np.all(ad[\"io\", \"particle_birth_time\"] <= 0)\n    whdynstars = ad[\"io\", \"particle_birth_time\"] == 0\n    assert np.all(ad[\"io\", \"star_age\"][whdynstars] == ds.current_time)\n\n\n@requires_file(ramses_new_format)\ndef test_cooling_fields():\n    # Test the field is being loaded correctly\n    ds = yt.load(ramses_new_format)\n\n    # Derived cooling fields\n    assert (\"gas\", \"cooling_net\") in ds.derived_field_list\n    assert (\"gas\", \"cooling_total\") in ds.derived_field_list\n    assert (\"gas\", \"heating_total\") in ds.derived_field_list\n    assert (\"gas\", \"number_density\") in ds.derived_field_list\n\n    # Original cooling fields\n    assert (\"gas\", \"cooling_primordial\") in ds.derived_field_list\n    assert (\"gas\", \"cooling_compton\") in ds.derived_field_list\n    assert (\"gas\", \"cooling_metal\") in ds.derived_field_list\n    assert (\"gas\", \"heating_primordial\") in ds.derived_field_list\n    assert (\"gas\", \"heating_compton\") in ds.derived_field_list\n    assert (\"gas\", \"cooling_primordial_prime\") in ds.derived_field_list\n    assert (\"gas\", \"cooling_compton_prime\") in ds.derived_field_list\n    assert (\"gas\", \"cooling_metal_prime\") in ds.derived_field_list\n    assert (\"gas\", \"heating_primordial_prime\") in ds.derived_field_list\n    assert (\"gas\", \"heating_compton_prime\") in ds.derived_field_list\n    assert (\"gas\", \"mu\") in ds.derived_field_list\n\n    # Abundances\n    assert (\"gas\", \"Electron_number_density\") in ds.derived_field_list\n    assert (\"gas\", \"HI_number_density\") in ds.derived_field_list\n    assert (\"gas\", \"HII_number_density\") in ds.derived_field_list\n    assert (\"gas\", \"HeI_number_density\") in ds.derived_field_list\n    assert (\"gas\", \"HeII_number_density\") in ds.derived_field_list\n    assert (\"gas\", \"HeIII_number_density\") in ds.derived_field_list\n\n    def check_unit(array, unit):\n        assert str(array.in_cgs().units) == unit\n\n    check_unit(ds.r[\"gas\", \"cooling_total\"], \"cm**5*g/s**3\")\n    check_unit(ds.r[\"gas\", \"cooling_primordial_prime\"], \"cm**5*g/(K*s**3)\")\n    check_unit(ds.r[\"gas\", \"number_density\"], \"cm**(-3)\")\n    check_unit(ds.r[\"gas\", \"mu\"], \"dimensionless\")\n    check_unit(ds.r[\"gas\", \"Electron_number_density\"], \"cm**(-3)\")\n\n\n@requires_file(ramses_rt)\ndef test_ramses_mixed_files():\n    # Test that one can use derived fields that depend on different\n    # files (here hydro and rt files)\n    ds = yt.load(ramses_rt)\n\n    def _mixed_field(data):\n        return data[\"rt\", \"photon_density_1\"] / data[\"gas\", \"H_nuclei_density\"]\n\n    ds.add_field((\"gas\", \"mixed_files\"), function=_mixed_field, sampling_type=\"cell\")\n\n    # Access the field\n    ds.r[\"gas\", \"mixed_files\"]\n\n\nramses_empty_record = \"ramses_empty_record/output_00003/info_00003.txt\"\n\n\n@requires_file(ramses_empty_record)\ndef test_ramses_empty_record():\n    # Test that yt can load datasets with empty records\n    ds = yt.load(ramses_empty_record)\n\n    # This should not fail\n    ds.index\n\n    # Access some field\n    ds.r[\"gas\", \"density\"]\n\n\n@requires_file(ramses_new_format)\n@requires_module(\"f90nml\")\ndef test_namelist_reading():\n    ds = data_dir_load(ramses_new_format)\n    namelist_fname = os.path.join(ds.directory, \"namelist.txt\")\n    with open(namelist_fname) as f:\n        ref = f90nml.read(f)\n\n    nml = ds.parameters[\"namelist\"]\n\n    assert nml == ref\n\n\n@requires_file(ramses_empty_record)\n@requires_file(output_00080)\n@requires_module(\"f90nml\")\ndef test_namelist_reading_should_not_fail():\n    for ds_name in (ramses_empty_record, output_00080):\n        # Test that the reading does not fail for malformed namelist.txt files\n        ds = data_dir_load(ds_name)\n        ds.index  # should work\n\n\nramses_mhd_128 = \"ramses_mhd_128/output_00027/info_00027.txt\"\n\n\n@requires_file(ramses_mhd_128)\ndef test_magnetic_field_aliasing():\n    # Test if RAMSES magnetic fields are correctly aliased to yt magnetic fields and if\n    # derived magnetic quantities are calculated\n    ds = data_dir_load(ramses_mhd_128)\n    ad = ds.all_data()\n\n    for field in [\n        \"magnetic_field_x\",\n        \"magnetic_field_magnitude\",\n        \"alfven_speed\",\n        \"magnetic_field_divergence\",\n    ]:\n        assert (\"gas\", field) in ds.derived_field_list\n        ad[\"gas\", field]\n\n\n@requires_file(output_00080)\ndef test_field_accession():\n    ds = yt.load(output_00080)\n    fields = [\n        (\"gas\", \"density\"),  # basic ones\n        (\"gas\", \"pressure\"),\n        (\"gas\", \"pressure_gradient_magnitude\"),  # requires ghost zones\n    ]\n    # Check accessing gradient works for a variety of spatial domains\n    for reg in (\n        ds.all_data(),\n        ds.sphere([0.1] * 3, 0.01),\n        ds.sphere([0.5] * 3, 0.05),\n        ds.box([0.1] * 3, [0.2] * 3),\n    ):\n        for field in fields:\n            reg[field]\n\n\n@requires_file(output_00080)\ndef test_ghost_zones():\n    ds = yt.load(output_00080)\n\n    def gen_dummy(ngz):\n        def dummy(data):\n            if not isinstance(data, FieldDetector):\n                shape = data[\"gas\", \"mach_number\"].shape[:3]\n                np.testing.assert_equal(shape, (2 + 2 * ngz, 2 + 2 * ngz, 2 + 2 * ngz))\n            return data[\"gas\", \"mach_number\"]\n\n        return dummy\n\n    fields = []\n    for ngz in (1, 2, 3):\n        fname = (\"gas\", f\"density_ghost_zone_{ngz}\")\n        ds.add_field(\n            fname,\n            gen_dummy(ngz),\n            sampling_type=\"cell\",\n            units=\"\",\n            validators=[yt.ValidateSpatial(ghost_zones=ngz)],\n        )\n        fields.append(fname)\n\n    box = ds.box([0, 0, 0], [0.1, 0.1, 0.1])\n    for f in fields:\n        print(\"Getting \", f)\n        box[f]\n\n\n@requires_file(output_00080)\ndef test_max_level():\n    ds = yt.load(output_00080)\n\n    assert any(ds.r[\"index\", \"grid_level\"] > 2)\n\n    # Should work\n    for ds in (\n        yt.load(output_00080, max_level=2, max_level_convention=\"yt\"),\n        yt.load(output_00080, max_level=8, max_level_convention=\"ramses\"),\n    ):\n        assert all(ds.r[\"index\", \"grid_level\"] <= 2)\n        assert any(ds.r[\"index\", \"grid_level\"] == 2)\n\n\n@requires_file(output_00080)\ndef test_invalid_max_level():\n    invalid_value_args = (\n        (1, None),\n        (1, \"foo\"),\n        (1, \"bar\"),\n        (-1, \"yt\"),\n    )\n    for lvl, convention in invalid_value_args:\n        with assert_raises(ValueError):\n            yt.load(output_00080, max_level=lvl, max_level_convention=convention)\n\n    invalid_type_args = (\n        (1.0, \"yt\"),  # not an int\n        (\"invalid\", \"yt\"),\n    )\n    # Should fail with value errors\n    for lvl, convention in invalid_type_args:\n        with assert_raises(TypeError):\n            yt.load(output_00080, max_level=lvl, max_level_convention=convention)\n\n\n@requires_file(ramses_new_format)\ndef test_print_stats():\n    ds = yt.load(ramses_new_format)\n\n    # Should work\n    ds.print_stats()\n\n    # FIXME #3197: use `capsys` with pytest to make sure the print_stats function works as intended\n\n\n@requires_file(output_00080)\ndef test_reading_order():\n    # This checks the bug unvovered in #4880\n    # This checks that the result of field accession doesn't\n    # depend on the order\n\n    def _dummy_field(data):\n        # Note: this is a dummy field\n        # that doesn't really have any physical meaning\n        # but may trigger some bug in the field\n        # handling.\n        T = data[\"gas\", \"temperature\"]\n        Z = data[\"gas\", \"metallicity\"]\n        return T * 1**Z\n\n    fields = [\n        \"Density\",\n        \"x-velocity\",\n        \"y-velocity\",\n        \"z-velocity\",\n        \"Pressure\",\n        \"Metallicity\",\n    ]\n    ds = yt.load(output_00080, fields=fields)\n\n    ds.add_field(\n        (\"gas\", \"test\"), function=_dummy_field, units=None, sampling_type=\"cell\"\n    )\n\n    ad = ds.all_data()\n    v0 = ad[\"gas\", \"test\"]\n\n    ad = ds.all_data()\n    ad[\"gas\", \"temperature\"]\n    v1 = ad[\"gas\", \"test\"]\n\n    np.testing.assert_allclose(v0, v1)\n\n\n# Simple context manager that overrides the value of\n# the self_shielding flag in the namelist.txt file\n@contextmanager\ndef override_self_shielding(root: Path, section: str, val: bool):\n    # Copy content of root in a temporary folder\n    with TemporaryDirectory() as tmp:\n        tmpdir = Path(tmp) / root.name\n        tmpdir.mkdir()\n\n        # Copy content of `root` in `tmpdir` recursively\n        copytree(root, tmpdir, dirs_exist_ok=True)\n\n        fname = Path(tmpdir) / \"namelist.txt\"\n\n        with open(fname) as f:\n            namelist_data = f90nml.read(f).todict()\n            sec = namelist_data.get(section, {})\n            sec[\"self_shielding\"] = val\n            namelist_data[section] = sec\n\n        with open(fname, \"w\") as f:\n            new_nml = f90nml.Namelist(namelist_data)\n            new_nml.write(f)\n\n        yield tmpdir\n\n\n@requires_file(ramses_new_format)\n@requires_module(\"f90nml\")\ndef test_self_shielding_logic():\n    ##################################################\n    # Check that the self_shielding flag is correctly read\n    ds = yt.load(ramses_new_format)\n\n    assert ds.parameters[\"namelist\"] is not None\n    assert ds.self_shielding is False\n\n    ##################################################\n    # Modify the namelist in-situ, reload and check\n    root_folder = Path(ds.root_folder)\n    for section in (\"physics_params\", \"cooling_params\"):\n        for val in (True, False):\n            with override_self_shielding(root_folder, section, val) as tmp_output:\n                # Autodetection should work\n                ds = yt.load(tmp_output)\n                assert ds.parameters[\"namelist\"] is not None\n                assert ds.self_shielding is val\n\n                # Manually set should ignore the namelist\n                ds = yt.load(tmp_output, self_shielding=True)\n                assert ds.self_shielding is True\n\n                ds = yt.load(tmp_output, self_shielding=False)\n                assert ds.self_shielding is False\n\n    ##################################################\n    # Manually set should work, even if namelist does not contain the flag\n    ds = yt.load(ramses_new_format, self_shielding=True)\n    assert ds.self_shielding is True\n\n    ds = yt.load(ramses_new_format, self_shielding=False)\n    assert ds.self_shielding is False\n\n\nramses_star_formation = \"ramses_star_formation/output_00013\"\n\n\n@requires_file(ramses_star_formation)\n@requires_module(\"f90nml\")\ndef test_self_shielding_loading():\n    ds_off = yt.load(ramses_star_formation, self_shielding=False)\n    ds_on = yt.load(ramses_star_formation, self_shielding=True)\n\n    # We do not expect any significant change at densities << 1e-2\n    reg_on = ds_on.all_data().cut_region(\n        [\"obj['gas', 'density'].to('mp/cm**3') < 1e-6\"]\n    )\n    reg_off = ds_off.all_data().cut_region(\n        [\"obj['gas', 'density'].to('mp/cm**3') < 1e-6\"]\n    )\n\n    np.testing.assert_allclose(\n        reg_on[\"gas\", \"cooling_total\"],\n        reg_off[\"gas\", \"cooling_total\"],\n        rtol=1e-5,\n    )\n\n    # We expect large differences from 1e-2 mp/cc\n    reg_on = ds_on.all_data().cut_region(\n        [\"obj['gas', 'density'].to('mp/cm**3') > 1e-2\"]\n    )\n    reg_off = ds_off.all_data().cut_region(\n        [\"obj['gas', 'density'].to('mp/cm**3') > 1e-2\"]\n    )\n    # Primordial cooling decreases with density (except at really high temperature)\n    # so we expect the boosted version to cool *less* efficiently\n    diff = (\n        reg_on[\"gas\", \"cooling_primordial\"] - reg_off[\"gas\", \"cooling_primordial\"]\n    ) / reg_off[\"gas\", \"cooling_primordial\"]\n    assert (np.isclose(diff, 0, atol=1e-5) | (diff < 0)).all()\n\n    # Also make sure the difference is large for some cells\n    assert (np.abs(diff) > 0.1).any()\n\n\n@requires_file(output_00080)\n@requires_file(ramses_mhd_128)\ndef test_order_does_not_matter():\n    for order in (1, 2):\n        ds0 = yt.load(output_00080)\n        ds1 = yt.load(ramses_mhd_128)\n\n        # This should not raise any exception\n        if order == 1:\n            _sp1 = ds1.all_data()\n            sp0 = ds0.all_data()\n        else:\n            sp0 = ds0.all_data()\n            _sp1 = ds1.all_data()\n\n        sp0[\"gas\", \"velocity_x\"].max().to(\"km/s\")\n"
  },
  {
    "path": "yt/frontends/ramses/tests/test_outputs_pytest.py",
    "content": "from copy import deepcopy\n\nimport pytest\n\nimport yt\nfrom yt.config import ytcfg\nfrom yt.testing import requires_file\n\noutput_00080 = \"output_00080/info_00080.txt\"\nramses_new_format = \"ramses_new_format/output_00002/info_00002.txt\"\n\ncustom_hydro = [\n    \"my-Density\",\n    \"my-x-velocity\",\n    \"my-y-velocity\",\n    \"my-z-velocity\",\n    \"my-Pressure\",\n    \"my-Metallicity\",\n]\ncustom_grav = [\n    \"my-x-acceleration\",\n    \"my-y-acceleration\",\n    \"my-z-acceleration\",\n]\n\n\n@pytest.fixture()\ndef custom_ramses_fields_conf():\n    old_config = deepcopy(ytcfg.config_root.as_dict())\n    ytcfg.add_section(\"ramses-hydro\")\n    ytcfg.add_section(\"ramses-grav\")\n    ytcfg.set(\"ramses-hydro\", \"fields\", custom_hydro)\n    ytcfg.set(\"ramses-grav\", \"fields\", custom_grav)\n    yield\n    ytcfg.remove_section(\"ramses-hydro\")\n    ytcfg.remove_section(\"ramses-grav\")\n    ytcfg.update(old_config)\n\n\n@requires_file(output_00080)\ndef test_field_config_1(custom_ramses_fields_conf):\n    ds = yt.load(output_00080)\n\n    for f in custom_hydro:\n        assert (\"ramses\", f) in ds.field_list\n    for f in custom_grav:\n        assert (\"gravity\", f) in ds.field_list\n\n\n@requires_file(ramses_new_format)\ndef test_field_config_2(custom_ramses_fields_conf):\n    ds = yt.load(ramses_new_format)\n\n    for f in custom_hydro:\n        assert (\"ramses\", f) in ds.field_list\n    for f in custom_grav:\n        assert (\"gravity\", f) in ds.field_list\n\n\n@requires_file(output_00080)\n@requires_file(ramses_new_format)\ndef test_warning_T2():\n    ds1 = yt.load(output_00080)\n    ds2 = yt.load(ramses_new_format)\n\n    # Should not raise warnings\n    ds1.r[\"gas\", \"temperature_over_mu\"]\n    ds2.r[\"gas\", \"temperature_over_mu\"]\n\n    # We cannot read the cooling tables of output_00080\n    # so this should raise a warning\n    with pytest.warns(\n        RuntimeWarning,\n        match=(\n            \"Trying to calculate temperature but the cooling tables couldn't be \"\n            r\"found or read\\. yt will return T/µ instead of T.*\"\n        ),\n    ):\n        ds1.r[\"gas\", \"temperature\"]\n\n    # But this one should not\n    ds2.r[\"gas\", \"temperature\"]\n"
  },
  {
    "path": "yt/frontends/rockstar/__init__.py",
    "content": "\"\"\"\nAPI for Rockstar frontend.\n\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/rockstar/api.py",
    "content": "from . import tests\nfrom .data_structures import RockstarDataset\nfrom .fields import RockstarFieldInfo\nfrom .io import IOHandlerRockstarBinary\n"
  },
  {
    "path": "yt/frontends/rockstar/data_structures.py",
    "content": "import glob\nimport os\nfrom functools import cached_property\nfrom typing import Any, Optional\n\nimport numpy as np\n\nfrom yt.data_objects.static_output import ParticleDataset\nfrom yt.frontends.halo_catalog.data_structures import HaloCatalogFile\nfrom yt.funcs import setdefaultattr\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.utilities import fortran_utils as fpu\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.exceptions import YTFieldNotFound\n\nfrom .definitions import header_dt\nfrom .fields import RockstarFieldInfo\n\n\nclass RockstarBinaryFile(HaloCatalogFile):\n    header: dict\n    _position_offset: int\n    _member_offset: int\n    _Npart: \"np.ndarray[Any, np.dtype[np.int64]]\"\n    _ids_halos: list[int]\n    _file_size: int\n\n    def __init__(self, ds, io, filename, file_id, range):\n        with open(filename, \"rb\") as f:\n            self.header = fpu.read_cattrs(f, header_dt, \"=\")\n            self._position_offset = f.tell()\n            pcount = self.header[\"num_halos\"]\n\n            halos = np.fromfile(f, dtype=io._halo_dt, count=pcount)\n            self._member_offset = f.tell()\n            self._ids_halos = list(halos[\"particle_identifier\"])\n            self._Npart = halos[\"num_p\"]\n\n            f.seek(0, os.SEEK_END)\n            self._file_size = f.tell()\n\n        expected_end = self._member_offset + 8 * self._Npart.sum()\n        if expected_end != self._file_size:\n            raise RuntimeError(\n                f\"File size {self._file_size} does not match expected size {expected_end}.\"\n            )\n\n        super().__init__(ds, io, filename, file_id, range)\n\n    def _read_member(\n        self, ihalo: int\n    ) -> Optional[\"np.ndarray[Any, np.dtype[np.int64]]\"]:\n        if ihalo not in self._ids_halos:\n            return None\n\n        ind_halo = self._ids_halos.index(ihalo)\n\n        ipos = self._member_offset + 8 * self._Npart[:ind_halo].sum()\n\n        with open(self.filename, \"rb\") as f:\n            f.seek(ipos, os.SEEK_SET)\n            ids = np.fromfile(f, dtype=np.int64, count=self._Npart[ind_halo])\n            return ids\n\n    def _read_particle_positions(self, ptype: str, f=None):\n        \"\"\"\n        Read all particle positions in this file.\n        \"\"\"\n\n        if f is None:\n            close = True\n            f = open(self.filename, \"rb\")\n        else:\n            close = False\n\n        pcount = self.header[\"num_halos\"]\n        pos = np.empty((pcount, 3), dtype=\"float64\")\n        f.seek(self._position_offset, os.SEEK_SET)\n        halos = np.fromfile(f, dtype=self.io._halo_dt, count=pcount)\n        for i, ax in enumerate(\"xyz\"):\n            pos[:, i] = halos[f\"particle_position_{ax}\"].astype(\"float64\")\n\n        if close:\n            f.close()\n\n        return pos\n\n\nclass RockstarIndex(ParticleIndex):\n    def get_member(self, ihalo: int):\n        for df in self.data_files:\n            members = df._read_member(ihalo)\n            if members is not None:\n                return members\n\n        raise RuntimeError(f\"Could not find halo {ihalo} in any data file.\")\n\n\nclass RockstarDataset(ParticleDataset):\n    _index_class = RockstarIndex\n    _file_class = RockstarBinaryFile\n    _field_info_class = RockstarFieldInfo\n    _suffix = \".bin\"\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"rockstar_binary\",\n        units_override=None,\n        unit_system=\"cgs\",\n        index_order=None,\n        index_filename=None,\n    ):\n        super().__init__(\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n\n    def _parse_parameter_file(self):\n        with open(self.parameter_filename, \"rb\") as f:\n            hvals = fpu.read_cattrs(f, header_dt)\n            hvals.pop(\"unused\")\n        self.dimensionality = 3\n        self.refine_by = 2\n        prefix = \".\".join(self.parameter_filename.rsplit(\".\", 2)[:-2])\n        self.filename_template = f\"{prefix}.%(num)s{self._suffix}\"\n        self.file_count = len(glob.glob(prefix + \".*\" + self._suffix))\n\n        # Now we can set up things we already know.\n        self.cosmological_simulation = 1\n        self.current_redshift = (1.0 / hvals[\"scale\"]) - 1.0\n        self.hubble_constant = hvals[\"h0\"]\n        self.omega_lambda = hvals[\"Ol\"]\n        self.omega_matter = hvals[\"Om\"]\n        cosmo = Cosmology(\n            hubble_constant=self.hubble_constant,\n            omega_matter=self.omega_matter,\n            omega_lambda=self.omega_lambda,\n        )\n        self.current_time = cosmo.lookback_time(self.current_redshift, 1e6).in_units(\n            \"s\"\n        )\n        self._periodicity = (True, True, True)\n        self.particle_types = \"halos\"\n        self.particle_types_raw = \"halos\"\n\n        self.domain_left_edge = np.array([0.0, 0.0, 0.0])\n        self.domain_right_edge = np.array([hvals[\"box_size\"]] * 3)\n\n        self.domain_dimensions = np.ones(3, \"int32\")\n        self.parameters.update(hvals)\n\n    def _set_code_unit_attributes(self):\n        z = self.current_redshift\n        setdefaultattr(self, \"length_unit\", self.quan(1.0 / (1.0 + z), \"Mpc / h\"))\n        setdefaultattr(self, \"mass_unit\", self.quan(1.0, \"Msun / h\"))\n        setdefaultattr(self, \"velocity_unit\", self.quan(1.0, \"km / s\"))\n        setdefaultattr(self, \"time_unit\", self.length_unit / self.velocity_unit)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".bin\"):\n            return False\n        try:\n            with open(filename, mode=\"rb\") as f:\n                header = fpu.read_cattrs(f, header_dt)\n        except OSError:\n            return False\n        else:\n            return header[\"magic\"] == 18077126535843729616\n\n    def halo(self, ptype, particle_identifier):\n        return RockstarHaloContainer(\n            ptype,\n            particle_identifier,\n            parent_ds=None,\n            halo_ds=self,\n        )\n\n\nclass RockstarHaloContainer:\n    def __init__(self, ptype, particle_identifier, *, parent_ds, halo_ds):\n        if ptype not in halo_ds.particle_types_raw:\n            raise RuntimeError(\n                f'Possible halo types are {halo_ds.particle_types_raw}, supplied \"{ptype}\".'\n            )\n\n        self.ds = parent_ds\n        self.halo_ds = halo_ds\n        self.ptype = ptype\n        self.particle_identifier = particle_identifier\n\n    def __repr__(self):\n        return f\"{self.halo_ds}_{self.ptype}_{self.particle_identifier:09d}\"\n\n    def __getitem__(self, key):\n        if isinstance(key, tuple):\n            ptype, field = key\n        else:\n            ptype = self.ptype\n            field = key\n\n        data = {\n            \"mass\": self.mass,\n            \"position\": self.position,\n            \"velocity\": self.velocity,\n            \"member_ids\": self.member_ids,\n        }\n        if ptype == \"halos\" and field in data:\n            return data[field]\n\n        raise YTFieldNotFound((ptype, field), dataset=self.ds)\n\n    @cached_property\n    def ihalo(self):\n        halo_id = self.particle_identifier\n        halo_ids = list(self.halo_ds.r[\"halos\", \"particle_identifier\"].astype(\"i8\"))\n        ihalo = halo_ids.index(halo_id)\n\n        assert halo_ids[ihalo] == halo_id\n\n        return ihalo\n\n    @property\n    def mass(self):\n        return self.halo_ds.r[\"halos\", \"particle_mass\"][self.ihalo]\n\n    @property\n    def position(self):\n        return self.halo_ds.r[\"halos\", \"particle_position\"][self.ihalo]\n\n    @property\n    def velocity(self):\n        return self.halo_ds.r[\"halos\", \"particle_velocity\"][self.ihalo]\n\n    @property\n    def member_ids(self):\n        return self.halo_ds.index.get_member(self.particle_identifier)\n"
  },
  {
    "path": "yt/frontends/rockstar/definitions.py",
    "content": "from typing import Any\n\nimport numpy as np\n\nBINARY_HEADER_SIZE = 256\nheader_dt = (\n    (\"magic\", 1, \"Q\"),\n    (\"snap\", 1, \"q\"),\n    (\"chunk\", 1, \"q\"),\n    (\"scale\", 1, \"f\"),\n    (\"Om\", 1, \"f\"),\n    (\"Ol\", 1, \"f\"),\n    (\"h0\", 1, \"f\"),\n    (\"bounds\", 6, \"f\"),\n    (\"num_halos\", 1, \"q\"),\n    (\"num_particles\", 1, \"q\"),\n    (\"box_size\", 1, \"f\"),\n    (\"particle_mass\", 1, \"f\"),\n    (\"particle_type\", 1, \"q\"),\n    (\"format_revision\", 1, \"i\"),\n    (\"version\", 12, \"c\"),\n    (\"unused\", BINARY_HEADER_SIZE - 4 * 12 - 4 - 8 * 6 - 12, \"c\"),\n)\n\n# Note the final field here, which is a field for min/max format revision in\n# which the field appears.\n\nKNOWN_REVISIONS: list[int] = [0, 1, 2]\n\n# using typing.Any here in lieu of numpy.typing.DTypeLike (should be backported for numpy < 1.20)\nHaloDataType = tuple[str, Any] | tuple[str, Any, tuple[int, int]]\nhalo_dt: list[HaloDataType] = [\n    (\"particle_identifier\", np.int64),\n    (\"particle_position_x\", np.float32),\n    (\"particle_position_y\", np.float32),\n    (\"particle_position_z\", np.float32),\n    (\"particle_mposition_x\", np.float32, (0, 0)),\n    (\"particle_mposition_y\", np.float32, (0, 0)),\n    (\"particle_mposition_z\", np.float32, (0, 0)),\n    (\"particle_velocity_x\", np.float32),\n    (\"particle_velocity_y\", np.float32),\n    (\"particle_velocity_z\", np.float32),\n    (\"particle_corevel_x\", np.float32, (1, 100)),\n    (\"particle_corevel_y\", np.float32, (1, 100)),\n    (\"particle_corevel_z\", np.float32, (1, 100)),\n    (\"particle_bulkvel_x\", np.float32),\n    (\"particle_bulkvel_y\", np.float32),\n    (\"particle_bulkvel_z\", np.float32),\n    (\"particle_mass\", np.float32),\n    (\"virial_radius\", np.float32),\n    (\"child_r\", np.float32),\n    (\"vmax_r\", np.float32),\n    (\"mgrav\", np.float32),\n    (\"vmax\", np.float32),\n    (\"rvmax\", np.float32),\n    (\"rs\", np.float32),\n    (\"klypin_rs\", np.float32),\n    (\"vrms\", np.float32),\n    (\"Jx\", np.float32),\n    (\"Jy\", np.float32),\n    (\"Jz\", np.float32),\n    (\"energy\", np.float32),\n    (\"spin\", np.float32),\n    (\"alt_m1\", np.float32),\n    (\"alt_m2\", np.float32),\n    (\"alt_m3\", np.float32),\n    (\"alt_m4\", np.float32),\n    (\"Xoff\", np.float32),\n    (\"Voff\", np.float32),\n    (\"b_to_a\", np.float32),\n    (\"c_to_a\", np.float32),\n    (\"Ax\", np.float32),\n    (\"Ay\", np.float32),\n    (\"Az\", np.float32),\n    (\"b_to_a2\", np.float32, (1, 100)),\n    (\"c_to_a2\", np.float32, (1, 100)),\n    (\"A2x\", np.float32, (1, 100)),\n    (\"A2y\", np.float32, (1, 100)),\n    (\"A2z\", np.float32, (1, 100)),\n    (\"bullock_spin\", np.float32),\n    (\"kin_to_pot\", np.float32),\n    (\"m_pe_b\", np.float32, (1, 100)),\n    (\"m_pe_d\", np.float32, (1, 100)),\n    (\"num_p\", np.int64),\n    (\"num_child_particles\", np.int64),\n    (\"p_start\", np.int64),\n    (\"desc\", np.int64),\n    (\"flags\", np.int64),\n    (\"n_core\", np.int64),\n    (\"min_pos_err\", np.float32),\n    (\"min_vel_err\", np.float32),\n    (\"min_bulkvel_err\", np.float32),\n    (\"type\", np.int32, (2, 100)),\n    (\"sm\", np.float32, (2, 100)),\n    (\"gas\", np.float32, (2, 100)),\n    (\"bh\", np.float32, (2, 100)),\n    (\"peak_density\", np.float32, (2, 100)),\n    (\"av_density\", np.float32, (2, 100)),\n]\n\n# using typing.Any here in lieu of numpy.typing.DTypeLike (should be backported for numpy < 1.20)\nhalo_dts_tmp: dict[int, list[HaloDataType]] = {}\nhalo_dts: dict[int, np.dtype] = {}\n\nfor rev in KNOWN_REVISIONS:\n    halo_dts_tmp[rev] = []\n    for item in halo_dt:\n        if len(item) == 2:\n            halo_dts_tmp[rev].append(item)\n        elif len(item) == 3:\n            mi, ma = item[2]\n            if (mi <= rev) and (rev <= ma):\n                halo_dts_tmp[rev].append(item[:2])\n    halo_dts[rev] = np.dtype(halo_dts_tmp[rev], align=True)\ndel halo_dts_tmp\n\nparticle_dt = np.dtype(\n    [\n        (\"particle_identifier\", np.int64),\n        (\"particle_position_x\", np.float32),\n        (\"particle_position_y\", np.float32),\n        (\"particle_position_z\", np.float32),\n        (\"particle_velocity_x\", np.float32),\n        (\"particle_velocity_y\", np.float32),\n        (\"particle_velocity_z\", np.float32),\n    ]\n)\n"
  },
  {
    "path": "yt/frontends/rockstar/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\nm_units = \"Msun / h\"  # Msun / h\np_units = \"Mpccm / h\"  # Mpc / h comoving\nv_units = \"km / s\"  # km /s phys, peculiar\nr_units = \"kpccm / h\"  # kpc / h comoving\n\n\nclass RockstarFieldInfo(FieldInfoContainer):\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_identifier\", (\"\", [], None)),\n        (\"particle_position_x\", (p_units, [], None)),\n        (\"particle_position_y\", (p_units, [], None)),\n        (\"particle_position_z\", (p_units, [], None)),\n        (\"particle_velocity_x\", (v_units, [], None)),\n        (\"particle_velocity_y\", (v_units, [], None)),\n        (\"particle_velocity_z\", (v_units, [], None)),\n        (\"particle_corevel_x\", (v_units, [], None)),\n        (\"particle_corevel_y\", (v_units, [], None)),\n        (\"particle_corevel_z\", (v_units, [], None)),\n        (\"particle_bulkvel_x\", (v_units, [], None)),\n        (\"particle_bulkvel_y\", (v_units, [], None)),\n        (\"particle_bulkvel_z\", (v_units, [], None)),\n        (\"particle_mass\", (m_units, [], \"Mass\")),\n        (\"virial_radius\", (r_units, [], \"Radius\")),\n        (\"child_r\", (r_units, [], None)),\n        (\"vmax_r\", (v_units, [], None)),\n        # These fields I don't have good definitions for yet.\n        (\"mgrav\", (\"\", [], None)),\n        (\"vmax\", (v_units, [], \"V_{max}\")),\n        (\"rvmax\", (v_units, [], None)),\n        (\"rs\", (r_units, [], \"R_s\")),\n        (\"klypin_rs\", (r_units, [], \"Klypin R_s\")),\n        (\"vrms\", (v_units, [], \"V_{rms}\")),\n        (\"Jx\", (\"\", [], \"J_x\")),\n        (\"Jy\", (\"\", [], \"J_y\")),\n        (\"Jz\", (\"\", [], \"J_z\")),\n        (\"energy\", (\"\", [], None)),\n        (\"spin\", (\"\", [], \"Spin Parameter\")),\n        (\"alt_m1\", (m_units, [], None)),\n        (\"alt_m2\", (m_units, [], None)),\n        (\"alt_m3\", (m_units, [], None)),\n        (\"alt_m4\", (m_units, [], None)),\n        (\"Xoff\", (\"\", [], None)),\n        (\"Voff\", (\"\", [], None)),\n        (\"b_to_a\", (\"\", [], \"Ellipsoidal b to a\")),\n        (\"c_to_a\", (\"\", [], \"Ellipsoidal c to a\")),\n        (\"Ax\", (\"\", [], \"A_x\")),\n        (\"Ay\", (\"\", [], \"A_y\")),\n        (\"Az\", (\"\", [], \"A_z\")),\n        (\"b_to_a2\", (\"\", [], None)),\n        (\"c_to_a2\", (\"\", [], None)),\n        (\"A2x\", (\"\", [], \"A2_x\")),\n        (\"A2y\", (\"\", [], \"A2_y\")),\n        (\"A2z\", (\"\", [], \"A2_z\")),\n        (\"bullock_spin\", (\"\", [], \"Bullock Spin Parameter\")),\n        (\"kin_to_pot\", (\"\", [], \"Kinetic to Potential\")),\n        (\"m_pe_b\", (\"\", [], None)),\n        (\"m_pe_d\", (\"\", [], None)),\n        (\"num_p\", (\"\", [], \"Number of Particles\")),\n        (\"num_child_particles\", (\"\", [], \"Number of Child Particles\")),\n        (\"p_start\", (\"\", [], None)),\n        (\"desc\", (\"\", [], None)),\n        (\"flags\", (\"\", [], None)),\n        (\"n_core\", (\"\", [], None)),\n        (\"min_pos_err\", (\"\", [], None)),\n        (\"min_vel_err\", (\"\", [], None)),\n        (\"min_bulkvel_err\", (\"\", [], None)),\n    )\n"
  },
  {
    "path": "yt/frontends/rockstar/io.py",
    "content": "import os\nfrom collections.abc import Sequence\n\nimport numpy as np\n\nfrom yt.utilities import fortran_utils as fpu\nfrom yt.utilities.io_handler import BaseParticleIOHandler\n\nfrom .definitions import halo_dts, header_dt\n\n\ndef _can_load_with_format(\n    filename: str, header_fmt: Sequence[tuple[str, int, str]], halo_format: np.dtype\n) -> bool:\n    with open(filename, \"rb\") as f:\n        header = fpu.read_cattrs(f, header_fmt, \"=\")\n        Nhalos = header[\"num_halos\"]\n        Nparttot = header[\"num_particles\"]\n        halos = np.fromfile(f, dtype=halo_format, count=Nhalos)\n\n        # Make sure all masses are > 0\n        if np.any(halos[\"particle_mass\"] <= 0):\n            return False\n        # Make sure number of particles sums to expected value\n        if halos[\"num_p\"].sum() != Nparttot:\n            return False\n\n    return True\n\n\nclass IOHandlerRockstarBinary(BaseParticleIOHandler):\n    _dataset_type = \"rockstar_binary\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._halo_dt = self.detect_rockstar_format(\n            self.ds.filename,\n            self.ds.parameters[\"format_revision\"],\n        )\n\n    @staticmethod\n    def detect_rockstar_format(\n        filename: str,\n        guess: int,\n    ) -> np.dtype:\n        revisions: list[int] = list(halo_dts.keys())\n        if guess in revisions:\n            revisions.pop(revisions.index(guess))\n        revisions = [guess] + revisions\n        for revision in revisions:\n            if _can_load_with_format(filename, header_dt, halo_dts[revision]):\n                return halo_dts[revision]\n        raise RuntimeError(f\"Could not detect Rockstar format for file {filename}\")\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This will read chunks and yield the results.\n\n        # Only support halo reading for now.\n        assert len(ptf) == 1\n        assert list(ptf.keys())[0] == \"halos\"\n        ptype = \"halos\"\n        for data_file in self._sorted_chunk_iterator(chunks):\n            pcount = data_file.header[\"num_halos\"]\n            if pcount == 0:\n                continue\n            with open(data_file.filename, \"rb\") as f:\n                pos = data_file._get_particle_positions(ptype, f=f)\n                yield \"halos\", (pos[:, i] for i in range(3)), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # Only support halo reading for now.\n        assert len(ptf) == 1\n        assert list(ptf.keys())[0] == \"halos\"\n        for data_file in self._sorted_chunk_iterator(chunks):\n            si, ei = data_file.start, data_file.end\n            pcount = data_file.header[\"num_halos\"]\n            if pcount == 0:\n                continue\n            with open(data_file.filename, \"rb\") as f:\n                for ptype, field_list in sorted(ptf.items()):\n                    pos = data_file._get_particle_positions(ptype, f=f)\n                    x, y, z = (pos[:, i] for i in range(3))\n                    mask = selector.select_points(x, y, z, 0.0)\n                    del x, y, z\n                    f.seek(data_file._position_offset, os.SEEK_SET)\n                    halos = np.fromfile(f, dtype=self._halo_dt, count=pcount)\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        data = halos[field][si:ei][mask].astype(\"float64\")\n                        yield (ptype, field), data\n\n    def _yield_coordinates(self, data_file):\n        # Just does halos\n        pcount = data_file.header[\"num_halos\"]\n        with open(data_file.filename, \"rb\") as f:\n            f.seek(data_file._position_offset, os.SEEK_SET)\n            halos = np.fromfile(f, dtype=self._halo_dt, count=pcount)\n            pos = np.empty((halos.size, 3), dtype=\"float64\")\n            pos[:, 0] = halos[\"particle_position_x\"]\n            pos[:, 1] = halos[\"particle_position_y\"]\n            pos[:, 2] = halos[\"particle_position_z\"]\n            yield \"halos\", pos\n\n    def _count_particles(self, data_file):\n        nhalos = data_file.header[\"num_halos\"]\n        si, ei = data_file.start, data_file.end\n        if None not in (si, ei):\n            nhalos = np.clip(nhalos - si, 0, ei - si)\n        return {\"halos\": nhalos}\n\n    def _identify_fields(self, data_file):\n        fields = [(\"halos\", f) for f in self._halo_dt.fields if \"padding\" not in f]\n        return fields, {}\n"
  },
  {
    "path": "yt/frontends/rockstar/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/rockstar/tests/test_outputs.py",
    "content": "import os.path\n\nfrom numpy.testing import assert_equal\n\nfrom yt.frontends.rockstar.api import RockstarDataset\nfrom yt.testing import ParticleSelectionComparison, requires_file\nfrom yt.utilities.answer_testing.framework import (\n    FieldValuesTest,\n    data_dir_load,\n    requires_ds,\n)\n\n_fields = (\n    (\"all\", \"particle_position_x\"),\n    (\"all\", \"particle_position_y\"),\n    (\"all\", \"particle_position_z\"),\n    (\"all\", \"particle_mass\"),\n)\n\nr1 = \"rockstar_halos/halos_0.0.bin\"\n\n\n@requires_ds(r1)\ndef test_fields_r1():\n    ds = data_dir_load(r1)\n    assert_equal(str(ds), os.path.basename(r1))\n    for field in _fields:\n        yield FieldValuesTest(r1, field, particle_type=True)\n\n\n@requires_file(r1)\ndef test_RockstarDataset():\n    assert isinstance(data_dir_load(r1), RockstarDataset)\n\n\n@requires_file(r1)\ndef test_particle_selection():\n    ds = data_dir_load(r1)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\n@requires_file(r1)\ndef test_halo_loading():\n    ds = data_dir_load(r1)\n\n    for halo_id, Npart in zip(\n        ds.r[\"halos\", \"particle_identifier\"],\n        ds.r[\"halos\", \"num_p\"],\n        strict=False,\n    ):\n        halo = ds.halo(\"halos\", halo_id)\n        assert halo is not None\n\n        # Try accessing properties\n        halo.position\n        halo.velocity\n        halo.mass\n\n        # Make sure we can access the member particles\n        assert_equal(len(halo.member_ids), Npart)\n"
  },
  {
    "path": "yt/frontends/sdf/__init__.py",
    "content": "\"\"\"\n__init__ for yt.frontends.sdf\n\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/sdf/api.py",
    "content": "from .data_structures import SDFDataset\nfrom .fields import SDFFieldInfo\nfrom .io import IOHandlerSDF\n"
  },
  {
    "path": "yt/frontends/sdf/data_structures.py",
    "content": "import os\nfrom functools import cached_property\n\nimport numpy as np\n\nfrom yt.data_objects.static_output import ParticleDataset, ParticleFile\nfrom yt.funcs import setdefaultattr\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _requests as requests\nfrom yt.utilities.sdf import HTTPSDFRead, SDFIndex, SDFRead\n\nfrom .fields import SDFFieldInfo\n\n# currently specified by units_2HOT == 2 in header\n# in future will read directly from file\nunits_2HOT_v2_length = 3.08567802e21\nunits_2HOT_v2_mass = 1.98892e43\nunits_2HOT_v2_time = 3.1558149984e16\n\n\nclass SDFFile(ParticleFile):\n    pass\n\n\nclass SDFDataset(ParticleDataset):\n    _load_requirements = [\"requests\"]\n    _index_class = ParticleIndex\n    _file_class = SDFFile\n    _field_info_class = SDFFieldInfo\n    _particle_mass_name = None\n    _particle_coordinates_name = None\n    _particle_velocity_name = None\n    _skip_cache = True\n    _subspace = False\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"sdf_particles\",\n        index_order=None,\n        index_filename=None,\n        bounding_box=None,\n        sdf_header=None,\n        midx_filename=None,\n        midx_header=None,\n        midx_level=None,\n        field_map=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        if bounding_box is not None:\n            # This ensures that we know a bounding box has been applied\n            self._domain_override = True\n            self._subspace = True\n            bbox = np.array(bounding_box, dtype=\"float64\")\n            if bbox.shape == (2, 3):\n                bbox = bbox.transpose()\n            self.domain_left_edge = bbox[:, 0]\n            self.domain_right_edge = bbox[:, 1]\n        else:\n            self.domain_left_edge = self.domain_right_edge = None\n        self.sdf_header = sdf_header\n        self.midx_filename = midx_filename\n        self.midx_header = midx_header\n        self.midx_level = midx_level\n        if field_map is None:\n            field_map = {}\n        self._field_map = field_map\n        prefix = \"\"\n        if self.midx_filename is not None:\n            prefix += \"midx_\"\n        if filename.startswith(\"http\"):\n            prefix += \"http_\"\n        dataset_type = prefix + \"sdf_particles\"\n        super().__init__(\n            filename,\n            dataset_type=dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            index_order=index_order,\n            index_filename=index_filename,\n        )\n\n    def _parse_parameter_file(self):\n        if self.parameter_filename.startswith(\"http\"):\n            sdf_class = HTTPSDFRead\n        else:\n            sdf_class = SDFRead\n        self.sdf_container = sdf_class(self.parameter_filename, header=self.sdf_header)\n\n        # Reference\n        self.parameters = self.sdf_container.parameters\n        self.dimensionality = 3\n        self.refine_by = 2\n\n        if self.domain_left_edge is None or self.domain_right_edge is None:\n            R0 = self.parameters[\"R0\"]\n            if \"offset_center\" in self.parameters and self.parameters[\"offset_center\"]:\n                self.domain_left_edge = np.array([0, 0, 0], dtype=np.float64)\n                self.domain_right_edge = np.array(\n                    [2.0 * self.parameters.get(f\"R{ax}\", R0) for ax in \"xyz\"],\n                    dtype=np.float64,\n                )\n            else:\n                self.domain_left_edge = np.array(\n                    [-self.parameters.get(f\"R{ax}\", R0) for ax in \"xyz\"],\n                    dtype=np.float64,\n                )\n                self.domain_right_edge = np.array(\n                    [+self.parameters.get(f\"R{ax}\", R0) for ax in \"xyz\"],\n                    dtype=np.float64,\n                )\n            self.domain_left_edge *= self.parameters.get(\"a\", 1.0)\n            self.domain_right_edge *= self.parameters.get(\"a\", 1.0)\n\n        self.domain_dimensions = np.ones(3, \"int32\")\n        if self.parameters.get(\"do_periodic\", False):\n            self._periodicity = (True, True, True)\n        else:\n            self._periodicity = (False, False, False)\n\n        self.cosmological_simulation = 1\n\n        self.current_redshift = self.parameters.get(\"redshift\", 0.0)\n        self.omega_lambda = self.parameters[\"Omega0_lambda\"]\n        self.omega_matter = self.parameters[\"Omega0_m\"]\n        if \"Omega0_fld\" in self.parameters:\n            self.omega_lambda += self.parameters[\"Omega0_fld\"]\n        if \"Omega0_r\" in self.parameters:\n            # not correct, but most codes can't handle Omega0_r\n            self.omega_matter += self.parameters[\"Omega0_r\"]\n        self.hubble_constant = self.parameters[\"h_100\"]\n        self.current_time = units_2HOT_v2_time * self.parameters.get(\"tpos\", 0.0)\n        mylog.info(\"Calculating time to be %0.3e seconds\", self.current_time)\n        self.filename_template = self.parameter_filename\n        self.file_count = 1\n\n    @cached_property\n    def midx(self):\n        if self.midx_filename is None:\n            raise RuntimeError(\"SDF index0 file not supplied in load.\")\n\n        if \"http\" in self.midx_filename:\n            sdf_class = HTTPSDFRead\n        else:\n            sdf_class = SDFRead\n        indexdata = sdf_class(self.midx_filename, header=self.midx_header)\n        return SDFIndex(self.sdf_container, indexdata, level=self.midx_level)\n\n    def _set_code_unit_attributes(self):\n        setdefaultattr(\n            self,\n            \"length_unit\",\n            self.quan(1.0, self.parameters.get(\"length_unit\", \"kpc\")),\n        )\n        setdefaultattr(\n            self,\n            \"velocity_unit\",\n            self.quan(1.0, self.parameters.get(\"velocity_unit\", \"kpc/Gyr\")),\n        )\n        setdefaultattr(\n            self, \"time_unit\", self.quan(1.0, self.parameters.get(\"time_unit\", \"Gyr\"))\n        )\n        mass_unit = self.parameters.get(\"mass_unit\", \"1e10 Msun\")\n        if \" \" in mass_unit:\n            factor, unit = mass_unit.split(\" \")\n        else:\n            factor = 1.0\n            unit = mass_unit\n        setdefaultattr(self, \"mass_unit\", self.quan(float(factor), unit))\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if cls._missing_load_requirements():\n            return False\n\n        sdf_header = kwargs.get(\"sdf_header\", filename)\n        if sdf_header.startswith(\"http\"):\n            hreq = requests.get(sdf_header, stream=True)\n\n            if hreq.status_code != 200:\n                return False\n            # Grab a whole 4k page.\n            line = next(hreq.iter_content(4096))\n        elif os.path.isfile(sdf_header):\n            try:\n                with open(sdf_header, encoding=\"ISO-8859-1\") as f:\n                    line = f.read(10).strip()\n            except PermissionError:\n                return False\n        else:\n            return False\n        return line.startswith(\"# SDF\")\n"
  },
  {
    "path": "yt/frontends/sdf/definitions.py",
    "content": ""
  },
  {
    "path": "yt/frontends/sdf/fields.py",
    "content": "from yt.fields.field_info_container import FieldInfoContainer\n\n\nclass SDFFieldInfo(FieldInfoContainer):\n    known_other_fields = ()\n\n    known_particle_fields = ()\n    _mass_field = None\n\n    def __init__(self, ds, field_list):\n        if \"mass\" in field_list:\n            self.known_particle_fields.append(\n                (\"mass\", \"code_mass\", [\"particle_mass\"], None)\n            )\n        possible_masses = [\"mass\", \"m200b\", \"mvir\"]\n        mnf = \"mass\"\n        for mn in possible_masses:\n            if mn in ds.sdf_container.keys():\n                mnf = self._mass_field = mn\n                break\n\n        idf = ds._field_map.get(\"particle_index\", \"ident\")\n        xf = ds._field_map.get(\"particle_position_x\", \"x\")\n        yf = ds._field_map.get(\"particle_position_y\", \"y\")\n        zf = ds._field_map.get(\"particle_position_z\", \"z\")\n        vxf = ds._field_map.get(\"particle_velocity_x\", \"vx\")\n        vyf = ds._field_map.get(\"particle_velocity_z\", \"vy\")\n        vzf = ds._field_map.get(\"particle_velocity_z\", \"vz\")\n\n        self.known_particle_fields = (\n            (idf, (\"dimensionless\", [\"particle_index\"], None)),\n            (xf, (\"code_length\", [\"particle_position_x\"], None)),\n            (yf, (\"code_length\", [\"particle_position_y\"], None)),\n            (zf, (\"code_length\", [\"particle_position_z\"], None)),\n            (vxf, (\"code_velocity\", [\"particle_velocity_x\"], None)),\n            (vyf, (\"code_velocity\", [\"particle_velocity_y\"], None)),\n            (vzf, (\"code_velocity\", [\"particle_velocity_z\"], None)),\n            (mnf, (\"code_mass\", [\"particle_mass\"], None)),\n        )\n        super().__init__(ds, field_list)\n"
  },
  {
    "path": "yt/frontends/sdf/io.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog\nfrom yt.utilities.io_handler import BaseParticleIOHandler\n\n\nclass IOHandlerSDF(BaseParticleIOHandler):\n    _dataset_type = \"sdf_particles\"\n\n    @property\n    def _handle(self):\n        return self.ds.sdf_container\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _read_particle_coords(self, chunks, ptf):\n        assert len(ptf) == 1\n        assert ptf.keys()[0] == \"dark_matter\"\n        data_files = self._get_data_files(chunks)\n        assert len(data_files) == 1\n        for _data_file in sorted(data_files, key=lambda x: (x.filename, x.start)):\n            yield (\n                \"dark_matter\",\n                (\n                    self._handle[\"x\"],\n                    self._handle[\"y\"],\n                    self._handle[\"z\"],\n                ),\n                0.0,\n            )\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        assert len(ptf) == 1\n        assert ptf.keys()[0] == \"dark_matter\"\n        data_files = self._get_data_files(chunks)\n        assert len(data_files) == 1\n        for _data_file in sorted(data_files, key=lambda x: (x.filename, x.start)):\n            for ptype, field_list in sorted(ptf.items()):\n                x = self._handle[\"x\"]\n                y = self._handle[\"y\"]\n                z = self._handle[\"z\"]\n                mask = selector.select_points(x, y, z, 0.0)\n                del x, y, z\n                if mask is None:\n                    continue\n                for field in field_list:\n                    if field == \"mass\":\n                        data = np.ones(mask.sum(), dtype=\"float64\")\n                        data *= self.ds.parameters[\"particle_mass\"]\n                    else:\n                        data = self._handle[field][mask]\n                    yield (ptype, field), data\n\n    def _identify_fields(self, data_file):\n        fields = [(\"dark_matter\", v) for v in self._handle.keys()]\n        fields.append((\"dark_matter\", \"mass\"))\n        return fields, {}\n\n    def _count_particles(self, data_file):\n        pcount = self._handle[\"x\"].size\n        if pcount > 1e9:\n            mylog.warning(\n                \"About to load %i particles into memory. \"\n                \"You may want to consider a midx-enabled load\",\n                pcount,\n            )\n        return {\"dark_matter\": pcount}\n\n\nclass IOHandlerHTTPSDF(IOHandlerSDF):\n    _dataset_type = \"http_sdf_particles\"\n\n    def _read_particle_coords(self, chunks, ptf):\n        chunks = list(chunks)\n        data_files = set()\n        assert len(ptf) == 1\n        assert ptf.keys()[0] == \"dark_matter\"\n        for chunk in chunks:\n            for obj in chunk.objs:\n                data_files.update(obj.data_files)\n        assert len(data_files) == 1\n        for _data_file in data_files:\n            pcount = self._handle[\"x\"].size\n            yield (\n                \"dark_matter\",\n                (\n                    self._handle[\"x\"][:pcount],\n                    self._handle[\"y\"][:pcount],\n                    self._handle[\"z\"][:pcount],\n                ),\n                0.0,\n            )\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        chunks = list(chunks)\n        data_files = set()\n        assert len(ptf) == 1\n        assert ptf.keys()[0] == \"dark_matter\"\n        for chunk in chunks:\n            for obj in chunk.objs:\n                data_files.update(obj.data_files)\n        assert len(data_files) == 1\n        for _data_file in data_files:\n            pcount = self._handle[\"x\"].size\n            for ptype, field_list in sorted(ptf.items()):\n                x = self._handle[\"x\"][:pcount]\n                y = self._handle[\"y\"][:pcount]\n                z = self._handle[\"z\"][:pcount]\n                mask = selector.select_points(x, y, z, 0.0)\n                del x, y, z\n                if mask is None:\n                    continue\n                for field in field_list:\n                    if field == \"mass\":\n                        if self.ds.field_info._mass_field is None:\n                            pm = 1.0\n                            if \"particle_mass\" in self.ds.parameters:\n                                pm = self.ds.parameters[\"particle_mass\"]\n                            else:\n                                raise RuntimeError\n                            data = pm * np.ones(mask.sum(), dtype=\"float64\")\n                        else:\n                            data = self._handle[self.ds.field_info._mass_field][:][mask]\n                    else:\n                        data = self._handle[field][:][mask]\n                    yield (ptype, field), data\n\n    def _count_particles(self, data_file):\n        return {\"dark_matter\": self._handle[\"x\"].http_array.shape}\n\n\nclass IOHandlerSIndexSDF(IOHandlerSDF):\n    _dataset_type = \"midx_sdf_particles\"\n\n    def _read_particle_coords(self, chunks, ptf):\n        dle = self.ds.domain_left_edge.in_units(\"code_length\").d\n        dre = self.ds.domain_right_edge.in_units(\"code_length\").d\n        for dd in self.ds.midx.iter_bbox_data(dle, dre, [\"x\", \"y\", \"z\"]):\n            yield \"dark_matter\", (dd[\"x\"], dd[\"y\"], dd[\"z\"]), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        dle = self.ds.domain_left_edge.in_units(\"code_length\").d\n        dre = self.ds.domain_right_edge.in_units(\"code_length\").d\n        required_fields = []\n        for field_list in sorted(ptf.values()):\n            for field in field_list:\n                if field == \"mass\":\n                    continue\n                required_fields.append(field)\n\n        for dd in self.ds.midx.iter_bbox_data(dle, dre, required_fields):\n            for ptype, field_list in sorted(ptf.items()):\n                x = dd[\"x\"]\n                y = dd[\"y\"]\n                z = dd[\"z\"]\n                mask = selector.select_points(x, y, z, 0.0)\n                del x, y, z\n                if mask is None:\n                    continue\n                for field in field_list:\n                    if field == \"mass\":\n                        data = np.ones(mask.sum(), dtype=\"float64\")\n                        data *= self.ds.parameters[\"particle_mass\"]\n                    else:\n                        data = dd[field][mask]\n                    yield (ptype, field), data\n\n    def _count_particles(self, data_file):\n        dle = self.ds.domain_left_edge.in_units(\"code_length\").d\n        dre = self.ds.domain_right_edge.in_units(\"code_length\").d\n        pcount_estimate = self.ds.midx.get_nparticles_bbox(dle, dre)\n        if pcount_estimate > 1e9:\n            mylog.warning(\n                \"Filtering %i particles to find total. \"\n                \"You may want to reconsider your bounding box.\",\n                pcount_estimate,\n            )\n        pcount = 0\n        for dd in self.ds.midx.iter_bbox_data(dle, dre, [\"x\"]):\n            pcount += dd[\"x\"].size\n        return {\"dark_matter\": pcount}\n\n    def _identify_fields(self, data_file):\n        fields = [(\"dark_matter\", v) for v in self._handle.keys()]\n        fields.append((\"dark_matter\", \"mass\"))\n        return fields, {}\n\n\nclass IOHandlerSIndexHTTPSDF(IOHandlerSIndexSDF):\n    _dataset_type = \"midx_http_sdf_particles\"\n"
  },
  {
    "path": "yt/frontends/sdf/misc.py",
    "content": ""
  },
  {
    "path": "yt/frontends/sph/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/sph/api.py",
    "content": "\"\"\"\nAPI for general SPH frontend machinery\n\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/sph/data_structures.py",
    "content": "import os\n\nimport numpy as np\n\nfrom yt.data_objects.static_output import ParticleDataset\nfrom yt.funcs import mylog\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\n\n\nclass SPHDataset(ParticleDataset):\n    default_kernel_name = \"cubic\"\n    _sph_smoothing_styles = [\"scatter\", \"gather\"]\n    _sph_smoothing_style = \"scatter\"\n    _num_neighbors = 32\n    _use_sph_normalization = True\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        index_order=None,\n        index_filename=None,\n        kdtree_filename=None,\n        kernel_name=None,\n        default_species_fields=None,\n    ):\n        if kernel_name is None:\n            self.kernel_name = self.default_kernel_name\n        else:\n            self.kernel_name = kernel_name\n        self.kdtree_filename = kdtree_filename\n        super().__init__(\n            filename,\n            dataset_type=dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            index_order=index_order,\n            index_filename=index_filename,\n            default_species_fields=default_species_fields,\n        )\n\n    @property\n    def num_neighbors(self):\n        return self._num_neighbors\n\n    @num_neighbors.setter\n    def num_neighbors(self, value):\n        if value < 0:\n            raise ValueError(f\"Negative value not allowed: {value}\")\n        self._num_neighbors = value\n\n    @property\n    def sph_smoothing_style(self):\n        return self._sph_smoothing_style\n\n    @sph_smoothing_style.setter\n    def sph_smoothing_style(self, value):\n        if value not in self._sph_smoothing_styles:\n            raise ValueError(\n                f\"Smoothing style not implemented: {value}, \"\n                \"please select one of the following: \",\n                self._sph_smoothing_styles,\n            )\n\n        self._sph_smoothing_style = value\n\n    @property\n    def use_sph_normalization(self):\n        return self._use_sph_normalization\n\n    @use_sph_normalization.setter\n    def use_sph_normalization(self, value):\n        if value is not True and value is not False:\n            raise ValueError(\"SPH normalization needs to be True or False!\")\n        self._use_sph_normalization = value\n\n\nclass SPHParticleIndex(ParticleIndex):\n    def _initialize_index(self):\n        ds = self.dataset\n\n        ds._file_hash = self._generate_hash()\n\n        if hasattr(self.io, \"_generate_smoothing_length\"):\n            self.io._generate_smoothing_length(self)\n\n        super()._initialize_index()\n\n    def _generate_kdtree(self, fname):\n        from yt.utilities.lib.cykdtree import PyKDTree\n\n        if fname is not None:\n            if os.path.exists(fname):\n                mylog.info(\"Loading KDTree from %s\", os.path.basename(fname))\n                kdtree = PyKDTree.from_file(fname)\n                if kdtree.data_version != self.ds._file_hash:\n                    mylog.info(\"Detected hash mismatch, regenerating KDTree\")\n                else:\n                    self._kdtree = kdtree\n                    return\n        positions = []\n        for data_file in self.data_files:\n            for _, ppos in self.io._yield_coordinates(\n                data_file, needed_ptype=self.ds._sph_ptypes[0]\n            ):\n                positions.append(ppos)\n        if positions == []:\n            self._kdtree = None\n            return\n        positions = np.concatenate(positions)\n        mylog.info(\"Allocating KDTree for %s particles\", positions.shape[0])\n        num_neighbors = getattr(self.ds, \"num_neighbors\", 32)\n        self._kdtree = PyKDTree(\n            positions.astype(\"float64\"),\n            left_edge=self.ds.domain_left_edge,\n            right_edge=self.ds.domain_right_edge,\n            periodic=np.array(self.ds.periodicity),\n            leafsize=2 * int(num_neighbors),\n            data_version=self.ds._file_hash,\n        )\n        if fname is not None:\n            self._kdtree.save(fname)\n\n    @property\n    def kdtree(self):\n        if hasattr(self, \"_kdtree\"):\n            return self._kdtree\n\n        ds = self.ds\n\n        if getattr(ds, \"kdtree_filename\", None) is None:\n            if os.path.exists(ds.parameter_filename):\n                fname = ds.parameter_filename + \".kdtree\"\n            else:\n                # we don't want to write to disk for in-memory data\n                fname = None\n        else:\n            fname = ds.kdtree_filename\n\n        self._generate_kdtree(fname)\n\n        return self._kdtree\n"
  },
  {
    "path": "yt/frontends/sph/fields.py",
    "content": "from yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.fields.species_fields import setup_species_fields\n\n\nclass SPHFieldInfo(FieldInfoContainer):\n    known_particle_fields: KnownFieldsT = (\n        (\"Mass\", (\"code_mass\", [\"particle_mass\"], None)),\n        (\"Masses\", (\"code_mass\", [\"particle_mass\"], None)),\n        (\"Coordinates\", (\"code_length\", [\"particle_position\"], None)),\n        (\"Velocity\", (\"code_velocity\", [\"particle_velocity\"], None)),\n        (\"Velocities\", (\"code_velocity\", [\"particle_velocity\"], None)),\n        (\"ParticleIDs\", (\"\", [\"particle_index\"], None)),\n        (\"InternalEnergy\", (\"code_specific_energy\", [\"specific_thermal_energy\"], None)),\n        (\"SmoothingLength\", (\"code_length\", [\"smoothing_length\"], None)),\n        (\"Density\", (\"code_mass / code_length**3\", [\"density\"], None)),\n        (\"MaximumTemperature\", (\"K\", [], None)),\n        (\"Temperature\", (\"K\", [\"temperature\"], None)),\n        (\"Epsilon\", (\"code_length\", [], None)),\n        (\"Metals\", (\"code_metallicity\", [\"metallicity\"], None)),\n        (\"Metallicity\", (\"code_metallicity\", [\"metallicity\"], None)),\n        (\"Phi\", (\"code_length\", [], None)),\n        (\"Potential\", (\"code_velocity**2\", [\"gravitational_potential\"], None)),\n        (\"StarFormationRate\", (\"Msun / yr\", [\"star_formation_rate\"], None)),\n        (\"FormationTime\", (\"code_time\", [\"creation_time\"], None)),\n        (\"Metallicity_00\", (\"\", [\"metallicity\"], None)),\n        (\"InitialMass\", (\"code_mass\", [], None)),\n        (\"TrueMass\", (\"code_mass\", [], None)),\n        (\"ElevenMetalMasses\", (\"code_mass\", [], None)),\n        (\"ColdFraction\", (\"\", [\"cold_fraction\"], None)),\n        (\"HotTemperature\", (\"code_temperature\", [\"hot_temperature\"], None)),\n        (\"CloudFraction\", (\"\", [\"cold_fraction\"], None)),\n        (\"HotPhaseTemperature\", (\"code_temperature\", [\"hot_temperature\"], None)),\n    )\n\n    def setup_particle_fields(self, ptype, *args, **kwargs):\n        super().setup_particle_fields(ptype, *args, **kwargs)\n        setup_species_fields(self, ptype)\n\n    def setup_fluid_index_fields(self):\n        pass\n"
  },
  {
    "path": "yt/frontends/sph/io.py",
    "content": "\"\"\"\nGeneric file-handing functions for SPH data\n\n\n\n\n\"\"\"\n\nfrom yt.utilities.io_handler import BaseParticleIOHandler\n\n\nclass IOHandlerSPH(BaseParticleIOHandler):\n    \"\"\"IOHandler implementation specifically for SPH data\n\n    This exists to handle particles with smoothing lengths, which require us\n    to read in smoothing lengths along with the the particle coordinates to\n    determine particle extents.\n\n    At present this is non-functional.\n    \"\"\"\n\n    pass\n"
  },
  {
    "path": "yt/frontends/stream/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/stream/api.py",
    "content": "from . import sample_data, tests\nfrom .data_structures import (\n    StreamDataset,\n    StreamGrid,\n    StreamHandler,\n    StreamHierarchy,\n    hexahedral_connectivity,\n)\nfrom .fields import StreamFieldInfo\nfrom .io import IOHandlerStream\n"
  },
  {
    "path": "yt/frontends/stream/data_structures.py",
    "content": "import os\nimport time\nimport uuid\nimport weakref\nfrom collections import UserDict\nfrom functools import cached_property\nfrom itertools import chain, repeat\nfrom numbers import Number as numeric_type\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt._typing import AxisOrder, FieldKey\nfrom yt.data_objects.field_data import YTFieldData\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.index_subobjects.octree_subset import OctreeSubset\nfrom yt.data_objects.index_subobjects.stretched_grid import StretchedGrid\nfrom yt.data_objects.index_subobjects.unstructured_mesh import (\n    SemiStructuredMesh,\n    UnstructuredMesh,\n)\nfrom yt.data_objects.static_output import Dataset, ParticleFile\nfrom yt.data_objects.unions import MeshUnion, ParticleUnion\nfrom yt.frontends.sph.data_structures import SPHParticleIndex\nfrom yt.funcs import setdefaultattr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.geometry_handler import Index, YTDataChunk\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.geometry.oct_container import OctreeContainer\nfrom yt.geometry.oct_geometry_handler import OctreeIndex\nfrom yt.geometry.unstructured_mesh_handler import UnstructuredIndex\nfrom yt.units import YTQuantity\nfrom yt.utilities.io_handler import io_registry\nfrom yt.utilities.lib.cykdtree import PyKDTree\nfrom yt.utilities.lib.misc_utilities import (\n    _obtain_coords_and_widths,\n    get_box_grids_level,\n)\nfrom yt.utilities.lib.particle_kdtree_tools import (\n    estimate_density,\n    generate_smoothing_length,\n)\nfrom yt.utilities.logger import ytLogger as mylog\n\nfrom .definitions import process_data, set_particle_types\nfrom .fields import StreamFieldInfo\n\n\nclass StreamGrid(AMRGridPatch):\n    \"\"\"\n    Class representing a single In-memory Grid instance.\n    \"\"\"\n\n    __slots__ = [\"proc_num\"]\n    _id_offset = 0\n\n    def __init__(self, id, index):\n        \"\"\"\n        Returns an instance of StreamGrid with *id*, associated with *filename*\n        and *index*.\n        \"\"\"\n        # All of the field parameters will be passed to us as needed.\n        AMRGridPatch.__init__(self, id, filename=None, index=index)\n        self._children_ids = []\n        self._parent_id = -1\n        self.Level = -1\n\n    def set_filename(self, filename):\n        pass\n\n    @property\n    def Parent(self):\n        if self._parent_id == -1:\n            return None\n        return self.index.grids[self._parent_id - self._id_offset]\n\n    @property\n    def Children(self):\n        return [self.index.grids[cid - self._id_offset] for cid in self._children_ids]\n\n\nclass StreamStretchedGrid(StretchedGrid):\n    _id_offset = 0\n\n    def __init__(self, id, index):\n        cell_widths = index.grid_cell_widths[id - self._id_offset]\n        super().__init__(id, cell_widths, index=index)\n        self._children_ids = []\n        self._parent_id = -1\n        self.Level = -1\n\n    @property\n    def Parent(self):\n        if self._parent_id == -1:\n            return None\n        return self.index.grids[self._parent_id - self._id_offset]\n\n    @property\n    def Children(self):\n        return [self.index.grids[cid - self._id_offset] for cid in self._children_ids]\n\n\nclass StreamHandler:\n    def __init__(\n        self,\n        left_edges,\n        right_edges,\n        dimensions,\n        levels,\n        parent_ids,\n        particle_count,\n        processor_ids,\n        fields,\n        field_units,\n        code_units,\n        io=None,\n        particle_types=None,\n        periodicity=(True, True, True),\n        *,\n        cell_widths=None,\n        parameters=None,\n    ):\n        if particle_types is None:\n            particle_types = {}\n        self.left_edges = np.array(left_edges)\n        self.right_edges = np.array(right_edges)\n        self.dimensions = dimensions\n        self.levels = levels\n        self.parent_ids = parent_ids\n        self.particle_count = particle_count\n        self.processor_ids = processor_ids\n        self.num_grids = self.levels.size\n        self.fields = fields\n        self.field_units = field_units\n        self.code_units = code_units\n        self.io = io\n        self.particle_types = particle_types\n        self.periodicity = periodicity\n        self.cell_widths = cell_widths\n\n        if parameters is None:\n            self.parameters = {}\n        else:\n            self.parameters = parameters.copy()\n\n    def get_fields(self):\n        return self.fields.all_fields\n\n    def get_particle_type(self, field):\n        if field in self.particle_types:\n            return self.particle_types[field]\n        else:\n            return False\n\n\nclass StreamHierarchy(GridIndex):\n    grid = StreamGrid\n\n    def __init__(self, ds, dataset_type=None):\n        self.dataset_type = dataset_type\n        self.float_type = \"float64\"\n        self.dataset = weakref.proxy(ds)  # for _obtain_enzo\n        self.stream_handler = ds.stream_handler\n        self.float_type = \"float64\"\n        self.directory = os.getcwd()\n        GridIndex.__init__(self, ds, dataset_type)\n\n    def _count_grids(self):\n        self.num_grids = self.stream_handler.num_grids\n\n    def _icoords_to_fcoords(self, icoords, ires, axes=None):\n        \"\"\"\n        We check here that we have cell_widths, and if we do, we will provide them.\n        \"\"\"\n        if self.grid_cell_widths is None:\n            return super()._icoords_to_fcoords(icoords, ires, axes)\n        if axes is None:\n            axes = [0, 1, 2]\n        # Transpose these by reversing the shape\n        coords = np.empty(icoords.shape, dtype=\"f8\")\n        cell_widths = np.empty(icoords.shape, dtype=\"f8\")\n        for i, ax in enumerate(axes):\n            coords[:, i], cell_widths[:, i] = _obtain_coords_and_widths(\n                icoords[:, i],\n                ires,\n                self.grid_cell_widths[0][ax],\n                self.ds.domain_left_edge[ax].d,\n            )\n        return coords, cell_widths\n\n    def _parse_index(self):\n        self.grid_dimensions = self.stream_handler.dimensions\n        self.grid_left_edge[:] = self.stream_handler.left_edges\n        self.grid_right_edge[:] = self.stream_handler.right_edges\n        self.grid_levels[:] = self.stream_handler.levels\n        self.min_level = self.grid_levels.min()\n        self.grid_procs = self.stream_handler.processor_ids\n        self.grid_particle_count[:] = self.stream_handler.particle_count\n        if self.stream_handler.cell_widths is not None:\n            self.grid_cell_widths = self.stream_handler.cell_widths[:]\n            self.grid = StreamStretchedGrid\n        else:\n            self.grid_cell_widths = None\n        mylog.debug(\"Copying reverse tree\")\n        self.grids = []\n        # We enumerate, so it's 0-indexed id and 1-indexed pid\n        for id in range(self.num_grids):\n            self.grids.append(self.grid(id, self))\n            self.grids[id].Level = self.grid_levels[id, 0]\n        parent_ids = self.stream_handler.parent_ids\n        if parent_ids is not None:\n            reverse_tree = self.stream_handler.parent_ids.tolist()\n            # Initial setup:\n            for gid, pid in enumerate(reverse_tree):\n                if pid >= 0:\n                    self.grids[gid]._parent_id = pid\n                    self.grids[pid]._children_ids.append(self.grids[gid].id)\n        else:\n            mylog.debug(\"Reconstructing parent-child relationships\")\n            self._reconstruct_parent_child()\n        self.max_level = self.grid_levels.max()\n        mylog.debug(\"Preparing grids\")\n        temp_grids = np.empty(self.num_grids, dtype=\"object\")\n        for i, grid in enumerate(self.grids):\n            if (i % 1e4) == 0:\n                mylog.debug(\"Prepared % 7i / % 7i grids\", i, self.num_grids)\n            grid.filename = None\n            grid._prepare_grid()\n            grid._setup_dx()\n            grid.proc_num = self.grid_procs[i]\n            temp_grids[i] = grid\n        self.grids = temp_grids\n        mylog.debug(\"Prepared\")\n\n    def _reconstruct_parent_child(self):\n        mask = np.empty(len(self.grids), dtype=\"int32\")\n        mylog.debug(\"First pass; identifying child grids\")\n        for i, grid in enumerate(self.grids):\n            get_box_grids_level(\n                self.grid_left_edge[i, :],\n                self.grid_right_edge[i, :],\n                self.grid_levels[i].item() + 1,\n                self.grid_left_edge,\n                self.grid_right_edge,\n                self.grid_levels,\n                mask,\n            )\n            ids = np.where(mask.astype(\"bool\"))\n            grid._children_ids = ids[0]  # where is a tuple\n        mylog.debug(\"Second pass; identifying parents\")\n        self.stream_handler.parent_ids = (\n            np.zeros(self.stream_handler.num_grids, \"int64\") - 1\n        )\n        for i, grid in enumerate(self.grids):  # Second pass\n            for child in grid.Children:\n                child._parent_id = i\n                # _id_offset = 0\n                self.stream_handler.parent_ids[child.id] = i\n\n    def _initialize_grid_arrays(self):\n        GridIndex._initialize_grid_arrays(self)\n        self.grid_procs = np.zeros((self.num_grids, 1), \"int32\")\n\n    def _detect_output_fields(self):\n        # NOTE: Because particle unions add to the actual field list, without\n        # having the keys in the field list itself, we need to double check\n        # here.\n        fl = set(self.stream_handler.get_fields())\n        fl.update(set(getattr(self, \"field_list\", [])))\n        self.field_list = list(fl)\n\n    def _populate_grid_objects(self):\n        for g in self.grids:\n            g._setup_dx()\n        self.max_level = self.grid_levels.max()\n\n    def _setup_data_io(self):\n        if self.stream_handler.io is not None:\n            self.io = self.stream_handler.io\n        else:\n            self.io = io_registry[self.dataset_type](self.ds)\n\n    def _reset_particle_count(self):\n        self.grid_particle_count[:] = self.stream_handler.particle_count\n        for i, grid in enumerate(self.grids):\n            grid.NumberOfParticles = self.grid_particle_count[i, 0]\n\n    def update_data(self, data):\n        \"\"\"\n        Update the stream data with a new data dict. If fields already exist,\n        they will be replaced, but if they do not, they will be added. Fields\n        already in the stream but not part of the data dict will be left\n        alone.\n        \"\"\"\n        particle_types = set_particle_types(data[0])\n\n        self.stream_handler.particle_types.update(particle_types)\n        self.ds._find_particle_types()\n\n        for i, grid in enumerate(self.grids):\n            field_units, gdata, number_of_particles = process_data(data[i])\n            self.stream_handler.particle_count[i] = number_of_particles\n            self.stream_handler.field_units.update(field_units)\n            for field in gdata:\n                if field in grid.field_data:\n                    grid.field_data.pop(field, None)\n                self.stream_handler.fields[grid.id][field] = gdata[field]\n\n        self._reset_particle_count()\n        # We only want to create a superset of fields here.\n        for field in self.ds.field_list:\n            if field[0] == \"all\":\n                self.ds.field_list.remove(field)\n        self._detect_output_fields()\n        self.ds.create_field_info()\n        mylog.debug(\"Creating Particle Union 'all'\")\n        pu = ParticleUnion(\"all\", list(self.ds.particle_types_raw))\n        self.ds.add_particle_union(pu)\n        self.ds.particle_types = tuple(set(self.ds.particle_types))\n\n\nclass StreamDataset(Dataset):\n    _index_class: type[Index] = StreamHierarchy\n    _field_info_class = StreamFieldInfo\n    _dataset_type = \"stream\"\n\n    def __init__(\n        self,\n        stream_handler,\n        storage_filename=None,\n        geometry=\"cartesian\",\n        unit_system=\"cgs\",\n        default_species_fields=None,\n        *,\n        axis_order: AxisOrder | None = None,\n    ):\n        self.fluid_types += (\"stream\",)\n        self.geometry = Geometry(geometry)\n        self.stream_handler = stream_handler\n        self._find_particle_types()\n        name = f\"InMemoryParameterFile_{uuid.uuid4().hex}\"\n        from yt.data_objects.static_output import _cached_datasets\n\n        if geometry == \"spectral_cube\":\n            # mimic FITSDataset specific interface to allow testing with\n            # fake, in memory data\n            setdefaultattr(self, \"lon_axis\", 0)\n            setdefaultattr(self, \"lat_axis\", 1)\n            setdefaultattr(self, \"spec_axis\", 2)\n            setdefaultattr(self, \"lon_name\", \"X\")\n            setdefaultattr(self, \"lat_name\", \"Y\")\n            setdefaultattr(self, \"spec_name\", \"z\")\n            setdefaultattr(self, \"spec_unit\", \"\")\n            setdefaultattr(\n                self,\n                \"pixel2spec\",\n                lambda pixel_value: self.arr(pixel_value, self.spec_unit),  # type: ignore [attr-defined]\n            )\n            setdefaultattr(\n                self,\n                \"spec2pixel\",\n                lambda spec_value: self.arr(spec_value, \"code_length\"),\n            )\n\n        _cached_datasets[name] = self\n        Dataset.__init__(\n            self,\n            name,\n            self._dataset_type,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n            axis_order=axis_order,\n        )\n\n    @property\n    def filename(self):\n        return self.stream_handler.name\n\n    @cached_property\n    def unique_identifier(self) -> str:\n        return str(self.parameters[\"CurrentTimeIdentifier\"])\n\n    def _parse_parameter_file(self):\n        self.parameters[\"CurrentTimeIdentifier\"] = time.time()\n        self.domain_left_edge = self.stream_handler.domain_left_edge.copy()\n        self.domain_right_edge = self.stream_handler.domain_right_edge.copy()\n        self.refine_by = self.stream_handler.refine_by\n        self.dimensionality = self.stream_handler.dimensionality\n        self._periodicity = self.stream_handler.periodicity\n        self.domain_dimensions = self.stream_handler.domain_dimensions\n        self.current_time = self.stream_handler.simulation_time\n        self.gamma = 5.0 / 3.0\n        self.parameters[\"EOSType\"] = -1\n        self.parameters[\"CosmologyHubbleConstantNow\"] = 1.0\n        self.parameters[\"CosmologyCurrentRedshift\"] = 1.0\n        self.parameters[\"HydroMethod\"] = -1\n        self.parameters.update(self.stream_handler.parameters)\n        if self.stream_handler.cosmology_simulation:\n            self.cosmological_simulation = 1\n            self.current_redshift = self.stream_handler.current_redshift\n            self.omega_lambda = self.stream_handler.omega_lambda\n            self.omega_matter = self.stream_handler.omega_matter\n            self.hubble_constant = self.stream_handler.hubble_constant\n        else:\n            self.current_redshift = 0.0\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0\n            self.hubble_constant = 0.0\n            self.cosmological_simulation = 0\n\n    def _set_units(self):\n        self.field_units = self.stream_handler.field_units\n\n    def _set_code_unit_attributes(self):\n        base_units = self.stream_handler.code_units\n        attrs = (\n            \"length_unit\",\n            \"mass_unit\",\n            \"time_unit\",\n            \"velocity_unit\",\n            \"magnetic_unit\",\n        )\n        cgs_units = (\"cm\", \"g\", \"s\", \"cm/s\", \"gauss\")\n        for unit, attr, cgs_unit in zip(base_units, attrs, cgs_units, strict=True):\n            if isinstance(unit, str):\n                if unit == \"code_magnetic\":\n                    # If no magnetic unit was explicitly specified\n                    # we skip it now and take care of it at the bottom\n                    continue\n                else:\n                    uq = self.quan(1.0, unit)\n            elif isinstance(unit, numeric_type):\n                uq = self.quan(unit, cgs_unit)\n            elif isinstance(unit, YTQuantity):\n                uq = unit\n            elif isinstance(unit, tuple):\n                uq = self.quan(unit[0], unit[1])\n            else:\n                raise RuntimeError(f\"{attr} ({unit}) is invalid.\")\n            setattr(self, attr, uq)\n        if not hasattr(self, \"magnetic_unit\"):\n            self.magnetic_unit = np.sqrt(\n                4 * np.pi * self.mass_unit / (self.time_unit**2 * self.length_unit)\n            )\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        return False\n\n    @property\n    def _skip_cache(self):\n        return True\n\n    def _find_particle_types(self):\n        particle_types = set()\n        for k, v in self.stream_handler.particle_types.items():\n            if v:\n                particle_types.add(k[0])\n        self.particle_types = tuple(particle_types)\n        self.particle_types_raw = self.particle_types\n\n\nclass StreamDictFieldHandler(UserDict):\n    _additional_fields: tuple[FieldKey, ...] = ()\n\n    @property\n    def all_fields(self):\n        self_fields = chain.from_iterable(s.keys() for s in self.values())\n        self_fields = list(set(self_fields))\n        fields = list(self._additional_fields) + self_fields\n        fields = list(set(fields))\n        return fields\n\n\nclass StreamParticleIndex(SPHParticleIndex):\n    def __init__(self, ds, dataset_type=None):\n        self.stream_handler = ds.stream_handler\n        super().__init__(ds, dataset_type)\n\n    def _setup_data_io(self):\n        if self.stream_handler.io is not None:\n            self.io = self.stream_handler.io\n        else:\n            self.io = io_registry[self.dataset_type](self.ds)\n\n    def update_data(self, data):\n        \"\"\"\n        Update the stream data with a new data dict. If fields already exist,\n        they will be replaced, but if they do not, they will be added. Fields\n        already in the stream but not part of the data dict will be left\n        alone.\n        \"\"\"\n        # Alias\n        ds = self.ds\n        handler = ds.stream_handler\n\n        # Preprocess\n        field_units, data, _ = process_data(data)\n        pdata = {}\n        for key in data.keys():\n            if not isinstance(key, tuple):\n                field = (\"io\", key)\n                mylog.debug(\"Reassigning '%s' to '%s'\", key, field)\n            else:\n                field = key\n            pdata[field] = data[key]\n        data = pdata  # Drop reference count\n        particle_types = set_particle_types(data)\n\n        # Update particle types\n        handler.particle_types.update(particle_types)\n        ds._find_particle_types()\n\n        # Update fields\n        handler.field_units.update(field_units)\n        fields = handler.fields\n        for field in data.keys():\n            if field not in fields._additional_fields:\n                fields._additional_fields += (field,)\n        fields[\"stream_file\"].update(data)\n\n        # Update field list\n        for field in self.ds.field_list:\n            if field[0] in [\"all\", \"nbody\"]:\n                self.ds.field_list.remove(field)\n        self._detect_output_fields()\n        self.ds.create_field_info()\n\n\nclass StreamParticleFile(ParticleFile):\n    pass\n\n\nclass StreamParticlesDataset(StreamDataset):\n    _index_class = StreamParticleIndex\n    _file_class = StreamParticleFile\n    _field_info_class = StreamFieldInfo\n    _dataset_type = \"stream_particles\"\n    file_count = 1\n    filename_template = \"stream_file\"\n    _proj_type = \"particle_proj\"\n\n    def __init__(\n        self,\n        stream_handler,\n        storage_filename=None,\n        geometry=\"cartesian\",\n        unit_system=\"cgs\",\n        default_species_fields=None,\n        *,\n        axis_order: AxisOrder | None = None,\n    ):\n        super().__init__(\n            stream_handler,\n            storage_filename=storage_filename,\n            geometry=geometry,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n            axis_order=axis_order,\n        )\n        fields = list(stream_handler.fields[\"stream_file\"].keys())\n        sph_ptypes = []\n        for ptype in self.particle_types:\n            if (ptype, \"density\") in fields and (ptype, \"smoothing_length\") in fields:\n                sph_ptypes.append(ptype)\n        if len(sph_ptypes) == 1:\n            self._sph_ptypes = tuple(sph_ptypes)\n        elif len(sph_ptypes) > 1:\n            raise ValueError(\"Multiple SPH particle types are currently not supported!\")\n\n    def add_sph_fields(self, n_neighbors=32, kernel=\"cubic\", sph_ptype=\"io\"):\n        \"\"\"Add SPH fields for the specified particle type.\n\n        For a particle type with \"particle_position\" and \"particle_mass\" already\n        defined, this method adds the \"smoothing_length\" and \"density\" fields.\n        \"smoothing_length\" is computed as the distance to the nth nearest\n        neighbor. \"density\" is computed as the SPH (gather) smoothed mass. The\n        SPH fields are added only if they don't already exist.\n\n        Parameters\n        ----------\n        n_neighbors : int\n            The number of neighbors to use in smoothing length computation.\n        kernel : str\n            The kernel function to use in density estimation.\n        sph_ptype : str\n            The SPH particle type. Each dataset has one sph_ptype only. This\n            method will overwrite existing sph_ptype of the dataset.\n\n        \"\"\"\n        mylog.info(\"Generating SPH fields\")\n\n        # Unify units\n        l_unit = \"code_length\"\n        m_unit = \"code_mass\"\n        d_unit = \"code_mass / code_length**3\"\n\n        # Read basic fields\n        ad = self.all_data()\n        pos = ad[sph_ptype, \"particle_position\"].to(l_unit).d\n        mass = ad[sph_ptype, \"particle_mass\"].to(m_unit).d\n\n        # Construct k-d tree\n        kdtree = PyKDTree(\n            pos.astype(\"float64\"),\n            left_edge=self.domain_left_edge.to_value(l_unit),\n            right_edge=self.domain_right_edge.to_value(l_unit),\n            periodic=self.periodicity,\n            leafsize=2 * int(n_neighbors),\n        )\n        order = np.argsort(kdtree.idx)\n\n        def exists(fname):\n            if (sph_ptype, fname) in self.derived_field_list:\n                mylog.info(\n                    \"Field ('%s','%s') already exists. Skipping\", sph_ptype, fname\n                )\n                return True\n            else:\n                mylog.info(\"Generating field ('%s','%s')\", sph_ptype, fname)\n                return False\n\n        data = {}\n\n        # Add smoothing length field\n        fname = \"smoothing_length\"\n        if not exists(fname):\n            hsml = generate_smoothing_length(pos[kdtree.idx], kdtree, n_neighbors)\n            hsml = hsml[order]\n            data[sph_ptype, \"smoothing_length\"] = (hsml, l_unit)\n        else:\n            hsml = ad[sph_ptype, fname].to(l_unit).d\n\n        # Add density field\n        fname = \"density\"\n        if not exists(fname):\n            dens = estimate_density(\n                pos[kdtree.idx],\n                mass[kdtree.idx],\n                hsml[kdtree.idx],\n                kdtree,\n                kernel_name=kernel,\n            )\n            dens = dens[order]\n            data[sph_ptype, \"density\"] = (dens, d_unit)\n\n        # Add fields\n        self._sph_ptypes = (sph_ptype,)\n        self.index.update_data(data)\n        self.num_neighbors = n_neighbors\n\n\n_cis = np.array(\n    [\n        [0, 0, 0],\n        [0, 0, 1],\n        [0, 1, 0],\n        [0, 1, 1],\n        [1, 0, 0],\n        [1, 0, 1],\n        [1, 1, 0],\n        [1, 1, 1],\n    ],\n    dtype=\"int64\",\n)\n\n\ndef hexahedral_connectivity(xgrid, ygrid, zgrid):\n    r\"\"\"Define the cell coordinates and cell neighbors of a hexahedral mesh\n    for a semistructured grid. Used to specify the connectivity and\n    coordinates parameters used in\n    :func:`~yt.frontends.stream.data_structures.load_hexahedral_mesh`.\n\n    Parameters\n    ----------\n    xgrid : array_like\n       x-coordinates of boundaries of the hexahedral cells. Should be a\n       one-dimensional array.\n    ygrid : array_like\n       y-coordinates of boundaries of the hexahedral cells. Should be a\n       one-dimensional array.\n    zgrid : array_like\n       z-coordinates of boundaries of the hexahedral cells. Should be a\n       one-dimensional array.\n\n    Returns\n    -------\n    coords : array_like\n        The list of (x,y,z) coordinates of the vertices of the mesh.\n        Is of size (M,3) where M is the number of vertices.\n    connectivity : array_like\n        For each hexahedron h in the mesh, gives the index of each of h's\n        neighbors. Is of size (N,8), where N is the number of hexahedra.\n\n    Examples\n    --------\n\n    >>> xgrid = np.array([-1, -0.25, 0, 0.25, 1])\n    >>> coords, conn = hexahedral_connectivity(xgrid, xgrid, xgrid)\n    >>> coords\n    array([[-1.  , -1.  , -1.  ],\n           [-1.  , -1.  , -0.25],\n           [-1.  , -1.  ,  0.  ],\n           ...,\n           [ 1.  ,  1.  ,  0.  ],\n           [ 1.  ,  1.  ,  0.25],\n           [ 1.  ,  1.  ,  1.  ]])\n\n    >>> conn\n    array([[  0,   1,   5,   6,  25,  26,  30,  31],\n           [  1,   2,   6,   7,  26,  27,  31,  32],\n           [  2,   3,   7,   8,  27,  28,  32,  33],\n           ...,\n           [ 91,  92,  96,  97, 116, 117, 121, 122],\n           [ 92,  93,  97,  98, 117, 118, 122, 123],\n           [ 93,  94,  98,  99, 118, 119, 123, 124]])\n    \"\"\"\n    nx = len(xgrid)\n    ny = len(ygrid)\n    nz = len(zgrid)\n    coords = np.zeros((nx, ny, nz, 3), dtype=\"float64\", order=\"C\")\n    coords[:, :, :, 0] = xgrid[:, None, None]\n    coords[:, :, :, 1] = ygrid[None, :, None]\n    coords[:, :, :, 2] = zgrid[None, None, :]\n    coords = coords.reshape(nx * ny * nz, 3)\n    cycle = np.rollaxis(np.indices((nx - 1, ny - 1, nz - 1)), 0, 4).reshape(\n        (nx - 1) * (ny - 1) * (nz - 1), 3\n    )\n    off = _cis + cycle[:, np.newaxis]\n    connectivity = np.array(\n        ((off[:, :, 0] * ny) + off[:, :, 1]) * nz + off[:, :, 2], order=\"C\"\n    )\n    return coords, connectivity\n\n\nclass StreamHexahedralMesh(SemiStructuredMesh):\n    _connectivity_length = 8\n    _index_offset = 0\n\n\nclass StreamHexahedralHierarchy(UnstructuredIndex):\n    def __init__(self, ds, dataset_type=None):\n        self.stream_handler = ds.stream_handler\n        super().__init__(ds, dataset_type)\n\n    def _initialize_mesh(self):\n        coords = self.stream_handler.fields.pop(\"coordinates\")\n        connect = self.stream_handler.fields.pop(\"connectivity\")\n        self.meshes = [\n            StreamHexahedralMesh(0, self.index_filename, connect, coords, self)\n        ]\n\n    def _setup_data_io(self):\n        if self.stream_handler.io is not None:\n            self.io = self.stream_handler.io\n        else:\n            self.io = io_registry[self.dataset_type](self.ds)\n\n    def _detect_output_fields(self):\n        self.field_list = list(set(self.stream_handler.get_fields()))\n\n\nclass StreamHexahedralDataset(StreamDataset):\n    _index_class = StreamHexahedralHierarchy\n    _field_info_class = StreamFieldInfo\n    _dataset_type = \"stream_hexahedral\"\n\n\nclass StreamOctreeSubset(OctreeSubset):\n    domain_id = 1\n    _domain_offset = 1\n\n    def __init__(self, base_region, ds, oct_handler, num_zones=2, num_ghost_zones=0):\n        self._num_zones = num_zones\n        self.field_data = YTFieldData()\n        self.field_parameters = {}\n        self.ds = ds\n        self._oct_handler = oct_handler\n        self._last_mask = None\n        self._last_selector_id = None\n        self._current_particle_type = \"io\"\n        self._current_fluid_type = self.ds.default_fluid_type\n        self.base_region = base_region\n        self.base_selector = base_region.selector\n\n        self._num_ghost_zones = num_ghost_zones\n\n        if num_ghost_zones > 0:\n            if not all(ds.periodicity):\n                mylog.warning(\n                    \"Ghost zones will wrongly assume the domain to be periodic.\"\n                )\n            base_grid = StreamOctreeSubset(base_region, ds, oct_handler, num_zones)\n            self._base_grid = base_grid\n\n    @property\n    def oct_handler(self):\n        return self._oct_handler\n\n    def retrieve_ghost_zones(self, ngz, fields, smoothed=False):\n        try:\n            new_subset = self._subset_with_gz\n            mylog.debug(\"Reusing previous subset with ghost zone.\")\n        except AttributeError:\n            new_subset = StreamOctreeSubset(\n                self.base_region,\n                self.ds,\n                self.oct_handler,\n                self._num_zones,\n                num_ghost_zones=ngz,\n            )\n            self._subset_with_gz = new_subset\n\n        return new_subset\n\n    def _fill_no_ghostzones(self, content, dest, selector, offset):\n        # Here we get a copy of the file, which we skip through and read the\n        # bits we want.\n        oct_handler = self.oct_handler\n        cell_count = selector.count_oct_cells(self.oct_handler, self.domain_id)\n        levels, cell_inds, file_inds = self.oct_handler.file_index_octs(\n            selector, self.domain_id, cell_count\n        )\n        levels[:] = 0\n        dest.update((field, np.empty(cell_count, dtype=\"float64\")) for field in content)\n        # Make references ...\n        count = oct_handler.fill_level(\n            0, levels, cell_inds, file_inds, dest, content, offset\n        )\n        return count\n\n    def _fill_with_ghostzones(self, content, dest, selector, offset):\n        oct_handler = self.oct_handler\n        ndim = self.ds.dimensionality\n        cell_count = (\n            selector.count_octs(self.oct_handler, self.domain_id) * self.nz**ndim\n        )\n\n        gz_cache = getattr(self, \"_ghost_zone_cache\", None)\n        if gz_cache:\n            levels, cell_inds, file_inds, domains = gz_cache\n        else:\n            gz_cache = (\n                levels,\n                cell_inds,\n                file_inds,\n                domains,\n            ) = oct_handler.file_index_octs_with_ghost_zones(\n                selector, self.domain_id, cell_count\n            )\n            self._ghost_zone_cache = gz_cache\n        levels[:] = 0\n        dest.update((field, np.empty(cell_count, dtype=\"float64\")) for field in content)\n        # Make references ...\n        oct_handler.fill_level(0, levels, cell_inds, file_inds, dest, content, offset)\n\n    def fill(self, content, dest, selector, offset):\n        if self._num_ghost_zones == 0:\n            return self._fill_no_ghostzones(content, dest, selector, offset)\n        else:\n            return self._fill_with_ghostzones(content, dest, selector, offset)\n\n\nclass StreamOctreeHandler(OctreeIndex):\n    def __init__(self, ds, dataset_type=None):\n        self.stream_handler = ds.stream_handler\n        self.dataset_type = dataset_type\n        super().__init__(ds, dataset_type)\n\n    def _setup_data_io(self):\n        if self.stream_handler.io is not None:\n            self.io = self.stream_handler.io\n        else:\n            self.io = io_registry[self.dataset_type](self.ds)\n\n    def _initialize_oct_handler(self):\n        header = {\n            \"dims\": self.ds.domain_dimensions // self.ds.num_zones,\n            \"left_edge\": self.ds.domain_left_edge,\n            \"right_edge\": self.ds.domain_right_edge,\n            \"octree\": self.ds.octree_mask,\n            \"num_zones\": self.ds.num_zones,\n            \"partial_coverage\": self.ds.partial_coverage,\n        }\n        self.oct_handler = OctreeContainer.load_octree(header)\n        # We do now need to get the maximum level set, as well.\n        self.ds.max_level = self.oct_handler.max_level\n\n    def _identify_base_chunk(self, dobj):\n        if getattr(dobj, \"_chunk_info\", None) is None:\n            base_region = getattr(dobj, \"base_region\", dobj)\n            subset = [\n                StreamOctreeSubset(\n                    base_region,\n                    self.dataset,\n                    self.oct_handler,\n                    self.ds.num_zones,\n                )\n            ]\n            dobj._chunk_info = subset\n        dobj._current_chunk = list(self._chunk_all(dobj))[0]\n\n    def _chunk_all(self, dobj):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        yield YTDataChunk(dobj, \"all\", oobjs, None)\n\n    def _chunk_spatial(self, dobj, ngz, sort=None, preload_fields=None):\n        sobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        # This is where we will perform cutting of the Octree and\n        # load-balancing.  That may require a specialized selector object to\n        # cut based on some space-filling curve index.\n        for og in sobjs:\n            if ngz > 0:\n                g = og.retrieve_ghost_zones(ngz, [], smoothed=True)\n            else:\n                g = og\n            yield YTDataChunk(dobj, \"spatial\", [g])\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for subset in oobjs:\n            yield YTDataChunk(dobj, \"io\", [subset], None, cache=cache)\n\n    def _setup_classes(self):\n        dd = self._get_data_reader_dict()\n        super()._setup_classes(dd)\n\n    def _detect_output_fields(self):\n        # NOTE: Because particle unions add to the actual field list, without\n        # having the keys in the field list itself, we need to double check\n        # here.\n        fl = set(self.stream_handler.get_fields())\n        fl.update(set(getattr(self, \"field_list\", [])))\n        self.field_list = list(fl)\n\n\nclass StreamOctreeDataset(StreamDataset):\n    _index_class = StreamOctreeHandler\n    _field_info_class = StreamFieldInfo\n    _dataset_type = \"stream_octree\"\n\n    levelmax = None\n\n    def __init__(\n        self,\n        stream_handler,\n        storage_filename=None,\n        geometry=\"cartesian\",\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        super().__init__(\n            stream_handler,\n            storage_filename,\n            geometry,\n            unit_system,\n            default_species_fields=default_species_fields,\n        )\n        # Set up levelmax\n        self.max_level = stream_handler.levels.max()\n        self.min_level = stream_handler.levels.min()\n\n\nclass StreamUnstructuredMesh(UnstructuredMesh):\n    _index_offset = 0\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._connectivity_length = self.connectivity_indices.shape[1]\n\n\nclass StreamUnstructuredIndex(UnstructuredIndex):\n    def __init__(self, ds, dataset_type=None):\n        self.stream_handler = ds.stream_handler\n        super().__init__(ds, dataset_type)\n\n    def _initialize_mesh(self):\n        coords = self.stream_handler.fields.pop(\"coordinates\")\n        connect = always_iterable(self.stream_handler.fields.pop(\"connectivity\"))\n\n        self.meshes = [\n            StreamUnstructuredMesh(i, self.index_filename, c1, c2, self)\n            for i, (c1, c2) in enumerate(zip(connect, repeat(coords)))\n        ]\n        self.mesh_union = MeshUnion(\"mesh_union\", self.meshes)\n\n    def _setup_data_io(self):\n        if self.stream_handler.io is not None:\n            self.io = self.stream_handler.io\n        else:\n            self.io = io_registry[self.dataset_type](self.ds)\n\n    def _detect_output_fields(self):\n        self.field_list = list(set(self.stream_handler.get_fields()))\n        fnames = list({fn for ft, fn in self.field_list})\n        self.field_list += [(\"all\", fname) for fname in fnames]\n\n\nclass StreamUnstructuredMeshDataset(StreamDataset):\n    _index_class = StreamUnstructuredIndex\n    _field_info_class = StreamFieldInfo\n    _dataset_type = \"stream_unstructured\"\n\n    def _find_particle_types(self):\n        pass\n"
  },
  {
    "path": "yt/frontends/stream/definitions.py",
    "content": "from collections import defaultdict\n\nimport numpy as np\n\nfrom yt.funcs import is_sequence\nfrom yt.geometry.grid_container import GridTree, MatchPointsToGrids\nfrom yt.utilities.exceptions import (\n    YTInconsistentGridFieldShape,\n    YTInconsistentGridFieldShapeGridDims,\n    YTInconsistentParticleFieldShape,\n)\nfrom yt.utilities.logger import ytLogger as mylog\n\nfrom .fields import StreamFieldInfo\n\n\ndef assign_particle_data(ds, pdata, bbox):\n    \"\"\"\n    Assign particle data to the grids using MatchPointsToGrids. This\n    will overwrite any existing particle data, so be careful!\n    \"\"\"\n\n    particle_index_fields = [\n        f\"particle_position_{ax}\" for ax in ds.coordinates.axis_order\n    ]\n    for ptype in ds.particle_types_raw:\n        check_fields = [(ptype, pi_field) for pi_field in particle_index_fields]\n        check_fields.append((ptype, \"particle_position\"))\n        if all(f not in pdata for f in check_fields):\n            pdata_ftype = {}\n            for f in sorted(pdata):\n                if not hasattr(pdata[f], \"shape\"):\n                    continue\n                if f == \"number_of_particles\":\n                    continue\n                mylog.debug(\"Reassigning '%s' to ('%s','%s')\", f, ptype, f)\n                pdata_ftype[ptype, f] = pdata.pop(f)\n            pdata_ftype.update(pdata)\n            pdata = pdata_ftype\n\n    # Note: what we need to do here is a bit tricky.  Because occasionally this\n    # gets called before we property handle the field detection, we cannot use\n    # any information about the index.  Fortunately for us, we can generate\n    # most of the GridTree utilizing information we already have from the\n    # stream handler.\n\n    if len(ds.stream_handler.fields) > 1:\n        pdata.pop(\"number_of_particles\", None)\n        num_grids = len(ds.stream_handler.fields)\n        parent_ids = ds.stream_handler.parent_ids\n        num_children = np.zeros(num_grids, dtype=\"int64\")\n        # We're going to do this the slow way\n        mask = np.empty(num_grids, dtype=\"bool\")\n        for i in range(num_grids):\n            np.equal(parent_ids, i, mask)\n            num_children[i] = mask.sum()\n        levels = ds.stream_handler.levels.astype(\"int64\").ravel()\n        grid_tree = GridTree(\n            num_grids,\n            ds.stream_handler.left_edges,\n            ds.stream_handler.right_edges,\n            ds.stream_handler.dimensions,\n            ds.stream_handler.parent_ids,\n            levels,\n            num_children,\n        )\n\n        grid_pdata = []\n        for _ in range(num_grids):\n            grid = {\"number_of_particles\": 0}\n            grid_pdata.append(grid)\n            particle_index_fields = [\n                f\"particle_position_{ax}\" for ax in ds.coordinates.axis_order\n            ]\n\n        for ptype in ds.particle_types_raw:\n            if (ptype, \"particle_position_x\") in pdata:\n                # we call them x, y, z even though they may be different field names\n                x, y, z = (pdata[ptype, pi_field] for pi_field in particle_index_fields)\n            elif (ptype, \"particle_position\") in pdata:\n                x, y, z = pdata[ptype, \"particle_position\"].T\n            else:\n                raise KeyError(\n                    \"Cannot decompose particle data without position fields!\"\n                )\n            pts = MatchPointsToGrids(grid_tree, len(x), x, y, z)\n            particle_grid_inds = pts.find_points_in_tree()\n            (assigned_particles,) = (particle_grid_inds >= 0).nonzero()\n            num_particles = particle_grid_inds.size\n            num_unassigned = num_particles - assigned_particles.size\n            if num_unassigned > 0:\n                eps = np.finfo(x.dtype).eps\n                s = np.array(\n                    [\n                        [x.min() - eps, x.max() + eps],\n                        [y.min() - eps, y.max() + eps],\n                        [z.min() - eps, z.max() + eps],\n                    ]\n                )\n                sug_bbox = [\n                    [min(bbox[0, 0], s[0, 0]), max(bbox[0, 1], s[0, 1])],\n                    [min(bbox[1, 0], s[1, 0]), max(bbox[1, 1], s[1, 1])],\n                    [min(bbox[2, 0], s[2, 0]), max(bbox[2, 1], s[2, 1])],\n                ]\n                mylog.warning(\n                    \"Discarding %s particles (out of %s) that are outside \"\n                    \"bounding box. Set bbox=%s to avoid this in the future.\",\n                    num_unassigned,\n                    num_particles,\n                    sug_bbox,\n                )\n                particle_grid_inds = particle_grid_inds[assigned_particles]\n                x = x[assigned_particles]\n                y = y[assigned_particles]\n                z = z[assigned_particles]\n            idxs = np.argsort(particle_grid_inds)\n            particle_grid_count = np.bincount(\n                particle_grid_inds.astype(\"intp\"), minlength=num_grids\n            )\n            particle_indices = np.zeros(num_grids + 1, dtype=\"int64\")\n            if num_grids > 1:\n                np.add.accumulate(\n                    particle_grid_count.squeeze(), out=particle_indices[1:]\n                )\n            else:\n                particle_indices[1] = particle_grid_count.squeeze()\n            for i, pcount in enumerate(particle_grid_count):\n                grid_pdata[i][\"number_of_particles\"] += pcount\n                start = particle_indices[i]\n                end = particle_indices[i + 1]\n                for key in pdata.keys():\n                    if key[0] == ptype:\n                        grid_pdata[i][key] = pdata[key][idxs][start:end]\n\n    else:\n        grid_pdata = [pdata]\n\n    for pd, gi in zip(grid_pdata, sorted(ds.stream_handler.fields), strict=True):\n        ds.stream_handler.fields[gi].update(pd)\n        ds.stream_handler.particle_types.update(set_particle_types(pd))\n        npart = ds.stream_handler.fields[gi].pop(\"number_of_particles\", 0)\n        ds.stream_handler.particle_count[gi] = npart\n\n\ndef process_data(data, grid_dims=None, allow_callables=True):\n    new_data, field_units = {}, {}\n    for field, val in data.items():\n        # val is a data array\n        if isinstance(val, np.ndarray):\n            # val is a YTArray\n            if hasattr(val, \"units\"):\n                field_units[field] = val.units\n                new_data[field] = val.copy().d\n            # val is a numpy array\n            else:\n                field_units[field] = \"\"\n                new_data[field] = val.copy()\n\n        # val is a tuple of (data, units)\n        elif isinstance(val, tuple) and len(val) == 2:\n            valid_data = isinstance(val[0], np.ndarray)\n            if allow_callables:\n                valid_data = valid_data or callable(val[0])\n            if not isinstance(field, (str, tuple)):\n                raise TypeError(\"Field name is not a string!\")\n            if not valid_data:\n                raise TypeError(\n                    \"Field data is not an ndarray or callable (with nproc == 1)!\"\n                )\n            if not isinstance(val[1], str):\n                raise TypeError(\"Unit specification is not a string!\")\n            field_units[field] = val[1]\n            new_data[field] = val[0]\n        # val is a list of data to be turned into an array\n        elif is_sequence(val):\n            field_units[field] = \"\"\n            new_data[field] = np.asarray(val)\n\n        elif callable(val):\n            if not allow_callables:\n                raise RuntimeError(\n                    \"Callable functions can not be specified \"\n                    \"in conjunction with nprocs > 1.\"\n                )\n            field_units[field] = \"\"\n            new_data[field] = val\n        else:\n            raise RuntimeError(\n                \"The data dict appears to be invalid. \"\n                \"The data dictionary must map from field \"\n                \"names to (numpy array, unit spec) tuples. \"\n            )\n\n    data = new_data\n\n    # At this point, we have arrays for all our fields\n    new_data = {}\n    for field in data:\n        n_shape = 3\n        if not callable(data[field]):\n            n_shape = len(data[field].shape)\n        if isinstance(field, tuple):\n            new_field = field\n        elif n_shape in (1, 2):\n            new_field = (\"io\", field)\n        elif n_shape == 3:\n            new_field = (\"stream\", field)\n        else:\n            raise RuntimeError\n        new_data[new_field] = data[field]\n        field_units[new_field] = field_units.pop(field)\n        known_fields = (\n            StreamFieldInfo.known_particle_fields + StreamFieldInfo.known_other_fields\n        )\n        # We do not want to override any of the known ones, if it's not\n        # overridden here.\n        if (\n            any(f[0] == new_field[1] for f in known_fields)\n            and field_units[new_field] == \"\"\n        ):\n            field_units.pop(new_field)\n    data = new_data\n    # Sanity checking that all fields have the same dimensions.\n    g_shapes = []\n    p_shapes = defaultdict(list)\n    for field in data:\n        if callable(data[field]):\n            continue\n        f_shape = data[field].shape\n        n_shape = len(f_shape)\n        if n_shape in (1, 2):\n            p_shapes[field[0]].append((field[1], f_shape[0]))\n        elif n_shape == 3:\n            g_shapes.append((field, f_shape))\n    if len(g_shapes) > 0:\n        g_s = np.array([s[1] for s in g_shapes])\n        if not np.all(g_s == g_s[0]):\n            raise YTInconsistentGridFieldShape(g_shapes)\n        if grid_dims is not None:\n            if not np.all(g_s == grid_dims):\n                raise YTInconsistentGridFieldShapeGridDims(g_shapes, grid_dims)\n    if len(p_shapes) > 0:\n        for ptype, p_shape in p_shapes.items():\n            p_s = np.array([s[1] for s in p_shape])\n            if not np.all(p_s == p_s[0]):\n                raise YTInconsistentParticleFieldShape(ptype, p_shape)\n    # Now that we know the particle fields are consistent, determine the number\n    # of particles.\n    if len(p_shapes) > 0:\n        number_of_particles = np.sum([s[0][1] for s in p_shapes.values()])\n    else:\n        number_of_particles = 0\n    return field_units, data, number_of_particles\n\n\ndef set_particle_types(data):\n    particle_types = {}\n    for key in data.keys():\n        if key == \"number_of_particles\":\n            continue\n        elif callable(data[key]):\n            particle_types[key] = False\n        elif len(data[key].shape) == 1:\n            particle_types[key] = True\n        else:\n            particle_types[key] = False\n    return particle_types\n"
  },
  {
    "path": "yt/frontends/stream/fields.py",
    "content": "import re\n\nfrom yt._typing import KnownFieldsT\nfrom yt.fields.field_info_container import FieldInfoContainer\n\n\nclass StreamFieldInfo(FieldInfoContainer):\n    known_other_fields: KnownFieldsT = (\n        (\"density\", (\"code_mass/code_length**3\", [\"density\"], None)),\n        (\n            \"dark_matter_density\",\n            (\"code_mass/code_length**3\", [\"dark_matter_density\"], None),\n        ),\n        (\"number_density\", (\"1/code_length**3\", [\"number_density\"], None)),\n        (\"pressure\", (\"dyne/code_length**2\", [\"pressure\"], None)),\n        (\"specific_thermal_energy\", (\"erg / g\", [\"specific_thermal_energy\"], None)),\n        (\"temperature\", (\"K\", [\"temperature\"], None)),\n        (\"velocity_x\", (\"code_length/code_time\", [\"velocity_x\"], None)),\n        (\"velocity_y\", (\"code_length/code_time\", [\"velocity_y\"], None)),\n        (\"velocity_z\", (\"code_length/code_time\", [\"velocity_z\"], None)),\n        (\"magnetic_field_x\", (\"gauss\", [], None)),\n        (\"magnetic_field_y\", (\"gauss\", [], None)),\n        (\"magnetic_field_z\", (\"gauss\", [], None)),\n        (\"velocity_r\", (\"code_length/code_time\", [\"velocity_r\"], None)),\n        (\"velocity_theta\", (\"code_length/code_time\", [\"velocity_theta\"], None)),\n        (\"velocity_phi\", (\"code_length/code_time\", [\"velocity_phi\"], None)),\n        (\"magnetic_field_r\", (\"gauss\", [], None)),\n        (\"magnetic_field_theta\", (\"gauss\", [], None)),\n        (\"magnetic_field_phi\", (\"gauss\", [], None)),\n        (\n            \"radiation_acceleration_x\",\n            (\"code_length/code_time**2\", [\"radiation_acceleration_x\"], None),\n        ),\n        (\n            \"radiation_acceleration_y\",\n            (\"code_length/code_time**2\", [\"radiation_acceleration_y\"], None),\n        ),\n        (\n            \"radiation_acceleration_z\",\n            (\"code_length/code_time**2\", [\"radiation_acceleration_z\"], None),\n        ),\n        (\"metallicity\", (\"Zsun\", [\"metallicity\"], None)),\n        # We need to have a bunch of species fields here, too\n        (\"metal_density\", (\"code_mass/code_length**3\", [\"metal_density\"], None)),\n        (\"hi_density\", (\"code_mass/code_length**3\", [\"hi_density\"], None)),\n        (\"hii_density\", (\"code_mass/code_length**3\", [\"hii_density\"], None)),\n        (\"h2i_density\", (\"code_mass/code_length**3\", [\"h2i_density\"], None)),\n        (\"h2ii_density\", (\"code_mass/code_length**3\", [\"h2ii_density\"], None)),\n        (\"h2m_density\", (\"code_mass/code_length**3\", [\"h2m_density\"], None)),\n        (\"hei_density\", (\"code_mass/code_length**3\", [\"hei_density\"], None)),\n        (\"heii_density\", (\"code_mass/code_length**3\", [\"heii_density\"], None)),\n        (\"heiii_density\", (\"code_mass/code_length**3\", [\"heiii_density\"], None)),\n        (\"hdi_density\", (\"code_mass/code_length**3\", [\"hdi_density\"], None)),\n        (\"di_density\", (\"code_mass/code_length**3\", [\"di_density\"], None)),\n        (\"dii_density\", (\"code_mass/code_length**3\", [\"dii_density\"], None)),\n    )\n\n    known_particle_fields: KnownFieldsT = (\n        (\"particle_position\", (\"code_length\", [\"particle_position\"], None)),\n        (\"particle_position_x\", (\"code_length\", [\"particle_position_x\"], None)),\n        (\"particle_position_y\", (\"code_length\", [\"particle_position_y\"], None)),\n        (\"particle_position_z\", (\"code_length\", [\"particle_position_z\"], None)),\n        (\"particle_velocity\", (\"code_length/code_time\", [\"particle_velocity\"], None)),\n        (\n            \"particle_velocity_x\",\n            (\"code_length/code_time\", [\"particle_velocity_x\"], None),\n        ),\n        (\n            \"particle_velocity_y\",\n            (\"code_length/code_time\", [\"particle_velocity_y\"], None),\n        ),\n        (\n            \"particle_velocity_z\",\n            (\"code_length/code_time\", [\"particle_velocity_z\"], None),\n        ),\n        (\"particle_index\", (\"\", [\"particle_index\"], None)),\n        (\n            \"particle_gas_density\",\n            (\"code_mass/code_length**3\", [\"particle_gas_density\"], None),\n        ),\n        (\"particle_gas_temperature\", (\"K\", [\"particle_gas_temperature\"], None)),\n        (\"particle_mass\", (\"code_mass\", [\"particle_mass\"], None)),\n        (\"smoothing_length\", (\"code_length\", [\"smoothing_length\"], None)),\n        (\"density\", (\"code_mass/code_length**3\", [\"density\"], None)),\n        (\"temperature\", (\"code_temperature\", [\"temperature\"], None)),\n        (\"creation_time\", (\"code_time\", [\"creation_time\"], None)),\n        (\"age\", (\"code_time\", [], None)),\n    )\n\n    def setup_fluid_fields(self):\n        from yt.fields.magnetic_field import setup_magnetic_field_aliases\n        from yt.fields.species_fields import setup_species_fields\n        from yt.utilities.periodic_table import periodic_table\n\n        # First grab all the element symbols from the periodic table\n        # (this includes the electron and deuterium)\n        symbols = list(periodic_table.elements_by_symbol)\n        # Now add some common molecules\n        symbols += [\"H2\", \"CO\"]\n        species_names = []\n        for field in self.ds.stream_handler.field_units:\n            if field[0] in self.ds.particle_types:\n                continue\n            units = self.ds.stream_handler.field_units[field]\n            if units != \"\":\n                self.add_output_field(field, sampling_type=\"cell\", units=units)\n                # Check to see if this could be a species fraction field\n                if field[1].endswith(\"_fraction\"):\n                    sp = field[1].rsplit(\"_fraction\")[0]\n                    parts = sp.split(\"_\")\n                    # parts is now either an element or molecule symbol\n                    # by itself:\n                    valid = parts[0] in symbols\n                    # or it may also have an ionization state after it\n                    if valid and len(parts) > 1 and parts[0] != \"El\":\n                        # Note that this doesn't catch invalid ionization states,\n                        # which would indicate more electron states empty than actually\n                        # exist, but we'll leave that to the user to do correctly.\n                        valid &= re.match(\"^[pm](0|[1-9][0-9]*)$\", parts[1]) is not None\n                    if valid:\n                        # Add the species name to the list\n                        species_names.append(sp)\n                        # Alias the field\n                        self.alias((\"gas\", field[1]), (\"stream\", field[1]))\n        self.species_names = sorted(species_names)\n        setup_magnetic_field_aliases(\n            self,\n            \"stream\",\n            [f\"magnetic_field_{ax}\" for ax in self.ds.coordinates.axis_order],\n        )\n        setup_species_fields(self)\n\n    def add_output_field(self, name, sampling_type, **kwargs):\n        if name in self.ds.stream_handler.field_units:\n            kwargs[\"units\"] = self.ds.stream_handler.field_units[name]\n        super().add_output_field(name, sampling_type, **kwargs)\n"
  },
  {
    "path": "yt/frontends/stream/io.py",
    "content": "import numpy as np\n\nfrom yt.utilities.exceptions import YTDomainOverflow\nfrom yt.utilities.io_handler import BaseIOHandler, BaseParticleIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\n\n\nclass IOHandlerStream(BaseIOHandler):\n    _dataset_type = \"stream\"\n    _vector_fields = {\"particle_velocity\": 3, \"particle_position\": 3}\n\n    def __init__(self, ds):\n        self.fields = ds.stream_handler.fields\n        self.field_units = ds.stream_handler.field_units\n        super().__init__(ds)\n\n    def _read_data_set(self, grid, field):\n        # This is where we implement processor-locking\n        tr = self.fields[grid.id][field]\n        if callable(tr):\n            tr = tr(grid, field)\n        # If it's particles, we copy.\n        if len(tr.shape) == 1:\n            return tr.copy()\n        # New in-place unit conversion breaks if we don't copy first\n        return tr\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        if any((ftype not in self.ds.fluid_types for ftype, fname in fields)):\n            raise NotImplementedError\n        rv = {}\n        for field in fields:\n            rv[field] = self.ds.arr(np.empty(size, dtype=\"float64\"))\n\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s blocks\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n        for field in fields:\n            ftype, fname = field\n            ind = 0\n            for chunk in chunks:\n                for g in chunk.objs:\n                    ds = self.fields[g.id][ftype, fname]\n                    if callable(ds):\n                        ds = ds(g, field)\n                    ind += g.select(selector, ds, rv[field], ind)  # caches\n        return rv\n\n    def _read_particle_coords(self, chunks, ptf):\n        chunks = list(chunks)\n        for chunk in chunks:\n            for g in chunk.objs:\n                if g.NumberOfParticles == 0:\n                    continue\n                gf = self.fields[g.id]\n                for ptype in sorted(ptf):\n                    if (ptype, \"particle_position\") in gf:\n                        x, y, z = gf[ptype, \"particle_position\"].T\n                    else:\n                        x, y, z = (\n                            gf[ptype, f\"particle_position_{ax}\"]\n                            for ax in self.ds.coordinates.axis_order\n                        )\n                    yield ptype, (x, y, z), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        chunks = list(chunks)\n        for chunk in chunks:\n            for g in chunk.objs:\n                if g.NumberOfParticles == 0:\n                    continue\n                gf = self.fields[g.id]\n                for ptype, field_list in sorted(ptf.items()):\n                    if (ptype, \"particle_position\") in gf:\n                        x, y, z = gf[ptype, \"particle_position\"].T\n                    else:\n                        x, y, z = (\n                            gf[ptype, f\"particle_position_{ax}\"]\n                            for ax in self.ds.coordinates.axis_order\n                        )\n                    mask = selector.select_points(x, y, z, 0.0)\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        data = np.asarray(gf[ptype, field])\n                        yield (ptype, field), data[mask]\n\n    @property\n    def _read_exception(self):\n        return KeyError\n\n\nclass StreamParticleIOHandler(BaseParticleIOHandler):\n    _dataset_type = \"stream_particles\"\n    _vector_fields = {\"particle_velocity\": 3, \"particle_position\": 3}\n\n    def __init__(self, ds):\n        self.fields = ds.stream_handler.fields\n        super().__init__(ds)\n\n    def _read_particle_coords(self, chunks, ptf):\n        for data_file in self._sorted_chunk_iterator(chunks):\n            f = self.fields[data_file.filename]\n            # This double-reads\n            for ptype in sorted(ptf):\n                yield (\n                    ptype,\n                    (\n                        f[ptype, \"particle_position_x\"],\n                        f[ptype, \"particle_position_y\"],\n                        f[ptype, \"particle_position_z\"],\n                    ),\n                    0.0,\n                )\n\n    def _read_smoothing_length(self, chunks, ptf, ptype):\n        for data_file in self._sorted_chunk_iterator(chunks):\n            f = self.fields[data_file.filename]\n            return f[ptype, \"smoothing_length\"]\n\n    def _read_particle_data_file(self, data_file, ptf, selector=None):\n        return_data = {}\n        f = self.fields[data_file.filename]\n        for ptype, field_list in sorted(ptf.items()):\n            if (ptype, \"particle_position\") in f:\n                ppos = f[ptype, \"particle_position\"]\n                x = ppos[:, 0]\n                y = ppos[:, 1]\n                z = ppos[:, 2]\n            else:\n                x, y, z = (f[ptype, f\"particle_position_{ax}\"] for ax in \"xyz\")\n            if (ptype, \"smoothing_length\") in self.ds.field_list:\n                hsml = f[ptype, \"smoothing_length\"]\n            else:\n                hsml = 0.0\n\n            if selector:\n                mask = selector.select_points(x, y, z, hsml)\n            if mask is None:\n                continue\n            for field in field_list:\n                data = f[ptype, field]\n                if selector:\n                    data = data[mask]\n\n                return_data[ptype, field] = data\n\n        return return_data\n\n    def _yield_coordinates(self, data_file, needed_ptype=None):\n        # self.fields[g.id][fname] is the pattern here\n        for ptype in self.ds.particle_types_raw:\n            if needed_ptype is not None and needed_ptype is not ptype:\n                continue\n            try:\n                pos = np.column_stack(\n                    [\n                        self.fields[data_file.filename][\n                            ptype, f\"particle_position_{ax}\"\n                        ]\n                        for ax in \"xyz\"\n                    ]\n                )\n            except KeyError:\n                pos = self.fields[data_file.filename][ptype, \"particle_position\"]\n            if np.any(pos.min(axis=0) < data_file.ds.domain_left_edge) or np.any(\n                pos.max(axis=0) > data_file.ds.domain_right_edge\n            ):\n                raise YTDomainOverflow(\n                    pos.min(axis=0),\n                    pos.max(axis=0),\n                    data_file.ds.domain_left_edge,\n                    data_file.ds.domain_right_edge,\n                )\n            yield ptype, pos\n\n    def _get_smoothing_length(self, data_file, dtype, shape):\n        ptype = self.ds._sph_ptypes[0]\n        return self.fields[data_file.filename][ptype, \"smoothing_length\"]\n\n    def _count_particles(self, data_file):\n        pcount = {}\n        for ptype in self.ds.particle_types_raw:\n            pcount[ptype] = 0\n        # stream datasets only have one \"file\"\n        if data_file.file_id > 0:\n            return pcount\n        for ptype in self.ds.particle_types_raw:\n            d = self.fields[data_file.filename]\n            try:\n                pcount[ptype] = d[ptype, \"particle_position_x\"].size\n            except KeyError:\n                pcount[ptype] = d[ptype, \"particle_position\"].shape[0]\n        return pcount\n\n    def _identify_fields(self, data_file):\n        return self.fields[data_file.filename].keys(), {}\n\n\nclass IOHandlerStreamHexahedral(BaseIOHandler):\n    _dataset_type = \"stream_hexahedral\"\n    _vector_fields = {\"particle_velocity\": 3, \"particle_position\": 3}\n\n    def __init__(self, ds):\n        self.fields = ds.stream_handler.fields\n        super().__init__(ds)\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        assert len(chunks) == 1\n        chunk = chunks[0]\n        rv = {}\n        for field in fields:\n            ftype, fname = field\n            rv[field] = np.empty(size, dtype=\"float64\")\n        ngrids = sum(len(chunk.objs) for chunk in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s blocks\",\n            size,\n            [fn for ft, fn in fields],\n            ngrids,\n        )\n        for field in fields:\n            ind = 0\n            ftype, fname = field\n            for chunk in chunks:\n                for g in chunk.objs:\n                    ds = self.fields[g.mesh_id].get(field, None)\n                    if ds is None:\n                        ds = self.fields[g.mesh_id][fname]\n                    ind += g.select(selector, ds, rv[field], ind)  # caches\n        return rv\n\n\nclass IOHandlerStreamOctree(BaseIOHandler):\n    _dataset_type = \"stream_octree\"\n    _vector_fields = {\"particle_velocity\": 3, \"particle_position\": 3}\n\n    def __init__(self, ds):\n        self.fields = ds.stream_handler.fields\n        super().__init__(ds)\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        rv = {}\n        ind = 0\n        chunks = list(chunks)\n        assert len(chunks) == 1\n        for chunk in chunks:\n            assert len(chunk.objs) == 1\n            for subset in chunk.objs:\n                field_vals = {}\n                for field in fields:\n                    field_vals[field] = self.fields[\n                        subset.domain_id - subset._domain_offset\n                    ][field]\n\n                    if callable(field_vals[field]):\n                        field_vals[field] = field_vals[field]()\n                subset.fill(field_vals, rv, selector, ind)\n        return rv\n\n\nclass IOHandlerStreamUnstructured(BaseIOHandler):\n    _dataset_type = \"stream_unstructured\"\n\n    def __init__(self, ds):\n        self.fields = ds.stream_handler.fields\n        super().__init__(ds)\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        chunks = list(chunks)\n        rv = {}\n        for field in fields:\n            ftype, fname = field\n            if ftype == \"all\":\n                ci = np.concatenate(\n                    [mesh.connectivity_indices for mesh in self.ds.index.mesh_union]\n                )\n            else:\n                mesh_id = int(ftype[-1]) - 1\n                m = self.ds.index.meshes[mesh_id]\n                ci = m.connectivity_indices\n            num_elem = ci.shape[0]\n            if fname in self.ds._node_fields:\n                nodes_per_element = ci.shape[1]\n                rv[field] = np.empty((num_elem, nodes_per_element), dtype=\"float64\")\n            else:\n                rv[field] = np.empty(num_elem, dtype=\"float64\")\n        for field in fields:\n            ind = 0\n            ftype, fname = field\n            if ftype == \"all\":\n                objs = list(self.ds.index.mesh_union)\n            else:\n                mesh_ids = [int(ftype[-1])]\n                chunk = chunks[mesh_ids[0] - 1]\n                objs = chunk.objs\n            for g in objs:\n                ds = self.fields[g.mesh_id].get(field, None)\n                if ds is None:\n                    f = (f\"connect{(g.mesh_id + 1)}\", fname)\n                    ds = self.fields[g.mesh_id][f]\n                ind += g.select(selector, ds, rv[field], ind)  # caches\n            rv[field] = rv[field][:ind]\n        return rv\n"
  },
  {
    "path": "yt/frontends/stream/misc.py",
    "content": "import numpy as np\n\nfrom yt._typing import DomainDimensions\n\n\ndef _validate_cell_widths(\n    cell_widths: list[np.ndarray],\n    domain_dimensions: DomainDimensions,\n) -> list[np.ndarray]:\n    # check dimensionality\n    if (nwids := len(cell_widths)) != (ndims := len(domain_dimensions)):\n        raise ValueError(\n            f\"The number of elements in cell_widths ({nwids}) \"\n            f\"must match the number of dimensions ({ndims}).\"\n        )\n\n    # check the dtypes for each dimension, upcast to float64 if needed\n    for idim in range(len(cell_widths)):\n        cell_widths[idim] = cell_widths[idim].astype(np.float64, copy=False)\n\n    return cell_widths\n"
  },
  {
    "path": "yt/frontends/stream/sample_data/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/stream/sample_data/hexahedral_mesh.py",
    "content": "import numpy as np\n\n# This mesh can be found at\n# https://github.com/idaholab/moose/tree/master/test/tests/bcs/nodal_normals/cylinder-hexes.e\n\n_coordinates = np.array([\n       [  1.00000000e+00,   0.00000000e+00,   3.57142857e-01],\n       [  9.86361303e-01,   1.64594590e-01,   3.57142857e-01],\n       [  9.04164528e-01,   1.50878374e-01,   3.57142857e-01],\n       [  9.16666667e-01,   2.24432411e-17,   3.57142857e-01],\n       [  1.00000000e+00,   0.00000000e+00,   5.00000000e-01],\n       [  9.86361303e-01,   1.64594590e-01,   5.00000000e-01],\n       [  9.04164528e-01,   1.50878374e-01,   5.00000000e-01],\n       [  9.16666667e-01,  -4.50419429e-18,   5.00000000e-01],\n       [  9.45817242e-01,   3.24699469e-01,   3.57142857e-01],\n       [  8.66999138e-01,   2.97641180e-01,   3.57142857e-01],\n       [  9.45817242e-01,   3.24699469e-01,   5.00000000e-01],\n       [  8.66999138e-01,   2.97641180e-01,   5.00000000e-01],\n       [  8.79473751e-01,   4.75947393e-01,   3.57142857e-01],\n       [  8.06184272e-01,   4.36285110e-01,   3.57142857e-01],\n       [  8.79473751e-01,   4.75947393e-01,   5.00000000e-01],\n       [  8.06184272e-01,   4.36285110e-01,   5.00000000e-01],\n       [  7.89140509e-01,   6.14212713e-01,   3.57142857e-01],\n       [  7.23378800e-01,   5.63028320e-01,   3.57142857e-01],\n       [  7.89140509e-01,   6.14212713e-01,   5.00000000e-01],\n       [  7.23378800e-01,   5.63028320e-01,   5.00000000e-01],\n       [  6.77281572e-01,   7.35723911e-01,   3.57142857e-01],\n       [  6.20841441e-01,   6.74413585e-01,   3.57142857e-01],\n       [  6.77281572e-01,   7.35723911e-01,   5.00000000e-01],\n       [  6.20841441e-01,   6.74413585e-01,   5.00000000e-01],\n       [  5.46948158e-01,   8.37166478e-01,   3.57142857e-01],\n       [  5.01369145e-01,   7.67402605e-01,   3.57142857e-01],\n       [  5.46948158e-01,   8.37166478e-01,   5.00000000e-01],\n       [  5.01369145e-01,   7.67402605e-01,   5.00000000e-01],\n       [  4.01695425e-01,   9.15773327e-01,   3.57142857e-01],\n       [  3.68220806e-01,   8.39458883e-01,   3.57142857e-01],\n       [  4.01695425e-01,   9.15773327e-01,   5.00000000e-01],\n       [  3.68220806e-01,   8.39458883e-01,   5.00000000e-01],\n       [  8.21967753e-01,   1.37162159e-01,   3.57142857e-01],\n       [  8.33333333e-01,   3.56043101e-17,   3.57142857e-01],\n       [  8.21967753e-01,   1.37162159e-01,   5.00000000e-01],\n       [  8.33333333e-01,  -9.00838858e-18,   5.00000000e-01],\n       [  7.88181035e-01,   2.70582891e-01,   3.57142857e-01],\n       [  7.88181035e-01,   2.70582891e-01,   5.00000000e-01],\n       [  7.32894793e-01,   3.96622828e-01,   3.57142857e-01],\n       [  7.32894793e-01,   3.96622828e-01,   5.00000000e-01],\n       [  6.57617091e-01,   5.11843927e-01,   3.57142857e-01],\n       [  6.57617091e-01,   5.11843927e-01,   5.00000000e-01],\n       [  5.64401310e-01,   6.13103259e-01,   3.57142857e-01],\n       [  5.64401310e-01,   6.13103259e-01,   5.00000000e-01],\n       [  4.55790132e-01,   6.97638732e-01,   3.57142857e-01],\n       [  4.55790132e-01,   6.97638732e-01,   5.00000000e-01],\n       [  3.34746187e-01,   7.63144439e-01,   3.57142857e-01],\n       [  3.34746187e-01,   7.63144439e-01,   5.00000000e-01],\n       [  7.39770978e-01,   1.23445943e-01,   3.57142857e-01],\n       [  7.50000000e-01,   3.32197375e-17,   3.57142857e-01],\n       [  7.39770978e-01,   1.23445943e-01,   5.00000000e-01],\n       [  7.50000000e-01,  -1.35125829e-17,   5.00000000e-01],\n       [  7.09362931e-01,   2.43524602e-01,   3.57142857e-01],\n       [  7.09362931e-01,   2.43524602e-01,   5.00000000e-01],\n       [  6.59605313e-01,   3.56960545e-01,   3.57142857e-01],\n       [  6.59605313e-01,   3.56960545e-01,   5.00000000e-01],\n       [  5.91855382e-01,   4.60659535e-01,   3.57142857e-01],\n       [  5.91855382e-01,   4.60659535e-01,   5.00000000e-01],\n       [  5.07961179e-01,   5.51792933e-01,   3.57142857e-01],\n       [  5.07961179e-01,   5.51792933e-01,   5.00000000e-01],\n       [  4.10211119e-01,   6.27874859e-01,   3.57142857e-01],\n       [  4.10211119e-01,   6.27874859e-01,   5.00000000e-01],\n       [  3.01271568e-01,   6.86829995e-01,   3.57142857e-01],\n       [  3.01271568e-01,   6.86829995e-01,   5.00000000e-01],\n       [  6.57574202e-01,   1.09729727e-01,   3.57142857e-01],\n       [  6.66666667e-01,   2.67569398e-17,   3.57142857e-01],\n       [  6.57574202e-01,   1.09729727e-01,   5.00000000e-01],\n       [  6.66666667e-01,  -1.80167772e-17,   5.00000000e-01],\n       [  6.30544828e-01,   2.16466313e-01,   3.57142857e-01],\n       [  6.30544828e-01,   2.16466313e-01,   5.00000000e-01],\n       [  5.86315834e-01,   3.17298262e-01,   3.57142857e-01],\n       [  5.86315834e-01,   3.17298262e-01,   5.00000000e-01],\n       [  5.26093673e-01,   4.09475142e-01,   3.57142857e-01],\n       [  5.26093673e-01,   4.09475142e-01,   5.00000000e-01],\n       [  4.51521048e-01,   4.90482607e-01,   3.57142857e-01],\n       [  4.51521048e-01,   4.90482607e-01,   5.00000000e-01],\n       [  3.64632105e-01,   5.58110986e-01,   3.57142857e-01],\n       [  3.64632105e-01,   5.58110986e-01,   5.00000000e-01],\n       [  2.67796950e-01,   6.10515551e-01,   3.57142857e-01],\n       [  2.67796950e-01,   6.10515551e-01,   5.00000000e-01],\n       [  5.75377427e-01,   9.60135110e-02,   3.57142857e-01],\n       [  5.83333333e-01,   1.89153508e-17,   3.57142857e-01],\n       [  5.75377427e-01,   9.60135110e-02,   5.00000000e-01],\n       [  5.83333333e-01,  -2.25209714e-17,   5.00000000e-01],\n       [  5.51726724e-01,   1.89408024e-01,   3.57142857e-01],\n       [  5.51726724e-01,   1.89408024e-01,   5.00000000e-01],\n       [  5.13026355e-01,   2.77635979e-01,   3.57142857e-01],\n       [  5.13026355e-01,   2.77635979e-01,   5.00000000e-01],\n       [  4.60331964e-01,   3.58290749e-01,   3.57142857e-01],\n       [  4.60331964e-01,   3.58290749e-01,   5.00000000e-01],\n       [  3.95080917e-01,   4.29172281e-01,   3.57142857e-01],\n       [  3.95080917e-01,   4.29172281e-01,   5.00000000e-01],\n       [  3.19053092e-01,   4.88347112e-01,   3.57142857e-01],\n       [  3.19053092e-01,   4.88347112e-01,   5.00000000e-01],\n       [  2.34322331e-01,   5.34201107e-01,   3.57142857e-01],\n       [  2.34322331e-01,   5.34201107e-01,   5.00000000e-01],\n       [  4.93180652e-01,   8.22972951e-02,   3.57142857e-01],\n       [  5.00000000e-01,   1.04459875e-17,   3.57142857e-01],\n       [  4.93180652e-01,   8.22972951e-02,   5.00000000e-01],\n       [  5.00000000e-01,  -2.70251657e-17,   5.00000000e-01],\n       [  4.72908621e-01,   1.62349735e-01,   3.57142857e-01],\n       [  4.72908621e-01,   1.62349735e-01,   5.00000000e-01],\n       [  4.39736876e-01,   2.37973697e-01,   3.57142857e-01],\n       [  4.39736876e-01,   2.37973697e-01,   5.00000000e-01],\n       [  3.94570255e-01,   3.07106356e-01,   3.57142857e-01],\n       [  3.94570255e-01,   3.07106356e-01,   5.00000000e-01],\n       [  3.38640786e-01,   3.67861955e-01,   3.57142857e-01],\n       [  3.38640786e-01,   3.67861955e-01,   5.00000000e-01],\n       [  2.73474079e-01,   4.18583239e-01,   3.57142857e-01],\n       [  2.73474079e-01,   4.18583239e-01,   5.00000000e-01],\n       [  2.00847712e-01,   4.57886663e-01,   3.57142857e-01],\n       [  2.00847712e-01,   4.57886663e-01,   5.00000000e-01],\n       [  2.92606991e-01,   3.16445261e-01,   3.57142857e-01],\n       [  3.44188184e-01,   2.67935253e-01,   3.57142857e-01],\n       [  2.92606991e-01,   3.16445261e-01,   5.00000000e-01],\n       [  3.44188184e-01,   2.67935253e-01,   5.00000000e-01],\n       [  2.34867640e-01,   3.58265725e-01,   3.57142857e-01],\n       [  2.34867640e-01,   3.58265725e-01,   5.00000000e-01],\n       [  1.72155182e-01,   3.92474283e-01,   3.57142857e-01],\n       [  1.72155182e-01,   3.92474283e-01,   5.00000000e-01],\n       [  2.46573197e-01,   2.65028567e-01,   3.57142857e-01],\n       [  2.93806114e-01,   2.28764151e-01,   3.57142857e-01],\n       [  2.46573197e-01,   2.65028567e-01,   5.00000000e-01],\n       [  2.93806114e-01,   2.28764151e-01,   5.00000000e-01],\n       [  1.96261201e-01,   2.97948211e-01,   3.57142857e-01],\n       [  1.96261201e-01,   2.97948211e-01,   5.00000000e-01],\n       [  1.43462652e-01,   3.27061902e-01,   3.57142857e-01],\n       [  1.43462652e-01,   3.27061902e-01,   5.00000000e-01],\n       [  2.00539403e-01,   2.13611872e-01,   3.57142857e-01],\n       [  2.43424043e-01,   1.89593048e-01,   3.57142857e-01],\n       [  2.00539403e-01,   2.13611872e-01,   5.00000000e-01],\n       [  2.43424043e-01,   1.89593048e-01,   5.00000000e-01],\n       [  1.57654762e-01,   2.37630697e-01,   3.57142857e-01],\n       [  1.57654762e-01,   2.37630697e-01,   5.00000000e-01],\n       [  1.14770121e-01,   2.61649522e-01,   3.57142857e-01],\n       [  1.14770121e-01,   2.61649522e-01,   5.00000000e-01],\n       [  8.60775910e-02,   1.96237141e-01,   3.57142857e-01],\n       [  1.39074405e-01,   1.78223023e-01,   3.57142857e-01],\n       [  8.60775910e-02,   1.96237141e-01,   5.00000000e-01],\n       [  1.39074405e-01,   1.78223023e-01,   5.00000000e-01],\n       [  5.73850607e-02,   1.30824761e-01,   3.57142857e-01],\n       [  1.20494048e-01,   1.18815349e-01,   3.57142857e-01],\n       [  5.73850607e-02,   1.30824761e-01,   5.00000000e-01],\n       [  1.20494048e-01,   1.18815349e-01,   5.00000000e-01],\n       [  2.86925303e-02,   6.54123805e-02,   3.57142857e-01],\n       [  1.01913690e-01,   5.94076743e-02,   3.57142857e-01],\n       [  2.86925303e-02,   6.54123805e-02,   5.00000000e-01],\n       [  1.01913690e-01,   5.94076743e-02,   5.00000000e-01],\n       [ -1.23126238e-17,  -4.32625932e-17,   3.57142857e-01],\n       [  8.33333333e-02,  -3.43565351e-17,   3.57142857e-01],\n       [ -5.84327908e-18,  -5.40503315e-17,   5.00000000e-01],\n       [  8.33333333e-02,  -4.95461372e-17,   5.00000000e-01],\n       [  1.92071219e-01,   1.60208904e-01,   3.57142857e-01],\n       [  1.92071219e-01,   1.60208904e-01,   5.00000000e-01],\n       [  1.83603035e-01,   1.06805936e-01,   3.57142857e-01],\n       [  1.83603035e-01,   1.06805936e-01,   5.00000000e-01],\n       [  1.75134851e-01,   5.34029681e-02,   3.57142857e-01],\n       [  1.75134851e-01,   5.34029681e-02,   5.00000000e-01],\n       [  1.66666667e-01,  -2.53770275e-17,   3.57142857e-01],\n       [  1.66666667e-01,  -4.50419429e-17,   5.00000000e-01],\n       [  2.45068032e-01,   1.42194786e-01,   3.57142857e-01],\n       [  2.45068032e-01,   1.42194786e-01,   5.00000000e-01],\n       [  2.46712022e-01,   9.47965238e-02,   3.57142857e-01],\n       [  2.46712022e-01,   9.47965238e-02,   5.00000000e-01],\n       [  2.48356011e-01,   4.73982619e-02,   3.57142857e-01],\n       [  2.48356011e-01,   4.73982619e-02,   5.00000000e-01],\n       [  2.50000000e-01,  -1.63501274e-17,   3.57142857e-01],\n       [  2.50000000e-01,  -4.05377486e-17,   5.00000000e-01],\n       [  3.33333333e-01,  -7.31934467e-18,   3.57142857e-01],\n       [  3.29964224e-01,   5.90312730e-02,   3.57142857e-01],\n       [  3.33333333e-01,  -3.60335543e-17,   5.00000000e-01],\n       [  3.29964224e-01,   5.90312730e-02,   5.00000000e-01],\n       [  4.16666667e-01,   1.64710272e-18,   3.57142857e-01],\n       [  4.11572438e-01,   7.06642841e-02,   3.57142857e-01],\n       [  4.16666667e-01,  -3.15293600e-17,   5.00000000e-01],\n       [  4.11572438e-01,   7.06642841e-02,   5.00000000e-01],\n       [  3.22110888e-01,   1.17314261e-01,   3.57142857e-01],\n       [  3.22110888e-01,   1.17314261e-01,   5.00000000e-01],\n       [  3.97509754e-01,   1.39831998e-01,   3.57142857e-01],\n       [  3.97509754e-01,   1.39831998e-01,   5.00000000e-01],\n       [  3.09957647e-01,   1.74121089e-01,   3.57142857e-01],\n       [  3.09957647e-01,   1.74121089e-01,   5.00000000e-01],\n       [  3.74847261e-01,   2.06047393e-01,   3.57142857e-01],\n       [  3.74847261e-01,   2.06047393e-01,   5.00000000e-01],\n       [  2.45485487e-01,   9.69400266e-01,   3.57142857e-01],\n       [  2.25028363e-01,   8.88616910e-01,   3.57142857e-01],\n       [  2.45485487e-01,   9.69400266e-01,   5.00000000e-01],\n       [  2.25028363e-01,   8.88616910e-01,   5.00000000e-01],\n       [  8.25793455e-02,   9.96584493e-01,   3.57142857e-01],\n       [  7.56977333e-02,   9.13535785e-01,   3.57142857e-01],\n       [  8.25793455e-02,   9.96584493e-01,   5.00000000e-01],\n       [  7.56977333e-02,   9.13535785e-01,   5.00000000e-01],\n       [ -8.25793455e-02,   9.96584493e-01,   3.57142857e-01],\n       [ -7.56977333e-02,   9.13535785e-01,   3.57142857e-01],\n       [ -8.25793455e-02,   9.96584493e-01,   5.00000000e-01],\n       [ -7.56977333e-02,   9.13535785e-01,   5.00000000e-01],\n       [ -2.45485487e-01,   9.69400266e-01,   3.57142857e-01],\n       [ -2.25028363e-01,   8.88616910e-01,   3.57142857e-01],\n       [ -2.45485487e-01,   9.69400266e-01,   5.00000000e-01],\n       [ -2.25028363e-01,   8.88616910e-01,   5.00000000e-01],\n       [ -4.01695425e-01,   9.15773327e-01,   3.57142857e-01],\n       [ -3.68220806e-01,   8.39458883e-01,   3.57142857e-01],\n       [ -4.01695425e-01,   9.15773327e-01,   5.00000000e-01],\n       [ -3.68220806e-01,   8.39458883e-01,   5.00000000e-01],\n       [ -5.46948158e-01,   8.37166478e-01,   3.57142857e-01],\n       [ -5.01369145e-01,   7.67402605e-01,   3.57142857e-01],\n       [ -5.46948158e-01,   8.37166478e-01,   5.00000000e-01],\n       [ -5.01369145e-01,   7.67402605e-01,   5.00000000e-01],\n       [ -6.77281572e-01,   7.35723911e-01,   3.57142857e-01],\n       [ -6.20841441e-01,   6.74413585e-01,   3.57142857e-01],\n       [ -6.77281572e-01,   7.35723911e-01,   5.00000000e-01],\n       [ -6.20841441e-01,   6.74413585e-01,   5.00000000e-01],\n       [  2.04571239e-01,   8.07833555e-01,   3.57142857e-01],\n       [  2.04571239e-01,   8.07833555e-01,   5.00000000e-01],\n       [  6.88161212e-02,   8.30487078e-01,   3.57142857e-01],\n       [  6.88161212e-02,   8.30487078e-01,   5.00000000e-01],\n       [ -6.88161212e-02,   8.30487078e-01,   3.57142857e-01],\n       [ -6.88161212e-02,   8.30487078e-01,   5.00000000e-01],\n       [ -2.04571239e-01,   8.07833555e-01,   3.57142857e-01],\n       [ -2.04571239e-01,   8.07833555e-01,   5.00000000e-01],\n       [ -3.34746187e-01,   7.63144439e-01,   3.57142857e-01],\n       [ -3.34746187e-01,   7.63144439e-01,   5.00000000e-01],\n       [ -4.55790132e-01,   6.97638732e-01,   3.57142857e-01],\n       [ -4.55790132e-01,   6.97638732e-01,   5.00000000e-01],\n       [ -5.64401310e-01,   6.13103259e-01,   3.57142857e-01],\n       [ -5.64401310e-01,   6.13103259e-01,   5.00000000e-01],\n       [  1.84114115e-01,   7.27050199e-01,   3.57142857e-01],\n       [  1.84114115e-01,   7.27050199e-01,   5.00000000e-01],\n       [  6.19345091e-02,   7.47438370e-01,   3.57142857e-01],\n       [  6.19345091e-02,   7.47438370e-01,   5.00000000e-01],\n       [ -6.19345091e-02,   7.47438370e-01,   3.57142857e-01],\n       [ -6.19345091e-02,   7.47438370e-01,   5.00000000e-01],\n       [ -1.84114115e-01,   7.27050199e-01,   3.57142857e-01],\n       [ -1.84114115e-01,   7.27050199e-01,   5.00000000e-01],\n       [ -3.01271568e-01,   6.86829995e-01,   3.57142857e-01],\n       [ -3.01271568e-01,   6.86829995e-01,   5.00000000e-01],\n       [ -4.10211119e-01,   6.27874859e-01,   3.57142857e-01],\n       [ -4.10211119e-01,   6.27874859e-01,   5.00000000e-01],\n       [ -5.07961179e-01,   5.51792933e-01,   3.57142857e-01],\n       [ -5.07961179e-01,   5.51792933e-01,   5.00000000e-01],\n       [  1.63656991e-01,   6.46266844e-01,   3.57142857e-01],\n       [  1.63656991e-01,   6.46266844e-01,   5.00000000e-01],\n       [  5.50528970e-02,   6.64389662e-01,   3.57142857e-01],\n       [  5.50528970e-02,   6.64389662e-01,   5.00000000e-01],\n       [ -5.50528970e-02,   6.64389662e-01,   3.57142857e-01],\n       [ -5.50528970e-02,   6.64389662e-01,   5.00000000e-01],\n       [ -1.63656991e-01,   6.46266844e-01,   3.57142857e-01],\n       [ -1.63656991e-01,   6.46266844e-01,   5.00000000e-01],\n       [ -2.67796950e-01,   6.10515551e-01,   3.57142857e-01],\n       [ -2.67796950e-01,   6.10515551e-01,   5.00000000e-01],\n       [ -3.64632105e-01,   5.58110986e-01,   3.57142857e-01],\n       [ -3.64632105e-01,   5.58110986e-01,   5.00000000e-01],\n       [ -4.51521048e-01,   4.90482607e-01,   3.57142857e-01],\n       [ -4.51521048e-01,   4.90482607e-01,   5.00000000e-01],\n       [  1.43199867e-01,   5.65483488e-01,   3.57142857e-01],\n       [  1.43199867e-01,   5.65483488e-01,   5.00000000e-01],\n       [  4.81712849e-02,   5.81340954e-01,   3.57142857e-01],\n       [  4.81712849e-02,   5.81340954e-01,   5.00000000e-01],\n       [ -4.81712849e-02,   5.81340954e-01,   3.57142857e-01],\n       [ -4.81712849e-02,   5.81340954e-01,   5.00000000e-01],\n       [ -1.43199867e-01,   5.65483488e-01,   3.57142857e-01],\n       [ -1.43199867e-01,   5.65483488e-01,   5.00000000e-01],\n       [ -2.34322331e-01,   5.34201107e-01,   3.57142857e-01],\n       [ -2.34322331e-01,   5.34201107e-01,   5.00000000e-01],\n       [ -3.19053092e-01,   4.88347112e-01,   3.57142857e-01],\n       [ -3.19053092e-01,   4.88347112e-01,   5.00000000e-01],\n       [ -3.95080917e-01,   4.29172281e-01,   3.57142857e-01],\n       [ -3.95080917e-01,   4.29172281e-01,   5.00000000e-01],\n       [  1.22742744e-01,   4.84700133e-01,   3.57142857e-01],\n       [  1.22742744e-01,   4.84700133e-01,   5.00000000e-01],\n       [  4.12896727e-02,   4.98292247e-01,   3.57142857e-01],\n       [  4.12896727e-02,   4.98292247e-01,   5.00000000e-01],\n       [ -4.12896727e-02,   4.98292247e-01,   3.57142857e-01],\n       [ -4.12896727e-02,   4.98292247e-01,   5.00000000e-01],\n       [ -1.22742744e-01,   4.84700133e-01,   3.57142857e-01],\n       [ -1.22742744e-01,   4.84700133e-01,   5.00000000e-01],\n       [ -2.00847712e-01,   4.57886663e-01,   3.57142857e-01],\n       [ -2.00847712e-01,   4.57886663e-01,   5.00000000e-01],\n       [ -2.73474079e-01,   4.18583239e-01,   3.57142857e-01],\n       [ -2.73474079e-01,   4.18583239e-01,   5.00000000e-01],\n       [ -3.38640786e-01,   3.67861955e-01,   3.57142857e-01],\n       [ -3.38640786e-01,   3.67861955e-01,   5.00000000e-01],\n       [ -1.02283149e-01,   4.15336195e-01,   3.57142857e-01],\n       [ -3.59859419e-02,   4.34695086e-01,   3.57142857e-01],\n       [ -1.02283149e-01,   4.15336195e-01,   5.00000000e-01],\n       [ -3.59859419e-02,   4.34695086e-01,   5.00000000e-01],\n       [ -1.66348287e-01,   3.87163066e-01,   3.57142857e-01],\n       [ -1.66348287e-01,   3.87163066e-01,   5.00000000e-01],\n       [ -2.26761024e-01,   3.50663301e-01,   3.57142857e-01],\n       [ -2.26761024e-01,   3.50663301e-01,   5.00000000e-01],\n       [ -2.82200655e-01,   3.06551629e-01,   3.57142857e-01],\n       [ -2.82200655e-01,   3.06551629e-01,   5.00000000e-01],\n       [ -8.18235534e-02,   3.45972257e-01,   3.57142857e-01],\n       [ -3.06822110e-02,   3.71097926e-01,   3.57142857e-01],\n       [ -8.18235534e-02,   3.45972257e-01,   5.00000000e-01],\n       [ -3.06822110e-02,   3.71097926e-01,   5.00000000e-01],\n       [ -1.31848862e-01,   3.16439469e-01,   3.57142857e-01],\n       [ -1.31848862e-01,   3.16439469e-01,   5.00000000e-01],\n       [ -1.80047970e-01,   2.82743363e-01,   3.57142857e-01],\n       [ -1.80047970e-01,   2.82743363e-01,   5.00000000e-01],\n       [ -2.25760524e-01,   2.45241304e-01,   3.57142857e-01],\n       [ -2.25760524e-01,   2.45241304e-01,   5.00000000e-01],\n       [ -6.13639584e-02,   2.76608319e-01,   3.57142857e-01],\n       [ -2.53784802e-02,   3.07500766e-01,   3.57142857e-01],\n       [ -6.13639584e-02,   2.76608319e-01,   5.00000000e-01],\n       [ -2.53784802e-02,   3.07500766e-01,   5.00000000e-01],\n       [ -9.73494365e-02,   2.45715872e-01,   3.57142857e-01],\n       [ -9.73494365e-02,   2.45715872e-01,   5.00000000e-01],\n       [ -1.33334915e-01,   2.14823425e-01,   3.57142857e-01],\n       [ -1.33334915e-01,   2.14823425e-01,   5.00000000e-01],\n       [ -1.69320393e-01,   1.83930978e-01,   3.57142857e-01],\n       [ -1.69320393e-01,   1.83930978e-01,   5.00000000e-01],\n       [ -1.12880262e-01,   1.22620652e-01,   3.57142857e-01],\n       [ -7.93257664e-02,   1.65019743e-01,   3.57142857e-01],\n       [ -1.12880262e-01,   1.22620652e-01,   5.00000000e-01],\n       [ -7.93257664e-02,   1.65019743e-01,   5.00000000e-01],\n       [ -5.64401310e-02,   6.13103259e-02,   3.57142857e-01],\n       [ -2.53166180e-02,   1.15216062e-01,   3.57142857e-01],\n       [ -5.64401310e-02,   6.13103259e-02,   5.00000000e-01],\n       [ -2.53166180e-02,   1.15216062e-01,   5.00000000e-01],\n       [ -4.57712708e-02,   2.07418835e-01,   3.57142857e-01],\n       [ -4.57712708e-02,   2.07418835e-01,   5.00000000e-01],\n       [  5.80689493e-03,   1.69121798e-01,   3.57142857e-01],\n       [  5.80689493e-03,   1.69121798e-01,   5.00000000e-01],\n       [ -1.22167752e-02,   2.49817927e-01,   3.57142857e-01],\n       [ -1.22167752e-02,   2.49817927e-01,   5.00000000e-01],\n       [  3.69304079e-02,   2.23027534e-01,   3.57142857e-01],\n       [  3.69304079e-02,   2.23027534e-01,   5.00000000e-01],\n       [  2.13377203e-02,   2.92217018e-01,   3.57142857e-01],\n       [  2.13377203e-02,   2.92217018e-01,   5.00000000e-01],\n       [  6.80539208e-02,   2.76933270e-01,   3.57142857e-01],\n       [  6.80539208e-02,   2.76933270e-01,   5.00000000e-01],\n       [  8.62835284e-02,   3.46188891e-01,   3.57142857e-01],\n       [  8.62835284e-02,   3.46188891e-01,   5.00000000e-01],\n       [  1.04513136e-01,   4.15444512e-01,   3.57142857e-01],\n       [  1.04513136e-01,   4.15444512e-01,   5.00000000e-01],\n       [  2.79883711e-02,   3.60908761e-01,   3.57142857e-01],\n       [  2.79883711e-02,   3.60908761e-01,   5.00000000e-01],\n       [  3.46390219e-02,   4.29600504e-01,   3.57142857e-01],\n       [  3.46390219e-02,   4.29600504e-01,   5.00000000e-01],\n       [ -7.89140509e-01,   6.14212713e-01,   3.57142857e-01],\n       [ -7.23378800e-01,   5.63028320e-01,   3.57142857e-01],\n       [ -7.89140509e-01,   6.14212713e-01,   5.00000000e-01],\n       [ -7.23378800e-01,   5.63028320e-01,   5.00000000e-01],\n       [ -8.79473751e-01,   4.75947393e-01,   3.57142857e-01],\n       [ -8.06184272e-01,   4.36285110e-01,   3.57142857e-01],\n       [ -8.79473751e-01,   4.75947393e-01,   5.00000000e-01],\n       [ -8.06184272e-01,   4.36285110e-01,   5.00000000e-01],\n       [ -9.45817242e-01,   3.24699469e-01,   3.57142857e-01],\n       [ -8.66999138e-01,   2.97641180e-01,   3.57142857e-01],\n       [ -9.45817242e-01,   3.24699469e-01,   5.00000000e-01],\n       [ -8.66999138e-01,   2.97641180e-01,   5.00000000e-01],\n       [ -9.86361303e-01,   1.64594590e-01,   3.57142857e-01],\n       [ -9.04164528e-01,   1.50878374e-01,   3.57142857e-01],\n       [ -9.86361303e-01,   1.64594590e-01,   5.00000000e-01],\n       [ -9.04164528e-01,   1.50878374e-01,   5.00000000e-01],\n       [ -1.00000000e+00,   8.74747714e-17,   3.57142857e-01],\n       [ -9.16666667e-01,   7.64102399e-17,   3.57142857e-01],\n       [ -1.00000000e+00,   1.22464680e-16,   5.00000000e-01],\n       [ -9.16666667e-01,   1.07755096e-16,   5.00000000e-01],\n       [ -9.86361303e-01,  -1.64594590e-01,   3.57142857e-01],\n       [ -9.04164528e-01,  -1.50878374e-01,   3.57142857e-01],\n       [ -9.86361303e-01,  -1.64594590e-01,   5.00000000e-01],\n       [ -9.04164528e-01,  -1.50878374e-01,   5.00000000e-01],\n       [ -6.57617091e-01,   5.11843927e-01,   3.57142857e-01],\n       [ -6.57617091e-01,   5.11843927e-01,   5.00000000e-01],\n       [ -7.32894793e-01,   3.96622828e-01,   3.57142857e-01],\n       [ -7.32894793e-01,   3.96622828e-01,   5.00000000e-01],\n       [ -7.88181035e-01,   2.70582891e-01,   3.57142857e-01],\n       [ -7.88181035e-01,   2.70582891e-01,   5.00000000e-01],\n       [ -8.21967753e-01,   1.37162159e-01,   3.57142857e-01],\n       [ -8.21967753e-01,   1.37162159e-01,   5.00000000e-01],\n       [ -8.33333333e-01,   6.54603924e-17,   3.57142857e-01],\n       [ -8.33333333e-01,   9.30455114e-17,   5.00000000e-01],\n       [ -8.21967753e-01,  -1.37162159e-01,   3.57142857e-01],\n       [ -8.21967753e-01,  -1.37162159e-01,   5.00000000e-01],\n       [ -5.91855382e-01,   4.60659535e-01,   3.57142857e-01],\n       [ -5.91855382e-01,   4.60659535e-01,   5.00000000e-01],\n       [ -6.59605313e-01,   3.56960545e-01,   3.57142857e-01],\n       [ -6.59605313e-01,   3.56960545e-01,   5.00000000e-01],\n       [ -7.09362931e-01,   2.43524602e-01,   3.57142857e-01],\n       [ -7.09362931e-01,   2.43524602e-01,   5.00000000e-01],\n       [ -7.39770978e-01,   1.23445943e-01,   3.57142857e-01],\n       [ -7.39770978e-01,   1.23445943e-01,   5.00000000e-01],\n       [ -7.50000000e-01,   5.42913931e-17,   3.57142857e-01],\n       [ -7.50000000e-01,   7.83359271e-17,   5.00000000e-01],\n       [ -7.39770978e-01,  -1.23445943e-01,   3.57142857e-01],\n       [ -7.39770978e-01,  -1.23445943e-01,   5.00000000e-01],\n       [ -5.26093673e-01,   4.09475142e-01,   3.57142857e-01],\n       [ -5.26093673e-01,   4.09475142e-01,   5.00000000e-01],\n       [ -5.86315834e-01,   3.17298262e-01,   3.57142857e-01],\n       [ -5.86315834e-01,   3.17298262e-01,   5.00000000e-01],\n       [ -6.30544828e-01,   2.16466313e-01,   3.57142857e-01],\n       [ -6.30544828e-01,   2.16466313e-01,   5.00000000e-01],\n       [ -6.57574202e-01,   1.09729727e-01,   3.57142857e-01],\n       [ -6.57574202e-01,   1.09729727e-01,   5.00000000e-01],\n       [ -6.66666667e-01,   4.30651628e-17,   3.57142857e-01],\n       [ -6.66666667e-01,   6.36263428e-17,   5.00000000e-01],\n       [ -6.57574202e-01,  -1.09729727e-01,   3.57142857e-01],\n       [ -6.57574202e-01,  -1.09729727e-01,   5.00000000e-01],\n       [ -4.60331964e-01,   3.58290749e-01,   3.57142857e-01],\n       [ -4.60331964e-01,   3.58290749e-01,   5.00000000e-01],\n       [ -5.13026355e-01,   2.77635979e-01,   3.57142857e-01],\n       [ -5.13026355e-01,   2.77635979e-01,   5.00000000e-01],\n       [ -5.51726724e-01,   1.89408024e-01,   3.57142857e-01],\n       [ -5.51726724e-01,   1.89408024e-01,   5.00000000e-01],\n       [ -5.75377427e-01,   9.60135110e-02,   3.57142857e-01],\n       [ -5.75377427e-01,   9.60135110e-02,   5.00000000e-01],\n       [ -5.83333333e-01,   3.18758200e-17,   3.57142857e-01],\n       [ -5.83333333e-01,   4.89167585e-17,   5.00000000e-01],\n       [ -5.75377427e-01,  -9.60135110e-02,   3.57142857e-01],\n       [ -5.75377427e-01,  -9.60135110e-02,   5.00000000e-01],\n       [ -3.94570255e-01,   3.07106356e-01,   3.57142857e-01],\n       [ -3.94570255e-01,   3.07106356e-01,   5.00000000e-01],\n       [ -4.39736876e-01,   2.37973697e-01,   3.57142857e-01],\n       [ -4.39736876e-01,   2.37973697e-01,   5.00000000e-01],\n       [ -4.72908621e-01,   1.62349735e-01,   3.57142857e-01],\n       [ -4.72908621e-01,   1.62349735e-01,   5.00000000e-01],\n       [ -4.93180652e-01,   8.22972951e-02,   3.57142857e-01],\n       [ -4.93180652e-01,   8.22972951e-02,   5.00000000e-01],\n       [ -5.00000000e-01,   2.07780968e-17,   3.57142857e-01],\n       [ -5.00000000e-01,   3.42071742e-17,   5.00000000e-01],\n       [ -4.93180652e-01,  -8.22972951e-02,   3.57142857e-01],\n       [ -4.93180652e-01,  -8.22972951e-02,   5.00000000e-01],\n       [ -4.20156583e-01,   7.22539112e-02,   3.57142857e-01],\n       [ -4.11228248e-01,   1.41174836e-01,   3.57142857e-01],\n       [ -4.20156583e-01,   7.22539112e-02,   5.00000000e-01],\n       [ -4.11228248e-01,   1.41174836e-01,   5.00000000e-01],\n       [ -4.20116462e-01,   1.83641595e-03,   3.57142857e-01],\n       [ -4.20116462e-01,   1.83641595e-03,   5.00000000e-01],\n       [ -4.10983876e-01,  -6.85810793e-02,   3.57142857e-01],\n       [ -4.10983876e-01,  -6.85810793e-02,   5.00000000e-01],\n       [ -3.47132513e-01,   6.22105272e-02,   3.57142857e-01],\n       [ -3.49547876e-01,   1.19999937e-01,   3.57142857e-01],\n       [ -3.47132513e-01,   6.22105272e-02,   5.00000000e-01],\n       [ -3.49547876e-01,   1.19999937e-01,   5.00000000e-01],\n       [ -3.40232923e-01,   3.67283191e-03,   3.57142857e-01],\n       [ -3.40232923e-01,   3.67283191e-03,   5.00000000e-01],\n       [ -3.28787101e-01,  -5.48648634e-02,   3.57142857e-01],\n       [ -3.28787101e-01,  -5.48648634e-02,   5.00000000e-01],\n       [ -2.74108444e-01,   5.21671433e-02,   3.57142857e-01],\n       [ -2.87867503e-01,   9.88250387e-02,   3.57142857e-01],\n       [ -2.74108444e-01,   5.21671433e-02,   5.00000000e-01],\n       [ -2.87867503e-01,   9.88250387e-02,   5.00000000e-01],\n       [ -2.60349385e-01,   5.50924786e-03,   3.57142857e-01],\n       [ -2.60349385e-01,   5.50924786e-03,   5.00000000e-01],\n       [ -2.46590326e-01,  -4.11486476e-02,   3.57142857e-01],\n       [ -2.46590326e-01,  -4.11486476e-02,   5.00000000e-01],\n       [ -1.64393551e-01,  -2.74324317e-02,   3.57142857e-01],\n       [ -1.92379634e-01,   2.41096072e-02,   3.57142857e-01],\n       [ -1.64393551e-01,  -2.74324317e-02,   5.00000000e-01],\n       [ -1.92379634e-01,   2.41096072e-02,   5.00000000e-01],\n       [ -8.21967753e-02,  -1.37162159e-02,   3.57142857e-01],\n       [ -1.24409882e-01,   4.27099665e-02,   3.57142857e-01],\n       [ -8.21967753e-02,  -1.37162159e-02,   5.00000000e-01],\n       [ -1.24409882e-01,   4.27099665e-02,   5.00000000e-01],\n       [ -2.20365717e-01,   7.56516461e-02,   3.57142857e-01],\n       [ -2.20365717e-01,   7.56516461e-02,   5.00000000e-01],\n       [ -1.66622989e-01,   9.91361489e-02,   3.57142857e-01],\n       [ -1.66622989e-01,   9.91361489e-02,   5.00000000e-01],\n       [ -2.48351800e-01,   1.27193685e-01,   3.57142857e-01],\n       [ -2.48351800e-01,   1.27193685e-01,   5.00000000e-01],\n       [ -2.08836096e-01,   1.55562331e-01,   3.57142857e-01],\n       [ -2.08836096e-01,   1.55562331e-01,   5.00000000e-01],\n       [ -2.70747482e-01,   2.06077006e-01,   3.57142857e-01],\n       [ -2.70747482e-01,   2.06077006e-01,   5.00000000e-01],\n       [ -3.32658869e-01,   2.56591681e-01,   3.57142857e-01],\n       [ -3.32658869e-01,   2.56591681e-01,   5.00000000e-01],\n       [ -3.12146825e-01,   1.64120356e-01,   3.57142857e-01],\n       [ -3.12146825e-01,   1.64120356e-01,   5.00000000e-01],\n       [ -3.75941850e-01,   2.01047026e-01,   3.57142857e-01],\n       [ -3.75941850e-01,   2.01047026e-01,   5.00000000e-01],\n       [ -9.45817242e-01,  -3.24699469e-01,   3.57142857e-01],\n       [ -8.66999138e-01,  -2.97641180e-01,   3.57142857e-01],\n       [ -9.45817242e-01,  -3.24699469e-01,   5.00000000e-01],\n       [ -8.66999138e-01,  -2.97641180e-01,   5.00000000e-01],\n       [ -8.79473751e-01,  -4.75947393e-01,   3.57142857e-01],\n       [ -8.06184272e-01,  -4.36285110e-01,   3.57142857e-01],\n       [ -8.79473751e-01,  -4.75947393e-01,   5.00000000e-01],\n       [ -8.06184272e-01,  -4.36285110e-01,   5.00000000e-01],\n       [ -7.89140509e-01,  -6.14212713e-01,   3.57142857e-01],\n       [ -7.23378800e-01,  -5.63028320e-01,   3.57142857e-01],\n       [ -7.89140509e-01,  -6.14212713e-01,   5.00000000e-01],\n       [ -7.23378800e-01,  -5.63028320e-01,   5.00000000e-01],\n       [ -6.77281572e-01,  -7.35723911e-01,   3.57142857e-01],\n       [ -6.20841441e-01,  -6.74413585e-01,   3.57142857e-01],\n       [ -6.77281572e-01,  -7.35723911e-01,   5.00000000e-01],\n       [ -6.20841441e-01,  -6.74413585e-01,   5.00000000e-01],\n       [ -5.46948158e-01,  -8.37166478e-01,   3.57142857e-01],\n       [ -5.01369145e-01,  -7.67402605e-01,   3.57142857e-01],\n       [ -5.46948158e-01,  -8.37166478e-01,   5.00000000e-01],\n       [ -5.01369145e-01,  -7.67402605e-01,   5.00000000e-01],\n       [ -4.01695425e-01,  -9.15773327e-01,   3.57142857e-01],\n       [ -3.68220806e-01,  -8.39458883e-01,   3.57142857e-01],\n       [ -4.01695425e-01,  -9.15773327e-01,   5.00000000e-01],\n       [ -3.68220806e-01,  -8.39458883e-01,   5.00000000e-01],\n       [ -7.88181035e-01,  -2.70582891e-01,   3.57142857e-01],\n       [ -7.88181035e-01,  -2.70582891e-01,   5.00000000e-01],\n       [ -7.32894793e-01,  -3.96622828e-01,   3.57142857e-01],\n       [ -7.32894793e-01,  -3.96622828e-01,   5.00000000e-01],\n       [ -6.57617091e-01,  -5.11843927e-01,   3.57142857e-01],\n       [ -6.57617091e-01,  -5.11843927e-01,   5.00000000e-01],\n       [ -5.64401310e-01,  -6.13103259e-01,   3.57142857e-01],\n       [ -5.64401310e-01,  -6.13103259e-01,   5.00000000e-01],\n       [ -4.55790132e-01,  -6.97638732e-01,   3.57142857e-01],\n       [ -4.55790132e-01,  -6.97638732e-01,   5.00000000e-01],\n       [ -3.34746187e-01,  -7.63144439e-01,   3.57142857e-01],\n       [ -3.34746187e-01,  -7.63144439e-01,   5.00000000e-01],\n       [ -7.09362931e-01,  -2.43524602e-01,   3.57142857e-01],\n       [ -7.09362931e-01,  -2.43524602e-01,   5.00000000e-01],\n       [ -6.59605313e-01,  -3.56960545e-01,   3.57142857e-01],\n       [ -6.59605313e-01,  -3.56960545e-01,   5.00000000e-01],\n       [ -5.91855382e-01,  -4.60659535e-01,   3.57142857e-01],\n       [ -5.91855382e-01,  -4.60659535e-01,   5.00000000e-01],\n       [ -5.07961179e-01,  -5.51792933e-01,   3.57142857e-01],\n       [ -5.07961179e-01,  -5.51792933e-01,   5.00000000e-01],\n       [ -4.10211119e-01,  -6.27874859e-01,   3.57142857e-01],\n       [ -4.10211119e-01,  -6.27874859e-01,   5.00000000e-01],\n       [ -3.01271568e-01,  -6.86829995e-01,   3.57142857e-01],\n       [ -3.01271568e-01,  -6.86829995e-01,   5.00000000e-01],\n       [ -6.30544828e-01,  -2.16466313e-01,   3.57142857e-01],\n       [ -6.30544828e-01,  -2.16466313e-01,   5.00000000e-01],\n       [ -5.86315834e-01,  -3.17298262e-01,   3.57142857e-01],\n       [ -5.86315834e-01,  -3.17298262e-01,   5.00000000e-01],\n       [ -5.26093673e-01,  -4.09475142e-01,   3.57142857e-01],\n       [ -5.26093673e-01,  -4.09475142e-01,   5.00000000e-01],\n       [ -4.51521048e-01,  -4.90482607e-01,   3.57142857e-01],\n       [ -4.51521048e-01,  -4.90482607e-01,   5.00000000e-01],\n       [ -3.64632105e-01,  -5.58110986e-01,   3.57142857e-01],\n       [ -3.64632105e-01,  -5.58110986e-01,   5.00000000e-01],\n       [ -2.67796950e-01,  -6.10515551e-01,   3.57142857e-01],\n       [ -2.67796950e-01,  -6.10515551e-01,   5.00000000e-01],\n       [ -5.51726724e-01,  -1.89408024e-01,   3.57142857e-01],\n       [ -5.51726724e-01,  -1.89408024e-01,   5.00000000e-01],\n       [ -5.13026355e-01,  -2.77635979e-01,   3.57142857e-01],\n       [ -5.13026355e-01,  -2.77635979e-01,   5.00000000e-01],\n       [ -4.60331964e-01,  -3.58290749e-01,   3.57142857e-01],\n       [ -4.60331964e-01,  -3.58290749e-01,   5.00000000e-01],\n       [ -3.95080917e-01,  -4.29172281e-01,   3.57142857e-01],\n       [ -3.95080917e-01,  -4.29172281e-01,   5.00000000e-01],\n       [ -3.19053092e-01,  -4.88347112e-01,   3.57142857e-01],\n       [ -3.19053092e-01,  -4.88347112e-01,   5.00000000e-01],\n       [ -2.34322331e-01,  -5.34201107e-01,   3.57142857e-01],\n       [ -2.34322331e-01,  -5.34201107e-01,   5.00000000e-01],\n       [ -4.72908621e-01,  -1.62349735e-01,   3.57142857e-01],\n       [ -4.72908621e-01,  -1.62349735e-01,   5.00000000e-01],\n       [ -4.39736876e-01,  -2.37973697e-01,   3.57142857e-01],\n       [ -4.39736876e-01,  -2.37973697e-01,   5.00000000e-01],\n       [ -3.94570255e-01,  -3.07106356e-01,   3.57142857e-01],\n       [ -3.94570255e-01,  -3.07106356e-01,   5.00000000e-01],\n       [ -3.38640786e-01,  -3.67861955e-01,   3.57142857e-01],\n       [ -3.38640786e-01,  -3.67861955e-01,   5.00000000e-01],\n       [ -2.73474079e-01,  -4.18583239e-01,   3.57142857e-01],\n       [ -2.73474079e-01,  -4.18583239e-01,   5.00000000e-01],\n       [ -2.00847712e-01,  -4.57886663e-01,   3.57142857e-01],\n       [ -2.00847712e-01,  -4.57886663e-01,   5.00000000e-01],\n       [ -2.90292421e-01,  -3.12221863e-01,   3.57142857e-01],\n       [ -3.43107373e-01,  -2.67051188e-01,   3.57142857e-01],\n       [ -2.90292421e-01,  -3.12221863e-01,   5.00000000e-01],\n       [ -3.43107373e-01,  -2.67051188e-01,   5.00000000e-01],\n       [ -2.31319311e-01,  -3.50702994e-01,   3.57142857e-01],\n       [ -2.31319311e-01,  -3.50702994e-01,   5.00000000e-01],\n       [ -1.67373094e-01,  -3.81572219e-01,   3.57142857e-01],\n       [ -1.67373094e-01,  -3.81572219e-01,   5.00000000e-01],\n       [ -2.41944057e-01,  -2.56581770e-01,   3.57142857e-01],\n       [ -2.91644492e-01,  -2.26996019e-01,   3.57142857e-01],\n       [ -2.41944057e-01,  -2.56581770e-01,   5.00000000e-01],\n       [ -2.91644492e-01,  -2.26996019e-01,   5.00000000e-01],\n       [ -1.89164543e-01,  -2.82822750e-01,   3.57142857e-01],\n       [ -1.89164543e-01,  -2.82822750e-01,   5.00000000e-01],\n       [ -1.33898475e-01,  -3.05257776e-01,   3.57142857e-01],\n       [ -1.33898475e-01,  -3.05257776e-01,   5.00000000e-01],\n       [ -1.93595692e-01,  -2.00941678e-01,   3.57142857e-01],\n       [ -2.40181610e-01,  -1.86940851e-01,   3.57142857e-01],\n       [ -1.93595692e-01,  -2.00941678e-01,   5.00000000e-01],\n       [ -2.40181610e-01,  -1.86940851e-01,   5.00000000e-01],\n       [ -1.47009774e-01,  -2.14942505e-01,   3.57142857e-01],\n       [ -1.47009774e-01,  -2.14942505e-01,   5.00000000e-01],\n       [ -1.00423856e-01,  -2.28943332e-01,   3.57142857e-01],\n       [ -1.00423856e-01,  -2.28943332e-01,   5.00000000e-01],\n       [ -6.69492374e-02,  -1.52628888e-01,   3.57142857e-01],\n       [ -1.25405441e-01,  -1.47867075e-01,   3.57142857e-01],\n       [ -6.69492374e-02,  -1.52628888e-01,   5.00000000e-01],\n       [ -1.25405441e-01,  -1.47867075e-01,   5.00000000e-01],\n       [ -3.34746187e-02,  -7.63144439e-02,   3.57142857e-01],\n       [ -1.03801108e-01,  -8.07916455e-02,   3.57142857e-01],\n       [ -3.34746187e-02,  -7.63144439e-02,   5.00000000e-01],\n       [ -1.03801108e-01,  -8.07916455e-02,   5.00000000e-01],\n       [ -1.83861645e-01,  -1.43105263e-01,   3.57142857e-01],\n       [ -1.83861645e-01,  -1.43105263e-01,   5.00000000e-01],\n       [ -1.74127598e-01,  -8.52688471e-02,   3.57142857e-01],\n       [ -1.74127598e-01,  -8.52688471e-02,   5.00000000e-01],\n       [ -2.42317849e-01,  -1.38343450e-01,   3.57142857e-01],\n       [ -2.42317849e-01,  -1.38343450e-01,   5.00000000e-01],\n       [ -2.44454087e-01,  -8.97460487e-02,   3.57142857e-01],\n       [ -2.44454087e-01,  -8.97460487e-02,   5.00000000e-01],\n       [ -3.20605599e-01,  -1.13947277e-01,   3.57142857e-01],\n       [ -3.20605599e-01,  -1.13947277e-01,   5.00000000e-01],\n       [ -3.96757110e-01,  -1.38148506e-01,   3.57142857e-01],\n       [ -3.96757110e-01,  -1.38148506e-01,   5.00000000e-01],\n       [ -3.08124191e-01,  -1.71553532e-01,   3.57142857e-01],\n       [ -3.08124191e-01,  -1.71553532e-01,   5.00000000e-01],\n       [ -3.73930533e-01,  -2.04763614e-01,   3.57142857e-01],\n       [ -3.73930533e-01,  -2.04763614e-01,   5.00000000e-01],\n       [ -2.45485487e-01,  -9.69400266e-01,   3.57142857e-01],\n       [ -2.25028363e-01,  -8.88616910e-01,   3.57142857e-01],\n       [ -2.45485487e-01,  -9.69400266e-01,   5.00000000e-01],\n       [ -2.25028363e-01,  -8.88616910e-01,   5.00000000e-01],\n       [ -8.25793455e-02,  -9.96584493e-01,   3.57142857e-01],\n       [ -7.56977333e-02,  -9.13535785e-01,   3.57142857e-01],\n       [ -8.25793455e-02,  -9.96584493e-01,   5.00000000e-01],\n       [ -7.56977333e-02,  -9.13535785e-01,   5.00000000e-01],\n       [  8.25793455e-02,  -9.96584493e-01,   3.57142857e-01],\n       [  7.56977333e-02,  -9.13535785e-01,   3.57142857e-01],\n       [  8.25793455e-02,  -9.96584493e-01,   5.00000000e-01],\n       [  7.56977333e-02,  -9.13535785e-01,   5.00000000e-01],\n       [  2.45485487e-01,  -9.69400266e-01,   3.57142857e-01],\n       [  2.25028363e-01,  -8.88616910e-01,   3.57142857e-01],\n       [  2.45485487e-01,  -9.69400266e-01,   5.00000000e-01],\n       [  2.25028363e-01,  -8.88616910e-01,   5.00000000e-01],\n       [  4.01695425e-01,  -9.15773327e-01,   3.57142857e-01],\n       [  3.68220806e-01,  -8.39458883e-01,   3.57142857e-01],\n       [  4.01695425e-01,  -9.15773327e-01,   5.00000000e-01],\n       [  3.68220806e-01,  -8.39458883e-01,   5.00000000e-01],\n       [  5.46948158e-01,  -8.37166478e-01,   3.57142857e-01],\n       [  5.01369145e-01,  -7.67402605e-01,   3.57142857e-01],\n       [  5.46948158e-01,  -8.37166478e-01,   5.00000000e-01],\n       [  5.01369145e-01,  -7.67402605e-01,   5.00000000e-01],\n       [ -2.04571239e-01,  -8.07833555e-01,   3.57142857e-01],\n       [ -2.04571239e-01,  -8.07833555e-01,   5.00000000e-01],\n       [ -6.88161212e-02,  -8.30487078e-01,   3.57142857e-01],\n       [ -6.88161212e-02,  -8.30487078e-01,   5.00000000e-01],\n       [  6.88161212e-02,  -8.30487078e-01,   3.57142857e-01],\n       [  6.88161212e-02,  -8.30487078e-01,   5.00000000e-01],\n       [  2.04571239e-01,  -8.07833555e-01,   3.57142857e-01],\n       [  2.04571239e-01,  -8.07833555e-01,   5.00000000e-01],\n       [  3.34746187e-01,  -7.63144439e-01,   3.57142857e-01],\n       [  3.34746187e-01,  -7.63144439e-01,   5.00000000e-01],\n       [  4.55790132e-01,  -6.97638732e-01,   3.57142857e-01],\n       [  4.55790132e-01,  -6.97638732e-01,   5.00000000e-01],\n       [ -1.84114115e-01,  -7.27050199e-01,   3.57142857e-01],\n       [ -1.84114115e-01,  -7.27050199e-01,   5.00000000e-01],\n       [ -6.19345091e-02,  -7.47438370e-01,   3.57142857e-01],\n       [ -6.19345091e-02,  -7.47438370e-01,   5.00000000e-01],\n       [  6.19345091e-02,  -7.47438370e-01,   3.57142857e-01],\n       [  6.19345091e-02,  -7.47438370e-01,   5.00000000e-01],\n       [  1.84114115e-01,  -7.27050199e-01,   3.57142857e-01],\n       [  1.84114115e-01,  -7.27050199e-01,   5.00000000e-01],\n       [  3.01271568e-01,  -6.86829995e-01,   3.57142857e-01],\n       [  3.01271568e-01,  -6.86829995e-01,   5.00000000e-01],\n       [  4.10211119e-01,  -6.27874859e-01,   3.57142857e-01],\n       [  4.10211119e-01,  -6.27874859e-01,   5.00000000e-01],\n       [ -1.63656991e-01,  -6.46266844e-01,   3.57142857e-01],\n       [ -1.63656991e-01,  -6.46266844e-01,   5.00000000e-01],\n       [ -5.50528970e-02,  -6.64389662e-01,   3.57142857e-01],\n       [ -5.50528970e-02,  -6.64389662e-01,   5.00000000e-01],\n       [  5.50528970e-02,  -6.64389662e-01,   3.57142857e-01],\n       [  5.50528970e-02,  -6.64389662e-01,   5.00000000e-01],\n       [  1.63656991e-01,  -6.46266844e-01,   3.57142857e-01],\n       [  1.63656991e-01,  -6.46266844e-01,   5.00000000e-01],\n       [  2.67796950e-01,  -6.10515551e-01,   3.57142857e-01],\n       [  2.67796950e-01,  -6.10515551e-01,   5.00000000e-01],\n       [  3.64632105e-01,  -5.58110986e-01,   3.57142857e-01],\n       [  3.64632105e-01,  -5.58110986e-01,   5.00000000e-01],\n       [ -1.43199867e-01,  -5.65483488e-01,   3.57142857e-01],\n       [ -1.43199867e-01,  -5.65483488e-01,   5.00000000e-01],\n       [ -4.81712849e-02,  -5.81340954e-01,   3.57142857e-01],\n       [ -4.81712849e-02,  -5.81340954e-01,   5.00000000e-01],\n       [  4.81712849e-02,  -5.81340954e-01,   3.57142857e-01],\n       [  4.81712849e-02,  -5.81340954e-01,   5.00000000e-01],\n       [  1.43199867e-01,  -5.65483488e-01,   3.57142857e-01],\n       [  1.43199867e-01,  -5.65483488e-01,   5.00000000e-01],\n       [  2.34322331e-01,  -5.34201107e-01,   3.57142857e-01],\n       [  2.34322331e-01,  -5.34201107e-01,   5.00000000e-01],\n       [  3.19053092e-01,  -4.88347112e-01,   3.57142857e-01],\n       [  3.19053092e-01,  -4.88347112e-01,   5.00000000e-01],\n       [ -1.22742744e-01,  -4.84700133e-01,   3.57142857e-01],\n       [ -1.22742744e-01,  -4.84700133e-01,   5.00000000e-01],\n       [ -4.12896727e-02,  -4.98292247e-01,   3.57142857e-01],\n       [ -4.12896727e-02,  -4.98292247e-01,   5.00000000e-01],\n       [  4.12896727e-02,  -4.98292247e-01,   3.57142857e-01],\n       [  4.12896727e-02,  -4.98292247e-01,   5.00000000e-01],\n       [  1.22742744e-01,  -4.84700133e-01,   3.57142857e-01],\n       [  1.22742744e-01,  -4.84700133e-01,   5.00000000e-01],\n       [  2.00847712e-01,  -4.57886663e-01,   3.57142857e-01],\n       [  2.00847712e-01,  -4.57886663e-01,   5.00000000e-01],\n       [  2.73474079e-01,  -4.18583239e-01,   3.57142857e-01],\n       [  2.73474079e-01,  -4.18583239e-01,   5.00000000e-01],\n       [  1.02606772e-01,  -4.13792257e-01,   3.57142857e-01],\n       [  3.59043567e-02,  -4.33301147e-01,   3.57142857e-01],\n       [  1.02606772e-01,  -4.13792257e-01,   5.00000000e-01],\n       [  3.59043567e-02,  -4.33301147e-01,   5.00000000e-01],\n       [  1.67077120e-01,  -3.85469130e-01,   3.57142857e-01],\n       [  1.67077120e-01,  -3.85469130e-01,   5.00000000e-01],\n       [  2.27895066e-01,  -3.48819366e-01,   3.57142857e-01],\n       [  2.27895066e-01,  -3.48819366e-01,   5.00000000e-01],\n       [  8.24708009e-02,  -3.42884381e-01,   3.57142857e-01],\n       [  3.05190406e-02,  -3.68310047e-01,   3.57142857e-01],\n       [  8.24708009e-02,  -3.42884381e-01,   5.00000000e-01],\n       [  3.05190406e-02,  -3.68310047e-01,   5.00000000e-01],\n       [  1.33306527e-01,  -3.13051596e-01,   3.57142857e-01],\n       [  1.33306527e-01,  -3.13051596e-01,   5.00000000e-01],\n       [  1.82316053e-01,  -2.79055493e-01,   3.57142857e-01],\n       [  1.82316053e-01,  -2.79055493e-01,   5.00000000e-01],\n       [  6.23348295e-02,  -2.71976505e-01,   3.57142857e-01],\n       [  2.51337245e-02,  -3.03318947e-01,   3.57142857e-01],\n       [  6.23348295e-02,  -2.71976505e-01,   5.00000000e-01],\n       [  2.51337245e-02,  -3.03318947e-01,   5.00000000e-01],\n       [  9.95359345e-02,  -2.40634062e-01,   3.57142857e-01],\n       [  9.95359345e-02,  -2.40634062e-01,   5.00000000e-01],\n       [  1.36737040e-01,  -2.09291620e-01,   3.57142857e-01],\n       [  1.36737040e-01,  -2.09291620e-01,   5.00000000e-01],\n       [  9.11580264e-02,  -1.39527746e-01,   3.57142857e-01],\n       [  5.51990834e-02,  -1.85860856e-01,   3.57142857e-01],\n       [  9.11580264e-02,  -1.39527746e-01,   5.00000000e-01],\n       [  5.51990834e-02,  -1.85860856e-01,   5.00000000e-01],\n       [  4.55790132e-02,  -6.97638732e-02,   3.57142857e-01],\n       [  1.08622324e-02,  -1.31087650e-01,   3.57142857e-01],\n       [  4.55790132e-02,  -6.97638732e-02,   5.00000000e-01],\n       [  1.08622324e-02,  -1.31087650e-01,   5.00000000e-01],\n       [  1.92401405e-02,  -2.32193966e-01,   3.57142857e-01],\n       [  1.92401405e-02,  -2.32193966e-01,   5.00000000e-01],\n       [ -2.38545485e-02,  -1.92411427e-01,   3.57142857e-01],\n       [ -2.38545485e-02,  -1.92411427e-01,   5.00000000e-01],\n       [ -1.67188024e-02,  -2.78527075e-01,   3.57142857e-01],\n       [ -1.67188024e-02,  -2.78527075e-01,   5.00000000e-01],\n       [ -5.85713293e-02,  -2.53735203e-01,   3.57142857e-01],\n       [ -5.85713293e-02,  -2.53735203e-01,   5.00000000e-01],\n       [ -7.99618007e-02,  -3.30723513e-01,   3.57142857e-01],\n       [ -7.99618007e-02,  -3.30723513e-01,   5.00000000e-01],\n       [ -1.01352272e-01,  -4.07711823e-01,   3.57142857e-01],\n       [ -1.01352272e-01,  -4.07711823e-01,   5.00000000e-01],\n       [ -2.49090925e-02,  -3.51782132e-01,   3.57142857e-01],\n       [ -2.49090925e-02,  -3.51782132e-01,   5.00000000e-01],\n       [ -3.30993826e-02,  -4.25037189e-01,   3.57142857e-01],\n       [ -3.30993826e-02,  -4.25037189e-01,   5.00000000e-01],\n       [  6.77281572e-01,  -7.35723911e-01,   3.57142857e-01],\n       [  6.20841441e-01,  -6.74413585e-01,   3.57142857e-01],\n       [  6.77281572e-01,  -7.35723911e-01,   5.00000000e-01],\n       [  6.20841441e-01,  -6.74413585e-01,   5.00000000e-01],\n       [  7.89140509e-01,  -6.14212713e-01,   3.57142857e-01],\n       [  7.23378800e-01,  -5.63028320e-01,   3.57142857e-01],\n       [  7.89140509e-01,  -6.14212713e-01,   5.00000000e-01],\n       [  7.23378800e-01,  -5.63028320e-01,   5.00000000e-01],\n       [  8.79473751e-01,  -4.75947393e-01,   3.57142857e-01],\n       [  8.06184272e-01,  -4.36285110e-01,   3.57142857e-01],\n       [  8.79473751e-01,  -4.75947393e-01,   5.00000000e-01],\n       [  8.06184272e-01,  -4.36285110e-01,   5.00000000e-01],\n       [  9.45817242e-01,  -3.24699469e-01,   3.57142857e-01],\n       [  8.66999138e-01,  -2.97641180e-01,   3.57142857e-01],\n       [  9.45817242e-01,  -3.24699469e-01,   5.00000000e-01],\n       [  8.66999138e-01,  -2.97641180e-01,   5.00000000e-01],\n       [  9.86361303e-01,  -1.64594590e-01,   3.57142857e-01],\n       [  9.04164528e-01,  -1.50878374e-01,   3.57142857e-01],\n       [  9.86361303e-01,  -1.64594590e-01,   5.00000000e-01],\n       [  9.04164528e-01,  -1.50878374e-01,   5.00000000e-01],\n       [  5.64401310e-01,  -6.13103259e-01,   3.57142857e-01],\n       [  5.64401310e-01,  -6.13103259e-01,   5.00000000e-01],\n       [  6.57617091e-01,  -5.11843927e-01,   3.57142857e-01],\n       [  6.57617091e-01,  -5.11843927e-01,   5.00000000e-01],\n       [  7.32894793e-01,  -3.96622828e-01,   3.57142857e-01],\n       [  7.32894793e-01,  -3.96622828e-01,   5.00000000e-01],\n       [  7.88181035e-01,  -2.70582891e-01,   3.57142857e-01],\n       [  7.88181035e-01,  -2.70582891e-01,   5.00000000e-01],\n       [  8.21967753e-01,  -1.37162159e-01,   3.57142857e-01],\n       [  8.21967753e-01,  -1.37162159e-01,   5.00000000e-01],\n       [  5.07961179e-01,  -5.51792933e-01,   3.57142857e-01],\n       [  5.07961179e-01,  -5.51792933e-01,   5.00000000e-01],\n       [  5.91855382e-01,  -4.60659535e-01,   3.57142857e-01],\n       [  5.91855382e-01,  -4.60659535e-01,   5.00000000e-01],\n       [  6.59605313e-01,  -3.56960545e-01,   3.57142857e-01],\n       [  6.59605313e-01,  -3.56960545e-01,   5.00000000e-01],\n       [  7.09362931e-01,  -2.43524602e-01,   3.57142857e-01],\n       [  7.09362931e-01,  -2.43524602e-01,   5.00000000e-01],\n       [  7.39770978e-01,  -1.23445943e-01,   3.57142857e-01],\n       [  7.39770978e-01,  -1.23445943e-01,   5.00000000e-01],\n       [  4.51521048e-01,  -4.90482607e-01,   3.57142857e-01],\n       [  4.51521048e-01,  -4.90482607e-01,   5.00000000e-01],\n       [  5.26093673e-01,  -4.09475142e-01,   3.57142857e-01],\n       [  5.26093673e-01,  -4.09475142e-01,   5.00000000e-01],\n       [  5.86315834e-01,  -3.17298262e-01,   3.57142857e-01],\n       [  5.86315834e-01,  -3.17298262e-01,   5.00000000e-01],\n       [  6.30544828e-01,  -2.16466313e-01,   3.57142857e-01],\n       [  6.30544828e-01,  -2.16466313e-01,   5.00000000e-01],\n       [  6.57574202e-01,  -1.09729727e-01,   3.57142857e-01],\n       [  6.57574202e-01,  -1.09729727e-01,   5.00000000e-01],\n       [  3.95080917e-01,  -4.29172281e-01,   3.57142857e-01],\n       [  3.95080917e-01,  -4.29172281e-01,   5.00000000e-01],\n       [  4.60331964e-01,  -3.58290749e-01,   3.57142857e-01],\n       [  4.60331964e-01,  -3.58290749e-01,   5.00000000e-01],\n       [  5.13026355e-01,  -2.77635979e-01,   3.57142857e-01],\n       [  5.13026355e-01,  -2.77635979e-01,   5.00000000e-01],\n       [  5.51726724e-01,  -1.89408024e-01,   3.57142857e-01],\n       [  5.51726724e-01,  -1.89408024e-01,   5.00000000e-01],\n       [  5.75377427e-01,  -9.60135110e-02,   3.57142857e-01],\n       [  5.75377427e-01,  -9.60135110e-02,   5.00000000e-01],\n       [  3.38640786e-01,  -3.67861955e-01,   3.57142857e-01],\n       [  3.38640786e-01,  -3.67861955e-01,   5.00000000e-01],\n       [  3.94570255e-01,  -3.07106356e-01,   3.57142857e-01],\n       [  3.94570255e-01,  -3.07106356e-01,   5.00000000e-01],\n       [  4.39736876e-01,  -2.37973697e-01,   3.57142857e-01],\n       [  4.39736876e-01,  -2.37973697e-01,   5.00000000e-01],\n       [  4.72908621e-01,  -1.62349735e-01,   3.57142857e-01],\n       [  4.72908621e-01,  -1.62349735e-01,   5.00000000e-01],\n       [  4.93180652e-01,  -8.22972951e-02,   3.57142857e-01],\n       [  4.93180652e-01,  -8.22972951e-02,   5.00000000e-01],\n       [  4.02533591e-01,  -1.40423963e-01,   3.57142857e-01],\n       [  3.82383017e-01,  -2.06935340e-01,   3.57142857e-01],\n       [  4.02533591e-01,  -1.40423963e-01,   5.00000000e-01],\n       [  3.82383017e-01,  -2.06935340e-01,   5.00000000e-01],\n       [  4.14084357e-01,  -7.09602665e-02,   3.57142857e-01],\n       [  4.14084357e-01,  -7.09602665e-02,   5.00000000e-01],\n       [  3.32158562e-01,  -1.18498191e-01,   3.57142857e-01],\n       [  3.25029158e-01,  -1.75896984e-01,   3.57142857e-01],\n       [  3.32158562e-01,  -1.18498191e-01,   5.00000000e-01],\n       [  3.25029158e-01,  -1.75896984e-01,   5.00000000e-01],\n       [  3.34988061e-01,  -5.96232379e-02,   3.57142857e-01],\n       [  3.34988061e-01,  -5.96232379e-02,   5.00000000e-01],\n       [  2.61783533e-01,  -9.65724185e-02,   3.57142857e-01],\n       [  2.67675299e-01,  -1.44858628e-01,   3.57142857e-01],\n       [  2.61783533e-01,  -9.65724185e-02,   5.00000000e-01],\n       [  2.67675299e-01,  -1.44858628e-01,   5.00000000e-01],\n       [  2.55891766e-01,  -4.82862093e-02,   3.57142857e-01],\n       [  2.55891766e-01,  -4.82862093e-02,   5.00000000e-01],\n       [  1.85787515e-01,  -5.54454306e-02,   3.57142857e-01],\n       [  1.85787515e-01,  -5.54454306e-02,   5.00000000e-01],\n       [  1.15683264e-01,  -6.26046519e-02,   3.57142857e-01],\n       [  1.15683264e-01,  -6.26046519e-02,   5.00000000e-01],\n       [  2.04908364e-01,  -1.10890861e-01,   3.57142857e-01],\n       [  2.04908364e-01,  -1.10890861e-01,   5.00000000e-01],\n       [  1.48033195e-01,  -1.25209304e-01,   3.57142857e-01],\n       [  1.48033195e-01,  -1.25209304e-01,   5.00000000e-01],\n       [  2.24029213e-01,  -1.66336292e-01,   3.57142857e-01],\n       [  2.24029213e-01,  -1.66336292e-01,   5.00000000e-01],\n       [  1.80383126e-01,  -1.87813956e-01,   3.57142857e-01],\n       [  1.80383126e-01,  -1.87813956e-01,   5.00000000e-01],\n       [  2.33135679e-01,  -2.47829956e-01,   3.57142857e-01],\n       [  2.33135679e-01,  -2.47829956e-01,   5.00000000e-01],\n       [  2.85888233e-01,  -3.07845955e-01,   3.57142857e-01],\n       [  2.85888233e-01,  -3.07845955e-01,   5.00000000e-01],\n       [  2.80876227e-01,  -2.13259647e-01,   3.57142857e-01],\n       [  2.80876227e-01,  -2.13259647e-01,   5.00000000e-01],\n       [  3.37723241e-01,  -2.60183001e-01,   3.57142857e-01],\n       [  3.37723241e-01,  -2.60183001e-01,   5.00000000e-01],\n       [  1.00000000e+00,   0.00000000e+00,   2.14285714e-01],\n       [  9.86361303e-01,   1.64594590e-01,   2.14285714e-01],\n       [  9.04164528e-01,   1.50878374e-01,   2.14285714e-01],\n       [  9.16666667e-01,   7.92157468e-17,   2.14285714e-01],\n       [  9.45817242e-01,   3.24699469e-01,   2.14285714e-01],\n       [  8.66999138e-01,   2.97641180e-01,   2.14285714e-01],\n       [  8.79473751e-01,   4.75947393e-01,   2.14285714e-01],\n       [  8.06184272e-01,   4.36285110e-01,   2.14285714e-01],\n       [  7.89140509e-01,   6.14212713e-01,   2.14285714e-01],\n       [  7.23378800e-01,   5.63028320e-01,   2.14285714e-01],\n       [  6.77281572e-01,   7.35723911e-01,   2.14285714e-01],\n       [  6.20841441e-01,   6.74413585e-01,   2.14285714e-01],\n       [  5.46948158e-01,   8.37166478e-01,   2.14285714e-01],\n       [  5.01369145e-01,   7.67402605e-01,   2.14285714e-01],\n       [  4.01695425e-01,   9.15773327e-01,   2.14285714e-01],\n       [  3.68220806e-01,   8.39458883e-01,   2.14285714e-01],\n       [  8.21967753e-01,   1.37162159e-01,   2.14285714e-01],\n       [  8.33333333e-01,   1.09189934e-16,   2.14285714e-01],\n       [  7.88181035e-01,   2.70582891e-01,   2.14285714e-01],\n       [  7.32894793e-01,   3.96622828e-01,   2.14285714e-01],\n       [  6.57617091e-01,   5.11843927e-01,   2.14285714e-01],\n       [  5.64401310e-01,   6.13103259e-01,   2.14285714e-01],\n       [  4.55790132e-01,   6.97638732e-01,   2.14285714e-01],\n       [  3.34746187e-01,   7.63144439e-01,   2.14285714e-01],\n       [  7.39770978e-01,   1.23445943e-01,   2.14285714e-01],\n       [  7.50000000e-01,   1.08072838e-16,   2.14285714e-01],\n       [  7.09362931e-01,   2.43524602e-01,   2.14285714e-01],\n       [  6.59605313e-01,   3.56960545e-01,   2.14285714e-01],\n       [  5.91855382e-01,   4.60659535e-01,   2.14285714e-01],\n       [  5.07961179e-01,   5.51792933e-01,   2.14285714e-01],\n       [  4.10211119e-01,   6.27874859e-01,   2.14285714e-01],\n       [  3.01271568e-01,   6.86829995e-01,   2.14285714e-01],\n       [  6.57574202e-01,   1.09729727e-01,   2.14285714e-01],\n       [  6.66666667e-01,   9.87992924e-17,   2.14285714e-01],\n       [  6.30544828e-01,   2.16466313e-01,   2.14285714e-01],\n       [  5.86315834e-01,   3.17298262e-01,   2.14285714e-01],\n       [  5.26093673e-01,   4.09475142e-01,   2.14285714e-01],\n       [  4.51521048e-01,   4.90482607e-01,   2.14285714e-01],\n       [  3.64632105e-01,   5.58110986e-01,   2.14285714e-01],\n       [  2.67796950e-01,   6.10515551e-01,   2.14285714e-01],\n       [  5.75377427e-01,   9.60135110e-02,   2.14285714e-01],\n       [  5.83333333e-01,   8.67681639e-17,   2.14285714e-01],\n       [  5.51726724e-01,   1.89408024e-01,   2.14285714e-01],\n       [  5.13026355e-01,   2.77635979e-01,   2.14285714e-01],\n       [  4.60331964e-01,   3.58290749e-01,   2.14285714e-01],\n       [  3.95080917e-01,   4.29172281e-01,   2.14285714e-01],\n       [  3.19053092e-01,   4.88347112e-01,   2.14285714e-01],\n       [  2.34322331e-01,   5.34201107e-01,   2.14285714e-01],\n       [  4.93180652e-01,   8.22972951e-02,   2.14285714e-01],\n       [  5.00000000e-01,   7.34814867e-17,   2.14285714e-01],\n       [  4.72908621e-01,   1.62349735e-01,   2.14285714e-01],\n       [  4.39736876e-01,   2.37973697e-01,   2.14285714e-01],\n       [  3.94570255e-01,   3.07106356e-01,   2.14285714e-01],\n       [  3.38640786e-01,   3.67861955e-01,   2.14285714e-01],\n       [  2.73474079e-01,   4.18583239e-01,   2.14285714e-01],\n       [  2.00847712e-01,   4.57886663e-01,   2.14285714e-01],\n       [  2.92606991e-01,   3.16445261e-01,   2.14285714e-01],\n       [  3.44188184e-01,   2.67935253e-01,   2.14285714e-01],\n       [  2.34867640e-01,   3.58265725e-01,   2.14285714e-01],\n       [  1.72155182e-01,   3.92474283e-01,   2.14285714e-01],\n       [  2.46573197e-01,   2.65028567e-01,   2.14285714e-01],\n       [  2.93806114e-01,   2.28764151e-01,   2.14285714e-01],\n       [  1.96261201e-01,   2.97948211e-01,   2.14285714e-01],\n       [  1.43462652e-01,   3.27061902e-01,   2.14285714e-01],\n       [  2.00539403e-01,   2.13611872e-01,   2.14285714e-01],\n       [  2.43424043e-01,   1.89593048e-01,   2.14285714e-01],\n       [  1.57654762e-01,   2.37630697e-01,   2.14285714e-01],\n       [  1.14770121e-01,   2.61649522e-01,   2.14285714e-01],\n       [  8.60775910e-02,   1.96237141e-01,   2.14285714e-01],\n       [  1.39074405e-01,   1.78223023e-01,   2.14285714e-01],\n       [  5.73850607e-02,   1.30824761e-01,   2.14285714e-01],\n       [  1.20494048e-01,   1.18815349e-01,   2.14285714e-01],\n       [  2.86925303e-02,   6.54123805e-02,   2.14285714e-01],\n       [  1.01913690e-01,   5.94076743e-02,   2.14285714e-01],\n       [ -2.17036080e-17,  -1.20233781e-17,   2.14285714e-01],\n       [  8.33333333e-02,   2.13668869e-18,   2.14285714e-01],\n       [  1.92071219e-01,   1.60208904e-01,   2.14285714e-01],\n       [  1.83603035e-01,   1.06805936e-01,   2.14285714e-01],\n       [  1.75134851e-01,   5.34029681e-02,   2.14285714e-01],\n       [  1.66666667e-01,   1.64436543e-17,   2.14285714e-01],\n       [  2.45068032e-01,   1.42194786e-01,   2.14285714e-01],\n       [  2.46712022e-01,   9.47965238e-02,   2.14285714e-01],\n       [  2.48356011e-01,   4.73982619e-02,   2.14285714e-01],\n       [  2.50000000e-01,   3.08454053e-17,   2.14285714e-01],\n       [  3.33333333e-01,   4.52549212e-17,   2.14285714e-01],\n       [  3.29964224e-01,   5.90312730e-02,   2.14285714e-01],\n       [  4.16666667e-01,   5.95357666e-17,   2.14285714e-01],\n       [  4.11572438e-01,   7.06642841e-02,   2.14285714e-01],\n       [  3.22110888e-01,   1.17314261e-01,   2.14285714e-01],\n       [  3.97509754e-01,   1.39831998e-01,   2.14285714e-01],\n       [  3.09957647e-01,   1.74121089e-01,   2.14285714e-01],\n       [  3.74847261e-01,   2.06047393e-01,   2.14285714e-01],\n       [  2.45485487e-01,   9.69400266e-01,   2.14285714e-01],\n       [  2.25028363e-01,   8.88616910e-01,   2.14285714e-01],\n       [  8.25793455e-02,   9.96584493e-01,   2.14285714e-01],\n       [  7.56977333e-02,   9.13535785e-01,   2.14285714e-01],\n       [ -8.25793455e-02,   9.96584493e-01,   2.14285714e-01],\n       [ -7.56977333e-02,   9.13535785e-01,   2.14285714e-01],\n       [ -2.45485487e-01,   9.69400266e-01,   2.14285714e-01],\n       [ -2.25028363e-01,   8.88616910e-01,   2.14285714e-01],\n       [ -4.01695425e-01,   9.15773327e-01,   2.14285714e-01],\n       [ -3.68220806e-01,   8.39458883e-01,   2.14285714e-01],\n       [ -5.46948158e-01,   8.37166478e-01,   2.14285714e-01],\n       [ -5.01369145e-01,   7.67402605e-01,   2.14285714e-01],\n       [ -6.77281572e-01,   7.35723911e-01,   2.14285714e-01],\n       [ -6.20841441e-01,   6.74413585e-01,   2.14285714e-01],\n       [  2.04571239e-01,   8.07833555e-01,   2.14285714e-01],\n       [  6.88161212e-02,   8.30487078e-01,   2.14285714e-01],\n       [ -6.88161212e-02,   8.30487078e-01,   2.14285714e-01],\n       [ -2.04571239e-01,   8.07833555e-01,   2.14285714e-01],\n       [ -3.34746187e-01,   7.63144439e-01,   2.14285714e-01],\n       [ -4.55790132e-01,   6.97638732e-01,   2.14285714e-01],\n       [ -5.64401310e-01,   6.13103259e-01,   2.14285714e-01],\n       [  1.84114115e-01,   7.27050199e-01,   2.14285714e-01],\n       [  6.19345091e-02,   7.47438370e-01,   2.14285714e-01],\n       [ -6.19345091e-02,   7.47438370e-01,   2.14285714e-01],\n       [ -1.84114115e-01,   7.27050199e-01,   2.14285714e-01],\n       [ -3.01271568e-01,   6.86829995e-01,   2.14285714e-01],\n       [ -4.10211119e-01,   6.27874859e-01,   2.14285714e-01],\n       [ -5.07961179e-01,   5.51792933e-01,   2.14285714e-01],\n       [  1.63656991e-01,   6.46266844e-01,   2.14285714e-01],\n       [  5.50528970e-02,   6.64389662e-01,   2.14285714e-01],\n       [ -5.50528970e-02,   6.64389662e-01,   2.14285714e-01],\n       [ -1.63656991e-01,   6.46266844e-01,   2.14285714e-01],\n       [ -2.67796950e-01,   6.10515551e-01,   2.14285714e-01],\n       [ -3.64632105e-01,   5.58110986e-01,   2.14285714e-01],\n       [ -4.51521048e-01,   4.90482607e-01,   2.14285714e-01],\n       [  1.43199867e-01,   5.65483488e-01,   2.14285714e-01],\n       [  4.81712849e-02,   5.81340954e-01,   2.14285714e-01],\n       [ -4.81712849e-02,   5.81340954e-01,   2.14285714e-01],\n       [ -1.43199867e-01,   5.65483488e-01,   2.14285714e-01],\n       [ -2.34322331e-01,   5.34201107e-01,   2.14285714e-01],\n       [ -3.19053092e-01,   4.88347112e-01,   2.14285714e-01],\n       [ -3.95080917e-01,   4.29172281e-01,   2.14285714e-01],\n       [  1.22742744e-01,   4.84700133e-01,   2.14285714e-01],\n       [  4.12896727e-02,   4.98292247e-01,   2.14285714e-01],\n       [ -4.12896727e-02,   4.98292247e-01,   2.14285714e-01],\n       [ -1.22742744e-01,   4.84700133e-01,   2.14285714e-01],\n       [ -2.00847712e-01,   4.57886663e-01,   2.14285714e-01],\n       [ -2.73474079e-01,   4.18583239e-01,   2.14285714e-01],\n       [ -3.38640786e-01,   3.67861955e-01,   2.14285714e-01],\n       [ -1.02283149e-01,   4.15336195e-01,   2.14285714e-01],\n       [ -3.59859419e-02,   4.34695086e-01,   2.14285714e-01],\n       [ -1.66348287e-01,   3.87163066e-01,   2.14285714e-01],\n       [ -2.26761024e-01,   3.50663301e-01,   2.14285714e-01],\n       [ -2.82200655e-01,   3.06551629e-01,   2.14285714e-01],\n       [ -8.18235534e-02,   3.45972257e-01,   2.14285714e-01],\n       [ -3.06822110e-02,   3.71097926e-01,   2.14285714e-01],\n       [ -1.31848862e-01,   3.16439469e-01,   2.14285714e-01],\n       [ -1.80047970e-01,   2.82743363e-01,   2.14285714e-01],\n       [ -2.25760524e-01,   2.45241304e-01,   2.14285714e-01],\n       [ -6.13639584e-02,   2.76608319e-01,   2.14285714e-01],\n       [ -2.53784802e-02,   3.07500766e-01,   2.14285714e-01],\n       [ -9.73494365e-02,   2.45715872e-01,   2.14285714e-01],\n       [ -1.33334915e-01,   2.14823425e-01,   2.14285714e-01],\n       [ -1.69320393e-01,   1.83930978e-01,   2.14285714e-01],\n       [ -1.12880262e-01,   1.22620652e-01,   2.14285714e-01],\n       [ -7.93257664e-02,   1.65019743e-01,   2.14285714e-01],\n       [ -5.64401310e-02,   6.13103259e-02,   2.14285714e-01],\n       [ -2.53166180e-02,   1.15216062e-01,   2.14285714e-01],\n       [ -4.57712708e-02,   2.07418835e-01,   2.14285714e-01],\n       [  5.80689493e-03,   1.69121798e-01,   2.14285714e-01],\n       [ -1.22167752e-02,   2.49817927e-01,   2.14285714e-01],\n       [  3.69304079e-02,   2.23027534e-01,   2.14285714e-01],\n       [  2.13377203e-02,   2.92217018e-01,   2.14285714e-01],\n       [  6.80539208e-02,   2.76933270e-01,   2.14285714e-01],\n       [  8.62835284e-02,   3.46188891e-01,   2.14285714e-01],\n       [  1.04513136e-01,   4.15444512e-01,   2.14285714e-01],\n       [  2.79883711e-02,   3.60908761e-01,   2.14285714e-01],\n       [  3.46390219e-02,   4.29600504e-01,   2.14285714e-01],\n       [ -7.89140509e-01,   6.14212713e-01,   2.14285714e-01],\n       [ -7.23378800e-01,   5.63028320e-01,   2.14285714e-01],\n       [ -8.79473751e-01,   4.75947393e-01,   2.14285714e-01],\n       [ -8.06184272e-01,   4.36285110e-01,   2.14285714e-01],\n       [ -9.45817242e-01,   3.24699469e-01,   2.14285714e-01],\n       [ -8.66999138e-01,   2.97641180e-01,   2.14285714e-01],\n       [ -9.86361303e-01,   1.64594590e-01,   2.14285714e-01],\n       [ -9.04164528e-01,   1.50878374e-01,   2.14285714e-01],\n       [ -1.00000000e+00,   5.24848628e-17,   2.14285714e-01],\n       [ -9.16666667e-01,   5.61432674e-17,   2.14285714e-01],\n       [ -9.86361303e-01,  -1.64594590e-01,   2.14285714e-01],\n       [ -9.04164528e-01,  -1.50878374e-01,   2.14285714e-01],\n       [ -6.57617091e-01,   5.11843927e-01,   2.14285714e-01],\n       [ -7.32894793e-01,   3.96622828e-01,   2.14285714e-01],\n       [ -7.88181035e-01,   2.70582891e-01,   2.14285714e-01],\n       [ -8.21967753e-01,   1.37162159e-01,   2.14285714e-01],\n       [ -8.33333333e-01,   4.98053016e-17,   2.14285714e-01],\n       [ -8.21967753e-01,  -1.37162159e-01,   2.14285714e-01],\n       [ -5.91855382e-01,   4.60659535e-01,   2.14285714e-01],\n       [ -6.59605313e-01,   3.56960545e-01,   2.14285714e-01],\n       [ -7.09362931e-01,   2.43524602e-01,   2.14285714e-01],\n       [ -7.39770978e-01,   1.23445943e-01,   2.14285714e-01],\n       [ -7.50000000e-01,   4.30290320e-17,   2.14285714e-01],\n       [ -7.39770978e-01,  -1.23445943e-01,   2.14285714e-01],\n       [ -5.26093673e-01,   4.09475142e-01,   2.14285714e-01],\n       [ -5.86315834e-01,   3.17298262e-01,   2.14285714e-01],\n       [ -6.30544828e-01,   2.16466313e-01,   2.14285714e-01],\n       [ -6.57574202e-01,   1.09729727e-01,   2.14285714e-01],\n       [ -6.66666667e-01,   3.61383006e-17,   2.14285714e-01],\n       [ -6.57574202e-01,  -1.09729727e-01,   2.14285714e-01],\n       [ -4.60331964e-01,   3.58290749e-01,   2.14285714e-01],\n       [ -5.13026355e-01,   2.77635979e-01,   2.14285714e-01],\n       [ -5.51726724e-01,   1.89408024e-01,   2.14285714e-01],\n       [ -5.75377427e-01,   9.60135110e-02,   2.14285714e-01],\n       [ -5.83333333e-01,   2.93213442e-17,   2.14285714e-01],\n       [ -5.75377427e-01,  -9.60135110e-02,   2.14285714e-01],\n       [ -3.94570255e-01,   3.07106356e-01,   2.14285714e-01],\n       [ -4.39736876e-01,   2.37973697e-01,   2.14285714e-01],\n       [ -4.72908621e-01,   1.62349735e-01,   2.14285714e-01],\n       [ -4.93180652e-01,   8.22972951e-02,   2.14285714e-01],\n       [ -5.00000000e-01,   2.26876270e-17,   2.14285714e-01],\n       [ -4.93180652e-01,  -8.22972951e-02,   2.14285714e-01],\n       [ -4.20156583e-01,   7.22539112e-02,   2.14285714e-01],\n       [ -4.11228248e-01,   1.41174836e-01,   2.14285714e-01],\n       [ -4.20116462e-01,   1.83641595e-03,   2.14285714e-01],\n       [ -4.10983876e-01,  -6.85810793e-02,   2.14285714e-01],\n       [ -3.47132513e-01,   6.22105272e-02,   2.14285714e-01],\n       [ -3.49547876e-01,   1.19999937e-01,   2.14285714e-01],\n       [ -3.40232923e-01,   3.67283191e-03,   2.14285714e-01],\n       [ -3.28787101e-01,  -5.48648634e-02,   2.14285714e-01],\n       [ -2.74108444e-01,   5.21671433e-02,   2.14285714e-01],\n       [ -2.87867503e-01,   9.88250387e-02,   2.14285714e-01],\n       [ -2.60349385e-01,   5.50924786e-03,   2.14285714e-01],\n       [ -2.46590326e-01,  -4.11486476e-02,   2.14285714e-01],\n       [ -1.64393551e-01,  -2.74324317e-02,   2.14285714e-01],\n       [ -1.92379634e-01,   2.41096072e-02,   2.14285714e-01],\n       [ -8.21967753e-02,  -1.37162159e-02,   2.14285714e-01],\n       [ -1.24409882e-01,   4.27099665e-02,   2.14285714e-01],\n       [ -2.20365717e-01,   7.56516461e-02,   2.14285714e-01],\n       [ -1.66622989e-01,   9.91361489e-02,   2.14285714e-01],\n       [ -2.48351800e-01,   1.27193685e-01,   2.14285714e-01],\n       [ -2.08836096e-01,   1.55562331e-01,   2.14285714e-01],\n       [ -2.70747482e-01,   2.06077006e-01,   2.14285714e-01],\n       [ -3.32658869e-01,   2.56591681e-01,   2.14285714e-01],\n       [ -3.12146825e-01,   1.64120356e-01,   2.14285714e-01],\n       [ -3.75941850e-01,   2.01047026e-01,   2.14285714e-01],\n       [ -9.45817242e-01,  -3.24699469e-01,   2.14285714e-01],\n       [ -8.66999138e-01,  -2.97641180e-01,   2.14285714e-01],\n       [ -8.79473751e-01,  -4.75947393e-01,   2.14285714e-01],\n       [ -8.06184272e-01,  -4.36285110e-01,   2.14285714e-01],\n       [ -7.89140509e-01,  -6.14212713e-01,   2.14285714e-01],\n       [ -7.23378800e-01,  -5.63028320e-01,   2.14285714e-01],\n       [ -6.77281572e-01,  -7.35723911e-01,   2.14285714e-01],\n       [ -6.20841441e-01,  -6.74413585e-01,   2.14285714e-01],\n       [ -5.46948158e-01,  -8.37166478e-01,   2.14285714e-01],\n       [ -5.01369145e-01,  -7.67402605e-01,   2.14285714e-01],\n       [ -4.01695425e-01,  -9.15773327e-01,   2.14285714e-01],\n       [ -3.68220806e-01,  -8.39458883e-01,   2.14285714e-01],\n       [ -7.88181035e-01,  -2.70582891e-01,   2.14285714e-01],\n       [ -7.32894793e-01,  -3.96622828e-01,   2.14285714e-01],\n       [ -6.57617091e-01,  -5.11843927e-01,   2.14285714e-01],\n       [ -5.64401310e-01,  -6.13103259e-01,   2.14285714e-01],\n       [ -4.55790132e-01,  -6.97638732e-01,   2.14285714e-01],\n       [ -3.34746187e-01,  -7.63144439e-01,   2.14285714e-01],\n       [ -7.09362931e-01,  -2.43524602e-01,   2.14285714e-01],\n       [ -6.59605313e-01,  -3.56960545e-01,   2.14285714e-01],\n       [ -5.91855382e-01,  -4.60659535e-01,   2.14285714e-01],\n       [ -5.07961179e-01,  -5.51792933e-01,   2.14285714e-01],\n       [ -4.10211119e-01,  -6.27874859e-01,   2.14285714e-01],\n       [ -3.01271568e-01,  -6.86829995e-01,   2.14285714e-01],\n       [ -6.30544828e-01,  -2.16466313e-01,   2.14285714e-01],\n       [ -5.86315834e-01,  -3.17298262e-01,   2.14285714e-01],\n       [ -5.26093673e-01,  -4.09475142e-01,   2.14285714e-01],\n       [ -4.51521048e-01,  -4.90482607e-01,   2.14285714e-01],\n       [ -3.64632105e-01,  -5.58110986e-01,   2.14285714e-01],\n       [ -2.67796950e-01,  -6.10515551e-01,   2.14285714e-01],\n       [ -5.51726724e-01,  -1.89408024e-01,   2.14285714e-01],\n       [ -5.13026355e-01,  -2.77635979e-01,   2.14285714e-01],\n       [ -4.60331964e-01,  -3.58290749e-01,   2.14285714e-01],\n       [ -3.95080917e-01,  -4.29172281e-01,   2.14285714e-01],\n       [ -3.19053092e-01,  -4.88347112e-01,   2.14285714e-01],\n       [ -2.34322331e-01,  -5.34201107e-01,   2.14285714e-01],\n       [ -4.72908621e-01,  -1.62349735e-01,   2.14285714e-01],\n       [ -4.39736876e-01,  -2.37973697e-01,   2.14285714e-01],\n       [ -3.94570255e-01,  -3.07106356e-01,   2.14285714e-01],\n       [ -3.38640786e-01,  -3.67861955e-01,   2.14285714e-01],\n       [ -2.73474079e-01,  -4.18583239e-01,   2.14285714e-01],\n       [ -2.00847712e-01,  -4.57886663e-01,   2.14285714e-01],\n       [ -2.90292421e-01,  -3.12221863e-01,   2.14285714e-01],\n       [ -3.43107373e-01,  -2.67051188e-01,   2.14285714e-01],\n       [ -2.31319311e-01,  -3.50702994e-01,   2.14285714e-01],\n       [ -1.67373094e-01,  -3.81572219e-01,   2.14285714e-01],\n       [ -2.41944057e-01,  -2.56581770e-01,   2.14285714e-01],\n       [ -2.91644492e-01,  -2.26996019e-01,   2.14285714e-01],\n       [ -1.89164543e-01,  -2.82822750e-01,   2.14285714e-01],\n       [ -1.33898475e-01,  -3.05257776e-01,   2.14285714e-01],\n       [ -1.93595692e-01,  -2.00941678e-01,   2.14285714e-01],\n       [ -2.40181610e-01,  -1.86940851e-01,   2.14285714e-01],\n       [ -1.47009774e-01,  -2.14942505e-01,   2.14285714e-01],\n       [ -1.00423856e-01,  -2.28943332e-01,   2.14285714e-01],\n       [ -6.69492374e-02,  -1.52628888e-01,   2.14285714e-01],\n       [ -1.25405441e-01,  -1.47867075e-01,   2.14285714e-01],\n       [ -3.34746187e-02,  -7.63144439e-02,   2.14285714e-01],\n       [ -1.03801108e-01,  -8.07916455e-02,   2.14285714e-01],\n       [ -1.83861645e-01,  -1.43105263e-01,   2.14285714e-01],\n       [ -1.74127598e-01,  -8.52688471e-02,   2.14285714e-01],\n       [ -2.42317849e-01,  -1.38343450e-01,   2.14285714e-01],\n       [ -2.44454087e-01,  -8.97460487e-02,   2.14285714e-01],\n       [ -3.20605599e-01,  -1.13947277e-01,   2.14285714e-01],\n       [ -3.96757110e-01,  -1.38148506e-01,   2.14285714e-01],\n       [ -3.08124191e-01,  -1.71553532e-01,   2.14285714e-01],\n       [ -3.73930533e-01,  -2.04763614e-01,   2.14285714e-01],\n       [ -2.45485487e-01,  -9.69400266e-01,   2.14285714e-01],\n       [ -2.25028363e-01,  -8.88616910e-01,   2.14285714e-01],\n       [ -8.25793455e-02,  -9.96584493e-01,   2.14285714e-01],\n       [ -7.56977333e-02,  -9.13535785e-01,   2.14285714e-01],\n       [  8.25793455e-02,  -9.96584493e-01,   2.14285714e-01],\n       [  7.56977333e-02,  -9.13535785e-01,   2.14285714e-01],\n       [  2.45485487e-01,  -9.69400266e-01,   2.14285714e-01],\n       [  2.25028363e-01,  -8.88616910e-01,   2.14285714e-01],\n       [  4.01695425e-01,  -9.15773327e-01,   2.14285714e-01],\n       [  3.68220806e-01,  -8.39458883e-01,   2.14285714e-01],\n       [  5.46948158e-01,  -8.37166478e-01,   2.14285714e-01],\n       [  5.01369145e-01,  -7.67402605e-01,   2.14285714e-01],\n       [ -2.04571239e-01,  -8.07833555e-01,   2.14285714e-01],\n       [ -6.88161212e-02,  -8.30487078e-01,   2.14285714e-01],\n       [  6.88161212e-02,  -8.30487078e-01,   2.14285714e-01],\n       [  2.04571239e-01,  -8.07833555e-01,   2.14285714e-01],\n       [  3.34746187e-01,  -7.63144439e-01,   2.14285714e-01],\n       [  4.55790132e-01,  -6.97638732e-01,   2.14285714e-01],\n       [ -1.84114115e-01,  -7.27050199e-01,   2.14285714e-01],\n       [ -6.19345091e-02,  -7.47438370e-01,   2.14285714e-01],\n       [  6.19345091e-02,  -7.47438370e-01,   2.14285714e-01],\n       [  1.84114115e-01,  -7.27050199e-01,   2.14285714e-01],\n       [  3.01271568e-01,  -6.86829995e-01,   2.14285714e-01],\n       [  4.10211119e-01,  -6.27874859e-01,   2.14285714e-01],\n       [ -1.63656991e-01,  -6.46266844e-01,   2.14285714e-01],\n       [ -5.50528970e-02,  -6.64389662e-01,   2.14285714e-01],\n       [  5.50528970e-02,  -6.64389662e-01,   2.14285714e-01],\n       [  1.63656991e-01,  -6.46266844e-01,   2.14285714e-01],\n       [  2.67796950e-01,  -6.10515551e-01,   2.14285714e-01],\n       [  3.64632105e-01,  -5.58110986e-01,   2.14285714e-01],\n       [ -1.43199867e-01,  -5.65483488e-01,   2.14285714e-01],\n       [ -4.81712849e-02,  -5.81340954e-01,   2.14285714e-01],\n       [  4.81712849e-02,  -5.81340954e-01,   2.14285714e-01],\n       [  1.43199867e-01,  -5.65483488e-01,   2.14285714e-01],\n       [  2.34322331e-01,  -5.34201107e-01,   2.14285714e-01],\n       [  3.19053092e-01,  -4.88347112e-01,   2.14285714e-01],\n       [ -1.22742744e-01,  -4.84700133e-01,   2.14285714e-01],\n       [ -4.12896727e-02,  -4.98292247e-01,   2.14285714e-01],\n       [  4.12896727e-02,  -4.98292247e-01,   2.14285714e-01],\n       [  1.22742744e-01,  -4.84700133e-01,   2.14285714e-01],\n       [  2.00847712e-01,  -4.57886663e-01,   2.14285714e-01],\n       [  2.73474079e-01,  -4.18583239e-01,   2.14285714e-01],\n       [  1.02606772e-01,  -4.13792257e-01,   2.14285714e-01],\n       [  3.59043567e-02,  -4.33301147e-01,   2.14285714e-01],\n       [  1.67077120e-01,  -3.85469130e-01,   2.14285714e-01],\n       [  2.27895066e-01,  -3.48819366e-01,   2.14285714e-01],\n       [  8.24708009e-02,  -3.42884381e-01,   2.14285714e-01],\n       [  3.05190406e-02,  -3.68310047e-01,   2.14285714e-01],\n       [  1.33306527e-01,  -3.13051596e-01,   2.14285714e-01],\n       [  1.82316053e-01,  -2.79055493e-01,   2.14285714e-01],\n       [  6.23348295e-02,  -2.71976505e-01,   2.14285714e-01],\n       [  2.51337245e-02,  -3.03318947e-01,   2.14285714e-01],\n       [  9.95359345e-02,  -2.40634062e-01,   2.14285714e-01],\n       [  1.36737040e-01,  -2.09291620e-01,   2.14285714e-01],\n       [  9.11580264e-02,  -1.39527746e-01,   2.14285714e-01],\n       [  5.51990834e-02,  -1.85860856e-01,   2.14285714e-01],\n       [  4.55790132e-02,  -6.97638732e-02,   2.14285714e-01],\n       [  1.08622324e-02,  -1.31087650e-01,   2.14285714e-01],\n       [  1.92401405e-02,  -2.32193966e-01,   2.14285714e-01],\n       [ -2.38545485e-02,  -1.92411427e-01,   2.14285714e-01],\n       [ -1.67188024e-02,  -2.78527075e-01,   2.14285714e-01],\n       [ -5.85713293e-02,  -2.53735203e-01,   2.14285714e-01],\n       [ -7.99618007e-02,  -3.30723513e-01,   2.14285714e-01],\n       [ -1.01352272e-01,  -4.07711823e-01,   2.14285714e-01],\n       [ -2.49090925e-02,  -3.51782132e-01,   2.14285714e-01],\n       [ -3.30993826e-02,  -4.25037189e-01,   2.14285714e-01],\n       [  6.77281572e-01,  -7.35723911e-01,   2.14285714e-01],\n       [  6.20841441e-01,  -6.74413585e-01,   2.14285714e-01],\n       [  7.89140509e-01,  -6.14212713e-01,   2.14285714e-01],\n       [  7.23378800e-01,  -5.63028320e-01,   2.14285714e-01],\n       [  8.79473751e-01,  -4.75947393e-01,   2.14285714e-01],\n       [  8.06184272e-01,  -4.36285110e-01,   2.14285714e-01],\n       [  9.45817242e-01,  -3.24699469e-01,   2.14285714e-01],\n       [  8.66999138e-01,  -2.97641180e-01,   2.14285714e-01],\n       [  9.86361303e-01,  -1.64594590e-01,   2.14285714e-01],\n       [  9.04164528e-01,  -1.50878374e-01,   2.14285714e-01],\n       [  5.64401310e-01,  -6.13103259e-01,   2.14285714e-01],\n       [  6.57617091e-01,  -5.11843927e-01,   2.14285714e-01],\n       [  7.32894793e-01,  -3.96622828e-01,   2.14285714e-01],\n       [  7.88181035e-01,  -2.70582891e-01,   2.14285714e-01],\n       [  8.21967753e-01,  -1.37162159e-01,   2.14285714e-01],\n       [  5.07961179e-01,  -5.51792933e-01,   2.14285714e-01],\n       [  5.91855382e-01,  -4.60659535e-01,   2.14285714e-01],\n       [  6.59605313e-01,  -3.56960545e-01,   2.14285714e-01],\n       [  7.09362931e-01,  -2.43524602e-01,   2.14285714e-01],\n       [  7.39770978e-01,  -1.23445943e-01,   2.14285714e-01],\n       [  4.51521048e-01,  -4.90482607e-01,   2.14285714e-01],\n       [  5.26093673e-01,  -4.09475142e-01,   2.14285714e-01],\n       [  5.86315834e-01,  -3.17298262e-01,   2.14285714e-01],\n       [  6.30544828e-01,  -2.16466313e-01,   2.14285714e-01],\n       [  6.57574202e-01,  -1.09729727e-01,   2.14285714e-01],\n       [  3.95080917e-01,  -4.29172281e-01,   2.14285714e-01],\n       [  4.60331964e-01,  -3.58290749e-01,   2.14285714e-01],\n       [  5.13026355e-01,  -2.77635979e-01,   2.14285714e-01],\n       [  5.51726724e-01,  -1.89408024e-01,   2.14285714e-01],\n       [  5.75377427e-01,  -9.60135110e-02,   2.14285714e-01],\n       [  3.38640786e-01,  -3.67861955e-01,   2.14285714e-01],\n       [  3.94570255e-01,  -3.07106356e-01,   2.14285714e-01],\n       [  4.39736876e-01,  -2.37973697e-01,   2.14285714e-01],\n       [  4.72908621e-01,  -1.62349735e-01,   2.14285714e-01],\n       [  4.93180652e-01,  -8.22972951e-02,   2.14285714e-01],\n       [  4.02533591e-01,  -1.40423963e-01,   2.14285714e-01],\n       [  3.82383017e-01,  -2.06935340e-01,   2.14285714e-01],\n       [  4.14084357e-01,  -7.09602665e-02,   2.14285714e-01],\n       [  3.32158562e-01,  -1.18498191e-01,   2.14285714e-01],\n       [  3.25029158e-01,  -1.75896984e-01,   2.14285714e-01],\n       [  3.34988061e-01,  -5.96232379e-02,   2.14285714e-01],\n       [  2.61783533e-01,  -9.65724185e-02,   2.14285714e-01],\n       [  2.67675299e-01,  -1.44858628e-01,   2.14285714e-01],\n       [  2.55891766e-01,  -4.82862093e-02,   2.14285714e-01],\n       [  1.85787515e-01,  -5.54454306e-02,   2.14285714e-01],\n       [  1.15683264e-01,  -6.26046519e-02,   2.14285714e-01],\n       [  2.04908364e-01,  -1.10890861e-01,   2.14285714e-01],\n       [  1.48033195e-01,  -1.25209304e-01,   2.14285714e-01],\n       [  2.24029213e-01,  -1.66336292e-01,   2.14285714e-01],\n       [  1.80383126e-01,  -1.87813956e-01,   2.14285714e-01],\n       [  2.33135679e-01,  -2.47829956e-01,   2.14285714e-01],\n       [  2.85888233e-01,  -3.07845955e-01,   2.14285714e-01],\n       [  2.80876227e-01,  -2.13259647e-01,   2.14285714e-01],\n       [  3.37723241e-01,  -2.60183001e-01,   2.14285714e-01],\n       [  1.00000000e+00,   0.00000000e+00,   7.14285714e-02],\n       [  9.86361303e-01,   1.64594590e-01,   7.14285714e-02],\n       [  9.04164528e-01,   1.50878374e-01,   7.14285714e-02],\n       [  9.16666667e-01,   6.89122780e-17,   7.14285714e-02],\n       [  9.45817242e-01,   3.24699469e-01,   7.14285714e-02],\n       [  8.66999138e-01,   2.97641180e-01,   7.14285714e-02],\n       [  8.79473751e-01,   4.75947393e-01,   7.14285714e-02],\n       [  8.06184272e-01,   4.36285110e-01,   7.14285714e-02],\n       [  7.89140509e-01,   6.14212713e-01,   7.14285714e-02],\n       [  7.23378800e-01,   5.63028320e-01,   7.14285714e-02],\n       [  6.77281572e-01,   7.35723911e-01,   7.14285714e-02],\n       [  6.20841441e-01,   6.74413585e-01,   7.14285714e-02],\n       [  5.46948158e-01,   8.37166478e-01,   7.14285714e-02],\n       [  5.01369145e-01,   7.67402605e-01,   7.14285714e-02],\n       [  4.01695425e-01,   9.15773327e-01,   7.14285714e-02],\n       [  3.68220806e-01,   8.39458883e-01,   7.14285714e-02],\n       [  8.21967753e-01,   1.37162159e-01,   7.14285714e-02],\n       [  8.33333333e-01,   1.18742958e-16,   7.14285714e-02],\n       [  7.88181035e-01,   2.70582891e-01,   7.14285714e-02],\n       [  7.32894793e-01,   3.96622828e-01,   7.14285714e-02],\n       [  6.57617091e-01,   5.11843927e-01,   7.14285714e-02],\n       [  5.64401310e-01,   6.13103259e-01,   7.14285714e-02],\n       [  4.55790132e-01,   6.97638732e-01,   7.14285714e-02],\n       [  3.34746187e-01,   7.63144439e-01,   7.14285714e-02],\n       [  7.39770978e-01,   1.23445943e-01,   7.14285714e-02],\n       [  7.50000000e-01,   1.21936714e-16,   7.14285714e-02],\n       [  7.09362931e-01,   2.43524602e-01,   7.14285714e-02],\n       [  6.59605313e-01,   3.56960545e-01,   7.14285714e-02],\n       [  5.91855382e-01,   4.60659535e-01,   7.14285714e-02],\n       [  5.07961179e-01,   5.51792933e-01,   7.14285714e-02],\n       [  4.10211119e-01,   6.27874859e-01,   7.14285714e-02],\n       [  3.01271568e-01,   6.86829995e-01,   7.14285714e-02],\n       [  6.57574202e-01,   1.09729727e-01,   7.14285714e-02],\n       [  6.66666667e-01,   1.12895794e-16,   7.14285714e-02],\n       [  6.30544828e-01,   2.16466313e-01,   7.14285714e-02],\n       [  5.86315834e-01,   3.17298262e-01,   7.14285714e-02],\n       [  5.26093673e-01,   4.09475142e-01,   7.14285714e-02],\n       [  4.51521048e-01,   4.90482607e-01,   7.14285714e-02],\n       [  3.64632105e-01,   5.58110986e-01,   7.14285714e-02],\n       [  2.67796950e-01,   6.10515551e-01,   7.14285714e-02],\n       [  5.75377427e-01,   9.60135110e-02,   7.14285714e-02],\n       [  5.83333333e-01,   9.97185006e-17,   7.14285714e-02],\n       [  5.51726724e-01,   1.89408024e-01,   7.14285714e-02],\n       [  5.13026355e-01,   2.77635979e-01,   7.14285714e-02],\n       [  4.60331964e-01,   3.58290749e-01,   7.14285714e-02],\n       [  3.95080917e-01,   4.29172281e-01,   7.14285714e-02],\n       [  3.19053092e-01,   4.88347112e-01,   7.14285714e-02],\n       [  2.34322331e-01,   5.34201107e-01,   7.14285714e-02],\n       [  4.93180652e-01,   8.22972951e-02,   7.14285714e-02],\n       [  5.00000000e-01,   8.46578841e-17,   7.14285714e-02],\n       [  4.72908621e-01,   1.62349735e-01,   7.14285714e-02],\n       [  4.39736876e-01,   2.37973697e-01,   7.14285714e-02],\n       [  3.94570255e-01,   3.07106356e-01,   7.14285714e-02],\n       [  3.38640786e-01,   3.67861955e-01,   7.14285714e-02],\n       [  2.73474079e-01,   4.18583239e-01,   7.14285714e-02],\n       [  2.00847712e-01,   4.57886663e-01,   7.14285714e-02],\n       [  2.92606991e-01,   3.16445261e-01,   7.14285714e-02],\n       [  3.44188184e-01,   2.67935253e-01,   7.14285714e-02],\n       [  2.34867640e-01,   3.58265725e-01,   7.14285714e-02],\n       [  1.72155182e-01,   3.92474283e-01,   7.14285714e-02],\n       [  2.46573197e-01,   2.65028567e-01,   7.14285714e-02],\n       [  2.93806114e-01,   2.28764151e-01,   7.14285714e-02],\n       [  1.96261201e-01,   2.97948211e-01,   7.14285714e-02],\n       [  1.43462652e-01,   3.27061902e-01,   7.14285714e-02],\n       [  2.00539403e-01,   2.13611872e-01,   7.14285714e-02],\n       [  2.43424043e-01,   1.89593048e-01,   7.14285714e-02],\n       [  1.57654762e-01,   2.37630697e-01,   7.14285714e-02],\n       [  1.14770121e-01,   2.61649522e-01,   7.14285714e-02],\n       [  8.60775910e-02,   1.96237141e-01,   7.14285714e-02],\n       [  1.39074405e-01,   1.78223023e-01,   7.14285714e-02],\n       [  5.73850607e-02,   1.30824761e-01,   7.14285714e-02],\n       [  1.20494048e-01,   1.18815349e-01,   7.14285714e-02],\n       [  2.86925303e-02,   6.54123805e-02,   7.14285714e-02],\n       [  1.01913690e-01,   5.94076743e-02,   7.14285714e-02],\n       [ -1.35647550e-17,  -1.43830177e-17,   7.14285714e-02],\n       [  8.33333333e-02,   1.98768323e-18,   7.14285714e-02],\n       [  1.92071219e-01,   1.60208904e-01,   7.14285714e-02],\n       [  1.83603035e-01,   1.06805936e-01,   7.14285714e-02],\n       [  1.75134851e-01,   5.34029681e-02,   7.14285714e-02],\n       [  1.66666667e-01,   1.85787325e-17,   7.14285714e-02],\n       [  2.45068032e-01,   1.42194786e-01,   7.14285714e-02],\n       [  2.46712022e-01,   9.47965238e-02,   7.14285714e-02],\n       [  2.48356011e-01,   4.73982619e-02,   7.14285714e-02],\n       [  2.50000000e-01,   3.53119596e-17,   7.14285714e-02],\n       [  3.33333333e-01,   5.20568343e-17,   7.14285714e-02],\n       [  3.29964224e-01,   5.90312730e-02,   7.14285714e-02],\n       [  4.16666667e-01,   6.86087031e-17,   7.14285714e-02],\n       [  4.11572438e-01,   7.06642841e-02,   7.14285714e-02],\n       [  3.22110888e-01,   1.17314261e-01,   7.14285714e-02],\n       [  3.97509754e-01,   1.39831998e-01,   7.14285714e-02],\n       [  3.09957647e-01,   1.74121089e-01,   7.14285714e-02],\n       [  3.74847261e-01,   2.06047393e-01,   7.14285714e-02],\n       [  2.45485487e-01,   9.69400266e-01,   7.14285714e-02],\n       [  2.25028363e-01,   8.88616910e-01,   7.14285714e-02],\n       [  8.25793455e-02,   9.96584493e-01,   7.14285714e-02],\n       [  7.56977333e-02,   9.13535785e-01,   7.14285714e-02],\n       [ -8.25793455e-02,   9.96584493e-01,   7.14285714e-02],\n       [ -7.56977333e-02,   9.13535785e-01,   7.14285714e-02],\n       [ -2.45485487e-01,   9.69400266e-01,   7.14285714e-02],\n       [ -2.25028363e-01,   8.88616910e-01,   7.14285714e-02],\n       [ -4.01695425e-01,   9.15773327e-01,   7.14285714e-02],\n       [ -3.68220806e-01,   8.39458883e-01,   7.14285714e-02],\n       [ -5.46948158e-01,   8.37166478e-01,   7.14285714e-02],\n       [ -5.01369145e-01,   7.67402605e-01,   7.14285714e-02],\n       [ -6.77281572e-01,   7.35723911e-01,   7.14285714e-02],\n       [ -6.20841441e-01,   6.74413585e-01,   7.14285714e-02],\n       [  2.04571239e-01,   8.07833555e-01,   7.14285714e-02],\n       [  6.88161212e-02,   8.30487078e-01,   7.14285714e-02],\n       [ -6.88161212e-02,   8.30487078e-01,   7.14285714e-02],\n       [ -2.04571239e-01,   8.07833555e-01,   7.14285714e-02],\n       [ -3.34746187e-01,   7.63144439e-01,   7.14285714e-02],\n       [ -4.55790132e-01,   6.97638732e-01,   7.14285714e-02],\n       [ -5.64401310e-01,   6.13103259e-01,   7.14285714e-02],\n       [  1.84114115e-01,   7.27050199e-01,   7.14285714e-02],\n       [  6.19345091e-02,   7.47438370e-01,   7.14285714e-02],\n       [ -6.19345091e-02,   7.47438370e-01,   7.14285714e-02],\n       [ -1.84114115e-01,   7.27050199e-01,   7.14285714e-02],\n       [ -3.01271568e-01,   6.86829995e-01,   7.14285714e-02],\n       [ -4.10211119e-01,   6.27874859e-01,   7.14285714e-02],\n       [ -5.07961179e-01,   5.51792933e-01,   7.14285714e-02],\n       [  1.63656991e-01,   6.46266844e-01,   7.14285714e-02],\n       [  5.50528970e-02,   6.64389662e-01,   7.14285714e-02],\n       [ -5.50528970e-02,   6.64389662e-01,   7.14285714e-02],\n       [ -1.63656991e-01,   6.46266844e-01,   7.14285714e-02],\n       [ -2.67796950e-01,   6.10515551e-01,   7.14285714e-02],\n       [ -3.64632105e-01,   5.58110986e-01,   7.14285714e-02],\n       [ -4.51521048e-01,   4.90482607e-01,   7.14285714e-02],\n       [  1.43199867e-01,   5.65483488e-01,   7.14285714e-02],\n       [  4.81712849e-02,   5.81340954e-01,   7.14285714e-02],\n       [ -4.81712849e-02,   5.81340954e-01,   7.14285714e-02],\n       [ -1.43199867e-01,   5.65483488e-01,   7.14285714e-02],\n       [ -2.34322331e-01,   5.34201107e-01,   7.14285714e-02],\n       [ -3.19053092e-01,   4.88347112e-01,   7.14285714e-02],\n       [ -3.95080917e-01,   4.29172281e-01,   7.14285714e-02],\n       [  1.22742744e-01,   4.84700133e-01,   7.14285714e-02],\n       [  4.12896727e-02,   4.98292247e-01,   7.14285714e-02],\n       [ -4.12896727e-02,   4.98292247e-01,   7.14285714e-02],\n       [ -1.22742744e-01,   4.84700133e-01,   7.14285714e-02],\n       [ -2.00847712e-01,   4.57886663e-01,   7.14285714e-02],\n       [ -2.73474079e-01,   4.18583239e-01,   7.14285714e-02],\n       [ -3.38640786e-01,   3.67861955e-01,   7.14285714e-02],\n       [ -1.02283149e-01,   4.15336195e-01,   7.14285714e-02],\n       [ -3.59859419e-02,   4.34695086e-01,   7.14285714e-02],\n       [ -1.66348287e-01,   3.87163066e-01,   7.14285714e-02],\n       [ -2.26761024e-01,   3.50663301e-01,   7.14285714e-02],\n       [ -2.82200655e-01,   3.06551629e-01,   7.14285714e-02],\n       [ -8.18235534e-02,   3.45972257e-01,   7.14285714e-02],\n       [ -3.06822110e-02,   3.71097926e-01,   7.14285714e-02],\n       [ -1.31848862e-01,   3.16439469e-01,   7.14285714e-02],\n       [ -1.80047970e-01,   2.82743363e-01,   7.14285714e-02],\n       [ -2.25760524e-01,   2.45241304e-01,   7.14285714e-02],\n       [ -6.13639584e-02,   2.76608319e-01,   7.14285714e-02],\n       [ -2.53784802e-02,   3.07500766e-01,   7.14285714e-02],\n       [ -9.73494365e-02,   2.45715872e-01,   7.14285714e-02],\n       [ -1.33334915e-01,   2.14823425e-01,   7.14285714e-02],\n       [ -1.69320393e-01,   1.83930978e-01,   7.14285714e-02],\n       [ -1.12880262e-01,   1.22620652e-01,   7.14285714e-02],\n       [ -7.93257664e-02,   1.65019743e-01,   7.14285714e-02],\n       [ -5.64401310e-02,   6.13103259e-02,   7.14285714e-02],\n       [ -2.53166180e-02,   1.15216062e-01,   7.14285714e-02],\n       [ -4.57712708e-02,   2.07418835e-01,   7.14285714e-02],\n       [  5.80689493e-03,   1.69121798e-01,   7.14285714e-02],\n       [ -1.22167752e-02,   2.49817927e-01,   7.14285714e-02],\n       [  3.69304079e-02,   2.23027534e-01,   7.14285714e-02],\n       [  2.13377203e-02,   2.92217018e-01,   7.14285714e-02],\n       [  6.80539208e-02,   2.76933270e-01,   7.14285714e-02],\n       [  8.62835284e-02,   3.46188891e-01,   7.14285714e-02],\n       [  1.04513136e-01,   4.15444512e-01,   7.14285714e-02],\n       [  2.79883711e-02,   3.60908761e-01,   7.14285714e-02],\n       [  3.46390219e-02,   4.29600504e-01,   7.14285714e-02],\n       [ -7.89140509e-01,   6.14212713e-01,   7.14285714e-02],\n       [ -7.23378800e-01,   5.63028320e-01,   7.14285714e-02],\n       [ -8.79473751e-01,   4.75947393e-01,   7.14285714e-02],\n       [ -8.06184272e-01,   4.36285110e-01,   7.14285714e-02],\n       [ -9.45817242e-01,   3.24699469e-01,   7.14285714e-02],\n       [ -8.66999138e-01,   2.97641180e-01,   7.14285714e-02],\n       [ -9.86361303e-01,   1.64594590e-01,   7.14285714e-02],\n       [ -9.04164528e-01,   1.50878374e-01,   7.14285714e-02],\n       [ -1.00000000e+00,   1.74949543e-17,   7.14285714e-02],\n       [ -9.16666667e-01,   3.57545599e-17,   7.14285714e-02],\n       [ -9.86361303e-01,  -1.64594590e-01,   7.14285714e-02],\n       [ -9.04164528e-01,  -1.50878374e-01,   7.14285714e-02],\n       [ -6.57617091e-01,   5.11843927e-01,   7.14285714e-02],\n       [ -7.32894793e-01,   3.96622828e-01,   7.14285714e-02],\n       [ -7.88181035e-01,   2.70582891e-01,   7.14285714e-02],\n       [ -8.21967753e-01,   1.37162159e-01,   7.14285714e-02],\n       [ -8.33333333e-01,   3.09851013e-17,   7.14285714e-02],\n       [ -8.21967753e-01,  -1.37162159e-01,   7.14285714e-02],\n       [ -5.91855382e-01,   4.60659535e-01,   7.14285714e-02],\n       [ -6.59605313e-01,   3.56960545e-01,   7.14285714e-02],\n       [ -7.09362931e-01,   2.43524602e-01,   7.14285714e-02],\n       [ -7.39770978e-01,   1.23445943e-01,   7.14285714e-02],\n       [ -7.50000000e-01,   2.55581870e-17,   7.14285714e-02],\n       [ -7.39770978e-01,  -1.23445943e-01,   7.14285714e-02],\n       [ -5.26093673e-01,   4.09475142e-01,   7.14285714e-02],\n       [ -5.86315834e-01,   3.17298262e-01,   7.14285714e-02],\n       [ -6.30544828e-01,   2.16466313e-01,   7.14285714e-02],\n       [ -6.57574202e-01,   1.09729727e-01,   7.14285714e-02],\n       [ -6.66666667e-01,   1.99595799e-17,   7.14285714e-02],\n       [ -6.57574202e-01,  -1.09729727e-01,   7.14285714e-02],\n       [ -4.60331964e-01,   3.58290749e-01,   7.14285714e-02],\n       [ -5.13026355e-01,   2.77635979e-01,   7.14285714e-02],\n       [ -5.51726724e-01,   1.89408024e-01,   7.14285714e-02],\n       [ -5.75377427e-01,   9.60135110e-02,   7.14285714e-02],\n       [ -5.83333333e-01,   1.44716353e-17,   7.14285714e-02],\n       [ -5.75377427e-01,  -9.60135110e-02,   7.14285714e-02],\n       [ -3.94570255e-01,   3.07106356e-01,   7.14285714e-02],\n       [ -4.39736876e-01,   2.37973697e-01,   7.14285714e-02],\n       [ -4.72908621e-01,   1.62349735e-01,   7.14285714e-02],\n       [ -4.93180652e-01,   8.22972951e-02,   7.14285714e-02],\n       [ -5.00000000e-01,   9.25854954e-18,   7.14285714e-02],\n       [ -4.93180652e-01,  -8.22972951e-02,   7.14285714e-02],\n       [ -4.20156583e-01,   7.22539112e-02,   7.14285714e-02],\n       [ -4.11228248e-01,   1.41174836e-01,   7.14285714e-02],\n       [ -4.20116462e-01,   1.83641595e-03,   7.14285714e-02],\n       [ -4.10983876e-01,  -6.85810793e-02,   7.14285714e-02],\n       [ -3.47132513e-01,   6.22105272e-02,   7.14285714e-02],\n       [ -3.49547876e-01,   1.19999937e-01,   7.14285714e-02],\n       [ -3.40232923e-01,   3.67283191e-03,   7.14285714e-02],\n       [ -3.28787101e-01,  -5.48648634e-02,   7.14285714e-02],\n       [ -2.74108444e-01,   5.21671433e-02,   7.14285714e-02],\n       [ -2.87867503e-01,   9.88250387e-02,   7.14285714e-02],\n       [ -2.60349385e-01,   5.50924786e-03,   7.14285714e-02],\n       [ -2.46590326e-01,  -4.11486476e-02,   7.14285714e-02],\n       [ -1.64393551e-01,  -2.74324317e-02,   7.14285714e-02],\n       [ -1.92379634e-01,   2.41096072e-02,   7.14285714e-02],\n       [ -8.21967753e-02,  -1.37162159e-02,   7.14285714e-02],\n       [ -1.24409882e-01,   4.27099665e-02,   7.14285714e-02],\n       [ -2.20365717e-01,   7.56516461e-02,   7.14285714e-02],\n       [ -1.66622989e-01,   9.91361489e-02,   7.14285714e-02],\n       [ -2.48351800e-01,   1.27193685e-01,   7.14285714e-02],\n       [ -2.08836096e-01,   1.55562331e-01,   7.14285714e-02],\n       [ -2.70747482e-01,   2.06077006e-01,   7.14285714e-02],\n       [ -3.32658869e-01,   2.56591681e-01,   7.14285714e-02],\n       [ -3.12146825e-01,   1.64120356e-01,   7.14285714e-02],\n       [ -3.75941850e-01,   2.01047026e-01,   7.14285714e-02],\n       [ -9.45817242e-01,  -3.24699469e-01,   7.14285714e-02],\n       [ -8.66999138e-01,  -2.97641180e-01,   7.14285714e-02],\n       [ -8.79473751e-01,  -4.75947393e-01,   7.14285714e-02],\n       [ -8.06184272e-01,  -4.36285110e-01,   7.14285714e-02],\n       [ -7.89140509e-01,  -6.14212713e-01,   7.14285714e-02],\n       [ -7.23378800e-01,  -5.63028320e-01,   7.14285714e-02],\n       [ -6.77281572e-01,  -7.35723911e-01,   7.14285714e-02],\n       [ -6.20841441e-01,  -6.74413585e-01,   7.14285714e-02],\n       [ -5.46948158e-01,  -8.37166478e-01,   7.14285714e-02],\n       [ -5.01369145e-01,  -7.67402605e-01,   7.14285714e-02],\n       [ -4.01695425e-01,  -9.15773327e-01,   7.14285714e-02],\n       [ -3.68220806e-01,  -8.39458883e-01,   7.14285714e-02],\n       [ -7.88181035e-01,  -2.70582891e-01,   7.14285714e-02],\n       [ -7.32894793e-01,  -3.96622828e-01,   7.14285714e-02],\n       [ -6.57617091e-01,  -5.11843927e-01,   7.14285714e-02],\n       [ -5.64401310e-01,  -6.13103259e-01,   7.14285714e-02],\n       [ -4.55790132e-01,  -6.97638732e-01,   7.14285714e-02],\n       [ -3.34746187e-01,  -7.63144439e-01,   7.14285714e-02],\n       [ -7.09362931e-01,  -2.43524602e-01,   7.14285714e-02],\n       [ -6.59605313e-01,  -3.56960545e-01,   7.14285714e-02],\n       [ -5.91855382e-01,  -4.60659535e-01,   7.14285714e-02],\n       [ -5.07961179e-01,  -5.51792933e-01,   7.14285714e-02],\n       [ -4.10211119e-01,  -6.27874859e-01,   7.14285714e-02],\n       [ -3.01271568e-01,  -6.86829995e-01,   7.14285714e-02],\n       [ -6.30544828e-01,  -2.16466313e-01,   7.14285714e-02],\n       [ -5.86315834e-01,  -3.17298262e-01,   7.14285714e-02],\n       [ -5.26093673e-01,  -4.09475142e-01,   7.14285714e-02],\n       [ -4.51521048e-01,  -4.90482607e-01,   7.14285714e-02],\n       [ -3.64632105e-01,  -5.58110986e-01,   7.14285714e-02],\n       [ -2.67796950e-01,  -6.10515551e-01,   7.14285714e-02],\n       [ -5.51726724e-01,  -1.89408024e-01,   7.14285714e-02],\n       [ -5.13026355e-01,  -2.77635979e-01,   7.14285714e-02],\n       [ -4.60331964e-01,  -3.58290749e-01,   7.14285714e-02],\n       [ -3.95080917e-01,  -4.29172281e-01,   7.14285714e-02],\n       [ -3.19053092e-01,  -4.88347112e-01,   7.14285714e-02],\n       [ -2.34322331e-01,  -5.34201107e-01,   7.14285714e-02],\n       [ -4.72908621e-01,  -1.62349735e-01,   7.14285714e-02],\n       [ -4.39736876e-01,  -2.37973697e-01,   7.14285714e-02],\n       [ -3.94570255e-01,  -3.07106356e-01,   7.14285714e-02],\n       [ -3.38640786e-01,  -3.67861955e-01,   7.14285714e-02],\n       [ -2.73474079e-01,  -4.18583239e-01,   7.14285714e-02],\n       [ -2.00847712e-01,  -4.57886663e-01,   7.14285714e-02],\n       [ -2.90292421e-01,  -3.12221863e-01,   7.14285714e-02],\n       [ -3.43107373e-01,  -2.67051188e-01,   7.14285714e-02],\n       [ -2.31319311e-01,  -3.50702994e-01,   7.14285714e-02],\n       [ -1.67373094e-01,  -3.81572219e-01,   7.14285714e-02],\n       [ -2.41944057e-01,  -2.56581770e-01,   7.14285714e-02],\n       [ -2.91644492e-01,  -2.26996019e-01,   7.14285714e-02],\n       [ -1.89164543e-01,  -2.82822750e-01,   7.14285714e-02],\n       [ -1.33898475e-01,  -3.05257776e-01,   7.14285714e-02],\n       [ -1.93595692e-01,  -2.00941678e-01,   7.14285714e-02],\n       [ -2.40181610e-01,  -1.86940851e-01,   7.14285714e-02],\n       [ -1.47009774e-01,  -2.14942505e-01,   7.14285714e-02],\n       [ -1.00423856e-01,  -2.28943332e-01,   7.14285714e-02],\n       [ -6.69492374e-02,  -1.52628888e-01,   7.14285714e-02],\n       [ -1.25405441e-01,  -1.47867075e-01,   7.14285714e-02],\n       [ -3.34746187e-02,  -7.63144439e-02,   7.14285714e-02],\n       [ -1.03801108e-01,  -8.07916455e-02,   7.14285714e-02],\n       [ -1.83861645e-01,  -1.43105263e-01,   7.14285714e-02],\n       [ -1.74127598e-01,  -8.52688471e-02,   7.14285714e-02],\n       [ -2.42317849e-01,  -1.38343450e-01,   7.14285714e-02],\n       [ -2.44454087e-01,  -8.97460487e-02,   7.14285714e-02],\n       [ -3.20605599e-01,  -1.13947277e-01,   7.14285714e-02],\n       [ -3.96757110e-01,  -1.38148506e-01,   7.14285714e-02],\n       [ -3.08124191e-01,  -1.71553532e-01,   7.14285714e-02],\n       [ -3.73930533e-01,  -2.04763614e-01,   7.14285714e-02],\n       [ -2.45485487e-01,  -9.69400266e-01,   7.14285714e-02],\n       [ -2.25028363e-01,  -8.88616910e-01,   7.14285714e-02],\n       [ -8.25793455e-02,  -9.96584493e-01,   7.14285714e-02],\n       [ -7.56977333e-02,  -9.13535785e-01,   7.14285714e-02],\n       [  8.25793455e-02,  -9.96584493e-01,   7.14285714e-02],\n       [  7.56977333e-02,  -9.13535785e-01,   7.14285714e-02],\n       [  2.45485487e-01,  -9.69400266e-01,   7.14285714e-02],\n       [  2.25028363e-01,  -8.88616910e-01,   7.14285714e-02],\n       [  4.01695425e-01,  -9.15773327e-01,   7.14285714e-02],\n       [  3.68220806e-01,  -8.39458883e-01,   7.14285714e-02],\n       [  5.46948158e-01,  -8.37166478e-01,   7.14285714e-02],\n       [  5.01369145e-01,  -7.67402605e-01,   7.14285714e-02],\n       [ -2.04571239e-01,  -8.07833555e-01,   7.14285714e-02],\n       [ -6.88161212e-02,  -8.30487078e-01,   7.14285714e-02],\n       [  6.88161212e-02,  -8.30487078e-01,   7.14285714e-02],\n       [  2.04571239e-01,  -8.07833555e-01,   7.14285714e-02],\n       [  3.34746187e-01,  -7.63144439e-01,   7.14285714e-02],\n       [  4.55790132e-01,  -6.97638732e-01,   7.14285714e-02],\n       [ -1.84114115e-01,  -7.27050199e-01,   7.14285714e-02],\n       [ -6.19345091e-02,  -7.47438370e-01,   7.14285714e-02],\n       [  6.19345091e-02,  -7.47438370e-01,   7.14285714e-02],\n       [  1.84114115e-01,  -7.27050199e-01,   7.14285714e-02],\n       [  3.01271568e-01,  -6.86829995e-01,   7.14285714e-02],\n       [  4.10211119e-01,  -6.27874859e-01,   7.14285714e-02],\n       [ -1.63656991e-01,  -6.46266844e-01,   7.14285714e-02],\n       [ -5.50528970e-02,  -6.64389662e-01,   7.14285714e-02],\n       [  5.50528970e-02,  -6.64389662e-01,   7.14285714e-02],\n       [  1.63656991e-01,  -6.46266844e-01,   7.14285714e-02],\n       [  2.67796950e-01,  -6.10515551e-01,   7.14285714e-02],\n       [  3.64632105e-01,  -5.58110986e-01,   7.14285714e-02],\n       [ -1.43199867e-01,  -5.65483488e-01,   7.14285714e-02],\n       [ -4.81712849e-02,  -5.81340954e-01,   7.14285714e-02],\n       [  4.81712849e-02,  -5.81340954e-01,   7.14285714e-02],\n       [  1.43199867e-01,  -5.65483488e-01,   7.14285714e-02],\n       [  2.34322331e-01,  -5.34201107e-01,   7.14285714e-02],\n       [  3.19053092e-01,  -4.88347112e-01,   7.14285714e-02],\n       [ -1.22742744e-01,  -4.84700133e-01,   7.14285714e-02],\n       [ -4.12896727e-02,  -4.98292247e-01,   7.14285714e-02],\n       [  4.12896727e-02,  -4.98292247e-01,   7.14285714e-02],\n       [  1.22742744e-01,  -4.84700133e-01,   7.14285714e-02],\n       [  2.00847712e-01,  -4.57886663e-01,   7.14285714e-02],\n       [  2.73474079e-01,  -4.18583239e-01,   7.14285714e-02],\n       [  1.02606772e-01,  -4.13792257e-01,   7.14285714e-02],\n       [  3.59043567e-02,  -4.33301147e-01,   7.14285714e-02],\n       [  1.67077120e-01,  -3.85469130e-01,   7.14285714e-02],\n       [  2.27895066e-01,  -3.48819366e-01,   7.14285714e-02],\n       [  8.24708009e-02,  -3.42884381e-01,   7.14285714e-02],\n       [  3.05190406e-02,  -3.68310047e-01,   7.14285714e-02],\n       [  1.33306527e-01,  -3.13051596e-01,   7.14285714e-02],\n       [  1.82316053e-01,  -2.79055493e-01,   7.14285714e-02],\n       [  6.23348295e-02,  -2.71976505e-01,   7.14285714e-02],\n       [  2.51337245e-02,  -3.03318947e-01,   7.14285714e-02],\n       [  9.95359345e-02,  -2.40634062e-01,   7.14285714e-02],\n       [  1.36737040e-01,  -2.09291620e-01,   7.14285714e-02],\n       [  9.11580264e-02,  -1.39527746e-01,   7.14285714e-02],\n       [  5.51990834e-02,  -1.85860856e-01,   7.14285714e-02],\n       [  4.55790132e-02,  -6.97638732e-02,   7.14285714e-02],\n       [  1.08622324e-02,  -1.31087650e-01,   7.14285714e-02],\n       [  1.92401405e-02,  -2.32193966e-01,   7.14285714e-02],\n       [ -2.38545485e-02,  -1.92411427e-01,   7.14285714e-02],\n       [ -1.67188024e-02,  -2.78527075e-01,   7.14285714e-02],\n       [ -5.85713293e-02,  -2.53735203e-01,   7.14285714e-02],\n       [ -7.99618007e-02,  -3.30723513e-01,   7.14285714e-02],\n       [ -1.01352272e-01,  -4.07711823e-01,   7.14285714e-02],\n       [ -2.49090925e-02,  -3.51782132e-01,   7.14285714e-02],\n       [ -3.30993826e-02,  -4.25037189e-01,   7.14285714e-02],\n       [  6.77281572e-01,  -7.35723911e-01,   7.14285714e-02],\n       [  6.20841441e-01,  -6.74413585e-01,   7.14285714e-02],\n       [  7.89140509e-01,  -6.14212713e-01,   7.14285714e-02],\n       [  7.23378800e-01,  -5.63028320e-01,   7.14285714e-02],\n       [  8.79473751e-01,  -4.75947393e-01,   7.14285714e-02],\n       [  8.06184272e-01,  -4.36285110e-01,   7.14285714e-02],\n       [  9.45817242e-01,  -3.24699469e-01,   7.14285714e-02],\n       [  8.66999138e-01,  -2.97641180e-01,   7.14285714e-02],\n       [  9.86361303e-01,  -1.64594590e-01,   7.14285714e-02],\n       [  9.04164528e-01,  -1.50878374e-01,   7.14285714e-02],\n       [  5.64401310e-01,  -6.13103259e-01,   7.14285714e-02],\n       [  6.57617091e-01,  -5.11843927e-01,   7.14285714e-02],\n       [  7.32894793e-01,  -3.96622828e-01,   7.14285714e-02],\n       [  7.88181035e-01,  -2.70582891e-01,   7.14285714e-02],\n       [  8.21967753e-01,  -1.37162159e-01,   7.14285714e-02],\n       [  5.07961179e-01,  -5.51792933e-01,   7.14285714e-02],\n       [  5.91855382e-01,  -4.60659535e-01,   7.14285714e-02],\n       [  6.59605313e-01,  -3.56960545e-01,   7.14285714e-02],\n       [  7.09362931e-01,  -2.43524602e-01,   7.14285714e-02],\n       [  7.39770978e-01,  -1.23445943e-01,   7.14285714e-02],\n       [  4.51521048e-01,  -4.90482607e-01,   7.14285714e-02],\n       [  5.26093673e-01,  -4.09475142e-01,   7.14285714e-02],\n       [  5.86315834e-01,  -3.17298262e-01,   7.14285714e-02],\n       [  6.30544828e-01,  -2.16466313e-01,   7.14285714e-02],\n       [  6.57574202e-01,  -1.09729727e-01,   7.14285714e-02],\n       [  3.95080917e-01,  -4.29172281e-01,   7.14285714e-02],\n       [  4.60331964e-01,  -3.58290749e-01,   7.14285714e-02],\n       [  5.13026355e-01,  -2.77635979e-01,   7.14285714e-02],\n       [  5.51726724e-01,  -1.89408024e-01,   7.14285714e-02],\n       [  5.75377427e-01,  -9.60135110e-02,   7.14285714e-02],\n       [  3.38640786e-01,  -3.67861955e-01,   7.14285714e-02],\n       [  3.94570255e-01,  -3.07106356e-01,   7.14285714e-02],\n       [  4.39736876e-01,  -2.37973697e-01,   7.14285714e-02],\n       [  4.72908621e-01,  -1.62349735e-01,   7.14285714e-02],\n       [  4.93180652e-01,  -8.22972951e-02,   7.14285714e-02],\n       [  4.02533591e-01,  -1.40423963e-01,   7.14285714e-02],\n       [  3.82383017e-01,  -2.06935340e-01,   7.14285714e-02],\n       [  4.14084357e-01,  -7.09602665e-02,   7.14285714e-02],\n       [  3.32158562e-01,  -1.18498191e-01,   7.14285714e-02],\n       [  3.25029158e-01,  -1.75896984e-01,   7.14285714e-02],\n       [  3.34988061e-01,  -5.96232379e-02,   7.14285714e-02],\n       [  2.61783533e-01,  -9.65724185e-02,   7.14285714e-02],\n       [  2.67675299e-01,  -1.44858628e-01,   7.14285714e-02],\n       [  2.55891766e-01,  -4.82862093e-02,   7.14285714e-02],\n       [  1.85787515e-01,  -5.54454306e-02,   7.14285714e-02],\n       [  1.15683264e-01,  -6.26046519e-02,   7.14285714e-02],\n       [  2.04908364e-01,  -1.10890861e-01,   7.14285714e-02],\n       [  1.48033195e-01,  -1.25209304e-01,   7.14285714e-02],\n       [  2.24029213e-01,  -1.66336292e-01,   7.14285714e-02],\n       [  1.80383126e-01,  -1.87813956e-01,   7.14285714e-02],\n       [  2.33135679e-01,  -2.47829956e-01,   7.14285714e-02],\n       [  2.85888233e-01,  -3.07845955e-01,   7.14285714e-02],\n       [  2.80876227e-01,  -2.13259647e-01,   7.14285714e-02],\n       [  3.37723241e-01,  -2.60183001e-01,   7.14285714e-02],\n       [  1.00000000e+00,   0.00000000e+00,  -7.14285714e-02],\n       [  9.86361303e-01,   1.64594590e-01,  -7.14285714e-02],\n       [  9.04164528e-01,   1.50878374e-01,  -7.14285714e-02],\n       [  9.16666667e-01,   1.66466002e-16,  -7.14285714e-02],\n       [  9.45817242e-01,   3.24699469e-01,  -7.14285714e-02],\n       [  8.66999138e-01,   2.97641180e-01,  -7.14285714e-02],\n       [  8.79473751e-01,   4.75947393e-01,  -7.14285714e-02],\n       [  8.06184272e-01,   4.36285110e-01,  -7.14285714e-02],\n       [  7.89140509e-01,   6.14212713e-01,  -7.14285714e-02],\n       [  7.23378800e-01,   5.63028320e-01,  -7.14285714e-02],\n       [  6.77281572e-01,   7.35723911e-01,  -7.14285714e-02],\n       [  6.20841441e-01,   6.74413585e-01,  -7.14285714e-02],\n       [  5.46948158e-01,   8.37166478e-01,  -7.14285714e-02],\n       [  5.01369145e-01,   7.67402605e-01,  -7.14285714e-02],\n       [  4.01695425e-01,   9.15773327e-01,  -7.14285714e-02],\n       [  3.68220806e-01,   8.39458883e-01,  -7.14285714e-02],\n       [  8.21967753e-01,   1.37162159e-01,  -7.14285714e-02],\n       [  8.33333333e-01,   2.29336017e-16,  -7.14285714e-02],\n       [  7.88181035e-01,   2.70582891e-01,  -7.14285714e-02],\n       [  7.32894793e-01,   3.96622828e-01,  -7.14285714e-02],\n       [  6.57617091e-01,   5.11843927e-01,  -7.14285714e-02],\n       [  5.64401310e-01,   6.13103259e-01,  -7.14285714e-02],\n       [  4.55790132e-01,   6.97638732e-01,  -7.14285714e-02],\n       [  3.34746187e-01,   7.63144439e-01,  -7.14285714e-02],\n       [  7.39770978e-01,   1.23445943e-01,  -7.14285714e-02],\n       [  7.50000000e-01,   2.30023465e-16,  -7.14285714e-02],\n       [  7.09362931e-01,   2.43524602e-01,  -7.14285714e-02],\n       [  6.59605313e-01,   3.56960545e-01,  -7.14285714e-02],\n       [  5.91855382e-01,   4.60659535e-01,  -7.14285714e-02],\n       [  5.07961179e-01,   5.51792933e-01,  -7.14285714e-02],\n       [  4.10211119e-01,   6.27874859e-01,  -7.14285714e-02],\n       [  3.01271568e-01,   6.86829995e-01,  -7.14285714e-02],\n       [  6.57574202e-01,   1.09729727e-01,  -7.14285714e-02],\n       [  6.66666667e-01,   2.14398012e-16,  -7.14285714e-02],\n       [  6.30544828e-01,   2.16466313e-01,  -7.14285714e-02],\n       [  5.86315834e-01,   3.17298262e-01,  -7.14285714e-02],\n       [  5.26093673e-01,   4.09475142e-01,  -7.14285714e-02],\n       [  4.51521048e-01,   4.90482607e-01,  -7.14285714e-02],\n       [  3.64632105e-01,   5.58110986e-01,  -7.14285714e-02],\n       [  2.67796950e-01,   6.10515551e-01,  -7.14285714e-02],\n       [  5.75377427e-01,   9.60135110e-02,  -7.14285714e-02],\n       [  5.83333333e-01,   1.93257395e-16,  -7.14285714e-02],\n       [  5.51726724e-01,   1.89408024e-01,  -7.14285714e-02],\n       [  5.13026355e-01,   2.77635979e-01,  -7.14285714e-02],\n       [  4.60331964e-01,   3.58290749e-01,  -7.14285714e-02],\n       [  3.95080917e-01,   4.29172281e-01,  -7.14285714e-02],\n       [  3.19053092e-01,   4.88347112e-01,  -7.14285714e-02],\n       [  2.34322331e-01,   5.34201107e-01,  -7.14285714e-02],\n       [  4.93180652e-01,   8.22972951e-02,  -7.14285714e-02],\n       [  5.00000000e-01,   1.69605680e-16,  -7.14285714e-02],\n       [  4.72908621e-01,   1.62349735e-01,  -7.14285714e-02],\n       [  4.39736876e-01,   2.37973697e-01,  -7.14285714e-02],\n       [  3.94570255e-01,   3.07106356e-01,  -7.14285714e-02],\n       [  3.38640786e-01,   3.67861955e-01,  -7.14285714e-02],\n       [  2.73474079e-01,   4.18583239e-01,  -7.14285714e-02],\n       [  2.00847712e-01,   4.57886663e-01,  -7.14285714e-02],\n       [  2.92606991e-01,   3.16445261e-01,  -7.14285714e-02],\n       [  3.44188184e-01,   2.67935253e-01,  -7.14285714e-02],\n       [  2.34867640e-01,   3.58265725e-01,  -7.14285714e-02],\n       [  1.72155182e-01,   3.92474283e-01,  -7.14285714e-02],\n       [  2.46573197e-01,   2.65028567e-01,  -7.14285714e-02],\n       [  2.93806114e-01,   2.28764151e-01,  -7.14285714e-02],\n       [  1.96261201e-01,   2.97948211e-01,  -7.14285714e-02],\n       [  1.43462652e-01,   3.27061902e-01,  -7.14285714e-02],\n       [  2.00539403e-01,   2.13611872e-01,  -7.14285714e-02],\n       [  2.43424043e-01,   1.89593048e-01,  -7.14285714e-02],\n       [  1.57654762e-01,   2.37630697e-01,  -7.14285714e-02],\n       [  1.14770121e-01,   2.61649522e-01,  -7.14285714e-02],\n       [  8.60775910e-02,   1.96237141e-01,  -7.14285714e-02],\n       [  1.39074405e-01,   1.78223023e-01,  -7.14285714e-02],\n       [  5.73850607e-02,   1.30824761e-01,  -7.14285714e-02],\n       [  1.20494048e-01,   1.18815349e-01,  -7.14285714e-02],\n       [  2.86925303e-02,   6.54123805e-02,  -7.14285714e-02],\n       [  1.01913690e-01,   5.94076743e-02,  -7.14285714e-02],\n       [ -7.26236114e-17,   1.61257875e-17,  -7.14285714e-02],\n       [  8.33333333e-02,   4.15242815e-17,  -7.14285714e-02],\n       [  1.92071219e-01,   1.60208904e-01,  -7.14285714e-02],\n       [  1.83603035e-01,   1.06805936e-01,  -7.14285714e-02],\n       [  1.75134851e-01,   5.34029681e-02,  -7.14285714e-02],\n       [  1.66666667e-01,   6.72165733e-17,  -7.14285714e-02],\n       [  2.45068032e-01,   1.42194786e-01,  -7.14285714e-02],\n       [  2.46712022e-01,   9.47965238e-02,  -7.14285714e-02],\n       [  2.48356011e-01,   4.73982619e-02,  -7.14285714e-02],\n       [  2.50000000e-01,   9.30984356e-17,  -7.14285714e-02],\n       [  3.33333333e-01,   1.18995828e-16,  -7.14285714e-02],\n       [  3.29964224e-01,   5.90312730e-02,  -7.14285714e-02],\n       [  4.16666667e-01,   1.44635879e-16,  -7.14285714e-02],\n       [  4.11572438e-01,   7.06642841e-02,  -7.14285714e-02],\n       [  3.22110888e-01,   1.17314261e-01,  -7.14285714e-02],\n       [  3.97509754e-01,   1.39831998e-01,  -7.14285714e-02],\n       [  3.09957647e-01,   1.74121089e-01,  -7.14285714e-02],\n       [  3.74847261e-01,   2.06047393e-01,  -7.14285714e-02],\n       [  2.45485487e-01,   9.69400266e-01,  -7.14285714e-02],\n       [  2.25028363e-01,   8.88616910e-01,  -7.14285714e-02],\n       [  8.25793455e-02,   9.96584493e-01,  -7.14285714e-02],\n       [  7.56977333e-02,   9.13535785e-01,  -7.14285714e-02],\n       [ -8.25793455e-02,   9.96584493e-01,  -7.14285714e-02],\n       [ -7.56977333e-02,   9.13535785e-01,  -7.14285714e-02],\n       [ -2.45485487e-01,   9.69400266e-01,  -7.14285714e-02],\n       [ -2.25028363e-01,   8.88616910e-01,  -7.14285714e-02],\n       [ -4.01695425e-01,   9.15773327e-01,  -7.14285714e-02],\n       [ -3.68220806e-01,   8.39458883e-01,  -7.14285714e-02],\n       [ -5.46948158e-01,   8.37166478e-01,  -7.14285714e-02],\n       [ -5.01369145e-01,   7.67402605e-01,  -7.14285714e-02],\n       [ -6.77281572e-01,   7.35723911e-01,  -7.14285714e-02],\n       [ -6.20841441e-01,   6.74413585e-01,  -7.14285714e-02],\n       [  2.04571239e-01,   8.07833555e-01,  -7.14285714e-02],\n       [  6.88161212e-02,   8.30487078e-01,  -7.14285714e-02],\n       [ -6.88161212e-02,   8.30487078e-01,  -7.14285714e-02],\n       [ -2.04571239e-01,   8.07833555e-01,  -7.14285714e-02],\n       [ -3.34746187e-01,   7.63144439e-01,  -7.14285714e-02],\n       [ -4.55790132e-01,   6.97638732e-01,  -7.14285714e-02],\n       [ -5.64401310e-01,   6.13103259e-01,  -7.14285714e-02],\n       [  1.84114115e-01,   7.27050199e-01,  -7.14285714e-02],\n       [  6.19345091e-02,   7.47438370e-01,  -7.14285714e-02],\n       [ -6.19345091e-02,   7.47438370e-01,  -7.14285714e-02],\n       [ -1.84114115e-01,   7.27050199e-01,  -7.14285714e-02],\n       [ -3.01271568e-01,   6.86829995e-01,  -7.14285714e-02],\n       [ -4.10211119e-01,   6.27874859e-01,  -7.14285714e-02],\n       [ -5.07961179e-01,   5.51792933e-01,  -7.14285714e-02],\n       [  1.63656991e-01,   6.46266844e-01,  -7.14285714e-02],\n       [  5.50528970e-02,   6.64389662e-01,  -7.14285714e-02],\n       [ -5.50528970e-02,   6.64389662e-01,  -7.14285714e-02],\n       [ -1.63656991e-01,   6.46266844e-01,  -7.14285714e-02],\n       [ -2.67796950e-01,   6.10515551e-01,  -7.14285714e-02],\n       [ -3.64632105e-01,   5.58110986e-01,  -7.14285714e-02],\n       [ -4.51521048e-01,   4.90482607e-01,  -7.14285714e-02],\n       [  1.43199867e-01,   5.65483488e-01,  -7.14285714e-02],\n       [  4.81712849e-02,   5.81340954e-01,  -7.14285714e-02],\n       [ -4.81712849e-02,   5.81340954e-01,  -7.14285714e-02],\n       [ -1.43199867e-01,   5.65483488e-01,  -7.14285714e-02],\n       [ -2.34322331e-01,   5.34201107e-01,  -7.14285714e-02],\n       [ -3.19053092e-01,   4.88347112e-01,  -7.14285714e-02],\n       [ -3.95080917e-01,   4.29172281e-01,  -7.14285714e-02],\n       [  1.22742744e-01,   4.84700133e-01,  -7.14285714e-02],\n       [  4.12896727e-02,   4.98292247e-01,  -7.14285714e-02],\n       [ -4.12896727e-02,   4.98292247e-01,  -7.14285714e-02],\n       [ -1.22742744e-01,   4.84700133e-01,  -7.14285714e-02],\n       [ -2.00847712e-01,   4.57886663e-01,  -7.14285714e-02],\n       [ -2.73474079e-01,   4.18583239e-01,  -7.14285714e-02],\n       [ -3.38640786e-01,   3.67861955e-01,  -7.14285714e-02],\n       [ -1.02283149e-01,   4.15336195e-01,  -7.14285714e-02],\n       [ -3.59859419e-02,   4.34695086e-01,  -7.14285714e-02],\n       [ -1.66348287e-01,   3.87163066e-01,  -7.14285714e-02],\n       [ -2.26761024e-01,   3.50663301e-01,  -7.14285714e-02],\n       [ -2.82200655e-01,   3.06551629e-01,  -7.14285714e-02],\n       [ -8.18235534e-02,   3.45972257e-01,  -7.14285714e-02],\n       [ -3.06822110e-02,   3.71097926e-01,  -7.14285714e-02],\n       [ -1.31848862e-01,   3.16439469e-01,  -7.14285714e-02],\n       [ -1.80047970e-01,   2.82743363e-01,  -7.14285714e-02],\n       [ -2.25760524e-01,   2.45241304e-01,  -7.14285714e-02],\n       [ -6.13639584e-02,   2.76608319e-01,  -7.14285714e-02],\n       [ -2.53784802e-02,   3.07500766e-01,  -7.14285714e-02],\n       [ -9.73494365e-02,   2.45715872e-01,  -7.14285714e-02],\n       [ -1.33334915e-01,   2.14823425e-01,  -7.14285714e-02],\n       [ -1.69320393e-01,   1.83930978e-01,  -7.14285714e-02],\n       [ -1.12880262e-01,   1.22620652e-01,  -7.14285714e-02],\n       [ -7.93257664e-02,   1.65019743e-01,  -7.14285714e-02],\n       [ -5.64401310e-02,   6.13103259e-02,  -7.14285714e-02],\n       [ -2.53166180e-02,   1.15216062e-01,  -7.14285714e-02],\n       [ -4.57712708e-02,   2.07418835e-01,  -7.14285714e-02],\n       [  5.80689493e-03,   1.69121798e-01,  -7.14285714e-02],\n       [ -1.22167752e-02,   2.49817927e-01,  -7.14285714e-02],\n       [  3.69304079e-02,   2.23027534e-01,  -7.14285714e-02],\n       [  2.13377203e-02,   2.92217018e-01,  -7.14285714e-02],\n       [  6.80539208e-02,   2.76933270e-01,  -7.14285714e-02],\n       [  8.62835284e-02,   3.46188891e-01,  -7.14285714e-02],\n       [  1.04513136e-01,   4.15444512e-01,  -7.14285714e-02],\n       [  2.79883711e-02,   3.60908761e-01,  -7.14285714e-02],\n       [  3.46390219e-02,   4.29600504e-01,  -7.14285714e-02],\n       [ -7.89140509e-01,   6.14212713e-01,  -7.14285714e-02],\n       [ -7.23378800e-01,   5.63028320e-01,  -7.14285714e-02],\n       [ -8.79473751e-01,   4.75947393e-01,  -7.14285714e-02],\n       [ -8.06184272e-01,   4.36285110e-01,  -7.14285714e-02],\n       [ -9.45817242e-01,   3.24699469e-01,  -7.14285714e-02],\n       [ -8.66999138e-01,   2.97641180e-01,  -7.14285714e-02],\n       [ -9.86361303e-01,   1.64594590e-01,  -7.14285714e-02],\n       [ -9.04164528e-01,   1.50878374e-01,  -7.14285714e-02],\n       [ -1.00000000e+00,  -1.74949543e-17,  -7.14285714e-02],\n       [ -9.16666667e-01,  -2.67544510e-17,  -7.14285714e-02],\n       [ -9.86361303e-01,  -1.64594590e-01,  -7.14285714e-02],\n       [ -9.04164528e-01,  -1.50878374e-01,  -7.14285714e-02],\n       [ -6.57617091e-01,   5.11843927e-01,  -7.14285714e-02],\n       [ -7.32894793e-01,   3.96622828e-01,  -7.14285714e-02],\n       [ -7.88181035e-01,   2.70582891e-01,  -7.14285714e-02],\n       [ -8.21967753e-01,   1.37162159e-01,  -7.14285714e-02],\n       [ -8.33333333e-01,  -2.31382435e-17,  -7.14285714e-02],\n       [ -8.21967753e-01,  -1.37162159e-01,  -7.14285714e-02],\n       [ -5.91855382e-01,   4.60659535e-01,  -7.14285714e-02],\n       [ -6.59605313e-01,   3.56960545e-01,  -7.14285714e-02],\n       [ -7.09362931e-01,   2.43524602e-01,  -7.14285714e-02],\n       [ -7.39770978e-01,   1.23445943e-01,  -7.14285714e-02],\n       [ -7.50000000e-01,  -2.03986436e-17,  -7.14285714e-02],\n       [ -7.39770978e-01,  -1.23445943e-01,  -7.14285714e-02],\n       [ -5.26093673e-01,   4.09475142e-01,  -7.14285714e-02],\n       [ -5.86315834e-01,   3.17298262e-01,  -7.14285714e-02],\n       [ -6.30544828e-01,   2.16466313e-01,  -7.14285714e-02],\n       [ -6.57574202e-01,   1.09729727e-01,  -7.14285714e-02],\n       [ -6.66666667e-01,  -1.78879673e-17,  -7.14285714e-02],\n       [ -6.57574202e-01,  -1.09729727e-01,  -7.14285714e-02],\n       [ -4.60331964e-01,   3.58290749e-01,  -7.14285714e-02],\n       [ -5.13026355e-01,   2.77635979e-01,  -7.14285714e-02],\n       [ -5.51726724e-01,   1.89408024e-01,  -7.14285714e-02],\n       [ -5.75377427e-01,   9.60135110e-02,  -7.14285714e-02],\n       [ -5.83333333e-01,  -1.52297412e-17,  -7.14285714e-02],\n       [ -5.75377427e-01,  -9.60135110e-02,  -7.14285714e-02],\n       [ -3.94570255e-01,   3.07106356e-01,  -7.14285714e-02],\n       [ -4.39736876e-01,   2.37973697e-01,  -7.14285714e-02],\n       [ -4.72908621e-01,   1.62349735e-01,  -7.14285714e-02],\n       [ -4.93180652e-01,   8.22972951e-02,  -7.14285714e-02],\n       [ -5.00000000e-01,  -1.22050366e-17,  -7.14285714e-02],\n       [ -4.93180652e-01,  -8.22972951e-02,  -7.14285714e-02],\n       [ -4.20156583e-01,   7.22539112e-02,  -7.14285714e-02],\n       [ -4.11228248e-01,   1.41174836e-01,  -7.14285714e-02],\n       [ -4.20116462e-01,   1.83641595e-03,  -7.14285714e-02],\n       [ -4.10983876e-01,  -6.85810793e-02,  -7.14285714e-02],\n       [ -3.47132513e-01,   6.22105272e-02,  -7.14285714e-02],\n       [ -3.49547876e-01,   1.19999937e-01,  -7.14285714e-02],\n       [ -3.40232923e-01,   3.67283191e-03,  -7.14285714e-02],\n       [ -3.28787101e-01,  -5.48648634e-02,  -7.14285714e-02],\n       [ -2.74108444e-01,   5.21671433e-02,  -7.14285714e-02],\n       [ -2.87867503e-01,   9.88250387e-02,  -7.14285714e-02],\n       [ -2.60349385e-01,   5.50924786e-03,  -7.14285714e-02],\n       [ -2.46590326e-01,  -4.11486476e-02,  -7.14285714e-02],\n       [ -1.64393551e-01,  -2.74324317e-02,  -7.14285714e-02],\n       [ -1.92379634e-01,   2.41096072e-02,  -7.14285714e-02],\n       [ -8.21967753e-02,  -1.37162159e-02,  -7.14285714e-02],\n       [ -1.24409882e-01,   4.27099665e-02,  -7.14285714e-02],\n       [ -2.20365717e-01,   7.56516461e-02,  -7.14285714e-02],\n       [ -1.66622989e-01,   9.91361489e-02,  -7.14285714e-02],\n       [ -2.48351800e-01,   1.27193685e-01,  -7.14285714e-02],\n       [ -2.08836096e-01,   1.55562331e-01,  -7.14285714e-02],\n       [ -2.70747482e-01,   2.06077006e-01,  -7.14285714e-02],\n       [ -3.32658869e-01,   2.56591681e-01,  -7.14285714e-02],\n       [ -3.12146825e-01,   1.64120356e-01,  -7.14285714e-02],\n       [ -3.75941850e-01,   2.01047026e-01,  -7.14285714e-02],\n       [ -9.45817242e-01,  -3.24699469e-01,  -7.14285714e-02],\n       [ -8.66999138e-01,  -2.97641180e-01,  -7.14285714e-02],\n       [ -8.79473751e-01,  -4.75947393e-01,  -7.14285714e-02],\n       [ -8.06184272e-01,  -4.36285110e-01,  -7.14285714e-02],\n       [ -7.89140509e-01,  -6.14212713e-01,  -7.14285714e-02],\n       [ -7.23378800e-01,  -5.63028320e-01,  -7.14285714e-02],\n       [ -6.77281572e-01,  -7.35723911e-01,  -7.14285714e-02],\n       [ -6.20841441e-01,  -6.74413585e-01,  -7.14285714e-02],\n       [ -5.46948158e-01,  -8.37166478e-01,  -7.14285714e-02],\n       [ -5.01369145e-01,  -7.67402605e-01,  -7.14285714e-02],\n       [ -4.01695425e-01,  -9.15773327e-01,  -7.14285714e-02],\n       [ -3.68220806e-01,  -8.39458883e-01,  -7.14285714e-02],\n       [ -7.88181035e-01,  -2.70582891e-01,  -7.14285714e-02],\n       [ -7.32894793e-01,  -3.96622828e-01,  -7.14285714e-02],\n       [ -6.57617091e-01,  -5.11843927e-01,  -7.14285714e-02],\n       [ -5.64401310e-01,  -6.13103259e-01,  -7.14285714e-02],\n       [ -4.55790132e-01,  -6.97638732e-01,  -7.14285714e-02],\n       [ -3.34746187e-01,  -7.63144439e-01,  -7.14285714e-02],\n       [ -7.09362931e-01,  -2.43524602e-01,  -7.14285714e-02],\n       [ -6.59605313e-01,  -3.56960545e-01,  -7.14285714e-02],\n       [ -5.91855382e-01,  -4.60659535e-01,  -7.14285714e-02],\n       [ -5.07961179e-01,  -5.51792933e-01,  -7.14285714e-02],\n       [ -4.10211119e-01,  -6.27874859e-01,  -7.14285714e-02],\n       [ -3.01271568e-01,  -6.86829995e-01,  -7.14285714e-02],\n       [ -6.30544828e-01,  -2.16466313e-01,  -7.14285714e-02],\n       [ -5.86315834e-01,  -3.17298262e-01,  -7.14285714e-02],\n       [ -5.26093673e-01,  -4.09475142e-01,  -7.14285714e-02],\n       [ -4.51521048e-01,  -4.90482607e-01,  -7.14285714e-02],\n       [ -3.64632105e-01,  -5.58110986e-01,  -7.14285714e-02],\n       [ -2.67796950e-01,  -6.10515551e-01,  -7.14285714e-02],\n       [ -5.51726724e-01,  -1.89408024e-01,  -7.14285714e-02],\n       [ -5.13026355e-01,  -2.77635979e-01,  -7.14285714e-02],\n       [ -4.60331964e-01,  -3.58290749e-01,  -7.14285714e-02],\n       [ -3.95080917e-01,  -4.29172281e-01,  -7.14285714e-02],\n       [ -3.19053092e-01,  -4.88347112e-01,  -7.14285714e-02],\n       [ -2.34322331e-01,  -5.34201107e-01,  -7.14285714e-02],\n       [ -4.72908621e-01,  -1.62349735e-01,  -7.14285714e-02],\n       [ -4.39736876e-01,  -2.37973697e-01,  -7.14285714e-02],\n       [ -3.94570255e-01,  -3.07106356e-01,  -7.14285714e-02],\n       [ -3.38640786e-01,  -3.67861955e-01,  -7.14285714e-02],\n       [ -2.73474079e-01,  -4.18583239e-01,  -7.14285714e-02],\n       [ -2.00847712e-01,  -4.57886663e-01,  -7.14285714e-02],\n       [ -2.90292421e-01,  -3.12221863e-01,  -7.14285714e-02],\n       [ -3.43107373e-01,  -2.67051188e-01,  -7.14285714e-02],\n       [ -2.31319311e-01,  -3.50702994e-01,  -7.14285714e-02],\n       [ -1.67373094e-01,  -3.81572219e-01,  -7.14285714e-02],\n       [ -2.41944057e-01,  -2.56581770e-01,  -7.14285714e-02],\n       [ -2.91644492e-01,  -2.26996019e-01,  -7.14285714e-02],\n       [ -1.89164543e-01,  -2.82822750e-01,  -7.14285714e-02],\n       [ -1.33898475e-01,  -3.05257776e-01,  -7.14285714e-02],\n       [ -1.93595692e-01,  -2.00941678e-01,  -7.14285714e-02],\n       [ -2.40181610e-01,  -1.86940851e-01,  -7.14285714e-02],\n       [ -1.47009774e-01,  -2.14942505e-01,  -7.14285714e-02],\n       [ -1.00423856e-01,  -2.28943332e-01,  -7.14285714e-02],\n       [ -6.69492374e-02,  -1.52628888e-01,  -7.14285714e-02],\n       [ -1.25405441e-01,  -1.47867075e-01,  -7.14285714e-02],\n       [ -3.34746187e-02,  -7.63144439e-02,  -7.14285714e-02],\n       [ -1.03801108e-01,  -8.07916455e-02,  -7.14285714e-02],\n       [ -1.83861645e-01,  -1.43105263e-01,  -7.14285714e-02],\n       [ -1.74127598e-01,  -8.52688471e-02,  -7.14285714e-02],\n       [ -2.42317849e-01,  -1.38343450e-01,  -7.14285714e-02],\n       [ -2.44454087e-01,  -8.97460487e-02,  -7.14285714e-02],\n       [ -3.20605599e-01,  -1.13947277e-01,  -7.14285714e-02],\n       [ -3.96757110e-01,  -1.38148506e-01,  -7.14285714e-02],\n       [ -3.08124191e-01,  -1.71553532e-01,  -7.14285714e-02],\n       [ -3.73930533e-01,  -2.04763614e-01,  -7.14285714e-02],\n       [ -2.45485487e-01,  -9.69400266e-01,  -7.14285714e-02],\n       [ -2.25028363e-01,  -8.88616910e-01,  -7.14285714e-02],\n       [ -8.25793455e-02,  -9.96584493e-01,  -7.14285714e-02],\n       [ -7.56977333e-02,  -9.13535785e-01,  -7.14285714e-02],\n       [  8.25793455e-02,  -9.96584493e-01,  -7.14285714e-02],\n       [  7.56977333e-02,  -9.13535785e-01,  -7.14285714e-02],\n       [  2.45485487e-01,  -9.69400266e-01,  -7.14285714e-02],\n       [  2.25028363e-01,  -8.88616910e-01,  -7.14285714e-02],\n       [  4.01695425e-01,  -9.15773327e-01,  -7.14285714e-02],\n       [  3.68220806e-01,  -8.39458883e-01,  -7.14285714e-02],\n       [  5.46948158e-01,  -8.37166478e-01,  -7.14285714e-02],\n       [  5.01369145e-01,  -7.67402605e-01,  -7.14285714e-02],\n       [ -2.04571239e-01,  -8.07833555e-01,  -7.14285714e-02],\n       [ -6.88161212e-02,  -8.30487078e-01,  -7.14285714e-02],\n       [  6.88161212e-02,  -8.30487078e-01,  -7.14285714e-02],\n       [  2.04571239e-01,  -8.07833555e-01,  -7.14285714e-02],\n       [  3.34746187e-01,  -7.63144439e-01,  -7.14285714e-02],\n       [  4.55790132e-01,  -6.97638732e-01,  -7.14285714e-02],\n       [ -1.84114115e-01,  -7.27050199e-01,  -7.14285714e-02],\n       [ -6.19345091e-02,  -7.47438370e-01,  -7.14285714e-02],\n       [  6.19345091e-02,  -7.47438370e-01,  -7.14285714e-02],\n       [  1.84114115e-01,  -7.27050199e-01,  -7.14285714e-02],\n       [  3.01271568e-01,  -6.86829995e-01,  -7.14285714e-02],\n       [  4.10211119e-01,  -6.27874859e-01,  -7.14285714e-02],\n       [ -1.63656991e-01,  -6.46266844e-01,  -7.14285714e-02],\n       [ -5.50528970e-02,  -6.64389662e-01,  -7.14285714e-02],\n       [  5.50528970e-02,  -6.64389662e-01,  -7.14285714e-02],\n       [  1.63656991e-01,  -6.46266844e-01,  -7.14285714e-02],\n       [  2.67796950e-01,  -6.10515551e-01,  -7.14285714e-02],\n       [  3.64632105e-01,  -5.58110986e-01,  -7.14285714e-02],\n       [ -1.43199867e-01,  -5.65483488e-01,  -7.14285714e-02],\n       [ -4.81712849e-02,  -5.81340954e-01,  -7.14285714e-02],\n       [  4.81712849e-02,  -5.81340954e-01,  -7.14285714e-02],\n       [  1.43199867e-01,  -5.65483488e-01,  -7.14285714e-02],\n       [  2.34322331e-01,  -5.34201107e-01,  -7.14285714e-02],\n       [  3.19053092e-01,  -4.88347112e-01,  -7.14285714e-02],\n       [ -1.22742744e-01,  -4.84700133e-01,  -7.14285714e-02],\n       [ -4.12896727e-02,  -4.98292247e-01,  -7.14285714e-02],\n       [  4.12896727e-02,  -4.98292247e-01,  -7.14285714e-02],\n       [  1.22742744e-01,  -4.84700133e-01,  -7.14285714e-02],\n       [  2.00847712e-01,  -4.57886663e-01,  -7.14285714e-02],\n       [  2.73474079e-01,  -4.18583239e-01,  -7.14285714e-02],\n       [  1.02606772e-01,  -4.13792257e-01,  -7.14285714e-02],\n       [  3.59043567e-02,  -4.33301147e-01,  -7.14285714e-02],\n       [  1.67077120e-01,  -3.85469130e-01,  -7.14285714e-02],\n       [  2.27895066e-01,  -3.48819366e-01,  -7.14285714e-02],\n       [  8.24708009e-02,  -3.42884381e-01,  -7.14285714e-02],\n       [  3.05190406e-02,  -3.68310047e-01,  -7.14285714e-02],\n       [  1.33306527e-01,  -3.13051596e-01,  -7.14285714e-02],\n       [  1.82316053e-01,  -2.79055493e-01,  -7.14285714e-02],\n       [  6.23348295e-02,  -2.71976505e-01,  -7.14285714e-02],\n       [  2.51337245e-02,  -3.03318947e-01,  -7.14285714e-02],\n       [  9.95359345e-02,  -2.40634062e-01,  -7.14285714e-02],\n       [  1.36737040e-01,  -2.09291620e-01,  -7.14285714e-02],\n       [  9.11580264e-02,  -1.39527746e-01,  -7.14285714e-02],\n       [  5.51990834e-02,  -1.85860856e-01,  -7.14285714e-02],\n       [  4.55790132e-02,  -6.97638732e-02,  -7.14285714e-02],\n       [  1.08622324e-02,  -1.31087650e-01,  -7.14285714e-02],\n       [  1.92401405e-02,  -2.32193966e-01,  -7.14285714e-02],\n       [ -2.38545485e-02,  -1.92411427e-01,  -7.14285714e-02],\n       [ -1.67188024e-02,  -2.78527075e-01,  -7.14285714e-02],\n       [ -5.85713293e-02,  -2.53735203e-01,  -7.14285714e-02],\n       [ -7.99618007e-02,  -3.30723513e-01,  -7.14285714e-02],\n       [ -1.01352272e-01,  -4.07711823e-01,  -7.14285714e-02],\n       [ -2.49090925e-02,  -3.51782132e-01,  -7.14285714e-02],\n       [ -3.30993826e-02,  -4.25037189e-01,  -7.14285714e-02],\n       [  6.77281572e-01,  -7.35723911e-01,  -7.14285714e-02],\n       [  6.20841441e-01,  -6.74413585e-01,  -7.14285714e-02],\n       [  7.89140509e-01,  -6.14212713e-01,  -7.14285714e-02],\n       [  7.23378800e-01,  -5.63028320e-01,  -7.14285714e-02],\n       [  8.79473751e-01,  -4.75947393e-01,  -7.14285714e-02],\n       [  8.06184272e-01,  -4.36285110e-01,  -7.14285714e-02],\n       [  9.45817242e-01,  -3.24699469e-01,  -7.14285714e-02],\n       [  8.66999138e-01,  -2.97641180e-01,  -7.14285714e-02],\n       [  9.86361303e-01,  -1.64594590e-01,  -7.14285714e-02],\n       [  9.04164528e-01,  -1.50878374e-01,  -7.14285714e-02],\n       [  5.64401310e-01,  -6.13103259e-01,  -7.14285714e-02],\n       [  6.57617091e-01,  -5.11843927e-01,  -7.14285714e-02],\n       [  7.32894793e-01,  -3.96622828e-01,  -7.14285714e-02],\n       [  7.88181035e-01,  -2.70582891e-01,  -7.14285714e-02],\n       [  8.21967753e-01,  -1.37162159e-01,  -7.14285714e-02],\n       [  5.07961179e-01,  -5.51792933e-01,  -7.14285714e-02],\n       [  5.91855382e-01,  -4.60659535e-01,  -7.14285714e-02],\n       [  6.59605313e-01,  -3.56960545e-01,  -7.14285714e-02],\n       [  7.09362931e-01,  -2.43524602e-01,  -7.14285714e-02],\n       [  7.39770978e-01,  -1.23445943e-01,  -7.14285714e-02],\n       [  4.51521048e-01,  -4.90482607e-01,  -7.14285714e-02],\n       [  5.26093673e-01,  -4.09475142e-01,  -7.14285714e-02],\n       [  5.86315834e-01,  -3.17298262e-01,  -7.14285714e-02],\n       [  6.30544828e-01,  -2.16466313e-01,  -7.14285714e-02],\n       [  6.57574202e-01,  -1.09729727e-01,  -7.14285714e-02],\n       [  3.95080917e-01,  -4.29172281e-01,  -7.14285714e-02],\n       [  4.60331964e-01,  -3.58290749e-01,  -7.14285714e-02],\n       [  5.13026355e-01,  -2.77635979e-01,  -7.14285714e-02],\n       [  5.51726724e-01,  -1.89408024e-01,  -7.14285714e-02],\n       [  5.75377427e-01,  -9.60135110e-02,  -7.14285714e-02],\n       [  3.38640786e-01,  -3.67861955e-01,  -7.14285714e-02],\n       [  3.94570255e-01,  -3.07106356e-01,  -7.14285714e-02],\n       [  4.39736876e-01,  -2.37973697e-01,  -7.14285714e-02],\n       [  4.72908621e-01,  -1.62349735e-01,  -7.14285714e-02],\n       [  4.93180652e-01,  -8.22972951e-02,  -7.14285714e-02],\n       [  4.02533591e-01,  -1.40423963e-01,  -7.14285714e-02],\n       [  3.82383017e-01,  -2.06935340e-01,  -7.14285714e-02],\n       [  4.14084357e-01,  -7.09602665e-02,  -7.14285714e-02],\n       [  3.32158562e-01,  -1.18498191e-01,  -7.14285714e-02],\n       [  3.25029158e-01,  -1.75896984e-01,  -7.14285714e-02],\n       [  3.34988061e-01,  -5.96232379e-02,  -7.14285714e-02],\n       [  2.61783533e-01,  -9.65724185e-02,  -7.14285714e-02],\n       [  2.67675299e-01,  -1.44858628e-01,  -7.14285714e-02],\n       [  2.55891766e-01,  -4.82862093e-02,  -7.14285714e-02],\n       [  1.85787515e-01,  -5.54454306e-02,  -7.14285714e-02],\n       [  1.15683264e-01,  -6.26046519e-02,  -7.14285714e-02],\n       [  2.04908364e-01,  -1.10890861e-01,  -7.14285714e-02],\n       [  1.48033195e-01,  -1.25209304e-01,  -7.14285714e-02],\n       [  2.24029213e-01,  -1.66336292e-01,  -7.14285714e-02],\n       [  1.80383126e-01,  -1.87813956e-01,  -7.14285714e-02],\n       [  2.33135679e-01,  -2.47829956e-01,  -7.14285714e-02],\n       [  2.85888233e-01,  -3.07845955e-01,  -7.14285714e-02],\n       [  2.80876227e-01,  -2.13259647e-01,  -7.14285714e-02],\n       [  3.37723241e-01,  -2.60183001e-01,  -7.14285714e-02],\n       [  1.00000000e+00,   0.00000000e+00,  -2.14285714e-01],\n       [  9.86361303e-01,   1.64594590e-01,  -2.14285714e-01],\n       [  9.04164528e-01,   1.50878374e-01,  -2.14285714e-01],\n       [  9.16666667e-01,   1.69309911e-16,  -2.14285714e-01],\n       [  9.45817242e-01,   3.24699469e-01,  -2.14285714e-01],\n       [  8.66999138e-01,   2.97641180e-01,  -2.14285714e-01],\n       [  8.79473751e-01,   4.75947393e-01,  -2.14285714e-01],\n       [  8.06184272e-01,   4.36285110e-01,  -2.14285714e-01],\n       [  7.89140509e-01,   6.14212713e-01,  -2.14285714e-01],\n       [  7.23378800e-01,   5.63028320e-01,  -2.14285714e-01],\n       [  6.77281572e-01,   7.35723911e-01,  -2.14285714e-01],\n       [  6.20841441e-01,   6.74413585e-01,  -2.14285714e-01],\n       [  5.46948158e-01,   8.37166478e-01,  -2.14285714e-01],\n       [  5.01369145e-01,   7.67402605e-01,  -2.14285714e-01],\n       [  4.01695425e-01,   9.15773327e-01,  -2.14285714e-01],\n       [  3.68220806e-01,   8.39458883e-01,  -2.14285714e-01],\n       [  8.21967753e-01,   1.37162159e-01,  -2.14285714e-01],\n       [  8.33333333e-01,   2.52036419e-16,  -2.14285714e-01],\n       [  7.88181035e-01,   2.70582891e-01,  -2.14285714e-01],\n       [  7.32894793e-01,   3.96622828e-01,  -2.14285714e-01],\n       [  6.57617091e-01,   5.11843927e-01,  -2.14285714e-01],\n       [  5.64401310e-01,   6.13103259e-01,  -2.14285714e-01],\n       [  4.55790132e-01,   6.97638732e-01,  -2.14285714e-01],\n       [  3.34746187e-01,   7.63144439e-01,  -2.14285714e-01],\n       [  7.39770978e-01,   1.23445943e-01,  -2.14285714e-01],\n       [  7.50000000e-01,   2.57034718e-16,  -2.14285714e-01],\n       [  7.09362931e-01,   2.43524602e-01,  -2.14285714e-01],\n       [  6.59605313e-01,   3.56960545e-01,  -2.14285714e-01],\n       [  5.91855382e-01,   4.60659535e-01,  -2.14285714e-01],\n       [  5.07961179e-01,   5.51792933e-01,  -2.14285714e-01],\n       [  4.10211119e-01,   6.27874859e-01,  -2.14285714e-01],\n       [  3.01271568e-01,   6.86829995e-01,  -2.14285714e-01],\n       [  6.57574202e-01,   1.09729727e-01,  -2.14285714e-01],\n       [  6.66666667e-01,   2.41641892e-16,  -2.14285714e-01],\n       [  6.30544828e-01,   2.16466313e-01,  -2.14285714e-01],\n       [  5.86315834e-01,   3.17298262e-01,  -2.14285714e-01],\n       [  5.26093673e-01,   4.09475142e-01,  -2.14285714e-01],\n       [  4.51521048e-01,   4.90482607e-01,  -2.14285714e-01],\n       [  3.64632105e-01,   5.58110986e-01,  -2.14285714e-01],\n       [  2.67796950e-01,   6.10515551e-01,  -2.14285714e-01],\n       [  5.75377427e-01,   9.60135110e-02,  -2.14285714e-01],\n       [  5.83333333e-01,   2.19355109e-16,  -2.14285714e-01],\n       [  5.51726724e-01,   1.89408024e-01,  -2.14285714e-01],\n       [  5.13026355e-01,   2.77635979e-01,  -2.14285714e-01],\n       [  4.60331964e-01,   3.58290749e-01,  -2.14285714e-01],\n       [  3.95080917e-01,   4.29172281e-01,  -2.14285714e-01],\n       [  3.19053092e-01,   4.88347112e-01,  -2.14285714e-01],\n       [  2.34322331e-01,   5.34201107e-01,  -2.14285714e-01],\n       [  4.93180652e-01,   8.22972951e-02,  -2.14285714e-01],\n       [  5.00000000e-01,   1.93929455e-16,  -2.14285714e-01],\n       [  4.72908621e-01,   1.62349735e-01,  -2.14285714e-01],\n       [  4.39736876e-01,   2.37973697e-01,  -2.14285714e-01],\n       [  3.94570255e-01,   3.07106356e-01,  -2.14285714e-01],\n       [  3.38640786e-01,   3.67861955e-01,  -2.14285714e-01],\n       [  2.73474079e-01,   4.18583239e-01,  -2.14285714e-01],\n       [  2.00847712e-01,   4.57886663e-01,  -2.14285714e-01],\n       [  2.92606991e-01,   3.16445261e-01,  -2.14285714e-01],\n       [  3.44188184e-01,   2.67935253e-01,  -2.14285714e-01],\n       [  2.34867640e-01,   3.58265725e-01,  -2.14285714e-01],\n       [  1.72155182e-01,   3.92474283e-01,  -2.14285714e-01],\n       [  2.46573197e-01,   2.65028567e-01,  -2.14285714e-01],\n       [  2.93806114e-01,   2.28764151e-01,  -2.14285714e-01],\n       [  1.96261201e-01,   2.97948211e-01,  -2.14285714e-01],\n       [  1.43462652e-01,   3.27061902e-01,  -2.14285714e-01],\n       [  2.00539403e-01,   2.13611872e-01,  -2.14285714e-01],\n       [  2.43424043e-01,   1.89593048e-01,  -2.14285714e-01],\n       [  1.57654762e-01,   2.37630697e-01,  -2.14285714e-01],\n       [  1.14770121e-01,   2.61649522e-01,  -2.14285714e-01],\n       [  8.60775910e-02,   1.96237141e-01,  -2.14285714e-01],\n       [  1.39074405e-01,   1.78223023e-01,  -2.14285714e-01],\n       [  5.73850607e-02,   1.30824761e-01,  -2.14285714e-01],\n       [  1.20494048e-01,   1.18815349e-01,  -2.14285714e-01],\n       [  2.86925303e-02,   6.54123805e-02,  -2.14285714e-01],\n       [  1.01913690e-01,   5.94076743e-02,  -2.14285714e-01],\n       [  1.14778696e-17,   2.69135258e-17,  -2.14285714e-01],\n       [  8.33333333e-02,   5.45226540e-17,  -2.14285714e-01],\n       [  1.92071219e-01,   1.60208904e-01,  -2.14285714e-01],\n       [  1.83603035e-01,   1.06805936e-01,  -2.14285714e-01],\n       [  1.75134851e-01,   5.34029681e-02,  -2.14285714e-01],\n       [  1.66666667e-01,   8.24990293e-17,  -2.14285714e-01],\n       [  2.45068032e-01,   1.42194786e-01,  -2.14285714e-01],\n       [  2.46712022e-01,   9.47965238e-02,  -2.14285714e-01],\n       [  2.48356011e-01,   4.73982619e-02,  -2.14285714e-01],\n       [  2.50000000e-01,   1.10712368e-16,  -2.14285714e-01],\n       [  3.33333333e-01,   1.38945119e-16,  -2.14285714e-01],\n       [  3.29964224e-01,   5.90312730e-02,  -2.14285714e-01],\n       [  4.16666667e-01,   1.66856194e-16,  -2.14285714e-01],\n       [  4.11572438e-01,   7.06642841e-02,  -2.14285714e-01],\n       [  3.22110888e-01,   1.17314261e-01,  -2.14285714e-01],\n       [  3.97509754e-01,   1.39831998e-01,  -2.14285714e-01],\n       [  3.09957647e-01,   1.74121089e-01,  -2.14285714e-01],\n       [  3.74847261e-01,   2.06047393e-01,  -2.14285714e-01],\n       [  2.45485487e-01,   9.69400266e-01,  -2.14285714e-01],\n       [  2.25028363e-01,   8.88616910e-01,  -2.14285714e-01],\n       [  8.25793455e-02,   9.96584493e-01,  -2.14285714e-01],\n       [  7.56977333e-02,   9.13535785e-01,  -2.14285714e-01],\n       [ -8.25793455e-02,   9.96584493e-01,  -2.14285714e-01],\n       [ -7.56977333e-02,   9.13535785e-01,  -2.14285714e-01],\n       [ -2.45485487e-01,   9.69400266e-01,  -2.14285714e-01],\n       [ -2.25028363e-01,   8.88616910e-01,  -2.14285714e-01],\n       [ -4.01695425e-01,   9.15773327e-01,  -2.14285714e-01],\n       [ -3.68220806e-01,   8.39458883e-01,  -2.14285714e-01],\n       [ -5.46948158e-01,   8.37166478e-01,  -2.14285714e-01],\n       [ -5.01369145e-01,   7.67402605e-01,  -2.14285714e-01],\n       [ -6.77281572e-01,   7.35723911e-01,  -2.14285714e-01],\n       [ -6.20841441e-01,   6.74413585e-01,  -2.14285714e-01],\n       [  2.04571239e-01,   8.07833555e-01,  -2.14285714e-01],\n       [  6.88161212e-02,   8.30487078e-01,  -2.14285714e-01],\n       [ -6.88161212e-02,   8.30487078e-01,  -2.14285714e-01],\n       [ -2.04571239e-01,   8.07833555e-01,  -2.14285714e-01],\n       [ -3.34746187e-01,   7.63144439e-01,  -2.14285714e-01],\n       [ -4.55790132e-01,   6.97638732e-01,  -2.14285714e-01],\n       [ -5.64401310e-01,   6.13103259e-01,  -2.14285714e-01],\n       [  1.84114115e-01,   7.27050199e-01,  -2.14285714e-01],\n       [  6.19345091e-02,   7.47438370e-01,  -2.14285714e-01],\n       [ -6.19345091e-02,   7.47438370e-01,  -2.14285714e-01],\n       [ -1.84114115e-01,   7.27050199e-01,  -2.14285714e-01],\n       [ -3.01271568e-01,   6.86829995e-01,  -2.14285714e-01],\n       [ -4.10211119e-01,   6.27874859e-01,  -2.14285714e-01],\n       [ -5.07961179e-01,   5.51792933e-01,  -2.14285714e-01],\n       [  1.63656991e-01,   6.46266844e-01,  -2.14285714e-01],\n       [  5.50528970e-02,   6.64389662e-01,  -2.14285714e-01],\n       [ -5.50528970e-02,   6.64389662e-01,  -2.14285714e-01],\n       [ -1.63656991e-01,   6.46266844e-01,  -2.14285714e-01],\n       [ -2.67796950e-01,   6.10515551e-01,  -2.14285714e-01],\n       [ -3.64632105e-01,   5.58110986e-01,  -2.14285714e-01],\n       [ -4.51521048e-01,   4.90482607e-01,  -2.14285714e-01],\n       [  1.43199867e-01,   5.65483488e-01,  -2.14285714e-01],\n       [  4.81712849e-02,   5.81340954e-01,  -2.14285714e-01],\n       [ -4.81712849e-02,   5.81340954e-01,  -2.14285714e-01],\n       [ -1.43199867e-01,   5.65483488e-01,  -2.14285714e-01],\n       [ -2.34322331e-01,   5.34201107e-01,  -2.14285714e-01],\n       [ -3.19053092e-01,   4.88347112e-01,  -2.14285714e-01],\n       [ -3.95080917e-01,   4.29172281e-01,  -2.14285714e-01],\n       [  1.22742744e-01,   4.84700133e-01,  -2.14285714e-01],\n       [  4.12896727e-02,   4.98292247e-01,  -2.14285714e-01],\n       [ -4.12896727e-02,   4.98292247e-01,  -2.14285714e-01],\n       [ -1.22742744e-01,   4.84700133e-01,  -2.14285714e-01],\n       [ -2.00847712e-01,   4.57886663e-01,  -2.14285714e-01],\n       [ -2.73474079e-01,   4.18583239e-01,  -2.14285714e-01],\n       [ -3.38640786e-01,   3.67861955e-01,  -2.14285714e-01],\n       [ -1.02283149e-01,   4.15336195e-01,  -2.14285714e-01],\n       [ -3.59859419e-02,   4.34695086e-01,  -2.14285714e-01],\n       [ -1.66348287e-01,   3.87163066e-01,  -2.14285714e-01],\n       [ -2.26761024e-01,   3.50663301e-01,  -2.14285714e-01],\n       [ -2.82200655e-01,   3.06551629e-01,  -2.14285714e-01],\n       [ -8.18235534e-02,   3.45972257e-01,  -2.14285714e-01],\n       [ -3.06822110e-02,   3.71097926e-01,  -2.14285714e-01],\n       [ -1.31848862e-01,   3.16439469e-01,  -2.14285714e-01],\n       [ -1.80047970e-01,   2.82743363e-01,  -2.14285714e-01],\n       [ -2.25760524e-01,   2.45241304e-01,  -2.14285714e-01],\n       [ -6.13639584e-02,   2.76608319e-01,  -2.14285714e-01],\n       [ -2.53784802e-02,   3.07500766e-01,  -2.14285714e-01],\n       [ -9.73494365e-02,   2.45715872e-01,  -2.14285714e-01],\n       [ -1.33334915e-01,   2.14823425e-01,  -2.14285714e-01],\n       [ -1.69320393e-01,   1.83930978e-01,  -2.14285714e-01],\n       [ -1.12880262e-01,   1.22620652e-01,  -2.14285714e-01],\n       [ -7.93257664e-02,   1.65019743e-01,  -2.14285714e-01],\n       [ -5.64401310e-02,   6.13103259e-02,  -2.14285714e-01],\n       [ -2.53166180e-02,   1.15216062e-01,  -2.14285714e-01],\n       [ -4.57712708e-02,   2.07418835e-01,  -2.14285714e-01],\n       [  5.80689493e-03,   1.69121798e-01,  -2.14285714e-01],\n       [ -1.22167752e-02,   2.49817927e-01,  -2.14285714e-01],\n       [  3.69304079e-02,   2.23027534e-01,  -2.14285714e-01],\n       [  2.13377203e-02,   2.92217018e-01,  -2.14285714e-01],\n       [  6.80539208e-02,   2.76933270e-01,  -2.14285714e-01],\n       [  8.62835284e-02,   3.46188891e-01,  -2.14285714e-01],\n       [  1.04513136e-01,   4.15444512e-01,  -2.14285714e-01],\n       [  2.79883711e-02,   3.60908761e-01,  -2.14285714e-01],\n       [  3.46390219e-02,   4.29600504e-01,  -2.14285714e-01],\n       [ -7.89140509e-01,   6.14212713e-01,  -2.14285714e-01],\n       [ -7.23378800e-01,   5.63028320e-01,  -2.14285714e-01],\n       [ -8.79473751e-01,   4.75947393e-01,  -2.14285714e-01],\n       [ -8.06184272e-01,   4.36285110e-01,  -2.14285714e-01],\n       [ -9.45817242e-01,   3.24699469e-01,  -2.14285714e-01],\n       [ -8.66999138e-01,   2.97641180e-01,  -2.14285714e-01],\n       [ -9.86361303e-01,   1.64594590e-01,  -2.14285714e-01],\n       [ -9.04164528e-01,   1.50878374e-01,  -2.14285714e-01],\n       [ -1.00000000e+00,  -5.24848628e-17,  -2.14285714e-01],\n       [ -9.16666667e-01,  -3.39957805e-17,  -2.14285714e-01],\n       [ -9.86361303e-01,  -1.64594590e-01,  -2.14285714e-01],\n       [ -9.04164528e-01,  -1.50878374e-01,  -2.14285714e-01],\n       [ -6.57617091e-01,   5.11843927e-01,  -2.14285714e-01],\n       [ -7.32894793e-01,   3.96622828e-01,  -2.14285714e-01],\n       [ -7.88181035e-01,   2.70582891e-01,  -2.14285714e-01],\n       [ -8.21967753e-01,   1.37162159e-01,  -2.14285714e-01],\n       [ -8.33333333e-01,  -2.88110659e-17,  -2.14285714e-01],\n       [ -8.21967753e-01,  -1.37162159e-01,  -2.14285714e-01],\n       [ -5.91855382e-01,   4.60659535e-01,  -2.14285714e-01],\n       [ -6.59605313e-01,   3.56960545e-01,  -2.14285714e-01],\n       [ -7.09362931e-01,   2.43524602e-01,  -2.14285714e-01],\n       [ -7.39770978e-01,   1.23445943e-01,  -2.14285714e-01],\n       [ -7.50000000e-01,  -2.47221107e-17,  -2.14285714e-01],\n       [ -7.39770978e-01,  -1.23445943e-01,  -2.14285714e-01],\n       [ -5.26093673e-01,   4.09475142e-01,  -2.14285714e-01],\n       [ -5.86315834e-01,   3.17298262e-01,  -2.14285714e-01],\n       [ -6.30544828e-01,   2.16466313e-01,  -2.14285714e-01],\n       [ -6.57574202e-01,   1.09729727e-01,  -2.14285714e-01],\n       [ -6.66666667e-01,  -2.09193101e-17,  -2.14285714e-01],\n       [ -6.57574202e-01,  -1.09729727e-01,  -2.14285714e-01],\n       [ -4.60331964e-01,   3.58290749e-01,  -2.14285714e-01],\n       [ -5.13026355e-01,   2.77635979e-01,  -2.14285714e-01],\n       [ -5.51726724e-01,   1.89408024e-01,  -2.14285714e-01],\n       [ -5.75377427e-01,   9.60135110e-02,  -2.14285714e-01],\n       [ -5.83333333e-01,  -1.69320721e-17,  -2.14285714e-01],\n       [ -5.75377427e-01,  -9.60135110e-02,  -2.14285714e-01],\n       [ -3.94570255e-01,   3.07106356e-01,  -2.14285714e-01],\n       [ -4.39736876e-01,   2.37973697e-01,  -2.14285714e-01],\n       [ -4.72908621e-01,   1.62349735e-01,  -2.14285714e-01],\n       [ -4.93180652e-01,   8.22972951e-02,  -2.14285714e-01],\n       [ -5.00000000e-01,  -1.24867361e-17,  -2.14285714e-01],\n       [ -4.93180652e-01,  -8.22972951e-02,  -2.14285714e-01],\n       [ -4.20156583e-01,   7.22539112e-02,  -2.14285714e-01],\n       [ -4.11228248e-01,   1.41174836e-01,  -2.14285714e-01],\n       [ -4.20116462e-01,   1.83641595e-03,  -2.14285714e-01],\n       [ -4.10983876e-01,  -6.85810793e-02,  -2.14285714e-01],\n       [ -3.47132513e-01,   6.22105272e-02,  -2.14285714e-01],\n       [ -3.49547876e-01,   1.19999937e-01,  -2.14285714e-01],\n       [ -3.40232923e-01,   3.67283191e-03,  -2.14285714e-01],\n       [ -3.28787101e-01,  -5.48648634e-02,  -2.14285714e-01],\n       [ -2.74108444e-01,   5.21671433e-02,  -2.14285714e-01],\n       [ -2.87867503e-01,   9.88250387e-02,  -2.14285714e-01],\n       [ -2.60349385e-01,   5.50924786e-03,  -2.14285714e-01],\n       [ -2.46590326e-01,  -4.11486476e-02,  -2.14285714e-01],\n       [ -1.64393551e-01,  -2.74324317e-02,  -2.14285714e-01],\n       [ -1.92379634e-01,   2.41096072e-02,  -2.14285714e-01],\n       [ -8.21967753e-02,  -1.37162159e-02,  -2.14285714e-01],\n       [ -1.24409882e-01,   4.27099665e-02,  -2.14285714e-01],\n       [ -2.20365717e-01,   7.56516461e-02,  -2.14285714e-01],\n       [ -1.66622989e-01,   9.91361489e-02,  -2.14285714e-01],\n       [ -2.48351800e-01,   1.27193685e-01,  -2.14285714e-01],\n       [ -2.08836096e-01,   1.55562331e-01,  -2.14285714e-01],\n       [ -2.70747482e-01,   2.06077006e-01,  -2.14285714e-01],\n       [ -3.32658869e-01,   2.56591681e-01,  -2.14285714e-01],\n       [ -3.12146825e-01,   1.64120356e-01,  -2.14285714e-01],\n       [ -3.75941850e-01,   2.01047026e-01,  -2.14285714e-01],\n       [ -9.45817242e-01,  -3.24699469e-01,  -2.14285714e-01],\n       [ -8.66999138e-01,  -2.97641180e-01,  -2.14285714e-01],\n       [ -8.79473751e-01,  -4.75947393e-01,  -2.14285714e-01],\n       [ -8.06184272e-01,  -4.36285110e-01,  -2.14285714e-01],\n       [ -7.89140509e-01,  -6.14212713e-01,  -2.14285714e-01],\n       [ -7.23378800e-01,  -5.63028320e-01,  -2.14285714e-01],\n       [ -6.77281572e-01,  -7.35723911e-01,  -2.14285714e-01],\n       [ -6.20841441e-01,  -6.74413585e-01,  -2.14285714e-01],\n       [ -5.46948158e-01,  -8.37166478e-01,  -2.14285714e-01],\n       [ -5.01369145e-01,  -7.67402605e-01,  -2.14285714e-01],\n       [ -4.01695425e-01,  -9.15773327e-01,  -2.14285714e-01],\n       [ -3.68220806e-01,  -8.39458883e-01,  -2.14285714e-01],\n       [ -7.88181035e-01,  -2.70582891e-01,  -2.14285714e-01],\n       [ -7.32894793e-01,  -3.96622828e-01,  -2.14285714e-01],\n       [ -6.57617091e-01,  -5.11843927e-01,  -2.14285714e-01],\n       [ -5.64401310e-01,  -6.13103259e-01,  -2.14285714e-01],\n       [ -4.55790132e-01,  -6.97638732e-01,  -2.14285714e-01],\n       [ -3.34746187e-01,  -7.63144439e-01,  -2.14285714e-01],\n       [ -7.09362931e-01,  -2.43524602e-01,  -2.14285714e-01],\n       [ -6.59605313e-01,  -3.56960545e-01,  -2.14285714e-01],\n       [ -5.91855382e-01,  -4.60659535e-01,  -2.14285714e-01],\n       [ -5.07961179e-01,  -5.51792933e-01,  -2.14285714e-01],\n       [ -4.10211119e-01,  -6.27874859e-01,  -2.14285714e-01],\n       [ -3.01271568e-01,  -6.86829995e-01,  -2.14285714e-01],\n       [ -6.30544828e-01,  -2.16466313e-01,  -2.14285714e-01],\n       [ -5.86315834e-01,  -3.17298262e-01,  -2.14285714e-01],\n       [ -5.26093673e-01,  -4.09475142e-01,  -2.14285714e-01],\n       [ -4.51521048e-01,  -4.90482607e-01,  -2.14285714e-01],\n       [ -3.64632105e-01,  -5.58110986e-01,  -2.14285714e-01],\n       [ -2.67796950e-01,  -6.10515551e-01,  -2.14285714e-01],\n       [ -5.51726724e-01,  -1.89408024e-01,  -2.14285714e-01],\n       [ -5.13026355e-01,  -2.77635979e-01,  -2.14285714e-01],\n       [ -4.60331964e-01,  -3.58290749e-01,  -2.14285714e-01],\n       [ -3.95080917e-01,  -4.29172281e-01,  -2.14285714e-01],\n       [ -3.19053092e-01,  -4.88347112e-01,  -2.14285714e-01],\n       [ -2.34322331e-01,  -5.34201107e-01,  -2.14285714e-01],\n       [ -4.72908621e-01,  -1.62349735e-01,  -2.14285714e-01],\n       [ -4.39736876e-01,  -2.37973697e-01,  -2.14285714e-01],\n       [ -3.94570255e-01,  -3.07106356e-01,  -2.14285714e-01],\n       [ -3.38640786e-01,  -3.67861955e-01,  -2.14285714e-01],\n       [ -2.73474079e-01,  -4.18583239e-01,  -2.14285714e-01],\n       [ -2.00847712e-01,  -4.57886663e-01,  -2.14285714e-01],\n       [ -2.90292421e-01,  -3.12221863e-01,  -2.14285714e-01],\n       [ -3.43107373e-01,  -2.67051188e-01,  -2.14285714e-01],\n       [ -2.31319311e-01,  -3.50702994e-01,  -2.14285714e-01],\n       [ -1.67373094e-01,  -3.81572219e-01,  -2.14285714e-01],\n       [ -2.41944057e-01,  -2.56581770e-01,  -2.14285714e-01],\n       [ -2.91644492e-01,  -2.26996019e-01,  -2.14285714e-01],\n       [ -1.89164543e-01,  -2.82822750e-01,  -2.14285714e-01],\n       [ -1.33898475e-01,  -3.05257776e-01,  -2.14285714e-01],\n       [ -1.93595692e-01,  -2.00941678e-01,  -2.14285714e-01],\n       [ -2.40181610e-01,  -1.86940851e-01,  -2.14285714e-01],\n       [ -1.47009774e-01,  -2.14942505e-01,  -2.14285714e-01],\n       [ -1.00423856e-01,  -2.28943332e-01,  -2.14285714e-01],\n       [ -6.69492374e-02,  -1.52628888e-01,  -2.14285714e-01],\n       [ -1.25405441e-01,  -1.47867075e-01,  -2.14285714e-01],\n       [ -3.34746187e-02,  -7.63144439e-02,  -2.14285714e-01],\n       [ -1.03801108e-01,  -8.07916455e-02,  -2.14285714e-01],\n       [ -1.83861645e-01,  -1.43105263e-01,  -2.14285714e-01],\n       [ -1.74127598e-01,  -8.52688471e-02,  -2.14285714e-01],\n       [ -2.42317849e-01,  -1.38343450e-01,  -2.14285714e-01],\n       [ -2.44454087e-01,  -8.97460487e-02,  -2.14285714e-01],\n       [ -3.20605599e-01,  -1.13947277e-01,  -2.14285714e-01],\n       [ -3.96757110e-01,  -1.38148506e-01,  -2.14285714e-01],\n       [ -3.08124191e-01,  -1.71553532e-01,  -2.14285714e-01],\n       [ -3.73930533e-01,  -2.04763614e-01,  -2.14285714e-01],\n       [ -2.45485487e-01,  -9.69400266e-01,  -2.14285714e-01],\n       [ -2.25028363e-01,  -8.88616910e-01,  -2.14285714e-01],\n       [ -8.25793455e-02,  -9.96584493e-01,  -2.14285714e-01],\n       [ -7.56977333e-02,  -9.13535785e-01,  -2.14285714e-01],\n       [  8.25793455e-02,  -9.96584493e-01,  -2.14285714e-01],\n       [  7.56977333e-02,  -9.13535785e-01,  -2.14285714e-01],\n       [  2.45485487e-01,  -9.69400266e-01,  -2.14285714e-01],\n       [  2.25028363e-01,  -8.88616910e-01,  -2.14285714e-01],\n       [  4.01695425e-01,  -9.15773327e-01,  -2.14285714e-01],\n       [  3.68220806e-01,  -8.39458883e-01,  -2.14285714e-01],\n       [  5.46948158e-01,  -8.37166478e-01,  -2.14285714e-01],\n       [  5.01369145e-01,  -7.67402605e-01,  -2.14285714e-01],\n       [ -2.04571239e-01,  -8.07833555e-01,  -2.14285714e-01],\n       [ -6.88161212e-02,  -8.30487078e-01,  -2.14285714e-01],\n       [  6.88161212e-02,  -8.30487078e-01,  -2.14285714e-01],\n       [  2.04571239e-01,  -8.07833555e-01,  -2.14285714e-01],\n       [  3.34746187e-01,  -7.63144439e-01,  -2.14285714e-01],\n       [  4.55790132e-01,  -6.97638732e-01,  -2.14285714e-01],\n       [ -1.84114115e-01,  -7.27050199e-01,  -2.14285714e-01],\n       [ -6.19345091e-02,  -7.47438370e-01,  -2.14285714e-01],\n       [  6.19345091e-02,  -7.47438370e-01,  -2.14285714e-01],\n       [  1.84114115e-01,  -7.27050199e-01,  -2.14285714e-01],\n       [  3.01271568e-01,  -6.86829995e-01,  -2.14285714e-01],\n       [  4.10211119e-01,  -6.27874859e-01,  -2.14285714e-01],\n       [ -1.63656991e-01,  -6.46266844e-01,  -2.14285714e-01],\n       [ -5.50528970e-02,  -6.64389662e-01,  -2.14285714e-01],\n       [  5.50528970e-02,  -6.64389662e-01,  -2.14285714e-01],\n       [  1.63656991e-01,  -6.46266844e-01,  -2.14285714e-01],\n       [  2.67796950e-01,  -6.10515551e-01,  -2.14285714e-01],\n       [  3.64632105e-01,  -5.58110986e-01,  -2.14285714e-01],\n       [ -1.43199867e-01,  -5.65483488e-01,  -2.14285714e-01],\n       [ -4.81712849e-02,  -5.81340954e-01,  -2.14285714e-01],\n       [  4.81712849e-02,  -5.81340954e-01,  -2.14285714e-01],\n       [  1.43199867e-01,  -5.65483488e-01,  -2.14285714e-01],\n       [  2.34322331e-01,  -5.34201107e-01,  -2.14285714e-01],\n       [  3.19053092e-01,  -4.88347112e-01,  -2.14285714e-01],\n       [ -1.22742744e-01,  -4.84700133e-01,  -2.14285714e-01],\n       [ -4.12896727e-02,  -4.98292247e-01,  -2.14285714e-01],\n       [  4.12896727e-02,  -4.98292247e-01,  -2.14285714e-01],\n       [  1.22742744e-01,  -4.84700133e-01,  -2.14285714e-01],\n       [  2.00847712e-01,  -4.57886663e-01,  -2.14285714e-01],\n       [  2.73474079e-01,  -4.18583239e-01,  -2.14285714e-01],\n       [  1.02606772e-01,  -4.13792257e-01,  -2.14285714e-01],\n       [  3.59043567e-02,  -4.33301147e-01,  -2.14285714e-01],\n       [  1.67077120e-01,  -3.85469130e-01,  -2.14285714e-01],\n       [  2.27895066e-01,  -3.48819366e-01,  -2.14285714e-01],\n       [  8.24708009e-02,  -3.42884381e-01,  -2.14285714e-01],\n       [  3.05190406e-02,  -3.68310047e-01,  -2.14285714e-01],\n       [  1.33306527e-01,  -3.13051596e-01,  -2.14285714e-01],\n       [  1.82316053e-01,  -2.79055493e-01,  -2.14285714e-01],\n       [  6.23348295e-02,  -2.71976505e-01,  -2.14285714e-01],\n       [  2.51337245e-02,  -3.03318947e-01,  -2.14285714e-01],\n       [  9.95359345e-02,  -2.40634062e-01,  -2.14285714e-01],\n       [  1.36737040e-01,  -2.09291620e-01,  -2.14285714e-01],\n       [  9.11580264e-02,  -1.39527746e-01,  -2.14285714e-01],\n       [  5.51990834e-02,  -1.85860856e-01,  -2.14285714e-01],\n       [  4.55790132e-02,  -6.97638732e-02,  -2.14285714e-01],\n       [  1.08622324e-02,  -1.31087650e-01,  -2.14285714e-01],\n       [  1.92401405e-02,  -2.32193966e-01,  -2.14285714e-01],\n       [ -2.38545485e-02,  -1.92411427e-01,  -2.14285714e-01],\n       [ -1.67188024e-02,  -2.78527075e-01,  -2.14285714e-01],\n       [ -5.85713293e-02,  -2.53735203e-01,  -2.14285714e-01],\n       [ -7.99618007e-02,  -3.30723513e-01,  -2.14285714e-01],\n       [ -1.01352272e-01,  -4.07711823e-01,  -2.14285714e-01],\n       [ -2.49090925e-02,  -3.51782132e-01,  -2.14285714e-01],\n       [ -3.30993826e-02,  -4.25037189e-01,  -2.14285714e-01],\n       [  6.77281572e-01,  -7.35723911e-01,  -2.14285714e-01],\n       [  6.20841441e-01,  -6.74413585e-01,  -2.14285714e-01],\n       [  7.89140509e-01,  -6.14212713e-01,  -2.14285714e-01],\n       [  7.23378800e-01,  -5.63028320e-01,  -2.14285714e-01],\n       [  8.79473751e-01,  -4.75947393e-01,  -2.14285714e-01],\n       [  8.06184272e-01,  -4.36285110e-01,  -2.14285714e-01],\n       [  9.45817242e-01,  -3.24699469e-01,  -2.14285714e-01],\n       [  8.66999138e-01,  -2.97641180e-01,  -2.14285714e-01],\n       [  9.86361303e-01,  -1.64594590e-01,  -2.14285714e-01],\n       [  9.04164528e-01,  -1.50878374e-01,  -2.14285714e-01],\n       [  5.64401310e-01,  -6.13103259e-01,  -2.14285714e-01],\n       [  6.57617091e-01,  -5.11843927e-01,  -2.14285714e-01],\n       [  7.32894793e-01,  -3.96622828e-01,  -2.14285714e-01],\n       [  7.88181035e-01,  -2.70582891e-01,  -2.14285714e-01],\n       [  8.21967753e-01,  -1.37162159e-01,  -2.14285714e-01],\n       [  5.07961179e-01,  -5.51792933e-01,  -2.14285714e-01],\n       [  5.91855382e-01,  -4.60659535e-01,  -2.14285714e-01],\n       [  6.59605313e-01,  -3.56960545e-01,  -2.14285714e-01],\n       [  7.09362931e-01,  -2.43524602e-01,  -2.14285714e-01],\n       [  7.39770978e-01,  -1.23445943e-01,  -2.14285714e-01],\n       [  4.51521048e-01,  -4.90482607e-01,  -2.14285714e-01],\n       [  5.26093673e-01,  -4.09475142e-01,  -2.14285714e-01],\n       [  5.86315834e-01,  -3.17298262e-01,  -2.14285714e-01],\n       [  6.30544828e-01,  -2.16466313e-01,  -2.14285714e-01],\n       [  6.57574202e-01,  -1.09729727e-01,  -2.14285714e-01],\n       [  3.95080917e-01,  -4.29172281e-01,  -2.14285714e-01],\n       [  4.60331964e-01,  -3.58290749e-01,  -2.14285714e-01],\n       [  5.13026355e-01,  -2.77635979e-01,  -2.14285714e-01],\n       [  5.51726724e-01,  -1.89408024e-01,  -2.14285714e-01],\n       [  5.75377427e-01,  -9.60135110e-02,  -2.14285714e-01],\n       [  3.38640786e-01,  -3.67861955e-01,  -2.14285714e-01],\n       [  3.94570255e-01,  -3.07106356e-01,  -2.14285714e-01],\n       [  4.39736876e-01,  -2.37973697e-01,  -2.14285714e-01],\n       [  4.72908621e-01,  -1.62349735e-01,  -2.14285714e-01],\n       [  4.93180652e-01,  -8.22972951e-02,  -2.14285714e-01],\n       [  4.02533591e-01,  -1.40423963e-01,  -2.14285714e-01],\n       [  3.82383017e-01,  -2.06935340e-01,  -2.14285714e-01],\n       [  4.14084357e-01,  -7.09602665e-02,  -2.14285714e-01],\n       [  3.32158562e-01,  -1.18498191e-01,  -2.14285714e-01],\n       [  3.25029158e-01,  -1.75896984e-01,  -2.14285714e-01],\n       [  3.34988061e-01,  -5.96232379e-02,  -2.14285714e-01],\n       [  2.61783533e-01,  -9.65724185e-02,  -2.14285714e-01],\n       [  2.67675299e-01,  -1.44858628e-01,  -2.14285714e-01],\n       [  2.55891766e-01,  -4.82862093e-02,  -2.14285714e-01],\n       [  1.85787515e-01,  -5.54454306e-02,  -2.14285714e-01],\n       [  1.15683264e-01,  -6.26046519e-02,  -2.14285714e-01],\n       [  2.04908364e-01,  -1.10890861e-01,  -2.14285714e-01],\n       [  1.48033195e-01,  -1.25209304e-01,  -2.14285714e-01],\n       [  2.24029213e-01,  -1.66336292e-01,  -2.14285714e-01],\n       [  1.80383126e-01,  -1.87813956e-01,  -2.14285714e-01],\n       [  2.33135679e-01,  -2.47829956e-01,  -2.14285714e-01],\n       [  2.85888233e-01,  -3.07845955e-01,  -2.14285714e-01],\n       [  2.80876227e-01,  -2.13259647e-01,  -2.14285714e-01],\n       [  3.37723241e-01,  -2.60183001e-01,  -2.14285714e-01],\n       [  1.00000000e+00,   0.00000000e+00,  -3.57142857e-01],\n       [  9.86361303e-01,   1.64594590e-01,  -3.57142857e-01],\n       [  9.04164528e-01,   1.50878374e-01,  -3.57142857e-01],\n       [  9.16666667e-01,   1.88101103e-16,  -3.57142857e-01],\n       [  9.45817242e-01,   3.24699469e-01,  -3.57142857e-01],\n       [  8.66999138e-01,   2.97641180e-01,  -3.57142857e-01],\n       [  8.79473751e-01,   4.75947393e-01,  -3.57142857e-01],\n       [  8.06184272e-01,   4.36285110e-01,  -3.57142857e-01],\n       [  7.89140509e-01,   6.14212713e-01,  -3.57142857e-01],\n       [  7.23378800e-01,   5.63028320e-01,  -3.57142857e-01],\n       [  6.77281572e-01,   7.35723911e-01,  -3.57142857e-01],\n       [  6.20841441e-01,   6.74413585e-01,  -3.57142857e-01],\n       [  5.46948158e-01,   8.37166478e-01,  -3.57142857e-01],\n       [  5.01369145e-01,   7.67402605e-01,  -3.57142857e-01],\n       [  4.01695425e-01,   9.15773327e-01,  -3.57142857e-01],\n       [  3.68220806e-01,   8.39458883e-01,  -3.57142857e-01],\n       [  8.21967753e-01,   1.37162159e-01,  -3.57142857e-01],\n       [  8.33333333e-01,   2.89101549e-16,  -3.57142857e-01],\n       [  7.88181035e-01,   2.70582891e-01,  -3.57142857e-01],\n       [  7.32894793e-01,   3.96622828e-01,  -3.57142857e-01],\n       [  6.57617091e-01,   5.11843927e-01,  -3.57142857e-01],\n       [  5.64401310e-01,   6.13103259e-01,  -3.57142857e-01],\n       [  4.55790132e-01,   6.97638732e-01,  -3.57142857e-01],\n       [  3.34746187e-01,   7.63144439e-01,  -3.57142857e-01],\n       [  7.39770978e-01,   1.23445943e-01,  -3.57142857e-01],\n       [  7.50000000e-01,   2.96828145e-16,  -3.57142857e-01],\n       [  7.09362931e-01,   2.43524602e-01,  -3.57142857e-01],\n       [  6.59605313e-01,   3.56960545e-01,  -3.57142857e-01],\n       [  5.91855382e-01,   4.60659535e-01,  -3.57142857e-01],\n       [  5.07961179e-01,   5.51792933e-01,  -3.57142857e-01],\n       [  4.10211119e-01,   6.27874859e-01,  -3.57142857e-01],\n       [  3.01271568e-01,   6.86829995e-01,  -3.57142857e-01],\n       [  6.57574202e-01,   1.09729727e-01,  -3.57142857e-01],\n       [  6.66666667e-01,   2.80085390e-16,  -3.57142857e-01],\n       [  6.30544828e-01,   2.16466313e-01,  -3.57142857e-01],\n       [  5.86315834e-01,   3.17298262e-01,  -3.57142857e-01],\n       [  5.26093673e-01,   4.09475142e-01,  -3.57142857e-01],\n       [  4.51521048e-01,   4.90482607e-01,  -3.57142857e-01],\n       [  3.64632105e-01,   5.58110986e-01,  -3.57142857e-01],\n       [  2.67796950e-01,   6.10515551e-01,  -3.57142857e-01],\n       [  5.75377427e-01,   9.60135110e-02,  -3.57142857e-01],\n       [  5.83333333e-01,   2.55069887e-16,  -3.57142857e-01],\n       [  5.51726724e-01,   1.89408024e-01,  -3.57142857e-01],\n       [  5.13026355e-01,   2.77635979e-01,  -3.57142857e-01],\n       [  4.60331964e-01,   3.58290749e-01,  -3.57142857e-01],\n       [  3.95080917e-01,   4.29172281e-01,  -3.57142857e-01],\n       [  3.19053092e-01,   4.88347112e-01,  -3.57142857e-01],\n       [  2.34322331e-01,   5.34201107e-01,  -3.57142857e-01],\n       [  4.93180652e-01,   8.22972951e-02,  -3.57142857e-01],\n       [  5.00000000e-01,   2.26287739e-16,  -3.57142857e-01],\n       [  4.72908621e-01,   1.62349735e-01,  -3.57142857e-01],\n       [  4.39736876e-01,   2.37973697e-01,  -3.57142857e-01],\n       [  3.94570255e-01,   3.07106356e-01,  -3.57142857e-01],\n       [  3.38640786e-01,   3.67861955e-01,  -3.57142857e-01],\n       [  2.73474079e-01,   4.18583239e-01,  -3.57142857e-01],\n       [  2.00847712e-01,   4.57886663e-01,  -3.57142857e-01],\n       [  2.92606991e-01,   3.16445261e-01,  -3.57142857e-01],\n       [  3.44188184e-01,   2.67935253e-01,  -3.57142857e-01],\n       [  2.34867640e-01,   3.58265725e-01,  -3.57142857e-01],\n       [  1.72155182e-01,   3.92474283e-01,  -3.57142857e-01],\n       [  2.46573197e-01,   2.65028567e-01,  -3.57142857e-01],\n       [  2.93806114e-01,   2.28764151e-01,  -3.57142857e-01],\n       [  1.96261201e-01,   2.97948211e-01,  -3.57142857e-01],\n       [  1.43462652e-01,   3.27061902e-01,  -3.57142857e-01],\n       [  2.00539403e-01,   2.13611872e-01,  -3.57142857e-01],\n       [  2.43424043e-01,   1.89593048e-01,  -3.57142857e-01],\n       [  1.57654762e-01,   2.37630697e-01,  -3.57142857e-01],\n       [  1.14770121e-01,   2.61649522e-01,  -3.57142857e-01],\n       [  8.60775910e-02,   1.96237141e-01,  -3.57142857e-01],\n       [  1.39074405e-01,   1.78223023e-01,  -3.57142857e-01],\n       [  5.73850607e-02,   1.30824761e-01,  -3.57142857e-01],\n       [  1.20494048e-01,   1.18815349e-01,  -3.57142857e-01],\n       [  2.86925303e-02,   6.54123805e-02,  -3.57142857e-01],\n       [  1.01913690e-01,   5.94076743e-02,  -3.57142857e-01],\n       [ -6.67803323e-18,   3.62404443e-17,  -3.57142857e-01],\n       [  8.33333333e-02,   6.76427614e-17,  -3.57142857e-01],\n       [  1.92071219e-01,   1.60208904e-01,  -3.57142857e-01],\n       [  1.83603035e-01,   1.06805936e-01,  -3.57142857e-01],\n       [  1.75134851e-01,   5.34029681e-02,  -3.57142857e-01],\n       [  1.66666667e-01,   9.94857751e-17,  -3.57142857e-01],\n       [  2.45068032e-01,   1.42194786e-01,  -3.57142857e-01],\n       [  2.46712022e-01,   9.47965238e-02,  -3.57142857e-01],\n       [  2.48356011e-01,   4.73982619e-02,  -3.57142857e-01],\n       [  2.50000000e-01,   1.31613145e-16,  -3.57142857e-01],\n       [  3.33333333e-01,   1.63763809e-16,  -3.57142857e-01],\n       [  3.29964224e-01,   5.90312730e-02,  -3.57142857e-01],\n       [  4.16666667e-01,   1.95528462e-16,  -3.57142857e-01],\n       [  4.11572438e-01,   7.06642841e-02,  -3.57142857e-01],\n       [  3.22110888e-01,   1.17314261e-01,  -3.57142857e-01],\n       [  3.97509754e-01,   1.39831998e-01,  -3.57142857e-01],\n       [  3.09957647e-01,   1.74121089e-01,  -3.57142857e-01],\n       [  3.74847261e-01,   2.06047393e-01,  -3.57142857e-01],\n       [  2.45485487e-01,   9.69400266e-01,  -3.57142857e-01],\n       [  2.25028363e-01,   8.88616910e-01,  -3.57142857e-01],\n       [  8.25793455e-02,   9.96584493e-01,  -3.57142857e-01],\n       [  7.56977333e-02,   9.13535785e-01,  -3.57142857e-01],\n       [ -8.25793455e-02,   9.96584493e-01,  -3.57142857e-01],\n       [ -7.56977333e-02,   9.13535785e-01,  -3.57142857e-01],\n       [ -2.45485487e-01,   9.69400266e-01,  -3.57142857e-01],\n       [ -2.25028363e-01,   8.88616910e-01,  -3.57142857e-01],\n       [ -4.01695425e-01,   9.15773327e-01,  -3.57142857e-01],\n       [ -3.68220806e-01,   8.39458883e-01,  -3.57142857e-01],\n       [ -5.46948158e-01,   8.37166478e-01,  -3.57142857e-01],\n       [ -5.01369145e-01,   7.67402605e-01,  -3.57142857e-01],\n       [ -6.77281572e-01,   7.35723911e-01,  -3.57142857e-01],\n       [ -6.20841441e-01,   6.74413585e-01,  -3.57142857e-01],\n       [  2.04571239e-01,   8.07833555e-01,  -3.57142857e-01],\n       [  6.88161212e-02,   8.30487078e-01,  -3.57142857e-01],\n       [ -6.88161212e-02,   8.30487078e-01,  -3.57142857e-01],\n       [ -2.04571239e-01,   8.07833555e-01,  -3.57142857e-01],\n       [ -3.34746187e-01,   7.63144439e-01,  -3.57142857e-01],\n       [ -4.55790132e-01,   6.97638732e-01,  -3.57142857e-01],\n       [ -5.64401310e-01,   6.13103259e-01,  -3.57142857e-01],\n       [  1.84114115e-01,   7.27050199e-01,  -3.57142857e-01],\n       [  6.19345091e-02,   7.47438370e-01,  -3.57142857e-01],\n       [ -6.19345091e-02,   7.47438370e-01,  -3.57142857e-01],\n       [ -1.84114115e-01,   7.27050199e-01,  -3.57142857e-01],\n       [ -3.01271568e-01,   6.86829995e-01,  -3.57142857e-01],\n       [ -4.10211119e-01,   6.27874859e-01,  -3.57142857e-01],\n       [ -5.07961179e-01,   5.51792933e-01,  -3.57142857e-01],\n       [  1.63656991e-01,   6.46266844e-01,  -3.57142857e-01],\n       [  5.50528970e-02,   6.64389662e-01,  -3.57142857e-01],\n       [ -5.50528970e-02,   6.64389662e-01,  -3.57142857e-01],\n       [ -1.63656991e-01,   6.46266844e-01,  -3.57142857e-01],\n       [ -2.67796950e-01,   6.10515551e-01,  -3.57142857e-01],\n       [ -3.64632105e-01,   5.58110986e-01,  -3.57142857e-01],\n       [ -4.51521048e-01,   4.90482607e-01,  -3.57142857e-01],\n       [  1.43199867e-01,   5.65483488e-01,  -3.57142857e-01],\n       [  4.81712849e-02,   5.81340954e-01,  -3.57142857e-01],\n       [ -4.81712849e-02,   5.81340954e-01,  -3.57142857e-01],\n       [ -1.43199867e-01,   5.65483488e-01,  -3.57142857e-01],\n       [ -2.34322331e-01,   5.34201107e-01,  -3.57142857e-01],\n       [ -3.19053092e-01,   4.88347112e-01,  -3.57142857e-01],\n       [ -3.95080917e-01,   4.29172281e-01,  -3.57142857e-01],\n       [  1.22742744e-01,   4.84700133e-01,  -3.57142857e-01],\n       [  4.12896727e-02,   4.98292247e-01,  -3.57142857e-01],\n       [ -4.12896727e-02,   4.98292247e-01,  -3.57142857e-01],\n       [ -1.22742744e-01,   4.84700133e-01,  -3.57142857e-01],\n       [ -2.00847712e-01,   4.57886663e-01,  -3.57142857e-01],\n       [ -2.73474079e-01,   4.18583239e-01,  -3.57142857e-01],\n       [ -3.38640786e-01,   3.67861955e-01,  -3.57142857e-01],\n       [ -1.02283149e-01,   4.15336195e-01,  -3.57142857e-01],\n       [ -3.59859419e-02,   4.34695086e-01,  -3.57142857e-01],\n       [ -1.66348287e-01,   3.87163066e-01,  -3.57142857e-01],\n       [ -2.26761024e-01,   3.50663301e-01,  -3.57142857e-01],\n       [ -2.82200655e-01,   3.06551629e-01,  -3.57142857e-01],\n       [ -8.18235534e-02,   3.45972257e-01,  -3.57142857e-01],\n       [ -3.06822110e-02,   3.71097926e-01,  -3.57142857e-01],\n       [ -1.31848862e-01,   3.16439469e-01,  -3.57142857e-01],\n       [ -1.80047970e-01,   2.82743363e-01,  -3.57142857e-01],\n       [ -2.25760524e-01,   2.45241304e-01,  -3.57142857e-01],\n       [ -6.13639584e-02,   2.76608319e-01,  -3.57142857e-01],\n       [ -2.53784802e-02,   3.07500766e-01,  -3.57142857e-01],\n       [ -9.73494365e-02,   2.45715872e-01,  -3.57142857e-01],\n       [ -1.33334915e-01,   2.14823425e-01,  -3.57142857e-01],\n       [ -1.69320393e-01,   1.83930978e-01,  -3.57142857e-01],\n       [ -1.12880262e-01,   1.22620652e-01,  -3.57142857e-01],\n       [ -7.93257664e-02,   1.65019743e-01,  -3.57142857e-01],\n       [ -5.64401310e-02,   6.13103259e-02,  -3.57142857e-01],\n       [ -2.53166180e-02,   1.15216062e-01,  -3.57142857e-01],\n       [ -4.57712708e-02,   2.07418835e-01,  -3.57142857e-01],\n       [  5.80689493e-03,   1.69121798e-01,  -3.57142857e-01],\n       [ -1.22167752e-02,   2.49817927e-01,  -3.57142857e-01],\n       [  3.69304079e-02,   2.23027534e-01,  -3.57142857e-01],\n       [  2.13377203e-02,   2.92217018e-01,  -3.57142857e-01],\n       [  6.80539208e-02,   2.76933270e-01,  -3.57142857e-01],\n       [  8.62835284e-02,   3.46188891e-01,  -3.57142857e-01],\n       [  1.04513136e-01,   4.15444512e-01,  -3.57142857e-01],\n       [  2.79883711e-02,   3.60908761e-01,  -3.57142857e-01],\n       [  3.46390219e-02,   4.29600504e-01,  -3.57142857e-01],\n       [ -7.89140509e-01,   6.14212713e-01,  -3.57142857e-01],\n       [ -7.23378800e-01,   5.63028320e-01,  -3.57142857e-01],\n       [ -8.79473751e-01,   4.75947393e-01,  -3.57142857e-01],\n       [ -8.06184272e-01,   4.36285110e-01,  -3.57142857e-01],\n       [ -9.45817242e-01,   3.24699469e-01,  -3.57142857e-01],\n       [ -8.66999138e-01,   2.97641180e-01,  -3.57142857e-01],\n       [ -9.86361303e-01,   1.64594590e-01,  -3.57142857e-01],\n       [ -9.04164528e-01,   1.50878374e-01,  -3.57142857e-01],\n       [ -1.00000000e+00,  -8.74747714e-17,  -3.57142857e-01],\n       [ -9.16666667e-01,  -6.01060321e-17,  -3.57142857e-01],\n       [ -9.86361303e-01,  -1.64594590e-01,  -3.57142857e-01],\n       [ -9.04164528e-01,  -1.50878374e-01,  -3.57142857e-01],\n       [ -6.57617091e-01,   5.11843927e-01,  -3.57142857e-01],\n       [ -7.32894793e-01,   3.96622828e-01,  -3.57142857e-01],\n       [ -7.88181035e-01,   2.70582891e-01,  -3.57142857e-01],\n       [ -8.21967753e-01,   1.37162159e-01,  -3.57142857e-01],\n       [ -8.33333333e-01,  -5.17702556e-17,  -3.57142857e-01],\n       [ -8.21967753e-01,  -1.37162159e-01,  -3.57142857e-01],\n       [ -5.91855382e-01,   4.60659535e-01,  -3.57142857e-01],\n       [ -6.59605313e-01,   3.56960545e-01,  -3.57142857e-01],\n       [ -7.09362931e-01,   2.43524602e-01,  -3.57142857e-01],\n       [ -7.39770978e-01,   1.23445943e-01,  -3.57142857e-01],\n       [ -7.50000000e-01,  -4.47493904e-17,  -3.57142857e-01],\n       [ -7.39770978e-01,  -1.23445943e-01,  -3.57142857e-01],\n       [ -5.26093673e-01,   4.09475142e-01,  -3.57142857e-01],\n       [ -5.86315834e-01,   3.17298262e-01,  -3.57142857e-01],\n       [ -6.30544828e-01,   2.16466313e-01,  -3.57142857e-01],\n       [ -6.57574202e-01,   1.09729727e-01,  -3.57142857e-01],\n       [ -6.66666667e-01,  -3.80719106e-17,  -3.57142857e-01],\n       [ -6.57574202e-01,  -1.09729727e-01,  -3.57142857e-01],\n       [ -4.60331964e-01,   3.58290749e-01,  -3.57142857e-01],\n       [ -5.13026355e-01,   2.77635979e-01,  -3.57142857e-01],\n       [ -5.51726724e-01,   1.89408024e-01,  -3.57142857e-01],\n       [ -5.75377427e-01,   9.60135110e-02,  -3.57142857e-01],\n       [ -5.83333333e-01,  -3.11731060e-17,  -3.57142857e-01],\n       [ -5.75377427e-01,  -9.60135110e-02,  -3.57142857e-01],\n       [ -3.94570255e-01,   3.07106356e-01,  -3.57142857e-01],\n       [ -4.39736876e-01,   2.37973697e-01,  -3.57142857e-01],\n       [ -4.72908621e-01,   1.62349735e-01,  -3.57142857e-01],\n       [ -4.93180652e-01,   8.22972951e-02,  -3.57142857e-01],\n       [ -5.00000000e-01,  -2.37245839e-17,  -3.57142857e-01],\n       [ -4.93180652e-01,  -8.22972951e-02,  -3.57142857e-01],\n       [ -4.20156583e-01,   7.22539112e-02,  -3.57142857e-01],\n       [ -4.11228248e-01,   1.41174836e-01,  -3.57142857e-01],\n       [ -4.20116462e-01,   1.83641595e-03,  -3.57142857e-01],\n       [ -4.10983876e-01,  -6.85810793e-02,  -3.57142857e-01],\n       [ -3.47132513e-01,   6.22105272e-02,  -3.57142857e-01],\n       [ -3.49547876e-01,   1.19999937e-01,  -3.57142857e-01],\n       [ -3.40232923e-01,   3.67283191e-03,  -3.57142857e-01],\n       [ -3.28787101e-01,  -5.48648634e-02,  -3.57142857e-01],\n       [ -2.74108444e-01,   5.21671433e-02,  -3.57142857e-01],\n       [ -2.87867503e-01,   9.88250387e-02,  -3.57142857e-01],\n       [ -2.60349385e-01,   5.50924786e-03,  -3.57142857e-01],\n       [ -2.46590326e-01,  -4.11486476e-02,  -3.57142857e-01],\n       [ -1.64393551e-01,  -2.74324317e-02,  -3.57142857e-01],\n       [ -1.92379634e-01,   2.41096072e-02,  -3.57142857e-01],\n       [ -8.21967753e-02,  -1.37162159e-02,  -3.57142857e-01],\n       [ -1.24409882e-01,   4.27099665e-02,  -3.57142857e-01],\n       [ -2.20365717e-01,   7.56516461e-02,  -3.57142857e-01],\n       [ -1.66622989e-01,   9.91361489e-02,  -3.57142857e-01],\n       [ -2.48351800e-01,   1.27193685e-01,  -3.57142857e-01],\n       [ -2.08836096e-01,   1.55562331e-01,  -3.57142857e-01],\n       [ -2.70747482e-01,   2.06077006e-01,  -3.57142857e-01],\n       [ -3.32658869e-01,   2.56591681e-01,  -3.57142857e-01],\n       [ -3.12146825e-01,   1.64120356e-01,  -3.57142857e-01],\n       [ -3.75941850e-01,   2.01047026e-01,  -3.57142857e-01],\n       [ -9.45817242e-01,  -3.24699469e-01,  -3.57142857e-01],\n       [ -8.66999138e-01,  -2.97641180e-01,  -3.57142857e-01],\n       [ -8.79473751e-01,  -4.75947393e-01,  -3.57142857e-01],\n       [ -8.06184272e-01,  -4.36285110e-01,  -3.57142857e-01],\n       [ -7.89140509e-01,  -6.14212713e-01,  -3.57142857e-01],\n       [ -7.23378800e-01,  -5.63028320e-01,  -3.57142857e-01],\n       [ -6.77281572e-01,  -7.35723911e-01,  -3.57142857e-01],\n       [ -6.20841441e-01,  -6.74413585e-01,  -3.57142857e-01],\n       [ -5.46948158e-01,  -8.37166478e-01,  -3.57142857e-01],\n       [ -5.01369145e-01,  -7.67402605e-01,  -3.57142857e-01],\n       [ -4.01695425e-01,  -9.15773327e-01,  -3.57142857e-01],\n       [ -3.68220806e-01,  -8.39458883e-01,  -3.57142857e-01],\n       [ -7.88181035e-01,  -2.70582891e-01,  -3.57142857e-01],\n       [ -7.32894793e-01,  -3.96622828e-01,  -3.57142857e-01],\n       [ -6.57617091e-01,  -5.11843927e-01,  -3.57142857e-01],\n       [ -5.64401310e-01,  -6.13103259e-01,  -3.57142857e-01],\n       [ -4.55790132e-01,  -6.97638732e-01,  -3.57142857e-01],\n       [ -3.34746187e-01,  -7.63144439e-01,  -3.57142857e-01],\n       [ -7.09362931e-01,  -2.43524602e-01,  -3.57142857e-01],\n       [ -6.59605313e-01,  -3.56960545e-01,  -3.57142857e-01],\n       [ -5.91855382e-01,  -4.60659535e-01,  -3.57142857e-01],\n       [ -5.07961179e-01,  -5.51792933e-01,  -3.57142857e-01],\n       [ -4.10211119e-01,  -6.27874859e-01,  -3.57142857e-01],\n       [ -3.01271568e-01,  -6.86829995e-01,  -3.57142857e-01],\n       [ -6.30544828e-01,  -2.16466313e-01,  -3.57142857e-01],\n       [ -5.86315834e-01,  -3.17298262e-01,  -3.57142857e-01],\n       [ -5.26093673e-01,  -4.09475142e-01,  -3.57142857e-01],\n       [ -4.51521048e-01,  -4.90482607e-01,  -3.57142857e-01],\n       [ -3.64632105e-01,  -5.58110986e-01,  -3.57142857e-01],\n       [ -2.67796950e-01,  -6.10515551e-01,  -3.57142857e-01],\n       [ -5.51726724e-01,  -1.89408024e-01,  -3.57142857e-01],\n       [ -5.13026355e-01,  -2.77635979e-01,  -3.57142857e-01],\n       [ -4.60331964e-01,  -3.58290749e-01,  -3.57142857e-01],\n       [ -3.95080917e-01,  -4.29172281e-01,  -3.57142857e-01],\n       [ -3.19053092e-01,  -4.88347112e-01,  -3.57142857e-01],\n       [ -2.34322331e-01,  -5.34201107e-01,  -3.57142857e-01],\n       [ -4.72908621e-01,  -1.62349735e-01,  -3.57142857e-01],\n       [ -4.39736876e-01,  -2.37973697e-01,  -3.57142857e-01],\n       [ -3.94570255e-01,  -3.07106356e-01,  -3.57142857e-01],\n       [ -3.38640786e-01,  -3.67861955e-01,  -3.57142857e-01],\n       [ -2.73474079e-01,  -4.18583239e-01,  -3.57142857e-01],\n       [ -2.00847712e-01,  -4.57886663e-01,  -3.57142857e-01],\n       [ -2.90292421e-01,  -3.12221863e-01,  -3.57142857e-01],\n       [ -3.43107373e-01,  -2.67051188e-01,  -3.57142857e-01],\n       [ -2.31319311e-01,  -3.50702994e-01,  -3.57142857e-01],\n       [ -1.67373094e-01,  -3.81572219e-01,  -3.57142857e-01],\n       [ -2.41944057e-01,  -2.56581770e-01,  -3.57142857e-01],\n       [ -2.91644492e-01,  -2.26996019e-01,  -3.57142857e-01],\n       [ -1.89164543e-01,  -2.82822750e-01,  -3.57142857e-01],\n       [ -1.33898475e-01,  -3.05257776e-01,  -3.57142857e-01],\n       [ -1.93595692e-01,  -2.00941678e-01,  -3.57142857e-01],\n       [ -2.40181610e-01,  -1.86940851e-01,  -3.57142857e-01],\n       [ -1.47009774e-01,  -2.14942505e-01,  -3.57142857e-01],\n       [ -1.00423856e-01,  -2.28943332e-01,  -3.57142857e-01],\n       [ -6.69492374e-02,  -1.52628888e-01,  -3.57142857e-01],\n       [ -1.25405441e-01,  -1.47867075e-01,  -3.57142857e-01],\n       [ -3.34746187e-02,  -7.63144439e-02,  -3.57142857e-01],\n       [ -1.03801108e-01,  -8.07916455e-02,  -3.57142857e-01],\n       [ -1.83861645e-01,  -1.43105263e-01,  -3.57142857e-01],\n       [ -1.74127598e-01,  -8.52688471e-02,  -3.57142857e-01],\n       [ -2.42317849e-01,  -1.38343450e-01,  -3.57142857e-01],\n       [ -2.44454087e-01,  -8.97460487e-02,  -3.57142857e-01],\n       [ -3.20605599e-01,  -1.13947277e-01,  -3.57142857e-01],\n       [ -3.96757110e-01,  -1.38148506e-01,  -3.57142857e-01],\n       [ -3.08124191e-01,  -1.71553532e-01,  -3.57142857e-01],\n       [ -3.73930533e-01,  -2.04763614e-01,  -3.57142857e-01],\n       [ -2.45485487e-01,  -9.69400266e-01,  -3.57142857e-01],\n       [ -2.25028363e-01,  -8.88616910e-01,  -3.57142857e-01],\n       [ -8.25793455e-02,  -9.96584493e-01,  -3.57142857e-01],\n       [ -7.56977333e-02,  -9.13535785e-01,  -3.57142857e-01],\n       [  8.25793455e-02,  -9.96584493e-01,  -3.57142857e-01],\n       [  7.56977333e-02,  -9.13535785e-01,  -3.57142857e-01],\n       [  2.45485487e-01,  -9.69400266e-01,  -3.57142857e-01],\n       [  2.25028363e-01,  -8.88616910e-01,  -3.57142857e-01],\n       [  4.01695425e-01,  -9.15773327e-01,  -3.57142857e-01],\n       [  3.68220806e-01,  -8.39458883e-01,  -3.57142857e-01],\n       [  5.46948158e-01,  -8.37166478e-01,  -3.57142857e-01],\n       [  5.01369145e-01,  -7.67402605e-01,  -3.57142857e-01],\n       [ -2.04571239e-01,  -8.07833555e-01,  -3.57142857e-01],\n       [ -6.88161212e-02,  -8.30487078e-01,  -3.57142857e-01],\n       [  6.88161212e-02,  -8.30487078e-01,  -3.57142857e-01],\n       [  2.04571239e-01,  -8.07833555e-01,  -3.57142857e-01],\n       [  3.34746187e-01,  -7.63144439e-01,  -3.57142857e-01],\n       [  4.55790132e-01,  -6.97638732e-01,  -3.57142857e-01],\n       [ -1.84114115e-01,  -7.27050199e-01,  -3.57142857e-01],\n       [ -6.19345091e-02,  -7.47438370e-01,  -3.57142857e-01],\n       [  6.19345091e-02,  -7.47438370e-01,  -3.57142857e-01],\n       [  1.84114115e-01,  -7.27050199e-01,  -3.57142857e-01],\n       [  3.01271568e-01,  -6.86829995e-01,  -3.57142857e-01],\n       [  4.10211119e-01,  -6.27874859e-01,  -3.57142857e-01],\n       [ -1.63656991e-01,  -6.46266844e-01,  -3.57142857e-01],\n       [ -5.50528970e-02,  -6.64389662e-01,  -3.57142857e-01],\n       [  5.50528970e-02,  -6.64389662e-01,  -3.57142857e-01],\n       [  1.63656991e-01,  -6.46266844e-01,  -3.57142857e-01],\n       [  2.67796950e-01,  -6.10515551e-01,  -3.57142857e-01],\n       [  3.64632105e-01,  -5.58110986e-01,  -3.57142857e-01],\n       [ -1.43199867e-01,  -5.65483488e-01,  -3.57142857e-01],\n       [ -4.81712849e-02,  -5.81340954e-01,  -3.57142857e-01],\n       [  4.81712849e-02,  -5.81340954e-01,  -3.57142857e-01],\n       [  1.43199867e-01,  -5.65483488e-01,  -3.57142857e-01],\n       [  2.34322331e-01,  -5.34201107e-01,  -3.57142857e-01],\n       [  3.19053092e-01,  -4.88347112e-01,  -3.57142857e-01],\n       [ -1.22742744e-01,  -4.84700133e-01,  -3.57142857e-01],\n       [ -4.12896727e-02,  -4.98292247e-01,  -3.57142857e-01],\n       [  4.12896727e-02,  -4.98292247e-01,  -3.57142857e-01],\n       [  1.22742744e-01,  -4.84700133e-01,  -3.57142857e-01],\n       [  2.00847712e-01,  -4.57886663e-01,  -3.57142857e-01],\n       [  2.73474079e-01,  -4.18583239e-01,  -3.57142857e-01],\n       [  1.02606772e-01,  -4.13792257e-01,  -3.57142857e-01],\n       [  3.59043567e-02,  -4.33301147e-01,  -3.57142857e-01],\n       [  1.67077120e-01,  -3.85469130e-01,  -3.57142857e-01],\n       [  2.27895066e-01,  -3.48819366e-01,  -3.57142857e-01],\n       [  8.24708009e-02,  -3.42884381e-01,  -3.57142857e-01],\n       [  3.05190406e-02,  -3.68310047e-01,  -3.57142857e-01],\n       [  1.33306527e-01,  -3.13051596e-01,  -3.57142857e-01],\n       [  1.82316053e-01,  -2.79055493e-01,  -3.57142857e-01],\n       [  6.23348295e-02,  -2.71976505e-01,  -3.57142857e-01],\n       [  2.51337245e-02,  -3.03318947e-01,  -3.57142857e-01],\n       [  9.95359345e-02,  -2.40634062e-01,  -3.57142857e-01],\n       [  1.36737040e-01,  -2.09291620e-01,  -3.57142857e-01],\n       [  9.11580264e-02,  -1.39527746e-01,  -3.57142857e-01],\n       [  5.51990834e-02,  -1.85860856e-01,  -3.57142857e-01],\n       [  4.55790132e-02,  -6.97638732e-02,  -3.57142857e-01],\n       [  1.08622324e-02,  -1.31087650e-01,  -3.57142857e-01],\n       [  1.92401405e-02,  -2.32193966e-01,  -3.57142857e-01],\n       [ -2.38545485e-02,  -1.92411427e-01,  -3.57142857e-01],\n       [ -1.67188024e-02,  -2.78527075e-01,  -3.57142857e-01],\n       [ -5.85713293e-02,  -2.53735203e-01,  -3.57142857e-01],\n       [ -7.99618007e-02,  -3.30723513e-01,  -3.57142857e-01],\n       [ -1.01352272e-01,  -4.07711823e-01,  -3.57142857e-01],\n       [ -2.49090925e-02,  -3.51782132e-01,  -3.57142857e-01],\n       [ -3.30993826e-02,  -4.25037189e-01,  -3.57142857e-01],\n       [  6.77281572e-01,  -7.35723911e-01,  -3.57142857e-01],\n       [  6.20841441e-01,  -6.74413585e-01,  -3.57142857e-01],\n       [  7.89140509e-01,  -6.14212713e-01,  -3.57142857e-01],\n       [  7.23378800e-01,  -5.63028320e-01,  -3.57142857e-01],\n       [  8.79473751e-01,  -4.75947393e-01,  -3.57142857e-01],\n       [  8.06184272e-01,  -4.36285110e-01,  -3.57142857e-01],\n       [  9.45817242e-01,  -3.24699469e-01,  -3.57142857e-01],\n       [  8.66999138e-01,  -2.97641180e-01,  -3.57142857e-01],\n       [  9.86361303e-01,  -1.64594590e-01,  -3.57142857e-01],\n       [  9.04164528e-01,  -1.50878374e-01,  -3.57142857e-01],\n       [  5.64401310e-01,  -6.13103259e-01,  -3.57142857e-01],\n       [  6.57617091e-01,  -5.11843927e-01,  -3.57142857e-01],\n       [  7.32894793e-01,  -3.96622828e-01,  -3.57142857e-01],\n       [  7.88181035e-01,  -2.70582891e-01,  -3.57142857e-01],\n       [  8.21967753e-01,  -1.37162159e-01,  -3.57142857e-01],\n       [  5.07961179e-01,  -5.51792933e-01,  -3.57142857e-01],\n       [  5.91855382e-01,  -4.60659535e-01,  -3.57142857e-01],\n       [  6.59605313e-01,  -3.56960545e-01,  -3.57142857e-01],\n       [  7.09362931e-01,  -2.43524602e-01,  -3.57142857e-01],\n       [  7.39770978e-01,  -1.23445943e-01,  -3.57142857e-01],\n       [  4.51521048e-01,  -4.90482607e-01,  -3.57142857e-01],\n       [  5.26093673e-01,  -4.09475142e-01,  -3.57142857e-01],\n       [  5.86315834e-01,  -3.17298262e-01,  -3.57142857e-01],\n       [  6.30544828e-01,  -2.16466313e-01,  -3.57142857e-01],\n       [  6.57574202e-01,  -1.09729727e-01,  -3.57142857e-01],\n       [  3.95080917e-01,  -4.29172281e-01,  -3.57142857e-01],\n       [  4.60331964e-01,  -3.58290749e-01,  -3.57142857e-01],\n       [  5.13026355e-01,  -2.77635979e-01,  -3.57142857e-01],\n       [  5.51726724e-01,  -1.89408024e-01,  -3.57142857e-01],\n       [  5.75377427e-01,  -9.60135110e-02,  -3.57142857e-01],\n       [  3.38640786e-01,  -3.67861955e-01,  -3.57142857e-01],\n       [  3.94570255e-01,  -3.07106356e-01,  -3.57142857e-01],\n       [  4.39736876e-01,  -2.37973697e-01,  -3.57142857e-01],\n       [  4.72908621e-01,  -1.62349735e-01,  -3.57142857e-01],\n       [  4.93180652e-01,  -8.22972951e-02,  -3.57142857e-01],\n       [  4.02533591e-01,  -1.40423963e-01,  -3.57142857e-01],\n       [  3.82383017e-01,  -2.06935340e-01,  -3.57142857e-01],\n       [  4.14084357e-01,  -7.09602665e-02,  -3.57142857e-01],\n       [  3.32158562e-01,  -1.18498191e-01,  -3.57142857e-01],\n       [  3.25029158e-01,  -1.75896984e-01,  -3.57142857e-01],\n       [  3.34988061e-01,  -5.96232379e-02,  -3.57142857e-01],\n       [  2.61783533e-01,  -9.65724185e-02,  -3.57142857e-01],\n       [  2.67675299e-01,  -1.44858628e-01,  -3.57142857e-01],\n       [  2.55891766e-01,  -4.82862093e-02,  -3.57142857e-01],\n       [  1.85787515e-01,  -5.54454306e-02,  -3.57142857e-01],\n       [  1.15683264e-01,  -6.26046519e-02,  -3.57142857e-01],\n       [  2.04908364e-01,  -1.10890861e-01,  -3.57142857e-01],\n       [  1.48033195e-01,  -1.25209304e-01,  -3.57142857e-01],\n       [  2.24029213e-01,  -1.66336292e-01,  -3.57142857e-01],\n       [  1.80383126e-01,  -1.87813956e-01,  -3.57142857e-01],\n       [  2.33135679e-01,  -2.47829956e-01,  -3.57142857e-01],\n       [  2.85888233e-01,  -3.07845955e-01,  -3.57142857e-01],\n       [  2.80876227e-01,  -2.13259647e-01,  -3.57142857e-01],\n       [  3.37723241e-01,  -2.60183001e-01,  -3.57142857e-01],\n       [  1.00000000e+00,   0.00000000e+00,  -5.00000000e-01],\n       [  9.86361303e-01,   1.64594590e-01,  -5.00000000e-01],\n       [  8.85385799e-01,   1.47859195e-01,  -5.00000000e-01],\n       [  8.98367845e-01,   5.09955106e-05,  -5.00000000e-01],\n       [  9.45817242e-01,   3.24699469e-01,  -5.00000000e-01],\n       [  8.50003984e-01,   2.91939989e-01,  -5.00000000e-01],\n       [  8.79473751e-01,   4.75947393e-01,  -5.00000000e-01],\n       [  7.90244661e-01,   4.27798478e-01,  -5.00000000e-01],\n       [  7.89140509e-01,   6.14212713e-01,  -5.00000000e-01],\n       [  7.09163527e-01,   5.52078099e-01,  -5.00000000e-01],\n       [  6.77281572e-01,   7.35723911e-01,  -5.00000000e-01],\n       [  6.10767838e-01,   6.63497160e-01,  -5.00000000e-01],\n       [  5.46948158e-01,   8.37166478e-01,  -5.00000000e-01],\n       [  4.93229607e-01,   7.54957609e-01,  -5.00000000e-01],\n       [  4.01695425e-01,   9.15773327e-01,  -5.00000000e-01],\n       [  3.62235106e-01,   8.25828858e-01,  -5.00000000e-01],\n       [  7.94125993e-01,   1.32740732e-01,  -5.00000000e-01],\n       [  8.00800813e-01,   1.88651800e-04,  -5.00000000e-01],\n       [  7.57532995e-01,   2.60531900e-01,  -5.00000000e-01],\n       [  7.05311522e-01,   3.82151873e-01,  -5.00000000e-01],\n       [  6.33176967e-01,   4.93188206e-01,  -5.00000000e-01],\n       [  5.44359740e-01,   5.91554707e-01,  -5.00000000e-01],\n       [  4.39850704e-01,   6.73367897e-01,  -5.00000000e-01],\n       [  3.23078188e-01,   7.36661594e-01,  -5.00000000e-01],\n       [  7.01128052e-01,   1.17677998e-01,  -5.00000000e-01],\n       [  7.11649572e-01,   3.33951524e-04,  -5.00000000e-01],\n       [  6.72966890e-01,   2.31894729e-01,  -5.00000000e-01],\n       [  6.25669634e-01,   3.39513212e-01,  -5.00000000e-01],\n       [  5.64027416e-01,   4.39616287e-01,  -5.00000000e-01],\n       [  4.84508482e-01,   5.26668700e-01,  -5.00000000e-01],\n       [  3.91560912e-01,   5.99504467e-01,  -5.00000000e-01],\n       [  2.87602059e-01,   6.55840087e-01,  -5.00000000e-01],\n       [  6.17706136e-01,   1.04228490e-01,  -5.00000000e-01],\n       [  6.24422079e-01,   6.32285718e-04,  -5.00000000e-01],\n       [  5.91740630e-01,   2.04740807e-01,  -5.00000000e-01],\n       [  5.53662436e-01,   3.01062778e-01,  -5.00000000e-01],\n       [  4.96990333e-01,   3.87922085e-01,  -5.00000000e-01],\n       [  4.26905739e-01,   4.64324206e-01,  -5.00000000e-01],\n       [  3.44867105e-01,   5.28103963e-01,  -5.00000000e-01],\n       [  2.53361833e-01,   5.77868140e-01,  -5.00000000e-01],\n       [  5.38389125e-01,   9.17206205e-02,  -5.00000000e-01],\n       [  5.45454368e-01,   8.70266955e-04,  -5.00000000e-01],\n       [  5.17997560e-01,   1.80324497e-01,  -5.00000000e-01],\n       [  4.84931719e-01,   2.64732797e-01,  -5.00000000e-01],\n       [  4.36383812e-01,   3.41220989e-01,  -5.00000000e-01],\n       [  3.74978458e-01,   4.07950157e-01,  -5.00000000e-01],\n       [  3.02772277e-01,   4.63502415e-01,  -5.00000000e-01],\n       [  2.22468936e-01,   5.07437291e-01,  -5.00000000e-01],\n       [  4.64471094e-01,   8.04373661e-02,  -5.00000000e-01],\n       [  4.73417894e-01,   1.03377535e-03,  -5.00000000e-01],\n       [  4.49550108e-01,   1.58190845e-01,  -5.00000000e-01],\n       [  4.20123744e-01,   2.31037319e-01,  -5.00000000e-01],\n       [  3.77857876e-01,   2.96451484e-01,  -5.00000000e-01],\n       [  3.26056063e-01,   3.54556465e-01,  -5.00000000e-01],\n       [  2.63231766e-01,   4.02405818e-01,  -5.00000000e-01],\n       [  1.93502599e-01,   4.41318122e-01,  -5.00000000e-01],\n       [  2.80697996e-01,   3.04138803e-01,  -5.00000000e-01],\n       [  3.26828978e-01,   2.57477836e-01,  -5.00000000e-01],\n       [  2.27278087e-01,   3.45852853e-01,  -5.00000000e-01],\n       [  1.66385745e-01,   3.79356622e-01,  -5.00000000e-01],\n       [  2.41953376e-01,   2.58408137e-01,  -5.00000000e-01],\n       [  2.80636530e-01,   2.22682050e-01,  -5.00000000e-01],\n       [  1.94021253e-01,   2.91186055e-01,  -5.00000000e-01],\n       [  1.39733872e-01,   3.17911899e-01,  -5.00000000e-01],\n       [  2.12482604e-01,   2.15267429e-01,  -5.00000000e-01],\n       [  2.47964916e-01,   2.00029985e-01,  -5.00000000e-01],\n       [  1.65163116e-01,   2.36189539e-01,  -5.00000000e-01],\n       [  1.14601980e-01,   2.59689793e-01,  -5.00000000e-01],\n       [  8.97246141e-02,   2.03521438e-01,  -5.00000000e-01],\n       [  1.45294294e-01,   1.83523348e-01,  -5.00000000e-01],\n       [  6.53411100e-02,   1.48104882e-01,  -5.00000000e-01],\n       [  1.29690654e-01,   1.28069210e-01,  -5.00000000e-01],\n       [  3.66825649e-02,   8.40224902e-02,  -5.00000000e-01],\n       [  1.15178059e-01,   6.50962363e-02,  -5.00000000e-01],\n       [ -6.90931371e-03,  -1.43821650e-02,  -5.00000000e-01],\n       [  1.13891956e-01,  -7.46169753e-03,  -5.00000000e-01],\n       [  2.00104806e-01,   1.68875989e-01,  -5.00000000e-01],\n       [  1.92634871e-01,   1.16656531e-01,  -5.00000000e-01],\n       [  1.88903990e-01,   5.89872913e-02,  -5.00000000e-01],\n       [  1.90411041e-01,  -2.07447445e-03,  -5.00000000e-01],\n       [  2.50775614e-01,   1.62140475e-01,  -5.00000000e-01],\n       [  2.53396140e-01,   1.13391119e-01,  -5.00000000e-01],\n       [  2.55727085e-01,   5.80397186e-02,  -5.00000000e-01],\n       [  2.60266073e-01,   6.10581640e-04,  -5.00000000e-01],\n       [  3.30524944e-01,   1.08041301e-03,  -5.00000000e-01],\n       [  3.24967172e-01,   6.21259202e-02,  -5.00000000e-01],\n       [  4.00230719e-01,   1.27521573e-03,  -5.00000000e-01],\n       [  3.95498789e-01,   7.04582546e-02,  -5.00000000e-01],\n       [  3.15952931e-01,   1.20992370e-01,  -5.00000000e-01],\n       [  3.81156850e-01,   1.37671789e-01,  -5.00000000e-01],\n       [  3.01812446e-01,   1.76311672e-01,  -5.00000000e-01],\n       [  3.60992327e-01,   2.01144550e-01,  -5.00000000e-01],\n       [  2.45485487e-01,   9.69400266e-01,  -5.00000000e-01],\n       [  2.21362099e-01,   8.74184899e-01,  -5.00000000e-01],\n       [  8.25793455e-02,   9.96584493e-01,  -5.00000000e-01],\n       [  7.44564944e-02,   8.98711097e-01,  -5.00000000e-01],\n       [ -8.25793455e-02,   9.96584493e-01,  -5.00000000e-01],\n       [ -7.44723590e-02,   8.98720257e-01,  -5.00000000e-01],\n       [ -2.45485487e-01,   9.69400266e-01,  -5.00000000e-01],\n       [ -2.20691802e-01,   8.71719923e-01,  -5.00000000e-01],\n       [ -4.01695425e-01,   9.15773327e-01,  -5.00000000e-01],\n       [ -3.60903765e-01,   8.22973696e-01,  -5.00000000e-01],\n       [ -5.46948158e-01,   8.37166478e-01,  -5.00000000e-01],\n       [ -4.91337090e-01,   7.52168583e-01,  -5.00000000e-01],\n       [ -6.77281572e-01,   7.35723911e-01,  -5.00000000e-01],\n       [ -6.08422426e-01,   6.60965795e-01,  -5.00000000e-01],\n       [  1.97440834e-01,   7.79839076e-01,  -5.00000000e-01],\n       [  6.64059355e-02,   8.01246618e-01,  -5.00000000e-01],\n       [ -6.62645469e-02,   8.01029588e-01,  -5.00000000e-01],\n       [ -1.96854349e-01,   7.78286921e-01,  -5.00000000e-01],\n       [ -3.21946321e-01,   7.34659580e-01,  -5.00000000e-01],\n       [ -4.36798391e-01,   6.69120390e-01,  -5.00000000e-01],\n       [ -5.40612618e-01,   5.87506757e-01,  -5.00000000e-01],\n       [  1.75775694e-01,   6.94385920e-01,  -5.00000000e-01],\n       [  5.91673686e-02,   7.13777718e-01,  -5.00000000e-01],\n       [ -5.88119880e-02,   7.12616793e-01,  -5.00000000e-01],\n       [ -1.74101170e-01,   6.89878982e-01,  -5.00000000e-01],\n       [ -2.84169826e-01,   6.49644503e-01,  -5.00000000e-01],\n       [ -3.86858078e-01,   5.93199666e-01,  -5.00000000e-01],\n       [ -4.79144350e-01,   5.20924297e-01,  -5.00000000e-01],\n       [  1.54837478e-01,   6.11910244e-01,  -5.00000000e-01],\n       [  5.21907761e-02,   6.29193467e-01,  -5.00000000e-01],\n       [ -5.12184367e-02,   6.25994011e-01,  -5.00000000e-01],\n       [ -1.53060733e-01,   6.08704209e-01,  -5.00000000e-01],\n       [ -2.50417746e-01,   5.73884869e-01,  -5.00000000e-01],\n       [ -3.39314709e-01,   5.21301649e-01,  -5.00000000e-01],\n       [ -4.20125339e-01,   4.57152827e-01,  -5.00000000e-01],\n       [  1.35887547e-01,   5.37522969e-01,  -5.00000000e-01],\n       [  4.57175793e-02,   5.49353160e-01,  -5.00000000e-01],\n       [ -4.41750548e-02,   5.47282691e-01,  -5.00000000e-01],\n       [ -1.32346943e-01,   5.31088099e-01,  -5.00000000e-01],\n       [ -2.17069383e-01,   5.00276789e-01,  -5.00000000e-01],\n       [ -2.95441098e-01,   4.55297464e-01,  -5.00000000e-01],\n       [ -3.66529630e-01,   3.99203743e-01,  -5.00000000e-01],\n       [  1.17538501e-01,   4.65928006e-01,  -5.00000000e-01],\n       [  3.96664619e-02,   4.79172226e-01,  -5.00000000e-01],\n       [ -3.75472975e-02,   4.75808900e-01,  -5.00000000e-01],\n       [ -1.13238534e-01,   4.61485351e-01,  -5.00000000e-01],\n       [ -1.87421164e-01,   4.35519238e-01,  -5.00000000e-01],\n       [ -2.54075366e-01,   3.93685438e-01,  -5.00000000e-01],\n       [ -3.15875504e-01,   3.44415828e-01,  -5.00000000e-01],\n       [ -9.42722138e-02,   3.97659750e-01,  -5.00000000e-01],\n       [ -3.16082042e-02,   4.12808353e-01,  -5.00000000e-01],\n       [ -1.57313402e-01,   3.72682840e-01,  -5.00000000e-01],\n       [ -2.13902082e-01,   3.35146284e-01,  -5.00000000e-01],\n       [ -2.67798778e-01,   2.92234617e-01,  -5.00000000e-01],\n       [ -7.38121164e-02,   3.40587375e-01,  -5.00000000e-01],\n       [ -2.54537279e-02,   3.56949863e-01,  -5.00000000e-01],\n       [ -1.23885635e-01,   3.12926202e-01,  -5.00000000e-01],\n       [ -1.73736614e-01,   2.79179204e-01,  -5.00000000e-01],\n       [ -2.22166180e-01,   2.41993646e-01,  -5.00000000e-01],\n       [ -4.95025577e-02,   2.94314076e-01,  -5.00000000e-01],\n       [ -1.98454920e-02,   3.18279461e-01,  -5.00000000e-01],\n       [ -8.72841201e-02,   2.62569352e-01,  -5.00000000e-01],\n       [ -1.29852384e-01,   2.26256159e-01,  -5.00000000e-01],\n       [ -1.74652198e-01,   1.90687531e-01,  -5.00000000e-01],\n       [ -1.29778666e-01,   1.38814436e-01,  -5.00000000e-01],\n       [ -8.36228415e-02,   1.77780710e-01,  -5.00000000e-01],\n       [ -8.16435423e-02,   7.97110013e-02,  -5.00000000e-01],\n       [ -3.02608410e-02,   1.29131270e-01,  -5.00000000e-01],\n       [ -4.40670309e-02,   2.18498201e-01,  -5.00000000e-01],\n       [  7.24710626e-03,   1.80262002e-01,  -5.00000000e-01],\n       [ -1.03471103e-02,   2.62032046e-01,  -5.00000000e-01],\n       [  3.73861516e-02,   2.31491239e-01,  -5.00000000e-01],\n       [  1.54198095e-02,   3.03574443e-01,  -5.00000000e-01],\n       [  6.32129499e-02,   2.82903258e-01,  -5.00000000e-01],\n       [  8.26525900e-02,   3.39405613e-01,  -5.00000000e-01],\n       [  1.00909910e-01,   4.03015805e-01,  -5.00000000e-01],\n       [  2.64893934e-02,   3.53483382e-01,  -5.00000000e-01],\n       [  3.36435314e-02,   4.12308571e-01,  -5.00000000e-01],\n       [ -7.89140509e-01,   6.14212713e-01,  -5.00000000e-01],\n       [ -7.08964474e-01,   5.51797869e-01,  -5.00000000e-01],\n       [ -8.79473751e-01,   4.75947393e-01,  -5.00000000e-01],\n       [ -7.90197698e-01,   4.27606295e-01,  -5.00000000e-01],\n       [ -9.45817242e-01,   3.24699469e-01,  -5.00000000e-01],\n       [ -8.49856882e-01,   2.91743625e-01,  -5.00000000e-01],\n       [ -9.86361303e-01,   1.64594590e-01,  -5.00000000e-01],\n       [ -8.86275569e-01,   1.47901385e-01,  -5.00000000e-01],\n       [ -1.00000000e+00,  -1.22464680e-16,  -5.00000000e-01],\n       [ -8.98473805e-01,   1.15381832e-05,  -5.00000000e-01],\n       [ -9.86361303e-01,  -1.64594590e-01,  -5.00000000e-01],\n       [ -8.86174824e-01,  -1.47883246e-01,  -5.00000000e-01],\n       [ -6.30001456e-01,   4.90383785e-01,  -5.00000000e-01],\n       [ -7.02273443e-01,   3.80015358e-01,  -5.00000000e-01],\n       [ -7.55313724e-01,   2.59296912e-01,  -5.00000000e-01],\n       [ -7.87658470e-01,   1.31485016e-01,  -5.00000000e-01],\n       [ -7.98470908e-01,   4.55107425e-05,  -5.00000000e-01],\n       [ -7.87494160e-01,  -1.31426735e-01,  -5.00000000e-01],\n       [ -5.58627682e-01,   4.34835928e-01,  -5.00000000e-01],\n       [ -6.22870183e-01,   3.37003542e-01,  -5.00000000e-01],\n       [ -6.69946331e-01,   2.29998633e-01,  -5.00000000e-01],\n       [ -6.98601469e-01,   1.16688217e-01,  -5.00000000e-01],\n       [ -7.08116470e-01,   1.05809214e-04,  -5.00000000e-01],\n       [ -6.98261773e-01,  -1.16539499e-01,  -5.00000000e-01],\n       [ -4.90039236e-01,   3.81447281e-01,  -5.00000000e-01],\n       [ -5.46674699e-01,   2.95688707e-01,  -5.00000000e-01],\n       [ -5.88083370e-01,   2.01927138e-01,  -5.00000000e-01],\n       [ -6.13127891e-01,   1.02579208e-01,  -5.00000000e-01],\n       [ -6.21326911e-01,   2.49679885e-04,  -5.00000000e-01],\n       [ -6.12659159e-01,  -1.02238675e-01,  -5.00000000e-01],\n       [ -4.28201630e-01,   3.33102542e-01,  -5.00000000e-01],\n       [ -4.78292897e-01,   2.58422199e-01,  -5.00000000e-01],\n       [ -5.14588632e-01,   1.76707278e-01,  -5.00000000e-01],\n       [ -5.36209382e-01,   9.00148497e-02,  -5.00000000e-01],\n       [ -5.43295990e-01,   5.06463641e-04,  -5.00000000e-01],\n       [ -5.35354600e-01,  -8.93132854e-02,  -5.00000000e-01],\n       [ -3.70108778e-01,   2.87293073e-01,  -5.00000000e-01],\n       [ -4.14823967e-01,   2.23456652e-01,  -5.00000000e-01],\n       [ -4.46176995e-01,   1.53226227e-01,  -5.00000000e-01],\n       [ -4.64366203e-01,   7.86053306e-02,  -5.00000000e-01],\n       [ -4.70979406e-01,   1.02297782e-03,  -5.00000000e-01],\n       [ -4.62590465e-01,  -7.71173269e-02,  -5.00000000e-01],\n       [ -3.98997832e-01,   6.89882561e-02,  -5.00000000e-01],\n       [ -3.84007718e-01,   1.31869003e-01,  -5.00000000e-01],\n       [ -4.02767806e-01,   2.14475434e-03,  -5.00000000e-01],\n       [ -3.93927304e-01,  -6.55498719e-02,  -5.00000000e-01],\n       [ -3.37106322e-01,   6.21262340e-02,  -5.00000000e-01],\n       [ -3.30209654e-01,   1.13360219e-01,  -5.00000000e-01],\n       [ -3.35031657e-01,   5.42266022e-03,  -5.00000000e-01],\n       [ -3.27721161e-01,  -5.42580733e-02,  -5.00000000e-01],\n       [ -2.83447084e-01,   6.21745720e-02,  -5.00000000e-01],\n       [ -2.92001062e-01,   1.00268454e-01,  -5.00000000e-01],\n       [ -2.73720464e-01,   1.23812993e-02,  -5.00000000e-01],\n       [ -2.63751777e-01,  -4.33840760e-02,  -5.00000000e-01],\n       [ -1.98646355e-01,  -3.27574407e-02,  -5.00000000e-01],\n       [ -2.13311244e-01,   2.71005688e-02,  -5.00000000e-01],\n       [ -1.25363626e-01,  -2.31240997e-02,  -5.00000000e-01],\n       [ -1.50402001e-01,   4.80422198e-02,  -5.00000000e-01],\n       [ -2.35376990e-01,   8.01922238e-02,  -5.00000000e-01],\n       [ -1.81647343e-01,   1.06212482e-01,  -5.00000000e-01],\n       [ -2.61648693e-01,   1.25022130e-01,  -5.00000000e-01],\n       [ -2.20441300e-01,   1.56334892e-01,  -5.00000000e-01],\n       [ -2.65822474e-01,   2.01206908e-01,  -5.00000000e-01],\n       [ -3.15933525e-01,   2.43651325e-01,  -5.00000000e-01],\n       [ -3.03727391e-01,   1.58021732e-01,  -5.00000000e-01],\n       [ -3.58146942e-01,   1.91331513e-01,  -5.00000000e-01],\n       [ -9.45817242e-01,  -3.24699469e-01,  -5.00000000e-01],\n       [ -8.49758028e-01,  -2.91753303e-01,  -5.00000000e-01],\n       [ -8.79473751e-01,  -4.75947393e-01,  -5.00000000e-01],\n       [ -7.90200242e-01,  -4.27674318e-01,  -5.00000000e-01],\n       [ -7.89140509e-01,  -6.14212713e-01,  -5.00000000e-01],\n       [ -7.09077565e-01,  -5.51918815e-01,  -5.00000000e-01],\n       [ -6.77281572e-01,  -7.35723911e-01,  -5.00000000e-01],\n       [ -6.08569412e-01,  -6.61074211e-01,  -5.00000000e-01],\n       [ -5.46948158e-01,  -8.37166478e-01,  -5.00000000e-01],\n       [ -4.91428805e-01,  -7.52168929e-01,  -5.00000000e-01],\n       [ -4.01695425e-01,  -9.15773327e-01,  -5.00000000e-01],\n       [ -3.60888838e-01,  -8.22761670e-01,  -5.00000000e-01],\n       [ -7.55188846e-01,  -2.59345642e-01,  -5.00000000e-01],\n       [ -7.02337606e-01,  -3.80192065e-01,  -5.00000000e-01],\n       [ -6.32857312e-01,  -4.92618433e-01,  -5.00000000e-01],\n       [ -5.43151014e-01,  -5.89986003e-01,  -5.00000000e-01],\n       [ -4.37118826e-01,  -6.68993686e-01,  -5.00000000e-01],\n       [ -3.20766354e-01,  -7.31326287e-01,  -5.00000000e-01],\n       [ -6.69734997e-01,  -2.30080512e-01,  -5.00000000e-01],\n       [ -6.22993936e-01,  -3.37335567e-01,  -5.00000000e-01],\n       [ -5.59194551e-01,  -4.35298937e-01,  -5.00000000e-01],\n       [ -4.79828309e-01,  -5.21111684e-01,  -5.00000000e-01],\n       [ -3.87501919e-01,  -5.92949943e-01,  -5.00000000e-01],\n       [ -2.84426825e-01,  -6.48495027e-01,  -5.00000000e-01],\n       [ -5.87684351e-01,  -2.02039854e-01,  -5.00000000e-01],\n       [ -5.46979032e-01,  -2.96347299e-01,  -5.00000000e-01],\n       [ -4.93900401e-01,  -3.84490387e-01,  -5.00000000e-01],\n       [ -4.23918729e-01,  -4.60219985e-01,  -5.00000000e-01],\n       [ -3.40561104e-01,  -5.20838061e-01,  -5.00000000e-01],\n       [ -2.49643040e-01,  -5.69140160e-01,  -5.00000000e-01],\n       [ -5.13658677e-01,  -1.76864123e-01,  -5.00000000e-01],\n       [ -4.78660176e-01,  -2.59652919e-01,  -5.00000000e-01],\n       [ -4.29973501e-01,  -3.34721367e-01,  -5.00000000e-01],\n       [ -3.69196403e-01,  -4.00374180e-01,  -5.00000000e-01],\n       [ -2.97634891e-01,  -4.54622635e-01,  -5.00000000e-01],\n       [ -2.18156550e-01,  -4.97266712e-01,  -5.00000000e-01],\n       [ -4.43955972e-01,  -1.53433052e-01,  -5.00000000e-01],\n       [ -4.14976040e-01,  -2.25780487e-01,  -5.00000000e-01],\n       [ -3.73212995e-01,  -2.90523705e-01,  -5.00000000e-01],\n       [ -3.21264722e-01,  -3.47542486e-01,  -5.00000000e-01],\n       [ -2.57973317e-01,  -3.92813300e-01,  -5.00000000e-01],\n       [ -1.88659023e-01,  -4.29830508e-01,  -5.00000000e-01],\n       [ -2.76959851e-01,  -2.97681175e-01,  -5.00000000e-01],\n       [ -3.22068080e-01,  -2.50675254e-01,  -5.00000000e-01],\n       [ -2.21359240e-01,  -3.34292006e-01,  -5.00000000e-01],\n       [ -1.61403511e-01,  -3.67396927e-01,  -5.00000000e-01],\n       [ -2.36755182e-01,  -2.48291400e-01,  -5.00000000e-01],\n       [ -2.76170203e-01,  -2.14736566e-01,  -5.00000000e-01],\n       [ -1.88145259e-01,  -2.77458362e-01,  -5.00000000e-01],\n       [ -1.33977883e-01,  -3.03968290e-01,  -5.00000000e-01],\n       [ -2.07536544e-01,  -2.03087349e-01,  -5.00000000e-01],\n       [ -2.44322863e-01,  -1.89623778e-01,  -5.00000000e-01],\n       [ -1.60891409e-01,  -2.22173809e-01,  -5.00000000e-01],\n       [ -1.08132613e-01,  -2.43259688e-01,  -5.00000000e-01],\n       [ -8.28302097e-02,  -1.83773619e-01,  -5.00000000e-01],\n       [ -1.41840587e-01,  -1.63689740e-01,  -5.00000000e-01],\n       [ -5.28309761e-02,  -1.16741673e-01,  -5.00000000e-01],\n       [ -1.27483869e-01,  -9.76683493e-02,  -5.00000000e-01],\n       [ -1.97979852e-01,  -1.52716684e-01,  -5.00000000e-01],\n       [ -1.94512087e-01,  -9.49401261e-02,  -5.00000000e-01],\n       [ -2.49063661e-01,  -1.50709568e-01,  -5.00000000e-01],\n       [ -2.55937772e-01,  -1.00112636e-01,  -5.00000000e-01],\n       [ -3.15961679e-01,  -1.13123573e-01,  -5.00000000e-01],\n       [ -3.78395823e-01,  -1.32030107e-01,  -5.00000000e-01],\n       [ -2.99920321e-01,  -1.68386817e-01,  -5.00000000e-01],\n       [ -3.56808685e-01,  -1.95651482e-01,  -5.00000000e-01],\n       [ -2.45485487e-01,  -9.69400266e-01,  -5.00000000e-01],\n       [ -2.20527452e-01,  -8.70963424e-01,  -5.00000000e-01],\n       [ -8.25793455e-02,  -9.96584493e-01,  -5.00000000e-01],\n       [ -7.41639184e-02,  -8.95445060e-01,  -5.00000000e-01],\n       [  8.25793455e-02,  -9.96584493e-01,  -5.00000000e-01],\n       [  7.42192906e-02,  -8.95487034e-01,  -5.00000000e-01],\n       [  2.45485487e-01,  -9.69400266e-01,  -5.00000000e-01],\n       [  2.20573235e-01,  -8.71047175e-01,  -5.00000000e-01],\n       [  4.01695425e-01,  -9.15773327e-01,  -5.00000000e-01],\n       [  3.60903494e-01,  -8.22802941e-01,  -5.00000000e-01],\n       [  5.46948158e-01,  -8.37166478e-01,  -5.00000000e-01],\n       [  4.91381119e-01,  -7.52102570e-01,  -5.00000000e-01],\n       [ -1.95947086e-01,  -7.74127277e-01,  -5.00000000e-01],\n       [ -6.58591700e-02,  -7.95934883e-01,  -5.00000000e-01],\n       [  6.59950160e-02,  -7.96009686e-01,  -5.00000000e-01],\n       [  1.96033858e-01,  -7.74239363e-01,  -5.00000000e-01],\n       [  3.20688739e-01,  -7.31187985e-01,  -5.00000000e-01],\n       [  4.36565689e-01,  -6.68176192e-01,  -5.00000000e-01],\n       [ -1.73710483e-01,  -6.86594431e-01,  -5.00000000e-01],\n       [ -5.83403733e-02,  -7.06068825e-01,  -5.00000000e-01],\n       [  5.85663516e-02,  -7.06222728e-01,  -5.00000000e-01],\n       [  1.73845433e-01,  -6.86833114e-01,  -5.00000000e-01],\n       [  2.84310702e-01,  -6.48392566e-01,  -5.00000000e-01],\n       [  3.87077327e-01,  -5.92417398e-01,  -5.00000000e-01],\n       [ -1.52324550e-01,  -6.02613585e-01,  -5.00000000e-01],\n       [ -5.10827736e-02,  -6.20032612e-01,  -5.00000000e-01],\n       [  5.14407375e-02,  -6.20224621e-01,  -5.00000000e-01],\n       [  1.52492169e-01,  -6.03046508e-01,  -5.00000000e-01],\n       [  2.49356849e-01,  -5.69065471e-01,  -5.00000000e-01],\n       [  3.39522909e-01,  -5.19665127e-01,  -5.00000000e-01],\n       [ -1.32913591e-01,  -5.26851074e-01,  -5.00000000e-01],\n       [ -4.44373752e-02,  -5.42756409e-01,  -5.00000000e-01],\n       [  4.50390459e-02,  -5.43028575e-01,  -5.00000000e-01],\n       [  1.33228954e-01,  -5.27995406e-01,  -5.00000000e-01],\n       [  2.17713616e-01,  -4.97612407e-01,  -5.00000000e-01],\n       [  2.96632422e-01,  -4.54081308e-01,  -5.00000000e-01],\n       [ -1.14420539e-01,  -4.55704117e-01,  -5.00000000e-01],\n       [ -3.79657707e-02,  -4.70906926e-01,  -5.00000000e-01],\n       [  3.90798645e-02,  -4.71331248e-01,  -5.00000000e-01],\n       [  1.15221008e-01,  -4.58994868e-01,  -5.00000000e-01],\n       [  1.87693599e-01,  -4.30660195e-01,  -5.00000000e-01],\n       [  2.56191789e-01,  -3.92327216e-01,  -5.00000000e-01],\n       [  9.77205966e-02,  -3.94673911e-01,  -5.00000000e-01],\n       [  3.36893742e-02,  -4.06727121e-01,  -5.00000000e-01],\n       [  1.58803158e-01,  -3.68132920e-01,  -5.00000000e-01],\n       [  2.17966632e-01,  -3.34109450e-01,  -5.00000000e-01],\n       [  7.83407097e-02,  -3.33990665e-01,  -5.00000000e-01],\n       [  2.86928174e-02,  -3.48638215e-01,  -5.00000000e-01],\n       [  1.29463668e-01,  -3.09306875e-01,  -5.00000000e-01],\n       [  1.81227986e-01,  -2.78393827e-01,  -5.00000000e-01],\n       [  5.64523840e-02,  -2.84774155e-01,  -5.00000000e-01],\n       [  2.50675824e-02,  -3.08228657e-01,  -5.00000000e-01],\n       [  9.81604613e-02,  -2.56263578e-01,  -5.00000000e-01],\n       [  1.45747568e-01,  -2.24725813e-01,  -5.00000000e-01],\n       [  1.08776353e-01,  -1.69704406e-01,  -5.00000000e-01],\n       [  5.96672230e-02,  -2.08411981e-01,  -5.00000000e-01],\n       [  6.52344214e-02,  -1.07234898e-01,  -5.00000000e-01],\n       [  1.12299685e-02,  -1.59491418e-01,  -5.00000000e-01],\n       [  1.94489428e-02,  -2.49212860e-01,  -5.00000000e-01],\n       [ -2.75711446e-02,  -2.14453527e-01,  -5.00000000e-01],\n       [ -1.01419274e-02,  -2.90912747e-01,  -5.00000000e-01],\n       [ -5.63990856e-02,  -2.68644868e-01,  -5.00000000e-01],\n       [ -7.81326349e-02,  -3.26377817e-01,  -5.00000000e-01],\n       [ -9.65225590e-02,  -3.89249328e-01,  -5.00000000e-01],\n       [ -2.31032455e-02,  -3.43177165e-01,  -5.00000000e-01],\n       [ -3.13773433e-02,  -4.05719997e-01,  -5.00000000e-01],\n       [  6.77281572e-01,  -7.35723911e-01,  -5.00000000e-01],\n       [  6.08411375e-01,  -6.60869436e-01,  -5.00000000e-01],\n       [  7.89140509e-01,  -6.14212713e-01,  -5.00000000e-01],\n       [  7.08543237e-01,  -5.51440045e-01,  -5.00000000e-01],\n       [  8.79473751e-01,  -4.75947393e-01,  -5.00000000e-01],\n       [  7.88509514e-01,  -4.26698136e-01,  -5.00000000e-01],\n       [  9.45817242e-01,  -3.24699469e-01,  -5.00000000e-01],\n       [  8.49108858e-01,  -2.91497351e-01,  -5.00000000e-01],\n       [  9.86361303e-01,  -1.64594590e-01,  -5.00000000e-01],\n       [  8.85646606e-01,  -1.47773471e-01,  -5.00000000e-01],\n       [  5.40399291e-01,  -5.86902930e-01,  -5.00000000e-01],\n       [  6.31661212e-01,  -4.91529504e-01,  -5.00000000e-01],\n       [  7.06154712e-01,  -3.82110644e-01,  -5.00000000e-01],\n       [  7.57207596e-01,  -2.59933027e-01,  -5.00000000e-01],\n       [  7.90560453e-01,  -1.31868883e-01,  -5.00000000e-01],\n       [  4.79134872e-01,  -5.20246274e-01,  -5.00000000e-01],\n       [  5.57358575e-01,  -4.33573270e-01,  -5.00000000e-01],\n       [  6.22649467e-01,  -3.36858332e-01,  -5.00000000e-01],\n       [  6.70334308e-01,  -2.30102238e-01,  -5.00000000e-01],\n       [  7.00296083e-01,  -1.16745852e-01,  -5.00000000e-01],\n       [  4.20218711e-01,  -4.56063930e-01,  -5.00000000e-01],\n       [  4.90701218e-01,  -3.81516225e-01,  -5.00000000e-01],\n       [  5.48809022e-01,  -2.96865907e-01,  -5.00000000e-01],\n       [  5.88345346e-01,  -2.02001938e-01,  -5.00000000e-01],\n       [  6.14676740e-01,  -1.02418815e-01,  -5.00000000e-01],\n       [  3.67922567e-01,  -3.98927381e-01,  -5.00000000e-01],\n       [  4.28983572e-01,  -3.33156683e-01,  -5.00000000e-01],\n       [  4.78469364e-01,  -2.58774492e-01,  -5.00000000e-01],\n       [  5.14798664e-01,  -1.76976670e-01,  -5.00000000e-01],\n       [  5.37552876e-01,  -8.96851608e-02,  -5.00000000e-01],\n       [  3.18991616e-01,  -3.45088841e-01,  -5.00000000e-01],\n       [  3.72496945e-01,  -2.88521239e-01,  -5.00000000e-01],\n       [  4.17437712e-01,  -2.25772747e-01,  -5.00000000e-01],\n       [  4.46270753e-01,  -1.54015799e-01,  -5.00000000e-01],\n       [  4.65793651e-01,  -7.81566215e-02,  -5.00000000e-01],\n       [  3.83645678e-01,  -1.33842084e-01,  -5.00000000e-01],\n       [  3.57498091e-01,  -1.93408546e-01,  -5.00000000e-01],\n       [  3.94991079e-01,  -6.75531524e-02,  -5.00000000e-01],\n       [  3.23108985e-01,  -1.16767010e-01,  -5.00000000e-01],\n       [  3.07405476e-01,  -1.66484070e-01,  -5.00000000e-01],\n       [  3.28629393e-01,  -5.96308040e-02,  -5.00000000e-01],\n       [  2.69488721e-01,  -1.08491179e-01,  -5.00000000e-01],\n       [  2.71915330e-01,  -1.47551966e-01,  -5.00000000e-01],\n       [  2.65001259e-01,  -5.64391238e-02,  -5.00000000e-01],\n       [  2.02706372e-01,  -6.38527005e-02,  -5.00000000e-01],\n       [  1.35742212e-01,  -7.83273135e-02,  -5.00000000e-01],\n       [  2.18918251e-01,  -1.20081827e-01,  -5.00000000e-01],\n       [  1.63747295e-01,  -1.39842812e-01,  -5.00000000e-01],\n       [  2.38577514e-01,  -1.67677403e-01,  -5.00000000e-01],\n       [  1.94295104e-01,  -1.94156801e-01,  -5.00000000e-01],\n       [  2.30940050e-01,  -2.44114153e-01,  -5.00000000e-01],\n       [  2.74443311e-01,  -2.95203178e-01,  -5.00000000e-01],\n       [  2.74528072e-01,  -2.06666991e-01,  -5.00000000e-01],\n       [  3.20977283e-01,  -2.46876061e-01,  -5.00000000e-01]\n])\n\n_connectivity = np.array([\n       [   1,    2,    3,    4,    5,    6,    7,    8],\n       [   2,    9,   10,    3,    6,   11,   12,    7],\n       [   9,   13,   14,   10,   11,   15,   16,   12],\n       [  13,   17,   18,   14,   15,   19,   20,   16],\n       [  17,   21,   22,   18,   19,   23,   24,   20],\n       [  21,   25,   26,   22,   23,   27,   28,   24],\n       [  25,   29,   30,   26,   27,   31,   32,   28],\n       [   4,    3,   33,   34,    8,    7,   35,   36],\n       [   3,   10,   37,   33,    7,   12,   38,   35],\n       [  10,   14,   39,   37,   12,   16,   40,   38],\n       [  14,   18,   41,   39,   16,   20,   42,   40],\n       [  18,   22,   43,   41,   20,   24,   44,   42],\n       [  22,   26,   45,   43,   24,   28,   46,   44],\n       [  26,   30,   47,   45,   28,   32,   48,   46],\n       [  34,   33,   49,   50,   36,   35,   51,   52],\n       [  33,   37,   53,   49,   35,   38,   54,   51],\n       [  37,   39,   55,   53,   38,   40,   56,   54],\n       [  39,   41,   57,   55,   40,   42,   58,   56],\n       [  41,   43,   59,   57,   42,   44,   60,   58],\n       [  43,   45,   61,   59,   44,   46,   62,   60],\n       [  45,   47,   63,   61,   46,   48,   64,   62],\n       [  50,   49,   65,   66,   52,   51,   67,   68],\n       [  49,   53,   69,   65,   51,   54,   70,   67],\n       [  53,   55,   71,   69,   54,   56,   72,   70],\n       [  55,   57,   73,   71,   56,   58,   74,   72],\n       [  57,   59,   75,   73,   58,   60,   76,   74],\n       [  59,   61,   77,   75,   60,   62,   78,   76],\n       [  61,   63,   79,   77,   62,   64,   80,   78],\n       [  66,   65,   81,   82,   68,   67,   83,   84],\n       [  65,   69,   85,   81,   67,   70,   86,   83],\n       [  69,   71,   87,   85,   70,   72,   88,   86],\n       [  71,   73,   89,   87,   72,   74,   90,   88],\n       [  73,   75,   91,   89,   74,   76,   92,   90],\n       [  75,   77,   93,   91,   76,   78,   94,   92],\n       [  77,   79,   95,   93,   78,   80,   96,   94],\n       [  82,   81,   97,   98,   84,   83,   99,  100],\n       [  81,   85,  101,   97,   83,   86,  102,   99],\n       [  85,   87,  103,  101,   86,   88,  104,  102],\n       [  87,   89,  105,  103,   88,   90,  106,  104],\n       [  89,   91,  107,  105,   90,   92,  108,  106],\n       [  91,   93,  109,  107,   92,   94,  110,  108],\n       [  93,   95,  111,  109,   94,   96,  112,  110],\n       [ 105,  107,  113,  114,  106,  108,  115,  116],\n       [ 107,  109,  117,  113,  108,  110,  118,  115],\n       [ 109,  111,  119,  117,  110,  112,  120,  118],\n       [ 114,  113,  121,  122,  116,  115,  123,  124],\n       [ 113,  117,  125,  121,  115,  118,  126,  123],\n       [ 117,  119,  127,  125,  118,  120,  128,  126],\n       [ 122,  121,  129,  130,  124,  123,  131,  132],\n       [ 121,  125,  133,  129,  123,  126,  134,  131],\n       [ 125,  127,  135,  133,  126,  128,  136,  134],\n       [ 135,  137,  138,  133,  136,  139,  140,  134],\n       [ 137,  141,  142,  138,  139,  143,  144,  140],\n       [ 141,  145,  146,  142,  143,  147,  148,  144],\n       [ 145,  149,  150,  146,  147,  151,  152,  148],\n       [ 133,  138,  153,  129,  134,  140,  154,  131],\n       [ 138,  142,  155,  153,  140,  144,  156,  154],\n       [ 142,  146,  157,  155,  144,  148,  158,  156],\n       [ 146,  150,  159,  157,  148,  152,  160,  158],\n       [ 129,  153,  161,  130,  131,  154,  162,  132],\n       [ 153,  155,  163,  161,  154,  156,  164,  162],\n       [ 155,  157,  165,  163,  156,  158,  166,  164],\n       [ 157,  159,  167,  165,  158,  160,  168,  166],\n       [ 167,  169,  170,  165,  168,  171,  172,  166],\n       [ 169,  173,  174,  170,  171,  175,  176,  172],\n       [ 173,   98,   97,  174,  175,  100,   99,  176],\n       [ 165,  170,  177,  163,  166,  172,  178,  164],\n       [ 170,  174,  179,  177,  172,  176,  180,  178],\n       [ 174,   97,  101,  179,  176,   99,  102,  180],\n       [ 163,  177,  181,  161,  164,  178,  182,  162],\n       [ 177,  179,  183,  181,  178,  180,  184,  182],\n       [ 179,  101,  103,  183,  180,  102,  104,  184],\n       [ 161,  181,  122,  130,  162,  182,  124,  132],\n       [ 181,  183,  114,  122,  182,  184,  116,  124],\n       [ 183,  103,  105,  114,  184,  104,  106,  116],\n       [  29,  185,  186,   30,   31,  187,  188,   32],\n       [ 185,  189,  190,  186,  187,  191,  192,  188],\n       [ 189,  193,  194,  190,  191,  195,  196,  192],\n       [ 193,  197,  198,  194,  195,  199,  200,  196],\n       [ 197,  201,  202,  198,  199,  203,  204,  200],\n       [ 201,  205,  206,  202,  203,  207,  208,  204],\n       [ 205,  209,  210,  206,  207,  211,  212,  208],\n       [  30,  186,  213,   47,   32,  188,  214,   48],\n       [ 186,  190,  215,  213,  188,  192,  216,  214],\n       [ 190,  194,  217,  215,  192,  196,  218,  216],\n       [ 194,  198,  219,  217,  196,  200,  220,  218],\n       [ 198,  202,  221,  219,  200,  204,  222,  220],\n       [ 202,  206,  223,  221,  204,  208,  224,  222],\n       [ 206,  210,  225,  223,  208,  212,  226,  224],\n       [  47,  213,  227,   63,   48,  214,  228,   64],\n       [ 213,  215,  229,  227,  214,  216,  230,  228],\n       [ 215,  217,  231,  229,  216,  218,  232,  230],\n       [ 217,  219,  233,  231,  218,  220,  234,  232],\n       [ 219,  221,  235,  233,  220,  222,  236,  234],\n       [ 221,  223,  237,  235,  222,  224,  238,  236],\n       [ 223,  225,  239,  237,  224,  226,  240,  238],\n       [  63,  227,  241,   79,   64,  228,  242,   80],\n       [ 227,  229,  243,  241,  228,  230,  244,  242],\n       [ 229,  231,  245,  243,  230,  232,  246,  244],\n       [ 231,  233,  247,  245,  232,  234,  248,  246],\n       [ 233,  235,  249,  247,  234,  236,  250,  248],\n       [ 235,  237,  251,  249,  236,  238,  252,  250],\n       [ 237,  239,  253,  251,  238,  240,  254,  252],\n       [  79,  241,  255,   95,   80,  242,  256,   96],\n       [ 241,  243,  257,  255,  242,  244,  258,  256],\n       [ 243,  245,  259,  257,  244,  246,  260,  258],\n       [ 245,  247,  261,  259,  246,  248,  262,  260],\n       [ 247,  249,  263,  261,  248,  250,  264,  262],\n       [ 249,  251,  265,  263,  250,  252,  266,  264],\n       [ 251,  253,  267,  265,  252,  254,  268,  266],\n       [  95,  255,  269,  111,   96,  256,  270,  112],\n       [ 255,  257,  271,  269,  256,  258,  272,  270],\n       [ 257,  259,  273,  271,  258,  260,  274,  272],\n       [ 259,  261,  275,  273,  260,  262,  276,  274],\n       [ 261,  263,  277,  275,  262,  264,  278,  276],\n       [ 263,  265,  279,  277,  264,  266,  280,  278],\n       [ 265,  267,  281,  279,  266,  268,  282,  280],\n       [ 273,  275,  283,  284,  274,  276,  285,  286],\n       [ 275,  277,  287,  283,  276,  278,  288,  285],\n       [ 277,  279,  289,  287,  278,  280,  290,  288],\n       [ 279,  281,  291,  289,  280,  282,  292,  290],\n       [ 284,  283,  293,  294,  286,  285,  295,  296],\n       [ 283,  287,  297,  293,  285,  288,  298,  295],\n       [ 287,  289,  299,  297,  288,  290,  300,  298],\n       [ 289,  291,  301,  299,  290,  292,  302,  300],\n       [ 294,  293,  303,  304,  296,  295,  305,  306],\n       [ 293,  297,  307,  303,  295,  298,  308,  305],\n       [ 297,  299,  309,  307,  298,  300,  310,  308],\n       [ 299,  301,  311,  309,  300,  302,  312,  310],\n       [ 311,  313,  314,  309,  312,  315,  316,  310],\n       [ 313,  317,  318,  314,  315,  319,  320,  316],\n       [ 317,  149,  145,  318,  319,  151,  147,  320],\n       [ 309,  314,  321,  307,  310,  316,  322,  308],\n       [ 314,  318,  323,  321,  316,  320,  324,  322],\n       [ 318,  145,  141,  323,  320,  147,  143,  324],\n       [ 307,  321,  325,  303,  308,  322,  326,  305],\n       [ 321,  323,  327,  325,  322,  324,  328,  326],\n       [ 323,  141,  137,  327,  324,  143,  139,  328],\n       [ 303,  325,  329,  304,  305,  326,  330,  306],\n       [ 325,  327,  331,  329,  326,  328,  332,  330],\n       [ 327,  137,  135,  331,  328,  139,  136,  332],\n       [ 135,  127,  333,  331,  136,  128,  334,  332],\n       [ 127,  119,  335,  333,  128,  120,  336,  334],\n       [ 119,  111,  269,  335,  120,  112,  270,  336],\n       [ 331,  333,  337,  329,  332,  334,  338,  330],\n       [ 333,  335,  339,  337,  334,  336,  340,  338],\n       [ 335,  269,  271,  339,  336,  270,  272,  340],\n       [ 329,  337,  294,  304,  330,  338,  296,  306],\n       [ 337,  339,  284,  294,  338,  340,  286,  296],\n       [ 339,  271,  273,  284,  340,  272,  274,  286],\n       [ 209,  341,  342,  210,  211,  343,  344,  212],\n       [ 341,  345,  346,  342,  343,  347,  348,  344],\n       [ 345,  349,  350,  346,  347,  351,  352,  348],\n       [ 349,  353,  354,  350,  351,  355,  356,  352],\n       [ 353,  357,  358,  354,  355,  359,  360,  356],\n       [ 357,  361,  362,  358,  359,  363,  364,  360],\n       [ 210,  342,  365,  225,  212,  344,  366,  226],\n       [ 342,  346,  367,  365,  344,  348,  368,  366],\n       [ 346,  350,  369,  367,  348,  352,  370,  368],\n       [ 350,  354,  371,  369,  352,  356,  372,  370],\n       [ 354,  358,  373,  371,  356,  360,  374,  372],\n       [ 358,  362,  375,  373,  360,  364,  376,  374],\n       [ 225,  365,  377,  239,  226,  366,  378,  240],\n       [ 365,  367,  379,  377,  366,  368,  380,  378],\n       [ 367,  369,  381,  379,  368,  370,  382,  380],\n       [ 369,  371,  383,  381,  370,  372,  384,  382],\n       [ 371,  373,  385,  383,  372,  374,  386,  384],\n       [ 373,  375,  387,  385,  374,  376,  388,  386],\n       [ 239,  377,  389,  253,  240,  378,  390,  254],\n       [ 377,  379,  391,  389,  378,  380,  392,  390],\n       [ 379,  381,  393,  391,  380,  382,  394,  392],\n       [ 381,  383,  395,  393,  382,  384,  396,  394],\n       [ 383,  385,  397,  395,  384,  386,  398,  396],\n       [ 385,  387,  399,  397,  386,  388,  400,  398],\n       [ 253,  389,  401,  267,  254,  390,  402,  268],\n       [ 389,  391,  403,  401,  390,  392,  404,  402],\n       [ 391,  393,  405,  403,  392,  394,  406,  404],\n       [ 393,  395,  407,  405,  394,  396,  408,  406],\n       [ 395,  397,  409,  407,  396,  398,  410,  408],\n       [ 397,  399,  411,  409,  398,  400,  412,  410],\n       [ 267,  401,  413,  281,  268,  402,  414,  282],\n       [ 401,  403,  415,  413,  402,  404,  416,  414],\n       [ 403,  405,  417,  415,  404,  406,  418,  416],\n       [ 405,  407,  419,  417,  406,  408,  420,  418],\n       [ 407,  409,  421,  419,  408,  410,  422,  420],\n       [ 409,  411,  423,  421,  410,  412,  424,  422],\n       [ 417,  419,  425,  426,  418,  420,  427,  428],\n       [ 419,  421,  429,  425,  420,  422,  430,  427],\n       [ 421,  423,  431,  429,  422,  424,  432,  430],\n       [ 426,  425,  433,  434,  428,  427,  435,  436],\n       [ 425,  429,  437,  433,  427,  430,  438,  435],\n       [ 429,  431,  439,  437,  430,  432,  440,  438],\n       [ 434,  433,  441,  442,  436,  435,  443,  444],\n       [ 433,  437,  445,  441,  435,  438,  446,  443],\n       [ 437,  439,  447,  445,  438,  440,  448,  446],\n       [ 447,  449,  450,  445,  448,  451,  452,  446],\n       [ 449,  453,  454,  450,  451,  455,  456,  452],\n       [ 453,  149,  317,  454,  455,  151,  319,  456],\n       [ 445,  450,  457,  441,  446,  452,  458,  443],\n       [ 450,  454,  459,  457,  452,  456,  460,  458],\n       [ 454,  317,  313,  459,  456,  319,  315,  460],\n       [ 441,  457,  461,  442,  443,  458,  462,  444],\n       [ 457,  459,  463,  461,  458,  460,  464,  462],\n       [ 459,  313,  311,  463,  460,  315,  312,  464],\n       [ 311,  301,  465,  463,  312,  302,  466,  464],\n       [ 301,  291,  467,  465,  302,  292,  468,  466],\n       [ 291,  281,  413,  467,  292,  282,  414,  468],\n       [ 463,  465,  469,  461,  464,  466,  470,  462],\n       [ 465,  467,  471,  469,  466,  468,  472,  470],\n       [ 467,  413,  415,  471,  468,  414,  416,  472],\n       [ 461,  469,  434,  442,  462,  470,  436,  444],\n       [ 469,  471,  426,  434,  470,  472,  428,  436],\n       [ 471,  415,  417,  426,  472,  416,  418,  428],\n       [ 361,  473,  474,  362,  363,  475,  476,  364],\n       [ 473,  477,  478,  474,  475,  479,  480,  476],\n       [ 477,  481,  482,  478,  479,  483,  484,  480],\n       [ 481,  485,  486,  482,  483,  487,  488,  484],\n       [ 485,  489,  490,  486,  487,  491,  492,  488],\n       [ 489,  493,  494,  490,  491,  495,  496,  492],\n       [ 362,  474,  497,  375,  364,  476,  498,  376],\n       [ 474,  478,  499,  497,  476,  480,  500,  498],\n       [ 478,  482,  501,  499,  480,  484,  502,  500],\n       [ 482,  486,  503,  501,  484,  488,  504,  502],\n       [ 486,  490,  505,  503,  488,  492,  506,  504],\n       [ 490,  494,  507,  505,  492,  496,  508,  506],\n       [ 375,  497,  509,  387,  376,  498,  510,  388],\n       [ 497,  499,  511,  509,  498,  500,  512,  510],\n       [ 499,  501,  513,  511,  500,  502,  514,  512],\n       [ 501,  503,  515,  513,  502,  504,  516,  514],\n       [ 503,  505,  517,  515,  504,  506,  518,  516],\n       [ 505,  507,  519,  517,  506,  508,  520,  518],\n       [ 387,  509,  521,  399,  388,  510,  522,  400],\n       [ 509,  511,  523,  521,  510,  512,  524,  522],\n       [ 511,  513,  525,  523,  512,  514,  526,  524],\n       [ 513,  515,  527,  525,  514,  516,  528,  526],\n       [ 515,  517,  529,  527,  516,  518,  530,  528],\n       [ 517,  519,  531,  529,  518,  520,  532,  530],\n       [ 399,  521,  533,  411,  400,  522,  534,  412],\n       [ 521,  523,  535,  533,  522,  524,  536,  534],\n       [ 523,  525,  537,  535,  524,  526,  538,  536],\n       [ 525,  527,  539,  537,  526,  528,  540,  538],\n       [ 527,  529,  541,  539,  528,  530,  542,  540],\n       [ 529,  531,  543,  541,  530,  532,  544,  542],\n       [ 411,  533,  545,  423,  412,  534,  546,  424],\n       [ 533,  535,  547,  545,  534,  536,  548,  546],\n       [ 535,  537,  549,  547,  536,  538,  550,  548],\n       [ 537,  539,  551,  549,  538,  540,  552,  550],\n       [ 539,  541,  553,  551,  540,  542,  554,  552],\n       [ 541,  543,  555,  553,  542,  544,  556,  554],\n       [ 549,  551,  557,  558,  550,  552,  559,  560],\n       [ 551,  553,  561,  557,  552,  554,  562,  559],\n       [ 553,  555,  563,  561,  554,  556,  564,  562],\n       [ 558,  557,  565,  566,  560,  559,  567,  568],\n       [ 557,  561,  569,  565,  559,  562,  570,  567],\n       [ 561,  563,  571,  569,  562,  564,  572,  570],\n       [ 566,  565,  573,  574,  568,  567,  575,  576],\n       [ 565,  569,  577,  573,  567,  570,  578,  575],\n       [ 569,  571,  579,  577,  570,  572,  580,  578],\n       [ 579,  581,  582,  577,  580,  583,  584,  578],\n       [ 581,  585,  586,  582,  583,  587,  588,  584],\n       [ 585,  149,  453,  586,  587,  151,  455,  588],\n       [ 577,  582,  589,  573,  578,  584,  590,  575],\n       [ 582,  586,  591,  589,  584,  588,  592,  590],\n       [ 586,  453,  449,  591,  588,  455,  451,  592],\n       [ 573,  589,  593,  574,  575,  590,  594,  576],\n       [ 589,  591,  595,  593,  590,  592,  596,  594],\n       [ 591,  449,  447,  595,  592,  451,  448,  596],\n       [ 447,  439,  597,  595,  448,  440,  598,  596],\n       [ 439,  431,  599,  597,  440,  432,  600,  598],\n       [ 431,  423,  545,  599,  432,  424,  546,  600],\n       [ 595,  597,  601,  593,  596,  598,  602,  594],\n       [ 597,  599,  603,  601,  598,  600,  604,  602],\n       [ 599,  545,  547,  603,  600,  546,  548,  604],\n       [ 593,  601,  566,  574,  594,  602,  568,  576],\n       [ 601,  603,  558,  566,  602,  604,  560,  568],\n       [ 603,  547,  549,  558,  604,  548,  550,  560],\n       [ 493,  605,  606,  494,  495,  607,  608,  496],\n       [ 605,  609,  610,  606,  607,  611,  612,  608],\n       [ 609,  613,  614,  610,  611,  615,  616,  612],\n       [ 613,  617,  618,  614,  615,  619,  620,  616],\n       [ 617,  621,  622,  618,  619,  623,  624,  620],\n       [ 621,  625,  626,  622,  623,  627,  628,  624],\n       [ 494,  606,  629,  507,  496,  608,  630,  508],\n       [ 606,  610,  631,  629,  608,  612,  632,  630],\n       [ 610,  614,  633,  631,  612,  616,  634,  632],\n       [ 614,  618,  635,  633,  616,  620,  636,  634],\n       [ 618,  622,  637,  635,  620,  624,  638,  636],\n       [ 622,  626,  639,  637,  624,  628,  640,  638],\n       [ 507,  629,  641,  519,  508,  630,  642,  520],\n       [ 629,  631,  643,  641,  630,  632,  644,  642],\n       [ 631,  633,  645,  643,  632,  634,  646,  644],\n       [ 633,  635,  647,  645,  634,  636,  648,  646],\n       [ 635,  637,  649,  647,  636,  638,  650,  648],\n       [ 637,  639,  651,  649,  638,  640,  652,  650],\n       [ 519,  641,  653,  531,  520,  642,  654,  532],\n       [ 641,  643,  655,  653,  642,  644,  656,  654],\n       [ 643,  645,  657,  655,  644,  646,  658,  656],\n       [ 645,  647,  659,  657,  646,  648,  660,  658],\n       [ 647,  649,  661,  659,  648,  650,  662,  660],\n       [ 649,  651,  663,  661,  650,  652,  664,  662],\n       [ 531,  653,  665,  543,  532,  654,  666,  544],\n       [ 653,  655,  667,  665,  654,  656,  668,  666],\n       [ 655,  657,  669,  667,  656,  658,  670,  668],\n       [ 657,  659,  671,  669,  658,  660,  672,  670],\n       [ 659,  661,  673,  671,  660,  662,  674,  672],\n       [ 661,  663,  675,  673,  662,  664,  676,  674],\n       [ 543,  665,  677,  555,  544,  666,  678,  556],\n       [ 665,  667,  679,  677,  666,  668,  680,  678],\n       [ 667,  669,  681,  679,  668,  670,  682,  680],\n       [ 669,  671,  683,  681,  670,  672,  684,  682],\n       [ 671,  673,  685,  683,  672,  674,  686,  684],\n       [ 673,  675,  687,  685,  674,  676,  688,  686],\n       [ 681,  683,  689,  690,  682,  684,  691,  692],\n       [ 683,  685,  693,  689,  684,  686,  694,  691],\n       [ 685,  687,  695,  693,  686,  688,  696,  694],\n       [ 690,  689,  697,  698,  692,  691,  699,  700],\n       [ 689,  693,  701,  697,  691,  694,  702,  699],\n       [ 693,  695,  703,  701,  694,  696,  704,  702],\n       [ 698,  697,  705,  706,  700,  699,  707,  708],\n       [ 697,  701,  709,  705,  699,  702,  710,  707],\n       [ 701,  703,  711,  709,  702,  704,  712,  710],\n       [ 711,  713,  714,  709,  712,  715,  716,  710],\n       [ 713,  717,  718,  714,  715,  719,  720,  716],\n       [ 717,  149,  585,  718,  719,  151,  587,  720],\n       [ 709,  714,  721,  705,  710,  716,  722,  707],\n       [ 714,  718,  723,  721,  716,  720,  724,  722],\n       [ 718,  585,  581,  723,  720,  587,  583,  724],\n       [ 705,  721,  725,  706,  707,  722,  726,  708],\n       [ 721,  723,  727,  725,  722,  724,  728,  726],\n       [ 723,  581,  579,  727,  724,  583,  580,  728],\n       [ 579,  571,  729,  727,  580,  572,  730,  728],\n       [ 571,  563,  731,  729,  572,  564,  732,  730],\n       [ 563,  555,  677,  731,  564,  556,  678,  732],\n       [ 727,  729,  733,  725,  728,  730,  734,  726],\n       [ 729,  731,  735,  733,  730,  732,  736,  734],\n       [ 731,  677,  679,  735,  732,  678,  680,  736],\n       [ 725,  733,  698,  706,  726,  734,  700,  708],\n       [ 733,  735,  690,  698,  734,  736,  692,  700],\n       [ 735,  679,  681,  690,  736,  680,  682,  692],\n       [ 625,  737,  738,  626,  627,  739,  740,  628],\n       [ 737,  741,  742,  738,  739,  743,  744,  740],\n       [ 741,  745,  746,  742,  743,  747,  748,  744],\n       [ 745,  749,  750,  746,  747,  751,  752,  748],\n       [ 749,  753,  754,  750,  751,  755,  756,  752],\n       [ 753,    1,    4,  754,  755,    5,    8,  756],\n       [ 626,  738,  757,  639,  628,  740,  758,  640],\n       [ 738,  742,  759,  757,  740,  744,  760,  758],\n       [ 742,  746,  761,  759,  744,  748,  762,  760],\n       [ 746,  750,  763,  761,  748,  752,  764,  762],\n       [ 750,  754,  765,  763,  752,  756,  766,  764],\n       [ 754,    4,   34,  765,  756,    8,   36,  766],\n       [ 639,  757,  767,  651,  640,  758,  768,  652],\n       [ 757,  759,  769,  767,  758,  760,  770,  768],\n       [ 759,  761,  771,  769,  760,  762,  772,  770],\n       [ 761,  763,  773,  771,  762,  764,  774,  772],\n       [ 763,  765,  775,  773,  764,  766,  776,  774],\n       [ 765,   34,   50,  775,  766,   36,   52,  776],\n       [ 651,  767,  777,  663,  652,  768,  778,  664],\n       [ 767,  769,  779,  777,  768,  770,  780,  778],\n       [ 769,  771,  781,  779,  770,  772,  782,  780],\n       [ 771,  773,  783,  781,  772,  774,  784,  782],\n       [ 773,  775,  785,  783,  774,  776,  786,  784],\n       [ 775,   50,   66,  785,  776,   52,   68,  786],\n       [ 663,  777,  787,  675,  664,  778,  788,  676],\n       [ 777,  779,  789,  787,  778,  780,  790,  788],\n       [ 779,  781,  791,  789,  780,  782,  792,  790],\n       [ 781,  783,  793,  791,  782,  784,  794,  792],\n       [ 783,  785,  795,  793,  784,  786,  796,  794],\n       [ 785,   66,   82,  795,  786,   68,   84,  796],\n       [ 675,  787,  797,  687,  676,  788,  798,  688],\n       [ 787,  789,  799,  797,  788,  790,  800,  798],\n       [ 789,  791,  801,  799,  790,  792,  802,  800],\n       [ 791,  793,  803,  801,  792,  794,  804,  802],\n       [ 793,  795,  805,  803,  794,  796,  806,  804],\n       [ 795,   82,   98,  805,  796,   84,  100,  806],\n       [ 801,  803,  807,  808,  802,  804,  809,  810],\n       [ 803,  805,  811,  807,  804,  806,  812,  809],\n       [ 805,   98,  173,  811,  806,  100,  175,  812],\n       [ 808,  807,  813,  814,  810,  809,  815,  816],\n       [ 807,  811,  817,  813,  809,  812,  818,  815],\n       [ 811,  173,  169,  817,  812,  175,  171,  818],\n       [ 814,  813,  819,  820,  816,  815,  821,  822],\n       [ 813,  817,  823,  819,  815,  818,  824,  821],\n       [ 817,  169,  167,  823,  818,  171,  168,  824],\n       [ 167,  159,  825,  823,  168,  160,  826,  824],\n       [ 159,  150,  827,  825,  160,  152,  828,  826],\n       [ 150,  149,  717,  827,  152,  151,  719,  828],\n       [ 823,  825,  829,  819,  824,  826,  830,  821],\n       [ 825,  827,  831,  829,  826,  828,  832,  830],\n       [ 827,  717,  713,  831,  828,  719,  715,  832],\n       [ 819,  829,  833,  820,  821,  830,  834,  822],\n       [ 829,  831,  835,  833,  830,  832,  836,  834],\n       [ 831,  713,  711,  835,  832,  715,  712,  836],\n       [ 711,  703,  837,  835,  712,  704,  838,  836],\n       [ 703,  695,  839,  837,  704,  696,  840,  838],\n       [ 695,  687,  797,  839,  696,  688,  798,  840],\n       [ 835,  837,  841,  833,  836,  838,  842,  834],\n       [ 837,  839,  843,  841,  838,  840,  844,  842],\n       [ 839,  797,  799,  843,  840,  798,  800,  844],\n       [ 833,  841,  814,  820,  834,  842,  816,  822],\n       [ 841,  843,  808,  814,  842,  844,  810,  816],\n       [ 843,  799,  801,  808,  844,  800,  802,  810],\n       [ 845,  846,  847,  848,    1,    2,    3,    4],\n       [ 846,  849,  850,  847,    2,    9,   10,    3],\n       [ 849,  851,  852,  850,    9,   13,   14,   10],\n       [ 851,  853,  854,  852,   13,   17,   18,   14],\n       [ 853,  855,  856,  854,   17,   21,   22,   18],\n       [ 855,  857,  858,  856,   21,   25,   26,   22],\n       [ 857,  859,  860,  858,   25,   29,   30,   26],\n       [ 848,  847,  861,  862,    4,    3,   33,   34],\n       [ 847,  850,  863,  861,    3,   10,   37,   33],\n       [ 850,  852,  864,  863,   10,   14,   39,   37],\n       [ 852,  854,  865,  864,   14,   18,   41,   39],\n       [ 854,  856,  866,  865,   18,   22,   43,   41],\n       [ 856,  858,  867,  866,   22,   26,   45,   43],\n       [ 858,  860,  868,  867,   26,   30,   47,   45],\n       [ 862,  861,  869,  870,   34,   33,   49,   50],\n       [ 861,  863,  871,  869,   33,   37,   53,   49],\n       [ 863,  864,  872,  871,   37,   39,   55,   53],\n       [ 864,  865,  873,  872,   39,   41,   57,   55],\n       [ 865,  866,  874,  873,   41,   43,   59,   57],\n       [ 866,  867,  875,  874,   43,   45,   61,   59],\n       [ 867,  868,  876,  875,   45,   47,   63,   61],\n       [ 870,  869,  877,  878,   50,   49,   65,   66],\n       [ 869,  871,  879,  877,   49,   53,   69,   65],\n       [ 871,  872,  880,  879,   53,   55,   71,   69],\n       [ 872,  873,  881,  880,   55,   57,   73,   71],\n       [ 873,  874,  882,  881,   57,   59,   75,   73],\n       [ 874,  875,  883,  882,   59,   61,   77,   75],\n       [ 875,  876,  884,  883,   61,   63,   79,   77],\n       [ 878,  877,  885,  886,   66,   65,   81,   82],\n       [ 877,  879,  887,  885,   65,   69,   85,   81],\n       [ 879,  880,  888,  887,   69,   71,   87,   85],\n       [ 880,  881,  889,  888,   71,   73,   89,   87],\n       [ 881,  882,  890,  889,   73,   75,   91,   89],\n       [ 882,  883,  891,  890,   75,   77,   93,   91],\n       [ 883,  884,  892,  891,   77,   79,   95,   93],\n       [ 886,  885,  893,  894,   82,   81,   97,   98],\n       [ 885,  887,  895,  893,   81,   85,  101,   97],\n       [ 887,  888,  896,  895,   85,   87,  103,  101],\n       [ 888,  889,  897,  896,   87,   89,  105,  103],\n       [ 889,  890,  898,  897,   89,   91,  107,  105],\n       [ 890,  891,  899,  898,   91,   93,  109,  107],\n       [ 891,  892,  900,  899,   93,   95,  111,  109],\n       [ 897,  898,  901,  902,  105,  107,  113,  114],\n       [ 898,  899,  903,  901,  107,  109,  117,  113],\n       [ 899,  900,  904,  903,  109,  111,  119,  117],\n       [ 902,  901,  905,  906,  114,  113,  121,  122],\n       [ 901,  903,  907,  905,  113,  117,  125,  121],\n       [ 903,  904,  908,  907,  117,  119,  127,  125],\n       [ 906,  905,  909,  910,  122,  121,  129,  130],\n       [ 905,  907,  911,  909,  121,  125,  133,  129],\n       [ 907,  908,  912,  911,  125,  127,  135,  133],\n       [ 912,  913,  914,  911,  135,  137,  138,  133],\n       [ 913,  915,  916,  914,  137,  141,  142,  138],\n       [ 915,  917,  918,  916,  141,  145,  146,  142],\n       [ 917,  919,  920,  918,  145,  149,  150,  146],\n       [ 911,  914,  921,  909,  133,  138,  153,  129],\n       [ 914,  916,  922,  921,  138,  142,  155,  153],\n       [ 916,  918,  923,  922,  142,  146,  157,  155],\n       [ 918,  920,  924,  923,  146,  150,  159,  157],\n       [ 909,  921,  925,  910,  129,  153,  161,  130],\n       [ 921,  922,  926,  925,  153,  155,  163,  161],\n       [ 922,  923,  927,  926,  155,  157,  165,  163],\n       [ 923,  924,  928,  927,  157,  159,  167,  165],\n       [ 928,  929,  930,  927,  167,  169,  170,  165],\n       [ 929,  931,  932,  930,  169,  173,  174,  170],\n       [ 931,  894,  893,  932,  173,   98,   97,  174],\n       [ 927,  930,  933,  926,  165,  170,  177,  163],\n       [ 930,  932,  934,  933,  170,  174,  179,  177],\n       [ 932,  893,  895,  934,  174,   97,  101,  179],\n       [ 926,  933,  935,  925,  163,  177,  181,  161],\n       [ 933,  934,  936,  935,  177,  179,  183,  181],\n       [ 934,  895,  896,  936,  179,  101,  103,  183],\n       [ 925,  935,  906,  910,  161,  181,  122,  130],\n       [ 935,  936,  902,  906,  181,  183,  114,  122],\n       [ 936,  896,  897,  902,  183,  103,  105,  114],\n       [ 859,  937,  938,  860,   29,  185,  186,   30],\n       [ 937,  939,  940,  938,  185,  189,  190,  186],\n       [ 939,  941,  942,  940,  189,  193,  194,  190],\n       [ 941,  943,  944,  942,  193,  197,  198,  194],\n       [ 943,  945,  946,  944,  197,  201,  202,  198],\n       [ 945,  947,  948,  946,  201,  205,  206,  202],\n       [ 947,  949,  950,  948,  205,  209,  210,  206],\n       [ 860,  938,  951,  868,   30,  186,  213,   47],\n       [ 938,  940,  952,  951,  186,  190,  215,  213],\n       [ 940,  942,  953,  952,  190,  194,  217,  215],\n       [ 942,  944,  954,  953,  194,  198,  219,  217],\n       [ 944,  946,  955,  954,  198,  202,  221,  219],\n       [ 946,  948,  956,  955,  202,  206,  223,  221],\n       [ 948,  950,  957,  956,  206,  210,  225,  223],\n       [ 868,  951,  958,  876,   47,  213,  227,   63],\n       [ 951,  952,  959,  958,  213,  215,  229,  227],\n       [ 952,  953,  960,  959,  215,  217,  231,  229],\n       [ 953,  954,  961,  960,  217,  219,  233,  231],\n       [ 954,  955,  962,  961,  219,  221,  235,  233],\n       [ 955,  956,  963,  962,  221,  223,  237,  235],\n       [ 956,  957,  964,  963,  223,  225,  239,  237],\n       [ 876,  958,  965,  884,   63,  227,  241,   79],\n       [ 958,  959,  966,  965,  227,  229,  243,  241],\n       [ 959,  960,  967,  966,  229,  231,  245,  243],\n       [ 960,  961,  968,  967,  231,  233,  247,  245],\n       [ 961,  962,  969,  968,  233,  235,  249,  247],\n       [ 962,  963,  970,  969,  235,  237,  251,  249],\n       [ 963,  964,  971,  970,  237,  239,  253,  251],\n       [ 884,  965,  972,  892,   79,  241,  255,   95],\n       [ 965,  966,  973,  972,  241,  243,  257,  255],\n       [ 966,  967,  974,  973,  243,  245,  259,  257],\n       [ 967,  968,  975,  974,  245,  247,  261,  259],\n       [ 968,  969,  976,  975,  247,  249,  263,  261],\n       [ 969,  970,  977,  976,  249,  251,  265,  263],\n       [ 970,  971,  978,  977,  251,  253,  267,  265],\n       [ 892,  972,  979,  900,   95,  255,  269,  111],\n       [ 972,  973,  980,  979,  255,  257,  271,  269],\n       [ 973,  974,  981,  980,  257,  259,  273,  271],\n       [ 974,  975,  982,  981,  259,  261,  275,  273],\n       [ 975,  976,  983,  982,  261,  263,  277,  275],\n       [ 976,  977,  984,  983,  263,  265,  279,  277],\n       [ 977,  978,  985,  984,  265,  267,  281,  279],\n       [ 981,  982,  986,  987,  273,  275,  283,  284],\n       [ 982,  983,  988,  986,  275,  277,  287,  283],\n       [ 983,  984,  989,  988,  277,  279,  289,  287],\n       [ 984,  985,  990,  989,  279,  281,  291,  289],\n       [ 987,  986,  991,  992,  284,  283,  293,  294],\n       [ 986,  988,  993,  991,  283,  287,  297,  293],\n       [ 988,  989,  994,  993,  287,  289,  299,  297],\n       [ 989,  990,  995,  994,  289,  291,  301,  299],\n       [ 992,  991,  996,  997,  294,  293,  303,  304],\n       [ 991,  993,  998,  996,  293,  297,  307,  303],\n       [ 993,  994,  999,  998,  297,  299,  309,  307],\n       [ 994,  995, 1000,  999,  299,  301,  311,  309],\n       [1000, 1001, 1002,  999,  311,  313,  314,  309],\n       [1001, 1003, 1004, 1002,  313,  317,  318,  314],\n       [1003,  919,  917, 1004,  317,  149,  145,  318],\n       [ 999, 1002, 1005,  998,  309,  314,  321,  307],\n       [1002, 1004, 1006, 1005,  314,  318,  323,  321],\n       [1004,  917,  915, 1006,  318,  145,  141,  323],\n       [ 998, 1005, 1007,  996,  307,  321,  325,  303],\n       [1005, 1006, 1008, 1007,  321,  323,  327,  325],\n       [1006,  915,  913, 1008,  323,  141,  137,  327],\n       [ 996, 1007, 1009,  997,  303,  325,  329,  304],\n       [1007, 1008, 1010, 1009,  325,  327,  331,  329],\n       [1008,  913,  912, 1010,  327,  137,  135,  331],\n       [ 912,  908, 1011, 1010,  135,  127,  333,  331],\n       [ 908,  904, 1012, 1011,  127,  119,  335,  333],\n       [ 904,  900,  979, 1012,  119,  111,  269,  335],\n       [1010, 1011, 1013, 1009,  331,  333,  337,  329],\n       [1011, 1012, 1014, 1013,  333,  335,  339,  337],\n       [1012,  979,  980, 1014,  335,  269,  271,  339],\n       [1009, 1013,  992,  997,  329,  337,  294,  304],\n       [1013, 1014,  987,  992,  337,  339,  284,  294],\n       [1014,  980,  981,  987,  339,  271,  273,  284],\n       [ 949, 1015, 1016,  950,  209,  341,  342,  210],\n       [1015, 1017, 1018, 1016,  341,  345,  346,  342],\n       [1017, 1019, 1020, 1018,  345,  349,  350,  346],\n       [1019, 1021, 1022, 1020,  349,  353,  354,  350],\n       [1021, 1023, 1024, 1022,  353,  357,  358,  354],\n       [1023, 1025, 1026, 1024,  357,  361,  362,  358],\n       [ 950, 1016, 1027,  957,  210,  342,  365,  225],\n       [1016, 1018, 1028, 1027,  342,  346,  367,  365],\n       [1018, 1020, 1029, 1028,  346,  350,  369,  367],\n       [1020, 1022, 1030, 1029,  350,  354,  371,  369],\n       [1022, 1024, 1031, 1030,  354,  358,  373,  371],\n       [1024, 1026, 1032, 1031,  358,  362,  375,  373],\n       [ 957, 1027, 1033,  964,  225,  365,  377,  239],\n       [1027, 1028, 1034, 1033,  365,  367,  379,  377],\n       [1028, 1029, 1035, 1034,  367,  369,  381,  379],\n       [1029, 1030, 1036, 1035,  369,  371,  383,  381],\n       [1030, 1031, 1037, 1036,  371,  373,  385,  383],\n       [1031, 1032, 1038, 1037,  373,  375,  387,  385],\n       [ 964, 1033, 1039,  971,  239,  377,  389,  253],\n       [1033, 1034, 1040, 1039,  377,  379,  391,  389],\n       [1034, 1035, 1041, 1040,  379,  381,  393,  391],\n       [1035, 1036, 1042, 1041,  381,  383,  395,  393],\n       [1036, 1037, 1043, 1042,  383,  385,  397,  395],\n       [1037, 1038, 1044, 1043,  385,  387,  399,  397],\n       [ 971, 1039, 1045,  978,  253,  389,  401,  267],\n       [1039, 1040, 1046, 1045,  389,  391,  403,  401],\n       [1040, 1041, 1047, 1046,  391,  393,  405,  403],\n       [1041, 1042, 1048, 1047,  393,  395,  407,  405],\n       [1042, 1043, 1049, 1048,  395,  397,  409,  407],\n       [1043, 1044, 1050, 1049,  397,  399,  411,  409],\n       [ 978, 1045, 1051,  985,  267,  401,  413,  281],\n       [1045, 1046, 1052, 1051,  401,  403,  415,  413],\n       [1046, 1047, 1053, 1052,  403,  405,  417,  415],\n       [1047, 1048, 1054, 1053,  405,  407,  419,  417],\n       [1048, 1049, 1055, 1054,  407,  409,  421,  419],\n       [1049, 1050, 1056, 1055,  409,  411,  423,  421],\n       [1053, 1054, 1057, 1058,  417,  419,  425,  426],\n       [1054, 1055, 1059, 1057,  419,  421,  429,  425],\n       [1055, 1056, 1060, 1059,  421,  423,  431,  429],\n       [1058, 1057, 1061, 1062,  426,  425,  433,  434],\n       [1057, 1059, 1063, 1061,  425,  429,  437,  433],\n       [1059, 1060, 1064, 1063,  429,  431,  439,  437],\n       [1062, 1061, 1065, 1066,  434,  433,  441,  442],\n       [1061, 1063, 1067, 1065,  433,  437,  445,  441],\n       [1063, 1064, 1068, 1067,  437,  439,  447,  445],\n       [1068, 1069, 1070, 1067,  447,  449,  450,  445],\n       [1069, 1071, 1072, 1070,  449,  453,  454,  450],\n       [1071,  919, 1003, 1072,  453,  149,  317,  454],\n       [1067, 1070, 1073, 1065,  445,  450,  457,  441],\n       [1070, 1072, 1074, 1073,  450,  454,  459,  457],\n       [1072, 1003, 1001, 1074,  454,  317,  313,  459],\n       [1065, 1073, 1075, 1066,  441,  457,  461,  442],\n       [1073, 1074, 1076, 1075,  457,  459,  463,  461],\n       [1074, 1001, 1000, 1076,  459,  313,  311,  463],\n       [1000,  995, 1077, 1076,  311,  301,  465,  463],\n       [ 995,  990, 1078, 1077,  301,  291,  467,  465],\n       [ 990,  985, 1051, 1078,  291,  281,  413,  467],\n       [1076, 1077, 1079, 1075,  463,  465,  469,  461],\n       [1077, 1078, 1080, 1079,  465,  467,  471,  469],\n       [1078, 1051, 1052, 1080,  467,  413,  415,  471],\n       [1075, 1079, 1062, 1066,  461,  469,  434,  442],\n       [1079, 1080, 1058, 1062,  469,  471,  426,  434],\n       [1080, 1052, 1053, 1058,  471,  415,  417,  426],\n       [1025, 1081, 1082, 1026,  361,  473,  474,  362],\n       [1081, 1083, 1084, 1082,  473,  477,  478,  474],\n       [1083, 1085, 1086, 1084,  477,  481,  482,  478],\n       [1085, 1087, 1088, 1086,  481,  485,  486,  482],\n       [1087, 1089, 1090, 1088,  485,  489,  490,  486],\n       [1089, 1091, 1092, 1090,  489,  493,  494,  490],\n       [1026, 1082, 1093, 1032,  362,  474,  497,  375],\n       [1082, 1084, 1094, 1093,  474,  478,  499,  497],\n       [1084, 1086, 1095, 1094,  478,  482,  501,  499],\n       [1086, 1088, 1096, 1095,  482,  486,  503,  501],\n       [1088, 1090, 1097, 1096,  486,  490,  505,  503],\n       [1090, 1092, 1098, 1097,  490,  494,  507,  505],\n       [1032, 1093, 1099, 1038,  375,  497,  509,  387],\n       [1093, 1094, 1100, 1099,  497,  499,  511,  509],\n       [1094, 1095, 1101, 1100,  499,  501,  513,  511],\n       [1095, 1096, 1102, 1101,  501,  503,  515,  513],\n       [1096, 1097, 1103, 1102,  503,  505,  517,  515],\n       [1097, 1098, 1104, 1103,  505,  507,  519,  517],\n       [1038, 1099, 1105, 1044,  387,  509,  521,  399],\n       [1099, 1100, 1106, 1105,  509,  511,  523,  521],\n       [1100, 1101, 1107, 1106,  511,  513,  525,  523],\n       [1101, 1102, 1108, 1107,  513,  515,  527,  525],\n       [1102, 1103, 1109, 1108,  515,  517,  529,  527],\n       [1103, 1104, 1110, 1109,  517,  519,  531,  529],\n       [1044, 1105, 1111, 1050,  399,  521,  533,  411],\n       [1105, 1106, 1112, 1111,  521,  523,  535,  533],\n       [1106, 1107, 1113, 1112,  523,  525,  537,  535],\n       [1107, 1108, 1114, 1113,  525,  527,  539,  537],\n       [1108, 1109, 1115, 1114,  527,  529,  541,  539],\n       [1109, 1110, 1116, 1115,  529,  531,  543,  541],\n       [1050, 1111, 1117, 1056,  411,  533,  545,  423],\n       [1111, 1112, 1118, 1117,  533,  535,  547,  545],\n       [1112, 1113, 1119, 1118,  535,  537,  549,  547],\n       [1113, 1114, 1120, 1119,  537,  539,  551,  549],\n       [1114, 1115, 1121, 1120,  539,  541,  553,  551],\n       [1115, 1116, 1122, 1121,  541,  543,  555,  553],\n       [1119, 1120, 1123, 1124,  549,  551,  557,  558],\n       [1120, 1121, 1125, 1123,  551,  553,  561,  557],\n       [1121, 1122, 1126, 1125,  553,  555,  563,  561],\n       [1124, 1123, 1127, 1128,  558,  557,  565,  566],\n       [1123, 1125, 1129, 1127,  557,  561,  569,  565],\n       [1125, 1126, 1130, 1129,  561,  563,  571,  569],\n       [1128, 1127, 1131, 1132,  566,  565,  573,  574],\n       [1127, 1129, 1133, 1131,  565,  569,  577,  573],\n       [1129, 1130, 1134, 1133,  569,  571,  579,  577],\n       [1134, 1135, 1136, 1133,  579,  581,  582,  577],\n       [1135, 1137, 1138, 1136,  581,  585,  586,  582],\n       [1137,  919, 1071, 1138,  585,  149,  453,  586],\n       [1133, 1136, 1139, 1131,  577,  582,  589,  573],\n       [1136, 1138, 1140, 1139,  582,  586,  591,  589],\n       [1138, 1071, 1069, 1140,  586,  453,  449,  591],\n       [1131, 1139, 1141, 1132,  573,  589,  593,  574],\n       [1139, 1140, 1142, 1141,  589,  591,  595,  593],\n       [1140, 1069, 1068, 1142,  591,  449,  447,  595],\n       [1068, 1064, 1143, 1142,  447,  439,  597,  595],\n       [1064, 1060, 1144, 1143,  439,  431,  599,  597],\n       [1060, 1056, 1117, 1144,  431,  423,  545,  599],\n       [1142, 1143, 1145, 1141,  595,  597,  601,  593],\n       [1143, 1144, 1146, 1145,  597,  599,  603,  601],\n       [1144, 1117, 1118, 1146,  599,  545,  547,  603],\n       [1141, 1145, 1128, 1132,  593,  601,  566,  574],\n       [1145, 1146, 1124, 1128,  601,  603,  558,  566],\n       [1146, 1118, 1119, 1124,  603,  547,  549,  558],\n       [1091, 1147, 1148, 1092,  493,  605,  606,  494],\n       [1147, 1149, 1150, 1148,  605,  609,  610,  606],\n       [1149, 1151, 1152, 1150,  609,  613,  614,  610],\n       [1151, 1153, 1154, 1152,  613,  617,  618,  614],\n       [1153, 1155, 1156, 1154,  617,  621,  622,  618],\n       [1155, 1157, 1158, 1156,  621,  625,  626,  622],\n       [1092, 1148, 1159, 1098,  494,  606,  629,  507],\n       [1148, 1150, 1160, 1159,  606,  610,  631,  629],\n       [1150, 1152, 1161, 1160,  610,  614,  633,  631],\n       [1152, 1154, 1162, 1161,  614,  618,  635,  633],\n       [1154, 1156, 1163, 1162,  618,  622,  637,  635],\n       [1156, 1158, 1164, 1163,  622,  626,  639,  637],\n       [1098, 1159, 1165, 1104,  507,  629,  641,  519],\n       [1159, 1160, 1166, 1165,  629,  631,  643,  641],\n       [1160, 1161, 1167, 1166,  631,  633,  645,  643],\n       [1161, 1162, 1168, 1167,  633,  635,  647,  645],\n       [1162, 1163, 1169, 1168,  635,  637,  649,  647],\n       [1163, 1164, 1170, 1169,  637,  639,  651,  649],\n       [1104, 1165, 1171, 1110,  519,  641,  653,  531],\n       [1165, 1166, 1172, 1171,  641,  643,  655,  653],\n       [1166, 1167, 1173, 1172,  643,  645,  657,  655],\n       [1167, 1168, 1174, 1173,  645,  647,  659,  657],\n       [1168, 1169, 1175, 1174,  647,  649,  661,  659],\n       [1169, 1170, 1176, 1175,  649,  651,  663,  661],\n       [1110, 1171, 1177, 1116,  531,  653,  665,  543],\n       [1171, 1172, 1178, 1177,  653,  655,  667,  665],\n       [1172, 1173, 1179, 1178,  655,  657,  669,  667],\n       [1173, 1174, 1180, 1179,  657,  659,  671,  669],\n       [1174, 1175, 1181, 1180,  659,  661,  673,  671],\n       [1175, 1176, 1182, 1181,  661,  663,  675,  673],\n       [1116, 1177, 1183, 1122,  543,  665,  677,  555],\n       [1177, 1178, 1184, 1183,  665,  667,  679,  677],\n       [1178, 1179, 1185, 1184,  667,  669,  681,  679],\n       [1179, 1180, 1186, 1185,  669,  671,  683,  681],\n       [1180, 1181, 1187, 1186,  671,  673,  685,  683],\n       [1181, 1182, 1188, 1187,  673,  675,  687,  685],\n       [1185, 1186, 1189, 1190,  681,  683,  689,  690],\n       [1186, 1187, 1191, 1189,  683,  685,  693,  689],\n       [1187, 1188, 1192, 1191,  685,  687,  695,  693],\n       [1190, 1189, 1193, 1194,  690,  689,  697,  698],\n       [1189, 1191, 1195, 1193,  689,  693,  701,  697],\n       [1191, 1192, 1196, 1195,  693,  695,  703,  701],\n       [1194, 1193, 1197, 1198,  698,  697,  705,  706],\n       [1193, 1195, 1199, 1197,  697,  701,  709,  705],\n       [1195, 1196, 1200, 1199,  701,  703,  711,  709],\n       [1200, 1201, 1202, 1199,  711,  713,  714,  709],\n       [1201, 1203, 1204, 1202,  713,  717,  718,  714],\n       [1203,  919, 1137, 1204,  717,  149,  585,  718],\n       [1199, 1202, 1205, 1197,  709,  714,  721,  705],\n       [1202, 1204, 1206, 1205,  714,  718,  723,  721],\n       [1204, 1137, 1135, 1206,  718,  585,  581,  723],\n       [1197, 1205, 1207, 1198,  705,  721,  725,  706],\n       [1205, 1206, 1208, 1207,  721,  723,  727,  725],\n       [1206, 1135, 1134, 1208,  723,  581,  579,  727],\n       [1134, 1130, 1209, 1208,  579,  571,  729,  727],\n       [1130, 1126, 1210, 1209,  571,  563,  731,  729],\n       [1126, 1122, 1183, 1210,  563,  555,  677,  731],\n       [1208, 1209, 1211, 1207,  727,  729,  733,  725],\n       [1209, 1210, 1212, 1211,  729,  731,  735,  733],\n       [1210, 1183, 1184, 1212,  731,  677,  679,  735],\n       [1207, 1211, 1194, 1198,  725,  733,  698,  706],\n       [1211, 1212, 1190, 1194,  733,  735,  690,  698],\n       [1212, 1184, 1185, 1190,  735,  679,  681,  690],\n       [1157, 1213, 1214, 1158,  625,  737,  738,  626],\n       [1213, 1215, 1216, 1214,  737,  741,  742,  738],\n       [1215, 1217, 1218, 1216,  741,  745,  746,  742],\n       [1217, 1219, 1220, 1218,  745,  749,  750,  746],\n       [1219, 1221, 1222, 1220,  749,  753,  754,  750],\n       [1221,  845,  848, 1222,  753,    1,    4,  754],\n       [1158, 1214, 1223, 1164,  626,  738,  757,  639],\n       [1214, 1216, 1224, 1223,  738,  742,  759,  757],\n       [1216, 1218, 1225, 1224,  742,  746,  761,  759],\n       [1218, 1220, 1226, 1225,  746,  750,  763,  761],\n       [1220, 1222, 1227, 1226,  750,  754,  765,  763],\n       [1222,  848,  862, 1227,  754,    4,   34,  765],\n       [1164, 1223, 1228, 1170,  639,  757,  767,  651],\n       [1223, 1224, 1229, 1228,  757,  759,  769,  767],\n       [1224, 1225, 1230, 1229,  759,  761,  771,  769],\n       [1225, 1226, 1231, 1230,  761,  763,  773,  771],\n       [1226, 1227, 1232, 1231,  763,  765,  775,  773],\n       [1227,  862,  870, 1232,  765,   34,   50,  775],\n       [1170, 1228, 1233, 1176,  651,  767,  777,  663],\n       [1228, 1229, 1234, 1233,  767,  769,  779,  777],\n       [1229, 1230, 1235, 1234,  769,  771,  781,  779],\n       [1230, 1231, 1236, 1235,  771,  773,  783,  781],\n       [1231, 1232, 1237, 1236,  773,  775,  785,  783],\n       [1232,  870,  878, 1237,  775,   50,   66,  785],\n       [1176, 1233, 1238, 1182,  663,  777,  787,  675],\n       [1233, 1234, 1239, 1238,  777,  779,  789,  787],\n       [1234, 1235, 1240, 1239,  779,  781,  791,  789],\n       [1235, 1236, 1241, 1240,  781,  783,  793,  791],\n       [1236, 1237, 1242, 1241,  783,  785,  795,  793],\n       [1237,  878,  886, 1242,  785,   66,   82,  795],\n       [1182, 1238, 1243, 1188,  675,  787,  797,  687],\n       [1238, 1239, 1244, 1243,  787,  789,  799,  797],\n       [1239, 1240, 1245, 1244,  789,  791,  801,  799],\n       [1240, 1241, 1246, 1245,  791,  793,  803,  801],\n       [1241, 1242, 1247, 1246,  793,  795,  805,  803],\n       [1242,  886,  894, 1247,  795,   82,   98,  805],\n       [1245, 1246, 1248, 1249,  801,  803,  807,  808],\n       [1246, 1247, 1250, 1248,  803,  805,  811,  807],\n       [1247,  894,  931, 1250,  805,   98,  173,  811],\n       [1249, 1248, 1251, 1252,  808,  807,  813,  814],\n       [1248, 1250, 1253, 1251,  807,  811,  817,  813],\n       [1250,  931,  929, 1253,  811,  173,  169,  817],\n       [1252, 1251, 1254, 1255,  814,  813,  819,  820],\n       [1251, 1253, 1256, 1254,  813,  817,  823,  819],\n       [1253,  929,  928, 1256,  817,  169,  167,  823],\n       [ 928,  924, 1257, 1256,  167,  159,  825,  823],\n       [ 924,  920, 1258, 1257,  159,  150,  827,  825],\n       [ 920,  919, 1203, 1258,  150,  149,  717,  827],\n       [1256, 1257, 1259, 1254,  823,  825,  829,  819],\n       [1257, 1258, 1260, 1259,  825,  827,  831,  829],\n       [1258, 1203, 1201, 1260,  827,  717,  713,  831],\n       [1254, 1259, 1261, 1255,  819,  829,  833,  820],\n       [1259, 1260, 1262, 1261,  829,  831,  835,  833],\n       [1260, 1201, 1200, 1262,  831,  713,  711,  835],\n       [1200, 1196, 1263, 1262,  711,  703,  837,  835],\n       [1196, 1192, 1264, 1263,  703,  695,  839,  837],\n       [1192, 1188, 1243, 1264,  695,  687,  797,  839],\n       [1262, 1263, 1265, 1261,  835,  837,  841,  833],\n       [1263, 1264, 1266, 1265,  837,  839,  843,  841],\n       [1264, 1243, 1244, 1266,  839,  797,  799,  843],\n       [1261, 1265, 1252, 1255,  833,  841,  814,  820],\n       [1265, 1266, 1249, 1252,  841,  843,  808,  814],\n       [1266, 1244, 1245, 1249,  843,  799,  801,  808],\n       [1267, 1268, 1269, 1270,  845,  846,  847,  848],\n       [1268, 1271, 1272, 1269,  846,  849,  850,  847],\n       [1271, 1273, 1274, 1272,  849,  851,  852,  850],\n       [1273, 1275, 1276, 1274,  851,  853,  854,  852],\n       [1275, 1277, 1278, 1276,  853,  855,  856,  854],\n       [1277, 1279, 1280, 1278,  855,  857,  858,  856],\n       [1279, 1281, 1282, 1280,  857,  859,  860,  858],\n       [1270, 1269, 1283, 1284,  848,  847,  861,  862],\n       [1269, 1272, 1285, 1283,  847,  850,  863,  861],\n       [1272, 1274, 1286, 1285,  850,  852,  864,  863],\n       [1274, 1276, 1287, 1286,  852,  854,  865,  864],\n       [1276, 1278, 1288, 1287,  854,  856,  866,  865],\n       [1278, 1280, 1289, 1288,  856,  858,  867,  866],\n       [1280, 1282, 1290, 1289,  858,  860,  868,  867],\n       [1284, 1283, 1291, 1292,  862,  861,  869,  870],\n       [1283, 1285, 1293, 1291,  861,  863,  871,  869],\n       [1285, 1286, 1294, 1293,  863,  864,  872,  871],\n       [1286, 1287, 1295, 1294,  864,  865,  873,  872],\n       [1287, 1288, 1296, 1295,  865,  866,  874,  873],\n       [1288, 1289, 1297, 1296,  866,  867,  875,  874],\n       [1289, 1290, 1298, 1297,  867,  868,  876,  875],\n       [1292, 1291, 1299, 1300,  870,  869,  877,  878],\n       [1291, 1293, 1301, 1299,  869,  871,  879,  877],\n       [1293, 1294, 1302, 1301,  871,  872,  880,  879],\n       [1294, 1295, 1303, 1302,  872,  873,  881,  880],\n       [1295, 1296, 1304, 1303,  873,  874,  882,  881],\n       [1296, 1297, 1305, 1304,  874,  875,  883,  882],\n       [1297, 1298, 1306, 1305,  875,  876,  884,  883],\n       [1300, 1299, 1307, 1308,  878,  877,  885,  886],\n       [1299, 1301, 1309, 1307,  877,  879,  887,  885],\n       [1301, 1302, 1310, 1309,  879,  880,  888,  887],\n       [1302, 1303, 1311, 1310,  880,  881,  889,  888],\n       [1303, 1304, 1312, 1311,  881,  882,  890,  889],\n       [1304, 1305, 1313, 1312,  882,  883,  891,  890],\n       [1305, 1306, 1314, 1313,  883,  884,  892,  891],\n       [1308, 1307, 1315, 1316,  886,  885,  893,  894],\n       [1307, 1309, 1317, 1315,  885,  887,  895,  893],\n       [1309, 1310, 1318, 1317,  887,  888,  896,  895],\n       [1310, 1311, 1319, 1318,  888,  889,  897,  896],\n       [1311, 1312, 1320, 1319,  889,  890,  898,  897],\n       [1312, 1313, 1321, 1320,  890,  891,  899,  898],\n       [1313, 1314, 1322, 1321,  891,  892,  900,  899],\n       [1319, 1320, 1323, 1324,  897,  898,  901,  902],\n       [1320, 1321, 1325, 1323,  898,  899,  903,  901],\n       [1321, 1322, 1326, 1325,  899,  900,  904,  903],\n       [1324, 1323, 1327, 1328,  902,  901,  905,  906],\n       [1323, 1325, 1329, 1327,  901,  903,  907,  905],\n       [1325, 1326, 1330, 1329,  903,  904,  908,  907],\n       [1328, 1327, 1331, 1332,  906,  905,  909,  910],\n       [1327, 1329, 1333, 1331,  905,  907,  911,  909],\n       [1329, 1330, 1334, 1333,  907,  908,  912,  911],\n       [1334, 1335, 1336, 1333,  912,  913,  914,  911],\n       [1335, 1337, 1338, 1336,  913,  915,  916,  914],\n       [1337, 1339, 1340, 1338,  915,  917,  918,  916],\n       [1339, 1341, 1342, 1340,  917,  919,  920,  918],\n       [1333, 1336, 1343, 1331,  911,  914,  921,  909],\n       [1336, 1338, 1344, 1343,  914,  916,  922,  921],\n       [1338, 1340, 1345, 1344,  916,  918,  923,  922],\n       [1340, 1342, 1346, 1345,  918,  920,  924,  923],\n       [1331, 1343, 1347, 1332,  909,  921,  925,  910],\n       [1343, 1344, 1348, 1347,  921,  922,  926,  925],\n       [1344, 1345, 1349, 1348,  922,  923,  927,  926],\n       [1345, 1346, 1350, 1349,  923,  924,  928,  927],\n       [1350, 1351, 1352, 1349,  928,  929,  930,  927],\n       [1351, 1353, 1354, 1352,  929,  931,  932,  930],\n       [1353, 1316, 1315, 1354,  931,  894,  893,  932],\n       [1349, 1352, 1355, 1348,  927,  930,  933,  926],\n       [1352, 1354, 1356, 1355,  930,  932,  934,  933],\n       [1354, 1315, 1317, 1356,  932,  893,  895,  934],\n       [1348, 1355, 1357, 1347,  926,  933,  935,  925],\n       [1355, 1356, 1358, 1357,  933,  934,  936,  935],\n       [1356, 1317, 1318, 1358,  934,  895,  896,  936],\n       [1347, 1357, 1328, 1332,  925,  935,  906,  910],\n       [1357, 1358, 1324, 1328,  935,  936,  902,  906],\n       [1358, 1318, 1319, 1324,  936,  896,  897,  902],\n       [1281, 1359, 1360, 1282,  859,  937,  938,  860],\n       [1359, 1361, 1362, 1360,  937,  939,  940,  938],\n       [1361, 1363, 1364, 1362,  939,  941,  942,  940],\n       [1363, 1365, 1366, 1364,  941,  943,  944,  942],\n       [1365, 1367, 1368, 1366,  943,  945,  946,  944],\n       [1367, 1369, 1370, 1368,  945,  947,  948,  946],\n       [1369, 1371, 1372, 1370,  947,  949,  950,  948],\n       [1282, 1360, 1373, 1290,  860,  938,  951,  868],\n       [1360, 1362, 1374, 1373,  938,  940,  952,  951],\n       [1362, 1364, 1375, 1374,  940,  942,  953,  952],\n       [1364, 1366, 1376, 1375,  942,  944,  954,  953],\n       [1366, 1368, 1377, 1376,  944,  946,  955,  954],\n       [1368, 1370, 1378, 1377,  946,  948,  956,  955],\n       [1370, 1372, 1379, 1378,  948,  950,  957,  956],\n       [1290, 1373, 1380, 1298,  868,  951,  958,  876],\n       [1373, 1374, 1381, 1380,  951,  952,  959,  958],\n       [1374, 1375, 1382, 1381,  952,  953,  960,  959],\n       [1375, 1376, 1383, 1382,  953,  954,  961,  960],\n       [1376, 1377, 1384, 1383,  954,  955,  962,  961],\n       [1377, 1378, 1385, 1384,  955,  956,  963,  962],\n       [1378, 1379, 1386, 1385,  956,  957,  964,  963],\n       [1298, 1380, 1387, 1306,  876,  958,  965,  884],\n       [1380, 1381, 1388, 1387,  958,  959,  966,  965],\n       [1381, 1382, 1389, 1388,  959,  960,  967,  966],\n       [1382, 1383, 1390, 1389,  960,  961,  968,  967],\n       [1383, 1384, 1391, 1390,  961,  962,  969,  968],\n       [1384, 1385, 1392, 1391,  962,  963,  970,  969],\n       [1385, 1386, 1393, 1392,  963,  964,  971,  970],\n       [1306, 1387, 1394, 1314,  884,  965,  972,  892],\n       [1387, 1388, 1395, 1394,  965,  966,  973,  972],\n       [1388, 1389, 1396, 1395,  966,  967,  974,  973],\n       [1389, 1390, 1397, 1396,  967,  968,  975,  974],\n       [1390, 1391, 1398, 1397,  968,  969,  976,  975],\n       [1391, 1392, 1399, 1398,  969,  970,  977,  976],\n       [1392, 1393, 1400, 1399,  970,  971,  978,  977],\n       [1314, 1394, 1401, 1322,  892,  972,  979,  900],\n       [1394, 1395, 1402, 1401,  972,  973,  980,  979],\n       [1395, 1396, 1403, 1402,  973,  974,  981,  980],\n       [1396, 1397, 1404, 1403,  974,  975,  982,  981],\n       [1397, 1398, 1405, 1404,  975,  976,  983,  982],\n       [1398, 1399, 1406, 1405,  976,  977,  984,  983],\n       [1399, 1400, 1407, 1406,  977,  978,  985,  984],\n       [1403, 1404, 1408, 1409,  981,  982,  986,  987],\n       [1404, 1405, 1410, 1408,  982,  983,  988,  986],\n       [1405, 1406, 1411, 1410,  983,  984,  989,  988],\n       [1406, 1407, 1412, 1411,  984,  985,  990,  989],\n       [1409, 1408, 1413, 1414,  987,  986,  991,  992],\n       [1408, 1410, 1415, 1413,  986,  988,  993,  991],\n       [1410, 1411, 1416, 1415,  988,  989,  994,  993],\n       [1411, 1412, 1417, 1416,  989,  990,  995,  994],\n       [1414, 1413, 1418, 1419,  992,  991,  996,  997],\n       [1413, 1415, 1420, 1418,  991,  993,  998,  996],\n       [1415, 1416, 1421, 1420,  993,  994,  999,  998],\n       [1416, 1417, 1422, 1421,  994,  995, 1000,  999],\n       [1422, 1423, 1424, 1421, 1000, 1001, 1002,  999],\n       [1423, 1425, 1426, 1424, 1001, 1003, 1004, 1002],\n       [1425, 1341, 1339, 1426, 1003,  919,  917, 1004],\n       [1421, 1424, 1427, 1420,  999, 1002, 1005,  998],\n       [1424, 1426, 1428, 1427, 1002, 1004, 1006, 1005],\n       [1426, 1339, 1337, 1428, 1004,  917,  915, 1006],\n       [1420, 1427, 1429, 1418,  998, 1005, 1007,  996],\n       [1427, 1428, 1430, 1429, 1005, 1006, 1008, 1007],\n       [1428, 1337, 1335, 1430, 1006,  915,  913, 1008],\n       [1418, 1429, 1431, 1419,  996, 1007, 1009,  997],\n       [1429, 1430, 1432, 1431, 1007, 1008, 1010, 1009],\n       [1430, 1335, 1334, 1432, 1008,  913,  912, 1010],\n       [1334, 1330, 1433, 1432,  912,  908, 1011, 1010],\n       [1330, 1326, 1434, 1433,  908,  904, 1012, 1011],\n       [1326, 1322, 1401, 1434,  904,  900,  979, 1012],\n       [1432, 1433, 1435, 1431, 1010, 1011, 1013, 1009],\n       [1433, 1434, 1436, 1435, 1011, 1012, 1014, 1013],\n       [1434, 1401, 1402, 1436, 1012,  979,  980, 1014],\n       [1431, 1435, 1414, 1419, 1009, 1013,  992,  997],\n       [1435, 1436, 1409, 1414, 1013, 1014,  987,  992],\n       [1436, 1402, 1403, 1409, 1014,  980,  981,  987],\n       [1371, 1437, 1438, 1372,  949, 1015, 1016,  950],\n       [1437, 1439, 1440, 1438, 1015, 1017, 1018, 1016],\n       [1439, 1441, 1442, 1440, 1017, 1019, 1020, 1018],\n       [1441, 1443, 1444, 1442, 1019, 1021, 1022, 1020],\n       [1443, 1445, 1446, 1444, 1021, 1023, 1024, 1022],\n       [1445, 1447, 1448, 1446, 1023, 1025, 1026, 1024],\n       [1372, 1438, 1449, 1379,  950, 1016, 1027,  957],\n       [1438, 1440, 1450, 1449, 1016, 1018, 1028, 1027],\n       [1440, 1442, 1451, 1450, 1018, 1020, 1029, 1028],\n       [1442, 1444, 1452, 1451, 1020, 1022, 1030, 1029],\n       [1444, 1446, 1453, 1452, 1022, 1024, 1031, 1030],\n       [1446, 1448, 1454, 1453, 1024, 1026, 1032, 1031],\n       [1379, 1449, 1455, 1386,  957, 1027, 1033,  964],\n       [1449, 1450, 1456, 1455, 1027, 1028, 1034, 1033],\n       [1450, 1451, 1457, 1456, 1028, 1029, 1035, 1034],\n       [1451, 1452, 1458, 1457, 1029, 1030, 1036, 1035],\n       [1452, 1453, 1459, 1458, 1030, 1031, 1037, 1036],\n       [1453, 1454, 1460, 1459, 1031, 1032, 1038, 1037],\n       [1386, 1455, 1461, 1393,  964, 1033, 1039,  971],\n       [1455, 1456, 1462, 1461, 1033, 1034, 1040, 1039],\n       [1456, 1457, 1463, 1462, 1034, 1035, 1041, 1040],\n       [1457, 1458, 1464, 1463, 1035, 1036, 1042, 1041],\n       [1458, 1459, 1465, 1464, 1036, 1037, 1043, 1042],\n       [1459, 1460, 1466, 1465, 1037, 1038, 1044, 1043],\n       [1393, 1461, 1467, 1400,  971, 1039, 1045,  978],\n       [1461, 1462, 1468, 1467, 1039, 1040, 1046, 1045],\n       [1462, 1463, 1469, 1468, 1040, 1041, 1047, 1046],\n       [1463, 1464, 1470, 1469, 1041, 1042, 1048, 1047],\n       [1464, 1465, 1471, 1470, 1042, 1043, 1049, 1048],\n       [1465, 1466, 1472, 1471, 1043, 1044, 1050, 1049],\n       [1400, 1467, 1473, 1407,  978, 1045, 1051,  985],\n       [1467, 1468, 1474, 1473, 1045, 1046, 1052, 1051],\n       [1468, 1469, 1475, 1474, 1046, 1047, 1053, 1052],\n       [1469, 1470, 1476, 1475, 1047, 1048, 1054, 1053],\n       [1470, 1471, 1477, 1476, 1048, 1049, 1055, 1054],\n       [1471, 1472, 1478, 1477, 1049, 1050, 1056, 1055],\n       [1475, 1476, 1479, 1480, 1053, 1054, 1057, 1058],\n       [1476, 1477, 1481, 1479, 1054, 1055, 1059, 1057],\n       [1477, 1478, 1482, 1481, 1055, 1056, 1060, 1059],\n       [1480, 1479, 1483, 1484, 1058, 1057, 1061, 1062],\n       [1479, 1481, 1485, 1483, 1057, 1059, 1063, 1061],\n       [1481, 1482, 1486, 1485, 1059, 1060, 1064, 1063],\n       [1484, 1483, 1487, 1488, 1062, 1061, 1065, 1066],\n       [1483, 1485, 1489, 1487, 1061, 1063, 1067, 1065],\n       [1485, 1486, 1490, 1489, 1063, 1064, 1068, 1067],\n       [1490, 1491, 1492, 1489, 1068, 1069, 1070, 1067],\n       [1491, 1493, 1494, 1492, 1069, 1071, 1072, 1070],\n       [1493, 1341, 1425, 1494, 1071,  919, 1003, 1072],\n       [1489, 1492, 1495, 1487, 1067, 1070, 1073, 1065],\n       [1492, 1494, 1496, 1495, 1070, 1072, 1074, 1073],\n       [1494, 1425, 1423, 1496, 1072, 1003, 1001, 1074],\n       [1487, 1495, 1497, 1488, 1065, 1073, 1075, 1066],\n       [1495, 1496, 1498, 1497, 1073, 1074, 1076, 1075],\n       [1496, 1423, 1422, 1498, 1074, 1001, 1000, 1076],\n       [1422, 1417, 1499, 1498, 1000,  995, 1077, 1076],\n       [1417, 1412, 1500, 1499,  995,  990, 1078, 1077],\n       [1412, 1407, 1473, 1500,  990,  985, 1051, 1078],\n       [1498, 1499, 1501, 1497, 1076, 1077, 1079, 1075],\n       [1499, 1500, 1502, 1501, 1077, 1078, 1080, 1079],\n       [1500, 1473, 1474, 1502, 1078, 1051, 1052, 1080],\n       [1497, 1501, 1484, 1488, 1075, 1079, 1062, 1066],\n       [1501, 1502, 1480, 1484, 1079, 1080, 1058, 1062],\n       [1502, 1474, 1475, 1480, 1080, 1052, 1053, 1058],\n       [1447, 1503, 1504, 1448, 1025, 1081, 1082, 1026],\n       [1503, 1505, 1506, 1504, 1081, 1083, 1084, 1082],\n       [1505, 1507, 1508, 1506, 1083, 1085, 1086, 1084],\n       [1507, 1509, 1510, 1508, 1085, 1087, 1088, 1086],\n       [1509, 1511, 1512, 1510, 1087, 1089, 1090, 1088],\n       [1511, 1513, 1514, 1512, 1089, 1091, 1092, 1090],\n       [1448, 1504, 1515, 1454, 1026, 1082, 1093, 1032],\n       [1504, 1506, 1516, 1515, 1082, 1084, 1094, 1093],\n       [1506, 1508, 1517, 1516, 1084, 1086, 1095, 1094],\n       [1508, 1510, 1518, 1517, 1086, 1088, 1096, 1095],\n       [1510, 1512, 1519, 1518, 1088, 1090, 1097, 1096],\n       [1512, 1514, 1520, 1519, 1090, 1092, 1098, 1097],\n       [1454, 1515, 1521, 1460, 1032, 1093, 1099, 1038],\n       [1515, 1516, 1522, 1521, 1093, 1094, 1100, 1099],\n       [1516, 1517, 1523, 1522, 1094, 1095, 1101, 1100],\n       [1517, 1518, 1524, 1523, 1095, 1096, 1102, 1101],\n       [1518, 1519, 1525, 1524, 1096, 1097, 1103, 1102],\n       [1519, 1520, 1526, 1525, 1097, 1098, 1104, 1103],\n       [1460, 1521, 1527, 1466, 1038, 1099, 1105, 1044],\n       [1521, 1522, 1528, 1527, 1099, 1100, 1106, 1105],\n       [1522, 1523, 1529, 1528, 1100, 1101, 1107, 1106],\n       [1523, 1524, 1530, 1529, 1101, 1102, 1108, 1107],\n       [1524, 1525, 1531, 1530, 1102, 1103, 1109, 1108],\n       [1525, 1526, 1532, 1531, 1103, 1104, 1110, 1109],\n       [1466, 1527, 1533, 1472, 1044, 1105, 1111, 1050],\n       [1527, 1528, 1534, 1533, 1105, 1106, 1112, 1111],\n       [1528, 1529, 1535, 1534, 1106, 1107, 1113, 1112],\n       [1529, 1530, 1536, 1535, 1107, 1108, 1114, 1113],\n       [1530, 1531, 1537, 1536, 1108, 1109, 1115, 1114],\n       [1531, 1532, 1538, 1537, 1109, 1110, 1116, 1115],\n       [1472, 1533, 1539, 1478, 1050, 1111, 1117, 1056],\n       [1533, 1534, 1540, 1539, 1111, 1112, 1118, 1117],\n       [1534, 1535, 1541, 1540, 1112, 1113, 1119, 1118],\n       [1535, 1536, 1542, 1541, 1113, 1114, 1120, 1119],\n       [1536, 1537, 1543, 1542, 1114, 1115, 1121, 1120],\n       [1537, 1538, 1544, 1543, 1115, 1116, 1122, 1121],\n       [1541, 1542, 1545, 1546, 1119, 1120, 1123, 1124],\n       [1542, 1543, 1547, 1545, 1120, 1121, 1125, 1123],\n       [1543, 1544, 1548, 1547, 1121, 1122, 1126, 1125],\n       [1546, 1545, 1549, 1550, 1124, 1123, 1127, 1128],\n       [1545, 1547, 1551, 1549, 1123, 1125, 1129, 1127],\n       [1547, 1548, 1552, 1551, 1125, 1126, 1130, 1129],\n       [1550, 1549, 1553, 1554, 1128, 1127, 1131, 1132],\n       [1549, 1551, 1555, 1553, 1127, 1129, 1133, 1131],\n       [1551, 1552, 1556, 1555, 1129, 1130, 1134, 1133],\n       [1556, 1557, 1558, 1555, 1134, 1135, 1136, 1133],\n       [1557, 1559, 1560, 1558, 1135, 1137, 1138, 1136],\n       [1559, 1341, 1493, 1560, 1137,  919, 1071, 1138],\n       [1555, 1558, 1561, 1553, 1133, 1136, 1139, 1131],\n       [1558, 1560, 1562, 1561, 1136, 1138, 1140, 1139],\n       [1560, 1493, 1491, 1562, 1138, 1071, 1069, 1140],\n       [1553, 1561, 1563, 1554, 1131, 1139, 1141, 1132],\n       [1561, 1562, 1564, 1563, 1139, 1140, 1142, 1141],\n       [1562, 1491, 1490, 1564, 1140, 1069, 1068, 1142],\n       [1490, 1486, 1565, 1564, 1068, 1064, 1143, 1142],\n       [1486, 1482, 1566, 1565, 1064, 1060, 1144, 1143],\n       [1482, 1478, 1539, 1566, 1060, 1056, 1117, 1144],\n       [1564, 1565, 1567, 1563, 1142, 1143, 1145, 1141],\n       [1565, 1566, 1568, 1567, 1143, 1144, 1146, 1145],\n       [1566, 1539, 1540, 1568, 1144, 1117, 1118, 1146],\n       [1563, 1567, 1550, 1554, 1141, 1145, 1128, 1132],\n       [1567, 1568, 1546, 1550, 1145, 1146, 1124, 1128],\n       [1568, 1540, 1541, 1546, 1146, 1118, 1119, 1124],\n       [1513, 1569, 1570, 1514, 1091, 1147, 1148, 1092],\n       [1569, 1571, 1572, 1570, 1147, 1149, 1150, 1148],\n       [1571, 1573, 1574, 1572, 1149, 1151, 1152, 1150],\n       [1573, 1575, 1576, 1574, 1151, 1153, 1154, 1152],\n       [1575, 1577, 1578, 1576, 1153, 1155, 1156, 1154],\n       [1577, 1579, 1580, 1578, 1155, 1157, 1158, 1156],\n       [1514, 1570, 1581, 1520, 1092, 1148, 1159, 1098],\n       [1570, 1572, 1582, 1581, 1148, 1150, 1160, 1159],\n       [1572, 1574, 1583, 1582, 1150, 1152, 1161, 1160],\n       [1574, 1576, 1584, 1583, 1152, 1154, 1162, 1161],\n       [1576, 1578, 1585, 1584, 1154, 1156, 1163, 1162],\n       [1578, 1580, 1586, 1585, 1156, 1158, 1164, 1163],\n       [1520, 1581, 1587, 1526, 1098, 1159, 1165, 1104],\n       [1581, 1582, 1588, 1587, 1159, 1160, 1166, 1165],\n       [1582, 1583, 1589, 1588, 1160, 1161, 1167, 1166],\n       [1583, 1584, 1590, 1589, 1161, 1162, 1168, 1167],\n       [1584, 1585, 1591, 1590, 1162, 1163, 1169, 1168],\n       [1585, 1586, 1592, 1591, 1163, 1164, 1170, 1169],\n       [1526, 1587, 1593, 1532, 1104, 1165, 1171, 1110],\n       [1587, 1588, 1594, 1593, 1165, 1166, 1172, 1171],\n       [1588, 1589, 1595, 1594, 1166, 1167, 1173, 1172],\n       [1589, 1590, 1596, 1595, 1167, 1168, 1174, 1173],\n       [1590, 1591, 1597, 1596, 1168, 1169, 1175, 1174],\n       [1591, 1592, 1598, 1597, 1169, 1170, 1176, 1175],\n       [1532, 1593, 1599, 1538, 1110, 1171, 1177, 1116],\n       [1593, 1594, 1600, 1599, 1171, 1172, 1178, 1177],\n       [1594, 1595, 1601, 1600, 1172, 1173, 1179, 1178],\n       [1595, 1596, 1602, 1601, 1173, 1174, 1180, 1179],\n       [1596, 1597, 1603, 1602, 1174, 1175, 1181, 1180],\n       [1597, 1598, 1604, 1603, 1175, 1176, 1182, 1181],\n       [1538, 1599, 1605, 1544, 1116, 1177, 1183, 1122],\n       [1599, 1600, 1606, 1605, 1177, 1178, 1184, 1183],\n       [1600, 1601, 1607, 1606, 1178, 1179, 1185, 1184],\n       [1601, 1602, 1608, 1607, 1179, 1180, 1186, 1185],\n       [1602, 1603, 1609, 1608, 1180, 1181, 1187, 1186],\n       [1603, 1604, 1610, 1609, 1181, 1182, 1188, 1187],\n       [1607, 1608, 1611, 1612, 1185, 1186, 1189, 1190],\n       [1608, 1609, 1613, 1611, 1186, 1187, 1191, 1189],\n       [1609, 1610, 1614, 1613, 1187, 1188, 1192, 1191],\n       [1612, 1611, 1615, 1616, 1190, 1189, 1193, 1194],\n       [1611, 1613, 1617, 1615, 1189, 1191, 1195, 1193],\n       [1613, 1614, 1618, 1617, 1191, 1192, 1196, 1195],\n       [1616, 1615, 1619, 1620, 1194, 1193, 1197, 1198],\n       [1615, 1617, 1621, 1619, 1193, 1195, 1199, 1197],\n       [1617, 1618, 1622, 1621, 1195, 1196, 1200, 1199],\n       [1622, 1623, 1624, 1621, 1200, 1201, 1202, 1199],\n       [1623, 1625, 1626, 1624, 1201, 1203, 1204, 1202],\n       [1625, 1341, 1559, 1626, 1203,  919, 1137, 1204],\n       [1621, 1624, 1627, 1619, 1199, 1202, 1205, 1197],\n       [1624, 1626, 1628, 1627, 1202, 1204, 1206, 1205],\n       [1626, 1559, 1557, 1628, 1204, 1137, 1135, 1206],\n       [1619, 1627, 1629, 1620, 1197, 1205, 1207, 1198],\n       [1627, 1628, 1630, 1629, 1205, 1206, 1208, 1207],\n       [1628, 1557, 1556, 1630, 1206, 1135, 1134, 1208],\n       [1556, 1552, 1631, 1630, 1134, 1130, 1209, 1208],\n       [1552, 1548, 1632, 1631, 1130, 1126, 1210, 1209],\n       [1548, 1544, 1605, 1632, 1126, 1122, 1183, 1210],\n       [1630, 1631, 1633, 1629, 1208, 1209, 1211, 1207],\n       [1631, 1632, 1634, 1633, 1209, 1210, 1212, 1211],\n       [1632, 1605, 1606, 1634, 1210, 1183, 1184, 1212],\n       [1629, 1633, 1616, 1620, 1207, 1211, 1194, 1198],\n       [1633, 1634, 1612, 1616, 1211, 1212, 1190, 1194],\n       [1634, 1606, 1607, 1612, 1212, 1184, 1185, 1190],\n       [1579, 1635, 1636, 1580, 1157, 1213, 1214, 1158],\n       [1635, 1637, 1638, 1636, 1213, 1215, 1216, 1214],\n       [1637, 1639, 1640, 1638, 1215, 1217, 1218, 1216],\n       [1639, 1641, 1642, 1640, 1217, 1219, 1220, 1218],\n       [1641, 1643, 1644, 1642, 1219, 1221, 1222, 1220],\n       [1643, 1267, 1270, 1644, 1221,  845,  848, 1222],\n       [1580, 1636, 1645, 1586, 1158, 1214, 1223, 1164],\n       [1636, 1638, 1646, 1645, 1214, 1216, 1224, 1223],\n       [1638, 1640, 1647, 1646, 1216, 1218, 1225, 1224],\n       [1640, 1642, 1648, 1647, 1218, 1220, 1226, 1225],\n       [1642, 1644, 1649, 1648, 1220, 1222, 1227, 1226],\n       [1644, 1270, 1284, 1649, 1222,  848,  862, 1227],\n       [1586, 1645, 1650, 1592, 1164, 1223, 1228, 1170],\n       [1645, 1646, 1651, 1650, 1223, 1224, 1229, 1228],\n       [1646, 1647, 1652, 1651, 1224, 1225, 1230, 1229],\n       [1647, 1648, 1653, 1652, 1225, 1226, 1231, 1230],\n       [1648, 1649, 1654, 1653, 1226, 1227, 1232, 1231],\n       [1649, 1284, 1292, 1654, 1227,  862,  870, 1232],\n       [1592, 1650, 1655, 1598, 1170, 1228, 1233, 1176],\n       [1650, 1651, 1656, 1655, 1228, 1229, 1234, 1233],\n       [1651, 1652, 1657, 1656, 1229, 1230, 1235, 1234],\n       [1652, 1653, 1658, 1657, 1230, 1231, 1236, 1235],\n       [1653, 1654, 1659, 1658, 1231, 1232, 1237, 1236],\n       [1654, 1292, 1300, 1659, 1232,  870,  878, 1237],\n       [1598, 1655, 1660, 1604, 1176, 1233, 1238, 1182],\n       [1655, 1656, 1661, 1660, 1233, 1234, 1239, 1238],\n       [1656, 1657, 1662, 1661, 1234, 1235, 1240, 1239],\n       [1657, 1658, 1663, 1662, 1235, 1236, 1241, 1240],\n       [1658, 1659, 1664, 1663, 1236, 1237, 1242, 1241],\n       [1659, 1300, 1308, 1664, 1237,  878,  886, 1242],\n       [1604, 1660, 1665, 1610, 1182, 1238, 1243, 1188],\n       [1660, 1661, 1666, 1665, 1238, 1239, 1244, 1243],\n       [1661, 1662, 1667, 1666, 1239, 1240, 1245, 1244],\n       [1662, 1663, 1668, 1667, 1240, 1241, 1246, 1245],\n       [1663, 1664, 1669, 1668, 1241, 1242, 1247, 1246],\n       [1664, 1308, 1316, 1669, 1242,  886,  894, 1247],\n       [1667, 1668, 1670, 1671, 1245, 1246, 1248, 1249],\n       [1668, 1669, 1672, 1670, 1246, 1247, 1250, 1248],\n       [1669, 1316, 1353, 1672, 1247,  894,  931, 1250],\n       [1671, 1670, 1673, 1674, 1249, 1248, 1251, 1252],\n       [1670, 1672, 1675, 1673, 1248, 1250, 1253, 1251],\n       [1672, 1353, 1351, 1675, 1250,  931,  929, 1253],\n       [1674, 1673, 1676, 1677, 1252, 1251, 1254, 1255],\n       [1673, 1675, 1678, 1676, 1251, 1253, 1256, 1254],\n       [1675, 1351, 1350, 1678, 1253,  929,  928, 1256],\n       [1350, 1346, 1679, 1678,  928,  924, 1257, 1256],\n       [1346, 1342, 1680, 1679,  924,  920, 1258, 1257],\n       [1342, 1341, 1625, 1680,  920,  919, 1203, 1258],\n       [1678, 1679, 1681, 1676, 1256, 1257, 1259, 1254],\n       [1679, 1680, 1682, 1681, 1257, 1258, 1260, 1259],\n       [1680, 1625, 1623, 1682, 1258, 1203, 1201, 1260],\n       [1676, 1681, 1683, 1677, 1254, 1259, 1261, 1255],\n       [1681, 1682, 1684, 1683, 1259, 1260, 1262, 1261],\n       [1682, 1623, 1622, 1684, 1260, 1201, 1200, 1262],\n       [1622, 1618, 1685, 1684, 1200, 1196, 1263, 1262],\n       [1618, 1614, 1686, 1685, 1196, 1192, 1264, 1263],\n       [1614, 1610, 1665, 1686, 1192, 1188, 1243, 1264],\n       [1684, 1685, 1687, 1683, 1262, 1263, 1265, 1261],\n       [1685, 1686, 1688, 1687, 1263, 1264, 1266, 1265],\n       [1686, 1665, 1666, 1688, 1264, 1243, 1244, 1266],\n       [1683, 1687, 1674, 1677, 1261, 1265, 1252, 1255],\n       [1687, 1688, 1671, 1674, 1265, 1266, 1249, 1252],\n       [1688, 1666, 1667, 1671, 1266, 1244, 1245, 1249],\n       [1689, 1690, 1691, 1692, 1267, 1268, 1269, 1270],\n       [1690, 1693, 1694, 1691, 1268, 1271, 1272, 1269],\n       [1693, 1695, 1696, 1694, 1271, 1273, 1274, 1272],\n       [1695, 1697, 1698, 1696, 1273, 1275, 1276, 1274],\n       [1697, 1699, 1700, 1698, 1275, 1277, 1278, 1276],\n       [1699, 1701, 1702, 1700, 1277, 1279, 1280, 1278],\n       [1701, 1703, 1704, 1702, 1279, 1281, 1282, 1280],\n       [1692, 1691, 1705, 1706, 1270, 1269, 1283, 1284],\n       [1691, 1694, 1707, 1705, 1269, 1272, 1285, 1283],\n       [1694, 1696, 1708, 1707, 1272, 1274, 1286, 1285],\n       [1696, 1698, 1709, 1708, 1274, 1276, 1287, 1286],\n       [1698, 1700, 1710, 1709, 1276, 1278, 1288, 1287],\n       [1700, 1702, 1711, 1710, 1278, 1280, 1289, 1288],\n       [1702, 1704, 1712, 1711, 1280, 1282, 1290, 1289],\n       [1706, 1705, 1713, 1714, 1284, 1283, 1291, 1292],\n       [1705, 1707, 1715, 1713, 1283, 1285, 1293, 1291],\n       [1707, 1708, 1716, 1715, 1285, 1286, 1294, 1293],\n       [1708, 1709, 1717, 1716, 1286, 1287, 1295, 1294],\n       [1709, 1710, 1718, 1717, 1287, 1288, 1296, 1295],\n       [1710, 1711, 1719, 1718, 1288, 1289, 1297, 1296],\n       [1711, 1712, 1720, 1719, 1289, 1290, 1298, 1297],\n       [1714, 1713, 1721, 1722, 1292, 1291, 1299, 1300],\n       [1713, 1715, 1723, 1721, 1291, 1293, 1301, 1299],\n       [1715, 1716, 1724, 1723, 1293, 1294, 1302, 1301],\n       [1716, 1717, 1725, 1724, 1294, 1295, 1303, 1302],\n       [1717, 1718, 1726, 1725, 1295, 1296, 1304, 1303],\n       [1718, 1719, 1727, 1726, 1296, 1297, 1305, 1304],\n       [1719, 1720, 1728, 1727, 1297, 1298, 1306, 1305],\n       [1722, 1721, 1729, 1730, 1300, 1299, 1307, 1308],\n       [1721, 1723, 1731, 1729, 1299, 1301, 1309, 1307],\n       [1723, 1724, 1732, 1731, 1301, 1302, 1310, 1309],\n       [1724, 1725, 1733, 1732, 1302, 1303, 1311, 1310],\n       [1725, 1726, 1734, 1733, 1303, 1304, 1312, 1311],\n       [1726, 1727, 1735, 1734, 1304, 1305, 1313, 1312],\n       [1727, 1728, 1736, 1735, 1305, 1306, 1314, 1313],\n       [1730, 1729, 1737, 1738, 1308, 1307, 1315, 1316],\n       [1729, 1731, 1739, 1737, 1307, 1309, 1317, 1315],\n       [1731, 1732, 1740, 1739, 1309, 1310, 1318, 1317],\n       [1732, 1733, 1741, 1740, 1310, 1311, 1319, 1318],\n       [1733, 1734, 1742, 1741, 1311, 1312, 1320, 1319],\n       [1734, 1735, 1743, 1742, 1312, 1313, 1321, 1320],\n       [1735, 1736, 1744, 1743, 1313, 1314, 1322, 1321],\n       [1741, 1742, 1745, 1746, 1319, 1320, 1323, 1324],\n       [1742, 1743, 1747, 1745, 1320, 1321, 1325, 1323],\n       [1743, 1744, 1748, 1747, 1321, 1322, 1326, 1325],\n       [1746, 1745, 1749, 1750, 1324, 1323, 1327, 1328],\n       [1745, 1747, 1751, 1749, 1323, 1325, 1329, 1327],\n       [1747, 1748, 1752, 1751, 1325, 1326, 1330, 1329],\n       [1750, 1749, 1753, 1754, 1328, 1327, 1331, 1332],\n       [1749, 1751, 1755, 1753, 1327, 1329, 1333, 1331],\n       [1751, 1752, 1756, 1755, 1329, 1330, 1334, 1333],\n       [1756, 1757, 1758, 1755, 1334, 1335, 1336, 1333],\n       [1757, 1759, 1760, 1758, 1335, 1337, 1338, 1336],\n       [1759, 1761, 1762, 1760, 1337, 1339, 1340, 1338],\n       [1761, 1763, 1764, 1762, 1339, 1341, 1342, 1340],\n       [1755, 1758, 1765, 1753, 1333, 1336, 1343, 1331],\n       [1758, 1760, 1766, 1765, 1336, 1338, 1344, 1343],\n       [1760, 1762, 1767, 1766, 1338, 1340, 1345, 1344],\n       [1762, 1764, 1768, 1767, 1340, 1342, 1346, 1345],\n       [1753, 1765, 1769, 1754, 1331, 1343, 1347, 1332],\n       [1765, 1766, 1770, 1769, 1343, 1344, 1348, 1347],\n       [1766, 1767, 1771, 1770, 1344, 1345, 1349, 1348],\n       [1767, 1768, 1772, 1771, 1345, 1346, 1350, 1349],\n       [1772, 1773, 1774, 1771, 1350, 1351, 1352, 1349],\n       [1773, 1775, 1776, 1774, 1351, 1353, 1354, 1352],\n       [1775, 1738, 1737, 1776, 1353, 1316, 1315, 1354],\n       [1771, 1774, 1777, 1770, 1349, 1352, 1355, 1348],\n       [1774, 1776, 1778, 1777, 1352, 1354, 1356, 1355],\n       [1776, 1737, 1739, 1778, 1354, 1315, 1317, 1356],\n       [1770, 1777, 1779, 1769, 1348, 1355, 1357, 1347],\n       [1777, 1778, 1780, 1779, 1355, 1356, 1358, 1357],\n       [1778, 1739, 1740, 1780, 1356, 1317, 1318, 1358],\n       [1769, 1779, 1750, 1754, 1347, 1357, 1328, 1332],\n       [1779, 1780, 1746, 1750, 1357, 1358, 1324, 1328],\n       [1780, 1740, 1741, 1746, 1358, 1318, 1319, 1324],\n       [1703, 1781, 1782, 1704, 1281, 1359, 1360, 1282],\n       [1781, 1783, 1784, 1782, 1359, 1361, 1362, 1360],\n       [1783, 1785, 1786, 1784, 1361, 1363, 1364, 1362],\n       [1785, 1787, 1788, 1786, 1363, 1365, 1366, 1364],\n       [1787, 1789, 1790, 1788, 1365, 1367, 1368, 1366],\n       [1789, 1791, 1792, 1790, 1367, 1369, 1370, 1368],\n       [1791, 1793, 1794, 1792, 1369, 1371, 1372, 1370],\n       [1704, 1782, 1795, 1712, 1282, 1360, 1373, 1290],\n       [1782, 1784, 1796, 1795, 1360, 1362, 1374, 1373],\n       [1784, 1786, 1797, 1796, 1362, 1364, 1375, 1374],\n       [1786, 1788, 1798, 1797, 1364, 1366, 1376, 1375],\n       [1788, 1790, 1799, 1798, 1366, 1368, 1377, 1376],\n       [1790, 1792, 1800, 1799, 1368, 1370, 1378, 1377],\n       [1792, 1794, 1801, 1800, 1370, 1372, 1379, 1378],\n       [1712, 1795, 1802, 1720, 1290, 1373, 1380, 1298],\n       [1795, 1796, 1803, 1802, 1373, 1374, 1381, 1380],\n       [1796, 1797, 1804, 1803, 1374, 1375, 1382, 1381],\n       [1797, 1798, 1805, 1804, 1375, 1376, 1383, 1382],\n       [1798, 1799, 1806, 1805, 1376, 1377, 1384, 1383],\n       [1799, 1800, 1807, 1806, 1377, 1378, 1385, 1384],\n       [1800, 1801, 1808, 1807, 1378, 1379, 1386, 1385],\n       [1720, 1802, 1809, 1728, 1298, 1380, 1387, 1306],\n       [1802, 1803, 1810, 1809, 1380, 1381, 1388, 1387],\n       [1803, 1804, 1811, 1810, 1381, 1382, 1389, 1388],\n       [1804, 1805, 1812, 1811, 1382, 1383, 1390, 1389],\n       [1805, 1806, 1813, 1812, 1383, 1384, 1391, 1390],\n       [1806, 1807, 1814, 1813, 1384, 1385, 1392, 1391],\n       [1807, 1808, 1815, 1814, 1385, 1386, 1393, 1392],\n       [1728, 1809, 1816, 1736, 1306, 1387, 1394, 1314],\n       [1809, 1810, 1817, 1816, 1387, 1388, 1395, 1394],\n       [1810, 1811, 1818, 1817, 1388, 1389, 1396, 1395],\n       [1811, 1812, 1819, 1818, 1389, 1390, 1397, 1396],\n       [1812, 1813, 1820, 1819, 1390, 1391, 1398, 1397],\n       [1813, 1814, 1821, 1820, 1391, 1392, 1399, 1398],\n       [1814, 1815, 1822, 1821, 1392, 1393, 1400, 1399],\n       [1736, 1816, 1823, 1744, 1314, 1394, 1401, 1322],\n       [1816, 1817, 1824, 1823, 1394, 1395, 1402, 1401],\n       [1817, 1818, 1825, 1824, 1395, 1396, 1403, 1402],\n       [1818, 1819, 1826, 1825, 1396, 1397, 1404, 1403],\n       [1819, 1820, 1827, 1826, 1397, 1398, 1405, 1404],\n       [1820, 1821, 1828, 1827, 1398, 1399, 1406, 1405],\n       [1821, 1822, 1829, 1828, 1399, 1400, 1407, 1406],\n       [1825, 1826, 1830, 1831, 1403, 1404, 1408, 1409],\n       [1826, 1827, 1832, 1830, 1404, 1405, 1410, 1408],\n       [1827, 1828, 1833, 1832, 1405, 1406, 1411, 1410],\n       [1828, 1829, 1834, 1833, 1406, 1407, 1412, 1411],\n       [1831, 1830, 1835, 1836, 1409, 1408, 1413, 1414],\n       [1830, 1832, 1837, 1835, 1408, 1410, 1415, 1413],\n       [1832, 1833, 1838, 1837, 1410, 1411, 1416, 1415],\n       [1833, 1834, 1839, 1838, 1411, 1412, 1417, 1416],\n       [1836, 1835, 1840, 1841, 1414, 1413, 1418, 1419],\n       [1835, 1837, 1842, 1840, 1413, 1415, 1420, 1418],\n       [1837, 1838, 1843, 1842, 1415, 1416, 1421, 1420],\n       [1838, 1839, 1844, 1843, 1416, 1417, 1422, 1421],\n       [1844, 1845, 1846, 1843, 1422, 1423, 1424, 1421],\n       [1845, 1847, 1848, 1846, 1423, 1425, 1426, 1424],\n       [1847, 1763, 1761, 1848, 1425, 1341, 1339, 1426],\n       [1843, 1846, 1849, 1842, 1421, 1424, 1427, 1420],\n       [1846, 1848, 1850, 1849, 1424, 1426, 1428, 1427],\n       [1848, 1761, 1759, 1850, 1426, 1339, 1337, 1428],\n       [1842, 1849, 1851, 1840, 1420, 1427, 1429, 1418],\n       [1849, 1850, 1852, 1851, 1427, 1428, 1430, 1429],\n       [1850, 1759, 1757, 1852, 1428, 1337, 1335, 1430],\n       [1840, 1851, 1853, 1841, 1418, 1429, 1431, 1419],\n       [1851, 1852, 1854, 1853, 1429, 1430, 1432, 1431],\n       [1852, 1757, 1756, 1854, 1430, 1335, 1334, 1432],\n       [1756, 1752, 1855, 1854, 1334, 1330, 1433, 1432],\n       [1752, 1748, 1856, 1855, 1330, 1326, 1434, 1433],\n       [1748, 1744, 1823, 1856, 1326, 1322, 1401, 1434],\n       [1854, 1855, 1857, 1853, 1432, 1433, 1435, 1431],\n       [1855, 1856, 1858, 1857, 1433, 1434, 1436, 1435],\n       [1856, 1823, 1824, 1858, 1434, 1401, 1402, 1436],\n       [1853, 1857, 1836, 1841, 1431, 1435, 1414, 1419],\n       [1857, 1858, 1831, 1836, 1435, 1436, 1409, 1414],\n       [1858, 1824, 1825, 1831, 1436, 1402, 1403, 1409],\n       [1793, 1859, 1860, 1794, 1371, 1437, 1438, 1372],\n       [1859, 1861, 1862, 1860, 1437, 1439, 1440, 1438],\n       [1861, 1863, 1864, 1862, 1439, 1441, 1442, 1440],\n       [1863, 1865, 1866, 1864, 1441, 1443, 1444, 1442],\n       [1865, 1867, 1868, 1866, 1443, 1445, 1446, 1444],\n       [1867, 1869, 1870, 1868, 1445, 1447, 1448, 1446],\n       [1794, 1860, 1871, 1801, 1372, 1438, 1449, 1379],\n       [1860, 1862, 1872, 1871, 1438, 1440, 1450, 1449],\n       [1862, 1864, 1873, 1872, 1440, 1442, 1451, 1450],\n       [1864, 1866, 1874, 1873, 1442, 1444, 1452, 1451],\n       [1866, 1868, 1875, 1874, 1444, 1446, 1453, 1452],\n       [1868, 1870, 1876, 1875, 1446, 1448, 1454, 1453],\n       [1801, 1871, 1877, 1808, 1379, 1449, 1455, 1386],\n       [1871, 1872, 1878, 1877, 1449, 1450, 1456, 1455],\n       [1872, 1873, 1879, 1878, 1450, 1451, 1457, 1456],\n       [1873, 1874, 1880, 1879, 1451, 1452, 1458, 1457],\n       [1874, 1875, 1881, 1880, 1452, 1453, 1459, 1458],\n       [1875, 1876, 1882, 1881, 1453, 1454, 1460, 1459],\n       [1808, 1877, 1883, 1815, 1386, 1455, 1461, 1393],\n       [1877, 1878, 1884, 1883, 1455, 1456, 1462, 1461],\n       [1878, 1879, 1885, 1884, 1456, 1457, 1463, 1462],\n       [1879, 1880, 1886, 1885, 1457, 1458, 1464, 1463],\n       [1880, 1881, 1887, 1886, 1458, 1459, 1465, 1464],\n       [1881, 1882, 1888, 1887, 1459, 1460, 1466, 1465],\n       [1815, 1883, 1889, 1822, 1393, 1461, 1467, 1400],\n       [1883, 1884, 1890, 1889, 1461, 1462, 1468, 1467],\n       [1884, 1885, 1891, 1890, 1462, 1463, 1469, 1468],\n       [1885, 1886, 1892, 1891, 1463, 1464, 1470, 1469],\n       [1886, 1887, 1893, 1892, 1464, 1465, 1471, 1470],\n       [1887, 1888, 1894, 1893, 1465, 1466, 1472, 1471],\n       [1822, 1889, 1895, 1829, 1400, 1467, 1473, 1407],\n       [1889, 1890, 1896, 1895, 1467, 1468, 1474, 1473],\n       [1890, 1891, 1897, 1896, 1468, 1469, 1475, 1474],\n       [1891, 1892, 1898, 1897, 1469, 1470, 1476, 1475],\n       [1892, 1893, 1899, 1898, 1470, 1471, 1477, 1476],\n       [1893, 1894, 1900, 1899, 1471, 1472, 1478, 1477],\n       [1897, 1898, 1901, 1902, 1475, 1476, 1479, 1480],\n       [1898, 1899, 1903, 1901, 1476, 1477, 1481, 1479],\n       [1899, 1900, 1904, 1903, 1477, 1478, 1482, 1481],\n       [1902, 1901, 1905, 1906, 1480, 1479, 1483, 1484],\n       [1901, 1903, 1907, 1905, 1479, 1481, 1485, 1483],\n       [1903, 1904, 1908, 1907, 1481, 1482, 1486, 1485],\n       [1906, 1905, 1909, 1910, 1484, 1483, 1487, 1488],\n       [1905, 1907, 1911, 1909, 1483, 1485, 1489, 1487],\n       [1907, 1908, 1912, 1911, 1485, 1486, 1490, 1489],\n       [1912, 1913, 1914, 1911, 1490, 1491, 1492, 1489],\n       [1913, 1915, 1916, 1914, 1491, 1493, 1494, 1492],\n       [1915, 1763, 1847, 1916, 1493, 1341, 1425, 1494],\n       [1911, 1914, 1917, 1909, 1489, 1492, 1495, 1487],\n       [1914, 1916, 1918, 1917, 1492, 1494, 1496, 1495],\n       [1916, 1847, 1845, 1918, 1494, 1425, 1423, 1496],\n       [1909, 1917, 1919, 1910, 1487, 1495, 1497, 1488],\n       [1917, 1918, 1920, 1919, 1495, 1496, 1498, 1497],\n       [1918, 1845, 1844, 1920, 1496, 1423, 1422, 1498],\n       [1844, 1839, 1921, 1920, 1422, 1417, 1499, 1498],\n       [1839, 1834, 1922, 1921, 1417, 1412, 1500, 1499],\n       [1834, 1829, 1895, 1922, 1412, 1407, 1473, 1500],\n       [1920, 1921, 1923, 1919, 1498, 1499, 1501, 1497],\n       [1921, 1922, 1924, 1923, 1499, 1500, 1502, 1501],\n       [1922, 1895, 1896, 1924, 1500, 1473, 1474, 1502],\n       [1919, 1923, 1906, 1910, 1497, 1501, 1484, 1488],\n       [1923, 1924, 1902, 1906, 1501, 1502, 1480, 1484],\n       [1924, 1896, 1897, 1902, 1502, 1474, 1475, 1480],\n       [1869, 1925, 1926, 1870, 1447, 1503, 1504, 1448],\n       [1925, 1927, 1928, 1926, 1503, 1505, 1506, 1504],\n       [1927, 1929, 1930, 1928, 1505, 1507, 1508, 1506],\n       [1929, 1931, 1932, 1930, 1507, 1509, 1510, 1508],\n       [1931, 1933, 1934, 1932, 1509, 1511, 1512, 1510],\n       [1933, 1935, 1936, 1934, 1511, 1513, 1514, 1512],\n       [1870, 1926, 1937, 1876, 1448, 1504, 1515, 1454],\n       [1926, 1928, 1938, 1937, 1504, 1506, 1516, 1515],\n       [1928, 1930, 1939, 1938, 1506, 1508, 1517, 1516],\n       [1930, 1932, 1940, 1939, 1508, 1510, 1518, 1517],\n       [1932, 1934, 1941, 1940, 1510, 1512, 1519, 1518],\n       [1934, 1936, 1942, 1941, 1512, 1514, 1520, 1519],\n       [1876, 1937, 1943, 1882, 1454, 1515, 1521, 1460],\n       [1937, 1938, 1944, 1943, 1515, 1516, 1522, 1521],\n       [1938, 1939, 1945, 1944, 1516, 1517, 1523, 1522],\n       [1939, 1940, 1946, 1945, 1517, 1518, 1524, 1523],\n       [1940, 1941, 1947, 1946, 1518, 1519, 1525, 1524],\n       [1941, 1942, 1948, 1947, 1519, 1520, 1526, 1525],\n       [1882, 1943, 1949, 1888, 1460, 1521, 1527, 1466],\n       [1943, 1944, 1950, 1949, 1521, 1522, 1528, 1527],\n       [1944, 1945, 1951, 1950, 1522, 1523, 1529, 1528],\n       [1945, 1946, 1952, 1951, 1523, 1524, 1530, 1529],\n       [1946, 1947, 1953, 1952, 1524, 1525, 1531, 1530],\n       [1947, 1948, 1954, 1953, 1525, 1526, 1532, 1531],\n       [1888, 1949, 1955, 1894, 1466, 1527, 1533, 1472],\n       [1949, 1950, 1956, 1955, 1527, 1528, 1534, 1533],\n       [1950, 1951, 1957, 1956, 1528, 1529, 1535, 1534],\n       [1951, 1952, 1958, 1957, 1529, 1530, 1536, 1535],\n       [1952, 1953, 1959, 1958, 1530, 1531, 1537, 1536],\n       [1953, 1954, 1960, 1959, 1531, 1532, 1538, 1537],\n       [1894, 1955, 1961, 1900, 1472, 1533, 1539, 1478],\n       [1955, 1956, 1962, 1961, 1533, 1534, 1540, 1539],\n       [1956, 1957, 1963, 1962, 1534, 1535, 1541, 1540],\n       [1957, 1958, 1964, 1963, 1535, 1536, 1542, 1541],\n       [1958, 1959, 1965, 1964, 1536, 1537, 1543, 1542],\n       [1959, 1960, 1966, 1965, 1537, 1538, 1544, 1543],\n       [1963, 1964, 1967, 1968, 1541, 1542, 1545, 1546],\n       [1964, 1965, 1969, 1967, 1542, 1543, 1547, 1545],\n       [1965, 1966, 1970, 1969, 1543, 1544, 1548, 1547],\n       [1968, 1967, 1971, 1972, 1546, 1545, 1549, 1550],\n       [1967, 1969, 1973, 1971, 1545, 1547, 1551, 1549],\n       [1969, 1970, 1974, 1973, 1547, 1548, 1552, 1551],\n       [1972, 1971, 1975, 1976, 1550, 1549, 1553, 1554],\n       [1971, 1973, 1977, 1975, 1549, 1551, 1555, 1553],\n       [1973, 1974, 1978, 1977, 1551, 1552, 1556, 1555],\n       [1978, 1979, 1980, 1977, 1556, 1557, 1558, 1555],\n       [1979, 1981, 1982, 1980, 1557, 1559, 1560, 1558],\n       [1981, 1763, 1915, 1982, 1559, 1341, 1493, 1560],\n       [1977, 1980, 1983, 1975, 1555, 1558, 1561, 1553],\n       [1980, 1982, 1984, 1983, 1558, 1560, 1562, 1561],\n       [1982, 1915, 1913, 1984, 1560, 1493, 1491, 1562],\n       [1975, 1983, 1985, 1976, 1553, 1561, 1563, 1554],\n       [1983, 1984, 1986, 1985, 1561, 1562, 1564, 1563],\n       [1984, 1913, 1912, 1986, 1562, 1491, 1490, 1564],\n       [1912, 1908, 1987, 1986, 1490, 1486, 1565, 1564],\n       [1908, 1904, 1988, 1987, 1486, 1482, 1566, 1565],\n       [1904, 1900, 1961, 1988, 1482, 1478, 1539, 1566],\n       [1986, 1987, 1989, 1985, 1564, 1565, 1567, 1563],\n       [1987, 1988, 1990, 1989, 1565, 1566, 1568, 1567],\n       [1988, 1961, 1962, 1990, 1566, 1539, 1540, 1568],\n       [1985, 1989, 1972, 1976, 1563, 1567, 1550, 1554],\n       [1989, 1990, 1968, 1972, 1567, 1568, 1546, 1550],\n       [1990, 1962, 1963, 1968, 1568, 1540, 1541, 1546],\n       [1935, 1991, 1992, 1936, 1513, 1569, 1570, 1514],\n       [1991, 1993, 1994, 1992, 1569, 1571, 1572, 1570],\n       [1993, 1995, 1996, 1994, 1571, 1573, 1574, 1572],\n       [1995, 1997, 1998, 1996, 1573, 1575, 1576, 1574],\n       [1997, 1999, 2000, 1998, 1575, 1577, 1578, 1576],\n       [1999, 2001, 2002, 2000, 1577, 1579, 1580, 1578],\n       [1936, 1992, 2003, 1942, 1514, 1570, 1581, 1520],\n       [1992, 1994, 2004, 2003, 1570, 1572, 1582, 1581],\n       [1994, 1996, 2005, 2004, 1572, 1574, 1583, 1582],\n       [1996, 1998, 2006, 2005, 1574, 1576, 1584, 1583],\n       [1998, 2000, 2007, 2006, 1576, 1578, 1585, 1584],\n       [2000, 2002, 2008, 2007, 1578, 1580, 1586, 1585],\n       [1942, 2003, 2009, 1948, 1520, 1581, 1587, 1526],\n       [2003, 2004, 2010, 2009, 1581, 1582, 1588, 1587],\n       [2004, 2005, 2011, 2010, 1582, 1583, 1589, 1588],\n       [2005, 2006, 2012, 2011, 1583, 1584, 1590, 1589],\n       [2006, 2007, 2013, 2012, 1584, 1585, 1591, 1590],\n       [2007, 2008, 2014, 2013, 1585, 1586, 1592, 1591],\n       [1948, 2009, 2015, 1954, 1526, 1587, 1593, 1532],\n       [2009, 2010, 2016, 2015, 1587, 1588, 1594, 1593],\n       [2010, 2011, 2017, 2016, 1588, 1589, 1595, 1594],\n       [2011, 2012, 2018, 2017, 1589, 1590, 1596, 1595],\n       [2012, 2013, 2019, 2018, 1590, 1591, 1597, 1596],\n       [2013, 2014, 2020, 2019, 1591, 1592, 1598, 1597],\n       [1954, 2015, 2021, 1960, 1532, 1593, 1599, 1538],\n       [2015, 2016, 2022, 2021, 1593, 1594, 1600, 1599],\n       [2016, 2017, 2023, 2022, 1594, 1595, 1601, 1600],\n       [2017, 2018, 2024, 2023, 1595, 1596, 1602, 1601],\n       [2018, 2019, 2025, 2024, 1596, 1597, 1603, 1602],\n       [2019, 2020, 2026, 2025, 1597, 1598, 1604, 1603],\n       [1960, 2021, 2027, 1966, 1538, 1599, 1605, 1544],\n       [2021, 2022, 2028, 2027, 1599, 1600, 1606, 1605],\n       [2022, 2023, 2029, 2028, 1600, 1601, 1607, 1606],\n       [2023, 2024, 2030, 2029, 1601, 1602, 1608, 1607],\n       [2024, 2025, 2031, 2030, 1602, 1603, 1609, 1608],\n       [2025, 2026, 2032, 2031, 1603, 1604, 1610, 1609],\n       [2029, 2030, 2033, 2034, 1607, 1608, 1611, 1612],\n       [2030, 2031, 2035, 2033, 1608, 1609, 1613, 1611],\n       [2031, 2032, 2036, 2035, 1609, 1610, 1614, 1613],\n       [2034, 2033, 2037, 2038, 1612, 1611, 1615, 1616],\n       [2033, 2035, 2039, 2037, 1611, 1613, 1617, 1615],\n       [2035, 2036, 2040, 2039, 1613, 1614, 1618, 1617],\n       [2038, 2037, 2041, 2042, 1616, 1615, 1619, 1620],\n       [2037, 2039, 2043, 2041, 1615, 1617, 1621, 1619],\n       [2039, 2040, 2044, 2043, 1617, 1618, 1622, 1621],\n       [2044, 2045, 2046, 2043, 1622, 1623, 1624, 1621],\n       [2045, 2047, 2048, 2046, 1623, 1625, 1626, 1624],\n       [2047, 1763, 1981, 2048, 1625, 1341, 1559, 1626],\n       [2043, 2046, 2049, 2041, 1621, 1624, 1627, 1619],\n       [2046, 2048, 2050, 2049, 1624, 1626, 1628, 1627],\n       [2048, 1981, 1979, 2050, 1626, 1559, 1557, 1628],\n       [2041, 2049, 2051, 2042, 1619, 1627, 1629, 1620],\n       [2049, 2050, 2052, 2051, 1627, 1628, 1630, 1629],\n       [2050, 1979, 1978, 2052, 1628, 1557, 1556, 1630],\n       [1978, 1974, 2053, 2052, 1556, 1552, 1631, 1630],\n       [1974, 1970, 2054, 2053, 1552, 1548, 1632, 1631],\n       [1970, 1966, 2027, 2054, 1548, 1544, 1605, 1632],\n       [2052, 2053, 2055, 2051, 1630, 1631, 1633, 1629],\n       [2053, 2054, 2056, 2055, 1631, 1632, 1634, 1633],\n       [2054, 2027, 2028, 2056, 1632, 1605, 1606, 1634],\n       [2051, 2055, 2038, 2042, 1629, 1633, 1616, 1620],\n       [2055, 2056, 2034, 2038, 1633, 1634, 1612, 1616],\n       [2056, 2028, 2029, 2034, 1634, 1606, 1607, 1612],\n       [2001, 2057, 2058, 2002, 1579, 1635, 1636, 1580],\n       [2057, 2059, 2060, 2058, 1635, 1637, 1638, 1636],\n       [2059, 2061, 2062, 2060, 1637, 1639, 1640, 1638],\n       [2061, 2063, 2064, 2062, 1639, 1641, 1642, 1640],\n       [2063, 2065, 2066, 2064, 1641, 1643, 1644, 1642],\n       [2065, 1689, 1692, 2066, 1643, 1267, 1270, 1644],\n       [2002, 2058, 2067, 2008, 1580, 1636, 1645, 1586],\n       [2058, 2060, 2068, 2067, 1636, 1638, 1646, 1645],\n       [2060, 2062, 2069, 2068, 1638, 1640, 1647, 1646],\n       [2062, 2064, 2070, 2069, 1640, 1642, 1648, 1647],\n       [2064, 2066, 2071, 2070, 1642, 1644, 1649, 1648],\n       [2066, 1692, 1706, 2071, 1644, 1270, 1284, 1649],\n       [2008, 2067, 2072, 2014, 1586, 1645, 1650, 1592],\n       [2067, 2068, 2073, 2072, 1645, 1646, 1651, 1650],\n       [2068, 2069, 2074, 2073, 1646, 1647, 1652, 1651],\n       [2069, 2070, 2075, 2074, 1647, 1648, 1653, 1652],\n       [2070, 2071, 2076, 2075, 1648, 1649, 1654, 1653],\n       [2071, 1706, 1714, 2076, 1649, 1284, 1292, 1654],\n       [2014, 2072, 2077, 2020, 1592, 1650, 1655, 1598],\n       [2072, 2073, 2078, 2077, 1650, 1651, 1656, 1655],\n       [2073, 2074, 2079, 2078, 1651, 1652, 1657, 1656],\n       [2074, 2075, 2080, 2079, 1652, 1653, 1658, 1657],\n       [2075, 2076, 2081, 2080, 1653, 1654, 1659, 1658],\n       [2076, 1714, 1722, 2081, 1654, 1292, 1300, 1659],\n       [2020, 2077, 2082, 2026, 1598, 1655, 1660, 1604],\n       [2077, 2078, 2083, 2082, 1655, 1656, 1661, 1660],\n       [2078, 2079, 2084, 2083, 1656, 1657, 1662, 1661],\n       [2079, 2080, 2085, 2084, 1657, 1658, 1663, 1662],\n       [2080, 2081, 2086, 2085, 1658, 1659, 1664, 1663],\n       [2081, 1722, 1730, 2086, 1659, 1300, 1308, 1664],\n       [2026, 2082, 2087, 2032, 1604, 1660, 1665, 1610],\n       [2082, 2083, 2088, 2087, 1660, 1661, 1666, 1665],\n       [2083, 2084, 2089, 2088, 1661, 1662, 1667, 1666],\n       [2084, 2085, 2090, 2089, 1662, 1663, 1668, 1667],\n       [2085, 2086, 2091, 2090, 1663, 1664, 1669, 1668],\n       [2086, 1730, 1738, 2091, 1664, 1308, 1316, 1669],\n       [2089, 2090, 2092, 2093, 1667, 1668, 1670, 1671],\n       [2090, 2091, 2094, 2092, 1668, 1669, 1672, 1670],\n       [2091, 1738, 1775, 2094, 1669, 1316, 1353, 1672],\n       [2093, 2092, 2095, 2096, 1671, 1670, 1673, 1674],\n       [2092, 2094, 2097, 2095, 1670, 1672, 1675, 1673],\n       [2094, 1775, 1773, 2097, 1672, 1353, 1351, 1675],\n       [2096, 2095, 2098, 2099, 1674, 1673, 1676, 1677],\n       [2095, 2097, 2100, 2098, 1673, 1675, 1678, 1676],\n       [2097, 1773, 1772, 2100, 1675, 1351, 1350, 1678],\n       [1772, 1768, 2101, 2100, 1350, 1346, 1679, 1678],\n       [1768, 1764, 2102, 2101, 1346, 1342, 1680, 1679],\n       [1764, 1763, 2047, 2102, 1342, 1341, 1625, 1680],\n       [2100, 2101, 2103, 2098, 1678, 1679, 1681, 1676],\n       [2101, 2102, 2104, 2103, 1679, 1680, 1682, 1681],\n       [2102, 2047, 2045, 2104, 1680, 1625, 1623, 1682],\n       [2098, 2103, 2105, 2099, 1676, 1681, 1683, 1677],\n       [2103, 2104, 2106, 2105, 1681, 1682, 1684, 1683],\n       [2104, 2045, 2044, 2106, 1682, 1623, 1622, 1684],\n       [2044, 2040, 2107, 2106, 1622, 1618, 1685, 1684],\n       [2040, 2036, 2108, 2107, 1618, 1614, 1686, 1685],\n       [2036, 2032, 2087, 2108, 1614, 1610, 1665, 1686],\n       [2106, 2107, 2109, 2105, 1684, 1685, 1687, 1683],\n       [2107, 2108, 2110, 2109, 1685, 1686, 1688, 1687],\n       [2108, 2087, 2088, 2110, 1686, 1665, 1666, 1688],\n       [2105, 2109, 2096, 2099, 1683, 1687, 1674, 1677],\n       [2109, 2110, 2093, 2096, 1687, 1688, 1671, 1674],\n       [2110, 2088, 2089, 2093, 1688, 1666, 1667, 1671],\n       [2111, 2112, 2113, 2114, 1689, 1690, 1691, 1692],\n       [2112, 2115, 2116, 2113, 1690, 1693, 1694, 1691],\n       [2115, 2117, 2118, 2116, 1693, 1695, 1696, 1694],\n       [2117, 2119, 2120, 2118, 1695, 1697, 1698, 1696],\n       [2119, 2121, 2122, 2120, 1697, 1699, 1700, 1698],\n       [2121, 2123, 2124, 2122, 1699, 1701, 1702, 1700],\n       [2123, 2125, 2126, 2124, 1701, 1703, 1704, 1702],\n       [2114, 2113, 2127, 2128, 1692, 1691, 1705, 1706],\n       [2113, 2116, 2129, 2127, 1691, 1694, 1707, 1705],\n       [2116, 2118, 2130, 2129, 1694, 1696, 1708, 1707],\n       [2118, 2120, 2131, 2130, 1696, 1698, 1709, 1708],\n       [2120, 2122, 2132, 2131, 1698, 1700, 1710, 1709],\n       [2122, 2124, 2133, 2132, 1700, 1702, 1711, 1710],\n       [2124, 2126, 2134, 2133, 1702, 1704, 1712, 1711],\n       [2128, 2127, 2135, 2136, 1706, 1705, 1713, 1714],\n       [2127, 2129, 2137, 2135, 1705, 1707, 1715, 1713],\n       [2129, 2130, 2138, 2137, 1707, 1708, 1716, 1715],\n       [2130, 2131, 2139, 2138, 1708, 1709, 1717, 1716],\n       [2131, 2132, 2140, 2139, 1709, 1710, 1718, 1717],\n       [2132, 2133, 2141, 2140, 1710, 1711, 1719, 1718],\n       [2133, 2134, 2142, 2141, 1711, 1712, 1720, 1719],\n       [2136, 2135, 2143, 2144, 1714, 1713, 1721, 1722],\n       [2135, 2137, 2145, 2143, 1713, 1715, 1723, 1721],\n       [2137, 2138, 2146, 2145, 1715, 1716, 1724, 1723],\n       [2138, 2139, 2147, 2146, 1716, 1717, 1725, 1724],\n       [2139, 2140, 2148, 2147, 1717, 1718, 1726, 1725],\n       [2140, 2141, 2149, 2148, 1718, 1719, 1727, 1726],\n       [2141, 2142, 2150, 2149, 1719, 1720, 1728, 1727],\n       [2144, 2143, 2151, 2152, 1722, 1721, 1729, 1730],\n       [2143, 2145, 2153, 2151, 1721, 1723, 1731, 1729],\n       [2145, 2146, 2154, 2153, 1723, 1724, 1732, 1731],\n       [2146, 2147, 2155, 2154, 1724, 1725, 1733, 1732],\n       [2147, 2148, 2156, 2155, 1725, 1726, 1734, 1733],\n       [2148, 2149, 2157, 2156, 1726, 1727, 1735, 1734],\n       [2149, 2150, 2158, 2157, 1727, 1728, 1736, 1735],\n       [2152, 2151, 2159, 2160, 1730, 1729, 1737, 1738],\n       [2151, 2153, 2161, 2159, 1729, 1731, 1739, 1737],\n       [2153, 2154, 2162, 2161, 1731, 1732, 1740, 1739],\n       [2154, 2155, 2163, 2162, 1732, 1733, 1741, 1740],\n       [2155, 2156, 2164, 2163, 1733, 1734, 1742, 1741],\n       [2156, 2157, 2165, 2164, 1734, 1735, 1743, 1742],\n       [2157, 2158, 2166, 2165, 1735, 1736, 1744, 1743],\n       [2163, 2164, 2167, 2168, 1741, 1742, 1745, 1746],\n       [2164, 2165, 2169, 2167, 1742, 1743, 1747, 1745],\n       [2165, 2166, 2170, 2169, 1743, 1744, 1748, 1747],\n       [2168, 2167, 2171, 2172, 1746, 1745, 1749, 1750],\n       [2167, 2169, 2173, 2171, 1745, 1747, 1751, 1749],\n       [2169, 2170, 2174, 2173, 1747, 1748, 1752, 1751],\n       [2172, 2171, 2175, 2176, 1750, 1749, 1753, 1754],\n       [2171, 2173, 2177, 2175, 1749, 1751, 1755, 1753],\n       [2173, 2174, 2178, 2177, 1751, 1752, 1756, 1755],\n       [2178, 2179, 2180, 2177, 1756, 1757, 1758, 1755],\n       [2179, 2181, 2182, 2180, 1757, 1759, 1760, 1758],\n       [2181, 2183, 2184, 2182, 1759, 1761, 1762, 1760],\n       [2183, 2185, 2186, 2184, 1761, 1763, 1764, 1762],\n       [2177, 2180, 2187, 2175, 1755, 1758, 1765, 1753],\n       [2180, 2182, 2188, 2187, 1758, 1760, 1766, 1765],\n       [2182, 2184, 2189, 2188, 1760, 1762, 1767, 1766],\n       [2184, 2186, 2190, 2189, 1762, 1764, 1768, 1767],\n       [2175, 2187, 2191, 2176, 1753, 1765, 1769, 1754],\n       [2187, 2188, 2192, 2191, 1765, 1766, 1770, 1769],\n       [2188, 2189, 2193, 2192, 1766, 1767, 1771, 1770],\n       [2189, 2190, 2194, 2193, 1767, 1768, 1772, 1771],\n       [2194, 2195, 2196, 2193, 1772, 1773, 1774, 1771],\n       [2195, 2197, 2198, 2196, 1773, 1775, 1776, 1774],\n       [2197, 2160, 2159, 2198, 1775, 1738, 1737, 1776],\n       [2193, 2196, 2199, 2192, 1771, 1774, 1777, 1770],\n       [2196, 2198, 2200, 2199, 1774, 1776, 1778, 1777],\n       [2198, 2159, 2161, 2200, 1776, 1737, 1739, 1778],\n       [2192, 2199, 2201, 2191, 1770, 1777, 1779, 1769],\n       [2199, 2200, 2202, 2201, 1777, 1778, 1780, 1779],\n       [2200, 2161, 2162, 2202, 1778, 1739, 1740, 1780],\n       [2191, 2201, 2172, 2176, 1769, 1779, 1750, 1754],\n       [2201, 2202, 2168, 2172, 1779, 1780, 1746, 1750],\n       [2202, 2162, 2163, 2168, 1780, 1740, 1741, 1746],\n       [2125, 2203, 2204, 2126, 1703, 1781, 1782, 1704],\n       [2203, 2205, 2206, 2204, 1781, 1783, 1784, 1782],\n       [2205, 2207, 2208, 2206, 1783, 1785, 1786, 1784],\n       [2207, 2209, 2210, 2208, 1785, 1787, 1788, 1786],\n       [2209, 2211, 2212, 2210, 1787, 1789, 1790, 1788],\n       [2211, 2213, 2214, 2212, 1789, 1791, 1792, 1790],\n       [2213, 2215, 2216, 2214, 1791, 1793, 1794, 1792],\n       [2126, 2204, 2217, 2134, 1704, 1782, 1795, 1712],\n       [2204, 2206, 2218, 2217, 1782, 1784, 1796, 1795],\n       [2206, 2208, 2219, 2218, 1784, 1786, 1797, 1796],\n       [2208, 2210, 2220, 2219, 1786, 1788, 1798, 1797],\n       [2210, 2212, 2221, 2220, 1788, 1790, 1799, 1798],\n       [2212, 2214, 2222, 2221, 1790, 1792, 1800, 1799],\n       [2214, 2216, 2223, 2222, 1792, 1794, 1801, 1800],\n       [2134, 2217, 2224, 2142, 1712, 1795, 1802, 1720],\n       [2217, 2218, 2225, 2224, 1795, 1796, 1803, 1802],\n       [2218, 2219, 2226, 2225, 1796, 1797, 1804, 1803],\n       [2219, 2220, 2227, 2226, 1797, 1798, 1805, 1804],\n       [2220, 2221, 2228, 2227, 1798, 1799, 1806, 1805],\n       [2221, 2222, 2229, 2228, 1799, 1800, 1807, 1806],\n       [2222, 2223, 2230, 2229, 1800, 1801, 1808, 1807],\n       [2142, 2224, 2231, 2150, 1720, 1802, 1809, 1728],\n       [2224, 2225, 2232, 2231, 1802, 1803, 1810, 1809],\n       [2225, 2226, 2233, 2232, 1803, 1804, 1811, 1810],\n       [2226, 2227, 2234, 2233, 1804, 1805, 1812, 1811],\n       [2227, 2228, 2235, 2234, 1805, 1806, 1813, 1812],\n       [2228, 2229, 2236, 2235, 1806, 1807, 1814, 1813],\n       [2229, 2230, 2237, 2236, 1807, 1808, 1815, 1814],\n       [2150, 2231, 2238, 2158, 1728, 1809, 1816, 1736],\n       [2231, 2232, 2239, 2238, 1809, 1810, 1817, 1816],\n       [2232, 2233, 2240, 2239, 1810, 1811, 1818, 1817],\n       [2233, 2234, 2241, 2240, 1811, 1812, 1819, 1818],\n       [2234, 2235, 2242, 2241, 1812, 1813, 1820, 1819],\n       [2235, 2236, 2243, 2242, 1813, 1814, 1821, 1820],\n       [2236, 2237, 2244, 2243, 1814, 1815, 1822, 1821],\n       [2158, 2238, 2245, 2166, 1736, 1816, 1823, 1744],\n       [2238, 2239, 2246, 2245, 1816, 1817, 1824, 1823],\n       [2239, 2240, 2247, 2246, 1817, 1818, 1825, 1824],\n       [2240, 2241, 2248, 2247, 1818, 1819, 1826, 1825],\n       [2241, 2242, 2249, 2248, 1819, 1820, 1827, 1826],\n       [2242, 2243, 2250, 2249, 1820, 1821, 1828, 1827],\n       [2243, 2244, 2251, 2250, 1821, 1822, 1829, 1828],\n       [2247, 2248, 2252, 2253, 1825, 1826, 1830, 1831],\n       [2248, 2249, 2254, 2252, 1826, 1827, 1832, 1830],\n       [2249, 2250, 2255, 2254, 1827, 1828, 1833, 1832],\n       [2250, 2251, 2256, 2255, 1828, 1829, 1834, 1833],\n       [2253, 2252, 2257, 2258, 1831, 1830, 1835, 1836],\n       [2252, 2254, 2259, 2257, 1830, 1832, 1837, 1835],\n       [2254, 2255, 2260, 2259, 1832, 1833, 1838, 1837],\n       [2255, 2256, 2261, 2260, 1833, 1834, 1839, 1838],\n       [2258, 2257, 2262, 2263, 1836, 1835, 1840, 1841],\n       [2257, 2259, 2264, 2262, 1835, 1837, 1842, 1840],\n       [2259, 2260, 2265, 2264, 1837, 1838, 1843, 1842],\n       [2260, 2261, 2266, 2265, 1838, 1839, 1844, 1843],\n       [2266, 2267, 2268, 2265, 1844, 1845, 1846, 1843],\n       [2267, 2269, 2270, 2268, 1845, 1847, 1848, 1846],\n       [2269, 2185, 2183, 2270, 1847, 1763, 1761, 1848],\n       [2265, 2268, 2271, 2264, 1843, 1846, 1849, 1842],\n       [2268, 2270, 2272, 2271, 1846, 1848, 1850, 1849],\n       [2270, 2183, 2181, 2272, 1848, 1761, 1759, 1850],\n       [2264, 2271, 2273, 2262, 1842, 1849, 1851, 1840],\n       [2271, 2272, 2274, 2273, 1849, 1850, 1852, 1851],\n       [2272, 2181, 2179, 2274, 1850, 1759, 1757, 1852],\n       [2262, 2273, 2275, 2263, 1840, 1851, 1853, 1841],\n       [2273, 2274, 2276, 2275, 1851, 1852, 1854, 1853],\n       [2274, 2179, 2178, 2276, 1852, 1757, 1756, 1854],\n       [2178, 2174, 2277, 2276, 1756, 1752, 1855, 1854],\n       [2174, 2170, 2278, 2277, 1752, 1748, 1856, 1855],\n       [2170, 2166, 2245, 2278, 1748, 1744, 1823, 1856],\n       [2276, 2277, 2279, 2275, 1854, 1855, 1857, 1853],\n       [2277, 2278, 2280, 2279, 1855, 1856, 1858, 1857],\n       [2278, 2245, 2246, 2280, 1856, 1823, 1824, 1858],\n       [2275, 2279, 2258, 2263, 1853, 1857, 1836, 1841],\n       [2279, 2280, 2253, 2258, 1857, 1858, 1831, 1836],\n       [2280, 2246, 2247, 2253, 1858, 1824, 1825, 1831],\n       [2215, 2281, 2282, 2216, 1793, 1859, 1860, 1794],\n       [2281, 2283, 2284, 2282, 1859, 1861, 1862, 1860],\n       [2283, 2285, 2286, 2284, 1861, 1863, 1864, 1862],\n       [2285, 2287, 2288, 2286, 1863, 1865, 1866, 1864],\n       [2287, 2289, 2290, 2288, 1865, 1867, 1868, 1866],\n       [2289, 2291, 2292, 2290, 1867, 1869, 1870, 1868],\n       [2216, 2282, 2293, 2223, 1794, 1860, 1871, 1801],\n       [2282, 2284, 2294, 2293, 1860, 1862, 1872, 1871],\n       [2284, 2286, 2295, 2294, 1862, 1864, 1873, 1872],\n       [2286, 2288, 2296, 2295, 1864, 1866, 1874, 1873],\n       [2288, 2290, 2297, 2296, 1866, 1868, 1875, 1874],\n       [2290, 2292, 2298, 2297, 1868, 1870, 1876, 1875],\n       [2223, 2293, 2299, 2230, 1801, 1871, 1877, 1808],\n       [2293, 2294, 2300, 2299, 1871, 1872, 1878, 1877],\n       [2294, 2295, 2301, 2300, 1872, 1873, 1879, 1878],\n       [2295, 2296, 2302, 2301, 1873, 1874, 1880, 1879],\n       [2296, 2297, 2303, 2302, 1874, 1875, 1881, 1880],\n       [2297, 2298, 2304, 2303, 1875, 1876, 1882, 1881],\n       [2230, 2299, 2305, 2237, 1808, 1877, 1883, 1815],\n       [2299, 2300, 2306, 2305, 1877, 1878, 1884, 1883],\n       [2300, 2301, 2307, 2306, 1878, 1879, 1885, 1884],\n       [2301, 2302, 2308, 2307, 1879, 1880, 1886, 1885],\n       [2302, 2303, 2309, 2308, 1880, 1881, 1887, 1886],\n       [2303, 2304, 2310, 2309, 1881, 1882, 1888, 1887],\n       [2237, 2305, 2311, 2244, 1815, 1883, 1889, 1822],\n       [2305, 2306, 2312, 2311, 1883, 1884, 1890, 1889],\n       [2306, 2307, 2313, 2312, 1884, 1885, 1891, 1890],\n       [2307, 2308, 2314, 2313, 1885, 1886, 1892, 1891],\n       [2308, 2309, 2315, 2314, 1886, 1887, 1893, 1892],\n       [2309, 2310, 2316, 2315, 1887, 1888, 1894, 1893],\n       [2244, 2311, 2317, 2251, 1822, 1889, 1895, 1829],\n       [2311, 2312, 2318, 2317, 1889, 1890, 1896, 1895],\n       [2312, 2313, 2319, 2318, 1890, 1891, 1897, 1896],\n       [2313, 2314, 2320, 2319, 1891, 1892, 1898, 1897],\n       [2314, 2315, 2321, 2320, 1892, 1893, 1899, 1898],\n       [2315, 2316, 2322, 2321, 1893, 1894, 1900, 1899],\n       [2319, 2320, 2323, 2324, 1897, 1898, 1901, 1902],\n       [2320, 2321, 2325, 2323, 1898, 1899, 1903, 1901],\n       [2321, 2322, 2326, 2325, 1899, 1900, 1904, 1903],\n       [2324, 2323, 2327, 2328, 1902, 1901, 1905, 1906],\n       [2323, 2325, 2329, 2327, 1901, 1903, 1907, 1905],\n       [2325, 2326, 2330, 2329, 1903, 1904, 1908, 1907],\n       [2328, 2327, 2331, 2332, 1906, 1905, 1909, 1910],\n       [2327, 2329, 2333, 2331, 1905, 1907, 1911, 1909],\n       [2329, 2330, 2334, 2333, 1907, 1908, 1912, 1911],\n       [2334, 2335, 2336, 2333, 1912, 1913, 1914, 1911],\n       [2335, 2337, 2338, 2336, 1913, 1915, 1916, 1914],\n       [2337, 2185, 2269, 2338, 1915, 1763, 1847, 1916],\n       [2333, 2336, 2339, 2331, 1911, 1914, 1917, 1909],\n       [2336, 2338, 2340, 2339, 1914, 1916, 1918, 1917],\n       [2338, 2269, 2267, 2340, 1916, 1847, 1845, 1918],\n       [2331, 2339, 2341, 2332, 1909, 1917, 1919, 1910],\n       [2339, 2340, 2342, 2341, 1917, 1918, 1920, 1919],\n       [2340, 2267, 2266, 2342, 1918, 1845, 1844, 1920],\n       [2266, 2261, 2343, 2342, 1844, 1839, 1921, 1920],\n       [2261, 2256, 2344, 2343, 1839, 1834, 1922, 1921],\n       [2256, 2251, 2317, 2344, 1834, 1829, 1895, 1922],\n       [2342, 2343, 2345, 2341, 1920, 1921, 1923, 1919],\n       [2343, 2344, 2346, 2345, 1921, 1922, 1924, 1923],\n       [2344, 2317, 2318, 2346, 1922, 1895, 1896, 1924],\n       [2341, 2345, 2328, 2332, 1919, 1923, 1906, 1910],\n       [2345, 2346, 2324, 2328, 1923, 1924, 1902, 1906],\n       [2346, 2318, 2319, 2324, 1924, 1896, 1897, 1902],\n       [2291, 2347, 2348, 2292, 1869, 1925, 1926, 1870],\n       [2347, 2349, 2350, 2348, 1925, 1927, 1928, 1926],\n       [2349, 2351, 2352, 2350, 1927, 1929, 1930, 1928],\n       [2351, 2353, 2354, 2352, 1929, 1931, 1932, 1930],\n       [2353, 2355, 2356, 2354, 1931, 1933, 1934, 1932],\n       [2355, 2357, 2358, 2356, 1933, 1935, 1936, 1934],\n       [2292, 2348, 2359, 2298, 1870, 1926, 1937, 1876],\n       [2348, 2350, 2360, 2359, 1926, 1928, 1938, 1937],\n       [2350, 2352, 2361, 2360, 1928, 1930, 1939, 1938],\n       [2352, 2354, 2362, 2361, 1930, 1932, 1940, 1939],\n       [2354, 2356, 2363, 2362, 1932, 1934, 1941, 1940],\n       [2356, 2358, 2364, 2363, 1934, 1936, 1942, 1941],\n       [2298, 2359, 2365, 2304, 1876, 1937, 1943, 1882],\n       [2359, 2360, 2366, 2365, 1937, 1938, 1944, 1943],\n       [2360, 2361, 2367, 2366, 1938, 1939, 1945, 1944],\n       [2361, 2362, 2368, 2367, 1939, 1940, 1946, 1945],\n       [2362, 2363, 2369, 2368, 1940, 1941, 1947, 1946],\n       [2363, 2364, 2370, 2369, 1941, 1942, 1948, 1947],\n       [2304, 2365, 2371, 2310, 1882, 1943, 1949, 1888],\n       [2365, 2366, 2372, 2371, 1943, 1944, 1950, 1949],\n       [2366, 2367, 2373, 2372, 1944, 1945, 1951, 1950],\n       [2367, 2368, 2374, 2373, 1945, 1946, 1952, 1951],\n       [2368, 2369, 2375, 2374, 1946, 1947, 1953, 1952],\n       [2369, 2370, 2376, 2375, 1947, 1948, 1954, 1953],\n       [2310, 2371, 2377, 2316, 1888, 1949, 1955, 1894],\n       [2371, 2372, 2378, 2377, 1949, 1950, 1956, 1955],\n       [2372, 2373, 2379, 2378, 1950, 1951, 1957, 1956],\n       [2373, 2374, 2380, 2379, 1951, 1952, 1958, 1957],\n       [2374, 2375, 2381, 2380, 1952, 1953, 1959, 1958],\n       [2375, 2376, 2382, 2381, 1953, 1954, 1960, 1959],\n       [2316, 2377, 2383, 2322, 1894, 1955, 1961, 1900],\n       [2377, 2378, 2384, 2383, 1955, 1956, 1962, 1961],\n       [2378, 2379, 2385, 2384, 1956, 1957, 1963, 1962],\n       [2379, 2380, 2386, 2385, 1957, 1958, 1964, 1963],\n       [2380, 2381, 2387, 2386, 1958, 1959, 1965, 1964],\n       [2381, 2382, 2388, 2387, 1959, 1960, 1966, 1965],\n       [2385, 2386, 2389, 2390, 1963, 1964, 1967, 1968],\n       [2386, 2387, 2391, 2389, 1964, 1965, 1969, 1967],\n       [2387, 2388, 2392, 2391, 1965, 1966, 1970, 1969],\n       [2390, 2389, 2393, 2394, 1968, 1967, 1971, 1972],\n       [2389, 2391, 2395, 2393, 1967, 1969, 1973, 1971],\n       [2391, 2392, 2396, 2395, 1969, 1970, 1974, 1973],\n       [2394, 2393, 2397, 2398, 1972, 1971, 1975, 1976],\n       [2393, 2395, 2399, 2397, 1971, 1973, 1977, 1975],\n       [2395, 2396, 2400, 2399, 1973, 1974, 1978, 1977],\n       [2400, 2401, 2402, 2399, 1978, 1979, 1980, 1977],\n       [2401, 2403, 2404, 2402, 1979, 1981, 1982, 1980],\n       [2403, 2185, 2337, 2404, 1981, 1763, 1915, 1982],\n       [2399, 2402, 2405, 2397, 1977, 1980, 1983, 1975],\n       [2402, 2404, 2406, 2405, 1980, 1982, 1984, 1983],\n       [2404, 2337, 2335, 2406, 1982, 1915, 1913, 1984],\n       [2397, 2405, 2407, 2398, 1975, 1983, 1985, 1976],\n       [2405, 2406, 2408, 2407, 1983, 1984, 1986, 1985],\n       [2406, 2335, 2334, 2408, 1984, 1913, 1912, 1986],\n       [2334, 2330, 2409, 2408, 1912, 1908, 1987, 1986],\n       [2330, 2326, 2410, 2409, 1908, 1904, 1988, 1987],\n       [2326, 2322, 2383, 2410, 1904, 1900, 1961, 1988],\n       [2408, 2409, 2411, 2407, 1986, 1987, 1989, 1985],\n       [2409, 2410, 2412, 2411, 1987, 1988, 1990, 1989],\n       [2410, 2383, 2384, 2412, 1988, 1961, 1962, 1990],\n       [2407, 2411, 2394, 2398, 1985, 1989, 1972, 1976],\n       [2411, 2412, 2390, 2394, 1989, 1990, 1968, 1972],\n       [2412, 2384, 2385, 2390, 1990, 1962, 1963, 1968],\n       [2357, 2413, 2414, 2358, 1935, 1991, 1992, 1936],\n       [2413, 2415, 2416, 2414, 1991, 1993, 1994, 1992],\n       [2415, 2417, 2418, 2416, 1993, 1995, 1996, 1994],\n       [2417, 2419, 2420, 2418, 1995, 1997, 1998, 1996],\n       [2419, 2421, 2422, 2420, 1997, 1999, 2000, 1998],\n       [2421, 2423, 2424, 2422, 1999, 2001, 2002, 2000],\n       [2358, 2414, 2425, 2364, 1936, 1992, 2003, 1942],\n       [2414, 2416, 2426, 2425, 1992, 1994, 2004, 2003],\n       [2416, 2418, 2427, 2426, 1994, 1996, 2005, 2004],\n       [2418, 2420, 2428, 2427, 1996, 1998, 2006, 2005],\n       [2420, 2422, 2429, 2428, 1998, 2000, 2007, 2006],\n       [2422, 2424, 2430, 2429, 2000, 2002, 2008, 2007],\n       [2364, 2425, 2431, 2370, 1942, 2003, 2009, 1948],\n       [2425, 2426, 2432, 2431, 2003, 2004, 2010, 2009],\n       [2426, 2427, 2433, 2432, 2004, 2005, 2011, 2010],\n       [2427, 2428, 2434, 2433, 2005, 2006, 2012, 2011],\n       [2428, 2429, 2435, 2434, 2006, 2007, 2013, 2012],\n       [2429, 2430, 2436, 2435, 2007, 2008, 2014, 2013],\n       [2370, 2431, 2437, 2376, 1948, 2009, 2015, 1954],\n       [2431, 2432, 2438, 2437, 2009, 2010, 2016, 2015],\n       [2432, 2433, 2439, 2438, 2010, 2011, 2017, 2016],\n       [2433, 2434, 2440, 2439, 2011, 2012, 2018, 2017],\n       [2434, 2435, 2441, 2440, 2012, 2013, 2019, 2018],\n       [2435, 2436, 2442, 2441, 2013, 2014, 2020, 2019],\n       [2376, 2437, 2443, 2382, 1954, 2015, 2021, 1960],\n       [2437, 2438, 2444, 2443, 2015, 2016, 2022, 2021],\n       [2438, 2439, 2445, 2444, 2016, 2017, 2023, 2022],\n       [2439, 2440, 2446, 2445, 2017, 2018, 2024, 2023],\n       [2440, 2441, 2447, 2446, 2018, 2019, 2025, 2024],\n       [2441, 2442, 2448, 2447, 2019, 2020, 2026, 2025],\n       [2382, 2443, 2449, 2388, 1960, 2021, 2027, 1966],\n       [2443, 2444, 2450, 2449, 2021, 2022, 2028, 2027],\n       [2444, 2445, 2451, 2450, 2022, 2023, 2029, 2028],\n       [2445, 2446, 2452, 2451, 2023, 2024, 2030, 2029],\n       [2446, 2447, 2453, 2452, 2024, 2025, 2031, 2030],\n       [2447, 2448, 2454, 2453, 2025, 2026, 2032, 2031],\n       [2451, 2452, 2455, 2456, 2029, 2030, 2033, 2034],\n       [2452, 2453, 2457, 2455, 2030, 2031, 2035, 2033],\n       [2453, 2454, 2458, 2457, 2031, 2032, 2036, 2035],\n       [2456, 2455, 2459, 2460, 2034, 2033, 2037, 2038],\n       [2455, 2457, 2461, 2459, 2033, 2035, 2039, 2037],\n       [2457, 2458, 2462, 2461, 2035, 2036, 2040, 2039],\n       [2460, 2459, 2463, 2464, 2038, 2037, 2041, 2042],\n       [2459, 2461, 2465, 2463, 2037, 2039, 2043, 2041],\n       [2461, 2462, 2466, 2465, 2039, 2040, 2044, 2043],\n       [2466, 2467, 2468, 2465, 2044, 2045, 2046, 2043],\n       [2467, 2469, 2470, 2468, 2045, 2047, 2048, 2046],\n       [2469, 2185, 2403, 2470, 2047, 1763, 1981, 2048],\n       [2465, 2468, 2471, 2463, 2043, 2046, 2049, 2041],\n       [2468, 2470, 2472, 2471, 2046, 2048, 2050, 2049],\n       [2470, 2403, 2401, 2472, 2048, 1981, 1979, 2050],\n       [2463, 2471, 2473, 2464, 2041, 2049, 2051, 2042],\n       [2471, 2472, 2474, 2473, 2049, 2050, 2052, 2051],\n       [2472, 2401, 2400, 2474, 2050, 1979, 1978, 2052],\n       [2400, 2396, 2475, 2474, 1978, 1974, 2053, 2052],\n       [2396, 2392, 2476, 2475, 1974, 1970, 2054, 2053],\n       [2392, 2388, 2449, 2476, 1970, 1966, 2027, 2054],\n       [2474, 2475, 2477, 2473, 2052, 2053, 2055, 2051],\n       [2475, 2476, 2478, 2477, 2053, 2054, 2056, 2055],\n       [2476, 2449, 2450, 2478, 2054, 2027, 2028, 2056],\n       [2473, 2477, 2460, 2464, 2051, 2055, 2038, 2042],\n       [2477, 2478, 2456, 2460, 2055, 2056, 2034, 2038],\n       [2478, 2450, 2451, 2456, 2056, 2028, 2029, 2034],\n       [2423, 2479, 2480, 2424, 2001, 2057, 2058, 2002],\n       [2479, 2481, 2482, 2480, 2057, 2059, 2060, 2058],\n       [2481, 2483, 2484, 2482, 2059, 2061, 2062, 2060],\n       [2483, 2485, 2486, 2484, 2061, 2063, 2064, 2062],\n       [2485, 2487, 2488, 2486, 2063, 2065, 2066, 2064],\n       [2487, 2111, 2114, 2488, 2065, 1689, 1692, 2066],\n       [2424, 2480, 2489, 2430, 2002, 2058, 2067, 2008],\n       [2480, 2482, 2490, 2489, 2058, 2060, 2068, 2067],\n       [2482, 2484, 2491, 2490, 2060, 2062, 2069, 2068],\n       [2484, 2486, 2492, 2491, 2062, 2064, 2070, 2069],\n       [2486, 2488, 2493, 2492, 2064, 2066, 2071, 2070],\n       [2488, 2114, 2128, 2493, 2066, 1692, 1706, 2071],\n       [2430, 2489, 2494, 2436, 2008, 2067, 2072, 2014],\n       [2489, 2490, 2495, 2494, 2067, 2068, 2073, 2072],\n       [2490, 2491, 2496, 2495, 2068, 2069, 2074, 2073],\n       [2491, 2492, 2497, 2496, 2069, 2070, 2075, 2074],\n       [2492, 2493, 2498, 2497, 2070, 2071, 2076, 2075],\n       [2493, 2128, 2136, 2498, 2071, 1706, 1714, 2076],\n       [2436, 2494, 2499, 2442, 2014, 2072, 2077, 2020],\n       [2494, 2495, 2500, 2499, 2072, 2073, 2078, 2077],\n       [2495, 2496, 2501, 2500, 2073, 2074, 2079, 2078],\n       [2496, 2497, 2502, 2501, 2074, 2075, 2080, 2079],\n       [2497, 2498, 2503, 2502, 2075, 2076, 2081, 2080],\n       [2498, 2136, 2144, 2503, 2076, 1714, 1722, 2081],\n       [2442, 2499, 2504, 2448, 2020, 2077, 2082, 2026],\n       [2499, 2500, 2505, 2504, 2077, 2078, 2083, 2082],\n       [2500, 2501, 2506, 2505, 2078, 2079, 2084, 2083],\n       [2501, 2502, 2507, 2506, 2079, 2080, 2085, 2084],\n       [2502, 2503, 2508, 2507, 2080, 2081, 2086, 2085],\n       [2503, 2144, 2152, 2508, 2081, 1722, 1730, 2086],\n       [2448, 2504, 2509, 2454, 2026, 2082, 2087, 2032],\n       [2504, 2505, 2510, 2509, 2082, 2083, 2088, 2087],\n       [2505, 2506, 2511, 2510, 2083, 2084, 2089, 2088],\n       [2506, 2507, 2512, 2511, 2084, 2085, 2090, 2089],\n       [2507, 2508, 2513, 2512, 2085, 2086, 2091, 2090],\n       [2508, 2152, 2160, 2513, 2086, 1730, 1738, 2091],\n       [2511, 2512, 2514, 2515, 2089, 2090, 2092, 2093],\n       [2512, 2513, 2516, 2514, 2090, 2091, 2094, 2092],\n       [2513, 2160, 2197, 2516, 2091, 1738, 1775, 2094],\n       [2515, 2514, 2517, 2518, 2093, 2092, 2095, 2096],\n       [2514, 2516, 2519, 2517, 2092, 2094, 2097, 2095],\n       [2516, 2197, 2195, 2519, 2094, 1775, 1773, 2097],\n       [2518, 2517, 2520, 2521, 2096, 2095, 2098, 2099],\n       [2517, 2519, 2522, 2520, 2095, 2097, 2100, 2098],\n       [2519, 2195, 2194, 2522, 2097, 1773, 1772, 2100],\n       [2194, 2190, 2523, 2522, 1772, 1768, 2101, 2100],\n       [2190, 2186, 2524, 2523, 1768, 1764, 2102, 2101],\n       [2186, 2185, 2469, 2524, 1764, 1763, 2047, 2102],\n       [2522, 2523, 2525, 2520, 2100, 2101, 2103, 2098],\n       [2523, 2524, 2526, 2525, 2101, 2102, 2104, 2103],\n       [2524, 2469, 2467, 2526, 2102, 2047, 2045, 2104],\n       [2520, 2525, 2527, 2521, 2098, 2103, 2105, 2099],\n       [2525, 2526, 2528, 2527, 2103, 2104, 2106, 2105],\n       [2526, 2467, 2466, 2528, 2104, 2045, 2044, 2106],\n       [2466, 2462, 2529, 2528, 2044, 2040, 2107, 2106],\n       [2462, 2458, 2530, 2529, 2040, 2036, 2108, 2107],\n       [2458, 2454, 2509, 2530, 2036, 2032, 2087, 2108],\n       [2528, 2529, 2531, 2527, 2106, 2107, 2109, 2105],\n       [2529, 2530, 2532, 2531, 2107, 2108, 2110, 2109],\n       [2530, 2509, 2510, 2532, 2108, 2087, 2088, 2110],\n       [2527, 2531, 2518, 2521, 2105, 2109, 2096, 2099],\n       [2531, 2532, 2515, 2518, 2109, 2110, 2093, 2096],\n       [2532, 2510, 2511, 2515, 2110, 2088, 2089, 2093],\n       [2533, 2534, 2535, 2536, 2111, 2112, 2113, 2114],\n       [2534, 2537, 2538, 2535, 2112, 2115, 2116, 2113],\n       [2537, 2539, 2540, 2538, 2115, 2117, 2118, 2116],\n       [2539, 2541, 2542, 2540, 2117, 2119, 2120, 2118],\n       [2541, 2543, 2544, 2542, 2119, 2121, 2122, 2120],\n       [2543, 2545, 2546, 2544, 2121, 2123, 2124, 2122],\n       [2545, 2547, 2548, 2546, 2123, 2125, 2126, 2124],\n       [2536, 2535, 2549, 2550, 2114, 2113, 2127, 2128],\n       [2535, 2538, 2551, 2549, 2113, 2116, 2129, 2127],\n       [2538, 2540, 2552, 2551, 2116, 2118, 2130, 2129],\n       [2540, 2542, 2553, 2552, 2118, 2120, 2131, 2130],\n       [2542, 2544, 2554, 2553, 2120, 2122, 2132, 2131],\n       [2544, 2546, 2555, 2554, 2122, 2124, 2133, 2132],\n       [2546, 2548, 2556, 2555, 2124, 2126, 2134, 2133],\n       [2550, 2549, 2557, 2558, 2128, 2127, 2135, 2136],\n       [2549, 2551, 2559, 2557, 2127, 2129, 2137, 2135],\n       [2551, 2552, 2560, 2559, 2129, 2130, 2138, 2137],\n       [2552, 2553, 2561, 2560, 2130, 2131, 2139, 2138],\n       [2553, 2554, 2562, 2561, 2131, 2132, 2140, 2139],\n       [2554, 2555, 2563, 2562, 2132, 2133, 2141, 2140],\n       [2555, 2556, 2564, 2563, 2133, 2134, 2142, 2141],\n       [2558, 2557, 2565, 2566, 2136, 2135, 2143, 2144],\n       [2557, 2559, 2567, 2565, 2135, 2137, 2145, 2143],\n       [2559, 2560, 2568, 2567, 2137, 2138, 2146, 2145],\n       [2560, 2561, 2569, 2568, 2138, 2139, 2147, 2146],\n       [2561, 2562, 2570, 2569, 2139, 2140, 2148, 2147],\n       [2562, 2563, 2571, 2570, 2140, 2141, 2149, 2148],\n       [2563, 2564, 2572, 2571, 2141, 2142, 2150, 2149],\n       [2566, 2565, 2573, 2574, 2144, 2143, 2151, 2152],\n       [2565, 2567, 2575, 2573, 2143, 2145, 2153, 2151],\n       [2567, 2568, 2576, 2575, 2145, 2146, 2154, 2153],\n       [2568, 2569, 2577, 2576, 2146, 2147, 2155, 2154],\n       [2569, 2570, 2578, 2577, 2147, 2148, 2156, 2155],\n       [2570, 2571, 2579, 2578, 2148, 2149, 2157, 2156],\n       [2571, 2572, 2580, 2579, 2149, 2150, 2158, 2157],\n       [2574, 2573, 2581, 2582, 2152, 2151, 2159, 2160],\n       [2573, 2575, 2583, 2581, 2151, 2153, 2161, 2159],\n       [2575, 2576, 2584, 2583, 2153, 2154, 2162, 2161],\n       [2576, 2577, 2585, 2584, 2154, 2155, 2163, 2162],\n       [2577, 2578, 2586, 2585, 2155, 2156, 2164, 2163],\n       [2578, 2579, 2587, 2586, 2156, 2157, 2165, 2164],\n       [2579, 2580, 2588, 2587, 2157, 2158, 2166, 2165],\n       [2585, 2586, 2589, 2590, 2163, 2164, 2167, 2168],\n       [2586, 2587, 2591, 2589, 2164, 2165, 2169, 2167],\n       [2587, 2588, 2592, 2591, 2165, 2166, 2170, 2169],\n       [2590, 2589, 2593, 2594, 2168, 2167, 2171, 2172],\n       [2589, 2591, 2595, 2593, 2167, 2169, 2173, 2171],\n       [2591, 2592, 2596, 2595, 2169, 2170, 2174, 2173],\n       [2594, 2593, 2597, 2598, 2172, 2171, 2175, 2176],\n       [2593, 2595, 2599, 2597, 2171, 2173, 2177, 2175],\n       [2595, 2596, 2600, 2599, 2173, 2174, 2178, 2177],\n       [2600, 2601, 2602, 2599, 2178, 2179, 2180, 2177],\n       [2601, 2603, 2604, 2602, 2179, 2181, 2182, 2180],\n       [2603, 2605, 2606, 2604, 2181, 2183, 2184, 2182],\n       [2605, 2607, 2608, 2606, 2183, 2185, 2186, 2184],\n       [2599, 2602, 2609, 2597, 2177, 2180, 2187, 2175],\n       [2602, 2604, 2610, 2609, 2180, 2182, 2188, 2187],\n       [2604, 2606, 2611, 2610, 2182, 2184, 2189, 2188],\n       [2606, 2608, 2612, 2611, 2184, 2186, 2190, 2189],\n       [2597, 2609, 2613, 2598, 2175, 2187, 2191, 2176],\n       [2609, 2610, 2614, 2613, 2187, 2188, 2192, 2191],\n       [2610, 2611, 2615, 2614, 2188, 2189, 2193, 2192],\n       [2611, 2612, 2616, 2615, 2189, 2190, 2194, 2193],\n       [2616, 2617, 2618, 2615, 2194, 2195, 2196, 2193],\n       [2617, 2619, 2620, 2618, 2195, 2197, 2198, 2196],\n       [2619, 2582, 2581, 2620, 2197, 2160, 2159, 2198],\n       [2615, 2618, 2621, 2614, 2193, 2196, 2199, 2192],\n       [2618, 2620, 2622, 2621, 2196, 2198, 2200, 2199],\n       [2620, 2581, 2583, 2622, 2198, 2159, 2161, 2200],\n       [2614, 2621, 2623, 2613, 2192, 2199, 2201, 2191],\n       [2621, 2622, 2624, 2623, 2199, 2200, 2202, 2201],\n       [2622, 2583, 2584, 2624, 2200, 2161, 2162, 2202],\n       [2613, 2623, 2594, 2598, 2191, 2201, 2172, 2176],\n       [2623, 2624, 2590, 2594, 2201, 2202, 2168, 2172],\n       [2624, 2584, 2585, 2590, 2202, 2162, 2163, 2168],\n       [2547, 2625, 2626, 2548, 2125, 2203, 2204, 2126],\n       [2625, 2627, 2628, 2626, 2203, 2205, 2206, 2204],\n       [2627, 2629, 2630, 2628, 2205, 2207, 2208, 2206],\n       [2629, 2631, 2632, 2630, 2207, 2209, 2210, 2208],\n       [2631, 2633, 2634, 2632, 2209, 2211, 2212, 2210],\n       [2633, 2635, 2636, 2634, 2211, 2213, 2214, 2212],\n       [2635, 2637, 2638, 2636, 2213, 2215, 2216, 2214],\n       [2548, 2626, 2639, 2556, 2126, 2204, 2217, 2134],\n       [2626, 2628, 2640, 2639, 2204, 2206, 2218, 2217],\n       [2628, 2630, 2641, 2640, 2206, 2208, 2219, 2218],\n       [2630, 2632, 2642, 2641, 2208, 2210, 2220, 2219],\n       [2632, 2634, 2643, 2642, 2210, 2212, 2221, 2220],\n       [2634, 2636, 2644, 2643, 2212, 2214, 2222, 2221],\n       [2636, 2638, 2645, 2644, 2214, 2216, 2223, 2222],\n       [2556, 2639, 2646, 2564, 2134, 2217, 2224, 2142],\n       [2639, 2640, 2647, 2646, 2217, 2218, 2225, 2224],\n       [2640, 2641, 2648, 2647, 2218, 2219, 2226, 2225],\n       [2641, 2642, 2649, 2648, 2219, 2220, 2227, 2226],\n       [2642, 2643, 2650, 2649, 2220, 2221, 2228, 2227],\n       [2643, 2644, 2651, 2650, 2221, 2222, 2229, 2228],\n       [2644, 2645, 2652, 2651, 2222, 2223, 2230, 2229],\n       [2564, 2646, 2653, 2572, 2142, 2224, 2231, 2150],\n       [2646, 2647, 2654, 2653, 2224, 2225, 2232, 2231],\n       [2647, 2648, 2655, 2654, 2225, 2226, 2233, 2232],\n       [2648, 2649, 2656, 2655, 2226, 2227, 2234, 2233],\n       [2649, 2650, 2657, 2656, 2227, 2228, 2235, 2234],\n       [2650, 2651, 2658, 2657, 2228, 2229, 2236, 2235],\n       [2651, 2652, 2659, 2658, 2229, 2230, 2237, 2236],\n       [2572, 2653, 2660, 2580, 2150, 2231, 2238, 2158],\n       [2653, 2654, 2661, 2660, 2231, 2232, 2239, 2238],\n       [2654, 2655, 2662, 2661, 2232, 2233, 2240, 2239],\n       [2655, 2656, 2663, 2662, 2233, 2234, 2241, 2240],\n       [2656, 2657, 2664, 2663, 2234, 2235, 2242, 2241],\n       [2657, 2658, 2665, 2664, 2235, 2236, 2243, 2242],\n       [2658, 2659, 2666, 2665, 2236, 2237, 2244, 2243],\n       [2580, 2660, 2667, 2588, 2158, 2238, 2245, 2166],\n       [2660, 2661, 2668, 2667, 2238, 2239, 2246, 2245],\n       [2661, 2662, 2669, 2668, 2239, 2240, 2247, 2246],\n       [2662, 2663, 2670, 2669, 2240, 2241, 2248, 2247],\n       [2663, 2664, 2671, 2670, 2241, 2242, 2249, 2248],\n       [2664, 2665, 2672, 2671, 2242, 2243, 2250, 2249],\n       [2665, 2666, 2673, 2672, 2243, 2244, 2251, 2250],\n       [2669, 2670, 2674, 2675, 2247, 2248, 2252, 2253],\n       [2670, 2671, 2676, 2674, 2248, 2249, 2254, 2252],\n       [2671, 2672, 2677, 2676, 2249, 2250, 2255, 2254],\n       [2672, 2673, 2678, 2677, 2250, 2251, 2256, 2255],\n       [2675, 2674, 2679, 2680, 2253, 2252, 2257, 2258],\n       [2674, 2676, 2681, 2679, 2252, 2254, 2259, 2257],\n       [2676, 2677, 2682, 2681, 2254, 2255, 2260, 2259],\n       [2677, 2678, 2683, 2682, 2255, 2256, 2261, 2260],\n       [2680, 2679, 2684, 2685, 2258, 2257, 2262, 2263],\n       [2679, 2681, 2686, 2684, 2257, 2259, 2264, 2262],\n       [2681, 2682, 2687, 2686, 2259, 2260, 2265, 2264],\n       [2682, 2683, 2688, 2687, 2260, 2261, 2266, 2265],\n       [2688, 2689, 2690, 2687, 2266, 2267, 2268, 2265],\n       [2689, 2691, 2692, 2690, 2267, 2269, 2270, 2268],\n       [2691, 2607, 2605, 2692, 2269, 2185, 2183, 2270],\n       [2687, 2690, 2693, 2686, 2265, 2268, 2271, 2264],\n       [2690, 2692, 2694, 2693, 2268, 2270, 2272, 2271],\n       [2692, 2605, 2603, 2694, 2270, 2183, 2181, 2272],\n       [2686, 2693, 2695, 2684, 2264, 2271, 2273, 2262],\n       [2693, 2694, 2696, 2695, 2271, 2272, 2274, 2273],\n       [2694, 2603, 2601, 2696, 2272, 2181, 2179, 2274],\n       [2684, 2695, 2697, 2685, 2262, 2273, 2275, 2263],\n       [2695, 2696, 2698, 2697, 2273, 2274, 2276, 2275],\n       [2696, 2601, 2600, 2698, 2274, 2179, 2178, 2276],\n       [2600, 2596, 2699, 2698, 2178, 2174, 2277, 2276],\n       [2596, 2592, 2700, 2699, 2174, 2170, 2278, 2277],\n       [2592, 2588, 2667, 2700, 2170, 2166, 2245, 2278],\n       [2698, 2699, 2701, 2697, 2276, 2277, 2279, 2275],\n       [2699, 2700, 2702, 2701, 2277, 2278, 2280, 2279],\n       [2700, 2667, 2668, 2702, 2278, 2245, 2246, 2280],\n       [2697, 2701, 2680, 2685, 2275, 2279, 2258, 2263],\n       [2701, 2702, 2675, 2680, 2279, 2280, 2253, 2258],\n       [2702, 2668, 2669, 2675, 2280, 2246, 2247, 2253],\n       [2637, 2703, 2704, 2638, 2215, 2281, 2282, 2216],\n       [2703, 2705, 2706, 2704, 2281, 2283, 2284, 2282],\n       [2705, 2707, 2708, 2706, 2283, 2285, 2286, 2284],\n       [2707, 2709, 2710, 2708, 2285, 2287, 2288, 2286],\n       [2709, 2711, 2712, 2710, 2287, 2289, 2290, 2288],\n       [2711, 2713, 2714, 2712, 2289, 2291, 2292, 2290],\n       [2638, 2704, 2715, 2645, 2216, 2282, 2293, 2223],\n       [2704, 2706, 2716, 2715, 2282, 2284, 2294, 2293],\n       [2706, 2708, 2717, 2716, 2284, 2286, 2295, 2294],\n       [2708, 2710, 2718, 2717, 2286, 2288, 2296, 2295],\n       [2710, 2712, 2719, 2718, 2288, 2290, 2297, 2296],\n       [2712, 2714, 2720, 2719, 2290, 2292, 2298, 2297],\n       [2645, 2715, 2721, 2652, 2223, 2293, 2299, 2230],\n       [2715, 2716, 2722, 2721, 2293, 2294, 2300, 2299],\n       [2716, 2717, 2723, 2722, 2294, 2295, 2301, 2300],\n       [2717, 2718, 2724, 2723, 2295, 2296, 2302, 2301],\n       [2718, 2719, 2725, 2724, 2296, 2297, 2303, 2302],\n       [2719, 2720, 2726, 2725, 2297, 2298, 2304, 2303],\n       [2652, 2721, 2727, 2659, 2230, 2299, 2305, 2237],\n       [2721, 2722, 2728, 2727, 2299, 2300, 2306, 2305],\n       [2722, 2723, 2729, 2728, 2300, 2301, 2307, 2306],\n       [2723, 2724, 2730, 2729, 2301, 2302, 2308, 2307],\n       [2724, 2725, 2731, 2730, 2302, 2303, 2309, 2308],\n       [2725, 2726, 2732, 2731, 2303, 2304, 2310, 2309],\n       [2659, 2727, 2733, 2666, 2237, 2305, 2311, 2244],\n       [2727, 2728, 2734, 2733, 2305, 2306, 2312, 2311],\n       [2728, 2729, 2735, 2734, 2306, 2307, 2313, 2312],\n       [2729, 2730, 2736, 2735, 2307, 2308, 2314, 2313],\n       [2730, 2731, 2737, 2736, 2308, 2309, 2315, 2314],\n       [2731, 2732, 2738, 2737, 2309, 2310, 2316, 2315],\n       [2666, 2733, 2739, 2673, 2244, 2311, 2317, 2251],\n       [2733, 2734, 2740, 2739, 2311, 2312, 2318, 2317],\n       [2734, 2735, 2741, 2740, 2312, 2313, 2319, 2318],\n       [2735, 2736, 2742, 2741, 2313, 2314, 2320, 2319],\n       [2736, 2737, 2743, 2742, 2314, 2315, 2321, 2320],\n       [2737, 2738, 2744, 2743, 2315, 2316, 2322, 2321],\n       [2741, 2742, 2745, 2746, 2319, 2320, 2323, 2324],\n       [2742, 2743, 2747, 2745, 2320, 2321, 2325, 2323],\n       [2743, 2744, 2748, 2747, 2321, 2322, 2326, 2325],\n       [2746, 2745, 2749, 2750, 2324, 2323, 2327, 2328],\n       [2745, 2747, 2751, 2749, 2323, 2325, 2329, 2327],\n       [2747, 2748, 2752, 2751, 2325, 2326, 2330, 2329],\n       [2750, 2749, 2753, 2754, 2328, 2327, 2331, 2332],\n       [2749, 2751, 2755, 2753, 2327, 2329, 2333, 2331],\n       [2751, 2752, 2756, 2755, 2329, 2330, 2334, 2333],\n       [2756, 2757, 2758, 2755, 2334, 2335, 2336, 2333],\n       [2757, 2759, 2760, 2758, 2335, 2337, 2338, 2336],\n       [2759, 2607, 2691, 2760, 2337, 2185, 2269, 2338],\n       [2755, 2758, 2761, 2753, 2333, 2336, 2339, 2331],\n       [2758, 2760, 2762, 2761, 2336, 2338, 2340, 2339],\n       [2760, 2691, 2689, 2762, 2338, 2269, 2267, 2340],\n       [2753, 2761, 2763, 2754, 2331, 2339, 2341, 2332],\n       [2761, 2762, 2764, 2763, 2339, 2340, 2342, 2341],\n       [2762, 2689, 2688, 2764, 2340, 2267, 2266, 2342],\n       [2688, 2683, 2765, 2764, 2266, 2261, 2343, 2342],\n       [2683, 2678, 2766, 2765, 2261, 2256, 2344, 2343],\n       [2678, 2673, 2739, 2766, 2256, 2251, 2317, 2344],\n       [2764, 2765, 2767, 2763, 2342, 2343, 2345, 2341],\n       [2765, 2766, 2768, 2767, 2343, 2344, 2346, 2345],\n       [2766, 2739, 2740, 2768, 2344, 2317, 2318, 2346],\n       [2763, 2767, 2750, 2754, 2341, 2345, 2328, 2332],\n       [2767, 2768, 2746, 2750, 2345, 2346, 2324, 2328],\n       [2768, 2740, 2741, 2746, 2346, 2318, 2319, 2324],\n       [2713, 2769, 2770, 2714, 2291, 2347, 2348, 2292],\n       [2769, 2771, 2772, 2770, 2347, 2349, 2350, 2348],\n       [2771, 2773, 2774, 2772, 2349, 2351, 2352, 2350],\n       [2773, 2775, 2776, 2774, 2351, 2353, 2354, 2352],\n       [2775, 2777, 2778, 2776, 2353, 2355, 2356, 2354],\n       [2777, 2779, 2780, 2778, 2355, 2357, 2358, 2356],\n       [2714, 2770, 2781, 2720, 2292, 2348, 2359, 2298],\n       [2770, 2772, 2782, 2781, 2348, 2350, 2360, 2359],\n       [2772, 2774, 2783, 2782, 2350, 2352, 2361, 2360],\n       [2774, 2776, 2784, 2783, 2352, 2354, 2362, 2361],\n       [2776, 2778, 2785, 2784, 2354, 2356, 2363, 2362],\n       [2778, 2780, 2786, 2785, 2356, 2358, 2364, 2363],\n       [2720, 2781, 2787, 2726, 2298, 2359, 2365, 2304],\n       [2781, 2782, 2788, 2787, 2359, 2360, 2366, 2365],\n       [2782, 2783, 2789, 2788, 2360, 2361, 2367, 2366],\n       [2783, 2784, 2790, 2789, 2361, 2362, 2368, 2367],\n       [2784, 2785, 2791, 2790, 2362, 2363, 2369, 2368],\n       [2785, 2786, 2792, 2791, 2363, 2364, 2370, 2369],\n       [2726, 2787, 2793, 2732, 2304, 2365, 2371, 2310],\n       [2787, 2788, 2794, 2793, 2365, 2366, 2372, 2371],\n       [2788, 2789, 2795, 2794, 2366, 2367, 2373, 2372],\n       [2789, 2790, 2796, 2795, 2367, 2368, 2374, 2373],\n       [2790, 2791, 2797, 2796, 2368, 2369, 2375, 2374],\n       [2791, 2792, 2798, 2797, 2369, 2370, 2376, 2375],\n       [2732, 2793, 2799, 2738, 2310, 2371, 2377, 2316],\n       [2793, 2794, 2800, 2799, 2371, 2372, 2378, 2377],\n       [2794, 2795, 2801, 2800, 2372, 2373, 2379, 2378],\n       [2795, 2796, 2802, 2801, 2373, 2374, 2380, 2379],\n       [2796, 2797, 2803, 2802, 2374, 2375, 2381, 2380],\n       [2797, 2798, 2804, 2803, 2375, 2376, 2382, 2381],\n       [2738, 2799, 2805, 2744, 2316, 2377, 2383, 2322],\n       [2799, 2800, 2806, 2805, 2377, 2378, 2384, 2383],\n       [2800, 2801, 2807, 2806, 2378, 2379, 2385, 2384],\n       [2801, 2802, 2808, 2807, 2379, 2380, 2386, 2385],\n       [2802, 2803, 2809, 2808, 2380, 2381, 2387, 2386],\n       [2803, 2804, 2810, 2809, 2381, 2382, 2388, 2387],\n       [2807, 2808, 2811, 2812, 2385, 2386, 2389, 2390],\n       [2808, 2809, 2813, 2811, 2386, 2387, 2391, 2389],\n       [2809, 2810, 2814, 2813, 2387, 2388, 2392, 2391],\n       [2812, 2811, 2815, 2816, 2390, 2389, 2393, 2394],\n       [2811, 2813, 2817, 2815, 2389, 2391, 2395, 2393],\n       [2813, 2814, 2818, 2817, 2391, 2392, 2396, 2395],\n       [2816, 2815, 2819, 2820, 2394, 2393, 2397, 2398],\n       [2815, 2817, 2821, 2819, 2393, 2395, 2399, 2397],\n       [2817, 2818, 2822, 2821, 2395, 2396, 2400, 2399],\n       [2822, 2823, 2824, 2821, 2400, 2401, 2402, 2399],\n       [2823, 2825, 2826, 2824, 2401, 2403, 2404, 2402],\n       [2825, 2607, 2759, 2826, 2403, 2185, 2337, 2404],\n       [2821, 2824, 2827, 2819, 2399, 2402, 2405, 2397],\n       [2824, 2826, 2828, 2827, 2402, 2404, 2406, 2405],\n       [2826, 2759, 2757, 2828, 2404, 2337, 2335, 2406],\n       [2819, 2827, 2829, 2820, 2397, 2405, 2407, 2398],\n       [2827, 2828, 2830, 2829, 2405, 2406, 2408, 2407],\n       [2828, 2757, 2756, 2830, 2406, 2335, 2334, 2408],\n       [2756, 2752, 2831, 2830, 2334, 2330, 2409, 2408],\n       [2752, 2748, 2832, 2831, 2330, 2326, 2410, 2409],\n       [2748, 2744, 2805, 2832, 2326, 2322, 2383, 2410],\n       [2830, 2831, 2833, 2829, 2408, 2409, 2411, 2407],\n       [2831, 2832, 2834, 2833, 2409, 2410, 2412, 2411],\n       [2832, 2805, 2806, 2834, 2410, 2383, 2384, 2412],\n       [2829, 2833, 2816, 2820, 2407, 2411, 2394, 2398],\n       [2833, 2834, 2812, 2816, 2411, 2412, 2390, 2394],\n       [2834, 2806, 2807, 2812, 2412, 2384, 2385, 2390],\n       [2779, 2835, 2836, 2780, 2357, 2413, 2414, 2358],\n       [2835, 2837, 2838, 2836, 2413, 2415, 2416, 2414],\n       [2837, 2839, 2840, 2838, 2415, 2417, 2418, 2416],\n       [2839, 2841, 2842, 2840, 2417, 2419, 2420, 2418],\n       [2841, 2843, 2844, 2842, 2419, 2421, 2422, 2420],\n       [2843, 2845, 2846, 2844, 2421, 2423, 2424, 2422],\n       [2780, 2836, 2847, 2786, 2358, 2414, 2425, 2364],\n       [2836, 2838, 2848, 2847, 2414, 2416, 2426, 2425],\n       [2838, 2840, 2849, 2848, 2416, 2418, 2427, 2426],\n       [2840, 2842, 2850, 2849, 2418, 2420, 2428, 2427],\n       [2842, 2844, 2851, 2850, 2420, 2422, 2429, 2428],\n       [2844, 2846, 2852, 2851, 2422, 2424, 2430, 2429],\n       [2786, 2847, 2853, 2792, 2364, 2425, 2431, 2370],\n       [2847, 2848, 2854, 2853, 2425, 2426, 2432, 2431],\n       [2848, 2849, 2855, 2854, 2426, 2427, 2433, 2432],\n       [2849, 2850, 2856, 2855, 2427, 2428, 2434, 2433],\n       [2850, 2851, 2857, 2856, 2428, 2429, 2435, 2434],\n       [2851, 2852, 2858, 2857, 2429, 2430, 2436, 2435],\n       [2792, 2853, 2859, 2798, 2370, 2431, 2437, 2376],\n       [2853, 2854, 2860, 2859, 2431, 2432, 2438, 2437],\n       [2854, 2855, 2861, 2860, 2432, 2433, 2439, 2438],\n       [2855, 2856, 2862, 2861, 2433, 2434, 2440, 2439],\n       [2856, 2857, 2863, 2862, 2434, 2435, 2441, 2440],\n       [2857, 2858, 2864, 2863, 2435, 2436, 2442, 2441],\n       [2798, 2859, 2865, 2804, 2376, 2437, 2443, 2382],\n       [2859, 2860, 2866, 2865, 2437, 2438, 2444, 2443],\n       [2860, 2861, 2867, 2866, 2438, 2439, 2445, 2444],\n       [2861, 2862, 2868, 2867, 2439, 2440, 2446, 2445],\n       [2862, 2863, 2869, 2868, 2440, 2441, 2447, 2446],\n       [2863, 2864, 2870, 2869, 2441, 2442, 2448, 2447],\n       [2804, 2865, 2871, 2810, 2382, 2443, 2449, 2388],\n       [2865, 2866, 2872, 2871, 2443, 2444, 2450, 2449],\n       [2866, 2867, 2873, 2872, 2444, 2445, 2451, 2450],\n       [2867, 2868, 2874, 2873, 2445, 2446, 2452, 2451],\n       [2868, 2869, 2875, 2874, 2446, 2447, 2453, 2452],\n       [2869, 2870, 2876, 2875, 2447, 2448, 2454, 2453],\n       [2873, 2874, 2877, 2878, 2451, 2452, 2455, 2456],\n       [2874, 2875, 2879, 2877, 2452, 2453, 2457, 2455],\n       [2875, 2876, 2880, 2879, 2453, 2454, 2458, 2457],\n       [2878, 2877, 2881, 2882, 2456, 2455, 2459, 2460],\n       [2877, 2879, 2883, 2881, 2455, 2457, 2461, 2459],\n       [2879, 2880, 2884, 2883, 2457, 2458, 2462, 2461],\n       [2882, 2881, 2885, 2886, 2460, 2459, 2463, 2464],\n       [2881, 2883, 2887, 2885, 2459, 2461, 2465, 2463],\n       [2883, 2884, 2888, 2887, 2461, 2462, 2466, 2465],\n       [2888, 2889, 2890, 2887, 2466, 2467, 2468, 2465],\n       [2889, 2891, 2892, 2890, 2467, 2469, 2470, 2468],\n       [2891, 2607, 2825, 2892, 2469, 2185, 2403, 2470],\n       [2887, 2890, 2893, 2885, 2465, 2468, 2471, 2463],\n       [2890, 2892, 2894, 2893, 2468, 2470, 2472, 2471],\n       [2892, 2825, 2823, 2894, 2470, 2403, 2401, 2472],\n       [2885, 2893, 2895, 2886, 2463, 2471, 2473, 2464],\n       [2893, 2894, 2896, 2895, 2471, 2472, 2474, 2473],\n       [2894, 2823, 2822, 2896, 2472, 2401, 2400, 2474],\n       [2822, 2818, 2897, 2896, 2400, 2396, 2475, 2474],\n       [2818, 2814, 2898, 2897, 2396, 2392, 2476, 2475],\n       [2814, 2810, 2871, 2898, 2392, 2388, 2449, 2476],\n       [2896, 2897, 2899, 2895, 2474, 2475, 2477, 2473],\n       [2897, 2898, 2900, 2899, 2475, 2476, 2478, 2477],\n       [2898, 2871, 2872, 2900, 2476, 2449, 2450, 2478],\n       [2895, 2899, 2882, 2886, 2473, 2477, 2460, 2464],\n       [2899, 2900, 2878, 2882, 2477, 2478, 2456, 2460],\n       [2900, 2872, 2873, 2878, 2478, 2450, 2451, 2456],\n       [2845, 2901, 2902, 2846, 2423, 2479, 2480, 2424],\n       [2901, 2903, 2904, 2902, 2479, 2481, 2482, 2480],\n       [2903, 2905, 2906, 2904, 2481, 2483, 2484, 2482],\n       [2905, 2907, 2908, 2906, 2483, 2485, 2486, 2484],\n       [2907, 2909, 2910, 2908, 2485, 2487, 2488, 2486],\n       [2909, 2533, 2536, 2910, 2487, 2111, 2114, 2488],\n       [2846, 2902, 2911, 2852, 2424, 2480, 2489, 2430],\n       [2902, 2904, 2912, 2911, 2480, 2482, 2490, 2489],\n       [2904, 2906, 2913, 2912, 2482, 2484, 2491, 2490],\n       [2906, 2908, 2914, 2913, 2484, 2486, 2492, 2491],\n       [2908, 2910, 2915, 2914, 2486, 2488, 2493, 2492],\n       [2910, 2536, 2550, 2915, 2488, 2114, 2128, 2493],\n       [2852, 2911, 2916, 2858, 2430, 2489, 2494, 2436],\n       [2911, 2912, 2917, 2916, 2489, 2490, 2495, 2494],\n       [2912, 2913, 2918, 2917, 2490, 2491, 2496, 2495],\n       [2913, 2914, 2919, 2918, 2491, 2492, 2497, 2496],\n       [2914, 2915, 2920, 2919, 2492, 2493, 2498, 2497],\n       [2915, 2550, 2558, 2920, 2493, 2128, 2136, 2498],\n       [2858, 2916, 2921, 2864, 2436, 2494, 2499, 2442],\n       [2916, 2917, 2922, 2921, 2494, 2495, 2500, 2499],\n       [2917, 2918, 2923, 2922, 2495, 2496, 2501, 2500],\n       [2918, 2919, 2924, 2923, 2496, 2497, 2502, 2501],\n       [2919, 2920, 2925, 2924, 2497, 2498, 2503, 2502],\n       [2920, 2558, 2566, 2925, 2498, 2136, 2144, 2503],\n       [2864, 2921, 2926, 2870, 2442, 2499, 2504, 2448],\n       [2921, 2922, 2927, 2926, 2499, 2500, 2505, 2504],\n       [2922, 2923, 2928, 2927, 2500, 2501, 2506, 2505],\n       [2923, 2924, 2929, 2928, 2501, 2502, 2507, 2506],\n       [2924, 2925, 2930, 2929, 2502, 2503, 2508, 2507],\n       [2925, 2566, 2574, 2930, 2503, 2144, 2152, 2508],\n       [2870, 2926, 2931, 2876, 2448, 2504, 2509, 2454],\n       [2926, 2927, 2932, 2931, 2504, 2505, 2510, 2509],\n       [2927, 2928, 2933, 2932, 2505, 2506, 2511, 2510],\n       [2928, 2929, 2934, 2933, 2506, 2507, 2512, 2511],\n       [2929, 2930, 2935, 2934, 2507, 2508, 2513, 2512],\n       [2930, 2574, 2582, 2935, 2508, 2152, 2160, 2513],\n       [2933, 2934, 2936, 2937, 2511, 2512, 2514, 2515],\n       [2934, 2935, 2938, 2936, 2512, 2513, 2516, 2514],\n       [2935, 2582, 2619, 2938, 2513, 2160, 2197, 2516],\n       [2937, 2936, 2939, 2940, 2515, 2514, 2517, 2518],\n       [2936, 2938, 2941, 2939, 2514, 2516, 2519, 2517],\n       [2938, 2619, 2617, 2941, 2516, 2197, 2195, 2519],\n       [2940, 2939, 2942, 2943, 2518, 2517, 2520, 2521],\n       [2939, 2941, 2944, 2942, 2517, 2519, 2522, 2520],\n       [2941, 2617, 2616, 2944, 2519, 2195, 2194, 2522],\n       [2616, 2612, 2945, 2944, 2194, 2190, 2523, 2522],\n       [2612, 2608, 2946, 2945, 2190, 2186, 2524, 2523],\n       [2608, 2607, 2891, 2946, 2186, 2185, 2469, 2524],\n       [2944, 2945, 2947, 2942, 2522, 2523, 2525, 2520],\n       [2945, 2946, 2948, 2947, 2523, 2524, 2526, 2525],\n       [2946, 2891, 2889, 2948, 2524, 2469, 2467, 2526],\n       [2942, 2947, 2949, 2943, 2520, 2525, 2527, 2521],\n       [2947, 2948, 2950, 2949, 2525, 2526, 2528, 2527],\n       [2948, 2889, 2888, 2950, 2526, 2467, 2466, 2528],\n       [2888, 2884, 2951, 2950, 2466, 2462, 2529, 2528],\n       [2884, 2880, 2952, 2951, 2462, 2458, 2530, 2529],\n       [2880, 2876, 2931, 2952, 2458, 2454, 2509, 2530],\n       [2950, 2951, 2953, 2949, 2528, 2529, 2531, 2527],\n       [2951, 2952, 2954, 2953, 2529, 2530, 2532, 2531],\n       [2952, 2931, 2932, 2954, 2530, 2509, 2510, 2532],\n       [2949, 2953, 2940, 2943, 2527, 2531, 2518, 2521],\n       [2953, 2954, 2937, 2940, 2531, 2532, 2515, 2518],\n       [2954, 2932, 2933, 2937, 2532, 2510, 2511, 2515],\n       [2955, 2956, 2957, 2958, 2533, 2534, 2535, 2536],\n       [2956, 2959, 2960, 2957, 2534, 2537, 2538, 2535],\n       [2959, 2961, 2962, 2960, 2537, 2539, 2540, 2538],\n       [2961, 2963, 2964, 2962, 2539, 2541, 2542, 2540],\n       [2963, 2965, 2966, 2964, 2541, 2543, 2544, 2542],\n       [2965, 2967, 2968, 2966, 2543, 2545, 2546, 2544],\n       [2967, 2969, 2970, 2968, 2545, 2547, 2548, 2546],\n       [2958, 2957, 2971, 2972, 2536, 2535, 2549, 2550],\n       [2957, 2960, 2973, 2971, 2535, 2538, 2551, 2549],\n       [2960, 2962, 2974, 2973, 2538, 2540, 2552, 2551],\n       [2962, 2964, 2975, 2974, 2540, 2542, 2553, 2552],\n       [2964, 2966, 2976, 2975, 2542, 2544, 2554, 2553],\n       [2966, 2968, 2977, 2976, 2544, 2546, 2555, 2554],\n       [2968, 2970, 2978, 2977, 2546, 2548, 2556, 2555],\n       [2972, 2971, 2979, 2980, 2550, 2549, 2557, 2558],\n       [2971, 2973, 2981, 2979, 2549, 2551, 2559, 2557],\n       [2973, 2974, 2982, 2981, 2551, 2552, 2560, 2559],\n       [2974, 2975, 2983, 2982, 2552, 2553, 2561, 2560],\n       [2975, 2976, 2984, 2983, 2553, 2554, 2562, 2561],\n       [2976, 2977, 2985, 2984, 2554, 2555, 2563, 2562],\n       [2977, 2978, 2986, 2985, 2555, 2556, 2564, 2563],\n       [2980, 2979, 2987, 2988, 2558, 2557, 2565, 2566],\n       [2979, 2981, 2989, 2987, 2557, 2559, 2567, 2565],\n       [2981, 2982, 2990, 2989, 2559, 2560, 2568, 2567],\n       [2982, 2983, 2991, 2990, 2560, 2561, 2569, 2568],\n       [2983, 2984, 2992, 2991, 2561, 2562, 2570, 2569],\n       [2984, 2985, 2993, 2992, 2562, 2563, 2571, 2570],\n       [2985, 2986, 2994, 2993, 2563, 2564, 2572, 2571],\n       [2988, 2987, 2995, 2996, 2566, 2565, 2573, 2574],\n       [2987, 2989, 2997, 2995, 2565, 2567, 2575, 2573],\n       [2989, 2990, 2998, 2997, 2567, 2568, 2576, 2575],\n       [2990, 2991, 2999, 2998, 2568, 2569, 2577, 2576],\n       [2991, 2992, 3000, 2999, 2569, 2570, 2578, 2577],\n       [2992, 2993, 3001, 3000, 2570, 2571, 2579, 2578],\n       [2993, 2994, 3002, 3001, 2571, 2572, 2580, 2579],\n       [2996, 2995, 3003, 3004, 2574, 2573, 2581, 2582],\n       [2995, 2997, 3005, 3003, 2573, 2575, 2583, 2581],\n       [2997, 2998, 3006, 3005, 2575, 2576, 2584, 2583],\n       [2998, 2999, 3007, 3006, 2576, 2577, 2585, 2584],\n       [2999, 3000, 3008, 3007, 2577, 2578, 2586, 2585],\n       [3000, 3001, 3009, 3008, 2578, 2579, 2587, 2586],\n       [3001, 3002, 3010, 3009, 2579, 2580, 2588, 2587],\n       [3007, 3008, 3011, 3012, 2585, 2586, 2589, 2590],\n       [3008, 3009, 3013, 3011, 2586, 2587, 2591, 2589],\n       [3009, 3010, 3014, 3013, 2587, 2588, 2592, 2591],\n       [3012, 3011, 3015, 3016, 2590, 2589, 2593, 2594],\n       [3011, 3013, 3017, 3015, 2589, 2591, 2595, 2593],\n       [3013, 3014, 3018, 3017, 2591, 2592, 2596, 2595],\n       [3016, 3015, 3019, 3020, 2594, 2593, 2597, 2598],\n       [3015, 3017, 3021, 3019, 2593, 2595, 2599, 2597],\n       [3017, 3018, 3022, 3021, 2595, 2596, 2600, 2599],\n       [3022, 3023, 3024, 3021, 2600, 2601, 2602, 2599],\n       [3023, 3025, 3026, 3024, 2601, 2603, 2604, 2602],\n       [3025, 3027, 3028, 3026, 2603, 2605, 2606, 2604],\n       [3027, 3029, 3030, 3028, 2605, 2607, 2608, 2606],\n       [3021, 3024, 3031, 3019, 2599, 2602, 2609, 2597],\n       [3024, 3026, 3032, 3031, 2602, 2604, 2610, 2609],\n       [3026, 3028, 3033, 3032, 2604, 2606, 2611, 2610],\n       [3028, 3030, 3034, 3033, 2606, 2608, 2612, 2611],\n       [3019, 3031, 3035, 3020, 2597, 2609, 2613, 2598],\n       [3031, 3032, 3036, 3035, 2609, 2610, 2614, 2613],\n       [3032, 3033, 3037, 3036, 2610, 2611, 2615, 2614],\n       [3033, 3034, 3038, 3037, 2611, 2612, 2616, 2615],\n       [3038, 3039, 3040, 3037, 2616, 2617, 2618, 2615],\n       [3039, 3041, 3042, 3040, 2617, 2619, 2620, 2618],\n       [3041, 3004, 3003, 3042, 2619, 2582, 2581, 2620],\n       [3037, 3040, 3043, 3036, 2615, 2618, 2621, 2614],\n       [3040, 3042, 3044, 3043, 2618, 2620, 2622, 2621],\n       [3042, 3003, 3005, 3044, 2620, 2581, 2583, 2622],\n       [3036, 3043, 3045, 3035, 2614, 2621, 2623, 2613],\n       [3043, 3044, 3046, 3045, 2621, 2622, 2624, 2623],\n       [3044, 3005, 3006, 3046, 2622, 2583, 2584, 2624],\n       [3035, 3045, 3016, 3020, 2613, 2623, 2594, 2598],\n       [3045, 3046, 3012, 3016, 2623, 2624, 2590, 2594],\n       [3046, 3006, 3007, 3012, 2624, 2584, 2585, 2590],\n       [2969, 3047, 3048, 2970, 2547, 2625, 2626, 2548],\n       [3047, 3049, 3050, 3048, 2625, 2627, 2628, 2626],\n       [3049, 3051, 3052, 3050, 2627, 2629, 2630, 2628],\n       [3051, 3053, 3054, 3052, 2629, 2631, 2632, 2630],\n       [3053, 3055, 3056, 3054, 2631, 2633, 2634, 2632],\n       [3055, 3057, 3058, 3056, 2633, 2635, 2636, 2634],\n       [3057, 3059, 3060, 3058, 2635, 2637, 2638, 2636],\n       [2970, 3048, 3061, 2978, 2548, 2626, 2639, 2556],\n       [3048, 3050, 3062, 3061, 2626, 2628, 2640, 2639],\n       [3050, 3052, 3063, 3062, 2628, 2630, 2641, 2640],\n       [3052, 3054, 3064, 3063, 2630, 2632, 2642, 2641],\n       [3054, 3056, 3065, 3064, 2632, 2634, 2643, 2642],\n       [3056, 3058, 3066, 3065, 2634, 2636, 2644, 2643],\n       [3058, 3060, 3067, 3066, 2636, 2638, 2645, 2644],\n       [2978, 3061, 3068, 2986, 2556, 2639, 2646, 2564],\n       [3061, 3062, 3069, 3068, 2639, 2640, 2647, 2646],\n       [3062, 3063, 3070, 3069, 2640, 2641, 2648, 2647],\n       [3063, 3064, 3071, 3070, 2641, 2642, 2649, 2648],\n       [3064, 3065, 3072, 3071, 2642, 2643, 2650, 2649],\n       [3065, 3066, 3073, 3072, 2643, 2644, 2651, 2650],\n       [3066, 3067, 3074, 3073, 2644, 2645, 2652, 2651],\n       [2986, 3068, 3075, 2994, 2564, 2646, 2653, 2572],\n       [3068, 3069, 3076, 3075, 2646, 2647, 2654, 2653],\n       [3069, 3070, 3077, 3076, 2647, 2648, 2655, 2654],\n       [3070, 3071, 3078, 3077, 2648, 2649, 2656, 2655],\n       [3071, 3072, 3079, 3078, 2649, 2650, 2657, 2656],\n       [3072, 3073, 3080, 3079, 2650, 2651, 2658, 2657],\n       [3073, 3074, 3081, 3080, 2651, 2652, 2659, 2658],\n       [2994, 3075, 3082, 3002, 2572, 2653, 2660, 2580],\n       [3075, 3076, 3083, 3082, 2653, 2654, 2661, 2660],\n       [3076, 3077, 3084, 3083, 2654, 2655, 2662, 2661],\n       [3077, 3078, 3085, 3084, 2655, 2656, 2663, 2662],\n       [3078, 3079, 3086, 3085, 2656, 2657, 2664, 2663],\n       [3079, 3080, 3087, 3086, 2657, 2658, 2665, 2664],\n       [3080, 3081, 3088, 3087, 2658, 2659, 2666, 2665],\n       [3002, 3082, 3089, 3010, 2580, 2660, 2667, 2588],\n       [3082, 3083, 3090, 3089, 2660, 2661, 2668, 2667],\n       [3083, 3084, 3091, 3090, 2661, 2662, 2669, 2668],\n       [3084, 3085, 3092, 3091, 2662, 2663, 2670, 2669],\n       [3085, 3086, 3093, 3092, 2663, 2664, 2671, 2670],\n       [3086, 3087, 3094, 3093, 2664, 2665, 2672, 2671],\n       [3087, 3088, 3095, 3094, 2665, 2666, 2673, 2672],\n       [3091, 3092, 3096, 3097, 2669, 2670, 2674, 2675],\n       [3092, 3093, 3098, 3096, 2670, 2671, 2676, 2674],\n       [3093, 3094, 3099, 3098, 2671, 2672, 2677, 2676],\n       [3094, 3095, 3100, 3099, 2672, 2673, 2678, 2677],\n       [3097, 3096, 3101, 3102, 2675, 2674, 2679, 2680],\n       [3096, 3098, 3103, 3101, 2674, 2676, 2681, 2679],\n       [3098, 3099, 3104, 3103, 2676, 2677, 2682, 2681],\n       [3099, 3100, 3105, 3104, 2677, 2678, 2683, 2682],\n       [3102, 3101, 3106, 3107, 2680, 2679, 2684, 2685],\n       [3101, 3103, 3108, 3106, 2679, 2681, 2686, 2684],\n       [3103, 3104, 3109, 3108, 2681, 2682, 2687, 2686],\n       [3104, 3105, 3110, 3109, 2682, 2683, 2688, 2687],\n       [3110, 3111, 3112, 3109, 2688, 2689, 2690, 2687],\n       [3111, 3113, 3114, 3112, 2689, 2691, 2692, 2690],\n       [3113, 3029, 3027, 3114, 2691, 2607, 2605, 2692],\n       [3109, 3112, 3115, 3108, 2687, 2690, 2693, 2686],\n       [3112, 3114, 3116, 3115, 2690, 2692, 2694, 2693],\n       [3114, 3027, 3025, 3116, 2692, 2605, 2603, 2694],\n       [3108, 3115, 3117, 3106, 2686, 2693, 2695, 2684],\n       [3115, 3116, 3118, 3117, 2693, 2694, 2696, 2695],\n       [3116, 3025, 3023, 3118, 2694, 2603, 2601, 2696],\n       [3106, 3117, 3119, 3107, 2684, 2695, 2697, 2685],\n       [3117, 3118, 3120, 3119, 2695, 2696, 2698, 2697],\n       [3118, 3023, 3022, 3120, 2696, 2601, 2600, 2698],\n       [3022, 3018, 3121, 3120, 2600, 2596, 2699, 2698],\n       [3018, 3014, 3122, 3121, 2596, 2592, 2700, 2699],\n       [3014, 3010, 3089, 3122, 2592, 2588, 2667, 2700],\n       [3120, 3121, 3123, 3119, 2698, 2699, 2701, 2697],\n       [3121, 3122, 3124, 3123, 2699, 2700, 2702, 2701],\n       [3122, 3089, 3090, 3124, 2700, 2667, 2668, 2702],\n       [3119, 3123, 3102, 3107, 2697, 2701, 2680, 2685],\n       [3123, 3124, 3097, 3102, 2701, 2702, 2675, 2680],\n       [3124, 3090, 3091, 3097, 2702, 2668, 2669, 2675],\n       [3059, 3125, 3126, 3060, 2637, 2703, 2704, 2638],\n       [3125, 3127, 3128, 3126, 2703, 2705, 2706, 2704],\n       [3127, 3129, 3130, 3128, 2705, 2707, 2708, 2706],\n       [3129, 3131, 3132, 3130, 2707, 2709, 2710, 2708],\n       [3131, 3133, 3134, 3132, 2709, 2711, 2712, 2710],\n       [3133, 3135, 3136, 3134, 2711, 2713, 2714, 2712],\n       [3060, 3126, 3137, 3067, 2638, 2704, 2715, 2645],\n       [3126, 3128, 3138, 3137, 2704, 2706, 2716, 2715],\n       [3128, 3130, 3139, 3138, 2706, 2708, 2717, 2716],\n       [3130, 3132, 3140, 3139, 2708, 2710, 2718, 2717],\n       [3132, 3134, 3141, 3140, 2710, 2712, 2719, 2718],\n       [3134, 3136, 3142, 3141, 2712, 2714, 2720, 2719],\n       [3067, 3137, 3143, 3074, 2645, 2715, 2721, 2652],\n       [3137, 3138, 3144, 3143, 2715, 2716, 2722, 2721],\n       [3138, 3139, 3145, 3144, 2716, 2717, 2723, 2722],\n       [3139, 3140, 3146, 3145, 2717, 2718, 2724, 2723],\n       [3140, 3141, 3147, 3146, 2718, 2719, 2725, 2724],\n       [3141, 3142, 3148, 3147, 2719, 2720, 2726, 2725],\n       [3074, 3143, 3149, 3081, 2652, 2721, 2727, 2659],\n       [3143, 3144, 3150, 3149, 2721, 2722, 2728, 2727],\n       [3144, 3145, 3151, 3150, 2722, 2723, 2729, 2728],\n       [3145, 3146, 3152, 3151, 2723, 2724, 2730, 2729],\n       [3146, 3147, 3153, 3152, 2724, 2725, 2731, 2730],\n       [3147, 3148, 3154, 3153, 2725, 2726, 2732, 2731],\n       [3081, 3149, 3155, 3088, 2659, 2727, 2733, 2666],\n       [3149, 3150, 3156, 3155, 2727, 2728, 2734, 2733],\n       [3150, 3151, 3157, 3156, 2728, 2729, 2735, 2734],\n       [3151, 3152, 3158, 3157, 2729, 2730, 2736, 2735],\n       [3152, 3153, 3159, 3158, 2730, 2731, 2737, 2736],\n       [3153, 3154, 3160, 3159, 2731, 2732, 2738, 2737],\n       [3088, 3155, 3161, 3095, 2666, 2733, 2739, 2673],\n       [3155, 3156, 3162, 3161, 2733, 2734, 2740, 2739],\n       [3156, 3157, 3163, 3162, 2734, 2735, 2741, 2740],\n       [3157, 3158, 3164, 3163, 2735, 2736, 2742, 2741],\n       [3158, 3159, 3165, 3164, 2736, 2737, 2743, 2742],\n       [3159, 3160, 3166, 3165, 2737, 2738, 2744, 2743],\n       [3163, 3164, 3167, 3168, 2741, 2742, 2745, 2746],\n       [3164, 3165, 3169, 3167, 2742, 2743, 2747, 2745],\n       [3165, 3166, 3170, 3169, 2743, 2744, 2748, 2747],\n       [3168, 3167, 3171, 3172, 2746, 2745, 2749, 2750],\n       [3167, 3169, 3173, 3171, 2745, 2747, 2751, 2749],\n       [3169, 3170, 3174, 3173, 2747, 2748, 2752, 2751],\n       [3172, 3171, 3175, 3176, 2750, 2749, 2753, 2754],\n       [3171, 3173, 3177, 3175, 2749, 2751, 2755, 2753],\n       [3173, 3174, 3178, 3177, 2751, 2752, 2756, 2755],\n       [3178, 3179, 3180, 3177, 2756, 2757, 2758, 2755],\n       [3179, 3181, 3182, 3180, 2757, 2759, 2760, 2758],\n       [3181, 3029, 3113, 3182, 2759, 2607, 2691, 2760],\n       [3177, 3180, 3183, 3175, 2755, 2758, 2761, 2753],\n       [3180, 3182, 3184, 3183, 2758, 2760, 2762, 2761],\n       [3182, 3113, 3111, 3184, 2760, 2691, 2689, 2762],\n       [3175, 3183, 3185, 3176, 2753, 2761, 2763, 2754],\n       [3183, 3184, 3186, 3185, 2761, 2762, 2764, 2763],\n       [3184, 3111, 3110, 3186, 2762, 2689, 2688, 2764],\n       [3110, 3105, 3187, 3186, 2688, 2683, 2765, 2764],\n       [3105, 3100, 3188, 3187, 2683, 2678, 2766, 2765],\n       [3100, 3095, 3161, 3188, 2678, 2673, 2739, 2766],\n       [3186, 3187, 3189, 3185, 2764, 2765, 2767, 2763],\n       [3187, 3188, 3190, 3189, 2765, 2766, 2768, 2767],\n       [3188, 3161, 3162, 3190, 2766, 2739, 2740, 2768],\n       [3185, 3189, 3172, 3176, 2763, 2767, 2750, 2754],\n       [3189, 3190, 3168, 3172, 2767, 2768, 2746, 2750],\n       [3190, 3162, 3163, 3168, 2768, 2740, 2741, 2746],\n       [3135, 3191, 3192, 3136, 2713, 2769, 2770, 2714],\n       [3191, 3193, 3194, 3192, 2769, 2771, 2772, 2770],\n       [3193, 3195, 3196, 3194, 2771, 2773, 2774, 2772],\n       [3195, 3197, 3198, 3196, 2773, 2775, 2776, 2774],\n       [3197, 3199, 3200, 3198, 2775, 2777, 2778, 2776],\n       [3199, 3201, 3202, 3200, 2777, 2779, 2780, 2778],\n       [3136, 3192, 3203, 3142, 2714, 2770, 2781, 2720],\n       [3192, 3194, 3204, 3203, 2770, 2772, 2782, 2781],\n       [3194, 3196, 3205, 3204, 2772, 2774, 2783, 2782],\n       [3196, 3198, 3206, 3205, 2774, 2776, 2784, 2783],\n       [3198, 3200, 3207, 3206, 2776, 2778, 2785, 2784],\n       [3200, 3202, 3208, 3207, 2778, 2780, 2786, 2785],\n       [3142, 3203, 3209, 3148, 2720, 2781, 2787, 2726],\n       [3203, 3204, 3210, 3209, 2781, 2782, 2788, 2787],\n       [3204, 3205, 3211, 3210, 2782, 2783, 2789, 2788],\n       [3205, 3206, 3212, 3211, 2783, 2784, 2790, 2789],\n       [3206, 3207, 3213, 3212, 2784, 2785, 2791, 2790],\n       [3207, 3208, 3214, 3213, 2785, 2786, 2792, 2791],\n       [3148, 3209, 3215, 3154, 2726, 2787, 2793, 2732],\n       [3209, 3210, 3216, 3215, 2787, 2788, 2794, 2793],\n       [3210, 3211, 3217, 3216, 2788, 2789, 2795, 2794],\n       [3211, 3212, 3218, 3217, 2789, 2790, 2796, 2795],\n       [3212, 3213, 3219, 3218, 2790, 2791, 2797, 2796],\n       [3213, 3214, 3220, 3219, 2791, 2792, 2798, 2797],\n       [3154, 3215, 3221, 3160, 2732, 2793, 2799, 2738],\n       [3215, 3216, 3222, 3221, 2793, 2794, 2800, 2799],\n       [3216, 3217, 3223, 3222, 2794, 2795, 2801, 2800],\n       [3217, 3218, 3224, 3223, 2795, 2796, 2802, 2801],\n       [3218, 3219, 3225, 3224, 2796, 2797, 2803, 2802],\n       [3219, 3220, 3226, 3225, 2797, 2798, 2804, 2803],\n       [3160, 3221, 3227, 3166, 2738, 2799, 2805, 2744],\n       [3221, 3222, 3228, 3227, 2799, 2800, 2806, 2805],\n       [3222, 3223, 3229, 3228, 2800, 2801, 2807, 2806],\n       [3223, 3224, 3230, 3229, 2801, 2802, 2808, 2807],\n       [3224, 3225, 3231, 3230, 2802, 2803, 2809, 2808],\n       [3225, 3226, 3232, 3231, 2803, 2804, 2810, 2809],\n       [3229, 3230, 3233, 3234, 2807, 2808, 2811, 2812],\n       [3230, 3231, 3235, 3233, 2808, 2809, 2813, 2811],\n       [3231, 3232, 3236, 3235, 2809, 2810, 2814, 2813],\n       [3234, 3233, 3237, 3238, 2812, 2811, 2815, 2816],\n       [3233, 3235, 3239, 3237, 2811, 2813, 2817, 2815],\n       [3235, 3236, 3240, 3239, 2813, 2814, 2818, 2817],\n       [3238, 3237, 3241, 3242, 2816, 2815, 2819, 2820],\n       [3237, 3239, 3243, 3241, 2815, 2817, 2821, 2819],\n       [3239, 3240, 3244, 3243, 2817, 2818, 2822, 2821],\n       [3244, 3245, 3246, 3243, 2822, 2823, 2824, 2821],\n       [3245, 3247, 3248, 3246, 2823, 2825, 2826, 2824],\n       [3247, 3029, 3181, 3248, 2825, 2607, 2759, 2826],\n       [3243, 3246, 3249, 3241, 2821, 2824, 2827, 2819],\n       [3246, 3248, 3250, 3249, 2824, 2826, 2828, 2827],\n       [3248, 3181, 3179, 3250, 2826, 2759, 2757, 2828],\n       [3241, 3249, 3251, 3242, 2819, 2827, 2829, 2820],\n       [3249, 3250, 3252, 3251, 2827, 2828, 2830, 2829],\n       [3250, 3179, 3178, 3252, 2828, 2757, 2756, 2830],\n       [3178, 3174, 3253, 3252, 2756, 2752, 2831, 2830],\n       [3174, 3170, 3254, 3253, 2752, 2748, 2832, 2831],\n       [3170, 3166, 3227, 3254, 2748, 2744, 2805, 2832],\n       [3252, 3253, 3255, 3251, 2830, 2831, 2833, 2829],\n       [3253, 3254, 3256, 3255, 2831, 2832, 2834, 2833],\n       [3254, 3227, 3228, 3256, 2832, 2805, 2806, 2834],\n       [3251, 3255, 3238, 3242, 2829, 2833, 2816, 2820],\n       [3255, 3256, 3234, 3238, 2833, 2834, 2812, 2816],\n       [3256, 3228, 3229, 3234, 2834, 2806, 2807, 2812],\n       [3201, 3257, 3258, 3202, 2779, 2835, 2836, 2780],\n       [3257, 3259, 3260, 3258, 2835, 2837, 2838, 2836],\n       [3259, 3261, 3262, 3260, 2837, 2839, 2840, 2838],\n       [3261, 3263, 3264, 3262, 2839, 2841, 2842, 2840],\n       [3263, 3265, 3266, 3264, 2841, 2843, 2844, 2842],\n       [3265, 3267, 3268, 3266, 2843, 2845, 2846, 2844],\n       [3202, 3258, 3269, 3208, 2780, 2836, 2847, 2786],\n       [3258, 3260, 3270, 3269, 2836, 2838, 2848, 2847],\n       [3260, 3262, 3271, 3270, 2838, 2840, 2849, 2848],\n       [3262, 3264, 3272, 3271, 2840, 2842, 2850, 2849],\n       [3264, 3266, 3273, 3272, 2842, 2844, 2851, 2850],\n       [3266, 3268, 3274, 3273, 2844, 2846, 2852, 2851],\n       [3208, 3269, 3275, 3214, 2786, 2847, 2853, 2792],\n       [3269, 3270, 3276, 3275, 2847, 2848, 2854, 2853],\n       [3270, 3271, 3277, 3276, 2848, 2849, 2855, 2854],\n       [3271, 3272, 3278, 3277, 2849, 2850, 2856, 2855],\n       [3272, 3273, 3279, 3278, 2850, 2851, 2857, 2856],\n       [3273, 3274, 3280, 3279, 2851, 2852, 2858, 2857],\n       [3214, 3275, 3281, 3220, 2792, 2853, 2859, 2798],\n       [3275, 3276, 3282, 3281, 2853, 2854, 2860, 2859],\n       [3276, 3277, 3283, 3282, 2854, 2855, 2861, 2860],\n       [3277, 3278, 3284, 3283, 2855, 2856, 2862, 2861],\n       [3278, 3279, 3285, 3284, 2856, 2857, 2863, 2862],\n       [3279, 3280, 3286, 3285, 2857, 2858, 2864, 2863],\n       [3220, 3281, 3287, 3226, 2798, 2859, 2865, 2804],\n       [3281, 3282, 3288, 3287, 2859, 2860, 2866, 2865],\n       [3282, 3283, 3289, 3288, 2860, 2861, 2867, 2866],\n       [3283, 3284, 3290, 3289, 2861, 2862, 2868, 2867],\n       [3284, 3285, 3291, 3290, 2862, 2863, 2869, 2868],\n       [3285, 3286, 3292, 3291, 2863, 2864, 2870, 2869],\n       [3226, 3287, 3293, 3232, 2804, 2865, 2871, 2810],\n       [3287, 3288, 3294, 3293, 2865, 2866, 2872, 2871],\n       [3288, 3289, 3295, 3294, 2866, 2867, 2873, 2872],\n       [3289, 3290, 3296, 3295, 2867, 2868, 2874, 2873],\n       [3290, 3291, 3297, 3296, 2868, 2869, 2875, 2874],\n       [3291, 3292, 3298, 3297, 2869, 2870, 2876, 2875],\n       [3295, 3296, 3299, 3300, 2873, 2874, 2877, 2878],\n       [3296, 3297, 3301, 3299, 2874, 2875, 2879, 2877],\n       [3297, 3298, 3302, 3301, 2875, 2876, 2880, 2879],\n       [3300, 3299, 3303, 3304, 2878, 2877, 2881, 2882],\n       [3299, 3301, 3305, 3303, 2877, 2879, 2883, 2881],\n       [3301, 3302, 3306, 3305, 2879, 2880, 2884, 2883],\n       [3304, 3303, 3307, 3308, 2882, 2881, 2885, 2886],\n       [3303, 3305, 3309, 3307, 2881, 2883, 2887, 2885],\n       [3305, 3306, 3310, 3309, 2883, 2884, 2888, 2887],\n       [3310, 3311, 3312, 3309, 2888, 2889, 2890, 2887],\n       [3311, 3313, 3314, 3312, 2889, 2891, 2892, 2890],\n       [3313, 3029, 3247, 3314, 2891, 2607, 2825, 2892],\n       [3309, 3312, 3315, 3307, 2887, 2890, 2893, 2885],\n       [3312, 3314, 3316, 3315, 2890, 2892, 2894, 2893],\n       [3314, 3247, 3245, 3316, 2892, 2825, 2823, 2894],\n       [3307, 3315, 3317, 3308, 2885, 2893, 2895, 2886],\n       [3315, 3316, 3318, 3317, 2893, 2894, 2896, 2895],\n       [3316, 3245, 3244, 3318, 2894, 2823, 2822, 2896],\n       [3244, 3240, 3319, 3318, 2822, 2818, 2897, 2896],\n       [3240, 3236, 3320, 3319, 2818, 2814, 2898, 2897],\n       [3236, 3232, 3293, 3320, 2814, 2810, 2871, 2898],\n       [3318, 3319, 3321, 3317, 2896, 2897, 2899, 2895],\n       [3319, 3320, 3322, 3321, 2897, 2898, 2900, 2899],\n       [3320, 3293, 3294, 3322, 2898, 2871, 2872, 2900],\n       [3317, 3321, 3304, 3308, 2895, 2899, 2882, 2886],\n       [3321, 3322, 3300, 3304, 2899, 2900, 2878, 2882],\n       [3322, 3294, 3295, 3300, 2900, 2872, 2873, 2878],\n       [3267, 3323, 3324, 3268, 2845, 2901, 2902, 2846],\n       [3323, 3325, 3326, 3324, 2901, 2903, 2904, 2902],\n       [3325, 3327, 3328, 3326, 2903, 2905, 2906, 2904],\n       [3327, 3329, 3330, 3328, 2905, 2907, 2908, 2906],\n       [3329, 3331, 3332, 3330, 2907, 2909, 2910, 2908],\n       [3331, 2955, 2958, 3332, 2909, 2533, 2536, 2910],\n       [3268, 3324, 3333, 3274, 2846, 2902, 2911, 2852],\n       [3324, 3326, 3334, 3333, 2902, 2904, 2912, 2911],\n       [3326, 3328, 3335, 3334, 2904, 2906, 2913, 2912],\n       [3328, 3330, 3336, 3335, 2906, 2908, 2914, 2913],\n       [3330, 3332, 3337, 3336, 2908, 2910, 2915, 2914],\n       [3332, 2958, 2972, 3337, 2910, 2536, 2550, 2915],\n       [3274, 3333, 3338, 3280, 2852, 2911, 2916, 2858],\n       [3333, 3334, 3339, 3338, 2911, 2912, 2917, 2916],\n       [3334, 3335, 3340, 3339, 2912, 2913, 2918, 2917],\n       [3335, 3336, 3341, 3340, 2913, 2914, 2919, 2918],\n       [3336, 3337, 3342, 3341, 2914, 2915, 2920, 2919],\n       [3337, 2972, 2980, 3342, 2915, 2550, 2558, 2920],\n       [3280, 3338, 3343, 3286, 2858, 2916, 2921, 2864],\n       [3338, 3339, 3344, 3343, 2916, 2917, 2922, 2921],\n       [3339, 3340, 3345, 3344, 2917, 2918, 2923, 2922],\n       [3340, 3341, 3346, 3345, 2918, 2919, 2924, 2923],\n       [3341, 3342, 3347, 3346, 2919, 2920, 2925, 2924],\n       [3342, 2980, 2988, 3347, 2920, 2558, 2566, 2925],\n       [3286, 3343, 3348, 3292, 2864, 2921, 2926, 2870],\n       [3343, 3344, 3349, 3348, 2921, 2922, 2927, 2926],\n       [3344, 3345, 3350, 3349, 2922, 2923, 2928, 2927],\n       [3345, 3346, 3351, 3350, 2923, 2924, 2929, 2928],\n       [3346, 3347, 3352, 3351, 2924, 2925, 2930, 2929],\n       [3347, 2988, 2996, 3352, 2925, 2566, 2574, 2930],\n       [3292, 3348, 3353, 3298, 2870, 2926, 2931, 2876],\n       [3348, 3349, 3354, 3353, 2926, 2927, 2932, 2931],\n       [3349, 3350, 3355, 3354, 2927, 2928, 2933, 2932],\n       [3350, 3351, 3356, 3355, 2928, 2929, 2934, 2933],\n       [3351, 3352, 3357, 3356, 2929, 2930, 2935, 2934],\n       [3352, 2996, 3004, 3357, 2930, 2574, 2582, 2935],\n       [3355, 3356, 3358, 3359, 2933, 2934, 2936, 2937],\n       [3356, 3357, 3360, 3358, 2934, 2935, 2938, 2936],\n       [3357, 3004, 3041, 3360, 2935, 2582, 2619, 2938],\n       [3359, 3358, 3361, 3362, 2937, 2936, 2939, 2940],\n       [3358, 3360, 3363, 3361, 2936, 2938, 2941, 2939],\n       [3360, 3041, 3039, 3363, 2938, 2619, 2617, 2941],\n       [3362, 3361, 3364, 3365, 2940, 2939, 2942, 2943],\n       [3361, 3363, 3366, 3364, 2939, 2941, 2944, 2942],\n       [3363, 3039, 3038, 3366, 2941, 2617, 2616, 2944],\n       [3038, 3034, 3367, 3366, 2616, 2612, 2945, 2944],\n       [3034, 3030, 3368, 3367, 2612, 2608, 2946, 2945],\n       [3030, 3029, 3313, 3368, 2608, 2607, 2891, 2946],\n       [3366, 3367, 3369, 3364, 2944, 2945, 2947, 2942],\n       [3367, 3368, 3370, 3369, 2945, 2946, 2948, 2947],\n       [3368, 3313, 3311, 3370, 2946, 2891, 2889, 2948],\n       [3364, 3369, 3371, 3365, 2942, 2947, 2949, 2943],\n       [3369, 3370, 3372, 3371, 2947, 2948, 2950, 2949],\n       [3370, 3311, 3310, 3372, 2948, 2889, 2888, 2950],\n       [3310, 3306, 3373, 3372, 2888, 2884, 2951, 2950],\n       [3306, 3302, 3374, 3373, 2884, 2880, 2952, 2951],\n       [3302, 3298, 3353, 3374, 2880, 2876, 2931, 2952],\n       [3372, 3373, 3375, 3371, 2950, 2951, 2953, 2949],\n       [3373, 3374, 3376, 3375, 2951, 2952, 2954, 2953],\n       [3374, 3353, 3354, 3376, 2952, 2931, 2932, 2954],\n       [3371, 3375, 3362, 3365, 2949, 2953, 2940, 2943],\n       [3375, 3376, 3359, 3362, 2953, 2954, 2937, 2940],\n       [3376, 3354, 3355, 3359, 2954, 2932, 2933, 2937]\n], dtype='int64')\n"
  },
  {
    "path": "yt/frontends/stream/sample_data/tetrahedral_mesh.py",
    "content": "import numpy as np\n\n# This is just an export from `pydistmesh` using the '3D Unit Ball' example:\n# https://github.com/bfroehle/pydistmesh\n\n_coordinates = np.array([\n    [ -9.62967621e-01,  -2.68325669e-01,  -2.63570990e-02],\n    [ -9.23197206e-01,   6.00592270e-02,  -3.79604808e-01],\n    [ -9.99398840e-01,  -3.01322522e-02,   1.71466163e-02],\n    [ -9.23424144e-01,  -7.55355896e-02,   3.76274134e-01],\n    [ -9.76172900e-01,   2.13522703e-01,   3.86590740e-02],\n    [ -7.73044456e-01,  -5.39692475e-01,  -3.33368116e-01],\n    [ -7.87313888e-01,  -5.98131409e-01,  -1.49584956e-01],\n    [ -7.74823044e-01,  -6.03944560e-01,   1.86816007e-01],\n    [ -8.02947422e-01,  -3.76286648e-01,  -4.62259448e-01],\n    [ -9.01768086e-01,  -3.68179163e-01,  -2.26403233e-01],\n    [ -8.75147083e-01,  -4.83776676e-01,  -8.81535636e-03],\n    [ -9.02155201e-01,  -3.89290298e-01,   1.85927562e-01],\n    [ -7.89270641e-01,  -4.87597303e-01,   3.73230123e-01],\n    [ -7.52138168e-01,  -2.22375528e-01,  -6.20352562e-01],\n    [ -8.95457206e-01,  -1.92141852e-01,  -4.01544395e-01],\n    [ -9.71942256e-01,  -1.23239288e-01,  -2.00350515e-01],\n    [ -8.05378938e-01,  -2.23546612e-01,  -1.51473404e-04],\n    [ -9.66811578e-01,  -1.69359673e-01,   1.91292119e-01],\n    [ -8.70654663e-01,  -2.82390171e-01,   4.02760786e-01],\n    [ -7.11079737e-01,  -4.28959611e-01,   5.57098968e-01],\n    [ -7.88471049e-01,   1.78042094e-01,  -5.88739685e-01],\n    [ -8.39017123e-01,  -2.86996919e-02,  -5.43347582e-01],\n    [ -7.35821715e-01,  -2.11538194e-01,  -2.33136439e-01],\n    [ -7.85079983e-01,  -4.35631443e-03,  -7.29027161e-02],\n    [ -7.92742715e-01,   5.30843614e-04,   1.49140404e-01],\n    [ -8.17004797e-01,  -5.47499959e-02,   5.74025783e-01],\n    [ -7.04701668e-01,  -2.24209163e-01,   6.73146203e-01],\n    [ -7.70999764e-01,   3.71216019e-01,  -5.17453410e-01],\n    [ -8.90385164e-01,   2.68446994e-01,  -3.67628170e-01],\n    [ -9.79246646e-01,   1.15973457e-01,  -1.66211200e-01],\n    [ -7.72969304e-01,   2.21158909e-01,   4.35889641e-02],\n    [ -9.70179581e-01,   8.97731254e-02,   2.25149656e-01],\n    [ -8.86033779e-01,   1.44311425e-01,   4.40588645e-01],\n    [ -7.57393407e-01,   1.57682547e-01,   6.33633524e-01],\n    [ -8.20053173e-01,   4.86337456e-01,  -3.01643287e-01],\n    [ -9.21552627e-01,   3.65067689e-01,  -1.32160273e-01],\n    [ -8.88239706e-01,   4.53748187e-01,   7.17133752e-02],\n    [ -9.12745738e-01,   3.24240717e-01,   2.48521981e-01],\n    [ -8.08848385e-01,   3.57206222e-01,   4.67084580e-01],\n    [ -8.02123077e-01,   5.90021489e-01,  -9.20500458e-02],\n    [ -7.45948377e-01,   6.57205497e-01,   1.07897884e-01],\n    [ -7.98204994e-01,   5.25058631e-01,   2.95266357e-01],\n    [ -6.30887505e-01,  -7.39668679e-01,  -2.34246028e-01],\n    [ -7.15329115e-01,  -6.98453892e-01,   2.15967334e-02],\n    [ -5.77957725e-01,  -7.92693457e-01,   1.93912223e-01],\n    [ -6.42665949e-01,  -5.94884286e-01,  -4.82797228e-01],\n    [ -5.46038557e-01,  -5.44985033e-01,  -2.71787655e-01],\n    [ -5.54213927e-01,  -5.84648463e-01,  -5.10845856e-02],\n    [ -5.45487961e-01,  -5.77578168e-01,   2.00918083e-01],\n    [ -6.46377284e-01,  -6.53904664e-01,   3.93198547e-01],\n    [ -6.38955694e-01,  -4.18351960e-01,  -6.45536411e-01],\n    [ -5.99144091e-01,  -3.63886456e-01,  -4.05441933e-01],\n    [ -6.82849084e-01,  -3.86014000e-01,  -1.63498157e-01],\n    [ -6.68766881e-01,  -4.19225500e-01,   5.42136051e-02],\n    [ -7.30857178e-01,  -2.03227666e-01,   2.06228100e-01],\n    [ -5.43618891e-01,  -4.30741083e-01,   4.45877772e-01],\n    [ -5.58595071e-01,  -4.21540710e-01,   7.14335339e-01],\n    [ -5.82838566e-01,  -2.03229852e-01,  -7.86763518e-01],\n    [ -5.09947711e-01,  -2.26802295e-01,  -5.80406785e-01],\n    [ -6.57701812e-01,  -1.38424779e-01,  -4.27940410e-01],\n    [ -6.07756412e-01,  -1.72786293e-01,  -3.54305445e-02],\n    [ -5.06397150e-01,  -2.73398545e-01,   1.14366953e-01],\n    [ -6.62607234e-01,  -3.85526543e-01,   2.68375874e-01],\n    [ -7.05010419e-01,  -2.21273740e-01,   4.52510033e-01],\n    [ -4.93572234e-01,  -2.48290925e-01,   5.93171798e-01],\n    [ -5.30304677e-01,  -1.91881131e-01,   8.25807836e-01],\n    [ -6.95576819e-01,  -1.18317597e-02,  -7.18354299e-01],\n    [ -5.87300531e-01,   6.42382159e-03,  -5.97639073e-01],\n    [ -6.73330053e-01,   1.22449487e-01,  -4.02731110e-01],\n    [ -5.59319757e-01,  -1.73769322e-02,  -2.09348254e-01],\n    [ -5.95036090e-01,   6.63685740e-02,   3.03841620e-02],\n    [ -5.57266536e-01,  -5.60387817e-02,   1.93042065e-01],\n    [ -7.00883535e-01,  -6.04503771e-03,   3.64291599e-01],\n    [ -5.78057568e-01,  -3.71186623e-02,   5.64052628e-01],\n    [ -6.47137348e-01,  -2.91689448e-03,   7.62367854e-01],\n    [ -6.17566835e-01,   2.14112793e-01,  -7.56813660e-01],\n    [ -5.39967650e-01,   2.26181937e-01,  -5.56616845e-01],\n    [ -7.72970264e-01,  -6.67507940e-03,  -2.79806814e-01],\n    [ -6.13953196e-01,   1.78370120e-01,  -1.61353511e-01],\n    [ -5.29572241e-01,   2.85092966e-01,   5.07316309e-02],\n    [ -5.74738903e-01,   1.74657307e-01,   2.42139593e-01],\n    [ -7.56747405e-01,   2.10707702e-01,   2.69976416e-01],\n    [ -6.31176071e-01,   1.83653152e-01,   4.84414633e-01],\n    [ -5.87915999e-01,   2.24217289e-01,   7.77226727e-01],\n    [ -6.28395851e-01,   4.19919168e-01,  -6.54817950e-01],\n    [ -6.42450027e-01,   3.31162532e-01,  -3.28240646e-01],\n    [ -8.01427779e-01,   2.08645036e-01,  -1.92090617e-01],\n    [ -6.90906413e-01,   3.97333130e-01,  -9.61360295e-02],\n    [ -6.72545573e-01,   4.10691484e-01,   1.45013988e-01],\n    [ -6.04539604e-01,   3.87066026e-01,   3.57575500e-01],\n    [ -6.68054353e-01,   3.84236522e-01,   6.37232827e-01],\n    [ -6.75857115e-01,   5.72589964e-01,  -4.64066691e-01],\n    [ -5.59143406e-01,   5.23445316e-01,  -2.54861670e-01],\n    [ -5.63538680e-01,   5.59721915e-01,  -3.14218279e-02],\n    [ -6.39318728e-01,   6.99649299e-01,   3.19002231e-01],\n    [ -6.64343258e-01,   5.64739973e-01,   4.89608822e-01],\n    [ -6.86462131e-01,   6.83192131e-01,  -2.49034648e-01],\n    [ -6.20846808e-01,   7.80473026e-01,  -7.35601686e-02],\n    [ -5.64621392e-01,   8.16515778e-01,   1.20435325e-01],\n    [ -4.95021894e-01,  -7.59837405e-01,  -4.21426672e-01],\n    [ -4.11090255e-01,  -8.80338747e-01,  -2.36661137e-01],\n    [ -5.47447534e-01,  -8.36158957e-01,  -3.37549521e-02],\n    [ -3.66130310e-01,  -9.09648278e-01,   1.96185134e-01],\n    [ -4.62833735e-01,  -7.92039672e-01,   3.98067949e-01],\n    [ -4.89583007e-01,  -6.02925047e-01,  -6.29912587e-01],\n    [ -3.80304973e-01,  -6.03973036e-01,  -4.02431008e-01],\n    [ -3.91842972e-01,  -7.11773257e-01,  -1.85685509e-01],\n    [ -3.84974499e-01,  -7.08582426e-01,   3.83824107e-02],\n    [ -3.40651461e-01,  -6.72213220e-01,   2.28912670e-01],\n    [ -3.84521145e-01,  -5.86140658e-01,   4.14820856e-01],\n    [ -5.31073959e-01,  -6.17424650e-01,   5.80299277e-01],\n    [ -4.59909273e-01,  -3.95671027e-01,  -7.94938928e-01],\n    [ -3.98102385e-01,  -4.19059333e-01,  -5.61501051e-01],\n    [ -3.81193299e-01,  -3.93630978e-01,  -3.56732489e-01],\n    [ -4.77511655e-01,  -3.75863014e-01,  -1.26017536e-01],\n    [ -3.87190527e-01,  -4.74981994e-01,   5.57554008e-02],\n    [ -4.21088239e-01,  -3.96851005e-01,   2.59967897e-01],\n    [ -3.08478859e-01,  -3.41393062e-01,   4.32292613e-01],\n    [ -3.52402545e-01,  -4.36693481e-01,   5.99333058e-01],\n    [ -3.88440519e-01,  -3.70654859e-01,   8.43640290e-01],\n    [ -3.89098848e-01,  -1.77734623e-01,  -9.03887433e-01],\n    [ -3.06623610e-01,  -2.35268920e-01,  -7.00243202e-01],\n    [ -3.41856207e-01,  -1.73691252e-01,  -4.53096469e-01],\n    [ -4.95495891e-01,  -2.09375496e-01,  -2.66566326e-01],\n    [ -3.58979116e-01,  -2.38478707e-01,  -6.00078514e-02],\n    [ -3.61083064e-01,  -1.51054689e-01,   2.47282244e-01],\n    [ -5.22145140e-01,  -2.02932177e-01,   3.68602647e-01],\n    [ -2.88279144e-01,  -2.58872203e-01,   7.15313501e-01],\n    [ -3.30873242e-01,  -1.47363870e-01,   9.32098057e-01],\n    [ -5.21050518e-01,   2.37374153e-02,  -8.53195695e-01],\n    [ -3.96061295e-01,  -3.80024812e-02,  -6.94021762e-01],\n    [ -4.64462964e-01,  -1.24913007e-02,  -4.30642267e-01],\n    [ -3.26163230e-01,  -3.91595358e-02,  -2.32146758e-01],\n    [ -4.06814958e-01,  -5.71682613e-02,   2.94472862e-03],\n    [ -3.74040953e-01,   1.08402262e-01,   1.74064052e-01],\n    [ -4.51503692e-01,   2.09979658e-02,   4.07300559e-01],\n    [ -3.92321513e-01,  -4.58352294e-02,   7.04774801e-01],\n    [ -4.56241586e-01,   5.07189584e-02,   8.88409366e-01],\n    [ -4.25440817e-01,   2.32028056e-01,  -8.74736014e-01],\n    [ -2.99098712e-01,   2.14664654e-01,  -7.35610261e-01],\n    [ -3.56902556e-01,   1.27886858e-01,  -5.76114828e-01],\n    [ -4.38206907e-01,   1.84178546e-01,  -3.37738082e-01],\n    [ -4.01961192e-01,   1.38677100e-01,  -9.82255735e-02],\n    [ -4.11546738e-01,   3.58182028e-01,   2.47789091e-01],\n    [ -4.07517555e-01,   2.08108781e-01,   4.18341689e-01],\n    [ -4.51178528e-01,   1.49551301e-01,   6.51618844e-01],\n    [ -3.72680102e-01,   2.63356951e-01,   8.89804843e-01],\n    [ -4.64223868e-01,   4.23342694e-01,  -7.77995607e-01],\n    [ -3.64395973e-01,   4.03004411e-01,  -6.01158644e-01],\n    [ -5.12348000e-01,   4.07384566e-01,  -4.58573380e-01],\n    [ -4.44985738e-01,   3.51724152e-01,  -1.50316028e-01],\n    [ -3.86770739e-01,   4.64454002e-01,   5.02311117e-02],\n    [ -5.32930370e-01,   5.75775886e-01,   2.03141645e-01],\n    [ -2.66781216e-01,   4.13898117e-01,   4.30887385e-01],\n    [ -4.85656110e-01,   3.71762124e-01,   5.38458720e-01],\n    [ -4.68107747e-01,   4.18318830e-01,   7.78385826e-01],\n    [ -4.97986898e-01,   6.13339175e-01,  -6.13044946e-01],\n    [ -3.86761727e-01,   5.93271383e-01,  -4.03701051e-01],\n    [ -4.45885002e-01,   7.19487703e-01,  -1.83540323e-01],\n    [ -3.94993635e-01,   7.04316770e-01,   5.57745148e-02],\n    [ -4.34407638e-01,   8.12572050e-01,   2.97930026e-01],\n    [ -4.34808796e-01,   5.61352642e-01,   4.02806351e-01],\n    [ -5.03093415e-01,   5.81725526e-01,   6.39134124e-01],\n    [ -5.36185759e-01,   7.42670990e-01,  -4.01179053e-01],\n    [ -3.78389380e-01,   8.72517067e-01,  -3.09088084e-01],\n    [ -4.08836317e-01,   9.09543790e-01,  -7.47192069e-02],\n    [ -3.45264752e-01,   9.26749050e-01,   1.48082571e-01],\n    [ -4.65253738e-01,   7.44368236e-01,   4.79014498e-01],\n    [ -3.49013019e-01,  -9.36842287e-01,  -2.27253547e-02],\n    [ -3.14733849e-01,  -7.41414880e-01,  -5.92660594e-01],\n    [ -2.74412512e-01,  -8.66322161e-01,  -4.17353194e-01],\n    [ -1.92620923e-01,  -9.60021017e-01,  -2.03117769e-01],\n    [ -1.82051552e-01,  -7.61556292e-01,  -8.36865303e-02],\n    [ -1.51505552e-01,  -9.87579969e-01,   4.16157811e-02],\n    [ -2.54046269e-01,  -8.75951727e-01,   4.10084217e-01],\n    [ -3.15836068e-01,  -7.42333328e-01,   5.90921999e-01],\n    [ -3.16779695e-01,  -5.65032826e-01,  -7.61832353e-01],\n    [ -1.99380736e-01,  -5.83498966e-01,  -5.17971409e-01],\n    [ -1.93074469e-01,  -7.27602781e-01,  -3.13324024e-01],\n    [ -3.07440769e-01,  -5.34538758e-01,  -1.59248646e-01],\n    [ -1.83589817e-01,  -5.63598366e-01,   3.99710449e-02],\n    [ -1.72637366e-01,  -7.87780365e-01,   1.41563414e-01],\n    [ -1.66748215e-01,  -7.11019457e-01,   3.88397712e-01],\n    [ -1.79936519e-01,  -5.56267314e-01,   5.46800560e-01],\n    [ -3.58182261e-01,  -5.78742564e-01,   7.32640780e-01],\n    [ -2.39113604e-01,  -3.63405744e-01,  -9.00422651e-01],\n    [ -1.88667420e-01,  -4.25310126e-01,  -6.68457429e-01],\n    [ -1.64032518e-01,  -4.92768758e-01,  -3.23434511e-01],\n    [ -1.42286771e-01,  -3.78405111e-01,  -8.11029046e-02],\n    [ -2.44971665e-01,  -3.13503503e-01,   1.06085864e-01],\n    [ -2.28610813e-01,  -4.85183904e-01,   2.66130473e-01],\n    [ -1.28977866e-01,  -3.13541733e-01,   5.70211381e-01],\n    [ -1.78031279e-01,  -4.65204651e-01,   7.92395828e-01],\n    [ -1.72505832e-01,  -3.19168435e-01,   9.31865467e-01],\n    [ -1.71515483e-01,  -1.59643018e-01,  -9.72160762e-01],\n    [ -1.77643190e-01,  -3.20496856e-01,  -4.92602235e-01],\n    [ -1.14229852e-01,  -1.49074465e-01,  -3.42747649e-01],\n    [ -2.52795680e-01,  -2.86475470e-01,  -2.49110854e-01],\n    [ -1.74693523e-01,  -1.40098697e-01,  -8.60454071e-02],\n    [ -1.66194986e-01,  -2.46030595e-01,   3.10073251e-01],\n    [ -3.13732125e-01,  -1.40675034e-01,   5.06100644e-01],\n    [ -1.74134649e-01,  -1.01886625e-01,   7.32556325e-01],\n    [ -9.95450127e-02,  -1.14585341e-01,   9.88413370e-01],\n    [ -3.18739089e-01,   4.00091666e-02,  -9.46997709e-01],\n    [ -1.93708219e-01,   1.00722710e-02,  -7.78634557e-01],\n    [ -1.82613620e-01,  -7.75287419e-02,  -5.72815481e-01],\n    [ -2.28815875e-01,   6.25534005e-02,  -4.05248137e-01],\n    [ -9.94383439e-02,   2.68669966e-02,  -2.20864387e-01],\n    [ -2.07094208e-01,   9.82448007e-02,  -3.77870585e-02],\n    [ -2.15852843e-01,  -8.17490291e-02,   1.15598164e-01],\n    [ -2.30191010e-01,  -1.75544371e-04,   3.44118680e-01],\n    [ -2.35116145e-01,   7.71682150e-02,   5.52860241e-01],\n    [ -2.08185096e-01,   1.20081075e-01,   7.63698137e-01],\n    [ -2.40651767e-01,   5.78343201e-02,   9.68886948e-01],\n    [ -1.93731688e-01,   2.45702312e-01,  -9.49788612e-01],\n    [ -1.12016933e-01,   1.63531041e-01,  -5.86901497e-01],\n    [ -2.34602187e-01,   2.94459202e-01,  -4.73486651e-01],\n    [ -2.26208974e-01,   2.23166687e-01,  -2.41358207e-01],\n    [ -2.85908624e-01,   2.71530866e-01,   5.11117105e-02],\n    [ -1.34482864e-01,   1.24402789e-01,   1.86580767e-01],\n    [ -2.19932943e-01,   2.26626105e-01,   3.55951824e-01],\n    [ -3.01290816e-01,   3.03227979e-01,   6.38886854e-01],\n    [ -1.64692179e-01,   2.77015285e-01,   9.46646195e-01],\n    [ -2.62930799e-01,   4.35815525e-01,  -8.60774199e-01],\n    [ -1.45572649e-01,   3.98542626e-01,  -6.84093735e-01],\n    [ -2.07918055e-01,   5.55368124e-01,  -5.18712780e-01],\n    [ -3.42891725e-01,   4.03334468e-01,  -3.28874903e-01],\n    [ -1.88513526e-01,   3.73927074e-01,  -1.06486483e-01],\n    [ -1.73582211e-01,   3.83676674e-01,   1.80808854e-01],\n    [ -1.14880644e-01,   4.96441733e-01,   5.98104183e-01],\n    [ -1.47311184e-01,   3.54067258e-01,   7.64393318e-01],\n    [ -2.55934339e-01,   4.92690346e-01,   8.31717402e-01],\n    [ -3.03477171e-01,   6.16396048e-01,  -7.26606853e-01],\n    [ -3.37997757e-01,   7.73558160e-01,  -5.36064633e-01],\n    [ -2.26044087e-01,   7.08169734e-01,  -3.04701855e-01],\n    [ -3.23533824e-01,   5.57868526e-01,  -1.56651270e-01],\n    [ -1.95521147e-01,   5.66863051e-01,   3.43378062e-02],\n    [ -2.94013176e-01,   5.82311319e-01,   2.45762618e-01],\n    [ -1.97076732e-01,   6.60820318e-01,   4.16455346e-01],\n    [ -3.15119586e-01,   5.41216314e-01,   6.21821689e-01],\n    [ -1.35031842e-01,   6.70227569e-01,   7.29768050e-01],\n    [ -1.30922603e-01,   7.63763696e-01,  -6.32079337e-01],\n    [ -1.69367064e-01,   8.85500714e-01,  -4.32669948e-01],\n    [ -1.88284600e-01,   9.58082704e-01,  -2.15931571e-01],\n    [ -2.15357169e-01,   7.70995955e-01,  -7.15664769e-02],\n    [ -1.19113529e-01,   9.69485318e-01,   2.14266624e-01],\n    [ -2.50862111e-01,   8.84373013e-01,   3.93640159e-01],\n    [ -3.03000063e-01,   7.42360357e-01,   5.97571806e-01],\n    [ -1.87291251e-01,   9.82304419e-01,  -1.28846379e-04],\n    [ -3.84532991e-02,  -9.31861848e-01,  -3.60769787e-01],\n    [  1.44520011e-02,  -9.93720630e-01,  -1.10952461e-01],\n    [ -1.26363945e-01,  -9.56072126e-01,   2.64496206e-01],\n    [ -1.19844694e-01,  -6.78852423e-01,  -7.24428491e-01],\n    [ -8.69666571e-02,  -8.24194281e-01,  -5.59589661e-01],\n    [  1.92508773e-02,  -7.86178086e-01,  -1.99792242e-01],\n    [  2.59736870e-02,  -8.16027980e-01,   3.21822222e-02],\n    [  5.78263480e-02,  -7.77061434e-01,   2.69277400e-01],\n    [  8.48713263e-03,  -9.03365175e-01,   4.28788212e-01],\n    [ -8.12034498e-02,  -8.01090245e-01,   5.93009629e-01],\n    [ -9.44555528e-02,  -5.11948311e-01,  -8.53807400e-01],\n    [  1.22227825e-02,  -5.84779056e-01,  -6.17069967e-01],\n    [  1.14282616e-02,  -6.77557927e-01,  -4.03044568e-01],\n    [ -4.94206399e-02,  -5.79359611e-01,  -1.75221047e-01],\n    [  3.54180438e-02,  -6.16893928e-01,   5.76597508e-02],\n    [ -7.35585843e-02,  -6.27256660e-01,   2.41870856e-01],\n    [  4.69704885e-02,  -6.49539057e-01,   4.72840371e-01],\n    [  9.18934804e-02,  -6.86719418e-01,   7.21090861e-01],\n    [ -1.25360666e-01,  -6.52218717e-01,   7.47593104e-01],\n    [  1.10570869e-01,  -5.47391887e-01,  -8.29539755e-01],\n    [  3.39626961e-03,  -3.66635006e-01,  -6.93343934e-01],\n    [  1.25738074e-02,  -4.53904798e-01,  -4.69638034e-01],\n    [  7.03185078e-02,  -4.26921601e-01,  -6.79651552e-02],\n    [ -2.81962041e-02,  -4.08640743e-01,   1.57174041e-01],\n    [  1.29299577e-01,  -5.28129998e-01,   2.68603445e-01],\n    [ -6.51159350e-02,  -4.55882802e-01,   4.04884494e-01],\n    [  2.36123834e-02,  -4.72139675e-01,   6.39529107e-01],\n    [  2.46523286e-02,  -4.80995012e-01,   8.76376666e-01],\n    [  4.97649308e-02,  -1.37259058e-01,  -9.89284288e-01],\n    [  2.42859800e-02,  -3.39666980e-01,  -9.40232171e-01],\n    [ -9.69668126e-02,  -1.99263737e-01,  -7.68274355e-01],\n    [  2.08525613e-02,  -2.14097485e-01,  -5.31519023e-01],\n    [ -1.07013915e-03,  -3.39815934e-01,  -2.93721956e-01],\n    [  1.57139516e-02,  -1.92449444e-01,  -1.42390379e-01],\n    [ -2.04197400e-02,  -2.22306197e-01,   9.04305303e-02],\n    [  3.87266848e-02,  -2.76080684e-01,   3.60076717e-01],\n    [  9.31376446e-02,  -2.13251477e-01,   5.74822958e-01],\n    [ -2.06942363e-02,  -2.61315125e-01,   7.64083562e-01],\n    [  5.35699517e-02,  -2.57293791e-01,   9.64847224e-01],\n    [ -8.66763821e-02,   5.63452713e-02,  -9.94641853e-01],\n    [  4.50561348e-02,  -7.41066799e-03,  -8.47406496e-01],\n    [  3.33458782e-02,  -1.14143341e-02,  -6.60235249e-01],\n    [  6.84339708e-03,   8.90210227e-03,  -4.41613112e-01],\n    [  9.49578088e-02,   4.46302002e-02,  -2.10962018e-01],\n    [ -6.47323444e-03,   4.23441467e-03,   1.11262491e-02],\n    [ -2.20508593e-02,  -7.44769302e-02,   2.50509251e-01],\n    [ -8.08824268e-02,  -1.12803625e-01,   4.93814502e-01],\n    [  4.31118364e-03,   2.66036252e-02,   6.39348740e-01],\n    [ -1.51603188e-03,  -1.98988230e-02,   8.37604363e-01],\n    [ -9.23935066e-03,   1.09286102e-01,   9.93967395e-01],\n    [  5.63301619e-02,   2.24876724e-01,  -9.72757613e-01],\n    [ -4.61848355e-02,   2.04133531e-01,  -7.79695854e-01],\n    [ -3.96552100e-03,   3.80190538e-01,  -4.97582997e-01],\n    [ -1.18507316e-02,   2.17673333e-01,  -3.61334139e-01],\n    [ -6.02985784e-03,   2.21576339e-01,  -1.22196991e-01],\n    [ -2.32241591e-02,   2.50647992e-01,   6.38152715e-02],\n    [ -6.63051411e-03,   2.68706951e-01,   3.21898185e-01],\n    [ -8.58651811e-03,   8.43630474e-02,   4.22202755e-01],\n    [ -8.42376448e-02,   2.59602722e-01,   5.58129856e-01],\n    [  2.57323327e-02,   2.12104235e-01,   7.70285538e-01],\n    [  3.52746047e-02,   3.45035878e-01,   9.37926407e-01],\n    [ -2.97723438e-02,   4.24183729e-01,  -9.05086610e-01],\n    [  9.29712760e-02,   3.76707117e-01,  -7.06489067e-01],\n    [ -1.40981487e-01,   4.61266202e-01,  -3.29987153e-01],\n    [  4.92055611e-02,   3.91387717e-01,  -2.35631121e-01],\n    [ -1.97462635e-04,   4.51544969e-01,   6.47824769e-03],\n    [  1.23653883e-01,   3.67736045e-01,   2.05364992e-01],\n    [ -7.63551949e-02,   4.59397098e-01,   3.72865094e-01],\n    [  6.82176296e-02,   4.37151216e-01,   6.84436118e-01],\n    [  2.66618242e-04,   5.35613256e-01,   8.44463362e-01],\n    [ -8.06612085e-02,   6.06532435e-01,  -7.90956493e-01],\n    [  7.59431774e-03,   5.76466083e-01,  -5.91739541e-01],\n    [ -4.20662344e-02,   6.92842103e-01,  -4.04895589e-01],\n    [ -7.22163505e-02,   5.86341525e-01,  -1.76197982e-01],\n    [ -2.82929006e-03,   6.85092763e-01,   3.33712489e-02],\n    [ -3.54501018e-02,   5.73604344e-01,   2.16391791e-01],\n    [  8.87977875e-03,   7.54111200e-01,   3.05826038e-01],\n    [  2.83834106e-02,   6.24014787e-01,   5.06040338e-01],\n    [  9.15391011e-02,   7.01376458e-01,   7.06888717e-01],\n    [  1.19416186e-01,   7.35331387e-01,  -6.67103835e-01],\n    [  5.68688791e-02,   8.54595574e-01,  -5.16170841e-01],\n    [ -1.16699743e-02,   7.89916321e-01,  -1.85148337e-01],\n    [ -6.01400665e-03,   8.69990193e-01,   6.23534747e-02],\n    [ -1.95727679e-01,   7.64511507e-01,   1.81280060e-01],\n    [  1.20789453e-01,   8.40490066e-01,   5.28191591e-01],\n    [ -9.39626086e-02,   8.20506401e-01,   5.63861931e-01],\n    [  2.81148104e-02,   9.43729028e-01,  -3.29522502e-01],\n    [  3.41125444e-02,   9.95068116e-01,  -9.31438613e-02],\n    [ -2.14483362e-03,   9.25392070e-01,   3.79005168e-01],\n    [  2.18112689e-01,  -9.74821369e-01,  -4.63697576e-02],\n    [  1.23853444e-01,  -7.28502092e-01,  -6.73754426e-01],\n    [  1.44704613e-01,  -8.58537380e-01,  -4.91908673e-01],\n    [  1.87781455e-01,  -9.41233073e-01,  -2.80746195e-01],\n    [  2.31410729e-01,  -8.07540207e-01,  -8.25767126e-02],\n    [  6.97170007e-02,  -9.84450226e-01,   1.61236758e-01],\n    [  2.30393023e-01,  -9.04286027e-01,   3.59424311e-01],\n    [  1.89807547e-01,  -8.03635809e-01,   5.64041295e-01],\n    [  3.22755516e-01,  -5.86935920e-01,  -7.42519430e-01],\n    [  3.31597344e-01,  -7.46740775e-01,  -5.76559985e-01],\n    [  2.27776250e-01,  -7.23370447e-01,  -3.10605531e-01],\n    [  1.63795538e-01,  -5.17366379e-01,  -2.81379193e-01],\n    [  1.78823602e-01,  -6.28896291e-01,  -8.64287065e-02],\n    [  2.31860993e-01,  -7.39739895e-01,   1.49715345e-01],\n    [  2.69030761e-01,  -6.65754968e-01,   3.56967997e-01],\n    [  2.50134311e-01,  -5.68320717e-01,   5.65130746e-01],\n    [  2.21765258e-01,  -5.48596353e-01,   8.06140318e-01],\n    [  2.66915440e-01,  -3.83657939e-01,  -8.84060368e-01],\n    [  2.01519428e-01,  -4.30434869e-01,  -6.53813689e-01],\n    [  2.27114000e-01,  -5.81622952e-01,  -4.95126812e-01],\n    [  2.26971538e-01,  -3.11986319e-01,  -2.06952935e-01],\n    [  2.66317379e-01,  -5.03016563e-01,   1.08925528e-01],\n    [  4.45366001e-01,  -6.19390316e-01,   2.11676959e-01],\n    [  1.75225358e-01,  -4.29561039e-01,   4.56919428e-01],\n    [  2.04330462e-01,  -3.60931919e-01,   6.92428501e-01],\n    [  2.28824041e-01,  -3.32760745e-01,   9.14827768e-01],\n    [  2.48765341e-01,  -1.65057236e-01,  -9.54396099e-01],\n    [  1.39387281e-01,  -2.14073764e-01,  -7.55554463e-01],\n    [  1.97279259e-01,  -3.34597766e-01,  -4.57795165e-01],\n    [  1.55494033e-01,  -1.36566595e-01,  -3.29815444e-01],\n    [  1.69940802e-01,  -3.12986488e-01,   2.72903460e-02],\n    [  1.88913617e-01,  -2.97118516e-01,   2.31933019e-01],\n    [  2.83041605e-01,  -2.43544619e-01,   4.58124190e-01],\n    [  1.86156324e-01,  -1.28793574e-01,   7.73894896e-01],\n    [  1.72667984e-01,  -7.32071974e-02,   9.82255808e-01],\n    [  4.12455593e-01,   1.47398934e-02,  -9.10858452e-01],\n    [  2.69311185e-01,  -2.87406987e-02,  -7.57067959e-01],\n    [  2.19557754e-01,  -8.59316259e-02,  -5.45649375e-01],\n    [  2.16518363e-01,   8.98099440e-02,  -3.96642521e-01],\n    [  1.94983213e-01,  -1.03663895e-01,  -8.13730923e-02],\n    [  1.83294039e-01,  -8.80651206e-02,   1.23569725e-01],\n    [  1.07922312e-01,   1.15031462e-01,   1.92321541e-01],\n    [  1.65118717e-01,  -7.17364718e-02,   3.60784649e-01],\n    [  2.09732400e-01,  -1.20997873e-02,   5.59122842e-01],\n    [  2.18098013e-01,   9.38478667e-02,   7.75124598e-01],\n    [  3.73543318e-01,   5.17586579e-03,   9.27598297e-01],\n    [  2.05717715e-01,   7.53314927e-02,  -9.75707634e-01],\n    [  1.17345035e-01,   1.84580292e-01,  -5.76077495e-01],\n    [  2.25102402e-01,   3.27630607e-01,  -4.35006763e-01],\n    [  2.22753607e-01,   2.46290020e-01,  -2.26657613e-01],\n    [  1.97348236e-01,   1.22204372e-01,  -5.27994103e-03],\n    [  3.15010615e-01,   2.58654930e-01,   1.42312814e-01],\n    [  2.09783256e-01,   1.67156009e-01,   3.59093347e-01],\n    [  1.63202711e-01,   2.05291525e-01,   5.64763439e-01],\n    [  1.97878214e-01,   1.73604975e-01,   9.64730805e-01],\n    [  2.05583723e-01,   3.68622696e-01,  -9.06560886e-01],\n    [  1.99505685e-01,   1.68317631e-01,  -7.68578105e-01],\n    [  1.02120411e-01,   5.44777800e-01,  -3.74107242e-01],\n    [  2.91304671e-01,   4.63829103e-01,  -2.65149160e-01],\n    [  1.92393602e-01,   3.56306387e-01,  -3.61651193e-02],\n    [  1.66221667e-01,   5.40222401e-01,   3.16116237e-01],\n    [  1.03192068e-01,   3.84500538e-01,   4.76252225e-01],\n    [  2.40927417e-01,   3.27308102e-01,   7.05589375e-01],\n    [  2.22868825e-01,   4.06677398e-01,   8.85970079e-01],\n    [  1.51856985e-01,   5.73133196e-01,  -8.05268773e-01],\n    [  2.24221571e-01,   5.13573162e-01,  -5.71311460e-01],\n    [  1.76465420e-01,   7.38746010e-01,  -3.11570971e-01],\n    [  1.57586884e-01,   5.83668501e-01,  -1.32718527e-01],\n    [  1.93245363e-01,   5.67332754e-01,   9.12964174e-02],\n    [  1.82472617e-01,   7.64831299e-01,   1.71349495e-01],\n    [  2.37303191e-01,   6.80016753e-01,   4.01108365e-01],\n    [  2.50808507e-01,   5.30577516e-01,   5.61711743e-01],\n    [  2.60139071e-01,   5.85769469e-01,   7.67594812e-01],\n    [  3.37133701e-01,   6.84372138e-01,  -6.46510358e-01],\n    [  2.67125222e-01,   8.34839493e-01,  -4.81338901e-01],\n    [  2.24773284e-01,   9.44894725e-01,  -2.38014561e-01],\n    [  1.93469895e-01,   7.85372670e-01,  -6.87807929e-02],\n    [  1.28005405e-01,   9.72510108e-01,   1.94521731e-01],\n    [  2.59586513e-01,   8.95602508e-01,   3.61263047e-01],\n    [  3.03673017e-01,   7.51297047e-01,   5.85948332e-01],\n    [  2.38520764e-01,   9.71027131e-01,   1.46341398e-02],\n    [  3.84074854e-01,  -8.31551082e-01,  -4.01259647e-01],\n    [  4.13923711e-01,  -8.89137254e-01,  -1.95197607e-01],\n    [  4.53213495e-01,  -8.91167829e-01,   2.04310780e-02],\n    [  2.81856189e-01,  -9.48065186e-01,   1.47409267e-01],\n    [  4.10774439e-01,  -7.72051309e-01,   4.84975398e-01],\n    [  5.07299892e-01,  -6.19496981e-01,  -5.99057852e-01],\n    [  4.31433334e-01,  -5.95575682e-01,  -3.92692715e-01],\n    [  4.08083088e-01,  -6.51386299e-01,  -1.87980140e-01],\n    [  4.02323355e-01,  -7.02571172e-01,   1.91533619e-02],\n    [  4.44254565e-01,  -8.56088417e-01,   2.64103208e-01],\n    [  4.65500718e-01,  -5.51798409e-01,   4.34918703e-01],\n    [  3.82917700e-01,  -6.42536357e-01,   6.63717609e-01],\n    [  4.83513927e-01,  -4.23010976e-01,  -7.66339348e-01],\n    [  4.17627918e-01,  -4.27370036e-01,  -5.50514685e-01],\n    [  3.77960799e-01,  -4.21545760e-01,  -3.12935201e-01],\n    [  5.01986263e-01,  -3.72801757e-01,  -1.34702273e-01],\n    [  3.35515185e-01,  -4.74157052e-01,  -7.82543895e-02],\n    [  4.86118021e-01,  -3.67092357e-01,   1.16359356e-01],\n    [  3.61077213e-01,  -4.21637977e-01,   3.08204120e-01],\n    [  4.16969173e-01,  -3.85761421e-01,   5.58811541e-01],\n    [  4.16529017e-01,  -4.37785083e-01,   7.96773368e-01],\n    [  4.48997766e-01,  -2.11045782e-01,  -8.68251510e-01],\n    [  3.35477205e-01,  -2.61430553e-01,  -6.74970497e-01],\n    [  3.66362329e-01,  -2.22560833e-01,  -4.34081998e-01],\n    [  3.74108151e-01,  -1.71647694e-01,  -2.03504673e-01],\n    [  3.57819189e-01,  -2.49803706e-01,   2.19688702e-02],\n    [  3.61600941e-01,  -1.66483260e-01,   2.28496360e-01],\n    [  5.05691199e-01,  -2.54501047e-01,   3.39829791e-01],\n    [  3.62776630e-01,  -2.09215076e-01,   6.75053746e-01],\n    [  3.95673292e-01,  -2.08362685e-01,   8.94442640e-01],\n    [  6.03722991e-01,  -2.78474122e-02,  -7.96707646e-01],\n    [  4.40265013e-01,  -7.86809664e-02,  -6.55722319e-01],\n    [  4.39918700e-01,  -2.76851361e-02,  -4.13347503e-01],\n    [  3.29102633e-01,   4.00043008e-02,  -2.11496211e-01],\n    [  4.05774419e-01,  -2.14033991e-02,   4.17895530e-03],\n    [  3.43817328e-01,   5.31430401e-02,   2.18404731e-01],\n    [  3.96753464e-01,  -6.61189080e-02,   4.47365482e-01],\n    [  4.06048426e-01,  -1.47617094e-02,   6.86995863e-01],\n    [  5.44822069e-01,   1.06719344e-01,   8.31733066e-01],\n    [  3.75600036e-01,   2.54440923e-01,  -8.91170258e-01],\n    [  4.63684484e-01,   1.47006457e-01,  -7.51528889e-01],\n    [  3.45634061e-01,   1.25786727e-01,  -5.64749471e-01],\n    [  4.33771745e-01,   2.04274120e-01,  -3.49729146e-01],\n    [  3.89430106e-01,   2.08809630e-01,  -6.30142458e-02],\n    [  4.89357046e-01,   2.47087213e-01,   2.84235835e-01],\n    [  3.90697387e-01,   1.41030133e-01,   4.63836968e-01],\n    [  4.12857161e-01,   1.92822910e-01,   6.59096000e-01],\n    [  3.96276758e-01,   2.53444744e-01,   8.82457077e-01],\n    [  3.61150908e-01,   4.97243556e-01,  -7.88871895e-01],\n    [  3.26489787e-01,   3.30981779e-01,  -6.53409650e-01],\n    [  4.51588202e-01,   4.29381991e-01,  -4.71925699e-01],\n    [  4.56266502e-01,   3.74805015e-01,  -2.06872348e-01],\n    [  3.68187497e-01,   4.73294103e-01,  -2.70325319e-02],\n    [  3.71244032e-01,   4.52825679e-01,   2.00185303e-01],\n    [  3.04632697e-01,   3.52977527e-01,   3.88234862e-01],\n    [  4.14725316e-01,   3.85903492e-01,   5.70635473e-01],\n    [  4.45573712e-01,   4.55986081e-01,   7.70415968e-01],\n    [  5.29294460e-01,   5.68004268e-01,  -6.30252748e-01],\n    [  3.36555342e-01,   6.17472970e-01,  -4.19001181e-01],\n    [  3.79508522e-01,   6.72901111e-01,  -1.89083677e-01],\n    [  3.79210104e-01,   7.03099639e-01,   2.89663510e-02],\n    [  3.71716774e-01,   6.80534148e-01,   2.44314680e-01],\n    [  4.55066493e-01,   5.31958561e-01,   4.03363561e-01],\n    [  4.74304364e-01,   6.32321249e-01,   6.12539966e-01],\n    [  5.09305522e-01,   7.26531737e-01,  -4.61258627e-01],\n    [  4.14769083e-01,   8.52822936e-01,  -3.17269046e-01],\n    [  4.18775018e-01,   9.02581503e-01,  -9.98704997e-02],\n    [  3.94632869e-01,   9.03066896e-01,   1.69514249e-01],\n    [  4.61852907e-01,   7.86451245e-01,   4.10105270e-01],\n    [  5.78765894e-01,  -7.48034418e-01,  -3.24768457e-01],\n    [  6.22385024e-01,  -7.74420626e-01,  -1.13620313e-01],\n    [  6.24238372e-01,  -7.71683267e-01,   1.21784198e-01],\n    [  6.66471014e-01,  -5.96428244e-01,  -4.47313912e-01],\n    [  6.01827871e-01,  -5.26836192e-01,  -2.07474643e-01],\n    [  5.59753905e-01,  -5.67596319e-01,   1.41632354e-02],\n    [  6.15626467e-01,  -4.74253980e-01,   2.38852068e-01],\n    [  5.96741406e-01,  -7.18050370e-01,   3.58194584e-01],\n    [  6.64541504e-01,  -4.34981561e-01,  -6.07598249e-01],\n    [  5.94037691e-01,  -3.84380270e-01,  -3.94164624e-01],\n    [  7.37730601e-01,  -3.09096499e-01,  -2.03560626e-01],\n    [  7.00473230e-01,  -3.99470459e-01,   7.71399518e-03],\n    [  7.25721545e-01,  -2.69961817e-01,   2.12956514e-01],\n    [  6.25103528e-01,  -3.50378404e-01,   4.31830925e-01],\n    [  5.75160121e-01,  -5.28839768e-01,   6.24114841e-01],\n    [  6.37504952e-01,  -2.38247168e-01,  -7.32683917e-01],\n    [  5.56308954e-01,  -2.19302319e-01,  -5.54146677e-01],\n    [  6.91335007e-01,  -1.31863121e-01,  -4.04903216e-01],\n    [  5.50450679e-01,  -2.16461343e-01,  -2.95913339e-01],\n    [  5.76819434e-01,  -1.89855295e-01,  -6.06792724e-02],\n    [  5.68002996e-01,  -1.30984565e-01,   1.46840964e-01],\n    [  7.05672069e-01,  -1.11125915e-01,   3.88609961e-01],\n    [  5.53006773e-01,  -1.71911867e-01,   5.61461544e-01],\n    [  5.86529417e-01,  -3.17786124e-01,   7.44980015e-01],\n    [  6.75938804e-01,   1.80420223e-01,  -7.14531509e-01],\n    [  5.94310637e-01,   3.60783005e-02,  -5.54992825e-01],\n    [  6.74403271e-01,   9.99821774e-02,  -3.80833274e-01],\n    [  5.47740687e-01,   2.16242697e-03,  -2.17769258e-01],\n    [  6.52781177e-01,   1.98788373e-02,  -2.04197681e-02],\n    [  5.43580756e-01,   6.80793881e-03,   3.12214905e-01],\n    [  5.94453493e-01,   5.82306725e-02,   5.52769782e-01],\n    [  7.05676016e-01,   8.06852878e-02,   7.03925597e-01],\n    [  5.80128414e-01,  -1.09106402e-01,   8.07184499e-01],\n    [  5.42599917e-01,   3.68797555e-01,  -7.54701063e-01],\n    [  5.43213272e-01,   2.48965238e-01,  -5.43315884e-01],\n    [  7.72945716e-01,   1.57594270e-01,  -2.09995542e-01],\n    [  5.70040110e-01,   1.93374118e-01,  -1.49210043e-01],\n    [  5.37514570e-01,   3.43582877e-01,   5.02783860e-02],\n    [  5.49305756e-01,   1.26433159e-01,   1.10900907e-01],\n    [  7.04438426e-01,   1.39590407e-01,   3.72156642e-01],\n    [  5.76275599e-01,   2.97362798e-01,   4.81580372e-01],\n    [  6.07520197e-01,   3.02951102e-01,   7.34261425e-01],\n    [  6.99631576e-01,   4.05751597e-01,  -5.88116740e-01],\n    [  6.49674674e-01,   3.24746847e-01,  -3.61182477e-01],\n    [  6.90837315e-01,   3.92507432e-01,  -1.56065623e-01],\n    [  7.07297492e-01,   4.39430221e-01,   7.45724157e-02],\n    [  7.34624861e-01,   2.33302182e-01,   1.97568405e-01],\n    [  6.20925127e-01,   4.17105125e-01,   2.91445572e-01],\n    [  6.39732628e-01,   4.85274250e-01,   5.96029419e-01],\n    [  6.84957031e-01,   5.81126866e-01,  -4.39460386e-01],\n    [  5.42010952e-01,   5.44683144e-01,  -3.00708180e-01],\n    [  5.65520586e-01,   5.71231502e-01,  -7.77148654e-02],\n    [  5.42658377e-01,   5.80522401e-01,   1.50091398e-01],\n    [  6.45989977e-01,   6.39765095e-01,   4.16410344e-01],\n    [  6.11253189e-01,   7.58595339e-01,  -2.25616158e-01],\n    [  5.69866140e-01,   8.21707530e-01,   7.02263904e-03],\n    [  5.96124408e-01,   7.72064856e-01,   2.20344163e-01],\n    [  7.67799046e-01,  -5.95832390e-01,  -2.35517280e-01],\n    [  7.81073661e-01,  -6.23989069e-01,  -2.36976405e-02],\n    [  7.54107633e-01,  -6.22427589e-01,   2.09536569e-01],\n    [  8.15394602e-01,  -4.29633749e-01,  -3.88003204e-01],\n    [  8.95007919e-01,  -4.24199922e-01,  -1.37895800e-01],\n    [  8.74435840e-01,  -4.68344016e-01,   1.26553719e-01],\n    [  8.44167782e-01,  -4.13217508e-01,   3.41514343e-01],\n    [  7.04143218e-01,  -5.56758821e-01,   4.40683494e-01],\n    [  7.99734351e-01,  -2.56440289e-01,  -5.42829021e-01],\n    [  9.12597668e-01,  -2.34115328e-01,  -3.35194734e-01],\n    [  9.73305104e-01,  -1.91998325e-01,  -1.25753000e-01],\n    [  7.84069900e-01,  -1.86036000e-01,   1.64746740e-02],\n    [  9.45975754e-01,  -2.08962721e-01,   2.47920258e-01],\n    [  8.65952407e-01,  -2.10822307e-01,   4.53519992e-01],\n    [  7.36999230e-01,  -3.54489940e-01,   5.75472865e-01],\n    [  7.69123269e-01,  -4.39753477e-02,  -6.37585733e-01],\n    [  8.89753514e-01,  -2.12586220e-02,  -4.55946002e-01],\n    [  7.61925948e-01,  -8.17295281e-02,  -2.12584521e-01],\n    [  8.66579005e-01,   1.98271619e-02,  -2.65740411e-02],\n    [  7.71523404e-01,  -7.95716890e-03,   1.88624841e-01],\n    [  8.49571672e-01,   3.23679601e-02,   5.26479144e-01],\n    [  7.49529893e-01,  -1.37902806e-01,   6.47447107e-01],\n    [  8.13235532e-01,   2.00148039e-01,  -5.46432733e-01],\n    [  9.17920585e-01,   1.93823705e-01,  -3.46199610e-01],\n    [  9.83931066e-01,   1.52608447e-01,  -9.26839775e-02],\n    [  7.63532804e-01,   2.26521101e-01,   5.58254364e-04],\n    [  9.73502133e-01,   1.58275744e-01,   1.65052678e-01],\n    [  8.97439314e-01,   2.27709113e-01,   3.77824349e-01],\n    [  7.73925499e-01,   2.60018372e-01,   5.77433777e-01],\n    [  8.30247868e-01,   4.04004588e-01,  -3.84016628e-01],\n    [  9.13121193e-01,   3.66274908e-01,  -1.79031780e-01],\n    [  8.53887365e-01,   5.20387043e-01,  -8.58448400e-03],\n    [  8.84701271e-01,   4.04948543e-01,   2.30911967e-01],\n    [  7.88854593e-01,   4.39735361e-01,   4.29349790e-01],\n    [  7.78387183e-01,   5.87137721e-01,  -2.22222165e-01],\n    [  7.24110054e-01,   6.89672471e-01,   4.06359182e-03],\n    [  7.67279635e-01,   6.00429434e-01,   2.25314127e-01],\n    [  9.50421204e-01,  -3.05112721e-01,   6.00480158e-02],\n    [  9.68789916e-01,  -7.78690666e-03,  -2.47760899e-01],\n    [  9.94841187e-01,  -5.83791196e-02,   8.29632029e-02],\n    [  9.43181204e-01,  -1.36099283e-03,   3.32276037e-01],\n    [  9.43413869e-01,   3.28538485e-01,   4.50858739e-02]\n])\n\n_connectivity = np.array([\n    [ 95, 162, 167, 161],\n    [239, 162, 167, 161],\n    [342, 420, 427, 421],\n    [273, 264, 272, 263],\n    [180, 264, 272, 263],\n    [243, 164, 234, 242],\n    [243, 330, 234, 242],\n    [190, 180, 264, 272],\n    [238, 160, 167, 161],\n    [522, 521, 530, 469],\n    [522, 521, 468, 469],\n    [ 94,  95, 167, 161],\n    [ 94, 160, 167, 161],\n    [ 94, 160,  98, 167],\n    [466, 529, 465, 457],\n    [466, 529, 465, 475],\n    [474, 529, 465, 475],\n    [474, 464, 465, 391],\n    [398, 408, 409, 481],\n    [398, 408, 409, 326],\n    [398, 325, 408, 326],\n    [153, 238, 239, 161],\n    [  1,  77,  15,  14],\n    [  1,  21,  77,  14],\n    [335, 243, 330, 242],\n    [335, 330, 413, 404],\n    [335, 330, 413, 336],\n    [335, 243, 330, 336],\n    [148, 156, 147, 232],\n    [476, 521, 530, 469],\n    [476, 521, 468, 469],\n    [476, 477, 483, 469],\n    [476, 477, 483, 411],\n    [585, 527, 564, 572],\n    [574, 579, 532, 575],\n    [412, 484, 413, 404],\n    [412, 335, 413, 404],\n    [412, 477, 484, 404],\n    [412, 477, 483, 411],\n    [412, 477, 484, 483],\n    [ 92,  93, 235, 158],\n    [ 92,  93, 235, 150],\n    [ 92,  87,  93, 150],\n    [ 92,  87,  93,  39],\n    [244, 243, 330, 234],\n    [244, 235, 234, 158],\n    [244, 243, 164, 234],\n    [244, 165, 243, 164],\n    [244, 164, 234, 158],\n    [244, 165, 164, 158],\n    [ 89,  94,  41,  95],\n    [237, 238, 160, 161],\n    [237, 153, 238, 161],\n    [237, 153, 228, 316],\n    [237, 153, 238, 316],\n    [ 35,  87,  39,  36],\n    [ 30,  35,   4,  36],\n    [ 30,  35,  87,  36],\n    [ 30,  78,  23,  70],\n    [ 96,  92, 163,  91],\n    [ 96,  92, 163, 158],\n    [ 96,  92,  93, 158],\n    [ 96,  92,  93,  39],\n    [233, 164, 234, 242],\n    [254, 262, 263, 255],\n    [248, 244, 165, 243],\n    [248, 330, 331, 336],\n    [248, 244, 330, 331],\n    [248, 243, 330, 336],\n    [248, 244, 243, 330],\n    [536, 474, 529, 475],\n    [334, 238, 325, 326],\n    [460, 522, 468, 469],\n    [534, 527, 578, 572],\n    [534, 527, 564, 572],\n    [534, 527, 564, 526],\n    [534, 564, 526, 516],\n    [573, 527, 578, 572],\n    [381, 464, 390, 391],\n    [381, 464, 465, 391],\n    [381, 382, 465, 391],\n    [306, 381, 390, 391],\n    [528, 536, 474, 481],\n    [528, 573, 527, 578],\n    [528, 573, 536, 578],\n    [528, 474, 464, 465],\n    [528, 536, 474, 529],\n    [528, 573, 536, 529],\n    [528, 474, 529, 465],\n    [399, 398, 326, 316],\n    [399, 398, 409, 326],\n    [399, 317, 307, 391],\n    [399, 317, 409, 326],\n    [221, 231, 230, 239],\n    [359, 273, 272, 263],\n    [359, 369, 273, 272],\n    [ 16,  23,  15,   2],\n    [123, 197, 122, 132],\n    [ 76, 148, 216, 149],\n    [ 76, 148, 216, 140],\n    [ 84,  76,  27, 149],\n    [ 84,  27,  91, 149],\n    [ 84, 156,  91, 149],\n    [ 84, 148, 156, 149],\n    [ 84, 148, 156, 147],\n    [ 84,  76, 148, 149],\n    [ 84,  76, 148, 147],\n    [ 84,  76, 147,  75],\n    [ 84,  76,  20,  75],\n    [ 84,  76,  20,  27],\n    [223, 148, 147, 232],\n    [329, 412, 328, 404],\n    [329, 412, 335, 404],\n    [467, 476, 521, 468],\n    [467, 521, 458, 468],\n    [467, 393, 458, 468],\n    [299, 300, 214, 288],\n    [386, 477, 396, 469],\n    [386, 313, 387, 302],\n    [386, 313, 387, 396],\n    [386, 460, 468, 469],\n    [ 58, 122, 121, 112],\n    [215, 206, 216, 140],\n    [215, 206, 216, 302],\n    [403, 467, 476, 411],\n    [403, 476, 468, 469],\n    [403, 467, 476, 468],\n    [403, 386, 468, 469],\n    [403, 386, 477, 469],\n    [403, 412, 328, 411],\n    [403, 412, 477, 411],\n    [403, 476, 477, 469],\n    [403, 476, 477, 411],\n    [403, 386, 477, 396],\n    [142, 134, 133,  70],\n    [571, 585, 564, 584],\n    [571, 585, 564, 572],\n    [571, 534, 564, 572],\n    [557, 585, 564, 584],\n    [557, 556, 564, 584],\n    [582, 557, 556, 584],\n    [489, 420, 427, 421],\n    [146, 221, 231, 230],\n    [338, 342, 420, 341],\n    [338, 342, 420, 421],\n    [340, 249, 254, 341],\n    [424, 425, 491, 488],\n    [424, 425, 419, 488],\n    [204, 194, 203, 288],\n    [204, 214, 203, 288],\n    [204, 300, 214, 288],\n    [204, 194, 120, 203],\n    [204, 194, 121, 120],\n    [494, 495, 547, 552],\n    [350, 254, 262, 263],\n    [350, 254, 263, 255],\n    [350, 254, 342, 255],\n    [348, 342, 420, 341],\n    [348, 419, 420, 341],\n    [348, 254, 342, 341],\n    [348, 350, 254, 342],\n    [348, 340, 419, 341],\n    [348, 340, 254, 341],\n    [  8,   5,  52,   9],\n    [ 10,  16,  52,   9],\n    [115, 107, 179,  47],\n    [115, 107, 180, 179],\n    [415, 418, 331, 336],\n    [415, 337, 325, 416],\n    [414, 330, 413, 336],\n    [414, 418, 413, 336],\n    [414, 330, 331, 336],\n    [414, 418, 331, 336],\n    [414, 330, 413, 404],\n    [414, 418, 485, 413],\n    [531, 567, 574, 568],\n    [531, 574, 579, 532],\n    [531, 522, 530, 469],\n    [531, 567, 522, 530],\n    [531, 567, 574, 530],\n    [538, 477, 483, 469],\n    [538, 477, 396, 469],\n    [538, 531, 579, 532],\n    [303, 313, 387, 302],\n    [303, 388, 304, 293],\n    [322, 244, 330, 234],\n    [322, 244, 235, 234],\n    [322, 312, 235, 234],\n    [ 34,  92,  87,  39],\n    [ 34,  35,  87,  39],\n    [ 34,  96,  92,  91],\n    [ 34,  96,  92,  39],\n    [166, 248, 244, 165],\n    [166, 160,  98, 167],\n    [159,  93, 235, 158],\n    [159, 244, 235, 158],\n    [159, 244, 165, 158],\n    [159, 166, 165,  98],\n    [159, 166, 244, 165],\n    [159, 166, 160,  98],\n    [143, 237, 153, 161],\n    [143, 237, 153, 228],\n    [332, 248, 244, 331],\n    [332, 166, 248, 244],\n    [332, 159, 166, 244],\n    [332, 159, 237, 160],\n    [332, 159, 166, 160],\n    [332, 237, 238, 160],\n    [ 29,   4,  23,   2],\n    [ 29,  30,   4,  23],\n    [ 29,  23,  15,   2],\n    [ 29,  23,  77,  15],\n    [ 29,   1,  77,  15],\n    [ 79,  87,  93, 150],\n    [ 79,  30,  78,  70],\n    [ 79,  30,  78,  87],\n    [ 79,  78,  87, 150],\n    [ 79, 143, 218, 134],\n    [ 79, 142,  78,  70],\n    [ 79, 142,  78, 150],\n    [ 79, 142, 218, 150],\n    [ 79, 142, 134,  70],\n    [ 79, 142, 218, 134],\n    [ 97, 165, 164, 158],\n    [ 97, 163, 164, 158],\n    [ 97,  96, 163, 158],\n    [ 97, 159, 165, 158],\n    [ 97, 159, 165,  98],\n    [ 97,  96,  93, 158],\n    [ 97, 159,  93, 158],\n    [ 97, 159,  93,  98],\n    [ 97,  40,  93,  98],\n    [ 97,  96,  93,  39],\n    [ 97,  40,  93,  39],\n    [226, 148, 216, 149],\n    [226,  92, 235, 150],\n    [225, 148, 156, 232],\n    [225, 233, 156, 232],\n    [225, 233, 241, 232],\n    [225, 226, 148, 216],\n    [225, 226, 312, 216],\n    [279, 204, 194, 121],\n    [279, 277, 194, 278],\n    [172, 254, 262, 255],\n    [172, 262, 263, 255],\n    [172, 262, 180, 263],\n    [172, 262, 180, 179],\n    [172, 107, 180, 179],\n    [482, 474, 409, 481],\n    [482, 474, 409, 475],\n    [482, 536, 474, 481],\n    [482, 536, 474, 475],\n    [533, 581, 540, 580],\n    [541, 482, 487, 481],\n    [541, 482, 536, 481],\n    [480, 487, 408, 481],\n    [480, 487, 408, 416],\n    [480, 486, 487, 416],\n    [480, 398, 408, 481],\n    [480, 541, 487, 481],\n    [462, 389, 525, 471],\n    [462, 389, 525, 526],\n    [462, 524, 525, 526],\n    [462, 388, 387, 452],\n    [462, 454, 389, 526],\n    [523, 531, 524, 532],\n    [523, 574, 532, 575],\n    [523, 531, 574, 532],\n    [523, 574, 568, 575],\n    [523, 531, 574, 568],\n    [523, 583, 568, 561],\n    [470, 524, 525, 532],\n    [470, 462, 525, 471],\n    [470, 462, 524, 525],\n    [470, 531, 524, 532],\n    [470, 538, 531, 532],\n    [470, 538, 396, 469],\n    [470, 538, 531, 469],\n    [246, 334, 337, 325],\n    [246, 334, 238, 325],\n    [246, 332, 238, 325],\n    [246, 332, 238, 160],\n    [246, 238, 160, 167],\n    [246, 332, 166, 160],\n    [246, 166, 160, 167],\n    [417, 408, 409, 326],\n    [417, 408, 409, 481],\n    [417, 482, 409, 481],\n    [417, 487, 408, 481],\n    [417, 482, 487, 481],\n    [417, 487, 408, 416],\n    [308, 317, 307, 391],\n    [308, 317, 230, 307],\n    [229, 317, 230, 307],\n    [229, 153, 307, 316],\n    [229, 221, 230, 239],\n    [229, 221, 153, 239],\n    [229, 221, 230, 307],\n    [229, 221, 153, 307],\n    [229, 399, 307, 316],\n    [229, 399, 317, 307],\n    [229, 231, 230, 239],\n    [229, 240, 231, 239],\n    [229, 317, 230, 318],\n    [229, 240, 317, 318],\n    [229, 153, 238, 316],\n    [229, 153, 238, 239],\n    [229, 238, 326, 316],\n    [229, 399, 326, 316],\n    [229, 399, 317, 326],\n    [229, 231, 230, 318],\n    [229, 240, 231, 318],\n    [229, 334, 238, 326],\n    [229, 240, 334, 326],\n    [229, 240, 334, 238],\n    [400, 399, 317, 391],\n    [400, 399, 317, 409],\n    [400, 474, 465, 391],\n    [400, 308, 317, 391],\n    [400, 401, 308, 317],\n    [400, 474, 409, 475],\n    [400, 382, 465, 391],\n    [400, 474, 465, 475],\n    [400, 308, 382, 391],\n    [400, 382, 466, 465],\n    [400, 466, 465, 475],\n    [400, 401, 466, 475],\n    [400, 308, 382, 392],\n    [400, 401, 308, 392],\n    [400, 382, 466, 392],\n    [400, 401, 466, 392],\n    [ 71, 134, 133,  70],\n    [ 71, 134, 125, 133],\n    [ 71,  60, 133,  70],\n    [208, 142, 134, 133],\n    [208, 142, 218, 134],\n    [208, 142, 133, 132],\n    [208, 303, 304, 293],\n    [503, 504, 496, 553],\n    [503, 504, 496, 431],\n    [560, 503, 504, 553],\n    [560, 503, 504, 449],\n    [569, 523, 568, 575],\n    [569, 523, 583, 568],\n    [459, 521, 458, 468],\n    [459, 522, 521, 468],\n    [459, 460, 522, 468],\n    [383, 466, 465, 457],\n    [383, 382, 466, 465],\n    [383, 382, 466, 392],\n    [518, 528, 529, 465],\n    [518, 528, 573, 529],\n    [518, 528, 573, 527],\n    [518, 528, 464, 465],\n    [518, 528, 464, 527],\n    [565, 585, 527, 572],\n    [565, 573, 527, 572],\n    [565, 518, 573, 527],\n    [219, 218, 304, 228],\n    [219, 208, 218, 304],\n    [219, 208, 218, 134],\n    [219, 208, 304, 293],\n    [380, 306, 381, 390],\n    [315, 399, 398, 316],\n    [535, 534, 527, 578],\n    [535, 528, 527, 578],\n    [535, 533, 581, 540],\n    [535, 533, 525, 540],\n    [535, 533, 534, 525],\n    [535, 541, 540, 481],\n    [535, 528, 536, 481],\n    [535, 528, 536, 578],\n    [535, 541, 581, 578],\n    [535, 541, 581, 540],\n    [535, 541, 536, 481],\n    [535, 541, 536, 578],\n    [473, 474, 409, 481],\n    [473, 399, 390, 391],\n    [473, 528, 474, 481],\n    [473, 528, 474, 464],\n    [473, 464, 390, 391],\n    [473, 474, 464, 391],\n    [473, 398, 409, 481],\n    [473, 399, 398, 409],\n    [473, 315, 399, 390],\n    [473, 315, 399, 398],\n    [473, 400, 474, 409],\n    [473, 400, 399, 409],\n    [473, 400, 474, 391],\n    [473, 400, 399, 391],\n    [473, 315, 389, 390],\n    [368, 359, 369, 272],\n    [124, 123,  60, 133],\n    [124, 123, 133, 132],\n    [124, 123, 197, 132],\n    [113, 105,  46, 179],\n    [113, 123, 197, 122],\n    [113,  58, 122, 112],\n    [ 68,   1,  21,  77],\n    [ 68,  20,   1,  21],\n    [ 68,  20,   1,  28],\n    [ 68,  20,  27,  28],\n    [ 68,  76,  20,  27],\n    [ 67,  66,  20,  75],\n    [ 67,  76,  20,  75],\n    [ 67,  66, 129,  75],\n    [ 67,  68,  76,  20],\n    [ 67,  76, 140,  75],\n    [ 67,  66,  20,  21],\n    [ 67,  68,  20,  21],\n    [ 67,  66, 129,  57],\n    [ 67,  66,  13,  21],\n    [ 67,  66,  13,  57],\n    [ 67,  58,  13,  57],\n    [139,  76, 148, 147],\n    [139, 223, 148, 147],\n    [139, 223, 138, 147],\n    [139, 138, 147,  75],\n    [139,  76, 147,  75],\n    [139,  76, 148, 140],\n    [139,  76, 140,  75],\n    [139, 129, 140,  75],\n    [139, 138, 129,  75],\n    [139, 223, 138, 214],\n    [139, 148, 216, 140],\n    [139, 215, 216, 140],\n    [139, 204, 215, 140],\n    [139, 204, 215, 300],\n    [139, 204, 300, 214],\n    [139, 138, 129, 203],\n    [139, 138, 214, 203],\n    [139, 204, 214, 203],\n    [310, 299, 300, 214],\n    [301, 215, 216, 302],\n    [301, 386, 313, 302],\n    [301, 312, 313, 302],\n    [301, 312, 216, 302],\n    [301, 225, 312, 216],\n    [365, 355, 364, 278],\n    [365, 277, 364, 278],\n    [365, 279, 277, 278],\n    [185, 279, 194, 278],\n    [185, 279, 259, 278],\n    [185, 194, 121, 120],\n    [185, 279, 194, 121],\n    [551, 494, 547, 552],\n    [551, 494, 547, 550],\n    [551, 494, 501, 552],\n    [509, 557, 585, 564],\n    [509, 585, 527, 564],\n    [509, 565, 585, 527],\n    [509, 446, 510, 501],\n    [509, 565, 518, 527],\n    [509, 518, 510, 566],\n    [509, 565, 518, 566],\n    [438, 446, 510, 501],\n    [438, 511, 510, 501],\n    [438, 511, 502, 439],\n    [555, 582, 556, 549],\n    [555, 582, 556, 584],\n    [222, 146, 231, 230],\n    [222, 231, 230, 318],\n    [212, 146, 221, 145],\n    [212, 137, 146, 145],\n    [212, 146, 221, 230],\n    [212, 222, 146, 230],\n    [212, 221, 230, 307],\n    [212, 308, 230, 307],\n    [212, 222, 308, 230],\n    [212, 222, 308, 298],\n    [155, 146, 221, 231],\n    [155, 146, 221, 145],\n    [155, 231, 239, 162],\n    [155, 221, 231, 239],\n    [430, 438, 502, 439],\n    [422, 338, 343, 255],\n    [422, 338, 342, 421],\n    [422, 342, 427, 421],\n    [422, 428, 427, 421],\n    [256, 344, 343, 257],\n    [256, 422, 344, 343],\n    [256, 264, 263, 255],\n    [256, 273, 264, 263],\n    [292, 303, 387, 302],\n    [292, 388, 377, 452],\n    [292, 282, 377, 293],\n    [292, 388, 387, 452],\n    [292, 303, 388, 387],\n    [292, 388, 377, 293],\n    [292, 303, 388, 293],\n    [205, 279, 204, 121],\n    [205, 215, 206, 140],\n    [205, 204, 215, 140],\n    [437, 369, 273, 361],\n    [437, 359, 369, 273],\n    [437, 359, 369, 436],\n    [437, 494, 446, 436],\n    [437, 494, 446, 501],\n    [437, 438, 446, 501],\n    [426, 493, 489, 427],\n    [426, 489, 420, 488],\n    [426, 489, 420, 427],\n    [426, 419, 420, 488],\n    [426, 425, 419, 488],\n    [426, 348, 419, 420],\n    [426, 348, 425, 419],\n    [426, 342, 420, 427],\n    [426, 348, 342, 420],\n    [426, 350, 342, 427],\n    [426, 348, 350, 342],\n    [499, 494, 547, 550],\n    [499, 493, 494, 547],\n    [499, 546, 547, 550],\n    [499, 546, 493, 547],\n    [499, 493, 494, 436],\n    [499, 546, 549, 550],\n    [499, 582, 549, 550],\n    [499, 582, 556, 549],\n    [490, 489, 427, 421],\n    [490, 493, 489, 427],\n    [490, 546, 493, 547],\n    [490, 546, 493, 489],\n    [490, 428, 427, 421],\n    [261, 348, 340, 254],\n    [261, 340, 253, 339],\n    [261, 340, 249, 254],\n    [261, 340, 249, 253],\n    [  6,  42,  43,  47],\n    [  6,  42,  46,  47],\n    [  6,  42,   5,  46],\n    [  6,   5,  52,   9],\n    [  6,  10,  52,   9],\n    [  6,  52,  46,  47],\n    [  6,   5,  52,  46],\n    [106, 107, 179,  47],\n    [106,  46, 179,  47],\n    [106,  42,  46,  47],\n    [106, 172, 107, 179],\n    [106, 105,  46, 179],\n    [106, 168, 172, 107],\n    [106, 105,  99,  46],\n    [106,  42,  99,  46],\n    [ 50,  58,  13,  57],\n    [ 22,   8,   9,  14],\n    [ 22,   8,  52,   9],\n    [ 22, 123,  60,  52],\n    [ 22,  77,  15,  14],\n    [ 22,   9,  15,  14],\n    [ 22,  16,  52,   9],\n    [ 22,  16,  60,  52],\n    [ 22,  16,  60,  23],\n    [ 22,  23,  77,  15],\n    [ 22,  16,  23,  15],\n    [  0,  10,  16,   9],\n    [  0,  22,   9,  15],\n    [  0,  22,  16,  15],\n    [  0,  22,  16,   9],\n    [  0,  16,  15,   2],\n    [  0,  17,  16,   2],\n    [  0,  10,  16,  11],\n    [  0,  17,  16,  11],\n    [ 53,  10,  16,  52],\n    [ 53,  16,  60,  52],\n    [ 53,   6,  52,  47],\n    [ 53,   6,  10,  52],\n    [ 53,   6,  43,  47],\n    [ 53,   6,  10,  43],\n    [ 53,  10,  43,   7],\n    [ 53,  10,  11,   7],\n    [ 53,  10,  16,  11],\n    [ 48, 116, 109,  55],\n    [ 48, 115, 107,  47],\n    [ 48,  53, 115,  47],\n    [ 48,  53,  43,  47],\n    [ 48,  53,  43,   7],\n    [245, 332, 166, 248],\n    [245, 248, 331, 336],\n    [245, 415, 331, 336],\n    [245, 332, 248, 331],\n    [245, 415, 325, 331],\n    [245, 415, 337, 325],\n    [245, 246, 332, 166],\n    [245, 332, 325, 331],\n    [245, 246, 337, 325],\n    [245, 246, 332, 325],\n    [479, 543, 486, 485],\n    [479, 486, 418, 485],\n    [479, 414, 418, 485],\n    [407, 415, 486, 418],\n    [407, 479, 486, 418],\n    [407, 479, 414, 418],\n    [407, 415, 418, 331],\n    [407, 414, 418, 331],\n    [407, 479, 480, 486],\n    [407, 415, 325, 331],\n    [407, 415, 486, 416],\n    [407, 480, 486, 416],\n    [407, 415, 325, 416],\n    [407, 325, 408, 416],\n    [407, 480, 408, 416],\n    [407, 398, 325, 408],\n    [407, 480, 398, 408],\n    [539, 542, 579, 580],\n    [539, 538, 542, 579],\n    [539, 538, 579, 532],\n    [539, 533, 540, 580],\n    [539, 470, 538, 532],\n    [539, 479, 540, 471],\n    [539, 543, 542, 580],\n    [539, 470, 525, 471],\n    [539, 470, 525, 532],\n    [539, 543, 540, 580],\n    [539, 479, 543, 540],\n    [539, 525, 540, 471],\n    [539, 533, 525, 540],\n    [539, 533, 525, 532],\n    [537, 531, 574, 579],\n    [537, 538, 531, 579],\n    [537, 531, 574, 530],\n    [537, 531, 530, 469],\n    [537, 538, 531, 469],\n    [537, 538, 542, 483],\n    [537, 538, 542, 579],\n    [537, 476, 530, 469],\n    [537, 476, 483, 469],\n    [537, 538, 483, 469],\n    [544, 480, 486, 487],\n    [544, 541, 581, 540],\n    [544, 480, 541, 487],\n    [544, 479, 543, 540],\n    [544, 479, 480, 540],\n    [544, 479, 543, 486],\n    [544, 479, 480, 486],\n    [544, 581, 540, 580],\n    [544, 543, 540, 580],\n    [544, 541, 540, 481],\n    [544, 480, 540, 481],\n    [544, 480, 541, 481],\n    [314, 315, 304, 228],\n    [323, 414, 330, 331],\n    [323, 244, 330, 331],\n    [323, 322, 244, 330],\n    [323, 407, 414, 331],\n    [323, 332, 244, 331],\n    [323, 407, 325, 331],\n    [323, 332, 325, 331],\n    [ 86,  34,  35,  28],\n    [ 86,  34,  35,  87],\n    [ 86,  29,  35,  28],\n    [ 86,  30,  78,  87],\n    [ 86,  30,  35,  87],\n    [ 86,  68,  78,  77],\n    [ 86,  68,   1,  77],\n    [ 86,  68,   1,  28],\n    [ 86,  29,   1,  77],\n    [ 86,  29,   1,  28],\n    [ 86,  78,  23,  77],\n    [ 86,  29,  23,  77],\n    [ 86,  30,  78,  23],\n    [ 86,  29,  30,  23],\n    [ 86,  30,  35,   4],\n    [ 86,  29,  35,   4],\n    [ 86,  29,  30,   4],\n    [151, 159,  93, 235],\n    [151,  93, 235, 150],\n    [151,  79,  93, 150],\n    [151,  79, 218, 150],\n    [151,  79, 143, 218],\n    [151, 143, 237, 228],\n    [151, 143, 218, 228],\n    [152, 159,  93,  98],\n    [152,  40,  93,  98],\n    [152,  40,  94,  98],\n    [152,  94, 160,  98],\n    [152, 159, 160,  98],\n    [152, 151, 159,  93],\n    [152, 159, 237, 160],\n    [152, 151, 159, 237],\n    [152,  89,  94,  41],\n    [152,  40,  94,  41],\n    [152,  94, 160, 161],\n    [152, 237, 160, 161],\n    [152, 143,  89, 161],\n    [152, 143, 237, 161],\n    [152, 151, 143, 237],\n    [152,  94,  95, 161],\n    [152,  89,  95, 161],\n    [152,  89,  94,  95],\n    [ 80,  79,  30,  70],\n    [ 80,  79, 143, 134],\n    [ 80, 144, 143,  89],\n    [ 80, 144, 143, 134],\n    [ 80,  79, 134,  70],\n    [ 80,  71, 134,  70],\n    [227, 226, 235, 150],\n    [227, 151, 235, 150],\n    [227, 151, 218, 150],\n    [227, 226, 312, 235],\n    [227, 142, 218, 150],\n    [227, 208, 218, 304],\n    [227, 208, 303, 304],\n    [227, 322, 312, 235],\n    [227, 218, 304, 228],\n    [227, 314, 304, 228],\n    [227, 314, 303, 304],\n    [227, 322, 312, 313],\n    [227, 314, 322, 313],\n    [227, 314, 303, 313],\n    [ 85,  86,  68,  78],\n    [ 85,  76,  27, 149],\n    [ 85,  68,  76,  27],\n    [ 85, 226,  92, 149],\n    [ 85, 226,  92, 150],\n    [ 85,  78,  87, 150],\n    [ 85,  92,  87, 150],\n    [ 85,  86,  78,  87],\n    [ 85,  86,  34,  87],\n    [ 85,  27,  91, 149],\n    [ 85,  34,  27,  91],\n    [ 85,  92,  91, 149],\n    [ 85,  34,  92,  91],\n    [ 85,  34,  92,  87],\n    [ 85,  34,  27,  28],\n    [ 85,  86,  34,  28],\n    [ 85,  68,  27,  28],\n    [ 85,  86,  68,  28],\n    [157, 233, 156, 163],\n    [157, 225, 233, 156],\n    [157, 225, 233, 234],\n    [157, 156,  91, 149],\n    [157, 156, 163,  91],\n    [157, 148, 156, 149],\n    [157, 225, 148, 156],\n    [157, 233, 164, 234],\n    [157, 233, 163, 164],\n    [157,  92,  91, 149],\n    [157,  92, 163,  91],\n    [157, 226,  92, 149],\n    [157, 226, 148, 149],\n    [157, 225, 226, 148],\n    [157, 164, 234, 158],\n    [157, 163, 164, 158],\n    [157, 225, 312, 234],\n    [157, 225, 226, 312],\n    [157,  92, 163, 158],\n    [157, 226,  92, 235],\n    [157, 312, 235, 234],\n    [157, 226, 312, 235],\n    [157, 235, 234, 158],\n    [157,  92, 235, 158],\n    [321, 233, 241, 242],\n    [321, 225, 233, 241],\n    [321, 233, 234, 242],\n    [321, 225, 233, 234],\n    [321, 329, 241, 242],\n    [321, 330, 234, 242],\n    [321, 335, 330, 242],\n    [321, 329, 335, 242],\n    [321, 225, 312, 234],\n    [321, 335, 330, 404],\n    [321, 329, 335, 404],\n    [321, 322, 330, 404],\n    [321, 322, 330, 234],\n    [321, 322, 312, 234],\n    [576, 533, 581, 580],\n    [576, 539, 579, 580],\n    [576, 539, 533, 580],\n    [576, 539, 579, 532],\n    [576, 539, 533, 532],\n    [576, 579, 532, 575],\n    [577, 533, 534, 586],\n    [577, 576, 533, 586],\n    [577, 576, 533, 581],\n    [577, 534, 578, 572],\n    [577, 535, 581, 578],\n    [577, 535, 533, 581],\n    [577, 535, 534, 578],\n    [577, 535, 533, 534],\n    [577, 571, 534, 572],\n    [577, 571, 534, 586],\n    [472, 473, 315, 398],\n    [472, 473, 315, 389],\n    [472, 479, 540, 471],\n    [472, 479, 480, 540],\n    [472, 525, 540, 471],\n    [472, 389, 525, 471],\n    [472, 480, 398, 481],\n    [472, 473, 398, 481],\n    [472, 535, 525, 540],\n    [472, 480, 540, 481],\n    [472, 535, 540, 481],\n    [570, 523, 524, 532],\n    [570, 524, 525, 532],\n    [570, 523, 524, 516],\n    [570, 533, 525, 532],\n    [570, 533, 534, 525],\n    [570, 524, 525, 526],\n    [570, 534, 525, 526],\n    [570, 533, 534, 586],\n    [570, 523, 532, 575],\n    [570, 524, 526, 516],\n    [570, 534, 526, 516],\n    [570, 576, 533, 532],\n    [570, 576, 533, 586],\n    [570, 576, 532, 575],\n    [570, 576, 586, 575],\n    [570, 569, 586, 575],\n    [570, 569, 523, 575],\n    [570, 571, 534, 586],\n    [570, 569, 571, 586],\n    [570, 534, 564, 516],\n    [570, 571, 534, 564],\n    [376, 292, 387, 452],\n    [376, 386, 387, 302],\n    [376, 292, 387, 302],\n    [247, 240, 334, 238],\n    [247, 229, 238, 239],\n    [247, 229, 240, 239],\n    [247, 229, 240, 238],\n    [247, 239, 167, 161],\n    [247, 238, 167, 161],\n    [247, 238, 239, 161],\n    [247, 246, 238, 167],\n    [247, 246, 334, 238],\n    [247, 239, 162, 167],\n    [247, 231, 239, 162],\n    [247, 240, 231, 239],\n    [410, 401, 317, 318],\n    [410, 400, 317, 409],\n    [410, 400, 401, 317],\n    [410, 482, 409, 475],\n    [410, 417, 482, 409],\n    [410, 400, 409, 475],\n    [410, 400, 401, 475],\n    [327, 240, 334, 326],\n    [327, 417, 409, 326],\n    [327, 410, 417, 409],\n    [327, 240, 317, 318],\n    [327, 410, 317, 318],\n    [327, 317, 409, 326],\n    [327, 410, 317, 409],\n    [327, 229, 317, 326],\n    [327, 229, 240, 326],\n    [327, 229, 240, 317],\n    [333, 417, 408, 326],\n    [333, 325, 408, 326],\n    [333, 417, 408, 416],\n    [333, 334, 325, 326],\n    [333, 334, 337, 325],\n    [333, 325, 408, 416],\n    [333, 337, 325, 416],\n    [333, 327, 334, 326],\n    [333, 327, 417, 326],\n    [309, 317, 230, 318],\n    [309, 308, 317, 230],\n    [309, 222, 230, 318],\n    [309, 222, 308, 230],\n    [309, 401, 317, 318],\n    [309, 401, 308, 317],\n    [309, 401, 308, 392],\n    [309, 308, 392, 298],\n    [309, 222, 308, 298],\n    [297, 212, 308, 298],\n    [297, 308, 392, 298],\n    [297, 308, 382, 392],\n    [154, 144, 221, 145],\n    [154, 155, 221, 145],\n    [154, 144, 221, 153],\n    [154, 155, 239, 162],\n    [154, 155, 221, 239],\n    [154,  95, 162, 161],\n    [154,  89,  95, 161],\n    [154, 239, 162, 161],\n    [154, 143, 153, 161],\n    [154, 143,  89, 161],\n    [154, 144, 143, 153],\n    [154, 144, 143,  89],\n    [154, 153, 239, 161],\n    [154, 221, 153, 239],\n    [ 61,  71, 125, 133],\n    [ 61,  71,  60, 133],\n    [ 61, 126, 116, 125],\n    [ 61, 126,  71, 125],\n    [ 61, 124, 125, 133],\n    [ 61, 124,  60, 133],\n    [ 61,  48, 116, 115],\n    [ 61,  48,  53, 115],\n    [ 24,  60,  23,  70],\n    [ 24,  71,  60,  70],\n    [ 24,  80,  71,  70],\n    [ 24,  80,  71,  72],\n    [ 24,  30,  23,  70],\n    [ 24,  16,  60,  23],\n    [ 24,  80,  30,  70],\n    [ 24,  31,  17,   2],\n    [ 24,  31,   3,  72],\n    [ 24,  31,  17,   3],\n    [ 24,  16,  23,   2],\n    [ 24,  17,  16,   2],\n    [ 24,   4,  23,   2],\n    [ 24,  30,   4,  23],\n    [ 24,  31,   4,   2],\n    [ 24,  31,  30,   4],\n    [512, 522, 521, 530],\n    [512, 567, 522, 530],\n    [512, 459, 521, 458],\n    [512, 459, 522, 521],\n    [512, 459, 373, 458],\n    [512, 459, 373, 449],\n    [497, 496, 553, 548],\n    [497, 504, 496, 553],\n    [497, 496, 491, 548],\n    [456, 518, 465, 457],\n    [456, 383, 465, 457],\n    [456, 381, 464, 465],\n    [456, 518, 464, 465],\n    [456, 383, 448, 371],\n    [456, 381, 382, 465],\n    [456, 383, 382, 465],\n    [456, 381, 382, 371],\n    [456, 383, 382, 371],\n    [136, 212, 137, 145],\n    [558, 509, 557, 585],\n    [558, 509, 565, 585],\n    [558, 509, 565, 566],\n    [519, 565, 518, 573],\n    [519, 518, 573, 529],\n    [519, 565, 518, 566],\n    [519, 529, 465, 457],\n    [519, 518, 465, 457],\n    [519, 518, 529, 465],\n    [519, 456, 518, 457],\n    [220, 144, 143, 134],\n    [220, 153, 307, 316],\n    [220, 221, 153, 307],\n    [220, 144, 221, 153],\n    [220, 153, 228, 316],\n    [220, 143, 153, 228],\n    [220, 144, 143, 153],\n    [220, 143, 218, 228],\n    [220, 143, 218, 134],\n    [220, 219, 218, 228],\n    [220, 219, 218, 134],\n    [445, 437, 369, 436],\n    [445, 437, 446, 436],\n    [305, 315, 399, 390],\n    [305, 306, 390, 391],\n    [305, 399, 390, 391],\n    [305, 315, 399, 316],\n    [305, 306, 307, 391],\n    [305, 399, 307, 391],\n    [305, 220, 219, 306],\n    [305, 399, 307, 316],\n    [305, 219, 304, 228],\n    [305, 315, 304, 228],\n    [305, 220, 219, 228],\n    [305, 220, 307, 316],\n    [305, 220, 306, 307],\n    [305, 315, 228, 316],\n    [305, 220, 228, 316],\n    [463, 535, 528, 527],\n    [463, 528, 464, 527],\n    [463, 534, 527, 526],\n    [463, 535, 534, 527],\n    [463, 534, 525, 526],\n    [463, 535, 534, 525],\n    [463, 389, 525, 526],\n    [463, 454, 389, 526],\n    [463, 473, 528, 464],\n    [463, 472, 389, 525],\n    [463, 472, 535, 525],\n    [463, 472, 473, 389],\n    [463, 535, 528, 481],\n    [463, 473, 528, 481],\n    [463, 454, 464, 390],\n    [463, 454, 389, 390],\n    [463, 473, 464, 390],\n    [463, 473, 389, 390],\n    [463, 472, 535, 481],\n    [463, 472, 473, 481],\n    [271, 359, 272, 263],\n    [271, 368, 359, 272],\n    [271, 350, 359, 263],\n    [271, 180, 272, 263],\n    [271, 262, 180, 263],\n    [271, 350, 262, 263],\n    [198, 124, 197, 132],\n    [198, 208, 133, 132],\n    [198, 124, 133, 132],\n    [189, 116, 115, 190],\n    [189, 190, 180, 272],\n    [189, 115, 190, 180],\n    [189,  61, 116, 125],\n    [189,  61, 124, 125],\n    [189,  61, 116, 115],\n    [189,  61, 124, 115],\n    [114, 113,  46, 179],\n    [114,  46, 179,  47],\n    [114, 124, 197, 179],\n    [114, 113, 197, 179],\n    [114, 115, 179,  47],\n    [114, 124, 115, 179],\n    [114,  52,  46,  47],\n    [114, 124, 123, 197],\n    [114, 113, 123, 197],\n    [114, 123,  60,  52],\n    [114, 124, 123,  60],\n    [114,  53,  52,  47],\n    [114,  53, 115,  47],\n    [114,  61, 124, 115],\n    [114,  61, 124,  60],\n    [114,  61,  53, 115],\n    [114,  53,  60,  52],\n    [114,  61,  53,  60],\n    [ 69,  68,  78,  77],\n    [ 69,  78,  23,  77],\n    [ 69,  22, 123,  60],\n    [ 69, 123,  60, 133],\n    [ 69, 123, 133, 132],\n    [ 69,  22,  23,  77],\n    [ 69,  22,  60,  23],\n    [ 69,  78,  23,  70],\n    [ 69,  60,  23,  70],\n    [ 69,  60, 133,  70],\n    [ 69, 142, 133, 132],\n    [ 69, 142, 133,  70],\n    [ 69, 142,  78,  70],\n    [130,  67, 129,  57],\n    [130,  67,  58,  57],\n    [130,  58, 121,  57],\n    [130, 120, 129,  57],\n    [130, 121, 120,  57],\n    [130, 204, 121, 120],\n    [130, 129, 140,  75],\n    [130,  67, 140,  75],\n    [130,  67, 129,  75],\n    [130,  58, 122, 121],\n    [130, 120, 129, 203],\n    [130, 204, 120, 203],\n    [130, 139, 129, 140],\n    [130, 139, 204, 140],\n    [130, 205, 204, 140],\n    [130, 205, 204, 121],\n    [130, 205, 122, 140],\n    [130, 205, 122, 121],\n    [130, 139, 129, 203],\n    [130, 139, 204, 203],\n    [ 59,  67,  13,  21],\n    [ 59,  68,  21,  77],\n    [ 59,  67,  68,  21],\n    [ 59,  21,  77,  14],\n    [ 59,  22,  77,  14],\n    [ 59,  13,  21,  14],\n    [ 59,  69,  68,  77],\n    [ 59,  67,  58,  13],\n    [ 59,   8,  13,  14],\n    [ 59,  22,   8,  14],\n    [ 59,  69,  22,  77],\n    [ 59,  69,  22, 123],\n    [ 59,  58, 123, 122],\n    [402, 403, 328, 411],\n    [402, 403, 467, 411],\n    [224, 223, 148, 232],\n    [224, 225, 148, 232],\n    [224, 225, 148, 216],\n    [224, 301, 225, 216],\n    [224, 310, 300, 214],\n    [224, 310, 223, 214],\n    [224, 139, 223, 148],\n    [224, 139, 148, 216],\n    [224, 139, 300, 214],\n    [224, 139, 223, 214],\n    [224, 139, 215, 300],\n    [224, 139, 215, 216],\n    [224, 301, 215, 216],\n    [385, 386, 460, 468],\n    [385, 301, 386, 302],\n    [385, 301, 215, 302],\n    [385, 376, 386, 460],\n    [385, 376, 386, 302],\n    [395, 301, 386, 313],\n    [395, 301, 403, 386],\n    [395, 386, 313, 396],\n    [395, 403, 386, 396],\n    [395, 301, 312, 313],\n    [395, 477, 396, 404],\n    [395, 403, 477, 396],\n    [395, 321, 322, 404],\n    [395, 322, 312, 313],\n    [395, 321, 322, 312],\n    [395, 412, 477, 404],\n    [395, 403, 412, 477],\n    [395, 412, 328, 404],\n    [395, 403, 412, 328],\n    [395, 329, 328, 404],\n    [395, 321, 329, 404],\n    [395, 321, 329, 328],\n    [347, 424, 425, 419],\n    [347, 348, 340, 419],\n    [347, 348, 425, 419],\n    [177, 252, 169, 176],\n    [177, 113, 105, 112],\n    [177, 252, 169, 253],\n    [500, 551, 494, 501],\n    [500, 494, 446, 501],\n    [500, 509, 446, 501],\n    [500, 558, 551, 501],\n    [500, 558, 509, 501],\n    [500, 558, 551, 557],\n    [500, 558, 509, 557],\n    [500, 494, 446, 436],\n    [500, 499, 494, 436],\n    [500, 551, 494, 550],\n    [500, 499, 494, 550],\n    [500, 551, 557, 550],\n    [500, 557, 556, 564],\n    [500, 509, 557, 564],\n    [500, 507, 499, 556],\n    [500, 499, 582, 550],\n    [500, 499, 582, 556],\n    [500, 582, 557, 550],\n    [500, 582, 557, 556],\n    [517, 518, 464, 527],\n    [517, 509, 518, 527],\n    [517, 509, 446, 510],\n    [517, 509, 518, 510],\n    [517, 463, 464, 527],\n    [517, 463, 454, 464],\n    [517, 463, 527, 526],\n    [517, 463, 454, 526],\n    [517, 527, 564, 526],\n    [517, 509, 527, 564],\n    [447, 438, 511, 510],\n    [447, 511, 448, 439],\n    [447, 438, 511, 439],\n    [447, 438, 362, 439],\n    [447, 456, 448, 371],\n    [447, 363, 448, 439],\n    [447, 363, 362, 439],\n    [447, 456, 381, 371],\n    [447, 363, 448, 371],\n    [447, 363, 362, 371],\n    [447, 285, 381, 371],\n    [447, 285, 362, 371],\n    [545, 499, 546, 549],\n    [108, 190, 180, 264],\n    [108, 115, 190, 180],\n    [108, 115, 107, 180],\n    [108,  48, 115, 107],\n    [108, 116, 109, 190],\n    [108,  48, 116, 109],\n    [108, 116, 115, 190],\n    [108,  48, 116, 115],\n    [295, 294, 380, 306],\n    [295, 380, 306, 381],\n    [295, 285, 380, 381],\n    [ 37,  30,   4,  36],\n    [ 37,  31,  30,   4],\n    [ 54,  18,  17,   3],\n    [ 54,  24,   3,  72],\n    [ 54,  24,  17,   3],\n    [ 54,  24,  71,  72],\n    [ 54,  24,  17,  16],\n    [ 54, 126,  71,  72],\n    [ 54,  17,  16,  11],\n    [ 54,  18,  17,  11],\n    [ 54,  61, 126,  71],\n    [ 54,  24,  16,  60],\n    [ 54,  24,  71,  60],\n    [ 54,  61,  71,  60],\n    [ 54,  53,  16,  11],\n    [ 54,  53,  16,  60],\n    [ 54,  61,  53,  60],\n    [345, 256, 344, 257],\n    [354, 363, 362, 439],\n    [353, 438, 362, 439],\n    [353, 430, 438, 439],\n    [353, 354, 362, 439],\n    [353, 354, 430, 439],\n    [353, 438, 362, 361],\n    [353, 354, 430, 266],\n    [353, 345, 430, 266],\n    [353, 345, 430, 423],\n    [267, 183, 258, 175],\n    [372, 383, 448, 371],\n    [372, 363, 448, 371],\n    [372, 202, 297, 298],\n    [372, 383, 382, 392],\n    [372, 383, 382, 371],\n    [372, 297, 382, 392],\n    [372, 297, 382, 371],\n    [372, 297, 392, 298],\n    [182, 258, 174, 175],\n    [182, 183, 258, 175],\n    [182, 183, 175, 109],\n    [182, 258, 174, 257],\n    [182, 183, 109, 190],\n    [182, 108, 190, 264],\n    [182, 108, 109, 190],\n    [351, 422, 428, 344],\n    [351, 256, 422, 344],\n    [351, 422, 428, 427],\n    [351, 422, 342, 427],\n    [351, 359, 273, 263],\n    [351, 256, 273, 263],\n    [351, 350, 359, 263],\n    [351, 350, 359, 427],\n    [351, 350, 342, 427],\n    [351, 256, 263, 255],\n    [351, 338, 342, 255],\n    [351, 422, 338, 255],\n    [351, 422, 338, 342],\n    [351, 422, 343, 255],\n    [351, 256, 343, 255],\n    [351, 256, 422, 343],\n    [351, 350, 263, 255],\n    [351, 350, 342, 255],\n    [207, 292, 282, 293],\n    [207, 198, 282, 293],\n    [207, 292, 303, 302],\n    [207, 198, 208, 132],\n    [207, 198, 208, 293],\n    [207, 208, 303, 293],\n    [207, 292, 303, 293],\n    [360, 493, 494, 547],\n    [360, 490, 493, 547],\n    [360, 494, 495, 547],\n    [360, 490, 495, 547],\n    [360, 490, 493, 427],\n    [360, 493, 359, 427],\n    [360, 490, 428, 427],\n    [360, 490, 428, 495],\n    [360, 493, 359, 436],\n    [360, 493, 494, 436],\n    [360, 351, 359, 427],\n    [360, 437, 359, 436],\n    [360, 437, 494, 436],\n    [360, 351, 428, 427],\n    [360, 428, 495, 423],\n    [178, 172, 254, 262],\n    [178, 261, 254, 262],\n    [178, 261, 249, 254],\n    [178, 172, 262, 179],\n    [178, 106, 172, 179],\n    [178, 106, 105, 179],\n    [178, 261, 249, 253],\n    [178, 177, 261, 253],\n    [178, 177, 169, 105],\n    [100, 106,  42,  99],\n    [100, 106, 168, 172],\n    [100, 178, 106, 172],\n    [250, 338, 343, 255],\n    [250, 249, 254, 341],\n    [250, 172, 254, 255],\n    [250, 254, 342, 341],\n    [250, 338, 342, 341],\n    [250, 254, 342, 255],\n    [250, 338, 342, 255],\n    [251, 256, 343, 257],\n    [251, 256, 343, 255],\n    [251, 182, 174, 257],\n    [251, 182, 256, 257],\n    [ 44,  48,  43,   7],\n    [ 44, 108,  48, 107],\n    [ 44,  48,  43,  47],\n    [ 44,  48, 107,  47],\n    [111,  50,  58, 112],\n    [111,  50,  58,  57],\n    [111, 121, 120,  57],\n    [111, 185, 121, 120],\n    [111,  58, 121, 112],\n    [111,  58, 121,  57],\n    [104, 169, 105,  99],\n    [104, 111, 176, 112],\n    [104, 111,  50, 112],\n    [104, 177, 176, 112],\n    [104, 177, 169, 176],\n    [104, 177, 105, 112],\n    [104, 177, 169, 105],\n    [ 45, 104,  50, 112],\n    [ 45,  42,  99,  46],\n    [ 45,  42,   5,  46],\n    [ 45, 105,  99,  46],\n    [ 45, 104, 105,  99],\n    [ 45, 104, 105, 112],\n    [478, 539, 538, 542],\n    [478, 538, 477, 396],\n    [478, 542, 484, 483],\n    [478, 538, 542, 483],\n    [478, 542, 484, 485],\n    [478, 539, 543, 542],\n    [478, 539, 479, 543],\n    [478, 539, 479, 471],\n    [478, 477, 396, 404],\n    [478, 477, 484, 404],\n    [478, 477, 484, 483],\n    [478, 538, 477, 483],\n    [478, 479, 414, 485],\n    [478, 543, 542, 485],\n    [478, 479, 543, 485],\n    [478, 470, 396, 471],\n    [478, 470, 538, 396],\n    [478, 539, 470, 471],\n    [478, 539, 470, 538],\n    [478, 484, 413, 404],\n    [478, 414, 413, 404],\n    [478, 484, 485, 413],\n    [478, 414, 485, 413],\n    [397, 315, 388, 304],\n    [397, 314, 315, 304],\n    [397, 303, 388, 304],\n    [397, 314, 303, 304],\n    [397, 315, 388, 389],\n    [397, 303, 388, 387],\n    [397, 303, 313, 387],\n    [397, 314, 303, 313],\n    [397, 462, 389, 471],\n    [397, 462, 388, 389],\n    [397, 472, 389, 471],\n    [397, 472, 315, 389],\n    [397, 462, 388, 387],\n    [397, 470, 462, 471],\n    [397, 470, 462, 387],\n    [397, 313, 387, 396],\n    [397, 470, 396, 471],\n    [397, 470, 387, 396],\n    [405, 323, 414, 330],\n    [405, 323, 322, 330],\n    [405, 414, 330, 404],\n    [405, 322, 330, 404],\n    [405, 323, 314, 322],\n    [405, 478, 396, 404],\n    [405, 478, 414, 404],\n    [405, 395, 396, 404],\n    [405, 395, 322, 404],\n    [405, 314, 322, 313],\n    [405, 478, 396, 471],\n    [405, 395, 313, 396],\n    [405, 395, 322, 313],\n    [405, 397, 396, 471],\n    [405, 397, 313, 396],\n    [405, 397, 314, 313],\n    [405, 478, 479, 471],\n    [405, 478, 479, 414],\n    [236, 323, 314, 322],\n    [236, 227, 322, 235],\n    [236, 227, 314, 322],\n    [236, 322, 244, 235],\n    [236, 323, 322, 244],\n    [236, 227, 151, 235],\n    [236, 227, 314, 228],\n    [236, 159, 244, 235],\n    [236, 151, 159, 235],\n    [236, 151, 237, 228],\n    [236, 151, 218, 228],\n    [236, 227, 218, 228],\n    [236, 227, 151, 218],\n    [236, 151, 159, 237],\n    [236, 323, 332, 244],\n    [236, 332, 159, 244],\n    [236, 332, 159, 237],\n    [ 88, 152, 143,  89],\n    [ 88,  80, 143,  89],\n    [ 88,  80,  79, 143],\n    [ 88, 152,  89,  41],\n    [ 88, 151,  79, 143],\n    [ 88, 152, 151, 143],\n    [ 88,  80,  79,  30],\n    [ 88, 151,  79,  93],\n    [ 88, 152, 151,  93],\n    [ 88,  40,  41,  36],\n    [ 88, 152,  40,  41],\n    [ 88,  79,  87,  93],\n    [ 88,  37,  41,  36],\n    [ 88,  37,  30,  36],\n    [ 88,  30,  87,  36],\n    [ 88,  79,  30,  87],\n    [ 88, 152,  40,  93],\n    [ 88,  87,  39,  36],\n    [ 88,  40,  39,  36],\n    [ 88,  87,  93,  39],\n    [ 88,  40,  93,  39],\n    [217, 227, 142, 150],\n    [217, 227, 226, 150],\n    [217, 207, 206, 132],\n    [217, 207, 206, 302],\n    [217, 206, 216, 302],\n    [217, 226, 312, 216],\n    [217, 227, 226, 312],\n    [217, 208, 142, 132],\n    [217, 207, 208, 132],\n    [217, 207, 303, 302],\n    [217, 208, 142, 218],\n    [217, 227, 142, 218],\n    [217, 227, 208, 218],\n    [217, 227, 208, 303],\n    [217, 207, 208, 303],\n    [217, 312, 216, 302],\n    [217, 303, 313, 302],\n    [217, 227, 303, 313],\n    [217, 312, 313, 302],\n    [217, 227, 312, 313],\n    [406, 472, 315, 398],\n    [406, 397, 472, 471],\n    [406, 397, 472, 315],\n    [406, 472, 479, 471],\n    [406, 407, 480, 398],\n    [406, 472, 480, 398],\n    [406, 405, 397, 471],\n    [406, 405, 479, 471],\n    [406, 407, 479, 480],\n    [406, 472, 479, 480],\n    [406, 397, 314, 315],\n    [406, 405, 397, 314],\n    [406, 407, 479, 414],\n    [406, 405, 479, 414],\n    [406, 405, 323, 314],\n    [406, 323, 407, 414],\n    [406, 405, 323, 414],\n    [563, 570, 523, 516],\n    [563, 570, 569, 523],\n    [563, 569, 523, 583],\n    [563, 570, 564, 516],\n    [563, 569, 571, 584],\n    [563, 570, 569, 571],\n    [563, 569, 583, 584],\n    [563, 556, 564, 584],\n    [563, 556, 564, 516],\n    [563, 571, 564, 584],\n    [563, 570, 571, 564],\n    [563, 555, 556, 584],\n    [563, 555, 583, 584],\n    [117, 126, 116, 125],\n    [117, 126, 200, 125],\n    [117, 126, 116,  55],\n    [117, 116, 109, 190],\n    [117, 116, 109,  55],\n    [117, 126,  64,  55],\n    [117, 126, 200,  64],\n    [117, 183, 109, 190],\n    [394, 459, 373, 458],\n    [394, 384, 373, 458],\n    [394, 459, 458, 468],\n    [394, 459, 460, 468],\n    [394, 385, 460, 468],\n    [394, 384, 393, 458],\n    [394, 393, 458, 468],\n    [394, 384, 299, 393],\n    [450, 503, 504, 449],\n    [450, 440, 503, 449],\n    [450, 459, 373, 449],\n    [450, 440, 373, 449],\n    [562, 507, 556, 516],\n    [562, 563, 523, 516],\n    [562, 563, 523, 583],\n    [562, 523, 583, 561],\n    [562, 563, 556, 516],\n    [562, 583, 554, 561],\n    [562, 563, 555, 556],\n    [562, 563, 555, 583],\n    [562, 555, 583, 554],\n    [515, 562, 507, 516],\n    [515, 562, 507, 506],\n    [515, 462, 524, 452],\n    [515, 523, 524, 516],\n    [515, 562, 523, 516],\n    [434, 499, 493, 436],\n    [434, 507, 499, 436],\n    [434, 433, 497, 506],\n    [432, 424, 496, 491],\n    [432, 497, 496, 491],\n    [432, 424, 425, 491],\n    [432, 497, 425, 491],\n    [432, 424, 496, 431],\n    [432, 433, 497, 425],\n    [432, 504, 496, 431],\n    [432, 497, 504, 496],\n    [432, 346, 424, 431],\n    [367, 451, 376, 452],\n    [367, 292, 377, 452],\n    [367, 376, 292, 452],\n    [367, 292, 282, 377],\n    [520, 511, 510, 566],\n    [520, 447, 511, 510],\n    [520, 447, 456, 510],\n    [520, 518, 510, 566],\n    [520, 519, 518, 566],\n    [520, 519, 456, 457],\n    [520, 456, 518, 510],\n    [520, 519, 456, 518],\n    [520, 447, 511, 448],\n    [520, 447, 456, 448],\n    [520, 456, 383, 457],\n    [520, 456, 383, 448],\n    [455, 456, 518, 464],\n    [455, 456, 518, 510],\n    [455, 517, 518, 464],\n    [455, 517, 518, 510],\n    [455, 456, 381, 464],\n    [455, 447, 456, 510],\n    [455, 447, 456, 381],\n    [455, 517, 454, 464],\n    [455, 517, 445, 454],\n    [455, 517, 446, 510],\n    [455, 517, 445, 446],\n    [455, 381, 464, 390],\n    [455, 380, 381, 390],\n    [455, 445, 380, 454],\n    [455, 454, 464, 390],\n    [455, 380, 454, 390],\n    [135, 126, 200, 125],\n    [135, 126,  71,  72],\n    [135, 126,  71, 125],\n    [135,  80,  71,  72],\n    [135,  71, 134, 125],\n    [135,  80, 144, 134],\n    [135,  80,  71, 134],\n    [211, 136, 200, 201],\n    [211, 136, 212, 201],\n    [211, 295, 200, 201],\n    [211, 136, 212, 145],\n    [211, 135, 136, 145],\n    [211, 135, 136, 200],\n    [211, 135, 144, 145],\n    [211, 144, 221, 145],\n    [211, 212, 221, 145],\n    [211, 212, 221, 307],\n    [211, 220, 221, 307],\n    [211, 220, 306, 307],\n    [211, 220, 144, 221],\n    [559, 551, 501, 552],\n    [559, 558, 551, 501],\n    [559, 502, 501, 552],\n    [559, 438, 502, 501],\n    [559, 438, 511, 501],\n    [559, 438, 511, 502],\n    [559, 511, 510, 566],\n    [559, 511, 510, 501],\n    [559, 558, 509, 566],\n    [559, 558, 509, 501],\n    [559, 509, 510, 566],\n    [559, 509, 510, 501],\n    [379, 380, 454, 390],\n    [379, 380, 306, 390],\n    [379, 294, 380, 306],\n    [379, 388, 304, 293],\n    [379, 454, 389, 390],\n    [379, 454, 388, 389],\n    [379, 305, 306, 390],\n    [379, 219, 304, 293],\n    [379, 294, 219, 293],\n    [379, 315, 389, 390],\n    [379, 315, 388, 389],\n    [379, 315, 388, 304],\n    [379, 294, 219, 306],\n    [379, 305, 219, 306],\n    [379, 305, 315, 390],\n    [379, 305, 315, 304],\n    [379, 305, 219, 304],\n    [283, 282, 377, 293],\n    [283, 368, 282, 377],\n    [283, 368, 369, 272],\n    [283, 198, 282, 293],\n    [283, 271, 368, 272],\n    [283, 271, 368, 282],\n    [296, 285, 381, 371],\n    [296, 381, 382, 371],\n    [296, 297, 382, 371],\n    [296, 295, 285, 381],\n    [296, 297, 308, 382],\n    [296, 295, 306, 381],\n    [296, 295, 285, 201],\n    [296, 381, 382, 391],\n    [296, 308, 382, 391],\n    [296, 211, 295, 306],\n    [296, 211, 295, 201],\n    [296, 306, 381, 391],\n    [296, 297, 212, 201],\n    [296, 211, 212, 201],\n    [296, 306, 307, 391],\n    [296, 308, 307, 391],\n    [296, 211, 306, 307],\n    [296, 297, 212, 308],\n    [296, 212, 308, 307],\n    [296, 211, 212, 307],\n    [284, 295, 285, 380],\n    [284, 294, 380, 369],\n    [284, 295, 294, 380],\n    [284, 369, 273, 361],\n    [284, 369, 273, 272],\n    [284, 283, 369, 272],\n    [284, 283, 294, 369],\n    [378, 283, 368, 369],\n    [378, 283, 368, 377],\n    [378, 283, 294, 369],\n    [378, 294, 380, 369],\n    [378, 445, 380, 369],\n    [378, 283, 377, 293],\n    [378, 283, 294, 293],\n    [378, 379, 294, 380],\n    [378, 379, 294, 293],\n    [378, 445, 380, 454],\n    [378, 379, 380, 454],\n    [378, 388, 377, 293],\n    [378, 379, 388, 293],\n    [378, 379, 454, 388],\n    [444, 434, 507, 436],\n    [444, 445, 369, 436],\n    [444, 359, 369, 436],\n    [444, 368, 359, 369],\n    [444, 378, 368, 369],\n    [444, 378, 445, 369],\n    [444, 378, 368, 377],\n    [209, 294, 219, 293],\n    [209, 219, 208, 293],\n    [209, 198, 208, 293],\n    [209, 283, 294, 293],\n    [209, 283, 198, 293],\n    [209, 198, 208, 133],\n    [209, 208, 134, 133],\n    [209, 219, 208, 134],\n    [209, 283, 189, 198],\n    [209, 134, 125, 133],\n    [209, 124, 125, 133],\n    [209, 189, 124, 125],\n    [209, 198, 124, 133],\n    [209, 189, 198, 124],\n    [141,  69, 142, 132],\n    [141, 217, 206, 132],\n    [141, 217, 142, 132],\n    [141,  69, 142,  78],\n    [141,  69,  68,  78],\n    [141, 206, 216, 140],\n    [141, 217, 206, 216],\n    [141, 217, 226, 216],\n    [141,  76, 216, 140],\n    [141,  85,  68,  78],\n    [141,  85,  68,  76],\n    [141,  76, 216, 149],\n    [141, 226, 216, 149],\n    [141, 142,  78, 150],\n    [141, 217, 142, 150],\n    [141, 217, 226, 150],\n    [141,  85,  76, 149],\n    [141,  85, 226, 149],\n    [141,  85,  78, 150],\n    [141,  85, 226, 150],\n    [131,  59,  67,  68],\n    [131,  59,  69,  68],\n    [131, 141,  69,  68],\n    [131,  67,  68,  76],\n    [131, 141,  68,  76],\n    [131,  59,  58, 122],\n    [131,  59,  67,  58],\n    [131,  59, 123, 122],\n    [131,  59,  69, 123],\n    [131,  67,  76, 140],\n    [131, 141,  76, 140],\n    [131, 141,  69, 132],\n    [131, 130,  58, 122],\n    [131, 130,  67,  58],\n    [131, 123, 122, 132],\n    [131,  69, 123, 132],\n    [131, 130, 122, 140],\n    [131, 130,  67, 140],\n    [131, 141, 206, 140],\n    [131, 206, 122, 132],\n    [131, 141, 206, 132],\n    [131, 205, 122, 140],\n    [131, 205, 206, 140],\n    [131, 205, 206, 122],\n    [ 51,  59,  22, 123],\n    [ 51,  59,  22,   8],\n    [ 51,  22, 123,  52],\n    [ 51,  22,   8,  52],\n    [ 51,  59,  58, 123],\n    [ 51, 114,  52,  46],\n    [ 51, 114, 123,  52],\n    [ 51,   5,  52,  46],\n    [ 51,   8,   5,  52],\n    [ 51, 114, 113,  46],\n    [ 51, 114, 113, 123],\n    [ 51,  58, 123, 122],\n    [ 51, 113, 123, 122],\n    [ 51, 113,  58, 122],\n    [ 51,  59,  58,  13],\n    [ 51,  59,   8,  13],\n    [ 51,  45,   5,  46],\n    [ 51,  45,   8,   5],\n    [ 51, 113, 105,  46],\n    [ 51,  45, 105,  46],\n    [ 51,  45,  50,   8],\n    [ 51,  50,  58,  13],\n    [ 51,  50,   8,  13],\n    [ 51, 113,  58, 112],\n    [ 51,  50,  58, 112],\n    [ 51, 113, 105, 112],\n    [ 51,  45, 105, 112],\n    [ 51,  45,  50, 112],\n    [319, 224, 223, 232],\n    [319, 224, 310, 223],\n    [319, 225, 241, 232],\n    [319, 224, 225, 232],\n    [311, 385, 215, 300],\n    [311, 385, 301, 215],\n    [311, 224, 215, 300],\n    [311, 224, 301, 215],\n    [311, 394, 385, 300],\n    [311, 224, 310, 300],\n    [311, 402, 310, 393],\n    [311, 394, 299, 393],\n    [311, 394, 299, 300],\n    [311, 394, 393, 468],\n    [311, 394, 385, 468],\n    [311, 319, 402, 310],\n    [311, 319, 224, 310],\n    [311, 310, 299, 393],\n    [311, 310, 299, 300],\n    [311, 301, 403, 386],\n    [311, 385, 301, 386],\n    [311, 403, 386, 468],\n    [311, 385, 386, 468],\n    [311, 467, 393, 468],\n    [311, 402, 467, 393],\n    [311, 403, 467, 468],\n    [311, 402, 403, 467],\n    [269, 365, 355, 278],\n    [269, 268, 355, 278],\n    [269, 268, 259, 278],\n    [269, 279, 259, 278],\n    [269, 365, 279, 278],\n    [269, 185, 279, 259],\n    [260, 252, 253, 339],\n    [260, 261, 253, 339],\n    [260, 177, 270, 261],\n    [260, 177, 252, 253],\n    [260, 177, 261, 253],\n    [260, 268, 252, 339],\n    [260, 268, 252, 259],\n    [260, 269, 268, 259],\n    [187, 113, 197, 179],\n    [187, 113, 105, 179],\n    [187, 177, 113, 105],\n    [187, 270, 261, 262],\n    [187, 177, 270, 261],\n    [187, 178, 262, 179],\n    [187, 178, 105, 179],\n    [187, 178, 177, 105],\n    [187, 178, 261, 262],\n    [187, 178, 177, 261],\n    [508, 500, 509, 446],\n    [508, 517, 509, 446],\n    [508, 517, 445, 446],\n    [508, 500, 509, 564],\n    [508, 517, 509, 564],\n    [508, 445, 446, 436],\n    [508, 500, 446, 436],\n    [508, 500, 556, 564],\n    [508, 500, 507, 556],\n    [508, 444, 507, 436],\n    [508, 444, 445, 436],\n    [508, 507, 499, 436],\n    [508, 500, 499, 436],\n    [508, 500, 507, 499],\n    [508, 517, 454, 526],\n    [508, 517, 445, 454],\n    [508, 556, 564, 516],\n    [508, 507, 556, 516],\n    [508, 564, 526, 516],\n    [508, 517, 564, 526],\n    [118, 183, 175, 109],\n    [118, 117, 183, 109],\n    [118, 110, 109,  55],\n    [118, 117, 109,  55],\n    [118, 117,  64,  55],\n    [275, 267, 192, 183],\n    [275, 353, 362, 361],\n    [275, 285, 362, 361],\n    [275, 353, 354, 266],\n    [275, 353, 354, 362],\n    [275, 284, 285, 361],\n    [286, 363, 362, 371],\n    [286, 285, 362, 371],\n    [286, 275, 285, 362],\n    [286, 202, 297, 201],\n    [286, 295, 285, 201],\n    [286, 296, 297, 371],\n    [286, 296, 285, 371],\n    [286, 296, 297, 201],\n    [286, 296, 285, 201],\n    [ 82,  80, 144,  89],\n    [ 82, 154, 144,  89],\n    [ 82, 135,  80,  72],\n    [ 82, 135,  80, 144],\n    [ 82, 154, 144, 145],\n    [ 82, 135, 144, 145],\n    [ 62,  61, 126, 116],\n    [ 62,  54,  61, 126],\n    [ 62, 126, 116,  55],\n    [ 62,  54,  61,  53],\n    [ 62,  61,  48, 116],\n    [ 62,  61,  48,  53],\n    [ 62,  48, 116,  55],\n    [ 62,  54,  53,  11],\n    [ 62,  12,  19,  55],\n    [ 62,  18,  12,  11],\n    [ 62,  54,  18,  11],\n    [ 62,  48,  12,   7],\n    [ 62,  48,  53,   7],\n    [ 62,  12,  11,   7],\n    [ 62,  53,  11,   7],\n    [ 63,  25,   3,  72],\n    [ 63,  25,  18,   3],\n    [ 63,  54,   3,  72],\n    [ 63,  54,  18,   3],\n    [ 63,  54, 126,  72],\n    [ 63,  25,  26,  18],\n    [ 63,  62,  54,  18],\n    [ 63,  62,  54, 126],\n    [ 63,  26,  18,  19],\n    [ 63,  26,  19,  64],\n    [ 63,  18,  12,  19],\n    [ 63,  62,  12,  19],\n    [ 63,  62,  18,  12],\n    [ 63,  62,  19,  55],\n    [ 63,  62, 126,  55],\n    [ 63,  19,  64,  55],\n    [ 63, 126,  64,  55],\n    [ 74, 136, 137, 145],\n    [ 90, 154,  89,  95],\n    [ 90,  82, 154,  89],\n    [ 90, 154,  95, 162],\n    [ 90, 154, 155, 162],\n    [ 90,  82, 154, 145],\n    [ 32,  31,   3,  72],\n    [ 32,  25,   3,  72],\n    [352, 345, 344, 423],\n    [352, 353, 345, 423],\n    [352, 345, 256, 344],\n    [352, 351, 256, 273],\n    [352, 351, 256, 344],\n    [352, 428, 344, 423],\n    [352, 351, 428, 344],\n    [352, 351, 359, 273],\n    [352, 360, 351, 359],\n    [352, 360, 428, 423],\n    [352, 360, 351, 428],\n    [352, 437, 273, 361],\n    [352, 353, 437, 361],\n    [352, 437, 359, 273],\n    [352, 360, 437, 359],\n    [184, 110, 175, 109],\n    [184, 118, 175, 109],\n    [184, 118, 110, 109],\n    [184, 267, 183, 175],\n    [184, 118, 183, 175],\n    [184, 267, 192, 183],\n    [184, 118, 192, 183],\n    [213, 212, 222, 298],\n    [213, 297, 212, 298],\n    [213, 202, 297, 298],\n    [213, 297, 212, 201],\n    [213, 202, 297, 201],\n    [213, 212, 137, 146],\n    [213, 212, 222, 146],\n    [213, 136, 212, 201],\n    [213, 136, 212, 137],\n    [276, 275, 267, 192],\n    [276, 354, 363, 362],\n    [276, 286, 363, 362],\n    [276, 286, 275, 192],\n    [276, 275, 354, 266],\n    [276, 275, 267, 266],\n    [276, 275, 354, 362],\n    [276, 286, 275, 362],\n    [ 65,  74, 136, 137],\n    [196, 198, 197, 132],\n    [196, 207, 198, 132],\n    [196, 207, 206, 132],\n    [196, 198, 282, 197],\n    [196, 207, 198, 282],\n    [196, 206, 122, 132],\n    [196, 197, 122, 132],\n    [196, 205, 206, 122],\n    [196, 207, 292, 282],\n    [196, 367, 292, 282],\n    [349, 426, 348, 425],\n    [349, 433, 426, 425],\n    [349, 426, 348, 350],\n    [349, 270, 261, 262],\n    [349, 350, 254, 262],\n    [349, 348, 350, 254],\n    [349, 271, 350, 262],\n    [349, 261, 254, 262],\n    [349, 261, 348, 254],\n    [170, 178, 249, 253],\n    [170, 177, 169, 253],\n    [170, 178, 177, 253],\n    [170, 178, 177, 169],\n    [170, 169, 105,  99],\n    [170, 178, 169, 105],\n    [170, 100, 106,  99],\n    [170, 100, 178, 106],\n    [170, 106, 105,  99],\n    [170, 178, 106, 105],\n    [171, 100, 168, 172],\n    [171, 250, 249, 254],\n    [171, 250, 172, 254],\n    [171, 178, 249, 254],\n    [171, 178, 172, 254],\n    [171, 170, 178, 249],\n    [171, 100, 178, 172],\n    [171, 170, 100, 178],\n    [173, 171, 168, 172],\n    [173, 171, 250, 172],\n    [173, 250, 343, 255],\n    [173, 251, 343, 255],\n    [173, 250, 172, 255],\n    [181, 182, 108, 174],\n    [181, 251, 182, 174],\n    [181, 182, 108, 264],\n    [181, 182, 256, 264],\n    [181, 251, 182, 256],\n    [181, 168, 172, 107],\n    [181, 173, 168, 172],\n    [181, 108, 180, 264],\n    [181, 256, 264, 255],\n    [181, 251, 256, 255],\n    [181, 172, 107, 180],\n    [181, 108, 107, 180],\n    [181, 173, 172, 255],\n    [181, 173, 251, 255],\n    [181, 264, 263, 255],\n    [181, 180, 264, 263],\n    [181, 172, 263, 255],\n    [181, 172, 180, 263],\n    [101,  44,  43,  47],\n    [101,  44, 107,  47],\n    [101,  42,  43,  47],\n    [101, 100, 106,  42],\n    [101, 106, 168, 107],\n    [101, 100, 106, 168],\n    [101, 106, 107,  47],\n    [101, 106,  42,  47],\n    [102, 181, 251, 174],\n    [102, 101, 168, 107],\n    [102, 101,  44, 107],\n    [102, 181, 108, 174],\n    [102,  44, 108, 107],\n    [102, 181, 173, 168],\n    [102, 181, 173, 251],\n    [102, 181, 168, 107],\n    [102, 181, 108, 107],\n    [103, 110, 175, 109],\n    [103, 102, 108, 174],\n    [103, 102,  44, 108],\n    [103, 182, 175, 109],\n    [103, 182, 174, 175],\n    [103, 182, 108, 109],\n    [103, 182, 108, 174],\n    [103, 108,  48, 109],\n    [103,  44, 108,  48],\n    [324, 237, 228, 316],\n    [324, 236, 237, 228],\n    [324, 236, 314, 228],\n    [324, 236, 323, 314],\n    [324, 315, 228, 316],\n    [324, 314, 315, 228],\n    [324, 236, 332, 237],\n    [324, 323, 332, 325],\n    [324, 236, 323, 332],\n    [324, 315, 398, 316],\n    [324, 406, 323, 314],\n    [324, 398, 326, 316],\n    [324, 398, 325, 326],\n    [324, 406, 315, 398],\n    [324, 406, 314, 315],\n    [324, 237, 238, 316],\n    [324, 332, 238, 325],\n    [324, 332, 237, 238],\n    [324, 238, 326, 316],\n    [324, 238, 325, 326],\n    [324, 407, 398, 325],\n    [324, 406, 407, 398],\n    [324, 323, 407, 325],\n    [324, 406, 323, 407],\n    [199, 189, 116, 125],\n    [199, 117, 116, 125],\n    [199, 189, 116, 190],\n    [199, 117, 116, 190],\n    [199, 209, 189, 125],\n    [199, 284, 283, 294],\n    [199, 284, 295, 294],\n    [199, 189, 190, 272],\n    [199, 209, 283, 294],\n    [199, 209, 283, 189],\n    [199, 283, 189, 272],\n    [199, 284, 283, 272],\n    [199, 117, 200, 125],\n    [289, 365, 277, 364],\n    [289, 384, 277, 364],\n    [289, 384, 277, 288],\n    [289, 394, 299, 300],\n    [289, 394, 384, 299],\n    [289, 204, 300, 288],\n    [289, 365, 279, 277],\n    [289, 299, 300, 288],\n    [289, 384, 299, 288],\n    [289, 277, 194, 288],\n    [289, 279, 277, 194],\n    [289, 204, 194, 288],\n    [289, 279, 204, 194],\n    [441, 450, 503, 504],\n    [441, 450, 440, 503],\n    [441, 503, 504, 431],\n    [441, 440, 503, 431],\n    [441, 432, 504, 431],\n    [441, 440, 355, 431],\n    [441, 365, 355, 364],\n    [441, 440, 355, 364],\n    [374, 394, 459, 373],\n    [374, 450, 459, 373],\n    [374, 394, 459, 460],\n    [374, 450, 459, 460],\n    [374, 384, 373, 364],\n    [374, 394, 384, 373],\n    [374, 289, 365, 364],\n    [374, 440, 373, 364],\n    [374, 450, 440, 373],\n    [374, 441, 365, 364],\n    [374, 394, 385, 460],\n    [374, 289, 384, 364],\n    [374, 289, 394, 384],\n    [374, 441, 440, 364],\n    [374, 441, 450, 440],\n    [498, 507, 499, 556],\n    [498, 562, 507, 556],\n    [498, 499, 556, 549],\n    [498, 555, 556, 549],\n    [498, 562, 555, 556],\n    [498, 562, 507, 506],\n    [498, 555, 549, 554],\n    [498, 562, 555, 554],\n    [498, 434, 507, 506],\n    [498, 434, 507, 499],\n    [498, 434, 497, 506],\n    [498, 549, 554, 548],\n    [498, 545, 549, 548],\n    [514, 562, 523, 561],\n    [514, 515, 562, 523],\n    [514, 567, 568, 561],\n    [514, 523, 568, 561],\n    [514, 531, 567, 568],\n    [514, 523, 531, 568],\n    [514, 560, 567, 561],\n    [514, 531, 567, 522],\n    [514, 523, 531, 524],\n    [514, 515, 523, 524],\n    [357, 347, 346, 424],\n    [357, 432, 346, 424],\n    [357, 347, 424, 425],\n    [357, 432, 424, 425],\n    [357, 347, 346, 339],\n    [357, 347, 348, 425],\n    [357, 349, 348, 425],\n    [357, 432, 433, 425],\n    [357, 349, 433, 425],\n    [357, 260, 261, 339],\n    [357, 349, 261, 348],\n    [357, 261, 340, 339],\n    [357, 347, 340, 339],\n    [357, 260, 270, 261],\n    [357, 349, 270, 261],\n    [357, 261, 348, 340],\n    [357, 347, 348, 340],\n    [442, 451, 506, 504],\n    [442, 497, 506, 504],\n    [442, 433, 497, 506],\n    [442, 432, 497, 504],\n    [442, 432, 433, 497],\n    [442, 441, 432, 504],\n    [442, 450, 451, 504],\n    [442, 441, 450, 504],\n    [370, 447, 285, 381],\n    [370, 455, 447, 381],\n    [370, 285, 380, 381],\n    [370, 455, 380, 381],\n    [370, 447, 438, 362],\n    [370, 447, 285, 362],\n    [370, 447, 438, 510],\n    [370, 455, 447, 510],\n    [370, 284, 285, 361],\n    [370, 284, 285, 380],\n    [370, 438, 362, 361],\n    [370, 285, 362, 361],\n    [370, 437, 438, 361],\n    [370, 438, 446, 510],\n    [370, 455, 446, 510],\n    [370, 284, 369, 361],\n    [370, 284, 380, 369],\n    [370, 437, 369, 361],\n    [370, 445, 380, 369],\n    [370, 455, 445, 380],\n    [370, 437, 438, 446],\n    [370, 445, 437, 369],\n    [370, 445, 437, 446],\n    [370, 455, 445, 446],\n    [210, 211, 295, 200],\n    [210, 199, 200, 125],\n    [210, 199, 295, 200],\n    [210, 135, 200, 125],\n    [210, 211, 135, 200],\n    [210, 199, 295, 294],\n    [210, 211, 295, 306],\n    [210, 199, 209, 125],\n    [210, 199, 209, 294],\n    [210, 211, 220, 144],\n    [210, 211, 135, 144],\n    [210, 209, 294, 219],\n    [210, 294, 219, 306],\n    [210, 295, 294, 306],\n    [210, 220, 219, 306],\n    [210, 211, 220, 306],\n    [210, 135, 134, 125],\n    [210, 220, 219, 134],\n    [210, 220, 144, 134],\n    [210, 135, 144, 134],\n    [210, 209, 134, 125],\n    [210, 209, 219, 134],\n    [188, 283, 189, 198],\n    [188, 283, 271, 282],\n    [188, 283, 198, 282],\n    [188, 283, 271, 272],\n    [188, 283, 189, 272],\n    [188, 198, 282, 197],\n    [188, 198, 124, 197],\n    [188, 189, 198, 124],\n    [188, 271, 180, 272],\n    [188, 189, 180, 272],\n    [188, 271, 262, 180],\n    [188, 124, 197, 179],\n    [188, 262, 180, 179],\n    [188, 115, 180, 179],\n    [188, 189, 115, 180],\n    [188, 124, 115, 179],\n    [188, 189, 124, 115],\n    [188, 187, 262, 179],\n    [188, 187, 197, 179],\n    [274, 284, 273, 361],\n    [274, 275, 284, 361],\n    [274, 273, 264, 272],\n    [274, 284, 273, 272],\n    [274, 182, 190, 264],\n    [274, 182, 183, 190],\n    [274, 190, 264, 272],\n    [274, 117, 183, 190],\n    [274, 199, 117, 190],\n    [274, 275, 284, 285],\n    [274, 199, 190, 272],\n    [274, 199, 284, 272],\n    [435, 444, 368, 359],\n    [435, 434, 433, 426],\n    [435, 444, 359, 436],\n    [435, 444, 434, 436],\n    [435, 271, 350, 359],\n    [435, 271, 368, 359],\n    [435, 349, 271, 350],\n    [435, 349, 426, 350],\n    [435, 349, 433, 426],\n    [435, 493, 359, 436],\n    [435, 434, 493, 436],\n    [435, 350, 359, 427],\n    [435, 426, 350, 427],\n    [435, 493, 359, 427],\n    [435, 426, 493, 427],\n    [443, 434, 507, 506],\n    [443, 444, 434, 507],\n    [443, 515, 507, 506],\n    [443, 434, 433, 506],\n    [443, 515, 451, 452],\n    [443, 515, 451, 506],\n    [443, 442, 433, 506],\n    [443, 442, 451, 506],\n    [443, 367, 377, 452],\n    [443, 367, 451, 452],\n    [443, 442, 367, 451],\n    [320, 319, 241, 328],\n    [320, 319, 225, 241],\n    [320, 321, 225, 241],\n    [320, 329, 241, 328],\n    [320, 321, 329, 328],\n    [320, 321, 329, 241],\n    [320, 224, 301, 225],\n    [320, 319, 224, 225],\n    [320, 301, 225, 312],\n    [320, 321, 225, 312],\n    [320, 311, 301, 403],\n    [320, 311, 224, 301],\n    [320, 311, 319, 224],\n    [320, 395, 403, 328],\n    [320, 395, 321, 328],\n    [320, 395, 301, 403],\n    [320, 395, 301, 312],\n    [320, 395, 321, 312],\n    [320, 402, 403, 328],\n    [320, 311, 402, 403],\n    [320, 319, 402, 328],\n    [320, 311, 319, 402],\n    [290, 289, 365, 279],\n    [290, 374, 289, 365],\n    [290, 205, 279, 204],\n    [290, 289, 279, 204],\n    [290, 374, 394, 385],\n    [290, 374, 289, 394],\n    [290, 205, 204, 215],\n    [290, 385, 215, 300],\n    [290, 394, 385, 300],\n    [290, 289, 394, 300],\n    [290, 204, 215, 300],\n    [290, 289, 204, 300],\n    [195, 122, 121, 112],\n    [195, 187, 177, 270],\n    [195, 205, 122, 121],\n    [195, 205, 279, 121],\n    [195, 177, 113, 112],\n    [195, 187, 177, 113],\n    [195, 113, 122, 112],\n    [195, 196, 205, 122],\n    [195, 113, 197, 122],\n    [195, 187, 113, 197],\n    [195, 196, 197, 122],\n    [186, 260, 269, 259],\n    [186, 260, 252, 259],\n    [186, 260, 177, 252],\n    [186, 260, 177, 270],\n    [186, 260, 269, 270],\n    [186, 252, 259, 176],\n    [186, 177, 252, 176],\n    [186, 185, 259, 176],\n    [186, 269, 185, 259],\n    [186, 195, 177, 270],\n    [186, 195, 269, 270],\n    [186, 177, 176, 112],\n    [186, 195, 177, 112],\n    [186, 111, 185, 176],\n    [186, 111, 185, 121],\n    [186, 185, 279, 121],\n    [186, 269, 185, 279],\n    [186, 195, 121, 112],\n    [186, 195, 279, 121],\n    [186, 195, 269, 279],\n    [186, 111, 176, 112],\n    [186, 111, 121, 112],\n    [453, 508, 444, 507],\n    [453, 508, 526, 516],\n    [453, 508, 507, 516],\n    [453, 508, 454, 526],\n    [453, 443, 444, 507],\n    [453, 508, 445, 454],\n    [453, 508, 444, 445],\n    [453, 515, 507, 516],\n    [453, 462, 454, 526],\n    [453, 443, 515, 452],\n    [453, 443, 515, 507],\n    [453, 443, 377, 452],\n    [453, 443, 444, 377],\n    [453, 378, 445, 454],\n    [453, 444, 378, 445],\n    [453, 524, 526, 516],\n    [453, 462, 524, 526],\n    [453, 515, 462, 452],\n    [453, 462, 388, 452],\n    [453, 388, 377, 452],\n    [453, 444, 378, 377],\n    [453, 515, 524, 516],\n    [453, 515, 462, 524],\n    [453, 454, 388, 389],\n    [453, 462, 388, 389],\n    [453, 462, 454, 389],\n    [453, 378, 388, 377],\n    [453, 378, 454, 388],\n    [191, 118, 192, 183],\n    [191, 275, 192, 183],\n    [191, 118, 117, 183],\n    [191, 286, 275, 192],\n    [191, 274, 117, 183],\n    [191, 274, 275, 183],\n    [191, 274, 199, 117],\n    [191, 274, 275, 285],\n    [191, 286, 295, 285],\n    [191, 286, 275, 285],\n    [191, 199, 295, 200],\n    [191, 199, 117, 200],\n    [191, 199, 284, 295],\n    [191, 274, 199, 284],\n    [191, 284, 295, 285],\n    [191, 274, 284, 285],\n    [191, 295, 200, 201],\n    [191, 286, 295, 201],\n    [ 73,  74,  25,  26],\n    [ 73,  63,  26,  64],\n    [ 73,  63,  25,  26],\n    [ 73,  65,  26,  64],\n    [ 73,  65,  74,  26],\n    [ 73,  65, 136,  64],\n    [ 73,  65,  74, 136],\n    [ 73,  63, 126,  64],\n    [ 73,  63,  25,  72],\n    [ 73, 136, 200,  64],\n    [ 73, 135, 136, 200],\n    [ 73, 135, 136, 145],\n    [ 73,  74, 136, 145],\n    [ 73,  82,  25,  72],\n    [ 73,  82, 135,  72],\n    [ 73, 126, 200,  64],\n    [ 73, 135, 126, 200],\n    [ 73, 135, 126,  72],\n    [ 73,  63, 126,  72],\n    [ 73,  82, 135, 145],\n    [ 38,  89,  41,  95],\n    [ 38,  90,  89,  95],\n    [ 38,  90,  82,  89],\n    [ 33,  73,  82,  25],\n    [ 33,  73,  74,  25],\n    [ 33,  82,  25,  72],\n    [ 33,  32,  25,  72],\n    [ 33,  32,  82,  72],\n    [ 33,  38,  90,  82],\n    [ 33,  38,  32,  82],\n    [ 33,  73,  82, 145],\n    [ 33,  73,  74, 145],\n    [429, 352, 360, 423],\n    [429, 352, 360, 437],\n    [429, 360, 495, 423],\n    [429, 495, 502, 423],\n    [429, 360, 494, 495],\n    [429, 360, 437, 494],\n    [429, 352, 353, 423],\n    [429, 352, 353, 437],\n    [429, 495, 502, 552],\n    [429, 494, 495, 552],\n    [429, 430, 502, 423],\n    [429, 430, 438, 502],\n    [429, 353, 430, 423],\n    [429, 353, 430, 438],\n    [429, 437, 438, 361],\n    [429, 353, 438, 361],\n    [429, 353, 437, 361],\n    [429, 494, 501, 552],\n    [429, 437, 494, 501],\n    [429, 502, 501, 552],\n    [429, 438, 502, 501],\n    [429, 437, 438, 501],\n    [ 56, 184, 118, 110],\n    [ 56, 110,  19,  55],\n    [ 56, 118, 110,  55],\n    [ 56,  26,  19,  64],\n    [ 56,  65,  26,  64],\n    [ 56,  19,  64,  55],\n    [ 56, 118,  64,  55],\n    [287, 276, 286, 363],\n    [287, 372, 363, 371],\n    [287, 286, 363, 371],\n    [287, 372, 202, 297],\n    [287, 286, 202, 297],\n    [287, 372, 297, 371],\n    [287, 286, 297, 371],\n    [193, 184, 267, 192],\n    [193, 276, 267, 192],\n    [193, 287, 286, 202],\n    [193, 276, 286, 192],\n    [193, 287, 276, 286],\n    [193, 286, 202, 201],\n    [291, 367, 376, 292],\n    [291, 196, 367, 292],\n    [291, 376, 292, 302],\n    [291, 207, 292, 302],\n    [291, 196, 207, 292],\n    [291, 207, 206, 302],\n    [291, 196, 207, 206],\n    [291, 385, 376, 302],\n    [291, 385, 215, 302],\n    [291, 290, 385, 215],\n    [291, 215, 206, 302],\n    [291, 196, 205, 206],\n    [291, 205, 215, 206],\n    [291, 290, 205, 215],\n    [281, 196, 367, 282],\n    [281, 187, 270, 262],\n    [281, 349, 270, 262],\n    [281, 196, 282, 197],\n    [281, 195, 187, 270],\n    [281, 188, 271, 282],\n    [281, 349, 271, 262],\n    [281, 188, 282, 197],\n    [281, 188, 187, 197],\n    [281, 195, 187, 197],\n    [281, 195, 196, 197],\n    [281, 188, 271, 262],\n    [281, 188, 187, 262],\n    [ 49,  12,  19,  55],\n    [ 49, 110,  19,  55],\n    [ 49, 103,  44,  48],\n    [ 49, 103, 110, 109],\n    [ 49, 103,  48, 109],\n    [ 49,  48,  12,   7],\n    [ 49,  44,  48,   7],\n    [ 49,  62,  12,  55],\n    [ 49,  62,  48,  55],\n    [ 49,  62,  48,  12],\n    [ 49, 110, 109,  55],\n    [ 49,  48, 109,  55],\n    [492, 498, 434, 499],\n    [492, 434, 499, 493],\n    [492, 545, 499, 549],\n    [492, 498, 499, 549],\n    [492, 498, 545, 549],\n    [492, 498, 434, 497],\n    [492, 499, 546, 493],\n    [492, 545, 499, 546],\n    [492, 498, 497, 548],\n    [492, 498, 545, 548],\n    [492, 546, 493, 489],\n    [492, 545, 546, 489],\n    [492, 426, 493, 489],\n    [492, 435, 426, 493],\n    [492, 435, 434, 493],\n    [492, 435, 434, 426],\n    [492, 497, 425, 491],\n    [492, 433, 497, 425],\n    [492, 434, 433, 497],\n    [492, 497, 491, 548],\n    [492, 545, 491, 548],\n    [492, 426, 489, 488],\n    [492, 545, 489, 488],\n    [492, 426, 425, 488],\n    [492, 433, 426, 425],\n    [492, 434, 433, 426],\n    [492, 425, 491, 488],\n    [492, 545, 491, 488],\n    [505, 514, 562, 561],\n    [505, 515, 562, 506],\n    [505, 514, 515, 562],\n    [505, 554, 553, 561],\n    [505, 562, 554, 561],\n    [505, 515, 451, 506],\n    [505, 514, 515, 451],\n    [505, 498, 562, 554],\n    [505, 498, 562, 506],\n    [505, 560, 553, 561],\n    [505, 560, 504, 553],\n    [505, 514, 560, 561],\n    [505, 497, 504, 553],\n    [505, 497, 506, 504],\n    [505, 451, 506, 504],\n    [505, 554, 553, 548],\n    [505, 498, 554, 548],\n    [505, 498, 497, 506],\n    [505, 497, 553, 548],\n    [505, 498, 497, 548],\n    [461, 514, 531, 522],\n    [461, 514, 531, 524],\n    [461, 470, 531, 524],\n    [461, 531, 522, 469],\n    [461, 470, 531, 469],\n    [461, 514, 515, 524],\n    [461, 514, 515, 451],\n    [461, 515, 524, 452],\n    [461, 515, 451, 452],\n    [461, 462, 524, 452],\n    [461, 470, 462, 524],\n    [461, 460, 522, 469],\n    [461, 386, 460, 469],\n    [461, 386, 396, 469],\n    [461, 470, 396, 469],\n    [461, 376, 387, 452],\n    [461, 451, 376, 452],\n    [461, 451, 376, 460],\n    [461, 462, 387, 452],\n    [461, 470, 462, 387],\n    [461, 386, 387, 396],\n    [461, 470, 387, 396],\n    [461, 376, 386, 460],\n    [461, 376, 386, 387],\n    [513, 512, 567, 522],\n    [513, 514, 567, 522],\n    [513, 512, 560, 567],\n    [513, 514, 560, 567],\n    [513, 512, 459, 522],\n    [513, 512, 560, 449],\n    [513, 459, 460, 522],\n    [513, 450, 459, 460],\n    [513, 512, 459, 449],\n    [513, 450, 459, 449],\n    [513, 560, 504, 449],\n    [513, 450, 504, 449],\n    [513, 450, 451, 460],\n    [513, 461, 514, 522],\n    [513, 461, 514, 451],\n    [513, 505, 514, 560],\n    [513, 505, 514, 451],\n    [513, 505, 560, 504],\n    [513, 461, 460, 522],\n    [513, 461, 451, 460],\n    [513, 450, 451, 504],\n    [513, 505, 451, 504],\n    [375, 367, 451, 376],\n    [375, 442, 367, 451],\n    [375, 451, 376, 460],\n    [375, 450, 451, 460],\n    [375, 442, 450, 451],\n    [375, 374, 450, 460],\n    [375, 291, 367, 376],\n    [375, 385, 376, 460],\n    [375, 374, 385, 460],\n    [375, 290, 374, 385],\n    [375, 374, 441, 450],\n    [375, 442, 441, 450],\n    [375, 291, 385, 376],\n    [375, 291, 290, 385],\n    [375, 374, 441, 365],\n    [375, 290, 374, 365],\n    [366, 442, 441, 432],\n    [366, 375, 442, 441],\n    [366, 357, 432, 433],\n    [366, 442, 432, 433],\n    [366, 375, 442, 367],\n    [366, 357, 349, 270],\n    [366, 357, 349, 433],\n    [366, 375, 441, 365],\n    [366, 281, 349, 270],\n    [265, 353, 345, 266],\n    [265, 275, 353, 266],\n    [265, 275, 353, 361],\n    [265, 274, 275, 361],\n    [265, 345, 258, 266],\n    [265, 352, 345, 256],\n    [265, 352, 353, 345],\n    [265, 274, 275, 183],\n    [265, 274, 273, 361],\n    [265, 267, 258, 266],\n    [265, 275, 267, 266],\n    [265, 352, 256, 273],\n    [265, 182, 256, 264],\n    [265, 274, 182, 264],\n    [265, 256, 273, 264],\n    [265, 274, 273, 264],\n    [265, 182, 183, 258],\n    [265, 274, 182, 183],\n    [265, 267, 183, 258],\n    [265, 275, 267, 183],\n    [265, 352, 273, 361],\n    [265, 352, 353, 361],\n    [265, 345, 258, 257],\n    [265, 345, 256, 257],\n    [265, 182, 258, 257],\n    [265, 182, 256, 257],\n    [358, 435, 444, 434],\n    [358, 443, 444, 434],\n    [358, 435, 434, 433],\n    [358, 443, 434, 433],\n    [358, 435, 444, 368],\n    [358, 435, 349, 433],\n    [358, 444, 368, 377],\n    [358, 443, 444, 377],\n    [358, 435, 271, 368],\n    [358, 435, 349, 271],\n    [358, 368, 282, 377],\n    [358, 271, 368, 282],\n    [358, 366, 349, 433],\n    [358, 367, 282, 377],\n    [358, 443, 367, 377],\n    [358, 366, 281, 367],\n    [358, 366, 281, 349],\n    [358, 443, 442, 433],\n    [358, 366, 442, 433],\n    [358, 443, 442, 367],\n    [358, 366, 442, 367],\n    [358, 281, 349, 271],\n    [358, 281, 271, 282],\n    [358, 281, 367, 282],\n    [280, 269, 365, 279],\n    [280, 290, 365, 279],\n    [280, 290, 205, 279],\n    [280, 375, 290, 365],\n    [280, 195, 205, 279],\n    [280, 195, 269, 279],\n    [280, 366, 375, 365],\n    [280, 375, 291, 290],\n    [280, 291, 290, 205],\n    [280, 195, 196, 205],\n    [280, 291, 196, 205],\n    [280, 375, 291, 367],\n    [280, 366, 375, 367],\n    [280, 195, 269, 270],\n    [280, 366, 269, 270],\n    [280, 291, 196, 367],\n    [280, 281, 195, 196],\n    [280, 281, 195, 270],\n    [280, 366, 281, 270],\n    [280, 281, 196, 367],\n    [280, 366, 281, 367],\n    [127, 191, 118, 192],\n    [127, 191, 286, 201],\n    [127, 191, 286, 192],\n    [127, 191, 200, 201],\n    [127, 193, 286, 201],\n    [127, 193, 286, 192],\n    [127, 191, 117, 200],\n    [127, 191, 118, 117],\n    [127, 136, 200,  64],\n    [127, 136, 200, 201],\n    [127, 117, 200,  64],\n    [127, 118, 117,  64],\n    [127,  65, 136,  64],\n    [ 81,  38,  32,  37],\n    [ 81,  38,  32,  82],\n    [ 81,  24,  80,  30],\n    [ 81,  24,  80,  72],\n    [ 81,  32,  37,  31],\n    [ 81,  88,  80,  89],\n    [ 81,  82,  80,  89],\n    [ 81,  38,  82,  89],\n    [ 81,  38,  89,  41],\n    [ 81,  38,  37,  41],\n    [ 81,  82,  80,  72],\n    [ 81,  32,  82,  72],\n    [ 81,  88,  80,  30],\n    [ 81,  88,  37,  30],\n    [ 81,  24,  31,  30],\n    [ 81,  37,  31,  30],\n    [ 81,  24,  31,  72],\n    [ 81,  32,  31,  72],\n    [ 81,  88,  89,  41],\n    [ 81,  88,  37,  41],\n    [ 83,  33,  74, 145],\n    [ 83,  90,  82, 145],\n    [ 83,  33,  82, 145],\n    [ 83,  33,  90,  82],\n    [ 83,  74, 137, 145],\n    [ 83, 154, 155, 145],\n    [ 83,  90, 154, 145],\n    [ 83,  90, 154, 155],\n    [ 83, 137, 146, 145],\n    [ 83, 155, 146, 145],\n    [128, 213, 202, 201],\n    [128, 193, 202, 201],\n    [128, 127, 193, 201],\n    [128, 213, 136, 137],\n    [128,  65, 136, 137],\n    [128, 127,  65, 136],\n    [128, 213, 136, 201],\n    [128, 127, 136, 201],\n    [119, 128, 127,  65],\n    [119, 128, 127, 193],\n    [119,  56,  65,  64],\n    [119, 127,  65,  64],\n    [119,  56, 184, 118],\n    [119, 193, 184, 192],\n    [119, 127, 193, 192],\n    [119,  56, 118,  64],\n    [119, 127, 118,  64],\n    [119, 184, 118, 192],\n    [119, 127, 118, 192],\n    [356, 366, 441, 432],\n    [356, 366, 357, 432],\n    [356, 357, 432, 346],\n    [356, 432, 346, 431],\n    [356, 441, 432, 431],\n    [356, 346, 355, 431],\n    [356, 441, 355, 431],\n    [356, 366, 441, 365],\n    [356, 366, 269, 270],\n    [356, 366, 357, 270],\n    [356, 268, 346, 355],\n    [356, 269, 268, 355],\n    [356, 269, 365, 355],\n    [356, 441, 365, 355],\n    [356, 280, 269, 365],\n    [356, 280, 366, 365],\n    [356, 280, 366, 269],\n    [356, 260, 269, 268],\n    [356, 260, 269, 270],\n    [356, 357, 260, 270],\n    [356, 268, 346, 339],\n    [356, 260, 268, 339],\n    [356, 357, 346, 339],\n    [356, 357, 260, 339]\n], dtype='int64')\n"
  },
  {
    "path": "yt/frontends/stream/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/stream/tests/test_callable_grids.py",
    "content": "import numpy as np\nimport pytest\nimport unyt\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt import load_amr_grids, load_hdf5_file, load_uniform_grid\nfrom yt.testing import _amr_grid_index, requires_file, requires_module\n\nturb_vels = \"UnigridData/turb_vels.h5\"\n\n_existing_fields = (\n    \"Bx\",\n    \"By\",\n    \"Bz\",\n    \"Density\",\n    \"MagneticEnergy\",\n    \"Temperature\",\n    \"turb_x-velocity\",\n    \"turb_y-velocity\",\n    \"turb_z-velocity\",\n    \"x-velocity\",\n    \"y-velocity\",\n    \"z-velocity\",\n)\n\n\n@requires_file(turb_vels)\n@requires_module(\"h5py\")\ndef test_load_hdf5_file():\n    ds1 = load_hdf5_file(turb_vels)\n    assert_equal(ds1.domain_dimensions, [256, 256, 256])\n    for field_name in _existing_fields:\n        assert (\"stream\", field_name) in ds1.field_list\n    assert_equal(ds1.r[:][\"ones\"].size, 256 * 256 * 256)\n    assert_equal(ds1.r[:][\"Density\"].size, 256 * 256 * 256)\n    # Now we test that we get the same results regardless of our decomp\n    ds2 = load_hdf5_file(turb_vels, nchunks=19)\n    assert_equal(ds2.domain_dimensions, [256, 256, 256])\n    assert_equal(ds2.r[:][\"ones\"].size, 256 * 256 * 256)\n    assert_equal(ds2.r[:][\"Density\"].size, 256 * 256 * 256)\n    assert_almost_equal(ds2.r[:][\"Density\"].min(), ds1.r[:][\"Density\"].min())\n    assert_almost_equal(ds2.r[:][\"Density\"].max(), ds1.r[:][\"Density\"].max())\n    assert_almost_equal(ds2.r[:][\"Density\"].std(), ds1.r[:][\"Density\"].std())\n    # test that we can load this dataset with a different bounding box and length units\n    ds3 = load_hdf5_file(\n        turb_vels,\n        bbox=np.array([[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0]]),\n        dataset_arguments={\"length_unit\": (1.0, \"kpc\")},\n    )\n    assert_almost_equal(ds3.domain_width, ds3.arr([2, 2, 2], \"kpc\"))\n\n\n_x_coefficients = (100, 50, 30, 10, 20)\n_y_coefficients = (20, 90, 80, 30, 30)\n_z_coefficients = (50, 10, 90, 40, 40)\n\n\ndef _grid_data_function(grid, field_name):\n    # We want N points from the cell-center to the cell-center on the other side\n    x, y, z = (\n        np.linspace(\n            grid.LeftEdge[i] + grid.dds[i] / 2,\n            grid.RightEdge[i] - grid.dds[i] / 2,\n            grid.ActiveDimensions[i],\n        )\n        for i in (0, 1, 2)\n    )\n    r = np.sqrt(\n        ((x.d - 0.5) ** 2)[:, None, None]\n        + ((y.d - 0.5) ** 2)[None, :, None]\n        + ((z.d - 0.5) ** 2)[None, None, :]\n    )\n    atten = np.exp(-20 * (1.1 * r**2))\n    xv = sum(\n        c * np.sin(2 ** (1 + i) * (x.d * np.pi * 2))\n        for i, c in enumerate(_x_coefficients)\n    )\n    yv = sum(\n        c * np.sin(2 ** (1 + i) * (y.d * np.pi * 2))\n        for i, c in enumerate(_y_coefficients)\n    )\n    zv = sum(\n        c * np.sin(2 ** (1 + i) * (z.d * np.pi * 2))\n        for i, c in enumerate(_z_coefficients)\n    )\n    return atten * (xv[:, None, None] * yv[None, :, None] * zv[None, None, :])\n\n\ndef test_load_callable():\n    grid_data = []\n    for level, le, re, dims in _amr_grid_index:\n        grid_data.append(\n            {\n                \"level\": level,\n                \"left_edge\": le,\n                \"right_edge\": re,\n                \"dimensions\": dims,\n                \"density\": _grid_data_function,\n            }\n        )\n    ds = load_amr_grids(\n        grid_data, [32, 32, 32], bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])\n    )\n    assert_equal(ds.r[:].sum(\"cell_volume\"), ds.domain_width.prod())\n    assert_almost_equal(ds.r[:].max(\"density\").d, 2660218.62833899)\n    assert_almost_equal(ds.r[:].min(\"density\").d, -2660218.62833899)\n\n\ndef test_load_uniform_grid_callable():\n    data = {\"density\": _grid_data_function, \"my_temp\": (_grid_data_function, \"K\")}\n    ds = load_uniform_grid(\n        data, [32, 32, 32], bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])\n    )\n    assert_equal(ds.r[:].sum(\"cell_volume\"), ds.domain_width.prod())\n    # note: the following min/max values differ from test_load_callable because\n    # the grid here is coarser and the min/max values of the function are not\n    # well-sampled.\n    assert_almost_equal(ds.r[:].max(\"density\").d, 1559160.37194738)\n    assert_almost_equal(ds.r[:].min(\"density\").d, -1559160.37194738)\n\n    assert ds.r[:].min(\"my_temp\").units == unyt.K\n\n    with pytest.raises(RuntimeError, match=\"Callable functions can not be specified\"):\n        _ = load_uniform_grid(\n            data,\n            [32, 32, 32],\n            bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]]),\n            nprocs=16,\n        )\n"
  },
  {
    "path": "yt/frontends/stream/tests/test_outputs.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\n\nimport numpy as np\nfrom numpy.testing import assert_equal, assert_raises\n\nfrom yt.loaders import load_particles, load_uniform_grid\nfrom yt.utilities.exceptions import (\n    YTInconsistentGridFieldShape,\n    YTInconsistentGridFieldShapeGridDims,\n    YTInconsistentParticleFieldShape,\n)\n\n\nclass TestEmptyLoad(unittest.TestCase):\n    def setUp(self):\n        self.tmpdir = tempfile.mkdtemp()\n        self.curdir = os.getcwd()\n        os.chdir(self.tmpdir)\n\n        # create 0 byte file\n        open(\"empty_file\", \"a\")\n\n        # create empty directory\n        os.makedirs(\"empty_directory\")\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        shutil.rmtree(self.tmpdir)\n\n\ndef test_dimensionless_field_units():\n    Z = np.random.uniform(size=(32, 32, 32))\n    d = np.random.uniform(size=(32, 32, 32))\n\n    data = {\"density\": d, \"metallicity\": Z}\n\n    ds = load_uniform_grid(data, (32, 32, 32))\n\n    dd = ds.all_data()\n\n    assert_equal(Z.max(), float(dd[\"stream\", \"metallicity\"].max()))\n\n\ndef test_inconsistent_field_shape():\n    def load_field_field_mismatch():\n        d = np.random.uniform(size=(32, 32, 32))\n        t = np.random.uniform(size=(32, 64, 32))\n        data = {\"density\": d, \"temperature\": t}\n        load_uniform_grid(data, (32, 32, 32))\n\n    assert_raises(YTInconsistentGridFieldShape, load_field_field_mismatch)\n\n    def load_field_grid_mismatch():\n        d = np.random.uniform(size=(32, 32, 32))\n        t = np.random.uniform(size=(32, 32, 32))\n        data = {\"density\": d, \"temperature\": t}\n        load_uniform_grid(data, (32, 64, 32))\n\n    assert_raises(YTInconsistentGridFieldShapeGridDims, load_field_grid_mismatch)\n\n    def load_particle_fields_mismatch():\n        x = np.random.uniform(size=100)\n        y = np.random.uniform(size=100)\n        z = np.random.uniform(size=200)\n        data = {\n            \"particle_position_x\": x,\n            \"particle_position_y\": y,\n            \"particle_position_z\": z,\n        }\n        load_particles(data)\n\n    assert_raises(YTInconsistentParticleFieldShape, load_particle_fields_mismatch)\n\n\ndef test_parameters():\n    # simple test to check that we can pass in parameters\n    Z = np.random.uniform(size=(32, 32, 32))\n    d = np.random.uniform(size=(32, 32, 32))\n\n    data = {\"density\": d, \"metallicity\": Z}\n\n    ds = load_uniform_grid(data, (32, 32, 32), parameters={\"metadata_is_nice\": True})\n    assert ds.parameters[\"metadata_is_nice\"]\n"
  },
  {
    "path": "yt/frontends/stream/tests/test_stream_amrgrids.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_raises\n\nfrom yt import ProjectionPlot, load_amr_grids\nfrom yt.utilities.exceptions import YTIllDefinedAMR, YTIntDomainOverflow\n\n\ndef test_qt_overflow():\n    grid_data = []\n\n    grid_dict = {}\n\n    grid_dict[\"left_edge\"] = [-1.0, -1.0, -1.0]\n    grid_dict[\"right_edge\"] = [1.0, 1.0, 1.0]\n    grid_dict[\"dimensions\"] = [8, 8, 8]\n    grid_dict[\"level\"] = 0\n\n    grid_dict[\"density\"] = np.ones((8, 8, 8))\n\n    grid_data.append(grid_dict)\n\n    domain_dimensions = np.array([8, 8, 8])\n\n    spf = load_amr_grids(grid_data, domain_dimensions)\n\n    def make_proj():\n        p = ProjectionPlot(spf, \"x\", [(\"gas\", \"density\")], center=\"c\", origin=\"native\")\n        return p\n\n    assert_raises(YTIntDomainOverflow, make_proj)\n\n\ndef test_refine_by():\n    grid_data = []\n    ref_by = 4\n    lo = 0.0\n    hi = 1.0\n    fine_grid_width = (hi - lo) / ref_by\n    for level in range(2):\n        grid_dict = {}\n\n        grid_dict[\"left_edge\"] = [0.0 + 0.5 * fine_grid_width * level] * 3\n        grid_dict[\"right_edge\"] = [1.0 - 0.5 * fine_grid_width * level] * 3\n        grid_dict[\"dimensions\"] = [8, 8, 8]\n        grid_dict[\"level\"] = level\n\n        grid_dict[\"density\"] = np.ones((8, 8, 8))\n\n        grid_data.append(grid_dict)\n\n    domain_dimensions = np.array([8, 8, 8])\n\n    load_amr_grids(grid_data, domain_dimensions, refine_by=ref_by)\n\n\ndef test_validation():\n    dims = np.array([4, 2, 4])\n    grid_data = [\n        {\n            \"left_edge\": [0.0, 0.0, 0.0],\n            \"right_edge\": [1.0, 1.0, 1.0],\n            \"level\": 0,\n            \"dimensions\": dims,\n        },\n        {\n            \"left_edge\": [0.25, 0.25, 0.25],\n            \"right_edge\": [0.75, 0.75, 0.75],\n            \"level\": 1,\n            \"dimensions\": dims,\n        },\n    ]\n    bbox = np.array([[0, 1], [0, 1], [0, 1]])\n\n    def load_grids():\n        load_amr_grids(\n            grid_data,\n            dims,\n            bbox=bbox,\n            periodicity=(0, 0, 0),\n            length_unit=1.0,\n            refine_by=2,\n        )\n\n    assert_raises(YTIllDefinedAMR, load_grids)\n"
  },
  {
    "path": "yt/frontends/stream/tests/test_stream_hexahedral.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt import SlicePlot\nfrom yt.frontends.stream.data_structures import hexahedral_connectivity\nfrom yt.loaders import load_hexahedral_mesh\n\n# Field information\n\n\ndef test_stream_hexahedral():\n    np.random.seed(0x4D3D3D3)\n    Nx, Ny, Nz = 32, 18, 24\n    # Note what we're doing here -- we are creating a randomly spaced mesh, but\n    # because of how the accumulate operation works, we also reset the leftmost\n    # cell boundary to 0.0.\n    cell_x = np.random.random(Nx + 1)\n    cell_x /= cell_x.sum()\n    cell_x = np.add.accumulate(cell_x)\n    cell_x[0] = 0.0\n\n    cell_y = np.random.random(Ny + 1)\n    cell_y /= cell_y.sum()\n    cell_y = np.add.accumulate(cell_y)\n    cell_y[0] = 0.0\n\n    cell_z = np.random.random(Nz + 1)\n    cell_z /= cell_z.sum()\n    cell_z = np.add.accumulate(cell_z)\n    cell_z[0] = 0.0\n\n    coords, conn = hexahedral_connectivity(cell_x, cell_y, cell_z)\n    data = {\"random_field\": np.random.random((Nx, Ny, Nz))}\n    bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])\n    ds = load_hexahedral_mesh(data, conn, coords, bbox=bbox)\n    dd = ds.all_data()\n    # raise RuntimeError\n    assert_almost_equal(float(dd[\"gas\", \"cell_volume\"].sum(dtype=\"float64\")), 1.0)\n    assert_equal(dd[\"index\", \"ones\"].size, Nx * Ny * Nz)\n    # Now we try it with a standard mesh\n    cell_x = np.linspace(0.0, 1.0, Nx + 1)\n    cell_y = np.linspace(0.0, 1.0, Ny + 1)\n    cell_z = np.linspace(0.0, 1.0, Nz + 1)\n    coords, conn = hexahedral_connectivity(cell_x, cell_y, cell_z)\n    data = {\"random_field\": np.random.random((Nx, Ny, Nz))}\n    bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])\n    ds = load_hexahedral_mesh(data, conn, coords, bbox=bbox)\n    dd = ds.all_data()\n    assert_almost_equal(float(dd[\"gas\", \"cell_volume\"].sum(dtype=\"float64\")), 1.0)\n    assert_equal(dd[\"index\", \"ones\"].size, Nx * Ny * Nz)\n    assert_almost_equal(dd[\"index\", \"dx\"].to_ndarray(), 1.0 / Nx)\n    assert_almost_equal(dd[\"index\", \"dy\"].to_ndarray(), 1.0 / Ny)\n    assert_almost_equal(dd[\"index\", \"dz\"].to_ndarray(), 1.0 / Nz)\n\n    s = SlicePlot(ds, \"x\", \"random_field\")\n    s.render()\n    s.frb[\"stream\", \"random_field\"]\n"
  },
  {
    "path": "yt/frontends/stream/tests/test_stream_octree.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nimport yt\n\nOCT_MASK_LIST = [\n    8,\n    0,\n    0,\n    0,\n    0,\n    8,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    8,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n]\n\n\ndef test_octree():\n    # See Issue #1272\n    octree_mask = np.array(OCT_MASK_LIST, dtype=np.uint8)\n\n    quantities = {}\n    quantities[\"gas\", \"density\"] = np.random.random((22, 1))\n\n    bbox = np.array([[-10.0, 10.0], [-10.0, 10.0], [-10.0, 10.0]])\n\n    ds = yt.load_octree(\n        octree_mask=octree_mask,\n        data=quantities,\n        bbox=bbox,\n        num_zones=1,\n        partial_coverage=0,\n    )\n\n    proj = ds.proj((\"gas\", \"density\"), \"x\")\n    proj[\"gas\", \"density\"]\n\n    assert_equal(ds.r[:][\"ones\"].size, 22)\n    rho1 = quantities[\"gas\", \"density\"].ravel()\n    rho2 = ds.r[:][\"density\"].copy()\n    rho1.sort()\n    rho2.sort()\n    assert_equal(rho1, rho2)\n"
  },
  {
    "path": "yt/frontends/stream/tests/test_stream_particles.py",
    "content": "import numpy as np\nimport pytest\nfrom numpy.testing import assert_equal\n\nimport yt.utilities.initial_conditions as ic\nfrom yt.loaders import load_amr_grids, load_particles, load_uniform_grid\nfrom yt.testing import fake_particle_ds, fake_sph_orientation_ds\n\n# Field information\n\n\ndef test_stream_particles():\n    num_particles = 100000\n    domain_dims = (64, 64, 64)\n    dens = np.random.random(domain_dims)\n    x = np.random.uniform(size=num_particles)\n    y = np.random.uniform(size=num_particles)\n    z = np.random.uniform(size=num_particles)\n    m = np.ones(num_particles)\n\n    # Field operators and cell flagging methods\n\n    fo = []\n    fo.append(ic.TopHatSphere(0.1, [0.2, 0.3, 0.4], {\"density\": 2.0}))\n    fo.append(ic.TopHatSphere(0.05, [0.7, 0.4, 0.75], {\"density\": 20.0}))\n\n    # Add particles\n\n    fields1 = {\n        \"density\": dens,\n        \"particle_position_x\": x,\n        \"particle_position_y\": y,\n        \"particle_position_z\": z,\n        \"particle_mass\": m,\n    }\n\n    fields2 = fields1.copy()\n\n    ug1 = load_uniform_grid(fields1, domain_dims, 1.0)\n    ug2 = load_uniform_grid(fields2, domain_dims, 1.0, nprocs=8)\n\n    # Check to make sure the number of particles is the same\n\n    number_of_particles1 = np.sum([grid.NumberOfParticles for grid in ug1.index.grids])\n    number_of_particles2 = np.sum([grid.NumberOfParticles for grid in ug2.index.grids])\n\n    assert_equal(number_of_particles1, num_particles)\n    assert_equal(number_of_particles1, number_of_particles2)\n\n    for grid in ug2.index.grids:\n        tot_parts = grid[\"io\", \"particle_position_x\"].size\n        tot_all_parts = grid[\"all\", \"particle_position_x\"].size\n        assert tot_parts == grid.NumberOfParticles\n        assert tot_all_parts == grid.NumberOfParticles\n\n    # Check to make sure the fields have been defined correctly\n\n    for ptype in (\"all\", \"io\"):\n        assert (\n            ug1._get_field_info((ptype, \"particle_position_x\")).sampling_type\n            == \"particle\"\n        )\n        assert (\n            ug1._get_field_info((ptype, \"particle_position_y\")).sampling_type\n            == \"particle\"\n        )\n        assert (\n            ug1._get_field_info((ptype, \"particle_position_z\")).sampling_type\n            == \"particle\"\n        )\n        assert ug1._get_field_info((ptype, \"particle_mass\")).sampling_type == \"particle\"\n    assert not ug1._get_field_info((\"gas\", \"density\")).sampling_type == \"particle\"\n\n    for ptype in (\"all\", \"io\"):\n        assert (\n            ug2._get_field_info((ptype, \"particle_position_x\")).sampling_type\n            == \"particle\"\n        )\n        assert (\n            ug2._get_field_info((ptype, \"particle_position_y\")).sampling_type\n            == \"particle\"\n        )\n        assert (\n            ug2._get_field_info((ptype, \"particle_position_z\")).sampling_type\n            == \"particle\"\n        )\n        assert ug2._get_field_info((ptype, \"particle_mass\")).sampling_type == \"particle\"\n    assert not ug2._get_field_info((\"gas\", \"density\")).sampling_type == \"particle\"\n\n    # Now perform similar checks, but with multiple particle types\n\n    num_dm_particles = 30000\n    xd = np.random.uniform(size=num_dm_particles)\n    yd = np.random.uniform(size=num_dm_particles)\n    zd = np.random.uniform(size=num_dm_particles)\n    md = np.ones(num_dm_particles)\n\n    num_star_particles = 20000\n    xs = np.random.uniform(size=num_star_particles)\n    ys = np.random.uniform(size=num_star_particles)\n    zs = np.random.uniform(size=num_star_particles)\n    ms = 2.0 * np.ones(num_star_particles)\n\n    dens = np.random.random(domain_dims)\n\n    fields3 = {\n        \"density\": dens,\n        (\"dm\", \"particle_position_x\"): xd,\n        (\"dm\", \"particle_position_y\"): yd,\n        (\"dm\", \"particle_position_z\"): zd,\n        (\"dm\", \"particle_mass\"): md,\n        (\"star\", \"particle_position_x\"): xs,\n        (\"star\", \"particle_position_y\"): ys,\n        (\"star\", \"particle_position_z\"): zs,\n        (\"star\", \"particle_mass\"): ms,\n    }\n\n    fields4 = fields3.copy()\n\n    ug3 = load_uniform_grid(fields3, domain_dims, 1.0)\n    ug4 = load_uniform_grid(fields4, domain_dims, 1.0, nprocs=8)\n\n    # Check to make sure the number of particles is the same\n\n    number_of_particles3 = np.sum([grid.NumberOfParticles for grid in ug3.index.grids])\n    number_of_particles4 = np.sum([grid.NumberOfParticles for grid in ug4.index.grids])\n\n    assert_equal(number_of_particles3, num_dm_particles + num_star_particles)\n    assert_equal(number_of_particles3, number_of_particles4)\n\n    for grid in ug4.index.grids:\n        tot_parts = grid[\"dm\", \"particle_position_x\"].size\n        tot_parts += grid[\"star\", \"particle_position_x\"].size\n        tot_all_parts = grid[\"all\", \"particle_position_x\"].size\n        assert tot_parts == grid.NumberOfParticles\n        assert tot_all_parts == grid.NumberOfParticles\n\n    # Check to make sure the fields have been defined correctly\n\n    for ptype in (\"dm\", \"star\"):\n        assert (\n            ug3._get_field_info((ptype, \"particle_position_x\")).sampling_type\n            == \"particle\"\n        )\n        assert (\n            ug3._get_field_info((ptype, \"particle_position_y\")).sampling_type\n            == \"particle\"\n        )\n        assert (\n            ug3._get_field_info((ptype, \"particle_position_z\")).sampling_type\n            == \"particle\"\n        )\n        assert ug3._get_field_info((ptype, \"particle_mass\")).sampling_type == \"particle\"\n        assert (\n            ug4._get_field_info((ptype, \"particle_position_x\")).sampling_type\n            == \"particle\"\n        )\n        assert (\n            ug4._get_field_info((ptype, \"particle_position_y\")).sampling_type\n            == \"particle\"\n        )\n        assert (\n            ug4._get_field_info((ptype, \"particle_position_z\")).sampling_type\n            == \"particle\"\n        )\n        assert ug4._get_field_info((ptype, \"particle_mass\")).sampling_type == \"particle\"\n\n\ndef test_load_particles_types():\n    num_particles = 10000\n\n    data1 = {\n        \"particle_position_x\": np.random.random(size=num_particles),\n        \"particle_position_y\": np.random.random(size=num_particles),\n        \"particle_position_z\": np.random.random(size=num_particles),\n        \"particle_mass\": np.ones(num_particles),\n    }\n\n    ds1 = load_particles(data1)\n    ds1.index\n\n    assert set(ds1.particle_types) == {\"all\", \"io\", \"nbody\"}\n\n    dd = ds1.all_data()\n\n    for ax in \"xyz\":\n        assert dd[\"io\", f\"particle_position_{ax}\"].size == num_particles\n        assert dd[\"all\", f\"particle_position_{ax}\"].size == num_particles\n        assert dd[\"nbody\", f\"particle_position_{ax}\"].size == num_particles\n\n    num_dm_particles = 10000\n    num_star_particles = 50000\n    num_tot_particles = num_dm_particles + num_star_particles\n\n    data2 = {\n        (\"dm\", \"particle_position_x\"): np.random.random(size=num_dm_particles),\n        (\"dm\", \"particle_position_y\"): np.random.random(size=num_dm_particles),\n        (\"dm\", \"particle_position_z\"): np.random.random(size=num_dm_particles),\n        (\"dm\", \"particle_mass\"): np.ones(num_dm_particles),\n        (\"star\", \"particle_position_x\"): np.random.random(size=num_star_particles),\n        (\"star\", \"particle_position_y\"): np.random.random(size=num_star_particles),\n        (\"star\", \"particle_position_z\"): np.random.random(size=num_star_particles),\n        (\"star\", \"particle_mass\"): 2.0 * np.ones(num_star_particles),\n    }\n\n    ds2 = load_particles(data2)\n    ds2.index\n\n    # We use set here because we don't care about the order and we just need\n    # the elements to be correct\n    assert set(ds2.particle_types) == {\"all\", \"star\", \"dm\", \"nbody\"}\n\n    dd = ds2.all_data()\n\n    for ax in \"xyz\":\n        npart = 0\n        for ptype in ds2.particle_types_raw:\n            npart += dd[ptype, f\"particle_position_{ax}\"].size\n        assert npart == num_tot_particles\n        assert dd[\"all\", f\"particle_position_{ax}\"].size == num_tot_particles\n\n\ndef test_load_particles_sph_types():\n    num_particles = 10000\n\n    data = {\n        (\"gas\", \"particle_position_x\"): np.random.random(size=num_particles),\n        (\"gas\", \"particle_position_y\"): np.random.random(size=num_particles),\n        (\"gas\", \"particle_position_z\"): np.random.random(size=num_particles),\n        (\"gas\", \"particle_velocity_x\"): np.random.random(size=num_particles),\n        (\"gas\", \"particle_velocity_y\"): np.random.random(size=num_particles),\n        (\"gas\", \"particle_velocity_z\"): np.random.random(size=num_particles),\n        (\"gas\", \"particle_mass\"): np.ones(num_particles),\n        (\"gas\", \"density\"): np.ones(num_particles),\n        (\"gas\", \"smoothing_length\"): np.ones(num_particles),\n        (\"dm\", \"particle_position_x\"): np.random.random(size=num_particles),\n        (\"dm\", \"particle_position_y\"): np.random.random(size=num_particles),\n        (\"dm\", \"particle_position_z\"): np.random.random(size=num_particles),\n        (\"dm\", \"particle_velocity_x\"): np.random.random(size=num_particles),\n        (\"dm\", \"particle_velocity_y\"): np.random.random(size=num_particles),\n        (\"dm\", \"particle_velocity_z\"): np.random.random(size=num_particles),\n        (\"dm\", \"particle_mass\"): np.ones(num_particles),\n    }\n\n    ds = load_particles(data)\n\n    assert set(ds.particle_types) == {\"gas\", \"dm\"}\n    assert ds._sph_ptypes == (\"gas\",)\n\n    data.update(\n        {\n            (\"cr_gas\", \"particle_position_x\"): np.random.random(size=num_particles),\n            (\"cr_gas\", \"particle_position_y\"): np.random.random(size=num_particles),\n            (\"cr_gas\", \"particle_position_z\"): np.random.random(size=num_particles),\n            (\"cr_gas\", \"particle_velocity_x\"): np.random.random(size=num_particles),\n            (\"cr_gas\", \"particle_velocity_y\"): np.random.random(size=num_particles),\n            (\"cr_gas\", \"particle_velocity_z\"): np.random.random(size=num_particles),\n            (\"cr_gas\", \"particle_mass\"): np.ones(num_particles),\n            (\"cr_gas\", \"density\"): np.ones(num_particles),\n            (\"cr_gas\", \"smoothing_length\"): np.ones(num_particles),\n        }\n    )\n\n    with pytest.raises(\n        ValueError, match=\"Multiple SPH particle types are currently not supported!\"\n    ):\n        load_particles(data)\n\n\ndef test_load_particles_with_data_source():\n    ds1 = fake_particle_ds()\n\n    # Load from dataset\n    ad = ds1.all_data()\n    fields = [(\"all\", \"particle_mass\")]\n    fields += [(\"all\", f\"particle_position_{ax}\") for ax in \"xyz\"]\n    data = {field: ad[field] for field in fields}\n    ds2 = load_particles(data, data_source=ad)\n\n    def in_cgs(quan):\n        return quan.in_cgs().v\n\n    # Test bbox is parsed correctly\n    for attr in [\"domain_left_edge\", \"domain_right_edge\"]:\n        assert np.allclose(in_cgs(getattr(ds1, attr)), in_cgs(getattr(ds2, attr)))\n\n    # Test sim_time is parsed correctly\n    assert in_cgs(ds1.current_time) == in_cgs(ds2.current_time)\n\n    # Test code units are parsed correctly\n    def get_cu(ds, dim):\n        return ds.quan(1, \"code_\" + dim)\n\n    for dim in [\"length\", \"mass\", \"time\", \"velocity\", \"magnetic\"]:\n        assert in_cgs(get_cu(ds1, dim)) == in_cgs(get_cu(ds2, dim))\n\n\ndef test_add_sph_fields():\n    ds = fake_particle_ds()\n    ds.index\n    assert set(ds.particle_types) == {\"io\", \"all\", \"nbody\"}\n\n    ds.add_sph_fields()\n    assert set(ds.particle_types) == {\"io\", \"all\"}\n    assert (\"io\", \"smoothing_length\") in ds.field_list\n    assert (\"io\", \"density\") in ds.field_list\n\n\ndef test_particles_outside_domain():\n    np.random.seed(0x4D3D3D3)\n    posx_arr = np.random.uniform(low=-1.6, high=1.5, size=1000)\n    posy_arr = np.random.uniform(low=-1.5, high=1.5, size=1000)\n    posz_arr = np.random.uniform(low=-1.5, high=1.5, size=1000)\n    dens_arr = np.random.random((16, 16, 16))\n    data = {\n        \"density\": dens_arr,\n        \"particle_position_x\": posx_arr,\n        \"particle_position_y\": posy_arr,\n        \"particle_position_z\": posz_arr,\n    }\n    bbox = np.array([[-1.5, 1.5], [-1.5, 1.5], [-1.5, 1.5]])\n    ds = load_uniform_grid(data, (16, 16, 16), bbox=bbox, nprocs=4)\n    wh = (posx_arr < bbox[0, 0]).nonzero()[0]\n    assert wh.size == 1000 - ds.particle_type_counts[\"io\"]\n    ad = ds.all_data()\n    assert ds.particle_type_counts[\"io\"] == ad[\"all\", \"particle_position_x\"].size\n\n\ndef test_stream_sph_projection():\n    ds = fake_sph_orientation_ds()\n    proj = ds.proj((\"gas\", \"density\"), 2)\n    frb = proj.to_frb(ds.domain_width[0], (256, 256))\n    image = frb[\"gas\", \"density\"]\n    assert image.max() > 0\n    assert image.shape == (256, 256)\n\n\n@pytest.mark.parametrize(\"loader\", (load_uniform_grid, load_amr_grids))\ndef test_stream_non_cartesian_particles(loader):\n    eps = 1e-6\n    r, theta, phi = np.mgrid[\n        0.0 : 1.0 - eps : 64j, 0.0 : np.pi - eps : 64j, 0.0 : 2.0 * np.pi - eps : 64j\n    ]\n    np.random.seed(0x4D3D3D3)\n    ind = np.random.randint(0, 64 * 64 * 64, size=1000)\n\n    particle_position_r = r.ravel()[ind]\n    particle_position_theta = theta.ravel()[ind]\n    particle_position_phi = phi.ravel()[ind]\n\n    ds = load_uniform_grid(\n        {\n            \"density\": r,\n            \"temperature\": phi,\n            \"entropy\": phi,\n            \"particle_position_r\": particle_position_r,\n            \"particle_position_theta\": particle_position_theta,\n            \"particle_position_phi\": particle_position_phi,\n        },\n        (64, 64, 64),\n        bbox=np.array([[0.0, 1.0], [0.0, np.pi], [0.0, 2.0 * np.pi]]),\n        geometry=\"spherical\",\n    )\n\n    dd = ds.all_data()\n    assert_equal(dd[\"all\", \"particle_position_r\"].v, particle_position_r)\n    assert_equal(dd[\"all\", \"particle_position_phi\"].v, particle_position_phi)\n    assert_equal(dd[\"all\", \"particle_position_theta\"].v, particle_position_theta)\n\n\ndef test_stream_non_cartesian_particles_amr():\n    eps = 1e-6\n    r, theta, phi = np.mgrid[\n        0.0 : 1.0 - eps : 64j, 0.0 : np.pi - eps : 64j, 0.0 : 2.0 * np.pi - eps : 64j\n    ]\n    np.random.seed(0x4D3D3D3)\n    ind = np.random.randint(0, 64 * 64 * 64, size=1000)\n\n    particle_position_r = r.ravel()[ind]\n    particle_position_theta = theta.ravel()[ind]\n    particle_position_phi = phi.ravel()[ind]\n\n    ds = load_amr_grids(\n        [\n            {\n                \"density\": r,\n                \"temperature\": phi,\n                \"entropy\": phi,\n                \"particle_position_r\": particle_position_r,\n                \"particle_position_theta\": particle_position_theta,\n                \"particle_position_phi\": particle_position_phi,\n                \"dimensions\": [64, 64, 64],\n                \"level\": 0,\n                \"left_edge\": [0.0, 0.0, 0.0],\n                \"right_edge\": [1.0, np.pi, 2.0 * np.pi],\n            }\n        ],\n        (64, 64, 64),\n        bbox=np.array([[0.0, 1.0], [0.0, np.pi], [0.0, 2.0 * np.pi]]),\n        geometry=\"spherical\",\n    )\n\n    dd = ds.all_data()\n    assert_equal(dd[\"all\", \"particle_position_r\"].v, particle_position_r)\n    assert_equal(dd[\"all\", \"particle_position_phi\"].v, particle_position_phi)\n    assert_equal(dd[\"all\", \"particle_position_theta\"].v, particle_position_theta)\n"
  },
  {
    "path": "yt/frontends/stream/tests/test_stream_species.py",
    "content": "import numpy as np\n\nfrom yt.loaders import load_uniform_grid\nfrom yt.testing import assert_allclose_units\n\n\ndef test_stream_species():\n    prng = np.random.default_rng(seed=42)\n    arr = prng.uniform(size=(32, 32, 32))\n\n    data = {\n        \"density\": (arr, \"g/cm**3\"),\n        \"H_p0_fraction\": (0.37 * np.ones_like(arr), \"dimensionless\"),\n        \"H_p1_fraction\": (0.37 * np.ones_like(arr), \"dimensionless\"),\n        \"He_fraction\": (0.24 * np.ones_like(arr), \"dimensionless\"),\n        \"CO_fraction\": (0.02 * np.ones_like(arr), \"dimensionless\"),\n    }\n    bbox = np.array([[-1.5, 1.5], [-1.5, 1.5], [-1.5, 1.5]])\n    ds = load_uniform_grid(data, arr.shape, length_unit=\"Mpc\", bbox=bbox, nprocs=64)\n    assert (\"gas\", \"CO_density\") in ds.derived_field_list\n    assert (\"gas\", \"H_nuclei_density\") in ds.derived_field_list\n    assert (\"gas\", \"H_p0_number_density\") in ds.derived_field_list\n    dd = ds.all_data()\n    assert_allclose_units(dd[\"gas\", \"CO_density\"], 0.02 * dd[\"gas\", \"density\"])\n    all_H = dd[\"gas\", \"H_p0_number_density\"] + dd[\"gas\", \"H_p1_number_density\"]\n    assert_allclose_units(all_H, dd[\"gas\", \"H_nuclei_density\"])\n"
  },
  {
    "path": "yt/frontends/stream/tests/test_stream_stretched.py",
    "content": "import numpy as np\nimport pytest\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt import load_uniform_grid\n\n\ndef test_variable_dx():\n    np.random.seed(0x4D3D3D3)\n\n    data = {\"density\": np.random.random((128, 128, 128))}\n\n    cell_widths = []\n    for _ in range(3):\n        cw = np.random.random(128)\n        cw /= cw.sum()\n        cell_widths.append(cw)\n\n    ds = load_uniform_grid(\n        data,\n        [128, 128, 128],\n        bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]]),\n        cell_widths=cell_widths,\n    )\n\n    # We now check that we get all of our original cell widths back out, and\n    # only those cell widths\n\n    assert_equal(np.unique(ds.index.grids[0][\"index\", \"dx\"]).size, 128)\n    assert_equal(ds.index.grids[0][\"index\", \"dx\"][:, 0, 0], cell_widths[0])\n\n    assert_equal(np.unique(ds.index.grids[0][\"index\", \"dx\"]).size, 128)\n    assert_equal(ds.index.grids[0][\"index\", \"dy\"][0, :, 0], cell_widths[1])\n\n    assert_equal(np.unique(ds.index.grids[0][\"index\", \"dx\"]).size, 128)\n    assert_equal(ds.index.grids[0][\"index\", \"dz\"][0, 0, :], cell_widths[2])\n\n    assert_equal(np.unique(ds.index.grids[0][\"index\", \"x\"]).size, 128)\n    center_x = np.add.accumulate(cell_widths[0]) - 0.5 * cell_widths[0]\n    assert_equal(center_x, ds.index.grids[0][\"index\", \"x\"][:, 0, 0])\n\n    assert_equal(np.unique(ds.index.grids[0][\"index\", \"y\"]).size, 128)\n    center_y = np.add.accumulate(cell_widths[1]) - 0.5 * cell_widths[1]\n    assert_equal(center_y, ds.index.grids[0][\"index\", \"y\"][0, :, 0])\n\n    assert_equal(np.unique(ds.index.grids[0][\"index\", \"z\"]).size, 128)\n    center_z = np.add.accumulate(cell_widths[2]) - 0.5 * cell_widths[2]\n    assert_equal(center_z, ds.index.grids[0][\"index\", \"z\"][0, 0, :])\n\n    assert_almost_equal(ds.r[:].sum((\"index\", \"cell_volume\")), ds.domain_width.prod())\n\n    for ax in \"xyz\":\n        dd = ds.all_data()\n        p = dd.integrate(\"ones\", axis=ax)\n        assert_almost_equal(p[\"index\", \"ones\"].max().d, 1.0)\n        assert_almost_equal(p[\"index\", \"ones\"].min().d, 1.0)\n\n\n@pytest.fixture\ndef data_cell_widths_N16():\n    np.random.seed(0x4D3D3D3)\n    N = 16\n    data = {\n        \"density\": np.random.random((N, N, N)),\n        \"temperature\": np.random.random((N, N, N)),\n    }\n\n    cell_widths = []\n    for _ in range(3):\n        cw = np.random.random(N)\n        cw /= cw.sum()\n        cell_widths.append(cw)\n\n    return (data, cell_widths)\n\n\ndef test_cell_width_type(data_cell_widths_N16):\n    # checks that cell widths are properly upcast to float64 (this errors\n    # if that is not the case).\n\n    data, cell_widths = data_cell_widths_N16\n    cell_widths = [cw.astype(np.float32) for cw in cell_widths]\n    ds = load_uniform_grid(\n        data,\n        data[\"density\"].shape,\n        bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]]),\n        cell_widths=cell_widths,\n    )\n\n    _ = ds.slice(0, ds.domain_center[0])[\"stream\", \"density\"]\n\n\ndef test_cell_width_dimensionality(data_cell_widths_N16):\n    data, cell_widths = data_cell_widths_N16\n\n    # single np array in list should error\n    with pytest.raises(ValueError, match=\"The number of elements in cell_widths\"):\n        _ = load_uniform_grid(\n            data,\n            data[\"density\"].shape,\n            bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]]),\n            cell_widths=[cell_widths[0]],\n        )\n\n    # mismatched shapes should error\n    with pytest.raises(ValueError, match=\"The number of elements in cell_widths\"):\n        _ = load_uniform_grid(\n            data,\n            data[\"density\"].shape,\n            bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]]),\n            cell_widths=[cell_widths[1:]],\n        )\n\n\ndef test_cell_width_with_nproc(data_cell_widths_N16):\n    data, cell_widths = data_cell_widths_N16\n\n    ds = load_uniform_grid(\n        data,\n        data[\"density\"].shape,\n        bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]]),\n        cell_widths=cell_widths,\n        nprocs=4,\n    )\n\n    assert ds.index.num_grids == 4\n\n    # check that it successfully decomposed\n    grid = ds.index.grids[0]\n    n_cells = np.prod(grid.shape)\n    assert n_cells == data[\"density\"].size / 4\n\n    # and try a selection\n    c = (grid.RightEdge + grid.LeftEdge) / 2.0\n    reg = ds.region(c, grid.LeftEdge, grid.RightEdge)\n    assert reg[\"gas\", \"density\"].size == n_cells\n"
  },
  {
    "path": "yt/frontends/stream/tests/test_stream_unstructured.py",
    "content": "import numpy as np\n\nfrom yt import SlicePlot, load_unstructured_mesh\n\n\ndef test_multi_mesh():\n    coordsMulti = np.array(\n        [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], dtype=np.float64\n    )\n\n    connect1 = np.array(\n        [\n            [0, 1, 3],\n        ],\n        dtype=np.int64,\n    )\n    connect2 = np.array(\n        [\n            [1, 2, 3],\n        ],\n        dtype=np.int64,\n    )\n\n    data1 = {}\n    data2 = {}\n    data1[\"connect1\", \"test\"] = np.array(\n        [\n            [0.0, 1.0, 3.0],\n        ],\n        dtype=np.float64,\n    )\n    data2[\"connect2\", \"test\"] = np.array(\n        [\n            [1.0, 2.0, 3.0],\n        ],\n        dtype=np.float64,\n    )\n\n    connectList = [connect1, connect2]\n    dataList = [data1, data2]\n\n    ds = load_unstructured_mesh(connectList, coordsMulti, dataList)\n\n    sl = SlicePlot(ds, \"z\", (\"connect1\", \"test\"))\n    assert sl.data_source.field_data[\"connect1\", \"test\"].shape == (1, 3)\n    sl = SlicePlot(ds, \"z\", (\"connect2\", \"test\"))\n    assert sl.data_source.field_data[\"connect2\", \"test\"].shape == (1, 3)\n    sl = SlicePlot(ds, \"z\", (\"all\", \"test\"))\n    assert sl.data_source.field_data[\"all\", \"test\"].shape == (2, 3)\n    sl.annotate_mesh_lines()\n\n\ndef test_multi_field():\n    coords = np.array(\n        [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], dtype=np.float64\n    )\n\n    connect = np.array([[0, 1, 3], [1, 2, 3]], dtype=np.int64)\n\n    data = {}\n    data[\"connect1\", \"test\"] = np.array(\n        [[0.0, 1.0, 3.0], [1.0, 2.0, 3.0]], dtype=np.float64\n    )\n    data[\"connect1\", \"testAgain\"] = np.array(\n        [[0.0, 1.0, 3.0], [1.0, 2.0, 3.0]], dtype=np.float64\n    )\n\n    ds = load_unstructured_mesh(connect, coords, data)\n\n    sl = SlicePlot(ds, \"z\", (\"connect1\", \"test\"))\n    sl.annotate_mesh_lines()\n\n    sl = SlicePlot(ds, \"z\", (\"connect1\", \"testAgain\"))\n    sl.annotate_mesh_lines()\n\n\ndef test_units():\n    coords = np.array(\n        [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]], dtype=np.float64\n    )\n\n    connect = np.array([[0, 1, 3], [1, 2, 3]], dtype=np.int64)\n\n    data = {}\n    data[\"connect1\", \"density\"] = (\n        np.array([[0.0, 1.0, 3.0], [1.0, 2.0, 3.0]], dtype=np.float64),\n        \"mp/cm**3\",\n    )\n    data[\"connect1\", \"testAgain\"] = np.array(\n        [[0.0, 1.0, 3.0], [1.0, 2.0, 3.0]], dtype=np.float64\n    )\n\n    ds = load_unstructured_mesh(connect, coords, data)\n    ad = ds.all_data()\n    ad[\"connect1\", \"density\"].to(\"kg/m**3\")  # should work\n    ad[\"connect1\", \"testAgain\"].to(\"1\")  # should work\n"
  },
  {
    "path": "yt/frontends/stream/tests/test_update_data.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.profiles import create_profile\nfrom yt.testing import fake_particle_ds, fake_random_ds\n\n\ndef test_update_data_grid():\n    ds = fake_random_ds(64, nprocs=8)\n    ds.index\n    dims = (32, 32, 32)\n    grid_data = [\n        {\"temperature\": np.random.uniform(size=dims)} for i in range(ds.index.num_grids)\n    ]\n    ds.index.update_data(grid_data)\n    prj = ds.proj((\"gas\", \"temperature\"), 2)\n    prj[\"gas\", \"temperature\"]\n    dd = ds.all_data()\n    profile = create_profile(dd, (\"gas\", \"density\"), (\"gas\", \"temperature\"), 10)\n    profile[\"gas\", \"temperature\"]\n\n\ndef test_update_data_particle():\n    npart = 100\n    ds = fake_particle_ds(npart=npart)\n    part_data = {\"temperature\": np.random.rand(npart)}\n    ds.index.update_data(part_data)\n    assert (\"io\", \"temperature\") in ds.field_list\n    dd = ds.all_data()\n    dd[\"io\", \"temperature\"]\n"
  },
  {
    "path": "yt/frontends/swift/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/swift/api.py",
    "content": "from yt.frontends.sph.fields import SPHFieldInfo\n\nfrom . import tests\nfrom .data_structures import SwiftDataset\nfrom .io import IOHandlerSwift\n"
  },
  {
    "path": "yt/frontends/swift/data_structures.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.static_output import ParticleFile\nfrom yt.frontends.sph.data_structures import SPHDataset, SPHParticleIndex\nfrom yt.funcs import only_on_root\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .fields import SwiftFieldInfo\n\n\nclass SwiftParticleFile(ParticleFile):\n    pass\n\n\nclass SwiftDataset(SPHDataset):\n    _load_requirements = [\"h5py\"]\n    _index_class = SPHParticleIndex\n    _field_info_class = SwiftFieldInfo\n    _file_class = SwiftParticleFile\n\n    _particle_mass_name = \"Masses\"\n    _particle_coordinates_name = \"Coordinates\"\n    _particle_velocity_name = \"Velocities\"\n    _sph_ptypes = (\"PartType0\",)\n    _suffix = \".hdf5\"\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"swift\",\n        storage_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        super().__init__(\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n            default_species_fields=default_species_fields,\n        )\n        self.storage_filename = storage_filename\n        self.refine_by = 1\n\n    def _set_code_unit_attributes(self):\n        \"\"\"\n        Sets the units from the SWIFT internal unit system.\n\n        Currently sets length, mass, time, and temperature.\n\n        SWIFT uses comoving coordinates without the usual h-factors.\n        \"\"\"\n        units = self._get_info_attributes(\"Units\")\n\n        if self.cosmological_simulation == 1:\n            msg = \"Assuming length units are in comoving centimetres\"\n            only_on_root(mylog.info, msg)\n            self.length_unit = self.quan(\n                float(units[\"Unit length in cgs (U_L)\"]), \"cmcm\"\n            )\n        else:\n            msg = \"Assuming length units are in physical centimetres\"\n            only_on_root(mylog.info, msg)\n            self.length_unit = self.quan(float(units[\"Unit length in cgs (U_L)\"]), \"cm\")\n\n        self.mass_unit = self.quan(float(units[\"Unit mass in cgs (U_M)\"]), \"g\")\n        self.time_unit = self.quan(float(units[\"Unit time in cgs (U_t)\"]), \"s\")\n        self.temperature_unit = self.quan(\n            float(units[\"Unit temperature in cgs (U_T)\"]), \"K\"\n        )\n\n        return\n\n    def _get_info_attributes(self, dataset):\n        \"\"\"\n        Gets the information from a header-style dataset and returns it as a\n        python dictionary.\n\n        Example: self._get_info_attributes(header) returns a dictionary of all\n        of the information in the Header.attrs.\n        \"\"\"\n\n        with h5py.File(self.filename, mode=\"r\") as handle:\n            header = dict(handle[dataset].attrs)\n\n        return header\n\n    def _parse_parameter_file(self):\n        \"\"\"\n        Parse the SWIFT \"parameter file\" -- really this actually reads info\n        from the main HDF5 file as everything is replicated there and usually\n        parameterfiles are not transported.\n\n        The header information from the HDF5 file is stored in an un-parsed\n        format in self.parameters should users wish to use it.\n        \"\"\"\n        # Read from the HDF5 file, this gives us all the info we need. The rest\n        # of this function is just parsing.\n        header = self._get_info_attributes(\"Header\")\n        # RuntimePars were removed from snapshots at SWIFT commit 6271388\n        # between SWIFT versions 0.8.5 and 0.9.0\n        with h5py.File(self.filename, mode=\"r\") as handle:\n            has_runtime_pars = \"RuntimePars\" in handle.keys()\n\n        if has_runtime_pars:\n            runtime_parameters = self._get_info_attributes(\"RuntimePars\")\n        else:\n            runtime_parameters = {}\n\n        policy = self._get_info_attributes(\"Policy\")\n        # These are the parameterfile parameters from *.yml at runtime\n        parameters = self._get_info_attributes(\"Parameters\")\n\n        # Not used in this function, but passed to parameters\n        hydro = self._get_info_attributes(\"HydroScheme\")\n        subgrid = self._get_info_attributes(\"SubgridScheme\")\n\n        self.domain_right_edge = header[\"BoxSize\"]\n        self.domain_left_edge = np.zeros_like(self.domain_right_edge)\n\n        self.dimensionality = int(header[\"Dimension\"])\n\n        # SWIFT is either all periodic, or not periodic at all\n        if has_runtime_pars:\n            periodic = int(runtime_parameters[\"PeriodicBoundariesOn\"])\n        else:\n            periodic = int(parameters[\"InitialConditions:periodic\"])\n\n        if periodic:\n            self._periodicity = [True] * self.dimensionality\n        else:\n            self._periodicity = [False] * self.dimensionality\n\n        # Units get attached to this\n        self.current_time = float(header[\"Time\"])\n\n        # Now cosmology enters the fray, as a runtime parameter.\n        self.cosmological_simulation = int(policy[\"cosmological integration\"])\n\n        if self.cosmological_simulation:\n            try:\n                self.current_redshift = float(header[\"Redshift\"])\n                # These won't be present if self.cosmological_simulation is false\n                self.omega_lambda = float(parameters[\"Cosmology:Omega_lambda\"])\n                # Cosmology:Omega_m parameter deprecated at SWIFT commit d2783c2\n                # Between SWIFT versions 0.9.0 and 1.0.0\n                if \"Cosmology:Omega_cdm\" in parameters:\n                    self.omega_matter = float(parameters[\"Cosmology:Omega_b\"]) + float(\n                        parameters[\"Cosmology:Omega_cdm\"]\n                    )\n                else:\n                    self.omega_matter = float(parameters[\"Cosmology:Omega_m\"])\n                # This is \"little h\"\n                self.hubble_constant = float(parameters[\"Cosmology:h\"])\n            except KeyError:\n                mylog.warning(\n                    \"Could not find cosmology information in Parameters, \"\n                    \"despite having ran with -c signifying a cosmological \"\n                    \"run.\"\n                )\n                mylog.info(\"Setting up as a non-cosmological run. Check this!\")\n                self.cosmological_simulation = 0\n                self.current_redshift = 0.0\n                self.omega_lambda = 0.0\n                self.omega_matter = 0.0\n                self.hubble_constant = 0.0\n        else:\n            self.current_redshift = 0.0\n            self.omega_lambda = 0.0\n            self.omega_matter = 0.0\n            self.hubble_constant = 0.0\n\n        # Store the un-parsed information should people want it.\n        self.parameters = {\n            \"header\": header,\n            \"policy\": policy,\n            \"parameters\": parameters,\n            # NOTE: runtime_parameters may be empty\n            \"runtime_parameters\": runtime_parameters,\n            \"hydro\": hydro,\n            \"subgrid\": subgrid,\n        }\n\n        # SWIFT never has multi file snapshots\n        self.file_count = 1\n        self.filename_template = self.parameter_filename\n\n        return\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        \"\"\"\n        Checks to see if the file is a valid output from SWIFT.\n        This requires the file to have the Code attribute set in the\n        Header dataset to \"SWIFT\".\n        \"\"\"\n        if cls._missing_load_requirements():\n            return False\n\n        valid = True\n        # Attempt to open the file, if it's not a hdf5 then this will fail:\n        try:\n            handle = h5py.File(filename, mode=\"r\")\n            valid = handle[\"Header\"].attrs[\"Code\"].decode(\"utf-8\") == \"SWIFT\"\n            handle.close()\n        except (OSError, KeyError):\n            valid = False\n\n        return valid\n"
  },
  {
    "path": "yt/frontends/swift/fields.py",
    "content": "from yt.frontends.sph.fields import SPHFieldInfo\n\n\nclass SwiftFieldInfo(SPHFieldInfo):\n    def __init__(self, ds, field_list, slice_info=None):\n        self.known_particle_fields += (\n            (\n                \"InternalEnergies\",\n                (\"code_specific_energy\", [\"specific_thermal_energy\"], None),\n            ),\n            (\"Densities\", (\"code_mass / code_length**3\", [\"density\"], None)),\n            (\"SmoothingLengths\", (\"code_length\", [\"smoothing_length\"], None)),\n        )\n        super().__init__(ds, field_list, slice_info)\n\n    def setup_particle_fields(self, ptype, *args, **kwargs):\n        super().setup_particle_fields(ptype, *args, **kwargs)\n\n        if ptype in (\"PartType0\", \"Gas\"):\n            self.setup_gas_particle_fields(ptype)\n\n    def setup_gas_particle_fields(self, ptype):\n        self.alias((ptype, \"temperature\"), (ptype, \"Temperatures\"))\n        self.alias((\"gas\", \"temperature\"), (ptype, \"Temperatures\"))\n\n        for ax in (\"x\", \"y\", \"z\"):\n            self.alias((ptype, ax), (ptype, \"particle_position_\" + ax))\n"
  },
  {
    "path": "yt/frontends/swift/io.py",
    "content": "import numpy as np\n\nfrom yt.frontends.sph.io import IOHandlerSPH\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\nclass IOHandlerSwift(IOHandlerSPH):\n    _dataset_type = \"swift\"\n\n    def __init__(self, ds, *args, **kwargs):\n        super().__init__(ds, *args, **kwargs)\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    # NOTE: we refer to sub_files in the next sections, these sub_files may\n    # actually be full data_files.\n    # In the event data_files are too big, yt breaks them up into sub_files and\n    # we sort of treat them as files in the chunking system\n    def _read_particle_coords(self, chunks, ptf):\n        # This will read chunks and yield the results.\n        # yt has the concept of sub_files, i.e, we break up big files into\n        # virtual sub_files to deal with the chunking system\n        for sub_file in self._sorted_chunk_iterator(chunks):\n            si, ei = sub_file.start, sub_file.end\n            f = h5py.File(sub_file.filename, mode=\"r\")\n            # This double-reads\n            for ptype in sorted(ptf):\n                if sub_file.total_particles[ptype] == 0:\n                    continue\n                pos = f[f\"/{ptype}/Coordinates\"][si:ei, :]\n                pos = pos.astype(\"float64\", copy=False)\n                if ptype == self.ds._sph_ptypes[0]:\n                    hsml = self._get_smoothing_length(sub_file)\n                else:\n                    hsml = 0.0\n                yield ptype, (pos[:, 0], pos[:, 1], pos[:, 2]), hsml\n            f.close()\n\n    def _yield_coordinates(self, sub_file, needed_ptype=None):\n        si, ei = sub_file.start, sub_file.end\n        f = h5py.File(sub_file.filename, mode=\"r\")\n        pcount = f[\"/Header\"].attrs[\"NumPart_ThisFile\"][:].astype(\"int64\")\n        np.clip(pcount - si, 0, ei - si, out=pcount)\n        pcount = pcount.sum()\n        for key in f.keys():\n            if (\n                not key.startswith(\"PartType\")\n                or \"Coordinates\" not in f[key]\n                or needed_ptype\n                and key != needed_ptype\n            ):\n                continue\n            pos = f[key][\"Coordinates\"][si:ei, ...]\n            pos = pos.astype(\"float64\", copy=False)\n            yield key, pos\n        f.close()\n\n    def _get_smoothing_length(self, sub_file, pdtype=None, pshape=None):\n        # We do not need the pdtype and the pshape, but some frontends do so we\n        # accept them and then just ignore them\n        ptype = self.ds._sph_ptypes[0]\n        ind = int(ptype[-1])\n        si, ei = sub_file.start, sub_file.end\n        with h5py.File(sub_file.filename, mode=\"r\") as f:\n            pcount = f[\"/Header\"].attrs[\"NumPart_ThisFile\"][ind].astype(\"int64\")\n            pcount = np.clip(pcount - si, 0, ei - si)\n            keys = f[ptype].keys()\n            # SWIFT commit a94cc81 changed from \"SmoothingLength\" to \"SmoothingLengths\"\n            # between SWIFT versions 0.8.2 and 0.8.3\n            if \"SmoothingLengths\" in keys:\n                hsml = f[ptype][\"SmoothingLengths\"][si:ei, ...]\n            else:\n                hsml = f[ptype][\"SmoothingLength\"][si:ei, ...]\n            # we upscale to float64\n            hsml = hsml.astype(\"float64\", copy=False)\n            return hsml\n\n    def _read_particle_data_file(self, sub_file, ptf, selector=None):\n        # note: this frontend uses the variable name and terminology sub_file.\n        # other frontends use data_file with the understanding that it may\n        # actually be a sub_file, hence the super()._read_datafile is called\n        # ._read_datafile instead of ._read_subfile\n        return_data = {}\n\n        si, ei = sub_file.start, sub_file.end\n        f = h5py.File(sub_file.filename, mode=\"r\")\n        for ptype, field_list in sorted(ptf.items()):\n            if sub_file.total_particles[ptype] == 0:\n                continue\n            g = f[f\"/{ptype}\"]\n            # this should load as float64\n            coords = g[\"Coordinates\"][si:ei]\n            if ptype == \"PartType0\":\n                hsmls = self._get_smoothing_length(sub_file)\n            else:\n                hsmls = 0.0\n\n            if selector:\n                mask = selector.select_points(\n                    coords[:, 0], coords[:, 1], coords[:, 2], hsmls\n                )\n            del coords\n            if selector and mask is None:\n                continue\n            for field in field_list:\n                if field in (\"Mass\", \"Masses\"):\n                    data = g[self.ds._particle_mass_name][si:ei]\n                else:\n                    data = g[field][si:ei]\n\n                if selector:\n                    data = data[mask, ...]\n\n                data.astype(\"float64\", copy=False)\n\n                return_data[ptype, field] = data\n        f.close()\n\n        return return_data\n\n    def _count_particles(self, data_file):\n        si, ei = data_file.start, data_file.end\n        f = h5py.File(data_file.filename, mode=\"r\")\n        pcount = f[\"/Header\"].attrs[\"NumPart_ThisFile\"][:].astype(\"int64\")\n        f.close()\n        # if this data_file was a sub_file, then we just extract the region\n        # defined by the subfile\n        if None not in (si, ei):\n            np.clip(pcount - si, 0, ei - si, out=pcount)\n        npart = {f\"PartType{i}\": v for i, v in enumerate(pcount)}\n        return npart\n\n    def _identify_fields(self, data_file):\n        f = h5py.File(data_file.filename, mode=\"r\")\n        fields = []\n        cname = self.ds._particle_coordinates_name  # Coordinates\n        mname = self.ds._particle_mass_name  # Coordinates\n\n        for key in f.keys():\n            if not key.startswith(\"PartType\"):\n                continue\n\n            g = f[key]\n            if cname not in g:\n                continue\n\n            ptype = str(key)\n            for k in g.keys():\n                kk = k\n                if str(kk) == mname:\n                    fields.append((ptype, \"Mass\"))\n                    continue\n                if not hasattr(g[kk], \"shape\"):\n                    continue\n                if len(g[kk].shape) > 1:\n                    self._vector_fields[kk] = g[kk].shape[1]\n                fields.append((ptype, str(kk)))\n\n        f.close()\n        return fields, {}\n"
  },
  {
    "path": "yt/frontends/swift/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/swift/tests/test_outputs.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal\n\nfrom yt import load\nfrom yt.frontends.swift.api import SwiftDataset\nfrom yt.testing import ParticleSelectionComparison, requires_file, requires_module\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nkeplerian_ring = \"KeplerianRing/keplerian_ring_0020.hdf5\"\nEAGLE_6 = \"EAGLE_6/eagle_0005.hdf5\"\n\n\n# Combined the tests for loading a file and ensuring the units have been\n# implemented correctly to save time on re-loading a dataset\n@requires_module(\"h5py\")\n@requires_file(keplerian_ring)\ndef test_non_cosmo_dataset():\n    ds = load(keplerian_ring)\n    assert type(ds) is SwiftDataset\n\n    field = (\"gas\", \"density\")\n    ad = ds.all_data()\n    yt_density = ad[field]\n    yt_coords = ad[field[0], \"position\"]\n\n    # load some data the old fashioned way\n    fh = h5py.File(ds.parameter_filename, mode=\"r\")\n    part_data = fh[\"PartType0\"]\n\n    # set up a conversion factor by loading the unit mas and unit length in cm,\n    # and then converting to proper coordinates\n    units = fh[\"Units\"]\n    units = dict(units.attrs)\n    density_factor = float(units[\"Unit mass in cgs (U_M)\"])\n    density_factor /= float(units[\"Unit length in cgs (U_L)\"]) ** 3\n\n    # now load the raw density and coordinates\n    raw_density = part_data[\"Density\"][:].astype(\"float64\") * density_factor\n    raw_coords = part_data[\"Coordinates\"][:].astype(\"float64\")\n    fh.close()\n\n    # sort by the positions - yt often loads in a different order\n    ind_raw = np.lexsort((raw_coords[:, 2], raw_coords[:, 1], raw_coords[:, 0]))\n    ind_yt = np.lexsort((yt_coords[:, 2], yt_coords[:, 1], yt_coords[:, 0]))\n    raw_density = raw_density[ind_raw]\n    yt_density = yt_density[ind_yt]\n\n    # make sure we are comparing fair units\n    assert str(yt_density.units) == \"g/cm**3\"\n\n    # make sure the actual values are the same\n    assert_almost_equal(yt_density.d, raw_density)\n\n\n@requires_module(\"h5py\")\n@requires_file(keplerian_ring)\ndef test_non_cosmo_dataset_selection():\n    ds = load(keplerian_ring)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\n@requires_module(\"h5py\")\n@requires_file(EAGLE_6)\ndef test_cosmo_dataset():\n    ds = load(EAGLE_6)\n    assert type(ds) is SwiftDataset\n\n    field = (\"gas\", \"density\")\n    ad = ds.all_data()\n    yt_density = ad[field]\n    yt_coords = ad[field[0], \"position\"]\n\n    # load some data the old fashioned way\n    fh = h5py.File(ds.parameter_filename, mode=\"r\")\n    part_data = fh[\"PartType0\"]\n\n    # set up a conversion factor by loading the unit mas and unit length in cm,\n    # and then converting to proper coordinates\n    units = fh[\"Units\"]\n    units = dict(units.attrs)\n    density_factor = float(units[\"Unit mass in cgs (U_M)\"])\n    density_factor /= float(units[\"Unit length in cgs (U_L)\"]) ** 3\n\n    # add the redshift factor\n    header = fh[\"Header\"]\n    header = dict(header.attrs)\n    density_factor *= (1.0 + float(header[\"Redshift\"])) ** 3\n\n    # now load the raw density and coordinates\n    raw_density = part_data[\"Density\"][:].astype(\"float64\") * density_factor\n    raw_coords = part_data[\"Coordinates\"][:].astype(\"float64\")\n    fh.close()\n\n    # sort by the positions - yt often loads in a different order\n    ind_raw = np.lexsort((raw_coords[:, 2], raw_coords[:, 1], raw_coords[:, 0]))\n    ind_yt = np.lexsort((yt_coords[:, 2], yt_coords[:, 1], yt_coords[:, 0]))\n    raw_density = raw_density[ind_raw]\n    yt_density = yt_density[ind_yt]\n\n    # make sure we are comparing fair units\n    assert str(yt_density.units) == \"g/cm**3\"\n\n    # make sure the actual values are the same\n    assert_almost_equal(yt_density.d, raw_density)\n\n\n@requires_module(\"h5py\")\n@requires_file(EAGLE_6)\ndef test_cosmo_dataset_selection():\n    ds = load(EAGLE_6)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n"
  },
  {
    "path": "yt/frontends/tipsy/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/tipsy/api.py",
    "content": "from . import tests\nfrom .data_structures import TipsyDataset\nfrom .fields import TipsyFieldInfo\nfrom .io import IOHandlerTipsyBinary\n"
  },
  {
    "path": "yt/frontends/tipsy/data_structures.py",
    "content": "import glob\nimport os\nimport struct\n\nimport numpy as np\n\nfrom yt.data_objects.static_output import ParticleFile\nfrom yt.frontends.sph.data_structures import SPHDataset, SPHParticleIndex\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.physical_constants import G\nfrom yt.utilities.physical_ratios import cm_per_kpc\n\nfrom .fields import TipsyFieldInfo\n\n\nclass TipsyFile(ParticleFile):\n    def __init__(self, ds, io, filename, file_id, range=None):\n        super().__init__(ds, io, filename, file_id, range)\n        if not hasattr(io, \"_field_list\"):\n            io._create_dtypes(self)\n            # Check automatically what the domain size is\n            io._update_domain(self)\n        self._calculate_offsets(io._field_list)\n\n    def _calculate_offsets(self, field_list, pcounts=None):\n        self.field_offsets = self.io._calculate_particle_offsets(self, None)\n\n\nclass TipsyDataset(SPHDataset):\n    _index_class = SPHParticleIndex\n    _file_class = TipsyFile\n    _field_info_class = TipsyFieldInfo\n    _particle_mass_name = \"Mass\"\n    _particle_coordinates_name = \"Coordinates\"\n    _sph_ptypes = (\"Gas\",)\n    _header_spec = (\n        (\"time\", \"d\"),\n        (\"nbodies\", \"i\"),\n        (\"ndim\", \"i\"),\n        (\"nsph\", \"i\"),\n        (\"ndark\", \"i\"),\n        (\"nstar\", \"i\"),\n        (\"dummy\", \"i\"),\n    )\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"tipsy\",\n        field_dtypes=None,\n        unit_base=None,\n        parameter_file=None,\n        cosmology_parameters=None,\n        index_order=None,\n        index_filename=None,\n        kdtree_filename=None,\n        kernel_name=None,\n        bounding_box=None,\n        units_override=None,\n        unit_system=\"cgs\",\n        default_species_fields=None,\n    ):\n        # Because Tipsy outputs don't have a fixed domain boundary, one can\n        # specify a bounding box which effectively gives a domain_left_edge\n        # and domain_right_edge\n        self.bounding_box = bounding_box\n        self.filter_bbox = bounding_box is not None\n        if field_dtypes is None:\n            field_dtypes = {}\n        success, self.endian = self._validate_header(filename)\n        if not success:\n            print(\"SOMETHING HAS GONE WRONG.  NBODIES != SUM PARTICLES.\")\n            print(\n                \"{} != (sum == {} + {} + {})\".format(\n                    self.parameters[\"nbodies\"],\n                    self.parameters[\"nsph\"],\n                    self.parameters[\"ndark\"],\n                    self.parameters[\"nstar\"],\n                )\n            )\n            print(\"Often this can be fixed by changing the 'endian' parameter.\")\n            print(\"This defaults to '>' but may in fact be '<'.\")\n            raise RuntimeError\n        self.storage_filename = None\n\n        # My understanding is that dtypes are set on a field by field basis,\n        # not on a (particle type, field) basis\n        self._field_dtypes = field_dtypes\n\n        self._unit_base = unit_base or {}\n\n        self._cosmology_parameters = cosmology_parameters\n        if parameter_file is not None:\n            parameter_file = os.path.abspath(parameter_file)\n        self._param_file = parameter_file\n        filename = os.path.abspath(filename)\n        if units_override is not None:\n            raise RuntimeError(\n                \"units_override is not supported for TipsyDataset. \"\n                + \"Use unit_base instead.\"\n            )\n        super().__init__(\n            filename,\n            dataset_type=dataset_type,\n            unit_system=unit_system,\n            index_order=index_order,\n            index_filename=index_filename,\n            kdtree_filename=kdtree_filename,\n            kernel_name=kernel_name,\n            default_species_fields=default_species_fields,\n        )\n\n    def __str__(self):\n        return os.path.basename(self.parameter_filename)\n\n    def _parse_parameter_file(self):\n        # Parsing the header of the tipsy file, from this we obtain\n        # the snapshot time and particle counts.\n\n        f = open(self.parameter_filename, \"rb\")\n        hh = self.endian + \"\".join(str(b) for a, b in self._header_spec)\n        hvals = {\n            a: c\n            for (a, b), c in zip(\n                self._header_spec,\n                struct.unpack(hh, f.read(struct.calcsize(hh))),\n                strict=True,\n            )\n        }\n        self.parameters.update(hvals)\n        self._header_offset = f.tell()\n\n        # These are always true, for now.\n        self.dimensionality = 3\n        self.refine_by = 2\n        self.parameters[\"HydroMethod\"] = \"sph\"\n\n        # Read in parameter file, if available.\n        if self._param_file is None:\n            pfn = glob.glob(os.path.join(self.directory, \"*.param\"))\n            assert len(pfn) < 2, \"More than one param file is in the data directory\"\n            if pfn == []:\n                pfn = None\n            else:\n                pfn = pfn[0]\n        else:\n            pfn = self._param_file\n\n        if pfn is not None:\n            for line in (l.strip() for l in open(pfn)):\n                # skip comment lines and blank lines\n                l = line.strip()\n                if l.startswith(\"#\") or l == \"\":\n                    continue\n                # parse parameters according to tipsy parameter type\n                param, val = (i.strip() for i in line.split(\"=\", 1))\n                val = val.split(\"#\")[0]\n                if param.startswith(\"n\") or param.startswith(\"i\"):\n                    val = int(val)\n                elif param.startswith(\"d\"):\n                    val = float(val)\n                elif param.startswith(\"b\"):\n                    val = bool(float(val))\n                self.parameters[param] = val\n\n        self.current_time = hvals[\"time\"]\n        self.domain_dimensions = np.ones(3, \"int32\")\n        periodic = self.parameters.get(\"bPeriodic\", True)\n        period = self.parameters.get(\"dPeriod\", None)\n        self._periodicity = (periodic, periodic, periodic)\n        self.cosmological_simulation = float(\n            self.parameters.get(\"bComove\", self._cosmology_parameters is not None)\n        )\n        if self.cosmological_simulation and period is None:\n            period = 1.0\n        if self.bounding_box is None:\n            if periodic and period is not None:\n                # If we are periodic, that sets our domain width to\n                # either 1 or dPeriod.\n                self.domain_left_edge = np.zeros(3, \"float64\") - 0.5 * period\n                self.domain_right_edge = np.zeros(3, \"float64\") + 0.5 * period\n            else:\n                self.domain_left_edge = None\n                self.domain_right_edge = None\n        else:\n            # This ensures that we know a bounding box has been applied\n            self._domain_override = True\n            bbox = np.array(self.bounding_box, dtype=\"float64\")\n            if bbox.shape == (2, 3):\n                bbox = bbox.transpose()\n            self.domain_left_edge = bbox[:, 0]\n            self.domain_right_edge = bbox[:, 1]\n\n        # If the cosmology parameters dictionary got set when data is\n        # loaded, we can assume it's a cosmological data set\n        if self.cosmological_simulation == 1.0:\n            cosm = self._cosmology_parameters or {}\n            # In comoving simulations, time stores the scale factor a\n            self.scale_factor = hvals[\"time\"]\n            dcosm = {\n                \"current_redshift\": (1.0 / self.scale_factor) - 1.0,\n                \"omega_lambda\": self.parameters.get(\n                    \"dLambda\", cosm.get(\"omega_lambda\", 0.0)\n                ),\n                \"omega_matter\": self.parameters.get(\n                    \"dOmega0\", cosm.get(\"omega_matter\", 0.0)\n                ),\n                \"hubble_constant\": self.parameters.get(\n                    \"dHubble0\", cosm.get(\"hubble_constant\", 1.0)\n                ),\n            }\n            for param in dcosm.keys():\n                pval = dcosm[param]\n                setattr(self, param, pval)\n        else:\n            kpc_unit = self.parameters.get(\"dKpcUnit\", 1.0)\n            self._unit_base[\"cm\"] = 1.0 / (kpc_unit * cm_per_kpc)\n\n        self.filename_template = self.parameter_filename\n        self.file_count = 1\n\n        f.close()\n\n    def _set_derived_attrs(self):\n        if self.bounding_box is None and (\n            self.domain_left_edge is None or self.domain_right_edge is None\n        ):\n            self.domain_left_edge = np.array([np.nan, np.nan, np.nan])\n            self.domain_right_edge = np.array([np.nan, np.nan, np.nan])\n            self.index\n        super()._set_derived_attrs()\n\n    def _set_code_unit_attributes(self):\n        # First try to set units based on parameter file\n        if self.cosmological_simulation:\n            mu = self.parameters.get(\"dMsolUnit\", 1.0)\n            self.mass_unit = self.quan(mu, \"Msun\")\n            lu = self.parameters.get(\"dKpcUnit\", 1000.0)\n            # In cosmological runs, lengths are stored as length*scale_factor\n            self.length_unit = self.quan(lu, \"kpc\") * self.scale_factor\n            density_unit = self.mass_unit / (self.length_unit / self.scale_factor) ** 3\n            if \"dHubble0\" in self.parameters:\n                # Gasoline's internal hubble constant, dHubble0, is stored in\n                # units of proper code time\n                self.hubble_constant *= np.sqrt(G * density_unit)\n                # Finally, we scale the hubble constant by 100 km/s/Mpc\n                self.hubble_constant /= self.quan(100, \"km/s/Mpc\")\n                # If we leave it as a YTQuantity, the cosmology object\n                # used below will add units back on.\n                self.hubble_constant = self.hubble_constant.to_value(\"\")\n        else:\n            mu = self.parameters.get(\"dMsolUnit\", 1.0)\n            self.mass_unit = self.quan(mu, \"Msun\")\n            lu = self.parameters.get(\"dKpcUnit\", 1.0)\n            self.length_unit = self.quan(lu, \"kpc\")\n\n        # If unit base is defined by the user, override all relevant units\n        if self._unit_base is not None:\n            for my_unit in [\"length\", \"mass\", \"time\"]:\n                if my_unit in self._unit_base:\n                    my_val = self._unit_base[my_unit]\n                    my_val = (\n                        self.quan(*my_val)\n                        if isinstance(my_val, tuple)\n                        else self.quan(my_val)\n                    )\n                    setattr(self, f\"{my_unit}_unit\", my_val)\n\n        # Finally, set the dependent units\n        if self.cosmological_simulation:\n            cosmo = Cosmology(\n                hubble_constant=self.hubble_constant,\n                omega_matter=self.omega_matter,\n                omega_lambda=self.omega_lambda,\n            )\n            self.current_time = cosmo.lookback_time(self.current_redshift, 1e6)\n            # mass units are rho_crit(z=0) * domain volume\n            mu = (\n                cosmo.critical_density(0.0)\n                * (1 + self.current_redshift) ** 3\n                * self.length_unit**3\n            )\n            self.mass_unit = self.quan(mu.in_units(\"Msun\"), \"Msun\")\n            density_unit = self.mass_unit / (self.length_unit / self.scale_factor) ** 3\n            # need to do this again because we've modified the hubble constant\n            self.unit_registry.modify(\"h\", self.hubble_constant)\n        else:\n            density_unit = self.mass_unit / self.length_unit**3\n\n        if not hasattr(self, \"time_unit\"):\n            self.time_unit = 1.0 / np.sqrt(density_unit * G)\n\n    @staticmethod\n    def _validate_header(filename):\n        \"\"\"\n        This method automatically detects whether the tipsy file is big/little endian\n        and is not corrupt/invalid.  It returns a tuple of (Valid, endianswap) where\n        Valid is a boolean that is true if the file is a tipsy file, and endianswap is\n        the endianness character '>' or '<'.\n        \"\"\"\n        try:\n            f = open(filename, \"rb\")\n        except Exception:\n            return False, 1\n        try:\n            f.seek(0, os.SEEK_END)\n            fs = f.tell()\n            f.seek(0, os.SEEK_SET)\n            # Read in the header\n            t, n, ndim, ng, nd, ns = struct.unpack(\"<diiiii\", f.read(28))\n        except (OSError, struct.error):\n            return False, 1\n        endianswap = \"<\"\n        # Check Endianness\n        if ndim < 1 or ndim > 3:\n            endianswap = \">\"\n            f.seek(0)\n            t, n, ndim, ng, nd, ns = struct.unpack(\">diiiii\", f.read(28))\n        # File is borked if this is true.  The header is 28 bytes, and may\n        # Be followed by a 4 byte pad.  Next comes gas particles, which use\n        # 48 bytes, followed by 36 bytes per dark matter particle, and 44 bytes\n        # per star particle.  If positions are stored as doubles, each of these\n        # sizes is increased by 12 bytes.\n        if (\n            fs != 28 + 48 * ng + 36 * nd + 44 * ns\n            and fs != 28 + 60 * ng + 48 * nd + 56 * ns\n            and fs != 32 + 48 * ng + 36 * nd + 44 * ns\n            and fs != 32 + 60 * ng + 48 * nd + 56 * ns\n        ):\n            f.close()\n            return False, 0\n        f.close()\n        return True, endianswap\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        return TipsyDataset._validate_header(filename)[0]\n"
  },
  {
    "path": "yt/frontends/tipsy/definitions.py",
    "content": "npart_mapping = {\"Gas\": \"nsph\", \"DarkMatter\": \"ndark\", \"Stars\": \"nstar\"}\n"
  },
  {
    "path": "yt/frontends/tipsy/fields.py",
    "content": "from yt.frontends.sph.fields import SPHFieldInfo\n\n\nclass TipsyFieldInfo(SPHFieldInfo):\n    known_particle_fields = SPHFieldInfo.known_particle_fields + (\n        (\"smoothing_length\", (\"code_length\", [], None)),\n    )\n    aux_particle_fields = {\n        \"uDotFB\": (\"uDotFB\", (\"code_mass * code_velocity**2\", [\"\"], None)),\n        \"uDotAV\": (\"uDotAV\", (\"code_mass * code_velocity**2\", [\"\"], None)),\n        \"uDotPdV\": (\"uDotPdV\", (\"code_mass * code_velocity**2\", [\"\"], None)),\n        \"uDotHydro\": (\"uDotHydro\", (\"code_mass * code_velocity**2\", [\"\"], None)),\n        \"uDotDiff\": (\"uDotDiff\", (\"code_mass * code_velocity**2\", [\"\"], None)),\n        \"uDot\": (\"uDot\", (\"code_mass * code_velocity**2\", [\"\"], None)),\n        \"coolontime\": (\"coolontime\", (\"code_time\", [\"\"], None)),\n        \"timeform\": (\"timeform\", (\"code_time\", [\"\"], None)),\n        \"massform\": (\"massform\", (\"code_mass\", [\"\"], None)),\n        \"HI\": (\"HI\", (\"dimensionless\", [\"H_fraction\"], None)),\n        \"HII\": (\"HII\", (\"dimensionless\", [\"H_p1_fraction\"], None)),\n        \"HeI\": (\"HeI\", (\"dimensionless\", [\"He_fraction\"], None)),\n        \"HeII\": (\"HeII\", (\"dimensionless\", [\"He_p2_fraction\"], None)),\n        \"OxMassFrac\": (\"OxMassFrac\", (\"dimensionless\", [\"O_fraction\"], None)),\n        \"FeMassFrac\": (\"FeMassFrac\", (\"dimensionless\", [\"Fe_fraction\"], None)),\n        \"c\": (\"c\", (\"code_velocity\", [\"\"], None)),\n        \"acc\": (\"acc\", (\"code_velocity / code_time\", [\"\"], None)),\n        \"accg\": (\"accg\", (\"code_velocity / code_time\", [\"\"], None)),\n        \"smoothlength\": (\"smoothlength\", (\"code_length\", [\"smoothing_length\"], None)),\n    }\n\n    def __init__(self, ds, field_list, slice_info=None):\n        for field in field_list:\n            if (\n                field[1] in self.aux_particle_fields.keys()\n                and self.aux_particle_fields[field[1]] not in self.known_particle_fields\n            ):\n                self.known_particle_fields += (self.aux_particle_fields[field[1]],)\n        super().__init__(ds, field_list, slice_info)\n"
  },
  {
    "path": "yt/frontends/tipsy/io.py",
    "content": "import glob\nimport os\nimport struct\n\nimport numpy as np\n\nfrom yt.frontends.sph.io import IOHandlerSPH\nfrom yt.frontends.tipsy.definitions import npart_mapping\nfrom yt.utilities.lib.particle_kdtree_tools import generate_smoothing_length\nfrom yt.utilities.logger import ytLogger as mylog\n\n\nclass IOHandlerTipsyBinary(IOHandlerSPH):\n    _dataset_type = \"tipsy\"\n    _vector_fields = {\"Coordinates\": 3, \"Velocity\": 3, \"Velocities\": 3}\n\n    _pdtypes = None  # dtypes, to be filled in later\n    _aux_pdtypes = None  # auxiliary files' dtypes\n\n    _ptypes = (\"Gas\", \"DarkMatter\", \"Stars\")\n\n    _aux_fields = None\n    _fields = (\n        (\"Gas\", \"Mass\"),\n        (\"Gas\", \"Coordinates\"),\n        (\"Gas\", \"Velocities\"),\n        (\"Gas\", \"Density\"),\n        (\"Gas\", \"Temperature\"),\n        (\"Gas\", \"Epsilon\"),\n        (\"Gas\", \"Metals\"),\n        (\"Gas\", \"Phi\"),\n        (\"DarkMatter\", \"Mass\"),\n        (\"DarkMatter\", \"Coordinates\"),\n        (\"DarkMatter\", \"Velocities\"),\n        (\"DarkMatter\", \"Epsilon\"),\n        (\"DarkMatter\", \"Phi\"),\n        (\"Stars\", \"Mass\"),\n        (\"Stars\", \"Coordinates\"),\n        (\"Stars\", \"Velocities\"),\n        (\"Stars\", \"Metals\"),\n        (\"Stars\", \"FormationTime\"),\n        (\"Stars\", \"Epsilon\"),\n        (\"Stars\", \"Phi\"),\n    )\n\n    def __init__(self, *args, **kwargs):\n        self._aux_fields = []\n        super().__init__(*args, **kwargs)\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _fill_fields(self, fields, vals, hsml, mask, data_file):\n        if mask is None:\n            size = 0\n        elif isinstance(mask, slice):\n            if fields[0] == \"smoothing_length\":\n                size = hsml.size\n            else:\n                size = vals[fields[0]].size\n        else:\n            size = mask.sum()\n        rv = {}\n        for field in fields:\n            mylog.debug(\"Allocating %s values for %s\", size, field)\n            if field in self._vector_fields:\n                rv[field] = np.empty((size, 3), dtype=\"float64\")\n                if size == 0:\n                    continue\n                rv[field][:, 0] = vals[field][\"x\"][mask]\n                rv[field][:, 1] = vals[field][\"y\"][mask]\n                rv[field][:, 2] = vals[field][\"z\"][mask]\n            elif field == \"smoothing_length\":\n                rv[field] = hsml[mask]\n            else:\n                rv[field] = np.empty(size, dtype=\"float64\")\n                if size == 0:\n                    continue\n                rv[field][:] = vals[field][mask]\n            if field == \"Coordinates\":\n                eps = np.finfo(rv[field].dtype).eps\n                for i in range(3):\n                    rv[field][:, i] = np.clip(\n                        rv[field][:, i],\n                        self.ds.domain_left_edge[i].v + eps,\n                        self.ds.domain_right_edge[i].v - eps,\n                    )\n        return rv\n\n    def _read_particle_coords(self, chunks, ptf):\n        chunksize = self.ds.index.chunksize\n        for data_file in self._sorted_chunk_iterator(chunks):\n            poff = data_file.field_offsets\n            tp = data_file.total_particles\n            f = open(data_file.filename, \"rb\")\n            for ptype in sorted(ptf, key=lambda a, poff=poff: poff.get(a, -1)):\n                if data_file.total_particles[ptype] == 0:\n                    continue\n                f.seek(poff[ptype])\n                total = 0\n                while total < tp[ptype]:\n                    count = min(chunksize, tp[ptype] - total)\n                    p = np.fromfile(f, self._pdtypes[ptype], count=count)\n                    total += p.size\n                    d = [p[\"Coordinates\"][ax].astype(\"float64\") for ax in \"xyz\"]\n                    del p\n                    if ptype == self.ds._sph_ptypes[0]:\n                        hsml = self._read_smoothing_length(data_file, count)\n                    else:\n                        hsml = 0.0\n                    yield ptype, d, hsml\n\n    @property\n    def hsml_filename(self):\n        return f\"{self.ds.parameter_filename}-{'hsml'}\"\n\n    def _generate_smoothing_length(self, index):\n        if os.path.exists(self.hsml_filename):\n            with open(self.hsml_filename, \"rb\") as f:\n                file_hash = struct.unpack(\"q\", f.read(struct.calcsize(\"q\")))[0]\n            if file_hash != self.ds._file_hash:\n                os.remove(self.hsml_filename)\n            else:\n                return\n        positions = []\n        for data_file in index.data_files:\n            for _, ppos in self._yield_coordinates(\n                data_file, needed_ptype=self.ds._sph_ptypes[0]\n            ):\n                positions.append(ppos)\n        if positions == []:\n            return\n        kdtree = index.kdtree\n        positions = np.concatenate(positions)[kdtree.idx]\n        hsml = generate_smoothing_length(positions, kdtree, self.ds._num_neighbors)\n        hsml = hsml[np.argsort(kdtree.idx)]\n        dtype = self._pdtypes[\"Gas\"][\"Coordinates\"][0]\n        with open(self.hsml_filename, \"wb\") as f:\n            f.write(struct.pack(\"q\", self.ds._file_hash))\n            f.write(hsml.astype(dtype).tobytes())\n\n    def _read_smoothing_length(self, data_file, count):\n        dtype = self._pdtypes[\"Gas\"][\"Coordinates\"][0]\n        with open(self.hsml_filename, \"rb\") as f:\n            f.seek(struct.calcsize(\"q\") + data_file.start * dtype.itemsize)\n            hsmls = np.fromfile(f, dtype, count=count)\n        return hsmls.astype(\"float64\")\n\n    def _get_smoothing_length(self, data_file, dtype, shape):\n        return self._read_smoothing_length(data_file, shape[0])\n\n    def _read_particle_data_file(self, data_file, ptf, selector=None):\n        from numpy.lib.recfunctions import append_fields\n\n        return_data = {}\n\n        poff = data_file.field_offsets\n        aux_fields_offsets = self._calculate_particle_offsets_aux(data_file)\n        tp = data_file.total_particles\n        f = open(data_file.filename, \"rb\")\n\n        # we need to open all aux files for chunking to work\n        _aux_fh = {}\n\n        def aux_fh(afield):\n            if afield not in _aux_fh:\n                _aux_fh[afield] = open(data_file.filename + \".\" + afield, \"rb\")\n            return _aux_fh[afield]\n\n        for ptype, field_list in sorted(ptf.items(), key=lambda a: poff.get(a[0], -1)):\n            if data_file.total_particles[ptype] == 0:\n                continue\n            f.seek(poff[ptype])\n            afields = list(set(field_list).intersection(self._aux_fields))\n            count = min(self.ds.index.chunksize, tp[ptype])\n            p = np.fromfile(f, self._pdtypes[ptype], count=count)\n            auxdata = []\n            for afield in afields:\n                aux_fh(afield).seek(aux_fields_offsets[afield][ptype])\n                if isinstance(self._aux_pdtypes[afield], np.dtype):\n                    auxdata.append(\n                        np.fromfile(\n                            aux_fh(afield), self._aux_pdtypes[afield], count=count\n                        )\n                    )\n                else:\n                    aux_fh(afield).seek(0)\n                    sh = aux_fields_offsets[afield][ptype]\n                    if tp[ptype] > 0:\n                        aux = np.genfromtxt(\n                            aux_fh(afield), skip_header=sh, max_rows=count\n                        )\n                        if aux.ndim < 1:\n                            aux = np.array([aux])\n                        auxdata.append(aux)\n            if afields:\n                p = append_fields(p, afields, auxdata)\n            if ptype == \"Gas\":\n                hsml = self._read_smoothing_length(data_file, count)\n            else:\n                hsml = 0.0\n            if selector is None or getattr(selector, \"is_all_data\", False):\n                mask = slice(None, None, None)\n            else:\n                x = p[\"Coordinates\"][\"x\"].astype(\"float64\")\n                y = p[\"Coordinates\"][\"y\"].astype(\"float64\")\n                z = p[\"Coordinates\"][\"z\"].astype(\"float64\")\n                mask = selector.select_points(x, y, z, hsml)\n                del x, y, z\n            if mask is None:\n                continue\n            tf = self._fill_fields(field_list, p, hsml, mask, data_file)\n            for field in field_list:\n                return_data[ptype, field] = tf.pop(field)\n\n        # close all file handles\n        f.close()\n        for fh in _aux_fh.values():\n            fh.close()\n\n        return return_data\n\n    def _update_domain(self, data_file):\n        \"\"\"\n        This method is used to determine the size needed for a box that will\n        bound the particles.  It simply finds the largest position of the\n        whole set of particles, and sets the domain to +/- that value.\n        \"\"\"\n        ds = data_file.ds\n        ind = 0\n        # NOTE:\n        #  We hardcode this value here because otherwise we get into a\n        #  situation where we require the existence of index before we\n        #  can successfully instantiate it, or where we are calling it\n        #  from within its instantiation.\n        #\n        #  Because this value is not propagated later on, and does not\n        #  impact the construction of the bitmap indices, it should be\n        #  acceptable to just use a reasonable number here.\n        chunksize = 64**3\n        # Check to make sure that the domain hasn't already been set\n        # by the parameter file\n        if np.all(np.isfinite(ds.domain_left_edge)) and np.all(\n            np.isfinite(ds.domain_right_edge)\n        ):\n            return\n        with open(data_file.filename, \"rb\") as f:\n            ds.domain_left_edge = 0\n            ds.domain_right_edge = 0\n            f.seek(ds._header_offset)\n            mi = np.array([1e30, 1e30, 1e30], dtype=\"float64\")\n            ma = -np.array([1e30, 1e30, 1e30], dtype=\"float64\")\n            for ptype in self._ptypes:\n                # We'll just add the individual types separately\n                count = data_file.total_particles[ptype]\n                if count == 0:\n                    continue\n                stop = ind + count\n                while ind < stop:\n                    c = min(chunksize, stop - ind)\n                    pp = np.fromfile(f, dtype=self._pdtypes[ptype], count=c)\n                    np.minimum(\n                        mi,\n                        [\n                            pp[\"Coordinates\"][\"x\"].min(),\n                            pp[\"Coordinates\"][\"y\"].min(),\n                            pp[\"Coordinates\"][\"z\"].min(),\n                        ],\n                        out=mi,\n                    )\n                    np.maximum(\n                        ma,\n                        [\n                            pp[\"Coordinates\"][\"x\"].max(),\n                            pp[\"Coordinates\"][\"y\"].max(),\n                            pp[\"Coordinates\"][\"z\"].max(),\n                        ],\n                        out=ma,\n                    )\n                    ind += c\n        # We extend by 1%.\n        DW = ma - mi\n        mi -= 0.01 * DW\n        ma += 0.01 * DW\n        ds.domain_left_edge = ds.arr(mi, \"code_length\")\n        ds.domain_right_edge = ds.arr(ma, \"code_length\")\n        ds.domain_width = DW = ds.domain_right_edge - ds.domain_left_edge\n        ds.unit_registry.add(\n            \"unitary\", float(DW.max() * DW.units.base_value), DW.units.dimensions\n        )\n\n    def _yield_coordinates(self, data_file, needed_ptype=None):\n        with open(data_file.filename, \"rb\") as f:\n            poff = data_file.field_offsets\n            for ptype in self._ptypes:\n                if ptype not in poff:\n                    continue\n                f.seek(poff[ptype])\n                if needed_ptype is not None and ptype != needed_ptype:\n                    continue\n                # We'll just add the individual types separately\n                count = data_file.total_particles[ptype]\n                if count == 0:\n                    continue\n                pp = np.fromfile(f, dtype=self._pdtypes[ptype], count=count)\n                mis = np.empty(3, dtype=\"float64\")\n                mas = np.empty(3, dtype=\"float64\")\n                for axi, ax in enumerate(\"xyz\"):\n                    mi = pp[\"Coordinates\"][ax].min()\n                    ma = pp[\"Coordinates\"][ax].max()\n                    mylog.debug(\"Spanning: %0.3e .. %0.3e in %s\", mi, ma, ax)\n                    mis[axi] = mi\n                    mas[axi] = ma\n                pos = np.empty((pp.size, 3), dtype=\"float64\")\n                for i, ax in enumerate(\"xyz\"):\n                    pos[:, i] = pp[\"Coordinates\"][ax]\n                yield ptype, pos\n\n    def _count_particles(self, data_file):\n        pcount = np.array(\n            [\n                data_file.ds.parameters[\"nsph\"],\n                data_file.ds.parameters[\"nstar\"],\n                data_file.ds.parameters[\"ndark\"],\n            ]\n        )\n        si, ei = data_file.start, data_file.end\n        if None not in (si, ei):\n            np.clip(pcount - si, 0, ei - si, out=pcount)\n        ptypes = [\"Gas\", \"Stars\", \"DarkMatter\"]\n        npart = dict(zip(ptypes, pcount, strict=True))\n        return npart\n\n    @classmethod\n    def _compute_dtypes(cls, field_dtypes, endian=\"<\"):\n        pds = {}\n        for ptype, field in cls._fields:\n            dtbase = field_dtypes.get(field, \"f\")\n            ff = f\"{endian}{dtbase}\"\n            if field in cls._vector_fields:\n                dt = (field, [(\"x\", ff), (\"y\", ff), (\"z\", ff)])\n            else:\n                dt = (field, ff)\n            pds.setdefault(ptype, []).append(dt)\n        pdtypes = {}\n        for ptype in pds:\n            pdtypes[ptype] = np.dtype(pds[ptype])\n        return pdtypes\n\n    def _create_dtypes(self, data_file):\n        # We can just look at the particle counts.\n        self._header_offset = data_file.ds._header_offset\n        self._pdtypes = self._compute_dtypes(\n            data_file.ds._field_dtypes, data_file.ds.endian\n        )\n        self._field_list = []\n        for ptype, field in self._fields:\n            if data_file.total_particles[ptype] == 0:\n                # We do not want out _pdtypes to have empty particles.\n                self._pdtypes.pop(ptype, None)\n                continue\n            self._field_list.append((ptype, field))\n\n        if \"Gas\" in self._pdtypes.keys():\n            self._field_list.append((\"Gas\", \"smoothing_length\"))\n\n        # Find out which auxiliaries we have and what is their format\n        tot_parts = np.sum(\n            [\n                data_file.ds.parameters[\"nsph\"],\n                data_file.ds.parameters[\"nstar\"],\n                data_file.ds.parameters[\"ndark\"],\n            ]\n        )\n        endian = data_file.ds.endian\n        self._aux_pdtypes = {}\n        self._aux_fields = []\n        for f in glob.glob(data_file.filename + \".*\"):\n            afield = f.rsplit(\".\")[-1]\n            filename = data_file.filename + \".\" + afield\n            if not os.path.exists(filename):\n                continue\n            if afield in [\"log\", \"parameter\", \"kdtree\"]:\n                # Amiga halo finder makes files like this we need to ignore\n                continue\n            self._aux_fields.append(afield)\n        skip_afields = []\n        for afield in self._aux_fields:\n            filename = data_file.filename + \".\" + afield\n            # We need to do some fairly ugly detection to see what format the\n            # auxiliary files are in.  They can be either ascii or binary, and\n            # the binary files can be either floats, ints, or doubles.  We're\n            # going to use a try-catch cascade to determine the format.\n            filesize = os.stat(filename).st_size\n            dtype = np.dtype(endian + \"i4\")\n            tot_parts_from_file = np.fromfile(filename, dtype, count=1)\n            if tot_parts_from_file != tot_parts:\n                with open(filename, \"rb\") as f:\n                    header_nparts = f.readline()\n                    try:\n                        header_nparts = int(header_nparts)\n                    except ValueError:\n                        skip_afields.append(afield)\n                        continue\n                    if int(header_nparts) != tot_parts:\n                        raise RuntimeError\n                self._aux_pdtypes[afield] = \"ascii\"\n            elif (filesize - 4) / 8 == tot_parts:\n                self._aux_pdtypes[afield] = np.dtype([(\"aux\", endian + \"d\")])\n            elif (filesize - 4) / 4 == tot_parts:\n                if afield.startswith(\"i\"):\n                    self._aux_pdtypes[afield] = np.dtype([(\"aux\", endian + \"i\")])\n                else:\n                    self._aux_pdtypes[afield] = np.dtype([(\"aux\", endian + \"f\")])\n            else:\n                skip_afields.append(afield)\n        for afield in skip_afields:\n            self._aux_fields.remove(afield)\n        # Add the auxiliary fields to each ptype we have\n        for ptype in self._ptypes:\n            if any(ptype == field[0] for field in self._field_list):\n                self._field_list += [(ptype, afield) for afield in self._aux_fields]\n        return self._field_list\n\n    def _identify_fields(self, data_file):\n        return self._field_list, {}\n\n    def _calculate_particle_offsets(self, data_file, pcounts):\n        # This computes the offsets for each particle type into a \"data_file.\"\n        # Note that the term \"data_file\" here is a bit overloaded, and also refers to a\n        # \"chunk\" of particles inside a data file.\n        # data_file.start represents the *particle count* that we should start at.\n        #\n        # At this point, data_file will have the total number of particles\n        # that this chunk represents located in the property total_particles.\n        # Because in tipsy files the particles are stored sequentially, we can\n        # figure out where each one starts.\n        # We first figure out the global offsets, then offset them by the count\n        # and size of each individual particle type.\n        field_offsets = {}\n        # Initialize pos to the point the first particle type would start\n        pos = data_file.ds._header_offset\n        global_offsets = {}\n        field_offsets = {}\n        for ptype in self._ptypes:\n            if ptype not in self._pdtypes:\n                # This means we don't have any, I think, and so we shouldn't\n                # stick it in the offsets.\n                continue\n            # Note that much of this will be computed redundantly; future\n            # refactorings could fix this.\n            global_offsets[ptype] = pos\n            size = self._pdtypes[ptype].itemsize\n            npart = self.ds.parameters[npart_mapping[ptype]]\n            # Get the offset into just this particle type, and start at data_file.start\n            if npart > data_file.start:\n                field_offsets[ptype] = pos + size * data_file.start\n            pos += npart * size\n        return field_offsets\n\n    def _calculate_particle_offsets_aux(self, data_file):\n        aux_fields_offsets = {}\n        params = self.ds.parameters\n        for afield in self._aux_fields:\n            aux_fields_offsets[afield] = {}\n            if isinstance(self._aux_pdtypes[afield], np.dtype):\n                pos = 4  # i4\n                size = np.dtype(self._aux_pdtypes[afield]).itemsize\n            else:\n                pos = 1\n                size = 1\n            for i, ptype in enumerate(self._ptypes):\n                if data_file.total_particles[ptype] == 0:\n                    continue\n                elif params[npart_mapping[ptype]] > self.ds.index.chunksize:\n                    for j in range(i):\n                        npart = params[npart_mapping[self._ptypes[j]]]\n                        if npart > self.ds.index.chunksize:\n                            pos += npart * size\n                    pos += data_file.start * size\n                aux_fields_offsets[afield][ptype] = pos\n                pos += data_file.total_particles[ptype] * size\n        return aux_fields_offsets\n"
  },
  {
    "path": "yt/frontends/tipsy/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/tipsy/tests/test_outputs.py",
    "content": "from collections import OrderedDict\n\nfrom yt.frontends.tipsy.api import TipsyDataset\nfrom yt.testing import ParticleSelectionComparison, requires_file\nfrom yt.utilities.answer_testing.framework import (\n    data_dir_load,\n    nbody_answer,\n    requires_ds,\n    sph_answer,\n)\n\n_fields = OrderedDict(\n    [\n        ((\"all\", \"particle_mass\"), None),\n        ((\"all\", \"particle_ones\"), None),\n        ((\"all\", \"particle_velocity_x\"), (\"all\", \"particle_mass\")),\n        ((\"all\", \"particle_velocity_y\"), (\"all\", \"particle_mass\")),\n        ((\"all\", \"particle_velocity_z\"), (\"all\", \"particle_mass\")),\n    ]\n)\n\npkdgrav = \"halo1e11_run1.00400/halo1e11_run1.00400\"\npkdgrav_cosmology_parameters = {\n    \"current_redshift\": 0.0,\n    \"omega_lambda\": 0.728,\n    \"omega_matter\": 0.272,\n    \"hubble_constant\": 0.702,\n}\npkdgrav_kwargs = {\n    \"field_dtypes\": {\"Coordinates\": \"d\"},\n    \"cosmology_parameters\": pkdgrav_cosmology_parameters,\n    \"unit_base\": {\"length\": (60.0, \"Mpccm/h\")},\n}\n\n\n@requires_ds(pkdgrav, big_data=True, file_check=True)\ndef test_pkdgrav():\n    ds = data_dir_load(pkdgrav, TipsyDataset, (), kwargs=pkdgrav_kwargs)\n    yield from nbody_answer(ds, \"halo1e11_run1.00400\", 26847360, _fields)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\ngasoline_dmonly = \"agora_1e11.00400/agora_1e11.00400\"\n\n\n@requires_ds(gasoline_dmonly, big_data=True, file_check=True)\ndef test_gasoline_dmonly():\n    cosmology_parameters = {\n        \"current_redshift\": 0.0,\n        \"omega_lambda\": 0.728,\n        \"omega_matter\": 0.272,\n        \"hubble_constant\": 0.702,\n    }\n    kwargs = {\n        \"cosmology_parameters\": cosmology_parameters,\n        \"unit_base\": {\"length\": (60.0, \"Mpccm/h\")},\n    }\n    ds = data_dir_load(gasoline_dmonly, TipsyDataset, (), kwargs)\n    yield from nbody_answer(ds, \"agora_1e11.00400\", 10550576, _fields)\n    psc = ParticleSelectionComparison(ds)\n    psc.run_defaults()\n\n\ntg_sph_fields = OrderedDict(\n    [\n        ((\"gas\", \"density\"), None),\n        ((\"gas\", \"temperature\"), None),\n        ((\"gas\", \"temperature\"), (\"gas\", \"density\")),\n        ((\"gas\", \"velocity_magnitude\"), None),\n        ((\"gas\", \"Fe_fraction\"), None),\n    ]\n)\n\ntg_nbody_fields = OrderedDict(\n    [\n        ((\"Stars\", \"Metals\"), None),\n    ]\n)\n\ntipsy_gal = \"TipsyGalaxy/galaxy.00300\"\n\n\n@requires_ds(tipsy_gal)\ndef test_tipsy_galaxy():\n    ds = data_dir_load(\n        tipsy_gal,\n        kwargs={\"bounding_box\": [[-2000, 2000], [-2000, 2000], [-2000, 2000]]},\n    )\n    # These tests should be re-enabled.  But the holdup is that the region\n    # selector does not offset by domain_left_edge, and we have inelegant\n    # selection using bboxes.\n    # psc = ParticleSelectionComparison(ds)\n    # psc.run_defaults()\n    for test in sph_answer(ds, \"galaxy.00300\", 315372, tg_sph_fields):\n        test_tipsy_galaxy.__name__ = test.description\n        yield test\n    for test in nbody_answer(ds, \"galaxy.00300\", 315372, tg_nbody_fields):\n        test_tipsy_galaxy.__name__ = test.description\n        yield test\n\n\n@requires_file(gasoline_dmonly)\n@requires_file(pkdgrav)\ndef test_TipsyDataset():\n    assert isinstance(data_dir_load(pkdgrav, kwargs=pkdgrav_kwargs), TipsyDataset)\n    assert isinstance(data_dir_load(gasoline_dmonly), TipsyDataset)\n\n\n@requires_file(tipsy_gal)\ndef test_tipsy_index():\n    ds = data_dir_load(tipsy_gal)\n    sl = ds.slice(\"z\", 0.0)\n    assert sl[\"gas\", \"density\"].shape[0] != 0\n\n\n@requires_file(tipsy_gal)\ndef test_tipsy_smoothing_length():\n    ds = data_dir_load(tipsy_gal)\n    _ = ds.all_data()[\"Gas\", \"smoothing_length\"]\n"
  },
  {
    "path": "yt/frontends/ytdata/__init__.py",
    "content": "\"\"\"\nAPI for ytData frontend.\n\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/frontends/ytdata/api.py",
    "content": "from . import tests\nfrom .data_structures import (\n    YTClumpContainer,\n    YTClumpTreeDataset,\n    YTDataContainerDataset,\n    YTGrid,\n    YTGridDataset,\n    YTGridHierarchy,\n    YTNonspatialDataset,\n    YTNonspatialGrid,\n    YTNonspatialHierarchy,\n    YTProfileDataset,\n    YTSpatialPlotDataset,\n)\nfrom .fields import YTDataContainerFieldInfo, YTGridFieldInfo\nfrom .io import (\n    IOHandlerYTDataContainerHDF5,\n    IOHandlerYTGridHDF5,\n    IOHandlerYTNonspatialhdf5,\n    IOHandlerYTSpatialPlotHDF5,\n)\nfrom .utilities import save_as_dataset\n"
  },
  {
    "path": "yt/frontends/ytdata/data_structures.py",
    "content": "import os\nimport weakref\nfrom collections import defaultdict\nfrom functools import cached_property\nfrom numbers import Number as numeric_type\n\nimport numpy as np\n\nfrom yt.data_objects.index_subobjects.grid_patch import AMRGridPatch\nfrom yt.data_objects.profiles import (\n    Profile1DFromDataset,\n    Profile2DFromDataset,\n    Profile3DFromDataset,\n)\nfrom yt.data_objects.static_output import Dataset, ParticleFile\nfrom yt.data_objects.unions import ParticleUnion\nfrom yt.fields.field_exceptions import NeedsGridType\nfrom yt.fields.field_info_container import FieldInfoContainer\nfrom yt.funcs import is_root, parse_h5_attr\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.geometry_handler import Index\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.geometry.particle_geometry_handler import ParticleIndex\nfrom yt.units import dimensions\nfrom yt.units._numpy_wrapper_functions import uconcatenate\nfrom yt.units.unit_registry import UnitRegistry  # type: ignore\nfrom yt.units.yt_array import YTQuantity\nfrom yt.utilities.exceptions import GenerationInProgress, YTFieldTypeNotFound\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_root_only\nfrom yt.utilities.tree_container import TreeContainer\n\nfrom .fields import YTDataContainerFieldInfo, YTGridFieldInfo\n\n_grid_data_containers = [\"arbitrary_grid\", \"covering_grid\", \"smoothed_covering_grid\"]\n_set_attrs = {\"periodicity\": \"_periodicity\"}\n\n\nclass SavedDataset(Dataset):\n    \"\"\"\n    Base dataset class for products of calling save_as_dataset.\n    \"\"\"\n\n    geometry = Geometry.CARTESIAN\n    _con_attrs: tuple[str, ...] = ()\n\n    def _parse_parameter_file(self):\n        self.refine_by = 2\n        with h5py.File(self.parameter_filename, mode=\"r\") as f:\n            for key in f.attrs.keys():\n                v = parse_h5_attr(f, key)\n                if key == \"con_args\":\n                    try:\n                        v = eval(v)\n                    except ValueError:\n                        # support older ytdata outputs\n                        v = v.astype(\"str\")\n                    except NameError:\n                        # This is the most common error we expect, and it\n                        # results from having the eval do a concatenated decoded\n                        # set of the values.\n                        v = [_.decode(\"utf8\") for _ in v]\n                self.parameters[key] = v\n            self._with_parameter_file_open(f)\n\n        # if saved, restore unit registry from the json string\n        if \"unit_registry_json\" in self.parameters:\n            self.unit_registry = UnitRegistry.from_json(\n                self.parameters[\"unit_registry_json\"]\n            )\n            # reset self.arr and self.quan to use new unit_registry\n            self._arr = None\n            self._quan = None\n            for dim in [\n                \"length\",\n                \"mass\",\n                \"pressure\",\n                \"temperature\",\n                \"time\",\n                \"velocity\",\n            ]:\n                cu = \"code_\" + dim\n                if cu not in self.unit_registry:\n                    self.unit_registry.add(cu, 1.0, getattr(dimensions, dim))\n            if \"code_magnetic\" not in self.unit_registry:\n                self.unit_registry.add(\n                    \"code_magnetic\", 0.1**0.5, dimensions.magnetic_field_cgs\n                )\n\n        # if saved, set unit system\n        if \"unit_system_name\" in self.parameters:\n            unit_system = self.parameters[\"unit_system_name\"]\n            del self.parameters[\"unit_system_name\"]\n        else:\n            unit_system = \"cgs\"\n        # reset unit system since we may have a new unit registry\n        self._assign_unit_system(unit_system)\n\n        # assign units to parameters that have associated unit string\n        del_pars = []\n        for par in self.parameters:\n            ustr = f\"{par}_units\"\n            if ustr in self.parameters:\n                if isinstance(self.parameters[par], np.ndarray):\n                    to_u = self.arr\n                else:\n                    to_u = self.quan\n                self.parameters[par] = to_u(self.parameters[par], self.parameters[ustr])\n                del_pars.append(ustr)\n        for par in del_pars:\n            del self.parameters[par]\n\n        for attr in self._con_attrs:\n            sattr = _set_attrs.get(attr, attr)\n            if sattr == \"geometry\":\n                if \"geometry\" in self.parameters:\n                    self.geometry = Geometry(self.parameters[\"geometry\"])\n                continue\n            try:\n                setattr(self, sattr, self.parameters.get(attr))\n            except TypeError:\n                # some Dataset attributes are properties with setters\n                # which may not accept None as an input\n                pass\n\n    def _with_parameter_file_open(self, f):\n        # This allows subclasses to access the parameter file\n        # while it's still open to get additional information.\n        pass\n\n    def set_units(self):\n        if \"unit_registry_json\" in self.parameters:\n            self._set_code_unit_attributes()\n            del self.parameters[\"unit_registry_json\"]\n        else:\n            super().set_units()\n\n    def _set_code_unit_attributes(self):\n        attrs = (\n            \"length_unit\",\n            \"mass_unit\",\n            \"time_unit\",\n            \"velocity_unit\",\n            \"magnetic_unit\",\n        )\n        cgs_units = (\"cm\", \"g\", \"s\", \"cm/s\", \"gauss\")\n        base_units = np.ones(len(attrs))\n        for unit, attr, cgs_unit in zip(base_units, attrs, cgs_units, strict=True):\n            if attr in self.parameters and isinstance(\n                self.parameters[attr], YTQuantity\n            ):\n                uq = self.parameters[attr]\n            elif attr in self.parameters and f\"{attr}_units\" in self.parameters:\n                uq = self.quan(self.parameters[attr], self.parameters[f\"{attr}_units\"])\n                del self.parameters[attr]\n                del self.parameters[f\"{attr}_units\"]\n            elif isinstance(unit, str):\n                uq = self.quan(1.0, unit)\n            elif isinstance(unit, numeric_type):\n                uq = self.quan(unit, cgs_unit)\n            elif isinstance(unit, YTQuantity):\n                uq = unit\n            elif isinstance(unit, tuple):\n                uq = self.quan(unit[0], unit[1])\n            else:\n                raise RuntimeError(f\"{attr} ({unit}) is invalid.\")\n            setattr(self, attr, uq)\n\n\nclass YTDataset(SavedDataset):\n    \"\"\"Base dataset class for all ytdata datasets.\"\"\"\n\n    _con_attrs = (\n        \"cosmological_simulation\",\n        \"current_time\",\n        \"current_redshift\",\n        \"hubble_constant\",\n        \"omega_matter\",\n        \"omega_lambda\",\n        \"dimensionality\",\n        \"domain_dimensions\",\n        \"geometry\",\n        \"periodicity\",\n        \"domain_left_edge\",\n        \"domain_right_edge\",\n        \"container_type\",\n        \"data_type\",\n    )\n\n    def _with_parameter_file_open(self, f):\n        self.num_particles = {\n            group: parse_h5_attr(f[group], \"num_elements\")\n            for group in f\n            if group != self.default_fluid_type\n        }\n\n    def create_field_info(self):\n        self.field_dependencies = {}\n        self.derived_field_list = []\n        self.filtered_particle_types = []\n        self.field_info = self._field_info_class(self, self.field_list)\n        self.coordinates.setup_fields(self.field_info)\n        self.field_info.setup_fluid_fields()\n        for ptype in self.particle_types:\n            self.field_info.setup_particle_fields(ptype)\n\n        self._setup_gas_alias()\n        self.field_info.setup_fluid_index_fields()\n\n        if \"all\" not in self.particle_types:\n            mylog.debug(\"Creating Particle Union 'all'\")\n            pu = ParticleUnion(\"all\", list(self.particle_types_raw))\n            self.add_particle_union(pu)\n        self.field_info.setup_extra_union_fields()\n        mylog.debug(\"Loading field plugins.\")\n        self.field_info.load_all_plugins()\n        deps, unloaded = self.field_info.check_derived_fields()\n        self.field_dependencies.update(deps)\n\n    def _setup_gas_alias(self):\n        pass\n\n    def _setup_override_fields(self):\n        pass\n\n\nclass YTDataHDF5File(ParticleFile):\n    def __init__(self, ds, io, filename, file_id, range):\n        with h5py.File(filename, mode=\"r\") as f:\n            self.header = {field: parse_h5_attr(f, field) for field in f.attrs.keys()}\n\n        super().__init__(ds, io, filename, file_id, range)\n\n\nclass YTDataContainerDataset(YTDataset):\n    \"\"\"Dataset for saved geometric data containers.\"\"\"\n\n    _load_requirements = [\"h5py\"]\n    _index_class = ParticleIndex\n    _file_class = YTDataHDF5File\n    _field_info_class: type[FieldInfoContainer] = YTDataContainerFieldInfo\n    _suffix = \".h5\"\n    fluid_types = (\"grid\", \"gas\", \"deposit\", \"index\")\n\n    def __init__(\n        self,\n        filename,\n        dataset_type=\"ytdatacontainer_hdf5\",\n        index_order=None,\n        index_filename=None,\n        units_override=None,\n        unit_system=\"cgs\",\n    ):\n        self.index_order = index_order\n        self.index_filename = index_filename\n        super().__init__(\n            filename,\n            dataset_type,\n            units_override=units_override,\n            unit_system=unit_system,\n        )\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n        self.particle_types_raw = tuple(self.num_particles.keys())\n        self.particle_types = self.particle_types_raw\n        self.filename_template = self.parameter_filename\n        self.file_count = 1\n        self.domain_dimensions = np.ones(3, \"int32\")\n\n    def _setup_gas_alias(self):\n        \"Alias the grid type to gas by making a particle union.\"\n\n        if \"grid\" in self.particle_types and \"gas\" not in self.particle_types:\n            pu = ParticleUnion(\"gas\", [\"grid\"])\n            self.add_particle_union(pu)\n        # We have to alias this because particle unions only\n        # cover the field_list.\n        self.field_info.alias((\"gas\", \"cell_volume\"), (\"grid\", \"cell_volume\"))\n\n    @cached_property\n    def data(self):\n        \"\"\"\n        Return a data container configured like the original used to\n        create this dataset.\n        \"\"\"\n\n        # Some data containers can't be reconstructed in the same way\n        # since this is now particle-like data.\n        data_type = self.parameters.get(\"data_type\")\n        container_type = self.parameters.get(\"container_type\")\n        ex_container_type = [\"cutting\", \"quad_proj\", \"ray\", \"slice\", \"cut_region\"]\n        if data_type == \"yt_light_ray\" or container_type in ex_container_type:\n            mylog.info(\"Returning an all_data data container.\")\n            return self.all_data()\n\n        my_obj = getattr(self, self.parameters[\"container_type\"])\n        my_args = [self.parameters[con_arg] for con_arg in self.parameters[\"con_args\"]]\n        return my_obj(*my_args)\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".h5\"):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        with h5py.File(filename, mode=\"r\") as f:\n            data_type = parse_h5_attr(f, \"data_type\")\n            cont_type = parse_h5_attr(f, \"container_type\")\n            if data_type is None:\n                return False\n            if (\n                data_type == \"yt_data_container\"\n                and cont_type not in _grid_data_containers\n            ):\n                return True\n        return False\n\n\nclass YTDataLightRayDataset(YTDataContainerDataset):\n    \"\"\"Dataset for saved LightRay objects.\"\"\"\n\n    _load_requirements = [\"h5py\"]\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n        self._restore_light_ray_solution()\n\n    def _restore_light_ray_solution(self):\n        \"\"\"\n        Restore all information associate with the light ray solution\n        to its original form.\n        \"\"\"\n        key = \"light_ray_solution\"\n        self.light_ray_solution = []\n        lrs_fields = [\n            par for par in self.parameters if key in par and not par.endswith(\"_units\")\n        ]\n        if len(lrs_fields) == 0:\n            return\n        self.light_ray_solution = [{} for val in self.parameters[lrs_fields[0]]]\n        for sp3 in [\"unique_identifier\", \"filename\"]:\n            ksp3 = f\"{key}_{sp3}\"\n            if ksp3 not in lrs_fields:\n                continue\n            self.parameters[ksp3] = self.parameters[ksp3].astype(str)\n        for field in lrs_fields:\n            field_name = field[len(key) + 1 :]\n            for i in range(self.parameters[field].shape[0]):\n                self.light_ray_solution[i][field_name] = self.parameters[field][i]\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".h5\"):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        with h5py.File(filename, mode=\"r\") as f:\n            data_type = parse_h5_attr(f, \"data_type\")\n            if data_type in [\"yt_light_ray\"]:\n                return True\n        return False\n\n\nclass YTSpatialPlotDataset(YTDataContainerDataset):\n    \"\"\"Dataset for saved slices and projections.\"\"\"\n\n    _load_requirements = [\"h5py\"]\n    _field_info_class = YTGridFieldInfo\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, dataset_type=\"ytspatialplot_hdf5\", **kwargs)\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n        if self.parameters[\"container_type\"] == \"proj\":\n            if (\n                isinstance(self.parameters[\"weight_field\"], str)\n                and self.parameters[\"weight_field\"] == \"None\"\n            ):\n                self.parameters[\"weight_field\"] = None\n            elif isinstance(self.parameters[\"weight_field\"], np.ndarray):\n                self.parameters[\"weight_field\"] = tuple(self.parameters[\"weight_field\"])\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".h5\"):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        with h5py.File(filename, mode=\"r\") as f:\n            data_type = parse_h5_attr(f, \"data_type\")\n            cont_type = parse_h5_attr(f, \"container_type\")\n            if data_type == \"yt_data_container\" and cont_type in [\n                \"cutting\",\n                \"proj\",\n                \"slice\",\n                \"quad_proj\",\n            ]:\n                return True\n        return False\n\n\nclass YTGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, gid, index, filename=None):\n        AMRGridPatch.__init__(self, gid, filename=filename, index=index)\n        self._children_ids = []\n        self._parent_id = -1\n        self.Level = 0\n        self.LeftEdge = self.index.ds.domain_left_edge\n        self.RightEdge = self.index.ds.domain_right_edge\n\n    def __getitem__(self, key):\n        tr = super(AMRGridPatch, self).__getitem__(key)\n        try:\n            fields = self._determine_fields(key)\n        except YTFieldTypeNotFound:\n            return tr\n        finfo = self.ds._get_field_info(fields[0])\n        if not finfo.sampling_type == \"particle\":\n            return tr.reshape(self.ActiveDimensions[: self.ds.dimensionality])\n        return tr\n\n    @property\n    def Parent(self):\n        return None\n\n    @property\n    def Children(self):\n        return []\n\n\nclass YTDataHierarchy(GridIndex):\n    def __init__(self, ds, dataset_type=None):\n        self.dataset_type = dataset_type\n        self.float_type = \"float64\"\n        self.dataset = weakref.proxy(ds)\n        self.directory = os.getcwd()\n        super().__init__(ds, dataset_type)\n\n    def _count_grids(self):\n        self.num_grids = 1\n\n    def _parse_index(self):\n        self.grid_dimensions[:] = self.ds.domain_dimensions\n        self.grid_left_edge[:] = self.ds.domain_left_edge\n        self.grid_right_edge[:] = self.ds.domain_right_edge\n        self.grid_levels[:] = np.zeros(self.num_grids)\n        self.grid_procs = np.zeros(self.num_grids)\n        self.grid_particle_count[:] = sum(self.ds.num_particles.values())\n        self.grids = []\n        for gid in range(self.num_grids):\n            self.grids.append(self.grid(gid, self))\n            self.grids[gid].Level = self.grid_levels[gid, 0]\n        self.max_level = self.grid_levels.max()\n        temp_grids = np.empty(self.num_grids, dtype=\"object\")\n        for i, grid in enumerate(self.grids):\n            grid.filename = self.ds.parameter_filename\n            grid._prepare_grid()\n            grid.proc_num = self.grid_procs[i]\n            temp_grids[i] = grid\n        self.grids = temp_grids\n\n    def _detect_output_fields(self):\n        self.field_list = []\n        self.ds.field_units = self.ds.field_units or {}\n        with h5py.File(self.ds.parameter_filename, mode=\"r\") as f:\n            for group in f:\n                for field in f[group]:\n                    field_name = (str(group), str(field))\n                    self.field_list.append(field_name)\n                    self.ds.field_units[field_name] = parse_h5_attr(\n                        f[group][field], \"units\"\n                    )\n\n\nclass YTGridHierarchy(YTDataHierarchy):\n    grid = YTGrid\n\n    def _populate_grid_objects(self):\n        for g in self.grids:\n            g._setup_dx()\n        self.max_level = self.grid_levels.max()\n\n\nclass YTGridDataset(YTDataset):\n    \"\"\"Dataset for saved covering grids, arbitrary grids, and FRBs.\"\"\"\n\n    _load_requirements = [\"h5py\"]\n    _index_class: type[Index] = YTGridHierarchy\n    _field_info_class = YTGridFieldInfo\n    _dataset_type = \"ytgridhdf5\"\n    geometry = Geometry.CARTESIAN\n    default_fluid_type = \"grid\"\n    fluid_types: tuple[str, ...] = (\"grid\", \"gas\", \"deposit\", \"index\")\n\n    def __init__(self, filename, unit_system=\"cgs\"):\n        super().__init__(filename, self._dataset_type, unit_system=unit_system)\n        self.data = self.index.grids[0]\n\n    def _parse_parameter_file(self):\n        super()._parse_parameter_file()\n        self.num_particles.pop(self.default_fluid_type, None)\n        self.particle_types_raw = tuple(self.num_particles.keys())\n        self.particle_types = self.particle_types_raw\n\n        # correct domain dimensions for the covering grid dimension\n        self.base_domain_left_edge = self.domain_left_edge\n        self.base_domain_right_edge = self.domain_right_edge\n        self.base_domain_dimensions = self.domain_dimensions\n\n        if self.container_type in _grid_data_containers:\n            self.domain_left_edge = self.parameters[\"left_edge\"]\n\n            if \"level\" in self.parameters[\"con_args\"]:\n                dx = (self.base_domain_right_edge - self.base_domain_left_edge) / (\n                    self.domain_dimensions * self.refine_by ** self.parameters[\"level\"]\n                )\n                self.domain_right_edge = (\n                    self.domain_left_edge + self.parameters[\"ActiveDimensions\"] * dx\n                )\n                self.domain_dimensions = np.rint(\n                    (self.domain_right_edge - self.domain_left_edge) / dx\n                ).astype(\"int64\")\n            else:\n                self.domain_right_edge = self.parameters[\"right_edge\"]\n                self.domain_dimensions = self.parameters[\"ActiveDimensions\"]\n                dx = (\n                    self.domain_right_edge - self.domain_left_edge\n                ) / self.domain_dimensions\n\n            periodicity = (\n                np.abs(self.domain_left_edge - self.base_domain_left_edge) < 0.5 * dx\n            )\n            periodicity &= (\n                np.abs(self.domain_right_edge - self.base_domain_right_edge) < 0.5 * dx\n            )\n            self._periodicity = periodicity\n\n        elif self.data_type == \"yt_frb\":\n            dle = self.domain_left_edge\n            self.domain_left_edge = uconcatenate(\n                [self.parameters[\"left_edge\"].to(dle.units), [0] * dle.uq]\n            )\n            dre = self.domain_right_edge\n            self.domain_right_edge = uconcatenate(\n                [self.parameters[\"right_edge\"].to(dre.units), [1] * dre.uq]\n            )\n            self.domain_dimensions = np.concatenate(\n                [self.parameters[\"ActiveDimensions\"], [1]]\n            )\n\n    def _setup_gas_alias(self):\n        \"Alias the grid type to gas with a field alias.\"\n\n        for ftype, field in self.field_list:\n            if ftype == \"grid\":\n                self.field_info.alias((\"gas\", field), (\"grid\", field))\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".h5\"):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        with h5py.File(filename, mode=\"r\") as f:\n            data_type = parse_h5_attr(f, \"data_type\")\n            cont_type = parse_h5_attr(f, \"container_type\")\n            if data_type == \"yt_frb\":\n                return True\n            if data_type == \"yt_data_container\" and cont_type in _grid_data_containers:\n                return True\n        return False\n\n\nclass YTNonspatialGrid(AMRGridPatch):\n    _id_offset = 0\n\n    def __init__(self, gid, index, filename=None):\n        super().__init__(gid, filename=filename, index=index)\n        self._children_ids = []\n        self._parent_id = -1\n        self.Level = 0\n        self.LeftEdge = self.index.ds.domain_left_edge\n        self.RightEdge = self.index.ds.domain_right_edge\n\n    def __getitem__(self, key):\n        tr = super(AMRGridPatch, self).__getitem__(key)\n        try:\n            fields = self._determine_fields(key)\n        except YTFieldTypeNotFound:\n            return tr\n        self.ds._get_field_info(fields[0])\n        return tr\n\n    def get_data(self, fields=None):\n        if fields is None:\n            return\n        nfields = []\n        apply_fields = defaultdict(list)\n        for field in self._determine_fields(fields):\n            if field[0] in self.ds.filtered_particle_types:\n                f = self.ds.known_filters[field[0]]\n                apply_fields[field[0]].append((f.filtered_type, field[1]))\n            else:\n                nfields.append(field)\n        for filter_type in apply_fields:\n            f = self.ds.known_filters[filter_type]\n            with f.apply(self):\n                self.get_data(apply_fields[filter_type])\n        fields = nfields\n        if len(fields) == 0:\n            return\n        # Now we collect all our fields\n        # Here is where we need to perform a validation step, so that if we\n        # have a field requested that we actually *can't* yet get, we put it\n        # off until the end.  This prevents double-reading fields that will\n        # need to be used in spatial fields later on.\n        fields_to_get = []\n        # This will be pre-populated with spatial fields\n        fields_to_generate = []\n        for field in self._determine_fields(fields):\n            if field in self.field_data:\n                continue\n            finfo = self.ds._get_field_info(field)\n            try:\n                finfo.check_available(self)\n            except NeedsGridType:\n                fields_to_generate.append(field)\n                continue\n            fields_to_get.append(field)\n        if len(fields_to_get) == 0 and len(fields_to_generate) == 0:\n            return\n        elif self._locked:\n            raise GenerationInProgress(fields)\n        # Track which ones we want in the end\n        ofields = set(list(self.field_data.keys()) + fields_to_get + fields_to_generate)\n        # At this point, we want to figure out *all* our dependencies.\n        fields_to_get = self._identify_dependencies(fields_to_get, self._spatial)\n        # We now split up into readers for the types of fields\n        fluids, particles = [], []\n        finfos = {}\n        for field_key in fields_to_get:\n            finfo = self.ds._get_field_info(field_key)\n            finfos[field_key] = finfo\n            if finfo.sampling_type == \"particle\":\n                particles.append(field_key)\n            elif field_key not in fluids:\n                fluids.append(field_key)\n\n        # The _read method will figure out which fields it needs to get from\n        # disk, and return a dict of those fields along with the fields that\n        # need to be generated.\n        read_fluids, gen_fluids = self.index._read_fluid_fields(\n            fluids, self, self._current_chunk\n        )\n        for f, v in read_fluids.items():\n            convert = True\n            if v.dtype != np.float64:\n                if finfos[f].units == \"\":\n                    self.field_data[f] = v\n                    convert = False\n                else:\n                    v = v.astype(np.float64)\n            if convert:\n                self.field_data[f] = self.ds.arr(v, units=finfos[f].units)\n                self.field_data[f].convert_to_units(finfos[f].output_units)\n\n        read_particles, gen_particles = self.index._read_fluid_fields(\n            particles, self, self._current_chunk\n        )\n        for f, v in read_particles.items():\n            convert = True\n            if v.dtype != np.float64:\n                if finfos[f].units == \"\":\n                    self.field_data[f] = v\n                    convert = False\n                else:\n                    v = v.astype(np.float64)\n            if convert:\n                self.field_data[f] = self.ds.arr(v, units=finfos[f].units)\n                self.field_data[f].convert_to_units(finfos[f].output_units)\n\n        fields_to_generate += gen_fluids + gen_particles\n        self._generate_fields(fields_to_generate)\n        for field in list(self.field_data.keys()):\n            if field not in ofields:\n                self.field_data.pop(field)\n\n    @property\n    def Parent(self):\n        return None\n\n    @property\n    def Children(self):\n        return []\n\n\nclass YTNonspatialHierarchy(YTDataHierarchy):\n    grid = YTNonspatialGrid\n\n    def _populate_grid_objects(self):\n        for g in self.grids:\n            g._setup_dx()\n            # this is non-spatial, so remove the code_length units\n            g.dds = self.ds.arr(g.dds.d, \"\")\n            g.ActiveDimensions = self.ds.domain_dimensions\n        self.max_level = self.grid_levels.max()\n\n    def _read_fluid_fields(self, fields, dobj, chunk=None):\n        if len(fields) == 0:\n            return {}, []\n        fields_to_read, fields_to_generate = self._split_fields(fields)\n        if len(fields_to_read) == 0:\n            return {}, fields_to_generate\n        selector = dobj.selector\n        fields_to_return = self.io._read_fluid_selection(dobj, selector, fields_to_read)\n        return fields_to_return, fields_to_generate\n\n\nclass YTNonspatialDataset(YTGridDataset):\n    \"\"\"Dataset for general array data.\"\"\"\n\n    _load_requirements = [\"h5py\"]\n    _index_class = YTNonspatialHierarchy\n    _field_info_class = YTGridFieldInfo\n    _dataset_type = \"ytnonspatialhdf5\"\n    geometry = Geometry.CARTESIAN\n    default_fluid_type = \"data\"\n    fluid_types: tuple[str, ...] = (\"data\", \"gas\")\n\n    def _parse_parameter_file(self):\n        super(YTGridDataset, self)._parse_parameter_file()\n        self.num_particles.pop(self.default_fluid_type, None)\n        self.particle_types_raw = tuple(self.num_particles.keys())\n        self.particle_types = self.particle_types_raw\n\n    def _set_derived_attrs(self):\n        # set some defaults just to make things go\n        default_attrs = {\n            \"dimensionality\": 3,\n            \"domain_dimensions\": np.ones(3, dtype=\"int64\"),\n            \"domain_left_edge\": np.zeros(3),\n            \"domain_right_edge\": np.ones(3),\n            \"_periodicity\": np.ones(3, dtype=\"bool\"),\n        }\n        for att, val in default_attrs.items():\n            if getattr(self, att, None) is None:\n                setattr(self, att, val)\n\n    def _setup_classes(self):\n        # We don't allow geometric selection for non-spatial datasets\n        self.objects = []\n\n    @parallel_root_only\n    def print_key_parameters(self):\n        for a in [\n            \"current_time\",\n            \"domain_dimensions\",\n            \"domain_left_edge\",\n            \"domain_right_edge\",\n            \"cosmological_simulation\",\n        ]:\n            v = getattr(self, a)\n            if v is not None:\n                mylog.info(\"Parameters: %-25s = %s\", a, v)\n        if hasattr(self, \"cosmological_simulation\") and self.cosmological_simulation:\n            for a in [\n                \"current_redshift\",\n                \"omega_lambda\",\n                \"omega_matter\",\n                \"hubble_constant\",\n            ]:\n                v = getattr(self, a)\n                if v is not None:\n                    mylog.info(\"Parameters: %-25s = %s\", a, v)\n        mylog.warning(\"Geometric data selection not available for this dataset type.\")\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".h5\"):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        with h5py.File(filename, mode=\"r\") as f:\n            data_type = parse_h5_attr(f, \"data_type\")\n            if data_type == \"yt_array_data\":\n                return True\n        return False\n\n\nclass YTProfileDataset(YTNonspatialDataset):\n    \"\"\"Dataset for saved profile objects.\"\"\"\n\n    _load_requirements = [\"h5py\"]\n    fluid_types = (\"data\", \"gas\", \"standard_deviation\")\n\n    def __init__(self, filename, unit_system=\"cgs\"):\n        super().__init__(filename, unit_system=unit_system)\n\n    _profile = None\n\n    @property\n    def profile(self):\n        if self._profile is not None:\n            return self._profile\n        if self.dimensionality == 1:\n            self._profile = Profile1DFromDataset(self)\n        elif self.dimensionality == 2:\n            self._profile = Profile2DFromDataset(self)\n        elif self.dimensionality == 3:\n            self._profile = Profile3DFromDataset(self)\n        else:\n            self._profile = None\n        return self._profile\n\n    def _parse_parameter_file(self):\n        super(YTGridDataset, self)._parse_parameter_file()\n\n        if (\n            isinstance(self.parameters[\"weight_field\"], str)\n            and self.parameters[\"weight_field\"] == \"None\"\n        ):\n            self.parameters[\"weight_field\"] = None\n        elif isinstance(self.parameters[\"weight_field\"], np.ndarray):\n            self.parameters[\"weight_field\"] = tuple(\n                self.parameters[\"weight_field\"].astype(str)\n            )\n\n        for a in [\"profile_dimensions\"] + [\n            f\"{ax}_{attr}\" for ax in \"xyz\"[: self.dimensionality] for attr in [\"log\"]\n        ]:\n            setattr(self, a, self.parameters[a])\n\n        self.base_domain_left_edge = self.domain_left_edge\n        self.base_domain_right_edge = self.domain_right_edge\n        self.base_domain_dimensions = self.domain_dimensions\n\n        domain_dimensions = np.ones(3, dtype=\"int64\")\n        domain_dimensions[: self.dimensionality] = self.profile_dimensions\n        self.domain_dimensions = domain_dimensions\n        domain_left_edge = np.zeros(3)\n        domain_right_edge = np.ones(3)\n        for i, ax in enumerate(\"xyz\"[: self.dimensionality]):\n            range_name = f\"{ax}_range\"\n            my_range = self.parameters[range_name]\n            if getattr(self, f\"{ax}_log\", False):\n                my_range = np.log10(my_range)\n            domain_left_edge[i] = my_range[0]\n            domain_right_edge[i] = my_range[1]\n            setattr(self, range_name, self.parameters[range_name])\n\n            bin_field = f\"{ax}_field\"\n            if (\n                isinstance(self.parameters[bin_field], str)\n                and self.parameters[bin_field] == \"None\"\n            ):\n                self.parameters[bin_field] = None\n            elif isinstance(self.parameters[bin_field], np.ndarray):\n                self.parameters[bin_field] = (\n                    \"data\",\n                    self.parameters[bin_field].astype(str)[1],\n                )\n            setattr(self, bin_field, self.parameters[bin_field])\n        self.domain_left_edge = domain_left_edge\n        self.domain_right_edge = domain_right_edge\n\n    def _setup_gas_alias(self):\n        \"Alias the grid type to gas with a field alias.\"\n        for ftype, field in self.field_list:\n            if ftype == \"data\":\n                self.field_info.alias((\"gas\", field), (ftype, field))\n\n    def create_field_info(self):\n        super().create_field_info()\n        if self.parameters[\"weight_field\"] is not None:\n            self.field_info.alias(\n                self.parameters[\"weight_field\"], (self.default_fluid_type, \"weight\")\n            )\n\n    def _set_derived_attrs(self):\n        self.domain_center = 0.5 * (self.domain_right_edge + self.domain_left_edge)\n        self.domain_width = self.domain_right_edge - self.domain_left_edge\n\n    def print_key_parameters(self):\n        if is_root():\n            mylog.info(\"YTProfileDataset\")\n            for a in [\"dimensionality\", \"profile_dimensions\"] + [\n                f\"{ax}_{attr}\"\n                for ax in \"xyz\"[: self.dimensionality]\n                for attr in [\"field\", \"range\", \"log\"]\n            ]:\n                v = getattr(self, a)\n                mylog.info(\"Parameters: %-25s = %s\", a, v)\n        super().print_key_parameters()\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".h5\"):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        with h5py.File(filename, mode=\"r\") as f:\n            data_type = parse_h5_attr(f, \"data_type\")\n            if data_type == \"yt_profile\":\n                return True\n        return False\n\n\nclass YTClumpContainer(TreeContainer):\n    def __init__(\n        self, clump_id, global_id, parent_id, contour_key, contour_id, ds=None\n    ):\n        self.clump_id = clump_id\n        self.global_id = global_id\n        self.parent_id = parent_id\n        self.contour_key = contour_key\n        self.contour_id = contour_id\n        self.parent = None\n        self.ds = ds\n        TreeContainer.__init__(self)\n\n    def add_child(self, child):\n        if self.children is None:\n            self.children = []\n        self.children.append(child)\n        child.parent = self\n\n    def __repr__(self):\n        return f\"Clump[{self.clump_id}]\"\n\n    def __getitem__(self, field):\n        g = self.ds.data\n        f = g._determine_fields(field)[0]\n        if f[0] == \"clump\":\n            return g[f][self.global_id]\n        if self.contour_id == -1:\n            return g[f]\n        cfield = (f[0], f\"contours_{self.contour_key.decode('utf-8')}\")\n        if f[0] == \"grid\":\n            return g[f][g[cfield] == self.contour_id]\n        return self.parent[f][g[cfield] == self.contour_id]\n\n\nclass YTClumpTreeDataset(YTNonspatialDataset):\n    \"\"\"Dataset for saved clump-finder data.\"\"\"\n\n    _load_requirements = [\"h5py\"]\n\n    def __init__(self, filename, unit_system=\"cgs\"):\n        super().__init__(filename, unit_system=unit_system)\n        self._load_tree()\n\n    def _load_tree(self):\n        my_tree = {}\n        for i, clump_id in enumerate(self.data[\"clump\", \"clump_id\"]):\n            my_tree[clump_id] = YTClumpContainer(\n                clump_id,\n                i,\n                self.data[\"clump\", \"parent_id\"][i],\n                self.data[\"clump\", \"contour_key\"][i],\n                self.data[\"clump\", \"contour_id\"][i],\n                self,\n            )\n        for clump in my_tree.values():\n            if clump.parent_id == -1:\n                self.tree = clump\n            else:\n                parent = my_tree[clump.parent_id]\n                parent.add_child(clump)\n\n    @cached_property\n    def leaves(self):\n        return [clump for clump in self.tree if clump.children is None]\n\n    @classmethod\n    def _is_valid(cls, filename: str, *args, **kwargs) -> bool:\n        if not filename.endswith(\".h5\"):\n            return False\n\n        if cls._missing_load_requirements():\n            return False\n\n        with h5py.File(filename, mode=\"r\") as f:\n            data_type = parse_h5_attr(f, \"data_type\")\n            if data_type is None:\n                return False\n            if data_type == \"yt_clump_tree\":\n                return True\n        return False\n"
  },
  {
    "path": "yt/frontends/ytdata/fields.py",
    "content": "from yt.fields.field_info_container import FieldInfoContainer\n\nm_units = \"g\"\np_units = \"cm\"\nv_units = \"cm / s\"\nr_units = \"cm\"\n\n\nclass YTDataContainerFieldInfo(FieldInfoContainer):\n    known_other_fields = ()\n\n    known_particle_fields = ()\n\n    def __init__(self, ds, field_list):\n        super().__init__(ds, field_list)\n        self.add_fake_grid_fields()\n\n    def add_fake_grid_fields(self):\n        \"\"\"\n        Add cell volume and mass fields that use the dx, dy, and dz\n        fields that come with the dataset instead of the index fields\n        which correspond to the oct tree.  We need to do this for now\n        since we're treating the grid data like particles until we\n        implement exporting AMR hierarchies.\n        \"\"\"\n\n        if (\"grid\", \"cell_volume\") not in self.field_list:\n\n            def _cell_volume(data):\n                return data[\"grid\", \"dx\"] * data[\"grid\", \"dy\"] * data[\"grid\", \"dz\"]\n\n            self.add_field(\n                (\"grid\", \"cell_volume\"),\n                sampling_type=\"particle\",\n                function=_cell_volume,\n                units=\"cm**3\",\n            )\n\n\nclass YTGridFieldInfo(FieldInfoContainer):\n    known_other_fields = ()\n\n    known_particle_fields = ()\n"
  },
  {
    "path": "yt/frontends/ytdata/io.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog, parse_h5_attr\nfrom yt.geometry.selection_routines import GridSelector\nfrom yt.units._numpy_wrapper_functions import uvstack\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\nclass IOHandlerYTNonspatialhdf5(BaseIOHandler):\n    _dataset_type = \"ytnonspatialhdf5\"\n    _base = slice(None)\n    _field_dtype = \"float64\"\n\n    def _read_fluid_selection(self, g, selector, fields):\n        rv = {}\n        if isinstance(selector, GridSelector):\n            if g.id in self._cached_fields:\n                gf = self._cached_fields[g.id]\n                rv.update(gf)\n            if len(rv) == len(fields):\n                return rv\n            f = h5py.File(g.filename, mode=\"r\")\n            for field in fields:\n                if field in rv:\n                    self._hits += 1\n                    continue\n                self._misses += 1\n                ftype, fname = field\n                rv[ftype, fname] = f[ftype][fname][()]\n            if self._cache_on:\n                for gid in rv:\n                    self._cached_fields.setdefault(gid, {})\n                    self._cached_fields[gid].update(rv[gid])\n            f.close()\n            return rv\n        else:\n            raise RuntimeError(\n                \"Geometric selection not supported for non-spatial datasets.\"\n            )\n\n\nclass IOHandlerYTGridHDF5(BaseIOHandler):\n    _dataset_type = \"ytgridhdf5\"\n    _base = slice(None)\n    _field_dtype = \"float64\"\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        rv = {}\n        # Now we have to do something unpleasant\n        chunks = list(chunks)\n        if isinstance(selector, GridSelector):\n            if not (len(chunks) == len(chunks[0].objs) == 1):\n                raise RuntimeError\n            g = chunks[0].objs[0]\n            if g.id in self._cached_fields:\n                gf = self._cached_fields[g.id]\n                rv.update(gf)\n            if len(rv) == len(fields):\n                return rv\n            f = h5py.File(g.filename, mode=\"r\")\n            gds = f[self.ds.default_fluid_type]\n            for field in fields:\n                if field in rv:\n                    self._hits += 1\n                    continue\n                self._misses += 1\n                ftype, fname = field\n                rv[ftype, fname] = gds[fname][()]\n            if self._cache_on:\n                for gid in rv:\n                    self._cached_fields.setdefault(gid, {})\n                    self._cached_fields[gid].update(rv[gid])\n            f.close()\n            return rv\n        if size is None:\n            size = sum(g.count(selector) for chunk in chunks for g in chunk.objs)\n        for field in fields:\n            ftype, fname = field\n            fsize = size\n            rv[field] = np.empty(fsize, dtype=\"float64\")\n        ng = sum(len(c.objs) for c in chunks)\n        mylog.debug(\n            \"Reading %s cells of %s fields in %s grids\",\n            size,\n            [f2 for f1, f2 in fields],\n            ng,\n        )\n        ind = 0\n        for chunk in chunks:\n            f = None\n            for g in chunk.objs:\n                if g.filename is None:\n                    continue\n                if f is None:\n                    f = h5py.File(g.filename, mode=\"r\")\n                gf = self._cached_fields.get(g.id, {})\n                nd = 0\n                for field in fields:\n                    if field in gf:\n                        nd = g.select(selector, gf[field], rv[field], ind)\n                        self._hits += 1\n                        continue\n                    self._misses += 1\n                    ftype, fname = field\n                    # add extra dimensions to make data 3D\n                    data = f[ftype][fname][()].astype(self._field_dtype)\n                    for dim in range(len(data.shape), 3):\n                        data = np.expand_dims(data, dim)\n                    if self._cache_on:\n                        self._cached_fields.setdefault(g.id, {})\n                        self._cached_fields[g.id][field] = data\n                    nd = g.select(selector, data, rv[field], ind)  # caches\n                ind += nd\n            if f:\n                f.close()\n        return rv\n\n    def _read_particle_coords(self, chunks, ptf):\n        pn = \"particle_position_%s\"\n        chunks = list(chunks)\n        for chunk in chunks:\n            f = None\n            for g in chunk.objs:\n                if g.filename is None:\n                    continue\n                if f is None:\n                    f = h5py.File(g.filename, mode=\"r\")\n                if g.NumberOfParticles == 0:\n                    continue\n                for ptype, field_list in sorted(ptf.items()):\n                    units = parse_h5_attr(f[ptype][pn % \"x\"], \"units\")\n                    x, y, z = (\n                        self.ds.arr(f[ptype][pn % ax][()].astype(\"float64\"), units)\n                        for ax in \"xyz\"\n                    )\n                    for field in field_list:\n                        if np.asarray(f[ptype][field]).ndim > 1:\n                            self._array_fields[field] = f[ptype][field].shape[1:]\n                    yield ptype, (x, y, z), 0.0\n            if f:\n                f.close()\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        pn = \"particle_position_%s\"\n        chunks = list(chunks)\n        for chunk in chunks:  # These should be organized by grid filename\n            f = None\n            for g in chunk.objs:\n                if g.filename is None:\n                    continue\n                if f is None:\n                    f = h5py.File(g.filename, mode=\"r\")\n                if g.NumberOfParticles == 0:\n                    continue\n                for ptype, field_list in sorted(ptf.items()):\n                    units = parse_h5_attr(f[ptype][pn % \"x\"], \"units\")\n                    x, y, z = (\n                        self.ds.arr(f[ptype][pn % ax][()].astype(\"float64\"), units)\n                        for ax in \"xyz\"\n                    )\n                    mask = selector.select_points(x, y, z, 0.0)\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        data = np.asarray(f[ptype][field][()], \"=f8\")\n                        yield (ptype, field), data[mask]\n            if f:\n                f.close()\n\n\nclass IOHandlerYTDataContainerHDF5(BaseIOHandler):\n    _dataset_type = \"ytdatacontainer_hdf5\"\n\n    def _read_fluid_selection(self, chunks, selector, fields, size):\n        raise NotImplementedError\n\n    def _yield_coordinates(self, data_file):\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            for ptype in f.keys():\n                if \"x\" not in f[ptype].keys():\n                    continue\n                units = _get_position_array_units(ptype, f, \"x\")\n                x, y, z = (\n                    self.ds.arr(_get_position_array(ptype, f, ax), units)\n                    for ax in \"xyz\"\n                )\n                pos = uvstack([x, y, z]).T\n                pos.convert_to_units(\"code_length\")\n                yield ptype, pos\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This will read chunks and yield the results.\n        for data_file in self._sorted_chunk_iterator(chunks):\n            index_mask = slice(data_file.start, data_file.end)\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype in sorted(ptf):\n                    pcount = data_file.total_particles[ptype]\n                    if pcount == 0:\n                        continue\n                    units = _get_position_array_units(ptype, f, \"x\")\n                    x, y, z = (\n                        self.ds.arr(\n                            _get_position_array(ptype, f, ax, index_mask=index_mask),\n                            units,\n                        )\n                        for ax in \"xyz\"\n                    )\n                    yield ptype, (x, y, z), 0.0\n\n    def _read_particle_data_file(self, data_file, ptf, selector):\n        data_return = {}\n\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            index_mask = slice(data_file.start, data_file.end)\n            for ptype, field_list in sorted(ptf.items()):\n                if selector is None or getattr(selector, \"is_all_data\", False):\n                    mask = index_mask\n                else:\n                    units = _get_position_array_units(ptype, f, \"x\")\n                    x, y, z = (\n                        self.ds.arr(\n                            _get_position_array(ptype, f, ax, index_mask=index_mask),\n                            units,\n                        )\n                        for ax in \"xyz\"\n                    )\n                    mask = selector.select_points(x, y, z, 0.0)\n                    del x, y, z\n                    if mask is None:\n                        continue\n\n                for field in field_list:\n                    data = f[ptype][field][mask].astype(\"float64\", copy=False)\n                    data_return[ptype, field] = data\n\n        return data_return\n\n    def _count_particles(self, data_file):\n        si, ei = data_file.start, data_file.end\n        if None not in (si, ei):\n            pcount = {}\n            for ptype, npart in self.ds.num_particles.items():\n                pcount[ptype] = np.clip(npart - si, 0, ei - si)\n        else:\n            pcount = self.ds.num_particles\n        return pcount\n\n    def _identify_fields(self, data_file):\n        fields = []\n        units = {}\n        with h5py.File(data_file.filename, mode=\"r\") as f:\n            for ptype in f:\n                fields.extend([(ptype, str(field)) for field in f[ptype]])\n                units.update(\n                    {\n                        (ptype, str(field)): parse_h5_attr(f[ptype][field], \"units\")\n                        for field in f[ptype]\n                    }\n                )\n        return fields, units\n\n\nclass IOHandlerYTSpatialPlotHDF5(IOHandlerYTDataContainerHDF5):\n    _dataset_type = \"ytspatialplot_hdf5\"\n\n    def _read_particle_coords(self, chunks, ptf):\n        # This will read chunks and yield the results.\n        for data_file in self._sorted_chunk_iterator(chunks):\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype in sorted(ptf):\n                    pcount = data_file.total_particles[ptype]\n                    if pcount == 0:\n                        continue\n                    x = _get_position_array(ptype, f, \"px\")\n                    y = _get_position_array(ptype, f, \"py\")\n                    z = (\n                        np.zeros(x.size, dtype=\"float64\")\n                        + self.ds.domain_left_edge[2].to(\"code_length\").d\n                    )\n                    yield ptype, (x, y, z), 0.0\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # Now we have all the sizes, and we can allocate\n        for data_file in self._sorted_chunk_iterator(chunks):\n            all_count = self._count_particles(data_file)\n            with h5py.File(data_file.filename, mode=\"r\") as f:\n                for ptype, field_list in sorted(ptf.items()):\n                    x = _get_position_array(ptype, f, \"px\")\n                    y = _get_position_array(ptype, f, \"py\")\n                    z = (\n                        np.zeros(all_count[ptype], dtype=\"float64\")\n                        + self.ds.domain_left_edge[2].to(\"code_length\").d\n                    )\n                    mask = selector.select_points(x, y, z, 0.0)\n                    del x, y, z\n                    if mask is None:\n                        continue\n                    for field in field_list:\n                        data = f[ptype][field][mask].astype(\"float64\")\n                        yield (ptype, field), data\n\n\ndef _get_position_array(ptype, f, ax, index_mask=None):\n    if index_mask is None:\n        index_mask = slice(None, None, None)\n    if ptype == \"grid\":\n        pos_name = \"\"\n    else:\n        pos_name = \"particle_position_\"\n    return f[ptype][pos_name + ax][index_mask].astype(\"float64\")\n\n\ndef _get_position_array_units(ptype, f, ax):\n    if ptype == \"grid\":\n        pos_name = \"\"\n    else:\n        pos_name = \"particle_position_\"\n    return parse_h5_attr(f[ptype][pos_name + ax], \"units\")\n"
  },
  {
    "path": "yt/frontends/ytdata/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/frontends/ytdata/tests/test_data_reload.py",
    "content": "import numpy as np\n\nfrom yt.loaders import load_uniform_grid\nfrom yt.testing import requires_module_pytest\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\n@requires_module_pytest(\"h5py\")\ndef test_save_as_data_unit_system(tmp_path):\n    # This test checks that the file saved with calling save_as_dataset\n    # for a ds with a \"code\" unit system contains the proper \"unit_system_name\".\n    # It checks the hdf5 file directly rather than using yt.load(), because\n    # https://github.com/yt-project/yt/issues/4315 only manifested restarting\n    # the python kernel (because the unit registry is state dependent).\n\n    fi = tmp_path / \"output_data.h5\"\n    shp = (4, 4, 4)\n    data = {\"density\": np.random.random(shp)}\n    ds = load_uniform_grid(data, shp, unit_system=\"code\")\n    assert \"code\" in ds._unit_system_name\n\n    sp = ds.sphere(ds.domain_center, ds.domain_width[0] / 2.0)\n    sp.save_as_dataset(fi)\n\n    with h5py.File(fi, mode=\"r\") as f:\n        assert f.attrs[\"unit_system_name\"] == \"code\"\n"
  },
  {
    "path": "yt/frontends/ytdata/tests/test_old_outputs.py",
    "content": "\"\"\"\nytdata frontend tests using enzo_tiny_cosmology\n\n\n\n\"\"\"\n\nimport os\nimport shutil\nimport tempfile\n\nimport numpy as np\n\nfrom yt.data_objects.api import create_profile\nfrom yt.frontends.ytdata.api import (\n    YTDataContainerDataset,\n    YTGridDataset,\n    YTNonspatialDataset,\n    YTProfileDataset,\n    YTSpatialPlotDataset,\n)\nfrom yt.frontends.ytdata.tests.test_outputs import (\n    YTDataFieldTest,\n    compare_unit_attributes,\n)\nfrom yt.testing import (\n    assert_allclose_units,\n    assert_array_equal,\n    requires_file,\n    requires_module,\n    skip,\n)\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.answer_testing.framework import data_dir_load, requires_ds\nfrom yt.visualization.profile_plotter import PhasePlot, ProfilePlot\n\nenzotiny = \"enzo_tiny_cosmology/DD0046/DD0046\"\nytdata_dir = \"ytdata_test\"\n\n\n@skip(reason=\"See https://github.com/yt-project/yt/issues/3909\")\n@requires_module(\"h5py\")\n@requires_ds(enzotiny)\n@requires_file(os.path.join(ytdata_dir, \"DD0046_sphere.h5\"))\n@requires_file(os.path.join(ytdata_dir, \"DD0046_cut_region.h5\"))\ndef test_old_datacontainer_data():\n    ds = data_dir_load(enzotiny)\n    sphere = ds.sphere(ds.domain_center, (10, \"Mpc\"))\n    fn = \"DD0046_sphere.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    sphere_ds = data_dir_load(full_fn)\n    compare_unit_attributes(ds, sphere_ds)\n    assert isinstance(sphere_ds, YTDataContainerDataset)\n    yield YTDataFieldTest(full_fn, (\"grid\", \"density\"))\n    yield YTDataFieldTest(full_fn, (\"all\", \"particle_mass\"))\n    cr = ds.cut_region(sphere, ['obj[\"gas\", \"temperature\"] > 1e4'])\n    fn = \"DD0046_cut_region.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    cr_ds = data_dir_load(full_fn)\n    assert isinstance(cr_ds, YTDataContainerDataset)\n    assert (cr[\"gas\", \"temperature\"] == cr_ds.data[\"gas\", \"temperature\"]).all()\n\n\n@skip(reason=\"See https://github.com/yt-project/yt/issues/3909\")\n@requires_module(\"h5py\")\n@requires_ds(enzotiny)\n@requires_file(os.path.join(ytdata_dir, \"DD0046_covering_grid.h5\"))\n@requires_file(os.path.join(ytdata_dir, \"DD0046_arbitrary_grid.h5\"))\n@requires_file(os.path.join(ytdata_dir, \"DD0046_proj_frb.h5\"))\ndef test_old_grid_datacontainer_data():\n    ds = data_dir_load(enzotiny)\n\n    fn = \"DD0046_covering_grid.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    cg_ds = data_dir_load(full_fn)\n    compare_unit_attributes(ds, cg_ds)\n    assert isinstance(cg_ds, YTGridDataset)\n    yield YTDataFieldTest(full_fn, (\"grid\", \"density\"))\n    yield YTDataFieldTest(full_fn, (\"all\", \"particle_mass\"))\n\n    fn = \"DD0046_arbitrary_grid.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    ag_ds = data_dir_load(full_fn)\n    compare_unit_attributes(ds, ag_ds)\n    assert isinstance(ag_ds, YTGridDataset)\n    yield YTDataFieldTest(full_fn, (\"grid\", \"density\"))\n    yield YTDataFieldTest(full_fn, (\"all\", \"particle_mass\"))\n\n    my_proj = ds.proj(\"density\", \"x\", weight_field=\"density\")\n    frb = my_proj.to_frb(1.0, (800, 800))\n    fn = \"DD0046_proj_frb.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    frb_ds = data_dir_load(full_fn)\n    assert_allclose_units(frb[\"gas\", \"density\"], frb_ds.data[\"gas\", \"density\"], 1e-7)\n    compare_unit_attributes(ds, frb_ds)\n    assert isinstance(frb_ds, YTGridDataset)\n    yield YTDataFieldTest(full_fn, (\"gas\", \"density\"), geometric=False)\n\n\n@skip(reason=\"See https://github.com/yt-project/yt/issues/3909\")\n@requires_module(\"h5py\")\n@requires_ds(enzotiny)\n@requires_file(os.path.join(ytdata_dir, \"DD0046_proj.h5\"))\ndef test_old_spatial_data():\n    ds = data_dir_load(enzotiny)\n    fn = \"DD0046_proj.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    proj_ds = data_dir_load(full_fn)\n    compare_unit_attributes(ds, proj_ds)\n    assert isinstance(proj_ds, YTSpatialPlotDataset)\n    yield YTDataFieldTest(full_fn, (\"gas\", \"density\"), geometric=False)\n\n\n@skip(reason=\"See https://github.com/yt-project/yt/issues/3909\")\n@requires_module(\"h5py\")\n@requires_ds(enzotiny)\n@requires_file(os.path.join(ytdata_dir, \"DD0046_Profile1D.h5\"))\n@requires_file(os.path.join(ytdata_dir, \"DD0046_Profile2D.h5\"))\ndef test_old_profile_data():\n    tmpdir = tempfile.mkdtemp()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n    ds = data_dir_load(enzotiny)\n    ad = ds.all_data()\n    profile_1d = create_profile(\n        ad,\n        (\"gas\", \"density\"),\n        (\"gas\", \"temperature\"),\n        weight_field=(\"gas\", \"cell_mass\"),\n    )\n    fn = \"DD0046_Profile1D.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    prof_1d_ds = data_dir_load(full_fn)\n    compare_unit_attributes(ds, prof_1d_ds)\n    assert isinstance(prof_1d_ds, YTProfileDataset)\n\n    for field in profile_1d.standard_deviation:\n        assert_array_equal(\n            profile_1d.standard_deviation[field],\n            prof_1d_ds.profile.standard_deviation[\"data\", field[1]],\n        )\n\n    p1 = ProfilePlot(\n        prof_1d_ds.data,\n        (\"gas\", \"density\"),\n        (\"gas\", \"temperature\"),\n        weight_field=(\"gas\", \"cell_mass\"),\n    )\n    p1.save()\n\n    yield YTDataFieldTest(full_fn, (\"gas\", \"temperature\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"index\", \"x\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"gas\", \"density\"), geometric=False)\n    fn = \"DD0046_Profile2D.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    prof_2d_ds = data_dir_load(full_fn)\n    compare_unit_attributes(ds, prof_2d_ds)\n    assert isinstance(prof_2d_ds, YTProfileDataset)\n\n    p2 = PhasePlot(\n        prof_2d_ds.data,\n        (\"gas\", \"density\"),\n        (\"gas\", \"temperature\"),\n        (\"gas\", \"cell_mass\"),\n        weight_field=None,\n    )\n    p2.save()\n\n    yield YTDataFieldTest(full_fn, (\"gas\", \"density\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"index\", \"x\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"gas\", \"temperature\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"index\", \"y\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"gas\", \"cell_mass\"), geometric=False)\n    os.chdir(curdir)\n    shutil.rmtree(tmpdir)\n\n\n@skip(reason=\"See https://github.com/yt-project/yt/issues/3909\")\n@requires_module(\"h5py\")\n@requires_ds(enzotiny)\n@requires_file(os.path.join(ytdata_dir, \"test_data.h5\"))\n@requires_file(os.path.join(ytdata_dir, \"random_data.h5\"))\ndef test_old_nonspatial_data():\n    ds = data_dir_load(enzotiny)\n    region = ds.box([0.25] * 3, [0.75] * 3)\n    sphere = ds.sphere(ds.domain_center, (10, \"Mpc\"))\n    my_data = {}\n    my_data[\"region_density\"] = region[\"gas\", \"density\"]\n    my_data[\"sphere_density\"] = sphere[\"gas\", \"density\"]\n    fn = \"test_data.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    array_ds = data_dir_load(full_fn)\n    compare_unit_attributes(ds, array_ds)\n    assert isinstance(array_ds, YTNonspatialDataset)\n    yield YTDataFieldTest(full_fn, \"region_density\", geometric=False)\n    yield YTDataFieldTest(full_fn, \"sphere_density\", geometric=False)\n\n    my_data = {\"density\": YTArray(np.linspace(1.0, 20.0, 10), \"g/cm**3\")}\n    fn = \"random_data.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    new_ds = data_dir_load(full_fn)\n    assert isinstance(new_ds, YTNonspatialDataset)\n    yield YTDataFieldTest(full_fn, (\"gas\", \"density\"), geometric=False)\n"
  },
  {
    "path": "yt/frontends/ytdata/tests/test_outputs.py",
    "content": "import os\nimport shutil\nimport tempfile\n\nimport numpy as np\nfrom numpy.testing import assert_array_equal, assert_equal\n\nfrom yt.data_objects.api import create_profile\nfrom yt.frontends.ytdata.api import (\n    YTDataContainerDataset,\n    YTGridDataset,\n    YTNonspatialDataset,\n    YTProfileDataset,\n    YTSpatialPlotDataset,\n    save_as_dataset,\n)\nfrom yt.loaders import load\nfrom yt.testing import assert_allclose_units\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.answer_testing.framework import (\n    AnswerTestingTest,\n    data_dir_load,\n    requires_ds,\n)\nfrom yt.visualization.profile_plotter import PhasePlot, ProfilePlot\n\n\ndef make_tempdir():\n    if int(os.environ.get(\"GENERATE_YTDATA\", 0)):\n        return \".\"\n    else:\n        return tempfile.mkdtemp()\n\n\ndef compare_unit_attributes(ds1, ds2):\n    r\"\"\"\n    Checks to make sure that the length, mass, time, velocity, and\n    magnetic units are the same for two different dataset objects.\n    \"\"\"\n    attrs = (\"length_unit\", \"mass_unit\", \"time_unit\", \"velocity_unit\", \"magnetic_unit\")\n    for attr in attrs:\n        u1 = getattr(ds1, attr, None)\n        u2 = getattr(ds2, attr, None)\n        assert u1 == u2\n\n\nclass YTDataFieldTest(AnswerTestingTest):\n    _type_name = \"YTDataTest\"\n    _attrs = (\"field_name\",)\n\n    def __init__(self, ds_fn, field, decimals=10, geometric=True):\n        super().__init__(ds_fn)\n        self.field = field\n        if isinstance(field, tuple) and len(field) == 2:\n            self.field_name = field[1]\n        else:\n            self.field_name = field\n        self.decimals = decimals\n        self.geometric = geometric\n\n    def run(self):\n        if self.geometric:\n            obj = self.ds.all_data()\n        else:\n            obj = self.ds.data\n        num_e = obj[self.field].size\n        avg = obj[self.field].mean()\n        return np.array([num_e, avg])\n\n    def compare(self, new_result, old_result):\n        err_msg = f\"YTData field values for {self.field} not equal.\"\n        if self.decimals is None:\n            assert_equal(new_result, old_result, err_msg=err_msg, verbose=True)\n        else:\n            assert_allclose_units(\n                new_result,\n                old_result,\n                10.0 ** (-self.decimals),\n                err_msg=err_msg,\n                verbose=True,\n            )\n\n\nenzotiny = \"enzo_tiny_cosmology/DD0046/DD0046\"\n\n\n@requires_ds(enzotiny)\ndef test_datacontainer_data():\n    tmpdir = make_tempdir()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n    ds = data_dir_load(enzotiny)\n    sphere = ds.sphere(ds.domain_center, (10, \"Mpc\"))\n    fn = sphere.save_as_dataset(fields=[(\"gas\", \"density\"), (\"all\", \"particle_mass\")])\n    full_fn = os.path.join(tmpdir, fn)\n    sphere_ds = load(full_fn)\n    compare_unit_attributes(ds, sphere_ds)\n    assert isinstance(sphere_ds, YTDataContainerDataset)\n    yield YTDataFieldTest(full_fn, (\"grid\", \"density\"))\n    yield YTDataFieldTest(full_fn, (\"all\", \"particle_mass\"))\n    cr = ds.cut_region(sphere, ['obj[\"gas\", \"temperature\"] > 1e4'])\n    fn = cr.save_as_dataset(fields=[(\"gas\", \"temperature\")])\n    full_fn = os.path.join(tmpdir, fn)\n    cr_ds = load(full_fn)\n    assert isinstance(cr_ds, YTDataContainerDataset)\n    assert (cr[\"gas\", \"temperature\"] == cr_ds.data[\"gas\", \"temperature\"]).all()\n    os.chdir(curdir)\n    if tmpdir != \".\":\n        shutil.rmtree(tmpdir)\n\n\n@requires_ds(enzotiny)\ndef test_grid_datacontainer_data():\n    tmpdir = make_tempdir()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n    ds = data_dir_load(enzotiny)\n\n    cg = ds.covering_grid(level=0, left_edge=[0.25] * 3, dims=[16] * 3)\n    fn = cg.save_as_dataset(\n        fields=[\n            (\"gas\", \"density\"),\n            (\"all\", \"particle_mass\"),\n            (\"all\", \"particle_position\"),\n        ]\n    )\n    full_fn = os.path.join(tmpdir, fn)\n    cg_ds = load(full_fn)\n    compare_unit_attributes(ds, cg_ds)\n    assert isinstance(cg_ds, YTGridDataset)\n    assert (\n        cg[\"all\", \"particle_position\"].shape\n        == cg_ds.r[\"all\", \"particle_position\"].shape\n    )\n    yield YTDataFieldTest(full_fn, (\"grid\", \"density\"))\n    yield YTDataFieldTest(full_fn, (\"all\", \"particle_mass\"))\n\n    ag = ds.arbitrary_grid(left_edge=[0.25] * 3, right_edge=[0.75] * 3, dims=[16] * 3)\n    fn = ag.save_as_dataset(fields=[(\"gas\", \"density\"), (\"all\", \"particle_mass\")])\n    full_fn = os.path.join(tmpdir, fn)\n    ag_ds = load(full_fn)\n    compare_unit_attributes(ds, ag_ds)\n    assert isinstance(ag_ds, YTGridDataset)\n    yield YTDataFieldTest(full_fn, (\"grid\", \"density\"))\n    yield YTDataFieldTest(full_fn, (\"all\", \"particle_mass\"))\n\n    my_proj = ds.proj((\"gas\", \"density\"), \"x\", weight_field=(\"gas\", \"density\"))\n    frb = my_proj.to_frb(1.0, (800, 800))\n    fn = frb.save_as_dataset(fields=[(\"gas\", \"density\")])\n    frb_ds = load(fn)\n    assert_array_equal(frb[\"gas\", \"density\"], frb_ds.data[\"gas\", \"density\"])\n    compare_unit_attributes(ds, frb_ds)\n    assert isinstance(frb_ds, YTGridDataset)\n    yield YTDataFieldTest(full_fn, (\"grid\", \"density\"), geometric=False)\n    os.chdir(curdir)\n    if tmpdir != \".\":\n        shutil.rmtree(tmpdir)\n\n\n@requires_ds(enzotiny)\ndef test_spatial_data():\n    tmpdir = make_tempdir()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n    ds = data_dir_load(enzotiny)\n    proj = ds.proj((\"gas\", \"density\"), \"x\", weight_field=(\"gas\", \"density\"))\n    fn = proj.save_as_dataset()\n    full_fn = os.path.join(tmpdir, fn)\n    proj_ds = load(full_fn)\n    compare_unit_attributes(ds, proj_ds)\n    assert isinstance(proj_ds, YTSpatialPlotDataset)\n    yield YTDataFieldTest(full_fn, (\"grid\", \"density\"), geometric=False)\n    os.chdir(curdir)\n    if tmpdir != \".\":\n        shutil.rmtree(tmpdir)\n\n\n@requires_ds(enzotiny)\ndef test_profile_data():\n    tmpdir = make_tempdir()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n    ds = data_dir_load(enzotiny)\n    ad = ds.all_data()\n    profile_1d = create_profile(\n        ad,\n        (\"gas\", \"density\"),\n        (\"gas\", \"temperature\"),\n        weight_field=(\"gas\", \"cell_mass\"),\n    )\n    fn = profile_1d.save_as_dataset()\n    full_fn = os.path.join(tmpdir, fn)\n    prof_1d_ds = load(full_fn)\n    compare_unit_attributes(ds, prof_1d_ds)\n    assert isinstance(prof_1d_ds, YTProfileDataset)\n\n    for field in profile_1d.standard_deviation:\n        assert_array_equal(\n            profile_1d.standard_deviation[field],\n            prof_1d_ds.profile.standard_deviation[\"data\", field[1]],\n        )\n\n    p1 = ProfilePlot(\n        prof_1d_ds.data,\n        (\"gas\", \"density\"),\n        (\"gas\", \"temperature\"),\n        weight_field=(\"gas\", \"cell_mass\"),\n    )\n    p1.save()\n\n    yield YTDataFieldTest(full_fn, (\"data\", \"temperature\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"data\", \"x\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"data\", \"density\"), geometric=False)\n    profile_2d = create_profile(\n        ad,\n        [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        (\"gas\", \"cell_mass\"),\n        weight_field=None,\n        n_bins=(128, 128),\n    )\n    fn = profile_2d.save_as_dataset()\n    full_fn = os.path.join(tmpdir, fn)\n    prof_2d_ds = load(full_fn)\n    compare_unit_attributes(ds, prof_2d_ds)\n    assert isinstance(prof_2d_ds, YTProfileDataset)\n\n    p2 = PhasePlot(\n        prof_2d_ds.data,\n        (\"gas\", \"density\"),\n        (\"gas\", \"temperature\"),\n        (\"gas\", \"cell_mass\"),\n        weight_field=None,\n    )\n    p2.save()\n\n    yield YTDataFieldTest(full_fn, (\"data\", \"density\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"data\", \"x\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"data\", \"temperature\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"data\", \"y\"), geometric=False)\n    yield YTDataFieldTest(full_fn, (\"data\", \"cell_mass\"), geometric=False)\n    os.chdir(curdir)\n    if tmpdir != \".\":\n        shutil.rmtree(tmpdir)\n\n\n@requires_ds(enzotiny)\ndef test_nonspatial_data():\n    tmpdir = make_tempdir()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n    ds = data_dir_load(enzotiny)\n    region = ds.box([0.25] * 3, [0.75] * 3)\n    sphere = ds.sphere(ds.domain_center, (10, \"Mpc\"))\n    my_data = {}\n    my_data[\"region_density\"] = region[\"gas\", \"density\"]\n    my_data[\"sphere_density\"] = sphere[\"gas\", \"density\"]\n    fn = \"test_data.h5\"\n    save_as_dataset(ds, fn, my_data)\n    full_fn = os.path.join(tmpdir, fn)\n    array_ds = load(full_fn)\n    compare_unit_attributes(ds, array_ds)\n    assert isinstance(array_ds, YTNonspatialDataset)\n    yield YTDataFieldTest(full_fn, \"region_density\", geometric=False)\n    yield YTDataFieldTest(full_fn, \"sphere_density\", geometric=False)\n\n    my_data = {\"density\": YTArray(np.linspace(1.0, 20.0, 10), \"g/cm**3\")}\n    fake_ds = {\"current_time\": YTQuantity(10, \"Myr\")}\n    fn = \"random_data.h5\"\n    save_as_dataset(fake_ds, fn, my_data)\n    full_fn = os.path.join(tmpdir, fn)\n    new_ds = load(full_fn)\n    assert isinstance(new_ds, YTNonspatialDataset)\n    yield YTDataFieldTest(full_fn, (\"data\", \"density\"), geometric=False)\n    os.chdir(curdir)\n    if tmpdir != \".\":\n        shutil.rmtree(tmpdir)\n"
  },
  {
    "path": "yt/frontends/ytdata/tests/test_unit.py",
    "content": "import os\nimport shutil\nimport tempfile\n\nimport numpy as np\nfrom numpy.testing import assert_array_equal\n\nfrom yt.loaders import load, load_uniform_grid\nfrom yt.testing import (\n    assert_fname,\n    fake_random_ds,\n    requires_file,\n    requires_module,\n)\nfrom yt.utilities.answer_testing.framework import data_dir_load\nfrom yt.visualization.plot_window import ProjectionPlot, SlicePlot\n\nytdata_dir = \"ytdata_test\"\n\n\n@requires_module(\"h5py\")\n@requires_file(os.path.join(ytdata_dir, \"slice.h5\"))\n@requires_file(os.path.join(ytdata_dir, \"proj.h5\"))\n@requires_file(os.path.join(ytdata_dir, \"oas.h5\"))\ndef test_old_plot_data():\n    tmpdir = tempfile.mkdtemp()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n\n    fn = \"slice.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    ds_slice = data_dir_load(full_fn)\n    p = SlicePlot(ds_slice, \"z\", (\"gas\", \"density\"))\n    fn = p.save()\n    assert_fname(fn[0])\n\n    fn = \"proj.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    ds_proj = data_dir_load(full_fn)\n    p = ProjectionPlot(ds_proj, \"z\", (\"gas\", \"density\"))\n    fn = p.save()\n    assert_fname(fn[0])\n\n    fn = \"oas.h5\"\n    full_fn = os.path.join(ytdata_dir, fn)\n    ds_oas = data_dir_load(full_fn)\n    p = SlicePlot(ds_oas, [1, 1, 1], (\"gas\", \"density\"))\n    fn = p.save()\n    assert_fname(fn[0])\n\n    os.chdir(curdir)\n    shutil.rmtree(tmpdir)\n\n\n@requires_module(\"h5py\")\ndef test_plot_data():\n    tmpdir = tempfile.mkdtemp()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n    ds = fake_random_ds(16)\n\n    plot = SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    fn = plot.data_source.save_as_dataset(\"slice.h5\")\n    ds_slice = load(fn)\n    p = SlicePlot(ds_slice, \"z\", (\"gas\", \"density\"))\n    fn = p.save()\n    assert_fname(fn[0])\n\n    plot = ProjectionPlot(ds, \"z\", (\"gas\", \"density\"))\n    fn = plot.data_source.save_as_dataset(\"proj.h5\")\n    ds_proj = load(fn)\n    p = ProjectionPlot(ds_proj, \"z\", (\"gas\", \"density\"))\n    fn = p.save()\n    assert_fname(fn[0])\n\n    plot = SlicePlot(ds, [1, 1, 1], (\"gas\", \"density\"))\n    fn = plot.data_source.save_as_dataset(\"oas.h5\")\n    ds_oas = load(fn)\n    p = SlicePlot(ds_oas, [1, 1, 1], (\"gas\", \"density\"))\n    fn = p.save()\n    assert_fname(fn[0])\n\n    os.chdir(curdir)\n    if tmpdir != \".\":\n        shutil.rmtree(tmpdir)\n\n\n@requires_module(\"h5py\")\ndef test_non_square_frb():\n    tmpdir = tempfile.mkdtemp()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n\n    # construct an arbitrary dataset\n    arr = np.arange(8.0 * 9.0 * 10.0).reshape((8, 9, 10))\n    data = {\"density\": (arr, \"g/cm**3\")}\n    bbox = np.array([[-4, 4.0], [-4.5, 4.5], [-5.0, 5]])\n    ds = load_uniform_grid(\n        data, arr.shape, length_unit=\"Mpc\", bbox=bbox, periodicity=(False, False, False)\n    )\n\n    # make a slice\n    slc = ds.slice(axis=\"z\", coord=ds.quan(0.0, \"code_length\"))\n    # make a frb and save it to disk\n    center = (ds.quan(0.0, \"code_length\"), ds.quan(0.0, \"code_length\"))\n    xax, yax = ds.coordinates.x_axis[slc.axis], ds.coordinates.y_axis[slc.axis]\n    res = [ds.domain_dimensions[xax], ds.domain_dimensions[yax]]  # = [8,9]\n    width = ds.domain_right_edge[xax] - ds.domain_left_edge[xax]  # = 8 code_length\n    height = ds.domain_right_edge[yax] - ds.domain_left_edge[yax]  # = 9 code_length\n    frb = slc.to_frb(width=width, height=height, resolution=res, center=center)\n    fname = \"test_frb_roundtrip.h5\"\n    frb.save_as_dataset(fname, fields=[(\"gas\", \"density\")])\n\n    expected_vals = arr[:, :, 5].T\n    print(\n        \"\\nConfirmation that initial frb results are expected:\",\n        (expected_vals == frb[\"gas\", \"density\"].v).all(),\n        \"\\n\",\n    )\n\n    # yt-reload:\n    reloaded_ds = load(fname)\n\n    assert_array_equal(\n        frb[\"gas\", \"density\"].shape, reloaded_ds.data[\"gas\", \"density\"].shape\n    )\n    assert_array_equal(frb[\"gas\", \"density\"], reloaded_ds.data[\"gas\", \"density\"])\n\n    os.chdir(curdir)\n    if tmpdir != \".\":\n        shutil.rmtree(tmpdir)\n"
  },
  {
    "path": "yt/frontends/ytdata/utilities.py",
    "content": "from yt.units.yt_array import YTArray\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\ndef save_as_dataset(ds, filename, data, field_types=None, extra_attrs=None):\n    r\"\"\"Export a set of field arrays to a reloadable yt dataset.\n\n    This function can be used to create a yt loadable dataset from a\n    set of arrays.  The field arrays can either be associated with a\n    loaded dataset or, if not, a dictionary of dataset attributes can\n    be provided that will be used as metadata for the new dataset.  The\n    resulting dataset can be reloaded as a yt dataset.\n\n    Parameters\n    ----------\n    ds : dataset or dict\n        The dataset associated with the fields or a dictionary of\n        parameters.\n    filename : str\n        The name of the file to be written.\n    data : dict\n        A dictionary of field arrays to be saved.\n    field_types: dict, optional\n        A dictionary denoting the group name to which each field is to\n        be saved. When the resulting dataset is reloaded, this will be\n        the field type for this field. If not given, \"data\" will be\n        used.\n    extra_attrs: dict, optional\n        A dictionary of additional attributes to be saved.\n\n    Returns\n    -------\n    filename : str\n        The name of the file that has been created.\n\n    Examples\n    --------\n\n    >>> import numpy as np\n    >>> import yt\n    >>> ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n    >>> sphere = ds.sphere([0.5] * 3, (10, \"Mpc\"))\n    >>> sphere_density = sphere[\"gas\", \"density\"]\n    >>> region = ds.box([0.0] * 3, [0.25] * 3)\n    >>> region_density = region[\"gas\", \"density\"]\n    >>> data = {}\n    >>> data[\"sphere_density\"] = sphere_density\n    >>> data[\"region_density\"] = region_density\n    >>> yt.save_as_dataset(ds, \"density_data.h5\", data)\n    >>> new_ds = yt.load(\"density_data.h5\")\n    >>> print(new_ds.data[\"region_density\"])\n    [  7.47650434e-32   7.70370740e-32   9.74692941e-32 ...,   1.22384547e-27\n       5.13889063e-28   2.91811974e-28] g/cm**3\n    >>> print(new_ds.data[\"sphere_density\"])\n    [  4.46237613e-32   4.86830178e-32   4.46335118e-32 ...,   6.43956165e-30\n       3.57339907e-30   2.83150720e-30] g/cm**3\n    >>> data = {\n    ...     \"density\": yt.YTArray(1e-24 * np.ones(10), \"g/cm**3\"),\n    ...     \"temperature\": yt.YTArray(1000.0 * np.ones(10), \"K\"),\n    ... }\n    >>> ds_data = {\"current_time\": yt.YTQuantity(10, \"Myr\")}\n    >>> yt.save_as_dataset(ds_data, \"random_data.h5\", data)\n    >>> new_ds = yt.load(\"random_data.h5\")\n    >>> print(new_ds.data[\"gas\", \"temperature\"])\n    [ 1000.  1000.  1000.  1000.  1000.  1000.  1000.  1000.  1000.  1000.] K\n\n    \"\"\"\n\n    mylog.info(\"Saving field data to yt dataset: %s.\", filename)\n\n    if extra_attrs is None:\n        extra_attrs = {}\n    base_attrs = [\n        \"dimensionality\",\n        \"domain_left_edge\",\n        \"domain_right_edge\",\n        \"current_redshift\",\n        \"current_time\",\n        \"domain_dimensions\",\n        \"geometry\",\n        \"periodicity\",\n        \"cosmological_simulation\",\n        \"omega_lambda\",\n        \"omega_matter\",\n        \"hubble_constant\",\n        \"length_unit\",\n        \"mass_unit\",\n        \"time_unit\",\n        \"velocity_unit\",\n        \"magnetic_unit\",\n    ]\n\n    fh = h5py.File(filename, mode=\"w\")\n    if ds is None:\n        ds = {}\n\n    if hasattr(ds, \"parameters\") and isinstance(ds.parameters, dict):\n        for attr, val in ds.parameters.items():\n            _yt_array_hdf5_attr(fh, attr, val)\n\n    if hasattr(ds, \"unit_registry\"):\n        _yt_array_hdf5_attr(fh, \"unit_registry_json\", ds.unit_registry.to_json())\n\n    if hasattr(ds, \"unit_system\"):\n        # Note: ds._unit_system_name is written here rather than\n        # ds.unit_system.name because for a 'code' unit system, ds.unit_system.name\n        # is a hash, not a unit system. And on re-load, we want to designate\n        # a unit system not a hash value.\n        # See https://github.com/yt-project/yt/issues/4315 for more background.\n        _yt_array_hdf5_attr(fh, \"unit_system_name\", ds._unit_system_name)\n\n    for attr in base_attrs:\n        if isinstance(ds, dict):\n            my_val = ds.get(attr, None)\n        else:\n            my_val = getattr(ds, attr, None)\n        if my_val is None:\n            continue\n        _yt_array_hdf5_attr(fh, attr, my_val)\n\n    for attr in extra_attrs:\n        my_val = extra_attrs[attr]\n        _yt_array_hdf5_attr(fh, attr, my_val)\n    if \"data_type\" not in extra_attrs:\n        fh.attrs[\"data_type\"] = \"yt_array_data\"\n\n    for field in data:\n        if field_types is None:\n            field_type = \"data\"\n        else:\n            field_type = field_types[field]\n        if field_type not in fh:\n            fh.create_group(field_type)\n\n        if isinstance(field, tuple):\n            field_name = field[1]\n        else:\n            field_name = field\n\n        # for python3\n        if data[field].dtype.kind == \"U\":\n            data[field] = data[field].astype(\"|S\")\n\n        _yt_array_hdf5(fh[field_type], field_name, data[field])\n        if \"num_elements\" not in fh[field_type].attrs:\n            fh[field_type].attrs[\"num_elements\"] = data[field].size\n    fh.close()\n    return filename\n\n\ndef _hdf5_yt_array(fh, field, ds=None):\n    r\"\"\"Load an hdf5 dataset as a YTArray.\n\n    Reads in a dataset from an open hdf5 file or group and uses the\n    \"units\" attribute, if it exists, to apply units.\n\n    Parameters\n    ----------\n    fh : an open hdf5 file or hdf5 group\n        The hdf5 file or group in which the dataset exists.\n    field : str\n        The name of the field to be loaded.\n    ds : yt Dataset\n        If not None, the unit_registry of the dataset\n        is used to apply units.\n\n    Returns\n    -------\n    A YTArray of the requested field.\n\n    \"\"\"\n\n    if ds is None:\n        new_arr = YTArray\n    else:\n        new_arr = ds.arr\n    units = \"\"\n    if \"units\" in fh[field].attrs:\n        units = fh[field].attrs[\"units\"]\n    if units == \"dimensionless\":\n        units = \"\"\n    return new_arr(fh[field][()], units)\n\n\ndef _yt_array_hdf5(fh, field, data):\n    r\"\"\"Save a YTArray to an open hdf5 file or group.\n\n    Save a YTArray to an open hdf5 file or group, and save the\n    units to a \"units\" attribute.\n\n    Parameters\n    ----------\n    fh : an open hdf5 file or hdf5 group\n        The hdf5 file or group to which the data will be written.\n    field : str\n        The name of the field to be saved.\n    data : YTArray\n        The data array to be saved.\n\n    Returns\n    -------\n    dataset : hdf5 dataset\n        The created hdf5 dataset.\n\n    \"\"\"\n\n    dataset = fh.create_dataset(str(field), data=data)\n    units = \"\"\n    if isinstance(data, YTArray):\n        units = str(data.units)\n    dataset.attrs[\"units\"] = units\n    return dataset\n\n\ndef _yt_array_hdf5_attr(fh, attr, val):\n    r\"\"\"Save a YTArray or YTQuantity as an hdf5 attribute.\n\n    Save an hdf5 attribute.  If it has units, save an\n    additional attribute with the units.\n\n    Parameters\n    ----------\n    fh : an open hdf5 file, group, or dataset\n        The hdf5 file, group, or dataset to which the\n        attribute will be written.\n    attr : str\n        The name of the attribute to be saved.\n    val : anything\n        The value to be saved.\n\n    \"\"\"\n\n    if val is None:\n        val = \"None\"\n    if hasattr(val, \"units\"):\n        fh.attrs[f\"{attr}_units\"] = str(val.units)\n    try:\n        fh.attrs[str(attr)] = val\n    # This is raised if no HDF5 equivalent exists.\n    # In that case, save its string representation.\n    except TypeError:\n        fh.attrs[str(attr)] = str(val)\n"
  },
  {
    "path": "yt/funcs.py",
    "content": "import base64\nimport contextlib\nimport copy\nimport errno\nimport glob\nimport inspect\nimport itertools\nimport os\nimport re\nimport struct\nimport subprocess\nimport sys\nimport time\nimport traceback\nfrom collections import UserDict\nfrom collections.abc import Callable\nfrom copy import deepcopy\nfrom functools import lru_cache, wraps\nfrom numbers import Number as numeric_type\nfrom typing import Any\n\nimport numpy as np\nfrom more_itertools import always_iterable, collapse, first\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._maintenance.ipython_compat import IS_IPYTHON\nfrom yt.config import ytcfg\nfrom yt.units import YTArray, YTQuantity\nfrom yt.utilities.exceptions import YTFieldNotFound, YTInvalidWidthError\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _requests as requests\n\n# Some functions for handling sequences and other types\n\n\ndef is_sequence(obj):\n    \"\"\"\n    Grabbed from Python Cookbook / matplotlib.cbook.  Returns true/false for\n\n    Parameters\n    ----------\n    obj : iterable\n    \"\"\"\n    try:\n        len(obj)\n        return True\n    except TypeError:\n        return False\n\n\ndef iter_fields(field_or_fields):\n    \"\"\"\n    Create an iterator for field names, specified as single strings or tuples(fname,\n    ftype) alike.\n    This can safely be used in places where we accept a single field or a list as input.\n\n    Parameters\n    ----------\n    field_or_fields: str, tuple(str, str), or any iterable of the previous types.\n\n    Examples\n    --------\n\n    >>> fields = (\"gas\", \"density\")\n    >>> for field in iter_fields(fields):\n    ...     print(field)\n    density\n\n    >>> fields = (\"gas\", \"density\")\n    >>> for field in iter_fields(fields):\n    ...     print(field)\n    ('gas', 'density')\n\n    >>> fields = [(\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"index\", \"dx\")]\n    >>> for field in iter_fields(fields):\n    ...     print(field)\n    density\n    temperature\n    ('index', 'dx')\n    \"\"\"\n    return always_iterable(field_or_fields, base_type=(tuple, str, bytes))\n\n\ndef ensure_numpy_array(obj):\n    \"\"\"\n    This function ensures that *obj* is a numpy array. Typically used to\n    convert scalar, list or tuple argument passed to functions using Cython.\n    \"\"\"\n    if isinstance(obj, np.ndarray):\n        if obj.shape == ():\n            return np.array([obj])\n        # We cast to ndarray to catch ndarray subclasses\n        return np.array(obj)\n    elif isinstance(obj, (list, tuple)):\n        return np.asarray(obj)\n    else:\n        return np.asarray([obj])\n\n\ndef read_struct(f, fmt):\n    \"\"\"\n    This reads a struct, and only that struct, from an open file.\n    \"\"\"\n    s = f.read(struct.calcsize(fmt))\n    return struct.unpack(fmt, s)\n\n\ndef just_one(obj):\n    # If we have an iterable, sometimes we only want one item\n    return first(collapse(obj))\n\n\ndef compare_dicts(dict1, dict2):\n    if not set(dict1) <= set(dict2):\n        return False\n    for key in dict1.keys():\n        if dict1[key] is not None and dict2[key] is not None:\n            if isinstance(dict1[key], dict):\n                if compare_dicts(dict1[key], dict2[key]):\n                    continue\n                else:\n                    return False\n            try:\n                comparison = np.array_equal(dict1[key], dict2[key])\n            except TypeError:\n                comparison = dict1[key] == dict2[key]\n            if not comparison:\n                return False\n    return True\n\n\n# Taken from\n# http://www.goldb.org/goldblog/2008/02/06/PythonConvertSecsIntoHumanReadableTimeStringHHMMSS.aspx\ndef humanize_time(secs):\n    \"\"\"\n    Takes *secs* and returns a nicely formatted string\n    \"\"\"\n    mins, secs = divmod(secs, 60)\n    hours, mins = divmod(mins, 60)\n    return \":\".join(f\"{int(t):02}\" for t in (hours, mins, secs))\n\n\n#\n# Some function wrappers that come in handy once in a while\n#\n\n\ndef get_memory_usage(subtract_share=False):\n    \"\"\"\n    Returning resident size in megabytes\n    \"\"\"\n    pid = os.getpid()\n    # we use the resource module to get the memory page size\n\n    try:\n        import resource\n    except ImportError:\n        return -1024\n    else:\n        pagesize = resource.getpagesize()\n    status_file = f\"/proc/{pid}/statm\"\n    if not os.path.isfile(status_file):\n        return -1024\n    with open(status_file) as fh:\n        line = fh.read()\n    size, resident, share, text, library, data, dt = (int(i) for i in line.split())\n    if subtract_share:\n        resident -= share\n    return resident * pagesize / (1024 * 1024)  # return in megs\n\n\ndef time_execution(func):\n    r\"\"\"\n    Decorator for seeing how long a given function takes, depending on whether\n    or not the global 'yt.time_functions' config parameter is set.\n    \"\"\"\n\n    @wraps(func)\n    def wrapper(*arg, **kw):\n        t1 = time.time()\n        res = func(*arg, **kw)\n        t2 = time.time()\n        mylog.debug(\"%s took %0.3f s\", func.__name__, (t2 - t1))\n        return res\n\n    if ytcfg.get(\"yt\", \"time_functions\"):\n        return wrapper\n    else:\n        return func\n\n\ndef print_tb(func):\n    \"\"\"\n    This function is used as a decorate on a function to have the calling stack\n    printed whenever that function is entered.\n\n    This can be used like so:\n\n    >>> @print_tb\n    ... def some_deeply_nested_function(*args, **kwargs):\n    ...     ...\n\n    \"\"\"\n\n    @wraps(func)\n    def run_func(*args, **kwargs):\n        traceback.print_stack()\n        return func(*args, **kwargs)\n\n    return run_func\n\n\ndef rootonly(func):\n    \"\"\"\n    This is a decorator that, when used, will only call the function on the\n    root processor.\n\n    This can be used like so:\n\n    .. code-block:: python\n\n       @rootonly\n       def some_root_only_function(*args, **kwargs): ...\n    \"\"\"\n\n    @wraps(func)\n    def check_parallel_rank(*args, **kwargs):\n        if ytcfg.get(\"yt\", \"internals\", \"topcomm_parallel_rank\") > 0:\n            return\n        return func(*args, **kwargs)\n\n    return check_parallel_rank\n\n\ndef pdb_run(func):\n    \"\"\"\n    This decorator inserts a pdb session on top of the call-stack into a\n    function.\n\n    This can be used like so:\n\n    >>> @pdb_run\n    ... def some_function_to_debug(*args, **kwargs):\n    ...     ...\n\n    \"\"\"\n    import pdb\n\n    @wraps(func)\n    def wrapper(*args, **kw):\n        pdb.runcall(func, *args, **kw)\n\n    return wrapper\n\n\n__header = \"\"\"\n== Welcome to the embedded IPython Shell ==\n\n   You are currently inside the function:\n     %(fname)s\n\n   Defined in:\n     %(filename)s:%(lineno)s\n\"\"\"\n\n\ndef insert_ipython(num_up=1):\n    \"\"\"\n    Placed inside a function, this will insert an IPython interpreter at that\n    current location.  This will enabled detailed inspection of the current\n    execution environment, as well as (optional) modification of that environment.\n    *num_up* refers to how many frames of the stack get stripped off, and\n    defaults to 1 so that this function itself is stripped off.\n    \"\"\"\n    import IPython\n    from IPython.terminal.embed import InteractiveShellEmbed\n    from traitlets.config.loader import Config\n\n    frame = inspect.stack()[num_up]\n    loc = frame[0].f_locals.copy()\n    glo = frame[0].f_globals\n    dd = {\"fname\": frame[3], \"filename\": frame[1], \"lineno\": frame[2]}\n    cfg = Config()\n    cfg.InteractiveShellEmbed.local_ns = loc\n    cfg.InteractiveShellEmbed.global_ns = glo\n    IPython.embed(config=cfg, banner2=__header % dd)\n    ipshell = InteractiveShellEmbed(config=cfg)\n\n    del ipshell\n\n\n#\n# Our progress bar types and how to get one\n#\n\n\nclass TqdmProgressBar:\n    # This is a drop in replacement for pbar\n    # called tqdm\n    def __init__(self, title, maxval):\n        from tqdm import tqdm\n\n        self._pbar = tqdm(leave=True, total=maxval, desc=title)\n        self.i = 0\n\n    def update(self, i=None):\n        if i is None:\n            i = self.i + 1\n        n = i - self.i\n        self.i = i\n        self._pbar.update(n)\n\n    def finish(self):\n        self._pbar.close()\n\n\nclass DummyProgressBar:\n    # This progressbar gets handed if we don't\n    # want ANY output\n    def __init__(self, *args, **kwargs):\n        return\n\n    def update(self, *args, **kwargs):\n        return\n\n    def finish(self, *args, **kwargs):\n        return\n\n\ndef get_pbar(title, maxval):\n    \"\"\"\n    This returns a progressbar of the most appropriate type, given a *title*\n    and a *maxval*.\n    \"\"\"\n    maxval = max(maxval, 1)\n\n    if (\n        ytcfg.get(\"yt\", \"suppress_stream_logging\")\n        or ytcfg.get(\"yt\", \"internals\", \"within_testing\")\n        or maxval == 1\n        or not is_root()\n    ):\n        return DummyProgressBar()\n    return TqdmProgressBar(title, maxval)\n\n\ndef only_on_root(func, *args, **kwargs):\n    \"\"\"\n    This function accepts a *func*, a set of *args* and *kwargs* and then only\n    on the root processor calls the function.  All other processors get \"None\"\n    handed back.\n    \"\"\"\n    if kwargs.pop(\"global_rootonly\", False):\n        cfg_option = \"global_parallel_rank\"\n    else:\n        cfg_option = \"topcomm_parallel_rank\"\n    if not ytcfg.get(\"yt\", \"internals\", \"parallel\"):\n        return func(*args, **kwargs)\n    if ytcfg.get(\"yt\", \"internals\", cfg_option) > 0:\n        return\n    return func(*args, **kwargs)\n\n\ndef is_root():\n    \"\"\"\n    This function returns True if it is on the root processor of the\n    topcomm and False otherwise.\n    \"\"\"\n    if not ytcfg.get(\"yt\", \"internals\", \"parallel\"):\n        return True\n    return ytcfg.get(\"yt\", \"internals\", \"topcomm_parallel_rank\") == 0\n\n\n#\n# Our signal and traceback handling functions\n#\n\n\ndef signal_print_traceback(signo, frame):\n    print(traceback.print_stack(frame))\n\n\ndef signal_problem(signo, frame):\n    raise RuntimeError()\n\n\ndef signal_ipython(signo, frame):\n    insert_ipython(2)\n\n\ndef paste_traceback(exc_type, exc, tb):\n    \"\"\"\n    This is a traceback handler that knows how to paste to the pastebin.\n    Should only be used in sys.excepthook.\n    \"\"\"\n    sys.__excepthook__(exc_type, exc, tb)\n    import xmlrpc.client\n    from io import StringIO\n\n    p = xmlrpc.client.ServerProxy(\n        \"http://paste.yt-project.org/xmlrpc/\", allow_none=True\n    )\n    s = StringIO()\n    traceback.print_exception(exc_type, exc, tb, file=s)\n    s = s.getvalue()\n    ret = p.pastes.newPaste(\"pytb\", s, None, \"\", \"\", True)\n    print()\n    print(f\"Traceback pasted to http://paste.yt-project.org/show/{ret}\")\n    print()\n\n\ndef paste_traceback_detailed(exc_type, exc, tb):\n    \"\"\"\n    This is a traceback handler that knows how to paste to the pastebin.\n    Should only be used in sys.excepthook.\n    \"\"\"\n    import cgitb\n    import xmlrpc.client\n    from io import StringIO\n\n    s = StringIO()\n    handler = cgitb.Hook(format=\"text\", file=s)\n    handler(exc_type, exc, tb)\n    s = s.getvalue()\n    print(s)\n    p = xmlrpc.client.ServerProxy(\n        \"http://paste.yt-project.org/xmlrpc/\", allow_none=True\n    )\n    ret = p.pastes.newPaste(\"text\", s, None, \"\", \"\", True)\n    print()\n    print(f\"Traceback pasted to http://paste.yt-project.org/show/{ret}\")\n    print()\n\n\n_ss = \"fURbBUUBE0cLXgETJnZgJRMXVhVGUQpQAUBuehQMUhJWRFFRAV1ERAtBXw1dAxMLXT4zXBFfABNN\\nC0ZEXw1YUURHCxMXVlFERwxWCQw=\\n\"\n\n\ndef _rdbeta(key):\n    enc_s = base64.decodestring(_ss)\n    dec_s = \"\".join(chr(ord(a) ^ ord(b)) for a, b in zip(enc_s, itertools.cycle(key)))\n    print(dec_s)\n\n\n#\n# Some exceptions\n#\n\n\nclass NoCUDAException(Exception):\n    pass\n\n\nclass YTEmptyClass:\n    pass\n\n\ndef update_git(path):\n    try:\n        import git\n    except ImportError:\n        print(\"Updating and precise version information requires \")\n        print(\"gitpython to be installed.\")\n        print(\"Try: python -m pip install gitpython\")\n        return -1\n    with open(os.path.join(path, \"yt_updater.log\"), \"a\") as f:\n        repo = git.Repo(path)\n        if repo.is_dirty(untracked_files=True):\n            print(\"Changes have been made to the yt source code so I won't \")\n            print(\"update the code. You will have to do this yourself.\")\n            print(\"Here's a set of sample commands:\")\n            print(\"\")\n            print(f\"    $ cd {path}\")\n            print(\"    $ git stash\")\n            print(\"    $ git checkout main\")\n            print(\"    $ git pull\")\n            print(\"    $ git stash pop\")\n            print(f\"    $ {sys.executable} setup.py develop\")\n            print(\"\")\n            return 1\n        if repo.active_branch.name != \"main\":\n            print(\"yt repository is not tracking the main branch so I won't \")\n            print(\"update the code. You will have to do this yourself.\")\n            print(\"Here's a set of sample commands:\")\n            print(\"\")\n            print(f\"    $ cd {path}\")\n            print(\"    $ git checkout main\")\n            print(\"    $ git pull\")\n            print(f\"    $ {sys.executable} setup.py develop\")\n            print(\"\")\n            return 1\n        print(\"Updating the repository\")\n        f.write(\"Updating the repository\\n\\n\")\n        old_version = repo.git.rev_parse(\"HEAD\", short=12)\n        try:\n            remote = repo.remotes.yt_upstream\n        except AttributeError:\n            remote = repo.create_remote(\n                \"yt_upstream\", url=\"https://github.com/yt-project/yt\"\n            )\n            remote.fetch()\n        main = repo.heads.main\n        main.set_tracking_branch(remote.refs.main)\n        main.checkout()\n        remote.pull()\n        new_version = repo.git.rev_parse(\"HEAD\", short=12)\n        f.write(f\"Updated from {old_version} to {new_version}\\n\\n\")\n        rebuild_modules(path, f)\n    print(\"Updated successfully\")\n\n\ndef rebuild_modules(path, f):\n    f.write(\"Rebuilding modules\\n\\n\")\n    p = subprocess.Popen(\n        [sys.executable, \"setup.py\", \"build_clib\", \"build_ext\", \"-i\"],\n        cwd=path,\n        stdout=subprocess.PIPE,\n        stderr=subprocess.STDOUT,\n    )\n    stdout, stderr = p.communicate()\n    f.write(stdout.decode(\"utf-8\"))\n    f.write(\"\\n\\n\")\n    if p.returncode:\n        print(f\"BROKEN: See {os.path.join(path, 'yt_updater.log')}\")\n        sys.exit(1)\n    f.write(\"Successful!\\n\")\n\n\ndef get_git_version(path):\n    try:\n        import git\n    except ImportError:\n        print(\"Updating and precise version information requires \")\n        print(\"gitpython to be installed.\")\n        print(\"Try: python -m pip install gitpython\")\n        return None\n    try:\n        repo = git.Repo(path)\n        return repo.git.rev_parse(\"HEAD\", short=12)\n    except git.InvalidGitRepositoryError:\n        # path is not a git repository\n        return None\n\n\ndef get_yt_version():\n    import importlib.resources as importlib_resources\n\n    version = get_git_version(os.path.dirname(importlib_resources.files(\"yt\")))\n    if version is None:\n        return version\n    else:\n        v_str = version[:12].strip()\n        if hasattr(v_str, \"decode\"):\n            v_str = v_str.decode(\"utf-8\")\n        return v_str\n\n\ndef get_version_stack():\n    import matplotlib\n\n    from yt._version import __version__ as yt_version\n\n    version_info = {}\n    version_info[\"yt\"] = yt_version\n    version_info[\"numpy\"] = np.version.version\n    version_info[\"matplotlib\"] = matplotlib.__version__\n    return version_info\n\n\ndef get_script_contents():\n    top_frame = inspect.stack()[-1]\n    finfo = inspect.getframeinfo(top_frame[0])\n    if finfo[2] != \"<module>\":\n        return None\n    if not os.path.exists(finfo[0]):\n        return None\n    try:\n        contents = open(finfo[0]).read()\n    except Exception:\n        contents = None\n    return contents\n\n\ndef download_file(url, filename):\n    try:\n        return fancy_download_file(url, filename, requests)\n    except ImportError:\n        # fancy_download_file requires requests\n        return simple_download_file(url, filename)\n\n\ndef fancy_download_file(url, filename, requests=None):\n    response = requests.get(url, stream=True)\n    total_length = response.headers.get(\"content-length\")\n\n    with open(filename, \"wb\") as fh:\n        if total_length is None:\n            fh.write(response.content)\n        else:\n            blocksize = 4 * 1024**2\n            iterations = int(float(total_length) / float(blocksize))\n\n            pbar = get_pbar(\n                \"Downloading {} to {} \".format(*os.path.split(filename)[::-1]),\n                iterations,\n            )\n            iteration = 0\n            for chunk in response.iter_content(chunk_size=blocksize):\n                fh.write(chunk)\n                iteration += 1\n                pbar.update(iteration)\n            pbar.finish()\n    return filename\n\n\ndef simple_download_file(url, filename):\n    import urllib.error\n    import urllib.request\n\n    try:\n        fn, h = urllib.request.urlretrieve(url, filename)\n    except urllib.error.HTTPError as err:\n        raise RuntimeError(\n            f\"Attempt to download file from {url} failed with error {err.code}: {err.msg}.\"\n        ) from None\n\n    return fn\n\n\n# This code snippet is modified from Georg Brandl\ndef bb_apicall(endpoint, data, use_pass=True):\n    import getpass\n    import urllib.parse\n    import urllib.request\n\n    uri = f\"https://api.bitbucket.org/1.0/{endpoint}/\"\n    # since bitbucket doesn't return the required WWW-Authenticate header when\n    # making a request without Authorization, we cannot use the standard urllib2\n    # auth handlers; we have to add the requisite header from the start\n    if data is not None:\n        data = urllib.parse.urlencode(data)\n    req = urllib.request.Request(uri, data)\n    if use_pass:\n        username = input(\"Bitbucket Username? \")\n        password = getpass.getpass()\n        upw = f\"{username}:{password}\"\n        req.add_header(\"Authorization\", f\"Basic {base64.b64encode(upw).strip()}\")\n    return urllib.request.urlopen(req).read()\n\n\ndef fix_length(length, ds):\n    registry = ds.unit_registry\n    if isinstance(length, YTArray):\n        if registry is not None:\n            length.units.registry = registry\n        return length.in_units(\"code_length\")\n    if isinstance(length, numeric_type):\n        return YTArray(length, \"code_length\", registry=registry)\n    length_valid_tuple = isinstance(length, (list, tuple)) and len(length) == 2\n    unit_is_string = isinstance(length[1], str)\n    length_is_number = isinstance(length[0], numeric_type) and not isinstance(\n        length[0], YTArray\n    )\n    if length_valid_tuple and unit_is_string and length_is_number:\n        return YTArray(*length, registry=registry)\n    else:\n        raise RuntimeError(f\"Length {str(length)} is invalid\")\n\n\n@contextlib.contextmanager\ndef parallel_profile(prefix):\n    r\"\"\"A context manager for profiling parallel code execution using cProfile\n\n    This is a simple context manager that automatically profiles the execution\n    of a snippet of code.\n\n    Parameters\n    ----------\n    prefix : string\n        A string name to prefix outputs with.\n\n    Examples\n    --------\n\n    >>> from yt import PhasePlot\n    >>> from yt.testing import fake_random_ds\n    >>> fields = (\"density\", \"temperature\", \"cell_mass\")\n    >>> units = (\"g/cm**3\", \"K\", \"g\")\n    >>> ds = fake_random_ds(16, fields=fields, units=units)\n    >>> with parallel_profile(\"my_profile\"):\n    ...     plot = PhasePlot(ds.all_data(), *fields)\n    \"\"\"\n    import cProfile\n\n    topcomm_parallel_size = ytcfg.get(\"yt\", \"internals\", \"topcomm_parallel_size\")\n    topcomm_parallel_rank = ytcfg.get(\"yt\", \"internals\", \"topcomm_parallel_rank\")\n    fn = f\"{prefix}_{topcomm_parallel_size:04}_{topcomm_parallel_rank}.cprof\"\n    p = cProfile.Profile()\n    p.enable()\n    yield fn\n    p.disable()\n    p.dump_stats(fn)\n\n\ndef get_num_threads():\n    from .config import ytcfg\n\n    nt = ytcfg.get(\"yt\", \"num_threads\")\n    if nt < 0:\n        return os.environ.get(\"OMP_NUM_THREADS\", 0)\n    return nt\n\n\ndef fix_axis(axis, ds):\n    return ds.coordinates.axis_id.get(axis, axis)\n\n\ndef get_output_filename(name, keyword, suffix):\n    r\"\"\"Return an appropriate filename for output.\n\n    With a name provided by the user, this will decide how to appropriately name the\n    output file by the following rules:\n\n    1. if name is None, the filename will be the keyword plus the suffix.\n    2. if name ends with \"/\" (resp \"\\\" on Windows), assume name is a directory and the\n       file will be named name/(keyword+suffix).  If the directory does not exist, first\n       try to create it and raise an exception if an error occurs.\n    3. if name does not end in the suffix, add the suffix.\n\n    Parameters\n    ----------\n    name : str\n        A filename given by the user.\n    keyword : str\n        A default filename prefix if name is None.\n    suffix : str\n        Suffix that must appear at end of the filename.\n        This will be added if not present.\n\n    Examples\n    --------\n\n    >>> get_output_filename(None, \"Projection_x\", \".png\")\n    'Projection_x.png'\n    >>> get_output_filename(\"my_file\", \"Projection_x\", \".png\")\n    'my_file.png'\n    >>> get_output_filename(\"my_dir/\", \"Projection_x\", \".png\")\n    'my_dir/Projection_x.png'\n\n    \"\"\"\n    if name is None:\n        name = keyword\n    name = os.path.expanduser(name)\n    if name.endswith(os.sep) and not os.path.isdir(name):\n        ensure_dir(name)\n    if os.path.isdir(name):\n        name = os.path.join(name, keyword)\n    if not name.endswith(suffix):\n        name += suffix\n    return name\n\n\ndef ensure_dir_exists(path):\n    r\"\"\"Create all directories in path recursively in a parallel safe manner\"\"\"\n    my_dir = os.path.dirname(path)\n    # If path is a file in the current directory, like \"test.txt\", then my_dir\n    # would be an empty string, resulting in FileNotFoundError when passed to\n    # ensure_dir. Let's avoid that.\n    if my_dir:\n        ensure_dir(my_dir)\n\n\ndef ensure_dir(path):\n    r\"\"\"Parallel safe directory maker.\"\"\"\n    if os.path.exists(path):\n        return path\n\n    try:\n        os.makedirs(path)\n    except OSError as e:\n        if e.errno == errno.EEXIST:\n            pass\n        else:\n            raise\n    return path\n\n\ndef validate_width_tuple(width):\n    if not is_sequence(width) or len(width) != 2:\n        raise YTInvalidWidthError(f\"width ({width}) is not a two element tuple\")\n    is_numeric = isinstance(width[0], numeric_type)\n    length_has_units = isinstance(width[0], YTArray)\n    unit_is_string = isinstance(width[1], str)\n    if not is_numeric or length_has_units and unit_is_string:\n        msg = f\"width ({str(width)}) is invalid. \"\n        msg += \"Valid widths look like this: (12, 'au')\"\n        raise YTInvalidWidthError(msg)\n\n\n_first_cap_re = re.compile(\"(.)([A-Z][a-z]+)\")\n_all_cap_re = re.compile(\"([a-z0-9])([A-Z])\")\n\n\n@lru_cache(maxsize=128, typed=False)\ndef camelcase_to_underscore(name):\n    s1 = _first_cap_re.sub(r\"\\1_\\2\", name)\n    return _all_cap_re.sub(r\"\\1_\\2\", s1).lower()\n\n\ndef set_intersection(some_list):\n    if len(some_list) == 0:\n        return set()\n    # This accepts a list of iterables, which we get the intersection of.\n    s = set(some_list[0])\n    for l in some_list[1:]:\n        s.intersection_update(l)\n    return s\n\n\n@contextlib.contextmanager\ndef memory_checker(interval=15, dest=None):\n    r\"\"\"This is a context manager that monitors memory usage.\n\n    Parameters\n    ----------\n    interval : int\n        The number of seconds between printing the current memory usage in\n        gigabytes of the current Python interpreter.\n\n    Examples\n    --------\n\n    >>> with memory_checker(10):\n    ...     arr = np.zeros(1024 * 1024 * 1024, dtype=\"float64\")\n    ...     time.sleep(15)\n    ...     del arr\n    MEMORY: -1.000e+00 gb\n    \"\"\"\n    import threading\n\n    if dest is None:\n        dest = sys.stdout\n\n    class MemoryChecker(threading.Thread):\n        def __init__(self, event, interval):\n            self.event = event\n            self.interval = interval\n            threading.Thread.__init__(self)\n\n        def run(self):\n            while not self.event.wait(self.interval):\n                print(f\"MEMORY: {get_memory_usage() / 1024.0:0.3e} gb\", file=dest)\n\n    e = threading.Event()\n    mem_check = MemoryChecker(e, interval)\n    mem_check.start()\n    try:\n        yield\n    finally:\n        e.set()\n\n\ndef enable_plugins(plugin_filename=None):\n    \"\"\"Forces a plugin file to be parsed.\n\n    A plugin file is a means of creating custom fields, quantities,\n    data objects, colormaps, and other code classes and objects to be used\n    in yt scripts without modifying the yt source directly.\n\n    If ``plugin_filename`` is omitted, this function will look for a plugin file at\n    ``$HOME/.config/yt/my_plugins.py``, which is the preferred behaviour for a\n    system-level configuration.\n\n    Warning: a script using this function will only be reproducible if your plugin\n    file is shared with it.\n    \"\"\"\n    import yt\n    from yt.config import config_dir, ytcfg\n    from yt.fields.my_plugin_fields import my_plugins_fields\n\n    if plugin_filename is not None:\n        _fn = plugin_filename\n        if not os.path.isfile(_fn):\n            raise FileNotFoundError(_fn)\n    else:\n        # Determine global plugin location. By decreasing priority order:\n        # - absolute path\n        # - CONFIG_DIR\n        # - obsolete config dir.\n        my_plugin_name = ytcfg.get(\"yt\", \"plugin_filename\")\n        for base_prefix in (\"\", config_dir()):\n            if os.path.isfile(os.path.join(base_prefix, my_plugin_name)):\n                _fn = os.path.join(base_prefix, my_plugin_name)\n                break\n        else:\n            raise FileNotFoundError(\"Could not find a global system plugin file.\")\n\n    mylog.info(\"Loading plugins from %s\", _fn)\n    ytdict = yt.__dict__\n    execdict = ytdict.copy()\n    execdict[\"add_field\"] = my_plugins_fields.add_field\n    with open(_fn) as f:\n        code = compile(f.read(), _fn, \"exec\")\n        exec(code, execdict, execdict)\n    ytnamespace = list(ytdict.keys())\n    for k in execdict.keys():\n        if k not in ytnamespace:\n            if callable(execdict[k]):\n                setattr(yt, k, execdict[k])\n\n\ndef subchunk_count(n_total, chunk_size):\n    handled = 0\n    while handled < n_total:\n        tr = min(n_total - handled, chunk_size)\n        yield tr\n        handled += tr\n\n\ndef fix_unitary(u):\n    if u == \"1\":\n        return \"unitary\"\n    else:\n        return u\n\n\ndef get_hash(infile, algorithm=\"md5\", BLOCKSIZE=65536):\n    \"\"\"Generate file hash without reading in the entire file at once.\n\n    Original code licensed under MIT.  Source:\n    https://www.pythoncentral.io/hashing-files-with-python/\n\n    Parameters\n    ----------\n    infile : str\n        File of interest (including the path).\n    algorithm : str (optional)\n        Hash algorithm of choice. Defaults to 'md5'.\n    BLOCKSIZE : int (optional)\n        How much data in bytes to read in at once.\n\n    Returns\n    -------\n    hash : str\n        The hash of the file.\n\n    Examples\n    --------\n    >>> from tempfile import NamedTemporaryFile\n    >>> with NamedTemporaryFile() as file:\n    ...     get_hash(file.name)\n    'd41d8cd98f00b204e9800998ecf8427e'\n    \"\"\"\n    import hashlib\n\n    try:\n        hasher = getattr(hashlib, algorithm)()\n    except AttributeError as e:\n        raise NotImplementedError(\n            f\"'{algorithm}' not available!  Available algorithms: {hashlib.algorithms}\"\n        ) from e\n\n    filesize = os.path.getsize(infile)\n    iterations = int(float(filesize) / float(BLOCKSIZE))\n\n    pbar = get_pbar(f\"Generating {algorithm} hash\", iterations)\n\n    iter = 0\n    with open(infile, \"rb\") as f:\n        buf = f.read(BLOCKSIZE)\n        while len(buf) > 0:\n            hasher.update(buf)\n            buf = f.read(BLOCKSIZE)\n            iter += 1\n            pbar.update(iter)\n        pbar.finish()\n\n    return hasher.hexdigest()\n\n\ndef get_brewer_cmap(cmap):\n    \"\"\"Returns a colorbrewer colormap from palettable\"\"\"\n    try:\n        import palettable\n    except ImportError as exc:\n        raise RuntimeError(\n            \"Please install palettable to use colorbrewer colormaps\"\n        ) from exc\n\n    bmap = palettable.colorbrewer.get_map(*cmap)\n    return bmap.get_mpl_colormap(N=cmap[2])\n\n\ndef matplotlib_style_context(style=\"yt.default\", after_reset=False):\n    \"\"\"Returns a context manager for controlling matplotlib style.\n\n    Arguments are passed to matplotlib.style.context() if specified. Defaults\n    to setting yt's \"yt.default\" style, after resetting to the default config parameters.\n    \"\"\"\n    import matplotlib.style\n\n    issue_deprecation_warning(\n        \"yt.funcs.matplotlib_style_context is deprecated.\\n\"\n        f\"Use matplotlib.style.context({style=!r}) instead.\",\n        since=\"4.5\",\n        stacklevel=3,\n    )\n\n    return matplotlib.style.context(style, after_reset=after_reset)\n\n\ninteractivity = False\n\n\"\"\"Sets the condition that interactive backends can be used.\"\"\"\n\n\ndef toggle_interactivity():\n    global interactivity\n    interactivity = not interactivity\n    if interactivity:\n        if IS_IPYTHON:\n            import IPython\n\n            shell = IPython.get_ipython()\n            shell.magic(\"matplotlib\")\n        else:\n            import matplotlib\n\n            matplotlib.interactive(True)\n\n\ndef get_interactivity():\n    return interactivity\n\n\ndef setdefaultattr(obj, name, value):\n    \"\"\"Set attribute with *name* on *obj* with *value* if it doesn't exist yet\n\n    Analogous to dict.setdefault\n    \"\"\"\n    if not hasattr(obj, name):\n        setattr(obj, name, value)\n    return getattr(obj, name)\n\n\ndef parse_h5_attr(f, attr):\n    \"\"\"A Python3-safe function for getting hdf5 attributes.\n\n    If an attribute is supposed to be a string, this will return it as such.\n    \"\"\"\n    val = f.attrs.get(attr, None)\n    if isinstance(val, bytes):\n        return val.decode(\"utf8\")\n    else:\n        return val\n\n\ndef obj_length(v):\n    if is_sequence(v):\n        return len(v)\n    else:\n        # If something isn't iterable, we return 0\n        # to signify zero length (aka a scalar).\n        return 0\n\n\ndef array_like_field(data, x, field):\n    field = data._determine_fields(field)[0]\n    finfo = data.ds._get_field_info(field)\n    if finfo.sampling_type == \"particle\":\n        units = finfo.output_units\n    else:\n        units = finfo.units\n    if isinstance(x, YTArray):\n        arr = copy.deepcopy(x)\n        arr.convert_to_units(units)\n        return arr\n    if isinstance(x, np.ndarray):\n        return data.ds.arr(x, units)\n    else:\n        return data.ds.quan(x, units)\n\n\ndef _full_type_name(obj: object = None, /, *, cls: type | None = None) -> str:\n    if cls is not None and obj is not None:\n        raise TypeError(\"_full_type_name takes an object or a class, but not both\")\n    if cls is None:\n        cls = obj.__class__\n    prefix = f\"{cls.__module__}.\" if cls.__module__ != \"builtins\" else \"\"\n    return f\"{prefix}{cls.__name__}\"\n\n\ndef validate_3d_array(obj):\n    if not is_sequence(obj) or len(obj) != 3:\n        raise TypeError(\n            f\"Expected an array of size (3,), \"\n            f\"received {_full_type_name(obj)!r} of length {len(obj)}\"\n        )\n\n\ndef validate_float(obj):\n    \"\"\"Validates if the passed argument is a float value.\n\n    Raises an exception if `obj` is not a single float value\n    or a YTQuantity of size 1.\n\n    Parameters\n    ----------\n    obj : Any\n        Any argument which needs to be checked for a single float value.\n\n    Raises\n    ------\n    TypeError\n        Raised if `obj` is not a single float value or YTQuantity\n\n    Examples\n    --------\n    >>> validate_float(1)\n    >>> validate_float(1.50)\n    >>> validate_float(YTQuantity(1, \"cm\"))\n    >>> validate_float((1, \"cm\"))\n    >>> validate_float([1, 1, 1])\n    Traceback (most recent call last):\n    ...\n    TypeError: Expected a numeric value (or size-1 array), received 'list' of length 3\n\n    >>> validate_float([YTQuantity(1, \"cm\"), YTQuantity(2, \"cm\")])\n    Traceback (most recent call last):\n    ...\n    TypeError: Expected a numeric value (or size-1 array), received 'list' of length 2\n    \"\"\"\n    if isinstance(obj, tuple):\n        if (\n            len(obj) != 2\n            or not isinstance(obj[0], numeric_type)\n            or not isinstance(obj[1], str)\n        ):\n            raise TypeError(\n                \"Expected a numeric value (or tuple of format \"\n                f\"(float, String)), received an inconsistent tuple {str(obj)!r}.\"\n            )\n        else:\n            return\n    if is_sequence(obj) and (len(obj) != 1 or not isinstance(obj[0], numeric_type)):\n        raise TypeError(\n            \"Expected a numeric value (or size-1 array), \"\n            f\"received {_full_type_name(obj)!r} of length {len(obj)}\"\n        )\n\n\ndef validate_sequence(obj):\n    if obj is not None and not is_sequence(obj):\n        raise TypeError(\n            f\"Expected an iterable object, received {_full_type_name(obj)!r}\"\n        )\n\n\ndef validate_field_key(key):\n    if (\n        isinstance(key, tuple)\n        and len(key) == 2\n        and all(isinstance(_, str) for _ in key)\n    ):\n        return\n    raise TypeError(\n        \"Expected a 2-tuple of strings formatted as\\n\"\n        \"(field or particle type, field name)\\n\"\n        f\"Received invalid field key: {key}, with type {type(key)}\"\n    )\n\n\ndef is_valid_field_key(key):\n    try:\n        validate_field_key(key)\n    except TypeError:\n        return False\n    else:\n        return True\n\n\ndef validate_object(obj, data_type):\n    if obj is not None and not isinstance(obj, data_type):\n        raise TypeError(\n            f\"Expected an object of {_full_type_name(cls=data_type)!r} type, \"\n            f\"received {_full_type_name(obj)!r}\"\n        )\n\n\ndef validate_axis(ds, axis):\n    if ds is not None:\n        valid_axis = sorted(\n            ds.coordinates.axis_name.keys(), key=lambda k: str(k).swapcase()\n        )\n    else:\n        valid_axis = [0, 1, 2, \"x\", \"y\", \"z\", \"X\", \"Y\", \"Z\"]\n    if axis not in valid_axis:\n        raise TypeError(f\"Expected axis to be any of {valid_axis}, received {axis!r}\")\n\n\ndef validate_center(center):\n    if isinstance(center, str):\n        c = center.lower()\n        if (\n            c not in [\"c\", \"center\", \"m\", \"max\", \"min\"]\n            and not c.startswith(\"max_\")\n            and not c.startswith(\"min_\")\n        ):\n            raise TypeError(\n                \"Expected 'center' to be in ['c', 'center', \"\n                \"'m', 'max', 'min'] or the prefix to be \"\n                f\"'max_'/'min_', received {center!r}.\"\n            )\n    elif not isinstance(center, (numeric_type, YTQuantity)) and not is_sequence(center):\n        raise TypeError(\n            \"Expected 'center' to be a numeric object of type \"\n            \"list/tuple/np.ndarray/YTArray/YTQuantity, \"\n            f\"received {_full_type_name(center)}.\"\n        )\n\n\ndef parse_center_array(center, ds, axis: int | None = None):\n    known_shortnames = {\"m\": \"max\", \"c\": \"center\", \"l\": \"left\", \"r\": \"right\"}\n    valid_single_str_values = (\"center\", \"left\", \"right\")\n    valid_field_loc_str_values = (\"min\", \"max\")\n    valid_str_values = valid_single_str_values + valid_field_loc_str_values\n    default_error_message = (\n        \"Expected any of the following\\n\"\n        \"- 'c', 'center', 'l', 'left', 'r', 'right', 'm', 'max', or 'min'\\n\"\n        \"- a 2 element tuple with 'min' or 'max' as the first element, followed by a field identifier\\n\"\n        \"- a 3 element array-like: for a unyt_array, expects length dimensions, otherwise code_length is assumed\"\n    )\n    # store an unmodified copy of user input to be inserted in error messages\n    center_input = deepcopy(center)\n\n    if isinstance(center, str):\n        centerl = center.lower()\n        if centerl in known_shortnames:\n            centerl = known_shortnames[centerl]\n\n        match = re.match(r\"^(?P<extremum>(min|max))(_(?P<field>[\\w_]+))?\", centerl)\n        if match is not None:\n            if match[\"field\"] is not None:\n                for ftype, fname in ds.derived_field_list:  # noqa: B007\n                    if fname == match[\"field\"]:\n                        break\n                else:\n                    raise YTFieldNotFound(match[\"field\"], ds)\n            else:\n                ftype, fname = (\"gas\", \"density\")\n\n            center = (match[\"extremum\"], (ftype, fname))\n\n        elif centerl in (\"center\", \"left\", \"right\"):\n            # domain_left_edge and domain_right_edge might not be\n            # initialized until we create the index, so create it\n            ds.index\n            center = ds.domain_center.copy()\n            if centerl in (\"left\", \"right\") and axis is None:\n                raise ValueError(f\"center={center!r} is not valid with axis=None\")\n            if centerl == \"left\":\n                center = ds.domain_center.copy()\n                center[axis] = ds.domain_left_edge[axis]\n            elif centerl == \"right\":\n                # note that the right edge of a grid is excluded by slice selector\n                # which is why we offset the region center by the smallest distance possible\n                center = ds.domain_center.copy()\n                center[axis] = (\n                    ds.domain_right_edge[axis] - center.uq * np.finfo(center.dtype).eps\n                )\n\n        elif centerl not in valid_str_values:\n            raise ValueError(\n                f\"Received unknown center single string value {center!r}. \"\n                + default_error_message\n            )\n\n    if is_sequence(center):\n        if (\n            len(center) == 2\n            and isinstance(center[0], str)\n            and (is_valid_field_key(center[1]) or isinstance(center[1], str))\n        ):\n            center0l = center[0].lower()\n\n            if center0l not in valid_str_values:\n                raise ValueError(\n                    f\"Received unknown string value {center[0]!r}. \"\n                    f\"Expected one of {valid_field_loc_str_values} (case insensitive)\"\n                )\n            field_key = center[1]\n            if center0l == \"min\":\n                v, center = ds.find_min(field_key)\n            else:\n                assert center0l == \"max\"\n                v, center = ds.find_max(field_key)\n            center = ds.arr(center, \"code_length\")\n        elif len(center) == 2 and is_sequence(center[0]) and isinstance(center[1], str):\n            center = ds.arr(center[0], center[1])\n        elif len(center) == 3 and all(isinstance(_, YTQuantity) for _ in center):\n            center = ds.arr([c.copy() for c in center], dtype=\"float64\")\n        elif len(center) == 3:\n            center = ds.arr(center, \"code_length\")\n\n    if isinstance(center, np.ndarray) and center.ndim > 1:\n        mylog.debug(\"Removing singleton dimensions from 'center'.\")\n        center = np.squeeze(center)\n\n    if not isinstance(center, YTArray):\n        raise TypeError(\n            f\"Received {center_input!r}, but failed to transform to a unyt_array (obtained {center!r}).\\n\"\n            + default_error_message\n            + \"\\n\"\n            \"If you supplied an expected type, consider filing a bug report\"\n        )\n\n    if center.shape != (3,):\n        raise TypeError(\n            f\"Received {center_input!r} and obtained {center!r} after sanitizing.\\n\"\n            + default_error_message\n            + \"\\n\"\n            \"If you supplied an expected type, consider filing a bug report\"\n        )\n\n    # make sure the return value shares all\n    # unit symbols with ds.unit_registry\n    # we rely on unyt to invalidate unit dimensionality here\n    center = ds.arr(center).in_units(\"code_length\")\n\n    if not ds._is_within_domain(center):\n        mylog.warning(\n            \"Requested center at %s is outside of data domain with \"\n            \"left edge = %s, \"\n            \"right edge = %s, \"\n            \"periodicity = %s\",\n            center,\n            ds.domain_left_edge,\n            ds.domain_right_edge,\n            ds.periodicity,\n        )\n\n    return center.astype(\"float64\")\n\n\ndef sglob(pattern):\n    \"\"\"\n    Return the results of a glob through the sorted() function.\n    \"\"\"\n    return sorted(glob.glob(pattern))\n\n\ndef dictWithFactory(factory: Callable[[Any], Any]) -> type:\n    \"\"\"\n    Create a dictionary class with a default factory function.\n    Contrary to `collections.defaultdict`, the factory takes\n    the missing key as input parameter.\n\n    Parameters\n    ----------\n    factory : callable(key) -> value\n        The factory to call when hitting a missing key\n\n    Returns\n    -------\n    DictWithFactory class\n        A class to create new dictionaries handling missing keys.\n    \"\"\"\n\n    issue_deprecation_warning(\n        \"yt.funcs.dictWithFactory will be removed in a future version of yt, please do not rely on it. \"\n        \"If you need it, copy paste this function from yt's source code\",\n        stacklevel=3,\n        since=\"4.1\",\n    )\n\n    class DictWithFactory(UserDict):\n        def __init__(self, *args, **kwargs):\n            self.factory = factory\n            super().__init__(*args, **kwargs)\n\n        def __missing__(self, key):\n            val = self.factory(key)\n            self[key] = val\n            return val\n\n    return DictWithFactory\n\n\ndef levenshtein_distance(seq1, seq2, max_dist=None):\n    \"\"\"\n    Compute the levenshtein distance between seq1 and seq2.\n    From https://stackabuse.com/levenshtein-distance-and-text-similarity-in-python/\n\n    Parameters\n    ----------\n    seq1 : str\n    seq2 : str\n        The strings to compute the distance between\n    max_dist : integer\n        If not None, maximum distance returned (see notes).\n\n    Returns\n    -------\n    The Levenshtein distance as an integer.\n\n    Notes\n    -----\n    This computes the Levenshtein distance, i.e. the number of edits to change\n    seq1 into seq2. If a maximum distance is passed, the algorithm will stop as soon\n    as the number of edits goes above the value. This allows for an earlier break\n    and speeds calculations up.\n    \"\"\"\n    size_x = len(seq1) + 1\n    size_y = len(seq2) + 1\n    if max_dist is None:\n        max_dist = max(size_x, size_y)\n\n    if abs(size_x - size_y) > max_dist:\n        return max_dist + 1\n    matrix = np.zeros((size_x, size_y), dtype=int)\n    for x in range(size_x):\n        matrix[x, 0] = x\n    for y in range(size_y):\n        matrix[0, y] = y\n\n    for x in range(1, size_x):\n        for y in range(1, size_y):\n            if seq1[x - 1] == seq2[y - 1]:\n                matrix[x, y] = min(\n                    matrix[x - 1, y] + 1, matrix[x - 1, y - 1], matrix[x, y - 1] + 1\n                )\n            else:\n                matrix[x, y] = min(\n                    matrix[x - 1, y] + 1, matrix[x - 1, y - 1] + 1, matrix[x, y - 1] + 1\n                )\n\n        # Early break: the minimum distance is already larger than\n        # maximum allow value, can return safely.\n        if matrix[x].min() > max_dist:\n            return max_dist + 1\n    return matrix[size_x - 1, size_y - 1]\n\n\ndef validate_moment(moment, weight_field):\n    if moment == 2 and weight_field is None:\n        raise ValueError(\n            \"Cannot compute the second moment of a projection if weight_field=None!\"\n        )\n    if moment not in [1, 2]:\n        raise ValueError(\n            \"Weighted projections can only be made of averages \"\n            \"(moment = 1) or standard deviations (moment = 2)!\"\n        )\n\n\ndef setdefault_mpl_metadata(save_kwargs: dict[str, Any], name: str) -> None:\n    \"\"\"\n    Set a default Software metadata entry for use with Matplotlib outputs.\n    \"\"\"\n    _, ext = os.path.splitext(name.lower())\n    if ext in (\".eps\", \".ps\", \".svg\", \".pdf\"):\n        key = \"Creator\"\n    elif ext == \".png\":\n        key = \"Software\"\n    else:\n        return\n    default_software = (\n        \"Matplotlib version{matplotlib}, https://matplotlib.org|NumPy-{numpy}|yt-{yt}\"\n    ).format(**get_version_stack())\n\n    if \"metadata\" in save_kwargs:\n        save_kwargs[\"metadata\"].setdefault(key, default_software)\n    else:\n        save_kwargs[\"metadata\"] = {key: default_software}\n"
  },
  {
    "path": "yt/geometry/__init__.py",
    "content": ""
  },
  {
    "path": "yt/geometry/_selection_routines/always_selector.pxi",
    "content": "cdef class AlwaysSelector(SelectorObject):\n\n    def __init__(self, dobj):\n        self.overlap_cells = 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def select_grids(self,\n                     np.ndarray[np.float64_t, ndim=2] left_edges,\n                     np.ndarray[np.float64_t, ndim=2] right_edges,\n                     np.ndarray[np.int32_t, ndim=2] levels):\n        cdef int ng = left_edges.shape[0]\n        cdef np.ndarray[np.uint8_t, ndim=1] gridi = np.ones(ng, dtype='uint8')\n        return gridi.astype(\"bool\")\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        return 1\n\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        return 1\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        return 1\n\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        return 1\n\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        return 1\n\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        return 1\n\n    def _hash_vals(self):\n        return (\"always\", 1,)\n\nalways_selector = AlwaysSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/boolean_selectors.pxi",
    "content": "\ncdef class BooleanSelector(SelectorObject):\n\n    def __init__(self, dobj):\n        # Note that this has a different API than the other selector objects,\n        # so will not work as a traditional data selector.\n        if not hasattr(dobj.dobj1, \"selector\"):\n            self.sel1 = dobj.dobj1\n        else:\n            self.sel1 = dobj.dobj1.selector\n        if not hasattr(dobj.dobj2, \"selector\"):\n            self.sel2 = dobj.dobj2\n        else:\n            self.sel2 = dobj.dobj2.selector\n\ncdef class BooleanANDSelector(BooleanSelector):\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_bbox(left_edge, right_edge)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_bbox(left_edge, right_edge)\n        if rv2 == 0: return 0\n        return 1\n\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                              np.float64_t right_edge[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_bbox_edge(left_edge, right_edge)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_bbox_edge(left_edge, right_edge)\n        if rv2 == 0: return 0\n        return max(rv1, rv2)\n\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        cdef int rv1 = self.sel1.select_grid(left_edge, right_edge, level, o)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_grid(left_edge, right_edge, level, o)\n        if rv2 == 0: return 0\n        return 1\n\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_cell(pos, dds)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_cell(pos, dds)\n        if rv2 == 0: return 0\n        return 1\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_point(pos)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_point(pos)\n        if rv2 == 0: return 0\n        return 1\n\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef int rv1 = self.sel1.select_sphere(pos, radius)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_sphere(pos, radius)\n        if rv2 == 0: return 0\n        return 1\n\n    def _hash_vals(self):\n        return (self.sel1._hash_vals() +\n                (\"and\",) +\n                self.sel2._hash_vals())\n\ncdef class BooleanORSelector(BooleanSelector):\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_bbox(left_edge, right_edge)\n        if rv1 == 1: return 1\n        cdef int rv2 = self.sel2.select_bbox(left_edge, right_edge)\n        if rv2 == 1: return 1\n        return 0\n\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                              np.float64_t right_edge[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_bbox_edge(left_edge, right_edge)\n        if rv1 == 1: return 1\n        cdef int rv2 = self.sel2.select_bbox_edge(left_edge, right_edge)\n        if rv2 == 1: return 1\n        return max(rv1, rv2)\n\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        cdef int rv1 = self.sel1.select_grid(left_edge, right_edge, level, o)\n        if rv1 == 1: return 1\n        cdef int rv2 = self.sel2.select_grid(left_edge, right_edge, level, o)\n        if rv2 == 1: return 1\n        if (rv1 == 2) or (rv2 == 2): return 2\n        return 0\n\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_cell(pos, dds)\n        if rv1 == 1: return 1\n        cdef int rv2 = self.sel2.select_cell(pos, dds)\n        if rv2 == 1: return 1\n        return 0\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_point(pos)\n        if rv1 == 1: return 1\n        cdef int rv2 = self.sel2.select_point(pos)\n        if rv2 == 1: return 1\n        return 0\n\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef int rv1 = self.sel1.select_sphere(pos, radius)\n        if rv1 == 1: return 1\n        cdef int rv2 = self.sel2.select_sphere(pos, radius)\n        if rv2 == 1: return 1\n        return 0\n\n    def _hash_vals(self):\n        return (self.sel1._hash_vals() +\n                (\"or\",) +\n                self.sel2._hash_vals())\n\ncdef class BooleanNOTSelector(BooleanSelector):\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        # We always return True here, because we don't have a \"fully included\"\n        # check anywhere else.\n        return 1\n\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                              np.float64_t right_edge[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_bbox_edge(left_edge, right_edge)\n        if rv1 == 0: return 1\n        elif rv1 == 1: return 0\n        return 2\n\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        return 1\n\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_cell(pos, dds)\n        if rv1 == 0: return 1\n        return 0\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_point(pos)\n        if rv1 == 0: return 1\n        return 0\n\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef int rv1 = self.sel1.select_sphere(pos, radius)\n        if rv1 == 0: return 1\n        return 0\n\n    def _hash_vals(self):\n        return (self.sel1._hash_vals() +\n                (\"not\",))\n\ncdef class BooleanXORSelector(BooleanSelector):\n\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        # We always return True here, because we don't have a \"fully included\"\n        # check anywhere else.\n        return 1\n\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                              np.float64_t right_edge[3]) noexcept nogil:\n        # Return 2 in cases where one or both selectors partially overlap since\n        # part of the bounding box could satisfy the condition unless the\n        # selectors are identical.\n        cdef int rv1 = self.sel1.select_bbox_edge(left_edge, right_edge)\n        cdef int rv2 = self.sel2.select_bbox_edge(left_edge, right_edge)\n        if rv1 == rv2:\n            if rv1 == 2:\n                # If not identical, part of the bbox will be touched by one\n                # selector and not the other.\n                # if self.sel1 == self.sel2: return 0  # requires gil\n                return 2\n            return 0\n        if rv1 == 0: return rv2\n        if rv2 == 0: return rv1\n        return 2  # part of bbox only touched by selector fully covering bbox\n\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        return 1\n\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_cell(pos, dds)\n        cdef int rv2 = self.sel2.select_cell(pos, dds)\n        if rv1 == rv2: return 0\n        return 1\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_point(pos)\n        cdef int rv2 = self.sel2.select_point(pos)\n        if rv1 == rv2: return 0\n        return 1\n\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef int rv1 = self.sel1.select_sphere(pos, radius)\n        cdef int rv2 = self.sel2.select_sphere(pos, radius)\n        if rv1 == rv2: return 0\n        return 1\n\n    def _hash_vals(self):\n        return (self.sel1._hash_vals() +\n                (\"xor\",) +\n                self.sel2._hash_vals())\n\ncdef class BooleanNEGSelector(BooleanSelector):\n\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3]) noexcept nogil:\n        # We always return True here, because we don't have a \"fully included\"\n        # check anywhere else.\n        return self.sel1.select_bbox(left_edge, right_edge)\n\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                              np.float64_t right_edge[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_bbox_edge(left_edge, right_edge)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_bbox_edge(left_edge, right_edge)\n        if rv2 == 1:\n            return 0\n        elif rv2 == 0:\n            return rv1\n        # If sel2 is partial, then sel1 - sel2 will be partial as long\n        # as sel1 != sel2\n        # if self.sel1 == self.sel2: return 0  # requires gil\n        return 2\n\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        return self.sel1.select_grid(left_edge, right_edge, level, o)\n\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_cell(pos, dds)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_cell(pos, dds)\n        if rv2 == 1: return 0\n        return 1\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef int rv1 = self.sel1.select_point(pos)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_point(pos)\n        if rv2 == 1: return 0\n        return 1\n\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef int rv1 = self.sel1.select_sphere(pos, radius)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.sel2.select_sphere(pos, radius)\n        if rv2 == 1: return 0\n        return 1\n\n    def _hash_vals(self):\n        return (self.sel1._hash_vals() +\n                (\"neg\",) +\n                self.sel2._hash_vals())\n\ncdef class ChainedBooleanSelector(SelectorObject):\n    cdef int n_obj\n    cdef np.ndarray selectors\n    def __init__(self, dobj):\n        # These are data objects, not selectors\n        self.n_obj = len(dobj.data_objects)\n        self.selectors = np.empty(self.n_obj, dtype=\"object\")\n        for i in range(self.n_obj):\n            self.selectors[i] = dobj.data_objects[i].selector\n\ncdef class ChainedBooleanANDSelector(ChainedBooleanSelector):\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3]) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_bbox(\n                        left_edge, right_edge) == 0:\n                    return 0\n        return 1\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                              np.float64_t right_edge[3]) noexcept nogil:\n        cdef int selected = 1\n        cdef int ret\n        with gil:\n            for i in range(self.n_obj):\n                ret = (<SelectorObject>self.selectors[i]).select_bbox_edge(\n                    left_edge, right_edge)\n                if ret == 0:\n                    return 0\n                elif ret == 2:\n                    selected = 2\n        return selected\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_grid(\n                        left_edge, right_edge, level, o) == 0:\n                    return 0\n        return 1\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_cell(\n                        pos, dds) == 0:\n                    return 0\n        return 1\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_point(pos) == 0:\n                    return 0\n        return 1\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_sphere(\n                        pos, radius) == 0:\n                    return 0\n        return 1\n\n    def _hash_vals(self):\n        v = (\"chained_and\",)\n        for s in self.selectors:\n            v += s._hash_vals()\n        return v\n\nintersection_selector = ChainedBooleanANDSelector\n\ncdef class ChainedBooleanORSelector(ChainedBooleanSelector):\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3]) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_bbox(\n                        left_edge, right_edge) == 1:\n                    return 1\n        return 0\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3]) noexcept nogil:\n        cdef int selected = 0\n        cdef int ret\n        with gil:\n            for i in range(self.n_obj):\n                ret = (<SelectorObject>self.selectors[i]).select_bbox_edge(\n                    left_edge, right_edge)\n                if ret == 1:\n                    return 1\n                elif ret == 2:\n                    selected = 2\n        return selected\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_grid(\n                        left_edge, right_edge, level, o) == 1:\n                    return 1\n        return 0\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_cell(\n                        pos, dds) == 1:\n                    return 1\n        return 0\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_point(pos) == 1:\n                    return 1\n        return 0\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        with gil:\n            for i in range(self.n_obj):\n                if (<SelectorObject>self.selectors[i]).select_sphere(\n                        pos, radius) == 1:\n                    return 1\n        return 0\n\n    def _hash_vals(self):\n        v = (\"chained_or\",)\n        for s in self.selectors:\n            v += s._hash_vals()\n        return v\n\nunion_selector = ChainedBooleanORSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/compose_selector.pxi",
    "content": "cdef class ComposeSelector(SelectorObject):\n    cdef SelectorObject selector1\n    cdef SelectorObject selector2\n\n    def __init__(self, dobj, selector1, selector2):\n        self.selector1 = selector1\n        self.selector2 = selector2\n        self.min_level = max(selector1.min_level, selector2.min_level)\n        self.max_level = min(selector1.max_level, selector2.max_level)\n\n    def select_grids(self,\n                     np.ndarray[np.float64_t, ndim=2] left_edges,\n                     np.ndarray[np.float64_t, ndim=2] right_edges,\n                     np.ndarray[np.int32_t, ndim=2] levels):\n        return np.logical_or(\n                    self.selector1.select_grids(left_edges, right_edges, levels),\n                    self.selector2.select_grids(left_edges, right_edges, levels))\n\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        if self.selector1.select_cell(pos, dds) and \\\n                self.selector2.select_cell(pos, dds):\n            return 1\n        else:\n            return 0\n\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        if self.selector1.select_grid(left_edge, right_edge, level, o) or \\\n                self.selector2.select_grid(left_edge, right_edge, level, o):\n            return 1\n        else:\n            return 0\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        if self.selector1.select_point(pos) and \\\n                self.selector2.select_point(pos):\n            return 1\n        else:\n            return 0\n\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        if self.selector1.select_sphere(pos, radius) and \\\n                self.selector2.select_sphere(pos, radius):\n            return 1\n        else:\n            return 0\n\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        if self.selector1.select_bbox(left_edge, right_edge) and \\\n                self.selector2.select_bbox(left_edge, right_edge):\n            return 1\n        else:\n            return 0\n\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                              np.float64_t right_edge[3]) noexcept nogil:\n        cdef int rv1 = self.selector1.select_bbox_edge(left_edge, right_edge)\n        if rv1 == 0: return 0\n        cdef int rv2 = self.selector2.select_bbox_edge(left_edge, right_edge)\n        if rv2 == 0: return 0\n        return max(rv1, rv2)\n\n    def _hash_vals(self):\n        return (hash(self.selector1), hash(self.selector2))\n\ncompose_selector = ComposeSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/cut_region_selector.pxi",
    "content": "cdef class CutRegionSelector(SelectorObject):\n    cdef set _positions\n    cdef tuple _conditionals\n\n    def __init__(self, dobj):\n        axis_name = dobj.ds.coordinates.axis_name\n        positions = np.array([dobj['index', axis_name[0]],\n                              dobj['index', axis_name[1]],\n                              dobj['index', axis_name[2]]]).T\n        self._conditionals = tuple(dobj.conditionals)\n        self._positions = set(tuple(position) for position in positions)\n\n    cdef int select_bbox(self,  np.float64_t left_edge[3],\n                     np.float64_t right_edge[3]) noexcept nogil:\n        return 1\n\n    cdef int select_bbox_dge(self,  np.float64_t left_edge[3],\n                     np.float64_t right_edge[3]) noexcept nogil:\n        return 1\n\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        with gil:\n            if (pos[0], pos[1], pos[2]) in self._positions:\n                return 1\n            else:\n                return 0\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        return 1\n\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        return 1\n\n    def _hash_vals(self):\n        t = ()\n        for i, c in enumerate(self._conditionals):\n            t += (\"conditional[%s]\" % i, c)\n        return (\"conditionals\", t)\n\ncut_region_selector = CutRegionSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/cutting_plane_selector.pxi",
    "content": "cdef class CuttingPlaneSelector(SelectorObject):\n    cdef public np.float64_t norm_vec[3]\n    cdef public np.float64_t d\n\n    def __init__(self, dobj):\n        cdef int i\n        for i in range(3):\n            self.norm_vec[i] = dobj._norm_vec[i]\n        self.d = _ensure_code(dobj._d)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        cdef np.float64_t left_edge[3]\n        cdef np.float64_t right_edge[3]\n        cdef int i\n        for i in range(3):\n            left_edge[i] = pos[i] - 0.5*dds[i]\n            right_edge[i] = pos[i] + 0.5*dds[i]\n        return self.select_bbox(left_edge, right_edge)\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        # two 0-volume constructs don't intersect\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef int i\n        cdef np.float64_t height = self.d\n        for i in range(3) :\n            height += pos[i] * self.norm_vec[i]\n        if height*height <= radius*radius : return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef int i, j, k, n\n        cdef np.float64_t *arr[2]\n        cdef np.float64_t pos[3]\n        cdef np.float64_t gd\n        arr[0] = left_edge\n        arr[1] = right_edge\n        all_under = 1\n        all_over = 1\n        # Check each corner\n        for i in range(2):\n            pos[0] = arr[i][0]\n            for j in range(2):\n                pos[1] = arr[j][1]\n                for k in range(2):\n                    pos[2] = arr[k][2]\n                    gd = self.d\n                    for n in range(3):\n                        gd += pos[n] * self.norm_vec[n]\n                    # this allows corners and faces on the low-end to\n                    # collide, while not selecting cells on the high-side\n                    if i == 0 and j == 0 and k == 0 :\n                        if gd <= 0: all_over = 0\n                        if gd >= 0: all_under = 0\n                    else :\n                        if gd < 0: all_over = 0\n                        if gd > 0: all_under = 0\n        if all_over == 1 or all_under == 1:\n            return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef int i, j, k, n\n        cdef np.float64_t *arr[2]\n        cdef np.float64_t pos[3]\n        cdef np.float64_t gd\n        arr[0] = left_edge\n        arr[1] = right_edge\n        all_under = 1\n        all_over = 1\n        # Check each corner\n        for i in range(2):\n            pos[0] = arr[i][0]\n            for j in range(2):\n                pos[1] = arr[j][1]\n                for k in range(2):\n                    pos[2] = arr[k][2]\n                    gd = self.d\n                    for n in range(3):\n                        gd += pos[n] * self.norm_vec[n]\n                    # this allows corners and faces on the low-end to\n                    # collide, while not selecting cells on the high-side\n                    if i == 0 and j == 0 and k == 0 :\n                        if gd <= 0: all_over = 0\n                        if gd >= 0: all_under = 0\n                    else :\n                        if gd < 0: all_over = 0\n                        if gd > 0: all_under = 0\n        if all_over == 1 or all_under == 1:\n            return 0\n        return 2 # a box of non-zeros volume can't be inside a plane\n\n    def _hash_vals(self):\n        return ((\"norm_vec[0]\", self.norm_vec[0]),\n                (\"norm_vec[1]\", self.norm_vec[1]),\n                (\"norm_vec[2]\", self.norm_vec[2]),\n                (\"d\", self.d))\n\n    def _get_state_attnames(self):\n        return (\"d\", \"norm_vec\")\n\ncutting_selector = CuttingPlaneSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/data_collection_selector.pxi",
    "content": "cdef class DataCollectionSelector(SelectorObject):\n    cdef object obj_ids\n    cdef np.int64_t nids\n\n    def __init__(self, dobj):\n        self.obj_ids = dobj._obj_ids\n        self.nids = self.obj_ids.shape[0]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def select_grids(self,\n                     np.ndarray[np.float64_t, ndim=2] left_edges,\n                     np.ndarray[np.float64_t, ndim=2] right_edges,\n                     np.ndarray[np.int32_t, ndim=2] levels):\n        cdef int n\n        cdef int ng = left_edges.shape[0]\n        cdef np.ndarray[np.uint8_t, ndim=1] gridi = np.zeros(ng, dtype='uint8')\n        cdef np.ndarray[np.int64_t, ndim=1] oids = self.obj_ids\n        with nogil:\n            for n in range(self.nids):\n                gridi[oids[n]] = 1\n        return gridi.astype(\"bool\")\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_mask_regular_grid(self, gobj):\n        cdef np.ndarray[np.uint8_t, ndim=3] mask\n        mask = np.ones(gobj.ActiveDimensions, dtype='uint8')\n        return mask.astype(\"bool\"), mask.size\n\n    def _hash_vals(self):\n        return (hash(self.obj_ids.tobytes()), self.nids)\n\ndata_collection_selector = DataCollectionSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/disk_selector.pxi",
    "content": "cdef class DiskSelector(SelectorObject):\n    cdef public np.float64_t norm_vec[3]\n    cdef public np.float64_t center[3]\n    cdef public np.float64_t radius, radius2\n    cdef public np.float64_t height\n\n    def __init__(self, dobj):\n        cdef int i\n        for i in range(3):\n            self.norm_vec[i] = dobj._norm_vec[i]\n            self.center[i] = _ensure_code(dobj.center[i])\n        self.radius = _ensure_code(dobj.radius)\n        self.radius2 = self.radius * self.radius\n        self.height = _ensure_code(dobj.height)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        return self.select_point(pos)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef np.float64_t h, d, r2, temp\n        cdef int i\n        h = d = 0\n        for i in range(3):\n            temp = self.periodic_difference(pos[i], self.center[i], i)\n            h += temp * self.norm_vec[i]\n            d += temp*temp\n        r2 = (d - h*h)\n        if fabs(h) <= self.height and r2 <= self.radius2: return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef np.float64_t h, d, r2, temp\n        cdef int i\n        h = d = 0\n        for i in range(3):\n            temp = self.periodic_difference(pos[i], self.center[i], i)\n            h += temp * self.norm_vec[i]\n            d += temp*temp\n        r2 = (d - h*h)\n        d = self.radius+radius\n        if fabs(h) <= self.height+radius and r2 <= d*d: return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        # Until we can get our OBB/OBB intersection correct, disable this.\n        return 1\n        # cdef np.float64_t *arr[2]\n        # cdef np.float64_t pos[3]\n        # cdef np.float64_t H, D, R2, temp\n        # cdef int i, j, k, n\n        # cdef int all_under = 1\n        # cdef int all_over = 1\n        # cdef int any_radius = 0\n        # # A moment of explanation (revised):\n        # #    The disk and bounding box collide if any of the following are true:\n        # #    1) the center of the disk is inside the bounding box\n        # #    2) any corner of the box lies inside the disk\n        # #    3) the box spans the plane (!all_under and !all_over) and at least\n        # #       one corner is within the cylindrical radius\n\n        # # check if disk center lies inside bbox\n        # if left_edge[0] <= self.center[0] <= right_edge[0] and \\\n        #    left_edge[1] <= self.center[1] <= right_edge[1] and \\\n        #    left_edge[2] <= self.center[2] <= right_edge[2] :\n        #     return 1\n\n        # # check all corners\n        # arr[0] = left_edge\n        # arr[1] = right_edge\n        # for i in range(2):\n        #     pos[0] = arr[i][0]\n        #     for j in range(2):\n        #         pos[1] = arr[j][1]\n        #         for k in range(2):\n        #             pos[2] = arr[k][2]\n        #             H = D = 0\n        #             for n in range(3):\n        #                 temp = self.difference(pos[n], self.center[n], n)\n        #                 H += (temp * self.norm_vec[n])\n        #                 D += temp*temp\n        #             R2 = (D - H*H)\n        #             if R2 < self.radius2 :\n        #                 any_radius = 1\n        #                 if fabs(H) < self.height: return 1\n        #             if H < 0: all_over = 0\n        #             if H > 0: all_under = 0\n        # if all_over == 0 and all_under == 0 and any_radius == 1: return 1\n        # return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        # Until we can get our OBB/OBB intersection correct, disable this.\n        return 2\n        # cdef np.float64_t *arr[2]\n        # cdef np.float64_t pos[3], H, D, R2, temp\n        # cdef int i, j, k, n\n        # cdef int all_under = 1\n        # cdef int all_over = 1\n        # cdef int any_radius = 0\n        # # A moment of explanation (revised):\n        # #    The disk and bounding box collide if any of the following are true:\n        # #    1) the center of the disk is inside the bounding box\n        # #    2) any corner of the box lies inside the disk\n        # #    3) the box spans the plane (!all_under and !all_over) and at least\n        # #       one corner is within the cylindrical radius\n\n        # # check if disk center lies inside bbox\n        # if left_edge[0] <= self.center[0] <= right_edge[0] and \\\n        #    left_edge[1] <= self.center[1] <= right_edge[1] and \\\n        #    left_edge[2] <= self.center[2] <= right_edge[2] :\n        #     return 1\n\n        # # check all corners\n        # arr[0] = left_edge\n        # arr[1] = right_edge\n        # for i in range(2):\n        #     pos[0] = arr[i][0]\n        #     for j in range(2):\n        #         pos[1] = arr[j][1]\n        #         for k in range(2):\n        #             pos[2] = arr[k][2]\n        #             H = D = 0\n        #             for n in range(3):\n        #                 temp = self.periodic_difference(\n        #                     pos[n], self.center[n], n)\n        #                 H += (temp * self.norm_vec[n])\n        #                 D += temp*temp\n        #             R2 = (D - H*H)\n        #             if R2 < self.radius2 :\n        #                 any_radius = 1\n        #                 if fabs(H) < self.height: return 1\n        #             if H < 0: all_over = 0\n        #             if H > 0: all_under = 0\n        # if all_over == 0 and all_under == 0 and any_radius == 1: return 1\n        # return 0\n\n    def _hash_vals(self):\n        return ((\"norm_vec[0]\", self.norm_vec[0]),\n                (\"norm_vec[1]\", self.norm_vec[1]),\n                (\"norm_vec[2]\", self.norm_vec[2]),\n                (\"center[0]\", self.center[0]),\n                (\"center[1]\", self.center[1]),\n                (\"center[2]\", self.center[2]),\n                (\"radius\", self.radius),\n                (\"radius2\", self.radius2),\n                (\"height\", self.height))\n\n    def _get_state_attnames(self):\n        return (\"radius\", \"radius2\", \"height\", \"norm_vec\", \"center\")\n\n\ndisk_selector = DiskSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/ellipsoid_selector.pxi",
    "content": "cdef class EllipsoidSelector(SelectorObject):\n    cdef public np.float64_t vec[3][3]\n    cdef public np.float64_t mag[3]\n    cdef public np.float64_t center[3]\n\n    def __init__(self, dobj):\n        cdef int i\n        _ensure_code(dobj.center)\n        _ensure_code(dobj._e0)\n        _ensure_code(dobj._e1)\n        _ensure_code(dobj._e2)\n        _ensure_code(dobj._A)\n        _ensure_code(dobj._B)\n        _ensure_code(dobj._C)\n        for i in range(3):\n            self.center[i] = dobj.center[i]\n            self.vec[0][i] = dobj._e0[i]\n            self.vec[1][i] = dobj._e1[i]\n            self.vec[2][i] = dobj._e2[i]\n        self.mag[0] = dobj._A\n        self.mag[1] = dobj._B\n        self.mag[2] = dobj._C\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        return self.select_point(pos)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef np.float64_t dot_evec[3]\n        cdef np.float64_t dist\n        cdef int i, j\n        dot_evec[0] = dot_evec[1] = dot_evec[2] = 0\n        # Calculate the rotated dot product\n        for i in range(3): # axis\n            dist = self.periodic_difference(pos[i], self.center[i], i)\n            for j in range(3):\n                dot_evec[j] += dist * self.vec[j][i]\n        dist = 0.0\n        for i in range(3):\n            dist += (dot_evec[i] * dot_evec[i])/(self.mag[i] * self.mag[i])\n        if dist <= 1.0: return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        # this is the sphere selection\n        cdef int i\n        cdef np.float64_t dist, dist2_max, dist2 = 0\n        for i in range(3):\n            dist = self.periodic_difference(pos[i], self.center[i], i)\n            dist2 += dist * dist\n        dist2_max = (self.mag[0] + radius) * (self.mag[0] + radius)\n        if dist2 <= dist2_max:\n            return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        # This is the sphere selection\n        cdef int i\n        cdef np.float64_t box_center, relcenter, closest, dist, edge, dist_max\n        if left_edge[0] <= self.center[0] <= right_edge[0] and \\\n           left_edge[1] <= self.center[1] <= right_edge[1] and \\\n           left_edge[2] <= self.center[2] <= right_edge[2]:\n            return 1\n        # http://www.gamedev.net/topic/335465-is-this-the-simplest-sphere-aabb-collision-test/\n        dist = 0\n        for i in range(3):\n            box_center = (right_edge[i] + left_edge[i])/2.0\n            relcenter = self.periodic_difference(box_center, self.center[i], i)\n            edge = right_edge[i] - left_edge[i]\n            closest = relcenter - fclip(relcenter, -edge/2.0, edge/2.0)\n            dist += closest * closest\n        dist_max = self.mag[0] * self.mag[0]\n        if dist <= dist_max:\n            return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        # This is the sphere selection\n        cdef int i\n        cdef np.float64_t box_center, relcenter, closest, farthest, cdist, fdist, edge\n        if left_edge[0] <= self.center[0] <= right_edge[0] and \\\n           left_edge[1] <= self.center[1] <= right_edge[1] and \\\n           left_edge[2] <= self.center[2] <= right_edge[2]:\n            fdist = 0\n            for i in range(3):\n                edge = right_edge[i] - left_edge[i]\n                box_center = (right_edge[i] + left_edge[i])/2.0\n                relcenter = self.periodic_difference(\n                    box_center, self.center[i], i)\n                farthest = relcenter + fclip(relcenter, -edge/2.0, edge/2.0)\n                fdist += farthest*farthest\n                if fdist >= self.mag[0]**2: return 2\n            return 1\n        # http://www.gamedev.net/topic/335465-is-this-the-simplest-sphere-aabb-collision-test/\n        cdist = 0\n        fdist = 0\n        for i in range(3):\n            box_center = (right_edge[i] + left_edge[i])/2.0\n            relcenter = self.periodic_difference(box_center, self.center[i], i)\n            edge = right_edge[i] - left_edge[i]\n            closest = relcenter - fclip(relcenter, -edge/2.0, edge/2.0)\n            farthest = relcenter + fclip(relcenter, -edge/2.0, edge/2.0)\n            cdist += closest * closest\n            fdist += farthest * farthest\n            if cdist > self.mag[0]**2: return 0\n        if fdist < self.mag[0]**2:\n            return 1\n        else:\n            return 2\n\n    def _hash_vals(self):\n        return ((\"vec[0][0]\", self.vec[0][0]),\n                (\"vec[0][1]\", self.vec[0][1]),\n                (\"vec[0][2]\", self.vec[0][2]),\n                (\"vec[1][0]\", self.vec[1][0]),\n                (\"vec[1][1]\", self.vec[1][1]),\n                (\"vec[1][2]\", self.vec[1][2]),\n                (\"vec[2][0]\", self.vec[2][0]),\n                (\"vec[2][1]\", self.vec[2][1]),\n                (\"vec[2][2]\", self.vec[2][2]),\n                (\"mag[0]\", self.mag[0]),\n                (\"mag[1]\", self.mag[1]),\n                (\"mag[2]\", self.mag[2]),\n                (\"center[0]\", self.center[0]),\n                (\"center[1]\", self.center[1]),\n                (\"center[2]\", self.center[2]))\n\n    def _get_state_attnames(self):\n        return (\"mag\", \"center\", \"vec\")\n\n\nellipsoid_selector = EllipsoidSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/grid_selector.pxi",
    "content": "cdef class GridSelector(SelectorObject):\n    cdef object ind\n\n    def __init__(self, dobj):\n        self.ind = dobj.id - dobj._id_offset\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def select_grids(self,\n                     np.ndarray[np.float64_t, ndim=2] left_edges,\n                     np.ndarray[np.float64_t, ndim=2] right_edges,\n                     np.ndarray[np.int32_t, ndim=2] levels):\n        cdef int ng = left_edges.shape[0]\n        cdef np.ndarray[np.uint8_t, ndim=1] gridi = np.zeros(ng, dtype='uint8')\n        gridi[self.ind] = 1\n        return gridi.astype(\"bool\")\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_mask_regular_grid(self, gobj):\n        mask = np.ones(gobj.ActiveDimensions, dtype='bool')\n        return mask, mask.size\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        return 1\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        # we apparently don't check if the point actually lies in the grid..\n        return 1\n\n    def _hash_vals(self):\n        return (self.ind,)\n\ngrid_selector = GridSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/halo_particles_selector.pxi",
    "content": "cdef class HaloParticlesSelector(SelectorObject):\n    cdef public object base_source\n    cdef SelectorObject base_selector\n    cdef object pind\n    cdef public np.int64_t halo_id\n    def __init__(self, dobj):\n        self.base_source = dobj.base_source\n        self.base_selector = self.base_source.selector\n        self.pind = dobj.particle_indices\n\n    def _hash_vals(self):\n        return (\"halo_particles\", self.halo_id)\n\nhalo_particles_selector = HaloParticlesSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/indexed_octree_subset_selector.pxi",
    "content": "cdef class IndexedOctreeSubsetSelector(SelectorObject):\n    # This is a numpy array, which will be a bool of ndim 1\n    cdef np.uint64_t min_ind\n    cdef np.uint64_t max_ind\n    cdef public SelectorObject base_selector\n    cdef int filter_bbox\n    cdef np.float64_t DLE[3]\n    cdef np.float64_t DRE[3]\n\n    def __init__(self, dobj):\n        self.min_ind = dobj.min_ind\n        self.max_ind = dobj.max_ind\n        self.base_selector = dobj.base_selector\n        self.min_level = self.base_selector.min_level\n        self.max_level = self.base_selector.max_level\n        self.filter_bbox = 0\n        if getattr(dobj.ds, \"filter_bbox\", False):\n            self.filter_bbox = 1\n        for i in range(3):\n            self.DLE[i] = dobj.ds.domain_left_edge[i]\n            self.DRE[i] = dobj.ds.domain_right_edge[i]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def select_grids(self,\n                     np.ndarray[np.float64_t, ndim=2] left_edges,\n                     np.ndarray[np.float64_t, ndim=2] right_edges,\n                     np.ndarray[np.int32_t, ndim=2] levels):\n        raise RuntimeError\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef int i\n        if self.filter_bbox == 0:\n            return 1\n        for i in range(3):\n            if pos[i] < self.DLE[i] or pos[i] > self.DRE[i]:\n                return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        return self.base_selector.select_bbox(left_edge, right_edge)\n\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        # Because visitors now use select_grid, we should be explicitly\n        # checking this.\n        return self.base_selector.select_grid(left_edge, right_edge, level, o)\n\n    def _hash_vals(self):\n        return (hash(self.base_selector), self.min_ind, self.max_ind)\n\nindexed_octree_subset_selector = IndexedOctreeSubsetSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/octree_subset_selector.pxi",
    "content": "cdef class OctreeSubsetSelector(SelectorObject):\n\n    def __init__(self, dobj):\n        self.base_selector = dobj.base_selector\n        self.min_level = self.base_selector.min_level\n        self.max_level = self.base_selector.max_level\n        self.domain_id = dobj.domain_id\n        self.overlap_cells = getattr(dobj.oct_handler, 'overlap_cells', 1)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def select_grids(self,\n                     np.ndarray[np.float64_t, ndim=2] left_edges,\n                     np.ndarray[np.float64_t, ndim=2] right_edges,\n                     np.ndarray[np.int32_t, ndim=2] levels):\n        raise RuntimeError\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        return self.base_selector.select_point(pos)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        # return 1\n        return self.base_selector.select_bbox(left_edge, right_edge)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                         np.float64_t right_edge[3], np.int32_t level,\n                         Oct *o = NULL) noexcept nogil:\n        # Because visitors now use select_grid, we should be explicitly\n        # checking this.\n        cdef int res\n        res = self.base_selector.select_grid(left_edge, right_edge, level, o)\n        if self.domain_id == -1:\n            return res\n        elif res == 1 and o != NULL and o.domain != self.domain_id:\n            return -1\n        return res\n\n    def _hash_vals(self):\n        return (hash(self.base_selector), self.domain_id)\n\noctree_subset_selector = OctreeSubsetSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/ortho_ray_selector.pxi",
    "content": "cdef class OrthoRaySelector(SelectorObject):\n\n    cdef public np.uint8_t px_ax\n    cdef public np.uint8_t py_ax\n    cdef public np.float64_t px\n    cdef public np.float64_t py\n    cdef public int axis\n\n    def __init__(self, dobj):\n        self.axis = dobj.axis\n        self.px_ax = dobj.px_ax\n        self.py_ax = dobj.py_ax\n        self.px = dobj.px\n        self.py = dobj.py\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_mask_regular_grid(self, gobj):\n        cdef np.ndarray[np.uint8_t, ndim=3] mask\n        cdef np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask\n        cdef int i, j, k\n        cdef int total = 0\n        cdef int this_level = 0\n        cdef int ind[3][2]\n        cdef np.int32_t level = gobj.Level\n        _ensure_code(gobj.LeftEdge)\n        _ensure_code(gobj.RightEdge)\n        _ensure_code(gobj.dds)\n\n        if level < self.min_level or level > self.max_level:\n            return None\n        else:\n            child_mask = gobj.child_mask\n            mask = np.zeros(gobj.ActiveDimensions, dtype=np.uint8)\n            if level == self.max_level:\n                this_level = 1\n            ind[self.axis][0] = 0\n            ind[self.axis][1] = gobj.ActiveDimensions[self.axis]\n            ind[self.px_ax][0] = \\\n                <int> ((self.px - (gobj.LeftEdge).to_ndarray()[self.px_ax]) /\n                       gobj.dds[self.px_ax])\n            ind[self.px_ax][1] = ind[self.px_ax][0] + 1\n            ind[self.py_ax][0] = \\\n                <int> ((self.py - (gobj.LeftEdge).to_ndarray()[self.py_ax]) /\n                       gobj.dds[self.py_ax])\n            ind[self.py_ax][1] = ind[self.py_ax][0] + 1\n\n            with nogil:\n                for i in range(ind[0][0], ind[0][1]):\n                    for j in range(ind[1][0], ind[1][1]):\n                        for k in range(ind[2][0], ind[2][1]):\n                            if this_level == 1 or child_mask[i, j, k]:\n                                mask[i, j, k] = 1\n                                total += 1\n            if total == 0: return None, 0\n            return mask.astype(\"bool\"), total\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        if self.px >= pos[self.px_ax] - 0.5*dds[self.px_ax] and \\\n           self.px <  pos[self.px_ax] + 0.5*dds[self.px_ax] and \\\n           self.py >= pos[self.py_ax] - 0.5*dds[self.py_ax] and \\\n           self.py <  pos[self.py_ax] + 0.5*dds[self.py_ax]:\n            return 1\n        return 0\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        # two 0-volume constructs don't intersect\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef np.float64_t dx = self.periodic_difference(\n            pos[self.px_ax], self.px, self.px_ax)\n        cdef np.float64_t dy = self.periodic_difference(\n            pos[self.py_ax], self.py, self.py_ax)\n        if dx*dx + dy*dy < radius*radius:\n            return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        if left_edge[self.px_ax] <= self.px < right_edge[self.px_ax] and \\\n           left_edge[self.py_ax] <= self.py < right_edge[self.py_ax] :\n            return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        if left_edge[self.px_ax] <= self.px < right_edge[self.px_ax] and \\\n           left_edge[self.py_ax] <= self.py < right_edge[self.py_ax] :\n            return 2 # a box of non-zero volume can't be inside a ray\n        return 0\n\n    def _hash_vals(self):\n        return ((\"px_ax\", self.px_ax),\n                (\"py_ax\", self.py_ax),\n                (\"px\", self.px),\n                (\"py\", self.py),\n                (\"axis\", self.axis))\n\n    def _get_state_attnames(self):\n        return (\"px_ax\", \"py_ax\", \"px\", \"py\", \"axis\")\n\northo_ray_selector = OrthoRaySelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/point_selector.pxi",
    "content": "cdef class PointSelector(SelectorObject):\n    cdef public np.float64_t p[3]\n\n    def __init__(self, dobj):\n        cdef const np.float64_t[:] DLE = dobj.ds.domain_left_edge\n        cdef const np.float64_t[:] DRE = dobj.ds.domain_right_edge\n        for i in range(3):\n            self.p[i] = _ensure_code(dobj.p[i])\n\n            # ensure the point lies in the domain\n            if self.periodicity[i]:\n                self.p[i] = np.fmod(self.p[i], self.domain_width[i])\n                if self.p[i] < DLE[i]:\n                    self.p[i] += self.domain_width[i]\n                elif self.p[i] >= DRE[i]:\n                    self.p[i] -= self.domain_width[i]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        if (pos[0] - 0.5*dds[0] <= self.p[0] < pos[0]+0.5*dds[0] and\n            pos[1] - 0.5*dds[1] <= self.p[1] < pos[1]+0.5*dds[1] and\n            pos[2] - 0.5*dds[2] <= self.p[2] < pos[2]+0.5*dds[2]):\n            return 1\n        else:\n            return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef int i\n        cdef np.float64_t dist, dist2 = 0\n        for i in range(3):\n            dist = self.periodic_difference(pos[i], self.p[i], i)\n            dist2 += dist*dist\n        if dist2 <= radius*radius: return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        # point definitely can only be in one cell\n        if (left_edge[0] <= self.p[0] < right_edge[0] and\n            left_edge[1] <= self.p[1] < right_edge[1] and\n            left_edge[2] <= self.p[2] < right_edge[2]):\n            return 1\n        else:\n            return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        # point definitely can only be in one cell\n        # Return 2 in all cases to indicate that the point only overlaps\n        # portion of box\n        if (left_edge[0] <= self.p[0] <= right_edge[0] and\n            left_edge[1] <= self.p[1] <= right_edge[1] and\n            left_edge[2] <= self.p[2] <= right_edge[2]):\n            return 2\n        else:\n            return 0\n\n    def _hash_vals(self):\n        return ((\"p[0]\", self.p[0]),\n                (\"p[1]\", self.p[1]),\n                (\"p[2]\", self.p[2]))\n\n    def _get_state_attnames(self):\n        return ('p', )\n\npoint_selector = PointSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/ray_selector.pxi",
    "content": "cdef struct IntegrationAccumulator:\n    np.float64_t *t\n    np.float64_t *dt\n    np.uint8_t *child_mask\n    int hits\n\ncdef void dt_sampler(\n             VolumeContainer *vc,\n             np.float64_t v_pos[3],\n             np.float64_t v_dir[3],\n             np.float64_t enter_t,\n             np.float64_t exit_t,\n             int index[3],\n             void *data) noexcept nogil:\n    cdef IntegrationAccumulator *am = <IntegrationAccumulator *> data\n    cdef int di = (index[0]*vc.dims[1]+index[1])*vc.dims[2]+index[2]\n    if am.child_mask[di] == 0 or enter_t == exit_t:\n        return\n    am.hits += 1\n    am.t[di] = enter_t\n    am.dt[di] = (exit_t - enter_t)\n\ncdef class RaySelector(SelectorObject):\n\n    cdef public np.float64_t p1[3]\n    cdef public np.float64_t p2[3]\n    cdef public np.float64_t vec[3]\n\n    def __init__(self, dobj):\n        cdef int i\n        _ensure_code(dobj.start_point)\n        _ensure_code(dobj.end_point)\n        for i in range(3):\n            self.vec[i] = dobj.vec[i]\n            self.p1[i] = dobj.start_point[i]\n            self.p2[i] = dobj.end_point[i]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_mask_regular_grid(self, gobj):\n        cdef np.ndarray[np.float64_t, ndim=3] t, dt\n        cdef np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask\n        cdef int i\n        cdef int total = 0\n        cdef IntegrationAccumulator *ia\n        ia = <IntegrationAccumulator *> malloc(sizeof(IntegrationAccumulator))\n        cdef VolumeContainer vc\n        mask = np.zeros(gobj.ActiveDimensions, dtype='uint8')\n        t = np.zeros(gobj.ActiveDimensions, dtype=\"float64\")\n        dt = np.zeros(gobj.ActiveDimensions, dtype=\"float64\") - 1\n        child_mask = gobj.child_mask\n        ia.t = <np.float64_t *> t.data\n        ia.dt = <np.float64_t *> dt.data\n        ia.child_mask = <np.uint8_t *> child_mask.data\n        ia.hits = 0\n        _ensure_code(gobj.LeftEdge)\n        _ensure_code(gobj.RightEdge)\n        _ensure_code(gobj.dds)\n        for i in range(3):\n            vc.left_edge[i] = gobj.LeftEdge[i]\n            vc.right_edge[i] = gobj.RightEdge[i]\n            vc.dds[i] = gobj.dds[i]\n            vc.idds[i] = 1.0/gobj.dds[i]\n            vc.dims[i] = dt.shape[i]\n        walk_volume(&vc, self.p1, self.vec, dt_sampler, <void*> ia)\n        for i in range(dt.shape[0]):\n            for j in range(dt.shape[1]):\n                for k in range(dt.shape[2]):\n                    if dt[i, j, k] >= 0:\n                        mask[i, j, k] = 1\n                        total += 1\n        free(ia)\n        if total == 0: return None, 0\n        return mask.astype(\"bool\"), total\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def get_dt(self, gobj):\n        cdef np.ndarray[np.float64_t, ndim=3] t, dt\n        cdef np.ndarray[np.float64_t, ndim=1] tr, dtr\n        cdef np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask\n        cdef int i, j, k, ni\n        cdef IntegrationAccumulator *ia\n        ia = <IntegrationAccumulator *> malloc(sizeof(IntegrationAccumulator))\n        cdef VolumeContainer vc\n        t = np.zeros(gobj.ActiveDimensions, dtype=\"float64\")\n        dt = np.zeros(gobj.ActiveDimensions, dtype=\"float64\") - 1\n        child_mask = gobj.child_mask\n        ia.t = <np.float64_t *> t.data\n        ia.dt = <np.float64_t *> dt.data\n        ia.child_mask = <np.uint8_t *> child_mask.data\n        ia.hits = 0\n        _ensure_code(gobj.LeftEdge)\n        _ensure_code(gobj.RightEdge)\n        _ensure_code(gobj.dds)\n        for i in range(3):\n            vc.left_edge[i] = gobj.LeftEdge[i]\n            vc.right_edge[i] = gobj.RightEdge[i]\n            vc.dds[i] = gobj.dds[i]\n            vc.idds[i] = 1.0/gobj.dds[i]\n            vc.dims[i] = dt.shape[i]\n        walk_volume(&vc, self.p1, self.vec, dt_sampler, <void*> ia)\n        tr = np.zeros(ia.hits, dtype=\"float64\")\n        dtr = np.zeros(ia.hits, dtype=\"float64\")\n        ni = 0\n        for i in range(dt.shape[0]):\n            for j in range(dt.shape[1]):\n                for k in range(dt.shape[2]):\n                    if dt[i, j, k] >= 0:\n                        tr[ni] = t[i, j, k]\n                        dtr[ni] = dt[i, j, k]\n                        ni += 1\n        if not (ni == ia.hits):\n            print(ni, ia.hits)\n        free(ia)\n        return dtr, tr\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def get_dt_mesh(self, mesh, nz, int offset):\n        cdef np.ndarray[np.float64_t, ndim=3] t, dt\n        cdef np.ndarray[np.float64_t, ndim=1] tr, dtr\n        cdef np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask\n        cdef int i, j, k, ni\n        cdef np.float64_t LE[3]\n        cdef np.float64_t RE[3]\n        cdef np.float64_t pos\n        cdef IntegrationAccumulator *ia\n        ia = <IntegrationAccumulator *> malloc(sizeof(IntegrationAccumulator))\n        cdef np.ndarray[np.float64_t, ndim=2] coords\n        cdef np.ndarray[np.int64_t, ndim=2] indices\n        indices = mesh.connectivity_indices\n        coords = _ensure_code(mesh.connectivity_coords)\n        cdef int nc = indices.shape[0]\n        cdef int nv = indices.shape[1]\n        if nv != 8:\n            raise NotImplementedError\n        cdef VolumeContainer vc\n        child_mask = np.ones((1,1,1), dtype=\"uint8\")\n        t = np.zeros((1,1,1), dtype=\"float64\")\n        dt = np.zeros((1,1,1), dtype=\"float64\") - 1\n        tr = np.zeros(nz, dtype=\"float64\")\n        dtr = np.zeros(nz, dtype=\"float64\")\n        ia.t = <np.float64_t *> t.data\n        ia.dt = <np.float64_t *> dt.data\n        ia.child_mask = <np.uint8_t *> child_mask.data\n        ia.hits = 0\n        ni = 0\n        for i in range(nc):\n            for j in range(3):\n                LE[j] = 1e60\n                RE[j] = -1e60\n            for j in range(nv):\n                for k in range(3):\n                    pos = coords[indices[i, j] - offset, k]\n                    LE[k] = fmin(pos, LE[k])\n                    RE[k] = fmax(pos, RE[k])\n            for j in range(3):\n                vc.left_edge[j] = LE[j]\n                vc.right_edge[j] = RE[j]\n                vc.dds[j] = RE[j] - LE[j]\n                vc.idds[j] = 1.0/vc.dds[j]\n                vc.dims[j] = 1\n            t[0,0,0] = dt[0,0,0] = -1\n            walk_volume(&vc, self.p1, self.vec, dt_sampler, <void*> ia)\n            if dt[0,0,0] >= 0:\n                tr[ni] = t[0,0,0]\n                dtr[ni] = dt[0,0,0]\n                ni += 1\n        free(ia)\n        return dtr, tr\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        # two 0-volume constructs don't intersect\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n\n        cdef int i\n        cdef np.float64_t length = norm(self.vec)\n        cdef np.float64_t r[3]\n        for i in range(3):\n            r[i] = pos[i] - self.p1[i]\n        # the projected position of the sphere along the ray\n        cdef np.float64_t l = dot(r, self.vec) / length\n        # the square of the impact parameter\n        cdef np.float64_t b_sqr = dot(r, r) - l*l\n\n        # only accept spheres with radii larger than the impact parameter and\n        # with a projected position along the ray no more than a radius away\n        # from the ray\n        if -radius < l and l < (length+radius) and b_sqr < radius*radius:\n            return 1\n\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef int i, rv\n        cdef VolumeContainer vc\n        cdef IntegrationAccumulator *ia\n        ia = <IntegrationAccumulator *> malloc(sizeof(IntegrationAccumulator))\n        cdef np.float64_t dt[1]\n        cdef np.float64_t t[1]\n        cdef np.uint8_t cm[1]\n        for i in range(3):\n            vc.left_edge[i] = left_edge[i]\n            vc.right_edge[i] = right_edge[i]\n            vc.dds[i] = right_edge[i] - left_edge[i]\n            vc.idds[i] = 1.0/vc.dds[i]\n            vc.dims[i] = 1\n        t[0] = dt[0] = 0.0\n        cm[0] = 1\n        ia.t = t\n        ia.dt = dt\n        ia.child_mask = cm\n        ia.hits = 0\n        walk_volume(&vc, self.p1, self.vec, dt_sampler, <void*> ia)\n        rv = 0\n        if ia.hits > 0:\n            rv = 1\n        free(ia)\n        return rv\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef int i\n        cdef np.uint8_t cm = 1\n        cdef VolumeContainer vc\n        cdef IntegrationAccumulator ia\n        cdef np.float64_t dt, t\n        for i in range(3):\n            vc.left_edge[i] = left_edge[i]\n            vc.right_edge[i] = right_edge[i]\n            vc.dds[i] = right_edge[i] - left_edge[i]\n            vc.idds[i] = 1.0/vc.dds[i]\n            vc.dims[i] = 1\n        t = dt = 0.0\n        ia.t = &t\n        ia.dt = &dt\n        ia.child_mask = &cm\n        ia.hits = 0\n        walk_volume(&vc, self.p1, self.vec, dt_sampler, <void*> &ia)\n        if ia.hits > 0:\n            return 2 # a box of non-zero volume cannot be inside a ray\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3],\n                               np.float64_t dds[3]) noexcept nogil:\n        # This is terribly inefficient for Octrees.  For grids, it will never\n        # get called.\n        cdef int i\n        cdef np.float64_t left_edge[3]\n        cdef np.float64_t right_edge[3]\n        for i in range(3):\n            left_edge[i] = pos[i] - dds[i]/2.0\n            right_edge[i] = pos[i] + dds[i]/2.0\n        return self.select_bbox(left_edge, right_edge)\n\n    def _hash_vals(self):\n        return ((\"p1[0]\", self.p1[0]),\n                (\"p1[1]\", self.p1[1]),\n                (\"p1[2]\", self.p1[2]),\n                (\"p2[0]\", self.p2[0]),\n                (\"p2[1]\", self.p2[1]),\n                (\"p2[2]\", self.p2[2]),\n                (\"vec[0]\", self.vec[0]),\n                (\"vec[1]\", self.vec[1]),\n                (\"vec[2]\", self.vec[2]))\n\n    def _get_state_attnames(self):\n        return (\"p1\", \"p2\", \"vec\")\n\nray_selector = RaySelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/region_selector.pxi",
    "content": "cdef class RegionSelector(SelectorObject):\n    cdef public np.float64_t left_edge[3]\n    cdef public np.float64_t right_edge[3]\n    cdef public np.float64_t right_edge_shift[3]\n    cdef public bint is_all_data\n    cdef public bint loose_selection\n    cdef public bint check_period[3]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def __init__(self, dobj):\n        cdef int i\n        # We are modifying dobj.left_edge and dobj.right_edge , so here we will\n        # do an in-place conversion of those arrays.\n        cdef np.float64_t[:] RE = dobj.right_edge.copy()\n        cdef np.float64_t[:] LE = dobj.left_edge.copy()\n        cdef const np.float64_t[:] DW = dobj.ds.domain_width\n        cdef const np.float64_t[:] DLE = dobj.ds.domain_left_edge\n        cdef const np.float64_t[:] DRE = dobj.ds.domain_right_edge\n        le_all = (np.array(LE) == dobj.ds.domain_left_edge).all()\n        re_all = (np.array(RE) == dobj.ds.domain_right_edge).all()\n        # If we have a bounding box, then we should *not* revert to all data\n        domain_override = getattr(dobj.ds, '_domain_override', False)\n        if le_all and re_all and not domain_override:\n            self.is_all_data = True\n        else:\n            self.is_all_data = False\n        cdef np.float64_t region_width[3]\n        cdef bint p[3]\n        # This is for if we want to include zones that overlap and whose\n        # centers are not strictly included.\n        self.loose_selection = getattr(dobj, \"loose_selection\", False)\n\n        for i in range(3):\n            self.check_period[i] = False\n            region_width[i] = RE[i] - LE[i]\n            p[i] = dobj.ds.periodicity[i]\n            if region_width[i] <= 0:\n                raise RuntimeError(\n                    \"Region right edge[%s] < left edge: width = %s\" % (\n                        i, region_width[i]))\n\n        for i in range(3):\n\n            if p[i]:\n                # First, we check if any criteria requires a period check,\n                # without any adjustments.  This is for short-circuiting the\n                # short-circuit of the loop down below in mask filling.\n                if LE[i] < DLE[i] or LE[i] > DRE[i] or RE[i] > DRE[i]:\n                    self.check_period[i] = True\n                # shift so left_edge guaranteed in domain\n                if LE[i] < DLE[i]:\n                    LE[i] += DW[i]\n                    RE[i] += DW[i]\n                elif LE[i] > DRE[i]:\n                    LE[i] -= DW[i]\n                    RE[i] -= DW[i]\n            else:\n                if LE[i] < DLE[i] or RE[i] > DRE[i]:\n                    raise RuntimeError(\n                        \"yt attempted to read outside the boundaries of \"\n                        \"a non-periodic domain along dimension %s.\\n\"\n                        \"Region left edge = %s, Region right edge = %s\\n\"\n                        \"Dataset left edge = %s, Dataset right edge = %s\\n\\n\"\n                        \"This commonly happens when trying to compute ghost cells \"\n                        \"up to the domain boundary. Two possible solutions are to \"\n                        \"select a smaller region that does not border domain edge \"\n                        \"(see https://yt-project.org/docs/analyzing/objects.html?highlight=region)\\n\"\n                        \"or override the periodicity with\\n\"\n                        \"ds.force_periodicity()\" % \\\n                        (i, dobj.left_edge[i], dobj.right_edge[i],\n                         dobj.ds.domain_left_edge[i], dobj.ds.domain_right_edge[i])\n                    )\n            # Already ensured in code\n            self.left_edge[i] = LE[i]\n            self.right_edge[i] = RE[i]\n            self.right_edge_shift[i] = RE[i] - DW[i]\n            if not self.periodicity[i]:\n                self.right_edge_shift[i] = -np.inf\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef int i\n        for i in range(3):\n            if (right_edge[i] < self.left_edge[i] and \\\n                left_edge[i] >= self.right_edge_shift[i]) or \\\n                left_edge[i] >= self.right_edge[i]:\n                return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef int i\n        for i in range(3):\n            if (right_edge[i] < self.left_edge[i] and \\\n                left_edge[i] >= self.right_edge_shift[i]) or \\\n                left_edge[i] >= self.right_edge[i]:\n                return 0\n        for i in range(3):\n            if left_edge[i] < self.right_edge_shift[i]:\n                if right_edge[i] >= self.right_edge_shift[i]:\n                    return 2\n            elif left_edge[i] < self.left_edge[i] or \\\n                 right_edge[i] >= self.right_edge[i]:\n                return 2\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        cdef np.float64_t left_edge[3]\n        cdef np.float64_t right_edge[3]\n        cdef int i\n        if self.loose_selection:\n            for i in range(3):\n                left_edge[i] = pos[i] - dds[i]*0.5\n                right_edge[i] = pos[i] + dds[i]*0.5\n            return self.select_bbox(left_edge, right_edge)\n        return self.select_point(pos)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef int i\n        for i in range(3):\n            if (self.right_edge_shift[i] <= pos[i] < self.left_edge[i]) or \\\n               pos[i] >= self.right_edge[i]:\n                return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        # adapted from http://stackoverflow.com/a/4579192/1382869\n        cdef int i\n        cdef np.float64_t p\n        cdef np.float64_t r2 = radius**2\n        cdef np.float64_t dmin = 0\n        cdef np.float64_t d = 0\n        for i in range(3):\n            if (pos[i]+radius < self.left_edge[i] and \\\n                pos[i]-radius >= self.right_edge_shift[i]):\n                d = self.periodic_difference(pos[i], self.left_edge[i], i)\n            elif pos[i]-radius > self.right_edge[i]:\n                d = self.periodic_difference(pos[i], self.right_edge[i], i)\n            dmin += d*d\n        return int(dmin <= r2)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int fill_mask_selector_regular_grid(self, np.float64_t left_edge[3],\n                                np.float64_t right_edge[3],\n                                np.float64_t dds[3], int dim[3],\n                                np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask,\n                                np.ndarray[np.uint8_t, ndim=3] mask,\n                                int level):\n        cdef int i, j, k\n        cdef int total = 0, this_level = 0\n        cdef np.float64_t pos[3]\n        if level < self.min_level or level > self.max_level:\n            return 0\n        if level == self.max_level:\n            this_level = 1\n        cdef np.int64_t si[3]\n        cdef np.int64_t ei[3]\n        for i in range(3):\n            if not self.check_period[i]:\n                si[i] = <np.int64_t> ((self.left_edge[i] - left_edge[i])/dds[i])\n                ei[i] = <np.int64_t> ((self.right_edge[i] - left_edge[i])/dds[i])\n                si[i] = iclip(si[i] - 1, 0, dim[i])\n                ei[i] = iclip(ei[i] + 1, 0, dim[i])\n            else:\n                si[i] = 0\n                ei[i] = dim[i]\n        with nogil:\n\n            for i in range(si[0], ei[0]):\n                pos[0] = left_edge[0] + (i + 0.5) * dds[0]\n                for j in range(si[1], ei[1]):\n                    pos[1] = left_edge[1] + (j + 0.5) * dds[1]\n                    for k in range(si[2], ei[2]):\n                        pos[2] = left_edge[2] + (k + 0.5) * dds[2]\n                        if child_mask[i, j, k] == 1 or this_level == 1:\n                            mask[i, j, k] = self.select_cell(pos, dds)\n                            total += mask[i, j, k]\n        return total\n\n    def _hash_vals(self):\n        return ((\"left_edge[0]\", self.left_edge[0]),\n                (\"left_edge[1]\", self.left_edge[1]),\n                (\"left_edge[2]\", self.left_edge[2]),\n                (\"right_edge[0]\", self.right_edge[0]),\n                (\"right_edge[1]\", self.right_edge[1]),\n                (\"right_edge[2]\", self.right_edge[2]))\n\n    def _get_state_attnames(self):\n        return ('left_edge', 'right_edge', 'right_edge_shift', 'check_period',\n                'is_all_data', 'loose_selection')\n\nregion_selector = RegionSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/selector_object.pxi",
    "content": "from .oct_visitors cimport CountTotalCells, CountTotalOcts\n\n\ncdef class SelectorObject:\n\n    def __cinit__(self, dobj, *args):\n        self._hash_initialized = 0\n        cdef const np.float64_t [:] DLE\n        cdef const np.float64_t [:] DRE\n        min_level = getattr(dobj, \"min_level\", None)\n        max_level = getattr(dobj, \"max_level\", None)\n        if min_level is None:\n            min_level = 0\n        if max_level is None:\n            max_level = 99\n        self.min_level = min_level\n        self.max_level = max_level\n        self.overlap_cells = 0\n\n        ds = getattr(dobj, 'ds', None)\n        if ds is None:\n            for i in range(3):\n                # NOTE that this is not universal.\n                self.domain_width[i] = 1.0\n                self.periodicity[i] = False\n        else:\n            DLE = ds.domain_left_edge\n            DRE = ds.domain_right_edge\n            for i in range(3):\n                self.domain_width[i] = DRE[i] - DLE[i]\n                self.domain_center[i] = DLE[i] + 0.5 * self.domain_width[i]\n                self.periodicity[i] = ds.periodicity[i]\n\n    def get_periodicity(self):\n        cdef int i\n        cdef np.ndarray[np.uint8_t, ndim=1] periodicity\n        periodicity = np.zeros(3, dtype='uint8')\n        for i in range(3):\n            periodicity[i] = self.periodicity[i]\n        return periodicity\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def select_grids(self,\n                     np.ndarray[np.float64_t, ndim=2] left_edges,\n                     np.ndarray[np.float64_t, ndim=2] right_edges,\n                     np.ndarray[np.int32_t, ndim=2] levels):\n        cdef int i, n\n        cdef int ng = left_edges.shape[0]\n        cdef np.ndarray[np.uint8_t, ndim=1] gridi = np.zeros(ng, dtype='uint8')\n        cdef np.float64_t LE[3]\n        cdef np.float64_t RE[3]\n        _ensure_code(left_edges)\n        _ensure_code(right_edges)\n        with nogil:\n            for n in range(ng):\n                # Call our selector function\n                # Check if the sphere is inside the grid\n                for i in range(3):\n                    LE[i] = left_edges[n, i]\n                    RE[i] = right_edges[n, i]\n                gridi[n] = self.select_grid(LE, RE, levels[n, 0])\n        return gridi.astype(\"bool\")\n\n    def count_octs(self, OctreeContainer octree, int domain_id = -1):\n        cdef CountTotalOcts visitor\n        visitor = CountTotalOcts(octree, domain_id)\n        octree.visit_all_octs(self, visitor)\n        return visitor.index\n\n    def count_oct_cells(self, OctreeContainer octree, int domain_id = -1):\n        cdef CountTotalCells visitor\n        visitor = CountTotalCells(octree, domain_id)\n        octree.visit_all_octs(self, visitor)\n        return visitor.index\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void recursively_visit_octs(self, Oct *root,\n                        np.float64_t pos[3], np.float64_t dds[3],\n                        int level,\n                        OctVisitor visitor,\n                        int visit_covered = 0):\n        # visit_covered tells us whether this octree supports partial\n        # refinement.  If it does, we need to handle this specially -- first\n        # we visit *this* oct, then we make a second pass to check any child\n        # octs.\n        cdef np.float64_t LE[3]\n        cdef np.float64_t RE[3]\n        cdef np.float64_t sdds[3]\n        cdef np.float64_t spos[3]\n        cdef int i, j, k, res\n        cdef Oct *ch\n        # Remember that pos is the *center* of the oct, and dds is the oct\n        # width.  So to get to the edges, we add/subtract half of dds.\n        for i in range(3):\n            # sdds is the cell width\n            sdds[i] = dds[i]/2.0\n            LE[i] = pos[i] - dds[i]/2.0\n            RE[i] = pos[i] + dds[i]/2.0\n        #print(LE[0], RE[0], LE[1], RE[1], LE[2], RE[2])\n        res = self.select_grid(LE, RE, level, root)\n        if res == 1 and visitor.domain > 0 and root.domain != visitor.domain:\n            res = -1\n        cdef int increment = 1\n        cdef int next_level, this_level\n        # next_level: an int that says whether or not we can progress to children\n        # this_level: an int that says whether or not we can select from this\n        # level\n        next_level = this_level = 1\n        if res == -1:\n            # This happens when we do domain selection but the oct has\n            # children.  This would allow an oct to pass to its children but\n            # not get accessed itself.\n            next_level = 1\n            this_level = 0\n        elif level == self.max_level:\n            next_level = 0\n        elif level < self.min_level or level > self.max_level:\n            this_level = 0\n        if res == 0 and this_level == 1:\n            return\n        # Now we visit all our children.  We subtract off sdds for the first\n        # pass because we center it on the first cell.\n        cdef int iter = 1 - visit_covered # 2 if 1, 1 if 0.\n        # So the order here goes like so.  If visit_covered is 1, which usually\n        # comes from \"partial_coverage\", we visit the components of a zone even\n        # if it has children.  But in general, the first iteration through, we\n        # visit each cell.  This means that only if visit_covered is true do we\n        # visit potentially covered cells.  The next time through, we visit\n        # child cells.\n        while iter < 2:\n            spos[0] = pos[0] - sdds[0]/2.0\n            for i in range(2):\n                spos[1] = pos[1] - sdds[1]/2.0\n                for j in range(2):\n                    spos[2] = pos[2] - sdds[2]/2.0\n                    for k in range(2):\n                        ch = NULL\n                        # We only supply a child if we are actually going to\n                        # look at the next level.\n                        if root.children != NULL and next_level == 1:\n                            ch = root.children[cind(i, j, k)]\n                        if iter == 1 and next_level == 1 and ch != NULL:\n                            # Note that visitor.pos is always going to be the\n                            # position of the Oct -- it is *not* always going\n                            # to be the same as the position of the cell under\n                            # investigation.\n                            visitor.pos[0] = (visitor.pos[0] << 1) + i\n                            visitor.pos[1] = (visitor.pos[1] << 1) + j\n                            visitor.pos[2] = (visitor.pos[2] << 1) + k\n                            visitor.level += 1\n                            self.recursively_visit_octs(\n                                ch, spos, sdds, level + 1, visitor,\n                                visit_covered)\n                            visitor.pos[0] = (visitor.pos[0] >> 1)\n                            visitor.pos[1] = (visitor.pos[1] >> 1)\n                            visitor.pos[2] = (visitor.pos[2] >> 1)\n                            visitor.level -= 1\n                        elif this_level == 1 and visitor.nz > 1:\n                            visitor.global_index += increment\n                            increment = 0\n                            self.visit_oct_cells(root, ch, spos, sdds,\n                                                 visitor, i, j, k)\n                        elif this_level == 1 and increment == 1:\n                            visitor.global_index += increment\n                            increment = 0\n                            visitor.ind[0] = visitor.ind[1] = visitor.ind[2] = 0\n                            visitor.visit(root, 1)\n                        spos[2] += sdds[2]\n                    spos[1] += sdds[1]\n                spos[0] += sdds[0]\n            this_level = 0 # We turn this off for the second pass.\n            iter += 1\n\n    cdef void visit_oct_cells(self, Oct *root, Oct *ch,\n                              np.float64_t spos[3], np.float64_t sdds[3],\n                              OctVisitor visitor, int i, int j, int k):\n        # We can short-circuit the whole process if data.nz == 2.\n        # This saves us some funny-business.\n        cdef int selected\n        if visitor.nz == 2:\n            selected = self.select_cell(spos, sdds)\n            if ch != NULL:\n                selected *= self.overlap_cells\n            # visitor.ind refers to the cell, not to the oct.\n            visitor.ind[0] = i\n            visitor.ind[1] = j\n            visitor.ind[2] = k\n            visitor.visit(root, selected)\n            return\n        # Okay, now that we've got that out of the way, we have to do some\n        # other checks here.  In this case, spos[] is the position of the\n        # center of a *possible* oct child, which means it is the center of a\n        # cluster of cells.  That cluster might have 1, 8, 64, ... cells in it.\n        # But, we can figure it out by calculating the cell dds.\n        cdef np.float64_t dds[3]\n        cdef np.float64_t pos[3]\n        cdef int ci, cj, ck\n        cdef int nr = (visitor.nz >> 1)\n        for ci in range(3):\n            dds[ci] = sdds[ci] / nr\n        # Boot strap at the first index.\n        pos[0] = (spos[0] - sdds[0]/2.0) + dds[0] * 0.5\n        for ci in range(nr):\n            pos[1] = (spos[1] - sdds[1]/2.0) + dds[1] * 0.5\n            for cj in range(nr):\n                pos[2] = (spos[2] - sdds[2]/2.0) + dds[2] * 0.5\n                for ck in range(nr):\n                    selected = self.select_cell(pos, dds)\n                    if ch != NULL:\n                        selected *= self.overlap_cells\n                    visitor.ind[0] = ci + i * nr\n                    visitor.ind[1] = cj + j * nr\n                    visitor.ind[2] = ck + k * nr\n                    visitor.visit(root, selected)\n                    pos[2] += dds[2]\n                pos[1] += dds[1]\n            pos[0] += dds[0]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3],\n                               np.int32_t level, Oct *o = NULL) noexcept nogil:\n        if level < self.min_level or level > self.max_level: return 0\n        return self.select_bbox(left_edge, right_edge)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_grid_edge(self, np.float64_t left_edge[3],\n                                    np.float64_t right_edge[3],\n                                    np.int32_t level, Oct *o = NULL) noexcept nogil:\n        if level < self.min_level or level > self.max_level: return 0\n        return self.select_bbox_edge(left_edge, right_edge)\n\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        return 0\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        return 0\n\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        return 0\n\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        \"\"\"\n        Returns:\n          0: If the selector does not touch the bounding box.\n          1: If the selector overlaps the bounding box anywhere.\n        \"\"\"\n        return 0\n\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        \"\"\"\n        Returns:\n          0: If the selector does not touch the bounding box.\n          1: If the selector contains the entire bounding box.\n          2: If the selector contains part of the bounding box.\n        \"\"\"\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef np.float64_t periodic_difference(self, np.float64_t x1, np.float64_t x2, int d) noexcept nogil:\n        # domain_width is already in code units, and we assume what is fed in\n        # is too.\n        cdef np.float64_t rel = x1 - x2\n        if self.periodicity[d]:\n            if rel > self.domain_width[d] * 0.5:\n                rel -= self.domain_width[d]\n            elif rel < -self.domain_width[d] * 0.5:\n                rel += self.domain_width[d]\n        return rel\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_mesh_mask(self, mesh):\n        cdef np.float64_t pos[3]\n        cdef np.ndarray[np.int64_t, ndim=2] indices\n        cdef np.ndarray[np.float64_t, ndim=2] coords\n        cdef np.ndarray[np.uint8_t, ndim=1] mask\n        cdef int i, j, k, selected\n        cdef int npoints, nv = mesh._connectivity_length\n        cdef int total = 0\n        cdef int offset = mesh._index_offset\n        coords = _ensure_code(mesh.connectivity_coords)\n        indices = mesh.connectivity_indices\n        npoints = indices.shape[0]\n        mask = np.zeros(npoints, dtype='uint8')\n        for i in range(npoints):\n            selected = 0\n            for j in range(nv):\n                for k in range(3):\n                    pos[k] = coords[indices[i, j] - offset, k]\n                selected = self.select_point(pos)\n                if selected == 1: break\n            total += selected\n            mask[i] = selected\n        if total == 0: return None\n        return mask.astype(\"bool\")\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_mesh_cell_mask(self, mesh):\n        cdef np.float64_t pos\n        cdef np.float64_t le[3]\n        cdef np.float64_t re[3]\n        cdef np.ndarray[np.int64_t, ndim=2] indices\n        cdef np.ndarray[np.float64_t, ndim=2] coords\n        cdef np.ndarray[np.uint8_t, ndim=1] mask\n        cdef int i, j, k, selected\n        cdef int npoints, nv = mesh._connectivity_length\n        cdef int ndim = mesh.connectivity_coords.shape[1]\n        cdef int total = 0\n        cdef int offset = mesh._index_offset\n        coords = _ensure_code(mesh.connectivity_coords)\n        indices = mesh.connectivity_indices\n        npoints = indices.shape[0]\n        mask = np.zeros(npoints, dtype='uint8')\n        for i in range(npoints):\n            selected = 0\n            for k in range(3):\n                le[k] = 1e60\n                re[k] = -1e60\n            for j in range(nv):\n                for k in range(ndim):\n                    pos = coords[indices[i, j] - offset, k]\n                    le[k] = fmin(pos, le[k])\n                    re[k] = fmax(pos, re[k])\n                for k in range(2, ndim - 1, -1):\n                    le[k] = self.domain_center[k]\n                    re[k] = self.domain_center[k]\n            selected = self.select_bbox(le, re)\n            total += selected\n            mask[i] = selected\n        if total == 0: return None\n        return mask.astype(\"bool\")\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_mask_regular_grid(self, gobj):\n        cdef np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask\n        child_mask = gobj.child_mask\n        cdef np.ndarray[np.uint8_t, ndim=3] mask\n        cdef int dim[3]\n        _ensure_code(gobj.dds)\n        _ensure_code(gobj.LeftEdge)\n        _ensure_code(gobj.RightEdge)\n        cdef np.ndarray[np.float64_t, ndim=1] odds = gobj.dds.d\n        cdef np.ndarray[np.float64_t, ndim=1] oleft_edge = gobj.LeftEdge.d\n        cdef np.ndarray[np.float64_t, ndim=1] oright_edge = gobj.RightEdge.d\n        cdef int i\n        cdef np.float64_t dds[3]\n        cdef np.float64_t left_edge[3]\n        cdef np.float64_t right_edge[3]\n        for i in range(3):\n            dds[i] = odds[i]\n            dim[i] = gobj.ActiveDimensions[i]\n            left_edge[i] = oleft_edge[i]\n            right_edge[i] = oright_edge[i]\n        mask = np.zeros(gobj.ActiveDimensions, dtype='uint8')\n        # Check for the level bounds\n        cdef np.int32_t level = gobj.Level\n        # We set this to 1 if we ignore child_mask\n        cdef int total\n        total = self.fill_mask_selector_regular_grid(left_edge, right_edge,\n                                                     dds, dim, child_mask,\n                                                     mask, level)\n        if total == 0: return None, 0\n        return mask.astype(\"bool\"), total\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int fill_mask_selector_regular_grid(self, np.float64_t left_edge[3],\n                                np.float64_t right_edge[3],\n                                np.float64_t dds[3], int dim[3],\n                                np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask,\n                                np.ndarray[np.uint8_t, ndim=3] mask,\n                                int level):\n        cdef int i, j, k\n        cdef int total = 0, this_level = 0\n        cdef np.float64_t pos[3]\n        if level < self.min_level or level > self.max_level:\n            return 0\n        if level == self.max_level:\n            this_level = 1\n        with nogil:\n            for i in range(dim[0]):\n                pos[0] = left_edge[0] + (i + 0.5) * dds[0]\n                for j in range(dim[1]):\n                    pos[1] = left_edge[1] + (j + 0.5) * dds[1]\n                    for k in range(dim[2]):\n                        pos[2] = left_edge[2] + (k + 0.5) * dds[2]\n                        if child_mask[i, j, k] == 1 or this_level == 1:\n                            mask[i, j, k] = self.select_cell(pos, dds)\n                            total += mask[i, j, k]\n        return total\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_mask(self, gobj):\n        # This is for an irregular grid.  We make no assumptions about the\n        # shape of the dds values, which are supplied as differing-length\n        # arrays.\n        cdef np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask\n        child_mask = gobj.child_mask\n        cdef np.ndarray[np.uint8_t, ndim=3] mask\n        cdef int dim[3]\n        _ensure_code(gobj.cell_widths[0])\n        _ensure_code(gobj.cell_widths[1])\n        _ensure_code(gobj.cell_widths[2])\n        _ensure_code(gobj.LeftEdge)\n        _ensure_code(gobj.RightEdge)\n        cdef np.ndarray[np.float64_t, ndim=1] oleft_edge = gobj.LeftEdge.d\n        cdef np.ndarray[np.float64_t, ndim=1] oright_edge = gobj.RightEdge.d\n        cdef np.ndarray[np.float64_t, ndim=1] ocell_width\n        cdef int i, n = 0\n        cdef np.float64_t left_edge[3]\n        cdef np.float64_t right_edge[3]\n        cdef np.float64_t **dds\n        dds = <np.float64_t**> malloc(sizeof(np.float64_t*)*3)\n        for i in range(3):\n            dim[i] = gobj.ActiveDimensions[i]\n            left_edge[i] = oleft_edge[i]\n            right_edge[i] = oright_edge[i]\n            dds[i] = <np.float64_t *> malloc(sizeof(np.float64_t) * dim[i])\n            ocell_width = gobj.cell_widths[i]\n            for j in range(dim[i]):\n                dds[i][j] = ocell_width[j]\n        mask = np.zeros(gobj.ActiveDimensions, dtype='uint8')\n        # Check for the level bounds\n        cdef np.int32_t level = gobj.Level\n        # We set this to 1 if we ignore child_mask\n        cdef int total\n        total = self.fill_mask_selector(left_edge, right_edge,\n                                        dds, dim, child_mask,\n                                        mask, level)\n        for i in range(3):\n            free(dds[i])\n        free(dds)\n        if total == 0: return None\n        return mask.astype(\"bool\")\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int fill_mask_selector(self, np.float64_t left_edge[3],\n                                np.float64_t right_edge[3],\n                                np.float64_t **dds, int dim[3],\n                                np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask,\n                                np.ndarray[np.uint8_t, ndim=3] mask,\n                                int level):\n        cdef int i, j, k\n        cdef int total = 0, this_level = 0\n        cdef np.float64_t pos[3]\n        if level < self.min_level or level > self.max_level:\n            return 0\n        if level == self.max_level:\n            this_level = 1\n        cdef np.float64_t *offsets[3]\n        cdef np.float64_t c = 0.0\n        cdef np.float64_t tdds[3]\n        for i in range(3):\n            offsets[i] = <np.float64_t *> malloc(dim[i] * sizeof(np.float64_t))\n            c = left_edge[i]\n            for j in range(dim[i]):\n                offsets[i][j] = c\n                c += dds[i][j]\n        with nogil:\n            # We need to keep in mind that it is entirely possible to\n            # accumulate round-off error by doing lots of additions, etc.\n            # That's one of the reasons we construct (ahead of time) the edge\n            # array. I mean, we don't necessarily *have* to do that, but it\n            # seems OK.\n            for i in range(dim[0]):\n                tdds[0] = dds[0][i]\n                pos[0] = offsets[0][i] + 0.5 * tdds[0]\n                for j in range(dim[1]):\n                    tdds[1] = dds[1][j]\n                    pos[1] = offsets[1][j] + 0.5 * tdds[1]\n                    for k in range(dim[2]):\n                        tdds[2] = dds[2][k]\n                        pos[2] = offsets[2][k] + 0.5 * tdds[2]\n                        if child_mask[i, j, k] == 1 or this_level == 1:\n                            mask[i, j, k] = self.select_cell(pos, tdds)\n                            total += mask[i, j, k]\n        free(offsets[0])\n        free(offsets[1])\n        free(offsets[2])\n        return total\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void visit_grid_cells(self, GridVisitorData *data,\n                              grid_visitor_function *func,\n                              np.uint8_t *cached_mask = NULL):\n        # This function accepts a grid visitor function, the data that\n        # corresponds to the current grid being examined (the most important\n        # aspect of which is the .grid attribute, along with index values and\n        # void* pointers to arrays) and a possibly-pre-generated cached mask.\n        # Each cell is visited with the grid visitor function.\n        cdef np.float64_t left_edge[3]\n        cdef np.float64_t right_edge[3]\n        cdef np.float64_t dds[3]\n        cdef int dim[3]\n        cdef int this_level = 0, level, i\n        cdef np.float64_t pos[3]\n        level = data.grid.level\n        if level < self.min_level or level > self.max_level:\n            return\n        if level == self.max_level:\n            this_level = 1\n        cdef np.uint8_t child_masked, selected\n        for i in range(3):\n            left_edge[i] = data.grid.left_edge[i]\n            right_edge[i] = data.grid.right_edge[i]\n            dds[i] = (right_edge[i] - left_edge[i])/data.grid.dims[i]\n            dim[i] = data.grid.dims[i]\n        with nogil:\n            pos[0] = left_edge[0] + dds[0] * 0.5\n            data.pos[0] = 0\n            for i in range(dim[0]):\n                pos[1] = left_edge[1] + dds[1] * 0.5\n                data.pos[1] = 0\n                for j in range(dim[1]):\n                    pos[2] = left_edge[2] + dds[2] * 0.5\n                    data.pos[2] = 0\n                    for k in range(dim[2]):\n                        # We short-circuit if we have a cache; if we don't, we\n                        # only set selected to true if it's *not* masked by a\n                        # child and it *is* selected.\n                        if cached_mask != NULL:\n                            selected = ba_get_value(cached_mask,\n                                                    data.global_index)\n                        else:\n                            if this_level == 1:\n                                child_masked = 0\n                            else:\n                                child_masked = check_child_masked(data)\n                            if child_masked == 0:\n                                selected = self.select_cell(pos, dds)\n                            else:\n                                selected = 0\n                        func(data, selected)\n                        data.global_index += 1\n                        pos[2] += dds[2]\n                        data.pos[2] += 1\n                    pos[1] += dds[1]\n                    data.pos[1] += 1\n                pos[0] += dds[0]\n                data.pos[0] += 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def count_points(self, np.ndarray[cython.floating, ndim=1] x,\n                           np.ndarray[cython.floating, ndim=1] y,\n                           np.ndarray[cython.floating, ndim=1] z,\n                           radii):\n        cdef int count = 0\n        cdef int i\n        cdef np.float64_t pos[3]\n        cdef np.float64_t radius\n        cdef np.float64_t[:] _radii\n        if radii is not None:\n            _radii = np.atleast_1d(np.array(radii, dtype='float64'))\n        else:\n            _radii = np.array([0.0], dtype='float64')\n        _ensure_code(x)\n        _ensure_code(y)\n        _ensure_code(z)\n        with nogil:\n            for i in range(x.shape[0]):\n                pos[0] = x[i]\n                pos[1] = y[i]\n                pos[2] = z[i]\n                if _radii.shape[0] == 1:\n                    radius = _radii[0]\n                else:\n                    radius = _radii[i]\n                if radius == 0:\n                    count += self.select_point(pos)\n                else:\n                    count += self.select_sphere(pos, radius)\n        return count\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def select_points(self,\n                      np.ndarray[cython.floating, ndim=1] x,\n                      np.ndarray[cython.floating, ndim=1] y,\n                      np.ndarray[cython.floating, ndim=1] z,\n                      radii):\n        cdef int count = 0\n        cdef int i\n        cdef np.float64_t pos[3]\n        cdef np.float64_t radius\n        cdef np.ndarray[np.uint8_t, ndim=1] mask\n        cdef np.float64_t[:] _radii\n        if radii is not None:\n            _radii = np.atleast_1d(np.array(radii, dtype='float64'))\n        else:\n            _radii = np.array([0.0], dtype='float64')\n        mask = np.empty(x.shape[0], dtype='uint8')\n        _ensure_code(x)\n        _ensure_code(y)\n        _ensure_code(z)\n\n\n        # this is to allow selectors to optimize the point vs\n        # 0-radius sphere case.  These two may have different\n        # effects for 0-volume selectors, however (collision\n        # between a ray and a point is null, while ray and a\n        # sphere is allowed)\n        with nogil:\n            for i in range(x.shape[0]) :\n                pos[0] = x[i]\n                pos[1] = y[i]\n                pos[2] = z[i]\n                if _radii.shape[0] == 1:\n                    radius = 0\n                else:\n                    radius = _radii[i]\n                if radius == 0:\n                    mask[i] = self.select_point(pos)\n                else:\n                    mask[i] = self.select_sphere(pos, radius)\n                count += mask[i]\n        if count == 0: return None\n        return mask.view(\"bool\")\n\n    def __hash__(self):\n        # convert data to be hashed to a byte array, which FNV algorithm expects\n        if self._hash_initialized == 1:\n            return self._hash\n        hash_data = bytearray()\n        for v in self._hash_vals() + self._base_hash():\n            if isinstance(v, tuple):\n                hash_data.extend(v[0].encode('ascii'))\n                hash_data.extend(repr(v[1]).encode('ascii'))\n            else:\n                hash_data.extend(repr(v).encode('ascii'))\n        cdef np.int64_t hash_value = fnv_hash(hash_data)\n        self._hash = hash_value\n        self._hash_initialized = 1\n        return hash_value\n\n    def _hash_vals(self):\n        raise NotImplementedError\n\n    def _base_hash(self):\n        return ((\"min_level\", self.min_level),\n                (\"max_level\", self.max_level),\n                (\"overlap_cells\", self.overlap_cells),\n                (\"periodicity[0]\", self.periodicity[0]),\n                (\"periodicity[1]\", self.periodicity[1]),\n                (\"periodicity[2]\", self.periodicity[2]),\n                (\"domain_width[0]\", self.domain_width[0]),\n                (\"domain_width[1]\", self.domain_width[1]),\n                (\"domain_width[2]\", self.domain_width[2]))\n\n    def _get_state_attnames(self):\n        # return a tupe of attr names for __setstate__: implement for each subclass\n        raise NotImplementedError\n\n    def __getstate__(self):\n        # returns a tuple containing (attribute name, attribute value) tuples needed to\n        # rebuild the state:\n        base_atts = (\"min_level\", \"max_level\", \"overlap_cells\",\n                     \"periodicity\", \"domain_width\", \"domain_center\")\n        child_atts = self._get_state_attnames()\n\n        # assemble the state_tuple (('a1', a1val), ('a2', a2val),...)\n        state_tuple = ()\n        for fld in base_atts + child_atts:\n            state_tuple += ((fld, getattr(self, fld)), )\n        return state_tuple\n\n    def __getnewargs__(self):\n        # __setstate__ will always call __cinit__, this pickle hook returns arguments\n        # to __cinit__. We will give it None so we dont error then set attributes in\n        # __setstate__ Note that we could avoid this by making dobj an optional argument\n        # to __cinit__\n        return (None, )\n\n    def __setstate__(self, state_tuple):\n        # parse and set attributes from the state_tuple: (('a1',a1val),('a2',a2val),...)\n        for attr in state_tuple:\n            setattr(self, attr[0], attr[1])\n"
  },
  {
    "path": "yt/geometry/_selection_routines/slice_selector.pxi",
    "content": "cdef class SliceSelector(SelectorObject):\n    cdef public int axis\n    cdef public np.float64_t coord\n    cdef public int ax, ay\n    cdef public int reduced_dimensionality\n\n    def __init__(self, dobj):\n        self.axis = dobj.axis\n        self.coord = _ensure_code(dobj.coord)\n        # If we have a reduced dimensionality dataset, we want to avoid any\n        # checks against it in the axes that are beyond its dimensionality.\n        # This means that if we have a 2D dataset, *all* slices along z will\n        # select all the zones.\n        if self.axis >= dobj.ds.dimensionality:\n            self.reduced_dimensionality = 1\n        else:\n            self.reduced_dimensionality = 0\n\n        self.ax = (self.axis+1) % 3\n        self.ay = (self.axis+2) % 3\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_mask_regular_grid(self, gobj):\n        cdef np.ndarray[np.uint8_t, ndim=3] mask\n        cdef np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask\n        cdef int i, j, k\n        cdef int total = 0\n        cdef int this_level = 0\n        cdef int ind[3][2]\n        cdef np.uint64_t icoord\n        cdef np.int32_t level = gobj.Level\n        _ensure_code(gobj.LeftEdge)\n        _ensure_code(gobj.dds)\n\n        if level < self.min_level or level > self.max_level:\n            return None\n        else:\n            child_mask = gobj.child_mask\n            mask = np.zeros(gobj.ActiveDimensions, dtype=np.uint8)\n            if level == self.max_level:\n                this_level = 1\n            for i in range(3):\n                if i == self.axis:\n                    icoord = <np.uint64_t>(\n                        (self.coord - gobj.LeftEdge.d[i])/gobj.dds[i])\n                    # clip coordinate to avoid seg fault below if we're\n                    # exactly at a grid boundary\n                    ind[i][0] = iclip(\n                        icoord, 0, gobj.ActiveDimensions[i]-1)\n                    ind[i][1] = ind[i][0] + 1\n                else:\n                    ind[i][0] = 0\n                    ind[i][1] = gobj.ActiveDimensions[i]\n            with nogil:\n                for i in range(ind[0][0], ind[0][1]):\n                    for j in range(ind[1][0], ind[1][1]):\n                        for k in range(ind[2][0], ind[2][1]):\n                            if this_level == 1 or child_mask[i, j, k]:\n                                mask[i, j, k] = 1\n                                total += 1\n            if total == 0: return None, 0\n            return mask.astype(\"bool\"), total\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        if self.reduced_dimensionality == 1:\n            return 1\n        if pos[self.axis] + 0.5*dds[self.axis] > self.coord \\\n           and pos[self.axis] - 0.5*dds[self.axis] - grid_eps <= self.coord:\n            return 1\n        return 0\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        # two 0-volume constructs don't intersect\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        if self.reduced_dimensionality == 1:\n            return 1\n        cdef np.float64_t dist = self.periodic_difference(\n            pos[self.axis], self.coord, self.axis)\n        if dist*dist < radius*radius:\n            return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        if self.reduced_dimensionality == 1:\n            return 1\n        if left_edge[self.axis] - grid_eps <= self.coord < right_edge[self.axis]:\n            return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        if self.reduced_dimensionality == 1:\n            return 2\n        if left_edge[self.axis] - grid_eps <= self.coord < right_edge[self.axis]:\n            return 2 # a box with non-zero volume can't be inside a plane\n        return 0\n\n    def _hash_vals(self):\n        return ((\"axis\", self.axis),\n                (\"coord\", self.coord))\n\n    def _get_state_attnames(self):\n        return (\"axis\", \"coord\", \"ax\", \"ay\", \"reduced_dimensionality\")\n\nslice_selector = SliceSelector\n"
  },
  {
    "path": "yt/geometry/_selection_routines/sphere_selector.pxi",
    "content": "cdef class SphereSelector(SelectorObject):\n    cdef public np.float64_t radius\n    cdef public np.float64_t radius2\n    cdef public np.float64_t center[3]\n    cdef np.float64_t bbox[3][2]\n    cdef public bint check_box[3]\n\n    def __init__(self, dobj):\n        for i in range(3):\n            self.center[i] = _ensure_code(dobj.center[i])\n        self.radius = _ensure_code(dobj.radius)\n        self.radius2 = self.radius * self.radius\n        self.set_bbox(_ensure_code(dobj.center))\n        for i in range(3):\n            if self.bbox[i][0] < dobj.ds.domain_left_edge[i]:\n                self.check_box[i] = False\n            elif self.bbox[i][1] > dobj.ds.domain_right_edge[i]:\n                self.check_box[i] = False\n            else:\n                self.check_box[i] = True\n\n    def set_bbox(self, center):\n        for i in range(3):\n            self.center[i] = center[i]\n            self.bbox[i][0] = self.center[i] - self.radius\n            self.bbox[i][1] = self.center[i] + self.radius\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil:\n        # sphere center either inside cell or center of cell lies inside sphere\n        if (pos[0] - 0.5*dds[0] <= self.center[0] <= pos[0]+0.5*dds[0] and\n            pos[1] - 0.5*dds[1] <= self.center[1] <= pos[1]+0.5*dds[1] and\n            pos[2] - 0.5*dds[2] <= self.center[2] <= pos[2]+0.5*dds[2]):\n            return 1\n        return self.select_point(pos)\n        # # langmm: added to allow sphere to intersect edge/corner of cell\n        # cdef np.float64_t LE[3]\n        # cdef np.float64_t RE[3]\n        # cdef int i\n        # for i in range(3):\n        #     LE[i] = pos[i] - 0.5*dds[i]\n        #     RE[i] = pos[i] + 0.5*dds[i]\n        # return self.select_bbox(LE, RE)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil:\n        cdef int i\n        cdef np.float64_t dist, dist2 = 0\n        for i in range(3):\n            if self.check_box[i] and \\\n              (pos[i] < self.bbox[i][0] or\n               pos[i] > self.bbox[i][1]):\n                return 0\n            dist = _periodic_dist(pos[i], self.center[i], self.domain_width[i],\n                                  self.periodicity[i])\n            dist2 += dist*dist\n            if dist2 > self.radius2: return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil:\n        cdef int i\n        cdef np.float64_t dist, dist2 = 0\n        for i in range(3):\n            dist = self.periodic_difference(pos[i], self.center[i], i)\n            dist2 += dist*dist\n        dist = self.radius+radius\n        if dist2 <= dist*dist: return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef np.float64_t box_center, relcenter, closest, dist, edge\n        cdef int i\n        if (left_edge[0] <= self.center[0] < right_edge[0] and\n            left_edge[1] <= self.center[1] < right_edge[1] and\n            left_edge[2] <= self.center[2] < right_edge[2]):\n            return 1\n        for i in range(3):\n            if not self.check_box[i]: continue\n            if right_edge[i] < self.bbox[i][0] or \\\n               left_edge[i] > self.bbox[i][1]:\n                return 0\n        # http://www.gamedev.net/topic/335465-is-this-the-simplest-sphere-aabb-collision-test/\n        dist = 0\n        for i in range(3):\n            # Early terminate\n            box_center = (right_edge[i] + left_edge[i])/2.0\n            relcenter = self.periodic_difference(box_center, self.center[i], i)\n            edge = right_edge[i] - left_edge[i]\n            closest = relcenter - fclip(relcenter, -edge/2.0, edge/2.0)\n            dist += closest*closest\n            if dist > self.radius2: return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil:\n        cdef np.float64_t box_center, relcenter, closest, farthest, cdist, fdist, edge\n        cdef int i\n        if (left_edge[0] <= self.center[0] <= right_edge[0] and\n            left_edge[1] <= self.center[1] <= right_edge[1] and\n            left_edge[2] <= self.center[2] <= right_edge[2]):\n            fdist = 0\n            for i in range(3):\n                edge = right_edge[i] - left_edge[i]\n                box_center = (right_edge[i] + left_edge[i])/2.0\n                relcenter = self.periodic_difference(\n                    box_center, self.center[i], i)\n                if relcenter >= 0:\n                    farthest = relcenter + edge/2.0\n                else:\n                    farthest = relcenter - edge/2.0\n                # farthest = relcenter + fclip(relcenter, -edge/2.0, edge/2.0)\n                fdist += farthest*farthest\n                if fdist >= self.radius2:\n                    return 2  # Box extends outside sphere\n            return 1  # Box entirely inside sphere\n        for i in range(3):\n            if not self.check_box[i]: continue\n            if right_edge[i] < self.bbox[i][0] or \\\n               left_edge[i] > self.bbox[i][1]:\n                return 0  # Box outside sphere bounding box\n        # http://www.gamedev.net/topic/335465-is-this-the-simplest-sphere-aabb-collision-test/\n        cdist = 0\n        fdist = 0\n        for i in range(3):\n            # Early terminate\n            box_center = (right_edge[i] + left_edge[i])/2.0\n            relcenter = self.periodic_difference(box_center, self.center[i], i)\n            edge = right_edge[i] - left_edge[i]\n            closest = relcenter - fclip(relcenter, -edge/2.0, edge/2.0)\n            if relcenter >= 0:\n                farthest = relcenter + edge/2.0\n            else:\n                farthest = relcenter - edge/2.0\n            #farthest = relcenter + fclip(relcenter, -edge/2.0, edge/2.0)\n            cdist += closest*closest\n            fdist += farthest*farthest\n            if cdist > self.radius2:\n                return 0  # Box does not overlap sphere\n        if fdist < self.radius2:\n            return 1  # Sphere extends to entirely contain box\n        else:\n            return 2  # Sphere only partially overlaps box\n\n    def _hash_vals(self):\n        return ((\"radius\", self.radius),\n                (\"radius2\", self.radius2),\n                (\"center[0]\", self.center[0]),\n                (\"center[1]\", self.center[1]),\n                (\"center[2]\", self.center[2]))\n\n    def _get_state_attnames(self):\n        return (\"radius\", \"radius2\", \"center\", \"check_box\")\n\n    def __setstate__(self, hashes):\n        super(SphereSelector, self).__setstate__(hashes)\n        self.set_bbox(self.center)\n\n\nsphere_selector = SphereSelector\n"
  },
  {
    "path": "yt/geometry/api.py",
    "content": "from .geometry_enum import Geometry\nfrom .geometry_handler import Index\nfrom .grid_geometry_handler import GridIndex\n"
  },
  {
    "path": "yt/geometry/coordinates/__init__.py",
    "content": ""
  },
  {
    "path": "yt/geometry/coordinates/api.py",
    "content": "from .cartesian_coordinates import CartesianCoordinateHandler\nfrom .coordinate_handler import CoordinateHandler\nfrom .cylindrical_coordinates import CylindricalCoordinateHandler\nfrom .geographic_coordinates import (\n    GeographicCoordinateHandler,\n    InternalGeographicCoordinateHandler,\n)\nfrom .polar_coordinates import PolarCoordinateHandler\nfrom .spec_cube_coordinates import SpectralCubeCoordinateHandler\nfrom .spherical_coordinates import SphericalCoordinateHandler\n"
  },
  {
    "path": "yt/geometry/coordinates/cartesian_coordinates.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.index_subobjects.unstructured_mesh import SemiStructuredMesh\nfrom yt.funcs import mylog\nfrom yt.units._numpy_wrapper_functions import uconcatenate, uvstack\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.lib.pixelization_routines import (\n    interpolate_sph_grid_gather,\n    normalization_2d_utility,\n    pixelize_cartesian,\n    pixelize_cartesian_nodal,\n    pixelize_element_mesh,\n    pixelize_element_mesh_line,\n    pixelize_off_axis_cartesian,\n    pixelize_sph_kernel_cutting,\n    pixelize_sph_kernel_projection,\n    pixelize_sph_kernel_slice,\n)\nfrom yt.utilities.math_utils import compute_stddev_image\nfrom yt.utilities.nodal_data_utils import get_nodal_data\n\nfrom .coordinate_handler import (\n    CoordinateHandler,\n    _get_coord_fields,\n    _get_vert_fields,\n    cartesian_to_cylindrical,\n    cylindrical_to_cartesian,\n)\n\n\ndef _sample_ray(ray, npoints, field):\n    \"\"\"\n    Private function that uses a ray object for calculating the field values\n    that will be the y-axis values in a LinePlot object.\n\n    Parameters\n    ----------\n    ray : YTOrthoRay, YTRay, or LightRay\n        Ray object from which to sample field values\n    npoints : int\n        The number of points to sample\n    field : str or field tuple\n        The name of the field to sample\n    \"\"\"\n    start_point = ray.start_point\n    end_point = ray.end_point\n    sample_dr = (end_point - start_point) / (npoints - 1)\n    sample_points = [np.arange(npoints) * sample_dr[i] for i in range(3)]\n    sample_points = uvstack(sample_points).T + start_point\n    ray_coordinates = uvstack([ray[\"index\", d] for d in \"xyz\"]).T\n    ray_dds = uvstack([ray[\"index\", f\"d{d}\"] for d in \"xyz\"]).T\n    ray_field = ray[field]\n    field_values = ray.ds.arr(np.zeros(npoints), ray_field.units)\n    for i, sample_point in enumerate(sample_points):\n        ray_contains = (sample_point >= (ray_coordinates - ray_dds / 2)) & (\n            sample_point <= (ray_coordinates + ray_dds / 2)\n        )\n        ray_contains = ray_contains.all(axis=-1)\n        # use argmax to find the first nonzero index, sometimes there\n        # are two indices if the sampling point happens to fall exactly at\n        # a cell boundary\n        field_values[i] = ray_field[np.argmax(ray_contains)]\n    dr = np.sqrt((sample_dr**2).sum())\n    x = np.arange(npoints) / (npoints - 1) * (dr * npoints)\n    return x, field_values\n\n\ndef all_data(data, ptype, fields, kdtree=False):\n    field_data = {}\n    fields = set(fields)\n    for field in fields:\n        field_data[field] = []\n\n    for chunk in data.all_data().chunks([], \"io\"):\n        for field in fields:\n            field_data[field].append(chunk[ptype, field].in_base(\"code\"))\n\n    for field in fields:\n        field_data[field] = uconcatenate(field_data[field])\n\n    if kdtree is True:\n        kdtree = data.index.kdtree\n        for field in fields:\n            if len(field_data[field].shape) == 1:\n                field_data[field] = field_data[field][kdtree.idx]\n            else:\n                field_data[field] = field_data[field][kdtree.idx, :]\n\n    return field_data\n\n\nclass CartesianCoordinateHandler(CoordinateHandler):\n    name = \"cartesian\"\n    _default_axis_order = (\"x\", \"y\", \"z\")\n\n    def setup_fields(self, registry):\n        for axi, ax in enumerate(self.axis_order):\n            f1, f2 = _get_coord_fields(axi)\n            registry.add_field(\n                (\"index\", f\"d{ax}\"),\n                sampling_type=\"cell\",\n                function=f1,\n                display_field=False,\n                units=\"code_length\",\n            )\n\n            registry.add_field(\n                (\"index\", f\"path_element_{ax}\"),\n                sampling_type=\"cell\",\n                function=f1,\n                display_field=False,\n                units=\"code_length\",\n            )\n\n            registry.add_field(\n                (\"index\", f\"{ax}\"),\n                sampling_type=\"cell\",\n                function=f2,\n                display_field=False,\n                units=\"code_length\",\n            )\n\n            f3 = _get_vert_fields(axi)\n            registry.add_field(\n                (\"index\", f\"vertex_{ax}\"),\n                sampling_type=\"cell\",\n                function=f3,\n                display_field=False,\n                units=\"code_length\",\n            )\n\n        self._register_volume(registry)\n        self._check_fields(registry)\n\n    def _register_volume(self, registry):\n        def _cell_volume(data):\n            rv = data[\"index\", \"dx\"].copy(order=\"K\")\n            rv *= data[\"index\", \"dy\"]\n            rv *= data[\"index\", \"dz\"]\n            return rv\n\n        registry.add_field(\n            (\"index\", \"cell_volume\"),\n            sampling_type=\"cell\",\n            function=_cell_volume,\n            display_field=False,\n            units=\"code_length**3\",\n        )\n        registry.alias((\"index\", \"volume\"), (\"index\", \"cell_volume\"))\n\n    def _check_fields(self, registry):\n        registry.check_derived_fields(\n            [\n                (\"index\", \"dx\"),\n                (\"index\", \"dy\"),\n                (\"index\", \"dz\"),\n                (\"index\", \"x\"),\n                (\"index\", \"y\"),\n                (\"index\", \"z\"),\n                (\"index\", \"cell_volume\"),\n            ]\n        )\n\n    def pixelize(\n        self,\n        dimension,\n        data_source,\n        field,\n        bounds,\n        size,\n        antialias=True,\n        periodic=True,\n        *,\n        return_mask=False,\n    ):\n        \"\"\"\n        Method for pixelizing datasets in preparation for\n        two-dimensional image plots. Relies on several sampling\n        routines written in cython\n        \"\"\"\n        index = data_source.ds.index\n        if hasattr(index, \"meshes\") and not isinstance(\n            index.meshes[0], SemiStructuredMesh\n        ):\n            ftype, fname = field\n            if ftype == \"all\":\n                mesh_id = 0\n                indices = np.concatenate(\n                    [mesh.connectivity_indices for mesh in index.mesh_union]\n                )\n            else:\n                mesh_id = int(ftype[-1]) - 1\n                indices = index.meshes[mesh_id].connectivity_indices\n\n            coords = index.meshes[mesh_id].connectivity_coords\n            offset = index.meshes[mesh_id]._index_offset\n            ad = data_source.ds.all_data()\n            field_data = ad[field]\n            buff_size = size[0:dimension] + (1,) + size[dimension:]\n\n            ax = data_source.axis\n            xax = self.x_axis[ax]\n            yax = self.y_axis[ax]\n            c = np.float64(data_source.center[dimension].d)\n\n            extents = np.zeros((3, 2))\n            extents[ax] = np.array([c, c])\n            extents[xax] = bounds[0:2]\n            extents[yax] = bounds[2:4]\n\n            # if this is an element field, promote to 2D here\n            if len(field_data.shape) == 1:\n                field_data = np.expand_dims(field_data, 1)\n            # if this is a higher-order element, we demote to 1st order\n            # here, for now.\n            elif field_data.shape[1] == 27:\n                # hexahedral\n                mylog.warning(\n                    \"High order elements not yet supported, dropping to 1st order.\"\n                )\n                field_data = field_data[:, 0:8]\n                indices = indices[:, 0:8]\n\n            buff, mask = pixelize_element_mesh(\n                coords,\n                indices,\n                buff_size,\n                field_data,\n                extents,\n                index_offset=offset,\n                return_mask=True,\n            )\n\n            buff = np.squeeze(np.transpose(buff, (yax, xax, ax)))\n            mask = np.squeeze(np.transpose(mask, (yax, xax, ax)))\n\n        elif self.axis_id.get(dimension, dimension) is not None:\n            buff, mask = self._ortho_pixelize(\n                data_source, field, bounds, size, antialias, dimension, periodic\n            )\n        else:\n            buff, mask = self._oblique_pixelize(\n                data_source, field, bounds, size, antialias\n            )\n\n        if return_mask:\n            assert mask is None or mask.dtype == bool\n            return buff, mask\n        else:\n            return buff\n\n    def pixelize_line(self, field, start_point, end_point, npoints):\n        \"\"\"\n        Method for sampling datasets along a line in preparation for\n        one-dimensional line plots. For UnstructuredMesh, relies on a\n        sampling routine written in cython\n        \"\"\"\n        if npoints < 2:\n            raise ValueError(\n                \"Must have at least two sample points in order to draw a line plot.\"\n            )\n        index = self.ds.index\n        if hasattr(index, \"meshes\") and not isinstance(\n            index.meshes[0], SemiStructuredMesh\n        ):\n            ftype, fname = field\n            if ftype == \"all\":\n                mesh_id = 0\n                indices = np.concatenate(\n                    [mesh.connectivity_indices for mesh in index.mesh_union]\n                )\n            else:\n                mesh_id = int(ftype[-1]) - 1\n                indices = index.meshes[mesh_id].connectivity_indices\n\n            coords = index.meshes[mesh_id].connectivity_coords\n            if coords.shape[1] != end_point.size != start_point.size:\n                raise ValueError(\n                    \"The coordinate dimension doesn't match the \"\n                    \"start and end point dimensions.\"\n                )\n\n            offset = index.meshes[mesh_id]._index_offset\n            ad = self.ds.all_data()\n            field_data = ad[field]\n\n            # if this is an element field, promote to 2D here\n            if len(field_data.shape) == 1:\n                field_data = np.expand_dims(field_data, 1)\n            # if this is a higher-order element, we demote to 1st order\n            # here, for now.\n            elif field_data.shape[1] == 27:\n                # hexahedral\n                mylog.warning(\n                    \"High order elements not yet supported, dropping to 1st order.\"\n                )\n                field_data = field_data[:, 0:8]\n                indices = indices[:, 0:8]\n\n            arc_length, plot_values = pixelize_element_mesh_line(\n                coords,\n                indices,\n                start_point,\n                end_point,\n                npoints,\n                field_data,\n                index_offset=offset,\n            )\n            arc_length = YTArray(arc_length, start_point.units)\n            plot_values = YTArray(plot_values, field_data.units)\n        else:\n            ray = self.ds.ray(start_point, end_point)\n            arc_length, plot_values = _sample_ray(ray, npoints, field)\n        return arc_length, plot_values\n\n    def _ortho_pixelize(\n        self, data_source, field, bounds, size, antialias, dim, periodic\n    ):\n        from yt.data_objects.construction_data_containers import YTParticleProj\n        from yt.data_objects.selection_objects.slices import YTSlice\n        from yt.frontends.sph.data_structures import ParticleDataset\n        from yt.frontends.stream.data_structures import StreamParticlesDataset\n\n        # We should be using fcoords\n        field = data_source._determine_fields(field)[0]\n        finfo = data_source.ds.field_info[field]\n        # some coordinate handlers use only projection-plane periods,\n        # others need all box periods.\n        period2 = self.period[:2].copy()  # dummy here\n        period2[0] = self.period[self.x_axis[dim]]\n        period2[1] = self.period[self.y_axis[dim]]\n        period3 = self.period[:].copy()  # dummy here\n        period3[0] = self.period[self.x_axis[dim]]\n        period3[1] = self.period[self.y_axis[dim]]\n        zax = list({0, 1, 2} - {self.x_axis[dim], self.y_axis[dim]})[0]\n        period3[2] = self.period[zax]\n        if hasattr(period2, \"in_units\"):\n            period2 = period2.in_units(\"code_length\").d\n        if hasattr(period3, \"in_units\"):\n            period3 = period3.in_units(\"code_length\").d\n\n        buff = np.full((size[1], size[0]), np.nan, dtype=\"float64\")\n        particle_datasets = (ParticleDataset, StreamParticlesDataset)\n        is_sph_field = finfo.is_sph_field\n\n        finfo = self.ds._get_field_info(field)\n        if np.any(finfo.nodal_flag):\n            nodal_data = get_nodal_data(data_source, field)\n            coord = data_source.coord.d\n            mask = pixelize_cartesian_nodal(\n                buff,\n                data_source[\"px\"],\n                data_source[\"py\"],\n                data_source[\"pz\"],\n                data_source[\"pdx\"],\n                data_source[\"pdy\"],\n                data_source[\"pdz\"],\n                nodal_data,\n                coord,\n                bounds,\n                int(antialias),\n                period2,\n                int(periodic),\n                return_mask=True,\n            )\n        elif isinstance(data_source.ds, particle_datasets) and is_sph_field:\n            # SPH handling\n            ptype = field[0]\n            if ptype == \"gas\":\n                ptype = data_source.ds._sph_ptypes[0]\n            px_name = self.axis_name[self.x_axis[dim]]\n            py_name = self.axis_name[self.y_axis[dim]]\n            # need z coordinates for depth,\n            # but name isn't saved in the handler -> use the 'other one'\n            pz_name = list(set(self.axis_order) - {px_name, py_name})[0]\n\n            # ignore default True periodic argument\n            # (not actually supplied by a call from\n            # FixedResolutionBuffer), and use the dataset periodicity\n            # instead\n            xa = self.x_axis[dim]\n            ya = self.y_axis[dim]\n            # axorder = data_source.ds.coordinates.axis_order\n            za = list({0, 1, 2} - {xa, ya})[0]\n            ds_periodic = data_source.ds.periodicity\n            _periodic = np.array(ds_periodic)\n            _periodic[0] = ds_periodic[xa]\n            _periodic[1] = ds_periodic[ya]\n            _periodic[2] = ds_periodic[za]\n            ounits = data_source.ds.field_info[field].output_units\n            bnds = data_source.ds.arr(bounds, \"code_length\").tolist()\n            kernel_name = None\n            if hasattr(data_source.ds, \"kernel_name\"):\n                kernel_name = data_source.ds.kernel_name\n            if kernel_name is None:\n                kernel_name = \"cubic\"\n\n            if isinstance(data_source, YTParticleProj):  # projection\n                weight = data_source.weight_field\n                moment = data_source.moment\n                le, re = data_source.data_source.get_bbox()\n                # If we're not periodic, we need to clip to the boundary edges\n                # or we get errors about extending off the edge of the region.\n                # (depth/z range is handled by region setting)\n                if not self.ds.periodicity[xa]:\n                    le[xa] = max(bounds[0], self.ds.domain_left_edge[xa])\n                    re[xa] = min(bounds[1], self.ds.domain_right_edge[xa])\n                else:\n                    le[xa] = bounds[0]\n                    re[xa] = bounds[1]\n                if not self.ds.periodicity[ya]:\n                    le[ya] = max(bounds[2], self.ds.domain_left_edge[ya])\n                    re[ya] = min(bounds[3], self.ds.domain_right_edge[ya])\n                else:\n                    le[ya] = bounds[2]\n                    re[ya] = bounds[3]\n                # We actually need to clip these\n                proj_reg = data_source.ds.region(\n                    left_edge=le,\n                    right_edge=re,\n                    center=data_source.center,\n                    data_source=data_source.data_source,\n                )\n                proj_reg.set_field_parameter(\"axis\", data_source.axis)\n                # need some z bounds for SPH projection\n                # -> use source bounds\n                bnds3 = bnds + [le[za], re[za]]\n\n                buff = np.zeros(size, dtype=\"float64\")\n                mask_uint8 = np.zeros_like(buff, dtype=\"uint8\")\n                if weight is None:\n                    for chunk in proj_reg.chunks([], \"io\"):\n                        data_source._initialize_projected_units([field], chunk)\n                        pixelize_sph_kernel_projection(\n                            buff,\n                            mask_uint8,\n                            chunk[ptype, px_name].to(\"code_length\"),\n                            chunk[ptype, py_name].to(\"code_length\"),\n                            chunk[ptype, pz_name].to(\"code_length\"),\n                            chunk[ptype, \"smoothing_length\"].to(\"code_length\"),\n                            chunk[ptype, \"mass\"].to(\"code_mass\"),\n                            chunk[ptype, \"density\"].to(\"code_density\"),\n                            chunk[field].in_units(ounits),\n                            bnds3,\n                            _check_period=_periodic.astype(\"int\"),\n                            period=period3,\n                            kernel_name=kernel_name,\n                        )\n                    # We use code length here, but to get the path length right\n                    # we need to multiply by the conversion factor between\n                    # code length and the unit system's length unit\n                    default_path_length_unit = data_source.ds.unit_system[\"length\"]\n                    dl_conv = data_source.ds.quan(1.0, \"code_length\").to(\n                        default_path_length_unit\n                    )\n                    buff *= dl_conv.v\n                # if there is a weight field, take two projections:\n                # one of field*weight, the other of just weight, and divide them\n                else:\n                    weight_buff = np.zeros(size, dtype=\"float64\")\n                    buff = np.zeros(size, dtype=\"float64\")\n                    mask_uint8 = np.zeros_like(buff, dtype=\"uint8\")\n                    wounits = data_source.ds.field_info[weight].output_units\n                    for chunk in proj_reg.chunks([], \"io\"):\n                        data_source._initialize_projected_units([field], chunk)\n                        data_source._initialize_projected_units([weight], chunk)\n                        pixelize_sph_kernel_projection(\n                            buff,\n                            mask_uint8,\n                            chunk[ptype, px_name].to(\"code_length\"),\n                            chunk[ptype, py_name].to(\"code_length\"),\n                            chunk[ptype, pz_name].to(\"code_length\"),\n                            chunk[ptype, \"smoothing_length\"].to(\"code_length\"),\n                            chunk[ptype, \"mass\"].to(\"code_mass\"),\n                            chunk[ptype, \"density\"].to(\"code_density\"),\n                            chunk[field].in_units(ounits),\n                            bnds3,\n                            _check_period=_periodic.astype(\"int\"),\n                            period=period3,\n                            weight_field=chunk[weight].in_units(wounits),\n                            kernel_name=kernel_name,\n                        )\n                    mylog.info(\n                        \"Making a fixed resolution buffer of (%s) %d by %d\",\n                        weight,\n                        size[0],\n                        size[1],\n                    )\n                    for chunk in proj_reg.chunks([], \"io\"):\n                        data_source._initialize_projected_units([weight], chunk)\n                        pixelize_sph_kernel_projection(\n                            weight_buff,\n                            mask_uint8,\n                            chunk[ptype, px_name].to(\"code_length\"),\n                            chunk[ptype, py_name].to(\"code_length\"),\n                            chunk[ptype, pz_name].to(\"code_length\"),\n                            chunk[ptype, \"smoothing_length\"].to(\"code_length\"),\n                            chunk[ptype, \"mass\"].to(\"code_mass\"),\n                            chunk[ptype, \"density\"].to(\"code_density\"),\n                            chunk[weight].in_units(wounits),\n                            bnds3,\n                            _check_period=_periodic.astype(\"int\"),\n                            period=period3,\n                            kernel_name=kernel_name,\n                        )\n                    normalization_2d_utility(buff, weight_buff)\n                    if moment == 2:\n                        buff2 = np.zeros(size, dtype=\"float64\")\n                        for chunk in proj_reg.chunks([], \"io\"):\n                            data_source._initialize_projected_units([field], chunk)\n                            data_source._initialize_projected_units([weight], chunk)\n                            pixelize_sph_kernel_projection(\n                                buff2,\n                                mask_uint8,\n                                chunk[ptype, px_name].to(\"code_length\"),\n                                chunk[ptype, py_name].to(\"code_length\"),\n                                chunk[ptype, pz_name].to(\"code_length\"),\n                                chunk[ptype, \"smoothing_length\"].to(\"code_length\"),\n                                chunk[ptype, \"mass\"].to(\"code_mass\"),\n                                chunk[ptype, \"density\"].to(\"code_density\"),\n                                chunk[field].in_units(ounits) ** 2,\n                                bnds3,\n                                _check_period=_periodic.astype(\"int\"),\n                                period=period3,\n                                weight_field=chunk[weight].in_units(wounits),\n                                kernel_name=kernel_name,\n                            )\n                        normalization_2d_utility(buff2, weight_buff)\n                        buff = compute_stddev_image(buff2, buff)\n                mask = mask_uint8.astype(\"bool\")\n            elif isinstance(data_source, YTSlice):\n                smoothing_style = getattr(self.ds, \"sph_smoothing_style\", \"scatter\")\n                normalize = getattr(self.ds, \"use_sph_normalization\", True)\n\n                if smoothing_style == \"scatter\":\n                    buff = np.zeros(size, dtype=\"float64\")\n                    mask_uint8 = np.zeros_like(buff, dtype=\"uint8\")\n                    if normalize:\n                        buff_den = np.zeros(size, dtype=\"float64\")\n\n                    for chunk in data_source.chunks([], \"io\"):\n                        hsmlname = \"smoothing_length\"\n                        pixelize_sph_kernel_slice(\n                            buff,\n                            mask_uint8,\n                            chunk[ptype, px_name].to(\"code_length\").v,\n                            chunk[ptype, py_name].to(\"code_length\").v,\n                            chunk[ptype, pz_name].to(\"code_length\").v,\n                            chunk[ptype, hsmlname].to(\"code_length\").v,\n                            chunk[ptype, \"mass\"].to(\"code_mass\").v,\n                            chunk[ptype, \"density\"].to(\"code_density\").v,\n                            chunk[field].in_units(ounits).v,\n                            bnds,\n                            data_source.coord.to(\"code_length\").v,\n                            _check_period=_periodic.astype(\"int\"),\n                            period=period3,\n                            kernel_name=kernel_name,\n                        )\n                        if normalize:\n                            pixelize_sph_kernel_slice(\n                                buff_den,\n                                mask_uint8,\n                                chunk[ptype, px_name].to(\"code_length\").v,\n                                chunk[ptype, py_name].to(\"code_length\").v,\n                                chunk[ptype, pz_name].to(\"code_length\").v,\n                                chunk[ptype, hsmlname].to(\"code_length\").v,\n                                chunk[ptype, \"mass\"].to(\"code_mass\").v,\n                                chunk[ptype, \"density\"].to(\"code_density\").v,\n                                np.ones(chunk[ptype, \"density\"].shape[0]),\n                                bnds,\n                                data_source.coord.to(\"code_length\").v,\n                                _check_period=_periodic.astype(\"int\"),\n                                period=period3,\n                                kernel_name=kernel_name,\n                            )\n\n                    if normalize:\n                        normalization_2d_utility(buff, buff_den)\n\n                    mask = mask_uint8.astype(\"bool\", copy=False)\n\n                if smoothing_style == \"gather\":\n                    # Here we find out which axis are going to be the \"x\" and\n                    # \"y\" axis for the actual visualisation and then we set the\n                    # buffer size and bounds to match. The z axis of the plot\n                    # is the axis we slice over and the buffer will be of size 1\n                    # in that dimension\n                    x, y, z = self.x_axis[dim], self.y_axis[dim], dim\n\n                    buff_size = np.zeros(3, dtype=\"int64\")\n                    buff_size[x] = size[0]\n                    buff_size[y] = size[1]\n                    buff_size[z] = 1\n\n                    buff_bounds = np.zeros(6, dtype=\"float64\")\n                    buff_bounds[2 * x : 2 * x + 2] = bounds[0:2]\n                    buff_bounds[2 * y : 2 * y + 2] = bounds[2:4]\n                    buff_bounds[2 * z] = data_source.coord\n                    buff_bounds[2 * z + 1] = data_source.coord\n\n                    # then we do the interpolation\n                    buff_temp = np.zeros(buff_size, dtype=\"float64\")\n\n                    fields_to_get = [\n                        \"particle_position\",\n                        \"density\",\n                        \"mass\",\n                        \"smoothing_length\",\n                        field[1],\n                    ]\n                    all_fields = all_data(self.ds, ptype, fields_to_get, kdtree=True)\n\n                    num_neighbors = getattr(self.ds, \"num_neighbors\", 32)\n                    mask_temp = interpolate_sph_grid_gather(\n                        buff_temp,\n                        all_fields[\"particle_position\"].to(\"code_length\"),\n                        buff_bounds,\n                        all_fields[\"smoothing_length\"].to(\"code_length\"),\n                        all_fields[\"mass\"].to(\"code_mass\"),\n                        all_fields[\"density\"].to(\"code_density\"),\n                        all_fields[field[1]].in_units(ounits),\n                        self.ds.index.kdtree,\n                        num_neigh=num_neighbors,\n                        use_normalization=normalize,\n                        return_mask=True,\n                    )\n\n                    # We swap the axes back so the axis which was sliced over\n                    # is the last axis, as this is the \"z\" axis of the plots.\n                    if z != 2:\n                        buff_temp = buff_temp.swapaxes(2, z)\n                        mask_temp = mask_temp.swapaxes(2, z)\n                        if x == 2:\n                            x = z\n                        else:\n                            y = z\n\n                    buff = buff_temp[:, :, 0]\n                    mask = mask_temp[:, :, 0]\n\n                    # Then we just transpose if the buffer x and y are\n                    # different than the plot x and y\n                    if y < x:\n                        buff = buff.transpose()\n                        mask = mask.transpose()\n            else:\n                raise NotImplementedError(\n                    \"A pixelization routine has not been implemented for \"\n                    f\"{type(data_source)} data objects\"\n                )\n            buff = buff.transpose()\n            mask = mask.transpose()\n        else:\n            mask = pixelize_cartesian(\n                buff,\n                data_source[\"px\"],\n                data_source[\"py\"],\n                data_source[\"pdx\"],\n                data_source[\"pdy\"],\n                data_source[field],\n                bounds,\n                int(antialias),\n                period2,\n                int(periodic),\n                return_mask=True,\n            )\n        assert mask is None or mask.dtype == bool\n        return buff, mask\n\n    def _oblique_pixelize(self, data_source, field, bounds, size, antialias):\n        from yt.data_objects.selection_objects.slices import YTCuttingPlane\n        from yt.frontends.sph.data_structures import ParticleDataset\n        from yt.frontends.stream.data_structures import StreamParticlesDataset\n        from yt.frontends.ytdata.data_structures import YTSpatialPlotDataset\n\n        # Determine what sort of data we're dealing with\n        # -> what backend to use\n        # copied from the _ortho_pixelize method\n        field = data_source._determine_fields(field)[0]\n        _finfo = data_source.ds.field_info[field]\n        is_sph_field = _finfo.is_sph_field\n        particle_datasets = (ParticleDataset, StreamParticlesDataset)\n        # finfo = self.ds._get_field_info(field)\n\n        # SPH data\n        # only for slices: a function in off_axis_projection.py\n        # handles projections\n        if (\n            isinstance(data_source.ds, particle_datasets)\n            and is_sph_field\n            and isinstance(data_source, YTCuttingPlane)\n        ):\n            normalize = getattr(self.ds, \"use_sph_normalization\", True)\n            le = data_source.ds.domain_left_edge.to(\"code_length\")\n            re = data_source.ds.domain_right_edge.to(\"code_length\")\n            boxbounds = np.array([le[0], re[0], le[1], re[1], le[2], re[2]])\n            periodic = data_source.ds.periodicity\n            ptype = field[0]\n            if ptype == \"gas\":\n                ptype = data_source.ds._sph_ptypes[0]\n            axorder = data_source.ds.coordinates.axis_order\n            ounits = data_source.ds.field_info[field].output_units\n            # input bounds are in code length units already\n            widthxy = np.array((bounds[1] - bounds[0], bounds[3] - bounds[2]))\n            kernel_name = None\n            if hasattr(data_source.ds, \"kernel_name\"):\n                kernel_name = data_source.ds.kernel_name\n            if kernel_name is None:\n                kernel_name = \"cubic\"\n            # data_source should be a YTCuttingPlane object\n            # dimensionless unyt normal/north\n            # -> numpy array cython can deal with\n            normal_vector = data_source.normal.v\n            north_vector = data_source._y_vec.v\n            center = data_source.center.to(\"code_length\")\n\n            buff = np.zeros(size, dtype=\"float64\")\n            mask_uint8 = np.zeros_like(buff, dtype=\"uint8\")\n            if normalize:\n                buff_den = np.zeros(size, dtype=\"float64\")\n\n            for chunk in data_source.chunks([], \"io\"):\n                pixelize_sph_kernel_cutting(\n                    buff,\n                    mask_uint8,\n                    chunk[ptype, axorder[0]].to(\"code_length\").v,\n                    chunk[ptype, axorder[1]].to(\"code_length\").v,\n                    chunk[ptype, axorder[2]].to(\"code_length\").v,\n                    chunk[ptype, \"smoothing_length\"].to(\"code_length\").v,\n                    chunk[ptype, \"mass\"].to(\"code_mass\"),\n                    chunk[ptype, \"density\"].to(\"code_density\"),\n                    chunk[field].in_units(ounits),\n                    center,\n                    widthxy,\n                    normal_vector,\n                    north_vector,\n                    boxbounds,\n                    periodic,\n                    kernel_name=kernel_name,\n                    check_period=1,\n                )\n                if normalize:\n                    pixelize_sph_kernel_cutting(\n                        buff_den,\n                        mask_uint8,\n                        chunk[ptype, axorder[0]].to(\"code_length\"),\n                        chunk[ptype, axorder[1]].to(\"code_length\"),\n                        chunk[ptype, axorder[2]].to(\"code_length\"),\n                        chunk[ptype, \"smoothing_length\"].to(\"code_length\"),\n                        chunk[ptype, \"mass\"].to(\"code_mass\"),\n                        chunk[ptype, \"density\"].to(\"code_density\"),\n                        np.ones(chunk[ptype, \"density\"].shape[0]),\n                        center,\n                        widthxy,\n                        normal_vector,\n                        north_vector,\n                        boxbounds,\n                        periodic,\n                        kernel_name=kernel_name,\n                        check_period=1,\n                    )\n\n            if normalize:\n                normalization_2d_utility(buff, buff_den)\n\n            mask = mask_uint8.astype(\"bool\", copy=False)\n            # swap axes for image plotting\n            mask = mask.swapaxes(0, 1)\n            buff = buff.swapaxes(0, 1)\n\n        # whatever other data this code could handle before the\n        # SPH option was added\n        else:\n            indices = np.argsort(data_source[\"pdx\"])[::-1].astype(\"int64\", copy=False)\n            buff = np.full((size[1], size[0]), np.nan, dtype=\"float64\")\n            ftype = \"index\"\n            if isinstance(data_source.ds, YTSpatialPlotDataset):\n                ftype = \"gas\"\n            mask = pixelize_off_axis_cartesian(\n                buff,\n                data_source[ftype, \"x\"],\n                data_source[ftype, \"y\"],\n                data_source[ftype, \"z\"],\n                data_source[\"px\"],\n                data_source[\"py\"],\n                data_source[\"pdx\"],\n                data_source[\"pdy\"],\n                data_source[\"pdz\"],\n                data_source.center,\n                data_source._inv_mat,\n                indices,\n                data_source[field],\n                bounds,\n            )\n        return buff, mask\n\n    def convert_from_cartesian(self, coord):\n        return coord\n\n    def convert_to_cartesian(self, coord):\n        return coord\n\n    def convert_to_cylindrical(self, coord):\n        center = self.ds.domain_center\n        return cartesian_to_cylindrical(coord, center)\n\n    def convert_from_cylindrical(self, coord):\n        center = self.ds.domain_center\n        return cylindrical_to_cartesian(coord, center)\n\n    def convert_to_spherical(self, coord):\n        raise NotImplementedError\n\n    def convert_from_spherical(self, coord):\n        raise NotImplementedError\n\n    _x_pairs = ((\"x\", \"y\"), (\"y\", \"z\"), (\"z\", \"x\"))\n    _y_pairs = ((\"x\", \"z\"), (\"y\", \"x\"), (\"z\", \"y\"))\n\n    @property\n    def period(self):\n        return self.ds.domain_width\n"
  },
  {
    "path": "yt/geometry/coordinates/coordinate_handler.py",
    "content": "import abc\nimport weakref\nfrom functools import cached_property\nfrom numbers import Number\nfrom typing import Any, Literal, overload\n\nimport numpy as np\n\nfrom yt._typing import AxisOrder\nfrom yt.funcs import fix_unitary, is_sequence, parse_center_array, validate_width_tuple\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.exceptions import YTCoordinateNotImplemented, YTInvalidWidthError\n\n\ndef _unknown_coord(data):\n    raise YTCoordinateNotImplemented\n\n\ndef _get_coord_fields(axi, units=\"code_length\"):\n    def _dds(data):\n        rv = data.ds.arr(data.fwidth[..., axi].copy(), units)\n        return data._reshape_vals(rv)\n\n    def _coords(data):\n        rv = data.ds.arr(data.fcoords[..., axi].copy(), units)\n        return data._reshape_vals(rv)\n\n    return _dds, _coords\n\n\ndef _get_vert_fields(axi, units=\"code_length\"):\n    def _vert(data):\n        rv = data.ds.arr(data.fcoords_vertex[..., axi].copy(), units)\n        return rv\n\n    return _vert\n\n\ndef _setup_dummy_cartesian_coords_and_widths(registry, axes: tuple[str]):\n    for ax in axes:\n        registry.add_field(\n            (\"index\", f\"d{ax}\"), sampling_type=\"cell\", function=_unknown_coord\n        )\n        registry.add_field((\"index\", ax), sampling_type=\"cell\", function=_unknown_coord)\n\n\ndef _setup_polar_coordinates(registry, axis_id):\n    f1, f2 = _get_coord_fields(axis_id[\"r\"])\n    registry.add_field(\n        (\"index\", \"dr\"),\n        sampling_type=\"cell\",\n        function=f1,\n        display_field=False,\n        units=\"code_length\",\n    )\n\n    registry.add_field(\n        (\"index\", \"r\"),\n        sampling_type=\"cell\",\n        function=f2,\n        display_field=False,\n        units=\"code_length\",\n    )\n\n    f1, f2 = _get_coord_fields(axis_id[\"theta\"], \"dimensionless\")\n    registry.add_field(\n        (\"index\", \"dtheta\"),\n        sampling_type=\"cell\",\n        function=f1,\n        display_field=False,\n        units=\"dimensionless\",\n    )\n\n    registry.add_field(\n        (\"index\", \"theta\"),\n        sampling_type=\"cell\",\n        function=f2,\n        display_field=False,\n        units=\"dimensionless\",\n    )\n\n    def _path_r(data):\n        return data[\"index\", \"dr\"]\n\n    registry.add_field(\n        (\"index\", \"path_element_r\"),\n        sampling_type=\"cell\",\n        function=_path_r,\n        units=\"code_length\",\n    )\n\n    def _path_theta(data):\n        # Note: this already assumes cell-centered\n        return data[\"index\", \"r\"] * data[\"index\", \"dtheta\"]\n\n    registry.add_field(\n        (\"index\", \"path_element_theta\"),\n        sampling_type=\"cell\",\n        function=_path_theta,\n        units=\"code_length\",\n    )\n\n\ndef validate_sequence_width(width, ds, unit=None):\n    if isinstance(width[0], tuple) and isinstance(width[1], tuple):\n        validate_width_tuple(width[0])\n        validate_width_tuple(width[1])\n        return (\n            ds.quan(width[0][0], fix_unitary(width[0][1])),\n            ds.quan(width[1][0], fix_unitary(width[1][1])),\n        )\n    elif isinstance(width[0], Number) and isinstance(width[1], Number):\n        return (ds.quan(width[0], \"code_length\"), ds.quan(width[1], \"code_length\"))\n    elif isinstance(width[0], YTQuantity) and isinstance(width[1], YTQuantity):\n        return (ds.quan(width[0]), ds.quan(width[1]))\n    else:\n        validate_width_tuple(width)\n        # If width and unit are both valid width tuples, we\n        # assume width controls x and unit controls y\n        try:\n            validate_width_tuple(unit)\n            return (\n                ds.quan(width[0], fix_unitary(width[1])),\n                ds.quan(unit[0], fix_unitary(unit[1])),\n            )\n        except YTInvalidWidthError:\n            return (\n                ds.quan(width[0], fix_unitary(width[1])),\n                ds.quan(width[0], fix_unitary(width[1])),\n            )\n\n\nclass CoordinateHandler(abc.ABC):\n    name: str\n    _default_axis_order: AxisOrder\n\n    def __init__(self, ds, ordering: AxisOrder | None = None):\n        self.ds = weakref.proxy(ds)\n        if ordering is not None:\n            self.axis_order = ordering\n        else:\n            self.axis_order = self._default_axis_order\n\n    @abc.abstractmethod\n    def setup_fields(self):\n        # This should return field definitions for x, y, z, r, theta, phi\n        pass\n\n    @overload\n    def pixelize(\n        self,\n        dimension,\n        data_source,\n        field,\n        bounds,\n        size,\n        antialias=True,\n        periodic=True,\n        *,\n        return_mask: Literal[False],\n    ) -> \"np.ndarray[Any, np.dtype[np.float64]]\": ...\n\n    @overload\n    def pixelize(\n        self,\n        dimension,\n        data_source,\n        field,\n        bounds,\n        size,\n        antialias=True,\n        periodic=True,\n        *,\n        return_mask: Literal[True],\n    ) -> tuple[\n        \"np.ndarray[Any, np.dtype[np.float64]]\", \"np.ndarray[Any, np.dtype[np.bool_]]\"\n    ]: ...\n\n    @abc.abstractmethod\n    def pixelize(\n        self,\n        dimension,\n        data_source,\n        field,\n        bounds,\n        size,\n        antialias=True,\n        periodic=True,\n        *,\n        return_mask=False,\n    ):\n        # This should *actually* be a pixelize call, not just returning the\n        # pixelizer\n        pass\n\n    @abc.abstractmethod\n    def pixelize_line(self, field, start_point, end_point, npoints):\n        pass\n\n    def distance(self, start, end):\n        p1 = self.convert_to_cartesian(start)\n        p2 = self.convert_to_cartesian(end)\n        return np.sqrt(((p1 - p2) ** 2.0).sum())\n\n    @abc.abstractmethod\n    def convert_from_cartesian(self, coord):\n        pass\n\n    @abc.abstractmethod\n    def convert_to_cartesian(self, coord):\n        pass\n\n    @abc.abstractmethod\n    def convert_to_cylindrical(self, coord):\n        pass\n\n    @abc.abstractmethod\n    def convert_from_cylindrical(self, coord):\n        pass\n\n    @abc.abstractmethod\n    def convert_to_spherical(self, coord):\n        pass\n\n    @abc.abstractmethod\n    def convert_from_spherical(self, coord):\n        pass\n\n    @cached_property\n    def data_projection(self):\n        return dict.fromkeys(self.axis_order)\n\n    @cached_property\n    def data_transform(self):\n        return dict.fromkeys(self.axis_order)\n\n    @cached_property\n    def axis_name(self):\n        an = {}\n        for axi, ax in enumerate(self.axis_order):\n            an[axi] = ax\n            an[ax] = ax\n            an[ax.capitalize()] = ax\n        return an\n\n    @cached_property\n    def axis_id(self):\n        ai = {}\n        for axi, ax in enumerate(self.axis_order):\n            ai[ax] = ai[axi] = axi\n        return ai\n\n    @property\n    def image_axis_name(self):\n        rv = {}\n        for i in range(3):\n            rv[i] = (self.axis_name[self.x_axis[i]], self.axis_name[self.y_axis[i]])\n            rv[self.axis_name[i]] = rv[i]\n            rv[self.axis_name[i].capitalize()] = rv[i]\n        return rv\n\n    @cached_property\n    def x_axis(self):\n        ai = self.axis_id\n        xa = {}\n        for a1, a2 in self._x_pairs:\n            xa[a1] = xa[ai[a1]] = ai[a2]\n        return xa\n\n    @cached_property\n    def y_axis(self):\n        ai = self.axis_id\n        ya = {}\n        for a1, a2 in self._y_pairs:\n            ya[a1] = ya[ai[a1]] = ai[a2]\n        return ya\n\n    @property\n    @abc.abstractmethod\n    def period(self):\n        pass\n\n    def sanitize_depth(self, depth):\n        if is_sequence(depth):\n            validate_width_tuple(depth)\n            depth = (self.ds.quan(depth[0], fix_unitary(depth[1])),)\n        elif isinstance(depth, Number):\n            depth = (\n                self.ds.quan(depth, \"code_length\", registry=self.ds.unit_registry),\n            )\n        elif isinstance(depth, YTQuantity):\n            depth = (depth,)\n        else:\n            raise YTInvalidWidthError(depth)\n        return depth\n\n    def sanitize_width(self, axis, width, depth):\n        if width is None:\n            # initialize the index if it is not already initialized\n            self.ds.index\n            # Default to code units\n            if not is_sequence(axis):\n                xax = self.x_axis[axis]\n                yax = self.y_axis[axis]\n                w = self.ds.domain_width[np.array([xax, yax])]\n            else:\n                # axis is actually the normal vector\n                # for an off-axis data object.\n                mi = np.argmin(self.ds.domain_width)\n                w = self.ds.domain_width[np.array((mi, mi))]\n            width = (w[0], w[1])\n        elif is_sequence(width):\n            width = validate_sequence_width(width, self.ds)\n        elif isinstance(width, YTQuantity):\n            width = (width, width)\n        elif isinstance(width, Number):\n            width = (\n                self.ds.quan(width, \"code_length\"),\n                self.ds.quan(width, \"code_length\"),\n            )\n        else:\n            raise YTInvalidWidthError(width)\n        if depth is not None:\n            depth = self.sanitize_depth(depth)\n            return width + depth\n        return width\n\n    def sanitize_center(self, center, axis):\n        center = parse_center_array(center, ds=self.ds, axis=axis)\n        # This has to return both a center and a display_center\n        display_center = self.convert_to_cartesian(center)\n        return center, display_center\n\n\ndef cartesian_to_cylindrical(coord, center=(0, 0, 0)):\n    c2 = np.zeros_like(coord)\n    if not isinstance(center, YTArray):\n        center = center * coord.uq\n    c2[..., 0] = (\n        (coord[..., 0] - center[0]) ** 2.0 + (coord[..., 1] - center[1]) ** 2.0\n    ) ** 0.5\n    c2[..., 1] = coord[..., 2]  # rzt\n    c2[..., 2] = np.arctan2(coord[..., 1] - center[1], coord[..., 0] - center[0])\n    return c2\n\n\ndef cylindrical_to_cartesian(coord, center=(0, 0, 0)):\n    c2 = np.zeros_like(coord)\n    if not isinstance(center, YTArray):\n        center = center * coord.uq\n    c2[..., 0] = np.cos(coord[..., 0]) * coord[..., 1] + center[0]\n    c2[..., 1] = np.sin(coord[..., 0]) * coord[..., 1] + center[1]\n    c2[..., 2] = coord[..., 2]\n    return c2\n\n\ndef _get_polar_bounds(self: CoordinateHandler, axes: tuple[str, str]):\n    # a small helper function that is needed by two unrelated classes\n    ri = self.axis_id[axes[0]]\n    pi = self.axis_id[axes[1]]\n    rmin = self.ds.domain_left_edge[ri]\n    rmax = self.ds.domain_right_edge[ri]\n    phimin = self.ds.domain_left_edge[pi]\n    phimax = self.ds.domain_right_edge[pi]\n    corners = [\n        (rmin, phimin),\n        (rmin, phimax),\n        (rmax, phimin),\n        (rmax, phimax),\n    ]\n\n    def to_polar_plane(r, phi):\n        x = r * np.cos(phi)\n        y = r * np.sin(phi)\n        return x, y\n\n    conic_corner_coords = [to_polar_plane(*corner) for corner in corners]\n\n    phimin = phimin.d\n    phimax = phimax.d\n\n    if phimin <= np.pi <= phimax:\n        xxmin = -rmax\n    else:\n        xxmin = min(xx for xx, yy in conic_corner_coords)\n\n    if phimin <= 0 <= phimax:\n        xxmax = rmax\n    else:\n        xxmax = max(xx for xx, yy in conic_corner_coords)\n\n    if phimin <= 3 * np.pi / 2 <= phimax:\n        yymin = -rmax\n    else:\n        yymin = min(yy for xx, yy in conic_corner_coords)\n\n    if phimin <= np.pi / 2 <= phimax:\n        yymax = rmax\n    else:\n        yymax = max(yy for xx, yy in conic_corner_coords)\n\n    return xxmin, xxmax, yymin, yymax\n"
  },
  {
    "path": "yt/geometry/coordinates/cylindrical_coordinates.py",
    "content": "from functools import cached_property\n\nimport numpy as np\n\nfrom yt.utilities.lib.pixelization_routines import pixelize_cartesian, pixelize_cylinder\n\nfrom .coordinate_handler import (\n    CoordinateHandler,\n    _get_coord_fields,\n    _get_polar_bounds,\n    _setup_dummy_cartesian_coords_and_widths,\n    _setup_polar_coordinates,\n    cartesian_to_cylindrical,\n    cylindrical_to_cartesian,\n)\n\n#\n# Cylindrical fields\n#\n\n\nclass CylindricalCoordinateHandler(CoordinateHandler):\n    name = \"cylindrical\"\n    _default_axis_order = (\"r\", \"z\", \"theta\")\n\n    def __init__(self, ds, ordering=None):\n        super().__init__(ds, ordering)\n        self.image_units = {}\n        self.image_units[self.axis_id[\"r\"]] = (\"rad\", None)\n        self.image_units[self.axis_id[\"theta\"]] = (None, None)\n        self.image_units[self.axis_id[\"z\"]] = (None, None)\n\n    def setup_fields(self, registry):\n        # Missing implementation for x and y coordinates.\n        _setup_dummy_cartesian_coords_and_widths(registry, axes=(\"x\", \"y\"))\n        _setup_polar_coordinates(registry, self.axis_id)\n\n        f1, f2 = _get_coord_fields(self.axis_id[\"z\"])\n        registry.add_field(\n            (\"index\", \"dz\"),\n            sampling_type=\"cell\",\n            function=f1,\n            display_field=False,\n            units=\"code_length\",\n        )\n\n        registry.add_field(\n            (\"index\", \"z\"),\n            sampling_type=\"cell\",\n            function=f2,\n            display_field=False,\n            units=\"code_length\",\n        )\n\n        def _CylindricalVolume(data):\n            r = data[\"index\", \"r\"]\n            dr = data[\"index\", \"dr\"]\n            vol = 0.5 * ((r + 0.5 * dr) ** 2 - (r - 0.5 * dr) ** 2)\n            vol *= data[\"index\", \"dtheta\"]\n            vol *= data[\"index\", \"dz\"]\n            return vol\n\n        registry.add_field(\n            (\"index\", \"cell_volume\"),\n            sampling_type=\"cell\",\n            function=_CylindricalVolume,\n            units=\"code_length**3\",\n        )\n        registry.alias((\"index\", \"volume\"), (\"index\", \"cell_volume\"))\n\n        def _path_z(data):\n            return data[\"index\", \"dz\"]\n\n        registry.add_field(\n            (\"index\", \"path_element_z\"),\n            sampling_type=\"cell\",\n            function=_path_z,\n            units=\"code_length\",\n        )\n\n    def pixelize(\n        self,\n        dimension,\n        data_source,\n        field,\n        bounds,\n        size,\n        antialias=True,\n        periodic=False,\n        *,\n        return_mask=False,\n    ):\n        # Note that above, we set periodic by default to be *false*.  This is\n        # because our pixelizers, at present, do not handle periodicity\n        # correctly, and if you change the \"width\" of a cylindrical plot, it\n        # double-counts in the edge buffers.  See, for instance, issue 1669.\n        ax_name = self.axis_name[dimension]\n        if ax_name in (\"r\", \"theta\"):\n            buff, mask = self._ortho_pixelize(\n                data_source, field, bounds, size, antialias, dimension, periodic\n            )\n        elif ax_name == \"z\":\n            # This is admittedly a very hacky way to resolve a bug\n            # it's very likely that the *right* fix would have to\n            # be applied upstream of this function, *but* this case\n            # has never worked properly so maybe it's still preferable to\n            # not having a solution ?\n            # see https://github.com/yt-project/yt/pull/3533\n            bounds = (*bounds[2:4], *bounds[:2])\n            buff, mask = self._cyl_pixelize(data_source, field, bounds, size, antialias)\n        else:\n            # Pixelizing along a cylindrical surface is a bit tricky\n            raise NotImplementedError\n\n        if return_mask:\n            assert mask is None or mask.dtype == bool\n            return buff, mask\n        else:\n            return buff\n\n    def pixelize_line(self, field, start_point, end_point, npoints):\n        raise NotImplementedError\n\n    def _ortho_pixelize(\n        self, data_source, field, bounds, size, antialias, dim, periodic\n    ):\n        period = self.period[:2].copy()  # dummy here\n        period[0] = self.period[self.x_axis[dim]]\n        period[1] = self.period[self.y_axis[dim]]\n        if hasattr(period, \"in_units\"):\n            period = period.in_units(\"code_length\").d\n        buff = np.full(size, np.nan, dtype=\"float64\")\n        mask = pixelize_cartesian(\n            buff,\n            data_source[\"px\"],\n            data_source[\"py\"],\n            data_source[\"pdx\"],\n            data_source[\"pdy\"],\n            data_source[field],\n            bounds,\n            int(antialias),\n            period,\n            int(periodic),\n        )\n        return buff, mask\n\n    def _cyl_pixelize(self, data_source, field, bounds, size, antialias):\n        buff = np.full((size[1], size[0]), np.nan, dtype=\"f8\")\n        mask = pixelize_cylinder(\n            buff,\n            data_source[\"px\"],\n            data_source[\"pdx\"],\n            data_source[\"py\"],\n            data_source[\"pdy\"],\n            data_source[field],\n            bounds,\n            return_mask=True,\n        )\n        return buff, mask\n\n    _x_pairs = ((\"r\", \"theta\"), (\"z\", \"r\"), (\"theta\", \"r\"))\n    _y_pairs = ((\"r\", \"z\"), (\"z\", \"theta\"), (\"theta\", \"z\"))\n\n    _image_axis_name = None\n\n    @property\n    def image_axis_name(self):\n        if self._image_axis_name is not None:\n            return self._image_axis_name\n        # This is the x and y axes labels that get displayed.  For\n        # non-Cartesian coordinates, we usually want to override these for\n        # Cartesian coordinates, since we transform them.\n        rv = {\n            self.axis_id[\"r\"]: (\"\\\\theta\", \"z\"),\n            self.axis_id[\"z\"]: (\"x\", \"y\"),\n            self.axis_id[\"theta\"]: (\"r\", \"z\"),\n        }\n        for i in list(rv.keys()):\n            rv[self.axis_name[i]] = rv[i]\n            rv[self.axis_name[i].upper()] = rv[i]\n        self._image_axis_name = rv\n        return rv\n\n    def convert_from_cartesian(self, coord):\n        return cartesian_to_cylindrical(coord)\n\n    def convert_to_cartesian(self, coord):\n        return cylindrical_to_cartesian(coord)\n\n    def convert_to_cylindrical(self, coord):\n        return coord\n\n    def convert_from_cylindrical(self, coord):\n        return coord\n\n    def convert_to_spherical(self, coord):\n        raise NotImplementedError\n\n    def convert_from_spherical(self, coord):\n        raise NotImplementedError\n\n    @property\n    def period(self):\n        return np.array([0.0, 0.0, 2.0 * np.pi])\n\n    @cached_property\n    def _polar_bounds(self):\n        return _get_polar_bounds(self, axes=(\"r\", \"theta\"))\n\n    def sanitize_center(self, center, axis):\n        center, display_center = super().sanitize_center(center, axis)\n        display_center = [\n            0.0 * display_center[0],\n            0.0 * display_center[1],\n            0.0 * display_center[2],\n        ]\n        ax_name = self.axis_name[axis]\n        r_ax = self.axis_id[\"r\"]\n        theta_ax = self.axis_id[\"theta\"]\n        z_ax = self.axis_id[\"z\"]\n        if ax_name == \"r\":\n            display_center[theta_ax] = self.ds.domain_center[theta_ax]\n            display_center[z_ax] = self.ds.domain_center[z_ax]\n        elif ax_name == \"theta\":\n            # use existing center value\n            for idx in (r_ax, z_ax):\n                display_center[idx] = center[idx]\n        elif ax_name == \"z\":\n            xxmin, xxmax, yymin, yymax = self._polar_bounds\n            xc = (xxmin + xxmax) / 2\n            yc = (yymin + yymax) / 2\n            display_center = (xc, yc, 0 * xc)\n        return center, display_center\n\n    def sanitize_width(self, axis, width, depth):\n        name = self.axis_name[axis]\n        r_ax, theta_ax, z_ax = (\n            self.ds.coordinates.axis_id[ax] for ax in (\"r\", \"theta\", \"z\")\n        )\n        if width is not None:\n            width = super().sanitize_width(axis, width, depth)\n        # Note: regardless of axes, these are set up to give consistent plots\n        # when plotted, which is not strictly a \"right hand rule\" for axes.\n        elif name == \"r\":  # soup can label\n            width = [self.ds.domain_width[theta_ax], self.ds.domain_width[z_ax]]\n        elif name == \"theta\":\n            width = [self.ds.domain_width[r_ax], self.ds.domain_width[z_ax]]\n        elif name == \"z\":\n            xxmin, xxmax, yymin, yymax = self._polar_bounds\n            xw = xxmax - xxmin\n            yw = yymax - yymin\n            width = [xw, yw]\n        return width\n"
  },
  {
    "path": "yt/geometry/coordinates/geographic_coordinates.py",
    "content": "import numpy as np\nimport unyt\n\nfrom yt.utilities.lib.pixelization_routines import pixelize_cartesian, pixelize_cylinder\n\nfrom .coordinate_handler import (\n    CoordinateHandler,\n    _get_coord_fields,\n    _setup_dummy_cartesian_coords_and_widths,\n)\n\n\nclass GeographicCoordinateHandler(CoordinateHandler):\n    radial_axis = \"altitude\"\n    name = \"geographic\"\n\n    def __init__(self, ds, ordering=None):\n        if ordering is None:\n            ordering = (\"latitude\", \"longitude\", self.radial_axis)\n        super().__init__(ds, ordering)\n        self.image_units = {}\n        self.image_units[self.axis_id[\"latitude\"]] = (None, None)\n        self.image_units[self.axis_id[\"longitude\"]] = (None, None)\n        self.image_units[self.axis_id[self.radial_axis]] = (\"deg\", \"deg\")\n\n    def setup_fields(self, registry):\n        # Missing implementation for x, y and z coordinates.\n        _setup_dummy_cartesian_coords_and_widths(registry, axes=(\"x\", \"y\", \"z\"))\n\n        f1, f2 = _get_coord_fields(self.axis_id[\"latitude\"], \"\")\n        registry.add_field(\n            (\"index\", \"dlatitude\"),\n            sampling_type=\"cell\",\n            function=f1,\n            display_field=False,\n            units=\"\",\n        )\n\n        registry.add_field(\n            (\"index\", \"latitude\"),\n            sampling_type=\"cell\",\n            function=f2,\n            display_field=False,\n            units=\"\",\n        )\n\n        f1, f2 = _get_coord_fields(self.axis_id[\"longitude\"], \"\")\n        registry.add_field(\n            (\"index\", \"dlongitude\"),\n            sampling_type=\"cell\",\n            function=f1,\n            display_field=False,\n            units=\"\",\n        )\n\n        registry.add_field(\n            (\"index\", \"longitude\"),\n            sampling_type=\"cell\",\n            function=f2,\n            display_field=False,\n            units=\"\",\n        )\n\n        f1, f2 = _get_coord_fields(self.axis_id[self.radial_axis])\n        registry.add_field(\n            (\"index\", f\"d{self.radial_axis}\"),\n            sampling_type=\"cell\",\n            function=f1,\n            display_field=False,\n            units=\"code_length\",\n        )\n\n        registry.add_field(\n            (\"index\", self.radial_axis),\n            sampling_type=\"cell\",\n            function=f2,\n            display_field=False,\n            units=\"code_length\",\n        )\n\n        def _SphericalVolume(data):\n            # We can use the transformed coordinates here.\n            # Here we compute the spherical volume element exactly\n            r = data[\"index\", \"r\"]\n            dr = data[\"index\", \"dr\"]\n            theta = data[\"index\", \"theta\"]\n            dtheta = data[\"index\", \"dtheta\"]\n            vol = ((r + 0.5 * dr) ** 3 - (r - 0.5 * dr) ** 3) / 3.0\n            vol *= np.cos(theta - 0.5 * dtheta) - np.cos(theta + 0.5 * dtheta)\n            vol *= data[\"index\", \"dphi\"]\n            return vol\n\n        registry.add_field(\n            (\"index\", \"cell_volume\"),\n            sampling_type=\"cell\",\n            function=_SphericalVolume,\n            units=\"code_length**3\",\n        )\n        registry.alias((\"index\", \"volume\"), (\"index\", \"cell_volume\"))\n\n        def _path_radial_axis(data):\n            return data[\"index\", f\"d{self.radial_axis}\"]\n\n        registry.add_field(\n            (\"index\", f\"path_element_{self.radial_axis}\"),\n            sampling_type=\"cell\",\n            function=_path_radial_axis,\n            units=\"code_length\",\n        )\n\n        def _path_latitude(data):\n            # We use r here explicitly\n            return data[\"index\", \"r\"] * data[\"index\", \"dlatitude\"] * np.pi / 180.0\n\n        registry.add_field(\n            (\"index\", \"path_element_latitude\"),\n            sampling_type=\"cell\",\n            function=_path_latitude,\n            units=\"code_length\",\n        )\n\n        def _path_longitude(data):\n            # We use r here explicitly\n            return (\n                data[\"index\", \"r\"]\n                * data[\"index\", \"dlongitude\"]\n                * np.pi\n                / 180.0\n                * np.sin((90 - data[\"index\", \"latitude\"]) * np.pi / 180.0)\n            )\n\n        registry.add_field(\n            (\"index\", \"path_element_longitude\"),\n            sampling_type=\"cell\",\n            function=_path_longitude,\n            units=\"code_length\",\n        )\n\n        def _latitude_to_theta(data):\n            # latitude runs from -90 to 90\n            # theta = 0 at +90 deg, np.pi at -90\n            return (90.0 - data[\"index\", \"latitude\"]) * np.pi / 180.0\n\n        registry.add_field(\n            (\"index\", \"theta\"),\n            sampling_type=\"cell\",\n            function=_latitude_to_theta,\n            units=\"\",\n        )\n\n        def _dlatitude_to_dtheta(data):\n            return data[\"index\", \"dlatitude\"] * np.pi / 180.0\n\n        registry.add_field(\n            (\"index\", \"dtheta\"),\n            sampling_type=\"cell\",\n            function=_dlatitude_to_dtheta,\n            units=\"\",\n        )\n\n        def _longitude_to_phi(data):\n            # longitude runs from -180 to 180\n            lonvals = data[\"index\", \"longitude\"]\n            neglons = lonvals < 0.0\n            if np.any(neglons):\n                lonvals[neglons] = lonvals[neglons] + 360.0\n            return lonvals * np.pi / 180.0\n\n        registry.add_field(\n            (\"index\", \"phi\"), sampling_type=\"cell\", function=_longitude_to_phi, units=\"\"\n        )\n\n        def _dlongitude_to_dphi(data):\n            return data[\"index\", \"dlongitude\"] * np.pi / 180.0\n\n        registry.add_field(\n            (\"index\", \"dphi\"),\n            sampling_type=\"cell\",\n            function=_dlongitude_to_dphi,\n            units=\"\",\n        )\n\n        self._setup_radial_fields(registry)\n\n    def _setup_radial_fields(self, registry):\n        # This stays here because we don't want to risk the field detector not\n        # properly getting the data_source, etc.\n        def _altitude_to_radius(data):\n            surface_height = data.get_field_parameter(\"surface_height\")\n            if surface_height is None:\n                if hasattr(data.ds, \"surface_height\"):\n                    surface_height = data.ds.surface_height\n                else:\n                    surface_height = data.ds.quan(0.0, \"code_length\")\n            return data[\"index\", \"altitude\"] + surface_height\n\n        registry.add_field(\n            (\"index\", \"r\"),\n            sampling_type=\"cell\",\n            function=_altitude_to_radius,\n            units=\"code_length\",\n        )\n        registry.alias((\"index\", \"dr\"), (\"index\", \"daltitude\"))\n\n    def _retrieve_radial_offset(self, data_source=None):\n        # This returns the factor by which the radial field is multiplied and\n        # the scalar its offset by.  Typically the \"factor\" will *only* be\n        # either 1.0 or -1.0.  The order will be factor * r + offset.\n        # Altitude is the radius from the central zone minus the radius of the\n        # surface.  Depth to radius is negative value of depth plus the\n        # outermost radius.\n        surface_height = None\n        if data_source is not None:\n            surface_height = data_source.get_field_parameter(\"surface_height\")\n        if surface_height is None:\n            if hasattr(self.ds, \"surface_height\"):\n                surface_height = self.ds.surface_height\n            else:\n                surface_height = self.ds.quan(0.0, \"code_length\")\n        return surface_height, 1.0\n\n    def pixelize(\n        self,\n        dimension,\n        data_source,\n        field,\n        bounds,\n        size,\n        antialias=True,\n        periodic=True,\n        *,\n        return_mask=False,\n    ):\n        if self.axis_name[dimension] in (\"latitude\", \"longitude\"):\n            buff, mask = self._cyl_pixelize(\n                data_source, field, bounds, size, antialias, dimension\n            )\n        elif self.axis_name[dimension] == self.radial_axis:\n            buff, mask = self._ortho_pixelize(\n                data_source, field, bounds, size, antialias, dimension, periodic\n            )\n        else:\n            raise NotImplementedError\n\n        if return_mask:\n            assert mask is None or mask.dtype == bool\n            return buff, mask\n        else:\n            return buff\n\n    def pixelize_line(self, field, start_point, end_point, npoints):\n        raise NotImplementedError\n\n    def _ortho_pixelize(\n        self, data_source, field, bounds, size, antialias, dimension, periodic\n    ):\n        period = self.period[:2].copy()\n        period[0] = self.period[self.x_axis[dimension]]\n        period[1] = self.period[self.y_axis[dimension]]\n        if hasattr(period, \"in_units\"):\n            period = period.in_units(\"code_length\").d\n\n        # For a radial axis, px will correspond to longitude and py will\n        # correspond to latitude.\n        px = data_source[\"px\"]\n        pdx = data_source[\"pdx\"]\n        py = data_source[\"py\"]\n        pdy = data_source[\"pdy\"]\n        buff = np.full((size[1], size[0]), np.nan, dtype=\"float64\")\n        mask = pixelize_cartesian(\n            buff,\n            px,\n            py,\n            pdx,\n            pdy,\n            data_source[field],\n            bounds,\n            int(antialias),\n            period,\n            int(periodic),\n        )\n        return buff, mask\n\n    def _cyl_pixelize(self, data_source, field, bounds, size, antialias, dimension):\n        offset, factor = self._retrieve_radial_offset(data_source)\n        r = factor * data_source[\"py\"] + offset\n        # Because of the axis-ordering, dimensions 0 and 1 both have r as py\n        # and the angular coordinate as px.  But we need to figure out how to\n        # convert our coordinate back to an actual angle, based on which\n        # dimension we're in.\n        pdx = data_source[\"pdx\"].d * np.pi / 180\n        if self.axis_name[self.x_axis[dimension]] == \"latitude\":\n            px = (data_source[\"px\"].d + 90) * np.pi / 180\n            do_transpose = True\n        elif self.axis_name[self.x_axis[dimension]] == \"longitude\":\n            px = (data_source[\"px\"].d + 180) * np.pi / 180\n            do_transpose = False\n        else:\n            # We should never get here!\n            raise NotImplementedError\n        buff = np.full((size[1], size[0]), np.nan, dtype=\"f8\")\n        mask = pixelize_cylinder(\n            buff,\n            r,\n            data_source[\"pdy\"],\n            px,\n            pdx,\n            data_source[field],\n            bounds,\n            return_mask=True,\n        )\n        if do_transpose:\n            buff = buff.transpose()\n            mask = mask.transpose()\n        return buff, mask\n\n    def convert_from_cartesian(self, coord):\n        raise NotImplementedError\n\n    def convert_to_cartesian(self, coord):\n        offset, factor = self._retrieve_radial_offset()\n\n        if isinstance(coord, np.ndarray) and len(coord.shape) > 1:\n            rad = self.axis_id[self.radial_axis]\n            lon = self.axis_id[\"longitude\"]\n            lat = self.axis_id[\"latitude\"]\n            r = factor * coord[:, rad] + offset\n            colatitude = _latitude_to_colatitude(coord[:, lat])\n            phi = coord[:, lon] * np.pi / 180\n            nc = np.zeros_like(coord)\n            # r, theta, phi\n            nc[:, lat] = np.cos(phi) * np.sin(colatitude) * r\n            nc[:, lon] = np.sin(phi) * np.sin(colatitude) * r\n            nc[:, rad] = np.cos(colatitude) * r\n        else:\n            a, b, c = coord\n            colatitude = _latitude_to_colatitude(b)\n            phi = a * np.pi / 180\n            r = factor * c + offset\n            nc = (\n                np.cos(phi) * np.sin(colatitude) * r,\n                np.sin(phi) * np.sin(colatitude) * r,\n                np.cos(colatitude) * r,\n            )\n        return nc\n\n    def convert_to_cylindrical(self, coord):\n        raise NotImplementedError\n\n    def convert_from_cylindrical(self, coord):\n        raise NotImplementedError\n\n    def convert_to_spherical(self, coord):\n        raise NotImplementedError\n\n    def convert_from_spherical(self, coord):\n        raise NotImplementedError\n\n    _image_axis_name = None\n\n    @property\n    def image_axis_name(self):\n        if self._image_axis_name is not None:\n            return self._image_axis_name\n        # This is the x and y axes labels that get displayed.  For\n        # non-Cartesian coordinates, we usually want to override these for\n        # Cartesian coordinates, since we transform them.\n        rv = {\n            self.axis_id[\"latitude\"]: (\n                \"x / \\\\sin(\\\\mathrm{latitude})\",\n                \"y / \\\\sin(\\\\mathrm{latitude})\",\n            ),\n            self.axis_id[\"longitude\"]: (\"R\", \"z\"),\n            self.axis_id[self.radial_axis]: (\"longitude\", \"latitude\"),\n        }\n        for i in list(rv.keys()):\n            rv[self.axis_name[i]] = rv[i]\n            rv[self.axis_name[i].capitalize()] = rv[i]\n        self._image_axis_name = rv\n        return rv\n\n    _x_pairs = (\n        (\"latitude\", \"longitude\"),\n        (\"longitude\", \"latitude\"),\n        (\"altitude\", \"longitude\"),\n    )\n\n    _y_pairs = (\n        (\"latitude\", \"altitude\"),\n        (\"longitude\", \"altitude\"),\n        (\"altitude\", \"latitude\"),\n    )\n\n    _data_projection = None\n\n    @property\n    def data_projection(self):\n        # this will control the default projection to use when displaying data\n        if self._data_projection is not None:\n            return self._data_projection\n        dpj = {}\n        for ax in self.axis_order:\n            if ax == self.radial_axis:\n                dpj[ax] = \"Mollweide\"\n            else:\n                dpj[ax] = None\n        self._data_projection = dpj\n        return dpj\n\n    _data_transform = None\n\n    @property\n    def data_transform(self):\n        # this is the coordinate system on which the data is defined (the crs).\n        if self._data_transform is not None:\n            return self._data_transform\n        dtx = {}\n        for ax in self.axis_order:\n            if ax == self.radial_axis:\n                dtx[ax] = \"PlateCarree\"\n            else:\n                dtx[ax] = None\n        self._data_transform = dtx\n        return dtx\n\n    @property\n    def period(self):\n        return self.ds.domain_width\n\n    def sanitize_center(self, center, axis):\n        center, display_center = super().sanitize_center(center, axis)\n        name = self.axis_name[axis]\n        if name == self.radial_axis:\n            display_center = center\n        elif name == \"latitude\":\n            display_center = (\n                0.0 * display_center[0],\n                0.0 * display_center[1],\n                0.0 * display_center[2],\n            )\n        elif name == \"longitude\":\n            ri = self.axis_id[self.radial_axis]\n            c = (self.ds.domain_right_edge[ri] + self.ds.domain_left_edge[ri]) / 2.0\n            display_center = [\n                0.0 * display_center[0],\n                0.0 * display_center[1],\n                0.0 * display_center[2],\n            ]\n            display_center[self.axis_id[\"latitude\"]] = c\n        return center, display_center\n\n    def sanitize_width(self, axis, width, depth):\n        name = self.axis_name[axis]\n        if width is not None:\n            width = super().sanitize_width(axis, width, depth)\n        elif name == self.radial_axis:\n            rax = self.radial_axis\n            width = [\n                self.ds.domain_width[self.x_axis[rax]],\n                self.ds.domain_width[self.y_axis[rax]],\n            ]\n        elif name == \"latitude\":\n            ri = self.axis_id[self.radial_axis]\n            # Remember, in spherical coordinates when we cut in theta,\n            # we create a conic section\n            width = [2.0 * self.ds.domain_width[ri], 2.0 * self.ds.domain_width[ri]]\n        elif name == \"longitude\":\n            ri = self.axis_id[self.radial_axis]\n            width = [self.ds.domain_width[ri], 2.0 * self.ds.domain_width[ri]]\n        return width\n\n\nclass InternalGeographicCoordinateHandler(GeographicCoordinateHandler):\n    radial_axis = \"depth\"\n    name = \"internal_geographic\"\n\n    def _setup_radial_fields(self, registry):\n        # Altitude is the radius from the central zone minus the radius of the\n        # surface.\n        def _depth_to_radius(data):\n            outer_radius = data.get_field_parameter(\"outer_radius\")\n            if outer_radius is None:\n                if hasattr(data.ds, \"outer_radius\"):\n                    outer_radius = data.ds.outer_radius\n                else:\n                    # Otherwise, we assume that the depth goes to full depth,\n                    # so we can look at the domain right edge in depth.\n                    rax = self.axis_id[self.radial_axis]\n                    outer_radius = data.ds.domain_right_edge[rax]\n            return -1.0 * data[\"index\", \"depth\"] + outer_radius\n\n        registry.add_field(\n            (\"index\", \"r\"),\n            sampling_type=\"cell\",\n            function=_depth_to_radius,\n            units=\"code_length\",\n        )\n        registry.alias((\"index\", \"dr\"), (\"index\", \"ddepth\"))\n\n    def _retrieve_radial_offset(self, data_source=None):\n        # Depth means switching sign and adding to full radius\n        outer_radius = None\n        if data_source is not None:\n            outer_radius = data_source.get_field_parameter(\"outer_radius\")\n        if outer_radius is None:\n            if hasattr(self.ds, \"outer_radius\"):\n                outer_radius = self.ds.outer_radius\n            else:\n                # Otherwise, we assume that the depth goes to full depth,\n                # so we can look at the domain right edge in depth.\n                rax = self.axis_id[self.radial_axis]\n                outer_radius = self.ds.domain_right_edge[rax]\n        return outer_radius, -1.0\n\n    _x_pairs = (\n        (\"latitude\", \"longitude\"),\n        (\"longitude\", \"latitude\"),\n        (\"depth\", \"longitude\"),\n    )\n\n    _y_pairs = ((\"latitude\", \"depth\"), (\"longitude\", \"depth\"), (\"depth\", \"latitude\"))\n\n    def sanitize_center(self, center, axis):\n        center, display_center = super(\n            GeographicCoordinateHandler, self\n        ).sanitize_center(center, axis)\n        name = self.axis_name[axis]\n        if name == self.radial_axis:\n            display_center = center\n        elif name == \"latitude\":\n            display_center = (\n                0.0 * display_center[0],\n                0.0 * display_center[1],\n                0.0 * display_center[2],\n            )\n        elif name == \"longitude\":\n            ri = self.axis_id[self.radial_axis]\n            offset, factor = self._retrieve_radial_offset()\n            outermost = factor * self.ds.domain_left_edge[ri] + offset\n            display_center = [\n                0.0 * display_center[0],\n                0.0 * display_center[1],\n                0.0 * display_center[2],\n            ]\n            display_center[self.axis_id[\"latitude\"]] = outermost / 2.0\n        return center, display_center\n\n    def sanitize_width(self, axis, width, depth):\n        name = self.axis_name[axis]\n        if width is not None:\n            width = super(GeographicCoordinateHandler, self).sanitize_width(\n                axis, width, depth\n            )\n        elif name == self.radial_axis:\n            rax = self.radial_axis\n            width = [\n                self.ds.domain_width[self.x_axis[rax]],\n                self.ds.domain_width[self.y_axis[rax]],\n            ]\n        elif name == \"latitude\":\n            ri = self.axis_id[self.radial_axis]\n            # Remember, in spherical coordinates when we cut in theta,\n            # we create a conic section\n            offset, factor = self._retrieve_radial_offset()\n            outermost = factor * self.ds.domain_left_edge[ri] + offset\n            width = [2.0 * outermost, 2.0 * outermost]\n        elif name == \"longitude\":\n            ri = self.axis_id[self.radial_axis]\n            offset, factor = self._retrieve_radial_offset()\n            outermost = factor * self.ds.domain_left_edge[ri] + offset\n            width = [outermost, 2.0 * outermost]\n        return width\n\n\ndef _latitude_to_colatitude(lat_vals):\n    # convert latitude to theta, accounting for units,\n    # including the case where the units are code_length\n    # due to how yt stores the domain_center units for\n    # geographic coordinates.\n    if isinstance(lat_vals, unyt.unyt_array):\n        if lat_vals.units.dimensions == unyt.dimensions.length:\n            return (90.0 - lat_vals.d) * np.pi / 180.0\n        ninety = unyt.unyt_quantity(90.0, \"degree\")\n        return (ninety - lat_vals).to(\"radian\")\n    return (90 - lat_vals) * np.pi / 180.0\n"
  },
  {
    "path": "yt/geometry/coordinates/polar_coordinates.py",
    "content": "from .cylindrical_coordinates import CylindricalCoordinateHandler\n\n\nclass PolarCoordinateHandler(CylindricalCoordinateHandler):\n    name = \"polar\"\n    _default_axis_order = (\"r\", \"theta\", \"z\")\n"
  },
  {
    "path": "yt/geometry/coordinates/spec_cube_coordinates.py",
    "content": "from .cartesian_coordinates import CartesianCoordinateHandler\nfrom .coordinate_handler import _get_coord_fields\n\n\nclass SpectralCubeCoordinateHandler(CartesianCoordinateHandler):\n    name = \"spectral_cube\"\n\n    def __init__(self, ds, ordering=None):\n        if ordering is None:\n            ordering = tuple(\n                \"xyz\"[axis] for axis in (ds.lon_axis, ds.lat_axis, ds.spec_axis)\n            )\n        super().__init__(ds, ordering)\n\n        self.default_unit_label = {}\n        names = {}\n        if ds.lon_name != \"X\" or ds.lat_name != \"Y\":\n            names[\"x\"] = r\"Image\\ x\"\n            names[\"y\"] = r\"Image\\ y\"\n            # We can just use ds.lon_axis here\n            self.default_unit_label[ds.lon_axis] = \"pixel\"\n            self.default_unit_label[ds.lat_axis] = \"pixel\"\n        names[\"z\"] = ds.spec_name\n        # Again, can use spec_axis here\n        self.default_unit_label[ds.spec_axis] = ds.spec_unit\n\n        self._image_axis_name = ian = {}\n        for ax in \"xyz\":\n            axi = self.axis_id[ax]\n            xax = self.axis_name[self.x_axis[ax]]\n            yax = self.axis_name[self.y_axis[ax]]\n            ian[axi] = ian[ax] = ian[ax.upper()] = (\n                names.get(xax, xax),\n                names.get(yax, yax),\n            )\n\n        def _spec_axis(ax, x, y):\n            p = (x, y)[ax]\n            return [self.ds.pixel2spec(pp).v for pp in p]\n\n        self.axis_field = {}\n        self.axis_field[self.ds.spec_axis] = _spec_axis\n\n    def setup_fields(self, registry):\n        if not self.ds.no_cgs_equiv_length:\n            return super().setup_fields(registry)\n        for axi, ax in enumerate(\"xyz\"):\n            f1, f2 = _get_coord_fields(axi)\n\n            def _get_length_func():\n                def _length_func(field, data):\n                    # Just use axis 0\n                    rv = data.ds.arr(data.fcoords[..., 0].copy(), field.units)\n                    rv[:] = 1.0\n                    return rv\n\n                return _length_func\n\n            registry.add_field(\n                (\"index\", f\"d{ax}\"),\n                sampling_type=\"cell\",\n                function=f1,\n                display_field=False,\n                units=\"code_length\",\n            )\n\n            registry.add_field(\n                (\"index\", f\"path_element_{ax}\"),\n                sampling_type=\"cell\",\n                function=_get_length_func(),\n                display_field=False,\n                units=\"\",\n            )\n\n            registry.add_field(\n                (\"index\", f\"{ax}\"),\n                sampling_type=\"cell\",\n                function=f2,\n                display_field=False,\n                units=\"code_length\",\n            )\n\n        self._register_volume(registry)\n        self._check_fields(registry)\n\n    _x_pairs = ((\"x\", \"y\"), (\"y\", \"x\"), (\"z\", \"x\"))\n    _y_pairs = ((\"x\", \"z\"), (\"y\", \"z\"), (\"z\", \"y\"))\n\n    def convert_to_cylindrical(self, coord):\n        raise NotImplementedError\n\n    def convert_from_cylindrical(self, coord):\n        raise NotImplementedError\n\n    @property\n    def image_axis_name(self):\n        return self._image_axis_name\n"
  },
  {
    "path": "yt/geometry/coordinates/spherical_coordinates.py",
    "content": "from functools import cached_property\n\nimport numpy as np\n\nfrom yt.utilities.lib.pixelization_routines import pixelize_aitoff, pixelize_cylinder\n\nfrom .coordinate_handler import (\n    CoordinateHandler,\n    _get_coord_fields,\n    _get_polar_bounds,\n    _setup_dummy_cartesian_coords_and_widths,\n    _setup_polar_coordinates,\n)\n\n\nclass SphericalCoordinateHandler(CoordinateHandler):\n    name = \"spherical\"\n    _default_axis_order = (\"r\", \"theta\", \"phi\")\n\n    def __init__(self, ds, ordering=None):\n        super().__init__(ds, ordering)\n        # Generate\n        self.image_units = {}\n        self.image_units[self.axis_id[\"r\"]] = (1, 1)\n        self.image_units[self.axis_id[\"theta\"]] = (None, None)\n        self.image_units[self.axis_id[\"phi\"]] = (None, None)\n\n    def setup_fields(self, registry):\n        # Missing implementation for x, y and z coordinates.\n        _setup_dummy_cartesian_coords_and_widths(registry, axes=(\"x\", \"y\", \"z\"))\n        _setup_polar_coordinates(registry, self.axis_id)\n\n        f1, f2 = _get_coord_fields(self.axis_id[\"phi\"], \"dimensionless\")\n        registry.add_field(\n            (\"index\", \"dphi\"),\n            sampling_type=\"cell\",\n            function=f1,\n            display_field=False,\n            units=\"dimensionless\",\n        )\n\n        registry.add_field(\n            (\"index\", \"phi\"),\n            sampling_type=\"cell\",\n            function=f2,\n            display_field=False,\n            units=\"dimensionless\",\n        )\n\n        def _SphericalVolume(data):\n            # Here we compute the spherical volume element exactly\n            r = data[\"index\", \"r\"]\n            dr = data[\"index\", \"dr\"]\n            theta = data[\"index\", \"theta\"]\n            dtheta = data[\"index\", \"dtheta\"]\n            vol = ((r + 0.5 * dr) ** 3 - (r - 0.5 * dr) ** 3) / 3.0\n            vol *= np.cos(theta - 0.5 * dtheta) - np.cos(theta + 0.5 * dtheta)\n            vol *= data[\"index\", \"dphi\"]\n            return vol\n\n        registry.add_field(\n            (\"index\", \"cell_volume\"),\n            sampling_type=\"cell\",\n            function=_SphericalVolume,\n            units=\"code_length**3\",\n        )\n        registry.alias((\"index\", \"volume\"), (\"index\", \"cell_volume\"))\n\n        def _path_phi(data):\n            # Note: this already assumes cell-centered\n            return (\n                data[\"index\", \"r\"]\n                * data[\"index\", \"dphi\"]\n                * np.sin(data[\"index\", \"theta\"])\n            )\n\n        registry.add_field(\n            (\"index\", \"path_element_phi\"),\n            sampling_type=\"cell\",\n            function=_path_phi,\n            units=\"code_length\",\n        )\n\n    def pixelize(\n        self,\n        dimension,\n        data_source,\n        field,\n        bounds,\n        size,\n        antialias=True,\n        periodic=True,\n        *,\n        return_mask=False,\n    ):\n        self.period\n        name = self.axis_name[dimension]\n        if name == \"r\":\n            buff, mask = self._ortho_pixelize(\n                data_source, field, bounds, size, antialias, dimension, periodic\n            )\n        elif name in (\"theta\", \"phi\"):\n            if name == \"theta\":\n                # This is admittedly a very hacky way to resolve a bug\n                # it's very likely that the *right* fix would have to\n                # be applied upstream of this function, *but* this case\n                # has never worked properly so maybe it's still preferable to\n                # not having a solution ?\n                # see https://github.com/yt-project/yt/pull/3533\n                bounds = (*bounds[2:4], *bounds[:2])\n            buff, mask = self._cyl_pixelize(\n                data_source, field, bounds, size, antialias, dimension\n            )\n        else:\n            raise NotImplementedError\n\n        if return_mask:\n            assert mask is None or mask.dtype == bool\n            return buff, mask\n        else:\n            return buff\n\n    def pixelize_line(self, field, start_point, end_point, npoints):\n        raise NotImplementedError\n\n    def _ortho_pixelize(\n        self, data_source, field, bounds, size, antialias, dim, periodic\n    ):\n        # use Aitoff projection\n        # http://paulbourke.net/geometry/transformationprojection/\n        bounds = tuple(_.ndview for _ in self._aitoff_bounds)\n        buff, mask = pixelize_aitoff(\n            azimuth=data_source[\"py\"],\n            dazimuth=data_source[\"pdy\"],\n            colatitude=data_source[\"px\"],\n            dcolatitude=data_source[\"pdx\"],\n            buff_size=size,\n            field=data_source[field],\n            bounds=bounds,\n            input_img=None,\n            azimuth_offset=0,\n            colatitude_offset=0,\n            return_mask=True,\n        )\n        return buff.T, mask.T\n\n    def _cyl_pixelize(self, data_source, field, bounds, size, antialias, dimension):\n        name = self.axis_name[dimension]\n        buff = np.full((size[1], size[0]), np.nan, dtype=\"f8\")\n        if name == \"theta\":\n            mask = pixelize_cylinder(\n                buff,\n                data_source[\"px\"],\n                data_source[\"pdx\"],\n                data_source[\"py\"],\n                data_source[\"pdy\"],\n                data_source[field],\n                bounds,\n                return_mask=True,\n            )\n        elif name == \"phi\":\n            # Note that we feed in buff.T here\n            mask = pixelize_cylinder(\n                buff.T,\n                data_source[\"px\"],\n                data_source[\"pdx\"],\n                data_source[\"py\"],\n                data_source[\"pdy\"],\n                data_source[field],\n                bounds,\n                return_mask=True,\n            ).T\n        else:\n            raise RuntimeError\n        return buff, mask\n\n    def convert_from_cartesian(self, coord):\n        raise NotImplementedError\n\n    def convert_to_cartesian(self, coord):\n        if isinstance(coord, np.ndarray) and len(coord.shape) > 1:\n            ri = self.axis_id[\"r\"]\n            thetai = self.axis_id[\"theta\"]\n            phii = self.axis_id[\"phi\"]\n            r = coord[:, ri]\n            theta = coord[:, thetai]\n            phi = coord[:, phii]\n            nc = np.zeros_like(coord)\n            # r, theta, phi\n            nc[:, ri] = np.cos(phi) * np.sin(theta) * r\n            nc[:, thetai] = np.sin(phi) * np.sin(theta) * r\n            nc[:, phii] = np.cos(theta) * r\n        else:\n            r, theta, phi = coord\n            nc = (\n                np.cos(phi) * np.sin(theta) * r,\n                np.sin(phi) * np.sin(theta) * r,\n                np.cos(theta) * r,\n            )\n        return nc\n\n    def convert_to_cylindrical(self, coord):\n        raise NotImplementedError\n\n    def convert_from_cylindrical(self, coord):\n        raise NotImplementedError\n\n    def convert_to_spherical(self, coord):\n        raise NotImplementedError\n\n    def convert_from_spherical(self, coord):\n        raise NotImplementedError\n\n    _image_axis_name = None\n\n    @property\n    def image_axis_name(self):\n        if self._image_axis_name is not None:\n            return self._image_axis_name\n        # This is the x and y axes labels that get displayed.  For\n        # non-Cartesian coordinates, we usually want to override these for\n        # Cartesian coordinates, since we transform them.\n        rv = {\n            self.axis_id[\"r\"]: (\n                # these are the Hammer-Aitoff normalized coordinates\n                # conventions:\n                #   - theta is the colatitude, from 0 to PI\n                #   - bartheta is the latitude, from -PI/2 to +PI/2 (bartheta = PI/2 - theta)\n                #   - phi is the azimuth, from 0 to 2PI\n                #   - lambda is the longitude, from -PI to PI (lambda = phi - PI)\n                r\"\\frac{2\\cos(\\mathrm{\\bar{\\theta}})\\sin(\\lambda/2)}{\\sqrt{1 + \\cos(\\bar{\\theta}) \\cos(\\lambda/2)}}\",\n                r\"\\frac{sin(\\bar{\\theta})}{\\sqrt{1 + \\cos(\\bar{\\theta}) \\cos(\\lambda/2)}}\",\n                \"\\\\theta\",\n            ),\n            self.axis_id[\"theta\"]: (\"x / \\\\sin(\\\\theta)\", \"y / \\\\sin(\\\\theta)\"),\n            self.axis_id[\"phi\"]: (\"R\", \"z\"),\n        }\n        for i in list(rv.keys()):\n            rv[self.axis_name[i]] = rv[i]\n            rv[self.axis_name[i].capitalize()] = rv[i]\n        self._image_axis_name = rv\n        return rv\n\n    _x_pairs = ((\"r\", \"theta\"), (\"theta\", \"r\"), (\"phi\", \"r\"))\n    _y_pairs = ((\"r\", \"phi\"), (\"theta\", \"phi\"), (\"phi\", \"theta\"))\n\n    @property\n    def period(self):\n        return self.ds.domain_width\n\n    @cached_property\n    def _poloidal_bounds(self):\n        ri = self.axis_id[\"r\"]\n        ti = self.axis_id[\"theta\"]\n        rmin = self.ds.domain_left_edge[ri]\n        rmax = self.ds.domain_right_edge[ri]\n        thetamin = self.ds.domain_left_edge[ti]\n        thetamax = self.ds.domain_right_edge[ti]\n        corners = [\n            (rmin, thetamin),\n            (rmin, thetamax),\n            (rmax, thetamin),\n            (rmax, thetamax),\n        ]\n\n        def to_poloidal_plane(r, theta):\n            # take a r, theta position and return\n            # cylindrical R, z coordinates\n            R = r * np.sin(theta)\n            z = r * np.cos(theta)\n            return R, z\n\n        cylindrical_corner_coords = [to_poloidal_plane(*corner) for corner in corners]\n\n        thetamin = thetamin.d\n        thetamax = thetamax.d\n\n        Rmin = min(R for R, z in cylindrical_corner_coords)\n\n        if thetamin <= np.pi / 2 <= thetamax:\n            Rmax = rmax\n        else:\n            Rmax = max(R for R, z in cylindrical_corner_coords)\n\n        zmin = min(z for R, z in cylindrical_corner_coords)\n        zmax = max(z for R, z in cylindrical_corner_coords)\n\n        return Rmin, Rmax, zmin, zmax\n\n    @cached_property\n    def _conic_bounds(self):\n        return _get_polar_bounds(self, axes=(\"r\", \"phi\"))\n\n    @cached_property\n    def _aitoff_bounds(self):\n        # at the time of writing this function, yt's support for curvilinear\n        # coordinates is a bit hacky, as many components of the system still\n        # expect to receive coordinates with a length dimension. Ultimately\n        # this is not needed but calls for a large refactor.\n        ONE = self.ds.quan(1, \"code_length\")\n\n        # colatitude\n        ti = self.axis_id[\"theta\"]\n        thetamin = self.ds.domain_left_edge[ti]\n        thetamax = self.ds.domain_right_edge[ti]\n        # latitude\n        latmin = ONE * np.pi / 2 - thetamax\n        latmax = ONE * np.pi / 2 - thetamin\n\n        # azimuth\n        pi = self.axis_id[\"phi\"]\n        phimin = self.ds.domain_left_edge[pi]\n        phimax = self.ds.domain_right_edge[pi]\n        # longitude\n        lonmin = phimin - ONE * np.pi\n        lonmax = phimax - ONE * np.pi\n\n        corners = [\n            (latmin, lonmin),\n            (latmin, lonmax),\n            (latmax, lonmin),\n            (latmax, lonmax),\n        ]\n\n        def aitoff_z(latitude, longitude):\n            return np.sqrt(1 + np.cos(latitude) * np.cos(longitude / 2))\n\n        def aitoff_x(latitude, longitude):\n            return (\n                2\n                * np.cos(latitude)\n                * np.sin(longitude / 2)\n                / aitoff_z(latitude, longitude)\n            )\n\n        def aitoff_y(latitude, longitude):\n            return np.sin(latitude) / aitoff_z(latitude, longitude)\n\n        def to_aitoff_plane(latitude, longitude):\n            return aitoff_x(latitude, longitude), aitoff_y(latitude, longitude)\n\n        aitoff_corner_coords = [to_aitoff_plane(*corner) for corner in corners]\n\n        xmin = ONE * min(x for x, y in aitoff_corner_coords)\n        xmax = ONE * max(x for x, y in aitoff_corner_coords)\n\n        # theta is the colatitude\n        # What this branch is meant to do is check whether the equator (latitude = 0)\n        # is included in the domain.\n\n        if latmin < 0 < latmax:\n            xmin = min(xmin, ONE * aitoff_x(0, lonmin))\n            xmax = max(xmax, ONE * aitoff_x(0, lonmax))\n\n        # the y direction is more straightforward because aitoff-projected parallels (y)\n        # draw a convex shape, while aitoff-projected meridians (x) draw a concave shape\n        ymin = ONE * min(y for x, y in aitoff_corner_coords)\n        ymax = ONE * max(y for x, y in aitoff_corner_coords)\n\n        return xmin, xmax, ymin, ymax\n\n    def sanitize_center(self, center, axis):\n        center, display_center = super().sanitize_center(center, axis)\n        name = self.axis_name[axis]\n        if name == \"r\":\n            xxmin, xxmax, yymin, yymax = self._aitoff_bounds\n            xc = (xxmin + xxmax) / 2\n            yc = (yymin + yymax) / 2\n            display_center = (0 * xc, xc, yc)\n        elif name == \"theta\":\n            xxmin, xxmax, yymin, yymax = self._conic_bounds\n            xc = (xxmin + xxmax) / 2\n            yc = (yymin + yymax) / 2\n            display_center = (xc, 0 * xc, yc)\n        elif name == \"phi\":\n            Rmin, Rmax, zmin, zmax = self._poloidal_bounds\n            xc = (Rmin + Rmax) / 2\n            yc = (zmin + zmax) / 2\n            display_center = (xc, yc)\n        return center, display_center\n\n    def sanitize_width(self, axis, width, depth):\n        name = self.axis_name[axis]\n        if width is not None:\n            width = super().sanitize_width(axis, width, depth)\n        elif name == \"r\":\n            xxmin, xxmax, yymin, yymax = self._aitoff_bounds\n            xw = xxmax - xxmin\n            yw = yymax - yymin\n            width = [xw, yw]\n        elif name == \"theta\":\n            # Remember, in spherical coordinates when we cut in theta,\n            # we create a conic section\n            xxmin, xxmax, yymin, yymax = self._conic_bounds\n            xw = xxmax - xxmin\n            yw = yymax - yymin\n            width = [xw, yw]\n        elif name == \"phi\":\n            Rmin, Rmax, zmin, zmax = self._poloidal_bounds\n            xw = Rmax - Rmin\n            yw = zmax - zmin\n            width = [xw, yw]\n        return width\n"
  },
  {
    "path": "yt/geometry/coordinates/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/geometry/coordinates/tests/test_axial_pixelization.py",
    "content": "from yt.testing import _geom_transforms, fake_amr_ds\nfrom yt.utilities.answer_testing.framework import AxialPixelizationTest\n\n\ndef test_axial_pixelization():\n    for geom in sorted(_geom_transforms):\n        if geom == \"spectral_cube\":\n            # skip this case as it was added much later and we don't want to keep\n            # adding yield-based tests during the nose->pytest migration\n            continue\n        ds = fake_amr_ds(geometry=geom)\n        yield AxialPixelizationTest(ds)\n"
  },
  {
    "path": "yt/geometry/coordinates/tests/test_cartesian_coordinates.py",
    "content": "# Some tests for the Cartesian coordinates handler\n\nimport numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import fake_amr_ds\n\n# Our canonical tests are that we can access all of our fields and we can\n# compute our volume correctly.\n\n\ndef test_cartesian_coordinates():\n    # We're going to load up a simple AMR grid and check its volume\n    # calculations and path length calculations.\n    ds = fake_amr_ds()\n    axes = sorted(set(ds.coordinates.axis_name.values()))\n    for i, axis in enumerate(axes):\n        dd = ds.all_data()\n        fi = (\"index\", axis)\n        fd = (\"index\", f\"d{axis}\")\n        fp = (\"index\", f\"path_element_{axis}\")\n        ma = np.argmax(dd[fi])\n        assert_equal(dd[fi][ma] + dd[fd][ma] / 2.0, ds.domain_right_edge[i])\n        mi = np.argmin(dd[fi])\n        assert_equal(dd[fi][mi] - dd[fd][mi] / 2.0, ds.domain_left_edge[i])\n        assert_equal(dd[fd].min(), ds.index.get_smallest_dx())\n        assert_equal(dd[fd].max(), (ds.domain_width / ds.domain_dimensions)[i])\n        assert_equal(dd[fd], dd[fp])\n    assert_equal(\n        dd[\"index\", \"cell_volume\"].sum(dtype=\"float64\"), ds.domain_width.prod()\n    )\n"
  },
  {
    "path": "yt/geometry/coordinates/tests/test_cylindrical_coordinates.py",
    "content": "# Some tests for the Cylindrical coordinates handler\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.testing import fake_amr_ds\n\n# Our canonical tests are that we can access all of our fields and we can\n# compute our volume correctly.\n\n\ndef test_cylindrical_coordinates():\n    # We're going to load up a simple AMR grid and check its volume\n    # calculations and path length calculations.\n    ds = fake_amr_ds(geometry=\"cylindrical\")\n    axes = [\"r\", \"z\", \"theta\"]\n    for i, axis in enumerate(axes):\n        dd = ds.all_data()\n        fi = (\"index\", axis)\n        fd = (\"index\", f\"d{axis}\")\n        ma = np.argmax(dd[fi])\n        assert_equal(dd[fi][ma] + dd[fd][ma] / 2.0, ds.domain_right_edge[i].d)\n        mi = np.argmin(dd[fi])\n        assert_equal(dd[fi][mi] - dd[fd][mi] / 2.0, ds.domain_left_edge[i].d)\n        assert_equal(dd[fd].max(), (ds.domain_width / ds.domain_dimensions)[i].d)\n    assert_almost_equal(\n        dd[\"index\", \"cell_volume\"].sum(dtype=\"float64\"),\n        np.pi * ds.domain_width[0] ** 2 * ds.domain_width[1],\n    )\n    assert_equal(dd[\"index\", \"path_element_r\"], dd[\"index\", \"dr\"])\n    assert_equal(dd[\"index\", \"path_element_z\"], dd[\"index\", \"dz\"])\n    assert_equal(\n        dd[\"index\", \"path_element_theta\"], dd[\"index\", \"r\"] * dd[\"index\", \"dtheta\"]\n    )\n"
  },
  {
    "path": "yt/geometry/coordinates/tests/test_geographic_coordinates.py",
    "content": "# Some tests for the geographic coordinates handler\n\nimport numpy as np\nimport pytest\nimport unyt\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import assert_rel_equal, fake_amr_ds\n\n# Our canonical tests are that we can access all of our fields and we can\n# compute our volume correctly.\n\n\n@pytest.mark.parametrize(\"geometry\", (\"geographic\", \"internal_geographic\"))\ndef test_geographic_coordinates(geometry):\n    # We're going to load up a simple AMR grid and check its volume\n    # calculations and path length calculations.\n\n    # Note that we are setting it up to have an altitude of 1000 maximum, which\n    # means our volume will be that of a shell 1000 wide, starting at r of\n    # whatever our surface_height is set to.\n    ds = fake_amr_ds(geometry=geometry)\n    if geometry == \"geographic\":\n        ds.surface_height = ds.quan(5000.0, \"code_length\")\n        inner_r = ds.surface_height\n        outer_r = ds.surface_height + ds.domain_width[2]\n    else:\n        ds.outer_radius = ds.quan(5000.0, \"code_length\")\n        inner_r = ds.outer_radius - ds.domain_right_edge[2]\n        outer_r = ds.outer_radius\n    radial_axis = ds.coordinates.radial_axis\n    axes = ds.coordinates.axis_order\n    for i, axis in enumerate(axes):\n        dd = ds.all_data()\n        fi = (\"index\", axis)\n        fd = (\"index\", f\"d{axis}\")\n        ma = np.argmax(dd[fi])\n        assert_equal(dd[fi][ma] + dd[fd][ma] / 2.0, ds.domain_right_edge[i].d)\n        mi = np.argmin(dd[fi])\n        assert_equal(dd[fi][mi] - dd[fd][mi] / 2.0, ds.domain_left_edge[i].d)\n        assert_equal(dd[fd].max(), (ds.domain_width / ds.domain_dimensions)[i].d)\n\n    assert_equal(dd[\"index\", \"dtheta\"], dd[\"index\", \"dlatitude\"] * np.pi / 180.0)\n    assert_equal(dd[\"index\", \"dphi\"], dd[\"index\", \"dlongitude\"] * np.pi / 180.0)\n    # Note our terrible agreement here.\n    assert_rel_equal(\n        dd[\"index\", \"cell_volume\"].sum(dtype=\"float64\"),\n        (4.0 / 3.0) * np.pi * (outer_r**3 - inner_r**3),\n        14,\n    )\n    assert_equal(\n        dd[\"index\", f\"path_element_{radial_axis}\"], dd[\"index\", f\"d{radial_axis}\"]\n    )\n    assert_equal(dd[\"index\", f\"path_element_{radial_axis}\"], dd[\"index\", \"dr\"])\n    # Note that latitude corresponds to theta, longitude to phi\n    assert_equal(\n        dd[\"index\", \"path_element_latitude\"],\n        dd[\"index\", \"r\"] * dd[\"index\", \"dlatitude\"] * np.pi / 180.0,\n    )\n    assert_equal(\n        dd[\"index\", \"path_element_longitude\"],\n        (\n            dd[\"index\", \"r\"]\n            * dd[\"index\", \"dlongitude\"]\n            * np.pi\n            / 180.0\n            * np.sin((90 - dd[\"index\", \"latitude\"]) * np.pi / 180.0)\n        ),\n    )\n    # We also want to check that our radius is correct\n    offset, factor = ds.coordinates._retrieve_radial_offset()\n    radius = factor * dd[\"index\", radial_axis] + offset\n    assert_equal(dd[\"index\", \"r\"], radius)\n\n\n@pytest.mark.parametrize(\"geometry\", (\"geographic\", \"internal_geographic\"))\ndef test_geographic_conversions(geometry):\n    ds = fake_amr_ds(geometry=geometry)\n    ad = ds.all_data()\n    lats = ad[\"index\", \"latitude\"]\n    dlats = ad[\"index\", \"dlatitude\"]\n    theta = ad[\"index\", \"theta\"]\n    dtheta = ad[\"index\", \"dtheta\"]\n\n    # check that theta = 0, pi at latitudes of 90, -90\n    south_pole_id = np.where(lats == np.min(lats))[0][0]\n    north_pole_id = np.where(lats == np.max(lats))[0][0]\n\n    # check that we do in fact have -90, 90 exactly\n    assert lats[south_pole_id] - dlats[south_pole_id] / 2.0 == -90.0\n    assert lats[north_pole_id] + dlats[north_pole_id] / 2.0 == 90.0\n\n    # check that theta=0 at the north pole, np.pi at the south\n    assert theta[north_pole_id] - dtheta[north_pole_id] / 2 == 0.0\n    assert theta[south_pole_id] + dtheta[south_pole_id] / 2 == np.pi\n\n    # check that longitude-phi conversions\n    phi = ad[\"index\", \"phi\"]\n    dphi = ad[\"index\", \"dphi\"]\n    lons = ad[\"index\", \"longitude\"]\n    dlon = ad[\"index\", \"dlongitude\"]\n    lon_180 = np.where(lons == np.max(lons))[0][0]\n    lon_neg180 = np.where(lons == np.min(lons))[0][0]\n    # check we have -180, 180 exactly\n    assert lons[lon_neg180] - dlon[lon_neg180] / 2.0 == -180.0\n    assert lons[lon_180] + dlon[lon_180] / 2.0 == 180.0\n    # check that those both convert to phi = np.pi\n    assert phi[lon_neg180] - dphi[lon_neg180] / 2.0 == np.pi\n    assert phi[lon_180] + dphi[lon_180] / 2.0 == np.pi\n\n    # check that z = +/- radius at +/-90\n    # default expected axis order: lat, lon, radial axis\n    r_val = ds.coordinates._retrieve_radial_offset()[0]\n    coords = np.zeros((2, 3))\n    coords[0, 0] = 90.0\n    coords[1, 0] = -90.0\n    xyz = ds.coordinates.convert_to_cartesian(coords)\n    z = xyz[:, 2]\n    assert z[0] == r_val\n    assert z[1] == -r_val\n\n\n@pytest.mark.parametrize(\"geometry\", (\"geographic\", \"internal_geographic\"))\ndef test_geographic_conversions_with_units(geometry):\n    ds = fake_amr_ds(geometry=geometry)\n\n    # _sanitize_center will give all values in 'code_length'\n    coords = ds.arr(np.zeros((2, 3)), \"code_length\")\n    xyz_u = ds.coordinates.convert_to_cartesian(coords)\n    xyz = ds.coordinates.convert_to_cartesian(coords.d)\n    assert_equal(xyz, xyz_u)\n\n    coords = ds.arr(np.zeros((3,)), \"code_length\")\n    xyz_u = ds.coordinates.convert_to_cartesian(coords)\n    xyz = ds.coordinates.convert_to_cartesian(coords.d)\n    assert_equal(xyz, xyz_u)\n\n    # also check that if correct units are supplied, the\n    # result has dimensions of length.\n    coords = [\n        ds.arr(np.zeros((10,)), \"degree\"),\n        ds.arr(np.zeros((10,)), \"degree\"),\n        ds.arr(np.linspace(0, 100, 10), \"code_length\"),\n    ]\n    xyz = ds.coordinates.convert_to_cartesian(coords)\n    for dim in xyz:\n        assert dim.units.dimensions == unyt.dimensions.length\n"
  },
  {
    "path": "yt/geometry/coordinates/tests/test_polar_coordinates.py",
    "content": "# Some tests for the polar coordinates handler\n# (Pretty similar to cylindrical, but different ordering)\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.testing import fake_amr_ds\n\n# Our canonical tests are that we can access all of our fields and we can\n# compute our volume correctly.\n\n\ndef test_cylindrical_coordinates():\n    # We're going to load up a simple AMR grid and check its volume\n    # calculations and path length calculations.\n    ds = fake_amr_ds(geometry=\"polar\")\n    axes = [\"r\", \"theta\", \"z\"]\n    for i, axis in enumerate(axes):\n        dd = ds.all_data()\n        fi = (\"index\", axis)\n        fd = (\"index\", f\"d{axis}\")\n        ma = np.argmax(dd[fi])\n        assert_equal(dd[fi][ma] + dd[fd][ma] / 2.0, ds.domain_right_edge[i].d)\n        mi = np.argmin(dd[fi])\n        assert_equal(dd[fi][mi] - dd[fd][mi] / 2.0, ds.domain_left_edge[i].d)\n        assert_equal(dd[fd].max(), (ds.domain_width / ds.domain_dimensions)[i].d)\n    assert_almost_equal(\n        dd[\"index\", \"cell_volume\"].sum(dtype=\"float64\"),\n        np.pi * ds.domain_width[0] ** 2 * ds.domain_width[2],\n    )\n    assert_equal(dd[\"index\", \"path_element_r\"], dd[\"index\", \"dr\"])\n    assert_equal(dd[\"index\", \"path_element_z\"], dd[\"index\", \"dz\"])\n    assert_equal(\n        dd[\"index\", \"path_element_theta\"], dd[\"index\", \"r\"] * dd[\"index\", \"dtheta\"]\n    )\n"
  },
  {
    "path": "yt/geometry/coordinates/tests/test_sanitize_center.py",
    "content": "import re\n\nimport pytest\nfrom unyt import unyt_array\nfrom unyt.exceptions import UnitConversionError\n\nfrom yt.testing import fake_amr_ds\n\n\n@pytest.fixture(scope=\"module\")\ndef reusable_fake_dataset():\n    ds = fake_amr_ds(\n        fields=[(\"gas\", \"density\")],\n        units=[\"g/cm**3\"],\n    )\n    return ds\n\n\nvalid_single_str_values = (\"center\",)\nvalid_field_loc_str_values = (\"min\", \"max\")\n\nDEFAUT_ERROR_MESSAGE = (\n    \"Expected any of the following\\n\"\n    \"- 'c', 'center', 'l', 'left', 'r', 'right', 'm', 'max', or 'min'\\n\"\n    \"- a 2 element tuple with 'min' or 'max' as the first element, followed by a field identifier\\n\"\n    \"- a 3 element array-like: for a unyt_array, expects length dimensions, otherwise code_length is assumed\"\n)\n\n\n@pytest.mark.parametrize(\n    \"user_input\",\n    (\n        # second element can be a single str or a field tuple (2 str), but not three\n        ((\"max\", (\"not\", \"a\", \"field\"))),\n        # a 1-tuple is also not a valid field key\n        ((\"max\", (\"notafield\",))),\n        # both elements need to be str\n        ((\"max\", (0, \"invalid_field_type\"))),\n        ((\"max\", (\"invalid_field_type\", 1))),\n    ),\n)\ndef test_invalid_center_type_default_error(reusable_fake_dataset, user_input):\n    ds = reusable_fake_dataset\n    with pytest.raises(\n        TypeError,\n        match=re.escape(f\"Received {user_input!r}, \")\n        + r\"but failed to transform to a unyt_array \\(obtained .+\\)\\.\",\n    ):\n        # at the time of writing `axis` is an unused parameter of the base\n        # sanitize center method, which is used directly for cartesian coordinate handlers\n        # this probably hints that a refactor would make sense to separaet center sanitizing\n        # and display_center calculation\n        ds.coordinates.sanitize_center(user_input, axis=None)\n\n\n@pytest.mark.parametrize(\n    \"user_input, error_type, error_message\",\n    (\n        (\n            \"bad_str\",\n            ValueError,\n            re.escape(\n                \"Received unknown center single string value 'bad_str'. \"\n                + DEFAUT_ERROR_MESSAGE\n            ),\n        ),\n        (\n            (\"bad_str\", (\"gas\", \"density\")),\n            ValueError,\n            re.escape(\n                \"Received unknown string value 'bad_str'. \"\n                f\"Expected one of {valid_field_loc_str_values} (case insensitive)\"\n            ),\n        ),\n        (\n            (\"bad_str\", \"density\"),\n            ValueError,\n            re.escape(\n                \"Received unknown string value 'bad_str'. \"\n                \"Expected one of ('min', 'max') (case insensitive)\"\n            ),\n        ),\n        # even with exactly three elements, the dimension should be length\n        (\n            unyt_array([0.5] * 3, \"kg\"),\n            UnitConversionError,\n            \"...\",  # don't match the exact error message since it's unyt's responsibility\n        ),\n        # only validate 3 elements unyt_arrays\n        (\n            unyt_array([0.5] * 2, \"cm\"),\n            TypeError,\n            re.escape(\"Received unyt_array([0.5, 0.5], 'cm')\"),\n        ),\n        (\n            unyt_array([0.5] * 4, \"cm\"),\n            TypeError,\n            # don't attempt to match error message as details of how\n            # a unyt array with more than a couple elements is displayed are out of our control\n            \"...\",\n        ),\n        (\n            # check that the whole shape is used in validation, not just the length (number of rows)\n            unyt_array([0.5] * 6, \"cm\").reshape(3, 2),\n            TypeError,\n            # don't attempt to match error message as details of how\n            # a unyt array with more than a couple elements is displayed are out of our control\n            \"...\",\n        ),\n    ),\n)\ndef test_invalid_center_special_cases(\n    reusable_fake_dataset, user_input, error_type, error_message\n):\n    ds = reusable_fake_dataset\n    with pytest.raises(error_type, match=error_message):\n        ds.coordinates.sanitize_center(user_input, axis=None)\n"
  },
  {
    "path": "yt/geometry/coordinates/tests/test_sph_pixelization.py",
    "content": "import numpy as np\n\nimport yt\nfrom yt.testing import (\n    assert_rel_equal,\n    cubicspline_python,\n    fake_sph_flexible_grid_ds,\n    integrate_kernel,\n    requires_file,\n)\nfrom yt.utilities.math_utils import compute_stddev_image\n\n## off-axis projection tests for SPH data are in\n## yt/visualization/tests/test_offaxisprojection.py\n\n\nmagneticum = \"MagneticumCluster/snap_132\"\n\nmag_kwargs = {\n    \"long_ids\": True,\n    \"field_spec\": \"magneticum_box2_hr\",\n}\n\n\n@requires_file(magneticum)\ndef test_sph_moment():\n    ds = yt.load(magneticum, **mag_kwargs)\n\n    def _vysq(data):\n        return data[\"gas\", \"velocity_y\"] ** 2\n\n    ds.add_field((\"gas\", \"vysq\"), _vysq, sampling_type=\"local\", units=\"cm**2/s**2\")\n    prj1 = yt.ProjectionPlot(\n        ds,\n        \"y\",\n        [(\"gas\", \"velocity_y\"), (\"gas\", \"vysq\")],\n        weight_field=(\"gas\", \"density\"),\n        moment=1,\n        buff_size=(400, 400),\n    )\n    prj2 = yt.ProjectionPlot(\n        ds,\n        \"y\",\n        (\"gas\", \"velocity_y\"),\n        moment=2,\n        weight_field=(\"gas\", \"density\"),\n        buff_size=(400, 400),\n    )\n    sigy = compute_stddev_image(prj1.frb[\"gas\", \"vysq\"], prj1.frb[\"gas\", \"velocity_y\"])\n    assert_rel_equal(sigy, prj2.frb[\"gas\", \"velocity_y\"].d, 10)\n\n\ndef test_sph_projection_basic1():\n    \"\"\"\n    small, uniform grid: expected values for given dl?\n    pixel centers at 0.5, 1., 1.5, 2., 2.5\n    particles at 0.5, 1.5, 2.5\n    \"\"\"\n    bbox = np.array([[0.0, 3.0]] * 3)\n    ds = fake_sph_flexible_grid_ds(hsml_factor=1.0, nperside=3, bbox=bbox)\n    # works, but no depth control (at least without specific filters)\n    proj = ds.proj((\"gas\", \"density\"), 2)\n    frb = proj.to_frb(\n        width=(2.5, \"cm\"),\n        resolution=(5, 5),\n        height=(2.5, \"cm\"),\n        center=np.array([1.5, 1.5, 1.5]),\n        periodic=False,\n    )\n    out = frb.get_image((\"gas\", \"density\"))\n\n    expected_out = np.zeros((5, 5), dtype=np.float64)\n    dl_1part = integrate_kernel(cubicspline_python, 0.0, 0.5)\n    linedens_1part = dl_1part * 1.0  # unit mass, density\n    linedens = 3.0 * linedens_1part\n    expected_out[::2, ::2] = linedens\n\n    assert_rel_equal(expected_out, out.v, 5)\n    # return out\n\n\ndef test_sph_projection_basic2():\n    \"\"\"\n    small, uniform grid: expected values for given dl?\n    pixel centers at 0.5, 1., 1.5, 2., 2.5\n    particles at 0.5, 1.5, 2.5\n    but hsml radii are 0.25 -> try non-zero impact parameters,\n    other pixels are still zero.\n    \"\"\"\n    bbox = np.array([[0.0, 3.0]] * 3)\n    ds = fake_sph_flexible_grid_ds(hsml_factor=0.5, nperside=3, bbox=bbox)\n    proj = ds.proj((\"gas\", \"density\"), 2)\n    frb = proj.to_frb(\n        width=(2.5, \"cm\"),\n        resolution=(5, 5),\n        height=(2.5, \"cm\"),\n        center=np.array([1.375, 1.375, 1.5]),\n        periodic=False,\n    )\n    out = frb.get_image((\"gas\", \"density\"))\n\n    expected_out = np.zeros((5, 5), dtype=np.float64)\n    dl_1part = integrate_kernel(cubicspline_python, np.sqrt(2) * 0.125, 0.25)\n    linedens_1part = dl_1part * 1.0  # unit mass, density\n    linedens = 3.0 * linedens_1part\n    expected_out[::2, ::2] = linedens\n\n    # print(expected_out)\n    # print(out.v)\n    assert_rel_equal(expected_out, out.v, 4)\n    # return out\n\n\ndef get_dataset_sphrefine(reflevel: int = 1):\n    \"\"\"\n    constant density particle grid,\n    with increasing particle sampling\n    \"\"\"\n    lenfact = (1.0 / 3.0) ** (reflevel - 1)\n    massfact = lenfact**3\n    nperside = 3**reflevel\n\n    e1hat = np.array([lenfact, 0, 0])\n    e2hat = np.array([0, lenfact, 0])\n    e3hat = np.array([0, 0, lenfact])\n    hsml_factor = lenfact\n    bbox = np.array([[0.0, 3.0]] * 3)\n    offsets = np.ones(3, dtype=np.float64) * 0.5  # in units of ehat\n\n    def refmass(i: int, j: int, k: int) -> float:\n        return massfact\n\n    unitrho = 1.0 / massfact  # want density 1 for decreasing mass\n\n    ds = fake_sph_flexible_grid_ds(\n        hsml_factor=hsml_factor,\n        nperside=nperside,\n        periodic=True,\n        e1hat=e1hat,\n        e2hat=e2hat,\n        e3hat=e3hat,\n        offsets=offsets,\n        massgenerator=refmass,\n        unitrho=unitrho,\n        bbox=bbox,\n    )\n    return ds\n\n\ndef getdata_test_gridproj2():\n    # initial pixel centers at 0.5, 1., 1.5, 2., 2.5\n    # particles at 0.5, 1.5, 2.5\n    # refine particle grid, check if pixel values remain the\n    # same in the pixels passing through initial particle centers\n    outlist = []\n    dss = []\n    for rl in range(1, 4):\n        ds = get_dataset_sphrefine(reflevel=rl)\n        proj = ds.proj((\"gas\", \"density\"), 2)\n        frb = proj.to_frb(\n            width=(2.5, \"cm\"),\n            resolution=(5, 5),\n            height=(2.5, \"cm\"),\n            center=np.array([1.5, 1.5, 1.5]),\n            periodic=False,\n        )\n        out = frb.get_image((\"gas\", \"density\"))\n        outlist.append(out)\n        dss.append(ds)\n    return outlist, dss\n\n\ndef test_sph_gridproj_reseffect1():\n    \"\"\"\n    Comparing same pixel centers with higher particle resolution.\n    The pixel centers are at x/y coordinates [0.5, 1., 1.5, 2., 2.5]\n    at the first level, the spacing halves at each level.\n    Checking the pixels at [0.5, 1.5, 2.5],\n    which should have the same values at each resolution.\n    \"\"\"\n    imgs, _ = getdata_test_gridproj2()\n    ref = imgs[-1]\n    for img in imgs:\n        assert_rel_equal(\n            img[:: img.shape[0] // 2, :: img.shape[1] // 2],\n            ref[:: ref.shape[0] // 2, :: ref.shape[1] // 2],\n            4,\n        )\n\n\ndef test_sph_gridproj_reseffect2():\n    \"\"\"\n    refine the pixel grid instead of the particle grid\n    \"\"\"\n    ds = get_dataset_sphrefine(reflevel=2)\n    proj = ds.proj((\"gas\", \"density\"), 2)\n    imgs = {}\n    maxrl = 5\n    for rl in range(1, maxrl + 1):\n        npix = 1 + 2 ** (rl + 1)\n        margin = 0.5 - 0.5 ** (rl + 1)\n        frb = proj.to_frb(\n            width=(3.0 - 2.0 * margin, \"cm\"),\n            resolution=(npix, npix),\n            height=(3.0 - 2.0 * margin, \"cm\"),\n            center=np.array([1.5, 1.5, 1.5]),\n            periodic=False,\n        )\n        out = frb.get_image((\"gas\", \"density\"))\n        imgs[rl] = out\n    ref = imgs[maxrl]\n    pixspace_ref = 2 ** (maxrl)\n    for rl in imgs:\n        img = imgs[rl]\n        pixspace = 2 ** (rl)\n        # print(f'Grid refinement level {rl}:')\n        assert_rel_equal(\n            img[::pixspace, ::pixspace], ref[::pixspace_ref, ::pixspace_ref], 4\n        )\n"
  },
  {
    "path": "yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py",
    "content": "import numpy as np\nimport pytest\nimport unyt\n\nimport yt\nfrom yt.data_objects.selection_objects.region import YTRegion\nfrom yt.testing import (\n    assert_rel_equal,\n    cubicspline_python,\n    distancematrix,\n    fake_random_sph_ds,\n    fake_sph_flexible_grid_ds,\n    integrate_kernel,\n)\n\n\n@pytest.mark.parametrize(\"weighted\", [True, False])\n@pytest.mark.parametrize(\"periodic\", [True, False])\n@pytest.mark.parametrize(\"depth\", [None, (1.0, \"cm\")])\n@pytest.mark.parametrize(\"shiftcenter\", [False, True])\n@pytest.mark.parametrize(\"axis\", [0, 1, 2])\ndef test_sph_proj_general_alongaxes(\n    axis: int,\n    shiftcenter: bool,\n    depth: float | None,\n    periodic: bool,\n    weighted: bool,\n) -> None:\n    \"\"\"\n    The previous projection tests were for a specific issue.\n    Here, we test more functionality of the projections.\n    We just send lines of sight through pixel centers for convenience.\n    Particles at [0.5, 1.5, 2.5] (in each coordinate)\n    smoothing lengths 0.25\n    all particles have mass 1., density 1.5,\n    except the single center particle, with mass 2., density 3.\n\n    Parameters:\n    -----------\n    axis: {0, 1, 2}\n        projection axis (aligned with sim. axis)\n    shiftcenter: bool\n        shift the coordinates to center the projection on.\n        (The grid is offset to this same center)\n    depth: float or None\n        depth of the projection slice\n    periodic: bool\n        assume periodic boundary conditions, or not\n    weighted: bool\n        make a weighted projection (density-weighted density), or not\n\n    Returns:\n    --------\n    None\n    \"\"\"\n    if shiftcenter:\n        center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), \"cm\")\n    else:\n        center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), \"cm\")\n    bbox = unyt.unyt_array(np.array([[0.0, 3.0], [0.0, 3.0], [0.0, 3.0]]), \"cm\")\n    hsml_factor = 0.5\n    unitrho = 1.5\n\n    # test correct centering, particle selection\n    def makemasses(i, j, k):\n        if i == j == k == 1:\n            return 2.0\n        else:\n            return 1.0\n\n    # m / rho, factor 1. / hsml**2 is included in the kernel integral\n    # (density is adjusted, so same for center particle)\n    prefactor = 1.0 / unitrho  # / (0.5 * 0.5)**2\n    dl_cen = integrate_kernel(cubicspline_python, 0.0, 0.25)\n\n    # result shouldn't depend explicitly on the center if we re-center\n    # the data, unless we get cut-offs in the non-periodic case\n    ds = fake_sph_flexible_grid_ds(\n        hsml_factor=hsml_factor,\n        nperside=3,\n        periodic=periodic,\n        offsets=np.full(3, 0.5),\n        massgenerator=makemasses,\n        unitrho=unitrho,\n        bbox=bbox.v,\n        recenter=center.v,\n    )\n    if depth is None:\n        source = ds.all_data()\n    else:\n        depth = unyt.unyt_quantity(*depth)\n        le = np.array(ds.domain_left_edge)\n        re = np.array(ds.domain_right_edge)\n        le[axis] = center[axis] - 0.5 * depth\n        re[axis] = center[axis] + 0.5 * depth\n        cen = 0.5 * (le + re)\n        reg = YTRegion(center=cen, left_edge=le, right_edge=re, ds=ds)\n        source = reg\n\n    # we don't actually want a plot, it's just a straightforward,\n    # common way to get an frb / image array\n    if weighted:\n        toweight_field = (\"gas\", \"density\")\n    else:\n        toweight_field = None\n    prj = yt.ProjectionPlot(\n        ds,\n        axis,\n        (\"gas\", \"density\"),\n        width=(2.5, \"cm\"),\n        weight_field=toweight_field,\n        buff_size=(5, 5),\n        center=center,\n        data_source=source,\n    )\n    img = prj.frb.data[\"gas\", \"density\"]\n    if weighted:\n        expected_out = np.zeros(\n            (\n                5,\n                5,\n            ),\n            dtype=img.v.dtype,\n        )\n        expected_out[::2, ::2] = unitrho\n        if depth is None:\n            ## during shift, particle coords do wrap around edges\n            # if (not periodic) and shiftcenter:\n            #    # weight 1. for unitrho, 2. for 2. * untrho\n            #    expected_out[2, 2] *= 5. / 3.\n            # else:\n            # weight (2 * 1.) for unitrho, (1 * 2.) for 2. * unitrho\n            expected_out[2, 2] *= 1.5\n        else:\n            # only 2 * unitrho element included\n            expected_out[2, 2] *= 2.0\n    else:\n        expected_out = np.zeros(\n            (\n                5,\n                5,\n            ),\n            dtype=img.v.dtype,\n        )\n        expected_out[::2, ::2] = dl_cen * prefactor * unitrho\n        if depth is None:\n            # 3 particles per l.o.s., including the denser one\n            expected_out *= 3.0\n            expected_out[2, 2] *= 4.0 / 3.0\n        else:\n            # 1 particle per l.o.s., including the denser one\n            expected_out[2, 2] *= 2.0\n    # grid is shifted to the left -> 'missing' stuff at the left\n    if (not periodic) and shiftcenter:\n        expected_out[:1, :] = 0.0\n        expected_out[:, :1] = 0.0\n    # print(axis, shiftcenter, depth, periodic, weighted)\n    # print(expected_out)\n    # print(img.v)\n    assert_rel_equal(expected_out, img.v, 5)\n\n\n@pytest.mark.parametrize(\"periodic\", [True, False])\n@pytest.mark.parametrize(\"shiftcenter\", [False, True])\n@pytest.mark.parametrize(\"zoff\", [0.0, 0.1, 0.5, 1.0])\n@pytest.mark.parametrize(\"axis\", [0, 1, 2])\ndef test_sph_slice_general_alongaxes(\n    axis: int,\n    shiftcenter: bool,\n    periodic: bool,\n    zoff: float,\n) -> None:\n    \"\"\"\n    Particles at [0.5, 1.5, 2.5] (in each coordinate)\n    smoothing lengths 0.25\n    all particles have mass 1., density 1.5,\n    except the single center particle, with mass 2., density 3.\n\n    Parameters:\n    -----------\n    axis: {0, 1, 2}\n        projection axis (aligned with sim. axis)\n    northvector: tuple\n        y-axis direction in the final plot (direction vector)\n    shiftcenter: bool\n        shift the coordinates to center the projection on.\n        (The grid is offset to this same center)\n    zoff: float\n        offset of the slice plane from the SPH particle center plane\n    periodic: bool\n        assume periodic boundary conditions, or not\n\n    Returns:\n    --------\n    None\n    \"\"\"\n    if shiftcenter:\n        center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), \"cm\")\n    else:\n        center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), \"cm\")\n    bbox = unyt.unyt_array(np.array([[0.0, 3.0], [0.0, 3.0], [0.0, 3.0]]), \"cm\")\n    hsml_factor = 0.5\n    unitrho = 1.5\n\n    # test correct centering, particle selection\n    def makemasses(i, j, k):\n        if i == j == k == 1:\n            return 2.0\n        elif i == j == k == 2:\n            return 3.0\n        else:\n            return 1.0\n\n    # result shouldn't depend explicitly on the center if we re-center\n    # the data, unless we get cut-offs in the non-periodic case\n    ds = fake_sph_flexible_grid_ds(\n        hsml_factor=hsml_factor,\n        nperside=3,\n        periodic=periodic,\n        offsets=np.full(3, 0.5),\n        massgenerator=makemasses,\n        unitrho=unitrho,\n        bbox=bbox.v,\n        recenter=center.v,\n    )\n    ad = ds.all_data()\n    # print(ad[('gas', 'position')])\n    outgridsize = 10\n    width = 2.5\n    _center = center.to(\"cm\").v.copy()\n    _center[axis] += zoff\n\n    # we don't actually want a plot, it's just a straightforward,\n    # common way to get an frb / image array\n    slc = yt.SlicePlot(\n        ds,\n        axis,\n        (\"gas\", \"density\"),\n        width=(width, \"cm\"),\n        buff_size=(outgridsize,) * 2,\n        center=(_center, \"cm\"),\n    )\n    img = slc.frb.data[\"gas\", \"density\"]\n\n    # center is same in non-projection coords\n    if axis == 0:\n        ci = 1\n    else:\n        ci = 0\n    gridcens = (\n        _center[ci]\n        - 0.5 * width\n        + 0.5 * width / outgridsize\n        + np.arange(outgridsize) * width / outgridsize\n    )\n    xgrid = np.repeat(gridcens, outgridsize)\n    ygrid = np.tile(gridcens, outgridsize)\n    zgrid = np.full(outgridsize**2, _center[axis])\n    gridcoords = np.empty((outgridsize**2, 3), dtype=xgrid.dtype)\n    if axis == 2:\n        gridcoords[:, 0] = xgrid\n        gridcoords[:, 1] = ygrid\n        gridcoords[:, 2] = zgrid\n    elif axis == 0:\n        gridcoords[:, 0] = zgrid\n        gridcoords[:, 1] = xgrid\n        gridcoords[:, 2] = ygrid\n    elif axis == 1:\n        gridcoords[:, 0] = ygrid\n        gridcoords[:, 1] = zgrid\n        gridcoords[:, 2] = xgrid\n    ad = ds.all_data()\n    sphcoords = np.array(\n        [\n            (ad[\"gas\", \"x\"]).to(\"cm\"),\n            (ad[\"gas\", \"y\"]).to(\"cm\"),\n            (ad[\"gas\", \"z\"]).to(\"cm\"),\n        ]\n    ).T\n    # print(\"sphcoords:\")\n    # print(sphcoords)\n    # print(\"gridcoords:\")\n    # print(gridcoords)\n    dists = distancematrix(\n        gridcoords,\n        sphcoords,\n        periodic=(periodic,) * 3,\n        periods=np.array([3.0, 3.0, 3.0]),\n    )\n    # print(\"dists <= 1:\")\n    # print(dists <= 1)\n    sml = (ad[\"gas\", \"smoothing_length\"]).to(\"cm\")\n    normkern = cubicspline_python(dists / sml.v[np.newaxis, :])\n    sphcontr = normkern / sml[np.newaxis, :] ** 3 * ad[\"gas\", \"mass\"]\n    contsum = np.sum(sphcontr, axis=1)\n    sphweights = (\n        normkern / sml[np.newaxis, :] ** 3 * ad[\"gas\", \"mass\"] / ad[\"gas\", \"density\"]\n    )\n    weights = np.sum(sphweights, axis=1)\n    nzeromask = np.logical_not(weights == 0)\n    expected = np.zeros(weights.shape, weights.dtype)\n    expected[nzeromask] = contsum[nzeromask] / weights[nzeromask]\n    expected = expected.reshape((outgridsize, outgridsize))\n    # expected[np.isnan(expected)] = 0.0  # convention in the slices\n\n    # print(\"expected:\\n\", expected)\n    # print(\"recovered:\\n\", img.v)\n    assert_rel_equal(expected, img.v, 5)\n\n\n@pytest.mark.parametrize(\"periodic\", [True, False])\n@pytest.mark.parametrize(\"shiftcenter\", [False, True])\n@pytest.mark.parametrize(\"northvector\", [None, (1.0e-4, 1.0, 0.0)])\n@pytest.mark.parametrize(\"zoff\", [0.0, 0.1, 0.5, 1.0])\ndef test_sph_slice_general_offaxis(\n    northvector: tuple[float, float, float] | None,\n    shiftcenter: bool,\n    zoff: float,\n    periodic: bool,\n) -> None:\n    \"\"\"\n    Same as the on-axis slices, but we rotate the basis vectors\n    to test whether roations are handled ok. the rotation is chosen to\n    be small so that in/exclusion of particles within bboxes, etc.\n    works out the same way.\n    Particles at [0.5, 1.5, 2.5] (in each coordinate)\n    smoothing lengths 0.25\n    all particles have mass 1., density 1.5,\n    except the single center particle, with mass 2., density 3.\n\n    Parameters:\n    -----------\n    northvector: tuple\n        y-axis direction in the final plot (direction vector)\n    shiftcenter: bool\n        shift the coordinates to center the projection on.\n        (The grid is offset to this same center)\n    zoff: float\n        offset of the slice plane from the SPH particle center plane\n    periodic: bool\n        assume periodic boundary conditions, or not\n\n    Returns:\n    --------\n    None\n    \"\"\"\n    if shiftcenter:\n        center = np.array((0.625, 0.625, 0.625))  # cm\n    else:\n        center = np.array((1.5, 1.5, 1.5))  # cm\n    bbox = unyt.unyt_array(np.array([[0.0, 3.0], [0.0, 3.0], [0.0, 3.0]]), \"cm\")\n    hsml_factor = 0.5\n    unitrho = 1.5\n\n    # test correct centering, particle selection\n    def makemasses(i, j, k):\n        if i == j == k == 1:\n            return 2.0\n        else:\n            return 1.0\n\n    # try to make sure dl differences from periodic wrapping are small\n    epsilon = 1e-4\n    projaxis = np.array([epsilon, 0.00, np.sqrt(1.0 - epsilon**2)])\n    e1dir = projaxis / np.sqrt(np.sum(projaxis**2))\n    if northvector is None:\n        e2dir = np.array([0.0, 1.0, 0.0])\n    else:\n        e2dir = np.asarray(northvector)\n    e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir  # orthonormalize\n    e2dir /= np.sqrt(np.sum(e2dir**2))\n    e3dir = np.cross(e2dir, e1dir)\n\n    outgridsize = 10\n    width = 2.5\n    _center = center.copy()\n    _center += zoff * e1dir\n\n    ds = fake_sph_flexible_grid_ds(\n        hsml_factor=hsml_factor,\n        nperside=3,\n        periodic=periodic,\n        offsets=np.full(3, 0.5),\n        massgenerator=makemasses,\n        unitrho=unitrho,\n        bbox=bbox.v,\n        recenter=center,\n        e1hat=e1dir,\n        e2hat=e2dir,\n        e3hat=e3dir,\n    )\n\n    # source = ds.all_data()\n    # couple to dataset -> right unit registry\n    center = ds.arr(center, \"cm\")\n    # print(\"position:\\n\", source[\"gas\", \"position\"])\n    slc = yt.SlicePlot(\n        ds,\n        e1dir,\n        (\"gas\", \"density\"),\n        width=(width, \"cm\"),\n        buff_size=(outgridsize,) * 2,\n        center=(_center, \"cm\"),\n        north_vector=e2dir,\n    )\n    img = slc.frb.data[\"gas\", \"density\"]\n\n    # center is same in x/y (e3dir/e2dir)\n    gridcenx = (\n        np.dot(_center, e3dir)\n        - 0.5 * width\n        + 0.5 * width / outgridsize\n        + np.arange(outgridsize) * width / outgridsize\n    )\n    gridceny = (\n        np.dot(_center, e2dir)\n        - 0.5 * width\n        + 0.5 * width / outgridsize\n        + np.arange(outgridsize) * width / outgridsize\n    )\n    xgrid = np.repeat(gridcenx, outgridsize)\n    ygrid = np.tile(gridceny, outgridsize)\n    zgrid = np.full(outgridsize**2, np.dot(_center, e1dir))\n    gridcoords = (\n        xgrid[:, np.newaxis] * e3dir[np.newaxis, :]\n        + ygrid[:, np.newaxis] * e2dir[np.newaxis, :]\n        + zgrid[:, np.newaxis] * e1dir[np.newaxis, :]\n    )\n    # print(\"gridcoords:\")\n    # print(gridcoords)\n    ad = ds.all_data()\n    sphcoords = np.array(\n        [\n            (ad[\"gas\", \"x\"]).to(\"cm\"),\n            (ad[\"gas\", \"y\"]).to(\"cm\"),\n            (ad[\"gas\", \"z\"]).to(\"cm\"),\n        ]\n    ).T\n    dists = distancematrix(\n        gridcoords,\n        sphcoords,\n        periodic=(periodic,) * 3,\n        periods=np.array([3.0, 3.0, 3.0]),\n    )\n    sml = (ad[\"gas\", \"smoothing_length\"]).to(\"cm\")\n    normkern = cubicspline_python(dists / sml.v[np.newaxis, :])\n    sphcontr = normkern / sml[np.newaxis, :] ** 3 * ad[\"gas\", \"mass\"]\n    contsum = np.sum(sphcontr, axis=1)\n    sphweights = (\n        normkern / sml[np.newaxis, :] ** 3 * ad[\"gas\", \"mass\"] / ad[\"gas\", \"density\"]\n    )\n    weights = np.sum(sphweights, axis=1)\n    nzeromask = np.logical_not(weights == 0)\n    expected = np.zeros(weights.shape, weights.dtype)\n    expected[nzeromask] = contsum[nzeromask] / weights[nzeromask]\n    expected = expected.reshape((outgridsize, outgridsize))\n    expected = expected.T  # transposed for image plotting\n    # expected[np.isnan(expected)] = 0.0  # convention in the slices\n\n    # print(axis, shiftcenter, depth, periodic, weighted)\n    # print(\"expected:\\n\", expected)\n    # print(\"recovered:\\n\", img.v)\n    assert_rel_equal(expected, img.v, 4)\n\n\n# only axis-aligned; testing YTArbitraryGrid, YTCoveringGrid\n@pytest.mark.parametrize(\"periodic\", [True, False, (True, True, False)])\n@pytest.mark.parametrize(\"wholebox\", [True, False])\ndef test_sph_grid(\n    periodic: bool | tuple[bool, bool, bool],\n    wholebox: bool,\n) -> None:\n    bbox = np.array([[-1.0, 3.0], [1.0, 5.2], [-1.0, 3.0]])\n    ds = fake_random_sph_ds(50, bbox, periodic=periodic)\n\n    if not hasattr(periodic, \"__len__\"):\n        periodic = (periodic,) * 3\n\n    if wholebox:\n        left = bbox[:, 0].copy()\n        level = 2\n        ncells = np.array([2**level] * 3)\n        # print(\"left: \", left)\n        # print(\"ncells: \", ncells)\n        resgrid = ds.covering_grid(level, tuple(left), ncells)\n        right = bbox[:, 1].copy()\n        xedges = np.linspace(left[0], right[0], ncells[0] + 1)\n        yedges = np.linspace(left[1], right[1], ncells[1] + 1)\n        zedges = np.linspace(left[2], right[2], ncells[2] + 1)\n    else:\n        left = np.array([-1.0, 1.8, -1.0])\n        right = np.array([2.5, 5.2, 2.5])\n        ncells = np.array([3, 4, 4])\n        resgrid = ds.arbitrary_grid(left, right, dims=ncells)\n        xedges = np.linspace(left[0], right[0], ncells[0] + 1)\n        yedges = np.linspace(left[1], right[1], ncells[1] + 1)\n        zedges = np.linspace(left[2], right[2], ncells[2] + 1)\n    res = resgrid[\"gas\", \"density\"]\n    xcens = 0.5 * (xedges[:-1] + xedges[1:])\n    ycens = 0.5 * (yedges[:-1] + yedges[1:])\n    zcens = 0.5 * (zedges[:-1] + zedges[1:])\n\n    ad = ds.all_data()\n    sphcoords = np.array(\n        [\n            (ad[\"gas\", \"x\"]).to(\"cm\"),\n            (ad[\"gas\", \"y\"]).to(\"cm\"),\n            (ad[\"gas\", \"z\"]).to(\"cm\"),\n        ]\n    ).T\n    gridx, gridy, gridz = np.meshgrid(xcens, ycens, zcens, indexing=\"ij\")\n    outshape = gridx.shape\n    gridx = gridx.flatten()\n    gridy = gridy.flatten()\n    gridz = gridz.flatten()\n    gridcoords = np.array([gridx, gridy, gridz]).T\n    periods = bbox[:, 1] - bbox[:, 0]\n    dists = distancematrix(gridcoords, sphcoords, periodic=periodic, periods=periods)\n    sml = (ad[\"gas\", \"smoothing_length\"]).to(\"cm\")\n    normkern = cubicspline_python(dists / sml.v[np.newaxis, :])\n    sphcontr = normkern / sml[np.newaxis, :] ** 3 * ad[\"gas\", \"mass\"]\n    contsum = np.sum(sphcontr, axis=1)\n    sphweights = (\n        normkern / sml[np.newaxis, :] ** 3 * ad[\"gas\", \"mass\"] / ad[\"gas\", \"density\"]\n    )\n    weights = np.sum(sphweights, axis=1)\n    nzeromask = np.logical_not(weights == 0)\n    expected = np.zeros(weights.shape, weights.dtype)\n    expected[nzeromask] = contsum[nzeromask] / weights[nzeromask]\n    expected = expected.reshape(outshape)\n    # expected[np.isnan(expected)] = 0.0  # convention in the slices\n\n    # print(axis, shiftcenter, depth, periodic, weighted)\n    # print(\"expected:\\n\", expected)\n    # print(\"recovered:\\n\", res.v)\n    assert_rel_equal(expected, res.v, 4)\n"
  },
  {
    "path": "yt/geometry/coordinates/tests/test_spherical_coordinates.py",
    "content": "# Some tests for the Spherical coordinates handler\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.testing import fake_amr_ds\n\n# Our canonical tests are that we can access all of our fields and we can\n# compute our volume correctly.\n\n\ndef test_spherical_coordinates():\n    # We're going to load up a simple AMR grid and check its volume\n    # calculations and path length calculations.\n    ds = fake_amr_ds(geometry=\"spherical\")\n    axes = [\"r\", \"theta\", \"phi\"]\n    for i, axis in enumerate(axes):\n        dd = ds.all_data()\n        fi = (\"index\", axis)\n        fd = (\"index\", f\"d{axis}\")\n        ma = np.argmax(dd[fi])\n        assert_equal(dd[fi][ma] + dd[fd][ma] / 2.0, ds.domain_right_edge[i].d)\n        mi = np.argmin(dd[fi])\n        assert_equal(dd[fi][mi] - dd[fd][mi] / 2.0, ds.domain_left_edge[i].d)\n        assert_equal(dd[fd].max(), (ds.domain_width / ds.domain_dimensions)[i].d)\n    # Note that we're using a lot of funny transforms to get to this, so we do\n    # not expect to get actual agreement.  This is a bit of a shame, but I\n    # don't think it is avoidable as of right now.  Real datasets will almost\n    # certainly be correct, if this is correct to 3 decimel places.\n    assert_almost_equal(\n        dd[\"index\", \"cell_volume\"].sum(dtype=\"float64\"),\n        (4.0 / 3.0) * np.pi * ds.domain_width[0] ** 3,\n    )\n    assert_equal(dd[\"index\", \"path_element_r\"], dd[\"index\", \"dr\"])\n    assert_equal(\n        dd[\"index\", \"path_element_theta\"], dd[\"index\", \"r\"] * dd[\"index\", \"dtheta\"]\n    )\n    assert_equal(\n        dd[\"index\", \"path_element_phi\"],\n        (dd[\"index\", \"r\"] * dd[\"index\", \"dphi\"] * np.sin(dd[\"index\", \"theta\"])),\n    )\n"
  },
  {
    "path": "yt/geometry/fake_octree.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\n\"\"\"\nMake a fake octree, deposit particle at every leaf\n\n\n\n\n\"\"\"\n\n\ncimport numpy as np\nfrom libc.stdlib cimport RAND_MAX, rand\n\nfrom .oct_visitors cimport cind\n\nimport numpy as np\n\nfrom .oct_container cimport Oct, SparseOctreeContainer\n\n\n# Create a balanced octree by a random walk that recursively\n# subdivides\ndef create_fake_octree(SparseOctreeContainer oct_handler,\n                       long max_noct,\n                       long max_level,\n                       np.ndarray[np.int32_t, ndim=1] ndd,\n                       np.ndarray[np.float64_t, ndim=1] dle,\n                       np.ndarray[np.float64_t, ndim=1] dre,\n                       float fsubdivide):\n    cdef int[3] dd #hold the octant index\n    cdef int[3] ind #hold the octant index\n    cdef long i\n    cdef long cur_leaf = 0\n    cdef np.ndarray[np.uint8_t, ndim=2] mask\n    for i in range(3):\n        ind[i] = 0\n        dd[i] = ndd[i]\n    oct_handler.allocate_domains([max_noct])\n    parent = oct_handler.next_root(1, ind)\n    parent.domain = 1\n    cur_leaf = 8 #we've added one parent...\n    mask = np.ones((max_noct,8),dtype='uint8')\n    while oct_handler.domains[0].n_assigned < max_noct:\n        print(\"root: nocts \", oct_handler.domains[0].n_assigned)\n        cur_leaf = subdivide(oct_handler, parent, ind, dd, cur_leaf, 0,\n                             max_noct, max_level, fsubdivide, mask)\n    return cur_leaf\n\n\ncdef long subdivide(SparseOctreeContainer oct_handler,\n                    Oct *parent,\n                    int ind[3], int dd[3],\n                    long cur_leaf, long cur_level,\n                    long max_noct, long max_level, float fsubdivide,\n                    np.ndarray[np.uint8_t, ndim=2] mask):\n    print(\"child\", parent.file_ind, ind[0], ind[1], ind[2], cur_leaf, cur_level)\n    cdef int ddr[3]\n    cdef int ii\n    cdef long i\n    cdef float rf #random float from 0-1\n    if cur_level >= max_level:\n        return cur_leaf\n    if oct_handler.domains[0].n_assigned >= max_noct:\n        return cur_leaf\n    for i in range(3):\n        ind[i] = <int> ((rand() * 1.0 / RAND_MAX) * dd[i])\n        ddr[i] = 2\n    rf = rand() * 1.0 / RAND_MAX\n    if rf > fsubdivide:\n        ii = cind(ind[0], ind[1], ind[2])\n        if parent.children[ii] == NULL:\n            cur_leaf += 7\n        oct = oct_handler.next_child(1, ind, parent)\n        oct.domain = 1\n        cur_leaf = subdivide(oct_handler, oct, ind, ddr, cur_leaf,\n                             cur_level + 1, max_noct, max_level,\n                             fsubdivide, mask)\n    return cur_leaf\n"
  },
  {
    "path": "yt/geometry/geometry_enum.py",
    "content": "import sys\nfrom enum import auto\n\nif sys.version_info >= (3, 11):\n    from enum import StrEnum\nelse:\n    from yt._maintenance.backports import StrEnum\n\n\n# register all valid geometries\nclass Geometry(StrEnum):\n    CARTESIAN = auto()\n    CYLINDRICAL = auto()\n    POLAR = auto()\n    SPHERICAL = auto()\n    GEOGRAPHIC = auto()\n    INTERNAL_GEOGRAPHIC = auto()\n    SPECTRAL_CUBE = auto()\n\n    def __str__(self):\n        # Implemented for backward compatibility.\n        if sys.version_info >= (3, 11):\n            return super().__str__()\n        else:\n            return self.name.lower()\n"
  },
  {
    "path": "yt/geometry/geometry_handler.py",
    "content": "import abc\nimport os\nimport weakref\n\nimport numpy as np\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt.config import ytcfg\nfrom yt.units._numpy_wrapper_functions import uconcatenate\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.exceptions import YTFieldNotFound\nfrom yt.utilities.io_handler import io_registry\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    ParallelAnalysisInterface,\n    parallel_root_only,\n)\n\n\nclass Index(ParallelAnalysisInterface, abc.ABC):\n    \"\"\"The base index class\"\"\"\n\n    _unsupported_objects: tuple[str, ...] = ()\n    _index_properties: tuple[str, ...] = ()\n\n    def __init__(self, ds, dataset_type):\n        ParallelAnalysisInterface.__init__(self)\n        self.dataset = weakref.proxy(ds)\n        self.ds = self.dataset\n\n        self._initialize_state_variables()\n\n        mylog.debug(\"Initializing data storage.\")\n        self._initialize_data_storage()\n\n        mylog.debug(\"Setting up domain geometry.\")\n        self._setup_geometry()\n\n        mylog.debug(\"Initializing data grid data IO\")\n        self._setup_data_io()\n\n        # Note that this falls under the \"geometry\" object since it's\n        # potentially quite expensive, and should be done with the indexing.\n        mylog.debug(\"Detecting fields.\")\n        self._detect_output_fields()\n\n    @abc.abstractmethod\n    def _detect_output_fields(self):\n        pass\n\n    def _icoords_to_fcoords(\n        self,\n        icoords: np.ndarray,\n        ires: np.ndarray,\n        axes: tuple[int, ...] | None = None,\n    ) -> tuple[np.ndarray, np.ndarray]:\n        # What's the use of raising NotImplementedError for this, when it's an\n        # abstract base class?  Well, only *some* of the subclasses have it --\n        # and for those that *don't*, we should not be calling it -- and since\n        # it's a semi-private method, it shouldn't be called outside of yt\n        # machinery.  So we shouldn't ever get here!\n        raise NotImplementedError\n\n    def _initialize_state_variables(self):\n        self._parallel_locking = False\n        self._data_file = None\n        self._data_mode = None\n        self.num_grids = None\n\n    def _initialize_data_storage(self):\n        if not ytcfg.get(\"yt\", \"serialize\"):\n            return\n        fn = self.ds.storage_filename\n        if fn is None:\n            if os.path.isfile(\n                os.path.join(self.directory, f\"{self.ds.unique_identifier}.yt\")\n            ):\n                fn = os.path.join(self.directory, f\"{self.ds.unique_identifier}.yt\")\n            else:\n                fn = os.path.join(self.directory, f\"{self.dataset.basename}.yt\")\n        dir_to_check = os.path.dirname(fn)\n        if dir_to_check == \"\":\n            dir_to_check = \".\"\n        # We have four options:\n        #    Writeable, does not exist      : create, open as append\n        #    Writeable, does exist          : open as append\n        #    Not writeable, does not exist  : do not attempt to open\n        #    Not writeable, does exist      : open as read-only\n        exists = os.path.isfile(fn)\n        if not exists:\n            writeable = os.access(dir_to_check, os.W_OK)\n        else:\n            writeable = os.access(fn, os.W_OK)\n        writeable = writeable and not ytcfg.get(\"yt\", \"only_deserialize\")\n        # We now have our conditional stuff\n        self.comm.barrier()\n        if not writeable and not exists:\n            return\n        if writeable:\n            try:\n                if not exists:\n                    self.__create_data_file(fn)\n                self._data_mode = \"a\"\n            except OSError:\n                self._data_mode = None\n                return\n        else:\n            self._data_mode = \"r\"\n\n        self.__data_filename = fn\n        self._data_file = h5py.File(fn, mode=self._data_mode)\n\n    def __create_data_file(self, fn):\n        # Note that this used to be parallel_root_only; it no longer is,\n        # because we have better logic to decide who owns the file.\n        f = h5py.File(fn, mode=\"a\")\n        f.close()\n\n    def _setup_data_io(self):\n        if getattr(self, \"io\", None) is not None:\n            return\n        self.io = io_registry[self.dataset_type](self.dataset)\n\n    @parallel_root_only\n    def print_stats(self):\n        raise NotImplementedError(f\"{type(self)} has no print_stats method.\")\n\n    @parallel_root_only\n    def save_data(\n        self, array, node, name, set_attr=None, force=False, passthrough=False\n    ):\n        \"\"\"\n        Arbitrary numpy data will be saved to the region in the datafile\n        described by *node* and *name*.  If data file does not exist, it throws\n        no error and simply does not save.\n        \"\"\"\n\n        if self._data_mode != \"a\":\n            return\n        try:\n            node_loc = self._data_file[node]\n            if name in node_loc and force:\n                mylog.info(\"Overwriting node %s/%s\", node, name)\n                del self._data_file[node][name]\n            elif name in node_loc and passthrough:\n                return\n        except Exception:\n            pass\n        myGroup = self._data_file[\"/\"]\n        for q in node.split(\"/\"):\n            if q:\n                myGroup = myGroup.require_group(q)\n        arr = myGroup.create_dataset(name, data=array)\n        if set_attr is not None:\n            for i, j in set_attr.items():\n                arr.attrs[i] = j\n        self._data_file.flush()\n\n    def _reload_data_file(self, *args, **kwargs):\n        if self._data_file is None:\n            return\n        self._data_file.close()\n        del self._data_file\n        self._data_file = h5py.File(self.__data_filename, mode=self._data_mode)\n\n    def get_data(self, node, name):\n        \"\"\"\n        Return the dataset with a given *name* located at *node* in the\n        datafile.\n        \"\"\"\n        if self._data_file is None:\n            return None\n        if node[0] != \"/\":\n            node = f\"/{node}\"\n\n        myGroup = self._data_file[\"/\"]\n        for group in node.split(\"/\"):\n            if group:\n                if group not in myGroup:\n                    return None\n                myGroup = myGroup[group]\n        if name not in myGroup:\n            return None\n\n        full_name = f\"{node}/{name}\"\n        try:\n            return self._data_file[full_name][:]\n        except TypeError:\n            return self._data_file[full_name]\n\n    def _get_particle_type_counts(self):\n        # this is implemented by subclasses\n        raise NotImplementedError\n\n    def _close_data_file(self):\n        if self._data_file:\n            self._data_file.close()\n            del self._data_file\n            self._data_file = None\n\n    def _split_fields(self, fields):\n        # This will split fields into either generated or read fields\n        fields_to_read, fields_to_generate = [], []\n        for ftype, fname in fields:\n            if fname in self.field_list or (ftype, fname) in self.field_list:\n                fields_to_read.append((ftype, fname))\n            elif (\n                fname in self.ds.derived_field_list\n                or (ftype, fname) in self.ds.derived_field_list\n            ):\n                fields_to_generate.append((ftype, fname))\n            else:\n                raise YTFieldNotFound((ftype, fname), self.ds)\n        return fields_to_read, fields_to_generate\n\n    def _read_particle_fields(self, fields, dobj, chunk=None):\n        if len(fields) == 0:\n            return {}, []\n        fields_to_read, fields_to_generate = self._split_fields(fields)\n        if len(fields_to_read) == 0:\n            return {}, fields_to_generate\n        selector = dobj.selector\n        if chunk is None:\n            self._identify_base_chunk(dobj)\n        chunks = self._chunk_io(dobj, cache=False)\n        fields_to_return = self.io._read_particle_selection(\n            chunks, selector, fields_to_read\n        )\n        return fields_to_return, fields_to_generate\n\n    def _read_fluid_fields(self, fields, dobj, chunk=None):\n        if len(fields) == 0:\n            return {}, []\n        fields_to_read, fields_to_generate = self._split_fields(fields)\n        if len(fields_to_read) == 0:\n            return {}, fields_to_generate\n        selector = dobj.selector\n        if chunk is None:\n            self._identify_base_chunk(dobj)\n            chunk_size = dobj.size\n        else:\n            chunk_size = chunk.data_size\n        fields_to_return = self.io._read_fluid_selection(\n            self._chunk_io(dobj), selector, fields_to_read, chunk_size\n        )\n        return fields_to_return, fields_to_generate\n\n    def _chunk(self, dobj, chunking_style, ngz=0, **kwargs):\n        # A chunk is either None or (grids, size)\n        if dobj._current_chunk is None:\n            self._identify_base_chunk(dobj)\n        if ngz != 0 and chunking_style != \"spatial\":\n            raise NotImplementedError\n        if chunking_style == \"all\":\n            return self._chunk_all(dobj, **kwargs)\n        elif chunking_style == \"spatial\":\n            return self._chunk_spatial(dobj, ngz, **kwargs)\n        elif chunking_style == \"io\":\n            return self._chunk_io(dobj, **kwargs)\n        else:\n            raise NotImplementedError\n\n\ndef cacheable_property(func):\n    # not quite equivalent to functools.cached_property\n    # this decorator allows cached to be disabled via a self._cache flag attribute\n    n = f\"_{func.__name__}\"\n\n    @property\n    def cacheable_func(self):\n        if self._cache and getattr(self, n, None) is not None:\n            return getattr(self, n)\n        if self.data_size is None:\n            tr = self._accumulate_values(n[1:])\n        else:\n            tr = func(self)\n        if self._cache:\n            setattr(self, n, tr)\n        return tr\n\n    return cacheable_func\n\n\nclass YTDataChunk:\n    def __init__(\n        self,\n        dobj,\n        chunk_type,\n        objs,\n        data_size=None,\n        field_type=None,\n        cache=False,\n        fast_index=None,\n    ):\n        self.dobj = dobj\n        self.chunk_type = chunk_type\n        self.objs = objs\n        self.data_size = data_size\n        self._field_type = field_type\n        self._cache = cache\n        self._fast_index = fast_index\n\n    def _accumulate_values(self, method):\n        # We call this generically.  It's somewhat slower, since we're doing\n        # costly getattr functions, but this allows us to generalize.\n        mname = f\"select_{method}\"\n        arrs = []\n        for obj in self._fast_index or self.objs:\n            f = getattr(obj, mname)\n            arrs.append(f(self.dobj))\n        if method == \"dtcoords\":\n            arrs = [arr[0] for arr in arrs]\n        elif method == \"tcoords\":\n            arrs = [arr[1] for arr in arrs]\n        if len(arrs) == 0:\n            self.data_size = 0\n            return np.empty((0, 3), dtype=\"float64\")\n\n        arrs = uconcatenate(arrs)\n        self.data_size = arrs.shape[0]\n        return arrs\n\n    @cacheable_property\n    def fcoords(self):\n        if self._fast_index is not None:\n            ci = self._fast_index.select_fcoords(self.dobj.selector, self.data_size)\n            ci = YTArray(ci, units=\"code_length\", registry=self.dobj.ds.unit_registry)\n            return ci\n        ci = np.empty((self.data_size, 3), dtype=\"float64\")\n        ci = YTArray(ci, units=\"code_length\", registry=self.dobj.ds.unit_registry)\n        if self.data_size == 0:\n            return ci\n        ind = 0\n        for obj in self._fast_index or self.objs:\n            c = obj.select_fcoords(self.dobj)\n            if c.shape[0] == 0:\n                continue\n            ci.d[ind : ind + c.shape[0], :] = c\n            ind += c.shape[0]\n        return ci\n\n    @cacheable_property\n    def icoords(self):\n        if self._fast_index is not None:\n            ci = self._fast_index.select_icoords(self.dobj.selector, self.data_size)\n            return ci\n        ci = np.empty((self.data_size, 3), dtype=\"int64\")\n        if self.data_size == 0:\n            return ci\n        ind = 0\n        for obj in self._fast_index or self.objs:\n            c = obj.select_icoords(self.dobj)\n            if c.shape[0] == 0:\n                continue\n            ci[ind : ind + c.shape[0], :] = c\n            ind += c.shape[0]\n        return ci\n\n    @cacheable_property\n    def fwidth(self):\n        if self._fast_index is not None:\n            ci = self._fast_index.select_fwidth(self.dobj.selector, self.data_size)\n            ci = YTArray(ci, units=\"code_length\", registry=self.dobj.ds.unit_registry)\n            return ci\n        ci = np.empty((self.data_size, 3), dtype=\"float64\")\n        ci = YTArray(ci, units=\"code_length\", registry=self.dobj.ds.unit_registry)\n        if self.data_size == 0:\n            return ci\n        ind = 0\n        for obj in self._fast_index or self.objs:\n            c = obj.select_fwidth(self.dobj)\n            if c.shape[0] == 0:\n                continue\n            ci.d[ind : ind + c.shape[0], :] = c\n\n            ind += c.shape[0]\n        return ci\n\n    @cacheable_property\n    def ires(self):\n        if self._fast_index is not None:\n            ci = self._fast_index.select_ires(self.dobj.selector, self.data_size)\n            return ci\n        ci = np.empty(self.data_size, dtype=\"int64\")\n        if self.data_size == 0:\n            return ci\n        ind = 0\n        for obj in self._fast_index or self.objs:\n            c = obj.select_ires(self.dobj)\n            if c.shape == 0:\n                continue\n            ci[ind : ind + c.size] = c\n            ind += c.size\n        return ci\n\n    @cacheable_property\n    def tcoords(self):\n        self.dtcoords\n        return self._tcoords\n\n    @cacheable_property\n    def dtcoords(self):\n        ct = np.empty(self.data_size, dtype=\"float64\")\n        cdt = np.empty(self.data_size, dtype=\"float64\")\n        self._tcoords = ct  # Se this for tcoords\n        if self.data_size == 0:\n            return cdt\n        ind = 0\n        for obj in self._fast_index or self.objs:\n            gdt, gt = obj.select_tcoords(self.dobj)\n            if gt.size == 0:\n                continue\n            ct[ind : ind + gt.size] = gt\n            cdt[ind : ind + gdt.size] = gdt\n            ind += gt.size\n        return cdt\n\n    @cacheable_property\n    def fcoords_vertex(self):\n        nodes_per_elem = self.dobj.index.meshes[0].connectivity_indices.shape[1]\n        dim = self.dobj.ds.dimensionality\n        ci = np.empty((self.data_size, nodes_per_elem, dim), dtype=\"float64\")\n        ci = YTArray(ci, units=\"code_length\", registry=self.dobj.ds.unit_registry)\n        if self.data_size == 0:\n            return ci\n        ind = 0\n        for obj in self.objs:\n            c = obj.select_fcoords_vertex(self.dobj)\n            if c.shape[0] == 0:\n                continue\n            ci.d[ind : ind + c.shape[0], :, :] = c\n            ind += c.shape[0]\n        return ci\n\n\nclass ChunkDataCache:\n    def __init__(self, base_iter, preload_fields, geometry_handler, max_length=256):\n        # At some point, max_length should instead become a heuristic function,\n        # potentially looking at estimated memory usage.  Note that this never\n        # initializes the iterator; it assumes the iterator is already created,\n        # and it calls next() on it.\n        self.base_iter = base_iter.__iter__()\n        self.queue = []\n        self.max_length = max_length\n        self.preload_fields = preload_fields\n        self.geometry_handler = geometry_handler\n        self.cache = {}\n\n    def __iter__(self):\n        return self\n\n    def __next__(self):\n        if len(self.queue) == 0:\n            for _ in range(self.max_length):\n                try:\n                    self.queue.append(next(self.base_iter))\n                except StopIteration:\n                    break\n            # If it's still zero ...\n            if len(self.queue) == 0:\n                raise StopIteration\n            chunk = YTDataChunk(None, \"cache\", self.queue, cache=False)\n            self.cache = (\n                self.geometry_handler.io._read_chunk_data(chunk, self.preload_fields)\n                or {}\n            )\n        g = self.queue.pop(0)\n        g._initialize_cache(self.cache.pop(g.id, {}))\n        return g\n\n\ndef is_curvilinear(geo):\n    # tell geometry is curvilinear or not\n    issue_deprecation_warning(\n        \"the is_curvilear() function is deprecated. \"\n        \"Instead, compare the geometry object directly with yt.geometry.geometry_enum.Geometry \"\n        \"enum members, as for instance:\\n\"\n        \"if is_curvilinear(geometry):\\n    ...\\n\"\n        \"should be rewritten as:\"\n        \"if geometry is Geometry.POLAR or geometry is Geometry.CYLINDRICAL or geometry is Geometry.SPHERICAL:\\n    ...\",\n        stacklevel=3,\n        since=\"4.2\",\n    )\n    if geo in [\"polar\", \"cylindrical\", \"spherical\"]:\n        return True\n    else:\n        return False\n"
  },
  {
    "path": "yt/geometry/grid_container.pxd",
    "content": "\"\"\"\nMatching points on the grid to specific grids\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.geometry cimport grid_visitors\nfrom yt.geometry.grid_visitors cimport (\n    GridTreeNode,\n    GridTreeNodePadded,\n    GridVisitorData,\n    grid_visitor_function,\n)\nfrom yt.utilities.lib.bitarray cimport bitarray\nfrom yt.utilities.lib.fp_utils cimport iclip\n\nfrom .selection_routines cimport SelectorObject, _ensure_code\n\n\ncdef class GridTree:\n    cdef GridTreeNode *grids\n    cdef GridTreeNode *root_grids\n    cdef int num_grids\n    cdef int num_root_grids\n    cdef int num_leaf_grids\n    cdef public bitarray mask\n    cdef void setup_data(self, GridVisitorData *data)\n    cdef void visit_grids(self, GridVisitorData *data,\n                          grid_visitor_function *func,\n                          SelectorObject selector)\n    cdef void recursively_visit_grid(self,\n                          GridVisitorData *data,\n                          grid_visitor_function *func,\n                          SelectorObject selector,\n                          GridTreeNode *grid,\n                          np.uint8_t *buf = ?)\n\ncdef class MatchPointsToGrids:\n\n    cdef int num_points\n    cdef np.float64_t *xp\n    cdef np.float64_t *yp\n    cdef np.float64_t *zp\n    cdef GridTree tree\n    cdef np.int64_t *point_grids\n    cdef np.uint8_t check_position(self,\n                                   np.int64_t pt_index,\n                                   np.float64_t x,\n                                   np.float64_t y,\n                                   np.float64_t z,\n                                   GridTreeNode *grid)\n\n    cdef np.uint8_t is_in_grid(self,\n\t\t\t np.float64_t x,\n\t\t\t np.float64_t y,\n\t\t\t np.float64_t z,\n\t\t\t GridTreeNode *grid)\n\ncdef extern from \"platform_dep.h\" nogil:\n    double rint(double x)\n"
  },
  {
    "path": "yt/geometry/grid_container.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\n\"\"\"\nMatching points on the grid to specific grids\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\nfrom yt.utilities.lib.bitarray cimport bitarray\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef GridTreeNode Grid_initialize(np.ndarray[np.float64_t, ndim=1] le,\n                                  np.ndarray[np.float64_t, ndim=1] re,\n                                  np.ndarray[np.int32_t, ndim=1] dims,\n                                  int num_children, int level, int index):\n\n    cdef GridTreeNode node\n    cdef int i\n\n    node.index = index\n    node.level = level\n    for i in range(3):\n        node.left_edge[i] = le[i]\n        node.right_edge[i] = re[i]\n        node.dims[i] = dims[i]\n        node.dds[i] = (re[i] - le[i])/dims[i]\n        node.start_index[i] = <np.int64_t> rint(le[i] / node.dds[i])\n    node.num_children = num_children\n    if num_children <= 0:\n        node.children = NULL\n        return node\n    node.children = <GridTreeNode **> malloc(\n            sizeof(GridTreeNode *) * num_children)\n    for i in range(num_children):\n        node.children[i] = NULL\n\n    return node\n\ncdef class GridTree:\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def __cinit__(self, int num_grids,\n                  np.ndarray[np.float64_t, ndim=2] left_edge,\n                  np.ndarray[np.float64_t, ndim=2] right_edge,\n                  np.ndarray[np.int32_t, ndim=2] dimensions,\n                  np.ndarray[np.int64_t, ndim=1] parent_ind,\n                  np.ndarray[np.int64_t, ndim=1] level,\n                  np.ndarray[np.int64_t, ndim=1] num_children):\n\n        cdef int i, j, k\n        cdef np.ndarray[np.int64_t, ndim=1] child_ptr\n\n        child_ptr = np.zeros(num_grids, dtype='int64')\n\n        self.num_grids = num_grids\n        self.num_root_grids = 0\n        self.num_leaf_grids = 0\n\n        self.grids = <GridTreeNode *> malloc(\n                sizeof(GridTreeNode) * num_grids)\n\n        for i in range(num_grids):\n            self.grids[i] = Grid_initialize(left_edge[i,:],\n                                            right_edge[i,:],\n                                            dimensions[i,:],\n                                            num_children[i],\n                                            level[i], i)\n            if level[i] == 0:\n                self.num_root_grids += 1\n            if num_children[i] == 0:\n                self.num_leaf_grids += 1\n\n        self.root_grids = <GridTreeNode *> malloc(\n                sizeof(GridTreeNode) * self.num_root_grids)\n        k = 0\n        for i in range(num_grids):\n            j = parent_ind[i]\n            if j >= 0:\n                self.grids[j].children[child_ptr[j]] = &self.grids[i]\n                child_ptr[j] += 1\n            else:\n                if k >= self.num_root_grids:\n                    raise RuntimeError\n                self.root_grids[k] = self.grids[i]\n                k = k + 1\n\n    def __init__(self, *args, **kwargs):\n        self.mask = None\n\n    def __iter__(self):\n        yield self\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def return_tree_info(self):\n        cdef int i, j\n        levels = []\n        indices = []\n        nchild = []\n        children = []\n        for i in range(self.num_grids):\n            childs = []\n            levels.append(self.grids[i].level)\n            indices.append(self.grids[i].index)\n            nchild.append(self.grids[i].num_children)\n            for j in range(self.grids[i].num_children):\n                childs.append(self.grids[i].children[j].index)\n            children.append(childs)\n        return indices, levels, nchild, children\n\n    @property\n    def grid_arrays(self):\n        cdef GridTreeNodePadded[:] grids\n        grids = <GridTreeNodePadded[:self.num_grids]> \\\n            (<GridTreeNodePadded*> self.grids)\n        grids_basic = np.asarray(grids)\n        # This next bit is necessary because as of 0.23.4, Cython can't make\n        # nested dtypes automatically where you have a property that is\n        # something like float[3].  So we unroll all of those, then re-roll\n        # them in a new dtype.\n        dtn = {}\n        dt = grids_basic.dtype\n        for name in dt.names:\n            d, o = dt.fields[name]\n            n = name\n            if name.endswith(\"_x\"):\n                f = (d.char, 3)\n                n = name[:-2]\n            elif name.endswith(\"_y\") or name.endswith(\"_z\"):\n                continue\n            else:\n                f = d.char\n            dtn[n] = (f, o)\n        return grids_basic.view(dtype=np.dtype(dtn))\n\n    cdef void setup_data(self, GridVisitorData *data):\n        # Being handed a new GVD object, we initialize it to sane defaults.\n        data.index = 0\n        data.global_index = 0\n        data.n_tuples = 0\n        data.child_tuples = NULL\n        data.array = NULL\n        data.ref_factor = 2 #### FIX THIS\n\n    cdef void visit_grids(self, GridVisitorData *data,\n                          grid_visitor_function *func,\n                          SelectorObject selector):\n        # This iterates over all root grids, given a selector+data, and then\n        # visits each one and its children.\n        cdef int i\n        # Because of confusion about mapping of children to parents, we are\n        # going to do this the stupid way for now.\n        cdef GridTreeNode *grid\n        cdef np.uint8_t *buf = NULL\n        if self.mask is not None:\n            buf = self.mask.buf\n        for i in range(self.num_root_grids):\n            grid = &self.root_grids[i]\n            self.recursively_visit_grid(data, func, selector, grid, buf)\n        grid_visitors.free_tuples(data)\n\n    cdef void recursively_visit_grid(self, GridVisitorData *data,\n                                     grid_visitor_function *func,\n                                     SelectorObject selector,\n                                     GridTreeNode *grid,\n                                     np.uint8_t *buf = NULL):\n        # Visit this grid and all of its child grids, with a given grid visitor\n        # function.  We early terminate if we are not selected by the selector.\n        cdef int i\n        data.grid = grid\n        if selector.select_bbox(grid.left_edge, grid.right_edge) == 0:\n            # Note that this does not increment the global_index.\n            return\n        grid_visitors.setup_tuples(data)\n        selector.visit_grid_cells(data, func, buf)\n        for i in range(grid.num_children):\n            self.recursively_visit_grid(data, func, selector, grid.children[i],\n                                        buf)\n\n    def count(self, SelectorObject selector):\n        # Use the counting grid visitor\n        cdef GridVisitorData data\n        self.setup_data(&data)\n        cdef np.uint64_t size = 0\n        cdef int i\n        for i in range(self.num_grids):\n            size += (self.grids[i].dims[0] *\n                     self.grids[i].dims[1] *\n                     self.grids[i].dims[2])\n        cdef bitarray mask = bitarray(size)\n        data.array = <void*>mask.buf\n        self.visit_grids(&data, grid_visitors.mask_cells, selector)\n        self.mask = mask\n        size = 0\n        self.setup_data(&data)\n        data.array = <void*>(&size)\n        self.visit_grids(&data,  grid_visitors.count_cells, selector)\n        return size\n\n    def select_icoords(self, SelectorObject selector, np.uint64_t size = -1):\n        # Fill icoords with a selector\n        cdef GridVisitorData data\n        self.setup_data(&data)\n        if size == -1:\n            size = 0\n            data.array = <void*>(&size)\n            self.visit_grids(&data,  grid_visitors.count_cells, selector)\n        cdef np.ndarray[np.int64_t, ndim=2] icoords\n        icoords = np.empty((size, 3), dtype=\"int64\")\n        data.array = icoords.data\n        self.visit_grids(&data, grid_visitors.icoords_cells, selector)\n        return icoords\n\n    def select_ires(self, SelectorObject selector, np.uint64_t size = -1):\n        # Fill ires with a selector\n        cdef GridVisitorData data\n        self.setup_data(&data)\n        if size == -1:\n            size = 0\n            data.array = <void*>(&size)\n            self.visit_grids(&data,  grid_visitors.count_cells, selector)\n        cdef np.ndarray[np.int64_t, ndim=1] ires\n        ires = np.empty(size, dtype=\"int64\")\n        data.array = ires.data\n        self.visit_grids(&data, grid_visitors.ires_cells, selector)\n        return ires\n\n    def select_fcoords(self, SelectorObject selector, np.uint64_t size = -1):\n        # Fill fcoords with a selector\n        cdef GridVisitorData data\n        self.setup_data(&data)\n        if size == -1:\n            size = 0\n            data.array = <void*>(&size)\n            self.visit_grids(&data,  grid_visitors.count_cells, selector)\n        cdef np.ndarray[np.float64_t, ndim=2] fcoords\n        fcoords = np.empty((size, 3), dtype=\"float64\")\n        data.array = fcoords.data\n        self.visit_grids(&data, grid_visitors.fcoords_cells, selector)\n        return fcoords\n\n    def select_fwidth(self, SelectorObject selector, np.uint64_t size = -1):\n        # Fill fwidth with a selector\n        cdef GridVisitorData data\n        self.setup_data(&data)\n        if size == -1:\n            size = 0\n            data.array = <void*>(&size)\n            self.visit_grids(&data,  grid_visitors.count_cells, selector)\n        cdef np.ndarray[np.float64_t, ndim=2] fwidth\n        fwidth = np.empty((size, 3), dtype=\"float64\")\n        data.array = fwidth.data\n        self.visit_grids(&data, grid_visitors.fwidth_cells, selector)\n        return fwidth\n\ncdef class MatchPointsToGrids:\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def __cinit__(self, GridTree tree,\n                  int num_points,\n                  np.ndarray[np.float64_t, ndim=1] x,\n                  np.ndarray[np.float64_t, ndim=1] y,\n                  np.ndarray[np.float64_t, ndim=1] z):\n\n        cdef int i\n\n        self.num_points = num_points\n        self.xp = <np.float64_t *> malloc(\n                sizeof(np.float64_t) * num_points)\n        self.yp = <np.float64_t *> malloc(\n                sizeof(np.float64_t) * num_points)\n        self.zp = <np.float64_t *> malloc(\n                sizeof(np.float64_t) * num_points)\n        self.point_grids = <np.int64_t *> malloc(\n                sizeof(np.int64_t) * num_points)\n        for i in range(num_points):\n            self.xp[i] = x[i]\n            self.yp[i] = y[i]\n            self.zp[i] = z[i]\n            self.point_grids[i] = -1\n        self.tree = tree\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def find_points_in_tree(self):\n        cdef np.ndarray[np.int64_t, ndim=1] pt_grids\n        cdef int i, j\n        cdef np.uint8_t in_grid\n        pt_grids = np.zeros(self.num_points, dtype='int64')\n        for i in range(self.num_points):\n            in_grid = 0\n            for j in range(self.tree.num_root_grids):\n                if not in_grid:\n                    in_grid = self.check_position(i, self.xp[i], self.yp[i], self.zp[i],\n                                                  &self.tree.root_grids[j])\n        for i in range(self.num_points):\n            pt_grids[i] = self.point_grids[i]\n        return pt_grids\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef np.uint8_t check_position(self,\n                                   np.int64_t pt_index,\n                                   np.float64_t x,\n                                   np.float64_t y,\n                                   np.float64_t z,\n                                   GridTreeNode * grid):\n        cdef int i\n        cdef np.uint8_t in_grid\n        in_grid = self.is_in_grid(x, y, z, grid)\n        if in_grid:\n            if grid.num_children > 0:\n                in_grid = 0\n                for i in range(grid.num_children):\n                    if not in_grid:\n                        in_grid = self.check_position(pt_index, x, y, z, grid.children[i])\n                if not in_grid:\n                    self.point_grids[pt_index] = grid.index\n                    in_grid = 1\n            else:\n                self.point_grids[pt_index] = grid.index\n                in_grid = 1\n        return in_grid\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef np.uint8_t is_in_grid(self,\n             np.float64_t x,\n             np.float64_t y,\n             np.float64_t z,\n             GridTreeNode * grid):\n        if x >= grid.right_edge[0]: return 0\n        if y >= grid.right_edge[1]: return 0\n        if z >= grid.right_edge[2]: return 0\n        if x < grid.left_edge[0]: return 0\n        if y < grid.left_edge[1]: return 0\n        if z < grid.left_edge[2]: return 0\n        return 1\n"
  },
  {
    "path": "yt/geometry/grid_geometry_handler.py",
    "content": "import abc\nimport weakref\nfrom collections import defaultdict\n\nimport numpy as np\n\nfrom yt.arraytypes import blankRecordArray\nfrom yt.config import ytcfg\nfrom yt.fields.derived_field import ValidateSpatial\nfrom yt.fields.field_detector import FieldDetector\nfrom yt.funcs import ensure_numpy_array, iter_fields\nfrom yt.geometry.geometry_handler import ChunkDataCache, Index, YTDataChunk\nfrom yt.utilities.definitions import MAXLEVEL\nfrom yt.utilities.logger import ytLogger as mylog\n\nfrom .grid_container import GridTree, MatchPointsToGrids\n\n\nclass GridIndex(Index, abc.ABC):\n    \"\"\"The index class for patch and block AMR datasets.\"\"\"\n\n    float_type = \"float64\"\n    _preload_implemented = False\n    _index_properties = (\n        \"grid_left_edge\",\n        \"grid_right_edge\",\n        \"grid_levels\",\n        \"grid_particle_count\",\n        \"grid_dimensions\",\n    )\n\n    def _setup_geometry(self):\n        mylog.debug(\"Counting grids.\")\n        self._count_grids()\n\n        mylog.debug(\"Initializing grid arrays.\")\n        self._initialize_grid_arrays()\n\n        mylog.debug(\"Parsing index.\")\n        self._parse_index()\n\n        mylog.debug(\"Constructing grid objects.\")\n        self._populate_grid_objects()\n\n        mylog.debug(\"Re-examining index\")\n        self._initialize_level_stats()\n\n    @abc.abstractmethod\n    def _count_grids(self):\n        pass\n\n    @abc.abstractmethod\n    def _parse_index(self):\n        pass\n\n    @abc.abstractmethod\n    def _populate_grid_objects(self):\n        pass\n\n    def __del__(self):\n        del self.grid_dimensions\n        del self.grid_left_edge\n        del self.grid_right_edge\n        del self.grid_levels\n        del self.grid_particle_count\n        del self.grids\n\n    @property\n    def parameters(self):\n        return self.dataset.parameters\n\n    def _detect_output_fields_backup(self):\n        # grab fields from backup file as well, if present\n        return\n\n    def select_grids(self, level):\n        \"\"\"\n        Returns an array of grids at *level*.\n        \"\"\"\n        return self.grids[self.grid_levels.flat == level]\n\n    def get_levels(self):\n        for level in range(self.max_level + 1):\n            yield self.select_grids(level)\n\n    def _initialize_grid_arrays(self):\n        mylog.debug(\"Allocating arrays for %s grids\", self.num_grids)\n        self.grid_dimensions = np.ones((self.num_grids, 3), \"int32\")\n        self.grid_left_edge = self.ds.arr(\n            np.zeros((self.num_grids, 3), self.float_type), \"code_length\"\n        )\n        self.grid_right_edge = self.ds.arr(\n            np.ones((self.num_grids, 3), self.float_type), \"code_length\"\n        )\n        self.grid_levels = np.zeros((self.num_grids, 1), \"int32\")\n        self.grid_particle_count = np.zeros((self.num_grids, 1), \"int32\")\n\n    def clear_all_data(self):\n        \"\"\"\n        This routine clears all the data currently being held onto by the grids\n        and the data io handler.\n        \"\"\"\n        for g in self.grids:\n            g.clear_data()\n        self.io.queue.clear()\n\n    def get_smallest_dx(self):\n        \"\"\"\n        Returns (in code units) the smallest cell size in the simulation.\n        \"\"\"\n        return self.select_grids(self.grid_levels.max())[0].dds[:].min()\n\n    def _get_particle_type_counts(self):\n        return {self.ds.particle_types_raw[0]: self.grid_particle_count.sum()}\n\n    def _initialize_level_stats(self):\n        # Now some statistics:\n        #   0 = number of grids\n        #   1 = number of cells\n        #   2 = blank\n        desc = {\"names\": [\"numgrids\", \"numcells\", \"level\"], \"formats\": [\"int64\"] * 3}\n        self.level_stats = blankRecordArray(desc, MAXLEVEL)\n        self.level_stats[\"level\"] = list(range(MAXLEVEL))\n        self.level_stats[\"numgrids\"] = [0 for i in range(MAXLEVEL)]\n        self.level_stats[\"numcells\"] = [0 for i in range(MAXLEVEL)]\n        for level in range(self.max_level + 1):\n            self.level_stats[level][\"numgrids\"] = np.sum(self.grid_levels == level)\n            li = self.grid_levels[:, 0] == level\n            self.level_stats[level][\"numcells\"] = (\n                self.grid_dimensions[li, :].prod(axis=1).sum()\n            )\n\n    @property\n    def grid_corners(self):\n        return np.array(\n            [\n                [\n                    self.grid_left_edge[:, 0],\n                    self.grid_left_edge[:, 1],\n                    self.grid_left_edge[:, 2],\n                ],\n                [\n                    self.grid_right_edge[:, 0],\n                    self.grid_left_edge[:, 1],\n                    self.grid_left_edge[:, 2],\n                ],\n                [\n                    self.grid_right_edge[:, 0],\n                    self.grid_right_edge[:, 1],\n                    self.grid_left_edge[:, 2],\n                ],\n                [\n                    self.grid_left_edge[:, 0],\n                    self.grid_right_edge[:, 1],\n                    self.grid_left_edge[:, 2],\n                ],\n                [\n                    self.grid_left_edge[:, 0],\n                    self.grid_left_edge[:, 1],\n                    self.grid_right_edge[:, 2],\n                ],\n                [\n                    self.grid_right_edge[:, 0],\n                    self.grid_left_edge[:, 1],\n                    self.grid_right_edge[:, 2],\n                ],\n                [\n                    self.grid_right_edge[:, 0],\n                    self.grid_right_edge[:, 1],\n                    self.grid_right_edge[:, 2],\n                ],\n                [\n                    self.grid_left_edge[:, 0],\n                    self.grid_right_edge[:, 1],\n                    self.grid_right_edge[:, 2],\n                ],\n            ],\n            dtype=\"float64\",\n        )\n\n    def lock_grids_to_parents(self):\n        r\"\"\"This function locks grid edges to their parents.\n\n        This is useful in cases where the grid structure may be somewhat\n        irregular, or where setting the left and right edges is a lossy\n        process.  It is designed to correct situations where left/right edges\n        may be set slightly incorrectly, resulting in discontinuities in images\n        and the like.\n        \"\"\"\n        mylog.info(\"Locking grids to parents.\")\n        for i, g in enumerate(self.grids):\n            si = g.get_global_startindex()\n            g.LeftEdge = self.ds.domain_left_edge + g.dds * si\n            g.RightEdge = g.LeftEdge + g.ActiveDimensions * g.dds\n            self.grid_left_edge[i, :] = g.LeftEdge\n            self.grid_right_edge[i, :] = g.RightEdge\n\n    def print_stats(self):\n        \"\"\"\n        Prints out (stdout) relevant information about the simulation\n        \"\"\"\n        header = \"{:>3}\\t{:>6}\\t{:>14}\\t{:>14}\".format(\n            \"level\", \"# grids\", \"# cells\", \"# cells^3\"\n        )\n        print(header)\n        print(f\"{len(header.expandtabs()) * '-'}\")\n        for level in range(MAXLEVEL):\n            if (self.level_stats[\"numgrids\"][level]) == 0:\n                continue\n            print(\n                f\"{level:>3}\\t\"\n                f\"{self.level_stats['numgrids'][level]:>6}\\t\"\n                f\"{self.level_stats['numcells'][level]:>14}\\t\"\n                f\"{int(np.ceil(self.level_stats['numcells'][level] ** (1.0 / 3))):>14}\"\n            )\n            dx = self.select_grids(level)[0].dds[0]\n        print(\"-\" * 46)\n        print(\n            \"   \\t\"\n            f\"{self.level_stats['numgrids'].sum():>6}\\t\"\n            f\"{self.level_stats['numcells'].sum():>14}\"\n        )\n        print(\"\\n\")\n        try:\n            print(f\"z = {self['CosmologyCurrentRedshift']:0.8f}\")\n        except Exception:\n            pass\n        print(\n            \"t = {:0.8e} = {:0.8e} = {:0.8e}\".format(\n                self.ds.current_time.in_units(\"code_time\"),\n                self.ds.current_time.in_units(\"s\"),\n                self.ds.current_time.in_units(\"yr\"),\n            )\n        )\n        print(\"\\nSmallest Cell:\")\n        for item in (\"Mpc\", \"pc\", \"AU\", \"cm\"):\n            print(f\"\\tWidth: {dx.in_units(item):0.3e}\")\n\n    def _find_field_values_at_points(self, fields, coords):\n        r\"\"\"Find the value of fields at a set of coordinates.\n\n        Returns the values [field1, field2,...] of the fields at the given\n        (x, y, z) points. Returns a numpy array of field values cross coords\n        \"\"\"\n        coords = self.ds.arr(ensure_numpy_array(coords), \"code_length\")\n        grids = self._find_points(coords[:, 0], coords[:, 1], coords[:, 2])[0]\n        fields = list(iter_fields(fields))\n        mark = np.zeros(3, dtype=\"int64\")\n        out = []\n\n        # create point -> grid mapping\n        grid_index = {}\n        for coord_index, grid in enumerate(grids):\n            if grid not in grid_index:\n                grid_index[grid] = []\n            grid_index[grid].append(coord_index)\n\n        out = []\n        for field in fields:\n            funit = self.ds._get_field_info(field).units\n            out.append(self.ds.arr(np.empty(len(coords)), funit))\n\n        for grid in grid_index:\n            cellwidth = (grid.RightEdge - grid.LeftEdge) / grid.ActiveDimensions\n            for field_index, field in enumerate(fields):\n                for coord_index in grid_index[grid]:\n                    mark = (coords[coord_index, :] - grid.LeftEdge) / cellwidth\n                    mark = np.array(mark, dtype=\"int64\")\n                    out[field_index][coord_index] = grid[field][\n                        mark[0], mark[1], mark[2]\n                    ]\n        if len(fields) == 1:\n            return out[0]\n        return out\n\n    def _find_points(self, x, y, z):\n        \"\"\"\n        Returns the (objects, indices) of leaf grids\n        containing a number of (x,y,z) points\n        \"\"\"\n        x = ensure_numpy_array(x)\n        y = ensure_numpy_array(y)\n        z = ensure_numpy_array(z)\n        if not len(x) == len(y) == len(z):\n            raise ValueError(\"Arrays of indices must be of the same size\")\n\n        grid_tree = self._get_grid_tree()\n        pts = MatchPointsToGrids(grid_tree, len(x), x, y, z)\n        ind = pts.find_points_in_tree()\n        return self.grids[ind], ind\n\n    def _get_grid_tree(self):\n        left_edge = self.ds.arr(np.zeros((self.num_grids, 3)), \"code_length\")\n        right_edge = self.ds.arr(np.zeros((self.num_grids, 3)), \"code_length\")\n        level = np.zeros((self.num_grids), dtype=\"int64\")\n        parent_ind = np.zeros((self.num_grids), dtype=\"int64\")\n        num_children = np.zeros((self.num_grids), dtype=\"int64\")\n        dimensions = np.zeros((self.num_grids, 3), dtype=\"int32\")\n\n        for i, grid in enumerate(self.grids):\n            left_edge[i, :] = grid.LeftEdge\n            right_edge[i, :] = grid.RightEdge\n            level[i] = grid.Level\n            if grid.Parent is None:\n                parent_ind[i] = -1\n            else:\n                parent_ind[i] = grid.Parent.id - grid.Parent._id_offset\n            num_children[i] = np.int64(len(grid.Children))\n            dimensions[i, :] = grid.ActiveDimensions\n\n        return GridTree(\n            self.num_grids,\n            left_edge,\n            right_edge,\n            dimensions,\n            parent_ind,\n            level,\n            num_children,\n        )\n\n    def convert(self, unit):\n        return self.dataset.conversion_factors[unit]\n\n    def _identify_base_chunk(self, dobj):\n        fast_index = None\n        if dobj._type_name == \"grid\":\n            dobj._chunk_info = np.empty(1, dtype=\"object\")\n            dobj._chunk_info[0] = weakref.proxy(dobj)\n        elif getattr(dobj, \"_grids\", None) is None:\n            gi = dobj.selector.select_grids(\n                self.grid_left_edge, self.grid_right_edge, self.grid_levels\n            )\n            if any(g.filename is not None for g in self.grids[gi]):\n                _gsort = _grid_sort_mixed\n            else:\n                _gsort = _grid_sort_id\n            grids = sorted(self.grids[gi], key=_gsort)\n            dobj._chunk_info = np.empty(len(grids), dtype=\"object\")\n            for i, g in enumerate(grids):\n                dobj._chunk_info[i] = g\n        # These next two lines, when uncommented, turn \"on\" the fast index.\n        # if dobj._type_name != \"grid\":\n        #    fast_index = self._get_grid_tree()\n        if getattr(dobj, \"size\", None) is None:\n            dobj.size = self._count_selection(dobj, fast_index=fast_index)\n        if getattr(dobj, \"shape\", None) is None:\n            dobj.shape = (dobj.size,)\n        dobj._current_chunk = list(\n            self._chunk_all(dobj, cache=False, fast_index=fast_index)\n        )[0]\n\n    def _count_selection(self, dobj, grids=None, fast_index=None):\n        if fast_index is not None:\n            return fast_index.count(dobj.selector)\n        if grids is None:\n            grids = dobj._chunk_info\n        count = sum(g.count(dobj.selector) for g in grids)\n        return count\n\n    def _chunk_all(self, dobj, cache=True, fast_index=None):\n        gobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        fast_index = fast_index or getattr(dobj._current_chunk, \"_fast_index\", None)\n        yield YTDataChunk(dobj, \"all\", gobjs, dobj.size, cache, fast_index=fast_index)\n\n    def _chunk_spatial(self, dobj, ngz, sort=None, preload_fields=None):\n        gobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        if sort in (\"+level\", \"level\"):\n            giter = sorted(gobjs, key=lambda g: g.Level)\n        elif sort == \"-level\":\n            giter = sorted(gobjs, key=lambda g: -g.Level)\n        elif sort is None:\n            giter = gobjs\n        if preload_fields is None:\n            preload_fields = []\n        preload_fields, _ = self._split_fields(preload_fields)\n        if self._preload_implemented and len(preload_fields) > 0 and ngz == 0:\n            giter = ChunkDataCache(list(giter), preload_fields, self)\n        for og in giter:\n            if ngz > 0:\n                g = og.retrieve_ghost_zones(ngz, [], smoothed=True)\n            else:\n                g = og\n            size = self._count_selection(dobj, [og])\n            if size == 0:\n                continue\n            # We don't want to cache any of the masks or icoords or fcoords for\n            # individual grids.\n            yield YTDataChunk(dobj, \"spatial\", [g], size, cache=False)\n\n    _grid_chunksize = 1000\n\n    def _chunk_io(\n        self,\n        dobj,\n        cache=True,\n        local_only=False,\n        preload_fields=None,\n        chunk_sizing=\"auto\",\n    ):\n        # local_only is only useful for inline datasets and requires\n        # implementation by subclasses.\n        if preload_fields is None:\n            preload_fields = []\n        preload_fields, _ = self._split_fields(preload_fields)\n        gfiles = defaultdict(list)\n        gobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        fast_index = dobj._current_chunk._fast_index\n        for g in gobjs:\n            # Force to be a string because sometimes g.filename is None.\n            gfiles[str(g.filename)].append(g)\n        # We can apply a heuristic here to make sure we aren't loading too\n        # many grids all at once.\n        if chunk_sizing == \"auto\":\n            chunk_ngrids = len(gobjs)\n            if chunk_ngrids > 0:\n                nproc = int(ytcfg.get(\"yt\", \"internals\", \"global_parallel_size\"))\n                chunking_factor = np.int64(\n                    np.ceil(self._grid_chunksize * nproc / chunk_ngrids)\n                )\n                size = max(self._grid_chunksize // chunking_factor, 1)\n            else:\n                size = self._grid_chunksize\n        elif chunk_sizing == \"config_file\":\n            size = ytcfg.get(\"yt\", \"chunk_size\")\n        elif chunk_sizing == \"just_one\":\n            size = 1\n        elif chunk_sizing == \"old\":\n            size = self._grid_chunksize\n        else:\n            raise RuntimeError(\n                f\"{chunk_sizing} is an invalid value for the 'chunk_sizing' argument.\"\n            )\n        for fn in sorted(gfiles):\n            gs = gfiles[fn]\n            for grids in (gs[pos : pos + size] for pos in range(0, len(gs), size)):\n                dc = YTDataChunk(\n                    dobj,\n                    \"io\",\n                    grids,\n                    self._count_selection(dobj, grids),\n                    cache=cache,\n                    fast_index=fast_index,\n                )\n                # We allow four full chunks to be included.\n                with self.io.preload(dc, preload_fields, 4.0 * size):\n                    yield dc\n\n    def _icoords_to_fcoords(\n        self,\n        icoords: np.ndarray,\n        ires: np.ndarray,\n        axes: tuple[int, ...] | None = None,\n    ) -> tuple[np.ndarray, np.ndarray]:\n        \"\"\"\n        Accepts icoords and ires and returns appropriate fcoords and fwidth.\n        Mostly useful for cases where we have irregularly spaced or structured\n        grids.\n        \"\"\"\n        dds = self.ds.domain_width[axes,] / (\n            self.ds.domain_dimensions[axes,] * self.ds.refine_by ** ires[:, None]\n        )\n        pos = (0.5 + icoords) * dds + self.ds.domain_left_edge[axes,]\n        return pos, dds\n\n    def _add_mesh_sampling_particle_field(self, deposit_field, ftype, ptype):\n        units = self.ds.field_info[ftype, deposit_field].units\n        take_log = self.ds.field_info[ftype, deposit_field].take_log\n        field_name = f\"cell_{ftype}_{deposit_field}\"\n\n        def _mesh_sampling_particle_field(data):\n            pos = data[ptype, \"particle_position\"]\n            field_values = data[ftype, deposit_field]\n\n            if isinstance(data, FieldDetector):\n                return np.zeros(pos.shape[0])\n\n            i, j, k = np.floor((pos - data.LeftEdge) / data.dds).astype(\"int64\").T\n\n            # Make sure all particles are within the current grid, otherwise return nan\n            maxi, maxj, maxk = field_values.shape\n\n            mask = (i < maxi) & (j < maxj) & (k < maxk)\n            mask &= (i >= 0) & (j >= 0) & (k >= 0)\n\n            result = np.full(len(pos), np.nan, dtype=\"float64\")\n            if result.shape[0] > 0:\n                result[mask] = field_values[i[mask], j[mask], k[mask]]\n\n            return data.ds.arr(result, field_values.units)\n\n        self.ds.add_field(\n            (ptype, field_name),\n            function=_mesh_sampling_particle_field,\n            sampling_type=\"particle\",\n            units=units,\n            take_log=take_log,\n            validators=[ValidateSpatial()],\n        )\n\n\ndef _grid_sort_id(g):\n    return g.id\n\n\ndef _grid_sort_mixed(g):\n    if g.filename is None:\n        return str(g.id)\n    return g.filename\n"
  },
  {
    "path": "yt/geometry/grid_visitors.pxd",
    "content": "\"\"\"\nGrid visitor definitions file\n\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\n\ncdef struct GridTreeNode:\n    np.int32_t num_children\n    np.int32_t level\n    np.int64_t index\n    np.float64_t left_edge[3]\n    np.float64_t right_edge[3]\n    GridTreeNode **children\n    np.int64_t start_index[3]\n    np.int32_t dims[3]\n    np.float64_t dds[3]\n\ncdef struct GridTreeNodePadded:\n    np.int32_t num_children\n    np.int32_t level\n    np.int64_t index\n    np.float64_t left_edge_x\n    np.float64_t left_edge_y\n    np.float64_t left_edge_z\n    np.float64_t right_edge_x\n    np.float64_t right_edge_y\n    np.float64_t right_edge_z\n    np.int64_t children_pointers\n    np.int64_t start_index_x\n    np.int64_t start_index_y\n    np.int64_t start_index_z\n    np.int32_t dims_x\n    np.int32_t dims_y\n    np.int32_t dims_z\n    np.float64_t dds_x\n    np.float64_t dds_y\n    np.float64_t dds_z\n\ncdef struct GridVisitorData:\n    GridTreeNode *grid\n    np.uint64_t index\n    np.uint64_t global_index\n    np.int64_t pos[3]       # position in ints\n    int n_tuples\n    int **child_tuples # [N_child][6], where 0-1 are x_start, x_end, etc.\n    void *array\n    int ref_factor # This may change on a grid-by-grid basis\n                   # It is the number of cells a child grid has per dimension\n                   # in a cell of this grid.\n\ncdef void free_tuples(GridVisitorData *data) nogil\ncdef void setup_tuples(GridVisitorData *data) nogil\ncdef np.uint8_t check_child_masked(GridVisitorData *data) nogil\n\nctypedef void grid_visitor_function(GridVisitorData *data,\n                                         np.uint8_t selected) nogil\n# This is similar in spirit to the way oct visitor functions work.  However,\n# there are a few important differences.  Because the grid objects are expected\n# to be bigger, we don't need to pass them along -- we will not be recursively\n# visiting.  So the GridVisitorData will be updated in between grids.\n# Furthermore, we're only going to use them for a much smaller subset of\n# operations.  All child mask evaluation is going to be conducted inside the\n# outermost level of the visitor function, and visitor functions will receive\n# information about whether they have been selected and whether they are\n# covered by child cells.\n\ncdef grid_visitor_function count_cells\ncdef grid_visitor_function mask_cells\ncdef grid_visitor_function icoords_cells\ncdef grid_visitor_function ires_cells\ncdef grid_visitor_function fcoords_cells\ncdef grid_visitor_function fwidth_cells\n"
  },
  {
    "path": "yt/geometry/grid_visitors.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\n\"\"\"\nGrid visitor functions\n\n\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.lib.bitarray cimport ba_set_value\nfrom yt.utilities.lib.fp_utils cimport iclip\n\n\ncdef void free_tuples(GridVisitorData *data) noexcept nogil:\n    # This wipes out the tuples, which is necessary since they are\n    # heap-allocated\n    cdef int i\n    if data.child_tuples == NULL: return\n    for i in range(data.n_tuples):\n        free(data.child_tuples[i])\n    free(data.child_tuples)\n    data.child_tuples = NULL\n    data.n_tuples = 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void setup_tuples(GridVisitorData *data) noexcept nogil:\n    # This sets up child-mask tuples.  Rather than a single mask that covers\n    # everything, we instead allocate pairs of integers that are start/stop\n    # positions for child masks.  This may not be considerably more efficient\n    # memory-wise, but it is easier to keep and save when going through\n    # multiple grids and selectors.\n    cdef int i, j\n    cdef np.int64_t si, ei\n    cdef GridTreeNode *g\n    cdef GridTreeNode *c\n    free_tuples(data)\n    g = data.grid\n    data.child_tuples = <int**> malloc(sizeof(int*) * g.num_children)\n    for i in range(g.num_children):\n        c = g.children[i]\n        data.child_tuples[i] = <int *>malloc(sizeof(int) * 6)\n        # Now we fill them in\n        for j in range(3):\n            si = (c.start_index[j] / data.ref_factor) - g.start_index[j]\n            ei = si + c.dims[j]/data.ref_factor - 1\n            data.child_tuples[i][j*2+0] = iclip(si, 0, g.dims[j] - 1)\n            data.child_tuples[i][j*2+1] = iclip(ei, 0, g.dims[j] - 1)\n    data.n_tuples = g.num_children\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef np.uint8_t check_child_masked(GridVisitorData *data) noexcept nogil:\n    # This simply checks if we're inside any of the tuples.  Probably not the\n    # most efficient way, but the GVD* passed in has a position affiliated with\n    # it, and we can very easily look for that inside here.\n    cdef int i, j, k\n    cdef int *tup\n    for i in range(data.n_tuples):\n        # k is if we're inside a given child tuple.  We check each one\n        # individually, and invalidate if we're outside.\n        k = 1\n        tup = data.child_tuples[i]\n        for j in range(3):\n            # Check if pos is outside in any of the three dimensions\n            if data.pos[j] < tup[j*2+0] or data.pos[j] > tup[j*2+1]:\n                k = 0\n                break\n        if k == 1: return 1 # Return 1 for child masked\n    return 0 # Only return 0 if it doesn't match any of the children\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void count_cells(GridVisitorData *data, np.uint8_t selected) noexcept nogil:\n    # Simply increment for each one, if we've selected it.\n    if selected == 0: return\n    cdef np.uint64_t *count = <np.uint64_t*> data.array\n    count[0] += 1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void mask_cells(GridVisitorData *data, np.uint8_t selected) noexcept nogil:\n    # Set our bitarray -- we're creating a mask -- if we are selected.\n    if selected == 0: return\n    cdef np.uint8_t *mask = <np.uint8_t*> data.array\n    ba_set_value(mask, data.global_index, 1)\n    # No need to increment anything.\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void icoords_cells(GridVisitorData *data, np.uint8_t selected) noexcept nogil:\n    # Nice and easy icoord setter.\n    if selected == 0: return\n    cdef int i\n    cdef np.int64_t *icoords = <np.int64_t*> data.array\n    for i in range(3):\n        icoords[data.index * 3 + i] = data.pos[i] + data.grid.start_index[i]\n    data.index += 1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void ires_cells(GridVisitorData *data, np.uint8_t selected) noexcept nogil:\n    # Fill with the level value.\n    if selected == 0: return\n    cdef np.int64_t *ires = <np.int64_t*> data.array\n    ires[data.index] = data.grid.level\n    data.index += 1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void fwidth_cells(GridVisitorData *data, np.uint8_t selected) noexcept nogil:\n    # Fill with our dds.\n    if selected == 0: return\n    cdef int i\n    cdef np.float64_t *fwidth = <np.float64_t*> data.array\n    for i in range(3):\n        fwidth[data.index * 3 + i] = data.grid.dds[i]\n    data.index += 1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void fcoords_cells(GridVisitorData *data, np.uint8_t selected) noexcept nogil:\n    # Simple cell-centered position filling.\n    if selected == 0: return\n    cdef int i\n    cdef np.float64_t *fcoords = <np.float64_t*> data.array\n    for i in range(3):\n        fcoords[data.index * 3 + i] = data.grid.left_edge[i] + \\\n            (0.5 + data.pos[i])*data.grid.dds[i]\n    data.index += 1\n"
  },
  {
    "path": "yt/geometry/oct_container.pxd",
    "content": "\"\"\"\nOct definitions file\n\n\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\nfrom libc.math cimport floor\nfrom libc.stdlib cimport bsearch, free, malloc, qsort, realloc\n\nfrom yt.geometry cimport oct_visitors, selection_routines\nfrom yt.utilities.lib.allocation_container cimport AllocationContainer, ObjectPool\nfrom yt.utilities.lib.fp_utils cimport *\n\nfrom .oct_visitors cimport Oct, OctInfo, OctVisitor, cind\n\n\ncdef int ORDER_MAX\n\ncdef struct OctKey:\n    np.int64_t key\n    Oct *node\n    # These next two are for particle sparse octrees.\n    np.int64_t *indices\n    np.int64_t pcount\n\ncdef struct OctList\n\ncdef struct OctList:\n    OctList *next\n    Oct *o\n\n# NOTE: This object *has* to be the same size as the AllocationContainer\n# object.  There's an assert in the __cinit__ function.\ncdef struct OctAllocationContainer:\n    np.uint64_t n\n    np.uint64_t n_assigned\n    np.uint64_t offset\n    np.int64_t con_id # container id\n    Oct *my_objs\n\ncdef class OctObjectPool(ObjectPool):\n    cdef inline OctAllocationContainer *get_cont(self, int i):\n        return <OctAllocationContainer*> (&self.containers[i])\n\ncdef OctList *OctList_append(OctList *list, Oct *o)\ncdef int OctList_count(OctList *list)\ncdef void OctList_delete(OctList *list)\n\ncdef class OctreeContainer:\n    cdef public OctObjectPool domains\n    cdef Oct ****root_mesh\n    cdef int partial_coverage\n    cdef int level_offset\n    cdef int nn[3]\n    cdef np.uint8_t nz\n    cdef np.float64_t DLE[3]\n    cdef np.float64_t DRE[3]\n    cdef public np.int64_t nocts\n    cdef public int num_domains\n    cdef Oct *get(self, np.float64_t ppos[3], OctInfo *oinfo = ?,\n                  int max_level = ?) noexcept nogil\n    cdef int get_root(self, int ind[3], Oct **o) noexcept nogil\n    cdef Oct **neighbors(self, OctInfo *oinfo, np.int64_t *nneighbors,\n                         Oct *o, bint periodicity[3])\n    # This function must return the offset from global-to-local domains; i.e.,\n    # AllocationContainer.offset if such a thing exists.\n    cdef np.int64_t get_domain_offset(self, int domain_id)\n    cdef void visit_all_octs(self,\n                        selection_routines.SelectorObject selector,\n                        OctVisitor visitor,\n                        int vc = ?, np.int64_t *indices = ?)\n    cdef Oct *next_root(self, int domain_id, int ind[3])\n    cdef Oct *next_child(self, int domain_id, int ind[3], Oct *parent) except? NULL\n    cdef void append_domain(self, np.int64_t domain_count)\n    # The fill_style is the ordering, C or F, of the octs in the file.  \"o\"\n    # corresponds to C, and \"r\" is for Fortran.\n    cdef public object fill_style\n    cdef public int max_level\n\n    cpdef void fill_level(\n        self,\n        const int level,\n        const np.uint8_t[::1] level_inds,\n        const np.uint8_t[::1] cell_inds,\n        const np.int64_t[::1] file_inds,\n        dict dest_fields,\n        dict source_fields,\n        np.int64_t offset = ?\n    )\n    cpdef int fill_level_with_domain(\n        self,\n        const int level,\n        const np.uint8_t[::1] level_inds,\n        const np.uint8_t[::1] cell_inds,\n        const np.int64_t[::1] file_inds,\n        const np.int32_t[::1] domain_inds,\n        dict dest_fields,\n        dict source_fields,\n        const np.int32_t domain,\n        np.int64_t offset = ?\n    )\n\ncdef class SparseOctreeContainer(OctreeContainer):\n    cdef OctKey *root_nodes\n    cdef void *tree_root\n    cdef int num_root\n    cdef int max_root\n    cdef void key_to_ipos(self, np.int64_t key, np.int64_t pos[3])\n    cdef np.int64_t ipos_to_key(self, int pos[3]) noexcept nogil\n\ncdef class RAMSESOctreeContainer(SparseOctreeContainer):\n    pass\n\ncdef extern from \"tsearch.h\" nogil:\n    void *tsearch(const void *key, void **rootp,\n                    int (*compar)(const void *, const void *))\n    void *tfind(const void *key, const void **rootp,\n                    int (*compar)(const void *, const void *))\n    void *tdelete(const void *key, void **rootp,\n                    int (*compar)(const void *, const void *))\n"
  },
  {
    "path": "yt/geometry/oct_container.pyx",
    "content": "# distutils: sources = yt/utilities/lib/tsearch.c\n# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\n\"\"\"\nOct container\n\n\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\n\nimport numpy as np\n\nfrom libc.math cimport floor\n\nfrom .oct_visitors cimport (\n    NeighbourCellIndexVisitor,\n    NeighbourCellVisitor,\n    OctPadded,\n    StoreIndex,\n)\nfrom .selection_routines cimport AlwaysSelector, SelectorObject\n\nORDER_MAX = 20\n_ORDER_MAX = ORDER_MAX\n\ncdef extern from \"stdlib.h\":\n    # NOTE that size_t might not be int\n    void *alloca(int)\n\n# Here is the strategy for RAMSES containers:\n#   * Read each domain individually, creating *all* octs found in that domain\n#     file, even if they reside on other CPUs.\n#   * Only allocate octs that reside on >= domain\n#   * For all octs, insert into tree, which may require traversing existing\n#     octs\n#   * Note that this does not allow one component of an ObjectPool (an\n#     AllocationContainer) to exactly be a chunk, but it is close.  For IO\n#     chunking, we can theoretically examine those octs that live inside a\n#     given allocator.\n\ncdef class OctreeContainer:\n\n    def __init__(self, oct_domain_dimensions, domain_left_edge,\n                 domain_right_edge, partial_coverage = 0,\n                 num_zones = 2):\n        # This will just initialize the root mesh octs\n        self.nz = num_zones\n        self.partial_coverage = partial_coverage\n        cdef int i\n        for i in range(3):\n            self.nn[i] = oct_domain_dimensions[i]\n        self.num_domains = 0\n        self.level_offset = 0\n        self.domains = OctObjectPool()\n        self.nocts = 0 # Increment when initialized\n        for i in range(3):\n            self.DLE[i] = domain_left_edge[i] #0\n            self.DRE[i] = domain_right_edge[i] #num_grid\n        self._initialize_root_mesh()\n        self.fill_style = \"o\"\n\n    def _initialize_root_mesh(self):\n        self.root_mesh = <Oct****> malloc(sizeof(void*) * self.nn[0])\n        for i in range(self.nn[0]):\n            self.root_mesh[i] = <Oct ***> malloc(sizeof(void*) * self.nn[1])\n            for j in range(self.nn[1]):\n                self.root_mesh[i][j] = <Oct **> malloc(sizeof(void*) * self.nn[2])\n                for k in range(self.nn[2]):\n                    self.root_mesh[i][j][k] = NULL\n\n    @property\n    def oct_arrays(self):\n        return self.domains.to_arrays()\n\n    @classmethod\n    def load_octree(cls, header):\n        cdef np.ndarray[np.uint8_t, ndim=1] ref_mask\n        ref_mask = header['octree']\n        cdef OctreeContainer obj = cls(header['dims'], header['left_edge'],\n                header['right_edge'], num_zones = header['num_zones'],\n                partial_coverage = header['partial_coverage'])\n        # NOTE: We do not allow domain/file indices to be specified.\n        cdef SelectorObject selector = AlwaysSelector(None)\n        cdef oct_visitors.LoadOctree visitor\n        visitor = oct_visitors.LoadOctree(obj, -1)\n        cdef int i, j, k\n        visitor.global_index = -1\n        visitor.level = 0\n        visitor.nz = visitor.nzones = 1\n        visitor.max_level = 0\n        assert(ref_mask.shape[0] / float(visitor.nzones) ==\n            <int>(ref_mask.shape[0]/float(visitor.nzones)))\n        obj.allocate_domains([ref_mask.shape[0] / visitor.nzones])\n        cdef np.float64_t pos[3]\n        cdef np.float64_t dds[3]\n        # This dds is the oct-width\n        for i in range(3):\n            dds[i] = (obj.DRE[i] - obj.DLE[i]) / obj.nn[i]\n        # Pos is the center of the octs\n        cdef OctAllocationContainer *cur = obj.domains.get_cont(0)\n        cdef Oct *o\n        cdef np.uint64_t nfinest = 0\n        visitor.ref_mask = ref_mask\n        visitor.octs = cur.my_objs\n        visitor.nocts = &cur.n_assigned\n        visitor.nfinest = &nfinest\n        pos[0] = obj.DLE[0] + dds[0]/2.0\n        for i in range(obj.nn[0]):\n            pos[1] = obj.DLE[1] + dds[1]/2.0\n            for j in range(obj.nn[1]):\n                pos[2] = obj.DLE[2] + dds[2]/2.0\n                for k in range(obj.nn[2]):\n                    if obj.root_mesh[i][j][k] != NULL:\n                        raise RuntimeError\n                    o = &cur.my_objs[cur.n_assigned]\n                    o.domain_ind = o.file_ind = 0\n                    o.domain = 1\n                    obj.root_mesh[i][j][k] = o\n                    cur.n_assigned += 1\n                    visitor.pos[0] = i\n                    visitor.pos[1] = j\n                    visitor.pos[2] = k\n                    # Always visit covered\n                    selector.recursively_visit_octs(\n                        obj.root_mesh[i][j][k],\n                        pos, dds, 0, visitor, 1)\n                    pos[2] += dds[2]\n                pos[1] += dds[1]\n            pos[0] += dds[0]\n        obj.nocts = cur.n_assigned\n        if obj.nocts * visitor.nz != ref_mask.size:\n            raise KeyError(ref_mask.size, obj.nocts, obj.nz,\n                obj.partial_coverage, visitor.nzones)\n        obj.max_level = visitor.max_level\n        return obj\n\n    def __dealloc__(self):\n        if self.root_mesh == NULL: return\n        for i in range(self.nn[0]):\n            if self.root_mesh[i] == NULL: continue\n            for j in range(self.nn[1]):\n                if self.root_mesh[i][j] == NULL: continue\n                free(self.root_mesh[i][j])\n            if self.root_mesh[i] == NULL: continue\n            free(self.root_mesh[i])\n        free(self.root_mesh)\n\n    @cython.cdivision(True)\n    cdef void visit_all_octs(self, SelectorObject selector,\n                        OctVisitor visitor, int vc = -1,\n                        np.int64_t *indices = NULL):\n        cdef int i, j, k\n        if vc == -1:\n            vc = self.partial_coverage\n        visitor.global_index = -1\n        visitor.level = 0\n        cdef np.float64_t pos[3]\n        cdef np.float64_t dds[3]\n        # This dds is the oct-width\n        for i in range(3):\n            dds[i] = (self.DRE[i] - self.DLE[i]) / self.nn[i]\n        # Pos is the center of the octs\n        pos[0] = self.DLE[0] + dds[0]/2.0\n        for i in range(self.nn[0]):\n            pos[1] = self.DLE[1] + dds[1]/2.0\n            for j in range(self.nn[1]):\n                pos[2] = self.DLE[2] + dds[2]/2.0\n                for k in range(self.nn[2]):\n                    if self.root_mesh[i][j][k] == NULL:\n                        raise KeyError(i,j,k)\n                    visitor.pos[0] = i\n                    visitor.pos[1] = j\n                    visitor.pos[2] = k\n                    selector.recursively_visit_octs(\n                        self.root_mesh[i][j][k],\n                        pos, dds, 0, visitor, vc)\n                    pos[2] += dds[2]\n                pos[1] += dds[1]\n            pos[0] += dds[0]\n\n    cdef np.int64_t get_domain_offset(self, int domain_id):\n        return 0\n\n    def _check_root_mesh(self):\n        cdef count = 0\n        for i in range(self.nn[0]):\n            for j in range(self.nn[1]):\n                for k in range(self.nn[2]):\n                    if self.root_mesh[i][j][k] == NULL:\n                        print(\"Missing \", i, j, k)\n                        count += 1\n        print(\"Missing total of %s out of %s\" % (count, self.nn[0] * self.nn[1] * self.nn[2]))\n\n    cdef int get_root(self, int ind[3], Oct **o) noexcept nogil:\n        cdef int i\n        for i in range(3):\n            if ind[i] < 0 or ind[i] >= self.nn[i]:\n                o[0] = NULL\n                return 1\n        o[0] = self.root_mesh[ind[0]][ind[1]][ind[2]]\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef Oct *get(self, np.float64_t ppos[3], OctInfo *oinfo = NULL,\n                  int max_level = 99) noexcept nogil:\n        #Given a floating point position, retrieve the most\n        #refined oct at that time\n        cdef int ind32[3]\n        cdef np.int64_t ipos[3]\n        cdef np.float64_t dds[3]\n        cdef np.float64_t cp[3]\n        cdef Oct *cur\n        cdef Oct *next\n        cdef int i\n        cur = next = NULL\n        cdef np.int64_t ind[3]\n        cdef np.int64_t level = -1\n        for i in range(3):\n            dds[i] = (self.DRE[i] - self.DLE[i])/self.nn[i]\n            ind[i] = <np.int64_t> (floor((ppos[i] - self.DLE[i])/dds[i]))\n            cp[i] = (ind[i] + 0.5) * dds[i] + self.DLE[i]\n            ipos[i] = 0 # We add this to ind later, so it should be zero.\n            ind32[i] = ind[i]\n        self.get_root(ind32, &next)\n        # We want to stop recursing when there's nowhere else to go\n        while next != NULL and level < max_level:\n            level += 1\n            for i in range(3):\n                ipos[i] = (ipos[i] << 1) + ind[i]\n            cur = next\n            for i in range(3):\n                dds[i] = dds[i] / 2.0\n                if cp[i] > ppos[i]:\n                    ind[i] = 0\n                    cp[i] -= dds[i] / 2.0\n                else:\n                    ind[i] = 1\n                    cp[i] += dds[i]/2.0\n            if cur.children != NULL:\n                next = cur.children[cind(ind[0],ind[1],ind[2])]\n            else:\n                next = NULL\n        if oinfo == NULL: return cur\n        cdef np.float64_t factor = 1.0 / self.nz * 2\n        for i in range(3):\n            # We don't normally need to change dds[i] as it has been halved\n            # from the oct width, thus making it already the cell width.\n            # But, since not everything has the cell width equal to have the\n            # width of the oct, we need to apply \"factor\".\n            oinfo.dds[i] = dds[i] * factor # Cell width\n            oinfo.ipos[i] = ipos[i]\n            oinfo.left_edge[i] = oinfo.ipos[i] * (oinfo.dds[i] * self.nz) + self.DLE[i]\n        oinfo.level = level\n        return cur\n\n    def locate_positions(self, np.float64_t[:,:] positions):\n        \"\"\"\n        This routine, meant to be called by other internal routines, returns a\n        list of oct IDs and a dictionary of Oct info for all the positions\n        supplied.  Positions must be in code_length.\n        \"\"\"\n        cdef np.float64_t factor = self.nz\n        cdef dict all_octs = {}\n        cdef OctInfo oi\n        cdef Oct* o = NULL\n        cdef np.float64_t pos[3]\n        cdef np.ndarray[np.uint8_t, ndim=1] recorded\n        cdef np.ndarray[np.int64_t, ndim=1] oct_id\n        oct_id = np.ones(positions.shape[0], dtype=\"int64\") * -1\n        recorded = np.zeros(self.nocts, dtype=\"uint8\")\n        cdef np.int64_t i, j\n        for i in range(positions.shape[0]):\n            for j in range(3):\n                pos[j] = positions[i,j]\n            o = self.get(pos, &oi)\n            if o == NULL:\n                raise RuntimeError\n            if recorded[o.domain_ind] == 0:\n                left_edge = np.asarray(<np.float64_t[:3]>oi.left_edge).copy()\n                dds = np.asarray(<np.float64_t[:3]>oi.dds).copy()\n                right_edge = left_edge + dds*factor\n                all_octs[o.domain_ind] = dict(\n                    left_edge = left_edge,\n                    right_edge = right_edge,\n                    level = oi.level\n                )\n                recorded[o.domain_ind] = 1\n            oct_id[i] = o.domain_ind\n        return oct_id, all_octs\n\n    def domain_identify(self, SelectorObject selector):\n        cdef np.ndarray[np.uint8_t, ndim=1] domain_mask\n        domain_mask = np.zeros(self.num_domains, dtype=\"uint8\")\n        cdef oct_visitors.IdentifyOcts visitor\n        visitor = oct_visitors.IdentifyOcts(self)\n        visitor.domain_mask = domain_mask\n        self.visit_all_octs(selector, visitor)\n        cdef int i\n        domain_ids = []\n        for i in range(self.num_domains):\n            if domain_mask[i] == 1:\n                domain_ids.append(i+1)\n        return domain_ids\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef Oct** neighbors(self, OctInfo *oi, np.int64_t *nneighbors, Oct *o,\n                         bint periodicity[3]):\n        # We are going to do a brute-force search here.\n        # This is not the most efficient -- in fact, it's relatively bad.  But\n        # we will attempt to improve it in a future iteration, where we will\n        # grow a stack of parent Octs.\n        # Note that in the first iteration, we will just find the up-to-27\n        # neighbors, including the main oct.\n        cdef np.int64_t i, j, k, n, level, ii, dlevel\n        cdef int ind[3]\n        cdef OctList *olist\n        cdef OctList *my_list\n        my_list = olist = NULL\n        cdef Oct *cand\n        cdef np.int64_t npos[3]\n        cdef np.int64_t ndim[3]\n        # Now we get our boundaries for this level, so that we can wrap around\n        # if need be.\n        # ndim is the oct dimensions of the level, not the cell dimensions.\n        for i in range(3):\n            ndim[i] = <np.int64_t> ((self.DRE[i] - self.DLE[i]) / oi.dds[i])\n            # Here we adjust for oi.dds meaning *cell* width.\n            ndim[i] = (ndim[i] / self.nz)\n        my_list = olist = OctList_append(NULL, o)\n        for i in range(3):\n            npos[0] = (oi.ipos[0] + (1 - i))\n            if not periodicity[0] and not \\\n               (0 <= npos[0] < ndim[0]):\n                continue\n            elif npos[0] < 0: npos[0] += ndim[0]\n            elif npos[0] >= ndim[0]: npos[0] -= ndim[0]\n            for j in range(3):\n                npos[1] = (oi.ipos[1] + (1 - j))\n                if not periodicity[1] and not \\\n                   (0 <= npos[1] < ndim[1]):\n                    continue\n                elif npos[1] < 0: npos[1] += ndim[1]\n                elif npos[1] >= ndim[1]: npos[1] -= ndim[1]\n                for k in range(3):\n                    npos[2] = (oi.ipos[2] + (1 - k))\n                    if not periodicity[2] and not \\\n                       (0 <= npos[2] < ndim[2]):\n                        continue\n                    if npos[2] < 0: npos[2] += ndim[2]\n                    if npos[2] >= ndim[2]: npos[2] -= ndim[2]\n                    # Now we have our npos, which we just need to find.\n                    # Level 0 gets bootstrapped\n                    for n in range(3):\n                        ind[n] = ((npos[n] >> (oi.level)) & 1)\n                    cand = NULL\n                    self.get_root(ind, &cand)\n                    # We should not get a NULL if we handle periodicity\n                    # correctly, but we might.\n                    if cand == NULL: continue\n                    for level in range(1, oi.level+1):\n                        dlevel = oi.level - level\n                        if cand.children == NULL: break\n                        for n in range(3):\n                            ind[n] = (npos[n] >> dlevel) & 1\n                        ii = cind(ind[0],ind[1],ind[2])\n                        if cand.children[ii] == NULL: break\n                        cand = cand.children[ii]\n                    if cand.children != NULL:\n                        olist = OctList_subneighbor_find(\n                            olist, cand, i, j, k)\n                    else:\n                        olist = OctList_append(olist, cand)\n        olist = my_list\n        cdef int noct = OctList_count(olist)\n        cdef Oct **neighbors\n        neighbors = <Oct **> malloc(sizeof(Oct*)*noct)\n        for i in range(noct):\n            neighbors[i] = olist.o\n            olist = olist.next\n        OctList_delete(my_list)\n        nneighbors[0] = noct\n        return neighbors\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def mask(self, SelectorObject selector, np.int64_t num_cells = -1,\n             int domain_id = -1):\n        if num_cells == -1:\n            num_cells = selector.count_octs(self, domain_id)\n        cdef np.ndarray[np.uint8_t, ndim=4] mask\n        cdef oct_visitors.MaskOcts visitor\n        visitor = oct_visitors.MaskOcts(self, domain_id)\n        cdef int ns = self.nz\n        mask = np.zeros((num_cells, ns, ns, ns), dtype=\"uint8\")\n        visitor.mask = mask\n        self.visit_all_octs(selector, visitor)\n        return mask.astype(\"bool\")\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def icoords(self, SelectorObject selector, np.int64_t num_cells = -1,\n                int domain_id = -1):\n        if num_cells == -1:\n            num_cells = selector.count_oct_cells(self, domain_id)\n        cdef oct_visitors.ICoordsOcts visitor\n        visitor = oct_visitors.ICoordsOcts(self, domain_id)\n        cdef np.ndarray[np.int64_t, ndim=2] coords\n        coords = np.empty((num_cells, 3), dtype=\"int64\")\n        visitor.icoords = coords\n        self.visit_all_octs(selector, visitor)\n        return coords\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def ires(self, SelectorObject selector, np.int64_t num_cells = -1,\n                int domain_id = -1):\n        cdef int i\n        if num_cells == -1:\n            num_cells = selector.count_oct_cells(self, domain_id)\n        cdef oct_visitors.IResOcts visitor\n        visitor = oct_visitors.IResOcts(self, domain_id)\n        #Return the 'resolution' of each cell; ie the level\n        cdef np.ndarray[np.int64_t, ndim=1] res\n        res = np.empty(num_cells, dtype=\"int64\")\n        visitor.ires = res\n        self.visit_all_octs(selector, visitor)\n        if self.level_offset > 0:\n            for i in range(num_cells):\n                res[i] += self.level_offset\n        return res\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fwidth(self, SelectorObject selector, np.int64_t num_cells = -1,\n                int domain_id = -1):\n        if num_cells == -1:\n            num_cells = selector.count_oct_cells(self, domain_id)\n        cdef oct_visitors.FWidthOcts visitor\n        visitor = oct_visitors.FWidthOcts(self, domain_id)\n        cdef np.ndarray[np.float64_t, ndim=2] fwidth\n        fwidth = np.empty((num_cells, 3), dtype=\"float64\")\n        visitor.fwidth = fwidth\n        self.visit_all_octs(selector, visitor)\n        cdef np.float64_t base_dx\n        for i in range(3):\n            base_dx = (self.DRE[i] - self.DLE[i])/self.nn[i]\n            fwidth[:,i] *= base_dx\n        return fwidth\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fcoords(self, SelectorObject selector, np.int64_t num_cells = -1,\n                int domain_id = -1):\n        if num_cells == -1:\n            num_cells = selector.count_oct_cells(self, domain_id)\n        cdef oct_visitors.FCoordsOcts visitor\n        visitor = oct_visitors.FCoordsOcts(self, domain_id)\n        #Return the floating point unitary position of every cell\n        cdef np.ndarray[np.float64_t, ndim=2] coords\n        coords = np.empty((num_cells, 3), dtype=\"float64\")\n        visitor.fcoords = coords\n        self.visit_all_octs(selector, visitor)\n        cdef int i\n        cdef np.float64_t base_dx\n        for i in range(3):\n            base_dx = (self.DRE[i] - self.DLE[i])/self.nn[i]\n            coords[:,i] *= base_dx\n            coords[:,i] += self.DLE[i]\n        return coords\n\n    def save_octree(self):\n        # Get the header\n        header = dict(dims = (self.nn[0], self.nn[1], self.nn[2]),\n                      left_edge = (self.DLE[0], self.DLE[1], self.DLE[2]),\n                      right_edge = (self.DRE[0], self.DRE[1], self.DRE[2]),\n                      num_zones = self.nz,\n                      partial_coverage = self.partial_coverage)\n        cdef SelectorObject selector = AlwaysSelector(None)\n        # domain_id = -1 here, because we want *every* oct\n        cdef oct_visitors.StoreOctree visitor\n        visitor = oct_visitors.StoreOctree(self, -1)\n        visitor.nz = 1\n        cdef np.ndarray[np.uint8_t, ndim=1] ref_mask\n        ref_mask = np.zeros(self.nocts * visitor.nzones, dtype=\"uint8\") - 1\n        visitor.ref_mask = ref_mask\n        # Enforce partial_coverage here\n        self.visit_all_octs(selector, visitor, 1)\n        header['octree'] = ref_mask\n        return header\n\n    def selector_fill(self, SelectorObject selector,\n                      np.ndarray source,\n                      np.ndarray dest = None,\n                      np.int64_t offset = 0, int dims = 1,\n                      int domain_id = -1):\n        # This is actually not correct.  The hard part is that we need to\n        # iterate the same way visit_all_octs does, but we need to track the\n        # number of octs total visited.\n        cdef np.int64_t num_cells = -1\n        if dest is None:\n            # Note that RAMSES can have partial refinement inside an Oct.  This\n            # means we actually do want the number of Octs, not the number of\n            # cells.\n            num_cells = selector.count_oct_cells(self, domain_id)\n            dest = np.zeros((num_cells, dims), dtype=source.dtype,\n                            order='C')\n        if dims != 1:\n            raise RuntimeError\n        # Just make sure that we're in the right shape.  Ideally this will not\n        # duplicate memory.  Since we're in Cython, we want to avoid modifying\n        # the .shape attributes directly.\n        dest = dest.reshape((num_cells, 1))\n        source = source.reshape((source.shape[0], source.shape[1],\n                    source.shape[2], source.shape[3], dims))\n        cdef OctVisitor visitor\n        cdef oct_visitors.CopyArrayI64 visitor_i64\n        cdef oct_visitors.CopyArrayF64 visitor_f64\n        if source.dtype != dest.dtype:\n            raise RuntimeError\n        if source.dtype == np.int64:\n            visitor_i64 = oct_visitors.CopyArrayI64(self, domain_id)\n            visitor_i64.source = source\n            visitor_i64.dest = dest\n            visitor = visitor_i64\n        elif source.dtype == np.float64:\n            visitor_f64 = oct_visitors.CopyArrayF64(self, domain_id)\n            visitor_f64.source = source\n            visitor_f64.dest = dest\n            visitor = visitor_f64\n        else:\n            raise NotImplementedError\n        visitor.index = offset\n        # We only need this so we can continue calculating the offset\n        visitor.dims = dims\n        self.visit_all_octs(selector, visitor)\n        if (visitor.global_index + 1) * visitor.nzones * visitor.dims > source.size:\n            print(\"GLOBAL INDEX RAN AHEAD.\",)\n            print (visitor.global_index + 1) * visitor.nzones * visitor.dims - source.size\n            print(dest.size, source.size, num_cells)\n            raise RuntimeError\n        if visitor.index > dest.size:\n            print(\"DEST INDEX RAN AHEAD.\",)\n            print(visitor.index - dest.size)\n            print (visitor.global_index + 1) * visitor.nzones * visitor.dims, source.size\n            print(num_cells)\n            raise RuntimeError\n        if num_cells >= 0:\n            return dest\n        return visitor.index - offset\n\n    def domain_ind(self, selector, int domain_id = -1):\n        cdef np.ndarray[np.int64_t, ndim=1] ind\n        # Here's where we grab the masked items.\n        ind = np.full(self.nocts, -1, 'int64')\n        cdef oct_visitors.IndexOcts visitor\n        visitor = oct_visitors.IndexOcts(self, domain_id)\n        visitor.oct_index = ind\n        self.visit_all_octs(selector, visitor)\n        return ind\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def add(self, int curdom, int curlevel,\n            np.ndarray[np.float64_t, ndim=2] pos,\n            int skip_boundary = 1,\n            int count_boundary = 0,\n            np.ndarray[np.uint64_t, ndim=1, cast=True] levels = None\n            ):\n        # In this function, if we specify curlevel = -1, then we query the\n        # (optional) levels array for the oct level.\n        cdef int no, p, i\n        cdef int ind[3]\n        cdef int nb = 0\n        cdef Oct *cur\n        cdef np.float64_t pp[3]\n        cdef np.float64_t cp[3]\n        cdef np.float64_t dds[3]\n        no = pos.shape[0] #number of octs\n        if curdom > self.num_domains: return 0\n        cdef OctAllocationContainer *cont = self.domains.get_cont(curdom - 1)\n        cdef int initial = cont.n_assigned\n        cdef int in_boundary = 0\n        # How do we bootstrap ourselves?\n        for p in range(no):\n            # We allow specifying curlevel = -1 to query from the levels array\n            # instead.\n            if curlevel == -1:\n                this_level = levels[p]\n            else:\n                this_level = curlevel\n            #for every oct we're trying to add find the\n            #floating point unitary position on this level\n            in_boundary = 0\n            for i in range(3):\n                pp[i] = pos[p, i]\n                dds[i] = (self.DRE[i] - self.DLE[i])/self.nn[i]\n                ind[i] = <np.int64_t> ((pp[i] - self.DLE[i])/dds[i])\n                cp[i] = (ind[i] + 0.5) * dds[i] + self.DLE[i]\n                if ind[i] < 0 or ind[i] >= self.nn[i]:\n                    in_boundary = 1\n            if skip_boundary == in_boundary == 1:\n                nb += count_boundary\n                continue\n            cur = self.next_root(curdom, ind)\n            if cur == NULL: raise RuntimeError\n            # Now we find the location we want\n            # Note that RAMSES I think 1-findiceses levels, but we don't.\n            for _ in range(this_level):\n                # At every level, find the cell this oct\n                # lives inside\n                for i in range(3):\n                    #as we get deeper, oct size halves\n                    dds[i] = dds[i] / 2.0\n                    if cp[i] > pp[i]:\n                        ind[i] = 0\n                        cp[i] -= dds[i]/2.0\n                    else:\n                        ind[i] = 1\n                        cp[i] += dds[i]/2.0\n                # Check if it has not been allocated\n                cur = self.next_child(curdom, ind, cur)\n            # Now we should be at the right level\n            cur.domain = curdom\n            cur.file_ind = p\n        return cont.n_assigned - initial + nb\n\n    def allocate_domains(self, domain_counts):\n        cdef int count, i\n        self.num_domains = len(domain_counts) # 1-indexed\n        for i, count in enumerate(domain_counts):\n            self.domains.append(count)\n\n    cdef void append_domain(self, np.int64_t domain_count):\n        self.num_domains += 1\n        self.domains.append(domain_count)\n\n    cdef Oct* next_root(self, int domain_id, int ind[3]):\n        cdef Oct *next = self.root_mesh[ind[0]][ind[1]][ind[2]]\n        if next != NULL: return next\n        cdef OctAllocationContainer *cont = self.domains.get_cont(domain_id - 1)\n        if cont.n_assigned >= cont.n: raise RuntimeError\n        next = &cont.my_objs[cont.n_assigned]\n        cont.n_assigned += 1\n        self.root_mesh[ind[0]][ind[1]][ind[2]] = next\n        self.nocts += 1\n        return next\n\n    cdef Oct* next_child(self, int domain_id, int ind[3], Oct *parent) except? NULL:\n        cdef int i\n        cdef Oct *next = NULL\n        if parent.children != NULL:\n            next = parent.children[cind(ind[0],ind[1],ind[2])]\n        else:\n            # This *8 does NOT need to be made generic.\n            parent.children = <Oct **> malloc(sizeof(Oct *) * 8)\n            for i in range(8):\n                parent.children[i] = NULL\n        if next != NULL: return next\n        cdef OctAllocationContainer *cont = self.domains.get_cont(domain_id - 1)\n        if cont.n_assigned >= cont.n: raise RuntimeError\n        next = &cont.my_objs[cont.n_assigned]\n        cont.n_assigned += 1\n        parent.children[cind(ind[0],ind[1],ind[2])] = next\n        self.nocts += 1\n        return next\n\n    def file_index_octs(self, SelectorObject selector, int domain_id,\n                        num_cells = -1):\n        # We create oct arrays of the correct size\n        cdef np.ndarray[np.uint8_t, ndim=1] levels\n        cdef np.ndarray[np.uint8_t, ndim=1] cell_inds\n        cdef np.ndarray[np.int64_t, ndim=1] file_inds\n        if num_cells < 0:\n            num_cells = selector.count_oct_cells(self, domain_id)\n        # Initialize variables with dummy values\n        levels = np.full(num_cells, 255, dtype=\"uint8\")\n        file_inds = np.full(num_cells, -1, dtype=\"int64\")\n        cell_inds = np.full(num_cells, 8, dtype=\"uint8\")\n        cdef oct_visitors.FillFileIndicesO visitor_o\n        cdef oct_visitors.FillFileIndicesR visitor_r\n        if self.fill_style == \"r\":\n            visitor_r = oct_visitors.FillFileIndicesR(self, domain_id)\n            visitor_r.levels = levels\n            visitor_r.file_inds = file_inds\n            visitor_r.cell_inds = cell_inds\n            visitor = visitor_r\n        elif self.fill_style == \"o\":\n            visitor_o = oct_visitors.FillFileIndicesO(self, domain_id)\n            visitor_o.levels = levels\n            visitor_o.file_inds = file_inds\n            visitor_o.cell_inds = cell_inds\n            visitor = visitor_o\n        else:\n            raise RuntimeError\n        self.visit_all_octs(selector, visitor)\n        return levels, cell_inds, file_inds\n\n    def morton_index_octs(self, SelectorObject selector, int domain_id,\n                          num_cells = -1):\n        cdef np.int64_t i\n        cdef np.uint8_t[:] levels\n        cdef np.uint64_t[:] morton_inds\n        if num_cells < 0:\n            num_cells = selector.count_oct_cells(self, domain_id)\n        levels = np.zeros(num_cells, dtype=\"uint8\")\n        morton_inds = np.zeros(num_cells, dtype=\"uint64\")\n        for i in range(num_cells):\n            levels[i] = 100\n            morton_inds[i] = 0\n        cdef oct_visitors.MortonIndexOcts visitor\n        visitor = oct_visitors.MortonIndexOcts(self, domain_id)\n        visitor.level_arr = levels\n        visitor.morton_ind = morton_inds\n        self.visit_all_octs(selector, visitor)\n        return levels, morton_inds\n\n    def domain_count(self, SelectorObject selector):\n        # We create oct arrays of the correct size\n        cdef np.ndarray[np.int64_t, ndim=1] domain_counts\n        domain_counts = np.zeros(self.num_domains, dtype=\"int64\")\n        cdef oct_visitors.CountByDomain visitor\n        visitor = oct_visitors.CountByDomain(self, -1)\n        visitor.domain_counts = domain_counts\n        self.visit_all_octs(selector, visitor)\n        return domain_counts\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cpdef void fill_level(\n        self,\n        const int level,\n        const np.uint8_t[::1] levels,\n        const np.uint8_t[::1] cell_inds,\n        const np.int64_t[::1] file_inds,\n        dict dest_fields,\n        dict source_fields,\n        np.int64_t offset = 0\n    ):\n        cdef np.float64_t[:, :] source\n        cdef np.float64_t[::1] dest\n        cdef int i, lvl\n\n        for key in dest_fields:\n            dest = dest_fields[key]\n            source = source_fields[key]\n            for i in range(levels.shape[0]):\n                lvl = levels[i]\n                if lvl != level: continue\n                if file_inds[i] < 0:\n                    dest[i + offset] = np.nan\n                else:\n                    dest[i + offset] = source[file_inds[i], cell_inds[i]]\n\n    def fill_index(self, SelectorObject selector = AlwaysSelector(None)):\n        \"\"\"Get the on-file index of each cell\"\"\"\n        cdef StoreIndex visitor\n\n        cdef np.int64_t[:, :, :, :] cell_inds\n\n        cell_inds = np.full((self.nocts, 2, 2, 2), -1, dtype=np.int64)\n\n        visitor = StoreIndex(self, -1)\n        visitor.cell_inds = cell_inds\n\n        self.visit_all_octs(selector, visitor)\n\n        return np.asarray(cell_inds)\n\n    def fill_octcellindex_neighbours(self, SelectorObject selector, int num_octs=-1, int domain_id=-1, int n_ghost_zones=1):\n        \"\"\"Compute the oct and cell indices of all the cells within all selected octs, extended\n        by one cell in all directions (for ghost zones computations).\n\n        Parameters\n        ----------\n        selector : SelectorObject\n            Selector for the octs to compute neighbour of\n        num_octs : int, optional\n            The number of octs to read in\n        domain_id : int, optional\n            The domain to perform the selection over\n\n        Returns\n        -------\n        oct_inds : int64 ndarray (nocts*8, )\n            The on-domain index of the octs containing each cell\n        cell_inds : uint8 ndarray (nocts*8, )\n            The index of the cell in its parent oct\n\n        Note\n        ----\n        oct_inds/cell_inds\n        \"\"\"\n        if num_octs == -1:\n            num_octs = selector.count_octs(self, domain_id)\n\n        cdef NeighbourCellIndexVisitor visitor\n\n        cdef np.uint8_t[::1] cell_inds\n        cdef np.int64_t[::1] oct_inds\n\n        cell_inds = np.full(num_octs*4**3, 8, dtype=np.uint8)\n        oct_inds = np.full(num_octs*4**3, -1, dtype=np.int64)\n\n        visitor = NeighbourCellIndexVisitor(self, -1, n_ghost_zones)\n        visitor.cell_inds = cell_inds\n        visitor.domain_inds = oct_inds\n\n        self.visit_all_octs(selector, visitor)\n\n        return np.asarray(oct_inds), np.asarray(cell_inds)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cpdef int fill_level_with_domain(\n        self,\n        const int level,\n        const np.uint8_t[::1] level_inds,\n        const np.uint8_t[::1] cell_inds,\n        const np.int64_t[::1] file_inds,\n        const np.int32_t[::1] domain_inds,\n        dict dest_fields,\n        dict source_fields,\n        const np.int32_t domain,\n        np.int64_t offset = 0\n    ):\n        \"\"\"Similar to fill_level but accepts a domain argument.\n\n        This is particularly useful for frontends that have buffer zones at CPU boundaries.\n        These buffer oct cells have a different domain than the local one and\n        are usually not read, but one has to read them e.g. to compute ghost zones.\n        \"\"\"\n        cdef np.float64_t[:, :] source\n        cdef np.float64_t[::1] dest\n        cdef int i, count, lev\n        cdef np.int32_t dom\n\n        for key in dest_fields:\n            dest = dest_fields[key]\n            source = source_fields[key]\n            count = 0\n            for i in range(level_inds.shape[0]):\n                lev = level_inds[i]\n                dom = domain_inds[i]\n                if lev != level or dom != domain: continue\n                count += 1\n                if file_inds[i] < 0:\n                    dest[i + offset] = np.nan\n                else:\n                    dest[i + offset] = source[file_inds[i], cell_inds[i]]\n        return count\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def file_index_octs_with_ghost_zones(\n            self, SelectorObject selector, int domain_id,\n            int num_cells=1, int n_ghost_zones=1):\n        \"\"\"Similar as file_index_octs, but returns the level, cell index,\n        file index and domain of the neighbouring cells as well.\n\n        Arguments\n        ---------\n        selector : SelectorObject\n            The selector object. It is expected to select all cells for a given oct.\n        domain_id : int\n            The domain to select. Set to -1 to select all domains.\n        num_cells : int, optional\n            The total number of cells (accounting for a 1-cell thick ghost zone layer).\n\n        Returns\n        -------\n        levels : uint8, shape (num_cells,)\n            The level of each cell of the super oct\n        cell_inds : uint8, shape (num_cells, )\n            The index of each cell of the super oct within its own oct\n        file_inds : int64, shape (num_cells, )\n            The on-file position of the cell. See notes below.\n        domains : int32, shape (num_cells)\n            The domain to which the cells belongs. See notes below.\n\n        Notes\n        -----\n\n        The algorithm constructs a \"super-oct\" around each oct (see sketch below,\n        where the original oct cells are marked with an x).\n\n        Note that for sparse octrees (such as RAMSES'), the neighbouring cells\n        may belong to another domain (this is stored in `domains`). If the dataset\n        provides buffer zones between domains (such as RAMSES), this may be stored\n        locally and can be accessed directly.\n\n\n        +---+---+---+---+\n        |   |   |   |   |\n        |---+---+---+---|\n        |   | x | x |   |\n        |---+---+---+---|\n        |   | x | x |   |\n        |---+---+---+---|\n        |   |   |   |   |\n        +---+---+---+---+\n\n        \"\"\"\n        cdef int num_octs\n        if num_cells < 0:\n            num_octs = selector.count_octs(self, domain_id)\n            num_cells = num_octs * 4**3\n        cdef NeighbourCellVisitor visitor\n\n        cdef np.ndarray[np.uint8_t, ndim=1] levels\n        cdef np.ndarray[np.uint8_t, ndim=1] cell_inds\n        cdef np.ndarray[np.int64_t, ndim=1] file_inds\n        cdef np.ndarray[np.int32_t, ndim=1] domains\n        levels = np.full(num_cells, 255, dtype=\"uint8\")\n        file_inds = np.full(num_cells, -1, dtype=\"int64\")\n        cell_inds = np.full(num_cells, 8, dtype=\"uint8\")\n        domains = np.full(num_cells, -1, dtype=\"int32\")\n\n        visitor = NeighbourCellVisitor(self, -1, n_ghost_zones)\n        # output: level, file_ind and cell_ind of the neighbouring cells\n        visitor.levels = levels\n        visitor.file_inds = file_inds\n        visitor.cell_inds = cell_inds\n        visitor.domains = domains\n        # direction to explore and extra parameters of the visitor\n        visitor.octree = self\n        visitor.last = -1\n\n        # Compute indices\n        self.visit_all_octs(selector, visitor)\n\n        return levels, cell_inds, file_inds, domains\n\n    def finalize(self):\n        cdef SelectorObject selector = AlwaysSelector(None)\n        cdef oct_visitors.AssignDomainInd visitor\n        visitor = oct_visitors.AssignDomainInd(self, 1)\n        self.visit_all_octs(selector, visitor)\n        assert ((visitor.global_index+1)*visitor.nzones == visitor.index)\n\ncdef int root_node_compare(const void *a, const void *b) noexcept nogil:\n    cdef OctKey *ao\n    cdef OctKey *bo\n    ao = <OctKey *>a\n    bo = <OctKey *>b\n    if ao.key < bo.key:\n        return -1\n    elif ao.key == bo.key:\n        return 0\n    else:\n        return 1\n\ncdef class SparseOctreeContainer(OctreeContainer):\n\n    def __init__(self, domain_dimensions, domain_left_edge, domain_right_edge,\n                 num_zones = 2):\n        cdef int i\n        self.partial_coverage = 1\n        self.nz = num_zones\n        for i in range(3):\n            self.nn[i] = domain_dimensions[i]\n        self.domains = OctObjectPool()\n        self.num_domains = 0\n        self.level_offset = 0\n        self.nocts = 0 # Increment when initialized\n        self.root_mesh = NULL\n        self.root_nodes = NULL\n        self.tree_root = NULL\n        self.num_root = 0\n        self.max_root = 0\n        # We don't initialize the octs yet\n        for i in range(3):\n            self.DLE[i] = domain_left_edge[i] #0\n            self.DRE[i] = domain_right_edge[i] #num_grid\n        self.fill_style = \"r\"\n\n    @classmethod\n    def load_octree(cls, header):\n        raise NotImplementedError\n\n    def save_octree(self):\n        raise NotImplementedError\n\n    cdef int get_root(self, int ind[3], Oct **o) noexcept nogil:\n        o[0] = NULL\n        cdef np.int64_t key = self.ipos_to_key(ind)\n        cdef OctKey okey\n        cdef OctKey **oresult = NULL\n        okey.key = key\n        okey.node = NULL\n        oresult = <OctKey **> tfind(<void*>&okey,\n            &self.tree_root, root_node_compare)\n        if oresult != NULL:\n            o[0] = oresult[0].node\n            return 1\n        return 0\n\n    cdef void key_to_ipos(self, np.int64_t key, np.int64_t pos[3]):\n        # Note: this is the result of doing\n        # for i in range(20):\n        #     ukey |= (1 << i)\n        cdef np.int64_t ukey = 1048575\n        cdef int j\n        for j in range(3):\n            pos[2 - j] = (<np.int64_t>(key & ukey))\n            key = key >> 20\n\n    cdef np.int64_t ipos_to_key(self, int pos[3]) noexcept nogil:\n        # We (hope) that 20 bits is enough for each index.\n        cdef int i\n        cdef np.int64_t key = 0\n        for i in range(3):\n            # Note the casting here.  Bitshifting can cause issues otherwise.\n            key |= ((<np.int64_t>pos[i]) << 20 * (2 - i))\n        return key\n\n    @cython.cdivision(True)\n    cdef void visit_all_octs(self, SelectorObject selector,\n                        OctVisitor visitor,\n                        int vc = -1,\n                        np.int64_t *indices = NULL):\n        cdef int i, j\n        cdef np.int64_t key\n        visitor.global_index = -1\n        visitor.level = 0\n        if vc == -1:\n            vc = self.partial_coverage\n        cdef np.float64_t pos[3]\n        cdef np.float64_t dds[3]\n        # This dds is the oct-width\n        for i in range(3):\n            dds[i] = (self.DRE[i] - self.DLE[i]) / self.nn[i]\n        # Pos is the center of the octs\n        cdef Oct *o\n        for i in range(self.num_root):\n            o = self.root_nodes[i].node\n            key = self.root_nodes[i].key\n            self.key_to_ipos(key, visitor.pos)\n            for j in range(3):\n                pos[j] = self.DLE[j] + (visitor.pos[j] + 0.5) * dds[j]\n            selector.recursively_visit_octs(\n                o, pos, dds, 0, visitor, vc)\n            if indices != NULL:\n                indices[i] = visitor.index\n\n    cdef np.int64_t get_domain_offset(self, int domain_id):\n        return 0 # We no longer have a domain offset.\n\n    cdef Oct* next_root(self, int domain_id, int ind[3]):\n        cdef Oct *next = NULL\n        self.get_root(ind, &next)\n        if next != NULL: return next\n        cdef OctAllocationContainer *cont = self.domains.get_cont(domain_id - 1)\n        if cont.n_assigned >= cont.n:\n            print(\"Too many assigned.\")\n            return NULL\n        if self.num_root >= self.max_root:\n            print(\"Too many roots.\")\n            return NULL\n        next = &cont.my_objs[cont.n_assigned]\n        cont.n_assigned += 1\n        cdef np.int64_t key = 0\n        cdef OctKey *ikey = &self.root_nodes[self.num_root]\n        key = self.ipos_to_key(ind)\n        self.root_nodes[self.num_root].key = key\n        self.root_nodes[self.num_root].node = next\n        tsearch(<void*>ikey, &self.tree_root, root_node_compare)\n        self.num_root += 1\n        self.nocts += 1\n        return next\n\n    def allocate_domains(self, domain_counts, int root_nodes):\n        OctreeContainer.allocate_domains(self, domain_counts)\n        self.root_nodes = <OctKey*> malloc(sizeof(OctKey) * root_nodes)\n        self.max_root = root_nodes\n        for i in range(root_nodes):\n            self.root_nodes[i].key = -1\n            self.root_nodes[i].node = NULL\n\n    def __dealloc__(self):\n        # This gets called BEFORE the superclass deallocation.  But, both get\n        # called.\n        cdef OctKey *ikey\n        for i in range(self.num_root):\n            ikey = &self.root_nodes[i]\n            tdelete(<void *>ikey, &self.tree_root, root_node_compare)\n\n        if self.root_nodes != NULL: free(self.root_nodes)\n\ncdef class ARTOctreeContainer(OctreeContainer):\n    def __init__(self, oct_domain_dimensions, domain_left_edge,\n                 domain_right_edge, partial_coverage = 0,\n                 num_zones = 2):\n        OctreeContainer.__init__(self, oct_domain_dimensions,\n                domain_left_edge, domain_right_edge, partial_coverage,\n                 num_zones)\n        self.fill_style = \"r\"\n\ncdef OctList *OctList_subneighbor_find(OctList *olist, Oct *top,\n                                       int i, int j, int k):\n    if top.children == NULL: return olist\n    # The i, j, k here are the offsets of \"top\" with respect to\n    # the oct for whose neighbors we are searching.\n    # Note that this will be recursively called.  We will evaluate either 1, 2,\n    # or 4 octs for children and potentially adding them.  In fact, this will\n    # be 2**(num_zero) where num_zero is the number of indices that are equal\n    # to zero; i.e., the number of dimensions along which we are aligned.\n    # For now, we assume we will not be doing this along all three zeros,\n    # because that would be pretty tricky.\n    if i == j == k == 1: return olist\n    cdef np.int64_t n[3]\n    cdef np.int64_t ind[3]\n    cdef np.int64_t off[3][2]\n    cdef np.int64_t ii, ij, ik, ci\n    ind[0] = 1 - i\n    ind[1] = 1 - j\n    ind[2] = 1 - k\n    for ii in range(3):\n        if ind[ii] == 0:\n            n[ii] = 2\n            off[ii][0] = 0\n            off[ii][1] = 1\n        elif ind[ii] == -1:\n            n[ii] = 1\n            off[ii][0] = 1\n        elif ind[ii] == 1:\n            n[ii] = 1\n            off[ii][0] = 0\n    for ii in range(n[0]):\n        for ij in range(n[1]):\n            for ik in range(n[2]):\n                ci = cind(off[0][ii], off[1][ij], off[2][ik])\n                cand = top.children[ci]\n                if cand.children != NULL:\n                    olist = OctList_subneighbor_find(olist,\n                        cand, i, j, k)\n                else:\n                    olist = OctList_append(olist, cand)\n    return olist\n\ncdef OctList *OctList_append(OctList *olist, Oct *o):\n    cdef OctList *this = olist\n    if this == NULL:\n        this = <OctList *> malloc(sizeof(OctList))\n        this.next = NULL\n        this.o = o\n        return this\n    while this.next != NULL:\n        this = this.next\n    this.next = <OctList*> malloc(sizeof(OctList))\n    this = this.next\n    this.o = o\n    this.next = NULL\n    return this\n\ncdef int OctList_count(OctList *olist):\n    cdef OctList *this = olist\n    cdef int i = 0 # Count the list\n    while this != NULL:\n        i += 1\n        this = this.next\n    return i\n\ncdef void OctList_delete(OctList *olist):\n    cdef OctList *next\n    cdef OctList *this = olist\n    while this != NULL:\n        next = this.next\n        free(this)\n        this = next\n\ncdef class OctObjectPool(ObjectPool):\n    # This is an inherited version of the ObjectPool that provides setup and\n    # teardown functions for the individually allocated objects.  These allow\n    # us to initialize the Octs to default values, and we can also free any\n    # allocated memory in them.  Implementing _con_to_array also provides the\n    # opportunity to supply views of the octs in Python code.\n    def __cinit__(self):\n        # Base class will ALSO be called\n        self.itemsize = sizeof(Oct)\n        assert(sizeof(OctAllocationContainer) == sizeof(AllocationContainer))\n\n    cdef void setup_objs(self, void *obj, np.uint64_t n, np.uint64_t offset,\n                         np.int64_t con_id):\n        cdef Oct* octs = <Oct *> obj\n        for n in range(n):\n            octs[n].file_ind = octs[n].domain = - 1\n            octs[n].domain_ind = n + offset\n            octs[n].children = NULL\n\n    cdef void teardown_objs(self, void *obj, np.uint64_t n, np.uint64_t offset,\n                           np.int64_t con_id):\n        cdef np.uint64_t i\n        cdef Oct *my_octs = <Oct *> obj\n        for i in range(n):\n            if my_octs[i].children != NULL:\n                free(my_octs[i].children)\n        free(obj)\n\n    def _con_to_array(self, int i):\n        cdef AllocationContainer *obj = &self.containers[i]\n        if obj.n_assigned == 0:\n            return None\n        cdef OctPadded[:] mm = <OctPadded[:obj.n_assigned]> (\n                <OctPadded*> obj.my_objs)\n        rv = np.asarray(mm)\n        return rv\n"
  },
  {
    "path": "yt/geometry/oct_geometry_handler.py",
    "content": "import numpy as np\n\nfrom yt.fields.field_detector import FieldDetector\nfrom yt.geometry.geometry_handler import Index\nfrom yt.utilities.logger import ytLogger as mylog\n\n\nclass OctreeIndex(Index):\n    \"\"\"The Index subclass for oct AMR datasets\"\"\"\n\n    def _setup_geometry(self):\n        mylog.debug(\"Initializing Octree Geometry Handler.\")\n        self._initialize_oct_handler()\n\n    def get_smallest_dx(self):\n        \"\"\"\n        Returns (in code units) the smallest cell size in the simulation.\n        \"\"\"\n        return (\n            self.dataset.domain_width\n            / (self.dataset.domain_dimensions * 2 ** (self.max_level))\n        ).min()\n\n    def convert(self, unit):\n        return self.dataset.conversion_factors[unit]\n\n    def _add_mesh_sampling_particle_field(self, deposit_field, ftype, ptype):\n        units = self.ds.field_info[ftype, deposit_field].units\n        take_log = self.ds.field_info[ftype, deposit_field].take_log\n        field_name = f\"cell_{ftype}_{deposit_field}\"\n\n        def _cell_index(data):\n            # Get the position of the particles\n            pos = data[ptype, \"particle_position\"]\n            Npart = pos.shape[0]\n            ret = np.zeros(Npart, dtype=\"float64\")\n            tmp = np.zeros(Npart, dtype=\"float64\")\n\n            if isinstance(data, FieldDetector):\n                return ret\n\n            remaining = np.ones(Npart, dtype=bool)\n            Nremaining = Npart\n\n            Nobjs = len(data._current_chunk.objs)\n            Nbits = int(np.ceil(np.log2(Nobjs)))\n\n            # Sort objs by decreasing number of octs\n            enumerated_objs = sorted(\n                enumerate(data._current_chunk.objs),\n                key=lambda arg: arg[1].oct_handler.nocts,\n                reverse=True,\n            )\n            for i, obj in enumerated_objs:\n                if Nremaining == 0:\n                    break\n                icell = (\n                    obj[\"index\", \"ones\"].T.reshape(-1).astype(np.int64).cumsum().value\n                    - 1\n                )\n                mesh_data = ((icell << Nbits) + i).astype(np.float64)\n                # Access the mesh data and attach them to their particles\n                tmp[:Nremaining] = obj.mesh_sampling_particle_field(\n                    pos[remaining], mesh_data\n                )\n\n                ret[remaining] = tmp[:Nremaining]\n\n                remaining[remaining] = np.isnan(tmp[:Nremaining])\n                Nremaining = remaining.sum()\n\n            return data.ds.arr(ret, units=\"1\")\n\n        def _mesh_sampling_particle_field(data):\n            \"\"\"\n            Create a grid field for particle quantities using given method.\n            \"\"\"\n            ones = data[ptype, \"particle_ones\"]\n\n            # Access \"cell_index\" field\n            Npart = ones.shape[0]\n            ret = np.zeros(Npart)\n            cell_index = np.array(data[ptype, \"cell_index\"], np.int64)\n\n            if isinstance(data, FieldDetector):\n                return ret\n\n            # The index of the obj is stored on the first bits\n            Nobjs = len(data._current_chunk.objs)\n            Nbits = int(np.ceil(np.log2(Nobjs)))\n            icell = cell_index >> Nbits\n            iobj = cell_index - (icell << Nbits)\n            for i, subset in enumerate(data._current_chunk.objs):\n                mask = iobj == i\n\n                subset.field_parameters = data.field_parameters\n\n                cell_data = subset[ftype, deposit_field].T.reshape(-1)\n\n                ret[mask] = cell_data[icell[mask]]\n\n            return data.ds.arr(ret, units=cell_data.units)\n\n        if (ptype, \"cell_index\") not in self.ds.derived_field_list:\n            self.ds.add_field(\n                (ptype, \"cell_index\"),\n                function=_cell_index,\n                sampling_type=\"particle\",\n                units=\"1\",\n            )\n\n        self.ds.add_field(\n            (ptype, field_name),\n            function=_mesh_sampling_particle_field,\n            sampling_type=\"particle\",\n            units=units,\n            take_log=take_log,\n        )\n\n    def _icoords_to_fcoords(\n        self,\n        icoords: np.ndarray,\n        ires: np.ndarray,\n        axes: tuple[int, ...] | None = None,\n    ) -> tuple[np.ndarray, np.ndarray]:\n        \"\"\"\n        Accepts icoords and ires and returns appropriate fcoords and fwidth.\n        Mostly useful for cases where we have irregularly spaced or structured\n        grids.\n        \"\"\"\n        dds = self.ds.domain_width[axes,] / (\n            self.ds.domain_dimensions[axes,] * self.ds.refine_by ** ires[:, None]\n        )\n        pos = (0.5 + icoords) * dds + self.ds.domain_left_edge[axes,]\n        return pos, dds\n"
  },
  {
    "path": "yt/geometry/oct_visitors.pxd",
    "content": "\"\"\"\nOct visitor definitions file\n\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\n\ncdef struct Oct\ncdef struct Oct:\n    np.int64_t file_ind     # index with respect to the order in which it was\n                            # added\n    np.int64_t domain_ind   # index within the global set of domains\n    np.int64_t domain       # (opt) addl int index\n    Oct **children          # Up to 8 long\n\ncdef struct OctInfo:\n    np.float64_t left_edge[3]\n    np.float64_t dds[3]\n    np.int64_t ipos[3]\n    np.int32_t level\n\ncdef struct OctPadded:\n    np.int64_t file_ind\n    np.int64_t domain_ind\n    np.int64_t domain\n    np.int64_t padding\n\ncdef class OctVisitor:\n    cdef np.uint64_t index\n    cdef np.uint64_t last\n    cdef np.int64_t global_index\n    cdef np.int64_t pos[3]       # position in ints\n    cdef np.uint8_t ind[3]       # cell position\n    cdef int dims\n    cdef np.int32_t domain\n    cdef np.int8_t level\n    cdef np.int8_t nz # This is number of zones along each dimension.  1 => 1 zones, 2 => 8, etc.\n                        # To calculate nzones, nz**3\n    cdef np.int32_t nzones\n\n    # There will also be overrides for the memoryviews associated with the\n    # specific instance.\n\n    cdef void visit(self, Oct*, np.uint8_t selected)\n\n    cdef inline int oind(self):\n        cdef int d = self.nz\n        return (((self.ind[0]*d)+self.ind[1])*d+self.ind[2])\n\n    cdef inline int rind(self):\n        cdef int d = self.nz\n        return (((self.ind[2]*d)+self.ind[1])*d+self.ind[0])\n\ncdef class CountTotalOcts(OctVisitor):\n    pass\n\ncdef class CountTotalCells(OctVisitor):\n    pass\n\ncdef class MarkOcts(OctVisitor):\n    # Unused\n    cdef np.uint8_t[:,:,:,:] mark\n\ncdef class MaskOcts(OctVisitor):\n    cdef np.uint8_t[:,:,:,:] mask\n\ncdef class IndexOcts(OctVisitor):\n    cdef np.int64_t[:] oct_index\n\ncdef class MaskedIndexOcts(OctVisitor):\n    cdef np.int64_t[:] oct_index\n    cdef np.uint8_t[:] oct_mask\n\ncdef class IndexMaskMapOcts(OctVisitor):\n    cdef np.int64_t[:] oct_index\n    cdef np.uint8_t[:] oct_mask\n    cdef np.int64_t[:] map_domain_ind\n    cdef np.uint64_t map_index\n\ncdef class ICoordsOcts(OctVisitor):\n    cdef np.int64_t[:,:] icoords\n\ncdef class IResOcts(OctVisitor):\n    cdef np.int64_t[:] ires\n\ncdef class FCoordsOcts(OctVisitor):\n    cdef np.float64_t[:,:] fcoords\n\ncdef class FWidthOcts(OctVisitor):\n    cdef np.float64_t[:,:] fwidth\n\ncdef class CopyArrayI64(OctVisitor):\n    cdef np.int64_t[:,:,:,:,:,:] source\n    cdef np.int64_t[:,:] dest\n\ncdef class CopyArrayF64(OctVisitor):\n    cdef np.float64_t[:,:,:,:,:] source\n    cdef np.float64_t[:,:] dest\n\ncdef class CopyFileIndArrayI8(OctVisitor):\n    cdef np.int64_t root\n    cdef np.uint8_t[:] source\n    cdef np.uint8_t[:] dest\n\ncdef class IdentifyOcts(OctVisitor):\n    cdef np.uint8_t[:] domain_mask\n\ncdef class AssignDomainInd(OctVisitor):\n    pass\n\ncdef class FillFileIndicesO(OctVisitor):\n    cdef np.uint8_t[:] levels\n    cdef np.int64_t[:] file_inds\n    cdef np.uint8_t[:] cell_inds\n\ncdef class FillFileIndicesR(OctVisitor):\n    cdef np.uint8_t[:] levels\n    cdef np.int64_t[:] file_inds\n    cdef np.uint8_t[:] cell_inds\n\ncdef class CountByDomain(OctVisitor):\n    cdef np.int64_t[:] domain_counts\n\ncdef class StoreOctree(OctVisitor):\n    cdef np.uint8_t[:] ref_mask\n\ncdef class LoadOctree(OctVisitor):\n    cdef np.uint8_t[:] ref_mask\n    cdef Oct* octs\n    cdef np.uint64_t *nocts\n    cdef np.uint64_t *nfinest\n    cdef np.uint64_t max_level\n\ncdef class MortonIndexOcts(OctVisitor):\n    cdef np.uint8_t[:] level_arr\n    cdef np.uint64_t[:] morton_ind\n\ncdef inline int cind(int i, int j, int k) noexcept nogil:\n    # THIS ONLY WORKS FOR CHILDREN.  It is not general for zones.\n    return (((i*2)+j)*2+k)\n\nfrom .oct_container cimport OctreeContainer\n\n\ncdef class StoreIndex(OctVisitor):\n    cdef np.int64_t[:,:,:,:] cell_inds\n\n# cimport oct_container\ncdef class BaseNeighbourVisitor(OctVisitor):\n    cdef int idim      # 0,1,2 for x,y,z\n    cdef int direction # +1 for +x, -1 for -x\n    cdef np.uint8_t neigh_ind[3]\n    cdef bint other_oct\n    cdef Oct *neighbour\n    cdef OctreeContainer octree\n    cdef OctInfo oi\n    cdef int n_ghost_zones\n\n    cdef void set_neighbour_info(self, Oct *o, int ishift[3])\n\n    cdef inline np.uint8_t neighbour_rind(self):\n        cdef int d = self.nz\n        return (((self.neigh_ind[2]*d)+self.neigh_ind[1])*d+self.neigh_ind[0])\n\ncdef class NeighbourCellIndexVisitor(BaseNeighbourVisitor):\n    cdef np.uint8_t[::1] cell_inds\n    cdef np.int64_t[::1] domain_inds\n\ncdef class NeighbourCellVisitor(BaseNeighbourVisitor):\n    cdef np.uint8_t[::1] levels\n    cdef np.int64_t[::1] file_inds\n    cdef np.uint8_t[::1] cell_inds\n    cdef np.int32_t[::1] domains\n"
  },
  {
    "path": "yt/geometry/oct_visitors.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\n\"\"\"\nOct visitor functions\n\n\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\n\nimport numpy as np\n\nfrom libc.stdlib cimport malloc\n\nfrom yt.utilities.lib.fp_utils cimport *\nfrom yt.utilities.lib.geometry_utils cimport encode_morton_64bit\n\nfrom .oct_container cimport OctreeContainer\n\n# Now some visitor functions\n\ncdef class OctVisitor:\n    def __init__(self, OctreeContainer octree, int domain_id = -1):\n        cdef int i\n        self.index = 0\n        self.last = -1\n        self.global_index = -1\n        for i in range(3):\n            self.pos[i] = -1\n            self.ind[i] = -1\n        self.dims = 0\n        self.domain = domain_id\n        self.level = -1\n        self.nz = octree.nz\n        self.nzones = self.nz**3\n\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        raise NotImplementedError\n\n# This copies an integer array from the source to the destination, based on the\n# selection criteria.\ncdef class CopyArrayI64(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # We should always have global_index less than our source.\n        # \"last\" here tells us the dimensionality of the array.\n        if selected == 0: return\n        # There are this many records between \"octs\"\n        self.dest[self.index, :] = self.source[\n                self.ind[2], self.ind[1], self.ind[0],\n                self.global_index, :]\n        self.index += 1\n\n# This copies a floating point array from the source to the destination, based\n# on the selection criteria.\ncdef class CopyArrayF64(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # We should always have global_index less than our source.\n        # \"last\" here tells us the dimensionality of the array.\n        if selected == 0: return\n        # There are this many records between \"octs\"\n        self.dest[self.index, :] = self.source[\n                self.ind[2], self.ind[1], self.ind[0],\n                self.global_index, :]\n        self.index += 1\n\n# This copies a bit array from source to the destination, based on file_ind\ncdef class CopyFileIndArrayI8(OctVisitor):\n    def __init__(self, OctreeContainer octree, int domain_id = -1):\n        super(CopyFileIndArrayI8, self).__init__(octree, domain_id)\n        self.root = -1\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        if self.level == 0:\n            self.root += 1\n        if self.last != o.domain_ind:\n            self.last = o.domain_ind\n            self.dest[o.domain_ind] = self.source[self.root]\n            self.index += 1\n\n# This counts the number of octs, selected or not, that the selector hits.\n# Note that the selector will not recursively visit unselected octs, so this is\n# still useful.\ncdef class CountTotalOcts(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # Count even if not selected.\n        # Number of *octs* visited.\n        if self.last != o.domain_ind:\n            self.index += 1\n            self.last = o.domain_ind\n\n# This counts the number of selected cells.\ncdef class CountTotalCells(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # Number of *cells* visited and selected.\n        self.index += selected\n\n# Every time a cell is visited, mark it.  This will be for all visited octs.\ncdef class MarkOcts(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # We mark them even if they are not selected\n        if self.last != o.domain_ind:\n            self.last = o.domain_ind\n            self.index += 1\n        self.mark[self.index, self.ind[2], self.ind[1], self.ind[0]] = 1\n\n# Mask all the selected cells.\ncdef class MaskOcts(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        if selected == 0: return\n        self.mask[self.global_index, self.ind[2], self.ind[1], self.ind[0]] = 1\n\n# Compute a mapping from domain_ind to flattened index.\ncdef class IndexOcts(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # Note that we provide an index even if the cell is not selected.\n        if self.last != o.domain_ind:\n            self.last = o.domain_ind\n            self.oct_index[o.domain_ind] = self.index\n            self.index += 1\n\n# Compute a mapping from domain_ind to flattened index with some octs masked.\ncdef class MaskedIndexOcts(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # Note that we provide an index even if the cell is not selected.\n        if self.last != o.domain_ind:\n            self.last = o.domain_ind\n            if self.oct_mask[o.domain_ind] == 1:\n                self.oct_index[o.domain_ind] = self.index\n                self.index += 1\n\n# Compute a mapping from domain_ind to flattened index checking mask.\ncdef class IndexMaskMapOcts(OctVisitor):\n    def __init__(self, OctreeContainer octree, int domain_id = -1):\n        super(IndexMaskMapOcts, self).__init__(octree, domain_id)\n        self.map_index = 0\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        if self.last != o.domain_ind:\n            self.last = o.domain_ind\n            if self.oct_mask[o.domain_ind] == 1:\n                if self.map_domain_ind[self.map_index] >= 0:\n                    self.oct_index[self.map_domain_ind[self.map_index]] = self.index\n                self.map_index += 1\n            self.index += 1\n\n# Integer coordinates\ncdef class ICoordsOcts(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        if selected == 0: return\n        cdef int i\n        for i in range(3):\n            self.icoords[self.index,i] = (self.pos[i] * self.nz) + self.ind[i]\n        self.index += 1\n\n# Level\ncdef class IResOcts(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        if selected == 0: return\n        self.ires[self.index] = self.level\n        self.index += 1\n\n# Floating point coordinates\ncdef class FCoordsOcts(OctVisitor):\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # Note that this does not actually give the correct floating point\n        # coordinates.  It gives them in some unit system where the domain is 1.0\n        # in all directions, and assumes that they will be scaled later.\n        if selected == 0: return\n        cdef int i\n        cdef np.float64_t c, dx\n        dx = 1.0 / ((self.nz) << self.level)\n        for i in range(3):\n            c = <np.float64_t> ((self.pos[i] * self.nz) + self.ind[i])\n            self.fcoords[self.index,i] = (c + 0.5) * dx\n        self.index += 1\n\n# Floating point widths; domain modifications are done later.\ncdef class FWidthOcts(OctVisitor):\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # Note that this does not actually give the correct floating point\n        # coordinates.  It gives them in some unit system where the domain is 1.0\n        # in all directions, and assumes that they will be scaled later.\n        if selected == 0: return\n        cdef int i\n        cdef np.float64_t dx\n        dx = 1.0 / (self.nz << self.level)\n        for i in range(3):\n            self.fwidth[self.index,i] = dx\n        self.index += 1\n\n# Mark which domains are touched by a selector.\ncdef class IdentifyOcts(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # We assume that our domain has *already* been selected by, which means\n        # we'll get all cells within the domain for a by-domain selector and all\n        # cells within the domain *and* selector for the selector itself.\n        if selected == 0: return\n        self.domain_mask[o.domain - 1] = 1\n\n# Assign domain indices to octs\ncdef class AssignDomainInd(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        o.domain_ind = self.global_index\n        self.index += 1\n\n# From the file, fill in C order\ncdef class FillFileIndicesO(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # We fill these arrays, then inside the level filler we use these as\n        # indices as we fill a second array from the self.\n        if selected == 0: return\n        self.levels[self.index] = self.level\n        self.file_inds[self.index] = o.file_ind\n        self.cell_inds[self.index] = self.oind()\n        self.index +=1\n\n# From the file, fill in F order\ncdef class FillFileIndicesR(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        # We fill these arrays, then inside the level filler we use these as\n        # indices as we fill a second array from the self.\n        if selected == 0: return\n        self.levels[self.index] = self.level\n        self.file_inds[self.index] = o.file_ind\n        self.cell_inds[self.index] = self.rind()\n        self.index +=1\n\n# Count octs by domain\ncdef class CountByDomain(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        if selected == 0: return\n        # NOTE: We do this for every *cell*.\n        self.domain_counts[o.domain - 1] += 1\n\n# Store the refinement mapping of the octree to be loaded later\ncdef class StoreOctree(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        if o.children == NULL:\n            # Not refined.\n            res = 0\n        else:\n            res = 1\n        self.ref_mask[self.index] = res\n        self.index += 1\n\n# Go from a refinement mapping to a new octree\ncdef class LoadOctree(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        cdef int i, ii\n        ii = cind(self.ind[0], self.ind[1], self.ind[2])\n        if self.level > self.max_level:\n            self.max_level = self.level\n        if self.ref_mask[self.index] == 0:\n            # We only want to do this once.  Otherwise we end up with way too many\n            # nfinest for our tastes.\n            if o.file_ind == -1:\n                o.children = NULL\n                o.file_ind = self.nfinest[0]\n                o.domain = 1\n                self.nfinest[0] += 1\n        elif self.ref_mask[self.index] > 0:\n            if self.ref_mask[self.index] != 1 and self.ref_mask[self.index] != 8:\n                print(\"ARRAY CLUE: \", self.ref_mask[self.index], \"UNKNOWN\")\n                raise RuntimeError\n            if o.children == NULL:\n                o.children = <Oct **> malloc(sizeof(Oct *) * 8)\n                for i in range(8):\n                    o.children[i] = NULL\n            for i in range(8):\n                o.children[ii + i] = &self.octs[self.nocts[0]]\n                o.children[ii + i].domain_ind = self.nocts[0]\n                o.children[ii + i].file_ind = -1\n                o.children[ii + i].domain = -1\n                o.children[ii + i].children = NULL\n                self.nocts[0] += 1\n        else:\n            print(\"SOMETHING IS AMISS\", self.index)\n            raise RuntimeError\n        self.index += 1\n\ncdef class MortonIndexOcts(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        if selected == 0: return\n        cdef np.int64_t coord[3]\n        cdef int i\n        for i in range(3):\n            coord[i] = (self.pos[i] * self.nz) + self.ind[i]\n            if (coord[i] < 0):\n                raise RuntimeError(\"Oct coordinate in dimension {} is \".format(i)+\n                                   \"negative. ({})\".format(coord[i]))\n        self.level_arr[self.index] = self.level\n        self.morton_ind[self.index] = encode_morton_64bit(\n                np.uint64(coord[0]),\n                np.uint64(coord[1]),\n                np.uint64(coord[2]))\n        self.index += 1\n\n# Store cell index\ncdef class StoreIndex(OctVisitor):\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        if not selected: return\n        self.cell_inds[o.domain_ind, self.ind[2], self.ind[1], self.ind[0]] = self.index\n\n        self.index += 1\n\ncdef class BaseNeighbourVisitor(OctVisitor):\n    def __init__(self, OctreeContainer octree, int domain_id=-1, int n_ghost_zones=1):\n        self.octree = octree\n        self.neigh_ind[0] = 0\n        self.neigh_ind[1] = 0\n        self.neigh_ind[2] = 0\n        self.n_ghost_zones = n_ghost_zones\n        super(BaseNeighbourVisitor, self).__init__(octree, domain_id)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void set_neighbour_info(self, Oct *o, int ishift[3]):\n        cdef int i\n        cdef np.float64_t c, dx\n        cdef np.int64_t ipos\n        cdef np.float64_t fcoords[3]\n        cdef Oct *neighbour\n        cdef bint local_oct\n        cdef bint other_oct\n        dx = 1.0 / (self.nz << self.level)\n        local_oct = True\n\n        # Compute position of neighbouring cell\n        for i in range(3):\n            c = <np.float64_t> (self.pos[i] * self.nz)\n            fcoords[i] = (c + 0.5 + ishift[i]) * dx / self.octree.nn[i]\n            # Assuming periodicity\n            if fcoords[i] < 0:\n                fcoords[i] += 1\n            elif fcoords[i] > 1:\n                fcoords[i] -= 1\n            local_oct &= (0 <= ishift[i] <= 1)\n        other_oct = not local_oct\n\n        # Use octree to find neighbour\n        if other_oct:\n            neighbour = self.octree.get(fcoords, &self.oi, max_level=self.level)\n        else:\n            neighbour = o\n            self.oi.level = self.level\n            for i in range(3):\n                self.oi.ipos[i] = (self.pos[i] * self.nz) + ishift[i]\n\n        # Extra step - compute cell position in neighbouring oct (and store in oi.ipos)\n        if self.oi.level == self.level - 1:\n            for i in range(3):\n                ipos = (((self.pos[i] * self.nz) + ishift[i])) >> 1\n                if (self.oi.ipos[i] << 1) == ipos:\n                    self.oi.ipos[i] = 0\n                else:\n                    self.oi.ipos[i] = 1\n        self.neighbour = neighbour\n\n        # Index of neighbouring cell within its oct\n        for i in range(3):\n            self.neigh_ind[i] = <np.uint8_t>(ishift[i]) % 2\n\n        self.other_oct = other_oct\n        if other_oct:\n            if self.neighbour != NULL:\n                if self.oi.level == self.level - 1:\n                    # Position within neighbouring oct is stored in oi.ipos\n                    for i in range(3):\n                        self.neigh_ind[i] = self.oi.ipos[i]\n                elif self.oi.level != self.level:\n                    print('This should not happen! %s %s' % (self.oi.level, self.level))\n                    self.neighbour = NULL\n\n# Store neighbouring cell index in current cell\ncdef class NeighbourCellIndexVisitor(BaseNeighbourVisitor):\n    # This piece of code is very much optimizable. Here are possible routes to achieve\n    # much better performance:\n\n    # - Work oct by oct, which would reduce the number of neighbor lookup\n    #   from 4³=64 to 3³=27,\n    # - Use faster neighbor lookup method(s). For now, all searches are started from\n    #   the root mesh down to leaf nodes, but we could instead go up the tree from the\n    #   central oct then down to find all neighbors (see e.g.\n    #   https://geidav.wordpress.com/2017/12/02/advanced-octrees-4-finding-neighbor-nodes/).\n    # -  Pre-compute the face-neighbors of all octs.\n\n    # Note that for the last point, algorithms exist that generate the neighbors of all\n    # octs in O(1) time (https://link.springer.com/article/10.1007/s13319-015-0060-9)\n    # during the octree construction.\n    # Another possible solution would be to keep a unordered hash map of all the octs\n    # indexed by their (3-integers) position. With such structure, finding a neighbor\n    # takes O(1) time. This could even come as a replacement of the current\n    # pointer-based octree structure.\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        cdef int i, j, k\n        cdef int ishift[3]\n        cdef np.uint8_t neigh_cell_ind\n        cdef np.int64_t neigh_domain_ind\n        if selected == 0: return\n        # Work at oct level\n        if self.last == o.domain_ind: return\n\n        self.last = o.domain_ind\n\n        cdef int i0, i1\n        i0 = -self.n_ghost_zones\n        i1 = 2 + self.n_ghost_zones\n        # Loop over cells in and directly around oct\n        for i in range(i0, i1):\n            ishift[0] = i\n            for j in range(i0, i1):\n                ishift[1] = j\n                for k in range(i0, i1):\n                    ishift[2] = k\n                    self.set_neighbour_info(o, ishift)\n\n                    if not self.other_oct:\n                        neigh_domain_ind = o.domain_ind\n                        neigh_cell_ind   = self.neighbour_rind()\n                    elif self.neighbour != NULL:\n                        neigh_domain_ind = self.neighbour.domain_ind\n                        neigh_cell_ind   = self.neighbour_rind()\n                    else:\n                        neigh_domain_ind = -1\n                        neigh_cell_ind   = 8\n\n                    self.cell_inds[self.index]   = neigh_cell_ind\n                    self.domain_inds[self.index] = neigh_domain_ind\n\n                    self.index += 1\n\n# Store file position + cell of neighbouring cell in current cell\ncdef class NeighbourCellVisitor(BaseNeighbourVisitor):\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void visit(self, Oct* o, np.uint8_t selected):\n        cdef int i, j, k\n        cdef int ishift[3]\n        cdef np.int64_t neigh_file_ind\n        cdef np.uint8_t neigh_cell_ind\n        cdef np.int32_t neigh_domain\n        cdef np.uint8_t neigh_level\n        if selected == 0: return\n        # Work at oct level\n        if self.last == o.domain_ind: return\n\n        self.last = o.domain_ind\n\n        cdef int i0, i1\n        i0 = -self.n_ghost_zones\n        i1 = 2 + self.n_ghost_zones\n        # Loop over cells in and directly around oct\n        for i in range(i0, i1):\n            ishift[0] = i\n            for j in range(i0, i1):\n                ishift[1] = j\n                for k in range(i0, i1):\n                    ishift[2] = k\n                    self.set_neighbour_info(o, ishift)\n\n                    if not self.other_oct:\n                        neigh_level    = self.level\n                        neigh_domain   = o.domain\n                        neigh_file_ind = o.file_ind\n                        neigh_cell_ind = self.neighbour_rind()\n                    elif self.neighbour != NULL:\n                        neigh_level    = self.oi.level\n                        neigh_domain   = self.neighbour.domain\n                        neigh_file_ind = self.neighbour.file_ind\n                        neigh_cell_ind = self.neighbour_rind()\n                    else:\n                        neigh_level    = 255\n                        neigh_domain   = -1\n                        neigh_file_ind = -1\n                        neigh_cell_ind = 8\n\n                    self.levels[self.index]    = neigh_level\n                    self.file_inds[self.index] = neigh_file_ind\n                    self.cell_inds[self.index] = neigh_cell_ind\n                    self.domains[self.index]   = neigh_domain\n\n                    self.index += 1\n"
  },
  {
    "path": "yt/geometry/particle_deposit.pxd",
    "content": "\"\"\"\nParticle Deposition onto Octs\n\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\nimport numpy as np\n\ncimport cython\nfrom libc.math cimport sqrt\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.lib.fp_utils cimport *\n\nfrom .oct_container cimport Oct, OctreeContainer\n\ncdef extern from \"numpy/npy_math.h\":\n    double NPY_PI\n\ncdef extern from \"platform_dep.h\":\n    void *alloca(int)\n\ncdef inline int gind(int i, int j, int k, int dims[3]):\n    # The ordering is such that we want i to vary the slowest in this instance,\n    # even though in other instances it varies the fastest.  To see this in\n    # action, try looking at the results of an n_ref=256 particle CIC plot,\n    # which shows it the most clearly.\n    return ((i*dims[1])+j)*dims[2]+k\n\n\n####################################################\n# Standard SPH kernel for use with the Grid method #\n####################################################\n\ncdef inline np.float64_t sph_kernel_cubic(np.float64_t x) noexcept nogil:\n    cdef np.float64_t kernel\n    # C is 8/pi\n    cdef np.float64_t C = 2.5464790894703255\n    if x <= 0.5:\n        kernel = 1.-6.*x*x*(1.-x)\n    elif x>0.5 and x<=1.0:\n        kernel = 2.*(1.-x)*(1.-x)*(1.-x)\n    else:\n        kernel = 0.\n    return kernel * C\n\n########################################################\n# Alternative SPH kernels for use with the Grid method #\n########################################################\n\n# quartic spline\ncdef inline np.float64_t sph_kernel_quartic(np.float64_t x) noexcept nogil:\n    cdef np.float64_t kernel\n    cdef np.float64_t C = 9.71404681957369  # 5.**6/512/np.pi\n    if x < 1:\n        kernel = (1.-x)**4\n        if x < 3./5:\n            kernel -= 5*(3./5-x)**4\n            if x < 1./5:\n                kernel += 10*(1./5-x)**4\n    else:\n        kernel = 0.\n    return kernel * C\n\n# quintic spline\ncdef inline np.float64_t sph_kernel_quintic(np.float64_t x) noexcept nogil:\n    cdef np.float64_t kernel\n    cdef np.float64_t C = 17.403593027098754  # 3.**7/40/np.pi\n    if x < 1:\n        kernel = (1.-x)**5\n        if x < 2./3:\n            kernel -= 6*(2./3-x)**5\n            if x < 1./3:\n                kernel += 15*(1./3-x)**5\n    else:\n        kernel = 0.\n    return kernel * C\n\n# Wendland C2\ncdef inline np.float64_t sph_kernel_wendland2(np.float64_t x) noexcept nogil:\n    cdef np.float64_t kernel\n    cdef np.float64_t C = 3.3422538049298023  # 21./2/np.pi\n    if x < 1:\n        kernel = (1.-x)**4 * (1+4*x)\n    else:\n        kernel = 0.\n    return kernel * C\n\n# Wendland C4\ncdef inline np.float64_t sph_kernel_wendland4(np.float64_t x) noexcept nogil:\n    cdef np.float64_t kernel\n    cdef np.float64_t C = 4.923856051905513  # 495./32/np.pi\n    if x < 1:\n        kernel = (1.-x)**6 * (1+6*x+35./3*x**2)\n    else:\n        kernel = 0.\n    return kernel * C\n\n# Wendland C6\ncdef inline np.float64_t sph_kernel_wendland6(np.float64_t x) noexcept nogil:\n    cdef np.float64_t kernel\n    cdef np.float64_t C = 6.78895304126366  # 1365./64/np.pi\n    if x < 1:\n        kernel = (1.-x)**8 * (1+8*x+25*x**2+32*x**3)\n    else:\n        kernel = 0.\n    return kernel * C\n\ncdef inline np.float64_t sph_kernel_dummy(np.float64_t x) noexcept nogil:\n    return 0\n\n# I don't know the way to use a dict in a cdef class.\n# So in order to mimic a registry functionality,\n# I manually created a function to lookup the kernel functions.\nctypedef np.float64_t (*kernel_func) (np.float64_t) noexcept nogil\ncdef inline kernel_func get_kernel_func(str kernel_name) noexcept nogil:\n    with gil:\n        if kernel_name == 'cubic':\n            return sph_kernel_cubic\n        elif kernel_name == 'quartic':\n            return sph_kernel_quartic\n        elif kernel_name == 'quintic':\n            return sph_kernel_quintic\n        elif kernel_name == 'wendland2':\n            return sph_kernel_wendland2\n        elif kernel_name == 'wendland4':\n            return sph_kernel_wendland4\n        elif kernel_name == 'wendland6':\n            return sph_kernel_wendland6\n        elif kernel_name == 'none':\n            return sph_kernel_dummy\n        else:\n            raise NotImplementedError\n\ncdef class ParticleDepositOperation:\n    # We assume each will allocate and define their own temporary storage\n    cdef kernel_func sph_kernel\n    cdef public object nvals\n    cdef public int update_values\n    cdef int process(self, int dim[3], int ipart, np.float64_t left_edge[3],\n                     np.float64_t dds[3], np.int64_t offset,\n                     np.float64_t ppos[3], np.float64_t[:] fields,\n                     np.int64_t domain_ind) except -1 nogil\n"
  },
  {
    "path": "yt/geometry/particle_deposit.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\n\"\"\"\nParticle Deposition onto Cells\n\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\nimport numpy as np\n\ncimport cython\nfrom libc.math cimport sqrt\n\nfrom yt.utilities.lib.fp_utils cimport *\n\nfrom .oct_container cimport Oct, OctInfo, OctreeContainer\n\nfrom yt.utilities.lib.misc_utilities import OnceIndirect\n\n\ncdef append_axes(np.ndarray arr, int naxes):\n    if arr.ndim == naxes:\n        return arr\n    arr = np.expand_dims(arr, axis=tuple(range(arr.ndim, naxes)))\n    return arr\n\ncdef class ParticleDepositOperation:\n    def __init__(self, nvals, kernel_name):\n        # nvals is a tuple containing the active dimensions of the\n        # grid to deposit onto and the number of grids,\n        # (nx, ny, nz, ngrids)\n        self.nvals = nvals\n        self.update_values = 0 # This is the default\n        self.sph_kernel = get_kernel_func(kernel_name)\n\n    def initialize(self, *args):\n        raise NotImplementedError\n\n    def finalize(self, *args):\n        raise NotImplementedError\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def process_octree(self, OctreeContainer octree,\n                     np.ndarray[np.int64_t, ndim=1] dom_ind,\n                     np.ndarray[np.float64_t, ndim=2] positions,\n                     fields = None, int domain_id = -1,\n                     int domain_offset = 0, lvlmax = None):\n        cdef int nf, i, j\n        if fields is None:\n            fields = []\n        nf = len(fields)\n        cdef np.float64_t[::cython.view.indirect, ::1] field_pointers\n        if nf > 0: field_pointers = OnceIndirect(fields)\n        cdef np.float64_t pos[3]\n        cdef np.float64_t[:] field_vals = np.empty(nf, dtype=\"float64\")\n        cdef int dims[3]\n        dims[0] = dims[1] = dims[2] = octree.nz\n        cdef OctInfo oi\n        cdef np.int64_t offset, moff\n        cdef Oct *oct\n        cdef np.int8_t use_lvlmax\n        moff = octree.get_domain_offset(domain_id + domain_offset)\n        if lvlmax is None:\n            use_lvlmax = False\n            lvlmax = []\n        else:\n            use_lvlmax = True\n        cdef np.ndarray[np.int32_t, ndim=1] lvlmaxval = np.asarray(lvlmax, dtype=np.int32)\n\n        for i in range(positions.shape[0]):\n            # We should check if particle remains inside the Oct here\n            for j in range(nf):\n                field_vals[j] = field_pointers[j,i]\n            for j in range(3):\n                pos[j] = positions[i, j]\n            # This line should be modified to have it return the index into an\n            # array based on whatever cutting of the domain we have done.  This\n            # may or may not include the domain indices that we have\n            # previously generated.  This way we can support not knowing the\n            # full octree structure.  All we *really* care about is some\n            # arbitrary offset into a field value for deposition.\n            if not use_lvlmax:\n                oct = octree.get(pos, &oi)\n            else:\n                oct = octree.get(pos, &oi, max_level=lvlmaxval[i])\n            # This next line is unfortunate.  Basically it says, sometimes we\n            # might have particles that belong to octs outside our domain.\n            # For the distributed-memory octrees, this will manifest as a NULL\n            # oct.  For the non-distributed memory octrees, we'll simply see\n            # this as a domain_id that is not the current domain id.  Note that\n            # this relies on the idea that all the particles in a region are\n            # all fed to sequential domain subsets, which will not be true with\n            # RAMSES, where we *will* miss particles that live in ghost\n            # regions on other processors.  Addressing this is on the TODO\n            # list.\n            if oct == NULL or (domain_id > 0 and oct.domain != domain_id):\n                continue\n            # Note that this has to be our local index, not our in-file index.\n            offset = dom_ind[oct.domain_ind - moff]\n            if offset < 0: continue\n            # Check that we found the oct ...\n            self.process(dims, i, oi.left_edge, oi.dds,\n                         offset, pos, field_vals, oct.domain_ind)\n            if self.update_values == 1:\n                for j in range(nf):\n                    field_pointers[j][i] = field_vals[j]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def process_grid(self, gobj,\n                     np.ndarray[np.float64_t, ndim=2, cast=True] positions,\n                     fields = None):\n        cdef int nf, i, j\n        if fields is None:\n            fields = []\n        if positions.shape[0] == 0: return\n        nf = len(fields)\n        cdef np.float64_t[:] field_vals = np.empty(nf, dtype=\"float64\")\n        cdef np.float64_t[::cython.view.indirect, ::1] field_pointers\n        if nf > 0: field_pointers = OnceIndirect(fields)\n        cdef np.float64_t pos[3]\n        cdef np.int64_t gid = getattr(gobj, \"id\", -1)\n        cdef np.float64_t dds[3]\n        cdef np.float64_t left_edge[3]\n        cdef np.float64_t right_edge[3]\n        cdef int dims[3]\n        for i in range(3):\n            dds[i] = gobj.dds[i]\n            left_edge[i] = gobj.LeftEdge[i]\n            right_edge[i] = gobj.RightEdge[i]\n            dims[i] = gobj.ActiveDimensions[i]\n        for i in range(positions.shape[0]):\n            # Now we process\n            for j in range(nf):\n                field_vals[j] = field_pointers[j,i]\n            for j in range(3):\n                pos[j] = positions[i, j]\n            continue_loop = False\n            for j in range(3):\n                if pos[j] < left_edge[j] or pos[j] > right_edge[j]:\n                    continue_loop = True\n            if continue_loop:\n                continue\n            self.process(dims, i, left_edge, dds, 0, pos, field_vals, gid)\n            if self.update_values == 1:\n                for j in range(nf):\n                    field_pointers[j][i] = field_vals[j]\n\n    cdef int process(self, int dim[3], int ipart, np.float64_t left_edge[3],\n                     np.float64_t dds[3], np.int64_t offset,\n                     np.float64_t ppos[3], np.float64_t[:] fields,\n                     np.int64_t domain_ind) except -1 nogil:\n        with gil:\n            raise NotImplementedError\n\ncdef class CountParticles(ParticleDepositOperation):\n    cdef np.int64_t[:,:,:,:] count\n    def initialize(self):\n        # Create a numpy array accessible to python\n        self.count = append_axes(\n            np.zeros(self.nvals, dtype=\"int64\", order='F'), 4)\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    cdef int process(self, int dim[3], int ipart,\n                     np.float64_t left_edge[3],\n                     np.float64_t dds[3],\n                     np.int64_t offset, # offset into IO field\n                     np.float64_t ppos[3], # this particle's position\n                     np.float64_t[:] fields,\n                     np.int64_t domain_ind\n                     ) except -1 nogil:\n        # here we do our thing; this is the kernel\n        cdef int ii[3]\n        cdef int i\n        for i in range(3):\n            ii[i] = <int>((ppos[i] - left_edge[i])/dds[i])\n        self.count[ii[2], ii[1], ii[0], offset] += 1\n        return 0\n\n    def finalize(self):\n        return np.asarray(self.count).reshape(self.nvals).astype(\"float64\")\n\ndeposit_count = CountParticles\n\ncdef class SimpleSmooth(ParticleDepositOperation):\n    # Note that this does nothing at the edges.  So it will give a poor\n    # estimate there, and since Octrees are mostly edges, this will be a very\n    # poor SPH kernel.\n    cdef np.float64_t[:,:,:,:] data\n    cdef np.float64_t[:,:,:,:] temp\n\n    def initialize(self):\n        self.data = append_axes(\n            np.zeros(self.nvals, dtype=\"float64\", order='F'), 4)\n        self.temp = append_axes(\n            np.zeros(self.nvals, dtype=\"float64\", order='F'), 4)\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    cdef int process(self, int dim[3], int ipart,\n                     np.float64_t left_edge[3],\n                     np.float64_t dds[3],\n                     np.int64_t offset,\n                     np.float64_t ppos[3],\n                     np.float64_t[:] fields,\n                     np.int64_t domain_ind\n                     ) except -1 nogil:\n        cdef int ii[3]\n        cdef int ib0[3]\n        cdef int ib1[3]\n        cdef int i, j, k, half_len\n        cdef np.float64_t idist[3]\n        cdef np.float64_t kernel_sum, dist\n        # Smoothing length is fields[0]\n        kernel_sum = 0.0\n        for i in range(3):\n            ii[i] = <int>((ppos[i] - left_edge[i])/dds[i])\n            half_len = <int>(fields[0]/dds[i]) + 1\n            ib0[i] = ii[i] - half_len\n            ib1[i] = ii[i] + half_len\n            if ib0[i] >= dim[i] or ib1[i] <0:\n                return 0\n            ib0[i] = iclip(ib0[i], 0, dim[i] - 1)\n            ib1[i] = iclip(ib1[i], 0, dim[i] - 1)\n        for i from ib0[0] <= i <= ib1[0]:\n            idist[0] = (ii[0] - i) * dds[0]\n            idist[0] *= idist[0]\n            for j from ib0[1] <= j <= ib1[1]:\n                idist[1] = (ii[1] - j) * dds[1]\n                idist[1] *= idist[1]\n                for k from ib0[2] <= k <= ib1[2]:\n                    idist[2] = (ii[2] - k) * dds[2]\n                    idist[2] *= idist[2]\n                    dist = idist[0] + idist[1] + idist[2]\n                    # Calculate distance in multiples of the smoothing length\n                    dist = sqrt(dist) / fields[0]\n                    with gil:\n                        self.temp[k,j,i,offset] = self.sph_kernel(dist)\n                    kernel_sum += self.temp[k,j,i,offset]\n        # Having found the kernel, deposit accordingly into gdata\n        for i from ib0[0] <= i <= ib1[0]:\n            for j from ib0[1] <= j <= ib1[1]:\n                for k from ib0[2] <= k <= ib1[2]:\n                    dist = self.temp[k,j,i,offset] / kernel_sum\n                    self.data[k,j,i,offset] += fields[1] * dist\n        return 0\n\n    def finalize(self):\n        return self.odata\n\ndeposit_simple_smooth = SimpleSmooth\n\ncdef class SumParticleField(ParticleDepositOperation):\n    cdef np.float64_t[:,:,:,:] sum\n    def initialize(self):\n        self.sum = append_axes(\n            np.zeros(self.nvals, dtype=\"float64\", order='F'), 4)\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    cdef int process(self, int dim[3], int ipart,\n                     np.float64_t left_edge[3],\n                     np.float64_t dds[3],\n                     np.int64_t offset,\n                     np.float64_t ppos[3],\n                     np.float64_t[:] fields,\n                     np.int64_t domain_ind\n                     ) except -1 nogil:\n        cdef int ii[3]\n        cdef int i\n        for i in range(3):\n            ii[i] = <int>((ppos[i] - left_edge[i]) / dds[i])\n        self.sum[ii[2], ii[1], ii[0], offset] += fields[0]\n        return 0\n\n    def finalize(self):\n        return np.asarray(self.sum).reshape(self.nvals)\n\ndeposit_sum = SumParticleField\n\ncdef class StdParticleField(ParticleDepositOperation):\n    # Thanks to Britton and MJ Turk for the link\n    # to a single-pass STD\n    # http://www.cs.berkeley.edu/~mhoemmen/cs194/Tutorials/variance.pdf\n    cdef np.float64_t[:,:,:,:] mk\n    cdef np.float64_t[:,:,:,:] qk\n    cdef np.float64_t[:,:,:,:] i\n    def initialize(self):\n        # we do this in a single pass, but need two scalar\n        # per cell, M_k, and Q_k and also the number of particles\n        # deposited into each one\n        # the M_k term\n        self.mk = append_axes(\n            np.zeros(self.nvals, dtype=\"float64\", order='F'), 4)\n        self.qk = append_axes(\n            np.zeros(self.nvals, dtype=\"float64\", order='F'), 4)\n        self.i = append_axes(\n            np.zeros(self.nvals, dtype=\"float64\", order='F'), 4)\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    cdef int process(self, int dim[3], int ipart,\n                     np.float64_t left_edge[3],\n                     np.float64_t dds[3],\n                     np.int64_t offset,\n                     np.float64_t ppos[3],\n                     np.float64_t[:] fields,\n                     np.int64_t domain_ind\n                     ) except -1 nogil:\n        cdef int ii[3]\n        cdef int i\n        cdef float k, mk, qk\n        for i in range(3):\n            ii[i] = <int>((ppos[i] - left_edge[i])/dds[i])\n        k = self.i[ii[2], ii[1], ii[0], offset]\n        mk = self.mk[ii[2], ii[1], ii[0], offset]\n        qk = self.qk[ii[2], ii[1], ii[0], offset]\n        if k == 0.0:\n            # Initialize cell values\n            self.mk[ii[2], ii[1], ii[0], offset] = fields[0]\n        else:\n            self.mk[ii[2], ii[1], ii[0], offset] = mk + (fields[0] - mk) / k\n            self.qk[ii[2], ii[1], ii[0], offset] = \\\n                qk + (k - 1.0) * (fields[0] - mk) * (fields[0] - mk) / k\n        self.i[ii[2], ii[1], ii[0], offset] += 1\n        return 0\n\n    def finalize(self):\n        # This is the standard variance\n        # if we want sample variance divide by (self.oi - 1.0)\n        i = np.asarray(self.i)\n        std2 = np.asarray(self.qk) / i\n        std2[i == 0.0] = 0.0\n        return np.sqrt(std2.reshape(self.nvals))\n\ndeposit_std = StdParticleField\n\ncdef class CICDeposit(ParticleDepositOperation):\n    cdef np.float64_t[:,:,:,:] field\n    cdef public object ofield\n    def initialize(self):\n        if not all(_ > 1 for _ in self.nvals[:-1]):\n            from yt.utilities.exceptions import YTBoundsDefinitionError\n            raise YTBoundsDefinitionError(\n                \"CIC requires minimum of 2 zones in all spatial dimensions.\",\n                self.nvals[:-1])\n        self.field = append_axes(\n            np.zeros(self.nvals, dtype=\"float64\", order='F'), 4)\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    cdef int process(self, int dim[3], int ipart,\n                     np.float64_t left_edge[3],\n                     np.float64_t dds[3],\n                     np.int64_t offset, # offset into IO field\n                     np.float64_t ppos[3], # this particle's position\n                     np.float64_t[:] fields,\n                     np.int64_t domain_ind\n                     ) except -1 nogil:\n\n        cdef int i, j, k\n        cdef int ind[3]\n        cdef np.float64_t rpos[3]\n        cdef np.float64_t rdds[3][2]\n\n        # Compute the position of the central cell\n        for i in range(3):\n            rpos[i] = (ppos[i]-left_edge[i])/dds[i]\n            rpos[i] = fclip(rpos[i], 0.5001, dim[i]-0.5001)\n            ind[i] = <int> (rpos[i] + 0.5)\n            # Note these are 1, then 0\n            rdds[i][1] = (<np.float64_t> ind[i]) + 0.5 - rpos[i]\n            rdds[i][0] = 1.0 - rdds[i][1]\n\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    self.field[ind[2] - k, ind[1] - j, ind[0] - i, offset] += \\\n                        fields[0]*rdds[0][i]*rdds[1][j]*rdds[2][k]\n\n        return 0\n\n    def finalize(self):\n        return np.asarray(self.field).reshape(self.nvals)\n\ndeposit_cic = CICDeposit\n\ncdef class WeightedMeanParticleField(ParticleDepositOperation):\n    # Deposit both mass * field and mass into two scalars\n    # then in finalize divide mass * field / mass\n    cdef np.float64_t[:,:,:,:] wf\n    cdef np.float64_t[:,:,:,:] w\n    def initialize(self):\n        self.wf = append_axes(\n            np.zeros(self.nvals, dtype='float64', order='F'), 4)\n        self.w = append_axes(\n            np.zeros(self.nvals, dtype='float64', order='F'), 4)\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    cdef int process(self, int dim[3], int ipart,\n                     np.float64_t left_edge[3],\n                     np.float64_t dds[3],\n                     np.int64_t offset,\n                     np.float64_t ppos[3],\n                     np.float64_t[:] fields,\n                     np.int64_t domain_ind\n                     ) except -1 nogil:\n        cdef int ii[3]\n        cdef int i\n        for i in range(3):\n            ii[i] = <int>((ppos[i] - left_edge[i]) / dds[i])\n        self.w[ii[2], ii[1], ii[0], offset] += fields[1]\n        self.wf[ii[2], ii[1], ii[0], offset] += fields[0] * fields[1]\n        return 0\n\n    def finalize(self):\n        wf = np.asarray(self.wf)\n        w = np.asarray(self.w)\n        with np.errstate(divide='ignore', invalid='ignore'):\n            rv = wf / w\n        return rv.reshape(self.nvals)\n\ndeposit_weighted_mean = WeightedMeanParticleField\n\ncdef class MeshIdentifier(ParticleDepositOperation):\n    # This is a tricky one!  What it does is put into the particle array the\n    # value of the oct or block (grids will always be zero) identifier that a\n    # given particle resides in\n    def initialize(self):\n        self.update_values = 1\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    cdef int process(self, int dim[3], int ipart,\n                      np.float64_t left_edge[3],\n                      np.float64_t dds[3],\n                      np.int64_t offset,\n                      np.float64_t ppos[3],\n                      np.float64_t[:] fields,\n                      np.int64_t domain_ind\n                      ) except -1 nogil:\n        fields[0] = domain_ind\n        return 0\n\n    def finalize(self):\n        return\n\ndeposit_mesh_id = MeshIdentifier\n\ncdef class CellIdentifier(ParticleDepositOperation):\n    cdef np.int64_t[:] indexes, cell_index\n    # This method stores the offset of the grid containing each particle\n    # and compute the index of the cell in the oct.\n    def initialize(self, int npart):\n        self.indexes = np.zeros(npart, dtype=np.int64, order='F') - 1\n        self.cell_index = np.zeros(npart, dtype=np.int64, order='F') - 1\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    cdef int process(self, int dim[3], int ipart,\n                      np.float64_t left_edge[3],\n                      np.float64_t dds[3],\n                      np.int64_t offset,\n                      np.float64_t ppos[3],\n                      np.float64_t[:] fields,\n                      np.int64_t domain_ind\n                      ) except -1 nogil:\n        cdef int i, icell\n        self.indexes[ipart] = offset\n\n        icell = 0\n        for i in range(3):\n            if ppos[i] > left_edge[i] + dds[i]:\n                icell |= 4 >> i\n\n        # Compute cell index\n        self.cell_index[ipart] = icell\n\n        return 0\n\n    def finalize(self):\n        return np.asarray(self.indexes), np.asarray(self.cell_index)\n\ndeposit_cell_id = CellIdentifier\n\ncdef class NNParticleField(ParticleDepositOperation):\n    cdef np.float64_t[:,:,:,:] nnfield\n    cdef np.float64_t[:,:,:,:] distfield\n    def initialize(self):\n        self.nnfield = append_axes(\n            np.zeros(self.nvals, dtype=\"float64\", order='F'), 4)\n        self.distfield = append_axes(\n            np.zeros(self.nvals, dtype=\"float64\", order='F'), 4)\n        self.distfield[:] = np.inf\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    cdef int process(self, int dim[3], int ipart,\n                     np.float64_t left_edge[3],\n                     np.float64_t dds[3],\n                     np.int64_t offset,\n                     np.float64_t ppos[3],\n                     np.float64_t[:] fields,\n                     np.int64_t domain_ind\n                     ) except -1 nogil:\n        # This one is a bit slow.  Every grid cell is going to be iterated\n        # over, and we're going to deposit particles in it.\n        cdef int i, j, k\n        cdef np.float64_t r2\n        cdef np.float64_t gpos[3]\n        gpos[0] = left_edge[0] + 0.5 * dds[0]\n        for i in range(dim[0]):\n            gpos[1] = left_edge[1] + 0.5 * dds[1]\n            for j in range(dim[1]):\n                gpos[2] = left_edge[2] + 0.5 * dds[2]\n                for k in range(dim[2]):\n                    r2 = ((ppos[0] - gpos[0])*(ppos[0] - gpos[0]) +\n                          (ppos[1] - gpos[1])*(ppos[1] - gpos[1]) +\n                          (ppos[2] - gpos[2])*(ppos[2] - gpos[2]))\n                    if r2 < self.distfield[k,j,i,offset]:\n                        self.distfield[k,j,i,offset] = r2\n                        self.nnfield[k,j,i,offset] = fields[0]\n                    gpos[2] += dds[2]\n                gpos[1] += dds[1]\n            gpos[0] += dds[0]\n        return 0\n\n    def finalize(self):\n        return np.asarray(self.nnfield).reshape(self.nvals)\n\ndeposit_nearest = NNParticleField\n"
  },
  {
    "path": "yt/geometry/particle_geometry_handler.py",
    "content": "import collections\nimport errno\nimport os\nimport struct\nimport weakref\n\nimport numpy as np\nfrom ewah_bool_utils.ewah_bool_wrap import BoolArrayCollection\n\nfrom yt.data_objects.index_subobjects.particle_container import ParticleContainer\nfrom yt.funcs import get_pbar, is_sequence, only_on_root\nfrom yt.geometry.geometry_handler import Index, YTDataChunk\nfrom yt.geometry.particle_oct_container import ParticleBitmap\nfrom yt.utilities.lib.fnv_hash import fnv_hash\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_objects\n\n\ndef validate_index_order(index_order):\n    if index_order is None:\n        index_order = (6, 2)\n    elif not is_sequence(index_order):\n        index_order = (int(index_order), 1)\n    else:\n        if len(index_order) != 2:\n            raise RuntimeError(\n                f\"Tried to load a dataset with index_order={index_order}, but \"\n                \"index_order\\nmust be an integer or a two-element tuple of \"\n                \"integers.\"\n            )\n        index_order = tuple(int(o) for o in index_order)\n    return index_order\n\n\nclass ParticleIndexInfo:\n    def __init__(self, order1, order2, filename, mutable_index):\n        self._order1 = order1\n        self._order2 = order2\n        self._order2_orig = order2\n        self.filename = filename\n        self.mutable_index = mutable_index\n        self._is_loaded = False\n\n    @property\n    def order1(self):\n        return self._order1\n\n    @property\n    def order2(self):\n        return self._order2\n\n    @order2.setter\n    def order2(self, value):\n        if value == self._order2:\n            # do nothing if nothing changes\n            return\n        mylog.debug(\"Updating index_order2 from %s to %s\", self._order2, value)\n        self._order2 = value\n\n    @property\n    def order2_orig(self):\n        return self._order2_orig\n\n\nclass ParticleIndex(Index):\n    \"\"\"The Index subclass for particle datasets\"\"\"\n\n    def __init__(self, ds, dataset_type):\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        self.float_type = np.float64\n        super().__init__(ds, dataset_type)\n        self._initialize_index()\n\n    def _setup_geometry(self):\n        self.regions = None\n\n    def get_smallest_dx(self):\n        \"\"\"\n        Returns (in code units) the smallest cell size in the simulation.\n        \"\"\"\n        return self.ds.arr(0, \"code_length\")\n\n    def _get_particle_type_counts(self):\n        result = collections.defaultdict(lambda: 0)\n        for df in self.data_files:\n            for k in df.total_particles.keys():\n                result[k] += df.total_particles[k]\n        return dict(result)\n\n    def convert(self, unit):\n        return self.dataset.conversion_factors[unit]\n\n    @property\n    def chunksize(self):\n        # This can be overridden in subclasses\n        return 64**3\n\n    _data_files = None\n\n    @property\n    def data_files(self):\n        if self._data_files is not None:\n            return self._data_files\n\n        self._setup_filenames()\n        return self._data_files\n\n    @data_files.setter\n    def data_files(self, value):\n        self._data_files = value\n\n    _total_particles = None\n\n    @property\n    def total_particles(self):\n        if self._total_particles is not None:\n            return self._total_particles\n\n        self._total_particles = sum(\n            sum(d.total_particles.values()) for d in self.data_files\n        )\n        return self._total_particles\n\n    def _setup_filenames(self):\n        template = self.dataset.filename_template\n        ndoms = self.dataset.file_count\n        cls = self.dataset._file_class\n        self.data_files = []\n        fi = 0\n        for i in range(int(ndoms)):\n            start = 0\n            if self.chunksize > 0:\n                end = start + self.chunksize\n            else:\n                end = None\n            while True:\n                try:\n                    _filename = template % {\"num\": i}\n                    df = cls(self.dataset, self.io, _filename, fi, (start, end))\n                except FileNotFoundError:\n                    mylog.warning(\n                        \"Failed to load '%s' (missing file or directory)\", _filename\n                    )\n                    break\n                if max(df.total_particles.values()) == 0:\n                    break\n                fi += 1\n                self.data_files.append(df)\n                if end is None:\n                    break\n                start = end\n                end += self.chunksize\n\n    def _initialize_index(self):\n        ds = self.dataset\n        only_on_root(\n            mylog.info,\n            \"Allocating for %0.4g particles\",\n            self.total_particles,\n            global_rootonly=True,\n        )\n\n        # if we have not yet set domain_left_edge and domain_right_edge then do\n        # an I/O pass over the particle coordinates to determine a bounding box\n        if self.ds.domain_left_edge is None:\n            min_ppos = np.empty(3, dtype=\"float64\")\n            min_ppos[:] = np.nan\n            max_ppos = np.empty(3, dtype=\"float64\")\n            max_ppos[:] = np.nan\n            only_on_root(\n                mylog.info,\n                \"Bounding box cannot be inferred from metadata, reading \"\n                \"particle positions to infer bounding box\",\n            )\n            for df in self.data_files:\n                for _, ppos in self.io._yield_coordinates(df):\n                    min_ppos = np.nanmin(np.vstack([min_ppos, ppos]), axis=0)\n                    max_ppos = np.nanmax(np.vstack([max_ppos, ppos]), axis=0)\n            only_on_root(\n                mylog.info,\n                f\"Load this dataset with bounding_box=[{min_ppos}, {max_ppos}] \"\n                \"to avoid I/O overhead from inferring bounding_box.\",\n            )\n            ds.domain_left_edge = ds.arr(1.05 * min_ppos, \"code_length\")\n            ds.domain_right_edge = ds.arr(1.05 * max_ppos, \"code_length\")\n            ds.domain_width = ds.domain_right_edge - ds.domain_left_edge\n\n        # use a trivial morton index for datasets containing a single chunk\n        if len(self.data_files) == 1:\n            order1 = 1\n            order2 = 1\n            mutable_index = False\n        else:\n            mutable_index = ds.index_order is None\n            index_order = validate_index_order(ds.index_order)\n            order1 = index_order[0]\n            order2 = index_order[1]\n\n        if order1 == 1 and order2 == 1:\n            dont_cache = True\n        else:\n            dont_cache = False\n\n        if not hasattr(self.ds, \"_file_hash\"):\n            self.ds._file_hash = self._generate_hash()\n\n        # Load Morton index from file if provided\n        fname = getattr(ds, \"index_filename\", None) or f\"{ds.parameter_filename}.ewah\"\n\n        self.pii = ParticleIndexInfo(order1, order2, fname, mutable_index)\n\n        self.regions = ParticleBitmap(\n            ds.domain_left_edge,\n            ds.domain_right_edge,\n            ds.periodicity,\n            self.ds._file_hash,\n            len(self.data_files),\n            index_order1=self.pii.order1,\n            index_order2=self.pii.order2_orig,\n        )\n\n        dont_load = dont_cache and not hasattr(ds, \"index_filename\")\n        try:\n            if dont_load:\n                raise OSError\n            rflag, max_hsml = self.regions.load_bitmasks(fname)\n            if max_hsml > 0.0 and self.pii.mutable_index:\n                self._order2_update(max_hsml)\n            rflag = self.regions.check_bitmasks()\n            self._initialize_frontend_specific()\n            if rflag == 0:\n                raise OSError\n            self.pii._is_loaded = True\n        except (OSError, struct.error):\n            self.regions.reset_bitmasks()\n            max_hsml = self._initialize_coarse_index()\n            self._initialize_refined_index()\n            wdir = os.path.dirname(fname)\n            if not dont_cache and os.access(wdir, os.W_OK):\n                # Sometimes os mis-reports whether a directory is writable,\n                # So pass if writing the bitmask file fails.\n                try:\n                    self.regions.save_bitmasks(fname, max_hsml)\n                except OSError:\n                    pass\n            rflag = self.regions.check_bitmasks()\n\n        self.ds.index_order = (self.pii.order1, self.pii.order2)\n\n    def _order2_update(self, max_hsml):\n        # By passing this in, we only allow index_order2 to be increased by\n        # two at most, never increased.  One place this becomes particularly\n        # useful is in the case of an extremely small section of gas\n        # particles embedded in a much much larger domain.  The max\n        # smoothing length will be quite small, so based on the larger\n        # domain, it will correspond to a very very high index order, which\n        # is a large amount of memory!  Having multiple indexes, one for\n        # each particle type, would fix this.\n        new_order2 = self.regions.update_mi2(max_hsml, self.pii.order2 + 2)\n        self.pii.order2 = new_order2\n\n    def _initialize_coarse_index(self):\n        max_hsml = 0.0\n        pb = get_pbar(\"Initializing coarse index \", len(self.data_files))\n        for i, data_file in parallel_objects(enumerate(self.data_files)):\n            pb.update(i + 1)\n            for ptype, pos in self.io._yield_coordinates(data_file):\n                ds = self.ds\n                if hasattr(ds, \"_sph_ptypes\") and ptype == ds._sph_ptypes[0]:\n                    hsml = self.io._get_smoothing_length(\n                        data_file, pos.dtype, pos.shape\n                    )\n                    if hsml is not None and hsml.size > 0.0:\n                        max_hsml = max(max_hsml, hsml.max())\n                else:\n                    hsml = None\n                self.regions._coarse_index_data_file(pos, hsml, data_file.file_id)\n        pb.finish()\n        self.regions.masks = self.comm.mpi_allreduce(self.regions.masks, op=\"sum\")\n        self.regions.particle_counts = self.comm.mpi_allreduce(\n            self.regions.particle_counts, op=\"sum\"\n        )\n        for data_file in self.data_files:\n            self.regions._set_coarse_index_data_file(data_file.file_id)\n        self.regions.find_collisions_coarse()\n        if max_hsml > 0.0 and self.pii.mutable_index:\n            self._order2_update(max_hsml)\n        return max_hsml\n\n    def _initialize_refined_index(self):\n        mask = self.regions.masks.sum(axis=1).astype(\"uint8\")\n        max_npart = max(sum(d.total_particles.values()) for d in self.data_files) * 28\n        sub_mi1 = np.zeros(max_npart, \"uint64\")\n        sub_mi2 = np.zeros(max_npart, \"uint64\")\n        pb = get_pbar(\"Initializing refined index\", len(self.data_files))\n        mask_threshold = getattr(self, \"_index_mask_threshold\", 2)\n        count_threshold = getattr(self, \"_index_count_threshold\", 256)\n        mylog.debug(\n            \"Using estimated thresholds of %s and %s for refinement\",\n            mask_threshold,\n            count_threshold,\n        )\n        total_refined = 0\n        total_coarse_refined = (\n            (mask >= 2) & (self.regions.particle_counts > count_threshold)\n        ).sum()\n        mylog.debug(\n            \"This should produce roughly %s zones, for %s of the domain\",\n            total_coarse_refined,\n            100 * total_coarse_refined / mask.size,\n        )\n        storage = {}\n        for sto, (i, data_file) in parallel_objects(\n            enumerate(self.data_files), storage=storage\n        ):\n            coll = None\n            pb.update(i + 1)\n            nsub_mi = 0\n            for ptype, pos in self.io._yield_coordinates(data_file):\n                if pos.size == 0:\n                    continue\n                if hasattr(self.ds, \"_sph_ptypes\") and ptype == self.ds._sph_ptypes[0]:\n                    hsml = self.io._get_smoothing_length(\n                        data_file, pos.dtype, pos.shape\n                    )\n                else:\n                    hsml = None\n                nsub_mi, coll = self.regions._refined_index_data_file(\n                    coll,\n                    pos,\n                    hsml,\n                    mask,\n                    sub_mi1,\n                    sub_mi2,\n                    data_file.file_id,\n                    nsub_mi,\n                    count_threshold=count_threshold,\n                    mask_threshold=mask_threshold,\n                )\n                total_refined += nsub_mi\n            sto.result_id = i\n            if coll is None:\n                coll_str = b\"\"\n            else:\n                coll_str = coll.dumps()\n            sto.result = (data_file.file_id, coll_str)\n        pb.finish()\n        for i in sorted(storage):\n            file_id, coll_str = storage[i]\n            coll = BoolArrayCollection()\n            coll.loads(coll_str)\n            self.regions.bitmasks.append(file_id, coll)\n        self.regions.find_collisions_refined()\n\n    def _detect_output_fields(self):\n        # TODO: Add additional fields\n        dsl = []\n        units = {}\n        pcounts = self._get_particle_type_counts()\n        field_cache = {}\n        for dom in self.data_files:\n            if dom.filename in field_cache:\n                fl, _units = field_cache[dom.filename]\n            else:\n                fl, _units = self.io._identify_fields(dom)\n                field_cache[dom.filename] = fl, _units\n            units.update(_units)\n            dom._calculate_offsets(fl, pcounts)\n            for f in fl:\n                if f not in dsl:\n                    dsl.append(f)\n        self.field_list = dsl\n        ds = self.dataset\n        ds.particle_types = tuple({pt for pt, ds in dsl})\n        # This is an attribute that means these particle types *actually*\n        # exist.  As in, they are real, in the dataset.\n        ds.field_units.update(units)\n        ds.particle_types_raw = ds.particle_types\n\n    def _identify_base_chunk(self, dobj):\n        # Must check that chunk_info contains the right number of ghost zones\n        if getattr(dobj, \"_chunk_info\", None) is None:\n            if isinstance(dobj, ParticleContainer):\n                dobj._chunk_info = [dobj]\n            else:\n                # TODO: only return files\n                if getattr(dobj.selector, \"is_all_data\", False):\n                    nfiles = self.regions.nfiles\n                    dfi = np.arange(nfiles)\n                else:\n                    dfi, file_masks, addfi = self.regions.identify_file_masks(\n                        dobj.selector\n                    )\n                    nfiles = len(file_masks)\n                dobj._chunk_info = [None for _ in range(nfiles)]\n\n                # The following was moved here from ParticleContainer in order\n                # to make the ParticleContainer object pickleable. By having\n                # the base_selector as its own argument, we avoid having to\n                # rebuild the index on unpickling a ParticleContainer.\n                if hasattr(dobj, \"base_selector\"):\n                    base_selector = dobj.base_selector\n                    base_region = dobj.base_region\n                else:\n                    base_region = dobj\n                    base_selector = dobj.selector\n\n                for i in range(nfiles):\n                    domain_id = i + 1\n                    dobj._chunk_info[i] = ParticleContainer(\n                        base_region,\n                        base_selector,\n                        [self.data_files[dfi[i]]],\n                        domain_id=domain_id,\n                    )\n                # NOTE: One fun thing about the way IO works is that it\n                # consolidates things quite nicely.  So we should feel free to\n                # create as many objects as part of the chunk as we want, since\n                # it'll take the set() of them.  So if we break stuff up like\n                # this here, we end up in a situation where we have the ability\n                # to break things down further later on for buffer zones and the\n                # like.\n        (dobj._current_chunk,) = self._chunk_all(dobj)\n\n    def _chunk_all(self, dobj):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        yield YTDataChunk(dobj, \"all\", oobjs, None)\n\n    def _chunk_spatial(self, dobj, ngz, sort=None, preload_fields=None):\n        sobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for og in sobjs:\n            with og._expand_data_files():\n                if ngz > 0:\n                    g = og.retrieve_ghost_zones(ngz, [], smoothed=True)\n                else:\n                    g = og\n                yield YTDataChunk(dobj, \"spatial\", [g])\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for container in oobjs:\n            yield YTDataChunk(dobj, \"io\", [container], None, cache=cache)\n\n    def _generate_hash(self):\n        # Generate an FNV hash by creating a byte array containing the\n        # modification time of as well as the first and last 1 MB of data in\n        # every output file\n        ret = bytearray()\n        for pfile in self.data_files:\n            # only look at \"real\" files, not \"fake\" files generated by the\n            # chunking system\n            if pfile.start not in (0, None):\n                continue\n            try:\n                mtime = os.path.getmtime(pfile.filename)\n            except OSError as e:\n                if e.errno == errno.ENOENT:\n                    # this is an in-memory file so we return with a dummy\n                    # value\n                    return -1\n                else:\n                    raise\n            ret.extend(str(mtime).encode(\"utf-8\"))\n            size = os.path.getsize(pfile.filename)\n            if size > 1e6:\n                size = int(1e6)\n            with open(pfile.filename, \"rb\") as fh:\n                # read in first and last 1 MB of data\n                data = fh.read(size)\n                fh.seek(-size, os.SEEK_END)\n                data = fh.read(size)\n                ret.extend(data)\n            return fnv_hash(ret)\n\n    def _initialize_frontend_specific(self):\n        \"\"\"This is for frontend-specific initialization code\n\n        If there are frontend-specific things that need to be set while\n        creating the index, this function forces these operations to happen\n        in cases where we are reloading the index from a sidecar file.\n        \"\"\"\n        pass\n"
  },
  {
    "path": "yt/geometry/particle_oct_container.pyx",
    "content": "# distutils: language = c++\n# distutils: extra_compile_args = CPP14_FLAG\n# distutils: include_dirs = LIB_DIR\n# distutils: libraries = EWAH_LIBS\n\"\"\"\nOct container tuned for Particles\n\n\n\n\"\"\"\n\n\nfrom ewah_bool_utils.ewah_bool_array cimport (\n    bool_array,\n    ewah_bool_array,\n    ewah_bool_iterator,\n    ewah_word_type,\n)\nfrom libc.math cimport ceil, log2\nfrom libc.stdlib cimport free, malloc\nfrom libcpp.map cimport map as cmap\nfrom libcpp.vector cimport vector\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom cpython.exc cimport PyErr_CheckSignals\nfrom cython.operator cimport dereference, preincrement\n\nfrom yt.geometry cimport oct_visitors\nfrom yt.utilities.lib.fnv_hash cimport c_fnv_hash as fnv_hash\nfrom yt.utilities.lib.fp_utils cimport *\nfrom yt.utilities.lib.geometry_utils cimport (\n    bounded_morton,\n    bounded_morton_dds,\n    bounded_morton_split_dds,\n    bounded_morton_split_relative_dds,\n    decode_morton_64bit,\n    encode_morton_64bit,\n    morton_neighbors_coarse,\n    morton_neighbors_refined,\n)\n\nfrom .oct_container cimport (\n    ORDER_MAX,\n    Oct,\n    OctKey,\n    OctreeContainer,\n    SparseOctreeContainer,\n)\nfrom .oct_visitors cimport cind\nfrom .selection_routines cimport AlwaysSelector, SelectorObject\n\nfrom yt.funcs import get_pbar\n\nfrom ewah_bool_utils.ewah_bool_wrap cimport BoolArrayCollection\n\nimport os\n\nfrom ewah_bool_utils.ewah_bool_wrap cimport (\n    BoolArrayCollectionUncompressed as BoolArrayColl,\n    FileBitmasks,\n    SparseUnorderedRefinedBitmaskSet as SparseUnorderedRefinedBitmask,\n)\n\n\n_bitmask_version = np.uint64(5)\n\n\n\nctypedef cmap[np.uint64_t, bool_array] CoarseRefinedSets\n\ncdef class ParticleOctreeContainer(OctreeContainer):\n    cdef Oct** oct_list\n    #The starting oct index of each domain\n    cdef np.int64_t *dom_offsets\n    #How many particles do we keep before refining\n    cdef public int n_ref\n\n    def allocate_root(self):\n        cdef int i, j, k\n        cdef Oct *cur\n        for i in range(self.nn[0]):\n            for j in range(self.nn[1]):\n                for k in range(self.nn[2]):\n                    cur = self.allocate_oct()\n                    self.root_mesh[i][j][k] = cur\n\n    def __dealloc__(self):\n        #Call the freemem ops on every ocy\n        #of the root mesh recursively\n        cdef int i, j, k\n        if self.root_mesh == NULL: return\n        for i in range(self.nn[0]):\n            if self.root_mesh[i] == NULL: continue\n            for j in range(self.nn[1]):\n                if self.root_mesh[i][j] == NULL: continue\n                for k in range(self.nn[2]):\n                    if self.root_mesh[i][j][k] == NULL: continue\n                    self.visit_free(self.root_mesh[i][j][k])\n        free(self.oct_list)\n        free(self.dom_offsets)\n\n    cdef void visit_free(self, Oct *o):\n        #Free the memory for this oct recursively\n        cdef int i, j, k\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    if o.children != NULL \\\n                       and o.children[cind(i,j,k)] != NULL:\n                        self.visit_free(o.children[cind(i,j,k)])\n        free(o.children)\n        free(o)\n\n    def clear_fileind(self):\n        cdef int i, j, k\n        for i in range(self.nn[0]):\n            for j in range(self.nn[1]):\n                for k in range(self.nn[2]):\n                    self.visit_clear(self.root_mesh[i][j][k])\n\n    cdef void visit_clear(self, Oct *o):\n        #Free the memory for this oct recursively\n        cdef int i, j, k\n        o.file_ind = 0\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    if o.children != NULL \\\n                       and o.children[cind(i,j,k)] != NULL:\n                        self.visit_clear(o.children[cind(i,j,k)])\n\n    def __iter__(self):\n        #Get the next oct, will traverse domains\n        #Note that oct containers can be sorted\n        #so that consecutive octs are on the same domain\n        cdef int oi\n        cdef Oct *o\n        for oi in range(self.nocts):\n            o = self.oct_list[oi]\n            yield (o.file_ind, o.domain_ind, o.domain)\n\n    def allocate_domains(self, domain_counts):\n        pass\n\n    def finalize(self, int domain_id = 0):\n        #This will sort the octs in the oct list\n        #so that domains appear consecutively\n        #And then find the oct index/offset for\n        #every domain\n        cdef int max_level = 0\n        self.oct_list = <Oct**> malloc(sizeof(Oct*)*self.nocts)\n        cdef np.int64_t i = 0, lpos = 0\n        # Note that we now assign them in the same order they will be visited\n        # by recursive visitors.\n        for i in range(self.nn[0]):\n            for j in range(self.nn[1]):\n                for k in range(self.nn[2]):\n                    self.visit_assign(self.root_mesh[i][j][k], &lpos,\n                                      0, &max_level)\n        assert(lpos == self.nocts)\n        for i in range(self.nocts):\n            self.oct_list[i].domain_ind = i\n            self.oct_list[i].domain = domain_id\n        self.max_level = max_level\n\n    cdef visit_assign(self, Oct *o, np.int64_t *lpos, int level, int *max_level):\n        cdef int i, j, k\n        self.oct_list[lpos[0]] = o\n        lpos[0] += 1\n        max_level[0] = imax(max_level[0], level)\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    if o.children != NULL \\\n                       and o.children[cind(i,j,k)] != NULL:\n                        self.visit_assign(o.children[cind(i,j,k)], lpos,\n                                level + 1, max_level)\n        return\n\n    cdef np.int64_t get_domain_offset(self, int domain_id):\n        return 0\n\n    cdef Oct* allocate_oct(self):\n        #Allocate the memory, set to NULL or -1\n        #We reserve space for n_ref particles, but keep\n        #track of how many are used with np initially 0\n        self.nocts += 1\n        cdef Oct *my_oct = <Oct*> malloc(sizeof(Oct))\n        my_oct.domain = -1\n        my_oct.file_ind = 0\n        my_oct.domain_ind = self.nocts - 1\n        my_oct.children = NULL\n        return my_oct\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def add(self, np.ndarray[np.uint64_t, ndim=1] indices,\n            np.uint8_t order = ORDER_MAX):\n        #Add this particle to the root oct\n        #Then if that oct has children, add it to them recursively\n        #If the child needs to be refined because of max particles, do so\n        cdef np.int64_t no = indices.shape[0], p\n        cdef np.uint64_t index\n        cdef int i, level\n        cdef int ind[3]\n        if self.root_mesh[0][0][0] == NULL: self.allocate_root()\n        cdef np.uint64_t *data = <np.uint64_t *> indices.data\n        for p in range(no):\n            # We have morton indices, which means we choose left and right by\n            # looking at (MAX_ORDER - level) & with the values 1, 2, 4.\n            level = 0\n            index = indices[p]\n            if index == FLAG:\n                # This is a marker for the index not being inside the domain\n                # we're interested in.\n                continue\n            # Convert morton index to 3D index of octree root\n            for i in range(3):\n                ind[i] = (index >> ((order - level)*3 + (2 - i))) & 1\n            cur = self.root_mesh[ind[0]][ind[1]][ind[2]]\n            if cur == NULL:\n                raise RuntimeError\n            # Continue refining the octree until you reach the level of the\n            # morton indexing order. Along the way, use prefix to count\n            # previous indices at levels in the octree?\n            while (cur.file_ind + 1) > self.n_ref:\n                if level >= order: break # Just dump it here.\n                level += 1\n                for i in range(3):\n                    ind[i] = (index >> ((order - level)*3 + (2 - i))) & 1\n                if cur.children == NULL or \\\n                   cur.children[cind(ind[0],ind[1],ind[2])] == NULL:\n                    cur = self.refine_oct(cur, index, level, order)\n                    self.filter_particles(cur, data, p, level, order)\n                else:\n                    cur = cur.children[cind(ind[0],ind[1],ind[2])]\n            # If our n_ref is 1, we are always refining, which means we're an\n            # index octree.  In this case, we should store the index for fast\n            # lookup later on when we find neighbors and the like.\n            if self.n_ref == 1:\n                cur.file_ind = index\n            else:\n                cur.file_ind += 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef Oct *refine_oct(self, Oct *o, np.uint64_t index, int level,\n                         np.uint8_t order):\n        #Allocate and initialize child octs\n        #Attach particles to child octs\n        #Remove particles from this oct entirely\n        cdef int i, j, k\n        cdef int ind[3]\n        cdef Oct *noct\n        # TODO: This does not need to be changed.\n        o.children = <Oct **> malloc(sizeof(Oct *)*8)\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    noct = self.allocate_oct()\n                    noct.domain = o.domain\n                    noct.file_ind = 0\n                    o.children[cind(i,j,k)] = noct\n        o.file_ind = self.n_ref + 1\n        for i in range(3):\n            ind[i] = (index >> ((order - level)*3 + (2 - i))) & 1\n        noct = o.children[cind(ind[0],ind[1],ind[2])]\n        return noct\n\n    cdef void filter_particles(self, Oct *o, np.uint64_t *data, np.int64_t p,\n                               int level, np.uint8_t order):\n        # Now we look at the last nref particles to decide where they go.\n        # If p: Loops over all previous morton indices\n        # If n_ref: Loops over n_ref previous morton indices\n        cdef int n = imin(p, self.n_ref)\n        cdef np.uint64_t *arr = data + imax(p - self.n_ref, 0)\n        cdef np.uint64_t prefix1, prefix2\n        # Now we figure out our prefix, which is the oct address at this level.\n        # As long as we're actually in Morton order, we do not need to worry\n        # about *any* of the other children of the oct.\n        prefix1 = data[p] >> (order - level)*3\n        for i in range(n):\n            prefix2 = arr[i] >> (order - level)*3\n            if (prefix1 == prefix2):\n                o.file_ind += 1 # Says how many morton indices are in this octant?\n\n    def recursively_count(self):\n        #Visit every cell, accumulate the # of cells per level\n        cdef int i, j, k\n        cdef np.int64_t counts[128]\n        for i in range(128): counts[i] = 0\n        for i in range(self.nn[0]):\n            for j in range(self.nn[1]):\n                for k in range(self.nn[2]):\n                    if self.root_mesh[i][j][k] != NULL:\n                        self.visit(self.root_mesh[i][j][k], counts)\n        level_counts = {}\n        for i in range(128):\n            if counts[i] == 0: break\n            level_counts[i] = counts[i]\n        return level_counts\n\n    cdef visit(self, Oct *o, np.int64_t *counts, level = 0):\n        cdef int i, j, k\n        counts[level] += 1\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    if o.children != NULL \\\n                       and o.children[cind(i,j,k)] != NULL:\n                        self.visit(o.children[cind(i,j,k)], counts, level + 1)\n        return\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef Oct *get_from_index(self, np.uint64_t mi, np.uint8_t order = ORDER_MAX,\n                             int max_level = 99):\n        cdef Oct *cur\n        cdef Oct *next\n        cur = next = NULL\n        cdef int i\n        cdef np.int64_t level = -1\n        cdef int ind32[3]\n        cdef np.uint64_t ind[3]\n        cdef np.uint64_t index\n        # Get level offset\n        cdef int level_offset[3]\n        for i in range(3):\n            level_offset[i] = np.log2(self.nn[i])\n            if (1 << level_offset[i]) != self.nn[i]:\n                raise Exception(\"Octree does not have roots along dimension {} in a power of 2 \".format(i))\n        for i in range(2,3):\n            if level_offset[i] != level_offset[0]:\n                raise Exception(\"Octree must have the same number of roots in each dimension for this.\")\n        # Get root for index\n        index = (mi >> ((order - level_offset[0])*3))\n        decode_morton_64bit(index, ind)\n        for i in range(3):\n            ind32[i] = ind[i]\n        self.get_root(ind32, &next)\n        # We want to stop recursing when there's nowhere else to go\n        level = level_offset[0]\n        max_level = min(max_level, order)\n        while next != NULL and level <= max_level:\n            level += 1\n            for i in range(3):\n                ind[i] = (mi >> ((order - level)*3 + (2 - i))) & 1\n            cur = next\n            if cur.children != NULL:\n                next = cur.children[cind(ind[0],ind[1],ind[2])]\n            else:\n                next = NULL\n        return cur\n\n    def apply_domain(self, int domain_id, BoolArrayCollection mask,\n                     int masklevel):\n        cdef SelectorObject selector = AlwaysSelector(None)\n        ind = self.domain_ind(selector, mask = mask, masklevel = masklevel)\n        for i in range(self.nocts):\n            if ind[i] < 0: continue\n            self.oct_list[i].domain = domain_id\n        super(ParticleOctreeContainer,self).domain_ind(selector, domain_id = domain_id)\n\n    def domain_ind(self, selector, int domain_id = -1,\n                   BoolArrayCollection mask = None, int masklevel = 99):\n        if mask is None:\n            return super(ParticleOctreeContainer,self).domain_ind(selector, domain_id = domain_id)\n        # Create mask for octs that are touched by the mask\n        cdef ewah_bool_array *ewah_slct = <ewah_bool_array *> mask.ewah_keys\n        cdef ewah_bool_iterator *iter_set = new ewah_bool_iterator(ewah_slct[0].begin())\n        cdef ewah_bool_iterator *iter_end = new ewah_bool_iterator(ewah_slct[0].end())\n        cdef np.ndarray[np.uint8_t, ndim=1] oct_mask\n        oct_mask = np.zeros(self.nocts, 'uint8')\n        cdef Oct *o\n        cdef int coct, cmi\n        coct = cmi = 0\n        while iter_set[0] != iter_end[0]:\n            mi = dereference(iter_set[0])\n            o = self.get_from_index(mi, order = masklevel)\n            if o != NULL:\n                _mask_children(oct_mask, o)\n                coct += 1\n            cmi += 1\n            preincrement(iter_set[0])\n        # Get domain ind\n        cdef np.ndarray[np.int64_t, ndim=1] ind\n        ind = np.zeros(self.nocts, 'int64') - 1\n        cdef oct_visitors.MaskedIndexOcts visitor\n        visitor = oct_visitors.MaskedIndexOcts(self, domain_id)\n        visitor.oct_index = ind\n        visitor.oct_mask = oct_mask\n        self.visit_all_octs(selector, visitor)\n        return ind\n\ncdef void _mask_children(np.ndarray[np.uint8_t] mask, Oct *cur):\n    cdef int i, j, k\n    if cur == NULL:\n        return\n    mask[cur.domain_ind] = 1\n    if cur.children == NULL:\n        return\n    for i in range(2):\n        for j in range(2):\n            for k in range(2):\n                _mask_children(mask, cur.children[cind(i,j,k)])\n\ncdef np.uint64_t ONEBIT=1\ncdef np.uint64_t FLAG = ~(<np.uint64_t>0)\n\ncdef class ParticleBitmap:\n    cdef np.float64_t left_edge[3]\n    cdef np.float64_t right_edge[3]\n    cdef np.uint8_t periodicity[3]\n    cdef np.float64_t dds[3]\n    cdef np.float64_t dds_mi1[3]\n    cdef np.float64_t dds_mi2[3]\n    cdef np.float64_t idds[3]\n    cdef np.int32_t dims[3]\n    cdef np.int64_t file_hash\n    cdef np.uint64_t directional_max2[3]\n    cdef np.int64_t hash_value\n    cdef public np.uint64_t nfiles\n    cdef public np.int32_t index_order1\n    cdef public np.int32_t index_order2\n    cdef public object masks\n    cdef public object particle_counts\n    cdef public object counts\n    cdef public object max_count\n    cdef public object _last_selector\n    cdef public object _last_return_values\n    cdef public object _cached_octrees\n    cdef public object _last_octree_subset\n    cdef public object _last_oct_handler\n    cdef public object _prev_octree_subset\n    cdef public object _prev_oct_handler\n    cdef np.uint32_t *file_markers\n    cdef np.uint64_t n_file_markers\n    cdef np.uint64_t file_marker_i\n    cdef public FileBitmasks bitmasks\n    cdef public BoolArrayCollection collisions\n    cdef public int _used_mi2\n\n    def __init__(self, left_edge, right_edge, periodicity, file_hash, nfiles,\n                 index_order1, index_order2):\n        # TODO: Set limit on maximum orders?\n        cdef int i\n        self._cached_octrees = {}\n        self._last_selector = None\n        self._last_return_values = None\n        self._last_octree_subset = None\n        self._last_oct_handler = None\n        self._prev_octree_subset = None\n        self._prev_oct_handler = None\n        self.file_hash = file_hash\n        self.nfiles = nfiles\n        for i in range(3):\n            self.left_edge[i] = left_edge[i]\n            self.right_edge[i] = right_edge[i]\n            self.periodicity[i] = <np.uint8_t>periodicity[i]\n            self.dims[i] = (1<<index_order1)\n            self.dds[i] = (right_edge[i] - left_edge[i])/self.dims[i]\n            self.idds[i] = 1.0/self.dds[i]\n            self.dds_mi1[i] = (right_edge[i] - left_edge[i]) / (1<<index_order1)\n        # We use 64-bit masks\n        self._used_mi2 = 0\n        self.index_order1 = index_order1\n        self._update_mi2(index_order2)\n        # This will be an on/off flag for which morton index values are touched\n        # by particles.\n        # This is the simple way, for now.\n        self.masks = np.zeros((1 << (index_order1 * 3), nfiles), dtype=\"uint8\")\n        self.particle_counts = np.zeros(1 << (index_order1 * 3), dtype=\"uint64\")\n        self.bitmasks = FileBitmasks(self.nfiles)\n        self.collisions = BoolArrayCollection()\n        hash_data = bytearray()\n        hash_data.extend(self.file_hash.to_bytes(8, \"little\", signed=True))\n        hash_data.extend(np.array(self.left_edge).tobytes())\n        hash_data.extend(np.array(self.right_edge).tobytes())\n        hash_data.extend(np.array(self.periodicity).tobytes())\n        hash_data.extend(self.nfiles.to_bytes(8, \"little\", signed=False))\n        hash_data.extend(self.index_order1.to_bytes(4, \"little\", signed=True))\n        hash_data.extend(self.index_order2.to_bytes(4, \"little\", signed=True))\n        self.hash_value = fnv_hash(hash_data)\n\n    def _bitmask_logicaland(self, ifile, bcoll, out):\n        self.bitmasks._logicaland(ifile, bcoll, out)\n\n    def _bitmask_intersects(self, ifile, bcoll):\n        return self.bitmasks._intersects(ifile, bcoll)\n\n    def update_mi2(self, np.float64_t characteristic_size,\n                   np.uint64_t max_index_order2 = 6):\n        \"\"\"\n        mi2 is the *refined* morton index order; mi2 is thus the definition of\n        the size of the refined index objects we stick inside any collisions at\n        the coarse level.  This takes a characteristic size and attempts to\n        compute the mi2 such that the cell is roughly equivalent to the\n        characteristic size.  It will return whether or not it was able to\n        update; if the mi2 has already been used, it does not update.\n        There are cases where the maximum index_order2 that it would compute\n        would be extremely fine, which can do really bad things to memory.  So\n        we allow the setting of a maximum value, which it won't exceed.\n        \"\"\"\n        if self._used_mi2 > 0:\n            return self.index_order2\n        cdef np.uint64_t index_order2 = 2\n        for i in range(3):\n            # Note we're casting to signed here, to avoid negative issues.\n            if self.dds_mi1[i] < characteristic_size: continue\n            index_order2 = max(index_order2, <np.uint64_t> ceil(log2(self.dds_mi1[i] / characteristic_size)))\n        index_order2 = i64min(max_index_order2, index_order2)\n        self._update_mi2(index_order2)\n        return self.index_order2\n\n    cdef void _update_mi2(self, np.uint64_t index_order2):\n        self.index_order2 = index_order2\n        mi2_max = (1 << self.index_order2) - 1\n        self.directional_max2[0] = encode_morton_64bit(mi2_max, 0, 0)\n        self.directional_max2[1] = encode_morton_64bit(0, mi2_max, 0)\n        self.directional_max2[2] = encode_morton_64bit(0, 0, mi2_max)\n        for i in range(3):\n            self.dds_mi2[i] = self.dds_mi1[i] / (1<<index_order2)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def _coarse_index_data_file(self,\n                                np.ndarray[cython.floating, ndim=2] pos,\n                                np.ndarray[cython.floating, ndim=1] hsml,\n                                np.uint64_t file_id):\n        return self.__coarse_index_data_file(pos, hsml, file_id)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void __coarse_index_data_file(self,\n                                       np.ndarray[cython.floating, ndim=2] pos,\n                                       np.ndarray[cython.floating, ndim=1] hsml,\n                                       np.uint64_t file_id) except *:\n        # Initialize\n        cdef np.int64_t i, p\n        cdef np.uint64_t mi, miex\n        cdef np.uint64_t mi_split[3]\n        cdef np.float64_t ppos[3]\n        cdef np.float64_t s_ppos[3] # shifted ppos\n        cdef np.float64_t clip_pos_l[3]\n        cdef np.float64_t clip_pos_r[3]\n        cdef int skip\n        cdef np.uint64_t bounds[2][3]\n        cdef np.uint64_t xex, yex, zex\n        cdef np.float64_t LE[3]\n        cdef np.float64_t RE[3]\n        cdef np.float64_t DW[3]\n        cdef np.uint8_t PER[3]\n        cdef np.float64_t dds[3]\n        cdef np.float64_t radius\n        cdef np.uint8_t[:] mask = self.masks[:, file_id]\n        cdef np.uint64_t[:] particle_counts = self.particle_counts\n        cdef np.uint64_t msize = (1 << (self.index_order1 * 3))\n        cdef int axiter[3][2]\n        cdef np.float64_t axiterv[3][2]\n        # Copy over things for this file (type cast necessary?)\n        for i in range(3):\n            LE[i] = self.left_edge[i]\n            RE[i] = self.right_edge[i]\n            PER[i] = self.periodicity[i]\n            dds[i] = self.dds_mi1[i]\n            DW[i] = RE[i] - LE[i]\n            axiter[i][0] = 0 # We always do an offset of 0\n            axiterv[i][0] = 0.0\n        # Mark index of particles that are in this file\n        for p in range(pos.shape[0]):\n            skip = 0\n            for i in range(3):\n                axiter[i][1] = 999\n                # Skip particles outside the domain\n                if not (LE[i] <= pos[p, i] < RE[i]):\n                    skip = 1\n                    break\n                ppos[i] = pos[p,i]\n            if skip == 1: continue\n            mi = bounded_morton_split_dds(ppos[0], ppos[1], ppos[2], LE,\n                                          dds, mi_split)\n            mask[mi] = 1\n            particle_counts[mi] += 1\n            # Expand mask by softening\n            if hsml is None:\n                continue\n            if hsml[p] < 0:\n                raise RuntimeError(\n                    f\"Smoothing length for particle {p} is negative with \"\n                    f\"value {hsml[p]}\")\n            radius = hsml[p]\n            # We first check if we're bounded within the domain; this follows the logic in the\n            # pixelize_cartesian routine.  We assume that no smoothing\n            # length can wrap around both directions.\n            for i in range(3):\n                if PER[i] and ppos[i] - radius < LE[i]:\n                    axiter[i][1] = +1\n                    axiterv[i][1] = DW[i]\n                elif PER[i] and ppos[i] + radius > RE[i]:\n                    axiter[i][1] = -1\n                    axiterv[i][1] = -DW[i]\n            for xi in range(2):\n                if axiter[0][xi] == 999: continue\n                s_ppos[0] = ppos[0] + axiterv[0][xi]\n                for yi in range(2):\n                    if axiter[1][yi] == 999: continue\n                    s_ppos[1] = ppos[1] + axiterv[1][yi]\n                    for zi in range(2):\n                        if axiter[2][zi] == 999: continue\n                        s_ppos[2] = ppos[2] + axiterv[2][zi]\n                        # OK, now we compute the left and right edges for this shift.\n                        for i in range(3):\n                            clip_pos_l[i] = fmax(s_ppos[i] - radius, LE[i] + dds[i]/10)\n                            clip_pos_r[i] = fmin(s_ppos[i] + radius, RE[i] - dds[i]/10)\n                        bounded_morton_split_dds(clip_pos_l[0], clip_pos_l[1], clip_pos_l[2], LE, dds, bounds[0])\n                        bounded_morton_split_dds(clip_pos_r[0], clip_pos_r[1], clip_pos_r[2], LE, dds, bounds[1])\n                        # We go to the upper bound plus one so that we have *inclusive* loops -- the upper bound\n                        # is the cell *index*, so we want to make sure we include that cell.  This is also why\n                        # we don't need to worry about mi_max being the max index rather than the cell count.\n                        for xex in range(bounds[0][0], bounds[1][0] + 1):\n                            for yex in range(bounds[0][1], bounds[1][1] + 1):\n                                for zex in range(bounds[0][2], bounds[1][2] + 1):\n                                    miex = encode_morton_64bit(xex, yex, zex)\n                                    mask[miex] = 1\n                                    particle_counts[miex] += 1\n                                    if miex >= msize:\n                                        raise IndexError(\n                                            \"Index for a softening region \"\n                                            f\"({miex}) exceeds \"\n                                            f\"max ({msize})\")\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def _set_coarse_index_data_file(self, np.uint64_t file_id):\n        return self.__set_coarse_index_data_file(file_id)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void __set_coarse_index_data_file(self, np.uint64_t file_id):\n        cdef np.int64_t i\n        cdef FileBitmasks bitmasks = self.bitmasks\n        cdef np.ndarray[np.uint8_t, ndim=1] mask = self.masks[:,file_id]\n        # Add in order\n        for i in range(mask.shape[0]):\n            if mask[i] == 1:\n                bitmasks._set_coarse(file_id, i)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    def _refined_index_data_file(self,\n                                 BoolArrayCollection in_collection,\n                                 np.ndarray[cython.floating, ndim=2] pos,\n                                 np.ndarray[cython.floating, ndim=1] hsml,\n                                 np.ndarray[np.uint8_t, ndim=1] mask,\n                                 np.ndarray[np.uint64_t, ndim=1] sub_mi1,\n                                 np.ndarray[np.uint64_t, ndim=1] sub_mi2,\n                                 np.uint64_t file_id, np.int64_t nsub_mi,\n                                 np.uint64_t count_threshold = 128,\n                                 np.uint8_t mask_threshold = 2):\n        self._used_mi2 = 1\n        if in_collection is None:\n            in_collection = BoolArrayCollection()\n        cdef BoolArrayCollection _in_coll = in_collection\n        out_collection = self.__refined_index_data_file(_in_coll, pos, hsml, mask,\n                                              count_threshold, mask_threshold)\n        return 0, out_collection\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef BoolArrayCollection __refined_index_data_file(\n        self,\n        BoolArrayCollection in_collection,\n        np.ndarray[cython.floating, ndim=2] pos,\n        np.ndarray[cython.floating, ndim=1] hsml,\n        np.ndarray[np.uint8_t, ndim=1] mask,\n        np.uint64_t count_threshold, np.uint8_t mask_threshold\n    ):\n        # Initialize\n        cdef np.int64_t p, sorted_ind\n        cdef np.uint64_t i\n        cdef np.uint64_t mi1, mi2\n        cdef np.float64_t ppos[3]\n        cdef np.float64_t s_ppos[3] # shifted ppos\n        cdef int skip\n        cdef BoolArrayCollection this_collection, out_collection\n        cdef np.uint64_t bounds[2][3]\n        cdef np.uint8_t fully_enclosed\n        cdef np.float64_t LE[3]\n        cdef np.float64_t RE[3]\n        cdef np.float64_t DW[3]\n        cdef np.uint8_t PER[3]\n        cdef np.float64_t dds1[3]\n        cdef np.float64_t dds2[3]\n        cdef np.float64_t radius\n        cdef np.uint64_t mi_split1[3]\n        cdef np.uint64_t mi_split2[3]\n        cdef np.uint64_t miex1\n        cdef np.uint64_t[:] particle_counts = self.particle_counts\n        cdef np.uint64_t xex, yex, zex\n        cdef np.float64_t clip_pos_l[3]\n        cdef np.float64_t clip_pos_r[3]\n        cdef int axiter[3][2]\n        cdef np.float64_t axiterv[3][2]\n        cdef CoarseRefinedSets coarse_refined_map\n        cdef np.uint64_t nfully_enclosed = 0, n_calls = 0\n        cdef np.uint64_t max_mi1_elements = 1 << (3*self.index_order1)\n        cdef np.uint64_t max_mi2_elements = 1 << (3*self.index_order2)\n        cdef np.ndarray[np.uint64_t, ndim=1] refined_count = np.zeros(max_mi1_elements, dtype=\"uint64\")\n        # Copy things from structure (type cast)\n        for i in range(3):\n            LE[i] = self.left_edge[i]\n            RE[i] = self.right_edge[i]\n            PER[i] = self.periodicity[i]\n            dds1[i] = self.dds_mi1[i]\n            dds2[i] = self.dds_mi2[i]\n            DW[i] = RE[i] - LE[i]\n            axiter[i][0] = 0 # We always do an offset of 0\n            axiterv[i][0] = 0.0\n        cdef np.ndarray[np.uint64_t, ndim=1] morton_indices = np.empty(pos.shape[0], dtype=\"u8\")\n        for p in range(pos.shape[0]):\n            morton_indices[p] = bounded_morton(pos[p, 0], pos[p, 1], pos[p, 2],\n                                               LE, RE, self.index_order1)\n        # Loop over positions skipping those outside the domain\n        cdef np.ndarray[np.uint64_t, ndim=1, cast=True] sorted_order\n        if hsml is None:\n            # casting to uint64 for compatibility with 32 bits systems\n            # see https://github.com/yt-project/yt/issues/3656\n            sorted_order = np.argsort(morton_indices).astype(np.uint64, copy=False)\n        else:\n            sorted_order = np.argsort(hsml)[::-1].astype(np.uint64, copy=False)\n        for sorted_ind in range(sorted_order.shape[0]):\n            p = sorted_order[sorted_ind]\n            skip = 0\n            for i in range(3):\n                axiter[i][1] = 999\n                if not (LE[i] <= pos[p, i] < RE[i]):\n                    skip = 1\n                    break\n                ppos[i] = pos[p,i]\n            if skip == 1: continue\n            # Only look if collision at coarse index\n            mi1 = bounded_morton_split_dds(ppos[0], ppos[1], ppos[2], LE,\n                                           dds1, mi_split1)\n            if hsml is None:\n                if mask[mi1] < mask_threshold \\\n                        or particle_counts[mi1] < count_threshold:\n                    continue\n                # Determine sub index within cell of primary index\n                mi2 = bounded_morton_split_relative_dds(\n                    ppos[0], ppos[1], ppos[2], LE, dds1, dds2, mi_split2)\n                if refined_count[mi1] == 0:\n                    coarse_refined_map[mi1].padWithZeroes(max_mi2_elements)\n                if not coarse_refined_map[mi1].get(mi2):\n                    coarse_refined_map[mi1].set(mi2)\n                    refined_count[mi1] += 1\n            else: # only hit if we have smoothing lengths.\n                # We have to do essentially the identical process to in the coarse indexing,\n                # except here we need to fill in all the subranges as well as the coarse ranges\n                # Note that we are also doing the null case, where we do no shifting\n                radius = hsml[p]\n                #if mask[mi1] <= 4: # only one thing in this area\n                #    continue\n                for i in range(3):\n                    if PER[i] and ppos[i] - radius < LE[i]:\n                        axiter[i][1] = +1\n                        axiterv[i][1] = DW[i]\n                    elif PER[i] and ppos[i] + radius > RE[i]:\n                        axiter[i][1] = -1\n                        axiterv[i][1] = -DW[i]\n                for xi in range(2):\n                    if axiter[0][xi] == 999: continue\n                    s_ppos[0] = ppos[0] + axiterv[0][xi]\n                    for yi in range(2):\n                        if axiter[1][yi] == 999: continue\n                        s_ppos[1] = ppos[1] + axiterv[1][yi]\n                        for zi in range(2):\n                            if axiter[2][zi] == 999: continue\n                            s_ppos[2] = ppos[2] + axiterv[2][zi]\n                            # OK, now we compute the left and right edges for this shift.\n                            for i in range(3):\n                                # casting to int64 is not nice but is so we can have negative values we clip\n                                clip_pos_l[i] = fmax(s_ppos[i] - radius, LE[i] + dds1[i]/10)\n                                clip_pos_r[i] = fmin(s_ppos[i] + radius, RE[i] - dds1[i]/10)\n\n                            bounded_morton_split_dds(clip_pos_l[0], clip_pos_l[1], clip_pos_l[2], LE, dds1, bounds[0])\n                            bounded_morton_split_dds(clip_pos_r[0], clip_pos_r[1], clip_pos_r[2], LE, dds1, bounds[1])\n\n                            # We go to the upper bound plus one so that we have *inclusive* loops -- the upper bound\n                            # is the cell *index*, so we want to make sure we include that cell.  This is also why\n                            # we don't need to worry about mi_max being the max index rather than the cell count.\n                            # One additional thing to note is that for all of\n                            # the *internal* cells, i.e., those that are both\n                            # greater than the left edge and less than the\n                            # right edge, we are fully enclosed.\n                            for xex in range(bounds[0][0], bounds[1][0] + 1):\n                                for yex in range(bounds[0][1], bounds[1][1] + 1):\n                                    for zex in range(bounds[0][2], bounds[1][2] + 1):\n                                        miex1 = encode_morton_64bit(xex, yex, zex)\n                                        if mask[miex1] < mask_threshold or \\\n                                                particle_counts[miex1] < count_threshold:\n                                            continue\n                                        # this explicitly requires that it be *between*\n                                        # them, not overlapping\n                                        if xex > bounds[0][0] and xex < bounds[1][0] and \\\n                                           yex > bounds[0][1] and yex < bounds[1][1] and \\\n                                           zex > bounds[0][2] and zex < bounds[1][2]:\n                                            fully_enclosed = 1\n                                        else:\n                                            fully_enclosed = 0\n                                        # Now we need to fill our sub-range\n                                        if refined_count[miex1] == 0:\n                                            coarse_refined_map[miex1].padWithZeroes(max_mi2_elements)\n                                        elif refined_count[miex1] >= max_mi2_elements:\n                                            continue\n                                        if fully_enclosed == 1:\n                                            nfully_enclosed += 1\n                                            coarse_refined_map[miex1].inplace_logicalxor(\n                                                coarse_refined_map[miex1])\n                                            coarse_refined_map[miex1].inplace_logicalnot()\n                                            refined_count[miex1] = max_mi2_elements\n                                            continue\n                                        n_calls += 1\n                                        refined_count[miex1] += self.__fill_refined_ranges(s_ppos, radius, LE, RE,\n                                                                   dds1, xex, yex, zex,\n                                                                   dds2,\n                                                                   coarse_refined_map[miex1])\n        cdef np.uint64_t vec_i\n        cdef bool_array *buf = NULL\n        cdef ewah_word_type w\n        this_collection = BoolArrayCollection()\n        cdef ewah_bool_array *refined_arr = NULL\n        for it1 in coarse_refined_map:\n            mi1 = it1.first\n            refined_arr = &this_collection.ewah_coll[0][mi1]\n            this_collection.ewah_keys[0].set(mi1)\n            this_collection.ewah_refn[0].set(mi1)\n            buf = &it1.second\n            for vec_i in range(buf.sizeInBytes() / sizeof(ewah_word_type)):\n                w = buf.getWord(vec_i)\n                refined_arr.addWord(w)\n        out_collection = BoolArrayCollection()\n        in_collection._logicalor(this_collection, out_collection)\n        return out_collection\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef np.int64_t __fill_refined_ranges(self, np.float64_t s_ppos[3], np.float64_t radius,\n                                           np.float64_t LE[3], np.float64_t RE[3],\n                                           np.float64_t dds1[3], np.uint64_t xex, np.uint64_t yex, np.uint64_t zex,\n                                           np.float64_t dds2[3], bool_array &refined_set) except -1:\n        cdef int i\n        cdef np.uint64_t bounds_l[3]\n        cdef np.uint64_t bounds_r[3]\n        cdef np.uint64_t miex2, miex2_min, miex2_max\n        cdef np.float64_t clip_pos_l[3]\n        cdef np.float64_t clip_pos_r[3]\n        cdef np.float64_t cell_edge_l, cell_edge_r\n        cdef np.uint64_t ex1[3]\n        cdef np.uint64_t xiex_min, yiex_min, ziex_min\n        cdef np.uint64_t xiex_max, yiex_max, ziex_max\n        cdef np.uint64_t old_nsub = refined_set.numberOfOnes()\n        ex1[0] = xex\n        ex1[1] = yex\n        ex1[2] = zex\n        # Check a few special cases\n        for i in range(3):\n            # Figure out our bounds inside our coarse cell, in the space of the\n            # full domain\n            cell_edge_l = ex1[i] * dds1[i] + LE[i]\n            cell_edge_r = cell_edge_l + dds1[i]\n            if s_ppos[i] + radius < cell_edge_l or s_ppos[i] - radius > cell_edge_r:\n                return 0\n            clip_pos_l[i] = fmax(s_ppos[i] - radius, cell_edge_l + dds2[i]/2.0)\n            clip_pos_r[i] = fmin(s_ppos[i] + radius, cell_edge_r - dds2[i]/2.0)\n        miex2_min = bounded_morton_split_relative_dds(clip_pos_l[0], clip_pos_l[1], clip_pos_l[2],\n                                                LE, dds1, dds2, bounds_l)\n        miex2_max = bounded_morton_split_relative_dds(clip_pos_r[0], clip_pos_r[1], clip_pos_r[2],\n                                                LE, dds1, dds2, bounds_r)\n        xex_max = self.directional_max2[0]\n        yex_max = self.directional_max2[1]\n        zex_max = self.directional_max2[2]\n        xiex_min = miex2_min & xex_max\n        yiex_min = miex2_min & yex_max\n        ziex_min = miex2_min & zex_max\n        xiex_max = miex2_max & xex_max\n        yiex_max = miex2_max & yex_max\n        ziex_max = miex2_max & zex_max\n        # This could *probably* be sped up by iterating over words.\n        for miex2 in range(miex2_min, miex2_max + 1):\n            #miex2 = encode_morton_64bit(xex2, yex2, zex2)\n            #decode_morton_64bit(miex2, ex2)\n            # Let's check all our cases here\n            if (miex2 & xex_max) < (xiex_min): continue\n            if (miex2 & xex_max) > (xiex_max): continue\n            if (miex2 & yex_max) < (yiex_min): continue\n            if (miex2 & yex_max) > (yiex_max): continue\n            if (miex2 & zex_max) < (ziex_min): continue\n            if (miex2 & zex_max) > (ziex_max): continue\n            refined_set.set(miex2)\n        return refined_set.numberOfOnes() - old_nsub\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    def _set_refined_index_data_file(self,\n                                     np.ndarray[np.uint64_t, ndim=1] sub_mi1,\n                                     np.ndarray[np.uint64_t, ndim=1] sub_mi2,\n                                     np.uint64_t file_id, np.int64_t nsub_mi):\n        return self.__set_refined_index_data_file(sub_mi1, sub_mi2,\n                                                  file_id, nsub_mi)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef void __set_refined_index_data_file(self,\n                                            np.ndarray[np.uint64_t, ndim=1] sub_mi1,\n                                            np.ndarray[np.uint64_t, ndim=1] sub_mi2,\n                                            np.uint64_t file_id, np.int64_t nsub_mi):\n        cdef FileBitmasks bitmasks = self.bitmasks\n        bitmasks._set_refined_index_array(file_id, nsub_mi, sub_mi1, sub_mi2)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    def find_collisions(self, verbose=False):\n        cdef tuple cc, rc\n        cc, rc = self.bitmasks._find_collisions(self.collisions,verbose)\n        return cc, rc\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    def find_collisions_coarse(self, verbose=False, file_list = None):\n        cdef int nc, nm\n        nc, nm = self.bitmasks._find_collisions_coarse(self.collisions, verbose, file_list)\n        return nc, nm\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    def find_uncontaminated(self, np.uint32_t ifile, BoolArrayCollection mask,\n                            BoolArrayCollection mask2 = None):\n        cdef np.ndarray[np.uint8_t, ndim=1] arr = np.zeros((1 << (self.index_order1 * 3)),'uint8')\n        cdef np.uint8_t[:] arr_view = arr\n        self.bitmasks._select_uncontaminated(ifile, mask, arr_view, mask2)\n        return arr\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    def find_contaminated(self, np.uint32_t ifile, BoolArrayCollection mask,\n                          BoolArrayCollection mask2 = None):\n        cdef np.ndarray[np.uint8_t, ndim=1] arr = np.zeros((1 << (self.index_order1 * 3)),'uint8')\n        cdef np.uint8_t[:] arr_view = arr\n        cdef np.ndarray[np.uint8_t, ndim=1] sfiles = np.zeros(self.nfiles,'uint8')\n        cdef np.uint8_t[:] sfiles_view = sfiles\n        self.bitmasks._select_contaminated(ifile, mask, arr_view, sfiles_view, mask2)\n        return arr, np.where(sfiles)[0].astype('uint32')\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    def find_collisions_refined(self, verbose=False):\n        cdef np.int32_t nc, nm\n        nc, nm = self.bitmasks._find_collisions_refined(self.collisions,verbose)\n        return nc, nm\n\n    def get_bitmasks(self):\n        return self.bitmasks\n\n    def iseq_bitmask(self, solf):\n        return self.bitmasks._iseq(solf.get_bitmasks())\n\n    def save_bitmasks(self, fname, max_hsml):\n        import h5py\n        cdef bytes serial_BAC\n        cdef np.uint64_t ifile\n        with h5py.File(fname, mode=\"a\") as fp:\n            try:\n                grp = fp[str(self.hash_value)]\n                grp.clear()\n            except KeyError:\n                grp = fp.create_group(str(self.hash_value))\n\n            grp.attrs[\"bitmask_version\"] = _bitmask_version\n            grp.attrs[\"nfiles\"] = self.nfiles\n            grp.attrs[\"max_hsml\"] = max_hsml\n            # Add some attrs for convenience. They're not read back.\n            grp.attrs[\"file_hash\"] = self.file_hash\n            grp.attrs[\"left_edge\"] = self.left_edge\n            grp.attrs[\"right_edge\"] = self.right_edge\n            grp.attrs[\"periodicity\"] = self.periodicity\n            grp.attrs[\"index_order1\"] = self.index_order1\n            grp.attrs[\"index_order2\"] = self.index_order2\n\n            for ifile in range(self.nfiles):\n                serial_BAC = self.bitmasks._dumps(ifile)\n                grp.create_dataset(f\"nfile_{ifile:05}\", data=np.void(serial_BAC))\n            serial_BAC = self.collisions._dumps()\n            grp.create_dataset(\"collisions\", data=np.void(serial_BAC))\n\n    def check_bitmasks(self):\n        return self.bitmasks._check()\n\n    def reset_bitmasks(self):\n        self.bitmasks._reset()\n\n    def load_bitmasks(self, fname):\n        import h5py\n        cdef bint read_flag = 1\n        cdef bint irflag\n        cdef np.uint64_t ver\n        cdef bint overwrite = 0\n        # Verify that file is correct version\n        if not os.path.isfile(fname):\n            raise OSError\n        with h5py.File(fname, mode=\"r\") as fp:\n            try:\n                grp = fp[str(self.hash_value)]\n            except KeyError:\n                raise OSError(f\"Index not found in the {fname}\")\n\n            ver = grp.attrs[\"bitmask_version\"]\n            try:\n                max_hsml = grp.attrs[\"max_hsml\"]\n            except KeyError:\n                raise OSError(f\"'max_hsml' not found in the {fname}\")\n            if ver == self.nfiles and ver != _bitmask_version:\n                overwrite = 1\n                ver = 0 # Original bitmaps had number of files first\n            if ver != _bitmask_version:\n                raise OSError(\"The file format of the index has changed since \"\n                              \"this file was created. It will be replaced with an \"\n                              \"updated version.\")\n\n            # Read bitmap for each file\n            pb = get_pbar(\"Loading particle index\", self.nfiles)\n            for ifile in range(self.nfiles):\n                pb.update(ifile+1)\n                irflag = self.bitmasks._loads(ifile, grp[f\"nfile_{ifile:05}\"][...].tobytes())\n                if irflag == 0:\n                    read_flag = 0\n            pb.finish()\n            # Collisions\n            irflag = self.collisions._loads(grp[\"collisions\"][...].tobytes())\n            if irflag == 0:\n                read_flag = 0\n\n        # Save in correct format\n        if overwrite == 1:\n            self.save_bitmasks(fname, max_hsml)\n        return read_flag, max_hsml\n\n    def print_info(self):\n        cdef np.uint64_t ifile\n        for ifile in range(self.nfiles):\n            self.bitmasks.print_info(ifile, \"File: %03d\" % ifile)\n\n    def count_coarse(self, ifile):\n        r\"\"\"Get the number of coarse cells set for a file.\"\"\"\n        return self.bitmasks.count_coarse(ifile)\n\n    def count_refined(self, ifile):\n        r\"\"\"Get the number of cells refined for a file.\"\"\"\n        return self.bitmasks.count_refined(ifile)\n\n    def count_total(self, ifile):\n        r\"\"\"Get the total number of cells set for a file.\"\"\"\n        return self.bitmasks.count_total(ifile)\n\n    def check(self):\n        cdef np.uint64_t mi1\n        cdef ewah_bool_array arr_totref, arr_tottwo\n        cdef ewah_bool_array arr, arr_any, arr_two, arr_swap\n        cdef vector[size_t] vec_totref\n        cdef vector[size_t].iterator it_mi1\n        cdef int nm = 0, nc = 0\n        cdef np.uint64_t ifile, nbitmasks\n        nbitmasks = len(self.bitmasks)\n        # Locate all indices with second level refinement\n        for ifile in range(self.nfiles):\n            arr = (<ewah_bool_array**> self.bitmasks.ewah_refn)[ifile][0]\n            arr_totref.logicalor(arr,arr_totref)\n        # Count collections & second level indices\n        vec_totref = arr_totref.toArray()\n        it_mi1 = vec_totref.begin()\n        while it_mi1 != vec_totref.end():\n            mi1 = dereference(it_mi1)\n            arr_any.reset()\n            arr_two.reset()\n            for ifile in range(nbitmasks):\n                if self.bitmasks._isref(ifile, mi1) == 1:\n                    arr = (<cmap[np.int64_t, ewah_bool_array]**> self.bitmasks.ewah_coll)[ifile][0][mi1]\n                    arr_any.logicaland(arr, arr_two) # Indices in previous files\n                    arr_any.logicalor(arr, arr_swap) # All second level indices\n                    arr_any = arr_swap\n                    arr_two.logicalor(arr_tottwo,arr_tottwo)\n            nc += arr_tottwo.numberOfOnes()\n            nm += arr_any.numberOfOnes()\n            preincrement(it_mi1)\n        # nc: total number of second level morton indices that are repeated\n        # nm: total number of second level morton indices\n        print(\"Total of %s / %s collisions (% 3.5f%%)\" % (nc, nm, 100.0*float(nc)/nm))\n\n    def primary_indices(self):\n        mi = (<ewah_bool_array*> self.collisions.ewah_keys)[0].toArray()\n        return np.array(mi,'uint64')\n\n    def file_ownership_mask(self, fid):\n        cdef BoolArrayCollection out\n        out = self.bitmasks._get_bitmask(<np.uint32_t> fid)\n        return out\n\n    def finalize(self):\n        return\n        # self.index_octree = ParticleOctreeContainer([1,1,1],\n        #     [self.left_edge[0], self.left_edge[1], self.left_edge[2]],\n        #     [self.right_edge[0], self.right_edge[1], self.right_edge[2]],\n        #     num_zones = 1\n        # )\n        # self.index_octree.n_ref = 1\n        # mi = (<ewah_bool_array*> self.collisions.ewah_keys)[0].toArray()\n        # Change from vector to numpy\n        # mi = mi.astype(\"uint64\")\n        # self.index_octree.add(mi, self.index_order1)\n        # self.index_octree.finalize()\n\n    def get_DLE(self):\n        cdef int i\n        cdef np.ndarray[np.float64_t, ndim=1] DLE\n        DLE = np.zeros(3, dtype='float64')\n        for i in range(3):\n            DLE[i] = self.left_edge[i]\n        return DLE\n    def get_DRE(self):\n        cdef int i\n        cdef np.ndarray[np.float64_t, ndim=1] DRE\n        DRE = np.zeros(3, dtype='float64')\n        for i in range(3):\n            DRE[i] = self.right_edge[i]\n        return DRE\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def get_ghost_zones(self, SelectorObject selector, int ngz,\n                        BoolArrayCollection dmask = None, bint coarse_ghosts = False):\n        cdef BoolArrayCollection gmask, gmask2, out\n        cdef np.ndarray[np.uint8_t, ndim=1] periodic = selector.get_periodicity()\n        cdef bint periodicity[3]\n        cdef int i\n        for i in range(3):\n            periodicity[i] = periodic[i]\n        if dmask is None:\n            dmask = BoolArrayCollection()\n            gmask2 = BoolArrayCollection()\n            morton_selector = ParticleBitmapSelector(selector,self,ngz=0)\n            morton_selector.fill_masks(dmask, gmask2)\n        gmask = BoolArrayCollection()\n        dmask._get_ghost_zones(ngz, self.index_order1, self.index_order2,\n                               periodicity, gmask, <bint>coarse_ghosts)\n        _dfiles, gfiles = self.masks_to_files(dmask, gmask)\n        out = BoolArrayCollection()\n        gmask._logicalor(dmask, out)\n        return gfiles, out\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def selector2mask(self, SelectorObject selector):\n        cdef BoolArrayCollection cmask = BoolArrayCollection()\n        cdef ParticleBitmapSelector morton_selector\n        morton_selector = ParticleBitmapSelector(selector,self,ngz=0)\n        morton_selector.fill_masks(cmask)\n        return cmask\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def mask2files(self, BoolArrayCollection cmask):\n        cdef np.ndarray[np.uint32_t, ndim=1] file_idx\n        file_idx = self.mask_to_files(cmask)\n        return file_idx\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def mask2filemasks(self, BoolArrayCollection cmask, np.ndarray[np.uint32_t, ndim=1] file_idx):\n        cdef BoolArrayCollection fmask\n        cdef np.int32_t fid\n        cdef np.ndarray[object, ndim=1] file_masks\n        cdef int i\n        # Get bitmasks for parts of files touching the selector\n        file_masks = np.array([BoolArrayCollection() for i in range(len(file_idx))],\n                              dtype=\"object\")\n        for i, (fid, fmask) in enumerate(zip(file_idx,file_masks)):\n            self.bitmasks._logicaland(<np.uint32_t> fid, cmask, fmask)\n        return file_masks\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def filemasks2addfiles(self, np.ndarray[object, ndim=1] file_masks):\n        cdef list addfile_idx\n        addfile_idx = len(file_masks)*[None]\n        for i, fmask in enumerate(file_masks):\n            addfile_idx[i] = self.mask_to_files(fmask).astype('uint32')\n        return addfile_idx\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def identify_file_masks(self, SelectorObject selector):\n        cdef BoolArrayCollection cmask = BoolArrayCollection()\n        cdef BoolArrayCollection fmask\n        cdef np.int32_t fid\n        cdef np.ndarray[object, ndim=1] file_masks\n        cdef np.ndarray[np.uint32_t, ndim=1] file_idx\n        cdef list addfile_idx\n        # Get bitmask for selector\n        cdef ParticleBitmapSelector morton_selector\n        morton_selector = ParticleBitmapSelector(selector, self, ngz=0)\n        morton_selector.fill_masks(cmask)\n        # Get bitmasks for parts of files touching the selector\n        file_idx = self.mask_to_files(cmask)\n        file_masks = np.array([BoolArrayCollection() for i in range(len(file_idx))],\n                              dtype=\"object\")\n        addfile_idx = len(file_idx)*[None]\n        for i, (fid, fmask) in enumerate(zip(file_idx,file_masks)):\n            self.bitmasks._logicaland(<np.uint32_t> fid, cmask, fmask)\n            addfile_idx[i] = self.mask_to_files(fmask).astype('uint32')\n        return file_idx.astype('uint32'), file_masks, addfile_idx\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def identify_data_files(self, SelectorObject selector, int ngz = 0):\n        cdef BoolArrayCollection cmask_s = BoolArrayCollection()\n        cdef BoolArrayCollection cmask_g = BoolArrayCollection()\n        # Find mask of selected morton indices\n        cdef ParticleBitmapSelector morton_selector\n        morton_selector = ParticleBitmapSelector(selector, self, ngz=ngz)\n        morton_selector.fill_masks(cmask_s, cmask_g)\n        return self.masks_to_files(cmask_s, cmask_g), (cmask_s, cmask_g)\n\n    def mask_to_files(self, BoolArrayCollection mm_s):\n        cdef FileBitmasks mm_d = self.bitmasks\n        cdef np.uint32_t ifile\n        cdef np.ndarray[np.uint8_t, ndim=1] file_mask_p\n        file_mask_p = np.zeros(self.nfiles, dtype=\"uint8\")\n        # Compare with mask of particles\n        for ifile in range(self.nfiles):\n            # Only continue if the file is not already selected\n            if file_mask_p[ifile] == 0:\n                if mm_d._intersects(ifile, mm_s):\n                    file_mask_p[ifile] = 1\n        cdef np.ndarray[np.int32_t, ndim=1] file_idx_p\n        file_idx_p = np.where(file_mask_p)[0].astype('int32')\n        return file_idx_p.astype('uint32')\n\n    def masks_to_files(self, BoolArrayCollection mm_s, BoolArrayCollection mm_g):\n        cdef FileBitmasks mm_d = self.bitmasks\n        cdef np.uint32_t ifile\n        cdef np.ndarray[np.uint8_t, ndim=1] file_mask_p\n        cdef np.ndarray[np.uint8_t, ndim=1] file_mask_g\n        file_mask_p = np.zeros(self.nfiles, dtype=\"uint8\")\n        file_mask_g = np.zeros(self.nfiles, dtype=\"uint8\")\n        # Compare with mask of particles\n        for ifile in range(self.nfiles):\n            # Only continue if the file is not already selected\n            if file_mask_p[ifile] == 0:\n                if mm_d._intersects(ifile, mm_s):\n                    file_mask_p[ifile] = 1\n                    file_mask_g[ifile] = 0 # No intersection\n                elif mm_d._intersects(ifile, mm_g):\n                    file_mask_g[ifile] = 1\n        cdef np.ndarray[np.int32_t, ndim=1] file_idx_p\n        cdef np.ndarray[np.int32_t, ndim=1] file_idx_g\n        file_idx_p = np.where(file_mask_p)[0].astype('int32')\n        file_idx_g = np.where(file_mask_g)[0].astype('int32')\n        return file_idx_p.astype('uint32'), file_idx_g.astype('uint32')\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def construct_octree(self, index, io_handler, data_files,\n                         num_zones,\n                         BoolArrayCollection selector_mask,\n                         BoolArrayCollection base_mask = None):\n        cdef np.uint64_t total_pcount\n        cdef np.uint64_t i, j, k\n        cdef int ind[3]\n        cdef np.uint64_t ind64[3]\n        cdef ParticleBitmapOctreeContainer octree\n        cdef np.uint64_t mi, mi_root\n        cdef np.ndarray pos\n        cdef np.ndarray[np.float32_t, ndim=2] pos32\n        cdef np.ndarray[np.float64_t, ndim=2] pos64\n        cdef np.float64_t ppos[3]\n        cdef np.float64_t DLE[3]\n        cdef np.float64_t DRE[3]\n        cdef int bitsize = 0\n        for i in range(3):\n            DLE[i] = self.left_edge[i]\n            DRE[i] = self.right_edge[i]\n        cdef np.ndarray[np.uint64_t, ndim=1] morton_ind\n        # Determine cells that need to be added to the octree\n        cdef np.uint64_t nroot = selector_mask._count_total()\n        # Now we can actually create a sparse octree.\n        octree = ParticleBitmapOctreeContainer(\n            (self.dims[0], self.dims[1], self.dims[2]),\n            (self.left_edge[0], self.left_edge[1], self.left_edge[2]),\n            (self.right_edge[0], self.right_edge[1], self.right_edge[2]),\n            nroot, num_zones)\n        octree.n_ref = index.dataset.n_ref\n        octree.level_offset = self.index_order1\n        octree.allocate_domains()\n        # Add roots based on the mask\n        cdef np.uint64_t croot = 0\n        cdef ewah_bool_array *ewah_slct = <ewah_bool_array *> selector_mask.ewah_keys\n        cdef ewah_bool_array *ewah_base\n        if base_mask is not None:\n            ewah_base = <ewah_bool_array *> base_mask.ewah_keys\n        else:\n            ewah_base = NULL\n        cdef ewah_bool_iterator *iter_set = new ewah_bool_iterator(ewah_slct[0].begin())\n        cdef ewah_bool_iterator *iter_end = new ewah_bool_iterator(ewah_slct[0].end())\n        cdef np.ndarray[np.uint8_t, ndim=1] slct_arr\n        slct_arr = np.zeros((1 << (self.index_order1 * 3)),'uint8')\n        while iter_set[0] != iter_end[0]:\n            mi = dereference(iter_set[0])\n            if ewah_base != NULL and ewah_base[0].get(mi) == 0:\n                octree._index_base_roots[croot] = 0\n                slct_arr[mi] = 2\n            else:\n                slct_arr[mi] = 1\n            decode_morton_64bit(mi, ind64)\n            for j in range(3):\n                ind[j] = ind64[j]\n            octree.next_root(1, ind)\n            croot += 1\n            preincrement(iter_set[0])\n        assert(croot == nroot)\n        if ewah_base != NULL:\n            assert(np.sum(octree._index_base_roots) == ewah_base[0].numberOfOnes())\n        # Get morton indices for all particles in this file and those\n        # contaminating cells it has majority control of.\n        files_touched = data_files #+ buffer_files  # datafile object from ID goes here\n        total_pcount = 0\n        for data_file in files_touched:\n            total_pcount += sum(data_file.total_particles.values())\n        morton_ind = np.empty(total_pcount, dtype='uint64')\n        total_pcount = 0\n        cdef np.uint64_t base_pcount = 0\n        for data_file in files_touched:\n            # We now get our particle positions\n            for pos in io_handler._yield_coordinates(data_file):\n                pos32 = pos64 = None\n                bitsize = 0\n                if pos.dtype == np.float32:\n                    pos32 = pos\n                    bitsize = 32\n                    for j in range(pos.shape[0]):\n                        for k in range(3):\n                            ppos[k] = pos32[j,k]\n                        mi = bounded_morton(ppos[0], ppos[1], ppos[2],\n                                            DLE, DRE, ORDER_MAX)\n                        mi_root = mi >> (3*(ORDER_MAX-self.index_order1))\n                        if slct_arr[mi_root] > 0:\n                            morton_ind[total_pcount] = mi\n                            total_pcount += 1\n                            if slct_arr[mi_root] == 1:\n                                base_pcount += 1\n                elif pos.dtype == np.float64:\n                    pos64 = pos\n                    bitsize = 64\n                    for j in range(pos.shape[0]):\n                        for k in range(3):\n                            ppos[k] = pos64[j,k]\n                        mi = bounded_morton(ppos[0], ppos[1], ppos[2],\n                                            DLE, DRE, ORDER_MAX)\n                        mi_root = mi >> (3*(ORDER_MAX-self.index_order1))\n                        if slct_arr[mi_root] > 0:\n                            morton_ind[total_pcount] = mi\n                            total_pcount += 1\n                            if slct_arr[mi_root] == 1:\n                                base_pcount += 1\n                else:\n                    raise RuntimeError\n        morton_ind = morton_ind[:total_pcount]\n        morton_ind.sort()\n        octree.add(morton_ind, self.index_order1)\n        octree.finalize()\n        return octree\n\ncdef class ParticleBitmapSelector:\n    cdef SelectorObject selector\n    cdef ParticleBitmap bitmap\n    cdef np.uint32_t ngz\n    cdef np.float64_t DLE[3]\n    cdef np.float64_t DRE[3]\n    cdef bint periodicity[3]\n    cdef np.uint32_t order1\n    cdef np.uint32_t order2\n    cdef np.uint64_t max_index1\n    cdef np.uint64_t max_index2\n    cdef np.uint64_t s1\n    cdef np.uint64_t s2\n    cdef void* pointers[11]\n    cdef np.uint64_t[:,:] ind1_n\n    cdef np.uint64_t[:,:] ind2_n\n    cdef np.uint32_t[:,:] neighbors\n    cdef np.uint64_t[:] neighbor_list1\n    cdef np.uint64_t[:] neighbor_list2\n    cdef np.uint32_t nfiles\n    cdef np.uint8_t[:] file_mask_p\n    cdef np.uint8_t[:] file_mask_g\n    # Uncompressed boolean\n    cdef np.uint8_t[:] refined_select_bool\n    cdef np.uint8_t[:] refined_ghosts_bool\n    cdef np.uint8_t[:] coarse_select_bool\n    cdef np.uint8_t[:] coarse_ghosts_bool\n    cdef SparseUnorderedRefinedBitmask refined_ghosts_list\n    cdef BoolArrayColl select_ewah\n    cdef BoolArrayColl ghosts_ewah\n\n    def __cinit__(self, selector, bitmap, ngz=0):\n        cdef int i\n        cdef np.ndarray[np.uint8_t, ndim=1] periodicity = np.zeros(3, dtype='uint8')\n        cdef np.ndarray[np.float64_t, ndim=1] DLE = np.zeros(3, dtype='float64')\n        cdef np.ndarray[np.float64_t, ndim=1] DRE = np.zeros(3, dtype='float64')\n\n        self.selector = selector\n        self.bitmap = bitmap\n        self.ngz = ngz\n        # Things from the bitmap & selector\n        periodicity = selector.get_periodicity()\n        DLE = bitmap.get_DLE()\n        DRE = bitmap.get_DRE()\n        for i in range(3):\n            self.DLE[i] = DLE[i]\n            self.DRE[i] = DRE[i]\n            self.periodicity[i] = periodicity[i]\n        self.order1 = bitmap.index_order1\n        self.order2 = bitmap.index_order2\n        self.nfiles = bitmap.nfiles\n        self.max_index1 = <np.uint64_t>(1 << self.order1)\n        self.max_index2 = <np.uint64_t>(1 << self.order2)\n        self.s1 = <np.uint64_t>(1 << (self.order1*3))\n        self.s2 = <np.uint64_t>(1 << (self.order2*3))\n\n        self.neighbors = np.zeros((2*ngz+1, 3), dtype='uint32')\n        self.ind1_n = np.zeros((2*ngz+1, 3), dtype='uint64')\n        self.ind2_n = np.zeros((2*ngz+1, 3), dtype='uint64')\n        self.neighbor_list1 = np.zeros((2*ngz+1)**3, dtype='uint64')\n        self.neighbor_list2 = np.zeros((2*ngz+1)**3, dtype='uint64')\n        self.file_mask_p = np.zeros(bitmap.nfiles, dtype='uint8')\n        self.file_mask_g = np.zeros(bitmap.nfiles, dtype='uint8')\n\n        self.refined_select_bool = np.zeros(self.s2, 'uint8')\n        self.refined_ghosts_bool = np.zeros(self.s2, 'uint8')\n        self.coarse_select_bool = np.zeros(self.s1, 'uint8')\n        self.coarse_ghosts_bool = np.zeros(self.s1, 'uint8')\n\n        self.refined_ghosts_list = SparseUnorderedRefinedBitmask()\n        self.select_ewah = BoolArrayColl(self.s1, self.s2)\n        self.ghosts_ewah = BoolArrayColl(self.s1, self.s2)\n\n    def fill_masks(self, BoolArrayCollection mm_s, BoolArrayCollection mm_g = None):\n        # Normal variables\n        cdef int i\n        cdef np.int32_t level = 0\n        cdef np.uint64_t mi1\n        mi1 = ~(<np.uint64_t>0)\n        cdef np.float64_t pos[3]\n        cdef np.float64_t dds[3]\n        cdef np.uint64_t cur_ind[3]\n        for i in range(3):\n            cur_ind[i] = 0\n            pos[i] = self.DLE[i]\n            dds[i] = self.DRE[i] - self.DLE[i]\n        if mm_g is None:\n            mm_g = BoolArrayCollection()\n        # Uncompressed version\n        cdef BoolArrayColl mm_s0\n        cdef BoolArrayColl mm_g0\n        mm_s0 = BoolArrayColl(self.s1, self.s2)\n        mm_g0 = BoolArrayColl(self.s1, self.s2)\n        # Recurse\n        cdef np.float64_t rpos[3]\n        for i in range(3):\n            rpos[i] = self.DRE[i] - self.bitmap.dds_mi2[i]/2.0\n        sbbox = self.selector.select_bbox_edge(pos, rpos)\n        if sbbox == 1:\n            for mi1 in range(<np.uint64_t>self.s1):\n                mm_s0._set_coarse(mi1)\n            mm_s0._compress(mm_s)\n            return\n        else:\n            self.recursive_morton_mask(level, pos, dds, mi1, cur_ind)\n        # Set coarse morton indices in order\n        self.set_coarse_bool(mm_s0, mm_g0)\n        self.set_refined_list(mm_s0, mm_g0)\n        self.set_refined_bool(mm_s0, mm_g0)\n        # Compress\n        mm_s0._compress(mm_s)\n        mm_g0._compress(mm_g)\n\n    def find_files(self,\n                   np.ndarray[np.uint8_t, ndim=1] file_mask_p,\n                   np.ndarray[np.uint8_t, ndim=1] file_mask_g):\n        cdef np.uint64_t i\n        cdef np.int32_t level = 0\n        cdef np.uint64_t mi1\n        mi1 = ~(<np.uint64_t>0)\n        cdef np.float64_t pos[3]\n        cdef np.float64_t dds[3]\n        for i in range(3):\n            pos[i] = self.DLE[i]\n            dds[i] = self.DRE[i] - self.DLE[i]\n        # Fill with input\n        for i in range(self.nfiles):\n            self.file_mask_p[i] = file_mask_p[i]\n            self.file_mask_g[i] = file_mask_g[i]\n        # Recurse\n        self.recursive_morton_files(level, pos, dds, mi1)\n        # Fill with results\n        for i in range(self.nfiles):\n            file_mask_p[i] = self.file_mask_p[i]\n            if file_mask_p[i]:\n                file_mask_g[i] = 0\n            else:\n                file_mask_g[i] = self.file_mask_g[i]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef bint is_refined(self, np.uint64_t mi1):\n        return self.bitmap.collisions._isref(mi1)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef bint is_refined_files(self, np.uint64_t mi1):\n        cdef np.uint64_t i\n        if self.bitmap.collisions._isref(mi1):\n            # Don't refine if files all selected already\n            for i in range(self.nfiles):\n                if self.file_mask_p[i] == 0:\n                    if self.bitmap.bitmasks._isref(i, mi1) == 1:\n                        return 1\n            return 0\n        else:\n            return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef void add_coarse(self, np.uint64_t mi1, int bbox = 2):\n        self.coarse_select_bool[mi1] = 1\n        # Neighbors\n        if (self.ngz > 0) and (bbox == 2):\n            if not self.is_refined(mi1):\n                self.add_neighbors_coarse(mi1)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef void set_files_coarse(self, np.uint64_t mi1):\n        cdef np.uint64_t i\n        cdef bint flag_ref = self.is_refined(mi1)\n        # Flag files at coarse level\n        if flag_ref == 0:\n            for i in range(self.nfiles):\n                if self.file_mask_p[i] == 0:\n                    if self.bitmap.bitmasks._get_coarse(i, mi1) == 1:\n                        self.file_mask_p[i] = 1\n        # Neighbors\n        if (flag_ref == 0) and (self.ngz > 0):\n            self.set_files_neighbors_coarse(mi1)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef int add_refined(self, np.uint64_t mi1, np.uint64_t mi2, int bbox = 2) except -1:\n        self.refined_select_bool[mi2] = 1\n        # Neighbors\n        if (self.ngz > 0) and (bbox == 2):\n            self.add_neighbors_refined(mi1, mi2)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef void set_files_refined(self, np.uint64_t mi1, np.uint64_t mi2):\n        cdef np.uint64_t i\n        # Flag files\n        for i in range(self.nfiles):\n            if self.file_mask_p[i] == 0:\n                if self.bitmap.bitmasks._get(i, mi1, mi2):\n                    self.file_mask_p[i] = 1\n        # Neighbors\n        if (self.ngz > 0):\n            self.set_files_neighbors_refined(mi1, mi2)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef void add_neighbors_coarse(self, np.uint64_t mi1):\n        cdef np.uint64_t m\n        cdef np.uint32_t ntot\n        cdef np.uint64_t mi1_n\n        ntot = morton_neighbors_coarse(mi1, self.max_index1,\n                                       self.periodicity,\n                                       self.ngz, self.neighbors,\n                                       self.ind1_n, self.neighbor_list1)\n        for m in range(ntot):\n            mi1_n = self.neighbor_list1[m]\n            self.coarse_ghosts_bool[mi1_n] = 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef void set_files_neighbors_coarse(self, np.uint64_t mi1):\n        cdef np.uint64_t i, m\n        cdef np.uint32_t ntot\n        cdef np.uint64_t mi1_n\n        ntot = morton_neighbors_coarse(mi1, self.max_index1,\n                                       self.periodicity,\n                                       self.ngz, self.neighbors,\n                                       self.ind1_n, self.neighbor_list1)\n        for m in range(ntot):\n            mi1_n = self.neighbor_list1[m]\n            for i in range(self.nfiles):\n                if self.file_mask_g[i] == 0:\n                    if self.bitmap.bitmasks._get_coarse(i, mi1_n):\n                        self.file_mask_g[i] = 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef void add_neighbors_refined(self, np.uint64_t mi1, np.uint64_t mi2):\n        cdef int m\n        cdef np.uint32_t ntot\n        cdef np.uint64_t mi1_n, mi2_n\n        ntot = morton_neighbors_refined(mi1, mi2,\n                                        self.max_index1, self.max_index2,\n                                        self.periodicity, self.ngz,\n                                        self.neighbors, self.ind1_n, self.ind2_n,\n                                        self.neighbor_list1, self.neighbor_list2)\n        for m in range(<np.int32_t>ntot):\n            mi1_n = self.neighbor_list1[m]\n            mi2_n = self.neighbor_list2[m]\n            self.coarse_ghosts_bool[mi1_n] = 1\n\n            # Ghost cells are added at the refined level regardless of if the\n            # coarse cell containing it is refined in the selector.\n            if mi1_n == mi1:\n                self.refined_ghosts_bool[mi2_n] = 1\n            else:\n                self.refined_ghosts_list._set(mi1_n, mi2_n)\n\n            # alternative implementation by Meagan Lang\n            # see ed95b1ac2f7105092b1116f9c76568ae27024751\n            # Ghost cells are only added at the refined level if the coarse\n            # index for the ghost cell is refined in the selector.\n            #if mi1_n == mi1:\n            #    self.refined_ghosts_bool[mi2_n] = 1\n            #elif self.is_refined(mi1_n) == 1:\n            #    self.refined_ghosts_list._set(mi1_n, mi2_n)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef void set_files_neighbors_refined(self, np.uint64_t mi1, np.uint64_t mi2):\n        cdef int i, m\n        cdef np.uint32_t ntot\n        cdef np.uint64_t mi1_n, mi2_n\n        ntot = morton_neighbors_refined(mi1, mi2,\n                                        self.max_index1, self.max_index2,\n                                        self.periodicity, self.ngz,\n                                        self.neighbors, self.ind1_n, self.ind2_n,\n                                        self.neighbor_list1, self.neighbor_list2)\n        for m in range(<np.int32_t>ntot):\n            mi1_n = self.neighbor_list1[m]\n            mi2_n = self.neighbor_list2[m]\n            if self.is_refined(mi1_n) == 1:\n                for i in range(self.nfiles):\n                    if self.file_mask_g[i] == 0:\n                        if self.bitmap.bitmasks._get(i, mi1_n, mi2_n) == 1:\n                            self.file_mask_g[i] = 1\n            else:\n                for i in range(self.nfiles):\n                    if self.file_mask_g[i] == 0:\n                        if self.bitmap.bitmasks._get_coarse(i, mi1_n) == 1:\n                            self.file_mask_g[i] = 1\n                            break # If not refined, only one file should be selected\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void set_coarse_list(self, BoolArrayColl mm_s, BoolArrayColl mm_g):\n        self.coarse_select_list._fill_bool(mm_s)\n        self.coarse_ghosts_list._fill_bool(mm_g)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void set_refined_list(self, BoolArrayColl mm_s, BoolArrayColl mm_g):\n        self.refined_ghosts_list._fill_bool(mm_g)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void set_coarse_bool(self, BoolArrayColl mm_s, BoolArrayColl mm_g):\n        cdef np.uint64_t mi1\n        mm_s._set_coarse_array_ptr(&self.coarse_select_bool[0])\n        for mi1 in range(self.s1):\n            self.coarse_select_bool[mi1] = 0\n        mm_g._set_coarse_array_ptr(&self.coarse_ghosts_bool[0])\n        for mi1 in range(self.s1):\n            self.coarse_ghosts_bool[mi1] = 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void set_refined_bool(self, BoolArrayColl mm_s, BoolArrayColl mm_g):\n        mm_s._append(self.select_ewah)\n        mm_g._append(self.ghosts_ewah)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef void push_refined_bool(self, np.uint64_t mi1):\n        cdef np.uint64_t mi2\n        self.select_ewah._set_refined_array_ptr(mi1, &self.refined_select_bool[0])\n        for mi2 in range(self.s2):\n            self.refined_select_bool[mi2] = 0\n        self.ghosts_ewah._set_refined_array_ptr(mi1, &self.refined_ghosts_bool[0])\n        for mi2 in range(self.s2):\n            self.refined_ghosts_bool[mi2] = 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void add_ghost_zones(self, BoolArrayColl mm_s, BoolArrayColl mm_g):\n        cdef np.uint64_t mi1, mi2\n        # Get ghost zones, unordered\n        for mi1 in range(self.s1):\n            if mm_s._get_coarse(mi1):\n                if self.is_refined(mi1):\n                    for mi2 in range(self.s2):\n                        if mm_s._get(mi1, mi2):\n                            self.add_neighbors_refined(mi1, mi2)\n                    # self.push_refined_bool(mi1)\n                    self.ghosts_ewah._set_refined_array_ptr(mi1,\n                                                            &self.refined_ghosts_bool[0])\n                    for mi2 in range(self.s2):\n                        self.refined_ghosts_bool[mi2] = 0\n                else:\n                    self.add_neighbors_coarse(mi1)\n        # Add ghost zones to bool array in order\n        mm_g._set_coarse_array_ptr(&self.coarse_ghosts_bool[0])\n        for mi1 in range(self.s1):\n            self.coarse_ghosts_bool[mi1] = 0\n        self.refined_ghosts_list._fill_bool(mm_g)\n        mm_g._append(self.ghosts_ewah)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int fill_subcells_mi1(self,\n                               np.uint64_t nlevel,\n                               np.uint64_t ind1[3]) except -1:\n        cdef np.uint64_t imi, fmi\n        cdef np.uint64_t mi\n        cdef np.uint64_t shift_by = 3 * (self.bitmap.index_order1 - nlevel)\n        imi = encode_morton_64bit(ind1[0], ind1[1], ind1[2]) << shift_by\n        fmi = imi + (1 << shift_by)\n        for mi in range(imi, fmi):\n            self.add_coarse(mi, 1)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int fill_subcells_mi2(self,\n                               np.uint64_t nlevel,\n                               np.uint64_t mi1,\n                               np.uint64_t ind2[3]) except -1:\n        cdef np.uint64_t imi, fmi\n        cdef np.uint64_t shift_by = 3 * ((self.bitmap.index_order2 +\n                                          self.bitmap.index_order1) - nlevel)\n        imi = encode_morton_64bit(ind2[0], ind2[1], ind2[2]) << shift_by\n        fmi = imi + (1 << shift_by)\n        for mi2 in range(imi, fmi):\n            self.add_refined(mi1, mi2, 1)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int recursive_morton_mask(\n        self, np.int32_t level, np.float64_t pos[3],\n        np.float64_t dds[3], np.uint64_t mi1, np.uint64_t cur_ind[3]) except -1:\n        cdef np.uint64_t mi2\n        cdef np.float64_t npos[3]\n        cdef np.float64_t rpos[3]\n        cdef np.float64_t ndds[3]\n        cdef np.uint64_t nlevel\n        cdef np.uint64_t ncur_ind[3]\n        cdef np.uint64_t* zeros = [0, 0, 0]\n        cdef int i, j, k, sbbox\n        PyErr_CheckSignals()\n        for i in range(3):\n            ndds[i] = dds[i]/2\n        nlevel = level + 1\n        # Loop over octs\n        for i in range(2):\n            npos[0] = pos[0] + i*ndds[0]\n            rpos[0] = npos[0] + ndds[0]\n            ncur_ind[0] = (cur_ind[0] << 1) + i\n            for j in range(2):\n                npos[1] = pos[1] + j*ndds[1]\n                rpos[1] = npos[1] + ndds[1]\n                ncur_ind[1] = (cur_ind[1] << 1) + j\n                for k in range(2):\n                    npos[2] = pos[2] + k*ndds[2]\n                    rpos[2] = npos[2] + ndds[2]\n                    ncur_ind[2] = (cur_ind[2] << 1) + k\n                    # Only recurse into selected cells\n                    sbbox = self.selector.select_bbox_edge(npos, rpos)\n                    if sbbox == 0:\n                        continue\n                    if nlevel < self.order1:\n                        if sbbox == 1:\n                            self.fill_subcells_mi1(nlevel, ncur_ind)\n                        else:\n                            self.recursive_morton_mask(\n                                nlevel, npos, ndds, mi1, ncur_ind)\n                    elif nlevel == self.order1:\n                        mi1 = encode_morton_64bit(\n                            ncur_ind[0], ncur_ind[1], ncur_ind[2])\n                        if sbbox == 2: # an edge cell\n                            if self.is_refined(mi1) == 1:\n                                # note we pass zeros here in the last argument\n                                # this is because we now need to generate\n                                # *refined* indices above order1 so we need to\n                                # start a new running count of refined indices.\n                                #\n                                # note that recursive_morton_mask does not\n                                # mutate the last argument (a new index is\n                                # calculated in each stack frame) so this is\n                                # safe\n                                self.recursive_morton_mask(\n                                    nlevel, npos, ndds, mi1, zeros)\n                        self.add_coarse(mi1, sbbox)\n                        self.push_refined_bool(mi1)\n                    elif nlevel < (self.order1 + self.order2):\n                        if sbbox == 1:\n                            self.fill_subcells_mi2(nlevel, mi1, ncur_ind)\n                        else:\n                            self.recursive_morton_mask(\n                                nlevel, npos, ndds, mi1, ncur_ind)\n                    elif nlevel == (self.order1 + self.order2):\n                        mi2 = encode_morton_64bit(\n                            ncur_ind[0], ncur_ind[1], ncur_ind[2])\n                        self.add_refined(mi1, mi2, sbbox)\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void recursive_morton_files(self, np.int32_t level, np.float64_t pos[3],\n                                     np.float64_t dds[3], np.uint64_t mi1):\n        cdef np.uint64_t mi2\n        cdef np.float64_t npos[3]\n        cdef np.float64_t rpos[3]\n        cdef np.float64_t ndds[3]\n        cdef np.uint64_t nlevel\n        cdef np.float64_t DLE[3]\n        cdef np.uint64_t ind1[3]\n        cdef int i, j, k, m\n        for i in range(3):\n            ndds[i] = dds[i]/2\n        nlevel = level + 1\n        # Loop over octs\n        for i in range(2):\n            npos[0] = pos[0] + i*ndds[0]\n            rpos[0] = npos[0] + ndds[0]\n            for j in range(2):\n                npos[1] = pos[1] + j*ndds[1]\n                rpos[1] = npos[1] + ndds[1]\n                for k in range(2):\n                    npos[2] = pos[2] + k*ndds[2]\n                    rpos[2] = npos[2] + ndds[2]\n                    # Only recurse into selected cells\n                    if not self.selector.select_bbox(npos, rpos): continue\n                    if nlevel < self.order1:\n                        self.recursive_morton_files(nlevel, npos, ndds, mi1)\n                    elif nlevel == self.order1:\n                        mi1 = bounded_morton_dds(npos[0], npos[1], npos[2], self.DLE, ndds)\n                        if self.is_refined_files(mi1):\n                            self.recursive_morton_files(nlevel, npos, ndds, mi1)\n                        self.set_files_coarse(mi1)\n                    elif nlevel < (self.order1 + self.order2):\n                        self.recursive_morton_files(nlevel, npos, ndds, mi1)\n                    elif nlevel == (self.order1 + self.order2):\n                        decode_morton_64bit(mi1,ind1)\n                        for m in range(3):\n                            DLE[m] = self.DLE[m] + ndds[m]*ind1[m]*self.max_index2\n                        mi2 = bounded_morton_dds(npos[0], npos[1], npos[2], DLE, ndds)\n                        self.set_files_refined(mi1,mi2)\n\ncdef class ParticleBitmapOctreeContainer(SparseOctreeContainer):\n    cdef Oct** oct_list\n    cdef public int n_ref\n    cdef int loaded # Loaded with load_octree?\n    cdef np.uint8_t* _ptr_index_base_roots\n    cdef np.uint8_t* _ptr_index_base_octs\n    cdef np.uint64_t* _ptr_octs_per_root\n    cdef public np.uint8_t[:] _index_base_roots\n    cdef public np.uint8_t[:] _index_base_octs\n    cdef np.uint64_t[:] _octs_per_root\n    cdef public int overlap_cells\n    def __init__(self, domain_dimensions, domain_left_edge, domain_right_edge,\n                 int num_root, num_zones = 2):\n        super(ParticleBitmapOctreeContainer, self).__init__(\n            domain_dimensions, domain_left_edge, domain_right_edge,\n            num_zones)\n        self.loaded = 0\n        self.fill_style = \"o\"\n        self.partial_coverage = 2\n        self.overlap_cells = 0\n        # Now the overrides\n        self.max_level = -1\n        self.max_root = num_root\n        self.root_nodes = <OctKey*> malloc(sizeof(OctKey) * num_root)\n        self._ptr_index_base_roots = <np.uint8_t*> malloc(sizeof(np.uint8_t) * num_root)\n        self._ptr_octs_per_root = <np.uint64_t*> malloc(sizeof(np.uint64_t) * num_root)\n        for i in range(num_root):\n            self.root_nodes[i].key = -1\n            self.root_nodes[i].node = NULL\n            self._ptr_index_base_roots[i] = 1\n            self._ptr_octs_per_root[i] = 0\n        self._index_base_roots = <np.uint8_t[:num_root]> self._ptr_index_base_roots\n        self._octs_per_root = <np.uint64_t[:num_root]> self._ptr_octs_per_root\n\n    def allocate_domains(self, counts = None):\n        if counts is None:\n            counts = [self.max_root]\n        OctreeContainer.allocate_domains(self, counts)\n\n    def finalize(self):\n        # Assign domain ind\n        cdef SelectorObject selector = AlwaysSelector(None)\n        selector.overlap_cells = self.overlap_cells\n        cdef oct_visitors.AssignDomainInd visitor\n        visitor = oct_visitors.AssignDomainInd(self)\n        self.visit_all_octs(selector, visitor)\n        assert ((visitor.global_index+1)*visitor.nz == visitor.index)\n        # Copy indexes\n        self._ptr_index_base_octs = <np.uint8_t*> malloc(sizeof(np.uint8_t)*self.nocts)\n        self._index_base_octs = <np.uint8_t[:self.nocts]> self._ptr_index_base_octs\n        cdef np.int64_t nprev_octs = 0\n        cdef int i\n        for i in range(self.num_root):\n            self._index_base_octs[nprev_octs:(nprev_octs+self._octs_per_root[i])] = self._index_base_roots[i]\n            nprev_octs += self._octs_per_root[i]\n\n    cdef visit_assign(self, Oct *o, np.int64_t *lpos, int level, int *max_level,\n                      np.int64_t index_root):\n        cdef int i, j, k\n        if o.children == NULL:\n            self.oct_list[lpos[0]] = o\n            self._index_base_octs[lpos[0]] = self._index_base_roots[index_root]\n            lpos[0] += 1\n        max_level[0] = imax(max_level[0], level)\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    if o.children != NULL \\\n                       and o.children[cind(i,j,k)] != NULL:\n                        self.visit_assign(o.children[cind(i,j,k)], lpos,\n                                          level + 1, max_level, index_root)\n        return\n\n    cdef Oct* allocate_oct(self):\n        #Allocate the memory, set to NULL or -1\n        #We reserve space for n_ref particles, but keep\n        #track of how many are used with np initially 0\n        self.nocts += 1\n        cdef Oct *my_oct = <Oct*> malloc(sizeof(Oct))\n        my_oct.domain = -1\n        my_oct.file_ind = 0\n        my_oct.domain_ind = self.nocts - 1\n        my_oct.children = NULL\n        return my_oct\n\n    def get_index_base_octs(self, np.int64_t[:] domain_ind):\n        cdef np.int64_t ndst = np.max(domain_ind) + 1\n        ind = np.zeros(ndst, 'int64') - 1\n        self._get_index_base_octs(ind, domain_ind)\n        return ind[ind >= 0]\n\n    cdef void _get_index_base_octs(self, np.int64_t[:] ind, np.int64_t[:] domain_ind):\n        cdef SelectorObject selector = AlwaysSelector(None)\n        selector.overlap_cells = self.overlap_cells\n        cdef oct_visitors.IndexMaskMapOcts visitor\n        visitor = oct_visitors.IndexMaskMapOcts(self)\n        visitor.oct_mask = self._index_base_octs\n        visitor.oct_index = ind\n        visitor.map_domain_ind = domain_ind\n        self.visit_all_octs(selector, visitor)\n\n    def __dealloc__(self):\n        #Call the freemem ops on every ocy\n        #of the root mesh recursively\n        cdef int i\n        if self.root_nodes== NULL: return\n        if self.loaded == 0:\n            for i in range(self.max_root):\n                if self.root_nodes[i].node == NULL: continue\n                self.visit_free(&self.root_nodes.node[i], 0)\n            self.root_nodes = NULL\n        free(self.oct_list)\n        free(self._ptr_index_base_roots)\n        free(self._ptr_index_base_octs)\n        free(self._ptr_octs_per_root)\n        self.oct_list = NULL\n\n    cdef void visit_free(self, Oct *o, int free_this):\n        #Free the memory for this oct recursively\n        cdef int i, j, k\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    if o.children != NULL \\\n                       and o.children[cind(i,j,k)] != NULL:\n                        self.visit_free(o.children[cind(i,j,k)], 1)\n        if o.children != NULL:\n            free(o.children)\n        if free_this == 1:\n            free(o)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void recursive_add(self, Oct *o, np.ndarray[np.uint64_t, ndim=1] indices,\n                            int level, int *max_level, int domain_id, int *count):\n        cdef np.int64_t no = indices.shape[0], beg, end, nind\n        cdef np.int64_t index\n        cdef int i, j, k\n        cdef int ind[3]\n        cdef Oct *noct\n        beg = end = 0\n        if level > max_level[0]: max_level[0] = level\n        # Initialize children\n        if o.children == NULL:\n            o.children = <Oct **> malloc(sizeof(Oct *)*8)\n            for i in range(2):\n                for j in range(2):\n                    for k in range(2):\n                        o.children[cind(i,j,k)] = NULL\n                        # noct = self.allocate_oct()\n                        # noct.domain = o.domain\n                        # noct.file_ind = 0\n                        # o.children[cind(i,j,k)] = noct\n        # Loop through sets of particles with matching prefix at this level\n        while end < no:\n            beg = end\n            index = (indices[beg] >> ((ORDER_MAX - level)*3))\n            while (end < no) and (index == (indices[end] >> ((ORDER_MAX - level)*3))):\n                end += 1\n            nind = (end - beg)\n            # Add oct\n            for i in range(3):\n                ind[i] = ((index >> (2 - i)) & 1)\n            # noct = o.children[cind(ind[0],ind[1],ind[2])]\n            if o.children[cind(ind[0],ind[1],ind[2])] != NULL:\n                raise Exception('Child was already initialized...')\n            noct = self.allocate_oct()\n            noct.domain = o.domain\n            o.children[cind(ind[0],ind[1],ind[2])] = noct\n            # Don't add it to the list if it will be refined\n            if nind > self.n_ref and level < ORDER_MAX:\n                self.nocts -= 1\n                noct.domain_ind = -1 # overwritten by finalize\n            else:\n                count[0] += 1\n            noct.file_ind = o.file_ind\n            # noct.file_ind = nind\n            # o.file_ind = self.n_ref + 1\n            # Refine oct or add its children\n            if nind > self.n_ref and level < ORDER_MAX:\n                self.recursive_add(noct, indices[beg:end], level+1,\n                                   max_level, domain_id, count)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def add(self, np.ndarray[np.uint64_t, ndim=1] indices,\n             np.uint64_t order1, int domain_id = -1):\n        #Add this particle to the root oct\n        #Then if that oct has children, add it to them recursively\n        #If the child needs to be refined because of max particles, do so\n        cdef Oct *root = NULL\n        cdef np.int64_t no = indices.shape[0], beg, end, index\n        cdef int i\n        cdef int ind[3]\n        cdef np.uint64_t ind64[3]\n        cdef int max_level = self.max_level\n        # Note what we're doing here: we have decided the root will always be\n        # zero, since we're in a forest of octrees, where the root_mesh node is\n        # the level 0.  This means our morton indices should be made with\n        # respect to that, which means we need to keep a few different arrays\n        # of them.\n        cdef np.int64_t index_root = 0\n        cdef int root_count\n        beg = end = 0\n        self._octs_per_root[:] = 1 # Roots count regardless\n        while end < no:\n            # Determine number of octs with this prefix\n            beg = end\n            index = (indices[beg] >> ((ORDER_MAX - self.level_offset)*3))\n            while (end < no) and (index == (indices[end] >> ((ORDER_MAX - self.level_offset)*3))):\n                end += 1\n            # Find root for prefix\n            decode_morton_64bit(index, ind64)\n            for i in range(3):\n                ind[i] = ind64[i]\n            while (index_root < self.num_root) and \\\n                  (self.ipos_to_key(ind) != self.root_nodes[index_root].key):\n                index_root += 1\n            if index_root >= self.num_root:\n                raise Exception('No root found for {},{},{}'.format(ind[0],ind[1],ind[2]))\n            root = self.root_nodes[index_root].node\n            # self.get_root(ind, &root)\n            # if root == NULL:\n            #     raise Exception('No root found for {},{},{}'.format(ind[0],ind[1],ind[2]))\n            root.file_ind = index_root\n            # Refine root as necessary\n            if (end - beg) > self.n_ref:\n                root_count = 0\n                self.nocts -= 1\n                self.recursive_add(root, indices[beg:end], self.level_offset+1,\n                                   &max_level, domain_id, &root_count)\n                self._octs_per_root[index_root] = <np.uint64_t>root_count\n        self.max_level = max_level\n        assert(self.nocts == np.sum(self._octs_per_root))\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef Oct *refine_oct(self, Oct *o, np.uint64_t index, int level):\n        #Allocate and initialize child octs\n        #Attach particles to child octs\n        #Remove particles from this oct entirely\n        cdef int i, j, k\n        cdef int ind[3]\n        cdef Oct *noct\n\n        # Initialize empty children\n        if o.children == NULL:\n            o.children = <Oct **> malloc(sizeof(Oct *)*8)\n\n        # This version can be used to just add the child containing the index\n        #     for i in range(2):\n        #         for j in range(2):\n        #             for k in range(2):\n        #                 o.children[cind(i,j,k)] = NULL\n        # # Only allocate and count the indexed oct\n        # for i in range(3):\n        #     ind[i] = (index >> ((ORDER_MAX - level)*3 + (2 - i))) & 1\n\n        # noct = self.allocate_oct()\n        # noct.domain = o.domain\n        # noct.file_ind = 0\n        # o.children[cind(ind[0],ind[1],ind[2])] = noct\n        # o.file_ind = self.n_ref + 1\n\n\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    noct = self.allocate_oct()\n                    noct.domain = o.domain\n                    noct.file_ind = 0\n                    o.children[cind(i,j,k)] = noct\n        o.file_ind = self.n_ref + 1\n        for i in range(3):\n            ind[i] = (index >> ((ORDER_MAX - level)*3 + (2 - i))) & 1\n        noct = o.children[cind(ind[0],ind[1],ind[2])]\n        return noct\n\n    cdef void filter_particles(self, Oct *o, np.uint64_t *data, np.int64_t p,\n                               int level):\n        # Now we look at the last nref particles to decide where they go.\n        cdef int n = imin(p, self.n_ref)\n        cdef np.uint64_t *arr = data + imax(p - self.n_ref, 0)\n        # Now we figure out our prefix, which is the oct address at this level.\n        # As long as we're actually in Morton order, we do not need to worry\n        # about *any* of the other children of the oct.\n        prefix1 = data[p] >> (ORDER_MAX - level)*3\n        for i in range(n):\n            prefix2 = arr[i] >> (ORDER_MAX - level)*3\n            if (prefix1 == prefix2):\n                o.file_ind += 1\n"
  },
  {
    "path": "yt/geometry/particle_smooth.pxd",
    "content": "\"\"\"\nParticle Deposition onto Octs\n\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\nimport numpy as np\n\ncimport cython\nfrom libc.math cimport sqrt\nfrom libc.stdlib cimport free, malloc, qsort\n\nfrom yt.utilities.lib.distance_queue cimport (\n    DistanceQueue,\n    Neighbor_compare,\n    NeighborList,\n    r2dist,\n)\nfrom yt.utilities.lib.fp_utils cimport *\n\nfrom .oct_container cimport Oct, OctreeContainer\nfrom .particle_deposit cimport get_kernel_func, gind, kernel_func\n\n\ncdef extern from \"platform_dep.h\":\n    void *alloca(int)\n\ncdef class ParticleSmoothOperation:\n    # We assume each will allocate and define their own temporary storage\n    cdef kernel_func sph_kernel\n    cdef public object nvals\n    cdef np.float64_t DW[3]\n    cdef int nfields\n    cdef int maxn\n    cdef bint periodicity[3]\n    # Note that we are preallocating here, so this is *not* threadsafe.\n    cdef void (*pos_setup)(np.float64_t ipos[3], np.float64_t opos[3])\n    cdef void neighbor_process(self, int dim[3], np.float64_t left_edge[3],\n                               np.float64_t dds[3], np.float64_t[:,:] ppos,\n                               np.float64_t **fields,\n                               np.int64_t[:] doffs, np.int64_t **nind,\n                               np.int64_t[:] pinds, np.int64_t[:] pcounts,\n                               np.int64_t offset, np.float64_t **index_fields,\n                               OctreeContainer octree, np.int64_t domain_id,\n                               int *nsize, np.float64_t[:,:] oct_left_edges,\n                               np.float64_t[:,:] oct_dds, DistanceQueue dq)\n    cdef int neighbor_search(self, np.float64_t pos[3], OctreeContainer octree,\n                             np.int64_t **nind, int *nsize,\n                             np.int64_t nneighbors, np.int64_t domain_id,\n                             Oct **oct = ?, int extra_layer = ?)\n    cdef void neighbor_process_particle(self, np.float64_t cpos[3],\n                               np.float64_t[:,:] ppos,\n                               np.float64_t **fields,\n                               np.int64_t[:] doffs, np.int64_t **nind,\n                               np.int64_t[:] pinds, np.int64_t[:] pcounts,\n                               np.int64_t offset,\n                               np.float64_t **index_fields,\n                               OctreeContainer octree, np.int64_t domain_id,\n                               int *nsize, DistanceQueue dq)\n    cdef void neighbor_find(self,\n                            np.int64_t nneighbors,\n                            np.int64_t *nind,\n                            np.int64_t[:] doffs,\n                            np.int64_t[:] pcounts,\n                            np.int64_t[:] pinds,\n                            np.float64_t[:,:] ppos,\n                            np.float64_t cpos[3],\n                            np.float64_t[:,:] oct_left_edges,\n                            np.float64_t[:,:] oct_dds, DistanceQueue dq)\n    cdef void process(self, np.int64_t offset, int i, int j, int k,\n                      int dim[3], np.float64_t cpos[3], np.float64_t **fields,\n                      np.float64_t **index_fields, DistanceQueue dq)\n"
  },
  {
    "path": "yt/geometry/particle_smooth.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\n\"\"\"\nParticle smoothing in cells\n\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\nimport numpy as np\n\ncimport cython\nfrom cpython.exc cimport PyErr_CheckSignals\nfrom libc.math cimport cos, sin, sqrt\nfrom libc.stdlib cimport free, malloc, realloc\n\nfrom .oct_container cimport Oct, OctInfo, OctreeContainer\n\n\ncdef void spherical_coord_setup(np.float64_t ipos[3], np.float64_t opos[3]):\n    opos[0] = ipos[0] * sin(ipos[1]) * cos(ipos[2])\n    opos[1] = ipos[0] * sin(ipos[1]) * sin(ipos[2])\n    opos[2] = ipos[0] * cos(ipos[1])\n\ncdef void cart_coord_setup(np.float64_t ipos[3], np.float64_t opos[3]):\n    opos[0] = ipos[0]\n    opos[1] = ipos[1]\n    opos[2] = ipos[2]\n\ncdef class ParticleSmoothOperation:\n    def __init__(self, nvals, nfields, max_neighbors, kernel_name):\n        # This is the set of cells, in grids, blocks or octs, we are handling.\n        self.nvals = nvals\n        self.nfields = nfields\n        self.maxn = max_neighbors\n        self.sph_kernel = get_kernel_func(kernel_name)\n\n    def initialize(self, *args):\n        raise NotImplementedError\n\n    def finalize(self, *args):\n        raise NotImplementedError\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    def process_octree(self, OctreeContainer mesh_octree,\n                     np.int64_t [:] mdom_ind,\n                     np.float64_t[:,:] positions,\n                     np.float64_t[:,:] oct_positions,\n                     fields = None, int domain_id = -1,\n                     int domain_offset = 0,\n                     periodicity = (True, True, True),\n                     index_fields = None,\n                     OctreeContainer particle_octree = None,\n                     np.int64_t [:] pdom_ind = None,\n                     geometry = \"cartesian\"):\n        # This will be a several-step operation.\n        #\n        # We first take all of our particles and assign them to Octs.  If they\n        # are not in an Oct, we will assume they are out of bounds.  Note that\n        # this means that if we have loaded neighbor particles for which an Oct\n        # does not exist, we are going to be discarding them -- so sparse\n        # octrees will need to ensure that neighbor octs *exist*.  Particles\n        # will be assigned in a new NumPy array.  Note that this incurs\n        # overhead, but reduces complexity as we will now be able to use\n        # argsort.\n        #\n        # After the particles have been assigned to Octs, we process each Oct\n        # individually.  We will do this by calling \"get\" for the *first*\n        # particle in each set of Octs in the sorted list.  After this, we get\n        # neighbors for each Oct.\n        #\n        # Now, with the set of neighbors (and thus their indices) we allocate\n        # an array of particles and their fields, fill these in, and call our\n        # process function.\n        #\n        # This is not terribly efficient -- for starters, the neighbor function\n        # is not the most efficient yet.  We will also need to handle some\n        # mechanism of an expandable array for holding pointers to Octs, so\n        # that we can deal with >27 neighbors.\n        if particle_octree is None:\n            particle_octree = mesh_octree\n            pdom_ind = mdom_ind\n        cdef int nf, i, j\n        cdef int dims[3]\n        cdef np.float64_t **field_pointers\n        cdef np.float64_t pos[3]\n        cdef int nsize = 0\n        cdef np.int64_t *nind = NULL\n        cdef OctInfo moi\n        cdef Oct *oct\n        cdef np.int64_t offset, poff\n        cdef np.int64_t moff_p, moff_m\n        cdef np.int64_t[:] pind, doff, pdoms, pcount\n        cdef np.ndarray[np.float64_t, ndim=1] tarr\n        cdef np.ndarray[np.float64_t, ndim=4] iarr\n        cdef np.float64_t[:,:] cart_positions\n        cdef np.float64_t[:,:] oct_left_edges, oct_dds\n        cdef OctInfo oinfo\n        if geometry == \"cartesian\":\n            self.pos_setup = cart_coord_setup\n            cart_positions = positions\n        elif geometry == \"spherical\":\n            self.pos_setup = spherical_coord_setup\n            cart_positions = np.empty((positions.shape[0], 3), dtype=\"float64\")\n\n            cart_positions[:,0] = positions[:,0] * \\\n                                  np.sin(positions[:,1]) * \\\n                                  np.cos(positions[:,2])\n            cart_positions[:,1] = positions[:,0] * \\\n                                  np.sin(positions[:,1]) * \\\n                                  np.sin(positions[:,2])\n            cart_positions[:,2] = positions[:,0] * \\\n                                  np.cos(positions[:,1])\n            periodicity = (False, False, False)\n        else:\n            raise NotImplementedError\n        dims[0] = dims[1] = dims[2] = mesh_octree.nz\n        cdef int nz = dims[0] * dims[1] * dims[2]\n        # pcount is the number of particles per oct.\n        pcount = np.zeros_like(pdom_ind)\n        oct_left_edges = np.zeros((pdom_ind.shape[0], 3), dtype='float64')\n        oct_dds = np.zeros_like(oct_left_edges)\n        # doff is the offset to a given oct in the sorted particles.\n        doff = np.zeros_like(pdom_ind) - 1\n        moff_p = particle_octree.get_domain_offset(domain_id + domain_offset)\n        moff_m = mesh_octree.get_domain_offset(domain_id + domain_offset)\n        # pdoms points particles at their octs.  So the value in this array, for\n        # a given index, is the local oct index.\n        pdoms = np.zeros(positions.shape[0], dtype=\"int64\") - 1\n        nf = len(fields)\n        if fields is None:\n            fields = []\n        field_pointers = <np.float64_t**> alloca(sizeof(np.float64_t *) * nf)\n        for i in range(nf):\n            tarr = fields[i]\n            field_pointers[i] = <np.float64_t *> tarr.data\n        if index_fields is None:\n            index_fields = []\n        nf = len(index_fields)\n        index_field_pointers = <np.float64_t**> alloca(sizeof(np.float64_t *) * nf)\n        for i in range(nf):\n            iarr = index_fields[i]\n            index_field_pointers[i] = <np.float64_t *> iarr.data\n        for i in range(3):\n            self.DW[i] = (mesh_octree.DRE[i] - mesh_octree.DLE[i])\n            self.periodicity[i] = periodicity[i]\n        cdef np.float64_t factor = particle_octree.nz\n        for i in range(positions.shape[0]):\n            for j in range(3):\n                pos[j] = positions[i, j]\n            oct = particle_octree.get(pos, &oinfo)\n            if oct == NULL or (domain_id > 0 and oct.domain != domain_id):\n                continue\n            # Note that this has to be our local index, not our in-file index.\n            # This is the particle count, which we'll use once we have sorted\n            # the particles to calculate the offsets into each oct's particles.\n            offset = oct.domain_ind - moff_p\n            pcount[offset] += 1\n            pdoms[i] = offset # We store the *actual* offset.\n            # store oct positions and dds to avoid searching for neighbors\n            # in octs that we know are too far away\n            for j in range(3):\n                oct_left_edges[offset, j] = oinfo.left_edge[j]\n                oct_dds[offset, j] = oinfo.dds[j] * factor\n        # Now we have oct assignments.  Let's sort them.\n        # Note that what we will be providing to our processing functions will\n        # actually be indirectly-sorted fields.  This preserves memory at the\n        # expense of additional pointer lookups.\n        pind = np.asarray(np.argsort(pdoms), dtype='int64', order='C')\n        # So what this means is that we now have all the oct-0 particle indices\n        # in order, then the oct-1, etc etc.\n        # This now gives us the indices to the particles for each domain.\n        for i in range(positions.shape[0]):\n            # This value, poff, is the index of the particle in the *unsorted*\n            # arrays.\n            poff = pind[i]\n            offset = pdoms[poff]\n            # If we have yet to assign the starting index to this oct, we do so\n            # now.\n            if doff[offset] < 0: doff[offset] = i\n        #print(domain_id, domain_offset, moff_p, moff_m)\n        #raise RuntimeError\n        # Now doff is full of offsets to the first entry in the pind that\n        # refers to that oct's particles.\n        cdef np.ndarray[np.uint8_t, ndim=1] visited\n        visited = np.zeros(mdom_ind.shape[0], dtype=\"uint8\")\n        cdef int nproc = 0\n        # This should be thread-private if we ever go to OpenMP\n        cdef DistanceQueue dist_queue = DistanceQueue(self.maxn)\n        dist_queue._setup(self.DW, self.periodicity)\n        for i in range(oct_positions.shape[0]):\n            if (i % 10000) == 0:\n                PyErr_CheckSignals()\n            for j in range(3):\n                pos[j] = oct_positions[i, j]\n            oct = mesh_octree.get(pos, &moi)\n            offset = mdom_ind[oct.domain_ind - moff_m] * nz\n            if visited[oct.domain_ind - moff_m] == 1: continue\n            visited[oct.domain_ind - moff_m] = 1\n            if offset < 0: continue\n            nproc += 1\n            self.neighbor_process(\n                dims, moi.left_edge, moi.dds, cart_positions, field_pointers, doff,\n                &nind, pind, pcount, offset, index_field_pointers,\n                particle_octree, domain_id, &nsize, oct_left_edges,\n                oct_dds, dist_queue)\n        #print(\"VISITED\", visited.sum(), visited.size,)\n        #print(100.0*float(visited.sum())/visited.size)\n        if nind != NULL:\n            free(nind)\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    def process_particles(self, OctreeContainer particle_octree,\n                     np.ndarray[np.int64_t, ndim=1] pdom_ind,\n                     np.ndarray[np.float64_t, ndim=2] positions,\n                     fields = None, int domain_id = -1,\n                     int domain_offset = 0,\n                     periodicity = (True, True, True),\n                     geometry = \"cartesian\"):\n        # The other functions in this base class process particles in a way\n        # that results in a modification to the *mesh*.  This function is\n        # designed to process neighboring particles in such a way that a new\n        # *particle* field is defined -- this means that new particle\n        # attributes (*not* mesh attributes) can be created that rely on the\n        # values of nearby particles.  For instance, a smoothing kernel, or a\n        # nearest-neighbor field.\n        cdef int nf, i, j, k\n        cdef np.float64_t **field_pointers\n        cdef np.float64_t pos[3]\n        cdef int nsize = 0\n        cdef np.int64_t *nind = NULL\n        cdef Oct *oct\n        cdef np.int64_t offset\n        cdef np.int64_t moff_p, pind0, poff\n        cdef np.int64_t[:] pind, doff, pdoms, pcount\n        cdef np.ndarray[np.float64_t, ndim=1] tarr\n        cdef np.ndarray[np.float64_t, ndim=2] cart_positions\n        if geometry == \"cartesian\":\n            self.pos_setup = cart_coord_setup\n            cart_positions = positions\n        elif geometry == \"spherical\":\n            self.pos_setup = spherical_coord_setup\n            cart_positions = np.empty((positions.shape[0], 3), dtype=\"float64\")\n\n            cart_positions[:,0] = positions[:,0] * \\\n                                  np.sin(positions[:,1]) * \\\n                                  np.cos(positions[:,2])\n            cart_positions[:,1] = positions[:,0] * \\\n                                  np.sin(positions[:,1]) * \\\n                                  np.sin(positions[:,2])\n            cart_positions[:,2] = positions[:,0] * \\\n                                  np.cos(positions[:,1])\n            periodicity = (False, False, False)\n        else:\n            raise NotImplementedError\n        pcount = np.zeros_like(pdom_ind)\n        doff = np.zeros_like(pdom_ind) - 1\n        moff_p = particle_octree.get_domain_offset(domain_id + domain_offset)\n        pdoms = np.zeros(positions.shape[0], dtype=\"int64\") - 1\n        nf = len(fields)\n        if fields is None:\n            fields = []\n        field_pointers = <np.float64_t**> alloca(sizeof(np.float64_t *) * nf)\n        for i in range(nf):\n            tarr = fields[i]\n            field_pointers[i] = <np.float64_t *> tarr.data\n        for i in range(3):\n            self.DW[i] = (particle_octree.DRE[i] - particle_octree.DLE[i])\n            self.periodicity[i] = periodicity[i]\n        for i in range(positions.shape[0]):\n            for j in range(3):\n                pos[j] = positions[i, j]\n            oct = particle_octree.get(pos)\n            if oct == NULL or (domain_id > 0 and oct.domain != domain_id):\n                continue\n            # Note that this has to be our local index, not our in-file index.\n            # This is the particle count, which we'll use once we have sorted\n            # the particles to calculate the offsets into each oct's particles.\n            offset = oct.domain_ind - moff_p\n            pcount[offset] += 1\n            pdoms[i] = offset # We store the *actual* offset.\n        # Now we have oct assignments.  Let's sort them.\n        # Note that what we will be providing to our processing functions will\n        # actually be indirectly-sorted fields.  This preserves memory at the\n        # expense of additional pointer lookups.\n        pind = np.asarray(np.argsort(pdoms), dtype='int64', order='C')\n        # So what this means is that we now have all the oct-0 particle indices\n        # in order, then the oct-1, etc etc.\n        # This now gives us the indices to the particles for each domain.\n        for i in range(positions.shape[0]):\n            # This value, poff, is the index of the particle in the *unsorted*\n            # arrays.\n            poff = pind[i]\n            offset = pdoms[poff]\n            # If we have yet to assign the starting index to this oct, we do so\n            # now.\n            if doff[offset] < 0: doff[offset] = i\n        #print(domain_id, domain_offset, moff_p, moff_m)\n        #raise RuntimeError\n        # Now doff is full of offsets to the first entry in the pind that\n        # refers to that oct's particles.\n        # This should be thread-private if we ever go to OpenMP\n        cdef DistanceQueue dist_queue = DistanceQueue(self.maxn)\n        dist_queue._setup(self.DW, self.periodicity)\n        for i in range(doff.shape[0]):\n            if doff[i] < 0: continue\n            offset = pind[doff[i]]\n            for j in range(3):\n                pos[j] = positions[offset, j]\n            for j in range(pcount[i]):\n                pind0 = pind[doff[i] + j]\n                for k in range(3):\n                    pos[k] = positions[pind0, k]\n                self.neighbor_process_particle(pos, cart_positions, field_pointers,\n                            doff, &nind, pind, pcount, pind0,\n                            NULL, particle_octree, domain_id, &nsize,\n                            dist_queue)\n        #print(\"VISITED\", visited.sum(), visited.size,)\n        #print(100.0*float(visited.sum())/visited.size)\n        if nind != NULL:\n            free(nind)\n\n    cdef int neighbor_search(self, np.float64_t pos[3], OctreeContainer octree,\n                             np.int64_t **nind, int *nsize,\n                             np.int64_t nneighbors, np.int64_t domain_id,\n                             Oct **oct = NULL, int extra_layer = 0):\n        cdef OctInfo oi\n        cdef Oct *ooct\n        cdef Oct **neighbors\n        cdef Oct **first_layer\n        cdef int j, total_neighbors = 0, initial_layer = 0\n        cdef int layer_ind = 0\n        cdef np.int64_t moff = octree.get_domain_offset(domain_id)\n        ooct = octree.get(pos, &oi)\n        if oct != NULL and ooct == oct[0]:\n            return nneighbors\n        oct[0] = ooct\n        if nind[0] == NULL:\n            nsize[0] = 27\n            nind[0] = <np.int64_t *> malloc(sizeof(np.int64_t)*nsize[0])\n        # This is our \"seed\" set of neighbors.  If we are asked to, we will\n        # create a master list of neighbors that is much bigger and includes\n        # everything.\n        layer_ind = 0\n        first_layer = NULL\n        while 1:\n            neighbors = octree.neighbors(&oi, &nneighbors, ooct, self.periodicity)\n            # Now we have all our neighbors.  And, we should be set for what\n            # else we need to do.\n            if total_neighbors + nneighbors > nsize[0]:\n                nind[0] = <np.int64_t *> realloc(\n                    nind[0], sizeof(np.int64_t)*(nneighbors + total_neighbors))\n                nsize[0] = nneighbors + total_neighbors\n            for j in range(nneighbors):\n                # Particle octree neighbor indices\n                nind[0][j + total_neighbors] = neighbors[j].domain_ind - moff\n            total_neighbors += nneighbors\n            if extra_layer == 0:\n                # Not adding on any additional layers here.\n                free(neighbors)\n                neighbors = NULL\n                break\n            if initial_layer == 0:\n                initial_layer = nneighbors\n                first_layer = neighbors\n            else:\n                # Allocated internally; we free this in the loops if we aren't\n                # tracking it\n                free(neighbors)\n                neighbors = NULL\n            ooct = first_layer[layer_ind]\n            layer_ind += 1\n            if layer_ind == initial_layer:\n                neighbors\n                break\n\n\n        for j in range(total_neighbors):\n            # Particle octree neighbor indices\n            if nind[0][j] == -1: continue\n            for n in range(j):\n                if nind[0][j] == nind[0][n]:\n                    nind[0][j] = -1\n        # This is allocated by the neighbors function, so we deallocate it.\n        if first_layer != NULL:\n            free(first_layer)\n        return total_neighbors\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    def process_grid(self, gobj,\n                     np.ndarray[np.float64_t, ndim=2] positions,\n                     fields = None):\n        raise NotImplementedError\n\n    cdef void process(self, np.int64_t offset, int i, int j, int k,\n                      int dim[3], np.float64_t cpos[3], np.float64_t **fields,\n                      np.float64_t **ifields, DistanceQueue dq):\n        raise NotImplementedError\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void neighbor_find(self,\n                            np.int64_t nneighbors,\n                            np.int64_t *nind,\n                            np.int64_t[:] doffs,\n                            np.int64_t[:] pcounts,\n                            np.int64_t[:] pinds,\n                            np.float64_t[:,:] ppos,\n                            np.float64_t cpos[3],\n                            np.float64_t[:,:] oct_left_edges,\n                            np.float64_t[:,:] oct_dds,\n                            DistanceQueue dq\n                            ):\n        # We are now given the number of neighbors, the indices into the\n        # domains for them, and the number of particles for each.\n        cdef int ni, i, j, k\n        cdef np.int64_t offset, pn, pc\n        cdef np.float64_t pos[3]\n        cdef np.float64_t ex[2]\n        cdef np.float64_t DR[2]\n        cdef np.float64_t r2_trunc, r2, dist\n        dq.neighbor_reset()\n        for ni in range(nneighbors):\n            if nind[ni] == -1: continue\n            # terminate early if all 8 corners of oct are farther away than\n            # most distant currently known neighbor\n            if oct_left_edges != None and dq.curn == dq.maxn:\n                r2_trunc = dq.neighbors[dq.curn - 1].r2\n                # iterate over each dimension in the outer loop so we can\n                # consolidate temporary storage\n                # What this next bit does is figure out which component is the\n                # closest, of each possible permutation.\n                # k here is the dimension\n                r2 = 0.0\n                for k in range(3):\n                    # We start at left edge, then do halfway, then right edge.\n                    ex[0] = oct_left_edges[nind[ni], k]\n                    ex[1] = ex[0] + oct_dds[nind[ni], k]\n                    # There are three possibilities; we are between, left-of,\n                    # or right-of the extrema.  Thanks to\n                    # http://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point\n                    # for some help.  This has been modified to account for\n                    # periodicity.\n                    dist = 0.0\n                    DR[0] = (ex[0] - cpos[k])\n                    DR[1] = (cpos[k] - ex[1])\n                    for j in range(2):\n                        if not self.periodicity[k]:\n                            pass\n                        elif (DR[j] > self.DW[k]/2.0):\n                            DR[j] -= self.DW[k]\n                        elif (DR[j] < -self.DW[k]/2.0):\n                            DR[j] += self.DW[k]\n                        dist = fmax(dist, DR[j])\n                    r2 += dist*dist\n                if r2 > r2_trunc:\n                    continue\n            offset = doffs[nind[ni]]\n            pc = pcounts[nind[ni]]\n            for i in range(pc):\n                pn = pinds[offset + i]\n                for j in range(3):\n                    pos[j] = ppos[pn, j]\n                dq.neighbor_eval(pn, pos, cpos)\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void neighbor_process(self, int dim[3], np.float64_t left_edge[3],\n                               np.float64_t dds[3], np.float64_t[:,:] ppos,\n                               np.float64_t **fields,\n                               np.int64_t [:] doffs, np.int64_t **nind,\n                               np.int64_t [:] pinds, np.int64_t[:] pcounts,\n                               np.int64_t offset,\n                               np.float64_t **index_fields,\n                               OctreeContainer octree, np.int64_t domain_id,\n                               int *nsize, np.float64_t[:,:] oct_left_edges,\n                               np.float64_t[:,:] oct_dds,\n                               DistanceQueue dq):\n        # Note that we assume that fields[0] == smoothing length in the native\n        # units supplied.  We can now iterate over every cell in the block and\n        # every particle to find the nearest.  We will use a priority heap.\n        cdef int i, j, k, ntot, nntot, m, nneighbors\n        cdef np.float64_t cpos[3]\n        cdef np.float64_t opos[3]\n        cdef Oct* oct = NULL\n        cpos[0] = left_edge[0] + 0.5*dds[0]\n        for i in range(dim[0]):\n            cpos[1] = left_edge[1] + 0.5*dds[1]\n            for j in range(dim[1]):\n                cpos[2] = left_edge[2] + 0.5*dds[2]\n                for k in range(dim[2]):\n                    self.pos_setup(cpos, opos)\n                    nneighbors = self.neighbor_search(opos, octree,\n                                    nind, nsize, nneighbors, domain_id, &oct, 0)\n                    self.neighbor_find(nneighbors, nind[0], doffs, pcounts,\n                                       pinds, ppos, opos, oct_left_edges,\n                                       oct_dds, dq)\n                    # Now we have all our neighbors in our neighbor list.\n                    if dq.curn <-1*dq.maxn:\n                        ntot = nntot = 0\n                        for m in range(nneighbors):\n                            if nind[0][m] < 0: continue\n                            nntot += 1\n                            ntot += pcounts[nind[0][m]]\n                        print(\"SOMETHING WRONG\", dq.curn, nneighbors, ntot, nntot)\n                    self.process(offset, i, j, k, dim, opos, fields,\n                                 index_fields, dq)\n                    cpos[2] += dds[2]\n                cpos[1] += dds[1]\n            cpos[0] += dds[0]\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void neighbor_process_particle(self, np.float64_t cpos[3],\n                               np.float64_t[:,:] ppos,\n                               np.float64_t **fields,\n                               np.int64_t[:] doffs, np.int64_t **nind,\n                               np.int64_t[:] pinds, np.int64_t[:] pcounts,\n                               np.int64_t offset,\n                               np.float64_t **index_fields,\n                               OctreeContainer octree,\n                               np.int64_t domain_id, int *nsize,\n                               DistanceQueue dq):\n        # Note that we assume that fields[0] == smoothing length in the native\n        # units supplied.  We can now iterate over every cell in the block and\n        # every particle to find the nearest.  We will use a priority heap.\n        cdef int i, j, k\n        cdef int dim[3]\n        cdef Oct *oct = NULL\n        cdef np.int64_t nneighbors = 0\n        i = j = k = 0\n        dim[0] = dim[1] = dim[2] = 1\n        cdef np.float64_t opos[3]\n        self.pos_setup(cpos, opos)\n        nneighbors = self.neighbor_search(opos, octree,\n                        nind, nsize, nneighbors, domain_id, &oct, 0)\n        self.neighbor_find(nneighbors, nind[0], doffs, pcounts, pinds, ppos,\n                           opos, None, None, dq)\n        self.process(offset, i, j, k, dim, opos, fields, index_fields, dq)\n\ncdef class VolumeWeightedSmooth(ParticleSmoothOperation):\n    # This smoothing function evaluates the field, *without* normalization, at\n    # every point in the *mesh*.  Applying a normalization results in\n    # non-conservation of mass when smoothing density; to avoid this, we do not\n    # apply this normalization factor.  The SPLASH paper\n    # (http://arxiv.org/abs/0709.0832v1) discusses this in detail; what we are\n    # applying here is equation 6, with variable smoothing lengths (eq 13).\n    cdef np.float64_t **fp\n    cdef public object vals\n    def initialize(self):\n        cdef int i\n        if self.nfields < 4:\n            # We need four fields -- the mass should be the first, then the\n            # smoothing length for particles, the normalization factor to\n            # ensure mass conservation, then the field we're smoothing.\n            raise RuntimeError\n        cdef np.ndarray tarr\n        self.fp = <np.float64_t **> malloc(\n            sizeof(np.float64_t *) * (self.nfields - 3))\n        self.vals = []\n        # We usually only allocate one field; if we are doing multiple field,\n        # single-pass smoothing, then we might have more.\n        for i in range(self.nfields - 3):\n            tarr = np.zeros(self.nvals, dtype=\"float64\", order=\"F\")\n            self.vals.append(tarr)\n            self.fp[i] = <np.float64_t *> tarr.data\n\n    def finalize(self):\n        free(self.fp)\n        return self.vals\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void process(self, np.int64_t offset, int i, int j, int k,\n                      int dim[3], np.float64_t cpos[3], np.float64_t **fields,\n                      np.float64_t **index_fields, DistanceQueue dq):\n        # We have our i, j, k for our cell, as well as the cell position.\n        # We also have a list of neighboring particles with particle numbers.\n        cdef int n, fi\n        cdef np.float64_t weight, r2, val, hsml, dens, mass, max_r\n        cdef np.float64_t max_hsml, ihsml, ihsml3, kern\n        cdef np.int64_t pn\n        # We get back our mass\n        # rho_i = sum(j = 1 .. n) m_j * W_ij\n        max_r = sqrt(dq.neighbors[dq.curn-1].r2)\n        max_hsml = index_fields[0][gind(i,j,k,dim) + offset]\n        for n in range(dq.curn):\n            # No normalization for the moment.\n            # fields[0] is the smoothing length.\n            r2 = dq.neighbors[n].r2\n            pn = dq.neighbors[n].pn\n            # Smoothing kernel weight function\n            mass = fields[0][pn]\n            hsml = fields[1][pn]\n            dens = fields[2][pn]\n            if hsml < 0:\n                hsml = max_r\n            if hsml == 0: continue\n            ihsml = 1.0/hsml\n            hsml = fmax(max_hsml/2.0, hsml)\n            ihsml3 = ihsml*ihsml*ihsml\n            # Usually this density has been computed\n            if dens == 0.0: continue\n            weight = (mass / dens) * ihsml3\n            kern = self.sph_kernel(sqrt(r2) * ihsml)\n            weight *= kern\n            # Mass of the particle times the value\n            for fi in range(self.nfields - 3):\n                val = fields[fi + 3][pn]\n                self.fp[fi][gind(i,j,k,dim) + offset] += val * weight\n        return\n\nvolume_weighted_smooth = VolumeWeightedSmooth\n\ncdef class NearestNeighborSmooth(ParticleSmoothOperation):\n    cdef np.float64_t *fp\n    cdef public object vals\n    def initialize(self):\n        cdef np.ndarray tarr\n        assert(self.nfields == 1)\n        tarr = np.zeros(self.nvals, dtype=\"float64\", order=\"F\")\n        self.vals = tarr\n        self.fp = <np.float64_t *> tarr.data\n\n    def finalize(self):\n        return self.vals\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void process(self, np.int64_t offset, int i, int j, int k,\n                      int dim[3], np.float64_t cpos[3], np.float64_t **fields,\n                      np.float64_t **index_fields, DistanceQueue dq):\n        # We have our i, j, k for our cell, as well as the cell position.\n        # We also have a list of neighboring particles with particle numbers.\n        cdef np.int64_t pn\n        # We get back our mass\n        # rho_i = sum(j = 1 .. n) m_j * W_ij\n        pn = dq.neighbors[0].pn\n        self.fp[gind(i,j,k,dim) + offset] = fields[0][pn]\n        #self.fp[gind(i,j,k,dim) + offset] = dq.neighbors[0].r2\n        return\n\nnearest_smooth = NearestNeighborSmooth\n\ncdef class IDWInterpolationSmooth(ParticleSmoothOperation):\n    cdef np.float64_t *fp\n    cdef public int p2\n    cdef public object vals\n    def initialize(self):\n        cdef np.ndarray tarr\n        assert(self.nfields == 1)\n        tarr = np.zeros(self.nvals, dtype=\"float64\", order=\"F\")\n        self.vals = tarr\n        self.fp = <np.float64_t *> tarr.data\n        self.p2 = 2 # Power, for IDW, in units of 2.  So we only do even p's.\n\n    def finalize(self):\n        return self.vals\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void process(self, np.int64_t offset, int i, int j, int k,\n                      int dim[3], np.float64_t cpos[3], np.float64_t **fields,\n                      np.float64_t **index_fields, DistanceQueue dq):\n        # We have our i, j, k for our cell, as well as the cell position.\n        # We also have a list of neighboring particles with particle numbers.\n        cdef np.int64_t pn, ni, di\n        cdef np.float64_t total_weight = 0.0, total_value = 0.0, r2, val, w\n        # We're going to do a very simple IDW average\n        if dq.neighbors[0].r2 == 0.0:\n            pn = dq.neighbors[0].pn\n            self.fp[gind(i,j,k,dim) + offset] = fields[0][pn]\n        for ni in range(dq.curn):\n            r2 = dq.neighbors[ni].r2\n            val = fields[0][dq.neighbors[ni].pn]\n            w = r2\n            for di in range(self.p2 - 1):\n                w *= r2\n            total_value += w * val\n            total_weight += w\n        self.fp[gind(i,j,k,dim) + offset] = total_value / total_weight\n        return\n\nidw_smooth = IDWInterpolationSmooth\n\ncdef class NthNeighborDistanceSmooth(ParticleSmoothOperation):\n\n    def initialize(self):\n        return\n\n    def finalize(self):\n        return\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void process(self, np.int64_t offset, int i, int j, int k,\n                      int dim[3], np.float64_t cpos[3], np.float64_t **fields,\n                      np.float64_t **index_fields, DistanceQueue dq):\n        cdef np.float64_t max_r\n        # We assume \"offset\" here is the particle index.\n        max_r = sqrt(dq.neighbors[dq.curn-1].r2)\n        fields[0][offset] = max_r\n\nnth_neighbor_smooth = NthNeighborDistanceSmooth\n\ncdef class SmoothedDensityEstimate(ParticleSmoothOperation):\n    def initialize(self):\n        return\n\n    def finalize(self):\n        return\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.initializedcheck(False)\n    cdef void process(self, np.int64_t offset, int i, int j, int k,\n                      int dim[3], np.float64_t cpos[3], np.float64_t **fields,\n                      np.float64_t **index_fields, DistanceQueue dq):\n        cdef np.float64_t r2, hsml, dens, mass, weight, lw\n        cdef int pn\n        # We assume \"offset\" here is the particle index.\n        hsml = sqrt(dq.neighbors[dq.curn-1].r2)\n        dens = 0.0\n        weight = 0.0\n        for pn in range(dq.curn):\n            mass = fields[0][dq.neighbors[pn].pn]\n            r2 = dq.neighbors[pn].r2\n            lw = self.sph_kernel(sqrt(r2) / hsml)\n            dens += mass * lw\n        weight = (4.0/3.0) * 3.1415926 * hsml**3\n        fields[1][offset] = dens/weight\n\ndensity_smooth = SmoothedDensityEstimate\n"
  },
  {
    "path": "yt/geometry/selection_routines.pxd",
    "content": "\"\"\"\nGeometry selection routine imports.\n\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\nfrom yt.geometry.grid_visitors cimport (\n    GridTreeNode,\n    GridVisitorData,\n    check_child_masked,\n    grid_visitor_function,\n)\nfrom yt.utilities.lib.fp_utils cimport _ensure_code\nfrom yt.utilities.lib.geometry_utils cimport decode_morton_64bit\n\nfrom .oct_container cimport OctreeContainer\nfrom .oct_visitors cimport Oct, OctVisitor\n\n\ncdef class SelectorObject:\n    cdef public np.int32_t min_level\n    cdef public np.int32_t max_level\n    cdef public int overlap_cells\n    cdef public np.float64_t domain_width[3]\n    cdef public np.float64_t domain_center[3]\n    cdef public bint periodicity[3]\n    cdef bint _hash_initialized\n    cdef np.int64_t _hash\n\n    cdef void recursively_visit_octs(self, Oct *root,\n                        np.float64_t pos[3], np.float64_t dds[3],\n                        int level,\n                        OctVisitor visitor,\n                        int visit_covered = ?)\n    cdef void visit_oct_cells(self, Oct *root, Oct *ch,\n                              np.float64_t spos[3], np.float64_t sdds[3],\n                              OctVisitor visitor, int i, int j, int k)\n    cdef int select_grid(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3],\n                               np.int32_t level, Oct *o = ?) noexcept nogil\n    cdef int select_grid_edge(self, np.float64_t left_edge[3],\n                                    np.float64_t right_edge[3],\n                                    np.int32_t level, Oct *o = ?) noexcept nogil\n    cdef int select_cell(self, np.float64_t pos[3], np.float64_t dds[3]) noexcept nogil\n\n    cdef int select_point(self, np.float64_t pos[3]) noexcept nogil\n    cdef int select_sphere(self, np.float64_t pos[3], np.float64_t radius) noexcept nogil\n    cdef int select_bbox(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil\n    cdef int select_bbox_edge(self, np.float64_t left_edge[3],\n                               np.float64_t right_edge[3]) noexcept nogil\n    cdef int fill_mask_selector_regular_grid(self, np.float64_t left_edge[3],\n                                             np.float64_t right_edge[3],\n                                             np.float64_t dds[3], int dim[3],\n                                             np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask,\n                                             np.ndarray[np.uint8_t, ndim=3] mask,\n                                             int level)\n    cdef int fill_mask_selector(self, np.float64_t left_edge[3],\n                                np.float64_t right_edge[3],\n                                np.float64_t **dds, int dim[3],\n                                np.ndarray[np.uint8_t, ndim=3, cast=True] child_mask,\n                                np.ndarray[np.uint8_t, ndim=3] mask,\n                                int level)\n    cdef void visit_grid_cells(self, GridVisitorData *data,\n                    grid_visitor_function *func, np.uint8_t *cached_mask = ?)\n\n    # compute periodic distance (if periodicity set)\n    # assuming 0->domain_width[d] coordinates\n    cdef np.float64_t periodic_difference(\n        self, np.float64_t x1, np.float64_t x2, int d) noexcept nogil\n\ncdef class AlwaysSelector(SelectorObject):\n    pass\n\ncdef class OctreeSubsetSelector(SelectorObject):\n    cdef public SelectorObject base_selector\n    cdef public np.int64_t domain_id\n\ncdef class BooleanSelector(SelectorObject):\n    cdef public SelectorObject sel1\n    cdef public SelectorObject sel2\n\ncdef inline np.float64_t _periodic_dist(np.float64_t x1, np.float64_t x2,\n                                        np.float64_t dw, bint periodic) noexcept nogil:\n    cdef np.float64_t rel = x1 - x2\n    if not periodic: return rel\n    if rel > dw * 0.5:\n        rel -= dw\n    elif rel < -dw * 0.5:\n        rel += dw\n    return rel\n"
  },
  {
    "path": "yt/geometry/selection_routines.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\n\"\"\"\nGeometry selection routines.\n\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.math cimport sqrt\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.lib.bitarray cimport ba_get_value\nfrom yt.utilities.lib.fnv_hash cimport c_fnv_hash as fnv_hash\nfrom yt.utilities.lib.fp_utils cimport fclip, fmax, fmin, iclip\nfrom yt.utilities.lib.grid_traversal cimport walk_volume\nfrom yt.utilities.lib.volume_container cimport VolumeContainer\n\nfrom .oct_container cimport Oct, OctreeContainer\nfrom .oct_visitors cimport cind\n\n\ncdef extern from \"math.h\":\n    double exp(double x) noexcept nogil\n    float expf(float x) noexcept nogil\n    long double expl(long double x) noexcept nogil\n    double floor(double x) noexcept nogil\n    double ceil(double x) noexcept nogil\n    double fmod(double x, double y) noexcept nogil\n    double log2(double x) noexcept nogil\n    long int lrint(double x) noexcept nogil\n    double fabs(double x) noexcept nogil\n\n# use this as an epsilon test for grids aligned with selector\n# define here to avoid the gil later\ncdef np.float64_t grid_eps = np.finfo(np.float64).eps\ngrid_eps = 0.0\n\ncdef inline np.float64_t dot(np.float64_t* v1,\n                             np.float64_t* v2) noexcept nogil:\n    return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]\n\ncdef inline np.float64_t norm(np.float64_t* v) noexcept nogil:\n    return sqrt(dot(v, v))\n\n# These routines are separated into a couple different categories:\n#\n#   * Routines for identifying intersections of an object with a bounding box\n#   * Routines for identifying cells/points inside a bounding box that\n#     intersect with an object\n#   * Routines that speed up some type of geometric calculation\n\n# First, bounding box / object intersection routines.\n# These all respect the interface \"dobj\" and a set of left_edges, right_edges,\n# sometimes also accepting level and mask information.\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef convert_mask_to_indices(np.ndarray[np.uint8_t, ndim=3, cast=True] mask,\n            int count, int transpose = 0):\n    cdef int i, j, k, cpos\n    cdef np.ndarray[np.int64_t, ndim=2] indices\n    indices = np.zeros((count, 3), dtype='int64')\n    cpos = 0\n    for i in range(mask.shape[0]):\n        for j in range(mask.shape[1]):\n            for k in range(mask.shape[2]):\n                if mask[i, j, k] == 1:\n                    if transpose == 1:\n                        indices[cpos, 0] = k\n                        indices[cpos, 1] = j\n                        indices[cpos, 2] = i\n                    else:\n                        indices[cpos, 0] = i\n                        indices[cpos, 1] = j\n                        indices[cpos, 2] = k\n                    cpos += 1\n    return indices\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef _mask_fill(np.ndarray[np.float64_t, ndim=1] out,\n                np.int64_t offset,\n                np.ndarray[np.uint8_t, ndim=3, cast=True] mask,\n                np.ndarray[cython.floating, ndim=3] vals):\n    cdef np.int64_t count = 0\n    cdef int i, j, k\n    for i in range(mask.shape[0]):\n        for j in range(mask.shape[1]):\n            for k in range(mask.shape[2]):\n                if mask[i, j, k] == 1:\n                    out[offset + count] = vals[i, j, k]\n                    count += 1\n    return count\n\ndef mask_fill(np.ndarray[np.float64_t, ndim=1] out,\n              np.int64_t offset,\n              np.ndarray[np.uint8_t, ndim=3, cast=True] mask,\n              np.ndarray vals):\n    if vals.dtype == np.float32:\n        return _mask_fill[np.float32_t](out, offset, mask, vals)\n    elif vals.dtype == np.float64:\n        return _mask_fill[np.float64_t](out, offset, mask, vals)\n    else:\n        raise RuntimeError\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef points_in_cells(\n        np.float64_t[:] cx,\n        np.float64_t[:] cy,\n        np.float64_t[:] cz,\n        np.float64_t[:] dx,\n        np.float64_t[:] dy,\n        np.float64_t[:] dz,\n        np.float64_t[:] px,\n        np.float64_t[:] py,\n        np.float64_t[:] pz):\n    # Take a list of cells and particles and calculate which particles\n    # are enclosed within one of the cells.  This is used for querying\n    # particle fields on clump/contour objects.\n    # We use brute force since the cells are a relatively unordered collection.\n\n    cdef int p, c, n_p, n_c\n    cdef np.ndarray[np.uint8_t, ndim=1, cast=True] mask\n\n    n_p = px.size\n    n_c = cx.size\n    mask = np.zeros(n_p, dtype=\"bool\")\n\n    for p in range(n_p):\n        for c in range(n_c):\n            if (fabs(px[p] - cx[c]) <= 0.5 * dx[c] and\n                fabs(py[p] - cy[c]) <= 0.5 * dy[c] and\n                fabs(pz[p] - cz[c]) <= 0.5 * dz[c]):\n                mask[p] = True\n                break\n\n    return mask\n\ndef bbox_intersects(\n    SelectorObject selector,\n    np.float64_t[::1] left_edges,\n    np.float64_t dx\n):\n    cdef np.float64_t[3] right_edges\n    right_edges[0] = left_edges[0] + dx\n    right_edges[1] = left_edges[1] + dx\n    right_edges[2] = left_edges[2] + dx\n    return selector.select_bbox(&left_edges[0], right_edges) == 1\n\ndef fully_contains(\n    SelectorObject selector,\n    np.float64_t[::1] left_edges,\n    np.float64_t dx,\n):\n    cdef np.float64_t[3] right_edges\n\n    right_edges[0] = left_edges[0] + dx\n    right_edges[1] = left_edges[1] + dx\n    right_edges[2] = left_edges[2] + dx\n\n    return selector.select_bbox_edge(&left_edges[0], right_edges) == 1\n\n\n\ninclude \"_selection_routines/selector_object.pxi\"\ninclude \"_selection_routines/point_selector.pxi\"\ninclude \"_selection_routines/sphere_selector.pxi\"\ninclude \"_selection_routines/region_selector.pxi\"\ninclude \"_selection_routines/cut_region_selector.pxi\"\ninclude \"_selection_routines/disk_selector.pxi\"\ninclude \"_selection_routines/cutting_plane_selector.pxi\"\ninclude \"_selection_routines/slice_selector.pxi\"\ninclude \"_selection_routines/ortho_ray_selector.pxi\"\ninclude \"_selection_routines/ray_selector.pxi\"\ninclude \"_selection_routines/data_collection_selector.pxi\"\ninclude \"_selection_routines/ellipsoid_selector.pxi\"\ninclude \"_selection_routines/grid_selector.pxi\"\ninclude \"_selection_routines/octree_subset_selector.pxi\"\ninclude \"_selection_routines/indexed_octree_subset_selector.pxi\"\ninclude \"_selection_routines/always_selector.pxi\"\ninclude \"_selection_routines/compose_selector.pxi\"\ninclude \"_selection_routines/halo_particles_selector.pxi\"\ninclude \"_selection_routines/boolean_selectors.pxi\"\n"
  },
  {
    "path": "yt/geometry/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/geometry/tests/fake_octree.py",
    "content": "import numpy as np\n\nfrom yt.geometry.fake_octree import create_fake_octree\nfrom yt.geometry.oct_container import ParticleOctreeContainer, RAMSESOctreeContainer\n\nnocts = 3\nmax_level = 12\ndn = 2\ndd = np.ones(3, dtype=\"i4\") * dn\ndle = np.ones(3, dtype=\"f8\") * 0.0\ndre = np.ones(3, dtype=\"f8\")\nfsub = 0.25\ndomain = 1\n\noct_handler = RAMSESOctreeContainer(dd, dle, dre)\nleaves = create_fake_octree(oct_handler, nocts, max_level, dd, dle, dre, fsub)\nmask = np.ones((nocts, 8), dtype=\"bool\")\ncell_count = nocts * 8\noct_counts = oct_handler.count_levels(max_level, 1, mask)\nlevel_counts = np.concatenate(\n    (\n        [\n            0,\n        ],\n        np.cumsum(oct_counts),\n    )\n)\nfc = oct_handler.fcoords(domain, mask, cell_count, level_counts.copy())\nleavesb = oct_handler.count_leaves(mask)\nassert leaves == leavesb\n\n# Now take the fcoords, call them particles and recreate the same octree\nprint(\"particle-based recreate\")\noct_handler2 = ParticleOctreeContainer(dd, dle, dre)\noct_handler2.allocate_domains([nocts])\noct_handler2.n_ref = 1  # specifically make a maximum of 1 particle per oct\noct_handler2.add(fc, 1)\nprint(\"added particles\")\ncell_count2 = nocts * 8\noct_counts2 = oct_handler.count_levels(max_level, 1, mask)\nlevel_counts2 = np.concatenate(\n    (\n        [\n            0,\n        ],\n        np.cumsum(oct_counts),\n    )\n)\nfc2 = oct_handler.fcoords(domain, mask, cell_count, level_counts.copy())\nleaves2 = oct_handler2.count_leaves(mask)\nassert leaves == leaves2\n\nprint(\"success\")\n"
  },
  {
    "path": "yt/geometry/tests/test_ewah_write_load.py",
    "content": "from yt.loaders import load\nfrom yt.sample_data.api import _get_test_data_dir_path\nfrom yt.testing import assert_array_equal, requires_file\n\n\n@requires_file(\"TNGHalo/halo_59.hdf5\")\ndef test_ewah_write_load(tmp_path):\n    mock_file = tmp_path / \"halo_59.hdf5\"\n    mock_file.symlink_to(_get_test_data_dir_path() / \"TNGHalo\" / \"halo_59.hdf5\")\n\n    masses = []\n    opts = [\n        (None, True, (6, 2, 4), False),\n        (None, True, (6, 2, 4), True),\n        ((5, 3), False, (5, 3, 3), False),\n        ((5, 3), False, (5, 3, 3), True),\n    ]\n\n    for opt in opts:\n        ds = load(mock_file, index_order=opt[0])\n        _, c = ds.find_max((\"gas\", \"density\"))\n        assert ds.index.pii.mutable_index is opt[1]\n        assert ds.index.pii.order1 == opt[2][0]\n        assert ds.index.pii.order2_orig == opt[2][1]\n        assert ds.index.pii.order2 == opt[2][2]\n        assert ds.index.pii._is_loaded is opt[3]\n        c += ds.quan(0.5, \"Mpc\")\n        sp = ds.sphere(c, (20.0, \"kpc\"))\n        mass = sp.sum((\"gas\", \"mass\"))\n        masses.append(mass)\n\n    assert_array_equal(masses[0], masses[1:])\n"
  },
  {
    "path": "yt/geometry/tests/test_geometries.py",
    "content": "from importlib.util import find_spec\n\nimport pytest\n\nimport yt\nfrom yt.testing import fake_amr_ds\n\n\n@pytest.mark.parametrize(\n    \"geometry\",\n    [\n        \"cartesian\",\n        \"polar\",\n        \"cylindrical\",\n        \"spherical\",\n        \"geographic\",\n        \"internal_geographic\",\n        \"spectral_cube\",\n    ],\n)\ndef test_testable_geometries(geometry):\n    # check that initializing a simple fake dataset works in any geometry\n    ds = fake_amr_ds(fields=[(\"gas\", \"density\")], units=[\"g/cm**3\"], geometry=geometry)\n    # make sure basic plotting works\n    for axis in range(3):\n        if \"geographic\" in geometry and axis == 2 and find_spec(\"cartopy\") is None:\n            pytest.skip(\n                reason=(\n                    \"cannot test this case with vanilla yt (requires cartopy) \"\n                    \"see https://github.com/yt-project/yt/issues/4182\"\n                )\n            )\n        yt.SlicePlot(ds, axis, (\"gas\", \"density\"), buff_size=(8, 8))\n"
  },
  {
    "path": "yt/geometry/tests/test_grid_container.py",
    "content": "import random\n\nimport numpy as np\nfrom numpy.testing import assert_equal, assert_raises\n\nfrom yt.loaders import load_amr_grids\n\n\ndef setup_test_ds():\n    \"\"\"Prepare setup specific environment\"\"\"\n    grid_data = [\n        {\n            \"left_edge\": [0.0, 0.0, 0.0],\n            \"right_edge\": [1.0, 1.0, 1.0],\n            \"level\": 0,\n            \"dimensions\": [16, 16, 16],\n        },\n        {\n            \"left_edge\": [0.25, 0.25, 0.25],\n            \"right_edge\": [0.75, 0.75, 0.75],\n            \"level\": 1,\n            \"dimensions\": [16, 16, 16],\n        },\n        {\n            \"left_edge\": [0.25, 0.25, 0.375],\n            \"right_edge\": [0.5, 0.5, 0.625],\n            \"level\": 2,\n            \"dimensions\": [16, 16, 16],\n        },\n        {\n            \"left_edge\": [0.5, 0.5, 0.375],\n            \"right_edge\": [0.75, 0.75, 0.625],\n            \"level\": 2,\n            \"dimensions\": [16, 16, 16],\n        },\n        {\n            \"left_edge\": [0.3125, 0.3125, 0.4375],\n            \"right_edge\": [0.4375, 0.4375, 0.5625],\n            \"level\": 3,\n            \"dimensions\": [16, 16, 16],\n        },\n        {\n            \"left_edge\": [0.5625, 0.5625, 0.4375],\n            \"right_edge\": [0.6875, 0.6875, 0.5625],\n            \"level\": 3,\n            \"dimensions\": [16, 16, 16],\n        },\n    ]\n\n    for grid in grid_data:\n        grid[\"density\"] = (\n            np.random.random(grid[\"dimensions\"]) * 2 ** grid[\"level\"],\n            \"g/cm**3\",\n        )\n    return load_amr_grids(grid_data, [16, 16, 16])\n\n\ndef test_grid_tree():\n    \"\"\"Main test suite for GridTree\"\"\"\n    test_ds = setup_test_ds()\n    grid_tree = test_ds.index._get_grid_tree()\n    indices, levels, nchild, children = grid_tree.return_tree_info()\n\n    grid_levels = [grid.Level for grid in test_ds.index.grids]\n\n    grid_indices = [grid.id - grid._id_offset for grid in test_ds.index.grids]\n    grid_nchild = [len(grid.Children) for grid in test_ds.index.grids]\n\n    assert_equal(levels, grid_levels)\n    assert_equal(indices, grid_indices)\n    assert_equal(nchild, grid_nchild)\n\n    for i, grid in enumerate(test_ds.index.grids):\n        if grid_nchild[i] > 0:\n            grid_children = np.array(\n                [child.id - child._id_offset for child in grid.Children]\n            )\n            assert_equal(grid_children, children[i])\n\n\ndef test_find_points():\n    \"\"\"Main test suite for MatchPoints\"\"\"\n    num_points = 100\n    test_ds = setup_test_ds()\n    randx = np.random.uniform(\n        low=test_ds.domain_left_edge[0],\n        high=test_ds.domain_right_edge[0],\n        size=num_points,\n    )\n    randy = np.random.uniform(\n        low=test_ds.domain_left_edge[1],\n        high=test_ds.domain_right_edge[1],\n        size=num_points,\n    )\n    randz = np.random.uniform(\n        low=test_ds.domain_left_edge[2],\n        high=test_ds.domain_right_edge[2],\n        size=num_points,\n    )\n\n    point_grids, point_grid_inds = test_ds.index._find_points(randx, randy, randz)\n\n    grid_inds = np.zeros((num_points), dtype=\"int64\")\n\n    for ind, ixx, iyy, izz in zip(range(num_points), randx, randy, randz, strict=True):\n        pos = np.array([ixx, iyy, izz])\n        pt_level = -1\n\n        for grid in test_ds.index.grids:\n            if (\n                np.all(pos >= grid.LeftEdge)\n                and np.all(pos <= grid.RightEdge)\n                and grid.Level > pt_level\n            ):\n                pt_level = grid.Level\n                grid_inds[ind] = grid.id - grid._id_offset\n\n    assert_equal(point_grid_inds, grid_inds)\n\n    # Test whether find_points works for lists\n    point_grids, point_grid_inds = test_ds.index._find_points(\n        randx.tolist(), randy.tolist(), randz.tolist()\n    )\n    assert_equal(point_grid_inds, grid_inds)\n\n    # Test if find_points works for scalar\n    ind = random.randint(0, num_points - 1)\n    point_grids, point_grid_inds = test_ds.index._find_points(\n        randx[ind], randy[ind], randz[ind]\n    )\n    assert_equal(point_grid_inds, grid_inds[ind])\n\n    # Test if find_points fails properly for non equal indices' array sizes\n    assert_raises(ValueError, test_ds.index._find_points, [0], 1.0, [2, 3])\n\n\ndef test_grid_arrays_view():\n    ds = setup_test_ds()\n    tree = ds.index._get_grid_tree()\n    grid_arr = tree.grid_arrays\n    assert_equal(grid_arr[\"left_edge\"], ds.index.grid_left_edge)\n    assert_equal(grid_arr[\"right_edge\"], ds.index.grid_right_edge)\n    assert_equal(grid_arr[\"dims\"], ds.index.grid_dimensions)\n    assert_equal(grid_arr[\"level\"], ds.index.grid_levels[:, 0])\n"
  },
  {
    "path": "yt/geometry/tests/test_grid_index.py",
    "content": "from yt.testing import assert_allclose_units, fake_amr_ds\n\n\ndef test_icoords_to_ires():\n    for geometry in (\"cartesian\", \"spherical\", \"cylindrical\"):\n        ds = fake_amr_ds(geometry=geometry)\n\n        dd = ds.r[:]\n        icoords = dd.icoords\n        ires = dd.ires\n        fcoords, fwidth = ds.index._icoords_to_fcoords(icoords, ires)\n        assert_allclose_units(fcoords, dd.fcoords, rtol=1e-14)\n        assert_allclose_units(fwidth, dd.fwidth, rtol=1e-14)\n\n        fcoords_xz, fwidth_xz = ds.index._icoords_to_fcoords(\n            icoords[:, (0, 2)], ires, axes=(0, 2)\n        )\n        assert_allclose_units(fcoords_xz[:, 0], dd.fcoords[:, 0])\n        assert_allclose_units(fcoords_xz[:, 1], dd.fcoords[:, 2])\n        assert_allclose_units(fwidth_xz[:, 0], dd.fwidth[:, 0])\n        assert_allclose_units(fwidth_xz[:, 1], dd.fwidth[:, 2])\n"
  },
  {
    "path": "yt/geometry/tests/test_particle_deposit.py",
    "content": "from numpy.testing import assert_allclose, assert_array_less, assert_raises\n\nimport yt\nfrom yt.loaders import load\nfrom yt.testing import fake_random_ds, requires_file, requires_module\nfrom yt.utilities.exceptions import YTBoundsDefinitionError\n\n\ndef test_cic_deposit():\n    ds = fake_random_ds(64, nprocs=8, particles=64**3)\n    my_reg = ds.arbitrary_grid(\n        ds.domain_left_edge, ds.domain_right_edge, dims=[1, 800, 800]\n    )\n    f = (\"deposit\", \"all_cic\")\n    assert_raises(YTBoundsDefinitionError, my_reg.__getitem__, f)\n\n\nRAMSES = \"output_00080/info_00080.txt\"\nRAMSES_small = \"ramses_new_format/output_00002/info_00002.txt\"\nISOGAL = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\n\n@requires_file(RAMSES)\ndef test_one_zone_octree_deposit():\n    ds = load(RAMSES)\n\n    # Get a sphere centred on the main halo\n    hpos = ds.arr(\n        [0.5215110772898429, 0.5215110772898429, 0.5215110772898429], \"code_length\"\n    )\n    hrvir = ds.quan(0.042307235300540924, \"Mpc\")\n\n    sp = ds.sphere(hpos, hrvir * 10)\n    assert sp[\"deposit\", \"io_cic\"].shape == (1,)\n\n\n@requires_module(\"h5py\")\n@requires_file(RAMSES)\n@requires_file(ISOGAL)\ndef test_mesh_sampling():\n    for fn in (RAMSES, ISOGAL):\n        ds = yt.load(fn)\n        ds.add_mesh_sampling_particle_field((\"index\", \"x\"), ptype=\"all\")\n        ds.add_mesh_sampling_particle_field((\"index\", \"dx\"), ptype=\"all\")\n\n        dx = ds.r[\"all\", \"cell_index_dx\"]\n        xc = ds.r[\"all\", \"cell_index_x\"]\n        xp = ds.r[\"all\", \"particle_position_x\"]\n\n        dist = xp - xc\n\n        assert_array_less(dist, dx)\n        assert_array_less(-dist, dx)\n\n\n@requires_module(\"h5py\")\n@requires_file(RAMSES)\n@requires_file(ISOGAL)\ndef test_mesh_sampling_for_filtered_particles():\n    for fn in (RAMSES, ISOGAL):\n        ds = yt.load(fn)\n\n        @yt.particle_filter(requires=[\"particle_position_x\"], filtered_type=\"io\")\n        def left(pfilter, data):\n            return (\n                data[pfilter.filtered_type, \"particle_position_x\"].to(\"code_length\")\n                < 0.5\n            )\n\n        ds.add_particle_filter(\"left\")\n\n        for f in ((\"index\", \"x\"), (\"index\", \"dx\"), (\"gas\", \"density\")):\n            ds.add_mesh_sampling_particle_field(f, ptype=\"io\")\n            ds.add_mesh_sampling_particle_field(f, ptype=\"left\")\n\n        data_sources = (ds.all_data(), ds.box([0] * 3, [0.1] * 3))\n\n        def test_source(ptype, src):\n            # Test accessing\n            src[ptype, \"cell_index_x\"]\n            src[ptype, \"cell_index_dx\"]\n            src[ptype, \"cell_gas_density\"]\n\n        for ptype in (\"io\", \"left\"):\n            for src in data_sources:\n                test_source(ptype, src)\n\n\n@requires_file(RAMSES)\ndef test_mesh_sampling_with_indexing():\n    # Access with index caching\n    ds = yt.load(RAMSES)\n    ds.add_mesh_sampling_particle_field((\"gas\", \"density\"), ptype=\"all\")\n\n    ad = ds.all_data()\n    ad[\"all\", \"cell_index\"]\n    v1 = ad[\"all\", \"cell_gas_density\"]\n\n    # Access with no index caching\n    ds = yt.load(RAMSES)\n    ds.add_mesh_sampling_particle_field((\"gas\", \"density\"), ptype=\"all\")\n\n    ad = ds.all_data()\n    v2 = ad[\"all\", \"cell_gas_density\"]\n\n    # Check same answer is returned\n    assert_allclose(v1, v2)\n\n\n@requires_file(RAMSES_small)\ndef test_mesh_sampling_vs_field_value_at_point():\n    all_ds = (fake_random_ds(ndims=3, particles=500), yt.load(RAMSES_small))\n\n    for ds in all_ds:\n        ds.add_mesh_sampling_particle_field((\"gas\", \"density\"), ptype=\"all\")\n\n        val = ds.r[\"all\", \"cell_gas_density\"]\n        ref = ds.find_field_values_at_points(\n            (\"gas\", \"density\"), ds.r[\"all\", \"particle_position\"]\n        )\n\n        assert_allclose(val, ref)\n"
  },
  {
    "path": "yt/geometry/tests/test_particle_octree.py",
    "content": "import os\n\nimport numpy as np\nfrom numpy.testing import assert_array_equal, assert_equal\n\nimport yt.units.dimensions as dimensions\nfrom yt.geometry.oct_container import _ORDER_MAX\nfrom yt.geometry.particle_oct_container import ParticleBitmap, ParticleOctreeContainer\nfrom yt.geometry.selection_routines import RegionSelector\nfrom yt.testing import requires_module\nfrom yt.units.unit_registry import UnitRegistry\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.lib.geometry_utils import (\n    get_hilbert_indices,\n    get_hilbert_points,\n    get_morton_indices,\n    get_morton_points,\n)\n\nNPART = 32**3\nDLE = np.array([0.0, 0.0, 0.0])\nDRE = np.array([10.0, 10.0, 10.0])\nDW = DRE - DLE\nPER = np.array([0, 0, 0], \"bool\")\ndx = DW / (2**_ORDER_MAX)\n\n\ndef test_add_particles_random():\n    np.random.seed(0x4D3D3D3)\n    pos = np.random.normal(0.5, scale=0.05, size=(NPART, 3)) * (DRE - DLE) + DLE\n    # Now convert to integers\n    for i in range(3):\n        np.clip(pos[:, i], DLE[i], DRE[i], pos[:, i])\n    # Convert to integers\n    pos = np.floor((pos - DLE) / dx).astype(\"uint64\")\n    morton = get_morton_indices(pos)\n    morton.sort()\n    for ndom in [1, 2, 4, 8]:\n        octree = ParticleOctreeContainer((1, 1, 1), DLE, DRE)\n        octree.n_ref = 32\n        for split in np.array_split(morton, ndom):\n            octree.add(split)\n        octree.finalize()\n        # This visits every oct.\n        tc = octree.recursively_count()\n        total_count = np.zeros(len(tc), dtype=\"int32\")\n        for i in sorted(tc):\n            total_count[i] = tc[i]\n        assert_equal(octree.nocts, total_count.sum())\n        # This visits every cell -- including those covered by octs.\n        # for dom in range(ndom):\n        #    level_count += octree.count_levels(total_count.size-1, dom, mask)\n        assert_equal(total_count, [1, 8, 64, 64, 256, 536, 1856, 1672])\n\n\nclass FakeDS:\n    domain_left_edge = None\n    domain_right_edge = None\n    domain_width = None\n    unit_registry = UnitRegistry()\n    unit_registry.add(\"code_length\", 1.0, dimensions.length)\n    periodicity = (False, False, False)\n\n\nclass FakeRegion:\n    def __init__(self, nfiles, periodic=False):\n        self.ds = FakeDS()\n        self.ds.domain_left_edge = YTArray(\n            [0.0, 0.0, 0.0], \"code_length\", registry=self.ds.unit_registry\n        )\n        self.ds.domain_right_edge = YTArray(\n            [nfiles, nfiles, nfiles],\n            \"code_length\",\n            registry=self.ds.unit_registry,\n            dtype=\"float64\",\n        )\n        self.ds.domain_width = self.ds.domain_right_edge - self.ds.domain_left_edge\n        self.ds.periodicity = (periodic, periodic, periodic)\n        self.nfiles = nfiles\n\n    def set_edges(self, file_id, dx=0.1):\n        self.left_edge = YTArray(\n            [file_id + dx, 0.0, 0.0], \"code_length\", registry=self.ds.unit_registry\n        )\n        self.right_edge = YTArray(\n            [file_id + 1 - dx, self.nfiles, self.nfiles],\n            \"code_length\",\n            registry=self.ds.unit_registry,\n        )\n\n\nclass FakeBoxRegion:\n    def __init__(self, nfiles, left_edge, right_edge):\n        self.ds = FakeDS()\n        self.ds.domain_left_edge = YTArray(\n            left_edge, \"code_length\", registry=self.ds.unit_registry\n        )\n        self.ds.domain_right_edge = YTArray(\n            right_edge, \"code_length\", registry=self.ds.unit_registry\n        )\n        self.ds.domain_width = self.ds.domain_right_edge - self.ds.domain_left_edge\n        self.nfiles = nfiles\n\n    def set_edges(self, center, width):\n        self.left_edge = self.ds.domain_left_edge + self.ds.domain_width * (\n            center - width / 2\n        )\n        self.right_edge = self.ds.domain_left_edge + self.ds.domain_width * (\n            center + width / 2\n        )\n\n\ndef FakeBitmap(\n    npart,\n    nfiles,\n    order1,\n    order2,\n    left_edge=None,\n    right_edge=None,\n    periodicity=None,\n    decomp=\"sliced\",\n    buff=0.1,\n    distrib=\"uniform\",\n    fname=None,\n):\n    if left_edge is None:\n        left_edge = np.array([0.0, 0.0, 0.0])\n    if right_edge is None:\n        right_edge = np.array([1.0, 1.0, 1.0])\n    if periodicity is None:\n        periodicity = np.array([0, 0, 0], \"bool\")\n    reg = ParticleBitmap(\n        left_edge, right_edge, periodicity, 12345, nfiles, order1, order2\n    )\n    # Load from file if it exists\n    if isinstance(fname, str) and os.path.isfile(fname):\n        reg.load_bitmasks(fname)\n    else:\n        # Create positions for each file\n        posgen = yield_fake_decomp(\n            decomp, npart, nfiles, left_edge, right_edge, buff=buff, distrib=distrib\n        )\n        # Coarse index\n        max_npart = 0\n        for i, (pos, hsml) in enumerate(posgen):\n            max_npart = max(max_npart, pos.shape[0])\n            reg._coarse_index_data_file(pos, hsml, i)\n            reg._set_coarse_index_data_file(i)\n        if i != (nfiles - 1):\n            raise RuntimeError(\n                f\"There are positions for {i + 1} files, but there should be {nfiles}.\"\n            )\n        # Refined index\n        mask = reg.masks.sum(axis=1).astype(\"uint8\")\n        sub_mi1 = np.zeros(max_npart, \"uint64\")\n        sub_mi2 = np.zeros(max_npart, \"uint64\")\n        posgen = yield_fake_decomp(\n            decomp, npart, nfiles, left_edge, right_edge, buff=buff, distrib=distrib\n        )\n        coll = None\n        for i, (pos, hsml) in enumerate(posgen):\n            nsub_mi, coll = reg._refined_index_data_file(\n                coll,\n                pos,\n                hsml,\n                mask,\n                sub_mi1,\n                sub_mi2,\n                i,\n                0,\n                count_threshold=1,\n                mask_threshold=2,\n            )\n            reg.bitmasks.append(i, coll)\n        # Save if file name provided\n        if isinstance(fname, str):\n            reg.save_bitmasks(fname)\n    return reg\n\n\ndef test_bitmap_no_collisions():\n    # Test init for slabs of points in x\n    left_edge = np.array([0.0, 0.0, 0.0])\n    right_edge = np.array([1.0, 1.0, 1.0])\n    periodicity = np.array([0, 0, 0], \"bool\")\n    npart = 100\n    nfiles = 2\n    file_hash = 12345\n    order1 = 2\n    order2 = 2\n    reg = ParticleBitmap(\n        left_edge, right_edge, periodicity, file_hash, nfiles, order1, order2\n    )\n    # Coarse index\n    posgen = yield_fake_decomp(\"sliced\", npart, nfiles, left_edge, right_edge)\n    max_npart = 0\n    for i, (pos, hsml) in enumerate(posgen):\n        reg._coarse_index_data_file(pos, hsml, i)\n        max_npart = max(max_npart, pos.shape[0])\n        reg._set_coarse_index_data_file(i)\n        assert_equal(reg.count_total(i), np.sum(reg.masks[:, i]))\n    mask = reg.masks.sum(axis=1).astype(\"uint8\")\n    ncoll = np.sum(mask > 1)\n    nc, nm = reg.find_collisions_coarse()\n    assert_equal(nc, 0, f\"{nc} coarse collisions\")\n    assert_equal(ncoll, nc, f\"{ncoll} in mask, {nc} in bitmap\")\n    # Refined index\n    sub_mi1 = np.zeros(max_npart, \"uint64\")\n    sub_mi2 = np.zeros(max_npart, \"uint64\")\n    posgen = yield_fake_decomp(\"sliced\", npart, nfiles, left_edge, right_edge)\n    coll = None\n    for i, (pos, hsml) in enumerate(posgen):\n        nsub_mi, coll = reg._refined_index_data_file(\n            coll,\n            pos,\n            hsml,\n            mask,\n            sub_mi1,\n            sub_mi2,\n            i,\n            0,\n            count_threshold=1,\n            mask_threshold=2,\n        )\n        reg.bitmasks.append(i, coll)\n        assert_equal(reg.count_refined(i), 0)\n    nr, nm = reg.find_collisions_refined()\n    assert_equal(nr, 0, f\"{nr} collisions\")\n\n\ndef test_bitmap_collisions():\n    # Test init for slabs of points in x\n    left_edge = np.array([0.0, 0.0, 0.0])\n    right_edge = np.array([1.0, 1.0, 1.0])\n    periodicity = np.array([0, 0, 0], \"bool\")\n    nfiles = 2\n    file_hash = 12345\n    order1 = 2\n    order2 = 2\n    reg = ParticleBitmap(\n        left_edge, right_edge, periodicity, file_hash, nfiles, order1, order2\n    )\n    # Use same points for all files to force collisions\n    pos = cell_centers(order1 + order2, left_edge, right_edge)\n    hsml = None\n    # Coarse index\n    max_npart = 0\n    for i in range(nfiles):\n        reg._coarse_index_data_file(pos, hsml, i)\n        max_npart = max(max_npart, pos.shape[0])\n        reg._set_coarse_index_data_file(i)\n        assert_equal(reg.count_total(i), np.sum(reg.masks[:, i]))\n    mask = reg.masks.sum(axis=1).astype(\"uint8\")\n    ncoll = np.sum(mask > 1)\n    nc, nm = reg.find_collisions_coarse()\n    assert_equal(ncoll, nc, f\"{ncoll} in mask, {nc} in bitmap\")\n    assert_equal(nc, 2 ** (3 * order1), f\"{nc} coarse collisions\")\n    # Refined index\n    sub_mi1 = np.zeros(max_npart, \"uint64\")\n    sub_mi2 = np.zeros(max_npart, \"uint64\")\n    for i in range(nfiles):\n        nsub_mi, coll = reg._refined_index_data_file(\n            None,\n            pos,\n            hsml,\n            mask,\n            sub_mi1,\n            sub_mi2,\n            i,\n            0,\n            count_threshold=1,\n            mask_threshold=2,\n        )\n        reg.bitmasks.append(i, coll)\n        assert_equal(reg.count_refined(i), ncoll)\n    nr, nm = reg.find_collisions_refined()\n    assert_equal(nr, 2 ** (3 * (order1 + order2)), f\"{nr} collisions\")\n\n\n@requires_module(\"h5py\")\ndef test_bitmap_save_load():\n    # Test init for slabs of points in x\n    left_edge = np.array([0.0, 0.0, 0.0])\n    right_edge = np.array([1.0, 1.0, 1.0])\n    periodicity = np.array([0, 0, 0], \"bool\")\n    npart = NPART\n    file_hash = 12345\n    nfiles = 32\n    order1 = 2\n    order2 = 2\n    fname_fmt = \"temp_bitmasks{}.dat\"\n    i = 0\n    fname = fname_fmt.format(i)\n    while os.path.isfile(fname):\n        i += 1\n        fname = fname_fmt.format(i)\n    # Create bitmap and save to file\n    reg0 = FakeBitmap(npart, nfiles, order1, order2, left_edge, right_edge, periodicity)\n    reg0.save_bitmasks(fname, 0.0)\n    # Attempt to load bitmap\n    reg1 = ParticleBitmap(\n        left_edge, right_edge, periodicity, file_hash, nfiles, order1, order2\n    )\n    reg1.load_bitmasks(fname)\n    assert reg0.iseq_bitmask(reg1)\n    # Remove file\n    os.remove(fname)\n\n\ndef test_bitmap_select():\n    np.random.seed(0x4D3D3D3)\n    dx = 0.1\n    for periodic in [False, True]:\n        for nfiles in [2, 15, 31, 32, 33]:\n            # Now we create particles\n            # Note: we set order1 to log2(nfiles) here for testing purposes to\n            # ensure no collisions\n            order1 = int(np.ceil(np.log2(nfiles)))  # Ensures zero collisions\n            order2 = 2  # No overlap for N = nfiles\n            exact_division = nfiles == (1 << order1)\n            div = float(nfiles) / float(1 << order1)\n            reg = FakeBitmap(\n                nfiles**3,\n                nfiles,\n                order1,\n                order2,\n                decomp=\"grid\",\n                left_edge=np.array([0.0, 0.0, 0.0]),\n                right_edge=np.array([nfiles, nfiles, nfiles]),\n                periodicity=np.array([periodic, periodic, periodic]),\n            )\n            # Loop over regions selecting single files\n            fr = FakeRegion(nfiles, periodic=periodic)\n            for i in range(nfiles):\n                fr.set_edges(i, dx)\n                selector = RegionSelector(fr)\n                (df, gf), (dmask, gmask) = reg.identify_data_files(selector, ngz=1)\n                if exact_division:\n                    assert_equal(len(df), 1, f\"selector {i}, number of files\")\n                    assert_equal(df[0], i, f\"selector {i}, file selected\")\n                    if periodic and (nfiles != 2):\n                        ans_gf = sorted([(i - 1) % nfiles, (i + 1) % nfiles])\n                    elif i == 0:\n                        ans_gf = [i + 1]\n                    elif i == (nfiles - 1):\n                        ans_gf = [i - 1]\n                    else:\n                        ans_gf = [i - 1, i + 1]\n                    assert_equal(\n                        len(gf),\n                        len(ans_gf),\n                        f\"selector {i}, number of ghost files\",\n                    )\n                    for i in range(len(gf)):\n                        assert_equal(gf[i], ans_gf[i], f\"selector {i}, ghost files\")\n\n                else:\n                    lf_frac = np.floor(float(fr.left_edge[0]) / div) * div\n                    rf_frac = np.floor(float(fr.right_edge[0]) / div) * div\n                    # Selected files\n                    lf = int(\n                        np.floor(lf_frac)\n                        if ((lf_frac % 0.5) == 0)\n                        else np.round(lf_frac)\n                    )\n                    rf = int(\n                        np.floor(rf_frac)\n                        if ((rf_frac % 0.5) == 0)\n                        else np.round(rf_frac)\n                    )\n                    if (rf + 0.5) >= (rf_frac + div):\n                        rf -= 1\n                    if (lf + 0.5) <= (lf_frac - div):\n                        lf += 1\n                    df_ans = np.arange(max(lf, 0), min(rf + 1, nfiles))\n                    assert_array_equal(df, df_ans, f\"selector {i}, file array\")\n                    # Ghost zones selected files\n                    lf_ghost = int(\n                        np.floor(lf_frac - div)\n                        if (((lf_frac - div) % 0.5) == 0)\n                        else np.round(lf_frac - div)\n                    )\n                    rf_ghost = int(\n                        np.floor(rf_frac + div)\n                        if (((rf_frac + div) % 0.5) == 0)\n                        else np.round(rf_frac + div)\n                    )\n                    if not periodic:\n                        lf_ghost = max(lf_ghost, 0)\n                        rf_ghost = min(rf_ghost, nfiles - 1)\n                    if (rf_ghost + 0.5) >= (rf_frac + 2 * div):\n                        rf_ghost -= 1\n                    gf_ans = []\n                    if lf_ghost < lf:\n                        gf_ans.append(lf_ghost % nfiles)\n                    if rf_ghost > rf:\n                        gf_ans.append(rf_ghost % nfiles)\n                    gf_ans = np.array(sorted(gf_ans))\n                    assert_array_equal(gf, gf_ans, f\"selector {i}, ghost file array\")\n\n\ndef cell_centers(order, left_edge, right_edge):\n    ndim = left_edge.size\n    ncells = 2**order\n    dx = (right_edge - left_edge) / (2 * ncells)\n    d = [\n        np.linspace(left_edge[i] + dx[i], right_edge[i] - dx[i], ncells)\n        for i in range(ndim)\n    ]\n    dd = np.meshgrid(*d)\n    return np.vstack([x.flatten() for x in dd]).T\n\n\ndef fake_decomp_random(npart, nfiles, ifile, DLE, DRE, buff=0.0):\n    np.random.seed(0x4D3D3D3 + ifile)\n    nPF = int(npart / nfiles)\n    nR = npart % nfiles\n    if ifile == 0:\n        nPF += nR\n    pos = np.empty((nPF, 3), \"float64\")\n    for i in range(3):\n        pos[:, i] = np.random.uniform(DLE[i], DRE[i], nPF)\n    return pos\n\n\ndef fake_decomp_sliced(npart, nfiles, ifile, DLE, DRE, buff=0.0):\n    np.random.seed(0x4D3D3D3 + ifile)\n    DW = DRE - DLE\n    div = DW / nfiles\n    nPF = int(npart / nfiles)\n    nR = npart % nfiles\n    inp = nPF\n    if ifile == 0:\n        inp += nR\n    iLE = DLE[0] + ifile * div[0]\n    iRE = iLE + div[0]\n    if ifile != 0:\n        iLE -= buff * div[0]\n    if ifile != (nfiles - 1):\n        iRE += buff * div[0]\n    pos = np.empty((inp, 3), dtype=\"float\")\n    pos[:, 0] = np.random.uniform(iLE, iRE, inp)\n    for i in range(1, 3):\n        pos[:, i] = np.random.uniform(DLE[i], DRE[i], inp)\n    return pos\n\n\ndef makeall_decomp_hilbert_gaussian(\n    npart,\n    nfiles,\n    DLE,\n    DRE,\n    buff=0.0,\n    order=6,\n    verbose=False,\n    fname_base=None,\n    nchunk=10,\n    width=None,\n    center=None,\n    frac_random=0.1,\n):\n    import pickle\n\n    np.random.seed(0x4D3D3D3)\n    DW = DRE - DLE\n    if fname_base is None:\n        fname_base = f\"hilbert{order}_gaussian_np{npart}_nf{nfiles}_\"\n    if width is None:\n        width = 0.1 * DW\n    if center is None:\n        center = DLE + 0.5 * DW\n\n    def load_pos(file_id):\n        filename = fname_base + f\"file{file_id}\"\n        if os.path.isfile(filename):\n            fd = open(filename, \"rb\")\n            positions = pickle.load(fd)\n            fd.close()\n        else:\n            positions = np.empty((0, 3), dtype=\"float64\")\n        return positions\n\n    def save_pos(file_id, positions):\n        filename = fname_base + f\"file{file_id}\"\n        fd = open(filename, \"wb\")\n        pickle.dump(positions, fd)\n        fd.close()\n\n    npart_rnd = int(frac_random * npart)\n    npart_gau = npart - npart_rnd\n    dim_hilbert = 1 << order\n    nH = dim_hilbert**3\n    if nH < nfiles:\n        raise ValueError(\"Fewer hilbert cells than files.\")\n    nHPF = nH / nfiles\n    rHPF = nH % nfiles\n    for ichunk in range(nchunk):\n        inp = npart_gau / nchunk\n        if ichunk == 0:\n            inp += npart_gau % nchunk\n        pos = np.empty((inp, 3), dtype=\"float64\")\n        ind = np.empty((inp, 3), dtype=\"int64\")\n        for k in range(3):\n            pos[:, k] = np.clip(\n                np.random.normal(center[k], width[k], inp),\n                DLE[k],\n                DRE[k] - (1.0e-9) * DW[k],\n            )\n            ind[:, k] = (pos[:, k] - DLE[k]) / (DW[k] / dim_hilbert)\n        harr = get_hilbert_indices(order, ind)\n        farr = (harr - rHPF) / nHPF\n        for ifile in range(nfiles):\n            ipos = load_pos(ifile)\n            if ifile == 0:\n                idx = farr <= ifile  # Put remainders in first file\n            else:\n                idx = farr == ifile\n            ipos = np.concatenate((ipos, pos[idx, :]), axis=0)\n            save_pos(ifile, ipos)\n    # Random\n    for ifile in range(nfiles):\n        ipos = load_pos(ifile)\n        ipos_rnd = fake_decomp_hilbert_uniform(\n            npart_rnd, nfiles, ifile, DLE, DRE, buff=buff, order=order, verbose=verbose\n        )\n        ipos = np.concatenate((ipos, ipos_rnd), axis=0)\n        save_pos(ifile, ipos)\n\n\ndef fake_decomp_hilbert_gaussian(\n    npart, nfiles, ifile, DLE, DRE, buff=0.0, order=6, verbose=False, fname=None\n):\n    np.random.seed(0x4D3D3D3)\n    DW = DRE - DLE\n    dim_hilbert = 1 << order\n    nH = dim_hilbert**3\n    if nH < nfiles:\n        raise Exception(\"Fewer hilbert cells than files.\")\n    nHPF = nH / nfiles\n    rHPF = nH % nfiles\n    hdiv = DW / dim_hilbert\n    if ifile == 0:\n        hlist = np.arange(0, nHPF + rHPF, dtype=\"int64\")\n    else:\n        hlist = np.arange(ifile * nHPF + rHPF, (ifile + 1) * nHPF + rHPF, dtype=\"int64\")\n    hpos = get_hilbert_points(order, hlist)\n    iLE = np.empty((len(hlist), 3), dtype=\"float\")\n    iRE = np.empty((len(hlist), 3), dtype=\"float\")\n    count = np.zeros(3, dtype=\"int64\")\n    pos = np.empty((npart, 3), dtype=\"float\")\n    for k in range(3):\n        iLE[:, k] = DLE[k] + hdiv[k] * hpos[:, k]\n        iRE[:, k] = iLE[:, k] + hdiv[k]\n        iLE[hpos[:, k] != 0, k] -= buff * hdiv[k]\n        iRE[hpos[:, k] != (dim_hilbert - 1), k] += buff * hdiv[k]\n        gpos = np.clip(\n            np.random.normal(DLE[k] + DW[k] / 2.0, DW[k] / 10.0, npart), DLE[k], DRE[k]\n        )\n        for ipos in gpos:\n            for i in range(len(hlist)):\n                if iLE[i, k] <= ipos < iRE[i, k]:\n                    pos[count[k], k] = ipos\n                    count[k] += 1\n                    break\n    return pos[: count.min(), :]\n\n\ndef fake_decomp_hilbert_uniform(\n    npart, nfiles, ifile, DLE, DRE, buff=0.0, order=6, verbose=False\n):\n    np.random.seed(0x4D3D3D3 + ifile)\n    DW = DRE - DLE\n    dim_hilbert = 1 << order\n    nH = dim_hilbert**3\n    if nH < nfiles:\n        raise Exception(\"Fewer hilbert cells than files.\")\n    nHPF = nH / nfiles\n    rHPF = nH % nfiles\n    nPH = npart / nH\n    nRH = npart % nH\n    hind = np.arange(nH, dtype=\"int64\")\n    hpos = get_hilbert_points(order, hind)\n    hdiv = DW / dim_hilbert\n    if ifile == 0:\n        hlist = range(0, nHPF + rHPF)\n        nptot = nPH * len(hlist) + nRH\n    else:\n        hlist = range(ifile * nHPF + rHPF, (ifile + 1) * nHPF + rHPF)\n        nptot = nPH * len(hlist)\n    pos = np.empty((nptot, 3), dtype=\"float\")\n    pc = 0\n    for i in hlist:\n        iLE = DLE + hdiv * hpos[i, :]\n        iRE = iLE + hdiv\n        for k in range(3):  # Don't add buffer past domain bounds\n            if hpos[i, k] != 0:\n                iLE[k] -= buff * hdiv[k]\n            if hpos[i, k] != (dim_hilbert - 1):\n                iRE[k] += buff * hdiv[k]\n        inp = nPH\n        if (ifile == 0) and (i == 0):\n            inp += nRH\n        for k in range(3):\n            pos[pc : (pc + inp), k] = np.random.uniform(iLE[k], iRE[k], inp)\n        pc += inp\n    return pos\n\n\ndef fake_decomp_morton(\n    npart, nfiles, ifile, DLE, DRE, buff=0.0, order=6, verbose=False\n):\n    np.random.seed(0x4D3D3D3 + ifile)\n    DW = DRE - DLE\n    dim_morton = 1 << order\n    nH = dim_morton**3\n    if nH < nfiles:\n        raise Exception(\"Fewer morton cells than files.\")\n    nHPF = nH / nfiles\n    rHPF = nH % nfiles\n    nPH = npart / nH\n    nRH = npart % nH\n    hind = np.arange(nH, dtype=\"uint64\")\n    hpos = get_morton_points(hind)\n    hdiv = DW / dim_morton\n    if ifile == 0:\n        hlist = range(0, nHPF + rHPF)\n        nptot = nPH * len(hlist) + nRH\n    else:\n        hlist = range(ifile * nHPF + rHPF, (ifile + 1) * nHPF + rHPF)\n        nptot = nPH * len(hlist)\n    pos = np.empty((nptot, 3), dtype=\"float\")\n    pc = 0\n    for i in hlist:\n        iLE = DLE + hdiv * hpos[i, :]\n        iRE = iLE + hdiv\n        for k in range(3):  # Don't add buffer past domain bounds\n            if hpos[i, k] != 0:\n                iLE[k] -= buff * hdiv[k]\n            if hpos[i, k] != (dim_morton - 1):\n                iRE[k] += buff * hdiv[k]\n        inp = nPH\n        if (ifile == 0) and (i == 0):\n            inp += nRH\n        for k in range(3):\n            pos[pc : (pc + inp), k] = np.random.uniform(iLE[k], iRE[k], inp)\n        pc += inp\n    return pos\n\n\ndef fake_decomp_grid(npart, nfiles, ifile, DLE, DRE, buff=0.0, verbose=False):\n    # TODO: handle 'remainder' particles\n    np.random.seed(0x4D3D3D3 + ifile)\n    DW = DRE - DLE\n    nYZ = int(np.sqrt(npart / nfiles))\n    div = DW / nYZ\n    Y, Z = np.mgrid[\n        DLE[1] + 0.1 * div[1] : DRE[1] - 0.1 * div[1] : nYZ * 1j,\n        DLE[2] + 0.1 * div[2] : DRE[2] - 0.1 * div[2] : nYZ * 1j,\n    ]\n    X = 0.5 * div[0] * np.ones(Y.shape, dtype=\"float64\") + div[0] * ifile\n    pos = np.array([X.ravel(), Y.ravel(), Z.ravel()], dtype=\"float64\").transpose()\n    return pos\n\n\ndef yield_fake_decomp(decomp, npart, nfiles, DLE, DRE, **kws):\n    hsml = None\n    for ifile in range(nfiles):\n        yield fake_decomp(decomp, npart, nfiles, ifile, DLE, DRE, **kws), hsml\n\n\ndef fake_decomp(\n    decomp, npart, nfiles, ifile, DLE, DRE, distrib=\"uniform\", fname=None, **kws\n):\n    import pickle\n\n    if fname is None and distrib == \"gaussian\":\n        fname = f\"{decomp}6_{distrib}_np{npart}_nf{nfiles}_file{ifile}\"\n    if fname is not None and os.path.isfile(fname):\n        fd = open(fname, \"rb\")\n        pos = pickle.load(fd)\n        fd.close()\n        return pos\n    if decomp.startswith(\"zoom_\"):\n        zoom_factor = 5\n        decomp_zoom = decomp.split(\"zoom_\")[-1]\n        zoom_npart = npart / 2\n        zoom_rem = npart % 2\n        pos1 = fake_decomp(\n            decomp_zoom,\n            zoom_npart + zoom_rem,\n            nfiles,\n            ifile,\n            DLE,\n            DRE,\n            distrib=distrib,\n            **kws,\n        )\n        DLE_zoom = DLE + 0.5 * DW * (1.0 - 1.0 / float(zoom_factor))\n        DRE_zoom = DLE_zoom + DW / zoom_factor\n        pos2 = fake_decomp(\n            decomp_zoom,\n            zoom_npart,\n            nfiles,\n            ifile,\n            DLE_zoom,\n            DRE_zoom,\n            distrib=distrib,\n            **kws,\n        )\n        pos = np.concatenate((pos1, pos2), axis=0)\n    elif \"_\" in decomp:\n        decomp_list = decomp.split(\"_\")\n        decomp_np = npart / len(decomp_list)\n        decomp_nr = npart % len(decomp_list)\n        pos = np.empty((0, 3), dtype=\"float\")\n        for i, idecomp in enumerate(decomp_list):\n            inp = decomp_np\n            if i == 0:\n                inp += decomp_nr\n            ipos = fake_decomp(\n                idecomp, inp, nfiles, ifile, DLE, DRE, distrib=distrib, **kws\n            )\n            pos = np.concatenate((pos, ipos), axis=0)\n    # A perfect grid, no overlap between files\n    elif decomp == \"grid\":\n        pos = fake_decomp_grid(npart, nfiles, ifile, DLE, DRE, **kws)\n    # Completely random data set\n    elif decomp == \"random\":\n        if distrib == \"uniform\":\n            pos = fake_decomp_random(npart, nfiles, ifile, DLE, DRE, **kws)\n        else:\n            raise ValueError(\n                f\"Unsupported value {distrib} for input parameter 'distrib'\"\n            )\n    # Each file contains a slab (part of x domain, all of y/z domain)\n    elif decomp == \"sliced\":\n        if distrib == \"uniform\":\n            pos = fake_decomp_sliced(npart, nfiles, ifile, DLE, DRE, **kws)\n        else:\n            raise ValueError(\n                f\"Unsupported value {distrib} for input parameter 'distrib'\"\n            )\n    # Particles are assigned to files based on their location on a\n    # Peano-Hilbert curve of order 6\n    elif decomp.startswith(\"hilbert\"):\n        if decomp == \"hilbert\":\n            kws[\"order\"] = 6\n        else:\n            kws[\"order\"] = int(decomp.split(\"hilbert\")[-1])\n        if distrib == \"uniform\":\n            pos = fake_decomp_hilbert_uniform(npart, nfiles, ifile, DLE, DRE, **kws)\n        elif distrib == \"gaussian\":\n            makeall_decomp_hilbert_gaussian(\n                npart, nfiles, DLE, DRE, fname_base=fname.split(\"file\")[0], **kws\n            )\n            pos = fake_decomp(\n                decomp,\n                npart,\n                nfiles,\n                ifile,\n                DLE,\n                DRE,\n                distrib=distrib,\n                fname=fname,\n                **kws,\n            )\n        else:\n            raise ValueError(\n                f\"Unsupported value {distrib} for input parameter 'distrib'\"\n            )\n    # Particles are assigned to files based on their location on a\n    # Morton ordered Z-curve of order 6\n    elif decomp.startswith(\"morton\"):\n        if decomp == \"morton\":\n            kws[\"order\"] = 6\n        else:\n            kws[\"order\"] = int(decomp.split(\"morton\")[-1])\n        if distrib == \"uniform\":\n            pos = fake_decomp_morton(npart, nfiles, ifile, DLE, DRE, **kws)\n        else:\n            raise ValueError(\n                f\"Unsupported value {distrib} for input parameter 'distrib'\"\n            )\n    else:\n        raise ValueError(f\"Unsupported value {decomp} for input parameter 'decomp'\")\n    # Save\n    if fname is not None:\n        fd = open(fname, \"wb\")\n        pickle.dump(pos, fd)\n        fd.close()\n    return pos\n"
  },
  {
    "path": "yt/geometry/tests/test_pickleable_selections.py",
    "content": "import pickle\n\nimport numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import fake_particle_ds\n\n\ndef test_pickleability():\n    # tests the pickleability of the selection objects.\n\n    def pickle_test(sel_obj):\n        assert_fields = sel_obj._get_state_attnames()  # the attrs used in get/set state\n        new_sel_obj = pickle.loads(pickle.dumps(sel_obj))\n        for attr in assert_fields:\n            assert_equal(getattr(new_sel_obj, attr), getattr(sel_obj, attr))\n\n    # list of selection types and argument tuples for each selection type\n    c = np.array([0.5, 0.5, 0.5])\n    sargs = (\n        (\"point\", (c,)),\n        (\"sphere\", (c, 0.25)),\n        (\"box\", (c - 0.3, c)),\n        (\"ellipsoid\", (c, 0.3, 0.2, 0.1, c - 0.4, 0.2)),\n        (\"disk\", (c, [1, 0, 0], 0.2, 0.2)),\n        (\"cutting\", ([0.1, 0.2, -0.9], [0.5, 0.42, 0.6])),\n        (\"ortho_ray\", (\"z\", c)),\n        (\"ray\", (c, [0.1, 0.1, 0.1])),\n    )\n\n    # load fake data\n    ds = fake_particle_ds()\n    for sel_type, args in sargs:\n        sel = getattr(ds, sel_type)(*args)  # instantiate this selection type\n        pickle_test(sel.selector)  # make sure it (un)pickles\n"
  },
  {
    "path": "yt/geometry/unstructured_mesh_handler.py",
    "content": "import os\nimport weakref\n\nimport numpy as np\n\nfrom yt.geometry.geometry_handler import Index, YTDataChunk\nfrom yt.utilities.lib.mesh_utilities import smallest_fwidth\nfrom yt.utilities.logger import ytLogger as mylog\n\n\nclass UnstructuredIndex(Index):\n    \"\"\"The Index subclass for unstructured and hexahedral mesh datasets.\"\"\"\n\n    _unsupported_objects = (\"proj\", \"covering_grid\", \"smoothed_covering_grid\")\n\n    def __init__(self, ds, dataset_type):\n        self.dataset_type = dataset_type\n        self.dataset = weakref.proxy(ds)\n        self.index_filename = self.dataset.parameter_filename\n        self.directory = os.path.dirname(self.index_filename)\n        self.float_type = np.float64\n        super().__init__(ds, dataset_type)\n\n    def _setup_geometry(self):\n        mylog.debug(\"Initializing Unstructured Mesh Geometry Handler.\")\n        self._initialize_mesh()\n\n    def get_smallest_dx(self):\n        \"\"\"\n        Returns (in code units) the smallest cell size in the simulation.\n        \"\"\"\n        dx = min(\n            smallest_fwidth(\n                mesh.connectivity_coords, mesh.connectivity_indices, mesh._index_offset\n            )\n            for mesh in self.meshes\n        )\n        return dx\n\n    def convert(self, unit):\n        return self.dataset.conversion_factors[unit]\n\n    def _initialize_mesh(self):\n        raise NotImplementedError\n\n    def _identify_base_chunk(self, dobj):\n        if getattr(dobj, \"_chunk_info\", None) is None:\n            dobj._chunk_info = self.meshes\n        if getattr(dobj, \"size\", None) is None:\n            dobj.size = self._count_selection(dobj)\n        dobj._current_chunk = list(self._chunk_all(dobj))[0]\n\n    def _count_selection(self, dobj, meshes=None):\n        if meshes is None:\n            meshes = dobj._chunk_info\n        count = sum(m.count(dobj.selector) for m in meshes)\n        return count\n\n    def _chunk_all(self, dobj, cache=True):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        yield YTDataChunk(dobj, \"all\", oobjs, dobj.size, cache)\n\n    def _chunk_spatial(self, dobj, ngz, sort=None, preload_fields=None):\n        sobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        # This is where we will perform cutting of the Octree and\n        # load-balancing.  That may require a specialized selector object to\n        # cut based on some space-filling curve index.\n        for og in sobjs:\n            if ngz > 0:\n                g = og.retrieve_ghost_zones(ngz, [], smoothed=True)\n            else:\n                g = og\n            size = self._count_selection(dobj, [og])\n            if size == 0:\n                continue\n            yield YTDataChunk(dobj, \"spatial\", [g], size)\n\n    def _chunk_io(self, dobj, cache=True, local_only=False):\n        oobjs = getattr(dobj._current_chunk, \"objs\", dobj._chunk_info)\n        for subset in oobjs:\n            s = self._count_selection(dobj, oobjs)\n            yield YTDataChunk(dobj, \"io\", [subset], s, cache=cache)\n"
  },
  {
    "path": "yt/geometry/vectorized_ops.h",
    "content": "typedef double v4df __attribute__ ((vector_size(32))); // vector of four double floats\n"
  },
  {
    "path": "yt/loaders.py",
    "content": "\"\"\"\nThis module gathers all user-facing functions with a `load_` prefix.\n\n\"\"\"\n\nimport atexit\nimport os\nimport sys\nimport time\nimport types\nimport warnings\nfrom collections.abc import Mapping\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING, Any, Union, cast\nfrom urllib.parse import urlsplit\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt._maintenance.deprecation import (\n    future_positional_only,\n    issue_deprecation_warning,\n)\nfrom yt._typing import AnyFieldKey, AxisOrder, FieldKey\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import levenshtein_distance\nfrom yt.sample_data.api import lookup_on_disk_data\nfrom yt.utilities.decompose import decompose_array, get_psize\nfrom yt.utilities.exceptions import (\n    MountError,\n    YTAmbiguousDataType,\n    YTIllDefinedAMR,\n    YTSimulationNotIdentified,\n    YTUnidentifiedDataType,\n)\nfrom yt.utilities.hierarchy_inspection import find_lowest_subclasses\nfrom yt.utilities.lib.misc_utilities import get_box_grids_level\nfrom yt.utilities.logger import ytLogger as mylog\nfrom yt.utilities.object_registries import (\n    output_type_registry,\n    simulation_time_series_registry,\n)\nfrom yt.utilities.on_demand_imports import _pooch as pooch, _ratarmount as ratarmount\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    parallel_root_only_then_broadcast,\n)\n\nif TYPE_CHECKING:\n    from multiprocessing.connection import Connection\n\n# --- Loaders for known data formats ---\n\n\n# FUTURE: embedded warnings need to have their stacklevel decremented when this decorator is removed\n@future_positional_only({0: \"fn\"}, since=\"4.2\")\ndef load(fn: Union[str, \"os.PathLike[str]\"], *args, hint: str | None = None, **kwargs):\n    \"\"\"\n    Load a Dataset or DatasetSeries object.\n    The data format is automatically discovered, and the exact return type is the\n    corresponding subclass of :class:`yt.data_objects.static_output.Dataset`.\n    A :class:`yt.data_objects.time_series.DatasetSeries` is created if the first\n    argument is a pattern.\n\n    Parameters\n    ----------\n    fn : str, os.Pathlike[str]\n        A path to the data location. This can be a file name, directory name, a glob\n        pattern, or a url (for data types that support it).\n\n    hint : str, optional\n        Only classes whose name include a hint are considered. If loading fails with\n        a YTAmbiguousDataType exception, this argument can be used to lift ambiguity.\n        Hints are case insensitive.\n\n    Additional arguments, if any, are passed down to the return class.\n\n    Returns\n    -------\n    :class:`yt.data_objects.static_output.Dataset` object\n        If fn is a single path, create a Dataset from the appropriate subclass.\n\n    :class:`yt.data_objects.time_series.DatasetSeries`\n        If fn is a glob pattern (i.e. containing wildcards '[]?!*'), create a series.\n\n    Raises\n    ------\n    FileNotFoundError\n        If fn does not match any existing file or directory.\n\n    yt.utilities.exceptions.YTUnidentifiedDataType\n        If fn matches existing files or directories with undetermined format.\n\n    yt.utilities.exceptions.YTAmbiguousDataType\n        If the data format matches more than one class of similar specialization levels.\n    \"\"\"\n    from importlib.metadata import entry_points\n\n    from yt.frontends import _all  # type: ignore [attr-defined] # noqa\n\n    _input_fn = fn\n    fn = os.path.expanduser(fn)\n\n    if any(wildcard in fn for wildcard in \"[]?!*\"):\n        from yt.data_objects.time_series import DatasetSeries\n\n        return DatasetSeries(fn, *args, hint=hint, **kwargs)\n\n    # This will raise FileNotFoundError if the path isn't matched\n    # either in the current dir or yt.config.ytcfg['data_dir_directory']\n    if not fn.startswith(\"http\"):\n        fn = str(lookup_on_disk_data(fn))\n\n    external_frontends = entry_points(group=\"yt.frontends\")\n\n    # Ensure that external frontends are loaded\n    for entrypoint in external_frontends:\n        entrypoint.load()\n\n    candidates: list[type[Dataset]] = []\n    for cls in output_type_registry.values():\n        if cls._is_valid(fn, *args, **kwargs):\n            candidates.append(cls)\n\n    # Filter the candidates if a hint was given\n    if hint is not None:\n        candidates = [c for c in candidates if hint.lower() in c.__name__.lower()]\n\n    # Find only the lowest subclasses, i.e. most specialised front ends\n    candidates = find_lowest_subclasses(candidates)\n\n    if len(candidates) == 1:\n        cls = candidates[0]\n        if missing := cls._missing_load_requirements():\n            warnings.warn(\n                f\"This dataset appears to be of type {cls.__name__}, \"\n                \"but the following requirements are currently missing: \"\n                f\"{', '.join(missing)}\\n\"\n                \"Please verify your installation.\",\n                stacklevel=3,\n            )\n        return cls(fn, *args, **kwargs)\n\n    if len(candidates) > 1:\n        raise YTAmbiguousDataType(_input_fn, candidates)\n\n    raise YTUnidentifiedDataType(_input_fn, *args, **kwargs)\n\n\ndef load_simulation(fn, simulation_type, find_outputs=False):\n    \"\"\"\n    Load a simulation time series object of the specified simulation type.\n\n    Parameters\n    ----------\n    fn : str, os.Pathlike, or byte (types supported by os.path.expandusers)\n        Name of the data file or directory.\n\n    simulation_type : str\n        E.g. 'Enzo'\n\n    find_outputs : bool\n        Defaults to False\n\n    Raises\n    ------\n    FileNotFoundError\n        If fn is not found.\n\n    yt.utilities.exceptions.YTSimulationNotIdentified\n        If simulation_type is unknown.\n    \"\"\"\n    from yt.frontends import _all  # noqa\n\n    fn = str(lookup_on_disk_data(fn))\n\n    try:\n        cls = simulation_time_series_registry[simulation_type]\n    except KeyError as e:\n        raise YTSimulationNotIdentified(simulation_type) from e\n\n    return cls(fn, find_outputs=find_outputs)\n\n\n# --- Loaders for generic (\"stream\") data ---\n\n\ndef _sanitize_axis_order_args(\n    geometry: str | tuple[str, AxisOrder], axis_order: AxisOrder | None\n) -> tuple[str, AxisOrder | None]:\n    # this entire function should be removed at the end of its deprecation cycle\n    geometry_str: str\n    if isinstance(geometry, tuple):\n        issue_deprecation_warning(\n            f\"Received a tuple as {geometry=}\\nUse the `axis_order` argument instead.\",\n            since=\"4.2\",\n            stacklevel=4,\n        )\n        geometry_str, axis_order = geometry\n    else:\n        geometry_str = geometry\n    return geometry_str, axis_order\n\n\ndef load_uniform_grid(\n    data,\n    domain_dimensions,\n    length_unit=None,\n    bbox=None,\n    nprocs=1,\n    sim_time=0.0,\n    mass_unit=None,\n    time_unit=None,\n    velocity_unit=None,\n    magnetic_unit=None,\n    periodicity=(True, True, True),\n    geometry=\"cartesian\",\n    unit_system=\"cgs\",\n    default_species_fields=None,\n    *,\n    axis_order: AxisOrder | None = None,\n    cell_widths=None,\n    parameters=None,\n    dataset_name: str = \"UniformGridData\",\n):\n    r\"\"\"Load a uniform grid of data into yt as a\n    :class:`~yt.frontends.stream.data_structures.StreamHandler`.\n\n    This should allow a uniform grid of data to be loaded directly into yt and\n    analyzed as would any others.  This comes with several caveats:\n\n    * Units will be incorrect unless the unit system is explicitly\n      specified.\n    * Some functions may behave oddly, and parallelism will be\n      disappointing or non-existent in most cases.\n    * Particles may be difficult to integrate.\n\n    Particle fields are detected as one-dimensional fields.\n\n    Parameters\n    ----------\n    data : dict\n        This is a dict of numpy arrays, (numpy array, unit spec) tuples.\n        Functions may also be supplied in place of numpy arrays as long as the\n        subsequent argument nprocs is not specified to be greater than 1.\n        Supplied functions much accepts the arguments (grid_object, field_name)\n        and return numpy arrays.  The keys to the dict are the field names.\n    domain_dimensions : array_like\n        This is the domain dimensions of the grid\n    length_unit : string\n        Unit to use for lengths.  Defaults to unitless.\n    bbox : array_like (xdim:zdim, LE:RE), optional\n        Size of computational domain in units specified by length_unit.\n        Defaults to a cubic unit-length domain.\n    nprocs: integer, optional\n        If greater than 1, will create this number of subarrays out of data\n    sim_time : float, optional\n        The simulation time in seconds\n    mass_unit : string\n        Unit to use for masses.  Defaults to unitless.\n    time_unit : string\n        Unit to use for times.  Defaults to unitless.\n    velocity_unit : string\n        Unit to use for velocities.  Defaults to unitless.\n    magnetic_unit : string\n        Unit to use for magnetic fields. Defaults to unitless.\n    periodicity : tuple of booleans\n        Determines whether the data will be treated as periodic along\n        each axis\n    geometry : string (or tuple, deprecated)\n        \"cartesian\", \"cylindrical\", \"polar\", \"spherical\", \"geographic\" or\n        \"spectral_cube\".\n        [DEPRECATED]: Optionally, a tuple can be provided to specify the axis ordering.\n        For instance, to specify that the axis ordering should\n        be z, x, y, this would be: (\"cartesian\", (\"z\", \"x\", \"y\")).  The same\n        can be done for other coordinates, for instance:\n        (\"spherical\", (\"theta\", \"phi\", \"r\")).\n    default_species_fields : string, optional\n        If set, default species fields are created for H and He which also\n        determine the mean molecular weight. Options are \"ionized\" and \"neutral\".\n    axis_order: tuple of three strings, optional\n        Force axis ordering, e.g. (\"z\", \"y\", \"x\") with cartesian geometry\n        Otherwise use geometry-specific default ordering.\n    cell_widths: list, optional\n        If set, cell_widths is a list of arrays with an array for each dimension,\n        specificing the cell spacing in that dimension. Must be consistent with\n        the domain_dimensions.\n    parameters: dictionary, optional\n        Optional dictionary used to populate the dataset parameters, useful\n        for storing dataset metadata.\n    dataset_name: string, optional\n        Optional string used to assign a name to the dataset. Stream datasets will use\n        this value in place of a filename (in image prefixing, etc.)\n\n    Examples\n    --------\n    >>> np.random.seed(int(0x4D3D3D3))\n    >>> bbox = np.array([[0.0, 1.0], [-1.5, 1.5], [1.0, 2.5]])\n    >>> arr = np.random.random((128, 128, 128))\n    >>> data = dict(density=arr)\n    >>> ds = load_uniform_grid(data, arr.shape, length_unit=\"cm\", bbox=bbox, nprocs=12)\n    >>> dd = ds.all_data()\n    >>> dd[\"gas\", \"density\"]\n    unyt_array([0.76017901, 0.96855994, 0.49205428, ..., 0.78798258,\n                0.97569432, 0.99453904], 'g/cm**3')\n    \"\"\"\n    from yt.frontends.stream.data_structures import (\n        StreamDataset,\n        StreamDictFieldHandler,\n        StreamHandler,\n    )\n    from yt.frontends.stream.definitions import (\n        assign_particle_data,\n        process_data,\n        set_particle_types,\n    )\n    from yt.frontends.stream.misc import _validate_cell_widths\n\n    geometry, axis_order = _sanitize_axis_order_args(geometry, axis_order)\n    domain_dimensions = np.array(domain_dimensions)\n    if bbox is None:\n        bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]], \"float64\")\n    domain_left_edge = np.array(bbox[:, 0], \"float64\")\n    domain_right_edge = np.array(bbox[:, 1], \"float64\")\n    grid_levels = np.zeros(nprocs, dtype=\"int32\").reshape((nprocs, 1))\n    # First we fix our field names, apply units to data\n    # and check for consistency of field shapes\n    field_units, data, number_of_particles = process_data(\n        data, grid_dims=tuple(domain_dimensions), allow_callables=nprocs == 1\n    )\n\n    sfh = StreamDictFieldHandler()\n\n    if number_of_particles > 0:\n        particle_types = set_particle_types(data)\n        # Used much further below.\n        pdata: dict[str | FieldKey, Any] = {\"number_of_particles\": number_of_particles}\n        for key in list(data.keys()):\n            if len(data[key].shape) == 1 or key[0] == \"io\":\n                field: FieldKey\n                if not isinstance(key, tuple):\n                    field = (\"io\", key)\n                    mylog.debug(\"Reassigning '%s' to '%s'\", key, field)\n                else:\n                    key = cast(\"FieldKey\", key)\n                    field = key\n                sfh._additional_fields += (field,)\n                pdata[field] = data.pop(key)\n    else:\n        particle_types = {}\n\n    if cell_widths is not None:\n        cell_widths = _validate_cell_widths(cell_widths, domain_dimensions)\n\n    if nprocs > 1:\n        temp = {}\n        new_data = {}  # type: ignore [var-annotated]\n        for key in data.keys():\n            psize = get_psize(np.array(data[key].shape), nprocs)\n            (\n                grid_left_edges,\n                grid_right_edges,\n                shapes,\n                slices,\n                grid_cell_widths,\n            ) = decompose_array(\n                data[key].shape,\n                psize,\n                bbox,\n                cell_widths=cell_widths,\n            )\n            grid_dimensions = np.array(list(shapes), dtype=\"int32\")\n            temp[key] = [data[key][slice] for slice in slices]\n        cell_widths = grid_cell_widths\n\n        for gid in range(nprocs):\n            new_data[gid] = {}\n            for key in temp.keys():\n                new_data[gid].update({key: temp[key][gid]})\n        sfh.update(new_data)\n        del new_data, temp\n    else:\n        sfh.update({0: data})\n        grid_left_edges = domain_left_edge\n        grid_right_edges = domain_right_edge\n        grid_dimensions = domain_dimensions.reshape(nprocs, 3).astype(\"int32\")\n        if cell_widths is not None:\n            cell_widths = [\n                cell_widths,\n            ]\n\n    if length_unit is None:\n        length_unit = \"code_length\"\n    if mass_unit is None:\n        mass_unit = \"code_mass\"\n    if time_unit is None:\n        time_unit = \"code_time\"\n    if velocity_unit is None:\n        velocity_unit = \"code_velocity\"\n    if magnetic_unit is None:\n        magnetic_unit = \"code_magnetic\"\n\n    handler = StreamHandler(\n        grid_left_edges,\n        grid_right_edges,\n        grid_dimensions,\n        grid_levels,\n        -np.ones(nprocs, dtype=\"int64\"),\n        np.zeros(nprocs, dtype=\"int64\").reshape(nprocs, 1),  # particle count\n        np.zeros(nprocs).reshape((nprocs, 1)),\n        sfh,\n        field_units,\n        (length_unit, mass_unit, time_unit, velocity_unit, magnetic_unit),\n        particle_types=particle_types,\n        periodicity=periodicity,\n        cell_widths=cell_widths,\n        parameters=parameters,\n    )\n\n    handler.name = dataset_name  # type: ignore [attr-defined]\n    handler.domain_left_edge = domain_left_edge  # type: ignore [attr-defined]\n    handler.domain_right_edge = domain_right_edge  # type: ignore [attr-defined]\n    handler.refine_by = 2  # type: ignore [attr-defined]\n    if np.all(domain_dimensions[1:] == 1):\n        dimensionality = 1\n    elif domain_dimensions[2] == 1:\n        dimensionality = 2\n    else:\n        dimensionality = 3\n    handler.dimensionality = dimensionality  # type: ignore [attr-defined]\n    handler.domain_dimensions = domain_dimensions  # type: ignore [attr-defined]\n    handler.simulation_time = sim_time  # type: ignore [attr-defined]\n    handler.cosmology_simulation = 0  # type: ignore [attr-defined]\n\n    sds = StreamDataset(\n        handler,\n        geometry=geometry,\n        axis_order=axis_order,\n        unit_system=unit_system,\n        default_species_fields=default_species_fields,\n    )\n\n    # Now figure out where the particles go\n    if number_of_particles > 0:\n        # This will update the stream handler too\n        assign_particle_data(sds, pdata, bbox)\n\n    return sds\n\n\ndef load_amr_grids(\n    grid_data,\n    domain_dimensions,\n    bbox=None,\n    sim_time=0.0,\n    length_unit=None,\n    mass_unit=None,\n    time_unit=None,\n    velocity_unit=None,\n    magnetic_unit=None,\n    periodicity=(True, True, True),\n    geometry=\"cartesian\",\n    refine_by=2,\n    unit_system=\"cgs\",\n    default_species_fields=None,\n    *,\n    parameters=None,\n    dataset_name: str = \"AMRGridData\",\n    axis_order: AxisOrder | None = None,\n):\n    r\"\"\"Load a set of grids of data into yt as a\n    :class:`~yt.frontends.stream.data_structures.StreamHandler`.\n    This should allow a sequence of grids of varying resolution of data to be\n    loaded directly into yt and analyzed as would any others.  This comes with\n    several caveats:\n\n    * Units will be incorrect unless the unit system is explicitly specified.\n    * Some functions may behave oddly, and parallelism will be\n      disappointing or non-existent in most cases.\n    * Particles may be difficult to integrate.\n    * No consistency checks are performed on the index\n\n    Parameters\n    ----------\n\n    grid_data : list of dicts\n        This is a list of dicts. Each dict must have entries \"left_edge\",\n        \"right_edge\", \"dimensions\", \"level\", and then any remaining entries are\n        assumed to be fields. Field entries must map to an NDArray *or* a\n        function with the signature (grid_object, field_name) -> NDArray. The\n        grid_data may also include a particle count. If no particle count is\n        supplied, the dataset is understood to contain no particles. The\n        grid_data will be modified in place and can't be assumed to be static.\n        Grid data may also be supplied as a tuple of (NDarray or function, unit\n        string) to specify the units.\n    domain_dimensions : array_like\n        This is the domain dimensions of the grid\n    length_unit : string or float\n        Unit to use for lengths.  Defaults to unitless.  If set to be a string, the bbox\n        dimensions are assumed to be in the corresponding units.  If set to a float, the\n        value is a assumed to be the conversion from bbox dimensions to centimeters.\n    mass_unit : string or float\n        Unit to use for masses.  Defaults to unitless.\n    time_unit : string or float\n        Unit to use for times.  Defaults to unitless.\n    velocity_unit : string or float\n        Unit to use for velocities.  Defaults to unitless.\n    magnetic_unit : string or float\n        Unit to use for magnetic fields.  Defaults to unitless.\n    bbox : array_like (xdim:zdim, LE:RE), optional\n        Size of computational domain in units specified by length_unit.\n        Defaults to a cubic unit-length domain.\n    sim_time : float, optional\n        The simulation time in seconds\n    periodicity : tuple of booleans\n        Determines whether the data will be treated as periodic along\n        each axis\n    geometry : string (or tuple, deprecated)\n        \"cartesian\", \"cylindrical\", \"polar\", \"spherical\", \"geographic\" or\n        \"spectral_cube\".\n        [DEPRECATED]: Optionally, a tuple can be provided to specify the axis ordering.\n        For instance, to specify that the axis ordering should\n        be z, x, y, this would be: (\"cartesian\", (\"z\", \"x\", \"y\")).  The same\n        can be done for other coordinates, for instance:\n        (\"spherical\", (\"theta\", \"phi\", \"r\")).\n    refine_by : integer or list/array of integers.\n        Specifies the refinement ratio between levels.  Defaults to 2.  This\n        can be an array, in which case it specifies for each dimension.  For\n        instance, this can be used to say that some datasets have refinement of\n        1 in one dimension, indicating that they span the full range in that\n        dimension.\n    default_species_fields : string, optional\n        If set, default species fields are created for H and He which also\n        determine the mean molecular weight. Options are \"ionized\" and \"neutral\".\n    parameters: dictionary, optional\n        Optional dictionary used to populate the dataset parameters, useful\n        for storing dataset metadata.\n    dataset_name: string, optional\n        Optional string used to assign a name to the dataset. Stream datasets will use\n        this value in place of a filename (in image prefixing, etc.)\n    axis_order: tuple of three strings, optional\n        Force axis ordering, e.g. (\"z\", \"y\", \"x\") with cartesian geometry\n        Otherwise use geometry-specific default ordering.\n\n    Examples\n    --------\n\n    >>> grid_data = [\n    ...     dict(\n    ...         left_edge=[0.0, 0.0, 0.0],\n    ...         right_edge=[1.0, 1.0, 1.0],\n    ...         level=0,\n    ...         dimensions=[32, 32, 32],\n    ...         number_of_particles=0,\n    ...     ),\n    ...     dict(\n    ...         left_edge=[0.25, 0.25, 0.25],\n    ...         right_edge=[0.75, 0.75, 0.75],\n    ...         level=1,\n    ...         dimensions=[32, 32, 32],\n    ...         number_of_particles=0,\n    ...     ),\n    ... ]\n    ...\n    >>> for g in grid_data:\n    ...     g[\"gas\", \"density\"] = (\n    ...         np.random.random(g[\"dimensions\"]) * 2 ** g[\"level\"],\n    ...         \"g/cm**3\",\n    ...     )\n    ...\n    >>> ds = load_amr_grids(grid_data, [32, 32, 32], length_unit=1.0)\n    \"\"\"\n    from yt.frontends.stream.data_structures import (\n        StreamDataset,\n        StreamDictFieldHandler,\n        StreamHandler,\n    )\n    from yt.frontends.stream.definitions import process_data, set_particle_types\n\n    geometry, axis_order = _sanitize_axis_order_args(geometry, axis_order)\n    domain_dimensions = np.array(domain_dimensions)\n    ngrids = len(grid_data)\n    if bbox is None:\n        bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]], \"float64\")\n    domain_left_edge = np.array(bbox[:, 0], \"float64\")\n    domain_right_edge = np.array(bbox[:, 1], \"float64\")\n    grid_levels = np.zeros((ngrids, 1), dtype=\"int32\")\n    grid_left_edges = np.zeros((ngrids, 3), dtype=\"float64\")\n    grid_right_edges = np.zeros((ngrids, 3), dtype=\"float64\")\n    grid_dimensions = np.zeros((ngrids, 3), dtype=\"int32\")\n    number_of_particles = np.zeros((ngrids, 1), dtype=\"int64\")\n    parent_ids = np.zeros(ngrids, dtype=\"int64\") - 1\n    sfh = StreamDictFieldHandler()\n    for i, g in enumerate(grid_data):\n        grid_left_edges[i, :] = g.pop(\"left_edge\")\n        grid_right_edges[i, :] = g.pop(\"right_edge\")\n        grid_dimensions[i, :] = g.pop(\"dimensions\")\n        grid_levels[i, :] = g.pop(\"level\")\n        field_units, data, n_particles = process_data(\n            g, grid_dims=tuple(grid_dimensions[i, :])\n        )\n        number_of_particles[i, :] = n_particles\n        sfh[i] = data\n\n    # We now reconstruct our parent ids, so that our particle assignment can\n    # proceed.\n    mask = np.empty(ngrids, dtype=\"int32\")\n    for gi in range(ngrids):\n        get_box_grids_level(\n            grid_left_edges[gi, :],\n            grid_right_edges[gi, :],\n            grid_levels[gi].item() + 1,\n            grid_left_edges,\n            grid_right_edges,\n            grid_levels,\n            mask,\n        )\n        ids = np.where(mask.astype(\"bool\"))\n        for ci in ids:\n            parent_ids[ci] = gi\n\n    # Check if the grid structure is properly aligned (bug #1295)\n    for lvl in range(grid_levels.min() + 1, grid_levels.max() + 1):\n        idx = grid_levels.flatten() == lvl\n        dims = domain_dimensions * refine_by ** (lvl - 1)\n        for iax, ax in enumerate(\"xyz\"):\n            cell_edges = np.linspace(\n                domain_left_edge[iax], domain_right_edge[iax], dims[iax], endpoint=False\n            )\n            if set(grid_left_edges[idx, iax]) - set(cell_edges):\n                raise YTIllDefinedAMR(lvl, ax)\n\n    if length_unit is None:\n        length_unit = \"code_length\"\n    if mass_unit is None:\n        mass_unit = \"code_mass\"\n    if time_unit is None:\n        time_unit = \"code_time\"\n    if velocity_unit is None:\n        velocity_unit = \"code_velocity\"\n    if magnetic_unit is None:\n        magnetic_unit = \"code_magnetic\"\n\n    particle_types = {}\n\n    for grid in sfh.values():\n        particle_types.update(set_particle_types(grid))\n\n    handler = StreamHandler(\n        grid_left_edges,\n        grid_right_edges,\n        grid_dimensions,\n        grid_levels,\n        parent_ids,\n        number_of_particles,\n        np.zeros(ngrids).reshape((ngrids, 1)),\n        sfh,\n        field_units,\n        (length_unit, mass_unit, time_unit, velocity_unit, magnetic_unit),\n        particle_types=particle_types,\n        periodicity=periodicity,\n        parameters=parameters,\n    )\n\n    handler.name = dataset_name  # type: ignore [attr-defined]\n    handler.domain_left_edge = domain_left_edge  # type: ignore [attr-defined]\n    handler.domain_right_edge = domain_right_edge  # type: ignore [attr-defined]\n    handler.refine_by = refine_by  # type: ignore [attr-defined]\n    if np.all(domain_dimensions[1:] == 1):\n        dimensionality = 1\n    elif domain_dimensions[2] == 1:\n        dimensionality = 2\n    else:\n        dimensionality = 3\n    handler.dimensionality = dimensionality  # type: ignore [attr-defined]\n    handler.domain_dimensions = domain_dimensions  # type: ignore [attr-defined]\n    handler.simulation_time = sim_time  # type: ignore [attr-defined]\n    handler.cosmology_simulation = 0  # type: ignore [attr-defined]\n\n    sds = StreamDataset(\n        handler,\n        geometry=geometry,\n        axis_order=axis_order,\n        unit_system=unit_system,\n        default_species_fields=default_species_fields,\n    )\n    return sds\n\n\ndef load_particles(\n    data: Mapping[AnyFieldKey, np.ndarray | tuple[np.ndarray, str]],\n    length_unit=None,\n    bbox=None,\n    sim_time=None,\n    mass_unit=None,\n    time_unit=None,\n    velocity_unit=None,\n    magnetic_unit=None,\n    periodicity=(True, True, True),\n    geometry=\"cartesian\",\n    unit_system=\"cgs\",\n    data_source=None,\n    default_species_fields=None,\n    *,\n    axis_order: AxisOrder | None = None,\n    parameters=None,\n    dataset_name: str = \"ParticleData\",\n):\n    r\"\"\"Load a set of particles into yt as a\n    :class:`~yt.frontends.stream.data_structures.StreamParticleHandler`.\n\n    This will allow a collection of particle data to be loaded directly into\n    yt and analyzed as would any others.  This comes with several caveats:\n\n    * There must be sufficient space in memory to contain all the particle\n      data.\n    * Parallelism will be disappointing or non-existent in most cases.\n    * Fluid fields are not supported.\n\n    Note: in order for the dataset to take advantage of SPH functionality,\n    the following two fields must be provided:\n    * ('io', 'density')\n    * ('io', 'smoothing_length')\n\n    Parameters\n    ----------\n    data : dict\n        This is a dict of numpy arrays or (numpy array, unit name) tuples,\n        where the keys are the field names. Particles positions must be named\n        \"particle_position_x\", \"particle_position_y\", and \"particle_position_z\".\n    length_unit : float\n        Conversion factor from simulation length units to centimeters\n    bbox : array_like (xdim:zdim, LE:RE), optional\n        Size of computational domain in units of the length_unit\n    sim_time : float, optional\n        The simulation time in seconds\n    mass_unit : float\n        Conversion factor from simulation mass units to grams\n    time_unit : float\n        Conversion factor from simulation time units to seconds\n    velocity_unit : float\n        Conversion factor from simulation velocity units to cm/s\n    magnetic_unit : float\n        Conversion factor from simulation magnetic units to gauss\n    periodicity : tuple of booleans\n        Determines whether the data will be treated as periodic along\n        each axis\n    geometry : string (or tuple, deprecated)\n        \"cartesian\", \"cylindrical\", \"polar\", \"spherical\", \"geographic\" or\n        \"spectral_cube\".\n    data_source : YTSelectionContainer, optional\n        If set, parameters like `bbox`, `sim_time`, and code units are derived\n        from it.\n    default_species_fields : string, optional\n        If set, default species fields are created for H and He which also\n        determine the mean molecular weight. Options are \"ionized\" and \"neutral\".\n    axis_order: tuple of three strings, optional\n        Force axis ordering, e.g. (\"z\", \"y\", \"x\") with cartesian geometry\n        Otherwise use geometry-specific default ordering.\n    parameters: dictionary, optional\n        Optional dictionary used to populate the dataset parameters, useful\n        for storing dataset metadata.\n    dataset_name: string, optional\n        Optional string used to assign a name to the dataset. Stream datasets will use\n        this value in place of a filename (in image prefixing, etc.)\n\n    Examples\n    --------\n\n    >>> pos = [np.random.random(128 * 128 * 128) for i in range(3)]\n    >>> data = dict(\n    ...     particle_position_x=pos[0],\n    ...     particle_position_y=pos[1],\n    ...     particle_position_z=pos[2],\n    ... )\n    >>> bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])\n    >>> ds = load_particles(data, 3.08e24, bbox=bbox)\n\n    \"\"\"\n    from yt.frontends.stream.data_structures import (\n        StreamDictFieldHandler,\n        StreamHandler,\n        StreamParticlesDataset,\n    )\n    from yt.frontends.stream.definitions import process_data, set_particle_types\n\n    domain_dimensions = np.ones(3, \"int32\")\n    nprocs = 1\n\n    # Parse bounding box\n    if data_source is not None:\n        le, re = data_source.get_bbox()\n        le = le.to_value(\"code_length\")\n        re = re.to_value(\"code_length\")\n        bbox = list(zip(le, re, strict=True))\n    if bbox is None:\n        bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]], \"float64\")\n    else:\n        bbox = np.array(bbox)\n    domain_left_edge = np.array(bbox[:, 0], \"float64\")\n    domain_right_edge = np.array(bbox[:, 1], \"float64\")\n    grid_levels = np.zeros(nprocs, dtype=\"int32\").reshape((nprocs, 1))\n\n    # Parse simulation time\n    if data_source is not None:\n        sim_time = data_source.ds.current_time\n    if sim_time is None:\n        sim_time = 0.0\n    else:\n        sim_time = float(sim_time)\n\n    # Parse units\n    def parse_unit(unit, dimension):\n        if unit is None:\n            unit = \"code_\" + dimension\n            if data_source is not None:\n                unit = getattr(data_source.ds, dimension + \"_unit\", unit)\n        return unit\n\n    length_unit = parse_unit(length_unit, \"length\")\n    mass_unit = parse_unit(mass_unit, \"mass\")\n    time_unit = parse_unit(time_unit, \"time\")\n    velocity_unit = parse_unit(velocity_unit, \"velocity\")\n    magnetic_unit = parse_unit(magnetic_unit, \"magnetic\")\n\n    # Preprocess data\n    field_units, data, _ = process_data(data)\n    sfh = StreamDictFieldHandler()\n\n    pdata: dict[AnyFieldKey, np.ndarray | tuple[np.ndarray, str]] = {}\n    for key in data.keys():\n        field: FieldKey\n        if not isinstance(key, tuple):\n            field = (\"io\", key)\n            mylog.debug(\"Reassigning '%s' to '%s'\", key, field)\n        else:\n            field = key\n        pdata[field] = data[key]\n        sfh._additional_fields += (field,)\n    data = pdata  # Drop reference count\n    particle_types = set_particle_types(data)\n    sfh.update({\"stream_file\": data})\n    grid_left_edges = domain_left_edge\n    grid_right_edges = domain_right_edge\n    grid_dimensions = domain_dimensions.reshape(nprocs, 3).astype(\"int32\")\n\n    # I'm not sure we need any of this.\n    handler = StreamHandler(\n        grid_left_edges,\n        grid_right_edges,\n        grid_dimensions,\n        grid_levels,\n        -np.ones(nprocs, dtype=\"int64\"),\n        np.zeros(nprocs, dtype=\"int64\").reshape(nprocs, 1),  # Temporary\n        np.zeros(nprocs).reshape((nprocs, 1)),\n        sfh,\n        field_units,\n        (length_unit, mass_unit, time_unit, velocity_unit, magnetic_unit),\n        particle_types=particle_types,\n        periodicity=periodicity,\n        parameters=parameters,\n    )\n\n    handler.name = dataset_name  # type: ignore [attr-defined]\n    handler.domain_left_edge = domain_left_edge  # type: ignore [attr-defined]\n    handler.domain_right_edge = domain_right_edge  # type: ignore [attr-defined]\n    handler.refine_by = 2  # type: ignore [attr-defined]\n    handler.dimensionality = 3  # type: ignore [attr-defined]\n    handler.domain_dimensions = domain_dimensions  # type: ignore [attr-defined]\n    handler.simulation_time = sim_time  # type: ignore [attr-defined]\n    handler.cosmology_simulation = 0  # type: ignore [attr-defined]\n\n    sds = StreamParticlesDataset(\n        handler,\n        geometry=geometry,\n        unit_system=unit_system,\n        default_species_fields=default_species_fields,\n        axis_order=axis_order,\n    )\n\n    return sds\n\n\ndef load_hexahedral_mesh(\n    data,\n    connectivity,\n    coordinates,\n    length_unit=None,\n    bbox=None,\n    sim_time=0.0,\n    mass_unit=None,\n    time_unit=None,\n    velocity_unit=None,\n    magnetic_unit=None,\n    periodicity=(True, True, True),\n    geometry=\"cartesian\",\n    unit_system=\"cgs\",\n    *,\n    axis_order: AxisOrder | None = None,\n    parameters=None,\n    dataset_name: str = \"HexahedralMeshData\",\n):\n    r\"\"\"Load a hexahedral mesh of data into yt as a\n    :class:`~yt.frontends.stream.data_structures.StreamHandler`.\n\n    This should allow a semistructured grid of data to be loaded directly into\n    yt and analyzed as would any others.  This comes with several caveats:\n\n    * Units will be incorrect unless the data has already been converted to\n      cgs.\n    * Some functions may behave oddly, and parallelism will be\n      disappointing or non-existent in most cases.\n    * Particles may be difficult to integrate.\n\n    Particle fields are detected as one-dimensional fields. The number of particles\n    is set by the \"number_of_particles\" key in data.\n\n    Parameters\n    ----------\n    data : dict\n        This is a dict of numpy arrays, where the keys are the field names.\n        There must only be one. Note that the data in the numpy arrays should\n        define the cell-averaged value for of the quantity in in the hexahedral\n        cell.\n    connectivity : array_like\n        This should be of size (N,8) where N is the number of zones.\n    coordinates : array_like\n        This should be of size (M,3) where M is the number of vertices\n        indicated in the connectivity matrix.\n    bbox : array_like (xdim:zdim, LE:RE), optional\n        Size of computational domain in units of the length unit.\n    sim_time : float, optional\n        The simulation time in seconds\n    mass_unit : string\n        Unit to use for masses.  Defaults to unitless.\n    time_unit : string\n        Unit to use for times.  Defaults to unitless.\n    velocity_unit : string\n        Unit to use for velocities.  Defaults to unitless.\n    magnetic_unit : string\n        Unit to use for magnetic fields. Defaults to unitless.\n    periodicity : tuple of booleans\n        Determines whether the data will be treated as periodic along\n        each axis\n    geometry : string (or tuple, deprecated)\n        \"cartesian\", \"cylindrical\", \"polar\", \"spherical\", \"geographic\" or\n        \"spectral_cube\".\n        [DEPRECATED]: Optionally, a tuple can be provided to specify the axis ordering.\n        For instance, to specify that the axis ordering should\n        be z, x, y, this would be: (\"cartesian\", (\"z\", \"x\", \"y\")).  The same\n        can be done for other coordinates, for instance:\n        (\"spherical\", (\"theta\", \"phi\", \"r\")).\n    axis_order: tuple of three strings, optional\n        Force axis ordering, e.g. (\"z\", \"y\", \"x\") with cartesian geometry\n        Otherwise use geometry-specific default ordering.\n    parameters: dictionary, optional\n        Optional dictionary used to populate the dataset parameters, useful\n        for storing dataset metadata.\n    dataset_name: string, optional\n        Optional string used to assign a name to the dataset. Stream datasets will use\n        this value in place of a filename (in image prefixing, etc.)\n    \"\"\"\n    from yt.frontends.stream.data_structures import (\n        StreamDictFieldHandler,\n        StreamHandler,\n        StreamHexahedralDataset,\n    )\n    from yt.frontends.stream.definitions import process_data, set_particle_types\n\n    geometry, axis_order = _sanitize_axis_order_args(geometry, axis_order)\n    domain_dimensions = np.ones(3, \"int32\") * 2\n    nprocs = 1\n    if bbox is None:\n        bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]], \"float64\")\n    domain_left_edge = np.array(bbox[:, 0], \"float64\")\n    domain_right_edge = np.array(bbox[:, 1], \"float64\")\n    grid_levels = np.zeros(nprocs, dtype=\"int32\").reshape((nprocs, 1))\n\n    field_units, data, _ = process_data(data)\n    sfh = StreamDictFieldHandler()\n\n    particle_types = set_particle_types(data)\n\n    sfh.update({\"connectivity\": connectivity, \"coordinates\": coordinates, 0: data})\n    # Simple check for axis length correctness\n    if len(data) > 0:\n        fn = sorted(data)[0]\n        array_values = data[fn]\n        if array_values.size != connectivity.shape[0]:\n            mylog.error(\n                \"Dimensions of array must be one fewer than the coordinate set.\"\n            )\n            raise RuntimeError\n    grid_left_edges = domain_left_edge\n    grid_right_edges = domain_right_edge\n    grid_dimensions = domain_dimensions.reshape(nprocs, 3).astype(\"int32\")\n\n    if length_unit is None:\n        length_unit = \"code_length\"\n    if mass_unit is None:\n        mass_unit = \"code_mass\"\n    if time_unit is None:\n        time_unit = \"code_time\"\n    if velocity_unit is None:\n        velocity_unit = \"code_velocity\"\n    if magnetic_unit is None:\n        magnetic_unit = \"code_magnetic\"\n\n    # I'm not sure we need any of this.\n    handler = StreamHandler(\n        grid_left_edges,\n        grid_right_edges,\n        grid_dimensions,\n        grid_levels,\n        -np.ones(nprocs, dtype=\"int64\"),\n        np.zeros(nprocs, dtype=\"int64\").reshape(nprocs, 1),  # Temporary\n        np.zeros(nprocs).reshape((nprocs, 1)),\n        sfh,\n        field_units,\n        (length_unit, mass_unit, time_unit, velocity_unit, magnetic_unit),\n        particle_types=particle_types,\n        periodicity=periodicity,\n        parameters=parameters,\n    )\n\n    handler.name = dataset_name  # type: ignore [attr-defined]\n    handler.domain_left_edge = domain_left_edge  # type: ignore [attr-defined]\n    handler.domain_right_edge = domain_right_edge  # type: ignore [attr-defined]\n    handler.refine_by = 2  # type: ignore [attr-defined]\n    handler.dimensionality = 3  # type: ignore [attr-defined]\n    handler.domain_dimensions = domain_dimensions  # type: ignore [attr-defined]\n    handler.simulation_time = sim_time  # type: ignore [attr-defined]\n    handler.cosmology_simulation = 0  # type: ignore [attr-defined]\n\n    sds = StreamHexahedralDataset(\n        handler, geometry=geometry, axis_order=axis_order, unit_system=unit_system\n    )\n\n    return sds\n\n\ndef load_octree(\n    octree_mask,\n    data,\n    bbox=None,\n    sim_time=0.0,\n    length_unit=None,\n    mass_unit=None,\n    time_unit=None,\n    velocity_unit=None,\n    magnetic_unit=None,\n    periodicity=(True, True, True),\n    over_refine_factor=None,\n    num_zones=2,\n    partial_coverage=1,\n    unit_system=\"cgs\",\n    default_species_fields=None,\n    *,\n    parameters=None,\n    domain_dimensions=None,\n    dataset_name: str = \"OctreeData\",\n):\n    r\"\"\"Load an octree mask into yt.\n\n    Octrees can be saved out by calling save_octree on an OctreeContainer.\n    This enables them to be loaded back in.\n\n    This will initialize an Octree of data.  Note that fluid fields will not\n    work yet, or possibly ever.\n\n    Parameters\n    ----------\n    octree_mask : np.ndarray[uint8_t]\n        This is a depth-first refinement mask for an Octree.  It should be\n        of size n_octs * 8 (but see note about the root oct below), where\n        each item is 1 for an oct-cell being refined and 0 for it not being\n        refined.  For num_zones != 2, the children count will\n        still be 8, so there will still be n_octs * 8 entries. Note that if\n        the root oct is not refined, there will be only one entry\n        for the root, so the size of the mask will be (n_octs - 1)*8 + 1.\n    data : dict\n        A dictionary of 1D arrays.  Note that these must of the size of the\n        number of \"False\" values in the ``octree_mask``.\n    bbox : array_like (xdim:zdim, LE:RE), optional\n        Size of computational domain in units of length\n    sim_time : float, optional\n        The simulation time in seconds\n    length_unit : string\n        Unit to use for lengths.  Defaults to unitless.\n    mass_unit : string\n        Unit to use for masses.  Defaults to unitless.\n    time_unit : string\n        Unit to use for times.  Defaults to unitless.\n    velocity_unit : string\n        Unit to use for velocities.  Defaults to unitless.\n    magnetic_unit : string\n        Unit to use for magnetic fields. Defaults to unitless.\n    periodicity : tuple of booleans\n        Determines whether the data will be treated as periodic along\n        each axis\n    partial_coverage : boolean\n        Whether or not an oct can be refined cell-by-cell, or whether all\n        8 get refined.\n    default_species_fields : string, optional\n        If set, default species fields are created for H and He which also\n        determine the mean molecular weight. Options are \"ionized\" and \"neutral\".\n    num_zones : int\n        The number of zones along each dimension in an oct\n    parameters: dictionary, optional\n        Optional dictionary used to populate the dataset parameters, useful\n        for storing dataset metadata.\n    domain_dimensions : 3 elements array-like, optional\n        This is the domain dimensions of the root *mesh*, which can be used to\n        specify (indirectly) the number of root oct nodes.\n    dataset_name : string, optional\n        Optional string used to assign a name to the dataset. Stream datasets will use\n        this value in place of a filename (in image prefixing, etc.)\n\n    Example\n    -------\n\n    >>> import numpy as np\n    >>> oct_mask = np.zeros(33) # 5 refined values gives 7 * 4 + 5 octs to mask\n    ... oct_mask[[0,  5,  7, 16]] = 8\n    >>> octree_mask = np.array(oct_mask, dtype=np.uint8)\n    >>> quantities = {}\n    >>> quantities[\"gas\", \"density\"] = np.random.random((29, 1)) # num of false's\n    >>> # Quantities can also contain parameter-less callbacks\n    >>> quantities[\"gas\", \"temperature\"] = lambda: np.random.random((29, 1)) * 1e4\n    >>> bbox = np.array([[-10.0, 10.0], [-10.0, 10.0], [-10.0, 10.0]])\n\n    >>> ds = load_octree(\n    ...     octree_mask=octree_mask,\n    ...     data=quantities,\n    ...     bbox=bbox,\n    ...     num_zones=1,\n    ...     partial_coverage=0,\n    ... )\n\n    \"\"\"\n    from yt.frontends.stream.data_structures import (\n        StreamDictFieldHandler,\n        StreamHandler,\n        StreamOctreeDataset,\n    )\n    from yt.frontends.stream.definitions import process_data, set_particle_types\n\n    if not isinstance(octree_mask, np.ndarray) or octree_mask.dtype != np.uint8:\n        raise TypeError(\"octree_mask should be a Numpy array with type uint8\")\n\n    nz = num_zones\n    # for compatibility\n    if over_refine_factor is not None:\n        nz = 1 << over_refine_factor\n    if domain_dimensions is None:\n        # We assume that if it isn't specified, it defaults to the number of\n        # zones (i.e., a single root oct.)\n        domain_dimensions = np.array([nz, nz, nz])\n    else:\n        domain_dimensions = np.array(domain_dimensions)\n    nprocs = 1\n    if bbox is None:\n        bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]], \"float64\")\n    domain_left_edge = np.array(bbox[:, 0], \"float64\")\n    domain_right_edge = np.array(bbox[:, 1], \"float64\")\n    grid_levels = np.zeros(nprocs, dtype=\"int32\").reshape((nprocs, 1))\n\n    field_units, data, _ = process_data(data)\n    sfh = StreamDictFieldHandler()\n\n    particle_types = set_particle_types(data)\n\n    sfh.update({0: data})\n    grid_left_edges = domain_left_edge\n    grid_right_edges = domain_right_edge\n    grid_dimensions = domain_dimensions.reshape(nprocs, 3).astype(\"int32\")\n\n    if length_unit is None:\n        length_unit = \"code_length\"\n    if mass_unit is None:\n        mass_unit = \"code_mass\"\n    if time_unit is None:\n        time_unit = \"code_time\"\n    if velocity_unit is None:\n        velocity_unit = \"code_velocity\"\n    if magnetic_unit is None:\n        magnetic_unit = \"code_magnetic\"\n\n    # I'm not sure we need any of this.\n    handler = StreamHandler(\n        grid_left_edges,\n        grid_right_edges,\n        grid_dimensions,\n        grid_levels,\n        -np.ones(nprocs, dtype=\"int64\"),\n        np.zeros(nprocs, dtype=\"int64\").reshape(nprocs, 1),  # Temporary\n        np.zeros(nprocs).reshape((nprocs, 1)),\n        sfh,\n        field_units,\n        (length_unit, mass_unit, time_unit, velocity_unit, magnetic_unit),\n        particle_types=particle_types,\n        periodicity=periodicity,\n        parameters=parameters,\n    )\n\n    handler.name = dataset_name  # type: ignore [attr-defined]\n    handler.domain_left_edge = domain_left_edge  # type: ignore [attr-defined]\n    handler.domain_right_edge = domain_right_edge  # type: ignore [attr-defined]\n    handler.refine_by = 2  # type: ignore [attr-defined]\n    handler.dimensionality = 3  # type: ignore [attr-defined]\n    handler.domain_dimensions = domain_dimensions  # type: ignore [attr-defined]\n    handler.simulation_time = sim_time  # type: ignore [attr-defined]\n    handler.cosmology_simulation = 0  # type: ignore [attr-defined]\n\n    sds = StreamOctreeDataset(\n        handler, unit_system=unit_system, default_species_fields=default_species_fields\n    )\n    sds.octree_mask = octree_mask  # type: ignore [attr-defined]\n    sds.partial_coverage = partial_coverage  # type: ignore [attr-defined]\n    sds.num_zones = num_zones  # type: ignore [attr-defined]\n\n    return sds\n\n\ndef load_unstructured_mesh(\n    connectivity,\n    coordinates,\n    node_data=None,\n    elem_data=None,\n    length_unit=None,\n    bbox=None,\n    sim_time=0.0,\n    mass_unit=None,\n    time_unit=None,\n    velocity_unit=None,\n    magnetic_unit=None,\n    periodicity=(False, False, False),\n    geometry=\"cartesian\",\n    unit_system=\"cgs\",\n    *,\n    axis_order: AxisOrder | None = None,\n    parameters=None,\n    dataset_name: str = \"UnstructuredMeshData\",\n):\n    r\"\"\"Load an unstructured mesh of data into yt as a\n    :class:`~yt.frontends.stream.data_structures.StreamHandler`.\n\n    This should allow an unstructured mesh data to be loaded directly into\n    yt and analyzed as would any others.  Not all functionality for\n    visualization will be present, and some analysis functions may not yet have\n    been implemented.\n\n    Particle fields are detected as one-dimensional fields. The number of\n    particles is set by the \"number_of_particles\" key in data.\n\n    In the parameter descriptions below, a \"vertex\" is a 3D point in space, an\n    \"element\" is a single polyhedron whose location is defined by a set of\n    vertices, and a \"mesh\" is a set of polyhedral elements, each with the same\n    number of vertices.\n\n    Parameters\n    ----------\n\n    connectivity : list of array_like or array_like\n        This should either be a single 2D array or list of 2D arrays.  If this\n        is a list, each element in the list corresponds to the connectivity\n        information for a distinct mesh. Each array can have different\n        connectivity length and should be of shape (N,M) where N is the number\n        of elements and M is the number of vertices per element.\n    coordinates : array_like\n        The 3D coordinates of mesh vertices. This should be of size (L, D) where\n        L is the number of vertices and D is the number of coordinates per vertex\n        (the spatial dimensions of the dataset). Currently this must be either 2 or 3.\n        When loading more than one mesh, the data for each mesh should be concatenated\n        into a single coordinates array.\n    node_data : dict or list of dicts\n        For a single mesh, a dict mapping field names to 2D numpy arrays,\n        representing data defined at element vertices. For multiple meshes,\n        this must be a list of dicts.  Note that these are not the values as a\n        function of the coordinates, but of the connectivity.  Their shape\n        should be the same as the connectivity.  This means that if the data is\n        in the shape of the coordinates, you may need to reshape them using the\n        `connectivity` array as an index.\n    elem_data : dict or list of dicts\n        For a single mesh, a dict mapping field names to 1D numpy arrays, where\n        each array has a length equal to the number of elements. The data\n        must be defined at the center of each mesh element and there must be\n        only one data value for each element. For multiple meshes, this must be\n        a list of dicts, with one dict for each mesh.\n    bbox : array_like (xdim:zdim, LE:RE), optional\n        Size of computational domain in units of the length unit.\n    sim_time : float, optional\n        The simulation time in seconds\n    length_unit : string\n        Unit to use for length.  Defaults to unitless.\n    mass_unit : string\n        Unit to use for masses.  Defaults to unitless.\n    time_unit : string\n        Unit to use for times.  Defaults to unitless.\n    velocity_unit : string\n        Unit to use for velocities.  Defaults to unitless.\n    magnetic_unit : string\n        Unit to use for magnetic fields. Defaults to unitless.\n    periodicity : tuple of booleans\n        Determines whether the data will be treated as periodic along\n        each axis\n    geometry : string (or tuple, deprecated)\n        \"cartesian\", \"cylindrical\", \"polar\", \"spherical\", \"geographic\" or\n        \"spectral_cube\".\n        [DEPRECATED]: Optionally, a tuple can be provided to specify the axis ordering.\n        For instance, to specify that the axis ordering should\n        be z, x, y, this would be: (\"cartesian\", (\"z\", \"x\", \"y\")).  The same\n        can be done for other coordinates, for instance:\n        (\"spherical\", (\"theta\", \"phi\", \"r\")).\n    axis_order: tuple of three strings, optional\n        Force axis ordering, e.g. (\"z\", \"y\", \"x\") with cartesian geometry\n        Otherwise use geometry-specific default ordering.\n    parameters: dictionary, optional\n        Optional dictionary used to populate the dataset parameters, useful\n        for storing dataset metadata.\n    dataset_name: string, optional\n        Optional string used to assign a name to the dataset. Stream datasets will use\n        this value in place of a filename (in image prefixing, etc.)\n\n    Examples\n    --------\n\n    Load a simple mesh consisting of two tets.\n\n      >>> # Coordinates for vertices of two tetrahedra\n      >>> coordinates = np.array(\n      ...     [\n      ...         [0.0, 0.0, 0.5],\n      ...         [0.0, 1.0, 0.5],\n      ...         [0.5, 1, 0.5],\n      ...         [0.5, 0.5, 0.0],\n      ...         [0.5, 0.5, 1.0],\n      ...     ]\n      ... )\n      >>> # The indices in the coordinates array of mesh vertices.\n      >>> # This mesh has two elements.\n      >>> connectivity = np.array([[0, 1, 2, 4], [0, 1, 2, 3]])\n\n      >>> # Field data defined at the centers of the two mesh elements.\n      >>> elem_data = {(\"connect1\", \"elem_field\"): np.array([1, 2])}\n\n      >>> # Field data defined at node vertices\n      >>> node_data = {\n      ...     (\"connect1\", \"node_field\"): np.array(\n      ...         [[0.0, 1.0, 2.0, 4.0], [0.0, 1.0, 2.0, 3.0]]\n      ...     )\n      ... }\n\n      >>> ds = load_unstructured_mesh(\n      ...     connectivity, coordinates, elem_data=elem_data, node_data=node_data\n      ... )\n    \"\"\"\n    from yt.frontends.exodus_ii.util import get_num_pseudo_dims\n    from yt.frontends.stream.data_structures import (\n        StreamDictFieldHandler,\n        StreamHandler,\n        StreamUnstructuredMeshDataset,\n    )\n    from yt.frontends.stream.definitions import process_data, set_particle_types\n\n    geometry, axis_order = _sanitize_axis_order_args(geometry, axis_order)\n    dimensionality = coordinates.shape[1]\n    domain_dimensions = np.ones(3, \"int32\") * 2\n    nprocs = 1\n\n    if elem_data is None and node_data is None:\n        raise RuntimeError(\"No data supplied in load_unstructured_mesh.\")\n\n    connectivity = list(always_iterable(connectivity, base_type=np.ndarray))\n    num_meshes = max(1, len(connectivity))\n\n    elem_data = list(always_iterable(elem_data, base_type=dict)) or [{}] * num_meshes\n    node_data = list(always_iterable(node_data, base_type=dict)) or [{}] * num_meshes\n\n    data = [{} for i in range(num_meshes)]  # type: ignore [var-annotated]\n    for elem_dict, data_dict in zip(elem_data, data, strict=True):\n        for field, values in elem_dict.items():\n            data_dict[field] = values\n    for node_dict, data_dict in zip(node_data, data, strict=True):\n        for field, values in node_dict.items():\n            data_dict[field] = values\n\n    if bbox is None:\n        bbox = [\n            [\n                coordinates[:, i].min() - 0.1 * abs(coordinates[:, i].min()),\n                coordinates[:, i].max() + 0.1 * abs(coordinates[:, i].max()),\n            ]\n            for i in range(dimensionality)\n        ]\n\n    if dimensionality < 3:\n        bbox.append([0.0, 1.0])\n    if dimensionality < 2:\n        bbox.append([0.0, 1.0])\n\n    # handle pseudo-dims here\n    num_pseudo_dims = get_num_pseudo_dims(coordinates)\n    dimensionality -= num_pseudo_dims\n    for i in range(dimensionality, 3):\n        bbox[i][0] = 0.0\n        bbox[i][1] = 1.0\n\n    bbox = np.array(bbox, dtype=np.float64)\n    domain_left_edge = np.array(bbox[:, 0], \"float64\")\n    domain_right_edge = np.array(bbox[:, 1], \"float64\")\n    grid_levels = np.zeros(nprocs, dtype=\"int32\").reshape((nprocs, 1))\n\n    field_units = {}\n    particle_types = {}\n    sfh = StreamDictFieldHandler()\n\n    sfh.update({\"connectivity\": connectivity, \"coordinates\": coordinates})\n    for i, d in enumerate(data):\n        _f_unit, _data, _ = process_data(d)\n        field_units.update(_f_unit)\n        sfh[i] = _data\n        particle_types.update(set_particle_types(_data))\n\n    grid_left_edges = domain_left_edge\n    grid_right_edges = domain_right_edge\n    grid_dimensions = domain_dimensions.reshape(nprocs, 3).astype(\"int32\")\n\n    if length_unit is None:\n        length_unit = \"code_length\"\n    if mass_unit is None:\n        mass_unit = \"code_mass\"\n    if time_unit is None:\n        time_unit = \"code_time\"\n    if velocity_unit is None:\n        velocity_unit = \"code_velocity\"\n    if magnetic_unit is None:\n        magnetic_unit = \"code_magnetic\"\n\n    # I'm not sure we need any of this.\n    handler = StreamHandler(\n        grid_left_edges,\n        grid_right_edges,\n        grid_dimensions,\n        grid_levels,\n        -np.ones(nprocs, dtype=\"int64\"),\n        np.zeros(nprocs, dtype=\"int64\").reshape(nprocs, 1),  # Temporary\n        np.zeros(nprocs).reshape((nprocs, 1)),\n        sfh,\n        field_units,\n        (length_unit, mass_unit, time_unit, velocity_unit, magnetic_unit),\n        particle_types=particle_types,\n        periodicity=periodicity,\n        parameters=parameters,\n    )\n\n    handler.name = dataset_name  # type: ignore [attr-defined]\n    handler.domain_left_edge = domain_left_edge  # type: ignore [attr-defined]\n    handler.domain_right_edge = domain_right_edge  # type: ignore [attr-defined]\n    handler.refine_by = 2  # type: ignore [attr-defined]\n    handler.dimensionality = dimensionality  # type: ignore [attr-defined]\n    handler.domain_dimensions = domain_dimensions  # type: ignore [attr-defined]\n    handler.simulation_time = sim_time  # type: ignore [attr-defined]\n    handler.cosmology_simulation = 0  # type: ignore [attr-defined]\n\n    sds = StreamUnstructuredMeshDataset(\n        handler, geometry=geometry, axis_order=axis_order, unit_system=unit_system\n    )\n\n    fluid_types = [\"all\"]\n    for i in range(1, num_meshes + 1):\n        fluid_types += [f\"connect{i}\"]\n    sds.fluid_types = tuple(fluid_types)\n\n    def flatten(l):\n        return [item for sublist in l for item in sublist]\n\n    sds._node_fields = flatten([[f[1] for f in m] for m in node_data if m])  # type: ignore [attr-defined]\n    sds._elem_fields = flatten([[f[1] for f in m] for m in elem_data if m])  # type: ignore [attr-defined]\n    sds.default_field = [f for f in sds.field_list if f[0] == \"connect1\"][-1]\n    sds.default_fluid_type = sds.default_field[0]\n    return sds\n\n\n# --- Loader for yt sample datasets ---\n@parallel_root_only_then_broadcast\ndef _get_sample_data(\n    fn: str | None = None, *, progressbar: bool = True, timeout=None, **kwargs\n):\n    # this isolates all the filename management and downloading so that it\n    # can be restricted to a single process if running in parallel. Returns\n    # the loadable_path as well as the kwargs dictionary, which is modified\n    # by this function (note that the kwargs are returned explicitly rather than\n    # relying on in-place modification so that the updated kwargs can be\n    # broadcast to other processes during parallel execution).\n    import tarfile\n\n    from yt.sample_data.api import (\n        _download_sample_data_file,\n        _get_test_data_dir_path,\n        get_data_registry_table,\n        get_download_cache_dir,\n    )\n\n    pooch_logger = pooch.utils.get_logger()\n\n    # normalize path for platform portability\n    # for consistency with yt.load, we also convert to str explicitly,\n    # which gives us support Path objects for free\n    fn = str(fn).replace(\"/\", os.path.sep)\n\n    topdir, _, specific_file = fn.partition(os.path.sep)\n\n    registry_table = get_data_registry_table()\n\n    known_names: list[str] = registry_table.dropna()[\"filename\"].to_list()\n    if topdir not in known_names:\n        msg = f\"'{topdir}' is not an available dataset.\"\n        lexical_distances: list[tuple[str, int]] = [\n            (name, levenshtein_distance(name, topdir)) for name in known_names\n        ]\n        suggestions: list[str] = [name for name, dist in lexical_distances if dist < 4]\n        if len(suggestions) == 1:\n            msg += f\" Did you mean '{suggestions[0]}' ?\"\n        elif suggestions:\n            msg += \" Did you mean to type any of the following ?\\n\\n    \"\n            msg += \"\\n    \".join(f\"'{_}'\" for _ in suggestions)\n        raise ValueError(msg)\n\n    # PR 3089\n    # note: in the future the registry table should be reindexed\n    # so that the following line can be replaced with\n    #\n    # specs = registry_table.loc[fn]\n    #\n    # however we don't want to do it right now because the \"filename\" column is\n    # currently incomplete\n    specs = registry_table.query(f\"`filename` == '{topdir}'\").iloc[0]\n\n    load_name = specific_file or specs[\"load_name\"] or \"\"\n\n    if not isinstance(specs[\"load_kwargs\"], dict):\n        raise ValueError(\n            \"The requested dataset seems to be improperly registered.\\n\"\n            \"Tip: the entry in yt/sample_data_registry.json may be inconsistent with \"\n            \"https://github.com/yt-project/website/blob/master/data/datafiles.json\\n\"\n            \"Please report this to https://github.com/yt-project/yt/issues/new\"\n        )\n\n    load_kwargs = {**specs[\"load_kwargs\"], **kwargs}\n    save_dir = _get_test_data_dir_path()\n\n    data_path = save_dir.joinpath(fn)\n    if save_dir.joinpath(topdir).exists():\n        # if the data is already available locally, `load_sample`\n        # only acts as a thin wrapper around `load`\n        if load_name and os.sep not in fn:\n            data_path = data_path.joinpath(load_name)\n        mylog.info(\"Sample dataset found in '%s'\", data_path)\n        if timeout is not None:\n            mylog.info(\"Ignoring the `timeout` keyword argument received.\")\n        return data_path, load_kwargs\n\n    mylog.info(\"'%s' is not available locally. Looking up online.\", fn)\n\n    # effectively silence the pooch's logger and create our own log instead\n    pooch_logger.setLevel(100)\n    mylog.info(\"Downloading from %s\", specs[\"url\"])\n\n    # downloading via a pooch.Pooch instance behind the scenes\n    filename = urlsplit(specs[\"url\"]).path.split(\"/\")[-1]\n\n    tmp_file = _download_sample_data_file(\n        filename, progressbar=progressbar, timeout=timeout\n    )\n\n    # pooch has functionalities to unpack downloaded archive files,\n    # but it needs to be told in advance that we are downloading a tarball.\n    # Since that information is not necessarily trivial to guess from the filename,\n    # we rely on the standard library to perform a conditional unpacking instead.\n    if tarfile.is_tarfile(tmp_file):\n        mylog.info(\"Untaring downloaded file to '%s'\", save_dir)\n        with tarfile.open(tmp_file) as fh:\n\n            def is_within_directory(directory, target):\n                abs_directory = os.path.abspath(directory)\n                abs_target = os.path.abspath(target)\n\n                prefix = os.path.commonprefix([abs_directory, abs_target])\n\n                return prefix == abs_directory\n\n            def safe_extract(tar, path=\".\", members=None, *, numeric_owner=False):\n                for member in tar.getmembers():\n                    member_path = os.path.join(path, member.name)\n                    if not is_within_directory(path, member_path):\n                        raise Exception(\"Attempted Path Traversal in Tar File\")\n\n                if sys.version_info >= (3, 12):\n                    # the filter argument is new in Python 3.12, but not specifying it\n                    # explicitly raises a deprecation warning on 3.12 and 3.13\n                    extractall_kwargs = {\"filter\": \"data\"}\n                else:\n                    extractall_kwargs = {}\n                tar.extractall(\n                    path, members, numeric_owner=numeric_owner, **extractall_kwargs\n                )\n\n            safe_extract(fh, save_dir)\n        os.remove(tmp_file)\n    else:\n        os.replace(tmp_file, os.path.join(save_dir, fn))\n\n    loadable_path = Path.joinpath(save_dir, fn)\n    if load_name not in str(loadable_path):\n        loadable_path = loadable_path.joinpath(load_name, specific_file)\n\n    try:\n        # clean cache dir\n        get_download_cache_dir().rmdir()\n    except OSError:\n        # cache dir isn't empty\n        pass\n\n    return loadable_path, load_kwargs\n\n\ndef load_sample(\n    fn: str | None = None, *, progressbar: bool = True, timeout=None, **kwargs\n):\n    r\"\"\"\n    Load sample data with yt.\n\n    This is a simple wrapper around :func:`~yt.loaders.load` to include fetching\n    data with pooch from remote source.\n\n    The data registry table can be retrieved and visualized using\n    :func:`~yt.sample_data.api.get_data_registry_table`.\n    The `filename` column contains usable keys that can be passed\n    as the first positional argument to load_sample.\n    Some data samples contain series of datasets. It may be required to\n    supply the relative path to a specific dataset.\n\n    Parameters\n    ----------\n\n    fn: str\n        The `filename` of the dataset to load, as defined in the data registry\n        table.\n\n    progressbar: bool\n        display a progress bar (tqdm).\n\n    timeout: float or int (optional)\n        Maximal waiting time, in seconds, after which download is aborted.\n        `None` means \"no limit\". This parameter is directly passed to down to\n        requests.get via pooch.HTTPDownloader\n\n    Notes\n    -----\n\n    - This function is experimental as of yt 4.0.0, do not rely on its exact behaviour.\n    - Any additional keyword argument is passed down to :func:`~yt.loaders.load`.\n    - In case of collision with predefined keyword arguments as set in\n      the data registry, the ones passed to this function take priority.\n    - Datasets with slashes '/' in their names can safely be used even on Windows.\n      On the contrary, paths using backslashes '\\' won't work outside of Windows, so\n      it is recommended to favour the UNIX convention ('/') in scripts that are meant\n      to be cross-platform.\n    - This function requires pandas and pooch.\n    - Corresponding sample data live at https://yt-project.org/data\n\n    \"\"\"\n\n    if fn is None:\n        print(\n            \"One can see which sample datasets are available at: https://yt-project.org/data\\n\"\n            \"or alternatively by running: yt.sample_data.api.get_data_registry_table()\",\n            file=sys.stderr,\n        )\n        return None\n\n    loadable_path, load_kwargs = _get_sample_data(\n        fn, progressbar=progressbar, timeout=timeout, **kwargs\n    )\n    return load(loadable_path, **load_kwargs)\n\n\ndef _mount_helper(\n    archive: str, mountPoint: str, ratarmount_kwa: dict, conn: \"Connection\"\n):\n    try:\n        fuseOperationsObject = ratarmount.TarMount(\n            pathToMount=archive,\n            mountPoint=mountPoint,\n            lazyMounting=True,\n            **ratarmount_kwa,\n        )\n        fuseOperationsObject.use_ns = True\n        conn.send(True)\n    except Exception:\n        conn.send(False)\n        raise\n\n    ratarmount.fuse.FUSE(\n        operations=fuseOperationsObject,\n        mountpoint=mountPoint,\n        foreground=True,\n        nothreads=True,\n    )\n\n\n# --- Loader for tar-based datasets ---\ndef load_archive(\n    fn: str | Path,\n    path: str,\n    ratarmount_kwa: dict | None = None,\n    mount_timeout: float = 1.0,\n    *args,\n    **kwargs,\n) -> Dataset:\n    r\"\"\"\n    Load archived data with yt.\n\n    This is a wrapper around :func:`~yt.loaders.load` to include mounting\n    and unmounting the archive as a read-only filesystem and load it.\n\n    Parameters\n    ----------\n\n    fn: str\n        The `filename` of the archive containing the dataset.\n\n    path: str\n        The path to the dataset in the archive.\n\n    ratarmount_kwa: dict, optional\n        Optional parameters to pass to ratarmount to mount the archive.\n\n    mount_timeout: float, optional\n        The timeout to wait for ratarmount to mount the archive. Default is 1s.\n\n    Notes\n    -----\n\n    - The function is experimental and may work or not depending on your setup.\n    - Any additional keyword argument is passed down to :func:`~yt.loaders.load`.\n    - This function requires ratarmount to be installed.\n    - This function does not work on Windows system.\n    \"\"\"\n    import tarfile\n    from multiprocessing import Pipe, Process\n\n    warnings.warn(\n        \"The 'load_archive' function is still experimental and may be unstable.\",\n        stacklevel=2,\n    )\n\n    fn = os.path.expanduser(fn)\n\n    # This will raise FileNotFoundError if the path isn't matched\n    # either in the current dir or yt.config.ytcfg['data_dir_directory']\n    if not fn.startswith(\"http\"):\n        fn = str(lookup_on_disk_data(fn))\n\n    if ratarmount_kwa is None:\n        ratarmount_kwa = {}\n\n    try:\n        tarfile.open(fn)\n    except tarfile.ReadError as exc:\n        raise YTUnidentifiedDataType(fn, *args, **kwargs) from exc\n\n    # Note: the temporary directory will be created by ratarmount\n    tempdir = fn + \".mount\"\n    tempdir_base = tempdir\n    i = 0\n    while os.path.exists(tempdir):\n        i += 1\n        tempdir = f\"{tempdir_base}.{i}\"\n\n    parent_conn, child_conn = Pipe()\n    proc = Process(target=_mount_helper, args=(fn, tempdir, ratarmount_kwa, child_conn))\n    proc.start()\n    if not parent_conn.recv():\n        raise MountError(f\"An error occurred while mounting {fn} in {tempdir}\")\n\n    # Note: the mounting needs to happen in another process which\n    # needs be run in the foreground (otherwise it may\n    # unmount). To prevent a race-condition here, we wait\n    # for the folder to be mounted within a reasonable time.\n    t = 0.0\n    while t < mount_timeout:\n        if os.path.ismount(tempdir):\n            break\n        time.sleep(0.1)\n        t += 0.1\n    else:\n        raise MountError(f\"Folder {tempdir} does not appear to be mounted\")\n\n    # We need to kill the process at exit (to force unmounting)\n    def umount_callback():\n        proc.terminate()\n\n    atexit.register(umount_callback)\n\n    # Alternatively, can dismount manually\n    def del_callback(self):\n        proc.terminate()\n        atexit.unregister(umount_callback)\n\n    ds = load(os.path.join(tempdir, path), *args, **kwargs)\n    ds.dismount = types.MethodType(del_callback, ds)\n\n    return ds\n\n\ndef load_hdf5_file(\n    fn: Union[str, \"os.PathLike[str]\"],\n    root_node: str | None = \"/\",\n    fields: list[str] | None = None,\n    bbox: np.ndarray | None = None,\n    nchunks: int = 0,\n    dataset_arguments: dict | None = None,\n):\n    \"\"\"\n    Create a (grid-based) yt dataset given the path to an hdf5 file.\n\n    This function accepts a filename, as well as (potentially) a bounding box,\n    the root node where fields are stored, and the number of chunks to attempt\n    to decompose the object into.  This function will then introspect that HDF5\n    file, attempt to determine the available fields, and then supply a\n    :class:`yt.data_objects.static_output.Dataset` object.  However, unlike the\n    other loaders, the data is *not* required to be preloaded into memory, and will\n    only be loaded *on demand*.\n\n    This does not yet work with particle-type datasets.\n\n    Parameters\n    ----------\n    fn : str, os.Pathlike[str]\n        A path to the hdf5 file that contains the data.\n\n    root_node: str, optional\n        If the fields to be loaded are stored under an HDF5 group object,\n        specify it here.  Otherwise, the fields are assumed to be at the root\n        level of the HDF5 file hierarchy.\n\n    fields : list of str, optional\n        The fields to be included as part of the dataset.  If this is not\n        included, all of the datasets under *root_node* will be included.\n        If your file contains, for instance, a \"parameters\" node at the root\n        level next to other fields, it would be (mistakenly) included.  This\n        allows you to specify only those that should be included.\n\n    bbox : array_like (xdim:zdim, LE:RE), optional\n        If supplied, this will be the bounding box for the dataset.  If not\n        supplied, it will be assumed to be from 0 to 1 in all dimensions.\n\n    nchunks : int, optional\n        How many chunks should this dataset be split into?  If 0 or not\n        supplied, yt will attempt to ensure that there is one chunk for every\n        64**3 zones in the dataset.\n\n    dataset_arguments : dict, optional\n        Any additional arguments that should be passed to\n        :class:`yt.loaders.load_amr_grids`, including things like the unit\n        length and the coordinates.\n\n    Returns\n    -------\n    :class:`yt.data_objects.static_output.Dataset` object\n        This returns an instance of a dataset, created with `load_amr_grids`,\n        that can read from the HDF5 file supplied to this function.  An open\n        handle to the HDF5 file is retained.\n\n    Raises\n    ------\n    FileNotFoundError\n        If fn does not match any existing file or directory.\n\n    \"\"\"\n\n    from yt.utilities.on_demand_imports import _h5py as h5py\n\n    dataset_arguments = dataset_arguments or {}\n\n    if bbox is None:\n        bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])\n        mylog.info(\"Assuming unitary (0..1) bounding box.\")\n\n    def _read_data(handle, root_node):\n        def _reader(grid, field_name):\n            ftype, fname = field_name\n            si = grid.get_global_startindex()\n            ei = si + grid.ActiveDimensions\n            return handle[root_node][fname][si[0] : ei[0], si[1] : ei[1], si[2] : ei[2]]\n\n        return _reader\n\n    fn = str(lookup_on_disk_data(fn))\n    handle = h5py.File(fn, \"r\")\n    reader = _read_data(handle, root_node)\n    if fields is None:\n        fields = list(handle[root_node].keys())\n        mylog.debug(\"Identified fields %s\", fields)\n    shape = handle[root_node][fields[0]].shape\n    if nchunks <= 0:\n        # We apply a pretty simple heuristic here.  We don't want more than\n        # about 64^3 zones per chunk.  So ...\n        full_size = np.prod(shape)\n        nchunks = full_size // (64**3)\n        mylog.info(\"Auto-guessing %s chunks from a size of %s\", nchunks, full_size)\n    grid_data = []\n    psize = get_psize(np.array(shape), nchunks)\n    left_edges, right_edges, shapes, _, _ = decompose_array(shape, psize, bbox)\n    for le, re, s in zip(left_edges, right_edges, shapes, strict=True):\n        data = dict.fromkeys(fields, reader)\n        data.update({\"left_edge\": le, \"right_edge\": re, \"dimensions\": s, \"level\": 0})\n        grid_data.append(data)\n    return load_amr_grids(grid_data, shape, bbox=bbox, **dataset_arguments)\n"
  },
  {
    "path": "yt/py.typed",
    "content": ""
  },
  {
    "path": "yt/sample_data/__init__.py",
    "content": ""
  },
  {
    "path": "yt/sample_data/api.py",
    "content": "\"\"\"\nThis is a collection of helper functions to yt.load_sample\n\"\"\"\n\nimport json\nimport re\nimport sys\nfrom functools import lru_cache\nfrom pathlib import Path\nfrom typing import Optional\nfrom warnings import warn\n\nfrom yt.config import ytcfg\nfrom yt.utilities.on_demand_imports import (\n    _pandas as pd,\n    _pooch as pooch,\n    _requests as requests,\n)\n\n\nnum_exp = re.compile(r\"\\d*(\\.\\d*)?\")\nbyte_unit_exp = re.compile(r\"[KMGT]?B\")\n\n\ndef _parse_byte_size(s: str):\n    \"\"\"\n    Convert a string size specification to integer byte size.\n\n    This function should be insensitive to case and whitespace.\n\n    It doesn't always return an int, as a temporary measure to deal with missing\n    or corrupted data in the registry. This should be fixed in the future.\n\n    Examples\n    --------\n    # most of the following examples are adapted from\n    # https://stackoverflow.com/a/31631711/5489622\n    >>> _parse_byte_size(None)\n    <NA>\n    >>> from numpy import nan\n    >>> _parse_byte_size(nan)\n    <NA>\n    >>> _parse_byte_size(\"1B\")\n    1\n    >>> _parse_byte_size(\"1.00 KB\")\n    1024\n    >>> _parse_byte_size(\"488.28 KB\")\n    500000\n    >>> _parse_byte_size(\"1.00 MB\")\n    1049000\n    >>> _parse_byte_size(\"47.68 MB\")\n    50000000\n    >>> _parse_byte_size(\"1.00 GB\")\n    1074000000\n    >>> _parse_byte_size(\"4.66 GB\")\n    5004000000\n    >>> _parse_byte_size(\"1.00 TB\")\n    1100000000000\n    >>> _parse_byte_size(\"4.55 TB\")\n    5003000000000\n    \"\"\"\n    try:\n        s = s.upper()\n    except AttributeError:\n        # input is not a string (likely a np.nan)\n        return pd.NA\n\n    match = re.search(num_exp, s)\n    if match is None:\n        raise ValueError\n    val = float(match.group())\n\n    match = re.search(byte_unit_exp, s)\n    if match is None:\n        raise ValueError\n    unit = match.group()\n    prefixes = [\"B\", \"K\", \"M\", \"G\", \"T\"]\n    raw_res = val * 1024 ** prefixes.index(unit[0])\n    return int(float(f\"{raw_res:.3e}\"))\n\n\ndef _get_sample_data_registry():\n    import importlib.resources as importlib_resources\n\n    return json.loads(\n        importlib_resources.files(\"yt\")\n        .joinpath(\"sample_data_registry.json\")\n        .read_bytes()\n    )\n\n\n@lru_cache(maxsize=128)\ndef get_data_registry_table():\n    \"\"\"\n    Load the sample data registry as a pandas.Dataframe instance.\n\n    This function is considered experimental and is exposed for exploratory purposed.\n    The output format is subject to change.\n\n    The output of this function is cached so it will only generate one request per session.\n    \"\"\"\n    # it would be nicer to have an actual api on the yt website server,\n    # but this will do for now\n    api_url = \"https://raw.githubusercontent.com/yt-project/website/master/data/datafiles.json\"\n\n    response = requests.get(api_url)\n\n    if not response.ok:\n        raise RuntimeError(\n            \"Could not retrieve registry data. Please check your network setup.\"\n        )\n\n    website_json = response.json()\n    # this dict follows this schema: {frontend_name: {flat dataframe-like}}\n\n    columns = [\"code\", \"filename\", \"size\", \"url\", \"description\"]\n    website_table = pd.concat(pd.DataFrame(d) for d in website_json.values())[columns]\n\n    # add a int-type byte size column\n    # note that we cast to pandas specific type \"Int64\" because we expect missing values\n    # see https://pandas.pydata.org/pandas-docs/stable/user_guide/missing_data.html#integer-dtypes-and-missing-data\n    website_table[\"byte_size\"] = (\n        website_table[\"size\"].apply(_parse_byte_size).astype(\"Int64\")\n    )\n\n    # normalize urls to match the local json\n    website_table[\"url\"] = website_table[\"url\"].apply(\n        lambda u: u.replace(\"http:\", \"https:\")\n    )\n\n    # load local data\n    pooch_table = pd.DataFrame(_get_sample_data_registry().values())\n\n    # merge tables\n    unified_table = website_table.merge(pooch_table, on=\"url\", how=\"outer\")\n\n    # PR 3089\n    # ideally we should be able to do this, but it's not possible\n    # at the time of writing because fhe \"filename\" is incomplete\n    # see the companion comment in load_sample\n    # unified_table.set_index(\"filename\", inplace=True)\n    # unified_table.index.rename(\"id\", inplace=True)\n    return unified_table\n\n\ndef _get_test_data_dir_path():\n    p = Path(ytcfg.get(\"yt\", \"test_data_dir\"))\n    if p.is_dir():\n        return p\n    warn(\n        \"Storage directory from yt config doesn't exist \"\n        f\"(currently set to '{p}'). \"\n        \"Current working directory will be used instead.\"\n    )\n    return Path.cwd()\n\n\ndef lookup_on_disk_data(fn) -> Path:\n    \"\"\"\n    Look for data file/dir on disk.\n\n    Returns\n    -------\n\n    pathlib.Path to a file/dir matching fn if any\n\n    Raises\n    ------\n    FileNotFoundError\n    \"\"\"\n\n    path = Path(fn).expanduser().resolve()\n\n    if path.exists():\n        return path\n\n    err_msg = f\"No such file or directory: '{fn}'.\"\n    test_data_dir = _get_test_data_dir_path()\n    if not test_data_dir.is_dir():\n        raise FileNotFoundError(err_msg)\n\n    alt_path = _get_test_data_dir_path().joinpath(fn).resolve()\n    if alt_path != path:\n        if alt_path.exists():\n            return alt_path\n        err_msg += f\"\\n(Also tried '{alt_path}').\"\n    raise FileNotFoundError(err_msg)\n\n\ndef get_download_cache_dir():\n    return _get_test_data_dir_path() / \"yt_download_cache\"\n\n\n_POOCHIE = None\n\n\ndef _get_pooch_instance():\n    global _POOCHIE\n    if _POOCHIE is None:\n        data_registry = get_data_registry_table()\n        cache_storage = get_download_cache_dir()\n\n        registry = {k: v[\"hash\"] for k, v in _get_sample_data_registry().items()}\n        _POOCHIE = pooch.create(\n            path=cache_storage,\n            base_url=\"https://yt-project.org/data/\",\n            registry=registry,\n        )\n    return _POOCHIE\n\n\ndef _download_sample_data_file(\n    filename, progressbar=True, timeout: Optional[int] = None\n):\n    \"\"\"\n    Download a file by url.\n\n    Returns\n    -------\n    storage_filename : location of the downloaded file\n\n    \"\"\"\n    downloader = pooch.HTTPDownloader(progressbar=progressbar, timeout=timeout)\n\n    poochie = _get_pooch_instance()\n    poochie.fetch(filename, downloader=downloader)\n    return poochie.path / filename\n"
  },
  {
    "path": "yt/sample_data_registry.json",
    "content": "{\n  \"A2052.tar.gz\": {\n    \"hash\": \"a6a427750945b36a7b7478e3e301e4aba75e95af11bdda2e9ef759c9bbcde5e5\",\n    \"load_kwargs\": {},\n    \"load_name\": \"xray_fits/A2052_merged_0.3-2_match-core_tmap_bgecorr.fits\",\n    \"url\": \"https://yt-project.org/data/A2052.tar.gz\"\n  },\n  \"AM06.tar.gz\": {\n    \"hash\": \"d95090c2c65725411ad5c11f1627c311ec144f93ed04a88a4a4e446aeb0420a6\",\n    \"load_kwargs\": {},\n    \"load_name\": \"AM06.out1.00400.athdf\",\n    \"url\": \"https://yt-project.org/data/AM06.tar.gz\"\n  },\n  \"ActiveParticleCosmology.tar.gz\": {\n    \"hash\": \"6a52b6539db12f29c4e4209705ffa823bb8027f65bf9431cf068d3ffdb28f55e\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0046/DD0046\",\n    \"url\": \"https://yt-project.org/data/ActiveParticleCosmology.tar.gz\"\n  },\n  \"ActiveParticleTwoSphere.tar.gz\": {\n    \"hash\": \"7b056da9180417c9339bb5c3b98f5dfa5ac33f2f6c2a0d5b70c624a41dd60d4e\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0011/DD0011\",\n    \"url\": \"https://yt-project.org/data/ActiveParticleTwoSphere.tar.gz\"\n  },\n  \"ArepoBullet.tar.gz\": {\n    \"hash\": \"17afd084a0e527e9dccf4cf0dc7e9296a61e44faec3bbc1f9efb3609d9df1025\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snapshot_150.hdf5\",\n    \"url\": \"https://yt-project.org/data/ArepoBullet.tar.gz\"\n  },\n  \"ArepoCosmicRays.tar.gz\": {\n    \"hash\": \"e664ea7f634fff778511e9ae7b2e375ba9c5099e427f7c037d48bba9944388f4\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snapshot_039.hdf5\",\n    \"url\": \"https://yt-project.org/data/ArepoCosmicRays.tar.gz\"\n  },\n  \"BigEndianGadgetBinary.tar.gz\": {\n    \"hash\": \"0ab8d69b7c0c1a74282c567823916cc947f9ead7ab54b749a38a17c158b371c9\",\n    \"load_kwargs\": {},\n    \"load_name\": \"BigEndianGadgetBinary\",\n    \"url\": \"https://yt-project.org/data/BigEndianGadgetBinary.tar.gz\"\n  },\n  \"C15-3D-3deg.tar.gz\": {\n    \"hash\": \"b59a4ea80b3e5bf0c8fc1ab960241b23e8cba6d7346cab25d071d9a63c3be2b7\",\n    \"load_kwargs\": {},\n    \"load_name\": \"chimera_002715000_grid_1_01.h5\",\n    \"url\": \"https://yt-project.org/data/C15-3D-3deg.tar.gz\"\n  },\n  \"CfRadialGrid.tar.gz\": {\n    \"hash\": \"22a45b322773d4b2864ccab93c521d8e3d637d81c8d8c0852e1a67c7fa883e0c\",\n    \"load_kwargs\": {},\n    \"load_name\": \"grid1.nc\",\n    \"url\": \"https://yt-project.org/data/CfRadialGrid.tar.gz\"\n  },\n  \"ChollaSimple.tar.gz\": {\n    \"hash\": \"aa277471f694f9de2e46c190efd8cd631c0694044aa29cb8431f127319774d73\",\n    \"load_kwargs\": {},\n    \"load_name\": \"0.h5\",\n    \"url\": \"https://yt-project.org/data/ChollaSimple.tar.gz\"\n  },\n  \"ClusterMerger.tar.gz\": {\n    \"hash\": \"862a537bdecb8de363f20abec751665fd2b9208c29ae66331105cf848e7a0033\",\n    \"load_kwargs\": {},\n    \"load_name\": \"Data_000100\",\n    \"url\": \"https://yt-project.org/data/ClusterMerger.tar.gz\"\n  },\n  \"CompactObjects.tar.gz\": {\n    \"hash\": \"e24f275d86c95900f62c640c8f5fcc4fec3310e09893b6ed6a9f1e7adc6b9d64\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/CompactObjects.tar.gz\"\n  },\n  \"D9p_500.tar.gz\": {\n    \"hash\": \"6d62244dbb4d0974f2e63dd191c3aaf65a8a5ebb90c202e40e871576c3267040\",\n    \"load_kwargs\": {},\n    \"load_name\": \"10MpcBox_HartGal_csf_a0.500.d\",\n    \"url\": \"https://yt-project.org/data/D9p_500.tar.gz\"\n  },\n  \"DICEGalaxyDisk.tar.gz\": {\n    \"hash\": \"e7cb5255be0b6e712620d337d019239980827ce458585fe5f3141d0f1d910721\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00001\",\n    \"url\": \"https://yt-project.org/data/DICEGalaxyDisk.tar.gz\"\n  },\n  \"DICEGalaxyDisk_nonCosmological.tar.gz\": {\n    \"hash\": \"f95032fc9dc61e760e3919f2c3a59960684d21b9a44cc34cb1a15953de18f30d\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00002/info_00002.txt\",\n    \"url\": \"https://yt-project.org/data/DICEGalaxyDisk_nonCosmological.tar.gz\"\n  },\n  \"DMonly.tar.gz\": {\n    \"hash\": \"45ad285c0df4d7c9e08f497047ac04da373580cd70a8e6fefbe824726235c5c7\",\n    \"load_kwargs\": {},\n    \"load_name\": \"PMcrs0.0100.DAT\",\n    \"url\": \"https://yt-project.org/data/DMonly.tar.gz\"\n  },\n  \"DeeplyNestedZoom.tar.gz\": {\n    \"hash\": \"cf6c33fc9d743624f9ce95b947df18bae6159afc1eb18e65aecc1b57cc9432ed\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0025/data0025\",\n    \"url\": \"https://yt-project.org/data/DeeplyNestedZoom.tar.gz\"\n  },\n  \"DensTurbMag.tar.gz\": {\n    \"hash\": \"43f3e03d1065d233d315c620316ba8fd877a94bd368d911905dbf81861beb47f\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DensTurbMag_hdf5_plt_cnt_0015\",\n    \"url\": \"https://yt-project.org/data/DensTurbMag.tar.gz\"\n  },\n  \"EAGLE_6.tar.gz\": {\n    \"hash\": \"234309547fdce3f68e0399bc6f565364ec3bdd1887ce4e414071e55654157652\",\n    \"load_kwargs\": {},\n    \"load_name\": \"eagle_0005.hdf5\",\n    \"url\": \"https://yt-project.org/data/EAGLE_6.tar.gz\"\n  },\n  \"ENZOE_orszag-tang_0.5.tar.gz\": {\n    \"hash\": \"dbd93068131d3d100c186adb801302a303ac2c5c54118ab5f9e19b409eee5efc\",\n    \"load_kwargs\": {},\n    \"load_name\": \"ENZOE_orszag-tang_0.5.block_list\",\n    \"url\": \"https://yt-project.org/data/ENZOE_orszag-tang_0.5.tar.gz\"\n  },\n  \"ENZOP_DD0140.tar.gz\": {\n    \"hash\": \"8df14326a82845479398447b8b55bd93d8eea1c5f31657536b390e6703458e0d\",\n    \"load_kwargs\": {},\n    \"load_name\": \"ENZOP_DD0140.block_list\",\n    \"url\": \"https://yt-project.org/data/ENZOP_DD0140.tar.gz\"\n  },\n  \"EnzoKelvinHelmholtz.tar.gz\": {\n    \"hash\": \"750dcd29a060aee7b5607afdb30a6e3c2b465be60fc5e022c19a78ab04b35a39\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0011/DD0011\",\n    \"url\": \"https://yt-project.org/data/EnzoKelvinHelmholtz.tar.gz\"\n  },\n  \"Enzo_64.tar.gz\": {\n    \"hash\": \"2b17fffb6a2e8e471ef85dbdbfa5568f09f7bdb77715c69b40dfe48be8f71bc9\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0043/data0043\",\n    \"url\": \"https://yt-project.org/data/Enzo_64.tar.gz\"\n  },\n  \"ExodusII_tests.tar.gz\": {\n    \"hash\": \"d21c5ef320f2b4705ec4cacf26488a3a59544fab8cc3a9726e8989ad1bd8d177\",\n    \"load_kwargs\": {},\n    \"load_name\": \"ExodusII/gold.e\",\n    \"url\": \"https://yt-project.org/data/ExodusII_tests.tar.gz\"\n  },\n  \"F37_80.tar.gz\": {\n    \"hash\": \"ff0eeb2690ec53f25def74a346fdef07878e46d16424ce4f5d31d8c841408a46\",\n    \"load_kwargs\": {},\n    \"load_name\": \"chimera_00001_grid_1_01.h5\",\n    \"url\": \"https://yt-project.org/data/F37_80.tar.gz\"\n  },\n  \"FIRE_M12i_ref11.tar.gz\": {\n    \"hash\": \"74488ca3158810ad76ed65bf43a5769b899b63d3985a9b43ff55bffd04bf3af2\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snapshot_600.hdf5\",\n    \"url\": \"https://yt-project.org/data/FIRE_M12i_ref11.tar.gz\"\n  },\n  \"GDFClumps.tar.gz\": {\n    \"hash\": \"d9d81f934bdc8e1459bb04471fdcafcea2c2600d326e20a3d922b4c148f7783b\",\n    \"load_kwargs\": {},\n    \"load_name\": \"clumps.h5\",\n    \"url\": \"https://yt-project.org/data/GDFClumps.tar.gz\"\n  },\n  \"Gadget3-snap-format2.tar.gz\": {\n    \"hash\": \"bedc0f1979f0dd0cc9707c60a806171fe47b4bece0d29b474ff4030b560d5660\",\n    \"load_kwargs\": {},\n    \"load_name\": \"Gadget3-snap-format2\",\n    \"url\": \"https://yt-project.org/data/Gadget3-snap-format2.tar.gz\"\n  },\n  \"GadgetDiskGalaxy.tar.gz\": {\n    \"hash\": \"262bbd552786c133918a858c8ce09f5348583733fb906f6defbb6375b5a7951e\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snapshot_200.hdf5\",\n    \"url\": \"https://yt-project.org/data/GadgetDiskGalaxy.tar.gz\"\n  },\n  \"GalaxyClusterMerger.tar.gz\": {\n    \"hash\": \"64d2909369fc0ca7a810734aaa53a6db2f526b101993c401e6b66fc0db597ed7\",\n    \"load_kwargs\": {},\n    \"load_name\": \"fiducial_1to3_b0.273d_hdf5_plt_cnt_0175\",\n    \"url\": \"https://yt-project.org/data/GalaxyClusterMerger.tar.gz\"\n  },\n  \"GasSloshing.tar.gz\": {\n    \"hash\": \"3e16c5975f310bafd1e859080310de7ca01d5aa055b3504c10138c93154202a4\",\n    \"load_kwargs\": {},\n    \"load_name\": \"sloshing_nomag2_hdf5_plt_cnt_0150\",\n    \"url\": \"https://yt-project.org/data/GasSloshing.tar.gz\"\n  },\n  \"GasSloshingLowRes.tar.gz\": {\n    \"hash\": \"1eb53d8c0b90e2ebb95aee129046bd230f5296347c90a44558b6f2bdb79951ed\",\n    \"load_kwargs\": {},\n    \"load_name\": \"sloshing_low_res_hdf5_plt_cnt_0300\",\n    \"url\": \"https://yt-project.org/data/GasSloshingLowRes.tar.gz\"\n  },\n  \"GaussianBeam.tar.gz\": {\n    \"hash\": \"5200448cee1cc03f8ba630c5f88c6a258f9d55cab5672ce8847035d701cfef15\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plt03008\",\n    \"url\": \"https://yt-project.org/data/GaussianBeam.tar.gz\"\n  },\n  \"GaussianCloud.tar.gz\": {\n    \"hash\": \"edf869828cc14c2f47a389a62bf5a9bec22fba0de7e5c5380c6eb1acca9aa6a1\",\n    \"load_kwargs\": {},\n    \"load_name\": \"data.0077.3d.hdf5\",\n    \"url\": \"https://yt-project.org/data/GaussianCloud.tar.gz\"\n  },\n  \"HiresIsolatedGalaxy.tar.gz\": {\n    \"hash\": \"1b310a243158dbb9f42b9a71a1c71512a24f1e3cac906713b518555f8bc350c6\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0044/DD0044\",\n    \"url\": \"https://yt-project.org/data/HiresIsolatedGalaxy.tar.gz\"\n  },\n  \"InteractingJets.tar.gz\": {\n    \"hash\": \"e6a0d55df9763842b94fdd453b69b1f2a392f19b4188a9d08c58bf63613c281f\",\n    \"load_kwargs\": {},\n    \"load_name\": \"jet_000002\",\n    \"url\": \"https://yt-project.org/data/InteractingJets.tar.gz\"\n  },\n  \"IsolatedGalaxy.tar.gz\": {\n    \"hash\": \"fc081bd4420efd02f7ba2db7eaf4bff0299d5cc4da93436be55a30c219daaf18\",\n    \"load_kwargs\": {},\n    \"load_name\": \"galaxy0030/galaxy0030\",\n    \"url\": \"https://yt-project.org/data/IsolatedGalaxy.tar.gz\"\n  },\n  \"IsolatedGalaxy_Gravity.tar.gz\": {\n    \"hash\": \"2b7faa6a2472d4afe526678775c208242605a8918ace99154bd4ecb6df266505\",\n    \"load_kwargs\": {},\n    \"load_name\": \"galaxy0030/galaxy0030\",\n    \"url\": \"https://yt-project.org/data/IsolatedGalaxy_Gravity.tar.gz\"\n  },\n  \"IsothermalCollapse.tar.gz\": {\n    \"hash\": \"1420074fdef1c00692bdd4dee31b5b92eaebd668a80510dc554942590b0dae47\",\n    \"load_kwargs\": {\n      \"bounding_box\": [\n        [\n          -3,\n          3\n        ],\n        [\n          -3,\n          3\n        ],\n        [\n          -3,\n          3\n        ]\n      ],\n      \"unit_base\": {\n        \"UnitLength_in_cm\": 5e+16,\n        \"UnitMass_in_g\": 1.98992e+33,\n        \"UnitVelocity_in_cm_per_s\": 46385.19\n      }\n    },\n    \"load_name\": \"snap_505.hdf5\",\n    \"url\": \"https://yt-project.org/data/IsothermalCollapse.tar.gz\"\n  },\n  \"IsothermalSphere.tar.gz\": {\n    \"hash\": \"787b0aba3446c62d07caafd7db8e0f225d52b78ce2afe6d86c2535898fc7f4e5\",\n    \"load_kwargs\": {},\n    \"load_name\": \"data.0000.3d.hdf5\",\n    \"url\": \"https://yt-project.org/data/IsothermalSphere.tar.gz\"\n  },\n  \"JetICMWall.tar.gz\": {\n    \"hash\": \"4f156be55eb96d994001b64965017abf55728ce406bdbdbf5b0de3b3238b905a\",\n    \"load_kwargs\": {},\n    \"load_name\": \"Data_000060\",\n    \"url\": \"https://yt-project.org/data/JetICMWall.tar.gz\"\n  },\n  \"KelvinHelmholtz.tar.gz\": {\n    \"hash\": \"a70ccdab287e6b24553881d24a08a4025079b0e40eea9097c7ef686166ce571d\",\n    \"load_kwargs\": {},\n    \"load_name\": \"data.0004.hdf5\",\n    \"url\": \"https://yt-project.org/data/KelvinHelmholtz.tar.gz\"\n  },\n  \"KeplerianDisk.tar.gz\": {\n    \"hash\": \"488307807a1e53c404b316f28915d614aa6673defd7b2291e3f26b7fc7839506\",\n    \"load_kwargs\": {},\n    \"load_name\": \"disk.out1.00000.athdf\",\n    \"url\": \"https://yt-project.org/data/KeplerianDisk.tar.gz\"\n  },\n  \"KeplerianRing.tar.gz\": {\n    \"hash\": \"b2e26d9f98319289283e44e4303a3ac0bfc937931290d8aff86e437503634eab\",\n    \"load_kwargs\": {},\n    \"load_name\": \"keplerian_ring_0020.hdf5\",\n    \"url\": \"https://yt-project.org/data/KeplerianRing.tar.gz\"\n  },\n  \"LangmuirWave_v2.tar.gz\": {\n    \"hash\": \"27ab056b79b598b4b88e8aa4be042282c07899e85206dd42a506639a69e31270\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plt00020_v2\",\n    \"url\": \"https://yt-project.org/data/LangmuirWave_v2.tar.gz\"\n  },\n  \"Laser.tar.gz\": {\n    \"hash\": \"7850a9f8cba1fcab4712b269fbea707dbfbaf611605e319cefe444bf9a1347f0\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plt00015\",\n    \"url\": \"https://yt-project.org/data/Laser.tar.gz\"\n  },\n  \"LocBub_dust.tar.gz\": {\n    \"hash\": \"1ad9728ca982616686d9bd6c1c959fece3c067ecbbba64ff8c2c5bfcc1deed01\",\n    \"load_kwargs\": {},\n    \"load_name\": \"LocBub_dust_hdf5_plt_cnt_0220\",\n    \"url\": \"https://yt-project.org/data/LocBub_dust.tar.gz\"\n  },\n  \"MagneticumCluster.tar.gz\": {\n    \"hash\": \"403c2224c205a44ee92a5925053be8aef6e5d530d9297c18151cdb0f588c32a7\",\n    \"load_kwargs\": {\n      \"long_ids\": true,\n      \"field_spec\": \"magneticum_box2_hr\"\n    },\n    \"load_name\": \"snap_132\",\n    \"url\": \"https://yt-project.org/data/MagneticumCluster.tar.gz\"\n  },\n  \"MHDBlast.tar.gz\": {\n    \"hash\": \"fee1139df19448bae966a5396775476935886e69e3898fd7cf71d54548c4e14b\",\n    \"load_kwargs\": {},\n    \"load_name\": \"id0/Blast.0100.vtk\",\n    \"url\": \"https://yt-project.org/data/MHDBlast.tar.gz\"\n  },\n  \"MHDCTOrszagTang.tar.gz\": {\n    \"hash\": \"90d69d9ed5488051fdbab2ebddf1b45647ab513b19ab0b04bb9ea65b0bbbab26\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0004/data0004\",\n    \"url\": \"https://yt-project.org/data/MHDCTOrszagTang.tar.gz\"\n  },\n  \"MHDOrszagTangVortex.tar.gz\": {\n    \"hash\": \"24c97644ab8620333b0839ac69e0e4021820681eebafe96882fdf3b8fbec1270\",\n    \"load_kwargs\": {},\n    \"load_name\": \"Data_000018\",\n    \"url\": \"https://yt-project.org/data/MHDOrszagTangVortex.tar.gz\"\n  },\n  \"MHDSloshing.tar.gz\": {\n    \"hash\": \"f0d0b4e26145a2f7445c3afed8ed0294026a2209496c477cc0d1a7a0f022bf38\",\n    \"load_kwargs\": {\n      \"units_override\": {\n        \"length_unit\": [1.0, \"Mpc\"],\n        \"mass_unit\": [100000000000000.0, \"Msun\"],\n        \"time_unit\": [1.0, \"Myr\"]\n      }\n    },\n    \"load_name\": \"virgo_low_res.0054.vtk\",\n    \"url\": \"https://yt-project.org/data/MHDSloshing.tar.gz\"\n  },\n  \"MHD_Cyl3d_hdf5_plt_cnt_0100.tar.gz\": {\n    \"hash\": \"f3ad1a0d01e44617a2291733cdceb9b7a54a4f3551155e5ec1b14ee012357dc8\",\n    \"load_kwargs\": {},\n    \"load_name\": \"MHD_Cyl3d_hdf5_plt_cnt_0100.hdf5\",\n    \"url\": \"https://yt-project.org/data/MHD_Cyl3d_hdf5_plt_cnt_0100.tar.gz\"\n  },\n  \"MOOSE_Sample_data.tar.gz\": {\n    \"hash\": \"0a43e40931e25c4a0e4f205b9902666c2d7a89cd5959e2d5eb7819059cabe01f\",\n    \"load_kwargs\": {},\n    \"load_name\": \"out.e\",\n    \"url\": \"https://yt-project.org/data/MOOSE_Sample_data.tar.gz\"\n  },\n  \"MoabTest.tar.gz\": {\n    \"hash\": \"22609631c0e417964e0247b4ad62199a60c2d7979f38f8587960a9f8b2147fab\",\n    \"load_kwargs\": {},\n    \"load_name\": \"fng_usrbin22.h5m\",\n    \"url\": \"https://yt-project.org/data/MoabTest.tar.gz\"\n  },\n  \"MultiRegion.tar.gz\": {\n    \"hash\": \"6acf8e21f2b847102cfbb9f6e22b9b919a9b807e22b2176854fead43e3344e9d\",\n    \"load_kwargs\": {},\n    \"load_name\": \"two_region_example_out.e\",\n    \"url\": \"https://yt-project.org/data/MultiRegion.tar.gz\"\n  },\n  \"Nyx_LyA.tar.gz\": {\n    \"hash\": \"db9fff2e78ac326780d9ba4fcdb0f9fe30440dd9100b6c50cb1f97722f2c8ea8\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plt00000\",\n    \"url\": \"https://yt-project.org/data/Nyx_LyA.tar.gz\"\n  },\n  \"Orbit.tar.gz\": {\n    \"hash\": \"3df7988b555c5ff17e73f4675167c78baa33993e0c321d56c8da542c5ae03a4b\",\n    \"load_kwargs\": {},\n    \"load_name\": \"orbit_hdf5_chk_0014\",\n    \"url\": \"https://yt-project.org/data/Orbit.tar.gz\"\n  },\n  \"PlasmaAcceleration_v2.tar.gz\": {\n    \"hash\": \"45eea2e3b1d9bd796816c1d300e5ccd4db57c9609ad8dc9b93a157a90c3a89a4\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plt00030_v2\",\n    \"url\": \"https://yt-project.org/data/PlasmaAcceleration_v2.tar.gz\"\n  },\n  \"Plummer.tar.gz\": {\n    \"hash\": \"3ef04dad7aee393a9cb08cf6b8616f271aa733a53a07fdfad34b8effcb550998\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plummer_000000\",\n    \"url\": \"https://yt-project.org/data/Plummer.tar.gz\"\n  },\n  \"PopIII_mini.tar.gz\": {\n    \"hash\": \"184dfeb09474fad8ec91474d44b75f6a7d33be99c021b76902d91baad98ddca6\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0034/DD0034\",\n    \"url\": \"https://yt-project.org/data/PopIII_mini.tar.gz\"\n  },\n  \"RT_particles.tar.gz\": {\n    \"hash\": \"69f7a96dbc8fad60defc03f1a2e864de4ffbf5d881d7cac2c8727ae317e0a24f\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plt00050\",\n    \"url\": \"https://yt-project.org/data/RT_particles.tar.gz\"\n  },\n  \"RadAdvect.tar.gz\": {\n    \"hash\": \"0511e8d45cdb9b268089bc4ef4608a58b7218cf0db9da8d83a14cbd34722784b\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plt00000\",\n    \"url\": \"https://yt-project.org/data/RadAdvect.tar.gz\"\n  },\n  \"RadTube.tar.gz\": {\n    \"hash\": \"43a2f00c435cbaa4708d6e7e5e3ff0784699f5d28bc435ecbd677f64e6351837\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plt00500\",\n    \"url\": \"https://yt-project.org/data/RadTube.tar.gz\"\n  },\n  \"RamPressureStripping.tar.gz\": {\n    \"hash\": \"4ef4f9f9127668699111091d31ece0fff6e7ab2e9e6af7c547810df31db4a40b\",\n    \"load_kwargs\": {\n      \"units_override\": {\n        \"length_unit\": 8.0236e+22,\n        \"mass_unit\": 5.1649293e+39,\n        \"time_unit\": 308600000000000.0\n      }\n    },\n    \"load_name\": \"id0/rps.0062.vtk\",\n    \"url\": \"https://yt-project.org/data/RamPressureStripping.tar.gz\"\n  },\n  \"SecondOrderQuads.tar.gz\": {\n    \"hash\": \"b00000d4a6ec3907e2f22cbf58c7b582a9a63ca2792a0419fb56a8733b1ec904\",\n    \"load_kwargs\": {},\n    \"load_name\": \"lid_driven_out.e\",\n    \"url\": \"https://yt-project.org/data/SecondOrderQuads.tar.gz\"\n  },\n  \"SecondOrderTets.tar.gz\": {\n    \"hash\": \"73bbd2bf10d02287f15303215952d560969daab1919001969c29991561e26cb9\",\n    \"load_kwargs\": {},\n    \"load_name\": \"tet10_unstructured_out.e\",\n    \"url\": \"https://yt-project.org/data/SecondOrderTets.tar.gz\"\n  },\n  \"SecondOrderTris.tar.gz\": {\n    \"hash\": \"d5a4e05117ceb53b31e1ecef077355b05dbd8a43b6773751b48390ef80795165\",\n    \"load_kwargs\": {},\n    \"load_name\": \"RZ_p_no_parts_do_nothing_bcs_cone_out.e\",\n    \"url\": \"https://yt-project.org/data/SecondOrderTris.tar.gz\"\n  },\n  \"Sedov3D.tar.gz\": {\n    \"hash\": \"4e3220744024333293c707bafe64706d325e1f6ee0914bee4be78c3b48d3f97e\",\n    \"load_kwargs\": {},\n    \"load_name\": \"Sedov_3d/sedov_hdf5_chk_0000\",\n    \"url\": \"https://yt-project.org/data/Sedov3D.tar.gz\"\n  },\n  \"ShockCloud.tar.gz\": {\n    \"hash\": \"5a41fe85c293b5714fa3fb989c7d905ed8b6cc92276c524ec786933081176027\",\n    \"load_kwargs\": {},\n    \"load_name\": \"id0/Cloud.0050.vtk\",\n    \"url\": \"https://yt-project.org/data/ShockCloud.tar.gz\"\n  },\n  \"SimbaExample.tar.gz\": {\n    \"hash\": \"6770e4d5ead92cfc966bca5b8735903389809885f0f1b50523448f68bc53a5ec\",\n    \"load_kwargs\": {},\n    \"load_name\": \"simba_example.hdf5\",\n    \"url\": \"https://yt-project.org/data/SimbaExample.tar.gz\"\n  },\n  \"StarParticles.tar.gz\": {\n    \"hash\": \"83fbcb7d520cc49c25430003a9e1f1ca8ffed7ab9f83319aa91faa45494ed485\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plrd01000\",\n    \"url\": \"https://yt-project.org/data/StarParticles.tar.gz\"\n  },\n  \"TNGHalo.tar.gz\": {\n    \"hash\": \"759b517500362d1ccd6286fe8854b1a303ecbce4678b9ff7ce541cfecda8a604\",\n    \"load_kwargs\": {},\n    \"load_name\": \"halo_59.hdf5\",\n    \"url\": \"https://yt-project.org/data/TNGHalo.tar.gz\"\n  },\n  \"TipsyAuxiliary.tar.gz\": {\n    \"hash\": \"c27205c3b5f673b9822c76212c59c9632027210a6ee5f3c2e9eac213b805a143\",\n    \"load_kwargs\": {},\n    \"load_name\": \"ascii/onestar.00001\",\n    \"url\": \"https://yt-project.org/data/TipsyAuxiliary.tar.gz\"\n  },\n  \"TipsyGalaxy.tar.gz\": {\n    \"hash\": \"34228eec5d43a8c0465b785efea39e4e963390730919e0bf819f8fca474d3341\",\n    \"load_kwargs\": {},\n    \"load_name\": \"galaxy.00300\",\n    \"url\": \"https://yt-project.org/data/TipsyGalaxy.tar.gz\"\n  },\n  \"ToroShockTube.tar.gz\": {\n    \"hash\": \"016541e0e600f77b820bfc7281bafc2e3c3a1bd6ae11567a0dce7247032a5f6a\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0001/data0001\",\n    \"url\": \"https://yt-project.org/data/ToroShockTube.tar.gz\"\n  },\n  \"TurbBoxLowRes.tar.gz\": {\n    \"hash\": \"96b9e08dcf007bd4410ed98f9252aad33138515b8e83f4168f4f7bcda5e5bc27\",\n    \"load_kwargs\": {},\n    \"load_name\": \"data.0005.3d.hdf5\",\n    \"url\": \"https://yt-project.org/data/TurbBoxLowRes.tar.gz\"\n  },\n  \"UnigridData.tar.gz\": {\n    \"hash\": \"8efffc5377c99eaa45c086ac730d056b890c8358f17e8a2f7d8c281384fc2ab7\",\n    \"load_kwargs\": {},\n    \"load_name\": \"velocity_field_20.fits\",\n    \"url\": \"https://yt-project.org/data/UnigridData.tar.gz\"\n  },\n  \"WDMerger_hdf5_chk_1000.tar.gz\": {\n    \"hash\": \"a3806579f6e25b61ddc918dd6c3004d69cda88a94be3967111702cf7636ba158\",\n    \"load_kwargs\": {},\n    \"load_name\": \"WDMerger_hdf5_chk_1000.hdf5\",\n    \"url\": \"https://yt-project.org/data/WDMerger_hdf5_chk_1000.tar.gz\"\n  },\n  \"WaveDarkMatter.tar.gz\": {\n    \"hash\": \"4996fbf505ea56a196970d8920e9e46d0da70fecc792355dabaab6be8e5f0172\",\n    \"load_kwargs\": {},\n    \"load_name\": \"psiDM_000020\",\n    \"url\": \"https://yt-project.org/data/WaveDarkMatter.tar.gz\"\n  },\n  \"WindTunnel.tar.gz\": {\n    \"hash\": \"414b923d59f671196f2185c5216ae241156619b5042854df6236dea9fa10ee2b\",\n    \"load_kwargs\": {},\n    \"load_name\": \"windtunnel_4lev_hdf5_plt_cnt_0030\",\n    \"url\": \"https://yt-project.org/data/WindTunnel.tar.gz\"\n  },\n  \"ZeldovichPancake.tar.gz\": {\n    \"hash\": \"88467b9babe6886ce393ee9ee01ade9be3842a32cef9946863c68f765b8ebd32\",\n    \"load_kwargs\": {},\n    \"load_name\": \"plt32.2d.hdf5\",\n    \"url\": \"https://yt-project.org/data/ZeldovichPancake.tar.gz\"\n  },\n  \"acisf05356N003_evt2.fits.gz\": {\n    \"hash\": \"6fc406dd056b525592f704b202dadf52c41024660dbbd96cfbeb830d710a70d8\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/acisf05356N003_evt2.fits.gz\"\n  },\n  \"agora_1e11.00400.tar.gz\": {\n    \"hash\": \"3a8820735bcf5067dfbe0fcd90d79121a801ea1a34c7d6de7e09185d3c635e6a\",\n    \"load_kwargs\": {},\n    \"load_name\": \"agora_1e11.00400\",\n    \"url\": \"https://yt-project.org/data/agora_1e11.00400.tar.gz\"\n  },\n  \"ahf_halos.tar.gz\": {\n    \"hash\": \"66d60ab80520cdeb1510a9c7e48a214d79f2559f7808bc346722028b5b48f8f7\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snap_N64L16_068.parameter\",\n    \"url\": \"https://yt-project.org/data/ahf_halos.tar.gz\"\n  },\n  \"athenapk_cluster.tar.gz\": {\n    \"hash\": \"95f641b31cca00a39fa728be367861db9abc75ec3e3adcd9103325f675d3e9c1\",\n    \"load_kwargs\": {},\n    \"load_name\": \"athenapk_cluster.restart.00000.rhdf\",\n    \"url\": \"https://yt-project.org/data/athenapk_cluster.tar.gz\"\n  },\n  \"athenapk_disk.tar.gz\": {\n    \"hash\": \"3bd59a494a10b28cd691d1f69700f9e100a6a3a32541b0e16348d81163404cd0\",\n    \"load_kwargs\": {},\n    \"load_name\": \"athenapk_disk.prim.00000.phdf\",\n    \"url\": \"https://yt-project.org/data/athenapk_disk.tar.gz\"\n  },\n  \"bw_cartesian_3d.tar.gz\": {\n    \"hash\": \"de8b3b4c2a8c93f857c6729a118960eb1bcf244e1de07aee231d6fcc589c5628\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output0001.dat\",\n    \"url\": \"https://yt-project.org/data/bw_cartesian_3d.tar.gz\"\n  },\n  \"bw_cylindrical_3d.tar.gz\": {\n    \"hash\": \"7392e60dabd7ef636ce98929c56d4d8ece89958716a67e32792dce5981d64709\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output0001.dat\",\n    \"url\": \"https://yt-project.org/data/bw_cylindrical_3d.tar.gz\"\n  },\n  \"bw_polar_2d.tar.gz\": {\n    \"hash\": \"85308582ab55049474a9a302d1e88459a89f4e8925c5b901fbf3c590b1f269ab\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output0001.dat\",\n    \"url\": \"https://yt-project.org/data/bw_polar_2d.tar.gz\"\n  },\n  \"bw_spherical_2d.tar.gz\": {\n    \"hash\": \"2407f8f3352d7f2687a608bdfb7143e892d54b983d5a0d1f869132f41c98dbcc\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output0001.dat\",\n    \"url\": \"https://yt-project.org/data/bw_spherical_2d.tar.gz\"\n  },\n  \"kh2d.tar.gz\": {\n    \"hash\": \"2ea94f4f24b3423dcb67dc1b1eb28c83a02b66b6c79b29d1703b7aa2aa76b71e\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output0001.dat\",\n    \"url\": \"https://yt-project.org/data/kh2d.tar.gz\"\n  },\n  \"kh3d.tar.gz\": {\n    \"hash\": \"ae354a29eb88e2cd6720efdb98de96a22b4988364cd65cf8f8dce071914430c3\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output0001.dat\",\n    \"url\": \"https://yt-project.org/data/kh3d.tar.gz\"\n  },\n  \"mhd_jet.tar.gz\": {\n    \"hash\": \"24b7d24ec10d876dcc508563d516e1a4fe323150a6d5aef2f4640b020da817d7\",\n    \"load_kwargs\": {},\n    \"load_name\": \"Jet0003.dat\",\n    \"url\": \"https://yt-project.org/data/mhd_jet.tar.gz\"\n  },\n  \"riemann1d.tar.gz\": {\n    \"hash\": \"377483c6b74c1826b5f7c5e2b52a5b8ffeb974515d62d6135208cc96f7f09ae4\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output0001.dat\",\n    \"url\": \"https://yt-project.org/data/riemann1d.tar.gz\"\n  },\n  \"rmi_dust_2d.tar.gz\": {\n    \"hash\": \"96fafc63755b065ffa17d63a7ab5f01d4183ed1b10728ce1c5a23281cb43f5a6\",\n    \"load_kwargs\": {\"parfiles\": [\"rmi_dust_2d/amrvac.par\"]},\n    \"load_name\": \"output0001.dat\",\n    \"url\": \"https://yt-project.org/data/rmi_dust_2d.tar.gz\"\n  },\n  \"solar_prom2d.tar.gz\": {\n    \"hash\": \"a8ddb034c27badf8160af1c4622ec9e9835c4434a5556e55212df6803c5020dc\",\n    \"load_kwargs\": {\"parfiles\": [\"solar_prom2d/amrvac.par\"]},\n    \"load_name\": \"output0001.dat\",\n    \"url\": \"https://yt-project.org/data/solar_prom2d.tar.gz\"\n  },\n  \"arbor.tar.gz\": {\n    \"hash\": \"d6c89c6496acdd92ef086736b8644996de58b1f4292b67b01d5db94ce60210fb\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/arbor.tar.gz\"\n  },\n  \"big_tipsy.tar.gz\": {\n    \"hash\": \"fe7a5b1b7bb3d960a3ccb77457c039a010c05b5caa10b6891add3274fb9f7e36\",\n    \"load_kwargs\": {},\n    \"load_name\": \"g1536.00256\",\n    \"url\": \"https://yt-project.org/data/big_tipsy.tar.gz\"\n  },\n  \"c5.tar.gz\": {\n    \"hash\": \"db007304b128472d2439c946a8169f5cc00e0701bbcda18012e1beb8cd30dba6\",\n    \"load_kwargs\": {},\n    \"load_name\": \"c5.h5m\",\n    \"url\": \"https://yt-project.org/data/c5.tar.gz\"\n  },\n  \"castro_sedov_1d_cyl_plt00150.tar.gz\": {\n    \"hash\": \"c96a8fdf3cd43563a88e34569a6b37e57a0349692a9100ea65252506a167f08f\",\n    \"load_kwargs\": {},\n    \"load_name\": \"\",\n    \"url\": \"https://yt-project.org/data/castro_sedov_1d_cyl_plt00150.tar.gz\"\n  },\n  \"castro_sedov_2d_cyl_in_cart_plt00150.tar.gz\": {\n    \"hash\": \"8b73a37a0503dba593af65f8bf16dfcc11ac825d671f11e5a364a2dee63c9047\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/castro_sedov_2d_cyl_in_cart_plt00150.tar.gz\"\n  },\n  \"castro_sedov_2d_sph_in_cyl_plt00130.tar.gz\": {\n    \"hash\": \"ef4d081a2a2f8e10afe132768725c573631b82021e91f07782f1c1fbe043e2b5\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/castro_sedov_2d_sph_in_cyl_plt00130.tar.gz\"\n  },\n  \"castro_sod_x_plt00036.tar.gz\": {\n    \"hash\": \"3f0a586b41e7b54fa2b3cddd50f9384feb2efe1fe1a815e7348965ae7bf88f78\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/castro_sod_x_plt00036.tar.gz\"\n  },\n  \"check_pooch.py\": {\n    \"hash\": \"d73fa43c3d56b3487ba96d45c0e7198f3d5471475f19105ee4fcd6442e2a3024\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/check_pooch.py.tar.gz\"\n  },\n  \"cm1_tornado_lofs.tar.gz\": {\n    \"hash\": \"7a84f688b363b79d2ac7bf53d1a963f01d07926a0d268e4163ba87ea81699706\",\n    \"load_kwargs\": {},\n    \"load_name\": \"nc4_cm1_lofs_tornado_test.nc\",\n    \"url\": \"https://yt-project.org/data/cm1_tornado_lofs.tar.gz\"\n  },\n  \"datafiles.json\": {\n    \"hash\": \"56732241ddfe5770d0dacc52c1957a0c8b13ca095fd0d8f029dbd263be714838\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/datafiles.json.tar.gz\"\n  },\n  \"enzo_cosmology_plus.tar.gz\": {\n    \"hash\": \"dab1d5cdf30ec6a99b556fe20cc91ba9a3b37c079b608de7cb92a240b2a943c8\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0046/DD0046\",\n    \"url\": \"https://yt-project.org/data/enzo_cosmology_plus.tar.gz\"\n  },\n  \"enzo_tiny_cosmology.tar.gz\": {\n    \"hash\": \"91b04e443dfdafc2de234d7bb6f7ab4a0a970ec43e6ff5c59e0c9ded8fa41fa9\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0046/DD0046\",\n    \"url\": \"https://yt-project.org/data/enzo_tiny_cosmology.tar.gz\"\n  },\n  \"example-2d.tar.gz\": {\n    \"hash\": \"d9f93ec4c83bd2abe14ea7bac447d154f44b86087e62945de8ce8e295f0d231d\",\n    \"load_kwargs\": {},\n    \"load_name\": \"hdf5/data00000100.h5\",\n    \"url\": \"https://yt-project.org/data/example-2d.tar.gz\"\n  },\n  \"example-3d.tar.gz\": {\n    \"hash\": \"42f860e42e80885d80f4ecfa84f58a8b083bb69d6b9e3b1bae1517003cb163c3\",\n    \"load_kwargs\": {},\n    \"load_name\": \"hdf5/data00000100.h5\",\n    \"url\": \"https://yt-project.org/data/example-3d.tar.gz\"\n  },\n  \"fiducial_1to1_b0.tar.gz\": {\n    \"hash\": \"1af47a8230addb96055a5c73d1fab9b413f566df191d03c60f0f202df845e7ff\",\n    \"load_kwargs\": {},\n    \"load_name\": \"fiducial_1to1_b0_hdf5_part_0004\",\n    \"url\": \"https://yt-project.org/data/fiducial_1to1_b0.tar.gz\"\n  },\n  \"fiducial_1to3_b1.tar.gz\": {\n    \"hash\": \"23c9faaa1af084b186f4115662967473b1b5cfe76b9afe232be800b6bd7da941\",\n    \"load_kwargs\": {},\n    \"load_name\": \"fiducial_1to3_b1_hdf5_part_0080\",\n    \"url\": \"https://yt-project.org/data/fiducial_1to3_b1.tar.gz\"\n  },\n  \"gadget_fof_halos.tar.gz\": {\n    \"hash\": \"77430eaca62ccc0c476d863f0beff5b0c71fa827b7a2fc33aa9cf803e001cc90\",\n    \"load_kwargs\": {},\n    \"load_name\": \"groups_042/fof_subhalo_tab_042.0.hdf5\",\n    \"url\": \"https://yt-project.org/data/gadget_fof_halos.tar.gz\"\n  },\n  \"gadget_halos.tar.gz\": {\n    \"hash\": \"1eb2c77700fac6eb8f9435549e0613ce38035c9106b295b9d5f6c70c82542842\",\n    \"load_kwargs\": {},\n    \"load_name\": \"data/groups_076/fof_subhalo_tab_076.0.hdf5\",\n    \"url\": \"https://yt-project.org/data/gadget_halos.tar.gz\"\n  },\n  \"geos.tar.gz\": {\n    \"hash\": \"2fbf9537be8c4f5da84ea22ed0971ded1a9aa71140fddda92f00f20df6fc43d3\",\n    \"load_kwargs\": {},\n    \"load_name\": \"GEOS.fp.asm.inst3_3d_aer_Nv.20180822_0900.V01.nc4\",\n    \"url\": \"https://yt-project.org/data/geos.tar.gz\"\n  },\n  \"gizmo_64.tar.gz\": {\n    \"hash\": \"11b8f425832bc1579854e013390483c9131ef5f27e3a74c8685a6d6dfcb809e5\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output/snap_N64L16_135.hdf5\",\n    \"url\": \"https://yt-project.org/data/gizmo_64.tar.gz\"\n  },\n  \"gizmo_cosmology_plus.tar.gz\": {\n    \"hash\": \"73062872b13c6a8c86ac134b18e177c3526250000cef5559ceb0b2383cbaba6b\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snap_N128L16_131.hdf5\",\n    \"url\": \"https://yt-project.org/data/gizmo_cosmology_plus.tar.gz\"\n  },\n  \"gizmo_mhd_mwdisk.tar.gz\": {\n    \"hash\": \"6f64248af83f50543173ade9c2c3f33ef1eae762b8e97196da5caf79ba4c5651\",\n    \"load_kwargs\": {},\n    \"load_name\": \"gizmo_mhd_mwdisk.hdf5\",\n    \"url\": \"https://yt-project.org/data/gizmo_mhd_mwdisk.tar.gz\"\n  },\n  \"gizmo_zeldovich.tar.gz\": {\n    \"hash\": \"5f1a73ead736024ffb6c099f84e834ec927d2818c93d7c775d9ff625adaa7c51\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snapshot_076_wi_gizver.hdf5\",\n    \"url\": \"https://yt-project.org/data/gizmo_zeldovich.tar.gz\"\n  },\n  \"grs-50-cube.fits.gz\": {\n    \"hash\": \"1aff152f616d2626c8515c1493e65f07843d9b5f2ba73b7e4271a2dc6a9e6da7\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/grs-50-cube.fits.gz\"\n  },\n  \"halo1e11_run1.00400.tar.gz\": {\n    \"hash\": \"414d05f9d5aa2dc2811e55fab59ad2c1426ec51675aaf41db773b24cd2f0aa5f\",\n    \"load_kwargs\": {},\n    \"load_name\": \"halo1e11_run1.00400\",\n    \"url\": \"https://yt-project.org/data/halo1e11_run1.00400.tar.gz\"\n  },\n  \"hello-0210.tar.gz\": {\n    \"hash\": \"d18964e339c8ddc2a56859edd2cd853648b914e63434a59d4a457a876d9496f3\",\n    \"load_kwargs\": {},\n    \"load_name\": \"hello-0210.block_list\",\n    \"url\": \"https://yt-project.org/data/hello-0210.tar.gz\"\n  },\n  \"m33_hi.fits.gz\": {\n    \"hash\": \"6a44b6c352a5c98c58f0b2b7498fd87e2430c530971eaff68b843146d82d6609\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/m33_hi.fits.gz\"\n  },\n  \"maestro_subCh_plt00248.tar.gz\": {\n    \"hash\": \"6de1b63b63ce9738e4596ccf4cc75087d5029c892c07f40c2c69887e76b8ec1a\",\n    \"load_kwargs\": {},\n    \"load_name\": \"maestro_subCh_plt00248\",\n    \"url\": \"https://yt-project.org/data/maestro_subCh_plt00248.tar.gz\"\n  },\n  \"maestro_xrb_lores_23437.tar.gz\": {\n    \"hash\": \"a17b63609a2c50a829e26420f093ee898237b1ef8256ef38ee8be56a6f824460\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/maestro_xrb_lores_23437.tar.gz\"\n  },\n  \"medium_tipsy.tar.gz\": {\n    \"hash\": \"b77c1802c5ea6cf3ab3a698a741fbfdbbcdb561bf57be2fe7a3c9d00980ff438\",\n    \"load_kwargs\": {},\n    \"load_name\": \"g1536.00256\",\n    \"url\": \"https://yt-project.org/data/medium_tipsy.tar.gz\"\n  },\n  \"nyx_sedov_plt00086.tgz\": {\n    \"hash\": \"308f6b2a81363c5aada35941c199f149d8688f8ce65803f33d6dedce43a57a1c\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/nyx_sedov_plt00086.tgz\"\n  },\n  \"nyx_small.tar.gz\": {\n    \"hash\": \"1eefb443c2de6d9c6f4546bb87e1bde89ae98adb97c860a6065ac3177ecf1127\",\n    \"load_kwargs\": {},\n    \"load_name\": \"nyx_small_00000\",\n    \"url\": \"https://yt-project.org/data/nyx_small.tar.gz\"\n  },\n  \"output_00080.tar.gz\": {\n    \"hash\": \"175b18fbf72246c36442d7886f04194e1ea4d9d0b2bac2d5568a887d5f51bf29\",\n    \"load_kwargs\": {},\n    \"load_name\": \"info_00080.txt\",\n    \"url\": \"https://yt-project.org/data/output_00080.tar.gz\"\n  },\n  \"output_00080_halos.tar.gz\": {\n    \"hash\": \"fb8f573ea7cb183e1531091b3fce91cf7015cfdf5ccbce9ef2742e3135b52d4c\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/output_00080_halos.tar.gz\"\n  },\n  \"output_00101.tar.gz\": {\n    \"hash\": \"7ff2287c225d4e68f7ec04efd183732bc7ae5a2686c0d7626b4f9f4b6361f6df\",\n    \"load_kwargs\": {},\n    \"load_name\": \"info_00101.txt\",\n    \"url\": \"https://yt-project.org/data/output_00101.tar.gz\"\n  },\n  \"owls_fof_halos.tar.gz\": {\n    \"hash\": \"7ed6bd5f2eab16e8614f30676d7a0129c5fda98f994c06000e9fae8275a14c90\",\n    \"load_kwargs\": {},\n    \"load_name\": \"groups_008/group_008.0.hdf5\",\n    \"url\": \"https://yt-project.org/data/owls_fof_halos.tar.gz\"\n  },\n  \"parthenon_advection.tar.gz\": {\n    \"hash\": \"93e9e747cc9c0f230f87ea960a116c7e8c117b348f1488d72402aaaa906e4e89\",\n    \"load_kwargs\": {},\n    \"load_name\": \"advection_2d.out0.final.phdf\",\n    \"url\": \"https://yt-project.org/data/parthenon_advection.tar.gz\"\n  },\n  \"ramses_empty_record.tar.gz\": {\n    \"hash\": \"1d119d8afa144abce5b980e5ba47c45bdfe5c851de8c7a43823b62524b0bd6e0\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00003/info_00003.txt\",\n    \"url\": \"https://yt-project.org/data/ramses_empty_record.tar.gz\"\n  },\n  \"ramses_extra_fields_small.tar.gz\": {\n    \"hash\": \"8d2dde6e6150f0767ebe3ace84e2e790503154c5d90474008afdfecbac68ffed\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00001/info_00001.txt\",\n    \"url\": \"https://yt-project.org/data/ramses_extra_fields_small.tar.gz\"\n  },\n  \"ramses_mhd_128.tar.gz\": {\n    \"hash\": \"9bb48f090e2fdb795b2be49f571edecdd944c389c62283e5357a226406504d76\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00027/info_00027.txt\",\n    \"url\": \"https://yt-project.org/data/ramses_mhd_128.tar.gz\"\n  },\n  \"ramses_mhd_amr.tar.gz\": {\n    \"hash\": \"bd7e0a11f971e246a3393272be4e4b7e0d84e85351bdd2acac2bd8025cf00774\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00019/info_00019.txt\",\n    \"url\": \"https://yt-project.org/data/ramses_mhd_amr.tar.gz\"\n  },\n  \"ramses_new_format.tar.gz\": {\n    \"hash\": \"6ef426587454846456473c2026229d620e588a1fa9cbe409a0eb0a11517958c6\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00002/info_00002.txt\",\n    \"url\": \"https://yt-project.org/data/ramses_new_format.tar.gz\"\n  },\n  \"ramses_rt_00088.tar.gz\": {\n    \"hash\": \"bdeb1480aa3e669f972af982ad89cd17f45749a90e3ba74dc2370201bdda4b3c\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00088/info_00088.txt\",\n    \"url\": \"https://yt-project.org/data/ramses_rt_00088.tar.gz\"\n  },\n  \"ramses_sink_00016.tar.gz\": {\n    \"hash\": \"e28306c6e6cfc84ac0fcc209bb04b9677efd1916a2a1bcd5cb0b3f1ec90ce83a\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00016/info_00016.txt\",\n    \"url\": \"https://yt-project.org/data/ramses_sink_00016.tar.gz\"\n  },\n  \"ramses_star_formation.tar.gz\": {\n    \"hash\": \"7513bfe14e270de6f643623815d815cf9f4adf0bf7dfb33ff3bc20fcd3f965e6\",\n    \"load_kwargs\": {},\n    \"load_name\": \"output_00013/info_00013.txt\",\n    \"url\": \"https://yt-project.org/data/ramses_star_formation.tar.gz\"\n  },\n  \"rockstar_halos.tar.gz\": {\n    \"hash\": \"5090052faad3a397ab69d833a15b1779466e1893a024dca55de18bfd9aeae774\",\n    \"load_kwargs\": {},\n    \"load_name\": \"halos_0.0.bin\",\n    \"url\": \"https://yt-project.org/data/rockstar_halos.tar.gz\"\n  },\n  \"sedov_1d_sph_plt00120.tar.gz\": {\n    \"hash\": \"25e8316a64a747043c2c95bfaaf6092a5aa2748a8e31295fa86a0bb793e7c1af\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/sedov_1d_sph_plt00120.tar.gz\"\n  },\n  \"sedov_tst_0004.tar.gz\": {\n    \"hash\": \"f0c592efde0257efea77e743167439da6df44cde71a9917b305751a8818eabb5\",\n    \"load_kwargs\": {},\n    \"load_name\": \"sedov/sedov_tst_0004.h5\",\n    \"url\": \"https://yt-project.org/data/sedov_tst_0004.tar.gz\"\n  },\n  \"sizmbhloz-clref04SNth-rs9_a0.9011.tar.gz\": {\n    \"hash\": \"3fc14b8e48e9b80c4ea4b324c5815eccb4ca04fe90903ce77f1496838ad764d4\",\n    \"load_kwargs\": {},\n    \"load_name\": \"sizmbhloz-clref04SNth-rs9_a0.9011.art\",\n    \"url\": \"https://yt-project.org/data/sizmbhloz-clref04SNth-rs9_a0.9011.tar.gz\"\n  },\n  \"SmartStars.tar.gz\": {\n    \"hash\": \"990ff61aa85408d33c4c6be7181e1f64fcd738526dcf0e34be5605d8bb158605\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0100/output_0100\",\n    \"url\": \"https://yt-project.org/data/SmartStars.tar.gz\"\n  },\n  \"snapshot_010.tar.gz\": {\n    \"hash\": \"c98fa744de250d02833b643e3250dce2cf2c7a622c42c96b201024ee28582ba9\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snapshot_010\",\n    \"url\": \"https://yt-project.org/data/snapshot_010.tar.gz\"\n  },\n  \"snapshot_028_z000p000.tar.gz\": {\n    \"hash\": \"35b00963a0b34f315054a7b254854859f626155dc368ac2c83158c15ce73e6d1\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snap_028_z000p000.0.hdf5\",\n    \"url\": \"https://yt-project.org/data/snapshot_028_z000p000.tar.gz\"\n  },\n  \"snapshot_033.tar.gz\": {\n    \"hash\": \"f15788bd298c1f292548d2773f8232ad1f2e41723e210d200b479cb97c4d21bd\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snap_033.0.hdf5\",\n    \"url\": \"https://yt-project.org/data/snapshot_033.tar.gz\"\n  },\n  \"snipshot_399_z000p000.tar.gz\": {\n    \"hash\": \"a4009624898d9926a90dffa79f9eaf8fe3bde9ba5adaf40577bc36d932016672\",\n    \"load_kwargs\": {},\n    \"load_name\": \"snip_399_z000p000.0.hdf5\",\n    \"url\": \"https://yt-project.org/data/snipshot_399_z000p000.tar.gz\"\n  },\n  \"test_outputs.tar.gz\": {\n    \"hash\": \"f7f59ee7e8b9e45d42e005b8116f896ce85ea1ea4a7a1c9b44c6de5b2f09a625\",\n    \"load_kwargs\": {},\n    \"load_name\": \"RadTube/plt00500\",\n    \"url\": \"https://yt-project.org/data/test_outputs.tar.gz\"\n  },\n  \"tiny_fof_halos.tar.gz\": {\n    \"hash\": \"48555ba2c482be88d7944a79ecf27967b517366d4d1075a7a5639e6d2339d8f0\",\n    \"load_kwargs\": {},\n    \"load_name\": \"DD0045/DD0045.0.h5\",\n    \"url\": \"https://yt-project.org/data/tiny_fof_halos.tar.gz\"\n  },\n  \"xrb_spherical_smallplt00010.tar.gz\": {\n    \"hash\": \"27ede5ed03f7c89b2afac03a368beb56d5f25f0c7c95b81f14f071a54a795783\",\n    \"load_kwargs\": {},\n    \"load_name\": null,\n    \"url\": \"https://yt-project.org/data/xrb_spherical_smallplt00010.tar.gz\"\n  },\n  \"ytdata_test.tar.gz\": {\n    \"hash\": \"cafb2b06ab3190ba17909585b58a4724e25f27ac72f11d6dff1a482146eb8958\",\n    \"load_kwargs\": {},\n    \"load_name\": \"slice.h5\",\n    \"url\": \"https://yt-project.org/data/ytdata_test.tar.gz\"\n  }\n}\n"
  },
  {
    "path": "yt/startup_tasks.py",
    "content": "# This handles the command line.\n\nimport argparse\nimport os\nimport signal\nimport sys\n\nfrom yt.config import ytcfg\nfrom yt.funcs import (\n    mylog,\n    paste_traceback,\n    paste_traceback_detailed,\n    signal_ipython,\n    signal_print_traceback,\n)\nfrom yt.utilities import rpdb\n\nexe_name = os.path.basename(sys.executable)\n\n\n# At import time, we determined whether or not we're being run in parallel.\ndef turn_on_parallelism():\n    parallel_capable = False\n    try:\n        # we import this to check if mpi4py is installed\n        from mpi4py import MPI  # NOQA\n    except ImportError as e:\n        mylog.error(\n            \"Warning: Attempting to turn on parallelism, \"\n            \"but mpi4py import failed. Try pip install mpi4py.\"\n        )\n        raise e\n        # Now we have to turn on the parallelism from the perspective of the\n    # parallel_analysis_interface\n    from yt.utilities.parallel_tools.parallel_analysis_interface import (\n        enable_parallelism,\n    )\n\n    parallel_capable = enable_parallelism()\n    return parallel_capable\n\n\n# This fallback is for Paraview:\n\n# We use two signals, SIGUSR1 and SIGUSR2.  In a non-threaded environment,\n# we set up handlers to process these by printing the current stack and to\n# raise a RuntimeError.  The latter can be used, inside pdb, to catch an error\n# and then examine the current stack.\ntry:\n    signal.signal(signal.SIGUSR1, signal_print_traceback)\n    mylog.debug(\"SIGUSR1 registered for traceback printing\")\n    signal.signal(signal.SIGUSR2, signal_ipython)\n    mylog.debug(\"SIGUSR2 registered for IPython Insertion\")\nexcept (ValueError, RuntimeError, AttributeError):  # Not in main thread\n    pass\n\n\nclass SetExceptionHandling(argparse.Action):\n    def __call__(self, parser, namespace, values, option_string=None):\n        # If we recognize one of the arguments on the command line as indicating a\n        # different mechanism for handling tracebacks, we attach one of those handlers\n        # and remove the argument from sys.argv.\n        #\n        if self.dest == \"paste\":\n            sys.excepthook = paste_traceback\n            mylog.debug(\"Enabling traceback pasting\")\n        elif self.dest == \"paste-detailed\":\n            sys.excepthook = paste_traceback_detailed\n            mylog.debug(\"Enabling detailed traceback pasting\")\n        elif self.dest == \"detailed\":\n            import cgitb\n\n            cgitb.enable(format=\"text\")\n            mylog.debug(\"Enabling detailed traceback reporting\")\n        elif self.dest == \"rpdb\":\n            sys.excepthook = rpdb.rpdb_excepthook\n            mylog.debug(\"Enabling remote debugging\")\n\n\nclass SetConfigOption(argparse.Action):\n    def __call__(self, parser, namespace, values, option_string=None):\n        param, val = values.split(\"=\")\n        mylog.debug(\"Overriding config: %s = %s\", param, val)\n        ytcfg[\"yt\", param] = val\n        if param == \"log_level\":  # special case\n            mylog.setLevel(int(val))\n\n\nclass YTParser(argparse.ArgumentParser):\n    def error(self, message):\n        \"\"\"error(message: string)\n\n        Prints a help message that is more detailed than the argparse default\n        and then exits.\n        \"\"\"\n        self.print_help(sys.stderr)\n        self.exit(2, f\"{self.prog}: error: {message}\\n\")\n\n\nparser = YTParser(description=\"yt command line arguments\")\nparser.add_argument(\n    \"--config\",\n    action=SetConfigOption,\n    help=\"Set configuration option, in the form param=value\",\n)\nparser.add_argument(\n    \"--paste\",\n    action=SetExceptionHandling,\n    help=\"Paste traceback to paste.yt-project.org\",\n    nargs=0,\n)\nparser.add_argument(\n    \"--paste-detailed\",\n    action=SetExceptionHandling,\n    help=\"Paste a detailed traceback with local variables to \" + \"paste.yt-project.org\",\n    nargs=0,\n)\nparser.add_argument(\n    \"--detailed\",\n    action=SetExceptionHandling,\n    help=\"Display detailed traceback.\",\n    nargs=0,\n)\nparser.add_argument(\n    \"--rpdb\",\n    action=SetExceptionHandling,\n    help=\"Enable remote pdb interaction (for parallel debugging).\",\n    nargs=0,\n)\nparser.add_argument(\n    \"--parallel\",\n    action=\"store_true\",\n    default=False,\n    dest=\"parallel\",\n    help=\"Run in MPI-parallel mode (must be launched as an MPI task)\",\n)\nif not hasattr(sys, \"argv\") or sys.argv is None:\n    sys.argv = []\n\nunparsed_args: list[str] = []\n\nparallel_capable = False\nif not ytcfg.get(\"yt\", \"internals\", \"command_line\"):\n    opts, unparsed_args = parser.parse_known_args()\n    # THIS IS NOT SUCH A GOOD IDEA:\n    # sys.argv = [a for a in unparsed_args]\n    if opts.parallel:\n        parallel_capable = turn_on_parallelism()\n    subparsers = parser.add_subparsers(\n        title=\"subcommands\",\n        dest=\"subcommands\",\n        description=\"Valid subcommands\",\n    )\nelse:\n    subparsers = parser.add_subparsers(\n        title=\"subcommands\",\n        dest=\"subcommands\",\n        description=\"Valid subcommands\",\n    )\n\n    def print_help(*args, **kwargs):\n        parser.print_help()\n\n    help_parser = subparsers.add_parser(\"help\", help=\"Print help message\")\n    help_parser.set_defaults(func=print_help)\n\n\nif parallel_capable:\n    pass\nelif (\n    exe_name in [\"mpi4py\", \"embed_enzo\", \"python{}.{}-mpi\".format(*sys.version_info)]\n    or \"_parallel\" in dir(sys)\n    or any(\"ipengine\" in arg for arg in sys.argv)\n    or any(\"cluster-id\" in arg for arg in sys.argv)\n):\n    parallel_capable = turn_on_parallelism()\nelse:\n    parallel_capable = False\n"
  },
  {
    "path": "yt/test_fake_amr_ds_particle_positions_within_domain.py",
    "content": "from yt.testing import fake_amr_ds\n\n\ndef test_fake_amr_ds_particles_within_grid_bounds():\n    ds = fake_amr_ds(particles=100)\n\n    for g in ds.index.grids:\n        px = g[\"io\", \"particle_position_x\"]\n        le = g.LeftEdge[0].value\n        re = g.RightEdge[0].value\n        assert (px >= le).all() and (px < re).all()\n"
  },
  {
    "path": "yt/testing.py",
    "content": "import hashlib\nimport itertools as it\nimport os\nimport pickle\nimport shutil\nimport sys\nimport tempfile\nimport unittest\nfrom collections.abc import Callable, Mapping\nfrom functools import wraps\nfrom importlib.util import find_spec\nfrom shutil import which\nfrom typing import TYPE_CHECKING, TypeVar\nfrom unittest import SkipTest\n\nimport matplotlib\nimport numpy as np\nimport numpy.typing as npt\nfrom more_itertools import always_iterable\nfrom numpy.random import RandomState\nfrom unyt.exceptions import UnitOperationError\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt.config import ytcfg\nfrom yt.frontends.stream.data_structures import StreamParticlesDataset\nfrom yt.funcs import is_sequence\nfrom yt.loaders import load, load_particles\nfrom yt.units.yt_array import YTArray, YTQuantity\n\nif TYPE_CHECKING:\n    from collections.abc import Mapping\n\n    from yt._typing import AnyFieldKey\n\n\nANSWER_TEST_TAG = \"answer_test\"\n\n\n# Expose assert_true and assert_less_equal from unittest.TestCase\n# this is adopted from nose. Doing this here allows us to avoid importing\n# nose at the top level.\ndef _deprecated_assert_func(func):\n    @wraps(func)\n    def retf(*args, **kwargs):\n        issue_deprecation_warning(\n            f\"yt.testing.{func.__name__} is deprecated\",\n            since=\"4.2\",\n            stacklevel=3,\n        )\n        return func(*args, **kwargs)\n\n    return retf\n\n\nclass _Dummy(unittest.TestCase):\n    def nop(self):\n        pass\n\n\n_t = _Dummy(\"nop\")\n\nassert_true = _deprecated_assert_func(_t.assertTrue)\nassert_less_equal = _deprecated_assert_func(_t.assertLessEqual)\n\n\ndef assert_rel_equal(a1, a2, decimals, err_msg=\"\", verbose=True):\n    from numpy.testing import assert_almost_equal\n\n    # We have nan checks in here because occasionally we have fields that get\n    # weighted without non-zero weights.  I'm looking at you, particle fields!\n    if isinstance(a1, np.ndarray):\n        assert a1.size == a2.size\n        # Mask out NaNs\n        assert (np.isnan(a1) == np.isnan(a2)).all()\n        a1[np.isnan(a1)] = 1.0\n        a2[np.isnan(a2)] = 1.0\n        # Mask out 0\n        ind1 = np.array(np.abs(a1) < np.finfo(a1.dtype).eps)\n        ind2 = np.array(np.abs(a2) < np.finfo(a2.dtype).eps)\n        assert (ind1 == ind2).all()\n        a1[ind1] = 1.0\n        a2[ind2] = 1.0\n    elif np.any(np.isnan(a1)) and np.any(np.isnan(a2)):\n        return True\n    if not isinstance(a1, np.ndarray) and a1 == a2 == 0.0:\n        # NANS!\n        a1 = a2 = 1.0\n    return assert_almost_equal(\n        np.array(a1) / np.array(a2), 1.0, decimals, err_msg=err_msg, verbose=verbose\n    )\n\n\n# tested: volume integral is 1.\ndef cubicspline_python(\n    x: float | npt.NDArray[np.floating],\n) -> npt.NDArray[np.floating]:\n    \"\"\"\n    cubic spline SPH kernel function for testing against more\n    effiecient cython methods\n\n    Parameters\n    ----------\n    x:\n        impact parameter / smoothing length [dimenionless]\n\n    Returns\n    -------\n    value of the kernel function\n    \"\"\"\n    # C is 8/pi\n    _c = 8.0 / np.pi\n    x = np.asarray(x)\n    kernel = np.zeros(x.shape, dtype=x.dtype)\n    half1 = np.where(np.logical_and(x >= 0.0, x <= 0.5))\n    kernel[half1] = 1.0 - 6.0 * x[half1] ** 2 * (1.0 - x[half1])\n    half2 = np.where(np.logical_and(x > 0.5, x <= 1.0))\n    kernel[half2] = 2.0 * (1.0 - x[half2]) ** 3\n    return kernel * _c\n\n\ndef integrate_kernel(\n    kernelfunc: Callable[\n        [float | npt.NDArray[np.floating]], float | npt.NDArray[np.floating]\n    ],\n    b: float | npt.NDArray[np.floating],\n    hsml: float | npt.NDArray[np.floating],\n) -> npt.NDArray[np.floating]:\n    \"\"\"\n    integrates a kernel function over a line passing entirely\n    through it\n\n    Parameters:\n    -----------\n    kernelfunc:\n        the kernel function to integrate\n    b:\n        impact parameter\n    hsml:\n        smoothing length [same units as impact parameter]\n\n    Returns:\n    --------\n    the integral of the SPH kernel function.\n    units: 1  / units of b and hsml\n    \"\"\"\n    pre = 1.0 / hsml**2\n    x = b / hsml\n    xmax = np.sqrt(1.0 - x**2)\n    xmin = -1.0 * xmax\n    xe = np.linspace(xmin, xmax, 500)  # shape: 500, x.shape\n    xc = 0.5 * (xe[:-1, ...] + xe[1:, ...])\n    dx = np.diff(xe, axis=0)\n    spv = kernelfunc(np.sqrt(xc**2 + x**2))\n    integral = np.sum(spv * dx, axis=0)\n    return np.atleast_1d(pre * integral)\n\n\n_zeroperiods = np.array([0.0, 0.0, 0.0])\n\n\n_FloatingT = TypeVar(\"_FloatingT\", bound=np.floating)\n\n\ndef distancematrix(\n    pos3_i0: npt.NDArray[_FloatingT],\n    pos3_i1: npt.NDArray[_FloatingT],\n    periodic: tuple[bool, bool, bool] = (True,) * 3,\n    periods: npt.NDArray[_FloatingT] = _zeroperiods,\n) -> npt.NDArray[_FloatingT]:\n    \"\"\"\n    Calculates the distances between two arrays of points.\n\n    Parameters:\n    ----------\n    pos3_i0: shape (first number of points, 3)\n       positions of the first set of points. The second index is\n       for positions along the different cartesian axes\n    pos3_i1: shape (second number of points, 3)\n       as pos3_i0, but for the second set of points\n    periodic:\n       are the positions along each axis periodic (True) or not\n    periods:\n       the periods along each axis. Ignored if positions in a given\n       direction are not periodic.\n\n    Returns:\n    --------\n    a 2D-array of distances between positions `pos3_i0` (changes along\n    index 0) and `pos3_i1` (changes along index 1)\n\n    \"\"\"\n    d2 = np.zeros((len(pos3_i0), len(pos3_i1)), dtype=pos3_i0.dtype)\n    for ax in range(3):\n        # 'center on' pos3_i1\n        _d = pos3_i0[:, ax, np.newaxis] - pos3_i1[np.newaxis, :, ax]\n        if periodic[ax]:\n            _period = periods[ax]\n            _d += 0.5 * _period  # center on half box size\n            _d %= _period  # wrap coordinate to 0 -- boxsize range\n            _d -= 0.5 * _period  # center back to zero\n        d2 += _d**2\n    return np.sqrt(d2)\n\n\ndef amrspace(extent, levels=7, cells=8):\n    \"\"\"Creates two numpy arrays representing the left and right bounds of\n    an AMR grid as well as an array for the AMR level of each cell.\n\n    Parameters\n    ----------\n    extent : array-like\n        This a sequence of length 2*ndims that is the bounds of each dimension.\n        For example, the 2D unit square would be given by [0.0, 1.0, 0.0, 1.0].\n        A 3D cylindrical grid may look like [0.0, 2.0, -1.0, 1.0, 0.0, 2*np.pi].\n    levels : int or sequence of ints, optional\n        This is the number of AMR refinement levels.  If given as a sequence (of\n        length ndims), then each dimension will be refined down to this level.\n        All values in this array must be the same or zero.  A zero valued dimension\n        indicates that this dim should not be refined.  Taking the 3D cylindrical\n        example above if we don't want refine theta but want r and z at 5 we would\n        set levels=(5, 5, 0).\n    cells : int, optional\n        This is the number of cells per refinement level.\n\n    Returns\n    -------\n    left : float ndarray, shape=(npoints, ndims)\n        The left AMR grid points.\n    right : float ndarray, shape=(npoints, ndims)\n        The right AMR grid points.\n    level : int ndarray, shape=(npoints,)\n        The AMR level for each point.\n\n    Examples\n    --------\n    >>> l, r, lvl = amrspace([0.0, 2.0, 1.0, 2.0, 0.0, 3.14], levels=(3, 3, 0), cells=2)\n    >>> print(l)\n    [[ 0.     1.     0.   ]\n     [ 0.25   1.     0.   ]\n     [ 0.     1.125  0.   ]\n     [ 0.25   1.125  0.   ]\n     [ 0.5    1.     0.   ]\n     [ 0.     1.25   0.   ]\n     [ 0.5    1.25   0.   ]\n     [ 1.     1.     0.   ]\n     [ 0.     1.5    0.   ]\n     [ 1.     1.5    0.   ]]\n\n    \"\"\"\n    extent = np.asarray(extent, dtype=\"f8\")\n    dextent = extent[1::2] - extent[::2]\n    ndims = len(dextent)\n\n    if isinstance(levels, int):\n        minlvl = maxlvl = levels\n        levels = np.array([levels] * ndims, dtype=\"int32\")\n    else:\n        levels = np.asarray(levels, dtype=\"int32\")\n        minlvl = levels.min()\n        maxlvl = levels.max()\n        if minlvl != maxlvl and (minlvl != 0 or {minlvl, maxlvl} != set(levels)):\n            raise ValueError(\"all levels must have the same value or zero.\")\n    dims_zero = levels == 0\n    dims_nonzero = ~dims_zero\n    ndims_nonzero = dims_nonzero.sum()\n\n    npoints = (cells**ndims_nonzero - 1) * maxlvl + 1\n    left = np.empty((npoints, ndims), dtype=\"float64\")\n    right = np.empty((npoints, ndims), dtype=\"float64\")\n    level = np.empty(npoints, dtype=\"int32\")\n\n    # fill zero dims\n    left[:, dims_zero] = extent[::2][dims_zero]\n    right[:, dims_zero] = extent[1::2][dims_zero]\n\n    # fill non-zero dims\n    dcell = 1.0 / cells\n    left_slice = tuple(\n        (\n            slice(extent[2 * n], extent[2 * n + 1], extent[2 * n + 1])\n            if dims_zero[n]\n            else slice(0.0, 1.0, dcell)\n        )\n        for n in range(ndims)\n    )\n    right_slice = tuple(\n        (\n            slice(extent[2 * n + 1], extent[2 * n], -extent[2 * n + 1])\n            if dims_zero[n]\n            else slice(dcell, 1.0 + dcell, dcell)\n        )\n        for n in range(ndims)\n    )\n    left_norm_grid = np.reshape(np.mgrid[left_slice].T.flat[ndims:], (-1, ndims))\n    lng_zero = left_norm_grid[:, dims_zero]\n    lng_nonzero = left_norm_grid[:, dims_nonzero]\n\n    right_norm_grid = np.reshape(np.mgrid[right_slice].T.flat[ndims:], (-1, ndims))\n    rng_zero = right_norm_grid[:, dims_zero]\n    rng_nonzero = right_norm_grid[:, dims_nonzero]\n\n    level[0] = maxlvl\n    left[0, :] = extent[::2]\n    right[0, dims_zero] = extent[1::2][dims_zero]\n    right[0, dims_nonzero] = (dcell**maxlvl) * dextent[dims_nonzero] + extent[::2][\n        dims_nonzero\n    ]\n    for i, lvl in enumerate(range(maxlvl, 0, -1)):\n        start = (cells**ndims_nonzero - 1) * i + 1\n        stop = (cells**ndims_nonzero - 1) * (i + 1) + 1\n        dsize = dcell ** (lvl - 1) * dextent[dims_nonzero]\n        level[start:stop] = lvl\n        left[start:stop, dims_zero] = lng_zero\n        left[start:stop, dims_nonzero] = lng_nonzero * dsize + extent[::2][dims_nonzero]\n        right[start:stop, dims_zero] = rng_zero\n        right[start:stop, dims_nonzero] = (\n            rng_nonzero * dsize + extent[::2][dims_nonzero]\n        )\n\n    return left, right, level\n\n\ndef _check_field_unit_args_helper(args: dict, default_args: dict):\n    values = list(args.values())\n    keys = list(args.keys())\n    if all(v is None for v in values):\n        for key in keys:\n            args[key] = default_args[key]\n    elif None in values:\n        raise ValueError(\n            \"Error in creating a fake dataset:\"\n            f\" either all or none of the following arguments need to specified: {keys}.\"\n        )\n    elif any(len(v) != len(values[0]) for v in values):\n        raise ValueError(\n            \"Error in creating a fake dataset:\"\n            f\" all the following arguments must have the same length: {keys}.\"\n        )\n    return list(args.values())\n\n\n_fake_random_ds_default_fields = (\"density\", \"velocity_x\", \"velocity_y\", \"velocity_z\")\n_fake_random_ds_default_units = (\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\")\n_fake_random_ds_default_negative = (False, False, False, False)\n\n\ndef fake_random_ds(\n    ndims,\n    peak_value=1.0,\n    fields=None,\n    units=None,\n    particle_fields=None,\n    particle_field_units=None,\n    negative=False,\n    nprocs=1,\n    particles=0,\n    length_unit=1.0,\n    unit_system=\"cgs\",\n    bbox=None,\n    default_species_fields=None,\n):\n    from yt.loaders import load_uniform_grid\n\n    prng = RandomState(0x4D3D3D3)\n    if not is_sequence(ndims):\n        ndims = [ndims, ndims, ndims]\n    else:\n        assert len(ndims) == 3\n    if not is_sequence(negative):\n        if fields:\n            negative = [negative for f in fields]\n        else:\n            negative = None\n\n    fields, units, negative = _check_field_unit_args_helper(\n        {\n            \"fields\": fields,\n            \"units\": units,\n            \"negative\": negative,\n        },\n        {\n            \"fields\": _fake_random_ds_default_fields,\n            \"units\": _fake_random_ds_default_units,\n            \"negative\": _fake_random_ds_default_negative,\n        },\n    )\n\n    offsets = []\n    for n in negative:\n        if n:\n            offsets.append(0.5)\n        else:\n            offsets.append(0.0)\n    data = {}\n    for field, offset, u in zip(fields, offsets, units, strict=True):\n        v = (prng.random_sample(ndims) - offset) * peak_value\n        if field[0] == \"all\":\n            v = v.ravel()\n        data[field] = (v, u)\n    if particles:\n        if particle_fields is not None:\n            for field, unit in zip(particle_fields, particle_field_units, strict=True):\n                if field in (\"particle_position\", \"particle_velocity\"):\n                    data[\"io\", field] = (prng.random_sample((int(particles), 3)), unit)\n                else:\n                    data[\"io\", field] = (prng.random_sample(size=int(particles)), unit)\n        else:\n            for f in (f\"particle_position_{ax}\" for ax in \"xyz\"):\n                data[\"io\", f] = (prng.random_sample(size=particles), \"code_length\")\n            for f in (f\"particle_velocity_{ax}\" for ax in \"xyz\"):\n                data[\"io\", f] = (prng.random_sample(size=particles) - 0.5, \"cm/s\")\n            data[\"io\", \"particle_mass\"] = (prng.random_sample(particles), \"g\")\n    ug = load_uniform_grid(\n        data,\n        ndims,\n        length_unit=length_unit,\n        nprocs=nprocs,\n        unit_system=unit_system,\n        bbox=bbox,\n        default_species_fields=default_species_fields,\n    )\n    return ug\n\n\n_geom_transforms = {\n    # These are the bounds we want.  Cartesian we just assume goes 0 .. 1.\n    \"cartesian\": ((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)),\n    \"spherical\": ((0.0, 0.0, 0.0), (1.0, np.pi, 2 * np.pi)),\n    \"cylindrical\": ((0.0, 0.0, 0.0), (1.0, 1.0, 2.0 * np.pi)),  # rzt\n    \"polar\": ((0.0, 0.0, 0.0), (1.0, 2.0 * np.pi, 1.0)),  # rtz\n    \"geographic\": ((-90.0, -180.0, 0.0), (90.0, 180.0, 1000.0)),  # latlonalt\n    \"internal_geographic\": ((-90.0, -180.0, 0.0), (90.0, 180.0, 1000.0)),  # latlondep\n    \"spectral_cube\": ((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)),\n}\n\n\n_fake_amr_ds_default_fields = (\"Density\",)\n_fake_amr_ds_default_units = (\"g/cm**3\",)\n\n\ndef fake_amr_ds(\n    fields=None,\n    units=None,\n    geometry=\"cartesian\",\n    particles=0,\n    length_unit=None,\n    *,\n    domain_left_edge=None,\n    domain_right_edge=None,\n):\n    from yt.loaders import load_amr_grids\n\n    fields, units = _check_field_unit_args_helper(\n        {\n            \"fields\": fields,\n            \"units\": units,\n        },\n        {\n            \"fields\": _fake_amr_ds_default_fields,\n            \"units\": _fake_amr_ds_default_units,\n        },\n    )\n\n    prng = RandomState(0x4D3D3D3)\n    default_LE, default_RE = _geom_transforms[geometry]\n\n    LE = np.array(domain_left_edge or default_LE, dtype=\"float64\")\n    RE = np.array(domain_right_edge or default_RE, dtype=\"float64\")\n    data = []\n    for gspec in _amr_grid_index:\n        level, left_edge, right_edge, dims = gspec\n        left_edge = left_edge * (RE - LE) + LE\n        right_edge = right_edge * (RE - LE) + LE\n        gdata = {\n            \"level\": level,\n            \"left_edge\": left_edge,\n            \"right_edge\": right_edge,\n            \"dimensions\": dims,\n        }\n        for f, u in zip(fields, units, strict=True):\n            gdata[f] = (prng.random_sample(dims), u)\n        if particles:\n            for i, f in enumerate(f\"particle_position_{ax}\" for ax in \"xyz\"):\n                pdata = prng.random_sample(particles)\n                pdata *= right_edge[i] - left_edge[i]\n                pdata += left_edge[i]\n                gdata[\"io\", f] = (pdata, \"code_length\")\n            for f in (f\"particle_velocity_{ax}\" for ax in \"xyz\"):\n                gdata[\"io\", f] = (prng.random_sample(particles) - 0.5, \"cm/s\")\n            gdata[\"io\", \"particle_mass\"] = (prng.random_sample(particles), \"g\")\n        data.append(gdata)\n    bbox = np.array([LE, RE]).T\n    return load_amr_grids(\n        data, [32, 32, 32], geometry=geometry, bbox=bbox, length_unit=length_unit\n    )\n\n\n_fake_particle_ds_default_fields = (\n    \"particle_position_x\",\n    \"particle_position_y\",\n    \"particle_position_z\",\n    \"particle_mass\",\n    \"particle_velocity_x\",\n    \"particle_velocity_y\",\n    \"particle_velocity_z\",\n)\n_fake_particle_ds_default_units = (\"cm\", \"cm\", \"cm\", \"g\", \"cm/s\", \"cm/s\", \"cm/s\")\n_fake_particle_ds_default_negative = (False, False, False, False, True, True, True)\n\n\ndef fake_particle_ds(\n    fields=None,\n    units=None,\n    negative=None,\n    npart=16**3,\n    length_unit=1.0,\n    data=None,\n):\n    from yt.loaders import load_particles\n\n    prng = RandomState(0x4D3D3D3)\n    if negative is not None and not is_sequence(negative):\n        negative = [negative for f in fields]\n\n    fields, units, negative = _check_field_unit_args_helper(\n        {\n            \"fields\": fields,\n            \"units\": units,\n            \"negative\": negative,\n        },\n        {\n            \"fields\": _fake_particle_ds_default_fields,\n            \"units\": _fake_particle_ds_default_units,\n            \"negative\": _fake_particle_ds_default_negative,\n        },\n    )\n\n    offsets = []\n    for n in negative:\n        if n:\n            offsets.append(0.5)\n        else:\n            offsets.append(0.0)\n    data = data if data else {}\n    for field, offset, u in zip(fields, offsets, units, strict=True):\n        if field in data:\n            v = data[field]\n            continue\n        if \"position\" in field:\n            v = prng.normal(loc=0.5, scale=0.25, size=npart)\n            np.clip(v, 0.0, 1.0, v)\n        v = prng.random_sample(npart) - offset\n        data[field] = (v, u)\n    bbox = np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]])\n    ds = load_particles(data, 1.0, bbox=bbox)\n    return ds\n\n\ndef fake_tetrahedral_ds():\n    from yt.frontends.stream.sample_data.tetrahedral_mesh import (\n        _connectivity,\n        _coordinates,\n    )\n    from yt.loaders import load_unstructured_mesh\n\n    prng = RandomState(0x4D3D3D3)\n\n    # the distance from the origin\n    node_data = {}\n    dist = np.sum(_coordinates**2, 1)\n    node_data[\"connect1\", \"test\"] = dist[_connectivity]\n\n    # each element gets a random number\n    elem_data = {}\n    elem_data[\"connect1\", \"elem\"] = prng.rand(_connectivity.shape[0])\n\n    ds = load_unstructured_mesh(\n        _connectivity, _coordinates, node_data=node_data, elem_data=elem_data\n    )\n    return ds\n\n\ndef fake_hexahedral_ds(fields=None):\n    from yt.frontends.stream.sample_data.hexahedral_mesh import (\n        _connectivity,\n        _coordinates,\n    )\n    from yt.loaders import load_unstructured_mesh\n\n    prng = RandomState(0x4D3D3D3)\n    # the distance from the origin\n    node_data = {}\n    dist = np.sum(_coordinates**2, 1)\n    node_data[\"connect1\", \"test\"] = dist[_connectivity - 1]\n\n    for field in always_iterable(fields):\n        node_data[\"connect1\", field] = dist[_connectivity - 1]\n\n    # each element gets a random number\n    elem_data = {}\n    elem_data[\"connect1\", \"elem\"] = prng.rand(_connectivity.shape[0])\n\n    ds = load_unstructured_mesh(\n        _connectivity - 1, _coordinates, node_data=node_data, elem_data=elem_data\n    )\n    return ds\n\n\ndef small_fake_hexahedral_ds():\n    from yt.loaders import load_unstructured_mesh\n\n    _coordinates = np.array(\n        [\n            [-1.0, -1.0, -1.0],\n            [0.0, -1.0, -1.0],\n            [-0.0, 0.0, -1.0],\n            [-1.0, -0.0, -1.0],\n            [-1.0, -1.0, 0.0],\n            [-0.0, -1.0, 0.0],\n            [-0.0, 0.0, -0.0],\n            [-1.0, 0.0, -0.0],\n        ]\n    )\n    _connectivity = np.array([[1, 2, 3, 4, 5, 6, 7, 8]])\n\n    # the distance from the origin\n    node_data = {}\n    dist = np.sum(_coordinates**2, 1)\n    node_data[\"connect1\", \"test\"] = dist[_connectivity - 1]\n\n    ds = load_unstructured_mesh(_connectivity - 1, _coordinates, node_data=node_data)\n    return ds\n\n\ndef fake_stretched_ds(N=16):\n    from yt.loaders import load_uniform_grid\n\n    rng = np.random.default_rng(seed=0x4D3D3D3)\n\n    data = {\"density\": rng.random((N, N, N))}\n\n    cell_widths = []\n    for _ in range(3):\n        cw = rng.random(N)\n        cw /= cw.sum()\n        cell_widths.append(cw)\n    return load_uniform_grid(\n        data,\n        [N, N, N],\n        bbox=np.array([[0.0, 1.0], [0.0, 1.0], [0.0, 1.0]]),\n        cell_widths=cell_widths,\n    )\n\n\ndef fake_vr_orientation_test_ds(N=96, scale=1):\n    \"\"\"\n    create a toy dataset that puts a sphere at (0,0,0), a single cube\n    on +x, two cubes on +y, and three cubes on +z in a domain from\n    [-1*scale,1*scale]**3.  The lower planes\n    (x = -1*scale, y = -1*scale, z = -1*scale) are also given non-zero\n    values.\n\n    This dataset allows you to easily explore orientations and\n    handiness in VR and other renderings\n\n    Parameters\n    ----------\n\n    N : integer\n       The number of cells along each direction\n\n    scale : float\n       A spatial scale, the domain boundaries will be multiplied by scale to\n       test datasets that have spatial different scales (e.g. data in CGS units)\n\n    \"\"\"\n    from yt.loaders import load_uniform_grid\n\n    xmin = ymin = zmin = -1.0 * scale\n    xmax = ymax = zmax = 1.0 * scale\n\n    dcoord = (xmax - xmin) / N\n\n    arr = np.zeros((N, N, N), dtype=np.float64)\n    arr[:, :, :] = 1.0e-4\n\n    bbox = np.array([[xmin, xmax], [ymin, ymax], [zmin, zmax]])\n\n    # coordinates -- in the notation data[i, j, k]\n    x = (np.arange(N) + 0.5) * dcoord + xmin\n    y = (np.arange(N) + 0.5) * dcoord + ymin\n    z = (np.arange(N) + 0.5) * dcoord + zmin\n\n    x3d, y3d, z3d = np.meshgrid(x, y, z, indexing=\"ij\")\n\n    # sphere at the origin\n    c = np.array([0.5 * (xmin + xmax), 0.5 * (ymin + ymax), 0.5 * (zmin + zmax)])\n    r = np.sqrt((x3d - c[0]) ** 2 + (y3d - c[1]) ** 2 + (z3d - c[2]) ** 2)\n    arr[r < 0.05] = 1.0\n\n    arr[abs(x3d - xmin) < 2 * dcoord] = 0.3\n    arr[abs(y3d - ymin) < 2 * dcoord] = 0.3\n    arr[abs(z3d - zmin) < 2 * dcoord] = 0.3\n\n    # single cube on +x\n    xc = 0.75 * scale\n    dx = 0.05 * scale\n    idx = np.logical_and(\n        np.logical_and(x3d > xc - dx, x3d < xc + dx),\n        np.logical_and(\n            np.logical_and(y3d > -dx, y3d < dx), np.logical_and(z3d > -dx, z3d < dx)\n        ),\n    )\n    arr[idx] = 1.0\n\n    # two cubes on +y\n    dy = 0.05 * scale\n    for yc in [0.65 * scale, 0.85 * scale]:\n        idx = np.logical_and(\n            np.logical_and(y3d > yc - dy, y3d < yc + dy),\n            np.logical_and(\n                np.logical_and(x3d > -dy, x3d < dy), np.logical_and(z3d > -dy, z3d < dy)\n            ),\n        )\n        arr[idx] = 0.8\n\n    # three cubes on +z\n    dz = 0.05 * scale\n    for zc in [0.5 * scale, 0.7 * scale, 0.9 * scale]:\n        idx = np.logical_and(\n            np.logical_and(z3d > zc - dz, z3d < zc + dz),\n            np.logical_and(\n                np.logical_and(x3d > -dz, x3d < dz), np.logical_and(y3d > -dz, y3d < dz)\n            ),\n        )\n        arr[idx] = 0.6\n\n    data = {\"density\": (arr, \"g/cm**3\")}\n    ds = load_uniform_grid(data, arr.shape, bbox=bbox)\n    return ds\n\n\ndef fake_sph_orientation_ds():\n    \"\"\"Returns an in-memory SPH dataset useful for testing\n\n    This dataset should have one particle at the origin, one more particle\n    along the x axis, two along y, and three along z. All particles will\n    have non-overlapping smoothing regions with a radius of 0.25, masses of 1,\n    and densities of 1, and zero velocity.\n    \"\"\"\n    from yt import load_particles\n\n    npart = 7\n\n    # one particle at the origin, one particle along x-axis, two along y,\n    # three along z\n    data = {\n        \"particle_position_x\": (np.array([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]), \"cm\"),\n        \"particle_position_y\": (np.array([0.0, 0.0, 1.0, 2.0, 0.0, 0.0, 0.0]), \"cm\"),\n        \"particle_position_z\": (np.array([0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0]), \"cm\"),\n        \"particle_mass\": (np.ones(npart), \"g\"),\n        \"particle_velocity_x\": (np.zeros(npart), \"cm/s\"),\n        \"particle_velocity_y\": (np.zeros(npart), \"cm/s\"),\n        \"particle_velocity_z\": (np.zeros(npart), \"cm/s\"),\n        \"smoothing_length\": (0.25 * np.ones(npart), \"cm\"),\n        \"density\": (np.ones(npart), \"g/cm**3\"),\n        \"temperature\": (np.ones(npart), \"K\"),\n    }\n\n    bbox = np.array([[-4, 4], [-4, 4], [-4, 4]])\n\n    return load_particles(data=data, length_unit=1.0, bbox=bbox)\n\n\ndef fake_sph_grid_ds(hsml_factor=1.0):\n    \"\"\"Returns an in-memory SPH dataset useful for testing\n\n    This dataset should have 27 particles with the particles arranged uniformly\n    on a 3D grid. The bottom left corner is (0.5,0.5,0.5) and the top right\n    corner is (2.5,2.5,2.5). All particles will have non-overlapping smoothing\n    regions with a radius of 0.05, masses of 1, and densities of 1, and zero\n    velocity.\n    \"\"\"\n    from yt import load_particles\n\n    npart = 27\n\n    x = np.empty(npart)\n    y = np.empty(npart)\n    z = np.empty(npart)\n\n    tot = 0\n    for i in range(0, 3):\n        for j in range(0, 3):\n            for k in range(0, 3):\n                x[tot] = i + 0.5\n                y[tot] = j + 0.5\n                z[tot] = k + 0.5\n                tot += 1\n\n    data = {\n        \"particle_position_x\": (x, \"cm\"),\n        \"particle_position_y\": (y, \"cm\"),\n        \"particle_position_z\": (z, \"cm\"),\n        \"particle_mass\": (np.ones(npart), \"g\"),\n        \"particle_velocity_x\": (np.zeros(npart), \"cm/s\"),\n        \"particle_velocity_y\": (np.zeros(npart), \"cm/s\"),\n        \"particle_velocity_z\": (np.zeros(npart), \"cm/s\"),\n        \"smoothing_length\": (0.05 * np.ones(npart) * hsml_factor, \"cm\"),\n        \"density\": (np.ones(npart), \"g/cm**3\"),\n        \"temperature\": (np.ones(npart), \"K\"),\n    }\n\n    bbox = np.array([[0, 3], [0, 3], [0, 3]])\n\n    return load_particles(data=data, length_unit=1.0, bbox=bbox)\n\n\ndef constantmass(i: int, j: int, k: int) -> float:\n    return 1.0\n\n\n_xhat = np.array([1, 0, 0])\n_yhat = np.array([0, 1, 0])\n_zhat = np.array([0, 0, 1])\n_floathalves = 0.5 * np.ones((3,), dtype=np.float64)\n\n\ndef fake_sph_flexible_grid_ds(\n    hsml_factor: float = 1.0,\n    nperside: int = 3,\n    periodic: bool = True,\n    e1hat: np.ndarray = _xhat,\n    e2hat: np.ndarray = _yhat,\n    e3hat: np.ndarray = _zhat,\n    offsets: np.ndarray = _floathalves,\n    massgenerator: Callable[[int, int, int], float] = constantmass,\n    unitrho: float = 1.0,\n    bbox: np.ndarray | None = None,\n    recenter: np.ndarray | None = None,\n) -> StreamParticlesDataset:\n    \"\"\"Returns an in-memory SPH dataset useful for testing\n\n    Parameters:\n    -----------\n    hsml_factor:\n        all particles have smoothing lengths of `hsml_factor` * 0.5\n    nperside:\n        the dataset will have `nperside`**3 particles, arranged\n        uniformly on a 3D grid\n    periodic:\n        are the positions taken to be periodic? (applies to all\n        coordinate axes)\n    e1hat: shape (3,)\n        the first basis vector defining the 3D grid. If the basis\n        vectors are not normalized to 1 or not orthogonal, the spacing\n        or overlap between SPH particles will be affected, but this is\n        allowed.\n    e2hat: shape (3,)\n        the second basis vector defining the 3D grid. (See `e1hat`.)\n    e3hat: shape (3,)\n        the third basis vector defining the 3D grid. (See `e1hat`.)\n    offsets: shape (3,)\n        the the zero point of the 3D grid along each coordinate axis\n    massgenerator:\n        a function assigning a mass to each particle, as a function of\n        the e[1-3]hat indices, in order\n    unitrho:\n        defines the density for a particle with mass 1 ('g'), and the\n        standard (uniform) grid `hsml_factor`.\n    bbox: if np.ndarray, shape is (2, 3)\n        the assumed enclosing volume of the particles. Should enclose\n        all the coordinate values. If not specified, a bbox is defined\n        which encloses all coordinates values with a margin. If\n        `periodic`, the size of the `bbox` along each coordinate is\n        also the period along that axis.\n    recenter:\n        if not `None`, after generating the grid, the positions are\n        periodically shifted to move the old center to this positions.\n        Useful for testing periodicity handling.\n        This shift is relative to the halfway positions of the bbox\n        edges.\n\n    Returns:\n    --------\n    A `StreamParticlesDataset` object with particle positions, masses,\n    velocities (zero), smoothing lengths, and densities specified.\n    Values are in cgs units.\n    \"\"\"\n\n    npart = nperside**3\n\n    pos = np.empty((npart, 3), dtype=np.float64)\n    mass = np.empty((npart,), dtype=np.float64)\n    for i in range(0, nperside):\n        for j in range(0, nperside):\n            for k in range(0, nperside):\n                _pos = (\n                    (offsets[0] + i) * e1hat\n                    + (offsets[1] + j) * e2hat\n                    + (offsets[2] + k) * e3hat\n                )\n                ind = nperside**2 * i + nperside * j + k\n                pos[ind, :] = _pos\n                mass[ind] = massgenerator(i, j, k)\n    rho = unitrho * mass\n\n    if bbox is None:\n        eps = 1e-3\n        margin = (1.0 + eps) * hsml_factor\n        bbox = np.array(\n            [\n                [np.min(pos[:, 0]) - margin, np.max(pos[:, 0]) + margin],\n                [np.min(pos[:, 1]) - margin, np.max(pos[:, 1]) + margin],\n                [np.min(pos[:, 2]) - margin, np.max(pos[:, 2]) + margin],\n            ]\n        )\n\n    if recenter is not None:\n        periods = bbox[:, 1] - bbox[:, 0]\n        # old center -> new position\n        pos += -0.5 * periods[np.newaxis, :] + recenter[np.newaxis, :]\n        # wrap coordinates -> all in [0, boxsize) range\n        pos %= periods[np.newaxis, :]\n        # shift back to original bbox range\n        pos += (bbox[:, 0])[np.newaxis, :]\n    if not periodic:\n        # remove points outside bbox to avoid errors:\n        okinds = np.ones(len(mass), dtype=bool)\n        for ax in [0, 1, 2]:\n            okinds &= pos[:, ax] < bbox[ax, 1]\n            okinds &= pos[:, ax] >= bbox[ax, 0]\n        npart = sum(okinds)\n    else:\n        okinds = np.ones((npart,), dtype=bool)\n\n    data: Mapping[AnyFieldKey, tuple[np.ndarray, str]] = {\n        \"particle_position_x\": (np.copy(pos[okinds, 0]), \"cm\"),\n        \"particle_position_y\": (np.copy(pos[okinds, 1]), \"cm\"),\n        \"particle_position_z\": (np.copy(pos[okinds, 2]), \"cm\"),\n        \"particle_mass\": (np.copy(mass[okinds]), \"g\"),\n        \"particle_velocity_x\": (np.zeros(npart), \"cm/s\"),\n        \"particle_velocity_y\": (np.zeros(npart), \"cm/s\"),\n        \"particle_velocity_z\": (np.zeros(npart), \"cm/s\"),\n        \"smoothing_length\": (np.ones(npart) * 0.5 * hsml_factor, \"cm\"),\n        \"density\": (np.copy(rho[okinds]), \"g/cm**3\"),\n    }\n\n    ds = load_particles(\n        data=data,\n        bbox=bbox,\n        periodicity=(periodic,) * 3,\n        length_unit=1.0,\n        mass_unit=1.0,\n        time_unit=1.0,\n        velocity_unit=1.0,\n    )\n    ds.kernel_name = \"cubic\"\n    return ds\n\n\ndef fake_random_sph_ds(\n    npart: int,\n    bbox: np.ndarray,\n    periodic: bool | tuple[bool, bool, bool] = True,\n    massrange: tuple[float, float] = (0.5, 2.0),\n    hsmlrange: tuple[float, float] = (0.5, 2.0),\n    unitrho: float = 1.0,\n) -> StreamParticlesDataset:\n    \"\"\"Returns an in-memory SPH dataset useful for testing\n\n    Parameters:\n    -----------\n    npart:\n        number of particles to generate\n    bbox: shape: (3, 2), units: \"cm\"\n        the assumed enclosing volume of the particles. Particle\n        positions are drawn uniformly from these ranges.\n    periodic:\n        are the positions taken to be periodic? If a single value,\n        that value is applied to all axes\n    massrange:\n        particle masses are drawn uniformly from this range (unit: \"g\")\n    hsmlrange: units: \"cm\"\n        particle smoothing lengths are drawn uniformly from this range\n    unitrho:\n        defines the density for a particle with mass 1 (\"g\"), and\n        smoothing length 1 (\"cm\").\n\n    Returns:\n    --------\n    A `StreamParticlesDataset` object with particle positions, masses,\n    velocities (zero), smoothing lengths, and densities specified.\n    Values are in cgs units.\n    \"\"\"\n\n    if not hasattr(periodic, \"__len__\"):\n        periodic = (periodic,) * 3\n    gen = np.random.default_rng(seed=0)\n\n    posx = gen.uniform(low=bbox[0][0], high=bbox[0][1], size=npart)\n    posy = gen.uniform(low=bbox[1][0], high=bbox[1][1], size=npart)\n    posz = gen.uniform(low=bbox[2][0], high=bbox[2][1], size=npart)\n    mass = gen.uniform(low=massrange[0], high=massrange[1], size=npart)\n    hsml = gen.uniform(low=hsmlrange[0], high=hsmlrange[1], size=npart)\n    dens = mass / hsml**3 * unitrho\n\n    data: Mapping[AnyFieldKey, tuple[np.ndarray, str]] = {\n        \"particle_position_x\": (posx, \"cm\"),\n        \"particle_position_y\": (posy, \"cm\"),\n        \"particle_position_z\": (posz, \"cm\"),\n        \"particle_mass\": (mass, \"g\"),\n        \"particle_velocity_x\": (np.zeros(npart), \"cm/s\"),\n        \"particle_velocity_y\": (np.zeros(npart), \"cm/s\"),\n        \"particle_velocity_z\": (np.zeros(npart), \"cm/s\"),\n        \"smoothing_length\": (hsml, \"cm\"),\n        \"density\": (dens, \"g/cm**3\"),\n    }\n\n    ds = load_particles(\n        data=data,\n        bbox=bbox,\n        periodicity=periodic,\n        length_unit=1.0,\n        mass_unit=1.0,\n        time_unit=1.0,\n        velocity_unit=1.0,\n    )\n    ds.kernel_name = \"cubic\"\n    return ds\n\n\ndef construct_octree_mask(prng=RandomState(0x1D3D3D3), refined=None):  # noqa B008\n    # Implementation taken from url:\n    # http://docs.hyperion-rt.org/en/stable/advanced/indepth_oct.html\n\n    if refined in (None, True):\n        refined = [True]\n    if not refined:\n        refined = [False]\n        return refined\n\n    # Loop over subcells\n    for _ in range(8):\n        # Insert criterion for whether cell should be sub-divided. Here we\n        # just use a random number to demonstrate.\n        divide = prng.random_sample() < 0.12\n\n        # Append boolean to overall list\n        refined.append(divide)\n\n        # If the cell is sub-divided, recursively divide it further\n        if divide:\n            construct_octree_mask(prng, refined)\n    return refined\n\n\ndef fake_octree_ds(\n    prng=RandomState(0x4D3D3D3),  # noqa B008\n    refined=None,\n    quantities=None,\n    bbox=None,\n    sim_time=0.0,\n    length_unit=None,\n    mass_unit=None,\n    time_unit=None,\n    velocity_unit=None,\n    magnetic_unit=None,\n    periodicity=(True, True, True),\n    num_zones=2,\n    partial_coverage=1,\n    unit_system=\"cgs\",\n):\n    from yt.loaders import load_octree\n\n    octree_mask = np.asarray(\n        construct_octree_mask(prng=prng, refined=refined), dtype=np.uint8\n    )\n    particles = np.sum(np.invert(octree_mask))\n\n    if quantities is None:\n        quantities = {}\n        # Try both callable and arrays\n        quantities[\"gas\", \"density\"] = prng.random_sample((particles, 1))\n        quantities[\"gas\", \"velocity_x\"] = prng.random_sample((particles, 1))\n        quantities[\"gas\", \"velocity_y\"] = prng.random_sample((particles, 1))\n        quantities[\"gas\", \"velocity_z\"] = lambda: prng.random_sample((particles, 1))\n\n    ds = load_octree(\n        octree_mask=octree_mask,\n        data=quantities,\n        bbox=bbox,\n        sim_time=sim_time,\n        length_unit=length_unit,\n        mass_unit=mass_unit,\n        time_unit=time_unit,\n        velocity_unit=velocity_unit,\n        magnetic_unit=magnetic_unit,\n        periodicity=periodicity,\n        partial_coverage=partial_coverage,\n        num_zones=num_zones,\n        unit_system=unit_system,\n    )\n    return ds\n\n\ndef add_noise_fields(ds):\n    \"\"\"Add 4 classes of noise fields to a dataset\"\"\"\n    prng = RandomState(0x4D3D3D3)\n\n    def _binary_noise(data):\n        \"\"\"random binary data\"\"\"\n        return prng.randint(low=0, high=2, size=data.size).astype(\"float64\")\n\n    def _positive_noise(data):\n        \"\"\"random strictly positive data\"\"\"\n        return prng.random_sample(data.size) + 1e-16\n\n    def _negative_noise(data):\n        \"\"\"random negative data\"\"\"\n        return -prng.random_sample(data.size)\n\n    def _even_noise(data):\n        \"\"\"random data with mixed signs\"\"\"\n        return 2 * prng.random_sample(data.size) - 1\n\n    ds.add_field((\"gas\", \"noise0\"), _binary_noise, sampling_type=\"cell\")\n    ds.add_field((\"gas\", \"noise1\"), _positive_noise, sampling_type=\"cell\")\n    ds.add_field((\"gas\", \"noise2\"), _negative_noise, sampling_type=\"cell\")\n    ds.add_field((\"gas\", \"noise3\"), _even_noise, sampling_type=\"cell\")\n\n\ndef expand_keywords(keywords, full=False):\n    \"\"\"\n    expand_keywords is a means for testing all possible keyword\n    arguments in the nosetests.  Simply pass it a dictionary of all the\n    keyword arguments and all of the values for these arguments that you\n    want to test.\n\n    It will return a list of kwargs dicts containing combinations of\n    the various kwarg values you passed it.  These can then be passed\n    to the appropriate function in nosetests.\n\n    If full=True, then every possible combination of keywords is produced,\n    otherwise, every keyword option is included at least once in the output\n    list.  Be careful, by using full=True, you may be in for an exponentially\n    larger number of tests!\n\n    Parameters\n    ----------\n\n    keywords : dict\n        a dictionary where the keys are the keywords for the function,\n        and the values of each key are the possible values that this key\n        can take in the function\n\n    full : bool\n        if set to True, every possible combination of given keywords is\n        returned\n\n    Returns\n    -------\n\n    array of dicts\n        An array of dictionaries to be individually passed to the appropriate\n        function matching these kwargs.\n\n    Examples\n    --------\n\n    >>> keywords = {}\n    >>> keywords[\"dpi\"] = (50, 100, 200)\n    >>> keywords[\"cmap\"] = (\"cmyt.arbre\", \"cmyt.kelp\")\n    >>> list_of_kwargs = expand_keywords(keywords)\n    >>> print(list_of_kwargs)\n\n    array([{'cmap': 'cmyt.arbre', 'dpi': 50},\n           {'cmap': 'cmyt.kelp', 'dpi': 100},\n           {'cmap': 'cmyt.arbre', 'dpi': 200}], dtype=object)\n\n    >>> list_of_kwargs = expand_keywords(keywords, full=True)\n    >>> print(list_of_kwargs)\n\n    array([{'cmap': 'cmyt.arbre', 'dpi': 50},\n           {'cmap': 'cmyt.arbre', 'dpi': 100},\n           {'cmap': 'cmyt.arbre', 'dpi': 200},\n           {'cmap': 'cmyt.kelp', 'dpi': 50},\n           {'cmap': 'cmyt.kelp', 'dpi': 100},\n           {'cmap': 'cmyt.kelp', 'dpi': 200}], dtype=object)\n\n    >>> for kwargs in list_of_kwargs:\n    ...     write_projection(*args, **kwargs)\n    \"\"\"\n\n    issue_deprecation_warning(\n        \"yt.testing.expand_keywords is deprecated\", since=\"4.2\", stacklevel=3\n    )\n\n    # if we want every possible combination of keywords, use iter magic\n    if full:\n        keys = sorted(keywords)\n        list_of_kwarg_dicts = np.array(\n            [\n                dict(zip(keys, prod, strict=True))\n                for prod in it.product(*(keywords[key] for key in keys))\n            ]\n        )\n\n    # if we just want to probe each keyword, but not necessarily every\n    # combination\n    else:\n        # Determine the maximum number of values any of the keywords has\n        num_lists = 0\n        for val in keywords.values():\n            if isinstance(val, str):\n                num_lists = max(1.0, num_lists)\n            else:\n                num_lists = max(len(val), num_lists)\n\n        # Construct array of kwargs dicts, each element of the list is a different\n        # **kwargs dict.  each kwargs dict gives a different combination of\n        # the possible values of the kwargs\n\n        # initialize array\n        list_of_kwarg_dicts = np.array([{} for x in range(num_lists)])\n\n        # fill in array\n        for i in np.arange(num_lists):\n            list_of_kwarg_dicts[i] = {}\n            for key in keywords.keys():\n                # if it's a string, use it (there's only one)\n                if isinstance(keywords[key], str):\n                    list_of_kwarg_dicts[i][key] = keywords[key]\n                # if there are more options, use the i'th val\n                elif i < len(keywords[key]):\n                    list_of_kwarg_dicts[i][key] = keywords[key][i]\n                # if there are not more options, use the 0'th val\n                else:\n                    list_of_kwarg_dicts[i][key] = keywords[key][0]\n\n    return list_of_kwarg_dicts\n\n\ndef skip(reason: str):\n    # a drop-in replacement for pytest.mark.skip decorator with nose-compatibility\n    def dec(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            if os.getenv(\"PYTEST_VERSION\") is not None:\n                # this is the recommended way to detect a pytest session\n                # https://docs.pytest.org/en/stable/reference/reference.html#envvar-PYTEST_VERSION\n                import pytest\n\n                pytest.skip(reason)\n            else:\n                # running from nose, or unittest\n                raise SkipTest(reason)\n\n        return wrapper\n\n    return dec\n\n\ndef skipif(condition: bool, reason: str):\n    # a drop-in replacement for pytest.mark.skipif decorator with nose-compatibility\n    def dec(func):\n        if condition:\n            return skip(reason)(func)\n        else:\n            return func\n\n    return dec\n\n\ndef requires_module(module):\n    \"\"\"\n    Decorator that takes a module name as an argument and tries to import it.\n    If the module imports without issue, the function is returned, but if not,\n    a null function is returned. This is so tests that depend on certain modules\n    being imported will not fail if the module is not installed on the testing\n    platform.\n    \"\"\"\n    return skipif(find_spec(module) is None, reason=f\"Missing required module {module}\")\n\n\ndef requires_module_pytest(*module_names):\n    \"\"\"\n    This is a replacement for yt.testing.requires_module that's\n    compatible with pytest, and accepts an arbitrary number of requirements to\n    avoid stacking decorators\n\n    Important: this is meant to decorate test functions only, it won't work as a\n    decorator to fixture functions.\n    It's meant to be imported as\n    >>> from yt.testing import requires_module_pytest as requires_module\n\n    So that it can be later renamed to `requires_module`.\n    \"\"\"\n    # note: import pytest here so that it is not a hard requirement for\n    # importing yt.testing see https://github.com/yt-project/yt/issues/4507\n    import pytest\n\n    def deco(func):\n        missing = [name for name in module_names if find_spec(name) is None]\n\n        # note that order between these two decorators matters\n        @pytest.mark.skipif(\n            missing,\n            reason=f\"missing requirement(s): {', '.join(missing)}\",\n        )\n        @wraps(func)\n        def inner_func(*args, **kwargs):\n            return func(*args, **kwargs)\n\n        return inner_func\n\n    return deco\n\n\ndef requires_file(req_file):\n    condition = (\n        not os.path.exists(req_file)\n        and not os.path.exists(os.path.join(ytcfg.get(\"yt\", \"test_data_dir\"), req_file))\n        and not ytcfg.get(\"yt\", \"internals\", \"strict_requires\")\n    )\n    return skipif(condition, reason=f\"Missing required file {req_file}\")\n\n\ndef disable_dataset_cache(func):\n    @wraps(func)\n    def newfunc(*args, **kwargs):\n        restore_cfg_state = False\n        if not ytcfg.get(\"yt\", \"skip_dataset_cache\"):\n            ytcfg[\"yt\", \"skip_dataset_cache\"] = True\n            restore_cfg_state = True\n        rv = func(*args, **kwargs)\n        if restore_cfg_state:\n            ytcfg[\"yt\", \"skip_dataset_cache\"] = False\n        return rv\n\n    return newfunc\n\n\n@disable_dataset_cache\ndef units_override_check(fn):\n    from numpy.testing import assert_equal\n\n    units_list = [\"length\", \"time\", \"mass\", \"velocity\", \"magnetic\", \"temperature\"]\n    ds1 = load(fn)\n    units_override = {}\n    attrs1 = []\n    attrs2 = []\n    for u in units_list:\n        unit_attr = getattr(ds1, f\"{u}_unit\", None)\n        if unit_attr is not None:\n            attrs1.append(unit_attr)\n            units_override[f\"{u}_unit\"] = (unit_attr.v, unit_attr.units)\n    del ds1\n    ds2 = load(fn, units_override=units_override)\n    assert len(ds2.units_override) > 0\n    for u in units_list:\n        unit_attr = getattr(ds2, f\"{u}_unit\", None)\n        if unit_attr is not None:\n            attrs2.append(unit_attr)\n    assert_equal(attrs1, attrs2)\n\n\n# This is an export of the 40 grids in IsolatedGalaxy that are of level 4 or\n# lower.  It's just designed to give a sample AMR index to deal with.\n_amr_grid_index = [\n    [0, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [32, 32, 32]],\n    [1, [0.25, 0.21875, 0.25], [0.5, 0.5, 0.5], [16, 18, 16]],\n    [1, [0.5, 0.21875, 0.25], [0.75, 0.5, 0.5], [16, 18, 16]],\n    [1, [0.21875, 0.5, 0.25], [0.5, 0.75, 0.5], [18, 16, 16]],\n    [1, [0.5, 0.5, 0.25], [0.75, 0.75, 0.5], [16, 16, 16]],\n    [1, [0.25, 0.25, 0.5], [0.5, 0.5, 0.75], [16, 16, 16]],\n    [1, [0.5, 0.25, 0.5], [0.75, 0.5, 0.75], [16, 16, 16]],\n    [1, [0.25, 0.5, 0.5], [0.5, 0.75, 0.75], [16, 16, 16]],\n    [1, [0.5, 0.5, 0.5], [0.75, 0.75, 0.75], [16, 16, 16]],\n    [2, [0.5, 0.5, 0.5], [0.71875, 0.71875, 0.71875], [28, 28, 28]],\n    [3, [0.5, 0.5, 0.5], [0.6640625, 0.65625, 0.6796875], [42, 40, 46]],\n    [4, [0.5, 0.5, 0.5], [0.59765625, 0.6015625, 0.6015625], [50, 52, 52]],\n    [2, [0.28125, 0.5, 0.5], [0.5, 0.734375, 0.71875], [28, 30, 28]],\n    [3, [0.3359375, 0.5, 0.5], [0.5, 0.671875, 0.6640625], [42, 44, 42]],\n    [4, [0.40625, 0.5, 0.5], [0.5, 0.59765625, 0.59765625], [48, 50, 50]],\n    [2, [0.5, 0.28125, 0.5], [0.71875, 0.5, 0.71875], [28, 28, 28]],\n    [3, [0.5, 0.3359375, 0.5], [0.671875, 0.5, 0.6640625], [44, 42, 42]],\n    [4, [0.5, 0.40625, 0.5], [0.6015625, 0.5, 0.59765625], [52, 48, 50]],\n    [2, [0.28125, 0.28125, 0.5], [0.5, 0.5, 0.71875], [28, 28, 28]],\n    [3, [0.3359375, 0.3359375, 0.5], [0.5, 0.5, 0.671875], [42, 42, 44]],\n    [\n        4,\n        [0.46484375, 0.37890625, 0.50390625],\n        [0.4765625, 0.390625, 0.515625],\n        [6, 6, 6],\n    ],\n    [4, [0.40625, 0.40625, 0.5], [0.5, 0.5, 0.59765625], [48, 48, 50]],\n    [2, [0.5, 0.5, 0.28125], [0.71875, 0.71875, 0.5], [28, 28, 28]],\n    [3, [0.5, 0.5, 0.3359375], [0.6796875, 0.6953125, 0.5], [46, 50, 42]],\n    [4, [0.5, 0.5, 0.40234375], [0.59375, 0.6015625, 0.5], [48, 52, 50]],\n    [2, [0.265625, 0.5, 0.28125], [0.5, 0.71875, 0.5], [30, 28, 28]],\n    [3, [0.3359375, 0.5, 0.328125], [0.5, 0.65625, 0.5], [42, 40, 44]],\n    [4, [0.40234375, 0.5, 0.40625], [0.5, 0.60546875, 0.5], [50, 54, 48]],\n    [2, [0.5, 0.265625, 0.28125], [0.71875, 0.5, 0.5], [28, 30, 28]],\n    [3, [0.5, 0.3203125, 0.328125], [0.6640625, 0.5, 0.5], [42, 46, 44]],\n    [4, [0.5, 0.3984375, 0.40625], [0.546875, 0.5, 0.5], [24, 52, 48]],\n    [4, [0.546875, 0.41796875, 0.4453125], [0.5625, 0.4375, 0.5], [8, 10, 28]],\n    [4, [0.546875, 0.453125, 0.41796875], [0.5546875, 0.48046875, 0.4375], [4, 14, 10]],\n    [4, [0.546875, 0.4375, 0.4375], [0.609375, 0.5, 0.5], [32, 32, 32]],\n    [4, [0.546875, 0.4921875, 0.41796875], [0.56640625, 0.5, 0.4375], [10, 4, 10]],\n    [\n        4,\n        [0.546875, 0.48046875, 0.41796875],\n        [0.5703125, 0.4921875, 0.4375],\n        [12, 6, 10],\n    ],\n    [4, [0.55859375, 0.46875, 0.43359375], [0.5703125, 0.48046875, 0.4375], [6, 6, 2]],\n    [2, [0.265625, 0.28125, 0.28125], [0.5, 0.5, 0.5], [30, 28, 28]],\n    [3, [0.328125, 0.3359375, 0.328125], [0.5, 0.5, 0.5], [44, 42, 44]],\n    [4, [0.4140625, 0.40625, 0.40625], [0.5, 0.5, 0.5], [44, 48, 48]],\n]\n\n\ndef check_results(func):\n    r\"\"\"This is a decorator for a function to verify that the (numpy ndarray)\n    result of a function is what it should be.\n\n    This function is designed to be used for very light answer testing.\n    Essentially, it wraps around a larger function that returns a numpy array,\n    and that has results that should not change.  It is not necessarily used\n    inside the testing scripts themselves, but inside testing scripts written\n    by developers during the testing of pull requests and new functionality.\n    If a hash is specified, it \"wins\" and the others are ignored.  Otherwise,\n    tolerance is 1e-8 (just above single precision.)\n\n    The correct results will be stored if the command line contains\n    --answer-reference , and otherwise it will compare against the results on\n    disk.  The filename will be func_results_ref_FUNCNAME.cpkl where FUNCNAME\n    is the name of the function being tested.\n\n    If you would like more control over the name of the pickle file the results\n    are stored in, you can pass the result_basename keyword argument to the\n    function you are testing.  The check_results decorator will use the value\n    of the keyword to construct the filename of the results data file.  If\n    result_basename is not specified, the name of the testing function is used.\n\n    This will raise an exception if the results are not correct.\n\n    Examples\n    --------\n\n    >>> @check_results\n    ... def my_func(ds):\n    ...     return ds.domain_width\n\n    >>> my_func(ds)\n\n    >>> @check_results\n    ... def field_checker(dd, field_name):\n    ...     return dd[field_name]\n\n    >>> field_checker(ds.all_data(), \"density\", result_basename=\"density\")\n\n    \"\"\"\n\n    def compute_results(func):\n        @wraps(func)\n        def _func(*args, **kwargs):\n            name = kwargs.pop(\"result_basename\", func.__name__)\n            rv = func(*args, **kwargs)\n            if hasattr(rv, \"convert_to_base\"):\n                rv.convert_to_base()\n                _rv = rv.ndarray_view()\n            else:\n                _rv = rv\n            mi = _rv.min()\n            ma = _rv.max()\n            st = _rv.std(dtype=\"float64\")\n            su = _rv.sum(dtype=\"float64\")\n            si = _rv.size\n            ha = hashlib.md5(_rv.tobytes()).hexdigest()\n            fn = f\"func_results_ref_{name}.cpkl\"\n            with open(fn, \"wb\") as f:\n                pickle.dump((mi, ma, st, su, si, ha), f)\n            return rv\n\n        return _func\n\n    import yt.startup_tasks as _startup_tasks\n\n    unparsed_args = _startup_tasks.unparsed_args\n\n    if \"--answer-reference\" in unparsed_args:\n        return compute_results(func)\n\n    def compare_results(func):\n        @wraps(func)\n        def _func(*args, **kwargs):\n            from numpy.testing import assert_allclose, assert_equal\n\n            name = kwargs.pop(\"result_basename\", func.__name__)\n            rv = func(*args, **kwargs)\n            if hasattr(rv, \"convert_to_base\"):\n                rv.convert_to_base()\n                _rv = rv.ndarray_view()\n            else:\n                _rv = rv\n            vals = (\n                _rv.min(),\n                _rv.max(),\n                _rv.std(dtype=\"float64\"),\n                _rv.sum(dtype=\"float64\"),\n                _rv.size,\n                hashlib.md5(_rv.tobytes()).hexdigest(),\n            )\n            fn = f\"func_results_ref_{name}.cpkl\"\n            if not os.path.exists(fn):\n                print(\"Answers need to be created with --answer-reference .\")\n                return False\n            with open(fn, \"rb\") as f:\n                ref = pickle.load(f)\n            print(f\"Sizes: {vals[4] == ref[4]} ({vals[4]}, {ref[4]})\")\n            assert_allclose(vals[0], ref[0], 1e-8, err_msg=\"min\")\n            assert_allclose(vals[1], ref[1], 1e-8, err_msg=\"max\")\n            assert_allclose(vals[2], ref[2], 1e-8, err_msg=\"std\")\n            assert_allclose(vals[3], ref[3], 1e-8, err_msg=\"sum\")\n            assert_equal(vals[4], ref[4])\n            print(\"Hashes equal: %s\" % (vals[-1] == ref[-1]))\n            return rv\n\n        return _func\n\n    return compare_results(func)\n\n\ndef periodicity_cases(ds):\n    # This is a generator that yields things near the corners.  It's good for\n    # getting different places to check periodicity.\n    yield (ds.domain_left_edge + ds.domain_right_edge) / 2.0\n    dx = ds.domain_width / ds.domain_dimensions\n    # We start one dx in, and only go to one in as well.\n    for i in (1, ds.domain_dimensions[0] - 2):\n        for j in (1, ds.domain_dimensions[1] - 2):\n            for k in (1, ds.domain_dimensions[2] - 2):\n                center = dx * np.array([i, j, k]) + ds.domain_left_edge\n                yield center\n\n\ndef run_nose(\n    verbose=False,\n    run_answer_tests=False,\n    answer_big_data=False,\n    call_pdb=False,\n    module=None,\n):\n    issue_deprecation_warning(\n        \"yt.run_nose (aka yt.testing.run_nose) is deprecated. \"\n        \"Please do not rely on this function as it will be removed \"\n        \"in the process of migrating yt tests from nose to pytest.\",\n        stacklevel=3,\n        since=\"4.1\",\n    )\n\n    from yt.utilities.logger import ytLogger as mylog\n    from yt.utilities.on_demand_imports import _nose\n\n    orig_level = mylog.getEffectiveLevel()\n    mylog.setLevel(50)\n    nose_argv = sys.argv\n    nose_argv += [\"--exclude=answer_testing\", \"--detailed-errors\", \"--exe\"]\n    if call_pdb:\n        nose_argv += [\"--pdb\", \"--pdb-failures\"]\n    if verbose:\n        nose_argv.append(\"-v\")\n    if run_answer_tests:\n        nose_argv.append(\"--with-answer-testing\")\n    if answer_big_data:\n        nose_argv.append(\"--answer-big-data\")\n    if module:\n        nose_argv.append(module)\n    initial_dir = os.getcwd()\n    yt_file = os.path.abspath(__file__)\n    yt_dir = os.path.dirname(yt_file)\n    if os.path.samefile(os.path.dirname(yt_dir), initial_dir):\n        # Provide a nice error message to work around nose bug\n        # see https://github.com/nose-devs/nose/issues/701\n        raise RuntimeError(\n            \"\"\"\n    The yt.run_nose function does not work correctly when invoked in\n    the same directory as the installed yt package. Try starting\n    a python session in a different directory before invoking yt.run_nose\n    again. Alternatively, you can also run the \"nosetests\" executable in\n    the current directory like so:\n\n        $ nosetests\n            \"\"\"\n        )\n    os.chdir(yt_dir)\n    try:\n        _nose.run(argv=nose_argv)\n    finally:\n        os.chdir(initial_dir)\n        mylog.setLevel(orig_level)\n\n\ndef assert_allclose_units(actual, desired, rtol=1e-7, atol=0, **kwargs):\n    \"\"\"Raise an error if two objects are not equal up to desired tolerance\n\n    This is a wrapper for :func:`numpy.testing.assert_allclose` that also\n    verifies unit consistency\n\n    Parameters\n    ----------\n    actual : array-like\n        Array obtained (possibly with attached units)\n    desired : array-like\n        Array to compare with (possibly with attached units)\n    rtol : float, optional\n        Relative tolerance, defaults to 1e-7\n    atol : float or quantity, optional\n        Absolute tolerance. If units are attached, they must be consistent\n        with the units of ``actual`` and ``desired``. If no units are attached,\n        assumes the same units as ``desired``. Defaults to zero.\n\n    Notes\n    -----\n    Also accepts additional keyword arguments accepted by\n    :func:`numpy.testing.assert_allclose`, see the documentation of that\n    function for details.\n\n    \"\"\"\n    from numpy.testing import assert_allclose\n\n    # Create a copy to ensure this function does not alter input arrays\n    act = YTArray(actual)\n    des = YTArray(desired)\n\n    try:\n        des = des.in_units(act.units)\n    except UnitOperationError as e:\n        raise AssertionError(\n            f\"Units of actual ({act.units}) and desired ({des.units}) \"\n            \"do not have equivalent dimensions\"\n        ) from e\n\n    rt = YTArray(rtol)\n    if not rt.units.is_dimensionless:\n        raise AssertionError(f\"Units of rtol ({rt.units}) are not dimensionless\")\n\n    if not isinstance(atol, YTArray):\n        at = YTQuantity(atol, des.units)\n\n    try:\n        at = at.in_units(act.units)\n    except UnitOperationError as e:\n        raise AssertionError(\n            f\"Units of atol ({at.units}) and actual ({act.units}) \"\n            \"do not have equivalent dimensions\"\n        ) from e\n\n    # units have been validated, so we strip units before calling numpy\n    # to avoid spurious errors\n    act = act.value\n    des = des.value\n    rt = rt.value\n    at = at.value\n\n    return assert_allclose(act, des, rt, at, **kwargs)\n\n\ndef assert_fname(fname):\n    \"\"\"Function that checks file type using libmagic\"\"\"\n    if fname is None:\n        return\n\n    with open(fname, \"rb\") as fimg:\n        data = fimg.read()\n    image_type = \"\"\n\n    # see http://www.w3.org/TR/PNG/#5PNG-file-signature\n    if data.startswith(b\"\\211PNG\\r\\n\\032\\n\"):\n        image_type = \".png\"\n    # see http://www.mathguide.de/info/tools/media-types/image/jpeg\n    elif data.startswith(b\"\\377\\330\"):\n        image_type = \".jpeg\"\n    elif data.startswith(b\"%!PS-Adobe\"):\n        data_str = data.decode(\"utf-8\", \"ignore\")\n        if \"EPSF\" in data_str[: data_str.index(\"\\n\")]:\n            image_type = \".eps\"\n        else:\n            image_type = \".ps\"\n    elif data.startswith(b\"%PDF\"):\n        image_type = \".pdf\"\n\n    extension = os.path.splitext(fname)[1]\n\n    assert image_type == extension, (\n        f\"Expected an image of type {extension!r} but {fname!r} \"\n        \"is an image of type {image_type!r}\"\n    )\n\n\ndef requires_backend(backend):\n    \"\"\"Decorator to check for a specified matplotlib backend.\n\n    This decorator returns the decorated function if the specified `backend`\n    is same as of `matplotlib.get_backend()`, otherwise returns null function.\n    It could be used to execute function only when a particular `backend` of\n    matplotlib is being used.\n\n    Parameters\n    ----------\n    backend : String\n        The value which is compared with the current matplotlib backend in use.\n\n    \"\"\"\n    return skipif(\n        backend.lower() != matplotlib.get_backend().lower(),\n        reason=f\"'{backend}' backend not in use\",\n    )\n\n\ndef requires_external_executable(*names):\n    missing = [name for name in names if which(name) is None]\n    return skipif(\n        len(missing) > 0, reason=f\"missing external executable(s): {', '.join(missing)}\"\n    )\n\n\nclass TempDirTest(unittest.TestCase):\n    \"\"\"\n    A test class that runs in a temporary directory and\n    removes it afterward.\n    \"\"\"\n\n    def setUp(self):\n        self.curdir = os.getcwd()\n        self.tmpdir = tempfile.mkdtemp()\n        os.chdir(self.tmpdir)\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        shutil.rmtree(self.tmpdir)\n\n\nclass ParticleSelectionComparison:\n    \"\"\"\n    This is a test helper class that takes a particle dataset, caches the\n    particles it has on disk (manually reading them using lower-level IO\n    routines) and then received a data object that it compares against manually\n    running the data object's selection routines.  All supplied data objects\n    must be created from the input dataset.\n    \"\"\"\n\n    def __init__(self, ds):\n        self.ds = ds\n        # Construct an index so that we get all the data_files\n        ds.index\n        particles = {}\n        # hsml is the smoothing length we use for radial selection\n        hsml = {}\n        for data_file in ds.index.data_files:\n            for ptype, pos_arr in ds.index.io._yield_coordinates(data_file):\n                particles.setdefault(ptype, []).append(pos_arr)\n                if ptype in getattr(ds, \"_sph_ptypes\", ()):\n                    hsml.setdefault(ptype, []).append(\n                        ds.index.io._get_smoothing_length(\n                            data_file, pos_arr.dtype, pos_arr.shape\n                        )\n                    )\n        for ptype in particles:\n            particles[ptype] = np.concatenate(particles[ptype])\n            if ptype in hsml:\n                hsml[ptype] = np.concatenate(hsml[ptype])\n        self.particles = particles\n        self.hsml = hsml\n\n    def compare_dobj_selection(self, dobj):\n        from numpy.testing import assert_array_almost_equal_nulp\n\n        for ptype in sorted(self.particles):\n            x, y, z = self.particles[ptype].T\n            # Set our radii to zero for now, I guess?\n            radii = self.hsml.get(ptype, 0.0)\n            sel_index = dobj.selector.select_points(x, y, z, radii)\n            if sel_index is None:\n                sel_pos = np.empty((0, 3))\n            else:\n                sel_pos = self.particles[ptype][sel_index, :]\n\n            obj_results = []\n            for chunk in dobj.chunks([], \"io\"):\n                obj_results.append(chunk[ptype, \"particle_position\"])\n            if any(_.size > 0 for _ in obj_results):\n                obj_results = np.concatenate(obj_results, axis=0)\n            else:\n                obj_results = np.empty((0, 3))\n            # Sometimes we get unitary scaling or other floating point noise. 5\n            # NULP should be OK.  This is mostly for stuff like Rockstar, where\n            # the f32->f64 casting happens at different places depending on\n            # which code path we use.\n            assert_array_almost_equal_nulp(\n                np.asarray(sel_pos), np.asarray(obj_results), 5\n            )\n\n    def run_defaults(self):\n        \"\"\"\n        This runs lots of samples that touch different types of wraparounds.\n\n        Specifically, it does:\n\n            * sphere in center with radius 0.1 unitary\n            * sphere in center with radius 0.2 unitary\n            * sphere in each of the eight corners of the domain with radius 0.1 unitary\n            * sphere in center with radius 0.5 unitary\n            * box that covers 0.1 .. 0.9\n            * box from 0.8 .. 0.85\n            * box from 0.3..0.6, 0.2..0.8, 0.0..0.1\n        \"\"\"\n        sp1 = self.ds.sphere(\"c\", (0.1, \"unitary\"))\n        self.compare_dobj_selection(sp1)\n\n        sp2 = self.ds.sphere(\"c\", (0.2, \"unitary\"))\n        self.compare_dobj_selection(sp2)\n\n        centers = [\n            [0.04, 0.5, 0.5],\n            [0.5, 0.04, 0.5],\n            [0.5, 0.5, 0.04],\n            [0.04, 0.04, 0.04],\n            [0.96, 0.5, 0.5],\n            [0.5, 0.96, 0.5],\n            [0.5, 0.5, 0.96],\n            [0.96, 0.96, 0.96],\n        ]\n        r = self.ds.quan(0.1, \"unitary\")\n        for center in centers:\n            c = self.ds.arr(center, \"unitary\") + self.ds.domain_left_edge.in_units(\n                \"unitary\"\n            )\n            if not all(self.ds.periodicity):\n                # filter out the periodic bits for non-periodic datasets\n                if any(c - r < self.ds.domain_left_edge) or any(\n                    c + r > self.ds.domain_right_edge\n                ):\n                    continue\n            sp = self.ds.sphere(c, (0.1, \"unitary\"))\n            self.compare_dobj_selection(sp)\n\n        sp = self.ds.sphere(\"c\", (0.5, \"unitary\"))\n        self.compare_dobj_selection(sp)\n\n        dd = self.ds.all_data()\n        self.compare_dobj_selection(dd)\n\n        # This is in raw numbers, so we can offset for the left edge\n        LE = self.ds.domain_left_edge.in_units(\"unitary\").d\n\n        reg1 = self.ds.r[\n            (0.1 + LE[0], \"unitary\") : (0.9 + LE[0], \"unitary\"),\n            (0.1 + LE[1], \"unitary\") : (0.9 + LE[1], \"unitary\"),\n            (0.1 + LE[2], \"unitary\") : (0.9 + LE[2], \"unitary\"),\n        ]\n        self.compare_dobj_selection(reg1)\n\n        reg2 = self.ds.r[\n            (0.8 + LE[0], \"unitary\") : (0.85 + LE[0], \"unitary\"),\n            (0.8 + LE[1], \"unitary\") : (0.85 + LE[1], \"unitary\"),\n            (0.8 + LE[2], \"unitary\") : (0.85 + LE[2], \"unitary\"),\n        ]\n        self.compare_dobj_selection(reg2)\n\n        reg3 = self.ds.r[\n            (0.3 + LE[0], \"unitary\") : (0.6 + LE[0], \"unitary\"),\n            (0.2 + LE[1], \"unitary\") : (0.8 + LE[1], \"unitary\"),\n            (0.0 + LE[2], \"unitary\") : (0.1 + LE[2], \"unitary\"),\n        ]\n        self.compare_dobj_selection(reg3)\n\n\ndef _deprecated_numpy_testing_reexport(func):\n    import numpy.testing as npt\n\n    npt_func = getattr(npt, func.__name__)\n\n    @wraps(npt_func)\n    def retf(*args, **kwargs):\n        __tracebackhide__ = True  # Hide traceback for pytest\n        issue_deprecation_warning(\n            f\"yt.testing.{func.__name__} is a pure re-export of \"\n            f\"numpy.testing.{func.__name__}, it will stop working in the future. \"\n            \"Please import this function directly from numpy instead.\",\n            since=\"4.2\",\n            stacklevel=3,\n        )\n        return npt_func(*args, **kwargs)\n\n    return retf\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_array_equal(): ...\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_almost_equal(): ...\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_equal(): ...\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_array_less(): ...\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_string_equal(): ...\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_array_almost_equal_nulp(): ...\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_allclose(): ...\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_raises(): ...\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_approx_equal(): ...\n\n\n@_deprecated_numpy_testing_reexport\ndef assert_array_almost_equal(): ...\n"
  },
  {
    "path": "yt/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/tests/test_external_frontends.py",
    "content": "import importlib.metadata\n\nimport pytest\n\nimport yt\nfrom yt.data_objects.static_output import Dataset\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.object_registries import output_type_registry\n\n\nclass MockEntryPoint:\n    @classmethod\n    def load(cls):\n        class MockHierarchy(GridIndex):\n            grid = None\n\n        class ExtDataset(Dataset):\n            _index_class = MockHierarchy\n\n            def _parse_parameter_file(self):\n                self.current_time = 1.0\n                self.cosmological_simulation = 0\n\n            def _set_code_unit_attributes(self):\n                self.length_unit = self.quan(1.0, \"code_length\")\n                self.mass_unit = self.quan(1.0, \"code_mass\")\n                self.time_unit = self.quan(1.0, \"code_time\")\n\n            @classmethod\n            def _is_valid(cls, filename, *args, **kwargs):\n                return filename.endswith(\"mock\")\n\n\n@pytest.fixture()\ndef mock_external_frontend(monkeypatch):\n    def mock_entry_points(group=None):\n        return [MockEntryPoint]\n\n    monkeypatch.setattr(importlib.metadata, \"entry_points\", mock_entry_points)\n    assert \"ExtDataset\" not in output_type_registry\n\n    yield\n\n    assert \"ExtDataset\" in output_type_registry\n    # teardown to avoid test pollution\n    output_type_registry.pop(\"ExtDataset\")\n\n\n@pytest.mark.usefixtures(\"mock_external_frontend\")\ndef test_external_frontend(tmp_path):\n    test_file = tmp_path / \"tmp.mock\"\n    test_file.write_text(\"\")  # create the file\n    assert test_file.is_file()\n\n    ds = yt.load(test_file)\n    assert \"ExtDataset\" in ds.__class__.__name__\n"
  },
  {
    "path": "yt/tests/test_funcs.py",
    "content": "import os\n\nfrom nose.tools import assert_raises\nfrom numpy.testing import assert_equal\n\nfrom yt.funcs import (\n    just_one,\n    levenshtein_distance,\n    simple_download_file,\n    validate_axis,\n    validate_center,\n)\nfrom yt.testing import fake_amr_ds\nfrom yt.units import YTArray, YTQuantity\n\n\ndef test_validate_axis():\n    validate_axis(None, 0)\n    validate_axis(None, \"X\")\n\n    ds = fake_amr_ds(geometry=\"cylindrical\")\n    ds.slice(\"Theta\", 0.25)\n\n    with assert_raises(TypeError) as ex:\n        # default geometry is cartesian\n        ds = fake_amr_ds()\n        ds.slice(\"r\", 0.25)\n    desired = \"Expected axis to be any of [0, 1, 2, 'x', 'y', 'z', 'X', 'Y', 'Z'], received 'r'\"\n\n    actual = str(ex.exception)\n    assert actual == desired\n\n\ndef test_validate_center():\n    validate_center(\"max\")\n    validate_center(\"MIN_\")\n\n    with assert_raises(TypeError) as ex:\n        validate_center(\"avg\")\n    desired = (\n        \"Expected 'center' to be in ['c', 'center', 'm', 'max', 'min'] \"\n        \"or the prefix to be 'max_'/'min_', received 'avg'.\"\n    )\n    assert_equal(str(ex.exception), desired)\n\n    validate_center(YTQuantity(0.25, \"cm\"))\n    validate_center([0.25, 0.25, 0.25])\n\n    class CustomCenter:\n        def __init__(self, center):\n            self.center = center\n\n    with assert_raises(TypeError) as ex:\n        validate_center(CustomCenter(10))\n    desired = (\n        \"Expected 'center' to be a numeric object of type \"\n        \"list/tuple/np.ndarray/YTArray/YTQuantity, received \"\n        \"'yt.tests.test_funcs.test_validate_center.<locals>.\"\n        \"CustomCenter'.\"\n    )\n    assert_equal(str(ex.exception)[:50], desired[:50])\n\n\ndef test_just_one():\n    # Check that behaviour of this function is consistent before and after refactor\n    # PR 2893\n    for unit in [\"mm\", \"cm\", \"km\", \"pc\", \"g\", \"kg\", \"M_sun\"]:\n        obj = YTArray([0.0, 1.0], unit)\n        expected = YTQuantity(obj.flat[0], obj.units, registry=obj.units.registry)\n        jo = just_one(obj)\n        assert jo == expected\n\n\ndef test_levenshtein():\n    assert_equal(levenshtein_distance(\"abcdef\", \"abcdef\"), 0)\n\n    # Deletions / additions\n    assert_equal(levenshtein_distance(\"abcdef\", \"abcde\"), 1)\n    assert_equal(levenshtein_distance(\"abcdef\", \"abcd\"), 2)\n    assert_equal(levenshtein_distance(\"abcdef\", \"abc\"), 3)\n\n    assert_equal(levenshtein_distance(\"abcdf\", \"abcdef\"), 1)\n    assert_equal(levenshtein_distance(\"cdef\", \"abcdef\"), 2)\n    assert_equal(levenshtein_distance(\"bde\", \"abcdef\"), 3)\n\n    # Substitutions\n    assert_equal(levenshtein_distance(\"abcd\", \"abc_\"), 1)\n    assert_equal(levenshtein_distance(\"abcd\", \"ab__\"), 2)\n    assert_equal(levenshtein_distance(\"abcd\", \"a___\"), 3)\n    assert_equal(levenshtein_distance(\"abcd\", \"____\"), 4)\n\n    # Deletion + Substitutions\n    assert_equal(levenshtein_distance(\"abcd\", \"abc_z\"), 2)\n    assert_equal(levenshtein_distance(\"abcd\", \"ab__zz\"), 4)\n    assert_equal(levenshtein_distance(\"abcd\", \"a___zzz\"), 6)\n    assert_equal(levenshtein_distance(\"abcd\", \"____zzzz\"), 8)\n\n    # Max distance\n    assert_equal(levenshtein_distance(\"abcd\", \"\", max_dist=0), 1)\n    assert_equal(levenshtein_distance(\"abcd\", \"\", max_dist=3), 4)\n    assert_equal(levenshtein_distance(\"abcd\", \"\", max_dist=10), 4)\n    assert_equal(levenshtein_distance(\"abcd\", \"\", max_dist=1), 2)\n    assert_equal(levenshtein_distance(\"abcd\", \"a\", max_dist=2), 3)\n    assert_equal(levenshtein_distance(\"abcd\", \"ad\", max_dist=2), 2)\n    assert_equal(levenshtein_distance(\"abcd\", \"abd\", max_dist=2), 1)\n    assert_equal(levenshtein_distance(\"abcd\", \"abcd\", max_dist=2), 0)\n\n\ndef test_simple_download_file():\n    fn = simple_download_file(\"http://yt-project.org\", \"simple-download-file\")\n    try:\n        assert fn == \"simple-download-file\"\n        assert os.path.exists(\"simple-download-file\")\n    finally:\n        # Clean up after ourselves.\n        try:\n            os.unlink(\"simple-download-file\")\n        except FileNotFoundError:\n            pass\n\n    with assert_raises(RuntimeError) as ex:\n        simple_download_file(\"http://yt-project.org/404\", \"simple-download-file\")\n\n    desired = \"Attempt to download file from http://yt-project.org/404 failed with error 404: Not Found.\"\n    actual = str(ex.exception)\n    assert actual == desired\n    assert not os.path.exists(\"simple-download-file\")\n"
  },
  {
    "path": "yt/tests/test_load_archive.py",
    "content": "import os\nimport sys\nimport tarfile\nimport time\n\nimport pytest\n\nfrom yt.config import ytcfg\nfrom yt.loaders import load_archive\nfrom yt.sample_data.api import _download_sample_data_file, get_data_registry_table\nfrom yt.testing import requires_module_pytest\nfrom yt.utilities.exceptions import YTUnidentifiedDataType\n\n\n@pytest.fixture()\ndef data_registry():\n    yield get_data_registry_table()\n\n\n@pytest.fixture()\ndef tmp_data_dir(tmp_path):\n    pre_test_data_dir = ytcfg[\"yt\", \"test_data_dir\"]\n    ytcfg.set(\"yt\", \"test_data_dir\", str(tmp_path))\n\n    yield tmp_path\n\n    ytcfg.set(\"yt\", \"test_data_dir\", pre_test_data_dir)\n\n\n# Note: ratarmount cannot currently be installed on Windows as of v0.8.1\n@pytest.mark.skipif(\n    sys.platform.startswith(\"win\"),\n    reason=\"ratarmount cannot currently be installed on Windows as of v0.8.1\",\n)\n@pytest.mark.skipif(\n    os.environ.get(\"JENKINS_HOME\") is not None,\n    reason=\"Archive mounting times out on Jenkins.\",\n)\n@requires_module_pytest(\"pooch\", \"ratarmount\")\n@pytest.mark.parametrize(\n    \"fn, exact_loc, class_\",\n    [\n        (\n            \"ToroShockTube.tar.gz\",\n            \"ToroShockTube/DD0001/data0001\",\n            \"EnzoDataset\",\n        ),\n        (\n            \"ramses_sink_00016.tar.gz\",\n            \"ramses_sink_00016/output_00016\",\n            \"RAMSESDataset\",\n        ),\n    ],\n)\n@pytest.mark.parametrize(\"archive_suffix\", [\"\", \".gz\"])\ndef test_load_archive(\n    fn, exact_loc, class_: str, archive_suffix, tmp_data_dir, data_registry\n):\n    # Download the sample .tar.gz'd file\n    targz_path = _download_sample_data_file(filename=fn)\n    tar_path = targz_path.with_suffix(archive_suffix)\n\n    if tar_path != targz_path:\n        # Open the tarfile and uncompress it to .tar, .tar.gz, and .tar.bz2 files\n        with tarfile.open(targz_path, mode=\"r:*\") as targz:\n            mode = \"w\" + archive_suffix.replace(\".\", \":\")\n            with tarfile.open(tar_path, mode=mode) as tar:\n                for member in targz.getmembers():\n                    content = targz.extractfile(member)\n                    tar.addfile(member, fileobj=content)\n\n    # Now try to open the .tar.* files\n    warn_msg = \"The 'load_archive' function is still experimental and may be unstable.\"\n    with pytest.warns(UserWarning, match=warn_msg):\n        ds = load_archive(tar_path, exact_loc, mount_timeout=10)\n    assert type(ds).__name__ == class_\n\n    # Make sure the index is readable\n    ds.index\n\n    # Check cleanup\n    mount_path = tar_path.with_name(tar_path.name + \".mount\")\n    assert mount_path.is_mount()\n\n    ## Manually dismount\n    ds.dismount()\n\n    ## The dismounting happens concurrently, wait a few sec.\n    time.sleep(2)\n\n    ## Mount path should not exist anymore *and* have been deleted\n    assert not mount_path.is_mount()\n    assert not mount_path.exists()\n\n\n@pytest.mark.skipif(\n    sys.platform.startswith(\"win\"),\n    reason=\"ratarmount cannot currently be installed on Windows as of v0.8.1\",\n)\n@pytest.mark.skipif(\n    os.environ.get(\"JENKINS_HOME\") is not None,\n    reason=\"Archive mounting times out on Jenkins.\",\n)\n@pytest.mark.filterwarnings(\n    \"ignore:The 'load_archive' function is still experimental and may be unstable.\"\n)\n@requires_module_pytest(\"pooch\", \"ratarmount\")\ndef test_load_invalid_archive(tmp_data_dir, data_registry):\n    # Archive does not exist\n    with pytest.raises(FileNotFoundError):\n        load_archive(\"this_file_does_not_exist.tar.gz\", \"invalid_location\")\n\n    targz_path = _download_sample_data_file(filename=\"ToroShockTube.tar.gz\")\n    # File does not exist\n    with pytest.raises(FileNotFoundError):\n        load_archive(targz_path, \"invalid_location\")\n\n    # File exists but is not recognized\n    with pytest.raises(YTUnidentifiedDataType):\n        load_archive(targz_path, \"ToroShockTube/DD0001/data0001.memorymap\")\n"
  },
  {
    "path": "yt/tests/test_load_errors.py",
    "content": "import re\n\nimport pytest\n\nfrom yt.data_objects.static_output import Dataset\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.loaders import load, load_simulation\nfrom yt.utilities.exceptions import (\n    YTAmbiguousDataType,\n    YTSimulationNotIdentified,\n    YTUnidentifiedDataType,\n)\nfrom yt.utilities.object_registries import output_type_registry\n\n\n@pytest.fixture\ndef tmp_data_dir(tmp_path):\n    from yt.config import ytcfg\n\n    pre_test_data_dir = ytcfg[\"yt\", \"test_data_dir\"]\n    ytcfg.set(\"yt\", \"test_data_dir\", str(tmp_path))\n\n    yield tmp_path\n\n    ytcfg.set(\"yt\", \"test_data_dir\", pre_test_data_dir)\n\n\n@pytest.mark.usefixtures(\"tmp_data_dir\")\ndef test_load_not_a_file(tmp_path):\n    with pytest.raises(FileNotFoundError):\n        load(tmp_path / \"not_a_file\")\n\n\n@pytest.mark.parametrize(\"simtype\", [\"Enzo\", \"unregistered_simulation_type\"])\n@pytest.mark.usefixtures(\"tmp_data_dir\")\ndef test_load_simulation_not_a_file(simtype, tmp_path):\n    # it is preferable to report the most important problem in an error message\n    # (missing data is worse than a typo insimulation_type)\n    # so we make sure the error raised is not YTSimulationNotIdentified,\n    # even with an absurd simulation type\n    with pytest.raises(FileNotFoundError):\n        load_simulation(tmp_path / \"not_a_file\", simtype)\n\n\n@pytest.fixture()\ndef tmp_path_with_empty_file(tmp_path):\n    empty_file_path = tmp_path / \"empty_file\"\n    empty_file_path.touch()\n    return tmp_path, empty_file_path\n\n\ndef test_load_unidentified_data_dir(tmp_path_with_empty_file):\n    tmp_path, empty_file_path = tmp_path_with_empty_file\n    with pytest.raises(YTUnidentifiedDataType):\n        load(tmp_path)\n\n\ndef test_load_unidentified_data_file(tmp_path_with_empty_file):\n    tmp_path, empty_file_path = tmp_path_with_empty_file\n    with pytest.raises(YTUnidentifiedDataType):\n        load(empty_file_path)\n\n\ndef test_load_simulation_unidentified_data_dir(tmp_path_with_empty_file):\n    tmp_path, empty_file_path = tmp_path_with_empty_file\n    with pytest.raises(YTSimulationNotIdentified):\n        load_simulation(tmp_path, \"unregistered_simulation_type\")\n\n\ndef test_load_simulation_unidentified_data_file(tmp_path_with_empty_file):\n    tmp_path, empty_file_path = tmp_path_with_empty_file\n    with pytest.raises(YTSimulationNotIdentified):\n        load_simulation(\n            empty_file_path,\n            \"unregistered_simulation_type\",\n        )\n\n\n@pytest.fixture()\ndef ambiguous_dataset_classes():\n    # We deliberately setup a situation where two Dataset subclasses\n    # that aren't parents are consisdered valid.\n    # We implement the bare minimum for these classes to be actually\n    # loadable in order to test hints.\n    class MockHierarchy(GridIndex):\n        pass\n\n    class MockDataset(Dataset):\n        _index_class = MockHierarchy\n\n        def _parse_parameter_file(self, *args, **kwargs):\n            self.current_time = -1.0\n            self.cosmological_simulation = 0\n\n        def _set_code_unit_attributes(self, *args, **kwargs):\n            self.length_unit = self.quan(1, \"m\")\n            self.mass_unit = self.quan(1, \"kg\")\n            self.time_unit = self.quan(1, \"s\")\n\n        @classmethod\n        def _is_valid(cls, *args, **kwargs):\n            return True\n\n    class AlphaDataset(MockDataset):\n        @classmethod\n        def _is_valid(cls, *args, **kwargs):\n            return True\n\n    class BetaDataset(MockDataset):\n        @classmethod\n        def _is_valid(cls, *args, **kwargs):\n            return True\n\n    yield\n\n    # teardown to avoid possible breakage in following tests\n    output_type_registry.pop(\"MockDataset\")\n    output_type_registry.pop(\"AlphaDataset\")\n    output_type_registry.pop(\"BetaDataset\")\n\n\n@pytest.mark.usefixtures(\"ambiguous_dataset_classes\")\ndef test_load_ambiguous_data(tmp_path):\n    with pytest.raises(YTAmbiguousDataType):\n        load(tmp_path)\n\n    file = tmp_path / \"fake_datafile0011.dump\"\n    file.touch()\n\n    pattern = str(tmp_path / \"fake_datafile00??.dump\")\n\n    # loading a DatasetSeries should not crash until an item is retrieved\n    ts = load(pattern)\n    with pytest.raises(YTAmbiguousDataType):\n        ts[0]\n\n\n@pytest.mark.parametrize(\n    \"hint, expected_type\",\n    [\n        (\"alpha\", \"AlphaDataset\"),\n        (\"al\", \"AlphaDataset\"),\n        (\"ph\", \"AlphaDataset\"),\n        (\"beta\", \"BetaDataset\"),\n        (\"BeTA\", \"BetaDataset\"),\n        (\"b\", \"BetaDataset\"),\n        (\"mock\", \"MockDataset\"),\n        (\"MockDataset\", \"MockDataset\"),\n    ],\n)\n@pytest.mark.usefixtures(\"ambiguous_dataset_classes\")\ndef test_load_ambiguous_data_with_hint(hint, expected_type, tmp_path):\n    ds = load(tmp_path, hint=hint)\n    assert type(ds).__name__ == expected_type\n\n    file1 = tmp_path / \"fake_datafile0011.dump\"\n    file2 = tmp_path / \"fake_datafile0022.dump\"\n    file1.touch()\n    file2.touch()\n\n    pattern = str(tmp_path / \"fake_datafile00??.dump\")\n\n    ts = load(pattern, hint=hint)\n    ds = ts[0]\n    assert type(ds).__name__ == expected_type\n\n    ds = ts[1]\n    assert type(ds).__name__ == expected_type\n\n\n@pytest.fixture()\ndef catchall_dataset_class():\n    # define a Dataset class matching any input,\n    # just so that we don't have to require an actual\n    # dataset for some tests\n    class MockHierarchy(GridIndex):\n        pass\n\n    class MockDataset(Dataset):\n        _index_class = MockHierarchy\n\n        def _parse_parameter_file(self, *args, **kwargs):\n            self.current_time = -1.0\n            self.cosmological_simulation = 0\n\n        def _set_code_unit_attributes(self, *args, **kwargs):\n            self.length_unit = self.quan(1, \"m\")\n            self.mass_unit = self.quan(1, \"kg\")\n            self.time_unit = self.quan(1, \"s\")\n\n        @classmethod\n        def _is_valid(cls, *args, **kwargs):\n            return True\n\n    yield\n\n    # teardown to avoid possible breakage in following tests\n    output_type_registry.pop(\"MockDataset\")\n\n\n@pytest.mark.usefixtures(\"catchall_dataset_class\")\ndef test_depr_load_keyword(tmp_path):\n    with pytest.deprecated_call(\n        match=r\"Using the 'fn' argument as keyword \\(on position 0\\) is deprecated\\.\",\n    ):\n        load(fn=tmp_path)\n\n\n@pytest.fixture()\ndef invalid_dataset_class_with_missing_requirements():\n    # define a Dataset class matching any input,\n    # just so that we don't have to require an actual\n    # dataset for some tests\n    class MockHierarchy(GridIndex):\n        pass\n\n    class MockDataset(Dataset):\n        _load_requirements = [\"mock_package_name\"]\n        _index_class = MockHierarchy\n\n        def _parse_parameter_file(self, *args, **kwargs):\n            self.current_time = -1.0\n            self.cosmological_simulation = 0\n\n        def _set_code_unit_attributes(self, *args, **kwargs):\n            self.length_unit = self.quan(1, \"m\")\n            self.mass_unit = self.quan(1, \"kg\")\n            self.time_unit = self.quan(1, \"s\")\n\n        @classmethod\n        def _is_valid(cls, *args, **kwargs):\n            return False\n\n    yield\n\n    # teardown to avoid possible breakage in following tests\n    output_type_registry.pop(\"MockDataset\")\n\n\n@pytest.mark.usefixtures(\"invalid_dataset_class_with_missing_requirements\")\ndef test_all_invalid_with_missing_requirements(tmp_path):\n    with pytest.raises(\n        YTUnidentifiedDataType,\n        match=re.compile(\n            re.escape(f\"Could not determine input format from {tmp_path!r}\\n\")\n            + \"The following types could not be thorougly checked against your data because \"\n            \"their requirements are missing. \"\n            \"You may want to inspect this list and check your installation:\\n\"\n            r\".*\"\n            r\"- MockDataset \\(requires: mock_package_name\\)\"\n            r\".*\"\n            \"\\n\\n\"\n            \"Please make sure you are running a sufficiently recent version of yt.\",\n            re.DOTALL,\n        ),\n    ):\n        load(tmp_path)\n\n\n@pytest.fixture()\ndef valid_dataset_class_with_missing_requirements():\n    # define a Dataset class matching any input,\n    # just so that we don't have to require an actual\n    # dataset for some tests\n    class MockHierarchy(GridIndex):\n        pass\n\n    class MockDataset(Dataset):\n        _load_requirements = [\"mock_package_name\"]\n        _index_class = MockHierarchy\n\n        def _parse_parameter_file(self, *args, **kwargs):\n            self.current_time = -1.0\n            self.cosmological_simulation = 0\n\n        def _set_code_unit_attributes(self, *args, **kwargs):\n            self.length_unit = self.quan(1, \"m\")\n            self.mass_unit = self.quan(1, \"kg\")\n            self.time_unit = self.quan(1, \"s\")\n\n        @classmethod\n        def _is_valid(cls, *args, **kwargs):\n            return True\n\n    yield\n\n    # teardown to avoid possible breakage in following tests\n    output_type_registry.pop(\"MockDataset\")\n\n\n@pytest.mark.usefixtures(\"valid_dataset_class_with_missing_requirements\")\ndef test_single_valid_with_missing_requirements(tmp_path):\n    with pytest.warns(\n        UserWarning,\n        match=(\n            \"This dataset appears to be of type MockDataset, \"\n            \"but the following requirements are currently missing: mock_package_name\\n\"\n            \"Please verify your installation.\"\n        ),\n    ):\n        load(tmp_path)\n"
  },
  {
    "path": "yt/tests/test_load_sample.py",
    "content": "import logging\nimport os\nimport re\nimport sys\nimport textwrap\n\nimport numpy as np\nimport pytest\n\nfrom yt.config import ytcfg\nfrom yt.loaders import load_sample\nfrom yt.sample_data.api import (\n    get_data_registry_table,\n    get_download_cache_dir,\n)\nfrom yt.testing import requires_module_pytest\nfrom yt.utilities.logger import ytLogger\n\n\n@pytest.fixture()\ndef tmp_data_dir(tmp_path):\n    pre_test_data_dir = ytcfg[\"yt\", \"test_data_dir\"]\n    ytcfg.set(\"yt\", \"test_data_dir\", str(tmp_path))\n\n    yield tmp_path\n\n    ytcfg.set(\"yt\", \"test_data_dir\", pre_test_data_dir)\n\n\n@pytest.fixture()\ndef capturable_logger(caplog):\n    \"\"\"\n    This set the minimal conditions to make pytest's caplog fixture usable.\n    \"\"\"\n\n    propagate = ytLogger.propagate\n    ytLogger.propagate = True\n\n    with caplog.at_level(logging.INFO, \"yt\"):\n        yield\n\n    ytLogger.propagate = propagate\n\n\n@requires_module_pytest(\"pandas\", \"pooch\", \"f90nml\")\n@pytest.mark.usefixtures(\"capturable_logger\")\n@pytest.mark.parametrize(\n    \"fn, archive, exact_loc, class_\",\n    [\n        (\n            \"ToroShockTube\",\n            \"ToroShockTube.tar.gz\",\n            \"ToroShockTube/DD0001/data0001\",\n            \"EnzoDataset\",\n        ),\n        (\n            \"KeplerianDisk\",\n            \"KeplerianDisk.tar.gz\",\n            \"KeplerianDisk/disk.out1.00000.athdf\",\n            \"AthenaPPDataset\",\n        ),\n        # trying with an exact name as well\n        (\n            \"KeplerianDisk/disk.out1.00000.athdf\",\n            \"KeplerianDisk.tar.gz\",\n            \"KeplerianDisk/disk.out1.00000.athdf\",\n            \"AthenaPPDataset\",\n        ),\n        # check this special case because it relies on implementations\n        # details in the AMRVAC frontend (using parfiles)\n        # and could easily fail to load. See GH PR #3343\n        (\n            \"rmi_dust_2d\",\n            \"rmi_dust_2d.tar.gz\",\n            \"rmi_dust_2d/output0001.dat\",\n            \"AMRVACDataset\",\n        ),\n    ],\n)\ndef test_load_sample_small_dataset(\n    fn, archive, exact_loc, class_: str, tmp_data_dir, caplog\n):\n    cache_path = get_download_cache_dir()\n    assert not cache_path.exists()\n\n    ds = load_sample(fn, progressbar=False, timeout=30)\n    assert type(ds).__name__ == class_\n    assert not cache_path.exists()\n\n    text = textwrap.dedent(\n        f\"\"\"\n            '{fn.replace(\"/\", os.path.sep)}' is not available locally. Looking up online.\n            Downloading from https://yt-project.org/data/{archive}\n            Untaring downloaded file to '{str(tmp_data_dir)}'\n        \"\"\"\n    ).strip(\"\\n\")\n    expected = [(\"yt\", 20, message) for message in text.split(\"\\n\")]\n    assert caplog.record_tuples[:3] == expected\n\n    caplog.clear()\n    # loading a second time should not result in a download request\n    ds2 = load_sample(fn)\n    assert type(ds2).__name__ == class_\n\n    assert caplog.record_tuples[0] == (\n        \"yt\",\n        20,\n        f\"Sample dataset found in '{os.path.join(str(tmp_data_dir), *exact_loc.split('/'))}'\",\n    )\n\n\n@pytest.mark.skipif(\n    sys.platform.startswith(\"win\"),\n    # flakyness is probably due to Windows' infamous lack of time resolution\n    # overall, this test doesn't seem worth it.\n    reason=\"This test is flaky on Windows\",\n)\n@requires_module_pytest(\"pandas\", \"pooch\")\n@pytest.mark.usefixtures(\"capturable_logger\")\ndef test_load_sample_timeout(tmp_data_dir, caplog):\n    from requests.exceptions import ConnectTimeout, ReadTimeout\n\n    # note that requests is a direct dependency to pooch,\n    # so we don't need to mark it in a decorator.\n    with pytest.raises((ConnectTimeout, ReadTimeout)):\n        load_sample(\"IsolatedGalaxy\", progressbar=False, timeout=0.00001)\n\n\n@requires_module_pytest(\"pandas\", \"requests\")\n@pytest.mark.xfail(reason=\"Registry is currently incomplete.\")\ndef test_registry_integrity():\n    reg = get_data_registry_table()\n    assert not any(reg.isna())\n\n\n@pytest.fixture()\ndef sound_subreg():\n    # this selection is needed because the full dataset is incomplete\n    # so we test only the values that can be parsed\n    reg = get_data_registry_table()\n    return reg[[\"size\", \"byte_size\"]][reg[\"size\"].notna()]\n\n\n@requires_module_pytest(\"pandas\", \"requests\")\ndef test_registry_byte_size_dtype(sound_subreg):\n    from pandas import Int64Dtype\n\n    assert sound_subreg[\"byte_size\"].dtype == Int64Dtype()\n\n\n@requires_module_pytest(\"pandas\", \"requests\")\ndef test_registry_byte_size_sign(sound_subreg):\n    np.testing.assert_array_less(0, sound_subreg[\"byte_size\"])\n\n\n@requires_module_pytest(\"pandas\", \"requests\")\ndef test_unknown_filename():\n    fake_name = \"these_are_not_the_files_your_looking_for\"\n    with pytest.raises(ValueError, match=f\"'{fake_name}' is not an available dataset.\"):\n        load_sample(fake_name)\n\n\n@requires_module_pytest(\"pandas\", \"requests\")\ndef test_typo_filename():\n    wrong_name = \"Isolatedgalaxy\"\n    with pytest.raises(\n        ValueError,\n        match=re.escape(\n            f\"'{wrong_name}' is not an available dataset. Did you mean 'IsolatedGalaxy' ?\"\n        ),\n    ):\n        load_sample(wrong_name, timeout=1)\n\n\n@pytest.fixture()\ndef fake_data_dir_in_a_vaccum(tmp_path):\n    pre_test_data_dir = ytcfg[\"yt\", \"test_data_dir\"]\n    ytcfg.set(\"yt\", \"test_data_dir\", \"/does/not/exist\")\n    origin = os.getcwd()\n    os.chdir(tmp_path)\n\n    yield\n\n    ytcfg.set(\"yt\", \"test_data_dir\", pre_test_data_dir)\n    os.chdir(origin)\n\n\n@pytest.mark.skipif(\n    sys.platform.startswith(\"win\"),\n    reason=\"can't figure out how to match the warning message in a cross-platform way\",\n)\n@requires_module_pytest(\"pandas\", \"pooch\")\n@pytest.mark.usefixtures(\"fake_data_dir_in_a_vaccum\")\ndef test_data_dir_broken():\n    # check that load_sample still works if the test_data_dir\n    # isn't properly set, in which case we should dl to the\n    # cwd and issue a warning.\n    msg = (\n        r\"Storage directory from yt config doesn't exist \"\n        r\"\\(currently set to '/does/not/exist'\\)\\. \"\n        r\"Current working directory will be used instead\\.\"\n    )\n    with pytest.warns(UserWarning, match=msg):\n        load_sample(\"ToroShockTube\")\n\n\ndef test_filename_none(capsys):\n    assert load_sample() is None\n    captured = capsys.readouterr()\n    assert \"yt.sample_data.api.get_data_registry_table\" in captured.err\n"
  },
  {
    "path": "yt/tests/test_testing.py",
    "content": "from unittest import SkipTest\n\nimport matplotlib\n\nfrom yt.testing import requires_backend\n\nactive_backend = matplotlib.get_backend()\ninactive_backend = ({\"gtkagg\", \"macosx\", \"wx\", \"tkagg\"} - {active_backend}).pop()\n\n\ndef test_requires_inactive_backend():\n    @requires_backend(inactive_backend)\n    def foo():\n        return\n\n    try:\n        foo()\n    except SkipTest:\n        pass\n    else:\n        raise AssertionError(\n            \"@requires_backend appears to be broken (skip was expected)\"\n        )\n\n\ndef test_requires_active_backend():\n    @requires_backend(active_backend)\n    def foo():\n        return\n\n    try:\n        foo()\n    except SkipTest:\n        raise AssertionError(\n            \"@requires_backend appears to be broken (skip was not expected)\"\n        ) from None\n"
  },
  {
    "path": "yt/tests/test_version.py",
    "content": "import pytest\n\nimport yt\nfrom yt._version import VersionTuple, _parse_to_version_info\n\n\n@pytest.mark.parametrize(\n    \"version_str, expected\",\n    (\n        (\"4.1.0\", VersionTuple(4, 1, 0, \"final\", 0)),\n        (\"6.2.5\", VersionTuple(6, 2, 5, \"final\", 0)),\n        (\"4.1.dev0\", VersionTuple(4, 1, 0, \"alpha\", 0)),\n        (\"4.1.0rc\", VersionTuple(4, 1, 0, \"candidate\", 0)),\n        (\"4.1.0rc1\", VersionTuple(4, 1, 0, \"candidate\", 1)),\n        (\"4.1.0rc2\", VersionTuple(4, 1, 0, \"candidate\", 2)),\n    ),\n)\ndef test_parse_version_info(version_str, expected):\n    actual = _parse_to_version_info(version_str)\n    assert actual == expected\n\n\ndef test_version_tuple_comp():\n    # exercise comparison with builtin tuples\n    # comparison results do not matter for this test\n\n    yt.version_info > (4,)  # noqa: B015\n    yt.version_info > (4, 1)  # noqa: B015\n    yt.version_info > (4, 1, 0)  # noqa: B015\n    yt.version_info < (4, 1, 0)  # noqa: B015\n    yt.version_info <= (4, 1, 0)  # noqa: B015\n    yt.version_info >= (4, 1, 0)  # noqa: B015\n    yt.version_info == (4, 1, 0)  # noqa: B015\n\n    assert isinstance(yt.version_info, tuple)\n"
  },
  {
    "path": "yt/units/__init__.py",
    "content": "from unyt.array import (\n    loadtxt,\n    savetxt,\n    unyt_array,\n    unyt_quantity,\n)\nfrom unyt.unit_object import Unit, define_unit  # NOQA: F401\nfrom unyt.unit_registry import UnitRegistry  # NOQA: Ffg401\nfrom unyt.unit_systems import UnitSystem  # NOQA: F401\n\nfrom yt.units.physical_constants import *\nfrom yt.units.physical_constants import _ConstantContainer\nfrom yt.units.unit_symbols import *\nfrom yt.units.unit_symbols import _SymbolContainer\nfrom yt.utilities.exceptions import YTArrayTooLargeToDisplay\nfrom yt.units._numpy_wrapper_functions import (\n    uconcatenate,\n    ucross,\n    udot,\n    uhstack,\n    uintersect1d,\n    unorm,\n    ustack,\n    uunion1d,\n    uvstack,\n)\nYTArray = unyt_array\n\nYTQuantity = unyt_quantity\n\n\nclass UnitContainer:\n    \"\"\"A container for units and constants to associate with a dataset\n\n    This object is usually accessed on a Dataset instance via ``ds.units``.\n\n    Parameters\n    ----------\n    registry : UnitRegistry instance\n        A unit registry to associate with units and constants accessed\n        on this object.\n\n    Example\n    -------\n\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> code_mass = ds.units.code_mass\n    >>> (12 * code_mass).to(\"Msun\")\n    unyt_quantity(4.89719136e+11, 'Msun')\n    >>> code_mass.registry is ds.unit_registry\n    True\n    >>> ds.units.newtons_constant\n    unyt_quantity(6.67384e-08, 'cm**3/(g*s**2)')\n\n    \"\"\"\n\n    def __init__(self, registry):\n        self.unit_symbols = _SymbolContainer(registry)\n        self.physical_constants = _ConstantContainer(registry)\n\n    def __dir__(self):\n        all_dir = self.unit_symbols.__dir__() + self.physical_constants.__dir__()\n        all_dir += object.__dir__(self)\n        return list(set(all_dir))\n\n    def __getattr__(self, item):\n        pc = self.physical_constants\n        us = self.unit_symbols\n        ret = getattr(us, item, None) or getattr(pc, item, None)\n        if not ret:\n            raise AttributeError(item)\n        return ret\n\n\ndef display_ytarray(arr):\n    r\"\"\"\n    Display a YTArray in a Jupyter widget that enables unit switching.\n\n    The array returned by this function is read-only, and only works with\n    arrays of size 3 or lower.\n\n    Parameters\n    ----------\n    arr : YTArray\n        The Array to display; must be of size 3 or lower.\n\n    Examples\n    --------\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> display_ytarray(ds.domain_width)\n    \"\"\"\n    if arr.size > 3:\n        raise YTArrayTooLargeToDisplay(arr.size, 3)\n    import ipywidgets\n\n    unit_registry = arr.units.registry\n    equiv = unit_registry.list_same_dimensions(arr.units)\n    dropdown = ipywidgets.Dropdown(options=sorted(equiv), value=str(arr.units))\n\n    def arr_updater(arr, texts):\n        def _value_updater(change):\n            arr2 = arr.in_units(change[\"new\"])\n            if arr2.shape == ():\n                arr2 = [arr2]\n            for v, t in zip(arr2, texts):\n                t.value = str(v.value)\n\n        return _value_updater\n\n    if arr.shape == ():\n        arr_iter = [arr]\n    else:\n        arr_iter = arr\n    texts = [ipywidgets.Text(value=str(_.value), disabled=True) for _ in arr_iter]\n    dropdown.observe(arr_updater(arr, texts), names=\"value\")\n    return ipywidgets.HBox(texts + [dropdown])\n\n\ndef _wrap_display_ytarray(arr):\n    from IPython.display import display\n\n    display(display_ytarray(arr))\n"
  },
  {
    "path": "yt/units/_numpy_wrapper_functions.py",
    "content": "# This module is not part of the public namespace `yt.units`\n# It is home to wrapper functions that are directly copied from unyt 2.9.2\n# We vendor them as a transition step towards unyt 3.0 (in development),\n# where these wrapper functions are deprecated and are should be replaced with vanilla numpy API\n# FUTURE:\n# - require unyt>=3.0\n# - deprecate these functions in yt too\n\nfrom unyt import unyt_array, unyt_quantity\nimport numpy as np\n\n\ndef _validate_numpy_wrapper_units(v, arrs):\n    if not any(isinstance(a, unyt_array) for a in arrs):\n        return v\n    if not all(isinstance(a, unyt_array) for a in arrs):\n        raise RuntimeError(\"Not all of your arrays are unyt_arrays.\")\n    a1 = arrs[0]\n    if not all(a.units == a1.units for a in arrs[1:]):\n        raise RuntimeError(\"Your arrays must have identical units.\")\n    v.units = a1.units\n    return v\n\n\ndef uconcatenate(arrs, axis=0):\n    \"\"\"Concatenate a sequence of arrays.\n\n    This wrapper around numpy.concatenate preserves units. All input arrays\n    must have the same units.  See the documentation of numpy.concatenate for\n    full details.\n\n    Examples\n    --------\n    >>> from unyt import cm\n    >>> A = [1, 2, 3]*cm\n    >>> B = [2, 3, 4]*cm\n    >>> uconcatenate((A, B))\n    unyt_array([1, 2, 3, 2, 3, 4], 'cm')\n\n    \"\"\"\n    v = np.concatenate(arrs, axis=axis)\n    v = _validate_numpy_wrapper_units(v, arrs)\n    return v\n\n\ndef ucross(arr1, arr2, registry=None, axisa=-1, axisb=-1, axisc=-1, axis=None):\n    \"\"\"Applies the cross product to two YT arrays.\n\n    This wrapper around numpy.cross preserves units.\n    See the documentation of numpy.cross for full\n    details.\n    \"\"\"\n    v = np.cross(arr1, arr2, axisa=axisa, axisb=axisb, axisc=axisc, axis=axis)\n    units = arr1.units * arr2.units\n    arr = unyt_array(v, units, registry=registry)\n    return arr\n\n\ndef uintersect1d(arr1, arr2, assume_unique=False):\n    \"\"\"Find the sorted unique elements of the two input arrays.\n\n    A wrapper around numpy.intersect1d that preserves units.  All input arrays\n    must have the same units.  See the documentation of numpy.intersect1d for\n    full details.\n\n    Examples\n    --------\n    >>> from unyt import cm\n    >>> A = [1, 2, 3]*cm\n    >>> B = [2, 3, 4]*cm\n    >>> uintersect1d(A, B)\n    unyt_array([2, 3], 'cm')\n\n    \"\"\"\n    v = np.intersect1d(arr1, arr2, assume_unique=assume_unique)\n    v = _validate_numpy_wrapper_units(v, [arr1, arr2])\n    return v\n\n\ndef uunion1d(arr1, arr2):\n    \"\"\"Find the union of two arrays.\n\n    A wrapper around numpy.intersect1d that preserves units.  All input arrays\n    must have the same units.  See the documentation of numpy.intersect1d for\n    full details.\n\n    Examples\n    --------\n    >>> from unyt import cm\n    >>> A = [1, 2, 3]*cm\n    >>> B = [2, 3, 4]*cm\n    >>> uunion1d(A, B)\n    unyt_array([1, 2, 3, 4], 'cm')\n\n    \"\"\"\n    v = np.union1d(arr1, arr2)\n    v = _validate_numpy_wrapper_units(v, [arr1, arr2])\n    return v\n\n\ndef unorm(data, ord=None, axis=None, keepdims=False):\n    \"\"\"Matrix or vector norm that preserves units\n\n    This is a wrapper around np.linalg.norm that preserves units. See\n    the documentation for that function for descriptions of the keyword\n    arguments.\n\n    Examples\n    --------\n    >>> from unyt import km\n    >>> data = [1, 2, 3]*km\n    >>> print(unorm(data))\n    3.7416573867739413 km\n    \"\"\"\n    norm = np.linalg.norm(data, ord=ord, axis=axis, keepdims=keepdims)\n    if norm.shape == ():\n        return unyt_quantity(norm, data.units)\n    return unyt_array(norm, data.units)\n\n\ndef udot(op1, op2):\n    \"\"\"Matrix or vector dot product that preserves units\n\n    This is a wrapper around np.dot that preserves units.\n\n    Examples\n    --------\n    >>> from unyt import km, s\n    >>> a = np.eye(2)*km\n    >>> b = (np.ones((2, 2)) * 2)*s\n    >>> print(udot(a, b))\n    [[2. 2.]\n     [2. 2.]] km*s\n    \"\"\"\n    dot = np.dot(op1.d, op2.d)\n    units = op1.units * op2.units\n    if dot.shape == ():\n        return unyt_quantity(dot, units)\n    return unyt_array(dot, units)\n\n\ndef uvstack(arrs):\n    \"\"\"Stack arrays in sequence vertically (row wise) while preserving units\n\n    This is a wrapper around np.vstack that preserves units.\n\n    Examples\n    --------\n    >>> from unyt import km\n    >>> a = [1, 2, 3]*km\n    >>> b = [2, 3, 4]*km\n    >>> print(uvstack([a, b]))\n    [[1 2 3]\n     [2 3 4]] km\n    \"\"\"\n    v = np.vstack(arrs)\n    v = _validate_numpy_wrapper_units(v, arrs)\n    return v\n\n\ndef uhstack(arrs):\n    \"\"\"Stack arrays in sequence horizontally while preserving units\n\n    This is a wrapper around np.hstack that preserves units.\n\n    Examples\n    --------\n    >>> from unyt import km\n    >>> a = [1, 2, 3]*km\n    >>> b = [2, 3, 4]*km\n    >>> print(uhstack([a, b]))\n    [1 2 3 2 3 4] km\n    >>> a = [[1],[2],[3]]*km\n    >>> b = [[2],[3],[4]]*km\n    >>> print(uhstack([a, b]))\n    [[1 2]\n     [2 3]\n     [3 4]] km\n    \"\"\"\n    v = np.hstack(arrs)\n    v = _validate_numpy_wrapper_units(v, arrs)\n    return v\n\n\ndef ustack(arrs, axis=0):\n    \"\"\"Join a sequence of arrays along a new axis while preserving units\n\n    The axis parameter specifies the index of the new axis in the\n    dimensions of the result. For example, if ``axis=0`` it will be the\n    first dimension and if ``axis=-1`` it will be the last dimension.\n\n    This is a wrapper around np.stack that preserves units. See the\n    documentation for np.stack for full details.\n\n    Examples\n    --------\n    >>> from unyt import km\n    >>> a = [1, 2, 3]*km\n    >>> b = [2, 3, 4]*km\n    >>> print(ustack([a, b]))\n    [[1 2 3]\n     [2 3 4]] km\n    \"\"\"\n    v = np.stack(arrs, axis=axis)\n    v = _validate_numpy_wrapper_units(v, arrs)\n    return v\n"
  },
  {
    "path": "yt/units/dimensions.py",
    "content": "from unyt.dimensions import current_mks # explicit re-export to enable type checking\nfrom unyt.dimensions import *\n"
  },
  {
    "path": "yt/units/equivalencies.py",
    "content": "from unyt.equivalencies import *\n"
  },
  {
    "path": "yt/units/physical_constants.py",
    "content": "from unyt.array import unyt_quantity\nfrom unyt.unit_systems import add_constants\n\nfrom yt.units.unit_registry import default_unit_registry\n\nadd_constants(globals(), registry=default_unit_registry)\n\n\nclass _ConstantContainer:\n    \"\"\"A container for physical constants to associate with a dataset.\n\n    This object is usually accessed on a Dataset instance via\n    ``ds.units.physical_constants``.\n\n    Parameters\n    ----------\n    registry : UnitRegistry instance\n        A unit registry to associate with units constants accessed on\n        this object.\n\n    Example\n    -------\n\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ds.units.physical_constants.newtons_constant\n    unyt_quantity(6.67384e-08, 'cm**3/(g*s**2)')\n    \"\"\"\n\n    def __init__(self, registry):\n        self._registry = registry\n        self._cache = {}\n\n    def __dir__(self):\n        ret = [p for p in globals() if not p.startswith(\"_\")] + object.__dir__(self)\n        return list(set(ret))\n\n    def __getattr__(self, item):\n        if item in self._cache:\n            return self._cache[item]\n        if item in globals():\n            const = globals()[item].copy()\n            const.units.registry = self._registry\n            const.convert_to_base(self._registry.unit_system)\n            const_v, const_unit = const.v, const.units\n            ret = unyt_quantity(const_v, const_unit, registry=self._registry)\n            self._cache[item] = ret\n            return ret\n        raise AttributeError(item)\n"
  },
  {
    "path": "yt/units/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/units/tests/test_magnetic_code_units.py",
    "content": "import numpy as np\n\nfrom yt.loaders import load_uniform_grid\nfrom numpy.testing import assert_allclose\n\n\ndef test_magnetic_code_units():\n\n    sqrt4pi = np.sqrt(4.0 * np.pi)\n    ddims = (16,) * 3\n    data = {\"density\": (np.random.uniform(size=ddims), \"g/cm**3\")}\n\n    ds1 = load_uniform_grid(\n        data, ddims, magnetic_unit=(sqrt4pi, \"gauss\"), unit_system=\"cgs\"\n    )\n\n    assert_allclose(ds1.magnetic_unit.value, sqrt4pi)\n    assert str(ds1.magnetic_unit.units) == \"G\"\n\n    mucu = ds1.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mucu.value, 1.0)\n    assert str(mucu.units) == \"code_magnetic\"\n\n    ds2 = load_uniform_grid(data, ddims, magnetic_unit=(1.0, \"T\"), unit_system=\"cgs\")\n    assert_allclose(ds2.magnetic_unit.value, 10000.0)\n    assert str(ds2.magnetic_unit.units) == \"G\"\n\n    mucu = ds2.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mucu.value, 1.0)\n    assert str(mucu.units) == \"code_magnetic\"\n\n    ds3 = load_uniform_grid(data, ddims, magnetic_unit=(1.0, \"T\"), unit_system=\"mks\")\n\n    assert_allclose(ds3.magnetic_unit.value, 1.0)\n    assert str(ds3.magnetic_unit.units) == \"T\"\n\n    mucu = ds3.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mucu.value, 1.0)\n    assert str(mucu.units) == \"code_magnetic\"\n\n    ds4 = load_uniform_grid(\n        data, ddims, magnetic_unit=(1.0, \"gauss\"), unit_system=\"mks\"\n    )\n\n    assert_allclose(ds4.magnetic_unit.value, 1.0e-4)\n    assert str(ds4.magnetic_unit.units) == \"T\"\n\n    mucu = ds4.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mucu.value, 1.0)\n    assert str(mucu.units) == \"code_magnetic\"\n\n    ds5 = load_uniform_grid(\n        data, ddims, magnetic_unit=(1.0, \"uG\"), unit_system=\"mks\"\n    )\n\n    assert_allclose(ds5.magnetic_unit.value, 1.0e-10)\n    assert str(ds5.magnetic_unit.units) == \"T\"\n\n    mucu = ds5.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mucu.value, 1.0)\n    assert str(mucu.units) == \"code_magnetic\"\n\n    ds6 = load_uniform_grid(\n        data, ddims, magnetic_unit=(1.0, \"nT\"), unit_system=\"cgs\"\n    )\n\n    assert_allclose(ds6.magnetic_unit.value, 1.0e-5)\n    assert str(ds6.magnetic_unit.units) == \"G\"\n\n    mucu = ds6.magnetic_unit.to(\"code_magnetic\")\n    assert_allclose(mucu.value, 1.0)\n    assert str(mucu.units) == \"code_magnetic\"\n"
  },
  {
    "path": "yt/units/unit_lookup_table.py",
    "content": "from unyt._unit_lookup_table import *\n"
  },
  {
    "path": "yt/units/unit_object.py",
    "content": "from unyt.unit_object import *\n"
  },
  {
    "path": "yt/units/unit_registry.py",
    "content": "from unyt.dimensions import dimensionless\nfrom unyt.unit_registry import *\n\ndefault_unit_registry = UnitRegistry(unit_system=\"cgs\") # type: ignore\n\ndefault_unit_registry.add(\"h\", 1.0, dimensionless, tex_repr=r\"h\")\n"
  },
  {
    "path": "yt/units/unit_symbols.py",
    "content": "from unyt.unit_object import Unit\nfrom unyt.unit_systems import add_symbols\n\nfrom yt.units.unit_registry import default_unit_registry\n\nadd_symbols(globals(), registry=default_unit_registry)\n\n\nclass _SymbolContainer:\n    \"\"\"A container for units to associate with a dataset.\n\n    This object is usually accessed on a Dataset instance via\n    ``ds.units.unit_symbols``.\n\n    Parameters\n    ----------\n    registry : UnitRegistry instance\n        A unit registry to associate with units accessed on this object.\n\n    Example\n    -------\n\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> code_mass = ds.units.code_mass\n    >>> (12 * code_mass).to(\"Msun\")\n    unyt_quantity(4.89719136e+11, 'Msun')\n    >>> code_mass.registry is ds.unit_registry\n    True\n    \"\"\"\n\n    def __init__(self, registry):\n        self._registry = registry\n        self._cache = {}\n\n    def __dir__(self):\n        ret = [u for u in globals() if not u.startswith(\"_\")]\n        ret += list(self._registry.keys())\n        ret += object.__dir__(self)\n        return list(set(ret))\n\n    def __getattr__(self, item):\n        if item in self._cache:\n            return self._cache[item]\n        if hasattr(globals(), item):\n            ret = Unit(globals()[item].expr, registry=self._registry)\n        elif item in self._registry:\n            ret = Unit(item, registry=self._registry)\n        else:\n            raise AttributeError(item)\n        self._cache[item] = ret\n        return ret\n"
  },
  {
    "path": "yt/units/unit_systems.py",
    "content": "from unyt.unit_systems import *\n\n\ndef create_code_unit_system(unit_registry, current_mks_unit=None):\n    code_unit_system = UnitSystem(\n        name=unit_registry.unit_system_id,\n        length_unit=\"code_length\",\n        mass_unit=\"code_mass\",\n        time_unit=\"code_time\",\n        temperature_unit=\"code_temperature\",\n        current_mks_unit=current_mks_unit,\n        registry=unit_registry,\n    )\n    code_unit_system[\"velocity\"] = \"code_velocity\"\n    if current_mks_unit:\n        code_unit_system[\"magnetic_field_mks\"] = \"code_magnetic\"\n    else:\n        code_unit_system[\"magnetic_field_cgs\"] = \"code_magnetic\"\n    code_unit_system[\"pressure\"] = \"code_pressure\"\n    return code_unit_system\n"
  },
  {
    "path": "yt/units/yt_array.py",
    "content": "from unyt.array import *  # NOQA: F403, F401\n\nfrom yt.funcs import array_like_field  # NOQA: F401\nfrom yt.units import YTArray, YTQuantity  # NOQA: F401\n"
  },
  {
    "path": "yt/utilities/__init__.py",
    "content": ""
  },
  {
    "path": "yt/utilities/amr_kdtree/__init__.py",
    "content": "\"\"\"\nInitialize amr_kdtree\n\n\"\"\"\n"
  },
  {
    "path": "yt/utilities/amr_kdtree/amr_kdtools.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog\n\n\ndef receive_and_reduce(comm, incoming_rank, image, add_to_front, *, use_opacity=True):\n    mylog.debug(\"Receiving image from %04i\", incoming_rank)\n    # mylog.debug( '%04i receiving image from %04i'%(self.comm.rank,back.owner))\n    arr2 = comm.recv_array(incoming_rank, incoming_rank).reshape(\n        (image.shape[0], image.shape[1], image.shape[2])\n    )\n\n    if not use_opacity:\n        np.add(image, arr2, image)\n        return image\n\n    if add_to_front:\n        front = arr2\n        back = image\n    else:\n        front = image\n        back = arr2\n\n    if image.shape[2] == 3:\n        # Assume Projection Camera, Add\n        np.add(image, front, image)\n        return image\n\n    ta = 1.0 - front[:, :, 3]\n    np.maximum(ta, 0.0, out=ta)\n    # This now does the following calculation, but in a memory\n    # conservative fashion\n    # image[:,:,i  ] = front[:,:,i] + ta*back[:,:,i]\n    image = back.copy()\n    for i in range(4):\n        np.multiply(image[:, :, i], ta, image[:, :, i])\n    np.add(image, front, image)\n\n    return image\n\n\ndef send_to_parent(comm, outgoing_rank, image):\n    mylog.debug(\"Sending image to %04i\", outgoing_rank)\n    comm.send_array(image, outgoing_rank, tag=comm.rank)\n\n\ndef scatter_image(comm, root, image):\n    mylog.debug(\"Scattering from %04i\", root)\n    image = comm.mpi_bcast(image, root=root)\n    return image\n"
  },
  {
    "path": "yt/utilities/amr_kdtree/amr_kdtree.py",
    "content": "import operator\n\nimport numpy as np\n\nfrom yt.funcs import is_sequence, mylog\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.utilities.amr_kdtree.amr_kdtools import (\n    receive_and_reduce,\n    scatter_image,\n    send_to_parent,\n)\nfrom yt.utilities.lib.amr_kdtools import Node\nfrom yt.utilities.lib.partitioned_grid import PartitionedGrid\nfrom yt.utilities.math_utils import periodic_position\nfrom yt.utilities.on_demand_imports import _h5py as h5py\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    ParallelAnalysisInterface,\n)\n\nsteps = np.array(\n    [\n        [-1, -1, -1],\n        [-1, -1, 0],\n        [-1, -1, 1],\n        [-1, 0, -1],\n        [-1, 0, 0],\n        [-1, 0, 1],\n        [-1, 1, -1],\n        [-1, 1, 0],\n        [-1, 1, 1],\n        [0, -1, -1],\n        [0, -1, 0],\n        [0, -1, 1],\n        [0, 0, -1],\n        # [ 0,  0,  0],\n        [0, 0, 1],\n        [0, 1, -1],\n        [0, 1, 0],\n        [0, 1, 1],\n        [1, -1, -1],\n        [1, -1, 0],\n        [1, -1, 1],\n        [1, 0, -1],\n        [1, 0, 0],\n        [1, 0, 1],\n        [1, 1, -1],\n        [1, 1, 0],\n        [1, 1, 1],\n    ]\n)\n\n\ndef _apply_log(data, log_changed, log_new):\n    \"\"\"Helper used to set log10/10^ to data in AMRKDTree\"\"\"\n    if not log_changed:\n        return\n    if log_new:\n        np.log10(data, data)\n    else:\n        np.power(10.0, data, data)\n\n\nclass Tree:\n    def __init__(\n        self,\n        ds,\n        comm_rank=0,\n        comm_size=1,\n        left=None,\n        right=None,\n        min_level=None,\n        max_level=None,\n        data_source=None,\n    ):\n        self.ds = ds\n        try:\n            self._id_offset = ds.index.grids[0]._id_offset\n        except AttributeError:\n            self._id_offset = 0\n\n        if data_source is None:\n            data_source = ds.all_data()\n        self.data_source = data_source\n        if left is None:\n            left = np.array([-np.inf] * 3)\n        if right is None:\n            right = np.array([np.inf] * 3)\n\n        if min_level is None:\n            min_level = 0\n        if max_level is None:\n            max_level = ds.index.max_level\n        self.min_level = min_level\n        self.max_level = max_level\n        self.comm_rank = comm_rank\n        self.comm_size = comm_size\n        self.trunk = Node(None, None, None, left, right, -1, 1)\n        self.build()\n\n    def add_grids(self, grids):\n        gles = np.array([g.LeftEdge for g in grids])\n        gres = np.array([g.RightEdge for g in grids])\n        gids = np.array([g.id for g in grids], dtype=\"int64\")\n        self.trunk.add_grids(\n            gids.size, gles, gres, gids, self.comm_rank, self.comm_size\n        )\n        del gles, gres, gids, grids\n\n    def build(self):\n        lvl_range = range(self.min_level, self.max_level + 1)\n        for lvl in lvl_range:\n            # grids = self.data_source.select_grids(lvl)\n            grids = np.array(\n                [b for b, mask in self.data_source.blocks if b.Level == lvl]\n            )\n            if len(grids) == 0:\n                continue\n            self.add_grids(grids)\n\n    def check_tree(self):\n        for node in self.trunk.depth_traverse():\n            if node.grid == -1:\n                continue\n            grid = self.ds.index.grids[node.grid - self._id_offset]\n            dds = grid.dds\n            gle = grid.LeftEdge\n            nle = self.ds.arr(node.get_left_edge(), units=\"code_length\")\n            nre = self.ds.arr(node.get_right_edge(), units=\"code_length\")\n            li = np.rint((nle - gle) / dds).astype(\"int32\")\n            ri = np.rint((nre - gle) / dds).astype(\"int32\")\n            dims = ri - li\n            assert np.all(grid.LeftEdge <= nle)\n            assert np.all(grid.RightEdge >= nre)\n            assert np.all(dims > 0)\n            # print(grid, dims, li, ri)\n\n        # Calculate the Volume\n        vol = self.trunk.kd_sum_volume()\n        mylog.debug(\"AMRKDTree volume = %e\", vol)\n        self.trunk.kd_node_check()\n\n    def sum_cells(self, all_cells=False):\n        cells = 0\n        for node in self.trunk.depth_traverse():\n            if node.grid == -1:\n                continue\n            if not all_cells and not node.kd_is_leaf():\n                continue\n            grid = self.ds.index.grids[node.grid - self._id_offset]\n            dds = grid.dds\n            gle = grid.LeftEdge\n            nle = self.ds.arr(node.get_left_edge(), units=\"code_length\")\n            nre = self.ds.arr(node.get_right_edge(), units=\"code_length\")\n            li = np.rint((nle - gle) / dds).astype(\"int32\")\n            ri = np.rint((nre - gle) / dds).astype(\"int32\")\n            dims = ri - li\n            cells += np.prod(dims)\n        return cells\n\n\nclass AMRKDTree(ParallelAnalysisInterface):\n    r\"\"\"A KDTree for AMR data.\n\n    Not applicable to particle or octree-based datasets.\n\n    \"\"\"\n\n    fields = None\n    log_fields = None\n    no_ghost = True\n\n    def __init__(self, ds, min_level=None, max_level=None, data_source=None):\n        if not issubclass(ds.index.__class__, GridIndex):\n            raise RuntimeError(\n                \"AMRKDTree does not support particle or octree-based data.\"\n            )\n\n        ParallelAnalysisInterface.__init__(self)\n\n        self.ds = ds\n        self.current_vcds = []\n        self.current_saved_grids = []\n        self.bricks = []\n        self.brick_dimensions = []\n        self.sdx = ds.index.get_smallest_dx()\n\n        self._initialized = False\n        try:\n            self._id_offset = ds.index.grids[0]._id_offset\n        except AttributeError:\n            self._id_offset = 0\n\n        if data_source is None:\n            data_source = self.ds.all_data()\n        self.data_source = data_source\n\n        mylog.debug(\"Building AMRKDTree\")\n        self.tree = Tree(\n            ds,\n            self.comm.rank,\n            self.comm.size,\n            min_level=min_level,\n            max_level=max_level,\n            data_source=data_source,\n        )\n\n    def set_fields(self, fields, log_fields, no_ghost, force=False):\n        new_fields = self.data_source._determine_fields(fields)\n        regenerate_data = (\n            self.fields is None\n            or len(self.fields) != len(new_fields)\n            or self.fields != new_fields\n            or force\n        )\n        if not is_sequence(log_fields):\n            log_fields = [log_fields]\n        new_log_fields = list(log_fields)\n        self.tree.trunk.set_dirty(regenerate_data)\n        self.fields = new_fields\n\n        if self.log_fields is not None and not regenerate_data:\n            flip_log = list(map(operator.ne, self.log_fields, new_log_fields))\n        else:\n            flip_log = [False] * len(new_log_fields)\n        self.log_fields = new_log_fields\n\n        self.no_ghost = no_ghost\n        del self.bricks, self.brick_dimensions\n        self.brick_dimensions = []\n        bricks = []\n\n        for b in self.traverse():\n            list(map(_apply_log, b.my_data, flip_log, self.log_fields))\n            bricks.append(b)\n        self.bricks = np.array(bricks)\n        self.brick_dimensions = np.array(self.brick_dimensions)\n        self._initialized = True\n\n    def initialize_source(self, fields, log_fields, no_ghost):\n        if (\n            fields == self.fields\n            and log_fields == self.log_fields\n            and no_ghost == self.no_ghost\n        ):\n            return\n        self.set_fields(fields, log_fields, no_ghost)\n\n    def traverse(self, viewpoint=None):\n        for node in self.tree.trunk.kd_traverse(viewpoint=viewpoint):\n            yield self.get_brick_data(node)\n\n    def slice_traverse(self, viewpoint=None):\n        if not hasattr(self.ds.index, \"grid\"):\n            raise NotImplementedError\n        for node in self.tree.trunk.kd_traverse(viewpoint=viewpoint):\n            grid = self.ds.index.grids[node.grid - self._id_offset]\n            dds = grid.dds\n            gle = grid.LeftEdge.in_units(\"code_length\").ndarray_view()\n            nle = node.get_left_edge()\n            nre = node.get_right_edge()\n            li = np.rint((nle - gle) / dds).astype(\"int32\")\n            ri = np.rint((nre - gle) / dds).astype(\"int32\")\n            dims = ri - li\n            sl = (slice(li[0], ri[0]), slice(li[1], ri[1]), slice(li[2], ri[2]))\n            gi = grid.get_global_startindex() + li\n            yield grid, node, (sl, dims, gi)\n\n    def get_node(self, nodeid):\n        path = np.binary_repr(nodeid)\n        depth = 1\n        temp = self.tree.trunk\n        for depth in range(1, len(path)):\n            if path[depth] == \"0\":\n                temp = temp.left\n            else:\n                temp = temp.right\n        assert temp is not None\n        return temp\n\n    def locate_node(self, pos):\n        return self.tree.trunk.find_node(pos)\n\n    def get_reduce_owners(self):\n        owners = {}\n        for bottom_id in range(self.comm.size, 2 * self.comm.size):\n            temp = self.get_node(bottom_id)\n            owners[temp.node_id] = temp.node_id - self.comm.size\n            while temp is not None:\n                if temp.parent is None:\n                    break\n                if temp == temp.parent.right:\n                    break\n                temp = temp.parent\n                owners[temp.node_id] = owners[temp.left.node_id]\n        return owners\n\n    def reduce_tree_images(self, image, viewpoint, *, use_opacity=True):\n        if self.comm.size <= 1:\n            return image\n        myrank = self.comm.rank\n        nprocs = self.comm.size\n        owners = self.get_reduce_owners()\n        node = self.get_node(nprocs + myrank)\n\n        while owners[node.parent.node_id] == myrank:\n            split_dim = node.parent.get_split_dim()\n            split_pos = node.parent.get_split_pos()\n            add_to_front = viewpoint[split_dim] >= split_pos\n            image = receive_and_reduce(\n                self.comm,\n                owners[node.parent.right.node_id],\n                image,\n                add_to_front,\n                use_opacity=use_opacity,\n            )\n            if node.parent.node_id == 1:\n                break\n            else:\n                node = node.parent\n        else:\n            send_to_parent(self.comm, owners[node.parent.node_id], image)\n\n        return scatter_image(self.comm, owners[1], image)\n\n    def get_brick_data(self, node):\n        if node.data is not None and not node.dirty:\n            return node.data\n        grid = self.ds.index.grids[node.grid - self._id_offset]\n        dds = grid.dds.ndarray_view()\n        gle = grid.LeftEdge.ndarray_view()\n        nle = node.get_left_edge()\n        nre = node.get_right_edge()\n        li = np.rint((nle - gle) / dds).astype(\"int32\")\n        ri = np.rint((nre - gle) / dds).astype(\"int32\")\n        dims = ri - li\n        assert np.all(grid.LeftEdge <= nle)\n        assert np.all(grid.RightEdge >= nre)\n\n        if grid in self.current_saved_grids and not node.dirty:\n            dds = self.current_vcds[self.current_saved_grids.index(grid)]\n        else:\n            dds = []\n            vcd = grid.get_vertex_centered_data(\n                self.fields, smoothed=True, no_ghost=self.no_ghost\n            )\n            for i, field in enumerate(self.fields):\n                if self.log_fields[i]:\n                    v = vcd[field].astype(\"float64\")\n                    v[v <= 0] = np.nan\n                    dds.append(np.log10(v))\n                else:\n                    dds.append(vcd[field].astype(\"float64\"))\n                self.current_saved_grids.append(grid)\n                self.current_vcds.append(dds)\n\n        if self.data_source.selector is None:\n            mask = np.ones(dims, dtype=\"uint8\")\n        else:\n            mask, _ = self.data_source.selector.fill_mask_regular_grid(grid)\n            mask = mask[li[0] : ri[0], li[1] : ri[1], li[2] : ri[2]].astype(\"uint8\")\n\n        data = [\n            d[li[0] : ri[0] + 1, li[1] : ri[1] + 1, li[2] : ri[2] + 1].copy()\n            for d in dds\n        ]\n\n        brick = PartitionedGrid(\n            grid.id, data, mask, nle.copy(), nre.copy(), dims.astype(\"int64\")\n        )\n        node.data = brick\n        node.dirty = False\n        if not self._initialized:\n            self.brick_dimensions.append(dims)\n        return brick\n\n    def locate_neighbors(self, grid, ci):\n        r\"\"\"Given a grid and cell index, finds the 26 neighbor grids\n        and cell indices.\n\n        Parameters\n        ----------\n        grid: Grid Object\n            Grid containing the cell of interest\n        ci: array-like\n            The cell index of the cell of interest\n\n        Returns\n        -------\n        grids: Numpy array of Grid objects\n        cis: List of neighbor cell index tuples\n\n        Both of these are neighbors that, relative to the current cell\n        index (i,j,k), are ordered as:\n\n        (i-1, j-1, k-1), (i-1, j-1, k ), (i-1, j-1, k+1), ...\n        (i-1, j  , k-1), (i-1, j  , k ), (i-1, j  , k+1), ...\n        (i+1, j+1, k-1), (i-1, j-1, k ), (i+1, j+1, k+1)\n\n        That is they start from the lower left and proceed to upper\n        right varying the third index most frequently. Note that the\n        center cell (i,j,k) is omitted.\n\n        \"\"\"\n        ci = np.array(ci)\n        center_dds = grid.dds\n        position = grid.LeftEdge + (np.array(ci) + 0.5) * grid.dds\n        grids = np.empty(26, dtype=\"object\")\n        cis = np.empty([26, 3], dtype=\"int64\")\n        offs = 0.5 * (center_dds + self.sdx)\n\n        new_cis = ci + steps\n        in_grid = np.all((new_cis >= 0) * (new_cis < grid.ActiveDimensions), axis=1)\n        new_positions = position + steps * offs\n        new_positions = [periodic_position(p, self.ds) for p in new_positions]\n        grids[in_grid] = grid\n\n        get_them = np.argwhere(in_grid).ravel()\n        cis[in_grid] = new_cis[in_grid]\n\n        if (in_grid).sum() > 0:\n            grids[np.logical_not(in_grid)] = [\n                self.ds.index.grids[\n                    self.locate_node(new_positions[i]).grid - self._id_offset\n                ]\n                for i in get_them\n            ]\n            cis[np.logical_not(in_grid)] = [\n                (new_positions[i] - grids[i].LeftEdge) / grids[i].dds for i in get_them\n            ]\n        cis = [tuple(_ci) for _ci in cis]\n        return grids, cis\n\n    def locate_neighbors_from_position(self, position):\n        r\"\"\"Given a position, finds the 26 neighbor grids\n        and cell indices.\n\n        This is a mostly a wrapper for locate_neighbors.\n\n        Parameters\n        ----------\n        position: array-like\n            Position of interest\n\n        Returns\n        -------\n        grids: Numpy array of Grid objects\n        cis: List of neighbor cell index tuples\n\n        Both of these are neighbors that, relative to the current cell\n        index (i,j,k), are ordered as:\n\n        (i-1, j-1, k-1), (i-1, j-1, k ), (i-1, j-1, k+1), ...\n        (i-1, j  , k-1), (i-1, j  , k ), (i-1, j  , k+1), ...\n        (i+1, j+1, k-1), (i-1, j-1, k ), (i+1, j+1, k+1)\n\n        That is they start from the lower left and proceed to upper\n        right varying the third index most frequently. Note that the\n        center cell (i,j,k) is omitted.\n\n        \"\"\"\n        position = np.array(position)\n        grid = self.ds.index.grids[self.locate_node(position).grid - self._id_offset]\n        ci = ((position - grid.LeftEdge) / grid.dds).astype(\"int64\")\n        return self.locate_neighbors(grid, ci)\n\n    def store_kd_bricks(self, fn=None):\n        if not self._initialized:\n            self.initialize_source()\n        if fn is None:\n            fn = f\"{self.ds}_kd_bricks.h5\"\n        if self.comm.rank != 0:\n            self.comm.recv_array(self.comm.rank - 1, tag=self.comm.rank - 1)\n        f = h5py.File(fn, mode=\"w\")\n        for node in self.tree.depth_traverse():\n            i = node.node_id\n            if node.data is not None:\n                for fi, field in enumerate(self.fields):\n                    try:\n                        f.create_dataset(\n                            f\"/brick_{hex(i)}_{field}\",\n                            data=node.data.my_data[fi].astype(\"float64\"),\n                        )\n                    except Exception:\n                        pass\n        f.close()\n        del f\n        if self.comm.rank != (self.comm.size - 1):\n            self.comm.send_array([0], self.comm.rank + 1, tag=self.comm.rank)\n\n    def load_kd_bricks(self, fn=None):\n        if fn is None:\n            fn = f\"{self.ds}_kd_bricks.h5\"\n        if self.comm.rank != 0:\n            self.comm.recv_array(self.comm.rank - 1, tag=self.comm.rank - 1)\n        try:\n            f = h5py.File(fn, mode=\"a\")\n            for node in self.tree.depth_traverse():\n                i = node.node_id\n                if node.grid != -1:\n                    data = [\n                        f[f\"brick_{hex(i)}_{field}\"][:].astype(\"float64\")\n                        for field in self.fields\n                    ]\n                    node.data = PartitionedGrid(\n                        node.grid.id,\n                        data,\n                        node.l_corner.copy(),\n                        node.r_corner.copy(),\n                        node.dims.astype(\"int64\"),\n                    )\n\n                    self.bricks.append(node.data)\n                    self.brick_dimensions.append(node.dims)\n\n            self.bricks = np.array(self.bricks)\n            self.brick_dimensions = np.array(self.brick_dimensions)\n\n            self._initialized = True\n            f.close()\n            del f\n        except Exception:\n            pass\n        if self.comm.rank != (self.comm.size - 1):\n            self.comm.send_array([0], self.comm.rank + 1, tag=self.comm.rank)\n\n    def join_parallel_trees(self):\n        if self.comm.size == 0:\n            return\n        nid, pid, lid, rid, les, res, gid, splitdims, splitposs = self.get_node_arrays()\n        nid = self.comm.par_combine_object(nid, \"cat\", \"list\")\n        pid = self.comm.par_combine_object(pid, \"cat\", \"list\")\n        lid = self.comm.par_combine_object(lid, \"cat\", \"list\")\n        rid = self.comm.par_combine_object(rid, \"cat\", \"list\")\n        gid = self.comm.par_combine_object(gid, \"cat\", \"list\")\n        les = self.comm.par_combine_object(les, \"cat\", \"list\")\n        res = self.comm.par_combine_object(res, \"cat\", \"list\")\n        splitdims = self.comm.par_combine_object(splitdims, \"cat\", \"list\")\n        splitposs = self.comm.par_combine_object(splitposs, \"cat\", \"list\")\n        nid = np.array(nid)\n        self.rebuild_tree_from_array(\n            nid, pid, lid, rid, les, res, gid, splitdims, splitposs\n        )\n\n    def get_node_arrays(self):\n        nids = []\n        leftids = []\n        rightids = []\n        parentids = []\n        les = []\n        res = []\n        gridids = []\n        splitdims = []\n        splitposs = []\n        for node in self.tree.trunk.depth_first_touch():\n            nids.append(node.node_id)\n            les.append(node.get_left_edge())\n            res.append(node.get_right_edge())\n            if node.left is None:\n                leftids.append(-1)\n            else:\n                leftids.append(node.left.node_id)\n            if node.right is None:\n                rightids.append(-1)\n            else:\n                rightids.append(node.right.node_id)\n            if node.parent is None:\n                parentids.append(-1)\n            else:\n                parentids.append(node.parent.node_id)\n            if node.grid is None:\n                gridids.append(-1)\n            else:\n                gridids.append(node.grid)\n            splitdims.append(node.get_split_dim())\n            splitposs.append(node.get_split_pos())\n\n        return (\n            nids,\n            parentids,\n            leftids,\n            rightids,\n            les,\n            res,\n            gridids,\n            splitdims,\n            splitposs,\n        )\n\n    def rebuild_tree_from_array(\n        self, nids, pids, lids, rids, les, res, gids, splitdims, splitposs\n    ):\n        del self.tree.trunk\n\n        self.tree.trunk = Node(None, None, None, les[0], res[0], gids[0], nids[0])\n\n        N = nids.shape[0]\n        for i in range(N):\n            n = self.get_node(nids[i])\n            n.set_left_edge(les[i])\n            n.set_right_edge(res[i])\n            if lids[i] != -1 and n.left is None:\n                n.left = Node(\n                    n,\n                    None,\n                    None,\n                    np.zeros(3, dtype=\"float64\"),\n                    np.zeros(3, dtype=\"float64\"),\n                    -1,\n                    lids[i],\n                )\n            if rids[i] != -1 and n.right is None:\n                n.right = Node(\n                    n,\n                    None,\n                    None,\n                    np.zeros(3, dtype=\"float64\"),\n                    np.zeros(3, dtype=\"float64\"),\n                    -1,\n                    rids[i],\n                )\n            if gids[i] != -1:\n                n.grid = gids[i]\n\n            if splitdims[i] != -1:\n                n.create_split(splitdims[i], splitposs[i])\n\n        mylog.info(\n            \"AMRKDTree rebuilt, Final Volume: %e\", self.tree.trunk.kd_sum_volume()\n        )\n        return self.tree.trunk\n\n    def count_volume(self):\n        return self.tree.trunk.kd_sum_volume()\n\n    def count_cells(self):\n        return self.tree.sum_cells()\n\n\nif __name__ == \"__main__\":\n    from time import time\n\n    import yt\n\n    ds = yt.load(\"/Users/skillman/simulations/DD1717/DD1717\")\n    ds.index\n\n    t1 = time()\n    hv = AMRKDTree(ds)\n    t2 = time()\n\n    print(hv.tree.trunk.kd_sum_volume())\n    print(hv.tree.trunk.kd_node_check())\n    print(f\"Time: {t2 - t1:e} seconds\")\n"
  },
  {
    "path": "yt/utilities/amr_kdtree/api.py",
    "content": "from .amr_kdtree import AMRKDTree\n"
  },
  {
    "path": "yt/utilities/answer_testing/__init__.py",
    "content": "\"\"\"\nThe components of the Enzo testing mechanism\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/utilities/answer_testing/answer_tests.py",
    "content": "\"\"\"\nTitle: answer_tests.py\nPurpose: Contains answer tests that are used by yt's various frontends\n\"\"\"\n\nimport hashlib\nimport os\nimport tempfile\n\nimport matplotlib.image as mpimg\nimport numpy as np\n\nimport yt.visualization.plot_window as pw\nfrom yt.utilities.answer_testing.framework import create_obj\nfrom yt.utilities.answer_testing.testing_utilities import (\n    _create_phase_plot_attribute_plot,\n    _create_plot_window_attribute_plot,\n)\n\n\ndef grid_hierarchy(ds):\n    result = {}\n    result[\"grid_dimensions\"] = ds.index.grid_dimensions\n    result[\"grid_left_edge\"] = ds.index.grid_left_edge\n    result[\"grid_right_edge\"] = ds.index.grid_right_edge\n    result[\"grid_levels\"] = ds.index.grid_levels\n    result[\"grid_particle_count\"] = ds.index.grid_particle_count\n    return result\n\n\ndef parentage_relationships(ds):\n    parents = []\n    children = []\n    for g in ds.index.grids:\n        p = g.Parent\n        if p is None:\n            parents.append(-1)\n        elif hasattr(p, \"id\"):\n            parents.append(p.id)\n        else:\n            parents = parents + [pg.id for pg in p]\n        children = children + [c.id for c in g.Children]\n    result = np.array(parents + children)\n    return result\n\n\ndef grid_values(ds, field):\n    # The hashing is done here so that there is only one entry for\n    # the test that contains info about all of the grids as opposed\n    # to having a separate 'grid_id : grid_hash' pair for each grid\n    # since that makes the answer file much larger\n    result = None\n    for g in ds.index.grids:\n        if result is None:\n            result = hashlib.md5(bytes(g.id) + g[field].tobytes())\n        else:\n            result.update(bytes(g.id) + g[field].tobytes())\n        g.clear_data()\n    return result.hexdigest()\n\n\ndef projection_values(ds, axis, field, weight_field, dobj_type):\n    if dobj_type is not None:\n        dobj = create_obj(ds, dobj_type)\n    else:\n        dobj = None\n    if ds.domain_dimensions[axis] == 1:\n        # This originally returned None, but None can't be converted\n        # to a bytes array (for hashing), so use -1 as a string,\n        # since ints can't be converted to bytes either\n        return bytes(str(-1).encode(\"utf-8\"))\n    proj = ds.proj(field, axis, weight_field=weight_field, data_source=dobj)\n    # This is to try and remove python-specific anchors in the yaml\n    # answer file. Also, using __repr__() results in weird strings\n    # of strings that make comparison fail even though the data is\n    # the same\n    result = None\n    for k, v in proj.field_data.items():\n        k = k.__repr__().encode(\"utf8\")\n        if result is None:\n            result = hashlib.md5(k + v.tobytes())\n        else:\n            result.update(k + v.tobytes())\n    return result.hexdigest()\n\n\ndef field_values(ds, field, obj_type=None, particle_type=False):\n    # If needed build an instance of the dataset type\n    obj = create_obj(ds, obj_type)\n    determined_field = obj._determine_fields(field)[0]\n    fd = ds.field_info[determined_field]\n    # Get the proper weight field depending on if we're looking at\n    # particles or not\n    if particle_type:\n        weight_field = (determined_field[0], \"particle_ones\")\n    elif fd.is_sph_field:\n        weight_field = (determined_field[0], \"ones\")\n    else:\n        weight_field = (\"index\", \"ones\")\n    # Get the average, min, and max\n    avg = obj.quantities.weighted_average_quantity(\n        determined_field, weight=weight_field\n    )\n    minimum, maximum = obj.quantities.extrema(field)\n    # Return as a hashable bytestring\n    return np.array([avg, minimum, maximum])\n\n\ndef pixelized_projection_values(ds, axis, field, weight_field=None, dobj_type=None):\n    if dobj_type is not None:\n        obj = create_obj(ds, dobj_type)\n    else:\n        obj = None\n    proj = ds.proj(field, axis, weight_field=weight_field, data_source=obj)\n    frb = proj.to_frb((1.0, \"unitary\"), 256)\n    frb.render(field)\n    if weight_field is not None:\n        frb.render(weight_field)\n    d = frb.data\n    for f in proj.field_data:\n        # Sometimes f will be a tuple.\n        d[f\"{f}_sum\"] = proj.field_data[f].sum(dtype=\"float64\")\n    # This is to try and remove python-specific anchors in the yaml\n    # answer file. Also, using __repr__() results in weird strings\n    # of strings that make comparison fail even though the data is\n    # the same\n    result = None\n    for k, v in d.items():\n        k = k.__repr__().encode(\"utf8\")\n        if result is None:\n            result = hashlib.md5(k + v.tobytes())\n        else:\n            result.update(k + v.tobytes())\n    return result.hexdigest()\n\n\ndef small_patch_amr(ds, field, weight, axis, ds_obj):\n    hex_digests = {}\n    # Grid hierarchy test\n    gh_hd = grid_hierarchy(ds)\n    hex_digests[\"grid_hierarchy\"] = gh_hd\n    # Parentage relationships test\n    pr_hd = parentage_relationships(ds)\n    hex_digests[\"parentage_relationships\"] = pr_hd\n    # Grid values, projection values, and field values tests\n    gv_hd = grid_values(ds, field)\n    hex_digests[\"grid_values\"] = gv_hd\n    fv_hd = field_values(ds, field, ds_obj)\n    hex_digests[\"field_values\"] = fv_hd\n    pv_hd = projection_values(ds, axis, field, weight, ds_obj)\n    hex_digests[\"projection_values\"] = pv_hd\n    return hex_digests\n\n\ndef big_patch_amr(ds, field, weight, axis, ds_obj):\n    hex_digests = {}\n    # Grid hierarchy test\n    gh_hd = grid_hierarchy(ds)\n    hex_digests[\"grid_hierarchy\"] = gh_hd\n    # Parentage relationships test\n    pr_hd = parentage_relationships(ds)\n    hex_digests[\"parentage_relationships\"] = pr_hd\n    # Grid values, projection values, and field values tests\n    gv_hd = grid_values(ds, field)\n    hex_digests[\"grid_values\"] = gv_hd\n    ppv_hd = pixelized_projection_values(ds, axis, field, weight, ds_obj)\n    hex_digests[\"pixelized_projection_values\"] = ppv_hd\n    return hex_digests\n\n\ndef generic_array(func, args=None, kwargs=None):\n    if args is None:\n        args = []\n    if kwargs is None:\n        kwargs = {}\n    return func(*args, **kwargs)\n\n\ndef sph_answer(ds, ds_str_repr, ds_nparticles, field, weight, ds_obj, axis):\n    # Make sure we're dealing with the right dataset\n    assert str(ds) == ds_str_repr\n    # Set up keys of test names\n    hex_digests = {}\n    dd = ds.all_data()\n    assert dd[\"particle_position\"].shape == (ds_nparticles, 3)\n    tot = sum(\n        dd[ptype, \"particle_position\"].shape[0]\n        for ptype in ds.particle_types\n        if ptype != \"all\"\n    )\n    # Check\n    assert tot == ds_nparticles\n    dobj = create_obj(ds, ds_obj)\n    s1 = dobj[\"ones\"].sum()\n    s2 = sum(mask.sum() for block, mask in dobj.blocks)\n    assert s1 == s2\n    if field[0] in ds.particle_types:\n        particle_type = True\n    else:\n        particle_type = False\n    if not particle_type:\n        ppv_hd = pixelized_projection_values(ds, axis, field, weight, ds_obj)\n        hex_digests[\"pixelized_projection_values\"] = ppv_hd\n    fv_hd = field_values(ds, field, ds_obj, particle_type=particle_type)\n    hex_digests[\"field_values\"] = fv_hd\n    return hex_digests\n\n\ndef get_field_size_and_mean(ds, field, geometric):\n    if geometric:\n        obj = ds.all_data()\n    else:\n        obj = ds.data\n    return np.array([obj[field].size, obj[field].mean()])\n\n\ndef plot_window_attribute(\n    ds,\n    plot_field,\n    plot_axis,\n    attr_name,\n    attr_args,\n    plot_type=\"SlicePlot\",\n    callback_id=\"\",\n    callback_runners=None,\n):\n    if callback_runners is None:\n        callback_runners = []\n    plot = _create_plot_window_attribute_plot(ds, plot_type, plot_field, plot_axis, {})\n    for r in callback_runners:\n        r(plot_field, plot)\n    attr = getattr(plot, attr_name)\n    attr(*attr_args[0], **attr_args[1])\n    tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n    os.close(tmpfd)\n    plot.save(name=tmpname)\n    image = mpimg.imread(tmpname)\n    os.remove(tmpname)\n    return image\n\n\ndef phase_plot_attribute(\n    ds_fn,\n    x_field,\n    y_field,\n    z_field,\n    attr_name,\n    attr_args,\n    plot_type=\"PhasePlot\",\n    plot_kwargs=None,\n):\n    if plot_kwargs is None:\n        plot_kwargs = {}\n    data_source = ds_fn.all_data()\n    plot = _create_phase_plot_attribute_plot(\n        data_source, x_field, y_field, z_field, plot_type, plot_kwargs\n    )\n    attr = getattr(plot, attr_name)\n    attr(*attr_args[0], **attr_args[1])\n    tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n    os.close(tmpfd)\n    plot.save(name=tmpname)\n    image = mpimg.imread(tmpname)\n    os.remove(tmpname)\n    return image\n\n\ndef generic_image(img_fname):\n    from yt._maintenance.deprecation import issue_deprecation_warning\n\n    issue_deprecation_warning(\n        \"yt.utilities.answer_testing.answer_tests.generic_image is deprecated \"\n        \"and will be removed in a future version. Please use pytest-mpl instead\",\n        since=\"4.4\",\n        stacklevel=2,\n    )\n    img_data = mpimg.imread(img_fname)\n    return img_data\n\n\ndef axial_pixelization(ds):\n    r\"\"\"\n    This test is typically used once per geometry or coordinates type.\n    Feed it a dataset, and it checks that the results of basic pixelization\n    don't change.\n    \"\"\"\n    for i, axis in enumerate(ds.coordinates.axis_order):\n        (bounds, center, display_center) = pw.get_window_parameters(\n            axis, ds.domain_center, None, ds\n        )\n        slc = ds.slice(axis, center[i])\n        xax = ds.coordinates.axis_name[ds.coordinates.x_axis[axis]]\n        yax = ds.coordinates.axis_name[ds.coordinates.y_axis[axis]]\n        pix_x = ds.coordinates.pixelize(axis, slc, xax, bounds, (512, 512))\n        pix_y = ds.coordinates.pixelize(axis, slc, yax, bounds, (512, 512))\n        # Wipe out all NaNs\n        pix_x[np.isnan(pix_x)] = 0.0\n        pix_y[np.isnan(pix_y)] = 0.0\n        pix_x\n        pix_y\n    return pix_x, pix_y\n\n\ndef extract_connected_sets(ds_fn, data_source, field, num_levels, min_val, max_val):\n    n, all_sets = data_source.extract_connected_sets(\n        field, num_levels, min_val, max_val\n    )\n    result = []\n    for level in all_sets:\n        for set_id in all_sets[level]:\n            result.append(\n                [\n                    all_sets[level][set_id][\"cell_mass\"].size,\n                    all_sets[level][set_id][\"cell_mass\"].sum(),\n                ]\n            )\n    result = np.array(result)\n    return result\n\n\ndef VR_image_comparison(scene):\n    tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n    os.close(tmpfd)\n    scene.save(tmpname, sigma_clip=1.0)\n    image = mpimg.imread(tmpname)\n    os.remove(tmpname)\n    return image\n"
  },
  {
    "path": "yt/utilities/answer_testing/api.py",
    "content": "from yt.utilities.answer_testing.framework import AnswerTesting\n"
  },
  {
    "path": "yt/utilities/answer_testing/framework.py",
    "content": "\"\"\"\nTitle: framework.py\nPurpose: Contains answer tests that are used by yt's various frontends\n\"\"\"\n\nimport contextlib\nimport hashlib\nimport logging\nimport os\nimport pickle\nimport shelve\nimport sys\nimport tempfile\nimport time\nimport warnings\nimport zlib\nfrom collections import defaultdict\n\nimport numpy as np\nfrom matplotlib import image as mpimg\nfrom matplotlib.testing.compare import compare_images\nfrom nose.plugins import Plugin\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.config import ytcfg\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import get_pbar, get_yt_version\nfrom yt.loaders import load, load_simulation\nfrom yt.testing import (\n    assert_allclose_units,\n    assert_rel_equal,\n    skipif,\n)\nfrom yt.utilities.exceptions import (\n    YTAmbiguousDataType,\n    YTCloudError,\n    YTNoAnswerNameSpecified,\n    YTNoOldAnswer,\n    YTUnidentifiedDataType,\n)\nfrom yt.utilities.logger import disable_stream_logging\nfrom yt.visualization import (\n    image_writer as image_writer,\n    particle_plots as particle_plots,\n    plot_window as pw,\n    profile_plotter as profile_plotter,\n)\n\nmylog = logging.getLogger(\"nose.plugins.answer-testing\")\nrun_big_data = False\n\n# Set the latest gold and local standard filenames\n_latest = ytcfg.get(\"yt\", \"gold_standard_filename\")\n_latest_local = ytcfg.get(\"yt\", \"local_standard_filename\")\n_url_path = ytcfg.get(\"yt\", \"answer_tests_url\")\n\n\nclass AnswerTesting(Plugin):\n    name = \"answer-testing\"\n    _my_version = None\n\n    def options(self, parser, env=os.environ):\n        super().options(parser, env=env)\n        parser.add_option(\n            \"--answer-name\",\n            dest=\"answer_name\",\n            metavar=\"str\",\n            default=None,\n            help=\"The name of the standard to store/compare against\",\n        )\n        parser.add_option(\n            \"--answer-store\",\n            dest=\"store_results\",\n            metavar=\"bool\",\n            default=False,\n            action=\"store_true\",\n            help=\"Should we store this result instead of comparing?\",\n        )\n        parser.add_option(\n            \"--local\",\n            dest=\"local_results\",\n            default=False,\n            action=\"store_true\",\n            help=\"Store/load reference results locally?\",\n        )\n        parser.add_option(\n            \"--answer-big-data\",\n            dest=\"big_data\",\n            default=False,\n            help=\"Should we run against big data, too?\",\n            action=\"store_true\",\n        )\n        parser.add_option(\n            \"--local-dir\",\n            dest=\"output_dir\",\n            metavar=\"str\",\n            help=\"The name of the directory to store local results\",\n        )\n\n    @property\n    def my_version(self, version=None):\n        if self._my_version is not None:\n            return self._my_version\n        if version is None:\n            try:\n                version = get_yt_version()\n            except Exception:\n                version = f\"UNKNOWN{time.time()}\"\n        self._my_version = version\n        return self._my_version\n\n    def configure(self, options, conf):\n        super().configure(options, conf)\n        if not self.enabled:\n            return\n        disable_stream_logging()\n\n        # Parse through the storage flags to make sense of them\n        # and use reasonable defaults\n        # If we're storing the data, default storage name is local\n        # latest version\n        if options.store_results:\n            if options.answer_name is None:\n                self.store_name = _latest_local\n            else:\n                self.store_name = options.answer_name\n            self.compare_name = None\n        # if we're not storing, then we're comparing, and we want default\n        # comparison name to be the latest gold standard\n        # either on network or local\n        else:\n            if options.answer_name is None:\n                if options.local_results:\n                    self.compare_name = _latest_local\n                else:\n                    self.compare_name = _latest\n            else:\n                self.compare_name = options.answer_name\n            self.store_name = self.my_version\n\n        self.store_results = options.store_results\n\n        ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n        AnswerTestingTest.result_storage = self.result_storage = defaultdict(dict)\n        if self.compare_name == \"SKIP\":\n            self.compare_name = None\n        elif self.compare_name == \"latest\":\n            self.compare_name = _latest\n\n        # Local/Cloud storage\n        if options.local_results:\n            if options.output_dir is None:\n                print(\"Please supply an output directory with the --local-dir option\")\n                sys.exit(1)\n            storage_class = AnswerTestLocalStorage\n            output_dir = os.path.realpath(options.output_dir)\n            # Fix up filename for local storage\n            if self.compare_name is not None:\n                self.compare_name = os.path.join(\n                    output_dir, self.compare_name, self.compare_name\n                )\n\n            # Create a local directory only when `options.answer_name` is\n            # provided. If it is not provided then creating local directory\n            # will depend on the `AnswerTestingTest.answer_name` value of the\n            # test, this case is handled in AnswerTestingTest class.\n            if options.store_results and options.answer_name is not None:\n                name_dir_path = os.path.join(output_dir, self.store_name)\n                if not os.path.isdir(name_dir_path):\n                    os.makedirs(name_dir_path)\n                self.store_name = os.path.join(name_dir_path, self.store_name)\n        else:\n            storage_class = AnswerTestCloudStorage\n\n        # Initialize answer/reference storage\n        AnswerTestingTest.reference_storage = self.storage = storage_class(\n            self.compare_name, self.store_name\n        )\n        AnswerTestingTest.options = options\n\n        self.local_results = options.local_results\n        global run_big_data\n        run_big_data = options.big_data\n\n    def finalize(self, result=None):\n        if not self.store_results:\n            return\n        self.storage.dump(self.result_storage)\n\n    def help(self):\n        return \"yt answer testing support\"\n\n\nclass AnswerTestStorage:\n    def __init__(self, reference_name=None, answer_name=None):\n        self.reference_name = reference_name\n        self.answer_name = answer_name\n        self.cache = {}\n\n    def dump(self, result_storage, result):\n        raise NotImplementedError\n\n    def get(self, ds_name, default=None):\n        raise NotImplementedError\n\n\nclass AnswerTestCloudStorage(AnswerTestStorage):\n    def get(self, ds_name, default=None):\n        import urllib.error\n        import urllib.request\n\n        if self.reference_name is None:\n            return default\n        if ds_name in self.cache:\n            return self.cache[ds_name]\n        url = _url_path.format(self.reference_name, ds_name)\n        try:\n            resp = urllib.request.urlopen(url)\n        except urllib.error.HTTPError as exc:\n            raise YTNoOldAnswer(url) from exc\n        else:\n            for _ in range(3):\n                try:\n                    data = resp.read()\n                except Exception:\n                    time.sleep(0.01)\n                else:\n                    # We were successful\n                    break\n            else:\n                # Raise error if all tries were unsuccessful\n                raise YTCloudError(url)\n            # This is dangerous, but we have a controlled S3 environment\n            rv = pickle.loads(data)\n        self.cache[ds_name] = rv\n        return rv\n\n    def progress_callback(self, current, total):\n        self.pbar.update(current)\n\n    def dump(self, result_storage):\n        if self.answer_name is None:\n            return\n        # This is where we dump our result storage up to Amazon, if we are able\n        # to.\n        import pyrax\n\n        credentials = os.path.expanduser(os.path.join(\"~\", \".yt\", \"rackspace\"))\n        pyrax.set_credential_file(credentials)\n        cf = pyrax.cloudfiles\n        c = cf.get_container(\"yt-answer-tests\")\n        pb = get_pbar(\"Storing results \", len(result_storage))\n        for i, ds_name in enumerate(result_storage):\n            pb.update(i + 1)\n            rs = pickle.dumps(result_storage[ds_name])\n            object_name = f\"{self.answer_name}_{ds_name}\"\n            if object_name in c.get_object_names():\n                obj = c.get_object(object_name)\n                c.delete_object(obj)\n            c.store_object(object_name, rs)\n        pb.finish()\n\n\nclass AnswerTestLocalStorage(AnswerTestStorage):\n    def dump(self, result_storage):\n        # The 'tainted' attribute is automatically set to 'True'\n        # if the dataset required for an answer test is missing\n        # (see can_run_ds().\n        # This logic check prevents creating a shelve with empty answers.\n        storage_is_tainted = result_storage.get(\"tainted\", False)\n        if self.answer_name is None or storage_is_tainted:\n            return\n        # Store data using shelve\n        ds = shelve.open(self.answer_name, protocol=-1)\n        for ds_name in result_storage:\n            answer_name = f\"{ds_name}\"\n            if answer_name in ds:\n                mylog.info(\"Overwriting %s\", answer_name)\n            ds[answer_name] = result_storage[ds_name]\n        ds.close()\n\n    def get(self, ds_name, default=None):\n        if self.reference_name is None:\n            return default\n        # Read data using shelve\n        answer_name = f\"{ds_name}\"\n        os.makedirs(os.path.dirname(self.reference_name), exist_ok=True)\n        ds = shelve.open(self.reference_name, protocol=-1)\n        try:\n            result = ds[answer_name]\n        except KeyError:\n            result = default\n        ds.close()\n        return result\n\n\n@contextlib.contextmanager\ndef temp_cwd(cwd):\n    oldcwd = os.getcwd()\n    os.chdir(cwd)\n    yield\n    os.chdir(oldcwd)\n\n\ndef can_run_ds(ds_fn, file_check=False):\n    result_storage = AnswerTestingTest.result_storage\n    if isinstance(ds_fn, Dataset):\n        return result_storage is not None\n    path = ytcfg.get(\"yt\", \"test_data_dir\")\n    if not os.path.isdir(path):\n        return False\n    if file_check:\n        return os.path.isfile(os.path.join(path, ds_fn)) and result_storage is not None\n    try:\n        load(ds_fn)\n    except FileNotFoundError:\n        if ytcfg.get(\"yt\", \"internals\", \"strict_requires\"):\n            if result_storage is not None:\n                result_storage[\"tainted\"] = True\n            raise\n        return False\n    except (YTUnidentifiedDataType, YTAmbiguousDataType):\n        return False\n\n    return result_storage is not None\n\n\ndef data_dir_load(ds_fn, cls=None, args=None, kwargs=None):\n    args = args or ()\n    kwargs = kwargs or {}\n    path = ytcfg.get(\"yt\", \"test_data_dir\")\n    if isinstance(ds_fn, Dataset):\n        return ds_fn\n    if not os.path.isdir(path):\n        return False\n    if cls is None:\n        ds = load(ds_fn, *args, **kwargs)\n    else:\n        ds = cls(os.path.join(path, ds_fn), *args, **kwargs)\n    ds.index\n    return ds\n\n\ndef data_dir_load_v2(fn, *args, **kwargs):\n    # a version of data_dir_load without type flexibility\n    # that is simpler to reason about\n    path = os.path.join(ytcfg.get(\"yt\", \"test_data_dir\"), fn)\n    return load(path, *args, **kwargs)\n\n\ndef sim_dir_load(sim_fn, path=None, sim_type=\"Enzo\", find_outputs=False):\n    if path is None and not os.path.exists(sim_fn):\n        raise OSError\n    if os.path.exists(sim_fn) or not path:\n        path = \".\"\n    return load_simulation(\n        os.path.join(path, sim_fn), sim_type, find_outputs=find_outputs\n    )\n\n\nclass AnswerTestingTest:\n    reference_storage = None\n    result_storage = None\n    prefix = \"\"\n    options = None\n    # This variable should be set if we are not providing `--answer-name` as\n    # command line parameter while running yt's answer testing using nosetests.\n    answer_name = None\n\n    def __init__(self, ds_fn):\n        if ds_fn is None:\n            self.ds = None\n        elif isinstance(ds_fn, Dataset):\n            self.ds = ds_fn\n        else:\n            self.ds = data_dir_load(ds_fn, kwargs={\"unit_system\": \"code\"})\n\n    def __call__(self):\n        if AnswerTestingTest.result_storage is None:\n            return\n        nv = self.run()\n\n        # Test answer name should be provided either as command line parameters\n        # or by setting AnswerTestingTest.answer_name\n        if self.options.answer_name is None and self.answer_name is None:\n            raise YTNoAnswerNameSpecified()\n\n        # This is for running answer test when `--answer-name` is not set in\n        # nosetests command line arguments. In this case, set the answer_name\n        # from the `answer_name` keyword in the test case\n        if self.options.answer_name is None:\n            pyver = f\"py{sys.version_info.major}{sys.version_info.minor}\"\n            self.answer_name = f\"{pyver}_{self.answer_name}\"\n\n            answer_store_dir = os.path.realpath(self.options.output_dir)\n            ref_name = os.path.join(\n                answer_store_dir, self.answer_name, self.answer_name\n            )\n            self.reference_storage.reference_name = ref_name\n            self.reference_storage.answer_name = ref_name\n\n            # If we are generating golden answers (passed --answer-store arg):\n            # - create the answer directory for this test\n            # - self.reference_storage.answer_name will be path to answer files\n            if self.options.store_results:\n                answer_test_dir = os.path.join(answer_store_dir, self.answer_name)\n                if not os.path.isdir(answer_test_dir):\n                    os.makedirs(answer_test_dir)\n                self.reference_storage.reference_name = None\n\n        if self.reference_storage.reference_name is not None:\n            # Compare test generated values against the golden answer\n            dd = self.reference_storage.get(self.storage_name)\n            if dd is None or self.description not in dd:\n                raise YTNoOldAnswer(f\"{self.storage_name} : {self.description}\")\n            ov = dd[self.description]\n            self.compare(nv, ov)\n        else:\n            # Store results, hence do nothing (in case of --answer-store arg)\n            ov = None\n        self.result_storage[self.storage_name][self.description] = nv\n\n    @property\n    def storage_name(self):\n        if self.prefix != \"\":\n            return f\"{self.prefix}_{self.ds}\"\n        return str(self.ds)\n\n    def compare(self, new_result, old_result):\n        raise RuntimeError\n\n    def create_plot(self, ds, plot_type, plot_field, plot_axis, plot_kwargs=None):\n        # plot_type should be a string\n        # plot_kwargs should be a dict\n        if plot_type is None:\n            raise RuntimeError(\"Must explicitly request a plot type\")\n        cls = getattr(pw, plot_type, None)\n        if cls is None:\n            cls = getattr(particle_plots, plot_type)\n        plot = cls(*(ds, plot_axis, plot_field), **plot_kwargs)\n        return plot\n\n    @property\n    def sim_center(self):\n        \"\"\"\n        This returns the center of the domain.\n        \"\"\"\n        return 0.5 * (self.ds.domain_right_edge + self.ds.domain_left_edge)\n\n    @property\n    def max_dens_location(self):\n        \"\"\"\n        This is a helper function to return the location of the most dense\n        point.\n        \"\"\"\n        return self.ds.find_max((\"gas\", \"density\"))[1]\n\n    @property\n    def entire_simulation(self):\n        \"\"\"\n        Return an unsorted array of values that cover the entire domain.\n        \"\"\"\n        return self.ds.all_data()\n\n    @property\n    def description(self):\n        obj_type = getattr(self, \"obj_type\", None)\n        if obj_type is None:\n            oname = \"all\"\n        else:\n            oname = \"_\".join(str(s) for s in obj_type)\n        args = [self._type_name, str(self.ds), oname]\n        args += [str(getattr(self, an)) for an in self._attrs]\n        suffix = getattr(self, \"suffix\", None)\n        if suffix:\n            args.append(suffix)\n        return \"_\".join(args).replace(\".\", \"_\")\n\n\nclass FieldValuesTest(AnswerTestingTest):\n    _type_name = \"FieldValues\"\n    _attrs = (\"field\",)\n\n    def __init__(self, ds_fn, field, obj_type=None, particle_type=False, decimals=10):\n        super().__init__(ds_fn)\n        self.obj_type = obj_type\n        self.field = field\n        self.particle_type = particle_type\n        self.decimals = decimals\n\n    def run(self):\n        obj = create_obj(self.ds, self.obj_type)\n        field = obj._determine_fields(self.field)[0]\n        fd = self.ds.field_info[field]\n        if self.particle_type:\n            weight_field = (field[0], \"particle_ones\")\n        elif fd.is_sph_field:\n            weight_field = (field[0], \"ones\")\n        else:\n            weight_field = (\"index\", \"ones\")\n        avg = obj.quantities.weighted_average_quantity(field, weight=weight_field)\n        mi, ma = obj.quantities.extrema(self.field)\n        return [avg, mi, ma]\n\n    def compare(self, new_result, old_result):\n        err_msg = f\"Field values for {self.field} not equal.\"\n        if hasattr(new_result, \"d\"):\n            new_result = new_result.d\n        if hasattr(old_result, \"d\"):\n            old_result = old_result.d\n        if self.decimals is None:\n            assert_equal(new_result, old_result, err_msg=err_msg, verbose=True)\n        else:\n            # What we do here is check if the old_result has units; if not, we\n            # assume they will be the same as the units of new_result.\n            if isinstance(old_result, np.ndarray) and not hasattr(\n                old_result, \"in_units\"\n            ):\n                # coerce it here to the same units\n                old_result = old_result * new_result[0].uq\n            assert_allclose_units(\n                new_result,\n                old_result,\n                10.0 ** (-self.decimals),\n                err_msg=err_msg,\n                verbose=True,\n            )\n\n\nclass AllFieldValuesTest(AnswerTestingTest):\n    _type_name = \"AllFieldValues\"\n    _attrs = (\"field\",)\n\n    def __init__(self, ds_fn, field, obj_type=None, decimals=None):\n        super().__init__(ds_fn)\n        self.obj_type = obj_type\n        self.field = field\n        self.decimals = decimals\n\n    def run(self):\n        obj = create_obj(self.ds, self.obj_type)\n        return obj[self.field]\n\n    def compare(self, new_result, old_result):\n        err_msg = f\"All field values for {self.field} not equal.\"\n        if hasattr(new_result, \"d\"):\n            new_result = new_result.d\n        if hasattr(old_result, \"d\"):\n            old_result = old_result.d\n        if self.decimals is None:\n            assert_equal(new_result, old_result, err_msg=err_msg, verbose=True)\n        else:\n            assert_rel_equal(\n                new_result, old_result, self.decimals, err_msg=err_msg, verbose=True\n            )\n\n\nclass ProjectionValuesTest(AnswerTestingTest):\n    _type_name = \"ProjectionValues\"\n    _attrs = (\"field\", \"axis\", \"weight_field\")\n\n    def __init__(\n        self, ds_fn, axis, field, weight_field=None, obj_type=None, decimals=10\n    ):\n        super().__init__(ds_fn)\n        self.axis = axis\n        self.field = field\n        self.weight_field = weight_field\n        self.obj_type = obj_type\n        self.decimals = decimals\n\n    def run(self):\n        if self.obj_type is not None:\n            obj = create_obj(self.ds, self.obj_type)\n        else:\n            obj = None\n        if self.ds.domain_dimensions[self.axis] == 1:\n            return None\n        proj = self.ds.proj(\n            self.field, self.axis, weight_field=self.weight_field, data_source=obj\n        )\n        return proj.field_data\n\n    def compare(self, new_result, old_result):\n        if new_result is None:\n            return\n        assert len(new_result) == len(old_result)\n        nind, oind = None, None\n        for k in new_result:\n            assert k in old_result\n            if oind is None:\n                oind = np.array(np.isnan(old_result[k]))\n            np.logical_or(oind, np.isnan(old_result[k]), oind)\n            if nind is None:\n                nind = np.array(np.isnan(new_result[k]))\n            np.logical_or(nind, np.isnan(new_result[k]), nind)\n        oind = ~oind\n        nind = ~nind\n        for k in new_result:\n            err_msg = f\"{k} values of {self.field} ({self.weight_field} weighted) projection (axis {self.axis}) not equal.\"\n            if k == \"weight_field\":\n                # Our weight_field can vary between unit systems, whereas we\n                # can do a unitful comparison for the other fields.  So we do\n                # not do the test here.\n                continue\n            nres, ores = new_result[k][nind], old_result[k][oind]\n            if hasattr(nres, \"d\"):\n                nres = nres.d\n            if hasattr(ores, \"d\"):\n                ores = ores.d\n            if self.decimals is None:\n                assert_equal(nres, ores, err_msg=err_msg)\n            else:\n                assert_allclose_units(\n                    nres, ores, 10.0**-(self.decimals), err_msg=err_msg\n                )\n\n\nclass PixelizedProjectionValuesTest(AnswerTestingTest):\n    _type_name = \"PixelizedProjectionValues\"\n    _attrs = (\"field\", \"axis\", \"weight_field\")\n\n    def __init__(self, ds_fn, axis, field, weight_field=None, obj_type=None):\n        super().__init__(ds_fn)\n        self.axis = axis\n        self.field = field\n        self.weight_field = weight_field\n        self.obj_type = obj_type\n\n    def _get_frb(self, obj):\n        proj = self.ds.proj(\n            self.field, self.axis, weight_field=self.weight_field, data_source=obj\n        )\n        frb = proj.to_frb((1.0, \"unitary\"), 256)\n        return proj, frb\n\n    def run(self):\n        if self.obj_type is not None:\n            obj = create_obj(self.ds, self.obj_type)\n        else:\n            obj = None\n        proj, frb = self._get_frb(obj)\n        frb.render(self.field)\n        if self.weight_field is not None:\n            frb.render(self.weight_field)\n        d = frb.data\n        for f in proj.field_data:\n            # Sometimes f will be a tuple.\n            d[f\"{f}_sum\"] = proj.field_data[f].sum(dtype=\"float64\")\n        return d\n\n    def compare(self, new_result, old_result):\n        assert len(new_result) == len(old_result)\n        for k in new_result:\n            assert k in old_result\n        for k in new_result:\n            # weight_field does not have units, so we do not directly compare them\n            if k == \"weight_field_sum\":\n                continue\n            try:\n                assert_allclose_units(new_result[k], old_result[k], 1e-10)\n            except AssertionError:\n                dump_images(new_result[k], old_result[k])\n                raise\n\n\nclass PixelizedParticleProjectionValuesTest(PixelizedProjectionValuesTest):\n    def _get_frb(self, obj):\n        proj_plot = particle_plots.ParticleProjectionPlot(\n            self.ds, self.axis, [self.field], weight_field=self.weight_field\n        )\n        return proj_plot.data_source, proj_plot.frb\n\n\nclass GridValuesTest(AnswerTestingTest):\n    _type_name = \"GridValues\"\n    _attrs = (\"field\",)\n\n    def __init__(self, ds_fn, field):\n        super().__init__(ds_fn)\n        self.field = field\n\n    def run(self):\n        hashes = {}\n        for g in self.ds.index.grids:\n            hashes[g.id] = hashlib.md5(g[self.field].tobytes()).hexdigest()\n            g.clear_data()\n        return hashes\n\n    def compare(self, new_result, old_result):\n        assert len(new_result) == len(old_result)\n        for k in new_result:\n            assert k in old_result\n        for k in new_result:\n            if hasattr(new_result[k], \"d\"):\n                new_result[k] = new_result[k].d\n            if hasattr(old_result[k], \"d\"):\n                old_result[k] = old_result[k].d\n            assert_equal(new_result[k], old_result[k])\n\n\nclass VerifySimulationSameTest(AnswerTestingTest):\n    _type_name = \"VerifySimulationSame\"\n    _attrs = ()\n\n    def __init__(self, simulation_obj):\n        self.ds = simulation_obj\n\n    def run(self):\n        result = [ds.current_time for ds in self.ds]\n        return result\n\n    def compare(self, new_result, old_result):\n        assert_equal(\n            len(new_result),\n            len(old_result),\n            err_msg=\"Number of outputs not equal.\",\n            verbose=True,\n        )\n        for i in range(len(new_result)):\n            assert_equal(\n                new_result[i],\n                old_result[i],\n                err_msg=\"Output times not equal.\",\n                verbose=True,\n            )\n\n\nclass GridHierarchyTest(AnswerTestingTest):\n    _type_name = \"GridHierarchy\"\n    _attrs = ()\n\n    def run(self):\n        result = {}\n        result[\"grid_dimensions\"] = self.ds.index.grid_dimensions\n        result[\"grid_left_edges\"] = self.ds.index.grid_left_edge\n        result[\"grid_right_edges\"] = self.ds.index.grid_right_edge\n        result[\"grid_levels\"] = self.ds.index.grid_levels\n        result[\"grid_particle_count\"] = self.ds.index.grid_particle_count\n        return result\n\n    def compare(self, new_result, old_result):\n        for k in new_result:\n            if hasattr(new_result[k], \"d\"):\n                new_result[k] = new_result[k].d\n            if hasattr(old_result[k], \"d\"):\n                old_result[k] = old_result[k].d\n            assert_equal(new_result[k], old_result[k])\n\n\nclass ParentageRelationshipsTest(AnswerTestingTest):\n    _type_name = \"ParentageRelationships\"\n    _attrs = ()\n\n    def run(self):\n        result = {}\n        result[\"parents\"] = []\n        result[\"children\"] = []\n        for g in self.ds.index.grids:\n            p = g.Parent\n            if p is None:\n                result[\"parents\"].append(None)\n            elif hasattr(p, \"id\"):\n                result[\"parents\"].append(p.id)\n            else:\n                result[\"parents\"].append([pg.id for pg in p])\n            result[\"children\"].append([c.id for c in g.Children])\n        return result\n\n    def compare(self, new_result, old_result):\n        for newp, oldp in zip(\n            new_result[\"parents\"],\n            old_result[\"parents\"],\n            strict=True,\n        ):\n            assert newp == oldp\n        for newc, oldc in zip(\n            new_result[\"children\"],\n            old_result[\"children\"],\n            strict=True,\n        ):\n            assert newc == oldc\n\n\ndef dump_images(new_result, old_result, decimals=10):\n    tmpfd, old_image = tempfile.mkstemp(prefix=\"baseline_\", suffix=\".png\")\n    os.close(tmpfd)\n    tmpfd, new_image = tempfile.mkstemp(prefix=\"thisPR_\", suffix=\".png\")\n    os.close(tmpfd)\n    image_writer.write_projection(new_result, new_image)\n    image_writer.write_projection(old_result, old_image)\n    results = compare_images(old_image, new_image, 10 ** (-decimals))\n    if results is not None:\n        tempfiles = [\n            line.strip() for line in results.split(\"\\n\") if line.endswith(\".png\")\n        ]\n        for fn in tempfiles:\n            sys.stderr.write(f\"\\n[[ATTACHMENT|{fn}]]\")\n        sys.stderr.write(\"\\n\")\n\n\ndef ensure_image_comparability(a, b):\n    # pad nans to the right and the bottom of two images to make them comparable\n    # via matplotlib if they do not have the same shape\n    if a.shape == b.shape:\n        return a, b\n\n    assert a.shape[2:] == b.shape[2:]\n\n    warnings.warn(\n        f\"Images have different shapes {a.shape} and {b.shape}. \"\n        \"Padding nans to make them comparable.\",\n        stacklevel=2,\n    )\n    smallest_containing_shape = (\n        max(a.shape[0], b.shape[0]),\n        max(a.shape[1], b.shape[1]),\n        *a.shape[2:],\n    )\n    pa = np.full(smallest_containing_shape, np.nan)\n    pa[: a.shape[0], : a.shape[1], ...] = a\n\n    pb = np.full(smallest_containing_shape, np.nan)\n    pb[: b.shape[0], : b.shape[1], ...] = b\n\n    return pa, pb\n\n\ndef compare_image_lists(new_result, old_result, decimals):\n    fns = []\n    for _ in range(2):\n        tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n        os.close(tmpfd)\n        fns.append(tmpname)\n    num_images = len(old_result)\n    assert num_images > 0\n    for i in range(num_images):\n        expected = pickle.loads(zlib.decompress(old_result[i]))\n        actual = pickle.loads(zlib.decompress(new_result[i]))\n        expected_p, actual_p = ensure_image_comparability(expected, actual)\n\n        mpimg.imsave(fns[0], expected_p)\n        mpimg.imsave(fns[1], actual_p)\n        results = compare_images(fns[0], fns[1], 10 ** (-decimals))\n        if results is not None:\n            tempfiles = [\n                line.strip() for line in results.split(\"\\n\") if line.endswith(\".png\")\n            ]\n            for fn, img, padded in zip(\n                tempfiles,\n                (expected, actual),\n                (expected_p, actual_p),\n                strict=True,\n            ):\n                # padded images are convenient for comparison\n                # but what we really want to store and upload\n                # are the actual results\n                if padded.shape != img.shape:\n                    mpimg.imsave(fn, img)\n            if os.environ.get(\"JENKINS_HOME\") is not None:\n                for fn in tempfiles:\n                    sys.stderr.write(f\"\\n[[ATTACHMENT|{fn}]]\")\n                sys.stderr.write(\"\\n\")\n        assert_equal(results, None, results)\n        for fn in fns:\n            os.remove(fn)\n\n\nclass PlotWindowAttributeTest(AnswerTestingTest):\n    _type_name = \"PlotWindowAttribute\"\n    _attrs = (\n        \"plot_type\",\n        \"plot_field\",\n        \"plot_axis\",\n        \"attr_name\",\n        \"attr_args\",\n        \"callback_id\",\n    )\n\n    def __init__(\n        self,\n        ds_fn: str,\n        plot_field: str,\n        plot_axis: str,\n        attr_name: str | None = None,\n        attr_args: tuple | None = None,\n        decimals: int | None = 12,\n        plot_type: str | None = \"SlicePlot\",\n        callback_id: str | None = \"\",\n        callback_runners: tuple | None = None,\n    ):\n        super().__init__(ds_fn)\n        self.plot_type = plot_type\n        self.plot_field = plot_field\n        self.plot_axis = plot_axis\n        self.plot_kwargs = {}\n        self.attr_name = attr_name\n        self.attr_args = attr_args\n        self.decimals = decimals\n        # callback_id is so that we don't have to hash the actual callbacks\n        # run, but instead we call them something\n        self.callback_id = callback_id\n        if callback_runners is None:\n            callback_runners = ()\n        self.callback_runners = callback_runners\n\n    def run(self):\n        plot = self.create_plot(\n            self.ds, self.plot_type, self.plot_field, self.plot_axis, self.plot_kwargs\n        )\n        for r in self.callback_runners:\n            r(self, plot)\n        if self.attr_name and self.attr_args:\n            attr = getattr(plot, self.attr_name)\n            attr(*self.attr_args[0], **self.attr_args[1])\n        tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n        os.close(tmpfd)\n        plot.save(name=tmpname)\n        image = mpimg.imread(tmpname)\n        os.remove(tmpname)\n        return [zlib.compress(image.dumps())]\n\n    def compare(self, new_result, old_result):\n        compare_image_lists(new_result, old_result, self.decimals)\n\n\nclass PhasePlotAttributeTest(AnswerTestingTest):\n    _type_name = \"PhasePlotAttribute\"\n    _attrs = (\"plot_type\", \"x_field\", \"y_field\", \"z_field\", \"attr_name\", \"attr_args\")\n\n    def __init__(\n        self,\n        ds_fn,\n        x_field,\n        y_field,\n        z_field,\n        attr_name,\n        attr_args,\n        decimals,\n        plot_type=\"PhasePlot\",\n    ):\n        super().__init__(ds_fn)\n        self.data_source = self.ds.all_data()\n        self.plot_type = plot_type\n        self.x_field = x_field\n        self.y_field = y_field\n        self.z_field = z_field\n        self.plot_kwargs = {}\n        self.attr_name = attr_name\n        self.attr_args = attr_args\n        self.decimals = decimals\n\n    def create_plot(\n        self, data_source, x_field, y_field, z_field, plot_type, plot_kwargs=None\n    ):\n        # plot_type should be a string\n        # plot_kwargs should be a dict\n        if plot_type is None:\n            raise RuntimeError(\"Must explicitly request a plot type\")\n        cls = getattr(profile_plotter, plot_type, None)\n        if cls is None:\n            cls = getattr(particle_plots, plot_type)\n        plot = cls(*(data_source, x_field, y_field, z_field), **plot_kwargs)\n        return plot\n\n    def run(self):\n        plot = self.create_plot(\n            self.data_source,\n            self.x_field,\n            self.y_field,\n            self.z_field,\n            self.plot_type,\n            self.plot_kwargs,\n        )\n        attr = getattr(plot, self.attr_name)\n        attr(*self.attr_args[0], **self.attr_args[1])\n        tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n        os.close(tmpfd)\n        plot.save(name=tmpname)\n        image = mpimg.imread(tmpname)\n        os.remove(tmpname)\n        return [zlib.compress(image.dumps())]\n\n    def compare(self, new_result, old_result):\n        compare_image_lists(new_result, old_result, self.decimals)\n\n\nclass GenericArrayTest(AnswerTestingTest):\n    _type_name = \"GenericArray\"\n    _attrs = (\"array_func_name\", \"args\", \"kwargs\")\n\n    def __init__(self, ds_fn, array_func, args=None, kwargs=None, decimals=None):\n        super().__init__(ds_fn)\n        self.array_func = array_func\n        self.array_func_name = array_func.__name__\n        self.args = args\n        self.kwargs = kwargs\n        self.decimals = decimals\n\n    def run(self):\n        if self.args is None:\n            args = []\n        else:\n            args = self.args\n        if self.kwargs is None:\n            kwargs = {}\n        else:\n            kwargs = self.kwargs\n        return self.array_func(*args, **kwargs)\n\n    def compare(self, new_result, old_result):\n        if not isinstance(new_result, dict):\n            new_result = {\"answer\": new_result}\n            old_result = {\"answer\": old_result}\n\n        assert_equal(\n            len(new_result),\n            len(old_result),\n            err_msg=\"Number of outputs not equal.\",\n            verbose=True,\n        )\n        for k in new_result:\n            if hasattr(new_result[k], \"d\"):\n                new_result[k] = new_result[k].d\n            if hasattr(old_result[k], \"d\"):\n                old_result[k] = old_result[k].d\n            if self.decimals is None:\n                assert_almost_equal(new_result[k], old_result[k])\n            else:\n                assert_allclose_units(\n                    new_result[k], old_result[k], 10 ** (-self.decimals)\n                )\n\n\nclass AxialPixelizationTest(AnswerTestingTest):\n    # This test is typically used once per geometry or coordinates type.\n    # Feed it a dataset, and it checks that the results of basic pixelization\n    # don't change.\n    _type_name = \"AxialPixelization\"\n    _attrs = (\"geometry\",)\n\n    def __init__(self, ds_fn, decimals=None):\n        super().__init__(ds_fn)\n        self.decimals = decimals\n        self.geometry = self.ds.coordinates.name\n\n    def run(self):\n        rv = {}\n        ds = self.ds\n        for i, axis in enumerate(ds.coordinates.axis_order):\n            (bounds, center, display_center) = pw.get_window_parameters(\n                axis, ds.domain_center, None, ds\n            )\n            slc = ds.slice(axis, center[i])\n            xax = ds.coordinates.axis_name[ds.coordinates.x_axis[axis]]\n            yax = ds.coordinates.axis_name[ds.coordinates.y_axis[axis]]\n            pix_x = ds.coordinates.pixelize(axis, slc, (\"gas\", xax), bounds, (512, 512))\n            pix_y = ds.coordinates.pixelize(axis, slc, (\"gas\", yax), bounds, (512, 512))\n            # Wipe out invalid values (fillers)\n            pix_x[~np.isfinite(pix_x)] = 0.0\n            pix_y[~np.isfinite(pix_y)] = 0.0\n            rv[f\"{axis}_x\"] = pix_x\n            rv[f\"{axis}_y\"] = pix_y\n        return rv\n\n    def compare(self, new_result, old_result):\n        assert_equal(\n            len(new_result),\n            len(old_result),\n            err_msg=\"Number of outputs not equal.\",\n            verbose=True,\n        )\n        for k in new_result:\n            if hasattr(new_result[k], \"d\"):\n                new_result[k] = new_result[k].d\n            if hasattr(old_result[k], \"d\"):\n                old_result[k] = old_result[k].d\n            if self.decimals is None:\n                assert_almost_equal(new_result[k], old_result[k])\n            else:\n                assert_allclose_units(\n                    new_result[k], old_result[k], 10 ** (-self.decimals)\n                )\n\n\ndef requires_answer_testing():\n    return skipif(\n        AnswerTestingTest.result_storage is None,\n        reason=\"answer testing storage is not properly setup\",\n    )\n\n\ndef requires_ds(ds_fn, big_data=False, file_check=False):\n    condition = (big_data and not run_big_data) or not can_run_ds(ds_fn, file_check)\n    return skipif(condition, reason=f\"cannot load dataset {ds_fn}\")\n\n\ndef small_patch_amr(ds_fn, fields, input_center=\"max\", input_weight=(\"gas\", \"density\")):\n    if not can_run_ds(ds_fn):\n        return\n    dso = [None, (\"sphere\", (input_center, (0.1, \"unitary\")))]\n    yield GridHierarchyTest(ds_fn)\n    yield ParentageRelationshipsTest(ds_fn)\n    for field in fields:\n        yield GridValuesTest(ds_fn, field)\n        for dobj_name in dso:\n            for axis in [0, 1, 2]:\n                for weight_field in [None, input_weight]:\n                    yield ProjectionValuesTest(\n                        ds_fn, axis, field, weight_field, dobj_name\n                    )\n            yield FieldValuesTest(ds_fn, field, dobj_name)\n\n\ndef big_patch_amr(ds_fn, fields, input_center=\"max\", input_weight=(\"gas\", \"density\")):\n    if not can_run_ds(ds_fn):\n        return\n    dso = [None, (\"sphere\", (input_center, (0.1, \"unitary\")))]\n    yield GridHierarchyTest(ds_fn)\n    yield ParentageRelationshipsTest(ds_fn)\n    for field in fields:\n        yield GridValuesTest(ds_fn, field)\n        for axis in [0, 1, 2]:\n            for dobj_name in dso:\n                for weight_field in [None, input_weight]:\n                    yield PixelizedProjectionValuesTest(\n                        ds_fn, axis, field, weight_field, dobj_name\n                    )\n\n\ndef _particle_answers(\n    ds, ds_str_repr, ds_nparticles, fields, proj_test_class, center=\"c\"\n):\n    if not can_run_ds(ds):\n        return\n    assert_equal(str(ds), ds_str_repr)\n    dso = [None, (\"sphere\", (center, (0.1, \"unitary\")))]\n    dd = ds.all_data()\n    # this needs to explicitly be \"all\"\n    assert_equal(dd[\"all\", \"particle_position\"].shape, (ds_nparticles, 3))\n    tot = sum(\n        dd[ptype, \"particle_position\"].shape[0] for ptype in ds.particle_types_raw\n    )\n    assert_equal(tot, ds_nparticles)\n    for dobj_name in dso:\n        for field, weight_field in fields.items():\n            particle_type = field[0] in ds.particle_types\n            for axis in [0, 1, 2]:\n                if not particle_type:\n                    yield proj_test_class(ds, axis, field, weight_field, dobj_name)\n            yield FieldValuesTest(ds, field, dobj_name, particle_type=particle_type)\n\n\ndef nbody_answer(ds, ds_str_repr, ds_nparticles, fields, center=\"c\"):\n    return _particle_answers(\n        ds,\n        ds_str_repr,\n        ds_nparticles,\n        fields,\n        PixelizedParticleProjectionValuesTest,\n        center=center,\n    )\n\n\ndef sph_answer(ds, ds_str_repr, ds_nparticles, fields, center=\"c\"):\n    return _particle_answers(\n        ds,\n        ds_str_repr,\n        ds_nparticles,\n        fields,\n        PixelizedProjectionValuesTest,\n        center=center,\n    )\n\n\ndef create_obj(ds, obj_type):\n    # obj_type should be tuple of\n    #  ( obj_name, ( args ) )\n    if obj_type is None:\n        return ds.all_data()\n    cls = getattr(ds, obj_type[0])\n    obj = cls(*obj_type[1])\n    return obj\n"
  },
  {
    "path": "yt/utilities/answer_testing/level_sets_tests.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.utilities.answer_testing.framework import AnswerTestingTest\n\n\nclass ExtractConnectedSetsTest(AnswerTestingTest):\n    _type_name = \"ExtractConnectedSets\"\n    _attrs = ()\n\n    def __init__(self, ds_fn, data_source, field, num_levels, min_val, max_val):\n        super().__init__(ds_fn)\n        self.data_source = data_source\n        self.field = field\n        self.num_levels = num_levels\n        self.min_val = min_val\n        self.max_val = max_val\n\n    def run(self):\n        n, all_sets = self.data_source.extract_connected_sets(\n            self.field, self.num_levels, self.min_val, self.max_val\n        )\n        result = []\n        for level in all_sets:\n            for set_id in all_sets[level]:\n                result.append(\n                    [\n                        all_sets[level][set_id][\"cell_mass\"].size,\n                        all_sets[level][set_id][\"cell_mass\"].sum(),\n                    ]\n                )\n        result = np.array(result)\n        return result\n\n    def compare(self, new_result, old_result):\n        err_msg = f\"Size and/or mass of connected sets do not agree for {self.ds_fn}.\"\n        assert_equal(new_result, old_result, err_msg=err_msg, verbose=True)\n"
  },
  {
    "path": "yt/utilities/answer_testing/testing_utilities.py",
    "content": "import functools\nimport hashlib\nimport inspect\nimport os\n\nimport numpy as np\nimport pytest\nimport yaml\n\nimport yt.visualization.particle_plots as particle_plots\nimport yt.visualization.plot_window as pw\nimport yt.visualization.profile_plotter as profile_plotter\nfrom yt.config import ytcfg\nfrom yt.data_objects.selection_objects.region import YTRegion\nfrom yt.data_objects.static_output import Dataset\nfrom yt.loaders import load, load_simulation\nfrom yt.utilities.on_demand_imports import _h5py as h5py\nfrom yt.visualization.volume_rendering.scene import Scene\n\n\ndef _streamline_for_io(params):\n    r\"\"\"\n    Put test results in a more io-friendly format.\n\n    Many of yt's tests use objects such as tuples as test parameters\n    (fields, for instance), but when these objects are written to a\n    yaml file, yaml includes python specific anchors that make the file\n    harder to read and less portable. The goal of this function is to\n    convert these objects to strings (using __repr__() has it's own\n    issues) in order to solve this problem.\n\n    Parameters\n    ----------\n    params : dict\n        The dictionary of test parameters in the form\n        {param_name : param_value}.\n\n    Returns\n    -------\n    streamlined_params : dict\n        The dictionary of parsed and converted\n        {param_name : param_value} pairs.\n    \"\"\"\n    streamlined_params = {}\n    for key, value in params.items():\n        # Check for user-defined functions\n        if inspect.isfunction(key):\n            key = key.__name__\n        if inspect.isfunction(value):\n            value = value.__name__\n        # The key can be nested iterables, e.g.,\n        # d = [None, ('sphere', (center, (0.1, 'unitary')))] so we need\n        # to use recursion\n        if not isinstance(key, str) and hasattr(key, \"__iter__\"):\n            key = _iterable_to_string(key)\n        # The value can also be nested iterables\n        if not isinstance(value, str) and hasattr(value, \"__iter__\"):\n            value = _iterable_to_string(value)\n        # Scene objects need special treatment to make them more IO friendly\n        if isinstance(value, Scene):\n            value = \"Scene\"\n        elif isinstance(value, YTRegion):\n            value = \"Region\"\n        streamlined_params[key] = value\n    return streamlined_params\n\n\ndef _iterable_to_string(iterable):\n    r\"\"\"\n    An extension of streamline_for_io that does the work of making an\n    iterable more io-friendly.\n\n    Parameters\n    ----------\n    iterable : python iterable\n        The object to be parsed and converted.\n\n    Returns\n    -------\n    result : str\n        The io-friendly version of the passed iterable.\n    \"\"\"\n    result = iterable.__class__.__name__\n    for elem in iterable:\n        # Check for user-defined functions\n        if inspect.isfunction(elem):\n            result += \"_\" + elem.__name__\n        # Non-string iterables (e.g., lists, tuples, etc.)\n        elif not isinstance(elem, str) and hasattr(elem, \"__iter__\"):\n            result += \"_\" + _iterable_to_string(elem)\n        # Non-string non-iterables (ints, floats, etc.)\n        elif not isinstance(elem, str) and not hasattr(elem, \"__iter__\"):\n            result += \"_\" + str(elem)\n        # Strings\n        elif isinstance(elem, str):\n            result += \"_\" + elem\n    return result\n\n\ndef _hash_results(results):\n    r\"\"\"\n    Driver function for hashing the test result.\n\n    Parameters\n    ----------\n    results : dict\n        Dictionary of {test_name : test_result} pairs.\n\n    Returns\n    -------\n    results : dict\n        Same as the passed results, but the test_results are now\n        hex digests of the hashed test_result.\n    \"\"\"\n    # Here, results should be comprised of only the tests, not the test\n    # parameters\n    # Use a new dictionary so as to not overwrite the non-hashed test\n    # results in case those are to be saved\n    hashed_results = {}\n    for test_name, test_value in results.items():\n        hashed_results[test_name] = generate_hash(test_value)\n    return hashed_results\n\n\ndef _hash_dict(data):\n    r\"\"\"\n    Specifically handles hashing a dictionary object.\n\n    Parameters\n    ----------\n    data : dict\n        The dictionary to be hashed.\n\n    Returns\n    -------\n    hd.hexdigest : str\n        The hex digest of the hashed dictionary.\n    \"\"\"\n    hd = None\n    for key, value in data.items():\n        # Some keys are tuples, not strings\n        if not isinstance(key, str):\n            key = key.__repr__()\n        # Test suites can return values that are dictionaries of other tests\n        if isinstance(value, dict):\n            hashed_data = _hash_dict(value)\n        else:\n            hashed_data = bytearray(key.encode(\"utf8\")) + bytearray(value)\n        # If we're returning from a recursive call (and therefore hashed_data\n        # is a hex digest), we need to encode the string before it can be\n        # hashed\n        if isinstance(hashed_data, str):\n            hashed_data = hashed_data.encode(\"utf8\")\n        if hd is None:\n            hd = hashlib.md5(hashed_data)\n        else:\n            hd.update(hashed_data)\n    return hd.hexdigest()\n\n\ndef generate_hash(data):\n    r\"\"\"\n    Actually performs the hash operation.\n\n    Parameters\n    ----------\n    data : python object\n        Data to be hashed.\n\n    Returns\n    -------\n    hd : str\n        Hex digest of the hashed data.\n    \"\"\"\n    # Sometimes md5 complains that the data is not contiguous, so we\n    # make it so here\n    if isinstance(data, np.ndarray):\n        data = np.ascontiguousarray(data)\n    elif isinstance(data, str):\n        data = data.encode(\"utf-8\")\n    # Try to hash. Some tests return hashable types (like ndarrays) and\n    # others don't (such as dictionaries)\n    try:\n        hd = hashlib.md5(data).hexdigest()\n        # Handle those tests that return non-hashable types. This is done\n        # here instead of in the tests themselves to try and reduce boilerplate\n        # and provide a central location where all of this is done in case it needs\n        # to be changed\n    except TypeError:\n        if isinstance(data, dict):\n            hd = _hash_dict(data)\n        elif data is None:\n            hd = hashlib.md5(bytes(str(-1).encode(\"utf-8\"))).hexdigest()\n        else:\n            raise\n    return hd\n\n\ndef _save_result(data, output_file):\n    r\"\"\"\n    Saves the test results to the desired answer file.\n\n    Parameters\n    ----------\n    data : dict\n        Test results to be saved.\n\n    output_file : str\n        Name of file to save results to.\n    \"\"\"\n    with open(output_file, \"a\") as f:\n        yaml.dump(data, f)\n\n\ndef _save_raw_arrays(arrays, answer_file, func_name):\n    r\"\"\"\n    Saves the raw arrays produced from answer tests to a file.\n\n    The structure of `answer_file` is: each test function (e.g.,\n    test_toro1d[0-None-None-0]) forms a group. Within each group is a\n    hdf5 dataset named after the test (e.g., field_values). The value\n    stored in each dataset is the raw array corresponding to that\n    test and function.\n\n    Parameters\n    ----------\n    arrays : dict\n        Keys are the test name (e.g. field_values) and the values are\n        the actual answer arrays produced by the test.\n\n    answer_file : str\n        The name of the file to save the answers to, in hdf5 format.\n\n    func_name : str\n        The name of the function (possibly augmented by pytest with\n        test parameters) that called the test functions\n        (e.g, test_toro1d).\n    \"\"\"\n    with h5py.File(answer_file, \"a\") as f:\n        grp = f.create_group(func_name)\n        for test_name, test_data in arrays.items():\n            # Some answer tests (e.g., grid_values, projection_values)\n            # return a dictionary, which cannot be handled by h5py\n            if isinstance(test_data, dict):\n                sub_grp = grp.create_group(test_name)\n                _parse_raw_answer_dict(test_data, sub_grp)\n            else:\n                # Some tests return None, which hdf5 can't handle, and there is\n                # no proxy, so we have to make one ourselves. Using -1\n                if test_data is None:\n                    test_data = -1\n                grp.create_dataset(test_name, data=test_data)\n\n\ndef _parse_raw_answer_dict(d, h5grp):\n    for k, v in d.items():\n        if isinstance(v, dict):\n            h5_sub_grp = h5grp.create_group(k)\n            _parse_raw_answer_dict(v, h5_sub_grp)\n        else:\n            k = str(k)\n            h5grp.create_dataset(k, data=v)\n\n\ndef _compare_raw_arrays(arrays, answer_file, func_name):\n    r\"\"\"\n    Reads in previously saved raw array data and compares the current\n    results with the old ones.\n\n    The structure of `answer_file` is: each test function (e.g.,\n    test_toro1d[0-None-None-0]) forms a group. Within each group is a\n    hdf5 dataset named after the test (e.g., field_values). The value\n    stored in each dataset is the raw array corresponding to that\n    test and function.\n\n    Parameters\n    ----------\n    arrays : dict\n        Keys are the test name (e.g. field_values) and the values are\n        the actual answer arrays produced by the test.\n\n    answer_file : str\n        The name of the file to load the answers from, in hdf5 format.\n\n    func_name : str\n        The name of the function (possibly augmented by pytest with\n        test parameters) that called the test functions\n        (e.g, test_toro1d).\n    \"\"\"\n    with h5py.File(answer_file, \"r\") as f:\n        for test_name, new_answer in arrays.items():\n            np.testing.assert_array_equal(f[func_name][test_name][:], new_answer)\n\n\ndef can_run_ds(ds_fn, file_check=False):\n    r\"\"\"\n    Validates whether or not a given input can be loaded and used as a\n    Dataset object.\n    \"\"\"\n    if isinstance(ds_fn, Dataset):\n        return True\n    path = ytcfg.get(\"yt\", \"test_data_dir\")\n    if not os.path.isdir(path):\n        return False\n    if file_check:\n        return os.path.isfile(os.path.join(path, ds_fn))\n    try:\n        load(ds_fn)\n        return True\n    except FileNotFoundError:\n        return False\n\n\ndef can_run_sim(sim_fn, sim_type, file_check=False):\n    r\"\"\"\n    Validates whether or not a given input can be used as a simulation\n    time-series object.\n    \"\"\"\n    path = ytcfg.get(\"yt\", \"test_data_dir\")\n    if not os.path.isdir(path):\n        return False\n    if file_check:\n        return os.path.isfile(os.path.join(path, sim_fn))\n    try:\n        load_simulation(sim_fn, sim_type)\n    except FileNotFoundError:\n        return False\n    return True\n\n\ndef data_dir_load(ds_fn, cls=None, args=None, kwargs=None):\n    r\"\"\"\n    Loads a sample dataset from the designated test_data_dir for use in\n    testing.\n    \"\"\"\n    args = args or ()\n    kwargs = kwargs or {}\n    path = ytcfg.get(\"yt\", \"test_data_dir\")\n    # Some frontends require their field_lists during test parameterization.\n    # If the data isn't found, the parameterizing functions return None, since\n    # pytest.skip cannot be called outside of a test or fixture.\n    if ds_fn is None:\n        raise FileNotFoundError\n    if not os.path.isdir(path):\n        raise FileNotFoundError\n    if isinstance(ds_fn, Dataset):\n        return ds_fn\n    if cls is None:\n        ds = load(ds_fn, *args, **kwargs)\n    else:\n        ds = cls(os.path.join(path, ds_fn), *args, **kwargs)\n    ds.index\n    return ds\n\n\ndef requires_ds(ds_fn, file_check=False):\n    r\"\"\"\n    Meta-wrapper for specifying required data for a test and\n    checking if said data exists.\n    \"\"\"\n\n    def ffalse(func):\n        @functools.wraps(func)\n        def skip(*args, **kwargs):\n            msg = f\"{ds_fn} not found, skipping {func.__name__}.\"\n            pytest.skip(msg)\n\n        return skip\n\n    def ftrue(func):\n        @functools.wraps(func)\n        def true_wrapper(*args, **kwargs):\n            return func(*args, **kwargs)\n\n        return true_wrapper\n\n    if not can_run_ds(ds_fn, file_check):\n        return ffalse\n    else:\n        return ftrue\n\n\ndef requires_sim(sim_fn, sim_type, file_check=False):\n    r\"\"\"\n    Meta-wrapper for specifying a required simulation for a test and\n    checking if said simulation exists.\n    \"\"\"\n\n    def ffalse(func):\n        @functools.wraps(func)\n        def skip(*args, **kwargs):\n            msg = f\"{sim_fn} not found, skipping {func.__name__}.\"\n            pytest.skip(msg)\n\n        return skip\n\n    def ftrue(func):\n        @functools.wraps(func)\n        def true_wrapper(*args, **kwargs):\n            return func\n\n        return true_wrapper\n\n    if not can_run_sim(sim_fn, sim_type, file_check):\n        return ffalse\n    else:\n        return ftrue\n\n\ndef _create_plot_window_attribute_plot(ds, plot_type, field, axis, pkwargs=None):\n    r\"\"\"\n    Convenience function used in plot_window_attribute_test.\n\n    Parameters\n    ----------\n    ds : Dataset\n        The Dataset object from which the plotting data is taken.\n\n    plot_type : string\n        Type of plot to make (e.g., SlicePlot).\n\n    field : yt field\n        The field (e.g, density) to plot.\n\n    axis : int\n        The plot axis to plot or project along.\n\n    pkwargs : dict\n        Any keywords to be passed when creating the plot.\n    \"\"\"\n    if plot_type is None:\n        raise RuntimeError(\"Must explicitly request a plot type\")\n    cls = getattr(pw, plot_type, None)\n    if cls is None:\n        cls = getattr(particle_plots, plot_type)\n    plot = cls(ds, axis, field, **pkwargs)\n    return plot\n\n\ndef _create_phase_plot_attribute_plot(\n    data_source, x_field, y_field, z_field, plot_type, plot_kwargs=None\n):\n    r\"\"\"\n    Convenience function used in phase_plot_attribute_test.\n\n    Parameters\n    ----------\n    data_source : Dataset object\n        The Dataset object from which the plotting data is taken.\n\n    x_field : yt field\n        Field to plot on x-axis.\n\n    y_field : yt field\n        Field to plot on y-axis.\n\n    z_field : yt field\n        Field to plot on z-axis.\n\n    plot_type : string\n        Type of plot to make (e.g., SlicePlot).\n\n    plot_kwargs : dict\n        Any keywords to be passed when creating the plot.\n    \"\"\"\n    if plot_type is None:\n        raise RuntimeError(\"Must explicitly request a plot type\")\n    cls = getattr(profile_plotter, plot_type, None)\n    if cls is None:\n        cls = getattr(particle_plots, plot_type)\n    plot = cls(*(data_source, x_field, y_field, z_field), **plot_kwargs)\n    return plot\n\n\ndef get_parameterization(fname):\n    \"\"\"\n    Returns a dataset's field list to make test parameterizationn easier.\n\n    Some tests (such as those that use the toro1d dataset in enzo) check\n    every field in a dataset. In order to parametrize the tests without\n    having to hardcode a list of every field, this function is used.\n    Additionally, if the dataset cannot be found, this function enables\n    pytest to mark the test as failed without the whole test run crashing,\n    since the parameterization happens at import time.\n    \"\"\"\n    try:\n        ds = data_dir_load(fname)\n        return ds.field_list\n    except FileNotFoundError:\n        return [\n            None,\n        ]\n"
  },
  {
    "path": "yt/utilities/api.py",
    "content": "\"\"\"\nAPI for yt.utilities\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/utilities/chemical_formulas.py",
    "content": "import re\n\nfrom .periodic_table import periodic_table\nfrom .physical_ratios import _primordial_mass_fraction\n\n\nclass ChemicalFormula:\n    def __init__(self, formula_string):\n        # See YTEP-0003 for information on the format.\n        self.formula_string = formula_string\n        self.elements = []\n        if \"_\" in self.formula_string:\n            molecule, ionization = self.formula_string.split(\"_\")\n            if ionization[0] == \"p\":\n                charge = int(ionization[1:])\n            elif ionization[0] == \"m\":\n                charge = -int(ionization[1:])\n            else:\n                raise NotImplementedError\n        elif self.formula_string.startswith(\"El\"):\n            molecule = self.formula_string\n            charge = -1\n        else:\n            molecule = self.formula_string\n            charge = 0\n        self.charge = charge\n        for element, count in re.findall(r\"([A-Z][a-z]*)(\\d*)\", molecule):\n            if count == \"\":\n                count = 1\n            self.elements.append((periodic_table[element], int(count)))\n        self.weight = sum(n * e.weight for e, n in self.elements)\n\n    def __repr__(self):\n        return self.formula_string\n\n\ndef compute_mu(ion_state):\n    if ion_state == \"ionized\" or ion_state is None:\n        # full ionization\n        n_H = 2.0  # fully ionized hydrogen gives two particles\n        n_He = 3.0  # fully ionized helium gives three particles\n    elif ion_state == \"neutral\":\n        n_H = 1.0  # neutral hydrogen gives one particle\n        n_He = 1.0  # neutral helium gives one particle\n    muinv = n_H * _primordial_mass_fraction[\"H\"] / ChemicalFormula(\"H\").weight\n    muinv += n_He * _primordial_mass_fraction[\"He\"] / ChemicalFormula(\"He\").weight\n    return 1.0 / muinv\n"
  },
  {
    "path": "yt/utilities/command_line.py",
    "content": "import argparse\nimport base64\nimport json\nimport os\nimport pprint\nimport sys\nimport textwrap\nfrom typing import Any\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt.config import ytcfg\nfrom yt.funcs import (\n    download_file,\n    enable_plugins,\n    ensure_dir,\n    ensure_dir_exists,\n    get_git_version,\n    mylog,\n    update_git,\n)\nfrom yt.loaders import load\nfrom yt.utilities.exceptions import YTFieldNotParseable, YTUnidentifiedDataType\nfrom yt.utilities.metadata import get_metadata\nfrom yt.visualization.plot_window import ProjectionPlot, SlicePlot\n\n# isort: off\n# This needs to be set before importing startup_tasks\nytcfg[\"yt\", \"internals\", \"command_line\"] = True  # isort: skip\nfrom yt.startup_tasks import parser, subparsers  # isort: skip # noqa: E402\n\n# isort: on\n\n# loading field plugins for backward compatibility, since this module\n# used to do \"from yt.mods import *\"\n\ntry:\n    enable_plugins()\nexcept FileNotFoundError:\n    pass\n\n_default_colormap = ytcfg.get(\"yt\", \"default_colormap\")\n\n\ndef _fix_ds(arg, *args, **kwargs):\n    if os.path.isdir(f\"{arg}\") and os.path.exists(f\"{arg}/{arg}\"):\n        ds = load(f\"{arg}/{arg}\", *args, **kwargs)\n    elif os.path.isdir(f\"{arg}.dir\") and os.path.exists(f\"{arg}.dir/{arg}\"):\n        ds = load(f\"{arg}.dir/{arg}\", *args, **kwargs)\n    elif arg.endswith(\".index\"):\n        ds = load(arg[:-10], *args, **kwargs)\n    else:\n        ds = load(arg, *args, **kwargs)\n    return ds\n\n\ndef _add_arg(sc, arg):\n    if isinstance(arg, str):\n        arg = _common_options[arg].copy()\n    elif isinstance(arg, tuple):\n        exclusive, *args = arg\n        if exclusive:\n            grp = sc.add_mutually_exclusive_group()\n        else:\n            grp = sc.add_argument_group()\n        for arg in args:\n            _add_arg(grp, arg)\n        return\n    argc = dict(arg.items())\n    argnames = []\n    if \"short\" in argc:\n        argnames.append(argc.pop(\"short\"))\n    if \"longname\" in argc:\n        argnames.append(argc.pop(\"longname\"))\n    sc.add_argument(*argnames, **argc)\n\n\ndef _print_failed_source_update(reinstall=False):\n    print()\n    print(\"The yt package is not installed from a git repository,\")\n    print(\"so you must update this installation manually.\")\n    if \"Continuum Analytics\" in sys.version or \"Anaconda\" in sys.version:\n        # see http://stackoverflow.com/a/21318941/1382869 for why we need\n        # to check both Continuum *and* Anaconda\n        print()\n        print(\"Since it looks like you are using a python installation\")\n        print(\"that is managed by conda, you may want to do:\")\n        print()\n        print(\"    $ conda update yt\")\n        print()\n        print(\"to update your yt installation.\")\n        if reinstall:\n            print()\n            print(\"To update all of your packages, you can do:\")\n            print()\n            print(\"    $ conda update --all\")\n    else:\n        print(\"If you manage your python dependencies with pip, you may\")\n        print(\"want to do:\")\n        print()\n        print(\"    $ python -m pip install -U yt\")\n        print()\n        print(\"to update your yt installation.\")\n\n\ndef _print_installation_information(path):\n    import yt\n\n    print()\n    print(\"yt module located at:\")\n    print(f\"    {path}\")\n    if \"YT_DEST\" in os.environ:\n        spath = os.path.join(os.environ[\"YT_DEST\"], \"src\", \"yt-supplemental\")\n        if os.path.isdir(spath):\n            print(\"The supplemental repositories are located at:\")\n            print(f\"    {spath}\")\n    print()\n    print(\"The current version of yt is:\")\n    print()\n    print(\"---\")\n    print(f\"Version = {yt.__version__}\")\n    vstring = get_git_version(path)\n    if vstring is not None:\n        print(f\"Changeset = {vstring.strip()}\")\n    print(\"---\")\n    return vstring\n\n\nclass FileStreamer:\n    final_size = None\n    next_sent = 0\n    chunksize = 100 * 1024\n\n    def __init__(self, f, final_size=None):\n        location = f.tell()\n        f.seek(0, os.SEEK_END)\n        self.final_size = f.tell() - location\n        f.seek(location)\n        self.f = f\n\n    def __iter__(self):\n        from tqdm import tqdm\n\n        with tqdm(\n            total=self.final_size, desc=\"Uploading file\", unit=\"B\", unit_scale=True\n        ) as pbar:\n            while self.f.tell() < self.final_size:\n                yield self.f.read(self.chunksize)\n                pbar.update(self.chunksize)\n\n\n_subparsers = {None: subparsers}\n_subparsers_description = {\n    \"config\": \"Get and set configuration values for yt\",\n}\n\n\nclass YTCommandSubtype(type):\n    def __init__(cls, name, b, d):\n        type.__init__(cls, name, b, d)\n        if cls.name is None:\n            return\n        if cls.subparser not in _subparsers:\n            try:\n                description = _subparsers_description[cls.subparser]\n            except KeyError:\n                description = cls.subparser\n            parent_parser = argparse.ArgumentParser(add_help=False)\n            p = subparsers.add_parser(\n                cls.subparser,\n                help=description,\n                description=description,\n                parents=[parent_parser],\n            )\n            _subparsers[cls.subparser] = p.add_subparsers(\n                title=cls.subparser, dest=cls.subparser\n            )\n        sp = _subparsers[cls.subparser]\n        for name in always_iterable(cls.name):\n            sc = sp.add_parser(name, description=cls.description, help=cls.description)\n            sc.set_defaults(func=cls.run)\n            for arg in cls.args:\n                _add_arg(sc, arg)\n\n\nclass YTCommand(metaclass=YTCommandSubtype):\n    args: tuple[str | dict[str, Any], ...] = ()\n    name: str | list[str] | None = None\n    description: str = \"\"\n    aliases = ()\n    ndatasets: int = 1\n    subparser: str | None = None\n\n    @classmethod\n    def run(cls, args):\n        self = cls()\n        # Check for some things we know; for instance, comma separated\n        # field names should be parsed as tuples.\n        if getattr(args, \"field\", None) is not None and \",\" in args.field:\n            if args.field.count(\",\") > 1:\n                raise YTFieldNotParseable(args.field)\n            args.field = tuple(_.strip() for _ in args.field.split(\",\"))\n        if getattr(args, \"weight\", None) is not None and \",\" in args.weight:\n            if args.weight.count(\",\") > 1:\n                raise YTFieldNotParseable(args.weight)\n            args.weight = tuple(_.strip() for _ in args.weight.split(\",\"))\n        # Some commands need to be run repeatedly on datasets\n        # In fact, this is the rule and the opposite is the exception\n        # BUT, we only want to parse the arguments once.\n        if cls.ndatasets > 1:\n            self(args)\n        else:\n            ds_args = getattr(args, \"ds\", [])\n            if len(ds_args) > 1:\n                datasets = args.ds\n                for ds in datasets:\n                    args.ds = ds\n                    self(args)\n            elif len(ds_args) == 0:\n                datasets = []\n                self(args)\n            else:\n                args.ds = getattr(args, \"ds\", [None])[0]\n                self(args)\n\n\nclass GetParameterFiles(argparse.Action):\n    def __call__(self, parser, namespace, values, option_string=None):\n        if len(values) == 1:\n            datasets = values\n        elif len(values) == 2 and namespace.basename is not None:\n            datasets = [\n                f\"{namespace.basename}{r:04}\"\n                for r in range(int(values[0]), int(values[1]), namespace.skip)\n            ]\n        else:\n            datasets = values\n        namespace.ds = [_fix_ds(ds) for ds in datasets]\n\n\n_common_options = {\n    \"all\": {\n        \"longname\": \"--all\",\n        \"dest\": \"reinstall\",\n        \"default\": False,\n        \"action\": \"store_true\",\n        \"help\": (\n            \"Reinstall the full yt stack in the current location. \"\n            \"This option has been deprecated and will not have any \"\n            \"effect.\"\n        ),\n    },\n    \"ds\": {\n        \"short\": \"ds\",\n        \"action\": GetParameterFiles,\n        \"nargs\": \"+\",\n        \"help\": \"datasets to run on\",\n    },\n    \"ods\": {\n        \"action\": GetParameterFiles,\n        \"dest\": \"ds\",\n        \"nargs\": \"*\",\n        \"help\": \"(Optional) datasets to run on\",\n    },\n    \"axis\": {\n        \"short\": \"-a\",\n        \"longname\": \"--axis\",\n        \"action\": \"store\",\n        \"type\": int,\n        \"dest\": \"axis\",\n        \"default\": 4,\n        \"help\": \"Axis (4 for all three)\",\n    },\n    \"log\": {\n        \"short\": \"-l\",\n        \"longname\": \"--log\",\n        \"action\": \"store_true\",\n        \"dest\": \"takelog\",\n        \"default\": True,\n        \"help\": \"Use logarithmic scale for image\",\n    },\n    \"linear\": {\n        \"longname\": \"--linear\",\n        \"action\": \"store_false\",\n        \"dest\": \"takelog\",\n        \"help\": \"Use linear scale for image\",\n    },\n    \"text\": {\n        \"short\": \"-t\",\n        \"longname\": \"--text\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"text\",\n        \"default\": None,\n        \"help\": \"Textual annotation\",\n    },\n    \"field\": {\n        \"short\": \"-f\",\n        \"longname\": \"--field\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"field\",\n        \"default\": \"density\",\n        \"help\": (\"Field to color by, use a comma to separate field tuple values\"),\n    },\n    \"weight\": {\n        \"short\": \"-g\",\n        \"longname\": \"--weight\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"weight\",\n        \"default\": None,\n        \"help\": (\n            \"Field to weight projections with, \"\n            \"use a comma to separate field tuple values\"\n        ),\n    },\n    \"cmap\": {\n        \"longname\": \"--colormap\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"cmap\",\n        \"default\": _default_colormap,\n        \"help\": \"Colormap name\",\n    },\n    \"zlim\": {\n        \"short\": \"-z\",\n        \"longname\": \"--zlim\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"zlim\",\n        \"default\": None,\n        \"nargs\": 2,\n        \"help\": \"Color limits (min, max)\",\n    },\n    \"dex\": {\n        \"longname\": \"--dex\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"dex\",\n        \"default\": None,\n        \"nargs\": 1,\n        \"help\": \"Number of dex above min to display\",\n    },\n    \"width\": {\n        \"short\": \"-w\",\n        \"longname\": \"--width\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"width\",\n        \"default\": None,\n        \"help\": \"Width in specified units\",\n    },\n    \"unit\": {\n        \"short\": \"-u\",\n        \"longname\": \"--unit\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"unit\",\n        \"default\": \"1\",\n        \"help\": \"Desired axes units\",\n    },\n    \"center\": {\n        \"short\": \"-c\",\n        \"longname\": \"--center\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"center\",\n        \"default\": None,\n        \"nargs\": 3,\n        \"help\": \"Center, space separated (-1 -1 -1 for max)\",\n    },\n    \"max\": {\n        \"short\": \"-m\",\n        \"longname\": \"--max\",\n        \"action\": \"store_true\",\n        \"dest\": \"max\",\n        \"default\": False,\n        \"help\": \"Center the plot on the density maximum\",\n    },\n    \"bn\": {\n        \"short\": \"-b\",\n        \"longname\": \"--basename\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"basename\",\n        \"default\": None,\n        \"help\": \"Basename of datasets\",\n    },\n    \"output\": {\n        \"short\": \"-o\",\n        \"longname\": \"--output\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"output\",\n        \"default\": \"frames/\",\n        \"help\": \"Folder in which to place output images\",\n    },\n    \"outputfn\": {\n        \"short\": \"-o\",\n        \"longname\": \"--output\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"output\",\n        \"default\": None,\n        \"help\": \"File in which to place output\",\n    },\n    \"skip\": {\n        \"short\": \"-s\",\n        \"longname\": \"--skip\",\n        \"action\": \"store\",\n        \"type\": int,\n        \"dest\": \"skip\",\n        \"default\": 1,\n        \"help\": \"Skip factor for outputs\",\n    },\n    \"proj\": {\n        \"short\": \"-p\",\n        \"longname\": \"--projection\",\n        \"action\": \"store_true\",\n        \"dest\": \"projection\",\n        \"default\": False,\n        \"help\": \"Use a projection rather than a slice\",\n    },\n    \"maxw\": {\n        \"longname\": \"--max-width\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"max_width\",\n        \"default\": 1.0,\n        \"help\": \"Maximum width in code units\",\n    },\n    \"minw\": {\n        \"longname\": \"--min-width\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"min_width\",\n        \"default\": 50,\n        \"help\": \"Minimum width in units of smallest dx (default: 50)\",\n    },\n    \"nframes\": {\n        \"short\": \"-n\",\n        \"longname\": \"--nframes\",\n        \"action\": \"store\",\n        \"type\": int,\n        \"dest\": \"nframes\",\n        \"default\": 100,\n        \"help\": \"Number of frames to generate\",\n    },\n    \"slabw\": {\n        \"longname\": \"--slab-width\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"slab_width\",\n        \"default\": 1.0,\n        \"help\": \"Slab width in specified units\",\n    },\n    \"slabu\": {\n        \"short\": \"-g\",\n        \"longname\": \"--slab-unit\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"slab_unit\",\n        \"default\": \"1\",\n        \"help\": \"Desired units for the slab\",\n    },\n    \"ptype\": {\n        \"longname\": \"--particle-type\",\n        \"action\": \"store\",\n        \"type\": int,\n        \"dest\": \"ptype\",\n        \"default\": 2,\n        \"help\": \"Particle type to select\",\n    },\n    \"agecut\": {\n        \"longname\": \"--age-cut\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"age_filter\",\n        \"default\": None,\n        \"nargs\": 2,\n        \"help\": \"Bounds for the field to select\",\n    },\n    \"uboxes\": {\n        \"longname\": \"--unit-boxes\",\n        \"action\": \"store_true\",\n        \"dest\": \"unit_boxes\",\n        \"help\": \"Display heldsul unit boxes\",\n    },\n    \"thresh\": {\n        \"longname\": \"--threshold\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"threshold\",\n        \"default\": None,\n        \"help\": \"Density threshold\",\n    },\n    \"dm_only\": {\n        \"longname\": \"--all-particles\",\n        \"action\": \"store_false\",\n        \"dest\": \"dm_only\",\n        \"default\": True,\n        \"help\": \"Use all particles\",\n    },\n    \"grids\": {\n        \"longname\": \"--show-grids\",\n        \"action\": \"store_true\",\n        \"dest\": \"grids\",\n        \"default\": False,\n        \"help\": \"Show the grid boundaries\",\n    },\n    \"time\": {\n        \"longname\": \"--time\",\n        \"action\": \"store_true\",\n        \"dest\": \"time\",\n        \"default\": False,\n        \"help\": \"Print time in years on image\",\n    },\n    \"contours\": {\n        \"longname\": \"--contours\",\n        \"action\": \"store\",\n        \"type\": int,\n        \"dest\": \"contours\",\n        \"default\": None,\n        \"help\": \"Number of Contours for Rendering\",\n    },\n    \"contour_width\": {\n        \"longname\": \"--contour_width\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"contour_width\",\n        \"default\": None,\n        \"help\": \"Width of gaussians used for rendering.\",\n    },\n    \"enhance\": {\n        \"longname\": \"--enhance\",\n        \"action\": \"store_true\",\n        \"dest\": \"enhance\",\n        \"default\": False,\n        \"help\": \"Enhance!\",\n    },\n    \"valrange\": {\n        \"short\": \"-r\",\n        \"longname\": \"--range\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"valrange\",\n        \"default\": None,\n        \"nargs\": 2,\n        \"help\": \"Range, space separated\",\n    },\n    \"up\": {\n        \"longname\": \"--up\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"up\",\n        \"default\": None,\n        \"nargs\": 3,\n        \"help\": \"Up, space separated\",\n    },\n    \"viewpoint\": {\n        \"longname\": \"--viewpoint\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"viewpoint\",\n        \"default\": [1.0, 1.0, 1.0],\n        \"nargs\": 3,\n        \"help\": \"Viewpoint, space separated\",\n    },\n    \"pixels\": {\n        \"longname\": \"--pixels\",\n        \"action\": \"store\",\n        \"type\": int,\n        \"dest\": \"pixels\",\n        \"default\": None,\n        \"help\": \"Number of Pixels for Rendering\",\n    },\n    \"halos\": {\n        \"longname\": \"--halos\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"halos\",\n        \"default\": \"multiple\",\n        \"help\": \"Run halo profiler on a 'single' halo or 'multiple' halos.\",\n    },\n    \"halo_radius\": {\n        \"longname\": \"--halo_radius\",\n        \"action\": \"store\",\n        \"type\": float,\n        \"dest\": \"halo_radius\",\n        \"default\": 0.1,\n        \"help\": \"Constant radius for profiling halos if using hop output files with no \"\n        + \"radius entry. Default: 0.1.\",\n    },\n    \"halo_radius_units\": {\n        \"longname\": \"--halo_radius_units\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"halo_radius_units\",\n        \"default\": \"1\",\n        \"help\": \"Units for radius used with --halo_radius flag. \"\n        + \"Default: '1' (code units).\",\n    },\n    \"halo_hop_style\": {\n        \"longname\": \"--halo_hop_style\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"halo_hop_style\",\n        \"default\": \"new\",\n        \"help\": \"Style of hop output file. \"\n        + \"'new' for yt_hop files and 'old' for enzo_hop files.\",\n    },\n    \"halo_dataset\": {\n        \"longname\": \"--halo_dataset\",\n        \"action\": \"store\",\n        \"type\": str,\n        \"dest\": \"halo_dataset\",\n        \"default\": None,\n        \"help\": \"HaloProfiler dataset.\",\n    },\n    \"make_profiles\": {\n        \"longname\": \"--make_profiles\",\n        \"action\": \"store_true\",\n        \"default\": False,\n        \"help\": \"Make profiles with halo profiler.\",\n    },\n    \"make_projections\": {\n        \"longname\": \"--make_projections\",\n        \"action\": \"store_true\",\n        \"default\": False,\n        \"help\": \"Make projections with halo profiler.\",\n    },\n}\n\n\nclass YTInstInfoCmd(YTCommand):\n    name = [\"instinfo\", \"version\"]\n    args = (\n        {\n            \"short\": \"-u\",\n            \"longname\": \"--update-source\",\n            \"action\": \"store_true\",\n            \"default\": False,\n            \"help\": \"Update the yt installation, if able\",\n        },\n        {\n            \"short\": \"-o\",\n            \"longname\": \"--output-version\",\n            \"action\": \"store\",\n            \"default\": None,\n            \"dest\": \"outputfile\",\n            \"help\": \"File into which the current revision number will be stored\",\n        },\n    )\n    description = \"\"\"\n        Get some information about the yt installation\n\n        \"\"\"\n\n    def __call__(self, opts):\n        import importlib.resources as importlib_resources\n\n        path = os.path.dirname(importlib_resources.files(\"yt\"))\n        vstring = _print_installation_information(path)\n        if vstring is not None:\n            print(\"This installation CAN be automatically updated.\")\n            if opts.update_source:\n                update_git(path)\n        elif opts.update_source:\n            _print_failed_source_update()\n        if vstring is not None and opts.outputfile is not None:\n            open(opts.outputfile, \"w\").write(vstring)\n\n\nclass YTLoadCmd(YTCommand):\n    name = \"load\"\n    description = \"\"\"\n        Load a single dataset into an IPython instance\n\n        \"\"\"\n\n    args = (\"ds\",)\n\n    def __call__(self, args):\n        if args.ds is None:\n            print(\"Could not load file.\")\n            sys.exit()\n        import IPython\n        from traitlets.config.loader import Config\n\n        import yt\n\n        local_ns = {}\n        local_ns[\"ds\"] = args.ds\n        local_ns[\"pf\"] = args.ds\n        local_ns[\"yt\"] = yt\n\n        cfg = Config()\n        # prepend sys.path with current working directory\n        sys.path.insert(0, \"\")\n        IPython.embed(config=cfg, user_ns=local_ns)\n\n\nclass YTMapserverCmd(YTCommand):\n    args = (\n        \"proj\",\n        \"field\",\n        \"weight\",\n        \"linear\",\n        \"center\",\n        \"width\",\n        \"cmap\",\n        {\n            \"short\": \"-a\",\n            \"longname\": \"--axis\",\n            \"action\": \"store\",\n            \"type\": int,\n            \"dest\": \"axis\",\n            \"default\": 0,\n            \"help\": \"Axis\",\n        },\n        {\n            \"short\": \"-o\",\n            \"longname\": \"--host\",\n            \"action\": \"store\",\n            \"type\": str,\n            \"dest\": \"host\",\n            \"default\": None,\n            \"help\": \"IP Address to bind on\",\n        },\n        {\"short\": \"ds\", \"nargs\": 1, \"type\": str, \"help\": \"The dataset to load.\"},\n    )\n\n    name = \"mapserver\"\n    description = \"\"\"\n        Serve a plot in a GMaps-style interface\n\n        \"\"\"\n\n    def __call__(self, args):\n        from yt.frontends.ramses.data_structures import RAMSESDataset\n        from yt.visualization.mapserver.pannable_map import PannableMapServer\n\n        # For RAMSES datasets, use the bbox feature to make the dataset load faster\n        if RAMSESDataset._is_valid(args.ds) and args.center and args.width:\n            kwa = {\n                \"bbox\": [\n                    [c - args.width / 2 for c in args.center],\n                    [c + args.width / 2 for c in args.center],\n                ]\n            }\n        else:\n            kwa = {}\n\n        ds = _fix_ds(args.ds, **kwa)\n        if args.center and args.width:\n            center = args.center\n            width = args.width\n            ad = ds.box(\n                left_edge=[c - args.width / 2 for c in args.center],\n                right_edge=[c + args.width / 2 for c in args.center],\n            )\n        else:\n            center = [0.5] * 3\n            width = 1.0\n            ad = ds.all_data()\n\n        if args.axis >= 4:\n            print(\"Doesn't work with multiple axes!\")\n            return\n        if args.projection:\n            p = ProjectionPlot(\n                ds,\n                args.axis,\n                args.field,\n                weight_field=args.weight,\n                data_source=ad,\n                center=center,\n                width=width,\n            )\n        else:\n            p = SlicePlot(\n                ds, args.axis, args.field, data_source=ad, center=center, width=width\n            )\n        p.set_log(\"all\", args.takelog)\n        p.set_cmap(\"all\", args.cmap)\n\n        PannableMapServer(p.data_source, args.field, args.takelog, args.cmap)\n        try:\n            import bottle\n        except ImportError as e:\n            raise ImportError(\n                \"The mapserver functionality requires the bottle \"\n                \"package to be installed. Please install using `pip \"\n                \"install bottle`.\"\n            ) from e\n        bottle.debug(True)\n        if args.host is not None:\n            colonpl = args.host.find(\":\")\n            if colonpl >= 0:\n                port = int(args.host.split(\":\")[-1])\n                args.host = args.host[:colonpl]\n            else:\n                port = 8080\n            bottle.run(server=\"auto\", host=args.host, port=port)\n        else:\n            bottle.run(server=\"auto\")\n\n\nclass YTPastebinCmd(YTCommand):\n    name = \"pastebin\"\n    args = (\n        {\n            \"short\": \"-l\",\n            \"longname\": \"--language\",\n            \"action\": \"store\",\n            \"default\": None,\n            \"dest\": \"language\",\n            \"help\": \"Use syntax highlighter for the file in language\",\n        },\n        {\n            \"short\": \"-L\",\n            \"longname\": \"--languages\",\n            \"action\": \"store_true\",\n            \"default\": False,\n            \"dest\": \"languages\",\n            \"help\": \"Retrieve a list of supported languages\",\n        },\n        {\n            \"short\": \"-e\",\n            \"longname\": \"--encoding\",\n            \"action\": \"store\",\n            \"default\": \"utf-8\",\n            \"dest\": \"encoding\",\n            \"help\": \"Specify the encoding of a file (default is \"\n            \"utf-8 or guessing if available)\",\n        },\n        {\n            \"short\": \"-b\",\n            \"longname\": \"--open-browser\",\n            \"action\": \"store_true\",\n            \"default\": False,\n            \"dest\": \"open_browser\",\n            \"help\": \"Open the paste in a web browser\",\n        },\n        {\n            \"short\": \"-p\",\n            \"longname\": \"--private\",\n            \"action\": \"store_true\",\n            \"default\": False,\n            \"dest\": \"private\",\n            \"help\": \"Paste as private\",\n        },\n        {\n            \"short\": \"-c\",\n            \"longname\": \"--clipboard\",\n            \"action\": \"store_true\",\n            \"default\": False,\n            \"dest\": \"clipboard\",\n            \"help\": \"File to output to; else, print.\",\n        },\n        {\"short\": \"file\", \"type\": str},\n    )\n    description = \"\"\"\n        Post a script to an anonymous pastebin\n\n        \"\"\"\n\n    def __call__(self, args):\n        from yt.utilities import lodgeit as lo\n\n        lo.main(\n            args.file,\n            languages=args.languages,\n            language=args.language,\n            encoding=args.encoding,\n            open_browser=args.open_browser,\n            private=args.private,\n            clipboard=args.clipboard,\n        )\n\n\nclass YTPastebinGrabCmd(YTCommand):\n    args = ({\"short\": \"number\", \"type\": str},)\n    name = \"pastebin_grab\"\n    description = \"\"\"\n        Print an online pastebin to STDOUT for local use.\n        \"\"\"\n\n    def __call__(self, args):\n        from yt.utilities import lodgeit as lo\n\n        lo.main(None, download=args.number)\n\n\nclass YTPlotCmd(YTCommand):\n    args = (\n        \"width\",\n        \"unit\",\n        \"bn\",\n        \"proj\",\n        \"center\",\n        \"zlim\",\n        \"axis\",\n        \"field\",\n        \"weight\",\n        \"skip\",\n        \"cmap\",\n        \"output\",\n        \"grids\",\n        \"time\",\n        \"ds\",\n        \"max\",\n        \"log\",\n        \"linear\",\n        {\n            \"short\": \"-fu\",\n            \"longname\": \"--field-unit\",\n            \"action\": \"store\",\n            \"type\": str,\n            \"dest\": \"field_unit\",\n            \"default\": None,\n            \"help\": \"Desired field units\",\n        },\n        {\n            \"longname\": \"--show-scale-bar\",\n            \"action\": \"store_true\",\n            \"help\": \"Annotate the plot with the scale\",\n        },\n    )\n\n    name = \"plot\"\n\n    description = \"\"\"\n        Create a set of images\n\n        \"\"\"\n\n    def __call__(self, args):\n        ds = args.ds\n        center = args.center\n        if args.center == (-1, -1, -1):\n            mylog.info(\"No center fed in; seeking.\")\n            v, center = ds.find_max(\"density\")\n        if args.max:\n            v, center = ds.find_max(\"density\")\n        elif args.center is None:\n            center = 0.5 * (ds.domain_left_edge + ds.domain_right_edge)\n        center = np.array(center)\n        if ds.dimensionality < 3:\n            dummy_dimensions = np.nonzero(ds.index.grids[0].ActiveDimensions <= 1)\n            axes = dummy_dimensions[0][0]\n        elif args.axis == 4:\n            axes = range(3)\n        else:\n            axes = args.axis\n\n        unit = args.unit\n        if unit is None:\n            unit = \"unitary\"\n        if args.width is None:\n            width = None\n        else:\n            width = (args.width, args.unit)\n\n        for ax in always_iterable(axes):\n            mylog.info(\"Adding plot for axis %i\", ax)\n            if args.projection:\n                plt = ProjectionPlot(\n                    ds,\n                    ax,\n                    args.field,\n                    center=center,\n                    width=width,\n                    weight_field=args.weight,\n                )\n            else:\n                plt = SlicePlot(ds, ax, args.field, center=center, width=width)\n            if args.grids:\n                plt.annotate_grids()\n            if args.time:\n                plt.annotate_timestamp()\n            if args.show_scale_bar:\n                plt.annotate_scale()\n\n            if args.field_unit:\n                plt.set_unit(args.field, args.field_unit)\n\n            plt.set_cmap(args.field, args.cmap)\n            plt.set_log(args.field, args.takelog)\n            if args.zlim:\n                plt.set_zlim(args.field, *args.zlim)\n            ensure_dir_exists(args.output)\n            plt.save(os.path.join(args.output, f\"{ds}\"))\n\n\nclass YTRPDBCmd(YTCommand):\n    name = \"rpdb\"\n    description = \"\"\"\n        Connect to a currently running (on localhost) rpd session.\n\n        Commands run with --rpdb will trigger an rpdb session with any\n        uncaught exceptions.\n\n        \"\"\"\n    args = (\n        {\n            \"short\": \"-t\",\n            \"longname\": \"--task\",\n            \"action\": \"store\",\n            \"default\": 0,\n            \"dest\": \"task\",\n            \"help\": \"Open a web browser.\",\n        },\n    )\n\n    def __call__(self, args):\n        from . import rpdb\n\n        rpdb.run_rpdb(int(args.task))\n\n\nclass YTStatsCmd(YTCommand):\n    args = (\n        \"outputfn\",\n        \"bn\",\n        \"skip\",\n        \"ds\",\n        \"field\",\n        {\n            \"longname\": \"--max\",\n            \"action\": \"store_true\",\n            \"default\": False,\n            \"dest\": \"max\",\n            \"help\": \"Display maximum of field requested through -f option.\",\n        },\n        {\n            \"longname\": \"--min\",\n            \"action\": \"store_true\",\n            \"default\": False,\n            \"dest\": \"min\",\n            \"help\": \"Display minimum of field requested through -f option.\",\n        },\n    )\n    name = \"stats\"\n    description = \"\"\"\n        Print stats and max/min value of a given field (if requested),\n        for one or more datasets\n\n        (default field is density)\n\n        \"\"\"\n\n    def __call__(self, args):\n        ds = args.ds\n        ds.print_stats()\n        vals = {}\n        field = ds._get_field_info(args.field)\n        if args.max:\n            vals[\"max\"] = ds.find_max(field)\n            print(f\"Maximum {field.name}: {vals['max'][0]:0.5e} at {vals['max'][1]}\")\n        if args.min:\n            vals[\"min\"] = ds.find_min(field)\n            print(f\"Minimum {field.name}: {vals['min'][0]:0.5e} at {vals['min'][1]}\")\n        if args.output is not None:\n            t = ds.current_time.to(\"yr\")\n            with open(args.output, \"a\") as f:\n                f.write(f\"{ds} ({t:0.5e})\\n\")\n                if \"min\" in vals:\n                    f.write(\n                        f\"Minimum {field.name} is {vals['min'][0]:0.5e} at {vals['min'][1]}\\n\"\n                    )\n                if \"max\" in vals:\n                    f.write(\n                        f\"Maximum {field.name} is {vals['max'][0]:0.5e} at {vals['max'][1]}\\n\"\n                    )\n\n\nclass YTUpdateCmd(YTCommand):\n    args = (\"all\",)\n    name = \"update\"\n    description = \"\"\"\n        Update the yt installation to the most recent version\n\n        \"\"\"\n\n    def __call__(self, opts):\n        import importlib.resources as importlib_resources\n\n        path = os.path.dirname(importlib_resources.files(\"yt\"))\n        vstring = _print_installation_information(path)\n        if vstring is not None:\n            print()\n            print(\"This installation CAN be automatically updated.\")\n            update_git(path)\n        else:\n            _print_failed_source_update(opts.reinstall)\n\n\nclass YTDeleteImageCmd(YTCommand):\n    args = ({\"short\": \"delete_hash\", \"type\": str},)\n    description = \"\"\"\n        Delete image from imgur.com.\n\n        \"\"\"\n    name = \"delete_image\"\n\n    def __call__(self, args):\n        import urllib.error\n        import urllib.request\n\n        headers = {\"Authorization\": f\"Client-ID {ytcfg.get('yt', 'imagebin_api_key')}\"}\n\n        delete_url = ytcfg.get(\"yt\", \"imagebin_delete_url\")\n        req = urllib.request.Request(\n            delete_url.format(delete_hash=args.delete_hash),\n            headers=headers,\n            method=\"DELETE\",\n        )\n        try:\n            response = urllib.request.urlopen(req).read().decode()\n        except urllib.error.HTTPError as e:\n            print(\"ERROR\", e)\n            return {\"deleted\": False}\n\n        rv = json.loads(response)\n        if \"success\" in rv and rv[\"success\"]:\n            print(\"\\nImage successfully deleted!\\n\")\n        else:\n            print()\n            print(\"Something has gone wrong!  Here is the server response:\")\n            print()\n            pprint.pprint(rv)\n\n\nclass YTUploadImageCmd(YTCommand):\n    args = ({\"short\": \"file\", \"type\": str},)\n    description = \"\"\"\n        Upload an image to imgur.com.  Must be PNG.\n\n        \"\"\"\n    name = \"upload_image\"\n\n    def __call__(self, args):\n        import urllib.error\n        import urllib.parse\n        import urllib.request\n\n        filename = args.file\n        if not filename.endswith(\".png\"):\n            print(\"File must be a PNG file!\")\n            return 1\n        headers = {\"Authorization\": f\"Client-ID {ytcfg.get('yt', 'imagebin_api_key')}\"}\n\n        image_data = base64.b64encode(open(filename, \"rb\").read())\n        parameters = {\n            \"image\": image_data,\n            type: \"base64\",\n            \"name\": filename,\n            \"title\": f\"{filename} uploaded by yt\",\n        }\n        data = urllib.parse.urlencode(parameters).encode(\"utf-8\")\n        req = urllib.request.Request(\n            ytcfg.get(\"yt\", \"imagebin_upload_url\"), data=data, headers=headers\n        )\n        try:\n            response = urllib.request.urlopen(req).read().decode()\n        except urllib.error.HTTPError as e:\n            print(\"ERROR\", e)\n            return {\"uploaded\": False}\n        rv = json.loads(response)\n        if \"data\" in rv and \"link\" in rv[\"data\"]:\n            print()\n            print(\"Image successfully uploaded!  You can find it at:\")\n            print(f\"    {rv['data']['link']}\")\n            print()\n            print(\"If you'd like to delete it, use the following\")\n            print(f\"    yt delete_image {rv['data']['deletehash']}\")\n            print()\n        else:\n            print()\n            print(\"Something has gone wrong!  Here is the server response:\")\n            print()\n            pprint.pprint(rv)\n\n\nclass YTUploadFileCmd(YTCommand):\n    args = ({\"short\": \"file\", \"type\": str},)\n    description = \"\"\"\n        Upload a file to yt's curldrop.\n\n        \"\"\"\n    name = \"upload\"\n\n    def __call__(self, args):\n        from yt.utilities.on_demand_imports import _requests as requests\n\n        fs = iter(FileStreamer(open(args.file, \"rb\")))\n        upload_url = ytcfg.get(\"yt\", \"curldrop_upload_url\")\n        r = requests.put(upload_url + \"/\" + os.path.basename(args.file), data=fs)\n        print()\n        print(r.text)\n\n\nclass YTConfigLocalConfigHandler:\n    def load_config(self, args) -> None:\n        import os\n\n        from yt.config import YTConfig\n        from yt.utilities.configure import CONFIG\n\n        local_config_file = YTConfig.get_local_config_file()\n        global_config_file = YTConfig.get_global_config_file()\n\n        local_exists = os.path.exists(local_config_file)\n        global_exists = os.path.exists(global_config_file)\n\n        local_arg_exists = hasattr(args, \"local\")\n        global_arg_exists = hasattr(args, \"global\")\n\n        config_file: str | None = None\n        if getattr(args, \"local\", False):\n            config_file = local_config_file\n        elif getattr(args, \"global\", False):\n            config_file = global_config_file\n        else:\n            if local_exists and global_exists:\n                s = (\n                    \"Yt detected a local and a global configuration file, refusing \"\n                    \"to proceed.\\n\"\n                    f\"Local config file: {local_config_file}\\n\"\n                    f\"Global config file: {global_config_file}\"\n                )\n                # Only print the info about \"--global\" and \"--local\" if they exist\n                if local_arg_exists and global_arg_exists:\n                    s += (\n                        \"\\n\"  # missing eol from previous string\n                        \"Specify which one you want to use using the `--local` or the \"\n                        \"`--global` flags.\"\n                    )\n                sys.exit(s)\n            elif local_exists:\n                config_file = local_config_file\n            elif global_exists:\n                config_file = global_config_file\n\n            if config_file is None:\n                print(\"WARNING: no configuration file installed.\", file=sys.stderr)\n            else:\n                print(\n                    f\"INFO: reading configuration file: {config_file}\", file=sys.stderr\n                )\n        CONFIG.read(config_file)\n\n        self.config_file = config_file\n\n\n_global_local_args = (\n    {\n        \"short\": \"--local\",\n        \"action\": \"store_true\",\n        \"help\": \"Store the configuration in the local configuration file.\",\n    },\n    {\n        \"short\": \"--global\",\n        \"action\": \"store_true\",\n        \"help\": \"Store the configuration in the global configuration file.\",\n    },\n)\n\n\nclass YTConfigGetCmd(YTCommand, YTConfigLocalConfigHandler):\n    subparser = \"config\"\n    name = \"get\"\n    description = \"get a config value\"\n    args = (\n        {\"short\": \"section\", \"help\": \"The section containing the option.\"},\n        {\"short\": \"option\", \"help\": \"The option to retrieve.\"},\n        *_global_local_args,\n    )\n\n    def __call__(self, args):\n        from yt.utilities.configure import get_config\n\n        self.load_config(args)\n\n        print(get_config(args.section, args.option))\n\n\nclass YTConfigSetCmd(YTCommand, YTConfigLocalConfigHandler):\n    subparser = \"config\"\n    name = \"set\"\n    description = \"set a config value\"\n    args = (\n        {\"short\": \"section\", \"help\": \"The section containing the option.\"},\n        {\"short\": \"option\", \"help\": \"The option to set.\"},\n        {\"short\": \"value\", \"help\": \"The value to set the option to.\"},\n        *_global_local_args,\n    )\n\n    def __call__(self, args):\n        from yt.utilities.configure import set_config\n\n        self.load_config(args)\n        if self.config_file is None:\n            self.config_file = os.path.join(os.getcwd(), \"yt.toml\")\n            print(\n                f\"INFO: configuration will be written to {self.config_file}\",\n                file=sys.stderr,\n            )\n        set_config(args.section, args.option, args.value, self.config_file)\n\n\nclass YTConfigRemoveCmd(YTCommand, YTConfigLocalConfigHandler):\n    subparser = \"config\"\n    name = \"rm\"\n    description = \"remove a config option\"\n    args = (\n        {\"short\": \"section\", \"help\": \"The section containing the option.\"},\n        {\"short\": \"option\", \"help\": \"The option to remove.\"},\n        *_global_local_args,\n    )\n\n    def __call__(self, args):\n        from yt.utilities.configure import rm_config\n\n        self.load_config(args)\n\n        rm_config(args.section, args.option, self.config_file)\n\n\nclass YTConfigListCmd(YTCommand, YTConfigLocalConfigHandler):\n    subparser = \"config\"\n    name = \"list\"\n    description = \"show the config content\"\n    args = _global_local_args\n\n    def __call__(self, args):\n        from yt.utilities.configure import write_config\n\n        self.load_config(args)\n\n        write_config(sys.stdout)\n\n\nclass YTConfigPrintPath(YTCommand, YTConfigLocalConfigHandler):\n    subparser = \"config\"\n    name = \"print-path\"\n    description = \"show path to the config file\"\n    args = _global_local_args\n\n    def __call__(self, args):\n        self.load_config(args)\n\n        print(self.config_file)\n\n\nclass YTSearchCmd(YTCommand):\n    args = (\n        {\n            \"short\": \"-o\",\n            \"longname\": \"--output\",\n            \"action\": \"store\",\n            \"type\": str,\n            \"dest\": \"output\",\n            \"default\": \"yt_index.json\",\n            \"help\": \"File in which to place output\",\n        },\n        {\n            \"longname\": \"--check-all\",\n            \"short\": \"-a\",\n            \"help\": \"Attempt to load every file\",\n            \"action\": \"store_true\",\n            \"default\": False,\n            \"dest\": \"check_all\",\n        },\n        {\n            \"longname\": \"--full\",\n            \"short\": \"-f\",\n            \"help\": \"Output full contents of parameter file\",\n            \"action\": \"store_true\",\n            \"default\": False,\n            \"dest\": \"full_output\",\n        },\n    )\n    description = \"\"\"\n        Attempt to find outputs that yt can recognize in directories.\n        \"\"\"\n    name = \"search\"\n\n    def __call__(self, args):\n        from yt.utilities.object_registries import output_type_registry\n\n        candidates = []\n        for base, dirs, files in os.walk(\".\", followlinks=True):\n            print(f\"({len(candidates):>10} candidates) Examining {base}\")\n            recurse = []\n            if args.check_all:\n                candidates.extend([os.path.join(base, _) for _ in files])\n            for _, otr in sorted(output_type_registry.items()):\n                c, r = otr._guess_candidates(base, dirs, files)\n                candidates.extend([os.path.join(base, _) for _ in c])\n                recurse.append(r)\n            if len(recurse) > 0 and not all(recurse):\n                del dirs[:]\n        # Now we have a ton of candidates.  We're going to do something crazy\n        # and try to load each one.\n        records = []\n        for i, c in enumerate(sorted(candidates)):\n            print(f\"({i:>10}/{len(candidates):>10}) Evaluating {c}\")\n            try:\n                record = get_metadata(c, args.full_output)\n            except YTUnidentifiedDataType:\n                continue\n            records.append(record)\n        with open(args.output, \"w\") as f:\n            json.dump(records, f, indent=4)\n        print(f\"Identified {len(records)} records output to {args.output}\")\n\n\nclass YTDownloadData(YTCommand):\n    args = (\n        {\n            \"short\": \"filename\",\n            \"action\": \"store\",\n            \"type\": str,\n            \"help\": \"The name of the file to download\",\n            \"nargs\": \"?\",\n            \"default\": \"\",\n        },\n        {\n            \"short\": \"location\",\n            \"action\": \"store\",\n            \"type\": str,\n            \"nargs\": \"?\",\n            \"help\": \"The location in which to place the file, can be \"\n            '\"supp_data_dir\", \"test_data_dir\", or any valid '\n            \"path on disk. \",\n            \"default\": \"\",\n        },\n        {\n            \"longname\": \"--overwrite\",\n            \"short\": \"-c\",\n            \"help\": \"Overwrite existing file.\",\n            \"action\": \"store_true\",\n            \"default\": False,\n        },\n        {\n            \"longname\": \"--list\",\n            \"short\": \"-l\",\n            \"help\": \"Display all available files.\",\n            \"action\": \"store_true\",\n            \"default\": False,\n        },\n    )\n    description = \"\"\"\n        Download a file from http://yt-project.org/data and save it to a\n        particular location. Files can be saved to the locations provided\n        by the \"test_data_dir\" or \"supp_data_dir\" configuration entries, or\n        any valid path to a location on disk.\n        \"\"\"\n    name = \"download\"\n\n    def __call__(self, args):\n        if args.list:\n            self.get_list()\n            return\n        if not args.filename:\n            raise RuntimeError(\n                \"You need to provide a filename. See --help \"\n                \"for details or use --list to get available \"\n                \"datasets.\"\n            )\n        elif not args.location:\n            raise RuntimeError(\n                \"You need to specify download location. See --help for details.\"\n            )\n        data_url = f\"http://yt-project.org/data/{args.filename}\"\n        if args.location in [\"test_data_dir\", \"supp_data_dir\"]:\n            data_dir = ytcfg.get(\"yt\", args.location)\n            if data_dir == \"/does/not/exist\":\n                raise RuntimeError(f\"'{args.location}' is not configured!\")\n        else:\n            data_dir = args.location\n        if not os.path.exists(data_dir):\n            print(f\"The directory '{data_dir}' does not exist. Creating...\")\n            ensure_dir(data_dir)\n        data_file = os.path.join(data_dir, args.filename)\n        if os.path.exists(data_file) and not args.overwrite:\n            raise OSError(f\"File '{data_file}' exists and overwrite=False!\")\n        print(f\"Attempting to download file: {args.filename}\")\n        fn = download_file(data_url, data_file)\n\n        if not os.path.exists(fn):\n            raise OSError(f\"The file '{args.filename}' did not download!!\")\n        print(f\"File: {args.filename} downloaded successfully to {data_file}\")\n\n    def get_list(self):\n        import urllib.request\n\n        data = (\n            urllib.request.urlopen(\"http://yt-project.org/data/datafiles.json\")\n            .read()\n            .decode(\"utf8\")\n        )\n        data = json.loads(data)\n        for key in data:\n            for ds in data[key]:\n                ds[\"fullname\"] = ds[\"url\"].replace(\"http://yt-project.org/data/\", \"\")\n                print(\"{fullname} ({size}) type: {code}\".format(**ds))\n                for line in textwrap.wrap(ds[\"description\"]):\n                    print(\"\\t\", line)\n\n\ndef run_main():\n    args = parser.parse_args()\n    # The following is a workaround for a nasty Python 3 bug:\n    # http://bugs.python.org/issue16308\n    # http://bugs.python.org/issue9253\n    try:\n        args.func\n    except AttributeError:\n        parser.print_help()\n        sys.exit(0)\n\n    args.func(args)\n\n\nif __name__ == \"__main__\":\n    run_main()\n"
  },
  {
    "path": "yt/utilities/configuration_tree.py",
    "content": "class ConfigNode:\n    def __init__(self, key, parent=None):\n        self.key = key\n        self.children = {}\n        self.parent = parent\n\n    def add(self, key, child):\n        self.children[key] = child\n        child.parent = self\n\n    def update(self, other, extra_data=None):\n        def _recursive_upsert(other_dict, keys):\n            for key, val in other_dict.items():\n                new_keys = keys + [key]\n                if isinstance(val, dict):\n                    _recursive_upsert(val, new_keys)\n                else:\n                    self.upsert_from_list(new_keys, val, extra_data)\n\n        _recursive_upsert(other, keys=[])\n\n    def get_child(self, key, constructor=None):\n        if key in self.children:\n            child = self.children[key]\n        elif constructor is not None:\n            child = self.children[key] = constructor()\n        else:\n            raise KeyError(f\"Cannot get key {key}\")\n        return child\n\n    def add_child(self, key):\n        self.get_child(key, lambda: ConfigNode(key, parent=self))\n\n    def remove_child(self, key):\n        self.children.pop(key)\n\n    def upsert_from_list(self, keys, value, extra_data=None):\n        key, *next_keys = keys\n        if len(next_keys) == 0:  # reach the end of the upsert\n            leaf = self.get_child(\n                key,\n                lambda: ConfigLeaf(\n                    key, parent=self, value=value, extra_data=extra_data\n                ),\n            )\n            leaf.value = value\n            leaf.extra_data = extra_data\n            if not isinstance(leaf, ConfigLeaf):\n                raise RuntimeError(f\"Expected a ConfigLeaf, got {leaf}!\")\n        else:\n            next_node = self.get_child(key, lambda: ConfigNode(key, parent=self))\n            if not isinstance(next_node, ConfigNode):\n                raise RuntimeError(f\"Expected a ConfigNode, got {next_node}!\")\n            next_node.upsert_from_list(next_keys, value, extra_data)\n\n    def get_from_list(self, key_list):\n        next, *key_list_remainder = key_list\n        child = self.get_child(next)\n        if len(key_list_remainder) == 0:\n            return child\n        else:\n            return child.get_from_list(key_list_remainder)\n\n    def get(self, *keys):\n        return self.get_from_list(keys)\n\n    def get_leaf(self, *keys, callback=lambda leaf: leaf.value):\n        leaf = self.get_from_list(keys)\n        return callback(leaf)\n\n    def pop_leaf(self, keys):\n        *node_keys, leaf_key = keys\n        node = self.get_from_list(node_keys)\n        node.children.pop(leaf_key)\n\n    def get_deepest_leaf(self, *keys, callback=lambda leaf: leaf.value):\n        root_key, *keys, leaf_key = keys\n\n        root_node = self.get_child(root_key)\n        node_list = [root_node]\n        node = root_node\n\n        # Traverse the tree down following the keys\n        for k in keys:\n            try:\n                node = node.get_child(k)\n                node_list.append(node)\n            except KeyError:\n                break\n\n        # For each node, starting from the deepest, try to find the leaf\n        for node in reversed(node_list):\n            try:\n                leaf = node.get_child(leaf_key)\n                if not isinstance(leaf, ConfigLeaf):\n                    raise RuntimeError(f\"Expected a ConfigLeaf, got {leaf}!\")\n                return callback(leaf)\n            except KeyError:\n                continue\n        raise KeyError(f\"Cannot any node that contains the leaf {leaf_key}.\")\n\n    def serialize(self):\n        retval = {}\n        for key, child in self.children.items():\n            retval[key] = child.serialize()\n        return retval\n\n    @staticmethod\n    def from_dict(other, parent=None, **kwa):\n        me = ConfigNode(None, parent=parent)\n        for key, val in other.items():\n            if isinstance(val, dict):\n                me.add(key, ConfigNode.from_dict(val, parent=me, **kwa))\n            else:\n                me.add(key, ConfigLeaf(key, parent=me, value=val, **kwa))\n        return me\n\n    def _as_dict_with_count(self, callback):\n        data = {}\n        total_count = 0\n        for key, child in self.children.items():\n            if isinstance(child, ConfigLeaf):\n                total_count += 1\n                data[key] = callback(child)\n            elif isinstance(child, ConfigNode):\n                child_data, count = child._as_dict_with_count(callback)\n                total_count += count\n                if count > 0:\n                    data[key] = child_data\n\n        return data, total_count\n\n    def as_dict(self, callback=lambda child: child.value):\n        data, _ = self._as_dict_with_count(callback)\n        return data\n\n    def __repr__(self):\n        return f\"<Node {self.key}>\"\n\n    def __contains__(self, item):\n        return item in self.children\n\n    # Add support for IPython rich display\n    # see https://ipython.readthedocs.io/en/stable/config/integrating.html\n    def _repr_json_(self):\n        return self.as_dict()\n\n\nclass ConfigLeaf:\n    def __init__(self, key, parent: ConfigNode, value, extra_data=None):\n        self.key = key  # the name of the config leaf\n        self._value = value\n        self.parent = parent\n        self.extra_data = extra_data\n\n    def serialize(self):\n        return self.value\n\n    def get_tree(self):\n        node = self\n        parents = []\n        while node is not None:\n            parents.append(node)\n            node = node.parent\n\n        return reversed(parents)\n\n    @property\n    def value(self):\n        return self._value\n\n    @value.setter\n    def value(self, new_value):\n        if type(self.value) is type(new_value):\n            self._value = new_value\n        else:\n            tree = self.get_tree()\n            tree_str = \".\".join(node.key for node in tree if node.key)\n            msg = f\"Error when setting {tree_str}.\\n\"\n            msg += (\n                \"Tried to assign a value of type \"\n                f\"{type(new_value)}, expected type {type(self.value)}.\"\n            )\n            source = self.extra_data.get(\"source\", None)\n            if source:\n                msg += f\"\\nThis entry was last modified in {source}.\"\n            raise TypeError(msg)\n\n    def __repr__(self):\n        return f\"<Leaf {self.key}: {self.value}>\"\n"
  },
  {
    "path": "yt/utilities/configure.py",
    "content": "import os\nimport sys\nimport warnings\nfrom collections.abc import Callable\nfrom pathlib import Path\n\nfrom more_itertools import always_iterable\n\nfrom yt.utilities.configuration_tree import ConfigLeaf, ConfigNode\n\nconfiguration_callbacks: list[Callable[[\"YTConfig\"], None]] = []\n\n\ndef config_dir():\n    config_root = os.environ.get(\n        \"XDG_CONFIG_HOME\", os.path.join(os.path.expanduser(\"~\"), \".config\")\n    )\n    conf_dir = os.path.join(config_root, \"yt\")\n    return conf_dir\n\n\nclass YTConfig:\n    def __init__(self, defaults=None):\n        if defaults is None:\n            defaults = {}\n        self.config_root = ConfigNode(None)\n\n    def get(self, section, *keys, callback=None):\n        node_or_leaf = self.config_root.get(section, *keys)\n        if isinstance(node_or_leaf, ConfigLeaf):\n            if callback is not None:\n                return callback(node_or_leaf)\n            return node_or_leaf.value\n        return node_or_leaf\n\n    def get_most_specific(self, section, *keys, **kwargs):\n        use_fallback = \"fallback\" in kwargs\n        fallback = kwargs.pop(\"fallback\", None)\n        try:\n            return self.config_root.get_deepest_leaf(section, *keys)\n        except KeyError as err:\n            if use_fallback:\n                return fallback\n            else:\n                raise err\n\n    def update(self, new_values, metadata=None):\n        if metadata is None:\n            metadata = {}\n        self.config_root.update(new_values, metadata)\n\n    def has_section(self, section):\n        try:\n            self.config_root.get_child(section)\n            return True\n        except KeyError:\n            return False\n\n    def add_section(self, section):\n        self.config_root.add_child(section)\n\n    def remove_section(self, section):\n        if self.has_section(section):\n            self.config_root.remove_child(section)\n            return True\n        else:\n            return False\n\n    def set(self, *args, metadata=None):\n        section, *keys, value = args\n        if metadata is None:\n            metadata = {\"source\": \"runtime\"}\n        self.config_root.upsert_from_list(\n            [section] + list(keys), value, extra_data=metadata\n        )\n\n    def remove(self, *args):\n        self.config_root.pop_leaf(args)\n\n    def read(self, file_names):\n        file_names_read = []\n        for fname in always_iterable(file_names):\n            if not os.path.exists(fname):\n                continue\n            metadata = {\"source\": f\"file: {fname}\"}\n            if sys.version_info >= (3, 11):\n                import tomllib\n            else:\n                import tomli as tomllib\n            try:\n                with open(fname, \"rb\") as fh:\n                    data = tomllib.load(fh)\n            except tomllib.TOMLDecodeError as exc:\n                warnings.warn(\n                    f\"Could not load configuration file {fname} (invalid TOML: {exc})\",\n                    stacklevel=2,\n                )\n            else:\n                self.update(data, metadata=metadata)\n                file_names_read.append(fname)\n\n        return file_names_read\n\n    def write(self, file_handler):\n        import tomli_w\n\n        value = self.config_root.as_dict()\n        config_as_str = tomli_w.dumps(value)\n\n        try:\n            file_path = Path(file_handler)\n        except TypeError:\n            if not hasattr(file_handler, \"write\"):\n                raise TypeError(\n                    f\"Expected a path to a file, or a writable object, got {file_handler}\"\n                ) from None\n            file_handler.write(config_as_str)\n        else:\n            pdir = file_path.parent\n            if not pdir.exists():\n                warnings.warn(\n                    f\"{pdir!s} does not exist, creating it (recursively)\", stacklevel=2\n                )\n                os.makedirs(pdir)\n            file_path.write_text(config_as_str)\n\n    @staticmethod\n    def get_global_config_file():\n        return os.path.join(config_dir(), \"yt.toml\")\n\n    @staticmethod\n    def get_local_config_file():\n        path = Path.cwd()\n        while path.parent is not path:\n            candidate = path.joinpath(\"yt.toml\")\n            if candidate.is_file():\n                return os.path.abspath(candidate)\n            else:\n                path = path.parent\n\n        return os.path.join(os.path.abspath(os.curdir), \"yt.toml\")\n\n    def __setitem__(self, args, value):\n        section, *keys = always_iterable(args)\n        self.set(section, *keys, value, metadata=None)\n\n    def __getitem__(self, key):\n        section, *keys = always_iterable(key)\n        return self.get(section, *keys)\n\n    def __contains__(self, item):\n        return item in self.config_root\n\n    # Add support for IPython rich display\n    # see https://ipython.readthedocs.io/en/stable/config/integrating.html\n    def _repr_json_(self):\n        return self.config_root._repr_json_()\n\n\nCONFIG = YTConfig()\n\n\ndef _cast_bool_helper(value):\n    if value in (\"true\", \"True\", True):\n        return True\n    elif value in (\"false\", \"False\", False):\n        return False\n    else:\n        raise ValueError(\"Cannot safely cast to bool\")\n\n\ndef _expand_all(s):\n    return os.path.expandvars(os.path.expanduser(s))\n\n\ndef _cast_value_helper(value, types=(_cast_bool_helper, int, float, _expand_all)):\n    for t in types:\n        try:\n            retval = t(value)\n            return retval\n        except ValueError:\n            pass\n\n\ndef get_config(section, option):\n    *option_path, option_name = option.split(\".\")\n    return CONFIG.get(section, *option_path, option_name)\n\n\ndef set_config(section, option, value, config_file):\n    if not CONFIG.has_section(section):\n        CONFIG.add_section(section)\n\n    option_path = option.split(\".\")\n    CONFIG.set(section, *option_path, _cast_value_helper(value))\n    write_config(config_file)\n\n\ndef write_config(config_file):\n    CONFIG.write(config_file)\n\n\ndef rm_config(section, option, config_file):\n    option_path = option.split(\".\")\n    CONFIG.remove(section, *option_path)\n    write_config(config_file)\n"
  },
  {
    "path": "yt/utilities/cosmology.py",
    "content": "import functools\n\nimport numpy as np\n\nfrom yt.units import dimensions\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.units.unit_registry import UnitRegistry  # type: ignore\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.physical_constants import (\n    gravitational_constant_cgs as G,\n    speed_of_light_cgs,\n)\n\n\nclass Cosmology:\n    r\"\"\"\n    Create a cosmology calculator to compute cosmological distances and times.\n\n    For an explanation of the various cosmological measures, see, for example\n    Hogg (1999, https://arxiv.org/abs/astro-ph/9905116).\n\n    WARNING: Cosmological distance calculations return values that are either\n    in the comoving or proper frame, depending on the specific quantity.  For\n    simplicity, the proper and comoving frames are set equal to each other\n    within the cosmology calculator.  This means that for some distance value,\n    x, x.to(\"Mpc\") and x.to(\"Mpccm\") will be the same.  The user should take\n    care to understand which reference frame is correct for the given calculation.\n\n    Parameters\n    ----------\n    hubble_constant : float\n        The Hubble parameter at redshift zero in units of 100 km/s/Mpc.\n        Default: 0.71.\n    omega_matter : the fraction of the energy density of the Universe in\n        matter at redshift zero.\n        Default: 0.27.\n    omega_lambda : the fraction of the energy density of the Universe in\n        a cosmological constant.\n        Default: 0.73.\n    omega_radiation : the fraction of the energy density of the Universe in\n        relativistic matter at redshift zero.\n    omega_curvature : the fraction of the energy density of the Universe in\n        curvature.\n        Default: 0.0.\n    unit_system : :class:`yt.units.unit_systems.UnitSystem`, optional\n        The units system to use when making calculations. If not specified,\n        cgs units are assumed.\n    use_dark_factor: Bool, optional\n        The flag to either use the cosmological constant (False, default)\n        or to use the parameterization of w(a) as given in Linder 2002. This,\n        along with w_0 and w_a, only matters in the function expansion_factor.\n    w_0 : float, optional\n        The Linder 2002 parameterization of w(a) is: w(a) = w_0 + w_a(1 - a).\n        w_0 is w(a = 1). Only matters if use_dark_factor = True. Default is None.\n        Cosmological constant case corresponds to w_0 = -1.\n    w_a : float, optional\n        See w_0. w_a is the derivative of w(a) evaluated at a = 1. Cosmological\n        constant case corresponds to w_a = 0. Default is None.\n\n    Examples\n    --------\n\n    >>> from yt.utilities.cosmology import Cosmology\n    >>> co = Cosmology()\n    >>> print(co.t_from_z(0.0).in_units(\"Gyr\"))\n\n    \"\"\"\n\n    def __init__(\n        self,\n        hubble_constant=0.71,\n        omega_matter=0.27,\n        omega_lambda=0.73,\n        omega_radiation=0.0,\n        omega_curvature=0.0,\n        unit_registry=None,\n        unit_system=\"cgs\",\n        use_dark_factor=False,\n        w_0=-1.0,\n        w_a=0.0,\n    ):\n        self.omega_matter = float(omega_matter)\n        self.omega_radiation = float(omega_radiation)\n        self.omega_lambda = float(omega_lambda)\n        self.omega_curvature = float(omega_curvature)\n        hubble_constant = float(hubble_constant)\n        if unit_registry is None:\n            unit_registry = UnitRegistry(unit_system=unit_system)\n            unit_registry.add(\"h\", hubble_constant, dimensions.dimensionless, r\"h\")\n            for my_unit in [\"m\", \"pc\", \"AU\", \"au\"]:\n                new_unit = f\"{my_unit}cm\"\n                my_u = Unit(my_unit, registry=unit_registry)\n                # technically not true, but distances here are actually comoving\n                unit_registry.add(\n                    new_unit,\n                    my_u.base_value,\n                    dimensions.length,\n                    f\"\\\\rm{{{my_unit}}}/(1+z)\",\n                    prefixable=True,\n                )\n        self.unit_registry = unit_registry\n        self.hubble_constant = self.quan(hubble_constant, \"100*km/s/Mpc\")\n        self.unit_system = unit_system\n\n        # For non-standard dark energy. If false, use default cosmological constant\n        # This only affects the expansion_factor function.\n        self.use_dark_factor = use_dark_factor\n        self.w_0 = w_0\n        self.w_a = w_a\n\n    def hubble_distance(self):\n        r\"\"\"\n        The distance corresponding to c / h, where c is the speed of light\n        and h is the Hubble parameter in units of 1 / time.\n        \"\"\"\n        return self.quan(speed_of_light_cgs / self.hubble_constant).in_base(\n            self.unit_system\n        )\n\n    def comoving_radial_distance(self, z_i, z_f):\n        r\"\"\"\n        The comoving distance along the line of sight to on object at redshift,\n        z_f, viewed at a redshift, z_i.\n\n        Parameters\n        ----------\n        z_i : float\n            The redshift of the observer.\n        z_f : float\n            The redshift of the observed object.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.comoving_radial_distance(0.0, 1.0).in_units(\"Mpccm\"))\n\n        \"\"\"\n        return (\n            self.hubble_distance()\n            * trapezoid_int(self.inverse_expansion_factor, z_i, z_f)\n        ).in_base(self.unit_system)\n\n    def comoving_transverse_distance(self, z_i, z_f):\n        r\"\"\"\n        When multiplied by some angle, the distance between two objects\n        observed at redshift, z_f, with an angular separation given by that\n        angle, viewed by an observer at redshift, z_i (Hogg 1999).\n\n        Parameters\n        ----------\n        z_i : float\n            The redshift of the observer.\n        z_f : float\n            The redshift of the observed object.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.comoving_transverse_distance(0.0, 1.0).in_units(\"Mpccm\"))\n\n        \"\"\"\n        if self.omega_curvature > 0:\n            return (\n                self.hubble_distance()\n                / np.sqrt(self.omega_curvature)\n                * np.sinh(\n                    np.sqrt(self.omega_curvature)\n                    * self.comoving_radial_distance(z_i, z_f)\n                    / self.hubble_distance()\n                )\n            ).in_base(self.unit_system)\n        elif self.omega_curvature < 0:\n            return (\n                self.hubble_distance()\n                / np.sqrt(np.fabs(self.omega_curvature))\n                * np.sin(\n                    np.sqrt(np.fabs(self.omega_curvature))\n                    * self.comoving_radial_distance(z_i, z_f)\n                    / self.hubble_distance()\n                )\n            ).in_base(self.unit_system)\n        else:\n            return self.comoving_radial_distance(z_i, z_f)\n\n    def comoving_volume(self, z_i, z_f):\n        r\"\"\"\n        \"The comoving volume is the volume measure in which number densities\n        of non-evolving objects locked into Hubble flow are constant with\n        redshift.\" -- Hogg (1999)\n\n        Parameters\n        ----------\n        z_i : float\n            The lower redshift of the interval.\n        z_f : float\n            The higher redshift of the interval.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.comoving_volume(0.0, 1.0).in_units(\"Gpccm**3\"))\n\n        \"\"\"\n        if self.omega_curvature > 1e-10:\n            return (\n                2\n                * np.pi\n                * np.power(self.hubble_distance(), 3)\n                / self.omega_curvature\n                * (\n                    self.comoving_transverse_distance(z_i, z_f)\n                    / self.hubble_distance()\n                    * np.sqrt(\n                        1\n                        + self.omega_curvature\n                        * np.sqrt(\n                            self.comoving_transverse_distance(z_i, z_f)\n                            / self.hubble_distance()\n                        )\n                    )\n                    - np.sinh(\n                        np.fabs(self.omega_curvature)\n                        * self.comoving_transverse_distance(z_i, z_f)\n                        / self.hubble_distance()\n                    )\n                    / np.sqrt(self.omega_curvature)\n                )\n            ).in_base(self.unit_system)\n        elif self.omega_curvature < -1e-10:\n            return (\n                2\n                * np.pi\n                * np.power(self.hubble_distance(), 3)\n                / np.fabs(self.omega_curvature)\n                * (\n                    self.comoving_transverse_distance(z_i, z_f)\n                    / self.hubble_distance()\n                    * np.sqrt(\n                        1\n                        + self.omega_curvature\n                        * np.sqrt(\n                            self.comoving_transverse_distance(z_i, z_f)\n                            / self.hubble_distance()\n                        )\n                    )\n                    - np.arcsin(\n                        np.fabs(self.omega_curvature)\n                        * self.comoving_transverse_distance(z_i, z_f)\n                        / self.hubble_distance()\n                    )\n                    / np.sqrt(np.fabs(self.omega_curvature))\n                )\n            ).in_base(self.unit_system)\n        else:\n            return (\n                4 * np.pi * np.power(self.comoving_transverse_distance(z_i, z_f), 3) / 3\n            ).in_base(self.unit_system)\n\n    def angular_diameter_distance(self, z_i, z_f):\n        r\"\"\"\n        Following Hogg (1999), the angular diameter distance is 'the ratio of\n        an object's physical transverse size to its angular size in radians.'\n\n        Parameters\n        ----------\n        z_i : float\n            The redshift of the observer.\n        z_f : float\n            The redshift of the observed object.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.angular_diameter_distance(0.0, 1.0).in_units(\"Mpc\"))\n\n        \"\"\"\n\n        return (\n            self.comoving_transverse_distance(0, z_f) / (1 + z_f)\n            - self.comoving_transverse_distance(0, z_i) / (1 + z_i)\n        ).in_base(self.unit_system)\n\n    def angular_scale(self, z_i, z_f):\n        r\"\"\"\n        The proper transverse distance between two points at redshift z_f\n        observed at redshift z_i per unit of angular separation.\n\n        Parameters\n        ----------\n        z_i : float\n            The redshift of the observer.\n        z_f : float\n            The redshift of the observed object.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.angular_scale(0.0, 1.0).in_units(\"kpc / arcsec\"))\n\n        \"\"\"\n\n        scale = self.angular_diameter_distance(z_i, z_f) / self.quan(1, \"radian\")\n        return scale.in_base(self.unit_system)\n\n    def luminosity_distance(self, z_i, z_f):\n        r\"\"\"\n        The distance that would be inferred from the inverse-square law of\n        light and the measured flux and luminosity of the observed object.\n\n        Parameters\n        ----------\n        z_i : float\n            The redshift of the observer.\n        z_f : float\n            The redshift of the observed object.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.luminosity_distance(0.0, 1.0).in_units(\"Mpc\"))\n\n        \"\"\"\n\n        return (\n            self.comoving_transverse_distance(0, z_f) * (1 + z_f)\n            - self.comoving_transverse_distance(0, z_i) * (1 + z_i)\n        ).in_base(self.unit_system)\n\n    def lookback_time(self, z_i, z_f):\n        r\"\"\"\n        The difference in the age of the Universe between the redshift interval\n        z_i to z_f.\n\n        Parameters\n        ----------\n        z_i : float\n            The lower redshift of the interval.\n        z_f : float\n            The higher redshift of the interval.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.lookback_time(0.0, 1.0).in_units(\"Gyr\"))\n\n        \"\"\"\n        return (\n            trapezoid_int(self.age_integrand, z_i, z_f) / self.hubble_constant\n        ).in_base(self.unit_system)\n\n    def critical_density(self, z):\n        r\"\"\"\n        The density required for closure of the Universe at a given\n        redshift in the proper frame.\n\n        Parameters\n        ----------\n        z : float\n            Redshift.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.critical_density(0.0).in_units(\"g/cm**3\"))\n        >>> print(co.critical_density(0).in_units(\"Msun/Mpc**3\"))\n\n        \"\"\"\n        return (3.0 * self.hubble_parameter(z) ** 2 / 8.0 / np.pi / G).in_base(\n            self.unit_system\n        )\n\n    def hubble_parameter(self, z):\n        r\"\"\"\n        The value of the Hubble parameter at a given redshift.\n\n        Parameters\n        ----------\n        z: float\n            Redshift.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.hubble_parameter(1.0).in_units(\"km/s/Mpc\"))\n\n        \"\"\"\n        return self.hubble_constant.in_base(self.unit_system) * self.expansion_factor(z)\n\n    def age_integrand(self, z):\n        return 1.0 / (z + 1) / self.expansion_factor(z)\n\n    def expansion_factor(self, z):\n        r\"\"\"\n        The ratio between the Hubble parameter at a given redshift and\n        redshift zero.\n\n        This is also the primary function integrated to calculate the\n        cosmological distances.\n\n        \"\"\"\n\n        # Use non-standard dark energy\n        if self.use_dark_factor:\n            dark_factor = self.get_dark_factor(z)\n\n        # Use default cosmological constant\n        else:\n            dark_factor = 1.0\n\n        zp1 = 1 + z\n        return np.sqrt(\n            self.omega_matter * zp1**3\n            + self.omega_curvature * zp1**2\n            + self.omega_radiation * zp1**4\n            + self.omega_lambda * dark_factor\n        )\n\n    def inverse_expansion_factor(self, z):\n        return 1.0 / self.expansion_factor(z)\n\n    def path_length_function(self, z):\n        return ((1 + z) ** 2) * self.inverse_expansion_factor(z)\n\n    def path_length(self, z_i, z_f):\n        return trapezoid_int(self.path_length_function, z_i, z_f)\n\n    def t_from_a(self, a):\n        \"\"\"\n        Compute the age of the Universe for a given scale factor.\n\n        Parameters\n        ----------\n        a : float\n            Scale factor.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.t_from_a(1.0).in_units(\"Gyr\"))\n\n        \"\"\"\n\n        # Interpolate from a table of log(a) vs. log(t)\n        la = np.log10(a)\n        la_i = min(-6, np.asarray(la).min() - 3)\n        la_f = np.asarray(la).max()\n        bins_per_dex = 1000\n        n_bins = int((la_f - la_i) * bins_per_dex + 1)\n        la_bins = np.linspace(la_i, la_f, n_bins)\n        z_bins = 1.0 / np.power(10, la_bins) - 1\n\n        # Integrate in redshift.\n        lt = trapezoid_cumulative_integral(self.age_integrand, z_bins)\n\n        # Add a minus sign because we've switched the integration limits.\n        table = InterpTable(la_bins[1:], np.log10(-lt))\n        t = np.power(10, table(la))\n\n        return (t / self.hubble_constant).in_base(self.unit_system)\n\n    def t_from_z(self, z):\n        \"\"\"\n        Compute the age of the Universe for a given redshift.\n\n        Parameters\n        ----------\n        z : float\n            Redshift.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.t_from_z(0.0).in_units(\"Gyr\"))\n\n        \"\"\"\n\n        return self.t_from_a(1.0 / (1.0 + z))\n\n    def a_from_t(self, t):\n        \"\"\"\n        Compute the scale factor for a given age of the Universe.\n\n        Parameters\n        ----------\n        t : YTQuantity or float\n            Time since the Big Bang.  If a float is given, units are\n            assumed to be seconds.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.a_from_t(4.0e17))\n\n        \"\"\"\n\n        if not isinstance(t, YTArray):\n            t = self.arr(t, \"s\")\n        lt = np.log10((t * self.hubble_constant).to(\"\"))\n\n        # Interpolate from a table of log(a) vs. log(t)\n        # Make initial guess for bounds and widen if necessary.\n        la_i = -6\n        la_f = 6\n        bins_per_dex = 1000\n        iter = 0\n\n        while True:\n            good = True\n            n_bins = int((la_f - la_i) * bins_per_dex + 1)\n            la_bins = np.linspace(la_i, la_f, n_bins)\n            z_bins = 1.0 / np.power(10, la_bins) - 1\n\n            # Integrate in redshift.\n            lt_bins = trapezoid_cumulative_integral(self.age_integrand, z_bins)\n\n            # Add a minus sign because we've switched the integration limits.\n            table = InterpTable(np.log10(-lt_bins), la_bins[1:])\n            la = table(lt)\n            # We want to have the la_bins lower bound be decently\n            # below the minimum calculated la values.\n\n            laa = np.asarray(la)\n            if laa.min() < la_i + 2:\n                la_i -= 3\n                good = False\n            if laa.max() > la_f:\n                la_f = laa.max() + 1\n                good = False\n            if good:\n                break\n            iter += 1\n            if iter > 10:\n                raise RuntimeError(\"a_from_t calculation did not converge!\")\n\n        a = np.power(10, table(lt))\n        return a\n\n    def z_from_t(self, t):\n        \"\"\"\n        Compute the redshift for a given age of the Universe.\n\n        Parameters\n        ----------\n        t : YTQuantity or float\n            Time since the Big Bang.  If a float is given, units are\n            assumed to be seconds.\n\n        Examples\n        --------\n\n        >>> from yt.utilities.cosmology import Cosmology\n        >>> co = Cosmology()\n        >>> print(co.z_from_t(4.0e17))\n\n        \"\"\"\n\n        a = self.a_from_t(t)\n        return 1.0 / a - 1.0\n\n    def get_dark_factor(self, z):\n        \"\"\"\n        This function computes the additional term that enters the expansion factor\n        when using non-standard dark energy. See Dolag et al 2004 eq. 7 for ref (but\n        note that there's a typo in his eq. There should be no negative sign).\n\n        At the moment, this only works using the parameterization given in Linder 2002\n        eq. 7: w(a) = w0 + wa(1 - a) = w0 + wa * z / (1+z). This gives rise to an\n        analytic expression.\n        It is also only functional for Gadget simulations, at the moment.\n\n        Parameters\n        ----------\n        z:  float\n            Redshift\n        \"\"\"\n\n        # Get value of scale factor a corresponding to redshift z\n        scale_factor = 1.0 / (1.0 + z)\n\n        # Evaluate exponential using Linder02 parameterization\n        dark_factor = np.power(\n            scale_factor, -3.0 * (1.0 + self.w_0 + self.w_a)\n        ) * np.exp(-3.0 * self.w_a * (1.0 - scale_factor))\n\n        return dark_factor\n\n    _arr = None\n\n    @property\n    def arr(self):\n        if self._arr is not None:\n            return self._arr\n        self._arr = functools.partial(YTArray, registry=self.unit_registry)\n        return self._arr\n\n    _quan = None\n\n    @property\n    def quan(self):\n        if self._quan is not None:\n            return self._quan\n        self._quan = functools.partial(YTQuantity, registry=self.unit_registry)\n        return self._quan\n\n\ndef trapzint(f, a, b, bins=10000):\n    from yt._maintenance.deprecation import issue_deprecation_warning\n\n    issue_deprecation_warning(\n        \"yt.utilities.cosmology.trapzint is an alias \"\n        \"to yt.utilities.cosmology.trapezoid_int, \"\n        \"and will be removed in a future version. \"\n        \"Please use yt.utilities.cosmology.trapezoid_int directly.\",\n        since=\"4.3.0\",\n        stacklevel=3,\n    )\n    return trapezoid_int(f, a, b, bins)\n\n\ndef trapezoid_int(f, a, b, bins=10000):\n    from yt._maintenance.numpy2_compat import trapezoid\n\n    zbins = np.logspace(np.log10(a + 1), np.log10(b + 1), bins) - 1\n    return trapezoid(f(zbins[:-1]), x=zbins[:-1], dx=np.diff(zbins))\n\n\ndef trapezoid_cumulative_integral(f, x):\n    \"\"\"\n    Perform cumulative integration using the trapezoid rule.\n    \"\"\"\n\n    fy = f(x)\n    return (0.5 * (fy[:-1] + fy[1:]) * np.diff(x)).cumsum()\n\n\nclass InterpTable:\n    \"\"\"\n    Generate a function to linearly interpolate from provided arrays.\n    \"\"\"\n\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n    def __call__(self, val):\n        i = np.clip(np.digitize(val, self.x) - 1, 0, self.x.size - 2)\n        slope = (self.y[i + 1] - self.y[i]) / (self.x[i + 1] - self.x[i])\n        return slope * (val - self.x[i]) + self.y[i]\n"
  },
  {
    "path": "yt/utilities/cython_fortran_utils.pxd",
    "content": "cimport numpy as np\nfrom libc.stdio cimport FILE\n\nctypedef np.int32_t INT32_t\nctypedef np.int64_t INT64_t\nctypedef np.float64_t DOUBLE_t\n\ncdef class FortranFile:\n    cdef FILE* cfile\n    cdef bint _closed\n\n    cpdef INT64_t skip(self, INT64_t n=*) except -1\n    cdef INT64_t get_size(self, str dtype)\n    cpdef INT32_t read_int(self) except? -1\n    cpdef np.ndarray read_vector(self, str dtype)\n    cdef int read_vector_inplace(self, str dtype, void *data)\n    cpdef INT64_t tell(self) except -1\n    cpdef INT64_t seek(self, INT64_t pos, INT64_t whence=*) except -1\n    cpdef void close(self)\n"
  },
  {
    "path": "yt/utilities/cython_fortran_utils.pyx",
    "content": "# distutils: libraries = STD_LIBS\ncimport numpy as np\nimport numpy as np\n\nfrom libc.stdio cimport *\n\ncdef INT32_SIZE = sizeof(np.int32_t)\ncdef DOUBLE_SIZE = sizeof(np.float64_t)\n\ncdef class FortranFile:\n    \"\"\"This class provides facilities to interact with files written\n    in fortran-record format.  Since this is a non-standard file\n    format, whose contents depend on the compiler and the endianness\n    of the machine, caution is advised. This code will assume that the\n    record header is written as a 32bit (4byte) signed integer. The\n    code also assumes that the records use the system's local\n    endianness.\n\n    Notes\n    -----\n    Since the assumed record header is an signed integer on 32bit, it\n    will overflow at 2**31=2147483648 elements.\n\n    This module has been inspired by scipy's FortranFile, especially\n    the docstrings.\n    \"\"\"\n    def __cinit__(self, str fname):\n        self.cfile = fopen(fname.encode('utf-8'), 'rb')\n        self._closed = False\n\n        if self.cfile is NULL:\n            self._closed = True\n            raise FileNotFoundError(fname.encode('utf-8'))\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, type, value, traceback):\n        self.close()\n\n    cpdef INT64_t skip(self, INT64_t n=1) except -1:\n        \"\"\"Skip records.\n\n        Parameters\n        ----------\n        - n : integer\n            The number of records to skip\n\n        Returns\n        -------\n        value : int\n            Returns 0 on success.\n        \"\"\"\n        cdef INT32_t s1, s2, i\n\n        if self._closed:\n            raise ValueError(\"Read of closed file.\")\n\n        for i in range(n):\n            fread(&s1, INT32_SIZE, 1, self.cfile)\n            fseek(self.cfile, s1, SEEK_CUR)\n            fread(&s2, INT32_SIZE, 1, self.cfile)\n\n            if s1 != s2:\n                raise IOError('Sizes do not agree in the header and footer for '\n                              'this record - check header dtype. Got %s and %s' % (s1, s2))\n\n        return 0\n\n    cdef INT64_t get_size(self, str dtype):\n        \"\"\"Return the size of an element given its datatype.\n\n        Parameters\n        ----------\n        dtype : str\n           The dtype, see note for details about the values of dtype.\n\n        Returns\n        -------\n        size : int\n           The size in byte of the dtype\n\n        Note:\n        -----\n        See\n        https://docs.python.org/3.5/library/struct.html#format-characters\n        for details about the formatting characters.\n        \"\"\"\n        if dtype == 'i':\n            return 4\n        elif dtype == 'd':\n            return 8\n        elif dtype == 'f':\n            return 4\n        elif dtype == 'l':\n            return 8\n        else:\n            # Fallback to (slow) numpy-based to compute the size\n            return np.dtype(dtype).itemsize\n\n    cpdef np.ndarray read_vector(self, str dtype):\n        \"\"\"Reads a record from the file and return it as numpy array.\n\n        Parameters\n        ----------\n        d : data type\n            This is the datatype (from the struct module) that we should read.\n\n        Returns\n        -------\n        tr : numpy.ndarray\n            This is the vector of values read from the file.\n\n        Examples\n        --------\n        >>> f = FortranFile(\"fort.3\")\n        >>> rv = f.read_vector(\"d\")  # Read a float64 array\n        >>> rv = f.read_vector(\"i\")  # Read an int32 array\n        \"\"\"\n        cdef INT32_t s1, s2, size\n        cdef np.ndarray data\n\n        if self._closed:\n            raise ValueError(\"I/O operation on closed file.\")\n\n        size = self.get_size(dtype)\n\n        fread(&s1, INT32_SIZE, 1, self.cfile)\n\n        # Check record is compatible with data type\n        if s1 % size != 0:\n            raise ValueError('Size obtained (%s) does not match with the expected '\n                             'size (%s) of multi-item record' % (s1, size))\n\n        data = np.empty(s1 // size, dtype=dtype)\n        fread(<void *>data.data, size, s1 // size, self.cfile)\n        fread(&s2, INT32_SIZE, 1, self.cfile)\n\n        if s1 != s2:\n            raise IOError('Sizes do not agree in the header and footer for '\n                          'this record - check header dtype')\n\n        return data\n\n    cdef int read_vector_inplace(self, str dtype, void *data):\n        \"\"\"Reads a record from the file.\n\n        Parameters\n        ----------\n        d : data type\n            This is the datatype (from the struct module) that we should read.\n        data : void*\n            The pointer where to store the data.\n            It should be preallocated and have the correct size.\n        \"\"\"\n        cdef INT32_t s1, s2, size\n\n        if self._closed:\n            raise ValueError(\"I/O operation on closed file.\")\n\n        size = self.get_size(dtype)\n\n        fread(&s1, INT32_SIZE, 1, self.cfile)\n\n        # Check record is compatible with data type\n        if s1 % size != 0:\n            raise ValueError('Size obtained (%s) does not match with the expected '\n                             'size (%s) of multi-item record' % (s1, size))\n\n        fread(data, size, s1 // size, self.cfile)\n        fread(&s2, INT32_SIZE, 1, self.cfile)\n\n        if s1 != s2:\n            raise IOError('Sizes do not agree in the header and footer for '\n                          'this record - check header dtype')\n\n    cpdef INT32_t read_int(self) except? -1:\n        \"\"\"Reads a single int32 from the file and return it.\n\n        Returns\n        -------\n        data : int32\n            The value.\n\n        Examples\n        --------\n        >>> f = FortranFile(\"fort.3\")\n        >>> rv = f.read_vector(\"d\")  # Read a float64 array\n        >>> rv = f.read_vector(\"i\")  # Read an int32 array\n        \"\"\"\n\n        cdef INT32_t s1, s2\n        cdef INT32_t data\n\n        if self._closed:\n            raise ValueError(\"I/O operation on closed file.\")\n\n        fread(&s1, INT32_SIZE, 1, self.cfile)\n\n        if s1 != INT32_SIZE != 0:\n            raise ValueError('Size obtained (%s) does not match with the expected '\n                             'size (%s) of record' % (s1, INT32_SIZE))\n\n        fread(&data, INT32_SIZE, s1 // INT32_SIZE, self.cfile)\n        fread(&s2, INT32_SIZE, 1, self.cfile)\n\n        if s1 != s2:\n            raise IOError('Sizes do not agree in the header and footer for '\n                          'this record - check header dtype')\n\n        return data\n\n    def read_attrs(self, object attrs):\n        \"\"\"This function reads from that file according to a\n        definition of attributes, returning a dictionary.\n\n        Fortran unformatted files provide total bytesize at the\n        beginning and end of a record. By correlating the components\n        of that record with attribute names, we construct a dictionary\n        that gets returned. Note that this function is used for\n        reading sequentially-written records. If you have many written\n        that were written simultaneously.\n\n        Parameters\n        ----------\n        attrs : iterable of iterables\n            This object should be an iterable of one of the formats:\n            [ (attr_name, count, struct type), ... ].\n            [ ((name1,name2,name3), count, vector type]\n            [ ((name1,name2,name3), count, 'type type type']\n            [ (attr_name, count, struct type, optional)]\n\n            `optional` : boolean.\n                If True, the attribute can be stored as an empty Fortran record.\n\n        Returns\n        -------\n        values : dict\n            This will return a dict of iterables of the components of the values in\n            the file.\n\n        Examples\n        --------\n\n        >>> header = [ (\"ncpu\", 1, \"i\"), (\"nfiles\", 2, \"i\") ]\n        >>> f = FortranFile(\"fort.3\")\n        >>> rv = f.read_attrs(header)\n        \"\"\"\n\n        cdef str dtype\n        cdef int n\n        cdef dict data\n        cdef key\n        cdef np.ndarray tmp\n        cdef bint optional\n\n        if self._closed:\n            raise ValueError(\"I/O operation on closed file.\")\n\n        data = {}\n\n        for a in attrs:\n            if len(a) == 3:\n                key, n, dtype = a\n                optional = False\n            else:\n                key, n, dtype, optional = a\n            if n == 1:\n                tmp = self.read_vector(dtype)\n                if len(tmp) == 0 and optional:\n                    continue\n                elif (len(tmp) == 1) or (n == -1):\n                    data[key] = tmp[0]\n                else:\n                    raise ValueError(\"Expected a record of length %s, got %s (%s)\" % (n, len(tmp), key))\n            else:\n                tmp = self.read_vector(dtype)\n                if (len(tmp) == 0 and optional):\n                    continue\n                elif (len(tmp) != n) and (n != -1):\n                    raise ValueError(\"Expected a record of length %s, got %s (%s)\" % (n, len(tmp), key))\n\n                if isinstance(key, tuple):\n                    # There are multiple keys\n                    for ikey in range(n):\n                        data[key[ikey]] = tmp[ikey]\n                else:\n                    data[key] = tmp\n\n        return data\n\n    cpdef INT64_t tell(self) except -1:\n        \"\"\"Return current stream position.\"\"\"\n        cdef INT64_t pos\n\n        if self._closed:\n            raise ValueError(\"I/O operation on closed file.\")\n\n        pos = ftell(self.cfile)\n        return pos\n\n    cpdef INT64_t seek(self, INT64_t pos, INT64_t whence=SEEK_SET) except -1:\n        \"\"\"Change stream position.\n\n        Parameters\n        ----------\n        pos : int\n            Change the stream position to the given byte offset. The offset is\n            interpreted relative to the position indicated by whence.\n        whence : int\n            Determine how pos is interpreted. Can by any of\n            * 0 -- start of stream (the default); offset should be zero or positive\n            * 1 -- current stream position; offset may be negative\n            * 2 -- end of stream; offset is usually negative\n\n        Returns\n        -------\n        pos : int\n            The new absolute position.\n        \"\"\"\n        if self._closed:\n            raise ValueError(\"I/O operation on closed file.\")\n        if whence < 0 or whence > 2:\n            raise ValueError(\"whence argument can be 0, 1, or 2. Got %s\" % whence)\n\n        fseek(self.cfile, pos, whence)\n        return self.tell()\n\n    cpdef void close(self):\n        \"\"\"Close the file descriptor.\n\n        This method has no effect if the file is already closed.\n        \"\"\"\n        if self._closed:\n            return\n        fclose(self.cfile)\n        self._closed = True\n\n    def __dealloc__(self):\n        if not self._closed:\n            self.close()\n"
  },
  {
    "path": "yt/utilities/decompose.py",
    "content": "import numpy as np\n\n\ndef SIEVE_PRIMES(x):\n    return x and x[:1] + SIEVE_PRIMES([n for n in x if n % x[0]])\n\n\ndef decompose_to_primes(max_prime):\n    \"\"\"Decompose number into the primes\"\"\"\n    for prime in SIEVE_PRIMES(list(range(2, max_prime))):\n        if prime * prime > max_prime:\n            break\n        while max_prime % prime == 0:\n            yield prime\n            max_prime //= prime\n    if max_prime > 1:\n        yield max_prime\n\n\ndef decompose_array(shape, psize, bbox, *, cell_widths=None):\n    \"\"\"Calculate list of product(psize) subarrays of arr, along with their\n    left and right edges\n    \"\"\"\n    return split_array(bbox[:, 0], bbox[:, 1], shape, psize, cell_widths=cell_widths)\n\n\ndef evaluate_domain_decomposition(n_d, pieces, ldom):\n    \"\"\"Evaluate longest to shortest edge ratio\n    BEWARE: lot's of magic here\"\"\"\n    eff_dim = (n_d > 1).sum()\n    exp = float(eff_dim - 1) / float(eff_dim)\n    ideal_bsize = eff_dim * pieces ** (1.0 / eff_dim) * np.prod(n_d) ** exp\n    mask = np.where(n_d > 1)\n    nd_arr = np.array(n_d, dtype=np.float64)[mask]\n    bsize = int(np.sum(ldom[mask] / nd_arr * np.prod(nd_arr)))\n    load_balance = float(np.prod(n_d)) / (\n        float(pieces) * np.prod((n_d - 1) // ldom + 1)\n    )\n\n    # 0.25 is magic number\n    quality = load_balance / (1 + 0.25 * (bsize / ideal_bsize - 1.0))\n    # \\todo add a factor that estimates lower cost when x-direction is\n    # not chopped too much\n    # \\deprecated estimate these magic numbers\n    quality *= 1.0 - (0.001 * ldom[0] + 0.0001 * ldom[1]) / pieces\n    if np.any(ldom > n_d):\n        quality = 0\n\n    return quality\n\n\ndef factorize_number(pieces):\n    \"\"\"Return array consisting of prime, its power and number of different\n    decompositions in three dimensions for this prime\n    \"\"\"\n    factors = list(decompose_to_primes(pieces))\n    temp = np.bincount(factors)\n    return np.array(\n        [\n            (prime, temp[prime], (temp[prime] + 1) * (temp[prime] + 2) // 2)\n            for prime in np.unique(factors)\n        ],\n        dtype=\"int64\",\n    )\n\n\ndef get_psize(n_d, pieces):\n    \"\"\"Calculate the best division of array into px*py*pz subarrays.\n    The goal is to minimize the ratio of longest to shortest edge\n    to minimize the amount of inter-process communication.\n    \"\"\"\n    fac = factorize_number(pieces)\n    nfactors = len(fac[:, 2])\n    best = 0.0\n    p_size = np.ones(3, dtype=np.int64)\n    if pieces == 1:\n        return p_size\n\n    while np.all(fac[:, 2] > 0):\n        ldom = np.ones(3, dtype=np.int64)\n        for nfac in range(nfactors):\n            i = int(np.sqrt(0.25 + 2 * (fac[nfac, 2] - 1)) - 0.5)\n            k = fac[nfac, 2] - (1 + i * (i + 1) // 2)\n            i = fac[nfac, 1] - i\n            j = fac[nfac, 1] - (i + k)\n            ldom *= fac[nfac, 0] ** np.array([i, j, k])\n\n        quality = evaluate_domain_decomposition(n_d, pieces, ldom)\n        if quality > best:\n            best = quality\n            p_size = ldom\n        # search for next unique combination\n        for j in range(nfactors):\n            if fac[j, 2] > 1:\n                fac[j, 2] -= 1\n                break\n            else:\n                if j < nfactors - 1:\n                    fac[j, 2] = int((fac[j, 1] + 1) * (fac[j, 1] + 2) / 2)\n                else:\n                    fac[:, 2] = 0  # no more combinations to try\n\n    return p_size\n\n\ndef split_array(gle, gre, shape, psize, *, cell_widths=None):\n    \"\"\"Split array into px*py*pz subarrays.\"\"\"\n    n_d = np.array(shape, dtype=np.int64)\n    dds = (gre - gle) / shape\n    left_edges = []\n    right_edges = []\n    shapes = []\n    slices = []\n\n    if cell_widths is None:\n        cell_widths_by_grid = None\n    else:\n        cell_widths_by_grid = []\n\n    for i in range(psize[0]):\n        for j in range(psize[1]):\n            for k in range(psize[2]):\n                piece = np.array((i, j, k), dtype=np.int64)\n                lei = n_d * piece // psize\n                rei = n_d * (piece + np.ones(3, dtype=np.int64)) // psize\n\n                if cell_widths is not None:\n                    cws = []\n                    offset_le = []\n                    offset_re = []\n                    for idim in range(3):\n                        cws.append(cell_widths[idim][lei[idim] : rei[idim]])\n                        offset_le.append(np.sum(cell_widths[idim][0 : lei[idim]]))\n                        offset_re.append(offset_le[idim] + np.sum(cws[idim]))\n                    cell_widths_by_grid.append(cws)\n                    offset_re = np.array(offset_re)\n                    offset_le = np.array(offset_le)\n                else:\n                    offset_le = lei * dds\n                    offset_re = rei * dds\n                lle = gle + offset_le\n                lre = gle + offset_re\n                left_edges.append(lle)\n                right_edges.append(lre)\n                shapes.append(rei - lei)\n                slices.append(np.s_[lei[0] : rei[0], lei[1] : rei[1], lei[2] : rei[2]])\n\n    return left_edges, right_edges, shapes, slices, cell_widths_by_grid\n"
  },
  {
    "path": "yt/utilities/definitions.py",
    "content": "from .physical_ratios import (\n    au_per_mpc,\n    cm_per_mpc,\n    km_per_mpc,\n    kpc_per_mpc,\n    miles_per_mpc,\n    mpc_per_mpc,\n    pc_per_mpc,\n    rsun_per_mpc,\n    sec_per_day,\n    sec_per_Gyr,\n    sec_per_Myr,\n    sec_per_year,\n)\n\n# The number of levels we expect to have at most\nMAXLEVEL = 48\n\n# How many of each thing are in an Mpc\nmpc_conversion = {\n    \"Mpc\": mpc_per_mpc,\n    \"mpc\": mpc_per_mpc,\n    \"kpc\": kpc_per_mpc,\n    \"pc\": pc_per_mpc,\n    \"au\": au_per_mpc,\n    \"rsun\": rsun_per_mpc,\n    \"miles\": miles_per_mpc,\n    \"km\": km_per_mpc,\n    \"cm\": cm_per_mpc,\n}\n\n# Nicely formatted versions of common length units\nformatted_length_unit_names = {\n    \"au\": \"AU\",\n    \"rsun\": r\"R_\\odot\",\n    \"code_length\": r\"code\\ length\",\n}\n\n# How many seconds are in each thing\nsec_conversion = {\n    \"Gyr\": sec_per_Gyr,\n    \"Myr\": sec_per_Myr,\n    \"years\": sec_per_year,\n    \"days\": sec_per_day,\n}\n"
  },
  {
    "path": "yt/utilities/exceptions.py",
    "content": "# We don't need to import 'exceptions'\nimport os.path\n\nfrom unyt.exceptions import UnitOperationError\n\nfrom yt._typing import FieldKey\n\n\nclass YTException(Exception):\n    pass\n\n\n# Data access exceptions:\n\n\nclass YTUnidentifiedDataType(YTException):\n    def __init__(self, filename, *args, **kwargs):\n        self.filename = filename\n        self.args = args\n        self.kwargs = kwargs\n\n    def __str__(self):\n        from yt.utilities.hierarchy_inspection import (\n            get_classes_with_missing_requirements,\n        )\n\n        msg = f\"Could not determine input format from {self.filename!r}\"\n        if self.args:\n            msg += \", \" + (\", \".join(f\"{a!r}\" for a in self.args))\n        if self.kwargs:\n            msg += \", \" + (\", \".join(f\"{k}={v!r}\" for k, v in self.kwargs.items()))\n\n        msg += \"\\n\"\n\n        if len(unusable_classes := get_classes_with_missing_requirements()) > 0:\n            msg += (\n                \"The following types could not be thorougly checked against your data because \"\n                \"their requirements are missing. \"\n                \"You may want to inspect this list and check your installation:\"\n            )\n            for cls, missing in unusable_classes.items():\n                requirements_str = \", \".join(missing)\n                msg += f\"\\n- {cls.__name__} (requires: {requirements_str})\"\n            msg += \"\\n\\n\"\n        msg += \"Please make sure you are running a sufficiently recent version of yt.\"\n        return msg\n\n\nclass YTAmbiguousDataType(YTUnidentifiedDataType):\n    def __init__(self, filename, candidates):\n        self.filename = filename\n        self.candidates = candidates\n\n    def __str__(self):\n        msg = f\"Multiple data type candidates for {self.filename}\\n\"\n        msg += \"The following independent classes were detected as valid :\\n\"\n        for c in self.candidates:\n            msg += f\"{c}\\n\"\n        msg += (\n            \"This degeneracy can be lifted using the `hint` keyword argument in yt.load\"\n        )\n        return msg\n\n\nclass YTAxesNotOrthogonalError(YTException):\n    def __init__(self, axes):\n        self.axes = axes\n\n    def __str__(self):\n        return f\"The supplied axes are not orthogonal.  {self.axes}\"\n\n\nclass YTNoDataInObjectError(YTException):\n    def __init__(self, obj):\n        self.obj_type = getattr(obj, \"_type_name\", \"\")\n\n    def __str__(self):\n        s = \"The object requested has no data included in it.\"\n        if self.obj_type == \"slice\":\n            s += \"  It may lie on a grid face.  Try offsetting slightly.\"\n        return s\n\n\nclass YTFieldNotFound(YTException):\n    def __init__(self, field, ds):\n        self.field = field\n        self.ds = ds\n\n    def _get_suggestions(self) -> list[FieldKey]:\n        from yt.funcs import levenshtein_distance\n\n        field = self.field\n        ds = self.ds\n\n        suggestions = {}\n        if not isinstance(field, tuple):\n            ftype, fname = None, field\n        elif field[1] is None:\n            ftype, fname = None, field[0]\n        else:\n            ftype, fname = field\n\n        # Limit the suggestions to a distance of 3 (at most 3 edits)\n        # This is very arbitrary, but is picked so that...\n        # - small typos lead to meaningful suggestions (e.g. `densty` -> `density`)\n        # - we don't suggest unrelated things (e.g. `pressure` -> `density` has a distance\n        #   of 6, we definitely do not want it)\n        # A threshold of 3 seems like a good middle point.\n        max_distance = 3\n\n        # Suggest (ftype, fname), with alternative ftype\n        for ft, fn in ds.derived_field_list:\n            if fn.lower() == fname.lower() and (\n                ftype is None or ft.lower() != ftype.lower()\n            ):\n                suggestions[ft, fn] = 0\n\n        if ftype is not None:\n            # Suggest close matches using levenshtein distance\n            fields_str = {_: str(_).lower() for _ in ds.derived_field_list}\n            field_str = str(field).lower()\n\n            for (ft, fn), fs in fields_str.items():\n                distance = levenshtein_distance(field_str, fs, max_dist=max_distance)\n                if distance < max_distance:\n                    if (ft, fn) in suggestions:\n                        continue\n                    suggestions[ft, fn] = distance\n\n        # Return suggestions sorted by increasing distance (first are most likely)\n        return [\n            (ft, fn)\n            for (ft, fn), distance in sorted(suggestions.items(), key=lambda v: v[1])\n        ]\n\n    def __str__(self):\n        msg = f\"Could not find field {self.field!r} in {self.ds}.\"\n        try:\n            suggestions = self._get_suggestions()\n        except AttributeError:\n            # This may happen if passing a field that is e.g. an Ellipsis\n            # e.g. when using ds.r[...]\n            suggestions = []\n        if suggestions:\n            msg += \"\\nDid you mean:\\n\\t\"\n            msg += \"\\n\\t\".join(str(_) for _ in suggestions)\n        return msg\n\n\nclass YTParticleTypeNotFound(YTException):\n    def __init__(self, fname, ds):\n        self.fname = fname\n        self.ds = ds\n\n    def __str__(self):\n        return f\"Could not find particle_type {self.fname!r} in {self.ds}.\"\n\n\nclass YTSceneFieldNotFound(YTException):\n    pass\n\n\nclass YTCouldNotGenerateField(YTFieldNotFound):\n    def __str__(self):\n        return f\"Could field '{self.fname}' in {self.ds} could not be generated.\"\n\n\nclass YTFieldTypeNotFound(YTException):\n    def __init__(self, ftype, ds=None):\n        self.ftype = ftype\n        self.ds = ds\n\n    def __str__(self):\n        if self.ds is not None and self.ftype in self.ds.particle_types:\n            return (\n                f\"Could not find field type {self.ftype!r}. \"\n                \"This field type is a known particle type for this dataset. \"\n                \"Try adding this field with sampling_type='particle'.\"\n            )\n        else:\n            return f\"Could not find field type {self.ftype!r}.\"\n\n\nclass YTSimulationNotIdentified(YTException):\n    def __init__(self, sim_type):\n        self.sim_type = sim_type\n\n    def __str__(self):\n        from yt.utilities.object_registries import simulation_time_series_registry\n\n        return (\n            f\"Simulation time-series type {self.sim_type!r} not defined. \"\n            f\"Supported types are {list(simulation_time_series_registry)}\"\n        )\n\n\nclass YTCannotParseFieldDisplayName(YTException):\n    def __init__(self, field_name, display_name, mathtext_error):\n        self.field_name = field_name\n        self.display_name = display_name\n        self.mathtext_error = mathtext_error\n\n    def __str__(self):\n        return (\n            f\"The display name {self.display_name!r} of the derived field {self.field_name!r} \"\n            f\"contains the following LaTeX parser errors:\\n{self.mathtext_error}\"\n        )\n\n\nclass YTCannotParseUnitDisplayName(YTException):\n    def __init__(self, field_name, unit_name, mathtext_error):\n        self.field_name = field_name\n        self.unit_name = unit_name\n        self.mathtext_error = mathtext_error\n\n    def __str__(self):\n        return (\n            f\"The unit display name {self.unit_name!r} of the derived field {self.field_name!r} \"\n            f\"contains the following LaTeX parser errors:\\n{self.mathtext_error}\"\n        )\n\n\nclass InvalidSimulationTimeSeries(YTException):\n    def __init__(self, message):\n        self.message = message\n\n    def __str__(self):\n        return self.message\n\n\nclass MissingParameter(YTException):\n    def __init__(self, ds, parameter):\n        self.ds = ds\n        self.parameter = parameter\n\n    def __str__(self):\n        return f\"dataset {self.ds} is missing {self.parameter} parameter.\"\n\n\nclass NoStoppingCondition(YTException):\n    def __init__(self, ds):\n        self.ds = ds\n\n    def __str__(self):\n        return f\"Simulation {self.ds} has no stopping condition. StopTime or StopCycle should be set.\"\n\n\nclass YTNotInsideNotebook(YTException):\n    def __str__(self):\n        return \"This function only works from within an IPython Notebook.\"\n\n\nclass YTCoordinateNotImplemented(YTException):\n    def __str__(self):\n        return \"This coordinate is not implemented for this geometry type.\"\n\n\n# define for back compat reasons for code written before yt 4.0\nYTUnitOperationError = UnitOperationError\n\n\nclass YTUnitNotRecognized(YTException):\n    def __init__(self, unit):\n        self.unit = unit\n\n    def __str__(self):\n        return f\"This dataset doesn't recognize {self.unit!r}\"\n\n\nclass YTFieldUnitError(YTException):\n    def __init__(self, field_info, returned_units):\n        self.msg = (\n            f\"The field function associated with the field {field_info.name!r} returned \"\n            f\"data with units {returned_units!r} but was defined with units {field_info.units!r}.\"\n        )\n\n    def __str__(self):\n        return self.msg\n\n\nclass YTFieldUnitParseError(YTException):\n    def __init__(self, field_info):\n        self.msg = (\n            f\"The field {field_info.name!r} has unparsable units {field_info.units!r}.\"\n        )\n\n    def __str__(self):\n        return self.msg\n\n\nclass YTSpatialFieldUnitError(YTException):\n    def __init__(self, field):\n        self.msg = (\n            f\"Field {field!r} is a spatial field but has unknown units but \"\n            \"spatial fields must have explicitly defined units. Add the \"\n            \"field with explicit 'units' to clear this error.\"\n        )\n\n    def __str__(self):\n        return self.msg\n\n\nclass YTHubRegisterError(YTException):\n    def __str__(self):\n        return (\n            \"You must create an API key before uploading. See \"\n            \"https://data.yt-project.org/getting_started.html\"\n        )\n\n\nclass YTNoFilenamesMatchPattern(YTException):\n    def __init__(self, pattern):\n        self.pattern = pattern\n\n    def __str__(self):\n        return f\"No filenames were found to match the pattern: {self.pattern!r}\"\n\n\nclass YTNoOldAnswer(YTException):\n    def __init__(self, path):\n        self.path = path\n\n    def __str__(self):\n        return f\"There is no old answer available.\\n{self.path!r}\"\n\n\nclass YTNoAnswerNameSpecified(YTException):\n    def __init__(self, message=None):\n        if message is None or message == \"\":\n            message = (\n                \"Answer name not provided for the answer testing test.\"\n                \"\\n  Please specify --answer-name=<answer_name> in\"\n                \" command line mode or in AnswerTestingTest.answer_name\"\n                \" variable.\"\n            )\n        self.message = message\n\n    def __str__(self):\n        return str(self.message)\n\n\nclass YTCloudError(YTException):\n    def __init__(self, path):\n        self.path = path\n\n    def __str__(self):\n        return (\n            f\"Failed to retrieve cloud data. Connection may be broken.\\n {self.path!r}\"\n        )\n\n\nclass YTEllipsoidOrdering(YTException):\n    def __init__(self, ds, A, B, C):\n        self.ds = ds\n        self._A = A\n        self._B = B\n        self._C = C\n\n    def __str__(self):\n        return \"Must have A>=B>=C\"\n\n\nclass EnzoTestOutputFileNonExistent(YTException):\n    def __init__(self, filename):\n        self.filename = filename\n        self.testname = os.path.basename(os.path.dirname(filename))\n\n    def __str__(self):\n        return (\n            f\"Enzo test output file (OutputLog) not generated for: {self.testname!r}.\\n\"\n            \"Test did not complete.\"\n        )\n\n\nclass YTNoAPIKey(YTException):\n    def __init__(self, service, config_name):\n        self.service = service\n        self.config_name = config_name\n\n    def __str__(self):\n        from yt.config import config_dir\n\n        try:\n            conf = os.path.join(config_dir(), \"yt\", \"yt.toml\")\n        except Exception:\n            # this is really not a good time to raise another exception\n            conf = \"yt's configuration file\"\n        return f\"You need to set an API key for {self.service!r} in {conf} as {self.config_name!r}\"\n\n\nclass YTTooManyVertices(YTException):\n    def __init__(self, nv, fn):\n        self.nv = nv\n        self.fn = fn\n\n    def __str__(self):\n        s = f\"There are too many vertices ({self.nv}) to upload to Sketchfab. \"\n        s += f\"Your model has been saved as {self.fn} .  You should upload manually.\"\n        return s\n\n\nclass YTInvalidWidthError(YTException):\n    def __init__(self, width):\n        self.error = f\"width ({str(width)}) is invalid\"\n\n    def __str__(self):\n        return str(self.error)\n\n\nclass YTFieldNotParseable(YTException):\n    def __init__(self, field):\n        self.field = field\n\n    def __str__(self):\n        return f\"Cannot identify field {self.field!r}\"\n\n\nclass YTDataSelectorNotImplemented(YTException):\n    def __init__(self, class_name):\n        self.class_name = class_name\n\n    def __str__(self):\n        return f\"Data selector {self.class_name!r} not implemented.\"\n\n\nclass YTParticleDepositionNotImplemented(YTException):\n    def __init__(self, class_name):\n        self.class_name = class_name\n\n    def __str__(self):\n        return f\"Particle deposition method {self.class_name!r} not implemented.\"\n\n\nclass YTDomainOverflow(YTException):\n    def __init__(self, mi, ma, dle, dre):\n        self.mi = mi\n        self.ma = ma\n        self.dle = dle\n        self.dre = dre\n\n    def __str__(self):\n        return (\n            f\"Particle bounds {self.mi} and {self.ma} \"\n            f\"exceed domain bounds {self.dle} and {self.dre}\"\n        )\n\n\nclass YTIntDomainOverflow(YTException):\n    def __init__(self, dims, dd):\n        self.dims = dims\n        self.dd = dd\n\n    def __str__(self):\n        return f\"Integer domain overflow: {self.dims} in {self.dd}\"\n\n\nclass YTIllDefinedFilter(YTException):\n    def __init__(self, filter, s1, s2):\n        self.filter = filter\n        self.s1 = s1\n        self.s2 = s2\n\n    def __str__(self):\n        return (\n            f\"Filter {self.filter!r} ill-defined. \"\n            f\"Applied to shape {self.s1} but is shape {self.s2}.\"\n        )\n\n\nclass YTIllDefinedParticleFilter(YTException):\n    def __init__(self, filter, missing):\n        self.filter = filter\n        self.missing = missing\n\n    def __str__(self):\n        msg = (\n            '\\nThe fields\\n\\t{},\\nrequired by the \"{}\" particle filter, '\n            \"are not defined for this dataset.\"\n        )\n        f = self.filter\n        return msg.format(\"\\n\".join(str(m) for m in self.missing), f.name)\n\n\nclass YTIllDefinedBounds(YTException):\n    def __init__(self, lb, ub):\n        self.lb = lb\n        self.ub = ub\n\n    def __str__(self):\n        v = f\"The bounds {self.lb:0.3e} and {self.ub:0.3e} are ill-defined. \"\n        v += \"Typically this happens when a log binning is specified \"\n        v += \"and zero or negative values are given for the bounds.\"\n        return v\n\n\nclass YTObjectNotImplemented(YTException):\n    def __init__(self, ds, obj_name):\n        self.ds = ds\n        self.obj_name = obj_name\n\n    def __str__(self):\n        return f\"The object type {self.obj_name!r} is not implemented for the dataset {self.ds!s}\"\n\n\nclass YTParticleOutputFormatNotImplemented(YTException):\n    def __str__(self):\n        return \"The particle output format is not supported.\"\n\n\nclass YTFileNotParseable(YTException):\n    def __init__(self, fname, line):\n        self.fname = fname\n        self.line = line\n\n    def __str__(self):\n        return f\"Error while parsing file {self.fname!r} at line {self.line}\"\n\n\nclass YTRockstarMultiMassNotSupported(YTException):\n    def __init__(self, mi, ma, ptype):\n        self.mi = mi\n        self.ma = ma\n        self.ptype = ptype\n\n    def __str__(self):\n        v = f\"Particle type '{self.ptype}' has minimum mass {self.mi:0.3e} and maximum \"\n        v += f\"mass {self.ma:0.3e}.  Multi-mass particles are not currently supported.\"\n        return v\n\n\nclass YTTooParallel(YTException):\n    def __str__(self):\n        return \"You've used too many processors for this dataset.\"\n\n\nclass YTElementTypeNotRecognized(YTException):\n    def __init__(self, dim, num_nodes):\n        self.dim = dim\n        self.num_nodes = num_nodes\n\n    def __str__(self):\n        return f\"Element type not recognized - dim = {self.dim}, num_nodes = {self.num_nodes}\"\n\n\nclass YTDuplicateFieldInProfile(YTException):\n    def __init__(self, field, new_spec, old_spec):\n        self.field = field\n        self.new_spec = new_spec\n        self.old_spec = old_spec\n\n    def __str__(self):\n        r = f\"\"\"Field {self.field} already exists with field spec:\n               {self.old_spec}\n               But being asked to add it with:\n               {self.new_spec}\"\"\"\n        return r\n\n\nclass YTInvalidPositionArray(YTException):\n    def __init__(self, shape, dimensions):\n        self.shape = shape\n        self.dimensions = dimensions\n\n    def __str__(self):\n        r = f\"\"\"Position arrays must be length and shape (N,3).\n               But this one has {self.dimensions} and {self.shape}.\"\"\"\n        return r\n\n\nclass YTIllDefinedCutRegion(YTException):\n    def __init__(self, conditions):\n        self.conditions = conditions\n\n    def __str__(self):\n        r = (\n            \"Can't mix particle/discrete and fluid/mesh conditions or quantities. \"\n            \"Conditions specified:\\n\"\n        )\n        r += \"\\n\".join(c for c in self.conditions)\n        return r\n\n\nclass YTMixedCutRegion(YTException):\n    def __init__(self, conditions, field):\n        self.conditions = conditions\n        self.field = field\n\n    def __str__(self):\n        r = f\"\"\"Can't mix particle/discrete and fluid/mesh conditions or\n               quantities.  Field: {self.field} and Conditions specified:\n            \"\"\"\n        r += \"\\n\".join(c for c in self.conditions)\n        return r\n\n\nclass YTGDFAlreadyExists(YTException):\n    def __init__(self, filename):\n        self.filename = filename\n\n    def __str__(self):\n        return f\"A file already exists at {self.filename} and overwrite=False.\"\n\n\nclass YTNonIndexedDataContainer(YTException):\n    def __init__(self, cont):\n        self.cont = cont\n\n    def __str__(self):\n        class_name = self.cont.__class__.__name__\n        return (\n            f\"The data container type ({class_name}) is an unindexed type. \"\n            \"Operations such as ires, icoords, fcoords and fwidth will not work on it.\\n\"\n            \"Did you just attempt to perform an off-axis operation ? \"\n            \"Be sure to consult the latest documentation to see whether the operation \"\n            \"you tried is actually supported for your data type.\"\n        )\n\n\nclass YTGDFUnknownGeometry(YTException):\n    def __init__(self, geometry):\n        self.geometry = geometry\n\n    def __str__(self):\n        return (\n            f\"Unknown geometry {self.geometry} . \"\n            \"Please refer to GDF standard for more information\"\n        )\n\n\nclass YTInvalidUnitEquivalence(YTException):\n    def __init__(self, equiv, unit1, unit2):\n        self.equiv = equiv\n        self.unit1 = unit1\n        self.unit2 = unit2\n\n    def __str__(self):\n        return f\"The unit equivalence {self.equiv!r} does not exist for the units {self.unit1!r} and {self.unit2!r}.\"\n\n\nclass YTPlotCallbackError(YTException):\n    def __init__(self, callback):\n        self.callback = \"annotate_\" + callback\n\n    def __str__(self):\n        return f\"{self.callback} callback failed\"\n\n\nclass YTUnsupportedPlotCallback(YTPlotCallbackError):\n    def __init__(self, callback: str, plot_type: str) -> None:\n        super().__init__(callback)\n        self.plot_type = plot_type\n\n    def __str__(self):\n        return f\"The `{self.plot_type}` class currently doesn't support the `{self.callback}` method.\"\n\n\nclass YTPixelizeError(YTException):\n    def __init__(self, message):\n        self.message = message\n\n    def __str__(self):\n        return self.message\n\n\nclass YTDimensionalityError(YTException):\n    def __init__(self, wrong, right):\n        self.wrong = wrong\n        self.right = right\n\n    def __str__(self):\n        return f\"Dimensionality specified was {self.wrong} but we need {self.right}\"\n\n\nclass YTInvalidShaderType(YTException):\n    def __init__(self, source):\n        self.source = source\n\n    def __str__(self):\n        return f\"Can't identify shader_type for file {self.source!r}\"\n\n\nclass YTInvalidFieldType(YTException):\n    def __init__(self, fields):\n        self.fields = fields\n\n    def __str__(self):\n        return (\n            \"\\nSlicePlot, ProjectionPlot, and OffAxisProjectionPlot can \"\n            \"only plot fields that\\n\"\n            \"are defined on a mesh or for SPH particles, but received the \"\n            \"following N-body\\n\"\n            \"particle fields:\\n\\n\"\n            f\"    {self.fields!r}\\n\\n\"\n            \"Did you mean to use ParticlePlot or plot a deposited particle \"\n            \"field instead?\"\n        )\n\n\nclass YTUnknownUniformKind(YTException):\n    def __init__(self, kind):\n        self.kind = kind\n\n    def __str__(self):\n        return f\"Can't determine kind specification for {self.kind!r}\"\n\n\nclass YTUnknownUniformSize(YTException):\n    def __init__(self, size_spec):\n        self.size_spec = size_spec\n\n    def __str__(self):\n        return f\"Can't determine size specification for {self.size_spec!r}\"\n\n\nclass YTDataTypeUnsupported(YTException):\n    def __init__(self, this, supported):\n        self.supported = supported\n        self.this = this\n\n    def __str__(self):\n        v = f\"This operation is not supported for data of geometry {self.this!r}; \"\n        v += f\"It supports data of geometries {self.supported!r}\"\n        return v\n\n\nclass YTBoundsDefinitionError(YTException):\n    def __init__(self, message, bounds):\n        self.bounds = bounds\n        self.message = message\n\n    def __str__(self):\n        v = f\"This operation has encountered a bounds error: {self.message} \"\n        v += f\"\\nSpecified bounds are {self.bounds!r}.\"\n        return v\n\n\ndef screen_one_element_list(lis):\n    if len(lis) == 1:\n        return lis[0]\n    return lis\n\n\nclass YTIllDefinedProfile(YTException):\n    def __init__(self, bin_fields, fields, weight_field, is_pfield):\n        nbin = len(bin_fields)\n        nfields = len(fields)\n        self.bin_fields = screen_one_element_list(bin_fields)\n        self.bin_fields_ptype = screen_one_element_list(is_pfield[:nbin])\n        self.fields = screen_one_element_list(fields)\n        self.fields_ptype = screen_one_element_list(is_pfield[nbin : nbin + nfields])\n        self.weight_field = weight_field\n        if self.weight_field is not None:\n            self.weight_field_ptype = is_pfield[-1]\n\n    def __str__(self):\n        msg = (\n            \"\\nCannot create a profile object that mixes particle and mesh \"\n            \"fields.\\n\\n\"\n            \"Received the following bin_fields:\\n\\n\"\n            \"   %s, particle_type = %s\\n\\n\"\n            \"Profile fields:\\n\\n\"\n            \"   %s, particle_type = %s\\n\"\n        )\n        msg = msg % (\n            self.bin_fields,\n            self.bin_fields_ptype,\n            self.fields,\n            self.fields_ptype,\n        )\n\n        if self.weight_field is not None:\n            weight_msg = \"\\nAnd weight field:\\n\\n   %s, particle_type = %s\\n\"\n            weight_msg = weight_msg % (self.weight_field, self.weight_field_ptype)\n        else:\n            weight_msg = \"\"\n\n        return msg + weight_msg\n\n\nclass YTProfileDataShape(YTException):\n    def __init__(self, field1, shape1, field2, shape2):\n        self.field1 = field1\n        self.shape1 = shape1\n        self.field2 = field2\n        self.shape2 = shape2\n\n    def __str__(self):\n        return (\n            \"Profile fields must have same shape: {self.field1!r} has \"\n            f\"shape {self.shape1} and {self.field2!r} has shape {self.shape2}.\"\n        )\n\n\nclass YTBooleanObjectError(YTException):\n    def __init__(self, bad_object):\n        self.bad_object = bad_object\n\n    def __str__(self):\n        v = f\"Supplied:\\n{self.bad_object}\\nto a boolean operation\"\n        v += \" but it is not a YTSelectionContainer3D object.\"\n        return v\n\n\nclass YTBooleanObjectsWrongDataset(YTException):\n    def __init__(self):\n        pass\n\n    def __str__(self):\n        return \"Boolean data objects must share a common dataset object.\"\n\n\nclass YTIllDefinedAMR(YTException):\n    def __init__(self, level, axis):\n        self.level = level\n        self.axis = axis\n\n    def __str__(self):\n        return (\n            f\"Grids on the level {self.level} are not properly aligned with cell edges \"\n            f\"on the parent level ({self.axis!r} axis)\"\n        )\n\n\nclass YTIllDefinedParticleData(YTException):\n    pass\n\n\nclass YTIllDefinedAMRData(YTException):\n    pass\n\n\nclass YTInconsistentGridFieldShape(YTException):\n    def __init__(self, shapes):\n        self.shapes = shapes\n\n    def __str__(self):\n        msg = \"Not all grid-based fields have the same shape!\\n\"\n        for name, shape in self.shapes:\n            msg += f\"    Field {name!r} has shape {shape}.\\n\"\n        return msg\n\n\nclass YTInconsistentParticleFieldShape(YTException):\n    def __init__(self, ptype, shapes):\n        self.ptype = ptype\n        self.shapes = shapes\n\n    def __str__(self):\n        msg = \"Not all fields with field type {self.ptype!r} have the same shape!\\n\"\n        for name, shape in self.shapes:\n            field = (self.ptype, name)\n            msg += f\"    Field {field} has shape {shape}.\\n\"\n        return msg\n\n\nclass YTInconsistentGridFieldShapeGridDims(YTException):\n    def __init__(self, shapes, grid_dims):\n        self.shapes = shapes\n        self.grid_dims = grid_dims\n\n    def __str__(self):\n        msg = \"Not all grid-based fields match the grid dimensions! \"\n        msg += f\"Grid dims are {self.grid_dims}, \"\n        msg += \"and the following fields have shapes that do not match them:\\n\"\n        for name, shape in self.shapes:\n            if shape != self.grid_dims:\n                msg += f\"    Field {name} has shape {shape}.\\n\"\n        return msg\n\n\nclass YTCommandRequiresModule(YTException):\n    def __init__(self, module: str):\n        self.module = module\n\n    def __str__(self):\n        msg = f\"This command requires {self.module!r} to be installed.\\n\\n\"\n        msg += f\"Please install {self.module!r} with the package manager \"\n        msg += \"appropriate for your python environment, e.g.:\\n\"\n        msg += f\"  conda install {self.module}\\n\"\n        msg += \"or:\\n\"\n        msg += f\" python -m pip install {self.module}\\n\"\n        return msg\n\n\nclass YTModuleRemoved(YTException):\n    def __init__(self, name, new_home=None, info=None):\n        message = f\"The {name} module has been removed from yt.\"\n        if new_home is not None:\n            message += f\"\\nIt has been moved to {new_home}.\"\n        if info is not None:\n            message += f\"\\nFor more information, see {info}.\"\n        super().__init__(message)\n\n\nclass YTArrayTooLargeToDisplay(YTException):\n    def __init__(self, size, max_size):\n        self.size = size\n        self.max_size = max_size\n\n    def __str__(self):\n        msg = f\"The requested array is of size {self.size}.\\n\"\n        msg += \"We do not support displaying arrays larger\\n\"\n        msg += f\"than size {self.max_size}.\"\n        return msg\n\n\nclass YTConfigurationError(YTException):\n    pass\n\n\nclass GenerationInProgress(Exception):\n    def __init__(self, fields):\n        self.fields = fields\n\n\nclass MountError(Exception):\n    def __init__(self, message):\n        self.message = message\n"
  },
  {
    "path": "yt/utilities/file_handler.py",
    "content": "from contextlib import contextmanager\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt.utilities.on_demand_imports import NotAModule, _h5py as h5py\n\n\ndef valid_hdf5_signature(fn: str, /) -> bool:\n    signature = b\"\\x89HDF\\r\\n\\x1a\\n\"\n    try:\n        with open(fn, \"rb\") as f:\n            header = f.read(8)\n            return header == signature\n    except Exception:\n        return False\n\n\ndef warn_h5py(fn):\n    issue_deprecation_warning(\n        \"warn_h5py is not used within yt any more and is deprecated.\", since=\"4.3\"\n    )\n    needs_h5py = valid_hdf5_signature(fn)\n    if needs_h5py and isinstance(h5py.File, NotAModule):\n        raise RuntimeError(\n            \"This appears to be an HDF5 file, but h5py is not installed.\"\n        )\n\n\nclass HDF5FileHandler:\n    handle = None\n\n    def __init__(self, filename):\n        self.handle = h5py.File(filename, mode=\"r\")\n\n    def __getitem__(self, key):\n        return self.handle[key]\n\n    def __contains__(self, item):\n        return item in self.handle\n\n    def __len__(self):\n        return len(self.handle)\n\n    @property\n    def attrs(self):\n        return self.handle.attrs\n\n    def keys(self):\n        return list(self.handle.keys())\n\n    def items(self):\n        return list(self.handle.items())\n\n    def close(self):\n        if self.handle is not None:\n            self.handle.close()\n\n\nclass FITSFileHandler(HDF5FileHandler):\n    def __init__(self, filename):\n        from yt.utilities.on_demand_imports import _astropy\n\n        if isinstance(filename, _astropy.pyfits.hdu.image._ImageBaseHDU):\n            self.handle = _astropy.pyfits.HDUList(filename)\n        elif isinstance(filename, _astropy.pyfits.HDUList):\n            self.handle = filename\n        else:\n            self.handle = _astropy.pyfits.open(\n                filename, memmap=True, do_not_scale_image_data=True, ignore_blank=True\n            )\n        self._fits_files = []\n\n    def __del__(self):\n        for f in self._fits_files:\n            f.close()\n        del self._fits_files\n        del self.handle\n        self.handle = None\n\n    def close(self):\n        self.handle.close()\n\n\ndef valid_netcdf_signature(fn: str, /) -> bool:\n    try:\n        with open(fn, \"rb\") as f:\n            header = f.read(4)\n    except Exception:\n        return False\n    else:\n        return header in (b\"CDF\\x01\", b\"CDF\\x02\") or (\n            fn.endswith((\".nc\", \".nc4\")) and valid_hdf5_signature(fn)\n        )\n\n\ndef valid_netcdf_classic_signature(filename):\n    issue_deprecation_warning(\n        \"valid_netcdf_classic_signature is not used within yt any more and is deprecated.\",\n        since=\"4.3\",\n    )\n    signature_v1 = b\"CDF\\x01\"\n    signature_v2 = b\"CDF\\x02\"\n    try:\n        with open(filename, \"rb\") as f:\n            header = f.read(4)\n            return header == signature_v1 or header == signature_v2\n    except Exception:\n        return False\n\n\ndef warn_netcdf(fn):\n    # There are a few variants of the netCDF format.\n    issue_deprecation_warning(\n        \"warn_netcdf is not used within yt any more and is deprecated.\", since=\"4.3\"\n    )\n\n    classic = valid_netcdf_classic_signature(fn)\n    # NetCDF-4 Classic files are HDF5 files constrained to the Classic\n    # data model used by netCDF-3.\n    netcdf4_classic = valid_hdf5_signature(fn) and fn.endswith((\".nc\", \".nc4\"))\n    needs_netcdf = classic or netcdf4_classic\n    from yt.utilities.on_demand_imports import _netCDF4 as netCDF4\n\n    if needs_netcdf and isinstance(netCDF4.Dataset, NotAModule):\n        raise RuntimeError(\n            \"This appears to be a netCDF file, but the \"\n            \"python bindings for netCDF4 are not installed.\"\n        )\n\n\nclass NetCDF4FileHandler:\n    def __init__(self, filename):\n        self.filename = filename\n\n    @contextmanager\n    def open_ds(self, **kwargs):\n        from yt.utilities.on_demand_imports import _netCDF4 as netCDF4\n\n        ds = netCDF4.Dataset(self.filename, mode=\"r\", **kwargs)\n        yield ds\n        ds.close()\n"
  },
  {
    "path": "yt/utilities/flagging_methods.py",
    "content": "import numpy as np  # For modern purposes\n\nfrom yt.utilities.lib.misc_utilities import grow_flagging_field\n\nflagging_method_registry = {}\n\n\nclass RegisteredFlaggingMethod(type):\n    def __init__(cls, name, b, d):\n        type.__init__(cls, name, b, d)\n\n\nclass FlaggingMethod:\n    _skip_add = False\n\n    def __init_subclass__(cls, *args, **kwargs):\n        super().__init_subclass__(*args, **kwargs)\n        if hasattr(cls, \"_type_name\") and not cls._skip_add:\n            flagging_method_registry[cls._type_name] = cls\n\n\nclass OverDensity(FlaggingMethod):\n    _type_name = \"overdensity\"\n\n    def __init__(self, over_density):\n        self.over_density = over_density\n\n    def __call__(self, grid):\n        rho = grid[\"gas\", \"density\"] / (grid.ds.refine_by**grid.Level)\n        return rho > self.over_density\n\n\nclass FlaggingGrid:\n    def __init__(self, grid, methods):\n        self.grid = grid\n        flagged = np.zeros(grid.ActiveDimensions, dtype=\"bool\")\n        for method in methods:\n            flagged |= method(self.grid)\n        self.flagged = grow_flagging_field(flagged)\n        self.subgrids = []\n        self.left_index = grid.get_global_startindex()\n        self.dimensions = grid.ActiveDimensions.copy()\n\n    def find_subgrids(self):\n        if not np.any(self.flagged):\n            return []\n        psg = ProtoSubgrid(self.flagged, self.left_index, self.dimensions)\n        sgl = [psg]\n        index = 0\n        while index < len(sgl):\n            psg = sgl[index]\n            psg.shrink()\n            if psg.dimensions.prod() == 0:\n                sgl[index] = None\n                continue\n            while not psg.acceptable:\n                new_psgs = []\n                for dim in np.argsort(psg.dimensions)[::-1]:\n                    new_psgs = psg.find_by_zero_signature(dim)\n                    if len(new_psgs) > 1:\n                        break\n                if len(new_psgs) <= 1:\n                    new_psgs = psg.find_by_second_derivative()\n                psg = new_psgs[0]\n                sgl[index] = psg\n                sgl.extend(new_psgs[1:])\n                psg.shrink()\n            index += 1\n        return sgl\n\n\n# Much or most of this is directly translated from Enzo\nclass ProtoSubgrid:\n    def __init__(self, flagged_base, left_index, dimensions, offset=(0, 0, 0)):\n        self.left_index = left_index.copy()\n        self.dimensions = dimensions.copy()\n        self.flagged = flagged_base[\n            offset[0] : offset[0] + dimensions[0],\n            offset[1] : offset[1] + dimensions[1],\n            offset[2] : offset[2] + dimensions[2],\n        ]\n        self.compute_signatures()\n\n    def compute_signatures(self):\n        self.sigs = []\n        for dim in range(3):\n            d1 = (dim + 1) % 3\n            d2 = int(dim == 0)\n            self.sigs.append(self.flagged.sum(axis=d1).sum(axis=d2))\n\n    @property\n    def acceptable(self):\n        return float(self.flagged.sum()) / self.flagged.size > 0.2\n\n    def shrink(self):\n        new_ind = []\n        for dim in range(3):\n            sig = self.sigs[dim]\n            new_start = 0\n            while sig[new_start] == 0:\n                new_start += 1\n            new_end = sig.size\n            while sig[new_end - 1] == 0:\n                new_end -= 1\n            self.dimensions[dim] = new_end - new_start\n            self.left_index[dim] += new_start\n            new_ind.append((new_start, new_end))\n        self.flagged = self.flagged[\n            new_ind[0][0] : new_ind[0][1],\n            new_ind[1][0] : new_ind[1][1],\n            new_ind[2][0] : new_ind[2][1],\n        ]\n        self.compute_signatures()\n\n    def find_by_zero_signature(self, dim):\n        sig = self.sigs[dim]\n        grid_ends = np.zeros((sig.size, 2), dtype=\"int64\")\n        ng = 0\n        i = 0\n        while i < sig.size:\n            if sig[i] != 0:\n                grid_ends[ng, 0] = i\n                while i < sig.size and sig[i] != 0:\n                    i += 1\n                grid_ends[ng, 1] = i - 1\n                ng += 1\n            i += 1\n        new_grids = []\n        for si, ei in grid_ends[:ng, :]:\n            li = self.left_index.copy()\n            dims = self.dimensions.copy()\n            li[dim] += si\n            dims[dim] = ei - si\n            offset = [0, 0, 0]\n            offset[dim] = si\n            new_grids.append(ProtoSubgrid(self.flagged, li, dims, offset))\n        return new_grids\n\n    def find_by_second_derivative(self):\n        max_strength = 0\n        max_axis = -1\n        for dim in range(3):\n            sig = self.sigs[dim]\n            sd = sig[:-2] - 2.0 * sig[1:-1] + sig[2:]\n            center = int((self.flagged.shape[dim] - 1) / 2)\n            strength = zero_strength = zero_cross = 0\n            for i in range(1, sig.size - 2):\n                # Note that sd is offset by one\n                if sd[i - 1] * sd[i] < 0:\n                    strength = np.abs(sd[i - 1] - sd[i])\n                    # TODO this differs from what I could find in ENZO\n                    # there's |center - i| < |center - zero_cross| instead\n                    # additionally zero_cross is undefined in first pass\n                    if strength > zero_strength or (\n                        strength == zero_strength\n                        and np.abs(center - i) < np.abs(zero_cross - i)\n                    ):\n                        zero_strength = strength\n                        zero_cross = i\n            if zero_strength > max_strength:\n                max_axis = dim\n        dims = self.dimensions.copy()\n        li = self.left_index.copy()\n        dims[max_axis] = zero_cross\n        psg1 = ProtoSubgrid(self.flagged, li, dims)\n        li[max_axis] += zero_cross\n        dims[max_axis] = self.dimensions[max_axis] - zero_cross\n        offset = np.zeros(3)\n        offset[max_axis] = zero_cross\n        psg2 = ProtoSubgrid(self.flagged, li, dims, offset)\n        return [psg1, psg2]\n\n    def __str__(self):\n        return f\"LI: ({self.left_index}) DIMS: ({self.dimensions})\"\n"
  },
  {
    "path": "yt/utilities/fortran_utils.py",
    "content": "import io\nimport os\nimport struct\n\nimport numpy as np\n\n\ndef read_attrs(f, attrs, endian=\"=\"):\n    r\"\"\"This function accepts a file pointer and reads from that file pointer\n    according to a definition of attributes, returning a dictionary.\n\n    Fortran unformatted files provide total bytesize at the beginning and end\n    of a record.  By correlating the components of that record with attribute\n    names, we construct a dictionary that gets returned.  Note that this\n    function is used for reading sequentially-written records.  If you have\n    many written that were written simultaneously, see read_record.\n\n    Parameters\n    ----------\n    f : File object\n        An open file object.  Should have been opened in mode rb.\n    attrs : iterable of iterables\n        This object should be an iterable of one of the formats:\n        [ (attr_name, count, struct type), ... ].\n        [ ((name1,name2,name3),count, vector type]\n        [ ((name1,name2,name3),count, 'type type type']\n    endian : str\n        '=' is native, '>' is big, '<' is little endian\n\n    Returns\n    -------\n    values : dict\n        This will return a dict of iterables of the components of the values in\n        the file.\n\n    Examples\n    --------\n\n    >>> header = [(\"ncpu\", 1, \"i\"), (\"nfiles\", 2, \"i\")]\n    >>> f = open(\"fort.3\", \"rb\")\n    >>> rv = read_attrs(f, header)\n    \"\"\"\n    vv = {}\n    net_format = endian\n    for _a, n, t in attrs:\n        for end in \"@=<>\":\n            t = t.replace(end, \"\")\n        net_format += \"\".join([\"I\"] + ([t] * n) + [\"I\"])\n    size = struct.calcsize(net_format)\n    vals = list(struct.unpack(net_format, f.read(size)))\n    vv = {}\n    for a, n, t in attrs:\n        for end in \"@=<>\":\n            t = t.replace(end, \"\")\n        if isinstance(a, tuple):\n            n = len(a)\n        s1 = vals.pop(0)\n        v = [vals.pop(0) for i in range(n)]\n        s2 = vals.pop(0)\n        if s1 != s2:\n            size = struct.calcsize(endian + \"I\" + \"\".join(n * [t]) + \"I\")\n            raise OSError(\n                \"An error occurred while reading a Fortran record. \"\n                \"Got a different size at the beginning and at the \"\n                \"end of the record: %s %s\",\n                s1,\n                s2,\n            )\n        if n == 1:\n            v = v[0]\n        if isinstance(a, tuple):\n            if len(a) != len(v):\n                raise OSError(\n                    \"An error occurred while reading a Fortran \"\n                    \"record. Record length is not equal to expected \"\n                    f\"length: {len(a)} {len(v)}\"\n                )\n            for k, val in zip(a, v, strict=True):\n                vv[k] = val\n        else:\n            vv[a] = v\n    return vv\n\n\ndef read_cattrs(f, attrs, endian=\"=\"):\n    r\"\"\"This function accepts a file pointer to a C-binary file and reads from\n    that file pointer according to a definition of attributes, returning a\n    dictionary.\n\n    This function performs very similarly to read_attrs, except it does not add\n    on any record padding.  It is thus useful for using the same header types\n    as in read_attrs, but for C files rather than Fortran.\n\n    Parameters\n    ----------\n    f : File object\n        An open file object.  Should have been opened in mode rb.\n    attrs : iterable of iterables\n        This object should be an iterable of one of the formats:\n        [ (attr_name, count, struct type), ... ].\n        [ ((name1,name2,name3),count, vector type]\n        [ ((name1,name2,name3),count, 'type type type']\n    endian : str\n        '=' is native, '>' is big, '<' is little endian\n\n    Returns\n    -------\n    values : dict\n        This will return a dict of iterables of the components of the values in\n        the file.\n\n    Examples\n    --------\n\n    >>> header = [(\"ncpu\", 1, \"i\"), (\"nfiles\", 2, \"i\")]\n    >>> f = open(\"cdata.bin\", \"rb\")\n    >>> rv = read_cattrs(f, header)\n    \"\"\"\n    vv = {}\n    net_format = endian\n    for _a, n, t in attrs:\n        for end in \"@=<>\":\n            t = t.replace(end, \"\")\n        net_format += \"\".join([t] * n)\n    size = struct.calcsize(net_format)\n    vals = list(struct.unpack(net_format, f.read(size)))\n    vv = {}\n    for a, n, t in attrs:\n        for end in \"@=<>\":\n            t = t.replace(end, \"\")\n        if isinstance(a, tuple):\n            n = len(a)\n        v = [vals.pop(0) for i in range(n)]\n        if n == 1:\n            v = v[0]\n        if isinstance(a, tuple):\n            if len(a) != len(v):\n                raise OSError(\n                    \"An error occurred while reading a Fortran \"\n                    \"record. Record length is not equal to expected \"\n                    f\"length: {len(a)} {len(v)}\"\n                )\n            for k, val in zip(a, v, strict=True):\n                vv[k] = val\n        else:\n            vv[a] = v\n    return vv\n\n\ndef read_vector(f, d, endian=\"=\"):\n    r\"\"\"This function accepts a file pointer and reads from that file pointer\n    a vector of values.\n\n    Parameters\n    ----------\n    f : File object\n        An open file object.  Should have been opened in mode rb.\n    d : data type\n        This is the datatype (from the struct module) that we should read.\n    endian : str\n        '=' is native, '>' is big, '<' is little endian\n\n    Returns\n    -------\n    tr : numpy.ndarray\n        This is the vector of values read from the file.\n\n    Examples\n    --------\n\n    >>> f = open(\"fort.3\", \"rb\")\n    >>> rv = read_vector(f, \"d\")\n    \"\"\"\n    pad_fmt = f\"{endian}I\"\n    pad_size = struct.calcsize(pad_fmt)\n    vec_len = struct.unpack(pad_fmt, f.read(pad_size))[0]  # bytes\n    vec_fmt = f\"{endian}{d}\"\n    vec_size = struct.calcsize(vec_fmt)\n    if vec_len % vec_size != 0:\n        raise OSError(\n            \"An error occurred while reading a Fortran record. \"\n            \"Vector length is not compatible with data type: %s %s\",\n            vec_len,\n            vec_size,\n        )\n    vec_num = int(vec_len / vec_size)\n    if isinstance(f, io.IOBase):\n        tr = np.frombuffer(f.read(vec_len), vec_fmt, count=vec_num)\n    else:\n        tr = np.frombuffer(f, vec_fmt, count=vec_num)\n    vec_len2 = struct.unpack(pad_fmt, f.read(pad_size))[0]\n    if vec_len != vec_len2:\n        raise OSError(\n            \"An error occurred while reading a Fortran record. \"\n            \"Got a different size at the beginning and at the \"\n            \"end of the record: %s %s\",\n            vec_len,\n            vec_len2,\n        )\n    return tr\n\n\ndef skip(f, n=1, endian=\"=\"):\n    r\"\"\"This function accepts a file pointer and skips a Fortran unformatted\n    record. Optionally check that the skip was done correctly by checking\n    the pad bytes.\n\n    Parameters\n    ----------\n    f : File object\n        An open file object.  Should have been opened in mode rb.\n    n : int\n        Number of records to skip.\n    endian : str\n        '=' is native, '>' is big, '<' is little endian\n\n    Returns\n    -------\n    skipped: The number of elements in the skipped array\n\n    Examples\n    --------\n\n    >>> f = open(\"fort.3\", \"rb\")\n    >>> skip(f, 3)\n    \"\"\"\n    skipped = np.zeros(n, dtype=np.int32)\n    fmt = endian + \"I\"\n    fmt_size = struct.calcsize(fmt)\n    for i in range(n):\n        size = f.read(fmt_size)\n        s1 = struct.unpack(fmt, size)[0]\n        f.seek(s1 + fmt_size, os.SEEK_CUR)\n        s2 = struct.unpack(fmt, size)[0]\n        if s1 != s2:\n            raise OSError(\n                \"An error occurred while reading a Fortran record. \"\n                \"Got a different size at the beginning and at the \"\n                \"end of the record: %s %s\",\n                s1,\n                s2,\n            )\n\n        skipped[i] = s1 / fmt_size\n    return skipped\n\n\ndef peek_record_size(f, endian=\"=\"):\n    r\"\"\"This function accept the file handle and returns\n    the size of the next record and then rewinds the file\n    to the previous position.\n\n    Parameters\n    ----------\n    f : File object\n        An open file object.  Should have been opened in mode rb.\n    endian : str\n        '=' is native, '>' is big, '<' is little endian\n\n    Returns\n    -------\n    Number of bytes in the next record\n    \"\"\"\n    pos = f.tell()\n    s = struct.unpack(\">i\", f.read(struct.calcsize(\">i\")))\n    f.seek(pos)\n    return s[0]\n\n\ndef read_record(f, rspec, endian=\"=\"):\n    r\"\"\"This function accepts a file pointer and reads from that file pointer\n    a single \"record\" with different components.\n\n    Fortran unformatted files provide total bytesize at the beginning and end\n    of a record.  By correlating the components of that record with attribute\n    names, we construct a dictionary that gets returned.\n\n    Parameters\n    ----------\n    f : File object\n        An open file object.  Should have been opened in mode rb.\n    rspec : iterable of iterables\n        This object should be an iterable of the format [ (attr_name, count,\n        struct type), ... ].\n    endian : str\n        '=' is native, '>' is big, '<' is little endian\n\n    Returns\n    -------\n    values : dict\n        This will return a dict of iterables of the components of the values in\n        the file.\n\n    Examples\n    --------\n\n    >>> header = [(\"ncpu\", 1, \"i\"), (\"nfiles\", 2, \"i\")]\n    >>> f = open(\"fort.3\", \"rb\")\n    >>> rv = read_record(f, header)\n    \"\"\"\n    vv = {}\n    net_format = endian + \"I\"\n    for _a, n, t in rspec:\n        t = t if len(t) == 1 else t[-1]\n        net_format += f\"{n}{t}\"\n    net_format += \"I\"\n    size = struct.calcsize(net_format)\n    vals = list(struct.unpack(net_format, f.read(size)))\n    s1, s2 = vals.pop(0), vals.pop(-1)\n    if s1 != s2:\n        raise OSError(\n            \"An error occurred while reading a Fortran record. Got \"\n            \"a different size at the beginning and at the end of \"\n            \"the record: %s %s\",\n            s1,\n            s2,\n        )\n    pos = 0\n    for a, n, _t in rspec:\n        vv[a] = vals[pos : pos + n]\n        pos += n\n    return vv\n"
  },
  {
    "path": "yt/utilities/grid_data_format/__init__.py",
    "content": "from .conversion import *\n"
  },
  {
    "path": "yt/utilities/grid_data_format/conversion/__init__.py",
    "content": "from .conversion_abc import Converter\nfrom .conversion_athena import AthenaConverter, AthenaDistributedConverter\n"
  },
  {
    "path": "yt/utilities/grid_data_format/conversion/conversion_abc.py",
    "content": "class Converter:\n    def __init__(self, basename, outname=None):\n        self.basename = basename\n        self.outname = outname\n\n    def convert(self):\n        pass\n"
  },
  {
    "path": "yt/utilities/grid_data_format/conversion/conversion_athena.py",
    "content": "import os\nfrom glob import glob\n\nimport numpy as np\n\nfrom yt.utilities.grid_data_format.conversion.conversion_abc import Converter\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\ntranslation_dict = {}\ntranslation_dict[\"density\"] = \"density\"\ntranslation_dict[\"total_energy\"] = \"specific_energy\"\ntranslation_dict[\"velocity_x\"] = \"velocity_x\"\ntranslation_dict[\"velocity_y\"] = \"velocity_y\"\ntranslation_dict[\"velocity_z\"] = \"velocity_z\"\ntranslation_dict[\"cell_centered_B_x\"] = \"mag_field_x\"\ntranslation_dict[\"cell_centered_B_y\"] = \"mag_field_y\"\ntranslation_dict[\"cell_centered_B_z\"] = \"mag_field_z\"\n\n\nclass AthenaDistributedConverter(Converter):\n    def __init__(self, basename, outname=None, source_dir=None, field_conversions=None):\n        self.fields = []\n        self.current_time = 0.0\n        name = basename.split(\".\")\n        self.ddn = int(name[1])\n        if source_dir is None:\n            source_dir = \"./\"\n        self.source_dir = source_dir\n        self.basename = name[0]\n        if outname is None:\n            outname = f\"{self.basename}.{self.ddn:04}.gdf\"\n        self.outname = outname\n        if field_conversions is None:\n            field_conversions = {}\n        self.field_conversions = field_conversions\n        self.handle = None\n\n    def parse_line(self, line, grid):\n        # grid is a dictionary\n        splitup = line.strip().split()\n        if \"vtk\" in splitup:\n            grid[\"vtk_version\"] = splitup[-1]\n        elif \"Really\" in splitup:\n            grid[\"time\"] = splitup[-1]\n            self.current_time = grid[\"time\"]\n        elif \"PRIMITIVE\" in splitup:\n            grid[\"time\"] = float(splitup[4].rstrip(\",\"))\n            grid[\"level\"] = int(splitup[6].rstrip(\",\"))\n            grid[\"domain\"] = int(splitup[8].rstrip(\",\"))\n            self.current_time = grid[\"time\"]\n        elif \"DIMENSIONS\" in splitup:\n            grid[\"dimensions\"] = np.array(splitup[-3:], dtype=\"int64\")\n        elif \"ORIGIN\" in splitup:\n            grid[\"left_edge\"] = np.array(splitup[-3:], dtype=\"float64\")\n        elif \"SPACING\" in splitup:\n            grid[\"dds\"] = np.array(splitup[-3:], dtype=\"float64\")\n        elif \"CELL_DATA\" in splitup:\n            grid[\"ncells\"] = int(splitup[-1])\n        elif \"SCALARS\" in splitup:\n            field = splitup[1]\n            grid[\"read_field\"] = field\n            grid[\"read_type\"] = \"scalar\"\n        elif \"VECTORS\" in splitup:\n            field = splitup[1]\n            grid[\"read_field\"] = field\n            grid[\"read_type\"] = \"vector\"\n\n    def write_gdf_field(self, fn, grid_number, field, data):\n        f = self.handle\n        ## --------- Store Grid Data --------- ##\n        if (group_name := f\"grid_{grid_number:010}\") not in f[\"data\"].keys():\n            g = f[\"data\"].create_group(group_name)\n        else:\n            g = f[\"data\"][group_name]\n        name = field\n        try:\n            name = translation_dict[name]\n        except Exception:\n            pass\n        # print 'Writing %s' % name\n        if name not in g.keys():\n            g.create_dataset(name, data=data)\n\n    def read_and_write_index(self, basename, ddn, gdf_name):\n        \"\"\"Read Athena legacy vtk file from multiple cpus\"\"\"\n        proc_names = glob(os.path.join(self.source_dir, \"id*\"))\n        # print('Reading a dataset from %i Processor Files' % len(proc_names))\n        N = len(proc_names)\n        grid_dims = np.empty([N, 3], dtype=\"int64\")\n        grid_left_edges = np.empty([N, 3], dtype=\"float64\")\n        grid_dds = np.empty([N, 3], dtype=\"float64\")\n        grid_levels = np.zeros(N, dtype=\"int64\")\n        grid_parent_ids = -1 * np.ones(N, dtype=\"int64\")\n        grid_particle_counts = np.zeros([N, 1], dtype=\"int64\")\n\n        for i in range(N):\n            if i == 0:\n                fn = os.path.join(\n                    self.source_dir, f\"id{i}\", basename + f\".{ddn:04d}.vtk\"\n                )\n            else:\n                fn = os.path.join(\n                    self.source_dir, f\"id{i}\", basename + f\"-id{i}.{ddn:04d}.vtk\"\n                )\n\n            print(f\"Reading file {fn}\")\n            f = open(fn, \"rb\")\n            grid = {}\n            grid[\"read_field\"] = None\n            grid[\"read_type\"] = None\n            line = f.readline()\n            while grid[\"read_field\"] is None:\n                self.parse_line(line, grid)\n                if \"SCALAR\" in line.strip().split():\n                    break\n                if \"VECTOR\" in line.strip().split():\n                    break\n                if \"TABLE\" in line.strip().split():\n                    break\n                if len(line) == 0:\n                    break\n                del line\n                line = f.readline()\n\n            if len(line) == 0:\n                break\n\n            if np.prod(grid[\"dimensions\"]) != grid[\"ncells\"]:\n                grid[\"dimensions\"] -= 1\n                grid[\"dimensions\"][grid[\"dimensions\"] == 0] = 1\n            if np.prod(grid[\"dimensions\"]) != grid[\"ncells\"]:\n                print(\n                    f\"product of dimensions {np.prod(grid['dimensions'])} \"\n                    f\"not equal to number of cells {grid['ncells']}\"\n                )\n                raise TypeError\n\n            # Append all hierarchy info before reading this grid's data\n            grid_dims[i] = grid[\"dimensions\"]\n            grid_left_edges[i] = grid[\"left_edge\"]\n            grid_dds[i] = grid[\"dds\"]\n            # grid_ncells[i]=grid['ncells']\n            del grid\n\n            f.close()\n            del f\n        f = self.handle\n\n        ## --------- Begin level nodes --------- ##\n        g = f.create_group(\"gridded_data_format\")\n        g.attrs[\"format_version\"] = np.float32(1.0)\n        g.attrs[\"data_software\"] = \"athena\"\n        f.create_group(\"data\")\n        f.create_group(\"field_types\")\n        f.create_group(\"particle_types\")\n        pars_g = f.create_group(\"simulation_parameters\")\n\n        gles = grid_left_edges\n        gdims = grid_dims\n        dle = np.min(gles, axis=0)\n        dre = np.max(gles + grid_dims * grid_dds, axis=0)\n        glis = ((gles - dle) / grid_dds).astype(\"int64\")\n\n        ddims = (dre - dle) / grid_dds[0]\n\n        # grid_left_index\n        f.create_dataset(\"grid_left_index\", data=glis)\n        # grid_dimensions\n        f.create_dataset(\"grid_dimensions\", data=gdims)\n\n        # grid_level\n        f.create_dataset(\"grid_level\", data=grid_levels)\n\n        ## ----------QUESTIONABLE NEXT LINE--------- ##\n        # This data needs two dimensions for now.\n        f.create_dataset(\"grid_particle_count\", data=grid_particle_counts)\n\n        # grid_parent_id\n        f.create_dataset(\"grid_parent_id\", data=grid_parent_ids)\n\n        ## --------- Done with top level nodes --------- ##\n\n        pars_g.attrs[\"refine_by\"] = np.int64(1)\n        pars_g.attrs[\"dimensionality\"] = np.int64(3)\n        pars_g.attrs[\"domain_dimensions\"] = ddims\n        pars_g.attrs[\"current_time\"] = self.current_time\n        pars_g.attrs[\"domain_left_edge\"] = dle\n        pars_g.attrs[\"domain_right_edge\"] = dre\n        pars_g.attrs[\"unique_identifier\"] = \"athenatest\"\n        pars_g.attrs[\"cosmological_simulation\"] = np.int64(0)\n        pars_g.attrs[\"num_ghost_zones\"] = np.int64(0)\n        pars_g.attrs[\"field_ordering\"] = np.int64(1)\n        pars_g.attrs[\"boundary_conditions\"] = np.int64([0] * 6)  # For Now\n\n        # Extra pars:\n        # pars_g.attrs['n_cells'] = grid['ncells']\n        pars_g.attrs[\"vtk_version\"] = 1.0\n\n        # Add particle types\n        # Nothing to do here\n\n        # Add particle field attributes\n        # f.close()\n\n    def read_and_write_data(self, basename, ddn, gdf_name):\n        proc_names = glob(os.path.join(self.source_dir, \"id*\"))\n        # print('Reading a dataset from %i Processor Files' % len(proc_names))\n        N = len(proc_names)\n        for i in range(N):\n            if i == 0:\n                fn = os.path.join(\n                    self.source_dir, f\"id{i}\", basename + f\".{ddn:04d}.vtk\"\n                )\n            else:\n                fn = os.path.join(\n                    self.source_dir, +f\"id{i}\", basename + f\"-id{i}.{ddn:04d}.vtk\"\n                )\n            f = open(fn, \"rb\")\n            # print('Reading data from %s' % fn)\n            line = f.readline()\n            while line != \"\":\n                if len(line) == 0:\n                    break\n                splitup = line.strip().split()\n\n                if \"DIMENSIONS\" in splitup:\n                    grid_dims = np.array(splitup[-3:], dtype=\"int64\")\n                    line = f.readline()\n                    continue\n                elif \"CELL_DATA\" in splitup:\n                    grid_ncells = int(splitup[-1])\n                    line = f.readline()\n                    if np.prod(grid_dims) != grid_ncells:\n                        grid_dims -= 1\n                        grid_dims[grid_dims == 0] = 1\n                    if np.prod(grid_dims) != grid_ncells:\n                        print(\n                            f\"product of dimensions {np.prod(grid_dims)} \"\n                            f\"not equal to number of cells {grid_ncells}\"\n                        )\n                        raise TypeError\n                    break\n                else:\n                    del line\n                    line = f.readline()\n            read_table = False\n            while line != \"\":\n                if len(line) == 0:\n                    break\n                splitup = line.strip().split()\n                if \"SCALARS\" in splitup:\n                    field = splitup[1]\n                    if not read_table:\n                        line = f.readline()  # Read the lookup table line\n                        read_table = True\n                    data = np.fromfile(f, dtype=\">f4\", count=grid_ncells).reshape(\n                        grid_dims, order=\"F\"\n                    )\n                    if i == 0:\n                        self.fields.append(field)\n                    # print('writing field %s' % field)\n                    self.write_gdf_field(gdf_name, i, field, data)\n                    read_table = False\n\n                elif \"VECTORS\" in splitup:\n                    field = splitup[1]\n                    data = np.fromfile(f, dtype=\">f4\", count=3 * grid_ncells)\n                    data_x = data[0::3].reshape(grid_dims, order=\"F\")\n                    data_y = data[1::3].reshape(grid_dims, order=\"F\")\n                    data_z = data[2::3].reshape(grid_dims, order=\"F\")\n                    if i == 0:\n                        self.fields.append(field + \"_x\")\n                        self.fields.append(field + \"_y\")\n                        self.fields.append(field + \"_z\")\n\n                    # print('writing field %s' % field)\n                    self.write_gdf_field(gdf_name, i, field + \"_x\", data_x)\n                    self.write_gdf_field(gdf_name, i, field + \"_y\", data_y)\n                    self.write_gdf_field(gdf_name, i, field + \"_z\", data_z)\n                    del data, data_x, data_y, data_z\n                del line\n                line = f.readline()\n            f.close()\n            del f\n\n        f = self.handle\n        field_g = f[\"field_types\"]\n        # Add Field Attributes\n        for name in self.fields:\n            tname = name\n            try:\n                tname = translation_dict[name]\n            except Exception:\n                pass\n            this_field = field_g.create_group(tname)\n            if name in self.field_conversions.keys():\n                this_field.attrs[\"field_to_cgs\"] = self.field_conversions[name]\n            else:\n                this_field.attrs[\"field_to_cgs\"] = np.float64(\"1.0\")  # For Now\n\n    def convert(self, index=True, data=True):\n        self.handle = h5py.File(self.outname, mode=\"a\")\n        if index:\n            self.read_and_write_index(self.basename, self.ddn, self.outname)\n        if data:\n            self.read_and_write_data(self.basename, self.ddn, self.outname)\n        self.handle.close()\n\n\nclass AthenaConverter(Converter):\n    def __init__(self, basename, outname=None, field_conversions=None):\n        self.fields = []\n        self.basename = basename\n        name = basename.split(\".\")\n        fn = f\"{name[0]}.{int(name[1]):04}\"\n        self.ddn = int(name[1])\n        self.basename = fn\n        if outname is None:\n            outname = fn + \".gdf\"\n        self.outname = outname\n        if field_conversions is None:\n            field_conversions = {}\n        self.field_conversions = field_conversions\n\n    def parse_line(self, line, grid):\n        # grid is a dictionary\n        splitup = line.strip().split()\n        if \"vtk\" in splitup:\n            grid[\"vtk_version\"] = splitup[-1]\n        elif \"Really\" in splitup:\n            grid[\"time\"] = splitup[-1]\n        elif \"DIMENSIONS\" in splitup:\n            grid[\"dimensions\"] = np.array(splitup[-3:], dtype=\"int64\")\n        elif \"ORIGIN\" in splitup:\n            grid[\"left_edge\"] = np.array(splitup[-3:], dtype=\"float64\")\n        elif \"SPACING\" in splitup:\n            grid[\"dds\"] = np.array(splitup[-3:], dtype=\"float64\")\n        elif \"CELL_DATA\" in splitup:\n            grid[\"ncells\"] = int(splitup[-1])\n        elif \"SCALARS\" in splitup:\n            field = splitup[1]\n            grid[\"read_field\"] = field\n            grid[\"read_type\"] = \"scalar\"\n        elif \"VECTORS\" in splitup:\n            field = splitup[1]\n            grid[\"read_field\"] = field\n            grid[\"read_type\"] = \"vector\"\n\n    def read_grid(self, filename):\n        \"\"\"Read Athena legacy vtk file from single cpu\"\"\"\n        f = open(filename, \"rb\")\n        # print('Reading from %s'%filename)\n        grid = {}\n        grid[\"read_field\"] = None\n        grid[\"read_type\"] = None\n        table_read = False\n        line = f.readline()\n        while line != \"\":\n            while grid[\"read_field\"] is None:\n                self.parse_line(line, grid)\n                if grid[\"read_type\"] == \"vector\":\n                    break\n                if not table_read:\n                    line = f.readline()\n                if \"TABLE\" in line.strip().split():\n                    table_read = True\n                if len(line) == 0:\n                    break\n\n            if len(line) == 0:\n                break\n            if np.prod(grid[\"dimensions\"]) != grid[\"ncells\"]:\n                grid[\"dimensions\"] -= 1\n            if np.prod(grid[\"dimensions\"]) != grid[\"ncells\"]:\n                print(\n                    f\"product of dimensions {np.prod(grid['dimensions'])} \"\n                    f\"not equal to number of cells {grid['ncells']}\"\n                )\n                raise TypeError\n\n            if grid[\"read_type\"] == \"scalar\":\n                grid[grid[\"read_field\"]] = np.fromfile(\n                    f, dtype=\">f4\", count=grid[\"ncells\"]\n                ).reshape(grid[\"dimensions\"], order=\"F\")\n                self.fields.append(grid[\"read_field\"])\n            elif grid[\"read_type\"] == \"vector\":\n                data = np.fromfile(f, dtype=\">f4\", count=3 * grid[\"ncells\"])\n                grid[grid[\"read_field\"] + \"_x\"] = data[0::3].reshape(\n                    grid[\"dimensions\"], order=\"F\"\n                )\n                grid[grid[\"read_field\"] + \"_y\"] = data[1::3].reshape(\n                    grid[\"dimensions\"], order=\"F\"\n                )\n                grid[grid[\"read_field\"] + \"_z\"] = data[2::3].reshape(\n                    grid[\"dimensions\"], order=\"F\"\n                )\n                self.fields.append(grid[\"read_field\"] + \"_x\")\n                self.fields.append(grid[\"read_field\"] + \"_y\")\n                self.fields.append(grid[\"read_field\"] + \"_z\")\n            else:\n                raise TypeError\n            grid[\"read_field\"] = None\n            grid[\"read_type\"] = None\n            line = f.readline()\n            if len(line) == 0:\n                break\n        grid[\"right_edge\"] = grid[\"left_edge\"] + grid[\"dds\"] * (grid[\"dimensions\"])\n        return grid\n\n    def write_to_gdf(self, fn, grid):\n        f = h5py.File(fn, mode=\"a\")\n\n        ## --------- Begin level nodes --------- ##\n        g = f.create_group(\"gridded_data_format\")\n        g.attrs[\"format_version\"] = np.float32(1.0)\n        g.attrs[\"data_software\"] = \"athena\"\n        data_g = f.create_group(\"data\")\n        field_g = f.create_group(\"field_types\")\n        f.create_group(\"particle_types\")\n        pars_g = f.create_group(\"simulation_parameters\")\n\n        dle = grid[\"left_edge\"]  # True only in this case of one grid for the domain\n        gles = np.array([grid[\"left_edge\"]])\n        gdims = np.array([grid[\"dimensions\"]])\n        glis = ((gles - dle) / grid[\"dds\"]).astype(\"int64\")\n\n        # grid_left_index\n        f.create_dataset(\"grid_left_index\", data=glis)\n        # grid_dimensions\n        f.create_dataset(\"grid_dimensions\", data=gdims)\n\n        levels = np.array([0], dtype=\"int64\")  # unigrid example\n        # grid_level\n        f.create_dataset(\"grid_level\", data=levels)\n\n        ## ----------QUESTIONABLE NEXT LINE--------- ##\n        # This data needs two dimensions for now.\n        n_particles = np.array([[0]], dtype=\"int64\")\n        # grid_particle_count\n        f.create_dataset(\"grid_particle_count\", data=n_particles)\n\n        # Assume -1 means no parent.\n        parent_ids = np.array([-1], dtype=\"int64\")\n        # grid_parent_id\n        f.create_dataset(\"grid_parent_id\", data=parent_ids)\n\n        ## --------- Done with top level nodes --------- ##\n\n        f.create_group(\"index\")\n\n        ## --------- Store Grid Data --------- ##\n\n        g0 = data_g.create_group(\"grid_{0:010}\")\n        for field in self.fields:\n            name = field\n            if field in translation_dict.keys():\n                name = translation_dict[name]\n            if name not in g0.keys():\n                g0.create_dataset(name, data=grid[field])\n\n        ## --------- Store Particle Data --------- ##\n\n        # Nothing to do\n\n        ## --------- Attribute Tables --------- ##\n\n        pars_g.attrs[\"refine_by\"] = np.int64(1)\n        pars_g.attrs[\"dimensionality\"] = np.int64(3)\n        pars_g.attrs[\"domain_dimensions\"] = grid[\"dimensions\"]\n        try:\n            pars_g.attrs[\"current_time\"] = grid[\"time\"]\n        except Exception:\n            pars_g.attrs[\"current_time\"] = 0.0\n        pars_g.attrs[\"domain_left_edge\"] = grid[\"left_edge\"]  # For Now\n        pars_g.attrs[\"domain_right_edge\"] = grid[\"right_edge\"]  # For Now\n        pars_g.attrs[\"unique_identifier\"] = \"athenatest\"\n        pars_g.attrs[\"cosmological_simulation\"] = np.int64(0)\n        pars_g.attrs[\"num_ghost_zones\"] = np.int64(0)\n        pars_g.attrs[\"field_ordering\"] = np.int64(0)\n        pars_g.attrs[\"boundary_conditions\"] = np.int64([0] * 6)  # For Now\n\n        # Extra pars:\n        pars_g.attrs[\"n_cells\"] = grid[\"ncells\"]\n        pars_g.attrs[\"vtk_version\"] = grid[\"vtk_version\"]\n\n        # Add Field Attributes\n        for name in g0.keys():\n            tname = name\n            try:\n                tname = translation_dict[name]\n            except Exception:\n                pass\n            this_field = field_g.create_group(tname)\n        if name in self.field_conversions.keys():\n            this_field.attrs[\"field_to_cgs\"] = self.field_conversions[name]\n        else:\n            this_field.attrs[\"field_to_cgs\"] = np.float64(\"1.0\")  # For Now\n\n        # Add particle types\n        # Nothing to do here\n\n        # Add particle field attributes\n        f.close()\n\n    def convert(self):\n        grid = self.read_grid(self.basename + \".vtk\")\n        self.write_to_gdf(self.outname, grid)\n\n\n# import sys\n# if __name__ == '__main__':\n#     n = sys.argv[-1]\n#     n = n.split('.')\n#     fn = '%s.%04i'%(n[0],int(n[1]))\n#     grid = read_grid(fn+'.vtk')\n#     write_to_hdf5(fn+'.gdf',grid)\n"
  },
  {
    "path": "yt/utilities/grid_data_format/docs/IRATE_notes.txt",
    "content": "Here is info from Erik Tollerud about the IRATE data format.\n\nThe bitbucket project is at https://bitbucket.org/eteq/irate-format\nand I've posted a copy of the docs at\nhttp://www.physics.uci.edu/~etolleru/irate-docs/ , in particular\nhttp://www.physics.uci.edu/~etolleru/irate-docs/formatspec.html ,\nwhich details the actual requirements for data to fit in the format.\nAs far as I can tell, the following steps are needed to make GDF fit\ninside the IRATE format:\n\n*move everything except \"/simulation_parameters\" into a group named \"/GridData\"\n\n*rename \"/simulation_parameters\" to \"SimulationParameters\"\n\n*remove the 'field_types' group (this is not absolutely necessary, but\nthe convention we had in mind for IRATE is that the dataset names\nthemselves (e.g. the datasets like /data/gridxxxxxx/density)  serve as\nthe field definitions.\n\n* The unit information that's in 'field_types' should then be\nattributes in either \"/GridData\" or \"/GridData/data\" following the\nnaming scheme e.g. \"densityunitcgs\" following the unit form given in\nthe IRATE doc and an additional attribute e.g. \"densityunitname\"\nshould be added with the human-readable name of the unit. This unit\ninformation can also live at the dataset level, but it probably makes\nmore sense to put it instead at the higher level (IRATE supports both\nways of doing it)\n\n* The Cosmology group (as defined in the IRATE specification) must be\nadded - for simulations that are not technically \"cosmological\", you\ncan just use one of the default cosmologies (WMAP7 is a reasonable\nchoice - there's a function in the IRATE tools that automatically\ntakes care of all the details for this).\n\n* optional: redo all the group names to follow the CamelCase\nconvention - that's what we've been using elsewhere in IRATE.  This is\nan arbitrary choice, but it would be nice for it to be consistent\nthroughout the format.\n"
  },
  {
    "path": "yt/utilities/grid_data_format/docs/gdf_specification.txt",
    "content": "Gridded Data Format\n===================\n\nThis is a pre-release of version 1.0 of this format.  Lots of formats have come\nbefore, but this one is simple and will work with yt; the idea is to create an\nimport and export function in yt that will read this, so that other codes (such\nas ZEUS-MP) can export directly to it or convert their data to it, and so that\nyt can export to it from any format it recognizes and reads.\n\nCaveats and Notes\n-----------------\n\n#. We avoid having many attributes on many nodes, as access can be quite slow\n#. Cartesian data only for now\n#. All grids must have the same number of ghost zones.\n#. If \"/grid_parent\" does not exist, parentage relationships will be\n   reconstructed and assumed to allow multiple grids\n#. No parentage can skip levels\n#. All grids are at the same time\n#. This format is designed for single-fluid calculations (with color fields)\n   but it should be viewed as extensible to multiple-fluids.\n#. All fluid quantities are assumed to be in every grid, filling every zone.  Inside\n   a given grid, for a given particle type, all the affiliated fields must be the\n   same length.  (i.e., dark matter's velocity must be the same in all dimensions.)\n#. Everything is in a single file; for extremely large datasets, the user may\n   utilize HDF5 external links to link to files other than the primary.  (This\n   enables, for instance, Enzo datasets to have only a thin wrapper that creates\n   this format.)\n#. All fluid fields in this version of the format are assumed to have the\n   dimensionality of the grid they reside in plus any ghost zones, plus any\n   additionally dimensionality required by the staggering property.\n#. Particles may have dataspaces affiliated with them.  (See Enzo's\n   OutputParticleTypeGrouping for more information.)  This enables a light\n   wrapper around data formats with interspersed particle types.\n#. Boundary conditions are very simply specified -- future revisions\n   will feature more complicated and rich specifications for the boundary.\n\nFurthermore, we make a distinction between fluid quantities and particle\nquantities.  Particles remain affiliated with grid nodes.  Positions of\nparticles are global, but this will change with future versions of this\ndocument.\n\nFormat Declaration\n------------------\n\nThe file type is HDF5.  We require version 1.8 or greater.  At the root level,\nthis group must exist: ::\n\n   /gridded_data_format\n\nThis must contain the (float) attribute ``format_version``.  This document\ndescribes version 1.0.  Optional attributes may exist:\n\n``data_software``\n   string, references the application creating the file, not the\n   author of the data\n``data_software_version``\n   string, should reference a unique version number\n``data_author``\n   string, references the person or persons who created the data,\n   should include an email address\n``data_comment``\n   string, anything about the data\n\nTop Level Nodes\n---------------\n\nAt least five top-level groups must exist, although some may be empty. ::\n\n   /gridded_data_format\n   /data\n   /simulation_parameters\n   /field_types\n   /particle_types\n\nAdditionally, the grid structure elements must exist.  The 0-indexed index into this array\ndefines a unique \"Grid ID\".\n\n``/grid_left_index``\n   (int64, Nx3): global, relative to current level, and only the active region\n``/grid_dimensions``\n   (int64, Nx3): only the active regions\n``/grid_level``\n   (int64, N): level, indexed by zero\n``/grid_particle_count``\n   (int64, N): total number of particles.  (May change in subsequent versions.)\n``/grid_parent_id``\n   (int64, N): optional, may only reference a single parent\n\nGrid Fields\n-----------\n\nUnderneath ``/data/`` there must be entries for every grid, of the format\n``/data/grid_%010i``.  These grids need no attributes, and underneath them\ndatasets live.\n\nFluid Fields\n++++++++++++\n\nFor every grid we then define ``/data/grid_%010i/%(field)s``.\n\nWhere ``%(field)s`` draws from all of the fields defined.  We define no\nstandard for which fields must be present, only the names and units.  Units\nshould always be ''proper'' cgs (or conversion factors should be supplied, below), and\nfield names should be drawn from this list, with these names.  Not all fields\nmust be represented.  Field must extend beyond the active region if ghost zones\nare included.  All pre-defined fields are assumed to be cell-centered unless this\nis overridden in ``field_types``.\n\n  * ``density`` (g/cc)\n  * ``temperature`` (K)\n  * ``specific_thermal_energy`` (erg/g)\n  * ``specific_energy`` (erg/g, includes kinetic and magnetic)\n  * ``magnetic_energy`` (erg/g)\n  * ``velocity_x`` (cm/s)\n  * ``velocity_y`` (cm/s)\n  * ``velocity_z`` (cm/s)\n  * ``species_density_%s`` (g/cc) where %s is the species name including ionization\n    state, such as H2I, HI, HII, CO, \"elec\" for electron\n  * ``mag_field_x``\n  * ``mag_field_y``\n  * ``mag_field_z``\n\nParticle Fields\n+++++++++++++++\n\nParticles are more expensive to sort and identify based on \"type\" -- for\ninstance, dark matter versus star particles.  The particles should be separated\nbased on type, under the group ``/data/grid_%010i/particles/``.\n\nThe particles group will have sub-groups, each of which will be named after the\ntype of particle it represents.  We only specify \"dark_matter\" as a type;\nanything else must be specified as described below.\n\nEach node, for instance ``/data/grid_%010i/particles/dark_matter/``, must\ncontain the following fields:\n\n  * ``mass`` (g)\n  * ``id``\n  * ``position_x`` (in physical units)\n  * ``position_y`` (in physical units)\n  * ``position_z`` (in physical units)\n  * ``velocity_x`` (cm/s)\n  * ``velocity_y`` (cm/s)\n  * ``velocity_z`` (cm/s)\n  * ``dataspace`` (optional) an HDF5 dataspace to be used when opening\n    all affiliated fields.   If this is to be used, it must be appropriately set in\n    the particle type definition.  This is of type ``H5T_STD_REF_DSETREG``.\n    (See Enzo's OutputParticleTypeGrouping for an example.)\n\nAdditional Fields\n+++++++++++++++++\n\nAny additional fields from the data can be added, but must have a corresponding\nentry in the root field table (described below.)  The naming scheme is to be as\nexplicit as possible, with units in cgs (or a conversion factor to the standard\ncgs unit, in the field table.)\n\nAttribute Table\n---------------\n\nIn the root node, we define several groups which contain attributes.\n\nSimulation Parameters\n+++++++++++++++++++++\n\nThese attributes will all be associated with ``/simulation_parameters``.\n\n``refine_by``\n   relative global refinement\n``dimensionality``\n   1-, 2- or 3-D data\n``domain_dimensions``\n   dimensions in the top grid\n``current_time``\n   current time in simulation, in seconds, from \"start\" of simulation\n``domain_left_edge``\n   the left edge of the domain, in cm\n``domain_right_edge``\n   the right edge of the domain, in cm\n``unique_identifier``\n   regarded as a string, but can be anything\n``cosmological_simulation``\n   0 or 1\n``num_ghost_zones``\n   integer\n``field_ordering``\n   integer: 0 for C, 1 for Fortran\n``boundary_conditions``\n   integer (6): 0 for periodic, 1 for mirrored, 2 for outflow.  Needs one for each face\n   of the cube.  Any past the dimensionality should be set to -1.  The order of specification\n   goes left in 0th dimension, right in 0th dimension, left in 1st dimension, right in 1st dimensions,\n   left in 2nd dimension, right in 2nd dimension.  Note also that yt does not currently support non-periodic\n   boundary conditions, and that the assumption of periodicity shows up primarily in plots and\n   covering grids.\n\nOptionally, attributes for cosmological simulations can be provided, if\ncosmological_simulation above is set to 1:\n\n  * current_redshift\n  * omega_matter (at z=0)\n  * omega_lambda (at z=0)\n  * hubble_constant (h100)\n\nFluid Field Attributes\n++++++++++++++++++++++\n\nEvery field that is included that is not both in CGS already and in the list\nabove requires parameters.  If a field is in the above list but is not in CGS,\nonly the field_to_cgs attribute is necessary.  These will be stored under\n``/field_types`` and each must possess the following attributes:\n\n``field_name``\n   a string that will be used to describe the field; can contain spaces.\n``field_to_cgs``\n   a float that will be used to convert the field to cgs units, if necessary.\n   Set to 1.0 if no conversion necessary.  Note that if non-CGS units are desired\n   this field should simply be viewed as the value by which field values are\n   multiplied to get to some internally consistent unit system.\n``field_units``\n   a string that names the units.\n``staggering``\n   an integer: 0 for cell-centered, 1 for face-centered, 2 for vertex-centered.\n   Non-cellcentered data will be linearly-interpolated; more complicated\n   reconstruction will be defined in a future version of this standard; for 1.0\n   we only allow for simple definitions.\n\nParticle Types\n++++++++++++++\n\nEvery particle type that is not recognized (i.e., all non-Dark Matter types)\nneeds to have an entry under ``/particle_types``.  Each entry must possess the\nfollowing attributes:\n\n``particle_type_name``\n   a string that will be used to describe the field; can contain spaces.\n``particle_use_dataspace``\n   (optional) if 1, the dataspace (see particle field definition above) will be used\n   for all particle fields for this type of particle.  Useful if a given type of particle\n   is embedded inside a larger list of different types of particle.\n``particle_type_num``\n   an integer giving the total number of particles of this type.\n\nFor instance, to define a particle of type ``accreting_black_hole``, the file\nmust contain ``/particle_types/accreting_black_hole``, with the\n``particle_type_name`` attribute of \"Accreting Black Hole\".\n\nParticle Field Attributes\n+++++++++++++++++++++++++\n\nEvery particle type that contains a new field (for instance, ``accretion_rate``)\nneeds to have an entry under ``/particle_types/{particle_type_name}/{field_name}``\ncontaining the following attributes:\n\n``field_name``\n   a string that will be used to describe the field; can contain spaces.\n``field_to_cgs``\n   a float that will be used to convert the field to cgs units, if necessary.\n   Set to 1.0 if no conversion necessary.\n``field_units``\n   a string that names the units.\n\nRole of YT\n----------\n\nyt will provide a reader for this data, so that any data in this format can be\nused by the code.  Additionally, the names and specifications in this code\nreflect the internal yt data structures.\n\nyt will also provide a writer for this data, which will operate on any existing\ndata format.  Provided that a simulation code can read this data, this will\nenable cross-platform comparison.  Furthermore, any external piece of software\n(i.e., Stranger) that implements reading this format will be able to read any\nformat of data that yt understands.\n\nExample File\n------------\n\nAn example file constructed from the ``RD0005-mine`` dataset is available\nat http://yt.enzotools.org/files/RD0005.gdf .  It is not yet a complete\nconversion, but it is a working proof of concept.  Readers and writers are\nforthcoming.\n"
  },
  {
    "path": "yt/utilities/grid_data_format/scripts/convert_distributed_athena.py",
    "content": "import sys\n\nfrom grid_data_format import AthenaDistributedConverter\n\n# Assumes that last input is the basename for the athena dataset.\n# i.e. kh_3d_mhd_hlld_128_beta5000_sub_tanhd.0030\nbasename = sys.argv[-1]\nconverter = AthenaDistributedConverter(basename)\nconverter.convert()\n\n# If you have units information, set up a conversion dictionary for\n# each field.  Each key is the name of the field that Athena uses.\n# Each value is what you have to multiply the raw output from Athena\n# by to get cgs units.\n\n# code_to_cgs = {'density':1.0e3,\n# \t       'total_energy':1.0e-3,\n# \t       'velocity_x':1.2345,\n# \t       'velocity_y':1.2345,\n# \t       'velocity_z':1.2345}\n\n# converter = AthenaDistributedConverter(basename, field_conversions=code_to_cgs)\n# converter.convert()\n"
  },
  {
    "path": "yt/utilities/grid_data_format/scripts/convert_single_athena.py",
    "content": "import sys\n\nfrom grid_data_format import AthenaConverter\n\n# Assumes that last input is the basename for the athena dataset.\n# i.e. kh_3d_mhd_hlld_128_beta5000_sub_tanhd.0030\nbasename = sys.argv[-1]\nconverter = AthenaConverter(basename)\nconverter.convert()\n\n# If you have units information, set up a conversion dictionary for\n# each field.  Each key is the name of the field that Athena uses.\n# Each value is what you have to multiply the raw output from Athena\n# by to get cgs units.\n\n# code_to_cgs = {'density':1.0e3,\n# \t       'total_energy':1.0e-3,\n# \t       'velocity_x':1.2345,\n# \t       'velocity_y':1.2345,\n# \t       'velocity_z':1.2345}\n\n# converter = AthenaDistributedConverter(basename, field_conversions=code_to_cgs)\n# converter.convert()\n"
  },
  {
    "path": "yt/utilities/grid_data_format/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/utilities/grid_data_format/tests/test_writer.py",
    "content": "import os\nimport shutil\nimport tempfile\n\nfrom numpy.testing import assert_equal\n\nfrom yt.frontends.gdf.data_structures import GDFDataset\nfrom yt.loaders import load\nfrom yt.testing import fake_random_ds, requires_module\nfrom yt.utilities.grid_data_format.writer import write_to_gdf\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nTEST_AUTHOR = \"yt test runner\"\nTEST_COMMENT = \"Testing write_to_gdf\"\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\n@requires_module(\"h5py\")\ndef test_write_gdf():\n    \"\"\"Main test suite for write_gdf\"\"\"\n    tmpdir = tempfile.mkdtemp()\n    tmpfile = os.path.join(tmpdir, \"test_gdf.h5\")\n\n    try:\n        test_ds = fake_random_ds(64)\n        write_to_gdf(\n            test_ds, tmpfile, data_author=TEST_AUTHOR, data_comment=TEST_COMMENT\n        )\n        del test_ds\n        assert isinstance(load(tmpfile), GDFDataset)\n\n        h5f = h5py.File(tmpfile, mode=\"r\")\n        gdf = h5f[\"gridded_data_format\"].attrs\n        assert_equal(gdf[\"data_author\"], TEST_AUTHOR)\n        assert_equal(gdf[\"data_comment\"], TEST_COMMENT)\n        h5f.close()\n\n    finally:\n        shutil.rmtree(tmpdir)\n"
  },
  {
    "path": "yt/utilities/grid_data_format/writer.py",
    "content": "import os\nimport sys\nfrom contextlib import contextmanager\n\nimport numpy as np\n\nfrom yt import __version__ as yt_version\nfrom yt.funcs import iter_fields\nfrom yt.utilities.exceptions import YTGDFAlreadyExists\nfrom yt.utilities.on_demand_imports import _h5py as h5py\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    communication_system,\n    parallel_objects,\n)\n\n\ndef write_to_gdf(\n    ds,\n    gdf_path,\n    fields=None,\n    data_author=None,\n    data_comment=None,\n    dataset_units=None,\n    particle_type_name=\"dark_matter\",\n    overwrite=False,\n    **kwargs,\n):\n    \"\"\"\n    Write a dataset to the given path in the Grid Data Format.\n\n    Parameters\n    ----------\n    ds : Dataset object\n        The yt data to write out.\n    gdf_path : string\n        The path of the file to output.\n    fields\n        The field or list of fields to write out. If None, defaults to\n        ds.field_list.\n    data_author : string, optional\n        The name of the author who wrote the data. Default: None.\n    data_comment : string, optional\n        A descriptive comment. Default: None.\n    dataset_units : dictionary, optional\n        A dictionary of (value, unit) tuples to set the default units\n        of the dataset. Keys can be:\n\n        * \"length_unit\"\n        * \"time_unit\"\n        * \"mass_unit\"\n        * \"velocity_unit\"\n        * \"magnetic_unit\"\n\n        If not specified, these will carry over from the parent\n        dataset.\n    particle_type_name : string, optional\n        The particle type of the particles in the dataset. Default: \"dark_matter\"\n    overwrite : boolean, optional\n        Whether or not to overwrite an already existing file. If False, attempting\n        to overwrite an existing file will result in an exception.\n\n    Examples\n    --------\n    >>> dataset_units = {\"length_unit\": (1.0, \"Mpc\"), \"time_unit\": (1.0, \"Myr\")}\n    >>> write_to_gdf(\n    ...     ds,\n    ...     \"clumps.h5\",\n    ...     data_author=\"John ZuHone\",\n    ...     dataset_units=dataset_units,\n    ...     data_comment=\"My Really Cool Dataset\",\n    ...     overwrite=True,\n    ... )\n    \"\"\"\n    if fields is None:\n        fields = ds.field_list\n\n    fields = list(iter_fields(fields))\n\n    with _create_new_gdf(\n        ds,\n        gdf_path,\n        data_author,\n        data_comment,\n        dataset_units=dataset_units,\n        particle_type_name=particle_type_name,\n        overwrite=overwrite,\n    ) as f:\n        # now add the fields one-by-one\n        _write_fields_to_gdf(ds, f, fields, particle_type_name)\n\n\ndef save_field(ds, fields, field_parameters=None):\n    \"\"\"\n    Write a single field associated with the dataset ds to the\n    backup file.\n\n    Parameters\n    ----------\n    ds : Dataset object\n        The yt dataset that the field is associated with.\n    fields : field of list of fields\n        The name(s) of the field(s) to save.\n    field_parameters : dictionary\n        A dictionary of field parameters to set.\n    \"\"\"\n\n    fields = list(iter_fields(fields))\n    for field_name in fields:\n        if isinstance(field_name, tuple):\n            field_name = field_name[1]\n        field_obj = ds._get_field_info(field_name)\n        if field_obj.sampling_type == \"particle\":\n            print(\"Saving particle fields currently not supported.\")\n            return\n\n    with _get_backup_file(ds) as f:\n        # now save the field\n        _write_fields_to_gdf(\n            ds,\n            f,\n            fields,\n            particle_type_name=\"dark_matter\",\n            field_parameters=field_parameters,\n        )\n\n\ndef _write_fields_to_gdf(\n    ds, fhandle, fields, particle_type_name, field_parameters=None\n):\n    for field in fields:\n        # add field info to field_types group\n        g = fhandle[\"field_types\"]\n        # create the subgroup with the field's name\n        fi = ds._get_field_info(field)\n        ftype, fname = fi.name\n        try:\n            sg = g.create_group(fname)\n        except ValueError:\n            print(\"Error - File already contains field called \" + field)\n            sys.exit(1)\n\n        # grab the display name and units from the field info container.\n        display_name = fi.display_name\n        units = fi.units\n\n        # check that they actually contain something...\n        if display_name:\n            sg.attrs[\"field_name\"] = np.bytes_(display_name)\n        else:\n            sg.attrs[\"field_name\"] = np.bytes_(str(field))\n        if units:\n            sg.attrs[\"field_units\"] = np.bytes_(units)\n        else:\n            sg.attrs[\"field_units\"] = np.bytes_(\"None\")\n        # @todo: is this always true?\n        sg.attrs[\"staggering\"] = 0\n\n    # first we must create the datasets on all processes.\n    g = fhandle[\"data\"]\n    for grid in ds.index.grids:\n        for field in fields:\n            # sanitize get the field info object\n            fi = ds._get_field_info(field)\n            ftype, fname = fi.name\n\n            grid_group = g[f\"grid_{grid.id - grid._id_offset:010}\"]\n            particles_group = grid_group[\"particles\"]\n            pt_group = particles_group[particle_type_name]\n\n            if fi.sampling_type == \"particle\":  # particle data\n                pt_group.create_dataset(fname, grid.ActiveDimensions, dtype=\"float64\")\n            else:  # a field\n                grid_group.create_dataset(fname, grid.ActiveDimensions, dtype=\"float64\")\n\n    # now add the actual data, grid by grid\n    g = fhandle[\"data\"]\n    data_source = ds.all_data()\n    citer = data_source.chunks([], \"io\", local_only=True)\n    for region in parallel_objects(citer):\n        # is there a better way to the get the grids on each chunk?\n        for chunk in ds.index._chunk_io(region):\n            for grid in chunk.objs:\n                for field in fields:\n                    # sanitize and get the field info object\n                    fi = ds._get_field_info(field)\n                    ftype, fname = fi.name\n\n                    # set field parameters, if specified\n                    if field_parameters is not None:\n                        for k, v in field_parameters.items():\n                            grid.set_field_parameter(k, v)\n\n                    grid_group = g[f\"grid_{grid.id - grid._id_offset:010}\"]\n                    particles_group = grid_group[\"particles\"]\n                    pt_group = particles_group[particle_type_name]\n                    # add the field data to the grid group\n                    # Check if this is a real field or particle data.\n                    grid.get_data(field)\n                    units = fhandle[\"field_types\"][fname].attrs[\"field_units\"]\n                    if fi.sampling_type == \"particle\":  # particle data\n                        dset = pt_group[fname]\n                        dset[:] = grid[field].in_units(units)\n                    else:  # a field\n                        dset = grid_group[fname]\n                        dset[:] = grid[field].in_units(units)\n\n\n@contextmanager\ndef _get_backup_file(ds):\n    backup_filename = ds.backup_filename\n    if os.path.exists(backup_filename):\n        # backup file already exists, open it. We use parallel\n        # h5py if it is available\n        if communication_system.communicators[-1].size > 1 and h5py.get_config().mpi:\n            mpi4py_communicator = communication_system.communicators[-1].comm\n            f = h5py.File(\n                backup_filename, mode=\"r+\", driver=\"mpio\", comm=mpi4py_communicator\n            )\n        else:\n            f = h5py.File(backup_filename, mode=\"r+\")\n        yield f\n        f.close()\n    else:\n        # backup file does not exist, create it\n        with _create_new_gdf(\n            ds,\n            backup_filename,\n            data_author=None,\n            data_comment=None,\n            particle_type_name=\"dark_matter\",\n        ) as f:\n            yield f\n\n\n@contextmanager\ndef _create_new_gdf(\n    ds,\n    gdf_path,\n    data_author=None,\n    data_comment=None,\n    dataset_units=None,\n    particle_type_name=\"dark_matter\",\n    overwrite=False,\n    **kwargs,\n):\n    # Make sure we have the absolute path to the file first\n    gdf_path = os.path.abspath(gdf_path)\n\n    # Is the file already there? If so, are we allowing\n    # overwriting?\n    if os.path.exists(gdf_path) and not overwrite:\n        raise YTGDFAlreadyExists(gdf_path)\n\n    ###\n    # Create and open the file with h5py. We use parallel\n    # h5py if it is available.\n    ###\n    if communication_system.communicators[-1].size > 1 and h5py.get_config().mpi:\n        mpi4py_communicator = communication_system.communicators[-1].comm\n        f = h5py.File(gdf_path, mode=\"w\", driver=\"mpio\", comm=mpi4py_communicator)\n    else:\n        f = h5py.File(gdf_path, mode=\"w\")\n\n    ###\n    # \"gridded_data_format\" group\n    ###\n    g = f.create_group(\"gridded_data_format\")\n    g.attrs[\"data_software\"] = \"yt\"\n    g.attrs[\"data_software_version\"] = yt_version\n    if data_author is not None:\n        g.attrs[\"data_author\"] = data_author\n    if data_comment is not None:\n        g.attrs[\"data_comment\"] = data_comment\n\n    ###\n    # \"simulation_parameters\" group\n    ###\n    g = f.create_group(\"simulation_parameters\")\n    g.attrs[\"refine_by\"] = ds.refine_by\n    g.attrs[\"dimensionality\"] = ds.dimensionality\n    g.attrs[\"domain_dimensions\"] = ds.domain_dimensions\n    g.attrs[\"current_time\"] = ds.current_time\n    g.attrs[\"domain_left_edge\"] = ds.domain_left_edge\n    g.attrs[\"domain_right_edge\"] = ds.domain_right_edge\n    g.attrs[\"unique_identifier\"] = ds.unique_identifier\n    g.attrs[\"cosmological_simulation\"] = ds.cosmological_simulation\n    # @todo: Where is this in the yt API?\n    g.attrs[\"num_ghost_zones\"] = 0\n    # @todo: Where is this in the yt API?\n    g.attrs[\"field_ordering\"] = 0\n    # @todo: not yet supported by yt.\n    g.attrs[\"boundary_conditions\"] = np.array([0, 0, 0, 0, 0, 0], \"int32\")\n\n    if ds.cosmological_simulation:\n        g.attrs[\"current_redshift\"] = ds.current_redshift\n        g.attrs[\"omega_matter\"] = ds.omega_matter\n        g.attrs[\"omega_lambda\"] = ds.omega_lambda\n        g.attrs[\"hubble_constant\"] = ds.hubble_constant\n\n    if dataset_units is None:\n        dataset_units = {}\n\n    g = f.create_group(\"dataset_units\")\n    for u in [\"length\", \"time\", \"mass\", \"velocity\", \"magnetic\"]:\n        unit_name = u + \"_unit\"\n        if unit_name in dataset_units:\n            value, units = dataset_units[unit_name]\n        else:\n            attr = getattr(ds, unit_name)\n            value = float(attr)\n            units = str(attr.units)\n        d = g.create_dataset(unit_name, data=value)\n        d.attrs[\"unit\"] = units\n\n    ###\n    # \"field_types\" group\n    ###\n    g = f.create_group(\"field_types\")\n\n    ###\n    # \"particle_types\" group\n    ###\n    g = f.create_group(\"particle_types\")\n\n    # @todo: Particle type iterator\n    sg = g.create_group(particle_type_name)\n    sg[\"particle_type_name\"] = np.bytes_(particle_type_name)\n\n    ###\n    # root datasets -- info about the grids\n    ###\n    f[\"grid_dimensions\"] = ds.index.grid_dimensions\n    f[\"grid_left_index\"] = np.array(\n        [grid.get_global_startindex() for grid in ds.index.grids]\n    ).reshape(ds.index.grid_dimensions.shape[0], 3)\n    f[\"grid_level\"] = ds.index.grid_levels.flat\n    # @todo: Fill with proper values\n    f[\"grid_parent_id\"] = -np.ones(ds.index.grid_dimensions.shape[0])\n    f[\"grid_particle_count\"] = ds.index.grid_particle_count\n\n    ###\n    # \"data\" group -- where we should spend the most time\n    ###\n\n    g = f.create_group(\"data\")\n    for grid in ds.index.grids:\n        # add group for this grid\n        grid_group = g.create_group(f\"grid_{grid.id - grid._id_offset:010}\")\n        # add group for the particles on this grid\n        particles_group = grid_group.create_group(\"particles\")\n        particles_group.create_group(particle_type_name)\n\n    yield f\n\n    # close the file when done\n    f.close()\n"
  },
  {
    "path": "yt/utilities/hierarchy_inspection.py",
    "content": "import inspect\nfrom collections import Counter\nfrom typing import TypeVar\n\nfrom more_itertools import flatten\n\nfrom yt.data_objects.static_output import Dataset\nfrom yt.utilities.object_registries import output_type_registry\n\nT = TypeVar(\"T\")\n\n\ndef find_lowest_subclasses(candidates: list[type[T]]) -> list[type[T]]:\n    \"\"\"\n    This function takes a list of classes, and returns only the ones that are\n    are not super classes of any others in the list. i.e. the ones that are at\n    the bottom of the specified mro of classes.\n\n    Parameters\n    ----------\n    candidates : Iterable\n        An iterable object that is a collection of classes to find the lowest\n        subclass of.\n\n    Returns\n    -------\n    result : list\n        A list of classes which are not super classes for any others in\n        candidates.\n    \"\"\"\n    count = Counter(flatten(inspect.getmro(c) for c in candidates))\n    return [x for x in candidates if count[x] == 1]\n\n\ndef get_classes_with_missing_requirements() -> dict[type[Dataset], list[str]]:\n    # We need a function here rather than an global constant registry because:\n    # - computation should be delayed until needed so that the result is independent of import order\n    # - tests might (temporarily) mutate output_type_registry\n    return {\n        cls: missing\n        for cls in sorted(output_type_registry.values(), key=lambda c: c.__name__)\n        if (missing := cls._missing_load_requirements())\n    }\n"
  },
  {
    "path": "yt/utilities/initial_conditions.py",
    "content": "import numpy as np\n\n\nclass FluidOperator:\n    def apply(self, ds):\n        for g in ds.index.grids:\n            self(g)\n\n\nclass TopHatSphere(FluidOperator):\n    def __init__(self, radius, center, fields):\n        self.radius = radius\n        self.center = center\n        self.fields = fields\n\n    def __call__(self, grid, sub_select=None):\n        r = np.zeros(grid.ActiveDimensions, dtype=\"float64\")\n        for i, ax in enumerate(\"xyz\"):\n            np.add(r, (grid[ax].to_ndarray() - self.center[i]) ** 2.0, r)\n        np.sqrt(r, r)\n        ind = r <= self.radius\n        if sub_select is not None:\n            ind &= sub_select\n        for field, val in self.fields.items():\n            grid[field][ind] = val\n\n\nclass CoredSphere(FluidOperator):\n    def __init__(self, core_radius, radius, center, fields):\n        self.radius = radius\n        self.center = center\n        self.fields = fields\n        self.core_radius = core_radius\n\n    def __call__(self, grid, sub_select=None):\n        r = np.zeros(grid.ActiveDimensions, dtype=\"float64\")\n        r2 = self.radius**2\n        cr2 = self.core_radius**2\n        for i, ax in enumerate(\"xyz\"):\n            np.add(r, (grid[ax] - self.center[i]) ** 2.0, r)\n        np.maximum(r, cr2, out=r)\n        ind = r <= r2\n        if sub_select is not None:\n            ind &= sub_select\n        for field, (outer_val, inner_val) in self.fields.items():\n            val = ((r[ind] - cr2) / (r2 - cr2)) ** 0.5 * (outer_val - inner_val)\n            grid[field][ind] = val + inner_val\n\n\nclass BetaModelSphere(FluidOperator):\n    def __init__(self, beta, core_radius, radius, center, fields):\n        self.radius = radius\n        self.center = center\n        self.fields = fields\n        self.core_radius = core_radius\n        self.beta = beta\n\n    def __call__(self, grid, sub_select=None):\n        r = np.zeros(grid.ActiveDimensions, dtype=\"float64\")\n        r2 = self.radius**2\n        cr2 = self.core_radius**2\n        for i, ax in enumerate(\"xyz\"):\n            np.add(r, (grid[ax].ndarray_view() - self.center[i]) ** 2.0, r)\n        ind = r <= r2\n        if sub_select is not None:\n            ind &= sub_select\n        for field, core_val in self.fields.items():\n            val = core_val * (1.0 + r[ind] / cr2) ** (-1.5 * self.beta)\n            grid[field][ind] = val\n\n\nclass NFWModelSphere(FluidOperator):\n    def __init__(self, scale_radius, radius, center, fields):\n        self.radius = radius\n        self.center = center\n        self.fields = fields\n        self.scale_radius = scale_radius  # unitless\n\n    def __call__(self, grid, sub_select=None):\n        r = np.zeros(grid.ActiveDimensions, dtype=\"float64\")\n        for i, ax in enumerate(\"xyz\"):\n            np.add(r, (grid[ax].ndarray_view() - self.center[i]) ** 2.0, r)\n        np.sqrt(r, r)\n        ind = r <= self.radius\n        r /= self.scale_radius\n        if sub_select is not None:\n            ind &= sub_select\n        for field, scale_val in self.fields.items():\n            val = scale_val / (r[ind] * (1.0 + r[ind]) ** 2)\n            grid[field][ind] = val\n\n\nclass RandomFluctuation(FluidOperator):\n    def __init__(self, fields):\n        self.fields = fields\n\n    def __call__(self, grid, sub_select=None):\n        if sub_select is None:\n            sub_select = Ellipsis\n        rng = np.random.default_rng()\n        for field, mag in self.fields.items():\n            vals = grid[field][sub_select]\n            rc = 1.0 + (rng.random(vals.shape) - 0.5) * mag\n            grid[field][sub_select] *= rc\n"
  },
  {
    "path": "yt/utilities/io_handler.py",
    "content": "import os\nfrom collections import defaultdict\nfrom collections.abc import Iterator, Mapping\nfrom contextlib import contextmanager\nfrom functools import _make_key, lru_cache\n\nimport numpy as np\n\nfrom yt._typing import FieldKey, ParticleCoordinateTuple\nfrom yt.geometry.selection_routines import GridSelector\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nio_registry = {}\n\nuse_caching = 0\n\n\ndef _make_io_key(args, *_args, **kwargs):\n    self, obj, field, ctx = args\n    # Ignore self because we have a self-specific cache\n    return _make_key((obj.id, field), *_args, **kwargs)\n\n\nclass BaseIOHandler:\n    _vector_fields: dict[str, int] = {}\n    _dataset_type: str\n    _particle_reader = False\n    _cache_on = False\n    _misses = 0\n    _hits = 0\n\n    def __init_subclass__(cls, *args, **kwargs):\n        super().__init_subclass__(*args, **kwargs)\n        if hasattr(cls, \"_dataset_type\"):\n            io_registry[cls._dataset_type] = cls\n        if use_caching and hasattr(cls, \"_read_obj_field\"):\n            cls._read_obj_field = lru_cache(\n                maxsize=use_caching, typed=True, make_key=_make_io_key\n            )(cls._read_obj_field)\n\n    def __init__(self, ds):\n        self.queue = defaultdict(dict)\n        self.ds = ds\n        self._last_selector_id = None\n        self._last_selector_counts = None\n        self._array_fields = {}\n        self._cached_fields = {}\n\n    # We need a function for reading a list of sets\n    # and a function for *popping* from a queue all the appropriate sets\n    @contextmanager\n    def preload(self, chunk, fields: list[FieldKey], max_size):\n        yield self\n\n    def peek(self, grid, field):\n        return self.queue[grid.id].get(field, None)\n\n    def push(self, grid, field, data):\n        if grid.id in self.queue and field in self.queue[grid.id]:\n            raise ValueError\n        self.queue[grid][field] = data\n\n    def _field_in_backup(self, grid, backup_file, field_name):\n        if os.path.exists(backup_file):\n            fhandle = h5py.File(backup_file, mode=\"r\")\n            g = fhandle[\"data\"]\n            grid_group = g[f\"grid_{grid.id - grid._id_offset:010}\"]\n            if field_name in grid_group:\n                return_val = True\n            else:\n                return_val = False\n            fhandle.close()\n            return return_val\n        else:\n            return False\n\n    def _read_data_set(self, grid, field):\n        # check backup file first. if field not found,\n        # call frontend-specific io method\n        backup_filename = grid.ds.backup_filename\n        if not os.path.exists(backup_filename):\n            return self._read_data(grid, field)\n        elif self._field_in_backup(grid, backup_filename, field):\n            fhandle = h5py.File(backup_filename, mode=\"r\")\n            g = fhandle[\"data\"]\n            grid_group = g[f\"grid_{grid.id - grid._id_offset:010}\"]\n            data = grid_group[field][:]\n            fhandle.close()\n            return data\n        else:\n            return self._read_data(grid, field)\n\n    # Now we define our interface\n    def _read_data(self, grid, field):\n        pass\n\n    def _read_fluid_selection(\n        self, chunks, selector, fields: list[FieldKey], size\n    ) -> Mapping[FieldKey, np.ndarray]:\n        # This function has an interesting history.  It previously was mandate\n        # to be defined by all of the subclasses.  But, to avoid having to\n        # rewrite a whole bunch of IO handlers all at once, and to allow a\n        # better abstraction for grid-based frontends, we're now defining it in\n        # the base class.\n        rv = {}\n        nodal_fields = []\n        for field in fields:\n            finfo = self.ds.field_info[field]\n            nodal_flag = finfo.nodal_flag\n            if np.any(nodal_flag):\n                num_nodes = 2 ** sum(nodal_flag)\n                rv[field] = np.empty((size, num_nodes), dtype=\"=f8\")\n                nodal_fields.append(field)\n            else:\n                rv[field] = np.empty(size, dtype=\"=f8\")\n        ind = dict.fromkeys(fields, 0)\n        for field, obj, data in self.io_iter(chunks, fields):\n            if data is None:\n                continue\n            if isinstance(selector, GridSelector) and field not in nodal_fields:\n                ind[field] += data.size\n                rv[field] = data.copy()\n            else:\n                ind[field] += obj.select(selector, data, rv[field], ind[field])\n        return rv\n\n    def io_iter(self, chunks, fields: list[FieldKey]):\n        raise NotImplementedError(\n            \"subclassing Dataset.io_iter this is required in order to use the default \"\n            \"implementation of Dataset._read_fluid_selection. \"\n            \"Custom implementations of the latter may not rely on this method.\"\n        )\n\n    def _read_data_slice(self, grid, field, axis, coord):\n        sl = [slice(None), slice(None), slice(None)]\n        sl[axis] = slice(coord, coord + 1)\n        tr = self._read_data_set(grid, field)[tuple(sl)]\n        if tr.dtype == \"float32\":\n            tr = tr.astype(\"float64\")\n        return tr\n\n    def _read_field_names(self, grid):\n        pass\n\n    @property\n    def _read_exception(self):\n        return None\n\n    def _read_chunk_data(self, chunk, fields):\n        return {}\n\n    def _read_particle_coords(\n        self, chunks, ptf: defaultdict[str, list[str]]\n    ) -> Iterator[ParticleCoordinateTuple]:\n        # An iterator that yields particle coordinates for each chunk by particle\n        # type. Must be implemented by each frontend. Must yield a tuple of\n        # (particle type, xyz, hsml) by chunk. If the frontend does not have\n        # a smoothing length, yield (particle type, xyz, 0.0)\n        raise NotImplementedError\n\n    def _read_particle_data_file(self, data_file, ptf, selector=None):\n        # each frontend needs to implement this: read from a data_file object\n        # and return a dict of fields for that data_file\n        raise NotImplementedError\n\n    def _read_particle_selection(\n        self, chunks, selector, fields: list[FieldKey]\n    ) -> dict[FieldKey, np.ndarray]:\n        data: dict[FieldKey, list[np.ndarray]] = {}\n\n        # Initialize containers for tracking particle, field information\n        # ptf (particle field types) maps particle type to list of on-disk fields to read\n        # field_maps stores fields, accounting for field unions\n        ptf: defaultdict[str, list[str]] = defaultdict(list)\n        field_maps: defaultdict[FieldKey, list[FieldKey]] = defaultdict(list)\n\n        # We first need a set of masks for each particle type\n        chunks = list(chunks)\n        unions = self.ds.particle_unions\n        # What we need is a mapping from particle types to return types\n        for field in fields:\n            ftype, fname = field\n            # We should add a check for p.fparticle_unions or something here\n            if ftype in unions:\n                for pt in unions[ftype]:\n                    ptf[pt].append(fname)\n                    field_maps[pt, fname].append(field)\n            else:\n                ptf[ftype].append(fname)\n                field_maps[field].append(field)\n            data[field] = []\n\n        # Now we read.\n        for field_r, vals in self._read_particle_fields(chunks, ptf, selector):\n            # Note that we now need to check the mappings\n            for field_f in field_maps[field_r]:\n                data[field_f].append(vals)\n\n        rv: dict[FieldKey, np.ndarray] = {}  # the return dictionary\n        fields = list(data.keys())\n        for field_f in fields:\n            # We need to ensure the arrays have the right shape if there are no\n            # particles in them.\n            total = sum(_.size for _ in data[field_f])\n            if total > 0:\n                vals = data.pop(field_f)\n                # note: numpy.concatenate has a dtype argument that would avoid\n                # a copy using .astype(...), available in numpy>=1.20\n                rv[field_f] = np.concatenate(vals, axis=0).astype(\"float64\")\n            else:\n                shape = [0]\n                if field_f[1] in self._vector_fields:\n                    shape.append(self._vector_fields[field_f[1]])\n                elif field_f[1] in self._array_fields:\n                    shape.append(self._array_fields[field_f[1]])\n                rv[field_f] = np.empty(shape, dtype=\"float64\")\n        return rv\n\n    def _read_particle_fields(self, chunks, ptf, selector):\n        # Now we have all the sizes, and we can allocate\n        for data_file in self._sorted_chunk_iterator(chunks):\n            data_file_data = self._read_particle_data_file(data_file, ptf, selector)\n            # temporary trickery so it's still an iterator, need to adjust\n            # the io_handler.BaseIOHandler.read_particle_selection() method\n            # to not use an iterator.\n            yield from data_file_data.items()\n\n    @staticmethod\n    def _get_data_files(chunks):\n        chunks = list(chunks)\n        data_files = set()\n        for chunk in chunks:\n            for obj in chunk.objs:\n                data_files.update(obj.data_files)\n        return data_files\n\n    def _sorted_chunk_iterator(self, chunks):\n        data_files = self._get_data_files(chunks)\n        yield from sorted(data_files, key=lambda x: (x.filename, x.start))\n\n\n# As a note: we don't *actually* want this to be how it is forever.  There's no\n# reason we need to have the fluid and particle IO handlers separated.  But,\n# for keeping track of which frontend is which, this is a useful abstraction.\nclass BaseParticleIOHandler(BaseIOHandler):\n    pass\n\n\nclass IOHandlerExtracted(BaseIOHandler):\n    _dataset_type = \"extracted\"\n\n    def _read_data_set(self, grid, field):\n        return grid.base_grid[field] / grid.base_grid.convert(field)\n\n    def _read_data_slice(self, grid, field, axis, coord):\n        sl = [slice(None), slice(None), slice(None)]\n        sl[axis] = slice(coord, coord + 1)\n        return grid.base_grid[field][tuple(sl)] / grid.base_grid.convert(field)\n"
  },
  {
    "path": "yt/utilities/lib/__init__.py",
    "content": "\"\"\"Blank __init__\"\"\"\n"
  },
  {
    "path": "yt/utilities/lib/_octree_raytracing.hpp",
    "content": "#include <vector>\n#include <algorithm>\n#include <memory>\n#include <array>\n#include <iostream>\n#include <cassert>\n#include <math.h>\n#include <random>\n#include <string>\n#include <fstream>\n\n\ntypedef double F;\nconst int Ndim = 3;\n\n/*  A simple node struct that contains a key and a fixed number of children,\n    typically Nchildren = 2**Ndim\n */\ntemplate <typename keyType, int Nchildren>\nstruct GenericNode\n{\n    // Tree data\n    GenericNode<keyType, Nchildren>** children = nullptr;\n    GenericNode<keyType, Nchildren>* parent = nullptr;\n\n    // Node data\n    keyType key;\n    int level = 0;\n    bool terminal = false;\n    int index = -1;\n};\n\ntemplate <typename keyType>\nstruct RayInfo {\n    std::vector<keyType> keys;\n    std::vector<F> t;\n\n    RayInfo() {};\n    RayInfo(int N) {\n        if (N > 0) {\n            keys.reserve(N);\n            t.reserve(2*N);\n        }\n    }\n};\n\nstruct Ray {\n    std::array<F, Ndim> o; // Origin\n    std::array<F, Ndim> d; // Direction\n    F tmin = -1e99;\n    F tmax = 1e99;\n\n    Ray(const F* _o, const F* _d, const F _tmin, const F _tmax) : tmin(_tmin), tmax(_tmax) {\n        for (auto idim = 0; idim < Ndim; ++idim) {\n            o[idim] = _o[idim];\n        }\n        F dd = 0;\n        for (auto idim = 0; idim < Ndim; ++idim) {\n            dd += _d[idim] * _d[idim];\n        }\n        dd = sqrt(dd);\n        for (auto idim = 0; idim < Ndim; ++idim) {\n            d[idim] = _d[idim] / dd;\n        }\n    };\n\n    Ray() {};\n};\n\n/*  Converts an array of integer position into a flattened index.\n    The fast varying index is the last one.\n */\ninline unsigned char ijk2iflat(const std::array<bool, Ndim> ijk) {\n    unsigned char iflat = 0;\n    for (auto i : ijk) {\n        iflat += i;\n        iflat <<= 1;\n    }\n    return iflat >> 1;\n};\n\n/*  Converts a flattened index into an array of integer position.\n    The fast varying index is the last one.\n*/\ninline std::array<bool, Ndim> iflat2ijk(unsigned char iflat) {\n    std::array<bool, Ndim> ijk;\n    for (auto idim = Ndim-1; idim >= 0; --idim) {\n        ijk[idim] = iflat & 0b1;\n        iflat >>= 1;\n    }\n    return ijk;\n};\n\n/*  A class to build an octree and cast rays through it. */\ntemplate <class keyType>\nclass Octree {\n    typedef GenericNode<keyType, (1<<Ndim)> Node;\n    typedef std::vector<keyType> keyVector;\n    typedef std::array<F, Ndim> Pos;\n    typedef std::array<int, Ndim> iPos;\n    typedef std::array<unsigned char, Ndim> ucPos;\n\nprivate:\n    const unsigned char twotondim;\n    const int maxDepth;\n    Pos size;\n    Pos DLE; // Domain left edge\n    Pos DRE; // Domain right edge\n    Node* root;\n    int global_index = 0;\n\npublic:\n    Octree(int _maxDepth, F* _DLE, F* _DRE) :\n            twotondim (1<<Ndim),\n            maxDepth(_maxDepth) {\n        for (auto idim = 0; idim < Ndim; ++idim) {\n            DRE[idim] = _DRE[idim];\n            DLE[idim] = _DLE[idim];\n            size[idim] = _DRE[idim] - _DLE[idim];\n        }\n\n        root = new Node();\n        // Allocate root's children\n        root->children = (Node**) malloc(sizeof(Node*)*twotondim);\n        for (auto i = 0; i < twotondim; ++i) root->children = nullptr;\n    }\n\n\n    ~Octree() {\n        recursive_remove_node(root);\n    };\n\n    /*\n        Insert a new node in the tree.\n    */\n    Node* insert_node(const iPos ipos, const int lvl, keyType key) {\n        assert(lvl <= maxDepth);\n\n        // std::cerr << \"Inserting at level: \" << lvl << \"/\" << maxDepth << std::endl;\n        // this is 0b100..., where the 1 is at position maxDepth\n        uint64_t mask = 1<<(maxDepth - 1);\n\n        iPos ijk = ipos;\n        std::array<bool, Ndim> bitMask;\n\n        Node* node = root;\n        Node* child = nullptr;\n\n        // Go down the tree\n        for (auto ibit = maxDepth-1; ibit >= maxDepth - lvl; --ibit) {\n            // Find children based on bits\n            for (auto idim = 0; idim < Ndim; ++idim) {\n                bitMask[idim] = ijk[idim] & mask;\n            }\n            mask >>= 1;\n            auto iflat = ijk2iflat(bitMask);\n\n            // Create child if it does not exist yet\n            child = create_get_node(node, iflat);\n            node = child;\n        }\n\n        // Mark last node as terminal\n        node->terminal = true;\n        node->key = key;\n\n        return node;\n    }\n\n    Node* insert_node(const int* ipos, const int lvl, keyType key) {\n        std::array<int, Ndim> ipos_as_arr;\n        for (auto idim = 0; idim < Ndim; ++idim) ipos_as_arr[idim] = ipos[idim];\n        return insert_node(ipos_as_arr, lvl, key);\n    }\n\n    void insert_node_no_ret(const int* ipos, const int lvl, keyType key) {\n        insert_node(ipos, lvl, key);\n    }\n\n    // Perform multiple ray cast\n    RayInfo<keyType>** cast_rays(const F *origins, const F *directions, const int Nrays) {\n        RayInfo<keyType> **ray_infos = (RayInfo<keyType>**) malloc(sizeof(RayInfo<keyType>*)*Nrays);\n        int Nfound = 0;\n        #pragma omp parallel for shared(ray_infos, Nfound) schedule(static, 100)\n        for (auto i = 0; i < Nrays; ++i) {\n            std::vector<F> tList;\n            ray_infos[i] = new RayInfo<keyType>(Nfound);\n            auto ri = ray_infos[i];\n            Ray r(&origins[3*i], &directions[3*i], -1e99, 1e99);\n            cast_ray(&r, ri->keys, ri->t);\n\n            // Keep track of the number of cells hit to preallocate the next ray info container\n            Nfound = std::max(Nfound, (int) ri->keys.size());\n        }\n        return ray_infos;\n    }\n\n    // Perform single ray tracing\n    void cast_ray(Ray *r, keyVector &keyList, std::vector<F> &tList) {\n        // Boolean mask for direction\n        unsigned char a = 0;\n        unsigned char bmask = twotondim >> 1;\n\n        // Put ray in positive direction and store info in bitmask \"a\"\n        for (auto idim = 0; idim < Ndim; ++idim) {\n            if (r->d[idim] < 0.0) {\n                r->o[idim] = size[idim]-r->o[idim];\n                r->d[idim] = -r->d[idim];\n                a |= bmask;\n            }\n            bmask >>= 1;\n        }\n\n        // Compute intersection points\n        Pos t0, t1;\n        for (auto idim = 0; idim < Ndim; ++idim){\n            t0[idim] = (DLE[idim] - r->o[idim]) / r->d[idim];\n            t1[idim] = (DRE[idim] - r->o[idim]) / r->d[idim];\n        }\n\n        // If entry point is smaller than exit point, find path in octree\n        if (*std::max_element(t0.begin(), t0.end()) < *std::min_element(t1.begin(), t1.end()))\n            proc_subtree(t0[0], t0[1], t0[2],\n                         t1[0], t1[1], t1[2],\n                         root, a, keyList, tList);\n    }\n\n    void cast_ray(double* o, double* d, keyVector &keyList, std::vector<F> &tList) {\n        Ray r(o, d, -1e99, 1e99);\n        cast_ray(&r, keyList, tList);\n    }\n\nprivate:\n\n    /*\n        Upsert a node as a child of another.\n\n        This will create a new node as a child of the current one, or return\n        an existing one if it already exists\n    */\n    Node* create_get_node(Node* parent, const int iflat) {\n        // Create children if not already existing\n        if (parent->children == nullptr) {\n            parent->children = (Node**) malloc(sizeof(Node*)*twotondim);\n            for (auto i = 0; i < twotondim; ++i) parent->children[i] = nullptr;\n        }\n\n        if (parent->children[iflat] == nullptr) {\n            Node* node = new Node();\n            node->level = parent->level + 1;\n            node->index = global_index;\n            node->parent = parent;\n            ++global_index;\n\n            parent->children[iflat] = node;\n        }\n        return parent->children[iflat];\n    }\n\n    /*\n        Recursively free memory.\n    */\n    void recursive_remove_node(Node* node) {\n        if (node->children) {\n            for (auto i = 0; i < twotondim; ++i) {\n                auto child = node->children[i];\n                if (child) {\n                    recursive_remove_node(child);\n                }\n                delete child;\n            }\n            free(node->children);\n        }\n    }\n\n    /*\n        Traverse the tree, assuming that the ray intersects\n        From http://wscg.zcu.cz/wscg2000/Papers_2000/X31.pdf\n    */\n    void proc_subtree(const F tx0, const F ty0, const F tz0,\n                      const F tx1, const F ty1, const F tz1,\n                      const Node *n, const unsigned char a,\n                      keyVector &keyList, std::vector<F> &tList, int lvl=0) {\n        // Check if exit face is not in our back\n        if (tx1 < 0 || ty1 < 0 || tz1 < 0) return;\n\n        // Exit if the node is null (happens if it hasn't been added to the tree)\n        if (!n) return;\n\n        // Process leaf node\n        if (n->terminal) {\n            keyList.push_back(n->key);\n            // Push entry & exit t\n            tList.push_back(std::max(std::max(tx0, ty0), tz0));\n            tList.push_back(std::min(std::min(tx1, ty1), tz1));\n            assert(n->children == nullptr);\n            return;\n        }\n\n        // Early break for leafs without children that aren't terminal\n        if (n->children == nullptr) return;\n\n        // Compute middle intersection\n        F txm, tym, tzm;\n        txm = (tx0 + tx1) * 0.5;\n        tym = (ty0 + ty1) * 0.5;\n        tzm = (tz0 + tz1) * 0.5;\n\n        unsigned char iNode = first_node(tx0, ty0, tz0, txm, tym, tzm);\n\n        // Iterate over children\n        do {\n\n            switch (iNode)\n            {\n            case 0:\n                proc_subtree(tx0, ty0, tz0, txm, tym, tzm, n->children[a], a, keyList, tList, lvl+1);\n                iNode = next_node(txm, tym, tzm, 4, 2, 1);\n                break;\n            case 1:\n                proc_subtree(tx0, ty0, tzm, txm, tym, tz1, n->children[1^a], a, keyList, tList, lvl+1);\n                iNode = next_node(txm, tym, tz1, 5, 3, 8);\n                break;\n            case 2:\n                proc_subtree(tx0, tym, tz0, txm, ty1, tzm, n->children[2^a], a, keyList, tList, lvl+1);\n                iNode = next_node(txm, ty1, tzm, 6, 8, 3);\n                break;\n            case 3:\n                proc_subtree(tx0, tym, tzm, txm, ty1, tz1, n->children[3^a], a, keyList, tList, lvl+1);\n                iNode = next_node(txm, ty1, tz1, 7, 8, 8);\n                break;\n            case 4:\n                proc_subtree(txm, ty0, tz0, tx1, tym, tzm, n->children[4^a], a, keyList, tList, lvl+1);\n                iNode = next_node(tx1, tym, tzm, 8, 6, 5);\n                break;\n            case 5:\n                proc_subtree(txm, ty0, tzm, tx1, tym, tz1, n->children[5^a], a, keyList, tList, lvl+1);\n                iNode = next_node(tx1, tym, tz1, 8, 7, 8);\n                break;\n            case 6:\n                proc_subtree(txm, tym, tz0, tx1, ty1, tzm, n->children[6^a], a, keyList, tList, lvl+1);\n                iNode = next_node(tx1, ty1, tzm, 8, 8, 7);\n                break;\n            case 7:\n                proc_subtree(txm, tym, tzm, tx1, ty1, tz1, n->children[7^a], a, keyList, tList, lvl+1);\n                iNode = 8;\n                break;\n            }\n        } while (iNode < twotondim);\n    }\n\n    // From \"An Efficient Parametric Algorithm for Octree Traversal\" by Revelles, Urena, & Lastra\n    inline unsigned char first_node(const F tx0, const F ty0, const F tz0,\n                                    const F txm, const F tym, const F tzm) {\n        unsigned char index = 0;\n        if (tx0 >= std::max(ty0, tz0)) {         // enters YZ plane\n            if (tym < tx0) index |= 0b010;\n            if (tzm < tx0) index |= 0b001;\n        } else if (ty0 >= std::max(tx0, tz0))  { // enters XZ plane\n            if (txm < ty0) index |= 0b100;\n            if (tzm < ty0) index |= 0b001;\n        } else {                                 // enters XY plane\n            if (txm < tz0) index |= 0b100;\n            if (tym < tz0) index |= 0b010;\n        }\n        return index;\n    }\n    // From \"An Efficient Parametric Algorithm for Octree Traversal\" by Revelles, Urena, & Lastra\n    inline unsigned char next_node(const F tx, const F ty, const F tz,\n                                   const uint8_t ix, const uint8_t iy, const uint8_t iz) {\n        if(tx < std::min(ty, tz)) {         // YZ plane\n            return ix;\n        } else if (ty < std::min(tx, tz)) { // XZ plane\n            return iy;\n        } else {                            // XY plane\n            return iz;\n        }\n    }\n};\n"
  },
  {
    "path": "yt/utilities/lib/_octree_raytracing.pxd",
    "content": "\"\"\"This is a wrapper around the C++ class to efficiently cast rays into an octree.\nIt relies on the seminal paper by  J. Revelles,, C.Ureña and M.Lastra.\n\"\"\"\n\n\ncimport numpy as np\n\nimport numpy as np\n\ncimport cython\nfrom libcpp.vector cimport vector\n\nfrom cython.parallel import parallel, prange\n\nfrom libc.stdlib cimport free, malloc\n\nfrom .grid_traversal cimport sampler_function\nfrom .image_samplers cimport ImageAccumulator, ImageSampler\nfrom .partitioned_grid cimport PartitionedGrid\nfrom .volume_container cimport VolumeContainer\n\n\ncdef extern from \"_octree_raytracing.hpp\":\n    cdef cppclass RayInfo[T]:\n        vector[T] keys\n        vector[double] t\n\n    cdef cppclass Octree[T] nogil:\n        Octree(int depth, double* LE, double* RE)\n        void insert_node_no_ret(const int* ipos, const int lvl, T key)\n        void cast_ray(double* origins, double* directions, vector[T] keyList, vector[double] tList)\n\ncdef class _OctreeRayTracing:\n    cdef Octree[int]* oct\n    cdef int depth\n"
  },
  {
    "path": "yt/utilities/lib/_octree_raytracing.pyx",
    "content": "# distutils: language = c++\n# distutils: extra_compile_args = CPP14_FLAG OMP_ARGS\n\"\"\"This is a wrapper around the C++ class to efficiently cast rays into an octree.\nIt relies on the seminal paper by  J. Revelles,, C.Ureña and M.Lastra.\n\"\"\"\n\n\ncimport numpy as np\nimport numpy as np\n\ncimport cython\n\n\ncdef class _OctreeRayTracing:\n    def __init__(self, np.ndarray LE, np.ndarray RE, int depth):\n        cdef double* LE_ptr = <double *>LE.data\n        cdef double* RE_ptr = <double *>RE.data\n        self.oct = new Octree[int](depth, LE_ptr, RE_ptr)\n        self.depth = depth\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def add_nodes(self, int[:, :] ipos_view, int[:] lvl_view, int[:] key):\n        cdef int i\n        cdef int ii[3]\n\n        for i in range(len(key)):\n            ii[0] = ipos_view[i, 0]\n            ii[1] = ipos_view[i, 1]\n            ii[2] = ipos_view[i, 2]\n            self.oct.insert_node_no_ret(ii, lvl_view[i], <int> key[i])\n\n    def __dealloc__(self):\n        del self.oct\n"
  },
  {
    "path": "yt/utilities/lib/allocation_container.pxd",
    "content": "\"\"\"\nAn allocation container and memory pool\n\n\n\n\"\"\"\n\n\ncimport numpy as np\nfrom libc.stdlib cimport free, malloc, realloc\n\n\ncdef struct AllocationContainer:\n    np.uint64_t n\n    np.uint64_t n_assigned\n    np.uint64_t offset\n    np.int64_t con_id # container id\n    void *my_objs\n\ncdef class ObjectPool:\n    cdef public np.uint64_t itemsize\n    cdef np.uint64_t n_con\n    cdef AllocationContainer* containers\n    cdef void allocate_objs(self, int n_objs, np.int64_t con_id = ?) except *\n    cdef void setup_objs(self, void *obj, np.uint64_t count,\n                         np.uint64_t offset, np.int64_t con_id)\n    cdef void teardown_objs(self, void *obj, np.uint64_t n, np.uint64_t offset,\n                           np.int64_t con_id)\n"
  },
  {
    "path": "yt/utilities/lib/allocation_container.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nAn allocation container and memory pool\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\nimport numpy as np\n\n\ncdef class ObjectPool:\n    def __cinit__(self):\n        \"\"\"This class is *not* meant to be initialized directly, but instead\n        through subclasses.  Those subclasses need to implement at a minimum\n        the setting of itemsize, but can optionally also implement setup_objs\n        and teardown_objs to either set default values or initialize additional\n        pointers and values, and then free them.\"\"\"\n        self.containers = NULL\n        self.n_con = 0\n        self.itemsize = -1 # Never use the base class\n\n    def __dealloc__(self):\n        cdef int i\n        cdef AllocationContainer *obj\n        for i in range(self.n_con):\n            obj = &self.containers[i]\n            self.teardown_objs(obj.my_objs, obj.n, obj.offset, obj.con_id)\n        if self.containers != NULL:\n            free(self.containers)\n\n    def __getitem__(self, int i):\n        \"\"\"This returns an array view (if possible and implemented in a\n        subclass) on the pool of objects specified by i.\"\"\"\n        return self._con_to_array(i)\n\n    def __len__(self):\n        return self.n_con\n\n    def append(self, int n_objs, np.int64_t con_id = -1):\n        \"\"\"This allocates a new batch of n_objs objects, with the container id\n        specified as con_id.  Return value is a view on them.\"\"\"\n        self.allocate_objs(n_objs, con_id)\n        return self[self.n_con - 1]\n\n    cdef void allocate_objs(self, int n_objs, np.int64_t con_id = -1) except *:\n        cdef AllocationContainer *n_cont\n        cdef AllocationContainer *prev\n        self.containers = <AllocationContainer*> realloc(\n              self.containers,\n              sizeof(AllocationContainer) * (self.n_con + 1))\n        n_cont = &self.containers[self.n_con]\n        if self.n_con == 0:\n            n_cont.offset = 0\n        else:\n            prev = &self.containers[self.n_con - 1]\n            n_cont.offset = prev.offset + prev.n\n        self.n_con += 1\n        n_cont.my_objs = malloc(self.itemsize * n_objs)\n        if n_cont.my_objs == NULL:\n            raise MemoryError\n        n_cont.n = n_objs\n        n_cont.n_assigned = 0\n        n_cont.con_id = con_id\n        self.setup_objs(n_cont.my_objs, n_objs, n_cont.offset, n_cont.con_id)\n\n    cdef void setup_objs(self, void *obj, np.uint64_t count,\n                         np.uint64_t offset, np.int64_t con_id):\n        \"\"\"This can be overridden in the subclass, where it can initialize any\n        of the allocated objects.\"\"\"\n        pass\n\n    cdef void teardown_objs(self, void *obj, np.uint64_t n, np.uint64_t offset,\n                           np.int64_t con_id):\n        # We assume that these are all allocated and have no sub-allocations\n        \"\"\"If overridden, additional behavior can be provided during teardown\n        of allocations.  For instance, if you allocate some memory on each of\n        the allocated objects.\"\"\"\n        if obj != NULL:\n            free(obj)\n\n    def to_arrays(self):\n        rv = []\n        cdef int i\n        for i in range(self.n_con):\n            rv.append(self._con_to_array(i))\n        return rv\n\n    def _con_to_array(self, int i):\n        \"\"\"This has to be implemented in a subclass, and should return an\n        appropriate np.asarray() of a memoryview of self.my_objs.\"\"\"\n        raise NotImplementedError\n\ncdef class BitmaskPool(ObjectPool):\n    def __cinit__(self):\n        \"\"\"This is an example implementation of object pools for bitmasks\n        (uint8) arrays.  It lets you reasonably quickly allocate a set of\n        uint8 arrays which can be accessed and modified, and then virtually\n        append to that.\"\"\"\n        # Base class will ALSO be called\n        self.itemsize = sizeof(np.uint8_t)\n\n    cdef void setup_objs(self, void *obj, np.uint64_t n, np.uint64_t offset,\n                         np.int64_t con_id):\n        cdef np.uint64_t i\n        cdef np.uint8_t *mask = <np.uint8_t *> obj\n        for i in range(n):\n            mask[i] = 0\n\n    def _con_to_array(self, int i):\n        cdef AllocationContainer *obj = &self.containers[i]\n        cdef np.uint8_t[:] o = <np.uint8_t[:obj.n]> (<np.uint8_t*> obj.my_objs)\n        rv = np.asarray(o)\n        return rv\n"
  },
  {
    "path": "yt/utilities/lib/alt_ray_tracers.pyx",
    "content": "# distutils: libraries = STD_LIBS\n\"\"\"\n\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport libc.math as math\ncimport numpy as np\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef _cyl2cart(np.ndarray[np.float64_t, ndim=2] x):\n    \"\"\"Converts points in cylindrical coordinates to cartesian points.\"\"\"\n    # NOTE this should be removed once the coord interface comes online\n    cdef int i, I\n    cdef np.ndarray[np.float64_t, ndim=2] xcart\n    I = x.shape[0]\n    xcart = np.empty((I, x.shape[1]), dtype='float64')\n    for i in range(I):\n        xcart[i,0] = x[i,0] * math.cos(x[i,2])\n        xcart[i,1] = x[i,0] * math.sin(x[i,2])\n        xcart[i,2] = x[i,1]\n    return xcart\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef _cart_intersect(np.ndarray[np.float64_t, ndim=1] a,\n                    np.ndarray[np.float64_t, ndim=1] b,\n                    np.ndarray[np.float64_t, ndim=2] c,\n                    np.ndarray[np.float64_t, ndim=2] d):\n    \"\"\"Finds the times and locations of the lines defined by a->b and\n    c->d in cartesian space.  a and b must be 1d points.  c and d must\n    be 2d arrays of equal shape whose second dim is the same length as\n    a and b.\n    \"\"\"\n    cdef int i, I\n    cdef np.ndarray[np.float64_t, ndim=1] r, s, t\n    cdef np.ndarray[np.float64_t, ndim=2] loc\n    I = c.shape[0]\n    shape = (I, c.shape[1])\n    t = np.empty(I, dtype='float64')\n    loc = np.empty(shape, dtype='float64')\n\n    r = b - a\n    for i in range(I):\n        s = d[i] - c[i]\n        t[i] = (np.cross(c[i] - a, s).sum()) / (np.cross(r, s).sum())\n        loc[i] = a + t[i]*r\n    return t, loc\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef cylindrical_ray_trace(np.ndarray[np.float64_t, ndim=1] p1,\n                          np.ndarray[np.float64_t, ndim=1] p2,\n                          np.ndarray[np.float64_t, ndim=2] left_edges,\n                          np.ndarray[np.float64_t, ndim=2] right_edges):\n    \"\"\"Computes straight (cartesian) rays being traced through a\n    cylindrical geometry.\n\n    Parameters\n    ----------\n    p1 : length 3 float ndarray\n        start point for ray\n    p2 : length 3 float ndarray\n        stop point for ray\n    left_edges : 2d ndarray\n        left edges of grid cells\n    right_edges : 2d ndarray\n        right edges of grid cells\n\n    Returns\n    -------\n    t : 1d float ndarray\n        ray parametric time on range [0,1]\n    s : 1d float ndarray\n        ray parametric distance on range [0,len(ray)]\n    rztheta : 2d float ndarray\n        ray grid cell intersections in cylindrical coordinates\n    inds : 1d int ndarray\n        indexes into the grid cells which the ray crosses in order.\n\n    \"\"\"\n    cdef np.int64_t i, I\n    cdef np.float64_t a, b, bsqrd, twoa\n    cdef np.ndarray[np.float64_t, ndim=1] p1cart, p2cart, dpcart, t, s, \\\n                                          rleft, rright, zleft, zright, \\\n                                          cleft, cright, thetaleft, thetaright, \\\n                                          tmleft, tpleft, tmright, tpright, tsect\n    cdef np.ndarray[np.int64_t, ndim=1, cast=True] inds, tinds, sinds\n    cdef np.ndarray[np.float64_t, ndim=2] xyz, rztheta, ptemp, b1, b2, dsect\n\n    # set up  points\n    ptemp = np.array([p1, p2])\n    ptemp = _cyl2cart(ptemp)\n    p1cart = ptemp[0]\n    p2cart = ptemp[1]\n    dpcart = p2cart - p1cart\n\n    # set up components\n    rleft = left_edges[:,0]\n    rright = right_edges[:,0]\n    zleft = left_edges[:,1]\n    zright = right_edges[:,1]\n\n    a = dpcart[0] * dpcart[0] + dpcart[1] * dpcart[1]\n    b = 2 * dpcart[0] * p1cart[0] + 2 * dpcart[1] * p1cart[1]\n    cleft = p1cart[0] * p1cart[0] + p1cart[1] * p1cart[1] - rleft * rleft\n    cright = p1cart[0] * p1cart[0] + p1cart[1] * p1cart[1] - rright * rright\n    twoa = 2 * a\n    bsqrd = b * b\n\n    # Compute positive and negative times and associated masks\n    I = np.intp(left_edges.shape[0])\n    tmleft = np.empty(I, dtype='float64')\n    tpleft = np.empty(I, dtype='float64')\n    tmright = np.empty(I, dtype='float64')\n    tpright = np.empty(I, dtype='float64')\n    for i in range(I):\n        tmleft[i] = (-b - math.sqrt(bsqrd - 4*a*cleft[i])) / twoa\n        tpleft[i] = (-b + math.sqrt(bsqrd - 4*a*cleft[i])) / twoa\n        tmright[i] = (-b - math.sqrt(bsqrd - 4*a*cright[i])) / twoa\n        tpright[i] = (-b + math.sqrt(bsqrd - 4*a*cright[i])) / twoa\n\n    tmmright = np.logical_and(~np.isnan(tmright), rright <= p1[0])\n    tpmright = np.logical_and(~np.isnan(tpright), rright <= p2[0])\n\n    tmmleft = np.logical_and(~np.isnan(tmleft), rleft <= p1[0])\n    tpmleft = np.logical_and(~np.isnan(tpleft), rleft <= p2[0])\n\n    # compute first cut of indexes and thetas, which\n    # have been filtered by those values for which intersection\n    # times are impossible (see above masks). Note that this is\n    # still independent of z.\n    inds = np.unique(np.concatenate([np.argwhere(tmmleft).flat,\n                                     np.argwhere(tpmleft).flat,\n                                     np.argwhere(tmmright).flat,\n                                     np.argwhere(tpmright).flat,]))\n    if 0 == inds.shape[0]:\n        inds = np.arange(np.intp(I))\n        thetaleft = np.empty(I)\n        thetaleft.fill(p1[2])\n        thetaright = np.empty(I)\n        thetaright.fill(p2[2])\n    else:\n        rleft = rleft[inds]\n        rright = rright[inds]\n\n        zleft = zleft[inds]\n        zright = zright[inds]\n\n        thetaleft = np.arctan2((p1cart[1] + tmleft[inds]*dpcart[1]),\n                               (p1cart[0] + tmleft[inds]*dpcart[0]))\n        nans = np.isnan(thetaleft)\n        thetaleft[nans] = np.arctan2((p1cart[1] + tpleft[inds[nans]]*dpcart[1]),\n                                     (p1cart[0] + tpleft[inds[nans]]*dpcart[0]))\n        thetaright = np.arctan2((p1cart[1] + tmright[inds]*dpcart[1]),\n                                (p1cart[0] + tmright[inds]*dpcart[0]))\n        nans = np.isnan(thetaright)\n        thetaright[nans] = np.arctan2((p1cart[1] + tpright[inds[nans]]*dpcart[1]),\n                                      (p1cart[0] + tpright[inds[nans]]*dpcart[0]))\n\n    # Set up the cell boundary arrays\n    b1 = np.concatenate([[rleft,  zright, thetaleft],\n                         [rleft,  zleft,  thetaleft],\n                         [rleft,  zleft,  thetaleft],\n                         [rright, zleft,  thetaright],\n                         [rleft,  zleft,  thetaleft],\n                         [rright, zright, thetaleft],\n                         [rleft,  zright, thetaleft],\n                         [rright, zleft,  thetaleft],\n                         ], axis=1).T\n\n    b2 = np.concatenate([[rright, zright, thetaright],\n                         [rright, zleft,  thetaright],\n                         [rleft,  zright, thetaleft],\n                         [rright, zright, thetaright],\n                         [rleft,  zleft,  thetaright],\n                         [rright, zright, thetaright],\n                         [rleft,  zright, thetaright],\n                         [rright, zleft,  thetaright],\n                         ], axis=1).T\n\n    inds = np.concatenate([inds, inds, inds, inds, inds, inds, inds, inds])\n\n    # find intersections and compute return values\n    tsect, dsect = _cart_intersect(p1cart, p2cart, _cyl2cart(b1), _cyl2cart(b2))\n    tmask = np.logical_and(0.0<=tsect, tsect<=1.0)\n    ret = np.unique(tsect[tmask], return_index=True)\n    tsect, tinds = ret[0], ret[1].astype('int64')\n    inds = inds[tmask][tinds]\n    xyz = dsect[tmask][tinds]\n    s = np.sqrt(((xyz - p1cart) * (xyz - p1cart)).sum(axis=1))\n    ret = np.unique(s, return_index=True)\n    s, sinds = ret[0], ret[1].astype('int64')\n    inds = inds[sinds]\n    xyz = xyz[sinds]\n    t = s/np.sqrt((dpcart*dpcart).sum())\n    sinds = s.argsort()\n    s = s[sinds]\n    t = t[sinds]\n    inds = inds[sinds]\n    xyz = xyz[sinds]\n    rztheta = np.concatenate([np.sqrt(xyz[:,0] * xyz[:,0] + xyz[:,1] * xyz[:,1])[:,np.newaxis],\n                              xyz[:,2:3],\n                              np.arctan2(xyz[:,1], xyz[:,0])[:,np.newaxis]], axis=1)\n    return t, s, rztheta, inds\n    #rztheta[:,2] = 0.0 + (rztheta[:,2] - np.pi*3/2)%(2*np.pi)\n"
  },
  {
    "path": "yt/utilities/lib/amr_kdtools.pxd",
    "content": "\"\"\"\nAMR kD-Tree Cython Tools\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\n\ncdef struct Split:\n    int dim\n    np.float64_t pos\n\ncdef class Node:\n    cdef public Node left\n    cdef public Node right\n    cdef public Node parent\n    cdef public int grid\n    cdef public bint dirty\n    cdef public np.int64_t node_id\n    cdef public np.int64_t node_ind\n    cdef np.float64_t left_edge[3]\n    cdef np.float64_t right_edge[3]\n    cdef public data\n    cdef Split * split\n    cdef int level\n    cdef int point_in_node(self, np.float64_t[:] point)\n    cdef Node _find_node(self, np.float64_t[:] point)\n    cdef int _kd_is_leaf(self)\n    cdef add_grid(self, np.float64_t[:] gle, np.float64_t[:] gre,\n                       int gid,\n                       int rank,\n                       int size)\n    cdef insert_grid(self,\n                       np.float64_t[:] gle,\n                       np.float64_t[:] gre,\n                       int grid_id,\n                       int rank,\n                       int size)\n    cpdef add_grids(self,\n                       int ngrids,\n                       np.float64_t[:,:] gles,\n                       np.float64_t[:,:] gres,\n                       np.int64_t[:] gids,\n                       int rank,\n                       int size)\n    cdef int should_i_split(self, int rank, int size)\n    cdef void insert_grids(self,\n                       int ngrids,\n                       np.float64_t[:,:] gles,\n                       np.float64_t[:,:] gres,\n                       np.int64_t[:] gids,\n                       int rank,\n                       int size)\n    cdef split_grid(self,\n                       np.float64_t[:] gle,\n                       np.float64_t[:] gre,\n                       int gid,\n                       int rank,\n                       int size)\n    cdef int split_grids(self,\n                       int ngrids,\n                       np.float64_t[:,:] gles,\n                       np.float64_t[:,:] gres,\n                       np.int64_t[:] gids,\n                       int rank,\n                       int size)\n    cdef geo_split(self,\n                       np.float64_t[:] gle,\n                       np.float64_t[:] gre,\n                       int grid_id,\n                       int rank,\n                       int size)\n    cdef void divide(self, Split * split)\n"
  },
  {
    "path": "yt/utilities/lib/amr_kdtools.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nAMR kD-Tree Cython Tools\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom cython.view cimport array as cvarray\nfrom libc.stdlib cimport free, malloc\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef class Node:\n\n    def __cinit__(self,\n                  Node parent,\n                  Node left,\n                  Node right,\n                  np.float64_t[:] left_edge,\n                  np.float64_t[:] right_edge,\n                  int grid,\n                  np.int64_t node_id):\n        self.dirty = False\n        self.left = left\n        self.right = right\n        self.parent = parent\n        cdef int i\n        for i in range(3):\n            self.left_edge[i] = left_edge[i]\n            self.right_edge[i] = right_edge[i]\n        self.grid = grid\n        self.node_id = node_id\n        self.split == NULL\n\n\n    def print_me(self):\n        print('Node %i' % self.node_id)\n        print('\\t le: %e %e %e' % (self.left_edge[0], self.left_edge[1],\n                                   self.left_edge[2]))\n        print('\\t re: %e %e %e' % (self.right_edge[0], self.right_edge[1],\n                                   self.right_edge[2]))\n        print('\\t grid: %i' % self.grid)\n\n    def get_split_dim(self):\n        if self.split != NULL:\n            return self.split.dim\n        else:\n            return -1\n\n    def get_split_pos(self):\n        if self.split != NULL:\n            return self.split.pos\n        else:\n            return np.nan\n\n    def set_left_edge(self, np.float64_t[:] left_edge):\n        cdef int i\n        for i in range(3):\n            self.left_edge[i] = left_edge[i]\n\n    def set_right_edge(self, np.float64_t[:] right_edge):\n        cdef int i\n        for i in range(3):\n            self.right_edge[i] = right_edge[i]\n\n    def create_split(self, dim, pos):\n        split = <Split *> malloc(sizeof(Split))\n        split.dim = dim\n        split.pos = pos\n        self.split = split\n\n    def __dealloc__(self):\n        if self.split != NULL: free(self.split)\n\n    # Begin input of converted methods\n\n    def get_left_edge(self):\n        le = np.empty(3, dtype='float64')\n        for i in range(3):\n            le[i] = self.left_edge[i]\n        return le\n\n    def get_right_edge(self):\n        re = np.empty(3, dtype='float64')\n        for i in range(3):\n            re[i] = self.right_edge[i]\n        return re\n\n    def set_dirty(self, bint state):\n        cdef Node node\n        for node in self.depth_traverse():\n            node.dirty = state\n\n    def kd_traverse(self, viewpoint=None):\n        cdef Node node\n        if viewpoint is None:\n            for node in self.depth_traverse():\n                if node._kd_is_leaf() == 1 and node.grid != -1:\n                    yield node\n        else:\n            for node in self.viewpoint_traverse(viewpoint):\n                if node._kd_is_leaf() == 1 and node.grid != -1:\n                    yield node\n\n    def add_pygrid(self,\n                       np.float64_t[:] gle,\n                       np.float64_t[:] gre,\n                       int gid,\n                       int rank,\n                       int size):\n\n        \"\"\"\n        The entire purpose of this function is to move everything from ndarrays\n        to internal C pointers.\n        \"\"\"\n        cdef np.float64_t[:] pgles = cvarray(format=\"d\", shape=(3,), itemsize=sizeof(np.float64_t))\n        cdef np.float64_t[:] pgres = cvarray(format=\"d\", shape=(3,), itemsize=sizeof(np.float64_t))\n        cdef int j\n        for j in range(3):\n            pgles[j] = gle[j]\n            pgres[j] = gre[j]\n\n        self.add_grid(pgles, pgres, gid, rank, size)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef add_grid(self,\n                       np.float64_t[:] gle,\n                       np.float64_t[:] gre,\n                       int gid,\n                       int rank,\n                       int size):\n\n        if not should_i_build(self, rank, size):\n            return\n\n        if self._kd_is_leaf() == 1:\n            self.insert_grid(gle, gre, gid, rank, size)\n        else:\n            less_id = gle[self.split.dim] < self.split.pos\n            if less_id:\n                self.left.add_grid(gle, gre,\n                         gid, rank, size)\n\n            greater_id = gre[self.split.dim] > self.split.pos\n            if greater_id:\n                self.right.add_grid(gle, gre,\n                         gid, rank, size)\n        return\n\n\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef insert_grid(self,\n                    np.float64_t[:] gle,\n                    np.float64_t[:] gre,\n                    int grid_id,\n                    int rank,\n                    int size):\n        if not should_i_build(self, rank, size):\n            return\n\n        # If we should continue to split based on parallelism, do so!\n        if self.should_i_split(rank, size):\n            self.geo_split(gle, gre, grid_id, rank, size)\n            return\n\n        cdef int contained = 1\n        for i in range(3):\n            if gle[i] > self.left_edge[i] or\\\n               gre[i] < self.right_edge[i]:\n                contained *= 0\n\n        if contained == 1:\n            self.grid = grid_id\n            assert(self.grid != -1)\n            return\n\n        # Split the grid\n        cdef int check = self.split_grid(gle, gre, grid_id, rank, size)\n        # If check is -1, then we have found a place where there are no choices.\n        # Exit out and set the node to None.\n        if check == -1:\n            self.grid = -1\n        return\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cpdef add_grids(self,\n                        int ngrids,\n                        np.float64_t[:,:] gles,\n                        np.float64_t[:,:] gres,\n                        np.int64_t[:] gids,\n                        int rank,\n                        int size):\n        cdef int i, j, nless, ngreater, index\n        cdef np.float64_t[:,:] less_gles, less_gres, greater_gles, greater_gres\n        cdef np.int64_t[:] l_ids, g_ids\n        if not should_i_build(self, rank, size):\n            return\n\n        if self._kd_is_leaf() == 1:\n            self.insert_grids(ngrids, gles, gres, gids, rank, size)\n            return\n\n        less_ids = cvarray(format=\"q\", shape=(ngrids,), itemsize=sizeof(np.int64_t))\n        greater_ids = cvarray(format=\"q\", shape=(ngrids,), itemsize=sizeof(np.int64_t))\n\n        nless = 0\n        ngreater = 0\n        for i in range(ngrids):\n            if gles[i, self.split.dim] < self.split.pos:\n                less_ids[nless] = i\n                nless += 1\n\n            if gres[i, self.split.dim] > self.split.pos:\n                greater_ids[ngreater] = i\n                ngreater += 1\n\n        #print('nless: %i' % nless)\n        #print('ngreater: %i' % ngreater)\n\n        if nless > 0:\n            less_gles = cvarray(format=\"d\", shape=(nless,3), itemsize=sizeof(np.float64_t))\n            less_gres = cvarray(format=\"d\", shape=(nless,3), itemsize=sizeof(np.float64_t))\n            l_ids = cvarray(format=\"q\", shape=(nless,), itemsize=sizeof(np.int64_t))\n\n            for i in range(nless):\n                index = less_ids[i]\n                l_ids[i] = gids[index]\n                for j in range(3):\n                    less_gles[i,j] = gles[index,j]\n                    less_gres[i,j] = gres[index,j]\n\n            self.left.add_grids(nless, less_gles, less_gres,\n                      l_ids, rank, size)\n\n        if ngreater > 0:\n            greater_gles = cvarray(format=\"d\", shape=(ngreater,3), itemsize=sizeof(np.float64_t))\n            greater_gres = cvarray(format=\"d\", shape=(ngreater,3), itemsize=sizeof(np.float64_t))\n            g_ids = cvarray(format=\"q\", shape=(ngreater,), itemsize=sizeof(np.int64_t))\n\n            for i in range(ngreater):\n                index = greater_ids[i]\n                g_ids[i] = gids[index]\n                for j in range(3):\n                    greater_gles[i,j] = gles[index,j]\n                    greater_gres[i,j] = gres[index,j]\n\n            self.right.add_grids(ngreater, greater_gles, greater_gres,\n                      g_ids, rank, size)\n\n        return\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int should_i_split(self, int rank, int size):\n        if self.node_id < size and self.node_id > 0:\n            return 1\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void insert_grids(self,\n                           int ngrids,\n                           np.float64_t[:,:] gles,\n                           np.float64_t[:,:] gres,\n                           np.int64_t[:] gids,\n                           int rank,\n                           int size):\n\n        if not should_i_build(self, rank, size) or ngrids == 0:\n            return\n        cdef int contained = 1\n        cdef int check\n\n        if ngrids == 1:\n            # If we should continue to split based on parallelism, do so!\n            if self.should_i_split(rank, size):\n                self.geo_split(gles[0,:], gres[0,:], gids[0], rank, size)\n                return\n\n            for i in range(3):\n                contained *= gles[0,i] <= self.left_edge[i]\n                contained *= gres[0,i] >= self.right_edge[i]\n\n            if contained == 1:\n                # print('Node fully contained, setting to grid: %i' % gids[0])\n                self.grid = gids[0]\n                assert(self.grid != -1)\n                return\n\n        # Split the grids\n        check = self.split_grids(ngrids, gles, gres, gids, rank, size)\n        # If check is -1, then we have found a place where there are no choices.\n        # Exit out and set the node to None.\n        if check == -1:\n            self.grid = -1\n        return\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef split_grid(self,\n                   np.float64_t[:] gle,\n                   np.float64_t[:] gre,\n                   int gid,\n                   int rank,\n                   int size):\n\n        cdef int j\n        cdef np.uint8_t[:] less_ids, greater_ids\n        data = cvarray(format=\"d\", shape=(1,2,3), itemsize=sizeof(np.float64_t))\n        for j in range(3):\n            data[0,0,j] = gle[j]\n            data[0,1,j] = gre[j]\n\n        less_ids = cvarray(format=\"B\", shape=(1,), itemsize=sizeof(np.uint8_t))\n        greater_ids = cvarray(format=\"B\", shape=(1,), itemsize=sizeof(np.uint8_t))\n\n        best_dim, split_pos, nless, ngreater = \\\n            kdtree_get_choices(1, data, self.left_edge, self.right_edge,\n                              less_ids, greater_ids)\n\n        # If best_dim is -1, then we have found a place where there are no choices.\n        # Exit out and set the node to None.\n        if best_dim == -1:\n            return -1\n\n\n        split = <Split *> malloc(sizeof(Split))\n        split.dim = best_dim\n        split.pos = split_pos\n\n        # Create a Split\n        self.divide(split)\n\n        # Populate Left Node\n        #print('Inserting left node', self.left_edge, self.right_edge)\n        if nless == 1:\n            self.left.insert_grid(gle, gre,\n                         gid, rank, size)\n\n        # Populate Right Node\n        #print('Inserting right node', self.left_edge, self.right_edge)\n        if ngreater == 1:\n            self.right.insert_grid(gle, gre,\n                         gid, rank, size)\n\n        return 0\n\n    #@cython.boundscheck(False)\n    #@cython.wraparound(False)\n    #@cython.cdivision(True)\n    cdef int split_grids(self,\n                           int ngrids,\n                           np.float64_t[:,:] gles,\n                           np.float64_t[:,:] gres,\n                           np.int64_t[:] gids,\n                           int rank,\n                           int size):\n        # Find a Split\n        cdef int i, j, index\n        cdef np.float64_t[:,:] less_gles, less_gres, greater_gles, greater_gres\n        cdef np.int64_t[:] l_ids, g_ids\n        if ngrids == 0: return 0\n\n        data = cvarray(format=\"d\", shape=(ngrids,2,3), itemsize=sizeof(np.float64_t))\n\n        for i in range(ngrids):\n            for j in range(3):\n                data[i,0,j] = gles[i,j]\n                data[i,1,j] = gres[i,j]\n\n        less_ids = cvarray(format=\"B\", shape=(ngrids,), itemsize=sizeof(np.uint8_t))\n        greater_ids = cvarray(format=\"B\", shape=(ngrids,), itemsize=sizeof(np.uint8_t))\n\n        best_dim, split_pos, nless, ngreater = \\\n            kdtree_get_choices(ngrids, data, self.left_edge, self.right_edge,\n                              less_ids, greater_ids)\n\n\n        # If best_dim is -1, then we have found a place where there are no choices.\n        # Exit out and set the node to None.\n        if best_dim == -1:\n            print('Failed to split grids.')\n            return -1\n\n        split = <Split *> malloc(sizeof(Split))\n        split.dim = best_dim\n        split.pos = split_pos\n\n        # Create a Split\n        self.divide(split)\n\n        less_index = cvarray(format=\"q\", shape=(ngrids,), itemsize=sizeof(np.int64_t))\n        greater_index = cvarray(format=\"q\", shape=(ngrids,), itemsize=sizeof(np.int64_t))\n\n        nless = 0\n        ngreater = 0\n        for i in range(ngrids):\n            if less_ids[i] == 1:\n                less_index[nless] = i\n                nless += 1\n\n            if greater_ids[i] == 1:\n                greater_index[ngreater] = i\n                ngreater += 1\n\n        if nless > 0:\n            less_gles = cvarray(format=\"d\", shape=(nless,3), itemsize=sizeof(np.float64_t))\n            less_gres = cvarray(format=\"d\", shape=(nless,3), itemsize=sizeof(np.float64_t))\n            l_ids = cvarray(format=\"q\", shape=(nless,), itemsize=sizeof(np.int64_t))\n\n            for i in range(nless):\n                index = less_index[i]\n                l_ids[i] = gids[index]\n                for j in range(3):\n                    less_gles[i,j] = gles[index,j]\n                    less_gres[i,j] = gres[index,j]\n\n            # Populate Left Node\n            #print('Inserting left node', self.left_edge, self.right_edge)\n            self.left.insert_grids(nless, less_gles, less_gres,\n                         l_ids, rank, size)\n\n        if ngreater > 0:\n            greater_gles = cvarray(format=\"d\", shape=(ngreater,3), itemsize=sizeof(np.float64_t))\n            greater_gres = cvarray(format=\"d\", shape=(ngreater,3), itemsize=sizeof(np.float64_t))\n            g_ids = cvarray(format=\"q\", shape=(ngreater,), itemsize=sizeof(np.int64_t))\n\n            for i in range(ngreater):\n                index = greater_index[i]\n                g_ids[i] = gids[index]\n                for j in range(3):\n                    greater_gles[i,j] = gles[index,j]\n                    greater_gres[i,j] = gres[index,j]\n\n            # Populate Right Node\n            #print('Inserting right node', self.left_edge, self.right_edge)\n            self.right.insert_grids(ngreater, greater_gles, greater_gres,\n                         g_ids, rank, size)\n\n        return 0\n\n    cdef geo_split(self,\n                   np.float64_t[:] gle,\n                   np.float64_t[:] gre,\n                   int grid_id,\n                   int rank,\n                   int size):\n        cdef int big_dim = 0\n        cdef int i\n        cdef np.float64_t v, my_max = 0.0\n\n        for i in range(3):\n            v = gre[i] - gle[i]\n            if v > my_max:\n                my_max = v\n                big_dim = i\n\n        new_pos = (gre[big_dim] + gle[big_dim])/2.\n\n        lnew_gle = cvarray(format=\"d\", shape=(3,), itemsize=sizeof(np.float64_t))\n        lnew_gre = cvarray(format=\"d\", shape=(3,), itemsize=sizeof(np.float64_t))\n        rnew_gle = cvarray(format=\"d\", shape=(3,), itemsize=sizeof(np.float64_t))\n        rnew_gre = cvarray(format=\"d\", shape=(3,), itemsize=sizeof(np.float64_t))\n\n        for j in range(3):\n            lnew_gle[j] = gle[j]\n            lnew_gre[j] = gre[j]\n            rnew_gle[j] = gle[j]\n            rnew_gre[j] = gre[j]\n\n        split = <Split *> malloc(sizeof(Split))\n        split.dim = big_dim\n        split.pos = new_pos\n\n        # Create a Split\n        self.divide(split)\n\n        #lnew_gre[big_dim] = new_pos\n        # Populate Left Node\n        #print('Inserting left node', self.left_edge, self.right_edge)\n        self.left.insert_grid(lnew_gle, lnew_gre,\n                grid_id, rank, size)\n\n        #rnew_gle[big_dim] = new_pos\n        # Populate Right Node\n        #print('Inserting right node', self.left_edge, self.right_edge)\n        self.right.insert_grid(rnew_gle, rnew_gre,\n                grid_id, rank, size)\n        return\n\n    cdef void divide(self, Split * split):\n        # Create a Split\n        self.split = split\n\n        cdef np.float64_t[:] le = np.empty(3, dtype='float64')\n        cdef np.float64_t[:] re = np.empty(3, dtype='float64')\n\n        cdef int i\n        for i in range(3):\n            le[i] = self.left_edge[i]\n            re[i] = self.right_edge[i]\n        re[split.dim] = split.pos\n\n        self.left = Node(self, None, None,\n                         le, re, self.grid,\n                         _lchild_id(self.node_id))\n\n        re[split.dim] = self.right_edge[split.dim]\n        le[split.dim] = split.pos\n        self.right = Node(self, None, None,\n                          le, re, self.grid,\n                          _rchild_id(self.node_id))\n\n        return\n    #\n    def kd_sum_volume(self):\n        cdef np.float64_t vol = 1.0\n        if (self.left is None) and (self.right is None):\n            if self.grid == -1:\n                return 0.0\n            for i in range(3):\n                vol *= self.right_edge[i] - self.left_edge[i]\n            return vol\n        else:\n            return self.left.kd_sum_volume() + self.right.kd_sum_volume()\n\n    def kd_node_check(self):\n        assert (self.left is None) == (self.right is None)\n        if (self.left is None) and (self.right is None):\n            if self.grid != -1:\n                return np.prod(self.right_edge - self.left_edge)\n            else: return 0.0\n        else:\n            return self.left.kd_node_check()+self.right.kd_node_check()\n\n    def kd_is_leaf(self):\n        cdef int has_l_child = self.left == None\n        cdef int has_r_child = self.right == None\n        assert has_l_child == has_r_child\n        return has_l_child\n\n    cdef int _kd_is_leaf(self):\n        if self.left is None or self.right is None:\n            return 1\n        return 0\n\n    def depth_traverse(self, max_node=None):\n        '''\n        Yields a depth-first traversal of the kd tree always going to\n        the left child before the right.\n        '''\n        current = self\n        previous = None\n        if max_node is None:\n            max_node = np.inf\n        while current is not None:\n            yield current\n            current, previous = step_depth(current, previous)\n            if current is None: break\n            if current.node_id >= max_node:\n                current = current.parent\n                previous = current.right\n\n    def depth_first_touch(self, max_node=None):\n        '''\n        Yields a depth-first traversal of the kd tree always going to\n        the left child before the right.\n        '''\n        current = self\n        previous = None\n        if max_node is None:\n            max_node = np.inf\n        while current is not None:\n            if previous is None or previous.parent != current:\n                yield current\n            current, previous = step_depth(current, previous)\n            if current is None: break\n            if current.node_id >= max_node:\n                current = current.parent\n                previous = current.right\n\n    def breadth_traverse(self):\n        '''\n        Yields a breadth-first traversal of the kd tree always going to\n        the left child before the right.\n        '''\n        current = self\n        previous = None\n        while current is not None:\n            yield current\n            current, previous = step_depth(current, previous)\n\n\n    def viewpoint_traverse(self, viewpoint):\n        '''\n        Yields a viewpoint dependent traversal of the kd-tree.  Starts\n        with nodes furthest away from viewpoint.\n        '''\n\n        current = self\n        previous = None\n        while current is not None:\n            yield current\n            current, previous = step_viewpoint(current, previous, viewpoint)\n\n    cdef int point_in_node(self,\n                           np.float64_t[:] point):\n        cdef int i\n        cdef int inside = 1\n        for i in range(3):\n            inside *= self.left_edge[i] <= point[i]\n            inside *= self.right_edge[i] > point[i]\n        return inside\n\n    cdef Node _find_node(self, np.float64_t[:] point):\n        while self._kd_is_leaf() == 0:\n            if point[self.split.dim] < self.split.pos:\n                self = self.left\n            else:\n                self = self.right\n        return self\n\n    def find_node(self,\n                  np.float64_t[:] point):\n        \"\"\"\n        Find the AMRKDTree node enclosing a position\n        \"\"\"\n        assert(self.point_in_node(point))\n        return self._find_node(point)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline np.int64_t _lchild_id(np.int64_t node_id):\n    return (node_id<<1)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline np.int64_t _rchild_id(np.int64_t node_id):\n    return (node_id<<1) + 1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline np.int64_t _parent_id(np.int64_t node_id):\n    return (node_id-1) >> 1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef int should_i_build(Node node, int rank, int size):\n    if (node.node_id < size) or (node.node_id >= 2*size):\n        return 1\n    elif node.node_id - size == rank:\n        return 1\n    else:\n        return 0\n\ndef step_depth(Node current, Node previous):\n    '''\n    Takes a single step in the depth-first traversal\n    '''\n    if current._kd_is_leaf() == 1: # At a leaf, move back up\n        previous = current\n        current = current.parent\n\n    elif current.parent is previous: # Moving down, go left first\n        previous = current\n        if current.left is not None:\n            current = current.left\n        elif current.right is not None:\n            current = current.right\n        else:\n            current = current.parent\n\n    elif current.left is previous: # Moving up from left, go right\n        previous = current\n        if current.right is not None:\n            current = current.right\n        else:\n            current = current.parent\n\n    elif current.right is previous: # Moving up from right child, move up\n        previous = current\n        current = current.parent\n\n    return current, previous\n\ndef step_viewpoint(Node current,\n                   Node previous,\n                   viewpoint):\n    '''\n    Takes a single step in the viewpoint based traversal.  Always\n    goes to the node furthest away from viewpoint first.\n    '''\n    if current._kd_is_leaf() == 1: # At a leaf, move back up\n        previous = current\n        current = current.parent\n    elif current.split.dim is None: # This is a dead node\n        previous = current\n        current = current.parent\n\n    elif current.parent is previous: # Moving down\n        previous = current\n        if viewpoint[current.split.dim] <= current.split.pos:\n            if current.right is not None:\n                current = current.right\n            else:\n                previous = current.right\n        else:\n            if current.left is not None:\n                current = current.left\n            else:\n                previous = current.left\n\n    elif current.right is previous: # Moving up from right\n        previous = current\n        if viewpoint[current.split.dim] <= current.split.pos:\n            if current.left is not None:\n                current = current.left\n            else:\n                current = current.parent\n        else:\n            current = current.parent\n\n    elif current.left is previous: # Moving up from left child\n        previous = current\n        if viewpoint[current.split.dim] > current.split.pos:\n            if current.right is not None:\n                current = current.right\n            else:\n                current = current.parent\n        else:\n            current = current.parent\n\n    return current, previous\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef kdtree_get_choices(int n_grids,\n                        np.float64_t[:,:,:] data,\n                        np.float64_t[:] l_corner,\n                        np.float64_t[:] r_corner,\n                        np.uint8_t[:] less_ids,\n                        np.uint8_t[:] greater_ids,\n                       ):\n    cdef int i, j, k, dim, n_unique, best_dim, my_split\n    cdef np.float64_t split\n    cdef np.float64_t[:,:] uniquedims\n    cdef np.float64_t[:] uniques\n    uniquedims = cvarray(format=\"d\", shape=(3, 2*n_grids), itemsize=sizeof(np.float64_t))\n    my_max = 0\n    my_split = 0\n    best_dim = -1\n    for dim in range(3):\n        n_unique = 0\n        uniques = uniquedims[dim]\n        for i in range(n_grids):\n            # Check for disqualification\n            for j in range(2):\n                # print(\"Checking against\", i,j,dim,data[i,j,dim])\n                if not (l_corner[dim] < data[i][j][dim] and\n                        data[i][j][dim] < r_corner[dim]):\n                    # print(\"Skipping \", data[i,j,dim], l_corner[dim], r_corner[dim])\n                    continue\n                skipit = 0\n                # Add our left ...\n                for k in range(n_unique):\n                    if uniques[k] == data[i][j][dim]:\n                        skipit = 1\n                        # print(\"Identified\", uniques[k], data[i,j,dim], n_unique)\n                        break\n                if skipit == 0:\n                    uniques[n_unique] = data[i][j][dim]\n                    n_unique += 1\n        if n_unique > my_max:\n            best_dim = dim\n            my_max = n_unique\n            my_split = (n_unique-1)/2\n    if best_dim == -1:\n        return -1, 0, 0, 0\n    # I recognize how lame this is.\n    cdef np.ndarray[np.float64_t, ndim=1] tarr = np.empty(my_max, dtype='float64')\n    for i in range(my_max):\n        # print(\"Setting tarr: \", i, uniquedims[best_dim][i])\n        tarr[i] = uniquedims[best_dim][i]\n    tarr.sort()\n    split = tarr[my_split]\n    cdef int nless=0, ngreater=0\n    for i in range(n_grids):\n        if data[i][0][best_dim] < split:\n            less_ids[i] = 1\n            nless += 1\n        else:\n            less_ids[i] = 0\n        if data[i][1][best_dim] > split:\n            greater_ids[i] = 1\n            ngreater += 1\n        else:\n            greater_ids[i] = 0\n\n    # Return out unique values\n    return best_dim, split, nless, ngreater\n"
  },
  {
    "path": "yt/utilities/lib/api.py",
    "content": "from .basic_octree import *\nfrom .contour_finding import *\nfrom .depth_first_octree import *\nfrom .fortran_reader import *\nfrom .grid_traversal import *\nfrom .image_utilities import *\nfrom .interpolators import *\nfrom .marching_cubes import *\nfrom .mesh_utilities import *\nfrom .misc_utilities import *\nfrom .particle_mesh_operations import *\nfrom .points_in_volume import *\nfrom .quad_tree import *\nfrom .write_array import *\n"
  },
  {
    "path": "yt/utilities/lib/autogenerated_element_samplers.pxd",
    "content": "cdef void Q1Function3D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void Q1Jacobian3D(double* rcol,\n                       double* scol,\n                       double* tcol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void Q1Function2D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void Q1Jacobian2D(double* rcol,\n                       double* scol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void Q2Function2D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void Q2Jacobian2D(double* rcol,\n                       double* scol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void Tet2Function3D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void Tet2Jacobian3D(double* rcol,\n                       double* scol,\n                       double* tcol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void T2Function2D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void T2Jacobian2D(double* rcol,\n                       double* scol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void W1Function3D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n\n\ncdef void W1Jacobian3D(double* rcol,\n                       double* scol,\n                       double* tcol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\n"
  },
  {
    "path": "yt/utilities/lib/autogenerated_element_samplers.pyx",
    "content": "# This file contains auto-generated functions for sampling\n# inside finite element solutions for various mesh types.\n# To see how the code generation works in detail, see\n# yt/utilities/mesh_code_generation.py.\n\n\ncimport cython\nfrom libc.math cimport pow\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void Q1Function3D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\tfx[0] = 0.125*(1 - x[0])*(1 - x[1])*(1 - x[2])*vertices[0] + 0.125*(1 - x[0])*(1 - x[1])*(1 + x[2])*vertices[12] + 0.125*(1 - x[0])*(1 + x[1])*(1 - x[2])*vertices[9] + 0.125*(1 - x[0])*(1 + x[1])*(1 + x[2])*vertices[21] + 0.125*(1 + x[0])*(1 - x[1])*(1 - x[2])*vertices[3] + 0.125*(1 + x[0])*(1 - x[1])*(1 + x[2])*vertices[15] + 0.125*(1 + x[0])*(1 + x[1])*(1 - x[2])*vertices[6] + 0.125*(1 + x[0])*(1 + x[1])*(1 + x[2])*vertices[18] - phys_x[0]\n\tfx[1] = 0.125*(1 - x[0])*(1 - x[1])*(1 - x[2])*vertices[1] + 0.125*(1 - x[0])*(1 - x[1])*(1 + x[2])*vertices[13] + 0.125*(1 - x[0])*(1 + x[1])*(1 - x[2])*vertices[10] + 0.125*(1 - x[0])*(1 + x[1])*(1 + x[2])*vertices[22] + 0.125*(1 + x[0])*(1 - x[1])*(1 - x[2])*vertices[4] + 0.125*(1 + x[0])*(1 - x[1])*(1 + x[2])*vertices[16] + 0.125*(1 + x[0])*(1 + x[1])*(1 - x[2])*vertices[7] + 0.125*(1 + x[0])*(1 + x[1])*(1 + x[2])*vertices[19] - phys_x[1]\n\tfx[2] = 0.125*(1 - x[0])*(1 - x[1])*(1 - x[2])*vertices[2] + 0.125*(1 - x[0])*(1 - x[1])*(1 + x[2])*vertices[14] + 0.125*(1 - x[0])*(1 + x[1])*(1 - x[2])*vertices[11] + 0.125*(1 - x[0])*(1 + x[1])*(1 + x[2])*vertices[23] + 0.125*(1 + x[0])*(1 - x[1])*(1 - x[2])*vertices[5] + 0.125*(1 + x[0])*(1 - x[1])*(1 + x[2])*vertices[17] + 0.125*(1 + x[0])*(1 + x[1])*(1 - x[2])*vertices[8] + 0.125*(1 + x[0])*(1 + x[1])*(1 + x[2])*vertices[20] - phys_x[2]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void Q1Jacobian3D(double* rcol,\n                       double* scol,\n                       double* tcol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\trcol[0] = -0.125*(1 - x[1])*(1 - x[2])*vertices[0] + 0.125*(1 - x[1])*(1 - x[2])*vertices[3] - 0.125*(1 - x[1])*(1 + x[2])*vertices[12] + 0.125*(1 - x[1])*(1 + x[2])*vertices[15] + 0.125*(1 + x[1])*(1 - x[2])*vertices[6] - 0.125*(1 + x[1])*(1 - x[2])*vertices[9] + 0.125*(1 + x[1])*(1 + x[2])*vertices[18] - 0.125*(1 + x[1])*(1 + x[2])*vertices[21]\n\tscol[0] = -0.125*(1 - x[0])*(1 - x[2])*vertices[0] + 0.125*(1 - x[0])*(1 - x[2])*vertices[9] - 0.125*(1 - x[0])*(1 + x[2])*vertices[12] + 0.125*(1 - x[0])*(1 + x[2])*vertices[21] - 0.125*(1 + x[0])*(1 - x[2])*vertices[3] + 0.125*(1 + x[0])*(1 - x[2])*vertices[6] - 0.125*(1 + x[0])*(1 + x[2])*vertices[15] + 0.125*(1 + x[0])*(1 + x[2])*vertices[18]\n\ttcol[0] = -0.125*(1 - x[0])*(1 - x[1])*vertices[0] + 0.125*(1 - x[0])*(1 - x[1])*vertices[12] - 0.125*(1 - x[0])*(1 + x[1])*vertices[9] + 0.125*(1 - x[0])*(1 + x[1])*vertices[21] - 0.125*(1 + x[0])*(1 - x[1])*vertices[3] + 0.125*(1 + x[0])*(1 - x[1])*vertices[15] - 0.125*(1 + x[0])*(1 + x[1])*vertices[6] + 0.125*(1 + x[0])*(1 + x[1])*vertices[18]\n\trcol[1] = -0.125*(1 - x[1])*(1 - x[2])*vertices[1] + 0.125*(1 - x[1])*(1 - x[2])*vertices[4] - 0.125*(1 - x[1])*(1 + x[2])*vertices[13] + 0.125*(1 - x[1])*(1 + x[2])*vertices[16] + 0.125*(1 + x[1])*(1 - x[2])*vertices[7] - 0.125*(1 + x[1])*(1 - x[2])*vertices[10] + 0.125*(1 + x[1])*(1 + x[2])*vertices[19] - 0.125*(1 + x[1])*(1 + x[2])*vertices[22]\n\tscol[1] = -0.125*(1 - x[0])*(1 - x[2])*vertices[1] + 0.125*(1 - x[0])*(1 - x[2])*vertices[10] - 0.125*(1 - x[0])*(1 + x[2])*vertices[13] + 0.125*(1 - x[0])*(1 + x[2])*vertices[22] - 0.125*(1 + x[0])*(1 - x[2])*vertices[4] + 0.125*(1 + x[0])*(1 - x[2])*vertices[7] - 0.125*(1 + x[0])*(1 + x[2])*vertices[16] + 0.125*(1 + x[0])*(1 + x[2])*vertices[19]\n\ttcol[1] = -0.125*(1 - x[0])*(1 - x[1])*vertices[1] + 0.125*(1 - x[0])*(1 - x[1])*vertices[13] - 0.125*(1 - x[0])*(1 + x[1])*vertices[10] + 0.125*(1 - x[0])*(1 + x[1])*vertices[22] - 0.125*(1 + x[0])*(1 - x[1])*vertices[4] + 0.125*(1 + x[0])*(1 - x[1])*vertices[16] - 0.125*(1 + x[0])*(1 + x[1])*vertices[7] + 0.125*(1 + x[0])*(1 + x[1])*vertices[19]\n\trcol[2] = -0.125*(1 - x[1])*(1 - x[2])*vertices[2] + 0.125*(1 - x[1])*(1 - x[2])*vertices[5] - 0.125*(1 - x[1])*(1 + x[2])*vertices[14] + 0.125*(1 - x[1])*(1 + x[2])*vertices[17] + 0.125*(1 + x[1])*(1 - x[2])*vertices[8] - 0.125*(1 + x[1])*(1 - x[2])*vertices[11] + 0.125*(1 + x[1])*(1 + x[2])*vertices[20] - 0.125*(1 + x[1])*(1 + x[2])*vertices[23]\n\tscol[2] = -0.125*(1 - x[0])*(1 - x[2])*vertices[2] + 0.125*(1 - x[0])*(1 - x[2])*vertices[11] - 0.125*(1 - x[0])*(1 + x[2])*vertices[14] + 0.125*(1 - x[0])*(1 + x[2])*vertices[23] - 0.125*(1 + x[0])*(1 - x[2])*vertices[5] + 0.125*(1 + x[0])*(1 - x[2])*vertices[8] - 0.125*(1 + x[0])*(1 + x[2])*vertices[17] + 0.125*(1 + x[0])*(1 + x[2])*vertices[20]\n\ttcol[2] = -0.125*(1 - x[0])*(1 - x[1])*vertices[2] + 0.125*(1 - x[0])*(1 - x[1])*vertices[14] - 0.125*(1 - x[0])*(1 + x[1])*vertices[11] + 0.125*(1 - x[0])*(1 + x[1])*vertices[23] - 0.125*(1 + x[0])*(1 - x[1])*vertices[5] + 0.125*(1 + x[0])*(1 - x[1])*vertices[17] - 0.125*(1 + x[0])*(1 + x[1])*vertices[8] + 0.125*(1 + x[0])*(1 + x[1])*vertices[20]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void Q1Function2D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\tfx[0] = 0.25*(1 - x[0])*(1 - x[1])*vertices[0] + 0.25*(1 - x[0])*(1 + x[1])*vertices[6] + 0.25*(1 + x[0])*(1 - x[1])*vertices[2] + 0.25*(1 + x[0])*(1 + x[1])*vertices[4] - phys_x[0]\n\tfx[1] = 0.25*(1 - x[0])*(1 - x[1])*vertices[1] + 0.25*(1 - x[0])*(1 + x[1])*vertices[7] + 0.25*(1 + x[0])*(1 - x[1])*vertices[3] + 0.25*(1 + x[0])*(1 + x[1])*vertices[5] - phys_x[1]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void Q1Jacobian2D(double* rcol,\n                       double* scol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\trcol[0] = -0.25*(1 - x[1])*vertices[0] + 0.25*(1 - x[1])*vertices[2] + 0.25*(1 + x[1])*vertices[4] - 0.25*(1 + x[1])*vertices[6]\n\tscol[0] = -0.25*(1 - x[0])*vertices[0] + 0.25*(1 - x[0])*vertices[6] - 0.25*(1 + x[0])*vertices[2] + 0.25*(1 + x[0])*vertices[4]\n\trcol[1] = -0.25*(1 - x[1])*vertices[1] + 0.25*(1 - x[1])*vertices[3] + 0.25*(1 + x[1])*vertices[5] - 0.25*(1 + x[1])*vertices[7]\n\tscol[1] = -0.25*(1 - x[0])*vertices[1] + 0.25*(1 - x[0])*vertices[7] - 0.25*(1 + x[0])*vertices[3] + 0.25*(1 + x[0])*vertices[5]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void Q2Function2D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\tfx[0] = (1 + x[0])*(-1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[16] - 0.5*(1 + x[0])*(-1 + x[0])*x[1]*(-1 + x[1])*vertices[8] - 0.5*(1 + x[0])*(-1 + x[0])*x[1]*(1 + x[1])*vertices[12] - phys_x[0] - 0.5*x[0]*(-1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[14] + 0.25*x[0]*(-1 + x[0])*x[1]*(-1 + x[1])*vertices[0] + 0.25*x[0]*(-1 + x[0])*x[1]*(1 + x[1])*vertices[6] - 0.5*x[0]*(1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[10] + 0.25*x[0]*(1 + x[0])*x[1]*(-1 + x[1])*vertices[2] + 0.25*x[0]*(1 + x[0])*x[1]*(1 + x[1])*vertices[4]\n\tfx[1] = (1 + x[0])*(-1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[17] - 0.5*(1 + x[0])*(-1 + x[0])*x[1]*(-1 + x[1])*vertices[9] - 0.5*(1 + x[0])*(-1 + x[0])*x[1]*(1 + x[1])*vertices[13] - phys_x[1] - 0.5*x[0]*(-1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[15] + 0.25*x[0]*(-1 + x[0])*x[1]*(-1 + x[1])*vertices[1] + 0.25*x[0]*(-1 + x[0])*x[1]*(1 + x[1])*vertices[7] - 0.5*x[0]*(1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[11] + 0.25*x[0]*(1 + x[0])*x[1]*(-1 + x[1])*vertices[3] + 0.25*x[0]*(1 + x[0])*x[1]*(1 + x[1])*vertices[5]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void Q2Jacobian2D(double* rcol,\n                       double* scol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\trcol[0] = -0.5*(-1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[14] + (-1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[16] + 0.25*(-1 + x[0])*x[1]*(-1 + x[1])*vertices[0] - 0.5*(-1 + x[0])*x[1]*(-1 + x[1])*vertices[8] + 0.25*(-1 + x[0])*x[1]*(1 + x[1])*vertices[6] - 0.5*(-1 + x[0])*x[1]*(1 + x[1])*vertices[12] - 0.5*(1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[10] + (1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[16] + 0.25*(1 + x[0])*x[1]*(-1 + x[1])*vertices[2] - 0.5*(1 + x[0])*x[1]*(-1 + x[1])*vertices[8] + 0.25*(1 + x[0])*x[1]*(1 + x[1])*vertices[4] - 0.5*(1 + x[0])*x[1]*(1 + x[1])*vertices[12] - 0.5*x[0]*(1 + x[1])*(-1 + x[1])*vertices[10] - 0.5*x[0]*(1 + x[1])*(-1 + x[1])*vertices[14] + 0.25*x[0]*x[1]*(-1 + x[1])*vertices[0] + 0.25*x[0]*x[1]*(-1 + x[1])*vertices[2] + 0.25*x[0]*x[1]*(1 + x[1])*vertices[4] + 0.25*x[0]*x[1]*(1 + x[1])*vertices[6]\n\tscol[0] = -0.5*(-1 + x[1])*(1 + x[0])*(-1 + x[0])*vertices[8] + (-1 + x[1])*(1 + x[0])*(-1 + x[0])*vertices[16] + 0.25*(-1 + x[1])*x[0]*(-1 + x[0])*vertices[0] - 0.5*(-1 + x[1])*x[0]*(-1 + x[0])*vertices[14] + 0.25*(-1 + x[1])*x[0]*(1 + x[0])*vertices[2] - 0.5*(-1 + x[1])*x[0]*(1 + x[0])*vertices[10] - 0.5*(1 + x[1])*(1 + x[0])*(-1 + x[0])*vertices[12] + (1 + x[1])*(1 + x[0])*(-1 + x[0])*vertices[16] + 0.25*(1 + x[1])*x[0]*(-1 + x[0])*vertices[6] - 0.5*(1 + x[1])*x[0]*(-1 + x[0])*vertices[14] + 0.25*(1 + x[1])*x[0]*(1 + x[0])*vertices[4] - 0.5*(1 + x[1])*x[0]*(1 + x[0])*vertices[10] - 0.5*x[1]*(1 + x[0])*(-1 + x[0])*vertices[8] - 0.5*x[1]*(1 + x[0])*(-1 + x[0])*vertices[12] + 0.25*x[1]*x[0]*(-1 + x[0])*vertices[0] + 0.25*x[1]*x[0]*(-1 + x[0])*vertices[6] + 0.25*x[1]*x[0]*(1 + x[0])*vertices[2] + 0.25*x[1]*x[0]*(1 + x[0])*vertices[4]\n\trcol[1] = -0.5*(-1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[15] + (-1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[17] + 0.25*(-1 + x[0])*x[1]*(-1 + x[1])*vertices[1] - 0.5*(-1 + x[0])*x[1]*(-1 + x[1])*vertices[9] + 0.25*(-1 + x[0])*x[1]*(1 + x[1])*vertices[7] - 0.5*(-1 + x[0])*x[1]*(1 + x[1])*vertices[13] - 0.5*(1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[11] + (1 + x[0])*(1 + x[1])*(-1 + x[1])*vertices[17] + 0.25*(1 + x[0])*x[1]*(-1 + x[1])*vertices[3] - 0.5*(1 + x[0])*x[1]*(-1 + x[1])*vertices[9] + 0.25*(1 + x[0])*x[1]*(1 + x[1])*vertices[5] - 0.5*(1 + x[0])*x[1]*(1 + x[1])*vertices[13] - 0.5*x[0]*(1 + x[1])*(-1 + x[1])*vertices[11] - 0.5*x[0]*(1 + x[1])*(-1 + x[1])*vertices[15] + 0.25*x[0]*x[1]*(-1 + x[1])*vertices[1] + 0.25*x[0]*x[1]*(-1 + x[1])*vertices[3] + 0.25*x[0]*x[1]*(1 + x[1])*vertices[5] + 0.25*x[0]*x[1]*(1 + x[1])*vertices[7]\n\tscol[1] = -0.5*(-1 + x[1])*(1 + x[0])*(-1 + x[0])*vertices[9] + (-1 + x[1])*(1 + x[0])*(-1 + x[0])*vertices[17] + 0.25*(-1 + x[1])*x[0]*(-1 + x[0])*vertices[1] - 0.5*(-1 + x[1])*x[0]*(-1 + x[0])*vertices[15] + 0.25*(-1 + x[1])*x[0]*(1 + x[0])*vertices[3] - 0.5*(-1 + x[1])*x[0]*(1 + x[0])*vertices[11] - 0.5*(1 + x[1])*(1 + x[0])*(-1 + x[0])*vertices[13] + (1 + x[1])*(1 + x[0])*(-1 + x[0])*vertices[17] + 0.25*(1 + x[1])*x[0]*(-1 + x[0])*vertices[7] - 0.5*(1 + x[1])*x[0]*(-1 + x[0])*vertices[15] + 0.25*(1 + x[1])*x[0]*(1 + x[0])*vertices[5] - 0.5*(1 + x[1])*x[0]*(1 + x[0])*vertices[11] - 0.5*x[1]*(1 + x[0])*(-1 + x[0])*vertices[9] - 0.5*x[1]*(1 + x[0])*(-1 + x[0])*vertices[13] + 0.25*x[1]*x[0]*(-1 + x[0])*vertices[1] + 0.25*x[1]*x[0]*(-1 + x[0])*vertices[7] + 0.25*x[1]*x[0]*(1 + x[0])*vertices[3] + 0.25*x[1]*x[0]*(1 + x[0])*vertices[5]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void Tet2Function3D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\tfx[0] = (-x[0] + 2*pow(x[0], 2))*vertices[3] + (-x[1] + 2*pow(x[1], 2))*vertices[6] + (-x[2] + 2*pow(x[2], 2))*vertices[9] + (4*x[0] - 4*x[0]*x[1] - 4*x[0]*x[2] - 4*pow(x[0], 2))*vertices[12] + (4*x[1] - 4*x[1]*x[0] - 4*x[1]*x[2] - 4*pow(x[1], 2))*vertices[18] + (4*x[2] - 4*x[2]*x[0] - 4*x[2]*x[1] - 4*pow(x[2], 2))*vertices[21] + (1 - 3*x[0] + 4*x[0]*x[1] + 4*x[0]*x[2] + 2*pow(x[0], 2) - 3*x[1] + 4*x[1]*x[2] + 2*pow(x[1], 2) - 3*x[2] + 2*pow(x[2], 2))*vertices[0] - phys_x[0] + 4*x[0]*x[1]*vertices[15] + 4*x[0]*x[2]*vertices[24] + 4*x[1]*x[2]*vertices[27]\n\tfx[1] = (-x[0] + 2*pow(x[0], 2))*vertices[4] + (-x[1] + 2*pow(x[1], 2))*vertices[7] + (-x[2] + 2*pow(x[2], 2))*vertices[10] + (4*x[0] - 4*x[0]*x[1] - 4*x[0]*x[2] - 4*pow(x[0], 2))*vertices[13] + (4*x[1] - 4*x[1]*x[0] - 4*x[1]*x[2] - 4*pow(x[1], 2))*vertices[19] + (4*x[2] - 4*x[2]*x[0] - 4*x[2]*x[1] - 4*pow(x[2], 2))*vertices[22] + (1 - 3*x[0] + 4*x[0]*x[1] + 4*x[0]*x[2] + 2*pow(x[0], 2) - 3*x[1] + 4*x[1]*x[2] + 2*pow(x[1], 2) - 3*x[2] + 2*pow(x[2], 2))*vertices[1] - phys_x[1] + 4*x[0]*x[1]*vertices[16] + 4*x[0]*x[2]*vertices[25] + 4*x[1]*x[2]*vertices[28]\n\tfx[2] = (-x[0] + 2*pow(x[0], 2))*vertices[5] + (-x[1] + 2*pow(x[1], 2))*vertices[8] + (-x[2] + 2*pow(x[2], 2))*vertices[11] + (4*x[0] - 4*x[0]*x[1] - 4*x[0]*x[2] - 4*pow(x[0], 2))*vertices[14] + (4*x[1] - 4*x[1]*x[0] - 4*x[1]*x[2] - 4*pow(x[1], 2))*vertices[20] + (4*x[2] - 4*x[2]*x[0] - 4*x[2]*x[1] - 4*pow(x[2], 2))*vertices[23] + (1 - 3*x[0] + 4*x[0]*x[1] + 4*x[0]*x[2] + 2*pow(x[0], 2) - 3*x[1] + 4*x[1]*x[2] + 2*pow(x[1], 2) - 3*x[2] + 2*pow(x[2], 2))*vertices[2] - phys_x[2] + 4*x[0]*x[1]*vertices[17] + 4*x[0]*x[2]*vertices[26] + 4*x[1]*x[2]*vertices[29]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void Tet2Jacobian3D(double* rcol,\n                       double* scol,\n                       double* tcol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\trcol[0] = (-1 + 4*x[0])*vertices[3] + (-3 + 4*x[0] + 4*x[1] + 4*x[2])*vertices[0] + (4 - 8*x[0] - 4*x[1] - 4*x[2])*vertices[12] + 4*x[1]*vertices[15] - 4*x[1]*vertices[18] - 4*x[2]*vertices[21] + 4*x[2]*vertices[24]\n\tscol[0] = (-1 + 4*x[1])*vertices[6] + (-3 + 4*x[0] + 4*x[1] + 4*x[2])*vertices[0] + (4 - 4*x[0] - 8*x[1] - 4*x[2])*vertices[18] - 4*x[0]*vertices[12] + 4*x[0]*vertices[15] - 4*x[2]*vertices[21] + 4*x[2]*vertices[27]\n\ttcol[0] = (-1 + 4*x[2])*vertices[9] + (-3 + 4*x[0] + 4*x[1] + 4*x[2])*vertices[0] + (4 - 4*x[0] - 4*x[1] - 8*x[2])*vertices[21] - 4*x[0]*vertices[12] + 4*x[0]*vertices[24] - 4*x[1]*vertices[18] + 4*x[1]*vertices[27]\n\trcol[1] = (-1 + 4*x[0])*vertices[4] + (-3 + 4*x[0] + 4*x[1] + 4*x[2])*vertices[1] + (4 - 8*x[0] - 4*x[1] - 4*x[2])*vertices[13] + 4*x[1]*vertices[16] - 4*x[1]*vertices[19] - 4*x[2]*vertices[22] + 4*x[2]*vertices[25]\n\tscol[1] = (-1 + 4*x[1])*vertices[7] + (-3 + 4*x[0] + 4*x[1] + 4*x[2])*vertices[1] + (4 - 4*x[0] - 8*x[1] - 4*x[2])*vertices[19] - 4*x[0]*vertices[13] + 4*x[0]*vertices[16] - 4*x[2]*vertices[22] + 4*x[2]*vertices[28]\n\ttcol[1] = (-1 + 4*x[2])*vertices[10] + (-3 + 4*x[0] + 4*x[1] + 4*x[2])*vertices[1] + (4 - 4*x[0] - 4*x[1] - 8*x[2])*vertices[22] - 4*x[0]*vertices[13] + 4*x[0]*vertices[25] - 4*x[1]*vertices[19] + 4*x[1]*vertices[28]\n\trcol[2] = (-1 + 4*x[0])*vertices[5] + (-3 + 4*x[0] + 4*x[1] + 4*x[2])*vertices[2] + (4 - 8*x[0] - 4*x[1] - 4*x[2])*vertices[14] + 4*x[1]*vertices[17] - 4*x[1]*vertices[20] - 4*x[2]*vertices[23] + 4*x[2]*vertices[26]\n\tscol[2] = (-1 + 4*x[1])*vertices[8] + (-3 + 4*x[0] + 4*x[1] + 4*x[2])*vertices[2] + (4 - 4*x[0] - 8*x[1] - 4*x[2])*vertices[20] - 4*x[0]*vertices[14] + 4*x[0]*vertices[17] - 4*x[2]*vertices[23] + 4*x[2]*vertices[29]\n\ttcol[2] = (-1 + 4*x[2])*vertices[11] + (-3 + 4*x[0] + 4*x[1] + 4*x[2])*vertices[2] + (4 - 4*x[0] - 4*x[1] - 8*x[2])*vertices[23] - 4*x[0]*vertices[14] + 4*x[0]*vertices[26] - 4*x[1]*vertices[20] + 4*x[1]*vertices[29]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void T2Function2D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\tfx[0] = (-x[0] + 2*pow(x[0], 2))*vertices[2] + (-x[1] + 2*pow(x[1], 2))*vertices[4] + (-4*x[0]*x[1] + 4*x[1] - 4*pow(x[1], 2))*vertices[10] + (4*x[0] - 4*x[0]*x[1] - 4*pow(x[0], 2))*vertices[6] + (1 - 3*x[0] + 4*x[0]*x[1] + 2*pow(x[0], 2) - 3*x[1] + 2*pow(x[1], 2))*vertices[0] - phys_x[0] + 4*x[0]*x[1]*vertices[8]\n\tfx[1] = (-x[0] + 2*pow(x[0], 2))*vertices[3] + (-x[1] + 2*pow(x[1], 2))*vertices[5] + (-4*x[0]*x[1] + 4*x[1] - 4*pow(x[1], 2))*vertices[11] + (4*x[0] - 4*x[0]*x[1] - 4*pow(x[0], 2))*vertices[7] + (1 - 3*x[0] + 4*x[0]*x[1] + 2*pow(x[0], 2) - 3*x[1] + 2*pow(x[1], 2))*vertices[1] - phys_x[1] + 4*x[0]*x[1]*vertices[9]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void T2Jacobian2D(double* rcol,\n                       double* scol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\trcol[0] = (-1 + 4*x[0])*vertices[2] + (-3 + 4*x[0] + 4*x[1])*vertices[0] + (4 - 8*x[0] - 4*x[1])*vertices[6] + 4*x[1]*vertices[8] - 4*x[1]*vertices[10]\n\tscol[0] = (-1 + 4*x[1])*vertices[4] + (-3 + 4*x[0] + 4*x[1])*vertices[0] + (4 - 4*x[0] - 8*x[1])*vertices[10] - 4*x[0]*vertices[6] + 4*x[0]*vertices[8]\n\trcol[1] = (-1 + 4*x[0])*vertices[3] + (-3 + 4*x[0] + 4*x[1])*vertices[1] + (4 - 8*x[0] - 4*x[1])*vertices[7] + 4*x[1]*vertices[9] - 4*x[1]*vertices[11]\n\tscol[1] = (-1 + 4*x[1])*vertices[5] + (-3 + 4*x[0] + 4*x[1])*vertices[1] + (4 - 4*x[0] - 8*x[1])*vertices[11] - 4*x[0]*vertices[7] + 4*x[0]*vertices[9]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void W1Function3D(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\tfx[0] = 0.5*(1 - x[0] - x[1])*(1 - x[2])*vertices[0] + 0.5*(1 - x[0] - x[1])*(1 + x[2])*vertices[9] - phys_x[0] + 0.5*x[0]*(1 - x[2])*vertices[3] + 0.5*x[0]*(1 + x[2])*vertices[12] + 0.5*x[1]*(1 - x[2])*vertices[6] + 0.5*x[1]*(1 + x[2])*vertices[15]\n\tfx[1] = 0.5*(1 - x[0] - x[1])*(1 - x[2])*vertices[1] + 0.5*(1 - x[0] - x[1])*(1 + x[2])*vertices[10] - phys_x[1] + 0.5*x[0]*(1 - x[2])*vertices[4] + 0.5*x[0]*(1 + x[2])*vertices[13] + 0.5*x[1]*(1 - x[2])*vertices[7] + 0.5*x[1]*(1 + x[2])*vertices[16]\n\tfx[2] = 0.5*(1 - x[0] - x[1])*(1 - x[2])*vertices[2] + 0.5*(1 - x[0] - x[1])*(1 + x[2])*vertices[11] - phys_x[2] + 0.5*x[0]*(1 - x[2])*vertices[5] + 0.5*x[0]*(1 + x[2])*vertices[14] + 0.5*x[1]*(1 - x[2])*vertices[8] + 0.5*x[1]*(1 + x[2])*vertices[17]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void W1Jacobian3D(double* rcol,\n                       double* scol,\n                       double* tcol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil:\n\trcol[0] = -0.5*(1 - x[2])*vertices[0] + 0.5*(1 - x[2])*vertices[3] - 0.5*(1 + x[2])*vertices[9] + 0.5*(1 + x[2])*vertices[12]\n\tscol[0] = -0.5*(1 - x[2])*vertices[0] + 0.5*(1 - x[2])*vertices[6] - 0.5*(1 + x[2])*vertices[9] + 0.5*(1 + x[2])*vertices[15]\n\ttcol[0] = -0.5*(1 - x[0] - x[1])*vertices[0] + 0.5*(1 - x[0] - x[1])*vertices[9] - 0.5*x[0]*vertices[3] + 0.5*x[0]*vertices[12] - 0.5*x[1]*vertices[6] + 0.5*x[1]*vertices[15]\n\trcol[1] = -0.5*(1 - x[2])*vertices[1] + 0.5*(1 - x[2])*vertices[4] - 0.5*(1 + x[2])*vertices[10] + 0.5*(1 + x[2])*vertices[13]\n\tscol[1] = -0.5*(1 - x[2])*vertices[1] + 0.5*(1 - x[2])*vertices[7] - 0.5*(1 + x[2])*vertices[10] + 0.5*(1 + x[2])*vertices[16]\n\ttcol[1] = -0.5*(1 - x[0] - x[1])*vertices[1] + 0.5*(1 - x[0] - x[1])*vertices[10] - 0.5*x[0]*vertices[4] + 0.5*x[0]*vertices[13] - 0.5*x[1]*vertices[7] + 0.5*x[1]*vertices[16]\n\trcol[2] = -0.5*(1 - x[2])*vertices[2] + 0.5*(1 - x[2])*vertices[5] - 0.5*(1 + x[2])*vertices[11] + 0.5*(1 + x[2])*vertices[14]\n\tscol[2] = -0.5*(1 - x[2])*vertices[2] + 0.5*(1 - x[2])*vertices[8] - 0.5*(1 + x[2])*vertices[11] + 0.5*(1 + x[2])*vertices[17]\n\ttcol[2] = -0.5*(1 - x[0] - x[1])*vertices[2] + 0.5*(1 - x[0] - x[1])*vertices[11] - 0.5*x[0]*vertices[5] + 0.5*x[0]*vertices[14] - 0.5*x[1]*vertices[8] + 0.5*x[1]*vertices[17]\n"
  },
  {
    "path": "yt/utilities/lib/basic_octree.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nA refine-by-two AMR-specific octree\n\n\n\n\"\"\"\n\n\n\nimport numpy as np\n\ncimport cython\n\n# Double up here for def'd functions\ncimport numpy as np\ncimport numpy as cnp\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.lib.fp_utils cimport imax\n\nimport sys\n\n\ncdef extern from \"platform_dep.h\":\n    # NOTE that size_t might not be int\n    void *alloca(int)\n\ncdef extern from \"math.h\":\n    double sqrt(double x)\n\ncdef inline np.float64_t f64max(np.float64_t f0, np.float64_t f1):\n    if f0 > f1: return f0\n    return f1\n\ncdef struct OctreeNode:\n    np.float64_t *val\n    np.float64_t weight_val\n    np.int64_t pos[3]\n    np.uint64_t level\n    int nvals\n    int max_level # The maximum level under this node with mass.\n    OctreeNode *children[2][2][2]\n    OctreeNode *parent\n    OctreeNode *next\n    OctreeNode *up_next\n\ncdef void OTN_add_value(OctreeNode *self,\n        np.float64_t *val, np.float64_t weight_val, int level, int treecode):\n    cdef int i\n    for i in range(self.nvals):\n        self.val[i] += val[i]\n    self.weight_val += weight_val\n    if treecode and val[0] > 0.:\n        self.max_level = imax(self.max_level, level)\n\ncdef void OTN_refine(OctreeNode *self, int incremental = 0):\n    cdef int i, j, k\n    cdef np.int64_t npos[3]\n    for i in range(2):\n        npos[0] = self.pos[0] * 2 + i\n        for j in range(2):\n            npos[1] = self.pos[1] * 2 + j\n            # We have to be careful with allocation...\n            for k in range(2):\n                npos[2] = self.pos[2] * 2 + k\n                self.children[i][j][k] = OTN_initialize(\n                            npos,\n                            self.nvals, self.val, self.weight_val,\n                            self.level + 1, self, incremental)\n    if incremental: return\n    for i in range(self.nvals): self.val[i] = 0.0\n    self.weight_val = 0.0\n\ncdef OctreeNode *OTN_initialize(np.int64_t pos[3], int nvals,\n                        np.float64_t *val, np.float64_t weight_val,\n                        int level, OctreeNode *parent, int incremental = 0):\n    cdef OctreeNode *node\n    cdef int i, j, k\n    node = <OctreeNode *> malloc(sizeof(OctreeNode))\n    node.pos[0] = pos[0]\n    node.pos[1] = pos[1]\n    node.pos[2] = pos[2]\n    node.nvals = nvals\n    node.parent = parent\n    node.next = NULL\n    node.up_next = NULL\n    node.max_level = 0\n    node.val = <np.float64_t *> malloc(\n                nvals * sizeof(np.float64_t))\n    if incremental:\n        for i in range(nvals):\n            node.val[i] = 0.\n        node.weight_val = 0.\n    else:\n        for i in range(nvals):\n            node.val[i] = val[i]\n        node.weight_val = weight_val\n    for i in range(2):\n        for j in range(2):\n            for k in range(2):\n                node.children[i][j][k] = NULL\n    node.level = level\n    return node\n\ncdef void OTN_free(OctreeNode *node):\n    cdef int i, j, k\n    for i in range(2):\n        for j in range(2):\n            for k in range(2):\n                if node.children[i][j][k] == NULL: continue\n                OTN_free(node.children[i][j][k])\n    free(node.val)\n    free(node)\n\ncdef class Octree:\n    cdef int nvals\n    cdef np.int64_t po2[80]\n    cdef OctreeNode ****root_nodes\n    cdef np.int64_t top_grid_dims[3]\n    cdef int incremental\n    # Below is for the treecode.\n    cdef np.float64_t opening_angle\n    # We'll store dist here so it doesn't have to be calculated twice.\n    cdef np.float64_t dist\n    cdef np.float64_t root_dx[3]\n    cdef OctreeNode *last_node\n\n    def __cinit__(self, np.ndarray[np.int64_t, ndim=1] top_grid_dims,\n                  int nvals, int incremental = False):\n        cdef np.uint64_t i, j, k\n        self.incremental = incremental\n        cdef np.int64_t pos[3]\n        cdef np.float64_t *vals = <np.float64_t *> alloca(\n                sizeof(np.float64_t)*nvals)\n        cdef np.float64_t weight_val = 0.0\n        self.nvals = nvals\n        for i in range(nvals): vals[i] = 0.0\n\n        self.top_grid_dims[0] = top_grid_dims[0]\n        self.top_grid_dims[1] = top_grid_dims[1]\n        self.top_grid_dims[2] = top_grid_dims[2]\n\n        # This wouldn't be necessary if we did bitshifting...\n        for i in range(80):\n            self.po2[i] = 2**i\n        # Cython doesn't seem to like sizeof(OctreeNode ***)\n        self.root_nodes = <OctreeNode ****> \\\n            malloc(sizeof(void*) * top_grid_dims[0])\n\n        # We initialize our root values to 0.0.\n        for i in range(top_grid_dims[0]):\n            pos[0] = i\n            self.root_nodes[i] = <OctreeNode ***> \\\n                malloc(sizeof(OctreeNode **) * top_grid_dims[1])\n            for j in range(top_grid_dims[1]):\n                pos[1] = j\n                self.root_nodes[i][j] = <OctreeNode **> \\\n                    malloc(sizeof(OctreeNode *) * top_grid_dims[1])\n                for k in range(top_grid_dims[2]):\n                    pos[2] = k\n                    self.root_nodes[i][j][k] = OTN_initialize(\n                        pos, nvals, vals, weight_val, 0, NULL)\n\n    cdef void add_to_position(self,\n                 int level, np.int64_t pos[3],\n                 np.float64_t *val,\n                 np.float64_t weight_val, treecode):\n        cdef int i, j, k, L\n        cdef OctreeNode *node\n        node = self.find_on_root_level(pos, level)\n        cdef np.int64_t fac\n        for L in range(level):\n            if self.incremental:\n                OTN_add_value(node, val, weight_val, level, treecode)\n            if node.children[0][0][0] == NULL:\n                OTN_refine(node, self.incremental)\n            # Maybe we should use bitwise operators?\n            fac = self.po2[level - L - 1]\n            i = (pos[0] >= fac*(2*node.pos[0]+1))\n            j = (pos[1] >= fac*(2*node.pos[1]+1))\n            k = (pos[2] >= fac*(2*node.pos[2]+1))\n            node = node.children[i][j][k]\n        OTN_add_value(node, val, weight_val, level, treecode)\n\n    cdef OctreeNode *find_on_root_level(self, np.int64_t pos[3], int level):\n        # We need this because the root level won't just have four children\n        # So we find on the root level, then we traverse the tree.\n        cdef np.int64_t i, j, k\n        i = <np.int64_t> (pos[0] / self.po2[level])\n        j = <np.int64_t> (pos[1] / self.po2[level])\n        k = <np.int64_t> (pos[2] / self.po2[level])\n        return self.root_nodes[i][j][k]\n\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def add_array_to_tree(self, int level,\n            np.ndarray[np.int64_t, ndim=1] pxs,\n            np.ndarray[np.int64_t, ndim=1] pys,\n            np.ndarray[np.int64_t, ndim=1] pzs,\n            np.ndarray[np.float64_t, ndim=2] pvals,\n            np.ndarray[np.float64_t, ndim=1] pweight_vals,\n            int treecode = 0):\n        cdef int npx = pxs.shape[0]\n        cdef int p\n        cdef cnp.float64_t *vals\n        cdef cnp.float64_t *data = <cnp.float64_t *> pvals.data\n        cdef cnp.int64_t pos[3]\n        for p in range(npx):\n            vals = data + self.nvals*p\n            pos[0] = pxs[p]\n            pos[1] = pys[p]\n            pos[2] = pzs[p]\n            self.add_to_position(level, pos, vals, pweight_vals[p], treecode)\n\n    def add_grid_to_tree(self, int level,\n                         np.ndarray[np.int64_t, ndim=1] start_index,\n                         np.ndarray[np.float64_t, ndim=2] pvals,\n                         np.ndarray[np.float64_t, ndim=2] wvals,\n                         np.ndarray[np.int32_t, ndim=2] cm):\n        pass\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def get_all_from_level(self, int level, int count_only = 0):\n        cdef int i, j, k\n        cdef int total = 0\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                for k in range(self.top_grid_dims[2]):\n                    total += self.count_at_level(self.root_nodes[i][j][k], level)\n        if count_only: return total\n        # Allocate our array\n        cdef np.ndarray[np.int64_t, ndim=2] npos\n        cdef np.ndarray[np.float64_t, ndim=2] nvals\n        cdef np.ndarray[np.float64_t, ndim=1] nwvals\n        npos = np.zeros( (total, 3), dtype='int64')\n        nvals = np.zeros( (total, self.nvals), dtype='float64')\n        nwvals = np.zeros( total, dtype='float64')\n        cdef np.int64_t curpos = 0\n        cdef np.int64_t *pdata = <np.int64_t *> npos.data\n        cdef np.float64_t *vdata = <np.float64_t *> nvals.data\n        cdef np.float64_t *wdata = <np.float64_t *> nwvals.data\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                for k in range(self.top_grid_dims[2]):\n                    curpos += self.fill_from_level(self.root_nodes[i][j][k],\n                        level, curpos, pdata, vdata, wdata)\n        return npos, nvals, nwvals\n\n    cdef int count_at_level(self, OctreeNode *node, int level):\n        cdef int i, j, k\n        # We only really return a non-zero, calculated value if we are at the\n        # level in question.\n        if node.level == level:\n            if self.incremental: return 1\n            # We return 1 if there are no finer points at this level and zero\n            # if there are\n            return (node.children[0][0][0] == NULL)\n        if node.children[0][0][0] == NULL: return 0\n        cdef int count = 0\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    count += self.count_at_level(node.children[i][j][k], level)\n        return count\n\n    cdef int fill_from_level(self, OctreeNode *node, int level,\n                              np.int64_t curpos,\n                              np.int64_t *pdata,\n                              np.float64_t *vdata,\n                              np.float64_t *wdata):\n        cdef int i, j, k\n        if node.level == level:\n            if node.children[0][0][0] != NULL and not self.incremental:\n                return 0\n            for i in range(self.nvals):\n                vdata[self.nvals * curpos + i] = node.val[i]\n            wdata[curpos] = node.weight_val\n            pdata[curpos * 3] = node.pos[0]\n            pdata[curpos * 3 + 1] = node.pos[1]\n            pdata[curpos * 3 + 2] = node.pos[2]\n            return 1\n        if node.children[0][0][0] == NULL: return 0\n        cdef np.int64_t added = 0\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    added += self.fill_from_level(node.children[i][j][k],\n                            level, curpos + added, pdata, vdata, wdata)\n        return added\n\n    @cython.cdivision(True)\n    cdef np.float64_t fbe_node_separation(self, OctreeNode *node1, OctreeNode *node2):\n        # Find the distance between the two nodes.\n        cdef np.float64_t dx1, dx2, p1, p2, dist\n        cdef int i\n        dist = 0.0\n        for i in range(3):\n            # Discover the appropriate dx for each node/dim.\n            dx1 = self.root_dx[i] / (<np.float64_t> self.po2[node1.level])\n            dx2 = self.root_dx[i] / (<np.float64_t> self.po2[node2.level])\n            # The added term is to re-cell center the data.\n            p1 = (<np.float64_t> node1.pos[i]) * dx1 + dx1/2.\n            p2 = (<np.float64_t> node2.pos[i]) * dx2 + dx2/2.\n            dist += (p1 - p2) * (p1 - p2)\n        dist = sqrt(dist)\n        return dist\n\n    @cython.cdivision(True)\n    cdef np.float64_t fbe_opening_angle(self, OctreeNode *node1,\n            OctreeNode *node2):\n        # Calculate the opening angle of node2 upon the center of node1.\n        # In order to keep things simple, we will not assume symmetry in all\n        # three directions of the octree, and we'll use the largest dimension\n        # if the tree is not symmetric. This is not strictly the opening angle\n        # the purest sense, but it's slightly more accurate, so it's OK.\n        # This is done in code units to match the distance calculation.\n        cdef np.float64_t d2, dx2, dist\n        cdef np.int64_t n2\n        cdef int i\n        d2 = 0.0\n        if node1 is node2: return 100000.0 # Just some large number.\n        if self.top_grid_dims[1] == self.top_grid_dims[0] and \\\n                self.top_grid_dims[2] == self.top_grid_dims[0]:\n            # Symmetric\n            n2 = self.po2[node2.level] * self.top_grid_dims[0]\n            d2 = 1. / (<np.float64_t> n2)\n        else:\n            # Not symmetric\n            for i in range(3):\n                n2 = self.po2[node2.level] * self.top_grid_dims[i]\n                dx2 = 1. / (<np.float64_t> n2)\n                d2 = f64max(d2, dx2)\n        # Now calculate the opening angle.\n        dist = self.fbe_node_separation(node1, node2)\n        self.dist = dist\n        return d2 / dist\n\n    cdef void set_next(self, OctreeNode *node, int treecode):\n        # This sets up the linked list, pointing node.next to the next node\n        # in the iteration order.\n        cdef int i, j, k\n        if treecode and node.val[0] is not 0.:\n            # In a treecode, we only make a new next link if this node has mass.\n            self.last_node.next = node\n            self.last_node = node\n        elif treecode and node.val[0] is 0.:\n            # No mass means it's children have no mass, no need to dig an\n            # further.\n            return\n        else:\n            # We're not doing the treecode, but we still want a linked list,\n            # we don't care about val[0] necessarily.\n            self.last_node.next = node\n            self.last_node = node\n        if node.children[0][0][0] is NULL: return\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    self.set_next(node.children[i][j][k], treecode)\n        return\n\n    cdef void set_up_next(self, OctreeNode *node):\n        # This sets up a second linked list, pointing node.up_next to the next\n        # node in the list that is at the same or lower (coarser) level than\n        # this node. This is useful in the treecode for skipping over nodes\n        # that don't need to be inspected.\n        cdef OctreeNode *initial_next\n        cdef OctreeNode *temp_next\n        initial_next = node.next\n        temp_next = node.next\n        if node.next is NULL: return\n        while temp_next.level > node.level:\n            temp_next = temp_next.next\n            if temp_next is NULL: break\n        node.up_next = temp_next\n        self.set_up_next(initial_next)\n\n    def finalize(self, int treecode = 0):\n        # Set up the linked list for the nodes.\n        # Set treecode = 1 if nodes with no mass are to be skipped in the\n        # list.\n        cdef int i, j, k, sum, top_grid_total, ii, jj, kk\n        self.last_node = self.root_nodes[0][0][0]\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                for k in range(self.top_grid_dims[2]):\n                    self.set_next(self.root_nodes[i][j][k], treecode)\n        # Now we want to link to the next node in the list that is\n        # on a level the same or lower (coarser) than us. This will be used\n        # during a treecode search so we can skip higher-level (finer) nodes.\n        sum = 1\n        top_grid_total = self.top_grid_dims[0] * self.top_grid_dims[1] * \\\n            self.top_grid_dims[2]\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                for k in range(self.top_grid_dims[2]):\n                    self.set_up_next(self.root_nodes[i][j][k])\n                    # Point the root_nodes.up_next to the next root_node in the\n                    # list, except for the last one which stays pointing to NULL.\n                    if sum < top_grid_total - 1:\n                        ii = i\n                        jj = j\n                        kk = (k + 1) % self.top_grid_dims[2]\n                        if kk < k:\n                            jj = (j + 1) % self.top_grid_dims[1]\n                            if jj < j:\n                                ii = (i + 1) % self.top_grid_dims[0]\n                        self.root_nodes[i][j][k].up_next = \\\n                            self.root_nodes[ii][jj][kk]\n                    sum += 1\n\n    @cython.cdivision(True)\n    cdef np.float64_t fbe_main(self, np.float64_t potential, int truncate,\n            np.float64_t kinetic):\n        # The work is done here. Starting at the top of the linked list of\n        # nodes,\n        cdef np.float64_t angle, dist\n        cdef OctreeNode *this_node\n        cdef OctreeNode *pair_node\n        this_node = self.root_nodes[0][0][0]\n        while this_node is not NULL:\n            # Iterate down the list to a node that either has no children and\n            # is at the max_level of the tree, or to a node where\n            # all of its children are massless. The second case is when data\n            # from a level that isn't the deepest has been added to the tree.\n            while this_node.max_level is not this_node.level:\n                this_node = this_node.next\n                # In case we reach the end of the list...\n                if this_node is NULL: break\n            if this_node is NULL: break\n            if truncate and potential > kinetic:\n                print('Truncating...')\n                break\n            pair_node = this_node.next\n            while pair_node is not NULL:\n                # If pair_node is massless, we can jump to up_next, because\n                # nothing pair_node contains will have mass either.\n                # I think that this should primarily happen for root_nodes\n                # created for padding to make the region cubical.\n                if pair_node.val[0] is 0.0:\n                    pair_node = pair_node.up_next\n                    continue\n                # If pair_node is a childless node, or is a coarser node with\n                # no children, we can calculate the pot\n                # right now, and get a new pair_node.\n                if pair_node.max_level is pair_node.level:\n                    dist = self.fbe_node_separation(this_node, pair_node)\n                    potential += this_node.val[0] * pair_node.val[0] / dist\n                    if truncate and potential > kinetic: break\n                    pair_node = pair_node.next\n                    continue\n                # Next, if the opening angle to pair_node is small enough,\n                # calculate the potential and get a new pair_node using\n                # up_next because we don't need to look at pair_node's children.\n                angle = self.fbe_opening_angle(this_node, pair_node)\n                if angle < self.opening_angle:\n                    # self.dist was just set in fbe_opening_angle, so we\n                    # can use it here without re-calculating it for these two\n                    # nodes.\n                    potential += this_node.val[0] * pair_node.val[0] / self.dist\n                    if truncate and potential > kinetic: break\n                    # We can skip all the nodes that are contained within\n                    # pair_node, saving time walking the linked list.\n                    pair_node = pair_node.up_next\n                # If we've gotten this far, pair_node has children, but it's\n                # too coarse, so we simply dig deeper using .next.\n                else:\n                    pair_node = pair_node.next\n            # We've exhausted the pair_nodes.\n            # Now we find a new this_node in the list, and do more searches\n            # over pair_node.\n            this_node = this_node.next\n        return potential\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def find_binding_energy(self, int truncate, np.float64_t kinetic,\n        np.ndarray[np.float64_t, ndim=1] root_dx, float opening_angle = 1.0):\n        r\"\"\"Find the binding energy of an ensemble of data points using the\n        treecode method.\n\n        Note: The first entry of the vals array MUST be Mass. Any other\n        values will be ignored, including the weight array.\n        \"\"\"\n        # The real work is done in fbe_main(), this just sets things up\n        # and returns the potential.\n        cdef int i\n        cdef np.float64_t potential\n        potential = 0.0\n        self.opening_angle = opening_angle\n        for i in range(3):\n            self.root_dx[i] = root_dx[i]\n        potential = self.fbe_main(potential, truncate, kinetic)\n        return potential\n\n    cdef int node_ID(self, OctreeNode *node):\n        # Returns an unique ID for this node based on its position and level.\n        cdef int ID, offset, root\n        cdef np.uint64_t i\n        cdef np.int64_t this_grid_dims[3]\n        offset = 0\n        root = 1\n        for i in range(3):\n            root *= self.top_grid_dims[i]\n            this_grid_dims[i] = self.top_grid_dims[i] * 2**node.level\n        for i in range(node.level):\n            offset += root * 2**(3 * i)\n        ID = offset + (node.pos[0] + this_grid_dims[0] * (node.pos[1] + \\\n            this_grid_dims[1] * node.pos[2]))\n        return ID\n\n    cdef int node_ID_on_level(self, OctreeNode *node):\n        # Returns the node ID on node.level for this node.\n        cdef int ID, i\n        cdef np.int64_t this_grid_dims[3]\n        for i in range(3):\n            this_grid_dims[i] = self.top_grid_dims[i] * 2**node.level\n        ID = node.pos[0] + this_grid_dims[0] * (node.pos[1] + \\\n            this_grid_dims[1] * node.pos[2])\n        return ID\n\n    cdef void print_node_info(self, OctreeNode *node):\n        cdef int i, j, k\n        line = \"%d\\t\" % self.node_ID(node)\n        if node.next is not NULL:\n            line += \"%d\\t\" % self.node_ID(node.next)\n        else: line += \"-1\\t\"\n        if node.up_next is not NULL:\n            line += \"%d\\t\" % self.node_ID(node.up_next)\n        else: line += \"-1\\t\"\n        line += \"%d\\t%d\\t%d\\t%d\\t\" % (node.level,node.pos[0],node.pos[1],node.pos[2])\n        for i in range(node.nvals):\n            line += \"%1.5e\\t\" % node.val[i]\n        line += \"%f\\t\" % node.weight_val\n        line += \"%s\\t%s\\t\" % (node.children[0][0][0] is not NULL, node.parent is not NULL)\n        if node.children[0][0][0] is not NULL:\n            nline = \"\"\n            for i in range(2):\n                for j in range(2):\n                    for k in range(2):\n                        nline += \"%d,\" % self.node_ID(node.children[i][j][k])\n            line += nline\n        print(line)\n        return\n\n    cdef void iterate_print_nodes(self, OctreeNode *node):\n        cdef int i, j, k\n        self.print_node_info(node)\n        if node.children[0][0][0] is NULL:\n            return\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    self.iterate_print_nodes(node.children[i][j][k])\n        return\n\n    def print_all_nodes(self):\n        r\"\"\"\n        Prints out information about all the nodes in the octree.\n\n        Parameters\n        ----------\n        None.\n\n        Examples\n        --------\n        >>> octree.print_all_nodes()\n        (many lines of data)\n        \"\"\"\n        cdef int i, j, k\n        sys.stdout.flush()\n        sys.stderr.flush()\n        line = \"ID\\tnext\\tup_n\\tlevel\\tx\\ty\\tz\\t\"\n        for i in range(self.nvals):\n            line += \"val%d\\t\\t\" % i\n        line += \"weight\\t\\tchild?\\tparent?\\tchildren\"\n        print(line)\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                for k in range(self.top_grid_dims[2]):\n                    self.iterate_print_nodes(self.root_nodes[i][j][k])\n        sys.stdout.flush()\n        sys.stderr.flush()\n        return\n\n    def __dealloc__(self):\n        cdef int i, j, k\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                for k in range(self.top_grid_dims[2]):\n                    OTN_free(self.root_nodes[i][j][k])\n                free(self.root_nodes[i][j])\n            free(self.root_nodes[i])\n        free(self.root_nodes)\n"
  },
  {
    "path": "yt/utilities/lib/bitarray.pxd",
    "content": "\"\"\"\nBit array functions\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\n\ncdef inline void ba_set_value(np.uint8_t *buf, np.uint64_t ind,\n                              np.uint8_t val) noexcept nogil:\n    # This assumes 8 bit buffer.  If value is greater than zero (thus allowing\n    # us to use 1-255 as 'True') then we identify first the index in the buffer\n    # we are setting.  We do this by truncating the index by bit-shifting to\n    # the left three times, essentially dividing it by eight (and taking the\n    # floor.)\n    # The next step is to turn *on* what we're attempting to turn on, which\n    # means taking our index and truncating it to the first 3 bits (which we do\n    # with an & operation) and then turning on the correct bit.\n    #\n    # So if we're asking for index 33 in the bitarray, we would want the 4th\n    # uint8 element, then the 2nd bit (index 1).\n    #\n    # To turn it on, we logical *or* with that.  To turn it off, we logical\n    # *and* with the *inverse*, which will allow everything *but* that bit to\n    # stay on.\n    if val > 0:\n        buf[ind >> 3] |= (1 << (ind & 7))\n    else:\n        buf[ind >> 3] &= ~(1 << (ind & 7))\n\ncdef inline np.uint8_t ba_get_value(np.uint8_t *buf, np.uint64_t ind) noexcept nogil:\n    cdef np.uint8_t rv = (buf[ind >> 3] & (1 << (ind & 7)))\n    if rv == 0: return 0\n    return 1\n\ncdef inline void ba_set_range(np.uint8_t *buf, np.uint64_t start_ind,\n                              np.uint64_t stop_ind, np.uint8_t val) nogil:\n    # Should this be inclusive of both end points?  I think it should not, to\n    # match slicing semantics.\n    #\n    # We need to figure out the first and last values, and then we just set the\n    # ones in-between to 255.\n    if stop_ind < start_ind: return\n    cdef np.uint64_t i\n    cdef np.uint8_t j, bitmask\n    cdef np.uint64_t buf_start = start_ind >> 3\n    cdef np.uint64_t buf_stop = stop_ind >> 3\n    cdef np.uint8_t start_j = start_ind & 7\n    cdef np.uint8_t stop_j = stop_ind & 7\n    if buf_start == buf_stop:\n        for j in range(start_j, stop_j):\n            ba_set_value(&buf[buf_start], j, val)\n        return\n    bitmask = 0\n    for j in range(start_j, 8):\n        bitmask |= (1 << j)\n    if val > 0:\n        buf[buf_start] |= bitmask\n    else:\n        buf[buf_start] &= ~bitmask\n    if val > 0:\n        bitmask = 255\n    else:\n        bitmask = 0\n    for i in range(buf_start + 1, buf_stop):\n        buf[i] = bitmask\n    bitmask = 0\n    for j in range(0, stop_j):\n        bitmask |= (1 << j)\n    if val > 0:\n        buf[buf_stop] |= bitmask\n    else:\n        buf[buf_stop] &= ~bitmask\n\n\ncdef inline np.uint8_t _num_set_bits( np.uint8_t b ):\n    # https://stackoverflow.com/questions/30688465/how-to-check-the-number-of-set-bits-in-an-8-bit-unsigned-char\n    b = b - ((b >> 1) & 0x55)\n    b = (b & 0x33) + ((b >> 2) & 0x33)\n    return (((b + (b >> 4)) & 0x0F) * 0x01)\n\ncdef class bitarray:\n    cdef np.uint8_t *buf\n    cdef np.uint64_t size\n    cdef np.uint64_t buf_size # Not exactly the same\n    cdef np.uint8_t final_bitmask\n    cdef public object ibuf\n\n    cdef void _set_value(self, np.uint64_t ind, np.uint8_t val)\n    cdef np.uint8_t _query_value(self, np.uint64_t ind)\n    cdef void _set_range(self, np.uint64_t start, np.uint64_t stop, np.uint8_t val)\n    cdef np.uint64_t _count(self)\n    cdef bitarray _logical_and(self, bitarray other, bitarray result = *)\n    cdef bitarray _logical_or(self, bitarray other, bitarray result = *)\n    cdef bitarray _logical_xor(self, bitarray other, bitarray result = *)\n"
  },
  {
    "path": "yt/utilities/lib/bitarray.pyx",
    "content": "# distutils: libraries = STD_LIBS\n\"\"\"\nBit array functions\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\n\ncdef class bitarray:\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def __cinit__(self, np.int64_t size = -1,\n                  np.ndarray[np.uint8_t, ndim=1, cast=True] arr = None):\n        r\"\"\"This is a bitarray, which flips individual bits to on/off inside a\n        uint8 container array.\n\n        By encoding on/off inside each bit in a uint8 array, we can compress\n        boolean information down by up to a factor of 8.  Either an input array\n        or a size must be provided.\n\n        Parameters\n        ----------\n        size : int\n            The size we should pre-allocate.\n        arr : array-like\n            An input array to turn into a bitarray.\n\n        Examples\n        --------\n\n        >>> arr_in1 = np.array([True, True, False])\n        >>> arr_in2 = np.array([False, True, True])\n        >>> a = ba.bitarray(arr = arr_in1)\n        >>> b = ba.bitarray(arr = arr_in2)\n        >>> print(a & b)\n        >>> print (a & b).as_bool_array()\n\n        \"\"\"\n        cdef np.uint64_t i\n        if size == -1 and arr is None:\n            raise RuntimeError\n        elif size == -1:\n            size = arr.size\n        elif size != -1 and arr is not None:\n            if size != arr.size:\n                raise RuntimeError\n        self.buf_size = (size >> 3)\n        cdef np.uint8_t bitmask = 255\n        if (size & 7) != 0:\n            # We need an extra one if we've got any lingering bits\n            self.buf_size += 1\n            bitmask = 0\n            for i in range(size & 7):\n                bitmask |= (1<<i)\n        self.final_bitmask = bitmask\n        cdef np.ndarray[np.uint8_t] ibuf_t\n        ibuf_t = self.ibuf = np.zeros(self.buf_size, \"uint8\")\n        self.buf = <np.uint8_t *> ibuf_t.data\n        self.size = size\n        if arr is not None:\n            self.set_from_array(arr)\n        else:\n            for i in range(self.buf_size):\n                self.buf[i] = 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def set_from_array(self, np.ndarray[np.uint8_t, cast=True] arr not None):\n        r\"\"\"Given an array that is either uint8_t or boolean, set the values of\n        this array to match it.\n\n        Parameters\n        ----------\n        arr : array, castable to uint8\n            The array we set from.\n        \"\"\"\n        cdef np.uint64_t i, j\n        cdef np.uint8_t *btemp = self.buf\n        arr = np.ascontiguousarray(arr)\n        j = 0\n        for i in range(self.size):\n            btemp[i >> 3] = btemp[i >> 3] | (arr[i] << (j))\n            j += 1\n            if j == 8:\n                j = 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def as_bool_array(self):\n        r\"\"\"Return a copy of this array, as a boolean array.\n\n        All of the values encoded in this bitarray are expanded into boolean\n        values in a new array and returned.\n\n        Returns\n        -------\n        arr : numpy array of type bool\n            The uint8 values expanded into boolean values\n\n        \"\"\"\n        cdef np.uint64_t i, j\n        cdef np.uint8_t *btemp = self.buf\n        cdef np.ndarray[np.uint8_t, ndim=1] output\n        output = np.zeros(self.size, \"uint8\")\n        j = 0\n        for i in range(self.size):\n            output[i] = (btemp[i >> 3] >> (j)) & 1\n            j += 1\n            if j == 8:\n                j = 0\n        return output.astype(\"bool\")\n\n    cdef void _set_value(self, np.uint64_t ind, np.uint8_t val):\n        ba_set_value(self.buf, ind, val)\n\n    def set_value(self, np.uint64_t ind, np.uint8_t val):\n        r\"\"\"Set the on/off value of a given bit.\n\n        Modify the value encoded in a given index.\n\n        Parameters\n        ----------\n        ind : int\n            The index to query in the bitarray.\n        val : bool or uint8_t\n            What to set the index to\n\n        Examples\n        --------\n\n        >>> arr_in = np.array([True, True, False])\n        >>> a = ba.bitarray(arr = arr_in)\n        >>> print(a.set_value(2, 1))\n\n        \"\"\"\n        ba_set_value(self.buf, ind, val)\n\n    cdef np.uint8_t _query_value(self, np.uint64_t ind):\n        return ba_get_value(self.buf, ind)\n\n    def query_value(self, np.uint64_t ind):\n        r\"\"\"Query the on/off value of a given bit.\n\n        Return the value encoded in a given index.\n\n        Parameters\n        ----------\n        ind : int\n            The index to query in the bitarray.\n\n        Examples\n        --------\n\n        >>> arr_in = np.array([True, True, False])\n        >>> a = ba.bitarray(arr = arr_in)\n        >>> print(a.query_value(2))\n\n        \"\"\"\n        return ba_get_value(self.buf, ind)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void _set_range(self, np.uint64_t start, np.uint64_t stop, np.uint8_t val):\n        ba_set_range(self.buf, start, stop, val)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def set_range(self, np.uint64_t start, np.uint64_t stop, np.uint8_t val):\n        r\"\"\"Set a range of values to on/off.  Uses slice-style indexing.\n\n        No return value.\n\n        Parameters\n        ----------\n        start : int\n            The starting component of a slice.\n        stop : int\n            The ending component of a slice.\n        val : bool or uint8_t\n            What to set the range to\n\n        Examples\n        --------\n\n        >>> arr_in = np.array([True, True, False, True, True, False])\n        >>> a = ba.bitarray(arr = arr_in)\n        >>> a.set_range(0, 3, 0)\n\n        \"\"\"\n        ba_set_range(self.buf, start, stop, val)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef np.uint64_t _count(self):\n        cdef np.uint64_t count = 0\n        cdef np.uint64_t i\n        self.buf[self.buf_size - 1] &= self.final_bitmask\n        for i in range(self.buf_size):\n            count += _num_set_bits(self.buf[i])\n        return count\n\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def count(self):\n        r\"\"\"Count the number of values set in the array.\n\n        Parameters\n        ----------\n\n        Examples\n        --------\n\n        >>> arr_in = np.array([True, True, False, True, True, False])\n        >>> a = ba.bitarray(arr = arr_in)\n        >>> a.count()\n\n        \"\"\"\n        return self._count()\n\n    cdef bitarray _logical_and(self, bitarray other, bitarray result = None):\n        # Create a place to put it.  Note that we might have trailing values,\n        # we actually need to reset the ending set.\n        if other.size != self.size:\n            raise IndexError\n        if result is None:\n            result = bitarray(self.size)\n        for i in range(self.buf_size):\n            result.buf[i] = other.buf[i] & self.buf[i]\n        result.buf[self.buf_size - 1] &= self.final_bitmask\n        return result\n\n    def logical_and(self, bitarray other, bitarray result = None):\n        return self._logical_and(other, result)\n\n    def __and__(self, bitarray other):\n        # Wrap it directly here.\n        return self.logical_and(other)\n\n    def __iand__(self, bitarray other):\n        rv = self.logical_and(other, self)\n        return rv\n\n    cdef bitarray _logical_or(self, bitarray other, bitarray result = None):\n        if other.size != self.size:\n            raise IndexError\n        if result is None:\n            result = bitarray(self.size)\n        for i in range(self.buf_size):\n            result.buf[i] = other.buf[i] | self.buf[i]\n        result.buf[self.buf_size - 1] &= self.final_bitmask\n        return result\n\n    def logical_or(self, bitarray other, bitarray result = None):\n        return self._logical_or(other, result)\n\n    def __or__(self, bitarray other):\n        return self.logical_or(other)\n\n    def __ior__(self, bitarray other):\n        return self.logical_or(other, self)\n\n    cdef bitarray _logical_xor(self, bitarray other, bitarray result = None):\n        if other.size != self.size:\n            raise IndexError\n        if result is None:\n            result = bitarray(self.size)\n        for i in range(self.buf_size):\n            result.buf[i] = other.buf[i] ^ self.buf[i]\n        result.buf[self.buf_size - 1] &= self.final_bitmask\n        return result\n\n    def logical_xor(self, bitarray other, bitarray result = None):\n        return self._logical_xor(other, result)\n\n    def __xor__(self, bitarray other):\n        return self.logical_xor(other)\n\n    def __ixor__(self, bitarray other):\n        return self.logical_xor(other, self)\n"
  },
  {
    "path": "yt/utilities/lib/bounded_priority_queue.pxd",
    "content": "\"\"\"\nA cython implementation of the bounded priority queue\n\nThis is a priority queue that only keeps track of smallest k values that have\nbeen added to it.\n\n\n\"\"\"\n\nimport numpy as np\n\ncimport numpy as np\n\n\ncdef class BoundedPriorityQueue:\n    cdef public np.float64_t[:] heap\n    cdef np.float64_t* heap_ptr\n    cdef public np.int64_t[:] pids\n    cdef np.int64_t* pids_ptr\n    cdef int use_pids\n\n    cdef np.intp_t size\n    cdef np.intp_t max_elements\n\n    cdef int max_heapify(self, np.intp_t index) except -1 nogil\n    cdef int propagate_up(self, np.intp_t index) except -1 nogil\n    cdef int add(self, np.float64_t val) except -1 nogil\n    cdef int add_pid(self, np.float64_t val, np.int64_t pid) except -1 nogil\n    cdef int heap_append(self, np.float64_t val, np.int64_t ind) except -1 nogil\n    cdef np.float64_t extract_max(self) except -1 nogil\n    cdef int validate_heap(self) except -1 nogil\n\ncdef class NeighborList:\n    cdef public np.float64_t[:] data\n    cdef np.float64_t* data_ptr\n    cdef public np.int64_t[:] pids\n    cdef np.int64_t* pids_ptr\n    cdef np.intp_t size\n    cdef np.intp_t _max_size\n\n    cdef int _update_memview(self) except -1\n    cdef int _extend(self) except -1 nogil\n    cdef int add_pid(self, np.float64_t val, np.int64_t ind) except -1 nogil\n"
  },
  {
    "path": "yt/utilities/lib/bounded_priority_queue.pyx",
    "content": "\"\"\"\nA cython implementation of the bounded priority queue\n\nThis is a priority queue that only keeps track of smallest k values that have\nbeen added to it.\n\nThis priority queue is implemented with the configuration of having the largest\nelement at the beginning - this exploited to store nearest neighbour lists.\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc\n\n\ncdef class BoundedPriorityQueue:\n    def __cinit__(self, np.intp_t max_elements, np.intp_t pids=0):\n        self.max_elements = max_elements\n        # mark invalid recently  values with -1\n        self.heap = np.zeros(max_elements)-1\n        self.heap_ptr = &(self.heap[0])\n        # only allocate memory if we intend to store particle ID's\n        self.use_pids = pids\n        if pids == 1:\n            self.pids = np.zeros(max_elements, dtype=\"int64\")-1\n            self.pids_ptr = &(self.pids[0])\n\n        self.size = 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef int max_heapify(self, np.intp_t index) except -1 nogil:\n        cdef np.intp_t left = 2 * index + 1\n        cdef np.intp_t right = 2 * index + 2\n        cdef np.intp_t largest = index\n\n        if left < self.size and self.heap_ptr[left] > self.heap_ptr[largest]:\n            largest = left\n        if right < self.size and self.heap_ptr[right] > self.heap_ptr[largest]:\n            largest = right\n\n        if largest != index:\n            self.heap_ptr[index], self.heap_ptr[largest] = \\\n                self.heap_ptr[largest], self.heap_ptr[index]\n            if self.use_pids:\n                self.pids_ptr[index], self.pids_ptr[largest] = \\\n                    self.pids_ptr[largest], self.pids_ptr[index]\n\n            self.max_heapify(largest)\n\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef int propagate_up(self, np.intp_t index) except -1 nogil:\n        while index != 0 and self.heap_ptr[(index - 1) // 2] < self.heap_ptr[index]:\n            self.heap_ptr[index], self.heap_ptr[(index - 1) // 2] = self.heap_ptr[(index - 1) // 2], self.heap_ptr[index]\n            if self.use_pids:\n                self.pids_ptr[index], self.pids_ptr[(index - 1) // 2] = self.pids_ptr[(index - 1) // 2], self.pids_ptr[index]\n            index = (index - 1) // 2\n\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef int add(self, np.float64_t val) except -1 nogil:\n        # if not at max size append, if at max size, only append if smaller than\n        # the maximum value\n        if self.size == self.max_elements:\n            if val < self.heap_ptr[0]:\n                self.extract_max()\n                self.heap_append(val, -1)\n        else:\n            self.heap_append(val, -1)\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef int add_pid(self, np.float64_t val, np.int64_t ind) except -1 nogil:\n        if self.size == self.max_elements:\n            if val < self.heap_ptr[0]:\n                self.extract_max()\n                self.heap_append(val, ind)\n        else:\n            self.heap_append(val, ind)\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef int heap_append(self, np.float64_t val, np.int64_t ind) except -1 nogil:\n        self.heap_ptr[self.size] = val\n        if self.use_pids:\n            self.pids_ptr[self.size] = ind\n        self.size += 1\n        self.propagate_up(self.size - 1)\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef np.float64_t extract_max(self) except -1 nogil:\n        cdef np.float64_t maximum = self.heap_ptr[0]\n        cdef np.float64_t val\n        cdef np.int64_t ind\n\n        val = self.heap_ptr[self.size-1]\n        self.heap_ptr[self.size-1] = -1\n\n        if self.use_pids:\n            ind = self.pids_ptr[self.size-1]\n            self.pids_ptr[self.size-1] = -1\n\n        self.size -= 1\n        if self.size > 0:\n            self.heap_ptr[0] = val\n            if self.use_pids:\n                self.pids_ptr[0] = ind\n            self.max_heapify(0)\n        return maximum\n\n    cdef int validate_heap(self) except -1 nogil:\n        # this function loops through every element in the heap, if any children\n        # are greater than their parents then we return zero, which is an error\n        # as the heap condition is not satisfied\n        cdef int i, index\n        for i in range(self.size-1, -1, -1):\n            index = i\n            while index != 0:\n                if self.heap_ptr[index] > self.heap_ptr[(index - 1) // 2]:\n                    return 0\n                index = (index - 1) // 2\n        return 1\n\ncdef class NeighborList:\n    def __cinit__(self, np.intp_t init_size=32):\n        self.size = 0\n        self._max_size = init_size\n        self.data_ptr = <np.float64_t*> PyMem_Malloc(\n            self._max_size * sizeof(np.float64_t)\n        )\n        self.pids_ptr = <np.int64_t*> PyMem_Malloc(\n            self._max_size * sizeof(np.int64_t)\n        )\n        self._update_memview()\n\n    def __dealloc__(self):\n        PyMem_Free(self.data_ptr)\n        PyMem_Free(self.pids_ptr)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef int _update_memview(self) except -1:\n        self.data = <np.float64_t[:self._max_size]> self.data_ptr\n        self.pids = <np.int64_t[:self._max_size]> self.pids_ptr\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef int _extend(self) except -1 nogil:\n        if self.size == self._max_size:\n            self._max_size *= 2\n            with gil:\n                self.data_ptr = <np.float64_t*> PyMem_Realloc(\n                    self.data_ptr,\n                    self._max_size * sizeof(np.float64_t)\n                )\n                self.pids_ptr = <np.int64_t*> PyMem_Realloc(\n                    self.pids_ptr,\n                    self._max_size * sizeof(np.int64_t)\n                )\n                self._update_memview()\n        return 0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @cython.initializedcheck(False)\n    cdef int add_pid(self, np.float64_t val, np.int64_t ind) except -1 nogil:\n        self._extend()\n        self.data_ptr[self.size] = val\n        self.pids_ptr[self.size] = ind\n        self.size += 1\n        return 0\n\n# these are test functions which are called from\n# yt/utilities/lib/tests/test_nn.py\n# they are stored here to allow easy interaction with functions not exposed at\n# the python level\ndef validate_pid():\n    m = BoundedPriorityQueue(5, True)\n\n    # Add elements to the queue\n    elements = [0.1, 0.25, 1.33, 0.5, 3.2, 4.6, 2.0, 0.4, 4.0, .001]\n    pids = [1,2,3,4,5,6,7,8,9,10]\n\n    for el, pid in zip(elements, pids):\n        m.add_pid(el, pid)\n\n    m.extract_max()\n    m.extract_max()\n    m.extract_max()\n\n    return np.asarray(m.heap), np.asarray(m.pids)\n\ndef validate():\n    m = BoundedPriorityQueue(5)\n\n    # Add elements to the queue\n    for el in [0.1, 0.25, 1.33, 0.5, 3.2, 4.6, 2.0, 0.4, 4.0, .001]:\n        m.add(el)\n\n    m.extract_max()\n    m.extract_max()\n    m.extract_max()\n\n    return np.asarray(m.heap)\n\ndef validate_nblist():\n    nblist = NeighborList(init_size=2)\n\n    for i in range(4):\n        nblist.add_pid(1.0, i)\n\n    # Copy is necessary here. Without it, the allocated memory would be freed.\n    # Leaving random data array.\n    return np.asarray(nblist.data).copy(), np.asarray(nblist.pids).copy()\n"
  },
  {
    "path": "yt/utilities/lib/bounding_volume_hierarchy.pxd",
    "content": "cimport cython\n\nimport numpy as np\n\ncimport numpy as np\n\nfrom yt.utilities.lib.element_mappings cimport ElementSampler\nfrom yt.utilities.lib.primitives cimport BBox, Ray\n\n\ncdef extern from \"mesh_triangulation.h\":\n    enum:\n        MAX_NUM_TRI\n\n    int HEX_NV\n    int HEX_NT\n    int TETRA_NV\n    int TETRA_NT\n    int WEDGE_NV\n    int WEDGE_NT\n    int triangulate_hex[MAX_NUM_TRI][3]\n    int triangulate_tetra[MAX_NUM_TRI][3]\n    int triangulate_wedge[MAX_NUM_TRI][3]\n    int hex20_faces[6][8]\n    int tet10_faces[4][6]\n\n# node for the bounding volume hierarchy\ncdef struct BVHNode:\n    np.int64_t begin\n    np.int64_t end\n    BVHNode* left\n    BVHNode* right\n    BBox bbox\n\n# pointer to function that computes primitive intersection\nctypedef np.int64_t (*intersect_func_type)(const void* primitives,\n                                           const np.int64_t item,\n                                           Ray* ray) noexcept nogil\n\n# pointer to function that computes primitive centroids\nctypedef void (*centroid_func_type)(const void *primitives,\n                                    const np.int64_t item,\n                                    np.float64_t[3] centroid) noexcept nogil\n\n# pointer to function that computes primitive bounding boxes\nctypedef void (*bbox_func_type)(const void *primitives,\n                                const np.int64_t item,\n                                BBox* bbox) noexcept nogil\n\n\ncdef class BVH:\n    cdef BVHNode* root\n    cdef void* primitives\n    cdef np.int64_t* prim_ids\n    cdef np.float64_t** centroids\n    cdef BBox* bboxes\n    cdef np.float64_t* vertices\n    cdef np.float64_t* field_data\n    cdef np.int64_t num_prim_per_elem\n    cdef np.int64_t num_prim\n    cdef np.int64_t num_elem\n    cdef np.int64_t num_verts_per_elem\n    cdef np.int64_t num_field_per_elem\n    cdef int[MAX_NUM_TRI][3] tri_array\n    cdef ElementSampler sampler\n    cdef centroid_func_type get_centroid\n    cdef bbox_func_type get_bbox\n    cdef intersect_func_type get_intersect\n    cdef np.int64_t _partition(self, np.int64_t begin, np.int64_t end,\n                               np.int64_t ax, np.float64_t split) noexcept nogil\n    cdef void _set_up_triangles(self,\n                                np.float64_t[:, :] vertices,\n                                np.int64_t[:, :] indices) noexcept nogil\n    cdef void _set_up_patches(self,\n                              np.float64_t[:, :] vertices,\n                              np.int64_t[:, :] indices) noexcept nogil\n    cdef void _set_up_tet_patches(self,\n                              np.float64_t[:, :] vertices,\n                              np.int64_t[:, :] indices) noexcept nogil\n    cdef void intersect(self, Ray* ray) noexcept nogil\n    cdef void _get_node_bbox(self, BVHNode* node,\n                             np.int64_t begin, np.int64_t end) noexcept nogil\n    cdef void _recursive_intersect(self, Ray* ray, BVHNode* node) noexcept nogil\n    cdef BVHNode* _recursive_build(self, np.int64_t begin, np.int64_t end) noexcept nogil\n    cdef void _recursive_free(self, BVHNode* node) noexcept nogil\n"
  },
  {
    "path": "yt/utilities/lib/bounding_volume_hierarchy.pyx",
    "content": "# distutils: libraries = STD_LIBS\n# distutils: include_dirs = LIB_DIR\n# distutils: language = c++\n# distutils: extra_compile_args = CPP14_FLAG OMP_ARGS\n# distutils: extra_link_args = CPP14_FLAG OMP_ARGS\ncimport cython\n\nimport numpy as np\n\ncimport numpy as np\nfrom libc.math cimport fabs\nfrom libc.stdlib cimport free, malloc\n\nfrom cython.parallel import parallel, prange\n\nfrom yt.utilities.lib.element_mappings cimport (\n    ElementSampler,\n    P1Sampler3D,\n    Q1Sampler3D,\n    S2Sampler3D,\n    Tet2Sampler3D,\n    W1Sampler3D,\n)\nfrom yt.utilities.lib.primitives cimport (\n    BBox,\n    Patch,\n    Ray,\n    TetPatch,\n    Triangle,\n    patch_bbox,\n    patch_centroid,\n    ray_bbox_intersect,\n    ray_patch_intersect,\n    ray_tet_patch_intersect,\n    ray_triangle_intersect,\n    tet_patch_bbox,\n    tet_patch_centroid,\n    triangle_bbox,\n    triangle_centroid,\n)\n\nfrom .image_samplers cimport ImageSampler\n\n\ncdef ElementSampler Q1Sampler = Q1Sampler3D()\ncdef ElementSampler P1Sampler = P1Sampler3D()\ncdef ElementSampler W1Sampler = W1Sampler3D()\ncdef ElementSampler S2Sampler = S2Sampler3D()\ncdef ElementSampler Tet2Sampler = Tet2Sampler3D()\n\ncdef extern from \"platform_dep.h\" nogil:\n    double fmax(double x, double y)\n    double fmin(double x, double y)\n\n# define some constants\ncdef np.float64_t INF = np.inf\ncdef np.int64_t   LEAF_SIZE = 16\n\n\ncdef class BVH:\n    '''\n\n    This class implements a bounding volume hierarchy (BVH), a spatial acceleration\n    structure for fast ray-tracing. A BVH is like a kd-tree, except that instead of\n    partitioning the *volume* of the parent to create the children, we partition the\n    primitives themselves into 'left' or 'right' sub-trees. The bounding volume for a\n    node is then determined by computing the bounding volume of the primitives that\n    belong to it. This allows us to quickly discard primitives that are not close\n    to intersecting a given ray.\n\n    This class is currently used to provide software 3D rendering support for\n    finite element datasets. For 1st-order meshes, every element of the mesh is\n    triangulated, and this set of triangles forms the primitives that will be used\n    for the ray-trace. The BVH can then quickly determine which element is hit by\n    each ray associated with the image plane, and the appropriate interpolation can\n    be performed to sample the finite element solution at that hit position.\n\n    Currently, 2nd-order meshes are only supported for 20-node hexahedral elements.\n    There, the primitive type is a bi-quadratic patch instead of a triangle, and\n    each intersection involves computing a Newton-Raphson solve.\n\n    See yt/utilities/lib/primitives.pyx for the definitions of both of these primitive\n    types.\n\n    '''\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def __cinit__(self,\n                  np.float64_t[:, :] vertices,\n                  np.int64_t[:, :] indices,\n                  np.float64_t[:, :] field_data):\n\n        self.num_elem = indices.shape[0]\n        self.num_verts_per_elem = indices.shape[1]\n        self.num_field_per_elem = field_data.shape[1]\n\n        # We need to figure out what kind of elements we've been handed.\n        if self.num_verts_per_elem == 8:\n            self.num_prim_per_elem = HEX_NT\n            self.tri_array = triangulate_hex\n            self.sampler = Q1Sampler\n        elif self.num_verts_per_elem == 6:\n            self.num_prim_per_elem = WEDGE_NT\n            self.tri_array = triangulate_wedge\n            self.sampler = W1Sampler\n        elif self.num_verts_per_elem == 4:\n            self.num_prim_per_elem = TETRA_NT\n            self.tri_array = triangulate_tetra\n            self.sampler = P1Sampler\n        elif self.num_verts_per_elem == 20:\n            self.num_prim_per_elem = 6\n            self.sampler = S2Sampler\n        elif self.num_verts_per_elem == 10:\n            self.num_prim_per_elem = 4\n            self.sampler = Tet2Sampler\n        else:\n            raise NotImplementedError(\"Could not determine element type for \"\n                                      \"nverts = %d. \" % self.num_verts_per_elem)\n        self.num_prim = self.num_prim_per_elem*self.num_elem\n\n        # allocate storage\n        cdef np.int64_t v_size = self.num_verts_per_elem * self.num_elem * 3\n        self.vertices = <np.float64_t*> malloc(v_size * sizeof(np.float64_t))\n        cdef np.int64_t f_size = self.num_field_per_elem * self.num_elem\n        self.field_data = <np.float64_t*> malloc(f_size * sizeof(np.float64_t))\n        self.prim_ids = <np.int64_t*> malloc(self.num_prim * sizeof(np.int64_t))\n        self.centroids = <np.float64_t**> malloc(self.num_prim * sizeof(np.float64_t*))\n        cdef np.int64_t i\n        for i in range(self.num_prim):\n            self.centroids[i] = <np.float64_t*> malloc(3*sizeof(np.float64_t))\n        self.bboxes = <BBox*> malloc(self.num_prim * sizeof(BBox))\n\n        # create data buffers\n        cdef np.int64_t j, k\n        cdef np.int64_t field_offset, vertex_offset\n        for i in range(self.num_elem):\n            for j in range(self.num_verts_per_elem):\n                vertex_offset = i*self.num_verts_per_elem*3 + j*3\n                for k in range(3):\n                    self.vertices[vertex_offset + k] = vertices[indices[i,j]][k]\n            field_offset = i*self.num_field_per_elem\n            for j in range(self.num_field_per_elem):\n                self.field_data[field_offset + j] = field_data[i][j]\n\n        # set up primitives\n        if self.num_verts_per_elem == 20:\n            self.primitives = malloc(self.num_prim * sizeof(Patch))\n            self.get_centroid = patch_centroid\n            self.get_bbox = patch_bbox\n            self.get_intersect = ray_patch_intersect\n            self._set_up_patches(vertices, indices)\n        elif self.num_verts_per_elem == 10:\n            self.primitives = malloc(self.num_prim * sizeof(TetPatch))\n            self.get_centroid = tet_patch_centroid\n            self.get_bbox = tet_patch_bbox\n            self.get_intersect = ray_tet_patch_intersect\n            self._set_up_tet_patches(vertices, indices)\n        else:\n            self.primitives = malloc(self.num_prim * sizeof(Triangle))\n            self.get_centroid = triangle_centroid\n            self.get_bbox = triangle_bbox\n            self.get_intersect = ray_triangle_intersect\n            self._set_up_triangles(vertices, indices)\n\n        self.root = self._recursive_build(0, self.num_prim)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void _set_up_patches(self, np.float64_t[:, :] vertices,\n                              np.int64_t[:, :] indices) noexcept nogil:\n        cdef Patch* patch\n        cdef np.int64_t i, j, k, ind, idim\n        cdef np.int64_t offset, prim_index\n        for i in range(self.num_elem):\n            offset = self.num_prim_per_elem*i\n            for j in range(self.num_prim_per_elem):  # for each face\n                prim_index = offset + j\n                patch = &( <Patch*> self.primitives)[prim_index]\n                self.prim_ids[prim_index] = prim_index\n                patch.elem_id = i\n                for k in range(8):  # for each vertex\n                    ind = hex20_faces[j][k]\n                    for idim in range(3):  # for each spatial dimension (yikes)\n                        patch.v[k][idim] = vertices[indices[i, ind]][idim]\n                self.get_centroid(self.primitives,\n                                  prim_index,\n                                  self.centroids[prim_index])\n                self.get_bbox(self.primitives,\n                              prim_index,\n                              &(self.bboxes[prim_index]))\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void _set_up_tet_patches(self, np.float64_t[:, :] vertices,\n                              np.int64_t[:, :] indices) noexcept nogil:\n        cdef TetPatch* tet_patch\n        cdef np.int64_t i, j, k, ind, idim\n        cdef np.int64_t offset, prim_index\n        for i in range(self.num_elem):\n            offset = self.num_prim_per_elem*i\n            for j in range(self.num_prim_per_elem):  # for each face\n                prim_index = offset + j\n                tet_patch = &( <TetPatch*> self.primitives)[prim_index]\n                self.prim_ids[prim_index] = prim_index\n                tet_patch.elem_id = i\n                for k in range(6):  # for each vertex\n                    ind = tet10_faces[j][k]\n                    for idim in range(3):  # for each spatial dimension (yikes)\n                        tet_patch.v[k][idim] = vertices[indices[i, ind]][idim]\n                self.get_centroid(self.primitives,\n                                  prim_index,\n                                  self.centroids[prim_index])\n                self.get_bbox(self.primitives,\n                              prim_index,\n                              &(self.bboxes[prim_index]))\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void _set_up_triangles(self, np.float64_t[:, :] vertices,\n                                np.int64_t[:, :] indices) noexcept nogil:\n        # fill our array of primitives\n        cdef np.int64_t offset, tri_index\n        cdef np.int64_t v0, v1, v2\n        cdef Triangle* tri\n        cdef np.int64_t i, j, k\n        for i in range(self.num_elem):\n            offset = self.num_prim_per_elem*i\n            for j in range(self.num_prim_per_elem):\n                tri_index = offset + j\n                self.prim_ids[tri_index] = tri_index\n                tri = &(<Triangle*> self.primitives)[tri_index]\n                tri.elem_id = i\n                v0 = indices[i][self.tri_array[j][0]]\n                v1 = indices[i][self.tri_array[j][1]]\n                v2 = indices[i][self.tri_array[j][2]]\n                for k in range(3):\n                    tri.p0[k] = vertices[v0][k]\n                    tri.p1[k] = vertices[v1][k]\n                    tri.p2[k] = vertices[v2][k]\n                self.get_centroid(self.primitives,\n                                  tri_index,\n                                  self.centroids[tri_index])\n                self.get_bbox(self.primitives,\n                              tri_index,\n                              &(self.bboxes[tri_index]))\n\n    cdef void _recursive_free(self, BVHNode* node) noexcept nogil:\n        if node.end - node.begin > LEAF_SIZE:\n            self._recursive_free(node.left)\n            self._recursive_free(node.right)\n        free(node)\n\n    def __dealloc__(self):\n        if self.root == NULL:\n            return\n        self._recursive_free(self.root)\n        free(self.primitives)\n        free(self.prim_ids)\n        for i in range(self.num_prim):\n            free(self.centroids[i])\n        free(self.centroids)\n        free(self.bboxes)\n        free(self.field_data)\n        free(self.vertices)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef np.int64_t _partition(self, np.int64_t begin, np.int64_t end,\n                               np.int64_t ax, np.float64_t split) noexcept nogil:\n        # this re-orders the primitive array so that all of the primitives\n        # to the left of mid have centroids less than or equal to \"split\"\n        # along the direction \"ax\". All the primitives to the right of mid\n        # will have centroids *greater* than \"split\" along \"ax\".\n        cdef np.int64_t mid = begin\n        while (begin != end):\n            if self.centroids[mid][ax] > split:\n                mid += 1\n            elif self.centroids[begin][ax] > split:\n                self.prim_ids[mid], self.prim_ids[begin] = \\\n                self.prim_ids[begin], self.prim_ids[mid]\n                self.centroids[mid], self.centroids[begin] = \\\n                self.centroids[begin], self.centroids[mid]\n                self.bboxes[mid], self.bboxes[begin] = \\\n                self.bboxes[begin], self.bboxes[mid]\n                mid += 1\n            begin += 1\n        return mid\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void _get_node_bbox(self, BVHNode* node,\n                             np.int64_t begin, np.int64_t end) noexcept nogil:\n        cdef np.int64_t i, j\n        cdef BBox box = self.bboxes[begin]\n        for i in range(begin+1, end):\n            for j in range(3):\n                box.left_edge[j] = fmin(box.left_edge[j],\n                                        self.bboxes[i].left_edge[j])\n                box.right_edge[j] = fmax(box.right_edge[j],\n                                         self.bboxes[i].right_edge[j])\n        node.bbox = box\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void intersect(self, Ray* ray) noexcept nogil:\n        self._recursive_intersect(ray, self.root)\n\n        if ray.elem_id < 0:\n            return\n\n        cdef np.float64_t[3] position\n        cdef np.int64_t i\n        for i in range(3):\n            position[i] = ray.origin[i] + ray.t_far*ray.direction[i]\n\n        cdef np.float64_t* vertex_ptr\n        cdef np.float64_t* field_ptr\n        vertex_ptr = self.vertices + ray.elem_id*self.num_verts_per_elem*3\n        field_ptr = self.field_data + ray.elem_id*self.num_field_per_elem\n\n        cdef np.float64_t[4] mapped_coord\n        self.sampler.map_real_to_unit(mapped_coord, vertex_ptr, position)\n        if self.num_field_per_elem == 1:\n            ray.data_val = field_ptr[0]\n        else:\n            ray.data_val = self.sampler.sample_at_unit_point(mapped_coord,\n                                                             field_ptr)\n        ray.near_boundary = self.sampler.check_mesh_lines(mapped_coord)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void _recursive_intersect(self, Ray* ray, BVHNode* node) noexcept nogil:\n\n        # check for bbox intersection:\n        if not ray_bbox_intersect(ray, node.bbox):\n            return\n\n        # check for leaf\n        cdef np.int64_t i\n        if (node.end - node.begin) <= LEAF_SIZE:\n            for i in range(node.begin, node.end):\n                self.get_intersect(self.primitives, self.prim_ids[i], ray)\n            return\n\n        # if not leaf, intersect with left and right children\n        self._recursive_intersect(ray, node.left)\n        self._recursive_intersect(ray, node.right)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef BVHNode* _recursive_build(self, np.int64_t begin, np.int64_t end) noexcept nogil:\n        cdef BVHNode *node = <BVHNode* > malloc(sizeof(BVHNode))\n        node.begin = begin\n        node.end = end\n\n        self._get_node_bbox(node, begin, end)\n\n        # check for leaf\n        if (end - begin) <= LEAF_SIZE:\n            return node\n\n        # we use the \"split in the middle of the longest axis approach\"\n        # see: http://www.vadimkravcenko.com/bvh-tree-building/\n\n        # compute longest dimension\n        cdef np.int64_t ax = 0\n        cdef np.float64_t d = fabs(node.bbox.right_edge[0] -\n                                   node.bbox.left_edge[0])\n        if fabs(node.bbox.right_edge[1] - node.bbox.left_edge[1]) > d:\n            ax = 1\n        if fabs(node.bbox.right_edge[2] - node.bbox.left_edge[2]) > d:\n            ax = 2\n\n        # split in half along that dimension\n        cdef np.float64_t split = 0.5*(node.bbox.right_edge[ax] +\n                                       node.bbox.left_edge[ax])\n\n        # sort triangle list\n        cdef np.int64_t mid = self._partition(begin, end, ax, split)\n\n        if(mid == begin or mid == end):\n            mid = begin + (end-begin)/2\n\n        # recursively build sub-trees\n        node.left = self._recursive_build(begin, mid)\n        node.right = self._recursive_build(mid, end)\n\n        return node\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void cast_rays(np.float64_t* image,\n                    const np.float64_t* origins,\n                    const np.float64_t* direction,\n                    const int N,\n                    BVH bvh) noexcept nogil:\n\n    cdef Ray* ray\n    cdef int i, j, k\n\n    with nogil, parallel():\n\n        ray = <Ray *> malloc(sizeof(Ray))\n\n        for k in range(3):\n            ray.direction[k] = direction[k]\n            ray.inv_dir[k] = 1.0 / direction[k]\n\n        for i in prange(N):\n            for j in range(3):\n                ray.origin[j] = origins[N*j + i]\n            ray.t_far = INF\n            ray.t_near = 0.0\n            ray.data_val = 0\n            ray.elem_id = -1\n            bvh.intersect(ray)\n            image[i] = ray.data_val\n\n        free(ray)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_ray_trace(np.ndarray[np.float64_t, ndim=1] image,\n                   np.ndarray[np.float64_t, ndim=2] origins,\n                   np.ndarray[np.float64_t, ndim=1] direction,\n                   BVH bvh):\n\n    cdef int N = origins.shape[0]\n    cast_rays(&image[0], &origins[0, 0], &direction[0], N, bvh)\n\ncdef class BVHMeshSampler(ImageSampler):\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def __call__(self,\n                 BVH bvh,\n                 int num_threads = 0):\n        '''\n\n        This function is supposed to cast the rays and return the\n        image.\n\n        '''\n\n        cdef int vi, vj, i, j\n        cdef np.float64_t *v_pos\n        cdef np.float64_t *v_dir\n        cdef np.int64_t nx, ny, size\n        cdef np.float64_t width[3]\n        for i in range(3):\n            width[i] = self.width[i]\n        nx = self.nv[0]\n        ny = self.nv[1]\n        size = nx * ny\n        cdef Ray* ray\n        with nogil, parallel():\n            ray = <Ray *> malloc(sizeof(Ray))\n            v_pos = <np.float64_t *> malloc(3 * sizeof(np.float64_t))\n            v_dir = <np.float64_t *> malloc(3 * sizeof(np.float64_t))\n            for j in prange(size):\n                vj = j % ny\n                vi = (j - vj) / ny\n                vj = vj\n                self.vector_function(self, vi, vj, width, v_dir, v_pos)\n                for i in range(3):\n                    ray.origin[i] = v_pos[i]\n                    ray.direction[i] = v_dir[i]\n                    ray.inv_dir[i] = 1.0 / v_dir[i]\n                ray.t_far = 1e37\n                ray.t_near = 0.0\n                ray.data_val = 0\n                ray.elem_id = -1\n                bvh.intersect(ray)\n                self.image[vi, vj, 0] = ray.data_val\n                self.image_used[vi, vj] = ray.elem_id\n                self.mesh_lines[vi, vj] = ray.near_boundary\n                self.zbuffer[vi, vj] = ray.t_far\n            free(v_pos)\n            free(v_dir)\n            free(ray)\n"
  },
  {
    "path": "yt/utilities/lib/contour_finding.pxd",
    "content": "\"\"\"\nContour finding exports\n\n\n\n\"\"\"\n\n\n\ncimport cython\ncimport numpy as np\n\n\ncdef inline np.int64_t i64max(np.int64_t i0, np.int64_t i1):\n    if i0 > i1: return i0\n    return i1\n\ncdef inline np.int64_t i64min(np.int64_t i0, np.int64_t i1):\n    if i0 < i1: return i0\n    return i1\n\ncdef extern from \"math.h\":\n    double fabs(double x)\n\ncdef extern from \"stdlib.h\":\n    # NOTE that size_t might not be int\n    void *alloca(int)\n\ncdef struct ContourID\n\ncdef struct ContourID:\n    np.int64_t contour_id\n    ContourID *parent\n    ContourID *next\n    ContourID *prev\n    np.int64_t count\n\ncdef struct CandidateContour\n\ncdef struct CandidateContour:\n    np.int64_t contour_id\n    np.int64_t join_id\n    CandidateContour *next\n\ncdef ContourID *contour_create(np.int64_t contour_id,\n                               ContourID *prev = ?)\ncdef void contour_delete(ContourID *node)\ncdef ContourID *contour_find(ContourID *node)\ncdef void contour_union(ContourID *node1, ContourID *node2)\ncdef int candidate_contains(CandidateContour *first,\n                            np.int64_t contour_id,\n                            np.int64_t join_id = ?)\ncdef CandidateContour *candidate_add(CandidateContour *first,\n                                     np.int64_t contour_id,\n                                     np.int64_t join_id = ?)\n"
  },
  {
    "path": "yt/utilities/lib/contour_finding.pyx",
    "content": "# distutils: libraries = STD_LIBS\n# distutils: include_dirs = LIB_DIR_GEOM\n\"\"\"\nA two-pass contour finding algorithm\n\n\n\n\"\"\"\n\nfrom __future__ import print_function\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.stdlib cimport free, malloc, realloc\n\nfrom yt.geometry.oct_container cimport OctInfo, OctreeContainer\nfrom yt.geometry.oct_visitors cimport Oct\n\nfrom .amr_kdtools cimport Node\nfrom .partitioned_grid cimport PartitionedGrid\nfrom .volume_container cimport VolumeContainer, vc_index, vc_pos_index\n\nimport sys\n\n\ncdef inline ContourID *contour_create(np.int64_t contour_id,\n                               ContourID *prev = NULL):\n    node = <ContourID *> malloc(sizeof(ContourID))\n    #print(\"Creating contour with id\", contour_id)\n    node.contour_id = contour_id\n    node.next = node.parent = NULL\n    node.prev = prev\n    node.count = 1\n    if prev != NULL: prev.next = node\n    return node\n\ncdef inline void contour_delete(ContourID *node):\n    if node.prev != NULL: node.prev.next = node.next\n    if node.next != NULL: node.next.prev = node.prev\n    free(node)\n\ncdef inline ContourID *contour_find(ContourID *node):\n    cdef ContourID *temp\n    cdef ContourID *root\n    root = node\n    # First we find the root\n    while root.parent != NULL and root.parent != root:\n        root = root.parent\n    if root == root.parent: root.parent = NULL\n    # Now, we update everything along the tree.\n    # So now everything along the line to the root has the parent set to the\n    # root.\n    while node.parent != NULL:\n        temp = node.parent\n        root.count += node.count\n        node.count = 0\n        node.parent = root\n        node = temp\n    return root\n\ncdef inline void contour_union(ContourID *node1, ContourID *node2):\n    if node1 == node2:\n        return\n    node1 = contour_find(node1)\n    node2 = contour_find(node2)\n    if node1 == node2:\n        return\n    cdef ContourID *pri\n    cdef ContourID *sec\n    if node1.count > node2.count:\n        pri = node1\n        sec = node2\n    elif node2.count > node1.count:\n        pri = node2\n        sec = node1\n    # might be a tie\n    elif node1.contour_id < node2.contour_id:\n        pri = node1\n        sec = node2\n    else:\n        pri = node2\n        sec = node1\n    pri.count += sec.count\n    sec.count = 0\n    sec.parent = pri\n\ncdef inline int candidate_contains(CandidateContour *first,\n                            np.int64_t contour_id,\n                            np.int64_t join_id = -1):\n    while first != NULL:\n        if first.contour_id == contour_id \\\n            and first.join_id == join_id: return 1\n        first = first.next\n    return 0\n\ncdef inline CandidateContour *candidate_add(CandidateContour *first,\n                                     np.int64_t contour_id,\n                                     np.int64_t join_id = -1):\n    cdef CandidateContour *node\n    node = <CandidateContour *> malloc(sizeof(CandidateContour))\n    node.contour_id = contour_id\n    node.join_id = join_id\n    node.next = first\n    return node\n\ncdef class ContourTree:\n    # This class is essentially a Union-Find algorithm.  What we want to do is\n    # to, given a connection between two objects, identify the unique ID for\n    # those two objects.  So what we have is a collection of contours, and they\n    # eventually all get joined and contain lots of individual IDs.  But it's\n    # easy to find the *first* contour, i.e., the primary ID, for each of the\n    # subsequent IDs.\n    #\n    # This means that we can connect id 202483 to id 2472, and if id 2472 is\n    # connected to id 143, the connection will *actually* be from 202483 to\n    # 143.  In this way we can speed up joining things and knowing their\n    # \"canonical\" id.\n    #\n    # This is a multi-step process, since we first want to connect all of the\n    # contours, then we end up wanting to coalesce them, and ultimately we join\n    # them at the end.  The join produces a table that maps the initial to the\n    # final, and we can go through and just update all of those.\n    cdef ContourID *first\n    cdef ContourID *last\n\n    def clear(self):\n        # Here, we wipe out ALL of our contours, but not the pointers to them\n        cdef ContourID *cur\n        cdef ContourID *next\n        cur = self.first\n        while cur != NULL:\n            next = cur.next\n            free(cur)\n            cur = next\n        self.first = self.last = NULL\n\n    def __init__(self):\n        self.first = self.last = NULL\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def add_contours(self, np.ndarray[np.int64_t, ndim=1] contour_ids):\n        # This adds new contours, from the given contour IDs, to the tree.\n        # Each one can be connected to a parent, as well as to next/prev in the\n        # set of contours belonging to this tree.\n        cdef int i, n\n        n = contour_ids.shape[0]\n        cdef ContourID *cur = self.last\n        for i in range(n):\n            #print(i, contour_ids[i])\n            cur = contour_create(contour_ids[i], cur)\n            if self.first == NULL: self.first = cur\n        self.last = cur\n\n    def add_contour(self, np.int64_t contour_id):\n        self.last = contour_create(contour_id, self.last)\n\n    def cull_candidates(self, np.ndarray[np.int64_t, ndim=3] candidates):\n        # This function looks at each preliminary contour ID belonging to a\n        # given collection of values, and then if need be it creates a new\n        # contour for it.\n        cdef int i, j, k, ni, nj, nk, nc\n        cdef CandidateContour *first = NULL\n        cdef CandidateContour *temp\n        cdef np.int64_t cid\n        nc = 0\n        ni = candidates.shape[0]\n        nj = candidates.shape[1]\n        nk = candidates.shape[2]\n        for i in range(ni):\n            for j in range(nj):\n                for k in range(nk):\n                    cid = candidates[i,j,k]\n                    if cid == -1: continue\n                    if candidate_contains(first, cid) == 0:\n                        nc += 1\n                        first = candidate_add(first, cid)\n        cdef np.ndarray[np.int64_t, ndim=1] contours\n        contours = np.empty(nc, dtype=\"int64\")\n        i = 0\n        # This removes all the temporary contours for this set of contours and\n        # instead constructs a final list of them.\n        while first != NULL:\n            contours[i] = first.contour_id\n            i += 1\n            temp = first.next\n            free(first)\n            first = temp\n        return contours\n\n    def cull_joins(self, np.ndarray[np.int64_t, ndim=2] cjoins):\n        # This coalesces contour IDs, so that we have only the final name\n        # resolutions -- the .join_id from a candidate.  So many items will map\n        # to a single join_id.\n        cdef int i, ni, nc\n        cdef CandidateContour *first = NULL\n        cdef CandidateContour *temp\n        cdef np.int64_t cid1, cid2\n        nc = 0\n        ni = cjoins.shape[0]\n        for i in range(ni):\n            cid1 = cjoins[i,0]\n            cid2 = cjoins[i,1]\n            if cid1 == -1: continue\n            if cid2 == -1: continue\n            if candidate_contains(first, cid1, cid2) == 0:\n                nc += 1\n                first = candidate_add(first, cid1, cid2)\n        cdef np.ndarray[np.int64_t, ndim=2] contours\n        contours = np.empty((nc,2), dtype=\"int64\")\n        i = 0\n        while first != NULL:\n            contours[i,0] = first.contour_id\n            contours[i,1] = first.join_id\n            i += 1\n            temp = first.next\n            free(first)\n            first = temp\n        return contours\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def add_joins(self, np.ndarray[np.int64_t, ndim=2] join_tree):\n        cdef int i, n, ins\n        cdef np.int64_t cid1, cid2\n        # Okay, this requires lots of iteration, unfortunately\n        cdef ContourID *cur\n        cdef ContourID *c1\n        cdef ContourID *c2\n        n = join_tree.shape[0]\n        #print(\"Counting\")\n        #print(\"Checking\", self.count())\n        for i in range(n):\n            ins = 0\n            cid1 = join_tree[i, 0]\n            cid2 = join_tree[i, 1]\n            c1 = c2 = NULL\n            cur = self.first\n            #print(\"Looking for \", cid1, cid2)\n            while c1 == NULL or c2 == NULL:\n                if cur.contour_id == cid1:\n                    c1 = contour_find(cur)\n                if cur.contour_id == cid2:\n                    c2 = contour_find(cur)\n                ins += 1\n                cur = cur.next\n                if cur == NULL: break\n            if c1 == NULL or c2 == NULL:\n                if c1 == NULL: print(\"  Couldn't find \", cid1)\n                if c2 == NULL: print(\"  Couldn't find \", cid2)\n                print(\"  Inspected \", ins)\n                raise RuntimeError\n            else:\n                c1.count = c2.count = 0\n                contour_union(c1, c2)\n\n    def count(self):\n        cdef int n = 0\n        cdef ContourID *cur = self.first\n        while cur != NULL:\n            cur = cur.next\n            n += 1\n        return n\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def export(self):\n        cdef int n = self.count()\n        cdef ContourID *cur\n        cdef ContourID *root\n        cur = self.first\n        cdef np.ndarray[np.int64_t, ndim=2] joins\n        joins = np.empty((n, 2), dtype=\"int64\")\n        n = 0\n        while cur != NULL:\n            root = contour_find(cur)\n            joins[n, 0] = cur.contour_id\n            joins[n, 1] = root.contour_id\n            cur = cur.next\n            n += 1\n        return joins\n\n    def __dealloc__(self):\n        self.clear()\n\ncdef class TileContourTree:\n    cdef np.float64_t min_val\n    cdef np.float64_t max_val\n\n    def __init__(self, np.float64_t min_val, np.float64_t max_val):\n        self.min_val = min_val\n        self.max_val = max_val\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def identify_contours(self, np.ndarray[np.float64_t, ndim=3] values,\n                                np.ndarray[np.int64_t, ndim=3] contour_ids,\n                                np.ndarray[np.uint8_t, ndim=3] mask,\n                                np.int64_t start):\n        # This just looks at neighbor values and tries to identify which zones\n        # are touching by face within a given brick.\n        cdef int i, j, k, ni, nj, nk, offset\n        cdef int off_i, off_j, off_k, oi, ok, oj\n        cdef ContourID *cur = NULL\n        cdef ContourID *c1\n        cdef ContourID *c2\n        cdef np.float64_t v\n        cdef np.int64_t nc\n        ni = values.shape[0]\n        nj = values.shape[1]\n        nk = values.shape[2]\n        nc = 0\n        cdef ContourID **container = <ContourID**> malloc(\n                sizeof(ContourID*)*ni*nj*nk)\n        for i in range(ni*nj*nk): container[i] = NULL\n        for i in range(ni):\n            for j in range(nj):\n                for k in range(nk):\n                    v = values[i,j,k]\n                    if mask[i,j,k] == 0: continue\n                    if v < self.min_val or v > self.max_val: continue\n                    nc += 1\n                    c1 = contour_create(nc + start)\n                    cur = container[i*nj*nk + j*nk + k] = c1\n                    for oi in range(3):\n                        off_i = oi - 1 + i\n                        if not (0 <= off_i < ni): continue\n                        for oj in range(3):\n                            off_j = oj - 1 + j\n                            if not (0 <= off_j < nj): continue\n                            for ok in range(3):\n                                if oi == oj == ok == 1: continue\n                                off_k = ok - 1 + k\n                                if not (0 <= off_k < nk): continue\n                                if off_k > k and off_j > j and off_i > i:\n                                    continue\n                                offset = off_i*nj*nk + off_j*nk + off_k\n                                c2 = container[offset]\n                                if c2 == NULL: continue\n                                c2 = contour_find(c2)\n                                cur.count = c2.count = 0\n                                contour_union(cur, c2)\n                                cur = contour_find(cur)\n        for i in range(ni):\n            for j in range(nj):\n                for k in range(nk):\n                    c1 = container[i*nj*nk + j*nk + k]\n                    if c1 == NULL: continue\n                    c1 = contour_find(c1)\n                    contour_ids[i,j,k] = c1.contour_id\n\n        for i in range(ni*nj*nk):\n            if container[i] != NULL: free(container[i])\n        free(container)\n        return nc\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef link_node_contours(Node trunk, contours, ContourTree tree,\n        np.ndarray[np.int64_t, ndim=1] node_ids):\n    cdef int n_nodes = node_ids.shape[0]\n    cdef np.int64_t node_ind\n    cdef VolumeContainer **vcs = <VolumeContainer **> malloc(\n        sizeof(VolumeContainer*) * n_nodes)\n    cdef int i\n    cdef PartitionedGrid pg\n    for i in range(n_nodes):\n        pg = contours[node_ids[i]][2]\n        vcs[i] = pg.container\n    cdef np.ndarray[np.uint8_t] examined = np.zeros(n_nodes, \"uint8\")\n    for _, cinfo in sorted(contours.items(), key = lambda a: -a[1][0]):\n        _, node_ind, pg, _ = cinfo\n        construct_boundary_relationships(trunk, tree, node_ind,\n            examined, vcs, node_ids)\n        examined[node_ind] = 1\n\ncdef inline void get_spos(VolumeContainer *vc, int i, int j, int k,\n                          int axis, np.float64_t *spos):\n    spos[0] = vc.left_edge[0] + i * vc.dds[0]\n    spos[1] = vc.left_edge[1] + j * vc.dds[1]\n    spos[2] = vc.left_edge[2] + k * vc.dds[2]\n    spos[axis] += 0.5 * vc.dds[axis]\n\ncdef inline int spos_contained(VolumeContainer *vc, np.float64_t *spos):\n    cdef int i\n    for i in range(3):\n        if spos[i] <= vc.left_edge[i] or spos[i] >= vc.right_edge[i]: return 0\n    return 1\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef void construct_boundary_relationships(Node trunk, ContourTree tree,\n                np.int64_t nid, np.ndarray[np.uint8_t, ndim=1] examined,\n                VolumeContainer **vcs,\n                np.ndarray[np.int64_t, ndim=1] node_ids):\n    # We only look at the boundary and find the nodes next to it.\n    # Contours is a dict, keyed by the node.id.\n    cdef int i, j, off_i, off_j, oi, oj, ax, ax0, ax1, n1, n2\n    cdef np.int64_t c1, c2\n    cdef Node adj_node\n    cdef VolumeContainer *vc1\n    cdef VolumeContainer *vc0 = vcs[nid]\n    cdef int s = (vc0.dims[1]*vc0.dims[0]\n                + vc0.dims[0]*vc0.dims[2]\n                + vc0.dims[1]*vc0.dims[2]) * 18\n    # We allocate an array of fixed (maximum) size\n    cdef np.ndarray[np.int64_t, ndim=2] joins = np.zeros((s, 2), dtype=\"int64\")\n    cdef int ti = 0, side, m1, m2, index\n    cdef int pos[3]\n    cdef int my_pos[3]\n    cdef np.float64_t spos[3]\n\n    for ax in range(3):\n        ax0 = (ax + 1) % 3\n        ax1 = (ax + 2) % 3\n        n1 = vc0.dims[ax0]\n        n2 = vc0.dims[ax1]\n        for i in range(n1):\n            for j in range(n2):\n                for off_i in range(3):\n                    oi = off_i - 1\n                    if i == 0 and oi == -1: continue\n                    if i == n1 - 1 and oi == 1: continue\n                    for off_j in range(3):\n                        oj = off_j - 1\n                        if j == 0 and oj == -1: continue\n                        if j == n2 - 1 and oj == 1: continue\n                        pos[ax0] = i + oi\n                        pos[ax1] = j + oj\n                        my_pos[ax0] = i\n                        my_pos[ax1] = j\n                        for side in range(2):\n                            # We go off each end of the block.\n                            if side == 0:\n                                pos[ax] = -1\n                                my_pos[ax] = 0\n                            else:\n                                pos[ax] = vc0.dims[ax]\n                                my_pos[ax] = vc0.dims[ax]-1\n                            get_spos(vc0, pos[0], pos[1], pos[2], ax, spos)\n                            adj_node = trunk._find_node(spos)\n                            vc1 = vcs[adj_node.node_ind]\n                            if spos_contained(vc1, spos):\n                                index = vc_index(vc0, my_pos[0],\n                                                 my_pos[1], my_pos[2])\n                                m1 = vc0.mask[index]\n                                c1 = (<np.int64_t*>vc0.data[0])[index]\n                                index = vc_pos_index(vc1, spos)\n                                m2 = vc1.mask[index]\n                                c2 = (<np.int64_t*>vc1.data[0])[index]\n                                if m1 == 1 and m2 == 1 and c1 > -1 and c2 > -1:\n                                    if examined[adj_node.node_ind] == 0:\n                                        joins[ti,0] = i64max(c1,c2)\n                                        joins[ti,1] = i64min(c1,c2)\n                                    else:\n                                        joins[ti,0] = c1\n                                        joins[ti,1] = c2\n                                    ti += 1\n\n    if ti == 0: return\n    new_joins = tree.cull_joins(joins[:ti,:])\n    tree.add_joins(new_joins)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef update_joins(np.ndarray[np.int64_t, ndim=2] joins,\n                 np.ndarray[np.int64_t, ndim=3] contour_ids,\n                 np.ndarray[np.int64_t, ndim=1] final_joins):\n    cdef int j, nj, nf\n    cdef int ci, cj, ck\n    nj = joins.shape[0]\n    nf = final_joins.shape[0]\n    for ci in range(contour_ids.shape[0]):\n        for cj in range(contour_ids.shape[1]):\n            for ck in range(contour_ids.shape[2]):\n                if contour_ids[ci,cj,ck] == -1: continue\n                for j in range(nj):\n                    if contour_ids[ci,cj,ck] == joins[j,0]:\n                        contour_ids[ci,cj,ck] = joins[j,1]\n                        break\n                for j in range(nf):\n                    if contour_ids[ci,cj,ck] == final_joins[j]:\n                        contour_ids[ci,cj,ck] = j + 1\n                        break\n\ncdef class FOFNode:\n    cdef np.int64_t tag, count\n    def __init__(self, np.int64_t tag):\n        self.tag = tag\n        self.count = 0\n\ncdef class ParticleContourTree(ContourTree):\n    cdef np.float64_t linking_length, linking_length2\n    cdef np.float64_t DW[3]\n    cdef np.float64_t DLE[3]\n    cdef np.float64_t DRE[3]\n    cdef bint periodicity[3]\n    cdef int minimum_count\n\n    def __init__(self, linking_length, periodicity = (True, True, True),\n                 int minimum_count = 8):\n        cdef int i\n        self.linking_length = linking_length\n        self.linking_length2 = linking_length * linking_length\n        self.first = self.last = NULL\n        for i in range(3):\n            self.periodicity[i] = periodicity[i]\n        self.minimum_count = minimum_count\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def identify_contours(self, OctreeContainer octree,\n                                np.ndarray[np.int64_t, ndim=1] dom_ind,\n                                np.ndarray[cython.floating, ndim=2] positions,\n                                np.ndarray[np.int64_t, ndim=1] particle_ids,\n                                int domain_id, int domain_offset):\n        cdef np.ndarray[np.int64_t, ndim=1] pdoms, pcount, pind, doff\n        cdef np.float64_t pos[3]\n        cdef Oct *oct = NULL\n        cdef Oct **neighbors = NULL\n        cdef OctInfo oi\n        cdef ContourID *c0\n        cdef np.int64_t moff = octree.get_domain_offset(domain_id + domain_offset)\n        cdef np.int64_t i, j, k, n, nneighbors = -1, pind0, offset\n        cdef int counter = 0\n        cdef int verbose = 0\n        pcount = np.zeros_like(dom_ind)\n        doff = np.zeros_like(dom_ind) - 1\n        # First, we find the oct for each particle.\n        pdoms = np.zeros(positions.shape[0], dtype=\"int64\")\n        pdoms -= -1\n        # First we allocate our container\n        cdef ContourID **container = <ContourID**> malloc(\n            sizeof(ContourID*) * positions.shape[0])\n        for i in range(3):\n            self.DW[i] = (octree.DRE[i] - octree.DLE[i])\n            self.DLE[i] = octree.DLE[i]\n            self.DRE[i] = octree.DRE[i]\n        for i in range(positions.shape[0]):\n            counter += 1\n            container[i] = NULL\n            for j in range(3):\n                pos[j] = positions[i, j]\n            oct = octree.get(pos, NULL)\n            if oct == NULL or (domain_id > 0 and oct.domain != domain_id):\n                continue\n            offset = oct.domain_ind - moff\n            pcount[offset] += 1\n            pdoms[i] = offset\n        pind = np.argsort(pdoms)\n        cdef np.int64_t *ipind = <np.int64_t*> pind.data\n        cdef cython.floating *fpos = <cython.floating*> positions.data\n        # pind is now the pointer into the position and particle_ids array.\n        for i in range(positions.shape[0]):\n            offset = pdoms[pind[i]]\n            if doff[offset] < 0:\n                doff[offset] = i\n        del pdoms\n        cdef int nsize = 27\n        cdef np.int64_t *nind = <np.int64_t *> malloc(sizeof(np.int64_t)*nsize)\n        counter = 0\n        cdef np.int64_t frac = <np.int64_t> (doff.shape[0] / 20.0)\n        if verbose == 1: print(\"Will be outputting every\", frac, file=sys.stderr)\n        for i in range(doff.shape[0]):\n            if verbose == 1 and counter >= frac:\n                counter = 0\n                print(\"FOF-ing % 5.1f%% done\" % ((100.0 * i)/doff.size), file=sys.stderr)\n            counter += 1\n            # Any particles found for this oct?\n            if doff[i] < 0: continue\n            offset = pind[doff[i]]\n            # This can probably be replaced at some point with a faster lookup.\n            for j in range(3):\n                pos[j] = positions[offset, j]\n            oct = octree.get(pos, &oi)\n            if oct == NULL or (domain_id > 0 and oct.domain != domain_id):\n                continue\n            # Now we have our primary oct, so we will get its neighbors.\n            neighbors = octree.neighbors(&oi, &nneighbors, oct,\n                                self.periodicity)\n            # Now we have all our neighbors.  And, we should be set for what\n            # else we need to do.\n            if nneighbors > nsize:\n                nind = <np.int64_t *> realloc(\n                    nind, sizeof(np.int64_t)*nneighbors)\n                nsize = nneighbors\n            for j in range(nneighbors):\n                nind[j] = neighbors[j].domain_ind - moff\n                for n in range(j):\n                    if nind[j] == nind[n]:\n                        nind[j] = -1\n                    break\n            # This is allocated by the neighbors function, so we deallocate it.\n            free(neighbors)\n            # We might know that all our internal particles are linked.\n            # Otherwise, we look at each particle.\n            for j in range(pcount[i]):\n                # Note that this offset is the particle index\n                pind0 = pind[doff[i] + j]\n                # Look at each neighboring oct\n                for k in range(nneighbors):\n                    if nind[k] == -1: continue\n                    offset = doff[nind[k]]\n                    if offset < 0: continue\n                    # NOTE: doff[i] will not monotonically increase.  So we\n                    # need a unique ID for each container that we are\n                    # accessing.\n                    self.link_particles(container,\n                                        fpos, ipind,\n                                        pcount[nind[k]],\n                                        offset, pind0,\n                                        doff[i] + j)\n        cdef np.ndarray[np.int64_t, ndim=1] contour_ids\n        contour_ids = np.ones(positions.shape[0], dtype=\"int64\")\n        contour_ids *= -1\n        # Perform one last contour_find on each.  Note that we no longer need\n        # to look at any of the doff or internal offset stuff.\n        for i in range(positions.shape[0]):\n            if container[i] == NULL: continue\n            container[i] = contour_find(container[i])\n        for i in range(positions.shape[0]):\n            if container[i] == NULL: continue\n            c0 = container[i]\n            if c0.count < self.minimum_count: continue\n            contour_ids[i] = particle_ids[pind[c0.contour_id]]\n        free(container)\n        del pind\n        return contour_ids\n\n    @cython.cdivision(True)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef void link_particles(self, ContourID **container,\n                                   cython.floating *positions,\n                                   np.int64_t *pind,\n                                   np.int64_t pcount,\n                                   np.int64_t noffset,\n                                   np.int64_t pind0,\n                                   np.int64_t poffset):\n        # Now we look at each particle and evaluate it\n        cdef np.float64_t pos0[3]\n        cdef np.float64_t pos1[3]\n        cdef np.float64_t edges[2][3]\n        cdef int link\n        cdef ContourID *c0\n        cdef ContourID *c1\n        cdef np.int64_t pind1\n        cdef int i, j\n        # We use pid here so that we strictly take new ones.\n        # Note that pind0 will not monotonically increase, but\n        c0 = container[pind0]\n        if c0 == NULL:\n            c0 = container[pind0] = contour_create(poffset, self.last)\n            self.last = c0\n            if self.first == NULL:\n                self.first = c0\n        c0 = container[pind0] = contour_find(c0)\n        for i in range(3):\n            # We make a very conservative guess here about the edges.\n            pos0[i] = positions[pind0*3 + i]\n            edges[0][i] = pos0[i] - self.linking_length*1.01\n            edges[1][i] = pos0[i] + self.linking_length*1.01\n            if edges[0][i] < self.DLE[i] or edges[0][i] > self.DRE[i]:\n                # We skip this one, since we're close to the boundary\n                edges[0][i] = -1e30\n                edges[1][i] = 1e30\n        # Lets set up some bounds for the particles.  Maybe we can get away\n        # with reducing our number of calls to r2dist_early.\n        for i in range(pcount):\n            pind1 = pind[noffset + i]\n            if pind1 == pind0: continue\n            c1 = container[pind1]\n            if c1 != NULL and c1.contour_id == c0.contour_id:\n                # Already linked.\n                continue\n            for j in range(3):\n                pos1[j] = positions[pind1*3 + j]\n            link = r2dist_early(pos0, pos1, self.DW, self.periodicity,\n                                self.linking_length2, edges)\n            if link == 0: continue\n            if c1 == NULL:\n                c0.count += 1\n                container[pind1] = c0\n            elif c0.contour_id != c1.contour_id:\n                contour_union(c0, c1)\n                c0 = container[pind1] = container[pind0] = contour_find(c0)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline int r2dist_early(np.float64_t ppos[3],\n                             np.float64_t cpos[3],\n                             np.float64_t DW[3],\n                             bint periodicity[3],\n                             np.float64_t max_r2,\n                             np.float64_t edges[2][3]):\n    cdef int i\n    cdef np.float64_t r2, DR\n    r2 = 0.0\n    for i in range(3):\n        if cpos[i] < edges[0][i]:\n            return 0\n        if cpos[i] > edges[1][i]:\n            return 0\n    for i in range(3):\n        DR = (ppos[i] - cpos[i])\n        if not periodicity[i]:\n            pass\n        elif (DR > DW[i]/2.0):\n            DR -= DW[i]\n        elif (DR < -DW[i]/2.0):\n            DR += DW[i]\n        r2 += DR * DR\n        if r2 > max_r2: return 0\n    return 1\n"
  },
  {
    "path": "yt/utilities/lib/cosmology_time.pyx",
    "content": "# distutils: language = c++\n# distutils: libraries = STD_LIBS\n\ncimport numpy as np\n\nimport numpy as np\n\nfrom libc.math cimport sqrt\nimport cython\n\n\n\n@cython.cdivision(True)\ncdef inline double _a_dot(double a, double h0, double om_m, double om_l) noexcept:\n    om_k = 1.0 - om_m - om_l\n    return h0 * a * sqrt(om_m * (a ** -3) + om_k * (a ** -2) + om_l)\n\n\n@cython.cdivision(True)\ncpdef double _a_dot_recip(double a, double h0, double om_m, double om_l):\n    return 1. / _a_dot(a, h0, om_m, om_l)\n\n\ncdef inline double _da_dtau(double a, double h0, double om_m, double om_l) noexcept:\n    return a**2 * _a_dot(a, h0, om_m, om_l)\n\n@cython.cdivision(True)\ncpdef double _da_dtau_recip(double a, double h0, double om_m, double om_l) noexcept:\n    return 1. / _da_dtau(a, h0, om_m, om_l)\n\n\n\ndef t_frw(ds, z):\n    from scipy.integrate import quad\n    aexp = 1 / (1 + z)\n\n    h0 = ds.hubble_constant\n    om_m = ds.omega_matter\n    om_l = ds.omega_lambda\n    conv = ds.quan(0.01, \"Mpc/km*s\").to(\"Gyr\")\n\n    if isinstance(z, (int, float)):\n        return ds.quan(\n            quad(_a_dot_recip, 0, aexp, args=(h0, om_m, om_l))[0],\n            units=conv,\n        )\n\n    return ds.arr(\n        [quad(_a_dot_recip, 0, a, args=(h0, om_m, om_l))[0] for a in aexp],\n        units=conv,\n    )\n\ndef tau_frw(ds, z):\n    from scipy.integrate import quad\n    aexp = 1 / (1 + z)\n\n    h0 = ds.hubble_constant\n    om_m = ds.omega_matter\n    om_l = ds.omega_lambda\n\n    if isinstance(z, (int, float)):\n        return quad(_da_dtau_recip, 1, aexp, args=(h0, om_m, om_l))[0]\n\n    return np.asarray(\n        [quad(_da_dtau_recip, 1, a, args=(h0, om_m, om_l))[0] for a in aexp],\n    )\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/__init__.py",
    "content": "from yt.utilities.lib.cykdtree import plot  # NOQA\nfrom yt.utilities.lib.cykdtree.kdtree import PyKDTree, PyNode  # NOQA\n\n\ndef make_tree(pts, **kwargs):\n    r\"\"\"Build a KD-tree for a set of points.\n\n    Args:\n        pts (np.ndarray of float64): (n,m) Array of n mD points.\n        \\*\\*kwargs: Additional keyword arguments are passed to the appropriate\n            class for constructuing the tree.\n\n    Returns:\n        T (:class:`cykdtree.PyKDTree`): KDTree object.\n\n    Raises:\n        ValueError: If `pts` is not a 2D array.\n\n    \"\"\"\n    # Check input\n    if pts.ndim != 2:\n        raise ValueError(\"pts must be a 2D array of ND coordinates\")\n    T = PyKDTree(pts, **kwargs)\n    return T\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/c_kdtree.cpp",
    "content": ""
  },
  {
    "path": "yt/utilities/lib/cykdtree/c_kdtree.hpp",
    "content": "#include <vector>\n#include <algorithm>\n#include <stdio.h>\n#include <math.h>\n#include <iostream>\n#include <fstream>\n#include <stdint.h>\n#include \"c_utils.hpp\"\n\n#define LEAF_MAX 4294967295\n\ntemplate <typename T>\nT deserialize_scalar(std::istream &is) {\n  T scalar;\n  is.read((char*)&scalar, sizeof(T));\n  return scalar;\n}\n\ntemplate <typename T>\nvoid serialize_scalar(std::ostream &os, const T &scalar) {\n  os.write((char*)&scalar, sizeof(scalar));\n}\n\ntemplate <typename T>\nT* deserialize_pointer_array(std::istream &is, uint64_t len) {\n  T* arr = (T*)malloc(len*sizeof(T));\n  is.read((char*)&arr[0], len*sizeof(T));\n  return arr;\n}\n\ntemplate <typename T>\nvoid serialize_pointer_array(std::ostream &os, const T* array, uint64_t len) {\n  os.write((char*)array, len*sizeof(T));\n}\n\nclass Node {\npublic:\n  bool is_empty;\n  bool is_leaf;\n  uint32_t leafid;\n  uint32_t ndim;\n  double *left_edge;\n  double *right_edge;\n  uint64_t left_idx;\n  uint64_t children;\n  bool *periodic_left;\n  bool *periodic_right;\n  std::vector<std::vector<uint32_t> > left_neighbors;\n  std::vector<std::vector<uint32_t> > right_neighbors;\n  std::vector<uint32_t> all_neighbors;\n  std::vector<Node*> left_nodes;\n  // innernode parameters\n  uint32_t split_dim;\n  double split;\n  Node *less;\n  Node *greater;\n  // empty node constructor\n  Node() {\n    is_empty = true;\n    is_leaf = false;\n    leafid = LEAF_MAX;\n    ndim = 0;\n    left_edge = NULL;\n    right_edge = NULL;\n    periodic_left = NULL;\n    periodic_right = NULL;\n    less = NULL;\n    greater = NULL;\n  }\n  // empty node with some info\n  Node(uint32_t ndim0, double *le, double *re, bool *ple, bool *pre) {\n    is_empty = true;\n    is_leaf = false;\n    leafid = 4294967295;\n    ndim = ndim0;\n    left_edge = (double*)malloc(ndim*sizeof(double));\n    right_edge = (double*)malloc(ndim*sizeof(double));\n    periodic_left = (bool*)malloc(ndim*sizeof(bool));\n    periodic_right = (bool*)malloc(ndim*sizeof(bool));\n    memcpy(left_edge, le, ndim*sizeof(double));\n    memcpy(right_edge, re, ndim*sizeof(double));\n    memcpy(periodic_left, ple, ndim*sizeof(bool));\n    memcpy(periodic_right, pre, ndim*sizeof(bool));\n    less = NULL;\n    greater = NULL;\n    for (uint32_t i=0; i<ndim; i++) {\n      left_nodes.push_back(NULL);\n    }\n  }\n  // innernode constructor\n  Node(uint32_t ndim0, double *le, double *re, bool *ple, bool *pre,\n       uint64_t Lidx, uint32_t sdim0, double split0, Node *lnode, Node *gnode,\n       std::vector<Node*> left_nodes0) {\n    is_empty = false;\n    is_leaf = false;\n    leafid = 4294967295;\n    ndim = ndim0;\n    left_idx = Lidx;\n\n    split_dim = sdim0;\n    split = split0;\n    less = lnode;\n    greater = gnode;\n    children = lnode->children + gnode->children;\n\n    left_edge = (double*)malloc(ndim*sizeof(double));\n    right_edge = (double*)malloc(ndim*sizeof(double));\n    periodic_left = (bool*)malloc(ndim*sizeof(bool));\n    periodic_right = (bool*)malloc(ndim*sizeof(bool));\n    memcpy(left_edge, le, ndim*sizeof(double));\n    memcpy(right_edge, re, ndim*sizeof(double));\n    memcpy(periodic_left, ple, ndim*sizeof(bool));\n    memcpy(periodic_right, pre, ndim*sizeof(bool));\n    for (uint32_t d = 0; d < ndim; d++)\n      left_nodes.push_back(left_nodes0[d]);\n\n    left_neighbors = std::vector<std::vector<uint32_t> >(ndim);\n    right_neighbors = std::vector<std::vector<uint32_t> >(ndim);\n  }\n  // leafnode constructor\n  Node(uint32_t ndim0, double *le, double *re, bool *ple, bool *pre,\n       uint64_t Lidx, uint64_t n, int leafid0,\n       std::vector<Node*> left_nodes0) {\n    is_empty = false;\n    is_leaf = true;\n    leafid = leafid0;\n    ndim = ndim0;\n    split = 0.0;\n    split_dim = 0;\n    left_idx = Lidx;\n    less = NULL;\n    greater = NULL;\n\n    children = n;\n\n    left_edge = (double*)malloc(ndim*sizeof(double));\n    right_edge = (double*)malloc(ndim*sizeof(double));\n    periodic_left = (bool*)malloc(ndim*sizeof(bool));\n    periodic_right = (bool*)malloc(ndim*sizeof(bool));\n    memcpy(left_edge, le, ndim*sizeof(double));\n    memcpy(right_edge, re, ndim*sizeof(double));\n    memcpy(periodic_left, ple, ndim*sizeof(bool));\n    memcpy(periodic_right, pre, ndim*sizeof(bool));\n    for (uint32_t d = 0; d < ndim; d++)\n      left_nodes.push_back(left_nodes0[d]);\n\n    left_neighbors = std::vector<std::vector<uint32_t> >(ndim);\n    right_neighbors = std::vector<std::vector<uint32_t> >(ndim);\n\n    for (uint32_t d = 0; d < ndim; d++) {\n      if ((left_nodes[d]) && (!(left_nodes[d]->is_empty)))\n    \tadd_neighbors(left_nodes[d], d);\n    }\n  }\n  Node(std::istream &is) {\n    // Note that Node instances initialized via this method do not have\n    // any neighbor information. We will build neighbor information later\n    // by walking the tree\n    bool check_bit = deserialize_scalar<bool>(is);\n    if (!check_bit) {\n      // something has gone terribly wrong so we crash\n      abort();\n    }\n    is_empty = deserialize_scalar<bool>(is);\n    is_leaf = deserialize_scalar<bool>(is);\n    leafid = deserialize_scalar<uint32_t>(is);\n    ndim = deserialize_scalar<uint32_t>(is);\n    left_edge = deserialize_pointer_array<double>(is, ndim);\n    right_edge = deserialize_pointer_array<double>(is, ndim);\n    left_idx = deserialize_scalar<uint64_t>(is);\n    children = deserialize_scalar<uint64_t>(is);\n    periodic_left = deserialize_pointer_array<bool>(is, ndim);\n    periodic_right = deserialize_pointer_array<bool>(is, ndim);\n    split_dim = deserialize_scalar<uint32_t>(is);\n    split = deserialize_scalar<double>(is);\n    less = NULL;\n    greater = NULL;\n    left_neighbors = std::vector<std::vector<uint32_t> >(ndim);\n    right_neighbors = std::vector<std::vector<uint32_t> >(ndim);\n    for (uint32_t i=0; i<ndim; i++) {\n      left_nodes.push_back(NULL);\n    }\n  }\n  void serialize(std::ostream &os) {\n    // prepend actual data for Node with true so we can indicate\n    // NULL nodes in the data stream, checking with istream.peek()\n    serialize_scalar<bool>(os, true);\n    serialize_scalar<bool>(os, is_empty);\n    serialize_scalar<bool>(os, is_leaf);\n    serialize_scalar<uint32_t>(os, leafid);\n    serialize_scalar<uint32_t>(os, ndim);\n    serialize_pointer_array<double>(os, left_edge, ndim);\n    serialize_pointer_array<double>(os, right_edge, ndim);\n    serialize_scalar<uint64_t>(os, left_idx);\n    serialize_scalar<uint64_t>(os, children);\n    serialize_pointer_array<bool>(os, periodic_left, ndim);\n    serialize_pointer_array<bool>(os, periodic_right, ndim);\n    serialize_scalar<uint32_t>(os, split_dim);\n    serialize_scalar<double>(os, split);\n  }\n  ~Node() {\n    if (left_edge)\n      free(left_edge);\n    if (right_edge)\n      free(right_edge);\n    if (periodic_left)\n      free(periodic_left);\n    if (periodic_right)\n      free(periodic_right);\n  }\n  friend std::ostream &operator<<(std::ostream &os, const Node &node) {\n    // this is available for nicely formatted debugging, use serialize\n    // to save data to disk\n    os << \"is_empty:      \" << node.is_empty << std::endl;\n    os << \"is_leaf:       \" << node.is_leaf << std::endl;\n    os << \"leafid:        \" << node.leafid << std::endl;\n    os << \"ndim:          \" << node.ndim << std::endl;\n    os << \"left_edge:     \";\n    for (uint32_t i = 0; i < node.ndim; i++) {\n      os << node.left_edge[i] << \" \";\n    }\n    os << std::endl;\n    os << \"right_edge:    \";\n    for (uint32_t i = 0; i < node.ndim; i++) {\n      os << node.right_edge[i] << \" \";\n    }\n    os << std::endl;\n    os << \"left_idx:      \" << node.left_idx << std::endl;\n    os << \"children:      \" << node.children << std::endl;\n    os << \"periodic_left: \";\n    for (uint32_t i = 0; i < node.ndim; i++) {\n      os << node.periodic_left[i] << \" \";\n    }\n    os << std::endl;\n    os << \"periodic_right: \";\n    for (uint32_t i = 0; i < node.ndim; i++) {\n      os << node.periodic_right[i] << \" \";\n    }\n    os << std::endl;\n    os << \"split_dim:     \" << node.split_dim << std::endl;\n    os << \"split:         \" << node.split << std::endl;\n    for (uint32_t i=0; i < node.left_nodes.size(); i++) {\n      os << node.left_nodes[i] << std::endl;\n      if (node.left_nodes[i]) {\n        os << node.left_nodes[i]->left_idx << std::endl;\n        os << node.left_nodes[i]->children << std::endl;\n      }\n    }\n\n    return os;\n  }\n\n  Node* copy() {\n    Node *out;\n    if (is_empty) {\n      if (left_edge) {\n\tout = new Node(ndim, left_edge, right_edge,\n\t\t       periodic_left, periodic_right);\n      } else {\n\tout = new Node();\n      }\n    } else if (is_leaf) {\n      std::vector<Node*> left_nodes_copy;\n      for (uint32_t d = 0; d < ndim; d++)\n\tleft_nodes_copy.push_back(NULL);\n      out = new Node(ndim, left_edge, right_edge,\n\t\t     periodic_left, periodic_right,\n\t\t     left_idx, children, leafid,\n\t\t     left_nodes_copy);\n    } else {\n      Node *lnode = less->copy();\n      Node *gnode = greater->copy();\n      std::vector<Node*> left_nodes_copy;\n      for (uint32_t d = 0; d < ndim; d++)\n\tleft_nodes_copy.push_back(NULL);\n      out = new Node(ndim, left_edge, right_edge,\n\t\t     periodic_left, periodic_right,\n\t\t     left_idx, split_dim, split, lnode, gnode,\n\t\t     left_nodes_copy);\n      std::vector<uint32_t>::iterator it;\n      for (uint32_t d = 0; d < ndim; d++) {\n\tfor (it = left_neighbors[d].begin();\n\t     it != left_neighbors[d].end(); it++) {\n\t  out->left_neighbors[d].push_back(*it);\n\t}\n\tfor (it = right_neighbors[d].begin();\n\t     it != right_neighbors[d].end(); it++) {\n\t  out->right_neighbors[d].push_back(*it);\n\t}\n      }\n    }\n\n    return out;\n  }\n\n  void update_ids(uint32_t add_to) {\n    leafid += add_to;\n    uint32_t i;\n    for (uint32_t d = 0; d < ndim; d++) {\n      for (i = 0; i < left_neighbors[d].size(); i++)\n\tleft_neighbors[d][i] += add_to;\n      for (i = 0; i < right_neighbors[d].size(); i++)\n\tright_neighbors[d][i] += add_to;\n    }\n    for (i = 0; i < all_neighbors.size(); i++)\n      all_neighbors[i] += add_to;\n  }\n\n  void print_neighbors() {\n    uint32_t i, j;\n    // Left\n    printf(\"left:  [\");\n    for (i = 0; i < ndim; i++) {\n      printf(\"[\");\n      for (j = 0; j < left_neighbors[i].size(); j++)\n\tprintf(\"%u \", left_neighbors[i][j]);\n      printf(\"] \");\n    }\n    printf(\"]\\n\");\n    // Right\n    printf(\"right: [\");\n    for (i = 0; i < ndim; i++) {\n      printf(\"[\");\n      for (j = 0; j < right_neighbors[i].size(); j++)\n\tprintf(\"%u \", right_neighbors[i][j]);\n      printf(\"] \");\n    }\n    printf(\"]\\n\");\n  }\n\n  void add_neighbors(Node* curr, uint32_t dim) {\n    if (curr->is_leaf) {\n      left_neighbors[dim].push_back(curr->leafid);\n      curr->right_neighbors[dim].push_back(leafid);\n    } else {\n      if (curr->split_dim == dim) {\n\tadd_neighbors(curr->greater, dim);\n      } else {\n\tif (curr->split > this->right_edge[curr->split_dim])\n\t  add_neighbors(curr->less, dim);\n\telse if (curr->split < this->left_edge[curr->split_dim])\n\t  add_neighbors(curr->greater, dim);\n\telse {\n\t  add_neighbors(curr->less, dim);\n\t  add_neighbors(curr->greater, dim);\n\t}\n      }\n    }\n  }\n\n  void clear_neighbors() {\n    uint32_t d;\n    for (d = 0; d < ndim; d++) {\n      left_neighbors[d].clear();\n      right_neighbors[d].clear();\n    }\n  }\n\n  bool is_left_node(Node *lnode, uint32_t ldim) {\n    uint32_t d;\n    for (d = 0; d < ndim; d++) {\n      if (d == ldim)\n\tcontinue;\n      if (right_edge[d] < lnode->left_edge[d])\n\treturn false;\n      if (left_edge[d] > lnode->right_edge[d])\n\treturn false;\n    }\n    return true;\n  }\n\n  void select_unique_neighbors() {\n    if (!is_leaf)\n      return;\n\n    uint32_t d;\n    std::vector<uint32_t>::iterator last;\n    for (d = 0; d < ndim; d++) {\n      // left\n      std::sort(left_neighbors[d].begin(), left_neighbors[d].end());\n      last = std::unique(left_neighbors[d].begin(), left_neighbors[d].end());\n      left_neighbors[d].erase(last, left_neighbors[d].end());\n      // right\n      std::sort(right_neighbors[d].begin(), right_neighbors[d].end());\n      last = std::unique(right_neighbors[d].begin(), right_neighbors[d].end());\n      right_neighbors[d].erase(last, right_neighbors[d].end());\n    }\n  }\n\n  void join_neighbors() {\n    if (!is_leaf)\n      return;\n\n    uint32_t d;\n    std::vector<uint32_t>::iterator last;\n    // Create concatenated vector and remove duplicates\n    all_neighbors = left_neighbors[0];\n    for (d = 1; d < ndim; d++)\n      all_neighbors.insert(all_neighbors.end(), left_neighbors[d].begin(), left_neighbors[d].end());\n    for (d = 0; d < ndim; d++)\n      all_neighbors.insert(all_neighbors.end(), right_neighbors[d].begin(), right_neighbors[d].end());\n\n    // Get unique\n    std::sort(all_neighbors.begin(), all_neighbors.end());\n    last = std::unique(all_neighbors.begin(), all_neighbors.end());\n    all_neighbors.erase(last, all_neighbors.end());\n\n  }\n\n  bool check_overlap(Node other, uint32_t dim) {\n    if (other.right_edge[dim] < left_edge[dim])\n      return false;\n    else if (other.left_edge[dim] > right_edge[dim])\n      return false;\n    else\n      return true;\n  }\n\n};\n\nvoid write_tree_nodes(std::ostream &os, Node* node) {\n  if (node) {\n    // depth first search of tree below node, writing each node to os\n    // as we go\n    node->serialize(os);\n    write_tree_nodes(os, node->less);\n    write_tree_nodes(os, node->greater);\n  }\n  else {\n    // write null character to indicate empty node\n    serialize_scalar<bool>(os, false);\n  }\n}\n\nNode* read_tree_nodes(std::istream &is,\n                      std::vector<Node*> &leaves,\n                      std::vector<Node*> &left_nodes) {\n  Node* node = new Node(is);\n  node->left_nodes = left_nodes;\n  bool is_leaf = true;\n\n  if (is.peek()) {\n    // read left subtree\n    node->less = read_tree_nodes(is, leaves, left_nodes);\n    is_leaf = false;\n  }\n  else {\n    // no left children\n    is.get();\n    node->less = NULL;\n  }\n\n  if (is.peek()) {\n    // read right subtree\n    std::vector<Node*> greater_left_nodes = left_nodes;\n    greater_left_nodes[node->split_dim] = node->less;\n    node->greater = read_tree_nodes(is, leaves, greater_left_nodes);\n    is_leaf = false;\n  }\n  else {\n    // no right children\n    is.get();\n    node->greater = NULL;\n  }\n\n  if (is_leaf) {\n    leaves.push_back(node);\n    for (uint32_t d = 0; d < node->ndim; d++) {\n      if ((node->left_nodes[d]) && (!(node->left_nodes[d]->is_empty))) {\n        node->add_neighbors(node->left_nodes[d], d);\n      }\n    }\n  }\n\n  return node;\n}\n\nvoid free_tree_nodes(Node* node) {\n  if (node)\n    {\n      free_tree_nodes(node->less);\n      free_tree_nodes(node->greater);\n      delete node;\n    }\n}\n\nclass KDTree\n{\npublic:\n  bool is_partial;\n  bool skip_dealloc_root;\n  bool use_sliding_midpoint;\n  uint64_t* all_idx;\n  uint64_t npts;\n  uint32_t ndim;\n  uint64_t left_idx;\n  int64_t data_version;\n  bool *periodic_left;\n  bool *periodic_right;\n  uint32_t leafsize;\n  double* domain_left_edge;\n  double* domain_right_edge;\n  double* domain_width;\n  bool* periodic;\n  bool any_periodic;\n  double* domain_mins;\n  double* domain_maxs;\n  uint32_t num_leaves;\n  std::vector<Node*> leaves;\n  Node* root;\n\n  // KDTree() {}\n  KDTree(double *pts, uint64_t *idx, uint64_t n, uint32_t m,\n\t uint32_t leafsize0, double *left_edge, double *right_edge,\n\t bool *periodic_left0, bool *periodic_right0,\n\t double *domain_mins0, double *domain_maxs0, int64_t dversion,\n\t bool use_sliding_midpoint0 = false, bool dont_build = false)\n  {\n    is_partial = true;\n    skip_dealloc_root = false;\n    use_sliding_midpoint = use_sliding_midpoint0;\n\n    all_idx = idx;\n    npts = n;\n    ndim = m;\n    leafsize = leafsize0;\n    domain_left_edge = (double*)malloc(ndim*sizeof(double));\n    domain_right_edge = (double*)malloc(ndim*sizeof(double));\n    periodic_left = (bool*)malloc(ndim*sizeof(bool));\n    periodic_right = (bool*)malloc(ndim*sizeof(bool));\n    data_version = dversion;\n    periodic = (bool*)malloc(ndim*sizeof(bool));\n    domain_mins = NULL;\n    domain_maxs = NULL;\n    domain_width = (double*)malloc(ndim*sizeof(double));\n    num_leaves = 0;\n\n    memcpy(domain_left_edge, left_edge, ndim*sizeof(double));\n    memcpy(domain_right_edge, right_edge, ndim*sizeof(double));\n    memcpy(periodic_left, periodic_left0, ndim*sizeof(bool));\n    memcpy(periodic_right, periodic_right0, ndim*sizeof(bool));\n\n    if (domain_mins0) {\n      domain_mins = (double*)malloc(ndim*sizeof(double));\n      memcpy(domain_mins, domain_mins0, ndim*sizeof(double));\n    } else if (pts) {\n      domain_mins = min_pts(pts, n, m);\n    }\n    if (domain_maxs0) {\n      domain_maxs = (double*)malloc(ndim*sizeof(double));\n      memcpy(domain_maxs, domain_maxs0, ndim*sizeof(double));\n    } else if (pts) {\n      domain_maxs = max_pts(pts, n, m);\n    }\n\n    any_periodic = false;\n    for (uint32_t d = 0; d < ndim; d++) {\n      if ((periodic_left[d]) && (periodic_right[d])) {\n\tperiodic[d] = true;\n\tany_periodic = true;\n      } else {\n\tperiodic[d] = false;\n      }\n    }\n\n    for (uint32_t d = 0; d < ndim; d++)\n      domain_width[d] = domain_right_edge[d] - domain_left_edge[d];\n\n    if ((pts) && (!(dont_build)))\n      build_tree(pts);\n\n  }\n  KDTree(double *pts, uint64_t *idx, uint64_t n, uint32_t m, uint32_t leafsize0,\n\t double *left_edge, double *right_edge, bool *periodic0, int64_t dversion,\n\t bool use_sliding_midpoint0 = false, bool dont_build = false)\n  {\n    is_partial = false;\n    skip_dealloc_root = false;\n    use_sliding_midpoint = use_sliding_midpoint0;\n    left_idx = 0;\n\n    all_idx = idx;\n    npts = n;\n    ndim = m;\n    leafsize = leafsize0;\n    domain_left_edge = (double*)malloc(ndim*sizeof(double));\n    domain_right_edge = (double*)malloc(ndim*sizeof(double));\n    data_version = dversion;\n    periodic_left = (bool*)malloc(ndim*sizeof(bool));\n    periodic_right = (bool*)malloc(ndim*sizeof(bool));\n    periodic = (bool*)malloc(ndim*sizeof(bool));\n    domain_mins = NULL;\n    domain_maxs = NULL;\n    domain_width = (double*)malloc(ndim*sizeof(double));\n    num_leaves = 0;\n\n    memcpy(domain_left_edge, left_edge, ndim*sizeof(double));\n    memcpy(domain_right_edge, right_edge, ndim*sizeof(double));\n    memcpy(periodic, periodic0, ndim*sizeof(bool));\n\n    if (pts) {\n      domain_mins = min_pts(pts, n, m);\n      domain_maxs = max_pts(pts, n, m);\n    }\n\n    any_periodic = false;\n    for (uint32_t d = 0; d < ndim; d++) {\n      if (periodic[d]) {\n\tperiodic_left[d] = true;\n\tperiodic_right[d] = true;\n\tany_periodic = true;\n      } else {\n\tperiodic_left[d] = false;\n\tperiodic_right[d] = false;\n      }\n    }\n\n    for (uint32_t d = 0; d < ndim; d++)\n      domain_width[d] = domain_right_edge[d] - domain_left_edge[d];\n\n    if ((pts) && (!(dont_build)))\n      build_tree(pts);\n\n  }\n  KDTree(std::istream &is)\n  {\n    data_version = deserialize_scalar<int64_t>(is);\n    is_partial = deserialize_scalar<bool>(is);\n    use_sliding_midpoint = deserialize_scalar<bool>(is);\n    npts = deserialize_scalar<uint64_t>(is);\n    all_idx = deserialize_pointer_array<uint64_t>(is, npts);\n    ndim = deserialize_scalar<uint32_t>(is);\n    left_idx = deserialize_scalar<uint64_t>(is);\n    periodic = deserialize_pointer_array<bool>(is, ndim);\n    periodic_left = deserialize_pointer_array<bool>(is, ndim);\n    periodic_right = deserialize_pointer_array<bool>(is, ndim);\n    any_periodic = deserialize_scalar<bool>(is);\n    leafsize = deserialize_scalar<uint32_t>(is);\n    domain_left_edge = deserialize_pointer_array<double>(is, ndim);\n    domain_right_edge = deserialize_pointer_array<double>(is, ndim);\n    domain_width = deserialize_pointer_array<double>(is, ndim);\n    domain_mins = deserialize_pointer_array<double>(is, ndim);\n    domain_maxs = deserialize_pointer_array<double>(is, ndim);\n    num_leaves = deserialize_scalar<uint32_t>(is);\n    std::vector<Node*> left_nodes;\n    for (uint32_t i=0; i < ndim; i++) {\n      left_nodes.push_back(NULL);\n    }\n    root = read_tree_nodes(is, leaves, left_nodes);\n    finalize_neighbors();\n  }\n  void serialize(std::ostream &os)\n  {\n    serialize_scalar<int64_t>(os, data_version);\n    serialize_scalar<bool>(os, is_partial);\n    serialize_scalar<bool>(os, use_sliding_midpoint);\n    serialize_scalar<uint64_t>(os, npts);\n    serialize_pointer_array<uint64_t>(os, all_idx, npts);\n    serialize_scalar<uint32_t>(os, ndim);\n    serialize_scalar<uint64_t>(os, left_idx);\n    serialize_pointer_array<bool>(os, periodic, ndim);\n    serialize_pointer_array<bool>(os, periodic_left, ndim);\n    serialize_pointer_array<bool>(os, periodic_right, ndim);\n    serialize_scalar<bool>(os, any_periodic);\n    serialize_scalar<uint32_t>(os, leafsize);\n    serialize_pointer_array<double>(os, domain_left_edge, ndim);\n    serialize_pointer_array<double>(os, domain_right_edge, ndim);\n    serialize_pointer_array<double>(os, domain_width, ndim);\n    serialize_pointer_array<double>(os, domain_mins, ndim);\n    serialize_pointer_array<double>(os, domain_maxs, ndim);\n    serialize_scalar<uint32_t>(os, num_leaves);\n    write_tree_nodes(os, root);\n  }\n  ~KDTree()\n  {\n    if (!(skip_dealloc_root))\n      free_tree_nodes(root);\n    free(domain_left_edge);\n    free(domain_right_edge);\n    free(domain_width);\n    if (domain_mins)\n      free(domain_mins);\n    if (domain_maxs)\n      free(domain_maxs);\n    free(periodic);\n    free(periodic_left);\n    free(periodic_right);\n  }\n\n  void consolidate_edges(double *leaves_le, double *leaves_re) {\n    for (uint32_t k = 0; k < num_leaves; k++) {\n      memcpy(leaves_le+ndim*leaves[k]->leafid,\n             leaves[k]->left_edge,\n             ndim*sizeof(double));\n      memcpy(leaves_re+ndim*leaves[k]->leafid,\n             leaves[k]->right_edge,\n             ndim*sizeof(double));\n    }\n  }\n\n  void build_tree(double* all_pts) {\n    uint32_t d;\n    double *LE = (double*)malloc(ndim*sizeof(double));\n    double *RE = (double*)malloc(ndim*sizeof(double));\n    bool *PLE = (bool*)malloc(ndim*sizeof(bool));\n    bool *PRE = (bool*)malloc(ndim*sizeof(bool));\n    double *mins = (double*)malloc(ndim*sizeof(double));\n    double *maxs = (double*)malloc(ndim*sizeof(double));\n    std::vector<Node*> left_nodes;\n\n    if (!(domain_mins))\n      domain_mins = min_pts(all_pts, npts, ndim);\n    if (!(domain_maxs))\n      domain_maxs = max_pts(all_pts, npts, ndim);\n\n    for (d = 0; d < ndim; d++) {\n      LE[d] = domain_left_edge[d];\n      RE[d] = domain_right_edge[d];\n      PLE[d] = periodic_left[d];\n      PRE[d] = periodic_right[d];\n      mins[d] = domain_mins[d];\n      maxs[d] = domain_maxs[d];\n      left_nodes.push_back(NULL);\n    }\n\n    root = build(0, npts, LE, RE, PLE, PRE, all_pts,\n                 mins, maxs, left_nodes);\n\n    free(LE);\n    free(RE);\n    free(PLE);\n    free(PRE);\n    free(mins);\n    free(maxs);\n\n    // Finalize neighbors\n    finalize_neighbors();\n\n  }\n\n  void finalize_neighbors() {\n    uint32_t d;\n\n    // Add periodic neighbors\n    if (any_periodic)\n      set_neighbors_periodic();\n\n    // Remove duplicate neighbors\n    for (d = 0; d < num_leaves; d++) {\n      leaves[d]->select_unique_neighbors();\n      leaves[d]->join_neighbors();\n    }\n  }\n\n  void clear_neighbors() {\n    std::vector<Node*>::iterator it;\n    for (it = leaves.begin(); it != leaves.end(); it++)\n      (*it)->clear_neighbors();\n  }\n\n  void set_neighbors_periodic()\n  {\n    uint32_t d0;\n    Node* leaf;\n    Node *prev;\n    uint64_t i, j;\n\n    // Periodic neighbors\n    for (i = 0; i < num_leaves; i++) {\n      leaf = leaves[i];\n      for (d0 = 0; d0 < ndim; d0++) {\n\tif (!leaf->periodic_left[d0])\n\t  continue;\n\tfor (j = i; j < num_leaves; j++) {\n\t  prev = leaves[j];\n\t  if (!prev->periodic_right[d0])\n\t    continue;\n\t  add_neighbors_periodic(leaf, prev, d0);\n\t}\n      }\n    }\n  }\n\n  void add_neighbors_periodic(Node *leaf, Node *prev, uint32_t d0) {\n    uint32_t d, ndim_escape;\n    bool match;\n    if (!leaf->periodic_left[d0])\n      return;\n    if (!prev->periodic_right[d0])\n      return;\n    match = true;\n    ndim_escape = 0;\n    for (d = 0; d < ndim; d++) {\n      if (d == d0)\n\tcontinue;\n      if (leaf->left_edge[d] >= prev->right_edge[d]) {\n\tif (!(leaf->periodic_right[d] && prev->periodic_left[d])) {\n\t  match = false;\n\t  break;\n\t} else {\n\t  ndim_escape++;\n\t}\n      }\n      if (leaf->right_edge[d] <= prev->left_edge[d]) {\n\tif (!(prev->periodic_right[d] && leaf->periodic_left[d])) {\n\t  match = false;\n\t  break;\n\t} else {\n\t  ndim_escape++;\n\t}\n      }\n    }\n    if ((match) && (ndim_escape < (ndim-1))) {\n      // printf(\"%d: %d, %d (%d)\\n\", d0, leaf->leafid, prev->leafid, ndim_escape);\n      leaf->left_neighbors[d0].push_back(prev->leafid);\n      prev->right_neighbors[d0].push_back(leaf->leafid);\n    }\n  }\n\n  Node* build(uint64_t Lidx, uint64_t n,\n              double *LE, double *RE,\n              bool *PLE, bool *PRE,\n              double* all_pts,\n              double *mins, double *maxes,\n              std::vector<Node*> left_nodes)\n  {\n    // Create leaf\n    if (n < leafsize) {\n      Node* out = new Node(ndim, LE, RE, PLE, PRE, Lidx, n, num_leaves,\n\t\t\t   left_nodes);\n      num_leaves++;\n      leaves.push_back(out);\n      return out;\n    } else {\n      // Split\n      uint32_t dmax, d;\n      int64_t split_idx = 0;\n      double split_val = 0.0;\n      dmax = split(all_pts, all_idx, Lidx, n, ndim, mins, maxes,\n\t\t   split_idx, split_val, use_sliding_midpoint);\n      if (maxes[dmax] == mins[dmax]) {\n\t// all points singular\n\tNode* out = new Node(ndim, LE, RE, PLE, PRE, Lidx, n, num_leaves,\n\t\t\t     left_nodes);\n\tnum_leaves++;\n\tleaves.push_back(out);\n\treturn out;\n      }\n\n      // Get new boundaries\n      uint64_t Nless = split_idx-Lidx+1;\n      uint64_t Ngreater = n - Nless;\n      double *lessmaxes = (double*)malloc(ndim*sizeof(double));\n      double *lessright = (double*)malloc(ndim*sizeof(double));\n      bool *lessPRE = (bool*)malloc(ndim*sizeof(bool));\n      double *greatermins = (double*)malloc(ndim*sizeof(double));\n      double *greaterleft = (double*)malloc(ndim*sizeof(double));\n      bool *greaterPLE = (bool*)malloc(ndim*sizeof(bool));\n      std::vector<Node*> greater_left_nodes;\n      for (d = 0; d < ndim; d++) {\n\tlessmaxes[d] = maxes[d];\n\tlessright[d] = RE[d];\n\tlessPRE[d] = PRE[d];\n\tgreatermins[d] = mins[d];\n\tgreaterleft[d] = LE[d];\n\tgreaterPLE[d] = PLE[d];\n\tgreater_left_nodes.push_back(left_nodes[d]);\n      }\n      lessmaxes[dmax] = split_val;\n      lessright[dmax] = split_val;\n      lessPRE[dmax] = false;\n      greatermins[dmax] = split_val;\n      greaterleft[dmax] = split_val;\n      greaterPLE[dmax] = false;\n\n      // Build less and greater nodes\n      Node* less = build(Lidx, Nless, LE, lessright, PLE, lessPRE,\n                         all_pts, mins, lessmaxes, left_nodes);\n      greater_left_nodes[dmax] = less;\n      Node* greater = build(Lidx+Nless, Ngreater, greaterleft, RE,\n                            greaterPLE, PRE, all_pts,\n                            greatermins, maxes, greater_left_nodes);\n\n      // Create innernode referencing child nodes\n      Node* out = new Node(ndim, LE, RE, PLE, PRE, Lidx, dmax, split_val,\n\t\t\t   less, greater, left_nodes);\n\n      free(lessright);\n      free(greaterleft);\n      free(lessPRE);\n      free(greaterPLE);\n      free(lessmaxes);\n      free(greatermins);\n      return out;\n    }\n  }\n\n  double* wrap_pos(double* pos) {\n    uint32_t d;\n    double* wrapped_pos = (double*)malloc(ndim*sizeof(double));\n    for (d = 0; d < ndim; d++) {\n      if (periodic[d]) {\n\tif (pos[d] < domain_left_edge[d]) {\n\t  wrapped_pos[d] = domain_right_edge[d] - fmod((domain_right_edge[d] - pos[d]),domain_width[d]);\n\t} else {\n\t  wrapped_pos[d] = domain_left_edge[d] + fmod((pos[d] - domain_left_edge[d]),domain_width[d]);\n\t}\n      } else {\n\twrapped_pos[d] = pos[d];\n      }\n    }\n    return wrapped_pos;\n  }\n\n  Node* search(double* pos0, bool dont_wrap = false)\n  {\n    uint32_t i;\n    Node* out = NULL;\n    bool valid;\n    // Wrap positions\n    double* pos;\n    if ((!dont_wrap) && (any_periodic))\n      pos = wrap_pos(pos0); // allocates new array\n    else\n      pos = pos0;\n    // Ensure that pos is in root, early return NULL if it's not\n    valid = true;\n    for (i = 0; i < ndim; i++) {\n      if (pos[i] < root->left_edge[i]) {\n\tvalid = false;\n\tbreak;\n      }\n      if (pos[i] >= root->right_edge[i]) {\n\tvalid = false;\n\tbreak;\n      }\n    }\n    // Traverse tree looking for leaf containing pos\n    if (valid) {\n      out = root;\n      while (!(out->is_leaf)) {\n\tif (pos[out->split_dim] < out->split)\n\t  out = out->less;\n\telse\n\t  out = out->greater;\n      }\n    }\n\n    if ((!dont_wrap) && (any_periodic))\n      free(pos);\n    return out;\n  }\n\n  std::vector<uint32_t> get_neighbor_ids(double* pos)\n  {\n    Node* leaf;\n    std::vector<uint32_t> neighbors;\n    leaf = search(pos);\n    if (leaf)\n      neighbors = leaf->all_neighbors;\n    return neighbors;\n  }\n\n};\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/c_utils.cpp",
    "content": "#include <vector>\n#include <stdio.h>\n#include <math.h>\n#include <iostream>\n#include <fstream>\n#include <stdint.h>\n#include \"c_utils.hpp\"\n#include <float.h>\n\nbool isEqual(double f1, double f2) {\n  return (fabs(f1 - f2) <= FLT_EPSILON);\n}\n\ndouble* max_pts(double *pts, uint64_t n, uint32_t m)\n{\n  double* max = (double*)std::malloc(m*sizeof(double));\n  uint32_t d;\n  for (d = 0; d < m; d++) max[d] = -DBL_MAX; // pts[d];\n  for (uint64_t i = 0; i < n; i++) {\n    for (d = 0; d < m; d++) {\n      if (pts[m*i + d] > max[d])\n        max[d] = pts[m*i + d];\n    }\n  }\n  return max;\n}\n\ndouble* min_pts(double *pts, uint64_t n, uint32_t m)\n{\n  double* min = (double*)std::malloc(m*sizeof(double));\n  uint32_t d;\n  for (d = 0; d < m; d++) min[d] = DBL_MAX; // pts[d];\n  for (uint64_t i = 0; i < n; i++) {\n    for (d = 0; d < m; d++) {\n      if (pts[m*i + d] < min[d])\n        min[d] = pts[m*i + d];\n    }\n  }\n  return min;\n}\n\nuint64_t argmax_pts_dim(double *pts, uint64_t *idx,\n\t\t\tuint32_t m, uint32_t d,\n\t\t\tuint64_t Lidx, uint64_t Ridx)\n{\n  double max = -DBL_MAX;\n  uint64_t idx_max = Lidx;\n  for (uint64_t i = Lidx; i <= Ridx; i++) {\n    if (pts[m*idx[i] + d] > max) {\n      max = pts[m*idx[i] + d];\n      idx_max = i;\n    }\n  }\n  return idx_max;\n}\n\nuint64_t argmin_pts_dim(double *pts, uint64_t *idx,\n\t\t\tuint32_t m, uint32_t d,\n\t\t\tuint64_t Lidx, uint64_t Ridx)\n{\n  double min = DBL_MAX;\n  uint64_t idx_min = Lidx;\n  for (uint64_t i = Lidx; i <= Ridx; i++) {\n    if (pts[m*idx[i] + d] < min) {\n      min = pts[m*idx[i] + d];\n      idx_min = i;\n    }\n  }\n  return idx_min;\n}\n\n// http://www.comp.dit.ie/rlawlor/Alg_DS/sorting/quickSort.c\nvoid quickSort(double *pts, uint64_t *idx,\n               uint32_t ndim, uint32_t d,\n               int64_t l, int64_t r)\n{\n  int64_t j;\n  if( l < r )\n    {\n      // divide and conquer\n      j = partition(pts, idx, ndim, d, l, r, (l+r)/2);\n      quickSort(pts, idx, ndim, d, l, j-1);\n      quickSort(pts, idx, ndim, d, j+1, r);\n    }\n}\n\nvoid insertSort(double *pts, uint64_t *idx,\n                uint32_t ndim, uint32_t d,\n                int64_t l, int64_t r)\n{\n  int64_t i, j;\n  uint64_t t;\n\n  if (r <= l) return;\n  for (i = l+1; i <= r; i++) {\n    t = idx[i];\n    j = i - 1;\n    while ((j >= l) && (pts[ndim*idx[j]+d] > pts[ndim*t+d])) {\n      idx[j+1] = idx[j];\n      j--;\n    }\n    idx[j+1] = t;\n  }\n}\n\nint64_t pivot(double *pts, uint64_t *idx,\n              uint32_t ndim, uint32_t d,\n              int64_t l, int64_t r)\n{\n  if (r < l) {\n    return -1;\n  } else if (r == l) {\n    return l;\n  } else if ((r - l) < 5) {\n    insertSort(pts, idx, ndim, d, l, r);\n    return (l+r)/2;\n  }\n\n  int64_t i, subr, m5;\n  uint64_t t;\n  int64_t nsub = 0;\n  for (i = l; i <= r; i+=5) {\n    subr = i + 4;\n    if (subr > r) subr = r;\n\n    insertSort(pts, idx, ndim, d, i, subr);\n    m5 = (i+subr)/2;\n    t = idx[m5]; idx[m5] = idx[l + nsub]; idx[l + nsub] = t;\n\n    nsub++;\n  }\n  return pivot(pts, idx, ndim, d, l, l+nsub-1);\n  // return select(pts, idx, ndim, d, l, l+nsub-1, (nsub/2)+(nsub%2));\n}\n\nint64_t partition_given_pivot(double *pts, uint64_t *idx,\n\t\t\t      uint32_t ndim, uint32_t d,\n\t\t\t      int64_t l, int64_t r, double pivot) {\n  // If all less than pivot, j will remain r\n  // If all greater than pivot, j will be l-1\n  if (r < l)\n    return -1;\n  int64_t i, j, tp = -1;\n  uint64_t t;\n  for (i = l, j = r; i <= j; ) {\n    if ((pts[ndim*idx[i]+d] > pivot) && (pts[ndim*idx[j]+d] <= pivot)) {\n      t = idx[i]; idx[i] = idx[j]; idx[j] = t;\n    }\n    if (isEqual(pts[ndim*idx[i]+d], pivot)) tp = i;\n    // if (pts[ndim*idx[i]+d] == pivot) tp = i;\n    if (pts[ndim*idx[i]+d] <= pivot) i++;\n    if (pts[ndim*idx[j]+d] > pivot) j--;\n  }\n  if ((tp >= 0) && (tp != j)) {\n    t = idx[tp]; idx[tp] = idx[j]; idx[j] = t;\n  }\n\n  return j;\n}\n\nint64_t partition(double *pts, uint64_t *idx,\n                  uint32_t ndim, uint32_t d,\n                  int64_t l, int64_t r, int64_t p)\n{\n  double pivot;\n  int64_t j;\n  uint64_t t;\n  if (r < l)\n    return -1;\n  pivot = pts[ndim*idx[p]+d];\n  t = idx[p]; idx[p] = idx[l]; idx[l] = t;\n\n  j = partition_given_pivot(pts, idx, ndim, d, l+1, r, pivot);\n\n  t = idx[l]; idx[l] = idx[j]; idx[j] = t;\n\n  return j;\n}\n\n// https://en.wikipedia.org/wiki/Median_of_medians\nint64_t select(double *pts, uint64_t *idx,\n               uint32_t ndim, uint32_t d,\n               int64_t l0, int64_t r0, int64_t n)\n{\n  int64_t p;\n  int64_t l = l0, r = r0;\n\n  while ( 1 ) {\n    if (l == r) return l;\n\n    p = pivot(pts, idx, ndim, d, l, r);\n    p = partition(pts, idx, ndim, d, l, r, p);\n    if (p < 0)\n      return -1;\n    else if (n == (p-l0+1)) {\n      return p;\n    } else if (n < (p-l0+1)) {\n      r = p - 1;\n    } else {\n      l = p + 1;\n    }\n  }\n}\n\nuint32_t split(double *all_pts, uint64_t *all_idx,\n               uint64_t Lidx, uint64_t n, uint32_t ndim,\n               double *mins, double *maxes,\n               int64_t &split_idx, double &split_val,\n\t       bool use_sliding_midpoint) {\n  // Return immediately if variables empty\n  if ((n == 0) || (ndim == 0)) {\n    split_idx = -1;\n    split_val = 0.0;\n    return 0;\n  }\n\n  // Find dimension to split along\n  uint32_t dmax, d;\n  dmax = 0;\n  for (d = 1; d < ndim; d++)\n    if ((maxes[d]-mins[d]) > (maxes[dmax]-mins[dmax]))\n      dmax = d;\n  if (maxes[dmax] == mins[dmax]) {\n    // all points singular\n    return ndim;\n  }\n\n  if (use_sliding_midpoint) {\n    // Split at middle, then slide midpoint as necessary\n    split_val = (mins[dmax] + maxes[dmax])/2.0;\n    split_idx = partition_given_pivot(all_pts, all_idx, ndim, dmax,\n\t\t\t\t      Lidx, Lidx+n-1, split_val);\n    if (split_idx == (int64_t)(Lidx-1)) {\n      uint64_t t;\n      split_idx = argmin_pts_dim(all_pts, all_idx, ndim, dmax, Lidx, Lidx+n-1);\n      t = all_idx[split_idx]; all_idx[split_idx] = all_idx[Lidx]; all_idx[Lidx] = t;\n      split_idx = Lidx;\n      split_val = all_pts[ndim*all_idx[split_idx] + dmax];\n    } else if (split_idx == (int64_t)(Lidx+n-1)) {\n      uint64_t t;\n      split_idx = argmax_pts_dim(all_pts, all_idx, ndim, dmax, Lidx, Lidx+n-1);\n      t = all_idx[split_idx]; all_idx[split_idx] = all_idx[Lidx+n-1]; all_idx[Lidx+n-1] = t;\n      split_idx = Lidx+n-2;\n      split_val = all_pts[ndim*all_idx[split_idx] + dmax];\n    }\n  } else {\n    // Find median along dimension\n    int64_t nsel = (n/2) + (n%2);\n    split_idx = select(all_pts, all_idx, ndim, dmax, Lidx, Lidx+n-1, nsel);\n    split_val = all_pts[ndim*all_idx[split_idx] + dmax];\n  }\n\n  return dmax;\n}\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/c_utils.hpp",
    "content": "#include <vector>\n#include <stdio.h>\n#include <math.h>\n#include <iostream>\n#include <fstream>\n#include <stdint.h>\n#include <float.h>\n#include <cstdlib>\n\nbool isEqual(double f1, double f2);\ndouble* max_pts(double *pts, uint64_t n, uint32_t m);\ndouble* min_pts(double *pts, uint64_t n, uint32_t m);\nuint64_t argmin_pts_dim(double *pts, uint64_t *idx,\n                        uint32_t m, uint32_t d,\n                        uint64_t Lidx, uint64_t Ridx);\nuint64_t argmax_pts_dim(double *pts, uint64_t *idx,\n                        uint32_t m, uint32_t d,\n                        uint64_t Lidx, uint64_t Ridx);\nvoid quickSort(double *pts, uint64_t *idx,\n               uint32_t ndim, uint32_t d,\n               int64_t l, int64_t r);\nvoid insertSort(double *pts, uint64_t *idx,\n                uint32_t ndim, uint32_t d,\n                int64_t l, int64_t r);\nint64_t pivot(double *pts, uint64_t *idx,\n              uint32_t ndim, uint32_t d,\n              int64_t l, int64_t r);\nint64_t partition_given_pivot(double *pts, uint64_t *idx,\n                              uint32_t ndim, uint32_t d,\n                              int64_t l, int64_t r, double pivot);\nint64_t partition(double *pts, uint64_t *idx,\n                  uint32_t ndim, uint32_t d,\n                  int64_t l, int64_t r, int64_t p);\nint64_t select(double *pts, uint64_t *idx,\n               uint32_t ndim, uint32_t d,\n               int64_t l, int64_t r, int64_t n);\nuint32_t split(double *all_pts, uint64_t *all_idx,\n               uint64_t Lidx, uint64_t n, uint32_t ndim,\n               double *mins, double *maxes,\n               int64_t &split_idx, double &split_val,\n\t       bool use_sliding_midpoint = false);\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/kdtree.pxd",
    "content": "cimport numpy as np\nfrom libc.stdint cimport int32_t, int64_t, uint32_t, uint64_t\nfrom libcpp cimport bool\nfrom libcpp.pair cimport pair\nfrom libcpp.vector cimport vector\n\n\ncdef extern from \"<iostream>\" namespace \"std\":\n    cdef cppclass istream:\n        pass\n\n    cdef cppclass ostream:\n        pass\n\n# the following extern definitions adapted from\n# http://stackoverflow.com/a/31009461/1382869\n\n# obviously std::ios_base isn't a namespace, but this lets\n# Cython generate the correct C++ code\ncdef extern from \"<iostream>\" namespace \"std::ios_base\":\n    cdef cppclass open_mode:\n        pass\n    cdef open_mode binary\n    # you can define other constants as needed\n\ncdef extern from \"<fstream>\" namespace \"std\":\n    cdef cppclass ofstream(ostream):\n        # constructors\n        ofstream(const char*) except +\n        ofstream(const char*, open_mode) except+\n\n    cdef cppclass ifstream(istream):\n        # constructors\n        ifstream(const char*) except +\n        ifstream(const char*, open_mode) except+\n\n\ncdef extern from \"c_kdtree.hpp\":\n    cdef cppclass Node:\n        bool is_leaf\n        uint32_t leafid\n        uint32_t ndim\n        double *left_edge\n        double *right_edge\n        uint64_t left_idx\n        uint64_t children\n        uint32_t split_dim\n        double split\n        Node* less\n        Node* greater\n        bool *periodic_left\n        bool *periodic_right\n        vector[vector[uint32_t]] left_neighbors\n        vector[vector[uint32_t]] right_neighbors\n        vector[uint32_t] all_neighbors\n    cdef cppclass KDTree:\n        uint64_t* all_idx\n        uint64_t npts\n        uint32_t ndim\n        int64_t data_version\n        uint32_t leafsize\n        double* domain_left_edge\n        double* domain_right_edge\n        double* domain_width\n        double* domain_mins\n        double* domain_maxs\n        bool* periodic\n        bool any_periodic\n        uint32_t num_leaves\n        vector[Node*] leaves\n        Node* root\n        KDTree(double *pts, uint64_t *idx, uint64_t n, uint32_t m,\n               uint32_t leafsize0, double *left_edge, double *right_edge,\n               bool *periodic, int64_t data_version)\n        KDTree(double *pts, uint64_t *idx, uint64_t n, uint32_t m,\n               uint32_t leafsize0, double *left_edge, double *right_edge,\n               bool *periodic, int64_t data_version,\n               bool use_sliding_midpoint)\n        KDTree(istream &ist)\n        void serialize(ostream &os)\n        double* wrap_pos(double* pos) noexcept nogil\n        vector[uint32_t] get_neighbor_ids(double* pos) noexcept nogil\n        Node* search(double* pos) noexcept nogil\n        Node* search(double* pos, bool dont_wrap) noexcept nogil\n        void consolidate_edges(double *leaves_le, double *leaves_re)\n\n\ncdef class PyNode:\n    cdef Node *_node\n    cdef readonly np.uint32_t id\n    cdef readonly np.uint64_t npts\n    cdef readonly np.uint32_t ndim\n    cdef readonly np.uint32_t num_leaves\n    cdef readonly np.uint64_t start_idx\n    cdef readonly np.uint64_t stop_idx\n    cdef double *_domain_width\n    cdef readonly object left_neighbors, right_neighbors\n    cdef void _init_node(self, Node* node, uint32_t num_leaves,\n                         double *domain_width)\n\ncdef class PyKDTree:\n    cdef KDTree *_tree\n    cdef readonly uint64_t npts\n    cdef readonly uint32_t ndim\n    cdef readonly uint32_t num_leaves\n    cdef readonly uint32_t leafsize\n    cdef readonly int64_t data_version\n    cdef double *_left_edge\n    cdef double *_right_edge\n    cdef bool *_periodic\n    cdef readonly object leaves\n    cdef readonly object _idx\n    cdef void _init_tree(self, KDTree* tree)\n    cdef void _make_tree(self, double *pts, bool use_sliding_midpoint)\n    cdef void _make_leaves(self)\n    cdef np.ndarray[np.uint32_t, ndim=1] _get_neighbor_ids(self, np.ndarray[double, ndim=1] pos)\n    cdef np.ndarray[np.uint32_t, ndim=1] _get_neighbor_ids_3(self, np.float64_t pos[3])\n    cdef PyNode _get(self, np.ndarray[double, ndim=1] pos)\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/kdtree.pyx",
    "content": "# distutils: libraries = STD_LIBS\n# Note that we used to include the empty c_kdtree.cpp file, but that seems to break cythonize.\n# distutils: sources = yt/utilities/lib/cykdtree/c_utils.cpp\n# distutils: depends = yt/utilities/lib/cykdtree/c_kdtree.hpp, yt/utilities/lib/cykdtree/c_utils.hpp\n# distutils: language = c++\n# distutils: extra_compile_args = CPP11_FLAG\nimport numpy as np\n\ncimport numpy as np\nfrom cpython cimport bool as pybool\nfrom cython.operator cimport dereference\nfrom libc.stdint cimport uint32_t, uint64_t\nfrom libc.stdlib cimport free, malloc\nfrom libcpp cimport bool as cbool\n\n\ncdef class PyNode:\n    r\"\"\"A container for leaf info.\n\n    Attributes:\n        npts (np.uint64_t): Number of points in this node.\n        ndim (np.uint32_t): Number of dimensions in domain.\n        num_leaves (np.uint32_t): Number of leaves in the tree containing this\n            node.\n        start_idx (np.uint64_t): Index where indices for this node begin.\n        stop_idx (np.uint64_t): One passed the end of indices for this node.\n        left_edge (np.ndarray of float64): Minimum bounds of this node in each\n            dimension.\n        right_edge (np.ndarray of float64): Maximum bounds of this node in each\n            dimension.\n        periodic_left (np.ndarray of bool): Periodicity of minimum bounds.\n        periodic_right (np.ndarray of bool): Periodicity of maximum bounds.\n        domain_width (np.ndarray of float64): Width of the total domain in each\n            dimension.\n        left_neighbors (list of lists): Indices of neighbor leaves at the\n            minimum bounds in each dimension.\n        right_neighbors (list of lists): Indices of neighbor leaves at the\n            maximum bounds in each dimension.\n\n    \"\"\"\n\n    cdef void _init_node(self, Node* node, uint32_t num_leaves,\n                         double *domain_width):\n        cdef np.uint32_t i, j\n        self._node = node\n        self.id = node.leafid\n        self.npts = node.children\n        self.ndim = node.ndim\n        self.num_leaves = num_leaves\n        self.start_idx = node.left_idx\n        self.stop_idx = (node.left_idx + node.children)\n        self._domain_width = domain_width\n        self.left_neighbors = [None for i in range(self.ndim)]\n        self.right_neighbors = [None for i in range(self.ndim)]\n        for i in range(self.ndim):\n            self.left_neighbors[i] = [node.left_neighbors[i][j] for j in\n                                      range(node.left_neighbors[i].size())]\n            self.right_neighbors[i] = [node.right_neighbors[i][j] for j in\n                                       range(node.right_neighbors[i].size())]\n\n    def __cinit__(self):\n        # Initialize everything to NULL/0/None to prevent seg fault\n        self._node = NULL\n        self.id = 0\n        self.npts = 0\n        self.ndim = 0\n        self.num_leaves = 0\n        self.start_idx = 0\n        self.stop_idx = 0\n        self._domain_width = NULL\n        self.left_neighbors = None\n        self.right_neighbors = None\n\n    def __init__(self):\n        pass\n\n    def __repr__(self):\n        nchars = 1 + len(str(self.__class__.__name__))\n        return ('%s(id=%i, npts=%i, start_idx=%i, stop_idx=%i,\\n' +\n                ' ' * nchars + 'left_edge=%s,\\n' +\n                ' ' * nchars + 'right_edge=%s)') % (\n            self.__class__.__name__,\n            self.id,\n            self.npts,\n            self.start_idx,\n            self.stop_idx,\n            self.left_edge,\n            self.right_edge,\n        )\n\n    @property\n    def periodic_left(self):\n        cdef cbool[:] view = <cbool[:self.ndim]> self._node.periodic_left\n        return np.asarray(view)\n    @property\n    def periodic_right(self):\n        cdef cbool[:] view = <cbool[:self.ndim]> self._node.periodic_right\n        return np.asarray(view)\n    @property\n    def left_edge(self):\n        cdef np.float64_t[:] view = <np.float64_t[:self.ndim]> self._node.left_edge\n        return np.asarray(view)\n    @property\n    def right_edge(self):\n        cdef np.float64_t[:] view = <np.float64_t[:self.ndim]> self._node.right_edge\n        return np.asarray(view)\n    @property\n    def domain_width(self):\n        cdef np.float64_t[:] view = <np.float64_t[:self.ndim]> self._domain_width\n        return np.asarray(view)\n\n    @property\n    def slice(self):\n        \"\"\"slice: Slice of kdtree indices contained by this node.\"\"\"\n        return slice(self.start_idx, self.stop_idx)\n\n    @property\n    def neighbors(self):\n        \"\"\"list of int: Indices of all neighboring leaves including this\n        leaf.\"\"\"\n        cdef np.uint32_t i\n        cdef object out\n        cdef vector[uint32_t] vout = self._node.all_neighbors\n        out = [vout[i] for i in range(<np.uint32_t>vout.size())]\n        return out\n\n    def assert_equal(self, PyNode solf):\n        \"\"\"Assert that node properties are equal.\"\"\"\n        np.testing.assert_equal(self.npts, solf.npts)\n        np.testing.assert_equal(self.ndim, solf.ndim)\n        np.testing.assert_equal(self.num_leaves, solf.num_leaves)\n        np.testing.assert_equal(self.id, solf.id)\n        np.testing.assert_equal(self.start_idx, solf.start_idx)\n        np.testing.assert_equal(self.stop_idx, solf.stop_idx)\n        np.testing.assert_array_equal(self.left_edge, solf.left_edge)\n        np.testing.assert_array_equal(self.right_edge, solf.right_edge)\n        np.testing.assert_array_equal(self.periodic_left, solf.periodic_left)\n        np.testing.assert_array_equal(self.periodic_right, solf.periodic_right)\n        for i in range(self.ndim):\n            np.testing.assert_equal(self.left_neighbors[i], solf.left_neighbors[i])\n            np.testing.assert_equal(self.right_neighbors[i], solf.right_neighbors[i])\n        np.testing.assert_equal(self.neighbors, solf.neighbors)\n\n\ncdef class PyKDTree:\n    r\"\"\"Construct a KDTree for a set of points.\n\n    Args:\n        pts (np.ndarray of double): (n,m) array of n coordinates in a\n            m-dimensional domain.\n        left_edge (np.ndarray of double): (m,) domain minimum in each dimension.\n        right_edge (np.ndarray of double): (m,) domain maximum in each dimension.\n        periodic (bool or np.ndarray of bool, optional): Truth of the domain\n            periodicity overall (if bool), or in each dimension (if np.ndarray).\n            Defaults to `False`.\n        leafsize (int, optional): The maximum number of points that should be in\n            a leaf. Defaults to 10000.\n        nleaves (int, optional): The number of leaves that should be in the\n            resulting tree. If greater than 0, leafsize is adjusted to produce a\n            tree with 2**(ceil(log2(nleaves))) leaves. The leafsize keyword\n            argument is ignored if nleaves is greater zero. Defaults to 0.\n        data_version (int, optional): An optional user-provided integer that\n            can be used to uniquely identify the data used to generate the\n            KDTree. This is useful if you save the kdtree to disk and restore\n            it later and need to verify that the underlying data is the same.\n        use_sliding_midpoint (bool, optional): If True, the sliding midpoint\n            rule is used to perform splits. Otherwise, the median is used.\n            Defaults to False.\n\n    Raises:\n        ValueError: If `leafsize < 2`. This currently segfaults.\n\n    Attributes:\n        npts (uint64): Number of points in the tree.\n        ndim (uint32): Number of dimensions points occupy.\n        data_version (int64): User-provided version number (defaults to 0)\n        num_leaves (uint32): Number of leaves in the tree.\n        leafsize (uint32): Maximum number of points a leaf can have.\n        leaves (list of `cykdtree.PyNode`): Tree leaves.\n        idx (np.ndarray of uint64): Indices sorting the points by leaf.\n        left_edge (np.ndarray of double): (m,) domain minimum in each dimension.\n        right_edge (np.ndarray of double): (m,) domain maximum in each dimension.\n        domain_width (np.ndarray of double): (m,) domain width in each dimension.\n        periodic (np.ndarray of bool): Truth of domain periodicity in each\n            dimension.\n\n    \"\"\"\n\n    cdef void _init_tree(self, KDTree* tree):\n        self._tree = tree\n        self.ndim = tree.ndim\n        self.data_version = tree.data_version\n        self.npts = tree.npts\n        self.num_leaves = tree.num_leaves\n        self.leafsize = tree.leafsize\n        self._make_leaves()\n        self._idx = np.empty(self.npts, 'uint64')\n        cdef uint64_t i\n        for i in range(self.npts):\n            self._idx[i] = tree.all_idx[i]\n\n    def __cinit__(self):\n        # Initialize everything to NULL/0/None to prevent seg fault\n        self._tree = NULL\n        self.npts = 0\n        self.ndim = 0\n        self.num_leaves = 0\n        self.leafsize = 0\n        self._left_edge = NULL\n        self._right_edge = NULL\n        self._periodic = NULL\n        self.leaves = None\n        self._idx = None\n\n    def __init__(self, np.ndarray[double, ndim=2] pts = None,\n                 left_edge = None,\n                 right_edge = None,\n                 periodic = False,\n                 int leafsize = 10000,\n                 int nleaves = 0,\n                 data_version = None,\n                 use_sliding_midpoint = False):\n        # Return with nothing set if points not provided\n        if pts is None:\n            return\n        # Set leafsize of number of leaves provided\n        if nleaves > 0:\n            nleaves = <int>(2**np.ceil(np.log2(<float>nleaves)))\n            leafsize = pts.shape[0]//nleaves + 1\n        if (leafsize < 2):\n            # This is here to prevent segfault. The cpp code needs modified to\n            # support leafsize = 1\n            raise ValueError(\"'leafsize' cannot be smaller than 2.\")\n        if left_edge is None:\n            left_edge = np.min(pts, axis=0)\n        else:\n            left_edge = np.array(left_edge)\n        if right_edge is None:\n            right_edge = np.max(pts, axis=0)\n        else:\n            right_edge = np.array(right_edge)\n        if data_version is None:\n            data_version = 0\n        self.data_version = data_version\n        cdef uint32_t i\n        self.npts = <uint64_t>pts.shape[0]\n        self.ndim = <uint32_t>pts.shape[1]\n        assert(left_edge.size == self.ndim)\n        assert(right_edge.size == self.ndim)\n        self.leafsize = leafsize\n        self._left_edge = <double *>malloc(self.ndim*sizeof(double))\n        self._right_edge = <double *>malloc(self.ndim*sizeof(double))\n        self._periodic = <cbool *>malloc(self.ndim*sizeof(cbool))\n        for i in range(self.ndim):\n            self._left_edge[i] = left_edge[i]\n            self._right_edge[i] = right_edge[i]\n        if isinstance(periodic, pybool):\n            for i in range(self.ndim):\n                self._periodic[i] = <cbool>periodic\n        else:\n            for i in range(self.ndim):\n                self._periodic[i] = <cbool>periodic[i]\n        # Create tree and leaves\n        self._make_tree(&pts[0,0], <cbool>use_sliding_midpoint)\n        self._make_leaves()\n\n    def __dealloc__(self):\n        if self._tree != NULL:\n            del self._tree\n        if self._left_edge != NULL:\n            free(self._left_edge)\n        if self._right_edge != NULL:\n            free(self._right_edge)\n        if self._periodic != NULL:\n            free(self._periodic)\n\n    def assert_equal(self, PyKDTree solf, pybool strict_idx = True):\n        r\"\"\"Compare this tree to another tree.\n\n        Args:\n            solf (PyKDTree): Another KDTree to compare with this one.\n            strict_idx (bool, optional): If True, the index vectors are\n                compared for equality element by element. If False,\n                corresponding leaves must contain the same indices, but they\n                can be in any order. Defaults to True.\n\n        Raises:\n            AssertionError: If there are mismatches between any of the two\n                trees' parameters.\n\n        \"\"\"\n        np.testing.assert_equal(self.npts, solf.npts)\n        np.testing.assert_equal(self.ndim, solf.ndim)\n        np.testing.assert_equal(self.data_version, solf.data_version)\n        np.testing.assert_equal(self.leafsize, solf.leafsize)\n        np.testing.assert_equal(self.num_leaves, solf.num_leaves)\n        np.testing.assert_array_equal(self.left_edge, solf.left_edge)\n        np.testing.assert_array_equal(self.right_edge, solf.right_edge)\n        np.testing.assert_array_equal(self.periodic, solf.periodic)\n        # Compare index at the leaf level since we only care that the leaves\n        # contain the same points\n        if strict_idx:\n            np.testing.assert_array_equal(self._idx, solf._idx)\n        for i in range(self.num_leaves):\n            self.leaves[i].assert_equal(solf.leaves[i])\n            if not strict_idx:\n                np.testing.assert_array_equal(\n                    np.sort(self._idx[self.leaves[i].slice]),\n                    np.sort(solf._idx[solf.leaves[i].slice]))\n\n    cdef void _make_tree(self, double *pts, bool use_sliding_midpoint):\n        r\"\"\"Carry out creation of KDTree at C++ level.\"\"\"\n        cdef uint64_t[:] idx = np.arange(self.npts).astype('uint64')\n        self._tree = new KDTree(pts, &idx[0], self.npts, self.ndim, self.leafsize,\n                                self._left_edge, self._right_edge, self._periodic,\n                                self.data_version, use_sliding_midpoint)\n        self._idx = idx\n\n    cdef void _make_leaves(self):\n        r\"\"\"Create a list of Python leaf objects from C++ leaves.\"\"\"\n        self.num_leaves = <uint32_t>self._tree.leaves.size()\n        self.leaves = [None for _ in xrange(self.num_leaves)]\n        cdef Node* leafnode\n        cdef PyNode leafnode_py\n        for k in xrange(self.num_leaves):\n            leafnode = self._tree.leaves[k]\n            leafnode_py = PyNode()\n            leafnode_py._init_node(leafnode, self.num_leaves,\n                                   self._tree.domain_width)\n            self.leaves[leafnode.leafid] = leafnode_py\n\n    @property\n    def left_edge(self):\n        cdef np.float64_t[:] view = <np.float64_t[:self.ndim]> self._tree.domain_left_edge\n        return np.asarray(view)\n    @property\n    def right_edge(self):\n        cdef np.float64_t[:] view = <np.float64_t[:self.ndim]> self._tree.domain_right_edge\n        return np.asarray(view)\n    @property\n    def domain_width(self):\n        cdef np.float64_t[:] view = <np.float64_t[:self.ndim]> self._tree.domain_width\n        return np.asarray(view)\n    @property\n    def periodic(self):\n        cdef cbool[:] view = <cbool[:self.ndim]> self._tree.periodic\n        # return np.asarray(view)\n        cdef object out = np.empty(self.ndim, 'bool')\n        cdef np.uint32_t i\n        for i in range(self.ndim):\n            out[i] = view[i]\n        return out\n\n    def leaf_idx(self, np.uint32_t leafid):\n        r\"\"\"Get array of indices for points in a leaf.\n\n        Args:\n            leafid (np.uint32_t): Unique index of the leaf in question.\n\n        Returns:\n            np.ndarray of np.uint64_t: Indices of points belonging to leaf.\n\n        \"\"\"\n        cdef np.ndarray[np.uint64_t] out = self._idx[self.leaves[leafid].slice]\n        return out\n\n    cdef np.ndarray[np.uint32_t, ndim=1] _get_neighbor_ids(self, np.ndarray[double, ndim=1] pos):\n        cdef np.uint32_t i\n        cdef vector[uint32_t] vout = self._tree.get_neighbor_ids(&pos[0])\n        cdef np.ndarray[np.uint32_t, ndim=1] out = np.empty(vout.size(), 'uint32')\n        for i in xrange(vout.size()):\n            out[i] = vout[i]\n        return out\n\n    @property\n    def idx(self):\n        return np.asarray(self._idx)\n\n    def get_neighbor_ids(self, np.ndarray[double, ndim=1] pos):\n        r\"\"\"Return the IDs of leaves containing & neighboring a given position.\n\n        Args:\n            pos (np.ndarray of double): Coordinates.\n\n        Returns:\n            np.ndarray of uint32: Leaves containing/neighboring `pos`.\n\n        Raises:\n            ValueError: If pos is not contained within the KDTree.\n\n        \"\"\"\n        return self._get_neighbor_ids(pos)\n\n    cdef np.ndarray[np.uint32_t, ndim=1] _get_neighbor_ids_3(self, np.float64_t pos[3]):\n        cdef np.uint32_t i\n        cdef vector[uint32_t] vout = self._tree.get_neighbor_ids(&pos[0])\n        cdef np.ndarray[np.uint32_t, ndim=1] out = np.empty(vout.size(), 'uint32')\n        for i in xrange(vout.size()):\n            out[i] = vout[i]\n        return out\n\n    cdef PyNode _get(self, np.ndarray[double, ndim=1] pos):\n        assert(<uint32_t>len(pos) == self.ndim)\n        cdef Node* leafnode = self._tree.search(&pos[0])\n        if leafnode == NULL:\n            raise ValueError(\"Position is not within the kdtree root node.\")\n        cdef PyNode out = self.leaves[leafnode.leafid]\n        return out\n\n    def get(self, np.ndarray[double, ndim=1] pos):\n        r\"\"\"Return the leaf containing a given position.\n\n        Args:\n            pos (np.ndarray of double): Coordinates.\n\n        Returns:\n            :class:`cykdtree.PyNode`: Leaf containing `pos`.\n\n        Raises:\n            ValueError: If pos is not contained within the KDTree.\n\n        \"\"\"\n        return self._get(pos)\n\n    def consolidate_edges(self):\n        r\"\"\"Return arrays of the left and right edges for all leaves in the\n        tree on each process.\n\n        Returns:\n            tuple(np.ndarray of double, np.ndarray of double): The left (first\n                array) and right (second array) edges of each leaf (1st array\n                dimension), in each dimension (2nd array dimension).\n\n        \"\"\"\n        cdef np.ndarray[np.float64_t, ndim=2] leaves_le\n        cdef np.ndarray[np.float64_t, ndim=2] leaves_re\n        leaves_le = np.empty((self.num_leaves, self.ndim), 'float64')\n        leaves_re = np.empty((self.num_leaves, self.ndim), 'float64')\n        self._tree.consolidate_edges(&leaves_le[0,0], &leaves_re[0,0])\n        return (leaves_le, leaves_re)\n\n    def save(self, str filename):\n        r\"\"\"Saves the PyKDTree to disk as raw binary data.\n\n        Note that this file may not necessarily be portable.\n\n        Args:\n            filename (string): Name of the file to serialize the kdtree to\n\n        \"\"\"\n        cdef KDTree* my_tree = self._tree\n        cdef ofstream* outputter = new ofstream(filename.encode('utf8'), binary)\n        try:\n            my_tree.serialize(dereference(outputter))\n        finally:\n            del outputter\n\n    @classmethod\n    def from_file(cls, str filename, data_version=None):\n        r\"\"\"Create a PyKDTree from a binary file created by ``PyKDTree.save()``\n\n        Note that loading a file created on another machine may create\n        a corrupted PyKDTree instance.\n\n        Args:\n            filename (string): Name of the file to load the kdtree from\n            data_version (int): A unique integer corresponding to the data\n                                being loaded. If the loaded data_version does\n                                not match the data_version supplied here then\n                                an OSError is raised. Optional.\n\n        Returns:\n            :class:`cykdtree.PyKDTree`: A KDTree restored from the file\n\n        \"\"\"\n        cdef ifstream* inputter = new ifstream(filename.encode(), binary)\n        cdef PyKDTree ret = cls()\n        if data_version is None:\n            data_version = 0\n        try:\n            ret._init_tree(new KDTree(dereference(inputter)))\n        finally:\n            del inputter\n        return ret\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/plot.py",
    "content": "import numpy as np\n\n\ndef _plot2D_root(\n    seg,\n    pts=None,\n    txt=None,\n    plotfile=None,\n    point_kw=None,\n    box_kw=None,\n    axs=None,\n    subplot_kw=None,\n    gridspec_kw=None,\n    fig_kw=None,\n    save_kw=None,\n    title=None,\n    xlabel=\"x\",\n    ylabel=\"y\",\n    label_kw=None,\n):\n    r\"\"\"Plot a 2D kd-tree.\n\n    Args:\n        seg (list of np.ndarray): Line segments to plot defining box edges.\n        pts (np.ndarray, optional): Points contained by the kdtree. Defaults to\n            None if not provided and points are not plotted.\n        txt (list of tuples, optional): Each tuple contains the (x, y, string)\n            information for text labels to be added to the boxes. Defaults to\n            None and text is not added.\n        plotfile (:obj:`str`, optional): Full path to file where the plot\n            should be saved. If None, the plot is displayed. Defaults to None\n        point_kw (:obj:`dict`, optional): Keywords passed directly to\n            :func:`matplotlib.pyplot.scatter` for drawing the points. Defaults\n            to empty dict.\n        box_kw (:obj:`dict`, optional): Keywords passed directly to\n            :class:`matplotlib.collections.LineCollection` for drawing the\n            leaf boxes. Defaults to empty dict.\n\n        axs (:obj:`matplotlib.pyplot.Axes`, optional): Axes that should be used\n            for plotting. Defaults to None and new axes are created.\n        subplot_kw (:obj:`dict`, optional): Keywords passed directly to\n            :meth:`matplotlib.figure.Figure.add_subplot`. Defaults to {}.\n        gridspec_kw (:obj:`dict`, optional): Keywords passed directly to\n            :class:`matplotlib.gridspec.GridSpec`. Defaults to empty dict.\n        fig_kw (:obj:`dict`, optional): Keywords passed directly to\n            :func:`matplotlib.pyplot.figure`. Defaults to empty dict.\n        save_kw (:obj:`dict`, optional): Keywords passed directly to\n            :func:`matplotlib.pyplot.savefig`. Defaults to empty dict.\n\n        title (:obj:`str`, optional): Title that the plot should be given.\n            Defaults to None and no title is displayed.\n        xlabel (:obj:`str`, optional): Label for the x-axis. Defaults to 'x'.\n        ylabel (:obj:`str`, optional): Label for the y-axis. Defaults to 'y'.\n        label_kw (:obj:`dict`, optional): Keywords passed directly to\n            :class:`matplotlib.text.Text` when creating box labels. Defaults\n            to empty dict.\n\n    Returns:\n        :obj:`matplotlib.pyplot.Axes`: Axes containing the plot.\n\n    \"\"\"\n    import matplotlib.pyplot as plt\n    from matplotlib.collections import LineCollection\n\n    if point_kw is None:\n        point_kw = {}\n    if box_kw is None:\n        box_kw = {}\n    if subplot_kw is None:\n        subplot_kw = {}\n    if gridspec_kw is None:\n        gridspec_kw = {}\n    if fig_kw is None:\n        fig_kw = {}\n    if save_kw is None:\n        save_kw = {}\n    if label_kw is None:\n        label_kw = {}\n    # Axes creation\n    if axs is None:\n        plt.close(\"all\")\n        fig, axs = plt.subplots(\n            subplot_kw=subplot_kw, gridspec_kw=gridspec_kw, **fig_kw\n        )\n\n    # Labels\n    if title is not None:\n        axs.set_title(title)\n    axs.set_xlabel(xlabel, **label_kw)\n    axs.set_ylabel(ylabel, **label_kw)\n\n    # Plot points\n    if isinstance(pts, list):\n        for p in pts:\n            if p is not None:\n                axs.scatter(p[:, 0], p[:, 1], **point_kw)\n    elif pts is not None:\n        axs.scatter(pts[:, 0], pts[:, 1], **point_kw)\n\n    # Plot boxes\n    lc = LineCollection(seg, **box_kw)\n    axs.add_collection(lc)\n\n    # Labels\n    if txt is not None:\n        # label_kw.setdefault('axes', axs)\n        label_kw.setdefault(\"verticalalignment\", \"bottom\")\n        label_kw.setdefault(\"horizontalalignment\", \"left\")\n        for t in txt:\n            plt.text(*t, **label_kw)\n\n    axs.autoscale()\n    axs.margins(0.1)\n\n    # Save\n    if plotfile is not None:\n        plt.savefig(plotfile, **save_kw)\n    else:\n        plt.show()\n\n    # Return axes\n    return axs\n\n\ndef plot2D_serial(tree, pts=None, label_boxes=False, **kwargs):\n    r\"\"\"Plot a 2D kd-tree constructed in serial.\n\n    Parameters\n    ----------\n\n    tree: :class:`cykdtree.kdtree.PyKDTree`\n        kd-tree class.\n    pts: np.ndarray, optional\n        Points contained by the kdtree.\n    label_boxes: bool\n        If True, leaves in the tree are labeled with their index. Defaults to False.\n\n    Additional keywords are passed to :func:`cykdtree.plot._plot2D_root`.\n\n    Returns\n    -------\n\n    :obj:`matplotlib.pyplot.Axes`: Axes containing the plot.\n\n    \"\"\"\n    # Box edges\n    seg = []\n    for leaf in tree.leaves:\n        le = leaf.left_edge\n        re = leaf.right_edge\n        # Top\n        seg.append(np.array([[le[0], re[1]], [re[0], re[1]]], \"float\"))\n        # Bottom\n        seg.append(np.array([[le[0], le[1]], [re[0], le[1]]], \"float\"))\n        # Left\n        seg.append(np.array([[le[0], le[1]], [le[0], re[1]]], \"float\"))\n        # Right\n        seg.append(np.array([[re[0], le[1]], [re[0], re[1]]], \"float\"))\n\n    # Labels\n    txt = None\n    if label_boxes:\n        txt = []\n        for leaf in tree.leaves:\n            txt.append((leaf.left_edge[0], leaf.left_edge[1], str(leaf.id)))\n\n    # Return axes\n    return _plot2D_root(seg, pts=pts, txt=txt, **kwargs)\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/tests/__init__.py",
    "content": "import cProfile\nimport itertools\nimport pstats\nimport sys\nimport time\nfrom datetime import datetime\nfrom subprocess import PIPE, Popen\n\nimport numpy as np\nfrom nose.tools import nottest\n\n\ndef assert_less_equal(x, y):\n    size_match = True\n    try:\n        xshape = (1,)\n        yshape = (1,)\n        if isinstance(x, np.ndarray) or isinstance(y, np.ndarray):\n            if isinstance(x, np.ndarray):\n                xshape = x.shape\n            if isinstance(y, np.ndarray):\n                yshape = y.shape\n            size_match = xshape == yshape\n            assert (x <= y).all()\n        else:\n            assert x <= y\n    except:\n        if not size_match:\n            raise AssertionError(\n                \"Shape mismatch\\n\\n\"\n                + f\"x.shape: {str(x.shape)}\\ny.shape: {str(y.shape)}\\n\"\n            )\n        raise AssertionError(\n            \"Variables are not less-equal ordered\\n\\n\" + f\"x: {str(x)}\\ny: {str(y)}\\n\"\n        )\n\n\ndef call_subprocess(np, func, args, kwargs):\n    # Create string with arguments & kwargs\n    args_str = \"\"\n    for a in args:\n        args_str += f\"{a},\"\n    for k, v in kwargs.items():\n        args_str += f\"{k}={v},\"\n    if args_str.endswith(\",\"):\n        args_str = args_str[:-1]\n    cmd = [\n        \"mpirun\",\n        \"-n\",\n        str(np),\n        sys.executable,\n        \"-c\",\n        f\"'from {func.__module__} import {func.__name__}; {func.__name__}({args_str})'\",\n    ]\n    cmd = \" \".join(cmd)\n    print(f\"Running the following command:\\n{cmd}\")\n    p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)\n    output, err = p.communicate()\n    exit_code = p.returncode\n    print(output.decode(\"utf-8\"))\n    if exit_code != 0:\n        print(err.decode(\"utf-8\"))\n        raise Exception(\"Error on spawned process. See output.\")\n        return None\n    return output.decode(\"utf-8\")\n\n\ndef iter_dict(dicts):\n    try:\n        return (\n            dict(itertools.izip(dicts, x))\n            for x in itertools.product(*dicts.itervalues())\n        )\n    except AttributeError:\n        # python 3\n        return (dict(zip(dicts, x)) for x in itertools.product(*dicts.values()))\n\n\ndef parametrize(**pargs):\n    for k in pargs.keys():\n        if not isinstance(pargs[k], (tuple, list)):\n            pargs[k] = (pargs[k],)\n\n    def dec(func):\n        def pfunc(kwargs0):\n            # Wrapper so that name encodes parameters\n            def wrapped(*args, **kwargs):\n                kwargs.update(**kwargs0)\n                return func(*args, **kwargs)\n\n            wrapped.__name__ = func.__name__\n            for k, v in kwargs0.items():\n                wrapped.__name__ += f\"_{k}{v}\"\n            return wrapped\n\n        def func_param(*args, **kwargs):\n            out = []\n            for ipargs in iter_dict(pargs):\n                out.append(pfunc(ipargs)(*args, **kwargs))\n            return out\n\n        func_param.__name__ = func.__name__\n\n        return func_param\n\n    return dec\n\n\nnp.random.seed(100)\npts2 = np.random.rand(100, 2).astype(\"float64\")\npts3 = np.random.rand(100, 3).astype(\"float64\")\nrand_state = np.random.get_state()\nleft_neighbors_x = [[], [0], [1], [2], [], [], [4, 5], [5]]  # None  # None  # None\nleft_neighbors_y = [\n    [],  # None\n    [],  # None\n    [],  # None\n    [],  # None\n    [0, 1],\n    [4],\n    [1, 2, 3],\n    [6],\n]\nleft_neighbors_x_periodic = [[3], [0], [1], [2], [6], [6, 7], [4, 5], [5]]\nleft_neighbors_y_periodic = [[5], [5, 7], [7], [7], [0, 1], [4], [1, 2, 3], [6]]\n\n\n@nottest\ndef make_points_neighbors(periodic=False):\n    ndim = 2\n    npts = 50\n    leafsize = 10\n    np.random.set_state(rand_state)\n    pts = np.random.rand(npts, ndim).astype(\"float64\")\n    left_edge = np.zeros(ndim, \"float64\")\n    right_edge = np.ones(ndim, \"float64\")\n    if periodic:\n        lx = left_neighbors_x_periodic\n        ly = left_neighbors_y_periodic\n    else:\n        lx = left_neighbors_x\n        ly = left_neighbors_y\n    num_leaves = len(lx)\n    ln = [lx, ly]\n    rn = [[[] for i in range(num_leaves)] for _ in range(ndim)]\n    for d in range(ndim):\n        for i in range(num_leaves):\n            for j in ln[d][i]:\n                rn[d][j].append(i)\n        for i in range(num_leaves):\n            rn[d][i] = list(set(rn[d][i]))\n    return pts, left_edge, right_edge, leafsize, ln, rn\n\n\n@nottest\ndef make_points(npts, ndim, leafsize=10, distrib=\"rand\", seed=100):\n    ndim = int(ndim)\n    npts = int(npts)\n    leafsize = int(leafsize)\n    np.random.seed(seed)\n    LE = 0.0\n    RE = 1.0\n    left_edge = LE * np.ones(ndim, \"float64\")\n    right_edge = RE * np.ones(ndim, \"float64\")\n    if npts <= 0:\n        npts = 100\n        leafsize = 10\n        if ndim == 2:\n            pts = pts2\n        elif ndim == 3:\n            pts = pts3\n        else:\n            pts = np.random.rand(npts, ndim).astype(\"float64\")\n    else:\n        if distrib == \"rand\":\n            pts = np.random.rand(npts, ndim).astype(\"float64\")\n        elif distrib == \"uniform\":\n            pts = np.random.uniform(low=LE, high=RE, size=(npts, ndim))\n        elif distrib in (\"gaussian\", \"normal\"):\n            pts = np.random.normal(\n                loc=(LE + RE) / 2.0, scale=(RE - LE) / 4.0, size=(npts, ndim)\n            )\n            np.clip(pts, LE, RE)\n        else:\n            raise ValueError(f\"Invalid 'distrib': {distrib}\")\n    return pts, left_edge, right_edge, leafsize\n\n\n@nottest\ndef run_test(\n    npts,\n    ndim,\n    nproc=0,\n    distrib=\"rand\",\n    periodic=False,\n    leafsize=10,\n    profile=False,\n    suppress_final_output=False,\n    **kwargs,\n):\n    r\"\"\"Run a routine with a designated number of points & dimensions on a\n    selected number of processors.\n\n    Args:\n        npart (int): Number of particles.\n        nproc (int): Number of processors.\n        ndim (int): Number of dimensions.\n        distrib (str, optional): Distribution that should be used when\n            generating points. Defaults to 'rand'.\n        periodic (bool, optional): If True, the domain is assumed to be\n            periodic. Defaults to False.\n        leafsize (int, optional): Maximum number of points that should be in\n            an leaf. Defaults to 10.\n        profile (bool, optional): If True cProfile is used. Defaults to False.\n        suppress_final_output (bool, optional): If True, the final output\n            from spawned MPI processes is suppressed. This is mainly for\n            timing purposes. Defaults to False.\n\n    \"\"\"\n    from yt.utilities.lib.cykdtree import make_tree\n\n    unique_str = datetime.today().strftime(\"%Y%j%H%M%S\")\n    pts, left_edge, right_edge, leafsize = make_points(\n        npts, ndim, leafsize=leafsize, distrib=distrib\n    )\n    # Set keywords for multiprocessing version\n    if nproc > 1:\n        kwargs[\"suppress_final_output\"] = suppress_final_output\n        if profile:\n            kwargs[\"profile\"] = f\"{unique_str}_mpi_profile.dat\"\n    # Run\n    if profile:\n        pr = cProfile.Profile()\n        t0 = time.time()\n        pr.enable()\n    make_tree(\n        pts,\n        nproc=nproc,\n        left_edge=left_edge,\n        right_edge=right_edge,\n        periodic=periodic,\n        leafsize=leafsize,\n        **kwargs,\n    )\n    if profile:\n        pr.disable()\n        t1 = time.time()\n        ps = pstats.Stats(pr)\n        ps.add(kwargs[\"profile\"])\n        if isinstance(profile, str):\n            ps.dump_stats(profile)\n            print(f\"Stats saved to {profile}\")\n        else:\n            sort_key = \"tottime\"\n            ps.sort_stats(sort_key).print_stats(25)\n            # ps.sort_stats(sort_key).print_callers(5)\n            print(f\"{t1 - t0} s according to 'time'\")\n        return ps\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/tests/scaling.py",
    "content": "r\"\"\"Routines for tracking the scaling of the triangulation routines.\"\"\"\n\nimport cProfile\nimport os\nimport pstats\nimport time\n\nimport numpy as np\n\nfrom yt.utilities.lib.cykdtree.tests import run_test\n\n\ndef stats_run(\n    npart,\n    nproc,\n    ndim,\n    periodic=False,\n    overwrite=False,\n    display=False,\n    suppress_final_output=False,\n):\n    r\"\"\"Get timing stats using :package:`cProfile`.\n\n    Args:\n        npart (int): Number of particles.\n        nproc (int): Number of processors.\n        ndim (int): Number of dimensions.\n        periodic (bool, optional): If True, the domain is assumed to be\n            periodic. Defaults to False.\n        overwrite (bool, optional): If True, the existing file for this\n            set of input parameters if overwritten. Defaults to False.\n        suppress_final_output (bool, optional): If True, the final output\n            from spawned MPI processes is suppressed. This is mainly for\n            timing purposes. Defaults to False.\n        display (bool, optional): If True, display the profile results.\n            Defaults to False.\n\n    \"\"\"\n    perstr = \"\"\n    outstr = \"\"\n    if periodic:\n        perstr = \"_periodic\"\n    if suppress_final_output:\n        outstr = \"_noout\"\n    fname_stat = f\"stat_{npart}part_{nproc}proc_{ndim}dim{perstr}{outstr}.txt\"\n    if overwrite or not os.path.isfile(fname_stat):\n        cProfile.run(\n            \"from yt.utilities.lib.cykdtree.tests import run_test; \"\n            + f\"run_test({npart}, {ndim}, nproc={nproc}, \"\n            + f\"periodic={periodic}, \"\n            + f\"suppress_final_output={suppress_final_output})\",\n            fname_stat,\n        )\n    if display:\n        p = pstats.Stats(fname_stat)\n        p.sort_stats(\"time\").print_stats(10)\n        return p\n    return fname_stat\n\n\ndef time_run(\n    npart, nproc, ndim, nrep=1, periodic=False, leafsize=10, suppress_final_output=False\n):\n    r\"\"\"Get running times using :package:`time`.\n\n    Args:\n        npart (int): Number of particles.\n        nproc (int): Number of processors.\n        ndim (int): Number of dimensions.\n        nrep (int, optional): Number of times the run should be performed to\n            get an average. Defaults to 1.\n        periodic (bool, optional): If True, the domain is assumed to be\n            periodic. Defaults to False.\n        leafsize (int, optional): The maximum number of points that should be\n            in any leaf in the tree. Defaults to 10.\n        suppress_final_output (bool, optional): If True, the final output\n            from spawned MPI processes is suppressed. This is mainly for\n            timing purposes. Defaults to False.\n\n    \"\"\"\n    times = np.empty(nrep, \"float\")\n    for i in range(nrep):\n        t1 = time.time()\n        run_test(\n            npart,\n            ndim,\n            nproc=nproc,\n            periodic=periodic,\n            leafsize=leafsize,\n            suppress_final_output=suppress_final_output,\n        )\n        t2 = time.time()\n        times[i] = t2 - t1\n    return np.mean(times), np.std(times)\n\n\ndef strong_scaling(\n    npart=1e6,\n    nrep=1,\n    periodic=False,\n    leafsize=10,\n    overwrite=True,\n    suppress_final_output=False,\n):\n    r\"\"\"Plot the scaling with number of processors for a particular function.\n\n    Args:\n        npart (int, optional): Number of particles. Defaults to 1e6.\n        nrep (int, optional): Number of times the run should be performed to\n            get an average. Defaults to 1.\n        periodic (bool, optional): If True, the domain is assumed to be\n            periodic. Defaults to False.\n        leafsize (int, optional): The maximum number of points that should be\n            in any leaf in the tree. Defaults to 10.\n        overwrite (bool, optional): If True, the existing file for this\n            set of input parameters if overwritten. Defaults to False.\n        suppress_final_output (bool, optional): If True, the final output\n            from spawned MPI processes is suppressed. This is mainly for\n            timing purposes. Defaults to False.\n\n    \"\"\"\n    import matplotlib.pyplot as plt\n\n    npart = int(npart)\n    perstr = \"\"\n    outstr = \"\"\n    if periodic:\n        perstr = \"_periodic\"\n    if suppress_final_output:\n        outstr = \"_noout\"\n    fname_plot = (\n        f\"plot_strong_scaling_nproc_{npart}part{perstr}_{leafsize}leafsize{outstr}.png\"\n    )\n    nproc_list = [1, 2, 4, 8]  # , 16]\n    ndim_list = [2, 3, 4]\n    clr_list = [\"b\", \"r\", \"g\", \"m\"]\n    times = np.empty((len(nproc_list), len(ndim_list), 2), \"float\")\n    for j, nproc in enumerate(nproc_list):\n        for i, ndim in enumerate(ndim_list):\n            times[j, i, 0], times[j, i, 1] = time_run(\n                npart,\n                nproc,\n                ndim,\n                nrep=nrep,\n                periodic=periodic,\n                leafsize=leafsize,\n                suppress_final_output=suppress_final_output,\n            )\n            print(f\"Finished {ndim}D on {nproc}.\")\n    fig, axs = plt.subplots(1, 1)\n    for i in range(len(ndim_list)):\n        ndim = ndim_list[i]\n        clr = clr_list[i]\n        axs.errorbar(\n            nproc_list,\n            times[:, i, 0],\n            yerr=times[:, i, 1],\n            fmt=clr,\n            label=f\"ndim = {ndim}\",\n        )\n    axs.set_xlabel(\"# of Processors\")\n    axs.set_ylabel(\"Time (s)\")\n    axs.legend()\n    fig.savefig(fname_plot)\n    print(\"    \" + fname_plot)\n\n\ndef weak_scaling(\n    npart=1e4,\n    nrep=1,\n    periodic=False,\n    leafsize=10,\n    overwrite=True,\n    suppress_final_output=False,\n):\n    r\"\"\"Plot the scaling with number of processors with a constant number of\n    particles per processor for a particular function.\n\n    Args:\n        npart (int, optional): Number of particles per processor. Defaults to\n            1e4.\n        nrep (int, optional): Number of times the run should be performed to\n            get an average. Defaults to 1.\n        periodic (bool, optional): If True, the domain is assumed to be\n            periodic. Defaults to False.\n        leafsize (int, optional): The maximum number of points that should be\n            in any leaf in the tree. Defaults to 10.\n        overwrite (bool, optional): If True, the existing file for this\n            set of input parameters if overwritten. Defaults to False.\n        suppress_final_output (bool, optional): If True, the final output\n            from spawned MPI processes is suppressed. This is mainly for\n            timing purposes. Defaults to False.\n\n    \"\"\"\n    import matplotlib.pyplot as plt\n\n    npart = int(npart)\n    perstr = \"\"\n    outstr = \"\"\n    if periodic:\n        perstr = \"_periodic\"\n    if suppress_final_output:\n        outstr = \"_noout\"\n    fname_plot = (\n        f\"plot_weak_scaling_nproc_{npart}part{perstr}_{leafsize}leafsize{outstr}.png\"\n    )\n    nproc_list = [1, 2, 4, 8, 16]\n    ndim_list = [2, 3]\n    clr_list = [\"b\", \"r\", \"g\", \"m\"]\n    times = np.empty((len(nproc_list), len(ndim_list), 2), \"float\")\n    for j, nproc in enumerate(nproc_list):\n        for i, ndim in enumerate(ndim_list):\n            times[j, i, 0], times[j, i, 1] = time_run(\n                npart * nproc,\n                nproc,\n                ndim,\n                nrep=nrep,\n                periodic=periodic,\n                leafsize=leafsize,\n                suppress_final_output=suppress_final_output,\n            )\n    fig, axs = plt.subplots(1, 1)\n    for i in range(len(ndim_list)):\n        ndim = ndim_list[i]\n        clr = clr_list[i]\n        axs.errorbar(\n            nproc_list,\n            times[:, i, 0],\n            yerr=times[:, i, 1],\n            fmt=clr,\n            label=f\"ndim = {ndim}\",\n        )\n    axs.set_xlabel(\"# of Processors\")\n    axs.set_ylabel(\"Time (s)\")\n    axs.legend()\n    fig.savefig(fname_plot)\n    print(\"    \" + fname_plot)\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/tests/test_kdtree.py",
    "content": "import tempfile\nimport time\n\nimport numpy as np\nfrom nose.tools import assert_raises\n\nimport yt.utilities.lib.cykdtree as cykdtree\nfrom yt.utilities.lib.cykdtree.tests import (\n    make_points,\n    make_points_neighbors,\n    parametrize,\n)\n\n\n@parametrize(\n    npts=100, ndim=(2, 3), periodic=(False, True), use_sliding_midpoint=(False, True)\n)\ndef test_PyKDTree(npts=100, ndim=2, periodic=False, use_sliding_midpoint=False):\n    pts, le, re, ls = make_points(npts, ndim)\n    cykdtree.PyKDTree(\n        pts,\n        le,\n        re,\n        leafsize=ls,\n        periodic=periodic,\n        use_sliding_midpoint=use_sliding_midpoint,\n    )\n\n\ndef test_PyKDTree_errors():\n    pts, le, re, ls = make_points(100, 2)\n    assert_raises(ValueError, cykdtree.PyKDTree, pts, le, re, leafsize=1)\n\n\n@parametrize(npts=100, ndim=(2, 3), periodic=(False, True))\ndef test_search(npts=100, ndim=2, periodic=False):\n    pts, le, re, ls = make_points(npts, ndim)\n    tree = cykdtree.PyKDTree(pts, le, re, leafsize=ls, periodic=periodic)\n    pos_list = [le, (le + re) / 2.0]\n    if periodic:\n        pos_list.append(re)\n    for pos in pos_list:\n        leaf = tree.get(pos)\n        leaf.neighbors\n\n\n@parametrize(npts=100, ndim=(2, 3))\ndef test_search_errors(npts=100, ndim=2):\n    pts, le, re, ls = make_points(npts, ndim)\n    tree = cykdtree.PyKDTree(pts, le, re, leafsize=ls)\n    assert_raises(ValueError, tree.get, re)\n\n\n@parametrize(periodic=(False, True))\ndef test_neighbors(periodic=False):\n    pts, le, re, ls, left_neighbors, right_neighbors = make_points_neighbors(\n        periodic=periodic\n    )\n    tree = cykdtree.PyKDTree(pts, le, re, leafsize=ls, periodic=periodic)\n    for leaf in tree.leaves:\n        out_str = str(leaf.id)\n        try:\n            for d in range(tree.ndim):\n                out_str += f\"\\nleft:  {d} {leaf.left_neighbors[d]} {left_neighbors[d][leaf.id]}\"\n                assert len(left_neighbors[d][leaf.id]) == len(leaf.left_neighbors[d])\n                for i in range(len(leaf.left_neighbors[d])):\n                    assert left_neighbors[d][leaf.id][i] == leaf.left_neighbors[d][i]\n                out_str += f\"\\nright: {d} {leaf.right_neighbors[d]} {right_neighbors[d][leaf.id]}\"\n                assert len(right_neighbors[d][leaf.id]) == len(leaf.right_neighbors[d])\n                for i in range(len(leaf.right_neighbors[d])):\n                    assert right_neighbors[d][leaf.id][i] == leaf.right_neighbors[d][i]\n        except Exception as e:\n            for leaf in tree.leaves:\n                print(leaf.id, leaf.left_edge, leaf.right_edge)\n            print(out_str)\n            raise e\n\n\n@parametrize(npts=100, ndim=(2, 3), periodic=(False, True))\ndef test_get_neighbor_ids(npts=100, ndim=2, periodic=False):\n    pts, le, re, ls = make_points(npts, ndim)\n    tree = cykdtree.PyKDTree(pts, le, re, leafsize=ls, periodic=periodic)\n    pos_list = [le, (le + re) / 2.0]\n    if periodic:\n        pos_list.append(re)\n    for pos in pos_list:\n        tree.get_neighbor_ids(pos)\n\n\ndef time_tree_construction(Ntime, LStime, ndim=2):\n    pts, le, re, ls = make_points(Ntime, ndim, leafsize=LStime)\n    t0 = time.time()\n    cykdtree.PyKDTree(pts, le, re, leafsize=LStime)\n    t1 = time.time()\n    print(f\"{Ntime} {ndim}D points, leafsize {LStime}: took {t1 - t0} s\")\n\n\ndef time_neighbor_search(Ntime, LStime, ndim=2):\n    pts, le, re, ls = make_points(Ntime, ndim, leafsize=LStime)\n    tree = cykdtree.PyKDTree(pts, le, re, leafsize=LStime)\n    t0 = time.time()\n    tree.get_neighbor_ids(0.5 * np.ones(tree.ndim, \"double\"))\n    t1 = time.time()\n    print(f\"{Ntime} {ndim}D points, leafsize {LStime}: took {t1 - t0} s\")\n\n\ndef test_save_load():\n    for periodic in (True, False):\n        for ndim in range(1, 5):\n            pts, le, re, ls = make_points(100, ndim)\n            tree = cykdtree.PyKDTree(\n                pts, le, re, leafsize=ls, periodic=periodic, data_version=ndim + 12\n            )\n            with tempfile.NamedTemporaryFile(delete=False) as tf:\n                tree.save(tf.name)\n                restore_tree = cykdtree.PyKDTree.from_file(tf.name)\n                tree.assert_equal(restore_tree)\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/tests/test_plot.py",
    "content": "import os\n\nfrom yt.utilities.lib.cykdtree.kdtree import PyKDTree\nfrom yt.utilities.lib.cykdtree.plot import plot2D_serial\nfrom yt.utilities.lib.cykdtree.tests import make_points\n\n\ndef test_plot2D_serial():\n    fname_test = \"test_plot2D_serial.png\"\n    pts, le, re, ls = make_points(100, 2)\n    tree = PyKDTree(pts, le, re, leafsize=ls)\n    axs = plot2D_serial(\n        tree, pts, title=\"Serial Test\", plotfile=fname_test, label_boxes=True\n    )\n    os.remove(fname_test)\n    # plot2D_serial(tree, pts, axs=axs)\n    del axs\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/tests/test_utils.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.utilities.lib.cykdtree import utils  # type: ignore\nfrom yt.utilities.lib.cykdtree.tests import assert_less_equal, parametrize\n\n\ndef test_max_pts():\n    pts = np.arange(5 * 3, dtype=\"float64\").reshape((5, 3))\n    out = utils.py_max_pts(pts)\n    np.testing.assert_allclose(out, np.max(pts, axis=0))\n\n\ndef test_min_pts():\n    pts = np.arange(5 * 3, dtype=\"float64\").reshape((5, 3))\n    out = utils.py_min_pts(pts)\n    np.testing.assert_allclose(out, np.min(pts, axis=0))\n\n\n@parametrize(N=(10), ndim=(2, 3), Lidx=(0, 5), Ridx=(5, 9))\ndef test_argmax_pts_dim(N=10, ndim=2, Lidx=0, Ridx=9):\n    d = ndim - 1\n    pts = np.random.rand(N, ndim).astype(\"float64\")\n    idx = np.argsort(pts[:, d]).astype(\"uint64\")\n    out = utils.py_argmax_pts_dim(pts, idx, d, Lidx, Ridx)\n    assert_equal(out, np.argmax(pts[idx[Lidx : (Ridx + 1)], d]) + Lidx)\n\n\n@parametrize(N=(10), ndim=(2, 3), Lidx=(0, 5), Ridx=(5, 9))\ndef test_argmin_pts_dim(N=10, ndim=2, Lidx=0, Ridx=9):\n    d = ndim - 1\n    pts = np.random.rand(N, ndim).astype(\"float64\")\n    idx = np.argsort(pts[:, d]).astype(\"uint64\")\n    out = utils.py_argmin_pts_dim(pts, idx, d, Lidx, Ridx)\n    assert_equal(out, np.argmin(pts[idx[Lidx : (Ridx + 1)], d]) + Lidx)\n\n\n@parametrize(N=(0, 10, 11), ndim=(2, 3))\ndef test_quickSort(N=10, ndim=2):\n    d = ndim - 1\n    np.random.seed(10)\n    pts = np.random.rand(N, ndim).astype(\"float64\")\n    idx = utils.py_quickSort(pts, d)\n    assert_equal(idx.size, N)\n    if N != 0:\n        np.testing.assert_allclose(idx, np.argsort(pts[:, d]))\n\n\n@parametrize(N=(0, 10, 11), ndim=(2, 3))\ndef test_insertSort(N=10, ndim=2):\n    d = ndim - 1\n    np.random.seed(10)\n    pts = np.random.rand(N, ndim).astype(\"float64\")\n    idx = utils.py_insertSort(pts, d)\n    assert_equal(idx.size, N)\n    if N != 0:\n        np.testing.assert_allclose(idx, np.argsort(pts[:, d]))\n\n\n@parametrize(N=(0, 10, 11), ndim=(2, 3))\ndef test_pivot(N=10, ndim=2):\n    d = ndim - 1\n    np.random.seed(10)\n    pts = np.random.rand(N, ndim).astype(\"float64\")\n    q, idx = utils.py_pivot(pts, d)\n    if N == 0:\n        np.testing.assert_equal(q, -1)\n    else:\n        piv = pts[idx[q], d]\n        nmax = 7 * N / 10 + 6\n        assert_less_equal(np.sum(pts[:, d] < piv), nmax)\n        assert_less_equal(np.sum(pts[:, d] > piv), nmax)\n\n\n@parametrize(N=(0, 10, 11), ndim=(2, 3))\ndef test_partition(N=10, ndim=2):\n    d = ndim - 1\n    p = 0\n    np.random.seed(10)\n    pts = np.random.rand(N, ndim).astype(\"float64\")\n    q, idx = utils.py_partition(pts, d, p)\n    if N == 0:\n        assert_equal(q, -1)\n    else:\n        piv = pts[p, d]\n        np.testing.assert_approx_equal(pts[idx[q], d], piv)\n        np.testing.assert_array_less(pts[idx[:q], d], piv)\n        np.testing.assert_array_less(piv, pts[idx[(q + 1) :], d])\n\n\n@parametrize(N=(0, 10, 11), ndim=(2, 3))\ndef test_partition_given_pivot(N=10, ndim=2):\n    d = ndim - 1\n    np.random.seed(10)\n    pts = np.random.rand(N, ndim).astype(\"float64\")\n    if N == 0:\n        piv_list = [0.5]\n    else:\n        piv_list = [0.5, np.median(pts[:, d])]\n    for piv in piv_list:\n        q, idx = utils.py_partition_given_pivot(pts, d, piv)\n        if N == 0:\n            assert_equal(q, -1)\n        else:\n            assert_less_equal(pts[idx[q], d], piv)\n            np.testing.assert_array_less(pts[idx[:q], d], piv)\n            np.testing.assert_array_less(piv, pts[idx[(q + 1) :], d])\n\n\n@parametrize(N=(0, 10, 11), ndim=(2, 3))\ndef test_select(N=10, ndim=2):\n    d = ndim - 1\n    np.random.seed(10)\n    pts = np.random.rand(N, ndim).astype(\"float64\")\n    p = int(N) // 2 + int(N) % 2\n    q, idx = utils.py_select(pts, d, p)\n    assert_equal(idx.size, N)\n    if N == 0:\n        assert_equal(q, -1)\n    else:\n        assert_equal(q, p - 1)\n        med = np.median(pts[:, d])\n        np.testing.assert_array_less(pts[idx[:q], d], med)\n        np.testing.assert_array_less(med, pts[idx[(q + 1) :], d])\n        if N % 2:\n            np.testing.assert_approx_equal(pts[idx[q], d], med)\n        else:\n            np.testing.assert_array_less(pts[idx[q], d], med)\n\n\n@parametrize(N=(0, 10, 11), ndim=(2, 3), use_sliding_midpoint=(False, True))\ndef test_split(N=10, ndim=2, use_sliding_midpoint=False):\n    np.random.seed(10)\n    pts = np.random.rand(N, ndim).astype(\"float64\")\n    p = int(N) // 2 + int(N) % 2\n    q, d, idx = utils.py_split(pts, use_sliding_midpoint=use_sliding_midpoint)\n    assert_equal(idx.size, N)\n    if N == 0:\n        assert_equal(q, -1)\n    else:\n        if use_sliding_midpoint:\n            # Midpoint\n            med = 0.5 * (np.min(pts[:, d]) + np.max(pts[:, d]))\n            np.testing.assert_array_less(pts[idx[:q], d], med)\n            np.testing.assert_array_less(med, pts[idx[(q + 1) :], d])\n            np.testing.assert_array_less(pts[idx[q], d], med)\n            # Sliding midpoint (slide to minimum)\n            q, d, idx = utils.py_split(\n                pts,\n                mins=-1 * np.ones(ndim),\n                maxs=np.ones(ndim),\n                use_sliding_midpoint=True,\n            )\n            med = np.min(pts[:, d])\n            assert_equal(q, 0)\n            np.testing.assert_array_less(pts[idx[:q], d], med)\n            np.testing.assert_array_less(med, pts[idx[(q + 1) :], d])\n            np.testing.assert_approx_equal(pts[idx[q], d], med)\n            # Sliding midpoint (slide to maximum)\n            q, d, idx = utils.py_split(\n                pts,\n                mins=np.zeros(ndim),\n                maxs=2 * np.ones(ndim),\n                use_sliding_midpoint=True,\n            )\n            med = np.max(pts[:, d])\n            assert_equal(q, N - 2)\n            np.testing.assert_array_less(pts[idx[: (q + 1)], d], med)\n            np.testing.assert_approx_equal(pts[idx[q + 1], d], med)\n        else:\n            assert_equal(q, p - 1)\n            med = np.median(pts[:, d])\n            np.testing.assert_array_less(pts[idx[:q], d], med)\n            np.testing.assert_array_less(med, pts[idx[(q + 1) :], d])\n            if N % 2:\n                np.testing.assert_approx_equal(pts[idx[q], d], med)\n            else:\n                np.testing.assert_array_less(pts[idx[q], d], med)\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/utils.pxd",
    "content": "cimport numpy as np\nfrom libc.stdint cimport int32_t, int64_t, uint32_t, uint64_t\nfrom libcpp cimport bool\nfrom libcpp.pair cimport pair\nfrom libcpp.vector cimport vector\n\n\ncdef extern from \"c_utils.hpp\":\n    double* max_pts(double *pts, uint64_t n, uint64_t m)\n    double* min_pts(double *pts, uint64_t n, uint64_t m)\n    uint64_t argmax_pts_dim(double *pts, uint64_t *idx,\n                            uint32_t m, uint32_t d,\n                            uint64_t Lidx, uint64_t Ridx)\n    uint64_t argmin_pts_dim(double *pts, uint64_t *idx,\n                            uint32_t m, uint32_t d,\n                            uint64_t Lidx, uint64_t Ridx)\n    void quickSort(double *pts, uint64_t *idx,\n                   uint32_t ndim, uint32_t d,\n                   int64_t l, int64_t r)\n    int64_t partition(double *pts, uint64_t *idx,\n                      uint32_t ndim, uint32_t d,\n                      int64_t l, int64_t r, int64_t p)\n    int64_t partition_given_pivot(double *pts, uint64_t *idx,\n                                  uint32_t ndim, uint32_t d,\n                                  int64_t l, int64_t r, double pivot)\n    int64_t select(double *pts, uint64_t *idx,\n                   uint32_t ndim, uint32_t d,\n                   int64_t l, int64_t r, int64_t n)\n    int64_t pivot(double *pts, uint64_t *idx,\n                  uint32_t ndim, uint32_t d,\n                  int64_t l, int64_t r)\n    void insertSort(double *pts, uint64_t *idx,\n                    uint32_t ndim, uint32_t d,\n                    int64_t l, int64_t r)\n    uint32_t split(double *all_pts, uint64_t *all_idx,\n                   uint64_t Lidx, uint64_t n, uint32_t ndim,\n                   double *mins, double *maxes,\n                   int64_t &split_idx, double &split_val)\n    uint32_t split(double *all_pts, uint64_t *all_idx,\n                   uint64_t Lidx, uint64_t n, uint32_t ndim,\n                   double *mins, double *maxes,\n                   int64_t &split_idx, double &split_val,\n                   bool use_sliding_midpoint)\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/utils.pyx",
    "content": "# distutils: libraries = STD_LIBS\n# distutils: sources = yt/utilities/lib/cykdtree/c_utils.cpp\n# distutils: depends = yt/utilities/lib/cykdtree/c_utils.hpp\n# distutils: language = c++\n# distutils: extra_compile_args = CPP11_FLAG\nimport numpy as np\n\ncimport numpy as np\nfrom libc.stdint cimport int64_t, uint32_t, uint64_t\nfrom libcpp cimport bool as cbool\n\n\ndef py_max_pts(np.ndarray[np.float64_t, ndim=2] pos):\n    r\"\"\"Get the maximum of points along each coordinate.\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n\n    Returns:\n        np.ndarray of float64: Maximum of pos along each coordinate.\n\n    \"\"\"\n    cdef uint64_t n = <uint64_t>pos.shape[0]\n    cdef uint32_t m = <uint32_t>pos.shape[1]\n    cdef np.float64_t* cout = max_pts(&pos[0,0], n, m)\n    cdef uint32_t i = 0\n    cdef np.ndarray[np.float64_t] out = np.zeros(m, 'float64')\n    for i in range(m):\n        out[i] = cout[i]\n    #free(cout)\n    return out\n\ndef py_min_pts(np.ndarray[np.float64_t, ndim=2] pos):\n    r\"\"\"Get the minimum of points along each coordinate.\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n\n    Returns:\n        np.ndarray of float64: Minimum of pos along each coordinate.\n\n    \"\"\"\n    cdef uint64_t n = <uint64_t>pos.shape[0]\n    cdef uint32_t m = <uint32_t>pos.shape[1]\n    cdef np.float64_t* cout = min_pts(&pos[0,0], n, m)\n    cdef uint32_t i = 0\n    cdef np.ndarray[np.float64_t] out = np.zeros(m, 'float64')\n    for i in range(m):\n        out[i] = cout[i]\n    #free(cout)\n    return out\n\ndef py_argmax_pts_dim(np.ndarray[np.float64_t, ndim=2] pos,\n                      uint64_t[:] idx,\n                      np.uint32_t d, int Lidx0, int Ridx0):\n    r\"\"\"Get the maximum of points along one dimension for a subset of the\n    point indices. This is essentially max(pos[idx[Lidx:(Ridx+1)], d]).\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n        idx (np.ndarray of uint64_t): (n,) array of indices for positions.\n        d (uint32_t): Dimension to compute maximum along.\n        Lidx (int): Index in idx that search should begin at.\n        Ridx (int): Index in idx that search should end at.\n\n    Returns:\n        uint64_t: Index in idx that provides maximum position in the subset\n            indices along dimension d.\n\n    \"\"\"\n    cdef np.intp_t n = pos.shape[0]\n    cdef uint32_t m = <uint32_t>pos.shape[1]\n    cdef uint64_t Lidx = 0\n    cdef uint64_t Ridx = <uint64_t>(n-1)\n    if (Lidx0 < 0):\n        Lidx = <uint64_t>(n + Lidx0)\n    elif Lidx0 >= n:\n        raise Exception(\"Left index (%d) exceeds size of positions array (%d).\"\n                            % (Lidx0, n))\n    else:\n        Lidx = <uint64_t>Lidx0\n    if (Ridx0 < 0):\n        Ridx = <uint64_t>(n + Ridx0)\n    elif Ridx0 >= n:\n        raise Exception(\"Right index (%d) exceeds size of positions array (%d).\"\n                            % (Ridx0, n))\n    else:\n        Ridx = <uint64_t>Ridx0\n    cdef np.uint64_t cout = Lidx\n    if (Ridx > Lidx):\n        cout = argmax_pts_dim(&pos[0,0], &idx[0], m, d, Lidx, Ridx)\n    return cout\n\ndef py_argmin_pts_dim(np.ndarray[np.float64_t, ndim=2] pos,\n                      uint64_t[:] idx,\n                      np.uint32_t d, int Lidx0, int Ridx0):\n    r\"\"\"Get the minimum of points along one dimension for a subset of the\n    point indices. This is essentially min(pos[idx[Lidx:(Ridx+1)], d]).\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n        idx (np.ndarray of uint64_t): (n,) array of indices for positions.\n        d (uint32_t): Dimension to compute minimum along.\n        Lidx (int): Index in idx that search should begin at.\n        Ridx (int): Index in idx that search should end at.\n\n    Returns:\n        uint64_t: Index in idx that provides minimum position in the subset\n            indices along dimension d.\n\n    \"\"\"\n    cdef uint64_t n = <uint64_t>pos.shape[0]\n    cdef uint32_t m = <uint32_t>pos.shape[1]\n    cdef uint64_t Lidx = 0\n    cdef uint64_t Ridx = n\n    if (Lidx0 < 0):\n        Lidx = <uint64_t>(<int>n + Lidx0)\n    else:\n        Lidx = <uint64_t>Lidx0\n    if (Ridx0 < 0):\n        Ridx = <uint64_t>(<int>n + Ridx0)\n    else:\n        Ridx = <uint64_t>Ridx0\n    cdef np.uint64_t cout = Lidx\n    if (Ridx > Lidx):\n        cout = argmin_pts_dim(&pos[0,0], &idx[0], m, d, Lidx, Ridx)\n    return cout\n\ndef py_quickSort(np.ndarray[np.float64_t, ndim=2] pos, np.uint32_t d):\n    r\"\"\"Get the indices required to sort coordinates along one dimension.\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n        d (np.uint32_t): Dimension that pos should be sorted along.\n\n    Returns:\n        np.ndarray of uint64: Indices that sort pos along dimension d.\n\n    \"\"\"\n    cdef np.intp_t ndim = pos.shape[1]\n    cdef int64_t l = 0\n    cdef int64_t r = pos.shape[0]-1\n    cdef uint64_t[:] idx\n    idx = np.arange(pos.shape[0]).astype('uint64')\n    cdef double *ptr_pos = NULL\n    cdef uint64_t *ptr_idx = NULL\n    if pos.shape[0] != 0:\n        ptr_pos = &pos[0,0]\n        ptr_idx = &idx[0]\n    quickSort(ptr_pos, ptr_idx, ndim, d, l, r)\n    return idx\n\ndef py_insertSort(np.ndarray[np.float64_t, ndim=2] pos, np.uint32_t d):\n    r\"\"\"Get the indices required to sort coordinates along one dimension.\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n        d (np.uint32_t): Dimension that pos should be sorted along.\n\n    Returns:\n        np.ndarray of uint64: Indices that sort pos along dimension d.\n\n    \"\"\"\n    cdef np.intp_t ndim = pos.shape[1]\n    cdef int64_t l = 0\n    cdef int64_t r = pos.shape[0]-1\n    cdef uint64_t[:] idx\n    idx = np.arange(pos.shape[0]).astype('uint64')\n    cdef double *ptr_pos = NULL\n    cdef uint64_t *ptr_idx = NULL\n    if pos.shape[0] != 0:\n        ptr_pos = &pos[0,0]\n        ptr_idx = &idx[0]\n    insertSort(ptr_pos, ptr_idx, ndim, d, l, r)\n    return idx\n\ndef py_pivot(np.ndarray[np.float64_t, ndim=2] pos, np.uint32_t d):\n    r\"\"\"Get the index of the median of medians along one dimension and indices\n    that partition pos according to the median of medians.\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n        d (np.uint32_t): Dimension that pos should be partitioned along.\n\n    Returns:\n        tuple of int64 and np.ndarray of uint64: Index q of idx that is the\n            pivot. All elements of idx before the pivot will be less than\n            the pivot. If there is an odd number of points, the pivot will\n            be the median.\n\n    \"\"\"\n    cdef np.intp_t ndim = pos.shape[1]\n    cdef int64_t l = 0\n    cdef int64_t r = pos.shape[0]-1\n    cdef uint64_t[:] idx\n    idx = np.arange(pos.shape[0]).astype('uint64')\n    cdef double *ptr_pos = NULL\n    cdef uint64_t *ptr_idx = NULL\n    if pos.shape[0] != 0:\n        ptr_pos = &pos[0,0]\n        ptr_idx = &idx[0]\n    cdef int64_t q = pivot(ptr_pos, ptr_idx, ndim, d, l, r)\n    return q, idx\n\ndef py_partition(np.ndarray[np.float64_t, ndim=2] pos, np.uint32_t d,\n                 np.int64_t p):\n    r\"\"\"Get the indices required to partition coordinates along one dimension.\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n        d (np.uint32_t): Dimension that pos should be partitioned along.\n        p (np.int64_t): Element of pos[:,d] that should be used as the pivot\n            to partition pos.\n\n    Returns:\n        tuple of int64 and np.ndarray of uint64: Location of the pivot in the\n            partitioned array and the indices required to partition the array\n            such that elements before the pivot are smaller and elements after\n            the pivot are larger.\n\n    \"\"\"\n    cdef np.intp_t ndim = pos.shape[1]\n    cdef int64_t l = 0\n    cdef int64_t r = pos.shape[0]-1\n    cdef uint64_t[:] idx\n    idx = np.arange(pos.shape[0]).astype('uint64')\n    cdef double *ptr_pos = NULL\n    cdef uint64_t *ptr_idx = NULL\n    if pos.shape[0] != 0:\n        ptr_pos = &pos[0,0]\n        ptr_idx = &idx[0]\n    cdef int64_t q = partition(ptr_pos, ptr_idx, ndim, d, l, r, p)\n    return q, idx\n\ndef py_partition_given_pivot(np.ndarray[np.float64_t, ndim=2] pos,\n                             np.uint32_t d, np.float64_t pval):\n    r\"\"\"Get the indices required to partition coordinates along one dimension.\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n        d (np.uint32_t): Dimension that pos should be partitioned along.\n        pval (np.float64_t): Value that should be used to partition pos.\n\n    Returns:\n        tuple of int64 and np.ndarray of uint64: Location of the largest value\n            that is smaller than pval in partitioned array and the indices\n            required to partition the array such that elements before the pivot\n            are smaller and elements after the pivot are larger.\n\n    \"\"\"\n    cdef np.intp_t ndim = pos.shape[1]\n    cdef int64_t l = 0\n    cdef int64_t r = pos.shape[0]-1\n    cdef uint64_t[:] idx\n    idx = np.arange(pos.shape[0]).astype('uint64')\n    cdef double *ptr_pos = NULL\n    cdef uint64_t *ptr_idx = NULL\n    if pos.shape[0] != 0:\n        ptr_pos = &pos[0,0]\n        ptr_idx = &idx[0]\n    cdef int64_t q = partition_given_pivot(ptr_pos, ptr_idx, ndim, d, l, r,\n                                           pval)\n    return q, idx\n\ndef py_select(np.ndarray[np.float64_t, ndim=2] pos, np.uint32_t d,\n              np.int64_t t):\n    r\"\"\"Get the indices required to partition coordinates such that the first\n    t elements in pos[:,d] are the smallest t elements in pos[:,d].\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n        d (np.uint32_t): Dimension that pos should be partitioned along.\n        t (np.int64_t): Number of smallest elements in pos[:,d] that should be\n            partitioned.\n\n    Returns:\n        tuple of int64 and np.ndarray of uint64: Location of element t in the\n            partitioned array and the indices required to partition the array\n            such that elements before element t are smaller and elements after\n            the pivot are larger.\n\n    \"\"\"\n    cdef np.intp_t ndim = pos.shape[1]\n    cdef int64_t l = 0\n    cdef int64_t r = pos.shape[0]-1\n    cdef uint64_t[:] idx\n    idx = np.arange(pos.shape[0]).astype('uint64')\n    cdef double *ptr_pos = NULL\n    cdef uint64_t *ptr_idx = NULL\n    if pos.shape[0] != 0:\n        ptr_pos = &pos[0,0]\n        ptr_idx = &idx[0]\n    cdef int64_t q = select(ptr_pos, ptr_idx, ndim, d, l, r, t)\n    return q, idx\n\n\ndef py_split(np.ndarray[np.float64_t, ndim=2] pos,\n             np.ndarray[np.float64_t, ndim=1] mins = None,\n             np.ndarray[np.float64_t, ndim=1] maxs = None,\n             bool use_sliding_midpoint = False):\n    r\"\"\"Get the indices required to split the positions equally along the\n    largest dimension.\n\n    Args:\n        pos (np.ndarray of float64): (n,m) array of n m-D coordinates.\n        mins (np.ndarray of float64, optional): (m,) array of mins. Defaults\n            to None and is set to mins of pos along each dimension.\n        maxs (np.ndarray of float64, optional): (m,) array of maxs. Defaults\n            to None and is set to maxs of pos along each dimension.\n        use_sliding_midpoint (bool, optional): If True, the sliding midpoint\n             rule is used to split the positions. Defaults to False.\n\n    Returns:\n        tuple(int64, uint32, np.ndarray of uint64): The index of the split in\n            the partitioned array, the dimension of the split, and the indices\n            required to partition the array.\n\n    \"\"\"\n    cdef np.intp_t npts = pos.shape[0]\n    cdef np.intp_t ndim = pos.shape[1]\n    cdef uint64_t Lidx = 0\n    cdef uint64_t[:] idx\n    idx = np.arange(pos.shape[0]).astype('uint64')\n    cdef double *ptr_pos = NULL\n    cdef uint64_t *ptr_idx = NULL\n    cdef double *ptr_mins = NULL\n    cdef double *ptr_maxs = NULL\n    if (npts != 0) and (ndim != 0):\n        if mins is None:\n            mins = np.min(pos, axis=0)\n        if maxs is None:\n            maxs = np.max(pos, axis=0)\n        ptr_pos = &pos[0,0]\n        ptr_idx = &idx[0]\n        ptr_mins = &mins[0]\n        ptr_maxs = &maxs[0]\n    cdef int64_t q = 0\n    cdef double split_val = 0.0\n    cdef cbool c_midpoint_flag = <cbool>use_sliding_midpoint\n    cdef uint32_t dsplit = split(ptr_pos, ptr_idx, Lidx, npts, ndim,\n                                 ptr_mins, ptr_maxs, q, split_val,\n                                 c_midpoint_flag)\n    return q, dsplit, idx\n"
  },
  {
    "path": "yt/utilities/lib/cykdtree/windows/stdint.h",
    "content": "// ISO C9x  compliant stdint.h for Microsoft Visual Studio\n// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124\n//\n//  Copyright (c) 2006-2013 Alexander Chemeris\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n//\n//   1. Redistributions of source code must retain the above copyright notice,\n//      this list of conditions and the following disclaimer.\n//\n//   2. Redistributions in binary form must reproduce the above copyright\n//      notice, this list of conditions and the following disclaimer in the\n//      documentation and/or other materials provided with the distribution.\n//\n//   3. Neither the name of the product nor the names of its contributors may\n//      be used to endorse or promote products derived from this software\n//      without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED\n// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO\n// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;\n// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\n// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\n// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n///////////////////////////////////////////////////////////////////////////////\n\n#ifndef _MSC_VER // [\n#error \"Use this header only with Microsoft Visual C++ compilers!\"\n#endif // _MSC_VER ]\n\n#ifndef _MSC_STDINT_H_ // [\n#define _MSC_STDINT_H_\n\n#if _MSC_VER > 1000\n#pragma once\n#endif\n\n#if _MSC_VER >= 1600 // [\n#include <stdint.h>\n#else // ] _MSC_VER >= 1600 [\n\n#include <limits.h>\n\n// For Visual Studio 6 in C++ mode and for many Visual Studio versions when\n// compiling for ARM we should wrap <wchar.h> include with 'extern \"C++\" {}'\n// or compiler give many errors like this:\n//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n#  include <wchar.h>\n#ifdef __cplusplus\n}\n#endif\n\n// Define _W64 macros to mark types changing their size, like intptr_t.\n#ifndef _W64\n#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300\n#     define _W64 __w64\n#  else\n#     define _W64\n#  endif\n#endif\n\n\n// 7.18.1 Integer types\n\n// 7.18.1.1 Exact-width integer types\n\n// Visual Studio 6 and Embedded Visual C++ 4 doesn't\n// realize that, e.g. char has the same size as __int8\n// so we give up on __intX for them.\n#if (_MSC_VER < 1300)\n   typedef signed char       int8_t;\n   typedef signed short      int16_t;\n   typedef signed int        int32_t;\n   typedef unsigned char     uint8_t;\n   typedef unsigned short    uint16_t;\n   typedef unsigned int      uint32_t;\n#else\n   typedef signed __int8     int8_t;\n   typedef signed __int16    int16_t;\n   typedef signed __int32    int32_t;\n   typedef unsigned __int8   uint8_t;\n   typedef unsigned __int16  uint16_t;\n   typedef unsigned __int32  uint32_t;\n#endif\ntypedef signed __int64       int64_t;\ntypedef unsigned __int64     uint64_t;\n\n\n// 7.18.1.2 Minimum-width integer types\ntypedef int8_t    int_least8_t;\ntypedef int16_t   int_least16_t;\ntypedef int32_t   int_least32_t;\ntypedef int64_t   int_least64_t;\ntypedef uint8_t   uint_least8_t;\ntypedef uint16_t  uint_least16_t;\ntypedef uint32_t  uint_least32_t;\ntypedef uint64_t  uint_least64_t;\n\n// 7.18.1.3 Fastest minimum-width integer types\ntypedef int8_t    int_fast8_t;\ntypedef int16_t   int_fast16_t;\ntypedef int32_t   int_fast32_t;\ntypedef int64_t   int_fast64_t;\ntypedef uint8_t   uint_fast8_t;\ntypedef uint16_t  uint_fast16_t;\ntypedef uint32_t  uint_fast32_t;\ntypedef uint64_t  uint_fast64_t;\n\n// 7.18.1.4 Integer types capable of holding object pointers\n#ifdef _WIN64 // [\n   typedef signed __int64    intptr_t;\n   typedef unsigned __int64  uintptr_t;\n#else // _WIN64 ][\n   typedef _W64 signed int   intptr_t;\n   typedef _W64 unsigned int uintptr_t;\n#endif // _WIN64 ]\n\n// 7.18.1.5 Greatest-width integer types\ntypedef int64_t   intmax_t;\ntypedef uint64_t  uintmax_t;\n\n\n// 7.18.2 Limits of specified-width integer types\n\n#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259\n\n// 7.18.2.1 Limits of exact-width integer types\n#define INT8_MIN     ((int8_t)_I8_MIN)\n#define INT8_MAX     _I8_MAX\n#define INT16_MIN    ((int16_t)_I16_MIN)\n#define INT16_MAX    _I16_MAX\n#define INT32_MIN    ((int32_t)_I32_MIN)\n#define INT32_MAX    _I32_MAX\n#define INT64_MIN    ((int64_t)_I64_MIN)\n#define INT64_MAX    _I64_MAX\n#define UINT8_MAX    _UI8_MAX\n#define UINT16_MAX   _UI16_MAX\n#define UINT32_MAX   _UI32_MAX\n#define UINT64_MAX   _UI64_MAX\n\n// 7.18.2.2 Limits of minimum-width integer types\n#define INT_LEAST8_MIN    INT8_MIN\n#define INT_LEAST8_MAX    INT8_MAX\n#define INT_LEAST16_MIN   INT16_MIN\n#define INT_LEAST16_MAX   INT16_MAX\n#define INT_LEAST32_MIN   INT32_MIN\n#define INT_LEAST32_MAX   INT32_MAX\n#define INT_LEAST64_MIN   INT64_MIN\n#define INT_LEAST64_MAX   INT64_MAX\n#define UINT_LEAST8_MAX   UINT8_MAX\n#define UINT_LEAST16_MAX  UINT16_MAX\n#define UINT_LEAST32_MAX  UINT32_MAX\n#define UINT_LEAST64_MAX  UINT64_MAX\n\n// 7.18.2.3 Limits of fastest minimum-width integer types\n#define INT_FAST8_MIN    INT8_MIN\n#define INT_FAST8_MAX    INT8_MAX\n#define INT_FAST16_MIN   INT16_MIN\n#define INT_FAST16_MAX   INT16_MAX\n#define INT_FAST32_MIN   INT32_MIN\n#define INT_FAST32_MAX   INT32_MAX\n#define INT_FAST64_MIN   INT64_MIN\n#define INT_FAST64_MAX   INT64_MAX\n#define UINT_FAST8_MAX   UINT8_MAX\n#define UINT_FAST16_MAX  UINT16_MAX\n#define UINT_FAST32_MAX  UINT32_MAX\n#define UINT_FAST64_MAX  UINT64_MAX\n\n// 7.18.2.4 Limits of integer types capable of holding object pointers\n#ifdef _WIN64 // [\n#  define INTPTR_MIN   INT64_MIN\n#  define INTPTR_MAX   INT64_MAX\n#  define UINTPTR_MAX  UINT64_MAX\n#else // _WIN64 ][\n#  define INTPTR_MIN   INT32_MIN\n#  define INTPTR_MAX   INT32_MAX\n#  define UINTPTR_MAX  UINT32_MAX\n#endif // _WIN64 ]\n\n// 7.18.2.5 Limits of greatest-width integer types\n#define INTMAX_MIN   INT64_MIN\n#define INTMAX_MAX   INT64_MAX\n#define UINTMAX_MAX  UINT64_MAX\n\n// 7.18.3 Limits of other integer types\n\n#ifdef _WIN64 // [\n#  define PTRDIFF_MIN  _I64_MIN\n#  define PTRDIFF_MAX  _I64_MAX\n#else  // _WIN64 ][\n#  define PTRDIFF_MIN  _I32_MIN\n#  define PTRDIFF_MAX  _I32_MAX\n#endif  // _WIN64 ]\n\n#define SIG_ATOMIC_MIN  INT_MIN\n#define SIG_ATOMIC_MAX  INT_MAX\n\n#ifndef SIZE_MAX // [\n#  ifdef _WIN64 // [\n#     define SIZE_MAX  _UI64_MAX\n#  else // _WIN64 ][\n#     define SIZE_MAX  _UI32_MAX\n#  endif // _WIN64 ]\n#endif // SIZE_MAX ]\n\n// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>\n#ifndef WCHAR_MIN // [\n#  define WCHAR_MIN  0\n#endif  // WCHAR_MIN ]\n#ifndef WCHAR_MAX // [\n#  define WCHAR_MAX  _UI16_MAX\n#endif  // WCHAR_MAX ]\n\n#define WINT_MIN  0\n#define WINT_MAX  _UI16_MAX\n\n#endif // __STDC_LIMIT_MACROS ]\n\n\n// 7.18.4 Limits of other integer types\n\n#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260\n\n// 7.18.4.1 Macros for minimum-width integer constants\n\n#define INT8_C(val)  val##i8\n#define INT16_C(val) val##i16\n#define INT32_C(val) val##i32\n#define INT64_C(val) val##i64\n\n#define UINT8_C(val)  val##ui8\n#define UINT16_C(val) val##ui16\n#define UINT32_C(val) val##ui32\n#define UINT64_C(val) val##ui64\n\n// 7.18.4.2 Macros for greatest-width integer constants\n// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>.\n// Check out Issue 9 for the details.\n#ifndef INTMAX_C //   [\n#  define INTMAX_C   INT64_C\n#endif // INTMAX_C    ]\n#ifndef UINTMAX_C //  [\n#  define UINTMAX_C  UINT64_C\n#endif // UINTMAX_C   ]\n\n#endif // __STDC_CONSTANT_MACROS ]\n\n#endif // _MSC_VER >= 1600 ]\n\n#endif // _MSC_STDINT_H_ ]\n"
  },
  {
    "path": "yt/utilities/lib/cyoctree.pyx",
    "content": "# distutils: libraries = STD_LIBS\n\"\"\"\nCyOctree building, loading and refining routines\n\"\"\"\ncimport cython\ncimport libc.math as math\ncimport numpy as np\n\nimport numpy as np\n\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.geometry.particle_deposit cimport get_kernel_func, kernel_func\n\nnp.import_array()\n\n\ncdef struct Octree:\n    # Array of 3*num_nodes [x1, y1, z1, x2, y2, z2, ...]\n    np.float64_t * node_positions\n    # 1 or 0 of whether the oct has refined to make children\n    np.uint8_t * refined\n    # Each oct stores the depth in the tree: the root node is 0\n    np.uint8_t * depth\n    # pstart and pend tell us which particles in the pidx are stored in each oct\n    np.int64_t * pstart\n    np.int64_t * pend\n    # This tells us the index of each child\n    np.int64_t * children\n\n    # Here we store the coordinates and IDs of all the particles in the tree\n    np.float64_t * pposx\n    np.float64_t * pposy\n    np.float64_t * pposz\n    np.int64_t * pidx\n\n    # The max number of particles per leaf, if above, we refine\n    np.int64_t n_ref\n\n    # The number of particles in our tree, e.g the length of ppos and pidx\n    np.int64_t num_particles\n\n    # The total size of the octree (x, y, z)\n    np.float64_t * size\n\n    # The current number of nodes in the octree\n    np.int64_t num_nodes\n\n    # The maximum depth before we stop refining, and the maximum number of nodes\n    # we can fit in our array\n    np.uint8_t max_depth\n    np.int64_t max_num_nodes\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef int octree_build_node(Octree * tree, long int node_idx):\n    \"\"\"\n    This is the main function in the building of the octree. This function takes\n    in the tree and an index of a oct to process. If the node has too many\n    particles (> n_ref) and the depth is less than the maximum tree depth then\n    we refine.\n\n    The `refine` creates 8 sub octs, within our oct. We indenfity the particles\n    in each of the subocts. We then recursively call this function on each of\n    the subocts.\n\n    Parameters\n    ----------\n    tree : Octree *\n        A pointer to the octree\n    node_idx : long int\n        The index of the current node we are processing\n\n    Returns\n    -------\n    int\n        Success of tree build\n    \"\"\"\n    cdef np.int64_t splits[9]\n    cdef np.int64_t i, j, k, n, start, end\n    cdef np.float64_t lx, ly, lz, sz, inv_size\n\n    # If we are running out of space in our tree, then we *try* to\n    # relloacate a tree of double the size\n    if (tree.num_nodes + 8) >= tree.max_num_nodes:\n        if octree_reallocate(tree, tree.max_num_nodes * 2):\n            return 1\n\n    if (\n        (tree.pend[node_idx] - tree.pstart[node_idx] > tree.n_ref) and\n        (tree.depth[node_idx] < tree.max_depth)\n    ):\n        tree.refined[node_idx] = 1\n\n        # As we have decided to refine, we need to know which of the particles\n        # in this oct will go into each of the 8 child octs\n        split_helper(tree, node_idx, splits)\n\n        # Figure out the size of the current oct\n        inv_size = 1. / 2.**tree.depth[node_idx]\n        sx = tree.size[0] * inv_size\n        sy = tree.size[1] * inv_size\n        sz = tree.size[2] * inv_size\n        lx = tree.node_positions[3*node_idx] - sx/2.\n        ly = tree.node_positions[(3*node_idx)+1] - sy/2.\n        lz = tree.node_positions[(3*node_idx)+2] - sz/2.\n\n        # Loop through and generate the children AND recursively refine...\n        n = 0\n        for i in range(2):\n            for j in range(2):\n                for k in range(2):\n                    start = splits[n]\n                    end = splits[n + 1]\n                    child = tree.num_nodes\n\n                    # Store the child location\n                    tree.children[8*node_idx + n] = child\n\n                    tree.node_positions[child*3] = lx + sx*i\n                    tree.node_positions[(child*3)+1] = ly + sy*j\n                    tree.node_positions[(child*3)+2] = lz + sz*k\n\n                    tree.refined[child] = 0\n                    tree.depth[child] = tree.depth[node_idx] + 1\n\n                    tree.pstart[child] = start\n                    tree.pend[child] = end\n                    tree.num_nodes += 1\n\n                    # Recursively refine child\n                    if octree_build_node(tree, child):\n                        return 1\n                    n += 1\n\n    return 0\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef int octree_allocate(Octree * octree, long int num_nodes):\n    \"\"\"\n    This is the main allocation function in the octree. We allocate all of the\n    arrays we require to store information about every single oct in the tree\n\n    Parameters\n    ----------\n    octree : Octree *\n        A pointer to the octree\n    num_nodes : long int\n        The maximum number of nodes to allocate for\n\n    Returns\n    -------\n    int\n        Success of allocation\n    \"\"\"\n    octree.node_positions = <np.float64_t *> malloc(\n        num_nodes * 3 * sizeof(np.float64_t))\n    if octree.node_positions == NULL:\n        return 1\n\n    octree.size = <np.float64_t *> malloc(3 * sizeof(np.float64_t))\n    if octree.size == NULL:\n        return 1\n\n    octree.children = <np.int64_t *> malloc(8 * num_nodes * sizeof(np.int64_t))\n    if octree.children == NULL:\n        return 1\n\n    octree.pstart = <np.int64_t *> malloc(num_nodes * sizeof(np.int64_t))\n    if octree.pstart == NULL:\n        return 1\n\n    octree.pend = <np.int64_t *> malloc(num_nodes * sizeof(np.int64_t))\n    if octree.pend == NULL:\n        return 1\n\n    octree.refined = <np.uint8_t *> malloc(num_nodes * sizeof(np.int8_t))\n    if octree.refined == NULL:\n        return 1\n\n    octree.depth = <np.uint8_t *> malloc(num_nodes * sizeof(np.int8_t))\n    if octree.depth == NULL:\n        return 1\n\n    return 0\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef int octree_reallocate(Octree * octree, long int num_nodes):\n    \"\"\"\n    This function re-allocates all of the arrays malloc'd in `octree_allocate`\n    See Notes for when we want to re-allocate.\n\n    Parameters\n    ----------\n    octree : Octree *\n        A pointer to the octree\n    num_nodes : long int\n        The maximum number of nodes to (re)allocate for\n\n    Returns\n    -------\n    int\n        Success of the reallocation\n\n    Notes\n    -----\n    Why do we want to re-allocate?\n\n    Well 2 cases,\n    1) The octree is still building and we have ran out of space, so we\n    have asked for an increased number of nodes and are reallocating each array\n\n    2) We have finished building the octree and we have used less nodes\n    than we originally allocated. We are now shrinking the octree and giving\n    the spare memory back.\n    \"\"\"\n    cdef np.float64_t * old_arr\n    cdef np.int64_t * old_arr_int\n    cdef np.uint8_t * old_arr_uint\n    cdef np.int64_t i\n\n    old_arr = octree.node_positions\n    octree.node_positions = <np.float64_t *> malloc(num_nodes * 3 * sizeof(np.float64_t))\n    if octree.node_positions == NULL: return 1\n    for i in range(3*octree.num_nodes):\n        octree.node_positions[i] = old_arr[i]\n    free(old_arr)\n\n    old_arr_int = octree.children\n    octree.children = <np.int64_t *> malloc(num_nodes * 8 * sizeof(np.int64_t))\n    if octree.children == NULL: return 1\n    for i in range(8*octree.num_nodes):\n        octree.children[i] = old_arr_int[i]\n    free(old_arr_int)\n\n    old_arr_int = octree.pstart\n    octree.pstart = <np.int64_t *> malloc(num_nodes * sizeof(np.int64_t))\n    if octree.pstart == NULL: return 1\n    for i in range(octree.num_nodes):\n        octree.pstart[i] = old_arr_int[i]\n    free(old_arr_int)\n\n    old_arr_int = octree.pend\n    octree.pend = <np.int64_t *> malloc(num_nodes * sizeof(np.int64_t))\n    if octree.pend == NULL: return 1\n    for i in range(octree.num_nodes):\n        octree.pend[i] = old_arr_int[i]\n    free(old_arr_int)\n\n    old_arr_uint = octree.refined\n    octree.refined = <np.uint8_t *> malloc(num_nodes * sizeof(np.int8_t))\n    if octree.refined == NULL: return 1\n    for i in range(octree.num_nodes):\n        octree.refined[i] = old_arr_uint[i]\n    free(old_arr_uint)\n\n    old_arr_uint = octree.depth\n    octree.depth = <np.uint8_t *> malloc(num_nodes * sizeof(np.int8_t))\n    if octree.depth == NULL: return 1\n    for i in range(octree.num_nodes):\n        octree.depth[i] = old_arr_uint[i]\n    free(old_arr_uint)\n\n    octree.max_num_nodes = num_nodes\n\n    return 0\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef void octree_deallocate(Octree * octree):\n    \"\"\"\n    Just free-ing every array we allocated to ensure we don't leak.\n\n    Parameter\n    ---------\n    octree : Octree *\n        Pointer to the octree\n    \"\"\"\n    free(octree.node_positions)\n    free(octree.size)\n    free(octree.children)\n\n    free(octree.pstart)\n    free(octree.pend)\n\n    free(octree.refined)\n    free(octree.depth)\n\n    free(octree.pidx)\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef class CyOctree:\n    \"\"\"\n    This a class to store the underlying octree and particle data that can be\n    interacted with from both Cython and Python\n    \"\"\"\n    cdef Octree * c_octree\n    cdef np.float64_t[::1, :] input_positions\n\n    cdef np.int64_t n_ref\n    cdef np.float64_t[:] left_edge\n    cdef np.float64_t[:] right_edge\n    cdef np.uint8_t max_depth\n\n    cdef kernel_func kernel\n\n    def __init__(\n        self,\n        np.float64_t[:, :] input_pos,\n        left_edge=None,\n        right_edge=None,\n        np.int64_t n_ref=32,\n        np.uint8_t max_depth=200\n    ):\n        \"\"\"\n        Octree initialiser.\n\n        We copy the inputted particle positions and make the root node. We then\n        refine the octree until every leaf has either less particles than n_ref\n        or is at the maximum depth.\n\n        Finally, we re-allocate all of the memory required by tree to ensure we\n        do not use more memory than required.\n\n        Parameters\n        ----------\n        input_pos : 2D memory view\n            Particles positions in the format (num_particles, 3)\n        {left,right}_edge : ndarray\n            xyz coordinates of the lower left (upper right) corner of the octree.\n        n_ref : int, default: 32\n            The maximum number of particles per leaf, if more, the oct\n            will refine\n        max_depth : int, default: 200\n            The maximum depth the octree will refine to. If we set\n            this too high then we may hit a stack overflow due to the\n            recursive nature of the build\n        \"\"\"\n        self.n_ref = n_ref\n        self.max_depth = max_depth\n        self.input_positions = np.asfortranarray(input_pos, dtype=np.float64)\n\n        if self._allocate_octree():\n            raise MemoryError(\"Unable to allocate memory required for octree build.\")\n\n        self._make_root(left_edge, right_edge)\n\n        if octree_build_node(self.c_octree, 0):\n            raise MemoryError(\"Unable to allocate memory required for octree build.\")\n\n        if octree_reallocate(self.c_octree, self.c_octree.num_nodes):\n            raise MemoryError(\"Unable to allocate memory required for octree build.\")\n\n    def __del__(self):\n        \"\"\"\n        Make sure we clean up properly!\n        \"\"\"\n        octree_deallocate(self.c_octree)\n        free(self.c_octree)\n\n    @property\n    def bound_particles(self):\n        \"\"\"\n        The particle selection may select SPH particles with smoothing lengths\n        which is in the octree domains. However, if the particle center is NOT\n        in the octree, they are not included.\n\n        So the number of particles passed to the tree *may* not be equal to the\n        number of particles which are bound by the tree.\n        \"\"\"\n        return self.c_octree.pend[0]\n\n    @property\n    def num_nodes(self):\n        \"\"\"\n        The total number of nodes after tree construction\n        \"\"\"\n        return self.c_octree.num_nodes\n\n    @property\n    def node_positions(self):\n        \"\"\"\n        The centre of every node within the octree\n        \"\"\"\n        cdef np.npy_intp shape[2]\n        shape[0] = <np.npy_intp> self.c_octree.num_nodes\n        shape[1] = 3\n        arr = np.PyArray_SimpleNewFromData(\n            2, &shape[0], np.NPY_FLOAT64, <void *>self.c_octree.node_positions)\n        return np.copy(arr)\n\n    @property\n    def node_refined(self):\n        \"\"\"\n        An array of length num_nodes which contains either True / False for\n        whether each cell has refined or not. E.g False for a leaf, True for a\n        node\n        \"\"\"\n        cdef np.npy_intp shape\n        shape = <np.npy_intp> self.c_octree.num_nodes\n        arr = np.PyArray_SimpleNewFromData(\n            1, &shape, np.NPY_UINT8, <void *>self.c_octree.refined)\n        return np.copy(arr).astype(np.bool_)\n\n    @property\n    def node_depth(self):\n        \"\"\"\n        The depth for each node in the tree. The root node is defined as a\n        depth of 0.\n        \"\"\"\n        cdef np.npy_intp shape = <np.npy_intp> self.c_octree.num_nodes\n        arr = np.PyArray_SimpleNewFromData(\n            1, &shape, np.NPY_UINT8, <void *>self.c_octree.depth)\n        return np.copy(arr)\n\n    @property\n    def node_sizes(self):\n        \"\"\"\n        The size of each node in the x, y and z directions. We calculate this\n        on the fly. As we know the size of the whole tree and the depth of each\n        node\n        \"\"\"\n        cdef np.int64_t i\n        sizes = np.zeros((self.c_octree.num_nodes, 3), dtype=np.float64)\n        sizes[:, 0] = self.c_octree.size[0]\n        sizes[:, 1] = self.c_octree.size[1]\n        sizes[:, 2] = self.c_octree.size[2]\n\n        for i in range(self.c_octree.num_nodes):\n            sizes[i, :] /= <np.float64_t> (2.0**self.c_octree.depth[i] / 2.0)\n\n        return sizes\n\n    def _make_root(self, left_edge, right_edge):\n        \"\"\"\n        The root node is the hardest node to build as we need to find out which\n        particles we contain, and then sieving them into children is easy.\n\n        In the case that the left_edge/right_edge is not defined then we select\n        a tree that is sufficiently big to contain every particle.\n\n        However, if they are defined then we need to loop through and find out\n        which particles are actually in the tree.\n\n        Parameters\n        ----------\n        {left,right}_edge : ndarray\n            xyz coordinates of the lower left (upper right) corner of the octree.\n            If None, the tree will be made large enough to encompass all particles.\n        \"\"\"\n        cdef int i = 0\n\n        # How many particles are there?\n        self.c_octree.num_particles = self.input_positions.shape[0]\n\n        # We now number all of the the particles in the tree. This allows us\n        # to shuffle the pids and say for example Oct11 contains particles\n        # 7 to 11\n        # This pidx[7:12] would give the input indices of the particles we\n        # store. This proxy allows us to re-arrange the particles without\n        # re-arranging the users input data.\n        self.c_octree.pidx = <np.int64_t *> malloc(\n            self.c_octree.num_particles * sizeof(np.int64_t)\n        )\n        for i in range(0, self.c_octree.num_particles):\n            self.c_octree.pidx[i] = i\n\n        if left_edge is None:\n            # If the edges are None, then we can just find the loop through\n            # and find them out\n            left_edge = np.zeros(3, dtype=np.float64)\n            right_edge = np.zeros(3, dtype=np.float64)\n\n            left_edge[0] = self.c_octree.pposx[0]\n            left_edge[1] = self.c_octree.pposy[0]\n            left_edge[2] = self.c_octree.pposz[0]\n            right_edge[0] = self.c_octree.pposx[0]\n            right_edge[1] = self.c_octree.pposy[0]\n            right_edge[2] = self.c_octree.pposz[0]\n\n            for i in range(self.c_octree.num_particles):\n                left_edge[0] = min(self.c_octree.pposx[i], left_edge[0])\n                left_edge[1] = min(self.c_octree.pposy[i], left_edge[1])\n                left_edge[2] = min(self.c_octree.pposz[i], left_edge[2])\n\n                right_edge[0] = max(self.c_octree.pposx[i], right_edge[0])\n                right_edge[1] = max(self.c_octree.pposy[i], right_edge[1])\n                right_edge[2] = max(self.c_octree.pposz[i], right_edge[2])\n\n            self.c_octree.pstart[0] = 0\n            self.c_octree.pend[0] = self.input_positions.shape[0]\n        else:\n            # Damn! The user did supply a left and right so we need to find\n            # which particles are in the range\n            left_edge = left_edge.astype(np.float64)\n            right_edge = right_edge.astype(np.float64)\n\n            # We loop through the particles and arrange them such that particles\n            # in the tree are to the left of the split and the particles not\n            # are to the right\n            # e.g.\n            # pidx = [1, 2, 3, 5 | 0, 4]\n            # where split = 4 and particles 0 and 4 are not in the tree\n            split = select(\n                self.c_octree, left_edge, right_edge, 0, self.input_positions.shape[0])\n            self.c_octree.pstart[0] = 0\n            self.c_octree.pend[0] = split\n\n        # Set the total size of the tree\n        size = (right_edge - left_edge) / 2.0\n        center = (right_edge + left_edge) / 2.0\n        self.left_edge = left_edge\n        self.right_edge = right_edge\n\n        # Now we add the data about the root node!\n        self.c_octree.node_positions[0] = center[0]\n        self.c_octree.node_positions[1] = center[1]\n        self.c_octree.node_positions[2] = center[2]\n\n        self.c_octree.size[0] = size[0]\n        self.c_octree.size[1] = size[1]\n        self.c_octree.size[2] = size[2]\n\n        # We are not refined yet\n        self.c_octree.refined[0] = 0\n        self.c_octree.depth[0] = 0\n\n    def _allocate_octree(self):\n        \"\"\"\n        This actually allocates the C struct Octree\n        \"\"\"\n        self.c_octree = <Octree*> malloc(sizeof(Octree))\n        self.c_octree.n_ref = self.n_ref\n        self.c_octree.num_nodes = 1\n\n        # This is sort of an arbitrary guess but it doesn't matter because\n        # we will increase this value and attempt to reallocate if it is too\n        # small\n        self.c_octree.max_num_nodes = max(self.input_positions.shape[0] / self.n_ref, 1)\n        self.c_octree.max_depth = self.max_depth\n\n        # Fast C pointers to the particle coordinates\n        self.c_octree.pposx = &self.input_positions[0, 0]\n        self.c_octree.pposy = &self.input_positions[0, 1]\n        self.c_octree.pposz = &self.input_positions[0, 2]\n\n        if octree_allocate(self.c_octree, self.c_octree.max_num_nodes): return 1\n\n    cdef void smooth_onto_cells(\n        self,\n        np.float64_t[:] buff,\n        np.float64_t[:] buff_den,\n        np.float64_t posx,\n        np.float64_t posy,\n        np.float64_t posz,\n        np.float64_t hsml,\n        np.float64_t prefactor,\n        np.float64_t prefactor_norm,\n        long int num_node,\n        int use_normalization=0\n    ):\n        \"\"\"\n        We smooth a field onto cells within an octree using SPH deposition. To\n        achieve this we loop through every oct in the tree and check if it is a\n        leaf. A leaf just means that an oct has not refined, and thus has no\n        children.\n\n        Parameters\n        ----------\n        buff : memoryview\n            The array which we are depositing the field onto, it has the\n            length of the number of leaves.\n        buff_den : memoryview\n            The array we deposit just mass onto to allow normalization\n        pos<> : float64_t\n            The x, y, and z coordinates of the particle we are depositing\n        hsml : float64_t\n            The smoothing length of the particle\n        prefactor(_norm) : float64_t\n            This is a pre-computed value, based on the particles\n                properties used in the deposition\n        num_node : lonmg int\n            The current node we are checking to see if refined or not\n        use_normalization : int, default: 0\n            Do we want a normalized sph field? If so, fill the buff_den.\n        \"\"\"\n\n        cdef Octree * tree = self.c_octree\n        cdef double q_ij, diff_x, diff_y, diff_z, diff, sx, sy, sz\n        cdef int i\n        cdef long int child_node\n\n        if tree.refined[num_node] == 0:\n            diff_x = tree.node_positions[3*num_node] - posx\n            diff_y = tree.node_positions[3*num_node+1] - posy\n            diff_z = tree.node_positions[3*num_node+2] - posz\n\n            q_ij = math.sqrt(diff_x*diff_x + diff_y*diff_y + diff_z*diff_z)\n            q_ij /= hsml\n\n            buff[num_node] += (prefactor * self.kernel(q_ij))\n            if use_normalization:\n                buff_den[num_node] += (prefactor_norm * self.kernel(q_ij))\n\n        else:\n            # All direct children of the current node are the same size, thus\n            # we can compute their size once, outside of the loop\n            sz_factor = 1.0 / 2.0**(tree.depth[num_node] + 1)\n            sqrt_sz_factor = math.sqrt(sz_factor)\n            sx = tree.size[0]\n            sy = tree.size[1]\n            sz = tree.size[2]\n            child_node_size = sqrt_sz_factor * math.sqrt(sx*sx + sy*sy + sz*sz)\n\n            for i in range(8):\n                child_node = tree.children[8*num_node + i]\n                diff_x = tree.node_positions[3*child_node] - posx\n                diff_y = tree.node_positions[3*child_node+1] - posy\n                diff_z = tree.node_positions[3*child_node+2] - posz\n                diff = math.sqrt(diff_x*diff_x + diff_y*diff_y + diff_z*diff_z)\n\n                # Could the current particle possibly intersect this child node?\n                if diff - child_node_size - hsml < 0:\n                    self.smooth_onto_cells(buff, buff_den, posx, posy, posz,\n                                    hsml, prefactor, prefactor_norm,\n                                    child_node, use_normalization=use_normalization)\n\n\n    def interpolate_sph_cells(self,\n        np.float64_t[:] buff,\n        np.float64_t[:] buff_den,\n        np.float64_t[:] posx,\n        np.float64_t[:] posy,\n        np.float64_t[:] posz,\n        np.float64_t[:] pmass,\n        np.float64_t[:] pdens,\n        np.float64_t[:] hsml,\n        np.float64_t[:] field,\n        kernel_name=\"cubic\",\n        int use_normalization=0\n    ):\n        \"\"\"\n        We loop through every particle in the simulation and begin to deposit\n        the particle properties onto all of the leaves that it intersects\n\n        Parameters\n        ----------\n        buff : memoryview\n            The array which we are depositing the field onto, it has the\n            length of the number of leaves.\n        buff_den : memoryview\n            The array we deposit just mass onto to allow normalization\n        pos<> : memoryview\n            The x, y, and z coordinates of all the partciles\n        pmass : memoryview\n            The mass of the particles\n        pdens : memoryview\n            The density of the particles\n        hsml : memoryview\n            The smoothing lengths of the particles\n        field : memoryview\n            The field we are depositing for each particle\n        kernel_name: str, default: \"cubic\"\n            Choice of kernel for SPH deposition\n        use_normalization : int, default: 0\n            Do we want a normalized sph field? If so, fill the buff_den.\n        \"\"\"\n        self.kernel = get_kernel_func(kernel_name)\n\n        cdef int i\n        cdef double prefactor, prefactor_norm\n        for i in range(posx.shape[0]):\n            prefactor = pmass[i] / pdens[i] / hsml[i]**3\n            prefactor_norm = prefactor\n            prefactor *= field[i]\n\n            self.smooth_onto_cells(buff, buff_den, posx[i], posy[i], posz[i],\n                                    hsml[i], prefactor, prefactor_norm,\n                                    0, use_normalization=use_normalization)\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef np.int64_t separate(\n        np.float64_t * pos,\n        np.int64_t * pidx,\n        double value,\n        np.int64_t start,\n        np.int64_t end\n    ) noexcept nogil:\n    \"\"\"\n    This is a simple utility function which takes a selection of particles and\n    re-arranges them such that values below `value` are to the left of split and\n    values above are to the right.\n\n    Parameters\n    ----------\n    pos : float64_t *\n        Pointer to the coordinates we are splitting along\n    pidx : int64_t &\n        Pointer to the corresponding particle IDs\n    value : double\n        The value to split the data along\n    start : int64_t\n        Index of first particle in the current node\n    end : int64_t\n        Index of the last particle in the current node\n    \"\"\"\n    cdef np.int64_t index\n    cdef np.int64_t split = start\n    for index in range(start, end):\n        if pos[pidx[index]] < value:\n            pidx[split], pidx[index] = pidx[index], pidx[split]\n            split +=  1\n\n    return split\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void split_helper(Octree * tree, np.int64_t node_idx, np.int64_t * splits):\n    \"\"\"\n    A utility function to split a collection of particles along the x, y and z\n    direction such that we identify the particles within the 8 children of an\n    oct.\n\n    We first split the particles in the x direction, then we split the particles\n    in the y and the z directions. This is currently hardcoded for 8 octs but\n    can be readily extended to allow over-refinement.\n\n    The splits[0] and splits[1] tell oct one the start and last particle\n    The splits[1] and splits[2] tell oct two the start and last particle and so\n    on\n\n    Parameters\n    ----------\n    tree : Octree *\n        A pointer to the octree\n    node_idx :  int64_t\n        The index of the oct we are splitting\n    splits : int64_t *\n        Pointer to split array which stores the start and end indices. It needs\n        to be N+1 long where N is the number of children\n    \"\"\"\n    splits[0] = tree.pstart[node_idx]\n    splits[8] = tree.pend[node_idx]\n\n    splits[4] = separate(\n        tree.pposx, tree.pidx, tree.node_positions[3*node_idx], splits[0], splits[8])\n\n    splits[2] = separate(\n        tree.pposy, tree.pidx, tree.node_positions[(3*node_idx)+1], splits[0], splits[4])\n    splits[6] = separate(\n        tree.pposy, tree.pidx, tree.node_positions[(3*node_idx)+1], splits[4], splits[8])\n\n    splits[1] = separate(\n        tree.pposz, tree.pidx, tree.node_positions[(3*node_idx)+2], splits[0], splits[2])\n    splits[3] = separate(\n        tree.pposz, tree.pidx, tree.node_positions[(3*node_idx)+2], splits[2], splits[4])\n    splits[5] = separate(\n        tree.pposz, tree.pidx, tree.node_positions[(3*node_idx)+2], splits[4], splits[6])\n    splits[7] = separate(\n        tree.pposz, tree.pidx, tree.node_positions[(3*node_idx)+2], splits[6], splits[8])\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef np.int64_t select(\n        Octree * octree,\n        np.float64_t[::1] left_edge,\n        np.float64_t[::1] right_edge,\n        np.int64_t start,\n        np.int64_t end\n    ) noexcept nogil:\n    \"\"\"\n    Re-arrange the input particles such that those outside the bounds of the\n    tree occur after the split index and can thus be ignored for the remainder\n    of the tree construction\n\n    Parameters\n    ----------\n    tree : Octree *\n        A pointer to the octree\n    left_edge : ndarray\n        The coords of the lower left corner of the box\n    right_edge : ndarray\n        The coords of the upper right corner of the box\n    start : int64_t\n        The first particle in the bounds (0)\n    end : int64_t\n        The last particle in the bounds\n    \"\"\"\n    cdef np.int64_t index\n    cdef np.int64_t split = start\n\n    cdef np.float64_t * posx = octree.pposx\n    cdef np.float64_t * posy = octree.pposy\n    cdef np.float64_t * posz = octree.pposz\n    cdef np.int64_t * pidx = octree.pidx\n\n    for index in range(start, end):\n        if posx[pidx[index]] < right_edge[0] and posx[pidx[index]] > left_edge[0]:\n            if posy[pidx[index]] < right_edge[1] and posy[pidx[index]] > left_edge[1]:\n                if posz[pidx[index]] < right_edge[2] and posz[pidx[index]] > left_edge[2]:\n                    if split < index:\n                        pidx[split], pidx[index] = pidx[index], pidx[split]\n                    split += 1\n\n    return split\n"
  },
  {
    "path": "yt/utilities/lib/depth_first_octree.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nThis is a recursive function to return a depth-first octree\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\n\ncdef class position:\n    cdef public int output_pos, refined_pos\n    def __cinit__(self):\n        self.output_pos = 0\n        self.refined_pos = 0\n\ncdef class OctreeGrid:\n    cdef public object child_indices, fields, left_edges, dimensions, dx\n    cdef public int level, offset\n    def __cinit__(self,\n                  np.ndarray[np.int32_t, ndim=3] child_indices,\n                  np.ndarray[np.float64_t, ndim=4] fields,\n                  np.ndarray[np.float64_t, ndim=1] left_edges,\n                  np.ndarray[np.int32_t, ndim=1] dimensions,\n                  np.ndarray[np.float64_t, ndim=1] dx,\n                  int level, int offset):\n        self.child_indices = child_indices\n        self.fields = fields\n        self.left_edges = left_edges\n        self.dimensions = dimensions\n        self.dx = dx\n        self.level = level\n        self.offset = offset\n\ncdef class OctreeGridList:\n    cdef public object grids\n    def __cinit__(self, grids):\n        self.grids = grids\n\n    def __getitem__(self, int item):\n        return self.grids[item]\n\n@cython.boundscheck(False)\ndef RecurseOctreeDepthFirst(int i_i, int j_i, int k_i,\n                            int i_f, int j_f, int k_f,\n                            position curpos, int gi,\n                            np.ndarray[np.float64_t, ndim=2] output,\n                            np.ndarray[np.int32_t, ndim=1] refined,\n                            OctreeGridList grids):\n    #cdef int s = curpos\n    cdef int i, i_off, j, j_off, k, k_off, ci, fi\n    cdef int child_i, child_j, child_k\n    cdef OctreeGrid child_grid\n    cdef OctreeGrid grid = grids[gi]\n    cdef np.ndarray[np.float64_t, ndim=4] fields = grid.fields\n    cdef np.ndarray[np.float64_t, ndim=1] leftedges = grid.left_edges\n    cdef np.float64_t dx = grid.dx[0]\n    cdef np.float64_t child_dx\n    cdef np.ndarray[np.float64_t, ndim=1] child_leftedges\n    cdef np.float64_t cx, cy, cz\n    #here we go over the 8 octants\n    #in general however, a mesh cell on this level\n    #may have more than 8 children on the next level\n    #so we find the int float center (cxyz) of each child cell\n    # and from that find the child cell indices\n    for i_off in range(i_f):\n        i = i_off + i_i #index\n        cx = (leftedges[0] + i*dx)\n        for j_off in range(j_f):\n            j = j_off + j_i\n            cy = (leftedges[1] + j*dx)\n            for k_off in range(k_f):\n                k = k_off + k_i\n                cz = (leftedges[2] + k*dx)\n                ci = grid.child_indices[i,j,k]\n                if ci == -1:\n                    for fi in range(fields.shape[0]):\n                        output[curpos.output_pos,fi] = fields[fi,i,j,k]\n                    refined[curpos.refined_pos] = 0\n                    curpos.output_pos += 1\n                    curpos.refined_pos += 1\n                else:\n                    refined[curpos.refined_pos] = 1\n                    curpos.refined_pos += 1\n                    child_grid = grids[ci-grid.offset]\n                    child_dx = child_grid.dx[0]\n                    child_leftedges = child_grid.left_edges\n                    child_i = int((cx - child_leftedges[0])/child_dx)\n                    child_j = int((cy - child_leftedges[1])/child_dx)\n                    child_k = int((cz - child_leftedges[2])/child_dx)\n                    # s = Recurs.....\n                    RecurseOctreeDepthFirst(child_i, child_j, child_k, 2, 2, 2,\n                                        curpos, ci - grid.offset, output, refined, grids)\n\n@cython.boundscheck(False)\ndef RecurseOctreeByLevels(int i_i, int j_i, int k_i,\n                          int i_f, int j_f, int k_f,\n                          np.ndarray[np.int32_t, ndim=1] curpos,\n                          int gi,\n                          np.ndarray[np.float64_t, ndim=2] output,\n                          np.ndarray[np.int32_t, ndim=2] genealogy,\n                          np.ndarray[np.float64_t, ndim=2] corners,\n                          OctreeGridList grids):\n    cdef np.int32_t i, i_off, j, j_off, k, k_off, ci, fi\n    cdef int child_i, child_j, child_k\n    cdef OctreeGrid child_grid\n    cdef OctreeGrid grid = grids[gi-1]\n    cdef int level = grid.level\n    cdef np.ndarray[np.int32_t, ndim=3] child_indices = grid.child_indices\n    cdef np.ndarray[np.float64_t, ndim=4] fields = grid.fields\n    cdef np.ndarray[np.float64_t, ndim=1] leftedges = grid.left_edges\n    cdef np.float64_t dx = grid.dx[0]\n    cdef np.float64_t child_dx\n    cdef np.ndarray[np.float64_t, ndim=1] child_leftedges\n    cdef np.float64_t cx, cy, cz\n    cdef int cp\n    s = None\n    for i_off in range(i_f):\n        i = i_off + i_i\n        cx = (leftedges[0] + i*dx)\n        for j_off in range(j_f):\n            j = j_off + j_i\n            cy = (leftedges[1] + j*dx)\n            for k_off in range(k_f):\n                k = k_off + k_i\n                cz = (leftedges[2] + k*dx)\n                cp = curpos[level]\n                corners[cp, 0] = cx\n                corners[cp, 1] = cy\n                corners[cp, 2] = cz\n                genealogy[curpos[level], 2] = level\n                # always output data\n                for fi in range(fields.shape[0]):\n                    output[cp,fi] = fields[fi,i,j,k]\n                ci = child_indices[i,j,k]\n                if ci > -1:\n                    child_grid = grids[ci-1]\n                    child_dx = child_grid.dx[0]\n                    child_leftedges = child_grid.left_edges\n                    child_i = int((cx-child_leftedges[0])/child_dx)\n                    child_j = int((cy-child_leftedges[1])/child_dx)\n                    child_k = int((cz-child_leftedges[2])/child_dx)\n                    # set current child id to id of next cell to examine\n                    genealogy[cp, 0] = curpos[level+1]\n                    # set next parent id to id of current cell\n                    genealogy[curpos[level+1]:curpos[level+1]+8, 1] = cp\n                    s = RecurseOctreeByLevels(child_i, child_j, child_k, 2, 2, 2,\n                                              curpos, ci, output, genealogy,\n                                              corners, grids)\n                curpos[level] += 1\n    return s\n"
  },
  {
    "path": "yt/utilities/lib/distance_queue.pxd",
    "content": "\"\"\"\nA queue for evaluating distances to discrete points\n\n\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\n\nimport numpy as np\n\nfrom libc.stdlib cimport free, malloc\nfrom libc.string cimport memmove\n\n# THESE TWO STRUCTS MUST BE EQUIVALENT\n\ncdef struct ItemList:\n    np.int64_t ind\n    np.float64_t value\n\ncdef struct NeighborList:\n    np.int64_t pn       # Particle number\n    np.float64_t r2     # radius**2\n\ncdef int Neighbor_compare(void *on1, void *on2) nogil\ncdef np.float64_t r2dist(np.float64_t ppos[3],\n                         np.float64_t cpos[3],\n                         np.float64_t DW[3],\n                         bint periodicity[3],\n                         np.float64_t max_dist2)\n\ncdef class PriorityQueue:\n    cdef int maxn\n    cdef int curn\n    cdef ItemList* items\n    cdef void item_reset(self)\n    cdef int item_insert(self, np.int64_t i, np.float64_t value)\n\ncdef class DistanceQueue(PriorityQueue):\n    cdef np.float64_t DW[3]\n    cdef bint periodicity[3]\n    cdef NeighborList* neighbors # flat array\n    cdef void _setup(self, np.float64_t DW[3], bint periodicity[3])\n    cdef void neighbor_eval(self, np.int64_t pn, np.float64_t ppos[3],\n                            np.float64_t cpos[3])\n    cdef void neighbor_reset(self)\n"
  },
  {
    "path": "yt/utilities/lib/distance_queue.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nDistance queue implementation\n\n\n\n\n\"\"\"\n\ncimport numpy as np\n\nimport numpy as np\n\ncimport cython\n\n\ncdef int Neighbor_compare(void *on1, void *on2) noexcept nogil:\n    cdef NeighborList *n1\n    cdef NeighborList *n2\n    n1 = <NeighborList *> on1\n    n2 = <NeighborList *> on2\n    # Note that we set this up so that \"greatest\" evaluates to the *end* of the\n    # list, so we can do standard radius comparisons.\n    if n1.r2 < n2.r2:\n        return -1\n    elif n1.r2 == n2.r2:\n        return 0\n    else:\n        return 1\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.initializedcheck(False)\ncdef np.float64_t r2dist(np.float64_t ppos[3],\n                         np.float64_t cpos[3],\n                         np.float64_t DW[3],\n                         bint periodicity[3],\n                         np.float64_t max_dist2):\n    cdef int i\n    cdef np.float64_t r2, DR\n    r2 = 0.0\n    for i in range(3):\n        DR = (ppos[i] - cpos[i])\n        if not periodicity[i]:\n            pass\n        elif (DR > DW[i]/2.0):\n            DR -= DW[i]\n        elif (DR < -DW[i]/2.0):\n            DR += DW[i]\n        r2 += DR * DR\n        if max_dist2 >= 0.0 and r2 > max_dist2:\n            return -1.0\n    return r2\n\ncdef class PriorityQueue:\n    \"\"\"This class acts as a \"priority-queue.\"  It was extracted from the\n    DistanceQueue object so that it could serve as a general-purpose method for\n    storing the N-most \"valuable\" objects.  It's relatively simple, in that it\n    provides storage for a single int64 (which is usually an 'index' into an\n    external array or list) and a single float64 \"value\" associated with them.\n    You can insert new objects, and then if it's already at maxn objects in it,\n    it'll bump the least valuable one off the end.  Of particular note is that\n    *lower* values are considered to be *more valuable* than higher ones; this\n    is because our typical use case is to store radii.\n    \"\"\"\n    def __cinit__(self, int maxn):\n        self.maxn = maxn\n        self.curn = 0\n        self.items = <ItemList *> malloc(\n            sizeof(ItemList) * self.maxn)\n\n    cdef void item_reset(self):\n        for i in range(self.maxn):\n            self.items[i].value = 1e300\n            self.items[i].ind = -1\n        self.curn = 0\n\n    cdef int item_insert(self, np.int64_t ind, np.float64_t value):\n        cdef int i, di\n        if self.curn == 0:\n            self.items[0].value = value\n            self.items[0].ind = ind\n            self.curn += 1\n            return 0\n        # Now insert in a sorted way\n        di = -1\n        for i in range(self.curn - 1, -1, -1):\n            # We are checking if i is less than us, to see if we should insert\n            # to the right (i.e., i+1).\n            if self.items[i].value < value:\n                di = i\n                break\n        # The outermost one is already too small.\n        if di == self.maxn - 1:\n            return -1\n        if (self.maxn - (di + 2)) > 0:\n            memmove(<void *> (self.items + di + 2),\n                    <void *> (self.items + di + 1),\n                    sizeof(ItemList) * (self.maxn - (di + 2)))\n        self.items[di + 1].value = value\n        self.items[di + 1].ind = ind\n        if self.curn < self.maxn:\n            self.curn += 1\n        return di + 1\n\ncdef class DistanceQueue:\n    \"\"\"This is a distance queue object, designed to incrementally evaluate N\n    positions against a reference point.  It is initialized with the maximum\n    number that are to be retained (i.e., maxn-nearest neighbors).\"\"\"\n    def __cinit__(self, int maxn):\n        if sizeof(ItemList) != sizeof(NeighborList):\n            # This should almost never, ever happen unless we do something very\n            # wrong, and must be broken at compile time.\n            raise RuntimeError\n        self.neighbors = <NeighborList *> self.items\n        self.neighbor_reset()\n        for i in range(3):\n            self.DW[i] = 0\n            self.periodicity[i] = False\n\n    cdef void _setup(self, np.float64_t DW[3], bint periodicity[3]):\n        for i in range(3):\n            self.DW[i] = DW[i]\n            self.periodicity[i] = periodicity[i]\n\n    def setup(self, np.float64_t[:] DW, periodicity):\n        for i in range(3):\n            self.DW[i] = DW[i]\n            self.periodicity[i] = periodicity[i]\n\n    def __dealloc__(self):\n        free(self.neighbors)\n\n    cdef void neighbor_eval(self, np.int64_t pn, np.float64_t ppos[3],\n                            np.float64_t cpos[3]):\n        # Here's a python+numpy simulator of this:\n        # http://paste.yt-project.org/show/5445/\n        cdef np.float64_t r2, r2_trunc\n        if self.curn == self.maxn:\n            # Truncate calculation if it's bigger than this in any dimension\n            r2_trunc = self.neighbors[self.curn - 1].r2\n        else:\n            # Don't truncate our calculation\n            r2_trunc = -1\n        r2 = r2dist(ppos, cpos, self.DW, self.periodicity, r2_trunc)\n        if r2 == -1:\n            return\n        self.item_insert(pn, r2)\n\n    cdef void neighbor_reset(self):\n        self.item_reset()\n\n    def find_nearest(self, np.float64_t[:] center, np.float64_t[:,:] points):\n        \"\"\"This function accepts a center and a set of [N,3] points, and it\n        will return the indices into the points array of the maxn closest\n        neighbors.\"\"\"\n        cdef int i, j\n        cdef np.float64_t ppos[3]\n        cdef np.float64_t cpos[3]\n        self.neighbor_reset()\n        for i in range(3):\n            cpos[i] = center[i]\n        for j in range(points.shape[0]):\n            for i in range(3):\n                ppos[i] = points[j,i]\n            self.neighbor_eval(j, ppos, cpos)\n        rv = np.empty(self.curn, dtype=\"int64\")\n        for i in range(self.curn):\n            rv[i] = self.neighbors[i].pn\n        return rv\n"
  },
  {
    "path": "yt/utilities/lib/element_mappings.pxd",
    "content": "cimport cython\ncimport numpy as np\nfrom numpy cimport ndarray\n\nimport numpy as np\n\nfrom libc.math cimport fabs, fmax\n\n\ncdef class ElementSampler:\n\n    # how close a point has to be to the element\n    # to get counted as \"inside\". This is in the\n    # mapped coordinates of the element.\n    cdef np.float64_t inclusion_tol\n    cdef int num_mapped_coords\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n\n    cdef double sample_at_real_point(self,\n                                     double* vertices,\n                                     double* field_values,\n                                     double* physical_x) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil\n\n\ncdef class P1Sampler1D(ElementSampler):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\n\ncdef class P1Sampler2D(ElementSampler):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\n\ncdef class P1Sampler3D(ElementSampler):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil\n\n\n# This typedef defines a function pointer that defines the system\n# of equations that will be solved by the NonlinearSolveSamplers.\n#\n# inputs:\n#     x        - pointer to the mapped coordinate\n#     vertices - pointer to the element vertices\n#     phys_x   - pointer to the physical coordinate\n#\n# outputs:\n#\n#     fx - the result of solving the system, should be close to 0\n#          once it is converged.\n#\nctypedef void (*func_type)(double* fx,\n                           double* x,\n                           double* vertices,\n                           double* phys_x) noexcept nogil\n\n# This typedef defines a function pointer that defines the Jacobian\n# matrix used by the NonlinearSolveSampler3D. Subclasses needed to\n# define a Jacobian function in this form.\n#\n# inputs:\n#     x        - pointer to the mapped coordinate\n#     vertices - pointer to the element vertices\n#     phys_x   - pointer to the physical coordinate\n#\n# outputs:\n#\n#     rcol     - the first column of the jacobian\n#     scol     - the second column of the jacobian\n#     tcol     - the third column of the jaocobian\n#\nctypedef void (*jac_type3D)(double* rcol,\n                            double* scol,\n                            double* tcol,\n                            double* x,\n                            double* vertices,\n                            double* phys_x) noexcept nogil\n\n\n# This typedef defines a function pointer that defines the Jacobian\n# matrix used by the NonlinearSolveSampler2D. Subclasses needed to\n# define a Jacobian function in this form.\n#\n# inputs:\n#     x        - pointer to the mapped coordinate\n#     vertices - pointer to the element vertices\n#     phys_x   - pointer to the physical coordinate\n#\n# outputs:\n#\n#     rcol     - the first column of the jacobian\n#     scol     - the second column of the jacobian\n#\nctypedef void (*jac_type2D)(double* rcol,\n                            double* scol,\n                            double* x,\n                            double* vertices,\n                            double* phys_x) noexcept nogil\n\n\ncdef class NonlinearSolveSampler3D(ElementSampler):\n\n    cdef int dim\n    cdef int max_iter\n    cdef np.float64_t tolerance\n    cdef func_type func\n    cdef jac_type3D jac\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\ncdef class Q1Sampler3D(NonlinearSolveSampler3D):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil\n\n\ncdef class W1Sampler3D(NonlinearSolveSampler3D):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil\n\n\n\ncdef class S2Sampler3D(NonlinearSolveSampler3D):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil\n\n\n\ncdef class NonlinearSolveSampler2D(ElementSampler):\n\n    cdef int dim\n    cdef int max_iter\n    cdef np.float64_t tolerance\n    cdef func_type func\n    cdef jac_type2D jac\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\ncdef class Q1Sampler2D(NonlinearSolveSampler2D):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\n\ncdef class Q2Sampler2D(NonlinearSolveSampler2D):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\n\ncdef class T2Sampler2D(NonlinearSolveSampler2D):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n\ncdef class Tet2Sampler3D(NonlinearSolveSampler3D):\n\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil\n\n\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil\n\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil\n"
  },
  {
    "path": "yt/utilities/lib/element_mappings.pyx",
    "content": "# distutils: libraries = STD_LIBS\n\"\"\"\nThis file contains coordinate mappings between physical coordinates and those\ndefined on unit elements, as well as doing the corresponding intracell\ninterpolation on finite element data.\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\nfrom numpy cimport ndarray\n\nimport numpy as np\n\nfrom libc.math cimport fabs\n\nfrom yt.utilities.lib.autogenerated_element_samplers cimport (\n    Q1Function2D,\n    Q1Function3D,\n    Q1Jacobian2D,\n    Q1Jacobian3D,\n    Q2Function2D,\n    Q2Jacobian2D,\n    T2Function2D,\n    T2Jacobian2D,\n    Tet2Function3D,\n    Tet2Jacobian3D,\n    W1Function3D,\n    W1Jacobian3D,\n)\n\n\ncdef extern from \"platform_dep.h\":\n    double fmax(double x, double y) noexcept nogil\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef double determinant_3x3(double* col0,\n                            double* col1,\n                            double* col2) noexcept nogil:\n    return col0[0]*col1[1]*col2[2] - col0[0]*col1[2]*col2[1] - \\\n           col0[1]*col1[0]*col2[2] + col0[1]*col1[2]*col2[0] + \\\n           col0[2]*col1[0]*col2[1] - col0[2]*col1[1]*col2[0]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef double maxnorm(double* f, int dim) noexcept nogil:\n    cdef double err\n    cdef int i\n    err = fabs(f[0])\n    for i in range(1, dim):\n        err = fmax(err, fabs(f[i]))\n    return err\n\n\ncdef class ElementSampler:\n    '''\n\n    This is a base class for sampling the value of a finite element solution\n    at an arbitrary point inside a mesh element. In general, this will be done\n    by transforming the requested physical coordinate into a mapped coordinate\n    system, sampling the solution in mapped coordinates, and returning the result.\n    This is not to be used directly; use one of the subclasses instead.\n\n    '''\n\n    def __init__(self):\n        self.inclusion_tol = 1.0e-8\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil:\n        pass\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil:\n        pass\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        pass\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def check_contains(self, np.float64_t[:,::1] vertices,\n                             np.float64_t[:,::1] positions):\n        cdef np.ndarray[np.float64_t, ndim=2] mapped_coords\n        cdef np.ndarray[np.uint8_t, ndim=1] mask\n        mapped_coords = self.map_reals_to_unit(vertices, positions)\n        mask = np.zeros(mapped_coords.shape[0], dtype=np.uint8)\n        cdef double[3] mapped_coord\n        cdef int i, j\n        for i in range(mapped_coords.shape[0]):\n            for j in range(mapped_coords.shape[1]):\n                mapped_coord[j] = mapped_coords[i, j]\n            mask[i] = self.check_inside(mapped_coord)\n        return mask\n\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil:\n        pass\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_real_point(self,\n                                     double* vertices,\n                                     double* field_values,\n                                     double* physical_x) noexcept nogil:\n        cdef double val\n        cdef double mapped_coord[4]\n\n        self.map_real_to_unit(mapped_coord, vertices, physical_x)\n        val = self.sample_at_unit_point(mapped_coord, field_values)\n        return val\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def map_reals_to_unit(self,\n                          np.float64_t[:,::1] vertices,\n                          np.float64_t[:,::1] positions):\n        cdef double mapped_x[3]\n        cdef int i, n\n        # We have N vertices, which each have three components.\n        cdef np.ndarray[np.float64_t, ndim=2] output_coords\n        output_coords = np.zeros((positions.shape[0], positions.shape[1]), dtype=\"float64\")\n        # Now for each position, we map\n        for n in range(positions.shape[0]):\n            self.map_real_to_unit(mapped_x, &vertices[0,0], &positions[n, 0])\n            for i in range(positions.shape[1]):\n                output_coords[n, i] = mapped_x[i]\n        return output_coords\n\n    def sample_at_real_points(self,\n                              np.float64_t[:,::1] vertices,\n                              np.float64_t[::1] field_values,\n                              np.float64_t[:,::1] positions):\n        cdef np.ndarray[np.float64_t, ndim=1] output_values\n        output_values = np.zeros(positions.shape[0], dtype=\"float64\")\n        for n in range(positions.shape[0]):\n            output_values[n] = self.sample_at_real_point(\n                &vertices[0,0], &field_values[0], &positions[n,0])\n        return output_values\n\ncdef class P1Sampler1D(ElementSampler):\n    '''\n\n    This implements sampling inside a linear, 1D element.\n\n    '''\n\n    def __init__(self):\n        super(P1Sampler1D, self).__init__()\n        self.num_mapped_coords = 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void map_real_to_unit(self, double* mapped_x,\n                               double* vertices, double* physical_x) noexcept nogil:\n        mapped_x[0] = -1.0 + 2.0*(physical_x[0] - vertices[0]) / (vertices[1] - vertices[0])\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil:\n        return vals[0] * (1 - coord[0]) / 2.0 + vals[1] * (1.0 + coord[0]) / 2.0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        if (fabs(mapped_coord[0]) - 1.0 > self.inclusion_tol):\n            return 0\n        return 1\n\n\ncdef class P1Sampler2D(ElementSampler):\n    '''\n\n    This implements sampling inside a linear, triangular mesh element.\n    This mapping is linear and can be inverted easily. Note that this\n    implementation uses triangular (or barycentric) coordinates.\n\n    '''\n\n    def __init__(self):\n        super(P1Sampler2D, self).__init__()\n        self.num_mapped_coords = 3\n\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void map_real_to_unit(self, double* mapped_x,\n                               double* vertices, double* physical_x) noexcept nogil:\n\n        cdef double[3] col0\n        cdef double[3] col1\n        cdef double[3] col2\n\n        col0[0] = vertices[0]\n        col0[1] = vertices[1]\n        col0[2] = 1.0\n\n        col1[0] = vertices[2]\n        col1[1] = vertices[3]\n        col1[2] = 1.0\n\n        col2[0] = vertices[4]\n        col2[1] = vertices[5]\n        col2[2] = 1.0\n\n        det = determinant_3x3(col0, col1, col2)\n\n        mapped_x[0] = ((vertices[3] - vertices[5])*physical_x[0] + \\\n                       (vertices[4] - vertices[2])*physical_x[1] + \\\n                       (vertices[2]*vertices[5] - vertices[4]*vertices[3])) / det\n\n        mapped_x[1] = ((vertices[5] - vertices[1])*physical_x[0] + \\\n                       (vertices[0] - vertices[4])*physical_x[1] + \\\n                       (vertices[4]*vertices[1] - vertices[0]*vertices[5])) / det\n\n        mapped_x[2] = 1.0 - mapped_x[1] - mapped_x[0]\n\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil:\n        return vals[0]*coord[0] + vals[1]*coord[1] + vals[2]*coord[2]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        # for triangles, we check whether all mapped_coords are\n        # between 0 and 1, to within the inclusion tolerance\n        cdef int i\n        for i in range(3):\n            if (mapped_coord[i] < -self.inclusion_tol or\n                mapped_coord[i] - 1.0 > self.inclusion_tol):\n                return 0\n        return 1\n\n\ncdef class P1Sampler3D(ElementSampler):\n    '''\n\n    This implements sampling inside a linear, tetrahedral mesh element.\n    This mapping is linear and can be inverted easily.\n\n    '''\n\n\n    def __init__(self):\n        super(P1Sampler3D, self).__init__()\n        self.num_mapped_coords = 4\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void map_real_to_unit(self, double* mapped_x,\n                               double* vertices, double* physical_x) noexcept nogil:\n\n        cdef int i\n        cdef double d\n        cdef double[3] bvec\n        cdef double[3] col0\n        cdef double[3] col1\n        cdef double[3] col2\n\n        # here, we express positions relative to the 4th element,\n        # which is selected by vertices[9]\n        for i in range(3):\n            bvec[i] = physical_x[i]       - vertices[9 + i]\n            col0[i] = vertices[0 + i]     - vertices[9 + i]\n            col1[i] = vertices[3 + i]     - vertices[9 + i]\n            col2[i] = vertices[6 + i]     - vertices[9 + i]\n\n        d = determinant_3x3(col0, col1, col2)\n        mapped_x[0] = determinant_3x3(bvec, col1, col2)/d\n        mapped_x[1] = determinant_3x3(col0, bvec, col2)/d\n        mapped_x[2] = determinant_3x3(col0, col1, bvec)/d\n        mapped_x[3] = 1.0 - mapped_x[0] - mapped_x[1] - mapped_x[2]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self,\n                                     double* coord,\n                                     double* vals) noexcept nogil:\n        return vals[0]*coord[0] + vals[1]*coord[1] + \\\n            vals[2]*coord[2] + vals[3]*coord[3]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        # for tetrahedra, we check whether all mapped coordinates\n        # are within 0 and 1, to within the inclusion tolerance\n        cdef int i\n        for i in range(4):\n            if (mapped_coord[i] < -self.inclusion_tol or\n                mapped_coord[i] - 1.0 > self.inclusion_tol):\n                return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil:\n        cdef double u, v, w\n        cdef double thresh = 2.0e-2\n        if mapped_coord[0] == 0:\n            u = mapped_coord[1]\n            v = mapped_coord[2]\n            w = mapped_coord[3]\n        elif mapped_coord[1] == 0:\n            u = mapped_coord[2]\n            v = mapped_coord[3]\n            w = mapped_coord[0]\n        elif mapped_coord[2] == 0:\n            u = mapped_coord[1]\n            v = mapped_coord[3]\n            w = mapped_coord[0]\n        else:\n            u = mapped_coord[1]\n            v = mapped_coord[2]\n            w = mapped_coord[0]\n        if ((u < thresh) or\n            (v < thresh) or\n            (w < thresh) or\n            (fabs(u - 1) < thresh) or\n            (fabs(v - 1) < thresh) or\n            (fabs(w - 1) < thresh)):\n            return 1\n        return -1\n\n\ncdef class NonlinearSolveSampler3D(ElementSampler):\n\n    '''\n\n    This is a base class for handling element samplers that require\n    a nonlinear solve to invert the mapping between coordinate systems.\n    To do this, we perform Newton-Raphson iteration using a specified\n    system of equations with an analytic Jacobian matrix. This solver\n    is hard-coded for 3D for reasons of efficiency. This is not to be\n    used directly, use one of the subclasses instead.\n\n    '''\n\n    def __init__(self):\n        super(NonlinearSolveSampler3D, self).__init__()\n        self.tolerance = 1.0e-9\n        self.max_iter = 10\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil:\n        '''\n\n        A thorough description of Newton's method and modifications for global\n        convergence can be found in Dennis's text \"Numerical Methods for\n        Unconstrained Optimization and Nonlinear Equations.\"\n\n        x: solution vector; holds unit/mapped coordinates\n        xk: temporary vector for holding solution of current iteration\n        f: residual vector\n        r, s, t: three columns of Jacobian matrix corresponding to unit/mapped\n                 coordinates r, s, and t\n        d: Jacobian determinant\n        s_n: Newton step vector\n        lam: fraction of Newton step by which to change x\n        alpha: constant proportional to how much residual required to decrease.\n               1e-4 is value of alpha recommended by Dennis\n        err_c: Error of current iteration\n        err_plus: Error of next iteration\n        min_lam: minimum fraction of Newton step that the line search is allowed\n                 to take. General experience suggests that lambda values smaller\n                 than 1e-3 will not significantly reduce the residual, but we\n                 set to 1e-6 just to be safe\n        '''\n\n        cdef int i\n        cdef double d, lam\n        cdef double[3] f\n        cdef double[3] r\n        cdef double[3] s\n        cdef double[3] t\n        cdef double[3] x, xk, s_n\n        cdef int iterations = 0\n        cdef double err_c, err_plus\n        cdef double alpha = 1e-4\n        cdef double min_lam = 1e-6\n\n        # initial guess\n        for i in range(3):\n            x[i] = 0.0\n\n        # initial error norm\n        self.func(f, x, vertices, physical_x)\n        err_c = maxnorm(f, 3)\n\n        # begin Newton iteration\n        while (err_c > self.tolerance and iterations < self.max_iter):\n            self.jac(r, s, t, x, vertices, physical_x)\n            d = determinant_3x3(r, s, t)\n\n            s_n[0] = - (determinant_3x3(f, s, t)/d)\n            s_n[1] = - (determinant_3x3(r, f, t)/d)\n            s_n[2] = - (determinant_3x3(r, s, f)/d)\n            xk[0] = x[0] + s_n[0]\n            xk[1] = x[1] + s_n[1]\n            xk[2] = x[2] + s_n[2]\n            self.func(f, xk, vertices, physical_x)\n            err_plus = maxnorm(f, 3)\n\n            lam = 1\n            while err_plus > err_c * (1. - alpha * lam) and lam > min_lam:\n                lam = lam / 2\n                xk[0] = x[0] + lam * s_n[0]\n                xk[1] = x[1] + lam * s_n[1]\n                xk[2] = x[2] + lam * s_n[2]\n                self.func(f, xk, vertices, physical_x)\n                err_plus = maxnorm(f, 3)\n\n            x[0] = xk[0]\n            x[1] = xk[1]\n            x[2] = xk[2]\n            err_c = err_plus\n            iterations += 1\n\n        if (err_c > self.tolerance):\n            # we did not converge, set bogus value\n            for i in range(3):\n                mapped_x[i] = -99.0\n        else:\n            for i in range(3):\n                mapped_x[i] = x[i]\n\n\ncdef class Q1Sampler3D(NonlinearSolveSampler3D):\n\n    '''\n\n    This implements sampling inside a 3D, linear, hexahedral mesh element.\n\n    '''\n\n    def __init__(self):\n        super(Q1Sampler3D, self).__init__()\n        self.num_mapped_coords = 3\n        self.dim = 3\n        self.func = Q1Function3D\n        self.jac = Q1Jacobian3D\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self, double* coord, double* vals) noexcept nogil:\n        cdef double F, rm, rp, sm, sp, tm, tp\n\n        rm = 1.0 - coord[0]\n        rp = 1.0 + coord[0]\n        sm = 1.0 - coord[1]\n        sp = 1.0 + coord[1]\n        tm = 1.0 - coord[2]\n        tp = 1.0 + coord[2]\n\n        F = vals[0]*rm*sm*tm + vals[1]*rp*sm*tm + vals[2]*rp*sp*tm + vals[3]*rm*sp*tm + \\\n            vals[4]*rm*sm*tp + vals[5]*rp*sm*tp + vals[6]*rp*sp*tp + vals[7]*rm*sp*tp\n        return 0.125*F\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        # for hexes, the mapped coordinates all go from -1 to 1\n        # if we are inside the element.\n        if (fabs(mapped_coord[0]) - 1.0 > self.inclusion_tol or\n            fabs(mapped_coord[1]) - 1.0 > self.inclusion_tol or\n            fabs(mapped_coord[2]) - 1.0 > self.inclusion_tol):\n            return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil:\n        if (fabs(fabs(mapped_coord[0]) - 1.0) < 1e-1 and\n            fabs(fabs(mapped_coord[1]) - 1.0) < 1e-1):\n            return 1\n        elif (fabs(fabs(mapped_coord[0]) - 1.0) < 1e-1 and\n              fabs(fabs(mapped_coord[2]) - 1.0) < 1e-1):\n            return 1\n        elif (fabs(fabs(mapped_coord[1]) - 1.0) < 1e-1 and\n              fabs(fabs(mapped_coord[2]) - 1.0) < 1e-1):\n            return 1\n        else:\n            return -1\n\n\ncdef class S2Sampler3D(NonlinearSolveSampler3D):\n\n    '''\n\n    This implements sampling inside a 3D, 20-node hexahedral mesh element.\n\n    '''\n\n    def __init__(self):\n        super(S2Sampler3D, self).__init__()\n        self.num_mapped_coords = 3\n        self.dim = 3\n        self.func = S2Function3D\n        self.jac = S2Jacobian3D\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self, double* coord, double* vals) noexcept nogil:\n        cdef double F, r, s, t, rm, rp, sm, sp, tm, tp\n\n        r = coord[0]\n        rm = 1.0 - r\n        rp = 1.0 + r\n\n        s = coord[1]\n        sm = 1.0 - s\n        sp = 1.0 + s\n\n        t = coord[2]\n        tm = 1.0 - t\n        tp = 1.0 + t\n\n        F = rm*sm*tm*(-r - s - t - 2.0)*vals[0] \\\n          + rp*sm*tm*( r - s - t - 2.0)*vals[1] \\\n          + rp*sp*tm*( r + s - t - 2.0)*vals[2] \\\n          + rm*sp*tm*(-r + s - t - 2.0)*vals[3] \\\n          + rm*sm*tp*(-r - s + t - 2.0)*vals[4] \\\n          + rp*sm*tp*( r - s + t - 2.0)*vals[5] \\\n          + rp*sp*tp*( r + s + t - 2.0)*vals[6] \\\n          + rm*sp*tp*(-r + s + t - 2.0)*vals[7] \\\n          + 2.0*(1.0 - r*r)*sm*tm*vals[8]  \\\n          + 2.0*rp*(1.0 - s*s)*tm*vals[9]  \\\n          + 2.0*(1.0 - r*r)*sp*tm*vals[10] \\\n          + 2.0*rm*(1.0 - s*s)*tm*vals[11] \\\n          + 2.0*rm*sm*(1.0 - t*t)*vals[12] \\\n          + 2.0*rp*sm*(1.0 - t*t)*vals[13] \\\n          + 2.0*rp*sp*(1.0 - t*t)*vals[14] \\\n          + 2.0*rm*sp*(1.0 - t*t)*vals[15] \\\n          + 2.0*(1.0 - r*r)*sm*tp*vals[16] \\\n          + 2.0*rp*(1.0 - s*s)*tp*vals[17] \\\n          + 2.0*(1.0 - r*r)*sp*tp*vals[18] \\\n          + 2.0*rm*(1.0 - s*s)*tp*vals[19]\n        return 0.125*F\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        if (fabs(mapped_coord[0]) - 1.0 > self.inclusion_tol or\n            fabs(mapped_coord[1]) - 1.0 > self.inclusion_tol or\n            fabs(mapped_coord[2]) - 1.0 > self.inclusion_tol):\n            return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil:\n        if (fabs(fabs(mapped_coord[0]) - 1.0) < 1e-1 and\n            fabs(fabs(mapped_coord[1]) - 1.0) < 1e-1):\n            return 1\n        elif (fabs(fabs(mapped_coord[0]) - 1.0) < 1e-1 and\n              fabs(fabs(mapped_coord[2]) - 1.0) < 1e-1):\n            return 1\n        elif (fabs(fabs(mapped_coord[1]) - 1.0) < 1e-1 and\n              fabs(fabs(mapped_coord[2]) - 1.0) < 1e-1):\n            return 1\n        else:\n            return -1\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline void S2Function3D(double* fx,\n                              double* x,\n                              double* vertices,\n                              double* phys_x) noexcept nogil:\n        cdef int i\n        cdef double r, s, t, rm, rp, sm, sp, tm, tp\n\n        r = x[0]\n        rm = 1.0 - r\n        rp = 1.0 + r\n\n        s = x[1]\n        sm = 1.0 - s\n        sp = 1.0 + s\n\n        t = x[2]\n        tm = 1.0 - t\n        tp = 1.0 + t\n\n        for i in range(3):\n            fx[i] = rm*sm*tm*(-r - s - t - 2.0)*vertices[0 + i]  \\\n                  + rp*sm*tm*( r - s - t - 2.0)*vertices[3 + i]  \\\n                  + rp*sp*tm*( r + s - t - 2.0)*vertices[6 + i]  \\\n                  + rm*sp*tm*(-r + s - t - 2.0)*vertices[9 + i]  \\\n                  + rm*sm*tp*(-r - s + t - 2.0)*vertices[12 + i] \\\n                  + rp*sm*tp*( r - s + t - 2.0)*vertices[15 + i] \\\n                  + rp*sp*tp*( r + s + t - 2.0)*vertices[18 + i] \\\n                  + rm*sp*tp*(-r + s + t - 2.0)*vertices[21 + i] \\\n                  + 2.0*(1.0 - r*r)*sm*tm*vertices[24 + i] \\\n                  + 2.0*rp*(1.0 - s*s)*tm*vertices[27 + i] \\\n                  + 2.0*(1.0 - r*r)*sp*tm*vertices[30 + i] \\\n                  + 2.0*rm*(1.0 - s*s)*tm*vertices[33 + i] \\\n                  + 2.0*rm*sm*(1.0 - t*t)*vertices[36 + i] \\\n                  + 2.0*rp*sm*(1.0 - t*t)*vertices[39 + i] \\\n                  + 2.0*rp*sp*(1.0 - t*t)*vertices[42 + i] \\\n                  + 2.0*rm*sp*(1.0 - t*t)*vertices[45 + i] \\\n                  + 2.0*(1.0 - r*r)*sm*tp*vertices[48 + i] \\\n                  + 2.0*rp*(1.0 - s*s)*tp*vertices[51 + i] \\\n                  + 2.0*(1.0 - r*r)*sp*tp*vertices[54 + i] \\\n                  + 2.0*rm*(1.0 - s*s)*tp*vertices[57 + i] \\\n                  - 8.0*phys_x[i]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline void S2Jacobian3D(double* rcol,\n                              double* scol,\n                              double* tcol,\n                              double* x,\n                              double* vertices,\n                              double* phys_x) noexcept nogil:\n        cdef int i\n        cdef double r, s, t, rm, rp, sm, sp, tm, tp\n\n        r = x[0]\n        rm = 1.0 - r\n        rp = 1.0 + r\n\n        s = x[1]\n        sm = 1.0 - s\n        sp = 1.0 + s\n\n        t = x[2]\n        tm = 1.0 - t\n        tp = 1.0 + t\n\n        for i in range(3):\n            rcol[i] = (sm*tm*(r + s + t + 2.0) - rm*sm*tm)*vertices[0  + i] \\\n                    + (sm*tm*(r - s - t - 2.0) + rp*sm*tm)*vertices[3  + i] \\\n                    + (sp*tm*(r + s - t - 2.0) + rp*sp*tm)*vertices[6  + i] \\\n                    + (sp*tm*(r - s + t + 2.0) - rm*sp*tm)*vertices[9  + i] \\\n                    + (sm*tp*(r + s - t + 2.0) - rm*sm*tp)*vertices[12 + i] \\\n                    + (sm*tp*(r - s + t - 2.0) + rp*sm*tp)*vertices[15 + i] \\\n                    + (sp*tp*(r + s + t - 2.0) + rp*sp*tp)*vertices[18 + i] \\\n                    + (sp*tp*(r - s - t + 2.0) - rm*sp*tp)*vertices[21 + i] \\\n                    - 4.0*r*sm*tm*vertices[24 + i] \\\n                    + 2.0*(1.0 - s*s)*tm*vertices[27 + i] \\\n                    - 4.0*r*sp*tm*vertices[30 + i] \\\n                    - 2.0*(1.0 - s*s)*tm*vertices[33 + i] \\\n                    - 2.0*sm*(1.0 - t*t)*vertices[36 + i] \\\n                    + 2.0*sm*(1.0 - t*t)*vertices[39 + i] \\\n                    + 2.0*sp*(1.0 - t*t)*vertices[42 + i] \\\n                    - 2.0*sp*(1.0 - t*t)*vertices[45 + i] \\\n                    - 4.0*r*sm*tp*vertices[48 + i] \\\n                    + 2.0*(1.0 - s*s)*tp*vertices[51 + i] \\\n                    - 4.0*r*sp*tp*vertices[54 + i] \\\n                    - 2.0*(1.0 - s*s)*tp*vertices[57 + i]\n            scol[i] = ( rm*tm*(r + s + t + 2.0) - rm*sm*tm)*vertices[0  + i] \\\n                    + (-rp*tm*(r - s - t - 2.0) - rp*sm*tm)*vertices[3  + i] \\\n                    + ( rp*tm*(r + s - t - 2.0) + rp*sp*tm)*vertices[6  + i] \\\n                    + (-rm*tm*(r - s + t + 2.0) + rm*sp*tm)*vertices[9  + i] \\\n                    + ( rm*tp*(r + s - t + 2.0) - rm*sm*tp)*vertices[12 + i] \\\n                    + (-rp*tp*(r - s + t - 2.0) - rp*sm*tp)*vertices[15 + i] \\\n                    + ( rp*tp*(r + s + t - 2.0) + rp*sp*tp)*vertices[18 + i] \\\n                    + (-rm*tp*(r - s - t + 2.0) + rm*sp*tp)*vertices[21 + i] \\\n                    - 2.0*(1.0 - r*r)*tm*vertices[24 + i] \\\n                    - 4.0*rp*s*tm*vertices[27 + i] \\\n                    + 2.0*(1.0 - r*r)*tm*vertices[30 + i] \\\n                    - 4.0*rm*s*tm*vertices[33 + i] \\\n                    - 2.0*rm*(1.0 - t*t)*vertices[36 + i] \\\n                    - 2.0*rp*(1.0 - t*t)*vertices[39 + i] \\\n                    + 2.0*rp*(1.0 - t*t)*vertices[42 + i] \\\n                    + 2.0*rm*(1.0 - t*t)*vertices[45 + i] \\\n                    - 2.0*(1.0 - r*r)*tp*vertices[48 + i] \\\n                    - 4.0*rp*s*tp*vertices[51 + i] \\\n                    + 2.0*(1.0 - r*r)*tp*vertices[54 + i] \\\n                    - 4.0*rm*s*tp*vertices[57 + i]\n            tcol[i] = ( rm*sm*(r + s + t + 2.0) - rm*sm*tm)*vertices[0  + i] \\\n                    + (-rp*sm*(r - s - t - 2.0) - rp*sm*tm)*vertices[3  + i] \\\n                    + (-rp*sp*(r + s - t - 2.0) - rp*sp*tm)*vertices[6  + i] \\\n                    + ( rm*sp*(r - s + t + 2.0) - rm*sp*tm)*vertices[9  + i] \\\n                    + (-rm*sm*(r + s - t + 2.0) + rm*sm*tp)*vertices[12 + i] \\\n                    + ( rp*sm*(r - s + t - 2.0) + rp*sm*tp)*vertices[15 + i] \\\n                    + ( rp*sp*(r + s + t - 2.0) + rp*sp*tp)*vertices[18 + i] \\\n                    + (-rm*sp*(r - s - t + 2.0) + rm*sp*tp)*vertices[21 + i] \\\n                    - 2.0*(1.0 - r*r)*sm*vertices[24 + i] \\\n                    - 2.0*rp*(1.0 - s*s)*vertices[27 + i] \\\n                    - 2.0*(1.0 - r*r)*sp*vertices[30 + i] \\\n                    - 2.0*rm*(1.0 - s*s)*vertices[33 + i] \\\n                    - 4.0*rm*sm*t*vertices[36 + i] \\\n                    - 4.0*rp*sm*t*vertices[39 + i] \\\n                    - 4.0*rp*sp*t*vertices[42 + i] \\\n                    - 4.0*rm*sp*t*vertices[45 + i] \\\n                    + 2.0*(1.0 - r*r)*sm*vertices[48 + i] \\\n                    + 2.0*rp*(1.0 - s*s)*vertices[51 + i] \\\n                    + 2.0*(1.0 - r*r)*sp*vertices[54 + i] \\\n                    + 2.0*rm*(1.0 - s*s)*vertices[57 + i]\n\n\ncdef class W1Sampler3D(NonlinearSolveSampler3D):\n\n    '''\n\n    This implements sampling inside a 3D, linear, wedge mesh element.\n\n    '''\n\n    def __init__(self):\n        super(W1Sampler3D, self).__init__()\n        self.num_mapped_coords = 3\n        self.dim = 3\n        self.func = W1Function3D\n        self.jac = W1Jacobian3D\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self, double* coord, double* vals) noexcept nogil:\n        cdef double F\n\n        F = vals[0]*(1.0 - coord[0] - coord[1])*(1.0 - coord[2]) + \\\n            vals[1]*coord[0]*(1.0 - coord[2]) + \\\n            vals[2]*coord[1]*(1.0 - coord[2]) + \\\n            vals[3]*(1.0 - coord[0] - coord[1])*(1.0 + coord[2]) + \\\n            vals[4]*coord[0]*(1.0 + coord[2]) + \\\n            vals[5]*coord[1]*(1.0 + coord[2])\n\n        return F / 2.0\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        # for wedges the bounds of the mapped coordinates are:\n        #     0 <= mapped_coord[0] <= 1 - mapped_coord[1]\n        #     0 <= mapped_coord[1]\n        #    -1 <= mapped_coord[2] <= 1\n        if (mapped_coord[0] < -self.inclusion_tol or\n            mapped_coord[0] + mapped_coord[1] - 1.0 > self.inclusion_tol):\n            return 0\n        if (mapped_coord[1] < -self.inclusion_tol):\n            return 0\n        if (fabs(mapped_coord[2]) - 1.0 > self.inclusion_tol):\n            return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil:\n        cdef double r, s\n        cdef double thresh = 5.0e-2\n        r = mapped_coord[0]\n        s = mapped_coord[1]\n\n        cdef int near_edge_r, near_edge_s, near_edge_t\n        near_edge_r = (r < thresh) or (fabs(r + s - 1.0) < thresh)\n        near_edge_s = (s < thresh)\n        near_edge_t = fabs(fabs(mapped_coord[2]) - 1.0) < thresh\n\n        # we use ray.instID to pass back whether the ray is near the\n        # element boundary or not (used to annotate mesh lines)\n        if (near_edge_r and near_edge_s):\n            return 1\n        elif (near_edge_r and near_edge_t):\n            return 1\n        elif (near_edge_s and near_edge_t):\n            return 1\n        else:\n            return -1\n\n\ncdef class NonlinearSolveSampler2D(ElementSampler):\n\n    '''\n\n    This is a base class for handling element samplers that require\n    a nonlinear solve to invert the mapping between coordinate systems.\n    To do this, we perform Newton-Raphson iteration using a specified\n    system of equations with an analytic Jacobian matrix. This solver\n    is hard-coded for 2D for reasons of efficiency. This is not to be\n    used directly, use one of the subclasses instead.\n\n    '''\n\n    def __init__(self):\n        super(NonlinearSolveSampler2D, self).__init__()\n        self.tolerance = 1.0e-9\n        self.max_iter = 10\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void map_real_to_unit(self,\n                               double* mapped_x,\n                               double* vertices,\n                               double* physical_x) noexcept nogil:\n        '''\n\n        A thorough description of Newton's method and modifications for global\n        convergence can be found in Dennis's text \"Numerical Methods for\n        Unconstrained Optimization and Nonlinear Equations.\"\n\n        x: solution vector; holds unit/mapped coordinates\n        xk: temporary vector for holding solution of current iteration\n        f: residual vector\n        A: Jacobian matrix (derivative of residual vector wrt x)\n        d: Jacobian determinant\n        s_n: Newton step vector\n        lam: fraction of Newton step by which to change x\n        alpha: constant proportional to how much residual required to decrease.\n               1e-4 is value of alpha recommended by Dennis\n        err_c: Error of current iteration\n        err_plus: Error of next iteration\n        min_lam: minimum fraction of Newton step that the line search is allowed\n                 to take. General experience suggests that lambda values smaller\n                 than 1e-3 will not significantly reduce the residual, but we\n                 set to 1e-6 just to be safe\n        '''\n\n        cdef int i\n        cdef double d, lam\n        cdef double[2] f\n        cdef double[2] x, xk, s_n\n        cdef double[4] A\n        cdef int iterations = 0\n        cdef double err_c, err_plus\n        cdef double alpha = 1e-4\n        cdef double min_lam = 1e-6\n\n        # initial guess\n        for i in range(2):\n            x[i] = 0.0\n\n        # initial error norm\n        self.func(f, x, vertices, physical_x)\n        err_c = maxnorm(f, 2)\n\n        # begin Newton iteration\n        while (err_c > self.tolerance and iterations < self.max_iter):\n            self.jac(&A[0], &A[2], x, vertices, physical_x)\n            d = (A[0]*A[3] - A[1]*A[2])\n\n            s_n[0] = -( A[3]*f[0] - A[2]*f[1]) / d\n            s_n[1] = -(-A[1]*f[0] + A[0]*f[1]) / d\n            xk[0] = x[0] + s_n[0]\n            xk[1] = x[1] + s_n[1]\n            self.func(f, xk, vertices, physical_x)\n            err_plus = maxnorm(f, 2)\n\n            lam = 1\n            while err_plus > err_c * (1. - alpha * lam) and lam > min_lam:\n                lam = lam / 2\n                xk[0] = x[0] + lam * s_n[0]\n                xk[1] = x[1] + lam * s_n[1]\n                self.func(f, xk, vertices, physical_x)\n                err_plus = maxnorm(f, 2)\n\n            x[0] = xk[0]\n            x[1] = xk[1]\n            err_c = err_plus\n            iterations += 1\n\n        if (err_c > self.tolerance):\n            # we did not converge, set bogus value\n            for i in range(2):\n                mapped_x[i] = -99.0\n        else:\n            for i in range(2):\n                mapped_x[i] = x[i]\n\n\ncdef class Q1Sampler2D(NonlinearSolveSampler2D):\n\n    '''\n\n    This implements sampling inside a 2D, linear, quadrilateral mesh element.\n\n    '''\n\n    def __init__(self):\n        super(Q1Sampler2D, self).__init__()\n        self.num_mapped_coords = 2\n        self.dim = 2\n        self.func = Q1Function2D\n        self.jac = Q1Jacobian2D\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self, double* coord, double* vals) noexcept nogil:\n        cdef double F, rm, rp, sm, sp\n\n        rm = 1.0 - coord[0]\n        rp = 1.0 + coord[0]\n        sm = 1.0 - coord[1]\n        sp = 1.0 + coord[1]\n\n        F = vals[0]*rm*sm + vals[1]*rp*sm + vals[2]*rp*sp + vals[3]*rm*sp\n        return 0.25*F\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        # for quads, we check whether the mapped_coord is between\n        # -1 and 1 in both directions.\n        if (fabs(mapped_coord[0]) - 1.0 > self.inclusion_tol or\n            fabs(mapped_coord[1]) - 1.0 > self.inclusion_tol):\n            return 0\n        return 1\n\ncdef class Q2Sampler2D(NonlinearSolveSampler2D):\n\n    '''\n\n    This implements sampling inside a 2D, quadratic, quadrilateral mesh element.\n\n    '''\n\n    def __init__(self):\n        super(Q2Sampler2D, self).__init__()\n        self.num_mapped_coords = 2\n        self.dim = 2\n        self.func = Q2Function2D\n        self.jac = Q2Jacobian2D\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self, double* coord, double* vals) noexcept nogil:\n        cdef double[9] phi\n        cdef double rv = 0\n\n        zet = coord[0]\n        eta = coord[1]\n        zetm = coord[0] - 1.\n        zetp = coord[0] + 1.\n        etam = coord[1] - 1.\n        etap = coord[1] + 1.\n\n        phi[0] = zet * zetm * eta * etam / 4.\n        phi[1] = zet * zetp * eta * etam / 4.\n        phi[2] = zet * zetp * eta * etap / 4.\n        phi[3] = zet * zetm * eta * etap / 4.\n        phi[4] = zetp * zetm * eta * etam / -2.\n        phi[5] = zet * zetp * etap * etam / -2.\n        phi[6] = zetp * zetm * eta * etap / -2.\n        phi[7] = zet * zetm * etap * etam / -2.\n        phi[8] = zetp * zetm * etap * etam\n\n        for i in range(9):\n            rv += vals[i] * phi[i]\n\n        return rv\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        # for quads, we check whether the mapped_coord is between\n        # -1 and 1 in both directions.\n        if (fabs(mapped_coord[0]) - 1.0 > self.inclusion_tol or\n            fabs(mapped_coord[1]) - 1.0 > self.inclusion_tol):\n            return 0\n        return 1\n\n\ncdef class T2Sampler2D(NonlinearSolveSampler2D):\n\n    '''\n\n    This implements sampling inside a 2D, quadratic, triangular mesh\n    element. Note that this implementation uses canonical coordinates.\n\n    '''\n\n    def __init__(self):\n        super(T2Sampler2D, self).__init__()\n        self.num_mapped_coords = 2\n        self.dim = 2\n        self.func = T2Function2D\n        self.jac = T2Jacobian2D\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self, double* coord, double* vals) noexcept nogil:\n        cdef double phi0, phi1, phi2, phi3, phi4, phi5, c0sq, c1sq, c0c1\n\n        c0sq = coord[0] * coord[0]\n        c1sq = coord[1] * coord[1]\n        c0c1 = coord[0] * coord[1]\n\n        phi0 = 1 - 3 * coord[0] + 2 * c0sq - 3 * coord[1] + \\\n               2 * c1sq + 4 * c0c1\n        phi1 = -coord[0] + 2 * c0sq\n        phi2 = -coord[1] + 2 * c1sq\n        phi3 = 4 * coord[0] - 4 * c0sq - 4 * c0c1\n        phi4 = 4 * c0c1\n        phi5 = 4 * coord[1] - 4 * c1sq - 4 * c0c1\n\n        return vals[0]*phi0 + vals[1]*phi1 + vals[2]*phi2 + vals[3]*phi3 + \\\n               vals[4]*phi4 + vals[5]*phi5\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        # for canonical tris, we check whether the mapped_coords are between\n        # 0 and 1.\n        if (mapped_coord[0] < -self.inclusion_tol or \\\n            mapped_coord[1] < -self.inclusion_tol or \\\n            mapped_coord[0] + mapped_coord[1] - 1.0 > self.inclusion_tol):\n            return 0\n        return 1\n\ncdef class Tet2Sampler3D(NonlinearSolveSampler3D):\n\n    '''\n\n    This implements sampling inside a 3D, quadratic, tetrahedral mesh\n    element. Note that this implementation uses canonical coordinates.\n\n    '''\n\n    def __init__(self):\n        super(Tet2Sampler3D, self).__init__()\n        self.num_mapped_coords = 3\n        self.dim = 3\n        self.func = Tet2Function3D\n        self.jac = Tet2Jacobian3D\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef double sample_at_unit_point(self, double* coord, double* vals) noexcept nogil:\n        cdef double[10] phi\n        cdef double coordsq[3]\n        cdef int i\n        cdef double return_value = 0\n\n        for i in range(3):\n            coordsq[i] = coord[i] * coord[i]\n\n        phi[0] = 1 - 3 * coord[0] + 2 * coordsq[0] - 3 * coord[1] + \\\n                 2 * coordsq[1] - 3 * coord[2] + 2 * coordsq[2] + \\\n                 4 * coord[0] * coord[1] + 4 * coord[0] * coord[2] + \\\n                 4 * coord[1] * coord[2]\n        phi[1] = -coord[0] + 2 * coordsq[0]\n        phi[2] = -coord[1] + 2 * coordsq[1]\n        phi[3] = -coord[2] + 2 * coordsq[2]\n        phi[4] = 4 * coord[0] - 4 * coordsq[0] - 4 * coord[0] * coord[1] - \\\n                 4 * coord[0] * coord[2]\n        phi[5] = 4 * coord[0] * coord[1]\n        phi[6] = 4 * coord[1] - 4 * coordsq[1] - 4 * coord[0] * coord[1] - \\\n                 4 * coord[1] * coord[2]\n        phi[7] = 4 * coord[2] - 4 * coordsq[2] - 4 * coord[2] * coord[0] - \\\n                 4 * coord[2] * coord[1]\n        phi[8] = 4 * coord[0] * coord[2]\n        phi[9] = 4 * coord[1] * coord[2]\n\n        for i in range(10):\n            return_value += phi[i] * vals[i]\n\n        return return_value\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_inside(self, double* mapped_coord) noexcept nogil:\n        # for canonical tets, we check whether the mapped_coords are between\n        # 0 and 1.\n        if (mapped_coord[0] < -self.inclusion_tol or \\\n            mapped_coord[1] < -self.inclusion_tol or \\\n            mapped_coord[2] < -self.inclusion_tol or \\\n            mapped_coord[0] + mapped_coord[1] + mapped_coord[2] - 1.0 > \\\n            self.inclusion_tol):\n            return 0\n        return 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef int check_mesh_lines(self, double* mapped_coord) noexcept nogil:\n        cdef double u, v\n        cdef double thresh = 2.0e-2\n        if mapped_coord[0] == 0:\n            u = mapped_coord[1]\n            v = mapped_coord[2]\n        elif mapped_coord[1] == 0:\n            u = mapped_coord[2]\n            v = mapped_coord[0]\n        elif mapped_coord[2] == 0:\n            u = mapped_coord[1]\n            v = mapped_coord[0]\n        else:\n            u = mapped_coord[1]\n            v = mapped_coord[2]\n        if ((u < thresh) or\n            (v < thresh) or\n            (fabs(u - 1) < thresh) or\n            (fabs(v - 1) < thresh)):\n            return 1\n        return -1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_hex_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                     np.ndarray[np.float64_t, ndim=1] field_values,\n                     np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    cdef Q1Sampler3D sampler = Q1Sampler3D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n    return val\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_hex20_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                       np.ndarray[np.float64_t, ndim=1] field_values,\n                       np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    cdef S2Sampler3D sampler = S2Sampler3D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n    return val\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_tetra_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                       np.ndarray[np.float64_t, ndim=1] field_values,\n                       np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    sampler = P1Sampler3D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n\n    return val\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_wedge_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                       np.ndarray[np.float64_t, ndim=1] field_values,\n                       np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    cdef W1Sampler3D sampler = W1Sampler3D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n    return val\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_linear1D_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                          np.ndarray[np.float64_t, ndim=1] field_values,\n                          np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    sampler = P1Sampler1D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n\n    return val\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_tri_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                     np.ndarray[np.float64_t, ndim=1] field_values,\n                     np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    sampler = P1Sampler2D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n\n    return val\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_quad_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                      np.ndarray[np.float64_t, ndim=1] field_values,\n                      np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    sampler = Q1Sampler2D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n\n    return val\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_quad2_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                       np.ndarray[np.float64_t, ndim=1] field_values,\n                       np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    sampler = Q2Sampler2D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n\n    return val\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_tri2_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                      np.ndarray[np.float64_t, ndim=1] field_values,\n                      np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    sampler = T2Sampler2D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n\n    return val\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef test_tet2_sampler(np.ndarray[np.float64_t, ndim=2] vertices,\n                      np.ndarray[np.float64_t, ndim=1] field_values,\n                      np.ndarray[np.float64_t, ndim=1] physical_x):\n\n    cdef double val\n\n    sampler = Tet2Sampler3D()\n\n    val = sampler.sample_at_real_point(<double*> vertices.data,\n                                       <double*> field_values.data,\n                                       <double*> physical_x.data)\n\n    return val\n"
  },
  {
    "path": "yt/utilities/lib/embree_mesh/__init__.py",
    "content": ""
  },
  {
    "path": "yt/utilities/lib/embree_mesh/mesh_construction.pxd",
    "content": "cimport numpy as np\nfrom pyembree.rtcore cimport Triangle, Vec3f, Vertex\n\nctypedef struct MeshDataContainer:\n    Vertex* vertices       # array of triangle vertices\n    Triangle* indices      # which vertices belong to which triangles\n    double* field_data     # the field values at the vertices\n    int* element_indices   # which vertices belong to which *element*\n    int tpe                # the number of triangles per element\n    int vpe                # the number of vertices per element\n    int fpe                # the number of field values per element\n\nctypedef struct Patch:\n    float[8][3] v\n    unsigned int geomID\n    np.float64_t* vertices\n    np.float64_t* field_data\n\nctypedef struct Tet_Patch:\n    float[6][3] v\n    unsigned int geomID\n    np.float64_t* vertices\n    np.float64_t* field_data\n"
  },
  {
    "path": "yt/utilities/lib/embree_mesh/mesh_construction.pyx",
    "content": "# distutils: include_dirs = EMBREE_INC_DIR\n# distutils: library_dirs = EMBREE_LIB_DIR\n# distutils: libraries = EMBREE_LIBS\n# distutils: language = c++\n\"\"\"\nThis file contains the ElementMesh classes, which represent the target that the\nrays will be cast at when rendering finite element data. This class handles\nthe interface between the internal representation of the mesh and the pyembree\nrepresentation.\n\nNote - this file is only used for the Embree-accelerated ray-tracer.\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport numpy as np\ncimport pyembree.rtcore_geometry as rtcg\ncimport pyembree.rtcore_geometry_user as rtcgu\nfrom libc.stdlib cimport free, malloc\nfrom mesh_intersection cimport (\n    patchBoundsFunc,\n    patchIntersectFunc,\n    tet_patchBoundsFunc,\n    tet_patchIntersectFunc,\n)\nfrom mesh_samplers cimport sample_hex, sample_tetra, sample_wedge\nfrom mesh_traversal cimport YTEmbreeScene\nfrom pyembree.rtcore cimport Triangle, Vertex\n\nfrom yt.utilities.exceptions import YTElementTypeNotRecognized\n\n\ncdef extern from \"mesh_triangulation.h\":\n    enum:\n        MAX_NUM_TRI\n\n    int HEX_NV\n    int HEX_NT\n    int TETRA_NV\n    int TETRA_NT\n    int WEDGE_NV\n    int WEDGE_NT\n    int triangulate_hex[MAX_NUM_TRI][3]\n    int triangulate_tetra[MAX_NUM_TRI][3]\n    int triangulate_wedge[MAX_NUM_TRI][3]\n    int hex20_faces[6][8]\n    int tet10_faces[4][6]\n\n\ncdef class LinearElementMesh:\n    r'''\n\n    This creates a 1st-order mesh to be ray-traced with embree.\n    Currently, we handle non-triangular mesh types by converting them\n    to triangular meshes. This class performs this transformation.\n    Currently, this is implemented for hexahedral and tetrahedral\n    meshes.\n\n    Parameters\n    ----------\n\n    scene : EmbreeScene\n        This is the scene to which the constructed polygons will be\n        added.\n    vertices : a np.ndarray of floats.\n        This specifies the x, y, and z coordinates of the vertices in\n        the polygon mesh. This should either have the shape\n        (num_vertices, 3). For example, vertices[2][1] should give the\n        y-coordinate of the 3rd vertex in the mesh.\n    indices : a np.ndarray of ints\n        This should either have the shape (num_elements, 4) or\n        (num_elements, 8) for tetrahedral and hexahedral meshes,\n        respectively. For tetrahedral meshes, each element will\n        be represented by four triangles in the scene. For hex meshes,\n        each element will be represented by 12 triangles, 2 for each\n        face. For hex meshes, we assume that the node ordering is as\n        defined here:\n        http://homepages.cae.wisc.edu/~tautges/papers/cnmev3.pdf\n\n    '''\n\n    cdef Vertex* vertices\n    cdef Triangle* indices\n    cdef unsigned int mesh\n    cdef double* field_data\n    cdef rtcg.RTCFilterFunc filter_func\n    # triangles per element, vertices per element, and field points per\n    # element, respectively:\n    cdef int tpe, vpe, fpe\n    cdef int[MAX_NUM_TRI][3] tri_array\n    cdef int* element_indices\n    cdef MeshDataContainer datac\n\n    def __init__(self, YTEmbreeScene scene,\n                 np.ndarray vertices,\n                 np.ndarray indices,\n                 np.ndarray data):\n\n        # We need to figure out what kind of elements we've been handed.\n        if indices.shape[1] == 8:\n            self.vpe = HEX_NV\n            self.tpe = HEX_NT\n            self.tri_array = triangulate_hex\n        elif indices.shape[1] == 6:\n            self.vpe = WEDGE_NV\n            self.tpe = WEDGE_NT\n            self.tri_array = triangulate_wedge\n        elif indices.shape[1] == 4:\n            self.vpe = TETRA_NV\n            self.tpe = TETRA_NT\n            self.tri_array = triangulate_tetra\n        else:\n            raise YTElementTypeNotRecognized(vertices.shape[1],\n                                             indices.shape[1])\n\n        self._build_from_indices(scene, vertices, indices)\n        self._set_field_data(scene, data)\n        self._set_sampler_type(scene)\n\n    cdef void _build_from_indices(self, YTEmbreeScene scene,\n                                  np.ndarray vertices_in,\n                                  np.ndarray indices_in):\n        cdef int i, j\n        cdef int nv = vertices_in.shape[0]\n        cdef int ne = indices_in.shape[0]\n        cdef int nt = self.tpe*ne\n\n        cdef unsigned int mesh = rtcg.rtcNewTriangleMesh(scene.scene_i,\n                    rtcg.RTC_GEOMETRY_STATIC, nt, nv, 1)\n\n        # first just copy over the vertices\n        cdef Vertex* vertices = <Vertex*> malloc(nv * sizeof(Vertex))\n        for i in range(nv):\n            vertices[i].x = vertices_in[i, 0]\n            vertices[i].y = vertices_in[i, 1]\n            vertices[i].z = vertices_in[i, 2]\n        rtcg.rtcSetBuffer(scene.scene_i, mesh, rtcg.RTC_VERTEX_BUFFER,\n                          vertices, 0, sizeof(Vertex))\n\n        # now build up the triangles\n        cdef Triangle* triangles = <Triangle*> malloc(nt * sizeof(Triangle))\n        for i in range(ne):\n            for j in range(self.tpe):\n                triangles[self.tpe*i+j].v0 = indices_in[i][self.tri_array[j][0]]\n                triangles[self.tpe*i+j].v1 = indices_in[i][self.tri_array[j][1]]\n                triangles[self.tpe*i+j].v2 = indices_in[i][self.tri_array[j][2]]\n        rtcg.rtcSetBuffer(scene.scene_i, mesh, rtcg.RTC_INDEX_BUFFER,\n                          triangles, 0, sizeof(Triangle))\n\n        cdef int* element_indices = <int *> malloc(ne * self.vpe * sizeof(int))\n        for i in range(ne):\n            for j in range(self.vpe):\n                element_indices[i*self.vpe + j] = indices_in[i][j]\n\n        self.element_indices = element_indices\n        self.vertices = vertices\n        self.indices = triangles\n        self.mesh = mesh\n\n    cdef void _set_field_data(self, YTEmbreeScene scene,\n                              np.ndarray data_in):\n\n        cdef int ne = data_in.shape[0]\n        self.fpe = data_in.shape[1]\n\n        cdef double* field_data = <double *> malloc(ne * self.fpe * sizeof(double))\n\n        for i in range(ne):\n            for j in range(self.fpe):\n                field_data[i*self.fpe+j] = data_in[i][j]\n\n        self.field_data = field_data\n\n        cdef MeshDataContainer datac\n        datac.vertices = self.vertices\n        datac.indices = self.indices\n        datac.field_data = self.field_data\n        datac.element_indices = self.element_indices\n        datac.tpe = self.tpe\n        datac.vpe = self.vpe\n        datac.fpe = self.fpe\n        self.datac = datac\n\n        rtcg.rtcSetUserData(scene.scene_i, self.mesh, &self.datac)\n\n    cdef void _set_sampler_type(self, YTEmbreeScene scene):\n\n        if self.vpe == 4:\n            self.filter_func = <rtcg.RTCFilterFunc> sample_tetra\n        elif self.vpe == 6:\n            self.filter_func = <rtcg.RTCFilterFunc> sample_wedge\n        elif self.vpe == 8:\n            self.filter_func = <rtcg.RTCFilterFunc> sample_hex\n        else:\n            raise NotImplementedError(\"Sampler type not implemented.\")\n\n        rtcg.rtcSetIntersectionFilterFunction(scene.scene_i,\n                                              self.mesh,\n                                              self.filter_func)\n\n    def __dealloc__(self):\n        free(self.field_data)\n        free(self.element_indices)\n        free(self.vertices)\n        free(self.indices)\n\n\ncdef class QuadraticElementMesh:\n    r'''\n\n    This creates a mesh of quadratic patches corresponding to the faces\n    of 2nd-order Lagrange elements for direct rendering via embree.\n    Currently, this is implemented for 20-point hexahedral meshes only.\n\n    Parameters\n    ----------\n\n    scene : EmbreeScene\n        This is the scene to which the constructed patches will be\n        added.\n    vertices : a np.ndarray of floats.\n        This specifies the x, y, and z coordinates of the vertices in\n        the mesh. This should either have the shape\n        (num_vertices, 3). For example, vertices[2][1] should give the\n        y-coordinate of the 3rd vertex in the mesh.\n    indices : a np.ndarray of ints\n        This should have the shape (num_elements, 20). Each hex will be\n        represented in the scene by 6 bi-quadratic patches. We assume that\n        the node ordering is as defined here:\n        http://homepages.cae.wisc.edu/~tautges/papers/cnmev3.pdf\n\n    '''\n\n    cdef void* patches\n    cdef np.float64_t* vertices\n    cdef np.float64_t* field_data\n    cdef unsigned int mesh\n    # patches per element, vertices per element, vertices per face,\n    # and field points per element, respectively:\n    cdef int ppe, vpe, vpf, fpe\n\n    def __init__(self, YTEmbreeScene scene,\n                 np.ndarray vertices,\n                 np.ndarray indices,\n                 np.ndarray field_data):\n\n        # 20-point hexes\n        if indices.shape[1] == 20:\n            self.vpe = 20\n            self.ppe = 6\n            self.vpf = 8\n            self._build_from_indices_hex20(scene, vertices, indices, field_data)\n        # 10-point tets\n        elif indices.shape[1] == 10:\n            self.vpe = 10\n            self.ppe = 4\n            self.vpf = 6\n            self._build_from_indices_tet10(scene, vertices, indices, field_data)\n        else:\n            raise NotImplementedError\n\n    cdef void _build_from_indices_hex20(self, YTEmbreeScene scene,\n                                  np.ndarray vertices_in,\n                                  np.ndarray indices_in,\n                                  np.ndarray field_data):\n        cdef int i, j, k, ind, idim\n        cdef int ne = indices_in.shape[0]\n        cdef int npatch = self.ppe*ne\n\n        cdef unsigned int mesh = rtcgu.rtcNewUserGeometry(scene.scene_i, npatch)\n        cdef np.ndarray[np.float64_t, ndim=2] element_vertices\n        cdef Patch* patches = <Patch*> malloc(npatch * sizeof(Patch))\n        self.vertices = <np.float64_t*> malloc(self.vpe * ne * 3 * sizeof(np.float64_t))\n        self.field_data = <np.float64_t*> malloc(self.vpe * ne * sizeof(np.float64_t))\n\n        for i in range(ne):\n            element_vertices = vertices_in[indices_in[i]]\n            for j in range(self.vpe):\n                self.field_data[i*self.vpe + j] = field_data[i][j]\n                for k in range(3):\n                    self.vertices[i*self.vpe*3 + j*3 + k] = element_vertices[j][k]\n\n        cdef Patch* patch\n        for i in range(ne):  # for each element\n            element_vertices = vertices_in[indices_in[i]]\n            for j in range(self.ppe):  # for each face\n                patch = &(patches[i*self.ppe+j])\n                patch.geomID = mesh\n                for k in range(self.vpf):  # for each vertex\n                    ind = hex20_faces[j][k]\n                    for idim in range(3):  # for each spatial dimension (yikes)\n                        patch.v[k][idim] = element_vertices[ind][idim]\n                patch.vertices = self.vertices + i*self.vpe*3\n                patch.field_data = self.field_data + i*self.vpe\n\n        self.patches = patches\n        self.mesh = mesh\n\n        rtcg.rtcSetUserData(scene.scene_i, self.mesh, patches)\n        rtcgu.rtcSetBoundsFunction(scene.scene_i, self.mesh,\n                                   <rtcgu.RTCBoundsFunc> patchBoundsFunc)\n        rtcgu.rtcSetIntersectFunction(scene.scene_i, self.mesh,\n                                      <rtcgu.RTCIntersectFunc> patchIntersectFunc)\n\n    cdef void _build_from_indices_tet10(self, YTEmbreeScene scene,\n                                  np.ndarray vertices_in,\n                                  np.ndarray indices_in,\n                                  np.ndarray field_data):\n        cdef int i, j, k, ind, idim\n        cdef int ne = indices_in.shape[0]\n        cdef int npatch = self.ppe*ne\n\n        cdef unsigned int mesh = rtcgu.rtcNewUserGeometry(scene.scene_i, npatch)\n        cdef np.ndarray[np.float64_t, ndim=2] element_vertices\n        cdef Tet_Patch* patches = <Tet_Patch*> malloc(npatch * sizeof(Tet_Patch))\n        self.vertices = <np.float64_t*> malloc(self.vpe * ne * 3 * sizeof(np.float64_t))\n        self.field_data = <np.float64_t*> malloc(self.vpe * ne * sizeof(np.float64_t))\n\n        for i in range(ne):\n            element_vertices = vertices_in[indices_in[i]]\n            for j in range(self.vpe):\n                self.field_data[i*self.vpe + j] = field_data[i][j]\n                for k in range(3):\n                    self.vertices[i*self.vpe*3 + j*3 + k] = element_vertices[j][k]\n\n        cdef Tet_Patch* patch\n        for i in range(ne):  # for each element\n            element_vertices = vertices_in[indices_in[i]]\n            for j in range(self.ppe):  # for each face\n                patch = &(patches[i*self.ppe+j])\n                patch.geomID = mesh\n                for k in range(self.vpf):  # for each vertex\n                    ind = tet10_faces[j][k]\n                    for idim in range(3):  # for each spatial dimension (yikes)\n                        patch.v[k][idim] = element_vertices[ind][idim]\n                patch.vertices = self.vertices + i*self.vpe*3\n                patch.field_data = self.field_data + i*self.vpe\n\n        self.patches = patches\n        self.mesh = mesh\n\n        rtcg.rtcSetUserData(scene.scene_i, self.mesh, patches)\n        rtcgu.rtcSetBoundsFunction(scene.scene_i, self.mesh,\n                                   <rtcgu.RTCBoundsFunc> tet_patchBoundsFunc)\n        rtcgu.rtcSetIntersectFunction(scene.scene_i, self.mesh,\n                                      <rtcgu.RTCIntersectFunc> tet_patchIntersectFunc)\n\n\n    def __dealloc__(self):\n        free(self.patches)\n        free(self.vertices)\n        free(self.field_data)\n"
  },
  {
    "path": "yt/utilities/lib/embree_mesh/mesh_intersection.pxd",
    "content": "cimport cython\ncimport pyembree.rtcore as rtc\ncimport pyembree.rtcore_geometry as rtcg\ncimport pyembree.rtcore_ray as rtcr\n\nfrom .mesh_construction cimport Patch, Tet_Patch\n\n\ncdef void patchIntersectFunc(Patch* patches,\n                             rtcr.RTCRay& ray,\n                             size_t item) noexcept nogil\n\ncdef void patchBoundsFunc(Patch* patches,\n                          size_t item,\n                          rtcg.RTCBounds* bounds_o) noexcept nogil\n\ncdef void tet_patchIntersectFunc(Tet_Patch* tet_patches,\n                             rtcr.RTCRay& ray,\n                             size_t item) noexcept nogil\n\ncdef void tet_patchBoundsFunc(Tet_Patch* tet_patches,\n                          size_t item,\n                          rtcg.RTCBounds* bounds_o) noexcept nogil\n"
  },
  {
    "path": "yt/utilities/lib/embree_mesh/mesh_intersection.pyx",
    "content": "# distutils: include_dirs = EMBREE_INC_DIR\n# distutils: library_dirs = EMBREE_LIB_DIR\n# distutils: libraries = EMBREE_LIBS\n# distutils: language = c++\n\"\"\"\nThis file contains functions used for performing ray-tracing with Embree\nfor 2nd-order Lagrange Elements.\n\nNote - this file is only used for the Embree-accelerated ray-tracer.\n\n\"\"\"\n\n\ncimport cython\ncimport pyembree.rtcore_geometry as rtcg\ncimport pyembree.rtcore_ray as rtcr\nfrom libc.math cimport fabs, fmax, fmin\n\nfrom yt.utilities.lib.primitives cimport (\n    RayHitData,\n    compute_patch_hit,\n    compute_tet_patch_hit,\n)\n\nfrom .mesh_samplers cimport sample_hex20, sample_tet10\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void patchBoundsFunc(Patch* patches,\n                          size_t item,\n                          rtcg.RTCBounds* bounds_o) noexcept nogil:\n\n    cdef Patch patch = patches[item]\n\n    cdef float lo_x = 1.0e300\n    cdef float lo_y = 1.0e300\n    cdef float lo_z = 1.0e300\n\n    cdef float hi_x = -1.0e300\n    cdef float hi_y = -1.0e300\n    cdef float hi_z = -1.0e300\n\n    cdef int i\n    for i in range(8):\n        lo_x = fmin(lo_x, patch.v[i][0])\n        lo_y = fmin(lo_y, patch.v[i][1])\n        lo_z = fmin(lo_z, patch.v[i][2])\n        hi_x = fmax(hi_x, patch.v[i][0])\n        hi_y = fmax(hi_y, patch.v[i][1])\n        hi_z = fmax(hi_z, patch.v[i][2])\n\n    bounds_o.lower_x = lo_x\n    bounds_o.lower_y = lo_y\n    bounds_o.lower_z = lo_z\n    bounds_o.upper_x = hi_x\n    bounds_o.upper_y = hi_y\n    bounds_o.upper_z = hi_z\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void patchIntersectFunc(Patch* patches,\n                             rtcr.RTCRay& ray,\n                             size_t item) noexcept nogil:\n\n    cdef Patch patch = patches[item]\n\n    cdef RayHitData hd = compute_patch_hit(patch.v, ray.org, ray.dir)\n\n    # only count this is it's the closest hit\n    if (hd.t < ray.tnear or hd.t > ray.Ng[0]):\n        return\n\n    if (fabs(hd.u) <= 1.0 and fabs(hd.v) <= 1.0 and hd.converged):\n\n        # we have a hit, so update ray information\n        ray.u = hd.u\n        ray.v = hd.v\n        ray.geomID = patch.geomID\n        ray.primID = item\n        ray.Ng[0] = hd.t\n\n        # sample the solution at the calculated point\n        sample_hex20(patches, ray)\n\n    return\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void tet_patchBoundsFunc(Tet_Patch* tet_patches,\n                          size_t item,\n                          rtcg.RTCBounds* bounds_o) noexcept nogil:\n\n    cdef Tet_Patch tet_patch = tet_patches[item]\n\n    cdef float lo_x = 1.0e300\n    cdef float lo_y = 1.0e300\n    cdef float lo_z = 1.0e300\n\n    cdef float hi_x = -1.0e300\n    cdef float hi_y = -1.0e300\n    cdef float hi_z = -1.0e300\n\n    cdef int i\n    for i in range(6):\n        lo_x = fmin(lo_x, tet_patch.v[i][0])\n        lo_y = fmin(lo_y, tet_patch.v[i][1])\n        lo_z = fmin(lo_z, tet_patch.v[i][2])\n        hi_x = fmax(hi_x, tet_patch.v[i][0])\n        hi_y = fmax(hi_y, tet_patch.v[i][1])\n        hi_z = fmax(hi_z, tet_patch.v[i][2])\n\n    bounds_o.lower_x = lo_x\n    bounds_o.lower_y = lo_y\n    bounds_o.lower_z = lo_z\n    bounds_o.upper_x = hi_x\n    bounds_o.upper_y = hi_y\n    bounds_o.upper_z = hi_z\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void tet_patchIntersectFunc(Tet_Patch* tet_patches,\n                             rtcr.RTCRay& ray,\n                             size_t item) noexcept nogil:\n\n    cdef Tet_Patch tet_patch = tet_patches[item]\n\n    cdef RayHitData hd = compute_tet_patch_hit(tet_patch.v, ray.org, ray.dir)\n\n    # only count this is it's the closest hit\n    if (hd.t < ray.tnear or hd.t > ray.Ng[0]):\n        return\n\n    if (hd.converged and 0 <= hd.u and 0 <= hd.v and hd.u + hd.v <= 1):\n\n        # we have a hit, so update ray information\n        ray.u = hd.u\n        ray.v = hd.v\n        ray.geomID = tet_patch.geomID\n        ray.primID = item\n        ray.Ng[0] = hd.t\n\n        # sample the solution at the calculated point\n        sample_tet10(tet_patches, ray)\n\n    return\n"
  },
  {
    "path": "yt/utilities/lib/embree_mesh/mesh_samplers.pxd",
    "content": "cimport cython\ncimport pyembree.rtcore as rtc\ncimport pyembree.rtcore_ray as rtcr\n\n\ncdef void sample_hex(void* userPtr,\n                     rtcr.RTCRay& ray) noexcept nogil\n\ncdef void sample_wedge(void* userPtr,\n                       rtcr.RTCRay& ray) noexcept nogil\n\ncdef void sample_tetra(void* userPtr,\n                       rtcr.RTCRay& ray) noexcept nogil\n\ncdef void sample_hex20(void* userPtr,\n                       rtcr.RTCRay& ray) noexcept nogil\n\ncdef void sample_tet10(void* userPtr,\n                       rtcr.RTCRay& ray) noexcept nogil\n"
  },
  {
    "path": "yt/utilities/lib/embree_mesh/mesh_samplers.pyx",
    "content": "# distutils: include_dirs = EMBREE_INC_DIR\n# distutils: library_dirs = EMBREE_LIB_DIR\n# distutils: libraries = EMBREE_LIBS\n# distutils: language = c++\n\"\"\"\nThis file contains functions that sample a surface mesh at the point hit by\na ray. These can be used with pyembree in the form of \"filter feedback functions.\"\n\nNote - this file is only used for the Embree-accelerated ray-tracer.\n\n\"\"\"\n\n\ncimport cython\ncimport pyembree.rtcore_ray as rtcr\nfrom pyembree.rtcore cimport Triangle\n\nfrom yt.utilities.lib.element_mappings cimport (\n    ElementSampler,\n    P1Sampler3D,\n    Q1Sampler3D,\n    S2Sampler3D,\n    Tet2Sampler3D,\n    W1Sampler3D,\n)\nfrom yt.utilities.lib.primitives cimport patchSurfaceFunc, tet_patchSurfaceFunc\n\nfrom .mesh_construction cimport MeshDataContainer, Patch, Tet_Patch\n\n\ncdef ElementSampler Q1Sampler = Q1Sampler3D()\ncdef ElementSampler P1Sampler = P1Sampler3D()\ncdef ElementSampler S2Sampler = S2Sampler3D()\ncdef ElementSampler W1Sampler = W1Sampler3D()\ncdef ElementSampler Tet2Sampler = Tet2Sampler3D()\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void get_hit_position(double* position,\n                           void* userPtr,\n                           rtcr.RTCRay& ray) noexcept nogil:\n    cdef int primID, i\n    cdef double[3][3] vertex_positions\n    cdef Triangle tri\n    cdef MeshDataContainer* data\n\n    primID = ray.primID\n    data = <MeshDataContainer*> userPtr\n    tri = data.indices[primID]\n\n    vertex_positions[0][0] = data.vertices[tri.v0].x\n    vertex_positions[0][1] = data.vertices[tri.v0].y\n    vertex_positions[0][2] = data.vertices[tri.v0].z\n\n    vertex_positions[1][0] = data.vertices[tri.v1].x\n    vertex_positions[1][1] = data.vertices[tri.v1].y\n    vertex_positions[1][2] = data.vertices[tri.v1].z\n\n    vertex_positions[2][0] = data.vertices[tri.v2].x\n    vertex_positions[2][1] = data.vertices[tri.v2].y\n    vertex_positions[2][2] = data.vertices[tri.v2].z\n\n    for i in range(3):\n        position[i] = vertex_positions[0][i]*(1.0 - ray.u - ray.v) + \\\n                      vertex_positions[1][i]*ray.u + \\\n                      vertex_positions[2][i]*ray.v\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void sample_hex(void* userPtr,\n                     rtcr.RTCRay& ray) noexcept nogil:\n    cdef int ray_id, elem_id, i\n    cdef double val\n    cdef double[8] field_data\n    cdef int[8] element_indices\n    cdef double[24] vertices\n    cdef double[3] position\n    cdef MeshDataContainer* data\n\n    data = <MeshDataContainer*> userPtr\n    ray_id = ray.primID\n    if ray_id == -1:\n        return\n\n    # ray_id records the id number of the hit according to\n    # embree, in which the primitives are triangles. Here,\n    # we convert this to the element id by dividing by the\n    # number of triangles per element.\n    elem_id = ray_id / data.tpe\n\n    get_hit_position(position, userPtr, ray)\n\n    for i in range(8):\n        element_indices[i] = data.element_indices[elem_id*8+i]\n\n    for i in range(data.fpe):\n        field_data[i] = data.field_data[elem_id*data.fpe+i]\n\n    for i in range(8):\n        vertices[i*3]     = data.vertices[element_indices[i]].x\n        vertices[i*3 + 1] = data.vertices[element_indices[i]].y\n        vertices[i*3 + 2] = data.vertices[element_indices[i]].z\n\n    # we use ray.time to pass the value of the field\n    cdef double mapped_coord[3]\n    Q1Sampler.map_real_to_unit(mapped_coord, vertices, position)\n    if data.fpe == 1:\n        val = field_data[0]\n    else:\n        val = Q1Sampler.sample_at_unit_point(mapped_coord, field_data)\n    ray.time = val\n\n    # we use ray.instID to pass back whether the ray is near the\n    # element boundary or not (used to annotate mesh lines)\n    ray.instID = Q1Sampler.check_mesh_lines(mapped_coord)\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void sample_wedge(void* userPtr,\n                       rtcr.RTCRay& ray) noexcept nogil:\n    cdef int ray_id, elem_id, i\n    cdef double val\n    cdef double[6] field_data\n    cdef int[6] element_indices\n    cdef double[18] vertices\n    cdef double[3] position\n    cdef MeshDataContainer* data\n\n    data = <MeshDataContainer*> userPtr\n    ray_id = ray.primID\n    if ray_id == -1:\n        return\n\n    # ray_id records the id number of the hit according to\n    # embree, in which the primitives are triangles. Here,\n    # we convert this to the element id by dividing by the\n    # number of triangles per element.\n    elem_id = ray_id / data.tpe\n\n    get_hit_position(position, userPtr, ray)\n\n    for i in range(6):\n        element_indices[i] = data.element_indices[elem_id*6+i]\n\n    for i in range(data.fpe):\n        field_data[i] = data.field_data[elem_id*data.fpe+i]\n\n    for i in range(6):\n        vertices[i*3]     = data.vertices[element_indices[i]].x\n        vertices[i*3 + 1] = data.vertices[element_indices[i]].y\n        vertices[i*3 + 2] = data.vertices[element_indices[i]].z\n\n    # we use ray.time to pass the value of the field\n    cdef double mapped_coord[3]\n    W1Sampler.map_real_to_unit(mapped_coord, vertices, position)\n    if data.fpe == 1:\n        val = field_data[0]\n    else:\n        val = W1Sampler.sample_at_unit_point(mapped_coord, field_data)\n    ray.time = val\n    ray.instID = W1Sampler.check_mesh_lines(mapped_coord)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.initializedcheck(False)\n@cython.cdivision(True)\ncdef void sample_hex20(void* userPtr,\n                       rtcr.RTCRay& ray) noexcept nogil:\n    cdef int ray_id, i\n    cdef double val\n    cdef double[3] position\n    cdef float[3] pos\n    cdef Patch* data\n\n    data = <Patch*> userPtr\n    ray_id = ray.primID\n    if ray_id == -1:\n        return\n    cdef Patch patch = data[ray_id]\n\n    # ray_id records the id number of the hit according to\n    # embree, in which the primitives are patches. Here,\n    # we convert this to the element id by dividing by the\n    # number of patches per element.\n\n    # fills \"position\" with the physical position of the hit\n    patchSurfaceFunc(data[ray_id].v, ray.u, ray.v, pos)\n    for i in range(3):\n        position[i] = <double> pos[i]\n\n    # we use ray.time to pass the value of the field\n    cdef double mapped_coord[3]\n    S2Sampler.map_real_to_unit(mapped_coord, patch.vertices, position)\n    val = S2Sampler.sample_at_unit_point(mapped_coord, patch.field_data)\n    ray.time = val\n    ray.instID = S2Sampler.check_mesh_lines(mapped_coord)\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void sample_tetra(void* userPtr,\n                       rtcr.RTCRay& ray) noexcept nogil:\n\n    cdef int ray_id, elem_id, i\n    cdef double val\n    cdef double[4] field_data\n    cdef int[4] element_indices\n    cdef double[12] vertices\n    cdef double[3] position\n    cdef MeshDataContainer* data\n\n    data = <MeshDataContainer*> userPtr\n    ray_id = ray.primID\n    if ray_id == -1:\n        return\n\n    get_hit_position(position, userPtr, ray)\n\n    # ray_id records the id number of the hit according to\n    # embree, in which the primitives are triangles. Here,\n    # we convert this to the element id by dividing by the\n    # number of triangles per element.\n    elem_id = ray_id / data.tpe\n\n    for i in range(4):\n        element_indices[i] = data.element_indices[elem_id*4+i]\n        vertices[i*3] = data.vertices[element_indices[i]].x\n        vertices[i*3 + 1] = data.vertices[element_indices[i]].y\n        vertices[i*3 + 2] = data.vertices[element_indices[i]].z\n\n    for i in range(data.fpe):\n        field_data[i] = data.field_data[elem_id*data.fpe+i]\n\n    # we use ray.time to pass the value of the field\n    cdef double mapped_coord[4]\n    P1Sampler.map_real_to_unit(mapped_coord, vertices, position)\n    if data.fpe == 1:\n        val = field_data[0]\n    else:\n        val = P1Sampler.sample_at_unit_point(mapped_coord, field_data)\n    ray.time = val\n    ray.instID = P1Sampler.check_mesh_lines(mapped_coord)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.initializedcheck(False)\n@cython.cdivision(True)\ncdef void sample_tet10(void* userPtr,\n                       rtcr.RTCRay& ray) noexcept nogil:\n    cdef int ray_id, i\n    cdef double val\n    cdef double[3] position\n    cdef float[3] pos\n    cdef Tet_Patch* data\n\n    data = <Tet_Patch*> userPtr\n    ray_id = ray.primID\n    if ray_id == -1:\n        return\n    cdef Tet_Patch tet_patch = data[ray_id]\n\n    # ray_id records the id number of the hit according to\n    # embree, in which the primitives are patches. Here,\n    # we convert this to the element id by dividing by the\n    # number of patches per element.\n\n    # fills \"position\" with the physical position of the hit\n    tet_patchSurfaceFunc(data[ray_id].v, ray.u, ray.v, pos)\n    for i in range(3):\n        position[i] = <double> pos[i]\n\n    # we use ray.time to pass the value of the field\n    cdef double mapped_coord[3]\n    Tet2Sampler.map_real_to_unit(mapped_coord, tet_patch.vertices, position)\n    val = Tet2Sampler.sample_at_unit_point(mapped_coord, tet_patch.field_data)\n    ray.time = val\n    ray.instID = Tet2Sampler.check_mesh_lines(mapped_coord)\n"
  },
  {
    "path": "yt/utilities/lib/embree_mesh/mesh_traversal.pxd",
    "content": "cimport pyembree.rtcore\ncimport pyembree.rtcore_ray\ncimport pyembree.rtcore_scene as rtcs\n\n\ncdef class YTEmbreeScene:\n    cdef rtcs.RTCScene scene_i\n"
  },
  {
    "path": "yt/utilities/lib/embree_mesh/mesh_traversal.pyx",
    "content": "# distutils: include_dirs = EMBREE_INC_DIR\n# distutils: library_dirs = EMBREE_LIB_DIR\n# distutils: libraries = EMBREE_LIBS\n# distutils: language = c++\n\"\"\"\nThis file contains the MeshSampler classes, which handles casting rays at a\nmesh source using either pyembree or the cython ray caster.\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\n\nimport numpy as np\n\ncimport pyembree.rtcore as rtc\ncimport pyembree.rtcore_geometry as rtcg\ncimport pyembree.rtcore_ray as rtcr\ncimport pyembree.rtcore_scene as rtcs\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.lib.image_samplers cimport ImageSampler\n\nrtc.rtcInit(NULL)\nrtc.rtcSetErrorFunction(error_printer)\n\ncdef void error_printer(const rtc.RTCError code, const char *_str):\n    print(\"ERROR CAUGHT IN EMBREE\")\n    rtc.print_error(code)\n    print(\"ERROR MESSAGE:\", _str)\n\ncdef class YTEmbreeScene:\n\n    def __init__(self):\n        self.scene_i = rtcs.rtcNewScene(rtcs.RTC_SCENE_STATIC, rtcs.RTC_INTERSECT1)\n\n    def __dealloc__(self):\n        rtcs.rtcDeleteScene(self.scene_i)\n\n\ncdef class EmbreeMeshSampler(ImageSampler):\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def __call__(self,\n                 YTEmbreeScene scene,\n                 int num_threads = 0):\n        '''\n\n        This function is supposed to cast the rays and return the\n        image.\n\n        '''\n\n        rtcs.rtcCommit(scene.scene_i)\n        cdef int vi, vj, i, j\n        cdef np.float64_t *v_pos\n        cdef np.float64_t *v_dir\n        cdef np.int64_t nx, ny, size\n        cdef np.float64_t width[3]\n        for i in range(3):\n            width[i] = self.width[i]\n        nx = self.nv[0]\n        ny = self.nv[1]\n        size = nx * ny\n        cdef rtcr.RTCRay ray\n        v_pos = <np.float64_t *> malloc(3 * sizeof(np.float64_t))\n        v_dir = <np.float64_t *> malloc(3 * sizeof(np.float64_t))\n        for j in range(size):\n            vj = j % ny\n            vi = (j - vj) / ny\n            vj = vj\n            self.vector_function(self, vi, vj, width, v_dir, v_pos)\n            for i in range(3):\n                ray.org[i] = v_pos[i]\n                ray.dir[i] = v_dir[i]\n            ray.tnear = 0.0\n            ray.tfar = 1e37\n            ray.geomID = rtcg.RTC_INVALID_GEOMETRY_ID\n            ray.primID = rtcg.RTC_INVALID_GEOMETRY_ID\n            ray.instID = rtcg.RTC_INVALID_GEOMETRY_ID\n            ray.mask = -1\n            ray.time = 0\n            ray.Ng[0] = 1e37  # we use this to track the hit distance\n            rtcs.rtcIntersect(scene.scene_i, ray)\n            self.image[vi, vj, 0] = ray.time\n            self.image_used[vi, vj] = ray.primID\n            self.mesh_lines[vi, vj] = ray.instID\n            self.zbuffer[vi, vj] = ray.tfar\n        free(v_pos)\n        free(v_dir)\n"
  },
  {
    "path": "yt/utilities/lib/endian_swap.h",
    "content": "/*\n   These macros are taken from Paul Bourke's page at\n   http://local.wasp.uwa.edu.au/~pbourke/dataformats/fortran/\n\n   The current year is 2010, and evidently we still have to deal with\n   endianness.\n*/\n\n#define SWAP_2(x) ( (((x) & 0xff) << 8) | ((unsigned short)(x) >> 8) )\n#define SWAP_4(x) ( ((x) << 24) | (((x) << 8) & 0x00ff0000) | \\\n         (((x) >> 8) & 0x0000ff00) | ((x) >> 24) )\n#define FIX_SHORT(x) (*(unsigned short *)&(x) = SWAP_2(*(unsigned short *)&(x)))\n#define FIX_LONG(x) (*(unsigned *)&(x) = SWAP_4(*(unsigned *)&(x)))\n#define FIX_FLOAT(x) FIX_LONG(x)\n"
  },
  {
    "path": "yt/utilities/lib/field_interpolation_tables.pxd",
    "content": "# distutils: language = c++\n# distutils: extra_compile_args = CPP14_FLAG\n# distutils: extra_link_args = CPP14_FLAG\n\"\"\"\nField Interpolation Tables\n\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\nfrom libc.stdlib cimport malloc\n\nfrom yt.utilities.lib.fp_utils cimport fabs, fclip, fmax, fmin, iclip, imax, imin\n\n\ncdef extern from \"<cmath>\" namespace \"std\":\n    bint isnormal(double x) noexcept nogil\n\n\ncdef extern from \"platform_dep_math.hpp\":\n    bint __isnormal(double) noexcept nogil\n\n\ncdef struct FieldInterpolationTable:\n    # Note that we make an assumption about retaining a reference to values\n    # externally.\n    np.float64_t *values\n    np.float64_t bounds[2]\n    np.float64_t dbin\n    np.float64_t idbin\n    np.float64_t *d0\n    np.float64_t *dy\n    int field_id\n    int weight_field_id\n    int weight_table_id\n    int nbins\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline void FIT_initialize_table(FieldInterpolationTable *fit, int nbins,\n              np.float64_t *values, np.float64_t bounds1, np.float64_t bounds2,\n              int field_id, int weight_field_id, int weight_table_id) noexcept nogil:\n    cdef int i\n    fit.bounds[0] = bounds1; fit.bounds[1] = bounds2\n    fit.nbins = nbins\n    fit.dbin = (fit.bounds[1] - fit.bounds[0])/(fit.nbins-1)\n    fit.idbin = 1.0/fit.dbin\n    # Better not pull this out from under us, yo\n    fit.values = values\n    fit.d0 = <np.float64_t *> malloc(sizeof(np.float64_t) * nbins)\n    fit.dy = <np.float64_t *> malloc(sizeof(np.float64_t) * nbins)\n    for i in range(nbins-1):\n        fit.d0[i] = fit.bounds[0] + i * fit.dbin\n        fit.dy[i] = (fit.values[i + 1] - fit.values[i]) * fit.idbin\n    fit.field_id = field_id\n    fit.weight_field_id = weight_field_id\n    fit.weight_table_id = weight_table_id\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline np.float64_t FIT_get_value(const FieldInterpolationTable *fit,\n                                       np.float64_t dvs[6]) noexcept nogil:\n    cdef np.float64_t dd, dout\n    cdef int bin_id\n    if dvs[fit.field_id] >= fit.bounds[1] or dvs[fit.field_id] <= fit.bounds[0]: return 0.0\n    if not __isnormal(dvs[fit.field_id]): return 0.0\n    bin_id = <int> ((dvs[fit.field_id] - fit.bounds[0]) * fit.idbin)\n    bin_id = iclip(bin_id, 0, fit.nbins-2)\n\n    dd = dvs[fit.field_id] - fit.d0[bin_id] # x - x0\n    dout = fit.values[bin_id] + dd * fit.dy[bin_id]\n    cdef int wfi = fit.weight_field_id\n    if wfi != -1:\n        dout *= dvs[wfi]\n    return dout\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline void FIT_eval_transfer(\n        const np.float64_t dt, np.float64_t *dvs,\n        np.float64_t *rgba, const int n_fits,\n        const FieldInterpolationTable fits[6],\n        const int field_table_ids[6], const int grey_opacity) noexcept nogil:\n    cdef int i, fid\n    cdef np.float64_t ta\n    cdef np.float64_t istorage[6]\n    cdef np.float64_t trgba[6]\n    for i in range(n_fits):\n        istorage[i] = FIT_get_value(&fits[i], dvs)\n    for i in range(n_fits):\n        fid = fits[i].weight_table_id\n        if fid != -1:\n            istorage[i] *= istorage[fid]\n    for i in range(6):\n        trgba[i] = istorage[field_table_ids[i]]\n\n    if grey_opacity == 1:\n        ta = fmax(1.0 - dt*trgba[3], 0.0)\n        for i in range(4):\n            rgba[i] = dt*trgba[i] + ta*rgba[i]\n    else:\n        for i in range(3):\n            ta = fmax(1.0-dt*trgba[i], 0.0)\n            rgba[i] = dt*trgba[i] + ta*rgba[i]\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline void FIT_eval_transfer_with_light(np.float64_t dt, np.float64_t *dvs,\n        np.float64_t *grad, np.float64_t *l_dir, np.float64_t *l_rgba,\n        np.float64_t *rgba, int n_fits,\n        FieldInterpolationTable fits[6],\n        int field_table_ids[6], int grey_opacity) noexcept nogil:\n    cdef int i, fid\n    cdef np.float64_t ta, dot_prod\n    cdef np.float64_t istorage[6]\n    cdef np.float64_t trgba[6]\n    dot_prod = 0.0\n    for i in range(3):\n        dot_prod += l_dir[i]*grad[i]\n    #dot_prod = fmax(0.0, dot_prod)\n    for i in range(6): istorage[i] = 0.0\n    for i in range(n_fits):\n        istorage[i] = FIT_get_value(&fits[i], dvs)\n    for i in range(n_fits):\n        fid = fits[i].weight_table_id\n        if fid != -1: istorage[i] *= istorage[fid]\n    for i in range(6):\n        trgba[i] = istorage[field_table_ids[i]]\n    if grey_opacity == 1:\n        ta = fmax(1.0-dt*(trgba[0] + trgba[1] + trgba[2]), 0.0)\n        for i in range(3):\n            rgba[i] = (1.-ta)*trgba[i]*(1. + dot_prod*l_rgba[i]) + ta * rgba[i]\n    else:\n        for i in range(3):\n            ta = fmax(1.0-dt*trgba[i], 0.0)\n            rgba[i] = (1.-ta)*trgba[i]*(1. + dot_prod*l_rgba[i]) + ta * rgba[i]\n"
  },
  {
    "path": "yt/utilities/lib/fixed_interpolator.cpp",
    "content": "/*******************************************************************************\n*******************************************************************************/\n\n//\n// A small, tiny, itty bitty module for computation-intensive interpolation\n// that I can't seem to make fast in Cython\n//\n\n#include \"fixed_interpolator.hpp\"\n\n#define VINDEX(A,B,C) data[((((A)+ci[0])*(ds[1]+1)+((B)+ci[1]))*(ds[2]+1)+ci[2]+(C))]\n//  (((C*ds[1])+B)*ds[0]+A)\n#define OINDEX(A,B,C) data[(A)*(ds[1]+1)*(ds[2]+1)+(B)*ds[2]+(B)+(C)]\n\nnpy_float64 fast_interpolate(int ds[3], int ci[3], npy_float64 dp[3],\n                             npy_float64 *data)\n{\n    int i;\n    npy_float64 dv, dm[3];\n    for(i=0;i<3;i++)dm[i] = (1.0 - dp[i]);\n    dv  = 0.0;\n    dv += VINDEX(0,0,0) * (dm[0]*dm[1]*dm[2]);\n    dv += VINDEX(0,0,1) * (dm[0]*dm[1]*dp[2]);\n    dv += VINDEX(0,1,0) * (dm[0]*dp[1]*dm[2]);\n    dv += VINDEX(0,1,1) * (dm[0]*dp[1]*dp[2]);\n    dv += VINDEX(1,0,0) * (dp[0]*dm[1]*dm[2]);\n    dv += VINDEX(1,0,1) * (dp[0]*dm[1]*dp[2]);\n    dv += VINDEX(1,1,0) * (dp[0]*dp[1]*dm[2]);\n    dv += VINDEX(1,1,1) * (dp[0]*dp[1]*dp[2]);\n    /*assert(dv < -20);*/\n    return dv;\n}\n\nnpy_float64 offset_interpolate(int ds[3], npy_float64 dp[3], npy_float64 *data)\n{\n    npy_float64 dv, vz[4];\n\n    dv = 1.0 - dp[2];\n    vz[0] = dv*OINDEX(0,0,0) + dp[2]*OINDEX(0,0,1);\n    vz[1] = dv*OINDEX(0,1,0) + dp[2]*OINDEX(0,1,1);\n    vz[2] = dv*OINDEX(1,0,0) + dp[2]*OINDEX(1,0,1);\n    vz[3] = dv*OINDEX(1,1,0) + dp[2]*OINDEX(1,1,1);\n\n    dv = 1.0 - dp[1];\n    vz[0] = dv*vz[0] + dp[1]*vz[1];\n    vz[1] = dv*vz[2] + dp[1]*vz[3];\n\n    dv = 1.0 - dp[0];\n    vz[0] = dv*vz[0] + dp[0]*vz[1];\n\n    return vz[0];\n}\n\nvoid offset_fill(int ds[3], npy_float64 *data, npy_float64 gridval[8])\n{\n    gridval[0] = OINDEX(0,0,0);\n    gridval[1] = OINDEX(1,0,0);\n    gridval[2] = OINDEX(1,1,0);\n    gridval[3] = OINDEX(0,1,0);\n    gridval[4] = OINDEX(0,0,1);\n    gridval[5] = OINDEX(1,0,1);\n    gridval[6] = OINDEX(1,1,1);\n    gridval[7] = OINDEX(0,1,1);\n}\n\nvoid vertex_interp(npy_float64 v1, npy_float64 v2, npy_float64 isovalue,\n                   npy_float64 vl[3], npy_float64 dds[3],\n                   npy_float64 x, npy_float64 y, npy_float64 z,\n                   int vind1, int vind2)\n{\n    /*if (fabs(isovalue - v1) < 0.000001) return 0.0;\n    if (fabs(isovalue - v2) < 0.000001) return 1.0;\n    if (fabs(v1 - v2) < 0.000001) return 0.0;*/\n    int i;\n    static npy_float64 cverts[8][3] =\n        {{0,0,0}, {1,0,0}, {1,1,0}, {0,1,0},\n         {0,0,1}, {1,0,1}, {1,1,1}, {0,1,1}};\n\n    npy_float64 mu = ((isovalue - v1) / (v2 - v1));\n\n    if (fabs(1.0 - isovalue/v1) < 0.000001) mu = 0.0;\n    if (fabs(1.0 - isovalue/v2) < 0.000001) mu = 1.0;\n    if (fabs(v1/v2) < 0.000001) mu = 0.0;\n\n    vl[0] = x; vl[1] = y; vl[2] = z;\n    for (i=0;i<3;i++)\n        vl[i] += dds[i] * cverts[vind1][i]\n               + dds[i] * mu*(cverts[vind2][i] - cverts[vind1][i]);\n}\n\nnpy_float64 trilinear_interpolate(int ds[3], int ci[3], npy_float64 dp[3],\n\t\t\t\t  npy_float64 *data)\n{\n    /* dims is one less than the dimensions of the array */\n    int i;\n    npy_float64 dm[3], vz[4];\n  //dp is the distance to the plane.  dm is val, dp = 1-val\n    for(i=0;i<3;i++)dm[i] = (1.0 - dp[i]);\n\n  //First interpolate in z\n    vz[0] = dm[2]*VINDEX(0,0,0) + dp[2]*VINDEX(0,0,1);\n    vz[1] = dm[2]*VINDEX(0,1,0) + dp[2]*VINDEX(0,1,1);\n    vz[2] = dm[2]*VINDEX(1,0,0) + dp[2]*VINDEX(1,0,1);\n    vz[3] = dm[2]*VINDEX(1,1,0) + dp[2]*VINDEX(1,1,1);\n\n  //Then in y\n    vz[0] = dm[1]*vz[0] + dp[1]*vz[1];\n    vz[1] = dm[1]*vz[2] + dp[1]*vz[3];\n\n  //Then in x\n    vz[0] = dm[0]*vz[0] + dp[0]*vz[1];\n    /*assert(dv < -20);*/\n    return vz[0];\n}\n\nvoid eval_gradient(int ds[3], npy_float64 dp[3],\n\t\t\t\t  npy_float64 *data, npy_float64 *grad)\n{\n    // We just take some small value\n\n    int i;\n    npy_float64 denom, plus, minus, backup, normval;\n\n    normval = 0.0;\n    for (i = 0; i < 3; i++) {\n      backup = dp[i];\n      grad[i] = 0.0;\n      if (dp[i] >= 0.95) {plus = dp[i]; minus = dp[i] - 0.05;}\n      else if (dp[i] <= 0.05) {plus = dp[i] + 0.05; minus = 0.0;}\n      else {plus = dp[i] + 0.05; minus = dp[i] - 0.05;}\n      //fprintf(stderr, \"DIM: %d %0.3lf %0.3lf\\n\", i, plus, minus);\n      denom = plus - minus;\n      dp[i] = plus;\n      grad[i] += offset_interpolate(ds, dp, data) / denom;\n      dp[i] = minus;\n      grad[i] -= offset_interpolate(ds, dp, data) / denom;\n      dp[i] = backup;\n      normval += grad[i]*grad[i];\n    }\n    if (normval != 0.0){\n      normval = sqrt(normval);\n      for (i = 0; i < 3; i++) grad[i] /= -normval;\n      //fprintf(stderr, \"Normval: %0.3lf %0.3lf %0.3lf %0.3lf\\n\",\n      //        normval, grad[0], grad[1], grad[2]);\n    }else{\n      grad[0]=grad[1]=grad[2]=0.0;\n    }\n}\n"
  },
  {
    "path": "yt/utilities/lib/fixed_interpolator.hpp",
    "content": "/*******************************************************************************\n*******************************************************************************/\n//\n// A small, tiny, itty bitty module for computation-intensive interpolation\n// that I can't seem to make fast in Cython\n//\n\n#include \"Python.h\"\n\n#include <stdio.h>\n#include <math.h>\n#include <signal.h>\n#include <ctype.h>\n\n#include \"numpy/ndarrayobject.h\"\n\nnpy_float64 fast_interpolate(int ds[3], int ci[3], npy_float64 dp[3],\n                             npy_float64 *data);\n\nnpy_float64 offset_interpolate(int ds[3], npy_float64 dp[3], npy_float64 *data);\n\nnpy_float64 trilinear_interpolate(int ds[3], int ci[3], npy_float64 dp[3],\n\t\t\t\t  npy_float64 *data);\n\nvoid eval_gradient(int ds[3], npy_float64 dp[3], npy_float64 *data, npy_float64 *grad);\n\nvoid offset_fill(int *ds, npy_float64 *data, npy_float64 *gridval);\n\nvoid vertex_interp(npy_float64 v1, npy_float64 v2, npy_float64 isovalue,\n                   npy_float64 vl[3], npy_float64 dds[3],\n                   npy_float64 x, npy_float64 y, npy_float64 z,\n                   int vind1, int vind2);\n"
  },
  {
    "path": "yt/utilities/lib/fixed_interpolator.pxd",
    "content": "\"\"\"\nFixed interpolator includes\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\n\ncdef extern from \"fixed_interpolator.hpp\":\n    np.float64_t fast_interpolate(int ds[3], int ci[3], np.float64_t dp[3],\n                                  np.float64_t *data) noexcept nogil\n    np.float64_t offset_interpolate(int ds[3], np.float64_t dp[3],\n                                    np.float64_t *data) noexcept nogil\n    np.float64_t trilinear_interpolate(int ds[3], int ci[3], np.float64_t dp[3],\n                                       np.float64_t *data) noexcept nogil\n    void eval_gradient(int ds[3], np.float64_t dp[3], np.float64_t *data,\n                       np.float64_t grad[3]) noexcept nogil\n    void offset_fill(int *ds, np.float64_t *data, np.float64_t *gridval) noexcept nogil\n    void vertex_interp(np.float64_t v1, np.float64_t v2, np.float64_t isovalue,\n                       np.float64_t vl[3], np.float64_t dds[3],\n                       np.float64_t x, np.float64_t y, np.float64_t z,\n                       int vind1, int vind2) noexcept nogil\n"
  },
  {
    "path": "yt/utilities/lib/fnv_hash.pxd",
    "content": "\"\"\"\nDefinitions for fnv_hash\n\n\n\n\n\"\"\"\n\n\n\nimport numpy as np\n\ncimport numpy as np\n\n\ncdef np.int64_t c_fnv_hash(unsigned unsigned char[:] octets) noexcept nogil\n"
  },
  {
    "path": "yt/utilities/lib/fnv_hash.pyx",
    "content": "# distutils: libraries = STD_LIBS\n# distutils: include_dirs = LIB_DIR\n\"\"\"\nFast hashing routines\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\n\n@cython.wraparound(False)\n@cython.boundscheck(False)\ncdef np.int64_t c_fnv_hash(unsigned char[:] octets) noexcept nogil:\n    # https://bitbucket.org/yt_analysis/yt/issues/1052/field-access-tests-fail-under-python3\n    # FNV hash cf. http://www.isthe.com/chongo/tech/comp/fnv/index.html\n    cdef np.int64_t hash_val = 2166136261\n    cdef int i\n    for i in range(octets.shape[0]):\n        hash_val = hash_val ^ octets[i]\n        hash_val = hash_val * 16777619\n    return hash_val\n\ndef fnv_hash(octets):\n    \"\"\"\n\n    Create a FNV hash from a bytestring.\n    Info: http://www.isthe.com/chongo/tech/comp/fnv/index.html\n\n    Parameters\n    ----------\n    octets : bytestring\n        The string of bytes to generate a hash from.\n    \"\"\"\n    return c_fnv_hash(octets)\n"
  },
  {
    "path": "yt/utilities/lib/fortran_reader.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nSimple readers for fortran unformatted data, specifically for the Tiger code.\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.stdio cimport FILE, fclose, fopen\n\n#cdef inline int imax(int i0, int i1):\n    #if i0 > i1: return i0\n    #return i1\n\ncdef extern from \"endian_swap.h\":\n    void FIX_SHORT( unsigned short )\n    void FIX_LONG( unsigned )\n    void FIX_FLOAT( float )\n\ncdef extern from \"platform_dep.h\":\n    void *alloca(size_t)\n\ncdef extern from \"stdio.h\":\n    cdef int SEEK_SET\n    cdef int SEEK_CUR\n    cdef int SEEK_END\n    int fseek(FILE *stream, long offset, int whence)\n    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)\n    long ftell(FILE *stream)\n    char *fgets(char *s, int size, FILE *stream)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef read_and_seek(char *filename, np.int64_t offset1,\n                  np.int64_t offset2, np.ndarray buffer, int bytes):\n    cdef FILE *f = fopen(filename, \"rb\")\n    cdef void *buf = <void *> buffer.data\n    cdef char line[1024]\n    cdef size_t n = 1023\n    fseek(f, offset1, SEEK_SET)\n    fgets(line, n, f)\n    fseek(f, offset2, SEEK_CUR)\n    fread(buf, 1, bytes, f)\n    fclose(f)\n\ndef count_art_octs(char *fn, long offset,\n                   int min_level, int max_level,\n                   int nhydro_vars,\n                   level_info):\n    cdef int nchild = 8\n    cdef int next_record = -1, nLevel = -1\n    cdef int dummy_records[9]\n    cdef int readin = -1\n    cdef FILE *f = fopen(fn, \"rb\")\n    fseek(f, offset, SEEK_SET)\n    for _ in range(min_level + 1, max_level + 1):\n        fread(dummy_records, sizeof(int), 2, f)\n        fread(&nLevel, sizeof(int), 1, f); FIX_LONG(nLevel)\n        print(level_info)\n        level_info.append(nLevel)\n        fread(dummy_records, sizeof(int), 2, f)\n        fread(&next_record, sizeof(int), 1, f); FIX_LONG(next_record)\n        print(\"Record size is:\", next_record)\n        # Offset for one record header we just read\n        next_record = (nLevel * (next_record + 2*sizeof(int))) - sizeof(int)\n        fseek(f, next_record, SEEK_CUR)\n        # Now we skip the second section\n        fread(&readin, sizeof(int), 1, f); FIX_LONG(readin)\n        nhydro_vars = next_record//4-2-3 #nhvar in daniel's code\n        #record length is normally 2 pad bytes, 8 + 2 hvars (the 2 is nchem)\n        # and then 3 vars, but we can find nhvars only here and not in other\n        # file headers\n        next_record = (2*sizeof(int) + readin) * (nLevel * nchild)\n        next_record -= sizeof(int)\n        fseek(f, next_record, SEEK_CUR)\n    print(\"nhvars\",nhydro_vars)\n    fclose(f)\n\ndef read_art_tree(char *fn, long offset,\n                  int min_level, int max_level,\n                  np.ndarray[np.int64_t, ndim=2] oct_indices,\n                  np.ndarray[np.int64_t, ndim=1] oct_levels,\n                  np.ndarray[np.int64_t, ndim=2] oct_info):\n    #             np.ndarray[np.int64_t, ndim=1] oct_mask,\n    #             np.ndarray[np.int64_t, ndim=1] oct_parents,\n\n    # This accepts the filename of the ART header and an integer offset that\n    # points to the start of the record *following* the reading of iOctFree and\n    # nOct.  For those following along at home, we only need to read:\n    #   iOctPr, iOctLv\n    cdef int nchild = 8\n    cdef int iOct, nLevel, ic1\n    cdef np.int64_t next_record = -1\n    cdef long long child_record\n    cdef int iOctPs[3]\n    cdef np.int64_t dummy_records[9]\n    cdef int readin = -1\n    cdef FILE *f = fopen(fn, \"rb\")\n    fseek(f, offset, SEEK_SET)\n    cdef int Level = -1\n    cdef int * iNOLL = <int *> alloca(sizeof(int)*(max_level-min_level+1))\n    cdef int * iHOLL = <int *> alloca(sizeof(int)*(max_level-min_level+1))\n    cdef int iOctMax = 0\n    level_offsets = [0]\n    for _ in range(min_level + 1, max_level + 1):\n        fread(&readin, sizeof(int), 1, f); FIX_LONG(readin)\n        fread(&Level, sizeof(int), 1, f); FIX_LONG(Level)\n        fread(&iNOLL[Level], sizeof(int), 1, f); FIX_LONG(iNOLL[Level])\n        fread(&iHOLL[Level], sizeof(int), 1, f); FIX_LONG(iHOLL[Level])\n        fread(&readin, sizeof(int), 1, f); FIX_LONG(readin)\n        iOct = iHOLL[Level] - 1\n        nLevel = iNOLL[Level]\n        #print(\"Reading Hierarchy for Level\", Lev, Level, nLevel, iOct)\n        #print(ftell(f))\n        for ic1 in range(nLevel):\n            iOctMax = max(iOctMax, iOct)\n            #print(readin, iOct, nLevel, sizeof(int))\n            next_record = ftell(f)\n            fread(&readin, sizeof(int), 1, f); FIX_LONG(readin)\n            assert readin==52\n            next_record += readin + sizeof(int)\n            fread(iOctPs, sizeof(int), 3, f)\n            FIX_LONG(iOctPs[0]); FIX_LONG(iOctPs[1]); FIX_LONG(iOctPs[2])\n            oct_indices[iOct, 0] = iOctPs[0]\n            oct_indices[iOct, 1] = iOctPs[1]\n            oct_indices[iOct, 2] = iOctPs[2]\n            oct_info[iOct, 1] = ic1\n            #grid_info[iOct, 2] = iOctPr # we don't seem to need this\n            fread(dummy_records, sizeof(int), 6, f) # skip Nb\n            fread(&readin, sizeof(int), 1, f); FIX_LONG(readin)\n            #oct_parents[iOct] = readin - 1\n            fread(&readin, sizeof(int), 1, f); FIX_LONG(readin)\n            oct_levels[iOct] = readin\n            fread(&iOct, sizeof(int), 1, f); FIX_LONG(iOct)\n            iOct -= 1\n            assert next_record > 0\n            fseek(f, next_record, SEEK_SET)\n            fread(&readin, sizeof(int), 1, f); FIX_LONG(readin)\n            assert readin==52\n\n        level_offsets.append(ftell(f))\n\n        #skip over the hydro variables\n        #find the length of one child section\n        #print('measuring child record ',)\n        fread(&next_record, sizeof(int), 1, f)\n        #print(next_record,)\n        FIX_LONG(next_record)\n        #print(next_record)\n        fseek(f,ftell(f)-sizeof(int),SEEK_SET) #rewind\n        #This is a sloppy fix; next_record is 64bit\n        #and I don't think FIX_LONG(next_record) is working\n        #correctly for 64bits\n        if next_record > 4294967296L:\n            next_record -= 4294967296L\n        assert next_record == 56\n\n        #find the length of all of the children section\n        child_record = ftell(f) +  (next_record+2*sizeof(int))*nLevel*nchild\n        #print('Skipping over hydro vars', ftell(f), child_record)\n        fseek(f, child_record, SEEK_SET)\n\n        # for ic1 in range(nLevel * nchild):\n        #     fread(&next_record, sizeof(int), 1, f); FIX_LONG(next_record)\n        #     fread(&idc, sizeof(int), 1, f); FIX_LONG(idc); idc -= 1 + (128**3)\n        #     fread(&cm, sizeof(int), 1, f); FIX_LONG(cm)\n        #     #if cm == 0: oct_mask[idc] = 1\n        #     #else: total_masked += 1\n        #     assert next_record > 0\n        #     fseek(f, next_record - sizeof(int), SEEK_CUR)\n    fclose(f)\n    return level_offsets\n\ndef read_art_root_vars(char *fn, long root_grid_offset,\n                    int nhydro_vars, int nx, int ny, int nz,\n                    int ix, int iy, int iz, fields, var):\n\n    cdef FILE *f = fopen(fn, \"rb\")\n    cdef int j,l, cell_record_size = nhydro_vars * sizeof(float)\n    cdef float temp = -1\n    l=0\n    fseek(f, root_grid_offset, SEEK_SET)\n    # Now we seet out the cell we want\n    cdef int my_offset = (((iz * ny) + iy) * nx + ix)\n    #print(cell_record_size, my_offset, ftell(f))\n    fseek(f, cell_record_size * my_offset, SEEK_CUR)\n    #(((C)*GridDimension[1]+(B))*GridDimension[0]+A)\n    for j in range(nhydro_vars):\n        fread(&temp, sizeof(float), 1, f)\n        if j in fields:\n            FIX_FLOAT(temp)\n            var[l]=temp\n            l+=1\n    fclose(f)\n\ncdef void read_art_vars(FILE *f,\n                    int min_level, int max_level, int nhydro_vars,\n                    int grid_level,long grid_id,long child_offset,\n                    fields,\n                    np.ndarray[np.int64_t, ndim=1] level_offsets,\n                    var):\n    # nhydro_vars is the number of columns- 3 (adjusting for vars)\n    # this is normally 10=(8+2chem species)\n    cdef int record_size = 2+1+1+nhydro_vars+2\n    cdef float temp = -1.0\n    cdef float varpad[2]\n    cdef int new_padding = -1\n    cdef int padding[3]\n    cdef long offset = 8*grid_id*record_size*sizeof(float)\n    fseek(f, level_offsets[grid_level] + offset, SEEK_SET)\n    for j in range(8): #iterate over the children\n        l = 0\n        fread(padding, sizeof(int), 3, f); FIX_LONG(padding[0])\n        #print(\"Record Size\", padding[0])\n        # This should be replaced by an fread of nhydro_vars length\n        for k in range(nhydro_vars): #iterate over the record\n            fread(&temp, sizeof(float), 1, f); FIX_FLOAT(temp)\n            #print(k, temp)\n            if k in fields:\n                var[j,l] = temp\n                l += 1\n        fread(varpad, sizeof(float), 2, f)\n        fread(&new_padding, sizeof(int), 1, f); FIX_LONG(new_padding)\n        assert(padding[0] == new_padding)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef read_art_grid(int varindex,\n              np.ndarray[np.int64_t, ndim=1] start_index,\n              np.ndarray[np.int32_t, ndim=1] grid_dims,\n              np.ndarray[np.float32_t, ndim=3] data,\n              np.ndarray[np.uint8_t, ndim=3] filled,\n              np.ndarray[np.float32_t, ndim=2] level_data,\n              int level, int ref_factor,\n              component_grid_info):\n    cdef int gi, i, j, k, grid_id\n    cdef int ir, jr, kr\n    cdef int offi, offj, offk, odind\n    cdef np.int64_t di, dj, dk\n    cdef np.ndarray[np.int64_t, ndim=1] ogrid_info\n    cdef np.ndarray[np.int64_t, ndim=1] og_start_index\n    cdef np.float64_t temp_data\n    cdef np.int64_t end_index[3]\n    cdef int kr_offset, jr_offset, ir_offset\n    cdef int to_fill = 0\n    # Note that indexing into a cell is:\n    #   (k*2 + j)*2 + i\n    for i in range(3):\n        end_index[i] = start_index[i] + grid_dims[i]\n    for gi in range(len(component_grid_info)):\n        ogrid_info = component_grid_info[gi]\n        grid_id = ogrid_info[1]\n        og_start_index = ogrid_info[3:6] #the oct left edge\n        for i in range(2*ref_factor):\n            di = i + og_start_index[0] * ref_factor\n            if di < start_index[0] or di >= end_index[0]: continue\n            ir = <int> (i / ref_factor)\n            for j in range(2 * ref_factor):\n                dj = j + og_start_index[1] * ref_factor\n                if dj < start_index[1] or dj >= end_index[1]: continue\n                jr = <int> (j / ref_factor)\n                for k in range(2 * ref_factor):\n                    dk = k + og_start_index[2] * ref_factor\n                    if dk < start_index[2] or dk >= end_index[2]: continue\n                    kr = <int> (k / ref_factor)\n                    offi = di - start_index[0]\n                    offj = dj - start_index[1]\n                    offk = dk - start_index[2]\n                    #print(offi, filled.shape[0],)\n                    #print(offj, filled.shape[1],)\n                    #print(offk, filled.shape[2])\n                    if filled[offi, offj, offk] == 1: continue\n                    if level > 0:\n                        odind = (kr*2 + jr)*2 + ir\n                        # Replace with an ART-specific reader\n                        #temp_data = local_hydro_data.m_var_array[\n                        #        level][8*offset + odind]\n                        temp_data = level_data[varindex, 8*grid_id + odind]\n                    else:\n                        kr_offset = kr + <int> (start_index[0] / ref_factor)\n                        jr_offset = jr + <int> (start_index[1] / ref_factor)\n                        ir_offset = ir + <int> (start_index[2] / ref_factor)\n                        odind = (kr_offset * grid_dims[0] + jr_offset)*grid_dims[1] + ir_offset\n                        temp_data = level_data[varindex, odind]\n                    data[offi, offj, offk] = temp_data\n                    filled[offi, offj, offk] = 1\n                    to_fill += 1\n    return to_fill\n\n@cython.cdivision(True)\n@cython.boundscheck(True)\n@cython.wraparound(False)\ndef fill_child_mask(np.ndarray[np.int64_t, ndim=2] file_locations,\n                    np.ndarray[np.int64_t, ndim=1] grid_le,\n                    np.ndarray[np.uint8_t, ndim=4] art_child_masks,\n                    np.ndarray[np.uint8_t, ndim=3] child_mask):\n\n    #loop over file_locations, for each row extracting the index & LE\n    #of the oct we will pull pull from art_child_masks\n    #then use the art_child_masks info to fill in child_mask\n    cdef int i,ioct,x,y,z\n    cdef int nocts = file_locations.shape[0]\n    cdef int lex,ley,lez\n    for i in range(nocts):\n        ioct = file_locations[i,1] #from fortran to python indexing?\n        lex = file_locations[i,3] - grid_le[0] #the oct left edge x\n        ley = file_locations[i,4] - grid_le[1]\n        lez = file_locations[i,5] - grid_le[2]\n        for x in range(2):\n            for y in range(2):\n                for z in range(2):\n                    child_mask[lex+x,ley+y,lez+z] = art_child_masks[ioct,x,y,z]\n"
  },
  {
    "path": "yt/utilities/lib/fp_utils.pxd",
    "content": "\"\"\"\nShareable definitions for common fp/int Cython utilities\n\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\n\n\ncdef inline np.int64_t imax(np.int64_t i0, np.int64_t i1) noexcept nogil:\n    if i0 > i1: return i0\n    return i1\n\ncdef inline np.float64_t fmax(np.float64_t f0, np.float64_t f1) noexcept nogil:\n    if f0 > f1: return f0\n    return f1\n\ncdef inline np.int64_t imin(np.int64_t i0, np.int64_t i1) noexcept nogil:\n    if i0 < i1: return i0\n    return i1\n\ncdef inline np.float64_t fmin(np.float64_t f0, np.float64_t f1) noexcept nogil:\n    if f0 < f1: return f0\n    return f1\n\ncdef inline np.float64_t fabs(np.float64_t f0) noexcept nogil:\n    if f0 < 0.0: return -f0\n    return f0\n\ncdef inline np.int64_t iclip(np.int64_t i, np.int64_t a, np.int64_t b) noexcept nogil:\n    if i < a: return a\n    if i > b: return b\n    return i\n\ncdef inline np.int64_t i64clip(np.int64_t i, np.int64_t a, np.int64_t b) noexcept nogil:\n    if i < a: return a\n    if i > b: return b\n    return i\n\ncdef inline np.float64_t fclip(np.float64_t f,\n                      np.float64_t a, np.float64_t b) noexcept nogil:\n    return fmin(fmax(f, a), b)\n\ncdef inline np.int64_t i64max(np.int64_t i0, np.int64_t i1) noexcept nogil:\n    if i0 > i1: return i0\n    return i1\n\ncdef inline np.int64_t i64min(np.int64_t i0, np.int64_t i1) noexcept nogil:\n    if i0 < i1: return i0\n    return i1\n\ncdef inline _ensure_code(arr):\n    if hasattr(arr, \"units\"):\n        if \"code_length\" == str(arr.units):\n            return arr\n        arr.convert_to_units(\"code_length\")\n    return arr\n\nctypedef fused any_float:\n    np.float32_t\n    np.float64_t\n"
  },
  {
    "path": "yt/utilities/lib/geometry_utils.pxd",
    "content": "\"\"\"\nParticle Deposition onto Octs\n\n\n\n\n\"\"\"\n\ncimport cython\ncimport numpy as np\nfrom libc.float cimport DBL_MANT_DIG\nfrom libc.math cimport frexp, ldexp, sqrt\n\ncdef enum:\n    ORDER_MAX=20\n\ncdef enum:\n    # TODO: Handle error for indices past max\n    INDEX_MAX_64=2097151\n\ncdef enum:\n    XSHIFT=2\n    YSHIFT=1\n    ZSHIFT=0\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.int64_t ifrexp(np.float64_t x, np.int64_t *e):\n    cdef np.float64_t m\n    cdef int e0 = 0\n    m = frexp(x,&e0)\n    e[0] = <np.int64_t>e0\n    return <np.int64_t>ldexp(m,<int>DBL_MANT_DIG)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.int64_t msdb(np.int64_t a, np.int64_t b):\n    \"\"\"Get the most significant differing bit between a and b.\"\"\"\n    cdef np.int64_t c, ndx\n    c = a ^ b\n    ndx = 0\n    while (0 < c):\n        c = (c >> 1)\n        ndx+=1\n    return ndx\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.int64_t xor_msb(np.float64_t a, np.float64_t b):\n    \"\"\"Get the exponent of the highest differing bit between a and b\"\"\"\n    # Get mantissa and exponents for each number\n    cdef np.int64_t a_m, a_e, b_m, b_e, x, y, z\n    b_e = 0\n    a_e = 0\n    a_m = ifrexp(a,&a_e)\n    b_m = ifrexp(b,&b_e)\n    x = <np.int64_t> ((a_e+1)*DBL_MANT_DIG)\n    y = <np.int64_t> ((b_e+1)*DBL_MANT_DIG)\n    # Compare mantissa if exponents equal\n    if x == y:\n        if a_m == b_m: return 0\n        z = msdb(a_m,b_m)\n        #if 1: return z\n        x = x - z\n        return x-1 # required so that xor_msb(0.0,1.0)!=xor_msb(1.0,1.0)\n    # Otherwise return largest exponent\n    if y < x:\n        return x\n    else:\n        return y\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline int compare_floats_morton(np.float64_t p[3], np.float64_t q[3]):\n    cdef int j, out, dim\n    cdef np.int64_t x, y\n    x = -9999999999\n    y = 0\n    dim = 0\n    for j in range(3):#[::-1]:\n        y = xor_msb(p[j],q[j])\n        if x < y:\n           x = y\n           dim = j\n    if p[dim] < q[dim]:\n        out = 1\n    else:\n        out = 0\n    return out\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.float64_t euclidean_distance(np.float64_t[:] p, np.float64_t[:] q):\n    cdef int j\n    cdef np.float64_t d\n    d = 0.0\n    for j in range(3):\n        d+=(p[j]-q[j])**2\n    return sqrt(d)\n\n# Todo: allow radius reported independently in each dimension for rectangular domain\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.float64_t smallest_quadtree_box(np.float64_t p[3], np.float64_t q[3], np.int32_t order,\n                                               np.float64_t DLE[3], np.float64_t DRE[3],\n                                               np.float64_t *cx, np.float64_t *cy, np.float64_t *cz):\n    cdef int j\n    cdef np.float64_t c[3]\n    cdef np.uint64_t pidx[3]\n    # cdef np.uint64_t qidx[3]\n    for j in range(3):\n        pidx[j] = 0\n        # qidx[j] = 0\n    cdef np.uint64_t pidx_next[3]\n    cdef np.uint64_t qidx_next[3]\n    cdef np.float64_t dds[3]\n    cdef np.float64_t rad\n    cdef int lvl = 0\n    cdef int done = 0\n    while not done:\n        if (lvl+1 >= order):\n            done = 1\n        for j in range(3):\n            dds[j] = (DRE[j] - DLE[j])/(1 << (<int> lvl+1))\n            pidx_next[j] = <np.uint64_t>((p[j] - DLE[j])/dds[j])\n            qidx_next[j] = <np.uint64_t>((q[j] - DLE[j])/dds[j])\n        for j in range(3):\n            if pidx_next[j]!=qidx_next[j]:\n                done = 1\n                break\n        if not done:\n            for j in range(3):\n                pidx[j] = pidx_next[j]\n                # qidx[j] = qidx_next[j]\n            lvl+=1\n    rad = 0.0\n    for j in range(3):\n        dds[j] = (DRE[j] - DLE[j])/(1 << lvl)\n        c[j] = dds[j]*(<np.float64_t>pidx[j]+0.5)\n        rad+=((dds[j]/2.0)**2)\n    cx[0] = c[0]\n    cy[0] = c[1]\n    cz[0] = c[2]\n    return sqrt(rad)\n\n#-----------------------------------------------------------------------------\n# 21 bits spread over 64 with 3 bits in between\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.uint64_t spread_64bits_by3(np.uint64_t x):\n    x=(x&(<np.uint64_t>0x00000000001FFFFF))\n    x=(x|(x<<20))*(<np.uint64_t>0x000001FFC00003FF)\n\n#-----------------------------------------------------------------------------\n# 21 bits spread over 64 with 2 bits in between\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.uint64_t spread_64bits_by2(np.uint64_t x):\n    # This magic comes from http://stackoverflow.com/questions/1024754/how-to-compute-a-3d-morton-number-interleave-the-bits-of-3-ints\n    # Only reversible up to 2097151\n    # Select highest 21 bits (Required to be reversible to 21st bit)\n    # x = ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---k jihg fedc ba98 7654 3210\n    x=(x&(<np.uint64_t>0x00000000001FFFFF))\n    # x = ---- ---- ---- ---- ---- ---k jihg fedc ba-- ---- ---- ---- ---- --98 7654 3210\n    x=(x|(x<<20))&(<np.uint64_t>0x000001FFC00003FF)\n    # x = ---- ---- ---- -kji hgf- ---- ---- -edc ba-- ---- ---- 9876 5--- ---- ---4 3210\n    x=(x|(x<<10))&(<np.uint64_t>0x0007E007C00F801F)\n    # x = ---- ---- -kji h--- -gf- ---- -edc ---- ba-- ---- 987- ---6 5--- ---4 32-- --10\n    x=(x|(x<<4))&(<np.uint64_t>0x00786070C0E181C3)\n    # x = ---- ---k ji-- h--g --f- ---e d--c --b- -a-- --98 --7- -6-- 5--- -43- -2-- 1--0\n    x=(x|(x<<2))&(<np.uint64_t>0x0199219243248649)\n    # x = ---- -kj- -i-- h--g --f- -e-- d--c --b- -a-- 9--8 --7- -6-- 5--4 --3- -2-- 1--0\n    x=(x|(x<<2))&(<np.uint64_t>0x0649249249249249)\n    # x = ---k --j- -i-- h--g --f- -e-- d--c --b- -a-- 9--8 --7- -6-- 5--4 --3- -2-- 1--0\n    x=(x|(x<<2))&(<np.uint64_t>0x1249249249249249)\n    return x\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.uint64_t compact_64bits_by2(np.uint64_t x):\n    # Reversed magic\n    x=x&(<np.uint64_t>0x1249249249249249)\n    x=(x|(x>>2))&(<np.uint64_t>0x0649249249249249)\n    x=(x|(x>>2))&(<np.uint64_t>0x0199219243248649)\n    x=(x|(x>>2))&(<np.uint64_t>0x00786070C0E181C3)\n    x=(x|(x>>4))&(<np.uint64_t>0x0007E007C00F801F)\n    x=(x|(x>>10))&(<np.uint64_t>0x000001FFC00003FF)\n    x=(x|(x>>20))&(<np.uint64_t>0x00000000001FFFFF)\n    return x\n\n#-----------------------------------------------------------------------------\n# 10 bits spread over 32 with 2 bits in between\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.uint32_t spread_32bits_by2(np.uint32_t x):\n    # Only reversible up to 1023\n    # Select highest 10 bits (Required to be reversible to 10st bit)\n    # x = ---- ---- ---- ---- ---- --98 7654 3210\n    x=(x&(<np.uint32_t>0x000003FF))\n    # x = ---- --98 ---- ---- ---- ---- 7654 3210\n    x=(x|(x<<16))&(<np.uint32_t>0xFF0000FF)\n    # x = ---- --98 ---- ---- 7654 ---- ---- 3210\n    x=(x|(x<<8))&(<np.uint32_t>0x0300F00F)\n    # x = ---- --98 ---- 76-- --54 ---- 32-- --10\n    x=(x|(x<<4))&(<np.uint32_t>0x030C30C3)\n    # x = ---- 9--8 --7- -6-- 5--4 --3- -2-- 1--0\n    x=(x|(x<<2))&(<np.uint32_t>0x09249249)\n    return x\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.uint32_t compact_32bits_by2(np.uint32_t x):\n    # Reversed magic\n    x=x&(<np.uint32_t>0x09249249)\n    x=(x|(x>>2))&(<np.uint32_t>0x030C30C3)\n    x=(x|(x>>4))&(<np.uint32_t>0x0300F00F)\n    x=(x|(x>>8))&(<np.uint32_t>0xFF0000FF)\n    x=(x|(x>>16))&(<np.uint32_t>0x000003FF)\n    return x\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.uint64_t masked_merge_64bit(np.uint64_t a, np.uint64_t b, np.uint64_t mask):\n    # https://graphics.stanford.edu/~seander/bithacks.html#MaskedMerge\n    return a ^ ((a ^ b) & mask)\n\n@cython.cdivision(True)\ncdef inline np.uint64_t encode_morton_64bit(np.uint64_t x_ind, np.uint64_t y_ind, np.uint64_t z_ind):\n    cdef np.uint64_t mi\n    mi = 0\n    mi |= spread_64bits_by2(z_ind)<<ZSHIFT\n    mi |= spread_64bits_by2(y_ind)<<YSHIFT\n    mi |= spread_64bits_by2(x_ind)<<XSHIFT\n    return mi\n\n@cython.cdivision(True)\ncdef inline void decode_morton_64bit(np.uint64_t mi, np.uint64_t *p):\n    p[0] = compact_64bits_by2(mi>>XSHIFT)\n    p[1] = compact_64bits_by2(mi>>YSHIFT)\n    p[2] = compact_64bits_by2(mi>>ZSHIFT)\n\n@cython.cdivision(True)\ncdef inline np.uint64_t bounded_morton(np.float64_t x, np.float64_t y, np.float64_t z,\n                               np.float64_t *DLE, np.float64_t *DRE, np.int32_t order):\n    cdef int i\n    cdef np.float64_t dds[3]\n    cdef np.uint64_t x_ind, y_ind, z_ind\n    cdef np.uint64_t mi\n    for i in range(3):\n        dds[i] = (DRE[i] - DLE[i]) / (1 << order)\n    x_ind = <np.uint64_t> ((x - DLE[0])/dds[0])\n    y_ind = <np.uint64_t> ((y - DLE[1])/dds[1])\n    z_ind = <np.uint64_t> ((z - DLE[2])/dds[2])\n    mi = encode_morton_64bit(x_ind,y_ind,z_ind)\n    return mi\n\n@cython.cdivision(True)\ncdef inline np.uint64_t bounded_morton_relative(np.float64_t x, np.float64_t y, np.float64_t z,\n                               np.float64_t *DLE, np.float64_t *DRE,\n                               np.int32_t order1, np.int32_t order2):\n    cdef int i\n    cdef np.float64_t dds1[3]\n    cdef np.float64_t dds2[3]\n    cdef np.float64_t DLE2[3]\n    cdef np.uint64_t x_ind, y_ind, z_ind\n    cdef np.uint64_t mi2\n    for i in range(3):\n        dds1[i] = (DRE[i] - DLE[i]) / (1 << order1)\n        dds2[i] = dds1[i] / (1 << order2)\n    DLE2[0] = <np.float64_t> (<np.uint64_t> ((x - DLE[0])/dds1[0])) * dds1[0]\n    DLE2[1] = <np.float64_t> (<np.uint64_t> ((y - DLE[1])/dds1[1])) * dds1[1]\n    DLE2[2] = <np.float64_t> (<np.uint64_t> ((z - DLE[2])/dds1[2])) * dds1[2]\n    x_ind = <np.uint64_t> ((x - DLE2[0])/dds2[0])\n    y_ind = <np.uint64_t> ((y - DLE2[1])/dds2[1])\n    z_ind = <np.uint64_t> ((z - DLE2[2])/dds2[2])\n    mi2 = encode_morton_64bit(x_ind,y_ind,z_ind)\n    return mi2\n\n\n# This doesn't seem to be much, if at all, faster...\n@cython.cdivision(True)\ncdef inline np.uint64_t bounded_morton_dds(np.float64_t x, np.float64_t y, np.float64_t z,\n                               np.float64_t *DLE, np.float64_t *dds):\n    cdef np.uint64_t x_ind, y_ind, z_ind\n    cdef np.uint64_t mi\n    x_ind = <np.uint64_t> ((x - DLE[0])/dds[0])\n    y_ind = <np.uint64_t> ((y - DLE[1])/dds[1])\n    z_ind = <np.uint64_t> ((z - DLE[2])/dds[2])\n    mi = encode_morton_64bit(x_ind,y_ind,z_ind)\n    return mi\n\n@cython.cdivision(True)\ncdef inline np.uint64_t bounded_morton_relative_dds(np.float64_t x, np.float64_t y, np.float64_t z,\n                               np.float64_t *DLE, np.float64_t *dds1, np.float64_t *dds2):\n    cdef np.float64_t DLE2[3]\n    cdef np.uint64_t x_ind, y_ind, z_ind\n    cdef np.uint64_t mi2\n    DLE2[0] = <np.float64_t> (<np.uint64_t> ((x - DLE[0])/dds1[0])) * dds1[0]\n    DLE2[1] = <np.float64_t> (<np.uint64_t> ((y - DLE[1])/dds1[1])) * dds1[1]\n    DLE2[2] = <np.float64_t> (<np.uint64_t> ((z - DLE[2])/dds1[2])) * dds1[2]\n    x_ind = <np.uint64_t> ((x - DLE2[0])/dds2[0])\n    y_ind = <np.uint64_t> ((y - DLE2[1])/dds2[1])\n    z_ind = <np.uint64_t> ((z - DLE2[2])/dds2[2])\n    mi2 = encode_morton_64bit(x_ind,y_ind,z_ind)\n    return mi2\n\n\n@cython.cdivision(True)\ncdef inline np.uint64_t bounded_morton_split_dds(np.float64_t x, np.float64_t y, np.float64_t z,\n                               np.float64_t *DLE, np.float64_t *dds, np.uint64_t *p):\n    cdef np.uint64_t mi\n    p[0] = <np.uint64_t> ((x - DLE[0])/dds[0])\n    p[1] = <np.uint64_t> ((y - DLE[1])/dds[1])\n    p[2] = <np.uint64_t> ((z - DLE[2])/dds[2])\n    mi = encode_morton_64bit(p[0], p[1], p[2])\n    return mi\n\n@cython.cdivision(True)\ncdef inline np.uint64_t bounded_morton_split_relative_dds(np.float64_t x, np.float64_t y, np.float64_t z,\n                               np.float64_t *DLE, np.float64_t *dds1, np.float64_t *dds2,\n                               np.uint64_t *p2):\n    cdef np.float64_t DLE2[3]\n    cdef np.uint64_t mi2\n    DLE2[0] = DLE[0] + <np.float64_t> (<np.uint64_t> ((x - DLE[0])/dds1[0])) * dds1[0]\n    DLE2[1] = DLE[1] + <np.float64_t> (<np.uint64_t> ((y - DLE[1])/dds1[1])) * dds1[1]\n    DLE2[2] = DLE[2] + <np.float64_t> (<np.uint64_t> ((z - DLE[2])/dds1[2])) * dds1[2]\n    p2[0] = <np.uint64_t> ((x - DLE2[0])/dds2[0])\n    p2[1] = <np.uint64_t> ((y - DLE2[1])/dds2[1])\n    p2[2] = <np.uint64_t> ((z - DLE2[2])/dds2[2])\n    mi2 = encode_morton_64bit(p2[0], p2[1], p2[2])\n    return mi2\n\n\ncdef np.uint32_t morton_neighbors_coarse(np.uint64_t mi1, np.uint64_t max_index1,\n                                         bint periodicity[3], np.uint32_t nn,\n                                         np.uint32_t[:,:] index,\n                                         np.uint64_t[:,:] ind1_n,\n                                         np.uint64_t[:] neighbors)\n\ncdef np.uint32_t morton_neighbors_refined(np.uint64_t mi1, np.uint64_t mi2,\n                                          np.uint64_t max_index1, np.uint64_t max_index2,\n                                          bint periodicity[3], np.uint32_t nn,\n                                          np.uint32_t[:,:] index,\n                                          np.uint64_t[:,:] ind1_n,\n                                          np.uint64_t[:,:] ind2_n,\n                                          np.uint64_t[:] neighbors1,\n                                          np.uint64_t[:] neighbors2)\n"
  },
  {
    "path": "yt/utilities/lib/geometry_utils.pyx",
    "content": "# distutils: libraries = STD_LIBS\n# distutils: language = c++\n# distutils: extra_compile_args = CPP14_FLAG OMP_ARGS\n# distutils: extra_link_args = CPP14_FLAG OMP_ARGS\n\"\"\"\nSimple integrators for the radiative transfer equation\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.math cimport copysign, fabs, log2\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.lib.fp_utils cimport i64clip\n\nfrom yt.utilities.exceptions import YTDomainOverflow\n\nfrom yt.utilities.lib.vec3_ops cimport L2_norm, cross, dot, subtract\n\ncdef extern from \"math.h\":\n    double exp(double x) noexcept nogil\n    float expf(float x) noexcept nogil\n    long double expl(long double x) noexcept nogil\n    double floor(double x) noexcept nogil\n    double ceil(double x) noexcept nogil\n    double fmod(double x, double y) noexcept nogil\n    double fabs(double x) noexcept nogil\n\ncdef extern from \"platform_dep.h\":\n    long int lrint(double x) noexcept nogil\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t graycode(np.int64_t x):\n    return x^(x>>1)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t igraycode(np.int64_t x):\n    cdef np.int64_t i, j\n    if x == 0:\n        return x\n    m = <np.int64_t> ceil(log2(x)) + 1\n    i, j = x, 1\n    while j < m:\n        i = i ^ (x>>j)\n        j += 1\n    return i\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t direction(np.int64_t x, np.int64_t n):\n    #assert x < 2**n\n    if x == 0:\n        return 0\n    elif x%2 == 0:\n        return tsb(x-1, n)%n\n    else:\n        return tsb(x, n)%n\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t tsb(np.int64_t x, np.int64_t width):\n    #assert x < 2**width\n    cdef np.int64_t i = 0\n    while x&1 and i <= width:\n        x = x >> 1\n        i += 1\n    return i\n\n@cython.cpow(True)\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t bitrange(np.int64_t x, np.int64_t width,\n                         np.int64_t start, np.int64_t end):\n    return x >> (width-end) & ((2**(end-start))-1)\n\n@cython.cpow(True)\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t rrot(np.int64_t x, np.int64_t i, np.int64_t width):\n    i = i%width\n    x = (x>>i) | (x<<width-i)\n    return x&(2**width-1)\n\n@cython.cpow(True)\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t lrot(np.int64_t x, np.int64_t i, np.int64_t width):\n    i = i%width\n    x = (x<<i) | (x>>width-i)\n    return x&(2**width-1)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t transform(np.int64_t entry, np.int64_t direction,\n                          np.int64_t width, np.int64_t x):\n    return rrot((x^entry), direction + 1, width)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t entry(np.int64_t x):\n    if x == 0: return 0\n    return graycode(2*((x-1)/2))\n\n@cython.cpow(True)\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t setbit(np.int64_t x, np.int64_t w, np.int64_t i, np.int64_t b):\n    if b == 1:\n        return x | 2**(w-i-1)\n    elif b == 0:\n        return x & ~2**(w-i-1)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef spread_bits(np.uint64_t x):\n    return spread_64bits_by2(x)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef compact_bits(np.uint64_t x):\n    return compact_64bits_by2(x)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef lsz(np.uint64_t v, int stride = 1, int start = 0):\n    cdef int c\n    c = start\n    while ((np.uint64(1) << np.uint64(c)) & np.uint64(v)):\n        c += stride\n    return c\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef lsb(np.uint64_t v, int stride = 1, int start = 0):\n    cdef int c\n    c = start\n    while (np.uint64(v) << np.uint64(c)) and not ((np.uint64(1) << np.uint64(c)) & np.uint64(v)):\n        c += stride\n    return c\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef bitwise_addition(np.uint64_t x, np.int64_t y0,\n                     int stride = 1, int start = 0):\n    if (y0 == 0): return x\n    cdef int end, p, pstart\n    cdef list mstr\n    cdef np.uint64_t m, y, out\n    y = np.uint64(np.abs(y0))\n    if (y0 > 0):\n        func_ls = lsz\n    else:\n        func_ls = lsb\n    # Continue until all bits added\n    p = 0\n    out = x\n    while (y >> p):\n        if (y & (1 << p)):\n            # Get end point\n            pstart = start + p*stride\n            end = func_ls(out,stride=stride,start=pstart)\n            # Create mask\n            mstr = (end + 1) * ['0']\n            for i in range(pstart,end+1,stride):\n                mstr[i] = '1'\n                m = int(''.join(mstr[::-1]), 2)\n            # Invert portion in mask\n            # print(mstr[::-1])\n            # print(y,p,(pstart,end+1),bin(m),bin(out),bin(~out))\n            out = masked_merge_64bit(out, ~out, m)\n        # Move to next bit\n        p += 1\n    return out\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t point_to_hilbert(int order, np.int64_t p[3]):\n    cdef np.int64_t h, e, d, l, b, w, i, x\n    h = e = d = 0\n    for i in range(order):\n        l = 0\n        for x in range(3):\n            b = bitrange(p[3-x-1], order, i, i+1)\n            l |= (b<<x)\n        l = transform(e, d, 3, l)\n        w = igraycode(l)\n        e = e ^ lrot(entry(w), d+1, 3)\n        d = (d + direction(w, 3) + 1)%3\n        h = (h<<3)|w\n    return h\n\n#def hilbert_point(dimension, order, h):\n#    \"\"\"\n#        Convert an index on the Hilbert curve of the specified dimension and\n#        order to a set of point coordinates.\n#    \"\"\"\n#    #    The bit widths in this function are:\n#    #        p[*]  - order\n#    #        h     - order*dimension\n#    #        l     - dimension\n#    #        e     - dimension\n#    hwidth = order*dimension\n#    e, d = 0, 0\n#    p = [0]*dimension\n#    for i in range(order):\n#        w = utils.bitrange(h, hwidth, i*dimension, i*dimension+dimension)\n#        l = utils.graycode(w)\n#        l = itransform(e, d, dimension, l)\n#        for j in range(dimension):\n#            b = utils.bitrange(l, dimension, j, j+1)\n#            p[j] = utils.setbit(p[j], order, i, b)\n#        e = e ^ utils.lrot(entry(w), d+1, dimension)\n#        d = (d + direction(w, dimension) + 1)%dimension\n#    return p\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef void hilbert_to_point(int order, np.int64_t h, np.int64_t *p):\n    cdef np.int64_t hwidth, e, d, w, l, b\n    cdef int i, j\n    hwidth = 3 * order\n    e = d = p[0] = p[1] = p[2] = 0\n    for i in range(order):\n        w = bitrange(h, hwidth, i*3, i*3+3)\n        l = graycode(w)\n        l = lrot(l, d +1, 3)^e\n        for j in range(3):\n            b = bitrange(l, 3, j, j+1)\n            p[j] = setbit(p[j], order, i, b)\n        e = e ^ lrot(entry(w), d+1, 3)\n        d = (d + direction(w, 3) + 1)%3\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_hilbert_indices(int order, np.ndarray[np.int64_t, ndim=2] left_index):\n    # This is inspired by the scurve package by user cortesi on GH.\n    cdef int i\n    cdef np.int64_t p[3]\n    cdef np.ndarray[np.int64_t, ndim=1] hilbert_indices\n    hilbert_indices = np.zeros(left_index.shape[0], 'int64')\n    for i in range(left_index.shape[0]):\n        p[0] = left_index[i, 0]\n        p[1] = left_index[i, 1]\n        p[2] = left_index[i, 2]\n        hilbert_indices[i] = point_to_hilbert(order, p)\n    return hilbert_indices\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_hilbert_points(int order, np.ndarray[np.int64_t, ndim=1] indices):\n    # This is inspired by the scurve package by user cortesi on GH.\n    cdef int i, j\n    cdef np.int64_t p[3]\n    cdef np.ndarray[np.int64_t, ndim=2] positions\n    positions = np.zeros((indices.shape[0], 3), 'int64')\n    for i in range(indices.shape[0]):\n        hilbert_to_point(order, indices[i], p)\n        for j in range(3):\n            positions[i, j] = p[j]\n    return positions\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.uint64_t point_to_morton(np.uint64_t p[3]):\n    # Weird indent thing going on... also, should this reference the pxd func?\n    return encode_morton_64bit(p[0],p[1],p[2])\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef void morton_to_point(np.uint64_t mi, np.uint64_t *p):\n    decode_morton_64bit(mi,p)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_morton_index(np.ndarray[np.uint64_t, ndim=1] left_index):\n    cdef int j\n    cdef np.uint64_t morton_index\n    cdef np.uint64_t p[3]\n    for j in range(3):\n        if left_index[j] >= INDEX_MAX_64:\n            raise ValueError(\"Point exceeds max ({}) \".format(INDEX_MAX_64)+\n                             \"for 64bit interleave.\")\n        p[j] = left_index[j]\n    morton_index = point_to_morton(p)\n    return morton_index\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_morton_indices(np.ndarray[np.uint64_t, ndim=2] left_index):\n    cdef np.int64_t i\n    cdef int j\n    cdef np.ndarray[np.uint64_t, ndim=1] morton_indices\n    cdef np.uint64_t p[3]\n    morton_indices = np.zeros(left_index.shape[0], 'uint64')\n    for i in range(left_index.shape[0]):\n        for j in range(3):\n            if left_index[i, j] >= INDEX_MAX_64:\n                raise ValueError(\"Point exceeds max ({}) \".format(INDEX_MAX_64)+\n                                 \"for 64bit interleave.\")\n            p[j] = left_index[i, j]\n        morton_indices[i] = point_to_morton(p)\n    return morton_indices\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_morton_indices_unravel(np.ndarray[np.uint64_t, ndim=1] left_x,\n                               np.ndarray[np.uint64_t, ndim=1] left_y,\n                               np.ndarray[np.uint64_t, ndim=1] left_z):\n    cdef np.int64_t i\n    cdef np.ndarray[np.uint64_t, ndim=1] morton_indices\n    cdef np.uint64_t p[3]\n    morton_indices = np.zeros(left_x.shape[0], 'uint64')\n    for i in range(left_x.shape[0]):\n        p[0] = left_x[i]\n        p[1] = left_y[i]\n        p[2] = left_z[i]\n        for j in range(3):\n            if p[j] >= INDEX_MAX_64:\n                raise ValueError(\"Point exceeds max ({}) \".format(INDEX_MAX_64)+\n                                 \"for 64 bit interleave.\")\n        morton_indices[i] = point_to_morton(p)\n    return morton_indices\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_morton_point(np.uint64_t index):\n    cdef int j\n    cdef np.uint64_t p[3]\n    cdef np.ndarray[np.uint64_t, ndim=1] position\n    position = np.zeros(3, 'uint64')\n    morton_to_point(index, p)\n    for j in range(3):\n        position[j] = p[j]\n    return position\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_morton_points(np.ndarray[np.uint64_t, ndim=1] indices):\n    # This is inspired by the scurve package by user cortesi on GH.\n    cdef int i, j\n    cdef np.uint64_t p[3]\n    cdef np.ndarray[np.uint64_t, ndim=2] positions\n    positions = np.zeros((indices.shape[0], 3), 'uint64')\n    for i in range(indices.shape[0]):\n        morton_to_point(indices[i], p)\n        for j in range(3):\n            positions[i, j] = p[j]\n    return positions\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_morton_neighbors_coarse(mi1, max_index1, periodic, nn):\n    cdef int i\n    cdef np.uint32_t ntot\n    cdef np.ndarray[np.uint32_t, ndim=2] index = np.zeros((2*nn+1,3), dtype='uint32')\n    cdef np.ndarray[np.uint64_t, ndim=2] ind1_n = np.zeros((2*nn+1,3), dtype='uint64')\n    cdef np.ndarray[np.uint64_t, ndim=1] neighbors = np.zeros((2*nn+1)**3, dtype='uint64')\n    cdef bint periodicity[3]\n    if periodic:\n        for i in range(3): periodicity[i] = 1\n    else:\n        for i in range(3): periodicity[i] = 0\n    ntot = morton_neighbors_coarse(mi1, max_index1, periodicity, nn,\n                                   index, ind1_n, neighbors)\n    return np.resize(neighbors, (ntot,))\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.uint32_t morton_neighbors_coarse(np.uint64_t mi1, np.uint64_t max_index1,\n                                         bint periodicity[3], np.uint32_t nn,\n                                         np.uint32_t[:,:] index,\n                                         np.uint64_t[:,:] ind1_n,\n                                         np.uint64_t[:] neighbors):\n    cdef np.uint32_t ntot = 0\n    cdef np.uint64_t ind1[3]\n    cdef np.uint32_t count[3]\n    cdef np.uint32_t origin[3]\n    cdef np.int64_t adv\n    cdef int i, j, k, ii, ij, ik\n    for i in range(3):\n        count[i] = 0\n        origin[i] = 0\n    # Get indices\n    decode_morton_64bit(mi1,ind1)\n    # Determine which directions are valid\n    for j,i in enumerate(range(-nn,(nn+1))):\n        if i == 0:\n            for k in range(3):\n                ind1_n[j,k] = ind1[k]\n                index[count[k],k] = j\n                origin[k] = count[k]\n                count[k] += 1\n        else:\n            for k in range(3):\n                adv = <np.int64_t>((<np.int64_t>ind1[k]) + i)\n                if (adv < 0):\n                    if periodicity[k]:\n                        while adv < 0:\n                            adv += max_index1\n                        ind1_n[j,k] = <np.uint64_t>(adv % max_index1)\n                    else:\n                        continue\n                elif (adv >= max_index1):\n                    if periodicity[k]:\n                        ind1_n[j,k] = <np.uint64_t>(adv % max_index1)\n                    else:\n                        continue\n                else:\n                    ind1_n[j,k] = <np.uint64_t>(adv)\n                # print(i,k,adv,max_index1,ind1_n[j,k],adv % max_index1)\n                index[count[k],k] = j\n                count[k] += 1\n    # Iterate over ever combinations\n    for ii in range(count[0]):\n        i = index[ii,0]\n        for ij in range(count[1]):\n            j = index[ij,1]\n            for ik in range(count[2]):\n                k = index[ik,2]\n                if (ii != origin[0]) or (ij != origin[1]) or (ik != origin[2]):\n                    neighbors[ntot] = encode_morton_64bit(ind1_n[i,0],\n                                                          ind1_n[j,1],\n                                                          ind1_n[k,2])\n                    ntot += 1\n    return ntot\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_morton_neighbors_refined(mi1, mi2, max_index1, max_index2, periodic, nn):\n    cdef int i\n    cdef np.uint32_t ntot\n    cdef np.ndarray[np.uint32_t, ndim=2] index = np.zeros((2*nn+1,3), dtype='uint32')\n    cdef np.ndarray[np.uint64_t, ndim=2] ind1_n = np.zeros((2*nn+1,3), dtype='uint64')\n    cdef np.ndarray[np.uint64_t, ndim=2] ind2_n = np.zeros((2*nn+1,3), dtype='uint64')\n    cdef np.ndarray[np.uint64_t, ndim=1] neighbors1 = np.zeros((2*nn+1)**3, dtype='uint64')\n    cdef np.ndarray[np.uint64_t, ndim=1] neighbors2 = np.zeros((2*nn+1)**3, dtype='uint64')\n    cdef bint periodicity[3]\n    if periodic:\n        for i in range(3): periodicity[i] = 1\n    else:\n        for i in range(3): periodicity[i] = 0\n    ntot = morton_neighbors_refined(mi1, mi2, max_index1, max_index2,\n                                    periodicity, nn,\n                                    index, ind1_n, ind2_n,\n                                    neighbors1, neighbors2)\n    return np.resize(neighbors1, (ntot,)), np.resize(neighbors2, (ntot,))\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.uint32_t morton_neighbors_refined(np.uint64_t mi1, np.uint64_t mi2,\n                                          np.uint64_t max_index1, np.uint64_t max_index2,\n                                          bint periodicity[3], np.uint32_t nn,\n                                          np.uint32_t[:,:] index,\n                                          np.uint64_t[:,:] ind1_n,\n                                          np.uint64_t[:,:] ind2_n,\n                                          np.uint64_t[:] neighbors1,\n                                          np.uint64_t[:] neighbors2):\n    cdef np.uint32_t ntot = 0\n    cdef np.uint64_t ind1[3]\n    cdef np.uint64_t ind2[3]\n    cdef np.uint32_t count[3]\n    cdef np.uint32_t origin[3]\n    cdef np.int64_t adv, maj, rem, adv1\n    cdef int i, j, k, ii, ij, ik\n    for i in range(3):\n        count[i] = 0\n        origin[i] = 0\n    # Get indices\n    decode_morton_64bit(mi1,ind1)\n    decode_morton_64bit(mi2,ind2)\n    # Determine which directions are valid\n    for j,i in enumerate(range(-nn,(nn+1))):\n        if i == 0:\n            for k in range(3):\n                ind1_n[j,k] = ind1[k]\n                ind2_n[j,k] = ind2[k]\n                index[count[k],k] = j\n                origin[k] = count[k]\n                count[k] += 1\n        else:\n            for k in range(3):\n                adv = <np.int64_t>(ind2[k] + i)\n                maj = adv / (<np.int64_t>max_index2)\n                rem = adv % (<np.int64_t>max_index2)\n                if adv < 0:\n                    adv1 = <np.int64_t>(ind1[k] + (maj-1))\n                    if adv1 < 0:\n                        if periodicity[k]:\n                            while adv1 < 0:\n                                adv1 += max_index1\n                            ind1_n[j,k] = <np.uint64_t>adv1\n                        else:\n                            continue\n                    else:\n                        ind1_n[j,k] = <np.uint64_t>adv1\n                    while adv < 0:\n                        adv += max_index2\n                    ind2_n[j,k] = <np.uint64_t>adv\n                elif adv >= max_index2:\n                    adv1 = <np.int64_t>(ind1[k] + maj)\n                    if adv1 >= max_index1:\n                        if periodicity[k]:\n                            ind1_n[j,k] = <np.uint64_t>(adv1 % <np.int64_t>max_index1)\n                        else:\n                            continue\n                    else:\n                        ind1_n[j,k] = <np.uint64_t>adv1\n                    ind2_n[j,k] = <np.uint64_t>rem\n                else:\n                    ind1_n[j,k] = ind1[k]\n                    ind2_n[j,k] = <np.uint64_t>(adv)\n                index[count[k],k] = j\n                count[k] += 1\n    # Iterate over ever combinations\n    for ii in range(count[0]):\n        i = index[ii,0]\n        for ij in range(count[1]):\n            j = index[ij,1]\n            for ik in range(count[2]):\n                k = index[ik,2]\n                if (ii != origin[0]) or (ij != origin[1]) or (ik != origin[2]):\n                    neighbors1[ntot] = encode_morton_64bit(ind1_n[i,0],\n                                                           ind1_n[j,1],\n                                                           ind1_n[k,2])\n                    neighbors2[ntot] = encode_morton_64bit(ind2_n[i,0],\n                                                           ind2_n[j,1],\n                                                           ind2_n[k,2])\n                    ntot += 1\n    return ntot\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef morton_neighbor_periodic(np.ndarray[np.uint64_t,ndim=1] p,\n                             list dim_list, list num_list,\n                             np.uint64_t max_index):\n    cdef np.uint64_t p1[3]\n    cdef int j, dim, num\n    for j in range(3):\n        p1[j] = np.uint64(p[j])\n    for dim,num in zip(dim_list,num_list):\n        p1[dim] = np.uint64((np.int64(p[dim]) + num) % max_index)\n    return np.int64(point_to_morton(p1))\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef morton_neighbor_bounded(np.ndarray[np.uint64_t,ndim=1] p,\n                            list dim_list, list num_list,\n                            np.uint64_t max_index):\n    cdef np.int64_t x\n    cdef np.uint64_t p1[3]\n    cdef int j, dim, num\n    for j in range(3):\n        p1[j] = np.uint64(p[j])\n    for dim,num in zip(dim_list,num_list):\n        x = np.int64(p[dim]) + num\n        if (x >= 0) and (x < max_index):\n            p1[dim] = np.uint64(x)\n        else:\n            return np.int64(-1)\n    return np.int64(point_to_morton(p1))\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef morton_neighbor(np.ndarray[np.uint64_t,ndim=1] p,\n                    list dim_list, list num_list,\n                    np.uint64_t max_index, periodic = False):\n    if periodic:\n        return morton_neighbor_periodic(p, dim_list, num_list, max_index)\n    else:\n        return morton_neighbor_bounded(p, dim_list, num_list, max_index)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_morton_neighbors(np.ndarray[np.uint64_t,ndim=1] mi,\n                         int order = ORDER_MAX, periodic = False):\n    \"\"\"Returns array of neighboring morton indices\"\"\"\n    # Declare\n    cdef int i, j, k, l, n\n    cdef np.uint64_t max_index\n    cdef np.ndarray[np.uint64_t, ndim=2] p\n    cdef np.int64_t nmi\n    cdef np.ndarray[np.uint64_t, ndim=1] mi_neighbors\n    p = get_morton_points(mi)\n    mi_neighbors = np.zeros(26*mi.shape[0], 'uint64')\n    n = 0\n    max_index = np.int64(1 << order)\n    # Define function\n    if periodic:\n        fneighbor = morton_neighbor_periodic\n    else:\n        fneighbor = morton_neighbor_bounded\n    for i in range(mi.shape[0]):\n        for j in range(3):\n            # +1 in dimension j\n            nmi = fneighbor(p[i,:],[j],[+1],max_index)\n            if nmi > 0:\n                mi_neighbors[n] = np.uint64(nmi)\n                n+=1\n                # +/- in dimension k\n                for k in range(j+1,3):\n                    # +1 in dimension k\n                    nmi = fneighbor(p[i,:],[j,k],[+1,+1],max_index)\n                    if nmi > 0:\n                        mi_neighbors[n] = np.uint64(nmi)\n                        n+=1\n                        # +/- in dimension l\n                        for l in range(k+1,3):\n                            nmi = fneighbor(p[i,:],[j,k,l],[+1,+1,+1],max_index)\n                            if nmi > 0:\n                                mi_neighbors[n] = np.uint64(nmi)\n                                n+=1\n                            nmi = fneighbor(p[i,:],[j,k,l],[+1,+1,-1],max_index)\n                            if nmi > 0:\n                                mi_neighbors[n] = np.uint64(nmi)\n                                n+=1\n                    # -1 in dimension k\n                    nmi = fneighbor(p[i,:],[j,k],[+1,-1],max_index)\n                    if nmi > 0:\n                        mi_neighbors[n] = np.uint64(nmi)\n                        n+=1\n                        # +/- in dimension l\n                        for l in range(k+1,3):\n                            nmi = fneighbor(p[i,:],[j,k,l],[+1,-1,+1],max_index)\n                            if nmi > 0:\n                                mi_neighbors[n] = np.uint64(nmi)\n                                n+=1\n                            nmi = fneighbor(p[i,:],[j,k,l],[+1,-1,-1],max_index)\n                            if nmi > 0:\n                                mi_neighbors[n] = np.uint64(nmi)\n                                n+=1\n            # -1 in dimension j\n            nmi = fneighbor(p[i,:],[j],[-1],max_index)\n            if nmi > 0:\n                mi_neighbors[n] = np.uint64(nmi)\n                n+=1\n                # +/- in dimension k\n                for k in range(j+1,3):\n                    # +1 in dimension k\n                    nmi = fneighbor(p[i,:],[j,k],[-1,+1],max_index)\n                    if nmi > 0:\n                        mi_neighbors[n] = np.uint64(nmi)\n                        n+=1\n                        # +/- in dimension l\n                        for l in range(k+1,3):\n                            nmi = fneighbor(p[i,:],[j,k,l],[-1,+1,+1],max_index)\n                            if nmi > 0:\n                                mi_neighbors[n] = np.uint64(nmi)\n                                n+=1\n                            nmi = fneighbor(p[i,:],[j,k,l],[-1,+1,-1],max_index)\n                            if nmi > 0:\n                                mi_neighbors[n] = np.uint64(nmi)\n                                n+=1\n                    # -1 in dimension k\n                    nmi = fneighbor(p[i,:],[j,k],[-1,-1],max_index)\n                    if nmi > 0:\n                        mi_neighbors[n] = np.uint64(nmi)\n                        n+=1\n                        # +/- in dimension l\n                        for l in range(k+1,3):\n                            nmi = fneighbor(p[i,:],[j,k,l],[-1,-1,+1],max_index)\n                            if nmi > 0:\n                                mi_neighbors[n] = np.uint64(nmi)\n                                n+=1\n                            nmi = fneighbor(p[i,:],[j,k,l],[-1,-1,-1],max_index)\n                            if nmi > 0:\n                                mi_neighbors[n] = np.uint64(nmi)\n                                n+=1\n    mi_neighbors = np.resize(mi_neighbors,(n,))\n    return np.unique(np.hstack([mi,mi_neighbors]))\n\ndef ifrexp_cy(np.float64_t x):\n    cdef np.int64_t e, m\n    m = ifrexp(x, &e)\n    return m,e\n\ndef msdb_cy(np.int64_t a, np.int64_t b):\n    return msdb(a,b)\n\ndef msdb_cy(np.int64_t a, np.int64_t b):\n    return msdb(a,b)\n\ndef xor_msb_cy(np.float64_t a, np.float64_t b):\n    return xor_msb(a,b)\n\ndef morton_qsort_swap(np.ndarray[np.uint64_t, ndim=1] ind,\n                      np.uint64_t a, np.uint64_t b):\n    # http://www.geeksforgeeks.org/iterative-quick-sort/\n    cdef np.int64_t t = ind[a]\n    ind[a] = ind[b]\n    ind[b] = t\n\ndef morton_qsort_partition(np.ndarray[cython.floating, ndim=2] pos,\n                           np.int64_t l, np.int64_t h,\n                           np.ndarray[np.uint64_t, ndim=1] ind,\n                           use_loop = False):\n    # Initialize\n    cdef int k\n    cdef np.int64_t i, j\n    cdef np.float64_t ppos[3]\n    cdef np.float64_t ipos[3]\n    cdef np.uint64_t done, pivot\n    if use_loop:\n        # http://www.geeksforgeeks.org/iterative-quick-sort/\n        # A bit slower\n        # Set starting point & pivot\n        i = (l - 1)\n        for k in range(3):\n            ppos[k] = pos[ind[h],k]\n        # Loop over array moving ind for points smaller than pivot to front\n        for j in range(l, h):\n            for k in range(3):\n                ipos[k] = pos[ind[j],k]\n            if compare_floats_morton(ipos,ppos):\n                i+=1\n                morton_qsort_swap(ind,i,j)\n        # Swap the pivot to the midpoint in the partition\n        i+=1\n        morton_qsort_swap(ind,i,h)\n        return i\n    else:\n        # Set starting point & pivot\n        i = l-1\n        j = h\n        done = 0\n        pivot = ind[h]\n        for k in range(3):\n            ppos[k] = pos[pivot,k]\n        # Loop until entire array processed\n        while not done:\n            # Process bottom\n            while not done:\n                i+=1\n                if i == j:\n                    done = 1\n                    break\n                for k in range(3):\n                    ipos[k] = pos[ind[i],k]\n                if compare_floats_morton(ppos,ipos):\n                    ind[j] = ind[i]\n                    break\n            # Process top\n            while not done:\n                j-=1\n                if j == i:\n                    done = 1\n                    break\n                for k in range(3):\n                    ipos[k] = pos[ind[j],k]\n                if compare_floats_morton(ipos,ppos):\n                    ind[i] = ind[j]\n                    break\n        ind[j] = pivot\n    return j\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef morton_qsort_recursive(np.ndarray[cython.floating, ndim=2] pos,\n                           np.int64_t l, np.int64_t h,\n                           np.ndarray[np.uint64_t, ndim=1] ind,\n                           use_loop = False):\n    # http://www.geeksforgeeks.org/iterative-quick-sort/\n    cdef np.int64_t p\n    if (l < h):\n        p = morton_qsort_partition(pos, l, h, ind, use_loop=use_loop)\n        morton_qsort_recursive(pos, l, p-1, ind, use_loop=use_loop)\n        morton_qsort_recursive(pos, p+1, h, ind, use_loop=use_loop)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef morton_qsort_iterative(np.ndarray[cython.floating, ndim=2] pos,\n                           np.int64_t l, np.int64_t h,\n                           np.ndarray[np.uint64_t, ndim=1] ind,\n                           use_loop = False):\n    # http://www.geeksforgeeks.org/iterative-quick-sort/\n    # Auxiliary stack\n    cdef np.ndarray[np.int64_t, ndim=1] stack = np.zeros(h-l+1, dtype=np.int64)\n    cdef np.int64_t top = -1\n    cdef np.int64_t p\n    top+=1\n    stack[top] = l\n    top+=1\n    stack[top] = h\n    # Pop from stack until it's empty\n    while (top >= 0):\n        # Get next set\n        h = stack[top]\n        top-=1\n        l = stack[top]\n        top-=1\n        # Partition\n        p = morton_qsort_partition(pos, l, h, ind, use_loop=use_loop)\n        # Add left partition to the stack\n        if (p-1) > l:\n            top+=1\n            stack[top] = l\n            top+=1\n            stack[top] = p - 1\n        # Add right partition to the stack\n        if (p+1) < h:\n            top+=1\n            stack[top] = p + 1\n            top+=1\n            stack[top] = h\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef morton_qsort(np.ndarray[cython.floating, ndim=2] pos,\n                 np.int64_t l, np.int64_t h,\n                 np.ndarray[np.uint64_t, ndim=1] ind,\n                 recursive = False,\n                 use_loop = False):\n    #get_morton_argsort1(pos,l,h,ind)\n    if recursive:\n        morton_qsort_recursive(pos,l,h,ind,use_loop=use_loop)\n    else:\n        morton_qsort_iterative(pos,l,h,ind,use_loop=use_loop)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_morton_argsort1(np.ndarray[cython.floating, ndim=2] pos,\n                        np.int64_t start, np.int64_t end,\n                        np.ndarray[np.uint64_t, ndim=1] ind):\n    # Return if only one position selected\n    if start >= end: return\n    # Initialize\n    cdef np.int64_t top\n    top = morton_qsort_partition(pos,start,end,ind)\n    # Do remaining parts on either side of pivot, sort side first\n    if (top-1-start < end-(top+1)):\n        get_morton_argsort1(pos,start,top-1,ind)\n        get_morton_argsort1(pos,top+1,end,ind)\n    else:\n        get_morton_argsort1(pos,top+1,end,ind)\n        get_morton_argsort1(pos,start,top-1,ind)\n    return\n\ndef compare_morton(np.ndarray[cython.floating, ndim=1] p0, np.ndarray[cython.floating, ndim=1] q0):\n    cdef np.float64_t p[3]\n    cdef np.float64_t q[3]\n    # cdef np.int64_t iep,ieq,imp,imq\n    cdef int j\n    for j in range(3):\n        p[j] = p0[j]\n        q[j] = q0[j]\n        # imp = ifrexp(p[j],&iep)\n        # imq = ifrexp(q[j],&ieq)\n        # print(j,p[j],q[j],xor_msb(p[j],q[j]),'m=',imp,imq,'e=',iep,ieq)\n    return compare_floats_morton(p,q)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.int64_t position_to_morton(np.ndarray[cython.floating, ndim=1] pos_x,\n                        np.ndarray[cython.floating, ndim=1] pos_y,\n                        np.ndarray[cython.floating, ndim=1] pos_z,\n                        np.float64_t dds[3], np.float64_t DLE[3],\n                        np.float64_t DRE[3],\n                        np.ndarray[np.uint64_t, ndim=1] ind,\n                        int filter):\n    cdef np.uint64_t ii[3]\n    cdef np.float64_t p[3]\n    cdef np.int64_t i, j, use\n    cdef np.uint64_t DD[3]\n    cdef np.uint64_t FLAG = ~(<np.uint64_t>0)\n    for i in range(3):\n        DD[i] = <np.uint64_t> ((DRE[i] - DLE[i]) / dds[i])\n    for i in range(pos_x.shape[0]):\n        use = 1\n        p[0] = <np.float64_t> pos_x[i]\n        p[1] = <np.float64_t> pos_y[i]\n        p[2] = <np.float64_t> pos_z[i]\n        for j in range(3):\n            if p[j] < DLE[j] or p[j] > DRE[j]:\n                if filter == 1:\n                    # We only allow 20 levels, so this is inaccessible\n                    use = 0\n                    break\n                return i\n            ii[j] = <np.uint64_t> ((p[j] - DLE[j])/dds[j])\n            ii[j] = i64clip(ii[j], 0, DD[j] - 1)\n        if use == 0:\n            ind[i] = FLAG\n            continue\n        ind[i] = encode_morton_64bit(ii[0],ii[1],ii[2])\n    return pos_x.shape[0]\n\ndef compute_morton(np.ndarray pos_x, np.ndarray pos_y, np.ndarray pos_z,\n                   domain_left_edge, domain_right_edge, filter_bbox = False,\n                   order = ORDER_MAX):\n    cdef int i\n    cdef int filter\n    if filter_bbox:\n        filter = 1\n    else:\n        filter = 0\n    cdef np.float64_t dds[3]\n    cdef np.float64_t DLE[3]\n    cdef np.float64_t DRE[3]\n    for i in range(3):\n        DLE[i] = domain_left_edge[i]\n        DRE[i] = domain_right_edge[i]\n        dds[i] = (DRE[i] - DLE[i]) / (1 << order)\n    cdef np.ndarray[np.uint64_t, ndim=1] ind\n    ind = np.zeros(pos_x.shape[0], dtype=\"uint64\")\n    cdef np.int64_t rv\n    if pos_x.dtype == np.float32:\n        rv = position_to_morton[np.float32_t](\n                pos_x, pos_y, pos_z, dds, DLE, DRE, ind,\n                filter)\n    elif pos_x.dtype == np.float64:\n        rv = position_to_morton[np.float64_t](\n                pos_x, pos_y, pos_z, dds, DLE, DRE, ind,\n                filter)\n    else:\n        print(\"Could not identify dtype.\", pos_x.dtype)\n        raise NotImplementedError\n    if rv < pos_x.shape[0]:\n        mis = (pos_x.min(), pos_y.min(), pos_z.min())\n        mas = (pos_x.max(), pos_y.max(), pos_z.max())\n        raise YTDomainOverflow(mis, mas,\n                               domain_left_edge, domain_right_edge)\n    return ind\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef dist(np.ndarray[np.float64_t, ndim=1] p0, np.ndarray[np.float64_t, ndim=1] q0):\n    cdef int j\n    cdef np.float64_t p[3]\n    cdef np.float64_t q[3]\n    for j in range(3):\n        p[j] = p0[j]\n        q[j] = q0[j]\n    return euclidean_distance(p,q)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef dist_to_box(np.ndarray[np.float64_t, ndim=1] p,\n                np.ndarray[np.float64_t, ndim=1] cbox,\n                np.float64_t rbox):\n    cdef int j\n    cdef np.float64_t d = 0.0\n    for j in range(3):\n        d+= max((cbox[j]-rbox)-p[j],0.0,p[j]-(cbox[j]+rbox))**2\n    return np.sqrt(d)\n\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef solution_radius(np.ndarray[np.float64_t, ndim=2] P, int k, np.uint64_t i,\n                    np.ndarray[np.uint64_t, ndim=1] idx, int order,\n                    np.ndarray[np.float64_t, ndim=1] DLE,\n                    np.ndarray[np.float64_t, ndim=1] DRE):\n    c = np.zeros(3, dtype=np.float64)\n    return quadtree_box(P[i,:],P[idx[k-1],:],order,DLE,DRE,c)\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef knn_direct(np.ndarray[np.float64_t, ndim=2] P, np.uint64_t k, np.uint64_t i,\n               np.ndarray[np.uint64_t, ndim=1] idx, return_dist = False,\n               return_rad = False):\n    \"\"\"Directly compute the k nearest neighbors by sorting on distance.\n\n    Args:\n        P (np.ndarray): (N,d) array of points to search sorted by Morton order.\n        k (int): number of nearest neighbors to find.\n        i (int): index of point that nearest neighbors should be found for.\n        idx (np.ndarray): indices of points from P to be considered.\n        return_dist (Optional[bool]): If True, distances to the k nearest\n            neighbors are also returned (in order of proximity).\n            (default = False)\n        return_rad (Optional[bool]): If True, distance to farthest nearest\n            neighbor is also returned. This is set to False if return_dist is\n            True. (default = False)\n\n    Returns:\n        np.ndarray: Indices of k nearest neighbors to point i.\n\n    \"\"\"\n    cdef int j,m\n    cdef np.int64_t[:] sort_fwd\n    cdef np.float64_t[:] ipos\n    cdef np.float64_t[:] jpos\n    cdef np.float64_t[:] dist = np.zeros(len(idx), dtype='float64')\n    ipos = np.zeros(3)\n    jpos = np.zeros(3)\n    for m in range(3):\n        ipos[m] = P[i,m]\n    for j in range(len(idx)):\n        for m in range(3):\n            jpos[m] = P[idx[j],m]\n        dist[j] = euclidean_distance(ipos, jpos)\n    # casting to uint64 for compatibility with 32 bits systems\n    # see https://github.com/yt-project/yt/issues/3656\n    sort_fwd = np.argsort(dist, kind='mergesort')[:k].astype(np.int64, copy=False)\n    if return_dist:\n        return np.array(idx)[sort_fwd], np.array(dist)[sort_fwd]\n    elif return_rad:\n        return np.array(idx)[sort_fwd], np.array(dist)[sort_fwd][k-1]\n    else:\n        return np.array(idx)[sort_fwd]\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef quadtree_box(np.ndarray[np.float64_t, ndim=1] p,\n                 np.ndarray[np.float64_t, ndim=1] q, int order,\n                 np.ndarray[np.float64_t, ndim=1] DLE,\n                 np.ndarray[np.float64_t, ndim=1] DRE,\n                 np.ndarray[np.float64_t, ndim=1] c):\n    # Declare & transfer values to ctypes\n    cdef int j\n    cdef np.float64_t ppos[3]\n    cdef np.float64_t qpos[3]\n    cdef np.float64_t rbox\n    cdef np.float64_t cbox[3]\n    cdef np.float64_t DLE1[3]\n    cdef np.float64_t DRE1[3]\n    for j in range(3):\n        ppos[j] = p[j]\n        qpos[j] = q[j]\n        DLE1[j] = DLE[j]\n        DRE1[j] = DRE[j]\n    # Get smallest box containing p & q\n    rbox = smallest_quadtree_box(ppos,qpos,order,DLE1,DRE1,\n                                 &cbox[0],&cbox[1],&cbox[2])\n    # Transfer values to python array\n    for j in range(3):\n        c[j] = cbox[j]\n    return rbox\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef csearch_morton(np.ndarray[np.float64_t, ndim=2] P, int k, np.uint64_t i,\n                   np.ndarray[np.uint64_t, ndim=1] Ai,\n                   np.uint64_t l, np.uint64_t h, int order,\n                   np.ndarray[np.float64_t, ndim=1] DLE,\n                   np.ndarray[np.float64_t, ndim=1] DRE, int nu = 4):\n    \"\"\"Expand search concentrically to determine set of k nearest neighbors for\n    point i.\n\n    Args:\n        P (np.ndarray): (N,d) array of points to search sorted by Morton order.\n        k (int): number of nearest neighbors to find.\n        i (int): index of point that nearest neighbors should be found for.\n        Ai (np.ndarray): (N,k) array of partial nearest neighbor indices.\n        l (int): index of lowest point to consider in addition to Ai.\n        h (int): index of highest point to consider in addition to Ai.\n        order (int): Maximum depth that Morton order quadtree should reach.\n        DLE (np.float64[3]): 3 floats defining domain lower bounds in each dim.\n        DRE (np.float64[3]): 3 floats defining domain upper bounds in each dim.\n        nu (int): minimum number of points before a direct knn search is\n            performed. (default = 4)\n\n    Returns:\n        np.ndarray: (N,k) array of nearest neighbor indices.\n\n    Raises:\n        ValueError: If l<i<h. l and h must be on the same side of i.\n\n    \"\"\"\n    cdef np.uint64_t m\n    # Make sure that h and l are both larger/smaller than i\n    if (l < i) and (h > i):\n        raise ValueError(\"Both l and h must be on the same side of i.\")\n    m = np.uint64((h + l)/2)\n    # New range is small enough to consider directly\n    if (h-l) < nu:\n        if m > i:\n            return knn_direct(P,k,i,np.hstack((Ai,np.arange(l,h+1,dtype=np.uint64))))\n        else:\n            return knn_direct(P,k,i,np.hstack((np.arange(l,h+1,dtype=np.uint64),Ai)))\n    # Add middle point\n    if m > i:\n        Ai,rad_Ai = knn_direct(P,k,i,np.hstack((Ai,m)).astype(np.uint64),return_rad=True)\n    else:\n        Ai,rad_Ai = knn_direct(P,k,i,np.hstack((m,Ai)).astype(np.uint64),return_rad=True)\n    cbox_sol = np.zeros(3,dtype=np.float64)\n    rbox_sol = quadtree_box(P[i,:],P[Ai[k-1],:],order,DLE,DRE,cbox_sol)\n    # Return current solution if hl box is outside current solution's box\n    # Uses actual box\n    cbox_hl = np.zeros(3,dtype=np.float64)\n    rbox_hl = quadtree_box(P[l,:],P[h,:],order,DLE,DRE,cbox_hl)\n    if dist_to_box(cbox_sol,cbox_hl,rbox_hl) >= 1.5*rbox_sol:\n        print('{} outside: rad = {}, rbox = {}, dist = {}'.format(m,rad_Ai,rbox_sol,dist_to_box(P[i,:],cbox_hl,rbox_hl)))\n        return Ai\n    # Expand search to lower/higher indices as needed\n    if i < m: # They are already sorted...\n        Ai = csearch_morton(P,k,i,Ai,l,m-1,order,DLE,DRE,nu=nu)\n        if compare_morton(P[m,:],P[i,:]+dist(P[i,:],P[Ai[k-1],:])):\n            Ai = csearch_morton(P,k,i,Ai,m+1,h,order,DLE,DRE,nu=nu)\n    else:\n        Ai = csearch_morton(P,k,i,Ai,m+1,h,order,DLE,DRE,nu=nu)\n        if compare_morton(P[i,:]-dist(P[i,:],P[Ai[k-1],:]),P[m,:]):\n            Ai = csearch_morton(P,k,i,Ai,l,m-1,order,DLE,DRE,nu=nu)\n    return Ai\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef knn_morton(np.ndarray[np.float64_t, ndim=2] P0, int k, np.uint64_t i0,\n               float c = 1.0, int nu = 4, issorted = False, int order = ORDER_MAX,\n               np.ndarray[np.float64_t, ndim=1] DLE = np.zeros(3,dtype=np.float64),\n               np.ndarray[np.float64_t, ndim=1] DRE = np.zeros(3,dtype=np.float64)):\n    \"\"\"Get the indices of the k nearest neighbors to point i.\n\n    Args:\n        P (np.ndarray): (N,d) array of points to search.\n        k (int): number of nearest neighbors to find for each point in P.\n        i (np.uint64): index of point to find neighbors for.\n        c (float): factor determining how many indices before/after i are used\n            in the initial search (i-c*k to i+c*k, default = 1.0)\n        nu (int): minimum number of points before a direct knn search is\n            performed. (default = 4)\n        issorted (Optional[bool]): if True, P is assumed to be sorted already\n            according to Morton order.\n        order (int): Maximum depth that Morton order quadtree should reach.\n            If not provided, ORDER_MAX is used.\n        DLE (np.ndarray): (d,) array of domain lower bounds in each dimension.\n            If not provided, this is determined from the points.\n        DRE (np.ndarray): (d,) array of domain upper bounds in each dimension.\n            If not provided, this is determined from the points.\n\n    Returns:\n        np.ndarray: (N,k) indices of k nearest neighbors for each point in P.\n\"\"\"\n    cdef int j\n    cdef np.uint64_t i\n    cdef np.int64_t N = P0.shape[0]\n    cdef np.ndarray[np.float64_t, ndim=2] P\n    cdef np.ndarray[np.uint64_t, ndim=1] sort_fwd = np.arange(N,dtype=np.uint64)\n    cdef np.ndarray[np.uint64_t, ndim=1] sort_rev = np.arange(N,dtype=np.uint64)\n    cdef np.ndarray[np.uint64_t, ndim=1] Ai\n    cdef np.int64_t idxmin, idxmax, u, l\n    cdef np.uint64_t I\n    # Sort if necessary\n    if issorted:\n        P = P0\n        i = i0\n    else:\n        morton_qsort(P0,0,N-1,sort_fwd)\n        sort_rev = np.argsort(sort_fwd).astype(np.uint64)\n        P = P0[sort_fwd,:]\n        i = sort_rev[i0]\n    # Check domain and set if singular\n    for j in range(3):\n        if DLE[j] == DRE[j]:\n            DLE[j] = min(P[:,j])\n            DRE[j] = max(P[:,j])\n    # Get initial guess bassed on position in z-order\n    idxmin = <np.int64_t>max(i-c*k, 0)\n    idxmax = <np.int64_t>min(i+c*k, N-1)\n    Ai = np.hstack((np.arange(idxmin,i,dtype=np.uint64),\n                    np.arange(i+1,idxmax+1,dtype=np.uint64)))\n    Ai,rad_Ai = knn_direct(P,k,i,Ai,return_rad=True)\n    # Get radius of solution\n    cbox_Ai = np.zeros(3,dtype=np.float64)\n    rbox_Ai = quadtree_box(P[i,:],P[Ai[k-1],:],order,DLE,DRE,cbox_Ai)\n    rad_Ai = rbox_Ai\n    # Extend upper bound to match lower bound\n    if idxmax < (N-1):\n        if compare_morton(P[i,:]+rad_Ai,P[idxmax,:]):\n            u = i\n        else:\n            I = 1\n            while (idxmax+(2**I) < N) and compare_morton(P[idxmax+(2**I),:],P[i,:]+rad_Ai):\n                I+=1\n            u = min(idxmax+(2**I),N-1)\n            Ai = csearch_morton(P,k,i,Ai,min(idxmax+1,N-1),u,order,DLE,DRE,nu=nu)\n    else:\n        u = idxmax\n    # Extend lower bound to match upper bound\n    if idxmin > 0:\n        if compare_morton(P[idxmin,:],P[i,:]-rad_Ai):\n            l = i\n        else:\n            I = 1\n            while (idxmin-(2**I) >= 0) and compare_morton(P[i,:]-rad_Ai,P[idxmin-(2**I),:]):\n                I+=1\n            l = max(idxmin-(2**I),0)\n            Ai = csearch_morton(P,k,i,Ai,l,max(idxmin-1,0),order,DLE,DRE,nu=nu)\n    else:\n        l = idxmin\n    # Return indices of neighbors in the correct order\n    if issorted:\n        return Ai\n    else:\n        return sort_fwd[Ai]\n\ncdef struct PointSet\ncdef struct PointSet:\n    int count\n    # First index is point index, second is xyz\n    np.float64_t points[2][3]\n    PointSet *next\n\ncdef inline void get_intersection(np.float64_t p0[3], np.float64_t p1[3],\n                                  int ax, np.float64_t coord, PointSet *p):\n    cdef np.float64_t vec[3]\n    cdef np.float64_t t\n    for j in range(3):\n        vec[j] = p1[j] - p0[j]\n    if vec[ax] == 0.0:\n        return  # bail if the line is in the plane\n    t = (coord - p0[ax])/vec[ax]\n    # We know that if they're on opposite sides, it has to intersect.  And we\n    # won't get called otherwise.\n    for j in range(3):\n        p.points[p.count][j] = p0[j] + vec[j] * t\n    p.count += 1\n\n@cython.cdivision(True)\ndef triangle_plane_intersect(int ax, np.float64_t coord,\n                             np.ndarray[np.float64_t, ndim=3] triangles):\n    cdef np.float64_t p0[3]\n    cdef np.float64_t p1[3]\n    cdef np.float64_t p2[3]\n    cdef np.float64_t E0[3]\n    cdef np.float64_t E1[3]\n    cdef np.float64_t tri_norm[3]\n    cdef np.float64_t plane_norm[3]\n    cdef np.float64_t dp\n    cdef int i, j, k, count, ntri, nlines\n    nlines = 0\n    ntri = triangles.shape[0]\n    cdef PointSet *first\n    cdef PointSet *last\n    cdef PointSet *points\n    first = last = points = NULL\n    for i in range(ntri):\n        count = 0\n\n        # Here p0 holds the triangle's zeroth node coordinates,\n        # p1 holds the first node's coordinates, and\n        # p2 holds the second node's coordinates\n        for j in range(3):\n            p0[j] = triangles[i, 0, j]\n            p1[j] = triangles[i, 1, j]\n            p2[j] = triangles[i, 2, j]\n            plane_norm[j] = 0.0\n        plane_norm[ax] = 1.0\n        subtract(p1, p0, E0)\n        subtract(p2, p0, E1)\n        cross(E0, E1, tri_norm)\n        dp = dot(tri_norm, plane_norm)\n        dp /= L2_norm(tri_norm)\n        # Skip if triangle is close to being parallel to plane.\n        if (fabs(dp) > 0.995):\n            continue\n\n        # Now for each line segment (01, 12, 20) we check to see how many cross\n        # the coordinate of the slice.\n        # Here, the components of p2 are either +1 or -1 depending on whether the\n        # node's coordinate corresponding to the slice axis is greater than the\n        # coordinate of the slice. p2[0] -> node 0; p2[1] -> node 1; p2[2] -> node2\n        for j in range(3):\n            # Add 0 so that any -0s become +0s. Necessary for consistent determination\n            # of plane intersection\n            p2[j] = copysign(1.0, triangles[i, j, ax] - coord + 0)\n        if p2[0] * p2[1] < 0: count += 1\n        if p2[1] * p2[2] < 0: count += 1\n        if p2[2] * p2[0] < 0: count += 1\n        if count == 2:\n            nlines += 1\n        elif count == 3:\n            raise RuntimeError(\"It should be geometrically impossible for a plane to\"\n                               \"to intersect all three legs of a triangle. Please contact\"\n                               \"yt developers with your mesh\")\n        else:\n            continue\n        points = <PointSet *> malloc(sizeof(PointSet))\n        points.count = 0\n        points.next = NULL\n\n        # Here p0 and p1 again hold node coordinates\n        if p2[0] * p2[1] < 0:\n            # intersection of 01 triangle segment with plane\n            for j in range(3):\n                p0[j] = triangles[i, 0, j]\n                p1[j] = triangles[i, 1, j]\n            get_intersection(p0, p1, ax, coord, points)\n        if p2[1] * p2[2] < 0:\n            # intersection of 12 triangle segment with plane\n            for j in range(3):\n                p0[j] = triangles[i, 1, j]\n                p1[j] = triangles[i, 2, j]\n            get_intersection(p0, p1, ax, coord, points)\n        if p2[2] * p2[0] < 0:\n            # intersection of 20 triangle segment with plane\n            for j in range(3):\n                p0[j] = triangles[i, 2, j]\n                p1[j] = triangles[i, 0, j]\n            get_intersection(p0, p1, ax, coord, points)\n        if last != NULL:\n            last.next = points\n        if first == NULL:\n            first = points\n        last = points\n\n    points = first\n    cdef np.ndarray[np.float64_t, ndim=3] line_segments\n    line_segments = np.empty((nlines, 2, 3), dtype=\"float64\")\n    k = 0\n    while points != NULL:\n        for j in range(3):\n            line_segments[k, 0, j] = points.points[0][j]\n            line_segments[k, 1, j] = points.points[1][j]\n        k += 1\n        last = points\n        points = points.next\n        free(last)\n    return line_segments\n"
  },
  {
    "path": "yt/utilities/lib/grid_traversal.pxd",
    "content": "\"\"\"\nDefinitions for the traversal code\n\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\nfrom .image_samplers cimport ImageSampler\nfrom .volume_container cimport VolumeContainer, vc_index, vc_pos_index\n\nctypedef void sampler_function(\n                VolumeContainer *vc,\n                np.float64_t v_pos[3],\n                np.float64_t v_dir[3],\n                np.float64_t enter_t,\n                np.float64_t exit_t,\n                int index[3],\n                void *data) noexcept nogil\n\n#-----------------------------------------------------------------------------\n# walk_volume(VolumeContainer *vc,  np.float64_t v_pos[3], np.float64_t v_dir[3], sampler_function *sample,\n#             void *data, np.float64_t *return_t = NULL, np.float64_t max_t = 1.0)\n#    vc        VolumeContainer*  : Pointer to the volume container to be traversed.\n#    v_pos     np.float64_t[3]   : The x,y,z coordinates of the ray's origin.\n#    v_dir     np.float64_t[3]   : The x,y,z coordinates of the ray's direction.\n#    sample    sampler_function* : Pointer to the sampler function to be used.\n#    return_t  np.float64_t*     : Pointer to the final value of t that is still inside the volume container. Defaulted to NULL.\n#    max_t     np.float64_t      : The maximum value of t that the ray is allowed to travel. Defaulted to 1.0 (no restriction).\n#\n#    Note: 't' is not time here. Rather, it is a factor representing the difference between the initial point 'v_pos'\n#             and the end point, which we might call v_end. It is scaled such that v_pos + v * t = v_pos at t = 0.0, and\n#             v_end at t = 1.0. Therefore, if max_t is set to 1.0, there is no restriction on t.\n#\n# Written by the yt Development Team.\n# Encapsulates the Amanatides & Woo \"Fast Traversal Voxel Algorithm\" to walk over a volume container 'vc'\n# The function occurs in two phases, initialization and traversal.\n# See: https://www.researchgate.net/publication/2611491_A_Fast_Voxel_Traversal_Algorithm_for_Ray_Tracing\n# Returns: The number of voxels hit during the traversal phase. If the traversal phase is not reached, returns 0.\n#-----------------------------------------------------------------------------\ncdef int walk_volume(VolumeContainer *vc,\n                     np.float64_t v_pos[3],\n                     np.float64_t v_dir[3],\n                     sampler_function *sampler,\n                     void *data,\n                     np.float64_t *return_t = *,\n                     np.float64_t max_t = *) noexcept nogil\n"
  },
  {
    "path": "yt/utilities/lib/grid_traversal.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS\n# distutils: language = c++\n# distutils: extra_compile_args = CPP14_FLAG\n# distutils: extra_link_args = CPP14_FLAG\n\"\"\"\nSimple integrators for the radiative transfer equation\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.math cimport atan2, cos, fabs, floor, sin, sqrt\n\nfrom yt.utilities.lib.fp_utils cimport fmin\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef int walk_volume(VolumeContainer *vc,\n                     np.float64_t v_pos[3],\n                     np.float64_t v_dir[3],\n                     sampler_function *sample,\n                     void *data,\n                     np.float64_t *return_t = NULL,\n                     np.float64_t max_t = 1.0) noexcept nogil:\n    cdef int cur_ind[3]\n    cdef int step[3]\n    cdef int x, y, i, hit, direction\n    cdef np.float64_t intersect_t = 1.1\n    cdef np.float64_t iv_dir[3]\n    cdef np.float64_t tmax[3]\n    cdef np.float64_t tdelta[3]\n    cdef np.float64_t exit_t = -1.0, enter_t = -1.0\n    cdef np.float64_t tl, temp_x, temp_y = -1\n    if max_t > 1.0: max_t = 1.0\n    direction = -1\n    if vc.left_edge[0] <= v_pos[0] and v_pos[0] < vc.right_edge[0] and \\\n       vc.left_edge[1] <= v_pos[1] and v_pos[1] < vc.right_edge[1] and \\\n       vc.left_edge[2] <= v_pos[2] and v_pos[2] < vc.right_edge[2]:\n        intersect_t = 0.0\n        direction = 3\n    for i in range(3):\n        if (v_dir[i] < 0):\n            step[i] = -1\n        elif (v_dir[i] == 0.0):\n            step[i] = 0\n            continue\n        else:\n            step[i] = 1\n        iv_dir[i] = 1.0/v_dir[i]\n        if direction == 3: continue\n        x = (i+1) % 3\n        y = (i+2) % 3\n        if step[i] > 0:\n            tl = (vc.left_edge[i] - v_pos[i])*iv_dir[i]\n        else:\n            tl = (vc.right_edge[i] - v_pos[i])*iv_dir[i]\n        temp_x = (v_pos[x] + tl*v_dir[x])\n        temp_y = (v_pos[y] + tl*v_dir[y])\n        if fabs(temp_x - vc.left_edge[x]) < 1e-10*vc.dds[x]:\n            temp_x = vc.left_edge[x]\n        elif fabs(temp_x - vc.right_edge[x]) < 1e-10*vc.dds[x]:\n            temp_x = vc.right_edge[x]\n        if fabs(temp_y - vc.left_edge[y]) < 1e-10*vc.dds[y]:\n            temp_y = vc.left_edge[y]\n        elif fabs(temp_y - vc.right_edge[y]) < 1e-10*vc.dds[y]:\n            temp_y = vc.right_edge[y]\n        if vc.left_edge[x] <= temp_x and temp_x <= vc.right_edge[x] and \\\n           vc.left_edge[y] <= temp_y and temp_y <= vc.right_edge[y] and \\\n           0.0 <= tl and tl < intersect_t:\n            direction = i\n            intersect_t = tl\n    if enter_t >= 0.0: intersect_t = enter_t\n    if not ((0.0 <= intersect_t) and (intersect_t < max_t)): return 0\n    for i in range(3):\n        # Two things have to be set inside this loop.\n        # cur_ind[i], the current index of the grid cell the ray is in\n        # tmax[i], the 't' until it crosses out of the grid cell\n        tdelta[i] = step[i] * iv_dir[i] * vc.dds[i]\n        if i == direction and step[i] > 0:\n            # Intersection with the left face in this direction\n            cur_ind[i] = 0\n        elif i == direction and step[i] < 0:\n            # Intersection with the right face in this direction\n            cur_ind[i] = vc.dims[i] - 1\n        else:\n            # We are somewhere in the middle of the face\n            temp_x = intersect_t * v_dir[i] + v_pos[i] # current position\n            temp_y = ((temp_x - vc.left_edge[i])*vc.idds[i])\n            # There are some really tough cases where we just within a couple\n            # least significant places of the edge, and this helps prevent\n            # killing the calculation through a segfault in those cases.\n            if -1 < temp_y < 0 and step[i] > 0:\n                temp_y = 0.0\n            elif vc.dims[i] - 1 < temp_y < vc.dims[i] and step[i] < 0:\n                temp_y = vc.dims[i] - 1\n            cur_ind[i] =  <int> (floor(temp_y))\n        if step[i] > 0:\n            temp_y = (cur_ind[i] + 1) * vc.dds[i] + vc.left_edge[i]\n        elif step[i] < 0:\n            temp_y = cur_ind[i] * vc.dds[i] + vc.left_edge[i]\n        tmax[i] = (temp_y - v_pos[i]) * iv_dir[i]\n        if step[i] == 0:\n            tmax[i] = 1e60\n    # We have to jumpstart our calculation\n    for i in range(3):\n        if cur_ind[i] == vc.dims[i] and step[i] >= 0:\n            return 0\n        if cur_ind[i] == -1 and step[i] <= -1:\n            return 0\n    enter_t = intersect_t\n    hit = 0\n    while 1:\n        hit += 1\n        if tmax[0] < tmax[1]:\n            if tmax[0] < tmax[2]:\n                i = 0\n            else:\n                i = 2\n        else:\n            if tmax[1] < tmax[2]:\n                i = 1\n            else:\n                i = 2\n        exit_t = fmin(tmax[i], max_t)\n        sample(vc, v_pos, v_dir, enter_t, exit_t, cur_ind, data)\n        cur_ind[i] += step[i]\n        enter_t = tmax[i]\n        tmax[i] += tdelta[i]\n        if cur_ind[i] < 0 or cur_ind[i] >= vc.dims[i] or enter_t >= max_t:\n            break\n    if return_t != NULL: return_t[0] = exit_t\n    return hit\n\ndef hp_pix2vec_nest(long nside, long ipix):\n    raise NotImplementedError\n    # cdef double v[3]\n    # healpix_interface.pix2vec_nest(nside, ipix, v)\n    # cdef np.ndarray[np.float64_t, ndim=1] tr = np.empty((3,), dtype='float64')\n    # tr[0] = v[0]\n    # tr[1] = v[1]\n    # tr[2] = v[2]\n    # return tr\n\ndef arr_pix2vec_nest(long nside,\n                     np.ndarray[np.int64_t, ndim=1] aipix):\n    raise NotImplementedError\n    # cdef int n = aipix.shape[0]\n    # cdef int i\n    # cdef double v[3]\n    # cdef long ipix\n    # cdef np.ndarray[np.float64_t, ndim=2] tr = np.zeros((n, 3), dtype='float64')\n    # for i in range(n):\n    #     ipix = aipix[i]\n    #     healpix_interface.pix2vec_nest(nside, ipix, v)\n    #     tr[i,0] = v[0]\n    #     tr[i,1] = v[1]\n    #     tr[i,2] = v[2]\n    # return tr\n\ndef hp_vec2pix_nest(long nside, double x, double y, double z):\n    raise NotImplementedError\n    # cdef double v[3]\n    # v[0] = x\n    # v[1] = y\n    # v[2] = z\n    # cdef long ipix\n    # healpix_interface.vec2pix_nest(nside, v, &ipix)\n    # return ipix\n\ndef arr_vec2pix_nest(long nside,\n                     np.ndarray[np.float64_t, ndim=1] x,\n                     np.ndarray[np.float64_t, ndim=1] y,\n                     np.ndarray[np.float64_t, ndim=1] z):\n    raise NotImplementedError\n    # cdef int n = x.shape[0]\n    # cdef int i\n    # cdef double v[3]\n    # cdef long ipix\n    # cdef np.ndarray[np.int64_t, ndim=1] tr = np.zeros(n, dtype='int64')\n    # for i in range(n):\n    #     v[0] = x[i]\n    #     v[1] = y[i]\n    #     v[2] = z[i]\n    #     healpix_interface.vec2pix_nest(nside, v, &ipix)\n    #     tr[i] = ipix\n    # return tr\n\ndef hp_pix2ang_nest(long nside, long ipnest):\n    raise NotImplementedError\n    # cdef double theta, phi\n    # healpix_interface.pix2ang_nest(nside, ipnest, &theta, &phi)\n    # return (theta, phi)\n\ndef arr_pix2ang_nest(long nside, np.ndarray[np.int64_t, ndim=1] aipnest):\n    raise NotImplementedError\n    # cdef int n = aipnest.shape[0]\n    # cdef int i\n    # cdef long ipnest\n    # cdef np.ndarray[np.float64_t, ndim=2] tr = np.zeros((n, 2), dtype='float64')\n    # cdef double theta, phi\n    # for i in range(n):\n    #     ipnest = aipnest[i]\n    #     healpix_interface.pix2ang_nest(nside, ipnest, &theta, &phi)\n    #     tr[i,0] = theta\n    #     tr[i,1] = phi\n    # return tr\n\ndef hp_ang2pix_nest(long nside, double theta, double phi):\n    raise NotImplementedError\n    # cdef long ipix\n    # healpix_interface.ang2pix_nest(nside, theta, phi, &ipix)\n    # return ipix\n\ndef arr_ang2pix_nest(long nside,\n                     np.ndarray[np.float64_t, ndim=1] atheta,\n                     np.ndarray[np.float64_t, ndim=1] aphi):\n    raise NotImplementedError\n    # cdef int n = atheta.shape[0]\n    # cdef int i\n    # cdef long ipnest\n    # cdef np.ndarray[np.int64_t, ndim=1] tr = np.zeros(n, dtype='int64')\n    # cdef double theta, phi\n    # for i in range(n):\n    #     theta = atheta[i]\n    #     phi = aphi[i]\n    #     healpix_interface.ang2pix_nest(nside, theta, phi, &ipnest)\n    #     tr[i] = ipnest\n    # return tr\n\n@cython.boundscheck(False)\n@cython.cdivision(False)\n@cython.wraparound(False)\ndef pixelize_healpix(long nside,\n                     np.ndarray[np.float64_t, ndim=1] values,\n                     long ntheta, long nphi,\n                     np.ndarray[np.float64_t, ndim=2] irotation):\n    raise NotImplementedError\n    # # We will first to pix2vec, rotate, then calculate the angle\n    # cdef int i, j, thetai, phii\n    # cdef long ipix\n    # cdef double v0[3], v1[3]\n    # cdef double pi = 3.1415926\n    # cdef np.float64_t pi2 = pi/2.0\n    # cdef np.float64_t phi, theta\n    # cdef np.ndarray[np.float64_t, ndim=2] results\n    # cdef np.ndarray[np.int32_t, ndim=2] count\n    # results = np.zeros((ntheta, nphi), dtype=\"float64\")\n    # count = np.zeros((ntheta, nphi), dtype=\"int32\")\n\n    # cdef np.float64_t phi0 = 0\n    # cdef np.float64_t dphi = 2.0 * pi/(nphi-1)\n\n    # cdef np.float64_t theta0 = 0\n    # cdef np.float64_t dtheta = pi/(ntheta-1)\n    # # We assume these are the rotated theta and phi\n    # for thetai in range(ntheta):\n    #     theta = theta0 + dtheta * thetai\n    #     for phii in range(nphi):\n    #         phi = phi0 + dphi * phii\n    #         # We have our rotated vector\n    #         v1[0] = cos(phi) * sin(theta)\n    #         v1[1] = sin(phi) * sin(theta)\n    #         v1[2] = cos(theta)\n    #         # Now we rotate back\n    #         for i in range(3):\n    #             v0[i] = 0\n    #             for j in range(3):\n    #                 v0[i] += v1[j] * irotation[j,i]\n    #         # Get the pixel this vector is inside\n    #         healpix_interface.vec2pix_nest(nside, v0, &ipix)\n    #         results[thetai, phii] = values[ipix]\n    #         count[i, j] += 1\n    # return results, count\n\ndef healpix_aitoff_proj(np.ndarray[np.float64_t, ndim=1] pix_image,\n                        long nside,\n                        np.ndarray[np.float64_t, ndim=2] image,\n                        np.ndarray[np.float64_t, ndim=2] irotation):\n    raise NotImplementedError\n    # cdef double pi = np.pi\n    # cdef int i, j, k, l\n    # cdef np.float64_t x, y, z, zb\n    # cdef np.float64_t dx, dy, inside\n    # cdef double v0[3], v1[3]\n    # dx = 2.0 / (image.shape[1] - 1)\n    # dy = 2.0 / (image.shape[0] - 1)\n    # cdef np.float64_t s2 = sqrt(2.0)\n    # cdef long ipix\n    # for i in range(image.shape[1]):\n    #     x = (-1.0 + i*dx)*s2*2.0\n    #     for j in range(image.shape[0]):\n    #         y = (-1.0 + j * dy)*s2\n    #         zb = (x*x/8.0 + y*y/2.0 - 1.0)\n    #         if zb > 0: continue\n    #         z = (1.0 - (x/4.0)**2.0 - (y/2.0)**2.0)\n    #         z = sqrt(z)\n    #         # Longitude\n    #         phi = (2.0*atan(z*x/(2.0 * (2.0*z*z-1.0))) + pi)\n    #         # Latitude\n    #         # We shift it into co-latitude\n    #         theta = (asin(z*y) + pi/2.0)\n    #         # Now to account for rotation we translate into vectors\n    #         v1[0] = cos(phi) * sin(theta)\n    #         v1[1] = sin(phi) * sin(theta)\n    #         v1[2] = cos(theta)\n    #         for k in range(3):\n    #             v0[k] = 0\n    #             for l in range(3):\n    #                 v0[k] += v1[l] * irotation[l,k]\n    #         healpix_interface.vec2pix_nest(nside, v0, &ipix)\n    #         #print(\"Rotated\", v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], ipix, pix_image[ipix])\n    #         image[j, i] = pix_image[ipix]\n\ndef arr_fisheye_vectors(int resolution, np.float64_t fov, int nimx=1, int\n        nimy=1, int nimi=0, int nimj=0, np.float64_t off_theta=0.0, np.float64_t\n        off_phi=0.0):\n    # We now follow figures 4-7 of:\n    # http://paulbourke.net/miscellaneous/domefisheye/fisheye/\n    # ...but all in Cython.\n    cdef np.ndarray[np.float64_t, ndim=3] vp\n    cdef int i, j\n    cdef np.float64_t r, phi, theta, px, py\n    cdef np.float64_t fov_rad = fov * np.pi / 180.0\n    cdef int nx = resolution//nimx\n    cdef int ny = resolution//nimy\n    vp = np.zeros((nx,ny, 3), dtype=\"float64\")\n    for i in range(nx):\n        px = (2.0 * (nimi*nx + i)) / resolution - 1.0\n        for j in range(ny):\n            py = (2.0 * (nimj*ny + j)) / resolution - 1.0\n            r = sqrt(px*px + py*py)\n            if r > 1.01:\n                vp[i,j,0] = vp[i,j,1] = vp[i,j,2] = 0.0\n                continue\n            phi = atan2(py, px)\n            theta = r * fov_rad / 2.0\n            theta += off_theta\n            phi += off_phi\n            vp[i,j,0] = sin(theta) * cos(phi)\n            vp[i,j,1] = sin(theta) * sin(phi)\n            vp[i,j,2] = cos(theta)\n    return vp\n"
  },
  {
    "path": "yt/utilities/lib/healpix_interface.pxd",
    "content": "\"\"\"\nA light interface to a few HEALPix routines\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.stdio cimport FILE, fclose, fopen\n\n\ncdef extern from \"healpix_vectors.h\":\n    int pix2vec_nest(long nside, long ipix, double *v)\n    void vec2pix_nest(long nside, double *vec, long *ipix)\n    void pix2ang_nest(long nside, long ipix, double *theta, double *phi)\n    void ang2pix_nest(long nside, double theta, double phi, long *ipix)\n"
  },
  {
    "path": "yt/utilities/lib/image_samplers.pxd",
    "content": "\"\"\"\nDefinitions for image samplers\n\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\nfrom .partitioned_grid cimport PartitionedGrid\nfrom .volume_container cimport VolumeContainer\n\ncdef enum:\n    Nch = 4\n\n# NOTE: We don't want to import the field_interpolator_tables here, as it\n# breaks a bunch of C++ interop.  Maybe some day it won't.  So, we just forward\n# declare.\ncdef struct VolumeRenderAccumulator\n\nctypedef int calculate_extent_function(ImageSampler image,\n            VolumeContainer *vc, np.int64_t rv[4]) except -1 nogil\n\nctypedef void generate_vector_info_function(ImageSampler im,\n            np.int64_t vi, np.int64_t vj,\n            np.float64_t width[2],\n            np.float64_t v_dir[3], np.float64_t v_pos[3]) noexcept nogil\n\ncdef struct ImageAccumulator:\n    np.float64_t rgba[Nch]\n    void *supp_data\n\ncdef class ImageSampler:\n    cdef np.float64_t[:,:,:] vp_pos\n    cdef np.float64_t[:,:,:] vp_dir\n    cdef np.float64_t *center\n    cdef np.float64_t[:,:,:] image\n    cdef np.float64_t[:,:] zbuffer\n    cdef np.int64_t[:,:] image_used\n    cdef np.int64_t[:,:] mesh_lines\n    cdef np.float64_t pdx, pdy\n    cdef np.float64_t bounds[4]\n    cdef np.float64_t[:,:] camera_data   # position, width, unit_vec[0,2]\n    cdef int nv[2]\n    cdef np.float64_t *x_vec\n    cdef np.float64_t *y_vec\n    cdef public object acenter, aimage, ax_vec, ay_vec\n    cdef public object azbuffer\n    cdef public object aimage_used\n    cdef public object amesh_lines\n    cdef void *supp_data\n    cdef np.float64_t width[3]\n    cdef public object lens_type\n    cdef public str volume_method\n    cdef calculate_extent_function *extent_function\n    cdef generate_vector_info_function *vector_function\n    cdef void setup(self, PartitionedGrid pg)\n    @staticmethod\n    cdef void sample(VolumeContainer *vc,\n                np.float64_t v_pos[3],\n                np.float64_t v_dir[3],\n                np.float64_t enter_t,\n                np.float64_t exit_t,\n                int index[3],\n                void *data) noexcept nogil\n\ncdef class ProjectionSampler(ImageSampler):\n    pass\n\ncdef class InterpolatedProjectionSampler(ImageSampler):\n    cdef VolumeRenderAccumulator *vra\n    cdef public object tf_obj\n    cdef public object my_field_tables\n\ncdef class VolumeRenderSampler(ImageSampler):\n    cdef VolumeRenderAccumulator *vra\n    cdef public object tf_obj\n    cdef public object my_field_tables\n    cdef object tree_containers\n\ncdef class LightSourceRenderSampler(ImageSampler):\n    cdef VolumeRenderAccumulator *vra\n    cdef public object tf_obj\n    cdef public object my_field_tables\n"
  },
  {
    "path": "yt/utilities/lib/image_samplers.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: extra_compile_args = CPP14_FLAG OMP_ARGS\n# distutils: extra_link_args = CPP14_FLAG OMP_ARGS\n# distutils: libraries = STD_LIBS FIXED_INTERP\n# distutils: language = c++\n\"\"\"\nImage sampler definitions\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\nfrom libc.math cimport sqrt\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.lib cimport lenses\nfrom yt.utilities.lib.fp_utils cimport fclip, i64clip, imin\n\nfrom .field_interpolation_tables cimport (\n    FieldInterpolationTable,\n    FIT_eval_transfer,\n    FIT_eval_transfer_with_light,\n    FIT_initialize_table,\n)\nfrom .fixed_interpolator cimport eval_gradient, offset_interpolate\nfrom .grid_traversal cimport sampler_function, walk_volume\n\nfrom yt.funcs import mylog\n\nfrom ._octree_raytracing cimport RayInfo, _OctreeRayTracing\n\n\ncdef extern from \"platform_dep.h\":\n    long int lrint(double x) noexcept nogil\n\nfrom cython.parallel import parallel, prange\n\nfrom cpython.exc cimport PyErr_CheckSignals\n\n\ncdef struct VolumeRenderAccumulator:\n    int n_fits\n    int n_samples\n    FieldInterpolationTable *fits\n    int field_table_ids[6]\n    np.float64_t star_coeff\n    np.float64_t star_er\n    np.float64_t star_sigma_num\n    np.float64_t *light_dir\n    np.float64_t *light_rgba\n    int grey_opacity\n\n\ncdef class ImageSampler:\n    def __init__(self,\n                  np.float64_t[:,:,:] vp_pos,\n                  np.float64_t[:,:,:] vp_dir,\n                  np.ndarray[np.float64_t, ndim=1] center,\n                  bounds,\n                  np.ndarray[np.float64_t, ndim=3] image,\n                  np.ndarray[np.float64_t, ndim=1] x_vec,\n                  np.ndarray[np.float64_t, ndim=1] y_vec,\n                  np.ndarray[np.float64_t, ndim=1] width,\n                  str volume_method,\n                  *args,\n                  **kwargs):\n        cdef int i\n\n        self.volume_method = volume_method\n        camera_data = kwargs.pop(\"camera_data\", None)\n        if camera_data is not None:\n            self.camera_data = camera_data\n\n        zbuffer = kwargs.pop(\"zbuffer\", None)\n        if zbuffer is None:\n            zbuffer = np.ones((image.shape[0], image.shape[1]), \"float64\")\n\n        image_used = np.zeros((image.shape[0], image.shape[1]), \"int64\")\n        mesh_lines = np.zeros((image.shape[0], image.shape[1]), \"int64\")\n\n        self.lens_type = kwargs.pop(\"lens_type\", None)\n        if self.lens_type == \"plane-parallel\":\n            self.extent_function = lenses.calculate_extent_plane_parallel\n            self.vector_function = lenses.generate_vector_info_plane_parallel\n        else:\n            if not (vp_pos.shape[0] == vp_dir.shape[0] == image.shape[0]) or \\\n               not (vp_pos.shape[1] == vp_dir.shape[1] == image.shape[1]):\n                msg = \"Bad lens shape / direction for %s\\n\" % (self.lens_type)\n                msg += \"Shapes: (%s - %s - %s) and (%s - %s - %s)\" % (\n                    vp_pos.shape[0], vp_dir.shape[0], image.shape[0],\n                    vp_pos.shape[1], vp_dir.shape[1], image.shape[1])\n                raise RuntimeError(msg)\n\n            if camera_data is not None and self.lens_type == 'perspective':\n                self.extent_function = lenses.calculate_extent_perspective\n            else:\n                self.extent_function = lenses.calculate_extent_null\n            self.\\\n                vector_function = lenses.generate_vector_info_null\n\n        # These assignments are so we can track the objects and prevent their\n        # de-allocation from reference counts.  Note that we do this to the\n        # \"atleast_3d\" versions.  Also, note that we re-assign the input\n        # arguments.\n        self.vp_pos = vp_pos\n        self.vp_dir = vp_dir\n        self.image = self.aimage = image\n        self.acenter = center\n        self.center = <np.float64_t *> center.data\n        self.ax_vec = x_vec\n        self.x_vec = <np.float64_t *> x_vec.data\n        self.ay_vec = y_vec\n        self.y_vec = <np.float64_t *> y_vec.data\n        self.zbuffer = zbuffer\n        self.azbuffer = np.asarray(zbuffer)\n        self.image_used = image_used\n        self.aimage_used = np.asarray(image_used)\n        self.mesh_lines = mesh_lines\n        self.amesh_lines = np.asarray(mesh_lines)\n        self.nv[0] = image.shape[0]\n        self.nv[1] = image.shape[1]\n        for i in range(4): self.bounds[i] = bounds[i]\n        self.pdx = (bounds[1] - bounds[0])/self.nv[0]\n        self.pdy = (bounds[3] - bounds[2])/self.nv[1]\n        for i in range(3):\n            self.width[i] = width[i]\n\n    def __call__(self, PartitionedGrid pg, **kwa):\n        if self.volume_method == 'KDTree':\n            return self.cast_through_kdtree(pg, **kwa)\n        elif self.volume_method == 'Octree':\n            return self.cast_through_octree(pg, **kwa)\n        else:\n            raise NotImplementedError(\n                'Volume rendering has not been implemented for method: \"%s\"' %\n                self.volume_method\n            )\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def cast_through_kdtree(self, PartitionedGrid pg, int num_threads = 0):\n        # This routine will iterate over all of the vectors and cast each in\n        # turn.  Might benefit from a more sophisticated intersection check,\n        # like http://courses.csusm.edu/cs697exz/ray_box.htm\n        cdef int vi, vj, hit, i, j\n        cdef VolumeContainer *vc = pg.container\n        self.setup(pg)\n        cdef np.float64_t *v_pos\n        cdef np.float64_t *v_dir\n        cdef np.float64_t max_t\n        hit = 0\n        cdef np.int64_t nx, ny, size\n        cdef np.int64_t iter[4]\n        self.extent_function(self, vc, iter)\n        iter[0] = i64clip(iter[0]-1, 0, self.nv[0])\n        iter[1] = i64clip(iter[1]+1, 0, self.nv[0])\n        iter[2] = i64clip(iter[2]-1, 0, self.nv[1])\n        iter[3] = i64clip(iter[3]+1, 0, self.nv[1])\n        nx = (iter[1] - iter[0])\n        ny = (iter[3] - iter[2])\n        size = nx * ny\n        cdef ImageAccumulator *idata\n        cdef np.float64_t width[3]\n        cdef int chunksize = 100\n        for i in range(3):\n            width[i] = self.width[i]\n        with nogil, parallel(num_threads = num_threads):\n            idata = <ImageAccumulator *> malloc(sizeof(ImageAccumulator))\n            idata.supp_data = self.supp_data\n            v_pos = <np.float64_t *> malloc(3 * sizeof(np.float64_t))\n            v_dir = <np.float64_t *> malloc(3 * sizeof(np.float64_t))\n            for j in prange(size, schedule=\"static\", chunksize=chunksize):\n                vj = j % ny\n                vi = (j - vj) / ny + iter[0]\n                vj = vj + iter[2]\n                # Dynamically calculate the position\n                self.vector_function(self, vi, vj, width, v_dir, v_pos)\n                for i in range(Nch):\n                    idata.rgba[i] = self.image[vi, vj, i]\n                max_t = fclip(self.zbuffer[vi, vj], 0.0, 1.0)\n                walk_volume(vc, v_pos, v_dir, self.sample,\n                            (<void *> idata), NULL, max_t)\n                if (j % (10*chunksize)) == 0:\n                    with gil:\n                        PyErr_CheckSignals()\n                for i in range(Nch):\n                    self.image[vi, vj, i] = idata.rgba[i]\n            idata.supp_data = NULL\n            free(idata)\n            free(v_pos)\n            free(v_dir)\n        return hit\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def cast_through_octree(self, PartitionedGrid pg, _OctreeRayTracing oct, int num_threads = 0):\n        cdef RayInfo[int]* ri\n        self.setup(pg)\n\n        cdef sampler_function* sampler = <sampler_function*> self.sample\n\n        cdef np.int64_t nx, ny, size\n        cdef VolumeContainer *vc\n        cdef ImageAccumulator *idata\n\n        nx = self.nv[0]\n        ny = self.nv[1]\n        size = nx * ny\n\n        cdef int i, j, k, vi, vj, icell\n        cdef int[3] index = [0, 0, 0]\n        cdef int chunksize = 100\n\n        cdef int n_fields = pg.container.n_fields\n\n        cdef np.float64_t vp_dir_len\n\n        mylog.debug('Integrating rays')\n        with nogil, parallel(num_threads=num_threads):\n            idata = <ImageAccumulator *> malloc(sizeof(ImageAccumulator))\n            idata.supp_data = self.supp_data\n\n            ri = new RayInfo[int]()\n\n            vc = <VolumeContainer*> malloc(sizeof(VolumeContainer))\n            vc.n_fields = 1\n            vc.data = <np.float64_t**> malloc(sizeof(np.float64_t*))\n            vc.mask = <np.uint8_t*> malloc(8*sizeof(np.uint8_t))\n            # The actual dimensions are 2x2x2, but the sampler\n            # assumes vertex-centred data for a 1x1x1 lattice (i.e.\n            # 2^3 vertices)\n            vc.dims[0] = 1\n            vc.dims[1] = 1\n            vc.dims[2] = 1\n            for j in prange(size, schedule='static', chunksize=chunksize):\n                vj = j % ny\n                vi = (j - vj) / ny\n\n                vp_dir_len = sqrt(\n                    self.vp_dir[vi, vj, 0]**2 +\n                    self.vp_dir[vi, vj, 1]**2 +\n                    self.vp_dir[vi, vj, 2]**2)\n\n                # Cast ray\n                oct.oct.cast_ray(&self.vp_pos[vi, vj, 0], &self.vp_dir[vi, vj, 0],\n                                 ri.keys, ri.t)\n                # Contains the ordered indices of the cells hit by the ray\n                # and the entry/exit t values\n                if ri.keys.size() == 0:\n                    continue\n\n                for i in range(Nch):\n                    idata.rgba[i] = self.image[vi, vj, i]\n                for i in range(8):\n                    vc.mask[i] = 1\n\n                # Iterate over cells\n                for i in range(ri.keys.size()):\n                    icell = ri.keys[i]\n                    for k in range(n_fields):\n                        vc.data[k] = &pg.container.data[k][14*icell]\n\n                    # Fill the volume container with the current boundaries\n                    for k in range(3):\n                        vc.left_edge[k] = pg.container.data[0][14*icell+8+k]\n                        vc.right_edge[k] = pg.container.data[0][14*icell+11+k]\n                        vc.dds[k] = (vc.right_edge[k] - vc.left_edge[k])\n                        vc.idds[k] = 1/vc.dds[k]\n                    # Now call the sampler\n                    sampler(\n                        vc,\n                        &self.vp_pos[vi, vj, 0],\n                        &self.vp_dir[vi, vj, 0],\n                        ri.t[2*i  ]/vp_dir_len,\n                        ri.t[2*i+1]/vp_dir_len,\n                        index,\n                        <void *> idata\n                        )\n                for i in range(Nch):\n                    self.image[vi, vj, i] = idata.rgba[i]\n\n                # Empty keys and t\n                ri.keys.clear()\n                ri.t.clear()\n\n            del ri\n            free(vc.data)\n            free(vc.mask)\n            free(vc)\n            idata.supp_data = NULL\n            free(idata)\n\n        mylog.debug('Done integration')\n\n\n    cdef void setup(self, PartitionedGrid pg):\n        return\n\n    @staticmethod\n    cdef void sample(\n                 VolumeContainer *vc,\n                 np.float64_t v_pos[3],\n                 np.float64_t v_dir[3],\n                 np.float64_t enter_t,\n                 np.float64_t exit_t,\n                 int index[3],\n                 void *data) noexcept nogil:\n        return\n\n    def ensure_code_unit_params(self, params):\n        for param_name in ['center', 'vp_pos', 'vp_dir', 'width']:\n            param = params[param_name]\n            if hasattr(param, 'in_units'):\n                params[param_name] = param.in_units('code_length')\n        bounds = params['bounds']\n        if hasattr(bounds[0], 'units'):\n            params['bounds'] = tuple(b.in_units('code_length').d for b in bounds)\n\n        return params\n\ncdef class ProjectionSampler(ImageSampler):\n\n    @staticmethod\n    cdef void sample(\n                 VolumeContainer *vc,\n                 np.float64_t v_pos[3],\n                 np.float64_t v_dir[3],\n                 np.float64_t enter_t,\n                 np.float64_t exit_t,\n                 int index[3],\n                 void *data) noexcept nogil:\n        cdef ImageAccumulator *im = <ImageAccumulator *> data\n        cdef int i\n        cdef np.float64_t dl = (exit_t - enter_t)\n        cdef int di = (index[0]*vc.dims[1]+index[1])*vc.dims[2]+index[2]\n        for i in range(imin(4, vc.n_fields)):\n            im.rgba[i] += vc.data[i][di] * dl\n\n\ncdef class InterpolatedProjectionSampler(ImageSampler):\n    def __cinit__(self,\n                  np.ndarray vp_pos,\n                  np.ndarray vp_dir,\n                  np.ndarray[np.float64_t, ndim=1] center,\n                  bounds,\n                  np.ndarray[np.float64_t, ndim=3] image,\n                  np.ndarray[np.float64_t, ndim=1] x_vec,\n                  np.ndarray[np.float64_t, ndim=1] y_vec,\n                  np.ndarray[np.float64_t, ndim=1] width,\n                  str volume_method,\n                  n_samples = 10,\n                  **kwargs\n        ):\n        ImageSampler.__init__(self, vp_pos, vp_dir, center, bounds, image,\n                               x_vec, y_vec, width, volume_method, **kwargs)\n        # Now we handle tf_obj\n        self.vra = <VolumeRenderAccumulator *> \\\n            malloc(sizeof(VolumeRenderAccumulator))\n        self.vra.n_samples = n_samples\n        self.supp_data = <void *> self.vra\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @staticmethod\n    cdef void sample(\n                     VolumeContainer *vc,\n                     np.float64_t v_pos[3],\n                     np.float64_t v_dir[3],\n                     np.float64_t enter_t,\n                     np.float64_t exit_t,\n                     int index[3],\n                     void *data) noexcept nogil:\n        cdef ImageAccumulator *im = <ImageAccumulator *> data\n        cdef VolumeRenderAccumulator *vri = <VolumeRenderAccumulator *> \\\n                im.supp_data\n        # we assume this has vertex-centered data.\n        cdef int offset = index[0] * (vc.dims[1] + 1) * (vc.dims[2] + 1) \\\n                        + index[1] * (vc.dims[2] + 1) + index[2]\n        cdef np.float64_t dp[3]\n        cdef np.float64_t ds[3]\n        cdef np.float64_t dt = (exit_t - enter_t) / vri.n_samples\n        cdef np.float64_t dvs[6]\n        for i in range(3):\n            dp[i] = (enter_t + 0.5 * dt) * v_dir[i] + v_pos[i]\n            dp[i] -= index[i] * vc.dds[i] + vc.left_edge[i]\n            dp[i] *= vc.idds[i]\n            ds[i] = v_dir[i] * vc.idds[i] * dt\n        for i in range(vri.n_samples):\n            for j in range(vc.n_fields):\n                dvs[j] = offset_interpolate(vc.dims, dp,\n                        vc.data[j] + offset)\n            for j in range(imin(3, vc.n_fields)):\n                im.rgba[j] += dvs[j] * dt\n            for j in range(3):\n                dp[j] += ds[j]\n\n\ncdef class VolumeRenderSampler(ImageSampler):\n    def __cinit__(self,\n                  np.ndarray vp_pos,\n                  np.ndarray vp_dir,\n                  np.ndarray[np.float64_t, ndim=1] center,\n                  bounds,\n                  np.ndarray[np.float64_t, ndim=3] image,\n                  np.ndarray[np.float64_t, ndim=1] x_vec,\n                  np.ndarray[np.float64_t, ndim=1] y_vec,\n                  np.ndarray[np.float64_t, ndim=1] width,\n                  str volume_method,\n                  tf_obj,\n                  n_samples = 10,\n                  **kwargs\n        ):\n        ImageSampler.__init__(self, vp_pos, vp_dir, center, bounds, image,\n                               x_vec, y_vec, width, volume_method, **kwargs)\n        cdef int i\n        cdef np.ndarray[np.float64_t, ndim=1] temp\n        # Now we handle tf_obj\n        self.vra = <VolumeRenderAccumulator *> \\\n            malloc(sizeof(VolumeRenderAccumulator))\n        self.vra.fits = <FieldInterpolationTable *> \\\n            malloc(sizeof(FieldInterpolationTable) * 6)\n        self.vra.n_fits = tf_obj.n_field_tables\n        assert(self.vra.n_fits <= 6)\n        self.vra.grey_opacity = getattr(tf_obj, \"grey_opacity\", 0)\n        self.vra.n_samples = n_samples\n        self.my_field_tables = []\n        for i in range(self.vra.n_fits):\n            temp = tf_obj.tables[i].y\n            FIT_initialize_table(&self.vra.fits[i],\n                      temp.shape[0],\n                      <np.float64_t *> temp.data,\n                      tf_obj.tables[i].x_bounds[0],\n                      tf_obj.tables[i].x_bounds[1],\n                      tf_obj.field_ids[i], tf_obj.weight_field_ids[i],\n                      tf_obj.weight_table_ids[i])\n            self.my_field_tables.append((tf_obj.tables[i],\n                                         tf_obj.tables[i].y))\n        for i in range(6):\n            self.vra.field_table_ids[i] = tf_obj.field_table_ids[i]\n        self.supp_data = <void *> self.vra\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @staticmethod\n    cdef void sample(\n                     VolumeContainer *vc,\n                     np.float64_t v_pos[3],\n                     np.float64_t v_dir[3],\n                     np.float64_t enter_t,\n                     np.float64_t exit_t,\n                     int index[3],\n                     void *data) noexcept nogil:\n        cdef ImageAccumulator *im = <ImageAccumulator *> data\n        cdef VolumeRenderAccumulator *vri = <VolumeRenderAccumulator *> \\\n                im.supp_data\n        # we assume this has vertex-centered data.\n        cdef int offset = index[0] * (vc.dims[1] + 1) * (vc.dims[2] + 1) \\\n                        + index[1] * (vc.dims[2] + 1) + index[2]\n        cdef int cell_offset = index[0] * (vc.dims[1]) * (vc.dims[2]) \\\n                        + index[1] * (vc.dims[2]) + index[2]\n        if vc.mask[cell_offset] != 1:\n            return\n        cdef np.float64_t dp[3]\n        cdef np.float64_t ds[3]\n        cdef np.float64_t dt = (exit_t - enter_t) / vri.n_samples\n        cdef np.float64_t dvs[6]\n        for i in range(3):\n            dp[i] = (enter_t + 0.5 * dt) * v_dir[i] + v_pos[i]\n            dp[i] -= index[i] * vc.dds[i] + vc.left_edge[i]\n            dp[i] *= vc.idds[i]\n            ds[i] = v_dir[i] * vc.idds[i] * dt\n        for i in range(vri.n_samples):\n            for j in range(vc.n_fields):\n                dvs[j] = offset_interpolate(vc.dims, dp,\n                        vc.data[j] + offset)\n            FIT_eval_transfer(dt, dvs, im.rgba, vri.n_fits,\n                    vri.fits, vri.field_table_ids, vri.grey_opacity)\n            for j in range(3):\n                dp[j] += ds[j]\n\n    def __dealloc__(self):\n        for i in range(self.vra.n_fits):\n            free(self.vra.fits[i].d0)\n            free(self.vra.fits[i].dy)\n        free(self.vra.fits)\n        free(self.vra)\n\ncdef class LightSourceRenderSampler(ImageSampler):\n    def __cinit__(self,\n                  np.ndarray vp_pos,\n                  np.ndarray vp_dir,\n                  np.ndarray[np.float64_t, ndim=1] center,\n                  bounds,\n                  np.ndarray[np.float64_t, ndim=3] image,\n                  np.ndarray[np.float64_t, ndim=1] x_vec,\n                  np.ndarray[np.float64_t, ndim=1] y_vec,\n                  np.ndarray[np.float64_t, ndim=1] width,\n                  str volume_method,\n                  tf_obj,\n                  n_samples = 10,\n                  light_dir=(1.,1.,1.),\n                  light_rgba=(1.,1.,1.,1.),\n                  **kwargs):\n        ImageSampler.__init__(self, vp_pos, vp_dir, center, bounds, image,\n                               x_vec, y_vec, width, volume_method, **kwargs)\n        cdef int i\n        cdef np.ndarray[np.float64_t, ndim=1] temp\n        # Now we handle tf_obj\n        self.vra = <VolumeRenderAccumulator *> \\\n            malloc(sizeof(VolumeRenderAccumulator))\n        self.vra.fits = <FieldInterpolationTable *> \\\n            malloc(sizeof(FieldInterpolationTable) * 6)\n        self.vra.n_fits = tf_obj.n_field_tables\n        assert(self.vra.n_fits <= 6)\n        self.vra.grey_opacity = getattr(tf_obj, \"grey_opacity\", 0)\n        self.vra.n_samples = n_samples\n        self.vra.light_dir = <np.float64_t *> malloc(sizeof(np.float64_t) * 3)\n        self.vra.light_rgba = <np.float64_t *> malloc(sizeof(np.float64_t) * 4)\n        light_dir /= np.sqrt(light_dir[0] * light_dir[0] +\n                             light_dir[1] * light_dir[1] +\n                             light_dir[2] * light_dir[2])\n        for i in range(3):\n            self.vra.light_dir[i] = light_dir[i]\n        for i in range(4):\n            self.vra.light_rgba[i] = light_rgba[i]\n        self.my_field_tables = []\n        for i in range(self.vra.n_fits):\n            temp = tf_obj.tables[i].y\n            FIT_initialize_table(&self.vra.fits[i],\n                      temp.shape[0],\n                      <np.float64_t *> temp.data,\n                      tf_obj.tables[i].x_bounds[0],\n                      tf_obj.tables[i].x_bounds[1],\n                      tf_obj.field_ids[i], tf_obj.weight_field_ids[i],\n                      tf_obj.weight_table_ids[i])\n            self.my_field_tables.append((tf_obj.tables[i],\n                                         tf_obj.tables[i].y))\n        for i in range(6):\n            self.vra.field_table_ids[i] = tf_obj.field_table_ids[i]\n        self.supp_data = <void *> self.vra\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    @staticmethod\n    cdef void sample(\n                     VolumeContainer *vc,\n                     np.float64_t v_pos[3],\n                     np.float64_t v_dir[3],\n                     np.float64_t enter_t,\n                     np.float64_t exit_t,\n                     int index[3],\n                     void *data) noexcept nogil:\n        cdef ImageAccumulator *im = <ImageAccumulator *> data\n        cdef VolumeRenderAccumulator *vri = <VolumeRenderAccumulator *> \\\n                im.supp_data\n        # we assume this has vertex-centered data.\n        cdef int offset = index[0] * (vc.dims[1] + 1) * (vc.dims[2] + 1) \\\n                        + index[1] * (vc.dims[2] + 1) + index[2]\n        cdef np.float64_t dp[3]\n        cdef np.float64_t ds[3]\n        cdef np.float64_t dt = (exit_t - enter_t) / vri.n_samples\n        cdef np.float64_t dvs[6]\n        cdef np.float64_t *grad\n        grad = <np.float64_t *> malloc(3 * sizeof(np.float64_t))\n        for i in range(3):\n            dp[i] = (enter_t + 0.5 * dt) * v_dir[i] + v_pos[i]\n            dp[i] -= index[i] * vc.dds[i] + vc.left_edge[i]\n            dp[i] *= vc.idds[i]\n            ds[i] = v_dir[i] * vc.idds[i] * dt\n        for i in range(vri.n_samples):\n            for j in range(vc.n_fields):\n                dvs[j] = offset_interpolate(vc.dims, dp,\n                        vc.data[j] + offset)\n            eval_gradient(vc.dims, dp, vc.data[0] + offset, grad)\n            FIT_eval_transfer_with_light(dt, dvs, grad,\n                    vri.light_dir, vri.light_rgba,\n                    im.rgba, vri.n_fits,\n                    vri.fits, vri.field_table_ids, vri.grey_opacity)\n            for j in range(3):\n                dp[j] += ds[j]\n        free(grad)\n\n\n    def __dealloc__(self):\n        for i in range(self.vra.n_fits):\n            free(self.vra.fits[i].d0)\n            free(self.vra.fits[i].dy)\n        free(self.vra.light_dir)\n        free(self.vra.light_rgba)\n        free(self.vra.fits)\n        free(self.vra)\n"
  },
  {
    "path": "yt/utilities/lib/image_utilities.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n# distutils: extra_compile_args = OMP_ARGS\n# distutils: extra_link_args = OMP_ARGS\n\"\"\"\nUtilities for images\n\"\"\"\n\n\nimport numpy as np\n\ncimport numpy as np\ncimport cython\nfrom libc.math cimport ceil, floor, log2, sqrt\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.lib.fp_utils cimport iclip, imin, imax, fclip, fmin, fmax\nfrom cython.parallel import prange, parallel\n\n@cython.wraparound(False)\ndef add_points_to_greyscale_image(\n        np.ndarray[np.float64_t, ndim=2] buffer,\n        np.ndarray[np.uint8_t,   ndim=2] buffer_mask,\n        np.ndarray[np.float64_t, ndim=1] px,\n        np.ndarray[np.float64_t, ndim=1] py,\n        np.ndarray[np.float64_t, ndim=1] pv):\n    cdef int i, j, pi\n    cdef int npx = px.shape[0]\n    cdef int xs = buffer.shape[0]\n    cdef int ys = buffer.shape[1]\n    for pi in range(npx):\n        j = <int> (xs * px[pi])\n        i = <int> (ys * py[pi])\n        if (i < 0) or (i >= buffer.shape[0]) or (j < 0) or (j >= buffer.shape[1]):\n            # some particles might intersect the image buffer\n            # but actually be centered out of bounds. Skip those.\n            # see https://github.com/yt-project/yt/issues/4603\n            continue\n\n        buffer[i, j] += pv[pi]\n        buffer_mask[i, j] = 1\n    return\n\ncdef inline int ij2idx(const int i, const int j, const int Nx) noexcept nogil:\n    return i * Nx + j\n\n@cython.cdivision(True)\n@cython.wraparound(False)\n@cython.boundscheck(False)\ncdef void _add_cell_to_image_offaxis(\n    const np.float64_t dx,\n    const np.float64_t w,\n    const np.float64_t q,\n    const np.float64_t cell_max_width,\n    const np.float64_t x,\n    const np.float64_t y,\n    const int Nx,\n    const int Ny,\n    const int Nsx,\n    const int Nsy,\n    np.float64_t* buffer,\n    np.float64_t* buffer_weight,\n    const int max_depth,\n    const np.float64_t[:, :, ::1] stamp,\n    const np.uint8_t[:, :, ::1] stamp_mask,\n) noexcept nogil:\n    cdef np.float64_t lx, rx, ly, ry\n    cdef int j, k, depth\n    cdef int jmin, jmax, kmin, kmax, jj1, jj2, kk1, kk2, itmp\n    cdef np.float64_t cell_max_half_width = cell_max_width / 2\n    cdef np.float64_t xx1, xx2, yy1, yy2, dvx, dvy, sw, sq, tmp, dx_loc, dy_loc\n    cdef np.float64_t dx3 = dx * dx * dx * Nx * Ny\n\n    lx = x - cell_max_half_width\n    rx = x + cell_max_half_width\n\n    ly = y - cell_max_half_width\n    ry = y + cell_max_half_width\n\n    # Compute the range of pixels that the cell may overlap\n    jmin = imax(<int>floor(lx * Nx), 0)\n    jmax = imax(<int>ceil(rx * Nx), Nx - 1)\n\n    kmin = imin(<int>floor(ly * Ny), 0)\n    kmax = imax(<int>ceil(ry * Ny), Ny - 1)\n\n    # If the cell is fully within one pixel\n    if (jmax == jmin + 1) and (kmax == kmin + 1):\n        buffer[ij2idx(jmin, kmin, Nx)]        += q * dx3\n        buffer_weight[ij2idx(jmin, kmin, Nx)] += w * dx3\n        return\n\n    # Our 'stamp' has multiple resolutions, select the one\n    # that is at a higher resolution than the pixel\n    # we are projecting onto with at least 4 pixels on the diagonal\n    depth = iclip(\n        <int> (ceil(log2(4 * sqrt(3) * dx * fmax(Nx, Ny)))),\n        1,\n        max_depth - 1,\n    )\n\n    jmax = imin(Nsx, 1 << depth)\n    kmax = imin(Nsy, 1 << depth)\n\n    dx_loc = cell_max_width / jmax\n    dy_loc = cell_max_width / kmax\n\n    for j in range(jmax):\n        xx1 = ((j - jmax / 2.) * dx_loc + x) * Nx\n        xx2 = ((j + 1 - jmax / 2.) * dx_loc + x) * Nx\n\n        jj1 = <int> xx1\n        jj2 = <int> xx2\n\n        # The subcell is out of the projected area\n        if jj2 < 0 or jj1 >= Nx: continue\n\n        # Fraction of overlap with the pixel in x direction\n        dvx = fclip((jj2 - xx1) / (xx2 - xx1), 0., 1.)\n\n        for k in range(kmax):\n            if stamp_mask[depth, j, k] == 0:\n                continue\n\n            yy1 = ((k - kmax / 2.) * dy_loc + y) * Ny\n            yy2 = ((k + 1 - kmax / 2.) * dy_loc + y) * Ny\n\n            kk1 = <int> yy1\n            kk2 = <int> yy2\n            # The subcell is out of the projected area\n            if kk2 < 0 or kk1 >= Ny: continue\n\n            tmp = stamp[depth, j, k] * dx3\n            sw = tmp * w\n            sq = tmp * q\n\n            # Fraction of overlap with the pixel in y direction\n            dvy = fclip((kk2 - yy1) / (yy2 - yy1), 0., 1.)\n\n            if jj1 >= 0 and kk1 >= 0:\n                tmp = dvx * dvy\n                itmp = ij2idx(jj1, kk1, Nx)\n                buffer[itmp]        += sq * tmp\n                buffer_weight[itmp] += sw * tmp\n\n            if jj1 >= 0 and kk2 < Ny:\n                tmp = dvx * (1 - dvy)\n                itmp = ij2idx(jj1, kk2, Nx)\n                buffer[itmp]        += sq * tmp\n                buffer_weight[itmp] += sw * tmp\n\n            if jj2 < Nx and kk1 >= 0:\n                tmp = (1 - dvx) * dvy\n                itmp = ij2idx(jj2, kk1, Nx)\n                buffer[itmp]        += sq * tmp\n                buffer_weight[itmp] += sw * tmp\n\n            if jj2 < Nx and kk2 < Ny:\n                tmp = (1 - dvx) * (1 - dvy)\n                itmp = ij2idx(jj2, kk2, Nx)\n                buffer[itmp]        += sq * tmp\n                buffer_weight[itmp] += sw * tmp\n\n@cython.boundscheck(False)\n@cython.cdivision(True)\n@cython.wraparound(False)\ndef add_cells_to_image_offaxis(\n    *,\n    const np.float64_t[:, ::1] Xp,\n    const np.float64_t[::1] dXp,\n    const np.float64_t[::1] qty,\n    const np.float64_t[::1] weight,\n    const np.float64_t[:, :] rotation,\n    np.float64_t[:, ::1] buffer,\n    np.float64_t[:, ::1] buffer_weight,\n    const int Nx,\n    const int Ny,\n    const int Npix_min = 4,\n):\n    cdef np.ndarray[np.float64_t, ndim=1] center = np.array([0.5, 0.5, 0.5])\n    cdef np.float64_t w0 = 1 / sqrt(3.)\n    cdef int i, j, k\n\n    cdef np.ndarray[np.float64_t, ndim=1] a = np.array([1., 0, 0]) * w0\n    cdef np.ndarray[np.float64_t, ndim=1] b = np.array([0, 1., 0]) * w0\n    cdef np.ndarray[np.float64_t, ndim=1] c = np.array([0, 0, 1.]) * w0\n\n    a = np.dot(rotation, a)\n    b = np.dot(rotation, b)\n    c = np.dot(rotation, c)\n\n    cdef np.ndarray[np.float64_t, ndim=1] o = center - (a + b + c) / 2\n\n    cdef int Nsx, Nsy\n    cdef np.float64_t dx_max = np.max(dXp)\n    # The largest cell needs to be resolved by at least this number of pixels\n    Nsx = max(Npix_min, int(ceil(2 * dx_max * sqrt(3) * Nx)))\n    Nsy = max(Npix_min, int(ceil(2 * dx_max * sqrt(3) * Ny)))\n    cdef int max_depth = int(ceil(log2(max(Nsx, Nsy))))\n    cdef int depth\n\n    cdef np.ndarray[np.float64_t, ndim=3] stamp_arr = np.zeros((max_depth, Nsx, Nsy), dtype=float)\n    cdef np.ndarray[np.uint8_t, ndim=3] stamp_mask_arr = np.zeros((max_depth, Nsx, Nsy), dtype=np.uint8)\n    cdef np.float64_t[:, :, ::1] stamp = stamp_arr\n    cdef np.uint8_t[:, :, ::1] stamp_mask = stamp_mask_arr\n\n    # Precompute the mip\n    for depth in range(max_depth):\n        if depth == 0:\n            stamp[0, 0, 0] = 1\n            continue\n        direct_integrate_cube(\n            o,\n            a,\n            b,\n            c,\n            stamp_arr[depth, :, :],\n            stamp_mask_arr[depth, :, :],\n            imin(1 << depth, Nsx),\n            imin(1 << depth, Nsy),\n        )\n\n        stamp_arr[depth] /= np.sum(stamp[depth])\n\n    # Iterate over all cells, applying the stamp\n    cdef np.float64_t x, y, dx\n    cdef np.float64_t[:, ::1] rotation_view = np.ascontiguousarray(rotation)\n    cdef np.float64_t w, q, cell_max_width, sq3\n\n    sq3 = sqrt(3.)\n\n    # Local buffers\n    cdef np.float64_t *lbuffer\n    cdef np.float64_t *lbuffer_weight\n\n    cdef int num_particles = len(Xp)\n\n    with nogil, parallel():\n        lbuffer = <np.float64_t*> malloc(sizeof(np.float64_t*) * Nx * Ny)\n        lbuffer_weight = <np.float64_t*> malloc(sizeof(np.float64_t*) * Nx * Ny)\n        for j in range(Nx * Ny):\n            lbuffer[j] = 0\n            lbuffer_weight[j] = 0\n\n        for i in prange(num_particles, schedule=\"runtime\"):\n            dx = dXp[i]\n            w = weight[i]\n            q = qty[i]\n            cell_max_width = dx * sq3\n            x = (\n                rotation_view[0, 0] * Xp[i, 0] +\n                rotation_view[0, 1] * Xp[i, 1] +\n                rotation_view[0, 2] * Xp[i, 2]\n            ) + 0.5\n            y = (\n                rotation_view[1, 0] * Xp[i, 0] +\n                rotation_view[1, 1] * Xp[i, 1] +\n                rotation_view[1, 2] * Xp[i, 2]\n            ) + 0.5\n\n            _add_cell_to_image_offaxis(\n                dx,\n                w,\n                q,\n                cell_max_width,\n                x,\n                y,\n                Nx,\n                Ny,\n                Nsx,\n                Nsy,\n                lbuffer,\n                lbuffer_weight,\n                max_depth,\n                stamp,\n                stamp_mask\n            )\n\n        # Copy back data in main buffer\n        with gil:\n            for j in range(Nx):\n                for k in range(Ny):\n                    buffer[j, k] += lbuffer[ij2idx(j, k, Nx)]\n                    buffer_weight[j, k] += lbuffer_weight[ij2idx(j, k, Nx)]\n        # Free memory\n        free(lbuffer)\n        free(lbuffer_weight)\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline np.float64_t det2d(const np.float64_t[::1] a, const np.float64_t[::1] b) noexcept nogil:\n    return a[0] * b[1] - a[1] * b[0]\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef bint check_in_parallelogram(\n    const np.float64_t[::1] PA,\n    const np.float64_t[::1] PQ,\n    const np.float64_t[::1] PR,\n    const int signPQ,\n    const int signPR,\n    np.float64_t[2] out\n) noexcept nogil:\n    cdef np.float64_t det_PQR = det2d(PQ, PR)\n    if det_PQR == 0:\n        out[0] = -1\n        out[1] = -1\n        return False\n\n    out[0] = -det2d(PA, PQ) / det_PQR\n    out[1] = det2d(PA, PR) / det_PQR\n\n    if 0 <= signPQ * out[0] <= 1 and 0 <= signPR * out[1] <= 1:\n        return True\n\n    out[0] = -1\n    out[1] = -1\n    return False\n\n@cython.boundscheck(False)\n@cython.cdivision(True)\ncdef int direct_integrate_cube(\n    np.ndarray[np.float64_t, ndim=1] O,\n    np.ndarray[np.float64_t, ndim=1] u,\n    np.ndarray[np.float64_t, ndim=1] v,\n    np.ndarray[np.float64_t, ndim=1] w,\n    np.ndarray[np.float64_t, ndim=2] buffer,\n    np.ndarray[np.uint8_t, ndim=2] buffer_mask,\n    const int Nx,\n    const int Ny,\n) except -1:\n    \"\"\"\n    Compute depth of cube from direct integration of entry/exit points of rays\n    \"\"\"\n    cdef np.float64_t[::1] u2d = u[:2]\n    cdef np.float64_t[::1] v2d = v[:2]\n    cdef np.float64_t[::1] w2d = w[:2]\n\n    cdef np.float64_t[::1] Oback = O + u + v + w\n\n    cdef np.float64_t[::1] X = np.zeros(2)\n    cdef np.float64_t[::1] OfrontA = np.zeros(2)\n    cdef np.float64_t[::1] ObackA = np.zeros(2)\n\n    cdef np.float64_t inv_dx = 1. / Nx\n    cdef np.float64_t inv_dy = 1. / Ny\n    cdef np.float64_t[2] nm\n    cdef bint within\n    cdef np.float64_t zmin, zmax, z\n    cdef int Nhit, i, j\n    for i in range(Nx):\n        X[0] = (i + 0.5) * inv_dx\n\n        OfrontA[0] = X[0] - O[0]\n        ObackA[0] = X[0] - Oback[0]\n\n        for j in range(Ny):\n            zmin = np.inf\n            zmax = -np.inf\n            Nhit = 0\n            X[1] = (j + 0.5) * inv_dy\n\n            OfrontA[1] = X[1] - O[1]\n            ObackA[1] = X[1] - Oback[1]\n\n            within = check_in_parallelogram(OfrontA, v2d, u2d, 1, 1, nm)\n            if within:\n                z = O[2] + nm[0] * u[2] + nm[1] * v[2]\n                zmin = fmin(z, zmin)\n                zmax = fmax(z, zmax)\n                Nhit += 1\n\n            within = check_in_parallelogram(OfrontA, w2d, v2d, 1, 1, nm)\n            if within:\n                z = O[2] + nm[0] * v[2] + nm[1] * w[2]\n                zmin = fmin(z, zmin)\n                zmax = fmax(z, zmax)\n                Nhit += 1\n\n            within = check_in_parallelogram(OfrontA, w2d, u2d, 1, 1, nm)\n            if within:\n                z = O[2] + nm[0] * u[2] + nm[1] * w[2]\n                zmin = fmin(z, zmin)\n                zmax = fmax(z, zmax)\n                Nhit += 1\n\n            within = check_in_parallelogram(ObackA, v2d, u2d, -1, -1, nm)\n            if within:\n                z = Oback[2] + nm[0] * u[2] + nm[1] * v[2]\n                zmin = fmin(z, zmin)\n                zmax = fmax(z, zmax)\n                Nhit += 1\n\n            within = check_in_parallelogram(ObackA, w2d, v2d, -1, -1, nm)\n            if within:\n                z = Oback[2] + nm[0] * v[2] + nm[1] * w[2]\n                zmin = fmin(z, zmin)\n                zmax = fmax(z, zmax)\n                Nhit += 1\n\n            within = check_in_parallelogram(ObackA, w2d, u2d, -1, -1, nm)\n            if within:\n                z = Oback[2] + nm[0] * u[2] + nm[1] * w[2]\n                zmin = fmin(z, zmin)\n                zmax = fmax(z, zmax)\n                Nhit += 1\n\n            if Nhit == 0:\n                continue\n            elif Nhit == 1:\n                raise RuntimeError(\"This should not happen\")\n            else:\n                buffer[i, j] += zmax - zmin\n                buffer_mask[i, j] = 1\n\ndef add_points_to_image(\n        np.ndarray[np.uint8_t, ndim=3] buffer,\n        np.ndarray[np.float64_t, ndim=1] px,\n        np.ndarray[np.float64_t, ndim=1] py,\n        np.float64_t pv):\n    cdef int i, j, k, pi\n    cdef int npx = px.shape[0]\n    cdef int xs = buffer.shape[0]\n    cdef int ys = buffer.shape[1]\n    cdef int v\n    v = iclip(<int>(pv * 255), 0, 255)\n    for pi in range(npx):\n        j = <int> (xs * px[pi])\n        i = <int> (ys * py[pi])\n        for k in range(3):\n            buffer[i, j, k] = v\n        buffer[i, j, 3] = 255\n    return\n\ndef add_rgba_points_to_image(\n        np.ndarray[np.float64_t, ndim=3] buffer,\n        np.ndarray[np.float64_t, ndim=1] px,\n        np.ndarray[np.float64_t, ndim=1] py,\n        np.ndarray[np.float64_t, ndim=2] rgba,\n        ):\n    \"\"\"\n    Splat rgba points onto an image\n\n    Given an image buffer, add colors to\n    pixels defined by fractional positions px and py,\n    with colors rgba.  px and py are one dimensional\n    arrays, and rgba is a an array of rgba values.\n    \"\"\"\n    cdef int i, j, k, pi\n    cdef int npart = px.shape[0]\n    cdef int xs = buffer.shape[0]\n    cdef int ys = buffer.shape[1]\n    #iv = iclip(<int>(pv * 255), 0, 255)\n    for pi in range(npart):\n        j = <int> (xs * px[pi])\n        i = <int> (ys * py[pi])\n        if i < 0 or j < 0 or i >= xs or j >= ys:\n            continue\n        for k in range(4):\n            buffer[i, j, k] += rgba[pi, k]\n    return\n"
  },
  {
    "path": "yt/utilities/lib/interpolators.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nSimple interpolators\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\nfrom yt.utilities.lib.fp_utils cimport iclip\n\n\n@cython.cdivision(True)\n@cython.wraparound(False)\n@cython.boundscheck(False)\ncpdef void UnilinearlyInterpolate(np.ndarray[np.float64_t, ndim=1] table,\n                           np.ndarray[np.float64_t, ndim=1] x_vals,\n                           np.ndarray[np.float64_t, ndim=1] x_bins,\n                           np.ndarray[np.int32_t, ndim=1] x_is,\n                           np.ndarray[np.float64_t, ndim=1] output):\n    cdef double x, xp, xm\n    cdef int i, x_i\n    for i in range(x_vals.shape[0]):\n        x_i = x_is[i]\n        x = x_vals[i]\n        dx_inv = 1.0 / (x_bins[x_i+1] - x_bins[x_i])\n        xp = (x - x_bins[x_i]) * dx_inv\n        xm = (x_bins[x_i+1] - x) * dx_inv\n        output[i]  = table[x_i  ] * (xm) \\\n                   + table[x_i+1] * (xp)\n\n@cython.cdivision(True)\n@cython.wraparound(False)\n@cython.boundscheck(False)\ncpdef void BilinearlyInterpolate(np.ndarray[np.float64_t, ndim=2] table,\n                          np.ndarray[np.float64_t, ndim=1] x_vals,\n                          np.ndarray[np.float64_t, ndim=1] y_vals,\n                          np.ndarray[np.float64_t, ndim=1] x_bins,\n                          np.ndarray[np.float64_t, ndim=1] y_bins,\n                          np.ndarray[np.int32_t, ndim=1] x_is,\n                          np.ndarray[np.int32_t, ndim=1] y_is,\n                          np.ndarray[np.float64_t, ndim=1] output):\n    cdef double x, xp, xm\n    cdef double y, yp, ym\n    cdef double dx_inv, dy_inv\n    cdef int i, x_i, y_i\n    for i in range(x_vals.shape[0]):\n        x_i = x_is[i]\n        y_i = y_is[i]\n        x = x_vals[i]\n        y = y_vals[i]\n        dx_inv = 1.0 / (x_bins[x_i+1] - x_bins[x_i])\n        dy_inv = 1.0 / (y_bins[y_i+1] - y_bins[y_i])\n        xp = (x - x_bins[x_i]) * dx_inv\n        yp = (y - y_bins[y_i]) * dy_inv\n        xm = (x_bins[x_i+1] - x) * dx_inv\n        ym = (y_bins[y_i+1] - y) * dy_inv\n        output[i]  = table[x_i  , y_i  ] * (xm*ym) \\\n                   + table[x_i+1, y_i  ] * (xp*ym) \\\n                   + table[x_i  , y_i+1] * (xm*yp) \\\n                   + table[x_i+1, y_i+1] * (xp*yp)\n\n@cython.cdivision(True)\n@cython.wraparound(False)\n@cython.boundscheck(False)\ncpdef void TrilinearlyInterpolate(np.ndarray[np.float64_t, ndim=3] table,\n                           np.ndarray[np.float64_t, ndim=1] x_vals,\n                           np.ndarray[np.float64_t, ndim=1] y_vals,\n                           np.ndarray[np.float64_t, ndim=1] z_vals,\n                           np.ndarray[np.float64_t, ndim=1] x_bins,\n                           np.ndarray[np.float64_t, ndim=1] y_bins,\n                           np.ndarray[np.float64_t, ndim=1] z_bins,\n                           np.ndarray[np.int64_t, ndim=1] x_is,\n                           np.ndarray[np.int64_t, ndim=1] y_is,\n                           np.ndarray[np.int64_t, ndim=1] z_is,\n                           np.ndarray[np.float64_t, ndim=1] output):\n    cdef double x, xp, xm\n    cdef double y, yp, ym\n    cdef double z, zp, zm\n    cdef double dx_inv, dy_inv, dz_inv\n    cdef int i, x_i, y_i, z_i\n    for i in range(x_vals.shape[0]):\n        x_i = x_is[i]\n        y_i = y_is[i]\n        z_i = z_is[i]\n        x = x_vals[i]\n        y = y_vals[i]\n        z = z_vals[i]\n        dx_inv = 1.0 / (x_bins[x_i+1] - x_bins[x_i])\n        dy_inv = 1.0 / (y_bins[y_i+1] - y_bins[y_i])\n        dz_inv = 1.0 / (z_bins[z_i+1] - z_bins[z_i])\n        xp = (x - x_bins[x_i]) * dx_inv\n        yp = (y - y_bins[y_i]) * dy_inv\n        zp = (z - z_bins[z_i]) * dz_inv\n        xm = (x_bins[x_i+1] - x) * dx_inv\n        ym = (y_bins[y_i+1] - y) * dy_inv\n        zm = (z_bins[z_i+1] - z) * dz_inv\n        output[i]  = table[x_i  ,y_i  ,z_i  ] * (xm*ym*zm) \\\n                   + table[x_i+1,y_i  ,z_i  ] * (xp*ym*zm) \\\n                   + table[x_i  ,y_i+1,z_i  ] * (xm*yp*zm) \\\n                   + table[x_i  ,y_i  ,z_i+1] * (xm*ym*zp) \\\n                   + table[x_i+1,y_i  ,z_i+1] * (xp*ym*zp) \\\n                   + table[x_i  ,y_i+1,z_i+1] * (xm*yp*zp) \\\n                   + table[x_i+1,y_i+1,z_i  ] * (xp*yp*zm) \\\n                   + table[x_i+1,y_i+1,z_i+1] * (xp*yp*zp)\n\n@cython.cdivision(True)\n@cython.wraparound(False)\n@cython.boundscheck(False)\ncpdef void QuadrilinearlyInterpolate(np.ndarray[np.float64_t, ndim=4] table,\n                              np.ndarray[np.float64_t, ndim=1] x_vals,\n                              np.ndarray[np.float64_t, ndim=1] y_vals,\n                              np.ndarray[np.float64_t, ndim=1] z_vals,\n                              np.ndarray[np.float64_t, ndim=1] w_vals,\n                              np.ndarray[np.float64_t, ndim=1] x_bins,\n                              np.ndarray[np.float64_t, ndim=1] y_bins,\n                              np.ndarray[np.float64_t, ndim=1] z_bins,\n                              np.ndarray[np.float64_t, ndim=1] w_bins,\n                              np.ndarray[np.int64_t, ndim=1] x_is,\n                              np.ndarray[np.int64_t, ndim=1] y_is,\n                              np.ndarray[np.int64_t, ndim=1] z_is,\n                              np.ndarray[np.int64_t, ndim=1] w_is,\n                              np.ndarray[np.float64_t, ndim=1] output):\n    cdef double x, xp, xm\n    cdef double y, yp, ym\n    cdef double z, zp, zm\n    cdef double w, wp, wm\n    cdef double dx_inv, dy_inv, dz_inv, dw_inv\n    cdef int i, x_i, y_i, z_i, w_i\n    for i in range(x_vals.shape[0]):\n        x_i = x_is[i]\n        y_i = y_is[i]\n        z_i = z_is[i]\n        w_i = w_is[i]\n        x = x_vals[i]\n        y = y_vals[i]\n        z = z_vals[i]\n        w = w_vals[i]\n        dx_inv = 1.0 / (x_bins[x_i+1] - x_bins[x_i])\n        dy_inv = 1.0 / (y_bins[y_i+1] - y_bins[y_i])\n        dz_inv = 1.0 / (z_bins[z_i+1] - z_bins[z_i])\n        dw_inv = 1.0 / (w_bins[w_i+1] - w_bins[w_i])\n        xp = (x - x_bins[x_i]) * dx_inv\n        yp = (y - y_bins[y_i]) * dy_inv\n        zp = (z - z_bins[z_i]) * dz_inv\n        wp = (w - w_bins[w_i]) * dw_inv\n        xm = (x_bins[x_i+1] - x) * dx_inv\n        ym = (y_bins[y_i+1] - y) * dy_inv\n        zm = (z_bins[z_i+1] - z) * dz_inv\n        wm = (w_bins[w_i+1] - w) * dw_inv\n        output[i]  = table[x_i  ,y_i  ,z_i  ,w_i  ] * (xm*ym*zm*wm) \\\n                   + table[x_i+1,y_i  ,z_i  ,w_i  ] * (xp*ym*zm*wm) \\\n                   + table[x_i  ,y_i+1,z_i  ,w_i  ] * (xm*yp*zm*wm) \\\n                   + table[x_i  ,y_i  ,z_i+1,w_i  ] * (xm*ym*zp*wm) \\\n                   + table[x_i  ,y_i  ,z_i  ,w_i+1] * (xm*ym*zm*wp) \\\n                   + table[x_i+1,y_i  ,z_i  ,w_i+1] * (xp*ym*zm*wp) \\\n                   + table[x_i  ,y_i+1,z_i  ,w_i+1] * (xm*yp*zm*wp) \\\n                   + table[x_i  ,y_i  ,z_i+1,w_i+1] * (xm*ym*zp*wp) \\\n                   + table[x_i+1,y_i  ,z_i+1,w_i  ] * (xp*ym*zp*wm) \\\n                   + table[x_i  ,y_i+1,z_i+1,w_i  ] * (xm*yp*zp*wm) \\\n                   + table[x_i+1,y_i+1,z_i  ,w_i  ] * (xp*yp*zm*wm) \\\n                   + table[x_i+1,y_i  ,z_i+1,w_i+1] * (xp*ym*zp*wp) \\\n                   + table[x_i  ,y_i+1,z_i+1,w_i+1] * (xm*yp*zp*wp) \\\n                   + table[x_i+1,y_i+1,z_i  ,w_i+1] * (xp*yp*zm*wp) \\\n                   + table[x_i+1,y_i+1,z_i+1,w_i  ] * (xp*yp*zp*wm) \\\n                   + table[x_i+1,y_i+1,z_i+1,w_i+1] * (xp*yp*zp*wp)\n\n@cython.cdivision(True)\n@cython.wraparound(False)\n@cython.boundscheck(False)\ncpdef void ghost_zone_interpolate(int rf,\n                           np.ndarray[np.float64_t, ndim=3] input_field,\n                           np.ndarray[np.float64_t, ndim=1] input_left,\n                           np.ndarray[np.float64_t, ndim=3] output_field,\n                           np.ndarray[np.float64_t, ndim=1] output_left):\n    cdef int oi, oj, ok\n    cdef int ii, ij, ik\n    cdef np.float64_t xp, xm, yp, ym, zp, zm, temp\n    cdef np.float64_t ods[3]\n    cdef np.float64_t ids[3]\n    cdef np.float64_t iids[3]\n    cdef np.float64_t opos[3]\n    cdef np.float64_t ropos[3]\n    cdef int i\n    for i in range(3):\n        temp = input_left[i] + (rf * (input_field.shape[i] - 1))\n        ids[i] = (temp - input_left[i])/(input_field.shape[i]-1)\n        temp = output_left[i] + output_field.shape[i] - 1\n        ods[i] = (temp - output_left[i])/(output_field.shape[i]-1)\n        iids[i] = 1.0/ids[i]\n    opos[0] = output_left[0]\n    for oi in range(output_field.shape[0]):\n        ropos[0] = ((opos[0] - input_left[0]) * iids[0])\n        ii = iclip(<int> ropos[0], 0, input_field.shape[0] - 2)\n        xp = ropos[0] - ii\n        xm = 1.0 - xp\n        opos[1] = output_left[1]\n        for oj in range(output_field.shape[1]):\n            ropos[1] = ((opos[1] - input_left[1]) * iids[1])\n            ij = iclip(<int> ropos[1], 0, input_field.shape[1] - 2)\n            yp = ropos[1] - ij\n            ym = 1.0 - yp\n            opos[2] = output_left[2]\n            for ok in range(output_field.shape[2]):\n                ropos[2] = ((opos[2] - input_left[2]) * iids[2])\n                ik = iclip(<int> ropos[2], 0, input_field.shape[2] - 2)\n                zp = ropos[2] - ik\n                zm = 1.0 - zp\n                output_field[oi,oj,ok] = \\\n                     input_field[ii  ,ij  ,ik  ] * (xm*ym*zm) \\\n                   + input_field[ii+1,ij  ,ik  ] * (xp*ym*zm) \\\n                   + input_field[ii  ,ij+1,ik  ] * (xm*yp*zm) \\\n                   + input_field[ii  ,ij  ,ik+1] * (xm*ym*zp) \\\n                   + input_field[ii+1,ij  ,ik+1] * (xp*ym*zp) \\\n                   + input_field[ii  ,ij+1,ik+1] * (xm*yp*zp) \\\n                   + input_field[ii+1,ij+1,ik  ] * (xp*yp*zm) \\\n                   + input_field[ii+1,ij+1,ik+1] * (xp*yp*zp)\n                opos[2] += ods[2]\n            opos[1] += ods[1]\n        opos[0] += ods[0]\n"
  },
  {
    "path": "yt/utilities/lib/lenses.pxd",
    "content": "\"\"\"\nDefinitions for the lens code\n\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.math cimport (\n    M_PI,\n    acos,\n    asin,\n    atan,\n    atan2,\n    cos,\n    exp,\n    fabs,\n    floor,\n    log2,\n    sin,\n    sqrt,\n)\n\nfrom yt.utilities.lib.fp_utils cimport fclip, fmax, fmin, i64clip, iclip, imax, imin\n\nfrom .image_samplers cimport (\n    ImageSampler,\n    calculate_extent_function,\n    generate_vector_info_function,\n)\nfrom .vec3_ops cimport L2_norm, dot, fma, subtract\nfrom .volume_container cimport VolumeContainer\n\n\ncdef extern from \"platform_dep.h\":\n    long int lrint(double x) noexcept nogil\n\ncdef extern from \"limits.h\":\n    cdef int SHRT_MAX\n\ncdef generate_vector_info_function generate_vector_info_plane_parallel\ncdef generate_vector_info_function generate_vector_info_null\ncdef calculate_extent_function calculate_extent_plane_parallel\ncdef calculate_extent_function calculate_extent_perspective\ncdef calculate_extent_function calculate_extent_null\n"
  },
  {
    "path": "yt/utilities/lib/lenses.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nFunctions for computing the extent of lenses and whatnot\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\nfrom .image_samplers cimport ImageSampler\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef int calculate_extent_plane_parallel(ImageSampler image,\n            VolumeContainer *vc, np.int64_t rv[4]) except -1 nogil:\n    # We do this for all eight corners\n    cdef np.float64_t temp\n    cdef np.float64_t *edges[2]\n    cdef np.float64_t cx, cy\n    cdef np.float64_t extrema[4]\n    cdef int i, j, k\n    edges[0] = vc.left_edge\n    edges[1] = vc.right_edge\n    extrema[0] = extrema[2] = 1e300; extrema[1] = extrema[3] = -1e300\n    for i in range(2):\n        for j in range(2):\n            for k in range(2):\n                # This should rotate it into the vector plane\n                temp  = edges[i][0] * image.x_vec[0]\n                temp += edges[j][1] * image.x_vec[1]\n                temp += edges[k][2] * image.x_vec[2]\n                if temp < extrema[0]: extrema[0] = temp\n                if temp > extrema[1]: extrema[1] = temp\n                temp  = edges[i][0] * image.y_vec[0]\n                temp += edges[j][1] * image.y_vec[1]\n                temp += edges[k][2] * image.y_vec[2]\n                if temp < extrema[2]: extrema[2] = temp\n                if temp > extrema[3]: extrema[3] = temp\n    cx = cy = 0.0\n    for i in range(3):\n        cx += image.center[i] * image.x_vec[i]\n        cy += image.center[i] * image.y_vec[i]\n    rv[0] = lrint((extrema[0] - cx - image.bounds[0])/image.pdx)\n    rv[1] = rv[0] + lrint((extrema[1] - extrema[0])/image.pdx)\n    rv[2] = lrint((extrema[2] - cy - image.bounds[2])/image.pdy)\n    rv[3] = rv[2] + lrint((extrema[3] - extrema[2])/image.pdy)\n    return 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef int calculate_extent_perspective(ImageSampler image,\n            VolumeContainer *vc, np.int64_t rv[4]) except -1 nogil:\n\n    cdef np.float64_t cam_pos[3]\n    cdef np.float64_t cam_width[3]\n    cdef np.float64_t north_vector[3]\n    cdef np.float64_t east_vector[3]\n    cdef np.float64_t normal_vector[3]\n    cdef np.float64_t vertex[3]\n    cdef np.float64_t pos1[3]\n    cdef np.float64_t sight_vector[3]\n    cdef np.float64_t sight_center[3]\n    cdef np.float64_t corners[3][8]\n    cdef float sight_vector_norm, sight_angle_cos, sight_length, dx, dy\n    cdef int i, iv, px, py\n    cdef int min_px, min_py, max_px, max_py\n\n    min_px = SHRT_MAX\n    min_py = SHRT_MAX\n    max_px = -SHRT_MAX\n    max_py = -SHRT_MAX\n\n    # calculate vertices for 8 corners of vc\n    corners[0][0] = vc.left_edge[0]\n    corners[0][1] = vc.right_edge[0]\n    corners[0][2] = vc.right_edge[0]\n    corners[0][3] = vc.left_edge[0]\n    corners[0][4] = vc.left_edge[0]\n    corners[0][5] = vc.right_edge[0]\n    corners[0][6] = vc.right_edge[0]\n    corners[0][7] = vc.left_edge[0]\n\n    corners[1][0] = vc.left_edge[1]\n    corners[1][1] = vc.left_edge[1]\n    corners[1][2] = vc.right_edge[1]\n    corners[1][3] = vc.right_edge[1]\n    corners[1][4] = vc.left_edge[1]\n    corners[1][5] = vc.left_edge[1]\n    corners[1][6] = vc.right_edge[1]\n    corners[1][7] = vc.right_edge[1]\n\n    corners[2][0] = vc.left_edge[2]\n    corners[2][1] = vc.left_edge[2]\n    corners[2][2] = vc.left_edge[2]\n    corners[2][3] = vc.left_edge[2]\n    corners[2][4] = vc.right_edge[2]\n    corners[2][5] = vc.right_edge[2]\n    corners[2][6] = vc.right_edge[2]\n    corners[2][7] = vc.right_edge[2]\n\n    # This code was ported from\n    #   yt.visualization.volume_rendering.lens.PerspectiveLens.project_to_plane()\n    for i in range(3):\n        cam_pos[i] = image.camera_data[0, i]\n        cam_width[i] = image.camera_data[1, i]\n        east_vector[i] = image.camera_data[2, i]\n        north_vector[i] = image.camera_data[3, i]\n        normal_vector[i] = image.camera_data[4, i]\n\n    for iv in range(8):\n        vertex[0] = corners[0][iv]\n        vertex[1] = corners[1][iv]\n        vertex[2] = corners[2][iv]\n\n        cam_width[1] = cam_width[0] * image.nv[1] / image.nv[0]\n\n        subtract(vertex, cam_pos, sight_vector)\n        fma(cam_width[2], normal_vector, cam_pos, sight_center)\n\n        sight_vector_norm = L2_norm(sight_vector)\n\n        if sight_vector_norm != 0:\n            for i in range(3):\n                sight_vector[i] /= sight_vector_norm\n\n        sight_angle_cos = dot(sight_vector, normal_vector)\n        sight_angle_cos = fclip(sight_angle_cos, -1.0, 1.0)\n\n        if acos(sight_angle_cos) < 0.5 * M_PI and sight_angle_cos != 0.0:\n            sight_length = cam_width[2] / sight_angle_cos\n        else:\n            sight_length = sqrt(cam_width[0] * cam_width[0] +\n                                cam_width[1] * cam_width[1])\n            sight_length /= sqrt(1.0 - sight_angle_cos * sight_angle_cos)\n\n        fma(sight_length, sight_vector, cam_pos, pos1)\n        subtract(pos1, sight_center, pos1)\n        dx = dot(pos1, east_vector)\n        dy = dot(pos1, north_vector)\n\n        px = int(image.nv[0] * 0.5 + image.nv[0] / cam_width[0] * dx)\n        py = int(image.nv[1] * 0.5 + image.nv[1] / cam_width[1] * dy)\n        min_px = min(min_px, px)\n        max_px = max(max_px, px)\n        min_py = min(min_py, py)\n        max_py = max(max_py, py)\n\n    rv[0] = max(min_px, 0)\n    rv[1] = min(max_px, image.nv[0])\n    rv[2] = max(min_py, 0)\n    rv[3] = min(max_py, image.nv[1])\n    return 0\n\n# We do this for a bunch of lenses.  Fallback is to grab them from the vector\n# info supplied.\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef int calculate_extent_null(ImageSampler image,\n            VolumeContainer *vc, np.int64_t rv[4]) except -1 nogil:\n    rv[0] = 0\n    rv[1] = image.nv[0]\n    rv[2] = 0\n    rv[3] = image.nv[1]\n    return 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef void generate_vector_info_plane_parallel(ImageSampler im,\n            np.int64_t vi, np.int64_t vj,\n            np.float64_t width[2],\n            # Now outbound\n            np.float64_t v_dir[3], np.float64_t v_pos[3]) noexcept nogil:\n    cdef int i\n    cdef np.float64_t px, py\n    px = width[0] * (<np.float64_t>vi)/(<np.float64_t>im.nv[0]-1) - width[0]/2.0\n    py = width[1] * (<np.float64_t>vj)/(<np.float64_t>im.nv[1]-1) - width[1]/2.0\n    # atleast_3d will add to beginning and end\n    v_pos[0] = im.vp_pos[0,0,0]*px + im.vp_pos[0,3,0]*py + im.vp_pos[0,9,0]\n    v_pos[1] = im.vp_pos[0,1,0]*px + im.vp_pos[0,4,0]*py + im.vp_pos[0,10,0]\n    v_pos[2] = im.vp_pos[0,2,0]*px + im.vp_pos[0,5,0]*py + im.vp_pos[0,11,0]\n    for i in range(3): v_dir[i] = im.vp_dir[0,i,0]\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef void generate_vector_info_null(ImageSampler im,\n            np.int64_t vi, np.int64_t vj,\n            np.float64_t width[2],\n            # Now outbound\n            np.float64_t v_dir[3], np.float64_t v_pos[3]) noexcept nogil:\n    cdef int i\n    for i in range(3):\n        # Here's a funny thing: we use vi here because our *image* will be\n        # flattened.  That means that im.nv will be a better one-d offset,\n        # since vp_pos has funny strides.\n        v_pos[i] = im.vp_pos[vi, vj, i]\n        v_dir[i] = im.vp_dir[vi, vj, i]\n"
  },
  {
    "path": "yt/utilities/lib/marching_cubes.h",
    "content": "int edge_table[256] = {\n    0x0  , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,\n    0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,\n    0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,\n    0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,\n    0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,\n    0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,\n    0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,\n    0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,\n    0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,\n    0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,\n    0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,\n    0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,\n    0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,\n    0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,\n    0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,\n    0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,\n    0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,\n    0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,\n    0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,\n    0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,\n    0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,\n    0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,\n    0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,\n    0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,\n    0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,\n    0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,\n    0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,\n    0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,\n    0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,\n    0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,\n    0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,\n    0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0\n};\n\nint tri_table[256][16] = {\n    {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1},\n    {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1},\n    {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1},\n    {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1},\n    {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1},\n    {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},\n    {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1},\n    {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1},\n    {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},\n    {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1},\n    {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1},\n    {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1},\n    {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1},\n    {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1},\n    {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1},\n    {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},\n    {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1},\n    {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1},\n    {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},\n    {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},\n    {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1},\n    {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1},\n    {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1},\n    {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1},\n    {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1},\n    {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1},\n    {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1},\n    {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1},\n    {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1},\n    {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1},\n    {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1},\n    {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1},\n    {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1},\n    {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1},\n    {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1},\n    {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1},\n    {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1},\n    {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1},\n    {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},\n    {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1},\n    {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1},\n    {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1},\n    {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},\n    {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},\n    {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1},\n    {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1},\n    {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1},\n    {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1},\n    {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1},\n    {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1},\n    {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},\n    {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1},\n    {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1},\n    {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1},\n    {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1},\n    {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1},\n    {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},\n    {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1},\n    {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1},\n    {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1},\n    {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1},\n    {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1},\n    {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1},\n    {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1},\n    {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1},\n    {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1},\n    {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1},\n    {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1},\n    {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1},\n    {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1},\n    {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1},\n    {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1},\n    {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1},\n    {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1},\n    {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1},\n    {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1},\n    {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1},\n    {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1},\n    {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1},\n    {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1},\n    {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1},\n    {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1},\n    {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1},\n    {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1},\n    {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1},\n    {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1},\n    {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1},\n    {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1},\n    {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1},\n    {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1},\n    {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},\n    {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},\n    {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},\n    {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1},\n    {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1},\n    {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1},\n    {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1},\n    {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1},\n    {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1},\n    {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1},\n    {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1},\n    {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1},\n    {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1},\n    {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1},\n    {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1},\n    {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1},\n    {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1},\n    {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1},\n    {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1},\n    {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1},\n    {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1},\n    {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1},\n    {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1},\n    {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},\n    {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},\n    {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1},\n    {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},\n    {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1},\n    {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1},\n    {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1},\n    {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1},\n    {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1},\n    {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1},\n    {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1},\n    {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1},\n    {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1},\n    {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1},\n    {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1},\n    {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1},\n    {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1},\n    {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1},\n    {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1},\n    {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1},\n    {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1},\n    {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1},\n    {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1},\n    {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1},\n    {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1},\n    {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1},\n    {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1},\n    {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1},\n    {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1},\n    {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1},\n    {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1},\n    {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1},\n    {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1},\n    {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1},\n    {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1},\n    {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1},\n    {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1},\n    {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1},\n    {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1},\n    {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1},\n    {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1},\n    {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1},\n    {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1},\n    {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1},\n    {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1},\n    {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1},\n    {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1},\n    {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1},\n    {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1},\n    {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1},\n    {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1},\n    {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1},\n    {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1},\n    {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1},\n    {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1},\n    {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1},\n    {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1},\n    {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1},\n    {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1},\n    {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1},\n    {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1},\n    {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1},\n    {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1},\n    {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1},\n    {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1},\n    {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1},\n    {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1},\n    {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1},\n    {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1},\n    {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1},\n    {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},\n    {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}\n};\n"
  },
  {
    "path": "yt/utilities/lib/marching_cubes.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS FIXED_INTERP\n# distutils: language = c++\n\"\"\"\nMarching cubes implementation\n\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\n\nimport numpy as np\n\nfrom libc.math cimport sqrt\nfrom libc.stdlib cimport free, malloc\n\nfrom .fixed_interpolator cimport (\n    eval_gradient,\n    offset_fill,\n    offset_interpolate,\n    vertex_interp,\n)\n\nfrom yt.units.yt_array import YTArray\n\n\ncdef extern from \"marching_cubes.h\":\n    int tri_table[256][16]\n    int edge_table[256]\n\ncdef struct Triangle:\n    Triangle *next\n    np.float64_t p[3][3]\n    np.float64_t val[3] # Usually only use one value\n\ncdef struct TriangleCollection:\n    int count\n    Triangle *first\n    Triangle *current\n\ncdef Triangle *AddTriangle(Triangle *self,\n                    np.float64_t p0[3], np.float64_t p1[3], np.float64_t p2[3]):\n    cdef Triangle *nn = <Triangle *> malloc(sizeof(Triangle))\n    if self != NULL:\n        self.next = nn\n    cdef int i\n    for i in range(3):\n        nn.p[0][i] = p0[i]\n    for i in range(3):\n        nn.p[1][i] = p1[i]\n    for i in range(3):\n        nn.p[2][i] = p2[i]\n    nn.next = NULL\n    return nn\n\ncdef int CountTriangles(Triangle *first):\n    cdef int count = 0\n    cdef Triangle *this = first\n    while this != NULL:\n        count += 1\n        this = this.next\n    return count\n\ncdef void FillTriangleValues(np.ndarray[np.float64_t, ndim=1] values,\n                             Triangle *first, int nskip = 1):\n    cdef Triangle *this = first\n    cdef int i = 0\n    cdef int j\n    while this != NULL:\n        for j in range(nskip):\n            values[i*nskip + j] = this.val[j]\n        i += 1\n        this = this.next\n\ncdef void WipeTriangles(Triangle *first):\n    cdef Triangle *this = first\n    cdef Triangle *last\n    while this != NULL:\n        last = this\n        this = this.next\n        free(last)\n\ncdef void FillAndWipeTriangles(np.ndarray[np.float64_t, ndim=2] vertices,\n                               Triangle *first):\n    cdef int count = 0\n    cdef Triangle *this = first\n    cdef Triangle *last\n    cdef int i, j\n    while this != NULL:\n        for i in range(3):\n            for j in range(3):\n                vertices[count, j] = this.p[i][j]\n            count += 1 # Do it at the end because it's an index\n        last = this\n        this = this.next\n        free(last)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef int march_cubes(\n                 np.float64_t gv[8], np.float64_t isovalue,\n                 np.float64_t dds[3],\n                 np.float64_t x, np.float64_t y, np.float64_t z,\n                 TriangleCollection *triangles):\n\n    cdef np.float64_t vertlist[12][3]\n    cdef int cubeindex = 0\n    cdef int n\n    cdef int nt = 0\n    for n in range(8):\n        if gv[n] < isovalue:\n            cubeindex |= (1 << n)\n    if edge_table[cubeindex] == 0:\n        return 0\n    if (edge_table[cubeindex] & 1): # 0,0,0 with 1,0,0\n        vertex_interp(gv[0], gv[1], isovalue, vertlist[0],\n                      dds, x, y, z, 0, 1)\n    if (edge_table[cubeindex] & 2): # 1,0,0 with 1,1,0\n        vertex_interp(gv[1], gv[2], isovalue, vertlist[1],\n                      dds, x, y, z, 1, 2)\n    if (edge_table[cubeindex] & 4): # 1,1,0 with 0,1,0\n        vertex_interp(gv[2], gv[3], isovalue, vertlist[2],\n                      dds, x, y, z, 2, 3)\n    if (edge_table[cubeindex] & 8): # 0,1,0 with 0,0,0\n        vertex_interp(gv[3], gv[0], isovalue, vertlist[3],\n                      dds, x, y, z, 3, 0)\n    if (edge_table[cubeindex] & 16): # 0,0,1 with 1,0,1\n        vertex_interp(gv[4], gv[5], isovalue, vertlist[4],\n                      dds, x, y, z, 4, 5)\n    if (edge_table[cubeindex] & 32): # 1,0,1 with 1,1,1\n        vertex_interp(gv[5], gv[6], isovalue, vertlist[5],\n                      dds, x, y, z, 5, 6)\n    if (edge_table[cubeindex] & 64): # 1,1,1 with 0,1,1\n        vertex_interp(gv[6], gv[7], isovalue, vertlist[6],\n                      dds, x, y, z, 6, 7)\n    if (edge_table[cubeindex] & 128): # 0,1,1 with 0,0,1\n        vertex_interp(gv[7], gv[4], isovalue, vertlist[7],\n                      dds, x, y, z, 7, 4)\n    if (edge_table[cubeindex] & 256): # 0,0,0 with 0,0,1\n        vertex_interp(gv[0], gv[4], isovalue, vertlist[8],\n                      dds, x, y, z, 0, 4)\n    if (edge_table[cubeindex] & 512): # 1,0,0 with 1,0,1\n        vertex_interp(gv[1], gv[5], isovalue, vertlist[9],\n                      dds, x, y, z, 1, 5)\n    if (edge_table[cubeindex] & 1024): # 1,1,0 with 1,1,1\n        vertex_interp(gv[2], gv[6], isovalue, vertlist[10],\n                      dds, x, y, z, 2, 6)\n    if (edge_table[cubeindex] & 2048): # 0,1,0 with 0,1,1\n        vertex_interp(gv[3], gv[7], isovalue, vertlist[11],\n                      dds, x, y, z, 3, 7)\n    n = 0\n    while 1:\n        triangles.current = AddTriangle(triangles.current,\n                    vertlist[tri_table[cubeindex][n  ]],\n                    vertlist[tri_table[cubeindex][n+1]],\n                    vertlist[tri_table[cubeindex][n+2]])\n        triangles.count += 1\n        nt += 1\n        if triangles.first == NULL:\n            triangles.first = triangles.current\n        n += 3\n        if tri_table[cubeindex][n] == -1: break\n    return nt\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef march_cubes_grid(np.float64_t isovalue,\n                     np.ndarray[np.float64_t, ndim=3] values,\n                     np.ndarray[np.uint8_t, ndim=3, cast=True] mask,\n                     np.ndarray[np.float64_t, ndim=1] left_edge,\n                     np.ndarray[np.float64_t, ndim=1] dxs,\n                     obj_sample = None, int sample_type = 1):\n    cdef int dims[3]\n    cdef int i, j, k, n, m, nt\n    cdef int offset\n    cdef np.float64_t gv[8]\n    cdef np.float64_t pos[3]\n    cdef np.float64_t point[3]\n    cdef np.float64_t idds[3]\n    cdef np.float64_t *intdata = NULL\n    cdef np.float64_t *sdata = NULL\n    cdef np.float64_t do_sample\n    cdef np.ndarray[np.float64_t, ndim=3] sample\n    cdef np.ndarray[np.float64_t, ndim=1] sampled\n    cdef TriangleCollection triangles\n    cdef Triangle *last\n    cdef Triangle *current\n    if obj_sample is not None:\n        sample = obj_sample\n        sdata = <np.float64_t *> sample.data\n        do_sample = sample_type # 1 for face, 2 for vertex\n    else:\n        do_sample = 0\n    for i in range(3):\n        dims[i] = values.shape[i] - 1\n        idds[i] = 1.0 / dxs[i]\n    triangles.first = triangles.current = NULL\n    last = current = NULL\n    triangles.count = 0\n    cdef np.float64_t *data = <np.float64_t *> values.data\n    cdef np.float64_t *dds = <np.float64_t *> dxs.data\n    pos[0] = left_edge[0]\n    for i in range(dims[0]):\n        pos[1] = left_edge[1]\n        for j in range(dims[1]):\n            pos[2] = left_edge[2]\n            for k in range(dims[2]):\n                if mask[i,j,k] == 1:\n                    offset = i * (dims[1] + 1) * (dims[2] + 1) \\\n                           + j * (dims[2] + 1) + k\n                    intdata = data + offset\n                    offset_fill(dims, intdata, gv)\n                    nt = march_cubes(gv, isovalue, dds, pos[0], pos[1], pos[2],\n                                &triangles)\n                    if nt == 0 or do_sample == 0:\n                        pos[2] += dds[2]\n                        continue\n                    if last == NULL and triangles.first != NULL:\n                        current = triangles.first\n                        last = NULL\n                    elif last != NULL:\n                        current = last.next\n                    if do_sample == 1:\n                        # At each triangle's center, sample our secondary field\n                        while current != NULL:\n                            for n in range(3):\n                                point[n] = 0.0\n                            for n in range(3):\n                                for m in range(3):\n                                    point[m] += (current.p[n][m]-pos[m])*idds[m]\n                            for n in range(3):\n                                point[n] /= 3.0\n                            current.val[0] = offset_interpolate(dims, point,\n                                                             sdata + offset)\n                            last = current\n                            if current.next == NULL: break\n                            current = current.next\n                    elif do_sample == 2:\n                        while current != NULL:\n                            for n in range(3):\n                                for m in range(3):\n                                    point[m] = (current.p[n][m]-pos[m])*idds[m]\n                                current.val[n] = offset_interpolate(dims,\n                                                    point, sdata + offset)\n                            last = current\n                            if current.next == NULL: break\n                            current = current.next\n                pos[2] += dds[2]\n            pos[1] += dds[1]\n        pos[0] += dds[0]\n    # Hallo, we are all done.\n    cdef np.ndarray[np.float64_t, ndim=2] vertices\n    vertices = np.zeros((triangles.count*3,3), dtype='float64')\n    if do_sample == 0:\n        FillAndWipeTriangles(vertices, triangles.first)\n        return vertices\n    cdef int nskip = 0\n    if do_sample == 1:\n        nskip = 1\n    elif do_sample == 2:\n        nskip = 3\n    sampled = np.zeros(triangles.count * nskip, dtype='float64')\n    FillTriangleValues(sampled, triangles.first, nskip)\n    FillAndWipeTriangles(vertices, triangles.first)\n    if hasattr(obj_sample, 'units'):\n        sampled = YTArray(sampled, obj_sample.units)\n    return vertices, sampled\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef march_cubes_grid_flux(\n                     np.float64_t isovalue,\n                     np.ndarray[np.float64_t, ndim=3] values,\n                     np.ndarray[np.float64_t, ndim=3] v1,\n                     np.ndarray[np.float64_t, ndim=3] v2,\n                     np.ndarray[np.float64_t, ndim=3] v3,\n                     np.ndarray[np.float64_t, ndim=3] flux_field,\n                     np.ndarray[np.uint8_t, ndim=3, cast=True] mask,\n                     np.ndarray[np.float64_t, ndim=1] left_edge,\n                     np.ndarray[np.float64_t, ndim=1] dxs):\n    cdef int dims[3]\n    cdef int i, j, k, n, m\n    cdef int offset\n    cdef np.float64_t gv[8]\n    cdef np.float64_t *intdata = NULL\n    cdef TriangleCollection triangles\n    cdef Triangle *current = NULL\n    cdef Triangle *last = NULL\n    cdef np.float64_t *data = <np.float64_t *> values.data\n    cdef np.float64_t *v1data = <np.float64_t *> v1.data\n    cdef np.float64_t *v2data = <np.float64_t *> v2.data\n    cdef np.float64_t *v3data = <np.float64_t *> v3.data\n    cdef np.float64_t *fdata = <np.float64_t *> flux_field.data\n    cdef np.float64_t *dds = <np.float64_t *> dxs.data\n    cdef np.float64_t flux = 0.0\n    cdef np.float64_t temp, area, s\n    cdef np.float64_t center[3]\n    cdef np.float64_t point[3]\n    cdef np.float64_t cell_pos[3]\n    cdef np.float64_t fv[3]\n    cdef np.float64_t idds[3]\n    cdef np.float64_t normal[3]\n    for i in range(3):\n        dims[i] = values.shape[i] - 1\n        idds[i] = 1.0 / dds[i]\n    triangles.first = triangles.current = NULL\n    triangles.count = 0\n    cell_pos[0] = left_edge[0]\n    for i in range(dims[0]):\n        cell_pos[1] = left_edge[1]\n        for j in range(dims[1]):\n            cell_pos[2] = left_edge[2]\n            for k in range(dims[2]):\n                if mask[i,j,k] == 1:\n                    offset = i * (dims[1] + 1) * (dims[2] + 1) \\\n                           + j * (dims[2] + 1) + k\n                    intdata = data + offset\n                    offset_fill(dims, intdata, gv)\n                    march_cubes(gv, isovalue, dds,\n                                cell_pos[0], cell_pos[1], cell_pos[2],\n                                &triangles)\n                    # Now our triangles collection has a bunch.  We now\n                    # calculate fluxes for each.\n                    if last == NULL and triangles.first != NULL:\n                        current = triangles.first\n                        last = NULL\n                    elif last != NULL:\n                        current = last.next\n                    while current != NULL:\n                        # Calculate the center of the triangle\n                        wval = 0.0\n                        for n in range(3):\n                            center[n] = 0.0\n                        for n in range(3):\n                            for m in range(3):\n                                point[m] = (current.p[n][m]-cell_pos[m])*idds[m]\n                            # Now we calculate the value at this point\n                            temp = offset_interpolate(dims, point, intdata)\n                            #print(\"something\", temp, point[0], point[1], point[2])\n                            wval += temp\n                            for m in range(3):\n                                center[m] += temp * point[m]\n                        # Now we divide by our normalizing factor\n                        for n in range(3):\n                            center[n] /= wval\n                        # We have our center point of the triangle, in 0..1\n                        # coordinates.  So now we interpolate our three\n                        # fields.\n                        fv[0] = offset_interpolate(dims, center, v1data + offset)\n                        fv[1] = offset_interpolate(dims, center, v2data + offset)\n                        fv[2] = offset_interpolate(dims, center, v3data + offset)\n                        # We interpolate again the actual value data\n                        wval = offset_interpolate(dims, center, fdata + offset)\n                        # Now we have our flux vector and our field value!\n                        # We just need a normal vector with which we can\n                        # dot it.  The normal should be equal to the gradient\n                        # in the center of the triangle, or thereabouts.\n                        eval_gradient(dims, center, intdata, normal)\n                        temp = 0.0\n                        for n in range(3):\n                            temp += normal[n]*normal[n]\n                        # Take the negative, to ensure it points inwardly\n                        temp = -sqrt(temp)\n                        # Dump this somewhere for now\n                        temp = wval * (fv[0] * normal[0] +\n                                       fv[1] * normal[1] +\n                                       fv[2] * normal[2])/temp\n                        # Now we need the area of the triangle.  This will take\n                        # a lot of time to calculate compared to the rest.\n                        # We use Heron's formula.\n                        for n in range(3):\n                            fv[n] = 0.0\n                        for n in range(3):\n                            fv[0] += (current.p[0][n] - current.p[2][n]) * (current.p[0][n] - current.p[2][n])\n                            fv[1] += (current.p[1][n] - current.p[0][n]) * (current.p[1][n] - current.p[0][n])\n                            fv[2] += (current.p[2][n] - current.p[1][n]) * (current.p[2][n] - current.p[1][n])\n                        s = 0.0\n                        for n in range(3):\n                            fv[n] = sqrt(fv[n])\n                            s += 0.5 * fv[n]\n                        area = (s*(s-fv[0])*(s-fv[1])*(s-fv[2]))\n                        area = sqrt(area)\n                        flux += temp*area\n                        last = current\n                        if current.next == NULL: break\n                        current = current.next\n                cell_pos[2] += dds[2]\n            cell_pos[1] += dds[1]\n        cell_pos[0] += dds[0]\n    # Hallo, we are all done.\n    WipeTriangles(triangles.first)\n    return flux\n"
  },
  {
    "path": "yt/utilities/lib/mesh_triangulation.h",
    "content": "#define MAX_NUM_TRI 12\n#define HEX_NV 8\n#define HEX_NT 12\n#define TETRA_NV 4\n#define TETRA_NT 4\n#define WEDGE_NV 6\n#define WEDGE_NT 8\n\n// This array is used to triangulate the hexahedral mesh elements\n// Each element has six faces with two triangles each.\n// The vertex ordering convention is assumed to follow that used\n// here: http://homepages.cae.wisc.edu/~tautges/papers/cnmev3.pdf\n// Note that this is the case for Exodus II data.\nint triangulate_hex[MAX_NUM_TRI][3] = {\n  {0, 2, 1}, {0, 3, 2}, // Face is 3 2 1 0\n  {4, 5, 6}, {4, 6, 7}, // Face is 4 5 6 7\n  {0, 1, 5}, {0, 5, 4}, // Face is 0 1 5 4\n  {1, 2, 6}, {1, 6, 5}, // Face is 1 2 6 5\n  {0, 7, 3}, {0, 4, 7}, // Face is 3 0 4 7\n  {3, 6, 2}, {3, 7, 6}  // Face is 2 3 7 6\n};\n\n// Similarly, this is used to triangulate the tetrahedral cells\nint triangulate_tetra[MAX_NUM_TRI][3] = {\n  {0, 1, 3},\n  {2, 3, 1},\n  {0, 3, 2},\n  {0, 2, 1},\n\n  {-1, -1, -1},\n  {-1, -1, -1},\n  {-1, -1, -1},\n  {-1, -1, -1},\n  {-1, -1, -1},\n  {-1, -1, -1},\n  {-1, -1, -1},\n  {-1, -1, -1}\n};\n\n// Triangulate wedges\nint triangulate_wedge[MAX_NUM_TRI][3] = {\n  {3, 0, 1},\n  {4, 3, 1},\n  {2, 5, 4},\n  {2, 4, 1},\n  {0, 3, 2},\n  {2, 3, 5},\n  {3, 4, 5},\n  {0, 2, 1},\n\n  {-1, -1, -1},\n  {-1, -1, -1},\n  {-1, -1, -1},\n  {-1, -1, -1}\n};\n\n\n// This is used to select faces from a 20-sided hex element\nint hex20_faces[6][8] = {\n  {0, 1, 5, 4, 12, 8,  13, 16},\n  {1, 2, 6, 5, 13, 9,  14, 17},\n  {3, 2, 6, 7, 15, 10, 14, 18},\n  {0, 3, 7, 4, 12, 11, 15, 19},\n  {4, 5, 6, 7, 19, 16, 17, 18},\n  {0, 1, 2, 3, 11, 8,  9,  10}\n};\n\n// This is used to select faces from a second-order tet element\nint tet10_faces[4][6] = {\n  {0, 1, 3, 4, 8, 7},\n  {2, 3, 1, 9, 8, 5},\n  {0, 3, 2, 7, 9, 6},\n  {0, 2, 1, 6, 5, 4}\n};\n"
  },
  {
    "path": "yt/utilities/lib/mesh_triangulation.pyx",
    "content": "\"\"\"\n\nThis file contains code for triangulating unstructured meshes. That is, for\nevery element in the mesh, it breaks up the element into some number of\ntriangles, returning a triangle mesh instead.\n\nIt also contains code for removing duplicate triangles from the resulting\nmesh using a hash-table approach, so that we don't waste time rendering\nimpossible-to-see triangles.\n\nThis code is currently used by the OpenGL-accelerated unstructured mesh\nrenderer, as well as when annotating mesh lines on regular slices.\n\n\"\"\"\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.exceptions import YTElementTypeNotRecognized\n\n\ncdef extern from \"mesh_triangulation.h\":\n    enum:\n        MAX_NUM_TRI\n    int HEX_NV\n    int HEX_NT\n    int TETRA_NV\n    int TETRA_NT\n    int WEDGE_NV\n    int WEDGE_NT\n    int triangulate_hex[MAX_NUM_TRI][3]\n    int triangulate_tetra[MAX_NUM_TRI][3]\n    int triangulate_wedge[MAX_NUM_TRI][3]\n    int hex20_faces[6][8]\n    int tet10_faces[4][6]\n\ncdef struct TriNode:\n    np.uint64_t key\n    np.int64_t elem\n    np.int64_t tri[3]\n    TriNode* next_node\n\ncdef np.int64_t triangles_are_equal(np.int64_t tri1[3], np.int64_t tri2[3]) noexcept nogil:\n    cdef np.int64_t found\n    for i in range(3):\n        found = False\n        for j in range(3):\n            if tri1[i] == tri2[j]:\n                found = True\n        if not found:\n            return 0\n    return 1\n\ncdef np.uint64_t triangle_hash(np.int64_t tri[3]) noexcept nogil:\n    # http://stackoverflow.com/questions/1536393/good-hash-function-for-permutations\n    cdef np.uint64_t h = 1\n    for i in range(3):\n        h *= (1779033703 + 2*tri[i])\n    return h // 2\n\n# should be enough, consider dynamic resizing in the future\ncdef np.int64_t TABLE_SIZE = 2**24\n\ncdef class TriSet:\n    '''\n\n    This is a hash table data structure for rapidly identifying the exterior\n    triangles in a polygon mesh. We loop over each triangle in each element and\n    update the TriSet for each one. We keep only the triangles that appear once,\n    as these make up the exterior of the mesh.\n\n    '''\n\n    cdef TriNode **table\n    cdef np.uint64_t num_items\n\n    def __cinit__(self):\n        self.table = <TriNode**> malloc(TABLE_SIZE * sizeof(TriNode*))\n        for i in range(TABLE_SIZE):\n            self.table[i] = NULL\n        self.num_items = 0\n\n    def __dealloc__(self):\n        cdef np.int64_t i\n        cdef TriNode *node\n        cdef TriNode *delete_node\n        for i in range(TABLE_SIZE):\n            node = self.table[i]\n            while (node != NULL):\n                delete_node = node\n                node = node.next_node\n                free(delete_node)\n            self.table[i] = NULL\n        free(self.table)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def get_exterior_tris(self):\n        '''\n\n        Returns two numpy arrays, one storing the exterior triangle\n        indices and the other storing the corresponding element ids.\n\n        '''\n\n        cdef np.int64_t[:, ::1] tri_indices = np.empty((self.num_items, 3), dtype=\"int64\")\n        cdef np.int64_t[::1] element_map = np.empty(self.num_items, dtype=\"int64\")\n\n        cdef TriNode* node\n        cdef np.int64_t counter = 0\n        cdef np.int64_t i, j\n        for i in range(TABLE_SIZE):\n            node = self.table[i]\n            while node != NULL:\n                for j in range(3):\n                    tri_indices[counter, j] = node.tri[j]\n                element_map[counter] = node.elem\n                counter += 1\n                node = node.next_node\n\n        return tri_indices, element_map\n\n    cdef TriNode* _allocate_new_node(self,\n                                     np.int64_t tri[3],\n                                     np.uint64_t key,\n                                     np.int64_t elem) noexcept nogil:\n        cdef TriNode* new_node = <TriNode* > malloc(sizeof(TriNode))\n        new_node.key = key\n        new_node.elem = elem\n        new_node.tri[0] = tri[0]\n        new_node.tri[1] = tri[1]\n        new_node.tri[2] = tri[2]\n        new_node.next_node = NULL\n        self.num_items += 1\n        return new_node\n\n    @cython.cdivision(True)\n    cdef void update(self, np.int64_t tri[3], np.int64_t elem) noexcept nogil:\n        cdef np.uint64_t key = triangle_hash(tri)\n        cdef np.uint64_t index = key % TABLE_SIZE\n        cdef TriNode *node = self.table[index]\n\n        if node == NULL:\n            self.table[index] = self._allocate_new_node(tri, key, elem)\n            return\n\n        if key == node.key and triangles_are_equal(node.tri, tri):\n            # this triangle is already here, delete it\n            self.table[index] = node.next_node\n            free(node)\n            self.num_items -= 1\n            return\n\n        elif node.next_node == NULL:\n            node.next_node = self._allocate_new_node(tri, key, elem)\n            return\n\n        # walk through node list\n        cdef TriNode* prev = node\n        node = node.next_node\n        while node != NULL:\n            if key == node.key and triangles_are_equal(node.tri, tri):\n                # this triangle is already here, delete it\n                prev.next_node = node.next_node\n                free(node)\n                self.num_items -= 1\n                return\n            if node.next_node == NULL:\n                # we have reached the end; add new node\n                node.next_node = self._allocate_new_node(tri, key, elem)\n                return\n            prev = node\n            node = node.next_node\n\n\ncdef class MeshInfoHolder:\n    cdef np.int64_t num_elem\n    cdef np.int64_t num_tri\n    cdef np.int64_t num_verts\n    cdef np.int64_t VPE  # num verts per element\n    cdef np.int64_t TPE  # num tris per element\n    cdef int[MAX_NUM_TRI][3] tri_array\n\n    def __cinit__(self, np.int64_t[:, ::1] indices):\n        '''\n\n        This class is used to store metadata about the type of mesh being used.\n\n        '''\n\n        self.num_elem = indices.shape[0]\n        self.VPE = indices.shape[1]\n\n        if (self.VPE == 8 or self.VPE == 20 or self.VPE == 27):\n            self.TPE = HEX_NT\n            self.tri_array = triangulate_hex\n        elif (self.VPE == 4 or self.VPE == 10):\n            self.TPE = TETRA_NT\n            self.tri_array = triangulate_tetra\n        elif self.VPE == 6:\n            self.TPE = WEDGE_NT\n            self.tri_array = triangulate_wedge\n        else:\n            raise YTElementTypeNotRecognized(3, self.VPE)\n\n        self.num_tri = self.TPE * self.num_elem\n        self.num_verts = self.num_tri * 3\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef cull_interior_triangles(np.int64_t[:, ::1] indices):\n    '''\n\n    This is used to remove interior triangles from the mesh before rendering\n    it on the GPU.\n\n    '''\n\n    cdef MeshInfoHolder m = MeshInfoHolder(indices)\n\n    cdef TriSet s = TriSet()\n    cdef np.int64_t i, j, k\n    cdef np.int64_t tri[3]\n    for i in range(m.num_elem):\n        for j in range(m.TPE):\n            for k in range(3):\n                tri[k] = indices[i, m.tri_array[j][k]]\n            s.update(tri, i)\n\n    return s.get_exterior_tris()\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef get_vertex_data(np.float64_t[:, ::1] coords,\n                    np.float64_t[:, ::1] data,\n                    np.int64_t[:, ::1] indices):\n\n    '''\n\n    This converts the data array from the shape (num_elem, conn_length)\n    to (num_verts, ).\n\n    '''\n\n    cdef MeshInfoHolder m = MeshInfoHolder(indices)\n    cdef np.int64_t num_verts = coords.shape[0]\n    cdef np.float32_t[:] vertex_data = np.zeros(num_verts, dtype=\"float32\")\n\n    cdef np.int64_t i, j\n    for i in range(m.num_elem):\n        for j in range(m.VPE):\n            vertex_data[indices[i, j]] = data[i, j]\n    return vertex_data\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef triangulate_mesh(np.float64_t[:, ::1] coords,\n                     np.ndarray data,\n                     np.int64_t[:, ::1] indices):\n    '''\n\n    This converts a mesh into a flattened triangle array suitable for\n    rendering on the GPU.\n\n    '''\n    cdef np.int64_t[:, ::1] exterior_tris\n    cdef np.int64_t[::1] element_map\n    exterior_tris, element_map = cull_interior_triangles(indices)\n\n    cdef np.int64_t num_tri = exterior_tris.shape[0]\n    cdef np.int64_t num_verts = 3 * num_tri\n    cdef np.int64_t num_coords = 3 * num_verts\n\n    cdef np.float32_t[:] vertex_data\n    if data.ndim == 2:\n        vertex_data = get_vertex_data(coords, data, indices)\n    else:\n        vertex_data = data.astype(\"float32\")\n\n    cdef np.int32_t[:] tri_indices = np.empty(num_verts, dtype=np.int32)\n    cdef np.float32_t[:] tri_data = np.empty(num_verts, dtype=np.float32)\n    cdef np.float32_t[:] tri_coords = np.empty(num_coords, dtype=np.float32)\n\n    cdef np.int64_t vert_index, i, j, k\n    for i in range(num_tri):\n        for j in range(3):\n            vert_index = i*3 + j\n            if data.ndim == 1:\n                tri_data[vert_index] = vertex_data[element_map[i]]\n            else:\n                tri_data[vert_index] = vertex_data[exterior_tris[i, j]]\n            tri_indices[vert_index] = vert_index\n            for k in range(3):\n                tri_coords[vert_index*3 + k] = coords[exterior_tris[i, j], k]\n\n    return np.array(tri_coords), np.array(tri_data), np.array(tri_indices)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef triangulate_indices(np.int64_t[:, ::1] indices):\n    '''\n\n    This is like triangulate_mesh, except it only considers the\n    connectivity information, instead of also copying the vertex\n    coordinates and the data values.\n\n    '''\n\n    cdef MeshInfoHolder m = MeshInfoHolder(indices)\n    cdef np.int64_t[:, ::1] tri_indices = np.empty((m.num_tri, 3), dtype=np.int_)\n\n    cdef np.int64_t i, j, k\n    for i in range(m.num_elem):\n        for j in range(m.TPE):\n            for k in range(3):\n                tri_indices[i*m.TPE + j, k] = indices[i, m.tri_array[j][k]]\n    return np.array(tri_indices)\n"
  },
  {
    "path": "yt/utilities/lib/mesh_utilities.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nUtilities for unstructured and semi-structured meshes\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\nfrom yt.utilities.lib.fp_utils cimport fmax, fmin\n\n\ncdef extern from \"platform_dep.h\":\n    double rint(double x)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef fill_fcoords(np.ndarray[np.float64_t, ndim=2] coords,\n                 np.ndarray[np.int64_t, ndim=2] indices,\n                 int offset = 0):\n    cdef np.ndarray[np.float64_t, ndim=2] fcoords\n    cdef int nc = indices.shape[0]\n    cdef int nv = indices.shape[1]\n    cdef np.float64_t pos[3]\n    cdef int i, j, k\n    fcoords = np.empty((nc, 3), dtype=\"float64\")\n    for i in range(nc):\n        for j in range(3):\n            pos[j] = 0.0\n        for j in range(nv):\n            for k in range(3):\n                pos[k] += coords[indices[i, j] - offset, k]\n        for j in range(3):\n            pos[j] /= nv\n            fcoords[i, j] = pos[j]\n    return fcoords\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef fill_fwidths(np.ndarray[np.float64_t, ndim=2] coords,\n                 np.ndarray[np.int64_t, ndim=2] indices,\n                 int offset = 0):\n    cdef np.ndarray[np.float64_t, ndim=2] fwidths\n    cdef int nc = indices.shape[0]\n    cdef int nv = indices.shape[1]\n    if nv != 8:\n        raise NotImplementedError\n    cdef np.float64_t LE[3]\n    cdef np.float64_t RE[3]\n    cdef int i, j, k\n    cdef np.float64_t pos\n    fwidths = np.empty((nc, 3), dtype=\"float64\")\n    for i in range(nc):\n        for j in range(3):\n            LE[j] = 1e60\n            RE[j] = -1e60\n        for j in range(nv):\n            for k in range(3):\n                pos = coords[indices[i, j] - offset, k]\n                LE[k] = fmin(pos, LE[k])\n                RE[k] = fmax(pos, RE[k])\n        for j in range(3):\n            fwidths[i, j] = RE[j] - LE[j]\n    return fwidths\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef smallest_fwidth(np.ndarray[np.float64_t, ndim=2] coords,\n                    np.ndarray[np.int64_t, ndim=2] indices,\n                    int offset = 0):\n    cdef np.float64_t fwidth = 1e60, pos\n    cdef int nc = indices.shape[0]\n    cdef int nv = indices.shape[1]\n    if nv != 8:\n        raise NotImplementedError\n    cdef np.float64_t LE[3]\n    cdef np.float64_t RE[3]\n    cdef int i, j, k\n    for i in range(nc):\n        for j in range(3):\n            LE[j] = 1e60\n            RE[j] = -1e60\n        for j in range(nv):\n            for k in range(3):\n                pos = coords[indices[i, j] - offset, k]\n                LE[k] = fmin(pos, LE[k])\n                RE[k] = fmax(pos, RE[k])\n        for j in range(3):\n            fwidth = fmin(fwidth, RE[j] - LE[j])\n    return fwidth\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef clamp_edges(np.float64_t[:] edge, np.float64_t[:] pleft,\n                np.float64_t[:] pdx):\n    \"\"\"Clamp edge to pleft + pdx*n where n is the closest integer\n\n    Note that edge is modified in-place.\n    \"\"\"\n    cdef np.float64_t start_index\n    cdef np.float64_t integer_index\n    cdef np.intp_t shape = edge.shape[0]\n    for i in range(shape):\n        start_index = (edge[i] - pleft[i]) / pdx[i]\n        integer_index = rint(start_index)\n        edge[i] = integer_index * pdx[i] + pleft[i]\n"
  },
  {
    "path": "yt/utilities/lib/misc_utilities.pyx",
    "content": "# distutils: libraries = STD_LIBS\n# distutils: language = c++\n# distutils: extra_compile_args = CPP14_FLAG OMP_ARGS\n# distutils: extra_link_args = CPP14_FLAG OMP_ARGS\n\"\"\"\nSimple utilities that don't fit anywhere else\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\nfrom yt.funcs import get_pbar\nfrom yt.units.yt_array import YTArray\n\ncimport cython\ncimport numpy as np\nfrom cpython cimport buffer\nfrom cython.view cimport memoryview\nfrom libc.math cimport abs, sqrt\nfrom libc.stdlib cimport free, malloc\nfrom libc.string cimport strcmp\n\nfrom yt.geometry.selection_routines cimport _ensure_code\nfrom yt.utilities.lib.fp_utils cimport fmax, fmin\n\n\ncdef extern from \"platform_dep.h\":\n    # NOTE that size_t might not be int\n    void *alloca(int)\n\nfrom cython.parallel import prange\n\nfrom cpython.exc cimport PyErr_CheckSignals\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef new_bin_profile1d(np.ndarray[np.intp_t, ndim=1] bins_x,\n                  np.ndarray[np.float64_t, ndim=1] wsource,\n                  np.ndarray[np.float64_t, ndim=2] bsource,\n                  np.ndarray[np.float64_t, ndim=1] wresult,\n                  np.ndarray[np.float64_t, ndim=2] bresult,\n                  np.ndarray[np.float64_t, ndim=2] mresult,\n                  np.ndarray[np.float64_t, ndim=2] qresult,\n                  np.ndarray[np.uint8_t, ndim=1, cast=True] used):\n    cdef int n, fi, bin\n    cdef np.float64_t wval, bval, oldwr, bval_mresult\n    cdef int nb = bins_x.shape[0]\n    cdef int nf = bsource.shape[1]\n    for n in range(nb):\n        bin = bins_x[n]\n        wval = wsource[n]\n        # Skip field value entries where the weight field is zero\n        if wval == 0:\n            continue\n        oldwr = wresult[bin]\n        wresult[bin] += wval\n        for fi in range(nf):\n            bval = bsource[n,fi]\n            bval_mresult = bval - mresult[bin,fi]\n            # qresult has to have the previous wresult\n            qresult[bin,fi] += oldwr * wval * bval_mresult * bval_mresult / \\\n                (oldwr + wval)\n            bresult[bin,fi] += wval*bval\n            # mresult needs the new wresult\n            mresult[bin,fi] += wval * bval_mresult / wresult[bin]\n        used[bin] = 1\n    return\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef new_bin_profile2d(np.ndarray[np.intp_t, ndim=1] bins_x,\n                  np.ndarray[np.intp_t, ndim=1] bins_y,\n                  np.ndarray[np.float64_t, ndim=1] wsource,\n                  np.ndarray[np.float64_t, ndim=2] bsource,\n                  np.ndarray[np.float64_t, ndim=2] wresult,\n                  np.ndarray[np.float64_t, ndim=3] bresult,\n                  np.ndarray[np.float64_t, ndim=3] mresult,\n                  np.ndarray[np.float64_t, ndim=3] qresult,\n                  np.ndarray[np.uint8_t, ndim=2, cast=True] used):\n    cdef int n, fi, bin_x, bin_y\n    cdef np.float64_t wval, bval, oldwr, bval_mresult\n    cdef int nb = bins_x.shape[0]\n    cdef int nf = bsource.shape[1]\n    for n in range(nb):\n        bin_x = bins_x[n]\n        bin_y = bins_y[n]\n        wval = wsource[n]\n        # Skip field value entries where the weight field is zero\n        if wval == 0:\n            continue\n        oldwr = wresult[bin_x, bin_y]\n        wresult[bin_x,bin_y] += wval\n        for fi in range(nf):\n            bval = bsource[n,fi]\n            bval_mresult = bval - mresult[bin_x,bin_y,fi]\n            # qresult has to have the previous wresult\n            qresult[bin_x,bin_y,fi] += oldwr * wval * bval_mresult * bval_mresult / \\\n                (oldwr + wval)\n            bresult[bin_x,bin_y,fi] += wval*bval\n            # mresult needs the new wresult\n            mresult[bin_x,bin_y,fi] += wval * bval_mresult / wresult[bin_x,bin_y]\n        used[bin_x,bin_y] = 1\n    return\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef new_bin_profile3d(np.ndarray[np.intp_t, ndim=1] bins_x,\n                  np.ndarray[np.intp_t, ndim=1] bins_y,\n                  np.ndarray[np.intp_t, ndim=1] bins_z,\n                  np.ndarray[np.float64_t, ndim=1] wsource,\n                  np.ndarray[np.float64_t, ndim=2] bsource,\n                  np.ndarray[np.float64_t, ndim=3] wresult,\n                  np.ndarray[np.float64_t, ndim=4] bresult,\n                  np.ndarray[np.float64_t, ndim=4] mresult,\n                  np.ndarray[np.float64_t, ndim=4] qresult,\n                  np.ndarray[np.uint8_t, ndim=3, cast=True] used):\n    cdef int n, fi, bin_x, bin_y, bin_z\n    cdef np.float64_t wval, bval, oldwr, bval_mresult\n    cdef int nb = bins_x.shape[0]\n    cdef int nf = bsource.shape[1]\n    for n in range(nb):\n        bin_x = bins_x[n]\n        bin_y = bins_y[n]\n        bin_z = bins_z[n]\n        wval = wsource[n]\n        # Skip field value entries where the weight field is zero\n        if wval == 0:\n            continue\n        oldwr = wresult[bin_x, bin_y, bin_z]\n        wresult[bin_x,bin_y,bin_z] += wval\n        for fi in range(nf):\n            bval = bsource[n,fi]\n            bval_mresult = bval - mresult[bin_x,bin_y,bin_z,fi]\n            # qresult has to have the previous wresult\n            qresult[bin_x,bin_y,bin_z,fi] += \\\n                oldwr * wval * bval_mresult * bval_mresult / \\\n                (oldwr + wval)\n            bresult[bin_x,bin_y,bin_z,fi] += wval*bval\n            # mresult needs the new wresult\n            mresult[bin_x,bin_y,bin_z,fi] += wval * bval_mresult / \\\n                 wresult[bin_x,bin_y,bin_z]\n        used[bin_x,bin_y,bin_z] = 1\n    return\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef lines(np.float64_t[:,:,:] image,\n          np.int64_t[:] xs,\n          np.int64_t[:] ys,\n          np.float64_t[:,:] colors,\n          int points_per_color=1,\n          int thick=1,\n\t      int flip=0,\n          int crop = 0):\n\n    cdef int nx = image.shape[0]\n    cdef int ny = image.shape[1]\n    cdef int nl = xs.shape[0]\n    cdef np.float64_t alpha[4]\n    cdef np.float64_t outa\n    cdef int i, j, xi, yi\n    cdef int dx, dy, sx, sy, e2, err\n    cdef np.int64_t x0, x1, y0, y1\n    cdef int has_alpha = (image.shape[2] == 4)\n    cdef int no_color = (image.shape[2] < 3)\n    for j in range(0, nl, 2):\n        # From wikipedia http://en.wikipedia.org/wiki/Bresenham's_line_algorithm\n        x0 = xs[j]\n        y0 = ys[j]\n        x1 = xs[j+1]\n        y1 = ys[j+1]\n        dx = abs(x1-x0)\n        dy = abs(y1-y0)\n        if crop == 1 and (dx > nx/2.0 or dy > ny/2.0):\n            continue\n        err = dx - dy\n\n        if no_color:\n            for i in range(4):\n                alpha[i] = colors[j, 0]\n        elif has_alpha:\n            for i in range(4):\n                alpha[i] = colors[j/points_per_color,i]\n        else:\n            for i in range(3):\n                alpha[i] = colors[j/points_per_color,3]*\\\n                        colors[j/points_per_color,i]\n\n        if x0 < x1:\n            sx = 1\n        else:\n            sx = -1\n        if y0 < y1:\n            sy = 1\n        else:\n            sy = -1\n        while(1):\n            if (x0 < thick and sx == -1): break\n            elif (x0 >= nx-thick+1 and sx == 1): break\n            elif (y0 < thick and sy == -1): break\n            elif (y0 >= ny-thick+1 and sy == 1): break\n            if x0 >= thick and x0 < nx-thick and y0 >= thick and y0 < ny-thick:\n                for xi in range(x0-thick/2, x0+(1+thick)/2):\n                    for yi in range(y0-thick/2, y0+(1+thick)/2):\n                        if flip:\n                            yi0 = ny - yi\n                        else:\n                            yi0 = yi\n\n                        if no_color:\n                            image[xi, yi0, 0] = fmin(alpha[0], image[xi, yi0, 0])\n                        elif has_alpha:\n                            image[xi, yi0, 3] = outa = alpha[3] + image[xi, yi0, 3]*(1-alpha[3])\n                            if outa != 0.0:\n                                outa = 1.0/outa\n                            for i in range(3):\n                                image[xi, yi0, i] = \\\n                                        ((1.-alpha[3])*image[xi, yi0, i]*image[xi, yi0, 3]\n                                         + alpha[3]*alpha[i])*outa\n                        else:\n                            for i in range(3):\n                                image[xi, yi0, i] = \\\n                                        (1.-alpha[i])*image[xi,yi0,i] + alpha[i]\n\n\n            if (x0 == x1 and y0 == y1):\n                break\n            e2 = 2*err\n            if e2 > -dy:\n                err = err - dy\n                x0 += sx\n            if e2 < dx :\n                err = err + dx\n                y0 += sy\n    return\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef zlines(np.ndarray[np.float64_t, ndim=3] image,\n        np.ndarray[np.float64_t, ndim=2] zbuffer,\n        np.ndarray[np.int64_t, ndim=1] xs,\n        np.ndarray[np.int64_t, ndim=1] ys,\n        np.ndarray[np.float64_t, ndim=1] zs,\n        np.ndarray[np.float64_t, ndim=2] colors,\n        int points_per_color=1,\n        int thick=1,\n        int flip=0,\n        int crop = 0):\n\n    cdef int nx = image.shape[0]\n    cdef int ny = image.shape[1]\n    cdef int nl = xs.shape[0]\n    cdef np.float64_t[:] alpha\n    cdef int i, j, c\n    cdef int dx, dy, sx, sy, e2, err\n    cdef np.int64_t x0, x1, y0, y1, yi0\n    cdef np.float64_t z0, z1, dzx, dzy\n    alpha = np.zeros(4)\n    for j in range(0, nl, 2):\n        # From wikipedia http://en.wikipedia.org/wiki/Bresenham's_line_algorithm\n        x0 = xs[j]\n        y0 = ys[j]\n        x1 = xs[j+1]\n        y1 = ys[j+1]\n        z0 = zs[j]\n        z1 = zs[j+1]\n        dx = abs(x1-x0)\n        dy = abs(y1-y0)\n        dzx = (z1-z0) / (dx * dx + dy * dy) * dx\n        dzy = (z1-z0) / (dx * dx + dy * dy) * dy\n        err = dx - dy\n        if crop == 1 and (dx > nx/2.0 or dy > ny/2.0):\n            continue\n\n        c = j/points_per_color/2\n\n        for i in range(3):\n            alpha[i] = colors[c, i] * colors[c, 3]\n        alpha[3] = colors[c, 3]\n\n        if x0 < x1:\n            sx = 1\n        else:\n            sx = -1\n        if y0 < y1:\n            sy = 1\n        else:\n            sy = -1\n        while(1):\n            if (x0 < thick and sx == -1): break\n            elif (x0 >= nx-thick+1 and sx == 1): break\n            elif (y0 < thick and sy == -1): break\n            elif (y0 >= ny-thick+1 and sy == 1): break\n            if x0 >= thick and x0 < nx-thick and y0 >= thick and y0 < ny-thick:\n                for _ in range(x0-thick/2, x0+(1+thick)/2):\n                    for yi in range(y0-thick/2, y0+(1+thick)/2):\n                        if flip:\n                            yi0 = ny - yi\n                        else:\n                            yi0 = yi\n                        if z0 < zbuffer[x0, yi0]:\n                            if alpha[3] != 1.0:\n                                talpha = image[x0, yi0, 3]\n                                image[x0, yi0, 3] = alpha[3] + talpha * (1 - alpha[3])\n                                for i in range(3):\n                                    if image[x0, yi0, 3] == 0.0:\n                                        image[x0, yi0, i] = 0.0\n                                    else:\n                                        image[x0, yi0, i] = (alpha[3]*alpha[i] + image[x0, yi0, i]*talpha*(1.0-alpha[3]))/image[x0,yi0,3]\n                            else:\n                                for i in range(4):\n                                    image[x0, yi0, i] = alpha[i]\n                            if (1.0 - image[x0, yi0, 3] < 1.0e-4):\n                                image[x0, yi0, 3] = 1.0\n                                zbuffer[x0, yi0] = z0\n\n            if (x0 == x1 and y0 == y1):\n                break\n            e2 = 2*err\n            if e2 > -dy:\n                err = err - dy\n                x0 += sx\n                z0 += dzx\n            if e2 < dx :\n                err = err + dx\n                y0 += sy\n                z0 += dzy\n        # assert(np.abs(z0 - z1) < 1.0e-3 * (np.abs(z0) + np.abs(z1)))\n    return\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef zpoints(np.ndarray[np.float64_t, ndim=3] image,\n        np.ndarray[np.float64_t, ndim=2] zbuffer,\n        np.ndarray[np.int64_t, ndim=1] xs,\n        np.ndarray[np.int64_t, ndim=1] ys,\n        np.ndarray[np.float64_t, ndim=1] zs,\n        np.ndarray[np.float64_t, ndim=2] colors,\n        np.ndarray[np.int64_t, ndim=1] radii, #pixels\n        int points_per_color=1,\n        int thick=1,\n        int flip=0):\n\n    cdef int nx = image.shape[0]\n    cdef int ny = image.shape[1]\n    cdef np.float64_t[:] alpha\n    cdef np.float64_t talpha\n    cdef int i, j, c\n    cdef np.int64_t kx, ky, r, r2\n    cdef np.int64_t[:] idx, ks\n    cdef np.int64_t x0, y0, yi0\n    cdef np.float64_t z0\n    alpha = np.zeros(4)\n    #the sources must be ordered along z to avoid edges when two overlap\n    idx = np.argsort(zs)\n    for j in idx:\n        r = radii[j]\n        r2 = int((r+0.3)*(r+0.3))  #0.3 to get nicer shape\n        ks = np.arange(-r, r+1, dtype=np.int64)\n        z0 = zs[j]\n        for kx in ks:\n            x0 = xs[j]+kx\n            if (x0 < 0 or x0 >= nx): continue\n            for ky in ks:\n                y0 = ys[j]+ky\n                if (y0 < 0 or y0 >= ny): continue\n                if (kx*kx + ky*ky > r2): continue\n\n                c = j/points_per_color\n                for i in range(3):\n                    alpha[i] = colors[c, i] * colors[c, 3]\n                alpha[3] = colors[c, 3]\n                if flip:\n                    yi0 = ny - y0\n                else:\n                    yi0 = y0\n\n                if z0 < zbuffer[x0, yi0]:\n                    if alpha[3] != 1.0:\n                        talpha = image[x0, yi0, 3]\n                        image[x0, yi0, 3] = alpha[3] + talpha * (1 - alpha[3])\n                        for i in range(3):\n                            image[x0, yi0, i] = (alpha[3]*alpha[i] + image[x0, yi0, i]*talpha*(1.0-alpha[3]))/image[x0,yi0,3]\n                            if image[x0, yi0, 3] == 0.0:\n                                image[x0, yi0, i] = 0.0\n                    else:\n                        for i in range(4):\n                            image[x0, yi0, i] = alpha[i]\n                    if (1.0 - image[x0, yi0, 3] < 1.0e-4):\n                        zbuffer[x0, yi0] = z0\n    return\n\n\ndef rotate_vectors(np.ndarray[np.float64_t, ndim=3] vecs,\n        np.ndarray[np.float64_t, ndim=2] R):\n    cdef int nx = vecs.shape[0]\n    cdef int ny = vecs.shape[1]\n    rotated = np.empty((nx,ny,3),dtype='float64')\n    for i in range(nx):\n        for j in range(ny):\n            for k in range(3):\n                rotated[i,j,k] =\\\n                    R[k,0]*vecs[i,j,0]+R[k,1]*vecs[i,j,1]+R[k,2]*vecs[i,j,2]\n    return rotated\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef get_color_bounds(np.ndarray[np.float64_t, ndim=1] px,\n                     np.ndarray[np.float64_t, ndim=1] py,\n                     np.ndarray[np.float64_t, ndim=1] pdx,\n                     np.ndarray[np.float64_t, ndim=1] pdy,\n                     np.ndarray[np.float64_t, ndim=1] value,\n                     np.float64_t leftx, np.float64_t rightx,\n                     np.float64_t lefty, np.float64_t righty,\n                     np.float64_t mindx = -1, np.float64_t maxdx = -1):\n    cdef int i\n    cdef np.float64_t mi = 1e100, ma = -1e100, v\n    cdef int npx = px.shape[0]\n    with nogil:\n        for i in range(npx):\n            v = value[i]\n            if v < mi or v > ma:\n                if px[i] + pdx[i] < leftx: continue\n                if px[i] - pdx[i] > rightx: continue\n                if py[i] + pdy[i] < lefty: continue\n                if py[i] - pdy[i] > righty: continue\n                if pdx[i] < mindx or pdy[i] < mindx: continue\n                if maxdx > 0 and (pdx[i] > maxdx or pdy[i] > maxdx): continue\n                if v < mi: mi = v\n                if v > ma: ma = v\n    return (mi, ma)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef kdtree_get_choices(np.ndarray[np.float64_t, ndim=3] data,\n                       np.ndarray[np.float64_t, ndim=1] l_corner,\n                       np.ndarray[np.float64_t, ndim=1] r_corner):\n    cdef int i, j, k, dim, n_unique, best_dim, n_grids, my_split\n    n_grids = data.shape[0]\n    cdef np.float64_t **uniquedims\n    cdef np.float64_t *uniques\n    cdef np.float64_t split\n    uniquedims = <np.float64_t **> alloca(3 * sizeof(np.float64_t*))\n    for i in range(3):\n        uniquedims[i] = <np.float64_t *> \\\n                alloca(2*n_grids * sizeof(np.float64_t))\n    my_max = 0\n    best_dim = -1\n    my_split = -1\n    for dim in range(3):\n        n_unique = 0\n        uniques = uniquedims[dim]\n        for i in range(n_grids):\n            # Check for disqualification\n            for j in range(2):\n                #print(\"Checking against\", i,j,dim,data[i,j,dim])\n                if not (l_corner[dim] < data[i, j, dim] and\n                        data[i, j, dim] < r_corner[dim]):\n                    #print(\"Skipping \", data[i,j,dim])\n                    continue\n                skipit = 0\n                # Add our left ...\n                for k in range(n_unique):\n                    if uniques[k] == data[i, j, dim]:\n                        skipit = 1\n                        #print(\"Identified\", uniques[k], data[i,j,dim], n_unique)\n                        break\n                if skipit == 0:\n                    uniques[n_unique] = data[i, j, dim]\n                    n_unique += 1\n        if n_unique > my_max:\n            best_dim = dim\n            my_max = n_unique\n            my_split = (n_unique-1)/2\n    # I recognize how lame this is.\n    cdef np.ndarray[np.float64_t, ndim=1] tarr = np.empty(my_max, dtype='float64')\n    for i in range(my_max):\n        #print(\"Setting tarr: \", i, uniquedims[best_dim][i])\n        tarr[i] = uniquedims[best_dim][i]\n    tarr.sort()\n    if my_split < 0:\n        raise RuntimeError\n    split = tarr[my_split]\n    cdef np.ndarray[np.uint8_t, ndim=1] less_ids = np.empty(n_grids, dtype='uint8')\n    cdef np.ndarray[np.uint8_t, ndim=1] greater_ids = np.empty(n_grids, dtype='uint8')\n    for i in range(n_grids):\n        if data[i, 0, best_dim] < split:\n            less_ids[i] = 1\n        else:\n            less_ids[i] = 0\n        if data[i, 1, best_dim] > split:\n            greater_ids[i] = 1\n        else:\n            greater_ids[i] = 0\n    # Return out unique values\n    return best_dim, split, less_ids.view(\"bool\"), greater_ids.view(\"bool\")\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef get_box_grids_level(np.ndarray[np.float64_t, ndim=1] left_edge,\n                        np.ndarray[np.float64_t, ndim=1] right_edge,\n                        int level,\n                        np.ndarray[np.float64_t, ndim=2] left_edges,\n                        np.ndarray[np.float64_t, ndim=2] right_edges,\n                        np.ndarray[np.int32_t, ndim=2] levels,\n                        np.ndarray[np.int32_t, ndim=1] mask,\n                        int min_index = 0):\n    cdef int i, n\n    cdef int nx = left_edges.shape[0]\n    cdef int inside\n    cdef np.float64_t eps = np.finfo(np.float64).eps\n    for i in range(nx):\n        if i < min_index or levels[i,0] != level:\n            mask[i] = 0\n            continue\n        inside = 1\n        for n in range(3):\n            if (right_edges[i,n] - left_edge[n]) <= eps or \\\n               (right_edge[n] - left_edges[i,n]) <= eps:\n                inside = 0\n                break\n        if inside == 1: mask[i] = 1\n        else: mask[i] = 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef get_box_grids_below_level(\n                        np.ndarray[np.float64_t, ndim=1] left_edge,\n                        np.ndarray[np.float64_t, ndim=1] right_edge,\n                        int level,\n                        np.ndarray[np.float64_t, ndim=2] left_edges,\n                        np.ndarray[np.float64_t, ndim=2] right_edges,\n                        np.ndarray[np.int32_t, ndim=2] levels,\n                        np.ndarray[np.int32_t, ndim=1] mask,\n                        int min_level = 0):\n    cdef int i, n\n    cdef int nx = left_edges.shape[0]\n    cdef int inside\n    cdef np.float64_t eps = np.finfo(np.float64).eps\n    for i in range(nx):\n        mask[i] = 0\n        if levels[i,0] <= level and levels[i,0] >= min_level:\n            inside = 1\n            for n in range(3):\n                if (right_edges[i,n] - left_edge[n]) <= eps or \\\n                   (right_edge[n] - left_edges[i,n]) <= eps:\n                    inside = 0\n                    break\n            if inside == 1: mask[i] = 1\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef obtain_position_vector(\n    data,\n    field_names = ((\"index\", \"x\"), (\"index\", \"y\"), (\"index\", \"z\"))\n):\n    # This is just to let the pointers exist and whatnot.  We can't cdef them\n    # inside conditionals.\n    cdef np.ndarray[np.float64_t, ndim=1] xf\n    cdef np.ndarray[np.float64_t, ndim=1] yf\n    cdef np.ndarray[np.float64_t, ndim=1] zf\n    cdef np.ndarray[np.float64_t, ndim=2] rf\n    cdef np.ndarray[np.float64_t, ndim=3] xg\n    cdef np.ndarray[np.float64_t, ndim=3] yg\n    cdef np.ndarray[np.float64_t, ndim=3] zg\n    cdef np.ndarray[np.float64_t, ndim=4] rg\n    cdef np.float64_t c[3]\n    cdef int i, j, k\n\n    units = data[field_names[0]].units\n    center = data.get_field_parameter(\"center\").to(units)\n    c[0] = center[0]; c[1] = center[1]; c[2] = center[2]\n    if len(data[field_names[0]].shape) == 1:\n        # One dimensional data\n        xf = data[field_names[0]]\n        yf = data[field_names[1]]\n        zf = data[field_names[2]]\n        rf = YTArray(np.empty((3, xf.shape[0]), 'float64'), xf.units)\n        for i in range(xf.shape[0]):\n            rf[0, i] = xf[i] - c[0]\n            rf[1, i] = yf[i] - c[1]\n            rf[2, i] = zf[i] - c[2]\n        return rf\n    else:\n        # Three dimensional data\n        xg = data[field_names[0]]\n        yg = data[field_names[1]]\n        zg = data[field_names[2]]\n        shape = (3, xg.shape[0], xg.shape[1], xg.shape[2])\n        rg = YTArray(np.empty(shape, 'float64'), xg.units)\n        #rg = YTArray(rg, xg.units)\n        for i in range(xg.shape[0]):\n            for j in range(xg.shape[1]):\n                for k in range(xg.shape[2]):\n                    rg[0,i,j,k] = xg[i,j,k] - c[0]\n                    rg[1,i,j,k] = yg[i,j,k] - c[1]\n                    rg[2,i,j,k] = zg[i,j,k] - c[2]\n        return rg\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef obtain_relative_velocity_vector(\n        data,\n        field_names = ((\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"), (\"gas\", \"velocity_z\")),\n        bulk_vector = \"bulk_velocity\"\n    ):\n    # This is just to let the pointers exist and whatnot.  We can't cdef them\n    # inside conditionals.\n    cdef np.ndarray[np.float64_t, ndim=1] vxf\n    cdef np.ndarray[np.float64_t, ndim=1] vyf\n    cdef np.ndarray[np.float64_t, ndim=1] vzf\n    cdef np.ndarray[np.float64_t, ndim=2] rvf\n    cdef np.ndarray[np.float64_t, ndim=3] vxg\n    cdef np.ndarray[np.float64_t, ndim=3] vyg\n    cdef np.ndarray[np.float64_t, ndim=3] vzg\n    cdef np.ndarray[np.float64_t, ndim=4] rvg\n    cdef np.float64_t bv[3]\n    cdef int i, j, k, dim\n\n    units = data[field_names[0]].units\n    bulk_vector = data.get_field_parameter(bulk_vector).to(units)\n    dim = data[field_names[0]].ndim\n    if dim == 1:\n        # One dimensional data\n        vxf = data[field_names[0]].astype(\"float64\")\n        vyf = data[field_names[1]].astype(\"float64\")\n        vzf = data[field_names[2]].astype(\"float64\")\n        vyf.convert_to_units(vxf.units)\n        vzf.convert_to_units(vxf.units)\n        rvf = YTArray(np.empty((3, vxf.shape[0]), 'float64'), vxf.units)\n        if bulk_vector is None:\n            bv[0] = bv[1] = bv[2] = 0.0\n        else:\n            bulk_vector = bulk_vector.in_units(vxf.units)\n            bv[0] = bulk_vector[0]\n            bv[1] = bulk_vector[1]\n            bv[2] = bulk_vector[2]\n        for i in range(vxf.shape[0]):\n            rvf[0, i] = vxf[i] - bv[0]\n            rvf[1, i] = vyf[i] - bv[1]\n            rvf[2, i] = vzf[i] - bv[2]\n        return rvf\n    elif dim == 3:\n        # Three dimensional data\n        vxg = data[field_names[0]].astype(\"float64\")\n        vyg = data[field_names[1]].astype(\"float64\")\n        vzg = data[field_names[2]].astype(\"float64\")\n        vyg.convert_to_units(vxg.units)\n        vzg.convert_to_units(vxg.units)\n        shape = (3, vxg.shape[0], vxg.shape[1], vxg.shape[2])\n        rvg = YTArray(np.empty(shape, 'float64'), vxg.units)\n        if bulk_vector is None:\n            bv[0] = bv[1] = bv[2] = 0.0\n        else:\n            bulk_vector = bulk_vector.in_units(vxg.units)\n            bv[0] = bulk_vector[0]\n            bv[1] = bulk_vector[1]\n            bv[2] = bulk_vector[2]\n        for i in range(vxg.shape[0]):\n            for j in range(vxg.shape[1]):\n                for k in range(vxg.shape[2]):\n                    rvg[0,i,j,k] = vxg[i,j,k] - bv[0]\n                    rvg[1,i,j,k] = vyg[i,j,k] - bv[1]\n                    rvg[2,i,j,k] = vzg[i,j,k] - bv[2]\n        return rvg\n    else:\n        raise NotImplementedError(f\"Unsupported dimensionality `{dim}`.\")\n\ndef grow_flagging_field(oofield):\n    cdef np.ndarray[np.uint8_t, ndim=3] ofield = oofield.astype(\"uint8\")\n    cdef np.ndarray[np.uint8_t, ndim=3] nfield\n    nfield = np.zeros_like(ofield)\n    cdef int i, j, k, ni, nj, nk\n    cdef int oi, oj, ok\n    for ni in range(ofield.shape[0]):\n        for nj in range(ofield.shape[1]):\n            for nk in range(ofield.shape[2]):\n                for oi in range(3):\n                    i = ni + (oi - 1)\n                    if i < 0 or i >= ofield.shape[0]: continue\n                    for oj in range(3):\n                        j = nj + (oj - 1)\n                        if j < 0 or j >= ofield.shape[1]: continue\n                        for ok in range(3):\n                            k = nk + (ok - 1)\n                            if k < 0 or k >= ofield.shape[2]: continue\n                            if ofield[i, j, k] == 1:\n                                nfield[ni, nj, nk] = 1\n    return nfield.astype(\"bool\")\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef fill_region(\n    input_fields,\n    output_fields,\n    np.int32_t output_level,\n    np.ndarray[np.int64_t, ndim=1] left_index,\n    np.ndarray[np.int64_t, ndim=2] ipos,\n    np.ndarray[np.int64_t, ndim=1] ires,\n    np.ndarray[np.int64_t, ndim=1] level_dims,\n    np.ndarray[np.int64_t, ndim=1] refine_by\n):\n    cdef int i, n\n    cdef np.int64_t tot = 0, oi, oj, ok\n    cdef np.int64_t rf[3]\n    cdef np.int64_t iind[3]\n    cdef np.int64_t oind[3]\n    cdef np.int64_t dim[3]\n    cdef np.ndarray[np.float64_t, ndim=3] ofield\n    cdef np.ndarray[np.float64_t, ndim=1] ifield\n    nf = len(input_fields)\n    # The variable offsets governs for each dimension and each possible\n    # wrapping if we do it.  Then the wi, wj, wk indices check into each\n    # [dim][wrap] inside the loops.\n    cdef int wi, wj, wk\n    cdef int offsets[3][3]\n    cdef np.int64_t off\n    for i in range(3):\n        # Offsets here is a way of accounting for periodicity.  It keeps track\n        # of how to offset our grid as we loop over the icoords.\n        dim[i] = output_fields[0].shape[i]\n        offsets[i][0] = offsets[i][2] = 0\n        offsets[i][1] = 1\n        if left_index[i] < 0:\n            offsets[i][2] = 1\n        if left_index[i] + dim[i] >= level_dims[i]:\n            offsets[i][0] = 1\n    for n in range(nf):\n        tot = 0\n        ofield = output_fields[n]\n        ifield = input_fields[n]\n        for i in range(ipos.shape[0]):\n            for k in range(3):\n                rf[k] = refine_by[k]**(output_level - ires[i])\n            for wi in range(3):\n                if offsets[0][wi] == 0: continue\n                off = (left_index[0] + level_dims[0]*(wi-1))\n                iind[0] = ipos[i, 0] * rf[0] - off\n                # rf here is the \"refinement factor\", or, the number of zones\n                # that this zone could potentially contribute to our filled\n                # grid.\n                for oi in range(rf[0]):\n                    # Now we need to apply our offset\n                    oind[0] = oi + iind[0]\n                    if oind[0] < 0:\n                        continue\n                    elif oind[0] >= dim[0]:\n                        break\n                    for wj in range(3):\n                        if offsets[1][wj] == 0: continue\n                        off = (left_index[1] + level_dims[1]*(wj-1))\n                        iind[1] = ipos[i, 1] * rf[1] - off\n                        for oj in range(rf[1]):\n                            oind[1] = oj + iind[1]\n                            if oind[1] < 0:\n                                continue\n                            elif oind[1] >= dim[1]:\n                                break\n                            for wk in range(3):\n                                if offsets[2][wk] == 0: continue\n                                off = (left_index[2] + level_dims[2]*(wk-1))\n                                iind[2] = ipos[i, 2] * rf[2] - off\n                                for ok in range(rf[2]):\n                                    oind[2] = ok + iind[2]\n                                    if oind[2] < 0:\n                                        continue\n                                    elif oind[2] >= dim[2]:\n                                        break\n                                    ofield[oind[0], oind[1], oind[2]] = \\\n                                        ifield[i]\n                                    tot += 1\n    return tot\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef flip_bitmask(np.ndarray[np.float64_t, ndim=1] vals,\n                 np.float64_t left_edge, np.float64_t right_edge,\n                 np.uint64_t nbins):\n    cdef np.uint64_t i, bin_id\n    cdef np.float64_t idx = nbins / (right_edge - left_edge)\n    cdef np.ndarray[np.uint8_t, ndim=1, cast=True] bitmask\n    bitmask = np.zeros(nbins, dtype=\"uint8\")\n    for i in range(vals.shape[0]):\n        bin_id = <np.uint64_t> ((vals[i] - left_edge)*idx)\n        bitmask[bin_id] = 1\n    return bitmask\n\n#@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef flip_morton_bitmask(np.ndarray[np.uint64_t, ndim=1] morton_indices,\n                        int max_order):\n    # We assume that the morton_indices are fed to us in a setup that allows\n    # for 20 levels.  This means that we shift right by 3*(20-max_order) (or is\n    # that a fencepost?)\n    cdef np.uint64_t mi, i\n    cdef np.ndarray[np.uint8_t, ndim=1, cast=True] bitmask\n    # Note that this will fail if it's too big, since numpy will check nicely\n    # the memory availability.  I guess.\n    bitmask = np.zeros(1 << (3*max_order), dtype=\"uint8\")\n    for i in range(morton_indices.shape[0]):\n        mi = (morton_indices[i] >> (3 * (20-max_order)))\n        bitmask[mi] = 1\n    return bitmask\n\n#@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef count_collisions(np.ndarray[np.uint8_t, ndim=2] masks):\n    cdef int i, j, k\n    cdef np.ndarray[np.uint32_t, ndim=1] counts\n    cdef np.ndarray[np.uint8_t, ndim=1] collides\n    counts = np.zeros(masks.shape[1], dtype=\"uint32\")\n    collides = np.zeros(masks.shape[1], dtype=\"uint8\")\n    for i in range(masks.shape[1]):\n        print(i)\n        for j in range(masks.shape[1]):\n            collides[j] = 0\n        for k in range(masks.shape[0]):\n            if masks[k,i] == 0: continue\n            for j in range(masks.shape[1]):\n                if j == i: continue\n                if masks[k,j] == 1:\n                    collides[j] = 1\n        counts[i] = collides.sum()\n    return counts\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef fill_region_float(np.ndarray[np.float64_t, ndim=2] fcoords,\n                      np.ndarray[np.float64_t, ndim=2] fwidth,\n                      np.ndarray[np.float64_t, ndim=1] data,\n                      np.ndarray[np.float64_t, ndim=1] box_left_edge,\n                      np.ndarray[np.float64_t, ndim=1] box_right_edge,\n                      np.ndarray[np.float64_t, ndim=3] dest,\n                      int antialias = 1,\n                      period = None,\n                      int check_period = 1):\n    cdef np.float64_t ds_period[3]\n    cdef np.float64_t box_dds[3]\n    cdef np.float64_t box_idds[3]\n    cdef np.float64_t width[3]\n    cdef np.float64_t LE[3]\n    cdef np.float64_t RE[3]\n    cdef np.int64_t i, j, k, p, xi, yi\n    cdef np.int64_t dims[3]\n    cdef np.int64_t ld[3]\n    cdef np.int64_t ud[3]\n    cdef np.float64_t overlap[3]\n    cdef np.float64_t dsp\n    cdef np.float64_t osp[3]\n    cdef np.float64_t odsp[3]\n    cdef np.float64_t sp[3]\n    cdef np.float64_t lfd[3]\n    cdef np.float64_t ufd[3]\n    # These are the temp vars we get from the arrays\n    # Some periodicity helpers\n    cdef int diter[3][2]\n    cdef np.float64_t diterv[3][2]\n    if period is not None:\n        for i in range(3):\n            ds_period[i] = period[i]\n    else:\n        ds_period[0] = ds_period[1] = ds_period[2] = 0.0\n    box_left_edge = _ensure_code(box_left_edge)\n    box_right_edge = _ensure_code(box_right_edge)\n    _ensure_code(fcoords)\n    _ensure_code(fwidth)\n    for i in range(3):\n        LE[i] = box_left_edge[i]\n        RE[i] = box_right_edge[i]\n        width[i] = RE[i] - LE[i]\n        dims[i] = dest.shape[i]\n        box_dds[i] = width[i] / dims[i]\n        box_idds[i] = 1.0/box_dds[i]\n        diter[i][0] = diter[i][1] = 0\n        diterv[i][0] = diterv[i][1] = 0.0\n        overlap[i] = 1.0\n    with nogil:\n        for p in range(fcoords.shape[0]):\n            for i in range(3):\n               diter[i][1] = 999\n               odsp[i] = fwidth[p,i]*0.5\n               osp[i] = fcoords[p,i] # already centered\n               overlap[i] = 1.0\n            dsp = data[p]\n            if check_period == 1:\n                for i in range(3):\n                    if (osp[i] - odsp[i] < LE[i]):\n                        diter[i][1] = +1\n                        diterv[i][1] = ds_period[i]\n                    elif (osp[i] + odsp[i] > RE[i]):\n                        diter[i][1] = -1\n                        diterv[i][1] = -ds_period[i]\n            for xi in range(2):\n                if diter[0][xi] == 999: continue\n                sp[0] = osp[0] + diterv[0][xi]\n                if (sp[0] + odsp[0] < LE[0]) or (sp[0] - odsp[0] > RE[0]): continue\n                for yi in range(2):\n                    if diter[1][yi] == 999: continue\n                    sp[1] = osp[1] + diterv[1][yi]\n                    if (sp[1] + odsp[1] < LE[1]) or (sp[1] - odsp[1] > RE[1]): continue\n                    for zi in range(2):\n                        if diter[2][zi] == 999: continue\n                        sp[2] = osp[2] + diterv[2][zi]\n                        if (sp[2] + odsp[2] < LE[2]) or (sp[2] - odsp[2] > RE[2]): continue\n                        for i in range(3):\n                            ld[i] = <np.int64_t> fmax(((sp[i]-odsp[i]-LE[i])*box_idds[i]),0)\n                            # NOTE: This is a different way of doing it than in the C\n                            # routines.  In C, we were implicitly casting the\n                            # initialization to int, but *not* the conditional, which\n                            # was allowed an extra value:\n                            #     for(j=lc;j<rc;j++)\n                            # here, when assigning lc (double) to j (int) it got\n                            # truncated, but no similar truncation was done in the\n                            # comparison of j to rc (double).  So give ourselves a\n                            # bonus row and bonus column here.\n                            ud[i] = <np.int64_t> fmin(((sp[i]+odsp[i]-LE[i])*box_idds[i] + 1), dims[i])\n                        for i in range(ld[0], ud[0]):\n                            if antialias == 1:\n                                lfd[0] = box_dds[0] * i + LE[0]\n                                ufd[0] = box_dds[0] * (i + 1) + LE[0]\n                                overlap[0] = ((fmin(ufd[0], sp[0]+odsp[0])\n                                           - fmax(lfd[0], (sp[0]-odsp[0])))*box_idds[0])\n                            if overlap[0] < 0.0: continue\n                            for j in range(ld[1], ud[1]):\n                                if antialias == 1:\n                                    lfd[1] = box_dds[1] * j + LE[1]\n                                    ufd[1] = box_dds[1] * (j + 1) + LE[1]\n                                    overlap[1] = ((fmin(ufd[1], sp[1]+odsp[1])\n                                               - fmax(lfd[1], (sp[1]-odsp[1])))*box_idds[1])\n                                if overlap[1] < 0.0: continue\n                                for k in range(ld[2], ud[2]):\n                                    if antialias == 1:\n                                        lfd[2] = box_dds[2] * k + LE[2]\n                                        ufd[2] = box_dds[2] * (k + 1) + LE[2]\n                                        overlap[2] = ((fmin(ufd[2], sp[2]+odsp[2])\n                                                   - fmax(lfd[2], (sp[2]-odsp[2])))*box_idds[2])\n                                        if overlap[2] < 0.0: continue\n                                        dest[i,j,k] += dsp * (overlap[0]*overlap[1]*overlap[2])\n                                    else:\n                                        dest[i,j,k] = dsp\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef gravitational_binding_energy(\n        np.float64_t[:] mass,\n        np.float64_t[:] x,\n        np.float64_t[:] y,\n        np.float64_t[:] z,\n        int truncate,\n        np.float64_t kinetic,\n        int num_threads = 0):\n\n    cdef int q_outer, q_inner, n_q\n    cdef np.float64_t mass_o, x_o, y_o, z_o\n    cdef np.float64_t mass_i, x_i, y_i, z_i\n    cdef np.float64_t total_potential = 0.\n    cdef np.float64_t this_potential\n\n    n_q = mass.size\n    pbar = get_pbar(\"Calculating potential for %d cells with %d thread(s)\" % (n_q,num_threads),\n        n_q)\n\n    # using reversed iterator in order to make use of guided scheduling\n    # (inner loop is getting more and more expensive)\n    for q_outer in prange(n_q - 1,-1,-1,\n        nogil=True,schedule='guided',num_threads=num_threads):\n        this_potential = 0.\n\n        mass_o = mass[q_outer]\n        x_o = x[q_outer]\n        y_o = y[q_outer]\n        z_o = z[q_outer]\n        for q_inner in range(q_outer + 1, n_q):\n            mass_i = mass[q_inner]\n            x_i = x[q_inner]\n            y_i = y[q_inner]\n            z_i = z[q_inner]\n            # not using += operator so that variable is not automatically reduced\n            this_potential = this_potential + mass_o * mass_i / \\\n              sqrt((x_i - x_o) * (x_i - x_o) +\n                   (y_i - y_o) * (y_i - y_o) +\n                   (z_i - z_o) * (z_i - z_o))\n        total_potential += this_potential\n        if truncate and this_potential / kinetic > 1.:\n            break\n        with gil:\n            PyErr_CheckSignals()\n            # this call is not thread safe, but it gives a reasonable approximation\n            pbar.update()\n\n    pbar.finish()\n    return total_potential\n\n# The OnceIndirect code is from:\n# http://stackoverflow.com/questions/10465091/assembling-a-cython-memoryview-from-numpy-arrays/12991519#12991519\n# This is under the CC-BY-SA license.\n\ncdef class OnceIndirect:\n    cdef object _objects\n    cdef void** buf\n    cdef int ndim\n    cdef int n_rows\n    cdef int buf_len\n    cdef Py_ssize_t* shape\n    cdef Py_ssize_t* strides\n    cdef Py_ssize_t* suboffsets\n    cdef Py_ssize_t itemsize\n    cdef bytes format\n    cdef int is_readonly\n\n    def __cinit__(self, object rows, want_writable=True, want_format=True, allow_indirect=False):\n        \"\"\"\n        Set want_writable to False if you don't want writable data. (This may\n        prevent copies.)\n        Set want_format to False if your input doesn't support PyBUF_FORMAT (unlikely)\n        Set allow_indirect to True if you are ok with the memoryview being indirect\n        in dimensions other than the first. (This may prevent copies.)\n\n        An example usage:\n\n        cdef double[::cython.view.indirect, ::1] vectors =\n            OnceIndirect([object.vector for object in objects])\n        \"\"\"\n        demand = buffer.PyBUF_INDIRECT if allow_indirect else buffer.PyBUF_STRIDES\n        if want_writable:\n            demand |= buffer.PyBUF_WRITABLE\n        if want_format:\n            demand |= buffer.PyBUF_FORMAT\n        self._objects = [memoryview(row, demand) for row in rows]\n        self.n_rows = len(self._objects)\n        self.buf_len = sizeof(void*) * self.n_rows\n        self.buf = <void**>malloc(self.buf_len)\n        self.ndim = 1 + self._objects[0].ndim\n        self.shape = <Py_ssize_t*>malloc(sizeof(Py_ssize_t) * self.ndim)\n        self.strides = <Py_ssize_t*>malloc(sizeof(Py_ssize_t) * self.ndim)\n        self.suboffsets = <Py_ssize_t*>malloc(sizeof(Py_ssize_t) * self.ndim)\n\n        cdef memoryview example_obj = self._objects[0]\n        self.itemsize = example_obj.itemsize\n\n        if want_format:\n            self.format = example_obj.view.format\n        else:\n            self.format = None\n        self.is_readonly |= example_obj.view.readonly\n\n        for dim in range(self.ndim):\n            if dim == 0:\n                self.shape[dim] = self.n_rows\n                self.strides[dim] = sizeof(void*)\n                self.suboffsets[dim] = 0\n            else:\n                self.shape[dim] = example_obj.view.shape[dim - 1]\n                self.strides[dim] = example_obj.view.strides[dim - 1]\n                if example_obj.view.suboffsets == NULL:\n                    self.suboffsets[dim] = -1\n                else:\n                    self.suboffsets[dim] = example_obj.suboffsets[dim - 1]\n\n        cdef memoryview obj\n        cdef int i = 0\n        for obj in self._objects:\n            assert_similar(example_obj, obj)\n            self.buf[i] = obj.view.buf\n            i += 1\n\n    def __getbuffer__(self, Py_buffer* buff, int flags):\n        if (flags & buffer.PyBUF_INDIRECT) != buffer.PyBUF_INDIRECT:\n            raise Exception(\"don't want to copy data\")\n        if flags & buffer.PyBUF_WRITABLE and self.is_readonly:\n            raise Exception(\"couldn't provide writable, you should have demanded it earlier\")\n        if flags & buffer.PyBUF_FORMAT:\n            if self.format is None:\n                raise Exception(\"couldn't provide format, you should have demanded it earlier\")\n            buff.format = self.format\n        else:\n            buff.format = NULL\n\n        buff.buf = <void*>self.buf\n        buff.obj = self\n        buff.len = self.buf_len\n        buff.readonly = self.is_readonly\n        buff.ndim = self.ndim\n        buff.shape = self.shape\n        buff.strides = self.strides\n        buff.suboffsets = self.suboffsets\n        buff.itemsize = self.itemsize\n        buff.internal = NULL\n\n    def __dealloc__(self):\n        free(self.buf)\n        free(self.shape)\n        free(self.strides)\n        free(self.suboffsets)\n\ncdef int assert_similar(memoryview left_, memoryview right_) except -1:\n    cdef Py_buffer left = left_.view\n    cdef Py_buffer right = right_.view\n    assert left.ndim == right.ndim\n    cdef int i\n    for i in range(left.ndim):\n        assert left.shape[i] == right.shape[i], (left_.shape, right_.shape)\n        assert left.strides[i] == right.strides[i], (left_.strides, right_.strides)\n\n    if left.suboffsets == NULL:\n        assert right.suboffsets == NULL, (left_.suboffsets, right_.suboffsets)\n    else:\n        for i in range(left.ndim):\n            assert left.suboffsets[i] == right.suboffsets[i], (left_.suboffsets, right_.suboffsets)\n\n    if left.format == NULL:\n        assert right.format == NULL, (bytes(left.format), bytes(right.format))\n    else:\n        #alternatively, compare as Python strings:\n        #assert bytes(left.format) == bytes(right.format)\n        assert strcmp(left.format, right.format) == 0, (bytes(left.format), bytes(right.format))\n    return 0\n\ndef _obtain_coords_and_widths(np.int64_t[:] icoords,\n                              np.int64_t[:] ires,\n                              np.float64_t[:] cell_widths,\n                              np.float64_t offset):\n    # This function only accepts *one* axis of icoords, because we will be\n    # looping over the axes in python.  This also simplifies cell_width\n    # allocation and computation.\n    cdef np.ndarray[np.float64_t, ndim=1] fcoords = np.zeros(icoords.size, dtype=\"f8\")\n    cdef np.ndarray[np.float64_t, ndim=1] fwidth = np.zeros(icoords.size, dtype=\"f8\")\n    cdef np.ndarray[np.float64_t, ndim=1] cell_centers = np.zeros(cell_widths.size, dtype=\"f8\")\n    cdef int i\n    cdef np.float64_t pos = offset\n    for i in range(cell_widths.size):\n        cell_centers[i] = pos + 0.5 * cell_widths[i]\n        pos += cell_widths[i]\n    for i in range(icoords.size):\n        fcoords[i] = cell_centers[icoords[i]]\n        fwidth[i] = cell_widths[icoords[i]]\n    return fcoords, fwidth\n"
  },
  {
    "path": "yt/utilities/lib/octree_raytracing.py",
    "content": "from itertools import product\n\nimport numpy as np\n\nfrom yt.funcs import mylog\nfrom yt.utilities.lib._octree_raytracing import _OctreeRayTracing\n\n\nclass OctreeRayTracing:\n    octree = None\n    data_source = None\n    log_fields = None\n    fields = None\n\n    # Internal data\n    _cell_index = None\n    _tvalues = None\n\n    def __init__(self, data_source):\n        self.data_source = data_source\n        ds = data_source.ds\n        LE = np.array([0, 0, 0], dtype=np.float64)\n        RE = np.array([1, 1, 1], dtype=np.float64)\n        lvl_min = ds.min_level + 1\n        # This is the max refinement so that the smallest cells have size\n        # 1/2**depth\n        depth = lvl_min + ds.max_level + 1\n\n        self.octree = _OctreeRayTracing(LE, RE, depth)\n        ds = data_source.ds\n\n        xyz = np.stack([data_source[key].to_value(\"unitary\") for key in \"xyz\"], axis=-1)\n        lvl = data_source[\"grid_level\"].value.astype(\"int64\", copy=False) + lvl_min\n\n        ipos = np.floor(xyz * (1 << depth)).astype(\"int64\")\n        mylog.debug(\"Adding cells to volume\")\n        self.octree.add_nodes(\n            ipos.astype(np.int32),\n            lvl.astype(np.int32),\n            np.arange(len(ipos), dtype=np.int32),\n        )\n\n    def vertex_centered_data(self, field):\n        data_source = self.data_source\n        chunks = data_source.index._chunk(data_source, \"spatial\", ngz=1)\n\n        finfo = data_source.ds._get_field_info(field)\n        units = finfo.units\n        rv = data_source.ds.arr(\n            np.zeros((2, 2, 2, data_source.ires.size), dtype=\"float64\"), units\n        )\n        binary_3D_index_iter = product(*[range(2)] * 3)\n        ind = dict.fromkeys(binary_3D_index_iter, 0)\n        for chunk in chunks:\n            with data_source._chunked_read(chunk):\n                gz = data_source._current_chunk.objs[0]\n                gz.field_parameters = data_source.field_parameters\n                wogz = gz._base_grid\n                vertex_data = gz.get_vertex_centered_data([field])[field]\n\n                for i, j, k in product(*[range(2)] * 3):\n                    ind[i, j, k] += wogz.select(\n                        data_source.selector,\n                        vertex_data[i : i + 2, j : j + 2, k : k + 2, ...],\n                        rv[i, j, k, :],\n                        ind[i, j, k],\n                    )\n        return rv\n\n    def set_fields(self, fields, log_fields, no_ghost, force=False):\n        if no_ghost:\n            raise NotImplementedError(\"Ghost zones are required with Octree datasets\")\n\n        if len(fields) != 1:\n            raise ValueError(\n                \"Can only set one fields at a time. \"\n                \"This is likely a bug, and should be reported.\"\n            )\n\n        field = self.data_source._determine_fields(fields)[0]\n        take_log = log_fields[0]\n        vertex_data = self.vertex_centered_data(field)\n\n        if take_log:\n            vertex_data = np.log10(vertex_data)\n\n        # Vertex_data has shape (2, 2, 2, ...)\n        # Note: here we have the wrong ordering within the oct (classical Fortran/C\n        # ordering issue) so we need to swap axis 0 and 2.\n        self.data = vertex_data.swapaxes(0, 2).reshape(8, -1)\n\n    def cast_rays(self, vp_pos, vp_dir):\n        \"\"\"Cast the rays through the oct.\n\n        Parameters\n        ----------\n        vp_pos : float arrays (Nrays, Ndim)\n        vp_dir : float arrays (Nrays, Ndim)\n            The position (unitary) and direction of each ray\n\n        Returns\n        -------\n        cell_index : list of integer arrays of shape (Ncell)\n            For each ray, contains an ordered array of cell ids\n            that it intersects with\n        tvalues : list of float arrays of shape (Ncell, 2)\n            The t value at entry and exit for each cell.\n        \"\"\"\n        if not self._cell_index:\n            self._cell_index, self._tvalues = self.octree.cast_rays(vp_pos, vp_dir)\n        return self._cell_index, self._tvalues\n"
  },
  {
    "path": "yt/utilities/lib/origami.pyx",
    "content": "# distutils: sources = yt/utilities/lib/origami_tags.c\n# distutils: include_dirs = LIB_DIR\n# distutils: depends = yt/utilities/lib/origami_tags.h\n# distutils: libraries = STD_LIBS\n\"\"\"\nThis calls the ORIGAMI routines\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport numpy as np\nfrom libc.stdlib cimport free, malloc\n\n\ncdef extern from \"origami_tags.h\":\n    int compute_tags(int ng, double boxsize, double **r, int npart,\n                     unsigned char *m)\n\ncdef int printed_citation = 0\n\ndef run_origami(np.ndarray[np.float64_t, ndim=1] pos_x,\n                np.ndarray[np.float64_t, ndim=1] pos_y,\n                np.ndarray[np.float64_t, ndim=1] pos_z,\n                double boxsize):\n    # We assume these have been passed in in the correct order and\n    # C-contiguous.\n    global printed_citation\n    if printed_citation == 0:\n        print(\"ORIGAMI was developed by Bridget Falck and Mark Neyrinck.\")\n        print(\"Please cite Falck, Neyrinck, & Szalay 2012, ApJ, 754, 2, 125.\")\n        printed_citation = 1\n    cdef int npart = pos_x.size\n    if npart == 1:\n        return np.zeros(1, dtype=\"uint8\")\n    assert(sizeof(unsigned char) == sizeof(np.uint8_t))\n    assert(sizeof(double) == sizeof(np.float64_t))\n    cdef int ng = np.round(npart**(1./3))\n    assert(ng**3 == npart)\n    cdef double **r = <double **> malloc(sizeof(double *) * 3)\n    r[0] = <double *> pos_x.data\n    r[1] = <double *> pos_y.data\n    r[2] = <double *> pos_z.data\n    cdef np.ndarray[np.uint8_t, ndim=1] tags = np.zeros(npart, dtype=\"uint8\")\n    cdef void *m = <void*> tags.data\n    compute_tags(ng, boxsize, r, npart, <unsigned char*> m)\n    free(r)\n    return tags\n"
  },
  {
    "path": "yt/utilities/lib/origami_tags.c",
    "content": "// This code was originally written by Bridget Falck and Mark Neyrinck.\n// They have agreed to release it under the terms of the BSD license.\n#include \"origami_tags.h\"\n\nint isneg(int h) {\n  return (int)(h < 0);\n}\n\nint par(int i, int j, int k, int ng) {\n  return i + (j + k*ng)*ng;\n}\n\nint compute_tags(int ng, double boxsize, double **r, int np,\n                 unsigned char *m) {\n  /* Note that the particles must be fed in according to the order specified in\n   * the README file */\n  double xmin,xmax,ymin,ymax,zmin,zmax;\n\n  double negb2, b2;\n  int ng4, h, i,i2, x,y,z,nhalo,nhalo0,nhalo1,nhalo2,nhaloany;\n  unsigned char *m0,*m1,*m2, mn,m0n,m1n,m2n; /*Morphology tag */\n\n  double dx,d1,d2;\n\n  b2 = boxsize/2.;\n  negb2 = -boxsize/2.;\n  ng4=ng/4;\n\n  /* Boxsize should be the range in r, yielding a range 0-1 */\n  printf(\"%d particles\\n\",np);fflush(stdout);\n  xmin = BF; xmax = -BF; ymin = BF; ymax = -BF; zmin = BF; zmax = -BF;\n  m0 = (unsigned char *)malloc(np*sizeof(unsigned char)); /* for the diagonals */\n  m1 = (unsigned char *)malloc(np*sizeof(unsigned char));\n  m2 = (unsigned char *)malloc(np*sizeof(unsigned char));\n  for (i=0; i<np;i++) {\n    if (r[0][i]<xmin) xmin = r[0][i]; if (r[0][i]>xmax) xmax = r[0][i];\n    if (r[1][i]<ymin) ymin = r[1][i]; if (r[1][i]>ymax) ymax = r[1][i];\n    if (r[2][i]<zmin) zmin = r[2][i]; if (r[2][i]>zmax) zmax = r[2][i];\n\n    m[i] = 1;\n    m0[i] = 1;\n    m1[i] = 1;\n    m2[i] = 1;\n  }\n\n  if (m==NULL) {\n    printf(\"Morphology array cannot be allocated.\\n\");\n    return 1;\n  }\n  //  printf(\"np: %d, x: %f,%f; y: %f,%f; z: %f,%f\\n\",np,xmin,xmax, ymin,ymax, zmin,zmax); fflush(stdout);\n\n  printf(\"Calculating ORIGAMI morphology.\\n\");\n\n  for (x=0; x<ng; x++){\n    //    printf(\"%d\\n\",x);fflush(stdout);\n    for (y=0; y<ng; y++) {\n      for (z=0; z<ng; z++) {\n\ti = par(x,y,z,ng);\n\t/* First just along the Cartesian axes */\n\t/* x-direction */\n\t  for (h=1; h<ng4; h++) {\n\t    i2 = par((x+h)%ng,y,z,ng);\n\t    dx = r[0][i2]-r[0][i];\n\t    if (dx < negb2) dx += boxsize;\n\t    if (dx > b2) dx -= boxsize;\n\t    if (dx < 0.) {\n\t      /*printf(\"x:%d %d %d %d %f\\n\",x,y,z,h,dx);*/\n\t    if (m[i] % 2 > 0) {\n\t      m[i] *= 2;\n\t    }\n\t      if (m[i2] % 2 > 0){\n\t\tm[i2] *= 2;\n\t      }\n\t    break;\n\t    }\n\t  }\n\t  for (h=1; h<ng4; h++) {\n\t    i2 = par(x,(y+h)%ng,z,ng);\n\t    dx = r[1][i2]-r[1][i];\n\t    if (dx < negb2) dx += boxsize;\n\t    if (dx > b2) dx -= boxsize;\n\t    if (dx < 0.) {\n\t      /*printf(\"y:%d %d %d %d %f\\n\",x,y,z,h,dx);*/\n\t    if (m[i] % 3 > 0) {\n\t      m[i] *= 3;\n\t    }\n\t      if (m[i2] % 3 > 0){\n\t\tm[i2] *= 3;\n\t      }\n\t      break;\n\t    }\n\t  }\n\t  for (h=1; h<ng4; h++) {\n\t    i2 = par(x,y,(z+h)%ng,ng);\n\t    dx = r[2][i2]-r[2][i];\n\t    if (dx < negb2) dx += boxsize;\n\t    if (dx > b2) dx -= boxsize;\n\t    if (dx < 0.) {\n\t      /*printf(\"z:%d %d %d %d %f\\n\",x,y,z,h,dx);*/\n\t    if (m[i] % 5 > 0) {\n\t      m[i] *= 5;\n\t    }\n\t      if (m[i2] % 5 > 0){\n\t\tm[i2] *= 5;\n\t      }\n\t      break;\n\t    }\n\t  }\n\t// Now do diagonal directions\n\tfor (h=1; h<ng4; h = -h + isneg(h)) {\n\t  i2 = par(x,goodmod(y+h,ng),goodmod(z+h,ng),ng);\n\t  d1 = r[1][i2]-r[1][i];\n\t  d2 = r[2][i2]-r[2][i];\n\t  if (d1 < negb2) d1 += boxsize;\n\t  if (d1 > b2) d1 -= boxsize;\n\t  if (d2 < negb2) d2 += boxsize;\n\t  if (d2 > b2) d2 -= boxsize;\n\t  if ((d1 + d2)*h < 0.) {\n\t    m0[i] *= 2;\n\t    break;\n\t  }\n\t}\n\tfor (h=1; h<ng4; h = -h + isneg(h)) {\n\t  i2 = par(x,goodmod(y+h,ng),goodmod(z-h,ng),ng);\n\t  d1 = r[1][i2]-r[1][i];\n\t  d2 = r[2][i2]-r[2][i];\n\t  if (d1 < negb2) d1 += boxsize;\n\t  if (d1 > b2) d1 -= boxsize;\n\t  if (d2 < negb2) d2 += boxsize;\n\t  if (d2 > b2) d2 -= boxsize;\n\t  if ((d1 - d2)*h < 0.) {\n\t    m0[i] *= 3;\n\t    break;\n\t  }\n\t}\n\t// y\n\tfor (h=1; h<ng4; h = -h + isneg(h)) {\n\t  i2 = par(goodmod(x+h,ng),y,goodmod(z+h,ng),ng);\n\t  d1 = r[0][i2]-r[0][i];\n\t  d2 = r[2][i2]-r[2][i];\n\t  if (d1 < negb2) d1 += boxsize;\n\t  if (d1 > b2) d1 -= boxsize;\n\t  if (d2 < negb2) d2 += boxsize;\n\t  if (d2 > b2) d2 -= boxsize;\n\t  if ((d1 + d2)*h < 0.) {\n\t    m1[i] *= 2;\n\t    break;\n\t  }\n\t}\n\tfor (h=1; h<ng4; h = -h + isneg(h)) {\n\t  i2 = par(goodmod(x+h,ng),y,goodmod(z-h,ng),ng);\n\t  d1 = r[0][i2]-r[0][i];\n\t  d2 = r[2][i2]-r[2][i];\n\t  if (d1 < negb2) d1 += boxsize;\n\t  if (d1 > b2) d1 -= boxsize;\n\t  if (d2 < negb2) d2 += boxsize;\n\t  if (d2 > b2) d2 -= boxsize;\n\t  if ((d1 - d2)*h < 0.) {\n\t    m1[i] *= 3;\n\t    break;\n\t  }\n\t}\n\t// z\n\tfor (h=1; h<ng4; h = -h + isneg(h)) {\n\t  i2 = par(goodmod(x+h,ng),goodmod(y+h,ng),z,ng);\n\t  d1 = r[0][i2]-r[0][i];\n\t  d2 = r[1][i2]-r[1][i];\n\t  if (d1 < negb2) d1 += boxsize;\n\t  if (d1 > b2) d1 -= boxsize;\n\t  if (d2 < negb2) d2 += boxsize;\n\t  if (d2 > b2) d2 -= boxsize;\n\t  if ((d1 + d2)*h < 0.) {\n\t    m2[i] *=2;\n\t    break;\n\t  }\n\t}\n\tfor (h=1; h<ng4; h = -h + isneg(h)) {\n\t  i2 = par(goodmod(x+h,ng),goodmod(y-h,ng),z,ng);\n\t  d1 = r[0][i2]-r[0][i];\n\t  d2 = r[1][i2]-r[1][i];\n\t  if (d1 < negb2) d1 += boxsize;\n\t  if (d1 > b2) d1 -= boxsize;\n\t  if (d2 < negb2) d2 += boxsize;\n\t  if (d2 > b2) d2 -= boxsize;\n\t  if ((d1 - d2)*h < 0.) {\n\t    m2[i] *= 3;\n\t    break;\n\t  }\n\t  }\n      }\n    }\n  }\n\n  nhalo = 0;\n  nhalo0 = 0;\n  nhalo1 = 0;\n  nhalo2 = 0;\n  nhaloany = 0;\n  for (i=0;i<np;i++){\n    mn = (m[i]%2 == 0) + (m[i]%3 == 0) + (m[i]%5 == 0);\n    m0n = (unsigned char)(m[i]%2 == 0) + (unsigned char)(m0[i]%2 == 0) + (unsigned char)(m0[i]%3 == 0);\n    m1n = (unsigned char)(m[i]%3 == 0) + (unsigned char)(m1[i]%2 == 0) + (unsigned char)(m1[i]%3 == 0);\n    m2n = (unsigned char)(m[i]%5 == 0) + (unsigned char)(m2[i]%2 == 0) + (unsigned char)(m2[i]%3 == 0);\n    m[i] = max(mn,max(m0n,max(m1n,m2n)));\n    if (mn == 3) nhalo++;\n    if (m0n == 3) nhalo0++;\n    if (m1n == 3) nhalo1++;\n    if (m2n == 3) nhalo2++;\n    if (m[i] == 3) {\n      nhaloany++;\n    }\n  }\n  printf(\"nhalo=%d,%d,%d,%d,%d\\n\",nhalo,nhalo0,nhalo1,nhalo2,nhaloany);\n  free(m0);\n  free(m1);\n  free(m2);\n\n  /* Output m */\n  return 0;\n}\n"
  },
  {
    "path": "yt/utilities/lib/origami_tags.h",
    "content": "#ifndef __ORIGAMI_TAGS_H__\n#define __ORIGAMI_TAGS_H__\n#include <stdio.h>\n#include <stdlib.h>\n\n#define BF 1e30\n#define max(A,B) (((A)>(B)) ? (A):(B))\n#define goodmod(A,B) (((A) >= (B)) ? (A-B):(((A) < 0) ? (A+B):(A)))\n\nint isneg(int h);\nint par(int i, int j, int k, int ng);\nint compute_tags(int ng, double boxsize, double **r, int npart,\n                 unsigned char *m);\n#endif // __ORIGAMI_TAGS_H__\n"
  },
  {
    "path": "yt/utilities/lib/particle_kdtree_tools.pxd",
    "content": "cimport cython\ncimport numpy as np\n\nfrom yt.utilities.lib.bounded_priority_queue cimport BoundedPriorityQueue\nfrom yt.utilities.lib.cykdtree.kdtree cimport KDTree, uint64_t\n\n\ncdef struct axes_range:\n    int start\n    int stop\n    int step\n\ncdef int set_axes_range(axes_range *axes, int skipaxis)\n\ncdef int find_neighbors(np.float64_t * pos, np.float64_t[:, ::1] tree_positions,\n                        BoundedPriorityQueue queue, KDTree * c_tree,\n                        uint64_t skipidx, axes_range * axes) except -1 nogil\n"
  },
  {
    "path": "yt/utilities/lib/particle_kdtree_tools.pyx",
    "content": "# distutils: language = c++\n\"\"\"\nCython tools for working with the PyKDTree particle KDTree.\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom cpython.exc cimport PyErr_CheckSignals\nfrom libc.math cimport sqrt\n\nfrom yt.utilities.lib.cykdtree.kdtree cimport KDTree, Node, PyKDTree, uint32_t, uint64_t\n\nfrom yt.funcs import get_pbar\n\nfrom yt.geometry.particle_deposit cimport get_kernel_func, kernel_func\nfrom yt.utilities.lib.bounded_priority_queue cimport BoundedPriorityQueue, NeighborList\n\n\ncdef int CHUNKSIZE = 4096\n\n# This structure allows the nearest neighbor finding to consider a subset of\n# spatial dimensions, i.e the spatial separation in the x and z coordinates\n# could be consider by using set_axes_range(axes, 1), this would cause the while\n# loops to skip the y dimensions, without the performance hit of an if statement\ncdef struct axes_range:\n    int start\n    int stop\n    int step\n\n# skipaxis: x=0, y=1, z=2\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef int set_axes_range(axes_range *axes, int skipaxis):\n    axes.start = 0\n    axes.stop = 3\n    axes.step = 1\n    if skipaxis == 0:\n        axes.start = 1\n    if skipaxis == 1:\n        axes.step = 2\n    if skipaxis == 2:\n        axes.stop = 2\n    return 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef generate_smoothing_length(np.float64_t[:, ::1] tree_positions,\n                              PyKDTree kdtree, int n_neighbors):\n    \"\"\"Calculate array of distances to the nth nearest neighbor\n\n    Parameters\n    ----------\n\n    tree_positions: arrays of floats with shape (n_particles, 3)\n        The positions of particles in kdtree sorted order. Currently assumed\n        to be 3D positions.\n    kdtree: A PyKDTree instance\n        A kdtree to do nearest neighbors searches with\n    n_neighbors: The neighbor number to calculate the distance to\n\n    Returns\n    -------\n\n    smoothing_lengths: arrays of floats with shape (n_particles, )\n        The calculated smoothing lengths\n\n    \"\"\"\n    cdef int i\n    cdef KDTree * c_tree = kdtree._tree\n    cdef int n_particles = tree_positions.shape[0]\n    cdef np.float64_t * pos\n    cdef np.float64_t[:] smoothing_length = np.empty(n_particles)\n    cdef BoundedPriorityQueue queue = BoundedPriorityQueue(n_neighbors)\n\n    # We are using all spatial dimensions\n    cdef axes_range axes\n    set_axes_range(&axes, -1)\n\n    pbar = get_pbar(\"Generate smoothing length\", n_particles)\n    with nogil:\n        for i in range(n_particles):\n            # Reset queue to \"empty\" state, doing it this way avoids\n            # needing to reallocate memory\n            queue.size = 0\n\n            if i % CHUNKSIZE == 0:\n                with gil:\n                    pbar.update(i-1)\n                    PyErr_CheckSignals()\n\n            pos = &(tree_positions[i, 0])\n            find_neighbors(pos, tree_positions, queue, c_tree, i, &axes)\n\n            smoothing_length[i] = sqrt(queue.heap_ptr[0])\n\n    pbar.update(n_particles-1)\n    pbar.finish()\n    return np.asarray(smoothing_length)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef estimate_density(np.float64_t[:, ::1] tree_positions, np.float64_t[:] mass,\n                      np.float64_t[:] smoothing_length,\n                      PyKDTree kdtree, kernel_name=\"cubic\"):\n    \"\"\"Estimate density using SPH gather method.\n\n    Parameters\n    ----------\n\n    tree_positions: array of floats with shape (n_particles, 3)\n        The positions of particles in kdtree sorted order. Currently assumed\n        to be 3D positions.\n    mass: array of floats with shape (n_particles)\n        The masses of particles in kdtree sorted order.\n    smoothing_length: array of floats with shape (n_particles)\n        The smoothing lengths of particles in kdtree sorted order.\n    kdtree: A PyKDTree instance\n        A kdtree to do nearest neighbors searches with.\n    kernel_name: str\n        The name of the kernel function to use in density estimation.\n\n    Returns\n    -------\n\n    density: array of floats with shape (n_particles)\n        The calculated density.\n\n    \"\"\"\n    cdef int i, j, k\n    cdef KDTree * c_tree = kdtree._tree\n    cdef int n_particles = tree_positions.shape[0]\n    cdef np.float64_t h_i2, ih_i2, q_ij\n    cdef np.float64_t * pos\n    cdef np.float64_t[:] density = np.empty(n_particles)\n    cdef kernel_func kernel = get_kernel_func(kernel_name)\n    cdef NeighborList nblist = NeighborList()\n\n    # We are using all spatial dimensions\n    cdef axes_range axes\n    set_axes_range(&axes, -1)\n\n    pbar = get_pbar(\"Estimating density\", n_particles)\n    with nogil:\n        for i in range(n_particles):\n            # Reset list to \"empty\" state, doing it this way avoids\n            # needing to reallocate memory\n            nblist.size = 0\n\n            if i % CHUNKSIZE == 0:\n                with gil:\n                    pbar.update(i - 1)\n                    PyErr_CheckSignals()\n\n            pos = &(tree_positions[i, 0])\n            h_i2 = smoothing_length[i] ** 2\n            find_neighbors_ball(pos, h_i2, tree_positions, nblist, c_tree, i, &axes)\n            ih_i2 = 1.0 / h_i2\n\n            # See eq. 10 of Price 2012\n            density[i] = mass[i] * kernel(0)\n            for k in range(nblist.size):\n                j = nblist.pids[k]\n                q_ij = sqrt(nblist.data[k] * ih_i2)\n                density[i] += mass[j] * kernel(q_ij)\n\n    pbar.update(n_particles - 1)\n    pbar.finish()\n    return np.asarray(density)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef int find_neighbors(np.float64_t * pos, np.float64_t[:, ::1] tree_positions,\n                        BoundedPriorityQueue queue, KDTree * c_tree,\n                        uint64_t skipidx, axes_range * axes) except -1 nogil:\n    cdef Node* leafnode\n\n    # Make an initial guess based on the closest node\n    leafnode = c_tree.search(&pos[0])\n    process_node_points(leafnode, queue, tree_positions, pos, skipidx, axes)\n\n    # Traverse the rest of the kdtree to finish the neighbor list\n    find_knn(c_tree.root, queue, tree_positions, pos, leafnode.leafid, skipidx,\n             axes)\n\n    return 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef int find_knn(Node* node,\n                  BoundedPriorityQueue queue,\n                  np.float64_t[:, ::1] tree_positions,\n                  np.float64_t* pos,\n                  uint32_t skipleaf,\n                  uint64_t skipidx,\n                  axes_range * axes,\n                  ) except -1 nogil:\n    # if we aren't a leaf then we keep traversing until we find a leaf, else we\n    # we actually begin to check the leaf\n    if not node.is_leaf:\n        if not cull_node(node.less, pos, queue, skipleaf, axes):\n            find_knn(node.less, queue, tree_positions, pos, skipleaf, skipidx,\n                     axes)\n        if not cull_node(node.greater, pos, queue, skipleaf, axes):\n            find_knn(node.greater, queue, tree_positions, pos, skipleaf,\n                     skipidx, axes)\n    else:\n        if not cull_node(node, pos, queue, skipleaf, axes):\n            process_node_points(node, queue, tree_positions, pos, skipidx,\n                                axes)\n    return 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline int cull_node(Node* node,\n                          np.float64_t* pos,\n                          BoundedPriorityQueue queue,\n                          uint32_t skipleaf,\n                          axes_range * axes,\n                          ) except -1 nogil:\n    cdef int k\n    cdef np.float64_t v\n    cdef np.float64_t tpos, ndist = 0\n\n    if node.leafid == skipleaf:\n        return True\n\n    k = axes.start\n    while k < axes.stop:\n        v = pos[k]\n        if v < node.left_edge[k]:\n            tpos = node.left_edge[k] - v\n        elif v > node.right_edge[k]:\n            tpos = v - node.right_edge[k]\n        else:\n            tpos = 0\n        ndist += tpos*tpos\n        k += axes.step\n\n    return (ndist > queue.heap[0] and queue.size == queue.max_elements)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline int process_node_points(Node* node,\n                                    BoundedPriorityQueue queue,\n                                    np.float64_t[:, ::1] positions,\n                                    np.float64_t* pos,\n                                    int skipidx,\n                                    axes_range * axes,\n                                    ) except -1 nogil:\n    cdef uint64_t i, k\n    cdef np.float64_t tpos, sq_dist\n    for i in range(node.left_idx, node.left_idx + node.children):\n        if i == skipidx:\n            continue\n\n        sq_dist = 0.0\n\n        k = axes.start\n        while k < axes.stop:\n            tpos = positions[i, k] - pos[k]\n            sq_dist += tpos*tpos\n            k += axes.step\n\n        queue.add_pid(sq_dist, i)\n\n    return 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef int find_neighbors_ball(np.float64_t * pos, np.float64_t r2,\n                             np.float64_t[:, ::1] tree_positions,\n                             NeighborList nblist, KDTree * c_tree,\n                             uint64_t skipidx, axes_range * axes\n                             ) except -1 nogil:\n    \"\"\"Find neighbors within a ball.\"\"\"\n    cdef Node* leafnode\n\n    # Make an initial guess based on the closest node\n    leafnode = c_tree.search(&pos[0])\n    process_node_points_ball(leafnode, nblist, tree_positions, pos, r2, skipidx, axes)\n\n    # Traverse the rest of the kdtree to finish the neighbor list\n    find_ball(c_tree.root, nblist, tree_positions, pos, r2, leafnode.leafid,\n              skipidx, axes)\n\n    return 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef int find_ball(Node* node,\n                   NeighborList nblist,\n                   np.float64_t[:, ::1] tree_positions,\n                   np.float64_t* pos,\n                   np.float64_t r2,\n                   uint32_t skipleaf,\n                   uint64_t skipidx,\n                   axes_range * axes,\n                   ) except -1 nogil:\n    \"\"\"Traverse the k-d tree to process leaf nodes.\"\"\"\n    if not node.is_leaf:\n        if not cull_node_ball(node.less, pos, r2, skipleaf, axes):\n            find_ball(node.less, nblist, tree_positions, pos, r2, skipleaf,\n                      skipidx, axes)\n        if not cull_node_ball(node.greater, pos, r2, skipleaf, axes):\n            find_ball(node.greater, nblist, tree_positions, pos, r2, skipleaf,\n                      skipidx, axes)\n    else:\n        if not cull_node_ball(node, pos, r2, skipleaf, axes):\n            process_node_points_ball(node, nblist, tree_positions, pos, r2,\n                                     skipidx, axes)\n    return 0\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline int cull_node_ball(Node* node,\n                               np.float64_t* pos,\n                               np.float64_t r2,\n                               uint32_t skipleaf,\n                               axes_range * axes,\n                               ) except -1 nogil:\n    \"\"\"Check if the node does not intersect with the ball at all.\"\"\"\n    cdef int k\n    cdef np.float64_t v\n    cdef np.float64_t tpos, ndist = 0\n\n    if node.leafid == skipleaf:\n        return True\n\n    k = axes.start\n    while k < axes.stop:\n        v = pos[k]\n        if v < node.left_edge[k]:\n            tpos = node.left_edge[k] - v\n        elif v > node.right_edge[k]:\n            tpos = v - node.right_edge[k]\n        else:\n            tpos = 0\n        ndist += tpos*tpos\n        k += axes.step\n\n    return ndist > r2\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef inline int process_node_points_ball(Node* node,\n                                         NeighborList nblist,\n                                         np.float64_t[:, ::1] positions,\n                                         np.float64_t* pos,\n                                         np.float64_t r2,\n                                         int skipidx,\n                                         axes_range * axes,\n                                         ) except -1 nogil:\n    \"\"\"Add points from the leaf node within the ball to the neighbor list.\"\"\"\n    cdef uint64_t i, k\n    cdef np.float64_t tpos, sq_dist\n    for i in range(node.left_idx, node.left_idx + node.children):\n        if i == skipidx:\n            continue\n\n        sq_dist = 0.0\n\n        k = axes.start\n        while k < axes.stop:\n            tpos = positions[i, k] - pos[k]\n            sq_dist += tpos*tpos\n            k += axes.step\n\n        if (sq_dist < r2):\n            nblist.add_pid(sq_dist, i)\n\n    return 0\n"
  },
  {
    "path": "yt/utilities/lib/particle_mesh_operations.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nSimple integrators for the radiative transfer equation\n\n\n\n\"\"\"\n\n\ncimport cython\ncimport numpy as np\n\nimport numpy as np\n\nfrom yt.utilities.lib.fp_utils cimport fclip\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef CICDeposit_3(np.ndarray[np.float64_t, ndim=1] posx,\n                 np.ndarray[np.float64_t, ndim=1] posy,\n                 np.ndarray[np.float64_t, ndim=1] posz,\n                 np.ndarray[np.float64_t, ndim=1] mass,\n                 np.int64_t npositions,\n                 np.ndarray[np.float64_t, ndim=3] field,\n                 np.ndarray[np.float64_t, ndim=1] leftEdge,\n                 np.ndarray[np.int32_t, ndim=1] gridDimension,\n                 np.float64_t cellSize):\n\n    cdef int i1, j1, k1, n\n    cdef np.float64_t xpos, ypos, zpos\n    cdef np.float64_t fact, edge0, edge1, edge2\n    cdef np.float64_t le0, le1, le2\n    cdef np.float64_t dx, dy, dz, dx2, dy2, dz2\n\n    edge0 = (<np.float64_t> gridDimension[0]) - 0.5001\n    edge1 = (<np.float64_t> gridDimension[1]) - 0.5001\n    edge2 = (<np.float64_t> gridDimension[2]) - 0.5001\n    fact = 1.0 / cellSize\n\n    le0 = leftEdge[0]\n    le1 = leftEdge[1]\n    le2 = leftEdge[2]\n\n    for n in range(npositions):\n\n        # Compute the position of the central cell\n        xpos = (posx[n] - le0)*fact\n        ypos = (posy[n] - le1)*fact\n        zpos = (posz[n] - le2)*fact\n\n        if (xpos < 0.5001) or (xpos > edge0):\n            continue\n        if (ypos < 0.5001) or (ypos > edge1):\n            continue\n        if (zpos < 0.5001) or (zpos > edge2):\n            continue\n\n        i1  = <int> (xpos + 0.5)\n        j1  = <int> (ypos + 0.5)\n        k1  = <int> (zpos + 0.5)\n\n        # Compute the weights\n        dx = (<np.float64_t> i1) + 0.5 - xpos\n        dy = (<np.float64_t> j1) + 0.5 - ypos\n        dz = (<np.float64_t> k1) + 0.5 - zpos\n        dx2 =  1.0 - dx\n        dy2 =  1.0 - dy\n        dz2 =  1.0 - dz\n\n        # Interpolate from field into sumfield\n        field[i1-1,j1-1,k1-1] += mass[n] * dx  * dy  * dz\n        field[i1  ,j1-1,k1-1] += mass[n] * dx2 * dy  * dz\n        field[i1-1,j1  ,k1-1] += mass[n] * dx  * dy2 * dz\n        field[i1  ,j1  ,k1-1] += mass[n] * dx2 * dy2 * dz\n        field[i1-1,j1-1,k1  ] += mass[n] * dx  * dy  * dz2\n        field[i1  ,j1-1,k1  ] += mass[n] * dx2 * dy  * dz2\n        field[i1-1,j1  ,k1  ] += mass[n] * dx  * dy2 * dz2\n        field[i1  ,j1  ,k1  ] += mass[n] * dx2 * dy2 * dz2\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef CICDeposit_2(np.float64_t[:] posx,\n                 np.float64_t[:] posy,\n                 np.float64_t[:] mass,\n                 np.int64_t npositions,\n                 np.float64_t[:, :] field,\n                 np.uint8_t[:, :] field_mask,\n                 np.float64_t[:] x_bin_edges,\n                 np.float64_t[:] y_bin_edges):\n\n    cdef int i1, j1, n\n    cdef np.float64_t xpos, ypos\n    cdef np.float64_t edgex, edgey\n    cdef np.float64_t dx, dy, ddx, ddy, ddx2, ddy2\n\n    edgex = (<np.float64_t> x_bin_edges.shape[0] - 1) + 0.5001\n    edgey = (<np.float64_t> y_bin_edges.shape[0] - 1) + 0.5001\n\n    # We are always dealing with uniformly spaced bins for CiC\n    dx = x_bin_edges[1] - x_bin_edges[0]\n    dy = y_bin_edges[1] - y_bin_edges[0]\n\n    for n in range(npositions):\n\n        # Compute the position of the central cell\n        xpos = (posx[n] - x_bin_edges[0])/dx\n        ypos = (posy[n] - y_bin_edges[0])/dy\n\n        if (xpos < -0.5001) or (xpos > edgex):\n            continue\n        if (ypos < -0.5001) or (ypos > edgey):\n            continue\n\n        i1  = <int> (xpos + 0.5)\n        j1  = <int> (ypos + 0.5)\n\n        # Compute the weights\n        ddx = (<np.float64_t> i1) + 0.5 - xpos\n        ddy = (<np.float64_t> j1) + 0.5 - ypos\n        ddx2 =  1.0 - ddx\n        ddy2 =  1.0 - ddy\n\n        # Deposit onto field\n        if i1 > 0 and j1 > 0:\n            field[i1-1,j1-1] += mass[n] * ddx  * ddy\n            field_mask[i1-1,j1-1] = 1\n        if j1 > 0 and i1 < field.shape[0]:\n            field[i1  ,j1-1] += mass[n] * ddx2 * ddy\n            field_mask[i1,j1-1] = 1\n        if i1 > 0 and j1 < field.shape[1]:\n            field[i1-1,j1  ] += mass[n] * ddx  * ddy2\n            field_mask[i1-1,j1] = 1\n        if i1 < field.shape[0] and j1 < field.shape[1]:\n            field[i1  ,j1  ] += mass[n] * ddx2 * ddy2\n            field_mask[i1,j1] = 1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef NGPDeposit_2(np.float64_t[:] posx,\n                 np.float64_t[:] posy,\n                 np.float64_t[:] mass,\n                 np.int64_t npositions,\n                 np.float64_t[:, :] field,\n                 np.uint8_t[:, :] field_mask,\n                 np.float64_t[:] x_bin_edges,\n                 np.float64_t[:] y_bin_edges):\n\n    cdef int i, j, i1, j1, n\n    cdef np.float64_t xpos, ypos\n    cdef np.float64_t[2] x_endpoints\n    cdef np.float64_t[2] y_endpoints\n\n    x_endpoints = (x_bin_edges[0], x_bin_edges[x_bin_edges.shape[0] - 1])\n    y_endpoints = (y_bin_edges[0], y_bin_edges[y_bin_edges.shape[0] - 1])\n\n    for n in range(npositions):\n\n        xpos = posx[n]\n        ypos = posy[n]\n\n        if (xpos < x_endpoints[0]) or (xpos > x_endpoints[1]):\n            continue\n        if (ypos < y_endpoints[0]) or (ypos > y_endpoints[1]):\n            continue\n\n        for i in range(x_bin_edges.shape[0]):\n            if (xpos >= x_bin_edges[i]) and (xpos < x_bin_edges[i+1]):\n                i1 = i\n                break\n\n        for j in range(y_bin_edges.shape[0]):\n            if (ypos >= y_bin_edges[j]) and (ypos < y_bin_edges[j+1]):\n                j1 = j\n                break\n\n        # Deposit onto field\n        field[i1,j1] += mass[n]\n        field_mask[i1,j1] = 1\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef sample_field_at_positions(np.ndarray[np.float64_t, ndim=3] arr,\n                              np.ndarray[np.float64_t, ndim=1] left_edge,\n                              np.ndarray[np.float64_t, ndim=1] right_edge,\n                              np.ndarray[np.float64_t, ndim=1] pos_x,\n                              np.ndarray[np.float64_t, ndim=1] pos_y,\n                              np.ndarray[np.float64_t, ndim=1] pos_z):\n    cdef np.float64_t idds[3]\n    cdef int dims[3]\n    cdef int ind[3]\n    cdef int i, npart\n    npart = pos_x.shape[0]\n    cdef np.ndarray[np.float64_t, ndim=1] sample\n    sample = np.zeros(npart, dtype='float64')\n    for i in range(3):\n        dims[i] = arr.shape[i]\n        idds[i] = (<np.float64_t> dims[i]) / (right_edge[i] - left_edge[i])\n    for i in range(npart):\n        if not ((left_edge[0] <= pos_x[i] <= right_edge[0]) and\n                (left_edge[1] <= pos_y[i] <= right_edge[1]) and\n                (left_edge[2] <= pos_z[i] <= right_edge[2])):\n            continue\n        ind[0] = <int> ((pos_x[i] - left_edge[0]) * idds[0])\n        ind[1] = <int> ((pos_y[i] - left_edge[1]) * idds[1])\n        ind[2] = <int> ((pos_z[i] - left_edge[2]) * idds[2])\n        sample[i] = arr[ind[0], ind[1], ind[2]]\n    return sample\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef CICSample_3(np.ndarray[np.float64_t, ndim=1] posx,\n                np.ndarray[np.float64_t, ndim=1] posy,\n                np.ndarray[np.float64_t, ndim=1] posz,\n                np.ndarray[np.float64_t, ndim=1] sample,\n                np.int64_t npositions,\n                np.ndarray[np.float64_t, ndim=3] field,\n                np.ndarray[np.float64_t, ndim=1] leftEdge,\n                np.ndarray[np.int32_t, ndim=1] gridDimension,\n                np.float64_t cellSize):\n\n    cdef int i1, j1, k1, n\n    cdef np.float64_t xpos, ypos, zpos\n    cdef np.float64_t fact, edge0, edge1, edge2\n    cdef np.float64_t le0, le1, le2\n    cdef np.float64_t dx, dy, dz, dx2, dy2, dz2\n\n    edge0 = (<np.float64_t> gridDimension[0]) - 0.5001\n    edge1 = (<np.float64_t> gridDimension[1]) - 0.5001\n    edge2 = (<np.float64_t> gridDimension[2]) - 0.5001\n    fact = 1.0 / cellSize\n\n    le0 = leftEdge[0]\n    le1 = leftEdge[1]\n    le2 = leftEdge[2]\n\n    for n in range(npositions):\n\n        # Compute the position of the central cell\n\n        xpos = (posx[n]-le0)*fact\n        ypos = (posy[n]-le1)*fact\n        zpos = (posz[n]-le2)*fact\n\n        if (xpos < -1 or ypos < -1 or zpos < -1 or\n            xpos >= edge0+1.5001 or ypos >= edge1+1.5001 or zpos >= edge2+1.5001):\n            continue\n\n        xpos = fclip(xpos, 0.5001, edge0)\n        ypos = fclip(ypos, 0.5001, edge1)\n        zpos = fclip(zpos, 0.5001, edge2)\n\n        i1  = <int> (xpos + 0.5)\n        j1  = <int> (ypos + 0.5)\n        k1  = <int> (zpos + 0.5)\n\n        # Compute the weights\n        dx = (<float> i1) + 0.5 - xpos\n        dy = (<float> j1) + 0.5 - ypos\n        dz = (<float> k1) + 0.5 - zpos\n        dx2 =  1.0 - dx\n        dy2 =  1.0 - dy\n        dz2 =  1.0 - dz\n\n        # Interpolate from field onto the particle\n        sample[n] = (field[i1-1,j1-1,k1-1] * dx  * dy  * dz +\n                     field[i1  ,j1-1,k1-1] * dx2 * dy  * dz +\n                     field[i1-1,j1  ,k1-1] * dx  * dy2 * dz +\n                     field[i1  ,j1  ,k1-1] * dx2 * dy2 * dz +\n                     field[i1-1,j1-1,k1  ] * dx  * dy  * dz2 +\n                     field[i1  ,j1-1,k1  ] * dx2 * dy  * dz2 +\n                     field[i1-1,j1  ,k1  ] * dx  * dy2 * dz2 +\n                     field[i1  ,j1  ,k1  ] * dx2 * dy2 * dz2)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef assign_particles_to_cells(np.ndarray[np.int32_t, ndim=1] levels, #for cells\n                              np.ndarray[np.float32_t, ndim=2] left_edges, #many cells\n                              np.ndarray[np.float32_t, ndim=2] right_edges,\n                              np.ndarray[np.float32_t, ndim=1] pos_x, #particle\n                              np.ndarray[np.float32_t, ndim=1] pos_y,\n                              np.ndarray[np.float32_t, ndim=1] pos_z):\n    #for every cell, assign the particles belonging to it,\n    #skipping previously assigned particles\n    cdef long level_max = np.max(levels)\n    cdef long i,j,level\n    cdef long npart = pos_x.shape[0]\n    cdef long ncells = left_edges.shape[0]\n    cdef np.ndarray[np.int32_t, ndim=1] assign = np.zeros(npart,dtype='int32')-1\n    for level in range(level_max,0,-1):\n        #start with the finest level\n        for i in range(ncells):\n            #go through every cell on the finest level first\n            if not levels[i] == level: continue\n            for j in range(npart):\n                #iterate over all particles, skip if assigned\n                if assign[j]>-1: continue\n                if (left_edges[i,0] <= pos_x[j] <= right_edges[i,0]):\n                    if (left_edges[i,1] <= pos_y[j] <= right_edges[i,1]):\n                        if (left_edges[i,2] <= pos_z[j] <= right_edges[i,2]):\n                            assign[j]=i\n    return assign\n\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef assign_particles_to_cell_lists(np.ndarray[np.int32_t, ndim=1] levels, #for cells\n                              np.ndarray[np.int32_t,ndim=1] assign,\n                              np.int64_t level_max,\n                              np.ndarray[np.float32_t, ndim=2] left_edges, #many cells\n                              np.ndarray[np.float32_t, ndim=2] right_edges,\n                              np.ndarray[np.float32_t, ndim=1] pos_x, #particle\n                              np.ndarray[np.float32_t, ndim=1] pos_y,\n                              np.ndarray[np.float32_t, ndim=1] pos_z):\n    #for every cell, assign the particles belonging to it,\n    #skipping previously assigned particles\n    #Todo: instead of iterating every particles, could use kdtree\n    cdef long i,j,level\n    cdef long npart = pos_x.shape[0]\n    cdef long ncells = left_edges.shape[0]\n    #cdef np.ndarray[np.int32_t, ndim=1] assign\n    #assign = np.zeros(npart,dtype='int32')-1\n    index_lists = []\n    for level in range(level_max,-1,-1):\n        #start with the finest level\n        for i in range(ncells):\n            #go through every cell on the finest level first\n            if not levels[i] == level: continue\n            index_list = []\n            for j in range(npart):\n                #iterate over all particles, skip if assigned\n                if assign[j]>-1: continue\n                if (left_edges[i,0] <= pos_x[j] <= right_edges[i,0]):\n                    if (left_edges[i,1] <= pos_y[j] <= right_edges[i,1]):\n                        if (left_edges[i,2] <= pos_z[j] <= right_edges[i,2]):\n                            assign[j]=i\n                            index_list += j,\n            index_lists += index_list,\n    return assign,index_lists\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef recursive_particle_assignment(grids, grid,\n                                  np.ndarray[np.float32_t, ndim=2] left_edges, #many cells\n                                  np.ndarray[np.float32_t, ndim=2] right_edges,\n                                  np.ndarray[np.float32_t, ndim=1] pos_x, #particle\n                                  np.ndarray[np.float32_t, ndim=1] pos_y,\n                                  np.ndarray[np.float32_t, ndim=1] pos_z):\n    #start on level zero, grid particles onto every mesh\n    #every particle we are fed, we can assume it exists on our grid\n    #must fill in the grid_particle_count array\n    #and particle_indices for every grid\n    cdef long i, j\n    cdef long npart = pos_x.shape[0]\n    cdef np.ndarray[np.int32_t, ndim=1] assigned       = np.zeros(npart,dtype='int32')\n    cdef np.ndarray[np.int32_t, ndim=1] never_assigned = np.ones(npart,dtype='int32')\n    for i in np.unique(grid.child_index_mask):\n        if i== -1: continue\n        #assigned to this subgrid\n        assigned = np.zeros(npart,dtype='int32')\n        for j in range(npart):\n            if (left_edges[i,0] <= pos_x[j] <= right_edges[i,0]):\n                if (left_edges[i,1] <= pos_y[j] <= right_edges[i,1]):\n                    if (left_edges[i,2] <= pos_z[j] <= right_edges[i,2]):\n                       assigned[j]=1\n                       never_assigned[j]=0\n        if np.sum(assigned)>0:\n            recursive_particle_assignment(grids,grid,left_edges,right_edges,\n                                           pos_x[assigned],pos_y[assigned],pos_z[assigned])\n    #now we have assigned particles to other subgrids, we are left with particles on our grid\n"
  },
  {
    "path": "yt/utilities/lib/partitioned_grid.pxd",
    "content": "\"\"\"\nDefinitions for the partitioned grid\n\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\nfrom .volume_container cimport VolumeContainer\n\n\ncdef class PartitionedGrid:\n    cdef public object my_data\n    cdef public object source_mask\n    cdef public object LeftEdge\n    cdef public object RightEdge\n    cdef public int parent_grid_id\n    cdef VolumeContainer *container\n    cdef np.float64_t star_er\n    cdef np.float64_t star_sigma_num\n    cdef np.float64_t star_coeff\n    cdef void get_vector_field(self, np.float64_t pos[3],\n                               np.float64_t *vel, np.float64_t *vel_mag)\n"
  },
  {
    "path": "yt/utilities/lib/partitioned_grid.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: libraries = STD_LIBS FIXED_INTERP\n# distutils: language = c++\n# distutils: extra_compile_args = CPP14_FLAG\n# distutils: extra_link_args = CPP14_FLAG\n\"\"\"\nImage sampler definitions\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.stdlib cimport free, malloc\n\nfrom .fixed_interpolator cimport offset_interpolate\n\n\ncdef class PartitionedGrid:\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def __cinit__(self,\n                  int parent_grid_id, data,\n                  mask,\n                  np.ndarray[np.float64_t, ndim=1] left_edge,\n                  np.ndarray[np.float64_t, ndim=1] right_edge,\n                  np.ndarray[np.int64_t, ndim=1] dims,\n                  int n_fields = -1):\n        # The data is likely brought in via a slice, so we copy it\n        cdef np.ndarray[np.float64_t, ndim=3] tdata\n        cdef np.ndarray[np.uint8_t, ndim=3] mask_data\n        self.container = NULL\n        self.parent_grid_id = parent_grid_id\n        self.LeftEdge = left_edge\n        self.RightEdge = right_edge\n        self.container = <VolumeContainer *> \\\n            malloc(sizeof(VolumeContainer))\n        cdef VolumeContainer *c = self.container # convenience\n        if n_fields == -1:\n            n_fields = len(data)\n        cdef int n_data = len(data)\n\n        c.n_fields = n_fields\n        for i in range(3):\n            c.left_edge[i] = left_edge[i]\n            c.right_edge[i] = right_edge[i]\n            c.dims[i] = dims[i]\n            c.dds[i] = (c.right_edge[i] - c.left_edge[i])/dims[i]\n            c.idds[i] = 1.0/c.dds[i]\n        self.my_data = data\n        self.source_mask = mask\n        mask_data = mask\n        c.data = <np.float64_t **> malloc(sizeof(np.float64_t*) * n_fields)\n        for i in range(n_data):\n            tdata = data[i]\n            c.data[i] = <np.float64_t *> tdata.data\n        c.mask = <np.uint8_t *> mask_data.data\n\n    def __dealloc__(self):\n        # The data fields are not owned by the container, they are owned by us!\n        # So we don't need to deallocate them.\n        if self.container == NULL: return\n        if self.container.data != NULL: free(self.container.data)\n        free(self.container)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def integrate_streamline(self, pos, np.float64_t h, mag):\n        cdef np.float64_t cmag[1]\n        cdef np.float64_t k1[3]\n        cdef np.float64_t k2[3]\n        cdef np.float64_t k3[3]\n        cdef np.float64_t k4[3]\n        cdef np.float64_t newpos[3]\n        cdef np.float64_t oldpos[3]\n        for i in range(3):\n            newpos[i] = oldpos[i] = pos[i]\n        self.get_vector_field(newpos, k1, cmag)\n        for i in range(3):\n            newpos[i] = oldpos[i] + 0.5*k1[i]*h\n\n        if not (self.LeftEdge[0] < newpos[0] and newpos[0] < self.RightEdge[0] and \\\n                self.LeftEdge[1] < newpos[1] and newpos[1] < self.RightEdge[1] and \\\n                self.LeftEdge[2] < newpos[2] and newpos[2] < self.RightEdge[2]):\n            if mag is not None:\n                mag[0] = cmag[0]\n            for i in range(3):\n                pos[i] = newpos[i]\n            return\n\n        self.get_vector_field(newpos, k2, cmag)\n        for i in range(3):\n            newpos[i] = oldpos[i] + 0.5*k2[i]*h\n\n        if not (self.LeftEdge[0] <= newpos[0] and newpos[0] <= self.RightEdge[0] and \\\n                self.LeftEdge[1] <= newpos[1] and newpos[1] <= self.RightEdge[1] and \\\n                self.LeftEdge[2] <= newpos[2] and newpos[2] <= self.RightEdge[2]):\n            if mag is not None:\n                mag[0] = cmag[0]\n            for i in range(3):\n                pos[i] = newpos[i]\n            return\n\n        self.get_vector_field(newpos, k3, cmag)\n        for i in range(3):\n            newpos[i] = oldpos[i] + k3[i]*h\n\n        if not (self.LeftEdge[0] <= newpos[0] and newpos[0] <= self.RightEdge[0] and \\\n                self.LeftEdge[1] <= newpos[1] and newpos[1] <= self.RightEdge[1] and \\\n                self.LeftEdge[2] <= newpos[2] and newpos[2] <= self.RightEdge[2]):\n            if mag is not None:\n                mag[0] = cmag[0]\n            for i in range(3):\n                pos[i] = newpos[i]\n            return\n\n        self.get_vector_field(newpos, k4, cmag)\n\n        for i in range(3):\n            pos[i] = oldpos[i] + h*(k1[i]/6.0 + k2[i]/3.0 + k3[i]/3.0 + k4[i]/6.0)\n\n        if mag is not None:\n            for i in range(3):\n                newpos[i] = pos[i]\n            self.get_vector_field(newpos, k4, cmag)\n            mag[0] = cmag[0]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef void get_vector_field(self, np.float64_t pos[3],\n                               np.float64_t *vel, np.float64_t *vel_mag):\n        cdef np.float64_t dp[3]\n        cdef int ci[3]\n        cdef VolumeContainer *c = self.container # convenience\n\n        for i in range(3):\n            ci[i] = (int)((pos[i]-self.LeftEdge[i])/c.dds[i])\n            dp[i] = (pos[i] - ci[i]*c.dds[i] - self.LeftEdge[i])/c.dds[i]\n\n        cdef int offset = ci[0] * (c.dims[1] + 1) * (c.dims[2] + 1) \\\n                          + ci[1] * (c.dims[2] + 1) + ci[2]\n\n        vel_mag[0] = 0.0\n        for i in range(3):\n            vel[i] = offset_interpolate(c.dims, dp, c.data[i] + offset)\n            vel_mag[0] += vel[i]*vel[i]\n        vel_mag[0] = np.sqrt(vel_mag[0])\n        if vel_mag[0] != 0.0:\n            for i in range(3):\n                vel[i] /= vel_mag[0]\n"
  },
  {
    "path": "yt/utilities/lib/pixelization_constants.cpp",
    "content": "/*******************************************************************************\n*******************************************************************************/\n//\n// Some Cython versions don't like module-level constants, so we'll put them\n// here.\n//\n\n#include \"pixelization_constants.hpp\"\n\n/*\n Six faces, two vectors for each, two indices for each vector.  The function\n below unrolls how these are defined.  Some info can be found at:\n http://www.mscsoftware.com/training_videos/patran/Reverb_help/index.html#page/Finite%20Element%20Modeling/elem_lib_topics.16.8.html\n This is [6][2][2] in shape.\n Here are the faces and their four edges each:\n F1    1   2   3   4\n F2    5   6   7   8\n F3    1   10  5   9\n F4    2   11  6   10\n F5    3   12  7   11\n F6    4   9   8   12\n\n The edges are then defined by:\n E1    1 2\n E2    2 6\n E3    6 5\n E4    5 1\n E5    4 3\n E6    3 7\n E7    7 8\n E8    8 4\n E9    1 4\n E10   2 3\n E11   6 7\n E12   5 8\n Now we unroll these here ...\n */\nconst npy_uint8 hex_face_defs[MAX_NUM_FACES][2][2] = {\n   /* Note that the first of each pair is the shared vertex */\n   {{1, 0}, {1, 5}},\n   {{2, 3}, {2, 6}},\n   {{1, 0}, {1, 2}},\n   {{5, 1}, {5, 6}},\n   {{4, 5}, {4, 7}},\n   {{0, 4}, {0, 3}},\n\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}}\n};\n\n/* http://www.mscsoftware.com/training_videos/patran/Reverb_help/index.html#page/Finite%2520Element%2520Modeling/elem_lib_topics.16.6.html\n\n  F1    1   2   3\n  F2    1   5   4\n  F3    2   6   5\n  F4    3   4   6\n\n  The edges are then defined by:\n  E1    1   2\n  E2    2   3\n  E3    3   1\n  E4    1   4\n  E5    2   4\n  E6    3   4\n*/\n\nconst npy_uint8 tetra_face_defs[MAX_NUM_FACES][2][2] = {\n   {{1, 0}, {1, 2}},\n   {{1, 0}, {1, 3}},\n   {{2, 1}, {2, 3}},\n   {{3, 0}, {3, 2}},\n\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}}\n};\n\n/* http://www.mscsoftware.com/training_videos/patran/Reverb_help/index.html#page/Finite%2520Element%2520Modeling/elem_lib_topics.16.7.html\n  F1    1   2   3   *\n  F2    4   5   6   *\n  F3    1   8   4   7\n  F4    2   9   5   8\n  F5    3   7   6   9\n\n  The edges are then defined by:\n  E1    2   1\n  E2    1   3\n  E3    3   2\n  E4    5   4\n  E5    4   6\n  E6    6   5\n  E7    2   5\n  E8    1   4\n  E9    3   6\n*/\n\nconst npy_uint8 wedge_face_defs[MAX_NUM_FACES][2][2] = {\n   {{0, 1}, {0, 2}},\n   {{3, 4}, {3, 5}},\n   {{0, 1}, {0, 3}},\n   {{2, 0}, {2, 5}},\n   {{1, 2}, {1, 4}},\n\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}},\n   {{255, 255}, {255, 255}}\n};\n"
  },
  {
    "path": "yt/utilities/lib/pixelization_constants.hpp",
    "content": "/*******************************************************************************\n*******************************************************************************/\n//\n// Some Cython versions don't like module-level constants, so we'll put them\n// here.\n//\n\n#include \"Python.h\"\n\n#include <stdio.h>\n#include <math.h>\n#include <signal.h>\n#include <ctype.h>\n\n#include \"numpy/ndarrayobject.h\"\n\n#define MAX_NUM_FACES 16\n\n#define HEX_IND     0\n#define HEX_NF      6\n#define TETRA_IND   1\n#define TETRA_NF    4\n#define WEDGE_IND   2\n#define WEDGE_NF    5\n\nextern const npy_uint8 hex_face_defs[MAX_NUM_FACES][2][2];\nextern const npy_uint8 tetra_face_defs[MAX_NUM_FACES][2][2];\nextern const npy_uint8 wedge_face_defs[MAX_NUM_FACES][2][2];\n"
  },
  {
    "path": "yt/utilities/lib/pixelization_routines.pyx",
    "content": "# distutils: include_dirs = LIB_DIR\n# distutils: extra_compile_args = CPP14_FLAG OMP_ARGS\n# distutils: extra_link_args = CPP14_FLAG OMP_ARGS\n# distutils: language = c++\n# distutils: libraries = STD_LIBS\n# distutils: sources = yt/utilities/lib/pixelization_constants.cpp\n\"\"\"\nPixelization routines\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport libc.math as math\ncimport numpy as np\nfrom cython.view cimport array as cvarray\n\nfrom yt.utilities.lib.fp_utils cimport (\n    any_float,\n    fabs,\n    fmax,\n    fmin,\n    i64max,\n    i64min,\n    iclip,\n)\n\nfrom yt.utilities.exceptions import YTElementTypeNotRecognized, YTPixelizeError\n\nfrom cpython.exc cimport PyErr_CheckSignals\nfrom cython.parallel cimport parallel, prange\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.geometry.particle_deposit cimport get_kernel_func, kernel_func\nfrom yt.utilities.lib.element_mappings cimport (\n    ElementSampler,\n    P1Sampler1D,\n    P1Sampler2D,\n    P1Sampler3D,\n    Q1Sampler2D,\n    Q1Sampler3D,\n    Q2Sampler2D,\n    S2Sampler3D,\n    T2Sampler2D,\n    Tet2Sampler3D,\n    W1Sampler3D,\n)\n\nfrom .vec3_ops cimport cross, dot, subtract\n\nfrom yt.funcs import get_pbar\n\nfrom yt.utilities.lib.bounded_priority_queue cimport BoundedPriorityQueue\nfrom yt.utilities.lib.cykdtree.kdtree cimport KDTree, PyKDTree\nfrom yt.utilities.lib.particle_kdtree_tools cimport (\n    axes_range,\n    find_neighbors,\n    set_axes_range,\n)\n\n\ncdef int TABLE_NVALS=512\n\ncdef extern from \"pixelization_constants.hpp\":\n    enum:\n        MAX_NUM_FACES\n\n    int HEX_IND\n    int HEX_NF\n    np.uint8_t hex_face_defs[MAX_NUM_FACES][2][2]\n\n    int TETRA_IND\n    int TETRA_NF\n    np.uint8_t tetra_face_defs[MAX_NUM_FACES][2][2]\n\n    int WEDGE_IND\n    int WEDGE_NF\n    np.uint8_t wedge_face_defs[MAX_NUM_FACES][2][2]\n\ncdef extern from \"numpy/npy_math.h\":\n    double NPY_PI\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef pixelize_cartesian(np.float64_t[:,:] buff,\n                       any_float[:] px,\n                       any_float[:] py,\n                       any_float[:] pdx,\n                       any_float[:] pdy,\n                       any_float[:] data,\n                       bounds,\n                       int antialias = 1,\n                       period = None,\n                       int check_period = 1,\n                       np.float64_t line_width = 0.0,\n                       *,\n                       int return_mask = 0,\n):\n    cdef np.float64_t x_min, x_max, y_min, y_max\n    cdef np.float64_t period_x = 0.0, period_y = 0.0\n    cdef np.float64_t width, height, px_dx, px_dy, ipx_dx, ipx_dy\n    cdef np.float64_t ld_x, ld_y, cx, cy\n    cdef int i, j, p, xi, yi\n    cdef int lc, lr, rc, rr\n    cdef np.float64_t lypx, rypx, lxpx, rxpx, overlap1, overlap2\n    # These are the temp vars we get from the arrays\n    cdef np.float64_t oxsp, oysp, xsp, ysp, dxsp, dysp, dsp\n    # Some periodicity helpers\n    cdef int xiter[2]\n    cdef int yiter[2]\n    cdef np.float64_t xiterv[2]\n    cdef np.float64_t yiterv[2]\n\n    cdef np.ndarray[np.uint8_t, ndim=2] mask_arr = np.zeros_like(buff, dtype=\"uint8\")\n    cdef np.uint8_t[:, :] mask = mask_arr\n\n    if period is not None:\n        period_x = period[0]\n        period_y = period[1]\n    x_min = bounds[0]\n    x_max = bounds[1]\n    y_min = bounds[2]\n    y_max = bounds[3]\n    width = x_max - x_min\n    height = y_max - y_min\n    px_dx = width / (<np.float64_t> buff.shape[1])\n    px_dy = height / (<np.float64_t> buff.shape[0])\n    ipx_dx = 1.0 / px_dx\n    ipx_dy = 1.0 / px_dy\n    if px.shape[0] != py.shape[0] or \\\n       px.shape[0] != pdx.shape[0] or \\\n       px.shape[0] != pdy.shape[0] or \\\n       px.shape[0] != data.shape[0]:\n        raise YTPixelizeError(\"Arrays are not of correct shape.\")\n    xiter[0] = yiter[0] = 0\n    xiterv[0] = yiterv[0] = 0.0\n    # Here's a basic outline of what we're going to do here.  The xiter and\n    # yiter variables govern whether or not we should check periodicity -- are\n    # we both close enough to the edge that it would be important *and* are we\n    # periodic?\n    #\n    # The other variables are all either pixel positions or data positions.\n    # Pixel positions will vary regularly from the left edge of the window to\n    # the right edge of the window; px_dx and px_dy are the dx (cell width, not\n    # half-width).  ipx_dx and ipx_dy are the inverse, for quick math.\n    #\n    # The values in xsp, dxsp, x_min and their y counterparts, are the\n    # data-space coordinates, and are related to the data fed in.  We make some\n    # modifications for periodicity.\n    #\n    # Inside the finest loop, we compute the \"left column\" (lc) and \"lower row\"\n    # (lr) and then iterate up to \"right column\" (rc) and \"uppeR row\" (rr),\n    # depositing into them the data value.  Overlap computes the relative\n    # overlap of a data value with a pixel.\n    #\n    # NOTE ON ROWS AND COLUMNS:\n    #\n    #   The way that images are plotting in matplotlib is somewhat different\n    #   from what most might expect.  The first axis of the array plotted is\n    #   what varies along the x axis.  So for instance, if you supply\n    #   origin='lower' and plot the results of an mgrid operation, at a fixed\n    #   'y' value you will see the results of that array held constant in the\n    #   first dimension.  Here is some example code:\n    #\n    #   import matplotlib.pyplot as plt\n    #   import numpy as np\n    #   x, y = np.mgrid[0:1:100j,0:1:100j]\n    #   plt.imshow(x, interpolation='nearest', origin='lower')\n    #   plt.imshow(y, interpolation='nearest', origin='lower')\n    #\n    #   The values in the image:\n    #       lower left:  arr[0,0]\n    #       lower right: arr[0,-1]\n    #       upper left:  arr[-1,0]\n    #       upper right: arr[-1,-1]\n    #\n    #   So what we want here is to fill an array such that we fill:\n    #       first axis : y_min .. y_max\n    #       second axis: x_min .. x_max\n    with nogil:\n        for p in range(px.shape[0]):\n            xiter[1] = yiter[1] = 999\n            xiterv[1] = yiterv[1] = 0.0\n            oxsp = px[p]\n            oysp = py[p]\n            dxsp = pdx[p]\n            dysp = pdy[p]\n            dsp = data[p]\n            if check_period == 1:\n                if (oxsp - dxsp < x_min):\n                    xiter[1] = +1\n                    xiterv[1] = period_x\n                elif (oxsp + dxsp > x_max):\n                    xiter[1] = -1\n                    xiterv[1] = -period_x\n                if (oysp - dysp < y_min):\n                    yiter[1] = +1\n                    yiterv[1] = period_y\n                elif (oysp + dysp > y_max):\n                    yiter[1] = -1\n                    yiterv[1] = -period_y\n            overlap1 = overlap2 = 1.0\n            for xi in range(2):\n                if xiter[xi] == 999: continue\n                xsp = oxsp + xiterv[xi]\n                if (xsp + dxsp < x_min) or (xsp - dxsp > x_max): continue\n                for yi in range(2):\n                    if yiter[yi] == 999: continue\n                    ysp = oysp + yiterv[yi]\n                    if (ysp + dysp < y_min) or (ysp - dysp > y_max): continue\n                    lc = <int> fmax(((xsp-dxsp-x_min)*ipx_dx),0)\n                    lr = <int> fmax(((ysp-dysp-y_min)*ipx_dy),0)\n                    # NOTE: This is a different way of doing it than in the C\n                    # routines.  In C, we were implicitly casting the\n                    # initialization to int, but *not* the conditional, which\n                    # was allowed an extra value:\n                    #     for(j=lc;j<rc;j++)\n                    # here, when assigning lc (double) to j (int) it got\n                    # truncated, but no similar truncation was done in the\n                    # comparison of j to rc (double).  So give ourselves a\n                    # bonus row and bonus column here.\n                    rc = <int> fmin(((xsp+dxsp-x_min)*ipx_dx + 1), buff.shape[1])\n                    rr = <int> fmin(((ysp+dysp-y_min)*ipx_dy + 1), buff.shape[0])\n                    # Note that we're iterating here over *y* in the i\n                    # direction.  See the note above about this.\n                    for i in range(lr, rr):\n                        lypx = px_dy * i + y_min\n                        rypx = px_dy * (i+1) + y_min\n                        if antialias == 1:\n                            overlap2 = ((fmin(rypx, ysp+dysp)\n                                       - fmax(lypx, (ysp-dysp)))*ipx_dy)\n                        if overlap2 < 0.0: continue\n                        for j in range(lc, rc):\n                            lxpx = px_dx * j + x_min\n                            rxpx = px_dx * (j+1) + x_min\n                            if line_width > 0:\n                                # Here, we figure out if we're within\n                                # line_width*px_dx of the cell edge\n                                # Midpoint of x:\n                                cx = (rxpx+lxpx)*0.5\n                                ld_x = fmin(fabs(cx - (xsp+dxsp)),\n                                            fabs(cx - (xsp-dxsp)))\n                                ld_x *= ipx_dx\n                                # Midpoint of y:\n                                cy = (rypx+lypx)*0.5\n                                ld_y = fmin(fabs(cy - (ysp+dysp)),\n                                            fabs(cy - (ysp-dysp)))\n                                ld_y *= ipx_dy\n                                if ld_x <= line_width or ld_y <= line_width:\n                                    buff[i,j] = 1.0\n                                    mask[i,j] = 1\n                            elif antialias == 1:\n                                overlap1 = ((fmin(rxpx, xsp+dxsp)\n                                           - fmax(lxpx, (xsp-dxsp)))*ipx_dx)\n                                if overlap1 < 0.0: continue\n                                # This next line is not commented out because\n                                # it's an oddity; we actually want to skip\n                                # depositing if the overlap is zero, and that's\n                                # how it used to work when we were more\n                                # conservative about the iteration indices.\n                                # This will reduce artifacts if we ever move to\n                                # compositing instead of replacing bitmaps.\n                                if overlap1 * overlap2 < 1.e-6: continue\n                                # make sure pixel value is not a NaN before incrementing it\n                                if buff[i,j] != buff[i,j]: buff[i,j] = 0.0\n                                buff[i,j] += (dsp * overlap1) * overlap2\n                                mask[i,j] = 1\n                            else:\n                                buff[i,j] = dsp\n                                mask[i,j] = 1\n\n    if return_mask:\n        return mask_arr.astype(\"bool\")\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef pixelize_cartesian_nodal(np.float64_t[:,:] buff,\n                             np.float64_t[:] px,\n                             np.float64_t[:] py,\n                             np.float64_t[:] pz,\n                             np.float64_t[:] pdx,\n                             np.float64_t[:] pdy,\n                             np.float64_t[:] pdz,\n                             np.float64_t[:, :] data,\n                             np.float64_t coord,\n                             bounds,\n                             int antialias = 1,\n                             period = None,\n                             int check_period = 1,\n                             *,\n                             int return_mask = 0,\n):\n    cdef np.float64_t x_min, x_max, y_min, y_max\n    cdef np.float64_t period_x = 0.0, period_y = 0.0\n    cdef np.float64_t width, height, px_dx, px_dy, ipx_dx, ipx_dy\n    cdef np.float64_t cx, cy, cz\n    cdef int i, j, p, xi, yi\n    cdef int lc, lr, rc, rr\n    cdef np.float64_t lypx, rypx, lxpx, rxpx, overlap1, overlap2\n    # These are the temp vars we get from the arrays\n    cdef np.float64_t oxsp, oysp, ozsp\n    cdef np.float64_t xsp, ysp, zsp\n    cdef np.float64_t dxsp, dysp, dzsp\n    # Some periodicity helpers\n    cdef int xiter[2]\n    cdef int yiter[2]\n    cdef int ii, jj, kk, ind\n    cdef np.float64_t xiterv[2]\n    cdef np.float64_t yiterv[2]\n    if period is not None:\n        period_x = period[0]\n        period_y = period[1]\n    x_min = bounds[0]\n    x_max = bounds[1]\n    y_min = bounds[2]\n    y_max = bounds[3]\n    width = x_max - x_min\n    height = y_max - y_min\n    px_dx = width / (<np.float64_t> buff.shape[1])\n    px_dy = height / (<np.float64_t> buff.shape[0])\n    ipx_dx = 1.0 / px_dx\n    ipx_dy = 1.0 / px_dy\n    if px.shape[0] != py.shape[0] or \\\n       px.shape[0] != pz.shape[0] or \\\n       px.shape[0] != pdx.shape[0] or \\\n       px.shape[0] != pdy.shape[0] or \\\n       px.shape[0] != pdz.shape[0] or \\\n       px.shape[0] != data.shape[0]:\n        raise YTPixelizeError(\"Arrays are not of correct shape.\")\n    xiter[0] = yiter[0] = 0\n    xiterv[0] = yiterv[0] = 0.0\n    # Here's a basic outline of what we're going to do here.  The xiter and\n    # yiter variables govern whether or not we should check periodicity -- are\n    # we both close enough to the edge that it would be important *and* are we\n    # periodic?\n    #\n    # The other variables are all either pixel positions or data positions.\n    # Pixel positions will vary regularly from the left edge of the window to\n    # the right edge of the window; px_dx and px_dy are the dx (cell width, not\n    # half-width).  ipx_dx and ipx_dy are the inverse, for quick math.\n    #\n    # The values in xsp, dxsp, x_min and their y counterparts, are the\n    # data-space coordinates, and are related to the data fed in.  We make some\n    # modifications for periodicity.\n    #\n    # Inside the finest loop, we compute the \"left column\" (lc) and \"lower row\"\n    # (lr) and then iterate up to \"right column\" (rc) and \"uppeR row\" (rr),\n    # depositing into them the data value.  Overlap computes the relative\n    # overlap of a data value with a pixel.\n    #\n    # NOTE ON ROWS AND COLUMNS:\n    #\n    #   The way that images are plotting in matplotlib is somewhat different\n    #   from what most might expect.  The first axis of the array plotted is\n    #   what varies along the x axis.  So for instance, if you supply\n    #   origin='lower' and plot the results of an mgrid operation, at a fixed\n    #   'y' value you will see the results of that array held constant in the\n    #   first dimension.  Here is some example code:\n    #\n    #   import matplotlib.pyplot as plt\n    #   import numpy as np\n    #   x, y = np.mgrid[0:1:100j,0:1:100j]\n    #   plt.imshow(x, interpolation='nearest', origin='lower')\n    #   plt.imshow(y, interpolation='nearest', origin='lower')\n    #\n    #   The values in the image:\n    #       lower left:  arr[0,0]\n    #       lower right: arr[0,-1]\n    #       upper left:  arr[-1,0]\n    #       upper right: arr[-1,-1]\n    #\n    #   So what we want here is to fill an array such that we fill:\n    #       first axis : y_min .. y_max\n    #       second axis: x_min .. x_max\n\n    cdef np.ndarray[np.uint8_t, ndim=2] mask_arr = np.zeros_like(buff, dtype=\"uint8\")\n    cdef np.uint8_t[:, :] mask = mask_arr\n\n    with nogil:\n        for p in range(px.shape[0]):\n            xiter[1] = yiter[1] = 999\n            xiterv[1] = yiterv[1] = 0.0\n            oxsp = px[p]\n            oysp = py[p]\n            ozsp = pz[p]\n            dxsp = pdx[p]\n            dysp = pdy[p]\n            dzsp = pdz[p]\n            if check_period == 1:\n                if (oxsp - dxsp < x_min):\n                    xiter[1] = +1\n                    xiterv[1] = period_x\n                elif (oxsp + dxsp > x_max):\n                    xiter[1] = -1\n                    xiterv[1] = -period_x\n                if (oysp - dysp < y_min):\n                    yiter[1] = +1\n                    yiterv[1] = period_y\n                elif (oysp + dysp > y_max):\n                    yiter[1] = -1\n                    yiterv[1] = -period_y\n            overlap1 = overlap2 = 1.0\n            zsp = ozsp\n            for xi in range(2):\n                if xiter[xi] == 999: continue\n                xsp = oxsp + xiterv[xi]\n                if (xsp + dxsp < x_min) or (xsp - dxsp > x_max): continue\n                for yi in range(2):\n                    if yiter[yi] == 999: continue\n                    ysp = oysp + yiterv[yi]\n                    if (ysp + dysp < y_min) or (ysp - dysp > y_max): continue\n                    lc = <int> fmax(((xsp-dxsp-x_min)*ipx_dx),0)\n                    lr = <int> fmax(((ysp-dysp-y_min)*ipx_dy),0)\n                    # NOTE: This is a different way of doing it than in the C\n                    # routines.  In C, we were implicitly casting the\n                    # initialization to int, but *not* the conditional, which\n                    # was allowed an extra value:\n                    #     for(j=lc;j<rc;j++)\n                    # here, when assigning lc (double) to j (int) it got\n                    # truncated, but no similar truncation was done in the\n                    # comparison of j to rc (double).  So give ourselves a\n                    # bonus row and bonus column here.\n                    rc = <int> fmin(((xsp+dxsp-x_min)*ipx_dx + 1), buff.shape[1])\n                    rr = <int> fmin(((ysp+dysp-y_min)*ipx_dy + 1), buff.shape[0])\n                    # Note that we're iterating here over *y* in the i\n                    # direction.  See the note above about this.\n                    for i in range(lr, rr):\n                        lypx = px_dy * i + y_min\n                        rypx = px_dy * (i+1) + y_min\n                        for j in range(lc, rc):\n                            lxpx = px_dx * j + x_min\n                            rxpx = px_dx * (j+1) + x_min\n\n                            cx = (rxpx+lxpx)*0.5\n                            cy = (rypx+lypx)*0.5\n                            cz = coord\n\n                            ii = <int> (cx - xsp + dxsp)\n                            jj = <int> (cy - ysp + dysp)\n                            kk = <int> (cz - zsp + dzsp)\n\n                            ind = 4*ii + 2*jj + kk\n\n                            buff[i,j] = data[p, ind]\n                            mask[i,j] = 1\n\n    if return_mask:\n        return mask_arr.astype(\"bool\")\n\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef pixelize_off_axis_cartesian(\n                       np.float64_t[:,:] buff,\n                       np.float64_t[:] x,\n                       np.float64_t[:] y,\n                       np.float64_t[:] z,\n                       np.float64_t[:] px,\n                       np.float64_t[:] py,\n                       np.float64_t[:] pdx,\n                       np.float64_t[:] pdy,\n                       np.float64_t[:] pdz,\n                       np.float64_t[:] center,\n                       np.float64_t[:,:] inv_mat,\n                       np.int64_t[:] indices,\n                       np.float64_t[:] data,\n                       bounds,\n                       *,\n                       int return_mask=0,\n):\n    cdef np.float64_t x_min, x_max, y_min, y_max\n    cdef np.float64_t width, height, px_dx, px_dy, ipx_dx, ipx_dy, md\n    cdef int i, j, p, ip\n    cdef int lc, lr, rc, rr\n    # These are the temp vars we get from the arrays\n    cdef np.float64_t xsp, ysp, zsp, dxsp, dysp, dzsp, dsp\n    cdef np.float64_t pxsp, pysp, cxpx, cypx, cx, cy, cz\n    # Some periodicity helpers\n    cdef np.ndarray[np.int64_t, ndim=2] mask\n    x_min = bounds[0]\n    x_max = bounds[1]\n    y_min = bounds[2]\n    y_max = bounds[3]\n    width = x_max - x_min\n    height = y_max - y_min\n    px_dx = width / (<np.float64_t> buff.shape[1])\n    px_dy = height / (<np.float64_t> buff.shape[0])\n    ipx_dx = 1.0 / px_dx\n    ipx_dy = 1.0 / px_dy\n    if px.shape[0] != py.shape[0] or \\\n       px.shape[0] != pdx.shape[0] or \\\n       px.shape[0] != pdy.shape[0] or \\\n       px.shape[0] != pdz.shape[0] or \\\n       px.shape[0] != indices.shape[0] or \\\n       px.shape[0] != data.shape[0]:\n        raise YTPixelizeError(\"Arrays are not of correct shape.\")\n    mask = np.zeros((buff.shape[0], buff.shape[1]), \"int64\")\n    with nogil:\n        for ip in range(indices.shape[0]):\n            p = indices[ip]\n            xsp = x[p]\n            ysp = y[p]\n            zsp = z[p]\n            pxsp = px[p]\n            pysp = py[p]\n            dxsp = pdx[p]\n            dysp = pdy[p]\n            dzsp = pdz[p]\n            dsp = data[p]\n            # Any point we want to plot is at most this far from the center\n            md = 2.0 * math.sqrt(dxsp*dxsp + dysp*dysp + dzsp*dzsp)\n            if pxsp + md < x_min or \\\n               pxsp - md > x_max or \\\n               pysp + md < y_min or \\\n               pysp - md > y_max:\n                continue\n            lc = <int> fmax(((pxsp - md - x_min)*ipx_dx),0)\n            lr = <int> fmax(((pysp - md - y_min)*ipx_dy),0)\n            rc = <int> fmin(((pxsp + md - x_min)*ipx_dx + 1), buff.shape[1])\n            rr = <int> fmin(((pysp + md - y_min)*ipx_dy + 1), buff.shape[0])\n            for i in range(lr, rr):\n                cypx = px_dy * (i + 0.5) + y_min\n                for j in range(lc, rc):\n                    cxpx = px_dx * (j + 0.5) + x_min\n                    cx = inv_mat[0,0]*cxpx + inv_mat[0,1]*cypx + center[0]\n                    cy = inv_mat[1,0]*cxpx + inv_mat[1,1]*cypx + center[1]\n                    cz = inv_mat[2,0]*cxpx + inv_mat[2,1]*cypx + center[2]\n                    if fabs(xsp - cx) * 0.99 > dxsp or \\\n                       fabs(ysp - cy) * 0.99 > dysp or \\\n                       fabs(zsp - cz) * 0.99 > dzsp:\n                        continue\n                    mask[i, j] += 1\n                    # make sure pixel value is not a NaN before incrementing it\n                    if buff[i,j] != buff[i,j]: buff[i,j] = 0.0\n                    buff[i, j] += dsp\n    for i in range(buff.shape[0]):\n        for j in range(buff.shape[1]):\n            if mask[i,j] == 0: continue\n            buff[i,j] /= mask[i,j]\n\n    if return_mask:\n        return mask!=0\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef pixelize_cylinder(np.float64_t[:,:] buff,\n                      np.float64_t[:] radius,\n                      np.float64_t[:] dradius,\n                      np.float64_t[:] theta,\n                      np.float64_t[:] dtheta,\n                      np.float64_t[:] field,\n                      extents,\n                      *,\n                      int return_mask=0,\n):\n\n    cdef np.float64_t x, y, dx, dy, r0, theta0\n    cdef np.float64_t rmin, rmax, tmin, tmax, x0, y0, x1, y1, xp, yp\n    cdef np.float64_t r_i, theta_i, dr_i, dtheta_i\n    cdef np.float64_t r_inc, theta_inc\n    cdef np.float64_t costheta, sintheta\n    cdef int i, i1, pi, pj\n    cdef np.float64_t twoPI = 2 * NPY_PI\n\n    cdef int imin, imax\n    imin = np.asarray(radius).argmin()\n    imax = np.asarray(radius).argmax()\n    rmin = radius[imin] - dradius[imin]\n    rmax = radius[imax] + dradius[imax]\n\n    imin = np.asarray(theta).argmin()\n    imax = np.asarray(theta).argmax()\n    tmin = theta[imin] - dtheta[imin]\n    tmax = theta[imax] + dtheta[imax]\n    cdef np.ndarray[np.uint8_t, ndim=2] mask_arr = np.zeros_like(buff, dtype=\"uint8\")\n    cdef np.uint8_t[:, :] mask = mask_arr\n\n    x0, x1, y0, y1 = extents\n    dx = (x1 - x0) / buff.shape[0]\n    dy = (y1 - y0) / buff.shape[1]\n    cdef np.float64_t rbounds[2]\n    cdef np.float64_t prbounds[2]\n    cdef np.float64_t ptbounds[2]\n    cdef np.float64_t corners[8]\n    # Find our min and max r\n    corners[0] = x0*x0+y0*y0\n    corners[1] = x1*x1+y0*y0\n    corners[2] = x0*x0+y1*y1\n    corners[3] = x1*x1+y1*y1\n    corners[4] = x0*x0\n    corners[5] = x1*x1\n    corners[6] = y0*y0\n    corners[7] = y1*y1\n    rbounds[0] = rbounds[1] = corners[0]\n    for i in range(8):\n        rbounds[0] = fmin(rbounds[0], corners[i])\n        rbounds[1] = fmax(rbounds[1], corners[i])\n    rbounds[0] = math.sqrt(rbounds[0])\n    rbounds[1] = math.sqrt(rbounds[1])\n    # If we include the origin in either direction, we need to have radius of\n    # zero as our lower bound.\n    if x0 < 0 and x1 > 0:\n        rbounds[0] = 0.0\n    if y0 < 0 and y1 > 0:\n        rbounds[0] = 0.0\n    r_inc = 0.5 * fmin(dx, dy)\n\n    with nogil:\n        for i in range(radius.shape[0]):\n            r0 = radius[i]\n            theta0 = theta[i]\n            dr_i = dradius[i]\n            dtheta_i = dtheta[i]\n            # Skip out early if we're offsides, for zoomed in plots\n            if r0 + dr_i < rbounds[0] or r0 - dr_i > rbounds[1]:\n                continue\n            theta_i = theta0 - dtheta_i\n            theta_inc = r_inc / (r0 + dr_i)\n\n            while theta_i < theta0 + dtheta_i:\n                r_i = r0 - dr_i\n                costheta = math.cos(theta_i)\n                sintheta = math.sin(theta_i)\n                while r_i < r0 + dr_i:\n                    if rmax <= r_i:\n                        r_i += r_inc\n                        continue\n                    y = r_i * costheta\n                    x = r_i * sintheta\n                    pi = <int>((x - x0)/dx)\n                    pj = <int>((y - y0)/dy)\n                    if pi >= 0 and pi < buff.shape[0] and \\\n                       pj >= 0 and pj < buff.shape[1]:\n                        # we got a pixel that intersects the grid cell\n                        # now check that this pixel doesn't go beyond the data domain\n                        xp = x0 + pi*dx\n                        yp = y0 + pj*dy\n                        corners[0] = xp*xp + yp*yp\n                        corners[1] = xp*xp + (yp+dy)**2\n                        corners[2] = (xp+dx)**2 + yp*yp\n                        corners[3] = (xp+dx)**2 + (yp+dy)**2\n                        prbounds[0] = prbounds[1] = corners[3]\n                        for i1 in range(3):\n                            prbounds[0] = fmin(prbounds[0], corners[i1])\n                            prbounds[1] = fmax(prbounds[1], corners[i1])\n                        prbounds[0] = math.sqrt(prbounds[0])\n                        prbounds[1] = math.sqrt(prbounds[1])\n\n                        corners[0] = math.atan2(xp, yp)\n                        corners[1] = math.atan2(xp, yp+dy)\n                        corners[2] = math.atan2(xp+dx, yp)\n                        corners[3] = math.atan2(xp+dx, yp+dy)\n                        ptbounds[0] = ptbounds[1] = corners[3]\n                        for i1 in range(3):\n                            ptbounds[0] = fmin(ptbounds[0], corners[i1])\n                            ptbounds[1] = fmax(ptbounds[1], corners[i1])\n\n                        # shift to a [0, 2*PI] interval\n                        # note: with fmod, the sign of the returned value\n                        # matches the sign of the first argument, so need\n                        # to offset by 2pi to ensure a positive result in [0, 2pi]\n                        ptbounds[0] = math.fmod(ptbounds[0]+twoPI, twoPI)\n                        ptbounds[1] = math.fmod(ptbounds[1]+twoPI, twoPI)\n\n                        if prbounds[0] >= rmin and prbounds[1] <= rmax and \\\n                           ptbounds[0] >= tmin and ptbounds[1] <= tmax:\n                            buff[pi, pj] = field[i]\n                            mask[pi, pj] = 1\n                    r_i += r_inc\n                theta_i += theta_inc\n\n    if return_mask:\n        return mask_arr.astype(\"bool\")\n\ncdef int aitoff_Lambda_btheta_to_xy(np.float64_t Lambda, np.float64_t btheta,\n                               np.float64_t *x, np.float64_t *y) except -1:\n    cdef np.float64_t z = math.sqrt(1 + math.cos(btheta) * math.cos(Lambda / 2.0))\n    x[0] = 2.0 * math.cos(btheta) * math.sin(Lambda / 2.0) / z\n    y[0] = math.sin(btheta) / z\n    return 0\n\n@cython.cdivision(True)\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef pixelize_aitoff(np.float64_t[:] azimuth,\n                    np.float64_t[:] dazimuth,\n                    np.float64_t[:] colatitude,\n                    np.float64_t[:] dcolatitude,\n                    buff_size,\n                    np.float64_t[:] field,\n                    bounds, # this is a 4-tuple\n                    input_img = None,\n                    np.float64_t azimuth_offset = 0.0,\n                    np.float64_t colatitude_offset = 0.0,\n                    *,\n                    int return_mask = 0\n):\n    # http://paulbourke.net/geometry/transformationprojection/\n    # (Lambda) longitude is -PI to PI (longitude = azimuth - PI)\n    # (btheta) latitude is -PI/2 to PI/2 (latitude = PI/2 - colatitude)\n    #\n    # z^2 = 1 + cos(latitude) cos(longitude/2)\n    # x = cos(latitude) sin(longitude/2) / z\n    # y = sin(latitude) / z\n    cdef np.ndarray[np.float64_t, ndim=2] img\n    cdef int i, j, nf, fi\n    cdef np.float64_t x, y, z, zb\n    cdef np.float64_t dx, dy, xw, yw\n    cdef np.float64_t Lambda0, btheta0, Lambda_p, dLambda_p, btheta_p, dbtheta_p\n    cdef np.float64_t PI = np.pi\n    cdef np.float64_t s2 = math.sqrt(2.0)\n    cdef np.float64_t xmax, ymax, xmin, ymin\n    nf = field.shape[0]\n\n    if input_img is None:\n        img = np.zeros((buff_size[0], buff_size[1]))\n        img[:] = np.nan\n    else:\n        img = input_img\n\n    cdef np.ndarray[np.uint8_t, ndim=2] mask_arr = np.ones_like(img, dtype=\"uint8\")\n    cdef np.uint8_t[:, :] mask = mask_arr\n\n    # Okay, here's our strategy.  We compute the bounds in x and y, which will\n    # be a rectangle, and then for each x, y position we check to see if it's\n    # within our Lambda.  This will cost *more* computations of the\n    # (x,y)->(Lambda,btheta) calculation, but because we no longer have to search\n    # through the Lambda, btheta arrays, it should be faster.\n    xw = bounds[1] - bounds[0]\n    yw = bounds[3] - bounds[2]\n    dx = xw / (img.shape[0] - 1)\n    dy = yw / (img.shape[1] - 1)\n    x = y = 0\n    for fi in range(nf):\n        Lambda_p = (azimuth[fi] + azimuth_offset) - PI\n        dLambda_p = dazimuth[fi]\n        btheta_p = PI/2.0 - (colatitude[fi] + colatitude_offset)\n        dbtheta_p = dcolatitude[fi]\n        # Four transformations\n        aitoff_Lambda_btheta_to_xy(Lambda_p - dLambda_p, btheta_p - dbtheta_p, &x, &y)\n        xmin = x\n        xmax = x\n        ymin = y\n        ymax = y\n        aitoff_Lambda_btheta_to_xy(Lambda_p - dLambda_p, btheta_p + dbtheta_p, &x, &y)\n        xmin = fmin(xmin, x)\n        xmax = fmax(xmax, x)\n        ymin = fmin(ymin, y)\n        ymax = fmax(ymax, y)\n        aitoff_Lambda_btheta_to_xy(Lambda_p + dLambda_p, btheta_p - dbtheta_p, &x, &y)\n        xmin = fmin(xmin, x)\n        xmax = fmax(xmax, x)\n        ymin = fmin(ymin, y)\n        ymax = fmax(ymax, y)\n        aitoff_Lambda_btheta_to_xy(Lambda_p + dLambda_p, btheta_p + dbtheta_p, &x, &y)\n        xmin = fmin(xmin, x)\n        xmax = fmax(xmax, x)\n        ymin = fmin(ymin, y)\n        ymax = fmax(ymax, y)\n        # special cases where the projection of the cell isn't\n        # bounded by the rectangle (in image space) that bounds its corners.\n        # Note that performance may take a serious hit here. The overarching algorithm\n        # is optimized for cells with small angular width.\n        if xmin * xmax < 0.0:\n            # on the central meridian\n            aitoff_Lambda_btheta_to_xy(0.0, btheta_p - dbtheta_p, &x, &y)\n            ymin = fmin(ymin, y)\n            ymax = fmax(ymax, y)\n            aitoff_Lambda_btheta_to_xy(0.0, btheta_p + dbtheta_p, &x, &y)\n            ymin = fmin(ymin, y)\n            ymax = fmax(ymax, y)\n        if ymin * ymax < 0.0:\n            # on the equator\n            aitoff_Lambda_btheta_to_xy(Lambda_p - dLambda_p, 0.0, &x, &y)\n            xmin = fmin(xmin, x)\n            xmax = fmax(xmax, x)\n            aitoff_Lambda_btheta_to_xy(Lambda_p + dLambda_p, 0.0, &x, &y)\n            xmin = fmin(xmin, x)\n            xmax = fmax(xmax, x)\n        # Now we have the (projected rectangular) bounds.\n\n        # Shift into normalized image coords\n        xmin = (xmin - bounds[0])\n        xmax = (xmax - bounds[0])\n        ymin = (ymin - bounds[2])\n        ymax = (ymax - bounds[2])\n\n        # Finally, select a rectangular region in image space\n        # that fully contains the projected data point.\n        # We'll reject image pixels in that rectangle that are\n        # not actually intersecting the data point as we go.\n        x0 = <int> (xmin / dx)\n        x1 = <int> (xmax / dx) + 1\n        y0 = <int> (ymin / dy)\n        y1 = <int> (ymax / dy) + 1\n        for i in range(x0, x1):\n            x = (bounds[0] + i * dx) / 2.0\n            for j in range(y0, y1):\n                y = (bounds[2] + j * dy)\n                zb = (x*x + y*y - 1.0)\n                if zb > 0: continue\n                z = (1.0 - 0.5*x*x - 0.5*y*y)\n                z = math.sqrt(z)\n                # Longitude\n                Lambda0 = 2.0*math.atan(z*x*s2/(2.0*z*z-1.0))\n                # Latitude\n                # We shift it into co-latitude\n                btheta0 = math.asin(z*y*s2)\n                # Now we just need to figure out which pixel contributes.\n                # We do not have a fast search.\n                if not (Lambda_p - dLambda_p <= Lambda0 <= Lambda_p + dLambda_p):\n                    continue\n                if not (btheta_p - dbtheta_p <= btheta0 <= btheta_p + dbtheta_p):\n                    continue\n                img[i, j] = field[fi]\n                mask[i, j] = 1\n\n    if return_mask:\n        return img, mask_arr.astype(\"bool\")\n    else:\n        return img\n\n\n# This function accepts a set of vertices (for a polyhedron) that are\n# assumed to be in order for bottom, then top, in the same clockwise or\n# counterclockwise direction (i.e., like points 1-8 in Figure 4 of the ExodusII\n# manual).  It will then either *match* or *fill* the results.  If it is\n# matching, it will early terminate with a 0 or final-terminate with a 1 if the\n# results match.  Otherwise, it will fill the signs with -1's and 1's to show\n# the sign of the dot product of the point with the cross product of the face.\ncdef int check_face_dot(int nvertices,\n                        np.float64_t point[3],\n                        np.float64_t **vertices,\n                        np.int8_t *signs,\n                        int match):\n    # Because of how we are doing this, we do not *care* what the signs are or\n    # how the faces are ordered, we only care if they match between the point\n    # and the centroid.\n    # So, let's compute these vectors.  See above where these are written out\n    # for ease of use.\n    cdef np.float64_t vec1[3]\n    cdef np.float64_t vec2[3]\n    cdef np.float64_t cp_vec[3]\n    cdef np.float64_t npoint[3]\n    cdef np.float64_t dp\n    cdef np.uint8_t faces[MAX_NUM_FACES][2][2]\n    cdef np.uint8_t nf\n    if nvertices == 4:\n        faces = tetra_face_defs\n        nf = TETRA_NF\n    elif nvertices == 6:\n        faces = wedge_face_defs\n        nf = WEDGE_NF\n    elif nvertices == 8:\n        faces = hex_face_defs\n        nf = HEX_NF\n    else:\n        return -1\n    cdef int n, vi1a, vi1b, vi2a, vi2b\n\n    for n in range(nf):\n        vi1a = faces[n][0][0]\n        vi1b = faces[n][0][1]\n        vi2a = faces[n][1][0]\n        vi2b = faces[n][1][1]\n        # Shared vertex is vi1a and vi2a\n        subtract(vertices[vi1b], vertices[vi1a], vec1)\n        subtract(vertices[vi2b], vertices[vi2a], vec2)\n        subtract(point, vertices[vi1b], npoint)\n        cross(vec1, vec2, cp_vec)\n        dp = dot(cp_vec, npoint)\n        if match == 0:\n            if dp < 0:\n                signs[n] = -1\n            else:\n                signs[n] = 1\n        else:\n            if dp <= 0 and signs[n] < 0:\n                continue\n            elif dp >= 0 and signs[n] > 0:\n                continue\n            else: # mismatch!\n                return 0\n    return 1\n\n\ndef pixelize_element_mesh(np.ndarray[np.float64_t, ndim=2] coords,\n                          np.ndarray[np.int64_t, ndim=2] conn,\n                          buff_size,\n                          np.ndarray[np.float64_t, ndim=2] field,\n                          extents,\n                          int index_offset = 0,\n                          *,\n                          return_mask=False,\n):\n    cdef np.ndarray[np.float64_t, ndim=3] img\n    img = np.zeros(buff_size, dtype=\"float64\")\n    img[:] = np.nan\n\n    cdef np.ndarray[np.uint8_t, ndim=3] mask_arr = np.ones_like(img, dtype=\"uint8\")\n    cdef np.uint8_t[:, :, :] mask = mask_arr\n\n    # Two steps:\n    #  1. Is image point within the mesh bounding box?\n    #  2. Is image point within the mesh element?\n    # Second is more intensive.  It will convert the element vertices to the\n    # mapped coordinate system, and check whether the result in in-bounds or not\n    # Note that we have to have a pseudo-3D pixel buffer.  One dimension will\n    # always be 1.\n    cdef np.float64_t pLE[3]\n    cdef np.float64_t pRE[3]\n    cdef np.float64_t LE[3]\n    cdef np.float64_t RE[3]\n    cdef int use\n    cdef np.int64_t n, i, pi, pj, pk, ci, cj\n    cdef np.int64_t pstart[3]\n    cdef np.int64_t pend[3]\n    cdef np.float64_t ppoint[3]\n    cdef np.float64_t idds[3]\n    cdef np.float64_t dds[3]\n    cdef np.float64_t *vertices\n    cdef np.float64_t *field_vals\n    cdef int nvertices = conn.shape[1]\n    cdef int ndim = coords.shape[1]\n    cdef int num_field_vals = field.shape[1]\n    cdef double[4] mapped_coord\n    cdef ElementSampler sampler\n\n    # Pick the right sampler and allocate storage for the mapped coordinate\n    if ndim == 3 and nvertices == 4:\n        sampler = P1Sampler3D()\n    elif ndim == 3 and nvertices == 6:\n        sampler = W1Sampler3D()\n    elif ndim == 3 and nvertices == 8:\n        sampler = Q1Sampler3D()\n    elif ndim == 3 and nvertices == 20:\n        sampler = S2Sampler3D()\n    elif ndim == 2 and nvertices == 3:\n        sampler = P1Sampler2D()\n    elif ndim == 1 and nvertices == 2:\n        sampler = P1Sampler1D()\n    elif ndim == 2 and nvertices == 4:\n        sampler = Q1Sampler2D()\n    elif ndim == 2 and nvertices == 9:\n        sampler = Q2Sampler2D()\n    elif ndim == 2 and nvertices == 6:\n        sampler = T2Sampler2D()\n    elif ndim == 3 and nvertices == 10:\n        sampler = Tet2Sampler3D()\n    else:\n        raise YTElementTypeNotRecognized(ndim, nvertices)\n\n    # if we are in 2D land, the 1 cell thick dimension had better be 'z'\n    if ndim == 2:\n        if buff_size[2] != 1:\n            raise RuntimeError(\"Slices of 2D datasets must be \"\n                               \"perpendicular to the 'z' direction.\")\n\n    # allocate temporary storage\n    vertices = <np.float64_t *> malloc(ndim * sizeof(np.float64_t) * nvertices)\n    field_vals = <np.float64_t *> malloc(sizeof(np.float64_t) * num_field_vals)\n\n    # fill the image bounds and pixel size information here\n    for i in range(ndim):\n        pLE[i] = extents[i][0]\n        pRE[i] = extents[i][1]\n        dds[i] = (pRE[i] - pLE[i])/buff_size[i]\n        if dds[i] == 0.0:\n            idds[i] = 0.0\n        else:\n            idds[i] = 1.0 / dds[i]\n\n    with cython.boundscheck(False):\n        for ci in range(conn.shape[0]):\n\n            # Fill the vertices\n            LE[0] = LE[1] = LE[2] = 1e60\n            RE[0] = RE[1] = RE[2] = -1e60\n\n            for n in range(num_field_vals):\n                field_vals[n] = field[ci, n]\n\n            for n in range(nvertices):\n                cj = conn[ci, n] - index_offset\n                for i in range(ndim):\n                    vertices[ndim*n + i] = coords[cj, i]\n                    LE[i] = fmin(LE[i], vertices[ndim*n+i])\n                    RE[i] = fmax(RE[i], vertices[ndim*n+i])\n\n            use = 1\n            for i in range(ndim):\n                if RE[i] < pLE[i] or LE[i] >= pRE[i]:\n                    use = 0\n                    break\n                pstart[i] = i64max(<np.int64_t> ((LE[i] - pLE[i])*idds[i]) - 1, 0)\n                pend[i] = i64min(<np.int64_t> ((RE[i] - pLE[i])*idds[i]) + 1, img.shape[i]-1)\n\n            # override for the low-dimensional case\n            if ndim < 3:\n                pstart[2] = 0\n                pend[2] = 0\n            if ndim < 2:\n                pstart[1] = 0\n                pend[1] = 0\n\n            if use == 0:\n                continue\n\n            # Now our bounding box intersects, so we get the extents of our pixel\n            # region which overlaps with the bounding box, and we'll check each\n            # pixel in there.\n            for pi in range(pstart[0], pend[0] + 1):\n                ppoint[0] = (pi + 0.5) * dds[0] + pLE[0]\n                for pj in range(pstart[1], pend[1] + 1):\n                    ppoint[1] = (pj + 0.5) * dds[1] + pLE[1]\n                    for pk in range(pstart[2], pend[2] + 1):\n                        ppoint[2] = (pk + 0.5) * dds[2] + pLE[2]\n                        # Now we just need to figure out if our ppoint is within\n                        # our set of vertices.\n                        sampler.map_real_to_unit(mapped_coord, vertices, ppoint)\n                        if not sampler.check_inside(mapped_coord):\n                            continue\n                        if (num_field_vals == 1):\n                            img[pi, pj, pk] = field_vals[0]\n                        else:\n                            img[pi, pj, pk] = sampler.sample_at_unit_point(mapped_coord,\n                                                                           field_vals)\n                        mask[pi, pj, pk] = 1\n    free(vertices)\n    free(field_vals)\n    if return_mask:\n        return img, mask_arr.astype(\"bool\")\n    else:\n        return img\n\n# used as a cache to avoid repeatedly creating\n# instances of SPHKernelInterpolationTable\nkernel_tables = {}\n\ncdef class SPHKernelInterpolationTable:\n    cdef public object kernel_name\n    cdef kernel_func kernel\n    cdef np.float64_t[::1] table\n    cdef np.float64_t[::1] q2_vals\n    cdef np.float64_t q2_range, iq2_range\n\n    def __init__(self, kernel_name):\n        self.kernel_name = kernel_name\n        self.kernel = get_kernel_func(kernel_name)\n        self.populate_table()\n\n    @cython.initializedcheck(False)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef np.float64_t integrate_q2(self, np.float64_t q2) noexcept nogil:\n        # See equation 30 of the SPLASH paper\n        cdef int i\n        # Our bounds are -sqrt(R*R - q2) and sqrt(R*R-q2)\n        # And our R is always 1; note that our smoothing kernel functions\n        # expect it to run from 0 .. 1, so we multiply the integrand by 2\n        cdef int N = 200\n        cdef np.float64_t qz\n        cdef np.float64_t R = 1\n        cdef np.float64_t R0 = -math.sqrt(R*R-q2)\n        cdef np.float64_t R1 = math.sqrt(R*R-q2)\n        cdef np.float64_t dR = (R1-R0)/N\n        # Set to our bounds\n        cdef np.float64_t integral = 0.0\n        integral += self.kernel(math.sqrt(R0*R0 + q2))\n        integral += self.kernel(math.sqrt(R1*R1 + q2))\n        # We're going to manually conduct a trapezoidal integration\n        for i in range(1, N):\n            qz = R0 + i * dR\n            integral += 2.0*self.kernel(math.sqrt(qz*qz + q2))\n        integral *= (R1-R0)/(2*N)\n        return integral\n\n    def populate_table(self):\n        cdef int i\n        self.table = cvarray(format=\"d\", shape=(TABLE_NVALS,),\n                             itemsize=sizeof(np.float64_t))\n        self.q2_vals = cvarray(format=\"d\", shape=(TABLE_NVALS,),\n                             itemsize=sizeof(np.float64_t))\n        # We run from 0 to 1 here over R\n        for i in range(TABLE_NVALS):\n            self.q2_vals[i] = i * 1.0/(TABLE_NVALS-1)\n            self.table[i] = self.integrate_q2(self.q2_vals[i])\n\n        self.q2_range = self.q2_vals[TABLE_NVALS-1] - self.q2_vals[0]\n        self.iq2_range = (TABLE_NVALS-1)/self.q2_range\n\n    @cython.initializedcheck(False)\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef inline np.float64_t interpolate(self, np.float64_t q2) noexcept nogil:\n        cdef int index\n        cdef np.float64_t F_interpolate\n        index = <int>((q2 - self.q2_vals[0])*(self.iq2_range))\n        if index >= TABLE_NVALS:\n            return 0.0\n        F_interpolate = self.table[index] + (\n                (self.table[index+1] - self.table[index])\n               *(q2 - self.q2_vals[index])*self.iq2_range)\n        return F_interpolate\n\n    def interpolate_array(self, np.float64_t[:] q2_vals):\n        cdef np.float64_t[:] ret = np.empty(q2_vals.shape[0])\n        cdef int i\n        for i in range(q2_vals.shape[0]):\n            ret[i] = self.interpolate(q2_vals[i])\n        return np.array(ret)\n\n@cython.initializedcheck(False)\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef pixelize_sph_kernel_projection(\n        np.float64_t[:, :] buff,\n        np.uint8_t[:, :] mask,\n        any_float[:] posx,\n        any_float[:] posy,\n        any_float[:] posz,\n        any_float[:] hsml,\n        any_float[:] pmass,\n        any_float[:] pdens,\n        any_float[:] quantity_to_smooth,\n        bounds,\n        kernel_name=\"cubic\",\n        weight_field=None,\n        _check_period = (1, 1, 1),\n        period=None):\n\n    cdef np.intp_t xsize, ysize\n    cdef np.float64_t x_min, x_max, y_min, y_max, z_min, z_max, prefactor_j\n    cdef np.int64_t xi, yi, x0, x1, y0, y1, xxi, yyi\n    cdef np.float64_t q_ij2, posx_diff, posy_diff, ih_j2\n    cdef np.float64_t x, y, dx, dy, idx, idy, h_j2, px, py, pz\n    cdef np.float64_t period_x = 0, period_y = 0, period_z = 0\n    cdef int i, j, ii, jj, kk\n    cdef np.float64_t[:] _weight_field\n    cdef int * xiter\n    cdef int * yiter\n    cdef int * ziter\n    cdef np.float64_t * xiterv\n    cdef np.float64_t * yiterv\n    cdef np.float64_t * ziterv\n    cdef np.int8_t[3] check_period\n\n    if weight_field is not None:\n        _weight_field = weight_field\n\n    if period is not None:\n        period_x = period[0]\n        period_y = period[1]\n        period_z = period[2]\n    for i in range(3):\n        check_period[i] = np.int8(_check_period[i])\n    # we find the x and y range over which we have pixels and we find how many\n    # pixels we have in each dimension\n    xsize, ysize = buff.shape[0], buff.shape[1]\n    x_min = bounds[0]\n    x_max = bounds[1]\n    y_min = bounds[2]\n    y_max = bounds[3]\n    z_min = bounds[4]\n    z_max = bounds[5]\n\n    dx = (x_max - x_min) / xsize\n    dy = (y_max - y_min) / ysize\n\n    idx = 1.0/dx\n    idy = 1.0/dy\n\n    if kernel_name not in kernel_tables:\n        kernel_tables[kernel_name] = SPHKernelInterpolationTable(kernel_name)\n    cdef SPHKernelInterpolationTable itab = kernel_tables[kernel_name]\n    with nogil, parallel():\n        # loop through every particle\n        # NOTE: this loop can be quite time consuming. However it is easily\n        # parallelizable in multiple ways, such as:\n        #   1) use multiple cores to process individual particles (the outer loop)\n        #   2) use multiple cores to process individual pixels for a given particle\n        #      (the inner loops)\n        # Depending on the ratio of particles' \"sphere of influence\" (a.k.a. the smoothing\n        # length) to the physical width of the pixels, different parallelization\n        # strategies may yield different speed-ups. Strategy #1 works better in the case\n        # of lots of itty bitty particles. Strategy #2 works well when we have a\n        # not-very-large-number of reasonably large-compared-to-pixels particles. We\n        # currently employ #1 as its workload is more even and consistent, even though it\n        # comes with a price of an additional, per thread memory for storing the\n        # intermediate results.\n\n        local_buff = <np.float64_t *> malloc(sizeof(np.float64_t) * xsize * ysize)\n        xiterv = <np.float64_t *> malloc(sizeof(np.float64_t) * 2)\n        yiterv = <np.float64_t *> malloc(sizeof(np.float64_t) * 2)\n        ziterv = <np.float64_t *> malloc(sizeof(np.float64_t) * 2)\n        xiter = <int *> malloc(sizeof(int) * 2)\n        yiter = <int *> malloc(sizeof(int) * 2)\n        ziter = <int *> malloc(sizeof(int) * 2)\n        xiter[0] = yiter[0] = ziter[0] = 0\n        xiterv[0] = yiterv[0] = ziterv[0] = 0.0\n        for i in range(xsize * ysize):\n            local_buff[i] = 0.0\n\n        for j in prange(0, posx.shape[0], schedule=\"dynamic\"):\n            if j % 100000 == 0:\n                with gil:\n                    PyErr_CheckSignals()\n\n            xiter[1] = yiter[1] = ziter[1] = 999\n\n            if check_period[0] == 1:\n                if posx[j] - hsml[j] < x_min:\n                    xiter[1] = +1\n                    xiterv[1] = period_x\n                elif posx[j] + hsml[j] > x_max:\n                    xiter[1] = -1\n                    xiterv[1] = -period_x\n            if check_period[1] == 1:\n                if posy[j] - hsml[j] < y_min:\n                    yiter[1] = +1\n                    yiterv[1] = period_y\n                elif posy[j] + hsml[j] > y_max:\n                    yiter[1] = -1\n                    yiterv[1] = -period_y\n            if check_period[2] == 1:\n                if posz[j] - hsml[j] < z_min:\n                    ziter[1] = +1\n                    ziterv[1] = period_z\n                elif posz[j] + hsml[j] > z_max:\n                    ziter[1] = -1\n                    ziterv[1] = -period_z\n\n            # we set the smoothing length squared with lower limit of the pixel\n            # Nope! that causes weird grid resolution dependences and increases\n            # total values when resolution elements have hsml < grid spacing\n            h_j2 = hsml[j]*hsml[j]\n            ih_j2 = 1.0/h_j2\n\n            prefactor_j = pmass[j] / pdens[j] / hsml[j]**2 * quantity_to_smooth[j]\n            if weight_field is not None:\n                prefactor_j *= _weight_field[j]\n\n            # Discussion point: do we want the hsml margin on the z direction?\n            # it's consistent with Ray and Region selections, I think,\n            # but does tend to 'tack on' stuff compared to the nominal depth\n            for kk in range(2):\n                # discard if z is outside bounds\n                if ziter[kk] == 999: continue\n                pz = posz[j] + ziterv[kk]\n                ## removed hsml 'margin' in the projection direction to avoid\n                ## double-counting particles near periodic edges\n                ## and adding extra 'depth' to projections\n                #if (pz + hsml[j] < z_min) or (pz  - hsml[j] > z_max): continue\n                if (pz < z_min) or (pz > z_max): continue\n\n                for ii in range(2):\n                    if xiter[ii] == 999: continue\n                    px = posx[j] + xiterv[ii]\n                    if (px + hsml[j] < x_min) or (px - hsml[j] > x_max): continue\n                    for jj in range(2):\n                        if yiter[jj] == 999: continue\n                        py = posy[j] + yiterv[jj]\n                        if (py + hsml[j] < y_min) or (py - hsml[j] > y_max): continue\n\n                        # here we find the pixels which this particle contributes to\n                        x0 = <np.int64_t> ((px - hsml[j] - x_min)*idx)\n                        x1 = <np.int64_t> ((px + hsml[j] - x_min)*idx)\n                        x0 = iclip(x0-1, 0, xsize)\n                        x1 = iclip(x1+1, 0, xsize)\n\n                        y0 = <np.int64_t> ((py - hsml[j] - y_min)*idy)\n                        y1 = <np.int64_t> ((py + hsml[j] - y_min)*idy)\n                        y0 = iclip(y0-1, 0, ysize)\n                        y1 = iclip(y1+1, 0, ysize)\n\n                        # found pixels we deposit on, loop through those pixels\n                        for xi in range(x0, x1):\n                            # we use the centre of the pixel to calculate contribution\n                            x = (xi + 0.5) * dx + x_min\n\n                            posx_diff = px - x\n                            posx_diff = posx_diff * posx_diff\n\n                            if posx_diff > h_j2: continue\n\n                            for yi in range(y0, y1):\n                                y = (yi + 0.5) * dy + y_min\n\n                                posy_diff = py - y\n                                posy_diff = posy_diff * posy_diff\n                                if posy_diff > h_j2: continue\n\n                                q_ij2 = (posx_diff + posy_diff) * ih_j2\n                                if q_ij2 >= 1:\n                                    continue\n\n                                # see equation 32 of the SPLASH paper\n                                # now we just use the kernel projection\n                                local_buff[xi + yi*xsize] += prefactor_j * itab.interpolate(q_ij2)\n                                mask[xi, yi] = 1\n\n        with gil:\n            for xxi in range(xsize):\n                for yyi in range(ysize):\n                    buff[xxi, yyi] += local_buff[xxi + yyi*xsize]\n        free(local_buff)\n        free(xiterv)\n        free(yiterv)\n        free(xiter)\n        free(yiter)\n\n    return mask\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef interpolate_sph_positions_gather(np.float64_t[:] buff,\n        np.float64_t[:, ::1] tree_positions, np.float64_t[:, ::1] field_positions,\n        np.float64_t[:] hsml, np.float64_t[:] pmass, np.float64_t[:] pdens,\n        np.float64_t[:] quantity_to_smooth, PyKDTree kdtree,\n        int use_normalization=1, kernel_name=\"cubic\", pbar=None,\n        int num_neigh=32):\n\n    \"\"\"\n    This function takes in arbitrary positions, field_positions, at which to\n    perform a nearest neighbor search and perform SPH interpolation.\n\n    The results are stored in the buffer, buff, which is in the same order as\n    the field_positions are put in.\n    \"\"\"\n\n    cdef np.float64_t q_ij, h_j2, ih_j2, prefactor_j, smoothed_quantity_j\n    cdef np.float64_t * pos_ptr\n    cdef int i, particle, index\n    cdef BoundedPriorityQueue queue = BoundedPriorityQueue(num_neigh, True)\n    cdef np.float64_t[:] buff_den\n    cdef KDTree * ctree = kdtree._tree\n\n    # Which dimensions shall we use for spatial distances?\n    cdef axes_range axes\n    set_axes_range(&axes, -1)\n\n    # Only allocate memory if we are using normalization\n    if use_normalization:\n        buff_den = np.zeros(buff.shape[0], dtype=\"float64\")\n\n    kernel = get_kernel_func(kernel_name)\n\n    # Loop through all the positions we want to interpolate the SPH field onto\n    with nogil:\n        for i in range(0, buff.shape[0]):\n            queue.size = 0\n\n            # Update the current position\n            pos_ptr = &field_positions[i, 0]\n\n            # Use the KDTree to find the nearest neighbors\n            find_neighbors(pos_ptr, tree_positions, queue, ctree, -1, &axes)\n\n            # Set the smoothing length squared to the square of the distance\n            # of the furthest nearest neighbor\n            h_j2 = queue.heap[0]\n            ih_j2 = 1.0/h_j2\n\n            # Loop through each nearest neighbor and add contribution to the\n            # buffer\n            for index in range(queue.max_elements):\n                particle = queue.pids[index]\n\n                # Calculate contribution of this particle\n                prefactor_j = (pmass[particle] / pdens[particle] /\n                               hsml[particle]**3)\n                q_ij = math.sqrt(queue.heap[index]*ih_j2)\n                smoothed_quantity_j = (prefactor_j *\n                                       quantity_to_smooth[particle] *\n                                       kernel(q_ij))\n\n                # See equations 6, 9, and 11 of the SPLASH paper\n                buff[i] += smoothed_quantity_j\n\n                if use_normalization:\n                    buff_den[i] += prefactor_j * kernel(q_ij)\n\n    if use_normalization:\n        normalization_1d_utility(buff, buff_den)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef interpolate_sph_grid_gather(np.float64_t[:, :, :] buff,\n        np.float64_t[:, ::1] tree_positions, np.float64_t[:] bounds,\n        np.float64_t[:] hsml, np.float64_t[:] pmass, np.float64_t[:] pdens,\n        np.float64_t[:] quantity_to_smooth, PyKDTree kdtree,\n        int use_normalization=1, kernel_name=\"cubic\", pbar=None,\n        int num_neigh=32,\n        *,\n        int return_mask=0,\n):\n    \"\"\"\n    This function takes in the bounds and number of cells in a grid (well,\n    actually we implicitly calculate this from the size of buff). Then we can\n    perform nearest neighbor search and SPH interpolation at the centre of each\n    cell in the grid.\n    \"\"\"\n\n    cdef np.float64_t q_ij, h_j2, ih_j2, prefactor_j, smoothed_quantity_j\n    cdef np.float64_t dx, dy, dz\n    cdef np.float64_t[::1] pos = np.zeros(3, dtype=\"float64\")\n    cdef np.float64_t * pos_ptr = &pos[0]\n    cdef int i, j, k, particle, index\n    cdef BoundedPriorityQueue queue = BoundedPriorityQueue(num_neigh, True)\n    cdef np.float64_t[:, :, :] buff_den\n    cdef KDTree * ctree = kdtree._tree\n    cdef int prog\n\n    # Which dimensions shall we use for spatial distances?\n    cdef axes_range axes\n    set_axes_range(&axes, -1)\n\n    # Only allocate memory if we are using normalization\n    if use_normalization:\n        buff_den = np.zeros([buff.shape[0], buff.shape[1],\n                             buff.shape[2]], dtype=\"float64\")\n\n    kernel = get_kernel_func(kernel_name)\n    dx = (bounds[1] - bounds[0]) / buff.shape[0]\n    dy = (bounds[3] - bounds[2]) / buff.shape[1]\n    dz = (bounds[5] - bounds[4]) / buff.shape[2]\n\n    # Loop through all the positions we want to interpolate the SPH field onto\n    pbar = get_pbar(title=\"Interpolating (gather) SPH field\",\n                    maxval=(buff.shape[0]*buff.shape[1]*buff.shape[2] //\n                            10000)*10000)\n\n    cdef np.ndarray[np.uint8_t, ndim=3] mask_arr = np.zeros_like(buff, dtype=\"uint8\")\n    cdef np.uint8_t[:, :, :] mask = mask_arr\n\n    prog = 0\n    with nogil:\n        for i in range(0, buff.shape[0]):\n            for j in range(0, buff.shape[1]):\n                for k in range(0, buff.shape[2]):\n                    prog += 1\n                    if prog % 10000 == 0:\n                        with gil:\n                            PyErr_CheckSignals()\n                            pbar.update(prog)\n\n                    queue.size = 0\n\n                    # Update the current position\n                    pos[0] = bounds[0] + (i + 0.5) * dx\n                    pos[1] = bounds[2] + (j + 0.5) * dy\n                    pos[2] = bounds[4] + (k + 0.5) * dz\n\n                    # Use the KDTree to find the nearest neighbors\n                    find_neighbors(pos_ptr, tree_positions, queue, ctree, -1, &axes)\n\n                    # Set the smoothing length squared to the square of the distance\n                    # of the furthest nearest neighbor\n                    h_j2 = queue.heap[0]\n                    ih_j2 = 1.0/h_j2\n\n                    # Loop through each nearest neighbor and add contribution to the\n                    # buffer\n                    for index in range(queue.max_elements):\n                        particle = queue.pids[index]\n\n                        # Calculate contribution of this particle\n                        prefactor_j = (pmass[particle] / pdens[particle] /\n                                       hsml[particle]**3)\n                        q_ij = math.sqrt(queue.heap[index]*ih_j2)\n                        smoothed_quantity_j = (prefactor_j *\n                                               quantity_to_smooth[particle] *\n                                               kernel(q_ij))\n\n                        # See equations 6, 9, and 11 of the SPLASH paper\n                        buff[i, j, k] += smoothed_quantity_j\n                        mask[i, j, k] = 1\n\n                        if use_normalization:\n                            buff_den[i, j, k] += prefactor_j * kernel(q_ij)\n\n    if use_normalization:\n        normalization_3d_utility(buff, buff_den)\n\n    if return_mask:\n        return mask_arr.astype(\"bool\")\n\n@cython.initializedcheck(False)\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef pixelize_sph_kernel_slice(\n        np.float64_t[:, :] buff,\n        np.uint8_t[:, :] mask,\n        np.float64_t[:] posx, np.float64_t[:] posy,\n        np.float64_t[:] posz,\n        np.float64_t[:] hsml, np.float64_t[:] pmass,\n        np.float64_t[:] pdens,\n        np.float64_t[:] quantity_to_smooth,\n        bounds,\n        np.float64_t slicez,\n        kernel_name=\"cubic\",\n        _check_period = (1, 1, 1),\n        period=None):\n    #print(\"bounds, slicez, kernel_name, check_period, period\")\n    #print(bounds)\n    #print(slicez)\n    #print(kernel_name)\n    #print(check_period)\n    #print(period)\n    #print()\n    # bounds are [x0, x1, y0, y1], slicez is the single coordinate\n    # of the slice along the normal direction.\n    # similar method to pixelize_sph_kernel_projection\n    cdef np.intp_t xsize, ysize\n    cdef np.float64_t x_min, x_max, y_min, y_max, prefactor_j\n    cdef np.int64_t xi, yi, x0, x1, y0, y1, xxi, yyi\n    cdef np.float64_t q_ij, posx_diff, posy_diff, posz_diff, ih_j\n    cdef np.float64_t x, y, dx, dy, idx, idy, h_j2, h_j, px, py, pz\n    cdef int i, j, ii, jj\n    cdef np.float64_t period_x = 0, period_y = 0, period_z = 0\n    cdef int * xiter\n    cdef int * yiter\n    cdef np.float64_t * xiterv\n    cdef np.float64_t * yiterv\n    cdef np.int8_t[3] check_period\n\n    if period is not None:\n        period_x = period[0]\n        period_y = period[1]\n        period_z = period[2]\n    for i in range(3):\n        check_period[i] = np.int8(_check_period[i])\n\n    xsize, ysize = buff.shape[0], buff.shape[1]\n    x_min = bounds[0]\n    x_max = bounds[1]\n    y_min = bounds[2]\n    y_max = bounds[3]\n\n    dx = (x_max - x_min) / xsize\n    dy = (y_max - y_min) / ysize\n    idx = 1.0/dx\n    idy = 1.0/dy\n\n    kernel = get_kernel_func(kernel_name)\n    #print('particle index, ii, jj, px, py, pz')\n    with nogil, parallel():\n        # NOTE see note in pixelize_sph_kernel_projection\n        local_buff = <np.float64_t *> malloc(sizeof(np.float64_t) * xsize * ysize)\n        xiterv = <np.float64_t *> malloc(sizeof(np.float64_t) * 2)\n        yiterv = <np.float64_t *> malloc(sizeof(np.float64_t) * 2)\n        xiter = <int *> malloc(sizeof(int) * 2)\n        yiter = <int *> malloc(sizeof(int) * 2)\n        xiter[0] = yiter[0] = 0\n        xiterv[0] = yiterv[0] = 0.0\n        for i in range(xsize * ysize):\n            local_buff[i] = 0.0\n\n        for j in prange(0, posx.shape[0], schedule=\"dynamic\"):\n            if j % 100000 == 0:\n                with gil:\n                    PyErr_CheckSignals()\n            #with gil:\n            #    print(j)\n            xiter[1] = yiter[1] = 999\n            pz = posz[j]\n            if check_period[0] == 1:\n                if posx[j] - hsml[j] < x_min:\n                    xiter[1] = 1\n                    xiterv[1] = period_x\n                elif posx[j] + hsml[j] > x_max:\n                    xiter[1] = -1\n                    xiterv[1] = -period_x\n            if check_period[1] == 1:\n                if posy[j] - hsml[j] < y_min:\n                    yiter[1] = 1\n                    yiterv[1] = period_y\n                elif posy[j] + hsml[j] > y_max:\n                    yiter[1] = -1\n                    yiterv[1] = -period_y\n            if check_period[2] == 1:\n                # z of particle might be < hsml from the slice plane\n                # but across a periodic boundary\n                if posz[j] - hsml[j] > slicez:\n                    pz = posz[j] - period_z\n                elif posz[j] + hsml[j] < slicez:\n                    pz = posz[j] + period_z\n\n            h_j2 = hsml[j] * hsml[j] #fmax(hsml[j]*hsml[j], dx*dy)\n            h_j =  hsml[j] #math.sqrt(h_j2)\n            ih_j = 1.0/h_j\n\n            posz_diff = pz - slicez\n            posz_diff = posz_diff * posz_diff\n            if posz_diff > h_j2:\n                continue\n\n            prefactor_j = pmass[j] / pdens[j] / hsml[j]**3\n            prefactor_j *= quantity_to_smooth[j]\n\n            for ii in range(2):\n                if xiter[ii] == 999: continue\n                px = posx[j] + xiterv[ii]\n                if (px + hsml[j] < x_min) or (px - hsml[j] > x_max): continue\n                for jj in range(2):\n                    if yiter[jj] == 999: continue\n                    py = posy[j] + yiterv[jj]\n                    if (py + hsml[j] < y_min) or (py - hsml[j] > y_max): continue\n\n                    x0 = <np.int64_t> ( (px - hsml[j] - x_min) * idx)\n                    x1 = <np.int64_t> ( (px + hsml[j] - x_min) * idx)\n                    x0 = iclip(x0-1, 0, xsize)\n                    x1 = iclip(x1+1, 0, xsize)\n\n                    y0 = <np.int64_t> ( (py - hsml[j] - y_min) * idy)\n                    y1 = <np.int64_t> ( (py + hsml[j] - y_min) * idy)\n                    y0 = iclip(y0-1, 0, ysize)\n                    y1 = iclip(y1+1, 0, ysize)\n                    #with gil:\n                    #    print(ii, jj, px, py, pz)\n                    # Now we know which pixels to deposit onto for this particle,\n                    # so loop over them and add this particle's contribution\n                    for xi in range(x0, x1):\n                        x = (xi + 0.5) * dx + x_min\n\n                        posx_diff = px - x\n                        posx_diff = posx_diff * posx_diff\n                        if posx_diff > h_j2:\n                            continue\n\n                        for yi in range(y0, y1):\n                            y = (yi + 0.5) * dy + y_min\n\n                            posy_diff = py - y\n                            posy_diff = posy_diff * posy_diff\n                            if posy_diff > h_j2:\n                                continue\n\n                            # see equation 4 of the SPLASH paper\n                            q_ij = math.sqrt(posx_diff\n                                             + posy_diff\n                                             + posz_diff) * ih_j\n                            if q_ij >= 1:\n                                continue\n\n                            # see equations 6, 9, and 11 of the SPLASH paper\n                            local_buff[xi + yi*xsize] += prefactor_j * kernel(q_ij)\n                            mask[xi, yi] = 1\n\n        with gil:\n            for xxi in range(xsize):\n                for yyi in range(ysize):\n                    buff[xxi, yyi] += local_buff[xxi + yyi*xsize]\n        free(local_buff)\n        free(xiterv)\n        free(yiterv)\n        free(xiter)\n        free(yiter)\n\n@cython.initializedcheck(False)\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ndef pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff,\n        np.float64_t[:] posx, np.float64_t[:] posy, np.float64_t[:] posz,\n        np.float64_t[:] hsml, np.float64_t[:] pmass,\n        np.float64_t[:] pdens,\n        np.float64_t[:] quantity_to_smooth,\n        bounds, pbar=None, kernel_name=\"cubic\",\n        check_period=True, period=None):\n\n    cdef np.intp_t xsize, ysize, zsize\n    cdef np.float64_t x_min, x_max, y_min, y_max, z_min, z_max, prefactor_j\n    cdef np.int64_t xi, yi, zi, x0, x1, y0, y1, z0, z1\n    cdef np.float64_t q_ij, posx_diff, posy_diff, posz_diff, px, py, pz\n    cdef np.float64_t x, y, z, dx, dy, dz, idx, idy, idz, h_j2, h_j, ih_j\n    # cdef np.float64_t h_j3\n    cdef int j, ii, jj, kk\n    cdef np.float64_t period_x = 0, period_y = 0, period_z = 0\n\n    cdef int xiter[2]\n    cdef int yiter[2]\n    cdef int ziter[2]\n    cdef np.float64_t xiterv[2]\n    cdef np.float64_t yiterv[2]\n    cdef np.float64_t ziterv[2]\n    cdef int[3] periodic\n\n\n    xiter[0] = yiter[0] = ziter[0] = 0\n    xiterv[0] = yiterv[0] = ziterv[0] = 0.0\n\n    if hasattr(check_period, \"__len__\"):\n        periodic[0] = int(check_period[0])\n        periodic[1] = int(check_period[1])\n        periodic[2] = int(check_period[2])\n    else:\n        _cp = int(check_period)\n        periodic[0] = _cp\n        periodic[1] = _cp\n        periodic[2] = _cp\n    if period is not None:\n        period_x = period[0]\n        period_y = period[1]\n        period_z = period[2]\n\n    xsize, ysize, zsize = buff.shape[0], buff.shape[1], buff.shape[2]\n    x_min = bounds[0]\n    x_max = bounds[1]\n    y_min = bounds[2]\n    y_max = bounds[3]\n    z_min = bounds[4]\n    z_max = bounds[5]\n\n    dx = (x_max - x_min) / xsize\n    dy = (y_max - y_min) / ysize\n    dz = (z_max - z_min) / zsize\n    idx = 1.0/dx\n    idy = 1.0/dy\n    idz = 1.0/dz\n\n    kernel = get_kernel_func(kernel_name)\n\n    # nogil seems dangerous here, but there are no actual parallel\n    # sections (e.g., prange instead of range) used here.\n    # However, for future writers:\n    # !!  the final buff array mutation has no protections against\n    # !!  race conditions (e.g., OpenMP's atomic read/write), and\n    # !!  cython doesn't seem to provide such options.\n    # (other routines in this file use private variable buffer arrays\n    # and add everything together at the end, but grid arrays can get\n    # big fast, and having such a large array in each thread could\n    # cause memory use issues.)\n    with nogil:\n        # TODO make this parallel without using too much memory\n        for j in range(0, posx.shape[0]):\n            if j % 50000 == 0:\n                with gil:\n                    if(pbar is not None):\n                        pbar.update(50000)\n                    PyErr_CheckSignals()\n                # end with gil\n\n            xiter[1] = yiter[1] = ziter[1] = 999\n            xiterv[1] = yiterv[1] = ziterv[1] = 0.0\n\n            if periodic[0] == 1:\n                if posx[j] - hsml[j] < x_min:\n                    xiter[1] = +1\n                    xiterv[1] = period_x\n                elif posx[j] + hsml[j] > x_max:\n                    xiter[1] = -1\n                    xiterv[1] = -period_x\n            if periodic[1] == 1:\n                if posy[j] - hsml[j] < y_min:\n                    yiter[1] = +1\n                    yiterv[1] = period_y\n                elif posy[j] + hsml[j] > y_max:\n                    yiter[1] = -1\n                    yiterv[1] = -period_y\n            if periodic[2] == 1:\n                if posz[j] - hsml[j] < z_min:\n                    ziter[1] = +1\n                    ziterv[1] = period_z\n                elif posz[j] + hsml[j] > z_max:\n                    ziter[1] = -1\n                    ziterv[1] = -period_z\n\n            #h_j3 = fmax(hsml[j]*hsml[j]*hsml[j], dx*dy*dz)\n            h_j = hsml[j] #math.cbrt(h_j3)\n            h_j2 = h_j*h_j\n            ih_j = 1/h_j\n\n            prefactor_j = pmass[j] / pdens[j] / hsml[j]**3 * quantity_to_smooth[j]\n\n            for ii in range(2):\n                if xiter[ii] == 999: continue\n                px = posx[j] + xiterv[ii]\n                if (px + hsml[j] < x_min) or (px - hsml[j] > x_max): continue\n                for jj in range(2):\n                    if yiter[jj] == 999: continue\n                    py = posy[j] + yiterv[jj]\n                    if (py + hsml[j] < y_min) or (py - hsml[j] > y_max): continue\n                    for kk in range(2):\n                        if ziter[kk] == 999: continue\n                        pz = posz[j] + ziterv[kk]\n                        if (pz + hsml[j] < z_min) or (pz - hsml[j] > z_max): continue\n\n                        x0 = <np.int64_t> ( (px - hsml[j] - x_min) * idx)\n                        x1 = <np.int64_t> ( (px + hsml[j] - x_min) * idx)\n                        x0 = iclip(x0-1, 0, xsize)\n                        x1 = iclip(x1+1, 0, xsize)\n\n                        y0 = <np.int64_t> ( (py - hsml[j] - y_min) * idy)\n                        y1 = <np.int64_t> ( (py + hsml[j] - y_min) * idy)\n                        y0 = iclip(y0-1, 0, ysize)\n                        y1 = iclip(y1+1, 0, ysize)\n\n                        z0 = <np.int64_t> ( (pz - hsml[j] - z_min) * idz)\n                        z1 = <np.int64_t> ( (pz + hsml[j] - z_min) * idz)\n                        z0 = iclip(z0-1, 0, zsize)\n                        z1 = iclip(z1+1, 0, zsize)\n\n                        # Now we know which voxels to deposit onto for this particle,\n                        # so loop over them and add this particle's contribution\n                        for xi in range(x0, x1):\n                            x = (xi + 0.5) * dx + x_min\n\n                            posx_diff = px - x\n                            posx_diff = posx_diff * posx_diff\n                            if posx_diff > h_j2:\n                                continue\n\n                            for yi in range(y0, y1):\n                                y = (yi + 0.5) * dy + y_min\n\n                                posy_diff = py - y\n                                posy_diff = posy_diff * posy_diff\n                                if posy_diff > h_j2:\n                                    continue\n\n                                for zi in range(z0, z1):\n                                    z = (zi + 0.5) * dz + z_min\n\n                                    posz_diff = pz - z\n                                    posz_diff = posz_diff * posz_diff\n                                    if posz_diff > h_j2:\n                                        continue\n\n                                    # see equation 4 of the SPLASH paper\n                                    q_ij = math.sqrt(posx_diff\n                                                     + posy_diff\n                                                     + posz_diff) * ih_j\n                                    if q_ij >= 1:\n                                        continue\n                                    # shared variable buff should not\n                                    # be mutatated in a nogil section\n                                    # where different threads may change\n                                    # the same array element\n                                    buff[xi, yi, zi] += prefactor_j \\\n                                                        * kernel(q_ij)\n\n\ndef pixelize_element_mesh_line(np.ndarray[np.float64_t, ndim=2] coords,\n                               np.ndarray[np.int64_t, ndim=2] conn,\n                               np.ndarray[np.float64_t, ndim=1] start_point,\n                               np.ndarray[np.float64_t, ndim=1] end_point,\n                               npoints,\n                               np.ndarray[np.float64_t, ndim=2] field,\n                               int index_offset = 0):\n\n    # This routine chooses the correct element sampler to interpolate field\n    # values at evenly spaced points along a sampling line\n    cdef np.float64_t *vertices\n    cdef np.float64_t *field_vals\n    cdef int nvertices = conn.shape[1]\n    cdef int ndim = coords.shape[1]\n    cdef int num_field_vals = field.shape[1]\n    cdef int num_plot_nodes = npoints\n    cdef int num_intervals = npoints - 1\n    cdef double[4] mapped_coord\n    cdef ElementSampler sampler\n    cdef np.ndarray[np.float64_t, ndim=1] lin_vec\n    cdef np.ndarray[np.float64_t, ndim=1] lin_inc\n    cdef np.ndarray[np.float64_t, ndim=2] lin_sample_points\n    cdef np.int64_t i, n, j, k\n    cdef np.ndarray[np.float64_t, ndim=1] arc_length\n    cdef np.float64_t lin_length, inc_length\n    cdef np.ndarray[np.float64_t, ndim=1] plot_values\n    cdef np.float64_t sample_point[3]\n\n    lin_vec = np.zeros(ndim, dtype=\"float64\")\n    lin_inc = np.zeros(ndim, dtype=\"float64\")\n\n    lin_sample_points = np.zeros((num_plot_nodes, ndim), dtype=\"float64\")\n    arc_length = np.zeros(num_plot_nodes, dtype=\"float64\")\n    plot_values = np.zeros(num_plot_nodes, dtype=\"float64\")\n\n    # Pick the right sampler and allocate storage for the mapped coordinate\n    if ndim == 3 and nvertices == 4:\n        sampler = P1Sampler3D()\n    elif ndim == 3 and nvertices == 6:\n        sampler = W1Sampler3D()\n    elif ndim == 3 and nvertices == 8:\n        sampler = Q1Sampler3D()\n    elif ndim == 3 and nvertices == 20:\n        sampler = S2Sampler3D()\n    elif ndim == 2 and nvertices == 3:\n        sampler = P1Sampler2D()\n    elif ndim == 1 and nvertices == 2:\n        sampler = P1Sampler1D()\n    elif ndim == 2 and nvertices == 4:\n        sampler = Q1Sampler2D()\n    elif ndim == 2 and nvertices == 9:\n        sampler = Q2Sampler2D()\n    elif ndim == 2 and nvertices == 6:\n        sampler = T2Sampler2D()\n    elif ndim == 3 and nvertices == 10:\n        sampler = Tet2Sampler3D()\n    else:\n        raise YTElementTypeNotRecognized(ndim, nvertices)\n\n    # allocate temporary storage\n    vertices = <np.float64_t *> malloc(ndim * sizeof(np.float64_t) * nvertices)\n    field_vals = <np.float64_t *> malloc(sizeof(np.float64_t) * num_field_vals)\n\n    lin_vec = end_point - start_point\n    lin_length = np.linalg.norm(lin_vec)\n    lin_inc = lin_vec / num_intervals\n    inc_length = lin_length / num_intervals\n    for j in range(ndim):\n        lin_sample_points[0, j] = start_point[j]\n    arc_length[0] = 0\n    for i in range(1, num_intervals + 1):\n        for j in range(ndim):\n            lin_sample_points[i, j] = lin_sample_points[i-1, j] + lin_inc[j]\n            arc_length[i] = arc_length[i-1] + inc_length\n\n    for i in range(num_intervals + 1):\n        for j in range(3):\n            if j < ndim:\n                sample_point[j] = lin_sample_points[i][j]\n            else:\n                sample_point[j] = 0\n        for ci in range(conn.shape[0]):\n            for n in range(num_field_vals):\n                field_vals[n] = field[ci, n]\n\n            # Fill the vertices\n            for n in range(nvertices):\n                cj = conn[ci, n] - index_offset\n                for k in range(ndim):\n                    vertices[ndim*n + k] = coords[cj, k]\n\n            sampler.map_real_to_unit(mapped_coord, vertices, sample_point)\n            if not sampler.check_inside(mapped_coord) and ci != conn.shape[0] - 1:\n                continue\n            elif not sampler.check_inside(mapped_coord):\n                raise ValueError(\"Check to see that both starting and ending line points \"\n                                 \"are within the domain of the mesh.\")\n            plot_values[i] = sampler.sample_at_unit_point(mapped_coord, field_vals)\n            break\n\n    free(vertices)\n    free(field_vals)\n    return arc_length, plot_values\n\n# intended for use in ParticleImageBuffer\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef rotate_particle_coord_pib(np.float64_t[:] px,\n                              np.float64_t[:] py,\n                              np.float64_t[:] pz,\n                              center,\n                              width,\n                              normal_vector,\n                              north_vector):\n    # We want to do two rotations, one to first rotate our coordinates to have\n    # the normal vector be the z-axis (i.e., the viewer's perspective), and then\n    # another rotation to make the north-vector be the y-axis (i.e., north).\n    # Fortunately, total_rotation_matrix = rotation_matrix_1 x rotation_matrix_2\n    cdef int num_particles = np.size(px)\n    cdef np.float64_t[:] z_axis = np.array([0., 0., 1.], dtype=\"float64\")\n    cdef np.float64_t[:] y_axis = np.array([0., 1., 0.], dtype=\"float64\")\n    cdef np.float64_t[:, :] normal_rotation_matrix\n    cdef np.float64_t[:] transformed_north_vector\n    cdef np.float64_t[:, :] north_rotation_matrix\n    cdef np.float64_t[:, :] rotation_matrix\n\n    normal_rotation_matrix = get_rotation_matrix(normal_vector, z_axis)\n    transformed_north_vector = np.matmul(normal_rotation_matrix, north_vector)\n    north_rotation_matrix = get_rotation_matrix(transformed_north_vector, y_axis)\n    rotation_matrix = np.matmul(north_rotation_matrix, normal_rotation_matrix)\n\n    cdef np.float64_t[:] px_rotated = np.empty(num_particles, dtype=\"float64\")\n    cdef np.float64_t[:] py_rotated = np.empty(num_particles, dtype=\"float64\")\n    cdef np.float64_t[:] coordinate_matrix = np.empty(3, dtype=\"float64\")\n    cdef np.float64_t[:] rotated_coordinates\n    cdef np.float64_t[:] rotated_center\n    rotated_center = rotation_matmul(\n        rotation_matrix, np.array([center[0], center[1], center[2]]))\n\n    # set up the rotated bounds\n    cdef np.float64_t rot_bounds_x0 = rotated_center[0] - width[0] / 2\n    cdef np.float64_t rot_bounds_x1 = rotated_center[0] + width[0] / 2\n    cdef np.float64_t rot_bounds_y0 = rotated_center[1] - width[1] / 2\n    cdef np.float64_t rot_bounds_y1 = rotated_center[1] + width[1] / 2\n\n    for i in range(num_particles):\n        coordinate_matrix[0] = px[i]\n        coordinate_matrix[1] = py[i]\n        coordinate_matrix[2] = pz[i]\n        rotated_coordinates = rotation_matmul(\n            rotation_matrix, coordinate_matrix)\n        px_rotated[i] = rotated_coordinates[0]\n        py_rotated[i] = rotated_coordinates[1]\n\n    return px_rotated, py_rotated, rot_bounds_x0, rot_bounds_x1, rot_bounds_y0, rot_bounds_y1\n\n# version intended for SPH off-axis slices/projections\n# includes dealing with periodic boundaries, but also\n# shifts particles so center -> origin.\n# therefore, don't want to use this in the ParticleImageBuffer,\n# which expects differently centered coordinates.\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef rotate_particle_coord(np.float64_t[:] px,\n                          np.float64_t[:] py,\n                          np.float64_t[:] pz,\n                          center,\n                          bounds,\n                          periodic,\n                          width,\n                          depth,\n                          normal_vector,\n                          north_vector):\n    # We want to do two rotations, one to first rotate our coordinates to have\n    # the normal vector be the z-axis (i.e., the viewer's perspective), and then\n    # another rotation to make the north-vector be the y-axis (i.e., north).\n    # Fortunately, total_rotation_matrix = rotation_matrix_1 x rotation_matrix_2\n    cdef np.int64_t num_particles = np.size(px)\n    cdef np.float64_t[:] z_axis = np.array([0., 0., 1.], dtype=\"float64\")\n    cdef np.float64_t[:] y_axis = np.array([0., 1., 0.], dtype=\"float64\")\n    cdef np.float64_t[:, :] normal_rotation_matrix\n    cdef np.float64_t[:] transformed_north_vector\n    cdef np.float64_t[:, :] north_rotation_matrix\n    cdef np.float64_t[:, :] rotation_matrix\n\n    normal_rotation_matrix = get_rotation_matrix(normal_vector, z_axis)\n    transformed_north_vector = np.matmul(normal_rotation_matrix, north_vector)\n    north_rotation_matrix = get_rotation_matrix(transformed_north_vector, y_axis)\n    rotation_matrix = np.matmul(north_rotation_matrix, normal_rotation_matrix)\n\n    cdef np.float64_t[:] px_rotated = np.empty(num_particles, dtype=\"float64\")\n    cdef np.float64_t[:] py_rotated = np.empty(num_particles, dtype=\"float64\")\n    cdef np.float64_t[:] pz_rotated = np.empty(num_particles, dtype=\"float64\")\n    cdef np.float64_t[:] coordinate_matrix = np.empty(3, dtype=\"float64\")\n    cdef np.float64_t[:] rotated_coordinates\n    cdef np.float64_t[:] rotated_center\n    cdef np.int64_t i\n    cdef int ax\n    #rotated_center = rotation_matmul(\n    #    rotation_matrix, np.array([center[0], center[1], center[2]]))\n    rotated_center = np.zeros((3,), dtype=center.dtype)\n    # set up the rotated bounds\n    cdef np.float64_t rot_bounds_x0 = rotated_center[0] - 0.5 * width[0]\n    cdef np.float64_t rot_bounds_x1 = rotated_center[0] + 0.5 * width[0]\n    cdef np.float64_t rot_bounds_y0 = rotated_center[1] - 0.5 * width[1]\n    cdef np.float64_t rot_bounds_y1 = rotated_center[1] + 0.5 * width[1]\n    cdef np.float64_t rot_bounds_z0 = rotated_center[2] - 0.5 * depth\n    cdef np.float64_t rot_bounds_z1 = rotated_center[2] + 0.5 * depth\n    for i in range(num_particles):\n        coordinate_matrix[0] = px[i]\n        coordinate_matrix[1] = py[i]\n        coordinate_matrix[2] = pz[i]\n\n        # centering:\n        # make sure this also works for centers close to periodic edges\n        # added consequence: the center is placed at the origin\n        # (might as well keep it there in these temporary coordinates)\n        for ax in range(3):\n            # assumed center is zero even if non-periodic\n            coordinate_matrix[ax] -= center[ax]\n            if not periodic[ax]: continue\n            period = bounds[2 * ax + 1] - bounds[2 * ax]\n            # abs. difference between points in the volume is <= period\n            if coordinate_matrix[ax] < -0.5 * period:\n                coordinate_matrix[ax] += period\n            if coordinate_matrix[ax] > 0.5 * period:\n                coordinate_matrix[ax] -= period\n\n        rotated_coordinates = rotation_matmul(\n            rotation_matrix, coordinate_matrix)\n        px_rotated[i] = rotated_coordinates[0]\n        py_rotated[i] = rotated_coordinates[1]\n        pz_rotated[i] = rotated_coordinates[2]\n\n    return (px_rotated, py_rotated, pz_rotated,\n            rot_bounds_x0, rot_bounds_x1,\n            rot_bounds_y0, rot_bounds_y1,\n            rot_bounds_z0, rot_bounds_z1)\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef off_axis_projection_SPH(np.float64_t[:] px,\n                            np.float64_t[:] py,\n                            np.float64_t[:] pz,\n                            np.float64_t[:] particle_masses,\n                            np.float64_t[:] particle_densities,\n                            np.float64_t[:] smoothing_lengths,\n                            bounds,\n                            center,\n                            width,\n                            periodic,\n                            np.float64_t[:] quantity_to_smooth,\n                            np.float64_t[:, :] projection_array,\n                            np.uint8_t[:, :] mask,\n                            normal_vector,\n                            north_vector,\n                            weight_field=None,\n                            depth=None,\n                            kernel_name=\"cubic\"):\n    # periodic: periodicity of the data set:\n    # Do nothing in event of a 0 normal vector\n    if np.allclose(normal_vector, 0.):\n        return\n    if depth is None:\n        # set to volume diagonal + margin -> won't exclude anything\n        depth = 2. * np.sqrt((bounds[1] - bounds[0])**2\n                           + (bounds[3] - bounds[2])**2\n                           + (bounds[5] - bounds[4])**2)\n    px_rotated, py_rotated, pz_rotated, \\\n    rot_bounds_x0, rot_bounds_x1, \\\n    rot_bounds_y0, rot_bounds_y1, \\\n    rot_bounds_z0, rot_bounds_z1 = rotate_particle_coord(px, py, pz,\n                                                         center, bounds,\n                                                         periodic,\n                                                         width, depth,\n                                                         normal_vector,\n                                                         north_vector)\n    # check_period=0: assumed to be a small region compared to the box\n    # size. The rotation already ensures that a center close to a\n    # periodic edge works out fine.\n    # since the simple single-coordinate modulo math periodicity\n    # does not apply to the *rotated* coordinates, the periodicity\n    # approach implemented for this along-axis projection method\n    # would fail here\n    check_period = np.array([0, 0, 0], dtype=\"int\")\n    pixelize_sph_kernel_projection(projection_array,\n                                   mask,\n                                   px_rotated,\n                                   py_rotated,\n                                   pz_rotated,\n                                   smoothing_lengths,\n                                   particle_masses,\n                                   particle_densities,\n                                   quantity_to_smooth,\n                                   [rot_bounds_x0, rot_bounds_x1,\n                                    rot_bounds_y0, rot_bounds_y1,\n                                    rot_bounds_z0, rot_bounds_z1],\n                                   weight_field=weight_field,\n                                   _check_period=check_period,\n                                   kernel_name=kernel_name)\n\n# like slice pixelization, but for off-axis planes\ndef pixelize_sph_kernel_cutting(\n        np.float64_t[:, :] buff,\n        np.uint8_t[:, :] mask,\n        np.float64_t[:] posx, np.float64_t[:] posy,\n        np.float64_t[:] posz,\n        np.float64_t[:] hsml, np.float64_t[:] pmass,\n        np.float64_t[:] pdens,\n        np.float64_t[:] quantity_to_smooth,\n        center, widthxy,\n        normal_vector, north_vector,\n        boxbounds, periodic,\n        kernel_name=\"cubic\",\n        int check_period=1):\n\n    if check_period == 0:\n        periodic = np.zeros(3, dtype=bool)\n\n    posx_rot, posy_rot, posz_rot, \\\n    rot_bounds_x0, rot_bounds_x1, \\\n    rot_bounds_y0, rot_bounds_y1, \\\n    rot_bounds_z0, _ = rotate_particle_coord(posx, posy, posz,\n                                             center, boxbounds,\n                                             periodic,\n                                             widthxy, 0.,\n                                             normal_vector,\n                                             north_vector)\n    bounds_rot = np.array([rot_bounds_x0, rot_bounds_x1,\n                           rot_bounds_y0, rot_bounds_y1])\n    slicez_rot = rot_bounds_z0\n    pixelize_sph_kernel_slice(buff, mask,\n                              posx_rot, posy_rot, posz_rot,\n                              hsml, pmass, pdens, quantity_to_smooth,\n                              bounds_rot, slicez_rot,\n                              kernel_name=kernel_name,\n                              _check_period=np.zeros(3, dtype=\"int\"),\n                              period=None)\n\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncdef np.float64_t[:] rotation_matmul(np.float64_t[:, :] rotation_matrix,\n                                     np.float64_t[:] coordinate_matrix):\n    cdef np.float64_t[:] out = np.zeros(3)\n    for i in range(3):\n        for j in range(3):\n            out[i] += rotation_matrix[i, j] * coordinate_matrix[j]\n    return out\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ncpdef np.float64_t[:, :] get_rotation_matrix(np.float64_t[:] normal_vector,\n                                             np.float64_t[:] final_vector):\n    \"\"\" Returns a numpy rotation matrix corresponding to the\n    rotation of the given normal vector to the specified final_vector.\n    See https://math.stackexchange.com/a/476311 although note we return the\n    inverse of what's specified there.\n    \"\"\"\n    cdef np.float64_t[:] normal_unit_vector = normal_vector / np.linalg.norm(normal_vector)\n    cdef np.float64_t[:] final_unit_vector = final_vector / np.linalg.norm(final_vector)\n    cdef np.float64_t[:] v = np.cross(final_unit_vector, normal_unit_vector)\n    cdef np.float64_t s = np.linalg.norm(v)\n    cdef np.float64_t c = np.dot(final_unit_vector, normal_unit_vector)\n    # if the normal vector is identical to the final vector, just return the\n    # identity matrix\n    if np.isclose(c, 1, rtol=1e-09):\n        return np.identity(3, dtype=\"float64\")\n    # if the normal vector is the negative final vector, return the appropriate\n    # rotation matrix for flipping your coordinate system.\n    if np.isclose(s, 0, rtol=1e-09):\n        return np.array([[0, -1, 0],[-1, 0, 0],[0, 0, -1]], dtype=\"float64\")\n\n    cdef np.float64_t[:, :] cross_product_matrix = np.array([[0, -1 * v[2], v[1]],\n                                                      [v[2], 0, -1 * v[0]],\n                                                      [-1 * v[1], v[0], 0]],\n                                                      dtype=\"float64\")\n    return np.linalg.inv(np.identity(3, dtype=\"float64\") + cross_product_matrix\n                         + np.matmul(cross_product_matrix, cross_product_matrix)\n                         * 1/(1+c))\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef normalization_3d_utility(np.float64_t[:, :, :] num,\n                             np.float64_t[:, :, :] den):\n    cdef int i, j, k\n    for i in range(num.shape[0]):\n        for j in range(num.shape[1]):\n            for k in range(num.shape[2]):\n                if den[i, j, k] != 0.0:\n                    num[i, j, k] = num[i, j, k] / den[i, j, k]\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef normalization_2d_utility(np.float64_t[:, :] num,\n                          np.float64_t[:, :] den):\n    cdef int i, j\n    for i in range(num.shape[0]):\n        for j in range(num.shape[1]):\n            if den[i, j] != 0.0:\n                num[i, j] = num[i, j] / den[i, j]\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\ndef normalization_1d_utility(np.float64_t[:] num,\n                             np.float64_t[:] den):\n    cdef int i\n    for i in range(num.shape[0]):\n        if den[i] != 0.0:\n            num[i] = num[i] / den[i]\n"
  },
  {
    "path": "yt/utilities/lib/platform_dep.h",
    "content": "#include <math.h>\n#ifdef MS_WIN32\n#include \"malloc.h\"\n/*\nnote: the following implicitly sets a mininum VS version: conservative\nminimum is _MSC_VER >= 1928 (VS 2019, 16.8), but may work for VS 2015\nbut that has not been tested. see https://github.com/yt-project/yt/pull/4980\nand https://learn.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance\n*/\n#include <float.h>\n#include <stdint.h>\n#elif defined(__FreeBSD__)\n#include <stdint.h>\n#include <stdlib.h>\n#include <math.h>\n#else\n#include <stdint.h>\n#include \"alloca.h\"\n#include <math.h>\n#endif\n"
  },
  {
    "path": "yt/utilities/lib/platform_dep_math.hpp",
    "content": "/*\nThis file provides a compatibility layout between MSVC, and different version of GCC.\n\nMSVC does not define isnormal in the std:: namespace, so we cannot import it from <cmath>, but from <math.h> instead.\nHowever with GCC-5, there is a clash between the definition of isnormal in <math.h> and using C++14, so we need to import from cmath instead.\n*/\n\n#if _MSC_VER\n#include <math.h>\ninline bool __isnormal(double x) {\n    return isnormal(x);\n}\n#elif defined(__FreeBSD__)\n#else\n#include <cmath>\ninline bool __isnormal(double x) {\n    return std::isnormal(x);\n}\n#endif\n"
  },
  {
    "path": "yt/utilities/lib/points_in_volume.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nChecks for points contained in a volume\n\n\n\n\"\"\"\n\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.math cimport sqrt\n\n\ncdef extern from \"math.h\":\n    double fabs(double x)\n\n@cython.wraparound(False)\n@cython.boundscheck(False)\ndef planar_points_in_volume(\n                   np.ndarray[np.float64_t, ndim=2] points,\n                   np.ndarray[np.int8_t, ndim=1] pmask,  # pixel mask\n                   np.ndarray[np.float64_t, ndim=1] left_edge,\n                   np.ndarray[np.float64_t, ndim=1] right_edge,\n                   np.ndarray[np.int32_t, ndim=3] mask,\n                   float dx):\n    cdef np.ndarray[np.int8_t, ndim=1] \\\n         valid = np.zeros(points.shape[0], dtype='int8')\n    cdef int i, dim, count\n    cdef int ex\n    cdef double dx_inv\n    cdef unsigned int idx[3]\n    count = 0\n    dx_inv = 1.0 / dx\n    for i in xrange(points.shape[0]):\n        if pmask[i] == 0:\n            continue\n        ex = 1\n        for dim in xrange(3):\n            if points[i,dim] < left_edge[dim] or points[i,dim] > right_edge[dim]:\n                valid[i] = ex = 0\n                break\n        if ex == 1:\n            for dim in xrange(3):\n                idx[dim] = <unsigned int> \\\n                           ((points[i,dim] - left_edge[dim]) * dx_inv)\n            if mask[idx[0], idx[1], idx[2]] == 1:\n                valid[i] = 1\n                count += 1\n\n    cdef np.ndarray[np.int32_t, ndim=1] result = np.empty(count, dtype='int32')\n    count = 0\n    for i in xrange(points.shape[0]):\n        if valid[i] == 1 and pmask[i] == 1:\n            result[count] = i\n            count += 1\n\n    return result\n\ncdef inline void set_rotated_pos(\n            np.float64_t cp[3], np.float64_t rdds[3][3],\n            np.float64_t rorigin[3], int i, int j, int k):\n    cdef int oi\n    for oi in range(3):\n        cp[oi] = rdds[0][oi] * (0.5 + i) \\\n               + rdds[1][oi] * (0.5 + j) \\\n               + rdds[2][oi] * (0.5 + k) \\\n               + rorigin[oi]\n\n#@cython.wraparound(False)\n#@cython.boundscheck(False)\ndef grid_points_in_volume(\n                   np.ndarray[np.float64_t, ndim=1] box_lengths,\n                   np.ndarray[np.float64_t, ndim=1] box_origin,\n                   np.ndarray[np.float64_t, ndim=2] rot_mat,\n                   np.ndarray[np.float64_t, ndim=1] grid_left_edge,\n                   np.ndarray[np.float64_t, ndim=1] grid_right_edge,\n                   np.ndarray[np.float64_t, ndim=1] dds,\n                   np.ndarray[np.int32_t, ndim=3] mask,\n                   int break_first):\n    cdef int n[3]\n    cdef int i, j, k\n    cdef np.float64_t rds[3][3]\n    cdef np.float64_t cur_pos[3]\n    cdef np.float64_t rorigin[3]\n    for i in range(3):\n        rorigin[i] = 0.0\n    for i in range(3):\n        n[i] = mask.shape[i]\n        for j in range(3):\n            # Set up our transposed dx, which has a component in every\n            # direction\n            rds[i][j] = dds[i] * rot_mat[j,i]\n            # In our rotated coordinate system, the box origin is 0,0,0\n            # so we subtract the box_origin from the grid_origin and rotate\n            # that\n            rorigin[j] += (grid_left_edge[i] - box_origin[i]) * rot_mat[j,i]\n\n    for i in range(n[0]):\n        for j in range(n[1]):\n            for k in range(n[2]):\n                set_rotated_pos(cur_pos, rds, rorigin, i, j, k)\n                if (cur_pos[0] > box_lengths[0]): continue\n                if (cur_pos[1] > box_lengths[1]): continue\n                if (cur_pos[2] > box_lengths[2]): continue\n                if (cur_pos[0] < 0.0): continue\n                if (cur_pos[1] < 0.0): continue\n                if (cur_pos[2] < 0.0): continue\n                if break_first:\n                    if mask[i,j,k]: return 1\n                else:\n                    mask[i,j,k] = 1\n    return 0\n\ncdef void normalize_vector(np.float64_t vec[3]):\n    cdef int i\n    cdef np.float64_t norm = 0.0\n    for i in range(3):\n        norm += vec[i]*vec[i]\n    norm = sqrt(norm)\n    for i in range(3):\n        vec[i] /= norm\n\ncdef void get_cross_product(np.float64_t v1[3],\n                            np.float64_t v2[3],\n                            np.float64_t cp[3]):\n    cp[0] = v1[1]*v2[2] - v1[2]*v2[1]\n    cp[1] = v1[3]*v2[0] - v1[0]*v2[3]\n    cp[2] = v1[0]*v2[1] - v1[1]*v2[0]\n    #print(cp[0], cp[1], cp[2])\n\ncdef int check_projected_overlap(\n        np.float64_t sep_ax[3], np.float64_t sep_vec[3], int gi,\n        np.float64_t b_vec[3][3], np.float64_t g_vec[3][3]):\n    cdef int g_ax, b_ax\n    cdef np.float64_t tba, tga, ba, ga, sep_dot\n    ba = ga = sep_dot = 0.0\n    for g_ax in range(3):\n        # We need the grid vectors, which we'll precompute here\n        tba = tga = 0.0\n        for b_ax in range(3):\n            tba += b_vec[g_ax][b_ax] * sep_vec[b_ax]\n            tga += g_vec[g_ax][b_ax] * sep_vec[b_ax]\n        ba += fabs(tba)\n        ga += fabs(tga)\n        sep_dot += sep_vec[g_ax] * sep_ax[g_ax]\n    #print(sep_vec[0], sep_vec[1], sep_vec[2],)\n    #print(sep_ax[0], sep_ax[1], sep_ax[2])\n    return (fabs(sep_dot) > ba+ga)\n    # Now we do\n\n@cython.wraparound(False)\n@cython.boundscheck(False)\ndef find_grids_in_inclined_box(\n        np.ndarray[np.float64_t, ndim=2] box_vectors,\n        np.ndarray[np.float64_t, ndim=1] box_center,\n        np.ndarray[np.float64_t, ndim=2] grid_left_edges,\n        np.ndarray[np.float64_t, ndim=2] grid_right_edges):\n\n    # http://www.gamasutra.com/view/feature/3383/simple_intersection_tests_for_games.php?page=5\n    cdef int n = grid_right_edges.shape[0]\n    cdef int g_ax, b_ax, gi\n    cdef np.float64_t b_vec[3][3]\n    cdef np.float64_t g_vec[3][3]\n    cdef np.float64_t a_vec[3][3]\n    cdef np.float64_t sep_ax[15][3]\n    cdef np.float64_t sep_vec[3]\n    cdef np.ndarray[np.int32_t, ndim=1] good = np.zeros(n, dtype='int32')\n    cdef np.ndarray[np.float64_t, ndim=2] grid_centers\n    # Fill in our axis unit vectors\n    for b_ax in range(3):\n        for g_ax in range(3):\n            a_vec[b_ax][g_ax] = <np.float64_t> (b_ax == g_ax)\n    grid_centers = (grid_right_edges + grid_left_edges)/2.0\n\n    # Now we pre-compute our candidate separating axes, because the unit\n    # vectors for all the grids are identical\n    for b_ax in range(3):\n        # We have 6 principal axes we already know, which are the grid (domain)\n        # principal axes and the box axes\n        sep_ax[b_ax][0] = sep_ax[b_ax][1] = sep_ax[b_ax][2] = 0.0\n        sep_ax[b_ax][b_ax] = 1.0 # delta_ijk, for grid axes\n        for g_ax in range(3):\n            b_vec[b_ax][g_ax] = 0.5*box_vectors[b_ax,g_ax]\n            sep_ax[b_ax + 3][g_ax] = b_vec[b_ax][g_ax] # box axes\n        normalize_vector(sep_ax[b_ax + 3])\n        for g_ax in range(3):\n            get_cross_product(b_vec[b_ax], a_vec[g_ax], sep_ax[b_ax*3 + g_ax + 6])\n            normalize_vector(sep_ax[b_ax*3 + g_ax + 6])\n\n    for gi in range(n):\n        for g_ax in range(3):\n            # Calculate the separation vector\n            sep_vec[g_ax] = grid_centers[gi, g_ax] - box_center[g_ax]\n            # Calculate the grid axis lengths\n            g_vec[g_ax][0] = g_vec[g_ax][1] = g_vec[g_ax][2] = 0.0\n            g_vec[g_ax][g_ax] = 0.5 * (grid_right_edges[gi, g_ax]\n                                     - grid_left_edges[gi, g_ax])\n        for b_ax in range(15):\n            #print(b_ax,)\n            if check_projected_overlap(\n                        sep_ax[b_ax], sep_vec, gi,\n                        b_vec,  g_vec):\n                good[gi] = 1\n                break\n    return good\n\ndef calculate_fill_grids(int fill_level, int refratio, int last_level,\n                         np.ndarray[np.int64_t, ndim=1] domain_width,\n                         np.ndarray[np.int64_t, ndim=1] cg_start_index,\n                         np.ndarray[np.int32_t, ndim=1] cg_dims,\n                         np.ndarray[np.int64_t, ndim=1] g_start_index,\n                         np.ndarray[np.int32_t, ndim=1] g_dims,\n                         np.ndarray[np.uint8_t, ndim=3, cast=True] g_child_mask):\n    cdef np.int64_t cgstart[3]\n    cdef np.int64_t gstart[3]\n    cdef np.int64_t cgend[3]\n    cdef np.int64_t gend[3]\n    cdef np.int64_t dw[3]\n    cdef np.int64_t cxi, cyi, czi, gxi, gyi, gzi, ci, cj, ck\n    cdef int i, total = 0\n    for i in range(3):\n        dw[i] = domain_width[i]\n        cgstart[i] = cg_start_index[i]\n        gstart[i] = g_start_index[i]\n        cgend[i] = cgstart[i] + cg_dims[i]\n        gend[i] = gstart[i] + g_dims[i]\n    for cxi in range(cgstart[0], cgend[0]+1):\n        ci = (cxi % dw[0])\n        if ci < 0: ci += dw[0]\n        if ci < gstart[0]*refratio or ci >= gend[0]*refratio: continue\n        gxi = (<np.int64_t> (ci / refratio)) - gstart[0]\n        for cyi in range(cgstart[1], cgend[1]):\n            cj = (cyi % dw[1])\n            if cj < 0: cj += dw[1]\n            if cj < gstart[1]*refratio or cj >= gend[1]*refratio: continue\n            gyi = (<np.int64_t> (cj / refratio)) - gstart[1]\n            for czi in range(cgstart[2], cgend[2]):\n                ck = (czi % dw[2])\n                if ck < 0: ck += dw[2]\n                if ck < gstart[2]*refratio or cj >= gend[2]*refratio: continue\n                gzi = (<np.int64_t> (ck / refratio)) - gstart[2]\n                if last_level or g_child_mask[gxi, gyi, gzi] > 0: total += 1\n    return total\n"
  },
  {
    "path": "yt/utilities/lib/primitives.pxd",
    "content": "cimport cython\n\nimport numpy as np\n\ncimport numpy as np\n\nfrom .vec3_ops cimport cross, dot, subtract\n\n\ncdef struct Ray:\n    np.float64_t origin[3]\n    np.float64_t direction[3]\n    np.float64_t inv_dir[3]\n    np.float64_t data_val\n    np.float64_t t_near\n    np.float64_t t_far\n    np.int64_t elem_id\n    np.int64_t near_boundary\n\ncdef struct BBox:\n    np.float64_t left_edge[3]\n    np.float64_t right_edge[3]\n\ncdef struct RayHitData:\n    np.float64_t u\n    np.float64_t v\n    np.float64_t t\n    np.int64_t converged\n\ncdef struct Triangle:\n    np.float64_t p0[3]\n    np.float64_t p1[3]\n    np.float64_t p2[3]\n    np.int64_t elem_id\n\ncdef np.int64_t ray_bbox_intersect(Ray* ray, const BBox bbox) noexcept nogil\n\ncdef np.int64_t ray_triangle_intersect(const void* primitives,\n                                       const np.int64_t item,\n                                       Ray* ray) noexcept nogil\n\ncdef void triangle_centroid(const void *primitives,\n                            const np.int64_t item,\n                            np.float64_t[3] centroid) noexcept nogil\n\ncdef void triangle_bbox(const void *primitives,\n                        const np.int64_t item,\n                        BBox* bbox) noexcept nogil\n\ncdef struct Patch:\n    np.float64_t[8][3] v  # 8 vertices per patch\n    np.int64_t elem_id\n\ncdef void patchSurfaceFunc(const cython.floating[8][3] verts,\n                           const cython.floating u,\n                           const cython.floating v,\n                           cython.floating[3] S) noexcept nogil\n\ncdef void patchSurfaceDerivU(const cython.floating[8][3] verts,\n                             const cython.floating u,\n                             const cython.floating v,\n                             cython.floating[3] Su) noexcept nogil\n\ncdef void patchSurfaceDerivV(const cython.floating[8][3] verts,\n                             const cython.floating u,\n                             const cython.floating v,\n                             cython.floating[3] Sv) noexcept nogil\n\ncdef RayHitData compute_patch_hit(cython.floating[8][3] verts,\n                                  cython.floating[3] ray_origin,\n                                  cython.floating[3] ray_direction) noexcept nogil\n\ncdef np.int64_t ray_patch_intersect(const void* primitives,\n                                    const np.int64_t item,\n                                    Ray* ray) noexcept nogil\n\ncdef void patch_centroid(const void *primitives,\n                         const np.int64_t item,\n                         np.float64_t[3] centroid) noexcept nogil\n\ncdef void patch_bbox(const void *primitives,\n                     const np.int64_t item,\n                     BBox* bbox) noexcept nogil\n\ncdef struct TetPatch:\n    np.float64_t[6][3] v # 6 vertices per patch\n    np.int64_t elem_id\n\ncdef RayHitData compute_tet_patch_hit(cython.floating[6][3] verts,\n                                  cython.floating[3] ray_origin,\n                                  cython.floating[3] ray_direction) noexcept nogil\n\ncdef void tet_patchSurfaceFunc(const cython.floating[6][3] verts,\n                           const cython.floating u,\n                           const cython.floating v,\n                           cython.floating[3] S) noexcept nogil\n\ncdef void tet_patchSurfaceDerivU(const cython.floating[6][3] verts,\n                             const cython.floating u,\n                             const cython.floating v,\n                             cython.floating[3] Su) noexcept nogil\n\ncdef void tet_patchSurfaceDerivV(const cython.floating[6][3] verts,\n                             const cython.floating u,\n                             const cython.floating v,\n                             cython.floating[3] Sv) noexcept nogil\n\ncdef np.int64_t ray_tet_patch_intersect(const void* primitives,\n                                    const np.int64_t item,\n                                    Ray* ray) noexcept nogil\n\ncdef void tet_patch_centroid(const void *primitives,\n                         const np.int64_t item,\n                         np.float64_t[3] centroid) noexcept nogil\n\ncdef void tet_patch_bbox(const void *primitives,\n                     const np.int64_t item,\n                     BBox* bbox) noexcept nogil\n"
  },
  {
    "path": "yt/utilities/lib/primitives.pyx",
    "content": "# distutils: libraries = STD_LIBS\n\"\"\"\n\nThis file contains definitions of the various primitives that can be used\nby the Cython ray-tracer for unstructured mesh rendering. To define a new\nprimitive type, you need to define a struct that represents it. You also\nneed to provide three functions:\n\n1. A function that computes the intersection between a given ray and a given primitive.\n2. A function that computes the centroid of the primitive type.\n3. A function that computes the axis-aligned bounding box of a given primitive.\n\n\"\"\"\n\ncimport cython\n\nimport numpy as np\n\ncimport numpy as np\nfrom libc.math cimport fabs\n\nfrom yt.utilities.lib.vec3_ops cimport L2_norm, cross, distance, dot, subtract\n\n\ncdef np.float64_t DETERMINANT_EPS = 1.0e-10\ncdef np.float64_t INF = np.inf\n\ncdef extern from \"platform_dep.h\" nogil:\n    double fmax(double x, double y)\n    double fmin(double x, double y)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef np.int64_t ray_bbox_intersect(Ray* ray, const BBox bbox) noexcept nogil:\n    '''\n\n    This returns an integer flag that indicates whether a ray and a bounding\n    box intersect. It does not modify either either the ray or the box.\n\n    '''\n\n    # https://tavianator.com/fast-branchless-raybounding-box-intersections/\n\n    cdef np.float64_t tmin = -INF\n    cdef np.float64_t tmax =  INF\n\n    cdef np.int64_t i\n    cdef np.float64_t t1, t2\n    for i in range(3):\n        t1 = (bbox.left_edge[i]  - ray.origin[i])*ray.inv_dir[i]\n        t2 = (bbox.right_edge[i] - ray.origin[i])*ray.inv_dir[i]\n        tmin = fmax(tmin, fmin(t1, t2))\n        tmax = fmin(tmax, fmax(t1, t2))\n\n    return tmax >= fmax(tmin, 0.0)\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef np.int64_t ray_triangle_intersect(const void* primitives,\n                                       const np.int64_t item,\n                                       Ray* ray) noexcept nogil:\n    '''\n\n    This returns an integer flag that indicates whether a triangle is the\n    closest hit for the ray so far. If it is, the ray is updated to store the\n    current triangle index and the distance to the first hit. The triangle used\n    is the one indexed by \"item\" in the array of primitives.\n\n\n    '''\n\n    # https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm\n\n    cdef Triangle tri = (<Triangle*> primitives)[item]\n\n    # edge vectors\n    cdef np.float64_t e1[3]\n    cdef np.float64_t e2[3]\n    subtract(tri.p1, tri.p0, e1)\n    subtract(tri.p2, tri.p0, e2)\n\n    cdef np.float64_t P[3]\n    cross(ray.direction, e2, P)\n\n    cdef np.float64_t det, inv_det\n    det = dot(e1, P)\n    if(det > -DETERMINANT_EPS and det < DETERMINANT_EPS):\n        return False\n    inv_det = 1.0 / det\n\n    cdef np.float64_t T[3]\n    subtract(ray.origin, tri.p0, T)\n\n    cdef np.float64_t u = dot(T, P) * inv_det\n    if(u < 0.0 or u > 1.0):\n        return False\n\n    cdef np.float64_t Q[3]\n    cross(T, e1, Q)\n\n    cdef np.float64_t v = dot(ray.direction, Q) * inv_det\n    if(v < 0.0 or u + v  > 1.0):\n        return False\n\n    cdef np.float64_t t = dot(e2, Q) * inv_det\n\n    if(t > DETERMINANT_EPS and t < ray.t_far):\n        ray.t_far = t\n        ray.elem_id = tri.elem_id\n        return True\n\n    return False\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void triangle_centroid(const void *primitives,\n                            const np.int64_t item,\n                            np.float64_t[3] centroid) noexcept nogil:\n    '''\n\n    This computes the centroid of the input triangle. The triangle used\n    is the one indexed by \"item\" in the array of primitives. The result\n    will be stored in the numpy array passed in as \"centroid\".\n\n    '''\n\n    cdef Triangle tri = (<Triangle*> primitives)[item]\n    cdef np.int64_t i\n    for i in range(3):\n        centroid[i] = (tri.p0[i] + tri.p1[i] + tri.p2[i]) / 3.0\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void triangle_bbox(const void *primitives,\n                        const np.int64_t item,\n                        BBox* bbox) noexcept nogil:\n    '''\n\n    This computes the bounding box of the input triangle. The triangle used\n    is the one indexed by \"item\" in the array of primitives. The result\n    will be stored in the input BBox.\n\n    '''\n\n    cdef Triangle tri = (<Triangle*> primitives)[item]\n    cdef np.int64_t i\n    for i in range(3):\n        bbox.left_edge[i] = fmin(fmin(tri.p0[i], tri.p1[i]), tri.p2[i])\n        bbox.right_edge[i] = fmax(fmax(tri.p0[i], tri.p1[i]), tri.p2[i])\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void patchSurfaceFunc(const cython.floating[8][3] verts,\n                           const cython.floating u,\n                           const cython.floating v,\n                           cython.floating[3] S) noexcept nogil:\n    '''\n\n    This function is a parametric representation of the surface of a bi-quadratic\n    patch. The inputs are the eight nodes that define a face of a 20-node hex element,\n    and two parameters u and v that vary from -1 to 1 and tell you where you are on\n    the surface of the patch. The output is the array 'S' that stores the physical\n    (x, y, z) position of the corresponding point on the patch. This function is needed\n    to compute the intersection of rays and bi-quadratic patches.\n\n    '''\n    cdef int i\n    for i in range(3):\n        S[i] = 0.25*(1.0 - u)*(1.0 - v)*(-u - v - 1)*verts[0][i] + \\\n               0.25*(1.0 + u)*(1.0 - v)*( u - v - 1)*verts[1][i] + \\\n               0.25*(1.0 + u)*(1.0 + v)*( u + v - 1)*verts[2][i] + \\\n               0.25*(1.0 - u)*(1.0 + v)*(-u + v - 1)*verts[3][i] + \\\n               0.5*(1 - u)*(1 - v*v)*verts[4][i] + \\\n               0.5*(1 - u*u)*(1 - v)*verts[5][i] + \\\n               0.5*(1 + u)*(1 - v*v)*verts[6][i] + \\\n               0.5*(1 - u*u)*(1 + v)*verts[7][i]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void patchSurfaceDerivU(const cython.floating[8][3] verts,\n                             const cython.floating u,\n                             const cython.floating v,\n                             cython.floating[3] Su) noexcept nogil:\n    '''\n\n    This function computes the derivative of the S(u, v) function w.r.t u.\n\n    '''\n    cdef int i\n    for i in range(3):\n        Su[i] = (-0.25*(v - 1.0)*(u + v + 1) - 0.25*(u - 1.0)*(v - 1.0))*verts[0][i] + \\\n                (-0.25*(v - 1.0)*(u - v - 1) - 0.25*(u + 1.0)*(v - 1.0))*verts[1][i] + \\\n                ( 0.25*(v + 1.0)*(u + v - 1) + 0.25*(u + 1.0)*(v + 1.0))*verts[2][i] + \\\n                ( 0.25*(v + 1.0)*(u - v + 1) + 0.25*(u - 1.0)*(v + 1.0))*verts[3][i] + \\\n                0.5*(v*v - 1.0)*verts[4][i] + u*(v - 1.0)*verts[5][i] - \\\n                0.5*(v*v - 1.0)*verts[6][i] - u*(v + 1.0)*verts[7][i]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void patchSurfaceDerivV(const cython.floating[8][3] verts,\n                             const cython.floating u,\n                             const cython.floating v,\n                             cython.floating[3] Sv) noexcept nogil:\n    '''\n\n    This function computes the derivative of the S(u, v) function w.r.t v.\n\n    '''\n\n    cdef int i\n    for i in range(3):\n        Sv[i] = (-0.25*(u - 1.0)*(u + v + 1) - 0.25*(u - 1.0)*(v - 1.0))*verts[0][i] + \\\n                (-0.25*(u + 1.0)*(u - v - 1) + 0.25*(u + 1.0)*(v - 1.0))*verts[1][i] + \\\n                ( 0.25*(u + 1.0)*(u + v - 1) + 0.25*(u + 1.0)*(v + 1.0))*verts[2][i] + \\\n                ( 0.25*(u - 1.0)*(u - v + 1) - 0.25*(u - 1.0)*(v + 1.0))*verts[3][i] + \\\n                0.5*(u*u - 1.0)*verts[5][i] + v*(u - 1.0)*verts[4][i] - \\\n                0.5*(u*u - 1.0)*verts[7][i] - v*(u + 1.0)*verts[6][i]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef RayHitData compute_patch_hit(cython.floating[8][3] verts,\n                                  cython.floating[3] ray_origin,\n                                  cython.floating[3] ray_direction) noexcept nogil:\n    \"\"\"\n\n    This function iteratively computes whether the bi-quadratic patch defined by the\n    eight input nodes intersects with the given ray. Either way, information about\n    the potential hit is stored in the returned RayHitData.\n\n    \"\"\"\n    # first we compute the two planes that define the ray.\n    cdef cython.floating[3] n, N1, N2\n    cdef cython.floating A = dot(ray_direction, ray_direction)\n    for i in range(3):\n        n[i] = ray_direction[i] / A\n\n    if ((fabs(n[0]) > fabs(n[1])) and (fabs(n[0]) > fabs(n[2]))):\n        N1[0] = n[1]\n        N1[1] =-n[0]\n        N1[2] = 0.0\n    else:\n        N1[0] = 0.0\n        N1[1] = n[2]\n        N1[2] =-n[1]\n    cross(N1, n, N2)\n\n    cdef cython.floating d1 = -dot(N1, ray_origin)\n    cdef cython.floating d2 = -dot(N2, ray_origin)\n\n    # the initial guess is set to zero\n    cdef cython.floating u = 0.0\n    cdef cython.floating v = 0.0\n    cdef cython.floating[3] S\n    patchSurfaceFunc(verts, u, v, S)\n    cdef cython.floating fu = dot(N1, S) + d1\n    cdef cython.floating fv = dot(N2, S) + d2\n    cdef cython.floating err = fmax(fabs(fu), fabs(fv))\n\n    # begin Newton iteration\n    cdef cython.floating tol = 1.0e-5\n    cdef int iterations = 0\n    cdef int max_iter = 10\n    cdef cython.floating[3] Su\n    cdef cython.floating[3] Sv\n    cdef cython.floating J11, J12, J21, J22, det\n    while ((err > tol) and (iterations < max_iter)):\n        # compute the Jacobian\n        patchSurfaceDerivU(verts, u, v, Su)\n        patchSurfaceDerivV(verts, u, v, Sv)\n        J11 = dot(N1, Su)\n        J12 = dot(N1, Sv)\n        J21 = dot(N2, Su)\n        J22 = dot(N2, Sv)\n        det = (J11*J22 - J12*J21)\n\n        # update the u, v values\n        u -= ( J22*fu - J12*fv) / det\n        v -= (-J21*fu + J11*fv) / det\n\n        patchSurfaceFunc(verts, u, v, S)\n        fu = dot(N1, S) + d1\n        fv = dot(N2, S) + d2\n\n        err = fmax(fabs(fu), fabs(fv))\n        iterations += 1\n\n    # t is the distance along the ray to this hit\n    cdef cython.floating t = distance(S, ray_origin) / L2_norm(ray_direction)\n\n    # return hit data\n    cdef RayHitData hd\n    hd.u = u\n    hd.v = v\n    hd.t = t\n    hd.converged = (iterations < max_iter)\n    return hd\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef np.int64_t ray_patch_intersect(const void* primitives,\n                                    const np.int64_t item,\n                                    Ray* ray) noexcept nogil:\n    '''\n\n    This returns an integer flag that indicates whether the given patch is the\n    closest hit for the ray so far. If it is, the ray is updated to store the\n    current primitive index and the distance to the first hit. The patch used\n    is the one indexed by \"item\" in the array of primitives.\n\n\n    '''\n    cdef Patch patch = (<Patch*> primitives)[item]\n\n    cdef RayHitData hd = compute_patch_hit(patch.v, ray.origin, ray.direction)\n\n    # only count this is it's the closest hit\n    if (hd.t < ray.t_near or hd.t > ray.t_far):\n        return False\n\n    if (fabs(hd.u) <= 1.0 and fabs(hd.v) <= 1.0 and hd.converged):\n        # we have a hit, so update ray information\n        ray.t_far = hd.t\n        ray.elem_id = patch.elem_id\n        return True\n\n    return False\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void patch_centroid(const void *primitives,\n                         const np.int64_t item,\n                         np.float64_t[3] centroid) noexcept nogil:\n    '''\n\n    This computes the centroid of the input patch. The patch used\n    is the one indexed by \"item\" in the array of primitives. The result\n    will be stored in the numpy array passed in as \"centroid\".\n\n    '''\n\n    cdef np.int64_t i, j\n    cdef Patch patch = (<Patch*> primitives)[item]\n\n    for j in range(3):\n        centroid[j] = 0.0\n\n    for i in range(8):\n        for j in range(3):\n            centroid[j] += patch.v[i][j]\n\n    for j in range(3):\n        centroid[j] /= 8.0\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void patch_bbox(const void *primitives,\n                    const np.int64_t item,\n                     BBox* bbox) noexcept nogil:\n\n    '''\n\n    This computes the bounding box of the input patch. The patch used\n    is the one indexed by \"item\" in the array of primitives. The result\n    will be stored in the input BBox.\n\n    '''\n\n    cdef np.int64_t i, j\n    cdef Patch patch = (<Patch*> primitives)[item]\n\n    for j in range(3):\n        bbox.left_edge[j] = patch.v[0][j]\n        bbox.right_edge[j] = patch.v[0][j]\n\n    for i in range(1, 8):\n        for j in range(3):\n            bbox.left_edge[j] = fmin(bbox.left_edge[j], patch.v[i][j])\n            bbox.right_edge[j] = fmax(bbox.right_edge[j], patch.v[i][j])\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void tet_patchSurfaceFunc(const cython.floating[6][3] verts,\n                           const cython.floating u,\n                           const cython.floating v,\n                           cython.floating[3] S) noexcept nogil:\n\n    cdef int i\n    # Computes for canonical triangle coordinates\n    for i in range(3):\n        S[i] = (1.0 - 3.0*u + 2.0*u*u - 3.0*v + 2.0*v*v + 4.0*u*v)*verts[0][i] + \\\n             (-u + 2.0*u*u)*verts[1][i] + \\\n             (-v + 2.0*v*v)*verts[2][i] + \\\n             (4.0*u - 4.0*u*u - 4.0*u*v)*verts[3][i] + \\\n             (4.0*u*v)*verts[4][i] + \\\n             (4.0*v - 4.0*v*v - 4.0*u*v)*verts[5][i]\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void tet_patchSurfaceDerivU(const cython.floating[6][3] verts,\n                             const cython.floating u,\n                             const cython.floating v,\n                             cython.floating[3] Su) noexcept nogil:\n    cdef int i\n    # Computes for canonical triangle coordinates\n    for i in range(3):\n        Su[i] = (-3.0 + 4.0*u + 4.0*v)*verts[0][i] + \\\n             (-1.0 + 4.0*u)*verts[1][i] + \\\n             (4.0 - 8.0*u - 4.0*v)*verts[3][i] + \\\n             (4.0*v)*verts[4][i] + \\\n             (-4.0*v)*verts[5][i]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void tet_patchSurfaceDerivV(const cython.floating[6][3] verts,\n                             const cython.floating u,\n                             const cython.floating v,\n                             cython.floating[3] Sv) noexcept nogil:\n\n    cdef int i\n    # Computes for canonical triangle coordinates\n    for i in range(3):\n        Sv[i] = (-3.0 + 4.0*v + 4.0*u)*verts[0][i] + \\\n             (-1.0 + 4.0*v)*verts[2][i] + \\\n             (-4.0*u)*verts[3][i] + \\\n             (4.0*u)*verts[4][i] + \\\n             (4.0 - 8.0*v - 4.0*u)*verts[5][i]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef RayHitData compute_tet_patch_hit(cython.floating[6][3] verts,\n                                  cython.floating[3] ray_origin,\n                                  cython.floating[3] ray_direction) noexcept nogil:\n\n    # first we compute the two planes that define the ray.\n    cdef cython.floating[3] n, N1, N2\n    cdef cython.floating A = dot(ray_direction, ray_direction)\n    for i in range(3):\n        n[i] = ray_direction[i] / A\n\n    if ((fabs(n[0]) > fabs(n[1])) and (fabs(n[0]) > fabs(n[2]))):\n        N1[0] = n[1]\n        N1[1] =-n[0]\n        N1[2] = 0.0\n    else:\n        N1[0] = 0.0\n        N1[1] = n[2]\n        N1[2] =-n[1]\n    cross(N1, n, N2)\n\n    cdef cython.floating d1 = -dot(N1, ray_origin)\n    cdef cython.floating d2 = -dot(N2, ray_origin)\n\n    # the initial guess is set to zero\n    cdef cython.floating u = 0.0\n    cdef cython.floating v = 0.0\n    cdef cython.floating[3] S\n    tet_patchSurfaceFunc(verts, u, v, S)\n    cdef cython.floating fu = dot(N1, S) + d1\n    cdef cython.floating fv = dot(N2, S) + d2\n    cdef cython.floating err = fmax(fabs(fu), fabs(fv))\n\n    # begin Newton iteration\n    cdef cython.floating tol = 1.0e-5\n    cdef int iterations = 0\n    cdef int max_iter = 10\n    cdef cython.floating[3] Su\n    cdef cython.floating[3] Sv\n    cdef cython.floating J11, J12, J21, J22, det\n    while ((err > tol) and (iterations < max_iter)):\n        # compute the Jacobian\n        tet_patchSurfaceDerivU(verts, u, v, Su)\n        tet_patchSurfaceDerivV(verts, u, v, Sv)\n        J11 = dot(N1, Su)\n        J12 = dot(N1, Sv)\n        J21 = dot(N2, Su)\n        J22 = dot(N2, Sv)\n        det = (J11*J22 - J12*J21)\n\n        # update the u, v values\n        u -= ( J22*fu - J12*fv) / det\n        v -= (-J21*fu + J11*fv) / det\n\n        tet_patchSurfaceFunc(verts, u, v, S)\n        fu = dot(N1, S) + d1\n        fv = dot(N2, S) + d2\n\n        err = fmax(fabs(fu), fabs(fv))\n        iterations += 1\n\n    # t is the distance along the ray to this hit\n    cdef cython.floating t = distance(S, ray_origin) / L2_norm(ray_direction)\n\n    # return hit data\n    cdef RayHitData hd\n    hd.u = u\n    hd.v = v\n    hd.t = t\n    hd.converged = (iterations < max_iter)\n    return hd\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef np.int64_t ray_tet_patch_intersect(const void* primitives,\n                                    const np.int64_t item,\n                                    Ray* ray) noexcept nogil:\n\n    cdef TetPatch tet_patch = (<TetPatch*> primitives)[item]\n\n    cdef RayHitData hd = compute_tet_patch_hit(tet_patch.v, ray.origin, ray.direction)\n\n    # only count this is it's the closest hit\n    if (hd.t < ray.t_near or hd.t > ray.t_far):\n        return False\n\n    if (0 <= hd.u and 0 <= hd.v and hd.u + hd.v <= 1.0 and hd.converged):\n        # we have a hit, so update ray information\n        ray.t_far = hd.t\n        ray.elem_id = tet_patch.elem_id\n        return True\n\n    return False\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void tet_patch_centroid(const void *primitives,\n                         const np.int64_t item,\n                         np.float64_t[3] centroid) noexcept nogil:\n\n    cdef np.int64_t i, j\n    cdef TetPatch tet_patch = (<TetPatch*> primitives)[item]\n\n    for j in range(3):\n        centroid[j] = 0.0\n\n    for i in range(6):\n        for j in range(3):\n            centroid[j] += tet_patch.v[i][j]\n\n    for j in range(3):\n        centroid[j] /= 6.0\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef void tet_patch_bbox(const void *primitives,\n                    const np.int64_t item,\n                    BBox* bbox) noexcept nogil:\n\n    cdef np.int64_t i, j\n    cdef TetPatch tet_patch = (<TetPatch*> primitives)[item]\n\n    for j in range(3):\n        bbox.left_edge[j] = tet_patch.v[0][j]\n        bbox.right_edge[j] = tet_patch.v[0][j]\n\n    for i in range(1, 6):\n        for j in range(3):\n            bbox.left_edge[j] = fmin(bbox.left_edge[j], tet_patch.v[i][j])\n            bbox.right_edge[j] = fmax(bbox.right_edge[j], tet_patch.v[i][j])\n"
  },
  {
    "path": "yt/utilities/lib/quad_tree.pyx",
    "content": "\n# distutils: libraries = STD_LIBS\n\"\"\"\nA refine-by-two AMR-specific quadtree\n\n\n\n\"\"\"\n\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\nfrom libc.stdlib cimport free, malloc\n\nfrom yt.utilities.lib.fp_utils cimport fmax, fmin\n\nfrom yt.utilities.exceptions import YTIntDomainOverflow\n\n\ncdef extern from \"platform_dep.h\":\n    # NOTE that size_t might not be int\n    void *alloca(int)\n\ncdef struct QuadTreeNode:\n    np.float64_t *val\n    np.float64_t weight_val\n    np.int64_t pos[2]\n    QuadTreeNode *children[2][2]\n\nctypedef void QTN_combine(QuadTreeNode *self,\n        np.float64_t *val, np.float64_t weight_val,\n        int nvals)\n\ncdef void QTN_add_value(QuadTreeNode *self,\n        np.float64_t *val, np.float64_t weight_val,\n        int nvals):\n    cdef int i\n    for i in range(nvals):\n        self.val[i] += val[i]\n    self.weight_val += weight_val\n\ncdef void QTN_max_value(QuadTreeNode *self,\n        np.float64_t *val, np.float64_t weight_val,\n        int nvals):\n    cdef int i\n    for i in range(nvals):\n        self.val[i] = fmax(val[i], self.val[i])\n    self.weight_val = 1.0\n\ncdef void QTN_min_value(QuadTreeNode *self,\n        np.float64_t *val, np.float64_t weight_val,\n        int nvals):\n    cdef int i\n    #cdef np.float64_t *big_num = 1.0\n    #big_num = 1.0 #1e10\n    for i in range(nvals):\n        if self.val[i] == 0:\n          self.val[i] = 1e50\n        # end if\n        self.val[i] = fmin(val[i], self.val[i])\n    self.weight_val = 1.0\n\ncdef void QTN_refine(QuadTreeNode *self, int nvals):\n    cdef int i, j\n    cdef np.int64_t npos[2]\n    cdef np.float64_t *tvals = <np.float64_t *> alloca(\n            sizeof(np.float64_t) * nvals)\n    for i in range(nvals): tvals[i] = 0.0\n    for i in range(2):\n        npos[0] = self.pos[0] * 2 + i\n        for j in range(2):\n            npos[1] = self.pos[1] * 2 + j\n            # We have to be careful with allocation...\n            self.children[i][j] = QTN_initialize(\n                        npos, nvals, tvals, 0.0)\n\ncdef QuadTreeNode *QTN_initialize(np.int64_t pos[2], int nvals,\n                        np.float64_t *val, np.float64_t weight_val):\n    cdef QuadTreeNode *node\n    cdef int i, j\n    node = <QuadTreeNode *> malloc(sizeof(QuadTreeNode))\n    node.pos[0] = pos[0]\n    node.pos[1] = pos[1]\n    node.val = <np.float64_t *> malloc(\n                nvals * sizeof(np.float64_t))\n    for i in range(2):\n        for j in range(2):\n            node.children[i][j] = NULL\n    if val != NULL:\n        for i in range(nvals):\n            node.val[i] = val[i]\n        node.weight_val = weight_val\n    return node\n\ncdef void QTN_free(QuadTreeNode *node):\n    cdef int i, j\n    for i in range(2):\n        for j in range(2):\n            if node.children[i][j] == NULL: continue\n            QTN_free(node.children[i][j])\n    free(node.val)\n    free(node)\n\ncdef class QuadTree:\n    cdef int nvals\n    cdef QuadTreeNode ***root_nodes\n    cdef public np.int64_t top_grid_dims[2]\n    cdef int merged\n    cdef int num_cells\n    cdef QTN_combine *combine\n    cdef np.float64_t bounds[4]\n    cdef np.float64_t dds[2]\n    cdef np.int64_t last_dims[2]\n    cdef int max_level\n\n    def __cinit__(self, np.ndarray[np.int64_t, ndim=1] top_grid_dims,\n                  int nvals, bounds, method = \"integrate\"):\n        if method == \"integrate\":\n            self.combine = QTN_add_value\n        elif method == \"mip\" or \\\n             method == \"max\":\n            self.combine = QTN_max_value\n        elif method == \"min\":\n            self.combine = QTN_min_value\n        else:\n            raise NotImplementedError(f\"Unknown projection method {self.method!r}\")\n        self.merged = 1\n        self.max_level = 0\n        cdef int i, j\n        cdef np.int64_t pos[2]\n        cdef np.float64_t *vals = <np.float64_t *> malloc(\n                sizeof(np.float64_t)*nvals)\n        cdef np.float64_t weight_val = 0.0\n        self.nvals = nvals\n        for i in range(nvals): vals[i] = 0.0\n        for i in range(4):\n            self.bounds[i] = bounds[i]\n\n        self.top_grid_dims[0] = top_grid_dims[0]\n        self.top_grid_dims[1] = top_grid_dims[1]\n        self.dds[0] = (self.bounds[1] - self.bounds[0])/self.top_grid_dims[0]\n        self.dds[1] = (self.bounds[3] - self.bounds[2])/self.top_grid_dims[1]\n\n        self.root_nodes = <QuadTreeNode ***> \\\n            malloc(sizeof(QuadTreeNode **) * top_grid_dims[0])\n\n        # We initialize our root values to 0.0.\n        for i in range(top_grid_dims[0]):\n            pos[0] = i\n            self.root_nodes[i] = <QuadTreeNode **> \\\n                malloc(sizeof(QuadTreeNode *) * top_grid_dims[1])\n            for j in range(top_grid_dims[1]):\n                pos[1] = j\n                self.root_nodes[i][j] = QTN_initialize(\n                    pos, nvals, vals, weight_val)\n        self.num_cells = self.top_grid_dims[0] * self.top_grid_dims[1]\n        free(vals)\n\n    cdef int count_total_cells(self, QuadTreeNode *root):\n        cdef int total = 0\n        cdef int i, j\n        if root.children[0][0] == NULL: return 1\n        for i in range(2):\n            for j in range(2):\n                total += self.count_total_cells(root.children[i][j])\n        return total + 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int fill_buffer(self, QuadTreeNode *root, int curpos,\n                          np.ndarray[np.int32_t, ndim=1] refined,\n                          np.ndarray[np.float64_t, ndim=2] values,\n                          np.ndarray[np.float64_t, ndim=1] wval):\n        cdef int i, j\n        for i in range(self.nvals):\n            values[curpos, i] = root.val[i]\n        wval[curpos] = root.weight_val\n        if root.children[0][0] != NULL: refined[curpos] = 1\n        else: return curpos+1\n        curpos += 1\n        for i in range(2):\n            for j in range(2):\n                curpos = self.fill_buffer(root.children[i][j], curpos,\n                                 refined, values, wval)\n        return curpos\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    cdef int unfill_buffer(self, QuadTreeNode *root, int curpos,\n                          np.ndarray[np.int32_t, ndim=1] refined,\n                          np.ndarray[np.float64_t, ndim=2] values,\n                          np.ndarray[np.float64_t, ndim=1] wval):\n        cdef int i, j\n        for i in range(self.nvals):\n            root.val[i] = values[curpos, i]\n        root.weight_val = wval[curpos]\n        if refined[curpos] == 0: return curpos+1\n        curpos += 1\n        cdef QuadTreeNode *child\n        cdef np.int64_t pos[2]\n        for i in range(2):\n            for j in range(2):\n                pos[0] = root.pos[0]*2 + i\n                pos[1] = root.pos[1]*2 + j\n                child = QTN_initialize(pos, self.nvals, NULL, 0.0)\n                root.children[i][j] = child\n                curpos = self.unfill_buffer(child, curpos, refined, values, wval)\n        return curpos\n\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def frombuffer(self, np.ndarray[np.int32_t, ndim=1] refined,\n                         np.ndarray[np.float64_t, ndim=2] values,\n                         np.ndarray[np.float64_t, ndim=1] wval,\n                         method):\n        if method == \"mip\" or method == \"max\" or method == -1:\n            self.merged = -1\n        if method == \"min\" or method == -2:\n            self.merged = -2\n        elif method == \"integrate\" or method == 1:\n            self.merged = 1\n        cdef int curpos = 0\n        self.num_cells = wval.shape[0]\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                curpos = self.unfill_buffer(self.root_nodes[i][j], curpos,\n                                 refined, values, wval)\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def tobuffer(self):\n        cdef int total = self.num_cells\n        # We now have four buffers:\n        # Refined or not (total,) int32\n        # Values in each node (total, nvals) float64\n        # Weight values in each node (total,) float64\n        cdef np.ndarray[np.int32_t, ndim=1] refined\n        refined = np.zeros(total, dtype='int32')\n        cdef np.ndarray[np.float64_t, ndim=2] values\n        values = np.zeros((total, self.nvals), dtype='float64')\n        cdef np.ndarray[np.float64_t, ndim=1] wval\n        wval = np.zeros(total, dtype='float64')\n        cdef int curpos = 0\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                curpos = self.fill_buffer(self.root_nodes[i][j], curpos,\n                                 refined, values, wval)\n        return (refined, values, wval)\n\n    def get_args(self):\n        return (self.top_grid_dims[0], self.top_grid_dims[1], self.nvals)\n\n    cdef int add_to_position(self,\n                 int level, np.int64_t pos[2],\n                 np.float64_t *val,\n                 np.float64_t weight_val, int skip = 0):\n        cdef int i, j, L\n        cdef QuadTreeNode *node\n        node = self.find_on_root_level(pos, level)\n        if node == NULL:\n            return -1\n        if level > self.max_level:\n            self.max_level = level\n        for L in range(level):\n            if node.children[0][0] == NULL:\n                QTN_refine(node, self.nvals)\n                self.num_cells += 4\n            # Maybe we should use bitwise operators?\n            i = (pos[0] >> (level - L - 1)) & 1\n            j = (pos[1] >> (level - L - 1)) & 1\n            node = node.children[i][j]\n        if skip == 1: return 0\n        self.combine(node, val, weight_val, self.nvals)\n        return 0\n\n    @cython.cdivision(True)\n    cdef QuadTreeNode *find_on_root_level(self, np.int64_t pos[2], int level):\n        # We need this because the root level won't just have four children\n        # So we find on the root level, then we traverse the tree.\n        cdef np.int64_t i, j\n        i = pos[0] >> level\n        j = pos[1] >> level\n        if i >= self.top_grid_dims[0] or i < 0 or \\\n           j >= self.top_grid_dims[1] or j < 0:\n            self.last_dims[0] = i\n            self.last_dims[1] = j\n            return NULL\n        return self.root_nodes[i][j]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def add_array_to_tree(self, int level, np.ndarray[np.int64_t, ndim=1] pxs,\n            np.ndarray[np.int64_t, ndim=1] pys,\n            np.ndarray[np.float64_t, ndim=2] pvals,\n            np.ndarray[np.float64_t, ndim=1] pweight_vals,\n            int skip = 0):\n        cdef int p\n        cdef np.float64_t *vals\n        cdef np.float64_t *data = <np.float64_t *> pvals.data\n        cdef np.int64_t pos[2]\n        for p in range(pxs.shape[0]):\n            vals = data + self.nvals*p\n            pos[0] = pxs[p]\n            pos[1] = pys[p]\n            self.add_to_position(level, pos, vals, pweight_vals[p], skip)\n        return\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def add_chunk_to_tree(self,\n            np.ndarray[np.int64_t, ndim=1] pxs,\n            np.ndarray[np.int64_t, ndim=1] pys,\n            np.ndarray[np.int64_t, ndim=1] level,\n            np.ndarray[np.float64_t, ndim=2] pvals,\n            np.ndarray[np.float64_t, ndim=1] pweight_vals):\n        cdef int ps = pxs.shape[0]\n        cdef int p, rv\n        cdef np.float64_t *vals\n        cdef np.float64_t *data = <np.float64_t *> pvals.data\n        cdef np.int64_t pos[2]\n        for p in range(ps):\n            vals = data + self.nvals*p\n            pos[0] = pxs[p]\n            pos[1] = pys[p]\n            rv = self.add_to_position(level[p], pos, vals, pweight_vals[p])\n            if rv == -1:\n                raise YTIntDomainOverflow(\n                    (self.last_dims[0], self.last_dims[1]),\n                    (self.top_grid_dims[0], self.top_grid_dims[1]))\n        return\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def initialize_chunk(self,\n            np.ndarray[np.int64_t, ndim=1] pxs,\n            np.ndarray[np.int64_t, ndim=1] pys,\n            np.ndarray[np.int64_t, ndim=1] level):\n        cdef int num = pxs.shape[0]\n        cdef int p, rv\n        cdef np.int64_t pos[2]\n        for p in range(num):\n            pos[0] = pxs[p]\n            pos[1] = pys[p]\n            rv = self.add_to_position(level[p], pos, NULL, 0.0, 1)\n            if rv == -1:\n                raise YTIntDomainOverflow(\n                    (self.last_dims[0], self.last_dims[1]),\n                    (self.top_grid_dims[0], self.top_grid_dims[1]))\n        return\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def get_all(self, int count_only = 0, int method = 1):\n        cdef int i, j, vi\n        cdef int total = 0\n        self.merged = method\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                total += self.count(self.root_nodes[i][j])\n        if count_only: return total\n        # Allocate our array\n        cdef np.ndarray[np.int64_t, ndim=1] oix\n        cdef np.ndarray[np.int64_t, ndim=1] oiy\n        cdef np.ndarray[np.int64_t, ndim=1] oires\n        cdef np.ndarray[np.float64_t, ndim=2] nvals\n        cdef np.ndarray[np.float64_t, ndim=1] nwvals\n        oix = np.zeros(total, dtype='int64')\n        oiy = np.zeros(total, dtype='int64')\n        oires = np.zeros(total, dtype='int64')\n        nvals = np.zeros( (total, self.nvals), dtype='float64')\n        nwvals = np.zeros( total, dtype='float64')\n        cdef np.int64_t curpos = 0\n        cdef np.int64_t *ix = <np.int64_t *> oix.data\n        cdef np.int64_t *iy = <np.int64_t *> oiy.data\n        cdef np.int64_t *ires = <np.int64_t *> oires.data\n        cdef np.float64_t *vdata = <np.float64_t *> nvals.data\n        cdef np.float64_t *wdata = <np.float64_t *> nwvals.data\n        cdef np.float64_t wtoadd\n        cdef np.float64_t *vtoadd = <np.float64_t *> malloc(\n                sizeof(np.float64_t)*self.nvals)\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                for vi in range(self.nvals): vtoadd[vi] = 0.0\n                wtoadd = 0.0\n                curpos += self.fill(self.root_nodes[i][j],\n                    curpos, ix, iy, ires, vdata, wdata, vtoadd, wtoadd, 0)\n        free(vtoadd)\n        return oix, oiy, oires, nvals, nwvals\n\n    cdef int count(self, QuadTreeNode *node):\n        cdef int i, j\n        cdef int count = 0\n        if node.children[0][0] == NULL: return 1\n        for i in range(2):\n            for j in range(2):\n                count += self.count(node.children[i][j])\n        return count\n\n    @cython.cdivision(True)\n    cdef int fill(self, QuadTreeNode *node,\n                        np.int64_t curpos,\n                        np.int64_t *ix,\n                        np.int64_t *iy,\n                        np.int64_t *ires,\n                        np.float64_t *vdata,\n                        np.float64_t *wdata,\n                        np.float64_t *vtoadd,\n                        np.float64_t wtoadd,\n                        np.int64_t level):\n        cdef int i, j, n\n        if node.children[0][0] == NULL:\n            if self.merged == -1:\n                for i in range(self.nvals):\n                    vdata[self.nvals * curpos + i] = fmax(node.val[i], vtoadd[i])\n            elif self.merged == -2:\n                for i in range(self.nvals):\n                    vdata[self.nvals * curpos + i] = fmin(node.val[i], vtoadd[i])\n                wdata[curpos] = 1.0\n            else:\n                for i in range(self.nvals):\n                    vdata[self.nvals * curpos + i] = node.val[i] + vtoadd[i]\n                wdata[curpos] = node.weight_val + wtoadd\n            ires[curpos] = level\n            ix[curpos] = node.pos[0]\n            iy[curpos] = node.pos[1]\n            return 1\n        cdef np.int64_t added = 0\n        cdef np.float64_t *vorig\n        vorig = <np.float64_t *> malloc(sizeof(np.float64_t) * self.nvals)\n        if self.merged == 1:\n            for i in range(self.nvals):\n                vorig[i] = vtoadd[i]\n                vtoadd[i] += node.val[i]\n            wtoadd += node.weight_val\n        elif self.merged == -1 or self.merged == -2:\n            for i in range(self.nvals):\n                vtoadd[i] = node.val[i]\n        for i in range(2):\n            for j in range(2):\n                if self.merged == -1:\n                    for n in range(self.nvals):\n                        vtoadd[n] = node.val[n]\n                added += self.fill(node.children[i][j],\n                        curpos + added, ix, iy, ires, vdata, wdata,\n                        vtoadd, wtoadd, level + 1)\n        if self.merged == 1:\n            for i in range(self.nvals):\n                vtoadd[i] = vorig[i]\n            wtoadd -= node.weight_val\n        free(vorig)\n        return added\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    def fill_image(self, np.ndarray[np.float64_t, ndim=2] buffer, _bounds,\n                   int val_index = 0, int weighted = 0):\n        cdef np.float64_t pos[2]\n        cdef np.float64_t dds[2]\n        cdef int nn[2]\n        cdef int i, j\n        cdef np.float64_t bounds[4]\n        cdef np.float64_t opos[4]\n        cdef np.float64_t weight = 0.0, value = 0.0\n        cdef np.float64_t *wval = NULL\n        if weighted == 1:\n            wval = &weight\n        for i in range(4):\n            bounds[i] = _bounds[i]\n        for i in range(2):\n            nn[i] = buffer.shape[i]\n            dds[i] = (bounds[i*2 + 1] - bounds[i*2])/nn[i]\n        pos[0] = bounds[0]\n        opos[0] = opos[1] = pos[0] + dds[0]\n        for i in range(nn[0]):\n            pos[1] = bounds[2]\n            opos[2] = opos[3] = pos[1] + dds[1]\n            for j in range(nn[1]):\n                # We start at level zero.  In the future we could optimize by\n                # retaining oct information from previous cells.\n                if not ((opos[0] <= pos[0] <= opos[1]) and\n                        (opos[2] <= pos[1] <= opos[3])):\n                    value = self.find_value_at_pos(pos, val_index,\n                                        opos, wval)\n                buffer[i,j] = value\n                if weighted == 1:\n                    buffer[i,j] /= weight\n                pos[1] += dds[1]\n            pos[0] += dds[0]\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    @cython.cdivision(True)\n    cdef np.float64_t find_value_at_pos(self, np.float64_t pos[2],\n                                         int val_index, np.float64_t opos[4],\n                                         np.float64_t *wval = NULL):\n        cdef int i\n        cdef np.int64_t ind[2]\n        cdef np.float64_t cp[2]\n        cdef np.float64_t dds[2]\n        cdef np.float64_t value = 0.0\n        cdef np.float64_t weight = 0.0\n        cdef QuadTreeNode *cur\n        for i in range(2):\n            ind[i] = <np.int64_t> (pos[i]/self.dds[i])\n            cp[i] = (ind[i] + 0.5) * self.dds[i]\n            dds[i] = self.dds[i]\n        cur = self.root_nodes[ind[0]][ind[1]]\n        value += cur.val[val_index]\n        weight += cur.weight_val\n        while cur.children[0][0] != NULL:\n            for i in range(2):\n                # Note that below offset by half a dx for center, after\n                # updating to the next level\n                dds[i] = dds[i] * 0.5\n                if cp[i] >= pos[i]:\n                    ind[i] = 0\n                    cp[i] -= dds[i] * 0.5\n                else:\n                    ind[i] = 1\n                    cp[i] += dds[i] * 0.5\n            cur = cur.children[ind[0]][ind[1]]\n            value += cur.val[val_index]\n            weight += cur.weight_val\n        opos[0] = cp[0] - dds[0] * 0.5\n        opos[1] = cp[0] + dds[0] * 0.5\n        opos[2] = cp[1] - dds[1] * 0.5\n        opos[3] = cp[1] + dds[1] * 0.5\n        if wval != NULL:\n            wval[0] = weight\n        return value\n\n    def __dealloc__(self):\n        cdef int i, j\n        for i in range(self.top_grid_dims[0]):\n            for j in range(self.top_grid_dims[1]):\n                QTN_free(self.root_nodes[i][j])\n            free(self.root_nodes[i])\n        free(self.root_nodes)\n\ncdef void QTN_merge_nodes(QuadTreeNode *n1, QuadTreeNode *n2, int nvals,\n                          QTN_combine *func):\n    # We have four choices when merging nodes.\n    # 1. If both nodes have no refinement, then we add values of n2 to n1.\n    # 2. If both have refinement, we call QTN_merge_nodes on all four children.\n    # 3. If n2 has refinement and n1 does not, we detach n2's children and\n    #    attach them to n1.\n    # 4. If n1 has refinement and n2 does not, we add the value of n2 to n1.\n    cdef int i, j\n\n    func(n1, n2.val, n2.weight_val, nvals)\n    if n1.children[0][0] == n2.children[0][0] == NULL:\n        pass\n    elif n1.children[0][0] != NULL and n2.children[0][0] != NULL:\n        for i in range(2):\n            for j in range(2):\n                QTN_merge_nodes(n1.children[i][j], n2.children[i][j], nvals, func)\n    elif n1.children[0][0] == NULL and n2.children[0][0] != NULL:\n        for i in range(2):\n            for j in range(2):\n                n1.children[i][j] = n2.children[i][j]\n                n2.children[i][j] = NULL\n    elif n1.children[0][0] != NULL and n2.children[0][0] == NULL:\n        pass\n    else:\n        raise RuntimeError\n\ndef merge_quadtrees(QuadTree qt1, QuadTree qt2, method = 1):\n    cdef int i, j\n    qt1.num_cells = 0\n    cdef QTN_combine *func\n    if method == 1:\n        qt1.merged = 1\n        func = QTN_add_value\n    elif method == -1:\n        qt1.merged = -1\n        func = QTN_max_value\n    elif method == -2:\n        qt1.merged = -2\n        func = QTN_min_value\n    else:\n        raise NotImplementedError\n    if qt1.merged != 0 or qt2.merged != 0:\n        assert(qt1.merged == qt2.merged)\n    for i in range(qt1.top_grid_dims[0]):\n        for j in range(qt1.top_grid_dims[1]):\n            QTN_merge_nodes(qt1.root_nodes[i][j],\n                            qt2.root_nodes[i][j],\n                            qt1.nvals, func)\n            qt1.num_cells += qt1.count_total_cells(\n                                qt1.root_nodes[i][j])\n"
  },
  {
    "path": "yt/utilities/lib/ragged_arrays.pyx",
    "content": "\"\"\"\nSome simple operations for operating on ragged arrays\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\n\ncdef fused numpy_dt:\n    np.float32_t\n    np.float64_t\n    np.int32_t\n    np.int64_t\n\ncdef numpy_dt r_min(numpy_dt a, numpy_dt b):\n    if a < b: return a\n    return b\n\ncdef numpy_dt r_max(numpy_dt a, numpy_dt b):\n    if a > b: return a\n    return b\n\ncdef numpy_dt r_add(numpy_dt a, numpy_dt b):\n    return a + b\n\ncdef numpy_dt r_subtract(numpy_dt a, numpy_dt b):\n    return a - b\n\ncdef numpy_dt r_multiply(numpy_dt a, numpy_dt b):\n    return a * b\n\n@cython.cdivision(True)\ncdef numpy_dt r_divide(numpy_dt a, numpy_dt b):\n    return a / b\n\ndef index_unop(np.ndarray[numpy_dt, ndim=1] values,\n              np.ndarray[np.int64_t, ndim=1] indices,\n              np.ndarray[np.int64_t, ndim=1] sizes,\n              operation):\n    cdef numpy_dt mi, ma\n    if numpy_dt == np.float32_t:\n        dt = \"float32\"\n        mi = np.finfo(dt).min\n        ma = np.finfo(dt).max\n    elif numpy_dt == np.float64_t:\n        dt = \"float64\"\n        mi = np.finfo(dt).min\n        ma = np.finfo(dt).max\n    elif numpy_dt == np.int32_t:\n        dt = \"int32\"\n        mi = np.iinfo(dt).min\n        ma = np.iinfo(dt).max\n    elif numpy_dt == np.int64_t:\n        dt = \"int64\"\n        mi = np.iinfo(dt).min\n        ma = np.iinfo(dt).max\n    cdef np.ndarray[numpy_dt] out_values = np.zeros(sizes.size, dtype=dt)\n    cdef numpy_dt (*func)(numpy_dt a, numpy_dt b)\n    # Now we figure out our function.  At present, we only allow addition and\n    # multiplication, because they are commutative and easy to bootstrap.\n    cdef numpy_dt ival, val\n    if operation == \"sum\":\n        ival = 0\n        func = r_add\n    elif operation == \"prod\":\n        ival = 1\n        func = r_multiply\n    elif operation == \"max\":\n        ival = mi\n        func = r_max\n    elif operation == \"min\":\n        ival = ma\n        func = r_min\n    else:\n        raise NotImplementedError\n    cdef np.int64_t i, ind_ind, ind_arr\n    ind_ind = 0\n    for i in range(sizes.size):\n        # Each entry in sizes is the size of the array\n        val = ival\n        for _ in range(sizes[i]):\n            ind_arr = indices[ind_ind]\n            val = func(val, values[ind_arr])\n            ind_ind += 1\n        out_values[i] = val\n    return out_values\n"
  },
  {
    "path": "yt/utilities/lib/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/utilities/lib/tests/test_allocation_container.py",
    "content": "from numpy.testing import assert_array_equal, assert_equal\n\nfrom yt.utilities.lib.allocation_container import BitmaskPool\n\n\ndef test_bitmask_pool():\n    bmp = BitmaskPool()\n    assert_equal(len(bmp), 0)\n    bmp.append(100)\n    assert_equal(len(bmp), 1)\n    assert_equal(bmp[0].size, 100)\n    bmp.append(200)\n    assert_equal(len(bmp), 2)\n    assert_equal(bmp[0].size, 100)\n    assert_equal(bmp[1].size, 200)\n    assert_equal(sum(_.size for _ in bmp.to_arrays()), 300)\n    arrs = bmp.to_arrays()\n    assert_equal(arrs[0].size, 100)\n    assert_equal(arrs[1].size, 200)\n    arrs[0][:] = 1\n    arrs = bmp.to_arrays()\n    assert_array_equal(arrs[0], 1)\n"
  },
  {
    "path": "yt/utilities/lib/tests/test_alt_ray_tracers.py",
    "content": "\"\"\"Tests for non-cartesian ray tracers.\"\"\"\n\nimport numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import amrspace\nfrom yt.utilities.lib.alt_ray_tracers import _cyl2cart, cylindrical_ray_trace\n\nleft_grid = right_grid = amr_levels = center_grid = data = None\nold_settings = None\n\n\ndef setup_module():\n    # set up some sample cylindrical grid data, radiating out from center\n    global left_grid, right_grid, amr_levels, center_grid, data, old_settings\n    old_settings = np.geterr()\n    np.seterr(all=\"ignore\")\n    l1, r1, lvl1 = amrspace([0.0, 1.0, 0.0, -1.0, 0.0, 2 * np.pi], levels=(7, 7, 0))\n    l2, r2, lvl2 = amrspace([0.0, 1.0, 0.0, 1.0, 0.0, 2 * np.pi], levels=(7, 7, 0))\n    left_grid = np.concatenate([l1, l2], axis=0)\n    right_grid = np.concatenate([r1, r2], axis=0)\n    amr_levels = np.concatenate([lvl1, lvl2], axis=0)\n    center_grid = (left_grid + right_grid) / 2.0\n    data = np.cos(np.sqrt(np.sum(center_grid[:, :2] ** 2, axis=1))) ** 2  # cos^2\n\n\ndef teardown_module():\n    np.seterr(**old_settings)\n\n\npoint_pairs = np.array(\n    [\n        # p1               p2\n        ([0.5, -1.0, 0.0], [1.0, 1.0, 0.75 * np.pi]),  # Everything different\n        ([0.5, -1.0, 0.0], [0.5, 1.0, 0.75 * np.pi]),  # r same\n        ([0.5, -1.0, 0.0], [0.5, 1.0, np.pi]),  # diagonal through z-axis\n        # straight through z-axis\n        ([0.5, 0.0, 0.0], [0.5, 0.0, np.pi]),\n        # ([0.5, 0.0, np.pi*3/2 + 0.0], [0.5, 0.0, np.pi*3/2 + np.pi]),\n        # ([0.5, 0.0, np.pi/2 + 0.0], [0.5, 0.0, np.pi/2 + np.pi]),\n        # ([0.5, 0.0, np.pi + 0.0], [0.5, 0.0, np.pi + np.pi]),\n        # const z, not through z-axis\n        ([0.5, 0.1, 0.0], [0.5, 0.1, 0.75 * np.pi]),\n        # ([0.5, 0.1, np.pi + 0.0], [0.5, 0.1, np.pi + 0.75*np.pi]),\n        # ([0.5, 0.1, np.pi*3/2 + 0.0], [0.5, 0.1, np.pi*3/2 + 0.75*np.pi]),\n        # ([0.5, 0.1, np.pi/2 + 0.0], [0.5, 0.1, np.pi/2 + 0.75*np.pi]),\n        # ([0.5, 0.1, 2*np.pi + 0.0], [0.5, 0.1, 2*np.pi + 0.75*np.pi]),\n        # ([0.5, 0.1, np.pi/4 + 0.0], [0.5, 0.1, np.pi/4 + 0.75*np.pi]),\n        # ([0.5, 0.1, np.pi*3/8 + 0.0], [0.5, 0.1, np.pi*3/8 + 0.75*np.pi]),\n        (\n            [0.5, -1.0, 0.75 * np.pi],\n            [1.0, 1.0, 0.75 * np.pi],\n        ),  # r,z different - theta same\n        ([0.5, -1.0, 0.75 * np.pi], [0.5, 1.0, 0.75 * np.pi]),  # z-axis parallel\n        ([0.0, -1.0, 0.0], [0.0, 1.0, 0.0]),  # z-axis itself\n    ]\n)\n\n\ndef check_monotonic_inc(arr):\n    assert np.all(0.0 <= (arr[1:] - arr[:-1]))\n\n\ndef check_bounds(arr, blower, bupper):\n    assert np.all(blower <= arr)\n    assert np.all(bupper >= arr)\n\n\ndef test_cylindrical_ray_trace():\n    for pair in point_pairs:\n        p1, p2 = pair\n        p1cart, p2cart = _cyl2cart(pair)\n        pathlen = np.sqrt(np.sum((p2cart - p1cart) ** 2))\n\n        t, s, rztheta, inds = cylindrical_ray_trace(p1, p2, left_grid, right_grid)\n        npoints = len(t)\n\n        check_monotonic_inc(t)\n        assert 0.0 <= t[0]\n        assert t[-1] <= 1.0\n\n        check_monotonic_inc(s)\n        assert 0.0 <= s[0]\n        assert s[-1] <= pathlen\n        assert_equal(npoints, len(s))\n\n        assert_equal((npoints, 3), rztheta.shape)\n        check_bounds(rztheta[:, 0], 0.0, 1.0)\n        check_bounds(rztheta[:, 1], -1.0, 1.0)\n        check_bounds(rztheta[:, 2], 0.0, 2 * np.pi)\n        check_monotonic_inc(rztheta[:, 2])\n\n        assert_equal(npoints, len(inds))\n        check_bounds(inds, 0, len(left_grid) - 1)\n"
  },
  {
    "path": "yt/utilities/lib/tests/test_bitarray.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_equal, assert_equal\n\nimport yt.utilities.lib.bitarray as ba\n\n\ndef test_inout_bitarray():\n    # Check that we can do it for bitarrays that are funny-shaped\n    rng = np.random.default_rng()\n    for i in range(7):\n        # Check we can feed in an array\n        arr_in = rng.random(32**3 + i) > 0.5\n        b = ba.bitarray(arr=arr_in)\n        if i > 0:\n            assert_equal(b.ibuf.size, (32**3) / 8.0 + 1)\n        arr_out = b.as_bool_array()\n        assert_equal(arr_in, arr_out)\n\n        # Let's check we can do it without feeding it at first\n        b = ba.bitarray(size=arr_in.size)\n        b.set_from_array(arr_in)\n        arr_out = b.as_bool_array()\n        assert_equal(arr_in, arr_out)\n\n    # Try a big array\n    arr_in = rng.random(32**3 + i) > 0.5\n    b = ba.bitarray(arr=arr_in)\n    arr_out = b.as_bool_array()\n    assert_equal(arr_in, arr_out)\n    assert_equal(b.count(), arr_in.sum())\n\n    # Let's check we can do something interesting.\n    arr_in1 = rng.random(32**3) > 0.5\n    arr_in2 = rng.random(32**3) > 0.5\n    b1 = ba.bitarray(arr=arr_in1)\n    b2 = ba.bitarray(arr=arr_in2)\n    b3 = ba.bitarray(arr=(arr_in1 & arr_in2))\n    assert_equal((b1.ibuf & b2.ibuf), b3.ibuf)\n    assert_equal(b1.count(), arr_in1.sum())\n    assert_equal(b2.count(), arr_in2.sum())\n    # Let's check the logical and operation\n    b4 = b1.logical_and(b2)\n    assert_equal(b4.count(), b3.count())\n    assert_array_equal(b4.as_bool_array(), b3.as_bool_array())\n\n    b5 = b1 & b2\n    assert_equal(b5.count(), b3.count())\n    assert_array_equal(b5.as_bool_array(), b3.as_bool_array())\n\n    b1 &= b2\n    assert_equal(b1.count(), b4.count())\n    assert_array_equal(b1.as_bool_array(), b4.as_bool_array())\n\n    # Repeat this, but with the logical or operators\n    b1 = ba.bitarray(arr=arr_in1)\n    b2 = ba.bitarray(arr=arr_in2)\n    b3 = ba.bitarray(arr=(arr_in1 | arr_in2))\n    assert_equal((b1.ibuf | b2.ibuf), b3.ibuf)\n    assert_equal(b1.count(), arr_in1.sum())\n    assert_equal(b2.count(), arr_in2.sum())\n    # Let's check the logical and operation\n    b4 = b1.logical_or(b2)\n    assert_equal(b4.count(), b3.count())\n    assert_array_equal(b4.as_bool_array(), b3.as_bool_array())\n\n    b5 = b1 | b2\n    assert_equal(b5.count(), b3.count())\n    assert_array_equal(b5.as_bool_array(), b3.as_bool_array())\n\n    b1 |= b2\n    assert_equal(b1.count(), b4.count())\n    assert_array_equal(b1.as_bool_array(), b4.as_bool_array())\n\n    # Repeat this, but with the logical xor operators\n    b1 = ba.bitarray(arr=arr_in1)\n    b2 = ba.bitarray(arr=arr_in2)\n    b3 = ba.bitarray(arr=(arr_in1 ^ arr_in2))\n    assert_equal((b1.ibuf ^ b2.ibuf), b3.ibuf)\n    assert_equal(b1.count(), arr_in1.sum())\n    assert_equal(b2.count(), arr_in2.sum())\n    # Let's check the logical and operation\n    b4 = b1.logical_xor(b2)\n    assert_equal(b4.count(), b3.count())\n    assert_array_equal(b4.as_bool_array(), b3.as_bool_array())\n\n    b5 = b1 ^ b2\n    assert_equal(b5.count(), b3.count())\n    assert_array_equal(b5.as_bool_array(), b3.as_bool_array())\n\n    b1 ^= b2\n    assert_equal(b1.count(), b4.count())\n    assert_array_equal(b1.as_bool_array(), b4.as_bool_array())\n\n    b = ba.bitarray(10)\n    for i in range(10):\n        b.set_value(i, 2)  # 2 should evaluate to True\n        arr = b.as_bool_array()\n        assert_equal(arr[: i + 1].all(), True)\n        assert_equal(arr[i + 1 :].any(), False)\n    for i in range(10):\n        b.set_value(i, 0)\n    arr = b.as_bool_array()\n    assert_equal(arr.any(), False)\n    b.set_value(7, 1)\n    arr = b.as_bool_array()\n    assert_array_equal(arr, [0, 0, 0, 0, 0, 0, 0, 1, 0, 0])\n    b.set_value(2, 1)\n    arr = b.as_bool_array()\n    assert_array_equal(arr, [0, 0, 1, 0, 0, 0, 0, 1, 0, 0])\n\n\ndef test_set_range():\n    b = ba.bitarray(127)\n    # Test once where we're in the middle of start and end bits\n    b.set_range(4, 65, 1)\n    comparison_array = np.zeros(127, dtype=\"uint8\")\n    comparison_array[4:65] = 1\n    arr = b.as_bool_array().astype(\"uint8\")\n    assert_array_equal(arr, comparison_array)\n    assert_equal(b.count(), comparison_array.sum())\n\n    # Test when we start and stop in the same byte\n    b = ba.bitarray(127)\n    b.set_range(4, 6, 1)\n    comparison_array = np.zeros(127, dtype=\"uint8\")\n    comparison_array[4:6] = 1\n    arr = b.as_bool_array().astype(\"uint8\")\n    assert_array_equal(arr, comparison_array)\n    assert_equal(b.count(), comparison_array.sum())\n\n    # Test now where we're in the middle of start\n    b = ba.bitarray(64)\n    b.set_range(33, 36, 1)\n    comparison_array = np.zeros(64, dtype=\"uint8\")\n    comparison_array[33:36] = 1\n    arr = b.as_bool_array().astype(\"uint8\")\n    assert_array_equal(arr, comparison_array)\n    assert_equal(b.count(), comparison_array.sum())\n\n    # Now we test when we end on a byte edge, but we have 65 entries\n    b = ba.bitarray(65)\n    b.set_range(32, 64, 1)\n    comparison_array = np.zeros(65, dtype=\"uint8\")\n    comparison_array[32:64] = 1\n    arr = b.as_bool_array().astype(\"uint8\")\n    assert_array_equal(arr, comparison_array)\n    assert_equal(b.count(), comparison_array.sum())\n\n    # Let's do the inverse\n    b = ba.bitarray(127)\n    b.set_range(0, 127, 1)\n    assert_equal(b.as_bool_array().all(), True)\n    b.set_range(0, 127, 0)\n    assert_equal(b.as_bool_array().any(), False)\n    b.set_range(3, 9, 1)\n    comparison_array = np.zeros(127, dtype=\"uint8\")\n    comparison_array[3:9] = 1\n    arr = b.as_bool_array().astype(\"uint8\")\n    assert_array_equal(arr, comparison_array)\n    assert_equal(b.count(), comparison_array.sum())\n\n    # Now let's overlay some zeros\n    b.set_range(7, 10, 0)\n    comparison_array[7:10] = 0\n    arr = b.as_bool_array().astype(\"uint8\")\n    assert_array_equal(arr, comparison_array)\n"
  },
  {
    "path": "yt/utilities/lib/tests/test_bounding_volume_hierarchy.py",
    "content": "import numpy as np\n\nimport yt\nfrom yt.testing import requires_file\nfrom yt.utilities.lib.bounding_volume_hierarchy import BVH, test_ray_trace\nfrom yt.visualization.volume_rendering.api import Camera, Scene\n\n\ndef get_rays(camera):\n    normal_vector = camera.unit_vectors[2].d\n    W = np.array([8.0, 8.0])\n    N = np.array([800, 800])\n    dx = W / N\n\n    x_points = np.linspace((-N[0] / 2 + 0.5) * dx[0], (N[0] / 2 - 0.5) * dx[0], N[0])\n    y_points = np.linspace((-N[1] / 2 + 0.5) * dx[1], (N[1] / 2 - 0.5) * dx[0], N[1])\n\n    X, Y = np.meshgrid(x_points, y_points)\n    result = np.dot(camera.unit_vectors[0:2].T, [X.ravel(), Y.ravel()])\n    vec_origins = camera.scene.arr(result.T, \"unitary\") + camera.position\n    return np.array(vec_origins), np.array(normal_vector)\n\n\nfn = \"MOOSE_sample_data/out.e-s010\"\n\n\n@requires_file(fn)\ndef test_bounding_volume_hierarchy():\n    ds = yt.load(fn)\n    vertices = ds.index.meshes[0].connectivity_coords\n    indices = ds.index.meshes[0].connectivity_indices - 1\n\n    ad = ds.all_data()\n    field_data = ad[\"connect1\", \"diffused\"]\n\n    bvh = BVH(vertices, indices, field_data)\n\n    sc = Scene()\n    cam = Camera(sc)\n    cam.set_position(np.array([8.0, 8.0, 8.0]))\n    cam.focus = np.array([0.0, 0.0, 0.0])\n    origins, direction = get_rays(cam)\n\n    image = np.empty(800 * 800, np.float64)\n    test_ray_trace(image, origins, direction, bvh)\n    image = image.reshape((800, 800))\n    return image\n"
  },
  {
    "path": "yt/utilities/lib/tests/test_element_mappings.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal\n\nfrom yt.utilities.lib.element_mappings import (\n    test_hex20_sampler,\n    test_hex_sampler,\n    test_linear1D_sampler,\n    test_quad2_sampler,\n    test_quad_sampler,\n    test_tet2_sampler,\n    test_tetra_sampler,\n    test_tri2_sampler,\n    test_tri_sampler,\n    test_wedge_sampler,\n)\n\n\ndef check_all_vertices(sampler, vertices, field_values):\n    NV = vertices.shape[0]\n    NDIM = vertices.shape[1]\n    x = np.empty(NDIM)\n    for i in range(NV):\n        x = vertices[i]\n        val = sampler(vertices, field_values, x)\n        assert_almost_equal(val, field_values[i])\n\n\ndef test_P1Sampler1D():\n    vertices = np.array([[0.1], [0.3]])\n    field_values = np.array([1.0, 2.0])\n\n    check_all_vertices(test_linear1D_sampler, vertices, field_values)\n\n\ndef test_P1Sampler2D():\n    vertices = np.array([[0.1, 0.2], [0.6, 0.3], [0.2, 0.7]])\n    field_values = np.array([1.0, 2.0, 3.0])\n\n    check_all_vertices(test_tri_sampler, vertices, field_values)\n\n\ndef test_P1Sampler3D():\n    vertices = np.array(\n        [[0.1, 0.1, 0.1], [0.6, 0.3, 0.2], [0.2, 0.7, 0.2], [0.4, 0.4, 0.7]]\n    )\n\n    field_values = np.array([1.0, 2.0, 3.0, 4.0])\n\n    check_all_vertices(test_tetra_sampler, vertices, field_values)\n\n\ndef test_Q1Sampler2D():\n    vertices = np.array([[0.1, 0.2], [0.6, 0.3], [0.7, 0.9], [0.2, 0.7]])\n\n    field_values = np.array([1.0, 2.0, 3.0, 4.0])\n\n    check_all_vertices(test_quad_sampler, vertices, field_values)\n\n\ndef test_Q2Sampler2D():\n    vertices = np.array(\n        [\n            [2.0, 3.0],\n            [7.0, 4.0],\n            [10.0, 15.0],\n            [4.0, 12.0],\n            [4.5, 3.5],\n            [8.5, 9.5],\n            [7.0, 13.5],\n            [3.0, 7.5],\n            [5.75, 8.5],\n        ]\n    )\n\n    field_values = np.array([7.0, 27.0, 40.0, 12.0, 13.0, 30.0, 22.0, 9.0, 16.0])\n\n    check_all_vertices(test_quad2_sampler, vertices, field_values)\n\n\ndef test_Q1Sampler3D():\n    vertices = np.array(\n        [\n            [2.00657905, 0.6888599, 1.4375],\n            [1.8658198, 1.00973171, 1.4375],\n            [1.97881594, 1.07088163, 1.4375],\n            [2.12808879, 0.73057381, 1.4375],\n            [2.00657905, 0.6888599, 1.2],\n            [1.8658198, 1.00973171, 1.2],\n            [1.97881594, 1.07088163, 1.2],\n            [2.12808879, 0.73057381, 1.2],\n        ]\n    )\n\n    field_values = np.array(\n        [\n            0.4526278,\n            0.45262656,\n            0.45262657,\n            0.4526278,\n            0.54464296,\n            0.54464149,\n            0.5446415,\n            0.54464296,\n        ]\n    )\n\n    check_all_vertices(test_hex_sampler, vertices, field_values)\n\n\ndef test_S2Sampler3D():\n    vertices = np.array(\n        [\n            [3.00608789e-03, 4.64941000e-02, -3.95758979e-04],\n            [3.03202730e-03, 4.64941000e-02, 0.00000000e00],\n            [3.03202730e-03, 4.70402000e-02, 3.34389809e-20],\n            [3.00608789e-03, 4.70402000e-02, -3.95758979e-04],\n            [2.45511948e-03, 4.64941000e-02, -3.23222611e-04],\n            [2.47630461e-03, 4.64941000e-02, 1.20370622e-35],\n            [2.47630461e-03, 4.70402000e-02, 3.34389809e-20],\n            [2.45511948e-03, 4.70402000e-02, -3.23222611e-04],\n            [3.01905760e-03, 4.64941000e-02, -1.97879489e-04],\n            [3.03202730e-03, 4.67671500e-02, 3.34389809e-20],\n            [3.01905760e-03, 4.70402000e-02, -1.97879489e-04],\n            [3.00608789e-03, 4.67671500e-02, -3.95758979e-04],\n            [2.73060368e-03, 4.64941000e-02, -3.59490795e-04],\n            [2.75416596e-03, 4.64941000e-02, -1.86574463e-34],\n            [2.75416596e-03, 4.70402000e-02, 6.68779617e-20],\n            [2.73060368e-03, 4.70402000e-02, -3.59490795e-04],\n            [2.47100265e-03, 4.64941000e-02, -1.61958070e-04],\n            [2.47630461e-03, 4.67671500e-02, 1.67194904e-20],\n            [2.47100265e-03, 4.70402000e-02, -1.61958070e-04],\n            [2.45511948e-03, 4.67671500e-02, -3.23222611e-04],\n        ]\n    )\n\n    field_values = np.array(\n        [\n            659.80151432,\n            650.95995348,\n            650.02809796,\n            658.81589888,\n            659.77560908,\n            650.93582507,\n            649.99987015,\n            658.78508795,\n            655.38073390,\n            650.49402572,\n            654.42199842,\n            659.30870660,\n            659.78856170,\n            650.94788928,\n            650.01398406,\n            658.80049342,\n            655.35571708,\n            650.46784761,\n            654.39247905,\n            659.28034852,\n        ]\n    )\n\n    check_all_vertices(test_hex20_sampler, vertices, field_values)\n\n\ndef test_W1Sampler3D():\n    vertices = np.array(\n        [\n            [-0.34641016, 0.3, 0.0],\n            [-0.31754265, 0.25, 0.0],\n            [-0.28867513, 0.3, 0.0],\n            [-0.34641016, 0.3, 0.05],\n            [-0.31754265, 0.25, 0.05],\n            [-0.28867513, 0.3, 0.05],\n        ]\n    )\n\n    field_values = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])\n\n    check_all_vertices(test_wedge_sampler, vertices, field_values)\n\n\ndef test_T2Sampler2D():\n    vertices = np.array(\n        [[0.1, 0.2], [0.3, 0.5], [0.2, 0.9], [0.2, 0.35], [0.25, 0.7], [0.15, 0.55]]\n    )\n\n    field_values = np.array([15.0, 37.0, 49.0, 32.0, 46.0, 24.0])\n\n    check_all_vertices(test_tri2_sampler, vertices, field_values)\n\n\ndef test_Tet2Sampler3D():\n    vertices = np.array(\n        [\n            [0.3, -0.4, 0.6],\n            [1.7, -0.7, 0.8],\n            [0.4, 1.2, 0.4],\n            [0.4, -0.2, 2.0],\n            [1.0, -0.55, 0.7],\n            [1.05, 0.25, 0.6],\n            [0.35, 0.4, 0.5],\n            [0.35, -0.3, 1.3],\n            [1.05, -0.45, 1.4],\n            [0.4, 0.5, 1.2],\n        ]\n    )\n\n    field_values = np.array(\n        [15.0, 37.0, 49.0, 24.0, 30.0, 44.0, 20.0, 17.0, 32.0, 36.0]\n    )\n\n    check_all_vertices(test_tet2_sampler, vertices, field_values)\n"
  },
  {
    "path": "yt/utilities/lib/tests/test_fill_region.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.utilities.lib.misc_utilities import fill_region\n\nNDIM = 32\n\n\ndef test_fill_region():\n    for level in range(2):\n        rf = 2**level\n        output_fields = [\n            np.zeros((NDIM * rf, NDIM * rf, NDIM * rf), \"float64\") for i in range(3)\n        ]\n        input_fields = [np.empty(NDIM**3, \"float64\") for i in range(3)]\n        v = np.mgrid[\n            0.0 : 1.0 : NDIM * 1j, 0.0 : 1.0 : NDIM * 1j, 0.0 : 1.0 : NDIM * 1j\n        ]\n        input_fields[0][:] = v[0].ravel()\n        input_fields[1][:] = v[1].ravel()\n        input_fields[2][:] = v[2].ravel()\n        left_index = np.zeros(3, \"int64\")\n        ipos = np.empty((NDIM**3, 3), dtype=\"int64\")\n        ind = np.indices((NDIM, NDIM, NDIM))\n        ipos[:, 0] = ind[0].ravel()\n        ipos[:, 1] = ind[1].ravel()\n        ipos[:, 2] = ind[2].ravel()\n        ires = np.zeros(NDIM * NDIM * NDIM, \"int64\")\n        ddims = np.array([NDIM, NDIM, NDIM], dtype=\"int64\") * rf\n        fill_region(\n            input_fields,\n            output_fields,\n            level,\n            left_index,\n            ipos,\n            ires,\n            ddims,\n            np.array([2, 2, 2], dtype=\"i8\"),\n        )\n        for r in range(level + 1):\n            for o, i in zip(output_fields, v, strict=True):\n                assert_equal(o[r::rf, r::rf, r::rf], i)\n"
  },
  {
    "path": "yt/utilities/lib/tests/test_geometry_utils.py",
    "content": "import numpy as np\nfrom numpy.testing import (\n    assert_array_equal,\n    assert_array_less,\n    assert_equal,\n    assert_raises,\n)\n\nfrom yt.testing import fake_random_ds\nfrom yt.utilities.lib.misc_utilities import (\n    obtain_position_vector,\n    obtain_relative_velocity_vector,\n)\n\n_fields = (\"density\", \"velocity_x\", \"velocity_y\", \"velocity_z\")\n_units = (\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\")\n\n# TODO: error compact/spread bits for incorrect size\n# TODO: test msdb for [0,0], [1,1], [2,2] etc.\n\n\ndef test_spread_bits():\n    from yt.utilities.lib.geometry_utils import spread_bits\n\n    li = [\n        (\n            np.uint64(0b111111111111111111111),\n            np.uint64(0b1001001001001001001001001001001001001001001001001001001001001),\n        )\n    ]\n    for i, ans in li:\n        out = spread_bits(i)\n        assert_equal(out, ans)\n\n\ndef test_compact_bits():\n    from yt.utilities.lib.geometry_utils import compact_bits\n\n    li = [\n        (\n            np.uint64(0b111111111111111111111),\n            np.uint64(0b1001001001001001001001001001001001001001001001001001001001001),\n        )\n    ]\n    for ans, i in li:\n        out = compact_bits(i)\n        assert_equal(out, ans)\n\n\ndef test_spread_and_compact_bits():\n    from yt.utilities.lib.geometry_utils import compact_bits, spread_bits\n\n    li = [np.uint64(0b111111111111111111111)]\n    for ans in li:\n        mi = spread_bits(ans)\n        out = compact_bits(mi)\n        assert_equal(out, ans)\n\n\ndef test_lsz():\n    from yt.utilities.lib.geometry_utils import lsz\n\n    li = [\n        (\n            np.uint64(0b1001001001001001001001001001001001001001001001001001001001001),\n            3 * 21,\n            3,\n            0,\n        ),\n        (\n            np.uint64(0b1001001001001001001001001001001001001001001001001001001001000),\n            3 * 0,\n            3,\n            0,\n        ),\n        (\n            np.uint64(0b1001001001001001001001001001001001001001001001001001001000001),\n            3 * 1,\n            3,\n            0,\n        ),\n        (\n            np.uint64(0b1001001001001001001001001001001001001001001001001001000001001),\n            3 * 2,\n            3,\n            0,\n        ),\n        (\n            np.uint64(0b10010010010010010010010010010010010010010010010010010010010010),\n            3 * 0,\n            3,\n            0,\n        ),\n        (\n            np.uint64(\n                0b100100100100100100100100100100100100100100100100100100100100100\n            ),\n            3 * 0,\n            3,\n            0,\n        ),\n        (np.uint64(0b100), 0, 1, 0),\n        (np.uint64(0b100), 1, 1, 1),\n        (np.uint64(0b100), 3, 1, 2),\n        (np.uint64(0b100), 3, 1, 3),\n    ]\n    for i, ans, stride, start in li:\n        out = lsz(i, stride=stride, start=start)\n        assert_equal(out, ans)\n\n\ndef test_lsb():\n    from yt.utilities.lib.geometry_utils import lsb\n\n    li = [\n        (\n            np.uint64(0b1001001001001001001001001001001001001001001001001001001001001),\n            3 * 0,\n        ),\n        (\n            np.uint64(0b1001001001001001001001001001001001001001001001001001001001000),\n            3 * 1,\n        ),\n        (\n            np.uint64(0b1001001001001001001001001001001001001001001001001001001000000),\n            3 * 2,\n        ),\n        (\n            np.uint64(0b1001001001001001001001001001001001001001001001001001000000000),\n            3 * 3,\n        ),\n        (\n            np.uint64(0b10010010010010010010010010010010010010010010010010010010010010),\n            3 * 21,\n        ),\n        (\n            np.uint64(\n                0b100100100100100100100100100100100100100100100100100100100100100\n            ),\n            3 * 21,\n        ),\n    ]\n    for i, ans in li:\n        out = lsb(i, stride=3)\n        assert_equal(out, ans)\n\n\ndef test_bitwise_addition():\n    from yt.utilities.lib.geometry_utils import bitwise_addition\n\n    # TODO: Handle negative & periodic boundaries\n    lz = [\n        (0, 1),\n        #          (0,-1),\n        (1, 1),\n        (1, 2),\n        (1, 4),\n        (1, -1),\n        (2, 1),\n        (2, 2),\n        (2, -1),\n        (2, -2),\n        (3, 1),\n        (3, 5),\n        (3, -1),\n    ]\n    for i, a in lz:\n        i = np.uint64(i)\n        a = np.int64(a)\n        out = bitwise_addition(i, a, stride=1, start=0)\n        assert_equal(out, i + a)\n\n\n# def test_add_to_morton_coord():\n#    from yt.utilities.lib.geometry_utils import add_to_morton_coord\n\n\ndef test_get_morton_indices():\n    from yt.utilities.lib.geometry_utils import (\n        get_morton_indices,\n        get_morton_indices_unravel,\n    )\n\n    INDEX_MAX_64 = np.uint64(2097151)\n    li = np.arange(6, dtype=np.uint64).reshape((2, 3))\n    mi_ans = np.array([10, 229], dtype=np.uint64)\n    mi_out = get_morton_indices(li)\n    mi_out2 = get_morton_indices_unravel(li[:, 0], li[:, 1], li[:, 2])\n    assert_array_equal(mi_out, mi_ans)\n    assert_array_equal(mi_out2, mi_ans)\n    li[0, :] = INDEX_MAX_64 * np.ones(3, dtype=np.uint64)\n    assert_raises(ValueError, get_morton_indices, li)\n    assert_raises(ValueError, get_morton_indices_unravel, li[:, 0], li[:, 1], li[:, 2])\n\n\ndef test_get_morton_points():\n    from yt.utilities.lib.geometry_utils import get_morton_points\n\n    mi = np.array([10, 229], dtype=np.uint64)\n    li_ans = np.arange(6, dtype=np.uint64).reshape((2, 3))\n    li_out = get_morton_points(mi)\n    assert_array_equal(li_out, li_ans)\n\n\ndef test_compare_morton():\n    # TODO: Add error messages to assertions\n    from yt.utilities.lib.geometry_utils import compare_morton\n\n    # Diagonal\n    p = np.array([0.0, 0.0, 0.0], dtype=np.float64)\n    q = np.array([1.0, 1.0, 1.0], dtype=np.float64)\n    assert_equal(compare_morton(p, q), 1)\n    assert_equal(compare_morton(q, p), 0)\n    assert_equal(compare_morton(p, p), 0)\n    # 1-1 vs 0-1\n    p = np.array([1.0, 1.0, 0.0], dtype=np.float64)\n    q = np.array([1.0, 1.0, 1.0], dtype=np.float64)\n    assert_equal(compare_morton(p, q), 1)\n    assert_equal(compare_morton(q, p), 0)\n    assert_equal(compare_morton(p, p), 0)\n    # x advance, y decrease\n    p = np.array([0.0, 1.0, 0.0], dtype=np.float64)\n    q = np.array([1.0, 0.0, 0.0], dtype=np.float64)\n    assert_equal(compare_morton(p, q), 1)\n    assert_equal(compare_morton(q, p), 0)\n    assert_equal(compare_morton(p, p), 0)\n    # x&y advance, z decrease\n    p = np.array([0.0, 0.0, 1.0], dtype=np.float64)\n    q = np.array([1.0, 1.0, 0.0], dtype=np.float64)\n    assert_equal(compare_morton(p, q), 1)\n    assert_equal(compare_morton(q, p), 0)\n    assert_equal(compare_morton(p, p), 0)\n\n\ndef test_get_morton_neighbors_coarse():\n    from yt.utilities.lib.geometry_utils import get_morton_neighbors_coarse\n\n    imax = 5\n    ngz = 1\n    tests = {\n        (7, 1): np.array(\n            [\n                35,\n                49,\n                56,\n                48,\n                33,\n                40,\n                32,\n                42,\n                34,\n                3,\n                17,\n                24,\n                16,\n                1,\n                8,\n                0,\n                10,\n                2,\n                21,\n                28,\n                20,\n                5,\n                12,\n                4,\n                14,\n                6,\n            ],\n            dtype=\"uint64\",\n        ),\n        (7, 0): np.array(\n            [\n                35,\n                49,\n                56,\n                48,\n                33,\n                40,\n                32,\n                42,\n                34,\n                3,\n                17,\n                24,\n                16,\n                1,\n                8,\n                0,\n                10,\n                2,\n                21,\n                28,\n                20,\n                5,\n                12,\n                4,\n                14,\n                6,\n            ],\n            dtype=\"uint64\",\n        ),\n        (0, 1): np.array(\n            [\n                4,\n                6,\n                7,\n                70,\n                132,\n                133,\n                196,\n                5,\n                68,\n                256,\n                258,\n                259,\n                322,\n                384,\n                385,\n                448,\n                257,\n                320,\n                2,\n                3,\n                66,\n                128,\n                129,\n                192,\n                1,\n                64,\n            ],\n            dtype=\"uint64\",\n        ),\n        (0, 0): np.array([4, 6, 7, 5, 2, 3, 1], dtype=\"uint64\"),\n        (448, 1): np.array(\n            [\n                192,\n                64,\n                0,\n                9,\n                82,\n                18,\n                27,\n                128,\n                137,\n                228,\n                100,\n                36,\n                45,\n                118,\n                54,\n                63,\n                164,\n                173,\n                320,\n                256,\n                265,\n                338,\n                274,\n                283,\n                384,\n                393,\n            ],\n            dtype=\"uint64\",\n        ),\n        (448, 0): np.array([228, 118, 63, 173, 338, 283, 393], dtype=\"uint64\"),\n    }\n    for (mi1, periodic), ans in tests.items():\n        n1 = get_morton_neighbors_coarse(mi1, imax, periodic, ngz)\n        assert_equal(np.sort(n1), np.sort(ans))\n\n\ndef test_get_morton_neighbors_refined():\n    from yt.utilities.lib.geometry_utils import get_morton_neighbors_refined\n\n    imax1 = 5\n    imax2 = 5\n    ngz = 1\n    tests = {\n        (7, 7, 1): (\n            np.array(\n                [\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                ],\n                dtype=\"uint64\",\n            ),\n            np.array(\n                [\n                    35,\n                    49,\n                    56,\n                    48,\n                    33,\n                    40,\n                    32,\n                    42,\n                    34,\n                    3,\n                    17,\n                    24,\n                    16,\n                    1,\n                    8,\n                    0,\n                    10,\n                    2,\n                    21,\n                    28,\n                    20,\n                    5,\n                    12,\n                    4,\n                    14,\n                    6,\n                ],\n                dtype=\"uint64\",\n            ),\n        ),\n        (7, 7, 0): (\n            np.array(\n                [\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                    7,\n                ],\n                dtype=\"uint64\",\n            ),\n            np.array(\n                [\n                    35,\n                    49,\n                    56,\n                    48,\n                    33,\n                    40,\n                    32,\n                    42,\n                    34,\n                    3,\n                    17,\n                    24,\n                    16,\n                    1,\n                    8,\n                    0,\n                    10,\n                    2,\n                    21,\n                    28,\n                    20,\n                    5,\n                    12,\n                    4,\n                    14,\n                    6,\n                ],\n                dtype=\"uint64\",\n            ),\n        ),\n        (0, 0, 1): (\n            np.array(\n                [\n                    0,\n                    0,\n                    0,\n                    64,\n                    128,\n                    128,\n                    192,\n                    0,\n                    64,\n                    256,\n                    256,\n                    256,\n                    320,\n                    384,\n                    384,\n                    448,\n                    256,\n                    320,\n                    0,\n                    0,\n                    64,\n                    128,\n                    128,\n                    192,\n                    0,\n                    64,\n                ],\n                dtype=\"uint64\",\n            ),\n            np.array(\n                [\n                    4,\n                    6,\n                    7,\n                    70,\n                    132,\n                    133,\n                    196,\n                    5,\n                    68,\n                    256,\n                    258,\n                    259,\n                    322,\n                    384,\n                    385,\n                    448,\n                    257,\n                    320,\n                    2,\n                    3,\n                    66,\n                    128,\n                    129,\n                    192,\n                    1,\n                    64,\n                ],\n                dtype=\"uint64\",\n            ),\n        ),\n        (0, 0, 0): (\n            np.array([0, 0, 0, 0, 0, 0, 0], dtype=\"uint64\"),\n            np.array([4, 6, 7, 5, 2, 3, 1], dtype=\"uint64\"),\n        ),\n        (448, 448, 1): (\n            np.array(\n                [\n                    192,\n                    64,\n                    0,\n                    64,\n                    192,\n                    128,\n                    192,\n                    128,\n                    192,\n                    448,\n                    320,\n                    256,\n                    320,\n                    448,\n                    384,\n                    448,\n                    384,\n                    448,\n                    320,\n                    256,\n                    320,\n                    448,\n                    384,\n                    448,\n                    384,\n                    448,\n                ],\n                dtype=\"uint64\",\n            ),\n            np.array(\n                [\n                    192,\n                    64,\n                    0,\n                    9,\n                    82,\n                    18,\n                    27,\n                    128,\n                    137,\n                    228,\n                    100,\n                    36,\n                    45,\n                    118,\n                    54,\n                    63,\n                    164,\n                    173,\n                    320,\n                    256,\n                    265,\n                    338,\n                    274,\n                    283,\n                    384,\n                    393,\n                ],\n                dtype=\"uint64\",\n            ),\n        ),\n        (448, 448, 0): (\n            np.array([448, 448, 448, 448, 448, 448, 448], dtype=\"uint64\"),\n            np.array([228, 118, 63, 173, 338, 283, 393], dtype=\"uint64\"),\n        ),\n    }\n    for (mi1, mi2, periodic), (ans1, ans2) in tests.items():\n        n1, n2 = get_morton_neighbors_refined(mi1, mi2, imax1, imax2, periodic, ngz)\n        assert_equal(np.sort(n1), np.sort(ans1))\n        assert_equal(np.sort(n2), np.sort(ans2))\n\n\ndef test_morton_neighbor():\n    from yt.utilities.lib.geometry_utils import get_morton_indices, morton_neighbor\n\n    order = 20\n    imax = np.uint64(1 << order)\n    p = np.array(\n        [\n            [imax / 2, imax / 2, imax / 2],\n            [imax / 2, imax / 2, 0],\n            [imax / 2, imax / 2, imax],\n        ],\n        dtype=np.uint64,\n    )\n    p_ans = np.array(\n        [\n            [imax / 2, imax / 2, imax / 2 + 1],\n            [imax / 2, imax / 2, imax / 2 - 1],\n            [imax / 2, imax / 2, imax - 1],\n            [imax / 2, imax / 2, 1],\n            [imax / 2, imax / 2 + 1, imax / 2 + 1],\n            [imax / 2 - 1, imax / 2 - 1, imax / 2],\n            [imax / 2 - 1, imax / 2, imax / 2 + 1],\n            [imax / 2, imax / 2 - 1, imax - 1],\n            [imax / 2, imax / 2 + 1, 1],\n        ],\n        dtype=np.uint64,\n    )\n    mi_ans = get_morton_indices(p_ans)\n    assert_equal(morton_neighbor(p[0, :], [2], [+1], imax), mi_ans[0])\n    assert_equal(morton_neighbor(p[0, :], [2], [-1], imax), mi_ans[1])\n    assert_equal(morton_neighbor(p[1, :], [2], [-1], imax, periodic=False), -1)\n    assert_equal(morton_neighbor(p[2, :], [2], [+1], imax, periodic=False), -1)\n    assert_equal(morton_neighbor(p[1, :], [2], [-1], imax, periodic=True), mi_ans[2])\n    assert_equal(morton_neighbor(p[2, :], [2], [+1], imax, periodic=True), mi_ans[3])\n    assert_equal(morton_neighbor(p[0, :], [1, 2], [+1, +1], imax), mi_ans[4])\n    assert_equal(morton_neighbor(p[0, :], [0, 1], [-1, -1], imax), mi_ans[5])\n    assert_equal(morton_neighbor(p[0, :], [0, 2], [-1, +1], imax), mi_ans[6])\n    assert_equal(morton_neighbor(p[1, :], [1, 2], [-1, -1], imax, periodic=False), -1)\n    assert_equal(morton_neighbor(p[2, :], [1, 2], [+1, +1], imax, periodic=False), -1)\n    assert_equal(\n        morton_neighbor(p[1, :], [1, 2], [-1, -1], imax, periodic=True), mi_ans[7]\n    )\n    assert_equal(\n        morton_neighbor(p[2, :], [1, 2], [+1, +1], imax, periodic=True), mi_ans[8]\n    )\n\n\ndef test_get_morton_neighbors():\n    from yt.utilities.lib.geometry_utils import get_morton_indices, get_morton_neighbors\n\n    order = 20\n    imax = 1 << order\n    p = np.array(\n        [\n            [imax / 2, imax / 2, imax / 2],\n            [imax / 2, imax / 2, 0],\n            [imax / 2, imax / 2, imax],\n        ],\n        dtype=np.uint64,\n    )\n    pn_non = [\n        np.array(\n            [\n                # x +/- 1\n                [imax / 2 + 1, imax / 2, imax / 2],\n                [imax / 2 + 1, imax / 2 + 1, imax / 2],\n                [imax / 2 + 1, imax / 2 + 1, imax / 2 + 1],\n                [imax / 2 + 1, imax / 2 + 1, imax / 2 - 1],\n                [imax / 2 + 1, imax / 2 - 1, imax / 2],\n                [imax / 2 + 1, imax / 2 - 1, imax / 2 + 1],\n                [imax / 2 + 1, imax / 2 - 1, imax / 2 - 1],\n                [imax / 2 + 1, imax / 2, imax / 2 + 1],\n                [imax / 2 + 1, imax / 2, imax / 2 - 1],\n                [imax / 2 - 1, imax / 2, imax / 2],\n                [imax / 2 - 1, imax / 2 + 1, imax / 2],\n                [imax / 2 - 1, imax / 2 + 1, imax / 2 + 1],\n                [imax / 2 - 1, imax / 2 + 1, imax / 2 - 1],\n                [imax / 2 - 1, imax / 2 - 1, imax / 2],\n                [imax / 2 - 1, imax / 2 - 1, imax / 2 + 1],\n                [imax / 2 - 1, imax / 2 - 1, imax / 2 - 1],\n                [imax / 2 - 1, imax / 2, imax / 2 + 1],\n                [imax / 2 - 1, imax / 2, imax / 2 - 1],\n                # y +/- 1\n                [imax / 2, imax / 2 + 1, imax / 2],\n                [imax / 2, imax / 2 + 1, imax / 2 + 1],\n                [imax / 2, imax / 2 + 1, imax / 2 - 1],\n                [imax / 2, imax / 2 - 1, imax / 2],\n                [imax / 2, imax / 2 - 1, imax / 2 + 1],\n                [imax / 2, imax / 2 - 1, imax / 2 - 1],\n                # x +/- 1\n                [imax / 2, imax / 2, imax / 2 + 1],\n                [imax / 2, imax / 2, imax / 2 - 1],\n            ],\n            dtype=np.uint64,\n        ),\n        np.array(\n            [\n                # x +/- 1\n                [imax / 2 + 1, imax / 2, 0],\n                [imax / 2 + 1, imax / 2 + 1, 0],\n                [imax / 2 + 1, imax / 2 + 1, 1],\n                [imax / 2 + 1, imax / 2 - 1, 0],\n                [imax / 2 + 1, imax / 2 - 1, 1],\n                [imax / 2 + 1, imax / 2, 1],\n                [imax / 2 - 1, imax / 2, 0],\n                [imax / 2 - 1, imax / 2 + 1, 0],\n                [imax / 2 - 1, imax / 2 + 1, 1],\n                [imax / 2 - 1, imax / 2 - 1, 0],\n                [imax / 2 - 1, imax / 2 - 1, 1],\n                [imax / 2 - 1, imax / 2, 1],\n                # y +/- 1\n                [imax / 2, imax / 2 + 1, 0],\n                [imax / 2, imax / 2 + 1, 1],\n                [imax / 2, imax / 2 - 1, 0],\n                [imax / 2, imax / 2 - 1, 1],\n                # z +/- 1\n                [imax / 2, imax / 2, 0 + 1],\n            ],\n            dtype=np.uint64,\n        ),\n        np.array(\n            [\n                # x +/- 1\n                [imax / 2 + 1, imax / 2, imax],\n                [imax / 2 + 1, imax / 2 + 1, imax],\n                [imax / 2 + 1, imax / 2 + 1, imax - 1],\n                [imax / 2 + 1, imax / 2 - 1, imax],\n                [imax / 2 + 1, imax / 2 - 1, imax - 1],\n                [imax / 2 + 1, imax / 2, imax - 1],\n                [imax / 2 - 1, imax / 2, imax],\n                [imax / 2 - 1, imax / 2 + 1, imax],\n                [imax / 2 - 1, imax / 2 + 1, imax - 1],\n                [imax / 2 - 1, imax / 2 - 1, imax],\n                [imax / 2 - 1, imax / 2 - 1, imax - 1],\n                [imax / 2 - 1, imax / 2, imax - 1],\n                # y +/- 1\n                [imax / 2, imax / 2 + 1, imax],\n                [imax / 2, imax / 2 + 1, imax - 1],\n                [imax / 2, imax / 2 - 1, imax],\n                [imax / 2, imax / 2 - 1, imax - 1],\n                # z +/- 1\n                [imax / 2, imax / 2, imax - 1],\n            ],\n            dtype=np.uint64,\n        ),\n    ]\n    pn_per = [\n        np.array(\n            [\n                # x +/- 1\n                [imax / 2 + 1, imax / 2, imax / 2],\n                [imax / 2 + 1, imax / 2 + 1, imax / 2],\n                [imax / 2 + 1, imax / 2 + 1, imax / 2 + 1],\n                [imax / 2 + 1, imax / 2 + 1, imax / 2 - 1],\n                [imax / 2 + 1, imax / 2 - 1, imax / 2],\n                [imax / 2 + 1, imax / 2 - 1, imax / 2 + 1],\n                [imax / 2 + 1, imax / 2 - 1, imax / 2 - 1],\n                [imax / 2 + 1, imax / 2, imax / 2 + 1],\n                [imax / 2 + 1, imax / 2, imax / 2 - 1],\n                [imax / 2 - 1, imax / 2, imax / 2],\n                [imax / 2 - 1, imax / 2 + 1, imax / 2],\n                [imax / 2 - 1, imax / 2 + 1, imax / 2 + 1],\n                [imax / 2 - 1, imax / 2 + 1, imax / 2 - 1],\n                [imax / 2 - 1, imax / 2 - 1, imax / 2],\n                [imax / 2 - 1, imax / 2 - 1, imax / 2 + 1],\n                [imax / 2 - 1, imax / 2 - 1, imax / 2 - 1],\n                [imax / 2 - 1, imax / 2, imax / 2 + 1],\n                [imax / 2 - 1, imax / 2, imax / 2 - 1],\n                # y +/- 1\n                [imax / 2, imax / 2 + 1, imax / 2],\n                [imax / 2, imax / 2 + 1, imax / 2 + 1],\n                [imax / 2, imax / 2 + 1, imax / 2 - 1],\n                [imax / 2, imax / 2 - 1, imax / 2],\n                [imax / 2, imax / 2 - 1, imax / 2 + 1],\n                [imax / 2, imax / 2 - 1, imax / 2 - 1],\n                # z +/- 1\n                [imax / 2, imax / 2, imax / 2 + 1],\n                [imax / 2, imax / 2, imax / 2 - 1],\n            ],\n            dtype=np.uint64,\n        ),\n        np.array(\n            [\n                # x +/- 1\n                [imax / 2 + 1, imax / 2, 0],\n                [imax / 2 + 1, imax / 2 + 1, 0],\n                [imax / 2 + 1, imax / 2 + 1, 1],\n                [imax / 2 + 1, imax / 2 + 1, imax - 1],\n                [imax / 2 + 1, imax / 2 - 1, 0],\n                [imax / 2 + 1, imax / 2 - 1, 1],\n                [imax / 2 + 1, imax / 2 - 1, imax - 1],\n                [imax / 2 + 1, imax / 2, 1],\n                [imax / 2 + 1, imax / 2, imax - 1],\n                [imax / 2 - 1, imax / 2, 0],\n                [imax / 2 - 1, imax / 2 + 1, 0],\n                [imax / 2 - 1, imax / 2 + 1, 1],\n                [imax / 2 - 1, imax / 2 + 1, imax - 1],\n                [imax / 2 - 1, imax / 2 - 1, 0],\n                [imax / 2 - 1, imax / 2 - 1, 1],\n                [imax / 2 - 1, imax / 2 - 1, imax - 1],\n                [imax / 2 - 1, imax / 2, 1],\n                [imax / 2 - 1, imax / 2, imax - 1],\n                # y +/- 1\n                [imax / 2, imax / 2 + 1, 0],\n                [imax / 2, imax / 2 + 1, 1],\n                [imax / 2, imax / 2 + 1, imax - 1],\n                [imax / 2, imax / 2 - 1, 0],\n                [imax / 2, imax / 2 - 1, 1],\n                [imax / 2, imax / 2 - 1, imax - 1],\n                # z +/- 1\n                [imax / 2, imax / 2, 0 + 1],\n                [imax / 2, imax / 2, imax - 1],\n            ],\n            dtype=np.uint64,\n        ),\n        np.array(\n            [\n                # x +/- 1\n                [imax / 2 + 1, imax / 2, imax],\n                [imax / 2 + 1, imax / 2 + 1, imax],\n                [imax / 2 + 1, imax / 2 + 1, 1],\n                [imax / 2 + 1, imax / 2 + 1, imax - 1],\n                [imax / 2 + 1, imax / 2 - 1, imax],\n                [imax / 2 + 1, imax / 2 - 1, 1],\n                [imax / 2 + 1, imax / 2 - 1, imax - 1],\n                [imax / 2 + 1, imax / 2, 1],\n                [imax / 2 + 1, imax / 2, imax - 1],\n                [imax / 2 - 1, imax / 2, imax],\n                [imax / 2 - 1, imax / 2 + 1, imax],\n                [imax / 2 - 1, imax / 2 + 1, 1],\n                [imax / 2 - 1, imax / 2 + 1, imax - 1],\n                [imax / 2 - 1, imax / 2 - 1, imax],\n                [imax / 2 - 1, imax / 2 - 1, 1],\n                [imax / 2 - 1, imax / 2 - 1, imax - 1],\n                [imax / 2 - 1, imax / 2, 1],\n                [imax / 2 - 1, imax / 2, imax - 1],\n                # y +/- 1\n                [imax / 2, imax / 2 + 1, imax],\n                [imax / 2, imax / 2 + 1, 1],\n                [imax / 2, imax / 2 + 1, imax - 1],\n                [imax / 2, imax / 2 - 1, imax],\n                [imax / 2, imax / 2 - 1, 1],\n                [imax / 2, imax / 2 - 1, imax - 1],\n                # z +/- 1\n                [imax / 2, imax / 2, 1],\n                [imax / 2, imax / 2, imax - 1],\n            ],\n            dtype=np.uint64,\n        ),\n    ]\n    mi = get_morton_indices(p)\n    N = mi.shape[0]\n    # Non-periodic\n    for i in range(N):\n        out = get_morton_neighbors(\n            np.array([mi[i]], dtype=np.uint64), order=order, periodic=False\n        )\n        ans = get_morton_indices(np.vstack([p[i, :], pn_non[i]]))\n        assert_array_equal(np.unique(out), np.unique(ans), err_msg=f\"Non-periodic: {i}\")\n    # Periodic\n    for i in range(N):\n        out = get_morton_neighbors(\n            np.array([mi[i]], dtype=np.uint64), order=order, periodic=True\n        )\n        ans = get_morton_indices(np.vstack([p[i, :], pn_per[i]]))\n        assert_array_equal(np.unique(out), np.unique(ans), err_msg=f\"Periodic: {i}\")\n\n\ndef test_dist():\n    from yt.utilities.lib.geometry_utils import dist\n\n    p = np.array([0.0, 0.0, 0.0], dtype=np.float64)\n    q = np.array([0.0, 0.0, 0.0], dtype=np.float64)\n    assert_equal(dist(p, q), 0.0)\n    p = np.array([0.0, 0.0, 0.0], dtype=np.float64)\n    q = np.array([1.0, 0.0, 0.0], dtype=np.float64)\n    assert_equal(dist(p, q), 1.0)\n    p = np.array([0.0, 0.0, 0.0], dtype=np.float64)\n    q = np.array([1.0, 1.0, 0.0], dtype=np.float64)\n    assert_equal(dist(p, q), np.sqrt(2.0))\n    p = np.array([0.0, 0.0, 0.0], dtype=np.float64)\n    q = np.array([1.0, 1.0, 1.0], dtype=np.float64)\n    assert_equal(dist(p, q), np.sqrt(3.0))\n\n\ndef test_knn_direct(seed=1):\n    from yt.utilities.lib.geometry_utils import knn_direct\n\n    np.random.seed(seed)\n    k = 64\n    N = 1e5\n    idx = np.arange(N, dtype=np.uint64)\n    rad = np.arange(N, dtype=np.float64)\n    pos = np.vstack(3 * [rad**2 / 3.0]).T\n    sort_shf = np.arange(N, dtype=np.uint64)\n    for _ in range(20):\n        np.random.shuffle(sort_shf)\n        sort_ans = np.argsort(sort_shf)[:k]\n        sort_out = knn_direct(pos[sort_shf, :], k, sort_ans[0], idx)\n        assert_array_equal(sort_out, sort_ans)\n\n\n# TODO: test of quadtree (.pxd)\n\n\ndef test_obtain_position_vector():\n    ds = fake_random_ds(\n        64, nprocs=8, fields=_fields, units=_units, negative=[False, True, True, True]\n    )\n\n    dd = ds.sphere((0.5, 0.5, 0.5), 0.2)\n\n    coords = obtain_position_vector(dd)\n\n    r = np.sqrt(np.sum(coords * coords, axis=0))\n\n    assert_array_less(r.max(), 0.2)\n\n    assert_array_less(0.0, r.min())\n\n\ndef test_obtain_relative_velocity_vector():\n    ds = fake_random_ds(\n        64, nprocs=8, fields=_fields, units=_units, negative=[False, True, True, True]\n    )\n\n    dd = ds.all_data()\n\n    vels = obtain_relative_velocity_vector(dd)\n\n    assert_array_equal(vels[0, :], dd[\"gas\", \"velocity_x\"])\n    assert_array_equal(vels[1, :], dd[\"gas\", \"velocity_y\"])\n    assert_array_equal(vels[2, :], dd[\"gas\", \"velocity_z\"])\n"
  },
  {
    "path": "yt/utilities/lib/tests/test_nn.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_equal\n\nfrom yt.utilities.lib.bounded_priority_queue import (\n    validate,\n    validate_nblist,\n    validate_pid,\n)\n\n\n# These test functions use utility functions in\n# yt.utilities.lib.bounded_priority_queue\n# to test functions which are not exposed at a python level\ndef test_bounded_priority_queue():\n    dists = validate()\n    answers = np.array([0.1, 0.001, -1.0, -1.0, -1.0])\n    assert_array_equal(answers, dists)\n\n\ndef test_bounded_priority_queue_pid():\n    dists, pids = validate_pid()\n    answers = np.array([0.1, 0.001, -1.0, -1.0, -1.0])\n    answers_pids = np.array([1, 10, -1, -1, -1])\n    assert_array_equal(answers, dists)\n    assert_array_equal(answers_pids, pids)\n\n\ndef test_neighbor_list():\n    data, pids = validate_nblist()\n    answers_data = np.array([1.0, 1.0, 1.0, 1.0])\n    answers_pids = np.array([0, 1, 2, 3])\n    assert_array_equal(answers_data, data)\n    assert_array_equal(answers_pids, pids)\n"
  },
  {
    "path": "yt/utilities/lib/tests/test_ragged_arrays.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import assert_rel_equal\nfrom yt.utilities.lib.ragged_arrays import index_unop\n\noperations = ((np.sum, \"sum\"), (np.prod, \"prod\"), (np.max, \"max\"), (np.min, \"min\"))\ndtypes = (\n    (-1e8, 1e8, \"float32\"),\n    (-1e8, 1e8, \"float64\"),\n    (-10000, 10000, \"int32\"),\n    (-100000000, 100000000, \"int64\"),\n)\n\n\ndef test_index_unop():\n    np.random.seed(0x4D3D3D3)\n    indices = np.arange(1000, dtype=\"int64\")\n    np.random.shuffle(indices)\n    sizes = np.array([200, 50, 50, 100, 32, 32, 32, 32, 32, 64, 376], dtype=\"int64\")\n    for mi, ma, dtype in dtypes:\n        for op, operation in operations:\n            # Create a random set of values\n            values = np.random.random(1000)\n            if operation != \"prod\":\n                values = values * ma + (ma - mi)\n            if operation == \"prod\" and dtype.startswith(\"int\"):\n                values = values.astype(dtype)\n                values[values != 0] = 1\n                values[values == 0] = -1\n            values = values.astype(dtype)\n            out_values = index_unop(values, indices, sizes, operation)\n            i = 0\n            for j, v in enumerate(sizes):\n                arr = values[indices[i : i + v]]\n                if dtype == \"float32\":\n                    # Numpy 1.9.1 changes the accumulator type to promote\n                    assert_rel_equal(op(arr), out_values[j], 6)\n                elif dtype == \"float64\":\n                    # Numpy 1.9.1 changes the accumulator type to promote\n                    assert_rel_equal(op(arr), out_values[j], 12)\n                else:\n                    assert_equal(op(arr), out_values[j])\n                i += v\n"
  },
  {
    "path": "yt/utilities/lib/tests/test_sample.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_allclose\n\nfrom yt.utilities.lib.particle_mesh_operations import CICSample_3\n\n\ndef test_sample():\n    grid = {}\n\n    dims = np.array([64, 64, 64], dtype=\"int32\")\n\n    inds = np.indices(dims)\n    grid[\"x\"] = inds[0] + 0.5\n    grid[\"y\"] = inds[1] + 0.5\n    grid[\"z\"] = inds[2] + 0.5\n\n    num_particles = np.int64(1000)\n\n    xp = np.random.uniform(low=1.0, high=63.0, size=num_particles)\n    yp = np.random.uniform(low=1.0, high=63.0, size=num_particles)\n    zp = np.random.uniform(low=1.0, high=63.0, size=num_particles)\n\n    xfield = np.zeros(num_particles)\n    yfield = np.zeros(num_particles)\n    zfield = np.zeros(num_particles)\n\n    dx = 1.0\n    le = np.zeros(3)\n\n    CICSample_3(xp, yp, zp, xfield, num_particles, grid[\"x\"], le, dims, dx)\n    CICSample_3(xp, yp, zp, yfield, num_particles, grid[\"y\"], le, dims, dx)\n    CICSample_3(xp, yp, zp, zfield, num_particles, grid[\"z\"], le, dims, dx)\n\n    assert_allclose(xp, xfield)\n    assert_allclose(yp, yfield)\n    assert_allclose(zp, zfield)\n"
  },
  {
    "path": "yt/utilities/lib/tsearch.c",
    "content": "/*\n * Tree search generalized from Knuth (6.2.2) Algorithm T just like\n * the AT&T man page says.\n *\n * The node_t structure is for internal use only, lint doesn't grok it.\n *\n * Written by reading the System V Interface Definition, not the code.\n *\n * Totally public domain.\n */\n/*LINTLIBRARY*/\n\n#include \"tsearch.h\"\n#include <stdlib.h>\n\ntypedef struct node_t {\n    char\t  *key;\n    struct node_t *left, *right;\n} node;\n\n/* find or insert datum into search tree */\nvoid *\ntsearch(const void *vkey, void **vrootp,\n    int (*compar)(const void *, const void *))\n{\n    node *q;\n    char *key = (char *)vkey;\n    node **rootp = (node **)vrootp;\n\n    if (rootp == (struct node_t **)0)\n\treturn ((void *)0);\n    while (*rootp != (struct node_t *)0) {\t/* Knuth's T1: */\n\tint r;\n\n\tif ((r = (*compar)(key, (*rootp)->key)) == 0)\t/* T2: */\n\t    return ((void *)*rootp);\t\t/* we found it! */\n\trootp = (r < 0) ?\n\t    &(*rootp)->left :\t\t/* T3: follow left branch */\n\t    &(*rootp)->right;\t\t/* T4: follow right branch */\n    }\n    q = (node *) malloc(sizeof(node));\t/* T5: key not found */\n    if (q != (struct node_t *)0) {\t/* make new node */\n\t*rootp = q;\t\t\t/* link new node to old */\n\tq->key = key;\t\t\t/* initialize new node */\n\tq->left = q->right = (struct node_t *)0;\n    }\n    return ((void *)q);\n}\n/* find datum in search tree */\nvoid *\ntfind(const void *vkey, void **vrootp,\n    int (*compar)(const void *, const void *))\n{\n\n    char *key = (char *)vkey;\n    node **rootp = (node **)vrootp;\n\n    if (rootp == (struct node_t **)0)\n\treturn ((void *)0);\n    while (*rootp != (struct node_t *)0) {\t/* Knuth's T1: */\n\tint r;\n\n\tif ((r = (*compar)(key, (*rootp)->key)) == 0)\t/* T2: */\n\t    return ((void *)*rootp);\t\t/* we found it! */\n\trootp = (r < 0) ?\n\t    &(*rootp)->left :\t\t/* T3: follow left branch */\n\t    &(*rootp)->right;\t\t/* T4: follow right branch */\n    }\n    return ((void *)0);\t/* T5: key not found */\n}\n\n\n/* delete node with given key */\nvoid *\ntdelete(const void *vkey, void **vrootp,\n    int (*compar)(const void *, const void *))\n{\n    node **rootp = (node **)vrootp;\n    char *key = (char *)vkey;\n    node *p = (node *)1;\n    node *q;\n    node *r;\n    int cmp;\n\n    if (rootp == (struct node_t **)0 || *rootp == (struct node_t *)0)\n\treturn ((struct node_t *)0);\n    while ((cmp = (*compar)(key, (*rootp)->key)) != 0) {\n\tp = *rootp;\n\trootp = (cmp < 0) ?\n\t    &(*rootp)->left :\t\t/* follow left branch */\n\t    &(*rootp)->right;\t\t/* follow right branch */\n\tif (*rootp == (struct node_t *)0)\n\t    return ((void *)0);\t\t/* key not found */\n    }\n    r = (*rootp)->right;\t\t\t/* D1: */\n    if ((q = (*rootp)->left) == (struct node_t *)0)\t/* Left (struct node_t *)0? */\n\tq = r;\n    else if (r != (struct node_t *)0) {\t\t/* Right link is null? */\n\tif (r->left == (struct node_t *)0) {\t/* D2: Find successor */\n\t    r->left = q;\n\t    q = r;\n\t} else {\t\t\t/* D3: Find (struct node_t *)0 link */\n\t    for (q = r->left; q->left != (struct node_t *)0; q = r->left)\n\t\tr = q;\n\t    r->left = q->right;\n\t    q->left = (*rootp)->left;\n\t    q->right = (*rootp)->right;\n\t}\n    }\n    free((struct node_t *) *rootp);\t/* D4: Free node */\n    *rootp = q;\t\t\t\t/* link parent to new node */\n    return(p);\n}\n"
  },
  {
    "path": "yt/utilities/lib/tsearch.h",
    "content": "/*\n * Tree search generalized from Knuth (6.2.2) Algorithm T just like\n * the AT&T man page says.\n *\n * The node_t structure is for internal use only, lint doesn't grok it.\n *\n * Written by reading the System V Interface Definition, not the code.\n *\n * Totally public domain.\n */\n/*LINTLIBRARY*/\n\n#ifndef TSEARCH_H\n#define TSEARCH_H\n\nvoid * tsearch(const void *vkey, void **vrootp,\n    int (*compar)(const void *, const void *));\n\nvoid * tfind(const void *vkey, void **vrootp,\n    int (*compar)(const void *, const void *));\n\nvoid * tdelete(const void *vkey, void **vrootp,\n    int (*compar)(const void *, const void *));\n\n\n#endif\n"
  },
  {
    "path": "yt/utilities/lib/vec3_ops.pxd",
    "content": "cimport cython\nfrom libc.math cimport sqrt\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline cython.floating dot(const cython.floating[3] a,\n                                const cython.floating[3] b) noexcept nogil:\n    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline void cross(const cython.floating[3] a,\n                       const cython.floating[3] b,\n                       cython.floating c[3]) noexcept nogil:\n    c[0] = a[1]*b[2] - a[2]*b[1]\n    c[1] = a[2]*b[0] - a[0]*b[2]\n    c[2] = a[0]*b[1] - a[1]*b[0]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline void subtract(const cython.floating[3] a,\n                          const cython.floating[3] b,\n                          cython.floating c[3]) noexcept nogil:\n    c[0] = a[0] - b[0]\n    c[1] = a[1] - b[1]\n    c[2] = a[2] - b[2]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline cython.floating distance(const cython.floating[3] a,\n                                     const cython.floating[3] b) noexcept nogil:\n    return sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2 +(a[2] - b[2])**2)\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline void fma(const cython.floating f,\n                     const cython.floating[3] a,\n                     const cython.floating[3] b,\n                     cython.floating[3] c) noexcept nogil:\n    c[0] = f * a[0] + b[0]\n    c[1] = f * a[1] + b[1]\n    c[2] = f * a[2] + b[2]\n\n\n@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True)\ncdef inline cython.floating L2_norm(const cython.floating[3] a) noexcept nogil:\n    return sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2])\n"
  },
  {
    "path": "yt/utilities/lib/volume_container.pxd",
    "content": "\"\"\"\nA volume container\n\n\n\n\n\"\"\"\n\n\ncimport numpy as np\n\n\ncdef struct VolumeContainer:\n    #-----------------------------------------------------------------------------\n    # Encapsulates a volume container used for volume rendering.\n    #\n    #    n_fields       int              : The number of fields available to the volume renderer.\n    #    data           np.float64_t**   : The data within the volume container.\n    #    mask           np.uint8_t*      : The mask of the volume container. It has dimensions one fewer in each direction than data.\n    #    left_edge      np.float64_t[3]  : The left edge of the volume container's bounding box.\n    #    right_edge     np.float64_t[3]  : The right edge of the volume container's bounding box.\n    #    np.float64_t   dds[3]           : The delta dimensions, such that dds[0] = ddx, dds[1] = ddy, dds[2] = ddz.\n    #    np.float64_t   idds[3]          : The inverse delta dimensions. Same as dds pattern, but the inverse. i.e. idds[0] = inv_ddx.\n    #    dims           int[3]           : The dimensions of the volume container. dims[0] = x, dims[1] = y, dims[2] = z.\n    #-----------------------------------------------------------------------------\n    int n_fields\n    np.float64_t **data\n    np.uint8_t *mask\n    np.float64_t left_edge[3]\n    np.float64_t right_edge[3]\n    np.float64_t dds[3]\n    np.float64_t idds[3]\n    int dims[3]\n\ncdef inline int vc_index(VolumeContainer *vc, int i, int j, int k):\n    #-----------------------------------------------------------------------------\n    # vc_index(VolumeContainer *vc, int i, int j, int k)\n    #    vc   VolumeContainer* : Pointer to the volume container to be indexed.\n    #    i    int              : The first dimension coordinate.\n    #    j    int              : The second dimension coordinate.\n    #    k    int              : The third dimension coordinates.\n    #\n    # Returns the 3-dimensional index in the volume container given coordinates i, j, k.\n    # This is used for 3-dimensional access in a flat container using C-ordering.\n    # This is calculated by:\n    #       vc_index = i * vc.dim[1] * vc.dims[2] + j * vc.dims[2] + k\n    # and then simplified (as shown below) by combining one multiplication operation.\n    #\n    # 2-dimensional example:\n    #       A 4 x 3 array may be represented as:\n    #                                      a = [0,  1,  2,  3,\n    #                                           4,  5,  6,  7,\n    #                                           8,  9,  10, 11]\n    #       or similarly, in a flat container in row-successive order as:\n    #                          a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]\n    #\n    # To access the coordinate at (1,1) in the flat container:\n    #                         i * dims[1] + j\n    #                       = 1 *   3     + 1\n    #                       = 4\n    # The 3-dimensional case (presented below) is similar.\n    #-----------------------------------------------------------------------------\n    return (i*vc.dims[1]+j)*vc.dims[2]+k\n\ncdef inline int vc_pos_index(VolumeContainer *vc, np.float64_t *spos):\n    cdef int index[3]\n    cdef int i\n    for i in range(3):\n        index[i] = <int> ((spos[i] - vc.left_edge[i]) * vc.idds[i])\n    return vc_index(vc, index[0], index[1], index[2])\n"
  },
  {
    "path": "yt/utilities/lib/write_array.pyx",
    "content": "\"\"\"\nFaster, cythonized file IO\n\n\n\n\"\"\"\n\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\nDTYPE = np.float64\nctypedef np.float64_t DTYPE_t\n\n@cython.boundscheck(False)\ndef write_3D_array(np.ndarray[DTYPE_t, ndim=3] data, fhandle):\n    assert data.dtype == DTYPE\n    cdef int Nx, Ny, Nz\n    Nx = data.shape[0]\n    Ny = data.shape[1]\n    Nz = data.shape[2]\n    cdef unsigned int i, j, k\n\n    for i in np.arange(Nz):\n        for j in np.arange(Ny):\n            for k in np.arange(Nx):\n                fhandle.write(str(data[k, j, i]) + '\\n')\n\n@cython.boundscheck(False)\ndef write_3D_vector_array(np.ndarray[DTYPE_t, ndim=3] data_x,\n                          np.ndarray[DTYPE_t, ndim=3] data_y,\n                          np.ndarray[DTYPE_t, ndim=3] data_z,\n                          fhandle):\n\n    assert data_x.dtype == DTYPE\n    cdef int Nx, Ny, Nz\n    Nx = data_x.shape[0]\n    Ny = data_x.shape[1]\n    Nz = data_x.shape[2]\n    cdef unsigned int i, j, k\n\n    for i in np.arange(Nz):\n        for j in np.arange(Ny):\n            for k in np.arange(Nx):\n                fx = data_x[k, j, i]\n                fy = data_y[k, j, i]\n                fz = data_z[k, j, i]\n                fhandle.write('{}    {}    {} \\n'.format(fx, fy, fz))\n"
  },
  {
    "path": "yt/utilities/linear_interpolators.py",
    "content": "import numpy as np\n\nimport yt.utilities.lib.interpolators as lib\nfrom yt.funcs import mylog\n\n\nclass UnilinearFieldInterpolator:\n    def __init__(self, table, boundaries, field_names, truncate=False):\n        r\"\"\"Initialize a 1D interpolator for field data.\n\n        table : array\n            The data table over which interpolation is performed.\n        boundaries: tuple or array\n            If a tuple, this should specify the upper and lower bounds\n            for the bins of the data table.  This assumes the bins are\n            evenly spaced.  If an array, this specifies the bins\n            explicitly.\n        field_names: str\n            Name of the field to be used as input data for interpolation.\n        truncate : bool\n            If False, an exception is raised if the input values are\n            outside the bounds of the table.  If True, extrapolation is\n            performed.\n\n        Examples\n        --------\n\n        ad = ds.all_data()\n        table_data = np.random.random(64)\n        interp = UnilinearFieldInterpolator(table_data, (0.0, 1.0), \"x\",\n                                            truncate=True)\n        field_data = interp(ad)\n\n        \"\"\"\n        self.table = table.astype(\"float64\")\n        self.truncate = truncate\n        self.x_name = field_names\n        if isinstance(boundaries, np.ndarray):\n            if boundaries.size != table.shape[0]:\n                mylog.error(\"Bins array not the same length as the data.\")\n                raise ValueError\n            self.x_bins = boundaries\n        else:\n            x0, x1 = boundaries\n            self.x_bins = np.linspace(x0, x1, table.shape[0], dtype=\"float64\")\n\n    def __call__(self, data_object):\n        orig_shape = data_object[self.x_name].shape\n        x_vals = data_object[self.x_name].ravel().astype(\"float64\")\n\n        x_i = (np.digitize(x_vals, self.x_bins) - 1).astype(\"int32\")\n        if np.any((x_i == -1) | (x_i == len(self.x_bins) - 1)):\n            if not self.truncate:\n                mylog.error(\n                    \"Sorry, but your values are outside \"\n                    \"the table!  Dunno what to do, so dying.\"\n                )\n                mylog.error(\"Error was in: %s\", data_object)\n                raise ValueError\n            else:\n                x_i = np.minimum(np.maximum(x_i, 0), len(self.x_bins) - 2)\n\n        my_vals = np.zeros(x_vals.shape, dtype=\"float64\")\n        lib.UnilinearlyInterpolate(self.table, x_vals, self.x_bins, x_i, my_vals)\n        return my_vals.reshape(orig_shape)\n\n\nclass BilinearFieldInterpolator:\n    def __init__(self, table, boundaries, field_names, truncate=False):\n        r\"\"\"Initialize a 2D interpolator for field data.\n\n        table : array\n            The data table over which interpolation is performed.\n        boundaries: tuple\n            Either a tuple of lower and upper bounds for the x and y bins\n            given as (x0, x1, y0, y1) or a tuple of two arrays containing the\n            x and y bins.\n        field_names: list\n            Names of the fields to be used as input data for interpolation.\n        truncate : bool\n            If False, an exception is raised if the input values are\n            outside the bounds of the table.  If True, extrapolation is\n            performed.\n\n        Examples\n        --------\n\n        ad = ds.all_data()\n        table_data = np.random.random((64, 64))\n        interp = BilinearFieldInterpolator(table_data, (0.0, 1.0, 0.0, 1.0),\n                                           [\"x\", \"y\"],\n                                           truncate=True)\n        field_data = interp(ad)\n\n        \"\"\"\n        self.table = table.astype(\"float64\")\n        self.truncate = truncate\n        self.x_name, self.y_name = field_names\n        if len(boundaries) == 4:\n            x0, x1, y0, y1 = boundaries\n            self.x_bins = np.linspace(x0, x1, table.shape[0], dtype=\"float64\")\n            self.y_bins = np.linspace(y0, y1, table.shape[1], dtype=\"float64\")\n        elif len(boundaries) == 2:\n            if boundaries[0].size != table.shape[0]:\n                mylog.error(\"X bins array not the same length as the data.\")\n                raise ValueError\n            if boundaries[1].size != table.shape[1]:\n                mylog.error(\"Y bins array not the same length as the data.\")\n                raise ValueError\n            self.x_bins = boundaries[0]\n            self.y_bins = boundaries[1]\n        else:\n            mylog.error(\n                \"Boundaries must be given as (x0, x1, y0, y1) or as (x_bins, y_bins)\"\n            )\n            raise ValueError\n\n    def __call__(self, data_object):\n        orig_shape = data_object[self.x_name].shape\n        x_vals = data_object[self.x_name].ravel().astype(\"float64\")\n        y_vals = data_object[self.y_name].ravel().astype(\"float64\")\n\n        x_i = (np.digitize(x_vals, self.x_bins) - 1).astype(\"int32\")\n        y_i = (np.digitize(y_vals, self.y_bins) - 1).astype(\"int32\")\n        if np.any((x_i == -1) | (x_i == len(self.x_bins) - 1)) or np.any(\n            (y_i == -1) | (y_i == len(self.y_bins) - 1)\n        ):\n            if not self.truncate:\n                mylog.error(\n                    \"Sorry, but your values are outside \"\n                    \"the table!  Dunno what to do, so dying.\"\n                )\n                mylog.error(\"Error was in: %s\", data_object)\n                raise ValueError\n            else:\n                x_i = np.minimum(np.maximum(x_i, 0), len(self.x_bins) - 2)\n                y_i = np.minimum(np.maximum(y_i, 0), len(self.y_bins) - 2)\n\n        my_vals = np.zeros(x_vals.shape, dtype=\"float64\")\n        lib.BilinearlyInterpolate(\n            self.table, x_vals, y_vals, self.x_bins, self.y_bins, x_i, y_i, my_vals\n        )\n        return my_vals.reshape(orig_shape)\n\n\nclass TrilinearFieldInterpolator:\n    def __init__(self, table, boundaries, field_names, truncate=False):\n        r\"\"\"Initialize a 3D interpolator for field data.\n\n        table : array\n            The data table over which interpolation is performed.\n        boundaries: tuple\n            Either a tuple of lower and upper bounds for the x, y, and z bins\n            given as (x0, x1, y0, y1, z0, z1) or a tuple of three arrays\n            containing the x, y, and z bins.\n        field_names: list\n            Names of the fields to be used as input data for interpolation.\n        truncate : bool\n            If False, an exception is raised if the input values are\n            outside the bounds of the table.  If True, extrapolation is\n            performed.\n\n        Examples\n        --------\n\n        ad = ds.all_data()\n        table_data = np.random.random((64, 64, 64))\n        interp = TrilinearFieldInterpolator(table_data,\n                                            (0.0, 1.0, 0.0, 1.0, 0.0, 1.0),\n                                            [\"x\", \"y\", \"z\"],\n                                            truncate=True)\n        field_data = interp(ad)\n\n        \"\"\"\n        self.table = table.astype(\"float64\")\n        self.truncate = truncate\n        self.x_name, self.y_name, self.z_name = field_names\n        if len(boundaries) == 6:\n            x0, x1, y0, y1, z0, z1 = boundaries\n            self.x_bins = np.linspace(x0, x1, table.shape[0], dtype=\"float64\")\n            self.y_bins = np.linspace(y0, y1, table.shape[1], dtype=\"float64\")\n            self.z_bins = np.linspace(z0, z1, table.shape[2], dtype=\"float64\")\n        elif len(boundaries) == 3:\n            if boundaries[0].size != table.shape[0]:\n                mylog.error(\"X bins array not the same length as the data.\")\n                raise ValueError\n            if boundaries[1].size != table.shape[1]:\n                mylog.error(\"Y bins array not the same length as the data.\")\n                raise ValueError\n            if boundaries[2].size != table.shape[2]:\n                mylog.error(\"Z bins array not the same length as the data.\")\n                raise ValueError\n            self.x_bins = boundaries[0]\n            self.y_bins = boundaries[1]\n            self.z_bins = boundaries[2]\n        else:\n            mylog.error(\n                \"Boundaries must be given as (x0, x1, y0, y1, z0, z1) \"\n                \"or as (x_bins, y_bins, z_bins)\"\n            )\n            raise ValueError\n\n    def __call__(self, data_object):\n        orig_shape = data_object[self.x_name].shape\n        x_vals = data_object[self.x_name].ravel().astype(\"float64\")\n        y_vals = data_object[self.y_name].ravel().astype(\"float64\")\n        z_vals = data_object[self.z_name].ravel().astype(\"float64\")\n\n        x_i = np.digitize(x_vals, self.x_bins).astype(\"int64\") - 1\n        y_i = np.digitize(y_vals, self.y_bins).astype(\"int64\") - 1\n        z_i = np.digitize(z_vals, self.z_bins).astype(\"int64\") - 1\n        if (\n            np.any((x_i == -1) | (x_i == len(self.x_bins) - 1))\n            or np.any((y_i == -1) | (y_i == len(self.y_bins) - 1))\n            or np.any((z_i == -1) | (z_i == len(self.z_bins) - 1))\n        ):\n            if not self.truncate:\n                mylog.error(\n                    \"Sorry, but your values are outside \"\n                    \"the table!  Dunno what to do, so dying.\"\n                )\n                mylog.error(\"Error was in: %s\", data_object)\n                raise ValueError\n            else:\n                x_i = np.minimum(np.maximum(x_i, 0), len(self.x_bins) - 2)\n                y_i = np.minimum(np.maximum(y_i, 0), len(self.y_bins) - 2)\n                z_i = np.minimum(np.maximum(z_i, 0), len(self.z_bins) - 2)\n\n        my_vals = np.zeros(x_vals.shape, dtype=\"float64\")\n        lib.TrilinearlyInterpolate(\n            self.table,\n            x_vals,\n            y_vals,\n            z_vals,\n            self.x_bins,\n            self.y_bins,\n            self.z_bins,\n            x_i,\n            y_i,\n            z_i,\n            my_vals,\n        )\n        return my_vals.reshape(orig_shape)\n\n\nclass QuadrilinearFieldInterpolator:\n    def __init__(self, table, boundaries, field_names, truncate=False):\n        r\"\"\"Initialize a 4D interpolator for field data.\n\n        table : array\n            The data table over which interpolation is performed.\n        boundaries: tuple\n            Either a tuple of lower and upper bounds for the x, y, z, and w bins\n            given as (x0, x1, y0, y1, z0, z1, w0, w1) or a tuple of four arrays\n            containing the x, y, z, and w bins.\n        field_names: list\n            Names of the fields to be used as input data for interpolation.\n        truncate : bool\n            If False, an exception is raised if the input values are\n            outside the bounds of the table.  If True, extrapolation is\n            performed.\n\n        Examples\n        --------\n        ad = ds.all_data()\n        table_data = np.random.random((64, 64, 64, 64))\n        interp = BilinearFieldInterpolator(table_data,\n                                           (0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0),\n                                           [\"x\", \"y\", \"z\", \"w\"],\n                                           truncate=True)\n        field_data = interp(ad)\n\n        \"\"\"\n        self.table = table.astype(\"float64\")\n        self.truncate = truncate\n        self.x_name, self.y_name, self.z_name, self.w_name = field_names\n        if len(boundaries) == 8:\n            x0, x1, y0, y1, z0, z1, w0, w1 = boundaries\n            self.x_bins = np.linspace(x0, x1, table.shape[0]).astype(\"float64\")\n            self.y_bins = np.linspace(y0, y1, table.shape[1]).astype(\"float64\")\n            self.z_bins = np.linspace(z0, z1, table.shape[2]).astype(\"float64\")\n            self.w_bins = np.linspace(w0, w1, table.shape[3]).astype(\"float64\")\n        elif len(boundaries) == 4:\n            if boundaries[0].size != table.shape[0]:\n                mylog.error(\"X bins array not the same length as the data.\")\n                raise ValueError\n            if boundaries[1].size != table.shape[1]:\n                mylog.error(\"Y bins array not the same length as the data.\")\n                raise ValueError\n            if boundaries[2].size != table.shape[2]:\n                mylog.error(\"Z bins array not the same length as the data.\")\n                raise ValueError\n            if boundaries[3].size != table.shape[3]:\n                mylog.error(\"W bins array not the same length as the data.\")\n                raise ValueError\n            self.x_bins = boundaries[0]\n            self.y_bins = boundaries[1]\n            self.z_bins = boundaries[2]\n            self.w_bins = boundaries[3]\n        else:\n            mylog.error(\n                \"Boundaries must be given as (x0, x1, y0, y1, z0, z1, w0, w1) \"\n                \"or as (x_bins, y_bins, z_bins, w_bins)\"\n            )\n            raise ValueError\n\n    def __call__(self, data_object):\n        orig_shape = data_object[self.x_name].shape\n        x_vals = data_object[self.x_name].ravel().astype(\"float64\")\n        y_vals = data_object[self.y_name].ravel().astype(\"float64\")\n        z_vals = data_object[self.z_name].ravel().astype(\"float64\")\n        w_vals = data_object[self.w_name].ravel().astype(\"float64\")\n\n        x_i = np.digitize(x_vals, self.x_bins).astype(\"int64\") - 1\n        y_i = np.digitize(y_vals, self.y_bins).astype(\"int64\") - 1\n        z_i = np.digitize(z_vals, self.z_bins).astype(\"int64\") - 1\n        w_i = np.digitize(w_vals, self.w_bins).astype(\"int64\") - 1\n        if (\n            np.any((x_i == -1) | (x_i == len(self.x_bins) - 1))\n            or np.any((y_i == -1) | (y_i == len(self.y_bins) - 1))\n            or np.any((z_i == -1) | (z_i == len(self.z_bins) - 1))\n            or np.any((w_i == -1) | (w_i == len(self.w_bins) - 1))\n        ):\n            if not self.truncate:\n                mylog.error(\n                    \"Sorry, but your values are outside \"\n                    \"the table!  Dunno what to do, so dying.\"\n                )\n                mylog.error(\"Error was in: %s\", data_object)\n                raise ValueError\n            else:\n                x_i = np.minimum(np.maximum(x_i, 0), len(self.x_bins) - 2)\n                y_i = np.minimum(np.maximum(y_i, 0), len(self.y_bins) - 2)\n                z_i = np.minimum(np.maximum(z_i, 0), len(self.z_bins) - 2)\n                w_i = np.minimum(np.maximum(w_i, 0), len(self.w_bins) - 2)\n\n        my_vals = np.zeros(x_vals.shape, dtype=\"float64\")\n        lib.QuadrilinearlyInterpolate(\n            self.table,\n            x_vals,\n            y_vals,\n            z_vals,\n            w_vals,\n            self.x_bins,\n            self.y_bins,\n            self.z_bins,\n            self.w_bins,\n            x_i,\n            y_i,\n            z_i,\n            w_i,\n            my_vals,\n        )\n        return my_vals.reshape(orig_shape)\n\n\ndef get_centers(ds, filename, center_cols, radius_col, unit=\"1\"):\n    \"\"\"\n    Return an iterator over EnzoSphere objects generated from the appropriate\n    columns in *filename*.  Optionally specify the *unit* radius is in.\n    \"\"\"\n    for line in open(filename):\n        if line.startswith(\"#\"):\n            continue\n        vals = line.split()\n        x, y, z = (float(vals[i]) for i in center_cols)\n        r = float(vals[radius_col])\n        yield ds.sphere([x, y, z], r / ds[unit])\n"
  },
  {
    "path": "yt/utilities/lodgeit.py",
    "content": "\"\"\"\n    LodgeIt!\n    ~~~~~~~~\n\n    A script that pastes stuff into the enzotools pastebin on\n    paste.enzotools.org.\n\n    Modified (very, very slightly) from the original script by the authors\n    below.\n\n    .lodgeitrc / _lodgeitrc\n    -----------------------\n\n    Under UNIX create a file called ``~/.lodgeitrc``, under Windows\n    create a file ``%APPDATA%/_lodgeitrc`` to override defaults::\n\n        language=default_language\n        clipboard=true/false\n        open_browser=true/false\n        encoding=fallback_charset\n\n    :authors: 2007-2008 Georg Brandl <georg@python.org>,\n              2006 Armin Ronacher <armin.ronacher@active-4.com>,\n              2006 Matt Good <matt@matt-good.net>,\n              2005 Raphael Slinckx <raphael@slinckx.net>\n\"\"\"\n\nimport os\nimport sys\nfrom optparse import OptionParser\n\nSCRIPT_NAME = os.path.basename(sys.argv[0])\nVERSION = \"0.3\"\nSERVICE_URL = \"http://paste.yt-project.org/\"\nSETTING_KEYS = [\"author\", \"title\", \"language\", \"private\", \"clipboard\", \"open_browser\"]\n\n# global server proxy\n_xmlrpc_service = None\n\n\ndef fail(msg, code):\n    \"\"\"Bail out with an error message.\"\"\"\n    print(f\"ERROR: {msg}\", file=sys.stderr)\n    sys.exit(code)\n\n\ndef load_default_settings():\n    \"\"\"Load the defaults from the lodgeitrc file.\"\"\"\n    settings = {\n        \"language\": None,\n        \"clipboard\": True,\n        \"open_browser\": False,\n        \"encoding\": \"iso-8859-15\",\n    }\n    rcfile = None\n    if os.name == \"posix\":\n        rcfile = os.path.expanduser(\"~/.lodgeitrc\")\n    elif os.name == \"nt\" and \"APPDATA\" in os.environ:\n        rcfile = os.path.expandvars(r\"$APPDATA\\_lodgeitrc\")\n    if rcfile:\n        try:\n            f = open(rcfile)\n            for line in f:\n                if line.strip()[:1] in \"#;\":\n                    continue\n                p = line.split(\"=\", 1)\n                if len(p) == 2:\n                    key = p[0].strip().lower()\n                    if key in settings:\n                        if key in (\"clipboard\", \"open_browser\"):\n                            settings[key] = p[1].strip().lower() in (\n                                \"true\",\n                                \"1\",\n                                \"on\",\n                                \"yes\",\n                            )\n                        else:\n                            settings[key] = p[1].strip()\n            f.close()\n        except OSError:\n            pass\n    settings[\"tags\"] = []\n    settings[\"title\"] = None\n    return settings\n\n\ndef make_utf8(text, encoding):\n    \"\"\"Convert a text to UTF-8, brute-force.\"\"\"\n    try:\n        u = str(text, \"utf-8\")\n        uenc = \"utf-8\"\n    except UnicodeError:\n        try:\n            u = str(text, encoding)\n            uenc = \"utf-8\"\n        except UnicodeError:\n            u = str(text, \"iso-8859-15\", \"ignore\")\n            uenc = \"iso-8859-15\"\n    try:\n        import chardet\n    except ImportError:\n        return u.encode(\"utf-8\")\n    d = chardet.detect(text)\n    if d[\"encoding\"] == uenc:\n        return u.encode(\"utf-8\")\n    return str(text, d[\"encoding\"], \"ignore\").encode(\"utf-8\")\n\n\ndef get_xmlrpc_service():\n    \"\"\"Create the XMLRPC server proxy and cache it.\"\"\"\n    global _xmlrpc_service\n    import xmlrpc.client\n\n    if _xmlrpc_service is None:\n        try:\n            _xmlrpc_service = xmlrpc.client.ServerProxy(\n                SERVICE_URL + \"xmlrpc/\", allow_none=True\n            )\n        except Exception as err:\n            fail(f\"Could not connect to Pastebin: {err}\", -1)\n    return _xmlrpc_service\n\n\ndef copy_url(url):\n    \"\"\"Copy the url into the clipboard.\"\"\"\n    # try windows first\n    try:\n        import win32clipboard\n    except ImportError:\n        # then give pbcopy a try.  do that before gtk because\n        # gtk might be installed on os x but nobody is interested\n        # in the X11 clipboard there.\n        from subprocess import PIPE, Popen\n\n        try:\n            client = Popen([\"pbcopy\"], stdin=PIPE)\n        except OSError:\n            try:\n                import pygtk\n\n                pygtk.require(\"2.0\")\n                import gobject\n                import gtk\n            except ImportError:\n                return\n            gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD).set_text(url)\n            gobject.idle_add(gtk.main_quit)\n            gtk.main()\n        else:\n            client.stdin.write(url)\n            client.stdin.close()\n            client.wait()\n    else:\n        win32clipboard.OpenClipboard()\n        win32clipboard.EmptyClipboard()\n        win32clipboard.SetClipboardText(url)\n        win32clipboard.CloseClipboard()\n\n\ndef open_webbrowser(url):\n    \"\"\"Open a new browser window.\"\"\"\n    import webbrowser\n\n    webbrowser.open(url)\n\n\ndef language_exists(language):\n    \"\"\"Check if a language alias exists.\"\"\"\n    xmlrpc = get_xmlrpc_service()\n    langs = xmlrpc.pastes.getLanguages()\n    return language in langs\n\n\ndef get_mimetype(data, filename):\n    \"\"\"Try to get MIME type from data.\"\"\"\n    try:\n        import gnomevfs\n    except ImportError:\n        from mimetypes import guess_type\n\n        if filename:\n            return guess_type(filename)[0]\n    else:\n        if filename:\n            return gnomevfs.get_mime_type(os.path.abspath(filename))\n        return gnomevfs.get_mime_type_for_data(data)\n\n\ndef print_languages():\n    \"\"\"Print a list of all supported languages, with description.\"\"\"\n    xmlrpc = get_xmlrpc_service()\n    languages = xmlrpc.pastes.getLanguages().items()\n\n    def cmp(x, y):\n        # emulate Python2's builtin cmp function\n        # https://docs.python.org/2.7/library/functions.html#cmp\n        # https://docs.python.org/3/whatsnew/3.0.html#ordering-comparisons\n        return (x > y) - (x < y)\n\n    languages.sort(lambda a, b: cmp(a[1].lower(), b[1].lower()))\n    print(\"Supported Languages:\")\n    for alias, name in languages:\n        print(f\"    {alias:<30}{name}\")\n\n\ndef download_paste(uid):\n    \"\"\"Download a paste given by ID.\"\"\"\n    xmlrpc = get_xmlrpc_service()\n    paste = xmlrpc.pastes.getPaste(uid)\n    if not paste:\n        fail(f'Paste \"{uid}\" does not exist.', 5)\n    code = paste[\"code\"]\n    print(code)\n\n\ndef create_paste(code, language, filename, mimetype, private):\n    \"\"\"Create a new paste.\"\"\"\n    xmlrpc = get_xmlrpc_service()\n    rv = xmlrpc.pastes.newPaste(language, code, None, filename, mimetype, private)\n    if not rv:\n        fail(\"Could not create paste. Something went wrong on the server side.\", 4)\n    return rv\n\n\ndef compile_paste(filenames, langopt):\n    \"\"\"Create a single paste out of zero, one or multiple files.\"\"\"\n\n    def read_file(f):\n        try:\n            return f.read()\n        finally:\n            f.close()\n\n    mime = \"\"\n    lang = langopt or \"\"\n    if not filenames:\n        data = read_file(sys.stdin)\n        if not langopt:\n            mime = get_mimetype(data, \"\") or \"\"\n        fname = \"\"\n    elif len(filenames) == 1:\n        fname = filenames[0]\n        data = read_file(open(filenames[0], \"rb\"))\n        if not langopt:\n            mime = get_mimetype(data, filenames[0]) or \"\"\n    else:\n        result = []\n        for fname in filenames:\n            data = read_file(open(fname, \"rb\"))\n            if langopt:\n                result.append(f\"### {fname} [{langopt}]\\n\\n\")\n            else:\n                result.append(f\"### {fname}\\n\\n\")\n            result.append(data)\n            result.append(\"\\n\\n\")\n        data = \"\".join(result)\n        lang = \"multi\"\n    return data, lang, fname, mime\n\n\ndef main(\n    filename,\n    languages=False,\n    language=None,\n    encoding=\"utf-8\",\n    open_browser=False,\n    private=False,\n    clipboard=False,\n    download=None,\n):\n    \"\"\"Paste a given script into a pastebin using the Lodgeit tool.\"\"\"\n\n    #    usage = ('Usage: %%prog [options] [FILE ...]\\n\\n'\n    #             'Read the files and paste their contents to %s.\\n'\n    #             'If no file is given, read from standard input.\\n'\n    #             'If multiple files are given, they are put into a single paste.'\n    #             % SERVICE_URL)\n    #    parser = OptionParser(usage=usage)\n    #\n    #    settings = load_default_settings()\n    #\n    #    parser.add_option('-v', '--version', action='store_true',\n    #                      help='Print script version')\n    #    parser.add_option('-L', '--languages', action='store_true', default=False,\n    #                      help='Retrieve a list of supported languages')\n    #    parser.add_option('-l', '--language', default=settings['language'],\n    #                      help='Used syntax highlighter for the file')\n    #    parser.add_option('-e', '--encoding', default=settings['encoding'],\n    #                      help='Specify the encoding of a file (default is '\n    #                           'utf-8 or guessing if available)')\n    #    parser.add_option('-b', '--open-browser', dest='open_browser',\n    #                      action='store_true',\n    #                      default=settings['open_browser'],\n    #                      help='Open the paste in a web browser')\n    #    parser.add_option('-p', '--private', action='store_true', default=False,\n    #                      help='Paste as private')\n    #    parser.add_option('--no-clipboard', dest='clipboard',\n    #                      action='store_false',\n    #                      default=settings['clipboard'],\n    #                      help=\"Don't copy the url into the clipboard\")\n    #    parser.add_option('--download', metavar='UID',\n    #                      help='Download a given paste')\n    #\n    #    opts, args = parser.parse_args()\n    #\n    if languages:\n        print_languages()\n        return\n    elif download:\n        download_paste(download)\n        return\n\n    # check language if given\n    if language and not language_exists(language):\n        print(f\"Language {language} is not supported.\")\n        return\n\n    # load file(s)\n    args = [filename]\n    try:\n        data, language, filename, mimetype = compile_paste(args, language)\n    except Exception as err:\n        fail(f\"Error while reading the file(s): {err}\", 2)\n    if not data:\n        fail(\"Aborted, no content to paste.\", 4)\n\n    # create paste\n    code = make_utf8(data, encoding).decode(\"utf-8\")\n    pid = create_paste(code, language, filename, mimetype, private)\n    url = f\"{SERVICE_URL}show/{pid}/\"\n    print(url)\n    if open_browser:\n        open_webbrowser(url)\n    if clipboard:\n        copy_url(url)\n"
  },
  {
    "path": "yt/utilities/logger.py",
    "content": "import logging\nimport sys\nfrom collections.abc import Callable\n\nfrom yt.utilities.configure import YTConfig, configuration_callbacks\n\n_yt_sh: logging.StreamHandler | None = None\n_original_emitter: Callable[[logging.LogRecord], None] | None = None\n\n\ndef set_log_level(level):\n    \"\"\"\n    Select which minimal logging level should be displayed.\n\n    Parameters\n    ----------\n    level: int or str\n        Possible values by increasing level:\n        0 or \"notset\"\n        1 or \"all\"\n        10 or \"debug\"\n        20 or \"info\"\n        30 or \"warning\"\n        40 or \"error\"\n        50 or \"critical\"\n    \"\"\"\n    # this is a user-facing interface to avoid importing from yt.utilities in user code.\n\n    if isinstance(level, str):\n        level = level.upper()\n\n    if level == \"ALL\":  # non-standard alias\n        level = 1\n    ytLogger.setLevel(level)\n    ytLogger.debug(\"Set log level to %s\", level)\n\n\nytLogger = logging.getLogger(\"yt\")\n\n\nclass DuplicateFilter(logging.Filter):\n    \"\"\"A filter that removes duplicated successive log entries.\"\"\"\n\n    # source\n    # https://stackoverflow.com/questions/44691558/suppress-multiple-messages-with-same-content-in-python-logging-module-aka-log-co\n    def filter(self, record):\n        current_log = (record.module, record.levelno, record.msg, record.args)\n        if current_log != getattr(self, \"last_log\", None):\n            self.last_log = current_log\n            return True\n        return False\n\n\nytLogger.addFilter(DuplicateFilter())\n\n\nclass DeprecatedFieldFilter(logging.Filter):\n    \"\"\"A filter that suppresses repeated logging of deprecated field warnings\"\"\"\n\n    def __init__(self, name=\"\"):\n        self.logged_fields = []\n        super().__init__(name=name)\n\n    def filter(self, record):\n        if not record.msg.startswith(\"The Derived Field\"):\n            return True\n\n        field = record.args[0]\n        if field in self.logged_fields:\n            return False\n\n        self.logged_fields.append(field)\n        return True\n\n\nytLogger.addFilter(DeprecatedFieldFilter())\n\n# This next bit is grabbed from:\n# http://stackoverflow.com/questions/384076/how-can-i-make-the-python-logging-output-to-be-colored\n\n\ndef add_coloring_to_emit_ansi(fn):\n    # add methods we need to the class\n    def new(*args):\n        levelno = args[0].levelno\n        if levelno >= 50:\n            color = \"\\x1b[31m\"  # red\n        elif levelno >= 40:\n            color = \"\\x1b[31m\"  # red\n        elif levelno >= 30:\n            color = \"\\x1b[33m\"  # yellow\n        elif levelno >= 20:\n            color = \"\\x1b[32m\"  # green\n        elif levelno >= 10:\n            color = \"\\x1b[35m\"  # pink\n        else:\n            color = \"\\x1b[0m\"  # normal\n        ln = color + args[0].levelname + \"\\x1b[0m\"\n        args[0].levelname = ln\n        return fn(*args)\n\n    return new\n\n\nufstring = \"%(name)-3s: [%(levelname)-9s] %(asctime)s %(message)s\"\ncfstring = \"%(name)-3s: [%(levelname)-18s] %(asctime)s %(message)s\"\n\n\ndef colorize_logging():\n    f = logging.Formatter(cfstring)\n    ytLogger.handlers[0].setFormatter(f)\n    ytLogger.handlers[0].emit = add_coloring_to_emit_ansi(ytLogger.handlers[0].emit)\n\n\ndef uncolorize_logging():\n    global _original_emitter, _yt_sh\n    if None not in (_original_emitter, _yt_sh):\n        f = logging.Formatter(ufstring)\n        ytLogger.handlers[0].setFormatter(f)\n        _yt_sh.emit = _original_emitter\n\n\ndef disable_stream_logging():\n    if len(ytLogger.handlers) > 0:\n        ytLogger.removeHandler(ytLogger.handlers[0])\n    h = logging.NullHandler()\n    ytLogger.addHandler(h)\n\n\ndef _runtime_configuration(ytcfg: YTConfig) -> None:\n    # only run this at the end of yt.__init__, after yt.config.ytcfg was instantiated\n\n    global _original_emitter, _yt_sh\n\n    if ytcfg.get(\"yt\", \"stdout_stream_logging\"):\n        stream = sys.stdout\n    else:\n        stream = sys.stderr\n\n    _level = min(max(ytcfg.get(\"yt\", \"log_level\"), 0), 50)\n\n    if ytcfg.get(\"yt\", \"suppress_stream_logging\"):\n        disable_stream_logging()\n    else:\n        _yt_sh = logging.StreamHandler(stream=stream)\n        # create formatter and add it to the handlers\n        formatter = logging.Formatter(ufstring)\n        _yt_sh.setFormatter(formatter)\n        # add the handler to the logger\n        ytLogger.addHandler(_yt_sh)\n        ytLogger.setLevel(_level)\n        ytLogger.propagate = False\n\n        _original_emitter = _yt_sh.emit\n\n        if ytcfg.get(\"yt\", \"colored_logs\"):\n            colorize_logging()\n\n\nconfiguration_callbacks.append(_runtime_configuration)\n"
  },
  {
    "path": "yt/utilities/math_utils.py",
    "content": "import math\n\nimport numpy as np\n\nfrom yt.units.yt_array import YTArray\n\nprec_accum = {\n    int: np.int64,\n    np.int8: np.int64,\n    np.int16: np.int64,\n    np.int32: np.int64,\n    np.int64: np.int64,\n    np.uint8: np.uint64,\n    np.uint16: np.uint64,\n    np.uint32: np.uint64,\n    np.uint64: np.uint64,\n    float: np.float64,\n    np.float16: np.float64,\n    np.float32: np.float64,\n    np.float64: np.float64,\n    complex: np.complex128,\n    np.complex64: np.complex128,\n    np.complex128: np.complex128,\n    np.dtype(\"int\"): np.int64,\n    np.dtype(\"int8\"): np.int64,\n    np.dtype(\"int16\"): np.int64,\n    np.dtype(\"int32\"): np.int64,\n    np.dtype(\"int64\"): np.int64,\n    np.dtype(\"uint8\"): np.uint64,\n    np.dtype(\"uint16\"): np.uint64,\n    np.dtype(\"uint32\"): np.uint64,\n    np.dtype(\"uint64\"): np.uint64,\n    np.dtype(\"float\"): np.float64,\n    np.dtype(\"float16\"): np.float64,\n    np.dtype(\"float32\"): np.float64,\n    np.dtype(\"float64\"): np.float64,\n    np.dtype(\"complex\"): np.complex128,\n    np.dtype(\"complex64\"): np.complex128,\n    np.dtype(\"complex128\"): np.complex128,\n}\n\n\ndef periodic_position(pos, ds):\n    r\"\"\"Assuming periodicity, find the periodic position within the domain.\n\n    Parameters\n    ----------\n    pos : array\n        An array of floats.\n\n    ds : ~yt.data_objects.static_output.Dataset\n        A simulation static output.\n\n    Examples\n    --------\n    >>> a = np.array([1.1, 0.5, 0.5])\n    >>> data = {\"Density\": np.ones([32, 32, 32])}\n    >>> ds = load_uniform_grid(data, [32, 32, 32], 1.0)\n    >>> ppos = periodic_position(a, ds)\n    >>> ppos\n    array([ 0.1,  0.5,  0.5])\n    \"\"\"\n\n    off = (pos - ds.domain_left_edge) % ds.domain_width\n    return ds.domain_left_edge + off\n\n\ndef periodic_dist(a, b, period, periodicity=(True, True, True)):\n    r\"\"\"Find the Euclidean periodic distance between two sets of points.\n\n    Parameters\n    ----------\n    a : array or list\n        Either an ndim long list of coordinates corresponding to a single point\n        or an (ndim, npoints) list of coordinates for many points in space.\n\n    b : array of list\n        Either an ndim long list of coordinates corresponding to a single point\n        or an (ndim, npoints) list of coordinates for many points in space.\n\n    period : float or array or list\n        If the volume is symmetrically periodic, this can be a single float,\n        otherwise an array or list of floats giving the periodic size of the\n        volume for each dimension.\n\n    periodicity : An ndim-element tuple of booleans\n        If an entry is true, the domain is assumed to be periodic along\n        that direction.\n\n    Examples\n    --------\n    >>> a = [0.1, 0.1, 0.1]\n    >>> b = [0.9, 0, 9, 0.9]\n    >>> period = 1.0\n    >>> dist = periodic_dist(a, b, 1.0)\n    >>> dist\n    0.346410161514\n    \"\"\"\n    a = np.array(a)\n    b = np.array(b)\n    period = np.array(period)\n\n    if period.size == 1:\n        period = np.array([period, period, period])\n\n    if a.shape != b.shape:\n        raise RuntimeError(\"Arrays must be the same shape.\")\n\n    if period.shape != a.shape and len(a.shape) > 1:\n        n_tup = tuple(1 for i in range(a.ndim - 1))\n        period = np.tile(np.reshape(period, (a.shape[0],) + n_tup), (1,) + a.shape[1:])\n    elif len(a.shape) == 1:\n        a = np.reshape(a, (a.shape[0],) + (1, 1))\n        b = np.reshape(b, (a.shape[0],) + (1, 1))\n        period = np.reshape(period, (a.shape[0],) + (1, 1))\n\n    c = np.empty((2,) + a.shape, dtype=\"float64\")\n    c[0, :] = np.abs(a - b)\n\n    p_directions = [i for i, p in enumerate(periodicity) if p]\n    np_directions = [i for i, p in enumerate(periodicity) if not p]\n    for d in p_directions:\n        c[1, d, :] = period[d, :] - np.abs(a - b)[d, :]\n    for d in np_directions:\n        c[1, d, :] = c[0, d, :]\n\n    d = np.amin(c, axis=0) ** 2\n    r2 = d.sum(axis=0)\n    if r2.size == 1:\n        return np.sqrt(r2[0, 0])\n    return np.sqrt(r2)\n\n\ndef periodic_ray(start, end, left=None, right=None):\n    \"\"\"\n    periodic_ray(start, end, left=None, right=None)\n\n    Break up periodic ray into non-periodic segments.\n    Accepts start and end points of periodic ray as YTArrays.\n    Accepts optional left and right edges of periodic volume as YTArrays.\n    Returns a list of lists of coordinates, where each element of the\n    top-most list is a 2-list of start coords and end coords of the\n    non-periodic ray:\n\n    [[[x0start,y0start,z0start], [x0end, y0end, z0end]],\n     [[x1start,y1start,z1start], [x1end, y1end, z1end]],\n     ...,]\n\n    Parameters\n    ----------\n    start : array\n        The starting coordinate of the ray.\n    end : array\n        The ending coordinate of the ray.\n    left : optional, array\n        The left corner of the periodic domain. If not given, an array\n        of zeros with same size as the starting coordinate us used.\n    right : optional, array\n        The right corner of the periodic domain. If not given, an array\n        of ones with same size as the starting coordinate us used.\n\n    Examples\n    --------\n    >>> import yt\n    >>> start = yt.YTArray([0.5, 0.5, 0.5])\n    >>> end = yt.YTArray([1.25, 1.25, 1.25])\n    >>> periodic_ray(start, end)\n    [\n        [\n            YTArray([0.5, 0.5, 0.5]) (dimensionless),\n            YTArray([1., 1., 1.]) (dimensionless)\n        ],\n        [\n            YTArray([0., 0., 0.]) (dimensionless),\n            YTArray([0.25, 0.25, 0.25]) (dimensionless)\n        ]\n     ]\n    \"\"\"\n\n    if left is None:\n        left = np.zeros(start.shape)\n    if right is None:\n        right = np.ones(start.shape)\n    dim = right - left\n\n    vector = end - start\n    wall = np.zeros_like(start)\n    close = np.zeros(start.shape, dtype=object)\n\n    left_bound = vector < 0\n    right_bound = vector > 0\n    no_bound = vector == 0.0\n    bound = vector != 0.0\n\n    wall[left_bound] = left[left_bound]\n    close[left_bound] = np.max\n    wall[right_bound] = right[right_bound]\n    close[right_bound] = np.min\n    wall[no_bound] = np.inf\n    close[no_bound] = np.min\n\n    segments = []\n    this_start = start.copy()\n    this_end = end.copy()\n    t = 0.0\n    tolerance = 1e-6\n    while t < 1.0 - tolerance:\n        hit_left = (this_start <= left) & (vector < 0)\n        if (hit_left).any():\n            this_start[hit_left] += dim[hit_left]\n            this_end[hit_left] += dim[hit_left]\n        hit_right = (this_start >= right) & (vector > 0)\n        if (hit_right).any():\n            this_start[hit_right] -= dim[hit_right]\n            this_end[hit_right] -= dim[hit_right]\n\n        nearest = vector.unit_array * np.array(\n            [close[q]([this_end[q], wall[q]]) for q in range(start.size)]\n        )\n        dt = ((nearest - this_start) / vector)[bound].min()\n        now = this_start + vector * dt\n        close_enough = np.abs(now - nearest) / np.abs(vector.max()) < 1e-10\n        now[close_enough] = nearest[close_enough]\n        segments.append([this_start.copy(), now.copy()])\n        this_start = now.copy()\n        t += dt\n\n    return segments\n\n\ndef euclidean_dist(a, b):\n    r\"\"\"Find the Euclidean distance between two points.\n\n    Parameters\n    ----------\n    a : array or list\n        Either an ndim long list of coordinates corresponding to a single point\n        or an (ndim, npoints) list of coordinates for many points in space.\n\n    b : array or list\n        Either an ndim long list of coordinates corresponding to a single point\n        or an (ndim, npoints) list of coordinates for many points in space.\n\n    Examples\n    --------\n    >>> a = [0.1, 0.1, 0.1]\n    >>> b = [0.9, 0, 9, 0.9]\n    >>> period = 1.0\n    >>> dist = euclidean_dist(a, b)\n    >>> dist\n    1.38564064606\n\n    \"\"\"\n    a = np.array(a)\n    b = np.array(b)\n    if a.shape != b.shape:\n        RuntimeError(\"Arrays must be the same shape.\")\n    c = a.copy()\n    np.subtract(c, b, c)\n    np.power(c, 2, c)\n    c = c.sum(axis=0)\n    if isinstance(c, np.ndarray):\n        np.sqrt(c, c)\n    else:\n        # This happens if a and b only have one entry.\n        c = math.sqrt(c)\n    return c\n\n\ndef rotate_vector_3D(a, dim, angle):\n    r\"\"\"Rotates the elements of an array around an axis by some angle.\n\n    Given an array of 3D vectors a, this rotates them around a coordinate axis\n    by a clockwise angle. An alternative way to think about it is the\n    coordinate axes are rotated counterclockwise, which changes the directions\n    of the vectors accordingly.\n\n    Parameters\n    ----------\n    a : array\n        An array of 3D vectors with dimension Nx3.\n\n    dim : integer\n        A integer giving the axis around which the vectors will be rotated.\n        (x, y, z) = (0, 1, 2).\n\n    angle : float\n        The angle in radians through which the vectors will be rotated\n        clockwise.\n\n    Examples\n    --------\n    >>> a = np.array([[1, 1, 0], [1, 0, 1], [0, 1, 1], [1, 1, 1], [3, 4, 5]])\n    >>> b = rotate_vector_3D(a, 2, np.pi / 2)\n    >>> print(b)\n    [[  1.00000000e+00  -1.00000000e+00   0.00000000e+00]\n    [  6.12323400e-17  -1.00000000e+00   1.00000000e+00]\n    [  1.00000000e+00   6.12323400e-17   1.00000000e+00]\n    [  1.00000000e+00  -1.00000000e+00   1.00000000e+00]\n    [  4.00000000e+00  -3.00000000e+00   5.00000000e+00]]\n\n    \"\"\"\n    mod = False\n    if len(a.shape) == 1:\n        mod = True\n        a = np.array([a])\n    if a.shape[1] != 3:\n        raise ValueError(\"The second dimension of the array a must be == 3!\")\n    if dim == 0:\n        R = np.array(\n            [\n                [1, 0, 0],\n                [0, np.cos(angle), np.sin(angle)],\n                [0, -np.sin(angle), np.cos(angle)],\n            ]\n        )\n    elif dim == 1:\n        R = np.array(\n            [\n                [np.cos(angle), 0, -np.sin(angle)],\n                [0, 1, 0],\n                [np.sin(angle), 0, np.cos(angle)],\n            ]\n        )\n    elif dim == 2:\n        R = np.array(\n            [\n                [np.cos(angle), np.sin(angle), 0],\n                [-np.sin(angle), np.cos(angle), 0],\n                [0, 0, 1],\n            ]\n        )\n    else:\n        raise ValueError(\"dim must be 0, 1, or 2!\")\n    if mod:\n        return np.dot(R, a.T).T[0]\n    else:\n        return np.dot(R, a.T).T\n\n\ndef modify_reference_frame(CoM, L, P=None, V=None):\n    r\"\"\"Rotates and translates data into a new reference frame to make\n    calculations easier.\n\n    This is primarily useful for calculations of halo data.\n    The data is translated into the center of mass frame.\n    Next, it is rotated such that the angular momentum vector for the data\n    is aligned with the z-axis. Put another way, if one calculates the angular\n    momentum vector on the data that comes out of this function, it will\n    always be along the positive z-axis.\n    If the center of mass is re-calculated, it will be at the origin.\n\n    Parameters\n    ----------\n    CoM : array\n        The center of mass in 3D.\n\n    L : array\n        The angular momentum vector.\n\n    Optional\n    --------\n\n    P : array\n        The positions of the data to be modified (i.e. particle or grid cell\n        positions). The array should be Nx3.\n\n    V : array\n        The velocities of the data to be modified (i.e. particle or grid cell\n        velocities). The array should be Nx3.\n\n    Returns\n    -------\n    L : array\n        The angular momentum vector equal to [0, 0, 1] modulo machine error.\n\n    P : array\n        The modified positional data. Only returned if P is not None\n\n    V : array\n        The modified velocity data. Only returned if V is not None\n\n    Examples\n    --------\n    >>> CoM = np.array([0.5, 0.5, 0.5])\n    >>> L = np.array([1, 0, 0])\n    >>> P = np.array([[1, 0.5, 0.5], [0, 0.5, 0.5], [0.5, 0.5, 0.5], [0, 0, 0]])\n    >>> V = p.copy()\n    >>> LL, PP, VV = modify_reference_frame(CoM, L, P, V)\n    >>> LL\n    array([  6.12323400e-17,   0.00000000e+00,   1.00000000e+00])\n    >>> PP\n    array([[  3.06161700e-17,   0.00000000e+00,   5.00000000e-01],\n           [ -3.06161700e-17,   0.00000000e+00,  -5.00000000e-01],\n           [  0.00000000e+00,   0.00000000e+00,   0.00000000e+00],\n           [  5.00000000e-01,  -5.00000000e-01,  -5.00000000e-01]])\n    >>> VV\n    array([[ -5.00000000e-01,   5.00000000e-01,   1.00000000e+00],\n           [ -5.00000000e-01,   5.00000000e-01,   3.06161700e-17],\n           [ -5.00000000e-01,   5.00000000e-01,   5.00000000e-01],\n           [  0.00000000e+00,   0.00000000e+00,   0.00000000e+00]])\n\n    \"\"\"\n    # First translate the positions to center of mass reference frame.\n    if P is not None:\n        P = P - CoM\n\n    # is the L vector pointing in the Z direction?\n    if np.inner(L[:2], L[:2]) == 0.0:\n        # the reason why we need to explicitly check for the above\n        # is that formula is used in denominator\n        # this just checks if we need to flip the z axis or not\n        if L[2] < 0.0:\n            # this is just a simple flip in direction of the z axis\n            if P is not None:\n                P = -P\n            if V is not None:\n                V = -V\n\n        # return the values\n        if V is None and P is not None:\n            return L, P\n        elif P is None and V is not None:\n            return L, V\n        else:\n            return L, P, V\n\n    # Normal vector is not aligned with simulation Z axis\n    # Therefore we are going to have to apply a rotation\n    # Now find the angle between modified L and the x-axis.\n    LL = L.copy()\n    LL[2] = 0.0\n    theta = np.arccos(np.inner(LL, [1.0, 0.0, 0.0]) / np.inner(LL, LL) ** 0.5)\n    if L[1] < 0.0:\n        theta = -theta\n    # Now rotate all the position, velocity, and L vectors by this much around\n    # the z axis.\n    if P is not None:\n        P = rotate_vector_3D(P, 2, theta)\n    if V is not None:\n        V = rotate_vector_3D(V, 2, theta)\n    L = rotate_vector_3D(L, 2, theta)\n    # Now find the angle between L and the z-axis.\n    theta = np.arccos(np.inner(L, [0.0, 0.0, 1.0]) / np.inner(L, L) ** 0.5)\n    # This time we rotate around the y axis.\n    if P is not None:\n        P = rotate_vector_3D(P, 1, theta)\n    if V is not None:\n        V = rotate_vector_3D(V, 1, theta)\n    L = rotate_vector_3D(L, 1, theta)\n\n    # return the values\n    if V is None and P is not None:\n        return L, P\n    elif P is None and V is not None:\n        return L, V\n    else:\n        return L, P, V\n\n\ndef compute_rotational_velocity(CoM, L, P, V):\n    r\"\"\"Computes the rotational velocity for some data around an axis.\n\n    This is primarily for halo computations.\n    Given some data, this computes the circular rotational velocity of each\n    point (particle) in reference to the axis defined by the angular momentum\n    vector.\n    This is accomplished by converting the reference frame of the center of\n    mass of the halo.\n\n    Parameters\n    ----------\n    CoM : array\n        The center of mass in 3D.\n\n    L : array\n        The angular momentum vector.\n\n    P : array\n        The positions of the data to be modified (i.e. particle or grid cell\n        positions). The array should be Nx3.\n\n    V : array\n        The velocities of the data to be modified (i.e. particle or grid cell\n        velocities). The array should be Nx3.\n\n    Returns\n    -------\n    v : array\n        An array N elements long that gives the circular rotational velocity\n        for each datum (particle).\n\n    Examples\n    --------\n    >>> CoM = np.array([0, 0, 0])\n    >>> L = np.array([0, 0, 1])\n    >>> P = np.array([[1, 0, 0], [1, 1, 1], [0, 0, 1], [1, 1, 0]])\n    >>> V = np.array([[0, 1, 10], [-1, -1, -1], [1, 1, 1], [1, -1, -1]])\n    >>> circV = compute_rotational_velocity(CoM, L, P, V)\n    >>> circV\n    array([ 1.        ,  0.        ,  0.        ,  1.41421356])\n\n    \"\"\"\n    # First we translate into the simple coordinates.\n    L, P, V = modify_reference_frame(CoM, L, P, V)\n    # Find the vector in the plane of the galaxy for each position point\n    # that is perpendicular to the radial vector.\n    radperp = np.cross([0, 0, 1], P)\n    # Find the component of the velocity along the radperp vector.\n    # Unf., I don't think there's a better way to do this.\n    res = np.empty(V.shape[0], dtype=\"float64\")\n    for i, rp in enumerate(radperp):\n        temp = np.dot(rp, V[i]) / np.dot(rp, rp) * rp\n        res[i] = np.dot(temp, temp) ** 0.5\n    return res\n\n\ndef compute_parallel_velocity(CoM, L, P, V):\n    r\"\"\"Computes the parallel velocity for some data around an axis.\n\n    This is primarily for halo computations.\n    Given some data, this computes the velocity component along the angular\n    momentum vector.\n    This is accomplished by converting the reference frame of the center of\n    mass of the halo.\n\n    Parameters\n    ----------\n    CoM : array\n        The center of mass in 3D.\n\n    L : array\n        The angular momentum vector.\n\n    P : array\n        The positions of the data to be modified (i.e. particle or grid cell\n        positions). The array should be Nx3.\n\n    V : array\n        The velocities of the data to be modified (i.e. particle or grid cell\n        velocities). The array should be Nx3.\n\n    Returns\n    -------\n    v : array\n        An array N elements long that gives the parallel velocity for\n        each datum (particle).\n\n    Examples\n    --------\n    >>> CoM = np.array([0, 0, 0])\n    >>> L = np.array([0, 0, 1])\n    >>> P = np.array([[1, 0, 0], [1, 1, 1], [0, 0, 1], [1, 1, 0]])\n    >>> V = np.array([[0, 1, 10], [-1, -1, -1], [1, 1, 1], [1, -1, -1]])\n    >>> paraV = compute_parallel_velocity(CoM, L, P, V)\n    >>> paraV\n    array([10, -1,  1, -1])\n\n    \"\"\"\n    # First we translate into the simple coordinates.\n    L, P, V = modify_reference_frame(CoM, L, P, V)\n    # And return just the z-axis velocities.\n    return V[:, 2]\n\n\ndef compute_radial_velocity(CoM, L, P, V):\n    r\"\"\"Computes the radial velocity for some data around an axis.\n\n    This is primarily for halo computations.\n    Given some data, this computes the radial velocity component for the data.\n    This is accomplished by converting the reference frame of the center of\n    mass of the halo.\n\n    Parameters\n    ----------\n    CoM : array\n        The center of mass in 3D.\n\n    L : array\n        The angular momentum vector.\n\n    P : array\n        The positions of the data to be modified (i.e. particle or grid cell\n        positions). The array should be Nx3.\n\n    V : array\n        The velocities of the data to be modified (i.e. particle or grid cell\n        velocities). The array should be Nx3.\n\n    Returns\n    -------\n    v : array\n        An array N elements long that gives the radial velocity for\n        each datum (particle).\n\n    Examples\n    --------\n    >>> CoM = np.array([0, 0, 0])\n    >>> L = np.array([0, 0, 1])\n    >>> P = np.array([[1, 0, 0], [1, 1, 1], [0, 0, 1], [1, 1, 0]])\n    >>> V = np.array([[0, 1, 10], [-1, -1, -1], [1, 1, 1], [1, -1, -1]])\n    >>> radV = compute_radial_velocity(CoM, L, P, V)\n    >>> radV\n    array([ 1.        ,  1.41421356 ,  0.        ,  0.])\n\n    \"\"\"\n    # First we translate into the simple coordinates.\n    L, P, V = modify_reference_frame(CoM, L, P, V)\n    # We find the tangential velocity by dotting the velocity vector\n    # with the cylindrical radial vector for this point.\n    # Unf., I don't think there's a better way to do this.\n    P[:, 2] = 0\n    res = np.empty(V.shape[0], dtype=\"float64\")\n    for i, rad in enumerate(P):\n        temp = np.dot(rad, V[i]) / np.dot(rad, rad) * rad\n        res[i] = np.dot(temp, temp) ** 0.5\n    return res\n\n\ndef compute_cylindrical_radius(CoM, L, P, V):\n    r\"\"\"Compute the radius for some data around an axis in cylindrical\n    coordinates.\n\n    This is primarily for halo computations.\n    Given some data, this computes the cylindrical radius for each point.\n    This is accomplished by converting the reference frame of the center of\n    mass of the halo.\n\n    Parameters\n    ----------\n    CoM : array\n        The center of mass in 3D.\n\n    L : array\n        The angular momentum vector.\n\n    P : array\n        The positions of the data to be modified (i.e. particle or grid cell\n        positions). The array should be Nx3.\n\n    V : array\n        The velocities of the data to be modified (i.e. particle or grid cell\n        velocities). The array should be Nx3.\n\n    Returns\n    -------\n    cyl_r : array\n        An array N elements long that gives the radial velocity for\n        each datum (particle).\n\n    Examples\n    --------\n    >>> CoM = np.array([0, 0, 0])\n    >>> L = np.array([0, 0, 1])\n    >>> P = np.array([[1, 0, 0], [1, 1, 1], [0, 0, 1], [1, 1, 0]])\n    >>> V = np.array([[0, 1, 10], [-1, -1, -1], [1, 1, 1], [1, -1, -1]])\n    >>> cyl_r = compute_cylindrical_radius(CoM, L, P, V)\n    >>> cyl_r\n    array([ 1.        ,  1.41421356,  0.        ,  1.41421356])\n\n    \"\"\"\n    # First we translate into the simple coordinates.\n    L, P, V = modify_reference_frame(CoM, L, P, V)\n    # Demote all the positions to the z=0 plane, which makes the distance\n    # calculation very easy.\n    P[:, 2] = 0\n    return np.sqrt((P * P).sum(axis=1))\n\n\ndef ortho_find(vec1):\n    r\"\"\"Find two complementary orthonormal vectors to a given vector.\n\n    For any given non-zero vector, there are infinite pairs of vectors\n    orthonormal to it.  This function gives you one arbitrary pair from\n    that set along with the normalized version of the original vector.\n\n    Parameters\n    ----------\n    vec1 : array_like\n           An array or list to represent a 3-vector.\n\n    Returns\n    -------\n    vec1 : array\n           The original 3-vector array after having been normalized.\n\n    vec2 : array\n           A 3-vector array which is orthonormal to vec1.\n\n    vec3 : array\n           A 3-vector array which is orthonormal to vec1 and vec2.\n\n    Raises\n    ------\n    ValueError\n           If input vector is the zero vector.\n\n    Notes\n    -----\n    Our initial vector is `vec1` which consists of 3 components: `x1`, `y1`,\n    and `z1`.  ortho_find determines a vector, `vec2`, which is orthonormal\n    to `vec1` by finding a vector which has a zero-value dot-product with\n    `vec1`.\n\n    .. math::\n\n       vec1 \\cdot vec2 = x_1 x_2 + y_1 y_2 + z_1 z_2 = 0\n\n    As a starting point, we arbitrarily choose `vec2` to have `x2` = 1,\n    `y2` = 0:\n\n    .. math::\n\n       vec1 \\cdot vec2 = x_1 + (z_1 z_2) = 0\n\n       \\rightarrow z_2 = -(x_1 / z_1)\n\n    Of course, this will fail if `z1` = 0, in which case, let's say use\n    `z2` = 1 and `x2` = 0:\n\n    .. math::\n\n       \\rightarrow y_2 = -(z_1 / y_1)\n\n    Similarly, if `y1` = 0, this case will fail, in which case we use\n    `y2` = 1 and `z2` = 0:\n\n    .. math::\n\n       \\rightarrow x_2 = -(y_1 / x_1)\n\n    Since we don't allow `vec1` to be zero, all cases are accounted for.\n\n    Producing `vec3`, the complementary orthonormal vector to `vec1` and `vec2`\n    is accomplished by simply taking the cross product of `vec1` and `vec2`.\n\n    Examples\n    --------\n    >>> a = [1.0, 2.0, 3.0]\n    >>> a, b, c = ortho_find(a)\n    >>> a\n    array([ 0.26726124,  0.53452248,  0.80178373])\n    >>> b\n    array([ 0.9486833 ,  0.        , -0.31622777])\n    >>> c\n    array([-0.16903085,  0.84515425, -0.50709255])\n    \"\"\"\n    vec1 = np.array(vec1, dtype=np.float64)\n    # Normalize\n    norm = np.sqrt(np.vdot(vec1, vec1))\n    if norm == 0:\n        raise ValueError(\"Zero vector used as input.\")\n    vec1 /= norm\n    x1 = vec1[0]\n    y1 = vec1[1]\n    z1 = vec1[2]\n    if z1 != 0:\n        x2 = 1.0\n        y2 = 0.0\n        z2 = -(x1 / z1)\n        norm2 = (1.0 + z2**2.0) ** (0.5)\n    elif y1 != 0:\n        x2 = 0.0\n        z2 = 1.0\n        y2 = -(z1 / y1)\n        norm2 = (1.0 + y2**2.0) ** (0.5)\n    else:\n        y2 = 1.0\n        z2 = 0.0\n        x2 = -(y1 / x1)\n        norm2 = (1.0 + z2**2.0) ** (0.5)\n    vec2 = np.array([x2, y2, z2])\n    vec2 /= norm2\n    vec3 = np.cross(vec1, vec2)\n    return vec1, vec2, vec3\n\n\ndef quartiles(a, axis=None, out=None, overwrite_input=False):\n    \"\"\"\n    Compute the quartile values (25% and 75%) along the specified axis\n    in the same way that the numpy.median calculates the median (50%) value\n    alone a specified axis.  Check numpy.median for details, as it is\n    virtually the same algorithm.\n\n    Returns an array of the quartiles of the array elements [lower quartile,\n    upper quartile].\n\n    Parameters\n    ----------\n    a : array_like\n        Input array or object that can be converted to an array.\n    axis : {None, int}, optional\n        Axis along which the quartiles are computed. The default (axis=None)\n        is to compute the quartiles along a flattened version of the array.\n    out : ndarray, optional\n        Alternative output array in which to place the result. It must\n        have the same shape and buffer length as the expected output,\n        but the type (of the output) will be cast if necessary.\n    overwrite_input : {False, True}, optional\n       If True, then allow use of memory of input array (a) for\n       calculations. The input array will be modified by the call to\n       quartiles. This will save memory when you do not need to preserve\n       the contents of the input array. Treat the input as undefined,\n       but it will probably be fully or partially sorted. Default is\n       False. Note that, if `overwrite_input` is True and the input\n       is not already an ndarray, an error will be raised.\n\n    Returns\n    -------\n    quartiles : ndarray\n        A new 2D array holding the result (unless `out` is specified, in\n        which case that array is returned instead).  If the input contains\n        integers, or floats of smaller precision than 64, then the output\n        data-type is float64.  Otherwise, the output data-type is the same\n        as that of the input.\n\n    See Also\n    --------\n    numpy.median, numpy.mean, numpy.percentile\n\n    Notes\n    -----\n    Given a vector V of length N, the quartiles of V are the 25% and 75% values\n    of a sorted copy of V, ``V_sorted`` - i.e., ``V_sorted[(N-1)/4]`` and\n    ``3*V_sorted[(N-1)/4]``, when N is odd.  When N is even, it is the average\n    of the two values bounding these values of ``V_sorted``.\n\n    Examples\n    --------\n    >>> a = np.arange(100).reshape(10, 10)\n    >>> a\n    array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],\n           [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],\n           [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],\n           [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],\n           [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],\n           [50, 51, 52, 53, 54, 55, 56, 57, 58, 59],\n           [60, 61, 62, 63, 64, 65, 66, 67, 68, 69],\n           [70, 71, 72, 73, 74, 75, 76, 77, 78, 79],\n           [80, 81, 82, 83, 84, 85, 86, 87, 88, 89],\n           [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]])\n    >>> mu.quartiles(a)\n    array([ 24.5,  74.5])\n    >>> mu.quartiles(a, axis=0)\n    array([[ 15.,  16.,  17.,  18.,  19.,  20.,  21.,  22.,  23.,  24.],\n           [ 65.,  66.,  67.,  68.,  69.,  70.,  71.,  72.,  73.,  74.]])\n    >>> mu.quartiles(a, axis=1)\n    array([[  1.5,  11.5,  21.5,  31.5,  41.5,  51.5,  61.5,  71.5,  81.5,\n             91.5],\n           [  6.5,  16.5,  26.5,  36.5,  46.5,  56.5,  66.5,  76.5,  86.5,\n             96.5]])\n    \"\"\"\n    if overwrite_input:\n        if axis is None:\n            a_sorted = sorted(a.ravel())\n        else:\n            a.sort(axis=axis)\n            a_sorted = a\n    else:\n        a_sorted = np.sort(a, axis=axis)\n    if axis is None:\n        axis = 0\n    indexer = [slice(None)] * a_sorted.ndim\n    indices = [int(a_sorted.shape[axis] / 4), int(a_sorted.shape[axis] * 0.75)]\n    result = []\n    for index in indices:\n        if a_sorted.shape[axis] % 2 == 1:\n            # index with slice to allow mean (below) to work\n            indexer[axis] = slice(index, index + 1)\n        else:\n            indexer[axis] = slice(index - 1, index + 1)\n        # special cases for small arrays\n        if a_sorted.shape[axis] == 2:\n            # index with slice to allow mean (below) to work\n            indexer[axis] = slice(index, index + 1)\n        # Use mean in odd and even case to coerce data type\n        # and check, use out array.\n        result.append(np.mean(a_sorted[tuple(indexer)], axis=axis, out=out))\n    return np.array(result)\n\n\ndef get_perspective_matrix(fovy, aspect, z_near, z_far):\n    \"\"\"\n    Given a field of view in radians, an aspect ratio, and a near\n    and far plane distance, this routine computes the transformation matrix\n    corresponding to perspective projection using homogeneous coordinates.\n\n    Parameters\n    ----------\n    fovy : scalar\n        The angle in degrees of the field of view.\n\n    aspect : scalar\n        The aspect ratio of width / height for the projection.\n\n    z_near : scalar\n        The distance of the near plane from the camera.\n\n    z_far : scalar\n        The distance of the far plane from the camera.\n\n    Returns\n    -------\n    persp_matrix : ndarray\n        A new 4x4 2D array. Represents a perspective transformation\n        in homogeneous coordinates. Note that this matrix does not\n        actually perform the projection. After multiplying a 4D\n        vector of the form (x_0, y_0, z_0, 1.0), the point will be\n        transformed to some (x_1, y_1, z_1, w). The final projection\n        is applied by performing a divide by w, that is\n        (x_1/w, y_1/w, z_1/w, w/w). The matrix uses a row-major\n        ordering, rather than the column major ordering typically\n        used by OpenGL.\n\n    Notes\n    -----\n    The usage of 4D homogeneous coordinates is for OpenGL and GPU\n    hardware that automatically performs the divide by w operation.\n    See the following for more details about the OpenGL perspective matrices.\n\n    https://www.tomdalling.com/blog/modern-opengl/explaining-homogenous-coordinates-and-projective-geometry/\n    http://www.songho.ca/opengl/gl_projectionmatrix.html\n\n    \"\"\"\n\n    tan_half_fovy = np.tan(np.radians(fovy) / 2)\n\n    result = np.zeros((4, 4), dtype=\"float32\", order=\"C\")\n    # result[0][0] = 1 / (aspect * tan_half_fovy)\n    # result[1][1] = 1 / tan_half_fovy\n    # result[2][2] = - (z_far + z_near) / (z_far - z_near)\n    # result[3][2] = -1\n    # result[2][3] = -(2 * z_far * z_near) / (z_far - z_near)\n\n    f = z_far\n    n = z_near\n\n    t = tan_half_fovy * n\n    b = -t * aspect\n    r = t * aspect\n    l = -t * aspect\n\n    result[0][0] = (2 * n) / (r - l)\n    result[2][0] = (r + l) / (r - l)\n    result[1][1] = (2 * n) / (t - b)\n    result[1][2] = (t + b) / (t - b)\n    result[2][2] = -(f + n) / (f - n)\n    result[2][3] = -2 * f * n / (f - n)\n    result[3][2] = -1\n\n    return result\n\n\ndef get_orthographic_matrix(maxr, aspect, z_near, z_far):\n    \"\"\"\n    Given a field of view in radians, an aspect ratio, and a near\n    and far plane distance, this routine computes the transformation matrix\n    corresponding to perspective projection using homogeneous coordinates.\n\n    Parameters\n    ----------\n    maxr : scalar\n        should be ``max(|x|, |y|)``\n\n    aspect : scalar\n        The aspect ratio of width / height for the projection.\n\n    z_near : scalar\n        The distance of the near plane from the camera.\n\n    z_far : scalar\n        The distance of the far plane from the camera.\n\n    Returns\n    -------\n    persp_matrix : ndarray\n        A new 4x4 2D array. Represents a perspective transformation\n        in homogeneous coordinates. Note that this matrix does not\n        actually perform the projection. After multiplying a 4D\n        vector of the form (x_0, y_0, z_0, 1.0), the point will be\n        transformed to some (x_1, y_1, z_1, w). The final projection\n        is applied by performing a divide by w, that is\n        (x_1/w, y_1/w, z_1/w, w/w). The matrix uses a row-major\n        ordering, rather than the column major ordering typically\n        used by OpenGL.\n\n    Notes\n    -----\n    The usage of 4D homogeneous coordinates is for OpenGL and GPU\n    hardware that automatically performs the divide by w operation.\n    See the following for more details about the OpenGL perspective matrices.\n\n    http://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/orthographic-projection-matrix\n    https://www.tomdalling.com/blog/modern-opengl/explaining-homogenous-coordinates-and-projective-geometry/\n    http://www.songho.ca/opengl/gl_projectionmatrix.html\n\n    \"\"\"\n\n    r = maxr * aspect\n    t = maxr\n    l = -r\n    b = -t\n\n    result = np.zeros((4, 4), dtype=\"float32\", order=\"C\")\n    result[0][0] = 2.0 / (r - l)\n    result[1][1] = 2.0 / (t - b)\n    result[2][2] = -2.0 / (z_far - z_near)\n    result[3][3] = 1\n\n    result[3][0] = -(r + l) / (r - l)\n    result[3][1] = -(t + b) / (t - b)\n    result[3][2] = -(z_far + z_near) / (z_far - z_near)\n\n    return result\n\n\ndef get_lookat_matrix(eye, center, up):\n    \"\"\"\n    Given the position of a camera, the point it is looking at, and\n    an up-direction. Computes the lookat matrix that moves all vectors\n    such that the camera is at the origin of the coordinate system,\n    looking down the z-axis.\n\n    Parameters\n    ----------\n    eye : array_like\n        The position of the camera. Must be 3D.\n\n    center : array_like\n        The location that the camera is looking at. Must be 3D.\n\n    up : array_like\n        The direction that is considered up for the camera. Must be\n        3D.\n\n    Returns\n    -------\n    lookat_matrix : ndarray\n        A new 4x4 2D array in homogeneous coordinates. This matrix\n        moves all vectors in the same way required to move the camera\n        to the origin of the coordinate system, with it pointing down\n        the negative z-axis.\n\n    \"\"\"\n\n    eye = np.array(eye)\n    center = np.array(center)\n    up = np.array(up)\n\n    f = (center - eye) / np.linalg.norm(center - eye)\n    s = np.cross(f, up) / np.linalg.norm(np.cross(f, up))\n    u = np.cross(s, f)\n\n    result = np.zeros((4, 4), dtype=\"float32\", order=\"C\")\n\n    result[0][0] = s[0]\n    result[0][1] = s[1]\n    result[0][2] = s[2]\n    result[1][0] = u[0]\n    result[1][1] = u[1]\n    result[1][2] = u[2]\n    result[2][0] = -f[0]\n    result[2][1] = -f[1]\n    result[2][2] = -f[2]\n    result[0][3] = -np.dot(s, eye)\n    result[1][3] = -np.dot(u, eye)\n    result[2][3] = np.dot(f, eye)\n    result[3][3] = 1.0\n    return result\n\n\ndef get_translate_matrix(dx, dy, dz):\n    \"\"\"\n    Given a movement amount for each coordinate, creates a translation\n    matrix that moves the vector by each amount.\n\n    Parameters\n    ----------\n    dx : scalar\n        A translation amount for the x-coordinate\n\n    dy : scalar\n        A translation amount for the y-coordinate\n\n    dz : scalar\n        A translation amount for the z-coordinate\n\n    Returns\n    -------\n    trans_matrix : ndarray\n        A new 4x4 2D array. Represents a translation by dx, dy\n        and dz in each coordinate respectively.\n    \"\"\"\n    result = np.zeros((4, 4), dtype=\"float32\", order=\"C\")\n\n    result[0][0] = 1.0\n    result[1][1] = 1.0\n    result[2][2] = 1.0\n    result[3][3] = 1.0\n\n    result[0][3] = dx\n    result[1][3] = dy\n    result[2][3] = dz\n\n    return result\n\n\ndef get_scale_matrix(dx, dy, dz):\n    \"\"\"\n    Given a scaling factor for each coordinate, returns a matrix that\n    corresponds to the given scaling amounts.\n\n    Parameters\n    ----------\n    dx : scalar\n        A scaling factor for the x-coordinate.\n\n    dy : scalar\n        A scaling factor for the y-coordinate.\n\n    dz : scalar\n        A scaling factor for the z-coordinate.\n\n    Returns\n    -------\n    scale_matrix : ndarray\n        A new 4x4 2D array. Represents a scaling by dx, dy, and dz\n        in each coordinate respectively.\n    \"\"\"\n    result = np.zeros((4, 4), dtype=\"float32\", order=\"C\")\n\n    result[0][0] = dx\n    result[1][1] = dy\n    result[2][2] = dz\n    result[3][3] = 1\n\n    return result\n\n\ndef get_rotation_matrix(theta, rot_vector):\n    \"\"\"\n    Given an angle theta and a 3D vector rot_vector, this routine\n    computes the rotation matrix corresponding to rotating theta\n    radians about rot_vector.\n\n    Parameters\n    ----------\n    theta : scalar\n        The angle in radians.\n\n    rot_vector : array_like\n        The axis of rotation.  Must be 3D.\n\n    Returns\n    -------\n    rot_matrix : ndarray\n         A new 3x3 2D array.  This is the representation of a\n         rotation of theta radians about rot_vector in the simulation\n         box coordinate frame\n\n    See Also\n    --------\n    ortho_find\n\n    Examples\n    --------\n    >>> a = [0, 1, 0]\n    >>> theta = 0.785398163  # pi/4\n    >>> rot = mu.get_rotation_matrix(theta, a)\n    >>> rot\n    array([[ 0.70710678,  0.        ,  0.70710678],\n           [ 0.        ,  1.        ,  0.        ],\n           [-0.70710678,  0.        ,  0.70710678]])\n    >>> np.dot(rot, a)\n    array([ 0.,  1.,  0.])\n    # since a is an eigenvector by construction\n    >>> np.dot(rot, [1, 0, 0])\n    array([ 0.70710678,  0.        , -0.70710678])\n    \"\"\"\n\n    ux = rot_vector[0]\n    uy = rot_vector[1]\n    uz = rot_vector[2]\n    cost = np.cos(theta)\n    sint = np.sin(theta)\n\n    R = np.array(\n        [\n            [\n                cost + ux**2 * (1 - cost),\n                ux * uy * (1 - cost) - uz * sint,\n                ux * uz * (1 - cost) + uy * sint,\n            ],\n            [\n                uy * ux * (1 - cost) + uz * sint,\n                cost + uy**2 * (1 - cost),\n                uy * uz * (1 - cost) - ux * sint,\n            ],\n            [\n                uz * ux * (1 - cost) - uy * sint,\n                uz * uy * (1 - cost) + ux * sint,\n                cost + uz**2 * (1 - cost),\n            ],\n        ]\n    )\n\n    return R\n\n\ndef quaternion_mult(q1, q2):\n    \"\"\"\n\n    Multiply two quaternions. The inputs are 4-component numpy arrays\n    in the order [w, x, y, z].\n\n    \"\"\"\n    w = q1[0] * q2[0] - q1[1] * q2[1] - q1[2] * q2[2] - q1[3] * q2[3]\n    x = q1[0] * q2[1] + q1[1] * q2[0] + q1[2] * q2[3] - q1[3] * q2[2]\n    y = q1[0] * q2[2] + q1[2] * q2[0] + q1[3] * q2[1] - q1[1] * q2[3]\n    z = q1[0] * q2[3] + q1[3] * q2[0] + q1[1] * q2[2] - q1[2] * q2[1]\n    return np.array([w, x, y, z])\n\n\ndef quaternion_to_rotation_matrix(quaternion):\n    \"\"\"\n\n    This converts a quaternion representation of on orientation to\n    a rotation matrix. The input is a 4-component numpy array in\n    the order [w, x, y, z], and the output is a 3x3 matrix stored\n    as a 2D numpy array.  We follow the approach in\n    \"3D Math Primer for Graphics and Game Development\" by\n    Dunn and Parberry.\n\n    \"\"\"\n\n    w = quaternion[0]\n    x = quaternion[1]\n    y = quaternion[2]\n    z = quaternion[3]\n\n    R = np.empty((3, 3), dtype=np.float64)\n\n    R[0][0] = 1.0 - 2.0 * y**2 - 2.0 * z**2\n    R[0][1] = 2.0 * x * y + 2.0 * w * z\n    R[0][2] = 2.0 * x * z - 2.0 * w * y\n\n    R[1][0] = 2.0 * x * y - 2.0 * w * z\n    R[1][1] = 1.0 - 2.0 * x**2 - 2.0 * z**2\n    R[1][2] = 2.0 * y * z + 2.0 * w * x\n\n    R[2][0] = 2.0 * x * z + 2.0 * w * y\n    R[2][1] = 2.0 * y * z - 2.0 * w * x\n    R[2][2] = 1.0 - 2.0 * x**2 - 2.0 * y**2\n\n    return R\n\n\ndef rotation_matrix_to_quaternion(rot_matrix):\n    \"\"\"\n\n    Convert a rotation matrix-based representation of an\n    orientation to a quaternion. The input should be a\n    3x3 rotation matrix, while the output will be a\n    4-component numpy array. We follow the approach in\n    \"3D Math Primer for Graphics and Game Development\" by\n    Dunn and Parberry.\n\n    \"\"\"\n    m11 = rot_matrix[0][0]\n    m12 = rot_matrix[0][1]\n    m13 = rot_matrix[0][2]\n    m21 = rot_matrix[1][0]\n    m22 = rot_matrix[1][1]\n    m23 = rot_matrix[1][2]\n    m31 = rot_matrix[2][0]\n    m32 = rot_matrix[2][1]\n    m33 = rot_matrix[2][2]\n\n    four_w_squared_minus_1 = m11 + m22 + m33\n    four_x_squared_minus_1 = m11 - m22 - m33\n    four_y_squared_minus_1 = m22 - m11 - m33\n    four_z_squared_minus_1 = m33 - m11 - m22\n    max_index = 0\n    four_max_squared_minus_1 = four_w_squared_minus_1\n    if four_x_squared_minus_1 > four_max_squared_minus_1:\n        four_max_squared_minus_1 = four_x_squared_minus_1\n        max_index = 1\n    if four_y_squared_minus_1 > four_max_squared_minus_1:\n        four_max_squared_minus_1 = four_y_squared_minus_1\n        max_index = 2\n    if four_z_squared_minus_1 > four_max_squared_minus_1:\n        four_max_squared_minus_1 = four_z_squared_minus_1\n        max_index = 3\n\n    max_val = 0.5 * np.sqrt(four_max_squared_minus_1 + 1.0)\n    mult = 0.25 / max_val\n\n    if max_index == 0:\n        w = max_val\n        x = (m23 - m32) * mult\n        y = (m31 - m13) * mult\n        z = (m12 - m21) * mult\n    elif max_index == 1:\n        x = max_val\n        w = (m23 - m32) * mult\n        y = (m12 + m21) * mult\n        z = (m31 + m13) * mult\n    elif max_index == 2:\n        y = max_val\n        w = (m31 - m13) * mult\n        x = (m12 + m21) * mult\n        z = (m23 + m32) * mult\n    elif max_index == 3:\n        z = max_val\n        w = (m12 - m21) * mult\n        x = (m31 + m13) * mult\n        y = (m23 + m32) * mult\n\n    return np.array([w, x, y, z])\n\n\ndef get_sph_r(coords):\n    # The spherical coordinates radius is simply the magnitude of the\n    # coordinate vector.\n\n    return np.sqrt(np.sum(coords**2, axis=0))\n\n\ndef resize_vector(vector, vector_array):\n    if len(vector_array.shape) == 4:\n        res_vector = np.resize(vector, (3, 1, 1, 1))\n    else:\n        res_vector = np.resize(vector, (3, 1))\n    return res_vector\n\n\ndef normalize_vector(vector):\n    # this function normalizes\n    # an input vector\n\n    L2 = np.atleast_1d(np.linalg.norm(vector))\n    L2[L2 == 0] = 1.0\n    vector = vector / L2\n    return vector\n\n\ndef get_sph_theta(coords, normal):\n    # The angle (theta) with respect to the normal (J), is the arccos\n    # of the dot product of the normal with the normalized coordinate\n    # vector.\n\n    res_normal = resize_vector(normal, coords)\n\n    # check if the normal vector is normalized\n    # since arccos requires the vector to be normalised\n    res_normal = normalize_vector(res_normal)\n\n    tile_shape = [1] + list(coords.shape)[1:]\n\n    J = np.tile(res_normal, tile_shape)\n\n    JdotCoords = np.sum(J * coords, axis=0)\n\n    with np.errstate(invalid=\"ignore\"):\n        ret = np.arccos(JdotCoords / np.sqrt(np.sum(coords**2, axis=0)))\n\n    ret[np.isnan(ret)] = 0\n\n    return ret\n\n\ndef get_sph_phi(coords, normal):\n    # We have freedom with respect to what axis (xprime) to define\n    # the disk angle. Here I've chosen to use the axis that is\n    # perpendicular to the normal and the y-axis. When normal ==\n    # y-hat, then set xprime = z-hat. With this definition, when\n    # normal == z-hat (as is typical), then xprime == x-hat.\n    #\n    # The angle is then given by the arctan of the ratio of the\n    # yprime-component and the xprime-component of the coordinate\n    # vector.\n\n    normal = normalize_vector(normal)\n    (zprime, xprime, yprime) = ortho_find(normal)\n\n    res_xprime = resize_vector(xprime, coords)\n    res_yprime = resize_vector(yprime, coords)\n\n    tile_shape = [1] + list(coords.shape)[1:]\n    Jx = np.tile(res_xprime, tile_shape)\n    Jy = np.tile(res_yprime, tile_shape)\n\n    Px = np.sum(Jx * coords, axis=0)\n    Py = np.sum(Jy * coords, axis=0)\n\n    return np.arctan2(Py, Px)\n\n\ndef get_cyl_r(coords, normal):\n    # The cross product of the normal (J) with a coordinate vector\n    # gives a vector of magnitude equal to the cylindrical radius.\n\n    res_normal = resize_vector(normal, coords)\n    res_normal = normalize_vector(res_normal)\n\n    tile_shape = [1] + list(coords.shape)[1:]\n    J = np.tile(res_normal, tile_shape)\n\n    JcrossCoords = np.cross(J, coords, axisa=0, axisb=0, axisc=0)\n    return np.sqrt(np.sum(JcrossCoords**2, axis=0))\n\n\ndef get_cyl_z(coords, normal):\n    # The dot product of the normal (J) with the coordinate vector\n    # gives the cylindrical height.\n\n    res_normal = resize_vector(normal, coords)\n    res_normal = normalize_vector(res_normal)\n\n    tile_shape = [1] + list(coords.shape)[1:]\n    J = np.tile(res_normal, tile_shape)\n\n    return np.sum(J * coords, axis=0)\n\n\ndef get_cyl_theta(coords, normal):\n    # This is identical to the spherical phi component\n\n    return get_sph_phi(coords, normal)\n\n\ndef get_cyl_r_component(vectors, theta, normal):\n    # The r of a vector is the vector dotted with rhat\n\n    normal = normalize_vector(normal)\n    (zprime, xprime, yprime) = ortho_find(normal)\n\n    res_xprime = resize_vector(xprime, vectors)\n    res_yprime = resize_vector(yprime, vectors)\n\n    tile_shape = [1] + list(vectors.shape)[1:]\n    Jx = np.tile(res_xprime, tile_shape)\n    Jy = np.tile(res_yprime, tile_shape)\n\n    rhat = Jx * np.cos(theta) + Jy * np.sin(theta)\n\n    return np.sum(vectors * rhat, axis=0)\n\n\ndef get_cyl_theta_component(vectors, theta, normal):\n    # The theta component of a vector is the vector dotted with thetahat\n    normal = normalize_vector(normal)\n    (zprime, xprime, yprime) = ortho_find(normal)\n\n    res_xprime = resize_vector(xprime, vectors)\n    res_yprime = resize_vector(yprime, vectors)\n\n    tile_shape = [1] + list(vectors.shape)[1:]\n    Jx = np.tile(res_xprime, tile_shape)\n    Jy = np.tile(res_yprime, tile_shape)\n\n    thetahat = -Jx * np.sin(theta) + Jy * np.cos(theta)\n\n    return np.sum(vectors * thetahat, axis=0)\n\n\ndef get_cyl_z_component(vectors, normal):\n    # The z component of a vector is the vector dotted with zhat\n    normal = normalize_vector(normal)\n    (zprime, xprime, yprime) = ortho_find(normal)\n\n    res_zprime = resize_vector(zprime, vectors)\n\n    tile_shape = [1] + list(vectors.shape)[1:]\n    zhat = np.tile(res_zprime, tile_shape)\n\n    return np.sum(vectors * zhat, axis=0)\n\n\ndef get_sph_r_component(vectors, theta, phi, normal):\n    # The r component of a vector is the vector dotted with rhat\n    normal = normalize_vector(normal)\n    (zprime, xprime, yprime) = ortho_find(normal)\n\n    res_xprime = resize_vector(xprime, vectors)\n    res_yprime = resize_vector(yprime, vectors)\n    res_zprime = resize_vector(zprime, vectors)\n\n    tile_shape = [1] + list(vectors.shape)[1:]\n\n    Jx, Jy, Jz = (\n        YTArray(np.tile(rprime, tile_shape), \"\")\n        for rprime in (res_xprime, res_yprime, res_zprime)\n    )\n\n    rhat = (\n        Jx * np.sin(theta) * np.cos(phi)\n        + Jy * np.sin(theta) * np.sin(phi)\n        + Jz * np.cos(theta)\n    )\n\n    return np.sum(vectors * rhat, axis=0)\n\n\ndef get_sph_phi_component(vectors, phi, normal):\n    # The phi component of a vector is the vector dotted with phihat\n    normal = normalize_vector(normal)\n    (zprime, xprime, yprime) = ortho_find(normal)\n\n    res_xprime = resize_vector(xprime, vectors)\n    res_yprime = resize_vector(yprime, vectors)\n\n    tile_shape = [1] + list(vectors.shape)[1:]\n    Jx = YTArray(np.tile(res_xprime, tile_shape), \"\")\n    Jy = YTArray(np.tile(res_yprime, tile_shape), \"\")\n\n    phihat = -Jx * np.sin(phi) + Jy * np.cos(phi)\n\n    return np.sum(vectors * phihat, axis=0)\n\n\ndef get_sph_theta_component(vectors, theta, phi, normal):\n    # The theta component of a vector is the vector dotted with thetahat\n    normal = normalize_vector(normal)\n    (zprime, xprime, yprime) = ortho_find(normal)\n\n    res_xprime = resize_vector(xprime, vectors)\n    res_yprime = resize_vector(yprime, vectors)\n    res_zprime = resize_vector(zprime, vectors)\n\n    tile_shape = [1] + list(vectors.shape)[1:]\n    Jx, Jy, Jz = (\n        YTArray(np.tile(rprime, tile_shape), \"\")\n        for rprime in (res_xprime, res_yprime, res_zprime)\n    )\n\n    thetahat = (\n        Jx * np.cos(theta) * np.cos(phi)\n        + Jy * np.cos(theta) * np.sin(phi)\n        - Jz * np.sin(theta)\n    )\n\n    return np.sum(vectors * thetahat, axis=0)\n\n\ndef compute_stddev_image(buff2, buff):\n    \"\"\"\n    This function computes the standard deviation of a weighted projection.\n    It defines the standard deviation as sigma^2 = <v^2>-<v>^2, where the\n    brackets indicate averages (with the weight) and v is the field in\n    question.\n\n    There is the possibility that if the projection at a particular location\n    is of a constant or a single cell/particle, then <v^2> = <v>^2 and instead\n    of getting zero one gets roundoff error that results in sigma^2 < 0,\n    which is unphysical.\n\n    We handle this here by performing the subtraction and checking that any\n    and all negative values of sigma^2 can be attributed to roundoff and\n    thus be safely set to zero. We error out if this is not the case. There\n    are ways of computing the standard deviation that are designed to avoid\n    this catastrophic cancellation, but this would require rather substantial\n    and invasive changes to the projection machinery so for the time being\n    it is avoided.\n\n    Parameters\n    ----------\n    buff2 : ImageArray\n        The image of the weighted averge of the field squared\n    buff : ImageArray\n        The image of the weighted averge of the field\n    \"\"\"\n    buff1 = buff * buff\n    y = buff2 - buff1\n    close_negs = np.isclose(np.asarray(buff2), np.asarray(buff1))[y < 0.0]\n    if close_negs.all():\n        y[y < 0.0] = 0.0\n    else:\n        raise ValueError(\n            \"Something went wrong, there are significant negative \"\n            \"variances in the standard deviation image!\"\n        )\n    return np.sqrt(y)\n"
  },
  {
    "path": "yt/utilities/mesh_code_generation.py",
    "content": "import yaml\nfrom sympy import Matrix, MatrixSymbol, ccode, diff, symarray\n\n# define some templates used below\nfun_signature = \"\"\"cdef void %s(double* fx,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\"\"\"\n\nfun_dec_template = fun_signature + \" \\n\"\nfun_def_template = (\n    \"\"\"@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True) \\n\"\"\"\n    + fun_signature\n    + \": \\n\"\n)\n\njac_signature_3D = \"\"\"cdef void %s(double* rcol,\n                       double* scol,\n                       double* tcol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\"\"\"\n\njac_dec_template_3D = jac_signature_3D + \" \\n\"\njac_def_template_3D = (\n    \"\"\"@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True) \\n\"\"\"\n    + jac_signature_3D\n    + \": \\n\"\n)\n\njac_signature_2D = \"\"\"cdef void %s(double* rcol,\n                       double* scol,\n                       double* x,\n                       double* vertices,\n                       double* phys_x) noexcept nogil\"\"\"\njac_dec_template_2D = jac_signature_2D + \" \\n\"\njac_def_template_2D = (\n    \"\"\"@cython.boundscheck(False)\n@cython.wraparound(False)\n@cython.cdivision(True) \\n\"\"\"\n    + jac_signature_2D\n    + \": \\n\"\n)\n\nfile_header = (\n    \"# This file contains auto-generated functions for sampling \\n\"\n    + \"# inside finite element solutions for various mesh types. \\n\"\n    + \"# To see how the code generation works in detail, see \\n\"\n    + \"# yt/utilities/mesh_code_generation.py. \\n\"\n)\n\n\nclass MeshCodeGenerator:\n    \"\"\"\n\n    A class for automatically generating the functions and jacobians used for\n    sampling inside finite element calculations.\n\n    \"\"\"\n\n    def __init__(self, mesh_data):\n        \"\"\"\n\n        Mesh data should be a dictionary containing information about the type\n        of elements used. See yt/utilities/mesh_types.yaml for more information.\n\n        \"\"\"\n        self.mesh_type = mesh_data[\"mesh_type\"]\n        self.num_dim = mesh_data[\"num_dim\"]\n        self.num_vertices = mesh_data[\"num_vertices\"]\n        self.num_mapped_coords = mesh_data[\"num_mapped_coords\"]\n\n        x = MatrixSymbol(\"x\", self.num_mapped_coords, 1)\n        self.x = x\n        self.N = Matrix(eval(mesh_data[\"shape_functions\"]))\n        self._compute_jacobian()\n\n    def _compute_jacobian(self):\n        assert self.num_vertices == len(self.N)\n        assert self.num_dim == self.num_mapped_coords\n\n        self.X = MatrixSymbol(\"vertices\", self.num_vertices, self.num_dim)\n        self.fx = MatrixSymbol(\"fx\", self.num_dim, 1)\n        self.physical_position = MatrixSymbol(\"phys_x\", self.num_dim, 1)\n\n        self.f = (self.N.T * Matrix(self.X)).T - self.physical_position\n\n        self.J = symarray(\"J\", (self.num_dim, self.num_dim))\n        for i in range(self.num_dim):\n            for j, var in enumerate(self.x):\n                self.J[i][j] = diff(self.f[i, 0], var)\n\n        self.rcol = MatrixSymbol(\"rcol\", self.num_dim, 1)\n        self.scol = MatrixSymbol(\"scol\", self.num_dim, 1)\n        self.tcol = MatrixSymbol(\"tcol\", self.num_dim, 1)\n\n        self.function_name = f\"{self.mesh_type}Function{self.num_dim}D\"\n        self.function_header = fun_def_template % self.function_name\n        self.function_declaration = fun_dec_template % self.function_name\n\n        self.jacobian_name = f\"{self.mesh_type}Jacobian{self.num_dim}D\"\n\n        if self.num_dim == 3:\n            self.jacobian_header = jac_def_template_3D % self.jacobian_name\n            self.jacobian_declaration = jac_dec_template_3D % self.jacobian_name\n\n        elif self.num_dim == 2:\n            self.jacobian_header = jac_def_template_2D % self.jacobian_name\n            self.jacobian_declaration = jac_dec_template_2D % self.jacobian_name\n\n    def get_interpolator_definition(self):\n        \"\"\"\n\n        This returns the function definitions for the given mesh type.\n\n        \"\"\"\n\n        function_code = self.function_header\n        for i in range(self.num_dim):\n            function_code += \"\\t\" + ccode(self.f[i, 0], self.fx[i, 0]) + \"\\n\"\n\n        jacobian_code = self.jacobian_header\n        for i in range(self.num_dim):\n            jacobian_code += \"\\t\" + ccode(self.J[i, 0], self.rcol[i, 0]) + \"\\n\"\n            jacobian_code += \"\\t\" + ccode(self.J[i, 1], self.scol[i, 0]) + \"\\n\"\n            if self.num_dim == 2:\n                continue\n            jacobian_code += \"\\t\" + ccode(self.J[i, 2], self.tcol[i, 0]) + \"\\n\"\n\n        return function_code, jacobian_code\n\n    def get_interpolator_declaration(self):\n        \"\"\"\n\n        This returns the function declarations for the given mesh type.\n\n        \"\"\"\n        return self.function_declaration, self.jacobian_declaration\n\n\nif __name__ == \"__main__\":\n    with open(\"mesh_types.yaml\") as f:\n        lines = f.read()\n\n    mesh_types = yaml.load(lines, Loader=yaml.FullLoader)\n\n    pxd_file = open(\"lib/autogenerated_element_samplers.pxd\", \"w\")\n    pyx_file = open(\"lib/autogenerated_element_samplers.pyx\", \"w\")\n\n    pyx_file.write(file_header)\n    pyx_file.write(\"\\n \\n\")\n    pyx_file.write(\"cimport cython \\n\")\n    pyx_file.write(\"from libc.math cimport pow \\n\")\n    pyx_file.write(\"\\n \\n\")\n\n    for _, mesh_data in sorted(mesh_types.items()):\n        codegen = MeshCodeGenerator(mesh_data)\n\n        function_code, jacobian_code = codegen.get_interpolator_definition()\n        function_decl, jacobian_decl = codegen.get_interpolator_declaration()\n\n        pxd_file.write(function_decl)\n        pxd_file.write(\"\\n \\n\")\n        pxd_file.write(jacobian_decl)\n        pxd_file.write(\"\\n \\n\")\n\n        pyx_file.write(function_code)\n        pyx_file.write(\"\\n \\n\")\n        pyx_file.write(jacobian_code)\n        pyx_file.write(\"\\n \\n\")\n\n    pxd_file.close()\n    pyx_file.close()\n"
  },
  {
    "path": "yt/utilities/mesh_types.yaml",
    "content": "Hex8:\n  mesh_type: Q1\n  num_dim: 3\n  num_vertices: 8\n  num_mapped_coords: 3\n  shape_functions: |\n    [(1 - x[0])*(1 - x[1])*(1 - x[2])/8.,\n     (1 + x[0])*(1 - x[1])*(1 - x[2])/8.,\n     (1 + x[0])*(1 + x[1])*(1 - x[2])/8.,\n     (1 - x[0])*(1 + x[1])*(1 - x[2])/8.,\n     (1 - x[0])*(1 - x[1])*(1 + x[2])/8.,\n     (1 + x[0])*(1 - x[1])*(1 + x[2])/8.,\n     (1 + x[0])*(1 + x[1])*(1 + x[2])/8.,\n     (1 - x[0])*(1 + x[1])*(1 + x[2])/8.]\n\nQuad4:\n  mesh_type: Q1\n  num_dim: 2\n  num_vertices: 4\n  num_mapped_coords: 2\n  shape_functions: |\n    [(1 - x[0])*(1 - x[1])/4.,\n     (1 + x[0])*(1 - x[1])/4.,\n     (1 + x[0])*(1 + x[1])/4.,\n     (1 - x[0])*(1 + x[1])/4.]\n\nQuad9:\n  mesh_type: Q2\n  num_dim: 2\n  num_vertices: 9\n  num_mapped_coords: 2\n  shape_functions: |\n    [x[0] * (x[0] - 1) * x[1] * (x[1] - 1) / 4.,\n     x[0] * (x[0] + 1) * x[1] * (x[1] - 1) / 4.,\n     x[0] * (x[0] + 1) * x[1] * (x[1] + 1) / 4.,\n     x[0] * (x[0] - 1) * x[1] * (x[1] + 1) / 4.,\n     (x[0] + 1) * (x[0] - 1) * x[1] * (x[1] - 1) / -2.,\n     x[0] * (x[0] + 1) * (x[1] + 1) * (x[1] - 1) / -2.,\n     (x[0] + 1) * (x[0] - 1) * x[1] * (x[1] + 1) / -2.,\n     x[0] * (x[0] - 1) * (x[1] + 1) * (x[1] - 1) / -2.,\n     (x[0] + 1) * (x[0] - 1) * (x[1] + 1) * (x[1] - 1)]\n\nWedge6:\n  mesh_type: W1\n  num_dim: 3\n  num_vertices: 6\n  num_mapped_coords: 3\n  shape_functions: |\n    [(1 - x[0] - x[1]) * (1 - x[2]) / 2.,\n     x[0] * (1 - x[2]) / 2.,\n     x[1] * (1 - x[2]) / 2.,\n     (1 - x[0] - x[1]) * (1 + x[2]) / 2.,\n     x[0] * (1 + x[2]) / 2.,\n     x[1] * (1 + x[2]) / 2.]\n\nTri6:\n  mesh_type: T2\n  num_dim: 2\n  num_vertices: 6\n  num_mapped_coords: 2\n  shape_functions: |\n    [1 - 3 * x[0] + 2 * x[0]**2 - 3 * x[1] + 2 * x[1]**2 + 4 * x[0] * x[1],\n     -x[0] + 2 * x[0]**2,\n     -x[1] + 2 * x[1]**2,\n     4 * x[0] - 4 * x[0]**2 - 4 * x[0] * x[1],\n     4 * x[0] * x[1],\n     4 * x[1] - 4 * x[1]**2 - 4 * x[0] * x[1]]\n\nTet10:\n  mesh_type: Tet2\n  num_dim: 3\n  num_vertices: 10\n  num_mapped_coords: 3\n  shape_functions: |\n    [1 - 3 * x[0] + 2 * x[0]**2 - 3 * x[1] + 2 * x[1]**2 - 3 * x[2] + 2 * x[2]**2 + 4 * x[0] * x[1] + 4 * x[0] * x[2] + 4 * x[1] * x[2],\n     -x[0] + 2 * x[0]**2,\n     -x[1] + 2 * x[1]**2,\n     -x[2] + 2 * x[2]**2,\n     4 * x[0] - 4 * x[0]**2 - 4 * x[0] * x[1] - 4 * x[0] * x[2],\n     4 * x[0] * x[1],\n     4 * x[1] - 4 * x[1]**2 - 4 * x[1] * x[0] - 4 * x[1] * x[2],\n     4 * x[2] - 4 * x[2]**2 - 4 * x[2] * x[0] - 4 * x[2] * x[1],\n     4 * x[0] * x[2],\n     4 * x[1] * x[2]]\n"
  },
  {
    "path": "yt/utilities/metadata.py",
    "content": "from yt.loaders import load\n\nDEFAULT_ATTRS = (\n    \"dimensionality\",\n    \"refine_by\",\n    \"domain_dimensions\",\n    \"current_time\",\n    \"domain_left_edge\",\n    \"domain_right_edge\",\n    \"unique_identifier\",\n    \"current_redshift\",\n    \"cosmological_simulation\",\n    \"omega_matter\",\n    \"omega_lambda\",\n    \"hubble_constant\",\n    \"dataset_type\",\n)\n\n\ndef get_metadata(path, full_output=False, attrs=DEFAULT_ATTRS):\n    ds = load(path)\n    metadata = {\"filename\": path}\n    for a in attrs:\n        v = getattr(ds, a, None)\n        if v is None:\n            continue\n        if hasattr(v, \"tolist\"):\n            v = v.tolist()\n        metadata[a] = v\n    if full_output:\n        params = {}\n        for p, v in ds.parameters.items():\n            if hasattr(v, \"tolist\"):\n                v = v.tolist()\n            params[p] = v\n        metadata[\"params\"] = params\n    ds.close()\n    return metadata\n"
  },
  {
    "path": "yt/utilities/minimal_representation.py",
    "content": "import abc\nimport json\nimport os\nfrom uuid import uuid4\n\nimport numpy as np\n\nfrom yt.funcs import compare_dicts, is_sequence\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\ndef _sanitize_list(flist):\n    temp = []\n    for item in flist:\n        if isinstance(item, str):\n            temp.append(item.encode(\"latin-1\"))\n        elif isinstance(item, tuple) and all(isinstance(i, str) for i in item):\n            temp.append(tuple(_sanitize_list(list(item))))\n        else:\n            temp.append(item)\n    return temp\n\n\ndef _serialize_to_h5(g, cdict):\n    for item in cdict:\n        if isinstance(cdict[item], (YTQuantity, YTArray)):\n            g[item] = cdict[item].d\n            g[item].attrs[\"units\"] = str(cdict[item].units)\n        elif isinstance(cdict[item], dict):\n            _serialize_to_h5(g.create_group(item), cdict[item])\n        elif cdict[item] is None:\n            g[item] = \"None\"\n        elif isinstance(cdict[item], list):\n            g[item] = _sanitize_list(cdict[item])\n        elif isinstance(cdict[item], tuple) and all(\n            isinstance(i, str) for i in cdict[item]\n        ):\n            g[item] = tuple(_sanitize_list(cdict[item]))\n        else:\n            g[item] = cdict[item]\n\n\ndef _deserialize_from_h5(g, ds):\n    result = {}\n    for item in g:\n        if item == \"chunks\":\n            continue\n        if \"units\" in g[item].attrs:\n            if is_sequence(g[item]):\n                result[item] = ds.arr(g[item][:], g[item].attrs[\"units\"])\n            else:\n                result[item] = ds.quan(g[item][()], g[item].attrs[\"units\"])\n        elif isinstance(g[item], h5py.Group):\n            result[item] = _deserialize_from_h5(g[item], ds)\n        elif g[item] == \"None\":\n            result[item] = None\n        else:\n            try:\n                result[item] = g[item][:]  # try array\n            except ValueError:\n                result[item] = g[item][()]  # fallback to scalar\n    return result\n\n\nclass ContainerClass:\n    pass\n\n\nclass MinimalRepresentation(metaclass=abc.ABCMeta):\n    def _update_attrs(self, obj, attr_list):\n        for attr in attr_list:\n            setattr(self, attr, getattr(obj, attr, None))\n        if hasattr(obj, \"ds\"):\n            self.output_hash = obj.ds._hash()\n            self._ds_mrep = obj.ds._mrep\n        if hasattr(obj, \"data_source\"):\n            self.data_source_hash = obj.data_source._hash\n\n    def __init__(self, obj):\n        self._update_attrs(obj, self._attr_list)\n\n    @abc.abstractmethod\n    def _generate_post(self):\n        pass\n\n    @property\n    @abc.abstractmethod\n    def _attr_list(self):\n        pass\n\n    def _return_filtered_object(self, attrs):\n        new_attrs = tuple(attr for attr in self._attr_list if attr not in attrs)\n        new_class = type(\n            f\"Filtered{self.__class__.__name__}\",\n            (FilteredRepresentation,),\n            {\"_attr_list\": new_attrs},\n        )\n        return new_class(self)\n\n    @property\n    def _attrs(self):\n        return {attr: getattr(self, attr) for attr in self._attr_list}\n\n    @classmethod\n    def _from_metadata(cls, metadata):\n        cc = ContainerClass()\n        for a, v in metadata.values():\n            setattr(cc, a, v)\n        return cls(cc)\n\n    def store(self, storage):\n        if hasattr(self, \"_ds_mrep\"):\n            self._ds_mrep.store(storage)\n        metadata, (final_name, chunks) = self._generate_post()\n        metadata[\"obj_type\"] = self.type\n        with h5py.File(storage, mode=\"r\") as h5f:\n            dset = str(uuid4())[:8]\n            h5f.create_group(dset)\n            _serialize_to_h5(h5f[dset], metadata)\n            if len(chunks) > 0:\n                g = h5f[dset].create_group(\"chunks\")\n                g.attrs[\"final_name\"] = final_name\n                for fname, fdata in chunks:\n                    if isinstance(fname, (tuple, list)):\n                        fname = \"*\".join(fname)\n\n                    if isinstance(fdata, (YTQuantity, YTArray)):\n                        g.create_dataset(fname, data=fdata.d, compression=\"lzf\")\n                        g[fname].attrs[\"units\"] = str(fdata.units)\n                    else:\n                        g.create_dataset(fname, data=fdata, compression=\"lzf\")\n\n    def restore(self, storage, ds):  # noqa: B027\n        pass\n\n    def upload(self):\n        raise NotImplementedError(\"This method hasn't been ported to python 3\")\n\n    def load(self, storage):\n        raise NotImplementedError(\"This method hasn't been ported to python 3\")\n\n    def dump(self, storage):\n        raise NotImplementedError(\"This method hasn't been ported to python 3\")\n\n\nclass FilteredRepresentation(MinimalRepresentation):\n    def _generate_post(self):\n        raise RuntimeError\n\n\nclass MinimalDataset(MinimalRepresentation):\n    _attr_list = (\n        \"dimensionality\",\n        \"refine_by\",\n        \"domain_dimensions\",\n        \"current_time\",\n        \"domain_left_edge\",\n        \"domain_right_edge\",\n        \"unique_identifier\",\n        \"current_redshift\",\n        \"output_hash\",\n        \"cosmological_simulation\",\n        \"omega_matter\",\n        \"omega_lambda\",\n        \"hubble_constant\",\n        \"name\",\n    )\n    type = \"simulation_output\"\n\n    def __init__(self, obj):\n        super().__init__(obj)\n        self.output_hash = obj._hash()\n        self.name = str(obj)\n\n    def _generate_post(self):\n        metadata = self._attrs\n        chunks = []\n        return (metadata, (None, chunks))\n\n\nclass MinimalMappableData(MinimalRepresentation):\n    _attr_list: tuple[str, ...] = (\n        \"field_data\",\n        \"field\",\n        \"weight_field\",\n        \"axis\",\n        \"output_hash\",\n        \"vm_type\",\n    )\n\n    def _generate_post(self):\n        nobj = self._return_filtered_object((\"field_data\",))\n        metadata = nobj._attrs\n        chunks = [(arr, self.field_data[arr]) for arr in self.field_data]\n        return (metadata, (\"field_data\", chunks))\n\n    def _read_chunks(self, g, ds):\n        for fname in g.keys():\n            if \"*\" in fname:\n                arr = tuple(fname.split(\"*\"))\n            else:\n                arr = fname\n            try:\n                self.field_data[arr] = ds.arr(g[fname][:], g[fname].attrs[\"units\"])\n            except KeyError:\n                self.field_data[arr] = g[fname][:]\n\n\nclass MinimalProjectionData(MinimalMappableData):\n    type = \"proj\"\n    vm_type = \"Projection\"\n    _attr_list = (\n        \"field_data\",\n        \"field\",\n        \"weight_field\",\n        \"axis\",\n        \"output_hash\",\n        \"center\",\n        \"method\",\n        \"field_parameters\",\n        \"data_source_hash\",\n    )\n\n    def restore(self, storage, ds):\n        if hasattr(self, \"_ds_mrep\"):\n            self._ds_mrep.restore(storage, ds)\n        metadata, (final_name, chunks) = self._generate_post()\n        with h5py.File(storage, mode=\"r\") as h5f:\n            for dset in h5f:\n                stored_metadata = _deserialize_from_h5(h5f[dset], ds)\n                if compare_dicts(metadata, stored_metadata):\n                    self._read_chunks(h5f[dset][\"chunks\"], ds)\n                    return True\n        return False\n\n\nclass MinimalSliceData(MinimalMappableData):\n    type = \"slice\"\n    vm_type = \"Slice\"\n    weight_field = \"None\"\n\n\nclass MinimalImageCollectionData(MinimalRepresentation):\n    type = \"image_collection\"\n    _attr_list = (\"name\", \"output_hash\", \"images\", \"image_metadata\")\n\n    def _generate_post(self):\n        nobj = self._return_filtered_object((\"images\",))\n        metadata = nobj._attrs\n        chunks = list(self.images)\n        return (metadata, (\"images\", chunks))\n\n\n_hub_categories = (\n    \"News\",\n    \"Documents\",\n    \"Simulation Management\",\n    \"Data Management\",\n    \"Analysis and Visualization\",\n    \"Paper Repositories\",\n    \"Astrophysical Utilities\",\n    \"yt Scripts\",\n)\n\n\nclass MinimalProjectDescription(MinimalRepresentation):\n    type = \"project\"\n    _attr_list = (\"title\", \"url\", \"description\", \"category\", \"image_url\")\n\n    def __init__(self, title, url, description, category, image_url=\"\"):\n        assert category in _hub_categories\n        self.title = title\n        self.url = url\n        self.description = description\n        self.category = category\n        self.image_url = image_url\n\n    def _generate_post(self):\n        metadata = self._attrs\n        return (metadata, (\"chunks\", []))\n\n\nclass MinimalNotebook(MinimalRepresentation):\n    type = \"notebook\"\n    _attr_list = (\"title\",)\n\n    def __init__(self, filename, title=None):\n        # First we read in the data\n        if not os.path.isfile(filename):\n            raise OSError(filename)\n        self.data = open(filename).read()\n        if title is None:\n            title = json.loads(self.data)[\"metadata\"][\"name\"]\n        self.title = title\n        self.data = np.fromstring(self.data, dtype=\"c\")\n\n    def _generate_post(self):\n        metadata = self._attrs\n        chunks = [(\"notebook\", self.data)]\n        return (metadata, (\"chunks\", chunks))\n\n\nclass ImageCollection:\n    def __init__(self, ds, name):\n        self.ds = ds\n        self.name = name\n        self.images = []\n        self.image_metadata = []\n\n    def add_image(self, fn, descr):\n        self.image_metadata.append(descr)\n        self.images.append((os.path.basename(fn), np.fromfile(fn, dtype=\"c\")))\n"
  },
  {
    "path": "yt/utilities/nodal_data_utils.py",
    "content": "import numpy as np\n\n_index_map = np.array(\n    [\n        [0, 0, 0, 0, 0, 0, 0, 0],\n        [0, 1, 0, 1, 0, 1, 0, 1],\n        [0, 0, 1, 1, 0, 0, 1, 1],\n        [0, 1, 2, 3, 0, 1, 2, 3],\n        [0, 0, 0, 0, 1, 1, 1, 1],\n        [0, 1, 0, 1, 2, 3, 2, 3],\n        [0, 0, 1, 1, 2, 2, 3, 3],\n        [0, 1, 2, 3, 4, 5, 6, 7],\n    ]\n)\n\n\ndef _get_linear_index(nodal_flag):\n    if len(nodal_flag) == 2:\n        return 1 * nodal_flag[1] + 2 * nodal_flag[0]\n    else:\n        return 1 * nodal_flag[2] + 2 * nodal_flag[1] + 4 * nodal_flag[0]\n\n\ndef _get_indices(nodal_flag):\n    li = _get_linear_index(nodal_flag)\n    return _index_map[li]\n\n\ndef get_nodal_data(data_source, field):\n    finfo = data_source.ds._get_field_info(field)\n    nodal_flag = finfo.nodal_flag\n    field_data = data_source[field]\n    inds = _get_indices(nodal_flag)\n    return field_data[:, inds]\n\n\ndef get_nodal_slices(shape, nodal_flag, dim):\n    slices = []\n    dir_slices = [[] for _ in range(dim)]\n\n    for i in range(dim):\n        if nodal_flag[i]:\n            dir_slices[i] = [slice(0, shape[i] - 1), slice(1, shape[i])]\n        else:\n            dir_slices[i] = [slice(0, shape[i])]\n\n    for sl_i in dir_slices[0]:\n        for sl_j in dir_slices[1]:\n            if dim > 2:\n                for sl_k in dir_slices[2]:\n                    slices.append([sl_i, sl_j, sl_k])\n            else:\n                slices.append([sl_i, sl_j])\n\n    return tuple(slices)\n"
  },
  {
    "path": "yt/utilities/object_registries.py",
    "content": "# These are some of the data object registries that are used in different places in the\n# code. Not all of the self-registering objects are included in these.\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from yt.data_objects.analyzer_objects import AnalysisTask\n    from yt.data_objects.data_containers import YTDataContainer\n    from yt.data_objects.derived_quantities import DerivedQuantity\n    from yt.data_objects.static_output import Dataset\n    from yt.data_objects.time_series import DatasetSeries\n    from yt.visualization.volume_rendering.old_camera import Camera\n\nanalysis_task_registry: dict[str, type[\"AnalysisTask\"]] = {}\nderived_quantity_registry: dict[str, type[\"DerivedQuantity\"]] = {}\noutput_type_registry: dict[str, type[\"Dataset\"]] = {}\nsimulation_time_series_registry: dict[str, type[\"DatasetSeries\"]] = {}\n\n# TODO: split into 2 registries to avoid a typing.Union\ndata_object_registry: dict[str, \"type[YTDataContainer] | type[Camera]\"] = {}\n"
  },
  {
    "path": "yt/utilities/on_demand_imports.py",
    "content": "import sys\nfrom functools import wraps\nfrom importlib.util import find_spec\n\n\nclass NotAModule:\n    \"\"\"\n    A class to implement an informative error message that will be outputted if\n    someone tries to use an on-demand import without having the requisite\n    package installed.\n    \"\"\"\n\n    def __init__(self, pkg_name, exc: BaseException | None = None):\n        self.pkg_name = pkg_name\n        self._original_exception = exc\n        error_note = (\n            f\"Something went wrong while trying to lazy-import {pkg_name}. \"\n            f\"Please make sure that {pkg_name} is properly installed.\\n\"\n            \"If the problem persists, please file an issue at \"\n            \"https://github.com/yt-project/yt/issues/new\"\n        )\n        self.error: BaseException\n        if exc is None:\n            self.error = ImportError(error_note)\n        elif sys.version_info >= (3, 11):\n            exc.add_note(error_note)\n            self.error = exc\n        else:\n            # mimic Python 3.11 behaviour:\n            # preserve error message and traceback\n            self.error = type(exc)(f\"{exc!s}\\n{error_note}\").with_traceback(\n                exc.__traceback__\n            )\n\n    def __getattr__(self, item):\n        raise self.error\n\n    def __call__(self, *args, **kwargs):\n        raise self.error\n\n    def __repr__(self) -> str:\n        if self._original_exception is None:\n            return f\"NotAModule({self.pkg_name!r})\"\n        else:\n            return f\"NotAModule({self.pkg_name!r}, {self._original_exception!r})\"\n\n\nclass OnDemand:\n    _default_factory: type[NotAModule] = NotAModule\n\n    def __init_subclass__(cls):\n        if not cls.__name__.endswith(\"_imports\"):\n            raise TypeError(f\"class {cls}'s name needs to be suffixed '_imports'\")\n\n    def __new__(cls):\n        if cls is OnDemand:\n            raise TypeError(\"The OnDemand base class cannot be instantiated.\")\n        else:\n            return object.__new__(cls)\n\n    @property\n    def _name(self) -> str:\n        _name, _, _suffix = self.__class__.__name__.rpartition(\"_\")\n        return _name\n\n    @property\n    def __is_available__(self) -> bool:\n        # special protocol to support testing framework\n        return find_spec(self._name) is not None\n\n\ndef safe_import(func):\n    @property\n    @wraps(func)\n    def inner(self):\n        try:\n            return func(self)\n        except ImportError as exc:\n            return self._default_factory(self._name, exc)\n\n    return inner\n\n\nclass netCDF4_imports(OnDemand):\n    @safe_import\n    def Dataset(self):\n        from netCDF4 import Dataset\n\n        return Dataset\n\n\n_netCDF4 = netCDF4_imports()\n\n\nclass astropy_imports(OnDemand):\n    @safe_import\n    def log(self):\n        from astropy import log\n\n        if log.exception_logging_enabled():\n            log.disable_exception_logging()\n\n        return log\n\n    @safe_import\n    def pyfits(self):\n        from astropy.io import fits\n\n        return fits\n\n    @safe_import\n    def pywcs(self):\n        import astropy.wcs as pywcs\n\n        self.log\n        return pywcs\n\n    @safe_import\n    def units(self):\n        from astropy import units\n\n        self.log\n        return units\n\n    @safe_import\n    def conv(self):\n        import astropy.convolution as conv\n\n        self.log\n        return conv\n\n    @safe_import\n    def time(self):\n        import astropy.time as time\n\n        self.log\n        return time\n\n    @safe_import\n    def wcsaxes(self):\n        from astropy.visualization import wcsaxes\n\n        self.log\n        return wcsaxes\n\n    @safe_import\n    def WCS(self):\n        from astropy.wcs import WCS\n\n        self.log\n        return WCS\n\n\n_astropy = astropy_imports()\n\n\nclass regions_imports(OnDemand):\n    @safe_import\n    def Regions(self):\n        from regions import Regions\n\n        return Regions\n\n\n_regions = regions_imports()\n\n\nclass NotCartopy(NotAModule):\n    \"\"\"\n    A custom class to return error messages dependent on system installation\n    for cartopy imports.\n    \"\"\"\n\n    def __init__(self, pkg_name, exc: BaseException | None = None):\n        super().__init__(pkg_name, exc)\n        if any(s in sys.version for s in (\"Anaconda\", \"Continuum\")):\n            # the conda-based installs of cartopy don't have issues with the\n            # GEOS library, so the error message for users with conda can be\n            # relatively short. Discussion related to this is in\n            # yt-project/yt#1966\n            self.error = ImportError(\n                f\"This functionality requires the {self.pkg_name} \"\n                \"package to be installed.\"\n            )\n        else:\n            self.error = ImportError(\n                f\"This functionality requires the {pkg_name} \"\n                \"package to be installed.\\n\"\n                \"For further instruction please refer to Cartopy's documentation\\n\"\n                \"https://cartopy.readthedocs.io/stable/installing.html\"\n            )\n\n\nclass cartopy_imports(OnDemand):\n    _default_factory = NotCartopy\n\n    @safe_import\n    def crs(self):\n        import cartopy.crs as crs\n\n        return crs\n\n\n_cartopy = cartopy_imports()\n\n\nclass pooch_imports(OnDemand):\n    @safe_import\n    def HTTPDownloader(self):\n        from pooch import HTTPDownloader\n\n        return HTTPDownloader\n\n    @safe_import\n    def utils(self):\n        from pooch import utils\n\n        return utils\n\n    @safe_import\n    def create(self):\n        from pooch import create\n\n        return create\n\n\n_pooch = pooch_imports()\n\n\nclass pyart_imports(OnDemand):\n    @safe_import\n    def io(self):\n        from pyart import io\n\n        return io\n\n    @safe_import\n    def map(self):\n        from pyart import map\n\n        return map\n\n\n_pyart = pyart_imports()\n\n\nclass xarray_imports(OnDemand):\n    @safe_import\n    def open_dataset(self):\n        from xarray import open_dataset\n\n        return open_dataset\n\n\n_xarray = xarray_imports()\n\n\nclass scipy_imports(OnDemand):\n    @safe_import\n    def signal(self):\n        from scipy import signal\n\n        return signal\n\n    @safe_import\n    def spatial(self):\n        from scipy import spatial\n\n        return spatial\n\n    @safe_import\n    def ndimage(self):\n        from scipy import ndimage\n\n        return ndimage\n\n    # Optimize is not presently used by yt, but appears here to enable\n    # required functionality in yt extension, trident\n\n    @safe_import\n    def optimize(self):\n        from scipy import optimize\n\n        return optimize\n\n\n_scipy = scipy_imports()\n\n\nclass h5py_imports(OnDemand):\n    def __init__(self):\n        # this ensures the import ordering between netcdf4 and h5py. If h5py is\n        # imported first, can get file lock errors on some systems (including travis-ci)\n        # so we need to do this before initializing h5py_imports()!\n        # similar to this issue https://github.com/pydata/xarray/issues/2560\n        if find_spec(\"h5py\") is None or find_spec(\"netCDF4\") is None:\n            return\n        try:\n            import netCDF4  # noqa F401\n        except ImportError:\n            pass\n\n    @safe_import\n    def File(self):\n        from h5py import File\n\n        return File\n\n    @safe_import\n    def Group(self):\n        from h5py import Group\n\n        return Group\n\n    @safe_import\n    def Dataset(self):\n        from h5py import Dataset\n\n        return Dataset\n\n    @safe_import\n    def get_config(self):\n        from h5py import get_config\n\n        return get_config\n\n    @safe_import\n    def h5f(self):\n        from h5py import h5f\n\n        return h5f\n\n    @safe_import\n    def h5p(self):\n        from h5py import h5p\n\n        return h5p\n\n    @safe_import\n    def h5d(self):\n        from h5py import h5d\n\n        return h5d\n\n    @safe_import\n    def h5s(self):\n        from h5py import h5s\n\n        return h5s\n\n\n_h5py = h5py_imports()\n\n\nclass nose_imports(OnDemand):\n    @safe_import\n    def run(self):\n        from nose import run\n\n        return run\n\n\n_nose = nose_imports()\n\n\nclass libconf_imports(OnDemand):\n    @safe_import\n    def load(self):\n        from libconf import load\n\n        return load\n\n\n_libconf = libconf_imports()\n\n\nclass yaml_imports(OnDemand):\n    @safe_import\n    def load(self):\n        from yaml import load\n\n        return load\n\n    @safe_import\n    def FullLoader(self):\n        from yaml import FullLoader\n\n        return FullLoader\n\n\n_yaml = yaml_imports()\n\n\nclass NotMiniball(NotAModule):\n    def __init__(self, pkg_name):\n        super().__init__(pkg_name)\n        str = (\n            \"This functionality requires the %s package to be installed. \"\n            \"Installation instructions can be found at \"\n            \"https://github.com/weddige/miniball or alternatively you can \"\n            \"install via `python -m pip install MiniballCpp`.\"\n        )\n        self.error = ImportError(str % self.pkg_name)\n\n\nclass miniball_imports(OnDemand):\n    @safe_import\n    def Miniball(self):\n        from miniball import Miniball\n\n        return Miniball\n\n\n_miniball = miniball_imports()\n\n\nclass f90nml_imports(OnDemand):\n    @safe_import\n    def read(self):\n        from f90nml import read\n\n        return read\n\n    @safe_import\n    def Namelist(self):\n        from f90nml import Namelist\n\n        return Namelist\n\n\n_f90nml = f90nml_imports()\n\n\nclass requests_imports(OnDemand):\n    @safe_import\n    def post(self):\n        from requests import post\n\n        return post\n\n    @safe_import\n    def put(self):\n        from requests import put\n\n        return put\n\n    @safe_import\n    def codes(self):\n        from requests import codes\n\n        return codes\n\n    @safe_import\n    def get(self):\n        from requests import get\n\n        return get\n\n    @safe_import\n    def exceptions(self):\n        from requests import exceptions\n\n        return exceptions\n\n\n_requests = requests_imports()\n\n\nclass pandas_imports(OnDemand):\n    @safe_import\n    def NA(self):\n        from pandas import NA\n\n        return NA\n\n    @safe_import\n    def DataFrame(self):\n        from pandas import DataFrame\n\n        return DataFrame\n\n    @safe_import\n    def concat(self):\n        from pandas import concat\n\n        return concat\n\n    @safe_import\n    def read_csv(self):\n        from pandas import read_csv\n\n        return read_csv\n\n\n_pandas = pandas_imports()\n\n\nclass firefly_imports(OnDemand):\n    @safe_import\n    def data_reader(self):\n        import firefly.data_reader as data_reader\n\n        return data_reader\n\n    @safe_import\n    def server(self):\n        import firefly.server as server\n\n        return server\n\n\n_firefly = firefly_imports()\n\n\n# Note: ratarmount may fail with an OSError on import if libfuse is missing\nclass ratarmount_imports(OnDemand):\n    @safe_import\n    def TarMount(self):\n        from ratarmount import TarMount\n\n        return TarMount\n\n    @safe_import\n    def fuse(self):\n        from ratarmount import fuse\n\n        return fuse\n\n\n_ratarmount = ratarmount_imports()\n"
  },
  {
    "path": "yt/utilities/operator_registry.py",
    "content": "import copy\nfrom collections import UserDict\n\n\nclass OperatorRegistry(UserDict):\n    def find(self, op, *args, **kwargs):\n        if isinstance(op, str):\n            # Lookup, assuming string or hashable object\n            op = copy.deepcopy(self[op])\n            op.args = args\n            op.kwargs = kwargs\n        return op\n"
  },
  {
    "path": "yt/utilities/orientation.py",
    "content": "import numpy as np\n\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.exceptions import YTException\n\n\ndef _aligned(a, b):\n    aligned_component = np.abs(np.dot(a, b) / np.linalg.norm(a) / np.linalg.norm(b))\n    return np.isclose(aligned_component, 1.0, 1.0e-13)\n\n\ndef _validate_unit_vectors(normal_vector, north_vector):\n    # Make sure vectors are unitless\n    if north_vector is not None:\n        north_vector = YTArray(north_vector, \"\", dtype=\"float64\")\n    if normal_vector is not None:\n        normal_vector = YTArray(normal_vector, \"\", dtype=\"float64\")\n\n    if not np.dot(normal_vector, normal_vector) > 0:\n        raise YTException(\"normal_vector cannot be the zero vector.\")\n    if north_vector is not None and _aligned(north_vector, normal_vector):\n        raise YTException(\"normal_vector and north_vector cannot be aligned.\")\n\n    return normal_vector, north_vector\n\n\nclass Orientation:\n    def __init__(self, normal_vector, north_vector=None, steady_north=False):\n        r\"\"\"An object that returns a set of basis vectors for orienting\n        cameras a data containers.\n\n        Parameters\n        ----------\n        normal_vector : array_like\n           A vector normal to the image plane\n        north_vector  : array_like, optional\n           The 'up' direction to orient the image plane.\n           If not specified, gets calculated automatically\n        steady_north  : bool, optional\n           Boolean to control whether to normalize the north_vector\n           by subtracting off the dot product of it and the normal\n           vector.  Makes it easier to do rotations along a single\n           axis.  If north_vector is specified, is switched to\n           True.  Default: False\n\n        \"\"\"\n\n        normal_vector, north_vector = _validate_unit_vectors(\n            normal_vector, north_vector\n        )\n        self.steady_north = steady_north\n        if north_vector is not None:\n            self.steady_north = True\n        self.north_vector = north_vector\n        self._setup_normalized_vectors(normal_vector, north_vector)\n        if self.north_vector is None:\n            self.north_vector = self.unit_vectors[1]\n\n    def _setup_normalized_vectors(self, normal_vector, north_vector):\n        normal_vector, north_vector = _validate_unit_vectors(\n            normal_vector, north_vector\n        )\n        # Now we set up our various vectors\n        normal_vector /= np.sqrt(np.dot(normal_vector, normal_vector))\n        if north_vector is None:\n            vecs = np.identity(3)\n            t = np.cross(normal_vector, vecs).sum(axis=1)\n            ax = t.argmax()\n            east_vector = np.cross(vecs[ax, :], normal_vector).ravel()\n            # self.north_vector must remain None otherwise rotations about a fixed axis\n            # will break. The north_vector calculated here will still be included\n            # in self.unit_vectors.\n            north_vector = np.cross(normal_vector, east_vector).ravel()\n        else:\n            if self.steady_north or (np.dot(north_vector, normal_vector) != 0.0):\n                north_vector = (\n                    north_vector - np.dot(north_vector, normal_vector) * normal_vector\n                )\n            east_vector = np.cross(north_vector, normal_vector).ravel()\n        north_vector /= np.sqrt(np.dot(north_vector, north_vector))\n        east_vector /= np.sqrt(np.dot(east_vector, east_vector))\n        self.normal_vector = normal_vector\n        self.north_vector = north_vector\n        self.unit_vectors = YTArray([east_vector, north_vector, normal_vector], \"\")\n        self.inv_mat = np.linalg.pinv(self.unit_vectors)\n"
  },
  {
    "path": "yt/utilities/parallel_tools/__init__.py",
    "content": "\"\"\"\nTools for parallelism.\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/utilities/parallel_tools/controller_system.py",
    "content": "from abc import abstractmethod\n\nfrom .parallel_analysis_interface import ProcessorPool\n\n\nclass WorkSplitter:\n    def __init__(self, controller, group1, group2):\n        self.group1 = group1\n        self.group2 = group2\n        self.controller = controller\n\n    @classmethod\n    def setup(cls, ng1, ng2):\n        pp, wg = ProcessorPool.from_sizes(\n            [(1, \"controller\"), (ng1, \"group1\"), (ng2, \"group2\")]\n        )\n        groupc = pp[\"controller\"]\n        group1 = pp[\"group1\"]\n        group2 = pp[\"group2\"]\n        obj = cls(groupc, group1, group2)\n        obj.run(wg.name)\n\n    def run(self, name):\n        if name == \"controller\":\n            self.run_controller()\n        elif name == \"group1\":\n            self.run_group1()\n        elif name == \"group2\":\n            self.run_group2()\n        else:\n            raise NotImplementedError\n\n    @abstractmethod\n    def run_controller(self):\n        pass\n\n    @abstractmethod\n    def run_group1(self):\n        pass\n\n    @abstractmethod\n    def run_group2(self):\n        pass\n"
  },
  {
    "path": "yt/utilities/parallel_tools/io_runner.py",
    "content": "import time\nfrom contextlib import contextmanager\n\nimport numpy as np\n\nfrom yt.utilities.io_handler import BaseIOHandler\nfrom yt.utilities.logger import ytLogger as mylog\n\nfrom .parallel_analysis_interface import ProcessorPool, parallel_objects\n\ntry:\n    from .parallel_analysis_interface import MPI\nexcept ImportError:\n    pass\n\nYT_TAG_MESSAGE = 317  # Cell 317 knows where to go\n\n\nclass IOCommunicator(BaseIOHandler):\n    def __init__(self, ds, wg, pool):\n        mylog.info(\"Initializing IOCommunicator\")\n        self.ds = ds\n        self.wg = wg  # We don't need to use this!\n        self.pool = pool\n        self.comm = pool.comm\n        # We read our grids here\n        self.grids = []\n        storage = {}\n        grids = ds.index.grids.tolist()\n        grids.sort(key=lambda a: a.filename)\n        for sto, g in parallel_objects(grids, storage=storage):\n            sto.result = self.comm.rank\n            sto.result_id = g.id\n            self.grids.append(g)\n        self._id_offset = ds.index.grids[0]._id_offset\n        mylog.info(\"Reading from disk ...\")\n        self.initialize_data()\n        mylog.info(\"Broadcasting ...\")\n        self.comm.comm.bcast(storage, root=wg.ranks[0])\n        mylog.info(\"Done.\")\n        self.hooks = []\n\n    def initialize_data(self):\n        ds = self.ds\n        fields = [\n            f for f in ds.field_list if not ds.field_info[f].sampling_type == \"particle\"\n        ]\n        pfields = [\n            f for f in ds.field_list if ds.field_info[f].sampling_type == \"particle\"\n        ]\n        # Preload is only defined for Enzo ...\n        if ds.index.io._dataset_type == \"enzo_packed_3d\":\n            self.queue = ds.index.io.queue\n            ds.index.io.preload(self.grids, fields)\n            for g in self.grids:\n                for f in fields:\n                    if f not in self.queue[g.id]:\n                        d = np.zeros(g.ActiveDimensions, dtype=\"float64\")\n                        self.queue[g.id][f] = d\n                for f in pfields:\n                    self.queue[g.id][f] = self._read(g, f)\n        else:\n            self.queue = {}\n            for g in self.grids:\n                for f in fields + pfields:\n                    self.queue[g.id][f] = ds.index.io._read(g, f)\n\n    def _read(self, g, f):\n        fi = self.ds.field_info[f]\n        if fi.sampling_type == \"particle\" and g.NumberOfParticles == 0:\n            # because this gets upcast to float\n            return np.array([], dtype=\"float64\")\n        try:\n            temp = self.ds.index.io._read_data_set(g, f)\n        except Exception:  # self.ds.index.io._read_exception as exc:\n            if fi.not_in_all:\n                temp = np.zeros(g.ActiveDimensions, dtype=\"float64\")\n            else:\n                raise\n        return temp\n\n    def wait(self):\n        status = MPI.Status()\n        while True:\n            if self.comm.comm.Iprobe(MPI.ANY_SOURCE, YT_TAG_MESSAGE, status=status):\n                msg = self.comm.comm.recv(source=status.source, tag=YT_TAG_MESSAGE)\n                if msg[\"op\"] == \"end\":\n                    mylog.debug(\"Shutting down IO.\")\n                    break\n                self._send_data(msg, status.source)\n                status = MPI.Status()\n            else:\n                time.sleep(1e-2)\n\n    def _send_data(self, msg, dest):\n        grid_id = msg[\"grid_id\"]\n        field = msg[\"field\"]\n        ts = self.queue[grid_id][field].astype(\"float64\")\n        mylog.debug(\"Opening send to %s (%s)\", dest, ts.shape)\n        self.hooks.append(self.comm.comm.Isend([ts, MPI.DOUBLE], dest=dest))\n\n\nclass IOHandlerRemote(BaseIOHandler):\n    _dataset_type = \"remote\"\n\n    def __init__(self, ds, wg, pool):\n        self.ds = ds\n        self.wg = wg  # probably won't need\n        self.pool = pool\n        self.comm = pool.comm\n        self.proc_map = self.comm.comm.bcast(None, root=pool[\"io\"].ranks[0])\n        super().__init__()\n\n    def _read_data_set(self, grid, field):\n        dest = self.proc_map[grid.id]\n        msg = {\"grid_id\": grid.id, \"field\": field, \"op\": \"read\"}\n        mylog.debug(\"Requesting %s for %s from %s\", field, grid, dest)\n        if self.ds.field_info[field].sampling_type == \"particle\":\n            data = np.empty(grid.NumberOfParticles, \"float64\")\n        else:\n            data = np.empty(grid.ActiveDimensions, \"float64\")\n        hook = self.comm.comm.Irecv([data, MPI.DOUBLE], source=dest)\n        self.comm.comm.send(msg, dest=dest, tag=YT_TAG_MESSAGE)\n        mylog.debug(\"Waiting for data.\")\n        MPI.Request.Wait(hook)\n        return data\n\n    def _read_data_slice(self, grid, field, axis, coord):\n        sl = [slice(None), slice(None), slice(None)]\n        sl[axis] = slice(coord, coord + 1)\n        # sl = tuple(reversed(sl))\n        return self._read_data_set(grid, field)[tuple(sl)]\n\n    def terminate(self):\n        msg = {\"op\": \"end\"}\n        if self.wg.comm.rank == 0:\n            for rank in self.pool[\"io\"].ranks:\n                mylog.debug(\"Sending termination message to %s\", rank)\n                self.comm.comm.send(msg, dest=rank, tag=YT_TAG_MESSAGE)\n\n\n@contextmanager\ndef remote_io(ds, wg, pool):\n    original_io = ds.index.io\n    ds.index.io = IOHandlerRemote(ds, wg, pool)\n    yield\n    ds.index.io.terminate()\n    ds.index.io = original_io\n\n\ndef io_nodes(fn, n_io, n_work, func, *args, **kwargs):\n    from yt.loaders import load\n\n    pool, wg = ProcessorPool.from_sizes([(n_io, \"io\"), (n_work, \"work\")])\n    rv = None\n    if wg.name == \"work\":\n        ds = load(fn)\n        with remote_io(ds, wg, pool):\n            rv = func(ds, *args, **kwargs)\n    elif wg.name == \"io\":\n        ds = load(fn)\n        io = IOCommunicator(ds, wg, pool)\n        io.wait()\n    # We should broadcast the result\n    rv = pool.comm.mpi_bcast(rv, root=pool[\"work\"].ranks[0])\n    pool.free_all()\n    mylog.debug(\"Return value: %s\", rv)\n    return rv\n\n\n# Here is an example of how to use this functionality.\nif __name__ == \"__main__\":\n\n    def gq(ds):\n        dd = ds.all_data()\n        return dd.quantities[\"TotalQuantity\"]((\"gas\", \"cell_mass\"))\n\n    q = io_nodes(\"DD0087/DD0087\", 8, 24, gq)\n    mylog.info(q)\n"
  },
  {
    "path": "yt/utilities/parallel_tools/parallel_analysis_interface.py",
    "content": "import itertools\nimport logging\nimport os\nimport sys\nimport traceback\nfrom functools import wraps\nfrom io import StringIO\n\nimport numpy as np\nfrom more_itertools import always_iterable\n\nimport yt.utilities.logger\nfrom yt.config import ytcfg\nfrom yt.data_objects.image_array import ImageArray\nfrom yt.funcs import is_sequence\nfrom yt.units.unit_registry import UnitRegistry  # type: ignore\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.exceptions import YTNoDataInObjectError\nfrom yt.utilities.lib.quad_tree import QuadTree, merge_quadtrees\nfrom yt.utilities.logger import ytLogger as mylog\n\n# We default to *no* parallelism unless it gets turned on, in which case this\n# will be changed.\nMPI = None\nparallel_capable = False\n\ndtype_names = {\n    \"float32\": \"MPI.FLOAT\",\n    \"float64\": \"MPI.DOUBLE\",\n    \"int32\": \"MPI.INT\",\n    \"int64\": \"MPI.LONG\",\n    \"c\": \"MPI.CHAR\",\n}\nop_names = {\"sum\": \"MPI.SUM\", \"min\": \"MPI.MIN\", \"max\": \"MPI.MAX\"}\n\n\nclass FilterAllMessages(logging.Filter):\n    \"\"\"\n    This is a simple filter for logging.Logger's that won't let any\n    messages pass.\n    \"\"\"\n\n    def filter(self, record):\n        return 0\n\n\n# Set up translation table and import things\n\n\ndef traceback_writer_hook(file_suffix=\"\"):\n    def write_to_file(exc_type, exc, tb):\n        sys.__excepthook__(exc_type, exc, tb)\n        fn = f\"yt_traceback{file_suffix}\"\n        with open(fn, \"w\") as fhandle:\n            traceback.print_exception(exc_type, exc, tb, file=fhandle)\n            print(f\"Wrote traceback to {fn}\")\n        MPI.COMM_WORLD.Abort(1)\n\n    return write_to_file\n\n\ndef default_mpi_excepthook(exception_type, exception_value, tb):\n    traceback.print_tb(tb)\n    mylog.error(\"%s: %s\", exception_type.__name__, exception_value)\n    comm = yt.communication_system.communicators[-1]\n    if comm.size > 1:\n        mylog.error(\"Error occurred on rank %d.\", comm.rank)\n    MPI.COMM_WORLD.Abort(1)\n\n\ndef enable_parallelism(suppress_logging: bool = False, communicator=None) -> bool:\n    \"\"\"\n    This method is used inside a script to turn on MPI parallelism, via\n    mpi4py.  More information about running yt in parallel can be found\n    here: https://yt-project.org/docs/3.0/analyzing/parallel_computation.html\n\n    Parameters\n    ----------\n    suppress_logging : bool\n       If set to True, only rank 0 will log information after the initial\n       setup of MPI.\n\n    communicator : mpi4py.MPI.Comm\n        The MPI communicator to use. This controls which processes yt can see.\n        If not specified, will be set to COMM_WORLD.\n\n    Returns\n    -------\n    parallel_capable: bool\n        True if the call was successful. False otherwise.\n    \"\"\"\n    global parallel_capable, MPI\n    try:\n        from mpi4py import MPI as _MPI\n    except ImportError:\n        mylog.error(\"Could not enable parallelism: mpi4py is not installed\")\n        return False\n    MPI = _MPI\n    exe_name = os.path.basename(sys.executable)\n\n    # if no communicator specified, set to COMM_WORLD\n    if communicator is None:\n        communicator = MPI.COMM_WORLD.Dup()\n    else:\n        communicator = communicator.Dup()\n\n    parallel_capable = communicator.size > 1\n    if not parallel_capable:\n        mylog.error(\n            \"Could not enable parallelism: only one mpi process is running. \"\n            \"To remedy this, launch the Python interpreter as\\n\"\n            \"  mpirun -n <X> python3 <yourscript>.py  # with X > 1 \",\n        )\n        return False\n\n    mylog.info(\n        \"Global parallel computation enabled: %s / %s\",\n        communicator.rank,\n        communicator.size,\n    )\n    communication_system.push(communicator)\n    ytcfg[\"yt\", \"internals\", \"global_parallel_rank\"] = communicator.rank\n    ytcfg[\"yt\", \"internals\", \"global_parallel_size\"] = communicator.size\n    ytcfg[\"yt\", \"internals\", \"parallel\"] = True\n    if exe_name == \"embed_enzo\" or (\"_parallel\" in dir(sys) and sys._parallel):  # type: ignore\n        ytcfg[\"yt\", \"inline\"] = True\n    yt.utilities.logger.uncolorize_logging()\n    # Even though the uncolorize function already resets the format string,\n    # we reset it again so that it includes the processor.\n    f = logging.Formatter(f\"P{communicator.rank:03} {yt.utilities.logger.ufstring}\")\n    if len(yt.utilities.logger.ytLogger.handlers) > 0:\n        yt.utilities.logger.ytLogger.handlers[0].setFormatter(f)\n\n    if ytcfg.get(\"yt\", \"parallel_traceback\"):\n        sys.excepthook = traceback_writer_hook(f\"_{communicator.rank:03}\")\n    else:\n        sys.excepthook = default_mpi_excepthook\n\n    if ytcfg.get(\"yt\", \"log_level\") < 20:\n        yt.utilities.logger.ytLogger.warning(\n            \"Log Level is set low -- this could affect parallel performance!\"\n        )\n    dtype_names.update(\n        {\n            \"float32\": MPI.FLOAT,\n            \"float64\": MPI.DOUBLE,\n            \"int32\": MPI.INT,\n            \"int64\": MPI.LONG,\n            \"c\": MPI.CHAR,\n        }\n    )\n    op_names.update({\"sum\": MPI.SUM, \"min\": MPI.MIN, \"max\": MPI.MAX})\n    # Turn off logging on all but the root rank, if specified.\n    if suppress_logging:\n        if communicator.rank > 0:\n            mylog.addFilter(FilterAllMessages())\n    return True\n\n\n# Because the dtypes will == correctly but do not hash the same, we need this\n# function for dictionary access.\ndef get_mpi_type(dtype):\n    for dt, val in dtype_names.items():\n        if dt == dtype:\n            return val\n\n\nclass ObjectIterator:\n    \"\"\"\n    This is a generalized class that accepts a list of objects and then\n    attempts to intelligently iterate over them.\n    \"\"\"\n\n    def __init__(self, pobj, just_list=False, attr=\"_grids\"):\n        self.pobj = pobj\n        if hasattr(pobj, attr) and getattr(pobj, attr) is not None:\n            gs = getattr(pobj, attr)\n        else:\n            gs = getattr(pobj._data_source, attr)\n        if len(gs) == 0:\n            raise YTNoDataInObjectError(pobj)\n        if hasattr(gs[0], \"proc_num\"):\n            # This one sort of knows about MPI, but not quite\n            self._objs = [\n                g\n                for g in gs\n                if g.proc_num == ytcfg.get(\"yt\", \"internals\", \"topcomm_parallel_rank\")\n            ]\n            self._use_all = True\n        else:\n            self._objs = gs\n            if hasattr(self._objs[0], \"filename\"):\n                self._objs = sorted(self._objs, key=lambda g: g.filename)\n            self._use_all = False\n        self.ng = len(self._objs)\n        self.just_list = just_list\n\n    def __iter__(self):\n        yield from self._objs\n\n\nclass ParallelObjectIterator(ObjectIterator):\n    \"\"\"\n    This takes an object, *pobj*, that implements ParallelAnalysisInterface,\n    and then does its thing, calling initialize and finalize on the object.\n    \"\"\"\n\n    def __init__(self, pobj, just_list=False, attr=\"_grids\", round_robin=False):\n        ObjectIterator.__init__(self, pobj, just_list, attr=attr)\n        # pobj has to be a ParallelAnalysisInterface, so it must have a .comm\n        # object.\n        self._offset = pobj.comm.rank\n        self._skip = pobj.comm.size\n        # Note that we're doing this in advance, and with a simple means\n        # of choosing them; more advanced methods will be explored later.\n        if self._use_all:\n            self.my_obj_ids = np.arange(len(self._objs))\n        else:\n            if not round_robin:\n                self.my_obj_ids = np.array_split(\n                    np.arange(len(self._objs)), self._skip\n                )[self._offset]\n            else:\n                self.my_obj_ids = np.arange(len(self._objs))[self._offset :: self._skip]\n\n    def __iter__(self):\n        for gid in self.my_obj_ids:\n            yield self._objs[gid]\n        if not self.just_list:\n            self.pobj._finalize_parallel()\n\n\ndef parallel_simple_proxy(func):\n    \"\"\"\n    This is a decorator that broadcasts the result of computation on a single\n    processor to all other processors.  To do so, it uses the _processing and\n    _distributed flags in the object to check for blocks.  Meant only to be\n    used on objects that subclass\n    :class:`~yt.utilities.parallel_tools.parallel_analysis_interface.ParallelAnalysisInterface`.\n    \"\"\"\n\n    @wraps(func)\n    def single_proc_results(self, *args, **kwargs):\n        retval = None\n        if hasattr(self, \"dont_wrap\"):\n            if func.__name__ in self.dont_wrap:\n                return func(self, *args, **kwargs)\n        if not parallel_capable or self._processing or not self._distributed:\n            return func(self, *args, **kwargs)\n        comm = _get_comm((self,))\n        if self._owner == comm.rank:\n            self._processing = True\n            retval = func(self, *args, **kwargs)\n            self._processing = False\n        # To be sure we utilize the root= kwarg, we manually access the .comm\n        # attribute, which must be an instance of MPI.Intracomm, and call bcast\n        # on that.\n        retval = comm.comm.bcast(retval, root=self._owner)\n        return retval\n\n    return single_proc_results\n\n\nclass ParallelDummy(type):\n    \"\"\"\n    This is a base class that, on instantiation, replaces all attributes that\n    don't start with ``_`` with\n    :func:`~yt.utilities.parallel_tools.parallel_analysis_interface.parallel_simple_proxy`-wrapped\n    attributes.  Used as a metaclass.\n    \"\"\"\n\n    def __init__(cls, name, bases, d):\n        super().__init__(name, bases, d)\n        skip = d.pop(\"dont_wrap\", [])\n        extra = d.pop(\"extra_wrap\", [])\n        for attrname in d:\n            if attrname.startswith(\"_\") or attrname in skip:\n                if attrname not in extra:\n                    continue\n            attr = getattr(cls, attrname)\n            if callable(attr):\n                setattr(cls, attrname, parallel_simple_proxy(attr))\n\n\ndef parallel_passthrough(func):\n    \"\"\"\n    If we are not run in parallel, this function passes the input back as\n    output; otherwise, the function gets called.  Used as a decorator.\n    \"\"\"\n\n    @wraps(func)\n    def passage(self, *args, **kwargs):\n        if not self._distributed:\n            return args[0]\n        return func(self, *args, **kwargs)\n\n    return passage\n\n\ndef _get_comm(args):\n    if len(args) > 0 and hasattr(args[0], \"comm\"):\n        comm = args[0].comm\n    else:\n        comm = communication_system.communicators[-1]\n    return comm\n\n\ndef parallel_blocking_call(func):\n    \"\"\"\n    This decorator blocks on entry and exit of a function.\n    \"\"\"\n\n    @wraps(func)\n    def barrierize(*args, **kwargs):\n        if not parallel_capable:\n            return func(*args, **kwargs)\n        mylog.debug(\"Entering barrier before %s\", func.__name__)\n        comm = _get_comm(args)\n        comm.barrier()\n        retval = func(*args, **kwargs)\n        mylog.debug(\"Entering barrier after %s\", func.__name__)\n        comm.barrier()\n        return retval\n\n    return barrierize\n\n\ndef parallel_root_only_then_broadcast(func):\n    \"\"\"\n    This decorator blocks and calls the function on the root processor and then\n    broadcasts the results to the other processors.\n    \"\"\"\n\n    @wraps(func)\n    def root_only(*args, **kwargs):\n        if not parallel_capable:\n            return func(*args, **kwargs)\n        comm = _get_comm(args)\n        rv0 = None\n        if comm.rank == 0:\n            try:\n                rv0 = func(*args, **kwargs)\n            except Exception:\n                traceback.print_last()\n        rv = comm.mpi_bcast(rv0)\n        if not rv:\n            raise RuntimeError\n        return rv\n\n    return root_only\n\n\ndef parallel_root_only(func):\n    \"\"\"\n    This decorator blocks and calls the function on the root processor,\n    but does not broadcast results to the other processors.\n    \"\"\"\n\n    @wraps(func)\n    def root_only(*args, **kwargs):\n        if not parallel_capable:\n            return func(*args, **kwargs)\n        comm = _get_comm(args)\n        rv = None\n        if comm.rank == 0:\n            try:\n                rv = func(*args, **kwargs)\n                all_clear = 1\n            except Exception:\n                traceback.print_last()\n                all_clear = 0\n        else:\n            all_clear = None\n        all_clear = comm.mpi_bcast(all_clear)\n        if not all_clear:\n            raise RuntimeError\n        return rv\n\n    return root_only\n\n\nclass Workgroup:\n    def __init__(self, size, ranks, comm, name):\n        self.size = size\n        self.ranks = ranks\n        self.comm = comm\n        self.name = name\n\n\nclass ProcessorPool:\n    comm = None\n    size = None\n    ranks = None\n    available_ranks = None\n    tasks = None\n\n    def __init__(self):\n        self.comm = communication_system.communicators[-1]\n        self.size = self.comm.size\n        self.ranks = list(range(self.size))\n        self.available_ranks = list(range(self.size))\n        self.workgroups = []\n\n    def add_workgroup(self, size=None, ranks=None, name=None):\n        if size is None:\n            size = len(self.available_ranks)\n        if len(self.available_ranks) < size:\n            mylog.error(\n                \"Not enough resources available, asked for %d have %d\",\n                size,\n                self.available_ranks,\n            )\n            raise RuntimeError\n        if ranks is None:\n            ranks = [self.available_ranks.pop(0) for i in range(size)]\n        # Default name to the workgroup number.\n        if name is None:\n            name = str(len(self.workgroups))\n        group = self.comm.comm.Get_group().Incl(ranks)\n        new_comm = self.comm.comm.Create(group)\n        if self.comm.rank in ranks:\n            communication_system.communicators.append(Communicator(new_comm))\n        self.workgroups.append(Workgroup(len(ranks), ranks, new_comm, name))\n\n    def free_workgroup(self, workgroup):\n        # If you want to actually delete the workgroup you will need to\n        # pop it out of the self.workgroups list so you don't have references\n        # that are left dangling, e.g. see free_all() below.\n        for i in workgroup.ranks:\n            if self.comm.rank == i:\n                communication_system.communicators.pop()\n            self.available_ranks.append(i)\n        self.available_ranks.sort()\n\n    def free_all(self):\n        for wg in self.workgroups:\n            self.free_workgroup(wg)\n        while self.workgroups:\n            self.workgroups.pop(0)\n\n    @classmethod\n    def from_sizes(cls, sizes):\n        pool = cls()\n        rank = pool.comm.rank\n        for i, size in enumerate(always_iterable(sizes)):\n            if is_sequence(size):\n                size, name = size\n            else:\n                name = f\"workgroup_{i:02}\"\n            pool.add_workgroup(size, name=name)\n        for wg in pool.workgroups:\n            if rank in wg.ranks:\n                workgroup = wg\n        return pool, workgroup\n\n    def __getitem__(self, key):\n        for wg in self.workgroups:\n            if wg.name == key:\n                return wg\n        raise KeyError(key)\n\n\nclass ResultsStorage:\n    slots = [\"result\", \"result_id\"]\n    result = None\n    result_id = None\n\n\ndef parallel_objects(objects, njobs=0, storage=None, barrier=True, dynamic=False):\n    r\"\"\"This function dispatches components of an iterable to different\n    processors.\n\n    The parallel_objects function accepts an iterable, *objects*, and based on\n    the number of jobs requested and number of available processors, decides\n    how to dispatch individual objects to processors or sets of processors.\n    This can implicitly include multi-level parallelism, such that the\n    processor groups assigned each object can be composed of several or even\n    hundreds of processors.  *storage* is also available, for collation of\n    results at the end of the iteration loop.\n\n    Calls to this function can be nested.\n\n    This should not be used to iterate over datasets --\n    :class:`~yt.data_objects.time_series.DatasetSeries` provides a much nicer\n    interface for that.\n\n    Parameters\n    ----------\n    objects : Iterable\n        The list of objects to dispatch to different processors.\n    njobs : int\n        How many jobs to spawn.  By default, one job will be dispatched for\n        each available processor.\n    storage : dict\n        This is a dictionary, which will be filled with results during the\n        course of the iteration.  The keys will be the dataset\n        indices and the values will be whatever is assigned to the *result*\n        attribute on the storage during iteration.\n    barrier : bool\n        Should a barier be placed at the end of iteration?\n    dynamic : bool\n        This governs whether or not dynamic load balancing will be enabled.\n        This requires one dedicated processor; if this is enabled with a set of\n        128 processors available, only 127 will be available to iterate over\n        objects as one will be load balancing the rest.\n\n\n    Examples\n    --------\n    Here is a simple example of iterating over a set of centers and making\n    slice plots centered at each.\n\n    >>> for c in parallel_objects(centers):\n    ...     SlicePlot(ds, \"x\", \"Density\", center=c).save()\n    ...\n\n    Here's an example of calculating the angular momentum vector of a set of\n    spheres, but with a set of four jobs of multiple processors each.  Note\n    that we also store the results.\n\n    >>> storage = {}\n    >>> for sto, c in parallel_objects(centers, njobs=4, storage=storage):\n    ...     sp = ds.sphere(c, (100, \"kpc\"))\n    ...     sto.result = sp.quantities[\"AngularMomentumVector\"]()\n    ...\n    >>> for sphere_id, L in sorted(storage.items()):\n    ...     print(centers[sphere_id], L)\n    ...\n\n    \"\"\"\n    if dynamic:\n        from .task_queue import dynamic_parallel_objects\n\n        yield from dynamic_parallel_objects(objects, njobs=njobs, storage=storage)\n        return\n\n    if not parallel_capable:\n        njobs = 1\n    my_communicator = communication_system.communicators[-1]\n    my_size = my_communicator.size\n    if njobs <= 0:\n        njobs = my_size\n    if njobs > my_size:\n        mylog.error(\n            \"You have asked for %s jobs, but you only have %s processors.\",\n            njobs,\n            my_size,\n        )\n        raise RuntimeError\n    my_rank = my_communicator.rank\n    all_new_comms = np.array_split(np.arange(my_size), njobs)\n    for i, comm_set in enumerate(all_new_comms):\n        if my_rank in comm_set:\n            my_new_id = i\n            break\n    if parallel_capable:\n        communication_system.push_with_ids(all_new_comms[my_new_id].tolist())\n    to_share = {}\n    # If our objects object is slice-aware, like time series data objects are,\n    # this will prevent intermediate objects from being created.\n    oiter = itertools.islice(enumerate(objects), my_new_id, None, njobs)\n    for result_id, obj in oiter:\n        if storage is not None:\n            rstore = ResultsStorage()\n            rstore.result_id = result_id\n            yield rstore, obj\n            to_share[rstore.result_id] = rstore.result\n        else:\n            yield obj\n    if parallel_capable:\n        communication_system.pop()\n    if storage is not None:\n        # Now we have to broadcast it\n        new_storage = my_communicator.par_combine_object(\n            to_share, datatype=\"dict\", op=\"join\"\n        )\n        storage.update(new_storage)\n    if barrier:\n        my_communicator.barrier()\n\n\ndef parallel_ring(objects, generator_func, mutable=False):\n    r\"\"\"This function loops in a ring around a set of objects, yielding the\n    results of generator_func and passing from one processor to another to\n    avoid IO or expensive computation.\n\n    This function is designed to operate in sequence on a set of objects, where\n    the creation of those objects might be expensive.  For instance, this could\n    be a set of particles that are costly to read from disk.  Processor N will\n    run generator_func on an object, and the results of that will both be\n    yielded and passed to processor N-1.  If the length of the objects is not\n    equal to the number of processors, then the final processor in the top\n    communicator will re-generate the data as needed.\n\n    In all likelihood, this function will only be useful internally to yt.\n\n    Parameters\n    ----------\n    objects : Iterable\n        The list of objects to operate on.\n    generator_func : callable\n        This function will be called on each object, and the results yielded.\n        It must return a single NumPy array; for multiple values, it needs to\n        have a custom dtype.\n    mutable : bool\n        Should the arrays be considered mutable?  Currently, this will only\n        work if the number of processors equals the number of objects.\n\n    Examples\n    --------\n    Here is a simple example of a ring loop around a set of integers, with a\n    custom dtype.\n\n    >>> dt = np.dtype([(\"x\", \"float64\"), (\"y\", \"float64\"), (\"z\", \"float64\")])\n    >>> def gfunc(o):\n    ...     np.random.seed(o)\n    ...     rv = np.empty(1000, dtype=dt)\n    ...     rv[\"x\"] = np.random.random(1000)\n    ...     rv[\"y\"] = np.random.random(1000)\n    ...     rv[\"z\"] = np.random.random(1000)\n    ...     return rv\n    ...\n    >>> obj = range(8)\n    >>> for obj, arr in parallel_ring(obj, gfunc):\n    ...     print(arr[\"x\"].sum(), arr[\"y\"].sum(), arr[\"z\"].sum())\n    ...\n\n    \"\"\"\n    if mutable:\n        raise NotImplementedError\n    my_comm = communication_system.communicators[-1]\n    my_size = my_comm.size\n    my_rank = my_comm.rank  # This will also be the first object we access\n    if not parallel_capable and not mutable:\n        for obj in objects:\n            yield obj, generator_func(obj)\n        return\n    generate_endpoints = len(objects) != my_size\n    # gback False: send the object backwards\n    # gforw False: receive an object from forwards\n    if len(objects) == my_size:\n        generate_endpoints = False\n        gback = False\n        gforw = False\n    else:\n        # In this case, the first processor (my_rank == 0) will generate.\n        generate_endpoints = True\n        gback = my_rank == 0\n        gforw = my_rank == my_size - 1\n    if generate_endpoints and mutable:\n        raise NotImplementedError\n    # Now we need to do pairwise sends\n    source = (my_rank + 1) % my_size\n    dest = (my_rank - 1) % my_size\n    oiter = itertools.islice(itertools.cycle(objects), my_rank, my_rank + len(objects))\n    idata = None\n    isize = np.zeros((1,), dtype=\"int64\")\n    osize = np.zeros((1,), dtype=\"int64\")\n    for obj in oiter:\n        if idata is None or gforw:\n            idata = generator_func(obj)\n            idtype = odtype = idata.dtype\n            if get_mpi_type(idtype) is None:\n                idtype = \"c\"\n        yield obj, idata\n        # We first send to the previous processor\n        tags = []\n        if not gforw:\n            tags.append(my_comm.mpi_nonblocking_recv(isize, source))\n        if not gback:\n            osize[0] = idata.size\n            tags.append(my_comm.mpi_nonblocking_send(osize, dest))\n        my_comm.mpi_Request_Waitall(tags)\n        odata = idata\n        tags = []\n        if not gforw:\n            idata = np.empty(isize[0], dtype=odtype)\n            tags.append(\n                my_comm.mpi_nonblocking_recv(idata.view(idtype), source, dtype=idtype)\n            )\n        if not gback:\n            tags.append(\n                my_comm.mpi_nonblocking_send(odata.view(idtype), dest, dtype=idtype)\n            )\n        my_comm.mpi_Request_Waitall(tags)\n        del odata\n\n\nclass CommunicationSystem:\n    communicators: list[\"Communicator\"] = []\n\n    def __init__(self):\n        self.communicators.append(Communicator(None))\n\n    def push(self, new_comm):\n        if not isinstance(new_comm, Communicator):\n            new_comm = Communicator(new_comm)\n        self.communicators.append(new_comm)\n        self._update_parallel_state(new_comm)\n\n    def push_with_ids(self, ids):\n        group = self.communicators[-1].comm.Get_group().Incl(ids)\n        new_comm = self.communicators[-1].comm.Create(group)\n        self.push(new_comm)\n        return new_comm\n\n    def _update_parallel_state(self, new_comm):\n        ytcfg[\"yt\", \"internals\", \"topcomm_parallel_size\"] = new_comm.size\n        ytcfg[\"yt\", \"internals\", \"topcomm_parallel_rank\"] = new_comm.rank\n        if new_comm.rank > 0 and ytcfg.get(\"yt\", \"serialize\"):\n            ytcfg[\"yt\", \"only_deserialize\"] = True\n\n    def pop(self):\n        self.communicators.pop()\n        self._update_parallel_state(self.communicators[-1])\n\n\ndef _reconstruct_communicator():\n    return communication_system.communicators[-1]\n\n\nclass Communicator:\n    comm = None\n    _grids = None\n    _distributed = None\n    __tocast = \"c\"\n\n    def __init__(self, comm=None):\n        self.comm = comm\n        self._distributed = comm is not None and self.comm.size > 1\n\n    def __del__(self):\n        if self.comm is not None:\n            self.comm.Free()\n\n    \"\"\"\n    This is an interface specification providing several useful utility\n    functions for analyzing something in parallel.\n    \"\"\"\n\n    def __reduce__(self):\n        # We don't try to reconstruct any of the properties of the communicator\n        # or the processors.  In general, we don't want to.\n        return (_reconstruct_communicator, ())\n\n    def barrier(self):\n        if not self._distributed:\n            return\n        mylog.debug(\"Opening MPI Barrier on %s\", self.comm.rank)\n        self.comm.Barrier()\n\n    def mpi_exit_test(self, data=False):\n        # data==True -> exit. data==False -> no exit\n        mine, statuses = self.mpi_info_dict(data)\n        if True in statuses.values():\n            raise RuntimeError(\"Fatal error. Exiting.\")\n        return None\n\n    @parallel_passthrough\n    def par_combine_object(self, data, op, datatype=None):\n        # op can be chosen from:\n        #   cat\n        #   join\n        # data is selected to be of types:\n        #   np.ndarray\n        #   dict\n        #   data field dict\n        if datatype is not None:\n            pass\n        elif isinstance(data, dict):\n            datatype = \"dict\"\n        elif isinstance(data, np.ndarray):\n            datatype = \"array\"\n        elif isinstance(data, list):\n            datatype = \"list\"\n        # Now we have our datatype, and we conduct our operation\n        if datatype == \"dict\" and op == \"join\":\n            if self.comm.rank == 0:\n                for i in range(1, self.comm.size):\n                    data.update(self.comm.recv(source=i, tag=0))\n            else:\n                self.comm.send(data, dest=0, tag=0)\n\n            # Send the keys first, then each item one by one\n            # This is to prevent MPI from crashing when sending more\n            # than 2GiB of data over the network.\n            keys = self.comm.bcast(list(data.keys()), root=0)\n            for key in keys:\n                tmp = data.get(key, None)\n                data[key] = self.comm.bcast(tmp, root=0)\n            return data\n        elif datatype == \"dict\" and op == \"cat\":\n            field_keys = sorted(data.keys())\n            size = data[field_keys[0]].shape[-1]\n            sizes = np.zeros(self.comm.size, dtype=\"int64\")\n            outsize = np.array(size, dtype=\"int64\")\n            self.comm.Allgather([outsize, 1, MPI.LONG], [sizes, 1, MPI.LONG])\n            # This nested concatenate is to get the shapes to work out correctly;\n            # if we just add [0] to sizes, it will broadcast a summation, not a\n            # concatenation.\n            offsets = np.add.accumulate(np.concatenate([[0], sizes]))[:-1]\n            arr_size = self.comm.allreduce(size, op=MPI.SUM)\n            for key in field_keys:\n                dd = data[key]\n                rv = self.alltoallv_array(dd, arr_size, offsets, sizes)\n                data[key] = rv\n            return data\n        elif datatype == \"array\" and op == \"cat\":\n            if data is None:\n                ncols = -1\n                size = 0\n                dtype = \"float64\"\n                mylog.warning(\n                    \"Array passed to par_combine_object was None. \"\n                    \"Setting dtype to float64. This may break things!\"\n                )\n            else:\n                dtype = data.dtype\n                if len(data) == 0:\n                    ncols = -1\n                    size = 0\n                elif len(data.shape) == 1:\n                    ncols = 1\n                    size = data.shape[0]\n                else:\n                    ncols, size = data.shape\n            ncols = self.comm.allreduce(ncols, op=MPI.MAX)\n            if ncols == 0:\n                data = np.zeros(0, dtype=dtype)  # This only works for\n            elif data is None:\n                data = np.zeros((ncols, 0), dtype=dtype)\n            size = data.shape[-1]\n            sizes = np.zeros(self.comm.size, dtype=\"int64\")\n            outsize = np.array(size, dtype=\"int64\")\n            self.comm.Allgather([outsize, 1, MPI.LONG], [sizes, 1, MPI.LONG])\n            # This nested concatenate is to get the shapes to work out correctly;\n            # if we just add [0] to sizes, it will broadcast a summation, not a\n            # concatenation.\n            offsets = np.add.accumulate(np.concatenate([[0], sizes]))[:-1]\n            arr_size = self.comm.allreduce(size, op=MPI.SUM)\n            data = self.alltoallv_array(data, arr_size, offsets, sizes)\n            return data\n        elif datatype == \"list\" and op == \"cat\":\n            recv_data = self.comm.allgather(data)\n            # Now flatten into a single list, since this\n            # returns us a list of lists.\n            data = []\n            while recv_data:\n                data.extend(recv_data.pop(0))\n            return data\n        raise NotImplementedError\n\n    @parallel_passthrough\n    def mpi_bcast(self, data, root=0):\n        # The second check below makes sure that we know how to communicate\n        # this type of array. Otherwise, we'll pickle it.\n        if isinstance(data, np.ndarray) and get_mpi_type(data.dtype) is not None:\n            if self.comm.rank == root:\n                if isinstance(data, YTArray):\n                    info = (\n                        data.shape,\n                        data.dtype,\n                        str(data.units),\n                        data.units.registry.lut,\n                    )\n                    if isinstance(data, ImageArray):\n                        info += (\"ImageArray\",)\n                    else:\n                        info += (\"YTArray\",)\n                else:\n                    info = (data.shape, data.dtype)\n            else:\n                info = ()\n            info = self.comm.bcast(info, root=root)\n            if self.comm.rank != root:\n                if len(info) == 5:\n                    registry = UnitRegistry(lut=info[3], add_default_symbols=False)\n                    if info[-1] == \"ImageArray\":\n                        data = ImageArray(\n                            np.empty(info[0], dtype=info[1]),\n                            units=info[2],\n                            registry=registry,\n                        )\n                    else:\n                        data = YTArray(\n                            np.empty(info[0], dtype=info[1]), info[2], registry=registry\n                        )\n                else:\n                    data = np.empty(info[0], dtype=info[1])\n            mpi_type = get_mpi_type(info[1])\n            self.comm.Bcast([data, mpi_type], root=root)\n            return data\n        else:\n            # Use pickled methods.\n            data = self.comm.bcast(data, root=root)\n            return data\n\n    def preload(self, grids, fields, io_handler):\n        # This is non-functional.\n        return\n\n    @parallel_passthrough\n    def mpi_allreduce(self, data, dtype=None, op=\"sum\"):\n        op = op_names[op]\n        if isinstance(data, np.ndarray) and data.dtype != bool:\n            if dtype is None:\n                dtype = data.dtype\n            if dtype != data.dtype:\n                data = data.astype(dtype)\n            temp = data.copy()\n            self.comm.Allreduce(\n                [temp, get_mpi_type(dtype)], [data, get_mpi_type(dtype)], op\n            )\n            return data\n        else:\n            # We use old-school pickling here on the assumption the arrays are\n            # relatively small ( < 1e7 elements )\n            return self.comm.allreduce(data, op)\n\n    ###\n    # Non-blocking stuff.\n    ###\n\n    def mpi_nonblocking_recv(self, data, source, tag=0, dtype=None):\n        if not self._distributed:\n            return -1\n        if dtype is None:\n            dtype = data.dtype\n        mpi_type = get_mpi_type(dtype)\n        return self.comm.Irecv([data, mpi_type], source, tag)\n\n    def mpi_nonblocking_send(self, data, dest, tag=0, dtype=None):\n        if not self._distributed:\n            return -1\n        if dtype is None:\n            dtype = data.dtype\n        mpi_type = get_mpi_type(dtype)\n        return self.comm.Isend([data, mpi_type], dest, tag)\n\n    def mpi_Request_Waitall(self, hooks):\n        if not self._distributed:\n            return\n        MPI.Request.Waitall(hooks)\n\n    def mpi_Request_Waititer(self, hooks):\n        for _hook in hooks:\n            req = MPI.Request.Waitany(hooks)\n            yield req\n\n    def mpi_Request_Testall(self, hooks):\n        \"\"\"\n        This returns False if any of the request hooks are un-finished,\n        and True if they are all finished.\n        \"\"\"\n        if not self._distributed:\n            return True\n        return MPI.Request.Testall(hooks)\n\n    ###\n    # End non-blocking stuff.\n    ###\n\n    ###\n    # Parallel rank and size properties.\n    ###\n\n    @property\n    def size(self):\n        if not self._distributed:\n            return 1\n        return self.comm.size\n\n    @property\n    def rank(self):\n        if not self._distributed:\n            return 0\n        return self.comm.rank\n\n    def mpi_info_dict(self, info):\n        if not self._distributed:\n            return 0, {0: info}\n        data = None\n        if self.comm.rank == 0:\n            data = {0: info}\n            for i in range(1, self.comm.size):\n                data[i] = self.comm.recv(source=i, tag=0)\n        else:\n            self.comm.send(info, dest=0, tag=0)\n        mylog.debug(\"Opening MPI Broadcast on %s\", self.comm.rank)\n        data = self.comm.bcast(data, root=0)\n        return self.comm.rank, data\n\n    def claim_object(self, obj):\n        if not self._distributed:\n            return\n        obj._owner = self.comm.rank\n        obj._distributed = True\n\n    def do_not_claim_object(self, obj):\n        if not self._distributed:\n            return\n        obj._owner = -1\n        obj._distributed = True\n\n    def write_on_root(self, fn):\n        if not self._distributed:\n            return open(fn, \"w\")\n        if self.comm.rank == 0:\n            return open(fn, \"w\")\n        else:\n            return StringIO()\n\n    def get_filename(self, prefix, rank=None):\n        if not self._distributed:\n            return prefix\n        else:\n            return f\"{prefix}_{rank or self.comm.rank:04}\"\n\n    def is_mine(self, obj):\n        if not obj._distributed:\n            return True\n        return obj._owner == self.comm.rank\n\n    def send_quadtree(self, target, buf, tgd, args):\n        sizebuf = np.zeros(1, \"int64\")\n        sizebuf[0] = buf[0].size\n        self.comm.Send([sizebuf, MPI.LONG], dest=target)\n        self.comm.Send([buf[0], MPI.INT], dest=target)\n        self.comm.Send([buf[1], MPI.DOUBLE], dest=target)\n        self.comm.Send([buf[2], MPI.DOUBLE], dest=target)\n\n    def recv_quadtree(self, target, tgd, args):\n        sizebuf = np.zeros(1, \"int64\")\n        self.comm.Recv(sizebuf, source=target)\n        buf = [\n            np.empty((sizebuf[0],), \"int32\"),\n            np.empty((sizebuf[0], args[2]), \"float64\"),\n            np.empty((sizebuf[0],), \"float64\"),\n        ]\n        self.comm.Recv([buf[0], MPI.INT], source=target)\n        self.comm.Recv([buf[1], MPI.DOUBLE], source=target)\n        self.comm.Recv([buf[2], MPI.DOUBLE], source=target)\n        return buf\n\n    @parallel_passthrough\n    def merge_quadtree_buffers(self, qt, merge_style):\n        # This is a modified version of pairwise reduction from Lisandro Dalcin,\n        # in the reductions demo of mpi4py\n        size = self.comm.size\n        rank = self.comm.rank\n\n        mask = 1\n\n        buf = qt.tobuffer()\n        print(\"PROC\", rank, buf[0].shape, buf[1].shape, buf[2].shape)\n        sys.exit()\n\n        args = qt.get_args()  # Will always be the same\n        tgd = np.array([args[0], args[1]], dtype=\"int64\")\n        sizebuf = np.zeros(1, \"int64\")\n\n        while mask < size:\n            if (mask & rank) != 0:\n                target = (rank & ~mask) % size\n                # print(\"SENDING FROM %02i to %02i\" % (rank, target))\n                buf = qt.tobuffer()\n                self.send_quadtree(target, buf, tgd, args)\n                # qt = self.recv_quadtree(target, tgd, args)\n            else:\n                target = rank | mask\n                if target < size:\n                    # print(\"RECEIVING FROM %02i on %02i\" % (target, rank))\n                    buf = self.recv_quadtree(target, tgd, args)\n                    qto = QuadTree(tgd, args[2], qt.bounds)\n                    qto.frombuffer(buf[0], buf[1], buf[2], merge_style)\n                    merge_quadtrees(qt, qto, style=merge_style)\n                    del qto\n                    # self.send_quadtree(target, qt, tgd, args)\n            mask <<= 1\n\n        if rank == 0:\n            buf = qt.tobuffer()\n            sizebuf[0] = buf[0].size\n        self.comm.Bcast([sizebuf, MPI.LONG], root=0)\n        if rank != 0:\n            buf = [\n                np.empty((sizebuf[0],), \"int32\"),\n                np.empty((sizebuf[0], args[2]), \"float64\"),\n                np.empty((sizebuf[0],), \"float64\"),\n            ]\n        self.comm.Bcast([buf[0], MPI.INT], root=0)\n        self.comm.Bcast([buf[1], MPI.DOUBLE], root=0)\n        self.comm.Bcast([buf[2], MPI.DOUBLE], root=0)\n        self.refined = buf[0]\n        if rank != 0:\n            qt = QuadTree(tgd, args[2], qt.bounds)\n            qt.frombuffer(buf[0], buf[1], buf[2], merge_style)\n        return qt\n\n    def send_array(self, arr, dest, tag=0):\n        if not isinstance(arr, np.ndarray):\n            self.comm.send((None, None), dest=dest, tag=tag)\n            self.comm.send(arr, dest=dest, tag=tag)\n            return\n        tmp = arr.view(self.__tocast)  # Cast to CHAR\n        # communicate type and shape and optionally units\n        if isinstance(arr, YTArray):\n            unit_metadata = (str(arr.units), arr.units.registry.lut)\n            if isinstance(arr, ImageArray):\n                unit_metadata += (\"ImageArray\",)\n            else:\n                unit_metadata += (\"YTArray\",)\n        else:\n            unit_metadata = ()\n        self.comm.send((arr.dtype.str, arr.shape) + unit_metadata, dest=dest, tag=tag)\n        self.comm.Send([arr, MPI.CHAR], dest=dest, tag=tag)\n        del tmp\n\n    def recv_array(self, source, tag=0):\n        metadata = self.comm.recv(source=source, tag=tag)\n        dt, ne = metadata[:2]\n        if ne is None and dt is None:\n            return self.comm.recv(source=source, tag=tag)\n        arr = np.empty(ne, dtype=dt)\n        if len(metadata) == 5:\n            registry = UnitRegistry(lut=metadata[3], add_default_symbols=False)\n            if metadata[-1] == \"ImageArray\":\n                arr = ImageArray(arr, units=metadata[2], registry=registry)\n            else:\n                arr = YTArray(arr, metadata[2], registry=registry)\n        tmp = arr.view(self.__tocast)\n        self.comm.Recv([tmp, MPI.CHAR], source=source, tag=tag)\n        return arr\n\n    def alltoallv_array(self, send, total_size, offsets, sizes):\n        if len(send.shape) > 1:\n            recv = []\n            for i in range(send.shape[0]):\n                recv.append(\n                    self.alltoallv_array(send[i, :].copy(), total_size, offsets, sizes)\n                )\n            recv = np.array(recv)\n            return recv\n        offset = offsets[self.comm.rank]\n        tmp_send = send.view(self.__tocast)\n        recv = np.empty(total_size, dtype=send.dtype)\n        if isinstance(send, YTArray):\n            # We assume send.units is consistent with the units\n            # on the receiving end.\n            if isinstance(send, ImageArray):\n                recv = ImageArray(recv, units=send.units)\n            else:\n                recv = YTArray(recv, send.units)\n        recv[offset : offset + send.size] = send[:]\n        dtr = send.dtype.itemsize / tmp_send.dtype.itemsize  # > 1\n        roff = [off * dtr for off in offsets]\n        rsize = [siz * dtr for siz in sizes]\n        tmp_recv = recv.view(self.__tocast)\n        self.comm.Allgatherv(\n            (tmp_send, tmp_send.size, MPI.CHAR), (tmp_recv, (rsize, roff), MPI.CHAR)\n        )\n        return recv\n\n    def probe_loop(self, tag, callback):\n        while True:\n            st = MPI.Status()\n            self.comm.Probe(MPI.ANY_SOURCE, tag=tag, status=st)\n            try:\n                callback(st)\n            except StopIteration:\n                mylog.debug(\"Probe loop ending.\")\n                break\n\n\ncommunication_system = CommunicationSystem()\n\n\nclass ParallelAnalysisInterface:\n    comm = None\n    _grids = None\n    _distributed = None\n\n    def __init__(self, comm=None):\n        if comm is None:\n            self.comm = communication_system.communicators[-1]\n        else:\n            self.comm = comm\n        self._grids = self.comm._grids\n        self._distributed = self.comm._distributed\n\n    def _get_objs(self, attr, *args, **kwargs):\n        if self._distributed:\n            rr = kwargs.pop(\"round_robin\", False)\n            self._initialize_parallel(*args, **kwargs)\n            return ParallelObjectIterator(self, attr=attr, round_robin=rr)\n        return ObjectIterator(self, attr=attr)\n\n    def _get_grids(self, *args, **kwargs):\n        if self._distributed:\n            self._initialize_parallel(*args, **kwargs)\n            return ParallelObjectIterator(self, attr=\"_grids\")\n        return ObjectIterator(self, attr=\"_grids\")\n\n    def _get_grid_objs(self):\n        if self._distributed:\n            return ParallelObjectIterator(self, True, attr=\"_grids\")\n        return ObjectIterator(self, True, attr=\"_grids\")\n\n    def get_dependencies(self, fields):\n        deps = []\n        fi = self.ds.field_info\n        for field in fields:\n            if any(getattr(v, \"ghost_zones\", 0) > 0 for v in fi[field].validators):\n                continue\n            deps += list(\n                always_iterable(fi[field].get_dependencies(ds=self.ds).requested)\n            )\n        return list(set(deps))\n\n    def _initialize_parallel(self):\n        pass\n\n    def _finalize_parallel(self):\n        pass\n\n    def partition_index_2d(self, axis):\n        center = getattr(self, \"center\", self.ds.domain_center)\n        if not self._distributed:\n            return False, self.index.grid_collection(center, self.index.grids)\n\n        xax = self.ds.coordinates.x_axis[axis]\n        yax = self.ds.coordinates.y_axis[axis]\n        cc = MPI.Compute_dims(self.comm.size, 2)\n        mi = self.comm.rank\n        cx, cy = np.unravel_index(mi, cc)\n        x = np.mgrid[0 : 1 : (cc[0] + 1) * 1j][cx : cx + 2]\n        y = np.mgrid[0 : 1 : (cc[1] + 1) * 1j][cy : cy + 2]\n\n        DLE, DRE = self.ds.domain_left_edge.copy(), self.ds.domain_right_edge.copy()\n        LE = np.ones(3, dtype=\"float64\") * DLE\n        RE = np.ones(3, dtype=\"float64\") * DRE\n        LE[xax] = x[0] * (DRE[xax] - DLE[xax]) + DLE[xax]\n        RE[xax] = x[1] * (DRE[xax] - DLE[xax]) + DLE[xax]\n        LE[yax] = y[0] * (DRE[yax] - DLE[yax]) + DLE[yax]\n        RE[yax] = y[1] * (DRE[yax] - DLE[yax]) + DLE[yax]\n        mylog.debug(\"Dimensions: %s %s\", LE, RE)\n\n        reg = self.ds.region(center, LE, RE)\n        return True, reg\n\n    def partition_index_3d(self, ds, padding=0.0, rank_ratio=1):\n        LE, RE = np.array(ds.left_edge), np.array(ds.right_edge)\n        # We need to establish if we're looking at a subvolume, in which case\n        # we *do* want to pad things.\n        if (LE == self.ds.domain_left_edge).all() and (\n            RE == self.ds.domain_right_edge\n        ).all():\n            subvol = False\n        else:\n            subvol = True\n        if not self._distributed and not subvol:\n            return False, LE, RE, ds\n        if not self._distributed and subvol:\n            return True, LE, RE, self.ds.region(self.center, LE - padding, RE + padding)\n        elif ytcfg.get(\"yt\", \"inline\"):\n            # At this point, we want to identify the root grid tile to which\n            # this processor is assigned.\n            # The only way I really know how to do this is to get the level-0\n            # grid that belongs to this processor.\n            grids = self.ds.index.select_grids(0)\n            root_grids = [g for g in grids if g.proc_num == self.comm.rank]\n            if len(root_grids) != 1:\n                raise RuntimeError\n            # raise KeyError\n            LE = root_grids[0].LeftEdge\n            RE = root_grids[0].RightEdge\n            return True, LE, RE, self.ds.region(self.center, LE, RE)\n\n        cc = MPI.Compute_dims(self.comm.size / rank_ratio, 3)\n        mi = self.comm.rank % (self.comm.size // rank_ratio)\n        cx, cy, cz = np.unravel_index(mi, cc)\n        x = np.mgrid[LE[0] : RE[0] : (cc[0] + 1) * 1j][cx : cx + 2]\n        y = np.mgrid[LE[1] : RE[1] : (cc[1] + 1) * 1j][cy : cy + 2]\n        z = np.mgrid[LE[2] : RE[2] : (cc[2] + 1) * 1j][cz : cz + 2]\n\n        LE = np.array([x[0], y[0], z[0]], dtype=\"float64\")\n        RE = np.array([x[1], y[1], z[1]], dtype=\"float64\")\n\n        if padding > 0:\n            return True, LE, RE, self.ds.region(self.center, LE - padding, RE + padding)\n\n        return False, LE, RE, self.ds.region(self.center, LE, RE)\n\n    def partition_region_3d(self, left_edge, right_edge, padding=0.0, rank_ratio=1):\n        \"\"\"\n        Given a region, it subdivides it into smaller regions for parallel\n        analysis.\n        \"\"\"\n        LE, RE = left_edge[:], right_edge[:]\n        if not self._distributed:\n            raise NotImplementedError\n\n        cc = MPI.Compute_dims(self.comm.size / rank_ratio, 3)\n        mi = self.comm.rank % (self.comm.size // rank_ratio)\n        cx, cy, cz = np.unravel_index(mi, cc)\n        x = np.mgrid[LE[0] : RE[0] : (cc[0] + 1) * 1j][cx : cx + 2]\n        y = np.mgrid[LE[1] : RE[1] : (cc[1] + 1) * 1j][cy : cy + 2]\n        z = np.mgrid[LE[2] : RE[2] : (cc[2] + 1) * 1j][cz : cz + 2]\n\n        LE = np.array([x[0], y[0], z[0]], dtype=\"float64\")\n        RE = np.array([x[1], y[1], z[1]], dtype=\"float64\")\n\n        if padding > 0:\n            return True, LE, RE, self.ds.region(self.center, LE - padding, RE + padding)\n\n        return False, LE, RE, self.ds.region(self.center, LE, RE)\n\n    def partition_index_3d_bisection_list(self):\n        \"\"\"\n        Returns an array that is used to drive _partition_index_3d_bisection,\n        below.\n        \"\"\"\n\n        def factor(n):\n            if n == 1:\n                return [1]\n            i = 2\n            limit = n**0.5\n            while i <= limit:\n                if n % i == 0:\n                    ret = factor(n / i)\n                    ret.append(i)\n                    return ret\n                i += 1\n            return [n]\n\n        cc = MPI.Compute_dims(self.comm.size, 3)\n        si = self.comm.size\n\n        factors = factor(si)\n        xyzfactors = [factor(cc[0]), factor(cc[1]), factor(cc[2])]\n\n        # Each entry of cuts is a two element list, that is:\n        # [cut dim, number of cuts]\n        cuts = []\n        # The higher cuts are in the beginning.\n        # We're going to do our best to make the cuts cyclic, i.e. x, then y,\n        # then z, etc...\n        lastdim = 0\n        for f in factors:\n            nextdim = (lastdim + 1) % 3\n            while True:\n                if f in xyzfactors[nextdim]:\n                    cuts.append([nextdim, f])\n                    topop = xyzfactors[nextdim].index(f)\n                    xyzfactors[nextdim].pop(topop)\n                    lastdim = nextdim\n                    break\n                nextdim = (nextdim + 1) % 3\n        return cuts\n\n\nclass GroupOwnership(ParallelAnalysisInterface):\n    def __init__(self, items):\n        ParallelAnalysisInterface.__init__(self)\n        self.num_items = len(items)\n        self.items = items\n        assert self.num_items >= self.comm.size\n        self.owned = list(range(self.comm.size))\n        self.pointer = 0\n        if parallel_capable:\n            communication_system.push_with_ids([self.comm.rank])\n\n    def __del__(self):\n        if parallel_capable:\n            communication_system.pop()\n\n    def inc(self, n=-1):\n        old_item = self.item\n        if n == -1:\n            n = self.comm.size\n        for _ in range(n):\n            if self.pointer >= self.num_items - self.comm.size:\n                break\n            self.owned[self.pointer % self.comm.size] += self.comm.size\n            self.pointer += 1\n        if self.item is not old_item:\n            self.switch()\n\n    def dec(self, n=-1):\n        old_item = self.item\n        if n == -1:\n            n = self.comm.size\n        for _ in range(n):\n            if self.pointer == 0:\n                break\n            self.owned[(self.pointer - 1) % self.comm.size] -= self.comm.size\n            self.pointer -= 1\n        if self.item is not old_item:\n            self.switch()\n\n    _last = None\n\n    @property\n    def item(self):\n        own = self.owned[self.comm.rank]\n        if self._last != own:\n            self._item = self.items[own]\n            self._last = own\n        return self._item\n\n    def switch(self):\n        pass\n"
  },
  {
    "path": "yt/utilities/parallel_tools/task_queue.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog\n\nfrom .parallel_analysis_interface import (\n    ResultsStorage,\n    _get_comm,\n    communication_system,\n    parallel_capable,\n)\n\nmessages = {\n    \"task\": {\"msg\": \"next\"},\n    \"result\": {\"msg\": \"result\"},\n    \"task_req\": {\"msg\": \"task_req\"},\n    \"end\": {\"msg\": \"no_more_tasks\"},\n}\n\n\nclass TaskQueueNonRoot:\n    def __init__(self, tasks, comm, subcomm):\n        self.tasks = tasks\n        self.results = {}\n        self.comm = comm\n        self.subcomm = subcomm\n\n    def send_result(self, result):\n        new_msg = messages[\"result\"].copy()\n        new_msg[\"value\"] = result\n        if self.subcomm.rank == 0:\n            self.comm.comm.send(new_msg, dest=0, tag=1)\n        self.subcomm.barrier()\n\n    def __next__(self):\n        msg = messages[\"task_req\"].copy()\n        if self.subcomm.rank == 0:\n            self.comm.comm.send(msg, dest=0, tag=1)\n            msg = self.comm.comm.recv(source=0, tag=2)\n        msg = self.subcomm.bcast(msg, root=0)\n        if msg[\"msg\"] == messages[\"end\"][\"msg\"]:\n            mylog.debug(\"Notified to end\")\n            raise StopIteration\n        return msg[\"value\"]\n\n    # For Python 2 compatibility\n    next = __next__\n\n    def __iter__(self):\n        return self\n\n    def run(self, callable):\n        for task in self:\n            self.send_result(callable(task))\n        return self.finalize()\n\n    def finalize(self, vals=None):\n        return self.comm.comm.bcast(vals, root=0)\n\n\nclass TaskQueueRoot(TaskQueueNonRoot):\n    def __init__(self, tasks, comm, njobs):\n        self.njobs = njobs\n        self.tasks = tasks\n        self.results = {}\n        self.assignments = {}\n        self._notified = 0\n        self._current = 0\n        self._remaining = len(self.tasks)\n        self.comm = comm\n        # Set up threading here\n        # self.dist = threading.Thread(target=self.handle_assignments)\n        # self.dist.daemon = True\n        # self.dist.start()\n\n    def run(self, func=None):\n        self.comm.probe_loop(1, self.handle_assignment)\n        return self.finalize(self.results)\n\n    def insert_result(self, source_id, rstore):\n        task_id = rstore.result_id\n        self.results[task_id] = rstore.result\n\n    def assign_task(self, source_id):\n        if self._remaining == 0:\n            mylog.debug(\"Notifying %s to end\", source_id)\n            msg = messages[\"end\"].copy()\n            self._notified += 1\n        else:\n            msg = messages[\"task\"].copy()\n            task_id = self._current\n            task = self.tasks[task_id]\n            self.assignments[source_id] = task_id\n            self._current += 1\n            self._remaining -= 1\n            msg[\"value\"] = task\n        self.comm.comm.send(msg, dest=source_id, tag=2)\n\n    def handle_assignment(self, status):\n        msg = self.comm.comm.recv(source=status.source, tag=1)\n        if msg[\"msg\"] == messages[\"result\"][\"msg\"]:\n            self.insert_result(status.source, msg[\"value\"])\n        elif msg[\"msg\"] == messages[\"task_req\"][\"msg\"]:\n            self.assign_task(status.source)\n        else:\n            mylog.error(\"GOT AN UNKNOWN MESSAGE: %s\", msg)\n            raise RuntimeError\n        if self._notified >= self.njobs:\n            raise StopIteration\n\n\ndef task_queue(func, tasks, njobs=0):\n    comm = _get_comm(())\n    if not parallel_capable:\n        mylog.error(\"Cannot create task queue for serial process.\")\n        raise RuntimeError\n    my_size = comm.comm.size\n    if njobs <= 0:\n        njobs = my_size - 1\n    if njobs >= my_size:\n        mylog.error(\n            \"You have asked for %s jobs, but only %s processors are available.\",\n            njobs,\n            (my_size - 1),\n        )\n        raise RuntimeError\n    my_rank = comm.rank\n    all_new_comms = np.array_split(np.arange(1, my_size), njobs)\n    all_new_comms.insert(0, np.array([0]))\n    for i, comm_set in enumerate(all_new_comms):\n        if my_rank in comm_set:\n            my_new_id = i\n            break\n    subcomm = communication_system.push_with_ids(all_new_comms[my_new_id].tolist())\n\n    if comm.comm.rank == 0:\n        my_q = TaskQueueRoot(tasks, comm, njobs)\n    else:\n        my_q = TaskQueueNonRoot(None, comm, subcomm)\n    communication_system.pop()\n    return my_q.run(func)\n\n\ndef dynamic_parallel_objects(tasks, njobs=0, storage=None, broadcast=True):\n    comm = _get_comm(())\n    if not parallel_capable:\n        mylog.error(\"Cannot create task queue for serial process.\")\n        raise RuntimeError\n    my_size = comm.comm.size\n    if njobs <= 0:\n        njobs = my_size - 1\n    if njobs >= my_size:\n        mylog.error(\n            \"You have asked for %s jobs, but only %s processors are available.\",\n            njobs,\n            (my_size - 1),\n        )\n        raise RuntimeError\n    my_rank = comm.rank\n    all_new_comms = np.array_split(np.arange(1, my_size), njobs)\n    all_new_comms.insert(0, np.array([0]))\n    for i, comm_set in enumerate(all_new_comms):\n        if my_rank in comm_set:\n            my_new_id = i\n            break\n    subcomm = communication_system.push_with_ids(all_new_comms[my_new_id].tolist())\n\n    if comm.comm.rank == 0:\n        my_q = TaskQueueRoot(tasks, comm, njobs)\n        my_q.comm.probe_loop(1, my_q.handle_assignment)\n    else:\n        my_q = TaskQueueNonRoot(None, comm, subcomm)\n        if storage is None:\n            for task in my_q:\n                yield task\n        else:\n            for task in my_q:\n                rstore = ResultsStorage()\n                yield rstore, task\n                my_q.send_result(rstore)\n\n    if storage is not None:\n        if broadcast:\n            my_results = my_q.comm.comm.bcast(my_q.results, root=0)\n        else:\n            my_results = my_q.results\n        storage.update(my_results)\n\n    communication_system.pop()\n"
  },
  {
    "path": "yt/utilities/parameter_file_storage.py",
    "content": "import os.path\nfrom itertools import islice\n\nfrom yt.config import ytcfg\nfrom yt.funcs import mylog\nfrom yt.utilities.object_registries import output_type_registry\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    parallel_simple_proxy,\n)\n\n_field_names = (\"hash\", \"bn\", \"fp\", \"tt\", \"ctid\", \"class_name\", \"last_seen\")\n\n\nclass NoParameterShelf(Exception):\n    pass\n\n\nclass UnknownDatasetType(Exception):\n    def __init__(self, name):\n        self.name = name\n\n    def __str__(self):\n        return f\"{self.name}\"\n\n    def __repr__(self):\n        return f\"{self.name}\"\n\n\nclass ParameterFileStore:\n    \"\"\"\n    This class is designed to be a semi-persistent storage for parameter\n    files.  By identifying each dataset with a unique hash, objects\n    can be stored independently of datasets -- when an object is\n    loaded, the dataset is as well, based on the hash.  For\n    storage concerns, only a few hundred will be retained in cache.\n\n    \"\"\"\n\n    _shared_state = {}  # type: ignore\n    _distributed = True\n    _processing = False\n    _owner = 0\n    _register = True\n\n    def __new__(cls, *p, **k):\n        self = object.__new__(cls, *p, **k)\n        self.__dict__ = cls._shared_state\n        return self\n\n    def __init__(self, in_memory=False):\n        \"\"\"\n        Create the dataset database if yt is configured to store them.\n        Otherwise, use read-only settings.\n\n        \"\"\"\n        if not self._register:\n            return\n        if ytcfg.get(\"yt\", \"store_parameter_files\"):\n            self._read_only = False\n            self.init_db()\n            self._records = self.read_db()\n        else:\n            self._read_only = True\n            self._records = {}\n        self._register = False\n\n    @parallel_simple_proxy\n    def init_db(self):\n        \"\"\"\n        This function ensures that the storage database exists and can be used.\n        \"\"\"\n        dbn = self._get_db_name()\n        dbdir = os.path.dirname(dbn)\n        try:\n            if not os.path.isdir(dbdir):\n                os.mkdir(dbdir)\n        except OSError as exc:\n            raise NoParameterShelf from exc\n        open(dbn, \"ab\")  # make sure it exists, allow to close\n        # Now we read in all our records and return them\n        # these will be broadcast\n\n    def _get_db_name(self):\n        base_file_name = ytcfg.get(\"yt\", \"parameter_file_store\")\n        if not os.access(os.path.expanduser(\"~/\"), os.W_OK):\n            return os.path.abspath(base_file_name)\n        return os.path.expanduser(f\"~/.yt/{base_file_name}\")\n\n    def get_ds_hash(self, hash):\n        \"\"\"This returns a dataset based on a hash.\"\"\"\n        return self._convert_ds(self._records[hash])\n\n    def get_ds_ctid(self, ctid):\n        \"\"\"This returns a dataset based on a CurrentTimeIdentifier.\"\"\"\n        for h in self._records:\n            if self._records[h][\"ctid\"] == ctid:\n                return self._convert_ds(self._records[h])\n\n    def _adapt_ds(self, ds):\n        \"\"\"This turns a dataset into a CSV entry.\"\"\"\n        return {\n            \"bn\": ds.basename,\n            \"fp\": ds.directory,\n            \"tt\": ds.current_time,\n            \"ctid\": ds.unique_identifier,\n            \"class_name\": ds.__class__.__name__,\n            \"last_seen\": ds._instantiated,\n        }\n\n    def _convert_ds(self, ds_dict):\n        \"\"\"This turns a CSV entry into a dataset.\"\"\"\n        bn = ds_dict[\"bn\"]\n        fp = ds_dict[\"fp\"]\n        fn = os.path.join(fp, bn)\n        class_name = ds_dict[\"class_name\"]\n        if class_name not in output_type_registry:\n            raise UnknownDatasetType(class_name)\n        mylog.info(\"Checking %s\", fn)\n        if os.path.exists(fn):\n            ds = output_type_registry[class_name](os.path.join(fp, bn))\n        else:\n            raise OSError\n        # This next one is to ensure that we manually update the last_seen\n        # record *now*, for during write_out.\n        self._records[ds._hash()][\"last_seen\"] = ds._instantiated\n        return ds\n\n    def check_ds(self, ds):\n        \"\"\"\n        This will ensure that the dataset (*ds*) handed to it is\n        recorded in the storage unit.  In doing so, it will update path\n        and \"last_seen\" information.\n        \"\"\"\n        hash = ds._hash()\n        if hash not in self._records:\n            self.insert_ds(ds)\n            return\n        ds_dict = self._records[hash]\n        self._records[hash][\"last_seen\"] = ds._instantiated\n        if ds_dict[\"bn\"] != ds.basename or ds_dict[\"fp\"] != ds.directory:\n            self.wipe_hash(hash)\n            self.insert_ds(ds)\n\n    def insert_ds(self, ds):\n        \"\"\"This will insert a new *ds* and flush the database to disk.\"\"\"\n        self._records[ds._hash()] = self._adapt_ds(ds)\n        self.flush_db()\n\n    def wipe_hash(self, hash):\n        \"\"\"\n        This removes a *hash* corresponding to a dataset from the\n        storage.\n        \"\"\"\n        if hash not in self._records:\n            return\n        del self._records[hash]\n        self.flush_db()\n\n    def flush_db(self):\n        \"\"\"This flushes the storage to disk.\"\"\"\n        if self._read_only:\n            return\n        self._write_out()\n        self.read_db()\n\n    def get_recent(self, n=10):\n        recs = sorted(self._records.values(), key=lambda a: -a[\"last_seen\"])[:n]\n        return recs\n\n    @parallel_simple_proxy\n    def _write_out(self):\n        import csv\n\n        if self._read_only:\n            return\n        fn = self._get_db_name()\n        f = open(f\"{fn}.tmp\", \"w\")\n        w = csv.DictWriter(f, _field_names)\n        maxn = ytcfg.get(\"yt\", \"maximum_stored_datasets\")  # number written\n        for h, v in islice(\n            sorted(self._records.items(), key=lambda a: -a[1][\"last_seen\"]), 0, maxn\n        ):\n            v[\"hash\"] = h\n            w.writerow(v)\n        f.close()\n        os.rename(f\"{fn}.tmp\", fn)\n\n    @parallel_simple_proxy\n    def read_db(self):\n        \"\"\"This will read the storage device from disk.\"\"\"\n        import csv\n\n        f = open(self._get_db_name())\n        vals = csv.DictReader(f, _field_names)\n        db = {}\n        for v in vals:\n            db[v.pop(\"hash\")] = v\n            if v[\"last_seen\"] is None:\n                v[\"last_seen\"] = 0.0\n            else:\n                v[\"last_seen\"] = float(v[\"last_seen\"])\n        f.close()\n        return db\n"
  },
  {
    "path": "yt/utilities/particle_generator.py",
    "content": "import numpy as np\n\nfrom yt.funcs import get_pbar\nfrom yt.units._numpy_wrapper_functions import uconcatenate\nfrom yt.utilities.lib.particle_mesh_operations import CICSample_3\n\n\nclass ParticleGenerator:\n    def __init__(self, ds, num_particles, field_list, ptype=\"io\"):\n        \"\"\"\n        Base class for generating particle fields which may be applied to\n        streams. Normally this would not be called directly, since it doesn't\n        really do anything except allocate memory. Takes a *ds* to serve as the\n        basis for determining grids, the number of particles *num_particles*,\n        a list of fields, *field_list*, and the particle type *ptype*, which\n        has a default value of \"io\".\n        \"\"\"\n        self.ds = ds\n        self.num_particles = num_particles\n        self.field_list = [\n            (ptype, fd) if isinstance(fd, str) else fd for fd in field_list\n        ]\n        self.field_list.append((ptype, \"particle_index\"))\n        self.field_units = {\n            (ptype, f\"particle_position_{ax}\"): \"code_length\" for ax in \"xyz\"\n        }\n        self.field_units[ptype, \"particle_index\"] = \"\"\n        self.ptype = ptype\n\n        self._set_default_fields()\n\n        try:\n            self.posx_index = self.field_list.index(self.default_fields[0])\n            self.posy_index = self.field_list.index(self.default_fields[1])\n            self.posz_index = self.field_list.index(self.default_fields[2])\n        except Exception as e:\n            raise KeyError(\n                \"You must specify position fields: \"\n                + \" \".join(f\"particle_position_{ax}\" for ax in \"xyz\")\n            ) from e\n        self.index_index = self.field_list.index((ptype, \"particle_index\"))\n\n        self.num_grids = self.ds.index.num_grids\n        self.NumberOfParticles = np.zeros(self.num_grids, dtype=\"int64\")\n        self.ParticleGridIndices = np.zeros(self.num_grids + 1, dtype=\"int64\")\n\n        self.num_fields = len(self.field_list)\n\n        self.particles = np.zeros(\n            (self.num_particles, self.num_fields), dtype=\"float64\"\n        )\n\n    def _set_default_fields(self):\n        self.default_fields = [\n            (self.ptype, \"particle_position_x\"),\n            (self.ptype, \"particle_position_y\"),\n            (self.ptype, \"particle_position_z\"),\n        ]\n\n    def has_key(self, key):\n        \"\"\"\n        Check to see if *key* is in the particle field list.\n        \"\"\"\n        return key in self.field_list\n\n    def keys(self):\n        \"\"\"\n        Return the list of particle fields.\n        \"\"\"\n        return self.field_list\n\n    def __getitem__(self, key):\n        \"\"\"\n        Get the field associated with key.\n        \"\"\"\n        return self.particles[:, self.field_list.index(key)]\n\n    def __setitem__(self, key, val):\n        \"\"\"\n        Sets a field to be some other value. Note that we assume\n        that the particles have been sorted by grid already, so\n        make sure the setting of the field is consistent with this.\n        \"\"\"\n        self.particles[:, self.field_list.index(key)] = val[:]\n\n    def __len__(self):\n        \"\"\"\n        The number of particles\n        \"\"\"\n        return self.num_particles\n\n    def get_for_grid(self, grid):\n        \"\"\"\n        Return a dict containing all of the particle fields in the specified grid.\n        \"\"\"\n        ind = grid.id - grid._id_offset\n        start = self.ParticleGridIndices[ind]\n        end = self.ParticleGridIndices[ind + 1]\n        tr = {}\n        for field in self.field_list:\n            fi = self.field_list.index(field)\n            if field in self.field_units:\n                tr[field] = self.ds.arr(\n                    self.particles[start:end, fi], self.field_units[field]\n                )\n            else:\n                tr[field] = self.particles[start:end, fi]\n        return tr\n\n    def _setup_particles(self, x, y, z, setup_fields=None):\n        \"\"\"\n        Assigns grids to particles and sets up particle positions. *setup_fields* is\n        a dict of fields other than the particle positions to set up.\n        \"\"\"\n        particle_grids, particle_grid_inds = self.ds.index._find_points(x, y, z)\n        idxs = np.argsort(particle_grid_inds)\n        self.particles[:, self.posx_index] = x[idxs]\n        self.particles[:, self.posy_index] = y[idxs]\n        self.particles[:, self.posz_index] = z[idxs]\n        self.NumberOfParticles = np.bincount(\n            particle_grid_inds.astype(\"intp\"), minlength=self.num_grids\n        )\n        if self.num_grids > 1:\n            np.add.accumulate(\n                self.NumberOfParticles.squeeze(), out=self.ParticleGridIndices[1:]\n            )\n        else:\n            self.ParticleGridIndices[1] = self.NumberOfParticles.squeeze()\n        if setup_fields is not None:\n            for key, value in setup_fields.items():\n                field = (self.ptype, key) if isinstance(key, str) else key\n                if field not in self.default_fields:\n                    self.particles[:, self.field_list.index(field)] = value[idxs]\n\n    def assign_indices(self, function=None, **kwargs):\n        \"\"\"\n        Assign unique indices to the particles. The default is to just use\n        numpy.arange, but any function may be supplied with keyword arguments.\n        \"\"\"\n        if function is None:\n            self.particles[:, self.index_index] = np.arange(self.num_particles)\n        else:\n            self.particles[:, self.index_index] = function(**kwargs)\n\n    def map_grid_fields_to_particles(self, mapping_dict):\n        r\"\"\"\n        For the fields in  *mapping_dict*, map grid fields to the particles\n        using CIC sampling.\n\n        Examples\n        --------\n        >>> field_map = {\n        ...     \"density\": \"particle_density\",\n        ...     \"temperature\": \"particle_temperature\",\n        ... }\n        >>> particles.map_grid_fields_to_particles(field_map)\n        \"\"\"\n        pbar = get_pbar(\"Mapping fields to particles\", self.num_grids)\n        for i, grid in enumerate(self.ds.index.grids):\n            pbar.update(i + 1)\n            if self.NumberOfParticles[i] > 0:\n                start = self.ParticleGridIndices[i]\n                end = self.ParticleGridIndices[i + 1]\n                # Note we add one ghost zone to the grid!\n                cube = grid.retrieve_ghost_zones(1, list(mapping_dict.keys()))\n                le = np.array(grid.LeftEdge).astype(np.float64)\n                dims = np.array(grid.ActiveDimensions).astype(np.int32)\n                for gfield, pfield in mapping_dict.items():\n                    self.field_units[pfield] = cube[gfield].units\n                    field_index = self.field_list.index(pfield)\n                    CICSample_3(\n                        self.particles[start:end, self.posx_index],\n                        self.particles[start:end, self.posy_index],\n                        self.particles[start:end, self.posz_index],\n                        self.particles[start:end, field_index],\n                        np.int64(self.NumberOfParticles[i]),\n                        cube[gfield],\n                        le,\n                        dims,\n                        grid.dds[0],\n                    )\n        pbar.finish()\n\n    def apply_to_stream(self, overwrite=False, **kwargs):\n        \"\"\"\n        Apply the particles to a grid-based stream dataset. If particles\n        already exist, and overwrite=False, do not overwrite them, but add\n        the new ones to them.\n        \"\"\"\n        grid_data = []\n        for i, g in enumerate(self.ds.index.grids):\n            data = {}\n            number_of_particles = self.NumberOfParticles[i]\n            if not overwrite:\n                number_of_particles += g.NumberOfParticles\n            grid_particles = self.get_for_grid(g)\n            for field in self.field_list:\n                if number_of_particles > 0:\n                    if (\n                        g.NumberOfParticles > 0\n                        and not overwrite\n                        and field in self.ds.field_list\n                    ):\n                        # We have particles in this grid, we're not\n                        # overwriting them, and the field is in the field\n                        # list already\n                        data[field] = uconcatenate([g[field], grid_particles[field]])\n                    else:\n                        # Otherwise, simply add the field in\n                        data[field] = grid_particles[field]\n                else:\n                    # We don't have particles in this grid\n                    data[field] = np.array([], dtype=\"float64\")\n            grid_data.append(data)\n        self.ds.index.update_data(grid_data)\n\n\nclass FromListParticleGenerator(ParticleGenerator):\n    def __init__(self, ds, num_particles, data, ptype=\"io\"):\n        r\"\"\"\n        Generate particle fields from array-like lists contained in a dict.\n\n        Parameters\n        ----------\n        ds : `Dataset`\n            The dataset which will serve as the base for these particles.\n        num_particles : int\n            The number of particles in the dict.\n        data : dict of NumPy arrays\n            The particle fields themselves.\n        ptype : string, optional\n            The particle type for these particle fields. Default: \"io\"\n\n        Examples\n        --------\n        >>> num_p = 100000\n        >>> posx = np.random.random(num_p)\n        >>> posy = np.random.random(num_p)\n        >>> posz = np.random.random(num_p)\n        >>> mass = np.ones(num_p)\n        >>> data = {\n        ...     \"particle_position_x\": posx,\n        ...     \"particle_position_y\": posy,\n        ...     \"particle_position_z\": posz,\n        ...     \"particle_mass\": mass,\n        ... }\n        >>> particles = FromListParticleGenerator(ds, num_p, data)\n        \"\"\"\n\n        field_list = list(data.keys())\n        if \"particle_position_x\" in data:\n            x = data.pop(\"particle_position_x\")\n            y = data.pop(\"particle_position_y\")\n            z = data.pop(\"particle_position_z\")\n        elif (ptype, \"particle_position_x\") in data:\n            x = data.pop((ptype, \"particle_position_x\"))\n            y = data.pop((ptype, \"particle_position_y\"))\n            z = data.pop((ptype, \"particle_position_z\"))\n\n        xcond = np.logical_or(x < ds.domain_left_edge[0], x >= ds.domain_right_edge[0])\n        ycond = np.logical_or(y < ds.domain_left_edge[1], y >= ds.domain_right_edge[1])\n        zcond = np.logical_or(z < ds.domain_left_edge[2], z >= ds.domain_right_edge[2])\n        cond = np.logical_or(xcond, ycond)\n        cond = np.logical_or(zcond, cond)\n\n        if np.any(cond):\n            raise ValueError(\"Some particles are outside of the domain!!!\")\n\n        super().__init__(ds, num_particles, field_list, ptype=ptype)\n        self._setup_particles(x, y, z, setup_fields=data)\n\n\nclass LatticeParticleGenerator(ParticleGenerator):\n    def __init__(\n        self,\n        ds,\n        particles_dims,\n        particles_left_edge,\n        particles_right_edge,\n        field_list,\n        ptype=\"io\",\n    ):\n        r\"\"\"\n        Generate particles in a lattice arrangement.\n\n        Parameters\n        ----------\n        ds : `Dataset`\n            The dataset which will serve as the base for these particles.\n        particles_dims : int, array-like\n            The number of particles along each dimension\n        particles_left_edge : float, array-like\n            The 'left-most' starting positions of the lattice.\n        particles_right_edge : float, array-like\n             The 'right-most' ending positions of the lattice.\n        field_list : list of strings\n             A list of particle fields\n        ptype : string, optional\n            The particle type for these particle fields. Default: \"io\"\n\n        Examples\n        --------\n        >>> dims = (128, 128, 128)\n        >>> le = np.array([0.25, 0.25, 0.25])\n        >>> re = np.array([0.75, 0.75, 0.75])\n        >>> fields = [\n        ...     (\"all\", \"particle_position_x\"),\n        ...     (\"all\", \"particle_position_y\"),\n        ...     (\"all\", \"particle_position_z\"),\n        ...     (\"all\", \"particle_density\"),\n        ...     (\"all\", \"particle_temperature\"),\n        ... ]\n        >>> particles = LatticeParticleGenerator(ds, dims, le, re, fields)\n        \"\"\"\n\n        num_x = particles_dims[0]\n        num_y = particles_dims[1]\n        num_z = particles_dims[2]\n        xmin = particles_left_edge[0]\n        ymin = particles_left_edge[1]\n        zmin = particles_left_edge[2]\n        xmax = particles_right_edge[0]\n        ymax = particles_right_edge[1]\n        zmax = particles_right_edge[2]\n        DLE = ds.domain_left_edge.in_units(\"code_length\").d\n        DRE = ds.domain_right_edge.in_units(\"code_length\").d\n\n        xcond = (xmin < DLE[0]) or (xmax >= DRE[0])\n        ycond = (ymin < DLE[1]) or (ymax >= DRE[1])\n        zcond = (zmin < DLE[2]) or (zmax >= DRE[2])\n        cond = xcond or ycond or zcond\n\n        if cond:\n            raise ValueError(\"Proposed bounds for particles are outside domain!!!\")\n\n        super().__init__(ds, num_x * num_y * num_z, field_list, ptype=ptype)\n\n        dx = (xmax - xmin) / (num_x - 1)\n        dy = (ymax - ymin) / (num_y - 1)\n        dz = (zmax - zmin) / (num_z - 1)\n        inds = np.indices((num_x, num_y, num_z))\n        xpos = inds[0] * dx + xmin\n        ypos = inds[1] * dy + ymin\n        zpos = inds[2] * dz + zmin\n\n        self._setup_particles(xpos.flat[:], ypos.flat[:], zpos.flat[:])\n\n\nclass WithDensityParticleGenerator(ParticleGenerator):\n    def __init__(\n        self,\n        ds,\n        data_source,\n        num_particles,\n        field_list,\n        density_field=(\"gas\", \"density\"),\n        ptype=\"io\",\n    ):\n        r\"\"\"\n        Generate particles based on a density field.\n\n        Parameters\n        ----------\n        ds : `Dataset`\n            The dataset which will serve as the base for these particles.\n        data_source :\n            `yt.data_objects.selection_objects.base_objects.YTSelectionContainer`\n            The data source containing the density field.\n        num_particles : int\n            The number of particles to be generated\n        field_list : list of strings\n            A list of particle fields\n        density_field : tuple, optional\n            A density field which will serve as the distribution function for the\n            particle positions. Theoretically, this could be any 'per-volume' field.\n        ptype : string, optional\n            The particle type for these particle fields. Default: \"io\"\n\n        Examples\n        --------\n        >>> sphere = ds.sphere(ds.domain_center, 0.5)\n        >>> num_p = 100000\n        >>> fields = [\n        ...     (\"all\", \"particle_position_x\"),\n        ...     (\"all\", \"particle_position_y\"),\n        ...     (\"all\", \"particle_position_z\"),\n        ...     (\"all\", \"particle_density\"),\n        ...     (\"all\", \"particle_temperature\"),\n        ... ]\n        >>> particles = WithDensityParticleGenerator(\n        ...     ds, sphere, num_particles, fields, density_field=\"Dark_Matter_Density\"\n        ... )\n        \"\"\"\n\n        super().__init__(ds, num_particles, field_list, ptype=ptype)\n\n        num_cells = len(data_source[\"index\", \"x\"].flat)\n        max_mass = (\n            data_source[density_field] * data_source[\"gas\", \"cell_volume\"]\n        ).max()\n        num_particles_left = num_particles\n        all_x = []\n        all_y = []\n        all_z = []\n\n        pbar = get_pbar(\"Generating Particles\", num_particles)\n        tot_num_accepted = 0\n        rng = np.random.default_rng()\n\n        while num_particles_left > 0:\n            m = rng.uniform(high=1.01 * max_mass, size=num_particles_left)\n            idxs = rng.integers(low=0, high=num_cells, size=num_particles_left)\n            m_true = (\n                data_source[density_field] * data_source[\"gas\", \"cell_volume\"]\n            ).flat[idxs]\n            accept = m <= m_true\n            num_accepted = accept.sum()\n            accepted_idxs = idxs[accept]\n\n            xpos = (\n                data_source[\"index\", \"x\"].flat[accepted_idxs]\n                + rng.uniform(low=-0.5, high=0.5, size=num_accepted)\n                * data_source[\"index\", \"dx\"].flat[accepted_idxs]\n            )\n            ypos = (\n                data_source[\"index\", \"y\"].flat[accepted_idxs]\n                + rng.uniform(low=-0.5, high=0.5, size=num_accepted)\n                * data_source[\"index\", \"dy\"].flat[accepted_idxs]\n            )\n            zpos = (\n                data_source[\"index\", \"z\"].flat[accepted_idxs]\n                + rng.uniform(low=-0.5, high=0.5, size=num_accepted)\n                * data_source[\"index\", \"dz\"].flat[accepted_idxs]\n            )\n\n            all_x.append(xpos)\n            all_y.append(ypos)\n            all_z.append(zpos)\n\n            num_particles_left -= num_accepted\n            tot_num_accepted += num_accepted\n            pbar.update(tot_num_accepted)\n\n        pbar.finish()\n\n        x = uconcatenate(all_x)\n        y = uconcatenate(all_y)\n        z = uconcatenate(all_z)\n\n        self._setup_particles(x, y, z)\n"
  },
  {
    "path": "yt/utilities/performance_counters.py",
    "content": "import atexit\nimport time\nfrom bisect import insort\nfrom collections import defaultdict\nfrom datetime import datetime as dt\nfrom functools import wraps\n\nfrom yt.config import ytcfg\nfrom yt.funcs import mylog\n\n\nclass PerformanceCounters:\n    _shared_state = {}  # type: ignore\n\n    def __new__(cls, *args, **kwargs):\n        self = object.__new__(cls, *args, **kwargs)\n        self.__dict__ = cls._shared_state\n        return self\n\n    def __init__(self):\n        self.counters = defaultdict(lambda: 0.0)\n        self.counting = defaultdict(lambda: False)\n        self.starttime = defaultdict(lambda: 0)\n        self.endtime = defaultdict(lambda: 0)\n        self._on = ytcfg.get(\"yt\", \"time_functions\")\n        self.exit()\n\n    def __call__(self, name):\n        if not self._on:\n            return\n        if self.counting[name]:\n            self.counters[name] = time.time() - self.counters[name]\n            self.counting[name] = False\n            self.endtime[name] = dt.now()\n        else:\n            self.counters[name] = time.time()\n            self.counting[name] = True\n            self.starttime[name] = dt.now()\n\n    def call_func(self, func):\n        if not self._on:\n            return func\n\n        @wraps(func)\n        def func_wrapper(*args, **kwargs):\n            self(func.__name__)\n            func(*args, **kwargs)\n            self(func.__name__)\n\n        return func_wrapper\n\n    def print_stats(self):\n        mylog.info(\"Current counter status:\\n\")\n        times = []\n        for i in self.counters:\n            insort(times, [self.starttime[i], i, 1])  # 1 for 'on'\n            if not self.counting[i]:\n                insort(times, [self.endtime[i], i, 0])  # 0 for 'off'\n        shifts = {}\n        order = []\n        endtimes = {}\n        shift = 0\n        multi = 5\n        for i in times:\n            # a starting entry\n            if i[2] == 1:\n                shifts[i[1]] = shift\n                order.append(i[1])\n                shift += 1\n            if i[2] == 0:\n                shift -= 1\n                endtimes[i[1]] = self.counters[i[1]]\n        line_fragments: list[str] = []\n        for i in order:\n            line_fragments.append(\n                f\"{' ' * shifts[i] * multi}{shifts[i]} : {i} : \"\n                f\"{'still running' if self.counting[i] else f'{self.counters[i]:0.3e}'}\\n\"\n            )\n        mylog.info(\"\\n%s\", \"\".join(line_fragments))\n\n    def exit(self):\n        if self._on:\n            atexit.register(self.print_stats)\n\n\nyt_counters = PerformanceCounters()\ntime_function = yt_counters.call_func\n\n\nclass ProfilingController:\n    def __init__(self):\n        self.profilers = {}\n\n    def profile_function(self, function_name):\n        def wrapper(func):\n            try:\n                import cProfile\n            except ImportError:\n                return func\n            my_prof = cProfile.Profile()\n            self.profilers[function_name] = my_prof\n\n            @wraps(func)\n            def run_in_profiler(*args, **kwargs):\n                my_prof.enable()\n                func(*args, **kwargs)\n                my_prof.disable()\n\n            return run_in_profiler\n\n        return wrapper\n\n    def write_out(self, filename_prefix):\n        pfn = str(filename_prefix)\n        if ytcfg.get(\"yt\", \"internals\", \"parallel\"):\n            global_parallel_rank = ytcfg.get(\"yt\", \"internals\", \"global_parallel_rank\")\n            global_parallel_size = ytcfg.get(\"yt\", \"internals\", \"global_parallel_size\")\n            pfn += f\"_{global_parallel_rank:03}_{global_parallel_size:03}\"\n\n        for n, p in sorted(self.profilers.items()):\n            fn = f\"{pfn}_{n}.cprof\"\n            mylog.info(\"Dumping %s into %s\", n, fn)\n            p.dump_stats(fn)\n"
  },
  {
    "path": "yt/utilities/periodic_table.py",
    "content": "import numbers\n\nimport numpy as np\n\n_elements = (\n    (1, 1.0079400000, \"Hydrogen\", \"H\"),\n    (2, 4.0026020000, \"Helium\", \"He\"),\n    (3, 6.9410000000, \"Lithium\", \"Li\"),\n    (4, 9.0121820000, \"Beryllium\", \"Be\"),\n    (5, 10.8110000000, \"Boron\", \"B\"),\n    (6, 12.0107000000, \"Carbon\", \"C\"),\n    (7, 14.0067000000, \"Nitrogen\", \"N\"),\n    (8, 15.9994000000, \"Oxygen\", \"O\"),\n    (9, 18.9994000000, \"Fluorine\", \"F\"),\n    (10, 20.1797000000, \"Neon\", \"Ne\"),\n    (11, 22.9897692800, \"Sodium\", \"Na\"),\n    (12, 24.3050000000, \"Magnesium\", \"Mg\"),\n    (13, 26.9815386000, \"Aluminium\", \"Al\"),\n    (14, 28.0855000000, \"Silicon\", \"Si\"),\n    (15, 30.9737620000, \"Phosphorus\", \"P\"),\n    (16, 32.0650000000, \"Sulphur\", \"S\"),\n    (17, 35.4530000000, \"Chlorine\", \"Cl\"),\n    (18, 39.9480000000, \"Argon\", \"Ar\"),\n    (19, 39.0983000000, \"Potassium\", \"K\"),\n    (20, 40.0780000000, \"Calcium\", \"Ca\"),\n    (21, 44.9559120000, \"Scandium\", \"Sc\"),\n    (22, 47.8670000000, \"Titanium\", \"Ti\"),\n    (23, 50.9415000000, \"Vanadium\", \"V\"),\n    (24, 51.9961000000, \"Chromium\", \"Cr\"),\n    (25, 54.9380450000, \"Manganese\", \"Mn\"),\n    (26, 55.8450000000, \"Iron\", \"Fe\"),\n    (27, 58.9331950000, \"Cobalt\", \"Co\"),\n    (28, 58.6934000000, \"Nickel\", \"Ni\"),\n    (29, 63.5460000000, \"Copper\", \"Cu\"),\n    (30, 65.3800000000, \"Zinc\", \"Zn\"),\n    (31, 69.7230000000, \"Gallium\", \"Ga\"),\n    (32, 72.6400000000, \"Germanium\", \"Ge\"),\n    (33, 74.9216000000, \"Arsenic\", \"As\"),\n    (34, 78.9600000000, \"Selenium\", \"Se\"),\n    (35, 79.9040000000, \"Bromine\", \"Br\"),\n    (36, 83.7980000000, \"Krypton\", \"Kr\"),\n    (37, 85.4678000000, \"Rubidium\", \"Rb\"),\n    (38, 87.6200000000, \"Strontium\", \"Sr\"),\n    (39, 88.9058500000, \"Yttrium\", \"Y\"),\n    (40, 91.2240000000, \"Zirkonium\", \"Zr\"),\n    (41, 92.9063800000, \"Niobium\", \"Nb\"),\n    (42, 95.9600000000, \"Molybdaenum\", \"Mo\"),\n    (43, 98.0000000000, \"Technetium\", \"Tc\"),\n    (44, 101.0700000000, \"Ruthenium\", \"Ru\"),\n    (45, 102.9055000000, \"Rhodium\", \"Rh\"),\n    (46, 106.4200000000, \"Palladium\", \"Pd\"),\n    (47, 107.8682000000, \"Silver\", \"Ag\"),\n    (48, 112.4110000000, \"Cadmium\", \"Cd\"),\n    (49, 114.8180000000, \"Indium\", \"In\"),\n    (50, 118.7100000000, \"Tin\", \"Sn\"),\n    (51, 121.7600000000, \"Antimony\", \"Sb\"),\n    (52, 127.6000000000, \"Tellurium\", \"Te\"),\n    (53, 126.9044700000, \"Iodine\", \"I\"),\n    (54, 131.2930000000, \"Xenon\", \"Xe\"),\n    (55, 132.9054519000, \"Cesium\", \"Cs\"),\n    (56, 137.3270000000, \"Barium\", \"Ba\"),\n    (57, 138.9054700000, \"Lanthanum\", \"La\"),\n    (58, 140.1160000000, \"Cerium\", \"Ce\"),\n    (59, 140.9076500000, \"Praseodymium\", \"Pr\"),\n    (60, 144.2420000000, \"Neodymium\", \"Nd\"),\n    (61, 145.0000000000, \"Promethium\", \"Pm\"),\n    (62, 150.3600000000, \"Samarium\", \"Sm\"),\n    (63, 151.9640000000, \"Europium\", \"Eu\"),\n    (64, 157.2500000000, \"Gadolinium\", \"Gd\"),\n    (65, 158.9253500000, \"Terbium\", \"Tb\"),\n    (66, 162.5001000000, \"Dysprosium\", \"Dy\"),\n    (67, 164.9303200000, \"Holmium\", \"Ho\"),\n    (68, 167.2590000000, \"Erbium\", \"Er\"),\n    (69, 168.9342100000, \"Thulium\", \"Tm\"),\n    (70, 173.0540000000, \"Ytterbium\", \"Yb\"),\n    (71, 174.9668000000, \"Lutetium\", \"Lu\"),\n    (72, 178.4900000000, \"Hafnium\", \"Hf\"),\n    (73, 180.9478800000, \"Tantalum\", \"Ta\"),\n    (74, 183.8400000000, \"Tungsten\", \"W\"),\n    (75, 186.2070000000, \"Rhenium\", \"Re\"),\n    (76, 190.2300000000, \"Osmium\", \"Os\"),\n    (77, 192.2170000000, \"Iridium\", \"Ir\"),\n    (78, 192.0840000000, \"Platinum\", \"Pt\"),\n    (79, 196.9665690000, \"Gold\", \"Au\"),\n    (80, 200.5900000000, \"Hydrargyrum\", \"Hg\"),\n    (81, 204.3833000000, \"Thallium\", \"Tl\"),\n    (82, 207.2000000000, \"Lead\", \"Pb\"),\n    (83, 208.9804010000, \"Bismuth\", \"Bi\"),\n    (84, 210.0000000000, \"Polonium\", \"Po\"),\n    (85, 210.0000000000, \"Astatine\", \"At\"),\n    (86, 220.0000000000, \"Radon\", \"Rn\"),\n    (87, 223.0000000000, \"Francium\", \"Fr\"),\n    (88, 226.0000000000, \"Radium\", \"Ra\"),\n    (89, 227.0000000000, \"Actinium\", \"Ac\"),\n    (90, 232.0380600000, \"Thorium\", \"Th\"),\n    (91, 231.0358800000, \"Protactinium\", \"Pa\"),\n    (92, 238.0289100000, \"Uranium\", \"U\"),\n    (93, 237.0000000000, \"Neptunium\", \"Np\"),\n    (94, 244.0000000000, \"Plutonium\", \"Pu\"),\n    (95, 243.0000000000, \"Americium\", \"Am\"),\n    (96, 247.0000000000, \"Curium\", \"Cm\"),\n    (97, 247.0000000000, \"Berkelium\", \"Bk\"),\n    (98, 251.0000000000, \"Californium\", \"Cf\"),\n    (99, 252.0000000000, \"Einsteinium\", \"Es\"),\n    (100, 257.0000000000, \"Fermium\", \"Fm\"),\n    (101, 258.0000000000, \"Mendelevium\", \"Md\"),\n    (102, 259.0000000000, \"Nobelium\", \"No\"),\n    (103, 262.0000000000, \"Lawrencium\", \"Lr\"),\n    (104, 261.0000000000, \"Rutherfordium\", \"Rf\"),\n    (105, 262.0000000000, \"Dubnium\", \"Db\"),\n    (106, 266.0000000000, \"Seaborgium\", \"Sg\"),\n    (107, 264.0000000000, \"Bohrium\", \"Bh\"),\n    (108, 277.0000000000, \"Hassium\", \"Hs\"),\n    (109, 268.0000000000, \"Meitnerium\", \"Mt\"),\n    (110, 271.0000000000, \"Ununnilium\", \"Ds\"),\n    (111, 272.0000000000, \"Unununium\", \"Rg\"),\n    (112, 285.0000000000, \"Ununbium\", \"Uub\"),\n    (113, 284.0000000000, \"Ununtrium\", \"Uut\"),\n    (114, 289.0000000000, \"Ununquadium\", \"Uuq\"),\n    (115, 288.0000000000, \"Ununpentium\", \"Uup\"),\n    (116, 292.0000000000, \"Ununhexium\", \"Uuh\"),\n    (118, 294.0000000000, \"Ununoctium\", \"Uuo\"),\n    # Now some special cases that are *not* elements\n    (-1, 2.014102, \"Deuterium\", \"D\"),\n    (-1, 0.00054858, \"Electron\", \"El\"),\n)\n\n\nclass Element:\n    def __init__(self, num, weight, name, symbol):\n        self.num = num\n        self.weight = weight\n        self.name = name\n        self.symbol = symbol\n\n    def __repr__(self):\n        return f\"Element: {self.symbol} ({self.name})\"\n\n\nclass PeriodicTable:\n    def __init__(self):\n        self.elements_by_number = {}\n        self.elements_by_name = {}\n        self.elements_by_symbol = {}\n        for num, weight, name, symbol in _elements:\n            e = Element(num, weight, name, symbol)\n            self.elements_by_number[num] = e\n            self.elements_by_name[name] = e\n            self.elements_by_symbol[symbol] = e\n\n    def __getitem__(self, key):\n        if isinstance(key, (np.number, numbers.Number)):\n            d = self.elements_by_number\n        elif isinstance(key, str):\n            if len(key) <= 2:\n                d = self.elements_by_symbol\n            elif len(key) == 3 and key[0] == \"U\":\n                d = self.elements_by_symbol\n            else:\n                d = self.elements_by_name\n        else:\n            raise KeyError(key)\n        return d[key]\n\n\nperiodic_table = PeriodicTable()\n"
  },
  {
    "path": "yt/utilities/physical_constants.py",
    "content": "from math import pi\n\nfrom yt.units.yt_array import YTQuantity\nfrom yt.utilities.physical_ratios import (\n    amu_grams,\n    boltzmann_constant_erg_per_K,\n    mass_earth_grams,\n    mass_electron_grams,\n    mass_hydrogen_grams,\n    mass_jupiter_grams,\n    mass_mars_grams,\n    mass_mercury_grams,\n    mass_neptune_grams,\n    mass_saturn_grams,\n    mass_sun_grams,\n    mass_uranus_grams,\n    mass_venus_grams,\n    newton_cgs,\n    planck_cgs,\n    planck_charge_esu,\n    planck_energy_erg,\n    planck_length_cm,\n    planck_mass_grams,\n    planck_temperature_K,\n    planck_time_s,\n    speed_of_light_cm_per_s,\n    standard_gravity_cm_per_s2,\n)\n\nmass_electron_cgs = YTQuantity(mass_electron_grams, \"g\")\nmass_electron = mass_electron_cgs\nme = mass_electron_cgs\n\namu_cgs = YTQuantity(amu_grams, \"g\")\namu = amu_cgs\nNa = 1 / amu_cgs\n\nmass_hydrogen_cgs = YTQuantity(mass_hydrogen_grams, \"g\")\nmass_hydrogen = mass_hydrogen_cgs\nmp = mass_hydrogen_cgs\nmh = mp\n\n# Velocities\nspeed_of_light_cgs = YTQuantity(speed_of_light_cm_per_s, \"cm/s\")\nspeed_of_light = speed_of_light_cgs\nclight = speed_of_light_cgs\nc = speed_of_light_cgs\n\n# Cross Sections\n# 8*pi/3 (alpha*hbar*c/(2*pi))**2\ncross_section_thompson_cgs = YTQuantity(6.65245854533e-25, \"cm**2\")\ncross_section_thompson = cross_section_thompson_cgs\nthompson_cross_section = cross_section_thompson_cgs\nsigma_thompson = cross_section_thompson_cgs\n\n# Charge\ncharge_proton_cgs = YTQuantity(4.8032056e-10, \"esu\")\ncharge_proton = charge_proton_cgs\nproton_charge = charge_proton_cgs\nelementary_charge = charge_proton_cgs\nqp = charge_proton_cgs\n\n# Physical Constants\nboltzmann_constant_cgs = YTQuantity(boltzmann_constant_erg_per_K, \"erg/K\")\nboltzmann_constant = boltzmann_constant_cgs\nkboltz = boltzmann_constant_cgs\nkb = kboltz\n\ngravitational_constant_cgs = YTQuantity(newton_cgs, \"cm**3/g/s**2\")\ngravitational_constant = gravitational_constant_cgs\nG = gravitational_constant_cgs\n\nplanck_constant_cgs = YTQuantity(planck_cgs, \"erg*s\")\nplanck_constant = planck_constant_cgs\nhcgs = planck_constant_cgs\nhbar = 0.5 * hcgs / pi\n\nstefan_boltzmann_constant_cgs = YTQuantity(5.670373e-5, \"erg/cm**2/s**1/K**4\")\nstefan_boltzmann_constant = stefan_boltzmann_constant_cgs\n\nTcmb = YTQuantity(2.726, \"K\")  # Current CMB temperature\nCMB_temperature = Tcmb\n\n# Solar System\nmass_sun_cgs = YTQuantity(mass_sun_grams, \"g\")\nmass_sun = mass_sun_cgs\nsolar_mass = mass_sun_cgs\nmsun = mass_sun_cgs\n# Standish, E.M. (1995) \"Report of the IAU WGAS Sub-Group on Numerical Standards\",\n# in Highlights of Astronomy (I. Appenzeller, ed.), Table 1,\n# Kluwer Academic Publishers, Dordrecht.\n# REMARK: following masses include whole systems (planet + moons)\nmass_jupiter_cgs = YTQuantity(mass_jupiter_grams, \"g\")\nmass_jupiter = mass_jupiter_cgs\njupiter_mas = mass_jupiter_cgs\n\nmass_mercury_cgs = YTQuantity(mass_mercury_grams, \"g\")\nmass_mercury = mass_mercury_cgs\nmercury_mass = mass_mercury_cgs\n\nmass_venus_cgs = YTQuantity(mass_venus_grams, \"g\")\nmass_venus = mass_venus_cgs\nvenus_mass = mass_venus_cgs\n\nmass_earth_cgs = YTQuantity(mass_earth_grams, \"g\")\nmass_earth = mass_earth_cgs\nearth_mass = mass_earth_cgs\nmearth = mass_earth_cgs\n\nmass_mars_cgs = YTQuantity(mass_mars_grams, \"g\")\nmass_mars = mass_mars_cgs\nmars_mass = mass_mars_cgs\n\nmass_saturn_cgs = YTQuantity(mass_saturn_grams, \"g\")\nmass_saturn = mass_saturn_cgs\nsaturn_mass = mass_saturn_cgs\n\nmass_uranus_cgs = YTQuantity(mass_uranus_grams, \"g\")\nmass_uranus = mass_uranus_cgs\nuranus_mass = mass_uranus_cgs\n\nmass_neptune_cgs = YTQuantity(mass_neptune_grams, \"g\")\nmass_neptune = mass_neptune_cgs\nneptune_mass = mass_neptune_cgs\n\n# Planck units\nm_pl = planck_mass = YTQuantity(planck_mass_grams, \"g\")\nl_pl = planck_length = YTQuantity(planck_length_cm, \"cm\")\nt_pl = planck_time = YTQuantity(planck_time_s, \"s\")\nE_pl = planck_energy = YTQuantity(planck_energy_erg, \"erg\")\nq_pl = planck_charge = YTQuantity(planck_charge_esu, \"esu\")\nT_pl = planck_temperature = YTQuantity(planck_temperature_K, \"K\")\n\n# MKS E&M units\nmu_0 = YTQuantity(4.0e-7 * pi, \"N/A**2\")\neps_0 = (1.0 / (clight**2 * mu_0)).in_units(\"C**2/N/m**2\")\n\n# Misc\nstandard_gravity_cgs = YTQuantity(standard_gravity_cm_per_s2, \"cm/s**2\")\nstandard_gravity = standard_gravity_cgs\n"
  },
  {
    "path": "yt/utilities/physical_ratios.py",
    "content": "import numpy as np\n\n#\n# Physical Constants and Units Conversion Factors\n#\n# Values for these constants, unless otherwise noted, are drawn from IAU,\n# IUPAC, NIST, and NASA data, whichever is newer.\n# http://maia.usno.navy.mil/NSFA/IAU2009_consts.html\n# http://goldbook.iupac.org/list_goldbook_phys_constants_defs.html\n# http://physics.nist.gov/cuu/Constants/index.html\n# http://nssdc.gsfc.nasa.gov/planetary/factsheet/jupiterfact.html\n\n# Elementary masses\nmass_electron_grams = 9.10938291e-28\namu_grams = 1.660538921e-24\nmass_hydrogen_grams = 1.007947 * amu_grams\n\n# Solar values (see Mamajek 2012)\n# https://sites.google.com/site/mamajeksstarnotes/bc-scale\nmass_sun_grams = 1.98841586e33\ntemp_sun_kelvin = 5870.0\nluminosity_sun_ergs_per_sec = 3.8270e33\n\n# Consistent with solar abundances used in Cloudy\nmetallicity_sun = 0.01295\n\n# Conversion Factors:  X au * mpc_per_au = Y mpc\n# length\nmpc_per_mpc = 1e0\nmpc_per_kpc = 1e-3\nmpc_per_pc = 1e-6\nmpc_per_au = 4.84813682e-12\nmpc_per_rsun = 2.253962e-14\nmpc_per_rearth = 2.06470307893e-16\nmpc_per_rjup = 2.26566120943e-15\nmpc_per_miles = 5.21552871e-20\nmpc_per_km = 3.24077929e-20\nmpc_per_cm = 3.24077929e-25\nkpc_per_cm = mpc_per_cm / mpc_per_kpc\npc_per_cm = mpc_per_cm / mpc_per_pc\nkm_per_pc = 3.08567758e13\nkm_per_m = 1e-3\nkm_per_cm = 1e-5\nm_per_cm = 1e-2\nly_per_cm = 1.05702341e-18\nrsun_per_cm = 1.4378145e-11\nrearth_per_cm = 1.56961033e-9  # Mean (volumetric) radius\nrjup_per_cm = 1.43039006737e-10  # Mean (volumetric) radius\nau_per_cm = 6.68458712e-14\nang_per_cm = 1.0e8\n\nm_per_fpc = 0.0324077929\n\nkpc_per_mpc = 1.0 / mpc_per_kpc\npc_per_mpc = 1.0 / mpc_per_pc\nau_per_mpc = 1.0 / mpc_per_au\nrsun_per_mpc = 1.0 / mpc_per_rsun\nrearth_per_mpc = 1.0 / mpc_per_rearth\nrjup_per_mpc = 1.0 / mpc_per_rjup\nmiles_per_mpc = 1.0 / mpc_per_miles\nkm_per_mpc = 1.0 / mpc_per_km\ncm_per_mpc = 1.0 / mpc_per_cm\ncm_per_kpc = 1.0 / kpc_per_cm\ncm_per_km = 1.0 / km_per_cm\ncm_per_m = 1.0 / m_per_cm\npc_per_km = 1.0 / km_per_pc\ncm_per_pc = 1.0 / pc_per_cm\ncm_per_ly = 1.0 / ly_per_cm\ncm_per_rsun = 1.0 / rsun_per_cm\ncm_per_rearth = 1.0 / rearth_per_cm\ncm_per_rjup = 1.0 / rjup_per_cm\ncm_per_au = 1.0 / au_per_cm\ncm_per_ang = 1.0 / ang_per_cm\n\n# time\n# \"IAU Style Manual\" by G.A. Wilkins, Comm. 5, in IAU Transactions XXB (1989)\nsec_per_Gyr = 31.5576e15\nsec_per_Myr = 31.5576e12\nsec_per_kyr = 31.5576e9\nsec_per_year = 31.5576e6\nsec_per_day = 86400.0\nsec_per_hr = 3600.0\nsec_per_min = 60.0\nday_per_year = 365.25\n\n# velocities, accelerations\nspeed_of_light_cm_per_s = 2.99792458e10\nstandard_gravity_cm_per_s2 = 9.80665e2\n\n# some constants\nnewton_cgs = 6.67384e-8\nplanck_cgs = 6.62606957e-27\n\n# temperature / energy\nboltzmann_constant_erg_per_K = 1.3806488e-16\nerg_per_eV = 1.602176562e-12\nerg_per_keV = erg_per_eV * 1.0e3\nK_per_keV = erg_per_keV / boltzmann_constant_erg_per_K\nkeV_per_K = 1.0 / K_per_keV\nkeV_per_erg = 1.0 / erg_per_keV\neV_per_erg = 1.0 / erg_per_eV\nkelvin_per_rankine = 5.0 / 9.0\n\n# Solar System masses\n# Standish, E.M. (1995) \"Report of the IAU WGAS Sub-Group on Numerical Standards\",\n# in Highlights of Astronomy (I. Appenzeller, ed.), Table 1,\n# Kluwer Academic Publishers, Dordrecht.\n# REMARK: following masses include whole systems (planet + moons)\nmass_jupiter_grams = mass_sun_grams / 1047.3486\nmass_mercury_grams = mass_sun_grams / 6023600.0\nmass_venus_grams = mass_sun_grams / 408523.71\nmass_earth_grams = mass_sun_grams / 328900.56\nmass_mars_grams = mass_sun_grams / 3098708.0\nmass_saturn_grams = mass_sun_grams / 3497.898\nmass_uranus_grams = mass_sun_grams / 22902.98\nmass_neptune_grams = mass_sun_grams / 19412.24\n\n# flux\njansky_cgs = 1.0e-23\n# Cosmological constants\n# Calculated with H = 100 km/s/Mpc, value given in units of h^2 g cm^-3\n# Multiply by h^2 to get the critical density in units of g cm^-3\nrho_crit_g_cm3_h2 = 1.8784710838431654e-29\nprimordial_H_mass_fraction = 0.76\n\n_primordial_mass_fraction = {\n    \"H\": primordial_H_mass_fraction,\n    \"He\": (1 - primordial_H_mass_fraction),\n}\n\n# Misc. Approximations\nmass_mean_atomic_cosmology = 1.22\nmass_mean_atomic_galactic = 2.3\n\n# Miscellaneous\nHUGE = 1.0e90\nTINY = 1.0e-40\n\n# Planck units\nhbar_cgs = 0.5 * planck_cgs / np.pi\nplanck_mass_grams = np.sqrt(hbar_cgs * speed_of_light_cm_per_s / newton_cgs)\nplanck_length_cm = np.sqrt(hbar_cgs * newton_cgs / speed_of_light_cm_per_s**3)\nplanck_time_s = planck_length_cm / speed_of_light_cm_per_s\nplanck_energy_erg = (\n    planck_mass_grams * speed_of_light_cm_per_s * speed_of_light_cm_per_s\n)\nplanck_temperature_K = planck_energy_erg / boltzmann_constant_erg_per_K\nplanck_charge_esu = np.sqrt(hbar_cgs * speed_of_light_cm_per_s)\n\n# Imperial and other non-metric units\ngrams_per_pound = 453.59237\npascal_per_atm = 101325.0\n"
  },
  {
    "path": "yt/utilities/png_writer.py",
    "content": "from io import BytesIO\n\nimport PIL\nfrom PIL import Image\nfrom PIL.PngImagePlugin import PngInfo\n\nfrom .._version import __version__ as yt_version\n\n\ndef call_png_write_png(buffer, fileobj, dpi):\n    metadata = PngInfo()\n    metadata.add_text(\"Software\", f\"PIL-{PIL.__version__}|yt-{yt_version}\")\n    Image.fromarray(buffer).save(\n        fileobj, dpi=(dpi, dpi), format=\"png\", pnginfo=metadata\n    )\n\n\ndef write_png(buffer, filename, dpi=100):\n    with open(filename, \"wb\") as fileobj:\n        call_png_write_png(buffer, fileobj, dpi)\n\n\ndef write_png_to_string(buffer, dpi=100):\n    fileobj = BytesIO()\n    call_png_write_png(buffer, fileobj, dpi)\n    png_str = fileobj.getvalue()\n    fileobj.close()\n    return png_str\n"
  },
  {
    "path": "yt/utilities/rpdb.py",
    "content": "import cmd\nimport signal\nimport sys\nimport traceback\nfrom io import StringIO\nfrom xmlrpc.client import ServerProxy\nfrom xmlrpc.server import SimpleXMLRPCServer\n\nfrom yt.config import ytcfg\n\n\nclass PdbXMLRPCServer(SimpleXMLRPCServer):\n    \"\"\"\n    shutdown-enabled XMLRPCServer from\n      http://code.activestate.com/recipes/114579/\n    \"\"\"\n\n    finished = False\n\n    def register_signal(self, signum):\n        signal.signal(signum, self.signal_handler)\n\n    def signal_handler(self, signum, frame):\n        print(\"Caught signal\", signum)\n        self.shutdown()\n\n    def shutdown(self):\n        self.finished = True\n        return 1\n\n    def serve_forever(self):\n        while not self.finished:\n            self.handle_request()\n        print(\"DONE SERVING\")\n\n\ndef rpdb_excepthook(exc_type, exc, tb):\n    traceback.print_exception(exc_type, exc, tb)\n    task = ytcfg.get(\"yt\", \"internals\", \"global_parallel_rank\")\n    size = ytcfg.get(\"yt\", \"internals\", \"global_parallel_size\")\n    print(f\"Starting RPDB server on task {task} ; connect with 'yt rpdb -t {task}'\")\n    handler = pdb_handler(tb)\n    server = PdbXMLRPCServer((\"localhost\", 8010 + task))\n    server.register_introspection_functions()\n    server.register_instance(handler)\n    server.register_function(server.shutdown)\n    server.serve_forever()\n    server.server_close()\n    if size > 1:\n        from mpi4py import MPI\n\n        # This COMM_WORLD is okay.  We want to barrierize here, while waiting\n        # for shutdown from the rest of the parallel group.  If you are running\n        # with --rpdb it is assumed you know what you are doing and you won't\n        # let this get out of hand.\n        MPI.COMM_WORLD.Barrier()\n\n\nclass pdb_handler:\n    def __init__(self, tb):\n        import pdb\n\n        self.cin = StringIO()\n        sys.stdin = self.cin\n        self.cout = StringIO()\n        sys.stdout = self.cout\n        sys.stderr = self.cout\n        self.debugger = pdb.Pdb(stdin=self.cin, stdout=self.cout)\n        self.debugger.reset()\n        self.debugger.setup(tb.tb_frame, tb)\n\n    def execute(self, line):\n        tt = self.cout.tell()\n        self.debugger.onecmd(line)\n        self.cout.seek(tt)\n        return self.cout.read()\n\n\nclass rpdb_cmd(cmd.Cmd):\n    def __init__(self, proxy):\n        self.proxy = proxy\n        cmd.Cmd.__init__(self)\n        print(self.proxy.execute(\"bt\"))\n\n    def default(self, line):\n        print(self.proxy.execute(line))\n\n    def do_shutdown(self, args):\n        print(self.proxy.shutdown())\n        return True\n\n    def do_help(self, line):\n        print(self.proxy.execute(f\"help {line}\"))\n\n    def postcmd(self, stop, line):\n        return stop\n\n    def postloop(self):\n        try:\n            self.proxy.shutdown()\n        except Exception:\n            pass\n\n\n__header = \"\"\"\nYou're in a remote PDB session with task %(task)s\n\nYou can run PDB commands, and when you're done, type 'shutdown' to quit.\n\"\"\"\n\n\ndef run_rpdb(task=None):\n    port = 8010\n    if task is None:\n        try:\n            task + int(sys.argv[-1])\n        except Exception:\n            pass\n    port += task\n    sp = ServerProxy(f\"http://localhost:{port}/\")\n    try:\n        pp = rpdb_cmd(sp)\n    except OSError:\n        print(\"Connection refused.  Is the server running?\")\n        sys.exit(1)\n    pp.cmdloop(__header % {\"task\": port - 8010})\n"
  },
  {
    "path": "yt/utilities/sdf.py",
    "content": "import os\nfrom collections import UserDict\nfrom io import StringIO\n\nimport numpy as np\n\nfrom yt.funcs import mylog\n\n\ndef get_thingking_deps():\n    try:\n        from thingking.arbitrary_page import PageCacheURL\n        from thingking.httpmmap import HTTPArray\n    except ImportError:\n        raise ImportError(\n            \"This functionality requires the thingking package to be installed\"\n        ) from None\n    return HTTPArray, PageCacheURL\n\n\n_types = {\n    \"int16_t\": \"int16\",\n    \"uint16_t\": \"uint16\",\n    \"int\": \"int32\",\n    \"int32_t\": \"int32\",\n    \"uint32_t\": \"uint32\",\n    \"int64_t\": \"int64\",\n    \"uint64_t\": \"uint64\",\n    \"float\": \"float32\",\n    \"double\": \"float64\",\n    \"unsigned int\": \"I\",\n    \"unsigned char\": \"B\",\n    \"char\": \"B\",\n}\n\n_rev_types = {}\nfor v, t in _types.items():\n    _rev_types[t] = v\n_rev_types[\"<f8\"] = \"double\"\n_rev_types[\"<f4\"] = \"float\"\n_rev_types[\"<i4\"] = \"int32_t\"\n_rev_types[\"<i8\"] = \"int64_t\"\n_rev_types[\"<u4\"] = \"uint32_t\"\n_rev_types[\"|u1\"] = \"char\"\n\n\ndef _get_type(vtype, tlen=None):\n    try:\n        t = _types[vtype]\n        if tlen is not None:\n            t = np.dtype((t, tlen))\n        else:\n            t = np.dtype(t)\n    except KeyError:\n        t = eval(\"np.\" + vtype)\n    return t\n\n\ndef _lstrip(text_list):\n    return [t.strip() for t in text_list]\n\n\ndef _get_struct_vars(line):\n    spl = _lstrip(line.split(\";\"))\n    multiv = _lstrip(spl[0].split(\",\"))\n    ret = _lstrip(multiv[0].split())\n    ctype = ret[0]\n    vnames = [ret[-1]] + multiv[1:]\n    vnames = [v.strip() for v in vnames]\n    for vtype in ret[1:-1]:\n        ctype += \" \" + vtype\n    num = None\n    if len(vnames) == 1:\n        if \"[\" in vnames[0]:\n            num = int(vnames[0].split(\"[\")[-1].strip(\"]\"))\n            # num = int(re.sub(\"\\D\", \"\", vnames[0]))\n    ctype = _get_type(ctype, tlen=num)\n    return ctype, vnames\n\n\ndef bbox_filter(left, right, domain_width):\n    def myfilter(chunk, mask=None):\n        pos = np.array([chunk[\"x\"], chunk[\"y\"], chunk[\"z\"]]).T\n\n        # This hurts, but is useful for periodicity. Probably should check\n        # first if it is even needed for a given left/right\n        for i in range(3):\n            pos[:, i] = np.mod(pos[:, i] - left[i], domain_width[i]) + left[i]\n\n        # Now get all particles that are within the bbox\n        if mask is None:\n            mask = np.all(pos >= left, axis=1)\n            np.logical_and(mask, np.all(pos < right, axis=1), mask)\n        else:\n            np.logical_and(mask, np.all(pos >= left, axis=1), mask)\n            np.logical_and(mask, np.all(pos < right, axis=1), mask)\n        return mask\n\n    return myfilter\n\n\ndef sphere_filter(center, radius, domain_width):\n    def myfilter(chunk, mask=None):\n        pos = np.array([chunk[\"x\"], chunk[\"y\"], chunk[\"z\"]]).T\n        left = center - radius\n\n        # This hurts, but is useful for periodicity. Probably should check\n        # first if it is even needed for a given left/right\n        for i in range(3):\n            pos[:, i] = np.mod(pos[:, i] - left[i], domain_width[i]) + left[i]\n\n        # Now get all particles that are within the radius\n        if mask is None:\n            mask = ((pos - center) ** 2).sum(axis=1) ** 0.5 < radius\n        else:\n            np.multiply(mask, np.linalg.norm(pos - center, 2) < radius, mask)\n        return mask\n\n    return myfilter\n\n\ndef _ensure_xyz_fields(fields):\n    for f in \"xyz\":\n        if f not in fields:\n            fields.append(f)\n\n\ndef spread_bitsv(ival, level):\n    res = np.zeros_like(ival, dtype=\"int64\")\n    for i in range(level):\n        ares = np.bitwise_and(ival, 1 << i) << (i * 2)\n        np.bitwise_or(res, ares, res)\n    return res\n\n\ndef get_keyv(iarr, level):\n    i1, i2, i3 = (v.astype(\"int64\") for v in iarr)\n    i1 = spread_bitsv(i1, level)\n    i2 = spread_bitsv(i2, level) << 1\n    i3 = spread_bitsv(i3, level) << 2\n    np.bitwise_or(i1, i2, i1)\n    np.bitwise_or(i1, i3, i1)\n    return i1\n\n\nclass DataStruct:\n    \"\"\"docstring for DataStruct\"\"\"\n\n    _offset = 0\n\n    def __init__(self, dtypes, num, filename):\n        self.filename = filename\n        self.dtype = np.dtype(dtypes)\n        self.size = num\n        self.itemsize = self.dtype.itemsize\n        self.data = {}\n        self.handle = None\n\n    def set_offset(self, offset):\n        self._offset = offset\n        if self.size == -1:\n            file_size = os.path.getsize(self.filename)\n            file_size -= offset\n            self.size = float(file_size) / self.itemsize\n            assert int(self.size) == self.size\n\n    def build_memmap(self):\n        assert self.size != -1\n        self.handle = np.memmap(\n            self.filename,\n            dtype=self.dtype,\n            mode=\"r\",\n            shape=self.size,\n            offset=self._offset,\n        )\n        for k in self.dtype.names:\n            self.data[k] = self.handle[k]\n\n    def __del__(self):\n        if self.handle is not None:\n            try:\n                self.handle.close()\n            except AttributeError:\n                pass\n            del self.handle\n            self.handle = None\n\n    def __getitem__(self, key):\n        mask = None\n        if isinstance(key, (int, np.integer)):\n            if key == -1:\n                key = slice(-1, None)\n            else:\n                key = slice(key, key + 1)\n        elif isinstance(key, np.ndarray):\n            mask = key\n            key = slice(None, None)\n        if not isinstance(key, slice):\n            raise NotImplementedError\n        if key.start is None:\n            key = slice(0, key.stop)\n        if key.stop is None:\n            key = slice(key.start, self.shape)\n        if key.start < 0:\n            key = slice(self.size + key.start, key.stop)\n        if key.stop < 0:\n            key = slice(key.start, self.size + key.stop)\n        arr = self.handle[key.start : key.stop]\n        if mask is None:\n            return arr\n        else:\n            return arr[mask]\n\n\nclass RedirectArray:\n    \"\"\"docstring for RedirectArray\"\"\"\n\n    def __init__(self, http_array, key):\n        self.http_array = http_array\n        self.key = key\n        self.size = http_array.shape\n        self.dtype = http_array.dtype[key]\n\n    def __getitem__(self, sl):\n        if isinstance(sl, int):\n            return self.http_array[sl][self.key][0]\n        return self.http_array[sl][self.key]\n\n\nclass HTTPDataStruct(DataStruct):\n    \"\"\"docstring for HTTPDataStruct\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        HTTPArray, PageCacheURL = get_thingking_deps()\n        self.HTTPArray = HTTPArray\n        self.pcu = PageCacheURL(self.filename)\n\n    def set_offset(self, offset):\n        self._offset = offset\n        if self.size == -1:\n            # Read small piece:\n            file_size = self.pcu.total_size\n            file_size -= offset\n            self.size = float(file_size) / self.itemsize\n            assert int(self.size) == self.size\n\n    def build_memmap(self):\n        assert self.size != -1\n        mylog.info(\n            \"Building memmap with offset: %i and size %i\", self._offset, self.size\n        )\n        self.handle = self.HTTPArray(\n            self.filename, dtype=self.dtype, shape=self.size, offset=self._offset\n        )\n        for k in self.dtype.names:\n            self.data[k] = RedirectArray(self.handle, k)\n\n\nclass SDFRead(UserDict):\n    _eof = \"SDF-EO\"\n    _data_struct = DataStruct\n\n    def __init__(self, filename=None, header=None):\n        r\"\"\"Read an SDF file, loading parameters and variables.\n\n        Given an SDF file (see https://bitbucket.org/JohnSalmon/sdf), parse the\n        ASCII header and construct numpy memmap array\n        access.\n\n        Parameters\n        ----------\n        filename: string\n        The filename associated with the data to be loaded.\n        header: string, optional\n        If separate from the data file, a file containing the\n        header can be specified. Default: None.\n\n        Returns\n        -------\n        self : SDFRead object\n        Dict-like container of parameters and data.\n\n\n        References\n        ----------\n        SDF is described here:\n\n            J. K. Salmon and M. S. Warren. Self-Describing File (SDF) Library.\n            Zenodo, Jun 2014. URL https://bitbucket.org/JohnSalmon/sdf.\n\n        Examples\n        --------\n\n        >>> sdf = SDFRead(\"data.sdf\", header=\"data.hdr\")\n        >>> print(sdf.parameters)\n        >>> print(sdf[\"x\"])\n\n        \"\"\"\n        super().__init__()\n        self.filename = filename\n        if header is None:\n            header = filename\n        self.header = header\n        self.parameters = {}\n        self.structs = []\n        self.comments = []\n        if filename is not None:\n            self.parse_header()\n            self.set_offsets()\n            self.load_memmaps()\n\n    def write(self, filename):\n        f = open(filename, \"w\")\n        f.write(\"# SDF 1.0\\n\")\n        f.write(f\"parameter byteorder = {self.parameters['byteorder']};\\n\")\n        for c in self.comments:\n            if \"\\x0c\" in c:\n                continue\n            if \"SDF 1.0\" in c:\n                continue\n            f.write(f\"{c}\")\n        for k, v in sorted(self.parameters.items()):\n            if k == \"byteorder\":\n                continue\n            try:\n                t = _rev_types[v.dtype.name]\n            except Exception:\n                t = type(v).__name__\n            if t == str.__name__:\n                f.write(f'parameter {k} = \"{v}\";\\n')\n            else:\n                f.write(f\"{t} {k} = {v};\\n\")\n\n        struct_order = []\n        for s in self.structs:\n            f.write(\"struct {\\n\")\n            to_write = []\n            for var in s.dtype.descr:\n                k, v = var[0], _rev_types[var[1]]\n                to_write.append(k)\n                f.write(f\"\\t{v} {k};\\n\")\n            f.write(f\"}}[{s.size}];\\n\")\n            struct_order.append(to_write)\n        f.write(\"#\\x0c\\n\")\n        f.write(\"# SDF-EOH\\n\")\n        return struct_order, f\n\n    def __repr__(self):\n        disp = f\"<SDFRead Object> file: {self.filename}\\n\"\n        disp += \"parameters: \\n\"\n        for k, v in self.parameters.items():\n            disp += f\"\\t{k}: {v}\\n\"\n        disp += \"arrays: \\n\"\n        for k, v in self.items():\n            disp += f\"\\t{k}[{v.size}]\\n\"\n        return disp\n\n    def parse_header(self):\n        \"\"\"docstring for parse_header\"\"\"\n        # Pre-process\n        ascfile = open(self.header)\n        while True:\n            l = ascfile.readline()\n            if self._eof in l:\n                break\n\n            self.parse_line(l, ascfile)\n\n        hoff = ascfile.tell()\n        ascfile.close()\n        if self.header != self.filename:\n            hoff = 0\n        self.parameters[\"header_offset\"] = hoff\n\n    def parse_line(self, line, ascfile):\n        \"\"\"Parse a line of sdf\"\"\"\n\n        if \"struct\" in line:\n            self.parse_struct(line, ascfile)\n            return\n\n        if \"#\" in line:\n            self.comments.append(line)\n            return\n\n        spl = _lstrip(line.split(\"=\"))\n        vtype, vname = _lstrip(spl[0].split())\n        vname = vname.strip(\"[]\")\n        vval = spl[-1].strip(\";\")\n        if vtype == \"parameter\":\n            self.parameters[vname] = vval\n            return\n        elif vtype == \"char\":\n            vtype = \"str\"\n\n        try:\n            vval = eval(\"np.\" + vtype + f\"({vval})\")\n        except AttributeError:\n            if vtype not in _types:\n                mylog.warning(\"Skipping parameter %s\", vname)\n                return\n            vval = eval(\"np.\" + _types[vtype] + f\"({vval})\")\n\n        self.parameters[vname] = vval\n\n    def parse_struct(self, line, ascfile):\n        assert \"struct\" in line\n\n        str_types = []\n        l = ascfile.readline()\n        while \"}\" not in l:\n            vtype, vnames = _get_struct_vars(l)\n            for v in vnames:\n                str_types.append((v, vtype))\n            l = ascfile.readline()\n        spec_chars = r\"{}[]\\;\\n\\\\\"\n        num = l.strip(spec_chars)\n        if len(num) == 0:\n            # We need to compute the number of records.  The DataStruct will\n            # handle this.\n            num = \"-1\"\n        num = int(num)\n        struct = self._data_struct(str_types, num, self.filename)\n        self.structs.append(struct)\n        return\n\n    def set_offsets(self):\n        running_off = self.parameters[\"header_offset\"]\n        for struct in self.structs:\n            struct.set_offset(running_off)\n            running_off += struct.size * struct.itemsize\n        return\n\n    def load_memmaps(self):\n        for struct in self.structs:\n            struct.build_memmap()\n            self.update(struct.data)\n\n\nclass HTTPSDFRead(SDFRead):\n    r\"\"\"Read an SDF file hosted on the internet.\n\n    Given an SDF file (see https://bitbucket.org/JohnSalmon/sdf), parse the\n    ASCII header and construct numpy memmap array\n    access.\n\n    Parameters\n    ----------\n    filename : string\n        The filename associated with the data to be loaded.\n    header : string, optional\n        If separate from the data file, a file containing the\n        header can be specified. Default: None.\n\n    Returns\n    -------\n    self : SDFRead object\n        Dict-like container of parameters and data.\n\n    References\n    ----------\n    SDF is described here:\n\n        J. K. Salmon and M. S. Warren. Self-Describing File (SDF) Library.\n        Zenodo, Jun 2014. URL https://bitbucket.org/JohnSalmon/sdf.\n\n    Examples\n    --------\n\n    >>> sdf = SDFRead(\"data.sdf\", header=\"data.hdr\")\n    >>> print(sdf.parameters)\n    >>> print(sdf[\"x\"])\n\n    \"\"\"\n\n    _data_struct = HTTPDataStruct\n\n    def __init__(self, *args, **kwargs):\n        HTTPArray, _ = get_thingking_deps()\n        self.HTTPArray = HTTPArray\n        super().__init__(*args, **kwargs)\n\n    def parse_header(self):\n        \"\"\"docstring for parse_header\"\"\"\n        # Pre-process\n        ascfile = self.HTTPArray(self.header)\n        max_header_size = 1024 * 1024\n        lines = StringIO(ascfile[:max_header_size].data[:])\n        while True:\n            l = lines.readline()\n            if self._eof in l:\n                break\n\n            self.parse_line(l, lines)\n\n        hoff = lines.tell()\n        if self.header != self.filename:\n            hoff = 0\n        self.parameters[\"header_offset\"] = hoff\n\n\ndef load_sdf(filename, header=None):\n    r\"\"\"Load an SDF file.\n\n    Given an SDF file (see https://bitbucket.org/JohnSalmon/sdf), parse the\n    ASCII header and construct numpy memmap array access. The file can\n    be either local (on a hard drive, for example), or remote (on the World\n    Wide Web).\n\n    Parameters\n    ----------\n    filename: string\n        The filename or WWW address associated with the data to be loaded.\n    header: string, optional\n        If separate from the data file, a file containing the\n        header can be specified. Default: None.\n\n    Returns\n    -------\n    sdf : SDFRead object\n        Dict-like container of parameters and data.\n\n    References\n    ----------\n    SDF is described here:\n\n        J. K. Salmon and M. S. Warren. Self-Describing File (SDF) Library.\n        Zenodo, Jun 2014. URL https://bitbucket.org/JohnSalmon/sdf.\n\n    Examples\n    --------\n\n    >>> sdf = SDFRead(\"data.sdf\", header=\"data.hdr\")\n    >>> print(sdf.parameters)\n    >>> print(sdf[\"x\"])\n\n    \"\"\"\n    if \"http\" in filename:\n        sdf = HTTPSDFRead(filename, header=header)\n    else:\n        sdf = SDFRead(filename, header=header)\n    return sdf\n\n\ndef _shift_periodic(pos, left, right, domain_width):\n    \"\"\"\n    Periodically shift positions that are right of left+domain_width to\n    the left, and those left of right-domain_width to the right.\n    \"\"\"\n    for i in range(3):\n        mask = pos[:, i] >= left[i] + domain_width[i]\n        pos[mask, i] -= domain_width[i]\n        mask = pos[:, i] < right[i] - domain_width[i]\n        pos[mask, i] += domain_width[i]\n    return\n\n\nclass SDFIndex:\n    \"\"\"docstring for SDFIndex\n\n    This provides an index mechanism into the full SDF Dataset.\n\n    Most useful class methods:\n        get_cell_data(level, cell_iarr, fields)\n        iter_bbox_data(left, right, fields)\n        iter_bbox_data(left, right, fields)\n\n    \"\"\"\n\n    def __init__(self, sdfdata, indexdata, level=None):\n        super().__init__()\n        self.sdfdata = sdfdata\n        self.indexdata = indexdata\n        if level is None:\n            level = self.indexdata.parameters.get(\"level\", None)\n        self.level = level\n\n        self.rmin = None\n        self.rmax = None\n        self.domain_width = None\n        self.domain_buffer = 0\n        self.domain_dims = 0\n        self.domain_active_dims = 0\n        self.wandering_particles = False\n        self.valid_indexdata = True\n        self.masks = {\n            \"p\": int(\"011\" * level, 2),\n            \"t\": int(\"101\" * level, 2),\n            \"r\": int(\"110\" * level, 2),\n            \"z\": int(\"011\" * level, 2),\n            \"y\": int(\"101\" * level, 2),\n            \"x\": int(\"110\" * level, 2),\n            2: int(\"011\" * level, 2),\n            1: int(\"101\" * level, 2),\n            0: int(\"110\" * level, 2),\n        }\n        self.dim_slices = {\n            \"p\": slice(0, None, 3),\n            \"t\": slice(1, None, 3),\n            \"r\": slice(2, None, 3),\n            \"z\": slice(0, None, 3),\n            \"y\": slice(1, None, 3),\n            \"x\": slice(2, None, 3),\n            2: slice(0, None, 3),\n            1: slice(1, None, 3),\n            0: slice(2, None, 3),\n        }\n        self.set_bounds()\n        self._midx_version = self.indexdata.parameters.get(\"midx_version\", 0)\n        if self._midx_version >= 1.0:\n            max_key = self.get_key(np.array([2**self.level - 1] * 3, dtype=\"int64\"))\n        else:\n            max_key = self.indexdata[\"index\"][-1]\n        self._max_key = max_key\n\n    def _fix_rexact(self, rmin, rmax):\n        center = 0.5 * (rmax + rmin)\n        mysize = rmax - rmin\n        mysize *= 1.0 + 4.0 * np.finfo(np.float32).eps\n        self.rmin = center - 0.5 * mysize\n        self.rmax = center + 0.5 * mysize\n\n    def set_bounds(self):\n        if (\n            \"x_min\" in self.sdfdata.parameters and \"x_max\" in self.sdfdata.parameters\n        ) or (\n            \"theta_min\" in self.sdfdata.parameters\n            and \"theta_max\" in self.sdfdata.parameters\n        ):\n            if \"x_min\" in self.sdfdata.parameters:\n                rmin = np.array(\n                    [\n                        self.sdfdata.parameters[\"x_min\"],\n                        self.sdfdata.parameters[\"y_min\"],\n                        self.sdfdata.parameters[\"z_min\"],\n                    ]\n                )\n                rmax = np.array(\n                    [\n                        self.sdfdata.parameters[\"x_max\"],\n                        self.sdfdata.parameters[\"y_max\"],\n                        self.sdfdata.parameters[\"z_max\"],\n                    ]\n                )\n            elif \"theta_min\" in self.sdfdata.parameters:\n                rmin = np.array(\n                    [\n                        self.sdfdata.parameters[\"r_min\"],\n                        self.sdfdata.parameters[\"theta_min\"],\n                        self.sdfdata.parameters[\"phi_min\"],\n                    ]\n                )\n                rmax = np.array(\n                    [\n                        self.sdfdata.parameters[\"r_max\"],\n                        self.sdfdata.parameters[\"theta_max\"],\n                        self.sdfdata.parameters[\"phi_max\"],\n                    ]\n                )\n            self._fix_rexact(rmin, rmax)\n            self.true_domain_left = self.rmin.copy()\n            self.true_domain_right = self.rmax.copy()\n            self.true_domain_width = self.rmax - self.rmin\n            self.domain_width = self.rmax - self.rmin\n            self.domain_dims = 1 << self.level\n            self.domain_buffer = 0\n            self.domain_active_dims = self.domain_dims\n        else:\n            mylog.debug(\"Setting up older data\")\n            rx = self.sdfdata.parameters.get(\"Rx\")\n            ry = self.sdfdata.parameters.get(\"Ry\")\n            rz = self.sdfdata.parameters.get(\"Rz\")\n            a = self.sdfdata.parameters.get(\"a\", 1.0)\n            rmin = -a * np.array([rx, ry, rz])\n            rmax = a * np.array([rx, ry, rz])\n            self.true_domain_left = rmin.copy()\n            self.true_domain_right = rmax.copy()\n            self.true_domain_width = rmax - rmin\n\n            expand_root = 0.0\n            morton_xyz = self.sdfdata.parameters.get(\"morton_xyz\", False)\n            if not morton_xyz:\n                mylog.debug(\"Accounting for wandering particles\")\n                self.wandering_particles = True\n                ic_Nmesh = self.sdfdata.parameters.get(\"ic_Nmesh\", 0)\n                # Expand root for non power-of-2\n                if ic_Nmesh != 0:\n                    f2 = 1 << int(np.log2(ic_Nmesh - 1) + 1)\n                    if f2 != ic_Nmesh:\n                        expand_root = 1.0 * f2 / ic_Nmesh - 1.0\n                        mylog.debug(\"Expanding: %s, %s, %s\", f2, ic_Nmesh, expand_root)\n                        rmin *= 1.0 + expand_root\n                        rmax *= 1.0 + expand_root\n\n            self._fix_rexact(rmin, rmax)\n            self.domain_width = self.rmax - self.rmin\n            self.domain_dims = 1 << self.level\n            self.domain_buffer = (\n                self.domain_dims - int(self.domain_dims / (1.0 + expand_root))\n            ) / 2\n            self.domain_active_dims = self.domain_dims - 2 * self.domain_buffer\n\n        mylog.debug(\"MIDX rmin: %s, rmax: %s\", self.rmin, self.rmax)\n        mylog.debug(\n            \"MIDX: domain_width: %s, domain_dims: %s, domain_active_dims: %s \",\n            self.domain_width,\n            self.domain_dims,\n            self.domain_active_dims,\n        )\n\n    def spread_bits(self, ival, level=None):\n        if level is None:\n            level = self.level\n        res = 0\n        for i in range(level):\n            res |= ((ival >> i) & 1) << (i * 3)\n        return res\n\n    def get_key(self, iarr, level=None):\n        if level is None:\n            level = self.level\n        i1, i2, i3 = (v.astype(\"int64\") for v in iarr)\n        return (\n            self.spread_bits(i1, level)\n            | self.spread_bits(i2, level) << 1\n            | self.spread_bits(i3, level) << 2\n        )\n\n    def spread_bitsv(self, ival, level=None):\n        if level is None:\n            level = self.level\n        return spread_bitsv(ival, level)\n\n    def get_keyv(self, iarr, level=None):\n        if level is None:\n            level = self.level\n        return get_keyv(iarr, level)\n\n    def get_key_slow(self, iarr, level=None):\n        if level is None:\n            level = self.level\n        i1, i2, i3 = iarr\n        rep1 = np.binary_repr(i1, width=self.level)\n        rep2 = np.binary_repr(i2, width=self.level)\n        rep3 = np.binary_repr(i3, width=self.level)\n        inter = np.zeros(self.level * 3, dtype=\"c\")\n        inter[self.dim_slices[0]] = rep1\n        inter[self.dim_slices[1]] = rep2\n        inter[self.dim_slices[2]] = rep3\n        return int(inter.tobytes(), 2)\n\n    def get_key_ijk(self, i1, i2, i3, level=None):\n        return self.get_key(np.array([i1, i2, i3]), level=level)\n\n    def get_slice_key(self, ind, dim=\"r\"):\n        slb = np.binary_repr(ind, width=self.level)\n        expanded = np.array([0] * self.level * 3, dtype=\"c\")\n        expanded[self.dim_slices[dim]] = slb\n        return int(expanded.tobytes(), 2)\n\n    def get_ind_from_key(self, key, dim=\"r\"):\n        ind = [0, 0, 0]\n        br = np.binary_repr(key, width=self.level * 3)\n        for dim in range(3):\n            ind[dim] = int(br[self.dim_slices[dim]], 2)\n        return ind\n\n    def get_slice_chunks(self, slice_dim, slice_index):\n        sl_key = self.get_slice_key(slice_index, dim=slice_dim)\n        mask = (self.indexdata[\"index\"] & ~self.masks[slice_dim]) == sl_key\n        offsets = self.indexdata[\"base\"][mask]\n        lengths = self.indexdata[\"len\"][mask]\n        return mask, offsets, lengths\n\n    def get_ibbox_slow(self, ileft, iright):\n        \"\"\"\n        Given left and right indices, return a mask and\n        set of offsets+lengths into the sdf data.\n        \"\"\"\n        mask = np.zeros(self.indexdata[\"index\"].shape, dtype=\"bool\")\n        ileft = np.array(ileft, dtype=\"int64\")\n        iright = np.array(iright, dtype=\"int64\")\n        for i in range(3):\n            left_key = self.get_slice_key(ileft[i], dim=i)\n            right_key = self.get_slice_key(iright[i], dim=i)\n            dim_inds = self.indexdata[\"index\"] & ~self.masks[i]\n            mask *= (dim_inds >= left_key) * (dim_inds <= right_key)\n            del dim_inds\n\n        offsets = self.indexdata[\"base\"][mask]\n        lengths = self.indexdata[\"len\"][mask]\n        return mask, offsets, lengths\n\n    def get_ibbox(self, ileft, iright):\n        \"\"\"\n        Given left and right indices, return a mask and\n        set of offsets+lengths into the sdf data.\n        \"\"\"\n        # print('Getting data from ileft to iright:',  ileft, iright)\n\n        ix, iy, iz = (iright - ileft) * 1j\n        mylog.debug(\"MIDX IBBOX: %s %s %s %s %s\", ileft, iright, ix, iy, iz)\n\n        # plus 1 that is sliced, plus a bit since mgrid is not inclusive\n        Z, Y, X = np.mgrid[\n            ileft[2] : iright[2] + 1.01,\n            ileft[1] : iright[1] + 1.01,\n            ileft[0] : iright[0] + 1.01,\n        ]\n\n        mask = slice(0, -1, None)\n        X = X[mask, mask, mask].astype(\"int64\").ravel()\n        Y = Y[mask, mask, mask].astype(\"int64\").ravel()\n        Z = Z[mask, mask, mask].astype(\"int64\").ravel()\n\n        if self.wandering_particles:\n            # Need to get padded bbox around the border to catch\n            # wandering particles.\n            dmask = X < self.domain_buffer\n            dmask += Y < self.domain_buffer\n            dmask += Z < self.domain_buffer\n            dmask += X >= self.domain_dims\n            dmask += Y >= self.domain_dims\n            dmask += Z >= self.domain_dims\n            dinds = self.get_keyv([X[dmask], Y[dmask], Z[dmask]])\n            dinds = dinds[dinds < self._max_key]\n            dinds = dinds[self.indexdata[\"len\"][dinds] > 0]\n            # print('Getting boundary layers for wanderers, cells: %i' % dinds.size)\n\n        # Correct For periodicity\n        X[X < self.domain_buffer] += self.domain_active_dims\n        Y[Y < self.domain_buffer] += self.domain_active_dims\n        Z[Z < self.domain_buffer] += self.domain_active_dims\n        X[X >= self.domain_buffer + self.domain_active_dims] -= self.domain_active_dims\n        Y[Y >= self.domain_buffer + self.domain_active_dims] -= self.domain_active_dims\n        Z[Z >= self.domain_buffer + self.domain_active_dims] -= self.domain_active_dims\n\n        # print('periodic:',  X.min(), X.max(), Y.min(), Y.max(), Z.min(), Z.max())\n\n        indices = self.get_keyv([X, Y, Z])\n        # Only mask out if we are actually getting data rather than getting indices into\n        # a space.\n        if self.valid_indexdata:\n            indices = indices[indices < self._max_key]\n            # indices = indices[self.indexdata['len'][indices] > 0]\n            # Faster for sparse lookups. Need better heuristic.\n            new_indices = []\n            for ind in indices:\n                if self.indexdata[\"len\"][ind] > 0:\n                    new_indices.append(ind)\n            indices = np.array(indices, dtype=\"int64\")\n\n        # indices = np.array([self.get_key_ijk(x, y, z) for x, y, z in zip(X, Y, Z)])\n        # Here we sort the indices to batch consecutive reads together.\n        if self.wandering_particles:\n            indices = np.sort(np.append(indices, dinds))\n        else:\n            indices = np.sort(indices)\n        return indices\n\n    def get_bbox(self, left, right):\n        \"\"\"\n        Given left and right indices, return a mask and\n        set of offsets+lengths into the sdf data.\n        \"\"\"\n        ileft = np.floor((left - self.rmin) / self.domain_width * self.domain_dims)\n        iright = np.floor((right - self.rmin) / self.domain_width * self.domain_dims)\n        if np.any(iright - ileft) > self.domain_dims:\n            mylog.warning(\n                \"Attempting to get data from bounding box larger than the domain. \"\n                \"You may want to check your units.\"\n            )\n        # iright[iright <= ileft+1] += 1\n\n        return self.get_ibbox(ileft, iright)\n\n    def get_nparticles_bbox(self, left, right):\n        \"\"\"\n        Given left and right edges, return total\n        number of particles present.\n        \"\"\"\n        ileft = np.floor((left - self.rmin) / self.domain_width * self.domain_dims)\n        iright = np.floor((right - self.rmin) / self.domain_width * self.domain_dims)\n        indices = self.get_ibbox(ileft, iright)\n        npart = 0\n        for ind in indices:\n            npart += self.indexdata[\"len\"][ind]\n        return npart\n\n    def get_data(self, chunk, fields):\n        data = {}\n        for field in fields:\n            data[field] = self.sdfdata[field][chunk]\n        return data\n\n    def get_next_nonzero_chunk(self, key, stop=None):\n        # These next two while loops are to squeeze the keys if they are empty.\n        # Would be better to go through and set base equal to the last non-zero base.\n        if stop is None:\n            stop = self._max_key\n        while key < stop:\n            if self.indexdata[\"len\"][key] == 0:\n                # print('Squeezing keys, incrementing')\n                key += 1\n            else:\n                break\n        return key\n\n    def get_previous_nonzero_chunk(self, key, stop=None):\n        # These next two while loops are to squeeze the keys if they are empty.\n        # Would be better to go through and set base equal to the last non-zero base.\n        if stop is None:\n            stop = self.indexdata[\"index\"][0]\n        while key > stop:\n            if self.indexdata[\"len\"][key] == 0:\n                # print('Squeezing keys, decrementing')\n                key -= 1\n            else:\n                break\n        return key\n\n    def iter_data(self, inds, fields):\n        num_inds = len(inds)\n        num_reads = 0\n        mylog.debug(\"MIDX Reading %i chunks\", num_inds)\n        i = 0\n        while i < num_inds:\n            ind = inds[i]\n            base = self.indexdata[\"base\"][ind]\n            length = self.indexdata[\"len\"][ind]\n            # Concatenate aligned reads\n            nexti = i + 1\n            combined = 0\n            while nexti < num_inds:\n                nextind = inds[nexti]\n                # print(\n                #    \"b: %i l: %i end: %i  next: %i\"\n                #    % (base, length, base + length, self.indexdata[\"base\"][nextind])\n                # )\n                if combined < 1024 and base + length == self.indexdata[\"base\"][nextind]:\n                    length += self.indexdata[\"len\"][nextind]\n                    i += 1\n                    nexti += 1\n                    combined += 1\n                else:\n                    break\n\n            chunk = slice(base, base + length)\n            mylog.debug(\n                \"Reading chunk %i of length %i after catting %i starting at %i\",\n                i,\n                length,\n                combined,\n                ind,\n            )\n            num_reads += 1\n            if length > 0:\n                data = self.get_data(chunk, fields)\n                yield data\n                del data\n            i += 1\n        mylog.debug(\"Read %i chunks, batched into %i reads\", num_inds, num_reads)\n\n    def filter_particles(self, myiter, myfilter):\n        for data in myiter:\n            mask = myfilter(data)\n\n            if mask.sum() == 0:\n                continue\n            filtered = {}\n            for f in data.keys():\n                filtered[f] = data[f][mask]\n\n            yield filtered\n\n    def filter_bbox(self, left, right, myiter):\n        \"\"\"\n        Filter data by masking out data outside of a bbox defined\n        by left/right. Account for periodicity of data, allowing left/right\n        to be outside of the domain.\n        \"\"\"\n\n        for data in myiter:\n            # mask = np.zeros_like(data, dtype='bool')\n            pos = np.array([data[\"x\"].copy(), data[\"y\"].copy(), data[\"z\"].copy()]).T\n\n            DW = self.true_domain_width\n            # This hurts, but is useful for periodicity. Probably should check first\n            # if it is even needed for a given left/right\n            _shift_periodic(pos, left, right, DW)\n\n            # Now get all particles that are within the bbox\n            mask = np.all(pos >= left, axis=1) * np.all(pos < right, axis=1)\n            # print('Mask shape, sum:', mask.shape, mask.sum())\n\n            mylog.debug(\n                \"Filtering particles, returning %i out of %i\", mask.sum(), mask.shape[0]\n            )\n\n            if not np.any(mask):\n                continue\n\n            filtered = {ax: pos[:, i][mask] for i, ax in enumerate(\"xyz\")}\n            for f in data.keys():\n                if f in \"xyz\":\n                    continue\n                filtered[f] = data[f][mask]\n\n            # for i, ax in enumerate('xyz'):\n            #    #print(left, right)\n            #    assert np.all(filtered[ax] >= left[i])\n            #    assert np.all(filtered[ax] < right[i])\n\n            yield filtered\n\n    def filter_sphere(self, center, radius, myiter):\n        \"\"\"\n        Filter data by masking out data outside of a sphere defined\n        by a center and radius. Account for periodicity of data, allowing\n        left/right to be outside of the domain.\n        \"\"\"\n\n        # Get left/right for periodicity considerations\n        left = center - radius\n        right = center + radius\n        for data in myiter:\n            pos = np.array([data[\"x\"].copy(), data[\"y\"].copy(), data[\"z\"].copy()]).T\n\n            DW = self.true_domain_width\n            _shift_periodic(pos, left, right, DW)\n\n            # Now get all particles that are within the sphere\n            mask = ((pos - center) ** 2).sum(axis=1) ** 0.5 < radius\n\n            mylog.debug(\n                \"Filtering particles, returning %i out of %i\", mask.sum(), mask.shape[0]\n            )\n\n            if not np.any(mask):\n                continue\n\n            filtered = {ax: pos[:, i][mask] for i, ax in enumerate(\"xyz\")}\n            for f in data.keys():\n                if f in \"xyz\":\n                    continue\n                filtered[f] = data[f][mask]\n\n            yield filtered\n\n    def iter_filtered_bbox_fields(self, left, right, data, pos_fields, fields):\n        \"\"\"\n        This function should be destroyed, as it will only work with units.\n        \"\"\"\n\n        kpcuq = left.in_units(\"kpccm\").uq\n        mpcuq = left.in_units(\"Mpccm/h\").uq\n        DW = (self.true_domain_width * kpcuq).in_units(\"Mpc/h\")\n        if pos_fields is None:\n            pos_fields = \"x\", \"y\", \"z\"\n        xf, yf, zf = pos_fields\n        mylog.debug(\"Using position fields: %s\", pos_fields)\n\n        # I'm sorry.\n        pos = (\n            mpcuq\n            * np.array(\n                [\n                    data[xf].in_units(\"Mpccm/h\"),\n                    data[yf].in_units(\"Mpccm/h\"),\n                    data[zf].in_units(\"Mpccm/h\"),\n                ]\n            ).T\n        )\n\n        # This hurts, but is useful for periodicity. Probably should check first\n        # if it is even needed for a given left/right\n        _shift_periodic(pos, left, right, DW)\n\n        mylog.debug(\n            \"Periodic filtering, %s %s %s %s\",\n            left,\n            right,\n            pos.min(axis=0),\n            pos.max(axis=0),\n        )\n        # Now get all particles that are within the bbox\n        mask = np.all(pos >= left, axis=1) * np.all(pos < right, axis=1)\n\n        mylog.debug(\n            \"Filtering particles, returning %i out of %i\", mask.sum(), mask.shape[0]\n        )\n\n        if np.any(mask):\n            for i, f in enumerate(pos_fields):\n                yield f, pos[:, i][mask]\n\n            for f in fields:\n                if f in pos_fields:\n                    continue\n                # print('yielding nonpos field', f)\n                yield f, data[f][mask]\n\n    def iter_bbox_data(self, left, right, fields):\n        \"\"\"\n        Iterate over all data within a bounding box defined by a left\n        and a right.\n        \"\"\"\n        _ensure_xyz_fields(fields)\n        mylog.debug(\"MIDX Loading region from %s to %s\", left, right)\n        inds = self.get_bbox(left, right)\n        # Need to put left/right in float32 to avoid fp roundoff errors\n        # in the bbox later.\n        # left = left.astype('float32')\n        # right = right.astype('float32')\n\n        # my_filter = bbox_filter(left, right, self.true_domain_width)\n        yield from self.filter_bbox(left, right, self.iter_data(inds, fields))\n        # for dd in self.filter_particles(\n        #    self.iter_data(inds, fields),\n        #    my_filter):\n        #    yield dd\n\n    def iter_sphere_data(self, center, radius, fields):\n        \"\"\"\n        Iterate over all data within some sphere defined by a center and\n        a radius.\n        \"\"\"\n        _ensure_xyz_fields(fields)\n        mylog.debug(\"MIDX Loading spherical region %s to %s\", center, radius)\n        inds = self.get_bbox(center - radius, center + radius)\n\n        yield from self.filter_sphere(center, radius, self.iter_data(inds, fields))\n\n    def iter_ibbox_data(self, left, right, fields):\n        mylog.debug(\"MIDX Loading region from %s to %s\", left, right)\n        inds = self.get_ibbox(left, right)\n        return self.iter_data(inds, fields)\n\n    def get_contiguous_chunk(self, left_key, right_key, fields):\n        lbase = 0\n        if left_key > self._max_key:\n            raise RuntimeError(\n                f\"Left key is too large. Key: {left_key} Max Key: {self._max_key}\"\n            )\n        right_key = min(right_key, self._max_key)\n\n        left_key = self.get_next_nonzero_chunk(left_key, right_key - 1)\n        right_key = self.get_previous_nonzero_chunk(right_key, left_key)\n\n        lbase = self.indexdata[\"base\"][left_key]\n\n        rbase = self.indexdata[\"base\"][right_key]\n        rlen = self.indexdata[\"len\"][right_key]\n\n        length = rbase + rlen - lbase\n        if length > 0:\n            mylog.debug(\n                \"Getting contiguous chunk of size %i starting at %i\", length, lbase\n            )\n        return self.get_data(slice(lbase, lbase + length), fields)\n\n    def get_key_data(self, key, fields):\n        if key > self._max_key:\n            raise RuntimeError(\n                f\"Left key is too large. Key: {key} Max Key: {self._max_key}\"\n            )\n        base = self.indexdata[\"base\"][key]\n        length = self.indexdata[\"len\"][key] - base\n        if length > 0:\n            mylog.debug(\n                \"Getting contiguous chunk of size %i starting at %i\", length, base\n            )\n        return self.get_data(slice(base, base + length), fields)\n\n    def iter_slice_data(self, slice_dim, slice_index, fields):\n        mask, offsets, lengths = self.get_slice_chunks(slice_dim, slice_index)\n        for off, l in zip(offsets, lengths, strict=True):\n            data = {}\n            chunk = slice(off, off + l)\n            for field in fields:\n                data[field] = self.sdfdata[field][chunk]\n            yield data\n            del data\n\n    def get_key_bounds(self, level, cell_iarr):\n        \"\"\"\n        Get index keys for index file supplied.\n\n        level: int\n            Requested level\n        cell_iarr: array-like, length 3\n            Requested cell from given level.\n\n        Returns:\n            lmax_lk, lmax_rk\n        \"\"\"\n        shift = self.level - level\n        level_buff = 0\n        level_lk = self.get_key(cell_iarr + level_buff)\n        level_rk = self.get_key(cell_iarr + level_buff) + 1\n        lmax_lk = level_lk << shift * 3\n        lmax_rk = ((level_rk) << shift * 3) - 1\n        # print(\n        #    \"Level \",\n        #    level,\n        #    np.binary_repr(level_lk, width=self.level * 3),\n        #    np.binary_repr(level_rk, width=self.level * 3),\n        # )\n        # print(\n        #    \"Level \",\n        #    self.level,\n        #    np.binary_repr(lmax_lk, width=self.level * 3),\n        #    np.binary_repr(lmax_rk, width=self.level * 3),\n        # )\n        return lmax_lk, lmax_rk\n\n    def find_max_cell(self):\n        max_cell = np.argmax(self.indexdata[\"len\"][:])\n        return max_cell\n\n    def find_max_cell_center(self):\n        max_cell = self.find_max_cell()\n        cell_ijk = np.array(\n            self.get_ind_from_key(self.indexdata[\"index\"][max_cell]), dtype=\"int64\"\n        )\n        position = (cell_ijk + 0.5) * (self.domain_width / self.domain_dims) + self.rmin\n        return position\n\n    def get_cell_data(self, level, cell_iarr, fields):\n        \"\"\"\n        Get data from requested cell\n\n        This uses the raw cell index, and doesn't account for periodicity or\n        an expanded domain (non-power of 2).\n\n        level: int\n            Requested level\n        cell_iarr: array-like, length 3\n            Requested cell from given level.         fields: list\n            Requested fields\n\n        Returns:\n            cell_data: dict\n                Dictionary of field_name, field_data\n        \"\"\"\n        cell_iarr = np.array(cell_iarr, dtype=\"int64\")\n        lk, rk = self.get_key_bounds(level, cell_iarr)\n        mylog.debug(\"Reading contiguous chunk from %i to %i\", lk, rk)\n        return self.get_contiguous_chunk(lk, rk, fields)\n\n    def get_cell_bbox(self, level, cell_iarr):\n        \"\"\"Get floating point bounding box for a given midx cell\n\n        Returns:\n            bbox: array-like of shape (3,2)\n\n        \"\"\"\n        cell_iarr = np.array(cell_iarr, dtype=\"int64\")\n        cell_width = self.get_cell_width(level)\n        le = self.rmin + cell_iarr * cell_width\n        re = le + cell_width\n        bbox = np.array([le, re]).T\n        assert bbox.shape == (3, 2)\n        return bbox\n\n    def iter_padded_bbox_data(self, level, cell_iarr, pad, fields):\n        \"\"\"\n        Yields data chunks for a cell on the given level\n        plus a padding around the cell, for a list of fields.\n\n        Yields:\n            dd: A dictionaries of data.\n\n        Example:\n\n        for chunk in midx.iter_padded_bbox_data(\n            6, np.array([128]*3), 8.0, ['x','y','z','ident']):\n\n            print(chunk['x'].max())\n\n        \"\"\"\n\n        _ensure_xyz_fields(fields)\n        bbox = self.get_cell_bbox(level, cell_iarr)\n        filter_left = bbox[:, 0] - pad\n        filter_right = bbox[:, 1] + pad\n\n        # Center cell\n        for dd in self.filter_bbox(\n            filter_left, filter_right, [self.get_cell_data(level, cell_iarr, fields)]\n        ):\n            yield dd\n            del dd\n\n        # Bottom & Top\n        pbox = bbox.copy()\n        pbox[0, 0] -= pad[0]\n        pbox[0, 1] += pad[0]\n        pbox[1, 0] -= pad[1]\n        pbox[1, 1] += pad[1]\n        pbox[2, 0] -= pad[2]\n        pbox[2, 1] = bbox[2, 0]\n        for dd in self.filter_bbox(\n            filter_left,\n            filter_right,\n            self.iter_bbox_data(pbox[:, 0], pbox[:, 1], fields),\n        ):\n            yield dd\n            del dd\n\n        pbox[2, 0] = bbox[2, 1]\n        pbox[2, 1] = pbox[2, 0] + pad[2]\n        for dd in self.filter_bbox(\n            filter_left,\n            filter_right,\n            self.iter_bbox_data(pbox[:, 0], pbox[:, 1], fields),\n        ):\n            yield dd\n            del dd\n\n        # Front & Back\n        pbox = bbox.copy()\n        pbox[0, 0] -= pad[0]\n        pbox[0, 1] += pad[0]\n        pbox[1, 0] -= pad[1]\n        pbox[1, 1] = bbox[1, 0]\n        for dd in self.filter_bbox(\n            filter_left,\n            filter_right,\n            self.iter_bbox_data(pbox[:, 0], pbox[:, 1], fields),\n        ):\n            yield dd\n            del dd\n\n        pbox[1, 0] = bbox[1, 1]\n        pbox[1, 1] = pbox[1, 0] + pad[1]\n        for dd in self.filter_bbox(\n            filter_left,\n            filter_right,\n            self.iter_bbox_data(pbox[:, 0], pbox[:, 1], fields),\n        ):\n            yield dd\n            del dd\n\n        # Left & Right\n        pbox = bbox.copy()\n        pbox[0, 0] -= pad[0]\n        pbox[0, 1] = bbox[0, 0]\n        for dd in self.filter_bbox(\n            filter_left,\n            filter_right,\n            self.iter_bbox_data(pbox[:, 0], pbox[:, 1], fields),\n        ):\n            yield dd\n            del dd\n\n        pbox[0, 0] = bbox[0, 1]\n        pbox[0, 1] = pbox[0, 0] + pad[0]\n        for dd in self.filter_bbox(\n            filter_left,\n            filter_right,\n            self.iter_bbox_data(pbox[:, 0], pbox[:, 1], fields),\n        ):\n            yield dd\n            del dd\n\n    def get_padded_bbox_data(self, level, cell_iarr, pad, fields):\n        \"\"\"\n        Return list of data chunks for a cell on the given level\n        plus a padding around the cell, for a list of fields.\n\n        Returns\n        -------\n            data: list\n                A list of dictionaries of data.\n\n        Examples\n        --------\n        >>> chunks = midx.get_padded_bbox_data(\n        ...     6, np.array([128] * 3), 8.0, [\"x\", \"y\", \"z\", \"ident\"]\n        ... )\n\n        \"\"\"\n        _ensure_xyz_fields(fields)\n\n        data = []\n        for dd in self.iter_padded_bbox_data(level, cell_iarr, pad, fields):\n            data.append(dd)\n        return data\n\n    def get_cell_width(self, level):\n        return self.domain_width / 2**level\n\n    def iter_padded_bbox_keys(self, level, cell_iarr, pad):\n        \"\"\"\n\n        Returns:\n            bbox: array-like of shape (3,2)\n\n        \"\"\"\n        bbox = self.get_cell_bbox(level, cell_iarr)\n\n        # Need to get all of these\n        low_key, high_key = self.get_key_bounds(level, cell_iarr)\n        yield from range(low_key, high_key)\n\n        # Bottom & Top\n        pbox = bbox.copy()\n        pbox[0, 0] -= pad[0]\n        pbox[0, 1] += pad[0]\n        pbox[1, 0] -= pad[1]\n        pbox[1, 1] += pad[1]\n        pbox[2, 0] -= pad[2]\n        pbox[2, 1] = bbox[2, 0]\n        yield from self.get_bbox(pbox[:, 0], pbox[:, 1])\n\n        pbox[2, 0] = bbox[2, 1]\n        pbox[2, 1] = pbox[2, 0] + pad[2]\n        yield from self.get_bbox(pbox[:, 0], pbox[:, 1])\n\n        # Front & Back\n        pbox = bbox.copy()\n        pbox[0, 0] -= pad[0]\n        pbox[0, 1] += pad[0]\n        pbox[1, 0] -= pad[1]\n        pbox[1, 1] = bbox[1, 0]\n        yield from self.get_bbox(pbox[:, 0], pbox[:, 1])\n        pbox[1, 0] = bbox[1, 1]\n        pbox[1, 1] = pbox[1, 0] + pad[1]\n        yield from self.get_bbox(pbox[:, 0], pbox[:, 1])\n\n        # Left & Right\n        pbox = bbox.copy()\n        pbox[0, 0] -= pad[0]\n        pbox[0, 1] = bbox[0, 0]\n        yield from self.get_bbox(pbox[:, 0], pbox[:, 1])\n        pbox[0, 0] = bbox[0, 1]\n        pbox[0, 1] = pbox[0, 0] + pad[0]\n        yield from self.get_bbox(pbox[:, 0], pbox[:, 1])\n"
  },
  {
    "path": "yt/utilities/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/utilities/tests/cosmology_answers.yml",
    "content": "cosmologies:\n  EdS: {hubble_constant: 0.7, omega_lambda: 0.0, omega_matter: 1.0, omega_radiation: 0.0}\n  LCDM: {hubble_constant: 0.7, omega_lambda: 0.7, omega_matter: 0.3, omega_radiation: 0.0}\n  omega_radiation: {hubble_constant: 0.7, omega_lambda: 0.7, omega_matter: 0.2999,\n    omega_radiation: 0.0001}\n  open: {hubble_constant: 0.7, omega_lambda: 0.0, omega_matter: 0.3, omega_radiation: 0.0}\nfunctions:\n  age_integrand:\n    answers: {EdS: 0.17677669529663687, LCDM: 0.28398091712353246, omega_radiation: 0.2839442815151592,\n      open: 0.21926450482675733}\n    args: [1]\n  angular_diameter_distance:\n    answers: {EdS: -47.63859400006291, LCDM: 74.71628440466395, omega_radiation: 74.61156201248941,\n      open: 114.42229537588507}\n    args: [1, 2]\n    units: Mpc\n  angular_scale:\n    answers: {EdS: -47.63859400006291, LCDM: 74.71628440466395, omega_radiation: 74.61156201248941,\n      open: 114.42229537588507}\n    args: [1, 2]\n    units: Mpc/radian\n  comoving_radial_distance:\n    answers: {EdS: 1111.3289801408714, LCDM: 1875.857637458183, omega_radiation: 1875.4640874252457,\n      open: 1448.958605095395}\n    args: [1, 2]\n    units: Mpc\n  comoving_transverse_distance:\n    answers: {EdS: 1111.3289801408714, LCDM: 1875.857637458183, omega_radiation: 1875.4640874252457,\n      open: 1468.3857559045264}\n    args: [1, 2]\n    units: Mpc\n  comoving_volume:\n    answers: {EdS: 5.749320615429804, LCDM: 27.64956077763837, omega_radiation: 27.632162011458195,\n      open: 82.84024895030079}\n    args: [1, 2]\n    units: Gpc**3\n  critical_density:\n    answers: {EdS: 1088.0152888198547, LCDM: 421.6059244176937, omega_radiation: 421.7147259465755,\n      open: 707.2099377329054}\n    args: [1]\n    units: Msun/kpc**3\n  expansion_factor:\n    answers: {EdS: 2.8284271247461903, LCDM: 1.7606816861659007, omega_radiation: 1.7609088562444108,\n      open: 2.2803508501982757}\n    args: [1]\n  hubble_distance:\n    answers: {EdS: 4282.749400000001, LCDM: 4282.749400000001, omega_radiation: 4282.749400000001,\n      open: 4282.749400000001}\n    units: Mpc\n  hubble_parameter:\n    answers: {EdS: 197.98989873223329, LCDM: 123.24771803161306, omega_radiation: 123.26361993710874,\n      open: 159.6245595138793}\n    args: [1]\n    units: km/s/Mpc\n  inverse_expansion_factor:\n    answers: {EdS: 0.35355339059327373, LCDM: 0.5679618342470649, omega_radiation: 0.5678885630303184,\n      open: 0.43852900965351466}\n    args: [1]\n  lookback_time:\n    answers: {EdS: 1500.1343648374723, LCDM: 2524.828936828046, omega_radiation: 2524.3141825048465,\n      open: 1949.3932768737345}\n    args: [1, 2]\n    units: Myr\n  luminosity_distance:\n    answers: {EdS: 5842.669112528931, LCDM: 8931.175468218224, omega_radiation: 8929.836295237686,\n      open: 8369.418267416617}\n    args: [1, 2]\n    units: Mpc\n  path_length:\n    answers: {EdS: 1.5782728314065704, LCDM: 2.679181396559161, omega_radiation: 2.6785873459658784,\n      open: 2.0714508064868244}\n    args: [1, 2]\n  path_length_function:\n    answers: {EdS: 1.414213562373095, LCDM: 2.2718473369882597, omega_radiation: 2.2715542521212737,\n      open: 1.7541160386140586}\n    args: [1]\n"
  },
  {
    "path": "yt/utilities/tests/test_amr_kdtree.py",
    "content": "import itertools\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal\n\nfrom yt.testing import fake_amr_ds\n\n\ndef test_amr_kdtree_set_fields():\n    ds = fake_amr_ds(fields=[\"density\", \"pressure\"], units=[\"g/cm**3\", \"dyn/cm**2\"])\n    dd = ds.all_data()\n\n    fields = ds.field_list\n    dd.tiles.set_fields(fields, [True, True], False)\n    gold = {}\n    for i, block in enumerate(dd.tiles.traverse()):\n        gold[i] = [data.copy() for data in block.my_data]\n\n    for log_fields in itertools.product([True, False], [True, False]):\n        dd.tiles.set_fields(fields, log_fields, False)\n        for iblock, block in enumerate(dd.tiles.traverse()):\n            for i in range(len(fields)):\n                if log_fields[i]:\n                    data = block.my_data[i]\n                else:\n                    data = np.log10(block.my_data[i])\n                assert_almost_equal(gold[iblock][i], data)\n"
  },
  {
    "path": "yt/utilities/tests/test_chemical_formulas.py",
    "content": "from numpy.testing import assert_allclose, assert_equal\n\nfrom yt.utilities.chemical_formulas import ChemicalFormula, compute_mu\nfrom yt.utilities.periodic_table import periodic_table\n\n_molecules = (\n    (\"H2O_p1\", ((\"H\", 2), (\"O\", 1)), 1),\n    (\"H2O_m1\", ((\"H\", 2), (\"O\", 1)), -1),\n    (\"H2O\", ((\"H\", 2), (\"O\", 1)), 0),\n    (\"H2SO4\", ((\"H\", 2), (\"S\", 1), (\"O\", 4)), 0),\n    # Now a harder one\n    (\"UuoMtUuq3\", ((\"Uuo\", 1), (\"Mt\", 1), (\"Uuq\", 3)), 0),\n)\n\n\ndef test_formulas():\n    for formula, components, charge in _molecules:\n        f = ChemicalFormula(formula)\n        w = sum(n * periodic_table[e].weight for e, n in components)\n        assert_equal(f.charge, charge)\n        assert_equal(f.weight, w)\n        for (n, c1), (e, c2) in zip(components, f.elements, strict=True):\n            assert_equal(n, e.symbol)\n            assert_equal(c1, c2)\n\n\ndef test_default_mu():\n    assert_allclose(compute_mu(None), 0.5924489101195808)\n    assert_allclose(compute_mu(\"ionized\"), 0.5924489101195808)\n    assert_allclose(compute_mu(\"neutral\"), 1.2285402715185552)\n"
  },
  {
    "path": "yt/utilities/tests/test_config.py",
    "content": "import contextlib\nimport os\nimport shutil\nimport sys\nimport tempfile\nimport unittest\nimport unittest.mock as mock\nfrom io import StringIO\n\nimport yt.utilities.command_line\nfrom yt.utilities.configure import YTConfig\n\n_TEST_PLUGIN = \"_test_plugin.py\"\n# NOTE: the normalization of the crazy camel-case will be checked\n_DUMMY_CFG_INI = f\"\"\"[yt]\nlogLevel = 49\npluginfilename = {_TEST_PLUGIN}\nboolean_stuff = True\nchunk_size = 3\n\"\"\"\n\n_DUMMY_CFG_TOML = f\"\"\"[yt]\nlog_level = 49\nplugin_filename = \"{_TEST_PLUGIN}\"\nboolean_stuff = true\nchunk_size = 3\n\"\"\"\n\n\n@contextlib.contextmanager\ndef captureOutput():\n    oldout, olderr = sys.stdout, sys.stderr\n    try:\n        out = [StringIO(), StringIO()]\n        sys.stdout, sys.stderr = out\n        yield out\n    finally:\n        sys.stdout, sys.stderr = oldout, olderr\n        out[0] = out[0].getvalue()\n        out[1] = out[1].getvalue()\n\n\nclass SysExitException(Exception):\n    pass\n\n\nclass TestYTConfig(unittest.TestCase):\n    def setUp(self):\n        self.xdg_config_home = os.environ.get(\"XDG_CONFIG_HOME\")\n        self.tmpdir = tempfile.mkdtemp()\n        os.environ[\"XDG_CONFIG_HOME\"] = self.tmpdir\n        os.mkdir(os.path.join(self.tmpdir, \"yt\"))\n\n        # run inside another temporary directory to avoid polluting the\n        # local space when we dump configuration to a local yt.toml file\n        self.origin = os.getcwd()\n        os.chdir(tempfile.mkdtemp())\n\n    def tearDown(self):\n        shutil.rmtree(self.tmpdir)\n        if self.xdg_config_home:\n            os.environ[\"XDG_CONFIG_HOME\"] = self.xdg_config_home\n        else:\n            os.environ.pop(\"XDG_CONFIG_HOME\")\n        os.chdir(self.origin)\n\n    def _runYTConfig(self, args):\n        args = [\"yt\", \"config\"] + args\n        retcode = 0\n\n        with (\n            mock.patch.object(sys, \"argv\", args),\n            mock.patch(\"sys.exit\", side_effect=SysExitException) as exit,\n            captureOutput() as output,\n        ):\n            try:\n                yt.utilities.command_line.run_main()\n            except SysExitException:\n                args = exit.mock_calls[0][1]\n                retcode = args[0] if len(args) else 0\n        return {\"rc\": retcode, \"stdout\": output[0], \"stderr\": output[1]}\n\n    def _testKeyValue(self, key, val_set, val_get):\n        info = self._runYTConfig([\"set\", \"yt\", key, str(val_set)])\n        self.assertEqual(info[\"rc\"], 0)\n\n        info = self._runYTConfig([\"get\", \"yt\", key])\n        self.assertEqual(info[\"rc\"], 0)\n        self.assertEqual(info[\"stdout\"].strip(), str(val_get))\n\n        info = self._runYTConfig([\"rm\", \"yt\", key])\n        self.assertEqual(info[\"rc\"], 0)\n\n    def _testKeyTypeError(self, key, val1, val2, expect_error):\n        info = self._runYTConfig([\"set\", \"yt\", key, str(val1)])\n        self.assertEqual(info[\"rc\"], 0)\n\n        if expect_error:\n            with self.assertRaises(TypeError):\n                info = self._runYTConfig([\"set\", \"yt\", key, str(val2)])\n        else:\n            info = self._runYTConfig([\"set\", \"yt\", key, str(val2)])\n\n        info = self._runYTConfig([\"rm\", \"yt\", key])\n        self.assertEqual(info[\"rc\"], 0)\n\n\nclass TestYTConfigCommands(TestYTConfig):\n    def testConfigCommands(self):\n        def remove_spaces_and_breaks(s):\n            return \"\".join(s.split())\n\n        self.assertFalse(os.path.exists(YTConfig.get_global_config_file()))\n\n        info = self._runYTConfig([\"--help\"])\n        self.assertEqual(info[\"rc\"], 0)\n        self.assertEqual(info[\"stderr\"], \"\")\n        self.assertIn(\n            remove_spaces_and_breaks(\"Get and set configuration values for yt\"),\n            remove_spaces_and_breaks(info[\"stdout\"]),\n        )\n\n        info = self._runYTConfig([\"list\"])\n        self.assertEqual(info[\"rc\"], 0)\n        self.assertEqual(info[\"stdout\"], \"\")\n\n        self._testKeyValue(\"internals.parallel\", True, True)\n        self._testKeyValue(\n            \"test_data_dir\", \"~/yt-data\", os.path.expanduser(\"~/yt-data\")\n        )\n        self._testKeyValue(\n            \"test_data_dir\", \"$HOME/yt-data\", os.path.expandvars(\"$HOME/yt-data\")\n        )\n\n        with self.assertRaises(KeyError):\n            self._runYTConfig([\"get\", \"yt\", \"foo\"])\n\n        # Check TypeErrors are raised when changing the type of an entry\n        self._testKeyTypeError(\"foo.bar\", \"test\", 10, expect_error=True)\n        self._testKeyTypeError(\"foo.bar\", \"test\", False, expect_error=True)\n\n        # Check no type error are raised when *not* changing the type\n        self._testKeyTypeError(\"foo.bar\", 10, 20, expect_error=False)\n        self._testKeyTypeError(\"foo.bar\", \"foo\", \"bar\", expect_error=False)\n\n    def tearDown(self):\n        if os.path.exists(YTConfig.get_global_config_file()):\n            os.remove(YTConfig.get_global_config_file())\n        super().tearDown()\n\n\nclass TestYTConfigGlobalLocal(TestYTConfig):\n    def setUp(self):\n        super().setUp()\n        with open(YTConfig.get_local_config_file(), mode=\"w\") as f:\n            f.writelines(\"[yt]\\n\")\n        with open(YTConfig.get_global_config_file(), mode=\"w\") as f:\n            f.writelines(\"[yt]\\n\")\n\n    def testAmbiguousConfig(self):\n        info = self._runYTConfig([\"list\"])\n        self.assertFalse(len(info[\"rc\"]) == 0)\n\n        for cmd in ([\"list\", \"--local\"], [\"list\", \"--global\"]):\n            info = self._runYTConfig(cmd)\n            self.assertEqual(info[\"rc\"], 0)\n"
  },
  {
    "path": "yt/utilities/tests/test_coordinate_conversions.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_almost_equal\n\nfrom yt.utilities.math_utils import (\n    get_cyl_r,\n    get_cyl_r_component,\n    get_cyl_theta,\n    get_cyl_theta_component,\n    get_cyl_z,\n    get_cyl_z_component,\n    get_sph_phi,\n    get_sph_phi_component,\n    get_sph_r,\n    get_sph_r_component,\n    get_sph_theta,\n    get_sph_theta_component,\n)\n\n# Randomly generated coordinates in the domain [[-1,1],[-1,1],-1,1]]\ncoords = np.array(\n    [\n        [-0.41503037, -0.22102472, -0.55774212],\n        [0.73828247, -0.17913899, 0.64076921],\n        [0.08922066, -0.94254844, -0.61774511],\n        [0.10173242, -0.95789145, 0.16294352],\n        [0.73186508, -0.3109153, 0.75728738],\n        [0.8757989, -0.41475119, -0.57039201],\n        [0.58040762, 0.81969082, 0.46759728],\n        [-0.89983356, -0.9853683, -0.38355343],\n    ]\n).T\n\n\ndef test_spherical_coordinate_conversion():\n    normal = [0, 0, 1]\n    real_r = [\n        0.72950559,\n        0.99384957,\n        1.13047198,\n        0.97696269,\n        1.09807968,\n        1.12445067,\n        1.10788685,\n        1.38843954,\n    ]\n    real_theta = [\n        2.44113629,\n        0.87012028,\n        2.14891444,\n        1.4032274,\n        0.80979483,\n        2.10280198,\n        1.13507735,\n        1.85068416,\n    ]\n    real_phi = [\n        -2.65224483,\n        -0.23804243,\n        -1.47641858,\n        -1.46498842,\n        -0.40172325,\n        -0.4422801,\n        0.95466734,\n        -2.31085392,\n    ]\n\n    calc_r = get_sph_r(coords)\n    calc_theta = get_sph_theta(coords, normal)\n    calc_phi = get_sph_phi(coords, normal)\n\n    assert_array_almost_equal(calc_r, real_r)\n    assert_array_almost_equal(calc_theta, real_theta)\n    assert_array_almost_equal(calc_phi, real_phi)\n\n    normal = [1, 0, 0]\n    real_theta = [\n        2.17598842,\n        0.73347681,\n        1.49179079,\n        1.46647589,\n        0.8412984,\n        0.67793705,\n        1.0193883,\n        2.27586987,\n    ]\n    real_phi = [\n        -1.94809584,\n        1.843405,\n        -2.56143151,\n        2.97309903,\n        1.96037671,\n        -2.1995016,\n        0.51841239,\n        -2.77038877,\n    ]\n\n    calc_theta = get_sph_theta(coords, normal)\n    calc_phi = get_sph_phi(coords, normal)\n\n    assert_array_almost_equal(calc_theta, real_theta)\n    assert_array_almost_equal(calc_phi, real_phi)\n\n\ndef test_cylindrical_coordinate_conversion():\n    normal = [0, 0, 1]\n    real_r = [\n        0.47021498,\n        0.75970506,\n        0.94676179,\n        0.96327853,\n        0.79516968,\n        0.96904193,\n        1.00437346,\n        1.3344104,\n    ]\n    real_theta = [\n        -2.65224483,\n        -0.23804243,\n        -1.47641858,\n        -1.46498842,\n        -0.40172325,\n        -0.4422801,\n        0.95466734,\n        -2.31085392,\n    ]\n    real_z = [\n        -0.55774212,\n        0.64076921,\n        -0.61774511,\n        0.16294352,\n        0.75728738,\n        -0.57039201,\n        0.46759728,\n        -0.38355343,\n    ]\n\n    calc_r = get_cyl_r(coords, normal)\n    calc_theta = get_cyl_theta(coords, normal)\n    calc_z = get_cyl_z(coords, normal)\n\n    assert_array_almost_equal(calc_r, real_r)\n    assert_array_almost_equal(calc_theta, real_theta)\n    assert_array_almost_equal(calc_z, real_z)\n\n    normal = [1, 0, 0]\n    real_r = [\n        0.59994016,\n        0.66533898,\n        1.12694569,\n        0.97165149,\n        0.81862843,\n        0.70524152,\n        0.94368441,\n        1.05738542,\n    ]\n    real_theta = [\n        -1.94809584,\n        1.843405,\n        -2.56143151,\n        2.97309903,\n        1.96037671,\n        -2.1995016,\n        0.51841239,\n        -2.77038877,\n    ]\n    real_z = [\n        -0.41503037,\n        0.73828247,\n        0.08922066,\n        0.10173242,\n        0.73186508,\n        0.8757989,\n        0.58040762,\n        -0.89983356,\n    ]\n\n    calc_r = get_cyl_r(coords, normal)\n    calc_theta = get_cyl_theta(coords, normal)\n    calc_z = get_cyl_z(coords, normal)\n\n    assert_array_almost_equal(calc_r, real_r)\n    assert_array_almost_equal(calc_theta, real_theta)\n    assert_array_almost_equal(calc_z, real_z)\n\n\ndef test_spherical_coordinate_projections():\n    normal = [0, 0, 1]\n    theta = get_sph_theta(coords, normal)\n    phi = get_sph_phi(coords, normal)\n    zero = np.tile(0, coords.shape[1])\n\n    # Purely radial field\n    vecs = np.array(\n        [np.sin(theta) * np.cos(phi), np.sin(theta) * np.sin(phi), np.cos(theta)]\n    )\n    assert_array_almost_equal(zero, get_sph_theta_component(vecs, theta, phi, normal))\n    assert_array_almost_equal(zero, get_sph_phi_component(vecs, phi, normal))\n\n    # Purely toroidal field\n    vecs = np.array([-np.sin(phi), np.cos(phi), zero])\n    assert_array_almost_equal(zero, get_sph_theta_component(vecs, theta, phi, normal))\n    assert_array_almost_equal(zero, get_sph_r_component(vecs, theta, phi, normal))\n\n    # Purely poloidal field\n    vecs = np.array(\n        [np.cos(theta) * np.cos(phi), np.cos(theta) * np.sin(phi), -np.sin(theta)]\n    )\n    assert_array_almost_equal(zero, get_sph_phi_component(vecs, phi, normal))\n    assert_array_almost_equal(zero, get_sph_r_component(vecs, theta, phi, normal))\n\n\ndef test_cylindrical_coordinate_projections():\n    normal = [0, 0, 1]\n    theta = get_cyl_theta(coords, normal)\n    z = get_cyl_z(coords, normal)\n    zero = np.tile(0, coords.shape[1])\n\n    # Purely radial field\n    vecs = np.array([np.cos(theta), np.sin(theta), zero])\n    assert_array_almost_equal(zero, get_cyl_theta_component(vecs, theta, normal))\n    assert_array_almost_equal(zero, get_cyl_z_component(vecs, normal))\n\n    # Purely toroidal field\n    vecs = np.array([-np.sin(theta), np.cos(theta), zero])\n    assert_array_almost_equal(zero, get_cyl_z_component(vecs, normal))\n    assert_array_almost_equal(zero, get_cyl_r_component(vecs, theta, normal))\n\n    # Purely z field\n    vecs = np.array([zero, zero, z])\n    assert_array_almost_equal(zero, get_cyl_theta_component(vecs, theta, normal))\n    assert_array_almost_equal(zero, get_cyl_r_component(vecs, theta, normal))\n"
  },
  {
    "path": "yt/utilities/tests/test_cosmology.py",
    "content": "import os\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.testing import assert_rel_equal, requires_file, requires_module\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.answer_testing.framework import data_dir_load\nfrom yt.utilities.cosmology import Cosmology\nfrom yt.utilities.on_demand_imports import _yaml as yaml\n\nlocal_dir = os.path.dirname(os.path.abspath(__file__))\n\n\ndef z_from_t_analytic(my_time, hubble_constant=0.7, omega_matter=0.3, omega_lambda=0.7):\n    \"\"\"\n    Compute the redshift from time after the big bang.  This is based on\n    Enzo's CosmologyComputeExpansionFactor.C, but altered to use physical\n    units.\n    \"\"\"\n\n    hubble_constant = YTQuantity(hubble_constant, \"100*km/s/Mpc\")\n    omega_curvature = 1.0 - omega_matter - omega_lambda\n\n    OMEGA_TOLERANCE = 1e-5\n    ETA_TOLERANCE = 1.0e-10\n\n    # Convert the time to Time * H0.\n\n    if not isinstance(my_time, YTArray):\n        my_time = YTArray(my_time, \"s\")\n\n    t0 = (my_time.in_units(\"s\") * hubble_constant.in_units(\"1/s\")).to_ndarray()\n\n    # For a flat universe with omega_matter = 1, it's easy.\n\n    if np.fabs(omega_matter - 1) < OMEGA_TOLERANCE and omega_lambda < OMEGA_TOLERANCE:\n        a = np.power(1.5 * t0, 2.0 / 3.0)\n\n    # For omega_matter < 1 and omega_lambda == 0 see\n    # Peebles 1993, eq. 13-3, 13-10.\n    # Actually, this is a little tricky since we must solve an equation\n    # of the form eta - np.sinh(eta) + x = 0..\n\n    elif omega_matter < 1 and omega_lambda < OMEGA_TOLERANCE:\n        x = 2 * t0 * np.power(1.0 - omega_matter, 1.5) / omega_matter\n\n        # Compute eta in a three step process, first from a third-order\n        # Taylor expansion of the formula above, then use that in a fifth-order\n        # approximation.  Then finally, iterate on the formula itself, solving for\n        # eta.  This works well because parts 1 & 2 are an excellent approximation\n        # when x is small and part 3 converges quickly when x is large.\n\n        eta = np.power(6 * x, 1.0 / 3.0)  # part 1\n        eta = np.power(120 * x / (20 + eta * eta), 1.0 / 3.0)  # part 2\n        mask = np.ones(eta.size, dtype=bool)\n        max_iter = 1000\n        for i in range(max_iter):  # part 3\n            eta_old = eta[mask]\n            eta[mask] = np.arcsinh(eta[mask] + x[mask])\n            mask[mask] = np.fabs(eta[mask] - eta_old) >= ETA_TOLERANCE\n            if not mask.any():\n                break\n            if i == max_iter - 1:\n                raise RuntimeError(f\"No convergence after {i} iterations.\")\n\n        # Now use eta to compute the expansion factor (eq. 13-10, part 2).\n\n        a = omega_matter / (2.0 * (1.0 - omega_matter)) * (np.cosh(eta) - 1.0)\n\n    # For flat universe, with non-zero omega_lambda, see eq. 13-20.\n\n    elif np.fabs(omega_curvature) < OMEGA_TOLERANCE and omega_lambda > OMEGA_TOLERANCE:\n        a = np.power(omega_matter / (1 - omega_matter), 1.0 / 3.0) * np.power(\n            np.sinh(1.5 * np.sqrt(1.0 - omega_matter) * t0), 2.0 / 3.0\n        )\n\n    else:\n        raise NotImplementedError\n\n    redshift = (1.0 / a) - 1.0\n\n    return redshift\n\n\ndef t_from_z_analytic(z, hubble_constant=0.7, omega_matter=0.3, omega_lambda=0.7):\n    \"\"\"\n    Compute the age of the Universe from redshift.  This is based on Enzo's\n    CosmologyComputeTimeFromRedshift.C, but altered to use physical units.\n    \"\"\"\n\n    hubble_constant = YTQuantity(hubble_constant, \"100*km/s/Mpc\")\n    omega_curvature = 1.0 - omega_matter - omega_lambda\n\n    # For a flat universe with omega_matter = 1, things are easy.\n\n    if omega_matter == 1.0 and omega_lambda == 0.0:\n        t0 = 2.0 / 3.0 / np.power(1 + z, 1.5)\n\n    # For omega_matter < 1 and omega_lambda == 0 see\n    # Peebles 1993, eq. 13-3, 13-10.\n\n    elif omega_matter < 1 and omega_lambda == 0:\n        eta = np.arccosh(1 + 2 * (1 - omega_matter) / omega_matter / (1 + z))\n        t0 = (\n            omega_matter\n            / (2 * np.power(1.0 - omega_matter, 1.5))\n            * (np.sinh(eta) - eta)\n        )\n\n    # For flat universe, with non-zero omega_lambda, see eq. 13-20.\n\n    elif np.fabs(omega_curvature) < 1.0e-3 and omega_lambda != 0:\n        t0 = (\n            2.0\n            / 3.0\n            / np.sqrt(1 - omega_matter)\n            * np.arcsinh(\n                np.sqrt((1 - omega_matter) / omega_matter) / np.power(1 + z, 1.5)\n            )\n        )\n\n    else:\n        raise NotImplementedError(f\"{hubble_constant}, {omega_matter}, {omega_lambda}\")\n\n    # Now convert from Time * H0 to time.\n\n    my_time = t0 / hubble_constant\n\n    return my_time\n\n\ndef test_z_t_roundtrip():\n    \"\"\"\n    Make sure t_from_z and z_from_t are consistent.\n\n    \"\"\"\n\n    co = Cosmology()\n    # random sample in log(a) from -6 to 6\n    my_random = np.random.RandomState(6132305)\n    la = 12 * my_random.random_sample(10000) - 6\n    z1 = 1 / np.power(10, la) - 1\n    t = co.t_from_z(z1)\n    z2 = co.z_from_t(t)\n    assert_rel_equal(z1, z2, 4)\n\n\ndef test_z_t_analytic():\n    \"\"\"\n    Test z/t conversions against analytic solutions.\n    \"\"\"\n\n    cosmos = (\n        {\"hubble_constant\": 0.7, \"omega_matter\": 0.3, \"omega_lambda\": 0.7},\n        {\"hubble_constant\": 0.7, \"omega_matter\": 1.0, \"omega_lambda\": 0.0},\n        {\"hubble_constant\": 0.7, \"omega_matter\": 0.3, \"omega_lambda\": 0.0},\n    )\n\n    for cosmo in cosmos:\n        omega_curvature = 1 - cosmo[\"omega_matter\"] - cosmo[\"omega_lambda\"]\n        co = Cosmology(omega_curvature=omega_curvature, **cosmo)\n        # random sample in log(a) from -6 to 6\n        my_random = np.random.RandomState(10132324)\n        la = 12 * my_random.random_sample(1000) - 6\n        z = 1 / np.power(10, la) - 1\n\n        t_an = t_from_z_analytic(z, **cosmo).to(\"Gyr\")\n        t_co = co.t_from_z(z).to(\"Gyr\")\n\n        assert_rel_equal(\n            t_an,\n            t_co,\n            4,\n            err_msg=f\"t_from_z does not match analytic version for cosmology {cosmo}.\",\n        )\n\n        # random sample in log(t/t0) from -3 to 1\n        t0 = np.power(10, 4 * my_random.random_sample(1000) - 3)\n        t = (t0 / co.hubble_constant).to(\"Gyr\")\n\n        z_an = z_from_t_analytic(t, **cosmo)\n        z_co = co.z_from_t(t)\n\n        # compare scale factors since z approaches 0\n        assert_rel_equal(\n            1 / (1 + z_an),\n            1 / (1 + z_co),\n            5,\n            err_msg=f\"z_from_t does not match analytic version for cosmology {cosmo}.\",\n        )\n\n\ndef test_dark_factor():\n    \"\"\"\n    Test that dark factor returns same value for when not\n    being used and when w_0 = -1 and w_z = 0.\n    \"\"\"\n\n    co = Cosmology(w_0=-1, w_a=0, use_dark_factor=False)\n\n    assert_equal(co.get_dark_factor(0), 1.0)\n    co.use_dark_factor = True\n    assert_equal(co.get_dark_factor(0), 1.0)\n\n\n@requires_module(\"yaml\")\ndef test_cosmology_calculator_answers():\n    \"\"\"\n    Test cosmology calculator functions against previously calculated values.\n    \"\"\"\n\n    fn = os.path.join(local_dir, \"cosmology_answers.yml\")\n    with open(fn) as fh:\n        data = yaml.load(fh, Loader=yaml.FullLoader)\n\n    cosmologies = data[\"cosmologies\"]\n    functions = data[\"functions\"]\n\n    for cname, copars in cosmologies.items():\n        omega_curvature = (\n            1\n            - copars[\"omega_matter\"]\n            - copars[\"omega_lambda\"]\n            - copars[\"omega_radiation\"]\n        )\n\n        cosmology = Cosmology(omega_curvature=omega_curvature, **copars)\n\n        for fname, finfo in functions.items():\n            func = getattr(cosmology, fname)\n            args = finfo.get(\"args\", [])\n            val = func(*args)\n            units = finfo.get(\"units\")\n            if units is not None:\n                val.convert_to_units(units)\n            val = float(val)\n\n            err_msg = (\n                \"{} answer has changed for {} cosmology, old: {:f}, new: {:f}.\".format(\n                    fname,\n                    cname,\n                    finfo[\"answers\"][cname],\n                    val,\n                )\n            )\n            assert_almost_equal(val, finfo[\"answers\"][cname], 10, err_msg=err_msg)\n\n\nenzotiny = \"enzo_tiny_cosmology/DD0020/DD0020\"\n\n\n@requires_module(\"h5py\")\n@requires_file(enzotiny)\ndef test_dataset_cosmology_calculator():\n    \"\"\"\n    Test datasets's cosmology calculator against standalone.\n    \"\"\"\n\n    ds = data_dir_load(enzotiny)\n\n    co = Cosmology(\n        hubble_constant=ds.hubble_constant,\n        omega_matter=ds.omega_matter,\n        omega_lambda=ds.omega_lambda,\n    )\n\n    v1 = ds.cosmology.comoving_radial_distance(1, 5).to(\"Mpccm\").v\n    v2 = co.comoving_radial_distance(1, 5).to(\"Mpccm\").v\n    assert_equal(v1, v2)\n"
  },
  {
    "path": "yt/utilities/tests/test_cython_fortran_utils.py",
    "content": "import struct\n\nimport numpy as np\nimport pytest\n\nfrom yt.utilities.cython_fortran_utils import FortranFile\n\n\ndef test_raise_error_when_file_does_not_exist():\n    with pytest.raises(FileNotFoundError):\n        FortranFile(\"/this/file/does/not/exist\")\n\n\ndef test_read(tmp_path):\n    dummy_file = tmp_path / \"test.bin\"\n    # Write a Fortran-formatted file containing one record with 4 doubles\n    # The format is a 32bit integer with value 4*sizeof(double)=32\n    # followed by 4 doubles and another 32bit integer with value 32\n    # Note that there is no memory alignment, hence the \"=\" below\n    buff = struct.pack(\"=i 4d i\", 32, 1.0, 2.0, 3.0, 4.0, 32)\n    dummy_file.write_bytes(buff)\n    with FortranFile(str(dummy_file)) as f:\n        np.testing.assert_equal(\n            f.read_vector(\"d\"),\n            [1.0, 2.0, 3.0, 4.0],\n        )\n"
  },
  {
    "path": "yt/utilities/tests/test_decompose.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_array_equal\n\nimport yt.utilities.decompose as dec\n\n\ndef test_psize_2d():\n    procs = dec.get_psize(np.array([5, 1, 7]), 6)\n    assert_array_equal(procs, np.array([3, 1, 2]))\n    procs = dec.get_psize(np.array([1, 7, 5]), 6)\n    assert_array_equal(procs, np.array([1, 2, 3]))\n    procs = dec.get_psize(np.array([7, 5, 1]), 6)\n    assert_array_equal(procs, np.array([2, 3, 1]))\n\n\ndef test_psize_3d():\n    procs = dec.get_psize(np.array([33, 35, 37]), 12)\n    assert_array_equal(procs, np.array([3, 2, 2]))\n\n\ndef test_decomposition_2d():\n    array = np.ones((7, 5, 1))\n    bbox = np.array([[-0.7, 0.0], [1.5, 2.0], [0.0, 0.7]])\n    ledge, redge, shapes, slices, _ = dec.decompose_array(\n        array.shape, np.array([2, 3, 1]), bbox\n    )\n\n    data = [array[slice] for slice in slices]\n    assert_array_equal(data[1].shape, np.array([3, 2, 1]))\n\n    gold_le = np.array(\n        [\n            [-0.7, 1.5, 0.0],\n            [-0.7, 1.6, 0.0],\n            [-0.7, 1.8, 0.0],\n            [-0.4, 1.5, 0.0],\n            [-0.4, 1.6, 0.0],\n            [-0.4, 1.8, 0.0],\n        ]\n    )\n    assert_almost_equal(ledge, gold_le, 8)\n\n    gold_re = np.array(\n        [\n            [-0.4, 1.6, 0.7],\n            [-0.4, 1.8, 0.7],\n            [-0.4, 2.0, 0.7],\n            [0.0, 1.6, 0.7],\n            [0.0, 1.8, 0.7],\n            [0.0, 2.0, 0.7],\n        ]\n    )\n    assert_almost_equal(redge, gold_re, 8)\n\n\ndef test_decomposition_3d():\n    array = np.ones((33, 35, 37))\n    bbox = np.array([[0.0, 1.0], [-1.5, 1.5], [1.0, 2.5]])\n\n    ledge, redge, shapes, slices, _ = dec.decompose_array(\n        array.shape, np.array([3, 2, 2]), bbox\n    )\n    data = [array[slice] for slice in slices]\n\n    assert_array_equal(data[0].shape, np.array([11, 17, 18]))\n\n    gold_le = np.array(\n        [\n            [0.00000, -1.50000, 1.00000],\n            [0.00000, -1.50000, 1.72973],\n            [0.00000, -0.04286, 1.00000],\n            [0.00000, -0.04286, 1.72973],\n            [0.33333, -1.50000, 1.00000],\n            [0.33333, -1.50000, 1.72973],\n            [0.33333, -0.04286, 1.00000],\n            [0.33333, -0.04286, 1.72973],\n            [0.66667, -1.50000, 1.00000],\n            [0.66667, -1.50000, 1.72973],\n            [0.66667, -0.04286, 1.00000],\n            [0.66667, -0.04286, 1.72973],\n        ]\n    )\n    assert_almost_equal(ledge, gold_le, 5)\n\n    gold_re = np.array(\n        [\n            [0.33333, -0.04286, 1.72973],\n            [0.33333, -0.04286, 2.50000],\n            [0.33333, 1.50000, 1.72973],\n            [0.33333, 1.50000, 2.50000],\n            [0.66667, -0.04286, 1.72973],\n            [0.66667, -0.04286, 2.50000],\n            [0.66667, 1.50000, 1.72973],\n            [0.66667, 1.50000, 2.50000],\n            [1.00000, -0.04286, 1.72973],\n            [1.00000, -0.04286, 2.50000],\n            [1.00000, 1.50000, 1.72973],\n            [1.00000, 1.50000, 2.50000],\n        ]\n    )\n    assert_almost_equal(redge, gold_re, 5)\n\n\ndef test_decomposition_with_cell_widths():\n    array = np.ones((33, 35, 37))\n    bbox = np.array([[0.0, 1.0], [-1.5, 1.5], [1.0, 2.5]])\n\n    # build some cell widths, rescale to match bounding box\n    cell_widths = []\n    for idim in range(3):\n        wid = bbox[idim][1] - bbox[idim][0]\n        cws = np.random.random((array.shape[idim],))\n        factor = wid / cws.sum()\n        cell_widths.append(factor * cws)\n\n    ledge, redge, _, _, widths_by_grid = dec.decompose_array(\n        array.shape, np.array([3, 2, 2]), bbox, cell_widths=cell_widths\n    )\n    for grid_id in range(len(ledge)):\n        grid_wid = redge[grid_id] - ledge[grid_id]\n        cws = widths_by_grid[grid_id]\n        cws_wid = np.array([np.sum(cws[dim]) for dim in range(3)])\n        assert_almost_equal(grid_wid, cws_wid, 5)\n"
  },
  {
    "path": "yt/utilities/tests/test_flagging_methods.py",
    "content": "import numpy as np\n\nfrom yt.testing import fake_random_ds\nfrom yt.utilities.flagging_methods import flagging_method_registry\n\n\ndef test_over_density():\n    ds = fake_random_ds(64)\n    ds.index\n    od_flag = flagging_method_registry[\"overdensity\"](0.75)\n    criterion = ds.index.grids[0][\"gas\", \"density\"] > 0.75\n    assert np.all(od_flag(ds.index.grids[0]) == criterion)\n"
  },
  {
    "path": "yt/utilities/tests/test_hierarchy_inspection.py",
    "content": "\"\"\"\nCreated on Wed Feb 18 18:24:09 2015\n\n@author: stuart\n\"\"\"\n\nfrom ..hierarchy_inspection import find_lowest_subclasses\n\n\nclass level1:\n    pass\n\n\nclass level1a:\n    pass\n\n\nclass level2(level1):\n    pass\n\n\nclass level3(level2):\n    pass\n\n\nclass level4(level3):\n    pass\n\n\ndef test_empty():\n    result = find_lowest_subclasses([])\n    assert len(result) == 0\n\n\ndef test_single():\n    result = find_lowest_subclasses([level2])\n    assert len(result) == 1\n    assert result[0] is level2\n\n\ndef test_two_classes():\n    result = find_lowest_subclasses([level1, level2])\n    assert len(result) == 1\n    assert result[0] is level2\n\n\ndef test_four_deep():\n    result = find_lowest_subclasses([level1, level2, level3, level4])\n    assert len(result) == 1\n    assert result[0] is level4\n\n\ndef test_four_deep_outoforder():\n    result = find_lowest_subclasses([level2, level3, level1, level4])\n    assert len(result) == 1\n    assert result[0] is level4\n\n\ndef test_diverging_tree():\n    result = find_lowest_subclasses([level1, level2, level3, level1a])\n    assert len(result) == 2\n    assert level1a in result and level3 in result\n\n\ndef test_without_parents():\n    result = find_lowest_subclasses([level1, level3])\n    assert len(result) == 1\n    assert result[0] is level3\n\n\ndef test_without_grandparents():\n    result = find_lowest_subclasses([level1, level4])\n    assert len(result) == 1\n    assert result[0] is level4\n"
  },
  {
    "path": "yt/utilities/tests/test_interpolators.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_almost_equal, assert_array_equal\n\nimport yt.utilities.linear_interpolators as lin\nfrom yt.testing import fake_random_ds\nfrom yt.utilities.lib.interpolators import ghost_zone_interpolate\n\n\ndef test_linear_interpolator_1d():\n    random_data = np.random.random(64)\n    fv = {\"x\": np.mgrid[0.0:1.0:64j]}\n    # evenly spaced bins\n    ufi = lin.UnilinearFieldInterpolator(random_data, (0.0, 1.0), \"x\", True)\n    assert_array_equal(ufi(fv), random_data)\n\n    # randomly spaced bins\n    size = 64\n    shift = (1.0 / size) * np.random.random(size) - (0.5 / size)\n    fv[\"x\"] += shift\n    ufi = lin.UnilinearFieldInterpolator(\n        random_data, np.linspace(0.0, 1.0, size) + shift, \"x\", True\n    )\n    assert_array_almost_equal(ufi(fv), random_data, 15)\n\n\ndef test_linear_interpolator_2d():\n    random_data = np.random.random((64, 64))\n    # evenly spaced bins\n    fv = dict(zip(\"xy\", np.mgrid[0.0:1.0:64j, 0.0:1.0:64j], strict=True))\n    bfi = lin.BilinearFieldInterpolator(random_data, (0.0, 1.0, 0.0, 1.0), \"xy\", True)\n    assert_array_equal(bfi(fv), random_data)\n\n    # randomly spaced bins\n    size = 64\n    bins = np.linspace(0.0, 1.0, size)\n    shifts = {ax: (1.0 / size) * np.random.random(size) - (0.5 / size) for ax in \"xy\"}\n    fv[\"x\"] += shifts[\"x\"][:, np.newaxis]\n    fv[\"y\"] += shifts[\"y\"]\n    bfi = lin.BilinearFieldInterpolator(\n        random_data, (bins + shifts[\"x\"], bins + shifts[\"y\"]), \"xy\", True\n    )\n    assert_array_almost_equal(bfi(fv), random_data, 15)\n\n\ndef test_linear_interpolator_3d():\n    random_data = np.random.random((64, 64, 64))\n    # evenly spaced bins\n    fv = dict(zip(\"xyz\", np.mgrid[0.0:1.0:64j, 0.0:1.0:64j, 0.0:1.0:64j], strict=True))\n    tfi = lin.TrilinearFieldInterpolator(\n        random_data, (0.0, 1.0, 0.0, 1.0, 0.0, 1.0), \"xyz\", True\n    )\n    assert_array_almost_equal(tfi(fv), random_data)\n\n    # randomly spaced bins\n    size = 64\n    bins = np.linspace(0.0, 1.0, size)\n    shifts = {ax: (1.0 / size) * np.random.random(size) - (0.5 / size) for ax in \"xyz\"}\n    fv[\"x\"] += shifts[\"x\"][:, np.newaxis, np.newaxis]\n    fv[\"y\"] += shifts[\"y\"][:, np.newaxis]\n    fv[\"z\"] += shifts[\"z\"]\n    tfi = lin.TrilinearFieldInterpolator(\n        random_data,\n        (bins + shifts[\"x\"], bins + shifts[\"y\"], bins + shifts[\"z\"]),\n        \"xyz\",\n        True,\n    )\n    assert_array_almost_equal(tfi(fv), random_data, 15)\n\n\ndef test_linear_interpolator_4d():\n    random_data = np.random.random((64, 64, 64, 64))\n    # evenly spaced bins\n    fv = dict(\n        zip(\n            \"xyzw\",\n            np.mgrid[0.0:1.0:64j, 0.0:1.0:64j, 0.0:1.0:64j, 0.0:1.0:64j],\n            strict=True,\n        )\n    )\n    tfi = lin.QuadrilinearFieldInterpolator(\n        random_data, (0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0), \"xyzw\", True\n    )\n    assert_array_almost_equal(tfi(fv), random_data)\n\n    # randomly spaced bins\n    size = 64\n    bins = np.linspace(0.0, 1.0, size)\n    shifts = {ax: (1.0 / size) * np.random.random(size) - (0.5 / size) for ax in \"xyzw\"}\n    fv[\"x\"] += shifts[\"x\"][:, np.newaxis, np.newaxis, np.newaxis]\n    fv[\"y\"] += shifts[\"y\"][:, np.newaxis, np.newaxis]\n    fv[\"z\"] += shifts[\"z\"][:, np.newaxis]\n    fv[\"w\"] += shifts[\"w\"]\n    tfi = lin.QuadrilinearFieldInterpolator(\n        random_data,\n        (\n            bins + shifts[\"x\"],\n            bins + shifts[\"y\"],\n            bins + shifts[\"z\"],\n            bins + shifts[\"w\"],\n        ),\n        \"xyzw\",\n        True,\n    )\n    assert_array_almost_equal(tfi(fv), random_data, 15)\n\n\ndef test_ghost_zone_extrapolation():\n    ds = fake_random_ds(16)\n\n    g = ds.index.grids[0]\n    vec = g.get_vertex_centered_data(\n        [(\"index\", \"x\"), (\"index\", \"y\"), (\"index\", \"z\")], no_ghost=True\n    )\n    for i, ax in enumerate(\"xyz\"):\n        xc = g[\"index\", ax]\n\n        tf = lin.TrilinearFieldInterpolator(\n            xc,\n            (\n                g.LeftEdge[0] + g.dds[0] / 2.0,\n                g.RightEdge[0] - g.dds[0] / 2.0,\n                g.LeftEdge[1] + g.dds[1] / 2.0,\n                g.RightEdge[1] - g.dds[1] / 2.0,\n                g.LeftEdge[2] + g.dds[2] / 2.0,\n                g.RightEdge[2] - g.dds[2] / 2.0,\n            ),\n            [\"x\", \"y\", \"z\"],\n            truncate=True,\n        )\n\n        lx, ly, lz = np.mgrid[\n            g.LeftEdge[0] : g.RightEdge[0] : (g.ActiveDimensions[0] + 1) * 1j,\n            g.LeftEdge[1] : g.RightEdge[1] : (g.ActiveDimensions[1] + 1) * 1j,\n            g.LeftEdge[2] : g.RightEdge[2] : (g.ActiveDimensions[2] + 1) * 1j,\n        ]\n        xi = tf({\"x\": lx, \"y\": ly, \"z\": lz})\n\n        xz = np.zeros(g.ActiveDimensions + 1)\n        ghost_zone_interpolate(\n            1,\n            xc,\n            np.array([0.5, 0.5, 0.5], dtype=\"f8\"),\n            xz,\n            np.array([0.0, 0.0, 0.0], dtype=\"f8\"),\n        )\n\n        ii = (lx, ly, lz)[i]\n        assert_array_equal(ii, vec[\"index\", ax])\n        assert_array_equal(ii, xi)\n        assert_array_equal(ii, xz)\n\n\ndef test_get_vertex_centered_data():\n    ds = fake_random_ds(16)\n    g = ds.index.grids[0]\n    g.get_vertex_centered_data([(\"gas\", \"density\")], no_ghost=True)\n"
  },
  {
    "path": "yt/utilities/tests/test_minimal_representation.py",
    "content": "import os.path\n\nfrom numpy.testing import assert_equal, assert_raises\n\nimport yt\nfrom yt.config import ytcfg\nfrom yt.testing import requires_file, requires_module\n\nG30 = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\nold_serialize = None\n\n\ndef setup_module():\n    global old_serialize\n    old_serialize = ytcfg.get(\"yt\", \"serialize\")\n    ytcfg[\"yt\", \"serialize\"] = True\n\n\ndef teardown_module():\n    ytcfg[\"yt\", \"serialize\"] = old_serialize\n\n\n@requires_module(\"h5py\")\n@requires_file(G30)\ndef test_store():\n    ds = yt.load(G30)\n    store = ds.parameter_filename + \".yt\"\n    field = \"density\"\n    if os.path.isfile(store):\n        os.remove(store)\n\n    proj1 = ds.proj(field, \"z\")\n    sp = ds.sphere(ds.domain_center, (4, \"kpc\"))\n    proj2 = ds.proj(field, \"z\", data_source=sp)\n\n    proj1_c = ds.proj(field, \"z\")\n    assert_equal(proj1[field], proj1_c[field])\n\n    proj2_c = ds.proj(field, \"z\", data_source=sp)\n    assert_equal(proj2[field], proj2_c[field])\n\n    def fail_for_different_method():\n        proj2_c = ds.proj(field, \"z\", data_source=sp, method=\"max\")\n        assert_equal(proj2[field], proj2_c[field])\n\n    # A note here: a unyt.exceptions.UnitOperationError is raised\n    # and caught by numpy, which reraises a ValueError\n    assert_raises(ValueError, fail_for_different_method)\n\n    def fail_for_different_source():\n        sp = ds.sphere(ds.domain_center, (2, \"kpc\"))\n        proj2_c = ds.proj(field, \"z\", data_source=sp, method=\"integrate\")\n        assert_equal(proj2_c[field], proj2[field])\n\n    assert_raises(AssertionError, fail_for_different_source)\n"
  },
  {
    "path": "yt/utilities/tests/test_on_demand_imports.py",
    "content": "import pytest\n\nfrom yt.utilities.on_demand_imports import OnDemand, safe_import\n\n\ndef test_access_available_module():\n    class os_imports(OnDemand):\n        @safe_import\n        def path(self):\n            from os import path\n\n            return path\n\n    _os = os_imports()\n\n    _os.path.join(\"eggs\", \"saussage\")\n\n\ndef test_access_unavailable_module():\n    class Bacon_imports(OnDemand):\n        @safe_import\n        def spam(self):\n            from Bacon import spam\n\n            return spam\n\n    _bacon = Bacon_imports()\n    with pytest.raises(\n        ImportError,\n        match=r\"No module named 'Bacon'\",\n    ) as excinfo:\n        _bacon.spam()\n\n    # yt should add information to the original error message\n    # but this done slightly differently in Python>=3.11\n    # (using exception notes), so we can't just match the error message\n    # directly. Instead this implements a Python-version agnostic check\n    # that the user-visible error message is what we expect.\n    complete_error_message = excinfo.exconly()\n    assert complete_error_message == (\n        \"ModuleNotFoundError: No module named 'Bacon'\\n\"\n        \"Something went wrong while trying to lazy-import Bacon. \"\n        \"Please make sure that Bacon is properly installed.\\n\"\n        \"If the problem persists, please file an issue at \"\n        \"https://github.com/yt-project/yt/issues/new\"\n    )\n\n\ndef test_class_invalidation():\n    with pytest.raises(\n        TypeError, match=\"class .*'s name needs to be suffixed '_imports'\"\n    ):\n\n        class Bacon(OnDemand):\n            pass\n\n\ndef test_base_class_instanciation():\n    with pytest.raises(\n        TypeError, match=\"The OnDemand base class cannot be instantiated.\"\n    ):\n        OnDemand()\n"
  },
  {
    "path": "yt/utilities/tests/test_particle_generator.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt.loaders import load_uniform_grid\nfrom yt.units._numpy_wrapper_functions import uconcatenate\nfrom yt.utilities.particle_generator import (\n    FromListParticleGenerator,\n    LatticeParticleGenerator,\n    WithDensityParticleGenerator,\n)\n\n\ndef test_particle_generator():\n    # First generate our dataset\n    domain_dims = (32, 32, 32)\n    dens = np.zeros(domain_dims) + 0.1\n    temp = 4.0 * np.ones(domain_dims)\n    fields = {\"density\": (dens, \"code_mass/code_length**3\"), \"temperature\": (temp, \"K\")}\n    ds = load_uniform_grid(fields, domain_dims, 1.0)\n\n    # Now generate particles from density\n\n    field_list = [\n        (\"io\", \"particle_position_x\"),\n        (\"io\", \"particle_position_y\"),\n        (\"io\", \"particle_position_z\"),\n        (\"io\", \"particle_index\"),\n        (\"io\", \"particle_gas_density\"),\n    ]\n    num_particles = 10000\n    field_dict = {(\"gas\", \"density\"): (\"io\", \"particle_gas_density\")}\n    sphere = ds.sphere(ds.domain_center, 0.45)\n\n    particles1 = WithDensityParticleGenerator(ds, sphere, num_particles, field_list)\n    particles1.assign_indices()\n    particles1.map_grid_fields_to_particles(field_dict)\n\n    # Test to make sure we ended up with the right number of particles per grid\n    particles1.apply_to_stream()\n    particles_per_grid1 = [grid.NumberOfParticles for grid in ds.index.grids]\n    assert_equal(particles_per_grid1, particles1.NumberOfParticles)\n    particles_per_grid1 = [\n        len(grid[\"all\", \"particle_position_x\"]) for grid in ds.index.grids\n    ]\n    assert_equal(particles_per_grid1, particles1.NumberOfParticles)\n\n    tags = uconcatenate([grid[\"all\", \"particle_index\"] for grid in ds.index.grids])\n    assert np.unique(tags).size == num_particles\n\n    del tags\n\n    # Set up a lattice of particles\n    pdims = np.array([32, 32, 32])\n\n    def new_indices():\n        # We just add new indices onto the existing ones\n        return np.arange(np.prod(pdims)) + num_particles\n\n    le = np.array([0.25, 0.25, 0.25])\n    re = np.array([0.75, 0.75, 0.75])\n\n    particles2 = LatticeParticleGenerator(ds, pdims, le, re, field_list)\n    particles2.assign_indices(function=new_indices)\n    particles2.map_grid_fields_to_particles(field_dict)\n\n    # Test lattice positions\n    xpos = np.unique(particles2[\"io\", \"particle_position_x\"])\n    ypos = np.unique(particles2[\"io\", \"particle_position_y\"])\n    zpos = np.unique(particles2[\"io\", \"particle_position_z\"])\n\n    xpred = np.linspace(le[0], re[0], num=pdims[0], endpoint=True)\n    ypred = np.linspace(le[1], re[1], num=pdims[1], endpoint=True)\n    zpred = np.linspace(le[2], re[2], num=pdims[2], endpoint=True)\n\n    assert_almost_equal(xpos, xpred)\n    assert_almost_equal(ypos, ypred)\n    assert_almost_equal(zpos, zpred)\n\n    del xpos, ypos, zpos\n    del xpred, ypred, zpred\n\n    # Test the number of particles again\n    particles2.apply_to_stream()\n    particles_per_grid2 = [grid.NumberOfParticles for grid in ds.index.grids]\n    assert_equal(\n        particles_per_grid2, particles1.NumberOfParticles + particles2.NumberOfParticles\n    )\n\n    [grid.field_data.clear() for grid in ds.index.grids]\n    particles_per_grid2 = [\n        len(grid[\"all\", \"particle_position_x\"]) for grid in ds.index.grids\n    ]\n    assert_equal(\n        particles_per_grid2, particles1.NumberOfParticles + particles2.NumberOfParticles\n    )\n\n    # Test the uniqueness of tags\n    tags = np.concatenate([grid[\"all\", \"particle_index\"] for grid in ds.index.grids])\n    tags.sort()\n    assert_equal(tags, np.arange(np.prod(pdims) + num_particles))\n\n    del tags\n\n    # Now dump all of these particle fields out into a dict\n    pdata = {}\n    dd = ds.all_data()\n    for field in field_list:\n        pdata[field] = dd[field]\n\n    # Test the \"from-list\" generator and particle field overwrite\n    num_particles3 = num_particles + np.prod(pdims)\n    particles3 = FromListParticleGenerator(ds, num_particles3, pdata)\n    particles3.apply_to_stream(overwrite=True)\n\n    # Test the number of particles again\n    particles_per_grid3 = [grid.NumberOfParticles for grid in ds.index.grids]\n    assert_equal(\n        particles_per_grid3, particles1.NumberOfParticles + particles2.NumberOfParticles\n    )\n    particles_per_grid2 = [\n        len(grid[\"all\", \"particle_position_z\"]) for grid in ds.index.grids\n    ]\n    assert_equal(\n        particles_per_grid3, particles1.NumberOfParticles + particles2.NumberOfParticles\n    )\n    assert_equal(particles_per_grid2, particles_per_grid3)\n\n    # Test adding in particles with a different particle type\n\n    num_star_particles = 20000\n    pdata2 = {\n        (\"star\", \"particle_position_x\"): np.random.uniform(size=num_star_particles),\n        (\"star\", \"particle_position_y\"): np.random.uniform(size=num_star_particles),\n        (\"star\", \"particle_position_z\"): np.random.uniform(size=num_star_particles),\n    }\n\n    particles4 = FromListParticleGenerator(ds, num_star_particles, pdata2, ptype=\"star\")\n    particles4.apply_to_stream()\n\n    dd = ds.all_data()\n    assert dd[\"star\", \"particle_position_x\"].size == num_star_particles\n    assert dd[\"io\", \"particle_position_x\"].size == num_particles3\n    assert dd[\"all\", \"particle_position_x\"].size == num_star_particles + num_particles3\n\n    del pdata\n    del pdata2\n    del ds\n    del particles1\n    del particles2\n    del particles4\n    del fields\n    del dens\n    del temp\n"
  },
  {
    "path": "yt/utilities/tests/test_periodic_table.py",
    "content": "from numpy.testing import assert_equal\n\nfrom yt.utilities.periodic_table import _elements, periodic_table\n\n\ndef test_element_accuracy():\n    for num, w, name, sym in _elements:\n        e0 = periodic_table[num]\n        e1 = periodic_table[name]\n        e2 = periodic_table[sym]\n        # If num == -1, then we are in one of the things like Deuterium or El\n        # that are not elements by themselves.\n        if num == -1:\n            e0 = e1\n        assert_equal(id(e0), id(e1))\n        assert_equal(id(e0), id(e2))\n        assert_equal(e0.num, num)\n        assert_equal(e0.weight, w)\n        assert_equal(e0.name, name)\n        assert_equal(e0.symbol, sym)\n"
  },
  {
    "path": "yt/utilities/tests/test_periodicity.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal\n\nfrom yt.testing import fake_random_ds\nfrom yt.utilities.math_utils import euclidean_dist, periodic_dist\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_periodicity():\n    # First test the simple case were we find the distance between two points\n    a = [0.1, 0.1, 0.1]\n    b = [0.9, 0.9, 0.9]\n    period = 1.0\n    dist = periodic_dist(a, b, period)\n    assert_almost_equal(dist, 0.34641016151377535)\n    dist = periodic_dist(a, b, period, (True, False, False))\n    assert_almost_equal(dist, 1.1489125293076059)\n    dist = periodic_dist(a, b, period, (False, True, False))\n    assert_almost_equal(dist, 1.1489125293076059)\n    dist = periodic_dist(a, b, period, (False, False, True))\n    assert_almost_equal(dist, 1.1489125293076059)\n    dist = periodic_dist(a, b, period, (True, True, False))\n    assert_almost_equal(dist, 0.84852813742385713)\n    dist = periodic_dist(a, b, period, (True, False, True))\n    assert_almost_equal(dist, 0.84852813742385713)\n    dist = periodic_dist(a, b, period, (False, True, True))\n    assert_almost_equal(dist, 0.84852813742385713)\n    dist = euclidean_dist(a, b)\n    assert_almost_equal(dist, 1.3856406460551021)\n\n    # Now test the more complicated cases where we're calculating radii based\n    # on data objects\n    ds = fake_random_ds(64)\n\n    # First we test flattened data\n    data = ds.all_data()\n    positions = np.array([data[\"index\", ax] for ax in \"xyz\"])\n    c = [0.1, 0.1, 0.1]\n    n_tup = tuple(1 for i in range(positions.ndim - 1))\n    center = np.tile(\n        np.reshape(np.array(c), (positions.shape[0],) + n_tup),\n        (1,) + positions.shape[1:],\n    )\n\n    dist = periodic_dist(positions, center, period, ds.periodicity)\n    assert_almost_equal(dist.min(), 0.00270632938683)\n    assert_almost_equal(dist.max(), 0.863319074398)\n\n    dist = euclidean_dist(positions, center)\n    assert_almost_equal(dist.min(), 0.00270632938683)\n    assert_almost_equal(dist.max(), 1.54531407988)\n\n    # Then grid-like data\n    data = ds.index.grids[0]\n    positions = np.array([data[\"index\", ax] for ax in \"xyz\"])\n    c = [0.1, 0.1, 0.1]\n    n_tup = tuple(1 for i in range(positions.ndim - 1))\n    center = np.tile(\n        np.reshape(np.array(c), (positions.shape[0],) + n_tup),\n        (1,) + positions.shape[1:],\n    )\n\n    dist = periodic_dist(positions, center, period, ds.periodicity)\n    assert_almost_equal(dist.min(), 0.00270632938683)\n    assert_almost_equal(dist.max(), 0.863319074398)\n\n    dist = euclidean_dist(positions, center)\n    assert_almost_equal(dist.min(), 0.00270632938683)\n    assert_almost_equal(dist.max(), 1.54531407988)\n"
  },
  {
    "path": "yt/utilities/tests/test_selectors.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_array_less, assert_equal\n\nfrom yt.testing import fake_random_ds\nfrom yt.utilities.math_utils import periodic_dist\n\n\ndef setup_module():\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_point_selector():\n    # generate fake amr data\n    bbox = np.array([[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0]])\n    ds = fake_random_ds(16, nprocs=7, bbox=bbox)\n    assert all(ds.periodicity)\n\n    dd = ds.all_data()\n    positions = np.array([dd[\"index\", ax] for ax in \"xyz\"]).T\n    delta = 0.5 * np.array([dd[\"index\", f\"d{ax}\"] for ax in \"xyz\"]).T\n    # ensure cell centers and corners always return one and\n    # only one point object\n    for p in positions:\n        data = ds.point(p)\n        assert_equal(data[\"index\", \"ones\"].shape[0], 1)\n    for p in positions - delta:\n        data = ds.point(p)\n        assert_equal(data[\"index\", \"ones\"].shape[0], 1)\n    for p in positions + delta:\n        data = ds.point(p)\n        assert_equal(data[\"index\", \"ones\"].shape[0], 1)\n\n\ndef test_sphere_selector():\n    # generate fake data with a number of non-cubical grids\n    ds = fake_random_ds(64, nprocs=51)\n    assert all(ds.periodicity)\n\n    # aligned tests\n    spheres = [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5], [1.0, 1.0, 1.0], [0.25, 0.75, 0.25]]\n\n    for center in spheres:\n        data = ds.sphere(center, 0.25)\n        # WARNING: this value has not be externally verified\n        dd = ds.all_data()\n        dd.set_field_parameter(\"center\", ds.arr(center, \"code_length\"))\n        n_outside = (dd[\"index\", \"radius\"] >= 0.25).sum()\n        assert_equal(\n            data[\"index\", \"radius\"].size + n_outside, dd[\"index\", \"radius\"].size\n        )\n\n        positions = np.array([data[\"index\", ax] for ax in \"xyz\"])\n        centers = (\n            np.tile(data.center, data[\"index\", \"x\"].shape[0])\n            .reshape(data[\"index\", \"x\"].shape[0], 3)\n            .transpose()\n        )\n        dist = periodic_dist(\n            positions,\n            centers,\n            ds.domain_right_edge - ds.domain_left_edge,\n            ds.periodicity,\n        )\n        # WARNING: this value has not been externally verified\n        assert_array_less(dist, 0.25)\n\n\ndef test_ellipsoid_selector():\n    # generate fake data with a number of non-cubical grids\n    ds = fake_random_ds(64, nprocs=51)\n    assert all(ds.periodicity)\n\n    ellipsoids = [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5], [1.0, 1.0, 1.0], [0.25, 0.75, 0.25]]\n\n    # spherical ellipsoid tests\n    ratios = 3 * [0.25]\n    for center in ellipsoids:\n        data = ds.ellipsoid(\n            center, ratios[0], ratios[1], ratios[2], np.array([1.0, 0.0, 0.0]), 0.0\n        )\n        data.get_data()\n\n        dd = ds.all_data()\n        dd.set_field_parameter(\"center\", ds.arr(center, \"code_length\"))\n        n_outside = (dd[\"index\", \"radius\"] >= ratios[0]).sum()\n        assert_equal(\n            data[\"index\", \"radius\"].size + n_outside, dd[\"index\", \"radius\"].size\n        )\n\n        positions = np.array([data[\"index\", ax] for ax in \"xyz\"])\n        centers = (\n            np.tile(data.center, data.shape[0]).reshape(data.shape[0], 3).transpose()\n        )\n        dist = periodic_dist(\n            positions,\n            centers,\n            ds.domain_right_edge - ds.domain_left_edge,\n            ds.periodicity,\n        )\n        # WARNING: this value has not been externally verified\n        assert_array_less(dist, ratios[0])\n\n    # aligned ellipsoid tests\n    ratios = [0.25, 0.1, 0.1]\n    for center in ellipsoids:\n        data = ds.ellipsoid(\n            center, ratios[0], ratios[1], ratios[2], np.array([1.0, 0.0, 0.0]), 0.0\n        )\n\n        # hack to compute elliptic distance\n        dist2 = np.zeros(data[\"index\", \"ones\"].shape[0])\n        for i, ax in enumerate((\"index\", k) for k in \"xyz\"):\n            positions = np.zeros((3, data[\"index\", \"ones\"].shape[0]))\n            positions[i, :] = data[ax]\n            centers = np.zeros((3, data[\"index\", \"ones\"].shape[0]))\n            centers[i, :] = center[i]\n            dist2 += (\n                periodic_dist(\n                    positions,\n                    centers,\n                    ds.domain_right_edge - ds.domain_left_edge,\n                    ds.periodicity,\n                )\n                / ratios[i]\n            ) ** 2\n        # WARNING: this value has not been externally verified\n        assert_array_less(dist2, 1.0)\n\n\ndef test_slice_selector():\n    # generate fake data with a number of non-cubical grids\n    ds = fake_random_ds(64, nprocs=51)\n    assert all(ds.periodicity)\n\n    for i, d in enumerate((\"index\", k) for k in \"xyz\"):\n        for coord in np.arange(0.0, 1.0, 0.1):\n            data = ds.slice(i, coord)\n            data.get_data()\n            v = data[d].to_ndarray()\n            assert_equal(data.shape[0], 64**2)\n            assert_equal(data[\"index\", \"ones\"].shape[0], 64**2)\n            assert_array_less(np.abs(v - coord), 1.0 / 128.0 + 1e-6)\n\n\ndef test_cutting_plane_selector():\n    # generate fake data with a number of non-cubical grids\n    ds = fake_random_ds(64, nprocs=51)\n    assert all(ds.periodicity)\n\n    # test cutting plane against orthogonal plane\n    for i in range(3):\n        norm = np.zeros(3)\n        norm[i] = 1.0\n\n        for coord in np.arange(0, 1.0, 0.1):\n            center = np.zeros(3)\n            center[i] = coord\n\n            data = ds.slice(i, coord)\n            data.get_data()\n            data2 = ds.cutting(norm, center)\n            data2.get_data()\n\n            assert data.shape[0] == data2.shape[0]\n\n            cells1 = np.lexsort(\n                (data[\"index\", \"x\"], data[\"index\", \"y\"], data[\"index\", \"z\"])\n            )\n            cells2 = np.lexsort(\n                (data2[\"index\", \"x\"], data2[\"index\", \"y\"], data2[\"index\", \"z\"])\n            )\n            for d2 in \"xyz\":\n                assert_equal(data[\"index\", d2][cells1], data2[\"index\", d2][cells2])\n\n\n# def test_region_selector():\n#\n# def test_disk_selector():\n#\n# def test_orthoray_selector():\n#\n# def test_ray_selector():\n"
  },
  {
    "path": "yt/utilities/tests/test_set_log_level.py",
    "content": "from numpy.testing import assert_raises\n\nfrom yt.utilities.logger import set_log_level\n\nold_level = None\n\n\ndef setup_module():\n    global old_level\n    from yt.utilities.logger import ytLogger\n\n    old_level = ytLogger.level\n\n\ndef teardown_module():\n    set_log_level(old_level)\n\n\ndef test_valid_level():\n    # test a subset of valid entries to cover\n    # - case-insensitivity\n    # - integer values\n    # - \"all\" alias, which isn't standard\n    for lvl in (\"all\", \"ALL\", 10, 42, \"info\", \"warning\", \"ERROR\", \"CRITICAL\"):\n        set_log_level(lvl)\n\n\ndef test_invalid_level():\n    # these are the exceptions raised by logging.Logger.setLog\n    # since they are perfectly clear and readable, we check that nothing else\n    # happens in the wrapper\n    assert_raises(TypeError, set_log_level, 1.5)\n    assert_raises(ValueError, set_log_level, \"invalid_level\")\n"
  },
  {
    "path": "yt/utilities/tree_container.py",
    "content": "class TreeContainer:\n    r\"\"\"A recursive data container for things like merger trees and\n    clump-finder trees.\n\n    \"\"\"\n\n    _child_attr = \"children\"\n\n    def __init__(self):\n        setattr(self, self._child_attr, None)\n\n    def __iter__(self):\n        yield self\n        children = getattr(self, self._child_attr)\n        if children is None:\n            return\n        for child in children:\n            yield from child\n"
  },
  {
    "path": "yt/utilities/voropp.pyx",
    "content": "\"\"\"\nWrapping code for voro++\n\n\n\n\"\"\"\n\n\ncimport libcpp\nfrom cython.operator cimport dereference as deref\n\nimport numpy as np\n\ncimport cython\ncimport numpy as np\n\n\ncdef extern from \"voro++.hh\" namespace \"voro\":\n    cdef cppclass c_loop_all\n\n    cdef cppclass voronoicell:\n        double volume()\n\n    cdef cppclass container:\n        container(double xmin, double xmax, double ymin, double ymax,\n                  double zmin, double zmax, int nx, int ny, int nz,\n                  libcpp.bool xper, libcpp.bool yper, libcpp.bool zper, int alloc)\n        void put(int n, double x, double y, double z)\n        void store_cell_volumes(double *vols)\n        int compute_cell(voronoicell c, c_loop_all vl)\n        double sum_cell_volumes()\n\n    cdef cppclass c_loop_all:\n        c_loop_all(container &con)\n        int inc()\n        int start()\n\ncdef class VoronoiVolume:\n    cdef container *my_con\n    cdef public int npart\n    def __init__(self, xi, yi, zi, left_edge, right_edge):\n        self.my_con = new container(left_edge[0], right_edge[0],\n                                    left_edge[1], right_edge[1],\n                                    left_edge[2], right_edge[2],\n                                    xi, yi, zi, False, False, False, 8)\n        self.npart = 0\n\n    def __dealloc__(self):\n        del self.my_con\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def add_array(self, np.ndarray[np.float64_t, ndim=1] xpos,\n                        np.ndarray[np.float64_t, ndim=1] ypos,\n                        np.ndarray[np.float64_t, ndim=1] zpos):\n        cdef int i\n        for i in range(xpos.shape[0]):\n            self.my_con.put(self.npart, xpos[i], ypos[i], zpos[i])\n            self.npart += 1\n\n    @cython.boundscheck(False)\n    @cython.wraparound(False)\n    def get_volumes(self):\n        cdef np.ndarray vol = np.zeros(self.npart, 'double')\n        #self.my_con.store_cell_volumes(vdouble)\n        cdef c_loop_all *vl = new c_loop_all(deref(self.my_con))\n        cdef voronoicell c\n        if not vl.start(): return\n        cdef int i = 0\n        while 1:\n            if self.my_con.compute_cell(c, deref(vl)):\n                vol[i] = c.volume()\n            if not vl.inc(): break\n            i += 1\n        del vl\n        return vol\n"
  },
  {
    "path": "yt/visualization/__init__.py",
    "content": "\"\"\"\nRaven\n=====\n\nRaven is the plotting interface, with support for several\ndifferent engines.  Well, two for now, but maybe more later.\nWho knows?\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/visualization/_colormap_data.py",
    "content": "### Auto-generated colormap tables, taken from Matplotlib ###\nimport numpy as np\nfrom numpy import array\n\ncolor_map_luts = {}\n\n### IDL colormap 0 :: B-W LINEAR ###\n\ncolor_map_luts['idl00'] = \\\n   (\narray([  0.0000000, 0.0039062, 0.0078125, 0.0117188, 0.0156250, 0.0195312,\n         0.0234375, 0.0273438, 0.0312500, 0.0351562, 0.0390625, 0.0429688,\n         0.0468750, 0.0507812, 0.0546875, 0.0585938, 0.0625000, 0.0664062,\n         0.0703125, 0.0742188, 0.0781250, 0.0820312, 0.0859375, 0.0898438,\n         0.0937500, 0.0976562, 0.1015625, 0.1054688, 0.1093750, 0.1132812,\n         0.1171875, 0.1210938, 0.1250000, 0.1289062, 0.1328125, 0.1367188,\n         0.1406250, 0.1445312, 0.1484375, 0.1523438, 0.1562500, 0.1601562,\n         0.1640625, 0.1679688, 0.1718750, 0.1757812, 0.1796875, 0.1835938,\n         0.1875000, 0.1914062, 0.1953125, 0.1992188, 0.2031250, 0.2070312,\n         0.2109375, 0.2148438, 0.2187500, 0.2226562, 0.2265625, 0.2304688,\n         0.2343750, 0.2382812, 0.2421875, 0.2460938, 0.2500000, 0.2539062,\n         0.2578125, 0.2617188, 0.2656250, 0.2695312, 0.2734375, 0.2773438,\n         0.2812500, 0.2851562, 0.2890625, 0.2929688, 0.2968750, 0.3007812,\n         0.3046875, 0.3085938, 0.3125000, 0.3164062, 0.3203125, 0.3242188,\n         0.3281250, 0.3320312, 0.3359375, 0.3398438, 0.3437500, 0.3476562,\n         0.3515625, 0.3554688, 0.3593750, 0.3632812, 0.3671875, 0.3710938,\n         0.3750000, 0.3789062, 0.3828125, 0.3867188, 0.3906250, 0.3945312,\n         0.3984375, 0.4023438, 0.4062500, 0.4101562, 0.4140625, 0.4179688,\n         0.4218750, 0.4257812, 0.4296875, 0.4335938, 0.4375000, 0.4414062,\n         0.4453125, 0.4492188, 0.4531250, 0.4570312, 0.4609375, 0.4648438,\n         0.4687500, 0.4726562, 0.4765625, 0.4804688, 0.4843750, 0.4882812,\n         0.4921875, 0.4960938, 0.5000000, 0.5039062, 0.5078125, 0.5117188,\n         0.5156250, 0.5195312, 0.5234375, 0.5273438, 0.5312500, 0.5351562,\n         0.5390625, 0.5429688, 0.5468750, 0.5507812, 0.5546875, 0.5585938,\n         0.5625000, 0.5664062, 0.5703125, 0.5742188, 0.5781250, 0.5820312,\n         0.5859375, 0.5898438, 0.5937500, 0.5976562, 0.6015625, 0.6054688,\n         0.6093750, 0.6132812, 0.6171875, 0.6210938, 0.6250000, 0.6289062,\n         0.6328125, 0.6367188, 0.6406250, 0.6445312, 0.6484375, 0.6523438,\n         0.6562500, 0.6601562, 0.6640625, 0.6679688, 0.6718750, 0.6757812,\n         0.6796875, 0.6835938, 0.6875000, 0.6914062, 0.6953125, 0.6992188,\n         0.7031250, 0.7070312, 0.7109375, 0.7148438, 0.7187500, 0.7226562,\n         0.7265625, 0.7304688, 0.7343750, 0.7382812, 0.7421875, 0.7460938,\n         0.7500000, 0.7539062, 0.7578125, 0.7617188, 0.7656250, 0.7695312,\n         0.7734375, 0.7773438, 0.7812500, 0.7851562, 0.7890625, 0.7929688,\n         0.7968750, 0.8007812, 0.8046875, 0.8085938, 0.8125000, 0.8164062,\n         0.8203125, 0.8242188, 0.8281250, 0.8320312, 0.8359375, 0.8398438,\n         0.8437500, 0.8476562, 0.8515625, 0.8554688, 0.8593750, 0.8632812,\n         0.8671875, 0.8710938, 0.8750000, 0.8789062, 0.8828125, 0.8867188,\n         0.8906250, 0.8945312, 0.8984375, 0.9023438, 0.9062500, 0.9101562,\n         0.9140625, 0.9179688, 0.9218750, 0.9257812, 0.9296875, 0.9335938,\n         0.9375000, 0.9414062, 0.9453125, 0.9492188, 0.9531250, 0.9570312,\n         0.9609375, 0.9648438, 0.9687500, 0.9726562, 0.9765625, 0.9804688,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.0039062, 0.0078125, 0.0117188, 0.0156250, 0.0195312,\n         0.0234375, 0.0273438, 0.0312500, 0.0351562, 0.0390625, 0.0429688,\n         0.0468750, 0.0507812, 0.0546875, 0.0585938, 0.0625000, 0.0664062,\n         0.0703125, 0.0742188, 0.0781250, 0.0820312, 0.0859375, 0.0898438,\n         0.0937500, 0.0976562, 0.1015625, 0.1054688, 0.1093750, 0.1132812,\n         0.1171875, 0.1210938, 0.1250000, 0.1289062, 0.1328125, 0.1367188,\n         0.1406250, 0.1445312, 0.1484375, 0.1523438, 0.1562500, 0.1601562,\n         0.1640625, 0.1679688, 0.1718750, 0.1757812, 0.1796875, 0.1835938,\n         0.1875000, 0.1914062, 0.1953125, 0.1992188, 0.2031250, 0.2070312,\n         0.2109375, 0.2148438, 0.2187500, 0.2226562, 0.2265625, 0.2304688,\n         0.2343750, 0.2382812, 0.2421875, 0.2460938, 0.2500000, 0.2539062,\n         0.2578125, 0.2617188, 0.2656250, 0.2695312, 0.2734375, 0.2773438,\n         0.2812500, 0.2851562, 0.2890625, 0.2929688, 0.2968750, 0.3007812,\n         0.3046875, 0.3085938, 0.3125000, 0.3164062, 0.3203125, 0.3242188,\n         0.3281250, 0.3320312, 0.3359375, 0.3398438, 0.3437500, 0.3476562,\n         0.3515625, 0.3554688, 0.3593750, 0.3632812, 0.3671875, 0.3710938,\n         0.3750000, 0.3789062, 0.3828125, 0.3867188, 0.3906250, 0.3945312,\n         0.3984375, 0.4023438, 0.4062500, 0.4101562, 0.4140625, 0.4179688,\n         0.4218750, 0.4257812, 0.4296875, 0.4335938, 0.4375000, 0.4414062,\n         0.4453125, 0.4492188, 0.4531250, 0.4570312, 0.4609375, 0.4648438,\n         0.4687500, 0.4726562, 0.4765625, 0.4804688, 0.4843750, 0.4882812,\n         0.4921875, 0.4960938, 0.5000000, 0.5039062, 0.5078125, 0.5117188,\n         0.5156250, 0.5195312, 0.5234375, 0.5273438, 0.5312500, 0.5351562,\n         0.5390625, 0.5429688, 0.5468750, 0.5507812, 0.5546875, 0.5585938,\n         0.5625000, 0.5664062, 0.5703125, 0.5742188, 0.5781250, 0.5820312,\n         0.5859375, 0.5898438, 0.5937500, 0.5976562, 0.6015625, 0.6054688,\n         0.6093750, 0.6132812, 0.6171875, 0.6210938, 0.6250000, 0.6289062,\n         0.6328125, 0.6367188, 0.6406250, 0.6445312, 0.6484375, 0.6523438,\n         0.6562500, 0.6601562, 0.6640625, 0.6679688, 0.6718750, 0.6757812,\n         0.6796875, 0.6835938, 0.6875000, 0.6914062, 0.6953125, 0.6992188,\n         0.7031250, 0.7070312, 0.7109375, 0.7148438, 0.7187500, 0.7226562,\n         0.7265625, 0.7304688, 0.7343750, 0.7382812, 0.7421875, 0.7460938,\n         0.7500000, 0.7539062, 0.7578125, 0.7617188, 0.7656250, 0.7695312,\n         0.7734375, 0.7773438, 0.7812500, 0.7851562, 0.7890625, 0.7929688,\n         0.7968750, 0.8007812, 0.8046875, 0.8085938, 0.8125000, 0.8164062,\n         0.8203125, 0.8242188, 0.8281250, 0.8320312, 0.8359375, 0.8398438,\n         0.8437500, 0.8476562, 0.8515625, 0.8554688, 0.8593750, 0.8632812,\n         0.8671875, 0.8710938, 0.8750000, 0.8789062, 0.8828125, 0.8867188,\n         0.8906250, 0.8945312, 0.8984375, 0.9023438, 0.9062500, 0.9101562,\n         0.9140625, 0.9179688, 0.9218750, 0.9257812, 0.9296875, 0.9335938,\n         0.9375000, 0.9414062, 0.9453125, 0.9492188, 0.9531250, 0.9570312,\n         0.9609375, 0.9648438, 0.9687500, 0.9726562, 0.9765625, 0.9804688,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.0039062, 0.0078125, 0.0117188, 0.0156250, 0.0195312,\n         0.0234375, 0.0273438, 0.0312500, 0.0351562, 0.0390625, 0.0429688,\n         0.0468750, 0.0507812, 0.0546875, 0.0585938, 0.0625000, 0.0664062,\n         0.0703125, 0.0742188, 0.0781250, 0.0820312, 0.0859375, 0.0898438,\n         0.0937500, 0.0976562, 0.1015625, 0.1054688, 0.1093750, 0.1132812,\n         0.1171875, 0.1210938, 0.1250000, 0.1289062, 0.1328125, 0.1367188,\n         0.1406250, 0.1445312, 0.1484375, 0.1523438, 0.1562500, 0.1601562,\n         0.1640625, 0.1679688, 0.1718750, 0.1757812, 0.1796875, 0.1835938,\n         0.1875000, 0.1914062, 0.1953125, 0.1992188, 0.2031250, 0.2070312,\n         0.2109375, 0.2148438, 0.2187500, 0.2226562, 0.2265625, 0.2304688,\n         0.2343750, 0.2382812, 0.2421875, 0.2460938, 0.2500000, 0.2539062,\n         0.2578125, 0.2617188, 0.2656250, 0.2695312, 0.2734375, 0.2773438,\n         0.2812500, 0.2851562, 0.2890625, 0.2929688, 0.2968750, 0.3007812,\n         0.3046875, 0.3085938, 0.3125000, 0.3164062, 0.3203125, 0.3242188,\n         0.3281250, 0.3320312, 0.3359375, 0.3398438, 0.3437500, 0.3476562,\n         0.3515625, 0.3554688, 0.3593750, 0.3632812, 0.3671875, 0.3710938,\n         0.3750000, 0.3789062, 0.3828125, 0.3867188, 0.3906250, 0.3945312,\n         0.3984375, 0.4023438, 0.4062500, 0.4101562, 0.4140625, 0.4179688,\n         0.4218750, 0.4257812, 0.4296875, 0.4335938, 0.4375000, 0.4414062,\n         0.4453125, 0.4492188, 0.4531250, 0.4570312, 0.4609375, 0.4648438,\n         0.4687500, 0.4726562, 0.4765625, 0.4804688, 0.4843750, 0.4882812,\n         0.4921875, 0.4960938, 0.5000000, 0.5039062, 0.5078125, 0.5117188,\n         0.5156250, 0.5195312, 0.5234375, 0.5273438, 0.5312500, 0.5351562,\n         0.5390625, 0.5429688, 0.5468750, 0.5507812, 0.5546875, 0.5585938,\n         0.5625000, 0.5664062, 0.5703125, 0.5742188, 0.5781250, 0.5820312,\n         0.5859375, 0.5898438, 0.5937500, 0.5976562, 0.6015625, 0.6054688,\n         0.6093750, 0.6132812, 0.6171875, 0.6210938, 0.6250000, 0.6289062,\n         0.6328125, 0.6367188, 0.6406250, 0.6445312, 0.6484375, 0.6523438,\n         0.6562500, 0.6601562, 0.6640625, 0.6679688, 0.6718750, 0.6757812,\n         0.6796875, 0.6835938, 0.6875000, 0.6914062, 0.6953125, 0.6992188,\n         0.7031250, 0.7070312, 0.7109375, 0.7148438, 0.7187500, 0.7226562,\n         0.7265625, 0.7304688, 0.7343750, 0.7382812, 0.7421875, 0.7460938,\n         0.7500000, 0.7539062, 0.7578125, 0.7617188, 0.7656250, 0.7695312,\n         0.7734375, 0.7773438, 0.7812500, 0.7851562, 0.7890625, 0.7929688,\n         0.7968750, 0.8007812, 0.8046875, 0.8085938, 0.8125000, 0.8164062,\n         0.8203125, 0.8242188, 0.8281250, 0.8320312, 0.8359375, 0.8398438,\n         0.8437500, 0.8476562, 0.8515625, 0.8554688, 0.8593750, 0.8632812,\n         0.8671875, 0.8710938, 0.8750000, 0.8789062, 0.8828125, 0.8867188,\n         0.8906250, 0.8945312, 0.8984375, 0.9023438, 0.9062500, 0.9101562,\n         0.9140625, 0.9179688, 0.9218750, 0.9257812, 0.9296875, 0.9335938,\n         0.9375000, 0.9414062, 0.9453125, 0.9492188, 0.9531250, 0.9570312,\n         0.9609375, 0.9648438, 0.9687500, 0.9726562, 0.9765625, 0.9804688,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 1 :: BLUE ###\n\ncolor_map_luts['idl01'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0195312, 0.0351562, 0.0507812, 0.0664062, 0.0820312,\n         0.0976562, 0.1132812, 0.1289062, 0.1445312, 0.1601562, 0.1757812,\n         0.1914062, 0.2070312, 0.2226562, 0.2382812, 0.2539062, 0.2695312,\n         0.2851562, 0.3007812, 0.3164062, 0.3320312, 0.3515625, 0.3671875,\n         0.3828125, 0.3984375, 0.4140625, 0.4296875, 0.4453125, 0.4609375,\n         0.4765625, 0.4921875, 0.5078125, 0.5234375, 0.5390625, 0.5546875,\n         0.5703125, 0.5859375, 0.6015625, 0.6171875, 0.6328125, 0.6484375,\n         0.6640625, 0.6835938, 0.6992188, 0.7148438, 0.7304688, 0.7460938,\n         0.7617188, 0.7773438, 0.7929688, 0.8085938, 0.8242188, 0.8398438,\n         0.8554688, 0.8710938, 0.8867188, 0.9023438, 0.9179688, 0.9335938,\n         0.9492188, 0.9648438, 0.9804688, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0078125, 0.0156250, 0.0195312, 0.0273438, 0.0351562,\n         0.0390625, 0.0468750, 0.0507812, 0.0585938, 0.0664062, 0.0703125,\n         0.0781250, 0.0820312, 0.0898438, 0.0976562, 0.1015625, 0.1093750,\n         0.1132812, 0.1210938, 0.1289062, 0.1328125, 0.1406250, 0.1445312,\n         0.1523438, 0.1601562, 0.1640625, 0.1718750, 0.1757812, 0.1835938,\n         0.1914062, 0.1953125, 0.2031250, 0.2070312, 0.2148438, 0.2226562,\n         0.2265625, 0.2343750, 0.2382812, 0.2460938, 0.2539062, 0.2578125,\n         0.2656250, 0.2695312, 0.2773438, 0.2851562, 0.2890625, 0.2968750,\n         0.3007812, 0.3085938, 0.3164062, 0.3203125, 0.3281250, 0.3320312,\n         0.3398438, 0.3476562, 0.3515625, 0.3593750, 0.3671875, 0.3710938,\n         0.3789062, 0.3828125, 0.3906250, 0.3984375, 0.4023438, 0.4101562,\n         0.4140625, 0.4218750, 0.4296875, 0.4335938, 0.4414062, 0.4453125,\n         0.4531250, 0.4609375, 0.4648438, 0.4726562, 0.4765625, 0.4843750,\n         0.4921875, 0.4960938, 0.5039062, 0.5078125, 0.5156250, 0.5234375,\n         0.5273438, 0.5351562, 0.5390625, 0.5468750, 0.5546875, 0.5585938,\n         0.5664062, 0.5703125, 0.5781250, 0.5859375, 0.5898438, 0.5976562,\n         0.6015625, 0.6093750, 0.6171875, 0.6210938, 0.6289062, 0.6328125,\n         0.6406250, 0.6484375, 0.6523438, 0.6601562, 0.6640625, 0.6718750,\n         0.6796875, 0.6835938, 0.6914062, 0.6992188, 0.7031250, 0.7109375,\n         0.7148438, 0.7226562, 0.7304688, 0.7343750, 0.7421875, 0.7460938,\n         0.7539062, 0.7617188, 0.7656250, 0.7734375, 0.7773438, 0.7851562,\n         0.7929688, 0.7968750, 0.8046875, 0.8085938, 0.8164062, 0.8242188,\n         0.8281250, 0.8359375, 0.8398438, 0.8476562, 0.8554688, 0.8593750,\n         0.8671875, 0.8710938, 0.8789062, 0.8867188, 0.8906250, 0.8984375,\n         0.9023438, 0.9101562, 0.9179688, 0.9218750, 0.9296875, 0.9335938,\n         0.9414062, 0.9492188, 0.9531250, 0.9609375, 0.9648438, 0.9726562,\n         0.9804688, 0.9843750, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.0039062, 0.0078125, 0.0156250, 0.0195312, 0.0234375,\n         0.0312500, 0.0351562, 0.0390625, 0.0468750, 0.0507812, 0.0546875,\n         0.0625000, 0.0664062, 0.0703125, 0.0781250, 0.0820312, 0.0898438,\n         0.0937500, 0.0976562, 0.1054688, 0.1093750, 0.1132812, 0.1210938,\n         0.1250000, 0.1289062, 0.1367188, 0.1406250, 0.1445312, 0.1523438,\n         0.1562500, 0.1640625, 0.1679688, 0.1718750, 0.1796875, 0.1835938,\n         0.1875000, 0.1953125, 0.1992188, 0.2031250, 0.2109375, 0.2148438,\n         0.2187500, 0.2265625, 0.2304688, 0.2382812, 0.2421875, 0.2460938,\n         0.2539062, 0.2578125, 0.2617188, 0.2695312, 0.2734375, 0.2773438,\n         0.2851562, 0.2890625, 0.2929688, 0.3007812, 0.3046875, 0.3125000,\n         0.3164062, 0.3203125, 0.3281250, 0.3320312, 0.3359375, 0.3437500,\n         0.3476562, 0.3515625, 0.3593750, 0.3632812, 0.3671875, 0.3750000,\n         0.3789062, 0.3867188, 0.3906250, 0.3945312, 0.4023438, 0.4062500,\n         0.4101562, 0.4179688, 0.4218750, 0.4257812, 0.4335938, 0.4375000,\n         0.4414062, 0.4492188, 0.4531250, 0.4609375, 0.4648438, 0.4687500,\n         0.4765625, 0.4804688, 0.4843750, 0.4921875, 0.4960938, 0.5000000,\n         0.5078125, 0.5117188, 0.5156250, 0.5234375, 0.5273438, 0.5312500,\n         0.5390625, 0.5429688, 0.5507812, 0.5546875, 0.5585938, 0.5664062,\n         0.5703125, 0.5742188, 0.5820312, 0.5859375, 0.5898438, 0.5976562,\n         0.6015625, 0.6054688, 0.6132812, 0.6171875, 0.6250000, 0.6289062,\n         0.6328125, 0.6406250, 0.6445312, 0.6484375, 0.6562500, 0.6601562,\n         0.6640625, 0.6718750, 0.6757812, 0.6796875, 0.6875000, 0.6914062,\n         0.6992188, 0.7031250, 0.7070312, 0.7148438, 0.7187500, 0.7226562,\n         0.7304688, 0.7343750, 0.7382812, 0.7460938, 0.7500000, 0.7539062,\n         0.7617188, 0.7656250, 0.7734375, 0.7773438, 0.7812500, 0.7890625,\n         0.7929688, 0.7968750, 0.8046875, 0.8085938, 0.8125000, 0.8203125,\n         0.8242188, 0.8281250, 0.8359375, 0.8398438, 0.8476562, 0.8515625,\n         0.8554688, 0.8632812, 0.8671875, 0.8710938, 0.8789062, 0.8828125,\n         0.8867188, 0.8945312, 0.8984375, 0.9023438, 0.9101562, 0.9140625,\n         0.9218750, 0.9257812, 0.9296875, 0.9375000, 0.9414062, 0.9453125,\n         0.9531250, 0.9570312, 0.9609375, 0.9687500, 0.9726562, 0.9765625,\n         0.9843750, 0.9882812, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 2 :: GRN-RED-BLU-WHT ###\n\ncolor_map_luts['idl02'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0234375,\n         0.0468750, 0.0703125, 0.0937500, 0.1171875, 0.1406250, 0.1640625,\n         0.1875000, 0.2109375, 0.2343750, 0.2578125, 0.2812500, 0.3046875,\n         0.3281250, 0.3515625, 0.3750000, 0.3984375, 0.4218750, 0.4453125,\n         0.4687500, 0.4921875, 0.5156250, 0.5390625, 0.5625000, 0.5859375,\n         0.6093750, 0.6328125, 0.6562500, 0.6796875, 0.7031250, 0.7265625,\n         0.7500000, 0.7734375, 0.7968750, 0.8203125, 0.8437500, 0.8671875,\n         0.8906250, 0.9140625, 0.9375000, 0.9492188, 0.9609375, 0.9726562,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9804688,\n         0.9765625, 0.9726562, 0.9687500, 0.9687500, 0.9687500, 0.9687500,\n         0.9687500, 0.9648438, 0.9609375, 0.9570312, 0.9531250, 0.9492188,\n         0.9453125, 0.9414062, 0.9375000, 0.9375000, 0.9335938, 0.9296875,\n         0.9257812, 0.9218750, 0.9218750, 0.9218750, 0.9218750, 0.9179688,\n         0.9140625, 0.9101562, 0.9062500, 0.9023438, 0.8984375, 0.8945312,\n         0.8906250, 0.8906250, 0.8906250, 0.8906250, 0.8906250, 0.8867188,\n         0.8828125, 0.8789062, 0.8750000, 0.8710938, 0.8671875, 0.8632812,\n         0.8593750, 0.8554688, 0.8515625, 0.8476562, 0.8437500, 0.8437500,\n         0.8437500, 0.8437500, 0.8437500, 0.8398438, 0.8359375, 0.8320312,\n         0.8281250, 0.8242188, 0.8203125, 0.8164062, 0.8125000, 0.8085938,\n         0.8046875, 0.8007812, 0.7968750, 0.7968750, 0.7968750, 0.7968750,\n         0.7968750, 0.7929688, 0.7890625, 0.7851562, 0.7812500, 0.7773438,\n         0.7734375, 0.7695312, 0.7656250, 0.7656250, 0.7656250, 0.7656250,\n         0.7656250, 0.7617188, 0.7578125, 0.7539062, 0.7500000, 0.7460938,\n         0.7421875, 0.7382812, 0.7343750, 0.7304688, 0.7265625, 0.7226562,\n         0.7187500, 0.7187500, 0.7187500, 0.7187500, 0.7187500, 0.7148438,\n         0.7109375, 0.7070312, 0.7031250, 0.6992188, 0.6953125, 0.6914062,\n         0.6875000, 0.6875000, 0.6875000, 0.6875000, 0.6875000, 0.6835938,\n         0.6796875, 0.6757812, 0.6718750, 0.6679688, 0.6640625, 0.6601562,\n         0.6562500, 0.6523438, 0.6484375, 0.6445312, 0.6406250, 0.6406250,\n         0.6406250, 0.6406250, 0.6406250, 0.6367188, 0.6328125, 0.6289062,\n         0.6250000, 0.6210938, 0.6171875, 0.6132812, 0.6093750, 0.6054688,\n         0.6015625, 0.5976562, 0.5937500, 0.5937500, 0.5937500, 0.5937500,\n         0.5937500, 0.5898438, 0.5859375, 0.5820312, 0.5781250, 0.5898438,\n         0.6015625, 0.6132812, 0.6250000, 0.6367188, 0.6484375, 0.6601562,\n         0.6718750, 0.6875000, 0.7031250, 0.7187500, 0.7343750, 0.7460938,\n         0.7578125, 0.7695312, 0.7812500, 0.7929688, 0.8046875, 0.8164062,\n         0.8281250, 0.8398438, 0.8515625, 0.8632812, 0.8750000, 0.8906250,\n         0.9062500, 0.9218750, 0.9375000, 0.9492188, 0.9609375, 0.9726562,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.1406250, 0.2812500, 0.2929688, 0.3085938, 0.3203125,\n         0.3359375, 0.3515625, 0.3632812, 0.3789062, 0.3906250, 0.4062500,\n         0.4218750, 0.4570312, 0.4921875, 0.5273438, 0.5625000, 0.5976562,\n         0.6328125, 0.6679688, 0.7031250, 0.7382812, 0.7734375, 0.8085938,\n         0.8437500, 0.8789062, 0.9140625, 0.9492188, 0.9843750, 0.9726562,\n         0.9609375, 0.9492188, 0.9375000, 0.9140625, 0.8906250, 0.8671875,\n         0.8437500, 0.8203125, 0.7968750, 0.7734375, 0.7500000, 0.7265625,\n         0.7031250, 0.6796875, 0.6562500, 0.6328125, 0.6093750, 0.5859375,\n         0.5625000, 0.5390625, 0.5156250, 0.4921875, 0.4687500, 0.4453125,\n         0.4218750, 0.3984375, 0.3750000, 0.3515625, 0.3281250, 0.3046875,\n         0.2812500, 0.2578125, 0.2343750, 0.2109375, 0.1875000, 0.1640625,\n         0.1406250, 0.1171875, 0.0937500, 0.0703125, 0.0468750, 0.0234375,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0312500,\n         0.0625000, 0.0937500, 0.1250000, 0.1562500, 0.1875000, 0.2187500,\n         0.2500000, 0.2812500, 0.3125000, 0.3437500, 0.3750000, 0.4062500,\n         0.4375000, 0.4687500, 0.5000000, 0.5273438, 0.5546875, 0.5820312,\n         0.6093750, 0.6406250, 0.6718750, 0.7031250, 0.7343750, 0.7656250,\n         0.7968750, 0.8281250, 0.8593750, 0.8906250, 0.9218750, 0.9531250,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0039062, 0.0117188,\n         0.0195312, 0.0273438, 0.0351562, 0.0390625, 0.0468750, 0.0546875,\n         0.0625000, 0.0703125, 0.0781250, 0.0859375, 0.0976562, 0.1054688,\n         0.1132812, 0.1210938, 0.1328125, 0.1367188, 0.1445312, 0.1523438,\n         0.1601562, 0.1679688, 0.1757812, 0.1835938, 0.1953125, 0.2031250,\n         0.2109375, 0.2187500, 0.2304688, 0.2382812, 0.2460938, 0.2539062,\n         0.2617188, 0.2656250, 0.2734375, 0.2812500, 0.2890625, 0.2968750,\n         0.3046875, 0.3125000, 0.3242188, 0.3320312, 0.3398438, 0.3476562,\n         0.3593750, 0.3671875, 0.3750000, 0.3828125, 0.3945312, 0.3984375,\n         0.4062500, 0.4140625, 0.4218750, 0.4296875, 0.4375000, 0.4453125,\n         0.4531250, 0.4609375, 0.4687500, 0.4765625, 0.4882812, 0.4960938,\n         0.5039062, 0.5117188, 0.5234375, 0.5273438, 0.5351562, 0.5429688,\n         0.5507812, 0.5585938, 0.5664062, 0.5742188, 0.5859375, 0.5937500,\n         0.6015625, 0.6093750, 0.6210938, 0.6250000, 0.6328125, 0.6367188,\n         0.6445312, 0.6523438, 0.6601562, 0.6679688, 0.6796875, 0.6875000,\n         0.6953125, 0.7031250, 0.7148438, 0.7226562, 0.7304688, 0.7382812,\n         0.7500000, 0.7539062, 0.7617188, 0.7695312, 0.7773438, 0.7851562,\n         0.7929688, 0.8007812, 0.8125000, 0.8203125, 0.8281250, 0.8359375,\n         0.8437500, 0.8476562, 0.8554688, 0.8632812, 0.8710938, 0.8789062,\n         0.8867188, 0.8945312, 0.9062500, 0.9140625, 0.9218750, 0.9296875,\n         0.9414062, 0.9492188, 0.9570312, 0.9648438, 0.9765625, 0.9804688,\n         0.9843750, 0.9882812, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 3 :: RED TEMPERATURE ###\n\ncolor_map_luts['idl03'] = \\\n   (\narray([  0.0000000, 0.0039062, 0.0078125, 0.0156250, 0.0195312, 0.0273438,\n         0.0312500, 0.0390625, 0.0429688, 0.0507812, 0.0546875, 0.0585938,\n         0.0664062, 0.0703125, 0.0781250, 0.0820312, 0.0898438, 0.0937500,\n         0.1015625, 0.1054688, 0.1093750, 0.1171875, 0.1210938, 0.1289062,\n         0.1328125, 0.1406250, 0.1445312, 0.1523438, 0.1562500, 0.1640625,\n         0.1679688, 0.1718750, 0.1796875, 0.1835938, 0.1914062, 0.1953125,\n         0.2031250, 0.2070312, 0.2148438, 0.2187500, 0.2226562, 0.2304688,\n         0.2343750, 0.2421875, 0.2460938, 0.2539062, 0.2578125, 0.2656250,\n         0.2695312, 0.2734375, 0.2812500, 0.2851562, 0.2929688, 0.2968750,\n         0.3046875, 0.3085938, 0.3164062, 0.3203125, 0.3281250, 0.3320312,\n         0.3359375, 0.3437500, 0.3476562, 0.3554688, 0.3593750, 0.3671875,\n         0.3710938, 0.3789062, 0.3828125, 0.3867188, 0.3945312, 0.3984375,\n         0.4062500, 0.4101562, 0.4179688, 0.4218750, 0.4296875, 0.4335938,\n         0.4414062, 0.4453125, 0.4492188, 0.4570312, 0.4609375, 0.4687500,\n         0.4726562, 0.4804688, 0.4843750, 0.4921875, 0.4960938, 0.5000000,\n         0.5078125, 0.5117188, 0.5195312, 0.5234375, 0.5312500, 0.5351562,\n         0.5429688, 0.5468750, 0.5507812, 0.5585938, 0.5625000, 0.5703125,\n         0.5742188, 0.5820312, 0.5859375, 0.5937500, 0.5976562, 0.6054688,\n         0.6093750, 0.6132812, 0.6210938, 0.6250000, 0.6328125, 0.6367188,\n         0.6445312, 0.6484375, 0.6562500, 0.6601562, 0.6640625, 0.6718750,\n         0.6757812, 0.6835938, 0.6875000, 0.6953125, 0.6992188, 0.7070312,\n         0.7109375, 0.7187500, 0.7226562, 0.7265625, 0.7343750, 0.7382812,\n         0.7460938, 0.7500000, 0.7578125, 0.7617188, 0.7695312, 0.7734375,\n         0.7773438, 0.7851562, 0.7890625, 0.7968750, 0.8007812, 0.8085938,\n         0.8125000, 0.8203125, 0.8242188, 0.8281250, 0.8359375, 0.8398438,\n         0.8476562, 0.8515625, 0.8593750, 0.8632812, 0.8710938, 0.8750000,\n         0.8828125, 0.8867188, 0.8906250, 0.8984375, 0.9023438, 0.9101562,\n         0.9140625, 0.9218750, 0.9257812, 0.9335938, 0.9375000, 0.9414062,\n         0.9492188, 0.9531250, 0.9609375, 0.9648438, 0.9726562, 0.9765625,\n         0.9843750, 0.9882812, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0039062, 0.0117188, 0.0195312, 0.0273438, 0.0351562,\n         0.0429688, 0.0507812, 0.0585938, 0.0664062, 0.0703125, 0.0781250,\n         0.0859375, 0.0937500, 0.1015625, 0.1093750, 0.1171875, 0.1250000,\n         0.1328125, 0.1367188, 0.1445312, 0.1523438, 0.1601562, 0.1679688,\n         0.1757812, 0.1835938, 0.1914062, 0.1992188, 0.2031250, 0.2109375,\n         0.2187500, 0.2265625, 0.2343750, 0.2421875, 0.2500000, 0.2578125,\n         0.2656250, 0.2695312, 0.2773438, 0.2851562, 0.2929688, 0.3007812,\n         0.3085938, 0.3164062, 0.3242188, 0.3320312, 0.3359375, 0.3437500,\n         0.3515625, 0.3593750, 0.3671875, 0.3750000, 0.3828125, 0.3906250,\n         0.3984375, 0.4023438, 0.4101562, 0.4179688, 0.4257812, 0.4335938,\n         0.4414062, 0.4492188, 0.4570312, 0.4648438, 0.4687500, 0.4765625,\n         0.4843750, 0.4921875, 0.5000000, 0.5078125, 0.5156250, 0.5234375,\n         0.5312500, 0.5351562, 0.5429688, 0.5507812, 0.5585938, 0.5664062,\n         0.5742188, 0.5820312, 0.5898438, 0.5976562, 0.6015625, 0.6093750,\n         0.6171875, 0.6250000, 0.6328125, 0.6406250, 0.6484375, 0.6562500,\n         0.6640625, 0.6679688, 0.6757812, 0.6835938, 0.6914062, 0.6992188,\n         0.7070312, 0.7148438, 0.7226562, 0.7304688, 0.7343750, 0.7421875,\n         0.7500000, 0.7578125, 0.7656250, 0.7734375, 0.7812500, 0.7890625,\n         0.7968750, 0.8007812, 0.8085938, 0.8164062, 0.8242188, 0.8320312,\n         0.8398438, 0.8476562, 0.8554688, 0.8632812, 0.8671875, 0.8750000,\n         0.8828125, 0.8906250, 0.8984375, 0.9062500, 0.9140625, 0.9218750,\n         0.9296875, 0.9335938, 0.9414062, 0.9492188, 0.9570312, 0.9648438,\n         0.9726562, 0.9804688, 0.9882812, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0117188,\n         0.0273438, 0.0429688, 0.0585938, 0.0742188, 0.0898438, 0.1054688,\n         0.1210938, 0.1367188, 0.1523438, 0.1679688, 0.1835938, 0.1992188,\n         0.2109375, 0.2265625, 0.2421875, 0.2578125, 0.2734375, 0.2890625,\n         0.3046875, 0.3203125, 0.3359375, 0.3515625, 0.3671875, 0.3828125,\n         0.3984375, 0.4101562, 0.4257812, 0.4414062, 0.4570312, 0.4726562,\n         0.4882812, 0.5039062, 0.5195312, 0.5351562, 0.5507812, 0.5664062,\n         0.5820312, 0.5976562, 0.6093750, 0.6250000, 0.6406250, 0.6562500,\n         0.6718750, 0.6875000, 0.7031250, 0.7187500, 0.7343750, 0.7500000,\n         0.7656250, 0.7812500, 0.7968750, 0.8085938, 0.8242188, 0.8398438,\n         0.8554688, 0.8710938, 0.8867188, 0.9023438, 0.9179688, 0.9335938,\n         0.9492188, 0.9648438, 0.9804688, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 4 :: BLUE ###\n\ncolor_map_luts['idl04'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0273438,\n         0.0585938, 0.0859375, 0.1171875, 0.1445312, 0.1757812, 0.2031250,\n         0.2343750, 0.2617188, 0.2929688, 0.3203125, 0.3515625, 0.3789062,\n         0.4101562, 0.4375000, 0.4687500, 0.4882812, 0.5078125, 0.5273438,\n         0.5468750, 0.5664062, 0.5859375, 0.6054688, 0.6250000, 0.6445312,\n         0.6640625, 0.6835938, 0.7031250, 0.7226562, 0.7421875, 0.7617188,\n         0.7812500, 0.7812500, 0.7851562, 0.7851562, 0.7890625, 0.7890625,\n         0.7929688, 0.7929688, 0.7968750, 0.7968750, 0.8007812, 0.8007812,\n         0.8046875, 0.8046875, 0.8085938, 0.8085938, 0.8125000, 0.8125000,\n         0.8164062, 0.8164062, 0.8203125, 0.8203125, 0.8242188, 0.8242188,\n         0.8281250, 0.8281250, 0.8320312, 0.8320312, 0.8359375, 0.8359375,\n         0.8398438, 0.8398438, 0.8437500, 0.8437500, 0.8476562, 0.8476562,\n         0.8515625, 0.8515625, 0.8554688, 0.8554688, 0.8593750, 0.8593750,\n         0.8632812, 0.8632812, 0.8671875, 0.8671875, 0.8710938, 0.8710938,\n         0.8750000, 0.8750000, 0.8789062, 0.8789062, 0.8828125, 0.8828125,\n         0.8867188, 0.8867188, 0.8906250, 0.8906250, 0.8945312, 0.8945312,\n         0.8984375, 0.8984375, 0.9023438, 0.9023438, 0.9062500, 0.9062500,\n         0.9101562, 0.9101562, 0.9140625, 0.9140625, 0.9179688, 0.9179688,\n         0.9218750, 0.9218750, 0.9257812, 0.9257812, 0.9296875, 0.9296875,\n         0.9335938, 0.9335938, 0.9375000, 0.9375000, 0.9414062, 0.9414062,\n         0.9453125, 0.9453125, 0.9492188, 0.9492188, 0.9531250, 0.9531250,\n         0.9570312, 0.9570312, 0.9609375, 0.9609375, 0.9648438, 0.9648438,\n         0.9687500, 0.9687500, 0.9726562, 0.9726562, 0.9765625, 0.9765625,\n         0.9804688, 0.9804688, 0.9843750, 0.9843750, 0.9882812, 0.9882812,\n         0.9921875, 0.9921875, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0117188, 0.0234375, 0.0351562,\n         0.0468750, 0.0585938, 0.0703125, 0.0820312, 0.0976562, 0.1093750,\n         0.1210938, 0.1328125, 0.1445312, 0.1562500, 0.1679688, 0.1796875,\n         0.1953125, 0.2070312, 0.2187500, 0.2304688, 0.2421875, 0.2539062,\n         0.2656250, 0.2773438, 0.2929688, 0.3046875, 0.3164062, 0.3281250,\n         0.3398438, 0.3515625, 0.3632812, 0.3750000, 0.3906250, 0.4023438,\n         0.4140625, 0.4257812, 0.4375000, 0.4492188, 0.4609375, 0.4726562,\n         0.4882812, 0.5000000, 0.5117188, 0.5234375, 0.5351562, 0.5468750,\n         0.5585938, 0.5703125, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5820312, 0.5781250, 0.5781250, 0.5742188, 0.5703125,\n         0.5703125, 0.5664062, 0.5664062, 0.5625000, 0.5585938, 0.5585938,\n         0.5546875, 0.5507812, 0.5507812, 0.5468750, 0.5468750, 0.5351562,\n         0.5273438, 0.5156250, 0.5078125, 0.4960938, 0.4882812, 0.4765625,\n         0.4687500, 0.4570312, 0.4492188, 0.4375000, 0.4296875, 0.4179688,\n         0.4101562, 0.3984375, 0.3906250, 0.3632812, 0.3398438, 0.3164062,\n         0.2929688, 0.2656250, 0.2421875, 0.2187500, 0.1953125, 0.1679688,\n         0.1445312, 0.1210938, 0.0976562, 0.0703125, 0.0468750, 0.0234375,\n         0.0000000, 0.0078125, 0.0156250, 0.0234375, 0.0351562, 0.0429688,\n         0.0507812, 0.0625000, 0.0703125, 0.0781250, 0.0898438, 0.0976562,\n         0.1054688, 0.1132812, 0.1250000, 0.1328125, 0.1406250, 0.1523438,\n         0.1601562, 0.1679688, 0.1796875, 0.1875000, 0.1953125, 0.2070312,\n         0.2148438, 0.2226562, 0.2304688, 0.2421875, 0.2500000, 0.2578125,\n         0.2695312, 0.2773438, 0.2851562, 0.2968750, 0.3046875, 0.3125000,\n         0.3242188, 0.3320312, 0.3398438, 0.3476562, 0.3593750, 0.3671875,\n         0.3750000, 0.3867188, 0.3945312, 0.4023438, 0.4140625, 0.4218750,\n         0.4296875, 0.4414062, 0.4492188, 0.4570312, 0.4648438, 0.4765625,\n         0.4843750, 0.4921875, 0.5039062, 0.5117188, 0.5195312, 0.5312500,\n         0.5390625, 0.5468750, 0.5546875, 0.5664062, 0.5742188, 0.5820312,\n         0.5937500, 0.6015625, 0.6093750, 0.6210938, 0.6289062, 0.6367188,\n         0.6484375, 0.6562500, 0.6640625, 0.6718750, 0.6835938, 0.6914062,\n         0.6992188, 0.7109375, 0.7187500, 0.7265625, 0.7382812, 0.7460938,\n         0.7539062, 0.7656250, 0.7734375, 0.7812500, 0.7890625, 0.8007812,\n         0.8085938, 0.8164062, 0.8281250, 0.8359375, 0.8437500, 0.8554688,\n         0.8632812, 0.8710938, 0.8828125, 0.8906250, 0.8984375, 0.9062500,\n         0.9179688, 0.9257812, 0.9335938, 0.9453125, 0.9531250, 0.9609375,\n         0.9726562, 0.9804688, 0.9882812, 0.9960938]),\narray([  0.0000000, 0.0078125, 0.0156250, 0.0234375, 0.0312500, 0.0390625,\n         0.0468750, 0.0546875, 0.0625000, 0.0703125, 0.0781250, 0.0859375,\n         0.0976562, 0.1054688, 0.1132812, 0.1210938, 0.1289062, 0.1367188,\n         0.1445312, 0.1523438, 0.1601562, 0.1679688, 0.1757812, 0.1835938,\n         0.1953125, 0.2031250, 0.2109375, 0.2187500, 0.2265625, 0.2343750,\n         0.2421875, 0.2500000, 0.2578125, 0.2656250, 0.2734375, 0.2812500,\n         0.2929688, 0.3007812, 0.3085938, 0.3164062, 0.3242188, 0.3320312,\n         0.3398438, 0.3476562, 0.3554688, 0.3632812, 0.3710938, 0.3789062,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3750000, 0.3632812, 0.3515625,\n         0.3398438, 0.3281250, 0.3164062, 0.3046875, 0.2929688, 0.2773438,\n         0.2656250, 0.2539062, 0.2421875, 0.2304688, 0.2187500, 0.2070312,\n         0.1953125, 0.1796875, 0.1679688, 0.1562500, 0.1445312, 0.1328125,\n         0.1210938, 0.1093750, 0.0976562, 0.0820312, 0.0703125, 0.0585938,\n         0.0468750, 0.0351562, 0.0234375, 0.0117188, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 5 :: STD GAMMA-II ###\n\ncolor_map_luts['idl05'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0156250, 0.0351562, 0.0546875, 0.0742188, 0.0898438, 0.1093750,\n         0.1289062, 0.1484375, 0.1640625, 0.1835938, 0.2031250, 0.2226562,\n         0.2382812, 0.2578125, 0.2773438, 0.2968750, 0.3164062, 0.3164062,\n         0.3164062, 0.3164062, 0.3164062, 0.3164062, 0.3164062, 0.3164062,\n         0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.3125000,\n         0.3125000, 0.3085938, 0.3281250, 0.3476562, 0.3671875, 0.3867188,\n         0.4062500, 0.4257812, 0.4453125, 0.4648438, 0.4843750, 0.5039062,\n         0.5234375, 0.5429688, 0.5625000, 0.5820312, 0.6015625, 0.6210938,\n         0.6406250, 0.6601562, 0.6796875, 0.7031250, 0.7226562, 0.7421875,\n         0.7656250, 0.7851562, 0.8046875, 0.8281250, 0.8476562, 0.8671875,\n         0.8906250, 0.9101562, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9687500, 0.9375000, 0.9062500, 0.8789062, 0.8476562,\n         0.8164062, 0.7890625, 0.7578125, 0.7265625, 0.6992188, 0.6679688,\n         0.6367188, 0.6562500, 0.6757812, 0.6953125, 0.7148438, 0.7343750,\n         0.7539062, 0.7734375, 0.7929688, 0.8164062, 0.8359375, 0.8554688,\n         0.8750000, 0.8945312, 0.9140625, 0.9335938, 0.9531250, 0.9726562,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0195312, 0.0390625, 0.0625000, 0.0820312, 0.1054688, 0.1250000,\n         0.1445312, 0.1679688, 0.1875000, 0.2109375, 0.2304688, 0.2500000,\n         0.2734375, 0.2929688, 0.3164062, 0.3320312, 0.3515625, 0.3710938,\n         0.3906250, 0.4101562, 0.4257812, 0.4453125, 0.4648438, 0.4843750,\n         0.5039062, 0.5234375, 0.5390625, 0.5585938, 0.5781250, 0.5976562,\n         0.6171875, 0.6367188, 0.6367188, 0.6367188, 0.6367188, 0.6367188,\n         0.6367188, 0.6367188, 0.6367188, 0.6367188, 0.6367188, 0.6367188,\n         0.6367188, 0.6367188, 0.6367188, 0.6367188, 0.6367188, 0.6367188,\n         0.6367188, 0.6367188, 0.6367188, 0.6367188, 0.6367188, 0.6367188,\n         0.6367188, 0.6367188, 0.6367188, 0.6367188, 0.6367188, 0.6367188,\n         0.6367188, 0.6367188, 0.6367188, 0.6601562, 0.6835938, 0.7070312,\n         0.7304688, 0.7539062, 0.7773438, 0.8007812, 0.8281250, 0.8515625,\n         0.8750000, 0.8984375, 0.9218750, 0.9453125, 0.9687500, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0195312, 0.0390625, 0.0585938, 0.0781250, 0.1015625,\n         0.1210938, 0.1406250, 0.1601562, 0.1796875, 0.2031250, 0.2226562,\n         0.2421875, 0.2617188, 0.2812500, 0.3046875, 0.3242188, 0.3437500,\n         0.3632812, 0.3828125, 0.4062500, 0.4257812, 0.4453125, 0.4648438,\n         0.4843750, 0.5078125, 0.5273438, 0.5468750, 0.5664062, 0.5859375,\n         0.6093750, 0.6289062, 0.6484375, 0.6679688, 0.6875000, 0.7109375,\n         0.7304688, 0.7500000, 0.7695312, 0.7890625, 0.8125000, 0.8320312,\n         0.8515625, 0.8710938, 0.8906250, 0.9140625, 0.9335938, 0.9531250,\n         0.9726562, 0.9960938, 0.9765625, 0.9570312, 0.9335938, 0.9140625,\n         0.8906250, 0.8710938, 0.8515625, 0.8281250, 0.8085938, 0.7851562,\n         0.7656250, 0.7421875, 0.7226562, 0.7031250, 0.6796875, 0.6601562,\n         0.6367188, 0.6171875, 0.5937500, 0.5742188, 0.5546875, 0.5312500,\n         0.5117188, 0.4882812, 0.4687500, 0.4453125, 0.4257812, 0.4062500,\n         0.3828125, 0.3632812, 0.3398438, 0.3203125, 0.2968750, 0.2773438,\n         0.2578125, 0.2343750, 0.2148438, 0.1914062, 0.1718750, 0.1484375,\n         0.1289062, 0.1093750, 0.0859375, 0.0664062, 0.0429688, 0.0234375,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0351562, 0.0546875,\n         0.0742188, 0.0937500, 0.1093750, 0.1289062, 0.1484375, 0.1679688,\n         0.1875000, 0.2070312, 0.2226562, 0.2421875, 0.2617188, 0.2812500,\n         0.3007812, 0.3203125, 0.3007812, 0.2773438, 0.2539062, 0.2304688,\n         0.2070312, 0.1835938, 0.1601562, 0.1406250, 0.1171875, 0.0937500,\n         0.0703125, 0.0468750, 0.0234375, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0117188, 0.0234375, 0.0351562,\n         0.0468750, 0.0625000, 0.0742188, 0.0859375, 0.0976562, 0.1132812,\n         0.1250000, 0.1367188, 0.1484375, 0.1601562, 0.1757812, 0.1875000,\n         0.1992188, 0.2109375, 0.2265625, 0.2382812, 0.2500000, 0.2617188,\n         0.2773438, 0.2890625, 0.3007812, 0.3125000, 0.3242188, 0.3398438,\n         0.3515625, 0.3632812, 0.3750000, 0.3906250, 0.4023438, 0.4140625,\n         0.4257812, 0.4375000, 0.4531250, 0.4648438, 0.4765625, 0.4882812,\n         0.5039062, 0.5156250, 0.5273438, 0.5390625, 0.5546875, 0.5664062,\n         0.5781250, 0.5898438, 0.6015625, 0.6171875, 0.6289062, 0.6406250,\n         0.6523438, 0.6679688, 0.6796875, 0.6914062, 0.7031250, 0.7148438,\n         0.7304688, 0.7421875, 0.7539062, 0.7656250, 0.7812500, 0.7929688,\n         0.8046875, 0.8164062, 0.8320312, 0.8437500, 0.8554688, 0.8671875,\n         0.8789062, 0.8945312, 0.9062500, 0.9179688, 0.9296875, 0.9453125,\n         0.9570312, 0.9687500, 0.9804688, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 6 :: PRISM ###\n\ncolor_map_luts['idl06'] = \\\n   (\narray([  0.0000000, 0.0117188, 0.0273438, 0.0429688, 0.0585938, 0.0742188,\n         0.0859375, 0.1015625, 0.1171875, 0.1328125, 0.1484375, 0.1601562,\n         0.1757812, 0.1914062, 0.2070312, 0.2226562, 0.2343750, 0.2500000,\n         0.2656250, 0.2812500, 0.2968750, 0.3085938, 0.3242188, 0.3398438,\n         0.3554688, 0.3710938, 0.3828125, 0.3984375, 0.4140625, 0.4296875,\n         0.4453125, 0.4570312, 0.4726562, 0.4882812, 0.5039062, 0.5195312,\n         0.5351562, 0.5468750, 0.5625000, 0.5781250, 0.5937500, 0.6093750,\n         0.6210938, 0.6367188, 0.6523438, 0.6679688, 0.6835938, 0.6953125,\n         0.7109375, 0.7265625, 0.7421875, 0.7578125, 0.7695312, 0.7851562,\n         0.8007812, 0.8164062, 0.8320312, 0.8437500, 0.8593750, 0.8750000,\n         0.8906250, 0.9062500, 0.9179688, 0.9335938, 0.9492188, 0.9648438,\n         0.9804688, 0.9960938, 0.9804688, 0.9648438, 0.9492188, 0.9335938,\n         0.9179688, 0.8984375, 0.8828125, 0.8671875, 0.8515625, 0.8359375,\n         0.8203125, 0.8007812, 0.7851562, 0.7695312, 0.7539062, 0.7382812,\n         0.7187500, 0.7031250, 0.6875000, 0.6718750, 0.6562500, 0.6406250,\n         0.6210938, 0.6054688, 0.5898438, 0.5742188, 0.5585938, 0.5390625,\n         0.5234375, 0.5078125, 0.4921875, 0.4765625, 0.4609375, 0.4414062,\n         0.4257812, 0.4101562, 0.3945312, 0.3789062, 0.3593750, 0.3437500,\n         0.3281250, 0.3125000, 0.2968750, 0.2812500, 0.2617188, 0.2460938,\n         0.2304688, 0.2148438, 0.1992188, 0.1796875, 0.1640625, 0.1484375,\n         0.1328125, 0.1171875, 0.1015625, 0.0820312, 0.0664062, 0.0507812,\n         0.0351562, 0.0195312, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0117188,\n         0.0273438, 0.0429688, 0.0585938, 0.0742188, 0.0898438, 0.1054688,\n         0.1210938, 0.1367188, 0.1523438, 0.1679688, 0.1835938, 0.1992188,\n         0.2148438, 0.2304688, 0.2460938, 0.2617188, 0.2773438, 0.2929688,\n         0.3085938, 0.3242188, 0.3398438, 0.3554688, 0.3710938, 0.3867188,\n         0.4023438, 0.4179688, 0.4335938, 0.4492188, 0.4648438, 0.4804688,\n         0.4960938, 0.5117188, 0.5273438, 0.5429688, 0.5585938, 0.5742188,\n         0.5898438, 0.6054688, 0.6210938, 0.6367188, 0.6523438, 0.6679688,\n         0.6835938, 0.6992188, 0.7148438, 0.7304688, 0.7460938, 0.7617188,\n         0.7773438, 0.7929688, 0.8085938, 0.8242188, 0.8398438, 0.8554688,\n         0.8710938, 0.8867188, 0.9023438, 0.9179688, 0.9335938, 0.9492188,\n         0.9648438, 0.9804688, 0.9960938, 0.9804688, 0.9648438, 0.9492188,\n         0.9335938, 0.9179688, 0.9023438, 0.8867188, 0.8710938, 0.8554688,\n         0.8398438, 0.8242188, 0.8085938, 0.7929688, 0.7773438, 0.7617188,\n         0.7460938, 0.7304688, 0.7148438, 0.6992188, 0.6835938, 0.6640625,\n         0.6484375, 0.6328125, 0.6171875, 0.6015625, 0.5859375, 0.5703125,\n         0.5546875, 0.5390625, 0.5234375, 0.5078125, 0.4921875, 0.4765625,\n         0.4609375, 0.4453125, 0.4296875, 0.4140625, 0.3984375, 0.3828125,\n         0.3671875, 0.3515625, 0.3320312, 0.3164062, 0.3007812, 0.2851562,\n         0.2695312, 0.2539062, 0.2382812, 0.2226562, 0.2070312, 0.1914062,\n         0.1757812, 0.1601562, 0.1445312, 0.1289062, 0.1132812, 0.0976562,\n         0.0820312, 0.0664062, 0.0507812, 0.0351562, 0.0195312, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0117188, 0.0273438, 0.0429688, 0.0585938, 0.0742188,\n         0.0898438, 0.1054688, 0.1171875, 0.1328125, 0.1484375, 0.1640625,\n         0.1796875, 0.1953125, 0.2109375, 0.2226562, 0.2382812, 0.2539062,\n         0.2695312, 0.2851562, 0.3007812, 0.3164062, 0.3320312, 0.3437500,\n         0.3593750, 0.3750000, 0.3906250, 0.4062500, 0.4218750, 0.4375000,\n         0.4492188, 0.4648438, 0.4804688, 0.4960938, 0.5117188, 0.5273438,\n         0.5429688, 0.5546875, 0.5703125, 0.5859375, 0.6015625, 0.6171875,\n         0.6328125, 0.6484375, 0.6640625, 0.6757812, 0.6914062, 0.7070312,\n         0.7226562, 0.7382812, 0.7539062, 0.7695312, 0.7812500, 0.7968750,\n         0.8125000, 0.8281250, 0.8437500, 0.8593750, 0.8750000, 0.8867188,\n         0.9023438, 0.9179688, 0.9335938, 0.9492188, 0.9648438, 0.9804688,\n         0.9960938, 0.9804688, 0.9648438, 0.9492188, 0.9335938, 0.9179688,\n         0.9023438, 0.8867188, 0.8710938, 0.8554688, 0.8398438, 0.8242188,\n         0.8085938, 0.7929688, 0.7773438, 0.7617188, 0.7460938, 0.7304688,\n         0.7148438, 0.6992188, 0.6835938, 0.6640625, 0.6484375, 0.6328125,\n         0.6171875, 0.6015625, 0.5859375, 0.5703125, 0.5546875, 0.5390625,\n         0.5234375, 0.5078125, 0.4921875, 0.4765625, 0.4609375, 0.4453125,\n         0.4296875, 0.4140625, 0.3984375, 0.3828125, 0.3671875, 0.3515625,\n         0.3320312, 0.3164062, 0.3007812, 0.2851562, 0.2695312, 0.2539062,\n         0.2382812, 0.2226562, 0.2070312, 0.1914062, 0.1757812, 0.1601562,\n         0.1445312, 0.1289062, 0.1132812, 0.0976562, 0.0820312, 0.0664062,\n         0.0507812, 0.0351562, 0.0195312, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 7 :: RED-PURPLE ###\n\ncolor_map_luts['idl07'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0117188, 0.0234375, 0.0390625, 0.0507812,\n         0.0664062, 0.0781250, 0.0937500, 0.1054688, 0.1171875, 0.1289062,\n         0.1406250, 0.1523438, 0.1640625, 0.1757812, 0.1875000, 0.1992188,\n         0.2109375, 0.2226562, 0.2343750, 0.2460938, 0.2578125, 0.2695312,\n         0.2812500, 0.2929688, 0.3046875, 0.3164062, 0.3281250, 0.3320312,\n         0.3359375, 0.3398438, 0.3437500, 0.3515625, 0.3593750, 0.3671875,\n         0.3750000, 0.3789062, 0.3828125, 0.3867188, 0.3906250, 0.3984375,\n         0.4062500, 0.4140625, 0.4218750, 0.4257812, 0.4296875, 0.4335938,\n         0.4375000, 0.4453125, 0.4531250, 0.4609375, 0.4687500, 0.4726562,\n         0.4765625, 0.4804688, 0.4843750, 0.4921875, 0.5000000, 0.5078125,\n         0.5156250, 0.5195312, 0.5234375, 0.5273438, 0.5312500, 0.5390625,\n         0.5468750, 0.5546875, 0.5625000, 0.5664062, 0.5703125, 0.5742188,\n         0.5781250, 0.5859375, 0.5937500, 0.6015625, 0.6093750, 0.6132812,\n         0.6171875, 0.6210938, 0.6250000, 0.6328125, 0.6406250, 0.6484375,\n         0.6562500, 0.6601562, 0.6640625, 0.6679688, 0.6718750, 0.6796875,\n         0.6875000, 0.6953125, 0.7031250, 0.7070312, 0.7109375, 0.7148438,\n         0.7187500, 0.7226562, 0.7265625, 0.7304688, 0.7343750, 0.7382812,\n         0.7421875, 0.7460938, 0.7500000, 0.7539062, 0.7578125, 0.7617188,\n         0.7656250, 0.7734375, 0.7812500, 0.7890625, 0.7968750, 0.8007812,\n         0.8046875, 0.8085938, 0.8125000, 0.8164062, 0.8203125, 0.8242188,\n         0.8281250, 0.8320312, 0.8359375, 0.8398438, 0.8437500, 0.8476562,\n         0.8515625, 0.8554688, 0.8593750, 0.8671875, 0.8750000, 0.8828125,\n         0.8906250, 0.8945312, 0.8984375, 0.9023438, 0.9062500, 0.9101562,\n         0.9140625, 0.9179688, 0.9218750, 0.9257812, 0.9296875, 0.9335938,\n         0.9375000, 0.9414062, 0.9453125, 0.9492188, 0.9531250, 0.9609375,\n         0.9687500, 0.9765625, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9882812, 0.9921875]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0039062,\n         0.0078125, 0.0117188, 0.0156250, 0.0156250, 0.0156250, 0.0156250,\n         0.0156250, 0.0156250, 0.0156250, 0.0156250, 0.0156250, 0.0156250,\n         0.0156250, 0.0156250, 0.0156250, 0.0195312, 0.0234375, 0.0273438,\n         0.0312500, 0.0312500, 0.0312500, 0.0312500, 0.0312500, 0.0312500,\n         0.0312500, 0.0312500, 0.0312500, 0.0312500, 0.0312500, 0.0312500,\n         0.0312500, 0.0312500, 0.0312500, 0.0312500, 0.0312500, 0.0312500,\n         0.0312500, 0.0312500, 0.0312500, 0.0312500, 0.0312500, 0.0312500,\n         0.0312500, 0.0351562, 0.0390625, 0.0429688, 0.0468750, 0.0468750,\n         0.0468750, 0.0468750, 0.0468750, 0.0468750, 0.0468750, 0.0468750,\n         0.0468750, 0.0468750, 0.0468750, 0.0468750, 0.0468750, 0.0468750,\n         0.0468750, 0.0468750, 0.0468750, 0.0468750, 0.0468750, 0.0468750,\n         0.0468750, 0.0507812, 0.0546875, 0.0585938, 0.0625000, 0.0625000,\n         0.0625000, 0.0625000, 0.0625000, 0.0625000, 0.0625000, 0.0625000,\n         0.0625000, 0.0625000, 0.0625000, 0.0625000, 0.0625000, 0.0625000,\n         0.0625000, 0.0625000, 0.0625000, 0.0664062, 0.0703125, 0.0742188,\n         0.0781250, 0.0781250, 0.0781250, 0.0781250, 0.0781250, 0.0820312,\n         0.0859375, 0.0898438, 0.0937500, 0.0937500, 0.0937500, 0.0937500,\n         0.0937500, 0.0976562, 0.1015625, 0.1054688, 0.1093750, 0.1171875,\n         0.1250000, 0.1328125, 0.1406250, 0.1484375, 0.1562500, 0.1640625,\n         0.1718750, 0.1796875, 0.1875000, 0.1953125, 0.2031250, 0.2109375,\n         0.2187500, 0.2265625, 0.2343750, 0.2382812, 0.2421875, 0.2460938,\n         0.2500000, 0.2578125, 0.2656250, 0.2734375, 0.2812500, 0.2890625,\n         0.2968750, 0.3046875, 0.3125000, 0.3203125, 0.3281250, 0.3359375,\n         0.3437500, 0.3515625, 0.3593750, 0.3671875, 0.3750000, 0.3828125,\n         0.3906250, 0.3984375, 0.4062500, 0.4179688, 0.4296875, 0.4414062,\n         0.4531250, 0.4648438, 0.4765625, 0.4882812, 0.5000000, 0.5117188,\n         0.5234375, 0.5351562, 0.5468750, 0.5585938, 0.5703125, 0.5820312,\n         0.5937500, 0.6054688, 0.6171875, 0.6289062, 0.6406250, 0.6523438,\n         0.6640625, 0.6757812, 0.6875000, 0.7031250, 0.7187500, 0.7343750,\n         0.7500000, 0.7695312, 0.7890625, 0.8085938, 0.8281250, 0.8476562,\n         0.8671875, 0.8867188, 0.9062500, 0.9257812, 0.9453125, 0.9648438,\n         0.9843750, 0.9843750, 0.9882812, 0.9921875]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0117188, 0.0117188, 0.0117188, 0.0117188,\n         0.0156250, 0.0156250, 0.0156250, 0.0156250, 0.0156250, 0.0195312,\n         0.0234375, 0.0273438, 0.0351562, 0.0351562, 0.0390625, 0.0429688,\n         0.0468750, 0.0507812, 0.0546875, 0.0585938, 0.0664062, 0.0664062,\n         0.0703125, 0.0703125, 0.0742188, 0.0781250, 0.0859375, 0.0898438,\n         0.0976562, 0.0976562, 0.1015625, 0.1015625, 0.1054688, 0.1093750,\n         0.1132812, 0.1171875, 0.1250000, 0.1250000, 0.1289062, 0.1289062,\n         0.1328125, 0.1367188, 0.1406250, 0.1445312, 0.1523438, 0.1523438,\n         0.1562500, 0.1562500, 0.1601562, 0.1640625, 0.1679688, 0.1718750,\n         0.1796875, 0.1796875, 0.1835938, 0.1875000, 0.1914062, 0.1953125,\n         0.1992188, 0.2031250, 0.2109375, 0.2109375, 0.2070312, 0.2070312,\n         0.2031250, 0.2109375, 0.2187500, 0.2265625, 0.2343750, 0.2343750,\n         0.2382812, 0.2421875, 0.2460938, 0.2500000, 0.2539062, 0.2578125,\n         0.2656250, 0.2695312, 0.2734375, 0.2773438, 0.2851562, 0.2890625,\n         0.2929688, 0.2968750, 0.3007812, 0.3046875, 0.3085938, 0.3125000,\n         0.3203125, 0.3203125, 0.3242188, 0.3242188, 0.3281250, 0.3320312,\n         0.3359375, 0.3398438, 0.3476562, 0.3515625, 0.3554688, 0.3593750,\n         0.3671875, 0.3710938, 0.3750000, 0.3789062, 0.3828125, 0.3867188,\n         0.3906250, 0.3945312, 0.4023438, 0.4023438, 0.4062500, 0.4101562,\n         0.4140625, 0.4179688, 0.4218750, 0.4257812, 0.4296875, 0.4335938,\n         0.4375000, 0.4414062, 0.4492188, 0.4531250, 0.4570312, 0.4609375,\n         0.4648438, 0.4687500, 0.4726562, 0.4765625, 0.4843750, 0.4843750,\n         0.4882812, 0.4882812, 0.4921875, 0.4960938, 0.5039062, 0.5117188,\n         0.5195312, 0.5234375, 0.5312500, 0.5390625, 0.5468750, 0.5507812,\n         0.5585938, 0.5625000, 0.5703125, 0.5742188, 0.5820312, 0.5898438,\n         0.5976562, 0.6015625, 0.6093750, 0.6132812, 0.6210938, 0.6250000,\n         0.6289062, 0.6328125, 0.6406250, 0.6445312, 0.6484375, 0.6523438,\n         0.6601562, 0.6640625, 0.6718750, 0.6757812, 0.6835938, 0.6875000,\n         0.6914062, 0.6953125, 0.7031250, 0.7070312, 0.7148438, 0.7187500,\n         0.7265625, 0.7304688, 0.7343750, 0.7382812, 0.7460938, 0.7500000,\n         0.7539062, 0.7578125, 0.7656250, 0.7695312, 0.7773438, 0.7812500,\n         0.7890625, 0.7929688, 0.7968750, 0.8007812, 0.8085938, 0.8125000,\n         0.8164062, 0.8203125, 0.8281250, 0.8320312, 0.8359375, 0.8398438,\n         0.8476562, 0.8515625, 0.8554688, 0.8593750, 0.8632812, 0.8671875,\n         0.8710938, 0.8750000, 0.8828125, 0.8867188, 0.8906250, 0.8945312,\n         0.8984375, 0.9023438, 0.9062500, 0.9101562, 0.9179688, 0.9218750,\n         0.9257812, 0.9296875, 0.9335938, 0.9375000, 0.9414062, 0.9453125,\n         0.9492188, 0.9492188, 0.9531250, 0.9570312, 0.9609375, 0.9609375,\n         0.9648438, 0.9687500, 0.9726562, 0.9726562, 0.9765625, 0.9804688,\n         0.9843750, 0.9843750, 0.9882812, 0.9921875]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 8 :: GREEN ###\n\ncolor_map_luts['idl08'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0039062, 0.0117188, 0.0156250, 0.0234375,\n         0.0312500, 0.0351562, 0.0429688, 0.0468750, 0.0546875, 0.0625000,\n         0.0664062, 0.0742188, 0.0781250, 0.0859375, 0.0937500, 0.0976562,\n         0.1054688, 0.1132812, 0.1171875, 0.1250000, 0.1289062, 0.1367188,\n         0.1445312, 0.1484375, 0.1562500, 0.1601562, 0.1679688, 0.1757812,\n         0.1796875, 0.1875000, 0.1953125, 0.1992188, 0.2070312, 0.2109375,\n         0.2187500, 0.2265625, 0.2304688, 0.2382812, 0.2421875, 0.2500000,\n         0.2578125, 0.2617188, 0.2695312, 0.2773438, 0.2812500, 0.2890625,\n         0.2929688, 0.3007812, 0.3085938, 0.3125000, 0.3203125, 0.3242188,\n         0.3320312, 0.3398438, 0.3437500, 0.3515625, 0.3554688, 0.3632812,\n         0.3710938, 0.3750000, 0.3828125, 0.3906250, 0.3945312, 0.4023438,\n         0.4062500, 0.4140625, 0.4218750, 0.4257812, 0.4335938, 0.4375000,\n         0.4453125, 0.4531250, 0.4570312, 0.4648438, 0.4726562, 0.4765625,\n         0.4843750, 0.4882812, 0.4960938, 0.5039062, 0.5078125, 0.5156250,\n         0.5195312, 0.5273438, 0.5351562, 0.5390625, 0.5468750, 0.5546875,\n         0.5585938, 0.5664062, 0.5703125, 0.5781250, 0.5859375, 0.5898438,\n         0.5976562, 0.6015625, 0.6093750, 0.6171875, 0.6210938, 0.6289062,\n         0.6367188, 0.6406250, 0.6484375, 0.6523438, 0.6601562, 0.6679688,\n         0.6718750, 0.6796875, 0.6835938, 0.6914062, 0.6992188, 0.7031250,\n         0.7109375, 0.7148438, 0.7226562, 0.7304688, 0.7343750, 0.7421875,\n         0.7500000, 0.7539062, 0.7617188, 0.7656250, 0.7734375, 0.7812500,\n         0.7851562, 0.7929688, 0.7968750, 0.8046875, 0.8125000, 0.8164062,\n         0.8242188, 0.8320312, 0.8359375, 0.8437500, 0.8476562, 0.8554688,\n         0.8632812, 0.8671875, 0.8750000, 0.8789062, 0.8867188, 0.8945312,\n         0.8984375, 0.9062500, 0.9140625, 0.9179688, 0.9257812, 0.9296875,\n         0.9375000, 0.9453125, 0.9492188, 0.9570312, 0.9609375, 0.9687500,\n         0.9765625, 0.9804688, 0.9882812, 0.9960938]),\narray([  0.0000000, 0.0039062, 0.0078125, 0.0117188, 0.0156250, 0.0195312,\n         0.0234375, 0.0273438, 0.0312500, 0.0351562, 0.0390625, 0.0429688,\n         0.0468750, 0.0507812, 0.0546875, 0.0585938, 0.0625000, 0.0664062,\n         0.0703125, 0.0742188, 0.0781250, 0.0820312, 0.0859375, 0.0898438,\n         0.0937500, 0.0976562, 0.1015625, 0.1054688, 0.1093750, 0.1132812,\n         0.1171875, 0.1210938, 0.1250000, 0.1289062, 0.1328125, 0.1367188,\n         0.1406250, 0.1445312, 0.1484375, 0.1523438, 0.1562500, 0.1601562,\n         0.1640625, 0.1679688, 0.1718750, 0.1757812, 0.1796875, 0.1835938,\n         0.1875000, 0.1914062, 0.1953125, 0.1992188, 0.2031250, 0.2070312,\n         0.2109375, 0.2148438, 0.2187500, 0.2226562, 0.2265625, 0.2304688,\n         0.2343750, 0.2382812, 0.2421875, 0.2460938, 0.2500000, 0.2539062,\n         0.2578125, 0.2617188, 0.2656250, 0.2695312, 0.2734375, 0.2773438,\n         0.2812500, 0.2851562, 0.2890625, 0.2929688, 0.2968750, 0.3007812,\n         0.3046875, 0.3085938, 0.3125000, 0.3164062, 0.3203125, 0.3242188,\n         0.3281250, 0.3320312, 0.3359375, 0.3398438, 0.3437500, 0.3476562,\n         0.3515625, 0.3554688, 0.3593750, 0.3632812, 0.3671875, 0.3710938,\n         0.3750000, 0.3789062, 0.3828125, 0.3867188, 0.3906250, 0.3945312,\n         0.3984375, 0.4023438, 0.4062500, 0.4101562, 0.4140625, 0.4179688,\n         0.4218750, 0.4257812, 0.4296875, 0.4335938, 0.4375000, 0.4414062,\n         0.4453125, 0.4492188, 0.4531250, 0.4570312, 0.4609375, 0.4648438,\n         0.4687500, 0.4726562, 0.4765625, 0.4804688, 0.4843750, 0.4882812,\n         0.4921875, 0.4960938, 0.5000000, 0.5039062, 0.5078125, 0.5117188,\n         0.5156250, 0.5195312, 0.5234375, 0.5273438, 0.5312500, 0.5351562,\n         0.5390625, 0.5429688, 0.5468750, 0.5507812, 0.5546875, 0.5585938,\n         0.5625000, 0.5664062, 0.5703125, 0.5742188, 0.5781250, 0.5820312,\n         0.5859375, 0.5898438, 0.5937500, 0.5976562, 0.6015625, 0.6054688,\n         0.6093750, 0.6132812, 0.6171875, 0.6210938, 0.6250000, 0.6289062,\n         0.6328125, 0.6367188, 0.6406250, 0.6445312, 0.6484375, 0.6523438,\n         0.6562500, 0.6601562, 0.6640625, 0.6679688, 0.6718750, 0.6757812,\n         0.6796875, 0.6835938, 0.6875000, 0.6914062, 0.6953125, 0.6992188,\n         0.7031250, 0.7070312, 0.7109375, 0.7148438, 0.7187500, 0.7226562,\n         0.7265625, 0.7304688, 0.7343750, 0.7382812, 0.7421875, 0.7460938,\n         0.7500000, 0.7539062, 0.7578125, 0.7617188, 0.7656250, 0.7695312,\n         0.7734375, 0.7773438, 0.7812500, 0.7851562, 0.7890625, 0.7929688,\n         0.7968750, 0.8007812, 0.8046875, 0.8085938, 0.8125000, 0.8164062,\n         0.8203125, 0.8242188, 0.8281250, 0.8320312, 0.8359375, 0.8398438,\n         0.8437500, 0.8476562, 0.8515625, 0.8554688, 0.8593750, 0.8632812,\n         0.8671875, 0.8710938, 0.8750000, 0.8789062, 0.8828125, 0.8867188,\n         0.8906250, 0.8945312, 0.8984375, 0.9023438, 0.9062500, 0.9101562,\n         0.9140625, 0.9179688, 0.9218750, 0.9257812, 0.9296875, 0.9335938,\n         0.9375000, 0.9414062, 0.9453125, 0.9492188, 0.9531250, 0.9570312,\n         0.9609375, 0.9648438, 0.9687500, 0.9726562, 0.9765625, 0.9804688,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0117188, 0.0234375, 0.0390625, 0.0507812,\n         0.0664062, 0.0781250, 0.0937500, 0.1054688, 0.1210938, 0.1328125,\n         0.1445312, 0.1601562, 0.1718750, 0.1875000, 0.1992188, 0.2148438,\n         0.2265625, 0.2421875, 0.2539062, 0.2656250, 0.2812500, 0.2929688,\n         0.3085938, 0.3203125, 0.3359375, 0.3476562, 0.3632812, 0.3750000,\n         0.3867188, 0.4023438, 0.4140625, 0.4296875, 0.4414062, 0.4570312,\n         0.4687500, 0.4843750, 0.4960938, 0.5078125, 0.5234375, 0.5351562,\n         0.5507812, 0.5625000, 0.5781250, 0.5898438, 0.6054688, 0.6171875,\n         0.6289062, 0.6445312, 0.6562500, 0.6718750, 0.6835938, 0.6992188,\n         0.7109375, 0.7265625, 0.7382812, 0.7500000, 0.7656250, 0.7773438,\n         0.7929688, 0.8046875, 0.8203125, 0.8320312, 0.8476562, 0.8593750,\n         0.8710938, 0.8867188, 0.8984375, 0.9140625, 0.9257812, 0.9414062,\n         0.9531250, 0.9687500, 0.9804688, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 9 :: GRN ###\n\ncolor_map_luts['idl09'] = \\\n   (\narray([  0.0039062, 0.0039062, 0.0039062, 0.0078125, 0.0078125, 0.0078125,\n         0.0117188, 0.0117188, 0.0156250, 0.0156250, 0.0156250, 0.0195312,\n         0.0195312, 0.0195312, 0.0234375, 0.0234375, 0.0273438, 0.0273438,\n         0.0273438, 0.0312500, 0.0312500, 0.0351562, 0.0351562, 0.0351562,\n         0.0390625, 0.0390625, 0.0390625, 0.0429688, 0.0429688, 0.0468750,\n         0.0468750, 0.0468750, 0.0507812, 0.0507812, 0.0507812, 0.0546875,\n         0.0546875, 0.0585938, 0.0585938, 0.0585938, 0.0625000, 0.0625000,\n         0.0664062, 0.0664062, 0.0664062, 0.0703125, 0.0703125, 0.0703125,\n         0.0742188, 0.0742188, 0.0781250, 0.0781250, 0.0781250, 0.0820312,\n         0.0820312, 0.0820312, 0.0859375, 0.0859375, 0.0898438, 0.0898438,\n         0.0898438, 0.0937500, 0.0937500, 0.0976562, 0.0976562, 0.0976562,\n         0.1015625, 0.1015625, 0.1015625, 0.1054688, 0.1054688, 0.1093750,\n         0.1093750, 0.1093750, 0.1132812, 0.1132812, 0.1132812, 0.1171875,\n         0.1171875, 0.1210938, 0.1210938, 0.1210938, 0.1250000, 0.1250000,\n         0.1289062, 0.1289062, 0.1289062, 0.1328125, 0.1328125, 0.1328125,\n         0.1367188, 0.1367188, 0.1406250, 0.1406250, 0.1406250, 0.1445312,\n         0.1445312, 0.1445312, 0.1484375, 0.1484375, 0.1523438, 0.1523438,\n         0.1523438, 0.1562500, 0.1562500, 0.1601562, 0.1601562, 0.1601562,\n         0.1640625, 0.1640625, 0.1640625, 0.1679688, 0.1679688, 0.1718750,\n         0.1718750, 0.1718750, 0.1757812, 0.1757812, 0.1757812, 0.1796875,\n         0.1796875, 0.1835938, 0.1835938, 0.1835938, 0.1875000, 0.1875000,\n         0.1914062, 0.1914062, 0.1914062, 0.1953125, 0.1953125, 0.1953125,\n         0.1992188, 0.1992188, 0.2031250, 0.2031250, 0.2031250, 0.2109375,\n         0.2109375, 0.2148438, 0.2187500, 0.2187500, 0.2226562, 0.2265625,\n         0.2265625, 0.2304688, 0.2343750, 0.2343750, 0.2382812, 0.2421875,\n         0.2421875, 0.2460938, 0.2500000, 0.2500000, 0.2578125, 0.2656250,\n         0.2734375, 0.2773438, 0.2851562, 0.2929688, 0.3007812, 0.3085938,\n         0.3164062, 0.3242188, 0.3320312, 0.3359375, 0.3437500, 0.3515625,\n         0.3593750, 0.3671875, 0.3750000, 0.3828125, 0.3867188, 0.3945312,\n         0.4023438, 0.4101562, 0.4179688, 0.4257812, 0.4335938, 0.4414062,\n         0.4453125, 0.4531250, 0.4609375, 0.4687500, 0.4765625, 0.4843750,\n         0.4921875, 0.4960938, 0.5039062, 0.5117188, 0.5195312, 0.5273438,\n         0.5351562, 0.5429688, 0.5507812, 0.5546875, 0.5625000, 0.5703125,\n         0.5781250, 0.5859375, 0.5937500, 0.6015625, 0.6093750, 0.6132812,\n         0.6210938, 0.6289062, 0.6367188, 0.6445312, 0.6523438, 0.6601562,\n         0.6640625, 0.6718750, 0.6796875, 0.6875000, 0.6953125, 0.7031250,\n         0.7109375, 0.7187500, 0.7226562, 0.7304688, 0.7382812, 0.7460938,\n         0.7539062, 0.7617188, 0.7695312, 0.7734375, 0.7812500, 0.7890625,\n         0.7968750, 0.8046875, 0.8125000, 0.8203125, 0.8281250, 0.8320312,\n         0.8398438, 0.8476562, 0.8554688, 0.8632812, 0.8710938, 0.8789062,\n         0.8828125, 0.8906250, 0.8984375, 0.9062500, 0.9140625, 0.9218750,\n         0.9296875, 0.9375000, 0.9414062, 0.9492188, 0.9570312, 0.9648438,\n         0.9726562, 0.9804688, 0.9882812, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0039062, 0.0078125, 0.0078125, 0.0117188,\n         0.0156250, 0.0156250, 0.0195312, 0.0234375, 0.0234375, 0.0273438,\n         0.0312500, 0.0312500, 0.0351562, 0.0390625, 0.0390625, 0.0429688,\n         0.0468750, 0.0468750, 0.0507812, 0.0546875, 0.0546875, 0.0585938,\n         0.0625000, 0.0625000, 0.0664062, 0.0703125, 0.0703125, 0.0742188,\n         0.0781250, 0.0781250, 0.0820312, 0.0859375, 0.0859375, 0.0898438,\n         0.0937500, 0.0937500, 0.0976562, 0.1015625, 0.1015625, 0.1054688,\n         0.1093750, 0.1093750, 0.1132812, 0.1171875, 0.1171875, 0.1210938,\n         0.1250000, 0.1250000, 0.1289062, 0.1328125, 0.1328125, 0.1367188,\n         0.1406250, 0.1406250, 0.1445312, 0.1484375, 0.1484375, 0.1523438,\n         0.1562500, 0.1562500, 0.1601562, 0.1640625, 0.1640625, 0.1679688,\n         0.1718750, 0.1718750, 0.1757812, 0.1796875, 0.1796875, 0.1835938,\n         0.1875000, 0.1875000, 0.1914062, 0.1953125, 0.1953125, 0.1992188,\n         0.2031250, 0.2031250, 0.2070312, 0.2109375, 0.2109375, 0.2148438,\n         0.2187500, 0.2187500, 0.2226562, 0.2265625, 0.2265625, 0.2304688,\n         0.2343750, 0.2343750, 0.2382812, 0.2421875, 0.2421875, 0.2460938,\n         0.2500000, 0.2500000, 0.2539062, 0.2578125, 0.2578125, 0.2617188,\n         0.2656250, 0.2656250, 0.2695312, 0.2734375, 0.2734375, 0.2773438,\n         0.2812500, 0.2812500, 0.2851562, 0.2890625, 0.2890625, 0.2929688,\n         0.2968750, 0.2968750, 0.3007812, 0.3046875, 0.3046875, 0.3085938,\n         0.3125000, 0.3125000, 0.3164062, 0.3203125, 0.3203125, 0.3242188,\n         0.3281250, 0.3281250, 0.3320312, 0.3359375, 0.3398438, 0.3437500,\n         0.3515625, 0.3554688, 0.3593750, 0.3671875, 0.3710938, 0.3750000,\n         0.3828125, 0.3867188, 0.3906250, 0.3984375, 0.4023438, 0.4062500,\n         0.4140625, 0.4179688, 0.4218750, 0.4296875, 0.4335938, 0.4375000,\n         0.4453125, 0.4492188, 0.4531250, 0.4609375, 0.4648438, 0.4687500,\n         0.4765625, 0.4804688, 0.4843750, 0.4921875, 0.4960938, 0.5000000,\n         0.5078125, 0.5117188, 0.5156250, 0.5234375, 0.5273438, 0.5312500,\n         0.5390625, 0.5429688, 0.5468750, 0.5546875, 0.5585938, 0.5664062,\n         0.5703125, 0.5742188, 0.5820312, 0.5859375, 0.5898438, 0.5976562,\n         0.6015625, 0.6054688, 0.6132812, 0.6171875, 0.6210938, 0.6289062,\n         0.6328125, 0.6367188, 0.6445312, 0.6484375, 0.6523438, 0.6601562,\n         0.6640625, 0.6679688, 0.6757812, 0.6796875, 0.6835938, 0.6914062,\n         0.6953125, 0.6992188, 0.7070312, 0.7109375, 0.7148438, 0.7226562,\n         0.7265625, 0.7304688, 0.7382812, 0.7421875, 0.7460938, 0.7539062,\n         0.7578125, 0.7617188, 0.7695312, 0.7734375, 0.7812500, 0.7851562,\n         0.7890625, 0.7968750, 0.8007812, 0.8046875, 0.8125000, 0.8164062,\n         0.8203125, 0.8281250, 0.8320312, 0.8359375, 0.8437500, 0.8476562,\n         0.8515625, 0.8593750, 0.8632812, 0.8671875, 0.8750000, 0.8789062,\n         0.8828125, 0.8906250, 0.8945312, 0.8984375, 0.9062500, 0.9101562,\n         0.9140625, 0.9218750, 0.9257812, 0.9296875, 0.9375000, 0.9414062,\n         0.9453125, 0.9531250, 0.9570312, 0.9609375, 0.9687500, 0.9726562,\n         0.9765625, 0.9843750, 0.9882812, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0117188, 0.0117188, 0.0117188, 0.0117188, 0.0156250, 0.0156250,\n         0.0156250, 0.0195312, 0.0195312, 0.0195312, 0.0195312, 0.0234375,\n         0.0234375, 0.0234375, 0.0234375, 0.0273438, 0.0273438, 0.0273438,\n         0.0273438, 0.0312500, 0.0312500, 0.0312500, 0.0312500, 0.0351562,\n         0.0351562, 0.0351562, 0.0390625, 0.0390625, 0.0390625, 0.0390625,\n         0.0429688, 0.0429688, 0.0429688, 0.0429688, 0.0468750, 0.0468750,\n         0.0468750, 0.0468750, 0.0507812, 0.0507812, 0.0507812, 0.0507812,\n         0.0546875, 0.0546875, 0.0546875, 0.0585938, 0.0585938, 0.0585938,\n         0.0585938, 0.0625000, 0.0625000, 0.0625000, 0.0625000, 0.0664062,\n         0.0664062, 0.0664062, 0.0664062, 0.0703125, 0.0703125, 0.0703125,\n         0.0703125, 0.0742188, 0.0742188, 0.0742188, 0.0781250, 0.0781250,\n         0.0781250, 0.0781250, 0.0820312, 0.0820312, 0.0820312, 0.0820312,\n         0.0859375, 0.0859375, 0.0859375, 0.0859375, 0.0898438, 0.0898438,\n         0.0898438, 0.0898438, 0.0937500, 0.0937500, 0.0937500, 0.0976562,\n         0.0976562, 0.0976562, 0.0976562, 0.1015625, 0.1015625, 0.1015625,\n         0.1015625, 0.1054688, 0.1054688, 0.1054688, 0.1054688, 0.1093750,\n         0.1093750, 0.1093750, 0.1093750, 0.1132812, 0.1132812, 0.1132812,\n         0.1171875, 0.1171875, 0.1171875, 0.1171875, 0.1210938, 0.1210938,\n         0.1210938, 0.1210938, 0.1250000, 0.1250000, 0.1250000, 0.1250000,\n         0.1289062, 0.1289062, 0.1289062, 0.1289062, 0.1328125, 0.1328125,\n         0.1328125, 0.1367188, 0.1367188, 0.1367188, 0.1367188, 0.1406250,\n         0.1406250, 0.1406250, 0.1406250, 0.1445312, 0.1445312, 0.1445312,\n         0.1445312, 0.1484375, 0.1484375, 0.1484375, 0.1484375, 0.1523438,\n         0.1523438, 0.1523438, 0.1562500, 0.1562500, 0.1562500, 0.1562500,\n         0.1601562, 0.1601562, 0.1601562, 0.1601562, 0.1640625, 0.1640625,\n         0.1640625, 0.1640625, 0.1679688, 0.1679688, 0.1679688, 0.1679688,\n         0.1718750, 0.1718750, 0.1718750, 0.1757812, 0.1757812, 0.1757812,\n         0.1757812, 0.1796875, 0.1796875, 0.1796875, 0.1796875, 0.1835938,\n         0.1835938, 0.1835938, 0.1835938, 0.1875000, 0.1835938, 0.1914062,\n         0.2031250, 0.2148438, 0.2265625, 0.2382812, 0.2500000, 0.2617188,\n         0.2734375, 0.2851562, 0.2968750, 0.3085938, 0.3203125, 0.3320312,\n         0.3437500, 0.3515625, 0.3632812, 0.3750000, 0.3867188, 0.3984375,\n         0.4101562, 0.4218750, 0.4335938, 0.4453125, 0.4570312, 0.4687500,\n         0.4804688, 0.4921875, 0.5039062, 0.5117188, 0.5234375, 0.5351562,\n         0.5468750, 0.5585938, 0.5703125, 0.5820312, 0.5937500, 0.6054688,\n         0.6171875, 0.6289062, 0.6406250, 0.6523438, 0.6640625, 0.6718750,\n         0.6835938, 0.6953125, 0.7070312, 0.7187500, 0.7304688, 0.7421875,\n         0.7539062, 0.7656250, 0.7773438, 0.7890625, 0.8007812, 0.8125000,\n         0.8242188, 0.8320312, 0.8437500, 0.8554688, 0.8671875, 0.8789062,\n         0.8906250, 0.9023438, 0.9140625, 0.9257812, 0.9375000, 0.9492188,\n         0.9609375, 0.9726562, 0.9843750, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 10 :: GREEN-PINK ###\n\ncolor_map_luts['idl10'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0195312,\n         0.0390625, 0.0585938, 0.0781250, 0.0976562, 0.1171875, 0.1367188,\n         0.1562500, 0.1757812, 0.1953125, 0.2148438, 0.2343750, 0.2539062,\n         0.2734375, 0.2929688, 0.3125000, 0.3281250, 0.3437500, 0.3593750,\n         0.3750000, 0.3945312, 0.4140625, 0.4335938, 0.4531250, 0.4726562,\n         0.4921875, 0.5117188, 0.5312500, 0.5507812, 0.5703125, 0.5898438,\n         0.6093750, 0.6210938, 0.6328125, 0.6445312, 0.6562500, 0.6679688,\n         0.6796875, 0.6914062, 0.7031250, 0.7148438, 0.7265625, 0.7382812,\n         0.7500000, 0.7617188, 0.7734375, 0.7851562, 0.7968750, 0.8085938,\n         0.8203125, 0.8320312, 0.8437500, 0.8554688, 0.8671875, 0.8789062,\n         0.8906250, 0.9023438, 0.9140625, 0.9257812, 0.9375000, 0.9492188,\n         0.9609375, 0.9726562, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750, 0.9843750,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0546875, 0.1093750,\n         0.1679688, 0.2226562, 0.2812500, 0.3164062, 0.3515625, 0.3867188,\n         0.4218750, 0.4570312, 0.4921875, 0.5273438, 0.5625000, 0.5976562,\n         0.6328125, 0.6679688, 0.7031250, 0.6992188, 0.6953125, 0.6914062,\n         0.6875000, 0.6835938, 0.6796875, 0.6757812, 0.6718750, 0.6679688,\n         0.6640625, 0.6601562, 0.6562500, 0.6523438, 0.6484375, 0.6445312,\n         0.6406250, 0.6367188, 0.6328125, 0.6289062, 0.6250000, 0.6210938,\n         0.6171875, 0.6132812, 0.6093750, 0.6015625, 0.5937500, 0.5859375,\n         0.5781250, 0.5742188, 0.5703125, 0.5664062, 0.5625000, 0.5585938,\n         0.5546875, 0.5507812, 0.5468750, 0.5429688, 0.5390625, 0.5351562,\n         0.5312500, 0.5273438, 0.5234375, 0.5195312, 0.5156250, 0.5117188,\n         0.5078125, 0.5039062, 0.5000000, 0.4921875, 0.4843750, 0.4765625,\n         0.4687500, 0.4648438, 0.4609375, 0.4570312, 0.4531250, 0.4492188,\n         0.4453125, 0.4414062, 0.4375000, 0.4335938, 0.4296875, 0.4257812,\n         0.4218750, 0.4179688, 0.4140625, 0.4101562, 0.4062500, 0.4023438,\n         0.3984375, 0.3945312, 0.3906250, 0.3867188, 0.3828125, 0.3789062,\n         0.3750000, 0.3671875, 0.3593750, 0.3515625, 0.3437500, 0.3398438,\n         0.3359375, 0.3320312, 0.3281250, 0.3242188, 0.3203125, 0.3164062,\n         0.3125000, 0.3085938, 0.3046875, 0.3007812, 0.2968750, 0.2929688,\n         0.2890625, 0.2851562, 0.2812500, 0.2773438, 0.2734375, 0.2695312,\n         0.2656250, 0.2578125, 0.2500000, 0.2421875, 0.2343750, 0.2304688,\n         0.2265625, 0.2226562, 0.2187500, 0.2109375, 0.2031250, 0.1953125,\n         0.1875000, 0.1835938, 0.1796875, 0.1757812, 0.1718750, 0.1640625,\n         0.1562500, 0.1484375, 0.1406250, 0.1328125, 0.1250000, 0.1171875,\n         0.1093750, 0.1015625, 0.0937500, 0.0859375, 0.0781250, 0.0742188,\n         0.0703125, 0.0664062, 0.0625000, 0.0546875, 0.0468750, 0.0390625,\n         0.0312500, 0.0234375, 0.0156250, 0.0078125, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0312500, 0.0468750,\n         0.0625000, 0.0781250, 0.0937500, 0.1093750, 0.1250000, 0.1406250,\n         0.1562500, 0.1718750, 0.1875000, 0.2031250, 0.2187500, 0.2343750,\n         0.2500000, 0.2656250, 0.2812500, 0.2968750, 0.3125000, 0.3281250,\n         0.3437500, 0.3593750, 0.3750000, 0.3906250, 0.4062500, 0.4218750,\n         0.4375000, 0.4531250, 0.4687500, 0.4843750, 0.5000000, 0.5156250,\n         0.5312500, 0.5468750, 0.5625000, 0.5742188, 0.5859375, 0.5976562,\n         0.6093750, 0.6250000, 0.6406250, 0.6562500, 0.6718750, 0.6875000,\n         0.7031250, 0.7187500, 0.7343750, 0.7500000, 0.7656250, 0.7812500,\n         0.7968750, 0.8125000, 0.8281250, 0.8437500, 0.8593750, 0.8750000,\n         0.8906250, 0.9062500, 0.9218750, 0.9375000, 0.9531250, 0.9687500,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0039062, 0.0117188, 0.0156250,\n         0.0234375, 0.0312500, 0.0390625, 0.0468750, 0.0546875, 0.0585938,\n         0.0664062, 0.0742188, 0.0820312, 0.0898438, 0.0976562, 0.1054688,\n         0.1132812, 0.1210938, 0.1289062, 0.1367188, 0.1445312, 0.1484375,\n         0.1562500, 0.1640625, 0.1718750, 0.1796875, 0.1875000, 0.1953125,\n         0.2070312, 0.2109375, 0.2187500, 0.2265625, 0.2343750, 0.2421875,\n         0.2500000, 0.2578125, 0.2656250, 0.2695312, 0.2773438, 0.2851562,\n         0.2929688, 0.3007812, 0.3085938, 0.3164062, 0.3242188, 0.3281250,\n         0.3359375, 0.3437500, 0.3515625, 0.3593750, 0.3671875, 0.3750000,\n         0.3867188, 0.3906250, 0.3984375, 0.4062500, 0.4140625, 0.4218750,\n         0.4296875, 0.4375000, 0.4453125, 0.4531250, 0.4609375, 0.4687500,\n         0.4765625, 0.4804688, 0.4882812, 0.4960938, 0.5039062, 0.5117188,\n         0.5195312, 0.5273438, 0.5351562, 0.5390625, 0.5468750, 0.5546875,\n         0.5625000, 0.5703125, 0.5781250, 0.5859375, 0.5976562, 0.6015625,\n         0.6093750, 0.6171875, 0.6250000, 0.6328125, 0.6406250, 0.6484375,\n         0.6562500, 0.6640625, 0.6718750, 0.6796875, 0.6875000, 0.6914062,\n         0.6992188, 0.7070312, 0.7148438, 0.7226562, 0.7304688, 0.7382812,\n         0.7460938, 0.7539062, 0.7617188, 0.7695312, 0.7773438, 0.7773438,\n         0.7734375, 0.7695312, 0.7656250, 0.7656250, 0.7656250, 0.7656250,\n         0.7617188, 0.7617188, 0.7578125, 0.7539062, 0.7500000, 0.7500000,\n         0.7460938, 0.7460938, 0.7421875, 0.7421875, 0.7421875, 0.7421875,\n         0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7382812, 0.7382812,\n         0.7343750, 0.7304688, 0.7265625, 0.7265625, 0.7226562, 0.7226562,\n         0.7187500, 0.7187500, 0.7226562, 0.7265625, 0.7304688, 0.7304688,\n         0.7304688, 0.7304688, 0.7304688, 0.7304688, 0.7304688, 0.7304688,\n         0.7343750, 0.7343750, 0.7343750, 0.7343750, 0.7382812, 0.7382812,\n         0.7382812, 0.7382812, 0.7382812, 0.7382812, 0.7382812, 0.7382812,\n         0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7460938, 0.7460938,\n         0.7460938, 0.7460938, 0.7460938, 0.7500000, 0.7539062, 0.7578125,\n         0.7617188, 0.7656250, 0.7695312, 0.7734375, 0.7773438, 0.7812500,\n         0.7851562, 0.7890625, 0.7929688, 0.7929688, 0.7968750, 0.8007812,\n         0.8046875, 0.8085938, 0.8125000, 0.8164062, 0.8203125, 0.8242188,\n         0.8281250, 0.8320312, 0.8359375, 0.8359375, 0.8398438, 0.8437500,\n         0.8476562, 0.8515625, 0.8554688, 0.8593750, 0.8632812, 0.8671875,\n         0.8710938, 0.8750000, 0.8789062, 0.8828125, 0.8867188, 0.8906250,\n         0.8945312, 0.8984375, 0.9023438, 0.9062500, 0.9101562, 0.9140625,\n         0.9179688, 0.9218750, 0.9257812, 0.9257812, 0.9296875, 0.9335938,\n         0.9375000, 0.9414062, 0.9453125, 0.9492188, 0.9531250, 0.9570312,\n         0.9609375, 0.9648438, 0.9687500, 0.9726562, 0.9765625, 0.9804688,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 11 :: BLUE-RED ###\n\ncolor_map_luts['idl11'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0312500, 0.0468750,\n         0.0625000, 0.0781250, 0.0937500, 0.1093750, 0.1250000, 0.1406250,\n         0.1562500, 0.1718750, 0.1875000, 0.2031250, 0.2187500, 0.2343750,\n         0.2500000, 0.2656250, 0.2812500, 0.2968750, 0.3125000, 0.3320312,\n         0.3476562, 0.3632812, 0.3789062, 0.3945312, 0.4101562, 0.4257812,\n         0.4414062, 0.4570312, 0.4726562, 0.4882812, 0.5039062, 0.5195312,\n         0.5351562, 0.5507812, 0.5664062, 0.5820312, 0.5976562, 0.6132812,\n         0.6289062, 0.6445312, 0.6640625, 0.6796875, 0.6953125, 0.7109375,\n         0.7265625, 0.7421875, 0.7578125, 0.7734375, 0.7890625, 0.8046875,\n         0.8203125, 0.8359375, 0.8515625, 0.8671875, 0.8828125, 0.8984375,\n         0.9140625, 0.9296875, 0.9453125, 0.9609375, 0.9765625, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0039062, 0.0078125, 0.0117188, 0.0156250, 0.0312500,\n         0.0468750, 0.0625000, 0.0820312, 0.0976562, 0.1132812, 0.1289062,\n         0.1484375, 0.1640625, 0.1796875, 0.1953125, 0.2148438, 0.2304688,\n         0.2460938, 0.2617188, 0.2812500, 0.2968750, 0.3125000, 0.3281250,\n         0.3476562, 0.3632812, 0.3789062, 0.3945312, 0.4140625, 0.4296875,\n         0.4453125, 0.4609375, 0.4804688, 0.4960938, 0.5117188, 0.5273438,\n         0.5468750, 0.5625000, 0.5781250, 0.5937500, 0.6132812, 0.6289062,\n         0.6445312, 0.6601562, 0.6796875, 0.6953125, 0.7109375, 0.7265625,\n         0.7460938, 0.7617188, 0.7773438, 0.7929688, 0.8125000, 0.8281250,\n         0.8437500, 0.8593750, 0.8789062, 0.8945312, 0.9101562, 0.9257812,\n         0.9453125, 0.9609375, 0.9765625, 0.9960938, 0.9960938, 0.9804688,\n         0.9648438, 0.9492188, 0.9335938, 0.9179688, 0.9023438, 0.8867188,\n         0.8710938, 0.8554688, 0.8398438, 0.8242188, 0.8085938, 0.7929688,\n         0.7773438, 0.7617188, 0.7460938, 0.7304688, 0.7148438, 0.6992188,\n         0.6835938, 0.6640625, 0.6484375, 0.6328125, 0.6171875, 0.6015625,\n         0.5859375, 0.5703125, 0.5546875, 0.5390625, 0.5234375, 0.5078125,\n         0.4921875, 0.4765625, 0.4609375, 0.4453125, 0.4296875, 0.4140625,\n         0.3984375, 0.3828125, 0.3671875, 0.3515625, 0.3320312, 0.3164062,\n         0.3007812, 0.2851562, 0.2695312, 0.2539062, 0.2382812, 0.2226562,\n         0.2070312, 0.1914062, 0.1757812, 0.1601562, 0.1445312, 0.1289062,\n         0.1132812, 0.0976562, 0.0820312, 0.0664062, 0.0507812, 0.0351562,\n         0.0195312, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.0000000, 0.0039062, 0.0078125, 0.0117188, 0.0156250, 0.0312500,\n         0.0468750, 0.0625000, 0.0820312, 0.0976562, 0.1132812, 0.1289062,\n         0.1484375, 0.1640625, 0.1796875, 0.1953125, 0.2148438, 0.2304688,\n         0.2460938, 0.2617188, 0.2812500, 0.2968750, 0.3125000, 0.3281250,\n         0.3476562, 0.3632812, 0.3789062, 0.3945312, 0.4140625, 0.4296875,\n         0.4453125, 0.4609375, 0.4804688, 0.4960938, 0.5117188, 0.5273438,\n         0.5468750, 0.5625000, 0.5781250, 0.5937500, 0.6132812, 0.6289062,\n         0.6445312, 0.6601562, 0.6796875, 0.6953125, 0.7109375, 0.7265625,\n         0.7460938, 0.7617188, 0.7773438, 0.7929688, 0.8125000, 0.8281250,\n         0.8437500, 0.8593750, 0.8789062, 0.8945312, 0.9101562, 0.9257812,\n         0.9453125, 0.9609375, 0.9765625, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9804688, 0.9648438, 0.9492188, 0.9335938, 0.9179688,\n         0.9023438, 0.8867188, 0.8710938, 0.8515625, 0.8359375, 0.8203125,\n         0.8046875, 0.7890625, 0.7734375, 0.7578125, 0.7421875, 0.7265625,\n         0.7070312, 0.6914062, 0.6757812, 0.6601562, 0.6445312, 0.6289062,\n         0.6132812, 0.5976562, 0.5820312, 0.5625000, 0.5468750, 0.5312500,\n         0.5156250, 0.5000000, 0.4843750, 0.4687500, 0.4531250, 0.4375000,\n         0.4179688, 0.4023438, 0.3867188, 0.3710938, 0.3554688, 0.3398438,\n         0.3242188, 0.3085938, 0.2929688, 0.2734375, 0.2578125, 0.2421875,\n         0.2265625, 0.2109375, 0.1953125, 0.1796875, 0.1640625, 0.1484375,\n         0.1289062, 0.1132812, 0.0976562, 0.0820312, 0.0664062, 0.0507812,\n         0.0351562, 0.0195312, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 12 :: 16 LEVEL ###\n\ncolor_map_luts['idl12'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.5000000, 0.5000000,\n         0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000,\n         0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000,\n         0.5000000, 0.5000000, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8593750, 0.8593750,\n         0.8593750, 0.8593750, 0.8593750, 0.8632812, 0.8632812, 0.8632812,\n         0.8632812, 0.8632812, 0.8671875, 0.8671875, 0.8671875, 0.8671875,\n         0.8671875, 0.8710938, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.3281250, 0.3281250, 0.3281250, 0.3281250, 0.3281250,\n         0.3281250, 0.3281250, 0.3281250, 0.3281250, 0.3281250, 0.3281250,\n         0.3281250, 0.3281250, 0.3281250, 0.3281250, 0.6562500, 0.6562500,\n         0.6562500, 0.6562500, 0.6562500, 0.6562500, 0.6562500, 0.6562500,\n         0.6562500, 0.6562500, 0.6562500, 0.6562500, 0.6562500, 0.6562500,\n         0.6562500, 0.6562500, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.7421875, 0.7421875,\n         0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7421875,\n         0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7421875,\n         0.7421875, 0.7421875, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.3281250, 0.3281250, 0.3281250, 0.3281250, 0.3281250, 0.3281250,\n         0.3281250, 0.3281250, 0.3281250, 0.3281250, 0.3281250, 0.3281250,\n         0.3281250, 0.3281250, 0.3281250, 0.3281250, 0.6562500, 0.6562500,\n         0.6562500, 0.6562500, 0.6562500, 0.6562500, 0.6562500, 0.6562500,\n         0.6562500, 0.6562500, 0.6562500, 0.6562500, 0.6562500, 0.6562500,\n         0.6562500, 0.6562500, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.7031250, 0.7031250, 0.7031250, 0.7031250, 0.7031250, 0.7031250,\n         0.7031250, 0.7031250, 0.7031250, 0.7031250, 0.7031250, 0.7031250,\n         0.7031250, 0.7031250, 0.7031250, 0.7031250, 0.5000000, 0.5000000,\n         0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000,\n         0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000,\n         0.5000000, 0.5000000, 0.2500000, 0.2500000, 0.2500000, 0.2500000,\n         0.2500000, 0.2500000, 0.2500000, 0.2500000, 0.2500000, 0.2500000,\n         0.2500000, 0.2500000, 0.2500000, 0.2500000, 0.2500000, 0.2500000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.7421875, 0.7421875,\n         0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7421875,\n         0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7421875, 0.7421875,\n         0.7421875, 0.7421875, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750, 0.8593750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 13 :: RAINBOW ###\n\ncolor_map_luts['idl13'] = \\\n   (\narray([  0.0000000, 0.0156250, 0.0351562, 0.0507812, 0.0703125, 0.0859375,\n         0.1054688, 0.1210938, 0.1406250, 0.1562500, 0.1757812, 0.1953125,\n         0.2109375, 0.2265625, 0.2382812, 0.2500000, 0.2656250, 0.2695312,\n         0.2812500, 0.2890625, 0.3007812, 0.3085938, 0.3125000, 0.3203125,\n         0.3242188, 0.3320312, 0.3281250, 0.3359375, 0.3398438, 0.3437500,\n         0.3359375, 0.3398438, 0.3398438, 0.3398438, 0.3320312, 0.3281250,\n         0.3281250, 0.3281250, 0.3242188, 0.3085938, 0.3046875, 0.3007812,\n         0.2968750, 0.2773438, 0.2734375, 0.2656250, 0.2578125, 0.2343750,\n         0.2265625, 0.2148438, 0.2070312, 0.1796875, 0.1679688, 0.1562500,\n         0.1406250, 0.1289062, 0.0976562, 0.0820312, 0.0625000, 0.0468750,\n         0.0156250, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0312500, 0.0468750,\n         0.0820312, 0.0976562, 0.1132812, 0.1289062, 0.1640625, 0.1796875,\n         0.1992188, 0.2148438, 0.2460938, 0.2617188, 0.2812500, 0.2968750,\n         0.3125000, 0.3476562, 0.3632812, 0.3789062, 0.3945312, 0.4296875,\n         0.4453125, 0.4648438, 0.4804688, 0.5117188, 0.5273438, 0.5468750,\n         0.5625000, 0.5976562, 0.6132812, 0.6289062, 0.6445312, 0.6601562,\n         0.6953125, 0.7109375, 0.7304688, 0.7460938, 0.7773438, 0.7929688,\n         0.8125000, 0.8281250, 0.8632812, 0.8789062, 0.8945312, 0.9101562,\n         0.9453125, 0.9609375, 0.9765625, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0156250, 0.0312500, 0.0625000, 0.0820312,\n         0.0976562, 0.1132812, 0.1484375, 0.1640625, 0.1796875, 0.1992188,\n         0.2148438, 0.2460938, 0.2617188, 0.2812500, 0.2968750, 0.3281250,\n         0.3476562, 0.3632812, 0.3789062, 0.4140625, 0.4296875, 0.4453125,\n         0.4648438, 0.4960938, 0.5117188, 0.5273438, 0.5468750, 0.5625000,\n         0.5937500, 0.6132812, 0.6289062, 0.6445312, 0.6796875, 0.6953125,\n         0.7109375, 0.7304688, 0.7617188, 0.7773438, 0.7929688, 0.8125000,\n         0.8437500, 0.8593750, 0.8789062, 0.8945312, 0.9101562, 0.9453125,\n         0.9609375, 0.9765625, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9765625, 0.9453125,\n         0.9296875, 0.9101562, 0.8945312, 0.8632812, 0.8437500, 0.8281250,\n         0.8125000, 0.7773438, 0.7617188, 0.7460938, 0.7304688, 0.6953125,\n         0.6796875, 0.6640625, 0.6445312, 0.6289062, 0.5976562, 0.5781250,\n         0.5625000, 0.5468750, 0.5117188, 0.4960938, 0.4804688, 0.4648438,\n         0.4296875, 0.4140625, 0.3984375, 0.3789062, 0.3476562, 0.3320312,\n         0.3125000, 0.2968750, 0.2812500, 0.2460938, 0.2304688, 0.2148438,\n         0.1992188, 0.1640625, 0.1484375, 0.1328125, 0.1132812, 0.0820312,\n         0.0664062, 0.0468750, 0.0312500, 0.0000000]),\narray([  0.0000000, 0.0117188, 0.0273438, 0.0390625, 0.0546875, 0.0742188,\n         0.0898438, 0.1093750, 0.1250000, 0.1484375, 0.1679688, 0.1875000,\n         0.2070312, 0.2304688, 0.2460938, 0.2656250, 0.2812500, 0.3007812,\n         0.3164062, 0.3359375, 0.3554688, 0.3710938, 0.3906250, 0.4062500,\n         0.4257812, 0.4414062, 0.4609375, 0.4765625, 0.4960938, 0.5156250,\n         0.5312500, 0.5507812, 0.5664062, 0.5859375, 0.6015625, 0.6210938,\n         0.6367188, 0.6562500, 0.6757812, 0.6914062, 0.7109375, 0.7265625,\n         0.7460938, 0.7617188, 0.7812500, 0.7968750, 0.8164062, 0.8359375,\n         0.8515625, 0.8710938, 0.8867188, 0.9062500, 0.9218750, 0.9414062,\n         0.9570312, 0.9765625, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9609375, 0.9453125, 0.9296875,\n         0.9101562, 0.8789062, 0.8593750, 0.8437500, 0.8281250, 0.7929688,\n         0.7773438, 0.7617188, 0.7460938, 0.7304688, 0.6953125, 0.6796875,\n         0.6640625, 0.6445312, 0.6132812, 0.5937500, 0.5781250, 0.5625000,\n         0.5273438, 0.5117188, 0.4960938, 0.4804688, 0.4453125, 0.4296875,\n         0.4140625, 0.3984375, 0.3789062, 0.3476562, 0.3281250, 0.3125000,\n         0.2968750, 0.2617188, 0.2460938, 0.2304688, 0.2148438, 0.1796875,\n         0.1640625, 0.1484375, 0.1328125, 0.0976562, 0.0820312, 0.0625000,\n         0.0468750, 0.0312500, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 14 :: STEPS ###\n\ncolor_map_luts['idl14'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0273438,\n         0.0585938, 0.0898438, 0.1210938, 0.1523438, 0.1835938, 0.2148438,\n         0.2460938, 0.2734375, 0.3046875, 0.3359375, 0.3671875, 0.3984375,\n         0.4296875, 0.4609375, 0.4921875, 0.5234375, 0.5546875, 0.5898438,\n         0.6210938, 0.6562500, 0.6875000, 0.7187500, 0.7539062, 0.7851562,\n         0.8203125, 0.8515625, 0.8828125, 0.9179688, 0.9492188, 0.9843750,\n         0.0000000, 0.0039062, 0.0078125, 0.0117188, 0.0156250, 0.0195312,\n         0.0234375, 0.0273438, 0.0312500, 0.0351562, 0.0390625, 0.0429688,\n         0.0468750, 0.0546875, 0.0625000, 0.0703125, 0.0781250, 0.0898438,\n         0.0976562, 0.1054688, 0.1132812, 0.1250000, 0.1328125, 0.1406250,\n         0.1484375, 0.1601562, 0.1718750, 0.1835938, 0.1953125, 0.2070312,\n         0.2187500, 0.2304688, 0.2460938, 0.2578125, 0.2695312, 0.2812500,\n         0.2929688, 0.3046875, 0.3203125, 0.3320312, 0.3476562, 0.3632812,\n         0.3789062, 0.3945312, 0.4101562, 0.4218750, 0.4375000, 0.4531250,\n         0.4687500, 0.4843750, 0.5000000, 0.5117188, 0.5273438, 0.5429688,\n         0.5585938, 0.5742188, 0.5898438, 0.6054688, 0.6210938, 0.6367188,\n         0.6523438, 0.6679688, 0.6835938, 0.6953125, 0.7070312, 0.7226562,\n         0.7343750, 0.7500000, 0.7617188, 0.7734375, 0.7890625, 0.8007812,\n         0.8164062, 0.8281250, 0.8437500, 0.8515625, 0.8593750, 0.8710938,\n         0.8789062, 0.8867188, 0.8984375, 0.9062500, 0.9140625, 0.9257812,\n         0.9335938, 0.9414062, 0.9531250, 0.9531250, 0.9570312, 0.9609375,\n         0.9648438, 0.9648438, 0.9687500, 0.9726562, 0.9765625, 0.9765625,\n         0.9804688, 0.9843750, 0.9882812, 0.9921875, 0.9921875, 0.9921875,\n         0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9921875,\n         0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9921875,\n         0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9921875,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.1640625, 0.3320312, 0.4960938, 0.6640625, 0.8281250,\n         0.9960938, 0.9609375, 0.9218750, 0.8828125, 0.8437500, 0.8046875,\n         0.7695312, 0.7304688, 0.6914062, 0.6523438, 0.6132812, 0.5781250,\n         0.5390625, 0.5000000, 0.4609375, 0.4218750, 0.3867188, 0.3476562,\n         0.3085938, 0.2695312, 0.2304688, 0.1953125, 0.1562500, 0.1171875,\n         0.0781250, 0.0390625, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0078125, 0.0039062, 0.0039062, 0.0117188,\n         0.0195312, 0.0312500, 0.0390625, 0.0468750, 0.0585938, 0.0664062,\n         0.0742188, 0.0859375, 0.0937500, 0.1015625, 0.1132812, 0.1210938,\n         0.1289062, 0.1406250, 0.1406250, 0.1484375, 0.1562500, 0.1640625,\n         0.1757812, 0.1875000, 0.1992188, 0.2109375, 0.2265625, 0.2382812,\n         0.2500000, 0.2617188, 0.2734375, 0.2851562, 0.3007812, 0.3203125,\n         0.3398438, 0.3593750, 0.3750000, 0.3906250, 0.4062500, 0.4218750,\n         0.4414062, 0.4648438, 0.4882812, 0.5117188, 0.5390625, 0.5625000,\n         0.5898438, 0.6132812, 0.6406250, 0.6679688, 0.6953125, 0.7226562,\n         0.7539062, 0.7812500, 0.8125000, 0.8398438, 0.8710938, 0.8945312,\n         0.9179688, 0.9453125, 0.9687500, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0312500, 0.0625000, 0.0937500,\n         0.1250000, 0.1601562, 0.1914062, 0.2226562, 0.2539062, 0.2890625,\n         0.3203125, 0.3515625, 0.3828125, 0.4140625, 0.4492188, 0.4804688,\n         0.5117188, 0.5429688, 0.5781250, 0.6093750, 0.6406250, 0.6718750,\n         0.7031250, 0.7382812, 0.7695312, 0.8007812, 0.8320312, 0.8671875,\n         0.8984375, 0.9296875, 0.9609375, 0.9960938, 0.0000000, 0.0195312,\n         0.0390625, 0.0585938, 0.0820312, 0.1015625, 0.1210938, 0.1445312,\n         0.1640625, 0.1835938, 0.2070312, 0.2265625, 0.2460938, 0.2695312,\n         0.2890625, 0.3085938, 0.3320312, 0.3476562, 0.3671875, 0.3828125,\n         0.4023438, 0.4218750, 0.4375000, 0.4570312, 0.4726562, 0.4921875,\n         0.5117188, 0.5273438, 0.5468750, 0.5625000, 0.5820312, 0.6015625,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125,\n         0.0078125, 0.0078125, 0.0078125, 0.0117188, 0.0156250, 0.0195312,\n         0.0273438, 0.0351562, 0.0468750, 0.0546875, 0.0664062, 0.0781250,\n         0.0898438, 0.1054688, 0.1171875, 0.1328125, 0.1523438, 0.1718750,\n         0.1914062, 0.2148438, 0.2343750, 0.2539062, 0.2773438, 0.2968750,\n         0.3203125, 0.3476562, 0.3789062, 0.4062500, 0.4375000, 0.4687500,\n         0.5000000, 0.5312500, 0.5664062, 0.5976562, 0.6328125, 0.6679688,\n         0.7031250, 0.7382812, 0.7734375, 0.8085938, 0.8476562, 0.8750000,\n         0.9062500, 0.9335938, 0.9648438, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 15 :: STERN SPECIAL ###\n\ncolor_map_luts['idl15'] = \\\n   (\narray([  0.0000000, 0.0703125, 0.1406250, 0.2109375, 0.2812500, 0.3515625,\n         0.4218750, 0.4960938, 0.5664062, 0.6367188, 0.7070312, 0.7773438,\n         0.8476562, 0.9179688, 0.9921875, 0.9726562, 0.9531250, 0.9335938,\n         0.9140625, 0.8945312, 0.8710938, 0.8515625, 0.8320312, 0.8125000,\n         0.7929688, 0.7695312, 0.7500000, 0.7304688, 0.7109375, 0.6914062,\n         0.6718750, 0.6484375, 0.6289062, 0.6093750, 0.5898438, 0.5703125,\n         0.5468750, 0.5273438, 0.5078125, 0.4882812, 0.4687500, 0.4492188,\n         0.4257812, 0.4062500, 0.3867188, 0.3671875, 0.3476562, 0.3242188,\n         0.3046875, 0.2851562, 0.2656250, 0.2460938, 0.2265625, 0.2031250,\n         0.1835938, 0.1640625, 0.1445312, 0.1250000, 0.1015625, 0.0820312,\n         0.0625000, 0.0429688, 0.0234375, 0.0000000, 0.2500000, 0.2539062,\n         0.2578125, 0.2617188, 0.2656250, 0.2695312, 0.2734375, 0.2773438,\n         0.2812500, 0.2851562, 0.2890625, 0.2929688, 0.2968750, 0.3007812,\n         0.3046875, 0.3085938, 0.3125000, 0.3164062, 0.3203125, 0.3242188,\n         0.3281250, 0.3320312, 0.3359375, 0.3398438, 0.3437500, 0.3476562,\n         0.3515625, 0.3554688, 0.3593750, 0.3632812, 0.3671875, 0.3710938,\n         0.3750000, 0.3789062, 0.3828125, 0.3867188, 0.3906250, 0.3945312,\n         0.3984375, 0.4023438, 0.4062500, 0.4101562, 0.4140625, 0.4179688,\n         0.4218750, 0.4257812, 0.4296875, 0.4335938, 0.4375000, 0.4414062,\n         0.4453125, 0.4492188, 0.4531250, 0.4570312, 0.4609375, 0.4648438,\n         0.4687500, 0.4726562, 0.4765625, 0.4804688, 0.4843750, 0.4882812,\n         0.4921875, 0.4960938, 0.5000000, 0.5039062, 0.5078125, 0.5117188,\n         0.5156250, 0.5195312, 0.5234375, 0.5273438, 0.5312500, 0.5351562,\n         0.5390625, 0.5429688, 0.5468750, 0.5507812, 0.5546875, 0.5585938,\n         0.5625000, 0.5664062, 0.5703125, 0.5742188, 0.5781250, 0.5820312,\n         0.5859375, 0.5898438, 0.5937500, 0.5976562, 0.6015625, 0.6054688,\n         0.6093750, 0.6132812, 0.6171875, 0.6210938, 0.6250000, 0.6289062,\n         0.6328125, 0.6367188, 0.6406250, 0.6445312, 0.6484375, 0.6523438,\n         0.6562500, 0.6601562, 0.6640625, 0.6679688, 0.6718750, 0.6757812,\n         0.6796875, 0.6835938, 0.6875000, 0.6914062, 0.6953125, 0.6992188,\n         0.7031250, 0.7070312, 0.7109375, 0.7148438, 0.7187500, 0.7226562,\n         0.7265625, 0.7304688, 0.7343750, 0.7382812, 0.7421875, 0.7460938,\n         0.7500000, 0.7539062, 0.7578125, 0.7617188, 0.7656250, 0.7695312,\n         0.7734375, 0.7773438, 0.7812500, 0.7851562, 0.7890625, 0.7929688,\n         0.7968750, 0.8007812, 0.8046875, 0.8085938, 0.8125000, 0.8164062,\n         0.8203125, 0.8242188, 0.8281250, 0.8320312, 0.8359375, 0.8398438,\n         0.8437500, 0.8476562, 0.8515625, 0.8554688, 0.8593750, 0.8632812,\n         0.8671875, 0.8710938, 0.8750000, 0.8789062, 0.8828125, 0.8867188,\n         0.8906250, 0.8945312, 0.8984375, 0.9023438, 0.9062500, 0.9101562,\n         0.9140625, 0.9179688, 0.9218750, 0.9257812, 0.9296875, 0.9335938,\n         0.9375000, 0.9414062, 0.9453125, 0.9492188, 0.9531250, 0.9570312,\n         0.9609375, 0.9648438, 0.9687500, 0.9726562, 0.9765625, 0.9804688,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.0039062, 0.0078125, 0.0117188, 0.0156250, 0.0195312,\n         0.0234375, 0.0273438, 0.0312500, 0.0351562, 0.0390625, 0.0429688,\n         0.0468750, 0.0507812, 0.0546875, 0.0585938, 0.0625000, 0.0664062,\n         0.0703125, 0.0742188, 0.0781250, 0.0820312, 0.0859375, 0.0898438,\n         0.0937500, 0.0976562, 0.1015625, 0.1054688, 0.1093750, 0.1132812,\n         0.1171875, 0.1210938, 0.1250000, 0.1289062, 0.1328125, 0.1367188,\n         0.1406250, 0.1445312, 0.1484375, 0.1523438, 0.1562500, 0.1601562,\n         0.1640625, 0.1679688, 0.1718750, 0.1757812, 0.1796875, 0.1835938,\n         0.1875000, 0.1914062, 0.1953125, 0.1992188, 0.2031250, 0.2070312,\n         0.2109375, 0.2148438, 0.2187500, 0.2226562, 0.2265625, 0.2304688,\n         0.2343750, 0.2382812, 0.2421875, 0.2460938, 0.2500000, 0.2539062,\n         0.2578125, 0.2617188, 0.2656250, 0.2695312, 0.2734375, 0.2773438,\n         0.2812500, 0.2851562, 0.2890625, 0.2929688, 0.2968750, 0.3007812,\n         0.3046875, 0.3085938, 0.3125000, 0.3164062, 0.3203125, 0.3242188,\n         0.3281250, 0.3320312, 0.3359375, 0.3398438, 0.3437500, 0.3476562,\n         0.3515625, 0.3554688, 0.3593750, 0.3632812, 0.3671875, 0.3710938,\n         0.3750000, 0.3789062, 0.3828125, 0.3867188, 0.3906250, 0.3945312,\n         0.3984375, 0.4023438, 0.4062500, 0.4101562, 0.4140625, 0.4179688,\n         0.4218750, 0.4257812, 0.4296875, 0.4335938, 0.4375000, 0.4414062,\n         0.4453125, 0.4492188, 0.4531250, 0.4570312, 0.4609375, 0.4648438,\n         0.4687500, 0.4726562, 0.4765625, 0.4804688, 0.4843750, 0.4882812,\n         0.4921875, 0.4960938, 0.5000000, 0.5039062, 0.5078125, 0.5117188,\n         0.5156250, 0.5195312, 0.5234375, 0.5273438, 0.5312500, 0.5351562,\n         0.5390625, 0.5429688, 0.5468750, 0.5507812, 0.5546875, 0.5585938,\n         0.5625000, 0.5664062, 0.5703125, 0.5742188, 0.5781250, 0.5820312,\n         0.5859375, 0.5898438, 0.5937500, 0.5976562, 0.6015625, 0.6054688,\n         0.6093750, 0.6132812, 0.6171875, 0.6210938, 0.6250000, 0.6289062,\n         0.6328125, 0.6367188, 0.6406250, 0.6445312, 0.6484375, 0.6523438,\n         0.6562500, 0.6601562, 0.6640625, 0.6679688, 0.6718750, 0.6757812,\n         0.6796875, 0.6835938, 0.6875000, 0.6914062, 0.6953125, 0.6992188,\n         0.7031250, 0.7070312, 0.7109375, 0.7148438, 0.7187500, 0.7226562,\n         0.7265625, 0.7304688, 0.7343750, 0.7382812, 0.7421875, 0.7460938,\n         0.7500000, 0.7539062, 0.7578125, 0.7617188, 0.7656250, 0.7695312,\n         0.7734375, 0.7773438, 0.7812500, 0.7851562, 0.7890625, 0.7929688,\n         0.7968750, 0.8007812, 0.8046875, 0.8085938, 0.8125000, 0.8164062,\n         0.8203125, 0.8242188, 0.8281250, 0.8320312, 0.8359375, 0.8398438,\n         0.8437500, 0.8476562, 0.8515625, 0.8554688, 0.8593750, 0.8632812,\n         0.8671875, 0.8710938, 0.8750000, 0.8789062, 0.8828125, 0.8867188,\n         0.8906250, 0.8945312, 0.8984375, 0.9023438, 0.9062500, 0.9101562,\n         0.9140625, 0.9179688, 0.9218750, 0.9257812, 0.9296875, 0.9335938,\n         0.9375000, 0.9414062, 0.9453125, 0.9492188, 0.9531250, 0.9570312,\n         0.9609375, 0.9648438, 0.9687500, 0.9726562, 0.9765625, 0.9804688,\n         0.9843750, 0.9882812, 0.9921875, 0.9960938]),\narray([  0.0000000, 0.0039062, 0.0117188, 0.0195312, 0.0273438, 0.0351562,\n         0.0429688, 0.0507812, 0.0585938, 0.0664062, 0.0742188, 0.0820312,\n         0.0898438, 0.0976562, 0.1054688, 0.1132812, 0.1210938, 0.1289062,\n         0.1367188, 0.1445312, 0.1523438, 0.1601562, 0.1679688, 0.1757812,\n         0.1835938, 0.1914062, 0.1992188, 0.2070312, 0.2148438, 0.2226562,\n         0.2304688, 0.2382812, 0.2460938, 0.2539062, 0.2617188, 0.2695312,\n         0.2773438, 0.2851562, 0.2929688, 0.3007812, 0.3085938, 0.3164062,\n         0.3242188, 0.3320312, 0.3398438, 0.3476562, 0.3554688, 0.3632812,\n         0.3710938, 0.3789062, 0.3867188, 0.3945312, 0.4023438, 0.4101562,\n         0.4179688, 0.4257812, 0.4335938, 0.4414062, 0.4492188, 0.4570312,\n         0.4648438, 0.4726562, 0.4804688, 0.4882812, 0.4960938, 0.5039062,\n         0.5117188, 0.5195312, 0.5273438, 0.5351562, 0.5429688, 0.5507812,\n         0.5585938, 0.5664062, 0.5742188, 0.5820312, 0.5898438, 0.5976562,\n         0.6054688, 0.6132812, 0.6210938, 0.6289062, 0.6367188, 0.6445312,\n         0.6523438, 0.6601562, 0.6679688, 0.6757812, 0.6835938, 0.6914062,\n         0.6992188, 0.7070312, 0.7148438, 0.7226562, 0.7304688, 0.7382812,\n         0.7460938, 0.7539062, 0.7617188, 0.7695312, 0.7773438, 0.7851562,\n         0.7929688, 0.8007812, 0.8085938, 0.8164062, 0.8242188, 0.8320312,\n         0.8398438, 0.8476562, 0.8554688, 0.8632812, 0.8710938, 0.8789062,\n         0.8867188, 0.8945312, 0.9023438, 0.9101562, 0.9179688, 0.9257812,\n         0.9335938, 0.9414062, 0.9492188, 0.9570312, 0.9648438, 0.9726562,\n         0.9804688, 0.9882812, 0.9960938, 0.9804688, 0.9648438, 0.9492188,\n         0.9296875, 0.9140625, 0.8984375, 0.8828125, 0.8632812, 0.8476562,\n         0.8320312, 0.8164062, 0.7968750, 0.7812500, 0.7656250, 0.7500000,\n         0.7304688, 0.7148438, 0.6992188, 0.6835938, 0.6640625, 0.6484375,\n         0.6328125, 0.6171875, 0.5976562, 0.5820312, 0.5664062, 0.5507812,\n         0.5312500, 0.5156250, 0.5000000, 0.4843750, 0.4648438, 0.4492188,\n         0.4335938, 0.4179688, 0.3984375, 0.3828125, 0.3671875, 0.3515625,\n         0.3320312, 0.3164062, 0.3007812, 0.2851562, 0.2656250, 0.2500000,\n         0.2343750, 0.2187500, 0.1992188, 0.1835938, 0.1679688, 0.1523438,\n         0.1328125, 0.1171875, 0.1015625, 0.0859375, 0.0664062, 0.0507812,\n         0.0351562, 0.0195312, 0.0000000, 0.0117188, 0.0273438, 0.0429688,\n         0.0585938, 0.0742188, 0.0859375, 0.1015625, 0.1171875, 0.1328125,\n         0.1484375, 0.1601562, 0.1757812, 0.1914062, 0.2070312, 0.2226562,\n         0.2343750, 0.2500000, 0.2656250, 0.2812500, 0.2968750, 0.3085938,\n         0.3242188, 0.3398438, 0.3554688, 0.3710938, 0.3828125, 0.3984375,\n         0.4140625, 0.4296875, 0.4453125, 0.4570312, 0.4726562, 0.4882812,\n         0.5039062, 0.5195312, 0.5351562, 0.5468750, 0.5625000, 0.5781250,\n         0.5937500, 0.6093750, 0.6210938, 0.6367188, 0.6523438, 0.6679688,\n         0.6835938, 0.6953125, 0.7109375, 0.7265625, 0.7421875, 0.7578125,\n         0.7695312, 0.7851562, 0.8007812, 0.8164062, 0.8320312, 0.8437500,\n         0.8593750, 0.8750000, 0.8906250, 0.9062500, 0.9179688, 0.9335938,\n         0.9492188, 0.9648438, 0.9804688, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 16 :: Haze ###\n\ncolor_map_luts['idl16'] = \\\n   (\narray([  0.6523438, 0.6523438, 0.9960938, 0.9921875, 0.9726562, 0.9648438,\n         0.9570312, 0.9492188, 0.9453125, 0.9375000, 0.9296875, 0.9218750,\n         0.9140625, 0.9062500, 0.8984375, 0.8906250, 0.8828125, 0.8750000,\n         0.8671875, 0.8593750, 0.8515625, 0.8437500, 0.8359375, 0.8281250,\n         0.8203125, 0.8125000, 0.8046875, 0.7968750, 0.7890625, 0.7812500,\n         0.7734375, 0.7656250, 0.7578125, 0.7500000, 0.7421875, 0.7343750,\n         0.7265625, 0.7187500, 0.7109375, 0.7031250, 0.6953125, 0.6875000,\n         0.6796875, 0.6718750, 0.6640625, 0.6562500, 0.6484375, 0.6406250,\n         0.6328125, 0.6250000, 0.6171875, 0.6093750, 0.6015625, 0.5937500,\n         0.5859375, 0.5781250, 0.5703125, 0.5625000, 0.5546875, 0.5507812,\n         0.5429688, 0.5351562, 0.5273438, 0.5195312, 0.5117188, 0.5039062,\n         0.4960938, 0.4882812, 0.4804688, 0.4726562, 0.4648438, 0.4570312,\n         0.4492188, 0.4414062, 0.4335938, 0.4257812, 0.4179688, 0.4101562,\n         0.4023438, 0.3945312, 0.3867188, 0.3789062, 0.3710938, 0.3632812,\n         0.3554688, 0.3476562, 0.3398438, 0.3320312, 0.3242188, 0.3164062,\n         0.3085938, 0.3007812, 0.2929688, 0.2851562, 0.2773438, 0.2695312,\n         0.2617188, 0.2539062, 0.2460938, 0.2382812, 0.2304688, 0.2226562,\n         0.2148438, 0.2070312, 0.1992188, 0.1914062, 0.1835938, 0.1757812,\n         0.1679688, 0.1601562, 0.1562500, 0.1484375, 0.1406250, 0.1328125,\n         0.1250000, 0.1171875, 0.1093750, 0.1015625, 0.0937500, 0.0859375,\n         0.0781250, 0.0703125, 0.0625000, 0.0546875, 0.0468750, 0.0507812,\n         0.0312500, 0.0234375, 0.0156250, 0.0156250, 0.0234375, 0.0273438,\n         0.0351562, 0.0429688, 0.0507812, 0.0585938, 0.0664062, 0.0742188,\n         0.0820312, 0.0898438, 0.0976562, 0.1054688, 0.1132812, 0.1210938,\n         0.1289062, 0.1367188, 0.1445312, 0.1523438, 0.1601562, 0.1679688,\n         0.1757812, 0.1835938, 0.1914062, 0.1992188, 0.2070312, 0.2148438,\n         0.2226562, 0.2304688, 0.2382812, 0.2460938, 0.2539062, 0.2617188,\n         0.2695312, 0.2773438, 0.2851562, 0.2929688, 0.3007812, 0.3085938,\n         0.3164062, 0.3242188, 0.3320312, 0.3398438, 0.3476562, 0.3554688,\n         0.3632812, 0.3710938, 0.3789062, 0.3867188, 0.3945312, 0.4023438,\n         0.4101562, 0.4179688, 0.4218750, 0.4296875, 0.4375000, 0.4453125,\n         0.4531250, 0.4609375, 0.4687500, 0.4765625, 0.4843750, 0.4921875,\n         0.5000000, 0.5078125, 0.5156250, 0.5234375, 0.5312500, 0.5390625,\n         0.5468750, 0.5546875, 0.5625000, 0.5703125, 0.5781250, 0.5859375,\n         0.5937500, 0.6015625, 0.6093750, 0.6171875, 0.6250000, 0.6328125,\n         0.6406250, 0.6484375, 0.6562500, 0.6640625, 0.6718750, 0.6796875,\n         0.6875000, 0.6953125, 0.7031250, 0.7109375, 0.7187500, 0.7265625,\n         0.7343750, 0.7421875, 0.7500000, 0.7578125, 0.7656250, 0.7734375,\n         0.7812500, 0.7890625, 0.7968750, 0.8046875, 0.8125000, 0.8203125,\n         0.8242188, 0.8320312, 0.8398438, 0.8476562, 0.8554688, 0.8632812,\n         0.8710938, 0.8789062, 0.8867188, 0.8945312, 0.9023438, 0.9101562,\n         0.9179688, 0.9257812, 0.9335938, 0.9414062, 0.9492188, 0.9570312,\n         0.9648438, 0.9726562, 0.9804688, 0.9804688]),\narray([  0.4375000, 0.4375000, 0.8320312, 0.8281250, 0.8203125, 0.8164062,\n         0.8125000, 0.8046875, 0.8007812, 0.7929688, 0.7890625, 0.7812500,\n         0.7773438, 0.7734375, 0.7656250, 0.7617188, 0.7539062, 0.7500000,\n         0.7460938, 0.7382812, 0.7343750, 0.7265625, 0.7226562, 0.7148438,\n         0.7109375, 0.7070312, 0.6992188, 0.6953125, 0.6875000, 0.6835938,\n         0.6796875, 0.6718750, 0.6679688, 0.6601562, 0.6562500, 0.6484375,\n         0.6445312, 0.6406250, 0.6328125, 0.6289062, 0.6210938, 0.6171875,\n         0.6132812, 0.6054688, 0.6015625, 0.5937500, 0.5898438, 0.5859375,\n         0.5781250, 0.5742188, 0.5664062, 0.5625000, 0.5546875, 0.5507812,\n         0.5468750, 0.5390625, 0.5351562, 0.5273438, 0.5234375, 0.5195312,\n         0.5117188, 0.5078125, 0.5000000, 0.4960938, 0.4882812, 0.4843750,\n         0.4804688, 0.4726562, 0.4687500, 0.4609375, 0.4570312, 0.4531250,\n         0.4453125, 0.4414062, 0.4335938, 0.4296875, 0.4257812, 0.4179688,\n         0.4140625, 0.4062500, 0.4023438, 0.3945312, 0.3906250, 0.3867188,\n         0.3789062, 0.3750000, 0.3671875, 0.3632812, 0.3593750, 0.3515625,\n         0.3476562, 0.3398438, 0.3359375, 0.3281250, 0.3242188, 0.3203125,\n         0.3125000, 0.3085938, 0.3007812, 0.2968750, 0.2929688, 0.2851562,\n         0.2812500, 0.2734375, 0.2695312, 0.2656250, 0.2578125, 0.2539062,\n         0.2460938, 0.2421875, 0.2343750, 0.2304688, 0.2265625, 0.2187500,\n         0.2148438, 0.2070312, 0.2031250, 0.1992188, 0.1914062, 0.1875000,\n         0.1796875, 0.1757812, 0.1679688, 0.1640625, 0.1601562, 0.1523438,\n         0.1484375, 0.1406250, 0.1367188, 0.1328125, 0.1250000, 0.1210938,\n         0.1250000, 0.1289062, 0.1328125, 0.1406250, 0.1445312, 0.1484375,\n         0.1562500, 0.1601562, 0.1640625, 0.1718750, 0.1757812, 0.1796875,\n         0.1875000, 0.1914062, 0.1953125, 0.2031250, 0.2070312, 0.2109375,\n         0.2187500, 0.2226562, 0.2265625, 0.2343750, 0.2382812, 0.2421875,\n         0.2500000, 0.2539062, 0.2578125, 0.2656250, 0.2695312, 0.2734375,\n         0.2812500, 0.2851562, 0.2929688, 0.2968750, 0.3007812, 0.3085938,\n         0.3125000, 0.3164062, 0.3242188, 0.3281250, 0.3320312, 0.3398438,\n         0.3437500, 0.3476562, 0.3554688, 0.3593750, 0.3632812, 0.3710938,\n         0.3750000, 0.3789062, 0.3867188, 0.3906250, 0.3945312, 0.4023438,\n         0.4062500, 0.4101562, 0.4179688, 0.4218750, 0.4257812, 0.4335938,\n         0.4375000, 0.4414062, 0.4492188, 0.4531250, 0.4570312, 0.4648438,\n         0.4687500, 0.4726562, 0.4804688, 0.4843750, 0.4882812, 0.4960938,\n         0.5000000, 0.5039062, 0.5117188, 0.5156250, 0.5195312, 0.5273438,\n         0.5312500, 0.5351562, 0.5429688, 0.5468750, 0.5507812, 0.5585938,\n         0.5625000, 0.5664062, 0.5742188, 0.5781250, 0.5820312, 0.5898438,\n         0.5937500, 0.5976562, 0.6054688, 0.6093750, 0.6132812, 0.6210938,\n         0.6250000, 0.6289062, 0.6367188, 0.6406250, 0.6445312, 0.6523438,\n         0.6562500, 0.6601562, 0.6679688, 0.6718750, 0.6757812, 0.6835938,\n         0.6875000, 0.6914062, 0.6992188, 0.7031250, 0.7070312, 0.7148438,\n         0.7187500, 0.7226562, 0.7304688, 0.7343750, 0.7382812, 0.7460938,\n         0.7500000, 0.7539062, 0.7617188, 0.7617188]),\narray([  0.9960938, 0.9960938, 0.9921875, 0.9804688, 0.9765625, 0.9726562,\n         0.9687500, 0.9648438, 0.9609375, 0.9570312, 0.9531250, 0.9492188,\n         0.9453125, 0.9414062, 0.9375000, 0.9335938, 0.9296875, 0.9257812,\n         0.9218750, 0.9179688, 0.9140625, 0.9101562, 0.9062500, 0.9023438,\n         0.8984375, 0.8945312, 0.8906250, 0.8867188, 0.8828125, 0.8789062,\n         0.8750000, 0.8710938, 0.8671875, 0.8632812, 0.8593750, 0.8554688,\n         0.8515625, 0.8476562, 0.8437500, 0.8398438, 0.8359375, 0.8320312,\n         0.8281250, 0.8242188, 0.8203125, 0.8164062, 0.8125000, 0.8085938,\n         0.8046875, 0.8007812, 0.7968750, 0.7929688, 0.7890625, 0.7851562,\n         0.7812500, 0.7773438, 0.7734375, 0.7695312, 0.7656250, 0.7617188,\n         0.7578125, 0.7539062, 0.7500000, 0.7460938, 0.7421875, 0.7382812,\n         0.7343750, 0.7304688, 0.7265625, 0.7226562, 0.7187500, 0.7148438,\n         0.7109375, 0.7070312, 0.7031250, 0.6992188, 0.6953125, 0.6914062,\n         0.6875000, 0.6835938, 0.6796875, 0.6757812, 0.6718750, 0.6679688,\n         0.6640625, 0.6601562, 0.6562500, 0.6523438, 0.6484375, 0.6445312,\n         0.6406250, 0.6367188, 0.6328125, 0.6289062, 0.6250000, 0.6210938,\n         0.6171875, 0.6132812, 0.6093750, 0.6054688, 0.6015625, 0.5976562,\n         0.5937500, 0.5898438, 0.5859375, 0.5820312, 0.5781250, 0.5742188,\n         0.5703125, 0.5664062, 0.5625000, 0.5585938, 0.5546875, 0.5507812,\n         0.5468750, 0.5429688, 0.5390625, 0.5351562, 0.5312500, 0.5273438,\n         0.5234375, 0.5195312, 0.5156250, 0.5117188, 0.5078125, 0.5039062,\n         0.5000000, 0.4960938, 0.4921875, 0.4882812, 0.4843750, 0.4804688,\n         0.4765625, 0.4726562, 0.4687500, 0.4648438, 0.4609375, 0.4570312,\n         0.4531250, 0.4492188, 0.4453125, 0.4414062, 0.4375000, 0.4335938,\n         0.4296875, 0.4257812, 0.4218750, 0.4179688, 0.4140625, 0.4101562,\n         0.4062500, 0.4023438, 0.3984375, 0.3945312, 0.3906250, 0.3867188,\n         0.3828125, 0.3789062, 0.3750000, 0.3710938, 0.3671875, 0.3632812,\n         0.3593750, 0.3554688, 0.3515625, 0.3476562, 0.3437500, 0.3398438,\n         0.3359375, 0.3320312, 0.3281250, 0.3242188, 0.3203125, 0.3164062,\n         0.3125000, 0.3085938, 0.3046875, 0.3007812, 0.2968750, 0.2929688,\n         0.2890625, 0.2851562, 0.2812500, 0.2773438, 0.2734375, 0.2695312,\n         0.2656250, 0.2617188, 0.2578125, 0.2539062, 0.2500000, 0.2460938,\n         0.2421875, 0.2382812, 0.2343750, 0.2304688, 0.2265625, 0.2226562,\n         0.2187500, 0.2148438, 0.2109375, 0.2070312, 0.2031250, 0.1992188,\n         0.1953125, 0.1914062, 0.1875000, 0.1835938, 0.1796875, 0.1757812,\n         0.1718750, 0.1679688, 0.1640625, 0.1601562, 0.1562500, 0.1523438,\n         0.1484375, 0.1445312, 0.1406250, 0.1367188, 0.1328125, 0.1289062,\n         0.1250000, 0.1210938, 0.1171875, 0.1132812, 0.1093750, 0.1054688,\n         0.1015625, 0.0976562, 0.0937500, 0.0898438, 0.0859375, 0.0820312,\n         0.0781250, 0.0742188, 0.0703125, 0.0664062, 0.0625000, 0.0585938,\n         0.0546875, 0.0507812, 0.0468750, 0.0429688, 0.0507812, 0.0351562,\n         0.0312500, 0.0273438, 0.0234375, 0.0195312, 0.0156250, 0.0117188,\n         0.0078125, 0.0039062, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 17 :: Blue - Pastel - Red ###\n\ncolor_map_luts['idl17'] = \\\n   (\narray([  0.1289062, 0.1289062, 0.1250000, 0.1210938, 0.1210938, 0.1171875,\n         0.1132812, 0.1093750, 0.1054688, 0.1015625, 0.0976562, 0.0937500,\n         0.0898438, 0.0859375, 0.0820312, 0.0781250, 0.0742188, 0.0664062,\n         0.0625000, 0.0585938, 0.0546875, 0.0468750, 0.0429688, 0.0507812,\n         0.0312500, 0.0273438, 0.0195312, 0.0156250, 0.0078125, 0.0039062,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0078125, 0.0156250, 0.0195312, 0.0273438, 0.0312500, 0.0507812,\n         0.0429688, 0.0507812, 0.0546875, 0.0585938, 0.0664062, 0.0703125,\n         0.0742188, 0.0781250, 0.0820312, 0.1132812, 0.1406250, 0.1640625,\n         0.1875000, 0.2070312, 0.2265625, 0.2382812, 0.2539062, 0.2656250,\n         0.2734375, 0.2812500, 0.2851562, 0.2890625, 0.2890625, 0.2890625,\n         0.2929688, 0.2929688, 0.2968750, 0.2968750, 0.2968750, 0.3007812,\n         0.2968750, 0.3007812, 0.3046875, 0.3085938, 0.3125000, 0.3164062,\n         0.3242188, 0.3281250, 0.3320312, 0.3359375, 0.3437500, 0.3476562,\n         0.3554688, 0.3593750, 0.3554688, 0.3671875, 0.3750000, 0.3828125,\n         0.3906250, 0.3984375, 0.4062500, 0.4140625, 0.4218750, 0.4257812,\n         0.4335938, 0.4375000, 0.4414062, 0.4492188, 0.4531250, 0.4570312,\n         0.4648438, 0.4687500, 0.4726562, 0.4804688, 0.4843750, 0.4882812,\n         0.4960938, 0.5000000, 0.5039062, 0.5117188, 0.5156250, 0.5195312,\n         0.5273438, 0.5312500, 0.5351562, 0.5390625, 0.5468750, 0.5507812,\n         0.5546875, 0.5625000, 0.5664062, 0.5703125, 0.5781250, 0.5820312,\n         0.5859375, 0.5937500, 0.5976562, 0.6015625, 0.6093750, 0.6132812,\n         0.6171875, 0.6250000, 0.6289062, 0.6328125, 0.6406250, 0.6445312,\n         0.6484375, 0.6562500, 0.6601562, 0.6640625, 0.6718750, 0.6757812,\n         0.6796875, 0.6835938, 0.6914062, 0.6953125, 0.6992188, 0.7070312,\n         0.7109375, 0.7148438, 0.7226562, 0.7265625, 0.7304688, 0.7382812,\n         0.7421875, 0.7460938, 0.7539062, 0.7539062]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0039062, 0.0117188, 0.0195312, 0.0234375, 0.0312500,\n         0.0507812, 0.0468750, 0.0507812, 0.0585938, 0.0664062, 0.0742188,\n         0.0820312, 0.0898438, 0.0976562, 0.1054688, 0.1132812, 0.1210938,\n         0.1289062, 0.1367188, 0.1445312, 0.1523438, 0.1640625, 0.1718750,\n         0.1796875, 0.1914062, 0.1992188, 0.2070312, 0.2187500, 0.2265625,\n         0.2343750, 0.2460938, 0.2539062, 0.2656250, 0.2773438, 0.2851562,\n         0.2968750, 0.3046875, 0.3164062, 0.3281250, 0.3398438, 0.3476562,\n         0.3593750, 0.3710938, 0.3828125, 0.3945312, 0.4062500, 0.4179688,\n         0.4296875, 0.4414062, 0.4531250, 0.4648438, 0.4765625, 0.4882812,\n         0.5000000, 0.5117188, 0.5234375, 0.5390625, 0.5507812, 0.5625000,\n         0.5781250, 0.5898438, 0.6015625, 0.6171875, 0.6289062, 0.6445312,\n         0.6562500, 0.6718750, 0.6835938, 0.6992188, 0.7109375, 0.7265625,\n         0.7421875, 0.7539062, 0.7695312, 0.7851562, 0.8007812, 0.8164062,\n         0.8281250, 0.8437500, 0.8593750, 0.8750000, 0.8906250, 0.9062500,\n         0.9140625, 0.9179688, 0.9218750, 0.9296875, 0.9335938, 0.9375000,\n         0.9414062, 0.9492188, 0.9531250, 0.9570312, 0.9609375, 0.9648438,\n         0.9726562, 0.9726562, 0.9609375, 0.9531250, 0.9414062, 0.9257812,\n         0.9062500, 0.8867188, 0.8710938, 0.8515625, 0.8359375, 0.8164062,\n         0.8007812, 0.7851562, 0.7656250, 0.7500000, 0.7343750, 0.7187500,\n         0.7031250, 0.6835938, 0.6679688, 0.6523438, 0.6367188, 0.6250000,\n         0.6093750, 0.5937500, 0.5781250, 0.5625000, 0.5507812, 0.5351562,\n         0.5195312, 0.5078125, 0.4921875, 0.4804688, 0.4648438, 0.4531250,\n         0.4531250, 0.4492188, 0.4414062, 0.4375000, 0.4296875, 0.4257812,\n         0.4062500, 0.4062500, 0.4023438, 0.3984375, 0.3945312, 0.3906250,\n         0.3867188, 0.3828125, 0.3789062, 0.3750000, 0.3710938, 0.3671875,\n         0.3632812, 0.3593750, 0.3437500, 0.3437500, 0.3437500, 0.3398438,\n         0.3359375, 0.3320312, 0.3320312, 0.3281250, 0.3242188, 0.3203125,\n         0.3125000, 0.3085938, 0.3046875, 0.2968750, 0.2929688, 0.2890625,\n         0.2851562, 0.2773438, 0.2734375, 0.2695312, 0.2656250, 0.2617188,\n         0.2578125, 0.2500000, 0.2460938, 0.2382812, 0.2265625, 0.2187500,\n         0.2109375, 0.2031250, 0.1914062, 0.1835938, 0.1757812, 0.1679688,\n         0.1601562, 0.1562500, 0.1484375, 0.1406250, 0.1328125, 0.1289062,\n         0.1210938, 0.1132812, 0.1093750, 0.1015625, 0.0976562, 0.0898438,\n         0.0859375, 0.0820312, 0.0742188, 0.0703125, 0.0664062, 0.0625000,\n         0.0546875, 0.0507812, 0.0468750, 0.0429688, 0.0507812, 0.0351562,\n         0.0312500, 0.0273438, 0.0273438, 0.0234375, 0.0195312, 0.0156250,\n         0.0156250, 0.0117188, 0.0078125, 0.0078125, 0.0039062, 0.0039062,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.3750000, 0.3750000, 0.3789062, 0.3867188, 0.3906250, 0.3945312,\n         0.3984375, 0.4062500, 0.4101562, 0.4140625, 0.4179688, 0.4218750,\n         0.4296875, 0.4335938, 0.4375000, 0.4414062, 0.4492188, 0.4531250,\n         0.4570312, 0.4609375, 0.4648438, 0.4726562, 0.4765625, 0.4804688,\n         0.4843750, 0.4882812, 0.4960938, 0.5000000, 0.5039062, 0.5078125,\n         0.5156250, 0.5195312, 0.5234375, 0.5273438, 0.5312500, 0.5390625,\n         0.5429688, 0.5468750, 0.5507812, 0.5585938, 0.5625000, 0.5664062,\n         0.5703125, 0.5742188, 0.5820312, 0.5859375, 0.5898438, 0.5937500,\n         0.6015625, 0.6054688, 0.6093750, 0.6132812, 0.6171875, 0.6250000,\n         0.6289062, 0.6328125, 0.6367188, 0.6406250, 0.6484375, 0.6523438,\n         0.6562500, 0.6601562, 0.6679688, 0.6718750, 0.6757812, 0.6796875,\n         0.6835938, 0.6914062, 0.6953125, 0.6992188, 0.7031250, 0.7109375,\n         0.7148438, 0.7187500, 0.7226562, 0.7265625, 0.7343750, 0.7382812,\n         0.7421875, 0.7460938, 0.7539062, 0.7578125, 0.7617188, 0.7656250,\n         0.7695312, 0.7773438, 0.7812500, 0.7851562, 0.7890625, 0.7929688,\n         0.8007812, 0.8046875, 0.8085938, 0.8125000, 0.8203125, 0.8242188,\n         0.8281250, 0.8320312, 0.8359375, 0.8437500, 0.8476562, 0.8515625,\n         0.8554688, 0.8632812, 0.8671875, 0.8710938, 0.8750000, 0.8789062,\n         0.8867188, 0.8906250, 0.8945312, 0.8984375, 0.9062500, 0.9101562,\n         0.9062500, 0.8984375, 0.8945312, 0.8867188, 0.8789062, 0.8710938,\n         0.8671875, 0.8593750, 0.8515625, 0.8437500, 0.8359375, 0.8281250,\n         0.8203125, 0.8125000, 0.5820312, 0.5546875, 0.5273438, 0.4960938,\n         0.4609375, 0.4335938, 0.4023438, 0.3710938, 0.3437500, 0.3164062,\n         0.2890625, 0.2617188, 0.2343750, 0.2070312, 0.1835938, 0.1562500,\n         0.1328125, 0.1093750, 0.0859375, 0.0859375, 0.0898438, 0.0937500,\n         0.0976562, 0.1015625, 0.1054688, 0.1093750, 0.1093750, 0.1132812,\n         0.1171875, 0.1171875, 0.1210938, 0.1210938, 0.1250000, 0.1250000,\n         0.1132812, 0.1093750, 0.1015625, 0.0976562, 0.0898438, 0.0859375,\n         0.0781250, 0.0703125, 0.0664062, 0.0625000, 0.0585938, 0.0546875,\n         0.0507812, 0.0468750, 0.0429688, 0.0507812, 0.0351562, 0.0312500,\n         0.0273438, 0.0234375, 0.0195312, 0.0195312, 0.0156250, 0.0117188,\n         0.0117188, 0.0078125, 0.0039062, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 18 :: Pastels ###\n\ncolor_map_luts['idl18'] = \\\n   (\narray([  0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9882812,\n         0.9804688, 0.9726562, 0.9648438, 0.9570312, 0.9492188, 0.9414062,\n         0.9335938, 0.9257812, 0.9179688, 0.9101562, 0.9023438, 0.8945312,\n         0.8867188, 0.8789062, 0.8710938, 0.8632812, 0.8554688, 0.8476562,\n         0.8437500, 0.8359375, 0.8281250, 0.8203125, 0.8125000, 0.8046875,\n         0.7968750, 0.7890625, 0.7812500, 0.7734375, 0.7656250, 0.7578125,\n         0.7500000, 0.7421875, 0.7343750, 0.7265625, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0078125, 0.0312500, 0.0507812,\n         0.0742188, 0.0976562, 0.1171875, 0.1406250, 0.1640625, 0.1875000,\n         0.2070312, 0.2304688, 0.2539062, 0.2773438, 0.2968750, 0.3203125,\n         0.3437500, 0.3632812, 0.3867188, 0.4101562, 0.4335938, 0.4531250,\n         0.4765625, 0.5000000, 0.5195312, 0.5429688, 0.5664062, 0.5898438,\n         0.6093750, 0.6328125, 0.6562500, 0.6757812, 0.6992188, 0.7226562,\n         0.7460938, 0.7656250, 0.7890625, 0.8125000, 0.8359375, 0.8554688,\n         0.8789062, 0.9023438, 0.9218750, 0.9453125, 0.9687500, 0.9921875,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.5468750, 0.5703125,\n         0.5937500, 0.6132812, 0.6367188, 0.6601562, 0.6796875, 0.7031250,\n         0.7265625, 0.7500000, 0.7695312, 0.7929688, 0.8164062, 0.8359375,\n         0.8593750, 0.8828125, 0.9062500, 0.9257812, 0.9492188, 0.9726562,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9765625, 0.9531250, 0.9296875, 0.9101562, 0.8867188, 0.8632812,\n         0.8398438, 0.8203125, 0.7968750, 0.7734375, 0.7539062, 0.7304688,\n         0.7070312, 0.6835938, 0.6640625, 0.6640625]),\narray([  0.2812500, 0.2812500, 0.2890625, 0.2968750, 0.3046875, 0.3125000,\n         0.3203125, 0.3281250, 0.3359375, 0.3437500, 0.3515625, 0.3554688,\n         0.3632812, 0.3710938, 0.3789062, 0.3867188, 0.3945312, 0.4023438,\n         0.4101562, 0.4179688, 0.4257812, 0.4335938, 0.4414062, 0.4492188,\n         0.4570312, 0.4648438, 0.4726562, 0.4804688, 0.4882812, 0.4960938,\n         0.5039062, 0.5117188, 0.5195312, 0.5273438, 0.5351562, 0.5429688,\n         0.5507812, 0.5546875, 0.5625000, 0.5703125, 0.5781250, 0.5859375,\n         0.5937500, 0.6015625, 0.6093750, 0.6171875, 0.6250000, 0.6328125,\n         0.6406250, 0.6484375, 0.6562500, 0.6640625, 0.6718750, 0.6796875,\n         0.6875000, 0.6953125, 0.7031250, 0.7109375, 0.7187500, 0.7265625,\n         0.7343750, 0.7421875, 0.7460938, 0.7539062, 0.7617188, 0.7695312,\n         0.7773438, 0.7851562, 0.7929688, 0.8007812, 0.8085938, 0.8164062,\n         0.8242188, 0.8320312, 0.8398438, 0.8476562, 0.8554688, 0.8632812,\n         0.8710938, 0.8789062, 0.8867188, 0.8945312, 0.9023438, 0.9101562,\n         0.9179688, 0.9257812, 0.9335938, 0.9414062, 0.9453125, 0.9531250,\n         0.9609375, 0.9687500, 0.9765625, 0.9843750, 0.9921875, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9726562, 0.9492188, 0.9257812, 0.9062500, 0.8828125,\n         0.8593750, 0.8359375, 0.8164062, 0.7929688, 0.7695312, 0.7500000,\n         0.7265625, 0.7031250, 0.6796875, 0.6601562, 0.6367188, 0.6132812,\n         0.5937500, 0.5703125, 0.5468750, 0.5234375, 0.5039062, 0.4804688,\n         0.4570312, 0.4375000, 0.4140625, 0.3906250, 0.3671875, 0.3476562,\n         0.3242188, 0.3007812, 0.2773438, 0.2578125, 0.2343750, 0.2109375,\n         0.1914062, 0.1679688, 0.1445312, 0.1210938, 0.1015625, 0.0781250,\n         0.0546875, 0.0351562, 0.0117188, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 19 :: Hue Sat Lightness 1 ###\n\ncolor_map_luts['idl19'] = \\\n   (\narray([  0.9804688, 0.9804688, 0.9804688, 0.9804688, 0.9843750, 0.9843750,\n         0.9882812, 0.9843750, 0.9843750, 0.9804688, 0.9804688, 0.9765625,\n         0.9765625, 0.9726562, 0.9726562, 0.9726562, 0.9687500, 0.9687500,\n         0.9648438, 0.9648438, 0.9609375, 0.9609375, 0.9609375, 0.9570312,\n         0.9570312, 0.9531250, 0.9531250, 0.9492188, 0.9492188, 0.9492188,\n         0.9453125, 0.9453125, 0.9414062, 0.9414062, 0.9414062, 0.9375000,\n         0.9375000, 0.9375000, 0.9335938, 0.9335938, 0.9335938, 0.9296875,\n         0.9296875, 0.9257812, 0.9179688, 0.8984375, 0.8828125, 0.8671875,\n         0.8476562, 0.8320312, 0.8164062, 0.8007812, 0.7851562, 0.7695312,\n         0.7578125, 0.7421875, 0.7265625, 0.7148438, 0.6992188, 0.6875000,\n         0.6757812, 0.6601562, 0.6484375, 0.6367188, 0.6250000, 0.6132812,\n         0.6015625, 0.5898438, 0.5781250, 0.5664062, 0.5585938, 0.5468750,\n         0.5351562, 0.5273438, 0.5195312, 0.5078125, 0.5000000, 0.4921875,\n         0.4843750, 0.4765625, 0.4687500, 0.4609375, 0.4531250, 0.4453125,\n         0.4375000, 0.4335938, 0.4257812, 0.4257812, 0.4296875, 0.4335938,\n         0.4414062, 0.4453125, 0.4492188, 0.4531250, 0.4570312, 0.4609375,\n         0.4648438, 0.4726562, 0.4765625, 0.4804688, 0.4882812, 0.4882812,\n         0.4921875, 0.4960938, 0.5000000, 0.5039062, 0.5117188, 0.5156250,\n         0.5195312, 0.5273438, 0.5312500, 0.5351562, 0.5390625, 0.5429688,\n         0.5468750, 0.5468750, 0.5507812, 0.5546875, 0.5625000, 0.5664062,\n         0.5703125, 0.5742188, 0.5781250, 0.5820312, 0.5859375, 0.5898438,\n         0.5937500, 0.5976562, 0.6015625, 0.6054688, 0.6093750, 0.6132812,\n         0.6171875, 0.6210938, 0.6250000, 0.6289062, 0.6328125, 0.6367188,\n         0.6406250, 0.6445312, 0.6484375, 0.6523438, 0.6562500, 0.6601562,\n         0.6640625, 0.6679688, 0.6679688, 0.6718750, 0.6796875, 0.6796875,\n         0.6835938, 0.6875000, 0.6914062, 0.6953125, 0.6992188, 0.7031250,\n         0.7070312, 0.7109375, 0.7148438, 0.7187500, 0.7226562, 0.7226562,\n         0.7265625, 0.7304688, 0.7343750, 0.7382812, 0.7421875, 0.7421875,\n         0.7460938, 0.7500000, 0.7539062, 0.7578125, 0.7617188, 0.7656250,\n         0.7695312, 0.7773438, 0.7812500, 0.7890625, 0.7929688, 0.7968750,\n         0.8046875, 0.8085938, 0.8125000, 0.8203125, 0.8242188, 0.8281250,\n         0.8320312, 0.8359375, 0.8398438, 0.8437500, 0.8476562, 0.8515625,\n         0.8554688, 0.8593750, 0.8593750, 0.8632812, 0.8671875, 0.8710938,\n         0.8750000, 0.8789062, 0.8828125, 0.8828125, 0.8867188, 0.8906250,\n         0.8906250, 0.8945312, 0.8984375, 0.8984375, 0.9023438, 0.9062500,\n         0.9062500, 0.9101562, 0.9101562, 0.9140625, 0.9179688, 0.9179688,\n         0.9179688, 0.9218750, 0.9218750, 0.9218750, 0.9257812, 0.9257812,\n         0.9257812, 0.9296875, 0.9296875, 0.9335938, 0.9335938, 0.9335938,\n         0.9375000, 0.9375000, 0.9375000, 0.9414062, 0.9453125, 0.9453125,\n         0.9453125, 0.9453125, 0.9492188, 0.9492188, 0.9531250, 0.9531250,\n         0.9531250, 0.9570312, 0.9570312, 0.9609375, 0.9609375, 0.9609375,\n         0.9648438, 0.9648438, 0.9687500, 0.9687500, 0.9726562, 0.9726562,\n         0.9765625, 0.9765625, 0.9804688, 0.9804688]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0039062, 0.0039062, 0.0078125,\n         0.0078125, 0.0156250, 0.0195312, 0.0273438, 0.0312500, 0.0273438,\n         0.0351562, 0.0507812, 0.0312500, 0.0585938, 0.0664062, 0.0703125,\n         0.0781250, 0.0820312, 0.0898438, 0.0937500, 0.1015625, 0.1054688,\n         0.1093750, 0.1171875, 0.1210938, 0.1289062, 0.1328125, 0.1367188,\n         0.1445312, 0.1484375, 0.1562500, 0.1601562, 0.1640625, 0.1718750,\n         0.1757812, 0.1796875, 0.1875000, 0.1914062, 0.1953125, 0.2031250,\n         0.2070312, 0.2148438, 0.2187500, 0.2226562, 0.2304688, 0.2343750,\n         0.2382812, 0.2460938, 0.2500000, 0.2539062, 0.2578125, 0.2656250,\n         0.2695312, 0.2734375, 0.2812500, 0.2851562, 0.2890625, 0.2968750,\n         0.3007812, 0.3046875, 0.3085938, 0.3164062, 0.3203125, 0.3242188,\n         0.3281250, 0.3320312, 0.3359375, 0.3398438, 0.3437500, 0.3515625,\n         0.3554688, 0.3593750, 0.3632812, 0.3710938, 0.3750000, 0.3789062,\n         0.3828125, 0.3867188, 0.3945312, 0.3984375, 0.4023438, 0.4062500,\n         0.4101562, 0.4179688, 0.4218750, 0.4296875, 0.4453125, 0.4609375,\n         0.4765625, 0.4882812, 0.5039062, 0.5156250, 0.5312500, 0.5429688,\n         0.5585938, 0.5703125, 0.5820312, 0.5937500, 0.6093750, 0.6171875,\n         0.6289062, 0.6406250, 0.6523438, 0.6640625, 0.6757812, 0.6835938,\n         0.6953125, 0.7070312, 0.7187500, 0.7265625, 0.7343750, 0.7460938,\n         0.7539062, 0.7617188, 0.7695312, 0.7773438, 0.7851562, 0.7929688,\n         0.8007812, 0.8085938, 0.8164062, 0.8242188, 0.8320312, 0.8398438,\n         0.8437500, 0.8515625, 0.8593750, 0.8632812, 0.8671875, 0.8671875,\n         0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875,\n         0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875,\n         0.8671875, 0.8671875, 0.8710938, 0.8710938, 0.8710938, 0.8710938,\n         0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938,\n         0.8710938, 0.8710938, 0.8710938, 0.8750000, 0.8750000, 0.8750000,\n         0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8789062,\n         0.8789062, 0.8789062, 0.8789062, 0.8789062, 0.8789062, 0.8789062,\n         0.8828125, 0.8828125, 0.8828125, 0.8828125, 0.8828125, 0.8828125,\n         0.8867188, 0.8867188, 0.8867188, 0.8867188, 0.8867188, 0.8906250,\n         0.8906250, 0.8906250, 0.8906250, 0.8906250, 0.8945312, 0.8945312,\n         0.8945312, 0.8945312, 0.8945312, 0.8945312, 0.8984375, 0.8984375,\n         0.8984375, 0.8984375, 0.9023438, 0.9023438, 0.9062500, 0.9062500,\n         0.9062500, 0.9062500, 0.9062500, 0.9101562, 0.9101562, 0.9101562,\n         0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9179688, 0.9179688,\n         0.9179688, 0.9218750, 0.9218750, 0.9218750, 0.9218750, 0.9218750,\n         0.9257812, 0.9257812, 0.9257812, 0.9257812, 0.9296875, 0.9296875,\n         0.9296875, 0.9335938, 0.9335938, 0.9335938, 0.9375000, 0.9375000,\n         0.9414062, 0.9414062, 0.9453125, 0.9453125, 0.9453125, 0.9492188,\n         0.9492188, 0.9531250, 0.9531250, 0.9570312, 0.9570312, 0.9609375,\n         0.9609375, 0.9648438, 0.9648438, 0.9687500, 0.9687500, 0.9726562,\n         0.9726562, 0.9765625, 0.9765625, 0.9765625]),\narray([  0.0117188, 0.0117188, 0.0351562, 0.0585938, 0.0859375, 0.1093750,\n         0.1328125, 0.1601562, 0.1875000, 0.2148438, 0.2421875, 0.2578125,\n         0.2851562, 0.3164062, 0.3281250, 0.3671875, 0.3906250, 0.4140625,\n         0.4375000, 0.4609375, 0.4843750, 0.5078125, 0.5273438, 0.5507812,\n         0.5742188, 0.5937500, 0.6132812, 0.6328125, 0.6562500, 0.6757812,\n         0.6953125, 0.7148438, 0.7304688, 0.7500000, 0.7695312, 0.7851562,\n         0.8046875, 0.8203125, 0.8398438, 0.8554688, 0.8710938, 0.8867188,\n         0.9023438, 0.9179688, 0.9257812, 0.9257812, 0.9218750, 0.9218750,\n         0.9218750, 0.9179688, 0.9179688, 0.9179688, 0.9179688, 0.9140625,\n         0.9140625, 0.9140625, 0.9101562, 0.9101562, 0.9101562, 0.9062500,\n         0.9062500, 0.9062500, 0.9062500, 0.9023438, 0.9023438, 0.9023438,\n         0.8984375, 0.8984375, 0.8984375, 0.8984375, 0.8984375, 0.8945312,\n         0.8945312, 0.8945312, 0.8945312, 0.8906250, 0.8906250, 0.8906250,\n         0.8906250, 0.8906250, 0.8867188, 0.8867188, 0.8867188, 0.8867188,\n         0.8867188, 0.8828125, 0.8828125, 0.8828125, 0.8828125, 0.8828125,\n         0.8789062, 0.8789062, 0.8789062, 0.8789062, 0.8789062, 0.8789062,\n         0.8789062, 0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000,\n         0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8710938, 0.8710938,\n         0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938,\n         0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8671875, 0.8671875,\n         0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875,\n         0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8632812, 0.8593750,\n         0.8515625, 0.8476562, 0.8437500, 0.8359375, 0.8320312, 0.8281250,\n         0.8242188, 0.8203125, 0.8125000, 0.8085938, 0.8046875, 0.8046875,\n         0.8007812, 0.7968750, 0.7929688, 0.7890625, 0.7890625, 0.7851562,\n         0.7812500, 0.7812500, 0.7773438, 0.7734375, 0.7734375, 0.7695312,\n         0.7695312, 0.7695312, 0.7656250, 0.7695312, 0.7656250, 0.7656250,\n         0.7617188, 0.7617188, 0.7617188, 0.7617188, 0.7617188, 0.7617188,\n         0.7617188, 0.7617188, 0.7617188, 0.7617188, 0.7617188, 0.7656250,\n         0.7656250, 0.7695312, 0.7734375, 0.7773438, 0.7812500, 0.7851562,\n         0.7851562, 0.7890625, 0.7929688, 0.7968750, 0.8007812, 0.8007812,\n         0.8046875, 0.8085938, 0.8125000, 0.8164062, 0.8164062, 0.8203125,\n         0.8242188, 0.8281250, 0.8281250, 0.8320312, 0.8320312, 0.8359375,\n         0.8398438, 0.8437500, 0.8437500, 0.8476562, 0.8515625, 0.8515625,\n         0.8554688, 0.8593750, 0.8632812, 0.8632812, 0.8671875, 0.8710938,\n         0.8710938, 0.8750000, 0.8789062, 0.8828125, 0.8828125, 0.8867188,\n         0.8906250, 0.8906250, 0.8945312, 0.8984375, 0.8984375, 0.9023438,\n         0.9062500, 0.9062500, 0.9101562, 0.9101562, 0.9140625, 0.9179688,\n         0.9179688, 0.9218750, 0.9257812, 0.9257812, 0.9296875, 0.9296875,\n         0.9335938, 0.9375000, 0.9375000, 0.9414062, 0.9414062, 0.9453125,\n         0.9492188, 0.9492188, 0.9531250, 0.9531250, 0.9570312, 0.9609375,\n         0.9609375, 0.9648438, 0.9648438, 0.9687500, 0.9687500, 0.9726562,\n         0.9726562, 0.9765625, 0.9765625, 0.9765625]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 20 :: Hue Sat Lightness 2 ###\n\ncolor_map_luts['idl20'] = \\\n   (\narray([  0.9882812, 0.9882812, 0.9804688, 0.9765625, 0.9765625, 0.9726562,\n         0.9726562, 0.9687500, 0.9687500, 0.9648438, 0.9648438, 0.9609375,\n         0.9609375, 0.9609375, 0.9570312, 0.9570312, 0.9531250, 0.9531250,\n         0.9531250, 0.9492188, 0.9492188, 0.9453125, 0.9453125, 0.9453125,\n         0.9414062, 0.9414062, 0.9375000, 0.9375000, 0.9375000, 0.9335938,\n         0.9335938, 0.9335938, 0.9296875, 0.9296875, 0.9296875, 0.9257812,\n         0.9257812, 0.9218750, 0.9218750, 0.9218750, 0.9179688, 0.9179688,\n         0.9179688, 0.9179688, 0.9140625, 0.9101562, 0.9101562, 0.9062500,\n         0.9062500, 0.9023438, 0.9023438, 0.8984375, 0.8945312, 0.8945312,\n         0.8906250, 0.8867188, 0.8828125, 0.8828125, 0.8789062, 0.8750000,\n         0.8710938, 0.8710938, 0.8671875, 0.8632812, 0.8593750, 0.8554688,\n         0.8515625, 0.8476562, 0.8437500, 0.8398438, 0.8359375, 0.8320312,\n         0.8281250, 0.8242188, 0.8203125, 0.8125000, 0.8085938, 0.8046875,\n         0.8007812, 0.7968750, 0.7890625, 0.7812500, 0.7773438, 0.7734375,\n         0.7656250, 0.7617188, 0.7578125, 0.7500000, 0.7500000, 0.7460938,\n         0.7460938, 0.7421875, 0.7382812, 0.7343750, 0.7304688, 0.7226562,\n         0.7226562, 0.7187500, 0.7148438, 0.7148438, 0.7109375, 0.7070312,\n         0.7031250, 0.6992188, 0.6953125, 0.6914062, 0.6875000, 0.6835938,\n         0.6796875, 0.6757812, 0.6718750, 0.6718750, 0.6679688, 0.6640625,\n         0.6601562, 0.6562500, 0.6523438, 0.6484375, 0.6445312, 0.6445312,\n         0.6406250, 0.6367188, 0.6328125, 0.6289062, 0.6250000, 0.6210938,\n         0.6171875, 0.6132812, 0.6093750, 0.6054688, 0.6015625, 0.5976562,\n         0.5937500, 0.5898438, 0.5820312, 0.5781250, 0.5742188, 0.5703125,\n         0.5664062, 0.5625000, 0.5585938, 0.5546875, 0.5468750, 0.5429688,\n         0.5390625, 0.5390625, 0.5351562, 0.5312500, 0.5273438, 0.5234375,\n         0.5156250, 0.5117188, 0.5078125, 0.5039062, 0.5000000, 0.4960938,\n         0.4921875, 0.4882812, 0.4843750, 0.4765625, 0.4726562, 0.4687500,\n         0.4648438, 0.4609375, 0.4609375, 0.4531250, 0.4453125, 0.4414062,\n         0.4375000, 0.4335938, 0.4335938, 0.4257812, 0.4218750, 0.4257812,\n         0.4335938, 0.4453125, 0.4492188, 0.4570312, 0.4609375, 0.4687500,\n         0.4804688, 0.4882812, 0.4960938, 0.5039062, 0.5117188, 0.5234375,\n         0.5351562, 0.5429688, 0.5507812, 0.5625000, 0.5742188, 0.5859375,\n         0.5937500, 0.6054688, 0.6171875, 0.6289062, 0.6406250, 0.6523438,\n         0.6679688, 0.6796875, 0.6914062, 0.7070312, 0.7187500, 0.7343750,\n         0.7500000, 0.7617188, 0.7773438, 0.7929688, 0.8085938, 0.8242188,\n         0.8437500, 0.8593750, 0.8750000, 0.8906250, 0.9101562, 0.9257812,\n         0.9296875, 0.9335938, 0.9335938, 0.9375000, 0.9375000, 0.9375000,\n         0.9414062, 0.9414062, 0.9414062, 0.9453125, 0.9453125, 0.9492188,\n         0.9492188, 0.9492188, 0.9531250, 0.9531250, 0.9570312, 0.9570312,\n         0.9570312, 0.9609375, 0.9609375, 0.9648438, 0.9648438, 0.9648438,\n         0.9687500, 0.9687500, 0.9726562, 0.9726562, 0.9765625, 0.9765625,\n         0.9804688, 0.9804688, 0.9843750, 0.9843750, 0.9882812, 0.9882812,\n         0.9921875, 0.9921875, 0.9921875, 0.9921875]),\narray([  0.9843750, 0.9843750, 0.9765625, 0.9765625, 0.9726562, 0.9726562,\n         0.9687500, 0.9687500, 0.9648438, 0.9648438, 0.9609375, 0.9609375,\n         0.9570312, 0.9570312, 0.9531250, 0.9531250, 0.9492188, 0.9453125,\n         0.9453125, 0.9414062, 0.9414062, 0.9375000, 0.9335938, 0.9335938,\n         0.9296875, 0.9296875, 0.9257812, 0.9218750, 0.9218750, 0.9179688,\n         0.9179688, 0.9140625, 0.9101562, 0.9101562, 0.9062500, 0.9062500,\n         0.9023438, 0.8984375, 0.8984375, 0.8945312, 0.8906250, 0.8906250,\n         0.8867188, 0.8828125, 0.8828125, 0.8789062, 0.8750000, 0.8710938,\n         0.8710938, 0.8671875, 0.8632812, 0.8632812, 0.8593750, 0.8554688,\n         0.8515625, 0.8515625, 0.8476562, 0.8476562, 0.8437500, 0.8398438,\n         0.8359375, 0.8359375, 0.8320312, 0.8281250, 0.8242188, 0.8242188,\n         0.8164062, 0.8164062, 0.8125000, 0.8125000, 0.8046875, 0.8046875,\n         0.8007812, 0.8007812, 0.7968750, 0.7890625, 0.7890625, 0.7851562,\n         0.7851562, 0.7812500, 0.7773438, 0.7695312, 0.7695312, 0.7656250,\n         0.7656250, 0.7617188, 0.7617188, 0.7578125, 0.7578125, 0.7578125,\n         0.7578125, 0.7578125, 0.7578125, 0.7578125, 0.7617188, 0.7578125,\n         0.7617188, 0.7617188, 0.7617188, 0.7656250, 0.7656250, 0.7656250,\n         0.7695312, 0.7695312, 0.7734375, 0.7734375, 0.7773438, 0.7773438,\n         0.7812500, 0.7812500, 0.7851562, 0.7890625, 0.7929688, 0.7968750,\n         0.7968750, 0.8007812, 0.8046875, 0.8085938, 0.8125000, 0.8203125,\n         0.8242188, 0.8281250, 0.8320312, 0.8398438, 0.8437500, 0.8476562,\n         0.8554688, 0.8593750, 0.8671875, 0.8671875, 0.8671875, 0.8671875,\n         0.8671875, 0.8671875, 0.8710938, 0.8710938, 0.8710938, 0.8710938,\n         0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938,\n         0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938,\n         0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000,\n         0.8750000, 0.8750000, 0.8750000, 0.8789062, 0.8789062, 0.8789062,\n         0.8789062, 0.8789062, 0.8789062, 0.8789062, 0.8828125, 0.8828125,\n         0.8828125, 0.8828125, 0.8828125, 0.8867188, 0.8867188, 0.8867188,\n         0.8867188, 0.8867188, 0.8867188, 0.8906250, 0.8906250, 0.8906250,\n         0.8906250, 0.8945312, 0.8945312, 0.8945312, 0.8945312, 0.8945312,\n         0.8984375, 0.8984375, 0.8984375, 0.8984375, 0.9023438, 0.9023438,\n         0.9023438, 0.9023438, 0.9062500, 0.9062500, 0.9062500, 0.9062500,\n         0.9101562, 0.9101562, 0.9101562, 0.9140625, 0.9140625, 0.9140625,\n         0.9140625, 0.9179688, 0.9179688, 0.9179688, 0.9218750, 0.9218750,\n         0.9218750, 0.9257812, 0.9257812, 0.9257812, 0.9296875, 0.9296875,\n         0.9179688, 0.9023438, 0.8867188, 0.8710938, 0.8515625, 0.8359375,\n         0.8203125, 0.8007812, 0.7851562, 0.7656250, 0.7460938, 0.7304688,\n         0.7109375, 0.6914062, 0.6718750, 0.6523438, 0.6328125, 0.6132812,\n         0.5898438, 0.5703125, 0.5468750, 0.5273438, 0.5039062, 0.4804688,\n         0.4570312, 0.4335938, 0.4140625, 0.3867188, 0.3632812, 0.3320312,\n         0.3046875, 0.2890625, 0.2617188, 0.2382812, 0.2109375, 0.1835938,\n         0.1562500, 0.1289062, 0.1054688, 0.1054688]),\narray([  0.9843750, 0.9843750, 0.9765625, 0.9765625, 0.9726562, 0.9726562,\n         0.9687500, 0.9687500, 0.9648438, 0.9648438, 0.9609375, 0.9609375,\n         0.9570312, 0.9570312, 0.9531250, 0.9531250, 0.9492188, 0.9492188,\n         0.9492188, 0.9453125, 0.9453125, 0.9414062, 0.9414062, 0.9414062,\n         0.9375000, 0.9375000, 0.9335938, 0.9335938, 0.9335938, 0.9296875,\n         0.9296875, 0.9296875, 0.9257812, 0.9257812, 0.9257812, 0.9257812,\n         0.9218750, 0.9218750, 0.9218750, 0.9218750, 0.9179688, 0.9179688,\n         0.9179688, 0.9179688, 0.9140625, 0.9140625, 0.9140625, 0.9140625,\n         0.9101562, 0.9101562, 0.9101562, 0.9062500, 0.9062500, 0.9062500,\n         0.9062500, 0.9023438, 0.9023438, 0.9023438, 0.8984375, 0.8984375,\n         0.8984375, 0.8984375, 0.8945312, 0.8945312, 0.8945312, 0.8945312,\n         0.8945312, 0.8906250, 0.8906250, 0.8906250, 0.8906250, 0.8867188,\n         0.8867188, 0.8867188, 0.8867188, 0.8867188, 0.8828125, 0.8828125,\n         0.8828125, 0.8828125, 0.8828125, 0.8828125, 0.8789062, 0.8828125,\n         0.8789062, 0.8789062, 0.8789062, 0.8789062, 0.8750000, 0.8789062,\n         0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000,\n         0.8750000, 0.8750000, 0.8750000, 0.8710938, 0.8710938, 0.8710938,\n         0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938, 0.8710938,\n         0.8710938, 0.8710938, 0.8710938, 0.8671875, 0.8671875, 0.8671875,\n         0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875,\n         0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8671875,\n         0.8671875, 0.8671875, 0.8671875, 0.8671875, 0.8593750, 0.8515625,\n         0.8476562, 0.8398438, 0.8320312, 0.8242188, 0.8203125, 0.8125000,\n         0.8046875, 0.7968750, 0.7890625, 0.7812500, 0.7695312, 0.7617188,\n         0.7539062, 0.7460938, 0.7343750, 0.7265625, 0.7148438, 0.7070312,\n         0.6953125, 0.6875000, 0.6757812, 0.6640625, 0.6562500, 0.6445312,\n         0.6328125, 0.6210938, 0.6093750, 0.5976562, 0.5859375, 0.5703125,\n         0.5585938, 0.5468750, 0.5351562, 0.5195312, 0.5039062, 0.4921875,\n         0.4765625, 0.4609375, 0.4531250, 0.4375000, 0.4179688, 0.4140625,\n         0.4101562, 0.4101562, 0.4062500, 0.3984375, 0.3906250, 0.3867188,\n         0.3867188, 0.3789062, 0.3750000, 0.3671875, 0.3632812, 0.3632812,\n         0.3554688, 0.3515625, 0.3437500, 0.3437500, 0.3359375, 0.3320312,\n         0.3242188, 0.3242188, 0.3164062, 0.3125000, 0.3046875, 0.3046875,\n         0.2968750, 0.2929688, 0.2851562, 0.2812500, 0.2773438, 0.2734375,\n         0.2695312, 0.2617188, 0.2578125, 0.2539062, 0.2460938, 0.2421875,\n         0.2421875, 0.2304688, 0.2265625, 0.2265625, 0.2148438, 0.2109375,\n         0.2109375, 0.1992188, 0.1953125, 0.1914062, 0.1835938, 0.1796875,\n         0.1757812, 0.1679688, 0.1640625, 0.1601562, 0.1523438, 0.1484375,\n         0.1406250, 0.1367188, 0.1328125, 0.1250000, 0.1210938, 0.1171875,\n         0.1093750, 0.1054688, 0.0976562, 0.0937500, 0.0898438, 0.0859375,\n         0.0781250, 0.0703125, 0.0664062, 0.0585938, 0.0546875, 0.0507812,\n         0.0312500, 0.0507812, 0.0312500, 0.0273438, 0.0195312, 0.0156250,\n         0.0078125, 0.0039062, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 21 :: Hue Sat Value 1 ###\n\ncolor_map_luts['idl21'] = \\\n   (\narray([  0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9843750, 0.9648438, 0.9453125, 0.9257812,\n         0.9101562, 0.8906250, 0.8710938, 0.8554688, 0.8359375, 0.8203125,\n         0.8007812, 0.7851562, 0.7656250, 0.7500000, 0.7343750, 0.7187500,\n         0.6992188, 0.6835938, 0.6679688, 0.6523438, 0.6367188, 0.6210938,\n         0.6054688, 0.5898438, 0.5742188, 0.5625000, 0.5468750, 0.5312500,\n         0.5195312, 0.5039062, 0.4882812, 0.4765625, 0.4609375, 0.4492188,\n         0.4375000, 0.4218750, 0.4101562, 0.3984375, 0.3867188, 0.3710938,\n         0.3593750, 0.3476562, 0.3359375, 0.3320312, 0.3359375, 0.3398438,\n         0.3437500, 0.3476562, 0.3515625, 0.3554688, 0.3593750, 0.3632812,\n         0.3671875, 0.3710938, 0.3750000, 0.3789062, 0.3828125, 0.3867188,\n         0.3906250, 0.3945312, 0.3984375, 0.4023438, 0.4062500, 0.4101562,\n         0.4140625, 0.4179688, 0.4218750, 0.4257812, 0.4296875, 0.4335938,\n         0.4375000, 0.4414062, 0.4453125, 0.4492188, 0.4531250, 0.4570312,\n         0.4609375, 0.4648438, 0.4687500, 0.4726562, 0.4765625, 0.4804688,\n         0.4843750, 0.4882812, 0.4921875, 0.4960938, 0.5000000, 0.5039062,\n         0.5078125, 0.5117188, 0.5156250, 0.5195312, 0.5234375, 0.5273438,\n         0.5312500, 0.5351562, 0.5390625, 0.5429688, 0.5468750, 0.5507812,\n         0.5546875, 0.5585938, 0.5625000, 0.5664062, 0.5703125, 0.5742188,\n         0.5781250, 0.5820312, 0.5859375, 0.5898438, 0.5937500, 0.5976562,\n         0.6015625, 0.6054688, 0.6093750, 0.6132812, 0.6171875, 0.6210938,\n         0.6250000, 0.6289062, 0.6328125, 0.6367188, 0.6406250, 0.6445312,\n         0.6484375, 0.6523438, 0.6562500, 0.6601562, 0.6640625, 0.6718750,\n         0.6835938, 0.6914062, 0.7031250, 0.7148438, 0.7265625, 0.7343750,\n         0.7460938, 0.7578125, 0.7656250, 0.7773438, 0.7851562, 0.7929688,\n         0.8046875, 0.8125000, 0.8203125, 0.8320312, 0.8398438, 0.8476562,\n         0.8554688, 0.8632812, 0.8710938, 0.8789062, 0.8867188, 0.8945312,\n         0.8984375, 0.9062500, 0.9140625, 0.9218750, 0.9257812, 0.9335938,\n         0.9375000, 0.9453125, 0.9492188, 0.9570312, 0.9609375, 0.9648438,\n         0.9687500, 0.9765625, 0.9804688, 0.9843750, 0.9882812, 0.9921875,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0039062, 0.0078125, 0.0117188, 0.0156250,\n         0.0195312, 0.0234375, 0.0273438, 0.0312500, 0.0351562, 0.0507812,\n         0.0429688, 0.0468750, 0.0507812, 0.0546875, 0.0585938, 0.0625000,\n         0.0664062, 0.0703125, 0.0742188, 0.0781250, 0.0820312, 0.0859375,\n         0.0898438, 0.0937500, 0.0976562, 0.1015625, 0.1054688, 0.1093750,\n         0.1132812, 0.1171875, 0.1210938, 0.1250000, 0.1289062, 0.1328125,\n         0.1367188, 0.1406250, 0.1445312, 0.1484375, 0.1523438, 0.1562500,\n         0.1601562, 0.1640625, 0.1679688, 0.1718750, 0.1757812, 0.1796875,\n         0.1835938, 0.1875000, 0.1914062, 0.1953125, 0.1992188, 0.2031250,\n         0.2070312, 0.2109375, 0.2148438, 0.2187500, 0.2226562, 0.2265625,\n         0.2304688, 0.2343750, 0.2382812, 0.2421875, 0.2460938, 0.2500000,\n         0.2500000, 0.2539062, 0.2578125, 0.2617188, 0.2656250, 0.2695312,\n         0.2734375, 0.2773438, 0.2812500, 0.2851562, 0.2890625, 0.2929688,\n         0.2968750, 0.3007812, 0.3046875, 0.3085938, 0.3125000, 0.3164062,\n         0.3203125, 0.3242188, 0.3281250, 0.3398438, 0.3593750, 0.3789062,\n         0.3984375, 0.4179688, 0.4335938, 0.4531250, 0.4726562, 0.4882812,\n         0.5078125, 0.5234375, 0.5429688, 0.5585938, 0.5742188, 0.5937500,\n         0.6093750, 0.6250000, 0.6406250, 0.6562500, 0.6718750, 0.6875000,\n         0.7031250, 0.7187500, 0.7343750, 0.7500000, 0.7656250, 0.7773438,\n         0.7929688, 0.8085938, 0.8203125, 0.8359375, 0.8476562, 0.8632812,\n         0.8750000, 0.8906250, 0.9023438, 0.9140625, 0.9296875, 0.9414062,\n         0.9531250, 0.9648438, 0.9765625, 0.9882812, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9921875, 0.9882812, 0.9843750, 0.9804688, 0.9804688, 0.9765625,\n         0.9726562, 0.9726562, 0.9687500, 0.9648438, 0.9648438, 0.9609375,\n         0.9609375, 0.9609375, 0.9570312, 0.9570312, 0.9570312, 0.9570312,\n         0.9531250, 0.9531250, 0.9531250, 0.9531250, 0.9531250, 0.9531250,\n         0.9570312, 0.9570312, 0.9570312, 0.9570312, 0.9609375, 0.9609375,\n         0.9609375, 0.9648438, 0.9648438, 0.9687500, 0.9726562, 0.9726562,\n         0.9765625, 0.9804688, 0.9804688, 0.9804688]),\narray([  0.0117188, 0.0117188, 0.0507812, 0.0664062, 0.0898438, 0.1171875,\n         0.1445312, 0.1718750, 0.1953125, 0.2226562, 0.2460938, 0.2734375,\n         0.2968750, 0.3203125, 0.3476562, 0.3710938, 0.3945312, 0.4179688,\n         0.4453125, 0.4687500, 0.4921875, 0.5156250, 0.5390625, 0.5625000,\n         0.5820312, 0.6054688, 0.6289062, 0.6523438, 0.6718750, 0.6953125,\n         0.7187500, 0.7382812, 0.7617188, 0.7812500, 0.8007812, 0.8242188,\n         0.8437500, 0.8632812, 0.8867188, 0.9062500, 0.9257812, 0.9453125,\n         0.9648438, 0.9843750, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9882812, 0.9765625,\n         0.9648438, 0.9531250, 0.9453125, 0.9335938, 0.9218750, 0.9140625,\n         0.9023438, 0.8945312, 0.8828125, 0.8750000, 0.8632812, 0.8554688,\n         0.8476562, 0.8359375, 0.8281250, 0.8203125, 0.8125000, 0.8046875,\n         0.7968750, 0.7890625, 0.7812500, 0.7734375, 0.7656250, 0.7578125,\n         0.7500000, 0.7460938, 0.7382812, 0.7304688, 0.7265625, 0.7187500,\n         0.7148438, 0.7070312, 0.7031250, 0.6992188, 0.6914062, 0.6875000,\n         0.6835938, 0.6796875, 0.6757812, 0.6718750, 0.6679688, 0.6679688,\n         0.6718750, 0.6757812, 0.6796875, 0.6835938, 0.6875000, 0.6914062,\n         0.6953125, 0.6992188, 0.7031250, 0.7070312, 0.7109375, 0.7148438,\n         0.7187500, 0.7226562, 0.7265625, 0.7304688, 0.7343750, 0.7382812,\n         0.7421875, 0.7460938, 0.7460938, 0.7500000, 0.7539062, 0.7578125,\n         0.7617188, 0.7656250, 0.7695312, 0.7734375, 0.7773438, 0.7812500,\n         0.7851562, 0.7890625, 0.7929688, 0.7968750, 0.8007812, 0.8046875,\n         0.8085938, 0.8125000, 0.8164062, 0.8203125, 0.8242188, 0.8281250,\n         0.8320312, 0.8359375, 0.8398438, 0.8437500, 0.8476562, 0.8515625,\n         0.8554688, 0.8593750, 0.8632812, 0.8671875, 0.8710938, 0.8750000,\n         0.8789062, 0.8828125, 0.8867188, 0.8906250, 0.8945312, 0.8984375,\n         0.9023438, 0.9062500, 0.9101562, 0.9140625, 0.9179688, 0.9218750,\n         0.9257812, 0.9296875, 0.9335938, 0.9375000, 0.9414062, 0.9453125,\n         0.9492188, 0.9531250, 0.9570312, 0.9609375, 0.9648438, 0.9687500,\n         0.9726562, 0.9765625, 0.9804688, 0.9804688]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 22 :: Hue Sat Value 2 ###\n\ncolor_map_luts['idl22'] = \\\n   (\narray([  0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9921875, 0.9882812, 0.9843750, 0.9804688,\n         0.9726562, 0.9687500, 0.9648438, 0.9609375, 0.9531250, 0.9492188,\n         0.9414062, 0.9375000, 0.9296875, 0.9257812, 0.9179688, 0.9101562,\n         0.9023438, 0.8984375, 0.8906250, 0.8828125, 0.8750000, 0.8671875,\n         0.8593750, 0.8515625, 0.8437500, 0.8320312, 0.8242188, 0.8164062,\n         0.8085938, 0.7968750, 0.7890625, 0.7773438, 0.7695312, 0.7578125,\n         0.7500000, 0.7382812, 0.7304688, 0.7187500, 0.7070312, 0.6953125,\n         0.6835938, 0.6718750, 0.6640625, 0.6562500, 0.6523438, 0.6484375,\n         0.6445312, 0.6406250, 0.6367188, 0.6328125, 0.6289062, 0.6250000,\n         0.6210938, 0.6171875, 0.6132812, 0.6093750, 0.6054688, 0.6015625,\n         0.5976562, 0.5937500, 0.5898438, 0.5859375, 0.5820312, 0.5781250,\n         0.5742188, 0.5703125, 0.5664062, 0.5625000, 0.5585938, 0.5546875,\n         0.5507812, 0.5468750, 0.5429688, 0.5390625, 0.5351562, 0.5312500,\n         0.5273438, 0.5234375, 0.5195312, 0.5156250, 0.5117188, 0.5078125,\n         0.5039062, 0.5000000, 0.4960938, 0.4921875, 0.4882812, 0.4843750,\n         0.4804688, 0.4765625, 0.4726562, 0.4687500, 0.4648438, 0.4609375,\n         0.4570312, 0.4531250, 0.4492188, 0.4453125, 0.4414062, 0.4375000,\n         0.4335938, 0.4296875, 0.4257812, 0.4218750, 0.4179688, 0.4140625,\n         0.4101562, 0.4062500, 0.4023438, 0.3984375, 0.3945312, 0.3906250,\n         0.3867188, 0.3828125, 0.3789062, 0.3750000, 0.3710938, 0.3671875,\n         0.3632812, 0.3593750, 0.3554688, 0.3515625, 0.3476562, 0.3437500,\n         0.3398438, 0.3359375, 0.3320312, 0.3320312, 0.3281250, 0.3281250,\n         0.3437500, 0.3554688, 0.3671875, 0.3789062, 0.3906250, 0.4023438,\n         0.4179688, 0.4296875, 0.4453125, 0.4570312, 0.4726562, 0.4843750,\n         0.5000000, 0.5117188, 0.5273438, 0.5429688, 0.5585938, 0.5703125,\n         0.5859375, 0.6015625, 0.6171875, 0.6328125, 0.6484375, 0.6640625,\n         0.6796875, 0.6992188, 0.7148438, 0.7304688, 0.7460938, 0.7656250,\n         0.7812500, 0.8007812, 0.8164062, 0.8359375, 0.8515625, 0.8710938,\n         0.8906250, 0.9101562, 0.9257812, 0.9453125, 0.9648438, 0.9843750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.9882812, 0.9882812, 0.9843750, 0.9804688, 0.9765625, 0.9726562,\n         0.9687500, 0.9648438, 0.9609375, 0.9570312, 0.9531250, 0.9492188,\n         0.9453125, 0.9414062, 0.9375000, 0.9335938, 0.9296875, 0.9257812,\n         0.9218750, 0.9179688, 0.9140625, 0.9101562, 0.9062500, 0.9023438,\n         0.8984375, 0.8945312, 0.8906250, 0.8867188, 0.8828125, 0.8789062,\n         0.8750000, 0.8710938, 0.8671875, 0.8632812, 0.8593750, 0.8554688,\n         0.8515625, 0.8476562, 0.8437500, 0.8398438, 0.8359375, 0.8320312,\n         0.8281250, 0.8242188, 0.8203125, 0.8164062, 0.8125000, 0.8085938,\n         0.8046875, 0.8007812, 0.7968750, 0.7929688, 0.7890625, 0.7851562,\n         0.7812500, 0.7773438, 0.7734375, 0.7695312, 0.7656250, 0.7617188,\n         0.7578125, 0.7539062, 0.7500000, 0.7460938, 0.7421875, 0.7382812,\n         0.7343750, 0.7304688, 0.7265625, 0.7226562, 0.7187500, 0.7148438,\n         0.7109375, 0.7070312, 0.7031250, 0.6992188, 0.6953125, 0.6914062,\n         0.6875000, 0.6835938, 0.6796875, 0.6757812, 0.6718750, 0.6679688,\n         0.6640625, 0.6601562, 0.6601562, 0.6601562, 0.6640625, 0.6679688,\n         0.6718750, 0.6757812, 0.6796875, 0.6875000, 0.6914062, 0.6953125,\n         0.7031250, 0.7070312, 0.7148438, 0.7187500, 0.7265625, 0.7343750,\n         0.7382812, 0.7460938, 0.7539062, 0.7617188, 0.7695312, 0.7773438,\n         0.7851562, 0.7929688, 0.8007812, 0.8085938, 0.8164062, 0.8242188,\n         0.8359375, 0.8437500, 0.8515625, 0.8632812, 0.8710938, 0.8828125,\n         0.8906250, 0.9023438, 0.9101562, 0.9218750, 0.9335938, 0.9414062,\n         0.9531250, 0.9648438, 0.9765625, 0.9882812, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9843750, 0.9648438, 0.9453125, 0.9257812, 0.9062500, 0.8828125,\n         0.8632812, 0.8437500, 0.8242188, 0.8007812, 0.7812500, 0.7578125,\n         0.7382812, 0.7148438, 0.6953125, 0.6718750, 0.6484375, 0.6250000,\n         0.6054688, 0.5820312, 0.5585938, 0.5351562, 0.5117188, 0.4882812,\n         0.4648438, 0.4414062, 0.4179688, 0.3906250, 0.3671875, 0.3437500,\n         0.3164062, 0.2929688, 0.2695312, 0.2421875, 0.2187500, 0.1914062,\n         0.1640625, 0.1406250, 0.1132812, 0.1132812]),\narray([  0.9882812, 0.9882812, 0.9843750, 0.9804688, 0.9765625, 0.9726562,\n         0.9687500, 0.9687500, 0.9648438, 0.9648438, 0.9609375, 0.9609375,\n         0.9570312, 0.9570312, 0.9531250, 0.9531250, 0.9531250, 0.9531250,\n         0.9492188, 0.9492188, 0.9492188, 0.9492188, 0.9492188, 0.9492188,\n         0.9492188, 0.9492188, 0.9531250, 0.9531250, 0.9531250, 0.9531250,\n         0.9570312, 0.9570312, 0.9609375, 0.9609375, 0.9648438, 0.9648438,\n         0.9687500, 0.9726562, 0.9765625, 0.9765625, 0.9804688, 0.9843750,\n         0.9882812, 0.9921875, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9882812, 0.9765625,\n         0.9648438, 0.9531250, 0.9414062, 0.9257812, 0.9140625, 0.9023438,\n         0.8867188, 0.8750000, 0.8593750, 0.8476562, 0.8320312, 0.8203125,\n         0.8046875, 0.7929688, 0.7773438, 0.7617188, 0.7460938, 0.7304688,\n         0.7148438, 0.6992188, 0.6835938, 0.6679688, 0.6523438, 0.6367188,\n         0.6210938, 0.6054688, 0.5859375, 0.5703125, 0.5546875, 0.5351562,\n         0.5195312, 0.5000000, 0.4843750, 0.4648438, 0.4453125, 0.4296875,\n         0.4101562, 0.3906250, 0.3710938, 0.3515625, 0.3359375, 0.3242188,\n         0.3203125, 0.3164062, 0.3125000, 0.3085938, 0.3046875, 0.3007812,\n         0.2968750, 0.2929688, 0.2890625, 0.2851562, 0.2812500, 0.2773438,\n         0.2734375, 0.2695312, 0.2656250, 0.2617188, 0.2578125, 0.2539062,\n         0.2500000, 0.2460938, 0.2421875, 0.2382812, 0.2343750, 0.2304688,\n         0.2265625, 0.2226562, 0.2187500, 0.2148438, 0.2109375, 0.2070312,\n         0.2031250, 0.1992188, 0.1953125, 0.1914062, 0.1875000, 0.1835938,\n         0.1796875, 0.1757812, 0.1718750, 0.1679688, 0.1640625, 0.1601562,\n         0.1562500, 0.1523438, 0.1484375, 0.1445312, 0.1406250, 0.1367188,\n         0.1328125, 0.1289062, 0.1250000, 0.1210938, 0.1171875, 0.1132812,\n         0.1093750, 0.1054688, 0.1015625, 0.0976562, 0.0937500, 0.0898438,\n         0.0859375, 0.0820312, 0.0781250, 0.0742188, 0.0703125, 0.0664062,\n         0.0625000, 0.0585938, 0.0546875, 0.0507812, 0.0468750, 0.0429688,\n         0.0507812, 0.0351562, 0.0312500, 0.0273438, 0.0234375, 0.0195312,\n         0.0156250, 0.0117188, 0.0078125, 0.0078125]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 23 :: Purple-Red + Stripes ###\n\ncolor_map_luts['idl23'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.4960938, 0.7460938, 0.7343750, 0.7265625,\n         0.7187500, 0.7109375, 0.6992188, 0.6914062, 0.5468750, 0.5390625,\n         0.6640625, 0.6562500, 0.6445312, 0.6367188, 0.6289062, 0.6171875,\n         0.6093750, 0.6015625, 0.4726562, 0.4648438, 0.5742188, 0.5664062,\n         0.5546875, 0.5468750, 0.5390625, 0.5273438, 0.5195312, 0.5117188,\n         0.4023438, 0.3945312, 0.4843750, 0.4765625, 0.4648438, 0.4570312,\n         0.4492188, 0.4375000, 0.4296875, 0.4218750, 0.3281250, 0.3203125,\n         0.3945312, 0.3867188, 0.3750000, 0.3671875, 0.3593750, 0.3476562,\n         0.3398438, 0.3320312, 0.2578125, 0.2500000, 0.3046875, 0.2968750,\n         0.2851562, 0.2773438, 0.2695312, 0.2578125, 0.2500000, 0.2421875,\n         0.1835938, 0.1796875, 0.2148438, 0.2031250, 0.1953125, 0.1875000,\n         0.1796875, 0.1679688, 0.1601562, 0.1523438, 0.1132812, 0.1054688,\n         0.1250000, 0.1132812, 0.1054688, 0.0976562, 0.0898438, 0.0781250,\n         0.0703125, 0.0625000, 0.0429688, 0.0351562, 0.0351562, 0.0234375,\n         0.0156250, 0.0078125, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0468750, 0.0703125,\n         0.0937500, 0.1171875, 0.1406250, 0.1640625, 0.1875000, 0.2109375,\n         0.1875000, 0.2070312, 0.2812500, 0.3046875, 0.3320312, 0.3554688,\n         0.3789062, 0.4023438, 0.4257812, 0.4492188, 0.3789062, 0.3984375,\n         0.5195312, 0.5429688, 0.5664062, 0.5898438, 0.6132812, 0.6367188,\n         0.6640625, 0.6875000, 0.5664062, 0.5859375, 0.7578125, 0.7812500,\n         0.8046875, 0.8281250, 0.8515625, 0.8750000, 0.8984375, 0.9218750,\n         0.7578125, 0.7773438, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.7968750, 0.7968750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.7968750, 0.7968750, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.7968750, 0.7968750, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.7968750, 0.7968750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.4960938, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0234375, 0.0468750, 0.0703125,\n         0.0742188, 0.0937500, 0.1406250, 0.1640625, 0.1875000, 0.2109375,\n         0.2343750, 0.2578125, 0.2812500, 0.3046875, 0.2656250, 0.2812500,\n         0.3789062, 0.4023438, 0.4257812, 0.4492188, 0.4726562, 0.4960938,\n         0.5195312, 0.5429688, 0.4531250, 0.4726562, 0.6132812, 0.6367188,\n         0.6640625, 0.6875000, 0.7109375, 0.7343750, 0.7578125, 0.7812500,\n         0.6445312, 0.6640625, 0.8515625, 0.8750000, 0.8984375, 0.9218750,\n         0.9453125, 0.9687500, 0.9960938, 0.9960938, 0.7968750, 0.7968750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.7968750, 0.7968750, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.7968750, 0.7968750, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.7968750, 0.7968750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.7968750, 0.7968750, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.7968750, 0.7968750, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.7968750, 0.7968750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.7968750, 0.7968750, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.7968750, 0.7968750, 0.9960938, 0.9687500, 0.9453125, 0.9179688,\n         0.8945312, 0.8710938, 0.8437500, 0.8203125, 0.6367188, 0.6171875,\n         0.7460938, 0.7187500, 0.6953125, 0.6718750, 0.6445312, 0.6210938,\n         0.5976562, 0.5703125, 0.4375000, 0.4179688, 0.4960938, 0.4726562,\n         0.4492188, 0.4296875, 0.4062500, 0.3828125, 0.3593750, 0.3359375,\n         0.2500000, 0.2343750, 0.2695312, 0.2460938, 0.2226562, 0.2031250,\n         0.1796875, 0.1562500, 0.1328125, 0.1093750, 0.0703125, 0.0507812,\n         0.0429688, 0.0195312, 0.0000000, 0.0000000]),\narray([  0.0000000, 0.0000000, 0.4960938, 0.7460938, 0.7500000, 0.7500000,\n         0.7539062, 0.7578125, 0.7617188, 0.7617188, 0.6132812, 0.6132812,\n         0.7734375, 0.7734375, 0.7773438, 0.7812500, 0.7851562, 0.7890625,\n         0.7890625, 0.7929688, 0.6367188, 0.6406250, 0.8007812, 0.8046875,\n         0.8085938, 0.8125000, 0.8125000, 0.8164062, 0.8203125, 0.8242188,\n         0.6601562, 0.6640625, 0.8320312, 0.8359375, 0.8398438, 0.8398438,\n         0.8437500, 0.8476562, 0.8515625, 0.8515625, 0.6835938, 0.6875000,\n         0.8632812, 0.8632812, 0.8671875, 0.8710938, 0.8750000, 0.8789062,\n         0.8789062, 0.8828125, 0.7070312, 0.7109375, 0.8906250, 0.8945312,\n         0.8984375, 0.9023438, 0.9023438, 0.9062500, 0.9101562, 0.9140625,\n         0.7343750, 0.7343750, 0.9218750, 0.9257812, 0.9296875, 0.9296875,\n         0.9335938, 0.9375000, 0.9414062, 0.9414062, 0.7578125, 0.7578125,\n         0.9531250, 0.9570312, 0.9570312, 0.9609375, 0.9648438, 0.9687500,\n         0.9687500, 0.9726562, 0.7812500, 0.7812500, 0.9804688, 0.9843750,\n         0.9882812, 0.9921875, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.7968750, 0.7968750, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.7968750, 0.7968750,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.7968750, 0.7968750, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.7968750, 0.7968750, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9687500, 0.7578125, 0.7382812,\n         0.8984375, 0.8750000, 0.8515625, 0.8281250, 0.8046875, 0.7812500,\n         0.7578125, 0.7343750, 0.5664062, 0.5468750, 0.6601562, 0.6367188,\n         0.6132812, 0.5898438, 0.5664062, 0.5429688, 0.5195312, 0.4960938,\n         0.3789062, 0.3593750, 0.4257812, 0.4023438, 0.3789062, 0.3554688,\n         0.3281250, 0.3046875, 0.2812500, 0.2578125, 0.1875000, 0.1679688,\n         0.1875000, 0.1640625, 0.1406250, 0.1171875, 0.0937500, 0.0703125,\n         0.0468750, 0.0234375, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0078125, 0.0195312, 0.0273438,\n         0.0390625, 0.0468750, 0.0585938, 0.0664062, 0.0625000, 0.0703125,\n         0.0976562, 0.1093750, 0.1171875, 0.1289062, 0.1367188, 0.1484375,\n         0.1562500, 0.1679688, 0.1406250, 0.1484375, 0.1992188, 0.1875000,\n         0.1796875, 0.1718750, 0.1601562, 0.1523438, 0.1445312, 0.1328125,\n         0.0976562, 0.0937500, 0.1054688, 0.0976562, 0.0898438, 0.0781250,\n         0.0703125, 0.0625000, 0.0507812, 0.0429688, 0.0273438, 0.0195312,\n         0.0156250, 0.0078125, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 24 :: Beach ###\n\ncolor_map_luts['idl24'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0312500,\n         0.0468750, 0.0664062, 0.0820312, 0.0976562, 0.1132812, 0.1328125,\n         0.1484375, 0.1640625, 0.1796875, 0.1992188, 0.2148438, 0.2304688,\n         0.2500000, 0.2656250, 0.2812500, 0.2968750, 0.3164062, 0.3320312,\n         0.3476562, 0.3632812, 0.3828125, 0.3984375, 0.4140625, 0.4296875,\n         0.4492188, 0.4648438, 0.4804688, 0.5000000, 0.5156250, 0.5312500,\n         0.5468750, 0.2968750, 0.2968750, 0.2968750, 0.3164062, 0.3320312,\n         0.3476562, 0.3632812, 0.3828125, 0.3984375, 0.4140625, 0.4296875,\n         0.4492188, 0.4648438, 0.4804688, 0.5000000, 0.5156250, 0.5312500,\n         0.5468750, 0.5664062, 0.5820312, 0.5976562, 0.6132812, 0.6132812,\n         0.6289062, 0.6406250, 0.6523438, 0.6640625, 0.6757812, 0.6914062,\n         0.7031250, 0.7148438, 0.7265625, 0.7382812, 0.7539062, 0.7656250,\n         0.7773438, 0.7890625, 0.8007812, 0.8164062, 0.8281250, 0.8398438,\n         0.8515625, 0.8515625, 0.8554688, 0.8593750, 0.8671875, 0.8710938,\n         0.8750000, 0.8789062, 0.8828125, 0.8867188, 0.8945312, 0.8984375,\n         0.9023438, 0.9062500, 0.9101562, 0.9140625, 0.9218750, 0.9257812,\n         0.9296875, 0.9335938, 0.9375000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.9804688, 0.9804688, 0.9726562, 0.9648438, 0.9570312, 0.9492188,\n         0.9414062, 0.9296875, 0.9218750, 0.9140625, 0.9062500, 0.8984375,\n         0.8906250, 0.8828125, 0.8750000, 0.8632812, 0.8554688, 0.8476562,\n         0.8398438, 0.8320312, 0.8242188, 0.8164062, 0.8046875, 0.7968750,\n         0.7890625, 0.7812500, 0.7734375, 0.7656250, 0.7578125, 0.7500000,\n         0.7382812, 0.7304688, 0.7226562, 0.7148438, 0.7070312, 0.6992188,\n         0.6914062, 0.6796875, 0.6718750, 0.6640625, 0.6562500, 0.6484375,\n         0.6406250, 0.6328125, 0.6250000, 0.6132812, 0.6054688, 0.5976562,\n         0.5898438, 0.5820312, 0.5742188, 0.5664062, 0.5546875, 0.5468750,\n         0.5390625, 0.5312500, 0.5234375, 0.5156250, 0.5078125, 0.5000000,\n         0.5000000, 0.5039062, 0.5117188, 0.5195312, 0.5234375, 0.5312500,\n         0.5390625, 0.5429688, 0.5507812, 0.5585938, 0.5664062, 0.5703125,\n         0.5781250, 0.5859375, 0.5898438, 0.5976562, 0.6054688, 0.6132812,\n         0.6171875, 0.6250000, 0.6328125, 0.6367188, 0.6445312, 0.6523438,\n         0.6562500, 0.6640625, 0.6718750, 0.6796875, 0.6835938, 0.6914062,\n         0.6992188, 0.7031250, 0.7109375, 0.7109375, 0.6914062, 0.6679688,\n         0.6445312, 0.6250000, 0.6015625, 0.5820312, 0.5585938, 0.5390625,\n         0.5156250, 0.4921875, 0.4726562, 0.4492188, 0.4296875, 0.4062500,\n         0.3867188, 0.3632812, 0.3437500, 0.3203125, 0.2968750, 0.2773438,\n         0.2539062, 0.2343750, 0.2109375, 0.1914062, 0.1679688, 0.1445312,\n         0.1250000, 0.1015625, 0.0820312, 0.0585938, 0.0507812, 0.0156250,\n         0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0312500, 0.0468750,\n         0.0664062, 0.0820312, 0.0976562, 0.1132812, 0.1328125, 0.1484375,\n         0.1640625, 0.1796875, 0.1992188, 0.2148438, 0.2304688, 0.2500000,\n         0.2656250, 0.2812500, 0.2968750, 0.3164062, 0.3164062, 0.3320312,\n         0.3476562, 0.3632812, 0.3828125, 0.3984375, 0.4140625, 0.4296875,\n         0.4492188, 0.4648438, 0.4804688, 0.5000000, 0.5156250, 0.5312500,\n         0.5468750, 0.5664062, 0.5820312, 0.5976562, 0.6132812, 0.6328125,\n         0.6328125, 0.6484375, 0.6640625, 0.6796875, 0.6992188, 0.7148438,\n         0.7304688, 0.7500000, 0.7656250, 0.7812500, 0.7968750, 0.8164062,\n         0.8320312, 0.8476562, 0.8632812, 0.8828125, 0.8984375, 0.9140625,\n         0.9296875, 0.9492188, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0156250, 0.0312500, 0.0468750, 0.0664062, 0.0820312, 0.0976562,\n         0.1132812, 0.1328125, 0.1484375, 0.1640625, 0.1796875, 0.1992188,\n         0.2148438, 0.2304688, 0.2500000, 0.2656250, 0.2812500, 0.2968750,\n         0.3164062, 0.3320312, 0.3476562, 0.3632812, 0.3828125, 0.3984375,\n         0.4140625, 0.4296875, 0.4492188, 0.4648438, 0.4804688, 0.5000000,\n         0.5156250, 0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500,\n         0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500,\n         0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500,\n         0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500,\n         0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500,\n         0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.5312500, 0.2968750,\n         0.2968750, 0.2968750, 0.2812500, 0.2656250, 0.2500000, 0.2343750,\n         0.2187500, 0.2031250, 0.1875000, 0.1718750, 0.1562500, 0.1406250,\n         0.1250000, 0.1093750, 0.0898438, 0.0742188, 0.0585938, 0.0429688,\n         0.0273438, 0.0117188, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0156250, 0.0312500, 0.0468750, 0.0664062,\n         0.0820312, 0.0976562, 0.1132812, 0.1328125, 0.1484375, 0.1640625,\n         0.1796875, 0.1992188, 0.2148438, 0.2304688, 0.2500000, 0.2656250,\n         0.2812500, 0.2968750, 0.3164062, 0.3164062, 0.3281250, 0.3437500,\n         0.3554688, 0.3710938, 0.3828125, 0.3984375, 0.4101562, 0.4257812,\n         0.4414062, 0.4531250, 0.4687500, 0.4804688, 0.4960938, 0.5078125,\n         0.5234375, 0.5351562, 0.5507812, 0.5664062, 0.5781250, 0.5937500,\n         0.6054688, 0.6210938, 0.6210938, 0.6328125, 0.6484375, 0.6640625,\n         0.6757812, 0.6914062, 0.7070312, 0.7187500, 0.7343750, 0.7500000,\n         0.7617188, 0.7773438, 0.7929688, 0.8046875, 0.8203125, 0.8359375,\n         0.8476562, 0.8632812, 0.8789062, 0.8906250, 0.9062500, 0.9218750,\n         0.9335938, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 25 :: Mac Style ###\n\ncolor_map_luts['idl25'] = \\\n   (\narray([  0.4843750, 0.4843750, 0.4687500, 0.4492188, 0.4335938, 0.4140625,\n         0.3984375, 0.3789062, 0.3632812, 0.3437500, 0.3281250, 0.3085938,\n         0.2929688, 0.2734375, 0.2578125, 0.2382812, 0.2226562, 0.2031250,\n         0.1875000, 0.1679688, 0.1523438, 0.1328125, 0.1171875, 0.0976562,\n         0.0820312, 0.0625000, 0.0468750, 0.0273438, 0.0117188, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0039062, 0.0195312,\n         0.0507812, 0.0546875, 0.0742188, 0.0898438, 0.1093750, 0.1250000,\n         0.1445312, 0.1601562, 0.1796875, 0.1953125, 0.2148438, 0.2304688,\n         0.2500000, 0.2656250, 0.2851562, 0.3007812, 0.3203125, 0.3359375,\n         0.3554688, 0.3710938, 0.3906250, 0.4062500, 0.4257812, 0.4414062,\n         0.4609375, 0.4804688, 0.4921875, 0.5156250, 0.5273438, 0.5507812,\n         0.5625000, 0.5859375, 0.5976562, 0.6210938, 0.6328125, 0.6562500,\n         0.6679688, 0.6914062, 0.7031250, 0.7265625, 0.7382812, 0.7617188,\n         0.7734375, 0.7968750, 0.8085938, 0.8320312, 0.8437500, 0.8671875,\n         0.8789062, 0.9023438, 0.9140625, 0.9375000, 0.9492188, 0.9726562,\n         0.9843750, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0078125,\n         0.0234375, 0.0429688, 0.0585938, 0.0781250, 0.0937500, 0.1132812,\n         0.1289062, 0.1484375, 0.1640625, 0.1835938, 0.1992188, 0.2187500,\n         0.2343750, 0.2539062, 0.2695312, 0.2890625, 0.3046875, 0.3242188,\n         0.3398438, 0.3593750, 0.3750000, 0.3945312, 0.4101562, 0.4296875,\n         0.4453125, 0.4648438, 0.4765625, 0.5000000, 0.5117188, 0.5351562,\n         0.5468750, 0.5703125, 0.5820312, 0.6054688, 0.6171875, 0.6406250,\n         0.6523438, 0.6757812, 0.6875000, 0.7109375, 0.7226562, 0.7460938,\n         0.7578125, 0.7812500, 0.7929688, 0.8164062, 0.8281250, 0.8515625,\n         0.8632812, 0.8867188, 0.8984375, 0.9218750, 0.9375000, 0.9570312,\n         0.9726562, 0.9921875, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9843750, 0.9648438, 0.9492188, 0.9296875, 0.9140625,\n         0.8945312, 0.8789062, 0.8593750, 0.8437500, 0.8242188, 0.8085938,\n         0.7890625, 0.7734375, 0.7539062, 0.7382812, 0.7187500, 0.7031250,\n         0.6835938, 0.6679688, 0.6484375, 0.6328125, 0.6132812, 0.5976562,\n         0.5781250, 0.5625000, 0.5429688, 0.5273438, 0.5117188, 0.4921875,\n         0.4765625, 0.4570312, 0.4414062, 0.4218750, 0.4062500, 0.3867188,\n         0.3710938, 0.3515625, 0.3359375, 0.3164062, 0.3007812, 0.2812500,\n         0.2656250, 0.2460938, 0.2304688, 0.2109375, 0.1953125, 0.1757812,\n         0.1601562, 0.1406250, 0.1250000, 0.1054688, 0.0898438, 0.0703125,\n         0.0546875, 0.0351562, 0.0195312, 0.0195312]),\narray([  0.9960938, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9921875, 0.9960938, 0.9921875, 0.9960938,\n         0.9921875, 0.9960938, 0.9765625, 0.9648438, 0.9414062, 0.9296875,\n         0.9062500, 0.8945312, 0.8710938, 0.8593750, 0.8359375, 0.8242188,\n         0.8007812, 0.7890625, 0.7656250, 0.7539062, 0.7304688, 0.7187500,\n         0.6953125, 0.6835938, 0.6601562, 0.6484375, 0.6250000, 0.6132812,\n         0.5898438, 0.5781250, 0.5546875, 0.5429688, 0.5195312, 0.5078125,\n         0.4882812, 0.4726562, 0.4531250, 0.4375000, 0.4179688, 0.4023438,\n         0.3828125, 0.3671875, 0.3476562, 0.3320312, 0.3125000, 0.2968750,\n         0.2773438, 0.2617188, 0.2421875, 0.2265625, 0.2070312, 0.1914062,\n         0.1718750, 0.1562500, 0.1367188, 0.1210938, 0.1015625, 0.0859375,\n         0.0664062, 0.0507812, 0.0312500, 0.0156250, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 26 :: Eos A ###\n\ncolor_map_luts['idl26'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.4960938, 0.4804688, 0.4648438, 0.4453125,\n         0.4296875, 0.4140625, 0.3984375, 0.3789062, 0.3281250, 0.3125000,\n         0.2968750, 0.3125000, 0.2968750, 0.2812500, 0.2617188, 0.2460938,\n         0.2304688, 0.2148438, 0.1757812, 0.1640625, 0.1484375, 0.1484375,\n         0.1289062, 0.1132812, 0.0976562, 0.0820312, 0.0625000, 0.0468750,\n         0.0273438, 0.0117188, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0312500, 0.0664062, 0.0976562,\n         0.1328125, 0.1640625, 0.1992188, 0.2304688, 0.2382812, 0.2656250,\n         0.2968750, 0.3632812, 0.3984375, 0.4296875, 0.4648438, 0.4960938,\n         0.5312500, 0.5625000, 0.5351562, 0.5664062, 0.5976562, 0.6953125,\n         0.7304688, 0.7617188, 0.7968750, 0.8281250, 0.8632812, 0.8945312,\n         0.8359375, 0.8632812, 0.8945312, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8945312, 0.8945312,\n         0.8945312, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.8945312, 0.8945312, 0.8945312, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.8945312, 0.8945312, 0.8945312, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8945312, 0.8945312,\n         0.8945312, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.8945312, 0.8945312, 0.8945312, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.8945312, 0.8945312, 0.8945312, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8945312, 0.8945312,\n         0.8945312, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.8945312, 0.8945312, 0.8945312, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.8945312, 0.8945312, 0.8945312, 0.9882812, 0.9804688, 0.9726562,\n         0.9687500, 0.9609375, 0.9531250, 0.9453125, 0.8476562, 0.8398438,\n         0.8320312, 0.9218750, 0.9140625, 0.9062500, 0.8984375, 0.8945312,\n         0.8867188, 0.8789062, 0.7851562, 0.7812500, 0.7734375, 0.8515625,\n         0.8476562, 0.8398438, 0.8320312, 0.8242188, 0.8203125, 0.8125000,\n         0.7265625, 0.7187500, 0.7109375, 0.7851562, 0.7773438, 0.7734375,\n         0.7656250, 0.7578125, 0.7500000, 0.7460938, 0.6640625, 0.6601562,\n         0.6523438, 0.7187500, 0.7109375, 0.7031250, 0.6992188, 0.6914062,\n         0.6835938, 0.6796875, 0.6054688, 0.5976562, 0.5898438, 0.6523438,\n         0.6445312, 0.6367188, 0.6289062, 0.6250000, 0.6171875, 0.6093750,\n         0.5429688, 0.5390625, 0.5312500, 0.5820312, 0.5781250, 0.5703125,\n         0.5625000, 0.5585938, 0.5507812, 0.5429688, 0.4843750, 0.4765625,\n         0.4687500, 0.5156250, 0.5078125, 0.5078125]),\narray([  0.0000000, 0.0000000, 0.4960938, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0664062, 0.1328125, 0.1992188,\n         0.2656250, 0.3320312, 0.3984375, 0.4648438, 0.4765625, 0.5351562,\n         0.5976562, 0.7304688, 0.7968750, 0.8632812, 0.9296875, 0.9960938,\n         0.9960938, 0.9960938, 0.8945312, 0.8945312, 0.8945312, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.8945312, 0.8945312, 0.8945312, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8945312, 0.8945312,\n         0.8945312, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.8945312, 0.8945312, 0.8945312, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.8945312, 0.8945312, 0.8945312, 0.9765625, 0.9609375, 0.9453125,\n         0.9296875, 0.9101562, 0.8945312, 0.8789062, 0.7734375, 0.7617188,\n         0.7460938, 0.8125000, 0.7968750, 0.7773438, 0.7617188, 0.7460938,\n         0.7304688, 0.7109375, 0.6250000, 0.6093750, 0.5937500, 0.6445312,\n         0.6289062, 0.6132812, 0.5976562, 0.5781250, 0.5625000, 0.5468750,\n         0.4765625, 0.4609375, 0.4453125, 0.4882812, 0.4804688, 0.4726562,\n         0.4648438, 0.4531250, 0.4453125, 0.4375000, 0.3867188, 0.3789062,\n         0.3710938, 0.4062500, 0.3984375, 0.3867188, 0.3789062, 0.3710938,\n         0.3632812, 0.3554688, 0.3125000, 0.3046875, 0.2968750, 0.3203125,\n         0.3125000, 0.3046875, 0.2968750, 0.2890625, 0.2812500, 0.2734375,\n         0.2382812, 0.2304688, 0.2226562, 0.2382812, 0.2304688, 0.2226562,\n         0.2148438, 0.2070312, 0.1953125, 0.1875000, 0.1640625, 0.1562500,\n         0.1484375, 0.1562500, 0.1484375, 0.1406250, 0.1289062, 0.1210938,\n         0.1132812, 0.1054688, 0.0859375, 0.0820312, 0.0742188, 0.0742188,\n         0.0625000, 0.0546875, 0.0468750, 0.0507812, 0.0312500, 0.0234375,\n         0.0117188, 0.0039062, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.0000000, 0.0000000, 0.4960938, 0.7539062, 0.7617188, 0.7695312,\n         0.7773438, 0.7851562, 0.7968750, 0.8046875, 0.7304688, 0.7382812,\n         0.7460938, 0.8359375, 0.8437500, 0.8515625, 0.8632812, 0.8710938,\n         0.8789062, 0.8867188, 0.8046875, 0.8125000, 0.8203125, 0.9179688,\n         0.9296875, 0.9375000, 0.9453125, 0.9531250, 0.9609375, 0.9687500,\n         0.8789062, 0.8867188, 0.8945312, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8945312, 0.8945312,\n         0.8945312, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9296875, 0.8632812, 0.7148438, 0.6562500, 0.5937500, 0.5976562,\n         0.5273438, 0.4609375, 0.3945312, 0.3281250, 0.2617188, 0.1953125,\n         0.1171875, 0.0585938, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 27 :: Eos B ###\n\ncolor_map_luts['idl27'] = \\\n   (\narray([  0.9960938, 0.9960938, 0.4960938, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0351562,\n         0.0703125, 0.1054688, 0.1406250, 0.1757812, 0.1914062, 0.2226562,\n         0.2812500, 0.3164062, 0.3554688, 0.3906250, 0.4257812, 0.4609375,\n         0.4960938, 0.5312500, 0.5117188, 0.5429688, 0.6367188, 0.6757812,\n         0.7109375, 0.7460938, 0.7812500, 0.8164062, 0.8515625, 0.8867188,\n         0.8320312, 0.8632812, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8945312, 0.8945312,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.8945312, 0.8945312, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.8945312, 0.8945312, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8945312, 0.8945312,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.8945312, 0.8945312, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.8945312, 0.8945312, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8945312, 0.8945312,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.8945312, 0.8945312, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.8945312, 0.8945312, 0.9960938, 0.9882812, 0.9804688, 0.9726562,\n         0.9648438, 0.9570312, 0.9492188, 0.9414062, 0.8398438, 0.8359375,\n         0.9179688, 0.9140625, 0.9062500, 0.8984375, 0.8906250, 0.8828125,\n         0.8750000, 0.8671875, 0.7734375, 0.7656250, 0.8437500, 0.8398438,\n         0.8320312, 0.8242188, 0.8164062, 0.8085938, 0.8007812, 0.7929688,\n         0.7070312, 0.6992188, 0.7695312, 0.7656250, 0.7578125, 0.7500000,\n         0.7421875, 0.7343750, 0.7265625, 0.7187500, 0.6406250, 0.6328125,\n         0.6953125, 0.6875000, 0.6835938, 0.6757812, 0.6679688, 0.6601562,\n         0.6523438, 0.6445312, 0.5742188, 0.5664062, 0.6210938, 0.6132812,\n         0.6093750, 0.6015625, 0.5937500, 0.5859375, 0.5781250, 0.5703125,\n         0.5078125, 0.5000000, 0.5468750, 0.5390625, 0.5351562, 0.5273438,\n         0.5195312, 0.5117188, 0.5039062, 0.4960938, 0.4414062, 0.4335938,\n         0.4726562, 0.4648438, 0.4570312, 0.4570312]),\narray([  0.9960938, 0.9960938, 0.4960938, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0273438, 0.0585938, 0.0898438,\n         0.1210938, 0.1523438, 0.1835938, 0.2148438, 0.2226562, 0.2500000,\n         0.3085938, 0.3398438, 0.3710938, 0.4023438, 0.4335938, 0.4648438,\n         0.4960938, 0.5273438, 0.5039062, 0.5312500, 0.6210938, 0.6523438,\n         0.6835938, 0.7148438, 0.7460938, 0.7773438, 0.8085938, 0.8398438,\n         0.7812500, 0.8085938, 0.9335938, 0.9648438, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.8945312, 0.8945312,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.8945312, 0.8945312, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.8945312, 0.8945312, 0.9960938, 0.9765625, 0.9609375, 0.9453125,\n         0.9296875, 0.9101562, 0.8945312, 0.8789062, 0.7734375, 0.7617188,\n         0.8281250, 0.8125000, 0.7968750, 0.7773438, 0.7617188, 0.7460938,\n         0.7304688, 0.7109375, 0.6250000, 0.6093750, 0.6601562, 0.6445312,\n         0.6289062, 0.6132812, 0.5976562, 0.5781250, 0.5625000, 0.5468750,\n         0.4765625, 0.4609375, 0.4960938, 0.4882812, 0.4804688, 0.4726562,\n         0.4648438, 0.4531250, 0.4453125, 0.4375000, 0.3867188, 0.3789062,\n         0.4140625, 0.4062500, 0.3984375, 0.3867188, 0.3789062, 0.3710938,\n         0.3632812, 0.3554688, 0.3125000, 0.3046875, 0.3281250, 0.3203125,\n         0.3125000, 0.3046875, 0.2968750, 0.2890625, 0.2812500, 0.2734375,\n         0.2382812, 0.2304688, 0.2460938, 0.2382812, 0.2304688, 0.2226562,\n         0.2148438, 0.2070312, 0.1953125, 0.1875000, 0.1640625, 0.1562500,\n         0.1640625, 0.1562500, 0.1484375, 0.1406250, 0.1289062, 0.1210938,\n         0.1132812, 0.1054688, 0.0859375, 0.0820312, 0.0820312, 0.0742188,\n         0.0625000, 0.0546875, 0.0468750, 0.0507812, 0.0312500, 0.0234375,\n         0.0117188, 0.0039062, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.9960938, 0.9960938, 0.4960938, 0.5117188, 0.5312500, 0.5468750,\n         0.5625000, 0.5781250, 0.5976562, 0.6132812, 0.5664062, 0.5820312,\n         0.6640625, 0.6796875, 0.6953125, 0.7109375, 0.7304688, 0.7460938,\n         0.7617188, 0.7773438, 0.7148438, 0.7304688, 0.8281250, 0.8437500,\n         0.8632812, 0.8789062, 0.8945312, 0.9101562, 0.9296875, 0.9453125,\n         0.8632812, 0.8789062, 0.9960938, 0.9648438, 0.9335938, 0.9023438,\n         0.8710938, 0.8398438, 0.8085938, 0.7773438, 0.6718750, 0.6406250,\n         0.6835938, 0.6523438, 0.6210938, 0.5898438, 0.5585938, 0.5273438,\n         0.4960938, 0.4648438, 0.3906250, 0.3632812, 0.3710938, 0.3398438,\n         0.3085938, 0.2773438, 0.2460938, 0.2148438, 0.1835938, 0.1523438,\n         0.1093750, 0.0820312, 0.0585938, 0.0273438, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 28 :: Hardcandy ###\n\ncolor_map_luts['idl28'] = \\\n   (\narray([  0.0156250, 0.0156250, 0.0195312, 0.0273438, 0.0351562, 0.0429688,\n         0.0468750, 0.0546875, 0.0585938, 0.0625000, 0.0664062, 0.0664062,\n         0.0703125, 0.0703125, 0.0703125, 0.0664062, 0.0625000, 0.0585938,\n         0.0546875, 0.0507812, 0.0429688, 0.0507812, 0.0312500, 0.0195312,\n         0.0117188, 0.0039062, 0.0000000, 0.0117188, 0.0195312, 0.0273438,\n         0.0507812, 0.0468750, 0.0507812, 0.0585938, 0.0664062, 0.0703125,\n         0.0742188, 0.0742188, 0.0742188, 0.0742188, 0.0742188, 0.0703125,\n         0.0625000, 0.0546875, 0.0468750, 0.0351562, 0.0195312, 0.0078125,\n         0.0078125, 0.0234375, 0.0429688, 0.0664062, 0.0898438, 0.1132812,\n         0.1406250, 0.1640625, 0.1914062, 0.2187500, 0.2500000, 0.2773438,\n         0.3046875, 0.3320312, 0.3632812, 0.3906250, 0.4140625, 0.4414062,\n         0.4648438, 0.4882812, 0.5078125, 0.5273438, 0.5429688, 0.5585938,\n         0.5703125, 0.5820312, 0.5859375, 0.5898438, 0.5937500, 0.5898438,\n         0.5859375, 0.5781250, 0.5664062, 0.5546875, 0.5390625, 0.5195312,\n         0.4960938, 0.4726562, 0.4453125, 0.4179688, 0.3867188, 0.3515625,\n         0.3203125, 0.2851562, 0.2460938, 0.2109375, 0.1718750, 0.1367188,\n         0.0976562, 0.0625000, 0.0234375, 0.0039062, 0.0507812, 0.0703125,\n         0.0976562, 0.1250000, 0.1484375, 0.1718750, 0.1875000, 0.2031250,\n         0.2148438, 0.2187500, 0.2226562, 0.2226562, 0.2187500, 0.2070312,\n         0.1953125, 0.1757812, 0.1523438, 0.1289062, 0.0976562, 0.0625000,\n         0.0234375, 0.0117188, 0.0585938, 0.1054688, 0.1562500, 0.2070312,\n         0.2617188, 0.3164062, 0.3750000, 0.4335938, 0.4921875, 0.5507812,\n         0.6093750, 0.6640625, 0.7226562, 0.7773438, 0.8281250, 0.8789062,\n         0.9257812, 0.9687500, 0.9843750, 0.9453125, 0.9140625, 0.8867188,\n         0.8671875, 0.8476562, 0.8359375, 0.8281250, 0.8281250, 0.8320312,\n         0.8437500, 0.8593750, 0.8789062, 0.9062500, 0.9375000, 0.9726562,\n         0.9804688, 0.9335938, 0.8828125, 0.8320312, 0.7734375, 0.7148438,\n         0.6523438, 0.5859375, 0.5195312, 0.4531250, 0.3828125, 0.3164062,\n         0.2500000, 0.1835938, 0.1171875, 0.0546875, 0.0039062, 0.0585938,\n         0.1132812, 0.1640625, 0.2109375, 0.2500000, 0.2851562, 0.3164062,\n         0.3398438, 0.3554688, 0.3671875, 0.3710938, 0.3710938, 0.3593750,\n         0.3437500, 0.3203125, 0.2929688, 0.2578125, 0.2148438, 0.1640625,\n         0.1093750, 0.0507812, 0.0117188, 0.0820312, 0.1562500, 0.2343750,\n         0.3164062, 0.4023438, 0.4882812, 0.5742188, 0.6640625, 0.7539062,\n         0.8437500, 0.9335938, 0.9726562, 0.8906250, 0.8085938, 0.7304688,\n         0.6562500, 0.5898438, 0.5234375, 0.4648438, 0.4140625, 0.3710938,\n         0.3320312, 0.3007812, 0.2773438, 0.2617188, 0.2539062, 0.2578125,\n         0.2656250, 0.2812500, 0.3085938, 0.3398438, 0.3828125, 0.4296875,\n         0.4843750, 0.5468750, 0.6171875, 0.6914062, 0.7695312, 0.8554688,\n         0.9414062, 0.9570312, 0.8632812, 0.7656250, 0.6679688, 0.5664062,\n         0.4687500, 0.3710938, 0.2734375, 0.1796875, 0.0898438, 0.0039062,\n         0.0742188, 0.1523438, 0.2226562, 0.2890625, 0.3476562, 0.3984375,\n         0.4375000, 0.4726562, 0.4960938, 0.4960938]),\narray([  0.1992188, 0.1992188, 0.2265625, 0.2539062, 0.2812500, 0.3085938,\n         0.3320312, 0.3593750, 0.3867188, 0.4101562, 0.4335938, 0.4570312,\n         0.4843750, 0.5039062, 0.5273438, 0.5507812, 0.5703125, 0.5937500,\n         0.6132812, 0.6328125, 0.6523438, 0.6679688, 0.6875000, 0.7031250,\n         0.7187500, 0.7343750, 0.7460938, 0.7617188, 0.7734375, 0.7851562,\n         0.7929688, 0.8046875, 0.8125000, 0.8203125, 0.8281250, 0.8320312,\n         0.8359375, 0.8398438, 0.8437500, 0.8437500, 0.8437500, 0.8437500,\n         0.8437500, 0.8398438, 0.8398438, 0.8320312, 0.8281250, 0.8203125,\n         0.8164062, 0.8046875, 0.7968750, 0.7851562, 0.7773438, 0.7617188,\n         0.7500000, 0.7382812, 0.7226562, 0.7070312, 0.6914062, 0.6718750,\n         0.6562500, 0.6367188, 0.6171875, 0.5976562, 0.5781250, 0.5546875,\n         0.5351562, 0.5117188, 0.4882812, 0.4648438, 0.4414062, 0.4140625,\n         0.3906250, 0.3632812, 0.3398438, 0.3125000, 0.2851562, 0.2617188,\n         0.2343750, 0.2070312, 0.1796875, 0.1523438, 0.1250000, 0.0976562,\n         0.0703125, 0.0429688, 0.0156250, 0.0078125, 0.0351562, 0.0625000,\n         0.0859375, 0.1132812, 0.1406250, 0.1640625, 0.1914062, 0.2148438,\n         0.2382812, 0.2656250, 0.2890625, 0.3085938, 0.3320312, 0.3554688,\n         0.3750000, 0.3945312, 0.4140625, 0.4335938, 0.4531250, 0.4687500,\n         0.4882812, 0.5039062, 0.5195312, 0.5312500, 0.5468750, 0.5585938,\n         0.5703125, 0.5820312, 0.5898438, 0.5976562, 0.6054688, 0.6132812,\n         0.6210938, 0.6250000, 0.6289062, 0.6328125, 0.6328125, 0.6328125,\n         0.6328125, 0.6328125, 0.6328125, 0.6289062, 0.6250000, 0.6210938,\n         0.6132812, 0.6054688, 0.5976562, 0.5898438, 0.5781250, 0.5703125,\n         0.5585938, 0.5429688, 0.5312500, 0.5156250, 0.5000000, 0.4843750,\n         0.4687500, 0.4492188, 0.4335938, 0.4140625, 0.3945312, 0.3710938,\n         0.3515625, 0.3281250, 0.3085938, 0.2851562, 0.2617188, 0.2382812,\n         0.2109375, 0.1875000, 0.1640625, 0.1367188, 0.1093750, 0.0859375,\n         0.0585938, 0.0312500, 0.0039062, 0.0195312, 0.0468750, 0.0742188,\n         0.1015625, 0.1289062, 0.1562500, 0.1835938, 0.2109375, 0.2382812,\n         0.2617188, 0.2890625, 0.3164062, 0.3437500, 0.3671875, 0.3945312,\n         0.4179688, 0.4414062, 0.4687500, 0.4921875, 0.5117188, 0.5351562,\n         0.5585938, 0.5781250, 0.6015625, 0.6210938, 0.6406250, 0.6562500,\n         0.6757812, 0.6914062, 0.7070312, 0.7226562, 0.7382812, 0.7539062,\n         0.7656250, 0.7773438, 0.7890625, 0.7968750, 0.8085938, 0.8164062,\n         0.8242188, 0.8281250, 0.8359375, 0.8398438, 0.8437500, 0.8437500,\n         0.8437500, 0.8437500, 0.8437500, 0.8437500, 0.8398438, 0.8359375,\n         0.8320312, 0.8281250, 0.8203125, 0.8125000, 0.8046875, 0.7929688,\n         0.7851562, 0.7734375, 0.7578125, 0.7460938, 0.7304688, 0.7187500,\n         0.7031250, 0.6835938, 0.6679688, 0.6484375, 0.6289062, 0.6093750,\n         0.5898438, 0.5703125, 0.5468750, 0.5273438, 0.5039062, 0.4804688,\n         0.4570312, 0.4335938, 0.4062500, 0.3828125, 0.3554688, 0.3320312,\n         0.3046875, 0.2773438, 0.2500000, 0.2226562, 0.1953125, 0.1718750,\n         0.1445312, 0.1171875, 0.0898438, 0.0898438]),\narray([  0.4531250, 0.4531250, 0.4101562, 0.3671875, 0.3281250, 0.2890625,\n         0.2500000, 0.2148438, 0.1796875, 0.1484375, 0.1171875, 0.0937500,\n         0.0703125, 0.0468750, 0.0312500, 0.0195312, 0.0078125, 0.0000000,\n         0.0000000, 0.0000000, 0.0039062, 0.0117188, 0.0234375, 0.0507812,\n         0.0546875, 0.0781250, 0.1015625, 0.1289062, 0.1601562, 0.1953125,\n         0.2304688, 0.2656250, 0.3046875, 0.3437500, 0.3867188, 0.4257812,\n         0.4687500, 0.5117188, 0.5546875, 0.5976562, 0.6367188, 0.6796875,\n         0.7187500, 0.7539062, 0.7890625, 0.8242188, 0.8515625, 0.8828125,\n         0.9062500, 0.9296875, 0.9492188, 0.9648438, 0.9765625, 0.9843750,\n         0.9882812, 0.9882812, 0.9882812, 0.9804688, 0.9726562, 0.9609375,\n         0.9414062, 0.9218750, 0.8984375, 0.8750000, 0.8437500, 0.8125000,\n         0.7812500, 0.7421875, 0.7070312, 0.6679688, 0.6250000, 0.5859375,\n         0.5429688, 0.5000000, 0.4570312, 0.4140625, 0.3750000, 0.3320312,\n         0.2929688, 0.2539062, 0.2187500, 0.1835938, 0.1523438, 0.1210938,\n         0.0937500, 0.0703125, 0.0507812, 0.0351562, 0.0195312, 0.0078125,\n         0.0000000, 0.0000000, 0.0000000, 0.0039062, 0.0117188, 0.0195312,\n         0.0351562, 0.0546875, 0.0742188, 0.0976562, 0.1250000, 0.1562500,\n         0.1875000, 0.2226562, 0.2617188, 0.2968750, 0.3398438, 0.3789062,\n         0.4218750, 0.4648438, 0.5078125, 0.5468750, 0.5898438, 0.6328125,\n         0.6718750, 0.7109375, 0.7500000, 0.7851562, 0.8164062, 0.8476562,\n         0.8789062, 0.9023438, 0.9257812, 0.9453125, 0.9609375, 0.9726562,\n         0.9843750, 0.9882812, 0.9921875, 0.9882812, 0.9843750, 0.9726562,\n         0.9609375, 0.9453125, 0.9257812, 0.9023438, 0.8789062, 0.8476562,\n         0.8164062, 0.7851562, 0.7500000, 0.7109375, 0.6718750, 0.6328125,\n         0.5898438, 0.5468750, 0.5078125, 0.4648438, 0.4218750, 0.3789062,\n         0.3398438, 0.2968750, 0.2617188, 0.2226562, 0.1875000, 0.1562500,\n         0.1250000, 0.0976562, 0.0742188, 0.0546875, 0.0351562, 0.0195312,\n         0.0117188, 0.0039062, 0.0000000, 0.0000000, 0.0000000, 0.0078125,\n         0.0195312, 0.0351562, 0.0507812, 0.0703125, 0.0937500, 0.1210938,\n         0.1523438, 0.1835938, 0.2187500, 0.2539062, 0.2929688, 0.3320312,\n         0.3750000, 0.4140625, 0.4570312, 0.5000000, 0.5429688, 0.5859375,\n         0.6250000, 0.6679688, 0.7070312, 0.7421875, 0.7812500, 0.8125000,\n         0.8437500, 0.8750000, 0.8984375, 0.9218750, 0.9414062, 0.9609375,\n         0.9726562, 0.9804688, 0.9882812, 0.9882812, 0.9882812, 0.9843750,\n         0.9765625, 0.9648438, 0.9492188, 0.9296875, 0.9062500, 0.8828125,\n         0.8515625, 0.8242188, 0.7890625, 0.7539062, 0.7187500, 0.6796875,\n         0.6367188, 0.5976562, 0.5546875, 0.5117188, 0.4687500, 0.4257812,\n         0.3867188, 0.3437500, 0.3046875, 0.2656250, 0.2304688, 0.1953125,\n         0.1601562, 0.1289062, 0.1015625, 0.0781250, 0.0546875, 0.0507812,\n         0.0234375, 0.0117188, 0.0039062, 0.0000000, 0.0000000, 0.0000000,\n         0.0078125, 0.0195312, 0.0312500, 0.0468750, 0.0703125, 0.0937500,\n         0.1171875, 0.1484375, 0.1796875, 0.2148438, 0.2500000, 0.2890625,\n         0.3281250, 0.3671875, 0.4101562, 0.4101562]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 29 :: Nature ###\n\ncolor_map_luts['idl29'] = \\\n   (\narray([  0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562,\n         0.4101562, 0.4101562, 0.4101562, 0.4296875, 0.4453125, 0.4804688,\n         0.4960938, 0.5117188, 0.5273438, 0.5585938, 0.5742188, 0.5859375,\n         0.6132812, 0.6250000, 0.6367188, 0.6445312, 0.6640625, 0.6718750,\n         0.6796875, 0.6875000, 0.6992188, 0.7031250, 0.7031250, 0.7070312,\n         0.7109375, 0.7070312, 0.7070312, 0.7031250, 0.6992188, 0.6914062,\n         0.6796875, 0.6718750, 0.6640625, 0.6562500, 0.6367188, 0.6250000,\n         0.6132812, 0.6015625, 0.5742188, 0.5585938, 0.5429688, 0.5117188,\n         0.4960938, 0.4804688, 0.4648438, 0.4296875, 0.4101562, 0.3906250,\n         0.3750000, 0.3398438, 0.3203125, 0.3007812, 0.2656250, 0.2460938,\n         0.2304688, 0.2109375, 0.1796875, 0.1640625, 0.1484375, 0.1171875,\n         0.1015625, 0.0898438, 0.0742188, 0.0507812, 0.0507812, 0.0312500,\n         0.0195312, 0.0039062, 0.0000000, 0.0078125, 0.0195312, 0.0234375,\n         0.0234375, 0.0273438, 0.0312500, 0.0273438, 0.0273438, 0.0234375,\n         0.0195312, 0.0117188, 0.0078125, 0.0039062, 0.0117188, 0.0195312,\n         0.0312500, 0.0507812, 0.0625000, 0.0742188, 0.1015625, 0.1171875,\n         0.1328125, 0.1484375, 0.1796875, 0.1953125, 0.2109375, 0.2304688,\n         0.2656250, 0.2851562, 0.3007812, 0.3359375, 0.3554688, 0.3750000,\n         0.3906250, 0.4296875, 0.4453125, 0.4648438, 0.4960938, 0.5117188,\n         0.5273438, 0.5429688, 0.5742188, 0.5859375, 0.6015625, 0.6132812,\n         0.6367188, 0.6445312, 0.6562500, 0.6718750, 0.6796875, 0.6875000,\n         0.6914062, 0.7031250, 0.7031250, 0.7070312, 0.7109375, 0.7070312,\n         0.7070312, 0.7031250, 0.6992188, 0.6914062, 0.6875000, 0.6796875,\n         0.6640625, 0.6562500, 0.6445312, 0.6250000, 0.6132812, 0.6015625,\n         0.5859375, 0.5585938, 0.5429688, 0.5273438, 0.4960938, 0.4804688,\n         0.4648438, 0.4453125, 0.4101562, 0.3906250, 0.3750000, 0.3554688,\n         0.3203125, 0.3007812, 0.2851562, 0.2460938, 0.2304688, 0.2109375,\n         0.1953125, 0.1640625, 0.1484375, 0.1328125, 0.1171875, 0.0898438,\n         0.0742188, 0.0625000, 0.0507812, 0.0312500, 0.0195312, 0.0117188,\n         0.0000000, 0.0078125, 0.0117188, 0.0234375, 0.0234375, 0.0273438,\n         0.0273438, 0.0273438, 0.0273438, 0.0234375, 0.0234375, 0.0117188,\n         0.0078125, 0.0000000, 0.0117188, 0.0195312, 0.0312500, 0.0507812,\n         0.0625000, 0.0742188, 0.0898438, 0.1171875, 0.1328125, 0.1484375,\n         0.1640625, 0.1953125, 0.2109375, 0.2304688, 0.2460938, 0.2851562,\n         0.3007812, 0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125,\n         0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125,\n         0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125,\n         0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125,\n         0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125,\n         0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125,\n         0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125,\n         0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125, 0.3203125,\n         0.3203125, 0.0000000, 0.9960938, 0.9960938]),\narray([  0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375,\n         0.9609375, 0.9609375, 0.9609375, 0.9804688, 0.9882812, 0.9531250,\n         0.9335938, 0.9179688, 0.9023438, 0.8750000, 0.8671875, 0.8554688,\n         0.8437500, 0.8359375, 0.8359375, 0.8320312, 0.8359375, 0.8398438,\n         0.8437500, 0.8515625, 0.8710938, 0.8828125, 0.8945312, 0.9257812,\n         0.9414062, 0.9609375, 0.9804688, 0.9687500, 0.9492188, 0.9257812,\n         0.8789062, 0.8515625, 0.8281250, 0.8007812, 0.7500000, 0.7265625,\n         0.6992188, 0.6757812, 0.6250000, 0.6015625, 0.5781250, 0.5351562,\n         0.5156250, 0.4960938, 0.4765625, 0.4414062, 0.4257812, 0.4101562,\n         0.3984375, 0.3750000, 0.3671875, 0.3593750, 0.3476562, 0.3437500,\n         0.3437500, 0.3437500, 0.3437500, 0.3476562, 0.3515625, 0.3632812,\n         0.3710938, 0.3789062, 0.3867188, 0.4062500, 0.4179688, 0.4296875,\n         0.4414062, 0.4687500, 0.4843750, 0.4960938, 0.5234375, 0.5390625,\n         0.5507812, 0.5664062, 0.5937500, 0.6054688, 0.6171875, 0.6406250,\n         0.6484375, 0.6601562, 0.6679688, 0.6835938, 0.6875000, 0.6953125,\n         0.6992188, 0.7031250, 0.7031250, 0.7031250, 0.7031250, 0.6992188,\n         0.6953125, 0.6875000, 0.6757812, 0.6679688, 0.6601562, 0.6484375,\n         0.6289062, 0.6171875, 0.6015625, 0.5781250, 0.5625000, 0.5468750,\n         0.5312500, 0.5000000, 0.4843750, 0.4687500, 0.4375000, 0.4218750,\n         0.4062500, 0.3906250, 0.3593750, 0.3437500, 0.3320312, 0.3164062,\n         0.2890625, 0.2773438, 0.2656250, 0.2421875, 0.2343750, 0.2226562,\n         0.2148438, 0.1992188, 0.1953125, 0.1875000, 0.1796875, 0.1757812,\n         0.1718750, 0.1718750, 0.1679688, 0.1679688, 0.1679688, 0.1718750,\n         0.1718750, 0.1757812, 0.1796875, 0.1835938, 0.1875000, 0.1914062,\n         0.1992188, 0.2070312, 0.2109375, 0.2148438, 0.2226562, 0.2304688,\n         0.2343750, 0.2382812, 0.2460938, 0.2500000, 0.2500000, 0.2539062,\n         0.2578125, 0.2617188, 0.2617188, 0.2617188, 0.2617188, 0.2617188,\n         0.2617188, 0.2578125, 0.2578125, 0.2539062, 0.2500000, 0.2421875,\n         0.2382812, 0.2343750, 0.2265625, 0.2187500, 0.2148438, 0.2070312,\n         0.1953125, 0.1875000, 0.1796875, 0.1679688, 0.1601562, 0.1523438,\n         0.1445312, 0.1328125, 0.1250000, 0.1171875, 0.1093750, 0.0976562,\n         0.0898438, 0.0859375, 0.0742188, 0.0664062, 0.0625000, 0.0585938,\n         0.0468750, 0.0429688, 0.0507812, 0.0312500, 0.0273438, 0.0234375,\n         0.0234375, 0.0156250, 0.0156250, 0.0117188, 0.0078125, 0.0039062,\n         0.0039062, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.9960938, 0.9960938]),\narray([  0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625,\n         0.9140625, 0.9140625, 0.9140625, 0.8671875, 0.8203125, 0.7265625,\n         0.6835938, 0.6406250, 0.5976562, 0.5156250, 0.4765625, 0.4414062,\n         0.3710938, 0.3398438, 0.3085938, 0.2812500, 0.2304688, 0.2109375,\n         0.1914062, 0.1718750, 0.1445312, 0.1328125, 0.1250000, 0.1171875,\n         0.1132812, 0.1171875, 0.1210938, 0.1328125, 0.1445312, 0.1562500,\n         0.1914062, 0.2109375, 0.2304688, 0.2578125, 0.3085938, 0.3398438,\n         0.3710938, 0.4062500, 0.4765625, 0.5156250, 0.5546875, 0.6406250,\n         0.6835938, 0.7265625, 0.7734375, 0.8671875, 0.9140625, 0.9609375,\n         0.9804688, 0.8867188, 0.8359375, 0.7890625, 0.6914062, 0.6445312,\n         0.5976562, 0.5507812, 0.4609375, 0.4179688, 0.3750000, 0.2929688,\n         0.2539062, 0.2187500, 0.1835938, 0.1171875, 0.0859375, 0.0585938,\n         0.0351562, 0.0078125, 0.0273438, 0.0468750, 0.0742188, 0.0859375,\n         0.0937500, 0.0976562, 0.1054688, 0.1015625, 0.0976562, 0.0859375,\n         0.0742188, 0.0625000, 0.0468750, 0.0078125, 0.0078125, 0.0351562,\n         0.0585938, 0.1171875, 0.1484375, 0.1835938, 0.2539062, 0.2929688,\n         0.3320312, 0.3750000, 0.4609375, 0.5039062, 0.5507812, 0.5976562,\n         0.6914062, 0.7382812, 0.7890625, 0.8828125, 0.9335938, 0.9804688,\n         0.9609375, 0.8671875, 0.8203125, 0.7734375, 0.6835938, 0.6406250,\n         0.5976562, 0.5546875, 0.4765625, 0.4414062, 0.4062500, 0.3710938,\n         0.3085938, 0.2812500, 0.2578125, 0.2109375, 0.1914062, 0.1718750,\n         0.1562500, 0.1328125, 0.1250000, 0.1210938, 0.1132812, 0.1171875,\n         0.1210938, 0.1250000, 0.1445312, 0.1562500, 0.1718750, 0.1914062,\n         0.2304688, 0.2578125, 0.2812500, 0.3398438, 0.3710938, 0.4062500,\n         0.4414062, 0.5156250, 0.5546875, 0.5976562, 0.6835938, 0.7265625,\n         0.7734375, 0.8203125, 0.9140625, 0.9609375, 0.9804688, 0.9335938,\n         0.8359375, 0.7890625, 0.7382812, 0.6445312, 0.5976562, 0.5507812,\n         0.5039062, 0.4179688, 0.3750000, 0.3320312, 0.2929688, 0.2187500,\n         0.1835938, 0.1484375, 0.0859375, 0.0585938, 0.0351562, 0.0078125,\n         0.0273438, 0.0468750, 0.0625000, 0.0859375, 0.0937500, 0.0976562,\n         0.1015625, 0.1015625, 0.0976562, 0.0937500, 0.0859375, 0.0625000,\n         0.0468750, 0.0273438, 0.0078125, 0.0351562, 0.0585938, 0.0859375,\n         0.1484375, 0.1835938, 0.2187500, 0.2929688, 0.3320312, 0.3750000,\n         0.4179688, 0.5039062, 0.5507812, 0.5976562, 0.6445312, 0.7382812,\n         0.7890625, 0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375,\n         0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375,\n         0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375,\n         0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375,\n         0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375,\n         0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375,\n         0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375,\n         0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375, 0.8359375,\n         0.8359375, 0.0000000, 0.9960938, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 30 :: Ocean ###\n\ncolor_map_luts['idl30'] = \\\n   (\narray([  0.2109375, 0.2109375, 0.2304688, 0.2460938, 0.2656250, 0.2851562,\n         0.3007812, 0.3203125, 0.3203125, 0.3398438, 0.3554688, 0.3750000,\n         0.3906250, 0.4101562, 0.4296875, 0.4453125, 0.4648438, 0.4804688,\n         0.4960938, 0.5117188, 0.5273438, 0.5429688, 0.5585938, 0.5585938,\n         0.5742188, 0.5859375, 0.6015625, 0.6132812, 0.6250000, 0.6367188,\n         0.6445312, 0.6562500, 0.6640625, 0.6718750, 0.6796875, 0.6875000,\n         0.6914062, 0.6914062, 0.6992188, 0.7031250, 0.7031250, 0.7070312,\n         0.7070312, 0.7109375, 0.7070312, 0.7070312, 0.7031250, 0.7031250,\n         0.6992188, 0.6914062, 0.6875000, 0.6796875, 0.6796875, 0.6718750,\n         0.6640625, 0.6562500, 0.6445312, 0.6367188, 0.6250000, 0.6132812,\n         0.6015625, 0.5859375, 0.5742188, 0.5585938, 0.5429688, 0.5273438,\n         0.5117188, 0.5117188, 0.4960938, 0.4804688, 0.4648438, 0.4453125,\n         0.4296875, 0.4101562, 0.3906250, 0.3750000, 0.3554688, 0.3359375,\n         0.3203125, 0.3007812, 0.2851562, 0.2656250, 0.2656250, 0.2460938,\n         0.2304688, 0.2109375, 0.1953125, 0.1796875, 0.1640625, 0.1484375,\n         0.1328125, 0.1171875, 0.1015625, 0.0898438, 0.0742188, 0.0625000,\n         0.0507812, 0.0507812, 0.0507812, 0.0312500, 0.0195312, 0.0117188,\n         0.0039062, 0.0000000, 0.0078125, 0.0117188, 0.0195312, 0.0234375,\n         0.0234375, 0.0273438, 0.0273438, 0.0312500, 0.0312500, 0.0273438,\n         0.0273438, 0.0234375, 0.0234375, 0.0195312, 0.0117188, 0.0078125,\n         0.0000000, 0.0039062, 0.0117188, 0.0195312, 0.0312500, 0.0507812,\n         0.0507812, 0.0507812, 0.0625000, 0.0742188, 0.0898438, 0.1015625,\n         0.1171875, 0.1328125, 0.1484375, 0.1640625, 0.1796875, 0.1953125,\n         0.2109375, 0.2304688, 0.2460938, 0.2656250, 0.2656250, 0.2851562,\n         0.3007812, 0.3203125, 0.3398438, 0.3554688, 0.3750000, 0.3906250,\n         0.4101562, 0.4296875, 0.4453125, 0.4648438, 0.4804688, 0.4960938,\n         0.5117188, 0.5117188, 0.5273438, 0.5429688, 0.5585938, 0.5742188,\n         0.5859375, 0.6015625, 0.6132812, 0.6250000, 0.6367188, 0.6445312,\n         0.6562500, 0.6640625, 0.6718750, 0.6718750, 0.6796875, 0.6875000,\n         0.6914062, 0.6992188, 0.7031250, 0.7031250, 0.7070312, 0.7070312,\n         0.7109375, 0.7070312, 0.7070312, 0.7031250, 0.7031250, 0.6992188,\n         0.6992188, 0.6914062, 0.6875000, 0.6796875, 0.6718750, 0.6640625,\n         0.6562500, 0.6445312, 0.6367188, 0.6250000, 0.6132812, 0.6015625,\n         0.5859375, 0.5742188, 0.5585938, 0.5585938, 0.5429688, 0.5273438,\n         0.5117188, 0.4960938, 0.4804688, 0.4648438, 0.4453125, 0.4296875,\n         0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562,\n         0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562,\n         0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562,\n         0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562,\n         0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562,\n         0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562,\n         0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562, 0.4101562,\n         0.4101562, 0.0000000, 0.9960938, 0.9960938]),\narray([  0.2617188, 0.2617188, 0.2617188, 0.2617188, 0.2617188, 0.2617188,\n         0.2617188, 0.2578125, 0.2578125, 0.2578125, 0.2539062, 0.2500000,\n         0.2500000, 0.2460938, 0.2421875, 0.2382812, 0.2343750, 0.2304688,\n         0.2226562, 0.2187500, 0.2148438, 0.2109375, 0.2070312, 0.2070312,\n         0.2031250, 0.1992188, 0.1914062, 0.1875000, 0.1835938, 0.1835938,\n         0.1796875, 0.1757812, 0.1718750, 0.1718750, 0.1718750, 0.1679688,\n         0.1679688, 0.1679688, 0.1679688, 0.1679688, 0.1718750, 0.1718750,\n         0.1757812, 0.1796875, 0.1835938, 0.1875000, 0.1953125, 0.1992188,\n         0.2070312, 0.2148438, 0.2226562, 0.2343750, 0.2343750, 0.2421875,\n         0.2539062, 0.2656250, 0.2773438, 0.2890625, 0.3046875, 0.3164062,\n         0.3320312, 0.3437500, 0.3593750, 0.3750000, 0.3906250, 0.4062500,\n         0.4218750, 0.4218750, 0.4375000, 0.4531250, 0.4687500, 0.4843750,\n         0.5000000, 0.5156250, 0.5312500, 0.5468750, 0.5625000, 0.5781250,\n         0.5898438, 0.6015625, 0.6171875, 0.6289062, 0.6289062, 0.6406250,\n         0.6484375, 0.6601562, 0.6679688, 0.6757812, 0.6835938, 0.6875000,\n         0.6953125, 0.6992188, 0.7031250, 0.7031250, 0.7031250, 0.7031250,\n         0.7031250, 0.7031250, 0.6992188, 0.6992188, 0.6953125, 0.6875000,\n         0.6835938, 0.6757812, 0.6679688, 0.6601562, 0.6484375, 0.6406250,\n         0.6289062, 0.6171875, 0.6054688, 0.5937500, 0.5937500, 0.5781250,\n         0.5664062, 0.5507812, 0.5390625, 0.5234375, 0.5117188, 0.4960938,\n         0.4843750, 0.4687500, 0.4570312, 0.4414062, 0.4296875, 0.4179688,\n         0.4062500, 0.4062500, 0.3945312, 0.3867188, 0.3789062, 0.3710938,\n         0.3632812, 0.3554688, 0.3515625, 0.3476562, 0.3437500, 0.3437500,\n         0.3437500, 0.3437500, 0.3437500, 0.3476562, 0.3476562, 0.3554688,\n         0.3593750, 0.3671875, 0.3750000, 0.3867188, 0.3984375, 0.4101562,\n         0.4257812, 0.4414062, 0.4570312, 0.4765625, 0.4960938, 0.5156250,\n         0.5351562, 0.5351562, 0.5546875, 0.5781250, 0.6015625, 0.6250000,\n         0.6484375, 0.6757812, 0.6992188, 0.7265625, 0.7500000, 0.7773438,\n         0.8007812, 0.8281250, 0.8515625, 0.8515625, 0.8789062, 0.9023438,\n         0.9257812, 0.9492188, 0.9687500, 0.9921875, 0.9804688, 0.9609375,\n         0.9414062, 0.9257812, 0.9101562, 0.8945312, 0.8828125, 0.8710938,\n         0.8710938, 0.8593750, 0.8515625, 0.8437500, 0.8398438, 0.8359375,\n         0.8320312, 0.8320312, 0.8359375, 0.8359375, 0.8437500, 0.8476562,\n         0.8554688, 0.8671875, 0.8750000, 0.8750000, 0.8906250, 0.9023438,\n         0.9179688, 0.9335938, 0.9531250, 0.9687500, 0.9882812, 0.9804688,\n         0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375,\n         0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375,\n         0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375,\n         0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375,\n         0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375,\n         0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375,\n         0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375,\n         0.9609375, 0.0000000, 0.9960938, 0.9960938]),\narray([  0.5507812, 0.5507812, 0.5976562, 0.6445312, 0.6914062, 0.7382812,\n         0.7890625, 0.8359375, 0.8359375, 0.8867188, 0.9335938, 0.9804688,\n         0.9609375, 0.9140625, 0.8671875, 0.8203125, 0.7734375, 0.7265625,\n         0.6835938, 0.6406250, 0.5976562, 0.5546875, 0.5156250, 0.5156250,\n         0.4765625, 0.4414062, 0.4062500, 0.3710938, 0.3398438, 0.3085938,\n         0.2812500, 0.2578125, 0.2304688, 0.2109375, 0.1914062, 0.1718750,\n         0.1562500, 0.1562500, 0.1445312, 0.1328125, 0.1250000, 0.1210938,\n         0.1171875, 0.1132812, 0.1171875, 0.1210938, 0.1250000, 0.1328125,\n         0.1445312, 0.1562500, 0.1718750, 0.1914062, 0.1914062, 0.2109375,\n         0.2304688, 0.2578125, 0.2812500, 0.3085938, 0.3398438, 0.3710938,\n         0.4062500, 0.4414062, 0.4765625, 0.5156250, 0.5546875, 0.5976562,\n         0.6406250, 0.6406250, 0.6835938, 0.7265625, 0.7734375, 0.8203125,\n         0.8671875, 0.9140625, 0.9609375, 0.9804688, 0.9335938, 0.8828125,\n         0.8359375, 0.7890625, 0.7382812, 0.6914062, 0.6914062, 0.6445312,\n         0.5976562, 0.5507812, 0.5039062, 0.4609375, 0.4179688, 0.3750000,\n         0.3320312, 0.2929688, 0.2539062, 0.2187500, 0.1835938, 0.1484375,\n         0.1171875, 0.1171875, 0.0859375, 0.0585938, 0.0351562, 0.0078125,\n         0.0078125, 0.0273438, 0.0468750, 0.0625000, 0.0742188, 0.0859375,\n         0.0937500, 0.0976562, 0.1015625, 0.1054688, 0.1054688, 0.1015625,\n         0.0976562, 0.0937500, 0.0859375, 0.0742188, 0.0625000, 0.0468750,\n         0.0273438, 0.0078125, 0.0078125, 0.0351562, 0.0585938, 0.0859375,\n         0.1171875, 0.1171875, 0.1484375, 0.1835938, 0.2187500, 0.2539062,\n         0.2929688, 0.3320312, 0.3750000, 0.4179688, 0.4609375, 0.5039062,\n         0.5507812, 0.5976562, 0.6445312, 0.6914062, 0.6914062, 0.7382812,\n         0.7890625, 0.8359375, 0.8867188, 0.9335938, 0.9804688, 0.9609375,\n         0.9140625, 0.8671875, 0.8203125, 0.7734375, 0.7265625, 0.6835938,\n         0.6406250, 0.6406250, 0.5976562, 0.5546875, 0.5156250, 0.4765625,\n         0.4414062, 0.4062500, 0.3710938, 0.3398438, 0.3085938, 0.2812500,\n         0.2578125, 0.2304688, 0.2109375, 0.2109375, 0.1914062, 0.1718750,\n         0.1562500, 0.1445312, 0.1328125, 0.1250000, 0.1210938, 0.1171875,\n         0.1132812, 0.1171875, 0.1210938, 0.1250000, 0.1328125, 0.1445312,\n         0.1445312, 0.1562500, 0.1718750, 0.1914062, 0.2109375, 0.2304688,\n         0.2578125, 0.2812500, 0.3085938, 0.3398438, 0.3710938, 0.4062500,\n         0.4414062, 0.4765625, 0.5156250, 0.5156250, 0.5546875, 0.5976562,\n         0.6406250, 0.6835938, 0.7265625, 0.7734375, 0.8203125, 0.8671875,\n         0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625,\n         0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625,\n         0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625,\n         0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625,\n         0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625,\n         0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625,\n         0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625, 0.9140625,\n         0.9140625, 0.0000000, 0.9960938, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 31 :: Peppermint ###\n\ncolor_map_luts['idl31'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.3125000, 0.3125000,\n         0.3125000, 0.3125000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.9375000, 0.9375000, 0.9375000, 0.9375000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.3125000, 0.3125000, 0.3125000, 0.3125000,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.9375000, 0.9375000,\n         0.9375000, 0.9375000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.9375000, 0.9375000, 0.9375000, 0.9375000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.3125000, 0.3125000,\n         0.3125000, 0.3125000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.9375000, 0.9375000, 0.9375000, 0.9375000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.3125000, 0.3125000, 0.3125000, 0.3125000,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.9375000, 0.9375000,\n         0.9375000, 0.9375000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.9375000, 0.9375000, 0.9375000, 0.9375000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.3125000, 0.3125000,\n         0.3125000, 0.3125000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.9375000, 0.9375000, 0.9375000, 0.9375000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.3125000, 0.3125000, 0.3125000, 0.3125000,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.9375000, 0.9375000,\n         0.9375000, 0.9375000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.9375000, 0.9375000, 0.9375000, 0.9375000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.3125000, 0.3125000,\n         0.3125000, 0.3125000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.9375000, 0.9375000, 0.9375000, 0.9375000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.3125000, 0.3125000, 0.3125000, 0.3125000,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.9375000, 0.9375000,\n         0.9375000, 0.9375000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.9375000, 0.9375000, 0.9375000, 0.9375000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.3125000, 0.3125000,\n         0.3164062, 0.3125000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.9375000, 0.9375000, 0.9375000, 0.9375000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.3125000, 0.3125000, 0.3125000, 0.3125000,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.9375000, 0.9375000,\n         0.9375000, 0.9375000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.9375000, 0.9375000, 0.9375000, 0.9375000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.3125000, 0.3125000,\n         0.3125000, 0.3125000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.9375000, 0.9375000, 0.9375000, 0.9375000]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0625000, 0.0625000,\n         0.0625000, 0.0625000, 0.0625000, 0.0625000, 0.0625000, 0.0625000,\n         0.0625000, 0.0625000, 0.0625000, 0.0625000, 0.0625000, 0.0625000,\n         0.0625000, 0.0625000, 0.1250000, 0.1250000, 0.1250000, 0.1250000,\n         0.1250000, 0.1250000, 0.1250000, 0.1250000, 0.1250000, 0.1250000,\n         0.1250000, 0.1250000, 0.1250000, 0.1250000, 0.1250000, 0.1250000,\n         0.1875000, 0.1875000, 0.1875000, 0.1875000, 0.1875000, 0.1875000,\n         0.1875000, 0.1875000, 0.1875000, 0.1875000, 0.1875000, 0.1875000,\n         0.1875000, 0.1875000, 0.1875000, 0.1875000, 0.2500000, 0.2500000,\n         0.2500000, 0.2500000, 0.2500000, 0.2500000, 0.2500000, 0.2500000,\n         0.2500000, 0.2500000, 0.2500000, 0.2500000, 0.2500000, 0.2500000,\n         0.2500000, 0.2500000, 0.3125000, 0.3125000, 0.3125000, 0.3125000,\n         0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.3125000,\n         0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.3125000, 0.3125000,\n         0.3750000, 0.3750000, 0.3750000, 0.3750000, 0.3750000, 0.3750000,\n         0.3750000, 0.3750000, 0.3750000, 0.3750000, 0.3750000, 0.3750000,\n         0.3750000, 0.3750000, 0.3750000, 0.3750000, 0.4375000, 0.4375000,\n         0.4375000, 0.4375000, 0.4375000, 0.4375000, 0.4375000, 0.4375000,\n         0.4375000, 0.4375000, 0.4375000, 0.4375000, 0.4375000, 0.4375000,\n         0.4375000, 0.4375000, 0.5000000, 0.5000000, 0.5000000, 0.5000000,\n         0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000,\n         0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000, 0.5000000,\n         0.5625000, 0.5625000, 0.5625000, 0.5625000, 0.5625000, 0.5625000,\n         0.5625000, 0.5625000, 0.5625000, 0.5625000, 0.5625000, 0.5625000,\n         0.5625000, 0.5625000, 0.5625000, 0.5625000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.6875000, 0.6875000, 0.6875000, 0.6875000,\n         0.6875000, 0.6875000, 0.6875000, 0.6875000, 0.6875000, 0.6875000,\n         0.6875000, 0.6875000, 0.6875000, 0.6875000, 0.6875000, 0.6875000,\n         0.7500000, 0.7500000, 0.7500000, 0.7500000, 0.7500000, 0.7500000,\n         0.7539062, 0.7500000, 0.7500000, 0.7500000, 0.7500000, 0.7500000,\n         0.7500000, 0.7500000, 0.7500000, 0.7500000, 0.8125000, 0.8125000,\n         0.8125000, 0.8125000, 0.8125000, 0.8125000, 0.8125000, 0.8125000,\n         0.8125000, 0.8125000, 0.8125000, 0.8125000, 0.8125000, 0.8125000,\n         0.8125000, 0.8125000, 0.8750000, 0.8750000, 0.8750000, 0.8750000,\n         0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000,\n         0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000, 0.8750000,\n         0.9375000, 0.9375000, 0.9375000, 0.9375000, 0.9375000, 0.9375000,\n         0.9375000, 0.9375000, 0.9375000, 0.9375000, 0.9375000, 0.9375000,\n         0.9375000, 0.9375000, 0.9375000, 0.9375000]),\narray([  0.3125000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.9375000, 0.0000000, 0.3125000,\n         0.6250000, 0.9375000, 0.0000000, 0.3125000, 0.6250000, 0.9375000,\n         0.0000000, 0.3125000, 0.6250000, 0.6250000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 32 :: Plasma ###\n\ncolor_map_luts['idl32'] = \\\n   (\narray([  0.0078125, 0.0078125, 0.0117188, 0.0156250, 0.0195312, 0.0234375,\n         0.0273438, 0.0312500, 0.0351562, 0.0507812, 0.0429688, 0.0507812,\n         0.0546875, 0.0585938, 0.0625000, 0.0664062, 0.0703125, 0.0742188,\n         0.0820312, 0.0859375, 0.0898438, 0.0937500, 0.0976562, 0.1015625,\n         0.1054688, 0.1093750, 0.1171875, 0.1210938, 0.1250000, 0.1289062,\n         0.1328125, 0.1367188, 0.1406250, 0.1445312, 0.1484375, 0.1523438,\n         0.1523438, 0.1562500, 0.1601562, 0.1640625, 0.1679688, 0.1718750,\n         0.1757812, 0.1796875, 0.1796875, 0.1835938, 0.1875000, 0.1914062,\n         0.1953125, 0.1992188, 0.2031250, 0.2070312, 0.2109375, 0.2148438,\n         0.2187500, 0.2226562, 0.2265625, 0.2304688, 0.2343750, 0.2382812,\n         0.2421875, 0.2460938, 0.2539062, 0.2578125, 0.2617188, 0.2656250,\n         0.2734375, 0.2773438, 0.2812500, 0.2890625, 0.2929688, 0.3007812,\n         0.3046875, 0.3085938, 0.3164062, 0.3203125, 0.3281250, 0.3320312,\n         0.3359375, 0.3437500, 0.3476562, 0.3515625, 0.3593750, 0.3632812,\n         0.3671875, 0.3710938, 0.3789062, 0.3828125, 0.3867188, 0.3906250,\n         0.3945312, 0.3984375, 0.4023438, 0.4023438, 0.4062500, 0.4101562,\n         0.4140625, 0.4179688, 0.4179688, 0.4218750, 0.4218750, 0.4257812,\n         0.4296875, 0.4296875, 0.4335938, 0.4335938, 0.4375000, 0.4414062,\n         0.4414062, 0.4453125, 0.4453125, 0.4492188, 0.4531250, 0.4570312,\n         0.4570312, 0.4609375, 0.4648438, 0.4687500, 0.4726562, 0.4765625,\n         0.4804688, 0.4843750, 0.4882812, 0.4960938, 0.5000000, 0.5039062,\n         0.5117188, 0.5156250, 0.5234375, 0.5273438, 0.5351562, 0.5390625,\n         0.5468750, 0.5546875, 0.5585938, 0.5664062, 0.5742188, 0.5820312,\n         0.5859375, 0.5937500, 0.6015625, 0.6054688, 0.6132812, 0.6171875,\n         0.6250000, 0.6289062, 0.6367188, 0.6406250, 0.6484375, 0.6523438,\n         0.6562500, 0.6601562, 0.6640625, 0.6679688, 0.6718750, 0.6757812,\n         0.6796875, 0.6796875, 0.6835938, 0.6835938, 0.6875000, 0.6875000,\n         0.6914062, 0.6914062, 0.6953125, 0.6953125, 0.6953125, 0.6953125,\n         0.6992188, 0.6992188, 0.6992188, 0.7031250, 0.7031250, 0.7031250,\n         0.7070312, 0.7070312, 0.7109375, 0.7109375, 0.7148438, 0.7187500,\n         0.7187500, 0.7226562, 0.7265625, 0.7304688, 0.7343750, 0.7421875,\n         0.7460938, 0.7500000, 0.7578125, 0.7617188, 0.7695312, 0.7734375,\n         0.7812500, 0.7890625, 0.7968750, 0.8046875, 0.8125000, 0.8203125,\n         0.8281250, 0.8359375, 0.8437500, 0.8515625, 0.8593750, 0.8671875,\n         0.8750000, 0.8789062, 0.8867188, 0.8945312, 0.9023438, 0.9062500,\n         0.9140625, 0.9218750, 0.9257812, 0.9296875, 0.9335938, 0.9414062,\n         0.9453125, 0.9453125, 0.9492188, 0.9531250, 0.9570312, 0.9570312,\n         0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9609375, 0.9648438,\n         0.9648438, 0.9648438, 0.9648438, 0.9648438, 0.9648438, 0.9648438,\n         0.9648438, 0.9648438, 0.9648438, 0.9648438, 0.9648438, 0.9648438,\n         0.9687500, 0.9687500, 0.9687500, 0.9726562, 0.9765625, 0.9804688,\n         0.9804688, 0.9843750, 0.9921875, 0.9960938, 0.9921875, 0.9843750,\n         0.9804688, 0.9726562, 0.9648438, 0.9648438]),\narray([  0.0742188, 0.0742188, 0.0781250, 0.0859375, 0.0976562, 0.1132812,\n         0.1289062, 0.1484375, 0.1718750, 0.1992188, 0.2265625, 0.2539062,\n         0.2851562, 0.3164062, 0.3476562, 0.3828125, 0.4140625, 0.4492188,\n         0.4843750, 0.5156250, 0.5468750, 0.5781250, 0.6054688, 0.6328125,\n         0.6601562, 0.6835938, 0.7031250, 0.7187500, 0.7343750, 0.7460938,\n         0.7539062, 0.7578125, 0.7617188, 0.7578125, 0.7539062, 0.7460938,\n         0.7343750, 0.7187500, 0.7031250, 0.6835938, 0.6601562, 0.6328125,\n         0.6054688, 0.5781250, 0.5468750, 0.5156250, 0.4843750, 0.4492188,\n         0.4179688, 0.3828125, 0.3476562, 0.3164062, 0.2851562, 0.2539062,\n         0.2265625, 0.1992188, 0.1718750, 0.1484375, 0.1289062, 0.1132812,\n         0.0976562, 0.0859375, 0.0781250, 0.0742188, 0.0742188, 0.0742188,\n         0.0781250, 0.0859375, 0.0976562, 0.1132812, 0.1289062, 0.1484375,\n         0.1718750, 0.1992188, 0.2265625, 0.2539062, 0.2851562, 0.3164062,\n         0.3476562, 0.3828125, 0.4179688, 0.4492188, 0.4843750, 0.5156250,\n         0.5468750, 0.5781250, 0.6054688, 0.6328125, 0.6601562, 0.6835938,\n         0.7031250, 0.7187500, 0.7343750, 0.7460938, 0.7539062, 0.7578125,\n         0.7617188, 0.7578125, 0.7539062, 0.7460938, 0.7343750, 0.7187500,\n         0.7031250, 0.6835938, 0.6601562, 0.6328125, 0.6054688, 0.5781250,\n         0.5468750, 0.5156250, 0.4843750, 0.4492188, 0.4140625, 0.3828125,\n         0.3476562, 0.3164062, 0.2851562, 0.2539062, 0.2265625, 0.1992188,\n         0.1718750, 0.1484375, 0.1289062, 0.1132812, 0.0976562, 0.0859375,\n         0.0781250, 0.0742188, 0.0742188, 0.0742188, 0.0781250, 0.0859375,\n         0.0976562, 0.1132812, 0.1289062, 0.1484375, 0.1718750, 0.1992188,\n         0.2265625, 0.2539062, 0.2851562, 0.3164062, 0.3476562, 0.3828125,\n         0.4179688, 0.4492188, 0.4843750, 0.5156250, 0.5468750, 0.5781250,\n         0.6054688, 0.6328125, 0.6601562, 0.6835938, 0.7031250, 0.7187500,\n         0.7343750, 0.7460938, 0.7539062, 0.7578125, 0.7617188, 0.7578125,\n         0.7539062, 0.7460938, 0.7343750, 0.7187500, 0.7031250, 0.6835938,\n         0.6601562, 0.6328125, 0.6054688, 0.5781250, 0.5468750, 0.5156250,\n         0.4843750, 0.4492188, 0.4140625, 0.3828125, 0.3476562, 0.3164062,\n         0.2851562, 0.2539062, 0.2265625, 0.1992188, 0.1718750, 0.1484375,\n         0.1289062, 0.1132812, 0.0976562, 0.0859375, 0.0781250, 0.0742188,\n         0.0742188, 0.0742188, 0.0781250, 0.0859375, 0.0976562, 0.1132812,\n         0.1289062, 0.1484375, 0.1718750, 0.1992188, 0.2265625, 0.2539062,\n         0.2851562, 0.3164062, 0.3476562, 0.3828125, 0.4179688, 0.4492188,\n         0.4843750, 0.5156250, 0.5468750, 0.5781250, 0.6054688, 0.6328125,\n         0.6601562, 0.6835938, 0.7031250, 0.7187500, 0.7343750, 0.7460938,\n         0.7539062, 0.7578125, 0.7617188, 0.7578125, 0.7539062, 0.7460938,\n         0.7343750, 0.7187500, 0.7031250, 0.6835938, 0.6601562, 0.6328125,\n         0.6054688, 0.5781250, 0.5468750, 0.5156250, 0.4843750, 0.4492188,\n         0.4140625, 0.3828125, 0.3476562, 0.3164062, 0.2851562, 0.2539062,\n         0.2265625, 0.1992188, 0.1718750, 0.1484375, 0.1289062, 0.1132812,\n         0.0976562, 0.0859375, 0.0781250, 0.0781250]),\narray([  0.0273438, 0.0273438, 0.0429688, 0.0585938, 0.0742188, 0.0859375,\n         0.1015625, 0.1171875, 0.1328125, 0.1523438, 0.1679688, 0.1835938,\n         0.1992188, 0.2148438, 0.2304688, 0.2460938, 0.2617188, 0.2773438,\n         0.2929688, 0.3046875, 0.3203125, 0.3359375, 0.3515625, 0.3632812,\n         0.3789062, 0.3906250, 0.4023438, 0.4179688, 0.4296875, 0.4414062,\n         0.4531250, 0.4648438, 0.4765625, 0.4882812, 0.5000000, 0.5078125,\n         0.5195312, 0.5312500, 0.5429688, 0.5507812, 0.5625000, 0.5742188,\n         0.5859375, 0.5976562, 0.6093750, 0.6210938, 0.6328125, 0.6445312,\n         0.6601562, 0.6718750, 0.6875000, 0.7031250, 0.7187500, 0.7343750,\n         0.7500000, 0.7656250, 0.7851562, 0.8007812, 0.8203125, 0.8398438,\n         0.8593750, 0.8789062, 0.8984375, 0.9179688, 0.9414062, 0.9609375,\n         0.9804688, 0.9882812, 0.9687500, 0.9492188, 0.9257812, 0.9062500,\n         0.8867188, 0.8671875, 0.8476562, 0.8281250, 0.8085938, 0.7890625,\n         0.7734375, 0.7578125, 0.7421875, 0.7265625, 0.7109375, 0.6992188,\n         0.6875000, 0.6718750, 0.6640625, 0.6523438, 0.6445312, 0.6328125,\n         0.6250000, 0.6171875, 0.6132812, 0.6054688, 0.6015625, 0.5937500,\n         0.5898438, 0.5859375, 0.5781250, 0.5742188, 0.5703125, 0.5625000,\n         0.5585938, 0.5507812, 0.5468750, 0.5390625, 0.5312500, 0.5234375,\n         0.5156250, 0.5039062, 0.4921875, 0.4804688, 0.4687500, 0.4531250,\n         0.4414062, 0.4218750, 0.4062500, 0.3867188, 0.3671875, 0.3476562,\n         0.3242188, 0.3046875, 0.2812500, 0.2539062, 0.2304688, 0.2070312,\n         0.1796875, 0.1523438, 0.1250000, 0.0976562, 0.0703125, 0.0429688,\n         0.0195312, 0.9882812, 0.9609375, 0.9335938, 0.9101562, 0.8867188,\n         0.8632812, 0.8398438, 0.8203125, 0.8007812, 0.7812500, 0.7617188,\n         0.7460938, 0.7343750, 0.7187500, 0.7070312, 0.6953125, 0.6875000,\n         0.6796875, 0.6718750, 0.6679688, 0.6640625, 0.6601562, 0.6601562,\n         0.6562500, 0.6562500, 0.6562500, 0.6562500, 0.6601562, 0.6601562,\n         0.6601562, 0.6601562, 0.6640625, 0.6640625, 0.6640625, 0.6601562,\n         0.6601562, 0.6562500, 0.6523438, 0.6484375, 0.6406250, 0.6328125,\n         0.6250000, 0.6132812, 0.6015625, 0.5859375, 0.5703125, 0.5507812,\n         0.5312500, 0.5117188, 0.4882812, 0.4648438, 0.4375000, 0.4140625,\n         0.3828125, 0.3554688, 0.3242188, 0.2929688, 0.2617188, 0.2265625,\n         0.1953125, 0.1601562, 0.1289062, 0.0937500, 0.0625000, 0.0312500,\n         0.9960938, 0.9648438, 0.9335938, 0.9062500, 0.8789062, 0.8554688,\n         0.8320312, 0.8085938, 0.7890625, 0.7695312, 0.7539062, 0.7382812,\n         0.7265625, 0.7148438, 0.7070312, 0.6992188, 0.6953125, 0.6914062,\n         0.6914062, 0.6914062, 0.6953125, 0.6992188, 0.7031250, 0.7070312,\n         0.7148438, 0.7187500, 0.7265625, 0.7343750, 0.7421875, 0.7500000,\n         0.7539062, 0.7617188, 0.7656250, 0.7695312, 0.7734375, 0.7734375,\n         0.7734375, 0.7734375, 0.7695312, 0.7617188, 0.7539062, 0.7460938,\n         0.7343750, 0.7187500, 0.7031250, 0.6835938, 0.6601562, 0.6367188,\n         0.6093750, 0.5820312, 0.5546875, 0.5195312, 0.4882812, 0.4531250,\n         0.4179688, 0.3789062, 0.3398438, 0.3398438]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 33 :: Blue-Red ###\n\ncolor_map_luts['idl33'] = \\\n   (\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0117188, 0.0273438, 0.0429688,\n         0.0585938, 0.0742188, 0.0898438, 0.1054688, 0.1210938, 0.1367188,\n         0.1523438, 0.1679688, 0.1835938, 0.1992188, 0.2148438, 0.2304688,\n         0.2460938, 0.2617188, 0.2773438, 0.2929688, 0.3085938, 0.3242188,\n         0.3398438, 0.3554688, 0.3710938, 0.3867188, 0.4023438, 0.4179688,\n         0.4335938, 0.4492188, 0.4648438, 0.4804688, 0.4960938, 0.5117188,\n         0.5273438, 0.5429688, 0.5585938, 0.5742188, 0.5898438, 0.6054688,\n         0.6210938, 0.6367188, 0.6523438, 0.6679688, 0.6835938, 0.6992188,\n         0.7148438, 0.7304688, 0.7460938, 0.7617188, 0.7773438, 0.7929688,\n         0.8085938, 0.8242188, 0.8398438, 0.8554688, 0.8710938, 0.8867188,\n         0.9023438, 0.9179688, 0.9335938, 0.9492188, 0.9648438, 0.9804688,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9765625,\n         0.9609375, 0.9414062, 0.9257812, 0.9101562, 0.8906250, 0.8750000,\n         0.8554688, 0.8398438, 0.8242188, 0.8046875, 0.7890625, 0.7695312,\n         0.7539062, 0.7382812, 0.7187500, 0.7031250, 0.6835938, 0.6679688,\n         0.6523438, 0.6328125, 0.6171875, 0.5976562, 0.5820312, 0.5664062,\n         0.5468750, 0.5312500, 0.5117188, 0.5117188]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0117188, 0.0273438,\n         0.0429688, 0.0585938, 0.0742188, 0.0898438, 0.1054688, 0.1210938,\n         0.1367188, 0.1523438, 0.1679688, 0.1835938, 0.1992188, 0.2148438,\n         0.2304688, 0.2460938, 0.2617188, 0.2773438, 0.2929688, 0.3085938,\n         0.3242188, 0.3398438, 0.3554688, 0.3710938, 0.3867188, 0.4023438,\n         0.4179688, 0.4335938, 0.4492188, 0.4648438, 0.4804688, 0.4960938,\n         0.5117188, 0.5273438, 0.5429688, 0.5585938, 0.5742188, 0.5898438,\n         0.6054688, 0.6210938, 0.6367188, 0.6523438, 0.6679688, 0.6835938,\n         0.6992188, 0.7148438, 0.7304688, 0.7460938, 0.7617188, 0.7773438,\n         0.7929688, 0.8085938, 0.8242188, 0.8398438, 0.8554688, 0.8710938,\n         0.8867188, 0.9023438, 0.9179688, 0.9335938, 0.9492188, 0.9648438,\n         0.9804688, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9804688, 0.9648438, 0.9492188, 0.9335938, 0.9179688,\n         0.9023438, 0.8867188, 0.8710938, 0.8554688, 0.8398438, 0.8242188,\n         0.8085938, 0.7929688, 0.7773438, 0.7617188, 0.7460938, 0.7304688,\n         0.7148438, 0.6992188, 0.6835938, 0.6679688, 0.6523438, 0.6367188,\n         0.6210938, 0.6054688, 0.5898438, 0.5742188, 0.5585938, 0.5429688,\n         0.5273438, 0.5117188, 0.4960938, 0.4804688, 0.4648438, 0.4492188,\n         0.4335938, 0.4179688, 0.4023438, 0.3867188, 0.3710938, 0.3554688,\n         0.3398438, 0.3242188, 0.3085938, 0.2929688, 0.2773438, 0.2617188,\n         0.2460938, 0.2304688, 0.2148438, 0.1992188, 0.1835938, 0.1679688,\n         0.1523438, 0.1367188, 0.1210938, 0.1054688, 0.0898438, 0.0742188,\n         0.0585938, 0.0429688, 0.0273438, 0.0117188, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.5117188, 0.5117188, 0.5273438, 0.5429688, 0.5585938, 0.5742188,\n         0.5898438, 0.6054688, 0.6210938, 0.6367188, 0.6523438, 0.6679688,\n         0.6835938, 0.6992188, 0.7148438, 0.7304688, 0.7460938, 0.7617188,\n         0.7773438, 0.7929688, 0.8085938, 0.8242188, 0.8398438, 0.8554688,\n         0.8710938, 0.8867188, 0.9023438, 0.9179688, 0.9335938, 0.9492188,\n         0.9648438, 0.9804688, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9804688, 0.9648438, 0.9492188,\n         0.9335938, 0.9179688, 0.9023438, 0.8867188, 0.8710938, 0.8554688,\n         0.8398438, 0.8242188, 0.8085938, 0.7929688, 0.7773438, 0.7617188,\n         0.7460938, 0.7304688, 0.7148438, 0.6992188, 0.6835938, 0.6679688,\n         0.6523438, 0.6367188, 0.6210938, 0.6054688, 0.5898438, 0.5742188,\n         0.5585938, 0.5429688, 0.5273438, 0.5117188, 0.4960938, 0.4804688,\n         0.4648438, 0.4492188, 0.4335938, 0.4179688, 0.4023438, 0.3867188,\n         0.3710938, 0.3554688, 0.3398438, 0.3242188, 0.3085938, 0.2929688,\n         0.2773438, 0.2617188, 0.2460938, 0.2304688, 0.2148438, 0.1992188,\n         0.1835938, 0.1679688, 0.1523438, 0.1367188, 0.1210938, 0.1054688,\n         0.0898438, 0.0742188, 0.0585938, 0.0429688, 0.0273438, 0.0117188,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 34 :: Rainbow ###\n\ncolor_map_luts['idl34'] = \\\n   (\narray([  0.4843750, 0.4843750, 0.4687500, 0.4492188, 0.4335938, 0.4140625,\n         0.3984375, 0.3789062, 0.3632812, 0.3437500, 0.3281250, 0.3085938,\n         0.2929688, 0.2734375, 0.2578125, 0.2382812, 0.2226562, 0.2031250,\n         0.1875000, 0.1679688, 0.1523438, 0.1328125, 0.1171875, 0.0976562,\n         0.0820312, 0.0625000, 0.0468750, 0.0273438, 0.0117188, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0039062, 0.0195312,\n         0.0507812, 0.0546875, 0.0742188, 0.0898438, 0.1093750, 0.1250000,\n         0.1445312, 0.1601562, 0.1796875, 0.1953125, 0.2148438, 0.2304688,\n         0.2500000, 0.2656250, 0.2851562, 0.3007812, 0.3203125, 0.3359375,\n         0.3554688, 0.3710938, 0.3906250, 0.4062500, 0.4257812, 0.4414062,\n         0.4609375, 0.4804688, 0.4960938, 0.5156250, 0.5312500, 0.5507812,\n         0.5664062, 0.5859375, 0.6015625, 0.6210938, 0.6367188, 0.6562500,\n         0.6718750, 0.6914062, 0.7070312, 0.7265625, 0.7421875, 0.7617188,\n         0.7773438, 0.7968750, 0.8125000, 0.8320312, 0.8476562, 0.8671875,\n         0.8828125, 0.9023438, 0.9179688, 0.9375000, 0.9531250, 0.9726562,\n         0.9882812, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0078125,\n         0.0234375, 0.0429688, 0.0585938, 0.0781250, 0.0937500, 0.1132812,\n         0.1289062, 0.1484375, 0.1640625, 0.1835938, 0.1992188, 0.2187500,\n         0.2343750, 0.2539062, 0.2695312, 0.2890625, 0.3046875, 0.3242188,\n         0.3398438, 0.3593750, 0.3750000, 0.3945312, 0.4101562, 0.4296875,\n         0.4453125, 0.4648438, 0.4804688, 0.5000000, 0.5156250, 0.5351562,\n         0.5507812, 0.5703125, 0.5859375, 0.6054688, 0.6210938, 0.6406250,\n         0.6562500, 0.6757812, 0.6914062, 0.7109375, 0.7265625, 0.7460938,\n         0.7617188, 0.7812500, 0.7968750, 0.8164062, 0.8320312, 0.8515625,\n         0.8671875, 0.8867188, 0.9023438, 0.9218750, 0.9414062, 0.9570312,\n         0.9765625, 0.9921875, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9843750, 0.9687500, 0.9492188, 0.9335938, 0.9140625,\n         0.8984375, 0.8789062, 0.8632812, 0.8437500, 0.8281250, 0.8085938,\n         0.7929688, 0.7734375, 0.7578125, 0.7382812, 0.7226562, 0.7031250,\n         0.6875000, 0.6679688, 0.6523438, 0.6328125, 0.6171875, 0.5976562,\n         0.5820312, 0.5625000, 0.5468750, 0.5273438, 0.5117188, 0.4921875,\n         0.4765625, 0.4570312, 0.4414062, 0.4218750, 0.4062500, 0.3867188,\n         0.3710938, 0.3515625, 0.3359375, 0.3164062, 0.3007812, 0.2812500,\n         0.2656250, 0.2460938, 0.2304688, 0.2109375, 0.1953125, 0.1757812,\n         0.1601562, 0.1406250, 0.1250000, 0.1054688, 0.0898438, 0.0703125,\n         0.0546875, 0.0351562, 0.0195312, 0.0195312]),\narray([  0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9804688, 0.9648438, 0.9453125, 0.9296875,\n         0.9101562, 0.8945312, 0.8750000, 0.8593750, 0.8398438, 0.8242188,\n         0.8046875, 0.7890625, 0.7695312, 0.7539062, 0.7343750, 0.7187500,\n         0.6992188, 0.6835938, 0.6640625, 0.6484375, 0.6289062, 0.6132812,\n         0.5937500, 0.5781250, 0.5585938, 0.5429688, 0.5234375, 0.5078125,\n         0.4882812, 0.4726562, 0.4531250, 0.4375000, 0.4179688, 0.4023438,\n         0.3828125, 0.3671875, 0.3476562, 0.3320312, 0.3125000, 0.2968750,\n         0.2773438, 0.2617188, 0.2421875, 0.2265625, 0.2070312, 0.1914062,\n         0.1718750, 0.1562500, 0.1367188, 0.1210938, 0.1015625, 0.0859375,\n         0.0664062, 0.0507812, 0.0312500, 0.0156250, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 35 :: Blue Waves ###\n\ncolor_map_luts['idl35'] = \\\n   (\narray([  0.3203125, 0.3203125, 0.3007812, 0.2851562, 0.2656250, 0.2460938,\n         0.2304688, 0.2109375, 0.1953125, 0.1796875, 0.1640625, 0.1484375,\n         0.1328125, 0.1171875, 0.1015625, 0.0898438, 0.0742188, 0.0625000,\n         0.0507812, 0.0507812, 0.0312500, 0.0195312, 0.0117188, 0.0039062,\n         0.0000000, 0.0078125, 0.0117188, 0.0195312, 0.0234375, 0.0234375,\n         0.0273438, 0.0273438, 0.0312500, 0.0273438, 0.0273438, 0.0234375,\n         0.0234375, 0.0195312, 0.0117188, 0.0078125, 0.0000000, 0.0039062,\n         0.0117188, 0.0195312, 0.0312500, 0.0507812, 0.0507812, 0.0625000,\n         0.0742188, 0.0898438, 0.1015625, 0.1171875, 0.1328125, 0.1484375,\n         0.1640625, 0.1796875, 0.1953125, 0.2109375, 0.2304688, 0.2460938,\n         0.2656250, 0.2851562, 0.3007812, 0.3203125, 0.3398438, 0.3554688,\n         0.3750000, 0.3906250, 0.4101562, 0.4296875, 0.4453125, 0.4648438,\n         0.4804688, 0.4960938, 0.5117188, 0.5273438, 0.5429688, 0.5585938,\n         0.5742188, 0.5859375, 0.6015625, 0.6132812, 0.6250000, 0.6367188,\n         0.6445312, 0.6562500, 0.6640625, 0.6718750, 0.6796875, 0.6875000,\n         0.6914062, 0.6992188, 0.7031250, 0.7031250, 0.7070312, 0.7070312,\n         0.7109375, 0.7070312, 0.7070312, 0.7031250, 0.7031250, 0.6992188,\n         0.6914062, 0.6875000, 0.6796875, 0.6718750, 0.6640625, 0.6562500,\n         0.6445312, 0.6367188, 0.6250000, 0.6132812, 0.6015625, 0.5859375,\n         0.5742188, 0.5585938, 0.5429688, 0.5273438, 0.5117188, 0.4960938,\n         0.4804688, 0.4648438, 0.4453125, 0.4296875, 0.4101562, 0.3906250,\n         0.3750000, 0.3554688, 0.3359375, 0.3203125, 0.3007812, 0.2851562,\n         0.2656250, 0.2460938, 0.2304688, 0.2109375, 0.1953125, 0.1796875,\n         0.1640625, 0.1484375, 0.1328125, 0.1171875, 0.1015625, 0.0898438,\n         0.0742188, 0.0625000, 0.0507812, 0.0507812, 0.0312500, 0.0195312,\n         0.0117188, 0.0039062, 0.0000000, 0.0078125, 0.0117188, 0.0195312,\n         0.0234375, 0.0234375, 0.0273438, 0.0273438, 0.0312500, 0.0273438,\n         0.0273438, 0.0234375, 0.0234375, 0.0195312, 0.0117188, 0.0078125,\n         0.0000000, 0.0039062, 0.0117188, 0.0195312, 0.0312500, 0.0507812,\n         0.0507812, 0.0625000, 0.0742188, 0.0898438, 0.1015625, 0.1171875,\n         0.1328125, 0.1484375, 0.1640625, 0.1796875, 0.1953125, 0.2109375,\n         0.2304688, 0.2460938, 0.2656250, 0.2851562, 0.3007812, 0.3203125,\n         0.3398438, 0.3554688, 0.3750000, 0.3906250, 0.4101562, 0.4296875,\n         0.4453125, 0.4648438, 0.4804688, 0.4960938, 0.5117188, 0.5273438,\n         0.5429688, 0.5585938, 0.5742188, 0.5859375, 0.6015625, 0.6132812,\n         0.6250000, 0.6367188, 0.6445312, 0.6562500, 0.6640625, 0.6718750,\n         0.6796875, 0.6875000, 0.6914062, 0.6992188, 0.7031250, 0.7031250,\n         0.7070312, 0.7070312, 0.7109375, 0.7070312, 0.7070312, 0.7031250,\n         0.7031250, 0.6992188, 0.6914062, 0.6875000, 0.6796875, 0.6718750,\n         0.6640625, 0.6562500, 0.6445312, 0.6367188, 0.6250000, 0.6132812,\n         0.6015625, 0.5859375, 0.5742188, 0.5585938, 0.5429688, 0.5273438,\n         0.5117188, 0.4960938, 0.4804688, 0.4648438, 0.4453125, 0.4296875,\n         0.4101562, 0.3906250, 0.3750000, 0.3750000]),\narray([  0.0000000, 0.0000000, 0.0039062, 0.0039062, 0.0078125, 0.0078125,\n         0.0117188, 0.0156250, 0.0156250, 0.0195312, 0.0234375, 0.0234375,\n         0.0273438, 0.0312500, 0.0351562, 0.0507812, 0.0429688, 0.0468750,\n         0.0507812, 0.0585938, 0.0625000, 0.0664062, 0.0742188, 0.0781250,\n         0.0859375, 0.0898438, 0.0976562, 0.1054688, 0.1093750, 0.1171875,\n         0.1250000, 0.1328125, 0.1367188, 0.1445312, 0.1523438, 0.1601562,\n         0.1679688, 0.1757812, 0.1796875, 0.1875000, 0.1953125, 0.1992188,\n         0.2070312, 0.2148438, 0.2187500, 0.2265625, 0.2304688, 0.2343750,\n         0.2382812, 0.2421875, 0.2460938, 0.2500000, 0.2539062, 0.2578125,\n         0.2578125, 0.2617188, 0.2617188, 0.2617188, 0.2617188, 0.2617188,\n         0.2617188, 0.2617188, 0.2617188, 0.2578125, 0.2578125, 0.2539062,\n         0.2500000, 0.2500000, 0.2460938, 0.2421875, 0.2382812, 0.2343750,\n         0.2304688, 0.2226562, 0.2187500, 0.2148438, 0.2109375, 0.2070312,\n         0.2031250, 0.1992188, 0.1914062, 0.1875000, 0.1835938, 0.1835938,\n         0.1796875, 0.1757812, 0.1718750, 0.1718750, 0.1718750, 0.1679688,\n         0.1679688, 0.1679688, 0.1679688, 0.1718750, 0.1718750, 0.1757812,\n         0.1796875, 0.1835938, 0.1875000, 0.1953125, 0.1992188, 0.2070312,\n         0.2148438, 0.2226562, 0.2343750, 0.2421875, 0.2539062, 0.2656250,\n         0.2773438, 0.2890625, 0.3046875, 0.3164062, 0.3320312, 0.3437500,\n         0.3593750, 0.3750000, 0.3906250, 0.4062500, 0.4218750, 0.4375000,\n         0.4531250, 0.4687500, 0.4843750, 0.5000000, 0.5156250, 0.5312500,\n         0.5468750, 0.5625000, 0.5781250, 0.5898438, 0.6015625, 0.6171875,\n         0.6289062, 0.6406250, 0.6484375, 0.6601562, 0.6679688, 0.6757812,\n         0.6835938, 0.6875000, 0.6953125, 0.6992188, 0.7031250, 0.7031250,\n         0.7031250, 0.7031250, 0.7031250, 0.6992188, 0.6992188, 0.6953125,\n         0.6875000, 0.6835938, 0.6757812, 0.6679688, 0.6601562, 0.6484375,\n         0.6406250, 0.6289062, 0.6171875, 0.6054688, 0.5937500, 0.5781250,\n         0.5664062, 0.5507812, 0.5390625, 0.5234375, 0.5117188, 0.4960938,\n         0.4843750, 0.4687500, 0.4570312, 0.4414062, 0.4296875, 0.4179688,\n         0.4062500, 0.3945312, 0.3867188, 0.3789062, 0.3710938, 0.3632812,\n         0.3554688, 0.3515625, 0.3476562, 0.3437500, 0.3437500, 0.3437500,\n         0.3437500, 0.3437500, 0.3476562, 0.3554688, 0.3593750, 0.3671875,\n         0.3750000, 0.3867188, 0.3984375, 0.4101562, 0.4257812, 0.4414062,\n         0.4570312, 0.4765625, 0.4960938, 0.5156250, 0.5351562, 0.5546875,\n         0.5781250, 0.6015625, 0.6250000, 0.6484375, 0.6757812, 0.6992188,\n         0.7265625, 0.7500000, 0.7773438, 0.8007812, 0.8281250, 0.8515625,\n         0.8789062, 0.9023438, 0.9257812, 0.9492188, 0.9687500, 0.9921875,\n         0.9804688, 0.9609375, 0.9414062, 0.9257812, 0.9101562, 0.8945312,\n         0.8828125, 0.8710938, 0.8593750, 0.8515625, 0.8437500, 0.8398438,\n         0.8359375, 0.8320312, 0.8320312, 0.8359375, 0.8359375, 0.8437500,\n         0.8476562, 0.8554688, 0.8671875, 0.8750000, 0.8906250, 0.9023438,\n         0.9179688, 0.9335938, 0.9531250, 0.9687500, 0.9882812, 0.9804688,\n         0.9609375, 0.9375000, 0.9140625, 0.9140625]),\narray([  0.8359375, 0.8359375, 0.7890625, 0.7382812, 0.6914062, 0.6445312,\n         0.5976562, 0.5507812, 0.5039062, 0.4609375, 0.4179688, 0.3750000,\n         0.3320312, 0.2929688, 0.2539062, 0.2187500, 0.1835938, 0.1484375,\n         0.1171875, 0.0859375, 0.0585938, 0.0351562, 0.0078125, 0.0078125,\n         0.0273438, 0.0468750, 0.0625000, 0.0742188, 0.0859375, 0.0937500,\n         0.0976562, 0.1015625, 0.1054688, 0.1015625, 0.0976562, 0.0937500,\n         0.0859375, 0.0742188, 0.0625000, 0.0468750, 0.0273438, 0.0078125,\n         0.0078125, 0.0351562, 0.0585938, 0.0859375, 0.1171875, 0.1484375,\n         0.1835938, 0.2187500, 0.2539062, 0.2929688, 0.3320312, 0.3750000,\n         0.4179688, 0.4609375, 0.5039062, 0.5507812, 0.5976562, 0.6445312,\n         0.6914062, 0.7382812, 0.7890625, 0.8359375, 0.8867188, 0.9335938,\n         0.9804688, 0.9609375, 0.9140625, 0.8671875, 0.8203125, 0.7734375,\n         0.7265625, 0.6835938, 0.6406250, 0.5976562, 0.5546875, 0.5156250,\n         0.4765625, 0.4414062, 0.4062500, 0.3710938, 0.3398438, 0.3085938,\n         0.2812500, 0.2578125, 0.2304688, 0.2109375, 0.1914062, 0.1718750,\n         0.1562500, 0.1445312, 0.1328125, 0.1250000, 0.1210938, 0.1171875,\n         0.1132812, 0.1171875, 0.1210938, 0.1250000, 0.1328125, 0.1445312,\n         0.1562500, 0.1718750, 0.1914062, 0.2109375, 0.2304688, 0.2578125,\n         0.2812500, 0.3085938, 0.3398438, 0.3710938, 0.4062500, 0.4414062,\n         0.4765625, 0.5156250, 0.5546875, 0.5976562, 0.6406250, 0.6835938,\n         0.7265625, 0.7734375, 0.8203125, 0.8671875, 0.9140625, 0.9609375,\n         0.9804688, 0.9335938, 0.8828125, 0.8359375, 0.7890625, 0.7382812,\n         0.6914062, 0.6445312, 0.5976562, 0.5507812, 0.5039062, 0.4609375,\n         0.4179688, 0.3750000, 0.3320312, 0.2929688, 0.2539062, 0.2187500,\n         0.1835938, 0.1484375, 0.1171875, 0.0859375, 0.0585938, 0.0351562,\n         0.0078125, 0.0078125, 0.0273438, 0.0468750, 0.0625000, 0.0742188,\n         0.0859375, 0.0937500, 0.0976562, 0.1015625, 0.1054688, 0.1015625,\n         0.0976562, 0.0937500, 0.0859375, 0.0742188, 0.0625000, 0.0468750,\n         0.0273438, 0.0078125, 0.0078125, 0.0351562, 0.0585938, 0.0859375,\n         0.1171875, 0.1484375, 0.1835938, 0.2187500, 0.2539062, 0.2929688,\n         0.3320312, 0.3750000, 0.4179688, 0.4609375, 0.5039062, 0.5507812,\n         0.5976562, 0.6445312, 0.6914062, 0.7382812, 0.7890625, 0.8359375,\n         0.8867188, 0.9335938, 0.9804688, 0.9609375, 0.9140625, 0.8671875,\n         0.8203125, 0.7734375, 0.7265625, 0.6835938, 0.6406250, 0.5976562,\n         0.5546875, 0.5156250, 0.4765625, 0.4414062, 0.4062500, 0.3710938,\n         0.3398438, 0.3085938, 0.2812500, 0.2578125, 0.2304688, 0.2109375,\n         0.1914062, 0.1718750, 0.1562500, 0.1445312, 0.1328125, 0.1250000,\n         0.1210938, 0.1171875, 0.1132812, 0.1171875, 0.1210938, 0.1250000,\n         0.1328125, 0.1445312, 0.1562500, 0.1718750, 0.1914062, 0.2109375,\n         0.2304688, 0.2578125, 0.2812500, 0.3085938, 0.3398438, 0.3710938,\n         0.4062500, 0.4414062, 0.4765625, 0.5156250, 0.5546875, 0.5976562,\n         0.6406250, 0.6835938, 0.7265625, 0.7734375, 0.8203125, 0.8671875,\n         0.9140625, 0.9609375, 0.9804688, 0.9804688]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 36 :: Volcano ###\n\ncolor_map_luts['idl36'] = \\\n   (\narray([  0.2500000, 0.2500000, 0.2343750, 0.2226562, 0.2109375, 0.1992188,\n         0.1875000, 0.1757812, 0.1640625, 0.1562500, 0.1445312, 0.1367188,\n         0.1250000, 0.1171875, 0.1093750, 0.1015625, 0.0937500, 0.0859375,\n         0.0781250, 0.0742188, 0.0664062, 0.0625000, 0.0546875, 0.0507812,\n         0.0468750, 0.0429688, 0.0507812, 0.0351562, 0.0351562, 0.0312500,\n         0.0273438, 0.0273438, 0.0273438, 0.0273438, 0.0273438, 0.0273438,\n         0.0273438, 0.0273438, 0.0273438, 0.0312500, 0.0312500, 0.0351562,\n         0.0507812, 0.0429688, 0.0468750, 0.0507812, 0.0546875, 0.0585938,\n         0.0664062, 0.0703125, 0.0781250, 0.0859375, 0.0898438, 0.0976562,\n         0.1054688, 0.1171875, 0.1250000, 0.1328125, 0.1445312, 0.1523438,\n         0.1640625, 0.1718750, 0.1835938, 0.1953125, 0.2070312, 0.2187500,\n         0.2304688, 0.2460938, 0.2578125, 0.2734375, 0.2851562, 0.3007812,\n         0.3125000, 0.3281250, 0.3437500, 0.3593750, 0.3750000, 0.3906250,\n         0.4062500, 0.4257812, 0.4414062, 0.4570312, 0.4765625, 0.4921875,\n         0.5117188, 0.5312500, 0.5468750, 0.5664062, 0.5859375, 0.6054688,\n         0.6250000, 0.6445312, 0.6640625, 0.6835938, 0.7031250, 0.7265625,\n         0.7460938, 0.7656250, 0.7851562, 0.8085938, 0.8281250, 0.8515625,\n         0.8710938, 0.8945312, 0.9140625, 0.9375000, 0.9609375, 0.9804688,\n         0.9882812, 0.9648438, 0.9414062, 0.9218750, 0.8984375, 0.8750000,\n         0.8515625, 0.8320312, 0.8085938, 0.7851562, 0.7617188, 0.7382812,\n         0.7148438, 0.6953125, 0.6718750, 0.6484375, 0.6250000, 0.6015625,\n         0.5781250, 0.5585938, 0.5351562, 0.5117188, 0.4882812, 0.4687500,\n         0.4453125, 0.4218750, 0.4023438, 0.3789062, 0.3593750, 0.3359375,\n         0.3125000, 0.2929688, 0.2734375, 0.2500000, 0.2304688, 0.2070312,\n         0.1875000, 0.1679688, 0.1484375, 0.1289062, 0.1093750, 0.0898438,\n         0.0703125, 0.0507812, 0.0312500, 0.0117188, 0.0039062, 0.0195312,\n         0.0507812, 0.0585938, 0.0742188, 0.0898438, 0.1093750, 0.1250000,\n         0.1406250, 0.1562500, 0.1718750, 0.1875000, 0.2031250, 0.2187500,\n         0.2343750, 0.2460938, 0.2617188, 0.2734375, 0.2851562, 0.3007812,\n         0.3125000, 0.3242188, 0.3359375, 0.3476562, 0.3593750, 0.3671875,\n         0.3789062, 0.3906250, 0.3984375, 0.4062500, 0.4179688, 0.4257812,\n         0.4335938, 0.4414062, 0.4453125, 0.4531250, 0.4609375, 0.4648438,\n         0.4726562, 0.4765625, 0.4804688, 0.4843750, 0.4882812, 0.4921875,\n         0.4960938, 0.5000000, 0.5000000, 0.5039062, 0.5039062, 0.5039062,\n         0.5039062, 0.5039062, 0.5039062, 0.5039062, 0.5039062, 0.5039062,\n         0.5000000, 0.4960938, 0.4960938, 0.4921875, 0.4882812, 0.4843750,\n         0.4804688, 0.4765625, 0.4687500, 0.4648438, 0.4570312, 0.4531250,\n         0.4453125, 0.4375000, 0.4296875, 0.4218750, 0.4140625, 0.4023438,\n         0.3945312, 0.3867188, 0.3750000, 0.3632812, 0.3554688, 0.3437500,\n         0.3320312, 0.3203125, 0.3085938, 0.2929688, 0.2812500, 0.2695312,\n         0.2539062, 0.2421875, 0.2265625, 0.2109375, 0.1953125, 0.1835938,\n         0.1679688, 0.1523438, 0.1328125, 0.1171875, 0.1015625, 0.0859375,\n         0.0664062, 0.0507812, 0.0312500, 0.0312500]),\narray([  0.1367188, 0.1367188, 0.1562500, 0.1718750, 0.1914062, 0.2109375,\n         0.2265625, 0.2460938, 0.2617188, 0.2812500, 0.2968750, 0.3125000,\n         0.3281250, 0.3476562, 0.3632812, 0.3750000, 0.3906250, 0.4062500,\n         0.4218750, 0.4335938, 0.4492188, 0.4609375, 0.4726562, 0.4843750,\n         0.4960938, 0.5078125, 0.5195312, 0.5273438, 0.5351562, 0.5468750,\n         0.5546875, 0.5625000, 0.5664062, 0.5742188, 0.5781250, 0.5859375,\n         0.5898438, 0.5937500, 0.5937500, 0.5976562, 0.5976562, 0.5976562,\n         0.5976562, 0.5976562, 0.5976562, 0.5976562, 0.5937500, 0.5898438,\n         0.5859375, 0.5820312, 0.5781250, 0.5703125, 0.5664062, 0.5585938,\n         0.5507812, 0.5429688, 0.5351562, 0.5234375, 0.5156250, 0.5039062,\n         0.4921875, 0.4804688, 0.4687500, 0.4570312, 0.4414062, 0.4296875,\n         0.4140625, 0.4023438, 0.3867188, 0.3710938, 0.3554688, 0.3398438,\n         0.3242188, 0.3085938, 0.2890625, 0.2734375, 0.2578125, 0.2382812,\n         0.2226562, 0.2031250, 0.1835938, 0.1679688, 0.1484375, 0.1289062,\n         0.1132812, 0.0937500, 0.0742188, 0.0585938, 0.0507812, 0.0195312,\n         0.0039062, 0.0117188, 0.0273438, 0.0468750, 0.0625000, 0.0781250,\n         0.0976562, 0.1132812, 0.1289062, 0.1445312, 0.1601562, 0.1757812,\n         0.1914062, 0.2070312, 0.2187500, 0.2343750, 0.2460938, 0.2578125,\n         0.2695312, 0.2812500, 0.2929688, 0.3046875, 0.3125000, 0.3242188,\n         0.3320312, 0.3398438, 0.3476562, 0.3554688, 0.3593750, 0.3671875,\n         0.3710938, 0.3750000, 0.3789062, 0.3828125, 0.3867188, 0.3867188,\n         0.3867188, 0.3867188, 0.3867188, 0.3867188, 0.3867188, 0.3828125,\n         0.3789062, 0.3789062, 0.3710938, 0.3671875, 0.3632812, 0.3554688,\n         0.3476562, 0.3437500, 0.3359375, 0.3242188, 0.3164062, 0.3046875,\n         0.2968750, 0.2851562, 0.2734375, 0.2617188, 0.2500000, 0.2382812,\n         0.2226562, 0.2109375, 0.1953125, 0.1796875, 0.1640625, 0.1484375,\n         0.1328125, 0.1171875, 0.1015625, 0.0859375, 0.0664062, 0.0507812,\n         0.0351562, 0.0156250, 0.0000000, 0.0156250, 0.0351562, 0.0507812,\n         0.0703125, 0.0898438, 0.1054688, 0.1250000, 0.1445312, 0.1601562,\n         0.1796875, 0.1992188, 0.2148438, 0.2343750, 0.2500000, 0.2695312,\n         0.2851562, 0.3007812, 0.3203125, 0.3359375, 0.3515625, 0.3671875,\n         0.3828125, 0.3984375, 0.4101562, 0.4257812, 0.4375000, 0.4531250,\n         0.4648438, 0.4765625, 0.4882812, 0.5000000, 0.5117188, 0.5195312,\n         0.5312500, 0.5390625, 0.5468750, 0.5546875, 0.5625000, 0.5703125,\n         0.5742188, 0.5820312, 0.5859375, 0.5898438, 0.5937500, 0.5937500,\n         0.5976562, 0.5976562, 0.5976562, 0.5976562, 0.5976562, 0.5976562,\n         0.5937500, 0.5937500, 0.5898438, 0.5859375, 0.5820312, 0.5742188,\n         0.5703125, 0.5625000, 0.5546875, 0.5468750, 0.5390625, 0.5312500,\n         0.5195312, 0.5117188, 0.5000000, 0.4882812, 0.4765625, 0.4648438,\n         0.4531250, 0.4375000, 0.4257812, 0.4101562, 0.3945312, 0.3828125,\n         0.3671875, 0.3515625, 0.3359375, 0.3164062, 0.3007812, 0.2851562,\n         0.2656250, 0.2500000, 0.2343750, 0.2148438, 0.1953125, 0.1796875,\n         0.1601562, 0.1445312, 0.1250000, 0.1250000]),\narray([  0.4531250, 0.4531250, 0.4101562, 0.3671875, 0.3281250, 0.2890625,\n         0.2500000, 0.2148438, 0.1796875, 0.1484375, 0.1171875, 0.0937500,\n         0.0703125, 0.0468750, 0.0312500, 0.0195312, 0.0078125, 0.0000000,\n         0.0000000, 0.0000000, 0.0039062, 0.0117188, 0.0234375, 0.0507812,\n         0.0546875, 0.0781250, 0.1015625, 0.1289062, 0.1601562, 0.1953125,\n         0.2304688, 0.2656250, 0.3046875, 0.3437500, 0.3867188, 0.4257812,\n         0.4687500, 0.5117188, 0.5546875, 0.5976562, 0.6367188, 0.6796875,\n         0.7187500, 0.7539062, 0.7890625, 0.8242188, 0.8515625, 0.8828125,\n         0.9062500, 0.9296875, 0.9492188, 0.9648438, 0.9765625, 0.9843750,\n         0.9882812, 0.9882812, 0.9882812, 0.9804688, 0.9726562, 0.9609375,\n         0.9414062, 0.9218750, 0.8984375, 0.8750000, 0.8437500, 0.8125000,\n         0.7812500, 0.7421875, 0.7070312, 0.6679688, 0.6250000, 0.5859375,\n         0.5429688, 0.5000000, 0.4570312, 0.4140625, 0.3750000, 0.3320312,\n         0.2929688, 0.2539062, 0.2187500, 0.1835938, 0.1523438, 0.1210938,\n         0.0937500, 0.0703125, 0.0507812, 0.0351562, 0.0195312, 0.0078125,\n         0.0000000, 0.0000000, 0.0000000, 0.0039062, 0.0117188, 0.0195312,\n         0.0351562, 0.0546875, 0.0742188, 0.0976562, 0.1250000, 0.1562500,\n         0.1875000, 0.2226562, 0.2617188, 0.2968750, 0.3398438, 0.3789062,\n         0.4218750, 0.4648438, 0.5078125, 0.5468750, 0.5898438, 0.6328125,\n         0.6718750, 0.7109375, 0.7500000, 0.7851562, 0.8164062, 0.8476562,\n         0.8789062, 0.9023438, 0.9257812, 0.9453125, 0.9609375, 0.9726562,\n         0.9843750, 0.9882812, 0.9921875, 0.9882812, 0.9843750, 0.9726562,\n         0.9609375, 0.9453125, 0.9257812, 0.9023438, 0.8789062, 0.8476562,\n         0.8164062, 0.7851562, 0.7500000, 0.7109375, 0.6718750, 0.6328125,\n         0.5898438, 0.5468750, 0.5078125, 0.4648438, 0.4218750, 0.3789062,\n         0.3398438, 0.2968750, 0.2617188, 0.2226562, 0.1875000, 0.1562500,\n         0.1250000, 0.0976562, 0.0742188, 0.0546875, 0.0351562, 0.0195312,\n         0.0117188, 0.0039062, 0.0000000, 0.0000000, 0.0000000, 0.0078125,\n         0.0195312, 0.0351562, 0.0507812, 0.0703125, 0.0937500, 0.1210938,\n         0.1523438, 0.1835938, 0.2187500, 0.2539062, 0.2929688, 0.3320312,\n         0.3750000, 0.4140625, 0.4570312, 0.5000000, 0.5429688, 0.5859375,\n         0.6250000, 0.6679688, 0.7070312, 0.7421875, 0.7812500, 0.8125000,\n         0.8437500, 0.8750000, 0.8984375, 0.9218750, 0.9414062, 0.9609375,\n         0.9726562, 0.9804688, 0.9882812, 0.9882812, 0.9882812, 0.9843750,\n         0.9765625, 0.9648438, 0.9492188, 0.9296875, 0.9062500, 0.8828125,\n         0.8515625, 0.8242188, 0.7890625, 0.7539062, 0.7187500, 0.6796875,\n         0.6367188, 0.5976562, 0.5546875, 0.5117188, 0.4687500, 0.4257812,\n         0.3867188, 0.3437500, 0.3046875, 0.2656250, 0.2304688, 0.1953125,\n         0.1601562, 0.1289062, 0.1015625, 0.0781250, 0.0546875, 0.0507812,\n         0.0234375, 0.0117188, 0.0039062, 0.0000000, 0.0000000, 0.0000000,\n         0.0078125, 0.0195312, 0.0312500, 0.0468750, 0.0703125, 0.0937500,\n         0.1171875, 0.1484375, 0.1796875, 0.2148438, 0.2500000, 0.2890625,\n         0.3281250, 0.3671875, 0.4101562, 0.4101562]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 37 :: Waves ###\n\ncolor_map_luts['idl37'] = \\\n   (\narray([  0.4843750, 0.4843750, 0.4726562, 0.4609375, 0.4492188, 0.4375000,\n         0.4257812, 0.4140625, 0.4023438, 0.3906250, 0.3789062, 0.3671875,\n         0.3554688, 0.3437500, 0.3320312, 0.3203125, 0.3085938, 0.2968750,\n         0.2851562, 0.2734375, 0.2656250, 0.2539062, 0.2421875, 0.2343750,\n         0.2226562, 0.2109375, 0.2031250, 0.1914062, 0.1835938, 0.1757812,\n         0.1640625, 0.1562500, 0.1484375, 0.1406250, 0.1289062, 0.1210938,\n         0.1132812, 0.1054688, 0.0976562, 0.0937500, 0.0859375, 0.0781250,\n         0.0742188, 0.0664062, 0.0585938, 0.0546875, 0.0507812, 0.0429688,\n         0.0507812, 0.0351562, 0.0312500, 0.0273438, 0.0234375, 0.0195312,\n         0.0156250, 0.0156250, 0.0117188, 0.0078125, 0.0078125, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0078125, 0.0078125,\n         0.0117188, 0.0156250, 0.0156250, 0.0195312, 0.0234375, 0.0273438,\n         0.0312500, 0.0351562, 0.0507812, 0.0429688, 0.0507812, 0.0546875,\n         0.0585938, 0.0664062, 0.0742188, 0.0781250, 0.0859375, 0.0937500,\n         0.0976562, 0.1054688, 0.1132812, 0.1210938, 0.1289062, 0.1406250,\n         0.1484375, 0.1562500, 0.1640625, 0.1757812, 0.1835938, 0.1914062,\n         0.2031250, 0.2109375, 0.2226562, 0.2343750, 0.2421875, 0.2539062,\n         0.2656250, 0.2734375, 0.2851562, 0.2968750, 0.3085938, 0.3203125,\n         0.3320312, 0.3437500, 0.3554688, 0.3671875, 0.3789062, 0.3906250,\n         0.4023438, 0.4140625, 0.4257812, 0.4375000, 0.4492188, 0.4609375,\n         0.4726562, 0.4843750, 0.5000000, 0.5117188, 0.5234375, 0.5351562,\n         0.5468750, 0.5585938, 0.5703125, 0.5820312, 0.5937500, 0.6054688,\n         0.6171875, 0.6289062, 0.6406250, 0.6523438, 0.6640625, 0.6757812,\n         0.6875000, 0.6992188, 0.7109375, 0.7226562, 0.7304688, 0.7421875,\n         0.7539062, 0.7617188, 0.7734375, 0.7851562, 0.7929688, 0.8046875,\n         0.8125000, 0.8203125, 0.8320312, 0.8398438, 0.8476562, 0.8554688,\n         0.8671875, 0.8750000, 0.8828125, 0.8906250, 0.8984375, 0.9023438,\n         0.9101562, 0.9179688, 0.9218750, 0.9296875, 0.9375000, 0.9414062,\n         0.9453125, 0.9531250, 0.9570312, 0.9609375, 0.9648438, 0.9687500,\n         0.9726562, 0.9765625, 0.9804688, 0.9804688, 0.9843750, 0.9882812,\n         0.9882812, 0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9921875,\n         0.9960938, 0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9921875,\n         0.9882812, 0.9882812, 0.9843750, 0.9804688, 0.9804688, 0.9765625,\n         0.9726562, 0.9687500, 0.9648438, 0.9609375, 0.9570312, 0.9531250,\n         0.9453125, 0.9414062, 0.9375000, 0.9296875, 0.9218750, 0.9179688,\n         0.9101562, 0.9023438, 0.8984375, 0.8906250, 0.8828125, 0.8750000,\n         0.8671875, 0.8554688, 0.8476562, 0.8398438, 0.8320312, 0.8203125,\n         0.8125000, 0.8046875, 0.7929688, 0.7851562, 0.7734375, 0.7617188,\n         0.7539062, 0.7421875, 0.7304688, 0.7226562, 0.7109375, 0.6992188,\n         0.6875000, 0.6757812, 0.6640625, 0.6523438, 0.6406250, 0.6289062,\n         0.6171875, 0.6054688, 0.5937500, 0.5820312, 0.5703125, 0.5585938,\n         0.5468750, 0.5351562, 0.5234375, 0.5234375]),\narray([  0.4726562, 0.4726562, 0.5507812, 0.6054688, 0.6250000, 0.6054688,\n         0.5507812, 0.4726562, 0.3789062, 0.2812500, 0.2031250, 0.1484375,\n         0.1328125, 0.1484375, 0.2031250, 0.2812500, 0.3750000, 0.4726562,\n         0.5507812, 0.6054688, 0.6250000, 0.6054688, 0.5507812, 0.4726562,\n         0.3789062, 0.2812500, 0.2031250, 0.1484375, 0.1328125, 0.1484375,\n         0.2031250, 0.2812500, 0.3750000, 0.4726562, 0.5507812, 0.6054688,\n         0.6250000, 0.6054688, 0.5507812, 0.4726562, 0.3789062, 0.2812500,\n         0.2031250, 0.1484375, 0.1328125, 0.1484375, 0.2031250, 0.2812500,\n         0.3750000, 0.4726562, 0.5507812, 0.6054688, 0.6250000, 0.6054688,\n         0.5507812, 0.4726562, 0.3789062, 0.2812500, 0.2031250, 0.1484375,\n         0.1328125, 0.1484375, 0.2031250, 0.2812500, 0.3750000, 0.4726562,\n         0.5507812, 0.6054688, 0.6250000, 0.6054688, 0.5507812, 0.4726562,\n         0.3789062, 0.2812500, 0.2031250, 0.1484375, 0.1328125, 0.1484375,\n         0.2031250, 0.2812500, 0.3750000, 0.4726562, 0.5507812, 0.6054688,\n         0.6250000, 0.6054688, 0.5507812, 0.4726562, 0.3789062, 0.2812500,\n         0.2031250, 0.1484375, 0.1328125, 0.1484375, 0.2031250, 0.2812500,\n         0.3750000, 0.4726562, 0.5507812, 0.6054688, 0.6250000, 0.6054688,\n         0.5507812, 0.4726562, 0.3789062, 0.2812500, 0.2031250, 0.1484375,\n         0.1328125, 0.1484375, 0.2031250, 0.2812500, 0.3750000, 0.4726562,\n         0.5507812, 0.6054688, 0.6250000, 0.6054688, 0.5507812, 0.4726562,\n         0.3750000, 0.2812500, 0.2031250, 0.1484375, 0.1328125, 0.1484375,\n         0.2031250, 0.2812500, 0.3789062, 0.4726562, 0.5507812, 0.6054688,\n         0.6250000, 0.6054688, 0.5507812, 0.4726562, 0.3750000, 0.2812500,\n         0.2031250, 0.1484375, 0.1328125, 0.1484375, 0.2031250, 0.2812500,\n         0.3789062, 0.4726562, 0.5507812, 0.6054688, 0.6250000, 0.6054688,\n         0.5507812, 0.4726562, 0.3750000, 0.2812500, 0.2031250, 0.1484375,\n         0.1328125, 0.1484375, 0.2031250, 0.2812500, 0.3789062, 0.4726562,\n         0.5507812, 0.6054688, 0.6250000, 0.6054688, 0.5507812, 0.4726562,\n         0.3750000, 0.2812500, 0.2031250, 0.1484375, 0.1328125, 0.1484375,\n         0.2031250, 0.2812500, 0.3789062, 0.4726562, 0.5507812, 0.6054688,\n         0.6250000, 0.6054688, 0.5507812, 0.4726562, 0.3750000, 0.2812500,\n         0.2031250, 0.1484375, 0.1328125, 0.1484375, 0.2031250, 0.2812500,\n         0.3789062, 0.4726562, 0.5507812, 0.6054688, 0.6250000, 0.6054688,\n         0.5507812, 0.4726562, 0.3750000, 0.2812500, 0.2031250, 0.1484375,\n         0.1328125, 0.1484375, 0.2031250, 0.2812500, 0.3789062, 0.4726562,\n         0.5507812, 0.6054688, 0.6250000, 0.6054688, 0.5507812, 0.4726562,\n         0.3750000, 0.2812500, 0.2031250, 0.1484375, 0.1328125, 0.1484375,\n         0.2031250, 0.2812500, 0.3789062, 0.4726562, 0.5507812, 0.6054688,\n         0.6250000, 0.6054688, 0.5507812, 0.4726562, 0.3750000, 0.2812500,\n         0.2031250, 0.1484375, 0.1328125, 0.1484375, 0.2031250, 0.2812500,\n         0.3789062, 0.4726562, 0.5507812, 0.6054688, 0.6250000, 0.6054688,\n         0.5507812, 0.4726562, 0.3750000, 0.2812500, 0.2031250, 0.1484375,\n         0.1328125, 0.1484375, 0.2031250, 0.2031250]),\narray([  0.5117188, 0.5117188, 0.5234375, 0.5351562, 0.5468750, 0.5585938,\n         0.5703125, 0.5820312, 0.5937500, 0.6054688, 0.6171875, 0.6289062,\n         0.6406250, 0.6523438, 0.6640625, 0.6757812, 0.6875000, 0.6992188,\n         0.7109375, 0.7226562, 0.7304688, 0.7421875, 0.7539062, 0.7617188,\n         0.7734375, 0.7851562, 0.7929688, 0.8046875, 0.8125000, 0.8203125,\n         0.8320312, 0.8398438, 0.8476562, 0.8554688, 0.8671875, 0.8750000,\n         0.8828125, 0.8906250, 0.8984375, 0.9023438, 0.9101562, 0.9179688,\n         0.9218750, 0.9296875, 0.9375000, 0.9414062, 0.9453125, 0.9531250,\n         0.9570312, 0.9609375, 0.9648438, 0.9687500, 0.9726562, 0.9765625,\n         0.9804688, 0.9804688, 0.9843750, 0.9882812, 0.9882812, 0.9921875,\n         0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9960938, 0.9921875,\n         0.9921875, 0.9921875, 0.9921875, 0.9921875, 0.9882812, 0.9882812,\n         0.9843750, 0.9804688, 0.9804688, 0.9765625, 0.9726562, 0.9687500,\n         0.9648438, 0.9609375, 0.9570312, 0.9531250, 0.9453125, 0.9414062,\n         0.9375000, 0.9296875, 0.9218750, 0.9179688, 0.9101562, 0.9023438,\n         0.8984375, 0.8906250, 0.8828125, 0.8750000, 0.8671875, 0.8554688,\n         0.8476562, 0.8398438, 0.8320312, 0.8203125, 0.8125000, 0.8046875,\n         0.7929688, 0.7851562, 0.7734375, 0.7617188, 0.7539062, 0.7421875,\n         0.7304688, 0.7226562, 0.7109375, 0.6992188, 0.6875000, 0.6757812,\n         0.6640625, 0.6523438, 0.6406250, 0.6289062, 0.6171875, 0.6054688,\n         0.5937500, 0.5820312, 0.5703125, 0.5585938, 0.5468750, 0.5351562,\n         0.5234375, 0.5117188, 0.4960938, 0.4843750, 0.4726562, 0.4609375,\n         0.4492188, 0.4375000, 0.4257812, 0.4140625, 0.4023438, 0.3906250,\n         0.3789062, 0.3671875, 0.3554688, 0.3437500, 0.3320312, 0.3203125,\n         0.3085938, 0.2968750, 0.2851562, 0.2734375, 0.2656250, 0.2539062,\n         0.2421875, 0.2343750, 0.2226562, 0.2109375, 0.2031250, 0.1914062,\n         0.1835938, 0.1757812, 0.1640625, 0.1562500, 0.1484375, 0.1406250,\n         0.1289062, 0.1210938, 0.1132812, 0.1054688, 0.0976562, 0.0937500,\n         0.0859375, 0.0781250, 0.0742188, 0.0664062, 0.0585938, 0.0546875,\n         0.0507812, 0.0429688, 0.0507812, 0.0351562, 0.0312500, 0.0273438,\n         0.0234375, 0.0195312, 0.0156250, 0.0156250, 0.0117188, 0.0078125,\n         0.0078125, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062, 0.0039062,\n         0.0078125, 0.0078125, 0.0117188, 0.0156250, 0.0156250, 0.0195312,\n         0.0234375, 0.0273438, 0.0312500, 0.0351562, 0.0507812, 0.0429688,\n         0.0507812, 0.0546875, 0.0585938, 0.0664062, 0.0742188, 0.0781250,\n         0.0859375, 0.0937500, 0.0976562, 0.1054688, 0.1132812, 0.1210938,\n         0.1289062, 0.1406250, 0.1484375, 0.1562500, 0.1640625, 0.1757812,\n         0.1835938, 0.1914062, 0.2031250, 0.2109375, 0.2226562, 0.2343750,\n         0.2421875, 0.2539062, 0.2656250, 0.2734375, 0.2851562, 0.2968750,\n         0.3085938, 0.3203125, 0.3320312, 0.3437500, 0.3554688, 0.3671875,\n         0.3789062, 0.3906250, 0.4023438, 0.4140625, 0.4257812, 0.4375000,\n         0.4492188, 0.4609375, 0.4726562, 0.4726562]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 38 :: Rainbow18 ###\n\ncolor_map_luts['idl38'] = \\\n   (\narray([  0.0000000, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.6835938, 0.6835938, 0.6835938, 0.6835938, 0.6835938, 0.6835938,\n         0.6835938, 0.6835938, 0.6835938, 0.6835938, 0.6835938, 0.6835938,\n         0.6835938, 0.6835938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.5468750, 0.5468750, 0.5468750, 0.5468750,\n         0.5468750, 0.5468750, 0.5468750, 0.5468750, 0.5468750, 0.5468750,\n         0.5468750, 0.5468750, 0.5468750, 0.5468750, 0.5468750, 0.5468750,\n         0.6640625, 0.6640625, 0.6640625, 0.6640625, 0.6640625, 0.6640625,\n         0.6640625, 0.6640625, 0.6640625, 0.6640625, 0.6640625, 0.6640625,\n         0.6640625, 0.6640625, 0.6640625, 0.6640625, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.6250000,\n         0.6250000, 0.6250000, 0.6250000, 0.6250000, 0.4882812, 0.4882812,\n         0.4882812, 0.4882812, 0.4882812, 0.4882812, 0.4882812, 0.4882812,\n         0.4882812, 0.4882812, 0.4882812, 0.4882812, 0.4882812, 0.4882812,\n         0.4882812, 0.4882812, 0.1953125, 0.1953125, 0.1953125, 0.1953125,\n         0.1953125, 0.1953125, 0.1953125, 0.1953125, 0.1953125, 0.1953125,\n         0.1953125, 0.1953125, 0.1953125, 0.1953125, 0.1953125, 0.1953125,\n         0.1953125, 0.1953125, 0.1953125, 0.1953125, 0.1953125, 0.1953125,\n         0.1953125, 0.1953125, 0.1953125, 0.1953125, 0.1953125, 0.1953125,\n         0.1953125, 0.1953125, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375, 0.5859375,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.7812500,\n         0.7812500, 0.7812500, 0.7812500, 0.7812500, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.4687500, 0.4687500, 0.4687500, 0.4687500, 0.4687500, 0.4687500,\n         0.4687500, 0.4687500, 0.4687500, 0.4687500, 0.4687500, 0.4687500,\n         0.4687500, 0.4687500, 0.4687500, 0.4687500, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250, 0.3906250,\n         0.2929688, 0.2929688, 0.2929688, 0.2929688, 0.2929688, 0.2929688,\n         0.2929688, 0.2929688, 0.2929688, 0.2929688, 0.2929688, 0.2929688,\n         0.2929688, 0.2929688, 0.9960938, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 39 :: Rainbow + white ###\n\ncolor_map_luts['idl39'] = \\\n   (\narray([  0.0000000, 0.0156250, 0.0351562, 0.0507812, 0.0703125, 0.0859375,\n         0.1054688, 0.1210938, 0.1406250, 0.1562500, 0.1757812, 0.1953125,\n         0.2265625, 0.2382812, 0.2500000, 0.2656250, 0.2695312, 0.2812500,\n         0.2890625, 0.3007812, 0.3085938, 0.3125000, 0.3203125, 0.3242188,\n         0.3281250, 0.3359375, 0.3398438, 0.3437500, 0.3359375, 0.3398438,\n         0.3398438, 0.3398438, 0.3320312, 0.3281250, 0.3281250, 0.3281250,\n         0.3085938, 0.3046875, 0.3007812, 0.2968750, 0.2773438, 0.2734375,\n         0.2656250, 0.2578125, 0.2343750, 0.2265625, 0.2148438, 0.1796875,\n         0.1679688, 0.1562500, 0.1406250, 0.1289062, 0.0976562, 0.0820312,\n         0.0625000, 0.0468750, 0.0156250, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0312500, 0.0468750,\n         0.0820312, 0.0976562, 0.1132812, 0.1640625, 0.1796875, 0.1992188,\n         0.2148438, 0.2460938, 0.2617188, 0.2812500, 0.2968750, 0.3125000,\n         0.3476562, 0.3632812, 0.3789062, 0.4296875, 0.4453125, 0.4648438,\n         0.4804688, 0.5117188, 0.5273438, 0.5468750, 0.5625000, 0.5976562,\n         0.6132812, 0.6289062, 0.6445312, 0.6953125, 0.7109375, 0.7304688,\n         0.7460938, 0.7773438, 0.7929688, 0.8125000, 0.8281250, 0.8632812,\n         0.8789062, 0.8945312, 0.9453125, 0.9609375, 0.9765625, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0625000,\n         0.0820312, 0.0976562, 0.1132812, 0.1484375, 0.1640625, 0.1796875,\n         0.1992188, 0.2148438, 0.2460938, 0.2617188, 0.2812500, 0.3281250,\n         0.3476562, 0.3632812, 0.3789062, 0.4140625, 0.4296875, 0.4453125,\n         0.4648438, 0.4960938, 0.5117188, 0.5273438, 0.5468750, 0.5937500,\n         0.6132812, 0.6289062, 0.6445312, 0.6796875, 0.6953125, 0.7109375,\n         0.7304688, 0.7617188, 0.7773438, 0.7929688, 0.8437500, 0.8593750,\n         0.8789062, 0.8945312, 0.9101562, 0.9453125, 0.9609375, 0.9765625,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9765625, 0.9453125, 0.9296875, 0.9101562, 0.8945312, 0.8632812,\n         0.8437500, 0.8281250, 0.7773438, 0.7617188, 0.7460938, 0.7304688,\n         0.6953125, 0.6796875, 0.6640625, 0.6445312, 0.6289062, 0.5976562,\n         0.5781250, 0.5625000, 0.5117188, 0.4960938, 0.4804688, 0.4648438,\n         0.4296875, 0.4140625, 0.3984375, 0.3789062, 0.3476562, 0.3320312,\n         0.3125000, 0.2968750, 0.2460938, 0.2304688, 0.2148438, 0.1992188,\n         0.1640625, 0.1484375, 0.1328125, 0.1132812, 0.0820312, 0.0664062,\n         0.0468750, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.9960938]),\narray([  0.0000000, 0.0117188, 0.0273438, 0.0390625, 0.0546875, 0.0742188,\n         0.0898438, 0.1093750, 0.1250000, 0.1484375, 0.1679688, 0.1875000,\n         0.2304688, 0.2460938, 0.2656250, 0.2812500, 0.3007812, 0.3164062,\n         0.3359375, 0.3554688, 0.3710938, 0.3906250, 0.4062500, 0.4257812,\n         0.4609375, 0.4765625, 0.4960938, 0.5156250, 0.5312500, 0.5507812,\n         0.5664062, 0.5859375, 0.6015625, 0.6210938, 0.6367188, 0.6562500,\n         0.6914062, 0.7109375, 0.7265625, 0.7460938, 0.7617188, 0.7812500,\n         0.7968750, 0.8164062, 0.8359375, 0.8515625, 0.8710938, 0.9062500,\n         0.9218750, 0.9414062, 0.9570312, 0.9765625, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9609375, 0.9453125, 0.9296875, 0.8789062, 0.8593750,\n         0.8437500, 0.8281250, 0.7929688, 0.7773438, 0.7617188, 0.7460938,\n         0.7304688, 0.6953125, 0.6796875, 0.6640625, 0.6132812, 0.5937500,\n         0.5781250, 0.5625000, 0.5273438, 0.5117188, 0.4960938, 0.4804688,\n         0.4453125, 0.4296875, 0.4140625, 0.3984375, 0.3476562, 0.3281250,\n         0.3125000, 0.2968750, 0.2617188, 0.2460938, 0.2304688, 0.2148438,\n         0.1796875, 0.1640625, 0.1484375, 0.0976562, 0.0820312, 0.0625000,\n         0.0468750, 0.0312500, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.9960938]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\n### IDL colormap 40 :: Rainbow + black ###\n\ncolor_map_luts['idl40'] = \\\n   (\narray([  0.0000000, 0.0156250, 0.0351562, 0.0507812, 0.0703125, 0.0859375,\n         0.1054688, 0.1210938, 0.1406250, 0.1562500, 0.1757812, 0.1953125,\n         0.2265625, 0.2382812, 0.2500000, 0.2656250, 0.2695312, 0.2812500,\n         0.2890625, 0.3007812, 0.3085938, 0.3125000, 0.3203125, 0.3242188,\n         0.3281250, 0.3359375, 0.3398438, 0.3437500, 0.3359375, 0.3398438,\n         0.3398438, 0.3398438, 0.3320312, 0.3281250, 0.3281250, 0.3281250,\n         0.3085938, 0.3046875, 0.3007812, 0.2968750, 0.2773438, 0.2734375,\n         0.2656250, 0.2578125, 0.2343750, 0.2265625, 0.2148438, 0.1796875,\n         0.1679688, 0.1562500, 0.1406250, 0.1289062, 0.0976562, 0.0820312,\n         0.0625000, 0.0468750, 0.0156250, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0312500, 0.0468750,\n         0.0820312, 0.0976562, 0.1132812, 0.1640625, 0.1796875, 0.1992188,\n         0.2148438, 0.2460938, 0.2617188, 0.2812500, 0.2968750, 0.3125000,\n         0.3476562, 0.3632812, 0.3789062, 0.4296875, 0.4453125, 0.4648438,\n         0.4804688, 0.5117188, 0.5273438, 0.5468750, 0.5625000, 0.5976562,\n         0.6132812, 0.6289062, 0.6445312, 0.6953125, 0.7109375, 0.7304688,\n         0.7460938, 0.7773438, 0.7929688, 0.8125000, 0.8281250, 0.8632812,\n         0.8789062, 0.8945312, 0.9453125, 0.9609375, 0.9765625, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.0000000]),\narray([  0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0156250, 0.0625000,\n         0.0820312, 0.0976562, 0.1132812, 0.1484375, 0.1640625, 0.1796875,\n         0.1992188, 0.2148438, 0.2460938, 0.2617188, 0.2812500, 0.3281250,\n         0.3476562, 0.3632812, 0.3789062, 0.4140625, 0.4296875, 0.4453125,\n         0.4648438, 0.4960938, 0.5117188, 0.5273438, 0.5468750, 0.5937500,\n         0.6132812, 0.6289062, 0.6445312, 0.6796875, 0.6953125, 0.7109375,\n         0.7304688, 0.7617188, 0.7773438, 0.7929688, 0.8437500, 0.8593750,\n         0.8789062, 0.8945312, 0.9101562, 0.9453125, 0.9609375, 0.9765625,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9765625, 0.9453125, 0.9296875, 0.9101562, 0.8945312, 0.8632812,\n         0.8437500, 0.8281250, 0.7773438, 0.7617188, 0.7460938, 0.7304688,\n         0.6953125, 0.6796875, 0.6640625, 0.6445312, 0.6289062, 0.5976562,\n         0.5781250, 0.5625000, 0.5117188, 0.4960938, 0.4804688, 0.4648438,\n         0.4296875, 0.4140625, 0.3984375, 0.3789062, 0.3476562, 0.3320312,\n         0.3125000, 0.2968750, 0.2460938, 0.2304688, 0.2148438, 0.1992188,\n         0.1640625, 0.1484375, 0.1328125, 0.1132812, 0.0820312, 0.0664062,\n         0.0468750, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  0.0000000, 0.0117188, 0.0273438, 0.0390625, 0.0546875, 0.0742188,\n         0.0898438, 0.1093750, 0.1250000, 0.1484375, 0.1679688, 0.1875000,\n         0.2304688, 0.2460938, 0.2656250, 0.2812500, 0.3007812, 0.3164062,\n         0.3359375, 0.3554688, 0.3710938, 0.3906250, 0.4062500, 0.4257812,\n         0.4609375, 0.4765625, 0.4960938, 0.5156250, 0.5312500, 0.5507812,\n         0.5664062, 0.5859375, 0.6015625, 0.6210938, 0.6367188, 0.6562500,\n         0.6914062, 0.7109375, 0.7265625, 0.7460938, 0.7617188, 0.7812500,\n         0.7968750, 0.8164062, 0.8359375, 0.8515625, 0.8710938, 0.9062500,\n         0.9218750, 0.9414062, 0.9570312, 0.9765625, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938, 0.9960938,\n         0.9960938, 0.9609375, 0.9453125, 0.9296875, 0.8789062, 0.8593750,\n         0.8437500, 0.8281250, 0.7929688, 0.7773438, 0.7617188, 0.7460938,\n         0.7304688, 0.6953125, 0.6796875, 0.6640625, 0.6132812, 0.5937500,\n         0.5781250, 0.5625000, 0.5273438, 0.5117188, 0.4960938, 0.4804688,\n         0.4453125, 0.4296875, 0.4140625, 0.3984375, 0.3476562, 0.3281250,\n         0.3125000, 0.2968750, 0.2617188, 0.2460938, 0.2304688, 0.2148438,\n         0.1796875, 0.1640625, 0.1484375, 0.0976562, 0.0820312, 0.0625000,\n         0.0468750, 0.0312500, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000,\n         0.0000000, 0.0000000, 0.0000000, 0.0000000]),\narray([  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,\n         1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),\n   )\n\ncolor_map_luts[\"doom\"] = (\narray([\n   0,  31,  23,  75, 255,  27,  19,  11,   7,  47,  35,  23,  15,  79,  71,  63,\n 255, 247, 243, 235, 231, 223, 219, 211, 203, 199, 191, 187, 179, 175, 167, 163,\n 155, 151, 143, 139, 131, 127, 119, 115, 107, 103,  95,  91,  83,  79,  71,  67,\n 255, 255, 255, 255, 255, 255, 255, 255, 255, 247, 239, 231, 223, 215, 207, 203,\n 191, 179, 171, 163, 155, 143, 135, 127, 119, 107,  95,  83,  75,  63,  51,  43,\n 239, 231, 223, 219, 211, 203, 199, 191, 183, 179, 171, 167, 159, 151, 147, 139,\n 131, 127, 119, 111, 107,  99,  91,  87,  79,  71,  67,  59,  55,  47,  39,  35,\n 119, 111, 103,  95,  91,  83,  75,  67,  63,  55,  47,  39,  31,  23,  19,  11,\n 191, 183, 175, 167, 159, 155, 147, 139, 131, 123, 119, 111, 103,  95,  87,  83,\n 159, 143, 131, 119, 103,  91,  79,  67, 123, 111, 103,  91,  83,  71,  63,  55,\n 255, 235, 215, 195, 175, 155, 135, 115, 255, 255, 255, 255, 255, 255, 255, 255,\n 255, 239, 227, 215, 203, 191, 179, 167, 155, 139, 127, 115, 103,  91,  79,  67,\n 231, 199, 171, 143, 115,  83,  55,  27,   0,   0,   0,   0,   0,   0,   0,   0,\n 255, 255, 255, 255, 255, 255, 255, 255, 243, 235, 223, 215, 203, 195, 183, 175,\n 255, 255, 255, 255, 255, 255, 255, 255, 167, 159, 147, 135,  79,  67,  55,  47,\n   0,   0,   0,   0,   0,   0,   0,   0, 255, 255, 255, 255, 207, 159, 111,\n   167]) / 255.0,\narray([\n   0,  23,  15,  75, 255,  27,  19,  11,   7,  55,  43,  31,  23,  59,  51,  43,\n 183, 171, 163, 151, 143, 135, 123, 115, 107,  99,  91,  87,  79,  71,  63,  59,\n  51,  47,  43,  35,  31,  27,  23,  19,  15,  11,   7,   7,   7,   0,   0,   0,\n 235, 227, 219, 211, 207, 199, 191, 187, 179, 171, 163, 155, 147, 139, 131, 127,\n 123, 115, 111, 107,  99,  95,  87,  83,  79,  71,  67,  63,  55,  47,  43,  35,\n 239, 231, 223, 219, 211, 203, 199, 191, 183, 179, 171, 167, 159, 151, 147, 139,\n 131, 127, 119, 111, 107,  99,  91,  87,  79,  71,  67,  59,  55,  47,  39,  35,\n 255, 239, 223, 207, 191, 175, 159, 147, 131, 115,  99,  83,  67,  51,  35,  23,\n 167, 159, 151, 143, 135, 127, 123, 115, 107,  99,  95,  87,  83,  75,  67,  63,\n 131, 119, 107,  95,  83,  71,  59,  51, 127, 115, 107,  99,  87,  79,  71,  63,\n 255, 219, 187, 155, 123,  91,  67,  43, 255, 219, 187, 155, 123,  95,  63,  31,\n   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,\n 231, 199, 171, 143, 115,  83,  55,  27,   0,   0,   0,   0,   0,   0,   0,   0,\n 255, 235, 215, 199, 179, 163, 143, 127, 115, 111, 103,  95,  87,  79,  71,  67,\n 255, 255, 255, 255, 255, 255, 255, 255,  63,  55,  47,  35,  59,  47,  35,  27,\n   0,   0,   0,   0,   0,   0,   0,   0, 159, 231, 123,   0,   0,   0,   0,\n   107]) / 255.0,\narray([\n   0,  11,   7,  75, 255,  27,  19,  11,   7,  31,  15,   7,   0,  43,  35,  27,\n 183, 171, 163, 151, 143, 135, 123, 115, 107,  99,  91,  87,  79,  71,  63,  59,\n  51,  47,  43,  35,  31,  27,  23,  19,  15,  11,   7,   7,   7,   0,   0,   0,\n 223, 211, 199, 187, 179, 167, 155, 147, 131, 123, 115, 107,  99,  91,  83,  79,\n  75,  71,  67,  63,  59,  55,  51,  47,  43,  39,  35,  31,  27,  23,  19,  15,\n 239, 231, 223, 219, 211, 203, 199, 191, 183, 179, 171, 167, 159, 151, 147, 139,\n 131, 127, 119, 111, 107,  99,  91,  87,  79,  71,  67,  59,  55,  47,  39,  35,\n 111, 103,  95,  87,  79,  71,  63,  55,  47,  43,  35,  27,  23,  15,  11,   7,\n 143, 135, 127, 119, 111, 107,  99,  91,  87,  79,  75,  67,  63,  55,  51,  47,\n  99,  83,  75,  63,  51,  43,  35,  27,  99,  87,  79,  71,  59,  51,  43,  39,\n 115,  87,  67,  47,  31,  19,   7,   0, 255, 219, 187, 155, 123,  95,  63,  31,\n   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,\n 255, 255, 255, 255, 255, 255, 255, 255, 255, 227, 203, 179, 155, 131, 107,  83,\n 255, 219, 187, 155, 123,  91,  59,  27,  23,  15,  15,  11,   7,   0,   0,   0,\n 255, 215, 179, 143, 107,  71,  35,   0,   0,   0,   0,   0,  39,  27,  19,  11,\n  83,  71,  59,  47,  35,  23,  11,   0,  67,  75, 255, 255, 207, 155, 107,\n  107]) / 255.0,\nnp.ones(256),\n)\n\n# Aliases\ncolor_map_luts['B-W LINEAR'] = color_map_luts['idl00']\ncolor_map_luts['BLUE'] = color_map_luts['idl01']\ncolor_map_luts['GRN-RED-BLU-WHT'] = color_map_luts['idl02']\ncolor_map_luts['RED TEMPERATURE'] = color_map_luts['idl03']\ncolor_map_luts['BLUE'] = color_map_luts['idl04']\ncolor_map_luts['STD GAMMA-II'] = color_map_luts['idl05']\ncolor_map_luts['PRISM'] = color_map_luts['idl06']\ncolor_map_luts['RED-PURPLE'] = color_map_luts['idl07']\ncolor_map_luts['GREEN'] = color_map_luts['idl08']\ncolor_map_luts['GRN'] = color_map_luts['idl09']\ncolor_map_luts['GREEN-PINK'] = color_map_luts['idl10']\ncolor_map_luts['BLUE-RED'] = color_map_luts['idl11']\ncolor_map_luts['16 LEVEL'] = color_map_luts['idl12']\ncolor_map_luts['RAINBOW'] = color_map_luts['idl13']\ncolor_map_luts['STEPS'] = color_map_luts['idl14']\ncolor_map_luts['STERN SPECIAL'] = color_map_luts['idl15']\ncolor_map_luts['Haze'] = color_map_luts['idl16']\ncolor_map_luts['Blue - Pastel - Red'] = color_map_luts['idl17']\ncolor_map_luts['Pastels'] = color_map_luts['idl18']\ncolor_map_luts['Hue Sat Lightness 1'] = color_map_luts['idl19']\ncolor_map_luts['Hue Sat Lightness 2'] = color_map_luts['idl20']\ncolor_map_luts['Hue Sat Value 1'] = color_map_luts['idl21']\ncolor_map_luts['Hue Sat Value 2'] = color_map_luts['idl22']\ncolor_map_luts['Purple-Red + Stripes'] = color_map_luts['idl23']\ncolor_map_luts['Beach'] = color_map_luts['idl24']\ncolor_map_luts['Mac Style'] = color_map_luts['idl25']\ncolor_map_luts['Eos A'] = color_map_luts['idl26']\ncolor_map_luts['Eos B'] = color_map_luts['idl27']\ncolor_map_luts['Hardcandy'] = color_map_luts['idl28']\ncolor_map_luts['Nature'] = color_map_luts['idl29']\ncolor_map_luts['Ocean'] = color_map_luts['idl30']\ncolor_map_luts['Peppermint'] = color_map_luts['idl31']\ncolor_map_luts['Plasma'] = color_map_luts['idl32']\ncolor_map_luts['Blue-Red'] = color_map_luts['idl33']\ncolor_map_luts['Rainbow'] = color_map_luts['idl34']\ncolor_map_luts['Blue Waves'] = color_map_luts['idl35']\ncolor_map_luts['Volcano'] = color_map_luts['idl36']\ncolor_map_luts['Waves'] = color_map_luts['idl37']\ncolor_map_luts['Rainbow18'] = color_map_luts['idl38']\ncolor_map_luts['Rainbow + white'] = color_map_luts['idl39']\ncolor_map_luts['Rainbow + black'] = color_map_luts['idl40']\n\n# Create a reversed LUT for each of the above defined LUTs\n# and append a \"_r\" (for reversal. consistent with MPL convention).\n# So for example, the reversal of \"Waves\" is \"Waves_r\"\ntemp = {}\nfor k,v in color_map_luts.items():\n    temp[k+\"_r\"] = (v[0][::-1], v[1][::-1], v[2][::-1], v[3][::-1])\ncolor_map_luts.update(temp)\n"
  },
  {
    "path": "yt/visualization/_commons.py",
    "content": "import os\nimport warnings\nfrom functools import wraps\nfrom typing import TYPE_CHECKING, TypeVar\n\nimport matplotlib as mpl\nfrom matplotlib.ticker import SymmetricalLogLocator\nfrom more_itertools import always_iterable\n\nfrom yt.config import ytcfg\n\nif TYPE_CHECKING:\n    from matplotlib.backend_bases import FigureCanvasBase\n\n\n_DEFAULT_FONT_PROPERTIES = None\n\n\ndef get_default_font_properties():\n    global _DEFAULT_FONT_PROPERTIES\n    if _DEFAULT_FONT_PROPERTIES is None:\n        import importlib.resources as importlib_resources\n\n        _yt_style = mpl.rc_params_from_file(\n            importlib_resources.files(\"yt\") / \"default.mplstyle\",\n            use_default_template=False,\n        )\n        _DEFAULT_FONT_PROPERTIES = {\n            \"family\": _yt_style[\"font.family\"][0],\n            \"math_fontfamily\": _yt_style[\"mathtext.fontset\"],\n        }\n\n    return _DEFAULT_FONT_PROPERTIES\n\n\ndef _get_supported_image_file_formats():\n    from matplotlib.backend_bases import FigureCanvasBase\n\n    return frozenset(FigureCanvasBase.get_supported_filetypes().keys())\n\n\ndef _get_supported_canvas_classes():\n    from matplotlib.backends.backend_agg import FigureCanvasAgg\n    from matplotlib.backends.backend_pdf import FigureCanvasPdf\n    from matplotlib.backends.backend_ps import FigureCanvasPS\n    from matplotlib.backends.backend_svg import FigureCanvasSVG\n\n    return frozenset(\n        (FigureCanvasAgg, FigureCanvasPdf, FigureCanvasPS, FigureCanvasSVG)\n    )\n\n\ndef get_canvas_class(suffix: str) -> type[\"FigureCanvasBase\"]:\n    s = suffix.removeprefix(\".\")\n    if s not in _get_supported_image_file_formats():\n        raise ValueError(f\"Unsupported file format '{suffix}'.\")\n    for cls in _get_supported_canvas_classes():\n        if s in cls.get_supported_filetypes():\n            return cls\n    raise RuntimeError(\n        \"Something went terribly wrong. \"\n        f\"File extension '{suffix}' is supposed to be supported \"\n        \"but no compatible backend was found.\"\n    )\n\n\ndef validate_image_name(filename, suffix: str | None = None) -> str:\n    \"\"\"\n    Build a valid image filename with a specified extension (default to png).\n    The suffix parameter is ignored if the input filename has a valid extension already.\n    Otherwise, suffix is appended to the filename, replacing any existing extension.\n    \"\"\"\n    name, psuffix = os.path.splitext(filename)\n    psuffix = psuffix.removeprefix(\".\")\n\n    if suffix is not None:\n        suffix = suffix.removeprefix(\".\")\n\n    if psuffix in _get_supported_image_file_formats():\n        if suffix in _get_supported_image_file_formats() and suffix != psuffix:\n            warnings.warn(\n                f\"Received two valid image formats {psuffix!r} (from filename) \"\n                f\"and {suffix!r} (from suffix). The former is ignored.\",\n                stacklevel=2,\n            )\n            return f\"{name}.{suffix}\"\n        return str(filename)\n\n    if suffix is None:\n        suffix = \"png\"\n\n    if suffix not in _get_supported_image_file_formats():\n        raise ValueError(f\"Unsupported file format {suffix!r}\")\n\n    return f\"{filename}.{suffix}\"\n\n\ndef get_canvas(figure, filename):\n    name, suffix = os.path.splitext(filename)\n\n    if not suffix:\n        raise ValueError(\n            f\"Can not determine canvas class from filename '{filename}' \"\n            f\"without an extension.\"\n        )\n    return get_canvas_class(suffix)(figure)\n\n\ndef invalidate_plot(f):\n    @wraps(f)\n    def newfunc(self, *args, **kwargs):\n        retv = f(self, *args, **kwargs)\n        self._plot_valid = False\n        return retv\n\n    return newfunc\n\n\ndef invalidate_data(f):\n    @wraps(f)\n    def newfunc(self, *args, **kwargs):\n        retv = f(self, *args, **kwargs)\n        self._data_valid = False\n        self._plot_valid = False\n        return retv\n\n    return newfunc\n\n\ndef invalidate_figure(f):\n    @wraps(f)\n    def newfunc(self, *args, **kwargs):\n        retv = f(self, *args, **kwargs)\n        for field in self.plots.keys():\n            self.plots[field].figure = None\n            self.plots[field].axes = None\n            self.plots[field].cax = None\n        self._setup_plots()\n        return retv\n\n    return newfunc\n\n\ndef validate_plot(f):\n    @wraps(f)\n    def newfunc(self, *args, **kwargs):\n        # TODO: _profile_valid and _data_valid seem to play very similar roles,\n        # there's probably room to abstract these into a common operation\n        if hasattr(self, \"_data_valid\") and not self._data_valid:\n            self._recreate_frb()\n        if hasattr(self, \"_profile_valid\") and not self._profile_valid:\n            self._recreate_profile()\n        if not self._plot_valid:\n            # it is the responsibility of _setup_plots to\n            # call plot.run_callbacks()\n            self._setup_plots()\n        retv = f(self, *args, **kwargs)\n        return retv\n\n    return newfunc\n\n\nT = TypeVar(\"T\", tuple, list)\n\n\ndef _swap_axes_extents(extent: T) -> T:\n    \"\"\"\n    swaps the x and y extent values, preserving type of extent\n\n    Parameters\n    ----------\n    extent : sequence of four unyt quantities\n        the current 4-element tuple or list of unyt quantities describing the\n        plot extent. extent = (xmin, xmax, ymin, ymax).\n\n    Returns\n    -------\n    tuple or list\n        the extent axes swapped, now with (ymin, ymax, xmin, xmax).\n\n    \"\"\"\n    extent_swapped = [extent[2], extent[3], extent[0], extent[1]]\n    return type(extent)(extent_swapped)\n\n\ndef _swap_arg_pair_order(*args):\n    \"\"\"\n    flips adjacent argument pairs, useful for swapping x-y plot arguments\n\n    Parameters\n    ----------\n    *args\n        argument pairs, must have an even number of *args\n\n    Returns\n    -------\n    tuple\n        args  with order of pairs switched, i.e,:\n\n        _swap_arg_pair_order(x, y, px, py) returns:\n            y, x, py, px\n\n    \"\"\"\n\n    if len(args) % 2 != 0:\n        raise TypeError(\"Number of arguments must be even.\")\n    n_pairs = len(args) // 2\n    new_args = []\n    for i in range(n_pairs):\n        x_id = i * 2\n        new_args.append(args[x_id + 1])\n        new_args.append(args[x_id])\n    return tuple(new_args)\n\n\nclass _MPL38_SymmetricalLogLocator(SymmetricalLogLocator):\n    # Backporting behaviour from matplotlib 3.8 (in development at the time of writing)\n    # see https://github.com/matplotlib/matplotlib/pull/25970\n\n    def __init__(self, *args, **kwargs):\n        if mpl.__version_info__ >= (3, 8):\n            raise RuntimeError(\n                \"_MPL38_SymmetricalLogLocator is not needed with matplotlib>=3.8\"\n            )\n        super().__init__(*args, **kwargs)\n\n    def tick_values(self, vmin, vmax):\n        linthresh = self._linthresh\n        if vmax < vmin:\n            vmin, vmax = vmax, vmin\n        if -linthresh <= vmin < vmax <= linthresh:\n            # only the linear range is present\n            return sorted({vmin, 0, vmax})\n\n        return super().tick_values(vmin, vmax)\n\n\ndef get_default_from_config(data_source, *, field, keys, defaults):\n    _keys = list(always_iterable(keys))\n    _defaults = list(always_iterable(defaults))\n\n    ftype, fname = data_source._determine_fields(field)[0]\n    ret = [\n        ytcfg.get_most_specific(\"plot\", ftype, fname, key, fallback=default)\n        for key, default in zip(_keys, _defaults, strict=True)\n    ]\n    if len(ret) == 1:\n        return ret[0]\n    else:\n        return ret\n\n\ndef _get_units_label(units: str) -> str:\n    if r\"\\frac\" in units:\n        return rf\"$\\ \\ \\left({units}\\right)$\"\n    elif units:\n        return rf\"$\\ \\ ({units})$\"\n    else:\n        return \"\"\n"
  },
  {
    "path": "yt/visualization/_handlers.py",
    "content": "import weakref\nfrom numbers import Real\nfrom typing import TYPE_CHECKING, Any, Literal, Optional, TypeAlias, Union\n\nimport matplotlib as mpl\nimport numpy as np\nimport unyt as un\nfrom matplotlib.colors import Colormap, LogNorm, Normalize, SymLogNorm\nfrom unyt import unyt_quantity\n\nfrom yt._typing import Quantity, Unit\nfrom yt.config import ytcfg\nfrom yt.funcs import get_brewer_cmap, is_sequence, mylog\n\nif TYPE_CHECKING:\n    # RGBColorType, RGBAColorType and ColorType are backported from matplotlib 3.8.0\n    RGBColorType = tuple[float, float, float] | str\n    RGBAColorType = Union[  # noqa: UP007\n        str,  # \"none\" or \"#RRGGBBAA\"/\"#RGBA\" hex strings\n        tuple[float, float, float, float],\n        # 2 tuple (color, alpha) representations, not infinitely recursive\n        # RGBColorType includes the (str, float) tuple, even for RGBA strings\n        tuple[RGBColorType, float],\n        # (4-tuple, float) is odd, but accepted as the outer float overriding A of 4-tuple\n        tuple[tuple[float, float, float, float], float],\n    ]\n\n    ColorType = RGBColorType | RGBAColorType\n\n    # this type alias is unique to the present module\n    ColormapInput: TypeAlias = Colormap | str | None\n\n\nclass NormHandler:\n    \"\"\"\n    A bookkeeper class that can hold a fully defined norm object, or dynamically\n    build one on demand according to a set of constraints.\n\n    If a fully defined norm object is added, any existing constraints are\n    dropped, and vice versa. These rules are implemented with properties and\n    watcher patterns.\n\n    It also keeps track of display units so that vmin, vmax and linthresh can be\n    updated with implicit units.\n    \"\"\"\n\n    # using slots here to minimize the risk of introducing bugs\n    # since attributes names are essential to this class's implementation\n    __slots__ = (\n        \"data_source\",\n        \"ds\",\n        \"_display_units\",\n        \"_vmin\",\n        \"_vmax\",\n        \"_dynamic_range\",\n        \"_norm_type\",\n        \"_linthresh\",\n        \"_norm_type\",\n        \"_norm\",\n        \"prefer_log\",\n    )\n    _constraint_attrs: list[str] = [\n        \"vmin\",\n        \"vmax\",\n        \"dynamic_range\",\n        \"norm_type\",\n        \"linthresh\",\n    ]\n\n    def __init__(\n        self,\n        data_source,\n        *,\n        display_units: un.Unit,\n        vmin: un.unyt_quantity | None = None,\n        vmax: un.unyt_quantity | None = None,\n        dynamic_range: float | None = None,\n        norm_type: type[Normalize] | None = None,\n        norm: Normalize | None = None,\n        linthresh: float | None = None,\n    ):\n        self.data_source = weakref.proxy(data_source)\n        self.ds = data_source.ds  # should already be a weakref proxy\n        self._display_units = display_units\n\n        self._norm = norm\n        self._vmin = vmin\n        self._vmax = vmax\n        self._dynamic_range = dynamic_range\n        self._norm_type = norm_type\n        self._linthresh = linthresh\n        self.prefer_log = True\n\n        if self.norm is not None and self.has_constraints:\n            raise TypeError(\n                \"NormHandler input is malformed. \"\n                \"A norm cannot be passed along other constraints.\"\n            )\n\n    def _get_constraints(self) -> dict[str, Any]:\n        return {\n            attr: getattr(self, attr)\n            for attr in self.__class__._constraint_attrs\n            if getattr(self, attr) is not None\n        }\n\n    @property\n    def has_constraints(self) -> bool:\n        return bool(self._get_constraints())\n\n    def _reset_constraints(self) -> None:\n        constraints = self._get_constraints()\n        if not constraints:\n            return\n\n        msg = \", \".join([f\"{name}={value}\" for name, value in constraints.items()])\n        mylog.warning(\"Dropping norm constraints (%s)\", msg)\n        for name in constraints.keys():\n            setattr(self, name, None)\n\n    def _reset_norm(self) -> None:\n        if self.norm is None:\n            return\n        mylog.warning(\"Dropping norm (%s)\", self.norm)\n        self._norm = None\n\n    def to_float(self, val: un.unyt_quantity) -> float:\n        return float(val.to(self.display_units).d)\n\n    def to_quan(self, val) -> un.unyt_quantity:\n        if isinstance(val, un.unyt_quantity):\n            return self.ds.quan(val)\n        elif (\n            is_sequence(val)\n            and len(val) == 2\n            and isinstance(val[0], Real)\n            and isinstance(val[1], (str, un.Unit))\n        ):\n            return self.ds.quan(*val)\n        elif isinstance(val, Real):\n            return self.ds.quan(val, self.display_units)\n        else:\n            raise TypeError(f\"Could not convert {val!r} to unyt_quantity\")\n\n    @property\n    def display_units(self) -> un.Unit:\n        return self._display_units\n\n    @display_units.setter\n    def display_units(self, newval: Unit) -> None:\n        self._display_units = un.Unit(newval, registry=self.ds.unit_registry)\n\n    def _set_quan_attr(self, attr: str, newval: Quantity | float | None) -> None:\n        if newval is None:\n            setattr(self, attr, None)\n        else:\n            try:\n                quan = self.to_quan(newval)\n            except TypeError as exc:\n                raise TypeError(\n                    \"Expected None, a float, or a unyt_quantity, \"\n                    f\"received {newval} with type {type(newval)}\"\n                ) from exc\n            else:\n                setattr(self, attr, quan)\n\n    @property\n    def vmin(self) -> un.unyt_quantity | Literal[\"min\"] | None:\n        return self._vmin\n\n    @vmin.setter\n    def vmin(self, newval: Quantity | float | Literal[\"min\"] | None) -> None:\n        self._reset_norm()\n        if newval == \"min\":\n            self._vmin = \"min\"\n        else:\n            self._set_quan_attr(\"_vmin\", newval)\n\n    @property\n    def vmax(self) -> un.unyt_quantity | Literal[\"max\"] | None:\n        return self._vmax\n\n    @vmax.setter\n    def vmax(self, newval: Quantity | float | Literal[\"max\"] | None) -> None:\n        self._reset_norm()\n        if newval == \"max\":\n            self._vmax = \"max\"\n        else:\n            self._set_quan_attr(\"_vmax\", newval)\n\n    @property\n    def dynamic_range(self) -> float | None:\n        return self._dynamic_range\n\n    @dynamic_range.setter\n    def dynamic_range(self, newval: float | None) -> None:\n        if newval is None:\n            return\n\n        try:\n            newval = float(newval)\n        except TypeError:\n            raise TypeError(\n                f\"Expected a float. Received {newval} with type {type(newval)}\"\n            ) from None\n\n        if newval == 0:\n            raise ValueError(\"Dynamic range cannot be zero.\")\n\n        if newval == 1:\n            raise ValueError(\"Dynamic range cannot be unity.\")\n\n        self._reset_norm()\n        self._dynamic_range = newval\n\n    def get_dynamic_range(\n        self, dvmin: float | None, dvmax: float | None\n    ) -> tuple[float, float]:\n        if self.dynamic_range is None:\n            raise RuntimeError(\n                \"Something went terribly wrong in setting up a dynamic range\"\n            )\n\n        if self.vmax is None:\n            if self.vmin is None:\n                raise TypeError(\n                    \"Cannot set dynamic range with neither \"\n                    \"vmin and vmax being constrained.\"\n                )\n            if dvmin is None:\n                raise RuntimeError(\n                    \"Something went terribly wrong in setting up a dynamic range\"\n                )\n            return dvmin, dvmin * self.dynamic_range\n        elif self.vmin is None:\n            if dvmax is None:\n                raise RuntimeError(\n                    \"Something went terribly wrong in setting up a dynamic range\"\n                )\n            return dvmax / self.dynamic_range, dvmax\n        else:\n            raise TypeError(\n                \"Cannot set dynamic range with both vmin and vmax already constrained.\"\n            )\n\n    @property\n    def norm_type(self) -> type[Normalize] | None:\n        return self._norm_type\n\n    @norm_type.setter\n    def norm_type(self, newval: type[Normalize] | None) -> None:\n        if not (\n            newval is None\n            or (isinstance(newval, type) and issubclass(newval, Normalize))\n        ):\n            raise TypeError(\n                \"Expected a subclass of matplotlib.colors.Normalize, \"\n                f\"received {newval} with type {type(newval)}\"\n            )\n        self._reset_norm()\n        if newval is not SymLogNorm:\n            self.linthresh = None\n        self._norm_type = newval\n\n    @property\n    def norm(self) -> Normalize | None:\n        return self._norm\n\n    @norm.setter\n    def norm(self, newval: Normalize) -> None:\n        if not isinstance(newval, Normalize):\n            raise TypeError(\n                \"Expected a matplotlib.colors.Normalize object, \"\n                f\"received {newval} with type {type(newval)}\"\n            )\n        self._reset_constraints()\n        self._norm = newval\n\n    @property\n    def linthresh(self) -> float | None:\n        return self._linthresh\n\n    @linthresh.setter\n    def linthresh(self, newval: Quantity | float | None) -> None:\n        self._reset_norm()\n        self._set_quan_attr(\"_linthresh\", newval)\n        if self._linthresh is not None and self._linthresh <= 0:\n            raise ValueError(\n                f\"linthresh can only be set to strictly positive values, got {newval}\"\n            )\n        if newval is not None:\n            self.norm_type = SymLogNorm\n\n    def get_norm(self, data: np.ndarray, *args, **kw) -> Normalize:\n        if self.norm is not None:\n            return self.norm\n\n        dvmin = dvmax = None\n\n        finite_values_mask = np.isfinite(data)\n        if self.vmin is not None and not (\n            isinstance(self.vmin, str) and self.vmin == \"min\"\n        ):\n            dvmin = self.to_float(self.vmin)\n        elif np.any(finite_values_mask):\n            dvmin = self.to_float(np.nanmin(data[finite_values_mask]))\n\n        if self.vmax is not None and not (\n            isinstance(self.vmax, str) and self.vmax == \"max\"\n        ):\n            dvmax = self.to_float(self.vmax)\n        elif np.any(finite_values_mask):\n            dvmax = self.to_float(np.nanmax(data[finite_values_mask]))\n\n        if self.dynamic_range is not None:\n            dvmin, dvmax = self.get_dynamic_range(dvmin, dvmax)\n\n        if dvmin is None:\n            dvmin = 1 * getattr(data, \"units\", 1)\n        kw.setdefault(\"vmin\", dvmin)\n\n        if dvmax is None:\n            dvmax = 1 * getattr(data, \"units\", 1)\n        kw.setdefault(\"vmax\", dvmax)\n\n        norm_type: type[Normalize]\n        if data.ndim == 3:\n            assert data.shape[-1] == 4\n            # this is an RGBA array, only linear normalization makes sense here\n            norm_type = Normalize\n        elif self.norm_type is not None:\n            # this is a convenience mechanism for backward compat,\n            # allowing to toggle between lin and log scaling without detailed user input\n            norm_type = self.norm_type\n        else:\n            if (\n                not self.prefer_log\n                or kw[\"vmin\"] == kw[\"vmax\"]\n                or not np.any(finite_values_mask)\n            ):\n                norm_type = Normalize\n            elif kw[\"vmin\"] <= 0:\n                # note: see issue 3944 (and PRs and issues linked therein) for a\n                # discussion on when to switch to SymLog and related questions\n                # of how to calculate a default linthresh value.\n                norm_type = SymLogNorm\n            else:\n                norm_type = LogNorm\n\n        if norm_type is SymLogNorm:\n            if self.linthresh is not None:\n                linthresh = self.to_float(self.linthresh)\n            else:\n                linthresh = self._guess_linthresh(data[finite_values_mask])\n\n            kw.setdefault(\"linthresh\", linthresh)\n            kw.setdefault(\"base\", 10)\n\n        return norm_type(*args, **kw)\n\n    def _guess_linthresh(self, finite_plot_data):\n        # finite_plot_data is the ImageArray or ColorbarHandler data, already\n        # filtered to be finite values\n\n        # get the extrema for the negative and positive values separately\n        # neg_min -> neg_max -> 0 -> pos_min -> pos_max\n        def get_minmax(data):\n            if len(data) > 0:\n                return np.nanmin(data), np.nanmax(data)\n            return None, None\n\n        pos_min, pos_max = get_minmax(finite_plot_data[finite_plot_data > 0])\n        neg_min, neg_max = get_minmax(finite_plot_data[finite_plot_data < 0])\n\n        has_pos = pos_min is not None\n        has_neg = neg_min is not None\n\n        # the starting guess is the absolute value of the point closest to 0\n        # (remember: neg_max is closer to 0 than neg_min)\n        if has_pos and has_neg:\n            linthresh = np.min((-neg_max, pos_min))\n        elif has_pos:\n            linthresh = pos_min\n        elif has_neg:\n            linthresh = -neg_max\n        else:\n            # this condition should be handled before here in get_norm\n            raise RuntimeError(\"No finite data points.\")\n\n        log10_linthresh = np.log10(linthresh)\n\n        # if either the pos or neg ranges exceed cutoff_sigdigs, then\n        # linthresh is shifted to decrease the range to avoid floating point\n        # precision errors in the normalization.\n        cutoff_sigdigs = 15  # max allowable range in significant digits\n        if has_pos and np.log10(pos_max) - log10_linthresh > cutoff_sigdigs:\n            linthresh = pos_max / (10.0**cutoff_sigdigs)\n            log10_linthresh = np.log10(linthresh)\n\n        if has_neg and np.log10(-neg_min) - log10_linthresh > cutoff_sigdigs:\n            linthresh = np.abs(neg_min) / (10.0**cutoff_sigdigs)\n\n        if isinstance(linthresh, unyt_quantity):\n            # if the original plot_data has units, linthresh will have units here\n            return self.to_float(linthresh)\n\n        return linthresh\n\n\nclass ColorbarHandler:\n    __slots__ = (\"_draw_cbar\", \"_draw_minorticks\", \"_cmap\", \"_background_color\")\n\n    def __init__(\n        self,\n        *,\n        draw_cbar: bool = True,\n        draw_minorticks: bool = True,\n        cmap: \"ColormapInput\" = None,\n        background_color: str | None = None,\n    ):\n        self._draw_cbar = draw_cbar\n        self._draw_minorticks = draw_minorticks\n        self._cmap: Colormap | None = None\n        self._set_cmap(cmap)\n        self._background_color: ColorType | None = background_color\n\n    @property\n    def draw_cbar(self) -> bool:\n        return self._draw_cbar\n\n    @draw_cbar.setter\n    def draw_cbar(self, newval) -> None:\n        if not isinstance(newval, bool):\n            raise TypeError(\n                f\"Expected a boolean, got {newval} with type {type(newval)}\"\n            )\n        self._draw_cbar = newval\n\n    @property\n    def draw_minorticks(self) -> bool:\n        return self._draw_minorticks\n\n    @draw_minorticks.setter\n    def draw_minorticks(self, newval) -> None:\n        if not isinstance(newval, bool):\n            raise TypeError(\n                f\"Expected a boolean, got {newval} with type {type(newval)}\"\n            )\n        self._draw_minorticks = newval\n\n    @property\n    def cmap(self) -> Colormap:\n        return self._cmap or mpl.colormaps[ytcfg.get(\"yt\", \"default_colormap\")]\n\n    @cmap.setter\n    def cmap(self, newval: \"ColormapInput\") -> None:\n        self._set_cmap(newval)\n\n    def _set_cmap(self, newval: \"ColormapInput\") -> None:\n        # a separate setter function is better supported by type checkers (mypy)\n        # than relying purely on a property setter to narrow type\n        # from ColormapInput to Colormap\n        if isinstance(newval, Colormap) or newval is None:\n            self._cmap = newval\n        elif isinstance(newval, str):\n            self._cmap = mpl.colormaps[newval]\n        elif is_sequence(newval):  # type: ignore[unreachable]\n            # tuple colormaps are from palettable (or brewer2mpl)\n            self._cmap = get_brewer_cmap(newval)\n        else:\n            raise TypeError(\n                \"Expected a colormap object or name, \"\n                f\"got {newval} with type {type(newval)}\"\n            )\n\n    @property\n    def background_color(self) -> \"ColorType\":\n        return self._background_color or \"white\"\n\n    @background_color.setter\n    def background_color(self, newval: Optional[\"ColorType\"]) -> None:\n        if newval is None:\n            self._background_color = self.cmap(0)\n        else:\n            self._background_color = newval\n\n    @property\n    def has_background_color(self) -> bool:\n        return self._background_color is not None\n"
  },
  {
    "path": "yt/visualization/api.py",
    "content": "from .base_plot_types import get_multi_plot\nfrom .color_maps import add_colormap, make_colormap, show_colormaps\nfrom .fits_image import (\n    FITSImageData,\n    FITSOffAxisProjection,\n    FITSOffAxisSlice,\n    FITSParticleOffAxisProjection,\n    FITSParticleProjection,\n    FITSProjection,\n    FITSSlice,\n)\nfrom .fixed_resolution import FixedResolutionBuffer, ParticleImageBuffer\nfrom .image_writer import (\n    apply_colormap,\n    map_to_colors,\n    multi_image_composite,\n    scale_image,\n    splat_points,\n    write_bitmap,\n    write_image,\n    write_projection,\n)\nfrom .line_plot import LineBuffer, LinePlot\nfrom .particle_plots import ParticlePhasePlot, ParticlePlot, ParticleProjectionPlot\nfrom .plot_modifications import PlotCallback, callback_registry\nfrom .plot_window import (\n    AxisAlignedProjectionPlot,\n    AxisAlignedSlicePlot,\n    OffAxisProjectionPlot,\n    OffAxisSlicePlot,\n    ProjectionPlot,\n    SlicePlot,\n    plot_2d,\n)\nfrom .profile_plotter import PhasePlot, ProfilePlot\nfrom .streamlines import Streamlines\n"
  },
  {
    "path": "yt/visualization/base_plot_types.py",
    "content": "import sys\nimport warnings\nfrom abc import ABC\nfrom io import BytesIO\nfrom typing import TYPE_CHECKING, Optional, TypedDict\n\nimport matplotlib as mpl\nimport matplotlib.style\nimport numpy as np\nfrom matplotlib.scale import SymmetricalLogTransform\nfrom matplotlib.ticker import LogFormatterMathtext\n\nfrom yt._typing import AlphaT\nfrom yt.funcs import (\n    get_interactivity,\n    is_sequence,\n    mylog,\n    setdefault_mpl_metadata,\n    setdefaultattr,\n)\nfrom yt.visualization._handlers import ColorbarHandler, NormHandler\n\nfrom ._commons import (\n    get_canvas,\n    validate_image_name,\n)\n\nif mpl.__version_info__ >= (3, 8):\n    from matplotlib.ticker import SymmetricalLogLocator\nelse:\n    from ._commons import _MPL38_SymmetricalLogLocator as SymmetricalLogLocator\n\nif TYPE_CHECKING:\n    from typing import Literal\n\n    from matplotlib.axes import Axes\n    from matplotlib.axis import Axis\n    from matplotlib.figure import Figure\n    from matplotlib.transforms import Transform\n\n    class FormatKwargs(TypedDict):\n        style: Literal[\"scientific\"]\n        scilimits: tuple[int, int]\n        useMathText: bool\n\n\nBACKEND_SPECS = {\n    \"macosx\": [\"backend_macosx\", \"FigureCanvasMac\", \"FigureManagerMac\"],\n    \"qt5agg\": [\"backend_qt5agg\", \"FigureCanvasQTAgg\", None],\n    \"qtagg\": [\"backend_qtagg\", \"FigureCanvasQTAgg\", None],\n    \"tkagg\": [\"backend_tkagg\", \"FigureCanvasTkAgg\", None],\n    \"wx\": [\"backend_wx\", \"FigureCanvasWx\", None],\n    \"wxagg\": [\"backend_wxagg\", \"FigureCanvasWxAgg\", None],\n    \"gtk3cairo\": [\n        \"backend_gtk3cairo\",\n        \"FigureCanvasGTK3Cairo\",\n        \"FigureManagerGTK3Cairo\",\n    ],\n    \"gtk3agg\": [\"backend_gtk3agg\", \"FigureCanvasGTK3Agg\", \"FigureManagerGTK3Agg\"],\n    \"webagg\": [\"backend_webagg\", \"FigureCanvasWebAgg\", None],\n    \"nbagg\": [\"backend_nbagg\", \"FigureCanvasNbAgg\", \"FigureManagerNbAgg\"],\n    \"agg\": [\"backend_agg\", \"FigureCanvasAgg\", None],\n}\n\n\nclass CallbackWrapper:\n    def __init__(self, viewer, window_plot, frb, field, font_properties, font_color):\n        self.frb = frb\n        self.data = frb.data_source\n        self._axes = window_plot.axes\n        self._figure = window_plot.figure\n        if len(self._axes.images) > 0:\n            self.raw_image_shape = self._axes.images[0]._A.shape\n            if viewer._has_swapped_axes:\n                # store the original un-transposed shape\n                self.raw_image_shape = self.raw_image_shape[1], self.raw_image_shape[0]\n        if frb.axis is not None:\n            DD = frb.ds.domain_width\n            xax = frb.ds.coordinates.x_axis[frb.axis]\n            yax = frb.ds.coordinates.y_axis[frb.axis]\n            self._period = (DD[xax], DD[yax])\n        self.ds = frb.ds\n        self.xlim = viewer.xlim\n        self.ylim = viewer.ylim\n        self._swap_axes = viewer._has_swapped_axes\n        self._flip_horizontal = viewer._flip_horizontal  # needed for quiver\n        self._flip_vertical = viewer._flip_vertical  # needed for quiver\n        # an important note on _swap_axes: _swap_axes will swap x,y arguments\n        # in callbacks (e.g., plt.plot(x,y) will be plt.plot(y, x). The xlim\n        # and ylim arguments above, and internal callback references to coordinates\n        # are the **unswapped** ranges.\n        self._axes_unit_names = viewer._axes_unit_names\n        if \"OffAxisSlice\" in viewer._plot_type:\n            self._type_name = \"CuttingPlane\"\n        else:\n            self._type_name = viewer._plot_type\n        self.aspect = window_plot._aspect\n        self.font_properties = font_properties\n        self.font_color = font_color\n        self.field = field\n        self._transform = viewer._transform\n\n\nclass PlotMPL:\n    \"\"\"A base class for all yt plots made using matplotlib, that is backend independent.\"\"\"\n\n    def __init__(\n        self,\n        fsize,\n        axrect: tuple[float, float, float, float],\n        *,\n        norm_handler: NormHandler,\n        figure: Optional[\"Figure\"] = None,\n        axes: Optional[\"Axes\"] = None,\n    ):\n        \"\"\"Initialize PlotMPL class\"\"\"\n        from matplotlib.figure import Figure\n\n        self._plot_valid = True\n        if figure is None:\n            if not is_sequence(fsize):\n                fsize = (fsize, fsize)\n            self.figure = Figure(figsize=fsize, frameon=True)\n        else:\n            figure.set_size_inches(fsize)\n            self.figure = figure\n        if axes is None:\n            self._create_axes(axrect)\n        else:\n            axes.clear()\n            axes.set_position(axrect)\n            self.axes = axes\n        self.interactivity = get_interactivity()\n\n        figure_canvas, figure_manager = self._get_canvas_classes()\n        self.canvas = figure_canvas(self.figure)\n        if figure_manager is not None:\n            # with matplotlib >= 3.9, figure_manager should always be not None\n            # see _get_canvas_classes for details.\n            self.manager = figure_manager(self.canvas, 1)\n\n        self.axes.tick_params(\n            which=\"both\", axis=\"both\", direction=\"in\", top=True, right=True\n        )\n\n        self.norm_handler = norm_handler\n\n    def _create_axes(self, axrect: tuple[float, float, float, float]) -> None:\n        self.axes = self.figure.add_axes(axrect)\n\n    def _get_canvas_classes(self):\n        if self.interactivity:\n            key = str(mpl.get_backend())\n        else:\n            key = \"agg\"\n\n        if mpl.__version_info__ >= (3, 9):\n            # once yt has a minimum matplotlib version of 3.9, this branch\n            # can replace the rest of this function and BACKEND_SPECS can\n            # be removed. See https://github.com/yt-project/yt/issues/5138\n            from matplotlib.backends import backend_registry\n\n            mod = backend_registry.load_backend_module(key)\n            return mod.FigureCanvas, mod.FigureManager\n\n        module, fig_canvas, fig_manager = BACKEND_SPECS[key.lower()]\n\n        mod = __import__(\n            \"matplotlib.backends\",\n            globals(),\n            locals(),\n            [module],\n            0,\n        )\n        submod = getattr(mod, module)\n        FigureCanvas = getattr(submod, fig_canvas)\n        if fig_manager is not None:\n            FigureManager = getattr(submod, fig_manager)\n            return FigureCanvas, FigureManager\n\n        return FigureCanvas, None\n\n    def save(self, name, mpl_kwargs=None, canvas=None):\n        \"\"\"Choose backend and save image to disk\"\"\"\n\n        if mpl_kwargs is None:\n            mpl_kwargs = {}\n\n        name = validate_image_name(name)\n        setdefault_mpl_metadata(mpl_kwargs, name)\n\n        try:\n            canvas = get_canvas(self.figure, name)\n        except ValueError:\n            canvas = self.canvas\n\n        mylog.info(\"Saving plot %s\", name)\n        with mpl.style.context(\"yt.default\"):\n            canvas.print_figure(name, **mpl_kwargs)\n        return name\n\n    def show(self):\n        try:\n            self.manager.show()\n        except AttributeError:\n            self.canvas.show()\n\n    def _get_labels(self):\n        ax = self.axes\n        labels = ax.xaxis.get_ticklabels() + ax.yaxis.get_ticklabels()\n        labels += ax.xaxis.get_minorticklabels()\n        labels += ax.yaxis.get_minorticklabels()\n        labels += [\n            ax.title,\n            ax.xaxis.label,\n            ax.yaxis.label,\n            ax.xaxis.get_offset_text(),\n            ax.yaxis.get_offset_text(),\n        ]\n        return labels\n\n    def _set_font_properties(self, font_properties, font_color):\n        for label in self._get_labels():\n            label.set_fontproperties(font_properties)\n            if font_color is not None:\n                label.set_color(font_color)\n\n    def _repr_png_(self):\n        from matplotlib.backends.backend_agg import FigureCanvasAgg\n\n        canvas = FigureCanvasAgg(self.figure)\n        f = BytesIO()\n        with mpl.style.context(\"yt.default\"):\n            canvas.print_figure(f)\n        f.seek(0)\n        return f.read()\n\n\nclass ImagePlotMPL(PlotMPL, ABC):\n    \"\"\"A base class for yt plots made using imshow\"\"\"\n\n    _default_font_size = 18.0\n\n    def __init__(\n        self,\n        fsize=None,\n        axrect=None,\n        caxrect=None,\n        *,\n        norm_handler: NormHandler,\n        colorbar_handler: ColorbarHandler,\n        figure: Optional[\"Figure\"] = None,\n        axes: Optional[\"Axes\"] = None,\n        cax: Optional[\"Axes\"] = None,\n    ):\n        \"\"\"Initialize ImagePlotMPL class object\"\"\"\n\n        self._transform: Transform | None\n        setdefaultattr(self, \"_transform\", None)\n\n        self.colorbar_handler = colorbar_handler\n        _missing_layout_specs = [_ is None for _ in (fsize, axrect, caxrect)]\n\n        if all(_missing_layout_specs):\n            fsize, axrect, caxrect = self._get_best_layout()\n        elif any(_missing_layout_specs):\n            raise TypeError(\n                \"ImagePlotMPL cannot be initialized with partially specified layout.\"\n            )\n\n        super().__init__(\n            fsize, axrect, norm_handler=norm_handler, figure=figure, axes=axes\n        )\n\n        if cax is None:\n            self.cax = self.figure.add_axes(caxrect)\n        else:\n            cax.clear()\n            cax.set_position(caxrect)\n            self.cax = cax\n\n    def _setup_layout_constraints(\n        self, figure_size: tuple[float, float] | float, fontsize: float\n    ):\n        # Setup base layout attributes\n        # derived classes need to call this before super().__init__\n        # but they are free to do other stuff in between\n\n        if isinstance(figure_size, tuple):\n            assert len(figure_size) == 2\n            assert all(isinstance(_, float) for _ in figure_size)\n            self._figure_size = figure_size\n        else:\n            assert isinstance(figure_size, float)\n            self._figure_size = (figure_size, figure_size)\n\n        self._draw_axes = True\n        fontscale = float(fontsize) / self.__class__._default_font_size\n        if fontscale < 1.0:\n            fontscale = np.sqrt(fontscale)\n\n        self._cb_size = 0.0375 * self._figure_size[0]\n        self._ax_text_size = [1.2 * fontscale, 0.9 * fontscale]\n        self._top_buff_size = 0.30 * fontscale\n        self._aspect = 1.0\n\n    def _reset_layout(self) -> None:\n        size, axrect, caxrect = self._get_best_layout()\n        self.axes.set_position(axrect)\n        self.cax.set_position(caxrect)\n        self.figure.set_size_inches(*size)\n\n    def _init_image(self, data, extent, aspect, *, alpha: AlphaT = None):\n        \"\"\"Store output of imshow in image variable\"\"\"\n\n        norm = self.norm_handler.get_norm(data)\n        extent = [float(e) for e in extent]\n\n        if self._transform is None:\n            # sets the transform to be an ax.TransData object, where the\n            # coordinate system of the data is controlled by the xlim and ylim\n            # of the data.\n            transform = self.axes.transData\n        else:\n            transform = self._transform\n\n        self._validate_axes_extent(extent, transform)\n\n        self.image = self.axes.imshow(\n            data.to_ndarray(),\n            origin=\"lower\",\n            extent=extent,\n            norm=norm,\n            aspect=aspect,\n            cmap=self.colorbar_handler.cmap,\n            interpolation=\"nearest\",\n            interpolation_stage=\"data\",\n            transform=transform,\n            alpha=alpha,\n        )\n        self._set_axes()\n\n    def _set_axes(self) -> None:\n        fmt_kwargs: FormatKwargs = {\n            \"style\": \"scientific\",\n            \"scilimits\": (-2, 3),\n            \"useMathText\": True,\n        }\n        self.image.axes.ticklabel_format(**fmt_kwargs)\n        self.image.axes.set_facecolor(self.colorbar_handler.background_color)\n\n        self.cax.tick_params(which=\"both\", direction=\"in\")\n\n        # For creating a multipanel plot by ImageGrid,\n        # we may need the location keyword\n        cb_location = getattr(self.cax, \"orientation\", None)\n        self.cb = self.figure.colorbar(self.image, self.cax, location=cb_location)\n        cb_axis: Axis\n        if self.cb.orientation == \"vertical\":\n            cb_axis = self.cb.ax.yaxis\n        else:\n            cb_axis = self.cb.ax.xaxis\n\n        cb_scale = cb_axis.get_scale()\n        if cb_scale == \"symlog\":\n            trf = cb_axis.get_transform()\n            if not isinstance(trf, SymmetricalLogTransform):\n                raise RuntimeError\n            cb_axis.set_major_locator(SymmetricalLogLocator(trf))\n            cb_axis.set_major_formatter(\n                LogFormatterMathtext(linthresh=trf.linthresh, base=trf.base)\n            )\n\n        if cb_scale not in (\"log\", \"symlog\"):\n            self.cb.ax.ticklabel_format(**fmt_kwargs)\n\n        if self.colorbar_handler.draw_minorticks and cb_scale == \"symlog\":\n            # no minor ticks are drawn by default in symlog, as of matplotlib 3.7.1\n            # see https://github.com/matplotlib/matplotlib/issues/25994\n            trf = cb_axis.get_transform()\n            if not isinstance(trf, SymmetricalLogTransform):\n                raise RuntimeError\n            if float(trf.base).is_integer():\n                locator = SymmetricalLogLocator(trf, subs=list(range(1, int(trf.base))))\n                cb_axis.set_minor_locator(locator)\n        elif self.colorbar_handler.draw_minorticks:\n            self.cb.minorticks_on()\n        else:\n            self.cb.minorticks_off()\n\n    def _validate_axes_extent(self, extent, transform):\n        # if the axes are cartopy GeoAxes, this checks that the axes extent\n        # is properly set.\n\n        if \"cartopy\" not in sys.modules:\n            # cartopy isn't already loaded, nothing to do here\n            return\n\n        from cartopy.mpl.geoaxes import GeoAxes\n\n        if isinstance(self.axes, GeoAxes):\n            # some projections have trouble when passing extents at or near the\n            # limits. So we only set_extent when the plot is a subset of the\n            # globe, within the tolerance of the transform.\n\n            # note that `set_extent` here is setting the extent of the axes.\n            # still need to pass the extent arg to imshow in order to\n            # ensure that it is properly scaled. also note that set_extent\n            # expects values in the coordinates of the transform: it will\n            # calculate the coordinates in the projection.\n            global_extent = transform.x_limits + transform.y_limits\n            thresh = transform.threshold\n            if all(\n                abs(extent[ie]) < (abs(global_extent[ie]) - thresh) for ie in range(4)\n            ):\n                self.axes.set_extent(extent, crs=transform)\n\n    def _get_best_layout(self):\n        # this method is called in ImagePlotMPL.__init__\n        # required attributes\n        # - self._figure_size: Union[float, Tuple[float, float]]\n        # - self._aspect: float\n        # - self._ax_text_size: Tuple[float, float]\n        # - self._draw_axes: bool\n        # - self.colorbar_handler: ColorbarHandler\n\n        # optional attributes\n        # - self._unit_aspect: float\n\n        # Ensure the figure size along the long axis is always equal to _figure_size\n        unit_aspect = getattr(self, \"_unit_aspect\", 1)\n        if is_sequence(self._figure_size):\n            x_fig_size, y_fig_size = self._figure_size\n            y_fig_size *= unit_aspect\n        else:\n            x_fig_size = y_fig_size = self._figure_size\n            scaling = self._aspect / unit_aspect\n            if scaling < 1:\n                x_fig_size *= scaling\n            else:\n                y_fig_size /= scaling\n\n        if self.colorbar_handler.draw_cbar:\n            cb_size = self._cb_size\n            cb_text_size = self._ax_text_size[1] + 0.45\n        else:\n            cb_size = x_fig_size * 0.04\n            cb_text_size = 0.0\n\n        if self._draw_axes:\n            x_axis_size = self._ax_text_size[0]\n            y_axis_size = self._ax_text_size[1]\n        else:\n            x_axis_size = x_fig_size * 0.04\n            y_axis_size = y_fig_size * 0.04\n\n        top_buff_size = self._top_buff_size\n\n        if not self._draw_axes and not self.colorbar_handler.draw_cbar:\n            x_axis_size = 0.0\n            y_axis_size = 0.0\n            cb_size = 0.0\n            cb_text_size = 0.0\n            top_buff_size = 0.0\n\n        xbins = np.array([x_axis_size, x_fig_size, cb_size, cb_text_size])\n        ybins = np.array([y_axis_size, y_fig_size, top_buff_size])\n\n        size = [xbins.sum(), ybins.sum()]\n\n        x_frac_widths = xbins / size[0]\n        y_frac_widths = ybins / size[1]\n\n        # axrect is the rectangle defining the area of the\n        # axis object of the plot.  Its range goes from 0 to 1 in\n        # x and y directions.  The first two values are the x,y\n        # start values of the axis object (lower left corner), and the\n        # second two values are the size of the axis object.  To get\n        # the upper right corner, add the first x,y to the second x,y.\n        axrect = (\n            x_frac_widths[0],\n            y_frac_widths[0],\n            x_frac_widths[1],\n            y_frac_widths[1],\n        )\n\n        # caxrect is the rectangle defining the area of the colorbar\n        # axis object of the plot.  It is defined just as the axrect\n        # tuple is.\n        caxrect = (\n            x_frac_widths[0] + x_frac_widths[1],\n            y_frac_widths[0],\n            x_frac_widths[2],\n            y_frac_widths[1],\n        )\n\n        return size, axrect, caxrect\n\n    def _toggle_axes(self, choice, draw_frame=None):\n        \"\"\"\n        Turn on/off displaying the axis ticks and labels for a plot.\n\n        Parameters\n        ----------\n        choice : boolean\n            If True, set the axes to be drawn. If False, set the axes to not be\n            drawn.\n        \"\"\"\n        self._draw_axes = choice\n        self._draw_frame = draw_frame\n        if draw_frame is None:\n            draw_frame = choice\n        if self.colorbar_handler.has_background_color and not draw_frame:\n            # workaround matplotlib's behaviour\n            # last checked with Matplotlib 3.5\n            warnings.warn(\n                f\"Previously set background color {self.colorbar_handler.background_color} \"\n                \"has no effect. Pass `draw_frame=True` if you wish to preserve background color.\",\n                stacklevel=4,\n            )\n        self.axes.set_frame_on(draw_frame)\n        self.axes.get_xaxis().set_visible(choice)\n        self.axes.get_yaxis().set_visible(choice)\n        self._reset_layout()\n\n    def _toggle_colorbar(self, choice: bool):\n        \"\"\"\n        Turn on/off displaying the colorbar for a plot\n\n        choice = True or False\n        \"\"\"\n        self.colorbar_handler.draw_cbar = choice\n        self.cax.set_visible(choice)\n        size, axrect, caxrect = self._get_best_layout()\n        self.axes.set_position(axrect)\n        self.cax.set_position(caxrect)\n        self.figure.set_size_inches(*size)\n\n    def _get_labels(self):\n        labels = super()._get_labels()\n        if getattr(self.cb, \"orientation\", \"vertical\") == \"horizontal\":\n            cbaxis = self.cb.ax.xaxis\n        else:\n            cbaxis = self.cb.ax.yaxis\n        labels += cbaxis.get_ticklabels()\n        labels += [cbaxis.label, cbaxis.get_offset_text()]\n        return labels\n\n    def hide_axes(self, *, draw_frame=None):\n        \"\"\"\n        Hide the axes for a plot including ticks and labels\n        \"\"\"\n        self._toggle_axes(False, draw_frame)\n        return self\n\n    def show_axes(self):\n        \"\"\"\n        Show the axes for a plot including ticks and labels\n        \"\"\"\n        self._toggle_axes(True)\n        return self\n\n    def hide_colorbar(self):\n        \"\"\"\n        Hide the colorbar for a plot including ticks and labels\n        \"\"\"\n        self._toggle_colorbar(False)\n        return self\n\n    def show_colorbar(self):\n        \"\"\"\n        Show the colorbar for a plot including ticks and labels\n        \"\"\"\n        self._toggle_colorbar(True)\n        return self\n\n\ndef get_multi_plot(nx, ny, colorbar=\"vertical\", bw=4, dpi=300, cbar_padding=0.4):\n    r\"\"\"Construct a multiple axes plot object, with or without a colorbar, into\n    which multiple plots may be inserted.\n\n    This will create a set of :class:`matplotlib.axes.Axes`, all lined up into\n    a grid, which are then returned to the user and which can be used to plot\n    multiple plots on a single figure.\n\n    Parameters\n    ----------\n    nx : int\n        Number of axes to create along the x-direction\n    ny : int\n        Number of axes to create along the y-direction\n    colorbar : {'vertical', 'horizontal', None}, optional\n        Should Axes objects for colorbars be allocated, and if so, should they\n        correspond to the horizontal or vertical set of axes?\n    bw : number\n        The base height/width of an axes object inside the figure, in inches\n    dpi : number\n        The dots per inch fed into the Figure instantiation\n\n    Returns\n    -------\n    fig : :class:`matplotlib.figure.Figure`\n        The figure created inside which the axes reside\n    tr : list of list of :class:`matplotlib.axes.Axes` objects\n        This is a list, where the inner list is along the x-axis and the outer\n        is along the y-axis\n    cbars : list of :class:`matplotlib.axes.Axes` objects\n        Each of these is an axes onto which a colorbar can be placed.\n\n    Notes\n    -----\n    This is a simple implementation for a common use case.  Viewing the source\n    can be instructive, and is encouraged to see how to generate more\n    complicated or more specific sets of multiplots for your own purposes.\n    \"\"\"\n    from matplotlib.backends.backend_agg import FigureCanvasAgg\n\n    hf, wf = 1.0 / ny, 1.0 / nx\n    fudge_x = fudge_y = 1.0\n    if colorbar is None:\n        fudge_x = fudge_y = 1.0\n    elif colorbar.lower() == \"vertical\":\n        fudge_x = nx / (cbar_padding + nx)\n        fudge_y = 1.0\n    elif colorbar.lower() == \"horizontal\":\n        fudge_x = 1.0\n        fudge_y = ny / (cbar_padding + ny)\n    fig = mpl.figure.Figure((bw * nx / fudge_x, bw * ny / fudge_y), dpi=dpi)\n\n    fig.set_canvas(FigureCanvasAgg(fig))\n    fig.subplots_adjust(\n        wspace=0.0, hspace=0.0, top=1.0, bottom=0.0, left=0.0, right=1.0\n    )\n    tr = []\n    for j in range(ny):\n        tr.append([])\n        for i in range(nx):\n            left = i * wf * fudge_x\n            bottom = fudge_y * (1.0 - (j + 1) * hf) + (1.0 - fudge_y)\n            ax = fig.add_axes([left, bottom, wf * fudge_x, hf * fudge_y])\n            tr[-1].append(ax)\n    cbars = []\n    if colorbar is None:\n        pass\n    elif colorbar.lower() == \"horizontal\":\n        for i in range(nx):\n            # left, bottom, width, height\n            # Here we want 0.10 on each side of the colorbar\n            # We want it to be 0.05 tall\n            # And we want a buffer of 0.15\n            ax = fig.add_axes(\n                [\n                    wf * (i + 0.10) * fudge_x,\n                    hf * fudge_y * 0.20,\n                    wf * (1 - 0.20) * fudge_x,\n                    hf * fudge_y * 0.05,\n                ]\n            )\n            cbars.append(ax)\n    elif colorbar.lower() == \"vertical\":\n        for j in range(ny):\n            ax = fig.add_axes(\n                [\n                    wf * (nx + 0.05) * fudge_x,\n                    hf * fudge_y * (ny - (j + 0.95)),\n                    wf * fudge_x * 0.05,\n                    hf * fudge_y * 0.90,\n                ]\n            )\n            ax.clear()\n            cbars.append(ax)\n    return fig, tr, cbars\n"
  },
  {
    "path": "yt/visualization/color_maps.py",
    "content": "import cmyt  # noqa: F401\nimport matplotlib as mpl\nimport numpy as np\nfrom matplotlib.colors import LinearSegmentedColormap\n\nfrom yt.funcs import get_brewer_cmap\nfrom yt.utilities.logger import ytLogger as mylog\n\nfrom . import _colormap_data as _cm\n\nyt_colormaps = {}\n\n\ndef add_colormap(name, cdict):\n    \"\"\"\n    Adds a colormap to the colormaps available in yt for this session\n    \"\"\"\n    # Note: this function modifies the global variable 'yt_colormaps'\n    yt_colormaps[name] = LinearSegmentedColormap(name, cdict, 256)\n    mpl.colormaps.register(yt_colormaps[name])\n\n\n# YTEP-0040 backward compatibility layer\n# yt colormaps used to be defined here, but were migrated to an external\n# package, cmyt. In the process, 5 of them were renamed. We register them again here\n# under their historical names to preserves backwards compatibility.\n\n_HISTORICAL_ALIASES = {\n    \"arbre\": \"cmyt.arbre\",\n    \"algae\": \"cmyt.algae\",\n    \"bds_highcontrast\": \"cmyt.algae\",\n    \"octarine\": \"cmyt.octarine\",\n    \"dusk\": \"cmyt.dusk\",\n    \"kamae\": \"cmyt.pastel\",\n    \"kelp\": \"cmyt.kelp\",\n    \"black_blueish\": \"cmyt.pixel_blue\",\n    \"black_green\": \"cmyt.pixel_green\",\n    \"purple_mm\": \"cmyt.xray\",\n}\n\n\ndef register_yt_colormaps_from_cmyt():\n    \"\"\"\n    For backwards compatibility, register yt colormaps without the \"cmyt.\"\n    prefix, but do it in a collision-safe way.\n    \"\"\"\n\n    for hist_name, alias in _HISTORICAL_ALIASES.items():\n        # note that mpl.colormaps.__getitem__ returns *copies*\n        cmap = mpl.colormaps[alias]\n        cmap.name = hist_name\n        mpl.colormaps.register(cmap)\n        mpl.colormaps.register(cmap.reversed())\n\n\nregister_yt_colormaps_from_cmyt()\n\n# Add colormaps in _colormap_data.py that weren't defined here\n_vs = np.linspace(0, 1, 256)\nfor k, v in list(_cm.color_map_luts.items()):\n    if k in yt_colormaps:\n        continue\n    cdict = {\n        \"red\": np.transpose([_vs, v[0], v[0]]),\n        \"green\": np.transpose([_vs, v[1], v[1]]),\n        \"blue\": np.transpose([_vs, v[2], v[2]]),\n    }\n    try:\n        add_colormap(k, cdict)\n    except ValueError:\n        # expected if another map with identical name was already registered\n        mylog.warning(\"cannot register colormap '%s' (naming collision)\", k)\n\n\ndef get_colormap_lut(cmap_id: tuple[str, str] | str):\n    # \"lut\" stands for \"lookup table\". This function provides a consistent and\n    # reusable accessor to a hidden (and by default, uninitialized) attribute\n    # (`_lut`) in registered colormaps, from matplotlib or palettable.\n    # colormap \"lookup tables\" are RGBA arrays in matplotlib,\n    # and contain sufficient data to reconstruct the colormaps entirely.\n    # This exists mostly for historical reasons, hence the custom output format.\n    # It isn't meant as part of yt's public api.\n\n    if isinstance(cmap_id, tuple) and len(cmap_id) == 2:\n        cmap = get_brewer_cmap(cmap_id)\n    elif isinstance(cmap_id, str):\n        cmap = mpl.colormaps[cmap_id]\n    else:\n        raise TypeError(\n            \"Expected a string or a 2-tuple of strings as a colormap id. \"\n            f\"Received: {cmap_id}\"\n        )\n    if not cmap._isinit:\n        cmap._init()\n    r = cmap._lut[:-3, 0]\n    g = cmap._lut[:-3, 1]\n    b = cmap._lut[:-3, 2]\n    a = np.ones(b.shape)\n    return [r, g, b, a]\n\n\ndef show_colormaps(subset=\"all\", filename=None):\n    \"\"\"\n    Displays the colormaps available to yt.  Note, most functions can use\n    both the matplotlib and the native yt colormaps; however, there are\n    some special functions existing within image_writer.py (e.g. write_image()\n    write_bitmap(), etc.), which cannot access the matplotlib\n    colormaps.\n\n    In addition to the colormaps listed, one can access the reverse of each\n    colormap by appending a \"_r\" to any map.\n\n    If you wish to only see certain colormaps, include them in the cmap_list\n    attribute.\n\n    Parameters\n    ----------\n\n    subset : string, or list of strings, optional\n\n        valid values : \"all\", \"yt_native\", or list of cmap names\n        default : \"all\"\n\n        As mentioned above, a few functions can only access yt_native\n        colormaps.  To display only the yt_native colormaps, set this\n        to \"yt_native\".\n\n        If you wish to only see a few colormaps side by side, you can\n        include them as a list of colormap names.\n        Example: ['cmyt.algae', 'gist_stern', 'cmyt.kamae', 'nipy_spectral']\n\n    filename : string, opt\n\n        default: None\n\n        If filename is set, then it will save the colormaps to an output\n        file.  If it is not set, it will \"show\" the result interactively.\n    \"\"\"\n    from matplotlib import pyplot as plt\n\n    a = np.outer(np.arange(0, 1, 0.01), np.ones(10))\n    if subset == \"all\":\n        maps = [\n            m\n            for m in plt.colormaps()\n            if (not m.startswith(\"idl\")) & (not m.endswith(\"_r\"))\n        ]\n    elif subset == \"yt_native\":\n        maps = [\n            m\n            for m in _cm.color_map_luts\n            if (not m.startswith(\"idl\")) & (not m.endswith(\"_r\"))\n        ]\n    else:\n        try:\n            maps = [m for m in plt.colormaps() if m in subset]\n            if len(maps) == 0:\n                raise AttributeError\n        except AttributeError as e:\n            raise AttributeError(\n                \"show_colormaps requires subset attribute \"\n                \"to be 'all', 'yt_native', or a list of \"\n                \"valid colormap names.\"\n            ) from e\n\n    maps = sorted(set(maps))\n    # scale the image size by the number of cmaps\n    plt.figure(figsize=(2.0 * len(maps) / 10.0, 6))\n    plt.subplots_adjust(top=0.7, bottom=0.05, left=0.01, right=0.99)\n    l = len(maps) + 1\n    for i, m in enumerate(maps):\n        plt.subplot(1, l, i + 1)\n        plt.axis(\"off\")\n        plt.imshow(a, aspect=\"auto\", cmap=mpl.colormaps[m], origin=\"lower\")\n        plt.title(m, rotation=90, fontsize=10, verticalalignment=\"bottom\")\n    if filename is not None:\n        plt.savefig(filename, dpi=100, facecolor=\"gray\")\n    else:\n        plt.show()\n\n\ndef make_colormap(ctuple_list, name=None, interpolate=True):\n    \"\"\"\n    This generates a custom colormap based on the colors and spacings you\n    provide.  Enter a ctuple_list, which consists of tuples of (color, spacing)\n    to return a colormap appropriate for use in yt.  If you specify a\n    name, it will automatically be added to the current session as a valid\n    colormap.\n\n    Output colormap is in the format yt expects for adding a colormap to the\n    current session: a dictionary with the appropriate RGB channels each\n    consisting of a 256x3 array :\n    First number is the number at which we are defining a color breakpoint\n    Second number is the (0..1) number to interpolate to when coming *from below*\n    Third number is the (0..1) number to interpolate to when coming *from above*\n\n    Parameters\n    ----------\n\n    ctuple_list: list of (color, float) tuples\n        The ctuple_list consists of pairs of (color, interval) tuples\n        identifying the colors to use in the colormap and the intervals\n        they take to change to the next color in the list.  A color can\n        either be a string of the name of a color, or it can be an array\n        of 3 floats, each representing the intensity of R, G, and B on\n        a scale of 0 to 1.  Valid color names and their equivalent\n        arrays are listed below.\n\n        Any interval can be given for the different color tuples, and\n        the total of all the intervals will be scaled to the 256 output\n        elements.\n\n        If a ctuple_list ends with a color and a non-zero interval,\n        a white 0-interval would be added to the end to finish the\n        interpolation.  To avoid finishing with white, specify your own\n        zero-interval color at the end.\n\n    name: string, optional\n        If you wish this colormap to be added as a valid colormap to the\n        current session, specify a name here.  Default: None\n\n    interpolate: boolean\n        Designates whether or not the colormap will interpolate between\n        the colors provided or just give solid colors across the intervals.\n        Default: True\n\n    Preset Color Options\n    --------------------\n\n    'white' : np.array([255, 255, 255 ])/255.\n    'gray' : np.array([130, 130, 130])/255.\n    'dgray' : np.array([80, 80, 80])/255.\n    'black' : np.array([0, 0, 0])/255.\n    'blue' : np.array([0, 0, 255])/255.\n    'dblue' : np.array([0, 0, 160])/255.\n    'purple' : np.array([100, 0, 200])/255.\n    'dpurple' : np.array([66, 0, 133])/255.\n    'dred' : np.array([160, 0, 0])/255.\n    'red' : np.array([255, 0, 0])/255.\n    'orange' : np.array([255, 128, 0])/255.\n    'dorange' : np.array([200,100, 0])/255.\n    'yellow' : np.array([255, 255, 0])/255.\n    'dyellow' : np.array([200, 200, 0])/255.\n    'green' : np.array([0, 255, 0])/255.\n    'dgreen' : np.array([0, 160, 0])/255.\n\n    Examples\n    --------\n\n    To obtain a colormap that starts at black with equal intervals in green,\n    blue, red, yellow in that order and interpolation between those colors.\n    (In reality, it starts at black, takes an interval of 10 to interpolate to\n    green, then an interval of 10 to interpolate to blue, then an interval of\n    10 to interpolate to red.)\n\n    >>> cm = make_colormap([(\"black\", 10), (\"green\", 10), (\"blue\", 10), (\"red\", 0)])\n\n    To add a colormap that has five equal blocks of solid major colors to\n    the current session as \"steps\":\n\n    >>> make_colormap(\n    ...     [(\"red\", 10), (\"orange\", 10), (\"yellow\", 10), (\"green\", 10), (\"blue\", 10)],\n    ...     name=\"steps\",\n    ...     interpolate=False,\n    ... )\n\n    To add a colormap that looks like the French flag (i.e. equal bands of\n    blue, white, and red) using your own RGB keys, then to display it:\n\n    >>> make_colormap(\n    ...     [([0, 0, 1], 10), ([1, 1, 1], 10), ([1, 0, 0], 10)],\n    ...     name=\"french_flag\",\n    ...     interpolate=False,\n    ... )\n    >>> show_colormaps([\"french_flag\"])\n\n    \"\"\"\n    # aliases for different colors\n    color_dict = {\n        \"white\": np.array([255, 255, 255]) / 255.0,\n        \"gray\": np.array([130, 130, 130]) / 255.0,\n        \"dgray\": np.array([80, 80, 80]) / 255.0,\n        \"black\": np.array([0, 0, 0]) / 255.0,\n        \"blue\": np.array([0, 0, 255]) / 255.0,\n        \"dblue\": np.array([0, 0, 160]) / 255.0,\n        \"purple\": np.array([100, 0, 200]) / 255.0,\n        \"dpurple\": np.array([66, 0, 133]) / 255.0,\n        \"dred\": np.array([160, 0, 0]) / 255.0,\n        \"red\": np.array([255, 0, 0]) / 255.0,\n        \"orange\": np.array([255, 128, 0]) / 255.0,\n        \"dorange\": np.array([200, 100, 0]) / 255.0,\n        \"yellow\": np.array([255, 255, 0]) / 255.0,\n        \"dyellow\": np.array([200, 200, 0]) / 255.0,\n        \"green\": np.array([0, 255, 0]) / 255.0,\n        \"dgreen\": np.array([0, 160, 0]) / 255.0,\n    }\n\n    cmap = np.zeros((256, 3))\n\n    # If the user provides a list with a non-zero final interval, it\n    # doesn't make sense because you have an interval but no final\n    # color to which it interpolates.  So provide a 0-length white final\n    # interval to end the previous interval in white.\n    if ctuple_list[-1][1] != 0:\n        ctuple_list.append((\"white\", 0))\n\n    # Figure out how many intervals there are total.\n    rolling_index = 0\n    for i, (color, interval) in enumerate(ctuple_list):\n        if isinstance(color, str):\n            ctuple_list[i] = (color_dict[color], interval)\n        rolling_index += interval\n    scale = 256.0 / rolling_index\n    n = len(ctuple_list)\n\n    # Step through each ctuple and interpolate from one color to the\n    # next over the interval provided\n    rolling_index = 0\n    for i in range(n - 1):\n        color, interval = ctuple_list[i]\n        interval *= scale\n        next_index = rolling_index + interval\n        next_color, next_interval = ctuple_list[i + 1]\n\n        if not interpolate:\n            next_color = color\n\n        # Interpolate the R, G, and B channels from one color to the next\n        # Use np.round to make sure you're on a discrete index\n        interval = int(np.round(next_index) - np.round(rolling_index))\n        for j in np.arange(3):\n            cmap[int(np.rint(rolling_index)) : int(np.rint(next_index)), j] = (\n                np.linspace(color[j], next_color[j], num=interval)\n            )\n\n        rolling_index = next_index\n\n    # Return a dictionary with the appropriate RGB channels each consisting of\n    # a 256x3 array in the format that is expected by add_colormap() to add a\n    # colormap to the session.\n\n    # The format is as follows:\n    #   First number is the number at which we are defining a color breakpoint\n    #   Second number is the (0..1) number to interpolate to when coming *from below*\n    #   Third number is the (0..1) number to interpolate to when coming *from above*\n    _vs = np.linspace(0, 1, 256)\n    cdict = {\n        \"red\": np.transpose([_vs, cmap[:, 0], cmap[:, 0]]),\n        \"green\": np.transpose([_vs, cmap[:, 1], cmap[:, 1]]),\n        \"blue\": np.transpose([_vs, cmap[:, 2], cmap[:, 2]]),\n    }\n\n    if name is not None:\n        add_colormap(name, cdict)\n\n    return cdict\n"
  },
  {
    "path": "yt/visualization/eps_writer.py",
    "content": "import os\n\nimport numpy as np\nimport pyx\nfrom matplotlib import pyplot as plt\nfrom matplotlib.colors import LogNorm, Normalize\n\nfrom yt.config import ytcfg\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.units.yt_array import YTQuantity\nfrom yt.utilities.logger import ytLogger as mylog\n\nfrom .plot_window import PlotWindow\nfrom .profile_plotter import PhasePlot, ProfilePlot\n\n\ndef convert_frac_to_tex(string):\n    frac_pos = string.find(r\"\\frac\")\n    result = string[frac_pos + 5 :]\n    level = [0] * len(result)\n    clevel = 0\n    for i in range(len(result)):\n        if result[i] == \"{\":\n            clevel += 1\n        elif result[i] == \"}\":\n            clevel -= 1\n        level[i] = clevel\n    div_pos = level.index(0)\n    end_pos = level.index(0, div_pos + 1)\n    result = (\n        r\"${\"\n        + result[: div_pos + 1]\n        + r\"\\over\"\n        + result[div_pos + 1 : end_pos]\n        + r\"}$\"\n        + result[end_pos:]\n    )\n    result = result.replace(r\"\\ \", r\"\\;\")\n    return result\n\n\ndef pyxize_label(string):\n    frac_pos = string.find(r\"\\frac\")\n    if frac_pos >= 0:\n        pre = string[:frac_pos]\n        result = pre + convert_frac_to_tex(string)\n    else:\n        result = string\n    result = result.replace(\"$\", \"\")\n    result = r\"$\" + result + r\"$\"\n    return result\n\n\nclass DualEPS:\n    def __init__(self, figsize=(12, 12)):\n        r\"\"\"Initializes the DualEPS class to which we can progressively add layers\n        of vector graphics and compressed bitmaps.\n\n        Parameters\n        ----------\n        figsize : tuple of floats\n            The width and height of a single figure in centimeters.\n        \"\"\"\n        pyx.unit.set(xscale=1.4)\n        self.figsize = figsize\n        self.canvas = None\n        self.colormaps = None\n        self.field = None\n        self.axes_drawn = False\n\n    def hello_world(self):\n        r\"\"\"A simple test.\"\"\"\n        if self.canvas is None:\n            self.canvas = pyx.canvas.canvas()\n        p = pyx.path.line(0, 0, 1, 1)\n        self.canvas.stroke(p)\n        self.canvas.text(0, 0, \"Hello world.\")\n\n    # =============================================================================\n\n    def return_field(self, plot):\n        if isinstance(plot, (PlotWindow, PhasePlot)):\n            return list(plot.plots.keys())[0]\n        else:\n            return None\n\n    # =============================================================================\n\n    def axis_box(\n        self,\n        xrange=(0, 1),\n        yrange=(0, 1),\n        xlabel=\"\",\n        ylabel=\"\",\n        xlog=False,\n        ylog=False,\n        xdata=None,\n        ydata=None,\n        tickcolor=None,\n        bare_axes=False,\n        pos=(0, 0),\n        xaxis_side=0,\n        yaxis_side=0,\n        size=None,\n    ):\n        r\"\"\"Draws an axis box in the figure.\n\n        Parameters\n        ----------\n        xrange : tuple of floats\n            The min and max of the x-axis\n        yrange : tuple of floats\n            The min and max of the y-axis\n        xlabel : string\n            Label for the x-axis\n        ylabel : string\n            Label for the y-axis\n        xlog : boolean\n            Flag to use a logarithmic x-axis\n        ylog : boolean\n            Flag to use a logarithmic y-axis\n        tickcolor : `pyx.color.*.*`\n            Color for the tickmarks.  Example: pyx.color.cmyk.black\n        bare_axes : boolean\n            Set to true to have no annotations or tick marks on all of the\n            axes.\n        pos : tuple of floats\n            (x,y) position in centimeters of the origin in the figure\n        xaxis_side : integer\n            Set to 0 for the x-axis annotations to be on the left.  Set\n            to 1 to print them on the right side.\n        yaxis_side : integer\n            Set to 0 for the y-axis annotations to be on the bottom.  Set\n            to 1 to print them on the top.\n        size : tuple of floats\n            Size of axis box in units of figsize\n\n        Examples\n        --------\n        >>> d = DualEPS()\n        >>> d.axis_box(xrange=(0, 100), yrange=(1e-3, 1), ylog=True)\n        >>> d.save_fig()\n        \"\"\"\n\n        if isinstance(xrange[0], YTQuantity):\n            xrange = (xrange[0].value, xrange[1].value)\n        if isinstance(yrange[0], YTQuantity):\n            yrange = (yrange[0].value, yrange[1].value)\n        if tickcolor is None:\n            c1 = pyx.graph.axis.painter.regular(tickattrs=[pyx.color.cmyk.black])\n            c2 = pyx.graph.axis.painter.regular(\n                tickattrs=[pyx.color.cmyk.black], labelattrs=None\n            )\n        else:\n            c1 = pyx.graph.axis.painter.regular(tickattrs=[tickcolor])\n            c2 = pyx.graph.axis.painter.regular(tickattrs=[tickcolor], labelattrs=None)\n\n        if size is None:\n            psize = self.figsize\n        else:\n            psize = (size[0] * self.figsize[0], size[1] * self.figsize[1])\n\n        xticklabels = True\n        yticklabels = True\n        if xaxis_side == 0:\n            xleftlabel = xlabel\n            xrightlabel = \"\"\n            c1x = c1\n            c2x = c2\n        elif xaxis_side == 1:\n            xleftlabel = \"\"\n            xrightlabel = xlabel\n            c1x = c2\n            c2x = c1\n        else:\n            xticklabels = False\n            xleftlabel = \"\"\n            xrightlabel = \"\"\n            c1x = c1\n            c2x = c2\n        if yaxis_side == 0:\n            yleftlabel = ylabel\n            yrightlabel = \"\"\n            c1y = c1\n            c2y = c2\n        elif yaxis_side == 1:\n            yleftlabel = \"\"\n            yrightlabel = ylabel\n            c1y = c2\n            c2y = c1\n        else:\n            yticklabels = False\n            yleftlabel = \"\"\n            yrightlabel = \"\"\n            c1y = c1\n            c2y = c2\n\n        if xlog:\n            if xticklabels:\n                xaxis = pyx.graph.axis.log(\n                    min=xrange[0], max=xrange[1], title=xleftlabel, painter=c1x\n                )\n                xaxis2 = pyx.graph.axis.log(\n                    min=xrange[0], max=xrange[1], title=xrightlabel, painter=c2x\n                )\n            else:\n                xaxis = pyx.graph.axis.log(\n                    min=xrange[0],\n                    max=xrange[1],\n                    title=xleftlabel,\n                    painter=c1x,\n                    parter=None,\n                )\n                xaxis2 = pyx.graph.axis.log(\n                    min=xrange[0],\n                    max=xrange[1],\n                    title=xrightlabel,\n                    painter=c2x,\n                    parter=None,\n                )\n        else:\n            if xticklabels:\n                xaxis = pyx.graph.axis.lin(\n                    min=xrange[0], max=xrange[1], title=xleftlabel, painter=c1x\n                )\n                xaxis2 = pyx.graph.axis.lin(\n                    min=xrange[0], max=xrange[1], title=xrightlabel, painter=c2x\n                )\n            else:\n                xaxis = pyx.graph.axis.lin(\n                    min=xrange[0],\n                    max=xrange[1],\n                    title=xleftlabel,\n                    painter=c1x,\n                    parter=None,\n                )\n                xaxis2 = pyx.graph.axis.lin(\n                    min=xrange[0],\n                    max=xrange[1],\n                    title=xrightlabel,\n                    painter=c2x,\n                    parter=None,\n                )\n        if ylog:\n            if yticklabels:\n                yaxis = pyx.graph.axis.log(\n                    min=yrange[0], max=yrange[1], title=yleftlabel, painter=c1y\n                )\n                yaxis2 = pyx.graph.axis.log(\n                    min=yrange[0], max=yrange[1], title=yrightlabel, painter=c2y\n                )\n            else:\n                yaxis = pyx.graph.axis.log(\n                    min=yrange[0],\n                    max=yrange[1],\n                    title=yleftlabel,\n                    painter=c1y,\n                    parter=None,\n                )\n                yaxis2 = pyx.graph.axis.log(\n                    min=yrange[0],\n                    max=yrange[1],\n                    title=yrightlabel,\n                    painter=c2y,\n                    parter=None,\n                )\n        else:\n            if yticklabels:\n                yaxis = pyx.graph.axis.lin(\n                    min=yrange[0], max=yrange[1], title=yleftlabel, painter=c1y\n                )\n                yaxis2 = pyx.graph.axis.lin(\n                    min=yrange[0], max=yrange[1], title=yrightlabel, painter=c2y\n                )\n            else:\n                yaxis = pyx.graph.axis.lin(\n                    min=yrange[0],\n                    max=yrange[1],\n                    title=yleftlabel,\n                    painter=c1y,\n                    parter=None,\n                )\n                yaxis2 = pyx.graph.axis.lin(\n                    min=yrange[0],\n                    max=yrange[1],\n                    title=yrightlabel,\n                    painter=c2y,\n                    parter=None,\n                )\n\n        if bare_axes:\n            if ylog:\n                yaxis = pyx.graph.axis.log(\n                    min=yrange[0], max=yrange[1], title=yleftlabel, parter=None\n                )\n                yaxis2 = pyx.graph.axis.log(\n                    min=yrange[0], max=yrange[1], title=yrightlabel, parter=None\n                )\n            else:\n                yaxis = pyx.graph.axis.lin(\n                    min=yrange[0], max=yrange[1], title=yleftlabel, parter=None\n                )\n                yaxis2 = pyx.graph.axis.lin(\n                    min=yrange[0], max=yrange[1], title=yrightlabel, parter=None\n                )\n            if xlog:\n                xaxis = pyx.graph.axis.log(\n                    min=xrange[0], max=xrange[1], title=xleftlabel, parter=None\n                )\n                xaxis2 = pyx.graph.axis.log(\n                    min=xrange[0], max=xrange[1], title=xrightlabel, parter=None\n                )\n            else:\n                xaxis = pyx.graph.axis.lin(\n                    min=xrange[0], max=xrange[1], title=xleftlabel, parter=None\n                )\n                xaxis2 = pyx.graph.axis.lin(\n                    min=xrange[0], max=xrange[1], title=xrightlabel, parter=None\n                )\n\n        blank_data = pyx.graph.data.points([(-1e20, -1e20), (-1e19, -1e19)], x=1, y=2)\n        if self.canvas is None:\n            self.canvas = pyx.graph.graphxy(\n                width=psize[0],\n                height=psize[1],\n                x=xaxis,\n                y=yaxis,\n                x2=xaxis2,\n                y2=yaxis2,\n                xpos=pos[0],\n                ypos=pos[1],\n            )\n            if xdata is None:\n                self.canvas.plot(blank_data)\n            else:\n                data = pyx.graph.data.points(np.array([xdata, ydata]).T, x=1, y=2)\n                self.canvas.plot(\n                    data, [pyx.graph.style.line([pyx.style.linewidth.Thick])]\n                )\n        else:\n            plot = pyx.graph.graphxy(\n                width=psize[0],\n                height=psize[1],\n                x=xaxis,\n                y=yaxis,\n                x2=xaxis2,\n                y2=yaxis2,\n                xpos=pos[0],\n                ypos=pos[1],\n            )\n            if xdata is None:\n                plot.plot(blank_data)\n            else:\n                data = pyx.graph.data.points(np.array([xdata, ydata]).T, x=1, y=2)\n                plot.plot(data, [pyx.graph.style.line([pyx.style.linewidth.Thick])])\n            self.canvas.insert(plot)\n        self.axes_drawn = True\n\n    # =============================================================================\n\n    def axis_box_yt(\n        self,\n        plot,\n        units=None,\n        bare_axes=False,\n        tickcolor=None,\n        xlabel=None,\n        ylabel=None,\n        **kwargs,\n    ):\n        r\"\"\"Wrapper around DualEPS.axis_box to automatically fill in the\n        axis ranges and labels from a yt plot.\n\n        This also accepts any parameters that DualEPS.axis_box takes.\n\n        Parameters\n        ----------\n        plot : `yt.visualization.plot_window.PlotWindow`\n            yt plot on which the axes are based.\n        units : string\n            Unit description that overrides yt's unit description.  Only\n            affects the axis label.\n        bare_axes : boolean\n            Set to true to have no annotations or tick marks on all of the\n            axes.\n\n        Examples\n        --------\n        >>> p = SlicePlot(ds, 0, \"density\")\n        >>> d = DualEPS()\n        >>> d.axis_box_yt(p)\n        >>> d.save_fig()\n        \"\"\"\n\n        if isinstance(plot, (PlotWindow, PhasePlot)):\n            plot.refresh()\n        if isinstance(plot, PlotWindow):\n            data = plot.frb\n            width = plot.width[0]\n            if units is None:\n                units = plot.ds.get_smallest_appropriate_unit(width)\n            width = width.in_units(str(units))\n            xc = 0.5 * (plot.xlim[0] + plot.xlim[1])\n            yc = 0.5 * (plot.ylim[0] + plot.ylim[1])\n            _xrange = [(plot.xlim[i] - xc).in_units(units) for i in (0, 1)]\n            _yrange = [(plot.ylim[i] - yc).in_units(units) for i in (0, 1)]\n            _xlog = False\n            _ylog = False\n            if bare_axes:\n                _xlabel = \"\"\n                _ylabel = \"\"\n            else:\n                if xlabel is not None:\n                    _xlabel = xlabel\n                else:\n                    if data.axis != 4:\n                        xi = plot.ds.coordinates.x_axis[data.axis]\n                        x_name = plot.ds.coordinates.axis_name[xi]\n                        _xlabel = f\"{x_name} ({units})\"\n                    else:\n                        _xlabel = f\"x ({units})\"\n                if ylabel is not None:\n                    _ylabel = ylabel\n                else:\n                    if data.axis != 4:\n                        yi = plot.ds.coordinates.y_axis[data.axis]\n                        y_name = plot.ds.coordinates.axis_name[yi]\n                        _ylabel = f\"{y_name} ({units})\"\n                    else:\n                        _ylabel = f\"y ({units})\"\n            if tickcolor is None:\n                _tickcolor = pyx.color.cmyk.white\n        elif isinstance(plot, ProfilePlot):\n            subplot = plot.axes.values()[0]\n            # limits for axes\n            xlimits = subplot.get_xlim()\n            _xrange = (\n                YTQuantity(xlimits[0], \"m\"),\n                YTQuantity(xlimits[1], \"m\"),\n            )  # unit hardcoded but afaik it is not used anywhere so it doesn't matter\n            if list(plot.axes.ylim.values())[0][0] is None:\n                ylimits = subplot.get_ylim()\n            else:\n                ylimits = list(plot.axes.ylim.values())[0]\n            _yrange = (\n                YTQuantity(ylimits[0], \"m\"),\n                YTQuantity(ylimits[1], \"m\"),\n            )  # unit hardcoded but afaik it is not used anywhere so it doesn't matter\n            # axis labels\n            xaxis = subplot.xaxis\n            _xlabel = pyxize_label(xaxis.label.get_text())\n            yaxis = subplot.yaxis\n            _ylabel = pyxize_label(yaxis.label.get_text())\n            # set log if necessary\n            if subplot.get_xscale() == \"log\":\n                _xlog = True\n            else:\n                _xlog = False\n            if subplot.get_yscale() == \"log\":\n                _ylog = True\n            else:\n                _ylog = False\n            _tickcolor = None\n        elif isinstance(plot, PhasePlot):\n            k = list(plot.plots.keys())[0]\n            _xrange = plot[k].axes.get_xlim()\n            _yrange = plot[k].axes.get_ylim()\n            _xlog = plot.profile.x_log\n            _ylog = plot.profile.y_log\n            if bare_axes:\n                _xlabel = \"\"\n                _ylabel = \"\"\n            else:\n                if xlabel is not None:\n                    _xlabel = xlabel\n                else:\n                    _xlabel = plot[k].axes.get_xlabel()\n                if ylabel is not None:\n                    _ylabel = ylabel\n                else:\n                    _ylabel = plot[k].axes.get_ylabel()\n                _xlabel = pyxize_label(_xlabel)\n                _ylabel = pyxize_label(_ylabel)\n            if tickcolor is None:\n                _tickcolor = None\n        elif isinstance(plot, np.ndarray):\n            ax = plt.gca()\n            _xrange = ax.get_xlim()\n            _yrange = ax.get_ylim()\n            _xlog = False\n            _ylog = False\n            if bare_axes:\n                _xlabel = \"\"\n                _ylabel = \"\"\n            else:\n                if xlabel is not None:\n                    _xlabel = xlabel\n                else:\n                    _xlabel = ax.get_xlabel()\n                if ylabel is not None:\n                    _ylabel = ylabel\n                else:\n                    _ylabel = ax.get_ylabel()\n            if tickcolor is None:\n                _tickcolor = None\n        else:\n            _xrange = plot._axes.get_xlim()\n            _yrange = plot._axes.get_ylim()\n            _xlog = plot._log_x\n            _ylog = plot._log_y\n            if bare_axes:\n                _xlabel = \"\"\n                _ylabel = \"\"\n            else:\n                if xlabel is not None:\n                    _xlabel = xlabel\n                else:\n                    _xlabel = plot._x_label\n                if ylabel is not None:\n                    _ylabel = ylabel\n                else:\n                    _ylabel = plot._y_label\n            if tickcolor is None:\n                _tickcolor = None\n        if tickcolor is not None:\n            _tickcolor = tickcolor\n        self.axis_box(\n            xrange=_xrange,\n            yrange=_yrange,\n            xlabel=_xlabel,\n            ylabel=_ylabel,\n            tickcolor=_tickcolor,\n            xlog=_xlog,\n            ylog=_ylog,\n            bare_axes=bare_axes,\n            **kwargs,\n        )\n\n    # =============================================================================\n\n    def insert_image(self, filename, pos=(0, 0), size=None):\n        r\"\"\"Inserts a JPEG file in the figure.\n\n        Parameters\n        ----------\n        filename : string\n            Name of the JPEG file\n        pos : tuple of floats\n            Position of the origin of the image in centimeters\n        size : tuple of flots\n            Size of image in units of figsize\n\n        Examples\n        --------\n        >>> d = DualEPS()\n        >>> d.axis_box(xrange=(0, 100), yrange=(1e-3, 1), ylog=True)\n        >>> d.insert_image(\"image.jpg\")\n        >>> d.save_fig()\n        \"\"\"\n        if size is not None:\n            width = size[0] * self.figsize[0]\n            height = size[1] * self.figsize[1]\n        else:\n            width = self.figsize[0]\n            height = self.figsize[1]\n        image = pyx.bitmap.jpegimage(filename)\n        if self.canvas is None:\n            self.canvas = pyx.canvas.canvas()\n        self.canvas.insert(\n            pyx.bitmap.bitmap(\n                pos[0], pos[1], image, compressmode=None, width=width, height=height\n            )\n        )\n\n    # =============================================================================\n\n    def insert_image_yt(self, plot, field=None, pos=(0, 0), scale=1.0):\n        r\"\"\"Inserts a bitmap taken from a yt plot.\n\n        Parameters\n        ----------\n        plot : `yt.visualization.plot_window.PlotWindow`\n            yt plot that provides the image\n        pos : tuple of floats\n            Position of the origin of the image in centimeters.\n\n        Examples\n        --------\n        >>> p = SlicePlot(ds, 0, \"density\")\n        >>> d = DualEPS()\n        >>> d.axis_box_yt(p)\n        >>> d.insert_image_yt(p)\n        >>> d.save_fig()\n\n        Notes\n        -----\n        For best results, set use_colorbar=False when creating the yt\n        image.\n        \"\"\"\n        from matplotlib.backends.backend_agg import FigureCanvasAgg\n\n        # We need to remove the colorbar (if necessary), remove the\n        # axes, and resize the figure to span the entire figure\n        force_square = False\n        if self.canvas is None:\n            self.canvas = pyx.canvas.canvas()\n        if isinstance(plot, (PlotWindow, PhasePlot)):\n            if field is None:\n                self.field = list(plot.plots.keys())[0]\n                mylog.warning(\n                    \"No field specified.  Choosing first field (%s)\", self.field\n                )\n            else:\n                self.field = plot.data_source._determine_fields(field)[0]\n            if self.field not in plot.plots.keys():\n                raise RuntimeError(\"Field '%s' does not exist!\", self.field)\n            if isinstance(plot, PlotWindow):\n                plot.hide_colorbar()\n                plot.hide_axes()\n            else:\n                plot.plots[self.field]._toggle_axes(False)\n                plot.plots[self.field]._toggle_colorbar(False)\n            plot.refresh()\n            _p1 = plot.plots[self.field].figure\n            force_square = True\n        elif isinstance(plot, ProfilePlot):\n            _p1 = plot.figures.items()[0][1]\n        elif isinstance(plot, np.ndarray):\n            plt.figure()\n            iplot = plt.figimage(plot)\n            _p1 = iplot.figure\n            _p1.set_size_inches(self.figsize[0], self.figsize[1])\n            ax = plt.gca()\n            _p1.add_axes(ax)\n        else:\n            raise RuntimeError(\"Unknown plot type\")\n\n        _p1.axes[0].set_position([0, 0, 1, 1])  # rescale figure\n        _p1.set_facecolor(\"w\")  # set background color\n        figure_canvas = FigureCanvasAgg(_p1)\n        figure_canvas.draw()\n        size = (_p1.get_size_inches() * _p1.dpi).astype(\"int64\")\n\n        # Account for non-square images after removing the colorbar.\n        scale *= 1.0 - 1.0 / (_p1.dpi * self.figsize[0])\n        if force_square:\n            yscale = scale * float(size[1]) / float(size[0])\n        else:\n            yscale = scale\n        image = pyx.bitmap.image(size[0], size[1], \"RGB\", figure_canvas.tostring_rgb())\n        self.canvas.insert(\n            pyx.bitmap.bitmap(\n                pos[0],\n                pos[1],\n                image,\n                width=scale * self.figsize[0],\n                height=yscale * self.figsize[1],\n            )\n        )\n\n    # =============================================================================\n\n    def colorbar(\n        self,\n        name,\n        zrange=(0, 1),\n        label=\"\",\n        log=False,\n        tickcolor=None,\n        orientation=\"right\",\n        pos=None,\n        shrink=1.0,\n    ):\n        r\"\"\"Places a colorbar adjacent to the current figure.\n\n        Parameters\n        ----------\n        name : string\n            name of the (matplotlib) colormap to use\n        zrange : tuple of floats\n            min and max of the colorbar's range\n        label : string\n            colorbar label\n        log : boolean\n            Flag to use a logarithmic scale\n        tickcolor : `pyx.color.*.*`\n            Color for the tickmarks.  Example: pyx.color.cmyk.black\n        orientation : string\n            Placement of the colorbar.  Can be \"left\", \"right\", \"top\",\n            or \"bottom\".\n        pos : list of floats\n            (x,y) position of the origin of the colorbar in centimeters.\n        shrink : float\n            Factor to shrink the colorbar's size.  A value of 1 means the\n            colorbar will have a height / width of the figure.\n\n        Examples\n        --------\n        >>> d = DualEPS()\n        >>> d.axis_box(xrange=(0, 100), yrange=(1e-3, 1), ylog=True)\n        >>> d.insert_image(\"image.jpg\")\n        >>> d.colorbar(\n        ...     \"hot\", xrange=(1e-2, 1e-4), log=True, label=\"Density [cm$^{-3}$]\"\n        ... )\n        >>> d.save_fig()\n        \"\"\"\n        if pos is None:\n            pos = [0, 0]\n\n        if orientation == \"right\":\n            origin = (pos[0] + self.figsize[0] + 0.5, pos[1])\n            size = (0.1 * self.figsize[0], self.figsize[1])\n            imsize = (1, 256)\n        elif orientation == \"left\":\n            origin = (pos[0] - 0.5 - 0.1 * self.figsize[0], pos[1])\n            size = (0.1 * self.figsize[0], self.figsize[1])\n            imsize = (1, 256)\n        elif orientation == \"top\":\n            origin = (pos[0], pos[1] + self.figsize[1] + 0.5)\n            imorigin = (pos[0] + self.figsize[0], pos[1] + self.figsize[1] + 0.5)\n            size = (self.figsize[0], 0.1 * self.figsize[1])\n            imsize = (256, 1)\n        elif orientation == \"bottom\":\n            origin = (pos[0], pos[1] - 0.5 - 0.1 * self.figsize[1])\n            imorigin = (pos[0] + self.figsize[0], pos[1] - 0.5 - 0.1 * self.figsize[1])\n            size = (self.figsize[0], 0.1 * self.figsize[1])\n            imsize = (256, 1)\n        else:\n            raise RuntimeError(f\"orientation {orientation} unknown\")\n\n        # If shrink is a scalar, then convert into tuple\n        if not isinstance(shrink, (tuple, list)):\n            shrink = (shrink, shrink)\n\n        # Scale the colorbar\n        shift = (0.5 * (1.0 - shrink[0]) * size[0], 0.5 * (1.0 - shrink[1]) * size[1])\n        # To facilitate stretching rather than shrinking\n        # If stretched in both directions (makes no sense?) then y dominates.\n        if shrink[0] > 1.0:\n            shift = (0.05 * self.figsize[0], 0.5 * (1.0 - shrink[1]) * size[1])\n        if shrink[1] > 1.0:\n            shift = (0.5 * (1.0 - shrink[0]) * size[0], 0.05 * self.figsize[1])\n        size = (size[0] * shrink[0], size[1] * shrink[1])\n        origin = (origin[0] + shift[0], origin[1] + shift[1])\n\n        # Convert the colormap into a string\n        x = np.linspace(1, 0, 256)\n        cm_string = plt.get_cmap(name)(x, bytes=True)[:, 0:3].tobytes()\n\n        cmap_im = pyx.bitmap.image(imsize[0], imsize[1], \"RGB\", cm_string)\n        if orientation == \"top\" or orientation == \"bottom\":\n            imorigin = (imorigin[0] - shift[0], imorigin[1] + shift[1])\n            self.canvas.insert(\n                pyx.bitmap.bitmap(\n                    imorigin[0], imorigin[1], cmap_im, width=-size[0], height=size[1]\n                )\n            )\n        else:\n            self.canvas.insert(\n                pyx.bitmap.bitmap(\n                    origin[0], origin[1], cmap_im, width=size[0], height=size[1]\n                )\n            )\n\n        if tickcolor is None:\n            c1 = pyx.graph.axis.painter.regular(tickattrs=[pyx.color.cmyk.black])\n            pyx.graph.axis.painter.regular(\n                tickattrs=[pyx.color.cmyk.black], labelattrs=None\n            )\n        else:\n            c1 = pyx.graph.axis.painter.regular(tickattrs=[tickcolor])\n            pyx.graph.axis.painter.regular(tickattrs=[tickcolor], labelattrs=None)\n        if log:\n            yaxis = pyx.graph.axis.log(\n                min=zrange[0], max=zrange[1], title=label, painter=c1\n            )\n            yaxis2 = pyx.graph.axis.log(min=zrange[0], max=zrange[1], parter=None)\n        else:\n            yaxis = pyx.graph.axis.lin(\n                min=zrange[0], max=zrange[1], title=label, painter=c1\n            )\n            yaxis2 = pyx.graph.axis.lin(min=zrange[0], max=zrange[1], parter=None)\n        xaxis = pyx.graph.axis.lin(parter=None)\n\n        if orientation == \"right\":\n            _colorbar = pyx.graph.graphxy(\n                width=size[0],\n                height=size[1],\n                xpos=origin[0],\n                ypos=origin[1],\n                x=xaxis,\n                y=yaxis2,\n                y2=yaxis,\n            )\n        elif orientation == \"left\":\n            _colorbar = pyx.graph.graphxy(\n                width=size[0],\n                height=size[1],\n                xpos=origin[0],\n                ypos=origin[1],\n                x=xaxis,\n                y2=yaxis2,\n                y=yaxis,\n            )\n        elif orientation == \"top\":\n            _colorbar = pyx.graph.graphxy(\n                width=size[0],\n                height=size[1],\n                xpos=origin[0],\n                ypos=origin[1],\n                y=xaxis,\n                x=yaxis2,\n                x2=yaxis,\n            )\n        elif orientation == \"bottom\":\n            _colorbar = pyx.graph.graphxy(\n                width=size[0],\n                height=size[1],\n                xpos=origin[0],\n                ypos=origin[1],\n                y=xaxis,\n                x2=yaxis2,\n                x=yaxis,\n            )\n\n        blank_data = pyx.graph.data.points([(-1e10, -1e10), (-9e10, -9e10)], x=1, y=2)\n        _colorbar.plot(blank_data)\n        self.canvas.insert(_colorbar)\n\n    # =============================================================================\n\n    def colorbar_yt(self, plot, field=None, cb_labels=None, **kwargs):\n        r\"\"\"Wrapper around DualEPS.colorbar to take information from a yt plot.\n\n        Accepts all parameters that DualEPS.colorbar takes.\n\n        Parameters\n        ----------\n        plot : A yt plot\n            yt plot from which the information is taken.\n        cb_labels : list of labels for the colorbars. List should be the same\n                    size as the number of colorbars used. Should be passed\n                    into this function by either the singleplot or multiplot api.\n\n        Examples\n        --------\n        >>> p = SlicePlot(ds, 0, \"density\")\n        >>> p.hide_colorbar()\n        >>> d = DualEPS()\n        >>> d.axis_box_yt(p)\n        >>> d.insert_image_yt(p)\n        >>> d.colorbar_yt(p)\n        >>> d.save_fig()\n        \"\"\"\n\n        if isinstance(plot, ProfilePlot):\n            raise RuntimeError(\n                \"When using ProfilePlots you must either set yt_nocbar=True or provide \"\n                \"colorbar flags so that the profiles don't have colorbars\"\n            )\n        _cmap = None\n        if field is not None:\n            self.field = plot.data_source._determine_fields(field)[0]\n        if isinstance(plot, (PlotWindow, PhasePlot)):\n            _cmap = plot[self.field].colorbar_handler.cmap\n        else:\n            if plot.cmap is not None:\n                _cmap = plot.cmap.name\n        if _cmap is None:\n            _cmap = ytcfg.get(\"yt\", \"default_colormap\")\n        if isinstance(plot, (PlotWindow, PhasePlot)):\n            if isinstance(plot, PlotWindow):\n                try:\n                    _zlabel = plot.frb[self.field].info[\"label\"]\n                    _unit = Unit(\n                        plot.frb[self.field].units, registry=plot.ds.unit_registry\n                    )\n                    units = _unit.latex_representation()\n                    # PyX does not support \\frac because it's based on TeX.\n                    units = pyxize_label(units)\n                    _zlabel += r\" (\" + units + r\")\"\n                except NotImplementedError:\n                    print(\"Colorbar label not available\")\n                    _zlabel = \"\"\n            else:\n                _, _, z_title = plot._get_field_title(self.field, plot.profile)\n                _zlabel = pyxize_label(z_title)\n            _zlabel = _zlabel.replace(\"_\", r\"\\;\")\n\n            _p = plot.plots[self.field]\n            _norm = _p.norm_handler.get_norm(plot.frb[self.field])\n            norm_type = type(_norm)\n            if norm_type is LogNorm:\n                _zlog = True\n            elif norm_type is Normalize:\n                # linear scaling\n                _zlog = False\n            else:\n                raise RuntimeError(\n                    \"eps_writer is not compatible with scalings other than linear and log, \"\n                    f\"received {norm_type}\"\n                )\n            _zrange = (_norm.vmin, _norm.vmax)\n        else:\n            _zlabel = plot._z_label.replace(\"_\", r\"\\;\")\n            _zlog = plot._log_z\n            _zrange = (plot.norm.vmin, plot.norm.vmax)\n        if cb_labels is not None:  # Overrides deduced labels\n            _zlabel = cb_labels.pop()\n        self.colorbar(_cmap, zrange=_zrange, label=_zlabel, log=_zlog, **kwargs)\n\n    # =============================================================================\n\n    def circle(\n        self,\n        radius=0.2,\n        loc=(0.5, 0.5),\n        color=pyx.color.cmyk.white,\n        linewidth=pyx.style.linewidth.normal,\n    ):\n        r\"\"\"Draws a circle in the current figure.\n\n        Parameters\n        ----------\n        radius : float\n            Radius of the circle in units of figsize\n        loc : tuple of floats\n            Location of the circle's center in units of figsize\n        color : `pyx.color.*.*`\n            Color of the circle stroke.  Example: pyx.color.cmyk.white\n        linewidth : `pyx.style.linewidth.*`\n            Width of the circle stroke width. Example:\n            pyx.style.linewidth.normal\n\n        Examples\n        --------\n        >>> d = DualEPS()\n        >>> d.axis_box(xrange=(0, 100), yrange=(1e-3, 1), ylog=True)\n        >>> d.insert_image(\"image.jpg\")\n        >>> d.circle(radius=0.1, color=pyx.color.cmyk.Red)\n        >>> d.save_fig()\n        \"\"\"\n        circle = pyx.path.circle(\n            self.figsize[0] * loc[0], self.figsize[1] * loc[1], self.figsize[0] * radius\n        )\n        self.canvas.stroke(circle, [color, linewidth])\n\n    # =============================================================================\n\n    def arrow(\n        self,\n        size=0.2,\n        label=\"\",\n        loc=(0.05, 0.08),\n        labelloc=\"top\",\n        color=pyx.color.cmyk.white,\n        linewidth=pyx.style.linewidth.normal,\n        rotation=0.0,\n    ):\n        r\"\"\"Draws an arrow in the current figure\n\n        Parameters\n        ----------\n        size : float\n            Length of arrow (base to tip) in units of the figure size.\n        label : string\n            Annotation label of the arrow.\n        loc : tuple of floats\n            Location of the left hand side of the arrow in units of\n            the figure size.\n        rotation : float\n            Orientation angle of the arrow in units of degrees\n        labelloc : string\n            Location of the label with respect to the line.  Can be\n            \"top\" or \"bottom\"\n        color : `pyx.color.*.*`\n            Color of the arrow.  Example: pyx.color.cymk.white\n        linewidth : `pyx.style.linewidth.*`\n            Width of the arrow.  Example: pyx.style.linewidth.normal\n\n        Examples\n        --------\n        >>> d = DualEPS()\n        >>> d.axis_box(xrange=(0, 100), yrange=(1e-3, 1), ylog=True)\n        >>> d.insert_image(\"arrow_image.jpg\")\n        >>> d.arrow(size=0.2, label=\"Black Hole!\", loc=(0.05, 0.1))\n        >>> d.save_fig()\n        \"\"\"\n        line = pyx.path.line(\n            self.figsize[0] * loc[0],\n            self.figsize[1] * loc[1],\n            self.figsize[0] * (loc[0] + size * np.cos(np.pi * rotation / 180)),\n            self.figsize[1] * (loc[1] + size * np.sin(np.pi * rotation / 180)),\n        )\n        self.canvas.stroke(line, [linewidth, color, pyx.deco.earrow()])\n\n        if labelloc == \"bottom\":\n            yoff = -0.1 * size\n            valign = pyx.text.valign.top\n        else:\n            yoff = +0.1 * size\n            valign = pyx.text.valign.bottom\n        if label != \"\":\n            self.canvas.text(\n                self.figsize[0] * (loc[0] + 0.5 * size),\n                self.figsize[1] * (loc[1] + yoff),\n                label,\n                [color, valign, pyx.text.halign.center],\n            )\n\n    # =============================================================================\n\n    def scale_line(\n        self,\n        size=0.2,\n        label=\"\",\n        loc=(0.05, 0.08),\n        labelloc=\"top\",\n        color=pyx.color.cmyk.white,\n        linewidth=pyx.style.linewidth.normal,\n    ):\n        r\"\"\"Draws a scale line in the current figure.\n\n        Parameters\n        ----------\n        size : float\n            Length of the scale line in units of the figure size.\n        label : string\n            Annotation label of the scale line.\n        loc : tuple of floats\n            Location of the left hand side of the scale line in units of\n            the figure size.\n        labelloc : string\n            Location of the label with respect to the line.  Can be\n            \"top\" or \"bottom\"\n        color : `pyx.color.*.*`\n            Color of the scale line.  Example: pyx.color.cymk.white\n        linewidth : `pyx.style.linewidth.*`\n            Width of the scale line.  Example: pyx.style.linewidth.normal\n\n        Examples\n        --------\n        >>> d = DualEPS()\n        >>> d.axis_box(xrange=(0, 100), yrange=(1e-3, 1), ylog=True)\n        >>> d.insert_image(\"image.jpg\")\n        >>> d.scale_line(size=0.2, label=\"1 kpc\", loc=(0.05, 0.1))\n        >>> d.save_fig()\n        \"\"\"\n\n        line = pyx.path.line(\n            self.figsize[0] * loc[0],\n            self.figsize[1] * loc[1],\n            self.figsize[0] * (loc[0] + size),\n            self.figsize[1] * loc[1],\n        )\n        self.canvas.stroke(line, [linewidth, color])\n        line = pyx.path.line(\n            self.figsize[0] * loc[0],\n            self.figsize[1] * (loc[1] - 0.1 * size),\n            self.figsize[0] * loc[0],\n            self.figsize[1] * (loc[1] + 0.1 * size),\n        )\n        self.canvas.stroke(line, [linewidth, color])\n        line = pyx.path.line(\n            self.figsize[0] * (loc[0] + size),\n            self.figsize[1] * (loc[1] - 0.1 * size),\n            self.figsize[0] * (loc[0] + size),\n            self.figsize[1] * (loc[1] + 0.1 * size),\n        )\n        self.canvas.stroke(line, [linewidth, color])\n\n        if labelloc == \"bottom\":\n            yoff = -0.1 * size\n            valign = pyx.text.valign.top\n        else:\n            yoff = +0.1 * size\n            valign = pyx.text.valign.bottom\n        if label != \"\":\n            self.canvas.text(\n                self.figsize[0] * (loc[0] + 0.5 * size),\n                self.figsize[1] * (loc[1] + yoff),\n                label,\n                [color, valign, pyx.text.halign.center],\n            )\n\n    # =============================================================================\n\n    def title_box(\n        self,\n        text,\n        color=pyx.color.cmyk.black,\n        bgcolor=pyx.color.cmyk.white,\n        loc=(0.02, 0.98),\n        halign=pyx.text.halign.left,\n        valign=pyx.text.valign.top,\n        text_opts=None,\n    ):\n        r\"\"\"Inserts a box with text in the current figure.\n\n        Parameters\n        ----------\n        text : string\n            String to insert in the textbox.\n        color : `pyx.color.*.*`\n            Color of the text.  Example: pyx.color.cmyk.black\n        bgcolor : `pyx.color.*.*`\n            Color of the textbox background.  Example: pyx.color.cmyk.white\n        loc : tuple of floats\n            Location of the textbox origin in units of the figure size.\n        halign : `pyx.text.halign.*`\n            Horizontal alignment of the text.  Example: pyx.text.halign.left\n        valign : `pyx.text.valign.*`\n            Vertical alignment of the text.  Example: pyx.text.valign.top\n\n        Examples\n        --------\n        >>> d = DualEPS()\n        >>> d.axis_box(xrange=(0, 100), yrange=(1e-3, 1), ylog=True)\n        >>> d.insert_image(\"image.jpg\")\n        >>> d.title_box(\"Halo 1\", loc=(0.05, 0.95))\n        >>> d.save_fig()\n        \"\"\"\n        if text_opts is None:\n            text_opts = []\n        tbox = self.canvas.text(\n            self.figsize[0] * loc[0],\n            self.figsize[1] * loc[1],\n            text,\n            [color, valign, halign] + text_opts,\n        )\n        if bgcolor is not None:\n            tpath = tbox.bbox().enlarged(2 * pyx.unit.x_pt).path()\n            self.canvas.draw(tpath, [pyx.deco.filled([bgcolor]), pyx.deco.stroked()])\n        self.canvas.insert(tbox)\n\n    # =============================================================================\n\n    def save_fig(self, filename=\"test\", format=\"eps\", resolution=250):\n        r\"\"\"Saves current figure to a file.\n\n        Parameters\n        ----------\n        filename : string\n            Name of the saved file without the extension.\n        format : string\n            Format type.  Can be \"eps\" or \"pdf\"\n\n        Examples\n        --------\n        >>> d = DualEPS()\n        >>> d.axis_box(xrange=(0, 100), yrange=(1e-3, 1), ylog=True)\n        \"\"\"\n        filename = os.path.expanduser(filename)\n        if format == \"eps\":\n            self.canvas.writeEPSfile(filename)\n        elif format == \"pdf\":\n            self.canvas.writePDFfile(filename)\n        elif format == \"png\":\n            self.canvas.writeGSfile(filename + \".png\", \"png16m\", resolution=resolution)\n        elif format == \"jpg\":\n            self.canvas.writeGSfile(filename + \".jpeg\", \"jpeg\", resolution=resolution)\n        else:\n            raise RuntimeError(f\"format {format} unknown.\")\n\n\n# =============================================================================\n# =============================================================================\n# =============================================================================\n\n\ndef multiplot(\n    ncol,\n    nrow,\n    yt_plots=None,\n    fields=None,\n    images=None,\n    xranges=None,\n    yranges=None,\n    xlabels=None,\n    ylabels=None,\n    xdata=None,\n    ydata=None,\n    colorbars=None,\n    shrink_cb=0.95,\n    figsize=(8, 8),\n    margins=(0, 0),\n    titles=None,\n    savefig=None,\n    format=\"eps\",\n    yt_nocbar=False,\n    bare_axes=False,\n    xaxis_flags=None,\n    yaxis_flags=None,\n    cb_flags=None,\n    cb_location=None,\n    cb_labels=None,\n):\n    r\"\"\"Convenience routine to create a multi-panel figure from yt plots or\n    JPEGs.  The images are first placed from the origin, and then\n    bottom-to-top and left-to-right.\n\n    Parameters\n    ----------\n    ncol : integer\n        Number of columns in the figure.\n    nrow : integer\n        Number of rows in the figure.\n    yt_plots : list of yt plot instances\n        yt plots to include in the figure.\n    images : list of strings\n        JPEG filenames to include in the figure.\n    xranges : list of tuples\n        The min and max of the x-axes\n    yranges : list of tuples\n        The min and max of the y-axes\n    xlabels : list of strings\n        Labels for the x-axes\n    ylabels : list of strings\n        Labels for the y-axes\n    colorbars : list of dicts\n        Dicts that describe the type of colorbar to be used in each\n        figure.  Use the function return_colormap() to create these dicts.\n    shrink_cb : float\n        Factor by which the colorbar is shrunk.\n    figsize : tuple of floats\n        The width and height of a single figure in centimeters.\n    margins : tuple of floats\n        The horizontal and vertical margins between panels in centimeters.\n    titles : list of strings\n        Titles that are placed in textboxes in each panel.\n    savefig : string\n        Name of the saved file without the extension.\n    format : string\n        File format of the figure. eps or pdf accepted.\n    yt_nocbar : boolean\n        Flag to indicate whether or not colorbars are created.\n    bare_axes : boolean\n        Set to true to have no annotations or tick marks on all of the\n        axes.\n    cb_flags : list of booleans\n        Flags for each plot to have a colorbar or not.\n    cb_location : list of strings\n        Strings to control the location of the colorbar (left, right,\n        top, bottom)\n    cb_labels : list of labels for the colorbars. List should be the same\n                size as the number of colorbars used.\n\n    Examples\n    --------\n    >>> images = [\"density.jpg\", \"hi_density.jpg\", \"entropy.jpg\", \"special.jpg\"]\n    >>> cbs = []\n    >>> cbs.append(return_colormap(\"cmyt.arbre\", \"Density [cm$^{-3}$]\", (0, 10), False))\n    >>> cbs.append(return_colormap(\"cmyt.kelp\", \"HI Density\", (0, 5), False))\n    >>> cbs.append(return_colormap(\"hot\", r\"Entropy [K cm$^2$]\", (1e-2, 1e6), True))\n    >>> cbs.append(return_colormap(\"Spectral\", \"Stuff$_x$!\", (1, 300), True))\n    >>> mp = multiplot(\n    ...     2,\n    ...     2,\n    ...     images=images,\n    ...     margins=(0.1, 0.1),\n    ...     titles=[\"1\", \"2\", \"3\", \"4\"],\n    ...     xlabels=[\"one\", \"two\"],\n    ...     ylabels=None,\n    ...     colorbars=cbs,\n    ...     shrink_cb=0.95,\n    ... )\n    >>> mp.scale_line(label=\"$r_{vir}$\", labelloc=\"top\")\n    >>> mp.save_fig(\"multiplot\")\n\n    Notes\n    -----\n    If given both yt_plots and images, this will get preference to the\n    yt plots.\n    \"\"\"\n    # Error check\n    npanels = ncol * nrow\n    if cb_labels is not None:\n        cb_labels.reverse()  # Because I pop the list\n\n    if images is not None:\n        if len(images) != npanels:\n            raise RuntimeError(\n                f\"Number of images ({len(images)}) doesn't match \"\n                f\"nrow({nrow}) x ncol({ncol}).\"\n            )\n    if yt_plots is None and images is None:\n        raise RuntimeError(\"Must supply either yt_plots or image filenames.\")\n    if yt_plots is not None and images is not None:\n        mylog.warning(\"Given both images and yt plots.  Ignoring images.\")\n    if yt_plots is not None:\n        _yt = True\n    else:\n        _yt = False\n    if fields is None:\n        fields = [None] * npanels\n\n    # If no ranges or labels given and given only images, fill them in.\n    if not _yt:\n        if xranges is None:\n            xranges = []\n            for _ in range(npanels):\n                xranges.append((0, 1))\n        if yranges is None:\n            yranges = []\n            for _ in range(npanels):\n                yranges.append((0, 1))\n        if xlabels is None:\n            xlabels = []\n            for _ in range(npanels):\n                xlabels.append(\"\")\n        if ylabels is None:\n            ylabels = []\n            for _ in range(npanels):\n                ylabels.append(\"\")\n\n    d = DualEPS(figsize=figsize)\n    for j in range(nrow):\n        invj = nrow - j - 1\n        ypos = invj * (figsize[1] + margins[1])\n        for i in range(ncol):\n            xpos = i * (figsize[0] + margins[0])\n            index = j * ncol + i\n            if isinstance(yt_plots, list):\n                this_plot = yt_plots[index]\n            else:\n                this_plot = yt_plots\n            if j == nrow - 1:\n                xaxis = 0\n            elif j == 0:\n                xaxis = 1\n            else:\n                xaxis = -1\n            if i == 0:\n                yaxis = 0\n            elif i == ncol - 1:\n                yaxis = 1\n            else:\n                yaxis = -1\n            if xdata is None:\n                _xdata = None\n            else:\n                _xdata = xdata[index]\n            if ydata is None:\n                _ydata = None\n            else:\n                _ydata = ydata[index]\n            if xaxis_flags is not None:\n                if xaxis_flags[index] is not None:\n                    xaxis = xaxis_flags[index]\n            if yaxis_flags is not None:\n                if yaxis_flags[index] is not None:\n                    yaxis = yaxis_flags[index]\n            if _yt:\n                this_plot._setup_plots()\n                if xlabels is not None:\n                    xlabel = xlabels[i]\n                else:\n                    xlabel = None\n                if ylabels is not None:\n                    ylabel = ylabels[j]\n                else:\n                    ylabel = None\n                d.insert_image_yt(this_plot, pos=(xpos, ypos), field=fields[index])\n                d.axis_box_yt(\n                    this_plot,\n                    pos=(xpos, ypos),\n                    bare_axes=bare_axes,\n                    xaxis_side=xaxis,\n                    yaxis_side=yaxis,\n                    xlabel=xlabel,\n                    ylabel=ylabel,\n                    xdata=_xdata,\n                    ydata=_ydata,\n                )\n            else:\n                d.insert_image(images[index], pos=(xpos, ypos))\n                d.axis_box(\n                    pos=(xpos, ypos),\n                    xrange=xranges[index],\n                    yrange=yranges[index],\n                    xlabel=xlabels[i],\n                    ylabel=ylabels[j],\n                    bare_axes=bare_axes,\n                    xaxis_side=xaxis,\n                    yaxis_side=yaxis,\n                    xdata=_xdata,\n                    ydata=_ydata,\n                )\n            if titles is not None:\n                if titles[index] is not None:\n                    d.title_box(\n                        titles[index],\n                        loc=(\n                            i + 0.05 + i * margins[0] / figsize[0],\n                            j + 0.98 + j * margins[1] / figsize[1],\n                        ),\n                    )\n\n    # Insert colorbars after all axes are placed because we want to\n    # put them on the edges of the bounding box.\n    bbox = (\n        100.0 * d.canvas.bbox().left().t,\n        100.0 * d.canvas.bbox().right().t - d.figsize[0],\n        100.0 * d.canvas.bbox().bottom().t,\n        100.0 * d.canvas.bbox().top().t - d.figsize[1],\n    )\n    for j in range(nrow):\n        invj = nrow - j - 1\n        ypos0 = invj * (figsize[1] + margins[1])\n        for i in range(ncol):\n            xpos0 = i * (figsize[0] + margins[0])\n            index = j * ncol + i\n            if isinstance(yt_plots, list):\n                this_plot = yt_plots[index]\n            else:\n                this_plot = yt_plots\n            if (not _yt and colorbars is not None) or (_yt and not yt_nocbar):\n                if cb_flags is not None:\n                    if not cb_flags[index]:\n                        continue\n                if cb_location is None:\n                    if ncol == 1:\n                        orientation = \"right\"\n                    elif i == 0:\n                        orientation = \"left\"\n                    elif i + 1 == ncol:\n                        orientation = \"right\"\n                    elif j == 0:\n                        orientation = \"bottom\"\n                    elif j + 1 == nrow:\n                        orientation = \"top\"\n                    else:\n                        orientation = None  # Marker for interior plot\n                else:\n                    if isinstance(cb_location, dict):\n                        if fields[index] not in cb_location.keys():\n                            raise RuntimeError(\n                                f\"{fields[index]} not found in cb_location dict\"\n                            )\n                        orientation = cb_location[fields[index]]\n                    elif isinstance(cb_location, list):\n                        orientation = cb_location[index]\n                    else:\n                        raise RuntimeError(\"Bad format: cb_location\")\n                if orientation == \"right\":\n                    xpos = bbox[1]\n                    ypos = ypos0\n                elif orientation == \"left\":\n                    xpos = bbox[0]\n                    ypos = ypos0\n                elif orientation == \"bottom\":\n                    ypos = bbox[2]\n                    xpos = xpos0\n                elif orientation == \"top\":\n                    ypos = bbox[3]\n                    xpos = xpos0\n                else:\n                    mylog.warning(\n                        \"Unknown colorbar location %s. No colorbar displayed.\",\n                        orientation,\n                    )\n                    orientation = None  # Marker for interior plot\n\n                if orientation is not None:\n                    if _yt:\n                        # Set field if undefined\n                        if fields[index] is None:\n                            fields[index] = d.return_field(yt_plots[index])\n\n                        d.colorbar_yt(\n                            this_plot,\n                            field=fields[index],\n                            pos=[xpos, ypos],\n                            shrink=shrink_cb,\n                            orientation=orientation,\n                            cb_labels=cb_labels,\n                        )\n                    else:\n                        d.colorbar(\n                            colorbars[index][\"cmap\"],\n                            zrange=colorbars[index][\"range\"],\n                            label=colorbars[index][\"name\"],\n                            log=colorbars[index][\"log\"],\n                            orientation=orientation,\n                            pos=[xpos, ypos],\n                            shrink=shrink_cb,\n                        )\n\n    if savefig is not None:\n        d.save_fig(savefig, format=format)\n\n    return d\n\n\n# =============================================================================\n\n\ndef multiplot_yt(ncol, nrow, plots, fields=None, **kwargs):\n    r\"\"\"Wrapper for multiplot that takes a yt PlotWindow\n\n    Accepts all parameters used in multiplot.\n\n    Parameters\n    ----------\n    ncol : integer\n        Number of columns in the figure.\n    nrow : integer\n        Number of rows in the figure.\n    plots : ``PlotWindow`` instance, ``PhasePlot`` instance, or list of plots\n        yt plots to be used.\n\n    Examples\n    --------\n    >>> p1 = SlicePlot(ds, 0, \"density\")\n    >>> p1.set_width(10, \"kpc\")\n\n    >>> p2 = SlicePlot(ds, 0, \"temperature\")\n    >>> p2.set_width(10, \"kpc\")\n    >>> p2.set_colormap(\"temperature\", \"hot\")\n\n    >>> sph = ds.sphere(ds.domain_center, (10, \"kpc\"))\n    >>> p3 = PhasePlot(\n    ...     sph, \"radius\", \"density\", \"temperature\", weight_field=\"cell_mass\"\n    ... )\n\n    >>> p4 = PhasePlot(sph, \"radius\", \"density\", \"pressure\", \"cell_mass\")\n\n    >>> mp = multiplot_yt(\n    ...     2,\n    ...     2,\n    ...     [p1, p2, p3, p4],\n    ...     savefig=\"yt\",\n    ...     shrink_cb=0.9,\n    ...     bare_axes=True,\n    ...     yt_nocbar=False,\n    ...     margins=(0.5, 0.5),\n    ... )\n    \"\"\"\n    # Determine whether the plots are organized in a PlotWindow, or list\n    # of PlotWindows\n    if isinstance(plots, (PlotWindow, PhasePlot)):\n        if fields is None:\n            fields = plots.fields\n        if len(fields) < nrow * ncol:\n            raise RuntimeError(\n                f\"Number of plots ({len(fields)}) is less \"\n                f\"than nrow({nrow}) x ncol({ncol}).\"\n            )\n        figure = multiplot(ncol, nrow, yt_plots=plots, fields=fields, **kwargs)\n    elif isinstance(plots, list) and isinstance(plots[0], (PlotWindow, PhasePlot)):\n        if len(plots) < nrow * ncol:\n            raise RuntimeError(\n                f\"Number of plots ({len(fields)}) is less \"\n                f\"than nrow({nrow}) x ncol({ncol}).\"\n            )\n        figure = multiplot(ncol, nrow, yt_plots=plots, fields=fields, **kwargs)\n    else:\n        raise RuntimeError(\"Unknown plot type in multiplot_yt\")\n    return figure\n\n\n# =============================================================================\n\n\ndef single_plot(\n    plot,\n    field=None,\n    figsize=(12, 12),\n    cb_orient=\"right\",\n    bare_axes=False,\n    savefig=None,\n    colorbar=True,\n    file_format=\"eps\",\n    **kwargs,\n):\n    r\"\"\"Wrapper for DualEPS routines to create a figure directly from a yt\n    plot.  Calls insert_image_yt, axis_box_yt, and colorbar_yt.\n\n    Parameters\n    ----------\n    plot : `yt.visualization.plot_window.PlotWindow`\n        yt plot that provides the image and metadata\n    figsize : tuple of floats\n        Size of the figure in centimeters.\n    cb_orient : string\n        Placement of the colorbar.  Can be \"left\", \"right\", \"top\", or\n        \"bottom\".\n    bare_axes : boolean\n        Set to true to have no annotations or tick marks on all of the axes.\n    savefig : string\n        Name of the saved file without the extension.\n    colorbar : boolean\n        Set to true to include a colorbar\n    file_format : string\n        Format type.  Can be \"eps\" or \"pdf\"\n\n    Examples\n    --------\n    >>> p = SlicePlot(ds, 0, \"density\")\n    >>> p.set_width(0.1, \"kpc\")\n    >>> single_plot(p, savefig=\"figure1\")\n    \"\"\"\n    d = DualEPS(figsize=figsize)\n    d.insert_image_yt(plot, field=field)\n    d.axis_box_yt(plot, bare_axes=bare_axes, **kwargs)\n    if colorbar and not isinstance(plot, ProfilePlot):\n        d.colorbar_yt(plot, orientation=cb_orient)\n    if savefig is not None:\n        d.save_fig(savefig, format=file_format)\n    return d\n\n\n# =============================================================================\n\n\ndef return_colormap(cmap=None, label=\"\", range=(0, 1), log=False):\n    r\"\"\"Returns a dict that describes a colorbar.  Exclusively for use with\n    multiplot.\n\n    Parameters\n    ----------\n    cmap : string\n        name of the (matplotlib) colormap to use\n    label : string\n        colorbar label\n    range : tuple of floats\n        min and max of the colorbar's range\n    log : boolean\n        Flag to use a logarithmic scale\n\n    Examples\n    --------\n    >>> cb = return_colormap(\"cmyt.arbre\", \"Density [cm$^{-3}$]\", (0, 10), False)\n    \"\"\"\n    if cmap is None:\n        cmap = ytcfg.get(\"yt\", \"default_colormap\")\n    return {\"cmap\": cmap, \"name\": label, \"range\": range, \"log\": log}\n"
  },
  {
    "path": "yt/visualization/fits_image.py",
    "content": "import re\nimport sys\nfrom functools import partial\nfrom numbers import Number as numeric_type\n\nimport numpy as np\nfrom more_itertools import first, mark_ends\n\nfrom yt._typing import FieldKey\nfrom yt.data_objects.construction_data_containers import YTCoveringGrid\nfrom yt.data_objects.image_array import ImageArray\nfrom yt.fields.derived_field import DerivedField\nfrom yt.funcs import fix_axis, is_sequence, iter_fields, mylog, validate_moment\nfrom yt.units import dimensions\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.math_utils import compute_stddev_image\nfrom yt.utilities.on_demand_imports import _astropy\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import parallel_root_only\nfrom yt.visualization.fixed_resolution import FixedResolutionBuffer, ParticleImageBuffer\nfrom yt.visualization.particle_plots import (\n    ParticleAxisAlignedDummyDataSource,\n    ParticleDummyDataSource,\n    ParticleOffAxisDummyDataSource,\n)\nfrom yt.visualization.volume_rendering.off_axis_projection import off_axis_projection\n\n\nclass UnitfulHDU:\n    def __init__(self, hdu):\n        self.hdu = hdu\n        self.header = self.hdu.header\n        self.name = self.header[\"BTYPE\"]\n        self.units = self.header[\"BUNIT\"]\n        self.shape = self.hdu.shape\n\n    @property\n    def data(self):\n        return YTArray(self.hdu.data, self.units)\n\n    def __repr__(self):\n        im_shape = \" x \".join(str(s) for s in self.shape)\n        return f\"FITSImage: {self.name} ({im_shape}, {self.units})\"\n\n\nclass FITSImageData:\n    def __init__(\n        self,\n        data,\n        fields=None,\n        length_unit=None,\n        width=None,\n        img_ctr=None,\n        wcs=None,\n        current_time=None,\n        time_unit=None,\n        mass_unit=None,\n        velocity_unit=None,\n        magnetic_unit=None,\n        ds=None,\n        unit_header=None,\n        **kwargs,\n    ):\n        r\"\"\"Initialize a FITSImageData object.\n\n        FITSImageData contains a collection of FITS ImageHDU instances and\n        WCS information, along with units for each of the images. FITSImageData\n        instances can be constructed from ImageArrays, NumPy arrays, dicts\n        of such arrays, FixedResolutionBuffers, and YTCoveringGrids. The latter\n        two are the most powerful because WCS information can be constructed\n        automatically from their coordinates.\n\n        Parameters\n        ----------\n        data : FixedResolutionBuffer or a YTCoveringGrid. Or, an\n            ImageArray, an numpy.ndarray, or dict of such arrays\n            The data to be made into a FITS image or images.\n        fields : single string or list of strings, optional\n            The field names for the data. If *fields* is none and *data* has\n            keys, it will use these for the fields. If *data* is just a\n            single array one field name must be specified.\n        length_unit : string\n            The units of the WCS coordinates and the length unit of the file.\n            Defaults to the length unit of the dataset, if there is one, or\n            \"cm\" if there is not.\n        width : float or YTQuantity\n            The width of the image. Either a single value or iterable of values.\n            If a float, assumed to be in *units*. Only used if this information\n            is not already provided by *data*.\n        img_ctr : array_like or YTArray\n            The center coordinates of the image. If a list or NumPy array,\n            it is assumed to be in *units*. This will overwrite any center\n            coordinates potentially provided by *data*. Default in other cases\n            is [0.0]*(number of dimensions).\n        wcs : `~astropy.wcs.WCS` instance, optional\n            Supply an AstroPy WCS instance. Will override automatic WCS\n            creation from FixedResolutionBuffers and YTCoveringGrids.\n        current_time : float, tuple, or YTQuantity, optional\n            The current time of the image(s). If not specified, one will\n            be set from the dataset if there is one. If a float, it will\n            be assumed to be in *time_unit* units.\n        time_unit : string\n            The default time units of the file. Defaults to \"s\".\n        mass_unit : string\n            The default time units of the file. Defaults to \"g\".\n        velocity_unit : string\n            The default velocity units of the file. Defaults to \"cm/s\".\n        magnetic_unit : string\n            The default magnetic units of the file. Defaults to \"gauss\".\n        ds : `~yt.static_output.Dataset` instance, optional\n            The dataset associated with the image(s), typically used\n            to transfer metadata to the header(s). Does not need to be\n            specified if *data* has a dataset as an attribute.\n\n        Examples\n        --------\n\n        >>> # This example uses a FRB.\n        >>> ds = load(\"sloshing_nomag2_hdf5_plt_cnt_0150\")\n        >>> prj = ds.proj(2, \"kT\", weight_field=(\"gas\", \"density\"))\n        >>> frb = prj.to_frb((0.5, \"Mpc\"), 800)\n        >>> # This example just uses the FRB and puts the coords in kpc.\n        >>> f_kpc = FITSImageData(\n        ...     frb, fields=\"kT\", length_unit=\"kpc\", time_unit=(1.0, \"Gyr\")\n        ... )\n        >>> # This example specifies a specific WCS.\n        >>> from astropy.wcs import WCS\n        >>> w = WCS(naxis=self.dimensionality)\n        >>> w.wcs.crval = [30.0, 45.0]  # RA, Dec in degrees\n        >>> w.wcs.cunit = [\"deg\"] * 2\n        >>> nx, ny = 800, 800\n        >>> w.wcs.crpix = [0.5 * (nx + 1), 0.5 * (ny + 1)]\n        >>> w.wcs.ctype = [\"RA---TAN\", \"DEC--TAN\"]\n        >>> scale = 1.0 / 3600.0  # One arcsec per pixel\n        >>> w.wcs.cdelt = [-scale, scale]\n        >>> f_deg = FITSImageData(frb, fields=\"kT\", wcs=w)\n        >>> f_deg.writeto(\"temp.fits\")\n        \"\"\"\n\n        if fields is not None:\n            fields = list(iter_fields(fields))\n\n        if ds is None:\n            ds = getattr(data, \"ds\", None)\n\n        self.fields = []\n        self.field_units = {}\n\n        if unit_header is None:\n            self._set_units(\n                ds, [length_unit, mass_unit, time_unit, velocity_unit, magnetic_unit]\n            )\n        else:\n            self._set_units_from_header(unit_header)\n\n        wcs_unit = str(self.length_unit.units)\n\n        self._fix_current_time(ds, current_time)\n\n        if width is None:\n            width = 1.0\n        if isinstance(width, tuple):\n            if ds is None:\n                width = YTQuantity(width[0], width[1])\n            else:\n                width = ds.quan(width[0], width[1])\n\n        exclude_fields = [\n            \"x\",\n            \"y\",\n            \"z\",\n            \"px\",\n            \"py\",\n            \"pz\",\n            \"pdx\",\n            \"pdy\",\n            \"pdz\",\n            \"weight_field\",\n        ]\n\n        if isinstance(data, _astropy.pyfits.PrimaryHDU):\n            data = _astropy.pyfits.HDUList([data])\n\n        if isinstance(data, _astropy.pyfits.HDUList):\n            self.hdulist = data\n            for hdu in data:\n                self.fields.append(hdu.header[\"btype\"])\n                self.field_units[hdu.header[\"btype\"]] = hdu.header[\"bunit\"]\n\n            self.shape = self.hdulist[0].shape\n            self.dimensionality = len(self.shape)\n            wcs_names = [key for key in self.hdulist[0].header if \"WCSNAME\" in key]\n            for name in wcs_names:\n                if name == \"WCSNAME\":\n                    key = \" \"\n                else:\n                    key = name[-1]\n                w = _astropy.pywcs.WCS(\n                    header=self.hdulist[0].header, key=key, naxis=self.dimensionality\n                )\n                setattr(self, \"wcs\" + key.strip().lower(), w)\n\n            return\n\n        self.hdulist = _astropy.pyfits.HDUList()\n\n        stddev = False\n        if hasattr(data, \"keys\"):\n            img_data = data\n            if fields is None:\n                fields = list(img_data.keys())\n            if hasattr(data, \"data_source\"):\n                stddev = getattr(data.data_source, \"moment\", 1) == 2\n        elif isinstance(data, np.ndarray):\n            if fields is None:\n                mylog.warning(\n                    \"No field name given for this array. Calling it 'image_data'.\"\n                )\n                fn = \"image_data\"\n                fields = [fn]\n            else:\n                fn = fields[0]\n            img_data = {fn: data}\n\n        for fd in fields:\n            if isinstance(fd, tuple):\n                fname = fd[1]\n            elif isinstance(fd, DerivedField):\n                fname = fd.name[1]\n            else:\n                fname = fd\n            if stddev:\n                fname += \"_stddev\"\n            self.fields.append(fname)\n\n        # Sanity checking names\n        s = set()\n        duplicates = {f for f in self.fields if f in s or s.add(f)}\n        if len(duplicates) > 0:\n            for i, fd in enumerate(self.fields):\n                if fd in duplicates:\n                    if isinstance(fields[i], tuple):\n                        ftype, fname = fields[i]\n                    elif isinstance(fields[i], DerivedField):\n                        ftype, fname = fields[i].name\n                    else:\n                        raise RuntimeError(\n                            f\"Cannot distinguish between fields with same name {fd}!\"\n                        )\n                    self.fields[i] = f\"{ftype}_{fname}\"\n\n        for is_first, _is_last, (i, (name, field)) in mark_ends(\n            enumerate(zip(self.fields, fields, strict=True))\n        ):\n            if name not in exclude_fields:\n                this_img = img_data[field]\n                if hasattr(img_data[field], \"units\"):\n                    has_code_unit = False\n                    for atom in this_img.units.expr.atoms():\n                        if str(atom).startswith(\"code\"):\n                            has_code_unit = True\n                    if has_code_unit:\n                        mylog.warning(\n                            \"Cannot generate an image with code \"\n                            \"units. Converting to units in CGS.\"\n                        )\n                        funits = this_img.units.get_base_equivalent(\"cgs\")\n                        this_img.convert_to_units(funits)\n                    else:\n                        funits = this_img.units\n                    self.field_units[name] = str(funits)\n                else:\n                    self.field_units[name] = \"dimensionless\"\n                mylog.info(\"Making a FITS image of field %s\", name)\n                if isinstance(this_img, ImageArray):\n                    if i == 0:\n                        self.shape = this_img.shape[::-1]\n                    this_img = np.asarray(this_img)\n                else:\n                    if i == 0:\n                        self.shape = this_img.shape\n                    this_img = np.asarray(this_img.T)\n                if is_first:\n                    hdu = _astropy.pyfits.PrimaryHDU(this_img)\n                else:\n                    hdu = _astropy.pyfits.ImageHDU(this_img)\n                hdu.name = name\n                hdu.header[\"btype\"] = name\n                hdu.header[\"bunit\"] = re.sub(\"()\", \"\", self.field_units[name])\n                for unit in (\"length\", \"time\", \"mass\", \"velocity\", \"magnetic\"):\n                    if unit == \"magnetic\":\n                        short_unit = \"bf\"\n                    else:\n                        short_unit = unit[0]\n                    key = f\"{short_unit}unit\"\n                    value = getattr(self, f\"{unit}_unit\")\n                    if value is not None:\n                        hdu.header[key] = float(value.value)\n                        hdu.header.comments[key] = f\"[{value.units}]\"\n                hdu.header[\"time\"] = float(self.current_time.value)\n                if hasattr(self, \"current_redshift\"):\n                    hdu.header[\"HUBBLE\"] = self.hubble_constant\n                    hdu.header[\"REDSHIFT\"] = self.current_redshift\n                self.hdulist.append(hdu)\n\n        self.dimensionality = len(self.shape)\n\n        if wcs is None:\n            w = _astropy.pywcs.WCS(\n                header=self.hdulist[0].header, naxis=self.dimensionality\n            )\n            # FRBs and covering grids are special cases where\n            # we have coordinate information, so we take advantage\n            # of this and construct the WCS object\n            if isinstance(img_data, FixedResolutionBuffer):\n                dx = (img_data.bounds[1] - img_data.bounds[0]).to_value(wcs_unit)\n                dy = (img_data.bounds[3] - img_data.bounds[2]).to_value(wcs_unit)\n                dx /= self.shape[0]\n                dy /= self.shape[1]\n                if img_ctr is not None:\n                    xctr, yctr = img_ctr\n                else:\n                    xctr = 0.5 * (img_data.bounds[1] + img_data.bounds[0]).to_value(\n                        wcs_unit\n                    )\n                    yctr = 0.5 * (img_data.bounds[3] + img_data.bounds[2]).to_value(\n                        wcs_unit\n                    )\n                center = [xctr, yctr]\n                cdelt = [dx, dy]\n            elif isinstance(img_data, YTCoveringGrid):\n                cdelt = img_data.dds.to_value(wcs_unit)\n                if img_ctr is not None:\n                    center = img_ctr\n                else:\n                    center = 0.5 * (img_data.left_edge + img_data.right_edge).to_value(\n                        wcs_unit\n                    )\n            else:\n                # If img_data is just an array we use the width and img_ctr\n                # parameters to determine the cell widths\n                if img_ctr is None:\n                    img_ctr = np.zeros(3)\n                if not is_sequence(width):\n                    width = [width] * self.dimensionality\n                if isinstance(width[0], YTQuantity):\n                    cdelt = [\n                        wh.to_value(wcs_unit) / n\n                        for wh, n in zip(width, self.shape, strict=True)\n                    ]\n                else:\n                    cdelt = [\n                        float(wh) / n for wh, n in zip(width, self.shape, strict=True)\n                    ]\n                center = img_ctr[: self.dimensionality]\n            w.wcs.crpix = 0.5 * (np.array(self.shape) + 1)\n            w.wcs.crval = center\n            w.wcs.cdelt = cdelt\n            w.wcs.ctype = [\"linear\"] * self.dimensionality\n            w.wcs.cunit = [wcs_unit] * self.dimensionality\n            self.set_wcs(w)\n        else:\n            self.set_wcs(wcs)\n\n    def _fix_current_time(self, ds, current_time):\n        if ds is None:\n            registry = None\n        else:\n            registry = ds.unit_registry\n        tunit = Unit(self.time_unit, registry=registry)\n        if current_time is None:\n            if ds is not None:\n                current_time = ds.current_time\n            else:\n                self.current_time = YTQuantity(0.0, \"s\")\n                return\n        elif isinstance(current_time, numeric_type):\n            current_time = YTQuantity(current_time, tunit)\n        elif isinstance(current_time, tuple):\n            current_time = YTQuantity(current_time[0], current_time[1])\n        self.current_time = current_time.to(tunit)\n\n    def _set_units(self, ds, base_units):\n        if ds is not None:\n            if getattr(ds, \"cosmological_simulation\", False):\n                self.hubble_constant = ds.hubble_constant\n                self.current_redshift = ds.current_redshift\n        attrs = (\n            \"length_unit\",\n            \"mass_unit\",\n            \"time_unit\",\n            \"velocity_unit\",\n            \"magnetic_unit\",\n        )\n        cgs_units = (\"cm\", \"g\", \"s\", \"cm/s\", \"gauss\")\n        for unit, attr, cgs_unit in zip(base_units, attrs, cgs_units, strict=True):\n            if unit is None:\n                if ds is not None:\n                    u = getattr(ds, attr, None)\n                elif attr == \"velocity_unit\":\n                    u = self.length_unit / self.time_unit\n                elif attr == \"magnetic_unit\":\n                    u = np.sqrt(\n                        4.0\n                        * np.pi\n                        * self.mass_unit\n                        / (self.time_unit**2 * self.length_unit)\n                    )\n                else:\n                    u = cgs_unit\n            else:\n                u = unit\n\n            if isinstance(u, str):\n                uq = YTQuantity(1.0, u)\n            elif isinstance(u, numeric_type):\n                uq = YTQuantity(u, cgs_unit)\n            elif isinstance(u, YTQuantity):\n                uq = u.copy()\n            elif isinstance(u, tuple):\n                uq = YTQuantity(u[0], u[1])\n            else:\n                uq = None\n\n            if uq is not None:\n                atoms = {str(a) for a in uq.units.expr.atoms()}\n                if hasattr(self, \"hubble_constant\"):\n                    # Don't store cosmology units\n                    if str(uq.units).startswith(\"cm\") or \"h\" in atoms or \"a\" in atoms:\n                        uq.convert_to_cgs()\n                if any(a.startswith(\"code\") for a in atoms):\n                    # Don't store code units\n                    mylog.warning(\n                        \"Cannot use code units of '%s' \"\n                        \"when creating a FITSImageData instance! \"\n                        \"Converting to a cgs equivalent.\",\n                        uq.units,\n                    )\n                    uq.convert_to_cgs()\n\n            if attr == \"length_unit\" and uq.value != 1.0:\n                mylog.warning(\"Converting length units from %s to %s.\", uq, uq.units)\n                uq = YTQuantity(1.0, uq.units)\n\n            setattr(self, attr, uq)\n\n    def _set_units_from_header(self, header):\n        if \"hubble\" in header:\n            self.hubble_constant = header[\"HUBBLE\"]\n            self.current_redshift = header[\"REDSHIFT\"]\n        for unit in [\"length\", \"time\", \"mass\", \"velocity\", \"magnetic\"]:\n            if unit == \"magnetic\":\n                key = \"BFUNIT\"\n            else:\n                key = unit[0].upper() + \"UNIT\"\n            if key not in header:\n                continue\n            u = header.comments[key].strip(\"[]\")\n            uq = YTQuantity(header[key], u)\n            setattr(self, unit + \"_unit\", uq)\n\n    def set_wcs(self, wcs, wcsname=None, suffix=None):\n        \"\"\"\n        Set the WCS coordinate information for all images\n        with a WCS object *wcs*.\n        \"\"\"\n        if wcsname is None:\n            wcs.wcs.name = \"yt\"\n        else:\n            wcs.wcs.name = wcsname\n        h = wcs.to_header()\n        if suffix is None:\n            self.wcs = wcs\n        else:\n            setattr(self, \"wcs\" + suffix, wcs)\n        for img in self.hdulist:\n            for k, v in h.items():\n                kk = k\n                if suffix is not None:\n                    kk += suffix\n                img.header[kk] = v\n\n    def change_image_name(self, old_name, new_name):\n        \"\"\"\n        Change the name of a FITS image.\n\n        Parameters\n        ----------\n        old_name : string\n            The old name of the image.\n        new_name : string\n            The new name of the image.\n        \"\"\"\n        idx = self.fields.index(old_name)\n        self.hdulist[idx].name = new_name\n        self.hdulist[idx].header[\"BTYPE\"] = new_name\n        self.field_units[new_name] = self.field_units.pop(old_name)\n        self.fields[idx] = new_name\n\n    def convolve(self, field, kernel, **kwargs):\n        \"\"\"\n        Convolve an image with a kernel, either a simple\n        Gaussian kernel or one provided by AstroPy. Currently,\n        this only works for 2D images.\n\n        All keyword arguments are passed to\n        :meth:`~astropy.convolution.convolve`.\n\n        Parameters\n        ----------\n        field : string\n            The name of the field to convolve.\n        kernel : float, YTQuantity, (value, unit) tuple, or AstroPy Kernel object\n            The kernel to convolve the image with. If this is an AstroPy Kernel\n            object, the image will be convolved with it. Otherwise, it is\n            assumed that the kernel is a Gaussian and that this value is\n            the standard deviation. If a float, it is assumed that the units\n            are pixels, but a (value, unit) tuple or YTQuantity can be supplied\n            to specify the standard deviation in physical units.\n\n        Examples\n        --------\n        >>> fid = FITSSlice(ds, \"z\", (\"gas\", \"density\"))\n        >>> fid.convolve(\"density\", (3.0, \"kpc\"))\n        \"\"\"\n        if self.dimensionality == 3:\n            raise RuntimeError(\"Convolution currently only works for 2D FITSImageData!\")\n        conv = _astropy.conv\n        if field not in self.keys():\n            raise KeyError(f\"{field} not an image!\")\n        idx = self.fields.index(field)\n        if not isinstance(kernel, conv.Kernel):\n            if not isinstance(kernel, numeric_type):\n                unit = str(self.wcs.wcs.cunit[0])\n                pix_scale = YTQuantity(self.wcs.wcs.cdelt[0], unit)\n                if isinstance(kernel, tuple):\n                    stddev = YTQuantity(kernel[0], kernel[1]).to(unit)\n                else:\n                    stddev = kernel.to(unit)\n                kernel = stddev / pix_scale\n            kernel = conv.Gaussian2DKernel(x_stddev=kernel)\n        self.hdulist[idx].data = conv.convolve(self.hdulist[idx].data, kernel, **kwargs)\n\n    def update_header(self, field, key, value):\n        \"\"\"\n        Update the FITS header for *field* with a\n        *key*, *value* pair. If *field* == \"all\", all\n        headers will be updated.\n        \"\"\"\n        if field == \"all\":\n            for img in self.hdulist:\n                img.header[key] = value\n        else:\n            if field not in self.keys():\n                raise KeyError(f\"{field} not an image!\")\n            idx = self.fields.index(field)\n            self.hdulist[idx].header[key] = value\n\n    def keys(self):\n        return self.fields\n\n    def has_key(self, key):\n        return key in self.fields\n\n    def values(self):\n        return [self[k] for k in self.fields]\n\n    def items(self):\n        return [(k, self[k]) for k in self.fields]\n\n    def __getitem__(self, item):\n        return UnitfulHDU(self.hdulist[item])\n\n    def __repr__(self):\n        return str([self[k] for k in self.keys()])\n\n    def info(self, output=None):\n        \"\"\"\n        Summarize the info of the HDUs in this `FITSImageData`\n        instance.\n\n        Note that this function prints its results to the console---it\n        does not return a value.\n\n        Parameters\n        ----------\n        output : file, boolean, optional\n            A file-like object to write the output to.  If `False`, does not\n            output to a file and instead returns a list of tuples representing\n            the FITSImageData info.  Writes to ``sys.stdout`` by default.\n        \"\"\"\n        hinfo = self.hdulist.info(output=False)\n        num_cols = len(hinfo[0])\n        if output is None:\n            output = sys.stdout\n        if num_cols == 8:\n            header = \"No.    Name      Ver    Type      Cards   Dimensions   Format     Units\"\n            format = \"{:3d}  {:10}  {:3} {:11}  {:5d}   {}   {}   {}\"\n        else:\n            header = (\n                \"No.    Name         Type      Cards   Dimensions   Format     Units\"\n            )\n            format = \"{:3d}  {:10}  {:11}  {:5d}   {}   {}   {}\"\n        if self.hdulist._file is None:\n            name = \"(No file associated with this FITSImageData)\"\n        else:\n            name = self.hdulist._file.name\n        results = [f\"Filename: {name}\", header]\n        for line in hinfo:\n            units = self.field_units[self.hdulist[line[0]].header[\"btype\"]]\n            summary = tuple(list(line[:-1]) + [units])\n            if output:\n                results.append(format.format(*summary))\n            else:\n                results.append(summary)\n\n        if output:\n            output.write(\"\\n\".join(results))\n            output.write(\"\\n\")\n            output.flush()\n        else:\n            return results[2:]\n\n    @parallel_root_only\n    def writeto(self, fileobj, fields=None, overwrite=False, **kwargs):\n        r\"\"\"\n        Write all of the fields or a subset of them to a FITS file.\n\n        Parameters\n        ----------\n        fileobj : string\n            The name of the file to write to.\n        fields : list of strings, optional\n            The fields to write to the file. If not specified\n            all of the fields in the buffer will be written.\n        overwrite : boolean\n            Whether or not to overwrite a previously existing file.\n            Default: False\n        **kwargs\n            Additional keyword arguments are passed to\n            :meth:`~astropy.io.fits.HDUList.writeto`.\n        \"\"\"\n        if fields is None:\n            hdus = self.hdulist\n        else:\n            hdus = _astropy.pyfits.HDUList()\n            for field in fields:\n                hdus.append(self.hdulist[field])\n        hdus.writeto(fileobj, overwrite=overwrite, **kwargs)\n\n    def to_aplpy(self, **kwargs):\n        \"\"\"\n        Use APLpy (http://aplpy.github.io) for plotting. Returns an\n        `aplpy.FITSFigure` instance. All keyword arguments are passed\n        to the `aplpy.FITSFigure` constructor.\n        \"\"\"\n        import aplpy\n\n        return aplpy.FITSFigure(self.hdulist, **kwargs)\n\n    def get_data(self, field):\n        \"\"\"\n        Return the data array of the image corresponding to *field*\n        with units attached. Deprecated.\n        \"\"\"\n        return self[field].data\n\n    def set_unit(self, field, units):\n        \"\"\"\n        Set the units of *field* to *units*.\n        \"\"\"\n        if field not in self.keys():\n            raise KeyError(f\"{field} not an image!\")\n        idx = self.fields.index(field)\n        new_data = YTArray(self.hdulist[idx].data, self.field_units[field]).to(units)\n        self.hdulist[idx].data = new_data.v\n        self.hdulist[idx].header[\"bunit\"] = units\n        self.field_units[field] = units\n\n    def pop(self, key):\n        \"\"\"\n        Remove a field with name *key*\n        and return it as a new FITSImageData\n        instance.\n        \"\"\"\n        if key not in self.keys():\n            raise KeyError(f\"{key} not an image!\")\n        idx = self.fields.index(key)\n        im = self.hdulist.pop(idx)\n        self.field_units.pop(key)\n        self.fields.remove(key)\n        f = _astropy.pyfits.PrimaryHDU(im.data, header=im.header)\n        return FITSImageData(f, current_time=f.header[\"TIME\"], unit_header=f.header)\n\n    def close(self):\n        self.hdulist.close()\n\n    @classmethod\n    def from_file(cls, filename):\n        \"\"\"\n        Generate a FITSImageData instance from one previously written to\n        disk.\n\n        Parameters\n        ----------\n        filename : string\n            The name of the file to open.\n        \"\"\"\n        f = _astropy.pyfits.open(filename, lazy_load_hdus=False)\n        return cls(f, current_time=f[0].header[\"TIME\"], unit_header=f[0].header)\n\n    @classmethod\n    def from_images(cls, image_list):\n        \"\"\"\n        Generate a new FITSImageData instance from a list of FITSImageData\n        instances.\n\n        Parameters\n        ----------\n        image_list : list of FITSImageData instances\n            The images to be combined.\n        \"\"\"\n        image_list = image_list if isinstance(image_list, list) else [image_list]\n        first_image = first(image_list)\n\n        w = first_image.wcs\n        img_shape = first_image.shape\n        data = []\n        for fid in image_list:\n            assert_same_wcs(w, fid.wcs)\n            if img_shape != fid.shape:\n                raise RuntimeError(\"Images do not have the same shape!\")\n            for hdu in fid.hdulist:\n                if len(data) == 0:\n                    data.append(_astropy.pyfits.PrimaryHDU(hdu.data, header=hdu.header))\n                else:\n                    data.append(_astropy.pyfits.ImageHDU(hdu.data, header=hdu.header))\n        data = _astropy.pyfits.HDUList(data)\n        return cls(\n            data,\n            current_time=first_image.current_time,\n            unit_header=first_image[0].header,\n        )\n\n    def create_sky_wcs(\n        self,\n        sky_center,\n        sky_scale,\n        ctype=None,\n        crota=None,\n        cd=None,\n        pc=None,\n        wcsname=\"celestial\",\n        replace_old_wcs=True,\n    ):\n        \"\"\"\n        Takes a Cartesian WCS and converts it to one in a\n        sky-based coordinate system.\n\n        Parameters\n        ----------\n        sky_center : iterable of floats\n            Reference coordinates of the WCS in degrees.\n        sky_scale : tuple or YTQuantity\n            Conversion between an angle unit and a length unit,\n            e.g. (3.0, \"arcsec/kpc\")\n        ctype : list of strings, optional\n            The type of the coordinate system to create. Default:\n            A \"tangential\" projection.\n        crota : 2-element ndarray, optional\n            Rotation angles between cartesian coordinates and\n            the celestial coordinates.\n        cd : 2x2-element ndarray, optional\n            Dimensioned coordinate transformation matrix.\n        pc : 2x2-element ndarray, optional\n            Coordinate transformation matrix.\n        wcsname : string, optional\n            The name of the WCS to be stored in the FITS header.\n        replace_old_wcs : boolean, optional\n            If True, the original WCS will be overwritten but\n            first copied to a second WCS (\"WCSAXESA\"). If False, this\n            new WCS will be placed into the second WCS.\n        \"\"\"\n        if ctype is None:\n            ctype = [\"RA---TAN\", \"DEC--TAN\"]\n        old_wcs = self.wcs\n        naxis = old_wcs.naxis\n        crval = [sky_center[0], sky_center[1]]\n        if isinstance(sky_scale, YTQuantity):\n            scaleq = sky_scale\n        else:\n            scaleq = YTQuantity(sky_scale[0], sky_scale[1])\n        if scaleq.units.dimensions != dimensions.angle / dimensions.length:\n            raise RuntimeError(\n                f\"sky_scale {sky_scale} not in correct dimensions of angle/length!\"\n            )\n        deltas = old_wcs.wcs.cdelt\n        units = [str(unit) for unit in old_wcs.wcs.cunit]\n        new_dx = (YTQuantity(-deltas[0], units[0]) * scaleq).in_units(\"deg\")\n        new_dy = (YTQuantity(deltas[1], units[1]) * scaleq).in_units(\"deg\")\n        new_wcs = _astropy.pywcs.WCS(naxis=naxis)\n        cdelt = [new_dx.v, new_dy.v]\n        cunit = [\"deg\"] * 2\n        if naxis == 3:\n            crval.append(old_wcs.wcs.crval[2])\n            cdelt.append(old_wcs.wcs.cdelt[2])\n            ctype.append(old_wcs.wcs.ctype[2])\n            cunit.append(old_wcs.wcs.cunit[2])\n        new_wcs.wcs.crpix = old_wcs.wcs.crpix\n        new_wcs.wcs.cdelt = cdelt\n        new_wcs.wcs.crval = crval\n        new_wcs.wcs.cunit = cunit\n        new_wcs.wcs.ctype = ctype\n        if crota is not None:\n            new_wcs.wcs.crota = crota\n        if cd is not None:\n            new_wcs.wcs.cd = cd\n        if pc is not None:\n            new_wcs.wcs.cd = pc\n        if replace_old_wcs:\n            self.set_wcs(new_wcs, wcsname=wcsname)\n            self.set_wcs(old_wcs, wcsname=\"yt\", suffix=\"a\")\n        else:\n            self.set_wcs(new_wcs, wcsname=wcsname, suffix=\"a\")\n\n\nclass FITSImageBuffer(FITSImageData):\n    pass\n\n\ndef sanitize_fits_unit(unit):\n    if unit == \"Mpc\":\n        mylog.info(\"Changing FITS file length unit to kpc.\")\n        unit = \"kpc\"\n    elif unit == \"au\":\n        unit = \"AU\"\n    return unit\n\n\n# This list allows one to determine which axes are the\n# correct axes of the image in a right-handed coordinate\n# system depending on which axis is sliced or projected\naxis_wcs = [[1, 2], [2, 0], [0, 1]]\n\n\ndef construct_image(\n    ds, axis, data_source, center, image_res, width, length_unit, origin=\"domain\"\n):\n    if width is None:\n        width = ds.domain_width[axis_wcs[axis]]\n        unit = ds.get_smallest_appropriate_unit(width[0])\n        mylog.info(\n            \"Making an image of the entire domain, \"\n            \"so setting the center to the domain center.\"\n        )\n    else:\n        width = ds.coordinates.sanitize_width(axis, width, None)\n        unit = str(width[0].units)\n    if is_sequence(image_res):\n        nx, ny = image_res\n    else:\n        nx, ny = image_res, image_res\n    dx = width[0] / nx\n    dy = width[1] / ny\n    crpix = [0.5 * (nx + 1), 0.5 * (ny + 1)]\n    if unit == \"unitary\":\n        unit = ds.get_smallest_appropriate_unit(ds.domain_width.max())\n    elif unit == \"code_length\":\n        unit = ds.get_smallest_appropriate_unit(ds.quan(1.0, \"code_length\"))\n    unit = sanitize_fits_unit(unit)\n    if length_unit is None:\n        length_unit = unit\n    if any(char.isdigit() for char in length_unit) and \"*\" in length_unit:\n        length_unit = length_unit.split(\"*\")[-1]\n    cunit = [length_unit] * 2\n    ctype = [\"LINEAR\"] * 2\n    cdelt = [dx.in_units(length_unit), dy.in_units(length_unit)]\n    if origin == \"domain\":\n        if is_sequence(axis):\n            crval = center.in_units(length_unit)\n        else:\n            crval = [center[idx].in_units(length_unit) for idx in axis_wcs[axis]]\n    elif origin == \"image\":\n        crval = np.zeros(2)\n    if hasattr(data_source, \"to_frb\"):\n        if is_sequence(axis):\n            frb = data_source.to_frb(width[0], (nx, ny), height=width[1])\n        else:\n            frb = data_source.to_frb(width[0], (nx, ny), center=center, height=width[1])\n    elif isinstance(data_source, ParticleDummyDataSource):\n        if hasattr(data_source, \"normal_vector\"):\n            # If we have a normal vector, this means\n            # that the data source is off-axis\n            bounds = (-width[0] / 2, width[0] / 2, -width[1] / 2, width[1] / 2)\n            periodic = False\n        else:\n            # Otherwise, this is an on-axis data source\n            axes = axis_wcs[axis]\n            bounds = (\n                center[axes[0]] - width[0] / 2,\n                center[axes[0]] + width[0] / 2,\n                center[axes[1]] - width[1] / 2,\n                center[axes[1]] + width[1] / 2,\n            )\n            periodic = all(ds.periodicity)\n        frb = ParticleImageBuffer(data_source, bounds, (nx, ny), periodic=periodic)\n    else:\n        frb = None\n    w = _astropy.pywcs.WCS(naxis=2)\n    w.wcs.crpix = crpix\n    w.wcs.cdelt = cdelt\n    w.wcs.crval = crval\n    w.wcs.cunit = cunit\n    w.wcs.ctype = ctype\n    return w, frb, length_unit\n\n\ndef assert_same_wcs(wcs1, wcs2):\n    from numpy.testing import assert_allclose\n\n    assert wcs1.naxis == wcs2.naxis\n    for i in range(wcs1.naxis):\n        assert wcs1.wcs.cunit[i] == wcs2.wcs.cunit[i]\n        assert wcs1.wcs.ctype[i] == wcs2.wcs.ctype[i]\n    assert_allclose(wcs1.wcs.crpix, wcs2.wcs.crpix)\n    assert_allclose(wcs1.wcs.cdelt, wcs2.wcs.cdelt)\n    assert_allclose(wcs1.wcs.crval, wcs2.wcs.crval)\n    crota1 = getattr(wcs1.wcs, \"crota\", None)\n    crota2 = getattr(wcs2.wcs, \"crota\", None)\n    if crota1 is None or crota2 is None:\n        assert crota1 == crota2\n    else:\n        assert_allclose(wcs1.wcs.crota, wcs2.wcs.crota)\n    cd1 = getattr(wcs1.wcs, \"cd\", None)\n    cd2 = getattr(wcs2.wcs, \"cd\", None)\n    if cd1 is None or cd2 is None:\n        assert cd1 == cd2\n    else:\n        assert_allclose(wcs1.wcs.cd, wcs2.wcs.cd)\n    pc1 = getattr(wcs1.wcs, \"pc\", None)\n    pc2 = getattr(wcs2.wcs, \"pc\", None)\n    if pc1 is None or pc2 is None:\n        assert pc1 == pc2\n    else:\n        assert_allclose(wcs1.wcs.pc, wcs2.wcs.pc)\n\n\nclass FITSSlice(FITSImageData):\n    r\"\"\"\n    Generate a FITSImageData of an on-axis slice.\n\n    Parameters\n    ----------\n    ds : :class:`~yt.data_objects.static_output.Dataset`\n        The dataset object.\n    axis : character or integer\n        The axis of the slice. One of \"x\",\"y\",\"z\", or 0,1,2.\n    fields : string or list of strings\n        The fields to slice\n    image_res : an int or 2-tuple of ints\n        Specify the resolution of the resulting image. A single value will be\n        used for both axes, whereas a tuple of values will be used for the\n        individual axes. Default: 512\n    center : 'center', 'c', 'left', 'l', 'right', 'r', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n        The domain edges along the selected *axis* can be selected with\n        'left'/'l' and 'right'/'r' respectively.\n\n    width : tuple or a float.\n        Width can have four different formats to support variable x and y\n        widths.  They are:\n\n        ==================================     =======================\n        format                                 example\n        ==================================     =======================\n        (float, string)                        (10,'kpc')\n        ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n        float                                  0.2\n        (float, float)                         (0.2, 0.3)\n        ==================================     =======================\n\n        For example, (10, 'kpc') specifies a width that is 10 kiloparsecs wide\n        in the x and y directions, ((10,'kpc'),(15,'kpc')) specifies a width\n        that is 10 kiloparsecs wide along the x axis and 15 kiloparsecs wide\n        along the y axis.  In the other two examples, code units are assumed,\n        for example (0.2, 0.3) specifies a width that has an x width of 0.2 and\n        a y width of 0.3 in code units.\n    length_unit : string, optional\n        the length units that the coordinates are written in. The default\n        is to use the default length unit of the dataset.\n    origin : string\n        The origin of the coordinate system in the file. If \"domain\", then the\n        center coordinates will be the same as the center of the image as\n        defined by the *center* keyword argument. If \"image\", then the center\n        coordinates will be set to (0,0). Default: \"domain\"\n    \"\"\"\n\n    def __init__(\n        self,\n        ds,\n        axis,\n        fields,\n        image_res=512,\n        center=\"center\",\n        width=None,\n        length_unit=None,\n        *,\n        origin=\"domain\",\n        **kwargs,\n    ):\n        fields = list(iter_fields(fields))\n        axis = fix_axis(axis, ds)\n        center, dcenter = ds.coordinates.sanitize_center(center, axis)\n        slc = ds.slice(axis, center[axis], **kwargs)\n        w, frb, lunit = construct_image(\n            ds,\n            axis,\n            slc,\n            dcenter,\n            image_res,\n            width,\n            length_unit,\n            origin=origin,\n        )\n        super().__init__(frb, fields=fields, length_unit=lunit, wcs=w)\n\n\nclass FITSProjection(FITSImageData):\n    r\"\"\"\n    Generate a FITSImageData of an on-axis projection.\n\n    Parameters\n    ----------\n    ds : :class:`~yt.data_objects.static_output.Dataset`\n        The dataset object.\n    axis : character or integer\n        The axis along which to project. One of \"x\",\"y\",\"z\", or 0,1,2.\n    fields : string or list of strings\n        The fields to project\n    image_res : an int or 2-tuple of ints\n        Specify the resolution of the resulting image. A single value will be\n        used for both axes, whereas a tuple of values will be used for the\n        individual axes. Default: 512\n    center : 'center', 'c', 'left', 'l', 'right', 'r', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n        The domain edges along the selected *axis* can be selected with\n        'left'/'l' and 'right'/'r' respectively.\n    width : tuple or a float.\n        Width can have four different formats to support variable\n        x and y widths.  They are:\n\n        ==================================     =======================\n        format                                 example\n        ==================================     =======================\n        (float, string)                        (10,'kpc')\n        ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n        float                                  0.2\n        (float, float)                         (0.2, 0.3)\n        ==================================     =======================\n\n        For example, (10, 'kpc') specifies a width that is 10 kiloparsecs\n        wide in the x and y directions, ((10,'kpc'),(15,'kpc')) specifies a\n        width that is 10 kiloparsecs wide along the x-axis and 15\n        kiloparsecs wide along the y-axis.  In the other two examples, code\n        units are assumed, for example (0.2, 0.3) specifies a width that has an\n        x width of 0.2 and a y width of 0.3 in code units.\n    weight_field : string\n        The field used to weight the projection.\n    length_unit : string, optional\n        the length units that the coordinates are written in. The default\n        is to use the default length unit of the dataset.\n    origin : string\n        The origin of the coordinate system in the file. If \"domain\", then the\n        center coordinates will be the same as the center of the image as\n        defined by the *center* keyword argument. If \"image\", then the center\n        coordinates will be set to (0,0). Default: \"domain\"\n    moment : integer, optional\n        for a weighted projection, moment = 1 (the default) corresponds to a\n        weighted average. moment = 2 corresponds to a weighted standard\n        deviation.\n    \"\"\"\n\n    def __init__(\n        self,\n        ds,\n        axis,\n        fields,\n        image_res=512,\n        center=\"center\",\n        width=None,\n        weight_field=None,\n        length_unit=None,\n        *,\n        origin=\"domain\",\n        moment=1,\n        **kwargs,\n    ):\n        fields = list(iter_fields(fields))\n        axis = fix_axis(axis, ds)\n        center, dcenter = ds.coordinates.sanitize_center(center, axis)\n        prj = ds.proj(\n            fields[0], axis, weight_field=weight_field, moment=moment, **kwargs\n        )\n        w, frb, lunit = construct_image(\n            ds,\n            axis,\n            prj,\n            dcenter,\n            image_res,\n            width,\n            length_unit,\n            origin=origin,\n        )\n        super().__init__(frb, fields=fields, length_unit=lunit, wcs=w)\n\n\nclass FITSParticleProjection(FITSImageData):\n    r\"\"\"\n    Generate a FITSImageData of an on-axis projection of a\n    particle field.\n\n    Parameters\n    ----------\n    ds : :class:`~yt.data_objects.static_output.Dataset`\n        The dataset object.\n    axis : character or integer\n        The axis along which to project. One of \"x\",\"y\",\"z\", or 0,1,2.\n    fields : string or list of strings\n        The fields to project\n    image_res : an int or 2-tuple of ints\n        Specify the resolution of the resulting image. A single value will be\n        used for both axes, whereas a tuple of values will be used for the\n        individual axes. Default: 512\n    center : 'center', 'c', 'left', 'l', 'right', 'r', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n        The domain edges along the selected *axis* can be selected with\n        'left'/'l' and 'right'/'r' respectively.\n    width : tuple or a float.\n        Width can have four different formats to support variable\n        x and y widths.  They are:\n\n        ==================================     =======================\n        format                                 example\n        ==================================     =======================\n        (float, string)                        (10,'kpc')\n        ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n        float                                  0.2\n        (float, float)                         (0.2, 0.3)\n        ==================================     =======================\n\n        For example, (10, 'kpc') specifies a width that is 10 kiloparsecs\n        wide in the x and y directions, ((10,'kpc'),(15,'kpc')) specifies a\n        width that is 10 kiloparsecs wide along the x-axis and 15\n        kiloparsecs wide along the y-axis.  In the other two examples, code\n        units are assumed, for example (0.2, 0.3) specifies a width that has an\n        x width of 0.2 and a y width of 0.3 in code units.\n    depth : A tuple or a float\n         A tuple containing the depth to project through and the string\n         key of the unit: (width, 'unit').  If set to a float, code units\n         are assumed. Defaults to the entire domain.\n    weight_field : string\n        The field used to weight the projection.\n    length_unit : string, optional\n        the length units that the coordinates are written in. The default\n        is to use the default length unit of the dataset.\n    deposition : string, optional\n        Controls the order of the interpolation of the particles onto the\n        mesh. \"ngp\" is 0th-order \"nearest-grid-point\" method (the default),\n        \"cic\" is 1st-order \"cloud-in-cell\".\n    density : boolean, optional\n        If True, the quantity to be projected will be divided by the area of\n        the cells, to make a projected density of the quantity. Default: False\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source : yt.data_objects.data_containers.YTSelectionContainer, optional\n        If specified, this will be the data source used for selecting regions\n        to project.\n    origin : string\n        The origin of the coordinate system in the file. If \"domain\", then the\n        center coordinates will be the same as the center of the image as\n        defined by the *center* keyword argument. If \"image\", then the center\n        coordinates will be set to (0,0). Default: \"domain\"\n    \"\"\"\n\n    def __init__(\n        self,\n        ds,\n        axis,\n        fields,\n        image_res=512,\n        center=\"center\",\n        width=None,\n        depth=(1, \"1\"),\n        weight_field=None,\n        length_unit=None,\n        deposition=\"ngp\",\n        density=False,\n        field_parameters=None,\n        data_source=None,\n        *,\n        origin=\"domain\",\n    ):\n        fields = list(iter_fields(fields))\n        axis = fix_axis(axis, ds)\n        center, dcenter = ds.coordinates.sanitize_center(center, axis)\n        width = ds.coordinates.sanitize_width(axis, width, depth)\n        width[-1].convert_to_units(width[0].units)\n\n        if field_parameters is None:\n            field_parameters = {}\n\n        ps = ParticleAxisAlignedDummyDataSource(\n            center,\n            ds,\n            axis,\n            width,\n            fields,\n            weight_field=weight_field,\n            field_parameters=field_parameters,\n            data_source=data_source,\n            deposition=deposition,\n            density=density,\n        )\n        w, frb, lunit = construct_image(\n            ds, axis, ps, dcenter, image_res, width, length_unit, origin=origin\n        )\n        super().__init__(frb, fields=fields, length_unit=lunit, wcs=w)\n\n\nclass FITSParticleOffAxisProjection(FITSImageData):\n    r\"\"\"\n    Generate a FITSImageData of an off-axis projection of a\n    particle field.\n\n    Parameters\n    ----------\n    ds : :class:`~yt.data_objects.static_output.Dataset`\n        The dataset object.\n    axis : character or integer\n        The axis along which to project. One of \"x\",\"y\",\"z\", or 0,1,2.\n    fields : string or list of strings\n        The fields to project\n    image_res : an int or 2-tuple of ints\n        Specify the resolution of the resulting image. A single value will be\n        used for both axes, whereas a tuple of values will be used for the\n        individual axes. Default: 512\n    center : A sequence of floats, a string, or a tuple.\n        The coordinate of the center of the image. If set to 'c', 'center' or\n        left blank, the plot is centered on the middle of the domain. If set\n        to 'max' or 'm', the center will be located at the maximum of the\n        ('gas', 'density') field. Centering on the max or min of a specific\n        field is supported by providing a tuple such as (\"min\",\"temperature\")\n        or (\"max\",\"dark_matter_density\"). Units can be specified by passing in\n        *center* as a tuple containing a coordinate and string unit name or by\n        passing in a YTArray. If a list or unitless array is supplied, code\n        units are assumed.\n    width : tuple or a float.\n        Width can have four different formats to support variable\n        x and y widths.  They are:\n\n        ==================================     =======================\n        format                                 example\n        ==================================     =======================\n        (float, string)                        (10,'kpc')\n        ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n        float                                  0.2\n        (float, float)                         (0.2, 0.3)\n        ==================================     =======================\n\n        For example, (10, 'kpc') specifies a width that is 10 kiloparsecs\n        wide in the x and y directions, ((10,'kpc'),(15,'kpc')) specifies a\n        width that is 10 kiloparsecs wide along the x-axis and 15\n        kiloparsecs wide along the y-axis.  In the other two examples, code\n        units are assumed, for example (0.2, 0.3) specifies a width that has an\n        x width of 0.2 and a y width of 0.3 in code units.\n    depth : A tuple or a float\n         A tuple containing the depth to project through and the string\n         key of the unit: (width, 'unit').  If set to a float, code units\n         are assumed. Defaults to the entire domain.\n    weight_field : string\n        The field used to weight the projection.\n    length_unit : string, optional\n        the length units that the coordinates are written in. The default\n        is to use the default length unit of the dataset.\n    deposition : string, optional\n        Controls the order of the interpolation of the particles onto the\n        mesh. \"ngp\" is 0th-order \"nearest-grid-point\" method (the default),\n        \"cic\" is 1st-order \"cloud-in-cell\".\n    density : boolean, optional\n        If True, the quantity to be projected will be divided by the area of\n        the cells, to make a projected density of the quantity. Default: False\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source : yt.data_objects.data_containers.YTSelectionContainer, optional\n        If specified, this will be the data source used for selecting regions\n        to project.\n    origin : string\n        The origin of the coordinate system in the file. If \"domain\", then the\n        center coordinates will be the same as the center of the image as\n        defined by the *center* keyword argument. If \"image\", then the center\n        coordinates will be set to (0,0). Default: \"domain\"\n    \"\"\"\n\n    def __init__(\n        self,\n        ds,\n        normal,\n        fields,\n        image_res=512,\n        center=\"c\",\n        width=None,\n        depth=(1, \"1\"),\n        weight_field=None,\n        length_unit=None,\n        deposition=\"ngp\",\n        density=False,\n        field_parameters=None,\n        data_source=None,\n        north_vector=None,\n    ):\n        fields = list(iter_fields(fields))\n        center, dcenter = ds.coordinates.sanitize_center(center, None)\n        width = ds.coordinates.sanitize_width(normal, width, depth)\n        wd = tuple(el.in_units(\"code_length\").v for el in width)\n\n        if field_parameters is None:\n            field_parameters = {}\n\n        ps = ParticleOffAxisDummyDataSource(\n            center,\n            ds,\n            normal,\n            wd,\n            fields,\n            weight_field=weight_field,\n            field_parameters=field_parameters,\n            data_source=data_source,\n            deposition=deposition,\n            density=density,\n            north_vector=north_vector,\n        )\n        w, frb, lunit = construct_image(\n            ds,\n            normal,\n            ps,\n            dcenter,\n            image_res,\n            width,\n            length_unit,\n            origin=\"image\",\n        )\n        super().__init__(frb, fields=fields, length_unit=lunit, wcs=w)\n\n\nclass FITSOffAxisSlice(FITSImageData):\n    r\"\"\"\n    Generate a FITSImageData of an off-axis slice.\n\n    Parameters\n    ----------\n    ds : :class:`~yt.data_objects.static_output.Dataset`\n        The dataset object.\n    normal : a sequence of floats\n        The vector normal to the projection plane.\n    fields : string or list of strings\n        The fields to slice\n    image_res : an int or 2-tuple of ints\n        Specify the resolution of the resulting image. A single value will be\n        used for both axes, whereas a tuple of values will be used for the\n        individual axes. Default: 512\n    center : 'center', 'c', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n    width : tuple or a float.\n        Width can have four different formats to support variable\n        x and y widths.  They are:\n\n        ==================================     =======================\n        format                                 example\n        ==================================     =======================\n        (float, string)                        (10,'kpc')\n        ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n        float                                  0.2\n        (float, float)                         (0.2, 0.3)\n        ==================================     =======================\n\n        For example, (10, 'kpc') specifies a width that is 10 kiloparsecs\n        wide in the x and y directions, ((10,'kpc'),(15,'kpc')) specifies a\n        width that is 10 kiloparsecs wide along the x axis and 15\n        kiloparsecs wide along the y axis.  In the other two examples, code\n        units are assumed, for example (0.2, 0.3) specifies a width that has an\n        x width of 0.2 and a y width of 0.3 in code units.\n    north_vector : a sequence of floats\n        A vector defining the 'up' direction in the plot.  This\n        option sets the orientation of the slicing plane.  If not\n        set, an arbitrary grid-aligned north-vector is chosen.\n    length_unit : string, optional\n        the length units that the coordinates are written in. The default\n        is to use the default length unit of the dataset.\n    \"\"\"\n\n    def __init__(\n        self,\n        ds,\n        normal,\n        fields,\n        image_res=512,\n        center=\"center\",\n        width=None,\n        north_vector=None,\n        length_unit=None,\n    ):\n        fields = list(iter_fields(fields))\n        center, dcenter = ds.coordinates.sanitize_center(center, axis=None)\n        cut = ds.cutting(normal, center, north_vector=north_vector)\n        center = ds.arr([0.0] * 2, \"code_length\")\n        w, frb, lunit = construct_image(\n            ds, normal, cut, center, image_res, width, length_unit\n        )\n        super().__init__(frb, fields=fields, length_unit=lunit, wcs=w)\n\n\nclass FITSOffAxisProjection(FITSImageData):\n    r\"\"\"\n    Generate a FITSImageData of an off-axis projection.\n\n    Parameters\n    ----------\n    ds : :class:`~yt.data_objects.static_output.Dataset`\n        This is the dataset object corresponding to the\n        simulation output to be plotted.\n    normal : a sequence of floats\n        The vector normal to the projection plane.\n    fields : string, list of strings\n        The name of the field(s) to be plotted.\n    image_res : an int or 2-tuple of ints\n        Specify the resolution of the resulting image. A single value will be\n        used for both axes, whereas a tuple of values will be used for the\n        individual axes. Default: 512\n    center : 'center', 'c', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n    width : tuple or a float.\n        Width can have four different formats to support variable\n        x and y widths.  They are:\n\n        ==================================     =======================\n        format                                 example\n        ==================================     =======================\n        (float, string)                        (10,'kpc')\n        ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n        float                                  0.2\n        (float, float)                         (0.2, 0.3)\n        ==================================     =======================\n\n        For example, (10, 'kpc') specifies a width that is 10 kiloparsecs\n        wide in the x and y directions, ((10,'kpc'),(15,'kpc')) specifies a\n        width that is 10 kiloparsecs wide along the x-axis and 15\n        kiloparsecs wide along the y-axis. In the other two examples, code\n        units are assumed, for example (0.2, 0.3) specifies a width that has an\n        x width of 0.2 and a y width of 0.3 in code units.\n    depth : A tuple or a float\n        A tuple containing the depth to project through and the string\n        key of the unit: (width, 'unit').  If set to a float, code units\n        are assumed\n    weight_field : string\n         The name of the weighting field.  Set to None for no weight.\n    north_vector : a sequence of floats\n        A vector defining the 'up' direction in the plot.  This\n        option sets the orientation of the slicing plane.  If not\n        set, an arbitrary grid-aligned north-vector is chosen.\n    method : string\n        The method of projection.  Valid methods are:\n\n        \"integrate\" with no weight_field specified : integrate the requested\n        field along the line of sight.\n\n        \"integrate\" with a weight_field specified : weight the requested\n        field by the weighting field and integrate along the line of sight.\n\n        \"sum\" : This method is the same as integrate, except that it does not\n        multiply by a path length when performing the integration, and is\n        just a straight summation of the field along the given axis. WARNING:\n        This should only be used for uniform resolution grid datasets, as other\n        datasets may result in unphysical images.\n    data_source : yt.data_objects.data_containers.YTSelectionContainer, optional\n        If specified, this will be the data source used for selecting regions\n        to project.\n    length_unit : string, optional\n        the length units that the coordinates are written in. The default\n        is to use the default length unit of the dataset.\n    moment : integer, optional\n        for a weighted projection, moment = 1 (the default) corresponds to a\n        weighted average. moment = 2 corresponds to a weighted standard\n        deviation.\n    \"\"\"\n\n    def __init__(\n        self,\n        ds,\n        normal,\n        fields,\n        center=\"center\",\n        width=(1.0, \"unitary\"),\n        weight_field=None,\n        image_res=512,\n        data_source=None,\n        north_vector=None,\n        depth=(1.0, \"unitary\"),\n        method=\"integrate\",\n        length_unit=None,\n        *,\n        moment=1,\n    ):\n        validate_moment(moment, weight_field)\n        center, dcenter = ds.coordinates.sanitize_center(center, axis=None)\n        fields = list(iter_fields(fields))\n        buf = {}\n        width = ds.coordinates.sanitize_width(normal, width, depth)\n        wd = tuple(el.in_units(\"code_length\").v for el in width)\n        if not is_sequence(image_res):\n            image_res = (image_res, image_res)\n        res = (image_res[0], image_res[1])\n        if data_source is None:\n            source = ds.all_data()\n        else:\n            source = data_source\n        fields = source._determine_fields(list(iter_fields(fields)))\n        stddev_str = \"_stddev\" if moment == 2 else \"\"\n        for item in fields:\n            ftype, fname = item\n            key = (ftype, f\"{fname}{stddev_str}\")\n\n            buf[key] = off_axis_projection(\n                source,\n                center,\n                normal,\n                wd,\n                res,\n                item,\n                north_vector=north_vector,\n                method=method,\n                weight=weight_field,\n                depth=depth,\n            ).swapaxes(0, 1)\n\n            if moment == 2:\n\n                def _sq_field(field, data, item: FieldKey):\n                    return data[item] ** 2\n\n                fd = ds._get_field_info(item)\n                field_sq = (ftype, f\"tmp_{fname}_squared\")\n\n                ds.add_field(\n                    field_sq,\n                    partial(_sq_field, item=item),\n                    sampling_type=fd.sampling_type,\n                    units=f\"({fd.units})*({fd.units})\",\n                )\n\n                buff2 = off_axis_projection(\n                    source,\n                    center,\n                    normal,\n                    wd,\n                    res,\n                    field_sq,\n                    north_vector=north_vector,\n                    method=method,\n                    weight=weight_field,\n                    depth=depth,\n                ).swapaxes(0, 1)\n\n                buf[key] = compute_stddev_image(buff2, buf[key])\n\n                ds.field_info.pop(field_sq)\n\n        center = ds.arr([0.0] * 2, \"code_length\")\n        w, not_an_frb, lunit = construct_image(\n            ds, normal, buf, center, image_res, width, length_unit\n        )\n        super().__init__(buf, fields=list(buf.keys()), wcs=w, length_unit=lunit, ds=ds)\n"
  },
  {
    "path": "yt/visualization/fixed_resolution.py",
    "content": "import sys\nimport weakref\nfrom functools import partial\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._typing import FieldKey, MaskT\nfrom yt.data_objects.image_array import ImageArray\nfrom yt.frontends.ytdata.utilities import save_as_dataset\nfrom yt.funcs import get_output_filename, iter_fields, mylog\nfrom yt.loaders import load_uniform_grid\nfrom yt.utilities.lib.api import (  # type: ignore\n    CICDeposit_2,\n    add_points_to_greyscale_image,\n)\nfrom yt.utilities.lib.pixelization_routines import (\n    pixelize_cylinder,\n    rotate_particle_coord_pib,\n)\nfrom yt.utilities.math_utils import compute_stddev_image\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\nfrom .volume_rendering.api import off_axis_projection\n\nif sys.version_info >= (3, 12):\n    from typing import override\nelse:\n    from typing_extensions import override\n\nif TYPE_CHECKING:\n    from yt.visualization.fixed_resolution_filters import FixedResolutionBufferFilter\n\n\nclass FixedResolutionBuffer:\n    r\"\"\"\n    FixedResolutionBuffer(data_source, bounds, buff_size, antialias = True)\n\n    This accepts a 2D data object, such as a Projection or Slice, and\n    implements a protocol for generating a pixelized, fixed-resolution\n    image buffer.\n\n    yt stores 2D AMR data internally as a set of 2D coordinates and the\n    half-width of individual pixels.  Converting this to an image buffer\n    requires a deposition step, where individual variable-resolution pixels\n    are deposited into a buffer of some resolution, to create an image.\n    This object is an interface to that pixelization step: it can deposit\n    multiple fields.  It acts as a standard YTDataContainer object, such that\n    dict-style access returns an image of a given field.\n\n    Parameters\n    ----------\n    data_source : :class:`yt.data_objects.construction_data_containers.YTQuadTreeProj`\n                   or :class:`yt.data_objects.selection_data_containers.YTSlice`\n        This is the source to be pixelized, which can be a projection, slice or\n        cutting plane.\n    bounds : sequence of floats\n        Bounds are the min and max in the image plane that we want our\n        image to cover.  It's in the order of (xmin, xmax, ymin, ymax),\n        where the coordinates are all in the appropriate code units.\n    buff_size : sequence of ints\n        The size of the image to generate.\n    antialias : boolean\n        This can be true or false.  It determines whether or not sub-pixel\n        rendering is used during data deposition.\n    periodic : boolean\n        This can be true or false, and governs whether the pixelization\n        will span the domain boundaries.\n\n    filters : list of FixedResolutionBufferFilter objects (optional)\n\n    Examples\n    --------\n    To make a projection and then several images, you can generate a\n    single FRB and then access multiple fields:\n\n    >>> proj = ds.proj(0, (\"gas\", \"density\"))\n    >>> frb1 = FixedResolutionBuffer(proj, (0.2, 0.3, 0.4, 0.5), (1024, 1024))\n    >>> print(frb1[\"gas\", \"density\"].max())\n    1.0914e-9 g/cm**3\n    >>> print(frb1[\"gas\", \"temperature\"].max())\n    104923.1 K\n    \"\"\"\n\n    _exclude_fields = (\n        \"pz\",\n        \"pdz\",\n        \"dx\",\n        \"x\",\n        \"y\",\n        \"z\",\n        \"r\",\n        \"dr\",\n        \"phi\",\n        \"dphi\",\n        \"theta\",\n        \"dtheta\",\n        (\"index\", \"dx\"),\n        (\"index\", \"x\"),\n        (\"index\", \"y\"),\n        (\"index\", \"z\"),\n        (\"index\", \"r\"),\n        (\"index\", \"dr\"),\n        (\"index\", \"phi\"),\n        (\"index\", \"dphi\"),\n        (\"index\", \"theta\"),\n        (\"index\", \"dtheta\"),\n    )\n\n    def __init__(\n        self,\n        data_source,\n        bounds,\n        buff_size,\n        antialias=True,\n        periodic=False,\n        *,\n        filters: list[\"FixedResolutionBufferFilter\"] | None = None,\n    ):\n        self.data_source = data_source\n        self.ds = data_source.ds\n        self.bounds = bounds\n        self.buff_size = (int(buff_size[0]), int(buff_size[1]))\n        self.antialias = antialias\n        self.data: dict[str, ImageArray] = {}\n        self.mask: dict[str, MaskT] = {}\n        self.axis = data_source.axis\n        self.periodic = periodic\n        self._data_valid = False\n\n        # import type here to avoid import cycles\n        # note that this import statement is actually crucial at runtime:\n        # the filter methods for the present class are defined only when\n        # fixed_resolution_filters is imported, so we need to guarantee\n        # that it happens no later than instantiation\n        from yt.visualization.fixed_resolution_filters import (  # noqa\n            FixedResolutionBufferFilter,\n        )\n\n        self._filters: list[FixedResolutionBufferFilter] = (\n            filters if filters is not None else []\n        )\n\n        ds = getattr(data_source, \"ds\", None)\n        if ds is not None:\n            ds.plots.append(weakref.proxy(self))\n\n        # Handle periodicity, just in case\n        if self.data_source.axis is not None:\n            DLE = self.ds.domain_left_edge\n            DRE = self.ds.domain_right_edge\n            DD = float(self.periodic) * (DRE - DLE)\n            axis = self.data_source.axis\n            xax = self.ds.coordinates.x_axis[axis]\n            yax = self.ds.coordinates.y_axis[axis]\n            self._period = (DD[xax], DD[yax])\n            self._edges = ((DLE[xax], DRE[xax]), (DLE[yax], DRE[yax]))\n\n    def keys(self):\n        return self.data.keys()\n\n    def __delitem__(self, item):\n        del self.data[item]\n\n    def _generate_image_and_mask(self, item) -> None:\n        mylog.info(\n            \"Making a fixed resolution buffer of (%s) %d by %d\",\n            item,\n            self.buff_size[0],\n            self.buff_size[1],\n        )\n        bounds = []\n        for b in self.bounds:\n            if hasattr(b, \"in_units\"):\n                b = float(b.in_units(\"code_length\"))\n            bounds.append(b)\n\n        buff, mask = self.ds.coordinates.pixelize(\n            self.data_source.axis,\n            self.data_source,\n            item,\n            bounds,\n            self.buff_size,\n            int(self.antialias),\n            return_mask=True,\n        )\n\n        buff = self._apply_filters(buff)\n\n        # FIXME FIXME FIXME we shouldn't need to do this for projections\n        # but that will require fixing data object access for particle\n        # projections\n        try:\n            if hasattr(item, \"name\"):\n                it = item.name\n            else:\n                it = item\n            units = self.data_source._projected_units[it]\n        except (KeyError, AttributeError):\n            units = self.data_source[item].units\n\n        self.data[item] = ImageArray(buff, units=units, info=self._get_info(item))\n        self.mask[item] = mask\n        self._data_valid = True\n\n    def __getitem__(self, item):\n        # backward compatibility\n        return self.get_image(item)\n\n    def get_image(self, key, /) -> ImageArray:\n        if not (key in self.data and self._data_valid):\n            self._generate_image_and_mask(key)\n        return self.data[key]\n\n    def get_mask(self, key, /) -> MaskT:\n        \"\"\"Return the boolean array associated with an image with the same key.\n\n        Elements set to True indicate pixels that were updated by a pixelisation routine\n        \"\"\"\n        if not (key in self.mask and self._data_valid):\n            self._generate_image_and_mask(key)\n        return self.mask[key]\n\n    def render(self, item):\n        # deleguate to __getitem__ for historical reasons\n        # this method exists for clarity of intention\n        return self[item]\n\n    def _apply_filters(self, buffer: np.ndarray) -> np.ndarray:\n        for f in self._filters:\n            buffer = f(buffer)\n        return buffer\n\n    def __setitem__(self, item, val):\n        self.data[item] = val\n\n    def _get_data_source_fields(self):\n        exclude = self.data_source._key_fields + list(self._exclude_fields)\n        fields = getattr(self.data_source, \"fields\", [])\n        fields += getattr(self.data_source, \"field_data\", {}).keys()\n        for f in fields:\n            if f not in exclude and f[0] not in self.data_source.ds.particle_types:\n                self.render(f)\n\n    def _get_info(self, item):\n        info = {}\n        ftype, fname = field = self.data_source._determine_fields(item)[0]\n        finfo = self.data_source.ds._get_field_info(field)\n        info[\"data_source\"] = self.data_source.__str__()\n        info[\"axis\"] = self.data_source.axis\n        info[\"field\"] = str(item)\n        info[\"xlim\"] = self.bounds[:2]\n        info[\"ylim\"] = self.bounds[2:]\n        info[\"length_unit\"] = self.data_source.ds.length_unit\n        info[\"length_to_cm\"] = info[\"length_unit\"].in_cgs().to_ndarray()\n        info[\"center\"] = self.data_source.center\n\n        try:\n            info[\"coord\"] = self.data_source.coord\n        except AttributeError:\n            pass\n\n        try:\n            info[\"weight_field\"] = self.data_source.weight_field\n        except AttributeError:\n            pass\n\n        info[\"label\"] = finfo.get_latex_display_name()\n\n        return info\n\n    def convert_to_pixel(self, coords):\n        r\"\"\"This function converts coordinates in code-space to pixel-space.\n\n        Parameters\n        ----------\n        coords : sequence of array_like\n            This is (x_coord, y_coord).  Because of the way the math is done,\n            these can both be arrays.\n\n        Returns\n        -------\n        output : sequence of array_like\n            This returns px_coord, py_coord\n\n        \"\"\"\n        dpx = (self.bounds[1] - self.bounds[0]) / self.buff_size[0]\n        dpy = (self.bounds[3] - self.bounds[2]) / self.buff_size[1]\n        px = (coords[0] - self.bounds[0]) / dpx\n        py = (coords[1] - self.bounds[2]) / dpy\n        return (px, py)\n\n    def convert_distance_x(self, distance):\n        r\"\"\"This function converts code-space distance into pixel-space\n        distance in the x-coordinate.\n\n        Parameters\n        ----------\n        distance : array_like\n            This is x-distance in code-space you would like to convert.\n\n        Returns\n        -------\n        output : array_like\n            The return value is the distance in the y-pixel coordinates.\n\n        \"\"\"\n        dpx = (self.bounds[1] - self.bounds[0]) / self.buff_size[0]\n        return distance / dpx\n\n    def convert_distance_y(self, distance):\n        r\"\"\"This function converts code-space distance into pixel-space\n        distance in the y-coordinate.\n\n        Parameters\n        ----------\n        distance : array_like\n            This is y-distance in code-space you would like to convert.\n\n        Returns\n        -------\n        output : array_like\n            The return value is the distance in the x-pixel coordinates.\n\n        \"\"\"\n        dpy = (self.bounds[3] - self.bounds[2]) / self.buff_size[1]\n        return distance / dpy\n\n    def set_unit(self, field, unit, equivalency=None, equivalency_kwargs=None):\n        \"\"\"Sets a new unit for the requested field\n\n        parameters\n        ----------\n        field : string or field tuple\n           The name of the field that is to be changed.\n\n        unit : string or Unit object\n           The name of the new unit.\n\n        equivalency : string, optional\n           If set, the equivalency to use to convert the current units to\n           the new requested unit. If None, the unit conversion will be done\n           without an equivalency\n\n        equivalency_kwargs : string, optional\n           Keyword arguments to be passed to the equivalency. Only used if\n           ``equivalency`` is set.\n        \"\"\"\n        if equivalency_kwargs is None:\n            equivalency_kwargs = {}\n        field = self.data_source._determine_fields(field)[0]\n        if equivalency is None:\n            self[field].convert_to_units(unit)\n        else:\n            equiv_array = self[field].to_equivalent(\n                unit, equivalency, **equivalency_kwargs\n            )\n            # equiv_array isn't necessarily an ImageArray. This is an issue\n            # inherent to the way the unit system handles YTArray\n            # subclasses and I don't see how to modify the unit system to\n            # fix this. Instead, we paper over this issue and hard code\n            # that equiv_array is an ImageArray\n            self[field] = ImageArray(\n                equiv_array,\n                equiv_array.units,\n                equiv_array.units.registry,\n                self[field].info,\n            )\n\n    def export_hdf5(self, filename, fields=None):\n        r\"\"\"Export a set of fields to a set of HDF5 datasets.\n\n        This function will export any number of fields into datasets in a new\n        HDF5 file.\n\n        Parameters\n        ----------\n        filename : string\n            This file will be opened in \"append\" mode.\n        fields : list of strings\n            These fields will be pixelized and output.\n        \"\"\"\n        if fields is None:\n            fields = list(self.data.keys())\n        output = h5py.File(filename, mode=\"a\")\n        for field in fields:\n            output.create_dataset(\"_\".join(field), data=self[field])\n        output.close()\n\n    def to_fits_data(self, fields=None, other_keys=None, length_unit=None, **kwargs):\n        r\"\"\"Export the fields in this FixedResolutionBuffer instance\n        to a FITSImageData instance.\n\n        This will export a set of FITS images of either the fields specified\n        or all the fields already in the object.\n\n        Parameters\n        ----------\n        fields : list of strings\n            These fields will be pixelized and output. If \"None\", the keys of\n            the FRB will be used.\n        other_keys : dictionary, optional\n            A set of header keys and values to write into the FITS header.\n        length_unit : string, optional\n            the length units that the coordinates are written in. The default\n            is to use the default length unit of the dataset.\n        \"\"\"\n        from yt.visualization.fits_image import FITSImageData\n\n        if length_unit is None:\n            length_unit = self.ds.length_unit\n\n        if fields is None:\n            fields = list(self.data.keys())\n        else:\n            fields = list(iter_fields(fields))\n\n        if len(fields) == 0:\n            raise RuntimeError(\n                \"No fields to export. Either pass a field or list of fields to \"\n                \"to_fits_data or access a field from the FixedResolutionBuffer \"\n                \"object.\"\n            )\n\n        fid = FITSImageData(self, fields=fields, length_unit=length_unit)\n        if other_keys is not None:\n            for k, v in other_keys.items():\n                fid.update_header(\"all\", k, v)\n        return fid\n\n    def export_dataset(self, fields=None, nprocs=1):\n        r\"\"\"Export a set of pixelized fields to an in-memory dataset that can be\n        analyzed as any other in yt. Unit information and other parameters (e.g.,\n        geometry, current_time, etc.) will be taken from the parent dataset.\n\n        Parameters\n        ----------\n        fields : list of strings, optional\n            These fields will be pixelized and output. If \"None\", the keys of the\n            FRB will be used.\n        nprocs: integer, optional\n            If greater than 1, will create this number of subarrays out of data\n\n        Examples\n        --------\n        >>> import yt\n        >>> ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n        >>> slc = ds.slice(2, 0.0)\n        >>> frb = slc.to_frb((500.0, \"kpc\"), 500)\n        >>> ds2 = frb.export_dataset(\n        ...     fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")], nprocs=32\n        ... )\n        \"\"\"\n        nx, ny = self.buff_size\n        data = {}\n        if fields is None:\n            fields = list(self.keys())\n        for field in fields:\n            arr = self[field]\n            data[field] = (arr.d.T.reshape(nx, ny, 1), str(arr.units))\n        bounds = [b.in_units(\"code_length\").v for b in self.bounds]\n        bbox = np.array([[bounds[0], bounds[1]], [bounds[2], bounds[3]], [0.0, 1.0]])\n        return load_uniform_grid(\n            data,\n            [nx, ny, 1],\n            length_unit=self.ds.length_unit,\n            bbox=bbox,\n            sim_time=self.ds.current_time.in_units(\"s\").v,\n            mass_unit=self.ds.mass_unit,\n            time_unit=self.ds.time_unit,\n            velocity_unit=self.ds.velocity_unit,\n            magnetic_unit=self.ds.magnetic_unit,\n            periodicity=(False, False, False),\n            geometry=self.ds.geometry,\n            nprocs=nprocs,\n        )\n\n    def save_as_dataset(self, filename=None, fields=None):\n        r\"\"\"Export a fixed resolution buffer to a reloadable yt dataset.\n\n        This function will take a fixed resolution buffer and output a\n        dataset containing either the fields presently existing or fields\n        given in the ``fields`` list.  The resulting dataset can be\n        reloaded as a yt dataset.\n\n        Parameters\n        ----------\n        filename : str, optional\n            The name of the file to be written.  If None, the name\n            will be a combination of the original dataset and the type\n            of data container.\n        fields : list of strings or tuples, optional\n            If this is supplied, it is the list of fields to be saved to\n            disk.  If not supplied, all the fields that have been queried\n            will be saved.\n\n        Returns\n        -------\n        filename : str\n            The name of the file that has been created.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n        >>> proj = ds.proj((\"gas\", \"density\"), \"x\", weight_field=(\"gas\", \"density\"))\n        >>> frb = proj.to_frb(1.0, (800, 800))\n        >>> fn = frb.save_as_dataset(fields=[(\"gas\", \"density\")])\n        >>> ds2 = yt.load(fn)\n        >>> print(ds2.data[\"gas\", \"density\"])\n        [[  1.25025353e-30   1.25025353e-30   1.25025353e-30 ...,   7.90820691e-31\n            7.90820691e-31   7.90820691e-31]\n         [  1.25025353e-30   1.25025353e-30   1.25025353e-30 ...,   7.90820691e-31\n            7.90820691e-31   7.90820691e-31]\n         [  1.25025353e-30   1.25025353e-30   1.25025353e-30 ...,   7.90820691e-31\n            7.90820691e-31   7.90820691e-31]\n         ...,\n         [  1.55834239e-30   1.55834239e-30   1.55834239e-30 ...,   8.51353199e-31\n            8.51353199e-31   8.51353199e-31]\n         [  1.55834239e-30   1.55834239e-30   1.55834239e-30 ...,   8.51353199e-31\n            8.51353199e-31   8.51353199e-31]\n         [  1.55834239e-30   1.55834239e-30   1.55834239e-30 ...,   8.51353199e-31\n            8.51353199e-31   8.51353199e-31]] g/cm**3\n\n        \"\"\"\n\n        keyword = f\"{str(self.ds)}_{self.data_source._type_name}_frb\"\n        filename = get_output_filename(filename, keyword, \".h5\")\n\n        data = {}\n        if fields is not None:\n            for f in self.data_source._determine_fields(fields):\n                data[f] = self[f]\n        else:\n            data.update(self.data)\n\n        ftypes = dict.fromkeys(data, \"grid\")\n        extra_attrs = {\n            arg: getattr(self.data_source, arg, None)\n            for arg in self.data_source._con_args + self.data_source._tds_attrs\n        }\n        extra_attrs[\"con_args\"] = self.data_source._con_args\n        extra_attrs[\"left_edge\"] = self.ds.arr([self.bounds[0], self.bounds[2]])\n        extra_attrs[\"right_edge\"] = self.ds.arr([self.bounds[1], self.bounds[3]])\n        # The data dimensions are [NY, NX] but buff_size is [NX, NY].\n        extra_attrs[\"ActiveDimensions\"] = self.buff_size[::-1]\n        extra_attrs[\"level\"] = 0\n        extra_attrs[\"data_type\"] = \"yt_frb\"\n        extra_attrs[\"container_type\"] = self.data_source._type_name\n        extra_attrs[\"dimensionality\"] = self.data_source._dimensionality\n        save_as_dataset(\n            self.ds, filename, data, field_types=ftypes, extra_attrs=extra_attrs\n        )\n\n        return filename\n\n    @property\n    def limits(self):\n        rv = {\"x\": None, \"y\": None, \"z\": None}\n        xax = self.ds.coordinates.x_axis[self.axis]\n        yax = self.ds.coordinates.y_axis[self.axis]\n        xn = self.ds.coordinates.axis_name[xax]\n        yn = self.ds.coordinates.axis_name[yax]\n        rv[xn] = (self.bounds[0], self.bounds[1])\n        rv[yn] = (self.bounds[2], self.bounds[3])\n        return rv\n\n    def setup_filters(self):\n        issue_deprecation_warning(\n            \"The FixedResolutionBuffer.setup_filters method is now a no-op. \",\n            stacklevel=3,\n            since=\"4.1\",\n        )\n\n\nclass CylindricalFixedResolutionBuffer(FixedResolutionBuffer):\n    \"\"\"\n    This object is a subclass of\n    :class:`yt.visualization.fixed_resolution.FixedResolutionBuffer`\n    that supports non-aligned input data objects, primarily cutting planes.\n    \"\"\"\n\n    def __init__(self, data_source, radius, buff_size, antialias=True, *, filters=None):\n        self.data_source = data_source\n        self.ds = data_source.ds\n        self.radius = radius\n        self.buff_size = buff_size\n        self.antialias = antialias\n        self.data = {}\n        self._filters = filters if filters is not None else []\n\n        ds = getattr(data_source, \"ds\", None)\n        if ds is not None:\n            ds.plots.append(weakref.proxy(self))\n\n    @override\n    def _generate_image_and_mask(self, item) -> None:\n        buff = np.zeros(self.buff_size, dtype=\"f8\")\n        mask = pixelize_cylinder(\n            buff,\n            self.data_source[\"r\"],\n            self.data_source[\"dr\"],\n            self.data_source[\"theta\"],\n            self.data_source[\"dtheta\"],\n            self.data_source[item].astype(\"float64\"),\n            self.radius,\n            return_mask=True,\n        )\n        self.data[item] = ImageArray(\n            buff, units=self.data_source[item].units, info=self._get_info(item)\n        )\n        self.mask[item] = mask\n\n\nclass OffAxisProjectionFixedResolutionBuffer(FixedResolutionBuffer):\n    \"\"\"\n    This object is a subclass of\n    :class:`yt.visualization.fixed_resolution.FixedResolutionBuffer`\n    that supports off axis projections.  This calls the volume renderer.\n    \"\"\"\n\n    @override\n    def _generate_image_and_mask(self, item) -> None:\n        mylog.info(\n            \"Making a fixed resolution buffer of (%s) %d by %d\",\n            item,\n            self.buff_size[0],\n            self.buff_size[1],\n        )\n        dd = self.data_source\n        # only need the first two for SPH,\n        # but need the third one for other data formats.\n        width = self.ds.arr(\n            (\n                self.bounds[1] - self.bounds[0],\n                self.bounds[3] - self.bounds[2],\n                self.bounds[5] - self.bounds[4],\n            )\n        )\n        buff = off_axis_projection(\n            dd.dd,\n            dd.center,\n            dd.normal_vector,\n            width,\n            self.buff_size,\n            item,\n            weight=dd.weight_field,\n            volume=dd.volume,\n            no_ghost=dd.no_ghost,\n            interpolated=dd.interpolated,\n            north_vector=dd.north_vector,\n            depth=dd.depth,\n            method=dd.method,\n        )\n        if self.data_source.moment == 2:\n\n            def _sq_field(field, data, item: FieldKey):\n                return data[item] ** 2\n\n            fd = self.ds._get_field_info(item)\n            ftype, fname = item\n\n            item_sq = (ftype, f\"tmp_{fname}_squared\")\n            self.ds.add_field(\n                item_sq,\n                partial(_sq_field, item=item),\n                sampling_type=fd.sampling_type,\n                units=f\"({fd.units})*({fd.units})\",\n            )\n\n            buff2 = off_axis_projection(\n                dd.dd,\n                dd.center,\n                dd.normal_vector,\n                width,\n                self.buff_size,\n                item_sq,\n                weight=dd.weight_field,\n                volume=dd.volume,\n                no_ghost=dd.no_ghost,\n                interpolated=dd.interpolated,\n                north_vector=dd.north_vector,\n                depth=dd.depth,\n                method=dd.method,\n            )\n            buff = compute_stddev_image(buff2, buff)\n\n            self.ds.field_info.pop(item_sq)\n\n        ia = ImageArray(buff.swapaxes(0, 1), info=self._get_info(item))\n        self.data[item] = ia\n        self.mask[item] = None\n\n\nclass ParticleImageBuffer(FixedResolutionBuffer):\n    \"\"\"\n\n    This object is a subclass of\n    :class:`yt.visualization.fixed_resolution.FixedResolutionBuffer`\n    that supports particle plots. It splats points onto an image\n    buffer.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        data_source,\n        bounds,\n        buff_size,\n        antialias=True,\n        periodic=False,\n        *,\n        filters=None,\n    ):\n        super().__init__(\n            data_source, bounds, buff_size, antialias, periodic, filters=filters\n        )\n\n        # set up the axis field names\n        axis = self.axis\n        if axis is not None:\n            self.xax = self.ds.coordinates.x_axis[axis]\n            self.yax = self.ds.coordinates.y_axis[axis]\n            axis_name = self.ds.coordinates.axis_name\n            self.x_field = f\"particle_position_{axis_name[self.xax]}\"\n            self.y_field = f\"particle_position_{axis_name[self.yax]}\"\n\n    @override\n    def _generate_image_and_mask(self, item) -> None:\n        deposition = self.data_source.deposition\n        density = self.data_source.density\n\n        mylog.info(\n            \"Splatting (%s) onto a %d by %d mesh using method '%s'\",\n            item,\n            self.buff_size[0],\n            self.buff_size[1],\n            deposition,\n        )\n\n        dd = self.data_source.dd\n\n        ftype = item[0]\n        if self.axis is None:\n            wd = []\n            for w in self.data_source.width:\n                if hasattr(w, \"to_value\"):\n                    w = w.to_value(\"code_length\")\n                wd.append(w)\n            x_data, y_data, *bounds = rotate_particle_coord_pib(\n                dd[ftype, \"particle_position_x\"].to_value(\"code_length\"),\n                dd[ftype, \"particle_position_y\"].to_value(\"code_length\"),\n                dd[ftype, \"particle_position_z\"].to_value(\"code_length\"),\n                self.data_source.center.to_value(\"code_length\"),\n                wd,\n                self.data_source.normal_vector,\n                self.data_source.north_vector,\n            )\n            x_data = np.array(x_data)\n            y_data = np.array(y_data)\n        else:\n            bounds = []\n            for b in self.bounds:\n                if hasattr(b, \"to_value\"):\n                    b = b.to_value(\"code_length\")\n                bounds.append(b)\n            x_data = dd[ftype, self.x_field].to_value(\"code_length\")\n            y_data = dd[ftype, self.y_field].to_value(\"code_length\")\n        data = dd[item]\n\n        # handle periodicity\n        dx = x_data - bounds[0]\n        dy = y_data - bounds[2]\n        if self.periodic:\n            dx %= float(self._period[0].in_units(\"code_length\"))\n            dy %= float(self._period[1].in_units(\"code_length\"))\n\n        # convert to pixels\n        px = dx / (bounds[1] - bounds[0])\n        py = dy / (bounds[3] - bounds[2])\n\n        # select only the particles that will actually show up in the image\n        mask = np.logical_and(\n            np.logical_and(px >= 0.0, px <= 1.0), np.logical_and(py >= 0.0, py <= 1.0)\n        )\n\n        weight_field = self.data_source.weight_field\n        if weight_field is None:\n            weight_data = np.ones_like(data.v)\n        else:\n            weight_data = dd[weight_field]\n        splat_vals = weight_data[mask] * data[mask]\n\n        x_bin_edges = np.linspace(0.0, 1.0, self.buff_size[0] + 1)\n        y_bin_edges = np.linspace(0.0, 1.0, self.buff_size[1] + 1)\n\n        # splat particles\n        buff = np.zeros(self.buff_size)\n        buff_mask = np.zeros_like(buff, dtype=\"uint8\")\n        if deposition == \"ngp\":\n            add_points_to_greyscale_image(\n                buff, buff_mask, px[mask], py[mask], splat_vals\n            )\n        elif deposition == \"cic\":\n            CICDeposit_2(\n                py[mask],\n                px[mask],\n                splat_vals,\n                mask.sum(),\n                buff,\n                buff_mask,\n                x_bin_edges,\n                y_bin_edges,\n            )\n        else:\n            raise ValueError(f\"Received unknown deposition method '{deposition}'\")\n\n        # remove values in no-particle region\n        buff[buff_mask == 0] = np.nan\n\n        # Normalize by the surface area of the pixel or volume of pencil if\n        # requested\n        info = self._get_info(item)\n        if density:\n            dpx = (bounds[1] - bounds[0]) / self.buff_size[0]\n            dpy = (bounds[3] - bounds[2]) / self.buff_size[1]\n            norm = self.ds.quan(dpx * dpy, \"code_length**2\").in_base()\n            buff /= norm.v\n            units = data.units / norm.units\n            info[\"label\"] += \" $\\\\rm{Density}$\"\n        else:\n            units = data.units\n\n        # divide by the weight_field, if needed\n        if weight_field is not None:\n            weight_buff = np.zeros(self.buff_size)\n            weight_buff_mask = np.zeros(self.buff_size, dtype=\"uint8\")\n            if deposition == \"ngp\":\n                add_points_to_greyscale_image(\n                    weight_buff, weight_buff_mask, px[mask], py[mask], weight_data[mask]\n                )\n            elif deposition == \"cic\":\n                CICDeposit_2(\n                    py[mask],\n                    px[mask],\n                    weight_data[mask],\n                    mask.sum(),\n                    weight_buff,\n                    weight_buff_mask,\n                    y_bin_edges,\n                    x_bin_edges,\n                )\n            # remove values in no-particle region\n            weight_buff[weight_buff_mask == 0] = np.nan\n            locs = np.where(weight_buff > 0)\n            buff[locs] /= weight_buff[locs]\n\n        self.data[item] = ImageArray(buff, units=units, info=info)\n        self.mask[item] = buff_mask != 0\n\n    # over-ride the base class version, since we don't want to exclude\n    # particle fields\n    def _get_data_source_fields(self):\n        exclude = self.data_source._key_fields + list(self._exclude_fields)\n        fields = getattr(self.data_source, \"fields\", [])\n        fields += getattr(self.data_source, \"field_data\", {}).keys()\n        for f in fields:\n            if f not in exclude:\n                self.render(f)\n"
  },
  {
    "path": "yt/visualization/fixed_resolution_filters.py",
    "content": "from abc import ABC, abstractmethod\nfrom functools import update_wrapper, wraps\n\nimport numpy as np\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt.visualization.fixed_resolution import FixedResolutionBuffer\n\n\ndef apply_filter(f):\n    issue_deprecation_warning(\n        \"The apply_filter decorator is not used in yt any more and \"\n        \"will be removed in a future version. \"\n        \"Please do not use it.\",\n        stacklevel=3,\n        since=\"4.1\",\n    )\n\n    @wraps(f)\n    def newfunc(self, *args, **kwargs):\n        self._filters.append((f.__name__, (args, kwargs)))\n        # Invalidate the data of the frb to force its regeneration\n        self._data_valid = False\n        return self\n\n    return newfunc\n\n\nclass FixedResolutionBufferFilter(ABC):\n    \"\"\"\n    This object allows to apply data transformation directly to\n    :class:`yt.visualization.fixed_resolution.FixedResolutionBuffer`\n    \"\"\"\n\n    def __init_subclass__(cls, *args, **kwargs):\n        if cls.__init__.__doc__ is None:\n            # allow docstring definition at the class level instead of __init__\n            cls.__init__.__doc__ = cls.__doc__\n\n        # add a method to FixedResolutionBuffer\n        method_name = \"apply_\" + cls._filter_name\n\n        def closure(self, *args, **kwargs):\n            self._filters.append(cls(*args, **kwargs))\n            self._data_valid = False\n            return self\n\n        update_wrapper(\n            wrapper=closure,\n            wrapped=cls.__init__,\n            assigned=(\"__annotations__\", \"__doc__\"),\n        )\n\n        closure.__name__ = method_name\n        setattr(FixedResolutionBuffer, method_name, closure)\n\n    @abstractmethod\n    def __init__(self, *args, **kwargs):\n        \"\"\"This method is required in subclasses, but the signature is arbitrary\"\"\"\n        pass\n\n    @abstractmethod\n    def apply(self, buff: np.ndarray) -> np.ndarray:\n        pass\n\n    def __call__(self, buff: np.ndarray) -> np.ndarray:\n        # alias to apply\n        return self.apply(buff)\n\n\nclass FixedResolutionBufferGaussBeamFilter(FixedResolutionBufferFilter):\n    \"\"\"\n    This filter convolves\n    :class:`yt.visualization.fixed_resolution.FixedResolutionBuffer` with\n    2d gaussian that is 'nbeam' pixels wide and has standard deviation\n    'sigma'.\n    \"\"\"\n\n    _filter_name = \"gauss_beam\"\n\n    def __init__(self, nbeam=30, sigma=2.0):\n        self.nbeam = nbeam\n        self.sigma = sigma\n\n    def apply(self, buff):\n        from yt.utilities.on_demand_imports import _scipy\n\n        hnbeam = self.nbeam // 2\n        sigma = self.sigma\n\n        l = np.linspace(-hnbeam, hnbeam, num=self.nbeam + 1)\n        x, y = np.meshgrid(l, l)\n        g2d = (1.0 / (sigma * np.sqrt(2.0 * np.pi))) * np.exp(\n            -((x / sigma) ** 2 + (y / sigma) ** 2) / (2 * sigma**2)\n        )\n        g2d /= g2d.max()\n\n        npm, nqm = np.shape(buff)\n        spl = _scipy.signal.convolve(buff, g2d)\n\n        return spl[hnbeam : npm + hnbeam, hnbeam : nqm + hnbeam]\n\n\nclass FixedResolutionBufferWhiteNoiseFilter(FixedResolutionBufferFilter):\n    \"\"\"\n    This filter adds white noise with the amplitude \"bg_lvl\" to\n    :class:`yt.visualization.fixed_resolution.FixedResolutionBuffer`.\n    If \"bg_lvl\" is not present, 10th percentile of the FRB's value is\n    used instead.\n    \"\"\"\n\n    _filter_name = \"white_noise\"\n\n    def __init__(self, bg_lvl=None):\n        self.bg_lvl = bg_lvl\n\n    def apply(self, buff):\n        if self.bg_lvl is None:\n            amp = np.percentile(buff, 10)\n        else:\n            amp = self.bg_lvl\n        npm, nqm = np.shape(buff)\n        rng = np.random.default_rng()\n        return buff + rng.standard_normal((npm, nqm)) * amp\n"
  },
  {
    "path": "yt/visualization/geo_plot_utils.py",
    "content": "from types import FunctionType\nfrom typing import Any\n\nvalid_transforms: dict[str, FunctionType] = {}\n\ntransform_list = [\n    \"PlateCarree\",\n    \"LambertConformal\",\n    \"LambertCylindrical\",\n    \"Mercator\",\n    \"Miller\",\n    \"Mollweide\",\n    \"Orthographic\",\n    \"Robinson\",\n    \"Stereographic\",\n    \"TransverseMercator\",\n    \"InterruptedGoodeHomolosine\",\n    \"RotatedPole\",\n    \"OSGB\",\n    \"EuroPP\",\n    \"Geostationary\",\n    \"Gnomonic\",\n    \"NorthPolarStereo\",\n    \"OSNI\",\n    \"SouthPolarStereo\",\n    \"AlbersEqualArea\",\n    \"AzimuthalEquidistant\",\n    \"Sinusoidal\",\n    \"UTM\",\n    \"NearsidePerspective\",\n    \"LambertAzimuthalEqualArea\",\n]\n\n\ndef cartopy_importer(transform_name):\n    r\"\"\"Convenience function to import cartopy projection types\"\"\"\n\n    def _func(*args, **kwargs):\n        from yt.utilities.on_demand_imports import _cartopy as cartopy\n\n        return getattr(cartopy.crs, transform_name)(*args, **kwargs)\n\n    return _func\n\n\ndef get_mpl_transform(mpl_proj) -> FunctionType | None:\n    r\"\"\"This returns an instantiated transform function given a transform\n    function name and arguments.\n\n    Parameters\n    ----------\n    mpl_proj : string or tuple\n        the matplotlib projection type. Can take the form of string or tuple.\n\n    Examples\n    --------\n\n    >>> get_mpl_transform(\"PlateCarree\")\n\n    >>> get_mpl_transform(\n    ...     (\"RotatedPole\", (), {\"pole_latitude\": 37.5, \"pole_longitude\": 177.5})\n    ... )\n\n    \"\"\"\n    # first check to see if the transform dict is empty, if it is fill it with\n    # the cartopy functions\n    if not valid_transforms:\n        for mpl_transform in transform_list:\n            valid_transforms[mpl_transform] = cartopy_importer(mpl_transform)\n\n    # check to see if mpl_proj is a string or tuple, and construct args and\n    # kwargs to pass to cartopy function based on that.\n    key: str | None = None\n    args: tuple = ()\n    kwargs: dict[str, Any] = {}\n    if isinstance(mpl_proj, str):\n        key = mpl_proj\n        instantiated_func = valid_transforms[key](*args, **kwargs)\n    elif isinstance(mpl_proj, tuple):\n        if len(mpl_proj) == 2:\n            key, args = mpl_proj\n            kwargs = {}\n        elif len(mpl_proj) == 3:\n            key, args, kwargs = mpl_proj\n        else:\n            raise ValueError(f\"Expected a tuple with len 2 or 3, received {mpl_proj}\")\n        if not isinstance(key, str):\n            raise TypeError(\n                f\"Expected a string a the first element in mpl_proj, got {key!r}\"\n            )\n        instantiated_func = valid_transforms[key](*args, **kwargs)\n    elif hasattr(mpl_proj, \"globe\"):\n        # cartopy transforms have a globe method associated with them\n        key = mpl_proj\n        instantiated_func = mpl_proj\n    elif hasattr(mpl_proj, \"set_transform\"):\n        # mpl axes objects have a set_transform method, so we'll check if that\n        # exists on something passed in.\n        key = mpl_proj\n        instantiated_func = mpl_proj\n    elif hasattr(mpl_proj, \"name\"):\n        # last we'll check if the transform is in the list of registered\n        # projections in matplotlib.\n        from matplotlib.projections import get_projection_names\n\n        registered_projections = get_projection_names()\n        if mpl_proj.name in registered_projections:\n            key = mpl_proj\n            instantiated_func = mpl_proj\n        else:\n            key = None\n\n    # build in a check that if none of the above options are satisfied by what\n    # the user passes that None is returned for the instantiated function\n    if key is None:\n        instantiated_func = None\n    return instantiated_func\n"
  },
  {
    "path": "yt/visualization/image_writer.py",
    "content": "import numpy as np\n\nfrom yt._maintenance.ipython_compat import IS_IPYTHON\nfrom yt.config import ytcfg\nfrom yt.funcs import mylog\nfrom yt.units.yt_array import YTQuantity\nfrom yt.utilities import png_writer as pw\nfrom yt.utilities.exceptions import YTNotInsideNotebook\nfrom yt.utilities.lib import image_utilities as au\nfrom yt.visualization.color_maps import get_colormap_lut\n\nfrom ._commons import get_canvas, validate_image_name\n\n\ndef scale_image(image, mi=None, ma=None):\n    r\"\"\"Scale an image ([NxNxM] where M = 1-4) to be uint8 and values scaled\n    from [0,255].\n\n    Parameters\n    ----------\n    image : array_like or tuple of image info\n\n    Examples\n    --------\n\n        >>> image = scale_image(image)\n\n        >>> image = scale_image(image, min=0, max=1000)\n    \"\"\"\n    if isinstance(image, np.ndarray) and image.dtype == np.uint8:\n        return image\n    if isinstance(image, (tuple, list)):\n        image, mi, ma = image\n    if mi is None:\n        mi = image.min()\n    if ma is None:\n        ma = image.max()\n    image = np.clip((image - mi) / (ma - mi) * 255, 0, 255).astype(\"uint8\")\n    return image\n\n\ndef multi_image_composite(\n    fn, red_channel, blue_channel, green_channel=None, alpha_channel=None\n):\n    r\"\"\"Write an image with different color channels corresponding to different\n    quantities.\n\n    Accepts at least a red and a blue array, of shape (N,N) each, that are\n    optionally scaled and composited into a final image, written into `fn`.\n    Can also accept green and alpha.\n\n    Parameters\n    ----------\n    fn : string\n        Filename to save\n    red_channel : array_like or tuple of image info\n        Array, of shape (N,N), to be written into the red channel of the output\n        image.  If not already uint8, will be converted (and scaled) into\n        uint8.  Optionally, you can also specify a tuple that includes scaling\n        information, in the form of (array_to_plot, min_value_to_scale,\n        max_value_to_scale).\n    blue_channel : array_like or tuple of image info\n        Array, of shape (N,N), to be written into the blue channel of the output\n        image.  If not already uint8, will be converted (and scaled) into\n        uint8.  Optionally, you can also specify a tuple that includes scaling\n        information, in the form of (array_to_plot, min_value_to_scale,\n        max_value_to_scale).\n    green_channel : array_like or tuple of image info, optional\n        Array, of shape (N,N), to be written into the green channel of the\n        output image.  If not already uint8, will be converted (and scaled)\n        into uint8.  If not supplied, will be left empty.  Optionally, you can\n        also specify a tuple that includes scaling information, in the form of\n        (array_to_plot, min_value_to_scale, max_value_to_scale).\n\n    alpha_channel : array_like or tuple of image info, optional\n        Array, of shape (N,N), to be written into the alpha channel of the output\n        image.  If not already uint8, will be converted (and scaled) into uint8.\n        If not supplied, will be made fully opaque.  Optionally, you can also\n        specify a tuple that includes scaling information, in the form of\n        (array_to_plot, min_value_to_scale, max_value_to_scale).\n\n    Examples\n    --------\n\n        >>> red_channel = np.log10(frb[\"gas\", \"temperature\"])\n        >>> blue_channel = np.log10(frb[\"gas\", \"density\"])\n        >>> multi_image_composite(\"multi_channel1.png\", red_channel, blue_channel)\n\n    \"\"\"\n    red_channel = scale_image(red_channel)\n    blue_channel = scale_image(blue_channel)\n    if green_channel is None:\n        green_channel = np.zeros(red_channel.shape, dtype=\"uint8\")\n    else:\n        green_channel = scale_image(green_channel)\n    if alpha_channel is None:\n        alpha_channel = np.zeros(red_channel.shape, dtype=\"uint8\") + 255\n    else:\n        alpha_channel = scale_image(alpha_channel)\n    image = np.array([red_channel, green_channel, blue_channel, alpha_channel])\n    image = image.transpose().copy()  # Have to make sure it's contiguous\n    pw.write_png(image, fn)\n\n\ndef write_bitmap(bitmap_array, filename, max_val=None, transpose=False):\n    r\"\"\"Write out a bitmapped image directly to a PNG file.\n\n    This accepts a three- or four-channel `bitmap_array`.  If the image is not\n    already uint8, it will be scaled and converted.  If it is four channel,\n    only the first three channels will be scaled, while the fourth channel is\n    assumed to be in the range of [0,1]. If it is not four channel, a fourth\n    alpha channel will be added and set to fully opaque.  The resultant image\n    will be directly written to `filename` as a PNG with no colormap applied.\n    `max_val` is a value used if the array is passed in as anything other than\n    uint8; it will be the value used for scaling and clipping in the first\n    three channels when the array is converted.  Additionally, the minimum is\n    assumed to be zero; this makes it primarily suited for the results of\n    volume rendered images, rather than misaligned projections.\n\n    Parameters\n    ----------\n    bitmap_array : array_like\n        Array of shape (N,M,3) or (N,M,4), to be written.  If it is not already\n        a uint8 array, it will be scaled and converted to uint8.\n    filename : string\n        Filename to save to.  If None, PNG contents will be returned as a\n        string.\n    max_val : float, optional\n        The upper limit to clip values to in the output, if converting to uint8.\n        If `bitmap_array` is already uint8, this will be ignore.\n    transpose : boolean, optional\n        If transpose is False, we assume that the incoming bitmap_array is such\n        that the first element resides in the upper-left corner.  If True, the\n        first element will be placed in the lower-left corner.\n    \"\"\"\n    if len(bitmap_array.shape) != 3 or bitmap_array.shape[-1] not in (3, 4):\n        raise RuntimeError(\n            \"Expecting image array of shape (N,M,3) or \"\n            f\"(N,M,4), received {str(bitmap_array.shape)}\"\n        )\n\n    if bitmap_array.dtype != np.uint8:\n        s1, s2 = bitmap_array.shape[:2]\n        if bitmap_array.shape[-1] == 3:\n            alpha_channel = np.full((s1, s2, 1), 255, dtype=\"uint8\")\n        else:\n            alpha_channel = (\n                (255 * bitmap_array[:, :, 3]).astype(\"uint8\").reshape(s1, s2, 1)\n            )\n        if max_val is None:\n            max_val = bitmap_array[:, :, :3].max()\n            if max_val == 0.0:\n                # avoid dividing by zero for blank images\n                max_val = 1.0\n        bitmap_array = np.clip(bitmap_array[:, :, :3] / max_val, 0.0, 1.0) * 255\n        bitmap_array = np.concatenate(\n            [bitmap_array.astype(\"uint8\"), alpha_channel], axis=-1\n        )\n    if transpose:\n        bitmap_array = bitmap_array.swapaxes(0, 1).copy(order=\"C\")\n    if filename is not None:\n        pw.write_png(bitmap_array, filename)\n    else:\n        return pw.write_png_to_string(bitmap_array.copy())\n    return bitmap_array\n\n\ndef write_image(image, filename, color_bounds=None, cmap_name=None, func=lambda x: x):\n    r\"\"\"Write out a floating point array directly to a PNG file, scaling it and\n    applying a colormap.\n\n    This function will scale an image and directly call libpng to write out a\n    colormapped version of that image.  It is designed for rapid-fire saving of\n    image buffers generated using `yt.visualization.api.FixedResolutionBuffers`\n    and the likes.\n\n    Parameters\n    ----------\n    image : array_like\n        This is an (unscaled) array of floating point values, shape (N,N,) to\n        save in a PNG file.\n    filename : string\n        Filename to save as.\n    color_bounds : tuple of floats, optional\n        The min and max to scale between.  Outlying values will be clipped.\n    cmap_name : string, optional\n        An acceptable colormap.  See either yt.visualization.color_maps or\n        https://scipy-cookbook.readthedocs.io/items/Matplotlib_Show_colormaps.html .\n    func : function, optional\n        A function to transform the buffer before applying a colormap.\n\n    Returns\n    -------\n    scaled_image : uint8 image that has been saved\n\n    Examples\n    --------\n\n    >>> sl = ds.slice(0, 0.5, \"Density\")\n    >>> frb1 = FixedResolutionBuffer(sl, (0.2, 0.3, 0.4, 0.5), (1024, 1024))\n    >>> write_image(frb1[\"gas\", \"density\"], \"saved.png\")\n    \"\"\"\n    if cmap_name is None:\n        cmap_name = ytcfg.get(\"yt\", \"default_colormap\")\n    if len(image.shape) == 3:\n        mylog.info(\"Using only channel 1 of supplied image\")\n        image = image[:, :, 0]\n    to_plot = apply_colormap(image, color_bounds=color_bounds, cmap_name=cmap_name)\n    pw.write_png(to_plot, filename)\n    return to_plot\n\n\ndef apply_colormap(image, color_bounds=None, cmap_name=None, func=lambda x: x):\n    r\"\"\"Apply a colormap to a floating point image, scaling to uint8.\n\n    This function will scale an image and directly call libpng to write out a\n    colormapped version of that image.  It is designed for rapid-fire saving of\n    image buffers generated using `yt.visualization.api.FixedResolutionBuffers`\n    and the likes.\n\n    Parameters\n    ----------\n    image : array_like\n        This is an (unscaled) array of floating point values, shape (N,N,) to\n        save in a PNG file.\n    color_bounds : tuple of floats, optional\n        The min and max to scale between.  Outlying values will be clipped.\n    cmap_name : string, optional\n        An acceptable colormap.  See either yt.visualization.color_maps or\n        https://scipy-cookbook.readthedocs.io/items/Matplotlib_Show_colormaps.html .\n    func : function, optional\n        A function to transform the buffer before applying a colormap.\n\n    Returns\n    -------\n    to_plot : uint8 image with colorbar applied.\n\n    \"\"\"\n    if cmap_name is None:\n        cmap_name = ytcfg.get(\"yt\", \"default_colormap\")\n    from yt.data_objects.image_array import ImageArray\n\n    image = ImageArray(func(image))\n    if color_bounds is None:\n        mi = np.nanmin(image[~np.isinf(image)]) * image.uq\n        ma = np.nanmax(image[~np.isinf(image)]) * image.uq\n        color_bounds = mi, ma\n    else:\n        color_bounds = [YTQuantity(func(c), image.units) for c in color_bounds]\n    image = (image - color_bounds[0]) / (color_bounds[1] - color_bounds[0])\n    to_plot = map_to_colors(image, cmap_name)\n    to_plot = np.clip(to_plot, 0, 255)\n    return to_plot\n\n\ndef map_to_colors(buff, cmap_name):\n    lut = get_colormap_lut(cmap_name)\n\n    if isinstance(cmap_name, tuple):\n        # If we are using the colorbrewer maps, don't interpolate\n        shape = buff.shape\n        # We add float_eps so that digitize doesn't go out of bounds\n        x = np.mgrid[0.0 : 1.0 + np.finfo(np.float32).eps : lut[0].shape[0] * 1j]\n        inds = np.digitize(buff.ravel(), x).reshape(shape[0], shape[1])\n        mapped = np.dstack([(v[inds] * 255).astype(\"uint8\") for v in lut])\n        del inds\n    else:\n        x = np.mgrid[0.0 : 1.0 : lut[0].shape[0] * 1j]\n        mapped = np.dstack([(np.interp(buff, x, v) * 255).astype(\"uint8\") for v in lut])\n    return mapped.copy(\"C\")\n\n\ndef splat_points(image, points_x, points_y, contribution=None, transposed=False):\n    if contribution is None:\n        contribution = 100.0\n    val = contribution * 1.0 / points_x.size\n    if transposed:\n        points_y = 1.0 - points_y\n        points_x = 1.0 - points_x\n    im = image.copy()\n    au.add_points_to_image(im, points_x, points_y, val)\n    return im\n\n\ndef write_projection(\n    data,\n    filename,\n    colorbar=True,\n    colorbar_label=None,\n    title=None,\n    vmin=None,\n    vmax=None,\n    take_log=True,\n    figsize=(8, 6),\n    dpi=100,\n    cmap_name=None,\n    extent=None,\n    xlabel=None,\n    ylabel=None,\n):\n    r\"\"\"Write a projection or volume rendering to disk with a variety of\n    pretty parameters such as limits, title, colorbar, etc.  write_projection\n    uses the standard matplotlib interface to create the figure.  N.B. This code\n    only works *after* you have created the projection using the standard\n    framework (i.e. the Camera interface or off_axis_projection).\n\n    Accepts an NxM sized array representing the projection itself as well\n    as the filename to which you will save this figure.  Note that the final\n    resolution of your image will be a product of dpi/100 * figsize.\n\n    Parameters\n    ----------\n    data : array_like\n        image array as output by off_axis_projection or camera.snapshot()\n    filename : string\n        the filename where the data will be saved\n    colorbar : boolean\n        do you want a colorbar generated to the right of the image?\n    colorbar_label : string\n        the label associated with your colorbar\n    title : string\n        the label at the top of the figure\n    vmin : float or None\n        the lower limit of the zaxis (part of matplotlib api)\n    vmax : float or None\n        the lower limit of the zaxis (part of matplotlib api)\n    take_log : boolean\n        plot the log of the data array (and take the log of the limits if set)?\n    figsize : array_like\n        width, height in inches of final image\n    dpi : int\n        final image resolution in pixels / inch\n    cmap_name : string\n        The name of the colormap.\n\n    Examples\n    --------\n\n    >>> image = off_axis_projection(ds, c, L, W, N, \"Density\", no_ghost=False)\n    >>> write_projection(\n    ...     image,\n    ...     \"test.png\",\n    ...     colorbar_label=\"Column Density (cm$^{-2}$)\",\n    ...     title=\"Offaxis Projection\",\n    ...     vmin=1e-5,\n    ...     vmax=1e-3,\n    ...     take_log=True,\n    ... )\n    \"\"\"\n    if cmap_name is None:\n        cmap_name = ytcfg.get(\"yt\", \"default_colormap\")\n    import matplotlib.colors\n    import matplotlib.figure\n\n    # If this is rendered as log, then apply now.\n    if take_log:\n        norm_cls = matplotlib.colors.LogNorm\n    else:\n        norm_cls = matplotlib.colors.Normalize\n    norm = norm_cls(vmin=vmin, vmax=vmax)\n\n    # Create the figure and paint the data on\n    fig = matplotlib.figure.Figure(figsize=figsize)\n    ax = fig.add_subplot(111)\n\n    cax = ax.imshow(\n        data.to_ndarray(),\n        norm=norm,\n        extent=extent,\n        cmap=cmap_name,\n    )\n\n    if title:\n        ax.set_title(title)\n\n    if xlabel:\n        ax.set_xlabel(xlabel)\n    if ylabel:\n        ax.set_ylabel(ylabel)\n\n    # Suppress the x and y pixel counts\n    if extent is None:\n        ax.set_xticks(())\n        ax.set_yticks(())\n\n    # Add a color bar and label if requested\n    if colorbar:\n        cbar = fig.colorbar(cax)\n        if colorbar_label:\n            cbar.ax.set_ylabel(colorbar_label)\n\n    filename = validate_image_name(filename)\n    canvas = get_canvas(fig, filename)\n\n    mylog.info(\"Saving plot %s\", filename)\n    fig.tight_layout()\n\n    canvas.print_figure(filename, dpi=dpi)\n    return filename\n\n\ndef display_in_notebook(image, max_val=None):\n    \"\"\"\n    A helper function to display images in an IPython notebook\n\n    Must be run from within an IPython notebook, or else it will raise\n    a YTNotInsideNotebook exception.\n\n    Parameters\n    ----------\n    image : array_like\n        This is an (unscaled) array of floating point values, shape (N,N,3) or\n        (N,N,4) to display in the notebook. The first three channels will be\n        scaled automatically.\n    max_val : float, optional\n        The upper limit to clip values of the image.  Only applies to the first\n        three channels.\n    \"\"\"\n\n    if IS_IPYTHON:\n        from IPython.core.displaypub import publish_display_data\n\n        data = write_bitmap(image, None, max_val=max_val)\n        publish_display_data(\n            data={\"image/png\": data},\n            source=\"yt.visualization.image_writer.display_in_notebook\",\n        )\n    else:\n        raise YTNotInsideNotebook\n"
  },
  {
    "path": "yt/visualization/line_plot.py",
    "content": "from collections import defaultdict\n\nimport numpy as np\nfrom matplotlib.colors import LogNorm, Normalize, SymLogNorm\n\nfrom yt.funcs import is_sequence, mylog\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.units.yt_array import YTArray\nfrom yt.visualization.plot_container import (\n    BaseLinePlot,\n    PlotDictionary,\n    invalidate_plot,\n)\n\n\nclass LineBuffer:\n    r\"\"\"\n    LineBuffer(ds, start_point, end_point, npoints, label = None)\n\n    This takes a data source and implements a protocol for generating a\n    'pixelized', fixed-resolution line buffer. In other words, LineBuffer\n    takes a starting point, ending point, and number of sampling points and\n    can subsequently generate YTArrays of field values along the sample points.\n\n    Parameters\n    ----------\n    ds : :class:`yt.data_objects.static_output.Dataset`\n        This is the dataset object holding the data that can be sampled by the\n        LineBuffer\n    start_point : n-element list, tuple, ndarray, or YTArray\n        Contains the coordinates of the first point for constructing the LineBuffer.\n        Must contain n elements where n is the dimensionality of the dataset.\n    end_point : n-element list, tuple, ndarray, or YTArray\n        Contains the coordinates of the first point for constructing the LineBuffer.\n        Must contain n elements where n is the dimensionality of the dataset.\n    npoints : int\n        How many points to sample between start_point and end_point\n\n    Examples\n    --------\n    >>> lb = yt.LineBuffer(ds, (0.25, 0, 0), (0.25, 1, 0), 100)\n    >>> lb[\"all\", \"u\"].max()\n    0.11562424257143075 dimensionless\n\n    \"\"\"\n\n    def __init__(self, ds, start_point, end_point, npoints, label=None):\n        self.ds = ds\n        self.start_point = _validate_point(start_point, ds, start=True)\n        self.end_point = _validate_point(end_point, ds)\n        self.npoints = npoints\n        self.label = label\n        self.data = {}\n\n    def keys(self):\n        return self.data.keys()\n\n    def __setitem__(self, item, val):\n        self.data[item] = val\n\n    def __getitem__(self, item):\n        if item in self.data:\n            return self.data[item]\n        mylog.info(\"Making a line buffer with %d points of %s\", self.npoints, item)\n        self.points, self.data[item] = self.ds.coordinates.pixelize_line(\n            item, self.start_point, self.end_point, self.npoints\n        )\n\n        return self.data[item]\n\n    def __delitem__(self, item):\n        del self.data[item]\n\n\nclass LinePlotDictionary(PlotDictionary):\n    def __init__(self, data_source):\n        super().__init__(data_source)\n        self.known_dimensions = {}\n\n    def _sanitize_dimensions(self, item):\n        field = self.data_source._determine_fields(item)[0]\n        finfo = self.data_source.ds.field_info[field]\n        dimensions = Unit(\n            finfo.units, registry=self.data_source.ds.unit_registry\n        ).dimensions\n        if dimensions not in self.known_dimensions:\n            self.known_dimensions[dimensions] = item\n        return self.known_dimensions[dimensions]\n\n    def __getitem__(self, item):\n        ret_item = self._sanitize_dimensions(item)\n        return super().__getitem__(ret_item)\n\n    def __setitem__(self, item, value):\n        ret_item = self._sanitize_dimensions(item)\n        super().__setitem__(ret_item, value)\n\n    def __contains__(self, item):\n        ret_item = self._sanitize_dimensions(item)\n        return super().__contains__(ret_item)\n\n\nclass LinePlot(BaseLinePlot):\n    r\"\"\"\n    A class for constructing line plots\n\n    Parameters\n    ----------\n\n    ds : :class:`yt.data_objects.static_output.Dataset`\n        This is the dataset object corresponding to the\n        simulation output to be plotted.\n    fields : string / tuple, or list of strings / tuples\n        The name(s) of the field(s) to be plotted.\n    start_point : n-element list, tuple, ndarray, or YTArray\n        Contains the coordinates of the first point for constructing the line.\n        Must contain n elements where n is the dimensionality of the dataset.\n    end_point : n-element list, tuple, ndarray, or YTArray\n        Contains the coordinates of the first point for constructing the line.\n        Must contain n elements where n is the dimensionality of the dataset.\n    npoints : int\n        How many points to sample between start_point and end_point for\n        constructing the line plot\n    figure_size : int or two-element iterable of ints\n        Size in inches of the image.\n        Default: 5 (5x5)\n    fontsize : int\n        Font size for all text in the plot.\n        Default: 14\n    field_labels : dictionary\n        Keys should be the field names. Values should be latex-formattable\n        strings used in the LinePlot legend\n        Default: None\n\n\n    Example\n    -------\n\n    >>> import yt\n\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    >>> plot = yt.LinePlot(ds, \"density\", [0, 0, 0], [1, 1, 1], 512)\n    >>> plot.add_legend(\"density\")\n    >>> plot.set_x_unit(\"cm\")\n    >>> plot.set_unit(\"density\", \"kg/cm**3\")\n    >>> plot.save()\n\n    \"\"\"\n\n    _plot_dict_type = LinePlotDictionary\n    _plot_type = \"line_plot\"\n\n    _default_figure_size = (5.0, 5.0)\n    _default_font_size = 14.0\n\n    def __init__(\n        self,\n        ds,\n        fields,\n        start_point,\n        end_point,\n        npoints,\n        figure_size=None,\n        fontsize: float | None = None,\n        field_labels=None,\n    ):\n        \"\"\"\n        Sets up figure and axes\n        \"\"\"\n        line = LineBuffer(ds, start_point, end_point, npoints, label=None)\n        self.lines = [line]\n        self._initialize_instance(self, ds, fields, figure_size, fontsize, field_labels)\n        self._setup_plots()\n\n    @classmethod\n    def _initialize_instance(\n        cls, obj, ds, fields, figure_size, fontsize, field_labels=None\n    ):\n        obj._x_unit = None\n        obj._titles = {}\n\n        data_source = ds.all_data()\n\n        obj.fields = data_source._determine_fields(fields)\n        obj.include_legend = defaultdict(bool)\n        super(LinePlot, obj).__init__(\n            data_source, figure_size=figure_size, fontsize=fontsize\n        )\n        if field_labels is None:\n            obj.field_labels = {}\n        else:\n            obj.field_labels = field_labels\n        for f in obj.fields:\n            if f not in obj.field_labels:\n                obj.field_labels[f] = f[1]\n\n    def _get_axrect(self):\n        fontscale = self._font_properties._size / self.__class__._default_font_size\n        top_buff_size = 0.35 * fontscale\n\n        x_axis_size = 1.35 * fontscale\n        y_axis_size = 0.7 * fontscale\n        right_buff_size = 0.2 * fontscale\n\n        if is_sequence(self.figure_size):\n            figure_size = self.figure_size\n        else:\n            figure_size = (self.figure_size, self.figure_size)\n\n        xbins = np.array([x_axis_size, figure_size[0], right_buff_size])\n        ybins = np.array([y_axis_size, figure_size[1], top_buff_size])\n\n        x_frac_widths = xbins / xbins.sum()\n        y_frac_widths = ybins / ybins.sum()\n\n        return (\n            x_frac_widths[0],\n            y_frac_widths[0],\n            x_frac_widths[1],\n            y_frac_widths[1],\n        )\n\n    @classmethod\n    def from_lines(\n        cls, ds, fields, lines, figure_size=None, font_size=None, field_labels=None\n    ):\n        \"\"\"\n        A class method for constructing a line plot from multiple sampling lines\n\n        Parameters\n        ----------\n\n        ds : :class:`yt.data_objects.static_output.Dataset`\n            This is the dataset object corresponding to the\n            simulation output to be plotted.\n        fields : field name or list of field names\n            The name(s) of the field(s) to be plotted.\n        lines : list of :class:`yt.visualization.line_plot.LineBuffer` instances\n            The lines from which to sample data\n        figure_size : int or two-element iterable of ints\n            Size in inches of the image.\n            Default: 5 (5x5)\n        font_size : int\n            Font size for all text in the plot.\n            Default: 14\n        field_labels : dictionary\n            Keys should be the field names. Values should be latex-formattable\n            strings used in the LinePlot legend\n            Default: None\n\n        Example\n        --------\n        >>> ds = yt.load(\n        ...     \"SecondOrderTris/RZ_p_no_parts_do_nothing_bcs_cone_out.e\", step=-1\n        ... )\n        >>> fields = [field for field in ds.field_list if field[0] == \"all\"]\n        >>> lines = [\n        ...     yt.LineBuffer(ds, [0.25, 0, 0], [0.25, 1, 0], 100, label=\"x = 0.25\"),\n        ...     yt.LineBuffer(ds, [0.5, 0, 0], [0.5, 1, 0], 100, label=\"x = 0.5\"),\n        ... ]\n        >>> lines.append()\n\n        >>> plot = yt.LinePlot.from_lines(ds, fields, lines)\n        >>> plot.save()\n\n        \"\"\"\n        obj = cls.__new__(cls)\n        obj.lines = lines\n        cls._initialize_instance(obj, ds, fields, figure_size, font_size, field_labels)\n        obj._setup_plots()\n        return obj\n\n    def _setup_plots(self):\n        if self._plot_valid:\n            return\n        for plot in self.plots.values():\n            plot.axes.cla()\n        for line in self.lines:\n            dimensions_counter = defaultdict(int)\n            for field in self.fields:\n                finfo = self.ds.field_info[field]\n                dimensions = Unit(\n                    finfo.units, registry=self.ds.unit_registry\n                ).dimensions\n                dimensions_counter[dimensions] += 1\n            for field in self.fields:\n                # get plot instance\n                plot = self._get_plot_instance(field)\n\n                # calculate x and y\n                x, y = self.ds.coordinates.pixelize_line(\n                    field, line.start_point, line.end_point, line.npoints\n                )\n\n                # scale x and y to proper units\n                if self._x_unit is None:\n                    unit_x = x.units\n                else:\n                    unit_x = self._x_unit\n\n                unit_y = plot.norm_handler.display_units\n\n                x.convert_to_units(unit_x)\n                y.convert_to_units(unit_y)\n\n                # determine legend label\n                str_seq = []\n                str_seq.append(line.label)\n                str_seq.append(self.field_labels[field])\n                delim = \"; \"\n                legend_label = delim.join(filter(None, str_seq))\n\n                # apply plot to matplotlib axes\n                plot.axes.plot(x, y, label=legend_label)\n\n                # apply log transforms if requested\n                norm = plot.norm_handler.get_norm(data=y)\n                y_norm_type = type(norm)\n                if y_norm_type is Normalize:\n                    plot.axes.set_yscale(\"linear\")\n                elif y_norm_type is LogNorm:\n                    plot.axes.set_yscale(\"log\")\n                elif y_norm_type is SymLogNorm:\n                    plot.axes.set_yscale(\"symlog\")\n                else:\n                    raise NotImplementedError(\n                        f\"LinePlot doesn't support y norm with type {type(norm)}\"\n                    )\n\n                # set font properties\n                plot._set_font_properties(self._font_properties, None)\n\n                # set x and y axis labels\n                axes_unit_labels = self._get_axes_unit_labels(unit_x, unit_y)\n\n                if self._xlabel is not None:\n                    x_label = self._xlabel\n                else:\n                    x_label = r\"$\\rm{Path\\ Length\" + axes_unit_labels[0] + \"}$\"\n\n                if self._ylabel is not None:\n                    y_label = self._ylabel\n                else:\n                    finfo = self.ds.field_info[field]\n                    dimensions = Unit(\n                        finfo.units, registry=self.ds.unit_registry\n                    ).dimensions\n                    if dimensions_counter[dimensions] > 1:\n                        y_label = (\n                            r\"$\\rm{Multiple\\ Fields}$\"\n                            + r\"$\\rm{\"\n                            + axes_unit_labels[1]\n                            + \"}$\"\n                        )\n                    else:\n                        y_label = (\n                            finfo.get_latex_display_name()\n                            + r\"$\\rm{\"\n                            + axes_unit_labels[1]\n                            + \"}$\"\n                        )\n\n                plot.axes.set_xlabel(x_label)\n                plot.axes.set_ylabel(y_label)\n\n                # apply title\n                if field in self._titles:\n                    plot.axes.set_title(self._titles[field])\n\n                # apply legend\n                dim_field = self.plots._sanitize_dimensions(field)\n                if self.include_legend[dim_field]:\n                    plot.axes.legend()\n\n        self._plot_valid = True\n\n    @invalidate_plot\n    def annotate_legend(self, field):\n        \"\"\"\n        Adds a legend to the `LinePlot` instance. The `_sanitize_dimensions`\n        call ensures that a legend label will be added for every field of\n        a multi-field plot\n        \"\"\"\n        dim_field = self.plots._sanitize_dimensions(field)\n        self.include_legend[dim_field] = True\n\n    @invalidate_plot\n    def set_x_unit(self, unit_name):\n        \"\"\"Set the unit to use along the x-axis\n\n        Parameters\n        ----------\n        unit_name: str\n          The name of the unit to use for the x-axis unit\n        \"\"\"\n        self._x_unit = unit_name\n\n    @invalidate_plot\n    def set_unit(self, field, new_unit):\n        \"\"\"Set the unit used to plot the field\n\n        Parameters\n        ----------\n        field: str or field tuple\n           The name of the field to set the units for\n        new_unit: string or Unit object\n        \"\"\"\n        field = self.data_source._determine_fields(field)[0]\n        pnh = self.plots[field].norm_handler\n        pnh.display_units = new_unit\n\n    @invalidate_plot\n    def annotate_title(self, field, title):\n        \"\"\"Set the unit used to plot the field\n\n        Parameters\n        ----------\n        field: str or field tuple\n           The name of the field to set the units for\n        title: str\n           The title to use for the plot\n        \"\"\"\n        self._titles[self.data_source._determine_fields(field)[0]] = title\n\n\ndef _validate_point(point, ds, start=False):\n    if not is_sequence(point):\n        raise RuntimeError(\"Input point must be array-like\")\n    if not isinstance(point, YTArray):\n        point = ds.arr(point, \"code_length\", dtype=np.float64)\n    if len(point.shape) != 1:\n        raise RuntimeError(\"Input point must be a 1D array\")\n    if point.shape[0] < ds.dimensionality:\n        raise RuntimeError(\"Input point must have an element for each dimension\")\n    # need to pad to 3D elements to avoid issues later\n    if point.shape[0] < 3:\n        if start:\n            val = 0\n        else:\n            val = 1\n        point = np.append(point.d, [val] * (3 - ds.dimensionality)) * point.uq\n    return point\n"
  },
  {
    "path": "yt/visualization/mapserver/__init__.py",
    "content": ""
  },
  {
    "path": "yt/visualization/mapserver/html/Leaflet.Coordinates-0.1.5.css",
    "content": "/*\n * From https://github.com/MrMufflon/Leaflet.Coordinates\n *\n * Fixed small issue about formatting by C. Cadiou (cphyc))\n */\n.leaflet-control-coordinates{background-color:#D8D8D8;background-color:rgba(255,255,255,.8);cursor:pointer}.leaflet-control-coordinates,.leaflet-control-coordinates .uiElement input{-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.leaflet-control-coordinates .uiElement{margin:4px}.leaflet-control-coordinates .uiElement .labelFirst{margin-right:4px}.leaflet-control-coordinates .uiHidden{display:none}.leaflet-control-coordinates .uiElement.label{color:inherit;font-weight:inherit;font-size:inherit;padding:0;display:inherit}\n"
  },
  {
    "path": "yt/visualization/mapserver/html/Leaflet.Coordinates-0.1.5.src.js",
    "content": "/*\n * From https://github.com/MrMufflon/Leaflet.Coordinates\n *\n * Fixed small issue about formatting by C. Cadiou (cphyc))\n */\n\n/*\n * L.Control.Coordinates is used for displaying current mouse coordinates on the map.\n */\n\nL.Control.Coordinates = L.Control.extend({\n\toptions: {\n\t\tposition: 'bottomright',\n\t\t//decimals used if not using DMS or labelFormatter functions\n\t\tdecimals: 4,\n\t\t//decimalseperator used if not using DMS or labelFormatter functions\n\t\tdecimalSeperator: \".\",\n\t\t//label templates for usage if no labelFormatter function is defined\n\t\tlabelTemplateLat: \"Lat: {y}\",\n\t\tlabelTemplateLng: \"Lng: {x}\",\n\t\t//label formatter functions\n\t\tlabelFormatterLat: undefined,\n\t\tlabelFormatterLng: undefined,\n\t\t//switch on/off input fields on click\n\t\tenableUserInput: true,\n\t\t//use Degree-Minute-Second\n\t\tuseDMS: false,\n\t\t//if true lat-lng instead of lng-lat label ordering is used\n\t\tuseLatLngOrder: false,\n\t\t//if true user given coordinates are centered directly\n\t\tcenterUserCoordinates: false,\n\t\t//leaflet marker type\n\t\tmarkerType: L.marker,\n\t\t//leaflet marker properties\n\t\tmarkerProps: {}\n\t},\n\n\tonAdd: function(map) {\n\t\tthis._map = map;\n\n\t\tvar className = 'leaflet-control-coordinates',\n\t\t\tcontainer = this._container = L.DomUtil.create('div', className),\n\t\t\toptions = this.options;\n\n\t\t//label containers\n\t\tthis._labelcontainer = L.DomUtil.create(\"div\", \"uiElement label\", container);\n\t\tthis._label = L.DomUtil.create(\"span\", \"labelFirst\", this._labelcontainer);\n\n\n\t\t//input containers\n\t\tthis._inputcontainer = L.DomUtil.create(\"div\", \"uiElement input uiHidden\", container);\n\t\tvar xSpan, ySpan;\n\t\tif (options.useLatLngOrder) {\n\t\t\tySpan = L.DomUtil.create(\"span\", \"\", this._inputcontainer);\n\t\t\tthis._inputY = this._createInput(\"inputY\", this._inputcontainer);\n\t\t\txSpan = L.DomUtil.create(\"span\", \"\", this._inputcontainer);\n\t\t\tthis._inputX = this._createInput(\"inputX\", this._inputcontainer);\n\t\t} else {\n\t\t\txSpan = L.DomUtil.create(\"span\", \"\", this._inputcontainer);\n\t\t\tthis._inputX = this._createInput(\"inputX\", this._inputcontainer);\n\t\t\tySpan = L.DomUtil.create(\"span\", \"\", this._inputcontainer);\n\t\t\tthis._inputY = this._createInput(\"inputY\", this._inputcontainer);\n\t\t}\n\t\txSpan.innerHTML = options.labelTemplateLng.replace(\"{x}\", \"\");\n\t\tySpan.innerHTML = options.labelTemplateLat.replace(\"{y}\", \"\");\n\n\t\tL.DomEvent.on(this._inputX, 'keyup', this._handleKeypress, this);\n\t\tL.DomEvent.on(this._inputY, 'keyup', this._handleKeypress, this);\n\n\t\t//connect to mouseevents\n\t\tmap.on(\"mousemove\", this._update, this);\n\t\tmap.on('dragstart', this.collapse, this);\n\n\t\tmap.whenReady(this._update, this);\n\n\t\tthis._showsCoordinates = true;\n\t\t//whether or not to show inputs on click\n\t\tif (options.enableUserInput) {\n\t\t\tL.DomEvent.addListener(this._container, \"click\", this._switchUI, this);\n\t\t}\n\n\t\treturn container;\n\t},\n\n\t/**\n\t *\tCreates an input HTML element in given container with given classname\n\t */\n\t_createInput: function(classname, container) {\n\t\tvar input = L.DomUtil.create(\"input\", classname, container);\n\t\tinput.type = \"text\";\n\t\tL.DomEvent.disableClickPropagation(input);\n\t\treturn input;\n\t},\n\n\t_clearMarker: function() {\n\t\tthis._map.removeLayer(this._marker);\n\t},\n\n\t/**\n\t *\tCalled onkeyup of input fields\n\t */\n\t_handleKeypress: function(e) {\n\t\tswitch (e.keyCode) {\n\t\t\tcase 27: //Esc\n\t\t\t\tthis.collapse();\n\t\t\t\tbreak;\n\t\t\tcase 13: //Enter\n\t\t\t\tthis._handleSubmit();\n\t\t\t\tthis.collapse();\n\t\t\t\tbreak;\n\t\t\tdefault: //All keys\n\t\t\t\tthis._handleSubmit();\n\t\t\t\tbreak;\n\t\t}\n\t},\n\n\t/**\n\t *\tCalled on each keyup except ESC\n\t */\n\t_handleSubmit: function() {\n\t\tvar x = L.NumberFormatter.createValidNumber(this._inputX.value, this.options.decimalSeperator);\n\t\tvar y = L.NumberFormatter.createValidNumber(this._inputY.value, this.options.decimalSeperator);\n\t\tif (x !== undefined && y !== undefined) {\n\t\t\tvar marker = this._marker;\n\t\t\tif (!marker) {\n\t\t\t\tmarker = this._marker = this._createNewMarker();\n\t\t\t\tmarker.on(\"click\", this._clearMarker, this);\n\t\t\t}\n\t\t\tvar ll = new L.LatLng(y, x);\n\t\t\tmarker.setLatLng(ll);\n\t\t\tmarker.addTo(this._map);\n\t\t\tif (this.options.centerUserCoordinates) {\n\t\t\t\tthis._map.setView(ll, this._map.getZoom());\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t *\tShows inputs fields\n\t */\n\texpand: function() {\n\t\tthis._showsCoordinates = false;\n\n\t\tthis._map.off(\"mousemove\", this._update, this);\n\n\t\tL.DomEvent.addListener(this._container, \"mousemove\", L.DomEvent.stop);\n\t\tL.DomEvent.removeListener(this._container, \"click\", this._switchUI, this);\n\n\t\tL.DomUtil.addClass(this._labelcontainer, \"uiHidden\");\n\t\tL.DomUtil.removeClass(this._inputcontainer, \"uiHidden\");\n\t},\n\n\t/**\n\t *\tCreates the label according to given options and formatters\n\t */\n\t_createCoordinateLabel: function(ll) {\n\t\tvar opts = this.options,\n\t\t\tx, y;\n\t\tif (opts.customLabelFcn) {\n\t\t\treturn opts.customLabelFcn(ll, opts);\n\t\t}\n\t\tif (opts.labelFormatterLng) {\n\t\t\tx = opts.labelFormatterLng(ll.lng);\n\t\t} else {\n\t\t\tx = L.Util.template(opts.labelTemplateLng, {\n\t\t\t\tx: this._getNumber(ll.lng, opts)\n\t\t\t});\n\t\t}\n\t\tif (opts.labelFormatterLat) {\n\t\t\ty = opts.labelFormatterLat(ll.lat);\n\t\t} else {\n\t\t\ty = L.Util.template(opts.labelTemplateLat, {\n\t\t\t\ty: this._getNumber(ll.lat, opts)\n\t\t\t});\n\t\t}\n\t\tif (opts.useLatLngOrder) {\n\t\t\treturn y + \" \" + x;\n\t\t}\n\t\treturn x + \" \" + y;\n\t},\n\n\t/**\n\t *\tReturns a Number according to options (DMS or decimal)\n\t */\n\t_getNumber: function(n, opts) {\n\t\tvar res;\n\t\tif (opts.useDMS) {\n\t\t\tres = L.NumberFormatter.toDMS(n);\n\t\t} else {\n\t\t\tres = L.NumberFormatter.round(n, opts.decimals, opts.decimalSeperator);\n\t\t}\n\t\treturn res;\n\t},\n\n\t/**\n\t *\tShows coordinate labels after user input has ended. Also\n\t *\tdisplays a marker with popup at the last input position.\n\t */\n\tcollapse: function() {\n\t\tif (!this._showsCoordinates) {\n\t\t\tthis._map.on(\"mousemove\", this._update, this);\n\t\t\tthis._showsCoordinates = true;\n\t\t\tvar opts = this.options;\n\t\t\tL.DomEvent.addListener(this._container, \"click\", this._switchUI, this);\n\t\t\tL.DomEvent.removeListener(this._container, \"mousemove\", L.DomEvent.stop);\n\n\t\t\tL.DomUtil.addClass(this._inputcontainer, \"uiHidden\");\n\t\t\tL.DomUtil.removeClass(this._labelcontainer, \"uiHidden\");\n\n\t\t\tif (this._marker) {\n\t\t\t\tvar m = this._createNewMarker(),\n\t\t\t\t\tll = this._marker.getLatLng();\n\t\t\t\tm.setLatLng(ll);\n\n\t\t\t\tvar container = L.DomUtil.create(\"div\", \"\");\n\t\t\t\tvar label = L.DomUtil.create(\"div\", \"\", container);\n\t\t\t\tlabel.innerHTML = this._ordinateLabel(ll);\n\n\t\t\t\tvar close = L.DomUtil.create(\"a\", \"\", container);\n\t\t\t\tclose.innerHTML = \"Remove\";\n\t\t\t\tclose.href = \"#\";\n\t\t\t\tvar stop = L.DomEvent.stopPropagation;\n\n\t\t\t\tL.DomEvent\n\t\t\t\t\t.on(close, 'click', stop)\n\t\t\t\t\t.on(close, 'mousedown', stop)\n\t\t\t\t\t.on(close, 'dblclick', stop)\n\t\t\t\t\t.on(close, 'click', L.DomEvent.preventDefault)\n\t\t\t\t\t.on(close, 'click', function() {\n\t\t\t\t\t\tthis._map.removeLayer(m);\n\t\t\t\t\t}, this);\n\n\t\t\t\tm.bindPopup(container);\n\t\t\t\tm.addTo(this._map);\n\t\t\t\tthis._map.removeLayer(this._marker);\n\t\t\t\tthis._marker = null;\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t *\tClick callback for UI\n\t */\n\t_switchUI: function(evt) {\n\t\tL.DomEvent.stop(evt);\n\t\tL.DomEvent.stopPropagation(evt);\n\t\tL.DomEvent.preventDefault(evt);\n\t\tif (this._showsCoordinates) {\n\t\t\t//show textfields\n\t\t\tthis.expand();\n\t\t} else {\n\t\t\t//show coordinates\n\t\t\tthis.collapse();\n\t\t}\n\t},\n\n\tonRemove: function(map) {\n\t\tmap.off(\"mousemove\", this._update, this);\n\t},\n\n\t/**\n\t *\tMousemove callback function updating labels and input elements\n\t */\n\t_update: function(evt) {\n\t\tvar pos = evt.latlng,\n\t\t\topts = this.options;\n\t\tif (pos) {\n\t\t\tpos = pos.wrap();\n\t\t\tthis._currentPos = pos;\n\t\t\tthis._inputY.value = L.NumberFormatter.round(pos.lat, opts.decimals, opts.decimalSeperator);\n\t\t\tthis._inputX.value = L.NumberFormatter.round(pos.lng, opts.decimals, opts.decimalSeperator);\n\t\t\tthis._label.innerHTML = this._createCoordinateLabel(pos);\n\t\t}\n\t},\n\n\t_createNewMarker: function() {\n\t\treturn this.options.markerType(null, this.options.markerProps);\n\t}\n\n});\n\n//constructor registration\nL.control.coordinates = function(options) {\n\treturn new L.Control.Coordinates(options);\n};\n\n//map init hook\nL.Map.mergeOptions({\n\tcoordinateControl: false\n});\n\nL.Map.addInitHook(function() {\n\tif (this.options.coordinateControl) {\n\t\tthis.coordinateControl = new L.Control.Coordinates();\n\t\tthis.addControl(this.coordinateControl);\n\t}\n});\nL.NumberFormatter = {\n\tround: function(num, dec, sep) {\n\t\tvar res = L.Util.formatNum(num, dec) + \"\",\n\t\t\tnumbers = res.split(\".\");\n\t\tif (numbers[1]) {\n\t\t\tvar d = dec - numbers[1].length;\n\t\t\tfor (; d > 0; d--) {\n\t\t\t\tnumbers[1] += \"0\";\n\t\t\t}\n\t\t\tres = numbers.join(sep || \".\");\n\t\t}\n\t\treturn res;\n\t},\n\n\ttoDMS: function(deg) {\n\t\tvar d = Math.floor(Math.abs(deg));\n\t\tvar minfloat = (Math.abs(deg) - d) * 60;\n\t\tvar m = Math.floor(minfloat);\n\t\tvar secfloat = (minfloat - m) * 60;\n\t\tvar s = Math.round(secfloat);\n\t\tif (s == 60) {\n\t\t\tm++;\n\t\t\ts = \"00\";\n\t\t}\n\t\tif (m == 60) {\n\t\t\td++;\n\t\t\tm = \"00\";\n\t\t}\n\t\tif (s < 10) {\n\t\t\ts = \"0\" + s;\n\t\t}\n\t\tif (m < 10) {\n\t\t\tm = \"0\" + m;\n\t\t}\n\t\tvar dir = \"\";\n\t\tif (deg < 0) {\n\t\t\tdir = \"-\";\n\t\t}\n\t\treturn (\"\" + dir + d + \"&deg; \" + m + \"' \" + s + \"''\");\n\t},\n\n\tcreateValidNumber: function(num, sep) {\n\t\tif (num && num.length > 0) {\n\t\t\tvar numbers = num.split(sep || \".\");\n\t\t\ttry {\n\t\t\t\tvar numRes = Number(numbers.join(\".\"));\n\t\t\t\tif (isNaN(numRes)) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\treturn numRes;\n\t\t\t} catch (e) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n};\n"
  },
  {
    "path": "yt/visualization/mapserver/html/__init__.py",
    "content": ""
  },
  {
    "path": "yt/visualization/mapserver/html/map.js",
    "content": "function setFullScreen () {\n    $(\"#map\").width($(window).width());\n    $(\"#map\").height($(window).height());\n}\n\nvar SearchWidget = function () {\n  var obj = {\n    filter: function (searchStrs) {\n      console.log(\"filtering on \" + searchStrs);\n      this._selector.each(function(i, el) {\n        var val = $(el).text();\n        // Search\n        var matched = searchStrs.map((str) => {\n          return val.indexOf(str) !== -1;\n        }).reduce((reduced, result) => {\n          return reduced && result;\n        }, true);\n        if (matched) {\n          $(el).show();\n        } else {\n          $(el).hide();\n        }\n      });\n    },\n    init: function () {\n      var self = this;\n\n      var searchElement = $('<div id=\"filter\"><input type=\"text\" placeholder=\"Filter layers\"></div>');\n      var selector = $('.leaflet-control-layers-list label');\n\n      this._selector = selector;\n\n      // Add input in the DOM\n      selector.first().parent().prepend(searchElement);\n\n      // Listen to keyboard input\n      $('#filter input').keyup(function(ev) {\n        const val = $(this).val();\n        self.filter(val.split(\" \"));\n      });\n\n    },\n    _selector: null\n  };\n  obj.init();\n  return obj;\n};\n$(document).ready(function() {\n  // Initialize to full screen\n  setFullScreen();\n  // initialize the map on the \"map\" div with a given center and zoom\n  $.getJSON('/list', function(data) {\n    var layers = [],\n        layer_groups = [],\n        default_layer = [null];\n    var layer_group = {};\n\n    // Loop over field types\n    for (var type in data['data']) {\n      var dtype = data['data'][type];\n\n      // Loop over fields of given type\n      for (var field in dtype) {\n        var loc = dtype[field]\n        var field = loc[0],\n            active = loc[1],\n            url = 'map/' + field[0] + ',' + field[1] + '/{z}/{x}/{y}.png';\n\n        // Create new layer\n        var layer = new L.TileLayer(url, {id: 'MapID', maxzoom: 18});\n\n        // Create readable name\n        human_name = field.join(' ');\n\n        // Store it\n        layers.push(layer);\n        layer_group[human_name] = layer;\n        if (active) {\n          default_layer[0] = layer;\n        }\n      }\n    }\n    var map = new L.Map('map', {\n      crs: L.CRS.Simple,\n      center: new L.LatLng(-128, -128),\n      zoom: 4,\n      layers: default_layer\n    });\n\n    L.control.layers(layer_group).addTo(map);\n\n    var unit = data['unit'], px2unit = data['px2unit'], decimals = 2;\n    var fmt = (n) => {\n      return L.NumberFormatter.round(n, decimals, \".\")\n    };\n    L.control.coordinates({\n      position: \"bottomleft\", //optional default \"bootomright\"\n      decimals: 2, //optional default 4\n      decimalSeperator: \".\", //optional default \".\"\n      enableUserInput: false, //optional default true\n      useDMS: false, //optional default false\n      useLatLngOrder: false, //ordering of labels, default false-> lng-lat\n      markerType: L.marker, //optional default L.marker\n      labelFormatterLng : (lng) => {\n        return fmt((lng+128)*px2unit) + \" \" + unit\n      }, //optional default none,\n      labelFormatterLat : (lat) => {\n        return fmt((lat+128)*px2unit) + \" \" + unit\n      }, //optional default none\n    }).addTo(map);\n\n    // Search widget\n    var search = SearchWidget();\n  });\n\n  // Resize map automatically\n  $(window).resize(setFullScreen);\n});\n"
  },
  {
    "path": "yt/visualization/mapserver/html/map_index.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n<!-- Leaflet JavaScript -->\n<script type=\"text/javascript\" src=\"//cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js\"></script>\n<link rel=\"stylesheet\" href=\"//cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.css\" />\n<script type=\"text/javascript\" src=\"//ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js\"></script>\n\n<script type=\"text/javascript\" src=\"static/Leaflet.Coordinates-0.1.5.src.js\"></script>\n<link rel=\"stylesheet\" href=\"static/Leaflet.Coordinates-0.1.5.css\" />\n\n<script type=\"text/javascript\" src=\"static/map.js\"></script>\n<style>\nbody {\n    margin: 0;\n}\n</style>\n</head>\n<body>\n  <div id=\"map\" style=\"height: 500px; width: 500px;\"></div>\n</body>\n</html>\n"
  },
  {
    "path": "yt/visualization/mapserver/pannable_map.py",
    "content": "import os\nfrom functools import wraps\n\nimport bottle\nimport numpy as np\n\nfrom yt.fields.derived_field import ValidateSpatial\nfrom yt.utilities.lib.misc_utilities import get_color_bounds\nfrom yt.utilities.png_writer import write_png_to_string\nfrom yt.visualization.fixed_resolution import FixedResolutionBuffer\nfrom yt.visualization.image_writer import apply_colormap\n\nlocal_dir = os.path.dirname(__file__)\n\n\ndef exc_writeout(f):\n    import traceback\n\n    @wraps(f)\n    def func(*args, **kwargs):\n        try:\n            rv = f(*args, **kwargs)\n            return rv\n        except Exception:\n            traceback.print_exc(None, open(\"temp.exc\", \"w\"))\n            raise\n\n    return func\n\n\nclass PannableMapServer:\n    _widget_name = \"pannable_map\"\n\n    def __init__(self, data, field, takelog, cmap, route_prefix=\"\"):\n        self.data = data\n        self.ds = data.ds\n        self.field = field\n        self.cmap = cmap\n\n        bottle.route(f\"{route_prefix}/map/:field/:L/:x/:y.png\")(self.map)\n        bottle.route(f\"{route_prefix}/map/:field/:L/:x/:y.png\")(self.map)\n        bottle.route(f\"{route_prefix}/\")(self.index)\n        bottle.route(f\"{route_prefix}/:field\")(self.index)\n        bottle.route(f\"{route_prefix}/index.html\")(self.index)\n        bottle.route(f\"{route_prefix}/list\", \"GET\")(self.list_fields)\n        # This is a double-check, since we do not always mandate this for\n        # slices:\n        self.data[self.field] = self.data[self.field].astype(\"float64\", copy=False)\n        bottle.route(f\"{route_prefix}/static/:path\", \"GET\")(self.static)\n\n        self.takelog = takelog\n        self._lock = False\n\n        for unit in [\"Gpc\", \"Mpc\", \"kpc\", \"pc\"]:\n            v = self.ds.domain_width[0].in_units(unit).value\n            if v > 1:\n                break\n        self.unit = unit\n        self.px2unit = self.ds.domain_width[0].in_units(unit).value / 256\n\n    def lock(self):\n        import time\n\n        while self._lock:\n            time.sleep(0.01)\n        self._lock = True\n\n    def unlock(self):\n        self._lock = False\n\n    def map(self, field, L, x, y):\n        if \",\" in field:\n            field = tuple(field.split(\",\"))\n        cmap = self.cmap\n        dd = 1.0 / (2.0 ** (int(L)))\n        relx = int(x) * dd\n        rely = int(y) * dd\n        DW = self.ds.domain_right_edge - self.ds.domain_left_edge\n        xl = self.ds.domain_left_edge[0] + relx * DW[0]\n        yl = self.ds.domain_left_edge[1] + rely * DW[1]\n        xr = xl + dd * DW[0]\n        yr = yl + dd * DW[1]\n        try:\n            self.lock()\n            w = 256  # pixels\n            data = self.data[field]\n            frb = FixedResolutionBuffer(self.data, (xl, xr, yl, yr), (w, w))\n            cmi, cma = get_color_bounds(\n                self.data[\"px\"],\n                self.data[\"py\"],\n                self.data[\"pdx\"],\n                self.data[\"pdy\"],\n                data,\n                self.ds.domain_left_edge[0],\n                self.ds.domain_right_edge[0],\n                self.ds.domain_left_edge[1],\n                self.ds.domain_right_edge[1],\n                dd * DW[0] / (64 * 256),\n                dd * DW[0],\n            )\n        finally:\n            self.unlock()\n\n        if self.takelog:\n            cmi = np.log10(cmi)\n            cma = np.log10(cma)\n            to_plot = apply_colormap(\n                np.log10(frb[field]), color_bounds=(cmi, cma), cmap_name=cmap\n            )\n        else:\n            to_plot = apply_colormap(\n                frb[field], color_bounds=(cmi, cma), cmap_name=cmap\n            )\n\n        rv = write_png_to_string(to_plot)\n        return rv\n\n    def index(self, field=None):\n        if field is not None:\n            self.field = field\n        return bottle.static_file(\n            \"map_index.html\", root=os.path.join(local_dir, \"html\")\n        )\n\n    def static(self, path):\n        if path[-4:].lower() in (\".png\", \".gif\", \".jpg\"):\n            bottle.response.headers[\"Content-Type\"] = f\"image/{path[-3:].lower()}\"\n        elif path[-4:].lower() == \".css\":\n            bottle.response.headers[\"Content-Type\"] = \"text/css\"\n        elif path[-3:].lower() == \".js\":\n            bottle.response.headers[\"Content-Type\"] = \"text/javascript\"\n        full_path = os.path.join(os.path.join(local_dir, \"html\"), path)\n        return open(full_path).read()\n\n    def list_fields(self):\n        d = {}\n\n        # Add fluid fields (only gas for now)\n        for ftype in self.ds.fluid_types:\n            d[ftype] = []\n            for f in self.ds.derived_field_list:\n                if f[0] != ftype:\n                    continue\n                # Discard fields which need ghost zones for now\n                df = self.ds.field_info[f]\n                if any(isinstance(v, ValidateSpatial) for v in df.validators):\n                    continue\n                # Discard cutting plane fields\n                if \"cutting\" in f[1]:\n                    continue\n                active = f[1] == self.field\n                d[ftype].append((f, active))\n\n        print(self.px2unit, self.unit)\n        return {\n            \"data\": d,\n            \"px2unit\": self.px2unit,\n            \"unit\": self.unit,\n            \"active\": self.field,\n        }\n"
  },
  {
    "path": "yt/visualization/particle_plots.py",
    "content": "import warnings\nfrom typing import Union\n\nimport numpy as np\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt.data_objects.profiles import create_profile\nfrom yt.data_objects.static_output import Dataset\nfrom yt.funcs import fix_axis, iter_fields, mylog\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.orientation import Orientation\nfrom yt.visualization.fixed_resolution import ParticleImageBuffer\nfrom yt.visualization.profile_plotter import PhasePlot\n\nfrom .plot_window import (\n    NormalPlot,\n    PWViewerMPL,\n    get_axes_unit,\n    get_oblique_window_parameters,\n    get_window_parameters,\n)\n\n\nclass ParticleDummyDataSource:\n    _type_name = \"Particle\"\n    _dimensionality = 2\n    _con_args = (\"center\", \"axis\", \"width\", \"fields\", \"weight_field\")\n    _tds_attrs = ()\n    _key_fields: list[str] = []\n\n    def __init__(\n        self,\n        center,\n        ds,\n        width,\n        fields,\n        dd,\n        *,\n        weight_field=None,\n        field_parameters=None,\n        deposition=\"ngp\",\n        density=False,\n    ):\n        self.center = center\n        self.ds = ds\n        self.width = width\n        self.dd = dd\n\n        if weight_field is not None:\n            weight_field = self._determine_fields(weight_field)[0]\n        self.weight_field = weight_field\n\n        self.deposition = deposition\n        self.density = density\n\n        if field_parameters is None:\n            self.field_parameters = {}\n        else:\n            self.field_parameters = field_parameters\n\n        fields = self._determine_fields(fields)\n        self.fields = fields\n\n    def _determine_fields(self, *args):\n        return self.dd._determine_fields(*args)\n\n    def get_field_parameter(self, name, default=None):\n        \"\"\"\n        This is typically only used by derived field functions, but\n        it returns parameters used to generate fields.\n        \"\"\"\n        if name in self.field_parameters:\n            return self.field_parameters[name]\n        else:\n            return default\n\n\nclass ParticleAxisAlignedDummyDataSource(ParticleDummyDataSource):\n    def __init__(\n        self,\n        center,\n        ds,\n        axis,\n        width,\n        fields,\n        *,\n        weight_field=None,\n        field_parameters=None,\n        data_source=None,\n        deposition=\"ngp\",\n        density=False,\n    ):\n        self.axis = axis\n\n        LE = center - 0.5 * YTArray(width)\n        RE = center + 0.5 * YTArray(width)\n        for ax in range(3):\n            if not ds.periodicity[ax]:\n                LE[ax] = max(LE[ax], ds.domain_left_edge[ax])\n                RE[ax] = min(RE[ax], ds.domain_right_edge[ax])\n\n        dd = ds.region(\n            center,\n            LE,\n            RE,\n            fields,\n            field_parameters=field_parameters,\n            data_source=data_source,\n        )\n\n        super().__init__(\n            center,\n            ds,\n            width,\n            fields,\n            dd,\n            weight_field=weight_field,\n            field_parameters=field_parameters,\n            deposition=deposition,\n            density=density,\n        )\n\n\nclass ParticleOffAxisDummyDataSource(ParticleDummyDataSource):\n    def __init__(\n        self,\n        center,\n        ds,\n        normal_vector,\n        width,\n        fields,\n        *,\n        weight_field=None,\n        field_parameters=None,\n        data_source=None,\n        deposition=\"ngp\",\n        density=False,\n        north_vector=None,\n    ):\n        self.axis = None  # always true for oblique data objects\n        normal = np.array(normal_vector)\n        normal = normal / np.linalg.norm(normal)\n\n        # If north_vector is None, we set the default here.\n        # This is chosen so that if normal_vector is one of the\n        # cartesian coordinate axes, the projection will match\n        # the corresponding on-axis projection.\n        if north_vector is None:\n            vecs = np.identity(3)\n            t = np.cross(vecs, normal).sum(axis=1)\n            ax = t.argmax()\n            east_vector = np.cross(vecs[ax, :], normal).ravel()\n            north = np.cross(normal, east_vector).ravel()\n        else:\n            north = np.array(north_vector)\n            north = north / np.linalg.norm(north)\n        self.normal_vector = normal\n        self.north_vector = north\n\n        if data_source is None:\n            dd = ds.all_data()\n        else:\n            dd = data_source\n\n        self.orienter = Orientation(normal_vector, north_vector=north_vector)\n\n        super().__init__(\n            center,\n            ds,\n            width,\n            fields,\n            dd,\n            weight_field=weight_field,\n            field_parameters=field_parameters,\n            deposition=deposition,\n            density=density,\n        )\n\n\nclass ParticleProjectionPlot(NormalPlot):\n    r\"\"\"Creates a particle plot from a dataset\n\n    Given a ds object, a normal to project along, and a field name\n    string, this will return a PWViewerMPL object containing\n    the plot.\n\n    The plot can be updated using one of the many helper functions\n    defined in PlotWindow.\n\n    Parameters\n    ----------\n    ds : `Dataset`\n         This is the dataset object corresponding to the\n         simulation output to be plotted.\n    normal : int, str, or 3-element sequence of floats\n        This specifies the normal vector to the projection.\n        Valid int values are 0, 1 and 2. Corresponding str values depend on the\n        geometry of the dataset and are generally given by `ds.coordinates.axis_order`.\n        E.g. in cartesian they are 'x', 'y' and 'z'.\n        An arbitrary normal vector may be specified as a 3-element sequence of floats.\n    fields : string, list or None\n         If a string or list, the name of the particle field(s) to be used\n         on the colorbar. The color shown will correspond to the sum of the\n         given field along the line of sight. If None, the particle positions\n         will be indicated using a fixed color, instead. Default is None.\n    color : 'b', 'g', 'r', 'c', 'm', 'y', 'k', or 'w'\n         One the matplotlib-recognized color strings.\n         The color that will indicate the particle locations\n         on the mesh. This argument is ignored if z_fields is\n         not None. Default is 'b'.\n    center : 'center', 'c', 'left', 'l', 'right', 'r', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n        The domain edges along the selected *axis* can be selected with\n        'left'/'l' and 'right'/'r' respectively.\n    width : tuple or a float.\n         Width can have four different formats to support windows with variable\n         x and y widths.  They are:\n\n         ==================================     =======================\n         format                                 example\n         ==================================     =======================\n         (float, string)                        (10,'kpc')\n         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n         float                                  0.2\n         (float, float)                         (0.2, 0.3)\n         ==================================     =======================\n\n         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs\n         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a\n         window that is 10 kiloparsecs wide along the x-axis and 15\n         kiloparsecs wide along the y-axis. In the other two examples, code\n         units are assumed, for example (0.2, 0.3) requests a plot that has an\n         x width of 0.2 and a y width of 0.3 in code units.  If units are\n         provided the resulting plot axis labels will use the supplied units.\n    depth : A tuple or a float\n         A tuple containing the depth to project through and the string\n         key of the unit: (width, 'unit').  If set to a float, code units\n         are assumed. Defaults to the entire domain.\n    weight_field : string\n         The name of the weighting field.  Set to None for no weight.\n         If given, the plot will show a weighted average along the line of\n         sight of the fields given in the ``fields`` argument.\n    axes_unit : A string\n         The name of the unit for the tick labels on the x and y axes.\n         Defaults to None, which automatically picks an appropriate unit.\n         If axes_unit is '1', 'u', or 'unitary', it will not display the\n         units, and only show the axes name.\n    origin : string or length 1, 2, or 3 sequence of strings\n         The location of the origin of the plot coordinate system.  This is\n         represented by '-' separated string or a tuple of strings.  In the\n         first index the y-location is given by 'lower', 'upper', or 'center'.\n         The second index is the x-location, given as 'left', 'right', or\n         'center'.  Finally, whether the origin is applied in 'domain'\n         space, plot 'window' space or 'native' simulation coordinate system\n         is given. For example, both 'upper-right-domain' and ['upper',\n         'right', 'domain'] both place the origin in the upper right hand\n         corner of domain space. If x or y are not given, a value is inferred.\n         For instance, 'left-domain' corresponds to the lower-left hand corner\n         of the simulation domain, 'center-domain' corresponds to the center\n         of the simulation domain, or 'center-window' for the center of the\n         plot window. Further examples:\n\n         ==================================     ============================\n         format                                 example\n         ==================================     ============================\n         '{space}'                              'domain'\n         '{xloc}-{space}'                       'left-window'\n         '{yloc}-{space}'                       'upper-domain'\n         '{yloc}-{xloc}-{space}'                'lower-right-window'\n         ('{space}',)                           ('window',)\n         ('{xloc}', '{space}')                  ('right', 'domain')\n         ('{yloc}', '{space}')                  ('lower', 'window')\n         ('{yloc}', '{xloc}', '{space}')        ('lower', 'right', 'window')\n         ==================================     ============================\n    fontsize : integer\n         The size of the fonts for the axis, colorbar, and tick labels.\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    window_size : float\n        The size of the window on the longest axis (in units of inches),\n        including the margins but not the colorbar.\n    aspect : float\n         The aspect ratio of the plot.  Set to None for 1.\n    data_source : YTSelectionContainer object\n         The object to be used for data selection.  Defaults to a region covering\n         the entire simulation.\n    deposition : string\n        Controls the order of the interpolation of the particles onto the\n        mesh. \"ngp\" is 0th-order \"nearest-grid-point\" method (the default),\n        \"cic\" is 1st-order \"cloud-in-cell\".\n    density : boolean\n        If True, the quantity to be projected will be divided by the area of\n        the cells, to make a projected density of the quantity. The plot\n        name and units will also reflect this. Default: False\n    north_vector : a sequence of floats\n        A vector defining the 'up' direction in off-axis particle projection plots;\n        not used if the plot is on-axis. This option sets the orientation of the\n        projected plane.  If not set, an arbitrary grid-aligned north-vector is\n        chosen.\n\n    Examples\n    --------\n\n    This will save an image to the file\n    'galaxy0030_Particle_z_particle_mass.png'\n\n    >>> from yt import load\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> p = yt.ParticleProjectionPlot(ds, 2, \"particle_mass\")\n    >>> p.save()\n\n    \"\"\"\n\n    # ignoring type check here, because mypy doesn't allow __new__ methods to\n    # return instances of subclasses. The design we use here is however based\n    # on the pathlib.Path class from the standard library\n    # https://github.com/python/mypy/issues/1020\n    def __new__(  # type: ignore\n        cls, ds, normal=None, *args, axis=None, **kwargs\n    ) -> Union[\"AxisAlignedParticleProjectionPlot\", \"OffAxisParticleProjectionPlot\"]:\n        # TODO: when axis' deprecation expires,\n        # remove default value for normal\n\n        normal = cls._handle_normalaxis_parameters(normal=normal, axis=axis)\n        if cls is ParticleProjectionPlot:\n            normal = cls.sanitize_normal_vector(ds, normal)\n            if isinstance(normal, str):\n                cls = AxisAlignedParticleProjectionPlot\n            else:\n                cls = OffAxisParticleProjectionPlot\n        self = object.__new__(cls)\n        return self  # type: ignore [return-value]\n\n    @staticmethod\n    def _handle_normalaxis_parameters(*, normal, axis) -> None:\n        # TODO: when axis' deprecation expires,\n        # remove this method entirely\n        if axis is not None:\n            issue_deprecation_warning(\n                \"Argument 'axis' is a deprecated alias for 'normal'.\",\n                since=\"4.2\",\n                stacklevel=4,\n            )\n            if normal is not None:\n                raise TypeError(\"Received incompatible arguments 'axis' and 'normal'\")\n            normal = axis\n\n        if normal is None:\n            raise TypeError(\"missing required positional argument: 'normal'\")\n\n        return normal\n\n\nclass AxisAlignedParticleProjectionPlot(ParticleProjectionPlot, PWViewerMPL):\n    _plot_type = \"Particle\"\n    _frb_generator = ParticleImageBuffer\n\n    def __init__(\n        self,\n        ds,\n        normal=None,\n        fields=None,\n        color=\"b\",\n        center=\"center\",\n        width=None,\n        depth=(1, \"1\"),\n        weight_field=None,\n        axes_unit=None,\n        origin=\"center-window\",\n        fontsize=18,\n        field_parameters=None,\n        window_size=8.0,\n        aspect=None,\n        data_source=None,\n        deposition=\"ngp\",\n        density=False,\n        *,\n        north_vector=None,\n        axis=None,\n    ):\n        if north_vector is not None:\n            # this kwarg exists only for symmetry reasons with OffAxisSlicePlot\n            mylog.warning(\n                \"Ignoring 'north_vector' keyword as it is ill-defined for \"\n                \"an AxisAlignedParticleProjectionPlot object.\"\n            )\n            del north_vector\n\n        normal = self._handle_normalaxis_parameters(normal=normal, axis=axis)\n\n        # this will handle time series data and controllers\n        ts = self._initialize_dataset(ds)\n        self.ts = ts\n        ds = self.ds = ts[0]\n        normal = self.sanitize_normal_vector(ds, normal)\n        if field_parameters is None:\n            field_parameters = {}\n\n        self.set_axes_unit(axes_unit or get_axes_unit(width, ds))\n\n        # if no fields are passed in, we simply mark the x and\n        # y fields using a given color. Use the 'particle_ones'\n        # field to do this. We also turn off the colorbar in\n        # this case.\n        use_cbar = True\n        splat_color = None\n        if fields is None:\n            fields = [(\"all\", \"particle_ones\")]\n            weight_field = (\"all\", \"particle_ones\")\n            use_cbar = False\n            splat_color = color\n\n        axis = fix_axis(normal, ds)\n        (bounds, center, display_center) = get_window_parameters(\n            axis, center, width, ds\n        )\n        x_coord = ds.coordinates.x_axis[axis]\n        y_coord = ds.coordinates.y_axis[axis]\n\n        depth = ds.coordinates.sanitize_depth(depth)\n\n        width = np.zeros_like(center)\n        width[x_coord] = bounds[1] - bounds[0]\n        width[y_coord] = bounds[3] - bounds[2]\n        width[axis] = depth[0].in_units(width[x_coord].units)\n\n        self.projected = weight_field is None\n\n        ParticleSource = ParticleAxisAlignedDummyDataSource(\n            center,\n            ds,\n            axis,\n            width,\n            fields,\n            weight_field=weight_field,\n            field_parameters=field_parameters,\n            data_source=data_source,\n            deposition=deposition,\n            density=density,\n        )\n\n        PWViewerMPL.__init__(\n            self,\n            ParticleSource,\n            bounds,\n            origin=origin,\n            fontsize=fontsize,\n            fields=fields,\n            window_size=window_size,\n            aspect=aspect,\n            splat_color=splat_color,\n            geometry=ds.geometry,\n            periodic=True,\n            oblique=False,\n        )\n\n        if not use_cbar:\n            self.hide_colorbar()\n\n\nclass OffAxisParticleProjectionPlot(ParticleProjectionPlot, PWViewerMPL):\n    _plot_type = \"Particle\"\n    _frb_generator = ParticleImageBuffer\n\n    def __init__(\n        self,\n        ds,\n        normal=None,\n        fields=None,\n        color=\"b\",\n        center=\"center\",\n        width=None,\n        depth=(1, \"1\"),\n        weight_field=None,\n        axes_unit=None,\n        origin=\"center-window\",\n        fontsize=18,\n        field_parameters=None,\n        window_size=8.0,\n        aspect=None,\n        data_source=None,\n        deposition=\"ngp\",\n        density=False,\n        *,\n        north_vector=None,\n        axis=None,\n    ):\n        if data_source is not None:\n            warnings.warn(\n                \"The 'data_source' argument has no effect for \"\n                \"off-axis particle projections (not implemented)\",\n                stacklevel=2,\n            )\n            del data_source\n        if origin != \"center-window\":\n            warnings.warn(\n                \"The 'origin' argument is ignored for off-axis \"\n                \"particle projections, it is always 'center-window'\",\n                stacklevel=2,\n            )\n            del origin\n\n        normal = self._handle_normalaxis_parameters(normal=normal, axis=axis)\n\n        # this will handle time series data and controllers\n        ts = self._initialize_dataset(ds)\n        self.ts = ts\n        ds = self.ds = ts[0]\n        normal = self.sanitize_normal_vector(ds, normal)\n        if field_parameters is None:\n            field_parameters = {}\n\n        self.set_axes_unit(axes_unit or get_axes_unit(width, ds))\n\n        # if no fields are passed in, we simply mark the x and\n        # y fields using a given color. Use the 'particle_ones'\n        # field to do this. We also turn off the colorbar in\n        # this case.\n        use_cbar = True\n        splat_color = None\n        if fields is None:\n            fields = [(\"all\", \"particle_ones\")]\n            weight_field = (\"all\", \"particle_ones\")\n            use_cbar = False\n            splat_color = color\n\n        (bounds, center_rot) = get_oblique_window_parameters(\n            normal, center, width, ds, depth=depth\n        )\n\n        width = ds.coordinates.sanitize_width(normal, width, depth)\n\n        self.projected = weight_field is None\n\n        ParticleSource = ParticleOffAxisDummyDataSource(\n            center_rot,\n            ds,\n            normal,\n            width,\n            fields,\n            weight_field=weight_field,\n            field_parameters=field_parameters,\n            data_source=None,\n            deposition=deposition,\n            density=density,\n            north_vector=north_vector,\n        )\n\n        PWViewerMPL.__init__(\n            self,\n            ParticleSource,\n            bounds,\n            origin=\"center-window\",\n            fontsize=fontsize,\n            fields=fields,\n            window_size=window_size,\n            aspect=aspect,\n            splat_color=splat_color,\n            geometry=ds.geometry,\n            periodic=False,\n            oblique=True,\n        )\n\n        if not use_cbar:\n            self.hide_colorbar()\n\n\nclass ParticlePhasePlot(PhasePlot):\n    r\"\"\"\n    Create a 2d particle phase plot from a data source or from\n    a `yt.data_objects.profiles.ParticleProfile` object.\n\n    Given a data object (all_data, region, sphere, etc.), an x field,\n    y field, and z field (or fields), this will create a particle plot\n    by depositing the particles onto a two-dimensional mesh, using either\n    nearest grid point or cloud-in-cell deposition.\n\n    Parameters\n    ----------\n    data_source : YTSelectionContainer or Dataset\n        The data object to be profiled, such as all_data, region, or\n        sphere. If data_source is a Dataset, data_source.all_data() will be used.\n    x_field : str\n        The x field for the mesh.\n    y_field : str\n        The y field for the mesh.\n    z_fields : None, str, or list\n        If None, particles will be splatted onto the mesh,\n        but no colormap will be used.\n        If str or list, the name of the field or fields to\n        be displayed on the colorbar. The displayed values will\n        correspond to the sum of the field or fields along the\n        line of sight.\n        Default: None.\n    color : 'b', 'g', 'r', 'c', 'm', 'y', 'k', or 'w'\n        One the matplotlib-recognized color strings.\n        The color that will indicate the particle locations\n        on the mesh. This argument is ignored if z_fields is\n        not None.\n        Default : 'b'\n    x_bins : int\n        The number of bins in x field for the mesh.\n        Default: 800.\n    y_bins : int\n        The number of bins in y field for the mesh.\n        Default: 800.\n    weight_field : str\n        The field to weight by. If given, the plot will show a weighted\n        average along the line of sight of the fields given in the\n        ``z_fields`` argument. Default: None.\n    deposition : str\n        Either 'ngp' or 'cic'. Controls what type of\n        interpolation will be used to deposit the\n        particle z_fields onto the mesh.\n        Default: 'ngp'\n    fontsize: int\n        Font size for all text in the plot.\n        Default: 18.\n    figure_size : int\n        Size in inches of the image.\n        Default: 8 (8x8)\n    shading : str\n        This argument is directly passed down to matplotlib.axes.Axes.pcolormesh\n        see\n        https://matplotlib.org/3.3.1/gallery/images_contours_and_fields/pcolormesh_grids.html#sphx-glr-gallery-images-contours-and-fields-pcolormesh-grids-py  # noqa\n        Default: 'nearest'\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> ad = ds.all_data()\n    >>> plot = ParticlePhasePlot(\n    ...     ad,\n    ...     \"particle_position_x\",\n    ...     \"particle_position_y\",\n    ...     [\"particle_mass\"],\n    ...     x_bins=800,\n    ...     y_bins=800,\n    ... )\n    >>> plot.save()\n\n    >>> # Change plot properties.\n    >>> plot.set_log(\"particle_mass\", True)\n    >>> plot.set_unit(\"particle_position_x\", \"Mpc\")\n    >>> plot.set_unit(\"particle_velocity_z\", \"km/s\")\n    >>> plot.set_unit(\"particle_mass\", \"Msun\")\n\n    \"\"\"\n\n    _plot_type = \"ParticlePhase\"\n\n    def __init__(\n        self,\n        data_source,\n        x_field,\n        y_field,\n        z_fields=None,\n        color=\"b\",\n        x_bins=800,\n        y_bins=800,\n        weight_field=None,\n        deposition=\"ngp\",\n        fontsize=18,\n        figure_size=8.0,\n        shading=\"nearest\",\n    ):\n        if isinstance(data_source, Dataset):\n            data_source = data_source.all_data()\n        # if no z_fields are passed in, use a constant color\n        if z_fields is None:\n            self.use_cbar = False\n            self.splat_color = color\n            z_fields = [(\"all\", \"particle_ones\")]\n\n        profile = create_profile(\n            data_source,\n            [x_field, y_field],\n            list(iter_fields(z_fields)),\n            n_bins=[x_bins, y_bins],\n            weight_field=weight_field,\n            deposition=deposition,\n        )\n\n        type(self)._initialize_instance(\n            self, data_source, profile, fontsize, figure_size, shading\n        )\n\n\ndef ParticlePlot(ds, x_field, y_field, z_fields=None, color=\"b\", *args, **kwargs):\n    r\"\"\"\n    A factory function for\n    :class:`yt.visualization.particle_plots.ParticleProjectionPlot`\n    and :class:`yt.visualization.profile_plotter.ParticlePhasePlot` objects.\n    This essentially allows for a single entry point to both types of particle\n    plots, the distinction being determined by the fields passed in.\n\n    If the x_field and y_field combination corresponds to a valid, right-handed\n    spatial plot, an ``ParticleProjectionPlot`` will be returned. This plot\n    object can be updated using one of the many helper functions defined in\n    ``PlotWindow``.\n\n    If the x_field and y_field combo do not correspond to a valid\n    ``ParticleProjectionPlot``, then a ``ParticlePhasePlot``. This object can be\n    modified by its own set of  helper functions defined in PhasePlot. We note\n    below which arguments are only accepted by ``ParticleProjectionPlot`` and\n    which arguments are only accepted by ``ParticlePhasePlot``.\n\n    Parameters\n    ----------\n\n    ds : :class:`yt.data_objects.static_output.Dataset`\n        This is the dataset object corresponding to the\n        simulation output to be plotted.\n    x_field : string\n        This is the particle field that will be plotted on the x-axis.\n    y_field : string\n        This is the particle field that will be plotted on the y-axis.\n    z_fields : string, list, or None.\n        If None, particles will be splatted onto the plot, but no colormap\n        will be used. The particle color will instead be determined by\n        the 'color' argument. If str or list, the name of the field or fields\n        to be displayed on the colorbar.\n        Default: None.\n    color : 'b', 'g', 'r', 'c', 'm', 'y', 'k', or 'w'\n         One the matplotlib-recognized color strings.\n         The color that will indicate the particle locations\n         on the plot. This argument is ignored if z_fields is\n         not None. Default is 'b'.\n    weight_field : string\n         The name of the weighting field.  Set to None for no weight.\n    fontsize : integer\n         The size of the fonts for the axis, colorbar, and tick labels.\n    data_source : YTSelectionContainer Object\n         Object to be used for data selection.  Defaults to a region covering\n         the entire simulation.\n    center : 'center', 'c', 'left', 'l', 'right', 'r', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n        The domain edges along the selected *axis* can be selected with\n        'left'/'l' and 'right'/'r' respectively.\n\n        This argument is only accepted by ``ParticleProjectionPlot``.\n    width : tuple or a float.\n         Width can have four different formats to support windows with variable\n         x and y widths.  They are:\n\n         ==================================     =======================\n         format                                 example\n         ==================================     =======================\n         (float, string)                        (10,'kpc')\n         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n         float                                  0.2\n         (float, float)                         (0.2, 0.3)\n         ==================================     =======================\n\n         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs\n         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a\n         window that is 10 kiloparsecs wide along the x axis and 15\n         kiloparsecs wide along the y axis.  In the other two examples, code\n         units are assumed, for example (0.2, 0.3) requests a plot that has an\n         x width of 0.2 and a y width of 0.3 in code units.  If units are\n         provided the resulting plot axis labels will use the supplied units.\n         This argument is only accepted by ``ParticleProjectionPlot``.\n    depth : A tuple or a float\n         A tuple containing the depth to project through and the string\n         key of the unit: (width, 'unit').  If set to a float, code units\n         are assumed. Defaults to the entire domain. This argument is only\n         accepted by ``ParticleProjectionPlot``.\n    axes_unit : A string\n         The name of the unit for the tick labels on the x and y axes.\n         Defaults to None, which automatically picks an appropriate unit.\n         If axes_unit is '1', 'u', or 'unitary', it will not display the\n         units, and only show the axes name.\n    origin : string or length 1, 2, or 3 sequence of strings\n         The location of the origin of the plot coordinate system.  This is\n         represented by '-' separated string or a tuple of strings.  In the\n         first index the y-location is given by 'lower', 'upper', or 'center'.\n         The second index is the x-location, given as 'left', 'right', or\n         'center'.  Finally, the whether the origin is applied in 'domain'\n         space, plot 'window' space or 'native' simulation coordinate system\n         is given. For example, both 'upper-right-domain' and ['upper',\n         'right', 'domain'] both place the origin in the upper right hand\n         corner of domain space. If x or y are not given, a value is inferred.\n         For instance, 'left-domain' corresponds to the lower-left hand corner\n         of the simulation domain, 'center-domain' corresponds to the center\n         of the simulation domain, or 'center-window' for the center of the\n         plot window. Further examples:\n\n         ==================================     ============================\n         format                                 example\n         ==================================     ============================\n         '{space}'                              'domain'\n         '{xloc}-{space}'                       'left-window'\n         '{yloc}-{space}'                       'upper-domain'\n         '{yloc}-{xloc}-{space}'                'lower-right-window'\n         ('{space}',)                           ('window',)\n         ('{xloc}', '{space}')                  ('right', 'domain')\n         ('{yloc}', '{space}')                  ('lower', 'window')\n         ('{yloc}', '{xloc}', '{space}')        ('lower', 'right', 'window')\n         ==================================     ============================\n\n         This argument is only accepted by ``ParticleProjectionPlot``.\n    window_size : float\n         The size of the window on the longest axis (in units of inches),\n         including the margins but not the colorbar. This argument is only\n         accepted by ``ParticleProjectionPlot``.\n    aspect : float\n         The aspect ratio of the plot.  Set to None for 1. This argument is\n         only accepted by ``ParticleProjectionPlot``.\n    x_bins : int\n        The number of bins in x field for the mesh. Defaults to 800. This\n        argument is only accepted by ``ParticlePhasePlot``.\n    y_bins : int\n        The number of bins in y field for the mesh. Defaults to 800. This\n        argument is only accepted by ``ParticlePhasePlot``.\n    deposition : str\n        Either 'ngp' or 'cic'. Controls what type of interpolation will be\n        used to deposit the particle z_fields onto the mesh. Defaults to 'ngp'.\n    figure_size : int\n        Size in inches of the image. Defaults to 8 (product an 8x8 inch figure).\n        This argument is only accepted by ``ParticlePhasePlot``.\n\n    Examples\n    --------\n\n    >>> from yt import load\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> p = yt.ParticlePlot(\n    ...     ds,\n    ...     \"particle_position_x\",\n    ...     \"particle_position_y\",\n    ...     \"particle_mass\",\n    ...     width=(0.5, 0.5),\n    ... )\n    >>> p.set_unit(\"particle_mass\", \"Msun\")\n    >>> p = yt.ParticlePlot(ds, \"particle_position_x\", \"particle_velocity_z\", color=\"g\")\n\n    \"\"\"\n    dd = kwargs.get(\"data_source\", None)\n    if dd is None:\n        dd = ds.all_data()\n    x_field = dd._determine_fields(x_field)[0]\n    y_field = dd._determine_fields(y_field)[0]\n\n    direction = 3\n    # try potential axes for a ParticleProjectionPlot:\n    for axis in [0, 1, 2]:\n        xax = ds.coordinates.x_axis[axis]\n        yax = ds.coordinates.y_axis[axis]\n        ax_field_template = \"particle_position_%s\"\n        xf = ax_field_template % ds.coordinates.axis_name[xax]\n        yf = ax_field_template % ds.coordinates.axis_name[yax]\n        if (x_field[1], y_field[1]) in [(xf, yf), (yf, xf)]:\n            direction = axis\n            break\n\n    if direction < 3:\n        # Make a ParticleProjectionPlot\n        return ParticleProjectionPlot(ds, direction, z_fields, color, *args, **kwargs)\n\n    # Does not correspond to any valid PlotWindow-style plot,\n    # use ParticlePhasePlot instead\n    else:\n        return ParticlePhasePlot(dd, x_field, y_field, z_fields, color, *args, **kwargs)\n"
  },
  {
    "path": "yt/visualization/plot_container.py",
    "content": "import abc\nimport base64\nimport os\nimport warnings\nfrom collections import defaultdict\nfrom functools import wraps\nfrom typing import Any, Final, Literal\n\nimport matplotlib\nfrom matplotlib.colors import LogNorm, Normalize, SymLogNorm\nfrom unyt.dimensions import length\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._maintenance.ipython_compat import IS_IPYTHON\nfrom yt._typing import FieldKey, Quantity\nfrom yt.config import ytcfg\nfrom yt.data_objects.time_series import DatasetSeries\nfrom yt.funcs import ensure_dir, is_sequence, iter_fields\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.utilities.definitions import formatted_length_unit_names\nfrom yt.utilities.exceptions import YTConfigurationError, YTNotInsideNotebook\nfrom yt.visualization._commons import get_default_from_config\nfrom yt.visualization._handlers import ColorbarHandler, NormHandler\nfrom yt.visualization.base_plot_types import PlotMPL\n\nfrom ._commons import (\n    _get_units_label,\n    get_default_font_properties,\n    invalidate_data,\n    invalidate_figure,\n    invalidate_plot,\n    validate_image_name,\n    validate_plot,\n)\n\nlatex_prefixes = {\n    \"u\": r\"\\mu\",\n}\n\n\ndef apply_callback(f):\n    issue_deprecation_warning(\n        \"The apply_callback decorator is not used in yt any more and \"\n        \"will be removed in a future version. \"\n        \"Please do not use it.\",\n        stacklevel=3,\n        since=\"4.1\",\n    )\n\n    @wraps(f)\n    def newfunc(*args, **kwargs):\n        args[0]._callbacks.append((f.__name__, (args, kwargs)))\n        return args[0]\n\n    return newfunc\n\n\ndef accepts_all_fields(func):\n    \"\"\"\n    Decorate a function whose second argument is <field> and deal with the special case\n    field == 'all', looping over all fields already present in the PlotContainer object.\n\n    \"\"\"\n\n    # This is to be applied to PlotContainer class methods with the following signature:\n    #\n    # f(self, field, *args, **kwargs) -> self\n    @wraps(func)\n    def newfunc(self, field, *args, **kwargs):\n        if field == \"all\":\n            field = self.plots.keys()\n        for f in self.data_source._determine_fields(field):\n            func(self, f, *args, **kwargs)\n        return self\n\n    return newfunc\n\n\n# define a singleton sentinel to be used as default value distinct from None\nclass Unset:\n    _instance = None\n\n    def __new__(cls):\n        if cls._instance is None:\n            cls._instance = object.__new__(cls)\n        return cls._instance\n\n\nUNSET: Final = Unset()\n\n\nclass PlotDictionary(defaultdict):\n    def __getitem__(self, item):\n        return defaultdict.__getitem__(\n            self, self.data_source._determine_fields(item)[0]\n        )\n\n    def __setitem__(self, item, value):\n        return defaultdict.__setitem__(\n            self, self.data_source._determine_fields(item)[0], value\n        )\n\n    def __contains__(self, item):\n        return defaultdict.__contains__(\n            self, self.data_source._determine_fields(item)[0]\n        )\n\n    def __init__(self, data_source, default_factory=None):\n        self.data_source = data_source\n        return defaultdict.__init__(self, default_factory)\n\n\nclass PlotContainer(abc.ABC):\n    \"\"\"A container for generic plots\"\"\"\n\n    _plot_dict_type: type[PlotDictionary] = PlotDictionary\n    _plot_type: str | None = None\n    _plot_valid = False\n\n    _default_figure_size = tuple(matplotlib.rcParams[\"figure.figsize\"])\n    _default_font_size = 14.0\n\n    def __init__(self, data_source, figure_size=None, fontsize: float | None = None):\n        from matplotlib.font_manager import FontProperties\n\n        self.data_source = data_source\n        self.ds = data_source.ds\n        self.ts = self._initialize_dataset(self.ds)\n        self.plots = self.__class__._plot_dict_type(data_source)\n\n        self._set_figure_size(figure_size)\n\n        if fontsize is None:\n            fontsize = self.__class__._default_font_size\n        font_dict = get_default_font_properties() | {\"size\": fontsize}\n\n        self._font_properties = FontProperties(**font_dict)\n        self._font_color = None\n        self._xlabel = None\n        self._ylabel = None\n        self._minorticks: dict[FieldKey, bool] = {}\n\n    @accepts_all_fields\n    @invalidate_plot\n    def set_log(\n        self,\n        field,\n        log: bool | None = None,\n        *,\n        linthresh: float | Quantity | Literal[\"auto\"] | None = None,\n        symlog_auto: bool | None = None,  # deprecated\n    ):\n        \"\"\"set a field to log, linear, or symlog.\n\n        Symlog scaling is a combination of linear and log, where from 0 to a\n        threshold value, it operates as linear, and then beyond that it operates as\n        log.  Symlog can also work with negative values in log space as well as\n        negative and positive values simultaneously and symmetrically.  If symlog\n        scaling is desired, please set log=True and either set symlog_auto=True or\n        select a value for linthresh.\n\n        Parameters\n        ----------\n        field : string\n            the field to set a transform\n            if field == 'all', applies to all plots.\n        log : boolean, optional\n            set log to True for log scaling, False for linear scaling.\n        linthresh : float, (float, str), unyt_quantity, or 'auto', optional\n            when using symlog scaling, linthresh is the value at which scaling\n            transitions from linear to logarithmic.  linthresh must be positive.\n            Note: setting linthresh will automatically enable symlog scale\n\n        Note that *log* and *linthresh* are mutually exclusive arguments\n        \"\"\"\n        if log is None and linthresh is None and symlog_auto is None:\n            raise TypeError(\"set_log requires log or linthresh be set\")\n\n        if symlog_auto is not None:\n            issue_deprecation_warning(\n                \"the symlog_auto argument is deprecated. Use linthresh='auto' instead\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            if symlog_auto is True:\n                linthresh = \"auto\"\n            elif symlog_auto is False:\n                pass\n            else:\n                raise TypeError(\n                    \"Received invalid value for parameter symlog_auto. \"\n                    f\"Expected a boolean, got {symlog_auto!r}\"\n                )\n\n        if log is not None and linthresh is not None:\n            # we do not raise an error here for backward compatibility\n            warnings.warn(\n                f\"log={log} has no effect because linthresh specified. Using symlog.\",\n                stacklevel=4,\n            )\n\n        pnh = self.plots[field].norm_handler\n\n        if linthresh is not None:\n            if isinstance(linthresh, str):\n                if linthresh == \"auto\":\n                    pnh.norm_type = SymLogNorm\n                else:\n                    raise ValueError(\n                        \"Expected a number, a unyt_quantity, a (float, 'unit') tuple, or 'auto'. \"\n                        f\"Got linthresh={linthresh!r}\"\n                    )\n            else:\n                # pnh takes care of switching to symlog when linthresh is set\n                pnh.linthresh = linthresh\n        elif log is True:\n            pnh.norm_type = LogNorm\n        elif log is False:\n            pnh.norm_type = Normalize\n        else:\n            raise TypeError(\n                f\"Could not parse arguments log={log!r}, linthresh={linthresh!r}\"\n            )\n\n        return self\n\n    def get_log(self, field):\n        \"\"\"get the transform type of a field.\n\n        Parameters\n        ----------\n        field : string\n            the field to get a transform\n            if field == 'all', applies to all plots.\n\n        \"\"\"\n        # devnote : accepts_all_fields decorator is not applicable here because\n        # the return variable isn't self\n        issue_deprecation_warning(\n            \"The get_log method is not reliable and is deprecated. \"\n            \"Please do not rely on it.\",\n            stacklevel=3,\n            since=\"4.1\",\n        )\n        log = {}\n        if field == \"all\":\n            fields = list(self.plots.keys())\n        else:\n            fields = field\n        for field in self.data_source._determine_fields(fields):\n            pnh = self.plots[field].norm_handler\n            if pnh.norm is not None:\n                log[field] = type(pnh.norm) is LogNorm\n            elif pnh.norm_type is not None:\n                log[field] = pnh.norm_type is LogNorm\n            else:\n                # the NormHandler object has no constraints yet\n                # so we'll assume defaults\n                log[field] = True\n        return log\n\n    @invalidate_plot\n    def set_transform(self, field, name: str):\n        field = self.data_source._determine_fields(field)[0]\n        pnh = self.plots[field].norm_handler\n        pnh.norm_type = {\n            \"linear\": Normalize,\n            \"log10\": LogNorm,\n            \"symlog\": SymLogNorm,\n        }[name]\n        return self\n\n    @accepts_all_fields\n    @invalidate_plot\n    def set_norm(self, field, norm: Normalize):\n        r\"\"\"\n        Set a custom ``matplotlib.colors.Normalize`` to plot *field*.\n\n        Any constraints previously set with `set_log`, `set_zlim` will be\n        dropped.\n\n        Note that any float value attached to *norm* (e.g. vmin, vmax,\n        vcenter ...) will be read in the current displayed units, which can be\n        controlled with the `set_unit` method.\n\n        Parameters\n        ----------\n        field : str or tuple[str, str]\n            if field == 'all', applies to all plots.\n        norm : matplotlib.colors.Normalize\n            see https://matplotlib.org/stable/tutorials/colors/colormapnorms.html\n        \"\"\"\n        pnh = self.plots[field].norm_handler\n        pnh.norm = norm\n        return self\n\n    @accepts_all_fields\n    @invalidate_plot\n    def set_minorticks(self, field, state):\n        \"\"\"Turn minor ticks on or off in the current plot.\n\n        Displaying minor ticks reduces performance; turn them off\n        using set_minorticks('all', False) if drawing speed is a problem.\n\n        Parameters\n        ----------\n        field : string\n            the field to remove minorticks\n            if field == 'all', applies to all plots.\n        state : bool\n            the state indicating 'on' (True) or 'off' (False)\n\n        \"\"\"\n        self._minorticks[field] = state\n        return self\n\n    @abc.abstractmethod\n    def _setup_plots(self):\n        # Left blank to be overridden in subclasses\n        pass\n\n    def render(self) -> None:\n        r\"\"\"Render plots.\n        This operation is expensive and usually doesn't need to be requested explicitly.\n        In most cases, yt handles rendering automatically and delays it as much as possible\n        to avoid redundant calls on each plot modification (e.g. via `annotate_*` methods).\n\n        However, valid use cases of this method include:\n        - fine control of render (and clear) operations when yt plots are combined with plot\n          customizations other than plot callbacks (`annotate_*`)\n        - testing\n        \"\"\"\n        # this public API method should never be no-op, so we invalidate\n        # the plot to force a fresh render in _setup_plots()\n        self._plot_valid = False\n        self._setup_plots()\n\n    def _initialize_dataset(self, ts):\n        if not isinstance(ts, DatasetSeries):\n            if not is_sequence(ts):\n                ts = [ts]\n            ts = DatasetSeries(ts)\n        return ts\n\n    @invalidate_data\n    def _switch_ds(self, new_ds, data_source=None):\n        old_object = self.data_source\n        name = old_object._type_name\n        kwargs = {n: getattr(old_object, n) for n in old_object._con_args}\n        kwargs[\"center\"] = getattr(old_object, \"center\", None)\n        if data_source is not None:\n            if name != \"proj\":\n                raise RuntimeError(\n                    \"The data_source keyword argument is only defined for projections.\"\n                )\n            kwargs[\"data_source\"] = data_source\n\n        self.ds = new_ds\n\n        # A _hack_ for ParticleProjectionPlots\n        if name == \"Particle\":\n            from yt.visualization.particle_plots import (\n                ParticleAxisAlignedDummyDataSource,\n            )\n\n            new_object = ParticleAxisAlignedDummyDataSource(ds=self.ds, **kwargs)\n        else:\n            new_object = getattr(new_ds, name)(**kwargs)\n\n        self.data_source = new_object\n\n        for d in \"xyz\":\n            lim_name = d + \"lim\"\n            if hasattr(self, lim_name):\n                lim = getattr(self, lim_name)\n                lim = tuple(new_ds.quan(l.value, str(l.units)) for l in lim)\n                setattr(self, lim_name, lim)\n        self.plots.data_source = new_object\n        self._colorbar_label.data_source = new_object\n        self._setup_plots()\n\n    @validate_plot\n    def __getitem__(self, item):\n        return self.plots[item]\n\n    def _set_font_properties(self):\n        for f in self.plots:\n            self.plots[f]._set_font_properties(self._font_properties, self._font_color)\n\n    @invalidate_plot\n    @invalidate_figure\n    def set_font(self, font_dict=None):\n        \"\"\"\n\n        Set the font and font properties.\n\n        Parameters\n        ----------\n\n        font_dict : dict\n            A dict of keyword parameters to be passed to\n            :class:`matplotlib.font_manager.FontProperties`.\n\n            Possible keys include:\n\n            * family - The font family. Can be serif, sans-serif, cursive,\n              'fantasy' or 'monospace'.\n            * style - The font style. Either normal, italic or oblique.\n            * color - A valid color string like 'r', 'g', 'red', 'cobalt',\n              and 'orange'.\n            * variant - Either normal or small-caps.\n            * size - Either a relative value of xx-small, x-small, small,\n              medium, large, x-large, xx-large or an absolute font size, e.g. 12\n            * stretch - A numeric value in the range 0-1000 or one of\n              ultra-condensed, extra-condensed, condensed, semi-condensed,\n              normal, semi-expanded, expanded, extra-expanded or ultra-expanded\n            * weight - A numeric value in the range 0-1000 or one of ultralight,\n              light, normal, regular, book, medium, roman, semibold, demibold,\n              demi, bold, heavy, extra bold, or black\n\n            See the matplotlib font manager API documentation for more details.\n            https://matplotlib.org/stable/api/font_manager_api.html\n\n        Notes\n        -----\n\n        Mathtext axis labels will only obey the `size` and `color` keyword.\n\n        Examples\n        --------\n\n        This sets the font to be 24-pt, blue, sans-serif, italic, and\n        bold-face.\n\n        >>> slc = SlicePlot(ds, \"x\", \"Density\")\n        >>> slc.set_font(\n        ...     {\n        ...         \"family\": \"sans-serif\",\n        ...         \"style\": \"italic\",\n        ...         \"weight\": \"bold\",\n        ...         \"size\": 24,\n        ...         \"color\": \"blue\",\n        ...     }\n        ... )\n\n        \"\"\"\n        from matplotlib.font_manager import FontProperties\n\n        if font_dict is None:\n            font_dict = {}\n        if \"color\" in font_dict:\n            self._font_color = font_dict.pop(\"color\")\n        # Set default values if the user does not explicitly set them.\n        # this prevents reverting to the matplotlib defaults.\n        _default_size = {\"size\": self.__class__._default_font_size}\n        font_dict = get_default_font_properties() | _default_size | font_dict\n        self._font_properties = FontProperties(**font_dict)\n        return self\n\n    def set_font_size(self, size):\n        \"\"\"Set the size of the font used in the plot\n\n        This sets the font size by calling the set_font function.  See set_font\n        for more font customization options.\n\n        Parameters\n        ----------\n        size : float\n        The absolute size of the font in points (1 pt = 1/72 inch).\n\n        \"\"\"\n        return self.set_font({\"size\": size})\n\n    def _set_figure_size(self, size):\n        if size is None:\n            self.figure_size = self.__class__._default_figure_size\n        elif is_sequence(size):\n            if len(size) != 2:\n                raise TypeError(f\"Expected a single float or a pair, got {size}\")\n            self.figure_size = float(size[0]), float(size[1])\n        else:\n            self.figure_size = float(size)\n\n    @invalidate_plot\n    @invalidate_figure\n    def set_figure_size(self, size):\n        \"\"\"Sets a new figure size for the plot\n\n        parameters\n        ----------\n        size : float, a sequence of two floats, or None\n            The size of the figure (in units of inches),  including the margins\n            but not the colorbar. If a single float is passed, it's interpreted\n            as the size along the long axis.\n            Pass None to reset\n        \"\"\"\n        self._set_figure_size(size)\n        return self\n\n    @validate_plot\n    def save(\n        self,\n        name: str | list[str] | tuple[str, ...] | None = None,\n        suffix: str | None = None,\n        mpl_kwargs: dict[str, Any] | None = None,\n    ):\n        \"\"\"saves the plot to disk.\n\n        Parameters\n        ----------\n        name : string or tuple, optional\n           The base of the filename. If name is a directory or if name is not\n           set, the filename of the dataset is used. For a tuple, the\n           resulting path will be given by joining the elements of the\n           tuple\n        suffix : string, optional\n           Specify the image type by its suffix. If not specified, the output\n           type will be inferred from the filename. Defaults to '.png'.\n        mpl_kwargs : dict, optional\n           A dict of keyword arguments to be passed to matplotlib.\n\n        >>> slc.save(mpl_kwargs={\"bbox_inches\": \"tight\"})\n\n        \"\"\"\n        names = []\n        if mpl_kwargs is None:\n            mpl_kwargs = {}\n        elif \"format\" in mpl_kwargs:\n            new_suffix = mpl_kwargs.pop(\"format\")\n            if new_suffix != suffix:\n                warnings.warn(\n                    f\"Overriding suffix {suffix!r} with mpl_kwargs['format'] = {new_suffix!r}. \"\n                    \"Use the `suffix` argument directly to suppress this warning.\",\n                    stacklevel=2,\n                )\n            suffix = new_suffix\n\n        if name is None:\n            name = str(self.ds)\n        elif isinstance(name, (list, tuple)):\n            if not all(isinstance(_, str) for _ in name):\n                raise TypeError(\n                    f\"Expected a single str or an iterable of str, got {name!r}\"\n                )\n            name = os.path.join(*name)\n\n        name = os.path.expanduser(name)\n\n        parent_dir, _, prefix1 = name.replace(os.sep, \"/\").rpartition(\"/\")\n        parent_dir = parent_dir.replace(\"/\", os.sep)\n\n        if parent_dir and not os.path.isdir(parent_dir):\n            ensure_dir(parent_dir)\n\n        if name.endswith((\"/\", os.path.sep)):\n            name = os.path.join(name, str(self.ds))\n\n        new_name = validate_image_name(name, suffix)\n        if new_name == name:\n            for v in self.plots.values():\n                out_name = v.save(name, mpl_kwargs)\n                names.append(out_name)\n            return names\n\n        name = new_name\n        prefix, suffix = os.path.splitext(name)\n\n        if hasattr(self.data_source, \"axis\"):\n            axis = self.ds.coordinates.axis_name.get(self.data_source.axis, \"\")\n        else:\n            axis = None\n        weight = None\n        stddev = None\n        plot_type = self._plot_type\n        if plot_type in [\"Projection\", \"OffAxisProjection\"]:\n            weight = self.data_source.weight_field\n            if weight is not None:\n                weight = weight[1].replace(\" \", \"_\")\n            if getattr(self.data_source, \"moment\", 1) == 2:\n                stddev = \"standard_deviation\"\n        if \"Cutting\" in self.data_source.__class__.__name__:\n            plot_type = \"OffAxisSlice\"\n\n        for k, v in self.plots.items():\n            if isinstance(k, tuple):\n                k = k[1]\n\n            if plot_type is None:\n                # implemented this check to make mypy happy, because we can't use str.join\n                # with PlotContainer._plot_type = None\n                raise TypeError(f\"{self.__class__} is missing a _plot_type value (str)\")\n\n            name_elements = [prefix, plot_type]\n            if axis:\n                name_elements.append(axis)\n            name_elements.append(k.replace(\" \", \"_\"))\n            if weight:\n                name_elements.append(weight)\n            if stddev:\n                name_elements.append(stddev)\n            name = \"_\".join(name_elements) + suffix\n            names.append(v.save(name, mpl_kwargs))\n        return names\n\n    @invalidate_data\n    def refresh(self):\n        # invalidate_data will take care of everything\n        return self\n\n    @validate_plot\n    def show(self):\n        r\"\"\"This will send any existing plots to the IPython notebook.\n\n        If yt is being run from within an IPython session, and it is able to\n        determine this, this function will send any existing plots to the\n        notebook for display.\n\n        If yt can't determine if it's inside an IPython session, it will raise\n        YTNotInsideNotebook.\n\n        Examples\n        --------\n\n        >>> from yt import SlicePlot\n        >>> slc = SlicePlot(\n        ...     ds, \"x\", [(\"gas\", \"density\"), (\"gas\", \"velocity_magnitude\")]\n        ... )\n        >>> slc.show()\n\n        \"\"\"\n        interactivity = self.plots[list(self.plots.keys())[0]].interactivity\n        if interactivity:\n            for v in sorted(self.plots.values()):\n                v.show()\n        else:\n            if IS_IPYTHON:\n                from IPython.display import display\n\n                display(self)\n            else:\n                raise YTNotInsideNotebook\n\n    @validate_plot\n    def display(self, name=None, mpl_kwargs=None):\n        \"\"\"Will attempt to show the plot in in an IPython notebook.\n        Failing that, the plot will be saved to disk.\"\"\"\n        try:\n            return self.show()\n        except YTNotInsideNotebook:\n            return self.save(name=name, mpl_kwargs=mpl_kwargs)\n\n    @validate_plot\n    def _repr_html_(self):\n        \"\"\"Return an html representation of the plot object. Will display as a\n        png for each WindowPlotMPL instance in self.plots\"\"\"\n        ret = \"\"\n        for field in self.plots:\n            img = base64.b64encode(self.plots[field]._repr_png_()).decode()\n            ret += (\n                r'<img style=\"max-width:100%;max-height:100%;\" '\n                rf'src=\"data:image/png;base64,{img}\"><br>'\n            )\n        return ret\n\n    @invalidate_plot\n    def set_xlabel(self, label):\n        r\"\"\"\n        Allow the user to modify the X-axis title\n        Defaults to the global value. Fontsize defaults\n        to 18.\n\n        Parameters\n        ----------\n        label : str\n            The new string for the x-axis.\n\n        >>> plot.set_xlabel(\"H2I Number Density (cm$^{-3}$)\")\n\n        \"\"\"\n        self._xlabel = label\n        return self\n\n    @invalidate_plot\n    def set_ylabel(self, label):\n        r\"\"\"\n        Allow the user to modify the Y-axis title\n        Defaults to the global value.\n\n        Parameters\n        ----------\n        label : str\n            The new string for the y-axis.\n\n        >>> plot.set_ylabel(\"Temperature (K)\")\n\n        \"\"\"\n        self._ylabel = label\n        return self\n\n    def _get_axes_unit_labels(self, unit_x, unit_y):\n        axes_unit_labels = [\"\", \"\"]\n        comoving = False\n        hinv = False\n        for i, un in enumerate((unit_x, unit_y)):\n            unn = None\n            if hasattr(self.data_source, \"axis\"):\n                if hasattr(self.ds.coordinates, \"image_units\"):\n                    # This *forces* an override\n                    unn = self.ds.coordinates.image_units[self.data_source.axis][i]\n                elif hasattr(self.ds.coordinates, \"default_unit_label\"):\n                    axax = getattr(self.ds.coordinates, f\"{'xy'[i]}_axis\")[\n                        self.data_source.axis\n                    ]\n                    unn = self.ds.coordinates.default_unit_label.get(axax, None)\n            if unn in (1, \"1\", \"dimensionless\"):\n                axes_unit_labels[i] = \"\"\n                continue\n            if unn is not None:\n                axes_unit_labels[i] = _get_units_label(unn).strip(\"$\")\n                continue\n            # Use sympy to factor h out of the unit.  In this context 'un'\n            # is a string, so we call the Unit constructor.\n            expr = Unit(un, registry=self.ds.unit_registry).expr\n            h_expr = Unit(\"h\", registry=self.ds.unit_registry).expr\n            # See http://docs.sympy.org/latest/modules/core.html#sympy.core.expr.Expr\n            h_power = expr.as_coeff_exponent(h_expr)[1]\n            # un is now the original unit, but with h factored out.\n            un = str(expr * h_expr ** (-1 * h_power))\n            un_unit = Unit(un, registry=self.ds.unit_registry)\n            cm = Unit(\"cm\").expr\n            if str(un).endswith(\"cm\") and cm not in un_unit.expr.atoms():\n                comoving = True\n                un = un[:-2]\n            # no length units besides code_length end in h so this is safe\n            if h_power == -1:\n                hinv = True\n            elif h_power != 0:\n                # It doesn't make sense to scale a position by anything\n                # other than h**-1\n                raise RuntimeError\n            if un not in [\"1\", \"u\", \"unitary\"]:\n                if un in formatted_length_unit_names:\n                    un = formatted_length_unit_names[un]\n                else:\n                    un = Unit(un, registry=self.ds.unit_registry)\n                    un = un.latex_representation()\n                    if hinv:\n                        un = un + r\"\\,h^{-1}\"\n                    if comoving:\n                        un = un + r\"\\,(1+z)^{-1}\"\n                    pp = un[0]\n                    if pp in latex_prefixes:\n                        symbol_wo_prefix = un[1:]\n                        if symbol_wo_prefix in self.ds.unit_registry.prefixable_units:\n                            un = un.replace(pp, \"{\" + latex_prefixes[pp] + \"}\", 1)\n                axes_unit_labels[i] = _get_units_label(un).strip(\"$\")\n        return axes_unit_labels\n\n    def hide_colorbar(self, field=None):\n        \"\"\"\n        Hides the colorbar for a plot and updates the size of the\n        plot accordingly.  Defaults to operating on all fields for a\n        PlotContainer object.\n\n        Parameters\n        ----------\n\n        field : string, field tuple, or list of strings or field tuples (optional)\n            The name of the field(s) that we want to hide the colorbar.\n            If None or 'all' is provided, will default to using all fields available\n            for this object.\n\n        Examples\n        --------\n\n        This will save an image with no colorbar.\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> s = SlicePlot(ds, 2, \"density\", \"c\", (20, \"kpc\"))\n        >>> s.hide_colorbar()\n        >>> s.save()\n\n        This will save an image with no axis or colorbar.\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> s = SlicePlot(ds, 2, \"density\", \"c\", (20, \"kpc\"))\n        >>> s.hide_axes()\n        >>> s.hide_colorbar()\n        >>> s.save()\n        \"\"\"\n        if field is None or field == \"all\":\n            field = self.plots.keys()\n        for f in self.data_source._determine_fields(field):\n            self.plots[f].hide_colorbar()\n        return self\n\n    def show_colorbar(self, field=None):\n        \"\"\"\n        Shows the colorbar for a plot and updates the size of the\n        plot accordingly.  Defaults to operating on all fields for a\n        PlotContainer object.  See hide_colorbar().\n\n        Parameters\n        ----------\n\n        field : string, field tuple, or list of strings or field tuples (optional)\n        The name of the field(s) that we want to show the colorbar.\n        \"\"\"\n        if field is None:\n            field = self.fields\n        for f in iter_fields(field):\n            self.plots[f].show_colorbar()\n        return self\n\n    def hide_axes(self, field=None, draw_frame=None):\n        \"\"\"\n        Hides the axes for a plot and updates the size of the\n        plot accordingly.  Defaults to operating on all fields for a\n        PlotContainer object.\n\n        Parameters\n        ----------\n\n        field : string, field tuple, or list of strings or field tuples (optional)\n            The name of the field(s) that we want to hide the axes.\n\n        draw_frame : boolean\n            If True, the axes frame will still be drawn. Defaults to False.\n            See note below for more details.\n\n        Examples\n        --------\n\n        This will save an image with no axes.\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> s = SlicePlot(ds, 2, \"density\", \"c\", (20, \"kpc\"))\n        >>> s.hide_axes()\n        >>> s.save()\n\n        This will save an image with no axis or colorbar.\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> s = SlicePlot(ds, 2, \"density\", \"c\", (20, \"kpc\"))\n        >>> s.hide_axes()\n        >>> s.hide_colorbar()\n        >>> s.save()\n\n        Note\n        ----\n        By default, when removing the axes, the patch on which the axes are\n        drawn is disabled, making it impossible to later change e.g. the\n        background colour. To force the axes patch to be displayed while still\n        hiding the axes, set the ``draw_frame`` keyword argument to ``True``.\n        \"\"\"\n        if field is None:\n            field = self.fields\n        for f in iter_fields(field):\n            self.plots[f].hide_axes(draw_frame=draw_frame)\n        return self\n\n    def show_axes(self, field=None):\n        \"\"\"\n        Shows the axes for a plot and updates the size of the\n        plot accordingly.  Defaults to operating on all fields for a\n        PlotContainer object.  See hide_axes().\n\n        Parameters\n        ----------\n\n        field : string, field tuple, or list of strings or field tuples (optional)\n            The name of the field(s) that we want to show the axes.\n        \"\"\"\n        if field is None:\n            field = self.fields\n        for f in iter_fields(field):\n            self.plots[f].show_axes()\n        return self\n\n\nclass ImagePlotContainer(PlotContainer, abc.ABC):\n    \"\"\"A container for plots with colorbars.\"\"\"\n\n    _colorbar_valid = False\n\n    def __init__(self, data_source, figure_size, fontsize):\n        super().__init__(data_source, figure_size, fontsize)\n        self._callbacks = []\n        self._colorbar_label = PlotDictionary(self.data_source, lambda: None)\n\n    def _get_default_handlers(\n        self, field, default_display_units: Unit\n    ) -> tuple[NormHandler, ColorbarHandler]:\n        usr_units_str = get_default_from_config(\n            self.data_source, field=field, keys=\"units\", defaults=[None]\n        )\n        if usr_units_str is not None:\n            usr_units = Unit(usr_units_str)\n            d1 = usr_units.dimensions\n            d2 = default_display_units.dimensions\n\n            if d1 == d2:\n                display_units = usr_units\n            elif getattr(self, \"projected\", False) and d2 / d1 == length:\n                path_length_units = Unit(\n                    ytcfg.get_most_specific(\n                        \"plot\", *field, \"path_length_units\", fallback=\"cm\"\n                    ),\n                    registry=self.data_source.ds.unit_registry,\n                )\n                display_units = usr_units * path_length_units\n            else:\n                raise YTConfigurationError(\n                    f\"Invalid units in configuration file for field {field!r}. \"\n                    f\"Found {usr_units!r}\"\n                )\n        else:\n            display_units = default_display_units\n\n        pnh = NormHandler(self.data_source, display_units=display_units)\n\n        cbh = ColorbarHandler(\n            cmap=get_default_from_config(\n                self.data_source,\n                field=field,\n                keys=\"cmap\",\n                defaults=[None],\n            )\n        )\n        return pnh, cbh\n\n    @accepts_all_fields\n    @invalidate_plot\n    def set_cmap(self, field, cmap):\n        \"\"\"set the colormap for one of the fields\n\n        Parameters\n        ----------\n        field : string\n            the field to set the colormap\n            if field == 'all', applies to all plots.\n        cmap : string or tuple\n            If a string, will be interpreted as name of the colormap.\n            If a tuple, it is assumed to be of the form (name, type, number)\n            to be used for palettable functionality. (name, type, number, bool)\n            can be used to specify if a reverse colormap is to be used.\n\n        \"\"\"\n        self._colorbar_valid = False\n        self.plots[field].colorbar_handler.cmap = cmap\n        return self\n\n    @accepts_all_fields\n    @invalidate_plot\n    def set_background_color(self, field, color=None):\n        \"\"\"set the background color to match provided color\n\n        Parameters\n        ----------\n        field : string\n            the field to set the colormap\n            if field == 'all', applies to all plots.\n        color : string or RGBA tuple (optional)\n            if set, set the background color to this color\n            if unset, background color is set to the bottom value of\n            the color map\n\n        \"\"\"\n        cbh = self[field].colorbar_handler\n        cbh.background_color = color\n        return self\n\n    @accepts_all_fields\n    @invalidate_plot\n    def set_zlim(\n        self,\n        field,\n        zmin: float | Quantity | Literal[\"min\"] | Unset = UNSET,\n        zmax: float | Quantity | Literal[\"max\"] | Unset = UNSET,\n        dynamic_range: float | None = None,\n    ):\n        \"\"\"set the scale of the colormap\n\n        Parameters\n        ----------\n        field : string\n            the field to set a colormap scale\n            if field == 'all', applies to all plots.\n        zmin : float, Quantity, or 'min'\n            the new minimum of the colormap scale. If 'min', will\n            set to the minimum value in the current view.\n        zmax : float, Quantity, or 'max'\n            the new maximum of the colormap scale. If 'max', will\n            set to the maximum value in the current view.\n\n        Other Parameters\n        ----------------\n        dynamic_range : float (default: None)\n            The dynamic range of the image.\n            If zmin == None, will set zmin = zmax / dynamic_range\n            If zmax == None, will set zmax = zmin * dynamic_range\n\n        \"\"\"\n        if zmin is UNSET and zmax is UNSET:\n            raise TypeError(\"Missing required argument zmin or zmax\")\n\n        if zmin is UNSET:\n            zmin = None\n        elif zmin is None:\n            # this sentinel value juggling is barely maintainable\n            # this use case is deprecated so we can simplify the logic here\n            # in the future and use `None` as the default value,\n            # instead of the custom sentinel UNSET\n            issue_deprecation_warning(\n                \"Passing `zmin=None` explicitly is deprecated. \"\n                \"If you wish to explicitly set zmin to the minimal \"\n                \"data value, pass `zmin='min'` instead. \"\n                \"Otherwise leave this argument unset.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            zmin = \"min\"\n\n        if zmax is UNSET:\n            zmax = None\n        elif zmax is None:\n            # see above\n            issue_deprecation_warning(\n                \"Passing `zmax=None` explicitly is deprecated. \"\n                \"If you wish to explicitly set zmax to the maximal \"\n                \"data value, pass `zmax='max'` instead. \"\n                \"Otherwise leave this argument unset.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            zmax = \"max\"\n\n        pnh = self.plots[field].norm_handler\n        pnh.vmin = zmin\n        pnh.vmax = zmax\n        pnh.dynamic_range = dynamic_range\n\n        return self\n\n    @accepts_all_fields\n    @invalidate_plot\n    def set_colorbar_minorticks(self, field, state):\n        \"\"\"turn colorbar minor ticks on or off in the current plot\n\n        Displaying minor ticks reduces performance; turn them off\n        using set_colorbar_minorticks('all', False) if drawing speed is a problem.\n\n        Parameters\n        ----------\n        field : string\n            the field to remove colorbar minorticks\n            if field == 'all', applies to all plots.\n        state : bool\n            the state indicating 'on' (True) or 'off' (False)\n        \"\"\"\n        self.plots[field].colorbar_handler.draw_minorticks = state\n        return self\n\n    @invalidate_plot\n    def set_colorbar_label(self, field, label):\n        r\"\"\"\n        Sets the colorbar label.\n\n        Parameters\n        ----------\n        field : str or tuple\n          The name of the field to modify the label for.\n        label : str\n          The new label\n\n        >>> plot.set_colorbar_label(\n        ...     (\"gas\", \"density\"), \"Dark Matter Density (g cm$^{-3}$)\"\n        ... )\n\n        \"\"\"\n        field = self.data_source._determine_fields(field)\n        self._colorbar_label[field] = label\n        return self\n\n    def _get_axes_labels(self, field):\n        return (self._xlabel, self._ylabel, self._colorbar_label[field])\n\n\nclass BaseLinePlot(PlotContainer, abc.ABC):\n    # A common ancestor to LinePlot and ProfilePlot\n\n    @abc.abstractmethod\n    def _get_axrect(self):\n        pass\n\n    def _get_plot_instance(self, field):\n        if field in self.plots:\n            return self.plots[field]\n        axrect = self._get_axrect()\n\n        pnh = NormHandler(\n            self.data_source, display_units=self.data_source.ds.field_info[field].units\n        )\n        finfo = self.data_source.ds._get_field_info(field)\n        if not finfo.take_log:\n            pnh.norm_type = Normalize\n        plot = PlotMPL(self.figure_size, axrect, norm_handler=pnh)\n        self.plots[field] = plot\n\n        return plot\n"
  },
  {
    "path": "yt/visualization/plot_modifications.py",
    "content": "import inspect\nimport re\nimport sys\nimport warnings\nfrom abc import ABC, abstractmethod\nfrom functools import update_wrapper\nfrom numbers import Integral, Number\nfrom typing import Any, TypeGuard\n\nimport matplotlib\nimport numpy as np\nimport rlic\nfrom unyt import unyt_quantity\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._typing import AnyFieldKey, FieldKey\nfrom yt.data_objects.data_containers import YTDataContainer\nfrom yt.data_objects.level_sets.clump_handling import Clump\nfrom yt.data_objects.selection_objects.cut_region import YTCutRegion\nfrom yt.frontends.ytdata.data_structures import YTClumpContainer\nfrom yt.funcs import is_sequence, mylog, validate_width_tuple\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.unstructured_mesh_handler import UnstructuredIndex\nfrom yt.units import dimensions\nfrom yt.units._numpy_wrapper_functions import uhstack\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.exceptions import (\n    YTDataTypeUnsupported,\n    YTFieldNotFound,\n    YTFieldTypeNotFound,\n    YTUnsupportedPlotCallback,\n)\nfrom yt.utilities.lib.geometry_utils import triangle_plane_intersect\nfrom yt.utilities.lib.mesh_triangulation import triangulate_indices\nfrom yt.utilities.lib.pixelization_routines import (\n    pixelize_cartesian,\n    pixelize_off_axis_cartesian,\n)\nfrom yt.utilities.math_utils import periodic_ray\nfrom yt.visualization._commons import (\n    _swap_arg_pair_order,\n    _swap_axes_extents,\n    invalidate_plot,\n)\nfrom yt.visualization.base_plot_types import CallbackWrapper\nfrom yt.visualization.image_writer import apply_colormap\nfrom yt.visualization.plot_window import PWViewerMPL\n\nif sys.version_info >= (3, 11):\n    from typing import assert_never\nelse:\n    from typing_extensions import assert_never\n\ncallback_registry: dict[str, type[\"PlotCallback\"]] = {}\n\n\ndef _validate_factor_tuple(factor) -> tuple[int, int]:\n    if (\n        is_sequence(factor)\n        and len(factor) == 2\n        and all(isinstance(_, Integral) for _ in factor)\n    ):\n        # - checking for \"is_sequence\" allows lists, numpy arrays and other containers\n        # - checking for Integral type allows numpy integer types\n        # in any case we return a with strict typing\n        return (int(factor[0]), int(factor[1]))\n    elif isinstance(factor, Integral):\n        return (int(factor), int(factor))\n    else:\n        raise TypeError(\n            f\"Expected a single, or a pair of integers, received {factor!r}\"\n        )\n\n\nclass PlotCallback(ABC):\n    # _supported_geometries is set by subclasses of PlotCallback to a tuple of\n    # strings corresponding to the names of the geometries that a callback\n    # supports.  By default it is None, which means it supports everything.\n    # Note that if there's a coord_system parameter that is set to \"axis\" or\n    # \"figure\" this is disregarded.  If \"force\" is included in the tuple, it\n    # will *not* check whether or not the coord_system is in axis or figure,\n    # and will only look at the geometries.\n    _supported_geometries: tuple[str, ...] | None = None\n    _incompatible_plot_types: tuple[str, ...] = ()\n\n    def __init_subclass__(cls, *args, **kwargs):\n        if inspect.isabstract(cls):\n            return\n\n        # register class\n        callback_registry[cls.__name__] = cls\n\n        # create a PWViewerMPL method by wrapping __init__\n        if cls.__init__.__doc__ is None:\n            # allow docstring definition at the class level instead of __init__\n            cls.__init__.__doc__ = cls.__doc__\n\n        supported_geometries = cls._supported_geometries\n        incompatible_plot_types = cls._incompatible_plot_types\n        type_name = cls._type_name\n\n        @invalidate_plot\n        def closure(self, *args, **kwargs):\n            nonlocal supported_geometries\n            nonlocal incompatible_plot_types\n            nonlocal type_name\n\n            geom = self.ds.geometry\n            if not (\n                supported_geometries is None\n                or geom in supported_geometries\n                or (\n                    kwargs.get(\"coord_system\") in (\"axis\", \"figure\")\n                    and \"force\" not in supported_geometries\n                )\n            ):\n                raise YTDataTypeUnsupported(geom, supported_geometries)\n            if self._plot_type in incompatible_plot_types:\n                raise YTUnsupportedPlotCallback(type_name, self._plot_type)\n            self._callbacks.append(cls(*args, **kwargs))\n            return self\n\n        update_wrapper(\n            wrapper=closure,\n            wrapped=cls.__init__,\n            assigned=(\"__annotations__\", \"__doc__\"),\n        )\n\n        method_name = \"annotate_\" + type_name\n        closure.__name__ = method_name\n        setattr(PWViewerMPL, method_name, closure)\n\n    @abstractmethod\n    def __init__(self, *args, **kwargs) -> None:\n        pass\n\n    @abstractmethod\n    def __call__(self, plot: CallbackWrapper) -> Any:\n        pass\n\n    def _project_coords(self, plot, coord):\n        \"\"\"\n        Convert coordinates from simulation data coordinates to projected\n        data coordinates.  Simulation data coordinates are three dimensional,\n        and can either be specified as a YTArray or as a list or array in\n        code_length units.  Projected data units are 2D versions of the\n        simulation data units relative to the axes of the final plot.\n        \"\"\"\n        if len(coord) == 3:\n            if not isinstance(coord, YTArray):\n                coord_copy = plot.data.ds.arr(coord, \"code_length\")\n            else:\n                # coord is being copied so that if the user has a unyt_array already\n                # we don't change the user's version\n                coord_copy = coord.to(\"code_length\")\n            ax = plot.data.axis\n            # if this is an on-axis projection or slice, then\n            # just grab the appropriate 2 coords for the on-axis view\n            if ax is not None:\n                (xi, yi) = (\n                    plot.data.ds.coordinates.x_axis[ax],\n                    plot.data.ds.coordinates.y_axis[ax],\n                )\n                ret_coord = (coord_copy[xi], coord_copy[yi])\n\n            # if this is an off-axis project or slice (ie cutting plane)\n            # we have to calculate where the data coords fall in the projected\n            # plane\n            else:\n                # transpose is just to get [[x1,x2,...],[y1,y2,...],[z1,z2,...]]\n                # in the same order as plot.data.center for array arithmetic\n                coord_vectors = coord_copy.transpose() - plot.data.center\n                x = np.dot(coord_vectors, plot.data.orienter.unit_vectors[1])\n                y = np.dot(coord_vectors, plot.data.orienter.unit_vectors[0])\n                # Transpose into image coords. Due to VR being not a\n                # right-handed coord system\n                ret_coord = (y, x)\n\n        # if the position is already two-coords, it is expected to be\n        # in the proper projected orientation\n        else:\n            raise ValueError(\"'data' coordinates must be 3 dimensions\")\n        return ret_coord\n\n    def _convert_to_plot(self, plot, coord, offset=True):\n        \"\"\"\n        Convert coordinates from projected data coordinates to PlotWindow\n        plot coordinates.  Projected data coordinates are two dimensional\n        and refer to the location relative to the specific axes being plotted,\n        although still in simulation units.  PlotWindow plot coordinates\n        are locations as found in the final plot, usually with the origin\n        in the center of the image and the extent of the image defined by\n        the final plot axis markers.\n        \"\"\"\n        # coord should be a 2 x ncoord array-like datatype.\n        try:\n            ncoord = np.array(coord).shape[1]\n        except IndexError:\n            ncoord = 1\n\n        # Convert the data and plot limits to tiled numpy arrays so that\n        # convert_to_plot is automatically vectorized.\n        phy_bounds = self._physical_bounds(plot)\n        x0, x1, y0, y1 = (np.array(np.tile(xyi, ncoord)) for xyi in phy_bounds)\n        plt_bounds = self._plot_bounds(plot)\n        xx0, xx1, yy0, yy1 = (np.tile(xyi, ncoord) for xyi in plt_bounds)\n\n        try:\n            ccoord = np.array(coord.to(\"code_length\"))\n        except AttributeError:\n            ccoord = np.array(coord)\n\n        # We need a special case for when we are only given one coordinate.\n        if ccoord.shape == (2,):\n            return np.array(\n                [\n                    ((ccoord[0] - x0) / (x1 - x0) * (xx1 - xx0) + xx0)[0],\n                    ((ccoord[1] - y0) / (y1 - y0) * (yy1 - yy0) + yy0)[0],\n                ]\n            )\n        else:\n            return np.array(\n                [\n                    (ccoord[0][:] - x0) / (x1 - x0) * (xx1 - xx0) + xx0,\n                    (ccoord[1][:] - y0) / (y1 - y0) * (yy1 - yy0) + yy0,\n                ]\n            )\n\n    def _sanitize_coord_system(self, plot, coord, coord_system):\n        \"\"\"\n        Given a set of one or more x,y (and z) coordinates and a coordinate\n        system, convert the coordinates (and transformation) ready for final\n        plotting.\n\n        Parameters\n        ----------\n\n        plot: a PlotMPL subclass\n           The plot that we are converting coordinates for\n\n        coord: array-like\n           Coordinates in some coordinate system: [x,y,z].\n           Alternatively, can specify multiple coordinates as:\n           [[x1,x2,...,xn], [y1, y2,...,yn], [z1,z2,...,zn]]\n\n        coord_system: string\n\n            Possible values include:\n\n            * ``'data'``\n                3D data coordinates relative to original dataset\n\n            * ``'plot'``\n                2D coordinates as defined by the final axis locations\n\n            * ``'axis'``\n                2D coordinates within the axis object from (0,0) in lower left\n                to (1,1) in upper right.  Same as matplotlib axis coords.\n\n            * ``'figure'``\n                2D coordinates within figure object from (0,0) in lower left\n                to (1,1) in upper right.  Same as matplotlib figure coords.\n        \"\"\"\n        # Assure coords are either a YTArray or numpy array\n        coord = np.asanyarray(coord, dtype=\"float64\")\n        # if in data coords, project them to plot coords\n        if coord_system == \"data\":\n            if len(coord) < 3:\n                raise ValueError(\n                    \"Coordinates in 'data' coordinate system need to be in 3D\"\n                )\n            coord = self._project_coords(plot, coord)\n            coord = self._convert_to_plot(plot, coord)\n        # if in plot coords, define the transform correctly\n        if coord_system == \"data\" or coord_system == \"plot\":\n            self.transform = plot._axes.transData\n            return coord\n        # if in axis coords, define the transform correctly\n        if coord_system == \"axis\":\n            self.transform = plot._axes.transAxes\n            if len(coord) > 2:\n                raise ValueError(\n                    \"Coordinates in 'axis' coordinate system need to be in 2D\"\n                )\n            return coord\n        # if in figure coords, define the transform correctly\n        elif coord_system == \"figure\":\n            self.transform = plot._figure.transFigure\n            return coord\n        else:\n            raise ValueError(\n                \"Argument coord_system must have a value of \"\n                \"'data', 'plot', 'axis', or 'figure'.\"\n            )\n\n    def _physical_bounds(self, plot):\n        xlims = tuple(v.in_units(\"code_length\") for v in plot.xlim)\n        ylims = tuple(v.in_units(\"code_length\") for v in plot.ylim)\n        # _swap_axes note: do NOT need to unswap here because plot (a CallbackWrapper\n        # instance) stores the x, y lims of the underlying data object.\n        return xlims + ylims\n\n    def _plot_bounds(self, plot):\n        xlims = plot._axes.get_xlim()\n        ylims = plot._axes.get_ylim()\n        # _swap_axes note: because we are getting the plot limits from the axes\n        # object, if the axes have been swapped, these will be reversed from the\n        # _physical_bounds. So we need to unswap here, but not in _physical_bounds.\n        if plot._swap_axes:\n            return ylims + xlims\n        return xlims + ylims\n\n    def _pixel_scale(self, plot):\n        x0, x1, y0, y1 = self._physical_bounds(plot)\n        xx0, xx1, yy0, yy1 = self._plot_bounds(plot)\n        dx = (xx1 - xx0) / (x1 - x0)\n        dy = (yy1 - yy0) / (y1 - y0)\n        return dx, dy\n\n    def _set_font_properties(self, plot, labels, **kwargs):\n        \"\"\"\n        This sets all of the text instances created by a callback to have\n        the same font size and properties as all of the other fonts in the\n        figure.  If kwargs are set, they override the defaults.\n        \"\"\"\n        # This is a little messy because there is no trivial way to update\n        # a MPL.font_manager.FontProperties object with new attributes\n        # aside from setting them individually.  So we pick out the relevant\n        # MPL.Text() kwargs from the local kwargs and let them override the\n        # defaults.\n        local_font_properties = plot.font_properties.copy()\n\n        # Turn off the default TT font file, otherwise none of this works.\n        local_font_properties.set_file(None)\n        local_font_properties.set_family(\"stixgeneral\")\n\n        if \"family\" in kwargs:\n            local_font_properties.set_family(kwargs[\"family\"])\n        if \"file\" in kwargs:\n            local_font_properties.set_file(kwargs[\"file\"])\n        if \"fontconfig_pattern\" in kwargs:\n            local_font_properties.set_fontconfig_pattern(kwargs[\"fontconfig_pattern\"])\n        if \"name\" in kwargs:\n            local_font_properties.set_name(kwargs[\"name\"])\n        if \"size\" in kwargs:\n            local_font_properties.set_size(kwargs[\"size\"])\n        if \"slant\" in kwargs:\n            local_font_properties.set_slant(kwargs[\"slant\"])\n        if \"stretch\" in kwargs:\n            local_font_properties.set_stretch(kwargs[\"stretch\"])\n        if \"style\" in kwargs:\n            local_font_properties.set_style(kwargs[\"style\"])\n        if \"variant\" in kwargs:\n            local_font_properties.set_variant(kwargs[\"variant\"])\n        if \"weight\" in kwargs:\n            local_font_properties.set_weight(kwargs[\"weight\"])\n\n        # For each label, set the font properties and color to the figure\n        # defaults if not already set in the callback itself\n        for label in labels:\n            if plot.font_color is not None and \"color\" not in kwargs:\n                label.set_color(plot.font_color)\n            label.set_fontproperties(local_font_properties)\n\n    def _set_plot_limits(self, plot, extent=None) -> None:\n        \"\"\"\n        calls set_xlim, set_ylim for plot, accounting for swapped axes\n\n        Parameters\n        ----------\n        plot : CallbackWrapper\n            a CallbackWrapper instance\n        extent : tuple or list\n            The raw extent (prior to swapping). if None, will fetch it.\n        \"\"\"\n        if extent is None:\n            extent = self._plot_bounds(plot)\n\n        if plot._swap_axes:\n            extent = _swap_axes_extents(extent)\n\n        plot._axes.set_xlim(extent[0], extent[1])\n        plot._axes.set_ylim(extent[2], extent[3])\n\n    @staticmethod\n    def _sanitize_xy_order(plot, *args):\n        \"\"\"\n        flips x-y pairs of plot arguments if needed\n\n        Parameters\n        ----------\n        plot : CallbackWrapper\n            a CallbackWrapper instance\n        *args\n            x, y plot arguments, must have an even number of *args\n\n        Returns\n        -------\n        tuple\n            either the original args or new args, with (x, y) pairs switched. i.e.,\n\n            _sanitize_xy_order(plot, x, y, px, py) returns:\n                x, y, px, py if plot._swap_axes is False\n                y, x, py, px if plot._swap_axes is True\n\n        \"\"\"\n        if plot._swap_axes:\n            return _swap_arg_pair_order(*args)\n        return args\n\n\nclass VelocityCallback(PlotCallback):\n    \"\"\"\n    Adds a 'quiver' plot of velocity to the plot, skipping all but\n    every *factor* datapoint. *scale* is the data units per arrow\n    length unit using *scale_units* and *plot_args* allows you to\n    pass in matplotlib arguments (see matplotlib.axes.Axes.quiver\n    for more info). if *normalize* is True, the velocity fields\n    will be scaled by their local (in-plane) length, allowing\n    morphological features to be more clearly seen for fields\n    with substantial variation in field strength.\n    \"\"\"\n\n    _type_name = \"velocity\"\n    _supported_geometries = (\n        \"cartesian\",\n        \"spectral_cube\",\n        \"polar\",\n        \"cylindrical\",\n        \"spherical\",\n    )\n    _incompatible_plot_types = (\"OffAxisProjection\", \"Particle\")\n\n    def __init__(\n        self,\n        factor: tuple[int, int] | int = 16,\n        *,\n        scale=None,\n        scale_units=None,\n        normalize=False,\n        plot_args=None,\n        **kwargs,\n    ):\n        self.factor = _validate_factor_tuple(factor)\n        self.scale = scale\n        self.scale_units = scale_units\n        self.normalize = normalize\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            plot_args.update(kwargs)\n        else:\n            plot_args = kwargs\n\n        self.plot_args = plot_args\n\n    def __call__(self, plot) -> \"BaseQuiverCallback\":\n        ftype = plot.data._current_fluid_type\n        # Instantiation of these is cheap\n        geometry: Geometry = plot.data.ds.geometry\n        if plot._type_name == \"CuttingPlane\":\n            match geometry:\n                case Geometry.CARTESIAN:\n                    pass\n                case (\n                    Geometry.POLAR\n                    | Geometry.CYLINDRICAL\n                    | Geometry.SPHERICAL\n                    | Geometry.GEOGRAPHIC\n                    | Geometry.INTERNAL_GEOGRAPHIC\n                    | Geometry.SPECTRAL_CUBE\n                ):\n                    raise NotImplementedError(\n                        f\"annotate_velocity is not supported for cutting plane for {geometry=}\"\n                    )\n                case _:\n                    assert_never(geometry)\n        qcb: BaseQuiverCallback\n        if plot._type_name == \"CuttingPlane\":\n            qcb = CuttingQuiverCallback(\n                (ftype, \"cutting_plane_velocity_x\"),\n                (ftype, \"cutting_plane_velocity_y\"),\n                factor=self.factor,\n                scale=self.scale,\n                normalize=self.normalize,\n                scale_units=self.scale_units,\n                **self.plot_args,\n            )\n        else:\n            xax = plot.data.ds.coordinates.x_axis[plot.data.axis]\n            yax = plot.data.ds.coordinates.y_axis[plot.data.axis]\n            axis_names = plot.data.ds.coordinates.axis_name\n\n            bv = plot.data.get_field_parameter(\"bulk_velocity\")\n            if bv is not None:\n                bv_x = bv[xax]\n                bv_y = bv[yax]\n            else:\n                bv_x = bv_y = 0\n\n            match geometry:\n                case Geometry.POLAR | Geometry.CYLINDRICAL:\n                    if axis_names[plot.data.axis] == \"z\":\n                        # polar_z and cyl_z is aligned with cartesian_z\n                        # should convert r-theta plane to x-y plane\n                        xv = (ftype, \"velocity_cartesian_x\")\n                        yv = (ftype, \"velocity_cartesian_y\")\n                    else:\n                        xv = (ftype, f\"velocity_{axis_names[xax]}\")\n                        yv = (ftype, f\"velocity_{axis_names[yax]}\")\n                case Geometry.SPHERICAL:\n                    if axis_names[plot.data.axis] == \"phi\":\n                        xv = (ftype, \"velocity_cylindrical_radius\")\n                        yv = (ftype, \"velocity_cylindrical_z\")\n                    elif axis_names[plot.data.axis] == \"theta\":\n                        xv = (ftype, \"velocity_conic_x\")\n                        yv = (ftype, \"velocity_conic_y\")\n                    else:\n                        raise NotImplementedError(\n                            f\"annotate_velocity is missing support for normal={axis_names[plot.data.axis]!r}\"\n                        )\n                case Geometry.CARTESIAN:\n                    xv = (ftype, f\"velocity_{axis_names[xax]}\")\n                    yv = (ftype, f\"velocity_{axis_names[yax]}\")\n                case (\n                    Geometry.GEOGRAPHIC\n                    | Geometry.INTERNAL_GEOGRAPHIC\n                    | Geometry.SPECTRAL_CUBE\n                ):\n                    raise NotImplementedError(\n                        f\"annotate_velocity is not supported for {geometry=}\"\n                    )\n                case _:\n                    assert_never(geometry)\n\n            # determine the full fields including field type\n            xv = plot.data._determine_fields(xv)[0]\n            yv = plot.data._determine_fields(yv)[0]\n\n            qcb = QuiverCallback(\n                xv,\n                yv,\n                factor=self.factor,\n                scale=self.scale,\n                scale_units=self.scale_units,\n                normalize=self.normalize,\n                bv_x=bv_x,\n                bv_y=bv_y,\n                **self.plot_args,\n            )\n        return qcb(plot)\n\n\nclass MagFieldCallback(PlotCallback):\n    \"\"\"\n    Adds a 'quiver' plot of magnetic field to the plot, skipping all but\n    every *factor* datapoint. *scale* is the data units per arrow\n    length unit using *scale_units* and *plot_args* allows you to pass\n    in matplotlib arguments (see matplotlib.axes.Axes.quiver for more info).\n    if *normalize* is True, the magnetic fields will be scaled by their\n    local (in-plane) length, allowing morphological features to be more\n    clearly seen for fields with substantial variation in field strength.\n    \"\"\"\n\n    _type_name = \"magnetic_field\"\n    _supported_geometries = (\n        \"cartesian\",\n        \"spectral_cube\",\n        \"polar\",\n        \"cylindrical\",\n        \"spherical\",\n    )\n    _incompatible_plot_types = (\"OffAxisProjection\", \"Particle\")\n\n    def __init__(\n        self,\n        factor: tuple[int, int] | int = 16,\n        *,\n        scale=None,\n        scale_units=None,\n        normalize=False,\n        plot_args=None,\n        **kwargs,\n    ):\n        self.factor = _validate_factor_tuple(factor)\n        self.scale = scale\n        self.scale_units = scale_units\n        self.normalize = normalize\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            plot_args.update(kwargs)\n        else:\n            plot_args = kwargs\n        self.plot_args = plot_args\n\n    def __call__(self, plot) -> \"BaseQuiverCallback\":\n        ftype = plot.data._current_fluid_type\n        # Instantiation of these is cheap\n        geometry: Geometry = plot.data.ds.geometry\n        qcb: BaseQuiverCallback\n        if plot._type_name == \"CuttingPlane\":\n            match geometry:\n                case Geometry.CARTESIAN:\n                    pass\n                case (\n                    Geometry.POLAR\n                    | Geometry.CYLINDRICAL\n                    | Geometry.SPHERICAL\n                    | Geometry.GEOGRAPHIC\n                    | Geometry.INTERNAL_GEOGRAPHIC\n                    | Geometry.SPECTRAL_CUBE\n                ):\n                    raise NotImplementedError(\n                        f\"annotate_magnetic_field is not supported for cutting plane for {geometry=}\"\n                    )\n                case _:\n                    assert_never(geometry)\n            qcb = CuttingQuiverCallback(\n                (ftype, \"cutting_plane_magnetic_field_x\"),\n                (ftype, \"cutting_plane_magnetic_field_y\"),\n                factor=self.factor,\n                scale=self.scale,\n                scale_units=self.scale_units,\n                normalize=self.normalize,\n                **self.plot_args,\n            )\n        else:\n            xax = plot.data.ds.coordinates.x_axis[plot.data.axis]\n            yax = plot.data.ds.coordinates.y_axis[plot.data.axis]\n            axis_names = plot.data.ds.coordinates.axis_name\n\n            match geometry:\n                case Geometry.POLAR | Geometry.CYLINDRICAL:\n                    if axis_names[plot.data.axis] == \"z\":\n                        # polar_z and cyl_z is aligned with cartesian_z\n                        # should convert r-theta plane to x-y plane\n                        xv = (ftype, \"magnetic_field_cartesian_x\")\n                        yv = (ftype, \"magnetic_field_cartesian_y\")\n                    else:\n                        xv = (ftype, f\"magnetic_field_{axis_names[xax]}\")\n                        yv = (ftype, f\"magnetic_field_{axis_names[yax]}\")\n                case Geometry.SPHERICAL:\n                    if axis_names[plot.data.axis] == \"phi\":\n                        xv = (ftype, \"magnetic_field_cylindrical_radius\")\n                        yv = (ftype, \"magnetic_field_cylindrical_z\")\n                    elif axis_names[plot.data.axis] == \"theta\":\n                        xv = (ftype, \"magnetic_field_conic_x\")\n                        yv = (ftype, \"magnetic_field_conic_y\")\n                    else:\n                        raise NotImplementedError(\n                            f\"annotate_magnetic_field is missing support for normal={axis_names[plot.data.axis]!r}\"\n                        )\n                case Geometry.CARTESIAN:\n                    xv = (ftype, f\"magnetic_field_{axis_names[xax]}\")\n                    yv = (ftype, f\"magnetic_field_{axis_names[yax]}\")\n                case (\n                    Geometry.GEOGRAPHIC\n                    | Geometry.INTERNAL_GEOGRAPHIC\n                    | Geometry.SPECTRAL_CUBE\n                ):\n                    raise NotImplementedError(\n                        f\"annotate_magnetic_field is not supported for {geometry=}\"\n                    )\n                case _:\n                    assert_never(geometry)\n\n            qcb = QuiverCallback(\n                xv,\n                yv,\n                factor=self.factor,\n                scale=self.scale,\n                scale_units=self.scale_units,\n                normalize=self.normalize,\n                **self.plot_args,\n            )\n        return qcb(plot)\n\n\nclass BaseQuiverCallback(PlotCallback, ABC):\n    _incompatible_plot_types = (\"OffAxisProjection\", \"Particle\")\n\n    def __init__(\n        self,\n        field_x,\n        field_y,\n        field_c=None,\n        *,\n        factor: tuple[int, int] | int = 16,\n        scale=None,\n        scale_units=None,\n        normalize=False,\n        plot_args=None,\n        **kwargs,\n    ):\n        self.field_x = field_x\n        self.field_y = field_y\n        self.field_c = field_c\n        self.factor = _validate_factor_tuple(factor)\n        self.scale = scale\n        self.scale_units = scale_units\n        self.normalize = normalize\n        if plot_args is None:\n            plot_args = kwargs\n        else:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            plot_args.update(kwargs)\n\n        self.plot_args = plot_args\n\n    @abstractmethod\n    def _get_quiver_data(self, plot, bounds: tuple, nx: int, ny: int):\n        # must return (pixX, pixY, pixC) arrays (pixC can be None)\n        pass\n\n    def __call__(self, plot):\n        # construct mesh\n        bounds = self._physical_bounds(plot)\n        nx = plot.raw_image_shape[1] // self.factor[0]\n        ny = plot.raw_image_shape[0] // self.factor[1]\n        xx0, xx1, yy0, yy1 = self._plot_bounds(plot)\n\n        if plot._transform is None:\n            X, Y = np.meshgrid(\n                np.linspace(xx0, xx1, nx, endpoint=True),\n                np.linspace(yy0, yy1, ny, endpoint=True),\n            )\n        else:\n            # when we have a cartopy transform, provide the x, y values\n            # in the coordinate reference system of the data and let cartopy\n            # do the transformation. Also check for the exact bounds of the transform\n            # which can cause issues with projections.\n            tform_bnds = plot._transform.x_limits + plot._transform.y_limits\n            if any(b.d == tb for b, tb in zip(bounds, tform_bnds, strict=True)):\n                # note: cartopy will also raise its own warning, but it is useful to add this\n                # warning as well since the only way to avoid the exact bounds is to change the\n                # extent of the plot.\n                warnings.warn(\n                    \"Using the exact bounds of the transform may cause errors at the bounds.\"\n                    \" To avoid this warning, adjust the width of your plot object to not include \"\n                    \"the bounds.\",\n                    stacklevel=2,\n                )\n            X, Y = np.meshgrid(\n                np.linspace(bounds[0].d, bounds[1].d, nx, endpoint=True),\n                np.linspace(bounds[2].d, bounds[3].d, ny, endpoint=True),\n            )\n\n        pixX, pixY, pixC = self._get_quiver_data(plot, bounds, nx, ny)\n\n        retv = self._finalize(plot, X, Y, pixX, pixY, pixC)\n        self._set_plot_limits(plot, (xx0, xx1, yy0, yy1))\n        return retv\n\n    def _finalize(self, plot, X, Y, pixX, pixY, pixC):\n        if self.normalize:\n            nn = np.sqrt(pixX**2 + pixY**2)\n            nn = np.where(nn == 0, 1, nn)\n            pixX /= nn\n            pixY /= nn\n\n        X, Y, pixX, pixY = self._sanitize_xy_order(plot, X, Y, pixX, pixY)\n        # quiver plots ignore x, y axes inversions when using angles=\"uv\" (the\n        # default), so reverse the direction of the vectors when flipping the axis.\n        if self.plot_args.get(\"angles\", None) != \"xy\":\n            if plot._flip_vertical:\n                pixY = -1 * pixY\n            if plot._flip_horizontal:\n                pixX = -1 * pixX\n\n        args = [X, Y, pixX, pixY]\n        if pixC is not None:\n            args.append(pixC)\n\n        kwargs = {\n            \"scale\": self.scale,\n            \"scale_units\": self.scale_units,\n        }\n        kwargs.update(self.plot_args)\n\n        if plot._transform is not None:\n            if \"transform\" in kwargs:\n                msg = (\n                    \"The base plot already has a transform set, ignoring the provided keyword \"\n                    \"argument and using the base transform.\"\n                )\n                warnings.warn(msg, stacklevel=2)\n            kwargs[\"transform\"] = plot._transform\n\n        return plot._axes.quiver(*args, **kwargs)\n\n\nclass QuiverCallback(BaseQuiverCallback):\n    \"\"\"\n    Adds a 'quiver' plot to any plot, using the *field_x* and *field_y*\n    from the associated data, skipping every *factor* pixels.\n    *field_c* is an optional field name used for color.\n    *scale* is the data units per arrow length unit using *scale_units*\n    and *plot_args* allows you to pass in matplotlib arguments (see\n    matplotlib.axes.Axes.quiver for more info). if *normalize* is True,\n    the fields will be scaled by their local (in-plane) length, allowing\n    morphological features to be more clearly seen for fields with\n    substantial variation in field strength.\n    \"\"\"\n\n    _type_name = \"quiver\"\n    _supported_geometries: tuple[str, ...] = (\n        \"cartesian\",\n        \"spectral_cube\",\n        \"polar\",\n        \"cylindrical\",\n        \"spherical\",\n        \"geographic\",\n        \"internal_geographic\",\n    )\n\n    def __init__(\n        self,\n        field_x,\n        field_y,\n        field_c=None,\n        *,\n        factor: tuple[int, int] | int = 16,\n        scale=None,\n        scale_units=None,\n        normalize=False,\n        bv_x=0,\n        bv_y=0,\n        plot_args=None,\n        **kwargs,\n    ):\n        super().__init__(\n            field_x,\n            field_y,\n            field_c,\n            factor=factor,\n            scale=scale,\n            scale_units=scale_units,\n            normalize=normalize,\n            plot_args=plot_args,\n            **kwargs,\n        )\n        self.bv_x = bv_x\n        self.bv_y = bv_y\n\n    def _get_quiver_data(self, plot, bounds: tuple, nx: int, ny: int):\n        # calls the pixelizer, returns pixX, pixY, pixC arrays\n\n        def transform(field_name, vector_value):\n            field_units = plot.data[field_name].units\n\n            def _transformed_field(data):\n                return data[field_name] - data.ds.arr(vector_value, field_units)\n\n            plot.data.ds.add_field(\n                (\"gas\", f\"transformed_{field_name}\"),\n                sampling_type=\"cell\",\n                function=_transformed_field,\n                units=field_units,\n                display_field=False,\n            )\n\n        if self.bv_x != 0.0 or self.bv_x != 0.0:\n            # We create a relative vector field\n            transform(self.field_x, self.bv_x)\n            transform(self.field_y, self.bv_y)\n            field_x = f\"transformed_{self.field_x}\"\n            field_y = f\"transformed_{self.field_y}\"\n        else:\n            field_x, field_y = self.field_x, self.field_y\n\n        periodic = int(any(plot.data.ds.periodicity))\n        pixX = plot.data.ds.coordinates.pixelize(\n            plot.data.axis,\n            plot.data,\n            field_x,\n            bounds,\n            (nx, ny),\n            False,  # antialias\n            periodic,\n        )\n        pixY = plot.data.ds.coordinates.pixelize(\n            plot.data.axis,\n            plot.data,\n            field_y,\n            bounds,\n            (nx, ny),\n            False,  # antialias\n            periodic,\n        )\n\n        if self.field_c is not None:\n            pixC = plot.data.ds.coordinates.pixelize(\n                plot.data.axis,\n                plot.data,\n                self.field_c,\n                bounds,\n                (nx, ny),\n                False,  # antialias\n                periodic,\n            )\n        else:\n            pixC = None\n\n        return pixX, pixY, pixC\n\n\nclass ContourCallback(PlotCallback):\n    \"\"\"\n    Add contours in *field* to the plot. *levels* governs the number of\n    contours generated, *factor* governs the number of points used in the\n    interpolation, *take_log* governs how it is contoured and *clim* gives\n    the (lower, upper) limits for contouring.  An alternate data source can be\n    specified with *data_source*, but by default the plot's data source will be\n    queried.\n    \"\"\"\n\n    _type_name = \"contour\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"cylindrical\")\n    _incompatible_plot_types = (\"Particle\",)\n\n    def __init__(\n        self,\n        field: AnyFieldKey,\n        levels: int = 5,\n        *,\n        factor: tuple[int, int] | int = 4,\n        clim: tuple[float, float] | None = None,\n        label: bool = False,\n        take_log: bool | None = None,\n        data_source: YTDataContainer | None = None,\n        plot_args: dict[str, Any] | None = None,\n        text_args: dict[str, Any] | None = None,\n        ncont: int | None = None,  # deprecated\n    ) -> None:\n        if ncont is not None:\n            issue_deprecation_warning(\n                \"The `ncont` keyword argument is deprecated, use `levels` instead.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            levels = ncont\n        if clim is not None and not isinstance(levels, (int, np.integer)):\n            raise TypeError(f\"clim requires levels be an integer, received {levels}\")\n\n        self.levels = levels\n        self.field = field\n        self.factor = _validate_factor_tuple(factor)\n        self.clim = clim\n        self.take_log = take_log\n        self.plot_args = {\n            \"colors\": \"black\",\n            \"linestyles\": \"solid\",\n            **(plot_args or {}),\n        }\n        self.label = label\n        self.text_args = {\n            \"colors\": \"white\",\n            **(text_args or {}),\n        }\n        self.data_source = data_source\n\n    def __call__(self, plot) -> None:\n        from matplotlib.tri import LinearTriInterpolator, Triangulation\n\n        # These need to be in code_length\n        x0, x1, y0, y1 = self._physical_bounds(plot)\n\n        # These are in plot coordinates, which may not be code coordinates.\n        xx0, xx1, yy0, yy1 = self._plot_bounds(plot)\n\n        # See the note about rows/columns in the pixelizer for more information\n        # on why we choose the bounds we do\n        numPoints_x = plot.raw_image_shape[1]\n        numPoints_y = plot.raw_image_shape[0]\n\n        # Go from data->plot coordinates\n        dx, dy = self._pixel_scale(plot)\n\n        # We want xi, yi in plot coordinates\n        xi, yi = np.mgrid[\n            xx0 : xx1 : numPoints_x / (self.factor[0] * 1j),\n            yy0 : yy1 : numPoints_y / (self.factor[1] * 1j),\n        ]\n        data = self.data_source or plot.data\n\n        if plot._type_name in [\"CuttingPlane\", \"Projection\", \"Slice\"]:\n            if plot._type_name == \"CuttingPlane\":\n                x = (data[\"px\"] * dx).to(\"1\")\n                y = (data[\"py\"] * dy).to(\"1\")\n                z = data[self.field]\n            elif plot._type_name in [\"Projection\", \"Slice\"]:\n                # Makes a copy of the position fields \"px\" and \"py\" and adds the\n                # appropriate shift to the copied field.\n\n                AllX = np.zeros(data[\"px\"].size, dtype=\"bool\")\n                AllY = np.zeros(data[\"py\"].size, dtype=\"bool\")\n                XShifted = data[\"px\"].copy()\n                YShifted = data[\"py\"].copy()\n                dom_x, dom_y = plot._period\n                for shift in np.mgrid[-1:1:3j]:  # type: ignore [misc]\n                    xlim = (data[\"px\"] + shift * dom_x >= x0) & (\n                        data[\"px\"] + shift * dom_x <= x1\n                    )\n                    ylim = (data[\"py\"] + shift * dom_y >= y0) & (\n                        data[\"py\"] + shift * dom_y <= y1\n                    )\n                    XShifted[xlim] += shift * dom_x\n                    YShifted[ylim] += shift * dom_y\n                    AllX |= xlim\n                    AllY |= ylim\n\n                # At this point XShifted and YShifted are the shifted arrays of\n                # position data in data coordinates\n                wI = AllX & AllY\n\n                # This converts XShifted and YShifted into plot coordinates\n                # Note: we force conversion into \"1\" to prevent issues in case\n                # one of the length has some dimensionless factor (Mpc/h)\n                x = ((XShifted[wI] - x0) * dx).to(\"1\").ndarray_view() + xx0\n                y = ((YShifted[wI] - y0) * dy).to(\"1\").ndarray_view() + yy0\n                z = data[self.field][wI]\n\n            # Both the input and output from the triangulator are in plot\n            # coordinates\n            triangulation = Triangulation(x, y)\n            zi = LinearTriInterpolator(triangulation, z)(xi, yi)\n        elif plot._type_name == \"OffAxisProjection\":\n            zi = plot.frb[self.field][:: self.factor[0], :: self.factor[1]].transpose()\n\n        take_log: bool\n        if self.take_log is not None:\n            take_log = self.take_log\n        else:\n            field = data._determine_fields([self.field])[0]\n            take_log = plot.ds._get_field_info(field).take_log\n\n        if take_log:\n            zi = np.log10(zi)\n\n        clim: tuple[float, float] | None\n        if take_log and self.clim is not None:\n            clim = np.log10(self.clim[0]), np.log10(self.clim[1])\n        else:\n            clim = self.clim\n\n        levels: np.ndarray | int\n        if clim is not None:\n            levels = np.linspace(clim[0], clim[1], self.levels)\n        else:\n            levels = self.levels\n\n        xi, yi = self._sanitize_xy_order(plot, xi, yi)\n        cset = plot._axes.contour(xi, yi, zi, levels, **self.plot_args)\n        self._set_plot_limits(plot, (xx0, xx1, yy0, yy1))\n\n        if self.label:\n            plot._axes.clabel(cset, **self.text_args)\n\n\nclass GridBoundaryCallback(PlotCallback):\n    \"\"\"\n    Draws grids on an existing PlotWindow object.  Adds grid boundaries to a\n    plot, optionally with alpha-blending. By default, colors different levels of\n    grids with different colors going from white to black, but you can change to\n    any arbitrary colormap with cmap keyword, to all black grid edges for all\n    levels with cmap=None and edgecolors=None, or to an arbitrary single color\n    for grid edges with edgecolors='YourChosenColor' defined in any of the\n    standard ways (e.g., edgecolors='white', edgecolors='r',\n    edgecolors='#00FFFF', or edgecolor='0.3', where the last is a float in 0-1\n    scale indicating gray).  Note that setting edgecolors overrides cmap if you\n    have both set to non-None values.  Cutoff for display is at min_pix\n    wide. draw_ids puts the grid id a the corner of the grid (but its not so\n    great in projections...).  id_loc determines which corner holds the grid id.\n    One can set min and maximum level of grids to display, and\n    can change the linewidth of the displayed grids.\n    \"\"\"\n\n    _type_name = \"grids\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"cylindrical\")\n    _incompatible_plot_types = (\"OffAxisSlice\", \"OffAxisProjection\", \"Particle\")\n\n    def __init__(\n        self,\n        alpha=0.7,\n        min_pix=1,\n        min_pix_ids=20,\n        draw_ids=False,\n        id_loc=None,\n        periodic=True,\n        min_level=None,\n        max_level=None,\n        cmap=\"B-W LINEAR_r\",\n        edgecolors=None,\n        linewidth=1.0,\n    ):\n        self.alpha = alpha\n        self.min_pix = min_pix\n        self.min_pix_ids = min_pix_ids\n        self.draw_ids = draw_ids  # put grid numbers in the corner.\n        if id_loc is None:\n            self.id_loc = \"lower left\"\n        else:\n            self.id_loc = id_loc.lower()  # Make case-insensitive\n            if not self.draw_ids:\n                mylog.warning(\n                    \"Supplied id_loc but draw_ids is False. Not drawing grid ids\"\n                )\n        self.periodic = periodic\n        self.min_level = min_level\n        self.max_level = max_level\n        self.linewidth = linewidth\n        self.cmap = cmap\n        self.edgecolors = edgecolors\n\n    def __call__(self, plot):\n        if plot.data.ds.geometry == \"cylindrical\" and plot.data.ds.dimensionality == 3:\n            raise NotImplementedError(\n                \"Grid annotation is only supported for \\\n                for 2D cylindrical geometry, not 3D\"\n            )\n        from matplotlib.colors import colorConverter\n\n        x0, x1, y0, y1 = self._physical_bounds(plot)\n        xx0, xx1, yy0, yy1 = self._plot_bounds(plot)\n        dx, dy = self._pixel_scale(plot)\n        ypix, xpix = plot.raw_image_shape\n        ax = plot.data.axis\n        px_index = plot.data.ds.coordinates.x_axis[ax]\n        py_index = plot.data.ds.coordinates.y_axis[ax]\n        DW = plot.data.ds.domain_width\n        if self.periodic:\n            pxs, pys = np.mgrid[-1:1:3j, -1:1:3j]\n        else:\n            pxs, pys = np.mgrid[0:0:1j, 0:0:1j]\n        GLE, GRE, levels, block_ids = [], [], [], []\n        for block, _mask in plot.data.blocks:\n            GLE.append(block.LeftEdge.in_units(\"code_length\"))\n            GRE.append(block.RightEdge.in_units(\"code_length\"))\n            levels.append(block.Level)\n            block_ids.append(block.id)\n        if len(GLE) == 0:\n            return\n        # Retain both units and registry\n        GLE = plot.ds.arr(GLE, units=GLE[0].units)\n        GRE = plot.ds.arr(GRE, units=GRE[0].units)\n        levels = np.array(levels)\n        min_level = self.min_level or 0\n        max_level = self.max_level or levels.max()\n\n        # sort the four arrays in order of ascending level, this makes images look nicer\n        new_indices = np.argsort(levels)\n        levels = levels[new_indices]\n        GLE = GLE[new_indices]\n        GRE = GRE[new_indices]\n        block_ids = np.array(block_ids)[new_indices]\n\n        for px_off, py_off in zip(pxs.ravel(), pys.ravel(), strict=True):\n            pxo = px_off * DW[px_index]\n            pyo = py_off * DW[py_index]\n            # Note: [dx] = 1/length, [GLE] = length\n            # we force conversion into \"1\" to prevent issues if e.g. GLE is in Mpc/h\n            # where dx * GLE would have units 1/h rather than being truly dimensionless\n            left_edge_x = np.array((((GLE[:, px_index] + pxo - x0) * dx) + xx0).to(\"1\"))\n            left_edge_y = np.array((((GLE[:, py_index] + pyo - y0) * dy) + yy0).to(\"1\"))\n            right_edge_x = np.array(\n                (((GRE[:, px_index] + pxo - x0) * dx) + xx0).to(\"1\")\n            )\n            right_edge_y = np.array(\n                (((GRE[:, py_index] + pyo - y0) * dy) + yy0).to(\"1\")\n            )\n            xwidth = xpix * (right_edge_x - left_edge_x) / (xx1 - xx0)\n            ywidth = ypix * (right_edge_y - left_edge_y) / (yy1 - yy0)\n            visible = np.logical_and(\n                np.logical_and(xwidth > self.min_pix, ywidth > self.min_pix),\n                np.logical_and(levels >= min_level, levels <= max_level),\n            )\n\n            # Grids can either be set by edgecolors OR a colormap.\n            if self.edgecolors is not None:\n                edgecolors = colorConverter.to_rgba(self.edgecolors, alpha=self.alpha)\n            else:  # use colormap if not explicitly overridden by edgecolors\n                if self.cmap is not None:\n                    color_bounds = [0, max_level]\n                    edgecolors = (\n                        apply_colormap(\n                            levels[visible] * 1.0,\n                            color_bounds=color_bounds,\n                            cmap_name=self.cmap,\n                        )[0, :, :]\n                        * 1.0\n                        / 255.0\n                    )\n                    edgecolors[:, 3] = self.alpha\n                else:\n                    edgecolors = (0.0, 0.0, 0.0, self.alpha)\n\n            if visible.nonzero()[0].size == 0:\n                continue\n\n            edge_x = (left_edge_x, left_edge_x, right_edge_x, right_edge_x)\n            edge_y = (left_edge_y, right_edge_y, right_edge_y, left_edge_y)\n            edge_x, edge_y = self._sanitize_xy_order(plot, edge_x, edge_y)\n            verts = np.array([edge_x, edge_y])\n            verts = verts.transpose()[visible, :, :]\n            grid_collection = matplotlib.collections.PolyCollection(\n                verts,\n                facecolors=\"none\",\n                edgecolors=edgecolors,\n                linewidth=self.linewidth,\n            )\n            plot._axes.add_collection(grid_collection)\n\n            visible_ids = np.logical_and(\n                np.logical_and(xwidth > self.min_pix_ids, ywidth > self.min_pix_ids),\n                np.logical_and(levels >= min_level, levels <= max_level),\n            )\n\n            if self.draw_ids:\n                plot_ids = np.where(visible_ids)[0]\n                x = np.empty(plot_ids.size)\n                y = np.empty(plot_ids.size)\n                for i, n in enumerate(plot_ids):\n                    if self.id_loc == \"lower left\":\n                        x[i] = left_edge_x[n] + (2 * (xx1 - xx0) / xpix)\n                        y[i] = left_edge_y[n] + (2 * (yy1 - yy0) / ypix)\n                    elif self.id_loc == \"lower right\":\n                        x[i] = right_edge_x[n] - (\n                            (10 * len(str(block_ids[i])) - 2) * (xx1 - xx0) / xpix\n                        )\n                        y[i] = left_edge_y[n] + (2 * (yy1 - yy0) / ypix)\n                    elif self.id_loc == \"upper left\":\n                        x[i] = left_edge_x[n] + (2 * (xx1 - xx0) / xpix)\n                        y[i] = right_edge_y[n] - (12 * (yy1 - yy0) / ypix)\n                    elif self.id_loc == \"upper right\":\n                        x[i] = right_edge_x[n] - (\n                            (10 * len(str(block_ids[i])) - 2) * (xx1 - xx0) / xpix\n                        )\n                        y[i] = right_edge_y[n] - (12 * (yy1 - yy0) / ypix)\n                    else:\n                        raise RuntimeError(\n                            f\"Unrecognized id_loc value ({self.id_loc!r}). \"\n                            \"Allowed values are 'lower left', lower right', \"\n                            \"'upper left', and 'upper right'.\"\n                        )\n                    xi, yi = self._sanitize_xy_order(plot, x[i], y[i])\n                    plot._axes.text(xi, yi, str(block_ids[n]), clip_on=True)\n\n\n# when type-checking with MPL >= 3.8, use\n# from matplotlib.typing import ColorType\n_ColorType = Any\n\n\nclass StreamlineCallback(PlotCallback):\n    \"\"\"\n    Plot streamlines using matplotlib.axes.Axes.streamplot\n\n    Arguments\n    ---------\n\n    field_x: field key\n        The \"velocity\" analoguous field along the horizontal direction.\n    field_y: field key\n        The \"velocity\" analoguous field along the vertical direction.\n\n    linewidth: float, or field key (default: 1.0)\n        A constant scalar will be passed directly to matplotlib.axes.Axes.streamplot\n        A field key will be first interpreted by yt and produce the adequate 2D array.\n        Data fields are normalized by their maximum value, so the maximal linewidth\n        is 1 by default. See `linewidth_upscaling` for fine tuning.\n        Note that the absolute value is taken in all cases.\n\n    linewidth_upscaling: float (default: 1.0)\n        A constant multiplicative factor applied to linewidth.\n        Final linewidth is obtained as:\n        linewidth_upscaling * abs(linewidth) / max(abs(linewidth))\n\n    color: a color identifier, or a field key (default: matplotlib.rcParams['line.color'])\n        A constant color identifier will be passed directly to matplotlib.axes.Axes.streamplot\n        A field key will be first interpreted by yt and produce the adequate 2D array.\n        See https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.streamplot.html\n        for how to customize color mapping using `cmap` and `norm` arguments.\n\n    color_threshold: float or unyt_quantity (default: -inf)\n        Regions where the field used for color is lower than this threshold will be masked.\n        Only used if color is a field key.\n\n    factor: int, or tuple[int, int] (default: 16)\n        Fields are downed-sampled by this factor with respect to the background image\n        buffer size. A single integer factor will be used for both direction, but a tuple\n        of 2 integers can be passed to set x and y downsampling independently.\n\n    **kwargs: any additional keyword arguments will be passed\n        directly to matplotlib.axes.Axes.streamplot\n    \"\"\"\n\n    _type_name = \"streamlines\"\n    _supported_geometries = (\n        \"cartesian\",\n        \"spectral_cube\",\n        \"polar\",\n        \"cylindrical\",\n        \"spherical\",\n    )\n    _incompatible_plot_types = (\"OffAxisProjection\", \"Particle\")\n\n    def __init__(\n        self,\n        field_x: AnyFieldKey,\n        field_y: AnyFieldKey,\n        *,\n        linewidth: float | AnyFieldKey = 1.0,\n        linewidth_upscaling: float = 1.0,\n        color: _ColorType | FieldKey | None = None,\n        color_threshold: float | unyt_quantity = float(\"-inf\"),\n        factor: tuple[int, int] | int = 16,\n        field_color=None,  # deprecated\n        display_threshold=None,  # deprecated\n        plot_args=None,  # deprecated\n        **kwargs,\n    ):\n        self.field_x = field_x\n        self.field_y = field_y\n        if color is not None and field_color is not None:\n            raise TypeError(\n                \"`color` and `field_color` keyword arguments \"\n                \"cannot be set at the same time.\"\n            )\n        elif field_color is not None:\n            issue_deprecation_warning(\n                \"The `field_color` keyword argument is deprecated. \"\n                \"Use `color` instead.\",\n                since=\"4.3\",\n                stacklevel=5,\n            )\n            self._color = field_color\n        else:\n            self._color = color\n\n        if color_threshold is not None and display_threshold is not None:\n            raise TypeError(\n                \"`color_threshold` and `display_threshold` keyword arguments \"\n                \"cannot be set at the same time.\"\n            )\n        elif display_threshold is not None:\n            issue_deprecation_warning(\n                \"The `display_threshold` keyword argument is deprecated. \"\n                \"Use `color_threshold` instead.\",\n                since=\"4.3\",\n                stacklevel=5,\n            )\n            self._color_threshold = display_threshold\n        else:\n            self._color_threshold = color_threshold\n\n        self._linewidth = linewidth\n        self._linewidth_upscaling = linewidth_upscaling\n        self.factor = _validate_factor_tuple(factor)\n\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            plot_args.update(kwargs)\n        else:\n            plot_args = kwargs\n\n        self.plot_args = plot_args\n\n    def __call__(self, plot) -> None:\n        xx0, xx1, yy0, yy1 = self._plot_bounds(plot)\n\n        # We are feeding this size into the pixelizer, where it will properly\n        # set it in reverse order\n        nx = plot.raw_image_shape[1] // self.factor[0]\n        ny = plot.raw_image_shape[0] // self.factor[1]\n\n        def pixelize(field):\n            retv = plot.data.ds.coordinates.pixelize(\n                plot.data.axis,\n                plot.data,\n                field=field,\n                bounds=self._physical_bounds(plot),\n                size=(nx, ny),\n            )\n            if plot._swap_axes:\n                return retv.transpose()\n            else:\n                return retv\n\n        def is_field_key(val) -> TypeGuard[AnyFieldKey]:\n            if not is_sequence(val):\n                return False\n            try:\n                plot.data._determine_fields(val)\n            except (YTFieldNotFound, YTFieldTypeNotFound):\n                return False\n            else:\n                return True\n\n        pixX = pixelize(self.field_x)\n        pixY = pixelize(self.field_y)\n\n        if isinstance(self._linewidth, (int, float)):\n            linewidth = self._linewidth_upscaling * self._linewidth\n        elif is_field_key(self._linewidth):\n            linewidth = pixelize(self._linewidth)\n            linewidth *= self._linewidth_upscaling / np.abs(linewidth).max()\n        else:\n            raise TypeError(\n                f\"annotate_streamlines received linewidth={self._linewidth!r}, \"\n                f\"with type {type(self._linewidth)}. Expected a float or a field key.\"\n            )\n\n        if is_field_key(self._color):\n            color = pixelize(self._color)\n            linewidth *= color > self._color_threshold\n        else:\n            if (_cmap := self.plot_args.get(\"cmap\")) is not None:\n                warnings.warn(\n                    f\"annotate_streamlines received color={self._color!r}, \"\n                    \"which wasn't recognized as as field key. \"\n                    \"It is assumed to be a fixed color identifier. \"\n                    f\"Also received cmap={_cmap!r}, which will be ignored.\",\n                    stacklevel=5,\n                )\n            color = self._color\n\n        X, Y = (\n            np.linspace(xx0, xx1, nx, endpoint=True),\n            np.linspace(yy0, yy1, ny, endpoint=True),\n        )\n        X, Y, pixX, pixY = self._sanitize_xy_order(plot, X, Y, pixX, pixY)\n        if plot._swap_axes:\n            # need an additional transpose here for streamline tracing\n            X = X.transpose()\n            Y = Y.transpose()\n        streamplot_args = {\n            \"x\": X,\n            \"y\": Y,\n            \"u\": pixX,\n            \"v\": pixY,\n            \"color\": color,\n            \"linewidth\": linewidth,\n            **self.plot_args,\n        }\n        plot._axes.streamplot(**streamplot_args)\n        self._set_plot_limits(plot, (xx0, xx1, yy0, yy1))\n\n\nclass LinePlotCallback(PlotCallback):\n    \"\"\"\n    Overplot a line with endpoints at p1 and p2.  p1 and p2\n    should be 2D or 3D coordinates consistent with the coordinate\n    system denoted in the \"coord_system\" keyword.\n\n    Parameters\n    ----------\n    p1, p2 : 2- or 3-element tuples, lists, or arrays\n        These are the coordinates of the endpoints of the line.\n\n    coord_system : string, optional\n        This string defines the coordinate system of the coordinates p1 and p2.\n        Valid coordinates are:\n\n            \"data\" -- the 3D dataset coordinates\n\n            \"plot\" -- the 2D coordinates defined by the actual plot limits\n\n            \"axis\" -- the MPL axis coordinates: (0,0) is lower left; (1,1) is\n                      upper right\n\n            \"figure\" -- the MPL figure coordinates: (0,0) is lower left, (1,1)\n                        is upper right\n\n    plot_args : dictionary, optional\n        This dictionary is passed to the MPL plot function for generating\n        the line.  By default, it is: {'color':'white', 'linewidth':2}\n\n    Examples\n    --------\n\n    >>> # Overplot a diagonal white line from the lower left corner to upper\n    >>> # right corner\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_line([0, 0], [1, 1], coord_system=\"axis\")\n    >>> s.save()\n\n    >>> # Overplot a red dashed line from data coordinate (0.1, 0.2, 0.3) to\n    >>> # (0.5, 0.6, 0.7)\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_line(\n    ...     [0.1, 0.2, 0.3],\n    ...     [0.5, 0.6, 0.7],\n    ...     coord_system=\"data\",\n    ...     color=\"red\",\n    ...     linestyles=\"--\",\n    ... )\n    >>> s.save()\n\n    \"\"\"\n\n    _type_name = \"line\"\n    _supported_geometries: tuple[str, ...] = (\n        \"cartesian\",\n        \"spectral_cube\",\n        \"polar\",\n        \"cylindrical\",\n    )\n\n    def __init__(\n        self,\n        p1,\n        p2,\n        *,\n        coord_system=\"data\",\n        plot_args: dict[str, Any] | None = None,\n        **kwargs,\n    ):\n        self.p1 = p1\n        self.p2 = p2\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n\n        self.plot_args = {\n            \"color\": \"white\",\n            \"linewidth\": 2,\n            **(plot_args or {}),\n            **kwargs,\n        }\n        self.coord_system = coord_system\n        self.transform = None\n\n    def __call__(self, plot):\n        p1 = self._sanitize_coord_system(plot, self.p1, coord_system=self.coord_system)\n        p2 = self._sanitize_coord_system(plot, self.p2, coord_system=self.coord_system)\n        start_pt, end_pt = [p1[0], p2[0]], [p1[1], p2[1]]\n        if plot._swap_axes:\n            start_pt, end_pt = [p2[0], p1[0]], [p2[1], p1[1]]\n        plot._axes.plot(start_pt, end_pt, transform=self.transform, **self.plot_args)\n        self._set_plot_limits(plot)\n\n\nclass CuttingQuiverCallback(BaseQuiverCallback):\n    \"\"\"\n    Get a quiver plot on top of a cutting plane, using *field_x* and\n    *field_y*, skipping every *factor* datapoint in the discretization.\n    *scale* is the data units per arrow length unit using *scale_units*\n    and *plot_args* allows you to pass in matplotlib arguments (see\n    matplotlib.axes.Axes.quiver for more info). if *normalize* is True,\n    the fields will be scaled by their local (in-plane) length, allowing\n    morphological features to be more clearly seen for fields with\n    substantial variation in field strength.\n    \"\"\"\n\n    _type_name = \"cquiver\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\")\n\n    def _get_quiver_data(self, plot, bounds: tuple, nx: int, ny: int):\n        # calls the pixelizer, returns pixX, pixY, pixC arrays\n        indices = np.argsort(plot.data[\"index\", \"dx\"])[::-1].astype(np.int_)\n\n        pixX = np.zeros((ny, nx), dtype=\"f8\")\n        pixY = np.zeros((ny, nx), dtype=\"f8\")\n        pixelize_off_axis_cartesian(\n            pixX,\n            plot.data[\"index\", \"x\"].to(\"code_length\"),\n            plot.data[\"index\", \"y\"].to(\"code_length\"),\n            plot.data[\"index\", \"z\"].to(\"code_length\"),\n            plot.data[\"px\"],\n            plot.data[\"py\"],\n            plot.data[\"pdx\"],\n            plot.data[\"pdy\"],\n            plot.data[\"pdz\"],\n            plot.data.center,\n            plot.data._inv_mat,\n            indices,\n            plot.data[self.field_x],\n            bounds,\n        )\n        pixelize_off_axis_cartesian(\n            pixY,\n            plot.data[\"index\", \"x\"].to(\"code_length\"),\n            plot.data[\"index\", \"y\"].to(\"code_length\"),\n            plot.data[\"index\", \"z\"].to(\"code_length\"),\n            plot.data[\"px\"],\n            plot.data[\"py\"],\n            plot.data[\"pdx\"],\n            plot.data[\"pdy\"],\n            plot.data[\"pdz\"],\n            plot.data.center,\n            plot.data._inv_mat,\n            indices,\n            plot.data[self.field_y],\n            bounds,\n        )\n\n        if self.field_c is None:\n            pixC = None\n        else:\n            pixC = np.zeros((ny, nx), dtype=\"f8\")\n            pixelize_off_axis_cartesian(\n                pixC,\n                plot.data[\"index\", \"x\"].to(\"code_length\"),\n                plot.data[\"index\", \"y\"].to(\"code_length\"),\n                plot.data[\"index\", \"z\"].to(\"code_length\"),\n                plot.data[\"px\"],\n                plot.data[\"py\"],\n                plot.data[\"pdx\"],\n                plot.data[\"pdy\"],\n                plot.data[\"pdz\"],\n                plot.data.center,\n                plot.data._inv_mat,\n                indices,\n                plot.data[self.field_c],\n                bounds,\n            )\n\n        return pixX, pixY, pixC\n\n\nclass ClumpContourCallback(PlotCallback):\n    \"\"\"\n    Take a list of *clumps* and plot them as a set of contours.\n    \"\"\"\n\n    _type_name = \"clumps\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"cylindrical\")\n    _incompatible_plot_types = (\"OffAxisSlice\", \"OffAxisProjection\", \"Particle\")\n\n    def __init__(self, clumps, *, plot_args=None, **kwargs):\n        self.clumps = clumps\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            plot_args.update(kwargs)\n        else:\n            plot_args = kwargs\n        if \"color\" in plot_args:\n            plot_args[\"colors\"] = plot_args.pop(\"color\")\n        self.plot_args = plot_args\n\n    def __call__(self, plot):\n        bounds = self._physical_bounds(plot)\n        extent = self._plot_bounds(plot)\n\n        ax = plot.data.axis\n        px_index = plot.data.ds.coordinates.x_axis[ax]\n        py_index = plot.data.ds.coordinates.y_axis[ax]\n\n        xf = plot.data.ds.coordinates.axis_name[px_index]\n        yf = plot.data.ds.coordinates.axis_name[py_index]\n        dxf = f\"d{xf}\"\n        dyf = f\"d{yf}\"\n\n        ny, nx = plot.raw_image_shape\n        buff = np.zeros((nx, ny), dtype=\"float64\")\n        for i, clump in enumerate(reversed(self.clumps)):\n            mylog.info(\"Pixelizing contour %s\", i)\n\n            if isinstance(clump, Clump):\n                ftype = \"index\"\n            elif isinstance(clump, YTClumpContainer):\n                ftype = \"grid\"\n            else:\n                raise RuntimeError(\n                    f\"Unknown field type for object of type {type(clump)}.\"\n                )\n\n            xf_copy = clump[ftype, xf].copy().in_units(\"code_length\")\n            yf_copy = clump[ftype, yf].copy().in_units(\"code_length\")\n\n            temp = np.zeros((ny, nx), dtype=\"f8\")\n            pixelize_cartesian(\n                temp,\n                xf_copy,\n                yf_copy,\n                clump[ftype, dxf].in_units(\"code_length\") / 2.0,\n                clump[ftype, dyf].in_units(\"code_length\") / 2.0,\n                clump[ftype, dxf].d * 0.0 + i + 1,  # inits inside Pixelize\n                bounds,\n                0,\n            )\n            buff = np.maximum(temp, buff)\n        if plot._swap_axes:\n            buff = buff.transpose()\n            extent = (extent[2], extent[3], extent[0], extent[1])\n        self.rv = plot._axes.contour(\n            buff, np.unique(buff), extent=extent, **self.plot_args\n        )\n\n\nclass ArrowCallback(PlotCallback):\n    \"\"\"\n    Overplot arrow(s) pointing at position(s) for highlighting specific\n    features.  By default, arrow points from lower left to the designated\n    position \"pos\" with arrow length \"length\".  Alternatively, if\n    \"starting_pos\" is set, arrow will stretch from \"starting_pos\" to \"pos\"\n    and \"length\" will be disregarded.\n\n    \"coord_system\" keyword refers to positions set in \"pos\" arg and\n    \"starting_pos\" keyword, which by default are in data coordinates.\n\n    \"length\", \"width\", \"head_length\", and \"head_width\" keywords for the arrow\n    are all in axis units, ie relative to the size of the plot axes as 1,\n    even if the position of the arrow is set relative to another coordinate\n    system.\n\n    Parameters\n    ----------\n    pos : array-like\n        These are the coordinates where the marker(s) will be overplotted\n        Either as [x,y,z] or as [[x1,x2,...],[y1,y2,...],[z1,z2,...]]\n\n    length : float, optional\n        The length, in axis units, of the arrow.\n        Default: 0.03\n\n    width : float, optional\n        The width, in axis units, of the tail line of the arrow.\n        Default: 0.003\n\n    head_length : float, optional\n        The length, in axis units, of the head of the arrow.  If set\n        to None, use 1.5*head_width\n        Default: None\n\n    head_width : float, optional\n        The width, in axis units, of the head of the arrow.\n        Default: 0.02\n\n    starting_pos : 2- or 3-element tuple, list, or array, optional\n        These are the coordinates from which the arrow starts towards its\n        point.  Not compatible with 'length' kwarg.\n\n    coord_system : string, optional\n        This string defines the coordinate system of the coordinates of pos\n        Valid coordinates are:\n\n            \"data\" -- the 3D dataset coordinates\n\n            \"plot\" -- the 2D coordinates defined by the actual plot limits\n\n            \"axis\" -- the MPL axis coordinates: (0,0) is lower left; (1,1) is\n                      upper right\n\n            \"figure\" -- the MPL figure coordinates: (0,0) is lower left, (1,1)\n                        is upper right\n\n    plot_args : dictionary, optional\n        This dictionary is passed to the MPL arrow function for generating\n        the arrow.  By default, it is: {'color':'white'}\n\n    Examples\n    --------\n\n    >>> # Overplot an arrow pointing to feature at data coord: (0.2, 0.3, 0.4)\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_arrow([0.2, 0.3, 0.4])\n    >>> s.save()\n\n    >>> # Overplot a red arrow with longer length pointing to plot coordinate\n    >>> # (0.1, -0.1)\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_arrow(\n    ...     [0.1, -0.1], length=0.06, coord_system=\"plot\", color=\"red\"\n    ... )\n    >>> s.save()\n\n    \"\"\"\n\n    _type_name = \"arrow\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"cylindrical\")\n\n    def __init__(\n        self,\n        pos,\n        *,\n        length=0.03,\n        width=0.0001,\n        head_width=0.01,\n        head_length=0.01,\n        starting_pos=None,\n        coord_system=\"data\",\n        plot_args: dict[str, Any] | None = None,  # deprecated\n        **kwargs,\n    ):\n        self.pos = pos\n        self.length = length\n        self.width = width\n        self.head_width = head_width\n        self.head_length = head_length\n        self.starting_pos = starting_pos\n        self.coord_system = coord_system\n        self.transform = None\n\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n        self.plot_args = {\n            \"color\": \"white\",\n            **(plot_args or {}),\n            **kwargs,\n        }\n\n    def __call__(self, plot):\n        x, y = self._sanitize_coord_system(\n            plot, self.pos, coord_system=self.coord_system\n        )\n        xx0, xx1, yy0, yy1 = self._plot_bounds(plot)\n        # normalize all of the kwarg lengths to the plot size\n        plot_diag = ((yy1 - yy0) ** 2 + (xx1 - xx0) ** 2) ** (0.5)\n        length = self.length * plot_diag\n        width = self.width * plot_diag\n        head_width = self.head_width * plot_diag\n        head_length = None\n        if self.head_length is not None:\n            head_length = self.head_length * plot_diag\n\n        if self.starting_pos is not None:\n            start_x, start_y = self._sanitize_coord_system(\n                plot, self.starting_pos, coord_system=self.coord_system\n            )\n            dx = x - start_x\n            dy = y - start_y\n        else:\n            dx = (xx1 - xx0) * 2 ** (0.5) * length\n            dy = (yy1 - yy0) * 2 ** (0.5) * length\n        # If the arrow is 0 length\n        if dx == dy == 0:\n            warnings.warn(\"The arrow has zero length. Not annotating.\", stacklevel=2)\n            return\n\n        x, y, dx, dy = self._sanitize_xy_order(plot, x, y, dx, dy)\n        try:\n            plot._axes.arrow(\n                x - dx,\n                y - dy,\n                dx,\n                dy,\n                width=width,\n                head_width=head_width,\n                head_length=head_length,\n                transform=self.transform,\n                length_includes_head=True,\n                **self.plot_args,\n            )\n        except ValueError:\n            for i in range(len(x)):\n                plot._axes.arrow(\n                    x[i] - dx,\n                    y[i] - dy,\n                    dx,\n                    dy,\n                    width=width,\n                    head_width=head_width,\n                    head_length=head_length,\n                    transform=self.transform,\n                    length_includes_head=True,\n                    **self.plot_args,\n                )\n        self._set_plot_limits(plot, (xx0, xx1, yy0, yy1))\n\n\nclass MarkerAnnotateCallback(PlotCallback):\n    \"\"\"\n    Overplot marker(s) at a position(s) for highlighting specific features.\n\n    Parameters\n    ----------\n    pos : array-like\n        These are the coordinates where the marker(s) will be overplotted\n        Either as [x,y,z] or as [[x1,x2,...],[y1,y2,...],[z1,z2,...]]\n\n    marker : string, optional\n        The shape of the marker to be passed to the MPL scatter function.\n        By default, it is 'x', but other acceptable values are: '.', 'o', 'v',\n        '^', 's', 'p' '*', etc.  See matplotlib.markers for more information.\n\n    coord_system : string, optional\n        This string defines the coordinate system of the coordinates of pos\n        Valid coordinates are:\n\n            \"data\" -- the 3D dataset coordinates\n\n            \"plot\" -- the 2D coordinates defined by the actual plot limits\n\n            \"axis\" -- the MPL axis coordinates: (0,0) is lower left; (1,1) is\n                      upper right\n\n            \"figure\" -- the MPL figure coordinates: (0,0) is lower left, (1,1)\n                        is upper right\n\n    plot_args : dictionary, optional\n        This dictionary is passed to the MPL scatter function for generating\n        the marker.  By default, it is: {'color':'white', 's':50}\n\n    Examples\n    --------\n\n    >>> # Overplot a white X on a feature at data location (0.5, 0.5, 0.5)\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_marker([0.4, 0.5, 0.6])\n    >>> s.save()\n\n    >>> # Overplot a big yellow circle at axis location (0.1, 0.2)\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_marker(\n    ...     [0.1, 0.2],\n    ...     marker=\"o\",\n    ...     coord_system=\"axis\",\n    ...     color=\"yellow\",\n    ...     s=200,\n    ... )\n    >>> s.save()\n\n    \"\"\"\n\n    _type_name = \"marker\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"polar\", \"cylindrical\")\n\n    def __init__(\n        self, pos, marker=\"x\", *, coord_system=\"data\", plot_args=None, **kwargs\n    ):\n        self.pos = pos\n        self.marker = marker\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n        self.plot_args = {\n            \"color\": \"white\",\n            \"s\": 50,\n            **(plot_args or {}),\n            **kwargs,\n        }\n        self.coord_system = coord_system\n        self.transform = None\n\n    def __call__(self, plot):\n        x, y = self._sanitize_coord_system(\n            plot, self.pos, coord_system=self.coord_system\n        )\n        x, y = self._sanitize_xy_order(plot, x, y)\n        plot._axes.scatter(\n            x, y, marker=self.marker, transform=self.transform, **self.plot_args\n        )\n        self._set_plot_limits(plot)\n\n\nclass SphereCallback(PlotCallback):\n    \"\"\"\n    Overplot a circle with designated center and radius with optional text.\n\n    Parameters\n    ----------\n    center : 2- or 3-element tuple, list, or array\n        These are the coordinates where the circle will be overplotted\n\n    radius : YTArray, float, or (1, ('kpc')) style tuple\n        The radius of the circle in code coordinates\n\n    circle_args : dict, optional\n        This dictionary is passed to the MPL circle object. By default,\n        {'color':'white'}\n\n    coord_system : string, optional\n        This string defines the coordinate system of the coordinates of pos\n        Valid coordinates are:\n\n            \"data\" -- the 3D dataset coordinates\n\n            \"plot\" -- the 2D coordinates defined by the actual plot limits\n\n            \"axis\" -- the MPL axis coordinates: (0,0) is lower left; (1,1) is\n                      upper right\n\n            \"figure\" -- the MPL figure coordinates: (0,0) is lower left, (1,1)\n                        is upper right\n\n    text : string, optional\n        Optional text to include next to the circle.\n\n    text_args : dictionary, optional\n        This dictionary is passed to the MPL text function. By default,\n        it is: {'color':'white'}\n\n    Examples\n    --------\n\n    >>> # Overplot a white circle of radius 100 kpc over the central galaxy\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_sphere([0.5, 0.5, 0.5], radius=(100, \"kpc\"))\n    >>> s.save()\n\n    \"\"\"\n\n    _type_name = \"sphere\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"polar\", \"cylindrical\")\n\n    def __init__(\n        self,\n        center,\n        radius,\n        *,\n        coord_system=\"data\",\n        text=None,\n        circle_args=None,\n        text_args=None,\n    ):\n        self.center = center\n        self.radius = radius\n        self.circle_args = {\n            \"color\": \"white\",\n            \"fill\": False,\n            **(circle_args or {}),\n        }\n        self.text = text\n        self.text_args = {\n            \"color\": \"white\",\n            **(text_args or {}),\n        }\n        self.coord_system = coord_system\n        self.transform = None\n\n    def __call__(self, plot):\n        from matplotlib.patches import Circle\n\n        if is_sequence(self.radius):\n            self.radius = plot.data.ds.quan(self.radius[0], self.radius[1])\n            self.radius = self.radius.in_units(plot.xlim[0].units)\n\n        if isinstance(self.radius, YTQuantity):\n            if isinstance(self.center, YTArray):\n                units = self.center.units\n            else:\n                units = \"code_length\"\n            self.radius = self.radius.to(units)\n\n        if not hasattr(self.radius, \"units\"):\n            self.radius = plot.data.ds.quan(self.radius, \"code_length\")\n\n        if not hasattr(self.center, \"units\"):\n            self.center = plot.data.ds.arr(self.center, \"code_length\")\n\n        # This assures the radius has the appropriate size in\n        # the different coordinate systems, since one cannot simply\n        # apply a different transform for a length in the same way\n        # you can for a coordinate.\n        if self.coord_system == \"data\" or self.coord_system == \"plot\":\n            # Note: we force conversion into \"1\" to prevent issues in case\n            # one of the length has some dimensionless factor (Mpc/h)\n            scaled_radius = (self.radius * self._pixel_scale(plot)[0]).to(\"1\")\n        else:\n            scaled_radius = self.radius / (plot.xlim[1] - plot.xlim[0])\n\n        x, y = self._sanitize_coord_system(\n            plot, self.center, coord_system=self.coord_system\n        )\n\n        x, y = self._sanitize_xy_order(plot, x, y)\n        cir = Circle(\n            (x, y), scaled_radius.v, transform=self.transform, **self.circle_args\n        )\n\n        plot._axes.add_patch(cir)\n        if self.text is not None:\n            label = plot._axes.text(\n                x, y, self.text, transform=self.transform, **self.text_args\n            )\n            self._set_font_properties(plot, [label], **self.text_args)\n\n        self._set_plot_limits(plot)\n\n\nclass TextLabelCallback(PlotCallback):\n    \"\"\"\n    Overplot text on the plot at a specified position. If you desire an inset\n    box around your text, set one with the inset_box_args dictionary\n    keyword.\n\n    Parameters\n    ----------\n    pos : 2- or 3-element tuple, list, or array\n        These are the coordinates where the text will be overplotted\n\n    text : string\n        The text you wish to include\n\n    coord_system : string, optional\n        This string defines the coordinate system of the coordinates of pos\n        Valid coordinates are:\n\n            \"data\" -- the 3D dataset coordinates\n\n            \"plot\" -- the 2D coordinates defined by the actual plot limits\n\n            \"axis\" -- the MPL axis coordinates: (0,0) is lower left; (1,1) is\n                      upper right\n\n            \"figure\" -- the MPL figure coordinates: (0,0) is lower left, (1,1)\n                        is upper right\n\n    text_args : dictionary, optional\n        This dictionary is passed to the MPL text function for generating\n        the text.  By default, it is: {'color':'white'} and uses the defaults\n        for the other fonts in the image.\n\n    inset_box_args : dictionary, optional\n        A dictionary of any arbitrary parameters to be passed to the Matplotlib\n        FancyBboxPatch object as the inset box around the text.  Default: {}\n\n    Examples\n    --------\n\n    >>> # Overplot white text at data location [0.55, 0.7, 0.4]\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_text([0.55, 0.7, 0.4], \"Here is a galaxy\")\n    >>> s.save()\n\n    >>> # Overplot yellow text at axis location [0.2, 0.8] with\n    >>> # a shaded inset box\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_text(\n    ...     [0.2, 0.8],\n    ...     \"Here is a galaxy\",\n    ...     coord_system=\"axis\",\n    ...     text_args={\"color\": \"yellow\"},\n    ...     inset_box_args={\n    ...         \"boxstyle\": \"square,pad=0.3\",\n    ...         \"facecolor\": \"black\",\n    ...         \"linewidth\": 3,\n    ...         \"edgecolor\": \"white\",\n    ...         \"alpha\": 0.5,\n    ...     },\n    ... )\n    >>> s.save()\n    \"\"\"\n\n    _type_name = \"text\"\n    _supported_geometries: tuple[str, ...] = (\n        \"cartesian\",\n        \"spectral_cube\",\n        \"polar\",\n        \"cylindrical\",\n    )\n\n    def __init__(\n        self,\n        pos,\n        text,\n        *,\n        coord_system=\"data\",\n        text_args=None,\n        inset_box_args=None,\n    ):\n        self.pos = pos\n        self.text = text\n        self.text_args = {\n            \"color\": \"white\",\n            **(text_args or {}),\n        }\n        self.inset_box_args = inset_box_args\n        self.coord_system = coord_system\n        self.transform = None\n\n    def __call__(self, plot):\n        kwargs = self.text_args.copy()\n        x, y = self._sanitize_coord_system(\n            plot, self.pos, coord_system=self.coord_system\n        )\n\n        # Set the font properties of text from this callback to be\n        # consistent with other text labels in this figure\n        if self.inset_box_args is not None:\n            kwargs[\"bbox\"] = self.inset_box_args\n        x, y = self._sanitize_xy_order(plot, x, y)\n        label = plot._axes.text(x, y, self.text, transform=self.transform, **kwargs)\n        self._set_font_properties(plot, [label], **kwargs)\n        self._set_plot_limits(plot)\n\n\nclass ParticleCallback(PlotCallback):\n    \"\"\"\n    Adds particle positions, based on a thick slab along *axis* with a\n    *width* along the line of sight.  *p_size* controls the number of\n    pixels per particle, and *col* governs the color.  *ptype* will\n    restrict plotted particles to only those that are of a given type.\n    *alpha* determines the opacity of the marker symbol used in the scatter.\n    An alternate data source can be specified with *data_source*, but by\n    default the plot's data source will be queried.\n    \"\"\"\n\n    _type_name = \"particles\"\n    region = None\n    _descriptor = None\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"cylindrical\")\n    _incompatible_plot_types = (\"OffAxisSlice\", \"OffAxisProjection\")\n\n    def __init__(\n        self,\n        width,\n        p_size=1.0,\n        col=\"k\",\n        marker=\"o\",\n        stride=1,\n        ptype=\"all\",\n        alpha=1.0,\n        data_source=None,\n    ):\n        self.width = width\n        self.p_size = p_size\n        self.color = col\n        self.marker = marker\n        self.stride = stride\n        self.ptype = ptype\n        self.alpha = alpha\n        self.data_source = data_source\n\n    def __call__(self, plot):\n        data = plot.data\n        if is_sequence(self.width):\n            validate_width_tuple(self.width)\n            self.width = plot.data.ds.quan(self.width[0], self.width[1])\n        elif isinstance(self.width, YTQuantity):\n            self.width = plot.data.ds.quan(self.width.value, self.width.units)\n        else:\n            self.width = plot.data.ds.quan(self.width, \"code_length\")\n        # we construct a rectangular prism\n        x0, x1, y0, y1 = self._physical_bounds(plot)\n        if isinstance(self.data_source, YTCutRegion):\n            mylog.warning(\n                \"Parameter 'width' is ignored in annotate_particles if the \"\n                \"data_source is a cut_region. \"\n                \"See https://github.com/yt-project/yt/issues/1933 for further details.\"\n            )\n            self.region = self.data_source\n        else:\n            self.region = self._get_region((x0, x1), (y0, y1), plot.data.axis, data)\n        ax = data.axis\n        xax = plot.data.ds.coordinates.x_axis[ax]\n        yax = plot.data.ds.coordinates.y_axis[ax]\n        axis_names = plot.data.ds.coordinates.axis_name\n        field_x = f\"particle_position_{axis_names[xax]}\"\n        field_y = f\"particle_position_{axis_names[yax]}\"\n        pt = self.ptype\n        self.periodic_x = plot.data.ds.periodicity[xax]\n        self.periodic_y = plot.data.ds.periodicity[yax]\n        self.LE = plot.data.ds.domain_left_edge[xax], plot.data.ds.domain_left_edge[yax]\n        self.RE = (\n            plot.data.ds.domain_right_edge[xax],\n            plot.data.ds.domain_right_edge[yax],\n        )\n        period_x = plot.data.ds.domain_width[xax]\n        period_y = plot.data.ds.domain_width[yax]\n        particle_x, particle_y = self._enforce_periodic(\n            self.region[pt, field_x],\n            self.region[pt, field_y],\n            x0,\n            x1,\n            period_x,\n            y0,\n            y1,\n            period_y,\n        )\n        gg = (\n            (particle_x >= x0)\n            & (particle_x <= x1)\n            & (particle_y >= y0)\n            & (particle_y <= y1)\n        )\n        px, py = [particle_x[gg][:: self.stride], particle_y[gg][:: self.stride]]\n        px, py = self._convert_to_plot(plot, [px, py])\n        px, py = self._sanitize_xy_order(plot, px, py)\n        plot._axes.scatter(\n            px,\n            py,\n            edgecolors=\"None\",\n            marker=self.marker,\n            s=self.p_size,\n            c=self.color,\n            alpha=self.alpha,\n        )\n        self._set_plot_limits(plot)\n\n    def _enforce_periodic(\n        self, particle_x, particle_y, x0, x1, period_x, y0, y1, period_y\n    ):\n        #  duplicate particles if periodic in that direction AND if the plot\n        #  extends outside the domain boundaries.\n        if self.periodic_x and x0 > self.RE[0]:\n            particle_x = uhstack((particle_x, particle_x + period_x))\n            particle_y = uhstack((particle_y, particle_y))\n        if self.periodic_x and x1 < self.LE[0]:\n            particle_x = uhstack((particle_x, particle_x - period_x))\n            particle_y = uhstack((particle_y, particle_y))\n        if self.periodic_y and y0 > self.RE[1]:\n            particle_y = uhstack((particle_y, particle_y + period_y))\n            particle_x = uhstack((particle_x, particle_x))\n        if self.periodic_y and y1 < self.LE[1]:\n            particle_y = uhstack((particle_y, particle_y - period_y))\n            particle_x = uhstack((particle_x, particle_x))\n        return particle_x, particle_y\n\n    def _get_region(self, xlim, ylim, axis, data):\n        LE, RE = [None] * 3, [None] * 3\n        ds = data.ds\n        xax = ds.coordinates.x_axis[axis]\n        yax = ds.coordinates.y_axis[axis]\n        zax = axis\n        LE[xax], RE[xax] = xlim\n        LE[yax], RE[yax] = ylim\n        LE[zax] = data.center[zax] - self.width * 0.5\n        LE[zax].convert_to_units(\"code_length\")\n        RE[zax] = LE[zax] + self.width\n        if (\n            self.region is not None\n            and np.all(self.region.left_edge <= LE)\n            and np.all(self.region.right_edge >= RE)\n        ):\n            return self.region\n        self.region = data.ds.region(data.center, LE, RE, data_source=self.data_source)\n        return self.region\n\n\nclass TitleCallback(PlotCallback):\n    \"\"\"\n    Accepts a *title* and adds it to the plot\n    \"\"\"\n\n    _type_name = \"title\"\n\n    def __init__(self, title):\n        self.title = title\n\n    def __call__(self, plot):\n        plot._axes.set_title(self.title)\n        # Set the font properties of text from this callback to be\n        # consistent with other text labels in this figure\n        label = plot._axes.title\n        self._set_font_properties(plot, [label])\n\n\nclass MeshLinesCallback(PlotCallback):\n    \"\"\"\n    Adds mesh lines to the plot. Only works for unstructured or\n    semi-structured mesh data. For structured grid data, see\n    GridBoundaryCallback or CellEdgesCallback.\n\n    Parameters\n    ----------\n\n    plot_args:   dict, optional\n        A dictionary of arguments that will be passed to matplotlib.\n\n    Example\n    -------\n\n    >>> import yt\n    >>> ds = yt.load(\"MOOSE_sample_data/out.e-s010\")\n    >>> sl = yt.SlicePlot(ds, \"z\", (\"connect2\", \"convected\"))\n    >>> sl.annotate_mesh_lines(color=\"black\")\n\n    \"\"\"\n\n    _type_name = \"mesh_lines\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\")\n    _incompatible_plot_types = (\"OffAxisSlice\", \"OffAxisProjection\")\n\n    def __init__(self, *, plot_args=None, **kwargs):\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            plot_args.update(kwargs)\n        else:\n            plot_args = kwargs\n        self.plot_args = plot_args\n\n    def promote_2d_to_3d(self, coords, indices, plot):\n        new_coords = np.zeros((2 * coords.shape[0], 3))\n        new_connects = np.zeros(\n            (indices.shape[0], 2 * indices.shape[1]), dtype=np.int64\n        )\n\n        new_coords[0 : coords.shape[0], 0:2] = coords\n        new_coords[0 : coords.shape[0], 2] = plot.ds.domain_left_edge[2]\n        new_coords[coords.shape[0] :, 0:2] = coords\n        new_coords[coords.shape[0] :, 2] = plot.ds.domain_right_edge[2]\n\n        new_connects[:, 0 : indices.shape[1]] = indices\n        new_connects[:, indices.shape[1] :] = indices + coords.shape[0]\n\n        return new_coords, new_connects\n\n    def __call__(self, plot):\n        index = plot.ds.index\n        if not issubclass(type(index), UnstructuredIndex):\n            raise RuntimeError(\n                \"Mesh line annotations only work for \"\n                \"unstructured or semi-structured mesh data.\"\n            )\n        for i, m in enumerate(index.meshes):\n            try:\n                ftype, fname = plot.field\n                if ftype.startswith(\"connect\") and int(ftype[-1]) - 1 != i:\n                    continue\n            except ValueError:\n                pass\n            coords = m.connectivity_coords\n            indices = m.connectivity_indices - m._index_offset\n\n            num_verts = indices.shape[1]\n            num_dims = coords.shape[1]\n\n            if num_dims == 2 and num_verts == 3:\n                coords, indices = self.promote_2d_to_3d(coords, indices, plot)\n            elif num_dims == 2 and num_verts == 4:\n                coords, indices = self.promote_2d_to_3d(coords, indices, plot)\n\n            tri_indices = triangulate_indices(indices.astype(np.int_))\n            points = coords[tri_indices]\n\n            tfc = TriangleFacetsCallback(points, **self.plot_args)\n            tfc(plot)\n\n\nclass TriangleFacetsCallback(PlotCallback):\n    \"\"\"\n    Intended for representing a slice of a triangular faceted\n    geometry in a slice plot.\n\n    Uses a set of *triangle_vertices* to find all triangles the plane of a\n    SlicePlot intersects with. The lines between the intersection points\n    of the triangles are then added to the plot to create an outline\n    of the geometry represented by the triangles.\n    \"\"\"\n\n    _type_name = \"triangle_facets\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\")\n\n    def __init__(self, triangle_vertices, *, plot_args=None, **kwargs):\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n            plot_args.update(kwargs)\n        else:\n            plot_args = kwargs\n        self.plot_args = kwargs\n        self.vertices = triangle_vertices\n\n    def __call__(self, plot):\n        ax = plot.data.axis\n        xax = plot.data.ds.coordinates.x_axis[ax]\n        yax = plot.data.ds.coordinates.y_axis[ax]\n\n        if not hasattr(self.vertices, \"in_units\"):\n            vertices = plot.data.pf.arr(self.vertices, \"code_length\")\n        else:\n            vertices = self.vertices\n        l_cy = triangle_plane_intersect(plot.data.axis, plot.data.coord, vertices)[\n            :, :, (xax, yax)\n        ]\n        # l_cy is shape (nlines, 2, 2)\n        # reformat for conversion to plot coordinates\n        l_cy = np.rollaxis(l_cy, 0, 3)  # shape is now (2, 2, nlines)\n        # convert all line starting points\n        l_cy[0] = self._convert_to_plot(plot, l_cy[0])\n        # convert all line ending points\n        l_cy[1] = self._convert_to_plot(plot, l_cy[1])\n        if plot._swap_axes:\n            # more convenient to swap the x, y values here before final roll\n            x0, y0 = l_cy[0]  # x, y values of start points\n            x1, y1 = l_cy[1]  # x, y values of end points\n            l_cy[0] = np.vstack([y0, x0])  # swap x, y for start points\n            l_cy[1] = np.vstack([y1, x1])  # swap x, y for end points\n        # convert back to shape (nlines, 2, 2)\n        l_cy = np.rollaxis(l_cy, 2, 0)\n        # create line collection and add it to the plot\n        lc = matplotlib.collections.LineCollection(l_cy, **self.plot_args)\n        plot._axes.add_collection(lc)\n\n\nclass TimestampCallback(PlotCallback):\n    r\"\"\"\n    Annotates the timestamp and/or redshift of the data output at a specified\n    location in the image (either in a present corner, or by specifying (x,y)\n    image coordinates with the x_pos, y_pos arguments.  If no time_units are\n    specified, it will automatically choose appropriate units.  It allows for\n    custom formatting of the time and redshift information, as well as the\n    specification of an inset box around the text.\n\n    Parameters\n    ----------\n\n    x_pos, y_pos : floats, optional\n        The image location of the timestamp in the coord system defined by the\n        coord_system kwarg.  Setting x_pos and y_pos overrides the corner\n        parameter.\n\n    corner : string, optional\n        Corner sets up one of 4 predeterimined locations for the timestamp\n        to be displayed in the image: 'upper_left', 'upper_right', 'lower_left',\n        'lower_right' (also allows None). This value will be overridden by the\n        optional x_pos and y_pos keywords.\n\n    time : boolean, optional\n        Whether or not to show the ds.current_time of the data output.  Can\n        be used solo or in conjunction with redshift parameter.\n\n    redshift : boolean, optional\n        Whether or not to show the ds.current_time of the data output.  Can\n        be used solo or in conjunction with the time parameter.\n\n    time_format : string, optional\n        This specifies the format of the time output assuming \"time\" is the\n        number of time and \"unit\" is units of the time (e.g. 's', 'Myr', etc.)\n        The time can be specified to arbitrary precision according to printf\n        formatting codes (defaults to .1f -- a float with 1 digits after\n        decimal).  Example: \"Age = {time:.2f} {units}\".\n\n    time_unit : string, optional\n        time_unit must be a valid yt time unit (e.g. 's', 'min', 'hr', 'yr',\n        'Myr', etc.)\n\n    redshift_format : string, optional\n        This specifies the format of the redshift output.  The redshift can\n        be specified to arbitrary precision according to printf formatting\n        codes (defaults to 0.2f -- a float with 2 digits after decimal).\n        Example: \"REDSHIFT = {redshift:03.3g}\",\n\n    draw_inset_box : boolean, optional\n        Whether or not an inset box should be included around the text\n        If so, it uses the inset_box_args to set the matplotlib FancyBboxPatch\n        object.\n\n    coord_system : string, optional\n        This string defines the coordinate system of the coordinates of pos\n        Valid coordinates are:\n\n        - \"data\": 3D dataset coordinates\n        - \"plot\": 2D coordinates defined by the actual plot limits\n        - \"axis\": MPL axis coordinates: (0,0) is lower left; (1,1) is upper right\n        - \"figure\": MPL figure coordinates: (0,0) is lower left, (1,1) is upper right\n\n    time_offset : float, (value, unit) tuple, or YTQuantity, optional\n        Apply an offset to the time shown in the annotation from the\n        value of the current time. If a scalar value with no units is\n        passed in, the value of the *time_unit* kwarg is used for the\n        units. Default: None, meaning no offset.\n\n    text_args : dictionary, optional\n        A dictionary of any arbitrary parameters to be passed to the Matplotlib\n        text object.  Defaults: ``{'color':'white',\n        'horizontalalignment':'center', 'verticalalignment':'top'}``.\n\n    inset_box_args : dictionary, optional\n        A dictionary of any arbitrary parameters to be passed to the Matplotlib\n        FancyBboxPatch object as the inset box around the text.\n        Defaults: ``{'boxstyle':'square', 'pad':0.3, 'facecolor':'black',\n        'linewidth':3, 'edgecolor':'white', 'alpha':0.5}``\n\n    Example\n    -------\n\n    >>> import yt\n    >>> ds = yt.load(\"Enzo_64/DD0020/data0020\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_timestamp()\n    \"\"\"\n\n    _type_name = \"timestamp\"\n    _supported_geometries = (\n        \"cartesian\",\n        \"spectral_cube\",\n        \"cylindrical\",\n        \"polar\",\n        \"spherical\",\n    )\n\n    def __init__(\n        self,\n        x_pos=None,\n        y_pos=None,\n        corner=\"lower_left\",\n        *,\n        time=True,\n        redshift=False,\n        time_format=\"t = {time:.1f} {units}\",\n        time_unit=None,\n        redshift_format=\"z = {redshift:.2f}\",\n        draw_inset_box=False,\n        coord_system=\"axis\",\n        time_offset=None,\n        text_args=None,\n        inset_box_args=None,\n    ):\n        # Set position based on corner argument.\n        self.pos = (x_pos, y_pos)\n        self.corner = corner\n        self.time = time\n        self.redshift = redshift\n        self.time_format = time_format\n        self.redshift_format = redshift_format\n        self.time_unit = time_unit\n        self.coord_system = coord_system\n        self.time_offset = time_offset\n        self.text_args = {\n            \"color\": \"white\",\n            \"horizontalalignment\": \"center\",\n            \"verticalalignment\": \"top\",\n            **(text_args or {}),\n        }\n\n        if draw_inset_box:\n            self.inset_box_args = {\n                \"boxstyle\": \"square,pad=0.3\",\n                \"facecolor\": \"black\",\n                \"linewidth\": 3,\n                \"edgecolor\": \"white\",\n                \"alpha\": 0.5,\n                **(inset_box_args or {}),\n            }\n        else:\n            self.inset_box_args = None\n\n    def __call__(self, plot):\n        # Setting pos overrides corner argument\n        if self.pos[0] is None or self.pos[1] is None:\n            if self.corner == \"upper_left\":\n                self.pos = (0.03, 0.96)\n                self.text_args[\"horizontalalignment\"] = \"left\"\n                self.text_args[\"verticalalignment\"] = \"top\"\n            elif self.corner == \"upper_right\":\n                self.pos = (0.97, 0.96)\n                self.text_args[\"horizontalalignment\"] = \"right\"\n                self.text_args[\"verticalalignment\"] = \"top\"\n            elif self.corner == \"lower_left\":\n                self.pos = (0.03, 0.03)\n                self.text_args[\"horizontalalignment\"] = \"left\"\n                self.text_args[\"verticalalignment\"] = \"bottom\"\n            elif self.corner == \"lower_right\":\n                self.pos = (0.97, 0.03)\n                self.text_args[\"horizontalalignment\"] = \"right\"\n                self.text_args[\"verticalalignment\"] = \"bottom\"\n            elif self.corner is None:\n                self.pos = (0.5, 0.5)\n                self.text_args[\"horizontalalignment\"] = \"center\"\n                self.text_args[\"verticalalignment\"] = \"center\"\n            else:\n                raise ValueError(\n                    \"Argument 'corner' must be set to \"\n                    \"'upper_left', 'upper_right', 'lower_left', \"\n                    \"'lower_right', or None\"\n                )\n\n        self.text = \"\"\n\n        # If we're annotating the time, put it in the correct format\n        if self.time:\n            # If no time_units are set, then identify a best fit time unit\n            if self.time_unit is None:\n                if plot.ds._uses_code_time_unit:\n                    # if the unit system is in code units\n                    # we should not convert to seconds for the plot.\n                    self.time_unit = plot.ds.unit_system.base_units[dimensions.time]\n                else:\n                    # in the case of non- code units then we\n                    self.time_unit = plot.ds.get_smallest_appropriate_unit(\n                        plot.ds.current_time, quantity=\"time\"\n                    )\n            t = plot.ds.current_time.in_units(self.time_unit)\n            if self.time_offset is not None:\n                if isinstance(self.time_offset, tuple):\n                    toffset = plot.ds.quan(self.time_offset[0], self.time_offset[1])\n                elif isinstance(self.time_offset, Number):\n                    toffset = plot.ds.quan(self.time_offset, self.time_unit)\n                elif not isinstance(self.time_offset, YTQuantity):\n                    raise RuntimeError(\n                        \"'time_offset' must be a float, tuple, or YTQuantity!\"\n                    )\n                t -= toffset.in_units(self.time_unit)\n            try:\n                # here the time unit will be in brackets on the annotation.\n                un = self.time_unit.latex_representation()\n                time_unit = r\"$\\ \\ (\" + un + r\")$\"\n            except AttributeError:\n                time_unit = str(self.time_unit).replace(\"_\", \" \")\n            self.text += self.time_format.format(time=float(t), units=time_unit)\n\n        # If time and redshift both shown, do one on top of the other\n        if self.time and self.redshift:\n            self.text += \"\\n\"\n\n        if self.redshift and not hasattr(plot.data.ds, \"current_redshift\"):\n            warnings.warn(\n                f\"dataset {plot.data.ds} does not have current_redshift attribute. \"\n                \"Set redshift=False to silence this warning.\",\n                stacklevel=2,\n            )\n            self.redshift = False\n\n        # If we're annotating the redshift, put it in the correct format\n        if self.redshift:\n            z = plot.data.ds.current_redshift\n            # Replace instances of -0.0* with 0.0* to avoid\n            # negative null redshifts (e.g., \"-0.00\").\n            self.text += self.redshift_format.format(redshift=float(z))\n            self.text = re.sub(\"-(0.0*)$\", r\"\\g<1>\", self.text)\n\n        # This is just a fancy wrapper around the TextLabelCallback\n        tcb = TextLabelCallback(\n            self.pos,\n            self.text,\n            coord_system=self.coord_system,\n            text_args=self.text_args,\n            inset_box_args=self.inset_box_args,\n        )\n        return tcb(plot)\n\n\nclass ScaleCallback(PlotCallback):\n    r\"\"\"\n    Annotates the scale of the plot at a specified location in the image\n    (either in a preset corner, or by specifying (x,y) image coordinates with\n    the pos argument.  Coeff and units (e.g. 1 Mpc or 100 kpc) refer to the\n    distance scale you desire to show on the plot.  If no coeff and units are\n    specified, an appropriate pair will be determined such that your scale bar\n    is never smaller than min_frac or greater than max_frac of your plottable\n    axis length.  Additional customization of the scale bar is possible by\n    adjusting the text_args and size_bar_args dictionaries.  The text_args\n    dictionary accepts matplotlib's font_properties arguments to override\n    the default font_properties for the current plot.  The size_bar_args\n    dictionary accepts keyword arguments for the AnchoredSizeBar class in\n    matplotlib's axes_grid toolkit.\n\n    Parameters\n    ----------\n\n    corner : string, optional\n        Corner sets up one of 4 predeterimined locations for the scale bar\n        to be displayed in the image: 'upper_left', 'upper_right', 'lower_left',\n        'lower_right' (also allows None). This value will be overridden by the\n        optional 'pos' keyword.\n\n    coeff : float, optional\n        The coefficient of the unit defining the distance scale (e.g. 10 kpc or\n        100 Mpc) for overplotting.  If set to None along with unit keyword,\n        coeff will be automatically determined to be a power of 10\n        relative to the best-fit unit.\n\n    unit : string, optional\n        unit must be a valid yt distance unit (e.g. 'm', 'km', 'AU', 'pc',\n        'kpc', etc.) or set to None.  If set to None, will be automatically\n        determined to be the best-fit to the data.\n\n    pos : 2- or 3-element tuples, lists, or arrays, optional\n        The image location of the scale bar in the plot coordinate system.\n        Setting pos overrides the corner parameter.\n\n    min_frac, max_frac: float, optional\n        The minimum/maximum fraction of the axis width for the scale bar to\n        extend. A value of 1 would allow the scale bar to extend across the\n        entire axis width.  Only used for automatically calculating\n        best-fit coeff and unit when neither is specified, otherwise\n        disregarded.\n\n    coord_system : string, optional\n        This string defines the coordinate system of the coordinates of pos\n        Valid coordinates are:\n\n        - \"data\": 3D dataset coordinates\n        - \"plot\": 2D coordinates defined by the actual plot limits\n        - \"axis\": MPL axis coordinates: (0,0) is lower left; (1,1) is upper right\n        - \"figure\": MPL figure coordinates: (0,0) is lower left, (1,1) is upper right\n\n    text_args : dictionary, optional\n        A dictionary of parameters to used to update the font_properties\n        for the text in this callback.  For any property not set, it will\n        use the defaults of the plot.  Thus one can modify the text size with\n        ``text_args={'size':24}``\n\n    size_bar_args : dictionary, optional\n        A dictionary of parameters to be passed to the Matplotlib\n        AnchoredSizeBar initializer.\n        Defaults: ``{'pad': 0.25, 'sep': 5, 'borderpad': 1, 'color': 'w'}``\n\n    draw_inset_box : boolean, optional\n        Whether or not an inset box should be included around the scale bar.\n\n    inset_box_args : dictionary, optional\n        A dictionary of keyword arguments to be passed to the matplotlib Patch\n        object that represents the inset box.\n        Defaults: ``{'facecolor': 'black', 'linewidth': 3,\n        'edgecolor': 'white', 'alpha': 0.5, 'boxstyle': 'square'}``\n\n    scale_text_format : string, optional\n        This specifies the format of the scalebar value assuming \"scale\" is the\n        numerical value and \"unit\" is units of the scale (e.g. 'cm', 'kpc', etc.)\n        The scale can be specified to arbitrary precision according to printf\n        formatting codes. The format string must only specify \"scale\" and \"units\".\n        Example: \"Length = {scale:.2f} {units}\". Default: \"{scale} {units}\"\n\n    Example\n    -------\n\n    >>> import yt\n    >>> ds = yt.load(\"Enzo_64/DD0020/data0020\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_scale()\n    \"\"\"\n\n    _type_name = \"scale\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"force\")\n\n    def __init__(\n        self,\n        *,\n        corner=\"lower_right\",\n        coeff=None,\n        unit=None,\n        pos=None,\n        max_frac=0.16,\n        min_frac=0.015,\n        coord_system=\"axis\",\n        text_args=None,\n        size_bar_args=None,\n        draw_inset_box=False,\n        inset_box_args=None,\n        scale_text_format=\"{scale} {units}\",\n    ):\n        # Set position based on corner argument.\n        self.corner = corner\n        self.coeff = coeff\n        self.unit = unit\n        self.pos = pos\n        self.max_frac = max_frac\n        self.min_frac = min_frac\n        self.coord_system = coord_system\n        self.scale_text_format = scale_text_format\n\n        self.size_bar_args = {\n            \"pad\": 0.05,\n            \"sep\": 5,\n            \"borderpad\": 1,\n            \"color\": \"white\",\n            **(size_bar_args or {}),\n        }\n        self.inset_box_args = {\n            \"facecolor\": \"black\",\n            \"linewidth\": 3,\n            \"edgecolor\": \"white\",\n            \"alpha\": 0.5,\n            \"boxstyle\": \"square\",\n            **(inset_box_args or {}),\n        }\n        self.text_args = text_args or {}\n        self.draw_inset_box = draw_inset_box\n\n    def __call__(self, plot):\n        from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar\n\n        # Callback only works for plots with axis ratios of 1\n        xsize = plot.xlim[1] - plot.xlim[0]\n\n        # Setting pos overrides corner argument\n        if self.pos is None:\n            if self.corner == \"upper_left\":\n                self.pos = (0.11, 0.952)\n            elif self.corner == \"upper_right\":\n                self.pos = (0.89, 0.952)\n            elif self.corner == \"lower_left\":\n                self.pos = (0.11, 0.052)\n            elif self.corner == \"lower_right\":\n                self.pos = (0.89, 0.052)\n            elif self.corner is None:\n                self.pos = (0.5, 0.5)\n            else:\n                raise ValueError(\n                    \"Argument 'corner' must be set to \"\n                    \"'upper_left', 'upper_right', 'lower_left', \"\n                    \"'lower_right', or None\"\n                )\n\n        # When identifying a best fit distance unit, do not allow scale marker\n        # to be greater than max_frac fraction of xaxis or under min_frac\n        # fraction of xaxis\n        max_scale = self.max_frac * xsize\n        min_scale = self.min_frac * xsize\n\n        # If no units are set, pick something sensible.\n        if self.unit is None:\n            # User has set the axes units and supplied a coefficient.\n            if plot._axes_unit_names is not None and self.coeff is not None:\n                self.unit = plot._axes_unit_names[0]\n            # Nothing provided; identify a best fit distance unit.\n            else:\n                min_scale = plot.ds.get_smallest_appropriate_unit(\n                    min_scale, return_quantity=True\n                )\n                max_scale = plot.ds.get_smallest_appropriate_unit(\n                    max_scale, return_quantity=True\n                )\n                if self.coeff is None:\n                    self.coeff = max_scale.v\n                self.unit = max_scale.units\n        elif self.coeff is None:\n            self.coeff = 1\n        self.scale = plot.ds.quan(self.coeff, self.unit)\n        text = self.scale_text_format.format(scale=int(self.coeff), units=self.unit)\n        image_scale = (\n            plot.frb.convert_distance_x(self.scale) / plot.frb.convert_distance_x(xsize)\n        ).v\n        size_vertical = self.size_bar_args.pop(\"size_vertical\", 0.005 * plot.aspect)\n        fontproperties = self.size_bar_args.pop(\n            \"fontproperties\", plot.font_properties.copy()\n        )\n        frameon = self.size_bar_args.pop(\"frameon\", self.draw_inset_box)\n        # FontProperties instances use set_<property>() setter functions\n        for key, val in self.text_args.items():\n            setter_func = \"set_\" + key\n            try:\n                getattr(fontproperties, setter_func)(val)\n            except AttributeError as e:\n                raise AttributeError(\n                    \"Cannot set text_args keyword \"\n                    f\"to include {key!r} because MPL's fontproperties object does \"\n                    f\"not contain function {setter_func!r}\"\n                ) from e\n\n        # this \"anchors\" the size bar to a box centered on self.pos in axis\n        # coordinates\n        self.size_bar_args[\"bbox_to_anchor\"] = self.pos\n        self.size_bar_args[\"bbox_transform\"] = plot._axes.transAxes\n\n        bar = AnchoredSizeBar(\n            plot._axes.transAxes,\n            image_scale,\n            text,\n            10,\n            size_vertical=size_vertical,\n            fontproperties=fontproperties,\n            frameon=frameon,\n            **self.size_bar_args,\n        )\n\n        bar.patch.set(**self.inset_box_args)\n\n        plot._axes.add_artist(bar)\n\n        return plot\n\n\nclass RayCallback(PlotCallback):\n    \"\"\"\n    Adds a line representing the projected path of a ray across the plot.\n    The ray can be either a YTOrthoRay, YTRay, or a LightRay object.\n    annotate_ray() will properly account for periodic rays across the volume.\n    If arrow is set to True, uses the MPL.pyplot.arrow function, otherwise\n    uses the MPL.pyplot.plot function to plot a normal line.  Adjust\n    plot_args accordingly.\n\n    Parameters\n    ----------\n\n    ray : YTOrthoRay, YTRay, or LightRay\n        Ray is the object that we want to include.  We overplot the projected\n        trajectory of the ray.  If the object is a trident.LightRay\n        object, it will only plot the segment of the LightRay that intersects\n        the dataset currently displayed.\n\n    arrow : boolean, optional\n        Whether or not to place an arrowhead on the front of the ray to denote\n        direction\n        Default: False\n\n    plot_args : dictionary, optional\n        A dictionary of any arbitrary parameters to be passed to the Matplotlib\n        line object.  Defaults: {'color':'white', 'linewidth':2}.\n\n    Examples\n    --------\n\n    >>> # Overplot a ray and an ortho_ray object on a projection\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> oray = ds.ortho_ray(1, (0.3, 0.4))  # orthoray down the y axis\n    >>> ray = ds.ray((0.1, 0.2, 0.3), (0.6, 0.7, 0.8))  # arbitrary ray\n    >>> p = yt.ProjectionPlot(ds, \"z\", \"density\")\n    >>> p.annotate_ray(oray)\n    >>> p.annotate_ray(ray)\n    >>> p.save()\n\n    >>> # Overplot a LightRay object on a projection\n    >>> import yt\n    >>> from trident import LightRay\n    >>> ds = yt.load(\"enzo_cosmology_plus/RD0004/RD0004\")\n    >>> lr = LightRay(\n    ...     \"enzo_cosmology_plus/AMRCosmology.enzo\", \"Enzo\", 0.0, 0.1, time_data=False\n    ... )\n    >>> lray = lr.make_light_ray(seed=1)\n    >>> p = yt.ProjectionPlot(ds, \"z\", \"density\")\n    >>> p.annotate_ray(lr)\n    >>> p.save()\n\n    \"\"\"\n\n    _type_name = \"ray\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"force\")\n\n    def __init__(self, ray, *, arrow=False, plot_args=None, **kwargs):\n        self.ray = ray\n        self.arrow = arrow\n        if plot_args is not None:\n            issue_deprecation_warning(\n                \"`plot_args` is deprecated. \"\n                \"You can now pass arbitrary keyword arguments instead of a dictionary.\",\n                since=\"4.1\",\n                stacklevel=5,\n            )\n        self.plot_args = {\n            \"color\": \"white\",\n            \"linewidth\": 2,\n            **(plot_args or {}),\n            **kwargs,\n        }\n\n    def _process_ray(self):\n        \"\"\"\n        Get the start_coord and end_coord of a ray object\n        \"\"\"\n        return (self.ray.start_point, self.ray.end_point)\n\n    def _process_ortho_ray(self):\n        \"\"\"\n        Get the start_coord and end_coord of an ortho_ray object\n        \"\"\"\n        start_coord = self.ray.ds.domain_left_edge.copy()\n        end_coord = self.ray.ds.domain_right_edge.copy()\n\n        xax = self.ray.ds.coordinates.x_axis[self.ray.axis]\n        yax = self.ray.ds.coordinates.y_axis[self.ray.axis]\n        start_coord[xax] = end_coord[xax] = self.ray.coords[0]\n        start_coord[yax] = end_coord[yax] = self.ray.coords[1]\n        return (start_coord, end_coord)\n\n    def _process_light_ray(self, plot):\n        \"\"\"\n        Get the start_coord and end_coord of a LightRay object.\n        Identify which of the sections of the LightRay is in the\n        dataset that is currently being plotted.  If there is one, return the\n        start and end of the corresponding ray segment\n        \"\"\"\n\n        for ray_ds in self.ray.light_ray_solution:\n            if ray_ds[\"unique_identifier\"] == str(plot.ds.unique_identifier):\n                start_coord = plot.ds.arr(ray_ds[\"start\"])\n                end_coord = plot.ds.arr(ray_ds[\"end\"])\n                return (start_coord, end_coord)\n        # if no intersection between the plotted dataset and the LightRay\n        # return a false tuple to pass to start_coord\n        return ((False, False), (False, False))\n\n    def __call__(self, plot):\n        type_name = getattr(self.ray, \"_type_name\", None)\n\n        if type_name == \"ray\":\n            start_coord, end_coord = self._process_ray()\n\n        elif type_name == \"ortho_ray\":\n            start_coord, end_coord = self._process_ortho_ray()\n\n        elif hasattr(self.ray, \"light_ray_solution\"):\n            start_coord, end_coord = self._process_light_ray(plot)\n\n        else:\n            raise ValueError(\"ray must be a YTRay, YTOrthoRay, or LightRay object.\")\n\n        # if start_coord and end_coord are all False, it means no intersecting\n        # ray segment with this plot.\n        if not all(start_coord) and not all(end_coord):\n            return plot\n\n        # if possible, break periodic ray into non-periodic\n        # segments and add each of them individually\n        if any(plot.ds.periodicity):\n            segments = periodic_ray(\n                start_coord.to(\"code_length\"),\n                end_coord.to(\"code_length\"),\n                left=plot.ds.domain_left_edge.to(\"code_length\"),\n                right=plot.ds.domain_right_edge.to(\"code_length\"),\n            )\n        else:\n            segments = [[start_coord, end_coord]]\n\n        # To assure that the last ray segment has an arrow if so desired\n        # and all other ray segments are lines\n        for segment in segments[:-1]:\n            cb = LinePlotCallback(\n                segment[0], segment[1], coord_system=\"data\", **self.plot_args\n            )\n            cb(plot)\n        segment = segments[-1]\n        if self.arrow:\n            cb = ArrowCallback(\n                segment[1],\n                starting_pos=segment[0],\n                coord_system=\"data\",\n                **self.plot_args,\n            )\n        else:\n            cb = LinePlotCallback(\n                segment[0], segment[1], coord_system=\"data\", **self.plot_args\n            )\n        cb(plot)\n        return plot\n\n\nclass LineIntegralConvolutionCallback(PlotCallback):\n    \"\"\"\n    Add the line integral convolution to the plot for vector fields\n    visualization. Two component of vector fields needed to be provided\n    (i.e., velocity_x and velocity_y, magnetic_field_x and magnetic_field_y).\n\n    Parameters\n    ----------\n\n    field_x, field_y : string\n        The names of two components of vector field which will be visualized\n\n    texture : 2-d array with the same shape of image, optional\n        Texture will be convolved when computing line integral convolution.\n        A white noise background will be used as default.\n\n    kernellen : float, optional\n        The lens of kernel for convolution, which is the length over which the\n        convolution will be performed. For longer kernellen, longer streamline\n        structure will appear.\n\n    lim : 2-element tuple, list, or array, optional\n        The value of line integral convolution will be clipped to the range\n        of lim, which applies upper and lower bounds to the values of line\n        integral convolution and enhance the visibility of plots. Each element\n        should be in the range of [0,1].\n\n    cmap : string, optional\n        The name of colormap for line integral convolution plot.\n\n    alpha : float, optional\n        The alpha value for line integral convolution plot.\n\n    const_alpha : boolean, optional\n        If set to False (by default), alpha will be weighted spatially by\n        the values of line integral convolution; otherwise a constant value\n        of the given alpha is used.\n\n    Example\n    -------\n\n    >>> import yt\n    >>> ds = yt.load(\"Enzo_64/DD0020/data0020\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_line_integral_convolution(\n    ...     \"velocity_x\", \"velocity_y\", lim=(0.5, 0.65)\n    ... )\n    \"\"\"\n\n    _type_name = \"line_integral_convolution\"\n    _supported_geometries = (\n        \"cartesian\",\n        \"spectral_cube\",\n        \"polar\",\n        \"cylindrical\",\n        \"spherical\",\n    )\n    _incompatible_plot_types = (\"LineIntegralConvolutionCallback\",)\n\n    def __init__(\n        self,\n        field_x,\n        field_y,\n        texture=None,\n        kernellen=50,\n        lim=(0.5, 0.6),\n        cmap=\"binary\",\n        alpha=0.5,\n        const_alpha=False,\n    ):\n        self.field_x = field_x\n        self.field_y = field_y\n        self.texture = texture\n        self.kernellen = kernellen\n        self.lim = lim\n        self.cmap = cmap\n        self.alpha = alpha\n        self.const_alpha = const_alpha\n\n    def __call__(self, plot):\n        from matplotlib import cm\n\n        bounds = self._physical_bounds(plot)\n        extent = self._plot_bounds(plot)\n\n        # We are feeding this size into the pixelizer, where it will properly\n        # set it in reverse order\n        nx = plot.raw_image_shape[1]\n        ny = plot.raw_image_shape[0]\n        pixX = plot.data.ds.coordinates.pixelize(\n            plot.data.axis, plot.data, self.field_x, bounds, (nx, ny)\n        )\n        pixY = plot.data.ds.coordinates.pixelize(\n            plot.data.axis, plot.data, self.field_y, bounds, (nx, ny)\n        )\n\n        if self.texture is None:\n            prng = np.random.default_rng(0x4D3D3D3)\n            self.texture = prng.random((nx, ny))\n\n        kernel = np.sin(\n            np.arange(self.kernellen, dtype=\"float64\") * np.pi / self.kernellen\n        )\n\n        lic_data = rlic.convolve(self.texture, pixX, pixY, kernel=kernel)\n        lic_data = lic_data / lic_data.max()\n        lic_data_clip = np.clip(lic_data, self.lim[0], self.lim[1])\n\n        if plot._swap_axes:\n            lic_data_clip = lic_data_clip.transpose()\n            extent = (extent[2], extent[3], extent[0], extent[1])\n\n        if self.const_alpha:\n            plot._axes.imshow(\n                lic_data_clip,\n                extent=extent,\n                cmap=self.cmap,\n                alpha=self.alpha,\n                origin=\"lower\",\n                aspect=\"auto\",\n            )\n        else:\n            lic_data_rgba = cm.ScalarMappable(norm=None, cmap=self.cmap).to_rgba(\n                lic_data_clip\n            )\n            lic_data_clip_rescale = (lic_data_clip - self.lim[0]) / (\n                self.lim[1] - self.lim[0]\n            )\n            lic_data_rgba[..., 3] = lic_data_clip_rescale * self.alpha\n            plot._axes.imshow(\n                lic_data_rgba,\n                extent=extent,\n                cmap=self.cmap,\n                origin=\"lower\",\n                aspect=\"auto\",\n            )\n\n        return plot\n\n\nclass CellEdgesCallback(PlotCallback):\n    \"\"\"\n    Annotate cell edges.  This is done through a second call to pixelize, where\n    the distance from a pixel to a cell boundary in pixels is compared against\n    the `line_width` argument.  The secondary image is colored as `color` and\n    overlaid with the `alpha` value.\n\n    Parameters\n    ----------\n    line_width : float\n        The width of the cell edge lines in normalized units relative to the\n        size of the longest axis.  Default is 1% of the size of the smallest\n        axis.\n    alpha : float\n        When the second image is overlaid, it will have this level of alpha\n        transparency.  Default is 1.0 (fully-opaque).\n    color : tuple of three floats or matplotlib color name\n        This is the color of the cell edge values.  It defaults to black.\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> s = yt.SlicePlot(ds, \"z\", \"density\")\n    >>> s.annotate_cell_edges()\n    >>> s.save()\n    \"\"\"\n\n    _type_name = \"cell_edges\"\n    _supported_geometries = (\"cartesian\", \"spectral_cube\", \"cylindrical\")\n\n    def __init__(self, line_width=0.002, alpha=1.0, color=\"black\"):\n        from matplotlib.colors import ColorConverter\n\n        conv = ColorConverter()\n        self.line_width = line_width\n        self.alpha = alpha\n        self.color = np.array(conv.to_rgb(color), dtype=\"uint8\") * 255\n\n    def __call__(self, plot):\n        if plot.data.ds.geometry == \"cylindrical\" and plot.data.ds.dimensionality == 3:\n            raise NotImplementedError(\n                \"Cell edge annotation is only supported for \\\n                for 2D cylindrical geometry, not 3D\"\n            )\n        x0, x1, y0, y1 = self._physical_bounds(plot)\n        nx = plot.raw_image_shape[1]\n        ny = plot.raw_image_shape[0]\n        aspect = float((y1 - y0) / (x1 - x0))\n        pixel_aspect = float(ny) / nx\n        relative_aspect = pixel_aspect / aspect\n        if relative_aspect > 1:\n            nx = int(nx / relative_aspect)\n        else:\n            ny = int(ny * relative_aspect)\n        if aspect > 1:\n            if nx < 1600:\n                nx = int(1600.0 / nx * ny)\n                ny = 1600\n            long_axis = ny\n        else:\n            if ny < 1600:\n                nx = int(1600.0 / ny * nx)\n                ny = 1600\n            long_axis = nx\n        line_width = max(self.line_width * long_axis, 1.0)\n        im = np.zeros((ny, nx), dtype=\"f8\")\n        pixelize_cartesian(\n            im,\n            plot.data[\"px\"],\n            plot.data[\"py\"],\n            plot.data[\"pdx\"],\n            plot.data[\"pdy\"],\n            plot.data[\"px\"],  # dummy field\n            (x0, x1, y0, y1),\n            line_width=line_width,\n        )\n        # New image:\n        im_buffer = np.zeros((ny, nx, 4), dtype=\"uint8\")\n        im_buffer[im > 0, 3] = 255\n        im_buffer[im > 0, :3] = self.color\n\n        extent = self._plot_bounds(plot)\n        if plot._swap_axes:\n            im_buffer = im_buffer.transpose((1, 0, 2))\n            # note: when using imshow, the extent keyword argument has to be the\n            # swapped extents, so the extent is swapped here (rather than\n            # calling self._set_plot_limits).\n            # https://github.com/yt-project/yt/issues/5094\n            extent = _swap_axes_extents(extent)\n\n        plot._axes.imshow(\n            im_buffer,\n            origin=\"lower\",\n            interpolation=\"bilinear\",\n            extent=extent,\n            alpha=self.alpha,\n        )\n"
  },
  {
    "path": "yt/visualization/plot_window.py",
    "content": "import abc\nimport sys\nfrom collections import defaultdict\nfrom numbers import Number\nfrom typing import TYPE_CHECKING, Union\n\nimport matplotlib\nimport numpy as np\nfrom more_itertools import always_iterable\nfrom unyt.exceptions import UnitConversionError\n\nfrom yt._maintenance.deprecation import issue_deprecation_warning\nfrom yt._typing import AlphaT\nfrom yt.data_objects.image_array import ImageArray\nfrom yt.frontends.sph.data_structures import ParticleDataset\nfrom yt.frontends.stream.data_structures import StreamParticlesDataset\nfrom yt.frontends.ytdata.data_structures import YTSpatialPlotDataset\nfrom yt.funcs import (\n    fix_axis,\n    fix_unitary,\n    is_sequence,\n    iter_fields,\n    mylog,\n    obj_length,\n    parse_center_array,\n    validate_moment,\n)\nfrom yt.geometry.api import Geometry\nfrom yt.geometry.oct_geometry_handler import OctreeIndex\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.units.unit_registry import UnitParseError  # type: ignore\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.exceptions import (\n    YTCannotParseUnitDisplayName,\n    YTDataTypeUnsupported,\n    YTInvalidFieldType,\n    YTPlotCallbackError,\n    YTUnitNotRecognized,\n)\nfrom yt.utilities.math_utils import ortho_find\nfrom yt.utilities.orientation import Orientation\nfrom yt.visualization._handlers import ColorbarHandler, NormHandler\nfrom yt.visualization.base_plot_types import CallbackWrapper, ImagePlotMPL\n\nfrom ._commons import (\n    _get_units_label,\n    _swap_axes_extents,\n    get_default_from_config,\n)\nfrom .fixed_resolution import (\n    FixedResolutionBuffer,\n    OffAxisProjectionFixedResolutionBuffer,\n)\nfrom .geo_plot_utils import get_mpl_transform\nfrom .plot_container import (\n    ImagePlotContainer,\n    invalidate_data,\n    invalidate_figure,\n    invalidate_plot,\n)\n\nif TYPE_CHECKING:\n    from yt.visualization.plot_modifications import PlotCallback\n\nif sys.version_info >= (3, 11):\n    from typing import assert_never\nelse:\n    from typing_extensions import assert_never\n\n\ndef get_window_parameters(axis, center, width, ds):\n    width = ds.coordinates.sanitize_width(axis, width, None)\n    center, display_center = ds.coordinates.sanitize_center(center, axis)\n    xax = ds.coordinates.x_axis[axis]\n    yax = ds.coordinates.y_axis[axis]\n    bounds = (\n        display_center[xax] - width[0] / 2,\n        display_center[xax] + width[0] / 2,\n        display_center[yax] - width[1] / 2,\n        display_center[yax] + width[1] / 2,\n    )\n    return (bounds, center, display_center)\n\n\ndef get_oblique_window_parameters(\n    normal,\n    center,\n    width,\n    ds,\n    depth=None,\n):\n    center, display_center = ds.coordinates.sanitize_center(center, axis=None)\n    width = ds.coordinates.sanitize_width(normal, width, depth)\n\n    if len(width) == 2:\n        # Transforming to the cutting plane coordinate system\n        # the original dimensionless center messes up off-axis\n        # SPH projections though -> don't use this center there\n        center = (\n            (center - ds.domain_left_edge) / ds.domain_width - 0.5\n        ) * ds.domain_width\n        (normal, perp1, perp2) = ortho_find(normal)\n        mat = np.transpose(np.column_stack((perp1, perp2, normal)))\n        center = np.dot(mat, center)\n\n    w = tuple(el.in_units(\"code_length\") for el in width)\n    bounds = tuple(((2 * (i % 2)) - 1) * w[i // 2] / 2 for i in range(len(w) * 2))\n    return bounds, center\n\n\ndef get_axes_unit(width, ds):\n    r\"\"\"\n    Infers the axes unit names from the input width specification\n    \"\"\"\n    if ds.no_cgs_equiv_length:\n        return (\"code_length\",) * 2\n    if is_sequence(width):\n        if isinstance(width[1], str):\n            axes_unit = (width[1], width[1])\n        elif is_sequence(width[1]):\n            axes_unit = (width[0][1], width[1][1])\n        elif isinstance(width[0], YTArray):\n            axes_unit = (str(width[0].units), str(width[1].units))\n        else:\n            axes_unit = None\n    else:\n        if isinstance(width, YTArray):\n            axes_unit = (str(width.units), str(width.units))\n        else:\n            axes_unit = None\n    return axes_unit\n\n\ndef validate_mesh_fields(data_source, fields):\n    # this check doesn't make sense for ytdata plot datasets, which\n    # load mesh data as a particle field but nonetheless can still\n    # make plots with it\n    if isinstance(data_source.ds, YTSpatialPlotDataset):\n        return\n    canonical_fields = data_source._determine_fields(fields)\n    invalid_fields = []\n    for field in canonical_fields:\n        finfo = data_source.ds.field_info[field]\n        if finfo.sampling_type == \"particle\":\n            if not hasattr(data_source.ds, \"_sph_ptypes\"):\n                pass\n            elif finfo.is_sph_field:\n                continue\n            invalid_fields.append(field)\n\n    if len(invalid_fields) > 0:\n        raise YTInvalidFieldType(invalid_fields)\n\n\nclass PlotWindow(ImagePlotContainer, abc.ABC):\n    r\"\"\"\n    A plotting mechanism based around the concept of a window into a\n    data source. It can have arbitrary fields, each of which will be\n    centered on the same viewpoint, but will have individual zlimits.\n\n    The data and plot are updated separately, and each can be\n    invalidated as the object is modified.\n\n    Data is handled by a FixedResolutionBuffer object.\n\n    Parameters\n    ----------\n\n    data_source :\n        :class:`yt.data_objects.selection_objects.base_objects.YTSelectionContainer2D`\n        This is the source to be pixelized, which can be a projection,\n        slice, or a cutting plane.\n    bounds : sequence of floats\n        Bounds are the min and max in the image plane that we want our\n        image to cover.  It's in the order of (xmin, xmax, ymin, ymax),\n        where the coordinates are all in the appropriate code units.\n    buff_size : sequence of ints\n        The size of the image to generate.\n    antialias : boolean\n        This can be true or false.  It determines whether or not sub-pixel\n        rendering is used during data deposition.\n    window_size : float\n        The size of the window on the longest axis (in units of inches),\n        including the margins but not the colorbar.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        data_source,\n        bounds,\n        buff_size=(800, 800),\n        antialias=True,\n        periodic=True,\n        origin=\"center-window\",\n        oblique=False,\n        window_size=8.0,\n        fields=None,\n        fontsize=18,\n        aspect=None,\n        setup=False,\n        *,\n        geometry: Geometry = Geometry.CARTESIAN,\n    ) -> None:\n        # axis manipulation operations are callback-only:\n        self._swap_axes_input = False\n        self._flip_vertical = False\n        self._flip_horizontal = False\n\n        self.center = None\n        self._periodic = periodic\n        self.oblique = oblique\n        self._equivalencies = defaultdict(lambda: (None, {}))  # type: ignore [var-annotated]\n        self.buff_size = buff_size\n        self.antialias = antialias\n        self._axes_unit_names = None\n        self._transform = None\n        self._projection = None\n\n        self.aspect = aspect\n        skip = list(FixedResolutionBuffer._exclude_fields) + data_source._key_fields\n\n        fields = list(iter_fields(fields))\n        self.override_fields = list(set(fields).intersection(set(skip)))\n        self.fields = [f for f in fields if f not in skip]\n        self._frb: FixedResolutionBuffer | None = None\n        super().__init__(data_source, window_size, fontsize)\n\n        self._set_window(bounds)  # this automatically updates the data and plot\n\n        if origin != \"native\":\n            match geometry:\n                case Geometry.CARTESIAN | Geometry.SPECTRAL_CUBE:\n                    pass\n                case (\n                    Geometry.CYLINDRICAL\n                    | Geometry.POLAR\n                    | Geometry.SPHERICAL\n                    | Geometry.GEOGRAPHIC\n                    | Geometry.INTERNAL_GEOGRAPHIC\n                ):\n                    mylog.info(\"Setting origin='native' for %s geometry.\", geometry)\n                    origin = \"native\"\n                case _:\n                    assert_never(geometry)\n\n        self.origin = origin\n        if self.data_source.center is not None and not oblique:\n            ax = self.data_source.axis\n            xax = self.ds.coordinates.x_axis[ax]\n            yax = self.ds.coordinates.y_axis[ax]\n            center, display_center = self.ds.coordinates.sanitize_center(\n                self.data_source.center, ax\n            )\n            center = [display_center[xax], display_center[yax]]\n            self.set_center(center)\n\n            axname = self.ds.coordinates.axis_name[ax]\n            transform = self.ds.coordinates.data_transform[axname]\n            projection = self.ds.coordinates.data_projection[axname]\n            self._projection = get_mpl_transform(projection)\n            self._transform = get_mpl_transform(transform)\n\n        self._setup_plots()\n\n        for field in self.data_source._determine_fields(self.fields):\n            finfo = self.data_source.ds._get_field_info(field)\n            pnh = self.plots[field].norm_handler\n\n            # take_log can be `None` so we explicitly compare against a boolean\n            pnh.prefer_log = finfo.take_log is not False\n\n            # override from user configuration if any\n            log, linthresh = get_default_from_config(\n                self.data_source,\n                field=field,\n                keys=[\"log\", \"linthresh\"],\n                defaults=[None, None],\n            )\n            if linthresh is not None:\n                self.set_log(field, linthresh=linthresh)\n            elif log is not None:\n                self.set_log(field, log)\n\n    def __iter__(self):\n        for ds in self.ts:\n            mylog.warning(\"Switching to %s\", ds)\n            self._switch_ds(ds)\n            yield self\n\n    def piter(self, *args, **kwargs):\n        for ds in self.ts.piter(*args, **kwargs):\n            self._switch_ds(ds)\n            yield self\n\n    @property\n    def frb(self):\n        # Force the regeneration of the fixed resolution buffer\n        # * if there's none\n        # * if the data has been invalidated\n        # * if the frb has been inalidated\n        if not self._data_valid:\n            self._recreate_frb()\n        return self._frb\n\n    @frb.setter\n    def frb(self, value):\n        self._frb = value\n        self._data_valid = True\n\n    @frb.deleter\n    def frb(self):\n        del self._frb\n        self._frb = None\n\n    def _recreate_frb(self):\n        old_fields = None\n        old_filters = []\n        # If we are regenerating an frb, we want to know what fields we had before\n        if self._frb is not None:\n            old_fields = list(self._frb.data.keys())\n            old_units = [_.units for _ in self._frb.data.values()]\n            old_filters = self._frb._filters\n        # Set the bounds\n        if hasattr(self, \"zlim\"):\n            # Support OffAxisProjectionPlot and OffAxisSlicePlot\n            bounds = self.xlim + self.ylim + self.zlim\n        else:\n            bounds = self.xlim + self.ylim\n\n        # Generate the FRB\n        self.frb = self._frb_generator(\n            self.data_source,\n            bounds,\n            self.buff_size,\n            self.antialias,\n            periodic=self._periodic,\n            filters=old_filters,\n        )\n\n        # At this point the frb has the valid bounds, size, aliasing, etc.\n        if old_fields is not None:\n            # Restore the old fields\n            for key, units in zip(old_fields, old_units, strict=False):\n                self._frb.render(key)\n                equiv = self._equivalencies[key]\n                if equiv[0] is None:\n                    self._frb[key].convert_to_units(units)\n                else:\n                    self.frb.set_unit(key, units, equiv[0], equiv[1])\n\n        # Restore the override fields\n        for key in self.override_fields:\n            self._frb.render(key)\n\n    @property\n    def _has_swapped_axes(self):\n        # note: we always run the validations here in case the states of\n        # the conflicting attributes have changed.\n        return self._validate_swap_axes(self._swap_axes_input)\n\n    @invalidate_data\n    def swap_axes(self):\n        # toggles the swap_axes behavior\n        new_swap_value = not self._swap_axes_input\n        # note: we also validate here to catch invalid states immediately, even\n        # though we validate on accessing the attribute in `_has_swapped_axes`.\n        self._swap_axes_input = self._validate_swap_axes(new_swap_value)\n        return self\n\n    def _validate_swap_axes(self, swap_value: bool) -> bool:\n        if swap_value and (self._transform or self._projection):\n            mylog.warning(\"Cannot swap axes due to transform or projection\")\n            return False\n        return swap_value\n\n    @property\n    def width(self):\n        Wx = self.xlim[1] - self.xlim[0]\n        Wy = self.ylim[1] - self.ylim[0]\n        return (Wx, Wy)\n\n    @property\n    def bounds(self):\n        return self.xlim + self.ylim\n\n    @invalidate_data\n    def zoom(self, factor):\n        r\"\"\"This zooms the window by *factor* > 0.\n        - zoom out with *factor* < 1\n        - zoom in with *factor* > 1\n\n        Parameters\n        ----------\n        factor : float\n            multiplier for the current width\n\n        \"\"\"\n        if factor <= 0:\n            raise ValueError(\"Only positive zooming factors are meaningful.\")\n        Wx, Wy = self.width\n        centerx = self.xlim[0] + Wx * 0.5\n        centery = self.ylim[0] + Wy * 0.5\n        nWx, nWy = Wx / factor, Wy / factor\n        self.xlim = (centerx - nWx * 0.5, centerx + nWx * 0.5)\n        self.ylim = (centery - nWy * 0.5, centery + nWy * 0.5)\n        return self\n\n    @invalidate_data\n    def pan(self, deltas):\n        r\"\"\"Pan the image by specifying absolute code unit coordinate deltas.\n\n        Parameters\n        ----------\n        deltas : Two-element sequence of floats, quantities, or (float, unit)\n                 tuples.\n\n            (delta_x, delta_y).  If a unit is not supplied the unit is assumed\n            to be code_length.\n\n        \"\"\"\n        if len(deltas) != 2:\n            raise TypeError(\n                f\"The pan function accepts a two-element sequence.\\nReceived {deltas}.\"\n            )\n        if isinstance(deltas[0], Number) and isinstance(deltas[1], Number):\n            deltas = (\n                self.ds.quan(deltas[0], \"code_length\"),\n                self.ds.quan(deltas[1], \"code_length\"),\n            )\n        elif isinstance(deltas[0], tuple) and isinstance(deltas[1], tuple):\n            deltas = (\n                self.ds.quan(deltas[0][0], deltas[0][1]),\n                self.ds.quan(deltas[1][0], deltas[1][1]),\n            )\n        elif isinstance(deltas[0], YTQuantity) and isinstance(deltas[1], YTQuantity):\n            pass\n        else:\n            raise TypeError(\n                \"The arguments of the pan function must be a sequence of floats,\\n\"\n                f\"quantities, or (float, unit) tuples. Received {deltas}\"\n            )\n        self.xlim = (self.xlim[0] + deltas[0], self.xlim[1] + deltas[0])\n        self.ylim = (self.ylim[0] + deltas[1], self.ylim[1] + deltas[1])\n        return self\n\n    @invalidate_data\n    def pan_rel(self, deltas):\n        r\"\"\"Pan the image by specifying relative deltas, to the FOV.\n\n        Parameters\n        ----------\n        deltas : sequence of floats\n            (delta_x, delta_y) in *relative* code unit coordinates\n\n        \"\"\"\n        Wx, Wy = self.width\n        self.xlim = (self.xlim[0] + Wx * deltas[0], self.xlim[1] + Wx * deltas[0])\n        self.ylim = (self.ylim[0] + Wy * deltas[1], self.ylim[1] + Wy * deltas[1])\n        return self\n\n    @invalidate_plot\n    def set_unit(self, field, new_unit, equivalency=None, equivalency_kwargs=None):\n        \"\"\"Sets a new unit for the requested field\n\n        parameters\n        ----------\n        field : string or field tuple\n           The name of the field that is to be changed.\n\n        new_unit : string or Unit object\n\n        equivalency : string, optional\n           If set, the equivalency to use to convert the current units to\n           the new requested unit. If None, the unit conversion will be done\n           without an equivalency\n\n        equivalency_kwargs : string, optional\n           Keyword arguments to be passed to the equivalency. Only used if\n           ``equivalency`` is set.\n        \"\"\"\n        for f, u in zip(iter_fields(field), always_iterable(new_unit), strict=True):\n            self.frb.set_unit(f, u, equivalency, equivalency_kwargs)\n            self._equivalencies[f] = (equivalency, equivalency_kwargs)\n            pnh = self.plots[f].norm_handler\n            pnh.display_units = u\n        return self\n\n    @invalidate_plot\n    def set_origin(self, origin):\n        \"\"\"Set the plot origin.\n\n        Parameters\n        ----------\n        origin : string or length 1, 2, or 3 sequence.\n           The location of the origin of the plot coordinate system. This\n           is typically represented by a '-' separated string or a tuple of\n           strings. In the first index the y-location is given by 'lower',\n           'upper', or 'center'. The second index is the x-location, given as\n           'left', 'right', or 'center'. Finally, whether the origin is\n           applied in 'domain' space, plot 'window' space or 'native'\n           simulation coordinate system is given. For example, both\n           'upper-right-domain' and ['upper', 'right', 'domain'] place the\n           origin in the upper right hand corner of domain space. If x or y\n           are not given, a value is inferred. For instance, 'left-domain'\n           corresponds to the lower-left hand corner of the simulation domain,\n           'center-domain' corresponds to the center of the simulation domain,\n           or 'center-window' for the center of the plot window. In the event\n           that none of these options place the origin in a desired location,\n           a sequence of tuples and a string specifying the\n           coordinate space can be given. If plain numeric types are input,\n           units of `code_length` are assumed. Further examples:\n\n        ===============================================  ===============================\n        format                                           example\n        ===============================================  ===============================\n        '{space}'                                        'domain'\n        '{xloc}-{space}'                                 'left-window'\n        '{yloc}-{space}'                                 'upper-domain'\n        '{yloc}-{xloc}-{space}'                          'lower-right-window'\n        ('{space}',)                                     ('window',)\n        ('{xloc}', '{space}')                            ('right', 'domain')\n        ('{yloc}', '{space}')                            ('lower', 'window')\n        ('{yloc}', '{xloc}', '{space}')                  ('lower', 'right', 'window')\n        ((yloc, '{unit}'), (xloc, '{unit}'), '{space}')  ((0, 'm'), (.4, 'm'), 'window')\n        (xloc, yloc, '{space}')                          (0.23, 0.5, 'domain')\n        ===============================================  ===============================\n        \"\"\"\n        self.origin = origin\n        return self\n\n    @invalidate_plot\n    @invalidate_figure\n    def set_mpl_projection(self, mpl_proj):\n        r\"\"\"\n        Set the matplotlib projection type with a cartopy transform function\n\n        Given a string or a tuple argument, this will project the data onto\n        the plot axes with the chosen transform function.\n\n        Assumes that the underlying data has a PlateCarree transform type.\n\n        To annotate the plot with coastlines or other annotations,\n        `render()` will need to be called after this function\n        to make the axes available for annotation.\n\n        Parameters\n        ----------\n\n        mpl_proj : string or tuple\n           if passed as a string, mpl_proj is the specified projection type,\n           if passed as a tuple, then tuple will take the form of\n           ``(\"ProjectionType\", (args))`` or ``(\"ProjectionType\", (args), {kwargs})``\n           Valid projection type options include:\n           'PlateCarree', 'LambertConformal', 'LabmbertCylindrical',\n           'Mercator', 'Miller', 'Mollweide', 'Orthographic',\n           'Robinson', 'Stereographic', 'TransverseMercator',\n           'InterruptedGoodeHomolosine', 'RotatedPole', 'OGSB',\n           'EuroPP', 'Geostationary', 'Gnomonic', 'NorthPolarStereo',\n           'OSNI', 'SouthPolarStereo', 'AlbersEqualArea',\n           'AzimuthalEquidistant', 'Sinusoidal', 'UTM',\n           'NearsidePerspective', 'LambertAzimuthalEqualArea'\n\n        Examples\n        --------\n\n        This will create a Mollweide projection using Mollweide default values\n        and annotate it with coastlines\n\n        >>> import yt\n        >>> ds = yt.load(\"\")\n        >>> p = yt.SlicePlot(ds, \"altitude\", \"AIRDENS\")\n        >>> p.set_mpl_projection(\"AIRDENS\", \"Mollweide\")\n        >>> p.render()\n        >>> p.plots[\"AIRDENS\"].axes.coastlines()\n        >>> p.show()\n\n        This will move the PlateCarree central longitude to 90 degrees and\n        annotate with coastlines.\n\n        >>> import yt\n        >>> ds = yt.load(\"\")\n        >>> p = yt.SlicePlot(ds, \"altitude\", \"AIRDENS\")\n        >>> p.set_mpl_projection(\n        ...     \"AIRDENS\", (\"PlateCarree\", (), {\"central_longitude\": 90, \"globe\": None})\n        ... )\n        >>> p.render()\n        >>> p.plots[\"AIRDENS\"].axes.set_global()\n        >>> p.plots[\"AIRDENS\"].axes.coastlines()\n        >>> p.show()\n\n\n        This will create a RoatatedPole projection with the unrotated pole\n        position at 37.5 degrees latitude and 177.5 degrees longitude by\n        passing them in as args.\n\n\n        >>> import yt\n        >>> ds = yt.load(\"\")\n        >>> p = yt.SlicePlot(ds, \"altitude\", \"AIRDENS\")\n        >>> p.set_mpl_projection(\"RotatedPole\", (177.5, 37.5))\n        >>> p.render()\n        >>> p.plots[\"AIRDENS\"].axes.set_global()\n        >>> p.plots[\"AIRDENS\"].axes.coastlines()\n        >>> p.show()\n\n        This will create a RoatatedPole projection with the unrotated pole\n        position at 37.5 degrees latitude and 177.5 degrees longitude by\n        passing them in as kwargs.\n\n        >>> import yt\n        >>> ds = yt.load(\"\")\n        >>> p = yt.SlicePlot(ds, \"altitude\", \"AIRDENS\")\n        >>> p.set_mpl_projection(\n        ...     (\"RotatedPole\", (), {\"pole_latitude\": 37.5, \"pole_longitude\": 177.5})\n        ... )\n        >>> p.render()\n        >>> p.plots[\"AIRDENS\"].axes.set_global()\n        >>> p.plots[\"AIRDENS\"].axes.coastlines()\n        >>> p.show()\n\n        \"\"\"\n\n        self._projection = get_mpl_transform(mpl_proj)\n        axname = self.ds.coordinates.axis_name[self.data_source.axis]\n        transform = self.ds.coordinates.data_transform[axname]\n        self._transform = get_mpl_transform(transform)\n        return self\n\n    @invalidate_data\n    def _set_window(self, bounds):\n        \"\"\"Set the bounds of the plot window.\n        This is normally only called internally, see set_width.\n\n\n        Parameters\n        ----------\n\n        bounds : a four element sequence of floats\n            The x and y bounds, in the format (x0, x1, y0, y1)\n\n        \"\"\"\n        if self.center is not None:\n            dx = bounds[1] - bounds[0]\n            dy = bounds[3] - bounds[2]\n            self.xlim = (self.center[0] - dx / 2.0, self.center[0] + dx / 2.0)\n            self.ylim = (self.center[1] - dy / 2.0, self.center[1] + dy / 2.0)\n        else:\n            self.xlim = tuple(bounds[0:2])\n            self.ylim = tuple(bounds[2:4])\n            if len(bounds) == 6:\n                # Support OffAxisProjectionPlot and OffAxisSlicePlot\n                self.zlim = tuple(bounds[4:6])\n        mylog.info(\"xlim = %f %f\", self.xlim[0], self.xlim[1])\n        mylog.info(\"ylim = %f %f\", self.ylim[0], self.ylim[1])\n        if hasattr(self, \"zlim\"):\n            mylog.info(\"zlim = %f %f\", self.zlim[0], self.zlim[1])\n\n    @invalidate_data\n    def set_width(self, width, unit=None):\n        \"\"\"set the width of the plot window\n\n        parameters\n        ----------\n        width : float, array of floats, (float, unit) tuple, or tuple of\n                (float, unit) tuples.\n\n             Width can have four different formats to support windows with\n             variable x and y widths.  They are:\n\n             ==================================     =======================\n             format                                 example\n             ==================================     =======================\n             (float, string)                        (10,'kpc')\n             ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n             float                                  0.2\n             (float, float)                         (0.2, 0.3)\n             ==================================     =======================\n\n             For example, (10, 'kpc') requests a plot window that is 10\n             kiloparsecs wide in the x and y directions,\n             ((10,'kpc'),(15,'kpc')) requests a window that is 10 kiloparsecs\n             wide along the x axis and 15 kiloparsecs wide along the y axis.\n             In the other two examples, code units are assumed, for example\n             (0.2, 0.3) requests a plot that has an x width of 0.2 and a y\n             width of 0.3 in code units.  If units are provided the resulting\n             plot axis labels will use the supplied units.\n        unit : str\n             the unit the width has been specified in. If width is a tuple, this\n             argument is ignored. Defaults to code units.\n        \"\"\"\n\n        if isinstance(width, Number):\n            if unit is None:\n                width = (width, \"code_length\")\n            else:\n                width = (width, fix_unitary(unit))\n\n        axes_unit = get_axes_unit(width, self.ds)\n\n        width = self.ds.coordinates.sanitize_width(self.frb.axis, width, None)\n\n        centerx = (self.xlim[1] + self.xlim[0]) / 2.0\n        centery = (self.ylim[1] + self.ylim[0]) / 2.0\n\n        self.xlim = (centerx - width[0] / 2, centerx + width[0] / 2)\n        self.ylim = (centery - width[1] / 2, centery + width[1] / 2)\n\n        if hasattr(self, \"zlim\"):\n            centerz = (self.zlim[1] + self.zlim[0]) / 2.0\n            mw = self.ds.arr(width).max()\n            self.zlim = (centerz - mw / 2.0, centerz + mw / 2.0)\n\n        self.set_axes_unit(axes_unit)\n\n        return self\n\n    @invalidate_data\n    def set_center(self, new_center, unit=\"code_length\"):\n        \"\"\"Sets a new center for the plot window\n\n        parameters\n        ----------\n        new_center : two element sequence of floats\n            The coordinates of the new center of the image in the\n            coordinate system defined by the plot axes. If the unit\n            keyword is not specified, the coordinates are assumed to\n            be in code units.\n\n        unit : string\n            The name of the unit new_center is given in.  If new_center is a\n            YTArray or tuple of YTQuantities, this keyword is ignored.\n\n        \"\"\"\n        error = RuntimeError(\n            \"\\n\"\n            \"new_center must be a two-element list or tuple of floats \\n\"\n            \"corresponding to a coordinate in the plot relative to \\n\"\n            \"the plot coordinate system.\\n\"\n        )\n        if new_center is None:\n            self.center = None\n        elif is_sequence(new_center):\n            if len(new_center) != 2:\n                raise error\n            for el in new_center:\n                if not isinstance(el, Number) and not isinstance(el, YTQuantity):\n                    raise error\n            if isinstance(new_center[0], Number):\n                new_center = [self.ds.quan(c, unit) for c in new_center]\n            self.center = new_center\n        else:\n            raise error\n        self._set_window(self.bounds)\n        return self\n\n    @invalidate_data\n    def set_antialias(self, aa):\n        \"\"\"Turn antialiasing on or off.\n\n        parameters\n        ----------\n        aa : boolean\n        \"\"\"\n        self.antialias = aa\n\n    @invalidate_data\n    def set_buff_size(self, size):\n        \"\"\"Sets a new buffer size for the fixed resolution buffer\n\n        parameters\n        ----------\n        size : int or two element sequence of ints\n            The number of data elements in the buffer on the x and y axes.\n            If a scalar is provided,  then the buffer is assumed to be square.\n        \"\"\"\n        if is_sequence(size):\n            self.buff_size = size\n        else:\n            self.buff_size = (size, size)\n        return self\n\n    @invalidate_plot\n    def set_axes_unit(self, unit_name):\n        r\"\"\"Set the unit for display on the x and y axes of the image.\n\n        Parameters\n        ----------\n        unit_name : string or two element tuple of strings\n            A unit, available for conversion in the dataset, that the\n            image extents will be displayed in.  If set to None, any previous\n            units will be reset.  If the unit is None, the default is chosen.\n            If unit_name is '1', 'u', or 'unitary', it will not display the\n            units, and only show the axes name. If unit_name is a tuple, the\n            first element is assumed to be the unit for the x axis and the\n            second element the unit for the y axis.\n\n        Raises\n        ------\n        YTUnitNotRecognized\n            If the unit is not known, this will be raised.\n\n        Examples\n        --------\n\n        >>> from yt import load\n        >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> p = ProjectionPlot(ds, \"y\", \"Density\")\n        >>> p.set_axes_unit(\"kpc\")\n\n        \"\"\"\n        # blind except because it could be in conversion_factors or units\n        if unit_name is not None:\n            if isinstance(unit_name, str):\n                unit_name = (unit_name, unit_name)\n            for un in unit_name:\n                try:\n                    self.ds.length_unit.in_units(un)\n                except (UnitConversionError, UnitParseError) as e:\n                    raise YTUnitNotRecognized(un) from e\n        self._axes_unit_names = unit_name\n        return self\n\n    @invalidate_plot\n    def flip_horizontal(self):\n        \"\"\"\n        inverts the horizontal axis (the image's abscissa)\n        \"\"\"\n        self._flip_horizontal = not self._flip_horizontal\n        return self\n\n    @invalidate_plot\n    def flip_vertical(self):\n        \"\"\"\n        inverts the vertical axis (the image's ordinate)\n        \"\"\"\n        self._flip_vertical = not self._flip_vertical\n        return self\n\n    def to_fits_data(self, fields=None, other_keys=None, length_unit=None, **kwargs):\n        r\"\"\"Export the fields in this PlotWindow instance\n        to a FITSImageData instance.\n\n        This will export a set of FITS images of either the fields specified\n        or all the fields already in the object.\n\n        Parameters\n        ----------\n        fields : list of strings\n            These fields will be pixelized and output. If \"None\", the keys of\n            the FRB will be used.\n        other_keys : dictionary, optional\n            A set of header keys and values to write into the FITS header.\n        length_unit : string, optional\n            the length units that the coordinates are written in. The default\n            is to use the default length unit of the dataset.\n        \"\"\"\n        return self.frb.to_fits_data(\n            fields=fields, other_keys=other_keys, length_unit=length_unit, **kwargs\n        )\n\n\nclass PWViewerMPL(PlotWindow):\n    \"\"\"Viewer using matplotlib as a backend via the WindowPlotMPL.\"\"\"\n\n    _current_field = None\n    _frb_generator: type[FixedResolutionBuffer] | None = None\n    _plot_type: str | None = None\n\n    def __init__(self, *args, **kwargs) -> None:\n        if self._frb_generator is None:\n            self._frb_generator = kwargs.pop(\"frb_generator\")\n        if self._plot_type is None:\n            self._plot_type = kwargs.pop(\"plot_type\")\n        self._splat_color = kwargs.pop(\"splat_color\", None)\n        PlotWindow.__init__(self, *args, **kwargs)\n\n        # import type here to avoid import cycles\n        # note that this import statement is actually crucial at runtime:\n        # the filter methods for the present class are defined only when\n        # fixed_resolution_filters is imported, so we need to guarantee\n        # that it happens no later than instantiation\n\n        self._callbacks: list[PlotCallback] = []\n\n    @property\n    def _data_valid(self) -> bool:\n        return self._frb is not None and self._frb._data_valid\n\n    @_data_valid.setter\n    def _data_valid(self, value):\n        if self._frb is None:\n            # we delegate the (in)validation responsibility to the FRB\n            # if we don't have one yet, we can exit without doing anything\n            return\n        else:\n            self._frb._data_valid = value\n\n    def _setup_origin(self):\n        origin = self.origin\n        axis_index = self.data_source.axis\n        xc = None\n        yc = None\n\n        if isinstance(origin, str):\n            origin = tuple(origin.split(\"-\"))\n\n        if len(origin) > 3:\n            raise ValueError(\n                \"Invalid origin argument with too many elements; \"\n                f\"expected 1, 2 or 3 elements, got {self.origin!r}, counting {len(origin)} elements. \"\n                \"Use '-' as a separator for string arguments.\"\n            )\n\n        if len(origin) == 1:\n            coord_system = origin[0]\n            if coord_system not in (\"window\", \"domain\", \"native\"):\n                raise ValueError(\n                    \"Invalid origin argument. \"\n                    \"Single element specification must be 'window', 'domain', or 'native'. \"\n                    f\"Got {self.origin!r}\"\n                )\n            origin = (\"lower\", \"left\", coord_system)\n\n        elif len(origin) == 2:\n            err_msg = \"Invalid origin argument. Using 2 elements:\\n\"\n\n            if origin[0] in (\"left\", \"right\", \"center\"):\n                o0map = {\"left\": \"lower\", \"right\": \"upper\", \"center\": \"center\"}\n                origin = (o0map[origin[0]],) + origin\n            elif origin[0] in (\"lower\", \"upper\"):\n                origin = (origin[0], \"center\", origin[-1])\n            else:\n                err_msg += \" - the first one must be 'left', 'right', 'lower', 'upper' or 'center'\\n\"\n\n            if origin[-1] not in (\"window\", \"domain\", \"native\"):\n                err_msg += \" - the second one must be 'window', 'domain', or 'native'\\n\"\n\n            if len(err_msg.split(\"\\n\")) > 2:\n                err_msg += f\"Got {self.origin!r}\"\n                raise ValueError(err_msg)\n\n        elif len(origin) == 3:\n            err_msg = \"Invalid origin argument. Using 3 elements:\\n\"\n            if isinstance(origin[0], (int, float)):\n                xc = self.ds.quan(origin[0], \"code_length\")\n            elif isinstance(origin[0], tuple):\n                xc = self.ds.quan(*origin[0])\n            elif origin[0] not in (\"lower\", \"upper\", \"center\"):\n                err_msg += \" - the first one must be 'lower', 'upper' or 'center' or a distance\\n\"\n\n            if isinstance(origin[1], (int, float)):\n                yc = self.ds.quan(origin[1], \"code_length\")\n            elif isinstance(origin[1], tuple):\n                yc = self.ds.quan(*origin[1])\n            elif origin[1] not in (\"left\", \"right\", \"center\"):\n                err_msg += \" - the second one must be 'left', 'right', 'center' or a distance\\n\"\n\n            if origin[-1] not in (\"window\", \"domain\", \"native\"):\n                err_msg += \" - the third one must be 'window', 'domain', or 'native'\\n\"\n\n            if len(err_msg.split(\"\\n\")) > 2:\n                err_msg += f\"Got {self.origin!r}\"\n                raise ValueError(err_msg)\n\n        assert not isinstance(origin, str)\n        assert len(origin) == 3\n        assert origin[2] in (\"window\", \"domain\", \"native\")\n\n        if origin[2] == \"window\":\n            xllim, xrlim = self.xlim\n            yllim, yrlim = self.ylim\n        elif origin[2] == \"domain\":\n            xax = self.ds.coordinates.x_axis[axis_index]\n            yax = self.ds.coordinates.y_axis[axis_index]\n            xllim = self.ds.domain_left_edge[xax]\n            xrlim = self.ds.domain_right_edge[xax]\n            yllim = self.ds.domain_left_edge[yax]\n            yrlim = self.ds.domain_right_edge[yax]\n        elif origin[2] == \"native\":\n            return (self.ds.quan(0.0, \"code_length\"), self.ds.quan(0.0, \"code_length\"))\n\n        if xc is None and yc is None:\n            assert origin[0] in (\"lower\", \"upper\", \"center\")\n            assert origin[1] in (\"left\", \"right\", \"center\")\n\n            if origin[0] == \"lower\":\n                yc = yllim\n            elif origin[0] == \"upper\":\n                yc = yrlim\n            elif origin[0] == \"center\":\n                yc = (yllim + yrlim) / 2.0\n\n            if origin[1] == \"left\":\n                xc = xllim\n            elif origin[1] == \"right\":\n                xc = xrlim\n            elif origin[1] == \"center\":\n                xc = (xllim + xrlim) / 2.0\n\n        x_in_bounds = xc >= xllim and xc <= xrlim\n        y_in_bounds = yc >= yllim and yc <= yrlim\n\n        if not x_in_bounds and not y_in_bounds:\n            raise ValueError(\n                \"origin inputs not in bounds of specified coordinate system domain; \"\n                f\"got {self.origin!r} Bounds are {xllim, xrlim} and {yllim, yrlim} respectively\"\n            )\n\n        return xc, yc\n\n    def _setup_plots(self):\n        from matplotlib.mathtext import MathTextParser\n\n        if self._plot_valid:\n            return\n        if not self._data_valid:\n            self._recreate_frb()\n        self._colorbar_valid = True\n        field_list = list(set(self.data_source._determine_fields(self.fields)))\n        for f in field_list:\n            axis_index = self.data_source.axis\n\n            xc, yc = self._setup_origin()\n            if self.ds._uses_code_length_unit:\n                # this should happen only if the dataset was initialized with\n                # argument unit_system=\"code\" or if it's set to have no CGS\n                # equivalent.  This only needs to happen here in the specific\n                # case that we're doing a computationally intense operation\n                # like using cartopy, but it prevents crashes in that case.\n                (unit_x, unit_y) = (\"code_length\", \"code_length\")\n            elif self._axes_unit_names is None:\n                unit = self.ds.get_smallest_appropriate_unit(\n                    self.xlim[1] - self.xlim[0]\n                )\n                unit_x = unit_y = unit\n                coords = self.ds.coordinates\n                if hasattr(coords, \"image_units\"):\n                    # check for special cases defined in\n                    # non cartesian CoordinateHandler subclasses\n                    image_units = coords.image_units[coords.axis_id[axis_index]]\n                    if image_units[0] in (\"deg\", \"rad\"):\n                        unit_x = \"code_length\"\n                    elif image_units[0] == 1:\n                        unit_x = \"dimensionless\"\n                    if image_units[1] in (\"deg\", \"rad\"):\n                        unit_y = \"code_length\"\n                    elif image_units[1] == 1:\n                        unit_y = \"dimensionless\"\n            else:\n                (unit_x, unit_y) = self._axes_unit_names\n\n            # For some plots we may set aspect by hand, such as for spectral cube data.\n            # This will likely be replaced at some point by the coordinate handler\n            # setting plot aspect.\n            if self.aspect is None:\n                self.aspect = float(\n                    (self.ds.quan(1.0, unit_y) / self.ds.quan(1.0, unit_x)).in_cgs()\n                )\n            extentx = (self.xlim - xc)[:2]\n            extenty = (self.ylim - yc)[:2]\n\n            # extentx/y arrays inherit units from xlim and ylim attributes\n            # and these attributes are always length even for angular and\n            # dimensionless axes so we need to strip out units for consistency\n            if unit_x == \"dimensionless\":\n                extentx = extentx / extentx.units\n            else:\n                extentx.convert_to_units(unit_x)\n            if unit_y == \"dimensionless\":\n                extenty = extenty / extenty.units\n            else:\n                extenty.convert_to_units(unit_y)\n\n            extent = [*extentx, *extenty]\n\n            image = self.frb.get_image(f)\n            mask = self.frb.get_mask(f)\n            assert mask is None or mask.dtype == bool\n\n            font_size = self._font_properties.get_size()\n\n            if f in self.plots.keys():\n                pnh = self.plots[f].norm_handler\n                cbh = self.plots[f].colorbar_handler\n            else:\n                pnh, cbh = self._get_default_handlers(\n                    field=f, default_display_units=image.units\n                )\n                if pnh.display_units != image.units:\n                    equivalency, equivalency_kwargs = self._equivalencies[f]\n                    image.convert_to_units(\n                        pnh.display_units, equivalency, **equivalency_kwargs\n                    )\n\n            fig = None\n            axes = None\n            cax = None\n            draw_axes = True\n            draw_frame = None\n            if f in self.plots:\n                draw_axes = self.plots[f]._draw_axes\n                draw_frame = self.plots[f]._draw_frame\n                if self.plots[f].figure is not None:\n                    fig = self.plots[f].figure\n                    axes = self.plots[f].axes\n                    cax = self.plots[f].cax\n\n            # This is for splatting particle positions with a single\n            # color instead of a colormap\n            if self._splat_color is not None:\n                # make image a rgba array, using the splat color\n                greyscale_image = self.frb[f]\n                ia = np.zeros((greyscale_image.shape[0], greyscale_image.shape[1], 4))\n                ia[:, :, 3] = 0.0  # set alpha to 0.0\n                locs = greyscale_image > 0.0\n                to_rgba = matplotlib.colors.colorConverter.to_rgba\n                color_tuple = to_rgba(self._splat_color)\n                ia[locs] = color_tuple\n                ia = ImageArray(ia)\n            else:\n                ia = image\n\n            swap_axes = self._has_swapped_axes\n            aspect = self.aspect\n            if swap_axes:\n                extent = _swap_axes_extents(extent)\n                ia = ia.transpose()\n                aspect = 1.0 / aspect  # aspect ends up passed to imshow(aspect=aspect)\n\n            self.plots[f] = WindowPlotMPL(\n                ia,\n                extent,\n                self.figure_size,\n                font_size,\n                aspect,\n                fig,\n                axes,\n                cax,\n                self._projection,\n                self._transform,\n                norm_handler=pnh,\n                colorbar_handler=cbh,\n                alpha=mask.astype(\"float64\") if mask is not None else None,\n            )\n\n            axes_unit_labels = self._get_axes_unit_labels(unit_x, unit_y)\n\n            if self.oblique:\n                labels = [\n                    r\"$\\rm{Image\\ x\" + axes_unit_labels[0] + \"}$\",\n                    r\"$\\rm{Image\\ y\" + axes_unit_labels[1] + \"}$\",\n                ]\n            else:\n                coordinates = self.ds.coordinates\n                axis_names = coordinates.image_axis_name[axis_index]\n                xax = coordinates.x_axis[axis_index]\n                yax = coordinates.y_axis[axis_index]\n\n                if hasattr(coordinates, \"axis_default_unit_name\"):\n                    axes_unit_labels = [\n                        coordinates.axis_default_unit_name[xax],\n                        coordinates.axis_default_unit_name[yax],\n                    ]\n                labels = [\n                    r\"$\\rm{\" + axis_names[0] + axes_unit_labels[0] + r\"}$\",\n                    r\"$\\rm{\" + axis_names[1] + axes_unit_labels[1] + r\"}$\",\n                ]\n\n                if hasattr(coordinates, \"axis_field\"):\n                    if xax in coordinates.axis_field:\n                        xmin, xmax = coordinates.axis_field[xax](\n                            0, self.xlim, self.ylim\n                        )\n                    else:\n                        xmin, xmax = (float(x) for x in extentx)\n                    if yax in coordinates.axis_field:\n                        ymin, ymax = coordinates.axis_field[yax](\n                            1, self.xlim, self.ylim\n                        )\n                    else:\n                        ymin, ymax = (float(y) for y in extenty)\n                    new_extent = (xmin, xmax, ymin, ymax)\n                    if swap_axes:\n                        new_extent = _swap_axes_extents(new_extent)\n                    self.plots[f].image.set_extent(new_extent)\n                    self.plots[f].axes.set_aspect(\"auto\")\n\n            x_label, y_label, colorbar_label = self._get_axes_labels(f)\n\n            if x_label is not None:\n                labels[0] = x_label\n            if y_label is not None:\n                labels[1] = y_label\n\n            if swap_axes:\n                labels.reverse()\n\n            self.plots[f].axes.set_xlabel(labels[0])\n            self.plots[f].axes.set_ylabel(labels[1])\n\n            # Determine the units of the data\n            units = Unit(image.units, registry=self.ds.unit_registry)\n            units = units.latex_representation()\n\n            if colorbar_label is None:\n                colorbar_label = image.info[\"label\"]\n                if getattr(self, \"moment\", 1) == 2:\n                    colorbar_label = f\"{colorbar_label} \\\\rm{{Standard Deviation}}\"\n                if hasattr(self, \"projected\"):\n                    colorbar_label = f\"$\\\\rm{{Projected }}$ {colorbar_label}\"\n                if units is not None and units != \"\":\n                    colorbar_label += _get_units_label(units)\n\n            parser = MathTextParser(\"Agg\")\n\n            try:\n                parser.parse(colorbar_label)\n            except Exception as err:\n                # unspecified exceptions might be raised from matplotlib via its own dependencies\n                raise YTCannotParseUnitDisplayName(f, colorbar_label, str(err)) from err\n\n            self.plots[f].cb.set_label(colorbar_label)\n\n            # x-y axes minorticks\n            if f not in self._minorticks:\n                self._minorticks[f] = True\n            if self._minorticks[f]:\n                self.plots[f].axes.minorticks_on()\n            else:\n                self.plots[f].axes.minorticks_off()\n\n            if not draw_axes:\n                self.plots[f]._toggle_axes(draw_axes, draw_frame)\n\n        self._set_font_properties()\n        self.run_callbacks()\n\n        if self._flip_horizontal or self._flip_vertical:\n            # some callbacks (e.g., streamlines) fail when applied to a\n            # flipped axis, so flip only at the end.\n            for f in field_list:\n                if self._flip_horizontal:\n                    ax = self.plots[f].axes\n                    ax.invert_xaxis()\n\n                if self._flip_vertical:\n                    ax = self.plots[f].axes\n                    ax.invert_yaxis()\n\n        self._plot_valid = True\n\n    def setup_callbacks(self):\n        issue_deprecation_warning(\n            \"The PWViewer.setup_callbacks method is a no-op.\",\n            since=\"4.1\",\n            stacklevel=3,\n        )\n\n    @invalidate_plot\n    def clear_annotations(self, index: int | None = None):\n        \"\"\"\n        Clear callbacks from the plot.  If index is not set, clear all\n        callbacks.  If index is set, clear that index (ie 0 is the first one\n        created, 1 is the 2nd one created, -1 is the last one created, etc.)\n        \"\"\"\n        if index is None:\n            self._callbacks.clear()\n        else:\n            self._callbacks.pop(index)\n        return self\n\n    def list_annotations(self):\n        \"\"\"\n        List the current callbacks for the plot, along with their index.  This\n        index can be used with `clear_annotations` to remove a callback from the\n        current plot.\n        \"\"\"\n        for i, cb in enumerate(self._callbacks):\n            print(i, cb)\n\n    def run_callbacks(self):\n        for f in self.fields:\n            keys = self.frb.keys()\n            for callback in self._callbacks:\n                # need to pass _swap_axes and adjust all the callbacks\n                cbw = CallbackWrapper(\n                    self,\n                    self.plots[f],\n                    self.frb,\n                    f,\n                    self._font_properties,\n                    self._font_color,\n                )\n                try:\n                    callback(cbw)\n                except (NotImplementedError, YTDataTypeUnsupported):\n                    raise\n                except Exception as e:\n                    raise YTPlotCallbackError(callback._type_name) from e\n            for key in self.frb.keys():\n                if key not in keys:\n                    del self.frb[key]\n\n    def export_to_mpl_figure(\n        self,\n        nrows_ncols,\n        axes_pad=1.0,\n        label_mode=\"L\",\n        cbar_location=\"right\",\n        cbar_size=\"5%\",\n        cbar_mode=\"each\",\n        cbar_pad=\"0%\",\n    ):\n        r\"\"\"\n        Creates a matplotlib figure object with the specified axes arrangement,\n        nrows_ncols, and maps the underlying figures to the matplotlib axes.\n        Note that all of these parameters are fed directly to the matplotlib ImageGrid\n        class to create the new figure layout.\n\n        Parameters\n        ----------\n\n        nrows_ncols : tuple\n           the number of rows and columns of the axis grid (e.g., nrows_ncols=(2,2,))\n        axes_pad : float\n           padding between axes in inches\n        label_mode : one of \"L\", \"1\", \"all\"\n           arrangement of axes that are labeled\n        cbar_location : one of \"left\", \"right\", \"bottom\", \"top\"\n           where to place the colorbar\n        cbar_size : string (percentage)\n           scaling of the colorbar (e.g., \"5%\")\n        cbar_mode : one of \"each\", \"single\", \"edge\", None\n           how to represent the colorbar\n        cbar_pad : string (percentage)\n           padding between the axis and colorbar (e.g. \"5%\")\n\n        Returns\n        -------\n\n        The return is a matplotlib figure object.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load_sample(\"IsolatedGalaxy\")\n        >>> fields = [\"density\", \"velocity_x\", \"velocity_y\", \"velocity_magnitude\"]\n        >>> p = yt.SlicePlot(ds, \"z\", fields)\n        >>> p.set_log(\"velocity_x\", False)\n        >>> p.set_log(\"velocity_y\", False)\n        >>> fig = p.export_to_mpl_figure((2, 2))\n        >>> fig.tight_layout()\n        >>> fig.savefig(\"test.png\")\n\n        \"\"\"\n        import matplotlib.pyplot as plt\n        from mpl_toolkits.axes_grid1 import ImageGrid\n\n        fig = plt.figure()\n        grid = ImageGrid(\n            fig,\n            111,\n            nrows_ncols=nrows_ncols,\n            axes_pad=axes_pad,\n            label_mode=label_mode,\n            cbar_location=cbar_location,\n            cbar_size=cbar_size,\n            cbar_mode=cbar_mode,\n            cbar_pad=cbar_pad,\n        )\n\n        fields = self.fields\n        if len(fields) > len(grid):\n            raise IndexError(\"not enough axes for the number of fields\")\n\n        for i, f in enumerate(self.fields):\n            plot = self.plots[f]\n            plot.figure = fig\n            plot.axes = grid[i].axes\n            plot.cax = grid.cbar_axes[i]\n\n        self._setup_plots()\n\n        return fig\n\n\nclass NormalPlot:\n    \"\"\"This is the abstraction for SlicePlot and ProjectionPlot, where\n    we define the common sanitizing mechanism for user input (normal direction).\n    It is implemented as a mixin class.\n    \"\"\"\n\n    @staticmethod\n    def sanitize_normal_vector(ds, normal) -> str | np.ndarray:\n        \"\"\"Return the name of a cartesian axis whener possible,\n        or a 3-element 1D ndarray of float64 in any other valid case.\n        Fail with a descriptive error message otherwise.\n        \"\"\"\n        axis_names = ds.coordinates.axis_order\n\n        if isinstance(normal, str):\n            if normal not in axis_names:\n                names_str = \", \".join(f\"'{name}'\" for name in axis_names)\n                raise ValueError(\n                    f\"'{normal}' is not a valid axis name. Expected one of {names_str}.\"\n                )\n            return normal\n\n        if isinstance(normal, (int, np.integer)):\n            if normal not in (0, 1, 2):\n                raise ValueError(\n                    f\"{normal} is not a valid axis identifier. Expected either 0, 1, or 2.\"\n                )\n            return axis_names[normal]\n\n        if not is_sequence(normal):\n            raise TypeError(\n                f\"{normal} is not a valid normal vector identifier. \"\n                \"Expected a string, integer or sequence of 3 floats.\"\n            )\n\n        if len(normal) != 3:\n            raise ValueError(\n                f\"{normal} with length {len(normal)} is not a valid normal vector. \"\n                \"Expected a 3-element sequence.\"\n            )\n\n        try:\n            retv = np.array(normal, dtype=\"float64\")\n            if retv.shape != (3,):\n                raise ValueError(f\"{normal} is incorrectly shaped.\")\n        except ValueError as exc:\n            raise TypeError(f\"{normal} is not a valid normal vector.\") from exc\n\n        nonzero_idx = np.nonzero(retv)[0]\n        if len(nonzero_idx) == 0:\n            raise ValueError(f\"A null vector {normal} isn't a valid normal vector.\")\n        if len(nonzero_idx) == 1:\n            return axis_names[nonzero_idx[0]]\n\n        return retv\n\n\nclass SlicePlot(NormalPlot):\n    r\"\"\"\n    A dispatch class for :class:`yt.visualization.plot_window.AxisAlignedSlicePlot`\n    and :class:`yt.visualization.plot_window.OffAxisSlicePlot` objects.  This\n    essentially allows for a single entry point to both types of slice plots,\n    the distinction being determined by the specified normal vector to the\n    projection.\n\n    The returned plot object can be updated using one of the many helper\n    functions defined in PlotWindow.\n\n    Parameters\n    ----------\n\n    ds : :class:`yt.data_objects.static_output.Dataset`\n        This is the dataset object corresponding to the\n        simulation output to be plotted.\n    normal : int, str, or 3-element sequence of floats\n        This specifies the normal vector to the slice.\n        Valid int values are 0, 1 and 2. Corresponding str values depend on the\n        geometry of the dataset and are generally given by `ds.coordinates.axis_order`.\n        E.g. in cartesian they are 'x', 'y' and 'z'.\n        An arbitrary normal vector may be specified as a 3-element sequence of floats.\n\n        This returns a :class:`OffAxisSlicePlot` object or a\n        :class:`AxisAlignedSlicePlot` object, depending on whether the requested\n        normal directions corresponds to a natural axis of the dataset's geometry.\n\n    fields : a (or a list of) 2-tuple of strings (ftype, fname)\n         The name of the field(s) to be plotted.\n\n    The following are nominally keyword arguments passed onto the respective\n    slice plot objects generated by this function.\n\n    Keyword Arguments\n    -----------------\n\n    center : 'center', 'c', 'left', 'l', 'right', 'r', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n        The domain edges along the selected *axis* can be selected with\n        'left'/'l' and 'right'/'r' respectively.\n    width : tuple or a float.\n         Width can have four different formats to support windows with variable\n         x and y widths.  They are:\n\n         ==================================     =======================\n         format                                 example\n         ==================================     =======================\n         (float, string)                        (10,'kpc')\n         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n         float                                  0.2\n         (float, float)                         (0.2, 0.3)\n         ==================================     =======================\n\n         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs\n         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a\n         window that is 10 kiloparsecs wide along the x axis and 15\n         kiloparsecs wide along the y axis.  In the other two examples, code\n         units are assumed, for example (0.2, 0.3) requests a plot that has an\n         x width of 0.2 and a y width of 0.3 in code units.  If units are\n         provided the resulting plot axis labels will use the supplied units.\n    axes_unit : string\n         The name of the unit for the tick labels on the x and y axes.\n         Defaults to None, which automatically picks an appropriate unit.\n         If axes_unit is '1', 'u', or 'unitary', it will not display the\n         units, and only show the axes name.\n    origin : string or length 1, 2, or 3 sequence.\n         The location of the origin of the plot coordinate system for\n         `AxisAlignedSlicePlot` object; for `OffAxisSlicePlot` objects this\n         parameter is discarded. This is typically represented by a '-'\n         separated string or a tuple of strings. In the first index the\n         y-location is given by 'lower', 'upper', or 'center'. The second index\n         is the x-location, given as 'left', 'right', or 'center'. Finally, the\n         whether the origin is applied in 'domain' space, plot 'window' space or\n         'native' simulation coordinate system is given. For example, both\n         'upper-right-domain' and ['upper', 'right', 'domain'] place the\n         origin in the upper right hand corner of domain space. If x or y\n         are not given, a value is inferred. For instance, 'left-domain'\n         corresponds to the lower-left hand corner of the simulation domain,\n         'center-domain' corresponds to the center of the simulation domain,\n         or 'center-window' for the center of the plot window. In the event\n         that none of these options place the origin in a desired location,\n         a sequence of tuples and a string specifying the\n         coordinate space can be given. If plain numeric types are input,\n         units of `code_length` are assumed. Further examples:\n\n         =============================================== ===============================\n         format                                          example\n         =============================================== ===============================\n         '{space}'                                       'domain'\n         '{xloc}-{space}'                                'left-window'\n         '{yloc}-{space}'                                'upper-domain'\n         '{yloc}-{xloc}-{space}'                         'lower-right-window'\n         ('{space}',)                                    ('window',)\n         ('{xloc}', '{space}')                           ('right', 'domain')\n         ('{yloc}', '{space}')                           ('lower', 'window')\n         ('{yloc}', '{xloc}', '{space}')                 ('lower', 'right', 'window')\n         ((yloc, '{unit}'), (xloc, '{unit}'), '{space}') ((0, 'm'), (.4, 'm'), 'window')\n         (xloc, yloc, '{space}')                         (0.23, 0.5, 'domain')\n         =============================================== ===============================\n    north_vector : a sequence of floats\n        A vector defining the 'up' direction in the `OffAxisSlicePlot`; not\n        used in `AxisAlignedSlicePlot`.  This option sets the orientation of the\n        slicing plane.  If not set, an arbitrary grid-aligned north-vector is\n        chosen.\n    fontsize : integer\n         The size of the fonts for the axis, colorbar, and tick labels.\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source : YTSelectionContainer Object\n         Object to be used for data selection.  Defaults to a region covering\n         the entire simulation.\n    swap_axes : bool\n\n\n    Raises\n    ------\n\n    ValueError or TypeError\n        If `normal` cannot be interpreted as a valid normal direction.\n\n    Examples\n    --------\n\n    >>> from yt import load\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> slc = SlicePlot(ds, \"x\", (\"gas\", \"density\"), center=[0.2, 0.3, 0.4])\n\n    >>> slc = SlicePlot(\n    ...     ds, [0.4, 0.2, -0.1], (\"gas\", \"pressure\"), north_vector=[0.2, -0.3, 0.1]\n    ... )\n\n    \"\"\"\n\n    # ignoring type check here, because mypy doesn't allow __new__ methods to\n    # return instances of subclasses. The design we use here is however based\n    # on the pathlib.Path class from the standard library\n    # https://github.com/python/mypy/issues/1020\n    def __new__(  # type: ignore\n        cls, ds, normal, fields, *args, **kwargs\n    ) -> Union[\"AxisAlignedSlicePlot\", \"OffAxisSlicePlot\"]:\n        if cls is SlicePlot:\n            normal = cls.sanitize_normal_vector(ds, normal)\n            if isinstance(normal, str):\n                cls = AxisAlignedSlicePlot\n            else:\n                cls = OffAxisSlicePlot\n        self = object.__new__(cls)\n        return self  # type: ignore [return-value]\n\n\nclass ProjectionPlot(NormalPlot):\n    r\"\"\"\n    A dispatch class for :class:`yt.visualization.plot_window.AxisAlignedProjectionPlot`\n    and :class:`yt.visualization.plot_window.OffAxisProjectionPlot` objects.  This\n    essentially allows for a single entry point to both types of projection plots,\n    the distinction being determined by the specified normal vector to the\n    slice.\n\n    The returned plot object can be updated using one of the many helper\n    functions defined in PlotWindow.\n\n    Parameters\n    ----------\n\n    ds : :class:`yt.data_objects.static_output.Dataset`\n        This is the dataset object corresponding to the\n        simulation output to be plotted.\n    normal : int, str, or 3-element sequence of floats\n        This specifies the normal vector to the projection.\n        Valid int values are 0, 1 and 2. Corresponding str values depend on the\n        geometry of the dataset and are generally given by `ds.coordinates.axis_order`.\n        E.g. in cartesian they are 'x', 'y' and 'z'.\n        An arbitrary normal vector may be specified as a 3-element sequence of floats.\n\n        This function will return a :class:`OffAxisProjectionPlot` object or a\n        :class:`AxisAlignedProjectionPlot` object, depending on whether the requested\n        normal directions corresponds to a natural axis of the dataset's geometry.\n\n    fields : a (or a list of) 2-tuple of strings (ftype, fname)\n         The name of the field(s) to be plotted.\n\n\n    Any additional positional and keyword arguments are passed down to the appropriate\n    return class. See :class:`yt.visualization.plot_window.AxisAlignedProjectionPlot`\n    and :class:`yt.visualization.plot_window.OffAxisProjectionPlot`.\n\n    Raises\n    ------\n\n    ValueError or TypeError\n        If `normal` cannot be interpreted as a valid normal direction.\n\n    \"\"\"\n\n    # ignoring type check here, because mypy doesn't allow __new__ methods to\n    # return instances of subclasses. The design we use here is however based\n    # on the pathlib.Path class from the standard library\n    # https://github.com/python/mypy/issues/1020\n    def __new__(  # type: ignore\n        cls, ds, normal, fields, *args, **kwargs\n    ) -> Union[\"AxisAlignedProjectionPlot\", \"OffAxisProjectionPlot\"]:\n        if cls is ProjectionPlot:\n            normal = cls.sanitize_normal_vector(ds, normal)\n            if isinstance(normal, str):\n                cls = AxisAlignedProjectionPlot\n            else:\n                cls = OffAxisProjectionPlot\n        self = object.__new__(cls)\n        return self  # type: ignore [return-value]\n\n\nclass AxisAlignedSlicePlot(SlicePlot, PWViewerMPL):\n    r\"\"\"Creates a slice plot from a dataset\n\n    Given a ds object, an axis to slice along, and a field name\n    string, this will return a PWViewerMPL object containing\n    the plot.\n\n    The plot can be updated using one of the many helper functions\n    defined in PlotWindow.\n\n    Parameters\n    ----------\n    ds : `Dataset`\n         This is the dataset object corresponding to the\n         simulation output to be plotted.\n    normal : int or one of 'x', 'y', 'z'\n         An int corresponding to the axis to slice along (0=x, 1=y, 2=z)\n         or the axis name itself\n    fields : string\n         The name of the field(s) to be plotted.\n    center : 'center', 'c', 'left', 'l', 'right', 'r', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n        The domain edges along the selected *axis* can be selected with\n        'left'/'l' and 'right'/'r' respectively.\n    width : tuple or a float.\n         Width can have four different formats to support windows with variable\n         x and y widths.  They are:\n\n         ==================================     =======================\n         format                                 example\n         ==================================     =======================\n         (float, string)                        (10,'kpc')\n         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n         float                                  0.2\n         (float, float)                         (0.2, 0.3)\n         ==================================     =======================\n\n         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs\n         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a\n         window that is 10 kiloparsecs wide along the x axis and 15\n         kiloparsecs wide along the y axis.  In the other two examples, code\n         units are assumed, for example (0.2, 0.3) requests a plot that has an\n         x width of 0.2 and a y width of 0.3 in code units.  If units are\n         provided the resulting plot axis labels will use the supplied units.\n    origin : string or length 1, 2, or 3 sequence.\n         The location of the origin of the plot coordinate system. This\n         is typically represented by a '-' separated string or a tuple of\n         strings. In the first index the y-location is given by 'lower',\n         'upper', or 'center'. The second index is the x-location, given as\n         'left', 'right', or 'center'. Finally, whether the origin is\n         applied in 'domain' space, plot 'window' space or 'native'\n         simulation coordinate system is given. For example, both\n         'upper-right-domain' and ['upper', 'right', 'domain'] place the\n         origin in the upper right hand corner of domain space. If x or y\n         are not given, a value is inferred. For instance, 'left-domain'\n         corresponds to the lower-left hand corner of the simulation domain,\n         'center-domain' corresponds to the center of the simulation domain,\n         or 'center-window' for the center of the plot window. In the event\n         that none of these options place the origin in a desired location,\n         a sequence of tuples and a string specifying the\n         coordinate space can be given. If plain numeric types are input,\n         units of `code_length` are assumed. Further examples:\n\n         =============================================== ===============================\n         format                                          example\n         =============================================== ===============================\n         '{space}'                                       'domain'\n         '{xloc}-{space}'                                'left-window'\n         '{yloc}-{space}'                                'upper-domain'\n         '{yloc}-{xloc}-{space}'                         'lower-right-window'\n         ('{space}',)                                    ('window',)\n         ('{xloc}', '{space}')                           ('right', 'domain')\n         ('{yloc}', '{space}')                           ('lower', 'window')\n         ('{yloc}', '{xloc}', '{space}')                 ('lower', 'right', 'window')\n         ((yloc, '{unit}'), (xloc, '{unit}'), '{space}') ((0, 'm'), (.4, 'm'), 'window')\n         (xloc, yloc, '{space}')                         (0.23, 0.5, 'domain')\n         =============================================== ===============================\n    axes_unit : string\n         The name of the unit for the tick labels on the x and y axes.\n         Defaults to None, which automatically picks an appropriate unit.\n         If axes_unit is '1', 'u', or 'unitary', it will not display the\n         units, and only show the axes name.\n    fontsize : integer\n         The size of the fonts for the axis, colorbar, and tick labels.\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source: YTSelectionContainer object\n         Object to be used for data selection. Defaults to ds.all_data(), a\n         region covering the full domain\n    buff_size: length 2 sequence\n         Size of the buffer to use for the image, i.e. the number of resolution elements\n         used.  Effectively sets a resolution limit to the image if buff_size is\n         smaller than the finest gridding.\n\n    Examples\n    --------\n\n    This will save an image in the file 'sliceplot_Density.png'\n\n    >>> from yt import load\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> p = SlicePlot(ds, 2, \"density\", \"c\", (20, \"kpc\"))\n    >>> p.save(\"sliceplot\")\n\n    \"\"\"\n\n    _plot_type = \"Slice\"\n    _frb_generator = FixedResolutionBuffer\n\n    def __init__(\n        self,\n        ds,\n        normal,\n        fields,\n        center=\"center\",\n        width=None,\n        axes_unit=None,\n        origin=\"center-window\",\n        fontsize=18,\n        field_parameters=None,\n        window_size=8.0,\n        aspect=None,\n        data_source=None,\n        buff_size=(800, 800),\n        *,\n        north_vector=None,\n    ):\n        if north_vector is not None:\n            # this kwarg exists only for symmetry reasons with OffAxisSlicePlot\n            mylog.warning(\n                \"Ignoring 'north_vector' keyword as it is ill-defined for \"\n                \"an AxisAlignedSlicePlot object.\"\n            )\n            del north_vector\n\n        normal = self.sanitize_normal_vector(ds, normal)\n        # this will handle time series data and controllers\n        axis = fix_axis(normal, ds)\n        # print('center at SlicePlot init: ', center)\n        # print('current domain left edge: ', ds.domain_left_edge)\n        (bounds, center, display_center) = get_window_parameters(\n            axis, center, width, ds\n        )\n        # print('center after get_window_parameters: ', center)\n        if field_parameters is None:\n            field_parameters = {}\n\n        if isinstance(ds, YTSpatialPlotDataset):\n            slc = ds.all_data()\n            slc.axis = axis\n            if slc.axis != ds.parameters[\"axis\"]:\n                raise RuntimeError(f\"Original slice axis is {ds.parameters['axis']}.\")\n        else:\n            slc = ds.slice(\n                axis,\n                center[axis],\n                field_parameters=field_parameters,\n                center=center,\n                data_source=data_source,\n            )\n            slc.get_data(fields)\n        validate_mesh_fields(slc, fields)\n        PWViewerMPL.__init__(\n            self,\n            slc,\n            bounds,\n            origin=origin,\n            fontsize=fontsize,\n            fields=fields,\n            window_size=window_size,\n            aspect=aspect,\n            buff_size=buff_size,\n            geometry=ds.geometry,\n        )\n        if axes_unit is None:\n            axes_unit = get_axes_unit(width, ds)\n        self.set_axes_unit(axes_unit)\n\n\nclass AxisAlignedProjectionPlot(ProjectionPlot, PWViewerMPL):\n    r\"\"\"Creates a projection plot from a dataset\n\n    Given a ds object, an axis to project along, and a field name\n    string, this will return a PWViewerMPL object containing\n    the plot.\n\n    The plot can be updated using one of the many helper functions\n    defined in PlotWindow.\n\n    Parameters\n    ----------\n    ds : `Dataset`\n        This is the dataset object corresponding to the\n        simulation output to be plotted.\n    normal : int or one of 'x', 'y', 'z'\n        An int corresponding to the axis to slice along (0=x, 1=y, 2=z)\n        or the axis name itself\n    fields : string\n        The name of the field(s) to be plotted.\n    center : 'center', 'c', 'left', 'l', 'right', 'r', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n        The domain edges along the selected *axis* can be selected with\n        'left'/'l' and 'right'/'r' respectively.\n    width : tuple or a float.\n        Width can have four different formats to support windows with variable\n        x and y widths.  They are:\n\n        ==================================     =======================\n        format                                 example\n        ==================================     =======================\n        (float, string)                        (10,'kpc')\n        ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n        float                                  0.2\n        (float, float)                         (0.2, 0.3)\n        ==================================     =======================\n\n        For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs\n        wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a\n        window that is 10 kiloparsecs wide along the x axis and 15\n        kiloparsecs wide along the y axis.  In the other two examples, code\n        units are assumed, for example (0.2, 0.3) requests a plot that has an\n        x width of 0.2 and a y width of 0.3 in code units.  If units are\n        provided the resulting plot axis labels will use the supplied units.\n    axes_unit : string\n        The name of the unit for the tick labels on the x and y axes.\n        Defaults to None, which automatically picks an appropriate unit.\n        If axes_unit is '1', 'u', or 'unitary', it will not display the\n        units, and only show the axes name.\n    origin : string or length 1, 2, or 3 sequence.\n        The location of the origin of the plot coordinate system. This\n        is typically represented by a '-' separated string or a tuple of\n        strings. In the first index the y-location is given by 'lower',\n        'upper', or 'center'. The second index is the x-location, given as\n        'left', 'right', or 'center'. Finally, whether the origin is\n        applied in 'domain' space, plot 'window' space or 'native'\n        simulation coordinate system is given. For example, both\n        'upper-right-domain' and ['upper', 'right', 'domain'] place the\n        origin in the upper right hand corner of domain space. If x or y\n        are not given, a value is inferred. For instance, 'left-domain'\n        corresponds to the lower-left hand corner of the simulation domain,\n        'center-domain' corresponds to the center of the simulation domain,\n        or 'center-window' for the center of the plot window. In the event\n        that none of these options place the origin in a desired location,\n        a sequence of tuples and a string specifying the\n        coordinate space can be given. If plain numeric types are input,\n        units of `code_length` are assumed. Further examples:\n\n        =============================================== ===============================\n        format                                          example\n        =============================================== ===============================\n        '{space}'                                       'domain'\n        '{xloc}-{space}'                                'left-window'\n        '{yloc}-{space}'                                'upper-domain'\n        '{yloc}-{xloc}-{space}'                         'lower-right-window'\n        ('{space}',)                                    ('window',)\n        ('{xloc}', '{space}')                           ('right', 'domain')\n        ('{yloc}', '{space}')                           ('lower', 'window')\n        ('{yloc}', '{xloc}', '{space}')                 ('lower', 'right', 'window')\n        ((yloc, '{unit}'), (xloc, '{unit}'), '{space}') ((0, 'm'), (.4, 'm'), 'window')\n        (xloc, yloc, '{space}')                            (0.23, 0.5, 'domain')\n        =============================================== ===============================\n\n    data_source : YTSelectionContainer Object\n        Object to be used for data selection.  Defaults to a region covering\n        the entire simulation.\n    weight_field : string\n        The name of the weighting field.  Set to None for no weight.\n    max_level: int\n        The maximum level to project to.\n    fontsize : integer\n        The size of the fonts for the axis, colorbar, and tick labels.\n    method : string\n        The method of projection.  Valid methods are:\n\n        \"integrate\" with no weight_field specified : integrate the requested\n        field along the line of sight.\n\n        \"integrate\" with a weight_field specified : weight the requested\n        field by the weighting field and integrate along the line of sight.\n\n        \"max\" : pick out the maximum value of the field in the line of sight.\n        \"min\" : pick out the minimum value of the field in the line of sight.\n\n        \"sum\" : This method is the same as integrate, except that it does not\n        multiply by a path length when performing the integration, and is\n        just a straight summation of the field along the given axis. WARNING:\n        This should only be used for uniform resolution grid datasets, as other\n        datasets may result in unphysical images.\n    window_size : float\n        The size of the window in inches. Set to 8 by default.\n    aspect : float\n        The aspect ratio of the plot.  Set to None for 1.\n    field_parameters : dictionary\n        A dictionary of field parameters than can be accessed by derived\n        fields.\n    data_source: YTSelectionContainer object\n        Object to be used for data selection. Defaults to ds.all_data(), a\n        region covering the full domain\n    buff_size: length 2 sequence\n        Size of the buffer to use for the image, i.e. the number of resolution elements\n        used. Effectively sets a resolution limit to the image if buff_size is\n        smaller than the finest gridding.\n    moment : integer, optional\n        for a weighted projection, moment = 1 (the default) corresponds to a\n        weighted average. moment = 2 corresponds to a weighted standard\n        deviation.\n\n    Examples\n    --------\n\n    Create a projection plot with a width of 20 kiloparsecs centered on the\n    center of the simulation box:\n\n    >>> from yt import load\n    >>> ds = load(\"IsolateGalaxygalaxy0030/galaxy0030\")\n    >>> p = AxisAlignedProjectionPlot(ds, \"z\", (\"gas\", \"density\"), width=(20, \"kpc\"))\n\n    \"\"\"\n\n    _plot_type = \"Projection\"\n    _frb_generator = FixedResolutionBuffer\n\n    def __init__(\n        self,\n        ds,\n        normal,\n        fields,\n        center=\"center\",\n        width=None,\n        axes_unit=None,\n        weight_field=None,\n        max_level=None,\n        origin=\"center-window\",\n        fontsize=18,\n        field_parameters=None,\n        data_source=None,\n        method=\"integrate\",\n        window_size=8.0,\n        buff_size=(800, 800),\n        aspect=None,\n        *,\n        moment=1,\n    ):\n        if method == \"mip\":\n            issue_deprecation_warning(\n                \"'mip' method is a deprecated alias for 'max'. \"\n                \"Please use method='max' directly.\",\n                since=\"4.1\",\n                stacklevel=3,\n            )\n            method = \"max\"\n        normal = self.sanitize_normal_vector(ds, normal)\n\n        axis = fix_axis(normal, ds)\n        # If a non-weighted integral projection, assure field-label reflects that\n        if weight_field is None and method == \"integrate\":\n            self.projected = True\n        (bounds, center, display_center) = get_window_parameters(\n            axis, center, width, ds\n        )\n        if field_parameters is None:\n            field_parameters = {}\n\n        # We don't use the plot's data source for validation like in the other\n        # plotting classes to avoid an exception\n        test_data_source = ds.all_data()\n        validate_mesh_fields(test_data_source, fields)\n\n        if isinstance(ds, YTSpatialPlotDataset):\n            proj = ds.all_data()\n            proj.axis = axis\n            if proj.axis != ds.parameters[\"axis\"]:\n                raise RuntimeError(\n                    f\"Original projection axis is {ds.parameters['axis']}.\"\n                )\n            if weight_field is not None:\n                proj.weight_field = proj._determine_fields(weight_field)[0]\n            else:\n                proj.weight_field = weight_field\n            proj.center = center\n        else:\n            proj = ds.proj(\n                fields,\n                axis,\n                weight_field=weight_field,\n                center=center,\n                data_source=data_source,\n                field_parameters=field_parameters,\n                method=method,\n                max_level=max_level,\n                moment=moment,\n            )\n        self.moment = moment\n        PWViewerMPL.__init__(\n            self,\n            proj,\n            bounds,\n            fields=fields,\n            origin=origin,\n            fontsize=fontsize,\n            window_size=window_size,\n            aspect=aspect,\n            buff_size=buff_size,\n            geometry=ds.geometry,\n        )\n        if axes_unit is None:\n            axes_unit = get_axes_unit(width, ds)\n        self.set_axes_unit(axes_unit)\n\n\nclass OffAxisSlicePlot(SlicePlot, PWViewerMPL):\n    r\"\"\"Creates an off axis slice plot from a dataset\n\n    Given a ds object, a normal vector defining a slicing plane, and\n    a field name string, this will return a PWViewerMPL object\n    containing the plot.\n\n    The plot can be updated using one of the many helper functions\n    defined in PlotWindow.\n\n    Parameters\n    ----------\n    ds : :class:`yt.data_objects.static_output.Dataset`\n         This is the dataset object corresponding to the\n         simulation output to be plotted.\n    normal : a sequence of floats\n         The vector normal to the slicing plane.\n    fields : string\n         The name of the field(s) to be plotted.\n    center : 'center', 'c' id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n    width : tuple or a float.\n         Width can have four different formats to support windows with variable\n         x and y widths.  They are:\n\n         ==================================     =======================\n         format                                 example\n         ==================================     =======================\n         (float, string)                        (10,'kpc')\n         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n         float                                  0.2\n         (float, float)                         (0.2, 0.3)\n         ==================================     =======================\n\n         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs\n         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a\n         window that is 10 kiloparsecs wide along the x axis and 15\n         kiloparsecs wide along the y axis.  In the other two examples, code\n         units are assumed, for example (0.2, 0.3) requests a plot that has an\n         x width of 0.2 and a y width of 0.3 in code units.  If units are\n         provided the resulting plot axis labels will use the supplied units.\n    axes_unit : string\n         The name of the unit for the tick labels on the x and y axes.\n         Defaults to None, which automatically picks an appropriate unit.\n         If axes_unit is '1', 'u', or 'unitary', it will not display the\n         units, and only show the axes name.\n    north_vector : a sequence of floats\n         A vector defining the 'up' direction in the plot.  This\n         option sets the orientation of the slicing plane.  If not\n         set, an arbitrary grid-aligned north-vector is chosen.\n    fontsize : integer\n         The size of the fonts for the axis, colorbar, and tick labels.\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source : YTSelectionContainer Object\n         Object to be used for data selection.  Defaults ds.all_data(), a\n         region covering the full domain.\n    buff_size: length 2 sequence\n         Size of the buffer to use for the image, i.e. the number of resolution elements\n         used.  Effectively sets a resolution limit to the image if buff_size is\n         smaller than the finest gridding.\n    \"\"\"\n\n    _plot_type = \"OffAxisSlice\"\n    _frb_generator = FixedResolutionBuffer\n    _supported_geometries = (\"cartesian\", \"spectral_cube\")\n\n    def __init__(\n        self,\n        ds,\n        normal,\n        fields,\n        center=\"center\",\n        width=None,\n        axes_unit=None,\n        north_vector=None,\n        fontsize=18,\n        field_parameters=None,\n        data_source=None,\n        buff_size=(800, 800),\n        *,\n        origin=None,\n    ):\n        if origin is not None:\n            # this kwarg exists only for symmetry reasons with AxisAlignedSlicePlot\n            # in OffAxisSlicePlot, the origin is hardcoded\n            mylog.warning(\n                \"Ignoring 'origin' keyword as it is ill-defined for \"\n                \"an OffAxisSlicePlot object.\"\n            )\n            del origin\n\n        if ds.geometry not in self._supported_geometries:\n            raise NotImplementedError(\n                f\"off-axis slices are not supported for {ds.geometry!r} geometry\\n\"\n                f\"currently supported geometries: {self._supported_geometries!r}\"\n            )\n        # bounds are in cutting plane coordinates, centered on 0:\n        # [xmin, xmax, ymin, ymax]. Can derive width/height back\n        # from these. unit is code_length\n        (bounds, center_rot) = get_oblique_window_parameters(normal, center, width, ds)\n        if field_parameters is None:\n            field_parameters = {}\n\n        if isinstance(ds, YTSpatialPlotDataset):\n            cutting = ds.all_data()\n            cutting.axis = None\n            cutting._inv_mat = ds.parameters[\"_inv_mat\"]\n        else:\n            cutting = ds.cutting(\n                normal,\n                center,\n                north_vector=north_vector,\n                field_parameters=field_parameters,\n                data_source=data_source,\n            )\n            cutting.get_data(fields)\n        validate_mesh_fields(cutting, fields)\n        # Hard-coding the origin keyword since the other two options\n        # aren't well-defined for off-axis data objects\n        PWViewerMPL.__init__(\n            self,\n            cutting,\n            bounds,\n            fields=fields,\n            origin=\"center-window\",\n            periodic=False,\n            oblique=True,\n            fontsize=fontsize,\n            buff_size=buff_size,\n        )\n        if axes_unit is None:\n            axes_unit = get_axes_unit(width, ds)\n        self.set_axes_unit(axes_unit)\n\n\nclass OffAxisProjectionDummyDataSource:\n    _type_name = \"proj\"\n    _key_fields: list[str] = []\n\n    def __init__(\n        self,\n        center,\n        ds,\n        normal_vector,\n        width,\n        fields,\n        interpolated,\n        weight=None,\n        volume=None,\n        no_ghost=False,\n        le=None,\n        re=None,\n        north_vector=None,\n        depth=None,\n        method=\"integrate\",\n        data_source=None,\n        *,\n        moment=1,\n    ):\n        validate_moment(moment, weight)\n        self.center = center\n        self.ds = ds\n        self.axis = None  # always true for oblique data objects\n        self.normal_vector = normal_vector\n        self.width = width\n        self.depth = depth\n        if data_source is None:\n            self.dd = ds.all_data()\n        else:\n            self.dd = data_source\n        fields = self.dd._determine_fields(fields)\n        self.fields = fields\n        self.interpolated = interpolated\n        if weight is not None:\n            weight = self.dd._determine_fields(weight)[0]\n        self.weight_field = weight\n        self.volume = volume\n        self.no_ghost = no_ghost\n        self.le = le\n        self.re = re\n        self.north_vector = north_vector\n        self.method = method\n        self.orienter = Orientation(normal_vector, north_vector=north_vector)\n        self.moment = moment\n\n    def _determine_fields(self, *args):\n        return self.dd._determine_fields(*args)\n\n\nclass OffAxisProjectionPlot(ProjectionPlot, PWViewerMPL):\n    r\"\"\"Creates an off axis projection plot from a dataset\n\n    Given a ds object, a normal vector to project along, and\n    a field name string, this will return a PWViewerMPL object\n    containing the plot.\n\n    The plot can be updated using one of the many helper functions\n    defined in PlotWindow.\n\n    Parameters\n    ----------\n    ds : :class:`yt.data_objects.static_output.Dataset`\n        This is the dataset object corresponding to the\n        simulation output to be plotted.\n    normal : a sequence of floats\n        The vector normal to the slicing plane.\n    fields : string\n        The name of the field(s) to be plotted.\n    center : 'center', 'c', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n    width : tuple or a float.\n        Width can have four different formats to support windows with variable\n        x and y widths. They are:\n\n        ==================================     =======================\n        format                                 example\n        ==================================     =======================\n        (float, string)                        (10,'kpc')\n        ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n        float                                  0.2\n        (float, float)                         (0.2, 0.3)\n        ==================================     =======================\n\n        For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs\n        wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a\n        window that is 10 kiloparsecs wide along the x axis and 15\n        kiloparsecs wide along the y axis.  In the other two examples, code\n        units are assumed, for example (0.2, 0.3) requests a plot that has an\n        x width of 0.2 and a y width of 0.3 in code units.  If units are\n        provided the resulting plot axis labels will use the supplied units.\n    depth : A tuple or a float\n        A tuple containing the depth to project through and the string\n        key of the unit: (width, 'unit'). If set to a float, code units\n        are assumed. In not set, then a depth equal to the diagonal of\n        the domain width plus a small margin will be used.\n    weight_field : string\n        The name of the weighting field.  Set to None for no weight.\n    max_level: int\n        The maximum level to project to.\n    axes_unit : string\n        The name of the unit for the tick labels on the x and y axes.\n        Defaults to None, which automatically picks an appropriate unit.\n        If axes_unit is '1', 'u', or 'unitary', it will not display the\n        units, and only show the axes name.\n    north_vector : a sequence of floats\n        A vector defining the 'up' direction in the plot. This\n        option sets the orientation of the slicing plane. If not\n        set, an arbitrary grid-aligned north-vector is chosen.\n    fontsize : integer\n        The size of the fonts for the axis, colorbar, and tick labels.\n    method : string\n        The method of projection. Valid methods are:\n\n        \"integrate\" with no weight_field specified : integrate the requested\n        field along the line of sight.\n\n        \"integrate\" with a weight_field specified : weight the requested\n        field by the weighting field and integrate along the line of sight.\n\n        \"sum\" : This method is the same as integrate, except that it does not\n        multiply by a path length when performing the integration, and is\n        just a straight summation of the field along the given axis. WARNING:\n        This should only be used for uniform resolution grid datasets, as other\n        datasets may result in unphysical images.\n    moment : integer, optional\n        for a weighted projection, moment = 1 (the default) corresponds to a\n        weighted average. moment = 2 corresponds to a weighted standard\n        deviation.\n    data_source: YTSelectionContainer object\n        Object to be used for data selection. Defaults to ds.all_data(), a\n        region covering the full domain\n    buff_size: length 2 sequence\n        Size of the buffer to use for the image, i.e. the number of resolution elements\n        used. Effectively sets a resolution limit to the image if buff_size is\n        smaller than the finest gridding.\n    \"\"\"\n\n    _plot_type = \"OffAxisProjection\"\n    _frb_generator = OffAxisProjectionFixedResolutionBuffer\n    _supported_geometries = (\"cartesian\", \"spectral_cube\")\n\n    def __init__(\n        self,\n        ds,\n        normal,\n        fields,\n        center=\"center\",\n        width=None,\n        depth=None,\n        axes_unit=None,\n        weight_field=None,\n        max_level=None,\n        north_vector=None,\n        volume=None,\n        no_ghost=False,\n        le=None,\n        re=None,\n        interpolated=False,\n        fontsize=18,\n        method=\"integrate\",\n        moment=1,\n        data_source=None,\n        buff_size=(800, 800),\n    ):\n        if ds.geometry not in self._supported_geometries:\n            raise NotImplementedError(\n                \"off-axis slices are not supported\"\n                f\" for {ds.geometry!r} geometry\\n\"\n                \"currently supported geometries:\"\n                f\" {self._supported_geometries!r}\"\n            )\n\n        if depth is None:\n            # off-axis projection, depth not specified\n            # -> set 'large enough' depth using half the box diagonal + margin\n            depth = np.linalg.norm(ds.domain_width.in_units(\"code_length\")) * 1.0001\n        depth = ds.coordinates.sanitize_depth(depth)[0]\n\n        # center_rot normalizes the center to (0,0),\n        # units match bounds\n        # for SPH data, we want to input the original center\n        # the cython backend handles centering to this point and\n        # rotation.\n        # get3bounds gets a depth 0.5 * diagonal + margin in the\n        # depth=None case.\n        (bounds, center_rot) = get_oblique_window_parameters(\n            normal,\n            center,\n            width,\n            ds,\n            depth=depth,\n        )\n        # will probably fail if you try to project an SPH and non-SPH\n        # field in a single call\n        # checks for SPH fields copied from the\n        # _ortho_pixelize method in cartesian_coordinates.py\n\n        ## data_source might be None here\n        ## (OffAxisProjectionDummyDataSource gets used later)\n        if data_source is None:\n            data_source = ds.all_data()\n        field = data_source._determine_fields(fields)[0]\n        finfo = data_source.ds.field_info[field]\n        is_sph_field = finfo.is_sph_field\n        particle_datasets = (ParticleDataset, StreamParticlesDataset)\n\n        dom_width = data_source.ds.domain_width\n        cubic_domain = dom_width.max() == dom_width.min()\n\n        if (isinstance(data_source.ds, particle_datasets) and is_sph_field) or (\n            isinstance(data_source.ds.index, OctreeIndex) and cubic_domain\n        ):\n            center_use = parse_center_array(center, ds=data_source.ds, axis=None)\n        else:\n            center_use = center_rot\n        fields = list(iter_fields(fields))[:]\n        # oap_width = ds.arr(\n        #    (bounds[1] - bounds[0],\n        #     bounds[3] - bounds[2])\n        # )\n        OffAxisProj = OffAxisProjectionDummyDataSource(\n            center_use,\n            ds,\n            normal,\n            width,\n            fields,\n            interpolated,\n            weight=weight_field,\n            volume=volume,\n            no_ghost=no_ghost,\n            le=le,\n            re=re,\n            north_vector=north_vector,\n            depth=depth,\n            method=method,\n            data_source=data_source,\n            moment=moment,\n        )\n\n        validate_mesh_fields(OffAxisProj, fields)\n\n        if max_level is not None:\n            OffAxisProj.dd.max_level = max_level\n\n        # If a non-weighted, integral projection, assure field label\n        # reflects that\n        if weight_field is None and OffAxisProj.method == \"integrate\":\n            self.projected = True\n\n        self.moment = moment\n\n        # Hard-coding the origin keyword since the other two options\n        # aren't well-defined for off-axis data objects\n        PWViewerMPL.__init__(\n            self,\n            OffAxisProj,\n            bounds,\n            fields=fields,\n            origin=\"center-window\",\n            periodic=False,\n            oblique=True,\n            fontsize=fontsize,\n            buff_size=buff_size,\n        )\n        if axes_unit is None:\n            axes_unit = get_axes_unit(width, ds)\n        self.set_axes_unit(axes_unit)\n\n\nclass WindowPlotMPL(ImagePlotMPL):\n    \"\"\"A container for a single PlotWindow matplotlib figure and axes\"\"\"\n\n    def __init__(\n        self,\n        data,\n        extent,\n        figure_size,\n        fontsize,\n        aspect,\n        figure,\n        axes,\n        cax,\n        mpl_proj,\n        mpl_transform,\n        *,\n        norm_handler: NormHandler,\n        colorbar_handler: ColorbarHandler,\n        alpha: AlphaT = None,\n    ):\n        self._projection = mpl_proj\n        self._transform = mpl_transform\n\n        self._setup_layout_constraints(figure_size, fontsize)\n        self._draw_frame = True\n        self._aspect = ((extent[1] - extent[0]) / (extent[3] - extent[2])).in_cgs()\n        self._unit_aspect = aspect\n\n        # Compute layout\n        self._figure_size = figure_size\n        self._draw_axes = True\n        fontscale = float(fontsize) / self.__class__._default_font_size\n        if fontscale < 1.0:\n            fontscale = np.sqrt(fontscale)\n\n        if is_sequence(figure_size):\n            self._cb_size = 0.0375 * figure_size[0]\n        else:\n            self._cb_size = 0.0375 * figure_size\n        self._ax_text_size = [1.2 * fontscale, 0.9 * fontscale]\n        self._top_buff_size = 0.30 * fontscale\n\n        super().__init__(\n            figure=figure,\n            axes=axes,\n            cax=cax,\n            norm_handler=norm_handler,\n            colorbar_handler=colorbar_handler,\n        )\n\n        self._init_image(data, extent, aspect, alpha=alpha)\n\n    def _create_axes(self, axrect):\n        self.axes = self.figure.add_axes(axrect, projection=self._projection)\n\n\ndef plot_2d(\n    ds,\n    fields,\n    center=\"center\",\n    width=None,\n    axes_unit=None,\n    origin=\"center-window\",\n    fontsize=18,\n    field_parameters=None,\n    window_size=8.0,\n    aspect=None,\n    data_source=None,\n) -> AxisAlignedSlicePlot:\n    r\"\"\"Creates a plot of a 2D dataset\n\n    Given a ds object and a field name string, this will return a\n    PWViewerMPL object containing the plot.\n\n    The plot can be updated using one of the many helper functions\n    defined in PlotWindow.\n\n    Parameters\n    ----------\n    ds : `Dataset`\n         This is the dataset object corresponding to the\n         simulation output to be plotted.\n    fields : string\n         The name of the field(s) to be plotted.\n    center : 'center', 'c', id of a global extremum, or array-like\n        The coordinate of the selection's center.\n        Defaults to the 'center', i.e. center of the domain.\n\n        Centering on the min or max of a field is supported by passing a tuple\n        such as ('min', ('gas', 'density')) or ('max', ('gas', 'temperature'). A\n        single string may also be used (e.g. \"min_density\" or\n        \"max_temperature\"), though it's not as flexible and does not allow to\n        select an exact field/particle type. With this syntax, the first field\n        matching the provided name is selected.\n        'max' or 'm' can be used as a shortcut for ('max', ('gas', 'density'))\n        'min' can be used as a shortcut for ('min', ('gas', 'density'))\n\n        One can also select an exact point as a 3 element coordinate sequence,\n        e.g. [0.5, 0.5, 0]\n        Units can be specified by passing in *center* as a tuple containing a\n        3-element coordinate sequence and string unit name, e.g. ([0, 0.5, 0.5], \"cm\"),\n        or by passing in a YTArray. Code units are assumed if unspecified.\n\n        plot_2d also accepts a coordinate in two dimensions.\n    width : tuple or a float.\n         Width can have four different formats to support windows with variable\n         x and y widths.  They are:\n\n         ==================================     =======================\n         format                                 example\n         ==================================     =======================\n         (float, string)                        (10,'kpc')\n         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))\n         float                                  0.2\n         (float, float)                         (0.2, 0.3)\n         ==================================     =======================\n\n         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs\n         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a\n         window that is 10 kiloparsecs wide along the x axis and 15\n         kiloparsecs wide along the y axis.  In the other two examples, code\n         units are assumed, for example (0.2, 0.3) requests a plot that has an\n         x width of 0.2 and a y width of 0.3 in code units.  If units are\n         provided the resulting plot axis labels will use the supplied units.\n    origin : string or length 1, 2, or 3 sequence.\n         The location of the origin of the plot coordinate system. This\n         is typically represented by a '-' separated string or a tuple of\n         strings. In the first index the y-location is given by 'lower',\n         'upper', or 'center'. The second index is the x-location, given as\n         'left', 'right', or 'center'. Finally, whether the origin is\n         applied in 'domain' space, plot 'window' space or 'native'\n         simulation coordinate system is given. For example, both\n         'upper-right-domain' and ['upper', 'right', 'domain'] place the\n         origin in the upper right hand corner of domain space. If x or y\n         are not given, a value is inferred. For instance, 'left-domain'\n         corresponds to the lower-left hand corner of the simulation domain,\n         'center-domain' corresponds to the center of the simulation domain,\n         or 'center-window' for the center of the plot window. In the event\n         that none of these options place the origin in a desired location,\n         a sequence of tuples and a string specifying the\n         coordinate space can be given. If plain numeric types are input,\n         units of `code_length` are assumed. Further examples:\n\n         =============================================== ===============================\n         format                                          example\n         =============================================== ===============================\n         '{space}'                                       'domain'\n         '{xloc}-{space}'                                'left-window'\n         '{yloc}-{space}'                                'upper-domain'\n         '{yloc}-{xloc}-{space}'                         'lower-right-window'\n         ('{space}',)                                    ('window',)\n         ('{xloc}', '{space}')                           ('right', 'domain')\n         ('{yloc}', '{space}')                           ('lower', 'window')\n         ('{yloc}', '{xloc}', '{space}')                 ('lower', 'right', 'window')\n         ((yloc, '{unit}'), (xloc, '{unit}'), '{space}') ((0, 'm'), (.4, 'm'), 'window')\n         (xloc, yloc, '{space}')                         (0.23, 0.5, 'domain')\n         =============================================== ===============================\n    axes_unit : string\n         The name of the unit for the tick labels on the x and y axes.\n         Defaults to None, which automatically picks an appropriate unit.\n         If axes_unit is '1', 'u', or 'unitary', it will not display the\n         units, and only show the axes name.\n    fontsize : integer\n         The size of the fonts for the axis, colorbar, and tick labels.\n    field_parameters : dictionary\n         A dictionary of field parameters than can be accessed by derived\n         fields.\n    data_source: YTSelectionContainer object\n         Object to be used for data selection. Defaults to ds.all_data(), a\n         region covering the full domain\n    \"\"\"\n    if ds.dimensionality != 2:\n        raise RuntimeError(\"plot_2d only plots 2D datasets!\")\n    match ds.geometry:\n        case Geometry.CARTESIAN | Geometry.POLAR | Geometry.SPECTRAL_CUBE:\n            axis = \"z\"\n        case Geometry.CYLINDRICAL:\n            axis = \"theta\"\n        case Geometry.SPHERICAL:\n            axis = \"phi\"\n        case Geometry.GEOGRAPHIC | Geometry.INTERNAL_GEOGRAPHIC:\n            raise NotImplementedError(\n                f\"plot_2d does not yet support datasets with {ds.geometry} geometries\"\n            )\n        case _:\n            assert_never(ds.geometry)\n\n    # Part of the convenience of plot_2d is to eliminate the use of the\n    # superfluous coordinate, so we do that also with the center argument\n    if not isinstance(center, str) and obj_length(center) == 2:\n        c0_string = isinstance(center[0], str)\n        c1_string = isinstance(center[1], str)\n        if not c0_string and not c1_string:\n            if obj_length(center[0]) == 2 and c1_string:\n                # turning off type checking locally because center arg is hard to type correctly\n                center = ds.arr(center[0], center[1])  # type: ignore [unreachable]\n            elif not isinstance(center, YTArray):\n                center = ds.arr(center, \"code_length\")\n            center.convert_to_units(\"code_length\")\n        center = ds.arr([center[0], center[1], ds.domain_center[2]])\n    return AxisAlignedSlicePlot(\n        ds,\n        axis,\n        fields,\n        center=center,\n        width=width,\n        axes_unit=axes_unit,\n        origin=origin,\n        fontsize=fontsize,\n        field_parameters=field_parameters,\n        window_size=window_size,\n        aspect=aspect,\n        data_source=data_source,\n    )\n"
  },
  {
    "path": "yt/visualization/profile_plotter.py",
    "content": "import base64\nimport os\nfrom functools import wraps\nfrom typing import TYPE_CHECKING, Any\n\nimport matplotlib as mpl\nimport matplotlib.style\nimport numpy as np\nfrom more_itertools.more import always_iterable, unzip\n\nfrom yt._maintenance.ipython_compat import IS_IPYTHON\nfrom yt._typing import FieldKey\nfrom yt.data_objects.profiles import create_profile, sanitize_field_tuple_keys\nfrom yt.data_objects.static_output import Dataset\nfrom yt.frontends.ytdata.data_structures import YTProfileDataset\nfrom yt.funcs import iter_fields\nfrom yt.utilities.exceptions import YTNotInsideNotebook\nfrom yt.visualization._commons import _get_units_label\nfrom yt.visualization._handlers import ColorbarHandler, NormHandler\nfrom yt.visualization.base_plot_types import ImagePlotMPL, PlotMPL\n\nfrom ..data_objects.selection_objects.data_selection_objects import YTSelectionContainer\nfrom ._commons import validate_image_name\nfrom .plot_container import (\n    BaseLinePlot,\n    ImagePlotContainer,\n    invalidate_plot,\n    validate_plot,\n)\n\nif TYPE_CHECKING:\n    from collections.abc import Iterable\n\n    from yt._typing import FieldKey\n\n\ndef invalidate_profile(f):\n    @wraps(f)\n    def newfunc(*args, **kwargs):\n        rv = f(*args, **kwargs)\n        args[0]._profile_valid = False\n        return rv\n\n    return newfunc\n\n\ndef sanitize_label(labels, nprofiles):\n    labels = list(always_iterable(labels)) or [None]\n\n    if len(labels) == 1:\n        labels = labels * nprofiles\n\n    if len(labels) != nprofiles:\n        raise ValueError(\n            f\"Number of labels {len(labels)} must match number of profiles {nprofiles}\"\n        )\n\n    invalid_data = [\n        (label, type(label))\n        for label in labels\n        if label is not None and not isinstance(label, str)\n    ]\n    if invalid_data:\n        invalid_labels, types = unzip(invalid_data)\n        raise TypeError(\n            \"All labels must be None or a string, \"\n            f\"received {invalid_labels} with type {types}\"\n        )\n\n    return labels\n\n\ndef data_object_or_all_data(data_source):\n    if isinstance(data_source, Dataset):\n        data_source = data_source.all_data()\n\n    if not isinstance(data_source, YTSelectionContainer):\n        raise RuntimeError(\"data_source must be a yt selection data object\")\n\n    return data_source\n\n\nclass ProfilePlot(BaseLinePlot):\n    r\"\"\"\n    Create a 1d profile plot from a data source or from a list\n    of profile objects.\n\n    Given a data object (all_data, region, sphere, etc.), an x field,\n    and a y field (or fields), this will create a one-dimensional profile\n    of the average (or total) value of the y field in bins of the x field.\n\n    This can be used to create profiles from given fields or to plot\n    multiple profiles created from\n    `yt.data_objects.profiles.create_profile`.\n\n    Parameters\n    ----------\n    data_source : YTSelectionContainer Object\n        The data object to be profiled, such as all_data, region, or\n        sphere. If a dataset is passed in instead, an all_data data object\n        is generated internally from the dataset.\n    x_field : str\n        The binning field for the profile.\n    y_fields : str or list\n        The field or fields to be profiled.\n    weight_field : str\n        The weight field for calculating weighted averages. If None,\n        the profile values are the sum of the field values within the bin.\n        Otherwise, the values are a weighted average.\n        Default : (\"gas\", \"mass\")\n    n_bins : int\n        The number of bins in the profile.\n        Default: 64.\n    accumulation : bool\n        If True, the profile values for a bin N are the cumulative sum of\n        all the values from bin 0 to N.\n        Default: False.\n    fractional : If True the profile values are divided by the sum of all\n        the profile data such that the profile represents a probability\n        distribution function.\n    label : str or list of strings\n        If a string, the label to be put on the line plotted.  If a list,\n        this should be a list of labels for each profile to be overplotted.\n        Default: None.\n    plot_spec : dict or list of dicts\n        A dictionary or list of dictionaries containing plot keyword\n        arguments.  For example, dict(color=\"red\", linestyle=\":\").\n        Default: None.\n    x_log : bool\n        Whether the x_axis should be plotted with a logarithmic\n        scaling (True), or linear scaling (False).\n        Default: True.\n    y_log : dict or bool\n        A dictionary containing field:boolean pairs, setting the logarithmic\n        property for that field. May be overridden after instantiation using\n        set_log\n        A single boolean can be passed to signify all fields should use\n        logarithmic (True) or linear scaling (False).\n        Default: True.\n\n    Examples\n    --------\n\n    This creates profiles of a single dataset.\n\n    >>> import yt\n    >>> ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n    >>> ad = ds.all_data()\n    >>> plot = yt.ProfilePlot(\n    ...     ad,\n    ...     (\"gas\", \"density\"),\n    ...     [(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")],\n    ...     weight_field=(\"gas\", \"mass\"),\n    ...     plot_spec=dict(color=\"red\", linestyle=\"--\"),\n    ... )\n    >>> plot.save()\n\n    This creates profiles from a time series object.\n\n    >>> es = yt.load_simulation(\"AMRCosmology.enzo\", \"Enzo\")\n    >>> es.get_time_series()\n\n    >>> profiles = []\n    >>> labels = []\n    >>> plot_specs = []\n    >>> for ds in es[-4:]:\n    ...     ad = ds.all_data()\n    ...     profiles.append(\n    ...         create_profile(\n    ...             ad,\n    ...             [(\"gas\", \"density\")],\n    ...             fields=[(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")],\n    ...         )\n    ...     )\n    ...     labels.append(ds.current_redshift)\n    ...     plot_specs.append(dict(linestyle=\"--\", alpha=0.7))\n\n    >>> plot = yt.ProfilePlot.from_profiles(\n    ...     profiles, labels=labels, plot_specs=plot_specs\n    ... )\n    >>> plot.save()\n\n    Use set_line_property to change line properties of one or all profiles.\n\n    \"\"\"\n\n    _default_figure_size = (10.0, 8.0)\n    _default_font_size = 18.0\n\n    x_log = None\n    y_log = None\n    x_title = None\n    y_title = None\n    _plot_valid = False\n\n    def __init__(\n        self,\n        data_source,\n        x_field,\n        y_fields,\n        weight_field=(\"gas\", \"mass\"),\n        n_bins=64,\n        accumulation=False,\n        fractional=False,\n        label=None,\n        plot_spec=None,\n        x_log=True,\n        y_log=True,\n    ):\n        data_source = data_object_or_all_data(data_source)\n        y_fields = list(iter_fields(y_fields))\n        logs = {x_field: bool(x_log)}\n        if isinstance(y_log, bool):\n            y_log = dict.fromkeys(y_fields, y_log)\n\n        if isinstance(data_source.ds, YTProfileDataset):\n            profiles = [data_source.ds.profile]\n        else:\n            profiles = [\n                create_profile(\n                    data_source,\n                    [x_field],\n                    n_bins=[n_bins],\n                    fields=y_fields,\n                    weight_field=weight_field,\n                    accumulation=accumulation,\n                    fractional=fractional,\n                    logs=logs,\n                )\n            ]\n\n        if plot_spec is None:\n            plot_spec = [{} for p in profiles]\n        if not isinstance(plot_spec, list):\n            plot_spec = [plot_spec.copy() for p in profiles]\n\n        ProfilePlot._initialize_instance(\n            self, data_source, profiles, label, plot_spec, y_log\n        )\n\n    @classmethod\n    def _initialize_instance(\n        cls,\n        obj,\n        data_source,\n        profiles,\n        labels,\n        plot_specs,\n        y_log,\n    ):\n        obj._plot_title = {}\n        obj._plot_text = {}\n        obj._text_xpos = {}\n        obj._text_ypos = {}\n        obj._text_kwargs = {}\n\n        super(ProfilePlot, obj).__init__(data_source)\n        obj.profiles = list(always_iterable(profiles))\n        obj.x_log = None\n        obj.y_log = sanitize_field_tuple_keys(y_log, data_source) or {}\n        obj.y_title = {}\n        obj.x_title = None\n        obj.label = sanitize_label(labels, len(obj.profiles))\n        if plot_specs is None:\n            plot_specs = [{} for p in obj.profiles]\n        obj.plot_spec = plot_specs\n        obj._xlim = (None, None)\n        obj._setup_plots()\n        obj._plot_valid = False  # see https://github.com/yt-project/yt/issues/4489\n        return obj\n\n    def _get_axrect(self):\n        return (0.1, 0.1, 0.8, 0.8)\n\n    @validate_plot\n    def save(\n        self,\n        name: str | None = None,\n        suffix: str | None = None,\n        mpl_kwargs: dict[str, Any] | None = None,\n    ):\n        r\"\"\"\n        Saves a 1d profile plot.\n\n        Parameters\n        ----------\n        name : str, optional\n            The output file keyword.\n        suffix : string, optional\n            Specify the image type by its suffix. If not specified, the output\n            type will be inferred from the filename. Defaults to '.png'.\n        mpl_kwargs : dict, optional\n            A dict of keyword arguments to be passed to matplotlib.\n        \"\"\"\n        if not self._plot_valid:\n            self._setup_plots()\n\n        # Mypy is hardly convinced that we have a `profiles` attribute\n        # at this stage, so we're lasily going to deactivate it locally\n        unique = set(self.plots.values())\n        iters: Iterable[tuple[int | FieldKey, PlotMPL]]\n        if len(unique) < len(self.plots):\n            iters = enumerate(sorted(unique))\n        else:\n            iters = self.plots.items()\n\n        if name is None:\n            if len(self.profiles) == 1:  # type: ignore\n                name = str(self.profiles[0].ds)  # type: ignore\n            else:\n                name = \"Multi-data\"\n\n        name = validate_image_name(name, suffix)\n        prefix, suffix = os.path.splitext(name)\n\n        xfn = self.profiles[0].x_field  # type: ignore\n        if isinstance(xfn, tuple):\n            xfn = xfn[1]\n\n        names = []\n        for uid, plot in iters:\n            if isinstance(uid, tuple):\n                uid = uid[1]\n            uid_name = f\"{prefix}_1d-Profile_{xfn}_{uid}{suffix}\"\n            names.append(uid_name)\n            with mpl.style.context(\"yt.default\"):\n                plot.save(uid_name, mpl_kwargs=mpl_kwargs)\n        return names\n\n    @validate_plot\n    def show(self):\n        r\"\"\"This will send any existing plots to the IPython notebook.\n\n        If yt is being run from within an IPython session, and it is able to\n        determine this, this function will send any existing plots to the\n        notebook for display.\n\n        If yt can't determine if it's inside an IPython session, it will raise\n        YTNotInsideNotebook.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> pp = ProfilePlot(ds.all_data(), (\"gas\", \"density\"), (\"gas\", \"temperature\"))\n        >>> pp.show()\n\n        \"\"\"\n        if IS_IPYTHON:\n            from IPython.display import display\n\n            display(self)\n        else:\n            raise YTNotInsideNotebook\n\n    @validate_plot\n    def _repr_html_(self):\n        \"\"\"Return an html representation of the plot object. Will display as a\n        png for each WindowPlotMPL instance in self.plots\"\"\"\n        ret = \"\"\n        unique = set(self.plots.values())\n        if len(unique) < len(self.plots):\n            iters = sorted(unique)\n        else:\n            iters = self.plots.values()\n        for plot in iters:\n            with mpl.style.context(\"yt.default\"):\n                img = plot._repr_png_()\n            img = base64.b64encode(img).decode()\n            ret += (\n                r'<img style=\"max-width:100%;max-height:100%;\" '\n                rf'src=\"data:image/png;base64,{img}\"><br>'\n            )\n        return ret\n\n    def _setup_plots(self):\n        if self._plot_valid:\n            return\n        for f, p in self.plots.items():\n            p.axes.cla()\n            if f in self._plot_text:\n                p.axes.text(\n                    self._text_xpos[f],\n                    self._text_ypos[f],\n                    self._plot_text[f],\n                    fontproperties=self._font_properties,\n                    **self._text_kwargs[f],\n                )\n        self._set_font_properties()\n\n        for i, profile in enumerate(self.profiles):\n            for field, field_data in profile.items():\n                plot = self._get_plot_instance(field)\n                plot.axes.plot(\n                    np.array(profile.x),\n                    np.array(field_data),\n                    label=self.label[i],\n                    **self.plot_spec[i],\n                )\n\n        for profile in self.profiles:\n            for fname in profile.keys():\n                axes = self.plots[fname].axes\n                xscale, yscale = self._get_field_log(fname, profile)\n                xtitle, ytitle = self._get_field_title(fname, profile)\n\n                axes.set_xscale(xscale)\n                axes.set_yscale(yscale)\n\n                axes.set_ylabel(ytitle)\n                axes.set_xlabel(xtitle)\n\n                pnh = self.plots[fname].norm_handler\n\n                axes.set_ylim(pnh.vmin, pnh.vmax)\n                axes.set_xlim(*self._xlim)\n\n                if fname in self._plot_title:\n                    axes.set_title(self._plot_title[fname])\n\n                if any(self.label):\n                    axes.legend(loc=\"best\")\n        self._set_font_properties()\n        self._plot_valid = True\n\n    @classmethod\n    def from_profiles(cls, profiles, labels=None, plot_specs=None, y_log=None):\n        r\"\"\"\n        Instantiate a ProfilePlot object from a list of profiles\n        created with :func:`~yt.data_objects.profiles.create_profile`.\n\n        Parameters\n        ----------\n        profiles : a profile or list of profiles\n            A single profile or list of profile objects created with\n            :func:`~yt.data_objects.profiles.create_profile`.\n        labels : list of strings\n            A list of labels for each profile to be overplotted.\n            Default: None.\n        plot_specs : list of dicts\n            A list of dictionaries containing plot keyword\n            arguments.  For example, [dict(color=\"red\", linestyle=\":\")].\n            Default: None.\n\n        Examples\n        --------\n\n        >>> from yt import load_simulation\n        >>> es = load_simulation(\"AMRCosmology.enzo\", \"Enzo\")\n        >>> es.get_time_series()\n\n        >>> profiles = []\n        >>> labels = []\n        >>> plot_specs = []\n        >>> for ds in es[-4:]:\n        ...     ad = ds.all_data()\n        ...     profiles.append(\n        ...         create_profile(\n        ...             ad,\n        ...             [(\"gas\", \"density\")],\n        ...             fields=[(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")],\n        ...         )\n        ...     )\n        ...     labels.append(ds.current_redshift)\n        ...     plot_specs.append(dict(linestyle=\"--\", alpha=0.7))\n        >>> plot = ProfilePlot.from_profiles(\n        ...     profiles, labels=labels, plot_specs=plot_specs\n        ... )\n        >>> plot.save()\n\n        \"\"\"\n        if labels is not None and len(profiles) != len(labels):\n            raise RuntimeError(\"Profiles list and labels list must be the same size.\")\n        if plot_specs is not None and len(plot_specs) != len(profiles):\n            raise RuntimeError(\n                \"Profiles list and plot_specs list must be the same size.\"\n            )\n        obj = cls.__new__(cls)\n        profiles = list(always_iterable(profiles))\n        return cls._initialize_instance(\n            obj, profiles[0].data_source, profiles, labels, plot_specs, y_log\n        )\n\n    @invalidate_plot\n    def set_line_property(self, property, value, index=None):\n        r\"\"\"\n        Set properties for one or all lines to be plotted.\n\n        Parameters\n        ----------\n        property : str\n            The line property to be set.\n        value : str, int, float\n            The value to set for the line property.\n        index : int\n            The index of the profile in the list of profiles to be\n            changed.  If None, change all plotted lines.\n            Default : None.\n\n        Examples\n        --------\n\n        Change all the lines in a plot\n        plot.set_line_property(\"linestyle\", \"-\")\n\n        Change a single line.\n        plot.set_line_property(\"linewidth\", 4, index=0)\n\n        \"\"\"\n        if index is None:\n            specs = self.plot_spec\n        else:\n            specs = [self.plot_spec[index]]\n        for spec in specs:\n            spec[property] = value\n        return self\n\n    @invalidate_plot\n    def set_log(self, field, log):\n        \"\"\"set a field to log or linear.\n\n        Parameters\n        ----------\n        field : string\n            the field to set a transform\n        log : boolean\n            Log on/off.\n        \"\"\"\n        if field == \"all\":\n            self.x_log = log\n            for field in list(self.profiles[0].field_data.keys()):\n                self.y_log[field] = log\n        else:\n            (field,) = self.profiles[0].data_source._determine_fields([field])\n            if field == self.profiles[0].x_field:\n                self.x_log = log\n            elif field in self.profiles[0].field_data:\n                self.y_log[field] = log\n            else:\n                raise KeyError(f\"Field {field} not in profile plot!\")\n        return self\n\n    @invalidate_plot\n    def set_ylabel(self, field, label):\n        \"\"\"Sets a new ylabel for the specified fields\n\n        Parameters\n        ----------\n        field : string\n           The name of the field that is to be changed.\n\n        label : string\n           The label to be placed on the y-axis\n        \"\"\"\n        if field == \"all\":\n            for field in self.profiles[0].field_data:\n                self.y_title[field] = label\n        else:\n            (field,) = self.profiles[0].data_source._determine_fields([field])\n            if field in self.profiles[0].field_data:\n                self.y_title[field] = label\n            else:\n                raise KeyError(f\"Field {field} not in profile plot!\")\n\n        return self\n\n    @invalidate_plot\n    def set_xlabel(self, label):\n        \"\"\"Sets a new xlabel for all profiles\n\n        Parameters\n        ----------\n        label : string\n           The label to be placed on the x-axis\n        \"\"\"\n        self.x_title = label\n\n        return self\n\n    @invalidate_plot\n    def set_unit(self, field, unit):\n        \"\"\"Sets a new unit for the requested field\n\n        Parameters\n        ----------\n        field : string\n           The name of the field that is to be changed.\n\n        unit : string or Unit object\n           The name of the new unit.\n        \"\"\"\n        fd = self.profiles[0].data_source._determine_fields(field)[0]\n        for profile in self.profiles:\n            if fd == profile.x_field:\n                profile.set_x_unit(unit)\n            elif fd[1] in self.profiles[0].field_map:\n                profile.set_field_unit(field, unit)\n            else:\n                raise KeyError(f\"Field {field} not in profile plot!\")\n        return self\n\n    @invalidate_plot\n    def set_xlim(self, xmin=None, xmax=None):\n        \"\"\"Sets the limits of the bin field\n\n        Parameters\n        ----------\n\n        xmin : float or None\n          The new x minimum.  Defaults to None, which leaves the xmin\n          unchanged.\n\n        xmax : float or None\n          The new x maximum.  Defaults to None, which leaves the xmax\n          unchanged.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> pp = yt.ProfilePlot(\n        ...     ds.all_data(), (\"gas\", \"density\"), (\"gas\", \"temperature\")\n        ... )\n        >>> pp.set_xlim(1e-29, 1e-24)\n        >>> pp.save()\n\n        \"\"\"\n        self._xlim = (xmin, xmax)\n        for i, p in enumerate(self.profiles):\n            if xmin is None:\n                xmi = p.x_bins.min()\n            else:\n                xmi = xmin\n            if xmax is None:\n                xma = p.x_bins.max()\n            else:\n                xma = xmax\n            extrema = {p.x_field: ((xmi, str(p.x.units)), (xma, str(p.x.units)))}\n            units = {p.x_field: str(p.x.units)}\n            if self.x_log is None:\n                logs = None\n            else:\n                logs = {p.x_field: self.x_log}\n            for field in p.field_map.values():\n                units[field] = str(p.field_data[field].units)\n            self.profiles[i] = create_profile(\n                p.data_source,\n                p.x_field,\n                n_bins=len(p.x_bins) - 1,\n                fields=list(p.field_map.values()),\n                weight_field=p.weight_field,\n                accumulation=p.accumulation,\n                fractional=p.fractional,\n                logs=logs,\n                extrema=extrema,\n                units=units,\n            )\n        return self\n\n    @invalidate_plot\n    def set_ylim(self, field, ymin=None, ymax=None):\n        \"\"\"Sets the plot limits for the specified field we are binning.\n\n        Parameters\n        ----------\n\n        field : string or field tuple\n\n        The field that we want to adjust the plot limits for.\n\n        ymin : float or None\n          The new y minimum.  Defaults to None, which leaves the ymin\n          unchanged.\n\n        ymax : float or None\n          The new y maximum.  Defaults to None, which leaves the ymax\n          unchanged.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> pp = yt.ProfilePlot(\n        ...     ds.all_data(),\n        ...     (\"gas\", \"density\"),\n        ...     [(\"gas\", \"temperature\"), (\"gas\", \"velocity_x\")],\n        ... )\n        >>> pp.set_ylim((\"gas\", \"temperature\"), 1e4, 1e6)\n        >>> pp.save()\n\n        \"\"\"\n        fields = list(self.plots.keys()) if field == \"all\" else field\n        for profile in self.profiles:\n            for field in profile.data_source._determine_fields(fields):\n                if field in profile.field_map:\n                    field = profile.field_map[field]\n                pnh = self.plots[field].norm_handler\n                pnh.vmin = ymin\n                pnh.vmax = ymax\n                # Continue on to the next profile.\n                break\n        return self\n\n    def _set_font_properties(self):\n        for f in self.plots:\n            self.plots[f]._set_font_properties(self._font_properties, self._font_color)\n\n    def _get_field_log(self, field_y, profile):\n        yfi = profile.field_info[field_y]\n        if self.x_log is None:\n            x_log = profile.x_log\n        else:\n            x_log = self.x_log\n        y_log = self.y_log.get(field_y, yfi.take_log)\n        scales = {True: \"log\", False: \"linear\"}\n        return scales[x_log], scales[y_log]\n\n    def _get_field_label(self, field, field_info, field_unit, fractional=False):\n        field_unit = field_unit.latex_representation()\n        field_name = field_info.display_name\n        if isinstance(field, tuple):\n            field = field[1]\n        if field_name is None:\n            field_name = field_info.get_latex_display_name()\n        elif field_name.find(\"$\") == -1:\n            field_name = field_name.replace(\" \", r\"\\ \")\n            field_name = r\"$\\rm{\" + field_name + r\"}$\"\n        if fractional:\n            label = field_name + r\"$\\rm{\\ Probability\\ Density}$\"\n        elif field_unit is None or field_unit == \"\":\n            label = field_name\n        else:\n            label = field_name + _get_units_label(field_unit)\n        return label\n\n    def _get_field_title(self, field_y, profile):\n        field_x = profile.x_field\n        xfi = profile.field_info[field_x]\n        yfi = profile.field_info[field_y]\n        x_unit = profile.x.units\n        y_unit = profile.field_units[field_y]\n        fractional = profile.fractional\n        x_title = self.x_title or self._get_field_label(field_x, xfi, x_unit)\n        y_title = self.y_title.get(field_y, None) or self._get_field_label(\n            field_y, yfi, y_unit, fractional\n        )\n\n        return (x_title, y_title)\n\n    @invalidate_plot\n    def annotate_title(self, title, field=\"all\"):\n        r\"\"\"Set a title for the plot.\n\n        Parameters\n        ----------\n        title : str\n          The title to add.\n        field : str or list of str\n          The field name for which title needs to be set.\n\n        Examples\n        --------\n        >>> # To set title for all the fields:\n        >>> plot.annotate_title(\"This is a Profile Plot\")\n\n        >>> # To set title for specific fields:\n        >>> plot.annotate_title(\"Profile Plot for Temperature\", (\"gas\", \"temperature\"))\n\n        >>> # Setting same plot title for both the given fields\n        >>> plot.annotate_title(\n        ...     \"Profile Plot: Temperature-Dark Matter Density\",\n        ...     [(\"gas\", \"temperature\"), (\"deposit\", \"dark_matter_density\")],\n        ... )\n\n        \"\"\"\n        fields = list(self.plots.keys()) if field == \"all\" else field\n        for profile in self.profiles:\n            for field in profile.data_source._determine_fields(fields):\n                if field in profile.field_map:\n                    field = profile.field_map[field]\n                self._plot_title[field] = title\n        return self\n\n    @invalidate_plot\n    def annotate_text(self, xpos=0.0, ypos=0.0, text=None, field=\"all\", **text_kwargs):\n        r\"\"\"Allow the user to insert text onto the plot\n\n        The x-position and y-position must be given as well as the text string.\n        Add *text* to plot at location *xpos*, *ypos* in plot coordinates for\n        the given fields or by default for all fields.\n        (see example below).\n\n        Parameters\n        ----------\n        xpos : float\n          Position on plot in x-coordinates.\n        ypos : float\n          Position on plot in y-coordinates.\n        text : str\n          The text to insert onto the plot.\n        field : str or tuple\n          The name of the field to add text to.\n        **text_kwargs : dict\n          Extra keyword arguments will be passed to matplotlib text instance\n\n        >>> import yt\n        >>> from yt.units import kpc\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> my_galaxy = ds.disk(ds.domain_center, [0.0, 0.0, 1.0], 10 * kpc, 3 * kpc)\n        >>> plot = yt.ProfilePlot(\n        ...     my_galaxy, (\"gas\", \"density\"), [(\"gas\", \"temperature\")]\n        ... )\n\n        >>> # Annotate text for all the fields\n        >>> plot.annotate_text(1e-26, 1e5, \"This is annotated text in the plot area.\")\n        >>> plot.save()\n\n        >>> # Annotate text for a given field\n        >>> plot.annotate_text(1e-26, 1e5, \"Annotated text\", (\"gas\", \"temperature\"))\n        >>> plot.save()\n\n        >>> # Annotate text for multiple fields\n        >>> fields = [(\"gas\", \"temperature\"), (\"gas\", \"density\")]\n        >>> plot.annotate_text(1e-26, 1e5, \"Annotated text\", fields)\n        >>> plot.save()\n\n        \"\"\"\n        fields = list(self.plots.keys()) if field == \"all\" else field\n        for profile in self.profiles:\n            for field in profile.data_source._determine_fields(fields):\n                if field in profile.field_map:\n                    field = profile.field_map[field]\n                self._plot_text[field] = text\n                self._text_xpos[field] = xpos\n                self._text_ypos[field] = ypos\n                self._text_kwargs[field] = text_kwargs\n        return self\n\n\nclass PhasePlot(ImagePlotContainer):\n    r\"\"\"\n    Create a 2d profile (phase) plot from a data source or from\n    profile object created with\n    `yt.data_objects.profiles.create_profile`.\n\n    Given a data object (all_data, region, sphere, etc.), an x field,\n    y field, and z field (or fields), this will create a two-dimensional\n    profile of the average (or total) value of the z field in bins of the\n    x and y fields.\n\n    Parameters\n    ----------\n    data_source : YTSelectionContainer Object\n        The data object to be profiled, such as all_data, region, or\n        sphere. If a dataset is passed in instead, an all_data data object\n        is generated internally from the dataset.\n    x_field : str\n        The x binning field for the profile.\n    y_field : str\n        The y binning field for the profile.\n    z_fields : str or list\n        The field or fields to be profiled.\n    weight_field : str\n        The weight field for calculating weighted averages.  If None,\n        the profile values are the sum of the field values within the bin.\n        Otherwise, the values are a weighted average.\n        Default : (\"gas\", \"mass\")\n    x_bins : int\n        The number of bins in x field for the profile.\n        Default: 128.\n    y_bins : int\n        The number of bins in y field for the profile.\n        Default: 128.\n    accumulation : bool or list of bools\n        If True, the profile values for a bin n are the cumulative sum of\n        all the values from bin 0 to n.  If -True, the sum is reversed so\n        that the value for bin n is the cumulative sum from bin N (total bins)\n        to n.  A list of values can be given to control the summation in each\n        dimension independently.\n        Default: False.\n    fractional : If True the profile values are divided by the sum of all\n        the profile data such that the profile represents a probability\n        distribution function.\n    fontsize : int\n        Font size for all text in the plot.\n        Default: 18.\n    figure_size : int\n        Size in inches of the image.\n        Default: 8 (8x8)\n    shading : str\n        This argument is directly passed down to matplotlib.axes.Axes.pcolormesh\n        see\n        https://matplotlib.org/3.3.1/gallery/images_contours_and_fields/pcolormesh_grids.html#sphx-glr-gallery-images-contours-and-fields-pcolormesh-grids-py  # noqa\n        Default: 'nearest'\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"enzo_tiny_cosmology/DD0046/DD0046\")\n    >>> ad = ds.all_data()\n    >>> plot = yt.PhasePlot(\n    ...     ad,\n    ...     (\"gas\", \"density\"),\n    ...     (\"gas\", \"temperature\"),\n    ...     [(\"gas\", \"mass\")],\n    ...     weight_field=None,\n    ... )\n    >>> plot.save()\n\n    >>> # Change plot properties.\n    >>> plot.set_cmap((\"gas\", \"mass\"), \"jet\")\n    >>> plot.set_zlim((\"gas\", \"mass\"), 1e8, 1e13)\n    >>> plot.annotate_title(\"This is a phase plot\")\n\n    \"\"\"\n\n    x_log = None\n    y_log = None\n    plot_title = None\n    _plot_valid = False\n    _profile_valid = False\n    _plot_type = \"Phase\"\n    _xlim = (None, None)\n    _ylim = (None, None)\n\n    def __init__(\n        self,\n        data_source,\n        x_field,\n        y_field,\n        z_fields,\n        weight_field=(\"gas\", \"mass\"),\n        x_bins=128,\n        y_bins=128,\n        accumulation=False,\n        fractional=False,\n        fontsize=18,\n        figure_size=8.0,\n        shading=\"nearest\",\n    ):\n        data_source = data_object_or_all_data(data_source)\n\n        if isinstance(z_fields, tuple):\n            z_fields = [z_fields]\n        z_fields = list(always_iterable(z_fields))\n\n        if isinstance(data_source.ds, YTProfileDataset):\n            profile = data_source.ds.profile\n        else:\n            profile = create_profile(\n                data_source,\n                [x_field, y_field],\n                z_fields,\n                n_bins=[x_bins, y_bins],\n                weight_field=weight_field,\n                accumulation=accumulation,\n                fractional=fractional,\n            )\n\n        type(self)._initialize_instance(\n            self, data_source, profile, fontsize, figure_size, shading\n        )\n\n    @classmethod\n    def _initialize_instance(\n        cls, obj, data_source, profile, fontsize, figure_size, shading\n    ):\n        obj.plot_title = {}\n        obj.z_log = {}\n        obj.z_title = {}\n        obj._initfinished = False\n        obj.x_log = None\n        obj.y_log = None\n        obj._plot_text = {}\n        obj._text_xpos = {}\n        obj._text_ypos = {}\n        obj._text_kwargs = {}\n        obj._profile = profile\n        obj._shading = shading\n        obj._profile_valid = True\n        obj._xlim = (None, None)\n        obj._ylim = (None, None)\n        super(PhasePlot, obj).__init__(data_source, figure_size, fontsize)\n        obj._setup_plots()\n        obj._plot_valid = False  # see https://github.com/yt-project/yt/issues/4489\n        obj._initfinished = True\n        return obj\n\n    def _get_field_title(self, field_z, profile):\n        field_x = profile.x_field\n        field_y = profile.y_field\n        xfi = profile.field_info[field_x]\n        yfi = profile.field_info[field_y]\n        zfi = profile.field_info[field_z]\n        x_unit = profile.x.units\n        y_unit = profile.y.units\n        z_unit = profile.field_units[field_z]\n        fractional = profile.fractional\n        x_label, y_label, z_label = self._get_axes_labels(field_z)\n        x_title = x_label or self._get_field_label(field_x, xfi, x_unit)\n        y_title = y_label or self._get_field_label(field_y, yfi, y_unit)\n        z_title = z_label or self._get_field_label(field_z, zfi, z_unit, fractional)\n        return (x_title, y_title, z_title)\n\n    def _get_field_label(self, field, field_info, field_unit, fractional=False):\n        field_unit = field_unit.latex_representation()\n        field_name = field_info.display_name\n        if isinstance(field, tuple):\n            field = field[1]\n        if field_name is None:\n            field_name = field_info.get_latex_display_name()\n        elif field_name.find(\"$\") == -1:\n            field_name = field_name.replace(\" \", r\"\\ \")\n            field_name = r\"$\\rm{\" + field_name + r\"}$\"\n        if fractional:\n            label = field_name + r\"$\\rm{\\ Probability\\ Density}$\"\n        elif field_unit is None or field_unit == \"\":\n            label = field_name\n        else:\n            label = field_name + _get_units_label(field_unit)\n        return label\n\n    def _get_field_log(self, field_z, profile):\n        zfi = profile.field_info[field_z]\n        if self.x_log is None:\n            x_log = profile.x_log\n        else:\n            x_log = self.x_log\n        if self.y_log is None:\n            y_log = profile.y_log\n        else:\n            y_log = self.y_log\n        if field_z in self.z_log:\n            z_log = self.z_log[field_z]\n        else:\n            z_log = zfi.take_log\n        scales = {True: \"log\", False: \"linear\"}\n        return scales[x_log], scales[y_log], scales[z_log]\n\n    @property\n    def profile(self):\n        if not self._profile_valid:\n            self._recreate_profile()\n        return self._profile\n\n    @property\n    def fields(self):\n        return list(self.plots.keys())\n\n    def _setup_plots(self):\n        if self._plot_valid:\n            return\n        for f, data in self.profile.items():\n            if f in self.plots:\n                pnh = self.plots[f].norm_handler\n                cbh = self.plots[f].colorbar_handler\n                draw_axes = self.plots[f]._draw_axes\n                if self.plots[f].figure is not None:\n                    fig = self.plots[f].figure\n                    axes = self.plots[f].axes\n                    cax = self.plots[f].cax\n                else:\n                    fig = None\n                    axes = None\n                    cax = None\n            else:\n                pnh, cbh = self._get_default_handlers(\n                    field=f, default_display_units=self.profile[f].units\n                )\n                fig = None\n                axes = None\n                cax = None\n                draw_axes = True\n\n            x_scale, y_scale, z_scale = self._get_field_log(f, self.profile)\n            x_title, y_title, z_title = self._get_field_title(f, self.profile)\n\n            font_size = self._font_properties.get_size()\n            f = self.profile.data_source._determine_fields(f)[0]\n\n            # if this is a Particle Phase Plot AND if we using a single color,\n            # override the colorbar here.\n            splat_color = getattr(self, \"splat_color\", None)\n            if splat_color is not None:\n                cbh.cmap = mpl.colors.ListedColormap(splat_color, \"dummy\")\n\n            masked_data = data.copy()\n            masked_data[~self.profile.used] = np.nan\n            self.plots[f] = PhasePlotMPL(\n                self.profile.x,\n                self.profile.y,\n                masked_data,\n                x_scale,\n                y_scale,\n                self.figure_size,\n                font_size,\n                fig,\n                axes,\n                cax,\n                shading=self._shading,\n                norm_handler=pnh,\n                colorbar_handler=cbh,\n            )\n\n            self.plots[f]._toggle_axes(draw_axes)\n            self.plots[f]._toggle_colorbar(cbh.draw_cbar)\n\n            self.plots[f].axes.xaxis.set_label_text(x_title)\n            self.plots[f].axes.yaxis.set_label_text(y_title)\n            self.plots[f].cax.yaxis.set_label_text(z_title)\n\n            self.plots[f].axes.set_xlim(self._xlim)\n            self.plots[f].axes.set_ylim(self._ylim)\n\n            if f in self._plot_text:\n                self.plots[f].axes.text(\n                    self._text_xpos[f],\n                    self._text_ypos[f],\n                    self._plot_text[f],\n                    fontproperties=self._font_properties,\n                    **self._text_kwargs[f],\n                )\n\n            if f in self.plot_title:\n                self.plots[f].axes.set_title(self.plot_title[f])\n\n            # x-y axes minorticks\n            if f not in self._minorticks:\n                self._minorticks[f] = True\n            if self._minorticks[f]:\n                self.plots[f].axes.minorticks_on()\n            else:\n                self.plots[f].axes.minorticks_off()\n\n        self._set_font_properties()\n\n        # if this is a particle plot with one color only, hide the cbar here\n        if hasattr(self, \"use_cbar\") and not self.use_cbar:\n            self.plots[f].hide_colorbar()\n\n        self._plot_valid = True\n\n    @classmethod\n    def from_profile(cls, profile, fontsize=18, figure_size=8.0, shading=\"nearest\"):\n        r\"\"\"\n        Instantiate a PhasePlot object from a profile object created\n        with :func:`~yt.data_objects.profiles.create_profile`.\n\n        Parameters\n        ----------\n        profile : An instance of :class:`~yt.data_objects.profiles.ProfileND`\n             A single profile object.\n        fontsize : float\n             The fontsize to use, in points.\n        figure_size : float\n             The figure size to use, in inches.\n        shading : str\n            This argument is directly passed down to matplotlib.axes.Axes.pcolormesh\n            see\n            https://matplotlib.org/3.3.1/gallery/images_contours_and_fields/pcolormesh_grids.html#sphx-glr-gallery-images-contours-and-fields-pcolormesh-grids-py  # noqa\n            Default: 'nearest'\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> extrema = {\n        ...     (\"gas\", \"density\"): (1e-31, 1e-24),\n        ...     (\"gas\", \"temperature\"): (1e1, 1e8),\n        ...     (\"gas\", \"mass\"): (1e-6, 1e-1),\n        ... }\n        >>> profile = yt.create_profile(\n        ...     ds.all_data(),\n        ...     [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        ...     fields=[(\"gas\", \"mass\")],\n        ...     extrema=extrema,\n        ...     fractional=True,\n        ... )\n        >>> ph = yt.PhasePlot.from_profile(profile)\n        >>> ph.save()\n        \"\"\"\n        obj = cls.__new__(cls)\n        data_source = profile.data_source\n        return cls._initialize_instance(\n            obj, data_source, profile, fontsize, figure_size, shading\n        )\n\n    def annotate_text(self, xpos=0.0, ypos=0.0, text=None, **text_kwargs):\n        r\"\"\"\n        Allow the user to insert text onto the plot\n        The x-position and y-position must be given as well as the text string.\n        Add *text* tp plot at location *xpos*, *ypos* in plot coordinates\n        (see example below).\n\n        Parameters\n        ----------\n        xpos : float\n          Position on plot in x-coordinates.\n        ypos : float\n          Position on plot in y-coordinates.\n        text : str\n          The text to insert onto the plot.\n        **text_kwargs : dict\n          Extra keyword arguments will be passed to matplotlib text instance\n\n        >>> plot.annotate_text(1e-15, 5e4, \"Hello YT\")\n\n        \"\"\"\n        for f in self.data_source._determine_fields(list(self.plots.keys())):\n            if self.plots[f].figure is not None and text is not None:\n                self.plots[f].axes.text(\n                    xpos,\n                    ypos,\n                    text,\n                    fontproperties=self._font_properties,\n                    **text_kwargs,\n                )\n            self._plot_text[f] = text\n            self._text_xpos[f] = xpos\n            self._text_ypos[f] = ypos\n            self._text_kwargs[f] = text_kwargs\n        return self\n\n    @validate_plot\n    def save(self, name: str | None = None, suffix: str | None = None, mpl_kwargs=None):\n        r\"\"\"\n        Saves a 2d profile plot.\n\n        Parameters\n        ----------\n        name : str, optional\n            The output file keyword.\n        suffix : string, optional\n           Specify the image type by its suffix. If not specified, the output\n           type will be inferred from the filename. Defaults to '.png'.\n        mpl_kwargs : dict, optional\n           A dict of keyword arguments to be passed to matplotlib.\n\n        >>> plot.save(mpl_kwargs={\"bbox_inches\": \"tight\"})\n\n        \"\"\"\n        names = []\n        if not self._plot_valid:\n            self._setup_plots()\n        if mpl_kwargs is None:\n            mpl_kwargs = {}\n        if name is None:\n            name = str(self.profile.ds)\n        name = os.path.expanduser(name)\n        xfn = self.profile.x_field\n        yfn = self.profile.y_field\n        if isinstance(xfn, tuple):\n            xfn = xfn[1]\n        if isinstance(yfn, tuple):\n            yfn = yfn[1]\n        for f in self.profile.field_data:\n            _f = f\n            if isinstance(f, tuple):\n                _f = _f[1]\n            middle = f\"2d-Profile_{xfn}_{yfn}_{_f}\"\n            splitname = os.path.split(name)\n            if splitname[0] != \"\" and not os.path.isdir(splitname[0]):\n                os.makedirs(splitname[0])\n            if os.path.isdir(name) and name != str(self.profile.ds):\n                name = name + (os.sep if name[-1] != os.sep else \"\")\n                name += str(self.profile.ds)\n\n            new_name = validate_image_name(name, suffix)\n            if new_name == name:\n                for v in self.plots.values():\n                    out_name = v.save(name, mpl_kwargs)\n                    names.append(out_name)\n                return names\n\n            name = new_name\n            prefix, suffix = os.path.splitext(name)\n            name = f\"{prefix}_{middle}{suffix}\"\n\n            names.append(name)\n            self.plots[f].save(name, mpl_kwargs)\n        return names\n\n    @invalidate_plot\n    def set_title(self, field, title):\n        \"\"\"Set a title for the plot.\n\n        Parameters\n        ----------\n        field : str\n            The z field of the plot to add the title.\n        title : str\n            The title to add.\n\n        Examples\n        --------\n\n        >>> plot.set_title((\"gas\", \"mass\"), \"This is a phase plot\")\n        \"\"\"\n        self.plot_title[self.data_source._determine_fields(field)[0]] = title\n        return self\n\n    @invalidate_plot\n    def annotate_title(self, title):\n        \"\"\"Set a title for the plot.\n\n        Parameters\n        ----------\n        title : str\n            The title to add.\n\n        Examples\n        --------\n\n        >>> plot.annotate_title(\"This is a phase plot\")\n\n        \"\"\"\n        for f in self._profile.field_data:\n            self.plot_title[self.data_source._determine_fields(f)[0]] = title\n        return self\n\n    @invalidate_plot\n    def reset_plot(self):\n        self.plots = {}\n        return self\n\n    @invalidate_plot\n    def set_log(self, field, log):\n        \"\"\"set a field to log or linear.\n\n        Parameters\n        ----------\n        field : string\n            the field to set a transform\n        log : boolean\n            Log on/off.\n        \"\"\"\n        p = self._profile\n        if field == \"all\":\n            self.x_log = log\n            self.y_log = log\n            for field in p.field_data:\n                self.z_log[field] = log\n            self._profile_valid = False\n        else:\n            (field,) = self.profile.data_source._determine_fields([field])\n            if field == p.x_field:\n                self.x_log = log\n                self._profile_valid = False\n            elif field == p.y_field:\n                self.y_log = log\n                self._profile_valid = False\n            elif field in p.field_data:\n                super().set_log(field, log)\n            else:\n                raise KeyError(f\"Field {field} not in phase plot!\")\n        return self\n\n    @invalidate_plot\n    def set_unit(self, field, unit):\n        \"\"\"Sets a new unit for the requested field\n\n        Parameters\n        ----------\n        field : string\n           The name of the field that is to be changed.\n\n        unit : string or Unit object\n           The name of the new unit.\n        \"\"\"\n        fd = self.data_source._determine_fields(field)[0]\n        if fd == self.profile.x_field:\n            self.profile.set_x_unit(unit)\n        elif fd == self.profile.y_field:\n            self.profile.set_y_unit(unit)\n        elif fd in self.profile.field_data.keys():\n            self.profile.set_field_unit(field, unit)\n            self.plots[field].norm_handler.display_units = unit\n        else:\n            raise KeyError(f\"Field {field} not in phase plot!\")\n        return self\n\n    @invalidate_plot\n    @invalidate_profile\n    def set_xlim(self, xmin=None, xmax=None):\n        \"\"\"Sets the limits of the x bin field\n\n        Parameters\n        ----------\n\n        xmin : float or None\n          The new x minimum in the current x-axis units.  Defaults to None,\n          which leaves the xmin unchanged.\n\n        xmax : float or None\n          The new x maximum in the current x-axis units.  Defaults to None,\n          which leaves the xmax unchanged.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> pp = yt.PhasePlot(ds.all_data(), \"density\", \"temperature\", (\"gas\", \"mass\"))\n        >>> pp.set_xlim(1e-29, 1e-24)\n        >>> pp.save()\n\n        \"\"\"\n        p = self._profile\n        if xmin is None:\n            xmin = p.x_bins.min()\n        elif not hasattr(xmin, \"units\"):\n            xmin = self.ds.quan(xmin, p.x_bins.units)\n        if xmax is None:\n            xmax = p.x_bins.max()\n        elif not hasattr(xmax, \"units\"):\n            xmax = self.ds.quan(xmax, p.x_bins.units)\n        self._xlim = (xmin, xmax)\n        return self\n\n    @invalidate_plot\n    @invalidate_profile\n    def set_ylim(self, ymin=None, ymax=None):\n        \"\"\"Sets the plot limits for the y bin field.\n\n        Parameters\n        ----------\n\n        ymin : float or None\n          The new y minimum in the current y-axis units.  Defaults to None,\n          which leaves the ymin unchanged.\n\n        ymax : float or None\n          The new y maximum in the current y-axis units.  Defaults to None,\n          which leaves the ymax unchanged.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> pp = yt.PhasePlot(\n        ...     ds.all_data(),\n        ...     (\"gas\", \"density\"),\n        ...     (\"gas\", \"temperature\"),\n        ...     (\"gas\", \"mass\"),\n        ... )\n        >>> pp.set_ylim(1e4, 1e6)\n        >>> pp.save()\n\n        \"\"\"\n        p = self._profile\n        if ymin is None:\n            ymin = p.y_bins.min()\n        elif not hasattr(ymin, \"units\"):\n            ymin = self.ds.quan(ymin, p.y_bins.units)\n        if ymax is None:\n            ymax = p.y_bins.max()\n        elif not hasattr(ymax, \"units\"):\n            ymax = self.ds.quan(ymax, p.y_bins.units)\n        self._ylim = (ymin, ymax)\n        return self\n\n    def _recreate_profile(self):\n        p = self._profile\n        units = {p.x_field: str(p.x.units), p.y_field: str(p.y.units)}\n        zunits = {field: str(p.field_units[field]) for field in p.field_units}\n        extrema = {p.x_field: self._xlim, p.y_field: self._ylim}\n        if self.x_log is not None or self.y_log is not None:\n            logs = {}\n        else:\n            logs = None\n        if self.x_log is not None:\n            logs[p.x_field] = self.x_log\n        if self.y_log is not None:\n            logs[p.y_field] = self.y_log\n        deposition = getattr(p, \"deposition\", None)\n        additional_kwargs = {\n            \"accumulation\": p.accumulation,\n            \"fractional\": p.fractional,\n            \"deposition\": deposition,\n        }\n        self._profile = create_profile(\n            p.data_source,\n            [p.x_field, p.y_field],\n            list(p.field_map.values()),\n            n_bins=[len(p.x_bins) - 1, len(p.y_bins) - 1],\n            weight_field=p.weight_field,\n            units=units,\n            extrema=extrema,\n            logs=logs,\n            **additional_kwargs,\n        )\n        for field in zunits:\n            self._profile.set_field_unit(field, zunits[field])\n        self._profile_valid = True\n\n\nclass PhasePlotMPL(ImagePlotMPL):\n    \"\"\"A container for a single matplotlib figure and axes for a PhasePlot\"\"\"\n\n    def __init__(\n        self,\n        x_data,\n        y_data,\n        data,\n        x_scale,\n        y_scale,\n        figure_size,\n        fontsize,\n        figure,\n        axes,\n        cax,\n        shading=\"nearest\",\n        *,\n        norm_handler: NormHandler,\n        colorbar_handler: ColorbarHandler,\n    ):\n        self._initfinished = False\n        self._shading = shading\n        self._setup_layout_constraints(figure_size, fontsize)\n\n        # this line is added purely to prevent exact image comparison tests\n        # to fail, but eventually we should embrace the change and\n        # use similar values for PhasePlotMPL and WindowPlotMPL\n        self._ax_text_size[0] *= 1.1 / 1.2  # TODO: remove this\n\n        super().__init__(\n            figure=figure,\n            axes=axes,\n            cax=cax,\n            norm_handler=norm_handler,\n            colorbar_handler=colorbar_handler,\n        )\n\n        self._init_image(x_data, y_data, data, x_scale, y_scale)\n\n        self._initfinished = True\n\n    def _init_image(\n        self,\n        x_data,\n        y_data,\n        image_data,\n        x_scale,\n        y_scale,\n    ):\n        \"\"\"Store output of imshow in image variable\"\"\"\n        norm = self.norm_handler.get_norm(image_data)\n        self.image = None\n        self.cb = None\n\n        self.image = self.axes.pcolormesh(\n            np.array(x_data),\n            np.array(y_data),\n            np.array(image_data.T),\n            norm=norm,\n            cmap=self.colorbar_handler.cmap,\n            shading=self._shading,\n        )\n\n        self._set_axes()\n        self.axes.set_xscale(x_scale)\n        self.axes.set_yscale(y_scale)\n"
  },
  {
    "path": "yt/visualization/streamlines.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.construction_data_containers import YTStreamline\nfrom yt.funcs import get_pbar\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.amr_kdtree.api import AMRKDTree\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    ParallelAnalysisInterface,\n    parallel_passthrough,\n)\n\n\ndef sanitize_length(length, ds):\n    # Ensure that lengths passed in with units are returned as code_length\n    # magnitudes without units\n    if isinstance(length, YTArray):\n        return ds.arr(length).in_units(\"code_length\").d\n    else:\n        return length\n\n\nclass Streamlines(ParallelAnalysisInterface):\n    r\"\"\"A collection of streamlines that flow through the volume\n\n    The Streamlines object contains a collection of streamlines\n    defined as paths that are parallel to a specified vector field.\n\n    Parameters\n    ----------\n    ds : ~yt.data_objects.static_output.Dataset\n        This is the dataset to streamline\n    positions : array_like\n        An array of initial starting positions of the streamlines.\n    xfield : str or tuple of str, optional\n        The x component of the vector field to be streamlined.\n        Default:'velocity_x'\n    yfield : str or tuple of str, optional\n        The y component of the vector field to be streamlined.\n        Default:'velocity_y'\n    zfield : str or tuple of str, optional\n        The z component of the vector field to be streamlined.\n        Default:'velocity_z'\n    volume : `yt.extensions.volume_rendering.AMRKDTree`, optional\n        The volume to be streamlined.  Can be specified for\n        finer-grained control, but otherwise will be automatically\n        generated.  At this point it must use the AMRKDTree.\n        Default: None\n    dx : float, optional\n        Optionally specify the step size during the integration.\n        Default: minimum dx\n    length : float, optional\n        Optionally specify the length of integration.\n        Default: np.max(self.ds.domain_right_edge-self.ds.domain_left_edge)\n    direction : real, optional\n        Specifies the direction of integration.  The magnitude of this\n        value has no effect, only the sign.\n    get_magnitude: bool, optional\n        Specifies whether the Streamlines.magnitudes array should be\n        filled with the magnitude of the vector field at each point in\n        the streamline.  This seems to be a ~10% hit to performance.\n        Default: False\n\n    Examples\n    --------\n    >>> import matplotlib.pyplot as plt\n    ... import numpy as np\n    ... from mpl_toolkits.mplot3d import Axes3D\n    ... import yt\n    ... from yt.visualization.api import Streamlines\n\n    >>> # Load the dataset and set some parameters\n    >>> ds = load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> c = np.array([0.5] * 3)\n    >>> N = 100\n    >>> scale = 1.0\n    >>> pos_dx = np.random.random((N, 3)) * scale - scale / 2.0\n    >>> pos = c + pos_dx\n\n    >>> # Define and construct streamlines\n    >>> streamlines = Streamlines(\n    ...     ds, pos, \"velocity_x\", \"velocity_y\", \"velocity_z\", length=1.0\n    ... )\n    >>> streamlines.integrate_through_volume()\n\n    >>> # Make a 3D plot of the streamlines and save it to disk\n    >>> fig = plt.figure()\n    >>> ax = Axes3D(fig)\n    >>> for stream in streamlines.streamlines:\n    ...     stream = stream[np.all(stream != 0.0, axis=1)]\n    ...     ax.plot3D(stream[:, 0], stream[:, 1], stream[:, 2], alpha=0.1)\n    >>> plt.savefig(\"streamlines.png\")\n    \"\"\"\n\n    def __init__(\n        self,\n        ds,\n        positions,\n        xfield=\"velocity_x\",\n        yfield=\"velocity_x\",\n        zfield=\"velocity_x\",\n        volume=None,\n        dx=None,\n        length=None,\n        direction=1,\n        get_magnitude=False,\n    ):\n        ParallelAnalysisInterface.__init__(self)\n        self.ds = ds\n        self.start_positions = sanitize_length(positions, ds)\n        self.N = self.start_positions.shape[0]\n        # I need a data object to resolve the field names to field tuples\n        # via _determine_fields()\n        ad = self.ds.all_data()\n        self.xfield = ad._determine_fields(xfield)[0]\n        self.yfield = ad._determine_fields(yfield)[0]\n        self.zfield = ad._determine_fields(zfield)[0]\n        self.get_magnitude = get_magnitude\n        self.direction = np.sign(direction)\n        if volume is None:\n            volume = AMRKDTree(self.ds)\n            volume.set_fields(\n                [self.xfield, self.yfield, self.zfield], [False, False, False], False\n            )\n            volume.join_parallel_trees()\n        self.volume = volume\n        if dx is None:\n            dx = self.ds.index.get_smallest_dx()\n        self.dx = sanitize_length(dx, ds)\n        if length is None:\n            length = np.max(self.ds.domain_right_edge - self.ds.domain_left_edge)\n        self.length = sanitize_length(length, ds)\n        self.steps = int(self.length / self.dx) + 1\n        # Fix up the dx.\n        self.dx = 1.0 * self.length / self.steps\n        self.streamlines = np.zeros((self.N, self.steps, 3), dtype=\"float64\")\n        self.magnitudes = None\n        if self.get_magnitude:\n            self.magnitudes = np.zeros((self.N, self.steps), dtype=\"float64\")\n\n    def integrate_through_volume(self):\n        nprocs = self.comm.size\n        my_rank = self.comm.rank\n        self.streamlines[my_rank::nprocs, 0, :] = self.start_positions[my_rank::nprocs]\n\n        pbar = get_pbar(\"Streamlining\", self.N)\n        for i, stream in enumerate(self.streamlines[my_rank::nprocs]):\n            thismag = None\n            if self.get_magnitude:\n                thismag = self.magnitudes[i, :]\n            step = self.steps\n            while step > 1:\n                this_node = self.volume.locate_node(stream[-step, :])\n                step = self._integrate_through_brick(\n                    this_node, stream, step, mag=thismag\n                )\n            pbar.update(i + 1)\n        pbar.finish()\n\n        self._finalize_parallel(None)\n        self.streamlines = self.ds.arr(self.streamlines, \"code_length\")\n        if self.get_magnitude:\n            self.magnitudes = self.ds.arr(\n                self.magnitudes, self.ds.field_info[self.xfield].units\n            )\n\n    @parallel_passthrough\n    def _finalize_parallel(self, data):\n        self.streamlines = self.comm.mpi_allreduce(self.streamlines, op=\"sum\")\n        if self.get_magnitude:\n            self.magnitudes = self.comm.mpi_allreduce(self.magnitudes, op=\"sum\")\n\n    def _integrate_through_brick(self, node, stream, step, periodic=False, mag=None):\n        LE = self.ds.domain_left_edge.d\n        RE = self.ds.domain_right_edge.d\n        while step > 1:\n            self.volume.get_brick_data(node)\n            brick = node.data\n            stream[-step + 1] = stream[-step]\n            if mag is None:\n                brick.integrate_streamline(\n                    stream[-step + 1], self.direction * self.dx, None\n                )\n            else:\n                marr = [mag]\n                brick.integrate_streamline(\n                    stream[-step + 1], self.direction * self.dx, marr\n                )\n                mag[-step + 1] = marr[0]\n\n            cur_stream = stream[-step + 1, :]\n            if np.sum(np.logical_or(cur_stream < LE, cur_stream >= RE)):\n                return 0\n\n            nLE = node.get_left_edge()\n            nRE = node.get_right_edge()\n            if np.sum(np.logical_or(cur_stream < nLE, cur_stream >= nRE)):\n                return step - 1\n            step -= 1\n        return step\n\n    def clean_streamlines(self):\n        temp = np.empty(self.N, dtype=\"object\")\n        temp2 = np.empty(self.N, dtype=\"object\")\n        for i, stream in enumerate(self.streamlines):\n            mask = np.all(stream != 0.0, axis=1)\n            temp[i] = stream[mask]\n            temp2[i] = self.magnitudes[i, mask]\n        self.streamlines = temp\n        self.magnitudes = temp2\n\n    def path(self, streamline_id):\n        \"\"\"\n        Returns an YTSelectionContainer1D object defined by a streamline.\n\n        Parameters\n        ----------\n        streamline_id : int\n            This defines which streamline from the Streamlines object\n            that will define the YTSelectionContainer1D object.\n\n        Returns\n        -------\n        An YTStreamline YTSelectionContainer1D object\n\n        Examples\n        --------\n\n        >>> from yt.visualization.api import Streamlines\n        >>> streamlines = Streamlines(ds, [0.5] * 3)\n        >>> streamlines.integrate_through_volume()\n        >>> stream = streamlines.path(0)\n        >>> fig, ax = plt.subplots()\n        >>> ax.set_yscale(\"log\")\n        >>> ax.plot(stream[\"t\"], stream[\"gas\", \"density\"], \"-x\")\n\n        \"\"\"\n        return YTStreamline(\n            self.streamlines[streamline_id], ds=self.ds, length=self.length\n        )\n"
  },
  {
    "path": "yt/visualization/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/visualization/tests/test_base_plot_types.py",
    "content": "import matplotlib\nimport pytest\n\nfrom yt.visualization.base_plot_types import BACKEND_SPECS\n\n\n@pytest.mark.skipif(\n    matplotlib.__version_info__ < (3, 9),\n    reason=\"This test requires matplotlib 3.9 or higher.\",\n)\n@pytest.mark.parametrize(\"backend_key\", BACKEND_SPECS.keys())\ndef test_backend_specs(backend_key):\n    # note: while this test itself requires matplotlib 3.9, it is\n    # testing functionality in yt that can be removed once yt has\n    # a minimal matplotlib version of 3.9,\n    # see https://github.com/yt-project/yt/issues/5138\n    from matplotlib.backends import backend_registry\n\n    assert backend_registry.is_valid_backend(backend_key)\n"
  },
  {
    "path": "yt/visualization/tests/test_callbacks.py",
    "content": "import contextlib\nimport inspect\nimport shutil\nimport tempfile\n\nfrom numpy.testing import assert_array_equal, assert_raises\n\nimport yt.units as u\nfrom yt.config import ytcfg\nfrom yt.loaders import load\nfrom yt.testing import (\n    assert_fname,\n    fake_amr_ds,\n    fake_hexahedral_ds,\n    fake_random_ds,\n    fake_tetrahedral_ds,\n    requires_file,\n    requires_module,\n)\nfrom yt.utilities.answer_testing.framework import (\n    PlotWindowAttributeTest,\n    data_dir_load,\n    requires_ds,\n)\nfrom yt.utilities.exceptions import YTDataTypeUnsupported, YTPlotCallbackError\nfrom yt.visualization.api import OffAxisSlicePlot, ProjectionPlot, SlicePlot\nfrom yt.visualization.plot_container import accepts_all_fields\n\n# These are a very simple set of tests that verify that each callback is or is\n# not working.  They only check that it functions without an error; they do not\n# check that it is providing correct results. Note that test_axis_manipulations\n# is one exception, which is a full answer test.\n\n# These are the callbacks still to test:\n#\n#  X velocity\n#  X magnetic_field\n#  X quiver\n#  X contour\n#  X grids\n#  X streamlines\n#    units\n#  X line\n#    cquiver\n#    clumps\n#  X arrow\n#  X marker\n#  X sphere\n#    hop_circles\n#    hop_particles\n#    coord_axes\n#  X text\n#  X particles\n#    title\n#    flash_ray_data\n#  X timestamp\n#  X scale\n#    material_boundary\n#  X ray\n#  X line_integral_convolution\n#\n#  X flip_horizontal, flip_vertical, swap_axes (all in test_axis_manipulations)\n\n# cylindrical data for callback test\ncyl_2d = \"WDMerger_hdf5_chk_1000/WDMerger_hdf5_chk_1000.hdf5\"\ncyl_3d = \"MHD_Cyl3d_hdf5_plt_cnt_0100/MHD_Cyl3d_hdf5_plt_cnt_0100.hdf5\"\n\n\n@contextlib.contextmanager\ndef _cleanup_fname():\n    tmpdir = tempfile.mkdtemp()\n    yield tmpdir\n    shutil.rmtree(tmpdir)\n\n\ndef test_method_signature():\n    ds = fake_amr_ds(\n        fields=[(\"gas\", \"density\"), (\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\")],\n        units=[\"g/cm**3\", \"m/s\", \"m/s\"],\n    )\n    p = SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    sig = inspect.signature(p.annotate_velocity)\n    # checking the first few arguments rather than the whole signature\n    # we just want to validate that method wrapping works\n    assert list(sig.parameters.keys())[:4] == [\n        \"factor\",\n        \"scale\",\n        \"scale_units\",\n        \"normalize\",\n    ]\n\n\ndef test_init_signature_error_callback():\n    ds = fake_amr_ds(\n        fields=[(\"gas\", \"density\"), (\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\")],\n        units=[\"g/cm**3\", \"m/s\", \"m/s\"],\n    )\n    p = SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    # annotate_velocity accepts only one positional argument\n    assert_raises(TypeError, p.annotate_velocity, 1, 2, 3)\n\n\ndef check_axis_manipulation(plot_obj, prefix):\n    # convenience function for testing functionality of axis manipulation\n    # callbacks. Can use in any of the other test functions.\n\n    # test individual callbacks\n    for cb in (\"swap_axes\", \"flip_horizontal\", \"flip_vertical\"):\n        callback_handle = getattr(plot_obj, cb)\n        callback_handle()  # toggles on for axis operation\n        assert_fname(plot_obj.save(prefix)[0])\n        callback_handle()  # toggle off\n\n    # test all at once\n    for cb in (\"swap_axes\", \"flip_horizontal\", \"flip_vertical\"):\n        callback_handle = getattr(plot_obj, cb)\n        callback_handle()\n\n    assert_fname(plot_obj.save(prefix)[0])\n\n\ndef test_timestamp_callback():\n    with _cleanup_fname() as prefix:\n        ax = \"z\"\n        vector = [1.0, 1.0, 1.0]\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_timestamp()\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_timestamp()\n        assert_fname(p.save(prefix)[0])\n        p = OffAxisSlicePlot(ds, vector, (\"gas\", \"density\"))\n        p.annotate_timestamp()\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_timestamp(corner=\"lower_right\", redshift=True, draw_inset_box=True)\n        p.save(prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        p.annotate_timestamp(coord_system=\"axis\")\n        assert_fname(p.save(prefix)[0])\n\n\ndef test_timestamp_callback_code_units():\n    # see https://github.com/yt-project/yt/issues/3869\n    with _cleanup_fname() as prefix:\n        ds = fake_random_ds(2, unit_system=\"code\")\n        p = SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n        p.annotate_timestamp()\n        assert_fname(p.save(prefix)[0])\n\n\ndef test_scale_callback():\n    with _cleanup_fname() as prefix:\n        ax = \"z\"\n        vector = [1.0, 1.0, 1.0]\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_scale()\n        assert_fname(p.save(prefix)[0])\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"), width=(0.5, 1.0))\n        p.annotate_scale()\n        assert_fname(p.save(prefix)[0])\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"), width=(1.0, 1.5))\n        p.annotate_scale()\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_scale()\n        assert_fname(p.save(prefix)[0])\n        p = OffAxisSlicePlot(ds, vector, (\"gas\", \"density\"))\n        p.annotate_scale()\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_scale(corner=\"upper_right\", coeff=10.0, unit=\"kpc\")\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_scale(text_args={\"size\": 24})\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_scale(text_args={\"font\": 24})\n        assert_raises(YTPlotCallbackError)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        assert_raises(YTDataTypeUnsupported, p.annotate_scale)\n        assert_raises(YTDataTypeUnsupported, p.annotate_scale, coord_system=\"axis\")\n\n\ndef test_line_callback():\n    with _cleanup_fname() as prefix:\n        ax = \"z\"\n        vector = [1.0, 1.0, 1.0]\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_line([0.1, 0.1, 0.1], [0.5, 0.5, 0.5])\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_line([0.1, 0.1, 0.1], [0.5, 0.5, 0.5])\n        assert_fname(p.save(prefix)[0])\n        p = OffAxisSlicePlot(ds, vector, (\"gas\", \"density\"))\n        p.annotate_line([0.1, 0.1, 0.1], [0.5, 0.5, 0.5])\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_line([0.1, 0.1], [0.5, 0.5], coord_system=\"axis\", color=\"red\")\n        p.save(prefix)\n        check_axis_manipulation(p, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        assert_raises(\n            YTDataTypeUnsupported, p.annotate_line, [0.1, 0.1, 0.1], [0.5, 0.5, 0.5]\n        )\n        p.annotate_line([0.1, 0.1], [0.5, 0.5], coord_system=\"axis\")\n        assert_fname(p.save(prefix)[0])\n\n\ndef test_ray_callback():\n    with _cleanup_fname() as prefix:\n        ax = \"z\"\n        vector = [1.0, 1.0, 1.0]\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        ray = ds.ray((0.1, 0.2, 0.3), (0.6, 0.8, 0.5))\n        oray = ds.ortho_ray(0, (0.3, 0.4))\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_ray(oray)\n        p.annotate_ray(ray)\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_ray(oray)\n        p.annotate_ray(ray)\n        assert_fname(p.save(prefix)[0])\n        p = OffAxisSlicePlot(ds, vector, (\"gas\", \"density\"))\n        p.annotate_ray(oray)\n        p.annotate_ray(ray)\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_ray(oray)\n        p.annotate_ray(ray, color=\"red\")\n        p.save(prefix)\n        check_axis_manipulation(p, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        ray = ds.ray((0.1, 0.2, 0.3), (0.6, 0.8, 0.5))\n        oray = ds.ortho_ray(0, (0.3, 0.4))\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        assert_raises(YTDataTypeUnsupported, p.annotate_ray, oray)\n        assert_raises(YTDataTypeUnsupported, p.annotate_ray, ray)\n\n\ndef test_arrow_callback():\n    with _cleanup_fname() as prefix:\n        ax = \"z\"\n        vector = [1.0, 1.0, 1.0]\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_arrow([0.5, 0.5, 0.5])\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_arrow([0.5, 0.5, 0.5])\n        assert_fname(p.save(prefix)[0])\n        p = OffAxisSlicePlot(ds, vector, (\"gas\", \"density\"))\n        p.annotate_arrow([0.5, 0.5, 0.5])\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_arrow([0.5, 0.5], coord_system=\"axis\", length=0.05)\n        p.annotate_arrow(\n            [[0.5, 0.6], [0.5, 0.6], [0.5, 0.6]], coord_system=\"data\", length=0.05\n        )\n        p.annotate_arrow(\n            [[0.5, 0.6, 0.8], [0.5, 0.6, 0.8], [0.5, 0.6, 0.8]],\n            coord_system=\"data\",\n            length=0.05,\n        )\n        p.annotate_arrow(\n            [[0.5, 0.6, 0.8], [0.5, 0.6, 0.8]], coord_system=\"axis\", length=0.05\n        )\n        p.annotate_arrow(\n            [[0.5, 0.6, 0.8], [0.5, 0.6, 0.8]], coord_system=\"figure\", length=0.05\n        )\n        p.annotate_arrow(\n            [[0.5, 0.6, 0.8], [0.5, 0.6, 0.8]], coord_system=\"plot\", length=0.05\n        )\n        p.save(prefix)\n        check_axis_manipulation(p, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        assert_raises(YTDataTypeUnsupported, p.annotate_arrow, [0.5, 0.5, 0.5])\n        p.annotate_arrow([0.5, 0.5], coord_system=\"axis\")\n        assert_fname(p.save(prefix)[0])\n\n\ndef test_marker_callback():\n    with _cleanup_fname() as prefix:\n        ax = \"z\"\n        vector = [1.0, 1.0, 1.0]\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_marker([0.5, 0.5, 0.5])\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_marker([0.5, 0.5, 0.5])\n        assert_fname(p.save(prefix)[0])\n        p = OffAxisSlicePlot(ds, vector, (\"gas\", \"density\"))\n        p.annotate_marker([0.5, 0.5, 0.5])\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        coord = ds.arr([0.75, 0.75, 0.75], \"unitary\")\n        coord.convert_to_units(\"kpc\")\n        p.annotate_marker(coord, coord_system=\"data\")\n        p.annotate_marker([0.5, 0.5], coord_system=\"axis\", marker=\"*\")\n        p.annotate_marker([[0.5, 0.6], [0.5, 0.6], [0.5, 0.6]], coord_system=\"data\")\n        p.annotate_marker(\n            [[0.5, 0.6, 0.8], [0.5, 0.6, 0.8], [0.5, 0.6, 0.8]], coord_system=\"data\"\n        )\n        p.annotate_marker([[0.5, 0.6, 0.8], [0.5, 0.6, 0.8]], coord_system=\"axis\")\n        p.annotate_marker([[0.5, 0.6, 0.8], [0.5, 0.6, 0.8]], coord_system=\"figure\")\n        p.annotate_marker([[0.5, 0.6, 0.8], [0.5, 0.6, 0.8]], coord_system=\"plot\")\n        p.save(prefix)\n        check_axis_manipulation(p, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        assert_raises(YTDataTypeUnsupported, p.annotate_marker, [0.5, 0.5, 0.5])\n        p.annotate_marker([0.5, 0.5], coord_system=\"axis\")\n        assert_fname(p.save(prefix)[0])\n\n\ndef test_particles_callback():\n    with _cleanup_fname() as prefix:\n        ax = \"z\"\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), particles=1)\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_particles((10, \"Mpc\"))\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_particles((10, \"Mpc\"))\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        ad = ds.all_data()\n        p.annotate_particles(\n            (10, \"Mpc\"),\n            p_size=1.0,\n            col=\"k\",\n            marker=\"o\",\n            stride=1,\n            ptype=\"all\",\n            alpha=1.0,\n            data_source=ad,\n        )\n        p.save(prefix)\n        check_axis_manipulation(p, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        assert_raises(YTDataTypeUnsupported, p.annotate_particles, (10, \"Mpc\"))\n\n\ndef test_sphere_callback():\n    with _cleanup_fname() as prefix:\n        ax = \"z\"\n        vector = [1.0, 1.0, 1.0]\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_sphere([0.5, 0.5, 0.5], 0.1)\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_sphere([0.5, 0.5, 0.5], 0.1)\n        assert_fname(p.save(prefix)[0])\n        p = OffAxisSlicePlot(ds, vector, (\"gas\", \"density\"))\n        p.annotate_sphere([0.5, 0.5, 0.5], 0.1)\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_sphere([0.5, 0.5], 0.1, coord_system=\"axis\", text=\"blah\")\n        p.save(prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        assert_raises(\n            YTDataTypeUnsupported,\n            p.annotate_sphere,\n            [0.5, 0.5, 0.5],\n            0.1,\n        )\n        p.annotate_sphere([0.5, 0.5], 0.1, coord_system=\"axis\", text=\"blah\")\n        assert_fname(p.save(prefix)[0])\n\n\ndef test_invalidated_annotations():\n    # check that annotate_sphere and annotate_arrow succeed on re-running after\n    # an operation that invalidates the plot (set_font_size), see\n    # https://github.com/yt-project/yt/issues/4698\n\n    ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n    p = SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    p.annotate_sphere([0.5, 0.5, 0.5], 0.1)\n    p.set_font_size(24)\n    p.render()\n\n    p = SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    p.annotate_arrow([0.5, 0.5, 0.5])\n    p.set_font_size(24)\n    p.render()\n\n\ndef test_text_callback():\n    with _cleanup_fname() as prefix:\n        ax = \"z\"\n        vector = [1.0, 1.0, 1.0]\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_text([0.5, 0.5, 0.5], \"dinosaurs!\")\n        assert_fname(p.save(prefix)[0])\n        p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n        p.annotate_text([0.5, 0.5, 0.5], \"dinosaurs!\")\n        assert_fname(p.save(prefix)[0])\n        p = OffAxisSlicePlot(ds, vector, (\"gas\", \"density\"))\n        p.annotate_text([0.5, 0.5, 0.5], \"dinosaurs!\")\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_text(\n            [0.5, 0.5], \"dinosaurs!\", coord_system=\"axis\", text_args={\"color\": \"red\"}\n        )\n        p.save(prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        assert_raises(\n            YTDataTypeUnsupported, p.annotate_text, [0.5, 0.5, 0.5], \"dinosaurs!\"\n        )\n        p.annotate_text(\n            [0.5, 0.5], \"dinosaurs!\", coord_system=\"axis\", text_args={\"color\": \"red\"}\n        )\n        assert_fname(p.save(prefix)[0])\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\n@requires_file(cyl_3d)\ndef test_velocity_callback():\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(\n            fields=(\"density\", \"velocity_x\", \"velocity_y\", \"velocity_z\"),\n            units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n        )\n        for ax in \"xyz\":\n            p = ProjectionPlot(\n                ds, ax, (\"gas\", \"density\"), weight_field=(\"gas\", \"density\")\n            )\n            p.annotate_velocity()\n            assert_fname(p.save(prefix)[0])\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_velocity()\n            assert_fname(p.save(prefix)[0])\n        # Test for OffAxis Slice\n        p = SlicePlot(ds, [1, 1, 0], (\"gas\", \"density\"), north_vector=[0, 0, 1])\n        p.annotate_velocity(factor=40, normalize=True)\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_velocity(factor=8, scale=0.5, scale_units=\"inches\", normalize=True)\n        assert_fname(p.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        ds = fake_hexahedral_ds(fields=[f\"velocity_{ax}\" for ax in \"xyz\"])\n        sl = SlicePlot(ds, 1, (\"connect1\", \"test\"))\n        sl.annotate_velocity()\n        assert_fname(sl.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_2d)\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_velocity()\n        assert_fname(slc.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_3d)\n        for ax in [\"r\", \"z\", \"theta\"]:\n            slc = SlicePlot(ds, ax, (\"gas\", \"velocity_magnitude\"))\n            slc.annotate_velocity()\n            assert_fname(slc.save(prefix)[0])\n            slc = ProjectionPlot(ds, ax, (\"gas\", \"velocity_magnitude\"))\n            slc.annotate_velocity()\n            assert_fname(slc.save(prefix)[0])\n\n\ndef test_velocity_callback_spherical():\n    ds = fake_amr_ds(\n        fields=(\"density\", \"velocity_r\", \"velocity_theta\", \"velocity_phi\"),\n        units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n        geometry=\"spherical\",\n    )\n\n    with _cleanup_fname() as prefix:\n        p = ProjectionPlot(ds, \"phi\", (\"stream\", \"density\"))\n        p.annotate_velocity(factor=40, normalize=True)\n        assert_fname(p.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        p = ProjectionPlot(ds, \"r\", (\"stream\", \"density\"))\n        p.annotate_velocity(factor=40, normalize=True)\n        assert_raises(NotImplementedError, p.save, prefix)\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\n@requires_file(cyl_3d)\ndef test_magnetic_callback():\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(\n            fields=(\n                \"density\",\n                \"magnetic_field_x\",\n                \"magnetic_field_y\",\n                \"magnetic_field_z\",\n            ),\n            units=(\n                \"g/cm**3\",\n                \"G\",\n                \"G\",\n                \"G\",\n            ),\n        )\n        for ax in \"xyz\":\n            p = ProjectionPlot(\n                ds, ax, (\"gas\", \"density\"), weight_field=(\"gas\", \"density\")\n            )\n            p.annotate_magnetic_field()\n            assert_fname(p.save(prefix)[0])\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_magnetic_field()\n            assert_fname(p.save(prefix)[0])\n        # Test for OffAxis Slice\n        p = SlicePlot(ds, [1, 1, 0], (\"gas\", \"density\"), north_vector=[0, 0, 1])\n        p.annotate_magnetic_field(factor=40, normalize=True)\n        assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_magnetic_field(\n            factor=8, scale=0.5, scale_units=\"inches\", normalize=True\n        )\n        assert_fname(p.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_2d)\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"magnetic_field_strength\"))\n        slc.annotate_magnetic_field()\n        assert_fname(slc.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_3d)\n        for ax in [\"r\", \"z\", \"theta\"]:\n            slc = SlicePlot(ds, ax, (\"gas\", \"magnetic_field_strength\"))\n            slc.annotate_magnetic_field()\n            assert_fname(slc.save(prefix)[0])\n            slc = ProjectionPlot(ds, ax, (\"gas\", \"magnetic_field_strength\"))\n            slc.annotate_magnetic_field()\n            assert_fname(slc.save(prefix)[0])\n        check_axis_manipulation(slc, prefix)  # only test the last axis\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(\n            fields=(\n                \"density\",\n                \"magnetic_field_r\",\n                \"magnetic_field_theta\",\n                \"magnetic_field_phi\",\n            ),\n            units=(\n                \"g/cm**3\",\n                \"G\",\n                \"G\",\n                \"G\",\n            ),\n            geometry=\"spherical\",\n        )\n        p = ProjectionPlot(ds, \"phi\", (\"gas\", \"density\"))\n        p.annotate_magnetic_field(\n            factor=8, scale=0.5, scale_units=\"inches\", normalize=True\n        )\n        assert_fname(p.save(prefix)[0])\n\n        p = ProjectionPlot(ds, \"theta\", (\"gas\", \"density\"))\n        p.annotate_magnetic_field(\n            factor=8, scale=0.5, scale_units=\"inches\", normalize=True\n        )\n        assert_fname(p.save(prefix)[0])\n\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        p.annotate_magnetic_field(\n            factor=8, scale=0.5, scale_units=\"inches\", normalize=True\n        )\n        assert_raises(NotImplementedError, p.save, prefix)\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\n@requires_file(cyl_3d)\ndef test_quiver_callback():\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(\n            fields=(\"density\", \"velocity_x\", \"velocity_y\", \"velocity_z\"),\n            units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n        )\n        for ax in \"xyz\":\n            p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_quiver((\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"))\n            assert_fname(p.save(prefix)[0])\n            p = ProjectionPlot(\n                ds, ax, (\"gas\", \"density\"), weight_field=(\"gas\", \"density\")\n            )\n            p.annotate_quiver((\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"))\n            assert_fname(p.save(prefix)[0])\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_quiver((\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"))\n            assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_quiver(\n            (\"gas\", \"velocity_x\"),\n            (\"gas\", \"velocity_y\"),\n            factor=8,\n            scale=0.5,\n            scale_units=\"inches\",\n            normalize=True,\n            bv_x=0.5 * u.cm / u.s,\n            bv_y=0.5 * u.cm / u.s,\n        )\n        assert_fname(p.save(prefix)[0])\n        check_axis_manipulation(p, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_2d)\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"density\"))\n        slc.annotate_quiver((\"gas\", \"velocity_r\"), (\"gas\", \"velocity_z\"))\n        assert_fname(slc.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_3d)\n        slc = SlicePlot(ds, \"r\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_quiver((\"gas\", \"velocity_theta\"), (\"gas\", \"velocity_z\"))\n        assert_fname(slc.save(prefix)[0])\n        slc = SlicePlot(ds, \"z\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_quiver(\n            (\"gas\", \"velocity_cartesian_x\"), (\"gas\", \"velocity_cartesian_y\")\n        )\n        assert_fname(slc.save(prefix)[0])\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_quiver((\"gas\", \"velocity_r\"), (\"gas\", \"velocity_z\"))\n        assert_fname(slc.save(prefix)[0])\n\n\ndef test_quiver_callback_spherical():\n    ds = fake_amr_ds(\n        fields=(\"density\", \"velocity_r\", \"velocity_theta\", \"velocity_phi\"),\n        units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n        geometry=\"spherical\",\n    )\n\n    with _cleanup_fname() as prefix:\n        p = ProjectionPlot(ds, \"phi\", (\"gas\", \"density\"))\n        p.annotate_quiver(\n            (\"gas\", \"velocity_cylindrical_radius\"),\n            (\"gas\", \"velocity_cylindrical_z\"),\n            factor=8,\n            scale=0.5,\n            scale_units=\"inches\",\n            normalize=True,\n        )\n        assert_fname(p.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        p = ProjectionPlot(ds, \"r\", (\"gas\", \"density\"))\n        p.annotate_quiver(\n            (\"gas\", \"velocity_theta\"),\n            (\"gas\", \"velocity_phi\"),\n            factor=8,\n            scale=0.5,\n            scale_units=\"inches\",\n            normalize=True,\n        )\n        assert_fname(p.save(prefix)[0])\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\ndef test_contour_callback():\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\", \"temperature\"), units=(\"g/cm**3\", \"K\"))\n        for ax in \"xyz\":\n            p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_contour((\"gas\", \"temperature\"))\n            assert_fname(p.save(prefix)[0])\n            p = ProjectionPlot(\n                ds, ax, (\"gas\", \"density\"), weight_field=(\"gas\", \"density\")\n            )\n            p.annotate_contour((\"gas\", \"temperature\"))\n            assert_fname(p.save(prefix)[0])\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_contour((\"gas\", \"temperature\"))  # BREAKS WITH ndarray\n            assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_contour(\n            (\"gas\", \"temperature\"),\n            levels=10,\n            factor=8,\n            take_log=False,\n            clim=(0.4, 0.6),\n            plot_args={\"linewidths\": 2.0},\n            label=True,\n            text_args={\"fontsize\": \"x-large\"},\n        )\n        p.save(prefix)\n\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        s2 = ds.slice(0, 0.2)\n        p.annotate_contour(\n            (\"gas\", \"temperature\"),\n            levels=10,\n            factor=8,\n            take_log=False,\n            clim=(0.4, 0.6),\n            plot_args={\"linewidths\": 2.0},\n            label=True,\n            text_args={\"fontsize\": \"x-large\"},\n            data_source=s2,\n        )\n        p.save(prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_2d)\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"plasma_beta\"))\n        slc.annotate_contour(\n            (\"gas\", \"plasma_beta\"),\n            levels=2,\n            factor=7,\n            take_log=False,\n            clim=(1.0e-1, 1.0e1),\n            label=True,\n            plot_args={\"colors\": (\"c\", \"w\"), \"linewidths\": 1},\n            text_args={\"fmt\": \"%1.1f\"},\n        )\n        assert_fname(slc.save(prefix)[0])\n        check_axis_manipulation(slc, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(\n            fields=(\"density\", \"temperature\"),\n            units=(\"g/cm**3\", \"K\"),\n            geometry=\"spherical\",\n        )\n        p = SlicePlot(ds, \"r\", (\"gas\", \"density\"))\n        kwargs = {\n            \"levels\": 10,\n            \"factor\": 8,\n            \"take_log\": False,\n            \"clim\": (0.4, 0.6),\n            \"plot_args\": {\"linewidths\": 2.0},\n            \"label\": True,\n            \"text_args\": {\"fontsize\": \"x-large\"},\n        }\n        assert_raises(\n            YTDataTypeUnsupported, p.annotate_contour, (\"gas\", \"temperature\"), **kwargs\n        )\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\ndef test_grids_callback():\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        for ax in \"xyz\":\n            p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_grids()\n            assert_fname(p.save(prefix)[0])\n            p = ProjectionPlot(\n                ds, ax, (\"gas\", \"density\"), weight_field=(\"gas\", \"density\")\n            )\n            p.annotate_grids()\n            assert_fname(p.save(prefix)[0])\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_grids()\n            assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_grids(\n            alpha=0.7,\n            min_pix=10,\n            min_pix_ids=30,\n            draw_ids=True,\n            id_loc=\"upper right\",\n            periodic=False,\n            min_level=2,\n            max_level=3,\n            cmap=\"gist_stern\",\n        )\n        p.save(prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_2d)\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"density\"))\n        slc.annotate_grids()\n        assert_fname(slc.save(prefix)[0])\n        check_axis_manipulation(slc, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = SlicePlot(ds, \"r\", (\"gas\", \"density\"))\n        kwargs = {\n            \"alpha\": 0.7,\n            \"min_pix\": 10,\n            \"min_pix_ids\": 30,\n            \"draw_ids\": True,\n            \"id_loc\": \"upper right\",\n            \"periodic\": False,\n            \"min_level\": 2,\n            \"max_level\": 3,\n            \"cmap\": \"gist_stern\",\n        }\n        assert_raises(YTDataTypeUnsupported, p.annotate_grids, **kwargs)\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\ndef test_cell_edges_callback():\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n        for ax in \"xyz\":\n            p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_cell_edges()\n            assert_fname(p.save(prefix)[0])\n            p = ProjectionPlot(\n                ds, ax, (\"gas\", \"density\"), weight_field=(\"gas\", \"density\")\n            )\n            p.annotate_cell_edges()\n            assert_fname(p.save(prefix)[0])\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_cell_edges()\n            assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_cell_edges(alpha=0.7, line_width=0.9, color=(0.0, 1.0, 1.0))\n        p.save(prefix)\n        check_axis_manipulation(p, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_2d)\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"density\"))\n        slc.annotate_cell_edges()\n        assert_fname(slc.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",), geometry=\"spherical\")\n        p = SlicePlot(ds, \"r\", (\"gas\", \"density\"))\n        assert_raises(YTDataTypeUnsupported, p.annotate_cell_edges)\n\n\ndef test_mesh_lines_callback():\n    with _cleanup_fname() as prefix:\n        ds = fake_hexahedral_ds()\n        for field in ds.field_list:\n            sl = SlicePlot(ds, 1, field)\n            sl.annotate_mesh_lines(color=\"black\")\n            assert_fname(sl.save(prefix)[0])\n\n        ds = fake_tetrahedral_ds()\n        for field in ds.field_list:\n            sl = SlicePlot(ds, 1, field)\n            sl.annotate_mesh_lines(color=\"black\")\n            assert_fname(sl.save(prefix)[0])\n        check_axis_manipulation(sl, prefix)  # only test the final field\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\n@requires_file(cyl_3d)\ndef test_streamline_callback():\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(\n            fields=(\"density\", \"velocity_x\", \"velocity_y\", \"magvel\"),\n            units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n        )\n\n        for ax in \"xyz\":\n            # Projection plot tests\n            p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_streamlines((\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"))\n            assert_fname(p.save(prefix)[0])\n\n            p = ProjectionPlot(\n                ds, ax, (\"gas\", \"density\"), weight_field=(\"gas\", \"density\")\n            )\n            p.annotate_streamlines((\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"))\n            assert_fname(p.save(prefix)[0])\n\n            # Slice plot test\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_streamlines((\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"))\n            assert_fname(p.save(prefix)[0])\n\n            # Additional features\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_streamlines(\n                (\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\"), factor=32, density=4\n            )\n            assert_fname(p.save(prefix)[0])\n\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_streamlines(\n                (\"gas\", \"velocity_x\"),\n                (\"gas\", \"velocity_y\"),\n                color=(\"stream\", \"magvel\"),\n            )\n            assert_fname(p.save(prefix)[0])\n            check_axis_manipulation(p, prefix)\n\n            # a more thorough example involving many keyword arguments\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_streamlines(\n                (\"gas\", \"velocity_x\"),\n                (\"gas\", \"velocity_y\"),\n                linewidth=(\"gas\", \"density\"),\n                linewidth_upscaling=3,\n                color=(\"stream\", \"magvel\"),\n                color_threshold=0.5,\n                cmap=\"viridis\",\n                arrowstyle=\"->\",\n            )\n            assert_fname(p.save(prefix)[0])\n\n    # Axisymmetric dataset\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_2d)\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_streamlines((\"gas\", \"velocity_r\"), (\"gas\", \"velocity_z\"))\n        assert_fname(slc.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_3d)\n        slc = SlicePlot(ds, \"r\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_streamlines((\"gas\", \"velocity_theta\"), (\"gas\", \"velocity_z\"))\n        assert_fname(slc.save(prefix)[0])\n        slc = SlicePlot(ds, \"z\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_streamlines(\n            (\"gas\", \"velocity_cartesian_x\"), (\"gas\", \"velocity_cartesian_y\")\n        )\n        assert_fname(slc.save(prefix)[0])\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_streamlines((\"gas\", \"velocity_r\"), (\"gas\", \"velocity_z\"))\n        assert_fname(slc.save(prefix)[0])\n\n    # Spherical dataset\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(\n            fields=(\"density\", \"velocity_r\", \"velocity_theta\", \"velocity_phi\"),\n            units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n            geometry=\"spherical\",\n        )\n        slc = SlicePlot(ds, \"phi\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_streamlines(\n            (\"gas\", \"velocity_cylindrical_radius\"), (\"gas\", \"velocity_cylindrical_z\")\n        )\n        assert_fname(slc.save(prefix)[0])\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_streamlines(\n            (\"gas\", \"velocity_conic_x\"), (\"gas\", \"velocity_conic_y\")\n        )\n        assert_fname(slc.save(prefix)[0])\n\n\n@requires_module(\"h5py\")\n@requires_file(cyl_2d)\n@requires_file(cyl_3d)\ndef test_line_integral_convolution_callback():\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(\n            fields=(\"density\", \"velocity_x\", \"velocity_y\", \"velocity_z\"),\n            units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n        )\n        for ax in \"xyz\":\n            p = ProjectionPlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_line_integral_convolution(\n                (\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\")\n            )\n            assert_fname(p.save(prefix)[0])\n            p = ProjectionPlot(\n                ds, ax, (\"gas\", \"density\"), weight_field=(\"gas\", \"density\")\n            )\n            p.annotate_line_integral_convolution(\n                (\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\")\n            )\n            assert_fname(p.save(prefix)[0])\n            p = SlicePlot(ds, ax, (\"gas\", \"density\"))\n            p.annotate_line_integral_convolution(\n                (\"gas\", \"velocity_x\"), (\"gas\", \"velocity_y\")\n            )\n            assert_fname(p.save(prefix)[0])\n        # Now we'll check a few additional minor things\n        p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n        p.annotate_line_integral_convolution(\n            (\"gas\", \"velocity_x\"),\n            (\"gas\", \"velocity_y\"),\n            kernellen=100.0,\n            lim=(0.4, 0.7),\n            cmap=ytcfg.get(\"yt\", \"default_colormap\"),\n            alpha=0.9,\n            const_alpha=True,\n        )\n        p.save(prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_2d)\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"magnetic_field_strength\"))\n        slc.annotate_line_integral_convolution(\n            (\"gas\", \"magnetic_field_r\"), (\"gas\", \"magnetic_field_z\")\n        )\n        assert_fname(slc.save(prefix)[0])\n\n    with _cleanup_fname() as prefix:\n        ds = load(cyl_3d)\n        slc = SlicePlot(ds, \"r\", (\"gas\", \"magnetic_field_strength\"))\n        slc.annotate_line_integral_convolution(\n            (\"gas\", \"magnetic_field_theta\"), (\"gas\", \"magnetic_field_z\")\n        )\n        assert_fname(slc.save(prefix)[0])\n        slc = SlicePlot(ds, \"z\", (\"gas\", \"magnetic_field_strength\"))\n        slc.annotate_line_integral_convolution(\n            (\"gas\", \"magnetic_field_cartesian_x\"), (\"gas\", \"magnetic_field_cartesian_y\")\n        )\n        assert_fname(slc.save(prefix)[0])\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"magnetic_field_strength\"))\n        slc.annotate_line_integral_convolution(\n            (\"gas\", \"magnetic_field_r\"), (\"gas\", \"magnetic_field_z\")\n        )\n        assert_fname(slc.save(prefix)[0])\n        check_axis_manipulation(slc, prefix)\n\n    with _cleanup_fname() as prefix:\n        ds = fake_amr_ds(\n            fields=(\"density\", \"velocity_r\", \"velocity_theta\", \"velocity_phi\"),\n            units=(\"g/cm**3\", \"cm/s\", \"cm/s\", \"cm/s\"),\n            geometry=\"spherical\",\n        )\n        slc = SlicePlot(ds, \"phi\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_line_integral_convolution(\n            (\"gas\", \"velocity_cylindrical_radius\"), (\"gas\", \"velocity_cylindrical_z\")\n        )\n        assert_fname(slc.save(prefix)[0])\n        slc = SlicePlot(ds, \"theta\", (\"gas\", \"velocity_magnitude\"))\n        slc.annotate_line_integral_convolution(\n            (\"gas\", \"velocity_conic_x\"), (\"gas\", \"velocity_conic_y\")\n        )\n        assert_fname(slc.save(prefix)[0])\n        check_axis_manipulation(slc, prefix)\n\n\ndef test_accepts_all_fields_decorator():\n    fields = [\n        (\"gas\", \"density\"),\n        (\"gas\", \"velocity_x\"),\n        (\"gas\", \"pressure\"),\n        (\"gas\", \"temperature\"),\n    ]\n    units = [\"g/cm**3\", \"cm/s\", \"dyn/cm**2\", \"K\"]\n    ds = fake_random_ds(16, fields=fields, units=units)\n    plot = SlicePlot(ds, \"z\", fields=fields)\n\n    # mocking a class method\n    plot.fake_attr = dict.fromkeys(fields, \"not set\")\n\n    @accepts_all_fields\n    def set_fake_field_attribute(self, field, value):\n        self.fake_attr[field] = value\n        return self\n\n    # test on a single field\n    plot = set_fake_field_attribute(plot, field=fields[0], value=1)\n    assert_array_equal([plot.fake_attr[f] for f in fields], [1] + [\"not set\"] * 3)\n\n    # test using \"all\" as a field\n    plot = set_fake_field_attribute(plot, field=\"all\", value=2)\n    assert_array_equal(list(plot.fake_attr.values()), [2] * 4)\n\n\nM7 = \"DD0010/moving7_0010\"\n\n\n@requires_ds(M7)\ndef test_axis_manipulations():\n    # tests flip_horizontal, flip_vertical and swap_axes in different combinations\n    # on a SlicePlot with a velocity callback.\n    plot_field = (\"gas\", \"density\")\n    decimals = 12\n    ds = data_dir_load(M7)\n\n    def simple_velocity(test_obj, plot):\n        # test_obj: the active PlotWindowAttributeTest\n        # plot: the actual PlotWindow\n        plot.annotate_velocity()\n\n    def swap_axes(test_obj, plot):\n        plot.swap_axes()\n\n    def flip_horizontal(test_obj, plot):\n        plot.flip_horizontal()\n\n    def flip_vertical(test_obj, plot):\n        plot.flip_vertical()\n\n    callback_tests = (\n        (\"flip_horizontal\", (simple_velocity, flip_horizontal)),\n        (\"flip_vertical\", (simple_velocity, flip_vertical)),\n        (\"swap_axes\", (simple_velocity, swap_axes)),\n        (\"flip_and_swap\", (simple_velocity, flip_vertical, flip_horizontal, swap_axes)),\n    )\n\n    for n, r in callback_tests:\n        test = PlotWindowAttributeTest(\n            ds,\n            plot_field,\n            \"x\",\n            attr_name=None,\n            attr_args=None,\n            decimals=decimals,\n            callback_id=n,\n            callback_runners=r,\n        )\n        test_axis_manipulations.__name__ = test.description\n        yield test\n"
  },
  {
    "path": "yt/visualization/tests/test_callbacks_geographic.py",
    "content": "import numpy as np\nimport pytest\n\nfrom yt import SlicePlot, load_uniform_grid\nfrom yt.testing import fake_amr_ds, requires_module\n\n\n@requires_module(\"cartopy\")\n@pytest.mark.parametrize(\"geometry\", [\"geographic\", \"internal_geographic\"])\ndef test_quiver_callback_geographic(geometry):\n    flds = (\"density\", \"velocity_ew\", \"velocity_ns\")\n    units = (\"g/cm**3\", \"m/s\", \"m/s\")\n\n    ds = fake_amr_ds(fields=flds, units=units, geometry=geometry)\n\n    for ax in ds.coordinates.axis_order:\n        slc = SlicePlot(ds, ax, \"density\", buff_size=(50, 50))\n        if ax == ds.coordinates.radial_axis:\n            # avoid the exact transform bounds\n            slc.set_width((359.99, 179.99))\n        slc.annotate_quiver((\"stream\", \"velocity_ew\"), (\"stream\", \"velocity_ns\"))\n        slc.render()\n\n\n@pytest.fixture()\ndef ds_geo_uni_grid():\n    yc = 0.0\n    xc = 0.0\n\n    def _vel_calculator(grid, ax):\n        y_lat = grid.fcoords[:, 1].d\n        x_lon = grid.fcoords[:, 2].d\n        x_lon[x_lon > 180] = x_lon[x_lon > 180] - 360.0\n        dist = np.sqrt((y_lat - yc) ** 2 + (xc - x_lon) ** 2)\n        if ax == 1:\n            sgn = np.sign(y_lat - yc)\n        elif ax == 2:\n            sgn = np.sign(x_lon - xc)\n\n        vel = np.exp(-((dist / 45) ** 2)) * sgn\n        return vel.reshape(grid.shape)\n\n    def _calculate_u(grid, field):\n        return _vel_calculator(grid, 2)\n\n    def _calculate_v(grid, field):\n        return _vel_calculator(grid, 1)\n\n    data = {\"u_vel\": _calculate_u, \"v_vel\": _calculate_v}\n\n    bbox = [[10.0, 1000], [-90, 90], [0, 360]]\n    bbox = np.array(bbox)\n\n    ds = load_uniform_grid(\n        data,\n        (16, 16, 32),\n        bbox=bbox,\n        geometry=\"geographic\",\n        axis_order=(\"altitude\", \"latitude\", \"longitude\"),\n    )\n\n    return ds\n\n\n@requires_module(\"cartopy\")\n@pytest.mark.mpl_image_compare\ndef test_geoquiver_answer(ds_geo_uni_grid):\n    slc = SlicePlot(ds_geo_uni_grid, \"altitude\", \"u_vel\")\n    slc.set_width((359.99, 179.99))\n    slc.set_log(\"u_vel\", False)\n    slc.annotate_quiver(\"u_vel\", \"v_vel\", scale=50)\n    slc.render()\n    return slc.plots[\"u_vel\"].figure\n"
  },
  {
    "path": "yt/visualization/tests/test_color_maps.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom nose.tools import assert_raises\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nfrom yt import make_colormap, show_colormaps\nfrom yt.testing import requires_backend\n\n\nclass TestColorMaps(unittest.TestCase):\n    def setUp(self):\n        self.tmpdir = tempfile.mkdtemp()\n        self.curdir = os.getcwd()\n        os.chdir(self.tmpdir)\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        shutil.rmtree(self.tmpdir)\n\n    @requires_backend(\"Agg\")\n    def test_show_colormaps(self):\n        show_colormaps()\n        show_colormaps(subset=[\"jet\", \"cool\"])\n        show_colormaps(subset=\"yt_native\", filename=\"yt_color_maps.png\")\n\n        # Test for non-existent color map\n        with assert_raises(AttributeError) as ex:\n            show_colormaps(subset=\"unknown\", filename=\"yt_color_maps.png\")\n        desired = (\n            \"show_colormaps requires subset attribute to be 'all', \"\n            \"'yt_native', or a list of valid colormap names.\"\n        )\n        assert_equal(str(ex.exception), desired)\n\n    @requires_backend(\"Agg\")\n    def test_make_colormap(self):\n        make_colormap(\n            [([0, 0, 1], 10), ([1, 1, 1], 10), ([1, 0, 0], 10)],\n            name=\"french_flag\",\n            interpolate=False,\n        )\n        show_colormaps(\"french_flag\")\n\n        cmap = make_colormap(\n            [(\"dred\", 5), (\"blue\", 2.0), (\"orange\", 0)], name=\"my_cmap\"\n        )\n        assert_almost_equal(\n            cmap[\"red\"][1], np.array([0.00392157, 0.62400345, 0.62400345])\n        )\n\n        assert_almost_equal(\n            cmap[\"blue\"][2], np.array([0.00784314, 0.01098901, 0.01098901])\n        )\n\n        assert_almost_equal(cmap[\"green\"][3], np.array([0.01176471, 0.0, 0.0]))\n\n\ndef test_cmyt_integration():\n    for name in [\"algae\", \"bds_highcontrast\", \"kelp\", \"arbre\", \"octarine\", \"kamae\"]:\n        cmap = plt.get_cmap(name)\n        assert cmap.name == name\n        name_r = name + \"_r\"\n        cmap_r = plt.get_cmap(name_r)\n        assert cmap_r.name == name_r\n\n    for name in [\"algae\", \"kelp\", \"arbre\", \"octarine\", \"pastel\"]:\n        cmap = plt.get_cmap(\"cmyt.\" + name)\n        assert cmap.name == \"cmyt.\" + name\n"
  },
  {
    "path": "yt/visualization/tests/test_commons.py",
    "content": "import pytest\nfrom numpy.testing import assert_raises\n\nfrom yt.visualization._commons import (\n    _swap_arg_pair_order,\n    _swap_axes_extents,\n    validate_image_name,\n)\n\n\n@pytest.mark.parametrize(\n    \"name, expected\",\n    [\n        (\"noext\", \"noext.png\"),\n        (\"nothing.png\", \"nothing.png\"),\n        (\"nothing.pdf\", \"nothing.pdf\"),\n        (\"version.1.2.3\", \"version.1.2.3.png\"),\n    ],\n)\ndef test_default(name, expected):\n    result = validate_image_name(name)\n    assert result == expected\n\n\n@pytest.mark.parametrize(\n    \"name, suffix, expected\",\n    [\n        (\"noext\", \".png\", \"noext.png\"),\n        (\"noext\", None, \"noext.png\"),\n        (\"nothing.png\", \".png\", \"nothing.png\"),\n        (\"nothing.png\", None, \"nothing.png\"),\n        (\"nothing.png\", \".pdf\", \"nothing.pdf\"),\n        (\"nothing.pdf\", \".pdf\", \"nothing.pdf\"),\n        (\"nothing.pdf\", None, \"nothing.pdf\"),\n        (\"nothing.pdf\", \".png\", \"nothing.png\"),\n        (\"version.1.2.3\", \".png\", \"version.1.2.3.png\"),\n        (\"version.1.2.3\", None, \"version.1.2.3.png\"),\n        (\"version.1.2.3\", \".pdf\", \"version.1.2.3.pdf\"),\n    ],\n)\n@pytest.mark.filterwarnings(\n    r\"ignore:Received two valid image formats '\\w+' \\(from filename\\) \"\n    r\"and '\\w+' \\(from suffix\\). The former is ignored.:UserWarning\"\n)\ndef test_custom_valid_ext(name, suffix, expected):\n    result1 = validate_image_name(name, suffix=suffix)\n    assert result1 == expected\n    if suffix is not None:\n        alt_suffix = suffix.replace(\".\", \"\")\n        result2 = validate_image_name(name, suffix=alt_suffix)\n        assert result2 == expected\n\n\ndef test_extent_swap():\n    input_extent = [1, 2, 3, 4]\n    expected = [3, 4, 1, 2]\n    assert _swap_axes_extents(input_extent) == expected\n    assert _swap_axes_extents(tuple(input_extent)) == tuple(expected)\n\n\ndef test_swap_arg_pair_order():\n    assert _swap_arg_pair_order(1, 2) == (2, 1)\n    assert _swap_arg_pair_order(1, 2, 3, 4, 5, 6) == (2, 1, 4, 3, 6, 5)\n    assert_raises(TypeError, _swap_arg_pair_order, 1)\n    assert_raises(TypeError, _swap_arg_pair_order, 1, 2, 3)\n"
  },
  {
    "path": "yt/visualization/tests/test_eps_writer.py",
    "content": "import yt\nfrom yt.testing import fake_amr_ds, requires_external_executable, requires_module\n\n\n@requires_external_executable(\"tex\")\n@requires_module(\"pyx\")\ndef test_eps_writer(tmp_path):\n    import yt.visualization.eps_writer as eps\n\n    fields = [\n        (\"gas\", \"density\"),\n        (\"gas\", \"temperature\"),\n    ]\n    units = [\n        \"g/cm**3\",\n        \"K\",\n    ]\n    ds = fake_amr_ds(fields=fields, units=units)\n    slc = yt.SlicePlot(\n        ds,\n        \"z\",\n        fields=fields,\n    )\n    eps_fig = eps.multiplot_yt(2, 1, slc, bare_axes=True)\n    eps_fig.scale_line(0.2, \"5 cm\")\n    savefile = tmp_path / \"multi\"\n    eps_fig.save_fig(savefile, format=\"eps\")\n    assert savefile.with_suffix(\".eps\").exists()\n"
  },
  {
    "path": "yt/visualization/tests/test_export_frb.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import assert_allclose_units, fake_random_ds\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_export_frb():\n    test_ds = fake_random_ds(128)\n    slc = test_ds.slice(0, 0.5)\n    frb = slc.to_frb((0.5, \"unitary\"), 64)\n    frb_ds = frb.export_dataset(fields=[(\"gas\", \"density\")], nprocs=8)\n    dd_frb = frb_ds.all_data()\n\n    assert_equal(frb_ds.domain_left_edge.v, np.array([0.25, 0.25, 0.0]))\n    assert_equal(frb_ds.domain_right_edge.v, np.array([0.75, 0.75, 1.0]))\n    assert_equal(frb_ds.domain_width.v, np.array([0.5, 0.5, 1.0]))\n    assert_equal(frb_ds.domain_dimensions, np.array([64, 64, 1], dtype=\"int64\"))\n    assert_allclose_units(\n        frb[\"gas\", \"density\"].sum(),\n        dd_frb.quantities.total_quantity((\"gas\", \"density\")),\n    )\n    assert_equal(frb_ds.index.num_grids, 8)\n"
  },
  {
    "path": "yt/visualization/tests/test_filters.py",
    "content": "\"\"\"\nTests for frb filters\n\n\"\"\"\n\nimport numpy as np\n\nimport yt\nfrom yt.testing import fake_amr_ds, requires_module\n\n\n@requires_module(\"scipy\")\ndef test_white_noise_filter():\n    ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n    p = ds.proj((\"gas\", \"density\"), \"z\")\n    frb = p.to_frb((1, \"unitary\"), 64)\n    frb.apply_white_noise()\n    frb.apply_white_noise(1e-3)\n    frb.render((\"gas\", \"density\"))\n\n\n@requires_module(\"scipy\")\ndef test_gauss_beam_filter():\n    ds = fake_amr_ds(fields=(\"density\",), units=(\"g/cm**3\",))\n    p = ds.proj((\"gas\", \"density\"), \"z\")\n    frb = p.to_frb((1, \"unitary\"), 64)\n    frb.apply_gauss_beam(sigma=1.0)\n    frb.render((\"gas\", \"density\"))\n\n\n@requires_module(\"scipy\")\ndef test_filter_wiring():\n    ds = fake_amr_ds(fields=[(\"gas\", \"density\")], units=[\"g/cm**3\"])\n    p = yt.SlicePlot(ds, \"x\", \"density\")\n\n    # Note: frb is a FixedResolutionBuffer object\n    frb1 = p.frb\n    data_orig = frb1[\"density\"].value\n\n    sigma = 2\n    nbeam = 30\n    p.frb.apply_gauss_beam(nbeam=nbeam, sigma=sigma)\n    frb2 = p.frb\n    data_gauss = frb2[\"density\"].value\n\n    p.frb.apply_white_noise()\n    frb3 = p.frb\n    data_white = frb3[\"density\"].value\n\n    # We check the frb objects are different\n    assert frb1 is not frb2\n    assert frb1 is not frb3\n    assert frb2 is not frb3\n\n    # We check the resulting image are different each time\n    assert not np.allclose(data_orig, data_gauss)\n    assert not np.allclose(data_orig, data_white)\n    assert not np.allclose(data_gauss, data_white)\n"
  },
  {
    "path": "yt/visualization/tests/test_fits_image.py",
    "content": "import os\nimport shutil\nimport tempfile\n\nimport numpy as np\nfrom numpy.testing import assert_allclose, assert_equal\n\nfrom yt.loaders import load\nfrom yt.testing import fake_random_ds, requires_file, requires_module\nfrom yt.utilities.on_demand_imports import _astropy\nfrom yt.visualization.fits_image import (\n    FITSImageData,\n    FITSOffAxisProjection,\n    FITSOffAxisSlice,\n    FITSParticleOffAxisProjection,\n    FITSParticleProjection,\n    FITSProjection,\n    FITSSlice,\n    assert_same_wcs,\n)\nfrom yt.visualization.volume_rendering.off_axis_projection import off_axis_projection\n\n\n@requires_module(\"astropy\")\ndef test_fits_image():\n    curdir = os.getcwd()\n    tmpdir = tempfile.mkdtemp()\n    os.chdir(tmpdir)\n\n    fields = (\"density\", \"temperature\", \"velocity_x\", \"velocity_y\", \"velocity_z\")\n    units = (\"g/cm**3\", \"K\", \"cm/s\", \"cm/s\", \"cm/s\")\n    ds = fake_random_ds(\n        64, fields=fields, units=units, nprocs=16, length_unit=100.0, particles=10000\n    )\n\n    prj = ds.proj((\"gas\", \"density\"), 2)\n    prj_frb = prj.to_frb((0.5, \"unitary\"), 128)\n\n    fid1 = prj_frb.to_fits_data(\n        fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")], length_unit=\"cm\"\n    )\n    fits_prj = FITSProjection(\n        ds,\n        \"z\",\n        [ds.fields.gas.density, (\"gas\", \"temperature\")],\n        image_res=128,\n        width=(0.5, \"unitary\"),\n    )\n\n    assert_equal(fid1[\"density\"].data, fits_prj[\"density\"].data)\n    assert_equal(fid1[\"temperature\"].data, fits_prj[\"temperature\"].data)\n\n    fid1.writeto(\"fid1.fits\", overwrite=True)\n    new_fid1 = FITSImageData.from_file(\"fid1.fits\")\n\n    assert_equal(fid1[\"density\"].data, new_fid1[\"density\"].data)\n    assert_equal(fid1[\"temperature\"].data, new_fid1[\"temperature\"].data)\n    assert_equal(fid1.length_unit, new_fid1.length_unit)\n    assert_equal(fid1.time_unit, new_fid1.time_unit)\n    assert_equal(fid1.mass_unit, new_fid1.mass_unit)\n    assert_equal(fid1.velocity_unit, new_fid1.velocity_unit)\n    assert_equal(fid1.magnetic_unit, new_fid1.magnetic_unit)\n    assert_equal(fid1.current_time, new_fid1.current_time)\n\n    ds2 = load(\"fid1.fits\")\n    ds2.index\n\n    assert (\"fits\", \"density\") in ds2.field_list\n    assert (\"fits\", \"temperature\") in ds2.field_list\n\n    dw_cm = ds2.domain_width.in_units(\"cm\")\n\n    assert dw_cm[0].v == 50.0\n    assert dw_cm[1].v == 50.0\n\n    slc = ds.slice(2, 0.5)\n    slc_frb = slc.to_frb((0.5, \"unitary\"), 128)\n\n    fid2 = slc_frb.to_fits_data(\n        fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")], length_unit=\"cm\"\n    )\n    fits_slc = FITSSlice(\n        ds,\n        \"z\",\n        [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        image_res=128,\n        width=(0.5, \"unitary\"),\n    )\n\n    assert_equal(fid2[\"density\"].data, fits_slc[\"density\"].data)\n    assert_equal(fid2[\"temperature\"].data, fits_slc[\"temperature\"].data)\n\n    fits_slc2 = FITSSlice(\n        ds,\n        \"z\",\n        [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        image_res=128,\n        width=(0.5, \"unitary\"),\n        origin=\"image\",\n    )\n\n    assert_equal(fits_slc2[\"density\"].data, fits_slc[\"density\"].data)\n    assert_equal(fits_slc2[\"temperature\"].data, fits_slc[\"temperature\"].data)\n    assert fits_slc.wcs.wcs.crval[0] != 0.0\n    assert fits_slc.wcs.wcs.crval[1] != 0.0\n    assert fits_slc2.wcs.wcs.crval[0] == 0.0\n    assert fits_slc2.wcs.wcs.crval[1] == 0.0\n\n    dens_img = fid2.pop(\"density\")\n    temp_img = fid2.pop(\"temperature\")\n\n    combined_fid = FITSImageData.from_images([dens_img, temp_img])\n    assert_equal(combined_fid.length_unit, dens_img.length_unit)\n    assert_equal(combined_fid.time_unit, dens_img.time_unit)\n    assert_equal(combined_fid.mass_unit, dens_img.mass_unit)\n    assert_equal(combined_fid.velocity_unit, dens_img.velocity_unit)\n    assert_equal(combined_fid.magnetic_unit, dens_img.magnetic_unit)\n    assert_equal(combined_fid.current_time, dens_img.current_time)\n\n    # Make sure that we can combine FITSImageData instances with more\n    # than one image each\n    combined_fid2 = FITSImageData.from_images([combined_fid, combined_fid])\n    # Writing the FITS file ensures that we have assembled the images\n    # together correctly\n    combined_fid2.writeto(\"combined.fits\", overwrite=True)\n\n    cut = ds.cutting([0.1, 0.2, -0.9], [0.5, 0.42, 0.6])\n    cut_frb = cut.to_frb((0.5, \"unitary\"), 128)\n\n    fid3 = cut_frb.to_fits_data(\n        fields=[(\"gas\", \"density\"), ds.fields.gas.temperature], length_unit=\"cm\"\n    )\n    fits_cut = FITSOffAxisSlice(\n        ds,\n        [0.1, 0.2, -0.9],\n        [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n        image_res=128,\n        center=[0.5, 0.42, 0.6],\n        width=(0.5, \"unitary\"),\n    )\n\n    assert_equal(fid3[\"density\"].data, fits_cut[\"density\"].data)\n    assert_equal(fid3[\"temperature\"].data, fits_cut[\"temperature\"].data)\n\n    fid3.create_sky_wcs([30.0, 45.0], (1.0, \"arcsec/kpc\"))\n    fid3.writeto(\"fid3.fits\", overwrite=True)\n    new_fid3 = FITSImageData.from_file(\"fid3.fits\")\n    assert_same_wcs(fid3.wcs, new_fid3.wcs)\n    assert new_fid3.wcs.wcs.cunit[0] == \"deg\"\n    assert new_fid3.wcs.wcs.cunit[1] == \"deg\"\n    assert new_fid3.wcs.wcs.ctype[0] == \"RA---TAN\"\n    assert new_fid3.wcs.wcs.ctype[1] == \"DEC--TAN\"\n    assert new_fid3.wcsa.wcs.cunit[0] == \"cm\"\n    assert new_fid3.wcsa.wcs.cunit[1] == \"cm\"\n    assert new_fid3.wcsa.wcs.ctype[0] == \"linear\"\n    assert new_fid3.wcsa.wcs.ctype[1] == \"linear\"\n\n    buf = off_axis_projection(\n        ds, ds.domain_center, [0.1, 0.2, -0.9], 0.5, 128, (\"gas\", \"density\")\n    ).swapaxes(0, 1)\n    fid4 = FITSImageData(buf, fields=[(\"gas\", \"density\")], width=100.0)\n    fits_oap = FITSOffAxisProjection(\n        ds,\n        [0.1, 0.2, -0.9],\n        (\"gas\", \"density\"),\n        width=(0.5, \"unitary\"),\n        image_res=128,\n        depth=(0.5, \"unitary\"),\n    )\n\n    assert_equal(fid4[\"density\"].data, fits_oap[\"density\"].data)\n\n    fid4.create_sky_wcs([30.0, 45.0], (1.0, \"arcsec/kpc\"), replace_old_wcs=False)\n    assert fid4.wcs.wcs.cunit[0] == \"cm\"\n    assert fid4.wcs.wcs.cunit[1] == \"cm\"\n    assert fid4.wcs.wcs.ctype[0] == \"linear\"\n    assert fid4.wcs.wcs.ctype[1] == \"linear\"\n    assert fid4.wcsa.wcs.cunit[0] == \"deg\"\n    assert fid4.wcsa.wcs.cunit[1] == \"deg\"\n    assert fid4.wcsa.wcs.ctype[0] == \"RA---TAN\"\n    assert fid4.wcsa.wcs.ctype[1] == \"DEC--TAN\"\n\n    cvg = ds.covering_grid(\n        ds.index.max_level,\n        [0.25, 0.25, 0.25],\n        [32, 32, 32],\n        fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n    )\n    fid5 = cvg.to_fits_data(fields=[(\"gas\", \"density\"), (\"gas\", \"temperature\")])\n    assert fid5.dimensionality == 3\n\n    fid5.update_header(\"density\", \"time\", 0.1)\n    fid5.update_header(\"all\", \"units\", \"cgs\")\n\n    assert fid5[\"density\"].header[\"time\"] == 0.1\n    assert fid5[\"temperature\"].header[\"units\"] == \"cgs\"\n    assert fid5[\"density\"].header[\"units\"] == \"cgs\"\n\n    fid6 = FITSImageData.from_images(fid5)\n\n    fid5.change_image_name(\"density\", \"mass_per_volume\")\n    assert fid5[\"mass_per_volume\"].name == \"mass_per_volume\"\n    assert fid5[\"mass_per_volume\"].header[\"BTYPE\"] == \"mass_per_volume\"\n    assert \"mass_per_volume\" in fid5.fields\n    assert \"mass_per_volume\" in fid5.field_units\n    assert \"density\" not in fid5.fields\n    assert \"density\" not in fid5.field_units\n\n    assert \"density\" in fid6.fields\n    assert_equal(fid6[\"density\"].data, fid5[\"mass_per_volume\"].data)\n\n    fid7 = FITSImageData.from_images(fid4)\n    fid7.convolve(\"density\", (3.0, \"cm\"))\n\n    sigma = 3.0 / fid7.wcs.wcs.cdelt[0]\n    kernel = _astropy.conv.Gaussian2DKernel(x_stddev=sigma)\n    data_conv = _astropy.conv.convolve(fid4[\"density\"].data.d, kernel)\n    assert_allclose(data_conv, fid7[\"density\"].data.d)\n\n    pfid = FITSParticleProjection(ds, \"x\", (\"io\", \"particle_mass\"))\n    assert pfid[\"particle_mass\"].name == \"particle_mass\"\n    assert pfid[\"particle_mass\"].header[\"BTYPE\"] == \"particle_mass\"\n    assert pfid[\"particle_mass\"].units == \"g\"\n\n    pofid = FITSParticleOffAxisProjection(ds, [1, 1, 1], (\"io\", \"particle_mass\"))\n    assert pofid[\"particle_mass\"].name == \"particle_mass\"\n    assert pofid[\"particle_mass\"].header[\"BTYPE\"] == \"particle_mass\"\n    assert pofid[\"particle_mass\"].units == \"g\"\n\n    pdfid = FITSParticleProjection(ds, \"x\", (\"io\", \"particle_mass\"), density=True)\n    assert pdfid[\"particle_mass\"].name == \"particle_mass\"\n    assert pdfid[\"particle_mass\"].header[\"BTYPE\"] == \"particle_mass\"\n    assert pdfid[\"particle_mass\"].units == \"g/cm**2\"\n\n    # Test moments for projections\n    def _vysq(data):\n        return data[\"gas\", \"velocity_y\"] ** 2\n\n    ds.add_field((\"gas\", \"vysq\"), _vysq, sampling_type=\"cell\", units=\"cm**2/s**2\")\n    fid8 = FITSProjection(\n        ds,\n        \"y\",\n        [(\"gas\", \"velocity_y\"), (\"gas\", \"vysq\")],\n        moment=1,\n        weight_field=(\"gas\", \"density\"),\n    )\n    fid9 = FITSProjection(\n        ds, \"y\", (\"gas\", \"velocity_y\"), moment=2, weight_field=(\"gas\", \"density\")\n    )\n    sigy = np.sqrt(fid8[\"vysq\"].data - fid8[\"velocity_y\"].data ** 2)\n    assert_allclose(sigy, fid9[\"velocity_y_stddev\"].data)\n\n    def _vlsq(data):\n        return data[\"gas\", \"velocity_los\"] ** 2\n\n    ds.add_field((\"gas\", \"vlsq\"), _vlsq, sampling_type=\"cell\", units=\"cm**2/s**2\")\n    fid10 = FITSOffAxisProjection(\n        ds,\n        [1.0, -1.0, 1.0],\n        [(\"gas\", \"velocity_los\"), (\"gas\", \"vlsq\")],\n        moment=1,\n        weight_field=(\"gas\", \"density\"),\n    )\n    fid11 = FITSOffAxisProjection(\n        ds,\n        [1.0, -1.0, 1.0],\n        (\"gas\", \"velocity_los\"),\n        moment=2,\n        weight_field=(\"gas\", \"density\"),\n    )\n    sigl = np.sqrt(fid10[\"vlsq\"].data - fid10[\"velocity_los\"].data ** 2)\n    assert_allclose(sigl, fid11[\"velocity_los_stddev\"].data)\n\n    # We need to manually close all the file descriptors so\n    # that windows can delete the folder that contains them.\n    ds2.close()\n    for fid in (\n        fid1,\n        fid2,\n        fid3,\n        fid4,\n        fid5,\n        fid6,\n        fid7,\n        fid8,\n        fid9,\n        new_fid1,\n        new_fid3,\n        pfid,\n        pdfid,\n        pofid,\n    ):\n        fid.close()\n\n    os.chdir(curdir)\n    shutil.rmtree(tmpdir)\n\n\netc = \"enzo_tiny_cosmology/DD0046/DD0046\"\n\n\n@requires_module(\"astropy\")\n@requires_file(etc)\ndef test_fits_cosmo():\n    ds = load(etc)\n\n    fid = FITSProjection(ds, \"z\", [\"density\"])\n    assert fid.wcs.wcs.cunit[0] == \"kpc\"\n    assert fid.wcs.wcs.cunit[1] == \"kpc\"\n    assert ds.hubble_constant == fid.hubble_constant\n    assert ds.current_redshift == fid.current_redshift\n    assert fid[\"density\"].header[\"HUBBLE\"] == ds.hubble_constant\n    assert fid[\"density\"].header[\"REDSHIFT\"] == ds.current_redshift\n    fid.close()\n"
  },
  {
    "path": "yt/visualization/tests/test_geo_projections.py",
    "content": "import unittest\n\nimport numpy as np\n\nimport yt\nfrom yt.testing import fake_amr_ds, requires_module\nfrom yt.visualization.geo_plot_utils import get_mpl_transform, transform_list\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass TestGeoProjections(unittest.TestCase):\n    @requires_module(\"cartopy\")\n    def setUp(self):\n        self.ds = fake_amr_ds(geometry=\"geographic\")\n        # switch off the log plot to avoid some unrelated matplotlib issues\n        f = self.ds._get_field_info((\"stream\", \"Density\"))\n        f.take_log = False\n\n    @requires_module(\"cartopy\")\n    def tearDown(self):\n        del self.ds\n\n    @requires_module(\"cartopy\")\n    def test_geo_projection_setup(self):\n        from yt.utilities.on_demand_imports import _cartopy as cartopy\n\n        axis = \"altitude\"\n        self.slc = yt.SlicePlot(self.ds, axis, (\"stream\", \"Density\"), origin=\"native\")\n\n        assert isinstance(self.slc._projection, cartopy.crs.Mollweide)\n        assert isinstance(self.slc._transform, cartopy.crs.PlateCarree)\n        assert self.ds.coordinates.data_projection[axis] == \"Mollweide\"\n        assert self.ds.coordinates.data_transform[axis] == \"PlateCarree\"\n        assert isinstance(\n            self.slc._projection,\n            type(self.slc.plots[\"stream\", \"Density\"].axes.projection),\n        )\n\n    @requires_module(\"cartopy\")\n    def test_geo_projections(self):\n        from yt.utilities.on_demand_imports import _cartopy as cartopy\n\n        self.slc = yt.SlicePlot(\n            self.ds, \"altitude\", (\"stream\", \"Density\"), origin=\"native\"\n        )\n\n        for transform in transform_list:\n            if transform == \"UTM\":\n                # this requires special arguments so let's skip it\n                continue\n            if transform == \"OSNI\":\n                # avoid crashes, see https://github.com/SciTools/cartopy/issues/1177\n                continue\n            self.slc.set_mpl_projection(transform)\n            proj_type = type(get_mpl_transform(transform))\n\n            assert isinstance(self.slc._projection, proj_type)\n            assert isinstance(self.slc._transform, cartopy.crs.PlateCarree)\n            assert isinstance(\n                self.slc.plots[\"stream\", \"Density\"].axes.projection, proj_type\n            )\n\n    @requires_module(\"cartopy\")\n    def test_projection_object(self):\n        from yt.utilities.on_demand_imports import _cartopy as cartopy\n\n        shortlist = [\"Orthographic\", \"PlateCarree\", \"Mollweide\"]\n\n        for transform in shortlist:\n            projection = get_mpl_transform(transform)\n            proj_type = type(projection)\n            self.slc = yt.SlicePlot(\n                self.ds, \"altitude\", (\"stream\", \"Density\"), origin=\"native\"\n            )\n            self.slc.set_mpl_projection(projection)\n\n            assert isinstance(self.slc._projection, proj_type)\n            assert isinstance(self.slc._transform, cartopy.crs.PlateCarree)\n            assert isinstance(\n                self.slc.plots[\"stream\", \"Density\"].axes.projection, proj_type\n            )\n\n    @requires_module(\"cartopy\")\n    def test_nondefault_transform(self):\n        from yt.utilities.on_demand_imports import _cartopy as cartopy\n\n        axis = \"altitude\"\n        # Note: The Miller transform has an extent of approx. +/- 180 in x,\n        # +/-132 in y (in Miller, x is longitude, y is a factor of latitude).\n        # So by changing the projection in this way, the dataset goes from\n        # covering the whole globe (+/- 90 latitude), to covering part of the\n        # globe (+/-72 latitude). Totally fine for testing that the code runs,\n        # but good to be aware of!\n        self.ds.coordinates.data_transform[axis] = \"Miller\"\n        self.slc = yt.SlicePlot(self.ds, axis, (\"stream\", \"Density\"), origin=\"native\")\n\n        shortlist = [\"Orthographic\", \"PlateCarree\", \"Mollweide\"]\n\n        for transform in shortlist:\n            self.slc.set_mpl_projection(transform)\n            proj_type = type(get_mpl_transform(transform))\n\n            assert isinstance(self.slc._projection, proj_type)\n            assert isinstance(self.slc._transform, cartopy.crs.Miller)\n            assert self.ds.coordinates.data_projection[axis] == \"Mollweide\"\n            assert self.ds.coordinates.data_transform[axis] == \"Miller\"\n            assert isinstance(\n                self.slc.plots[\"stream\", \"Density\"].axes.projection, proj_type\n            )\n\n    @requires_module(\"cartopy\")\n    def test_extent(self):\n        # checks that the axis extent is narrowed when doing a subselection\n        axis = \"altitude\"\n        slc = yt.SlicePlot(self.ds, axis, (\"stream\", \"Density\"), origin=\"native\")\n        ax = slc.plots[\"stream\", \"Density\"].axes\n        full_extent = np.abs(ax.get_extent())\n\n        slc = yt.SlicePlot(\n            self.ds, axis, (\"stream\", \"Density\"), origin=\"native\", width=(80.0, 50.0)\n        )\n        ax = slc.plots[\"stream\", \"Density\"].axes\n        extent = np.abs(ax.get_extent())\n        assert np.all(extent < full_extent)\n\n\nclass TestNonGeoProjections(unittest.TestCase):\n    def setUp(self):\n        self.ds = fake_amr_ds()\n\n    def tearDown(self):\n        del self.ds\n        del self.slc\n\n    def test_projection_setup(self):\n        axis = \"x\"\n        self.slc = yt.SlicePlot(self.ds, axis, (\"stream\", \"Density\"), origin=\"native\")\n\n        assert self.ds.coordinates.data_projection[axis] is None\n        assert self.ds.coordinates.data_transform[axis] is None\n        assert self.slc._projection is None\n        assert self.slc._transform is None\n"
  },
  {
    "path": "yt/visualization/tests/test_image_comp_2D_plots.py",
    "content": "# image tests using pytest-mpl\nfrom itertools import chain\n\nimport matplotlib as mpl\nimport numpy as np\nimport numpy.testing as npt\nimport pytest\nfrom matplotlib.colors import SymLogNorm\n\nfrom yt.data_objects.profiles import create_profile\nfrom yt.loaders import load_uniform_grid\nfrom yt.testing import add_noise_fields, fake_amr_ds, fake_particle_ds, fake_random_ds\nfrom yt.visualization.api import (\n    LinePlot,\n    ParticleProjectionPlot,\n    PhasePlot,\n    ProfilePlot,\n    SlicePlot,\n)\n\n\ndef test_sliceplot_set_unit_and_zlim_order():\n    ds = fake_random_ds(16)\n    field = (\"gas\", \"density\")\n\n    p0 = SlicePlot(ds, \"z\", field)\n    p0.set_unit(field, \"kg/m**3\")\n    p0.set_zlim(field, zmin=0)\n\n    # reversing order of operations\n    p1 = SlicePlot(ds, \"z\", field)\n    p1.set_zlim(field, zmin=0)\n    p1.set_unit(field, \"kg/m**3\")\n\n    p0.render()\n    p1.render()\n\n    im0 = p0.plots[field].image._A\n    im1 = p1.plots[field].image._A\n\n    npt.assert_allclose(im0, im1)\n\n\ndef test_annotation_parse_h():\n    ds = fake_random_ds(16)\n\n    # Make sure `h` (reduced Hubble constant) is not equal to 1\n    ds.unit_registry.modify(\"h\", 0.7)\n\n    rad = ds.quan(0.1, \"cm/h\")\n    center = ds.arr([0.5] * 3, \"code_length\")\n\n    # Twice the same slice plot\n    p1 = SlicePlot(ds, \"x\", \"density\")\n    p2 = SlicePlot(ds, \"x\", \"density\")\n\n    # But the *same* center is given in different units\n    p1.annotate_sphere(center.to(\"cm\"), rad, circle_args={\"color\": \"black\"})\n    p2.annotate_sphere(center.to(\"cm/h\"), rad, circle_args={\"color\": \"black\"})\n\n    # Render annotations, and extract matplotlib image\n    # as an RGB array\n    p1.render()\n    p1.plots[\"gas\", \"density\"].figure.canvas.draw()\n    img1 = p1.plots[\"gas\", \"density\"].figure.canvas.renderer.buffer_rgba()\n\n    p2.render()\n    p2.plots[\"gas\", \"density\"].figure.canvas.draw()\n    img2 = p2.plots[\"gas\", \"density\"].figure.canvas.renderer.buffer_rgba()\n\n    # This should be the same image\n    npt.assert_allclose(img1, img2)\n\n\n@pytest.mark.mpl_image_compare\ndef test_inf_and_finite_values_set_zlim():\n    # see https://github.com/yt-project/yt/issues/3901\n    shape = (32, 16, 1)\n    a = np.ones(16)\n    b = np.ones((32, 16))\n    c = np.reshape(a * b, shape)\n\n    # injecting an inf\n    c[0, 0, 0] = np.inf\n\n    field = (\"gas\", \"density\")\n    data = {field: c}\n\n    ds = load_uniform_grid(\n        data,\n        shape,\n        bbox=np.array([[0, 1], [0, 1], [0, 1]]),\n    )\n\n    p = SlicePlot(ds, \"z\", field)\n\n    # setting zlim manually\n    p.set_zlim(field, -10, 10)\n\n    p.render()\n    return p.plots[field].figure\n\n\n@pytest.mark.mpl_image_compare\ndef test_sliceplot_custom_norm():\n    from matplotlib.colors import TwoSlopeNorm\n\n    ds = fake_random_ds(16)\n    field = (\"gas\", \"density\")\n    p = SlicePlot(ds, \"z\", field)\n    p.set_norm(field, norm=TwoSlopeNorm(vcenter=0, vmin=-0.5, vmax=1))\n\n    p.render()\n    return p.plots[field].figure\n\n\n@pytest.mark.mpl_image_compare\ndef test_sliceplot_custom_norm_symlog_int_base():\n    ds = fake_random_ds(16)\n    add_noise_fields(ds)\n    field = \"noise3\"\n    p = SlicePlot(ds, \"z\", field)\n\n    # using integer base !=10 and >2 to exercise special case\n    # for colorbar minor ticks\n    p.set_norm(field, norm=SymLogNorm(linthresh=0.1, base=5))\n\n    p.render()\n    return p.plots[field].figure\n\n\n@pytest.mark.mpl_image_compare\ndef test_lineplot_set_axis_properties():\n    ds = fake_random_ds(16)\n    field = (\"gas\", \"density\")\n    p = LinePlot(\n        ds,\n        field,\n        start_point=[0, 0, 0],\n        end_point=[1, 1, 1],\n        npoints=32,\n    )\n    p.set_x_unit(\"cm\")\n    p.set_unit(field, \"kg/cm**3\")\n    p.set_log(field, False)\n\n    p.render()\n    return p.plots[field].figure\n\n\n@pytest.mark.mpl_image_compare\ndef test_profileplot_set_axis_properties():\n    ds = fake_random_ds(16)\n\n    disk = ds.disk(ds.domain_center, [0.0, 0.0, 1.0], (10, \"m\"), (1, \"m\"))\n    p = ProfilePlot(disk, (\"gas\", \"density\"), [(\"gas\", \"velocity_x\")])\n    p.set_unit((\"gas\", \"density\"), \"kg/cm**3\")\n    p.set_log((\"gas\", \"density\"), False)\n    p.set_unit((\"gas\", \"velocity_x\"), \"mile/hour\")\n\n    p.render()\n    return p.plots[\"gas\", \"velocity_x\"].figure\n\n\n@pytest.mark.mpl_image_compare\ndef test_particleprojectionplot_set_colorbar_properties():\n    ds = fake_particle_ds(npart=100)\n\n    field = (\"all\", \"particle_mass\")\n    p = ParticleProjectionPlot(ds, 2, field)\n    p.set_buff_size(10)\n\n    p.set_unit(field, \"Msun\")\n    p.set_zlim(field, zmax=1e-35)\n    p.set_log(field, False)\n\n    p.render()\n    return p.plots[field].figure\n\n\nclass TestMultipanelPlot:\n    @classmethod\n    def setup_class(cls):\n        cls.fields = [\n            (\"gas\", \"density\"),\n            (\"gas\", \"velocity_x\"),\n            (\"gas\", \"velocity_y\"),\n            (\"gas\", \"velocity_magnitude\"),\n        ]\n        cls.ds = fake_random_ds(16)\n\n    @pytest.mark.parametrize(\"cbar_location\", [\"top\", \"bottom\", \"left\", \"right\"])\n    @pytest.mark.mpl_image_compare\n    def test_multipanelplot_colorbar_orientation_simple(self, cbar_location):\n        p = SlicePlot(self.ds, \"z\", self.fields)\n        return p.export_to_mpl_figure((2, 2), cbar_location=cbar_location)\n\n    @pytest.mark.parametrize(\"cbar_location\", [\"top\", \"bottom\"])\n    def test_multipanelplot_colorbar_orientation_warning(self, cbar_location):\n        p = SlicePlot(self.ds, \"z\", self.fields)\n        p.export_to_mpl_figure((2, 2), cbar_location=cbar_location)\n\n\nclass TestProfilePlot:\n    @classmethod\n    def setup_class(cls):\n        fields = (\"density\", \"temperature\", \"velocity_x\", \"velocity_y\", \"velocity_z\")\n        units = (\"g/cm**3\", \"K\", \"cm/s\", \"cm/s\", \"cm/s\")\n        cls.ds = fake_random_ds(16, fields=fields, units=units)\n        regions = [cls.ds.region([0.5] * 3, [0.4] * 3, [0.6] * 3), cls.ds.all_data()]\n        pr_fields = [\n            [(\"gas\", \"density\"), (\"gas\", \"temperature\")],\n            [(\"gas\", \"density\"), (\"gas\", \"velocity_x\")],\n            [(\"gas\", \"temperature\"), (\"gas\", \"mass\")],\n            [(\"gas\", \"density\"), (\"index\", \"radius\")],\n            [(\"gas\", \"velocity_magnitude\"), (\"gas\", \"mass\")],\n        ]\n        cls.profiles: dict[str, ProfilePlot] = {}\n        for i_reg, reg in enumerate(regions):\n            id_prefix = str(i_reg)\n            for x_field, y_field in pr_fields:\n                id_suffix = \"_\".join([*x_field, *y_field])\n                base_id = f\"{id_prefix}_{id_suffix}\"\n                cls.profiles[base_id] = ProfilePlot(reg, x_field, y_field)\n                cls.profiles[f\"{base_id}_fractional_accumulation\"] = ProfilePlot(\n                    reg, x_field, y_field, fractional=True, accumulation=True\n                )\n\n                p1d = create_profile(reg, x_field, y_field)\n                cls.profiles[f\"{base_id}_from_profiles\"] = ProfilePlot.from_profiles(\n                    p1d\n                )\n\n        p1 = create_profile(\n            cls.ds.all_data(), (\"gas\", \"density\"), (\"gas\", \"temperature\")\n        )\n        p2 = create_profile(\n            cls.ds.all_data(), (\"gas\", \"density\"), (\"gas\", \"velocity_x\")\n        )\n        cls.profiles[\"from_multiple_profiles\"] = ProfilePlot.from_profiles(\n            [p1, p2], labels=[\"temperature\", \"velocity\"]\n        )\n\n    @pytest.mark.parametrize(\n        \"suffix\",\n        [None, \"from_profiles\", \"fractional_accumulation\"],\n    )\n    @pytest.mark.parametrize(\"region\", [\"0\", \"1\"])\n    @pytest.mark.parametrize(\n        \"xax, yax\",\n        [\n            ((\"gas\", \"density\"), (\"gas\", \"temperature\")),\n            ((\"gas\", \"density\"), (\"gas\", \"velocity_x\")),\n            ((\"gas\", \"temperature\"), (\"gas\", \"mass\")),\n            ((\"gas\", \"density\"), (\"index\", \"radius\")),\n            ((\"gas\", \"velocity_magnitude\"), (\"gas\", \"mass\")),\n        ],\n    )\n    @pytest.mark.mpl_image_compare\n    def test_profileplot_simple(self, region, xax, yax, suffix):\n        key = \"_\".join(chain(region, xax, yax))\n        if suffix is not None:\n            key += f\"_{suffix}\"\n        plots = list(self.profiles[key].plots.values())\n        assert len(plots) == 1\n        return plots[0].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_profileplot_from_multiple_profiles_0(self):\n        plots = list(self.profiles[\"from_multiple_profiles\"].plots.values())\n        assert len(plots) == 2\n        return plots[0].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_profileplot_from_multiple_profiles_1(self):\n        plots = list(self.profiles[\"from_multiple_profiles\"].plots.values())\n        assert len(plots) == 2\n        return plots[1].figure\n\n\nclass TestPhasePlot:\n    @classmethod\n    def setup_class(cls):\n        fields = (\"density\", \"temperature\", \"velocity_x\", \"velocity_y\", \"velocity_z\")\n        units = (\"g/cm**3\", \"K\", \"cm/s\", \"cm/s\", \"cm/s\")\n        cls.ds = fake_random_ds(16, fields=fields, units=units)\n        regions = [cls.ds.region([0.5] * 3, [0.4] * 3, [0.6] * 3), cls.ds.all_data()]\n        pr_fields = [\n            [(\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"mass\")],\n            [(\"gas\", \"density\"), (\"gas\", \"velocity_x\"), (\"gas\", \"mass\")],\n            [\n                (\"index\", \"radius\"),\n                (\"gas\", \"temperature\"),\n                (\"gas\", \"velocity_magnitude\"),\n            ],\n        ]\n        cls.profiles: dict[str, PhasePlot] = {}\n        for i_reg, reg in enumerate(regions):\n            id_prefix = str(i_reg)\n            for x_field, y_field, z_field in pr_fields:\n                id_suffix = \"_\".join([*x_field, *y_field, *z_field])\n                base_id = f\"{id_prefix}_{id_suffix}\"\n                cls.profiles[base_id] = PhasePlot(\n                    reg, x_field, y_field, z_field, x_bins=16, y_bins=16\n                )\n                cls.profiles[f\"{base_id}_fractional_accumulation\"] = PhasePlot(\n                    reg,\n                    x_field,\n                    y_field,\n                    z_field,\n                    fractional=True,\n                    accumulation=True,\n                    x_bins=16,\n                    y_bins=16,\n                )\n\n                p2d = create_profile(reg, [x_field, y_field], z_field, n_bins=[16, 16])\n                cls.profiles[f\"{base_id}_from_profiles\"] = PhasePlot.from_profile(p2d)\n\n    @pytest.mark.parametrize(\n        \"suffix\",\n        [None, \"from_profiles\", \"fractional_accumulation\"],\n    )\n    @pytest.mark.parametrize(\"region\", [\"0\", \"1\"])\n    @pytest.mark.parametrize(\n        \"xax, yax, zax\",\n        [\n            ((\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"mass\")),\n            ((\"gas\", \"density\"), (\"gas\", \"velocity_x\"), (\"gas\", \"mass\")),\n            (\n                (\"index\", \"radius\"),\n                (\"gas\", \"temperature\"),\n                (\"gas\", \"velocity_magnitude\"),\n            ),\n        ],\n    )\n    @pytest.mark.mpl_image_compare\n    def test_phaseplot(self, region, xax, yax, zax, suffix):\n        key = \"_\".join(chain(region, xax, yax, zax))\n        if suffix is not None:\n            key += f\"_{suffix}\"\n        plots = list(self.profiles[key].plots.values())\n        assert len(plots) == 1\n        return plots[0].figure\n\n\nclass TestPhasePlotSetZlim:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_random_ds(16)\n        add_noise_fields(cls.ds)\n        cls.data = cls.ds.sphere(\"c\", 1)\n\n    @pytest.mark.mpl_image_compare\n    def test_phaseplot_set_zlim_with_implicit_units(self):\n        p = PhasePlot(\n            self.data,\n            (\"gas\", \"noise1\"),\n            (\"gas\", \"noise3\"),\n            [(\"gas\", \"density\")],\n            weight_field=None,\n        )\n        field = (\"gas\", \"density\")\n        p.set_zlim(field, zmax=10)\n        p.render()\n        return p.plots[field].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_phaseplot_set_zlim_with_explicit_units(self):\n        p = PhasePlot(\n            self.data,\n            (\"gas\", \"noise1\"),\n            (\"gas\", \"noise3\"),\n            [(\"gas\", \"density\")],\n            weight_field=None,\n        )\n        field = (\"gas\", \"density\")\n        # using explicit units, we expect the colorbar units to stay unchanged\n        p.set_zlim(field, zmin=(1e36, \"kg/AU**3\"))\n        p.render()\n        return p.plots[field].figure\n\n\nclass TestSetBackgroundColor:\n    # see https://github.com/yt-project/yt/issues/3854\n\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_random_ds(16)\n\n        def some_nans_field(data):\n            ret = data[\"gas\", \"density\"]\n            ret[::2] *= np.nan\n            return ret\n\n        cls.ds.add_field(\n            name=(\"gas\", \"polluted_field\"),\n            function=some_nans_field,\n            sampling_type=\"local\",\n        )\n\n    @pytest.mark.mpl_image_compare\n    def test_sliceplot_set_background_color_linear(self):\n        field = (\"gas\", \"density\")\n        p = SlicePlot(self.ds, \"z\", field, width=1.5)\n        p.set_background_color(field, color=\"C0\")\n        p.set_log(field, False)\n\n        p.render()\n        return p.plots[field].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_sliceplot_set_background_color_log(self):\n        field = (\"gas\", \"density\")\n        p = SlicePlot(self.ds, \"z\", field, width=1.5)\n        p.set_background_color(field, color=\"C0\")\n\n        p.render()\n        return p.plots[field].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_sliceplot_set_background_color_and_bad_value(self):\n        # see https://github.com/yt-project/yt/issues/4639\n        field = (\"gas\", \"polluted_field\")\n        p = SlicePlot(self.ds, \"z\", field, width=1.5)\n        p.set_background_color(field, color=\"black\")\n\n        # copy the default colormap\n        cmap = mpl.colormaps[\"cmyt.arbre\"].with_extremes(bad=\"red\")\n        p.set_cmap(field, cmap)\n\n        p.render()\n        return p.plots[field].figure\n\n\nclass TestCylindricalZSlicePlot:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_amr_ds(geometry=\"cylindrical\")\n        add_noise_fields(cls.ds)\n        fields = [f\"noise{i}\" for i in range(4)]\n        cls.plot = SlicePlot(cls.ds, \"z\", fields)\n\n    @pytest.mark.parametrize(\"field\", [\"noise0\", \"noise1\", \"noise2\", \"noise3\"])\n    @pytest.mark.mpl_image_compare\n    def test_cylindrical_z_log(self, field):\n        return self.plot.plots[field].figure\n\n    @pytest.mark.parametrize(\"field\", [\"noise0\", \"noise1\", \"noise2\", \"noise3\"])\n    @pytest.mark.mpl_image_compare\n    def test_cylindrical_z_linear(self, field):\n        self.plot.set_log(\"noise0\", False)\n        return self.plot.plots[field].figure\n\n    @pytest.mark.parametrize(\n        \"theta_min, theta_max\",\n        [\n            pytest.param(0, 2 * np.pi, id=\"full_azimuthal_domain\"),\n            pytest.param(3 / 4 * np.pi, 5 / 4 * np.pi, id=\"restricted_sector\"),\n        ],\n    )\n    @pytest.mark.mpl_image_compare\n    def test_exclude_pixels_with_partial_bbox_intersection(self, theta_min, theta_max):\n        rmin = 1.0\n        rmax = 2.0\n        ds = fake_amr_ds(\n            geometry=\"cylindrical\",\n            domain_left_edge=[rmin, 0, theta_min],\n            domain_right_edge=[rmax, 1, theta_max],\n        )\n        add_noise_fields(ds)\n        plot = SlicePlot(ds, \"z\", (\"gas\", \"noise0\"))\n        for radius in [rmin - 0.01, rmax]:\n            plot.annotate_sphere(\n                center=[0, 0, 0],\n                radius=radius,\n                circle_args={\"color\": \"red\", \"alpha\": 0.4, \"linewidth\": 3},\n            )\n        plot.annotate_title(\"all pixels beyond (or on) red lines should be white\")\n        plot.render()\n        return plot.plots[\"gas\", \"noise0\"].figure\n\n\nclass TestSphericalPhiSlicePlot:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_amr_ds(geometry=\"spherical\")\n        add_noise_fields(cls.ds)\n        fields = [f\"noise{i}\" for i in range(4)]\n        cls.plot = SlicePlot(cls.ds, \"phi\", fields)\n\n    @pytest.mark.parametrize(\"field\", [\"noise0\", \"noise1\", \"noise2\", \"noise3\"])\n    @pytest.mark.mpl_image_compare\n    def test_spherical_phi_log(self, field):\n        return self.plot.plots[field].figure\n\n\nclass TestSphericalThetaSlicePlot:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_amr_ds(geometry=\"spherical\")\n        add_noise_fields(cls.ds)\n        fields = [f\"noise{i}\" for i in range(4)]\n        cls.plot = SlicePlot(cls.ds, \"theta\", fields)\n\n    @pytest.mark.parametrize(\"field\", [\"noise0\", \"noise1\", \"noise2\", \"noise3\"])\n    @pytest.mark.mpl_image_compare\n    def test_spherical_theta_log(self, field):\n        return self.plot.plots[field].figure\n"
  },
  {
    "path": "yt/visualization/tests/test_image_comp_geo.py",
    "content": "import pytest\n\nimport yt\nfrom yt.testing import fake_amr_ds, requires_module_pytest as requires_module\n\n\nclass TestGeoTransform:\n    # Cartopy require pykdtree *or* scipy to enable the projections\n    # we test here. We require scipy for simplicity because it offers\n    # better support for various platforms via PyPI at the time of writing.abs\n\n    # the following projections are skipped (reason)\n    # - UTM (requires additional arguments)\n    # - OSNI (avoid crashes, see https://github.com/SciTools/cartopy/issues/1177)\n\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_amr_ds(geometry=\"geographic\")\n\n    @requires_module(\"cartopy\", \"scipy\")\n    @pytest.mark.parametrize(\n        \"transform\",\n        [\n            \"PlateCarree\",\n            \"LambertConformal\",\n            \"LambertCylindrical\",\n            \"Mercator\",\n            \"Miller\",\n            \"Mollweide\",\n            \"Orthographic\",\n            \"Robinson\",\n            \"Stereographic\",\n            \"TransverseMercator\",\n            \"InterruptedGoodeHomolosine\",\n            \"RotatedPole\",\n            \"OSGB\",\n            \"EuroPP\",\n            \"Geostationary\",\n            \"Gnomonic\",\n            \"NorthPolarStereo\",\n            \"SouthPolarStereo\",\n            \"AlbersEqualArea\",\n            \"AzimuthalEquidistant\",\n            \"Sinusoidal\",\n            \"NearsidePerspective\",\n            \"LambertAzimuthalEqualArea\",\n        ],\n    )\n    @pytest.mark.mpl_image_compare\n    def test_geo_tranform(self, transform):\n        field = (\"stream\", \"Density\")\n        sl = yt.SlicePlot(self.ds, \"altitude\", field, origin=\"native\")\n        sl.set_mpl_projection(transform)\n        sl.set_log(field, False)\n        sl.render()\n        return sl.plots[field].figure\n"
  },
  {
    "path": "yt/visualization/tests/test_image_writer.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\n\nimport numpy as np\nfrom nose.tools import assert_raises\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import fake_random_ds\nfrom yt.visualization.image_writer import (\n    apply_colormap,\n    multi_image_composite,\n    splat_points,\n    write_bitmap,\n)\n\n\nclass TestImageWriter(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        cls.tmpdir = tempfile.mkdtemp()\n        cls.curdir = os.getcwd()\n        os.chdir(cls.tmpdir)\n\n    @classmethod\n    def tearDownClass(cls):\n        os.chdir(cls.curdir)\n        shutil.rmtree(cls.tmpdir)\n\n    def test_multi_image_composite(self):\n        ds = fake_random_ds(64, nprocs=4, particles=16**3)\n        center = [0.5, 0.5, 0.5]\n        normal = [1, 1, 1]\n        cut = ds.cutting(normal, center)\n        frb = cut.to_frb((0.75, \"unitary\"), 64)\n        multi_image_composite(\n            \"multi_channel1.png\", frb[\"index\", \"x\"], frb[\"index\", \"y\"]\n        )\n\n        # Test multi_image_composite with user specified scaling values\n        mi = ds.quan(0.1, \"code_length\")\n        ma = ds.quan(0.9, \"code_length\")\n        multi_image_composite(\n            \"multi_channel2.png\",\n            (frb[\"index\", \"x\"], mi, ma),\n            [frb[\"index\", \"y\"], mi, None],\n            green_channel=frb[\"index\", \"z\"],\n            alpha_channel=frb[\"gas\", \"density\"],\n        )\n\n        # Test with numpy integer array\n        x = np.array(np.random.randint(0, 256, size=(10, 10)), dtype=\"uint8\")\n        y = np.array(np.random.randint(0, 256, size=(10, 10)), dtype=\"uint8\")\n        multi_image_composite(\"multi_channel3.png\", x, y)\n\n    def test_write_bitmap(self):\n        image = np.zeros([16, 16, 4], dtype=\"uint8\")\n        xs = np.random.rand(100)\n        ys = np.random.rand(100)\n        image = splat_points(image, xs, ys)\n        png_str = write_bitmap(image.copy(), None)\n\n        image_trans = image.swapaxes(0, 1).copy(order=\"C\")\n        png_str_trans = write_bitmap(image_trans, None, transpose=True)\n        assert_equal(png_str, png_str_trans)\n\n        with assert_raises(RuntimeError) as ex:\n            write_bitmap(np.ones([16, 16]), None)\n        desired = \"Expecting image array of shape (N,M,3) or (N,M,4), received (16, 16)\"\n        assert_equal(str(ex.exception)[:50], desired[:50])\n\n    def test_apply_colormap(self):\n        x = np.array(np.random.randint(0, 256, size=(10, 10)), dtype=\"uint8\")\n        apply_colormap(x, color_bounds=None, cmap_name=None, func=lambda x: x**2)\n"
  },
  {
    "path": "yt/visualization/tests/test_invalid_origin.py",
    "content": "import re\n\nimport pytest\n\nfrom yt.testing import fake_amr_ds\nfrom yt.visualization.plot_window import SlicePlot\n\n\n@pytest.mark.parametrize(\n    (\"origin\", \"msg\"),\n    [\n        (\n            \"ONE\",\n            (\n                \"Invalid origin argument. \"\n                \"Single element specification must be 'window', 'domain', or 'native'. \"\n                \"Got 'ONE'\"\n            ),\n        ),\n        (\n            \"ONE-TWO\",\n            (\n                \"Invalid origin argument. \"\n                \"Using 2 elements:\\n\"\n                \" - the first one must be 'left', 'right', 'lower', 'upper' or 'center'\\n\"\n                \" - the second one must be 'window', 'domain', or 'native'\\n\"\n                \"Got 'ONE-TWO'\"\n            ),\n        ),\n        (\n            \"ONE-window\",\n            (\n                \"Invalid origin argument. \"\n                \"Using 2 elements:\\n\"\n                \" - the first one must be 'left', 'right', 'lower', 'upper' or 'center'\\n\"\n                \"Got 'ONE-window'\"\n            ),\n        ),\n        (\n            \"left-TWO\",\n            (\n                \"Invalid origin argument. \"\n                \"Using 2 elements:\\n\"\n                \" - the second one must be 'window', 'domain', or 'native'\\n\"\n                \"Got 'left-TWO'\"\n            ),\n        ),\n        (\n            \"ONE-TWO-THREE\",\n            (\n                \"Invalid origin argument. \"\n                \"Using 3 elements:\\n\"\n                \" - the first one must be 'lower', 'upper' or 'center' or a distance\\n\"\n                \" - the second one must be 'left', 'right', 'center' or a distance\\n\"\n                \" - the third one must be 'window', 'domain', or 'native'\\n\"\n                \"Got 'ONE-TWO-THREE'\"\n            ),\n        ),\n        (\n            \"ONE-TWO-window\",\n            (\n                \"Invalid origin argument. \"\n                \"Using 3 elements:\\n\"\n                \" - the first one must be 'lower', 'upper' or 'center' or a distance\\n\"\n                \" - the second one must be 'left', 'right', 'center' or a distance\\n\"\n                \"Got 'ONE-TWO-window'\"\n            ),\n        ),\n        (\n            \"ONE-left-window\",\n            (\n                \"Invalid origin argument. \"\n                \"Using 3 elements:\\n\"\n                \" - the first one must be 'lower', 'upper' or 'center' or a distance\\n\"\n                \"Got 'ONE-left-window'\"\n            ),\n        ),\n        (\n            \"ONE-left-THREE\",\n            (\n                \"Invalid origin argument. \"\n                \"Using 3 elements:\\n\"\n                \" - the first one must be 'lower', 'upper' or 'center' or a distance\\n\"\n                \" - the third one must be 'window', 'domain', or 'native'\\n\"\n                \"Got 'ONE-left-THREE'\"\n            ),\n        ),\n        (\n            \"lower-left-THREE\",\n            (\n                \"Invalid origin argument. \"\n                \"Using 3 elements:\\n\"\n                \" - the third one must be 'window', 'domain', or 'native'\\n\"\n                \"Got 'lower-left-THREE'\"\n            ),\n        ),\n        (\n            (\"ONE\", \"TWO\", \"THREE\"),\n            (\n                \"Invalid origin argument. \"\n                \"Using 3 elements:\\n\"\n                \" - the first one must be 'lower', 'upper' or 'center' or a distance\\n\"\n                \" - the second one must be 'left', 'right', 'center' or a distance\\n\"\n                \" - the third one must be 'window', 'domain', or 'native'\\n\"\n                \"Got ('ONE', 'TWO', 'THREE')\"\n            ),\n        ),\n        (\n            (\"ONE\", \"TWO\", (1, 1, 3)),\n            (\n                \"Invalid origin argument. \"\n                \"Using 3 elements:\\n\"\n                \" - the first one must be 'lower', 'upper' or 'center' or a distance\\n\"\n                \" - the second one must be 'left', 'right', 'center' or a distance\\n\"\n                \" - the third one must be 'window', 'domain', or 'native'\\n\"\n                \"Got ('ONE', 'TWO', (1, 1, 3))\"\n            ),\n        ),\n        (\n            \"ONE-TWO-THREE-FOUR\",\n            (\n                \"Invalid origin argument with too many elements; \"\n                \"expected 1, 2 or 3 elements, got 'ONE-TWO-THREE-FOUR', counting 4 elements. \"\n                \"Use '-' as a separator for string arguments.\"\n            ),\n        ),\n    ],\n)\ndef test_invalidate_origin_value(origin, msg):\n    ds = fake_amr_ds(fields=[(\"gas\", \"density\")], units=[\"g*cm**-3\"])\n    with pytest.raises(ValueError, match=re.escape(msg)):\n        SlicePlot(ds, \"z\", (\"gas\", \"density\"), origin=origin)\n\n\n@pytest.mark.parametrize(\n    \"origin\",\n    # don't attempt to match exactly a TypeError message because it should be\n    # emitted by unyt, not yt\n    [\n        ((50, 50, 50), \"TWO\", \"THREE\"),\n        (\"ONE\", (50, 50, 50), \"THREE\"),\n    ],\n)\ndef test_invalidate_origin_type(origin):\n    ds = fake_amr_ds(fields=[(\"gas\", \"density\")], units=[\"g*cm**-3\"])\n    with pytest.raises(TypeError):\n        SlicePlot(ds, \"z\", (\"gas\", \"density\"), origin=origin)\n"
  },
  {
    "path": "yt/visualization/tests/test_line_annotation_unit.py",
    "content": "import numpy as np\n\nfrom yt.loaders import load_uniform_grid\nfrom yt.visualization.plot_window import ProjectionPlot\n\n\ndef test_ds_arr_invariance_under_projection_plot(tmp_path):\n    data_array = np.random.random((10, 10, 10))\n    bbox = np.array([[-100, 100], [-100, 100], [-100, 100]])\n    data = {(\"gas\", \"density\"): (data_array, \"g*cm**(-3)\")}\n    ds = load_uniform_grid(data, data_array.shape, length_unit=\"kpc\", bbox=bbox)\n\n    start_source = np.array((0, 0, -0.5))\n    end_source = np.array((0, 0, 0.5))\n    start = ds.arr(start_source, \"unitary\")\n    end = ds.arr(end_source, \"unitary\")\n\n    start_i = start.copy()\n    end_i = end.copy()\n\n    p = ProjectionPlot(ds, 0, \"number_density\")\n    p.annotate_line(start, end)\n    p.save(tmp_path)\n\n    # for lack of a unyt.testing.assert_unit_array_equal function\n    np.testing.assert_array_equal(start_i, start)\n    assert start_i.units == start.units\n    np.testing.assert_array_equal(end_i, end)\n    assert end_i.units == end.units\n"
  },
  {
    "path": "yt/visualization/tests/test_line_plots.py",
    "content": "import pytest\nfrom numpy.testing import assert_equal\n\nimport yt\nfrom yt.testing import fake_random_ds\nfrom yt.visualization.line_plot import _validate_point\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass TestLinePlotSimple:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_random_ds(4)\n        fields = [field for field in cls.ds.field_list if field[0] == \"stream\"]\n        field_labels = {f: f[1] for f in fields}\n        plot = yt.LinePlot(\n            cls.ds, fields, (0, 0, 0), (1, 1, 0), 1000, field_labels=field_labels\n        )\n        plot.annotate_legend(fields[0])\n        plot.annotate_legend(fields[1])\n        plot.set_x_unit(\"cm\")\n        plot.set_unit(fields[0], \"kg/cm**3\")\n        plot.annotate_title(fields[0], \"Density Plot\")\n        plot.render()\n        cls.plot = plot\n\n    @pytest.mark.mpl_image_compare\n    def test_lineplot_simple_density(self):\n        return self.plot.plots[\"stream\", \"density\"].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_lineplot_simple_velocity_x(self):\n        return self.plot.plots[\"stream\", \"velocity_x\"].figure\n\n\nclass TestLinePlotMulti:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_random_ds(4)\n        fields = [field for field in cls.ds.field_list if field[0] == \"stream\"]\n        field_labels = {f: f[1] for f in fields}\n        lines = []\n        lines.append(\n            yt.LineBuffer(cls.ds, [0.25, 0, 0], [0.25, 1, 0], 100, label=\"x = 0.5\")\n        )\n        lines.append(\n            yt.LineBuffer(cls.ds, [0.5, 0, 0], [0.5, 1, 0], 100, label=\"x = 0.5\")\n        )\n        plot = yt.LinePlot.from_lines(cls.ds, fields, lines, field_labels=field_labels)\n        plot.annotate_legend(fields[0])\n        plot.annotate_legend(fields[1])\n        plot.set_x_unit(\"cm\")\n        plot.set_unit(fields[0], \"kg/cm**3\")\n        plot.annotate_title(fields[0], \"Density Plot\")\n        plot.render()\n        cls.plot = plot\n\n    @pytest.mark.mpl_image_compare\n    def test_lineplot_multi_density(self):\n        return self.plot.plots[\"stream\", \"density\"].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_lineplot_multi_velocity_x(self):\n        return self.plot.plots[\"stream\", \"velocity_x\"].figure\n\n\ndef test_line_buffer():\n    ds = fake_random_ds(32)\n    lb = yt.LineBuffer(ds, (0, 0, 0), (1, 1, 1), 512, label=\"diag\")\n    lb[\"gas\", \"density\"]\n    lb[\"gas\", \"velocity_x\"]\n    assert_equal(lb[\"gas\", \"density\"].size, 512)\n    lb[\"gas\", \"density\"] = 0\n    assert_equal(lb[\"gas\", \"density\"], 0)\n    assert_equal(set(lb.keys()), {(\"gas\", \"density\"), (\"gas\", \"velocity_x\")})\n    del lb[\"gas\", \"velocity_x\"]\n    assert_equal(set(lb.keys()), {(\"gas\", \"density\")})\n\n\ndef test_validate_point():\n    ds = fake_random_ds(3)\n    with pytest.raises(RuntimeError, match=\"Input point must be array-like\"):\n        _validate_point(0, ds, start=True)\n\n    with pytest.raises(RuntimeError, match=\"Input point must be a 1D array\"):\n        _validate_point(ds.arr([[0], [1]], \"code_length\"), ds, start=True)\n\n    with pytest.raises(\n        RuntimeError, match=\"Input point must have an element for each dimension\"\n    ):\n        _validate_point(ds.arr([0, 1], \"code_length\"), ds, start=True)\n\n    ds = fake_random_ds([32, 32, 1])\n    _validate_point(ds.arr([0, 1], \"code_length\"), ds, start=True)\n    _validate_point(ds.arr([0, 1], \"code_length\"), ds)\n"
  },
  {
    "path": "yt/visualization/tests/test_mesh_slices.py",
    "content": "import numpy as np\nimport pytest\n\nimport yt\nfrom yt.testing import fake_hexahedral_ds, fake_tetrahedral_ds, small_fake_hexahedral_ds\nfrom yt.utilities.lib.geometry_utils import triangle_plane_intersect\nfrom yt.utilities.lib.mesh_triangulation import triangulate_indices\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass TestTetrahedral:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_tetrahedral_ds()\n        cls.mesh = cls.ds.index.meshes[0]\n        cls.ad = cls.ds.all_data()\n\n        cls.slices = [\n            cls.ds.slice(idir, cls.ds.domain_center[idir]) for idir in range(3)\n        ]\n        cls.sps = [yt.SlicePlot(cls.ds, idir, cls.ds.field_list) for idir in range(3)]\n        for sp in cls.sps:\n            sp.annotate_mesh_lines()\n            sp.set_log(\"all\", False)\n            sp.render()\n\n    @pytest.mark.parametrize(\"idir\", range(3))\n    def test_mesh_selection(self, idir):\n        sl_obj = self.slices[idir]\n        for field in self.ds.field_list:\n            assert sl_obj[field].shape[0] == self.mesh.count(sl_obj.selector)\n            assert sl_obj[field].shape[0] < self.ad[field].shape[0]\n\n    @pytest.mark.parametrize(\"ax\", range(3))\n    @pytest.mark.mpl_image_compare\n    def test_mesh_slice_tetraheadral_all_elem(self, ax):\n        return self.sps[ax].plots[\"all\", \"elem\"].figure\n\n    @pytest.mark.parametrize(\"ax\", range(3))\n    @pytest.mark.mpl_image_compare\n    def test_mesh_slice_tetraheadral_all_test(self, ax):\n        return self.sps[ax].plots[\"all\", \"test\"].figure\n\n    @pytest.mark.parametrize(\"ax\", range(3))\n    @pytest.mark.mpl_image_compare\n    def test_mesh_slice_tetraheadral_connect1_elem(self, ax):\n        return self.sps[ax].plots[\"connect1\", \"elem\"].figure\n\n    @pytest.mark.parametrize(\"ax\", range(3))\n    @pytest.mark.mpl_image_compare\n    def test_mesh_slice_tetraheadral_connect1_test(self, ax):\n        return self.sps[ax].plots[\"connect1\", \"test\"].figure\n\n\nclass TestHexahedral:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_hexahedral_ds()\n        cls.mesh = cls.ds.index.meshes[0]\n        cls.ad = cls.ds.all_data()\n\n        cls.slices = [\n            cls.ds.slice(idir, cls.ds.domain_center[idir]) for idir in range(3)\n        ]\n        cls.sps = [yt.SlicePlot(cls.ds, idir, cls.ds.field_list) for idir in range(3)]\n        for sp in cls.sps:\n            sp.annotate_mesh_lines()\n            sp.set_log(\"all\", False)\n            sp.render()\n\n    @pytest.mark.parametrize(\"idir\", range(3))\n    def test_mesh_selection(self, idir):\n        sl_obj = self.slices[idir]\n        for field in self.ds.field_list:\n            assert sl_obj[field].shape[0] == self.mesh.count(sl_obj.selector)\n            assert sl_obj[field].shape[0] < self.ad[field].shape[0]\n\n    @pytest.mark.parametrize(\"ax\", range(3))\n    @pytest.mark.mpl_image_compare\n    def test_mesh_slice_hexaheadral_all_elem(self, ax):\n        return self.sps[ax].plots[\"all\", \"elem\"].figure\n\n    @pytest.mark.parametrize(\"ax\", range(3))\n    @pytest.mark.mpl_image_compare\n    def test_mesh_slice_hexaheadral_all_test(self, ax):\n        return self.sps[ax].plots[\"all\", \"test\"].figure\n\n    @pytest.mark.parametrize(\"ax\", range(3))\n    @pytest.mark.mpl_image_compare\n    def test_mesh_slice_hexaheadral_connect1_elem(self, ax):\n        return self.sps[ax].plots[\"connect1\", \"elem\"].figure\n\n    @pytest.mark.parametrize(\"ax\", range(3))\n    @pytest.mark.mpl_image_compare\n    def test_mesh_slice_hexaheadral_connect1_test(self, ax):\n        return self.sps[ax].plots[\"connect1\", \"test\"].figure\n\n\ndef test_perfect_element_intersection():\n    # This test tests mesh line annotation where a z=0 slice\n    # perfectly intersects the top of a hexahedral element with node\n    # z-coordinates containing both -0 and +0. Before\n    # https://github.com/yt-project/yt/pull/1437 this test falsely\n    # yielded three annotation lines, whereas the correct result is four\n    # corresponding to the four edges of the top hex face.\n\n    ds = small_fake_hexahedral_ds()\n    indices = ds.index.meshes[0].connectivity_indices\n    coords = ds.index.meshes[0].connectivity_coords\n    tri_indices = triangulate_indices(indices)\n    tri_coords = coords[tri_indices]\n    lines = triangle_plane_intersect(2, 0, tri_coords)\n    non_zero_lines = 0\n    for i in range(lines.shape[0]):\n        norm = np.linalg.norm(lines[i][0] - lines[i][1])\n        if norm > 1e-8:\n            non_zero_lines += 1\n    np.testing.assert_equal(non_zero_lines, 4)\n"
  },
  {
    "path": "yt/visualization/tests/test_normal_plot_api.py",
    "content": "from itertools import product\n\nimport numpy as np\nimport pytest\n\nfrom yt.testing import fake_amr_ds\nfrom yt.visualization.plot_window import ProjectionPlot, SlicePlot\n\n\n@pytest.fixture(scope=\"module\")\ndef ds():\n    return fake_amr_ds(geometry=\"cartesian\")\n\n\n@pytest.mark.parametrize(\"plot_cls\", (SlicePlot, ProjectionPlot))\ndef test_normalplot_all_positional_args(ds, plot_cls):\n    plot_cls(ds, \"z\", (\"stream\", \"Density\"))\n\n\n@pytest.mark.parametrize(\"plot_cls\", (SlicePlot, ProjectionPlot))\ndef test_normalplot_normal_kwarg(ds, plot_cls):\n    plot_cls(ds, normal=\"z\", fields=(\"stream\", \"Density\"))\n\n\n@pytest.mark.parametrize(\"plot_cls\", (SlicePlot, ProjectionPlot))\ndef test_error_with_missing_fields_and_normal(ds, plot_cls):\n    with pytest.raises(\n        TypeError,\n        match=\"missing 2 required positional arguments: 'normal' and 'fields'\",\n    ):\n        plot_cls(ds)\n\n\n@pytest.mark.parametrize(\"plot_cls\", (SlicePlot, ProjectionPlot))\ndef test_error_with_missing_fields_with_normal_kwarg(ds, plot_cls):\n    with pytest.raises(\n        TypeError, match=r\"missing (1 )?required positional argument: 'fields'$\"\n    ):\n        plot_cls(ds, normal=\"z\")\n\n\n@pytest.mark.parametrize(\"plot_cls\", (SlicePlot, ProjectionPlot))\ndef test_error_with_missing_fields_with_positional(ds, plot_cls):\n    with pytest.raises(\n        TypeError, match=r\"missing (1 )?required positional argument: 'fields'$\"\n    ):\n        plot_cls(ds, \"z\")\n\n\n@pytest.mark.parametrize(\n    \"plot_cls, normal\",\n    list(\n        product(\n            [SlicePlot, ProjectionPlot], [(0, 0, 1), [0, 0, 1], np.array((0, 0, 1))]\n        )\n    ),\n)\ndef test_normalplot_normal_array(ds, plot_cls, normal):\n    # see regression https://github.com/yt-project/yt/issues/3736\n    plot_cls(ds, normal, fields=(\"stream\", \"Density\"))\n"
  },
  {
    "path": "yt/visualization/tests/test_offaxisprojection.py",
    "content": "import itertools as it\nimport os\nimport shutil\nimport tempfile\nimport unittest\n\nimport numpy as np\nfrom numpy.testing import assert_equal\n\nfrom yt.testing import (\n    assert_fname,\n    assert_rel_equal,\n    fake_octree_ds,\n    fake_random_ds,\n)\nfrom yt.visualization.api import (\n    OffAxisProjectionPlot,\n    OffAxisSlicePlot,\n    ProjectionPlot,\n)\nfrom yt.visualization.image_writer import write_projection\nfrom yt.visualization.volume_rendering.api import off_axis_projection\n\n\n# TODO: replace this with pytest.mark.parametrize\ndef expand_keywords(keywords, full=False):\n    \"\"\"\n    expand_keywords is a means for testing all possible keyword\n    arguments in the nosetests.  Simply pass it a dictionary of all the\n    keyword arguments and all of the values for these arguments that you\n    want to test.\n\n    It will return a list of kwargs dicts containing combinations of\n    the various kwarg values you passed it.  These can then be passed\n    to the appropriate function in nosetests.\n\n    If full=True, then every possible combination of keywords is produced,\n    otherwise, every keyword option is included at least once in the output\n    list.  Be careful, by using full=True, you may be in for an exponentially\n    larger number of tests!\n\n    Parameters\n    ----------\n\n    keywords : dict\n        a dictionary where the keys are the keywords for the function,\n        and the values of each key are the possible values that this key\n        can take in the function\n\n    full : bool\n        if set to True, every possible combination of given keywords is\n        returned\n\n    Returns\n    -------\n\n    array of dicts\n        An array of dictionaries to be individually passed to the appropriate\n        function matching these kwargs.\n\n    Examples\n    --------\n\n    >>> keywords = {}\n    >>> keywords[\"dpi\"] = (50, 100, 200)\n    >>> keywords[\"cmap\"] = (\"cmyt.arbre\", \"cmyt.kelp\")\n    >>> list_of_kwargs = expand_keywords(keywords)\n    >>> print(list_of_kwargs)\n\n    array([{'cmap': 'cmyt.arbre', 'dpi': 50},\n           {'cmap': 'cmyt.kelp', 'dpi': 100},\n           {'cmap': 'cmyt.arbre', 'dpi': 200}], dtype=object)\n\n    >>> list_of_kwargs = expand_keywords(keywords, full=True)\n    >>> print(list_of_kwargs)\n\n    array([{'cmap': 'cmyt.arbre', 'dpi': 50},\n           {'cmap': 'cmyt.arbre', 'dpi': 100},\n           {'cmap': 'cmyt.arbre', 'dpi': 200},\n           {'cmap': 'cmyt.kelp', 'dpi': 50},\n           {'cmap': 'cmyt.kelp', 'dpi': 100},\n           {'cmap': 'cmyt.kelp', 'dpi': 200}], dtype=object)\n\n    >>> for kwargs in list_of_kwargs:\n    ...     write_projection(*args, **kwargs)\n    \"\"\"\n    # if we want every possible combination of keywords, use iter magic\n    if full:\n        keys = sorted(keywords)\n        list_of_kwarg_dicts = np.array(\n            [\n                dict(zip(keys, prod, strict=True))\n                for prod in it.product(*(keywords[key] for key in keys))\n            ]\n        )\n\n    # if we just want to probe each keyword, but not necessarily every\n    # combination\n    else:\n        # Determine the maximum number of values any of the keywords has\n        num_lists = 0\n        for val in keywords.values():\n            if isinstance(val, str):\n                num_lists = max(1.0, num_lists)\n            else:\n                num_lists = max(len(val), num_lists)\n\n        # Construct array of kwargs dicts, each element of the list is a different\n        # **kwargs dict.  each kwargs dict gives a different combination of\n        # the possible values of the kwargs\n\n        # initialize array\n        list_of_kwarg_dicts = np.array([{} for x in range(num_lists)])\n\n        # fill in array\n        for i in np.arange(num_lists):\n            list_of_kwarg_dicts[i] = {}\n            for key in keywords.keys():\n                # if it's a string, use it (there's only one)\n                if isinstance(keywords[key], str):\n                    list_of_kwarg_dicts[i][key] = keywords[key]\n                # if there are more options, use the i'th val\n                elif i < len(keywords[key]):\n                    list_of_kwarg_dicts[i][key] = keywords[key][i]\n                # if there are not more options, use the 0'th val\n                else:\n                    list_of_kwarg_dicts[i][key] = keywords[key][0]\n\n    return list_of_kwarg_dicts\n\n\nclass TestOffAxisProjection(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        cls.tmpdir = tempfile.mkdtemp()\n        cls.curdir = os.getcwd()\n        os.chdir(cls.tmpdir)\n\n    @classmethod\n    def tearDownClass(cls):\n        os.chdir(cls.curdir)\n        shutil.rmtree(cls.tmpdir)\n\n    def test_oap(self):\n        \"\"\"Tests functionality of off_axis_projection and write_projection.\"\"\"\n\n        # args for off_axis_projection\n        test_ds = fake_random_ds(64)\n        c = test_ds.domain_center\n        norm = [0.5, 0.5, 0.5]\n        W = test_ds.arr([0.5, 0.5, 1.0], \"unitary\")\n        N = 256\n        field = (\"gas\", \"density\")\n        oap_args = [test_ds, c, norm, W, N, field]\n\n        # kwargs for off_axis_projection\n        oap_kwargs = {}\n        oap_kwargs[\"weight\"] = (None, \"cell_mass\")\n        oap_kwargs[\"no_ghost\"] = (True, False)\n        oap_kwargs[\"interpolated\"] = (False,)\n        oap_kwargs[\"north_vector\"] = ((1, 0, 0), (0, 0.5, 1.0))\n        oap_kwargs_list = expand_keywords(oap_kwargs)\n\n        # args or write_projection\n        fn = \"test_%d.png\"\n\n        # kwargs for write_projection\n        wp_kwargs = {}\n        wp_kwargs[\"colorbar\"] = (True, False)\n        wp_kwargs[\"colorbar_label\"] = \"test\"\n        wp_kwargs[\"title\"] = \"test\"\n        wp_kwargs[\"vmin\"] = (None,)\n        wp_kwargs[\"vmax\"] = (1e3, 1e5)\n        wp_kwargs[\"take_log\"] = (True, False)\n        wp_kwargs[\"figsize\"] = ((8, 6), [1, 1])\n        wp_kwargs[\"dpi\"] = (100, 50)\n        wp_kwargs[\"cmap_name\"] = (\"cmyt.arbre\", \"cmyt.kelp\")\n        wp_kwargs_list = expand_keywords(wp_kwargs)\n\n        # test all off_axis_projection kwargs and write_projection kwargs\n        # make sure they are able to be projected, then remove and try next\n        # iteration\n        for i, oap_kwargs in enumerate(oap_kwargs_list):\n            image = off_axis_projection(*oap_args, **oap_kwargs)\n            for wp_kwargs in wp_kwargs_list:\n                write_projection(image, fn % i, **wp_kwargs)\n                assert_fname(fn % i)\n\n        # Test remaining parameters of write_projection\n        write_projection(image, \"test_2\", xlabel=\"x-axis\", ylabel=\"y-axis\")\n        assert_fname(\"test_2.png\")\n\n        write_projection(image, \"test_3.pdf\", xlabel=\"x-axis\", ylabel=\"y-axis\")\n        assert_fname(\"test_3.pdf\")\n\n        write_projection(image, \"test_4.eps\", xlabel=\"x-axis\", ylabel=\"y-axis\")\n        assert_fname(\"test_4.eps\")\n\n\ndef test_field_cut_off_axis_octree():\n    ds = fake_octree_ds()\n    cut = ds.all_data().cut_region('obj[\"gas\", \"density\"]>0.5')\n    p1 = OffAxisProjectionPlot(ds, [1, 0, 0], (\"gas\", \"density\"))\n    p2 = OffAxisProjectionPlot(ds, [1, 0, 0], (\"gas\", \"density\"), data_source=cut)\n    assert_equal(p2.frb[\"gas\", \"density\"].min() == 0.0, True)  # Lots of zeros\n    assert_equal((p1.frb[\"gas\", \"density\"] == p2.frb[\"gas\", \"density\"]).all(), False)\n    p3 = OffAxisSlicePlot(ds, [1, 0, 0], (\"gas\", \"density\"))\n    p4 = OffAxisSlicePlot(ds, [1, 0, 0], (\"gas\", \"density\"), data_source=cut)\n    assert_equal((p3.frb[\"gas\", \"density\"] == p4.frb[\"gas\", \"density\"]).all(), False)\n    p4rho = p4.frb[\"gas\", \"density\"]\n    assert_equal(np.nanmin(p4rho[p4rho > 0.0]) >= 0.5, True)\n\n\ndef test_off_axis_octree():\n    np.random.seed(12345)\n    ds = fake_octree_ds()\n    center = [0.4, 0.4, 0.4]\n\n    for weight in [(\"gas\", \"cell_mass\"), None, (\"index\", \"dx\")]:\n        p1 = ProjectionPlot(\n            ds,\n            \"x\",\n            (\"gas\", \"density\"),\n            center=center,\n            width=0.8,\n            weight_field=weight,\n        )\n        p2 = OffAxisProjectionPlot(\n            ds,\n            [1, 0, 0],\n            (\"gas\", \"density\"),\n            center=center,\n            width=0.8,\n            weight_field=weight,\n        )\n\n        # Note: due to our implementation, the off-axis projection will have a\n        # slightly blurred cell edges so we can't do an exact comparison\n        v1, v2 = p1.frb[\"gas\", \"density\"], p2.frb[\"gas\", \"density\"]\n        diff = (v1 - v2) / (v1 + v2) * 2\n\n        # Make sure the difference has a small bias\n        assert np.mean(diff).max() < 1e-3  # 0.1%\n\n        # Compute 10-90% percentile\n        q10, q90 = np.percentile(diff, q=(10, 90))\n        assert q10 > -0.02  # 2%: little up/down deviations\n        assert q90 < +0.02  # 2%: little up/down deviations\n\n\ndef test_offaxis_moment():\n    ds = fake_random_ds(64)\n\n    def _vlos_sq(data):\n        return data[\"gas\", \"velocity_los\"] ** 2\n\n    ds.add_field(\n        (\"gas\", \"velocity_los_squared\"),\n        _vlos_sq,\n        sampling_type=\"local\",\n        units=\"cm**2/s**2\",\n    )\n    p1 = OffAxisProjectionPlot(\n        ds,\n        [1, 1, 1],\n        [(\"gas\", \"velocity_los\"), (\"gas\", \"velocity_los_squared\")],\n        weight_field=(\"gas\", \"density\"),\n        moment=1,\n        buff_size=(400, 400),\n    )\n    p2 = OffAxisProjectionPlot(\n        ds,\n        [1, 1, 1],\n        (\"gas\", \"velocity_los\"),\n        weight_field=(\"gas\", \"density\"),\n        moment=2,\n        buff_size=(400, 400),\n    )\n    ## this failed because some <v**2> - <v>**2 values come out\n    ## marginally < 0, resulting in unmatched NaN values in the\n    ## first assert_rel_equal argument. The compute_stddev_image\n    ## function used in OffAxisProjectionPlot checks for and deals\n    ## with these cases.\n    # assert_rel_equal(\n    #    np.sqrt(\n    #        p1.frb[\"gas\", \"velocity_los_squared\"] - p1.frb[\"gas\", \"velocity_los\"] ** 2\n    #    ),\n    #    p2.frb[\"gas\", \"velocity_los\"],\n    #    10,\n    # )\n    p1_expsq = p1.frb[\"gas\", \"velocity_los_squared\"]\n    p1_sqexp = p1.frb[\"gas\", \"velocity_los\"] ** 2\n    # set values to zero that have <v>**2 - <v>**2 < 0, but\n    # the absolute values are much smaller than the smallest\n    # postive values of <v>**2 and <v>**2\n    # (i.e., the difference is pretty much zero)\n    mindiff = 1e-10 * min(\n        np.min(p1_expsq[p1_expsq > 0]), np.min(p1_sqexp[p1_sqexp > 0])\n    )\n    # print(mindiff)\n    safeorbad = np.logical_not(\n        np.logical_and(p1_expsq - p1_sqexp < 0, p1_expsq - p1_sqexp > -1.0 * mindiff)\n    )\n    # avoid errors from sqrt(negative)\n    # sqrt in zeros_like insures correct units\n    p1res = np.zeros_like(np.sqrt(p1_expsq))\n    p1res[safeorbad] = np.sqrt(p1_expsq[safeorbad] - p1_sqexp[safeorbad])\n    p2res = p2.frb[\"gas\", \"velocity_los\"]\n    assert_rel_equal(p1res, p2res, 10)\n\n\ndef test_two_offaxis():\n    ds = fake_octree_ds()\n\n    vec_proj = [1, 0, 0]\n    los_unit_vector = np.array(vec_proj) / np.linalg.norm(vec_proj)\n\n    # define a new yt velocity field relative to the bulk velocity of the halo and along the line of sight\n    def _velocity_los_test(field, data):\n        v = np.stack(\n            [data[\"gas\", f\"velocity_{k}\"].to(\"km/s\").d for k in \"xyz\"], axis=-1\n        )\n        v_los = np.dot(v, los_unit_vector)\n        return data.apply_units(v_los, \"km/s\")\n\n    ds.add_field(\n        (\"gas\", \"velocity_los_test\"),\n        function=_velocity_los_test,\n        units=\"km/s\",\n        sampling_type=\"cell\",\n        display_name=f\"Velocity LOS along {vec_proj}\",\n    )\n\n    p = OffAxisProjectionPlot(\n        ds,\n        [1.0, 0.0, 0.0],\n        [(\"gas\", \"velocity_x\"), (\"gas\", \"velocity_los_test\")],\n        weight_field=\"dx\",\n    )\n\n    v1 = p.frb[\"gas\", \"velocity_x\"]\n    v2 = p.frb[\"gas\", \"velocity_los_test\"]\n\n    np.testing.assert_allclose(v1, v2.to(v1.units))\n"
  },
  {
    "path": "yt/visualization/tests/test_offaxisprojection_pytestonly.py",
    "content": "import numpy as np\nimport pytest\nimport unyt\nfrom numpy.testing import assert_allclose\n\nfrom yt.testing import (\n    assert_rel_equal,\n    cubicspline_python,\n    fake_amr_ds,\n    fake_sph_flexible_grid_ds,\n    fake_sph_grid_ds,\n    integrate_kernel,\n    requires_module_pytest,\n)\nfrom yt.visualization.api import OffAxisProjectionPlot, ProjectionPlot\n\n\n@pytest.mark.parametrize(\"weighted\", [True, False])\n@pytest.mark.parametrize(\"periodic\", [True, False])\n@pytest.mark.parametrize(\"depth\", [None, (1.0, \"cm\"), (0.5, \"cm\")])\n@pytest.mark.parametrize(\"shiftcenter\", [False, True])\n@pytest.mark.parametrize(\"northvector\", [None, (1.0e-4, 1.0, 0.0)])\ndef test_sph_proj_general_offaxis(\n    northvector: tuple[float, float, float] | None,\n    shiftcenter: bool,\n    depth: tuple[float, str] | None,\n    periodic: bool,\n    weighted: bool,\n) -> None:\n    \"\"\"\n    Same as the on-axis projections, but we rotate the basis vectors\n    to test whether roations are handled ok. the rotation is chosen to\n    be small so that in/exclusion of particles within bboxes, etc.\n    works out the same way.\n    We just send lines of sight through pixel centers for convenience.\n    Particles at [0.5, 1.5, 2.5] (in each coordinate)\n    smoothing lengths 0.25\n    all particles have mass 1., density 1.5,\n    except the single center particle, with mass 2., density 3.\n\n    Parameters:\n    -----------\n    northvector: tuple\n        y-axis direction in the final plot (direction vector)\n    shiftcenter: bool\n        shift the coordinates to center the projection on.\n        (The grid is offset to this same center)\n    depth: float or None\n        depth of the projection slice\n    periodic: bool\n        assume periodic boundary conditions, or not\n    weighted: bool\n        make a weighted projection (density-weighted density), or not\n\n    Returns:\n    --------\n    None\n    \"\"\"\n    if shiftcenter:\n        center = np.array((0.625, 0.625, 0.625))  # cm\n    else:\n        center = np.array((1.5, 1.5, 1.5))  # cm\n    bbox = unyt.unyt_array(np.array([[0.0, 3.0], [0.0, 3.0], [0.0, 3.0]]), \"cm\")\n    hsml_factor = 0.5\n    unitrho = 1.5\n\n    # test correct centering, particle selection\n    def makemasses(i, j, k):\n        if i == j == k == 1:\n            return 2.0\n        else:\n            return 1.0\n\n    # result shouldn't depend explicitly on the center if we re-center\n    # the data, unless we get cut-offs in the non-periodic case\n    # *almost* the z-axis\n    # try to make sure dl differences from periodic wrapping are small\n    epsilon = 1e-4\n    projaxis = np.array([epsilon, 0.00, np.sqrt(1.0 - epsilon**2)])\n    e1dir = projaxis / np.sqrt(np.sum(projaxis**2))\n    # TODO: figure out other (default) axes for basis vectors here\n    if northvector is None:\n        e2dir = np.array([0.0, 1.0, 0.0])\n    else:\n        e2dir = np.asarray(northvector)\n    e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir  # orthonormalize\n    e2dir /= np.sqrt(np.sum(e2dir**2))\n    e3dir = np.cross(e1dir, e2dir)\n\n    ds = fake_sph_flexible_grid_ds(\n        hsml_factor=hsml_factor,\n        nperside=3,\n        periodic=periodic,\n        offsets=np.full(3, 0.5),\n        massgenerator=makemasses,\n        unitrho=unitrho,\n        bbox=bbox.v,\n        recenter=center,\n        e1hat=e1dir,\n        e2hat=e2dir,\n        e3hat=e3dir,\n    )\n\n    source = ds.all_data()\n    # couple to dataset -> right unit registry\n    center = ds.arr(center, \"cm\")\n    # print('position:\\n', source['gas','position'])\n\n    # m / rho, factor 1. / hsml**2 is included in the kernel integral\n    # (density is adjusted, so same for center particle)\n    prefactor = 1.0 / unitrho  # / (0.5 * 0.5)**2\n    dl_cen = integrate_kernel(cubicspline_python, 0.0, 0.25)\n\n    if weighted:\n        toweight_field = (\"gas\", \"density\")\n    else:\n        toweight_field = None\n    # we don't actually want a plot, it's just a straightforward,\n    # common way to get an frb / image array\n    prj = ProjectionPlot(\n        ds,\n        projaxis,\n        (\"gas\", \"density\"),\n        width=(2.5, \"cm\"),\n        weight_field=toweight_field,\n        buff_size=(5, 5),\n        center=center,\n        data_source=source,\n        north_vector=northvector,\n        depth=depth,\n    )\n    img = prj.frb.data[\"gas\", \"density\"]\n    if weighted:\n        # periodic shifts will modify the (relative) dl values a bit\n        expected_out = np.zeros(\n            (\n                5,\n                5,\n            ),\n            dtype=img.v.dtype,\n        )\n        expected_out[::2, ::2] = unitrho\n        if depth is None:\n            expected_out[2, 2] *= 1.5\n        else:\n            # only 2 * unitrho element included\n            expected_out[2, 2] *= 2.0\n    else:\n        expected_out = np.zeros(\n            (\n                5,\n                5,\n            ),\n            dtype=img.v.dtype,\n        )\n        expected_out[::2, ::2] = dl_cen * prefactor * unitrho\n        if depth is None:\n            # 3 particles per l.o.s., including the denser one\n            expected_out *= 3.0\n            expected_out[2, 2] *= 4.0 / 3.0\n        else:\n            # 1 particle per l.o.s., including the denser one\n            expected_out[2, 2] *= 2.0\n    # grid is shifted to the left -> 'missing' stuff at the left\n    if (not periodic) and shiftcenter:\n        expected_out[:1, :] = 0.0\n        expected_out[:, :1] = 0.0\n    # print(axis, shiftcenter, depth, periodic, weighted)\n    # print(\"expected:\\n\", expected_out)\n    # print(\"recovered:\\n\", img.v)\n    assert_rel_equal(expected_out, img.v, 4)\n\n\n_diag_dist = np.sqrt(3.0)  # diagonal distance of a cube with length 1.\n# each case is depth, center, expected integrated distance\n_cases_to_test = [\n    (_diag_dist / 3.0, \"domain_left_edge\", _diag_dist / 3.0 / 2.0),\n    (_diag_dist * 2.0, \"domain_left_edge\", _diag_dist),\n    (_diag_dist * 4.0, \"domain_left_edge\", _diag_dist),\n    (None, \"domain_center\", _diag_dist),\n]\n\n\n@pytest.mark.parametrize(\"depth,proj_center,expected\", _cases_to_test)\ndef test_offaxisprojection_depth(depth, proj_center, expected):\n    # this checks that the depth keyword argument works as expected.\n    # in all cases, it integrates the (index, ones) field for a normal\n    # pointing to the right edge corner of the domain.\n    #\n    # For the tests where the projection is centered on the left edge,\n    # the integrate distance will scale as depth / 2.0. When centered\n    # on the origin, it will scale with depth. The integrated distance\n    # should max out at the diagonal distance of the cube (when the depth\n    # exceeds the cube diagonal distance).\n    #\n    # Also note that the accuracy will depend on the buffer dimensions:\n    # using the default (800,800) results in accuracy of about 1 percent\n\n    ds = fake_amr_ds()\n\n    n = [1.0, 1.0, 1.0]\n    c = getattr(ds, proj_center)\n    field = (\"index\", \"ones\")\n\n    p = ProjectionPlot(ds, n, field, depth=depth, weight_field=None, center=c)\n\n    maxval = p.frb[field].max().d\n    assert_allclose(expected, maxval, atol=1e-2)\n\n\n_sph_test_cases = [\n    ([1.0, 1.0, 0.7], 27),\n    ([1.0, 1.0, 1], 19),\n    ([0.0, 0.0, 1], 9),\n]\n\n\n@requires_module_pytest(\"contourpy\")\n@pytest.mark.parametrize(\"normal_vec, n_particles\", _sph_test_cases)\ndef test_offaxisprojection_sph_defaultdepth(normal_vec, n_particles):\n    # checks that particles are picked up as expected for a range of\n    # depths and normal vectors. Certain viewing angles will result\n    # in overlapping particles (since fake_sph_grid_ds aligns particles\n    # on a grid): n_particles is the expected number of circles in the\n    # resulting image for the given normal vector. Circle counts are\n    # calculated here using a contour generator.\n    from contourpy import contour_generator\n\n    ds = fake_sph_grid_ds()\n    c = ds.domain_center\n    diag_dist = np.linalg.norm(ds.domain_width)\n    field = (\"gas\", \"mass\")\n    p = OffAxisProjectionPlot(\n        ds, normal_vec, field, weight_field=None, center=c, width=diag_dist\n    )\n    p.render()\n\n    # get the number of circles in the plot\n    cg = contour_generator(z=p.frb[\"gas\", \"mass\"].d)\n    assert n_particles == len(cg.lines(1.0))\n"
  },
  {
    "path": "yt/visualization/tests/test_particle_plot.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\nfrom unittest import mock\n\nimport numpy as np\nfrom numpy.testing import assert_allclose, assert_array_almost_equal\n\nfrom yt.data_objects.particle_filters import add_particle_filter\nfrom yt.data_objects.profiles import create_profile\nfrom yt.loaders import load\nfrom yt.testing import fake_particle_ds, requires_file\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.answer_testing.framework import (\n    PhasePlotAttributeTest,\n    PlotWindowAttributeTest,\n    data_dir_load,\n    requires_ds,\n)\nfrom yt.visualization.api import ParticlePhasePlot, ParticlePlot, ParticleProjectionPlot\nfrom yt.visualization.tests.test_plotwindow import ATTR_ARGS, WIDTH_SPECS\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\n#  override some of the plotwindow ATTR_ARGS\nPROJ_ATTR_ARGS = ATTR_ARGS.copy()\nPROJ_ATTR_ARGS[\"set_cmap\"] = [\n    (((\"all\", \"particle_mass\"), \"RdBu\"), {}),\n    (((\"all\", \"particle_mass\"), \"cmyt.pastel\"), {}),\n]\nPROJ_ATTR_ARGS[\"set_log\"] = [(((\"all\", \"particle_mass\"), False), {})]\nPROJ_ATTR_ARGS[\"set_zlim\"] = [\n    (((\"all\", \"particle_mass\"), 1e39, 1e42), {}),\n    (((\"all\", \"particle_mass\"),), {\"zmin\": 1e39, \"dynamic_range\": 4}),\n]\n\nPHASE_ATTR_ARGS = {\n    \"annotate_text\": [\n        (((5e-29, 5e7), \"Hello YT\"), {}),\n        (((5e-29, 5e7), \"Hello YT\"), {\"color\": \"b\"}),\n    ],\n    \"set_title\": [(((\"all\", \"particle_mass\"), \"A phase plot.\"), {})],\n    \"set_log\": [(((\"all\", \"particle_mass\"), False), {})],\n    \"set_unit\": [(((\"all\", \"particle_mass\"), \"Msun\"), {})],\n    \"set_xlim\": [((-4e7, 4e7), {})],\n    \"set_ylim\": [((-4e7, 4e7), {})],\n}\n\nTEST_FLNMS = [None, \"test\", \"test.png\", \"test.eps\", \"test.ps\", \"test.pdf\"]\n\nCENTER_SPECS = (\n    \"c\",\n    \"C\",\n    \"center\",\n    \"Center\",\n    [0.5, 0.5, 0.5],\n    [[0.2, 0.3, 0.4], \"cm\"],\n    YTArray([0.3, 0.4, 0.7], \"cm\"),\n)\n\nWEIGHT_FIELDS = (None, (\"all\", \"particle_ones\"), (\"all\", \"particle_mass\"))\n\nPHASE_FIELDS = [\n    (\n        (\"all\", \"particle_velocity_x\"),\n        (\"all\", \"particle_position_z\"),\n        (\"all\", \"particle_mass\"),\n    ),\n    (\n        (\"all\", \"particle_position_x\"),\n        (\"all\", \"particle_position_y\"),\n        (\"all\", \"particle_ones\"),\n    ),\n    (\n        (\"all\", \"particle_velocity_x\"),\n        (\"all\", \"particle_velocity_y\"),\n        [(\"all\", \"particle_mass\"), (\"all\", \"particle_ones\")],\n    ),\n]\n\n\ng30 = \"IsolatedGalaxy/galaxy0030/galaxy0030\"\n\n\n@requires_ds(g30, big_data=True)\ndef test_particle_projection_answers():\n    \"\"\"\n\n    This iterates over the all the plot modification functions in\n    PROJ_ATTR_ARGS. Each time, it compares the images produced by\n    ParticleProjectionPlot to the gold standard.\n\n\n    \"\"\"\n\n    plot_field = (\"all\", \"particle_mass\")\n    decimals = 12\n    ds = data_dir_load(g30)\n    for ax in \"xyz\":\n        for attr_name in PROJ_ATTR_ARGS.keys():\n            for args in PROJ_ATTR_ARGS[attr_name]:\n                test = PlotWindowAttributeTest(\n                    ds,\n                    plot_field,\n                    ax,\n                    attr_name,\n                    args,\n                    decimals,\n                    \"ParticleProjectionPlot\",\n                )\n                test_particle_projection_answers.__name__ = test.description\n                yield test\n\n\n@requires_ds(g30, big_data=True)\ndef test_particle_offaxis_projection_answers():\n    plot_field = (\"all\", \"particle_mass\")\n    decimals = 12\n    ds = data_dir_load(g30)\n    attr_name = \"set_cmap\"\n    attr_args = (((\"all\", \"particle_mass\"), \"RdBu\"), {})\n    L = [1, 1, 1]\n    test = PlotWindowAttributeTest(\n        ds,\n        plot_field,\n        L,\n        attr_name,\n        attr_args,\n        decimals,\n        \"ParticleProjectionPlot\",\n    )\n    test_particle_offaxis_projection_answers.__name__ = test.description\n    yield test\n\n\n@requires_ds(g30, big_data=True)\ndef test_particle_projection_filter():\n    \"\"\"\n\n    This tests particle projection plots for filter fields.\n\n\n    \"\"\"\n\n    def formed_star(pfilter, data):\n        filter = data[\"all\", \"creation_time\"] > 0\n        return filter\n\n    add_particle_filter(\n        \"formed_star\",\n        function=formed_star,\n        filtered_type=\"all\",\n        requires=[\"creation_time\"],\n    )\n\n    plot_field = (\"formed_star\", \"particle_mass\")\n\n    decimals = 12\n    ds = data_dir_load(g30)\n    ds.add_particle_filter(\"formed_star\")\n    for ax in \"xyz\":\n        attr_name = \"set_log\"\n        test = PlotWindowAttributeTest(\n            ds,\n            plot_field,\n            ax,\n            attr_name,\n            ((plot_field, False), {}),\n            decimals,\n            \"ParticleProjectionPlot\",\n        )\n        test_particle_projection_filter.__name__ = test.description\n        yield test\n\n\n@requires_ds(g30, big_data=True)\ndef test_particle_phase_answers():\n    \"\"\"\n\n    This iterates over the all the plot modification functions in\n    PHASE_ATTR_ARGS. Each time, it compares the images produced by\n    ParticlePhasePlot to the gold standard.\n\n    \"\"\"\n\n    decimals = 12\n    ds = data_dir_load(g30)\n\n    x_field = (\"all\", \"particle_velocity_x\")\n    y_field = (\"all\", \"particle_velocity_y\")\n    z_field = (\"all\", \"particle_mass\")\n    for attr_name in PHASE_ATTR_ARGS.keys():\n        for args in PHASE_ATTR_ARGS[attr_name]:\n            test = PhasePlotAttributeTest(\n                ds,\n                x_field,\n                y_field,\n                z_field,\n                attr_name,\n                args,\n                decimals,\n                \"ParticlePhasePlot\",\n            )\n\n            test_particle_phase_answers.__name__ = test.description\n            yield test\n\n\nclass TestParticlePhasePlotSave(unittest.TestCase):\n    def setUp(self):\n        self.tmpdir = tempfile.mkdtemp()\n        self.curdir = os.getcwd()\n        os.chdir(self.tmpdir)\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        shutil.rmtree(self.tmpdir)\n\n    def test_particle_phase_plot(self):\n        test_ds = fake_particle_ds()\n        data_sources = [\n            test_ds.region([0.5] * 3, [0.4] * 3, [0.6] * 3),\n            test_ds.all_data(),\n        ]\n        particle_phases = []\n\n        for source in data_sources:\n            for x_field, y_field, z_fields in PHASE_FIELDS:\n                particle_phases.append(\n                    ParticlePhasePlot(\n                        source,\n                        x_field,\n                        y_field,\n                        z_fields,\n                        x_bins=16,\n                        y_bins=16,\n                    )\n                )\n\n                particle_phases.append(\n                    ParticlePhasePlot(\n                        source,\n                        x_field,\n                        y_field,\n                        z_fields,\n                        x_bins=16,\n                        y_bins=16,\n                        deposition=\"cic\",\n                    )\n                )\n\n                pp = create_profile(\n                    source,\n                    [x_field, y_field],\n                    z_fields,\n                    weight_field=(\"all\", \"particle_ones\"),\n                    n_bins=[16, 16],\n                )\n\n                particle_phases.append(ParticlePhasePlot.from_profile(pp))\n        particle_phases[0]._repr_html_()\n\n        with (\n            mock.patch(\"matplotlib.backends.backend_agg.FigureCanvasAgg.print_figure\"),\n            mock.patch(\"matplotlib.backends.backend_pdf.FigureCanvasPdf.print_figure\"),\n            mock.patch(\"matplotlib.backends.backend_ps.FigureCanvasPS.print_figure\"),\n        ):\n            for p in particle_phases:\n                for fname in TEST_FLNMS:\n                    p.save(fname)\n\n\ntgal = \"TipsyGalaxy/galaxy.00300\"\n\n\n@requires_file(tgal)\ndef test_particle_phase_plot_semantics():\n    ds = load(tgal)\n    ad = ds.all_data()\n    dens_ex = ad.quantities.extrema((\"Gas\", \"density\"))\n    temp_ex = ad.quantities.extrema((\"Gas\", \"temperature\"))\n    plot = ParticlePlot(\n        ds, (\"Gas\", \"density\"), (\"Gas\", \"temperature\"), (\"Gas\", \"particle_mass\")\n    )\n    plot.set_log((\"Gas\", \"density\"), True)\n    plot.set_log((\"Gas\", \"temperature\"), True)\n    p = plot.profile\n\n    # bin extrema are field extrema\n    assert dens_ex[0] - np.spacing(dens_ex[0]) == p.x_bins[0]\n    assert dens_ex[-1] + np.spacing(dens_ex[-1]) == p.x_bins[-1]\n    assert temp_ex[0] - np.spacing(temp_ex[0]) == p.y_bins[0]\n    assert temp_ex[-1] + np.spacing(temp_ex[-1]) == p.y_bins[-1]\n\n    # bins are evenly spaced in log space\n    logxbins = np.log10(p.x_bins)\n    dxlogxbins = logxbins[1:] - logxbins[:-1]\n    assert_allclose(dxlogxbins, dxlogxbins[0])\n\n    logybins = np.log10(p.y_bins)\n    dylogybins = logybins[1:] - logybins[:-1]\n    assert_allclose(dylogybins, dylogybins[0])\n\n    plot.set_log((\"Gas\", \"density\"), False)\n    plot.set_log((\"Gas\", \"temperature\"), False)\n    p = plot.profile\n\n    # bin extrema are field extrema\n    assert dens_ex[0] - np.spacing(dens_ex[0]) == p.x_bins[0]\n    assert dens_ex[-1] + np.spacing(dens_ex[-1]) == p.x_bins[-1]\n    assert temp_ex[0] - np.spacing(temp_ex[0]) == p.y_bins[0]\n    assert temp_ex[-1] + np.spacing(temp_ex[-1]) == p.y_bins[-1]\n\n    # bins are evenly spaced in log space\n    dxbins = p.x_bins[1:] - p.x_bins[:-1]\n    assert_allclose(dxbins, dxbins[0])\n\n    dybins = p.y_bins[1:] - p.y_bins[:-1]\n    assert_allclose(dybins, dybins[0])\n\n\n@requires_file(tgal)\ndef test_set_units():\n    ds = load(tgal)\n    sp = ds.sphere(\"max\", (1.0, \"Mpc\"))\n    pp = ParticlePhasePlot(\n        sp, (\"Gas\", \"density\"), (\"Gas\", \"temperature\"), (\"Gas\", \"particle_mass\")\n    )\n    # make sure we can set the units using the tuple without erroring out\n    pp.set_unit((\"Gas\", \"particle_mass\"), \"Msun\")\n\n\n@requires_file(tgal)\ndef test_switch_ds():\n    \"\"\"\n    Tests the _switch_ds() method for ParticleProjectionPlots that as of\n    25th October 2017 requires a specific hack in plot_container.py\n    \"\"\"\n    ds = load(tgal)\n    ds2 = load(tgal)\n\n    plot = ParticlePlot(\n        ds,\n        (\"Gas\", \"particle_position_x\"),\n        (\"Gas\", \"particle_position_y\"),\n        (\"Gas\", \"density\"),\n    )\n\n    plot._switch_ds(ds2)\n\n    return\n\n\nclass TestParticleProjectionPlotSave(unittest.TestCase):\n    def setUp(self):\n        self.tmpdir = tempfile.mkdtemp()\n        self.curdir = os.getcwd()\n        os.chdir(self.tmpdir)\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        shutil.rmtree(self.tmpdir)\n\n    def test_particle_plot(self):\n        test_ds = fake_particle_ds()\n        particle_projs = []\n        for dim in range(3):\n            particle_projs += [\n                ParticleProjectionPlot(test_ds, dim, (\"all\", \"particle_mass\")),\n                ParticleProjectionPlot(\n                    test_ds, dim, (\"all\", \"particle_mass\"), deposition=\"cic\"\n                ),\n                ParticleProjectionPlot(\n                    test_ds, dim, (\"all\", \"particle_mass\"), density=True\n                ),\n            ]\n        particle_projs[0]._repr_html_()\n        with (\n            mock.patch(\"matplotlib.backends.backend_agg.FigureCanvasAgg.print_figure\"),\n            mock.patch(\"matplotlib.backends.backend_pdf.FigureCanvasPdf.print_figure\"),\n            mock.patch(\"matplotlib.backends.backend_ps.FigureCanvasPS.print_figure\"),\n        ):\n            for p in particle_projs:\n                for fname in TEST_FLNMS:\n                    p.save(fname)[0]\n\n    def test_particle_plot_ds(self):\n        test_ds = fake_particle_ds()\n        ds_region = test_ds.region([0.5] * 3, [0.4] * 3, [0.6] * 3)\n        for dim in range(3):\n            pplot_ds = ParticleProjectionPlot(\n                test_ds, dim, (\"all\", \"particle_mass\"), data_source=ds_region\n            )\n            with mock.patch(\n                \"matplotlib.backends.backend_agg.FigureCanvasAgg.print_figure\"\n            ):\n                pplot_ds.save()\n\n    def test_particle_plot_c(self):\n        test_ds = fake_particle_ds()\n        for center in CENTER_SPECS:\n            for dim in range(3):\n                pplot_c = ParticleProjectionPlot(\n                    test_ds, dim, (\"all\", \"particle_mass\"), center=center\n                )\n                with mock.patch(\n                    \"matplotlib.backends.backend_agg.FigureCanvasAgg.print_figure\"\n                ):\n                    pplot_c.save()\n\n    def test_particle_plot_wf(self):\n        test_ds = fake_particle_ds()\n        for dim in range(3):\n            for weight_field in WEIGHT_FIELDS:\n                pplot_wf = ParticleProjectionPlot(\n                    test_ds, dim, (\"all\", \"particle_mass\"), weight_field=weight_field\n                )\n                with mock.patch(\n                    \"matplotlib.backends.backend_agg.FigureCanvasAgg.print_figure\"\n                ):\n                    pplot_wf.save()\n\n    def test_particle_plot_offaxis(self):\n        test_ds = fake_particle_ds()\n        Ls = [[1, 1, 1], [0, 1, -0.5]]\n        Ns = [None, [1, 1, 1]]\n        for L, N in zip(Ls, Ns, strict=True):\n            for weight_field in WEIGHT_FIELDS:\n                pplot_off = ParticleProjectionPlot(\n                    test_ds,\n                    L,\n                    (\"all\", \"particle_mass\"),\n                    north_vector=N,\n                    weight_field=weight_field,\n                )\n                with mock.patch(\n                    \"matplotlib.backends.backend_agg.FigureCanvasAgg.print_figure\"\n                ):\n                    pplot_off.save()\n\n    def test_creation_with_width(self):\n        test_ds = fake_particle_ds()\n        for width, (xlim, ylim, pwidth, _aun) in WIDTH_SPECS.items():\n            plot = ParticleProjectionPlot(\n                test_ds, 0, (\"all\", \"particle_mass\"), width=width\n            )\n\n            xlim = [plot.ds.quan(el[0], el[1]) for el in xlim]\n            ylim = [plot.ds.quan(el[0], el[1]) for el in ylim]\n            pwidth = [plot.ds.quan(el[0], el[1]) for el in pwidth]\n\n            for px, x in zip(plot.xlim, xlim, strict=True):\n                assert_array_almost_equal(px, x, 14)\n            for py, y in zip(plot.ylim, ylim, strict=True):\n                assert_array_almost_equal(py, y, 14)\n            for pw, w in zip(plot.width, pwidth, strict=True):\n                assert_array_almost_equal(pw, w, 14)\n\n\ndef test_particle_plot_instance():\n    \"\"\"\n    Tests the type of plot instance returned by ParticlePlot.\n\n    If x_field and y_field are any combination of valid particle_position in x,\n    y or z axis,then ParticleProjectionPlot instance is expected.\n\n\n    \"\"\"\n    ds = fake_particle_ds()\n    x_field = (\"all\", \"particle_position_x\")\n    y_field = (\"all\", \"particle_position_y\")\n    z_field = (\"all\", \"particle_velocity_x\")\n\n    plot = ParticlePlot(ds, x_field, y_field)\n    assert isinstance(plot, ParticleProjectionPlot)\n\n    plot = ParticlePlot(ds, y_field, x_field)\n    assert isinstance(plot, ParticleProjectionPlot)\n\n    plot = ParticlePlot(ds, x_field, z_field)\n    assert isinstance(plot, ParticlePhasePlot)\n"
  },
  {
    "path": "yt/visualization/tests/test_plot_modifications.py",
    "content": "from yt.testing import requires_file\nfrom yt.utilities.answer_testing.framework import data_dir_load\nfrom yt.visualization.plot_window import SlicePlot\n\n\n@requires_file(\"amrvac/bw_3d0000.dat\")\ndef test_code_units_xy_labels():\n    ds = data_dir_load(\"amrvac/bw_3d0000.dat\", kwargs={\"unit_system\": \"code\"})\n    p = SlicePlot(ds, \"x\", (\"gas\", \"density\"))\n\n    ax = p.plots[\"gas\", \"density\"].axes\n    assert \"code length\" in ax.get_xlabel().replace(\"\\\\\", \"\")\n    assert \"code length\" in ax.get_ylabel().replace(\"\\\\\", \"\")\n"
  },
  {
    "path": "yt/visualization/tests/test_plotwindow.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\nfrom collections import OrderedDict\n\nimport numpy as np\nfrom matplotlib.colors import LogNorm, Normalize, SymLogNorm\nfrom numpy.testing import (\n    assert_array_almost_equal,\n    assert_array_equal,\n    assert_equal,\n    assert_raises,\n)\nfrom unyt import unyt_array\n\nfrom yt.loaders import load_uniform_grid\nfrom yt.testing import (\n    assert_allclose_units,\n    assert_fname,\n    assert_rel_equal,\n    fake_amr_ds,\n    fake_random_ds,\n    requires_file,\n    requires_module,\n)\nfrom yt.units import kboltz\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.answer_testing.framework import (\n    PlotWindowAttributeTest,\n    data_dir_load,\n    requires_ds,\n)\nfrom yt.utilities.exceptions import YTInvalidFieldType\nfrom yt.visualization.plot_window import (\n    AxisAlignedProjectionPlot,\n    AxisAlignedSlicePlot,\n    NormalPlot,\n    OffAxisProjectionPlot,\n    OffAxisSlicePlot,\n    ProjectionPlot,\n    SlicePlot,\n    plot_2d,\n)\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nTEST_FLNMS = [\"test.png\"]\nM7 = \"DD0010/moving7_0010\"\n\nFPROPS = {\"family\": \"sans-serif\", \"style\": \"italic\", \"weight\": \"bold\", \"size\": 24}\n\nATTR_ARGS = {\n    \"pan\": [(((0.1, 0.1),), {})],\n    \"pan_rel\": [(((0.1, 0.1),), {})],\n    \"set_axes_unit\": [\n        ((\"kpc\",), {}),\n        ((\"Mpc\",), {}),\n        (((\"kpc\", \"kpc\"),), {}),\n        (((\"kpc\", \"Mpc\"),), {}),\n    ],\n    \"set_buff_size\": [((1600,), {}), (((600, 800),), {})],\n    \"set_center\": [(((0.4, 0.3),), {})],\n    \"set_cmap\": [((\"density\", \"RdBu\"), {}), ((\"density\", \"cmyt.pastel\"), {})],\n    \"set_font\": [((OrderedDict(sorted(FPROPS.items(), key=lambda t: t[0])),), {})],\n    \"set_log\": [((\"density\", False), {})],\n    \"set_figure_size\": [((7.0,), {})],\n    \"set_zlim\": [\n        ((\"density\", 1e-25, 1e-23), {}),\n        ((\"density\",), {\"zmin\": 1e-25, \"dynamic_range\": 4}),\n    ],\n    \"zoom\": [((10,), {})],\n}\n\n\nCENTER_SPECS = (\n    \"m\",\n    \"M\",\n    \"max\",\n    \"Max\",\n    \"min\",\n    \"Min\",\n    \"c\",\n    \"C\",\n    \"center\",\n    \"Center\",\n    \"left\",\n    \"right\",\n    [0.5, 0.5, 0.5],\n    [[0.2, 0.3, 0.4], \"cm\"],\n    YTArray([0.3, 0.4, 0.7], \"cm\"),\n    (\"max\", (\"gas\", \"density\")),\n    (\"min\", (\"gas\", \"density\")),\n)\n\nWIDTH_SPECS = {\n    # Width choices map to xlim, ylim, width, axes_unit_name 4-tuples\n    None: (\n        ((0, \"code_length\"), (1, \"code_length\")),\n        ((0, \"code_length\"), (1, \"code_length\")),\n        ((1, \"code_length\"), (1, \"code_length\")),\n        None,\n    ),\n    0.2: (\n        ((0.4, \"code_length\"), (0.6, \"code_length\")),\n        ((0.4, \"code_length\"), (0.6, \"code_length\")),\n        ((0.2, \"code_length\"), (0.2, \"code_length\")),\n        None,\n    ),\n    (0.4, 0.3): (\n        ((0.3, \"code_length\"), (0.7, \"code_length\")),\n        ((0.35, \"code_length\"), (0.65, \"code_length\")),\n        ((0.4, \"code_length\"), (0.3, \"code_length\")),\n        None,\n    ),\n    (1.2, \"cm\"): (\n        ((-0.1, \"code_length\"), (1.1, \"code_length\")),\n        ((-0.1, \"code_length\"), (1.1, \"code_length\")),\n        ((1.2, \"code_length\"), (1.2, \"code_length\")),\n        (\"cm\", \"cm\"),\n    ),\n    ((1.2, \"cm\"), (2.0, \"cm\")): (\n        ((-0.1, \"code_length\"), (1.1, \"code_length\")),\n        ((-0.5, \"code_length\"), (1.5, \"code_length\")),\n        ((1.2, \"code_length\"), (2.0, \"code_length\")),\n        (\"cm\", \"cm\"),\n    ),\n    ((1.2, \"cm\"), (0.02, \"m\")): (\n        ((-0.1, \"code_length\"), (1.1, \"code_length\")),\n        ((-0.5, \"code_length\"), (1.5, \"code_length\")),\n        ((1.2, \"code_length\"), (2.0, \"code_length\")),\n        (\"cm\", \"m\"),\n    ),\n}\n\nWEIGHT_FIELDS = (\n    None,\n    (\"gas\", \"density\"),\n)\n\nPROJECTION_METHODS = (\"integrate\", \"sum\", \"min\", \"max\")\n\nBUFF_SIZES = [(800, 800), (1600, 1600), (1254, 1254), (800, 600)]\n\n\ndef simple_contour(test_obj, plot):\n    plot.annotate_contour(test_obj.plot_field)\n\n\ndef simple_velocity(test_obj, plot):\n    plot.annotate_velocity()\n\n\ndef simple_streamlines(test_obj, plot):\n    ax = test_obj.plot_axis\n    xax = test_obj.ds.coordinates.x_axis[ax]\n    yax = test_obj.ds.coordinates.y_axis[ax]\n    xn = test_obj.ds.coordinates.axis_name[xax]\n    yn = test_obj.ds.coordinates.axis_name[yax]\n    plot.annotate_streamlines((\"gas\", f\"velocity_{xn}\"), (\"gas\", f\"velocity_{yn}\"))\n\n\nCALLBACK_TESTS = (\n    (\"simple_contour\", (simple_contour,)),\n    (\"simple_velocity\", (simple_velocity,)),\n    # (\"simple_streamlines\", (simple_streamlines,)),\n    # (\"simple_all\", (simple_contour, simple_velocity, simple_streamlines)),\n)\n\n\n@requires_ds(M7)\ndef test_attributes():\n    \"\"\"Test plot member functions that aren't callbacks\"\"\"\n    plot_field = (\"gas\", \"density\")\n    decimals = 12\n\n    ds = data_dir_load(M7)\n    for ax in \"xyz\":\n        for attr_name in ATTR_ARGS.keys():\n            for args in ATTR_ARGS[attr_name]:\n                test = PlotWindowAttributeTest(\n                    ds, plot_field, ax, attr_name, args, decimals\n                )\n                test_attributes.__name__ = test.description\n                yield test\n                for n, r in CALLBACK_TESTS:\n                    yield PlotWindowAttributeTest(\n                        ds,\n                        plot_field,\n                        ax,\n                        attr_name,\n                        args,\n                        decimals,\n                        callback_id=n,\n                        callback_runners=r,\n                    )\n\n\nclass TestHideAxesColorbar(unittest.TestCase):\n    ds = None\n\n    def setUp(self):\n        if self.ds is None:\n            self.ds = fake_random_ds(64)\n            self.slc = SlicePlot(self.ds, 0, (\"gas\", \"density\"))\n        self.tmpdir = tempfile.mkdtemp()\n        self.curdir = os.getcwd()\n        os.chdir(self.tmpdir)\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        shutil.rmtree(self.tmpdir)\n        del self.ds\n        del self.slc\n\n    def test_hide_show_axes(self):\n        self.slc.hide_axes()\n        self.slc.save()\n        self.slc.show_axes()\n        self.slc.save()\n\n    def test_hide_show_colorbar(self):\n        self.slc.hide_colorbar()\n        self.slc.save()\n        self.slc.show_colorbar()\n        self.slc.save()\n\n    def test_hide_axes_colorbar(self):\n        self.slc.hide_colorbar()\n        self.slc.hide_axes()\n        self.slc.save()\n\n\nclass TestSetWidth(unittest.TestCase):\n    ds = None\n\n    def setUp(self):\n        if self.ds is None:\n            self.ds = fake_random_ds(64)\n            self.slc = SlicePlot(self.ds, 0, (\"gas\", \"density\"))\n\n    def tearDown(self):\n        del self.ds\n        del self.slc\n\n    def _assert_05cm(self):\n        assert_array_equal(\n            [self.slc.xlim, self.slc.ylim, self.slc.width],\n            [\n                (YTQuantity(0.25, \"cm\"), YTQuantity(0.75, \"cm\")),\n                (YTQuantity(0.25, \"cm\"), YTQuantity(0.75, \"cm\")),\n                (YTQuantity(0.5, \"cm\"), YTQuantity(0.5, \"cm\")),\n            ],\n        )\n\n    def _assert_05_075cm(self):\n        assert_array_equal(\n            [self.slc.xlim, self.slc.ylim, self.slc.width],\n            [\n                (YTQuantity(0.25, \"cm\"), YTQuantity(0.75, \"cm\")),\n                (YTQuantity(0.125, \"cm\"), YTQuantity(0.875, \"cm\")),\n                (YTQuantity(0.5, \"cm\"), YTQuantity(0.75, \"cm\")),\n            ],\n        )\n\n    def test_set_width_one(self):\n        assert_equal(\n            [self.slc.xlim, self.slc.ylim, self.slc.width],\n            [(0.0, 1.0), (0.0, 1.0), (1.0, 1.0)],\n        )\n        assert self.slc._axes_unit_names is None\n\n    def test_set_width_nonequal(self):\n        self.slc.set_width((0.5, 0.8))\n        assert_rel_equal(\n            [self.slc.xlim, self.slc.ylim, self.slc.width],\n            [(0.25, 0.75), (0.1, 0.9), (0.5, 0.8)],\n            15,\n        )\n        assert self.slc._axes_unit_names is None\n\n    def test_twoargs_eq(self):\n        self.slc.set_width(0.5, \"cm\")\n        self._assert_05cm()\n        assert self.slc._axes_unit_names == (\"cm\", \"cm\")\n\n    def test_tuple_eq(self):\n        self.slc.set_width((0.5, \"cm\"))\n        self._assert_05cm()\n        assert self.slc._axes_unit_names == (\"cm\", \"cm\")\n\n    def test_tuple_of_tuples_neq(self):\n        self.slc.set_width(((0.5, \"cm\"), (0.75, \"cm\")))\n        self._assert_05_075cm()\n        assert self.slc._axes_unit_names == (\"cm\", \"cm\")\n\n\nclass TestPlotWindowSave(unittest.TestCase):\n    def setUp(self):\n        self.tmpdir = tempfile.mkdtemp()\n        self.curdir = os.getcwd()\n        os.chdir(self.tmpdir)\n\n    def tearDown(self):\n        os.chdir(self.curdir)\n        shutil.rmtree(self.tmpdir)\n\n    def test_slice_plot(self):\n        test_ds = fake_random_ds(16)\n        for dim in range(3):\n            slc = SlicePlot(test_ds, dim, (\"gas\", \"density\"))\n            for fname in TEST_FLNMS:\n                assert_fname(slc.save(fname)[0])\n\n    def test_repr_html(self):\n        test_ds = fake_random_ds(16)\n        slc = SlicePlot(test_ds, 0, (\"gas\", \"density\"))\n        slc._repr_html_()\n\n    def test_projection_plot(self):\n        test_ds = fake_random_ds(16)\n        for dim in range(3):\n            proj = ProjectionPlot(test_ds, dim, (\"gas\", \"density\"))\n            for fname in TEST_FLNMS:\n                assert_fname(proj.save(fname)[0])\n\n    def test_projection_plot_ds(self):\n        test_ds = fake_random_ds(16)\n        reg = test_ds.region([0.5] * 3, [0.4] * 3, [0.6] * 3)\n        for dim in range(3):\n            proj = ProjectionPlot(test_ds, dim, (\"gas\", \"density\"), data_source=reg)\n            proj.save()\n\n    def test_projection_plot_c(self):\n        test_ds = fake_random_ds(16)\n        for center in CENTER_SPECS:\n            proj = ProjectionPlot(test_ds, 0, (\"gas\", \"density\"), center=center)\n            proj.save()\n\n    def test_projection_plot_wf(self):\n        test_ds = fake_random_ds(16)\n        for wf in WEIGHT_FIELDS:\n            proj = ProjectionPlot(test_ds, 0, (\"gas\", \"density\"), weight_field=wf)\n            proj.save()\n\n    def test_projection_plot_m(self):\n        test_ds = fake_random_ds(16)\n        for method in PROJECTION_METHODS:\n            proj = ProjectionPlot(test_ds, 0, (\"gas\", \"density\"), method=method)\n            proj.save()\n\n    def test_projection_plot_bs(self):\n        test_ds = fake_random_ds(16)\n        for bf in BUFF_SIZES:\n            proj = ProjectionPlot(test_ds, 0, (\"gas\", \"density\"), buff_size=bf)\n            image = proj.frb[\"gas\", \"density\"]\n\n            # note that image.shape is inverted relative to the passed in buff_size\n            assert_equal(image.shape[::-1], bf)\n\n    def test_offaxis_slice_plot(self):\n        test_ds = fake_random_ds(16)\n        slc = OffAxisSlicePlot(test_ds, [1, 1, 1], (\"gas\", \"density\"))\n        for fname in TEST_FLNMS:\n            assert_fname(slc.save(fname)[0])\n\n    def test_offaxis_projection_plot(self):\n        test_ds = fake_random_ds(16)\n        prj = OffAxisProjectionPlot(test_ds, [1, 1, 1], (\"gas\", \"density\"))\n        for fname in TEST_FLNMS:\n            assert_fname(prj.save(fname)[0])\n\n    def test_creation_with_width(self):\n        test_ds = fake_random_ds(16)\n        for width in WIDTH_SPECS:\n            xlim, ylim, pwidth, aun = WIDTH_SPECS[width]\n            plot = ProjectionPlot(test_ds, 0, (\"gas\", \"density\"), width=width)\n\n            xlim = [plot.ds.quan(el[0], el[1]) for el in xlim]\n            ylim = [plot.ds.quan(el[0], el[1]) for el in ylim]\n            pwidth = [plot.ds.quan(el[0], el[1]) for el in pwidth]\n\n            for px, x in zip(plot.xlim, xlim, strict=True):\n                assert_array_almost_equal(px, x, 14)\n            for py, y in zip(plot.ylim, ylim, strict=True):\n                assert_array_almost_equal(py, y, 14)\n            for pw, w in zip(plot.width, pwidth, strict=True):\n                assert_array_almost_equal(pw, w, 14)\n\n            assert aun == plot._axes_unit_names\n\n\nclass TestPerFieldConfig(unittest.TestCase):\n    ds = None\n\n    def setUp(self):\n        from yt.config import ytcfg\n\n        newConfig = {\n            (\"yt\", \"default_colormap\"): \"viridis\",\n            (\"plot\", \"gas\", \"log\"): False,\n            (\"plot\", \"gas\", \"density\", \"units\"): \"lb/yard**3\",\n            (\"plot\", \"gas\", \"density\", \"path_length_units\"): \"mile\",\n            (\"plot\", \"gas\", \"density\", \"cmap\"): \"plasma\",\n            (\"plot\", \"gas\", \"temperature\", \"log\"): True,\n            (\"plot\", \"gas\", \"temperature\", \"linthresh\"): 100,\n            (\"plot\", \"gas\", \"temperature\", \"cmap\"): \"hot\",\n            (\"plot\", \"gas\", \"pressure\", \"log\"): True,\n            (\"plot\", \"index\", \"radius\", \"linthresh\"): 1e3,\n        }\n        # Backup the old config\n        oldConfig = {}\n        for key in newConfig.keys():\n            try:\n                val = ytcfg[key]\n                oldConfig[key] = val\n            except KeyError:\n                pass\n        for key, val in newConfig.items():\n            ytcfg[key] = val\n\n        self.oldConfig = oldConfig\n        self.newConfig = newConfig\n\n        fields = [(\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"pressure\")]\n        units = [\"g/cm**3\", \"K\", \"dyn/cm**2\"]\n        fields_to_plot = fields + [(\"index\", \"radius\")]\n        if self.ds is None:\n            self.ds = fake_random_ds(16, fields=fields, units=units)\n            self.proj = ProjectionPlot(self.ds, 0, fields_to_plot)\n\n    def tearDown(self):\n        from yt.config import ytcfg\n\n        del self.ds\n        del self.proj\n        for key in self.newConfig.keys():\n            ytcfg.remove(*key)\n        for key, val in self.oldConfig.items():\n            ytcfg[key] = val\n\n    def test_units(self):\n        from unyt import Unit\n\n        assert_equal(self.proj.frb[\"gas\", \"density\"].units, Unit(\"mile*lb/yd**3\"))\n        assert_equal(self.proj.frb[\"gas\", \"temperature\"].units, Unit(\"cm*K\"))\n        assert_equal(self.proj.frb[\"gas\", \"pressure\"].units, Unit(\"dyn/cm\"))\n\n    def test_scale(self):\n        assert_equal(\n            self.proj.plots[\"gas\", \"density\"].norm_handler.norm_type, Normalize\n        )\n        assert_equal(\n            self.proj.plots[\"gas\", \"temperature\"].norm_handler.norm_type, SymLogNorm\n        )\n        assert_allclose_units(\n            self.proj.plots[\"gas\", \"temperature\"].norm_handler.linthresh,\n            unyt_array(100, \"K*cm\"),\n        )\n        assert_equal(self.proj.plots[\"gas\", \"pressure\"].norm_handler.norm_type, LogNorm)\n        assert_equal(\n            self.proj.plots[\"index\", \"radius\"].norm_handler.norm_type, SymLogNorm\n        )\n\n    def test_cmap(self):\n        assert_equal(\n            self.proj.plots[\"gas\", \"density\"].colorbar_handler.cmap.name, \"plasma\"\n        )\n        assert_equal(\n            self.proj.plots[\"gas\", \"temperature\"].colorbar_handler.cmap.name, \"hot\"\n        )\n        assert_equal(\n            self.proj.plots[\"gas\", \"pressure\"].colorbar_handler.cmap.name, \"viridis\"\n        )\n\n\ndef test_on_off_compare():\n    # fake density field that varies in the x-direction only\n    den = np.arange(32**3) / 32**2 + 1\n    den = den.reshape(32, 32, 32)\n    den = np.array(den, dtype=np.float64)\n    data = {\"density\": (den, \"g/cm**3\")}\n    bbox = np.array([[-1.5, 1.5], [-1.5, 1.5], [-1.5, 1.5]])\n    ds = load_uniform_grid(data, den.shape, length_unit=\"Mpc\", bbox=bbox, nprocs=64)\n\n    sl_on = SlicePlot(ds, \"z\", [(\"gas\", \"density\")])\n\n    L = [0, 0, 1]\n    north_vector = [0, 1, 0]\n    sl_off = OffAxisSlicePlot(\n        ds, L, (\"gas\", \"density\"), center=[0, 0, 0], north_vector=north_vector\n    )\n\n    assert_array_almost_equal(sl_on.frb[\"gas\", \"density\"], sl_off.frb[\"gas\", \"density\"])\n\n    sl_on.set_buff_size((800, 400))\n    sl_on._recreate_frb()\n    sl_off.set_buff_size((800, 400))\n    sl_off._recreate_frb()\n\n    assert_array_almost_equal(sl_on.frb[\"gas\", \"density\"], sl_off.frb[\"gas\", \"density\"])\n\n\ndef test_plot_particle_field_error():\n    ds = fake_random_ds(32, particles=100)\n\n    field_names = [\n        (\"all\", \"particle_mass\"),\n        [(\"all\", \"particle_mass\"), (\"gas\", \"density\")],\n        [(\"gas\", \"density\"), (\"all\", \"particle_mass\")],\n    ]\n\n    objects_normals = [\n        (SlicePlot, 2),\n        (SlicePlot, [1, 1, 1]),\n        (ProjectionPlot, 2),\n        (OffAxisProjectionPlot, [1, 1, 1]),\n    ]\n\n    for object, normal in objects_normals:\n        for field_name_list in field_names:\n            assert_raises(YTInvalidFieldType, object, ds, normal, field_name_list)\n\n\ndef test_setup_origin():\n    origin_inputs = (\n        \"domain\",\n        \"left-window\",\n        \"center-domain\",\n        \"lower-right-window\",\n        (\"window\",),\n        (\"right\", \"domain\"),\n        (\"lower\", \"window\"),\n        (\"lower\", \"right\", \"window\"),\n        (0.5, 0.5, \"domain\"),\n        ((50, \"cm\"), (50, \"cm\"), \"domain\"),\n    )\n    w = (10, \"cm\")\n\n    ds = fake_random_ds(32, length_unit=100.0)\n    generated_limits = []\n    # lower limit -> llim\n    # upper limit -> ulim\n    #                 xllim xulim yllim yulim\n    correct_limits = [\n        45.0,\n        55.0,\n        45.0,\n        55.0,\n        0.0,\n        10.0,\n        0.0,\n        10.0,\n        -5.0,\n        5.0,\n        -5.0,\n        5.0,\n        -10.0,\n        0,\n        0,\n        10.0,\n        0.0,\n        10.0,\n        0.0,\n        10.0,\n        -55.0,\n        -45.0,\n        -55.0,\n        -45.0,\n        -5.0,\n        5.0,\n        0.0,\n        10.0,\n        -10.0,\n        0,\n        0,\n        10.0,\n        -5.0,\n        5.0,\n        -5.0,\n        5.0,\n        -5.0,\n        5.0,\n        -5.0,\n        5.0,\n    ]\n    for o in origin_inputs:\n        slc = SlicePlot(ds, 2, (\"gas\", \"density\"), width=w, origin=o)\n        ax = slc.plots[\"gas\", \"density\"].axes\n        xlims = ax.get_xlim()\n        ylims = ax.get_ylim()\n        lims = [xlims[0], xlims[1], ylims[0], ylims[1]]\n        for l in lims:\n            generated_limits.append(l)\n    assert_array_almost_equal(correct_limits, generated_limits)\n\n\ndef test_frb_regen():\n    ds = fake_random_ds(32)\n    slc = SlicePlot(ds, 2, (\"gas\", \"density\"))\n    slc.set_buff_size(1200)\n    assert_equal(slc.frb[\"gas\", \"density\"].shape, (1200, 1200))\n    slc.set_buff_size((400.0, 200.7))\n    assert_equal(slc.frb[\"gas\", \"density\"].shape, (200, 400))\n\n\ndef test_set_background_color():\n    ds = fake_random_ds(32)\n    plot = SlicePlot(ds, 2, (\"gas\", \"density\"))\n    plot.set_background_color((\"gas\", \"density\"), \"red\")\n    plot.render()\n    ax = plot.plots[\"gas\", \"density\"].axes\n    assert_equal(ax.get_facecolor(), (1.0, 0.0, 0.0, 1.0))\n\n\ndef test_set_unit():\n    ds = fake_random_ds(32, fields=((\"gas\", \"temperature\"),), units=(\"K\",))\n    slc = SlicePlot(ds, 2, (\"gas\", \"temperature\"))\n\n    orig_array = slc.frb[\"gas\", \"temperature\"].copy()\n\n    slc.set_unit((\"gas\", \"temperature\"), \"degF\")\n\n    assert str(slc.frb[\"gas\", \"temperature\"].units) == \"°F\"\n    assert_array_almost_equal(\n        np.array(slc.frb[\"gas\", \"temperature\"]), np.array(orig_array) * 1.8 - 459.67\n    )\n\n    # test that a plot modifying function that destroys the frb preserves the\n    # new unit\n    slc.set_buff_size(1000)\n\n    assert str(slc.frb[\"gas\", \"temperature\"].units) == \"°F\"\n\n    slc.set_buff_size(800)\n\n    slc.set_unit((\"gas\", \"temperature\"), \"K\")\n    assert str(slc.frb[\"gas\", \"temperature\"].units) == \"K\"\n    assert_array_almost_equal(slc.frb[\"gas\", \"temperature\"], orig_array)\n\n    slc.set_unit((\"gas\", \"temperature\"), \"keV\", equivalency=\"thermal\")\n    assert str(slc.frb[\"gas\", \"temperature\"].units) == \"keV\"\n    assert_array_almost_equal(\n        slc.frb[\"gas\", \"temperature\"], (orig_array * kboltz).to(\"keV\")\n    )\n\n    # test that a plot modifying function that destroys the frb preserves the\n    # new unit with an equivalency\n    slc.set_buff_size(1000)\n\n    assert str(slc.frb[\"gas\", \"temperature\"].units) == \"keV\"\n\n    # test that destroying the FRB then changing the unit using an equivalency\n    # doesn't error out, see issue #1316\n    slc = SlicePlot(ds, 2, (\"gas\", \"temperature\"))\n    slc.set_buff_size(1000)\n    slc.set_unit((\"gas\", \"temperature\"), \"keV\", equivalency=\"thermal\")\n    assert str(slc.frb[\"gas\", \"temperature\"].units) == \"keV\"\n\n\nWD = \"WDMerger_hdf5_chk_1000/WDMerger_hdf5_chk_1000.hdf5\"\nblast_wave = \"amrvac/bw_2d0000.dat\"\n\n\n@requires_file(WD)\n@requires_file(blast_wave)\ndef test_plot_2d():\n    # Cartesian\n    ds = fake_random_ds((32, 32, 1), fields=(\"temperature\",), units=(\"K\",))\n    slc = SlicePlot(\n        ds,\n        \"z\",\n        [(\"gas\", \"temperature\")],\n        width=(0.2, \"unitary\"),\n        center=[0.4, 0.3, 0.5],\n    )\n    slc2 = plot_2d(\n        ds, (\"gas\", \"temperature\"), width=(0.2, \"unitary\"), center=[0.4, 0.3]\n    )\n    slc3 = plot_2d(\n        ds,\n        (\"gas\", \"temperature\"),\n        width=(0.2, \"unitary\"),\n        center=ds.arr([0.4, 0.3], \"cm\"),\n    )\n    assert_array_equal(slc.frb[\"gas\", \"temperature\"], slc2.frb[\"gas\", \"temperature\"])\n    assert_array_equal(slc.frb[\"gas\", \"temperature\"], slc3.frb[\"gas\", \"temperature\"])\n    # Cylindrical\n    ds = data_dir_load(WD)\n    slc = SlicePlot(ds, \"theta\", [(\"gas\", \"density\")], width=(30000.0, \"km\"))\n    slc2 = plot_2d(ds, (\"gas\", \"density\"), width=(30000.0, \"km\"))\n    assert_array_equal(slc.frb[\"gas\", \"density\"], slc2.frb[\"gas\", \"density\"])\n\n    # Spherical\n    ds = data_dir_load(blast_wave)\n    slc = SlicePlot(ds, \"phi\", [(\"gas\", \"density\")], width=(1, \"unitary\"))\n    slc2 = plot_2d(ds, (\"gas\", \"density\"), width=(1, \"unitary\"))\n    assert_array_equal(slc.frb[\"gas\", \"density\"], slc2.frb[\"gas\", \"density\"])\n\n\ndef test_symlog_colorbar():\n    ds = fake_random_ds(16)\n\n    def _thresh_density(data):\n        wh = data[\"gas\", \"density\"] < 0.5\n        ret = data[\"gas\", \"density\"]\n        ret[wh] = 0\n        return ret\n\n    def _neg_density(data):\n        return -data[\"gas\", \"threshold_density\"]\n\n    ds.add_field(\n        (\"gas\", \"threshold_density\"),\n        function=_thresh_density,\n        units=\"g/cm**3\",\n        sampling_type=\"cell\",\n    )\n    ds.add_field(\n        (\"gas\", \"negative_density\"),\n        function=_neg_density,\n        units=\"g/cm**3\",\n        sampling_type=\"cell\",\n    )\n\n    for field in [\n        (\"gas\", \"density\"),\n        (\"gas\", \"threshold_density\"),\n        (\"gas\", \"negative_density\"),\n    ]:\n        plot = SlicePlot(ds, 2, field)\n        plot.set_log(field, linthresh=0.1)\n        with tempfile.NamedTemporaryFile(suffix=\"png\") as f:\n            plot.save(f.name)\n\n\ndef test_symlog_min_zero():\n    # see https://github.com/yt-project/yt/issues/3791\n    shape = (32, 16, 1)\n    a = np.linspace(0, 1, 16)\n    b = np.ones((32, 16))\n    c = np.reshape(a * b, shape)\n    data = {(\"gas\", \"density\"): c}\n\n    ds = load_uniform_grid(\n        data,\n        shape,\n        bbox=np.array([[0.0, 5.0], [0, 1], [-0.1, +0.1]]),\n    )\n\n    p = SlicePlot(ds, \"z\", \"density\")\n    im_arr = p[\"gas\", \"density\"].image.get_array()\n\n    # check that no data value was mapped to a NaN (log(0))\n    assert np.all(~np.isnan(im_arr))\n    # 0 should be mapped to itself since we expect a symlog norm\n    assert np.min(im_arr) == 0.0\n\n\ndef test_symlog_extremely_small_vals():\n    # check that the plot can be constructed without crashing\n    # see https://github.com/yt-project/yt/issues/3858\n    # and https://github.com/yt-project/yt/issues/3944\n    shape = (64, 64, 1)\n    arr = np.full(shape, 5.0e-324)\n    arr[0, 0] = -1e12\n    arr[1, 1] = 200\n\n    arr2 = np.full(shape, 5.0e-324)\n    arr2[0, 0] = -1e12\n\n    arr3 = arr.copy()\n    arr3[4, 4] = 0.0\n\n    d = {\"scalar_spans_0\": arr, \"tiny_vmax\": arr2, \"scalar_tiny_with_0\": arr3}\n\n    ds = load_uniform_grid(d, shape)\n    for field in d:\n        p = SlicePlot(ds, \"z\", field)\n        p[\"stream\", field]\n\n\ndef test_symlog_linthresh_gt_vmax():\n    # check that some more edge cases do not crash\n\n    # linthresh will end up being larger than vmax here. This is OK.\n    shape = (64, 64, 1)\n    arr = np.full(shape, -1e30)\n    arr[1, 1] = -1e27\n    arr[2, 2] = 1e-12\n    arr[3, 3] = 1e-10\n\n    arr2 = -1 * arr.copy()  # also check the reverse\n    d = {\"linthresh_gt_vmax\": arr, \"linthresh_lt_vmin\": arr2}\n\n    ds = load_uniform_grid(d, shape)\n    for field in d:\n        p = SlicePlot(ds, \"z\", field)\n        p[\"stream\", field]\n\n\ndef test_symlog_symmetric():\n    # should run ok when abs(min negative) == abs(pos max)\n    shape = (64, 64, 1)\n    arr = np.full(shape, -1e30)\n    arr[1, 1] = -1e27\n    arr[2, 2] = 1e10\n    arr[3, 3] = 1e30\n    d = {\"linthresh_symmetric\": arr}\n\n    ds = load_uniform_grid(d, shape)\n    p = SlicePlot(ds, \"z\", \"linthresh_symmetric\")\n    p[\"stream\", \"linthresh_symmetric\"]\n\n\ndef test_nan_data():\n    data = np.random.random((16, 16, 16)) - 0.5\n    data[:9, :9, :9] = np.nan\n\n    data = {\"density\": data}\n\n    ds = load_uniform_grid(data, [16, 16, 16])\n\n    plot = SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n\n    with tempfile.NamedTemporaryFile(suffix=\"png\") as f:\n        plot.save(f.name)\n\n\ndef test_sanitize_valid_normal_vector():\n    # note: we don't test against non-cartesian geometries\n    # because the way normal \"vectors\" work isn't clearly\n    # specified and works more as an implementation detail\n    # at the moment\n    ds = fake_amr_ds(geometry=\"cartesian\")\n\n    # We allow maximal polymorphism for axis-aligned directions:\n    # even if 3-component vector is received, we want to use the\n    # AxisAligned* plotting class (as opposed to OffAxis*) because\n    # it's much easier to optimize so it's expected to be more\n    # performant.\n    axis_label_from_inputs = {\n        \"x\": [\"x\", 0, [1, 0, 0], [0.1, 0.0, 0.0], [-10, 0, 0]],\n        \"y\": [\"y\", 1, [0, 1, 0], [0.0, 0.1, 0.0], [0, -10, 0]],\n        \"z\": [\"z\", 2, [0, 0, 1], [0.0, 0.0, 0.1], [0, 0, -10]],\n    }\n    for expected, user_inputs in axis_label_from_inputs.items():\n        for ui in user_inputs:\n            assert NormalPlot.sanitize_normal_vector(ds, ui) == expected\n\n    # arbitrary 3-floats sequences are also valid input.\n    # They should be returned as np.ndarrays, but the norm and orientation\n    # could be altered. What's important is that their direction is preserved.\n    for ui in [(1, 1, 1), [0.0, -3, 1e9], np.ones(3, dtype=\"int8\")]:\n        res = NormalPlot.sanitize_normal_vector(ds, ui)\n        assert isinstance(res, np.ndarray)\n        assert res.dtype == np.float64\n        assert_array_equal(\n            np.cross(ui, res),\n            [0, 0, 0],\n        )\n\n\ndef test_reject_invalid_normal_vector():\n    ds = fake_amr_ds(geometry=\"cartesian\")\n    for ui in [0.0, 1.0, 2.0, 3.0]:\n        # acceptable scalar numeric values are restricted to integers.\n        # Floats might be a sign that something went wrong upstream\n        # e.g., rounding errors, parsing error...\n        assert_raises(TypeError, NormalPlot.sanitize_normal_vector, ds, ui)\n    for ui in [\n        \"X\",\n        \"xy\",\n        \"not-an-axis\",\n        (0, 0, 0),\n        [0, 0, 0],\n        np.zeros(3),\n        [1, 0, 0, 0],\n        [1, 0],\n        [1],\n        [0],\n        3,\n        10,\n    ]:\n        assert_raises(ValueError, NormalPlot.sanitize_normal_vector, ds, ui)\n\n\ndef test_dispatch_plot_classes():\n    ds = fake_random_ds(16)\n    p1 = ProjectionPlot(ds, \"z\", (\"gas\", \"density\"))\n    p2 = ProjectionPlot(ds, (1, 2, 3), (\"gas\", \"density\"))\n    s1 = SlicePlot(ds, \"z\", (\"gas\", \"density\"))\n    s2 = SlicePlot(ds, (1, 2, 3), (\"gas\", \"density\"))\n    assert isinstance(p1, AxisAlignedProjectionPlot)\n    assert isinstance(p2, OffAxisProjectionPlot)\n    assert isinstance(s1, AxisAlignedSlicePlot)\n    assert isinstance(s2, OffAxisSlicePlot)\n\n\n@requires_module(\"cartopy\")\ndef test_invalid_swap_projection():\n    # projections and transforms will not work\n    ds = fake_amr_ds(geometry=\"geographic\")\n    slc = SlicePlot(ds, \"altitude\", ds.field_list[0], origin=\"native\")\n    slc.set_mpl_projection(\"Robinson\")\n    slc.swap_axes()  # should raise mylog.warning and not toggle _swap_axes\n    assert slc._has_swapped_axes is False\n\n\ndef test_set_font():\n    # simply check that calling the set_font method doesn't raise an error\n    # https://github.com/yt-project/yt/issues/4263\n    ds = fake_amr_ds()\n    slc = SlicePlot(ds, \"x\", \"Density\")\n    slc.set_font(\n        {\n            \"family\": \"sans-serif\",\n            \"style\": \"italic\",\n            \"weight\": \"bold\",\n            \"size\": 24,\n            \"color\": \"blue\",\n        }\n    )\n"
  },
  {
    "path": "yt/visualization/tests/test_profile_plots.py",
    "content": "import os\nimport shutil\nimport tempfile\nimport unittest\n\nimport numpy as np\nimport pytest\n\nimport yt\nfrom yt.testing import assert_allclose_units, fake_random_ds, fake_random_sph_ds\nfrom yt.visualization.api import PhasePlot\n\n\nclass TestPhasePlotAPI:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_random_ds(\n            16, fields=(\"density\", \"temperature\"), units=(\"g/cm**3\", \"K\")\n        )\n\n    def get_plot(self):\n        return PhasePlot(\n            self.ds, (\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"mass\")\n        )\n\n    @pytest.mark.parametrize(\"kwargs\", [{}, {\"color\": \"b\"}])\n    @pytest.mark.mpl_image_compare\n    def test_phaseplot_annotate_text(self, kwargs):\n        p = self.get_plot()\n        p.annotate_text(1e-4, 1e-2, \"Test text annotation\", **kwargs)\n        p.render()\n        return p.plots[\"gas\", \"mass\"].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_phaseplot_set_title(self):\n        p = self.get_plot()\n        p.set_title((\"gas\", \"mass\"), \"Test Title\")\n        p.render()\n        return p.plots[\"gas\", \"mass\"].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_phaseplot_set_log(self):\n        p = self.get_plot()\n        p.set_log((\"gas\", \"mass\"), False)\n        p.render()\n        return p.plots[\"gas\", \"mass\"].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_phaseplot_set_unit(self):\n        p = self.get_plot()\n        p.set_unit((\"gas\", \"mass\"), \"Msun\")\n        p.render()\n        return p.plots[\"gas\", \"mass\"].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_phaseplot_set_xlim(self):\n        p = self.get_plot()\n        p.set_xlim(1e-3, 1e0)\n        p.render()\n        return p.plots[\"gas\", \"mass\"].figure\n\n    @pytest.mark.mpl_image_compare\n    def test_phaseplot_set_ylim(self):\n        p = self.get_plot()\n        p.set_ylim(1e-2, 1e0)\n        p.render()\n        return p.plots[\"gas\", \"mass\"].figure\n\n\nclass TestPhasePlotParticleAPI:\n    @classmethod\n    def setup_class(cls):\n        bbox = np.array([[-1.0, 3.0], [1.0, 5.2], [-1.0, 3.0]])\n        cls.ds = fake_random_sph_ds(50, bbox)\n\n    def get_plot(self):\n        return PhasePlot(\n            self.ds, (\"gas\", \"density\"), (\"gas\", \"density\"), (\"gas\", \"mass\")\n        )\n\n    @pytest.mark.parametrize(\"kwargs\", [{}, {\"color\": \"b\"}])\n    def test_phaseplot_annotate_text(self, kwargs):\n        p = self.get_plot()\n        p.annotate_text(1e-4, 1e-2, \"Test text annotation\", **kwargs)\n        p.render()\n\n    def test_phaseplot_set_title(self):\n        p = self.get_plot()\n        p.annotate_title(\"Test Title\")\n        p.render()\n\n    def test_phaseplot_set_log(self):\n        p = self.get_plot()\n        p.set_log((\"gas\", \"mass\"), False)\n        p.render()\n\n    def test_phaseplot_set_unit(self):\n        p = self.get_plot()\n        p.set_unit((\"gas\", \"mass\"), \"Msun\")\n        p.render()\n\n    def test_phaseplot_set_xlim(self):\n        p = self.get_plot()\n        p.set_xlim(1e-3, 1e0)\n        p.render()\n\n    def test_phaseplot_set_ylim(self):\n        p = self.get_plot()\n        p.set_ylim(1e-2, 1e0)\n        p.render()\n\n\ndef test_set_units():\n    fields = (\"density\", \"temperature\")\n    units = (\n        \"g/cm**3\",\n        \"K\",\n    )\n    ds = fake_random_ds(16, fields=fields, units=units)\n    sp = ds.sphere(\"max\", (1.0, \"Mpc\"))\n    p1 = yt.ProfilePlot(sp, (\"index\", \"radius\"), (\"gas\", \"density\"))\n    p2 = yt.PhasePlot(sp, (\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"mass\"))\n    # make sure we can set the units using the tuple without erroring out\n    p1.set_unit((\"gas\", \"density\"), \"Msun/kpc**3\")\n    p2.set_unit((\"gas\", \"temperature\"), \"R\")\n\n\ndef test_set_labels():\n    ds = fake_random_ds(16)\n    ad = ds.all_data()\n    plot = yt.ProfilePlot(\n        ad,\n        (\"index\", \"radius\"),\n        [(\"gas\", \"velocity_x\"), (\"gas\", \"density\")],\n        weight_field=None,\n    )\n    # make sure we can set the labels without erroring out\n    plot.set_ylabel(\"all\", \"test ylabel\")\n    plot.set_xlabel(\"test xlabel\")\n\n\ndef test_create_from_dataset():\n    ds = fake_random_ds(16)\n    plot1 = yt.ProfilePlot(\n        ds,\n        (\"index\", \"radius\"),\n        [(\"gas\", \"velocity_x\"), (\"gas\", \"density\")],\n        weight_field=None,\n    )\n    plot2 = yt.ProfilePlot(\n        ds.all_data(),\n        (\"index\", \"radius\"),\n        [(\"gas\", \"velocity_x\"), (\"gas\", \"density\")],\n        weight_field=None,\n    )\n    assert_allclose_units(\n        plot1.profiles[0][\"gas\", \"density\"], plot2.profiles[0][\"gas\", \"density\"]\n    )\n    assert_allclose_units(\n        plot1.profiles[0][\"velocity_x\"], plot2.profiles[0][\"velocity_x\"]\n    )\n\n    plot1 = yt.PhasePlot(ds, (\"gas\", \"density\"), (\"gas\", \"velocity_x\"), (\"gas\", \"mass\"))\n    plot2 = yt.PhasePlot(\n        ds.all_data(), (\"gas\", \"density\"), (\"gas\", \"velocity_x\"), (\"gas\", \"mass\")\n    )\n    assert_allclose_units(plot1.profile[\"mass\"], plot2.profile[\"mass\"])\n\n\nclass TestAnnotations(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        cls.tmpdir = tempfile.mkdtemp()\n        cls.curdir = os.getcwd()\n        os.chdir(cls.tmpdir)\n\n        ds = fake_random_ds(16)\n        ad = ds.all_data()\n        cls.fields = [\n            (\"gas\", \"velocity_x\"),\n            (\"gas\", \"velocity_y\"),\n            (\"gas\", \"velocity_z\"),\n        ]\n        cls.plot = yt.ProfilePlot(\n            ad, (\"index\", \"radius\"), cls.fields, weight_field=None\n        )\n\n    @classmethod\n    def tearDownClass(cls):\n        os.chdir(cls.curdir)\n        shutil.rmtree(cls.tmpdir)\n\n    def test_annotations(self):\n        # make sure we can annotate without erroring out\n        # annotate the plot with only velocity_x\n        self.plot.annotate_title(\"velocity_x plot\", self.fields[0])\n        self.plot.annotate_text(1e-1, 1e1, \"Annotated velocity_x\")\n\n        # annotate the plots with velocity_y and velocity_z with\n        # the same annotations\n        self.plot.annotate_title(\"Velocity Plots (Y or Z)\", self.fields[1:])\n        self.plot.annotate_text(1e-1, 1e1, \"Annotated vel_y, vel_z\", self.fields[1:])\n        self.plot.save()\n\n    def test_annotations_wrong_fields(self):\n        from yt.utilities.exceptions import YTFieldNotFound\n\n        with self.assertRaises(YTFieldNotFound):\n            self.plot.annotate_title(\"velocity_x plot\", \"wrong_field_name\")\n\n        with self.assertRaises(YTFieldNotFound):\n            self.plot.annotate_text(1e-1, 1e1, \"Annotated text\", \"wrong_field_name\")\n\n\ndef test_phaseplot_set_log():\n    fields = (\"density\", \"temperature\")\n    units = (\n        \"g/cm**3\",\n        \"K\",\n    )\n    ds = fake_random_ds(16, fields=fields, units=units)\n    sp = ds.sphere(\"max\", (1.0, \"Mpc\"))\n    p1 = yt.ProfilePlot(sp, (\"index\", \"radius\"), (\"gas\", \"density\"))\n    p2 = yt.PhasePlot(sp, (\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"mass\"))\n    # make sure we can set the log-scaling using the tuple without erroring out\n    p1.set_log((\"gas\", \"density\"), False)\n    p2.set_log((\"gas\", \"temperature\"), False)\n    assert not p1.y_log[\"gas\", \"density\"]\n    assert not p2.y_log\n\n    # make sure we can set the log-scaling using a string without erroring out\n    p1.set_log((\"gas\", \"density\"), True)\n    p2.set_log((\"gas\", \"temperature\"), True)\n    assert p1.y_log[\"gas\", \"density\"]\n    assert p2.y_log\n\n    # make sure we can set the log-scaling using a field object\n    p1.set_log(ds.fields.gas.density, False)\n    p2.set_log(ds.fields.gas.temperature, False)\n    assert not p1.y_log[\"gas\", \"density\"]\n    assert not p2.y_log\n\n\ndef test_phaseplot_showhide_colorbar_axes():\n    fields = (\"density\", \"temperature\")\n    units = (\n        \"g/cm**3\",\n        \"K\",\n    )\n    ds = fake_random_ds(16, fields=fields, units=units)\n    ad = ds.all_data()\n    plot = yt.PhasePlot(ad, (\"gas\", \"density\"), (\"gas\", \"temperature\"), (\"gas\", \"mass\"))\n\n    # make sure we can hide colorbar\n    plot.hide_colorbar()\n    with tempfile.NamedTemporaryFile(suffix=\"png\") as f1:\n        plot.save(f1.name)\n\n    # make sure we can show colorbar\n    plot.show_colorbar()\n    with tempfile.NamedTemporaryFile(suffix=\"png\") as f2:\n        plot.save(f2.name)\n\n    # make sure we can hide axes\n    plot.hide_axes()\n    with tempfile.NamedTemporaryFile(suffix=\"png\") as f3:\n        plot.save(f3.name)\n\n    # make sure we can show axes\n    plot.show_axes()\n    with tempfile.NamedTemporaryFile(suffix=\"png\") as f4:\n        plot.save(f4.name)\n"
  },
  {
    "path": "yt/visualization/tests/test_raw_field_slices.py",
    "content": "import pytest\n\nimport yt\nfrom yt.testing import requires_file\nfrom yt.utilities.answer_testing.framework import data_dir_load_v2\n\n\nclass TestRawFieldSlice:\n    @requires_file(\"Laser/plt00015\")\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(\"Laser/plt00015\")\n        fields = [\n            (\"raw\", \"Bx\"),\n            (\"raw\", \"By\"),\n            (\"raw\", \"Bz\"),\n            (\"raw\", \"Ex\"),\n            (\"raw\", \"Ey\"),\n            (\"raw\", \"Ez\"),\n            (\"raw\", \"jx\"),\n            (\"raw\", \"jy\"),\n            (\"raw\", \"jz\"),\n        ]\n        cls.sl = yt.SlicePlot(cls.ds, \"z\", fields)\n        cls.sl.set_log(\"all\", False)\n        cls.sl.render()\n\n    @pytest.mark.parametrize(\n        \"fname\", [\"Bx\", \"By\", \"Bz\", \"Ex\", \"Ey\", \"Ez\", \"jx\", \"jy\", \"jz\"]\n    )\n    @pytest.mark.mpl_image_compare\n    def test_raw_field_slice(self, fname):\n        return self.sl.plots[\"raw\", \"Bx\"].figure\n"
  },
  {
    "path": "yt/visualization/tests/test_save.py",
    "content": "import os\nimport re\n\nimport pytest\nfrom PIL import Image\n\nimport yt\nfrom yt.testing import fake_amr_ds\n\n# avoid testing every supported format as some backends may be buggy on testing platforms\nFORMATS_TO_TEST = [\".eps\", \".pdf\", \".svg\"]\n\n\n@pytest.fixture(scope=\"session\")\ndef simple_sliceplot():\n    ds = fake_amr_ds()\n    p = yt.SlicePlot(ds, \"z\", \"Density\")\n    yield p\n\n\ndef test_save_to_path(simple_sliceplot, tmp_path):\n    p = simple_sliceplot\n    p.save(f\"{tmp_path}/\")\n    assert len(list((tmp_path).glob(\"*.png\"))) == 1\n\n\ndef test_metadata(simple_sliceplot, tmp_path):\n    simple_sliceplot.save(tmp_path / \"ala.png\")\n    with Image.open(tmp_path / \"ala.png\") as img:\n        assert \"Software\" in img.info\n        assert \"yt-\" in img.info[\"Software\"]\n    simple_sliceplot.save(tmp_path / \"ala.pdf\")\n    with open(tmp_path / \"ala.pdf\", \"rb\") as f:\n        assert b\"|yt-\" in f.read()\n\n\ndef test_save_to_missing_path(simple_sliceplot, tmp_path):\n    # the missing layer should be created\n    p = simple_sliceplot\n\n    # using forward slashes should work even on windows !\n    save_path = os.path.join(tmp_path / \"out\") + \"/\"\n    p.save(save_path)\n    assert os.path.exists(save_path)\n    assert len(list((tmp_path / \"out\").glob(\"*.png\"))) == 1\n\n\ndef test_save_to_missing_path_with_file_prefix(simple_sliceplot, tmp_path):\n    # see issue\n    # https://github.com/yt-project/yt/issues/3210\n    p = simple_sliceplot\n    p.save(tmp_path.joinpath(\"out\", \"saymyname\"))\n    assert (tmp_path / \"out\").exists()\n    output_files = list((tmp_path / \"out\").glob(\"*.png\"))\n    assert len(output_files) == 1\n    assert output_files[0].stem.startswith(\"saymyname\")  # you're goddamn right\n\n\n@pytest.mark.parametrize(\"ext\", FORMATS_TO_TEST)\ndef test_suffix_from_filename(ext, simple_sliceplot, tmp_path):\n    p = simple_sliceplot\n\n    target = (tmp_path / \"myfile\").with_suffix(ext)\n    # this shouldn't raise a warning, see issue\n    # https://github.com/yt-project/yt/issues/3667\n    p.save(target)\n    assert target.is_file()\n\n\n@pytest.mark.parametrize(\"ext\", FORMATS_TO_TEST)\ndef test_suffix_clashing(ext, simple_sliceplot, tmp_path):\n    if ext == \".png\":\n        pytest.skip()\n\n    p = simple_sliceplot\n\n    target = (tmp_path / \"myfile\").with_suffix(ext)\n    expected_warning = re.compile(\n        rf\"Received two valid image formats {ext.removeprefix('.')!r} \"\n        r\"\\(from filename\\) and 'png' \\(from suffix\\)\\. The former is ignored\\.\"\n    )\n\n    with pytest.warns(UserWarning, match=expected_warning):\n        p.save(target, suffix=\"png\")\n    output_files = list(tmp_path.glob(\"*.png\"))\n    assert len(output_files) == 1\n    assert output_files[0].stem.startswith(\"myfile\")\n    assert not list((tmp_path / \"out\").glob(f\"*.{ext}\"))\n\n\ndef test_invalid_format_from_filename(simple_sliceplot, tmp_path):\n    p = simple_sliceplot\n    target = (tmp_path / \"myfile\").with_suffix(\".nope\")\n\n    p.save(target)\n    output_files = list(tmp_path.glob(\"*\"))\n    assert len(output_files) == 1\n    # the output filename may contain a generated part\n    # it's not exactly clear if it's desirable or intended in this case\n    # so we just check conditions that should hold in any case\n    assert output_files[0].name.startswith(\"myfile.nope\")\n    assert output_files[0].name.endswith(\".png\")\n\n\ndef test_invalid_format_from_suffix(simple_sliceplot, tmp_path):\n    p = simple_sliceplot\n    target = tmp_path / \"myfile\"\n    with pytest.raises(ValueError, match=r\"Unsupported file format 'nope'\"):\n        p.save(target, suffix=\"nope\")\n"
  },
  {
    "path": "yt/visualization/tests/test_set_zlim.py",
    "content": "import numpy as np\nimport numpy.testing as npt\nimport pytest\n\nfrom yt.testing import fake_amr_ds\nfrom yt.visualization.api import SlicePlot\n\n\ndef test_float_vmin_then_set_unit():\n    field = (\"gas\", \"density\")\n    ds = fake_amr_ds(fields=[field], units=[\"g/cm**3\"])\n\n    p = SlicePlot(ds, \"x\", field)\n    p.set_buff_size(16)\n\n    p.render()\n    cb = p.plots[field].image.colorbar\n    raw_lims = np.array((cb.vmin, cb.vmax))\n    desired_lims = raw_lims.copy()\n    desired_lims[0] = 1e-2\n\n    p.set_zlim(field, zmin=desired_lims[0])\n\n    p.render()\n    cb = p.plots[field].image.colorbar\n    new_lims = np.array((cb.vmin, cb.vmax))\n    npt.assert_almost_equal(new_lims, desired_lims)\n\n    # 1 g/cm**3 == 1000 kg/m**3\n    p.set_unit(field, \"kg/m**3\")\n    p.render()\n\n    cb = p.plots[field].image.colorbar\n    new_lims = np.array((cb.vmin, cb.vmax))\n    npt.assert_almost_equal(new_lims, 1000 * desired_lims)\n\n\ndef test_set_unit_then_float_vmin():\n    field = (\"gas\", \"density\")\n    ds = fake_amr_ds(fields=[field], units=[\"g/cm**3\"])\n\n    p = SlicePlot(ds, \"x\", field)\n    p.set_buff_size(16)\n\n    p.set_unit(field, \"kg/m**3\")\n    p.set_zlim(field, zmin=1)\n    p.render()\n    cb = p.plots[field].image.colorbar\n    assert cb.vmin == 1.0\n\n\ndef test_reset_zlim():\n    field = (\"gas\", \"density\")\n    ds = fake_amr_ds(fields=[field], units=[\"g/cm**3\"])\n\n    p = SlicePlot(ds, \"x\", field)\n    p.set_buff_size(16)\n\n    p.render()\n    cb = p.plots[field].image.colorbar\n    raw_lims = np.array((cb.vmin, cb.vmax))\n\n    # set a new zmin value\n    delta = np.diff(raw_lims)[0]\n    p.set_zlim(field, zmin=raw_lims[0] + delta / 2)\n\n    # passing \"min\" should restore default limit\n    p.set_zlim(field, zmin=\"min\")\n    p.render()\n\n    cb = p.plots[field].image.colorbar\n    new_lims = np.array((cb.vmin, cb.vmax))\n    npt.assert_array_equal(new_lims, raw_lims)\n\n\ndef test_set_dynamic_range_with_vmin():\n    field = (\"gas\", \"density\")\n    ds = fake_amr_ds(fields=[field], units=[\"g/cm**3\"])\n\n    p = SlicePlot(ds, \"x\", field)\n    p.set_buff_size(16)\n\n    zmin = 1e-2\n    p.set_zlim(field, zmin=zmin, dynamic_range=2)\n\n    p.render()\n    cb = p.plots[field].image.colorbar\n    new_lims = np.array((cb.vmin, cb.vmax))\n    npt.assert_almost_equal(new_lims, (zmin, 2 * zmin))\n\n\ndef test_set_dynamic_range_with_vmax():\n    field = (\"gas\", \"density\")\n    ds = fake_amr_ds(fields=[field], units=[\"g/cm**3\"])\n\n    p = SlicePlot(ds, \"x\", field)\n    p.set_buff_size(16)\n\n    zmax = 1\n    p.set_zlim(field, zmax=zmax, dynamic_range=2)\n\n    p.render()\n    cb = p.plots[field].image.colorbar\n    new_lims = np.array((cb.vmin, cb.vmax))\n    npt.assert_almost_equal(new_lims, (zmax / 2, zmax))\n\n\ndef test_set_dynamic_range_with_min():\n    field = (\"gas\", \"density\")\n    ds = fake_amr_ds(fields=[field], units=[\"g/cm**3\"])\n\n    p = SlicePlot(ds, \"x\", field)\n    p.set_buff_size(16)\n\n    p.render()\n    cb = p.plots[field].image.colorbar\n    vmin = cb.vmin\n\n    p.set_zlim(field, zmin=\"min\", dynamic_range=2)\n\n    p.render()\n    cb = p.plots[field].image.colorbar\n    new_lims = np.array((cb.vmin, cb.vmax))\n    npt.assert_almost_equal(new_lims, (vmin, 2 * vmin))\n\n\ndef test_set_dynamic_range_with_None():\n    field = (\"gas\", \"density\")\n    ds = fake_amr_ds(fields=[field], units=[\"g/cm**3\"])\n\n    p = SlicePlot(ds, \"x\", field)\n    p.set_buff_size(16)\n\n    p.render()\n    cb = p.plots[field].image.colorbar\n    vmin = cb.vmin\n\n    with pytest.deprecated_call(match=\"Passing `zmin=None` explicitly is deprecated\"):\n        p.set_zlim(field, zmin=None, dynamic_range=2)\n\n    p.render()\n    cb = p.plots[field].image.colorbar\n    new_lims = np.array((cb.vmin, cb.vmax))\n    npt.assert_almost_equal(new_lims, (vmin, 2 * vmin))\n"
  },
  {
    "path": "yt/visualization/tests/test_splat.py",
    "content": "import os\nimport os.path\nimport shutil\nimport tempfile\n\nimport matplotlib as mpl\nimport numpy as np\nfrom numpy.testing import assert_equal\n\nimport yt\nfrom yt.utilities.lib.api import add_rgba_points_to_image  # type: ignore\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\ndef test_splat():\n    # Perform I/O in safe place instead of yt main dir\n    tmpdir = tempfile.mkdtemp()\n    curdir = os.getcwd()\n    os.chdir(tmpdir)\n\n    prng = np.random.RandomState(0x4D3D3D3)\n    N = 16\n    Np = int(1e2)\n    image = np.zeros([N, N, 4])\n    xs = prng.random_sample(Np)\n    ys = prng.random_sample(Np)\n\n    cbx = mpl.colormaps[\"RdBu\"]\n    cs = cbx(prng.random_sample(Np))\n    add_rgba_points_to_image(image, xs, ys, cs)\n\n    before_hash = image.copy()\n    fn = \"tmp.png\"\n    yt.write_bitmap(image, fn)\n    assert_equal(os.path.exists(fn), True)\n    os.remove(fn)\n    assert_equal(before_hash, image)\n\n    os.chdir(curdir)\n    # clean up\n    shutil.rmtree(tmpdir)\n"
  },
  {
    "path": "yt/visualization/volume_rendering/UBVRI.py",
    "content": "import numpy as np\n\njohnson_filters = {\n    \"B\": {\n        \"wavelen\": np.array(\n            [\n                3600,\n                3650,\n                3700,\n                3750,\n                3800,\n                3850,\n                3900,\n                3950,\n                4000,\n                4050,\n                4100,\n                4150,\n                4200,\n                4250,\n                4300,\n                4350,\n                4400,\n                4450,\n                4500,\n                4550,\n                4600,\n                4650,\n                4700,\n                4750,\n                4800,\n                4850,\n                4900,\n                4950,\n                5000,\n                5050,\n                5100,\n                5150,\n                5200,\n                5250,\n                5300,\n                5350,\n                5400,\n                5450,\n                5500,\n                5550,\n            ],\n            dtype=\"float64\",\n        ),\n        \"trans\": np.array(\n            [\n                0.0,\n                0.0,\n                0.02,\n                0.05,\n                0.11,\n                0.18,\n                0.35,\n                0.55,\n                0.92,\n                0.95,\n                0.98,\n                0.99,\n                1.0,\n                0.99,\n                0.98,\n                0.96,\n                0.94,\n                0.91,\n                0.87,\n                0.83,\n                0.79,\n                0.74,\n                0.69,\n                0.63,\n                0.58,\n                0.52,\n                0.46,\n                0.41,\n                0.36,\n                0.3,\n                0.25,\n                0.2,\n                0.15,\n                0.12,\n                0.09,\n                0.06,\n                0.04,\n                0.02,\n                0.01,\n                0.0,\n            ],\n            dtype=\"float64\",\n        ),\n    },\n    \"I\": {\n        \"wavelen\": np.array(\n            [\n                6800,\n                6850,\n                6900,\n                6950,\n                7000,\n                7050,\n                7100,\n                7150,\n                7200,\n                7250,\n                7300,\n                7350,\n                7400,\n                7450,\n                7500,\n                7550,\n                7600,\n                7650,\n                7700,\n                7750,\n                7800,\n                7850,\n                7900,\n                7950,\n                8000,\n                8050,\n                8100,\n                8150,\n                8200,\n                8250,\n                8300,\n                8350,\n                8400,\n                8450,\n                8500,\n                8550,\n                8600,\n                8650,\n                8700,\n                8750,\n                8800,\n                8850,\n                8900,\n                8950,\n                9000,\n                9050,\n                9100,\n                9150,\n                9200,\n                9250,\n                9300,\n                9350,\n                9400,\n                9450,\n                9500,\n                9550,\n                9600,\n                9650,\n                9700,\n                9750,\n                9800,\n                9850,\n                9900,\n                9950,\n                10000,\n                10050,\n                10100,\n                10150,\n                10200,\n                10250,\n                10300,\n                10350,\n                10400,\n                10450,\n                10500,\n                10550,\n                10600,\n                10650,\n                10700,\n                10750,\n                10800,\n                10850,\n                10900,\n                10950,\n                11000,\n                11050,\n                11100,\n                11150,\n                11200,\n                11250,\n                11300,\n                11350,\n                11400,\n                11450,\n                11500,\n                11550,\n                11600,\n                11650,\n                11700,\n                11750,\n                11800,\n                11850,\n            ],\n            dtype=\"float64\",\n        ),\n        \"trans\": np.array(\n            [\n                0.0,\n                0.0,\n                0.01,\n                0.01,\n                0.01,\n                0.04,\n                0.08,\n                0.13,\n                0.17,\n                0.21,\n                0.26,\n                0.3,\n                0.36,\n                0.4,\n                0.44,\n                0.49,\n                0.56,\n                0.6,\n                0.65,\n                0.72,\n                0.76,\n                0.84,\n                0.9,\n                0.93,\n                0.96,\n                0.97,\n                0.97,\n                0.98,\n                0.98,\n                0.99,\n                0.99,\n                0.99,\n                0.99,\n                1.0,\n                1.0,\n                1.0,\n                1.0,\n                1.0,\n                0.99,\n                0.98,\n                0.98,\n                0.97,\n                0.96,\n                0.94,\n                0.93,\n                0.9,\n                0.88,\n                0.86,\n                0.84,\n                0.8,\n                0.76,\n                0.74,\n                0.71,\n                0.68,\n                0.65,\n                0.61,\n                0.58,\n                0.56,\n                0.52,\n                0.5,\n                0.47,\n                0.44,\n                0.42,\n                0.39,\n                0.36,\n                0.34,\n                0.32,\n                0.3,\n                0.28,\n                0.26,\n                0.24,\n                0.22,\n                0.2,\n                0.19,\n                0.17,\n                0.16,\n                0.15,\n                0.13,\n                0.12,\n                0.11,\n                0.1,\n                0.09,\n                0.09,\n                0.08,\n                0.08,\n                0.07,\n                0.06,\n                0.05,\n                0.05,\n                0.04,\n                0.04,\n                0.03,\n                0.03,\n                0.02,\n                0.02,\n                0.02,\n                0.02,\n                0.02,\n                0.01,\n                0.01,\n                0.01,\n                0.0,\n            ],\n            dtype=\"float64\",\n        ),\n    },\n    \"R\": {\n        \"wavelen\": np.array(\n            [\n                5200,\n                5250,\n                5300,\n                5350,\n                5400,\n                5450,\n                5500,\n                5550,\n                5600,\n                5650,\n                5700,\n                5750,\n                5800,\n                5850,\n                5900,\n                5950,\n                6000,\n                6050,\n                6100,\n                6150,\n                6200,\n                6250,\n                6300,\n                6350,\n                6400,\n                6450,\n                6500,\n                6550,\n                6600,\n                6650,\n                6700,\n                6750,\n                6800,\n                6850,\n                6900,\n                6950,\n                7000,\n                7050,\n                7100,\n                7150,\n                7200,\n                7250,\n                7300,\n                7350,\n                7400,\n                7450,\n                7500,\n                7550,\n                7600,\n                7650,\n                7700,\n                7750,\n                7800,\n                7850,\n                7900,\n                7950,\n                8000,\n                8050,\n                8100,\n                8150,\n                8200,\n                8250,\n                8300,\n                8350,\n                8400,\n                8450,\n                8500,\n                8550,\n                8600,\n                8650,\n                8700,\n                8750,\n                8800,\n                8850,\n                8900,\n                8950,\n                9000,\n                9050,\n                9100,\n                9150,\n                9200,\n                9250,\n                9300,\n                9350,\n                9400,\n                9450,\n                9500,\n            ],\n            dtype=\"float64\",\n        ),\n        \"trans\": np.array(\n            [\n                0.0,\n                0.01,\n                0.02,\n                0.04,\n                0.06,\n                0.11,\n                0.18,\n                0.23,\n                0.28,\n                0.34,\n                0.4,\n                0.46,\n                0.5,\n                0.55,\n                0.6,\n                0.64,\n                0.69,\n                0.71,\n                0.74,\n                0.77,\n                0.79,\n                0.81,\n                0.84,\n                0.86,\n                0.88,\n                0.9,\n                0.91,\n                0.92,\n                0.94,\n                0.95,\n                0.96,\n                0.97,\n                0.98,\n                0.99,\n                0.99,\n                1.0,\n                1.0,\n                0.99,\n                0.98,\n                0.96,\n                0.94,\n                0.92,\n                0.9,\n                0.88,\n                0.85,\n                0.83,\n                0.8,\n                0.77,\n                0.73,\n                0.7,\n                0.66,\n                0.62,\n                0.57,\n                0.53,\n                0.49,\n                0.45,\n                0.42,\n                0.39,\n                0.36,\n                0.34,\n                0.31,\n                0.27,\n                0.22,\n                0.19,\n                0.17,\n                0.15,\n                0.13,\n                0.12,\n                0.11,\n                0.1,\n                0.08,\n                0.07,\n                0.06,\n                0.06,\n                0.05,\n                0.04,\n                0.04,\n                0.03,\n                0.03,\n                0.02,\n                0.02,\n                0.02,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.0,\n            ],\n            dtype=\"float64\",\n        ),\n    },\n    \"U\": {\n        \"wavelen\": np.array(\n            [\n                3000,\n                3050,\n                3100,\n                3150,\n                3200,\n                3250,\n                3300,\n                3350,\n                3400,\n                3450,\n                3500,\n                3550,\n                3600,\n                3650,\n                3700,\n                3750,\n                3800,\n                3850,\n                3900,\n                3950,\n                4000,\n                4050,\n                4100,\n                4150,\n            ],\n            dtype=\"float64\",\n        ),\n        \"trans\": np.array(\n            [\n                0.0,\n                0.04,\n                0.1,\n                0.25,\n                0.61,\n                0.75,\n                0.84,\n                0.88,\n                0.93,\n                0.95,\n                0.97,\n                0.99,\n                1.0,\n                0.99,\n                0.97,\n                0.92,\n                0.73,\n                0.56,\n                0.36,\n                0.23,\n                0.05,\n                0.03,\n                0.01,\n                0.0,\n            ],\n            dtype=\"float64\",\n        ),\n    },\n    \"V\": {\n        \"wavelen\": np.array(\n            [\n                4600,\n                4650,\n                4700,\n                4750,\n                4800,\n                4850,\n                4900,\n                4950,\n                5000,\n                5050,\n                5100,\n                5150,\n                5200,\n                5250,\n                5300,\n                5350,\n                5400,\n                5450,\n                5500,\n                5550,\n                5600,\n                5650,\n                5700,\n                5750,\n                5800,\n                5850,\n                5900,\n                5950,\n                6000,\n                6050,\n                6100,\n                6150,\n                6200,\n                6250,\n                6300,\n                6350,\n                6400,\n                6450,\n                6500,\n                6550,\n                6600,\n                6650,\n                6700,\n                6750,\n                6800,\n                6850,\n                6900,\n                6950,\n                7000,\n                7050,\n                7100,\n                7150,\n                7200,\n                7250,\n                7300,\n                7350,\n            ],\n            dtype=\"float64\",\n        ),\n        \"trans\": np.array(\n            [\n                0.0,\n                0.0,\n                0.01,\n                0.01,\n                0.02,\n                0.05,\n                0.11,\n                0.2,\n                0.38,\n                0.67,\n                0.78,\n                0.85,\n                0.91,\n                0.94,\n                0.96,\n                0.98,\n                0.98,\n                0.95,\n                0.87,\n                0.79,\n                0.72,\n                0.71,\n                0.69,\n                0.65,\n                0.62,\n                0.58,\n                0.52,\n                0.46,\n                0.4,\n                0.34,\n                0.29,\n                0.24,\n                0.2,\n                0.17,\n                0.14,\n                0.11,\n                0.08,\n                0.06,\n                0.05,\n                0.03,\n                0.02,\n                0.02,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.01,\n                0.0,\n            ],\n            dtype=\"float64\",\n        ),\n    },\n}\n\nfor vals in johnson_filters.values():\n    wavelen = vals[\"wavelen\"]\n    trans = vals[\"trans\"]\n    vals[\"Lchar\"] = wavelen[np.argmax(trans)]\n"
  },
  {
    "path": "yt/visualization/volume_rendering/__init__.py",
    "content": "\"\"\"\nAPI for yt.visualization.volume_rendering\n\n\n\n\"\"\"\n"
  },
  {
    "path": "yt/visualization/volume_rendering/_cuda_caster.cu",
    "content": "// An attempt at putting the ray-casting operation into CUDA\n//extern __shared__ float array[];\n\n\n#define NUM_SAMPLES 25\n#define VINDEX(A,B,C) tg.data[((((A)+ci[0])*(tg.dims[1]+1)+((B)+ci[1]))*(tg.dims[2]+1)+ci[2]+(C))]\n\n#define fmin(A, B) ( A * (A < B) + B * (B < A) )\n#define fmax(A, B) ( A * (A > B) + B * (B > A) )\n#define fclip(A, B, C) ( fmax( fmin(A, C), B) )\n\nstruct transfer_function\n{\n    float vs[4][256];\n    float dbin;\n    float bounds[2];\n};\n\n__shared__ struct transfer_function tf;\n\nstruct grid\n{\n    float left_edge[3];\n    float right_edge[3];\n    float dds[3];\n    int dims[3];\n    float *data;\n};\n\n__shared__ struct grid tg;\n\n__device__ float interpolate(float *cache, int *ds, int *ci, float *dp)\n{\n    int i;\n    float dv, dm[3];\n    for(i=0;i<3;i++)dm[i] = (1.0 - dp[i]);\n    dv  = 0.0;\n    dv += cache[0] * (dm[0]*dm[1]*dm[2]);\n    dv += cache[1] * (dm[0]*dm[1]*dp[2]);\n    dv += cache[2] * (dm[0]*dp[1]*dm[2]);\n    dv += cache[3] * (dm[0]*dp[1]*dp[2]);\n    dv += cache[4] * (dp[0]*dm[1]*dm[2]);\n    dv += cache[5] * (dp[0]*dm[1]*dp[2]);\n    dv += cache[6] * (dp[0]*dp[1]*dm[2]);\n    dv += cache[7] * (dp[0]*dp[1]*dp[2]);\n    return dv;\n}\n\n__device__ void eval_transfer(float dt, float dv, float *rgba,\n                               transfer_function &tf)\n{\n    int i, bin_id;\n    float temp, bv, dy, dd, ta;\n\n    bin_id = (int) ((dv - tf.bounds[0]) / tf.dbin);\n    bv = tf.vs[3][bin_id  ];\n    dy = tf.vs[3][bin_id+1] - bv;\n    dd = dv - (tf.bounds[0] + bin_id*tf.dbin);\n    temp = bv+dd*(dy/tf.dbin);\n    ta = temp;\n    for (i = 0; i < 3; i++)\n    {\n        bv = tf.vs[i][bin_id  ];\n        dy = tf.vs[i][bin_id+1];\n        dd = dv - (tf.bounds[0] + bin_id*tf.dbin);\n        temp = bv+dd*(dy/tf.dbin);\n        rgba[i] += (1.0 - rgba[3])*ta*temp*dt;\n    }\n    //rgba[3] += (1.0 - rgba[3])*ta*dt;\n}\n\n__device__ void sample_values(float v_pos[3], float v_dir[3],\n                   float enter_t, float exit_t, int ci[3], float rgba[4],\n                   transfer_function &tf, grid &tg)\n{\n    float cp[3], dp[3], dt, t, dv;\n    int dti, i;\n    float cache[8];\n\n    cache[0] = VINDEX(0,0,0);\n    cache[1] = VINDEX(0,0,1);\n    cache[2] = VINDEX(0,1,0);\n    cache[3] = VINDEX(0,1,1);\n    cache[4] = VINDEX(1,0,0);\n    cache[5] = VINDEX(1,0,1);\n    cache[6] = VINDEX(1,1,0);\n    cache[7] = VINDEX(1,1,1);\n\n    dt = (exit_t - enter_t) / (NUM_SAMPLES-1);\n    for (dti = 0; dti < NUM_SAMPLES - 1; dti++)\n    {\n        t = enter_t + dt*dti;\n        for (i = 0; i < 3; i++)\n        {\n            cp[i] = v_pos[i] + t * v_dir[i];\n            dp[i] = fclip(fmod(cp[i], tg.dds[i])/tg.dds[i], 0.0, 1.0);\n        }\n        dv = interpolate(cache, tg.dims, ci, dp);\n        eval_transfer(dt, dv, rgba, tf);\n    }\n}\n\n\n/* We need to know several things if we want to ray cast through a grid.\n   We need the grid spatial information, as well as its values.  We also need\n   the transfer function, which defines what our image will look like. */\n\n__global__ void ray_cast(int ngrids,\n                         float *grid_data,\n                         int *dims,\n                         float *left_edge,\n                         float *right_edge,\n                         float *tf_r,\n                         float *tf_g,\n                         float *tf_b,\n                         float *tf_a,\n                         float *tf_bounds,\n                         float *v_dir,\n                         float *av_pos,\n                         float *image_r,\n                         float *image_g,\n                         float *image_b,\n                         float *image_a)\n{\n\n    int cur_ind[3], step[3], x, y, i, direction;\n    float intersect_t = 1.0, intersect_ts[3];\n    float tmax[3];\n    float tl, tr, temp_xl, temp_yl, temp_xr, temp_yr;\n\n    int offset;\n\n    //transfer_function tf;\n    for (i = 0; i < 4; i++)\n    {\n        x = 4 * (8 * threadIdx.x + threadIdx.y) + i;\n        tf.vs[0][x] = tf_r[x];\n        tf.vs[1][x] = tf_g[x];\n        tf.vs[2][x] = tf_b[x];\n        tf.vs[3][x] = tf_a[x];\n    }\n\n    tf.bounds[0] = tf_bounds[0]; tf.bounds[1] = tf_bounds[1];\n    tf.dbin = (tf.bounds[1] - tf.bounds[0])/255.0;\n\n    /* Set up the grid, just for convenience */\n    //grid tg;\n    int grid_i;\n\n    int tidx = (blockDim.x * gridDim.x) * (\n                    blockDim.y * blockIdx.y + threadIdx.y)\n             + (blockDim.x * blockIdx.x + threadIdx.x);\n\n    float rgba[4];\n    //rgba[0] = image_r[tidx];\n    //rgba[1] = image_g[tidx];\n    //rgba[2] = image_b[tidx];\n    //rgba[3] = image_a[tidx];\n\n    float v_pos[3];\n    v_pos[0] = av_pos[tidx + 0];\n    v_pos[1] = av_pos[tidx + 1];\n    v_pos[2] = av_pos[tidx + 2];\n\n    tg.data = grid_data;\n    int skip;\n    for (i = 0; i < 3; i++)\n    {\n        step[i] = 0;\n        step[i] +=      (v_dir[i] > 0);\n        step[i] += -1 * (v_dir[i] < 0);\n    }\n\n    for(grid_i = 0; grid_i < ngrids; grid_i++) {\n        skip = 0;\n\n        if (threadIdx.x == 0)\n        {\n            if (threadIdx.y == 0) tg.dims[0] = dims[3*grid_i + 0];\n            if (threadIdx.y == 1) tg.dims[1] = dims[3*grid_i + 1];\n            if (threadIdx.y == 2) tg.dims[2] = dims[3*grid_i + 2];\n        }\n\n        if (threadIdx.x == 1)\n        {\n            if (threadIdx.y == 0) tg.left_edge[0] = left_edge[3*grid_i + 0];\n            if (threadIdx.y == 1) tg.left_edge[1] = left_edge[3*grid_i + 1];\n            if (threadIdx.y == 2) tg.left_edge[2] = left_edge[3*grid_i + 2];\n        }\n\n        if (threadIdx.x == 2) {\n            if (threadIdx.y == 0) tg.right_edge[0] = right_edge[3*grid_i + 0];\n            if (threadIdx.y == 1) tg.right_edge[1] = right_edge[3*grid_i + 1];\n            if (threadIdx.y == 2) tg.right_edge[2] = right_edge[3*grid_i + 2];\n        }\n\n        if (threadIdx.x == 3) {\n            if (threadIdx.y == 0) tg.dds[0] = (tg.right_edge[0] - tg.left_edge[0])/tg.dims[0];\n            if (threadIdx.y == 1) tg.dds[1] = (tg.right_edge[1] - tg.left_edge[1])/tg.dims[1];\n            if (threadIdx.y == 2) tg.dds[2] = (tg.right_edge[2] - tg.left_edge[2])/tg.dims[2];\n        }\n\n        /* We integrate our ray */\n\n        for (i = 0; i < 3; i++)\n        {\n            x = (i + 1) % 3;\n            y = (i + 2) % 3;\n\n            tl = (tg.left_edge[i] - v_pos[i])/v_dir[i];\n            temp_xl = (v_pos[i] + tl*v_dir[x]);\n            temp_yr = (v_pos[i] + tl*v_dir[y]);\n\n            tr = (tg.right_edge[i] - v_pos[i])/v_dir[i];\n            temp_xr = (v_pos[x] + tr*v_dir[x]);\n            temp_yr = (v_pos[y] + tr*v_dir[y]);\n\n            intersect_ts[i] = 1.0;\n\n            intersect_ts[i] +=\n              ( (tg.left_edge[x] <= temp_xl) &&\n                (temp_xl <= tg.right_edge[x]) &&\n                (tg.left_edge[y] <= temp_yl) &&\n                (temp_yl <= tg.right_edge[y]) &&\n                (0.0 <= tl) && (tl < intersect_ts[i]) && (tl < tr) ) * tl;\n\n            intersect_ts[i] +=\n              ( (tg.left_edge[x] <= temp_xr) &&\n                (temp_xr <= tg.right_edge[x]) &&\n                (tg.left_edge[y] <= temp_yr) &&\n                (temp_yr <= tg.right_edge[y]) &&\n                (0.0 <= tr) && (tr < intersect_ts[i]) && (tr < tl) ) * tr;\n\n            intersect_t = ( intersect_ts[i] < intersect_t) * intersect_ts[i];\n\n        }\n\n        intersect_t *= (!( (tg.left_edge[0] <= v_pos[0]) &&\n                           (v_pos[0] <= tg.right_edge[0]) &&\n                           (tg.left_edge[0] <= v_pos[0]) &&\n                           (v_pos[0] <= tg.right_edge[0]) &&\n                           (tg.left_edge[0] <= v_pos[0]) &&\n                           (v_pos[0] <= tg.right_edge[0])));\n\n        skip = ((intersect_t < 0) || (intersect_t > 1.0));\n\n        for (i = 0; i < 3;  i++)\n        {\n            cur_ind[i] = (int) floor(((v_pos[i] + intersect_t * v_dir[i]) +\n                        step[i]*1e-7*tg.dds[i] -\n                        tg.left_edge[i])/tg.dds[i]);\n            tmax[i] = (((cur_ind[i]+step[i])*tg.dds[i])+\n                    tg.left_edge[i]-v_pos[i])/v_dir[i];\n            cur_ind[i] -= ((cur_ind[i] == tg.dims[i]) && (step[i] < 0));\n            skip = ((cur_ind[i] < 0) || (cur_ind[i] >= tg.dims[i]));\n            offset = (step[i] > 0);\n            tmax[i] = (((cur_ind[i]+offset)*tg.dds[i])+tg.left_edge[i]-v_pos[i])/v_dir[i];\n        }\n\n        /* This is the primary grid walking loop */\n        while(!( (skip)\n              ||((cur_ind[0] < 0) || (cur_ind[0] >= tg.dims[0])\n              || (cur_ind[1] < 0) || (cur_ind[1] >= tg.dims[1])\n              || (cur_ind[2] < 0) || (cur_ind[2] >= tg.dims[2]))))\n        {\n            direction = 0;\n            direction += 2 * (tmax[0] <  tmax[1]) * (tmax[0] >= tmax[2]);\n            direction += 1 * (tmax[0] >= tmax[1]) * (tmax[1] <  tmax[2]);\n            direction += 2 * (tmax[0] >= tmax[1]) * (tmax[1] >= tmax[2]);\n            sample_values(v_pos, v_dir, intersect_t, tmax[direction],\n                    cur_ind, rgba, tf, tg);\n            cur_ind[direction] += step[direction];\n            intersect_t = tmax[direction];\n            tmax[direction] += abs(tg.dds[direction]/v_dir[direction]);\n        }\n\n        tg.data += (tg.dims[0]+1) * (tg.dims[1]+1) * (tg.dims[2]+1);\n\n    }\n\n    int iy = threadIdx.y + blockDim.y * blockIdx.y;\n    int ix = threadIdx.x + blockDim.x * blockIdx.x;\n    __syncthreads();\n\n    image_r[tidx] = rgba[0];\n    image_g[tidx] = rgba[1];\n    image_b[tidx] = rgba[2];\n    image_a[tidx] = rgba[3];\n}\n"
  },
  {
    "path": "yt/visualization/volume_rendering/api.py",
    "content": "from .camera import Camera\nfrom .image_handling import export_rgba, import_rgba, plot_channel, plot_rgb\nfrom .off_axis_projection import off_axis_projection\nfrom .render_source import (\n    BoxSource,\n    CoordinateVectorSource,\n    GridSource,\n    LineSource,\n    MeshSource,\n    OpaqueSource,\n    PointSource,\n    create_volume_source,\n    set_raytracing_engine,\n)\nfrom .scene import Scene\nfrom .transfer_function_helper import TransferFunctionHelper\nfrom .transfer_functions import (\n    ColorTransferFunction,\n    MultiVariateTransferFunction,\n    PlanckTransferFunction,\n    ProjectionTransferFunction,\n    TransferFunction,\n)\nfrom .volume_rendering import create_scene, volume_render\nfrom .zbuffer_array import ZBuffer\n"
  },
  {
    "path": "yt/visualization/volume_rendering/blenders.py",
    "content": "import numpy as np\n\n\ndef enhance(im, stdval=6.0, just_alpha=True):\n    if just_alpha:\n        nz = im[im > 0.0]\n        im[:] = im[:] / (nz.mean() + stdval * np.std(nz))\n    else:\n        for c in range(3):\n            nz = im[:, :, c][im[:, :, c] > 0.0]\n            im[:, :, c] = im[:, :, c] / (nz.mean() + stdval * np.std(nz))\n            del nz\n    np.clip(im, 0.0, 1.0, im)\n\n\ndef enhance_rgba(im, stdval=6.0):\n    nzc = im[:, :, :3][im[:, :, :3] > 0.0]\n    cmax = nzc.mean() + stdval * nzc.std()\n\n    nza = im[:, :, 3][im[:, :, 3] > 0.0]\n    if len(nza) == 0:\n        im[:, :, 3] = 1.0\n        amax = 1.0\n    else:\n        amax = nza.mean() + stdval * nza.std()\n\n    im.rescale(amax=amax, cmax=cmax, inline=True)\n    np.clip(im, 0.0, 1.0, im)\n"
  },
  {
    "path": "yt/visualization/volume_rendering/camera.py",
    "content": "import weakref\nfrom numbers import Number as numeric_type\n\nimport numpy as np\n\nfrom yt.funcs import ensure_numpy_array, is_sequence\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.math_utils import get_rotation_matrix\nfrom yt.utilities.orientation import Orientation\n\nfrom .lens import Lens, lenses\nfrom .utils import data_source_or_all\n\n\ndef _sanitize_camera_property_units(value, scene):\n    if is_sequence(value):\n        if len(value) == 1:\n            return _sanitize_camera_property_units(value[0], scene)\n        elif isinstance(value, YTArray) and len(value) == 3:\n            return scene.arr(value).in_units(\"unitary\")\n        elif (\n            len(value) == 2\n            and isinstance(value[0], numeric_type)\n            and isinstance(value[1], str)\n        ):\n            return scene.arr([scene.arr(value[0], value[1]).in_units(\"unitary\")] * 3)\n        if len(value) == 3:\n            if all(is_sequence(v) for v in value):\n                if all(\n                    isinstance(v[0], numeric_type) and isinstance(v[1], str)\n                    for v in value\n                ):\n                    return scene.arr([scene.arr(v[0], v[1]) for v in value])\n                else:\n                    raise RuntimeError(\n                        f\"Cannot set camera width to invalid value '{value}'\"\n                    )\n            return scene.arr(value, \"unitary\")\n    else:\n        if isinstance(value, (YTQuantity, YTArray)):\n            return scene.arr([value.d] * 3, value.units).in_units(\"unitary\")\n        elif isinstance(value, numeric_type):\n            return scene.arr([value] * 3, \"unitary\")\n    raise RuntimeError(f\"Cannot set camera width to invalid value '{value}'\")\n\n\nclass Camera(Orientation):\n    r\"\"\"A representation of a point of view into a Scene.\n\n    It is defined by a position (the location of the camera\n    in the simulation domain,), a focus (the point at which the\n    camera is pointed), a width (the width of the snapshot that will\n    be taken, a resolution (the number of pixels in the image), and\n    a north_vector (the \"up\" direction in the resulting image). A\n    camera can use a variety of different Lens objects.\n\n    Parameters\n    ----------\n    scene: A :class:`yt.visualization.volume_rendering.scene.Scene` object\n        A scene object that the camera will be attached to.\n    data_source: :class:`AMR3DData` or :class:`Dataset`, optional\n        This is the source to be rendered, which can be any arbitrary yt\n        data object or dataset.\n    lens_type: string, optional\n        This specifies the type of lens to use for rendering. Current\n        options are 'plane-parallel', 'perspective', and 'fisheye'. See\n        :class:`yt.visualization.volume_rendering.lens.Lens` for details.\n        Default: 'plane-parallel'\n    auto: boolean\n        If True, build smart defaults using the data source extent. This\n        can be time-consuming to iterate over the entire dataset to find\n        the positional bounds. Default: False\n\n    Examples\n    --------\n\n    In this example, the camera is set using defaults that are chosen\n    to be reasonable for the argument Dataset.\n\n    >>> import yt\n    >>> from yt.visualization.volume_rendering.api import Scene\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> sc = Scene()\n    >>> cam = sc.add_camera(ds)\n\n    Here, we set the camera properties manually:\n\n    >>> import yt\n    >>> from yt.visualization.volume_rendering.api import Scene\n    >>> sc = Scene()\n    >>> cam = sc.add_camera()\n    >>> cam.position = np.array([0.5, 0.5, -1.0])\n    >>> cam.focus = np.array([0.5, 0.5, 0.0])\n    >>> cam.north_vector = np.array([1.0, 0.0, 0.0])\n\n    Finally, we create a camera with a non-default lens:\n\n    >>> import yt\n    >>> from yt.visualization.volume_rendering.api import Scene\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> sc = Scene()\n    >>> cam = sc.add_camera(ds, lens_type=\"perspective\")\n\n    \"\"\"\n\n    _moved = True\n    _width = None\n    _focus = None\n    _position = None\n    _resolution = None\n\n    def __init__(self, scene, data_source=None, lens_type=\"plane-parallel\", auto=False):\n        # import this here to avoid an import cycle\n        from .scene import Scene\n\n        if not isinstance(scene, Scene):\n            raise RuntimeError(\n                \"The first argument passed to the Camera initializer is a \"\n                f\"{type(scene)} object, expected a {Scene} object\"\n            )\n        self.scene = weakref.proxy(scene)\n        self.lens = None\n        self.north_vector = None\n        self.normal_vector = None\n        self.light = None\n        self.data_source = data_source_or_all(data_source)\n        self._resolution = (512, 512)\n\n        if self.data_source is not None:\n            self.scene._set_new_unit_registry(self.data_source.ds.unit_registry)\n            self._focus = self.data_source.ds.domain_center\n            self._position = self.data_source.ds.domain_right_edge\n            self._width = self.data_source.ds.arr(\n                [1.5 * self.data_source.ds.domain_width.max()] * 3\n            )\n            self._domain_center = self.data_source.ds.domain_center\n            self._domain_width = self.data_source.ds.domain_width\n        else:\n            self._focus = scene.arr([0.0, 0.0, 0.0], \"unitary\")\n            self._width = scene.arr([1.0, 1.0, 1.0], \"unitary\")\n            self._position = scene.arr([1.0, 1.0, 1.0], \"unitary\")\n\n        if auto:\n            self.set_defaults_from_data_source(data_source)\n\n        super().__init__(\n            self.focus - self.position, self.north_vector, steady_north=False\n        )\n\n        self.set_lens(lens_type)\n\n    @property\n    def position(self):\n        r\"\"\"\n        The location of the camera.\n\n        Parameters\n        ----------\n\n        position : number, YTQuantity, :obj:`!iterable`, or 3 element YTArray\n            If a scalar, assumes that the position is the same in all three\n            coordinates. If an iterable, must contain only scalars or\n            (length, unit) tuples.\n        \"\"\"\n        return self._position\n\n    @position.setter\n    def position(self, value):\n        position = _sanitize_camera_property_units(value, self.scene)\n        if np.array_equal(position, self.focus):\n            raise RuntimeError(\n                \"Cannot set the camera focus and position to the same value\"\n            )\n        self._position = position\n        self.switch_orientation(\n            normal_vector=self.focus - self._position,\n            north_vector=self.north_vector,\n        )\n\n    @position.deleter\n    def position(self):\n        del self._position\n\n    @property\n    def width(self):\n        r\"\"\"The width of the region that will be seen in the image.\n\n        Parameters\n        ----------\n\n        width : number, YTQuantity, :obj:`!iterable`, or 3 element YTArray\n            The width of the volume rendering in the horizontal, vertical, and\n            depth directions. If a scalar, assumes that the width is the same in\n            all three directions. If an iterable, must contain only scalars or\n            (length, unit) tuples.\n        \"\"\"\n        return self._width\n\n    @width.setter\n    def width(self, value):\n        width = _sanitize_camera_property_units(value, self.scene)\n        self._width = width\n        self.switch_orientation()\n\n    @width.deleter\n    def width(self):\n        del self._width\n        self._width = None\n\n    @property\n    def focus(self):\n        r\"\"\"\n        The focus defines the point the Camera is pointed at.\n\n        Parameters\n        ----------\n\n        focus : number, YTQuantity, :obj:`!iterable`, or 3 element YTArray\n            The width of the volume rendering in the horizontal, vertical, and\n            depth directions. If a scalar, assumes that the width is the same in\n            all three directions. If an iterable, must contain only scalars or\n            (length, unit) tuples.\n        \"\"\"\n        return self._focus\n\n    @focus.setter\n    def focus(self, value):\n        focus = _sanitize_camera_property_units(value, self.scene)\n        if np.array_equal(focus, self.position):\n            raise RuntimeError(\n                \"Cannot set the camera focus and position to the same value\"\n            )\n        self._focus = focus\n        self.switch_orientation(\n            normal_vector=self.focus - self._position, north_vector=None\n        )\n\n    @focus.deleter\n    def focus(self):\n        del self._focus\n\n    @property\n    def resolution(self):\n        r\"\"\"The resolution is the number of pixels in the image that\n        will be produced. Must be a 2-tuple of integers or an integer.\"\"\"\n        return self._resolution\n\n    @resolution.setter\n    def resolution(self, value):\n        if is_sequence(value):\n            if len(value) != 2:\n                raise RuntimeError\n        else:\n            value = (value, value)\n        self._resolution = value\n\n    @resolution.deleter\n    def resolution(self):\n        del self._resolution\n        self._resolution = None\n\n    def set_resolution(self, resolution):\n        \"\"\"\n        The resolution is the number of pixels in the image that\n        will be produced. Must be a 2-tuple of integers or an integer.\n        \"\"\"\n        self.resolution = resolution\n\n    def get_resolution(self):\n        \"\"\"\n        Returns the resolution of the volume rendering\n        \"\"\"\n        return self.resolution\n\n    def _get_sampler_params(self, render_source):\n        lens_params = self.lens._get_sampler_params(self, render_source)\n        lens_params.update(width=self.width)\n        pos = self.position.in_units(\"code_length\").d\n        width = self.width.in_units(\"code_length\").d\n        lens_params.update(camera_data=np.vstack((pos, width, self.unit_vectors.d)))\n        return lens_params\n\n    def set_lens(self, lens_type):\n        r\"\"\"Set the lens to be used with this camera.\n\n        Parameters\n        ----------\n\n        lens_type : string\n            Must be one of the following:\n            'plane-parallel'\n            'perspective'\n            'stereo-perspective'\n            'fisheye'\n            'spherical'\n            'stereo-spherical'\n\n        \"\"\"\n        if isinstance(lens_type, Lens):\n            self.lens = lens_type\n        elif lens_type not in lenses:\n            raise RuntimeError(\n                f\"Lens type {lens_type} not in available list of available lens \"\n                \"types ({})\".format(\", \".join([f\"{_!r}\" for _ in lenses]))\n            )\n        else:\n            self.lens = lenses[lens_type]()\n        self.lens.set_camera(self)\n\n    def set_defaults_from_data_source(self, data_source):\n        \"\"\"Resets the camera attributes to their default values\"\"\"\n\n        position = data_source.ds.domain_right_edge\n\n        width = 1.5 * data_source.ds.domain_width.max()\n        (xmi, xma), (ymi, yma), (zmi, zma) = data_source.quantities[\"Extrema\"](\n            [\"x\", \"y\", \"z\"]\n        )\n        width = np.sqrt((xma - xmi) ** 2 + (yma - ymi) ** 2 + (zma - zmi) ** 2)\n        focus = data_source.get_field_parameter(\"center\")\n\n        if is_sequence(width) and len(width) > 1 and isinstance(width[1], str):\n            width = data_source.ds.quan(width[0], units=width[1])\n            # Now convert back to code length for subsequent manipulation\n            width = width.in_units(\"code_length\")  # .value\n        if not is_sequence(width):\n            width = data_source.ds.arr([width, width, width], units=\"code_length\")\n            # left/right, top/bottom, front/back\n        if not isinstance(width, YTArray):\n            width = data_source.ds.arr(width, units=\"code_length\")\n        if not isinstance(focus, YTArray):\n            focus = data_source.ds.arr(focus, units=\"code_length\")\n\n        # We can't use the property setters yet, since they rely on attributes\n        # that will not be set up until the base class initializer is called.\n        # See Issue #1131.\n        self._width = width\n        self._focus = focus\n        self._position = position\n        self._domain_center = data_source.ds.domain_center\n        self._domain_width = data_source.ds.domain_width\n\n        super().__init__(\n            self.focus - self.position, self.north_vector, steady_north=False\n        )\n        self._moved = True\n\n    def set_width(self, width):\n        r\"\"\"Set the width of the image that will be produced by this camera.\n\n        Parameters\n        ----------\n\n        width : number, YTQuantity, :obj:`!iterable`, or 3 element YTArray\n            The width of the volume rendering in the horizontal, vertical, and\n            depth directions. If a scalar, assumes that the width is the same in\n            all three directions. If an iterable, must contain only scalars or\n            (length, unit) tuples.\n        \"\"\"\n        self.width = width\n        self.switch_orientation()\n\n    def get_width(self):\n        \"\"\"Return the current camera width\"\"\"\n        return self.width\n\n    def set_position(self, position, north_vector=None):\n        r\"\"\"Set the position of the camera.\n\n        Parameters\n        ----------\n\n        position : number, YTQuantity, :obj:`!iterable`, or 3 element YTArray\n            If a scalar, assumes that the position is the same in all three\n            coordinates. If an iterable, must contain only scalars or\n            (length, unit) tuples.\n\n        north_vector : array_like, optional\n            The 'up' direction for the plane of rays. If not specific,\n            calculated automatically.\n\n        \"\"\"\n        if north_vector is not None:\n            self.north_vector = north_vector\n        self.position = position\n\n    def get_position(self):\n        \"\"\"Return the current camera position\"\"\"\n        return self.position\n\n    def set_focus(self, new_focus):\n        \"\"\"Sets the point the Camera is pointed at.\n\n        Parameters\n        ----------\n\n        new_focus : number, YTQuantity, :obj:`!iterable`, or 3 element YTArray\n            If a scalar, assumes that the focus is the same is all three\n            coordinates. If an iterable, must contain only scalars or\n            (length, unit) tuples.\n\n        \"\"\"\n        self.focus = new_focus\n\n    def get_focus(self):\n        \"\"\"Returns the current camera focus\"\"\"\n        return self.focus\n\n    def switch_orientation(self, normal_vector=None, north_vector=None):\n        r\"\"\"Change the view direction based on any of the orientation parameters.\n\n        This will recalculate all the necessary vectors and vector planes\n        related to an orientable object.\n\n        Parameters\n        ----------\n        normal_vector: array_like, optional\n            The new looking vector from the camera to the focus.\n        north_vector : array_like, optional\n            The 'up' direction for the plane of rays.  If not specific,\n            calculated automatically.\n        \"\"\"\n        if north_vector is None:\n            north_vector = self.north_vector\n        if normal_vector is None:\n            normal_vector = self.normal_vector\n        self._setup_normalized_vectors(normal_vector, north_vector)\n        self.lens.setup_box_properties(self)\n\n    def switch_view(self, normal_vector=None, north_vector=None):\n        r\"\"\"Change the view based on any of the view parameters.\n\n        This will recalculate the orientation and width based on any of\n        normal_vector, width, center, and north_vector.\n\n        Parameters\n        ----------\n        normal_vector: array_like, optional\n            The new looking vector from the camera to the focus.\n        north_vector : array_like, optional\n            The 'up' direction for the plane of rays.  If not specific,\n            calculated automatically.\n        \"\"\"\n        if north_vector is None:\n            north_vector = self.north_vector\n        if normal_vector is None:\n            normal_vector = self.normal_vector\n        self.switch_orientation(normal_vector=normal_vector, north_vector=north_vector)\n        self._moved = True\n\n    def rotate(self, theta, rot_vector=None, rot_center=None):\n        r\"\"\"Rotate by a given angle\n\n        Rotate the view.  If `rot_vector` is None, rotation will occur\n        around the `north_vector`.\n\n        Parameters\n        ----------\n        theta : float, in radians\n             Angle (in radians) by which to rotate the view.\n        rot_vector  : array_like, optional\n            Specify the rotation vector around which rotation will\n            occur.  Defaults to None, which sets rotation around\n            `north_vector`\n        rot_center  : array_like, optional\n            Specify the center around which rotation will occur. Defaults\n            to None, which sets rotation around the original camera position\n            (i.e. the camera position does not change)\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> import numpy as np\n        >>> from yt.visualization.volume_rendering.api import Scene\n        >>> sc = Scene()\n        >>> cam = sc.add_camera()\n        >>> # rotate the camera by pi / 4 radians:\n        >>> cam.rotate(np.pi / 4.0)\n        >>> # rotate the camera about the y-axis instead of cam.north_vector:\n        >>> cam.rotate(np.pi / 4.0, np.array([0.0, 1.0, 0.0]))\n        >>> # rotate the camera about the origin instead of its own position:\n        >>> cam.rotate(np.pi / 4.0, rot_center=np.array([0.0, 0.0, 0.0]))\n\n        \"\"\"\n        rotate_all = rot_vector is not None\n        if rot_vector is None:\n            rot_vector = self.north_vector\n        if rot_center is None:\n            rot_center = self._position\n        rot_vector = ensure_numpy_array(rot_vector)\n        rot_vector = rot_vector / np.linalg.norm(rot_vector)\n\n        new_position = self._position - rot_center\n        R = get_rotation_matrix(theta, rot_vector)\n        new_position = np.dot(R, new_position) + rot_center\n\n        if (new_position == self._position).all():\n            normal_vector = self.unit_vectors[2]\n        else:\n            normal_vector = rot_center - new_position\n        normal_vector = normal_vector / np.sqrt((normal_vector**2).sum())\n\n        if rotate_all:\n            self.switch_view(\n                normal_vector=np.dot(R, normal_vector),\n                north_vector=np.dot(R, self.unit_vectors[1]),\n            )\n        else:\n            self.switch_view(normal_vector=np.dot(R, normal_vector))\n        if (new_position != self._position).any():\n            self.set_position(new_position)\n\n    def pitch(self, theta, rot_center=None):\n        r\"\"\"Rotate by a given angle about the horizontal axis\n\n        Pitch the view.\n\n        Parameters\n        ----------\n        theta : float, in radians\n             Angle (in radians) by which to pitch the view.\n        rot_center  : array_like, optional\n            Specify the center around which rotation will occur.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> import numpy as np\n        >>> from yt.visualization.volume_rendering.api import Scene\n        >>> sc = Scene()\n        >>> sc.add_camera()\n        >>> # pitch the camera by pi / 4 radians:\n        >>> cam.pitch(np.pi / 4.0)\n        >>> # pitch the camera about the origin instead of its own position:\n        >>> cam.pitch(np.pi / 4.0, rot_center=np.array([0.0, 0.0, 0.0]))\n\n        \"\"\"\n        self.rotate(theta, rot_vector=self.unit_vectors[0], rot_center=rot_center)\n\n    def yaw(self, theta, rot_center=None):\n        r\"\"\"Rotate by a given angle about the vertical axis\n\n        Yaw the view.\n\n        Parameters\n        ----------\n        theta : float, in radians\n             Angle (in radians) by which to yaw the view.\n        rot_center  : array_like, optional\n            Specify the center around which rotation will occur.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> import numpy as np\n        >>> from yt.visualization.volume_rendering.api import Scene\n        >>> sc = Scene()\n        >>> cam = sc.add_camera()\n        >>> # yaw the camera by pi / 4 radians:\n        >>> cam.yaw(np.pi / 4.0)\n        >>> # yaw the camera about the origin instead of its own position:\n        >>> cam.yaw(np.pi / 4.0, rot_center=np.array([0.0, 0.0, 0.0]))\n\n        \"\"\"\n        self.rotate(theta, rot_vector=self.unit_vectors[1], rot_center=rot_center)\n\n    def roll(self, theta, rot_center=None):\n        r\"\"\"Rotate by a given angle about the view normal axis\n\n        Roll the view.\n\n        Parameters\n        ----------\n        theta : float, in radians\n             Angle (in radians) by which to roll the view.\n        rot_center  : array_like, optional\n            Specify the center around which rotation will occur.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> import numpy as np\n        >>> from yt.visualization.volume_rendering.api import Scene\n        >>> sc = Scene()\n        >>> cam = sc.add_camera(ds)\n        >>> # roll the camera by pi / 4 radians:\n        >>> cam.roll(np.pi / 4.0)\n        >>> # roll the camera about the origin instead of its own position:\n        >>> cam.roll(np.pi / 4.0, rot_center=np.array([0.0, 0.0, 0.0]))\n\n        \"\"\"\n        self.rotate(theta, rot_vector=self.unit_vectors[2], rot_center=rot_center)\n\n    def iter_rotate(self, theta, n_steps, rot_vector=None, rot_center=None):\n        r\"\"\"Loop over rotate, creating a rotation\n\n        This will rotate `n_steps` until the current view has been\n        rotated by an angle `theta`.\n\n        Parameters\n        ----------\n        theta : float, in radians\n            Angle (in radians) by which to rotate the view.\n        n_steps : int\n            The number of snapshots to make.\n        rot_vector  : array_like, optional\n            Specify the rotation vector around which rotation will\n            occur.  Defaults to None, which sets rotation around the\n            original `north_vector`\n        rot_center  : array_like, optional\n            Specify the center around which rotation will occur. Defaults\n            to None, which sets rotation around the original camera position\n            (i.e. the camera position does not change)\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> import numpy as np\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> im, sc = yt.volume_render(ds)\n        >>> cam = sc.camera\n        >>> for i in cam.iter_rotate(np.pi, 10):\n        ...     im = sc.render()\n        ...     sc.save(\"rotation_%04i.png\" % i)\n\n        \"\"\"\n\n        dtheta = (1.0 * theta) / n_steps\n        for i in range(n_steps):\n            self.rotate(dtheta, rot_vector=rot_vector, rot_center=rot_center)\n            yield i\n\n    def iter_move(self, final, n_steps, exponential=False):\n        r\"\"\"Loop over an iter_move and return snapshots along the way.\n\n        This will yield `n_steps` until the current view has been\n        moved to a final center of `final`.\n\n        Parameters\n        ----------\n        final : YTArray\n            The final center to move to after `n_steps`\n        n_steps : int\n            The number of snapshots to make.\n        exponential : boolean\n            Specifies whether the move/zoom transition follows an\n            exponential path toward the destination or linear.\n            Default is False.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> import numpy as np\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> final_position = ds.arr([0.2, 0.3, 0.6], \"unitary\")\n        >>> im, sc = yt.volume_render(ds)\n        >>> cam = sc.camera\n        >>> for i in cam.iter_move(final_position, 10):\n        ...     sc.render()\n        ...     sc.save(\"move_%04i.png\" % i)\n\n        \"\"\"\n        assert isinstance(final, YTArray)\n        if exponential:\n            position_diff = (final / self.position) * 1.0\n            dx = position_diff ** (1.0 / n_steps)\n        else:\n            dx = (final - self.position) * 1.0 / n_steps\n        for i in range(n_steps):\n            if exponential:\n                self.set_position(self.position * dx)\n            else:\n                self.set_position(self.position + dx)\n            yield i\n\n    def zoom(self, factor):\n        r\"\"\"Change the width of the FOV of the camera.\n\n        This will appear to zoom the camera in by some `factor` toward the\n        focal point along the current view direction, but really it's just\n        changing the width of the field of view.\n\n        Parameters\n        ----------\n        factor : float\n            The factor by which to divide the width\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> from yt.visualization.volume_rendering.api import Scene\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> sc = Scene()\n        >>> cam = sc.add_camera(ds)\n        >>> cam.zoom(1.1)\n\n        \"\"\"\n\n        self.width[:2] = self.width[:2] / factor\n\n    def iter_zoom(self, final, n_steps):\n        r\"\"\"Loop over a iter_zoom and return snapshots along the way.\n\n        This will yield `n_steps` snapshots until the current view has been\n        zooming in to a final factor of `final`.\n\n        Parameters\n        ----------\n        final : float\n            The zoom factor, with respect to current, desired at the end of the\n            sequence.\n        n_steps : int\n            The number of zoom snapshots to make.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> im, sc = yt.volume_render(ds)\n        >>> cam = sc.camera\n        >>> for i in cam.iter_zoom(100.0, 10):\n        ...     sc.render()\n        ...     sc.save(\"zoom_%04i.png\" % i)\n\n        \"\"\"\n        f = final ** (1.0 / n_steps)\n        for i in range(n_steps):\n            self.zoom(f)\n            yield i\n\n    def __repr__(self):\n        disp = (\n            \"<Camera Object>:\\n\\tposition:%s\\n\\tfocus:%s\\n\\t\"\n            + \"north_vector:%s\\n\\twidth:%s\\n\\tlight:%s\\n\\tresolution:%s\\n\"\n        ) % (\n            self.position,\n            self.focus,\n            self.north_vector,\n            self.width,\n            self.light,\n            self.resolution,\n        )\n        disp += f\"Lens: {self.lens}\"\n        return disp\n"
  },
  {
    "path": "yt/visualization/volume_rendering/camera_path.py",
    "content": "import random\n\nimport numpy as np\n\nfrom yt.visualization.volume_rendering.create_spline import create_spline\n\n\nclass Keyframes:\n    def __init__(\n        self,\n        x,\n        y,\n        z=None,\n        north_vectors=None,\n        up_vectors=None,\n        times=None,\n        niter=50000,\n        init_temp=10.0,\n        alpha=0.999,\n        fixed_start=False,\n    ):\n        r\"\"\"Keyframes for camera path generation.\n\n        From a set of keyframes with position and optional up and\n        north vectors, an interpolated camera path is generated.\n\n        Parameters\n        ----------\n        x : array_like\n            The x positions of the keyframes\n        y : array_like\n            The y positions of the keyframes\n        z : array_like, optional\n            The z positions of the keyframes. Default: 0.0\n        north_vectors : array_like, optional\n            The north vectors of the keyframes. Default: None\n        up_vectors : array_like, optional\n            The up vectors of the keyframes. Default: None\n        times : array_like, optional\n            The times of the keyframes. Default: arange(N)\n        niter : integer, optional\n            Maximum number of iterations to find solution. Default: 50000\n        init_temp : float, optional\n            Initial temperature for simulated annealing when finding a\n            solution.  Lower initial temperatures result in an initial solution\n            in first several iterations that changes more rapidly. Default: 10.0\n        alpha : float, optional\n            Exponent in cooling function in simulated annealing.  Must be < 1.\n            In each iteration, the temperature_new = temperature_old * alpha.\n            Default: 0.999\n        fixed_start: boolean, optional\n            If true, the first point never changes when searching for shortest\n            path.  Default: False\n\n        Examples\n        --------\n\n        >>> import matplotlib.pyplot as plt\n        ... import numpy as np\n        ... from yt.visualization.volume_rendering.camera_path import *\n\n        >>> # Make a camera path from 10 random (x, y, z) keyframes\n        >>> data = np.random.random(10, 3)\n        >>> kf = Keyframes(data[:, 0], data[:, 1], data[:, 2])\n        >>> path = kf.create_path(250, shortest_path=False)\n\n        >>> # Plot the keyframes in the x-y plane and camera path\n        >>> plt.plot(kf.pos[:, 0], kf.pos[:, 1], \"ko\")\n        >>> plt.plot(path[\"position\"][:, 0], path[\"position\"][:, 1])\n        >>> plt.savefig(\"path.png\")\n        \"\"\"\n        Nx = len(x)\n        Ny = len(y)\n        if z is not None:\n            Nz = len(z)\n            ndims = 3\n        else:\n            Nz = 1\n            ndims = 2\n        if Nx * Ny * Nz != Nx**ndims:\n            print(f\"Need Nx ({Nx}) == Ny ({Ny}) == Nz ({Nz})\")\n            raise RuntimeError\n        self.nframes = Nx\n        self.pos = np.zeros((Nx, 3))\n        self.pos[:, 0] = x\n        self.pos[:, 1] = y\n        if z is not None:\n            self.pos[:, 2] = z\n        else:\n            self.pos[:, 2] = 0.0\n        self.north_vectors = north_vectors\n        self.up_vectors = up_vectors\n        if times is None:\n            self.times = np.arange(self.nframes)\n        else:\n            self.times = times\n        self.cartesian_matrix()\n        self.setup_tsp(niter, init_temp, alpha, fixed_start)\n\n    def setup_tsp(self, niter=50000, init_temp=10.0, alpha=0.999, fixed_start=False):\n        r\"\"\"Setup parameters for Travelling Salesman Problem.\n\n        Parameters\n        ----------\n        niter : integer, optional\n            Maximum number of iterations to find solution. Default: 50000\n        init_temp : float, optional\n            Initial temperature for simulated annealing when finding a\n            solution.  Lower initial temperatures result in an initial solution\n            in first several iterations that changes more rapidly. Default: 10.0\n        alpha : float, optional\n            Exponent in cooling function in simulated annealing.  Must be < 1.\n            In each iteration, the temperature_new = temperature_old * alpha.\n            Default: 0.999\n        fixed_start: boolean, optional\n            If true, the first point never changes when searching for shortest\n            path.  Default: False\n        \"\"\"\n        # randomize tour\n        self.tour = list(range(self.nframes))\n        rng = np.random.default_rng()\n        rng.shuffle(self.tour)\n        if fixed_start:\n            first = self.tour.index(0)\n            self.tour[0], self.tour[first] = self.tour[first], self.tour[0]\n        self.max_iterations = niter\n        self.initial_temp = init_temp\n        self.alpha = alpha\n        self.fixed_start = fixed_start\n        self.best_score = None\n        self.best = None\n\n    def set_times(self, times):\n        self.times = times\n\n    def rand_seq(self):\n        r\"\"\"\n        Generates values in random order, equivalent to using shuffle\n        in random without generation all values at once.\n        \"\"\"\n        values = list(range(self.nframes))\n        for i in range(self.nframes):\n            # pick a random index into remaining values\n            j = i + int(random.random() * (self.nframes - i))\n            # swap the values\n            values[j], values[i] = values[i], values[j]\n            # return the swapped value\n            yield values[i]\n\n    def all_pairs(self):\n        r\"\"\"\n        Generates all (i,j) pairs for (i,j) for 0-size\n        \"\"\"\n        for i in self.rand_seq():\n            for j in self.rand_seq():\n                yield (i, j)\n\n    def reversed_sections(self, tour):\n        r\"\"\"\n        Generator to return all possible variations where a section\n        between two cities are swapped.\n        \"\"\"\n        for i, j in self.all_pairs():\n            if i == j:\n                continue\n            copy = tour[:]\n            if i < j:\n                copy[i : j + 1] = reversed(tour[i : j + 1])\n            else:\n                copy[i + 1 :] = reversed(tour[:j])\n                copy[:j] = reversed(tour[i + 1 :])\n            if self.fixed_start:\n                ind = copy.index(0)\n                copy[0], copy[ind] = copy[ind], copy[0]\n            if copy != tour:  # no point return the same tour\n                yield copy\n\n    def cartesian_matrix(self):\n        r\"\"\"\n        Create a distance matrix for the city coords that uses\n        straight line distance\n        \"\"\"\n        self.dist_matrix = np.zeros((self.nframes, self.nframes))\n        xmat = np.zeros((self.nframes, self.nframes))\n        xmat[:, :] = self.pos[:, 0]\n        dx = xmat - xmat.T\n        ymat = np.zeros((self.nframes, self.nframes))\n        ymat[:, :] = self.pos[:, 1]\n        dy = ymat - ymat.T\n        zmat = np.zeros((self.nframes, self.nframes))\n        zmat[:, :] = self.pos[:, 2]\n        dz = zmat - zmat.T\n        self.dist_matrix = np.sqrt(dx * dx + dy * dy + dz * dz)\n\n    def tour_length(self, tour):\n        r\"\"\"\n        Calculate the total length of the tour based on the distance\n        matrix\n        \"\"\"\n        total = 0\n        num_cities = len(tour)\n        for i in range(num_cities):\n            j = (i + 1) % num_cities\n            city_i = tour[i]\n            city_j = tour[j]\n            total += self.dist_matrix[city_i, city_j]\n        return -total\n\n    def cooling(self):\n        T = self.initial_temp\n        while True:\n            yield T\n            T = self.alpha * T\n\n    def prob(self, prev, next, temperature):\n        if next > prev:\n            return 1.0\n        else:\n            return np.exp(-abs(next - prev) / temperature)\n\n    def get_shortest_path(self):\n        \"\"\"\n        Determine shortest path between all keyframes.\n        \"\"\"\n        # this obviously doesn't work. When someone fixes it, remove the NOQA\n        self.setup_tsp(niter, init_temp, alpha, fixed_start)  # NOQA\n        num_eval = 1\n        cooling_schedule = self.cooling()\n        current = self.tour\n        current_score = self.tour_length(current)\n        for temperature in cooling_schedule:\n            done = False\n            # Examine moves around the current position\n            for next in self.reversed_sections(current):\n                if num_eval >= self.max_iterations:\n                    done = True\n                    break\n                next_score = self.tour_length(next)\n                num_eval += 1\n\n                # Anneal.  Accept new solution if a random number is\n                # greater than our \"probability\".\n                p = self.prob(current_score, next_score, temperature)\n                if random.random() < p:\n                    current = next\n                    self.current_score = next_score\n                    if self.current_score > self.best_score:\n                        # print(num_eval, self.current_score, self.best_score, current)\n                        self.best_score = self.current_score\n                        self.best = current\n                    break\n\n            if done:\n                break\n        self.pos = self.pos[self.tour, :]\n        if self.north_vectors is not None:\n            self.north_vectors = self.north_vectors[self.tour]\n        if self.up_vectors is not None:\n            self.up_vectors = self.up_vectors[self.tour]\n\n    def create_path(self, npoints, path_time=None, tension=0.5, shortest_path=False):\n        r\"\"\"Create a interpolated camera path from keyframes.\n\n        Parameters\n        ----------\n        npoints : integer\n            Number of points to interpolate from keyframes\n        path_time : array_like, optional\n            Times of interpolated points.  Default: Linearly spaced\n        tension : float, optional\n            Controls how sharp of a curve the spline takes.  A higher tension\n            allows for more sharp turns.  Default: 0.5\n        shortest_path : boolean, optional\n            If true, estimate the shortest path between the keyframes.\n            Default: False\n\n        Returns\n        -------\n        path : dict\n            Dictionary (time, position, north_vectors, up_vectors) of camera\n            path.  Also saved to self.path.\n        \"\"\"\n        self.npoints = npoints\n        self.path = {\n            \"time\": np.zeros(npoints),\n            \"position\": np.zeros((npoints, 3)),\n            \"north_vectors\": np.zeros((npoints, 3)),\n            \"up_vectors\": np.zeros((npoints, 3)),\n        }\n        if shortest_path:\n            self.get_shortest_path()\n        if path_time is None:\n            path_time = np.linspace(0, self.nframes, npoints)\n        self.path[\"time\"] = path_time\n        for dim in range(3):\n            self.path[\"position\"][:, dim] = create_spline(\n                self.times, self.pos[:, dim], path_time, tension=tension\n            )\n            if self.north_vectors is not None:\n                self.path[\"north_vectors\"][:, dim] = create_spline(\n                    self.times, self.north_vectors[:, dim], path_time, tension=tension\n                )\n            if self.up_vectors is not None:\n                self.path[\"up_vectors\"][:, dim] = create_spline(\n                    self.times, self.up_vectors[:, dim], path_time, tension=tension\n                )\n        return self.path\n\n    def write_path(self, filename=\"path.dat\"):\n        r\"\"\"Writes camera path to ASCII file\n\n        Parameters\n        ----------\n        filename : string, optional\n            Filename containing the camera path.  Default: path.dat\n        \"\"\"\n        fp = open(filename, \"w\")\n        fields = [\n            \"y\",\n            \"z\",\n            \"north_x\",\n            \"north_y\",\n            \"north_z\",\n            \"up_x\",\n            \"up_y\",\n            \"up_z\",\n        ]\n        fp.write(f\"#{'x':>11}\" + \" \".join(f\"{s:>12}\" for s in fields) + \"\\n\")\n        for i in range(self.npoints):\n            values = [\n                self.path[\"position\"][i, 0],\n                self.path[\"position\"][i, 1],\n                self.path[\"position\"][i, 2],\n                self.path[\"north_vectors\"][i, 0],\n                self.path[\"north_vectors\"][i, 1],\n                self.path[\"north_vectors\"][i, 2],\n                self.path[\"up_vectors\"][i, 0],\n                self.path[\"up_vectors\"][i, 1],\n                self.path[\"up_vectors\"][i, 2],\n            ]\n            fp.write(\" \".join(f\"{v:.12f}\" for v in values) + \"\\n\")\n        fp.close()\n"
  },
  {
    "path": "yt/visualization/volume_rendering/create_spline.py",
    "content": "import sys\n\nimport numpy as np\n\n\ndef create_spline(old_x, old_y, new_x, tension=0.5, sorted=False):\n    \"\"\"\n    Inputs:\n      old_x: array of floats\n        Original x-data to be fit with a Catmull-Rom spline\n      old_y: array of floats\n        Original y-data to be fit with a Catmull-Rom spline\n      new_x: array of floats\n        interpolate to these x-coordinates\n      tension: float, optional\n        controls the tension at the specified coordinates\n      sorted: boolean, optional\n        If True, then the old_x and old_y arrays are sorted, and then this routine\n        does not try to sort the coordinates\n    Outputs:\n      result: array of floats\n        interpolated y-coordinates\n    \"\"\"\n    ndata = len(old_x)\n    N = len(new_x)\n    result = np.zeros(N)\n    if not sorted:\n        isort = np.argsort(old_x)\n        old_x = old_x[isort]\n        old_y = old_y[isort]\n    # Floor/ceiling of values outside of the original data\n    new_x = np.minimum(new_x, old_x[-1])\n    new_x = np.maximum(new_x, old_x[0])\n    ind = np.searchsorted(old_x, new_x)\n    im2 = np.maximum(ind - 2, 0)\n    im1 = np.maximum(ind - 1, 0)\n    ip1 = np.minimum(ind + 1, ndata - 1)\n    for i in range(N):\n        if ind[i] != im1[i]:\n            u = (new_x[i] - old_x[im1[i]]) / (old_x[ind[i]] - old_x[im1[i]])\n        elif ind[i] == im1[i]:\n            u = 0\n        else:\n            print(\"Bad index during interpolation?\")\n            sys.exit()\n        b0 = -tension * u + 2 * tension * u**2 - tension * u**3\n        b1 = 1.0 + (tension - 3) * u**2 + (2 - tension) * u**3\n        b2 = tension * u + (3 - 2 * tension) * u**2 + (tension - 2) * u**3\n        b3 = -tension * u**2 + tension * u**3\n        result[i] = (\n            b0 * old_y[im2[i]]\n            + b1 * old_y[im1[i]]\n            + b2 * old_y[ind[i]]\n            + b3 * old_y[ip1[i]]\n        )\n    return result\n"
  },
  {
    "path": "yt/visualization/volume_rendering/image_handling.py",
    "content": "import numpy as np\n\nfrom yt.funcs import mylog\nfrom yt.utilities.on_demand_imports import _h5py as h5py\n\n\ndef export_rgba(\n    image,\n    fn,\n    h5=True,\n    fits=False,\n):\n    \"\"\"\n    This function accepts an *image*, of shape (N,M,4) corresponding to r,g,b,a,\n    and saves to *fn*.  If *h5* is True, then it will save in hdf5 format.  If\n    *fits* is True, it will save in fits format.\n    \"\"\"\n    if (not h5 and not fits) or (h5 and fits):\n        raise ValueError(\"Choose either HDF5 or FITS format!\")\n    if h5:\n        f = h5py.File(f\"{fn}.h5\", mode=\"w\")\n        f.create_dataset(\"R\", data=image[:, :, 0])\n        f.create_dataset(\"G\", data=image[:, :, 1])\n        f.create_dataset(\"B\", data=image[:, :, 2])\n        f.create_dataset(\"A\", data=image[:, :, 3])\n        f.close()\n    if fits:\n        from yt.visualization.fits_image import FITSImageData\n\n        data = {}\n        data[\"r\"] = image[:, :, 0]\n        data[\"g\"] = image[:, :, 1]\n        data[\"b\"] = image[:, :, 2]\n        data[\"a\"] = image[:, :, 3]\n        fib = FITSImageData(data)\n        fib.writeto(f\"{fn}.fits\", overwrite=True)\n\n\ndef import_rgba(name, h5=True):\n    \"\"\"\n    This function will read back in an HDF5 file, as saved by export_rgba, and\n    return the frames to the user.  *name* is the name of the file to be read\n    in.\n    \"\"\"\n    if h5:\n        f = h5py.File(name, mode=\"r\")\n        r = f[\"R\"].value\n        g = f[\"G\"].value\n        b = f[\"B\"].value\n        a = f[\"A\"].value\n        f.close()\n    else:\n        mylog.error(\"No support for fits import.\")\n    return np.array([r, g, b, a]).swapaxes(0, 2).swapaxes(0, 1)\n\n\ndef plot_channel(\n    image,\n    name,\n    cmap=\"gist_heat\",\n    log=True,\n    dex=3,\n    zero_factor=1.0e-10,\n    label=None,\n    label_color=\"w\",\n    label_size=\"large\",\n):\n    \"\"\"\n    This function will plot a single channel. *image* is an array shaped like\n    (N,M), *name* is the pefix for the output filename.  *cmap* is the name of\n    the colormap to apply, *log* is whether or not the channel should be\n    logged.  Additionally, you may optionally specify the minimum-value cutoff\n    for scaling as *dex*, which is taken with respect to the minimum value of\n    the image.  *zero_factor* applies a minimum value to all zero-valued\n    elements.  Optionally, *label*, *label_color* and *label_size* may be\n    specified.\n    \"\"\"\n    import matplotlib as mpl\n    from matplotlib import pyplot as plt\n    from matplotlib.colors import LogNorm\n\n    Nvec = image.shape[0]\n    image[np.isnan(image)] = 0.0\n    ma = image[image > 0.0].max()\n    image[image == 0.0] = ma * zero_factor\n    if log:\n        mynorm = LogNorm(ma / (10.0**dex), ma)\n\n    fig = plt.gcf()\n    ax = plt.gca()\n    fig.clf()\n    fig.set_dpi(100)\n    fig.set_size_inches((Nvec / 100.0, Nvec / 100.0))\n    fig.subplots_adjust(\n        left=0.0, right=1.0, bottom=0.0, top=1.0, wspace=0.0, hspace=0.0\n    )\n    mycm = mpl.colormaps[cmap]\n    if log:\n        ax.imshow(image, cmap=mycm, norm=mynorm, interpolation=\"nearest\")\n    else:\n        ax.imshow(image, cmap=mycm, interpolation=\"nearest\")\n    if label is not None:\n        ax.text(20, 20, label, color=label_color, size=label_size)\n    fig.savefig(f\"{name}_{cmap}.png\")\n    fig.clf()\n\n\ndef plot_rgb(image, name, label=None, label_color=\"w\", label_size=\"large\"):\n    \"\"\"\n    This will plot the r,g,b channels of an *image* of shape (N,M,3) or\n    (N,M,4).  *name* is the prefix of the file name, which will be supplemented\n    with \"_rgb.png.\"  *label*, *label_color* and *label_size* may also be\n    specified.\n    \"\"\"\n    import matplotlib.pyplot as plt\n\n    Nvec = image.shape[0]\n    image[np.isnan(image)] = 0.0\n    if image.shape[2] >= 4:\n        image = image[:, :, :3]\n\n    fig = plt.gcf()\n    ax = plt.gca()\n    fig.clf()\n    fig.set_dpi(100)\n    fig.set_size_inches((Nvec / 100.0, Nvec / 100.0))\n    fig.subplots_adjust(\n        left=0.0, right=1.0, bottom=0.0, top=1.0, wspace=0.0, hspace=0.0\n    )\n    ax.imshow(image, interpolation=\"nearest\")\n    if label is not None:\n        ax.text(20, 20, label, color=label_color, size=label_size)\n    fig.savefig(f\"{name}_rgb.png\")\n    fig.clf()\n"
  },
  {
    "path": "yt/visualization/volume_rendering/lens.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.image_array import ImageArray\nfrom yt.units._numpy_wrapper_functions import uhstack, unorm, uvstack\nfrom yt.utilities.lib.grid_traversal import arr_fisheye_vectors\nfrom yt.utilities.math_utils import get_rotation_matrix\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    ParallelAnalysisInterface,\n)\n\n\nclass Lens(ParallelAnalysisInterface):\n    \"\"\"A Lens is used to define the set of rays for rendering.\"\"\"\n\n    def __init__(self):\n        super().__init__()\n        self.viewpoint = None\n        self.sub_samples = 5\n        self.num_threads = 0\n        self.box_vectors = None\n        self.origin = None\n        self.back_center = None\n        self.front_center = None\n        self.sampler = None\n\n    def set_camera(self, camera):\n        \"\"\"Set the properties of the lens based on the camera.\n\n        This is a proxy for setup_box_properties\n        \"\"\"\n        self.setup_box_properties(camera)\n\n    def new_image(self, camera):\n        \"\"\"Initialize a new ImageArray to be used with this lens.\"\"\"\n        self.current_image = ImageArray(\n            np.zeros(\n                (camera.resolution[0], camera.resolution[1], 4),\n                dtype=\"float64\",\n                order=\"C\",\n            ),\n            info={\"imtype\": \"rendering\"},\n        )\n        return self.current_image\n\n    def setup_box_properties(self, camera):\n        \"\"\"Set up the view and stage based on the properties of the camera.\"\"\"\n        unit_vectors = camera.unit_vectors\n        width = camera.width\n        center = camera.focus\n\n        self.box_vectors = camera.scene.arr(\n            [\n                unit_vectors[0] * width[0],\n                unit_vectors[1] * width[1],\n                unit_vectors[2] * width[2],\n            ]\n        )\n        self.origin = center - 0.5 * width.dot(unit_vectors)\n        self.back_center = center - 0.5 * width[2] * unit_vectors[2]\n        self.front_center = center + 0.5 * width[2] * unit_vectors[2]\n        self.set_viewpoint(camera)\n\n    def set_viewpoint(self, camera):\n        \"\"\"\n        Set the viewpoint used for AMRKDTree traversal such that you yield\n        bricks from back to front or front to back from with respect to this\n        point.  Must be implemented for each Lens type.\n        \"\"\"\n        raise NotImplementedError(\"Need to choose viewpoint for this class\")\n\n\nclass PlaneParallelLens(Lens):\n    r\"\"\"The lens for orthographic projections.\n\n    All rays emerge parallel to each other, arranged along a plane.\n\n    The initializer takes no parameters.\n    \"\"\"\n\n    def __init__(self):\n        super().__init__()\n\n    def _get_sampler_params(self, camera, render_source):\n        if render_source.zbuffer is not None:\n            image = render_source.zbuffer.rgba\n        else:\n            image = self.new_image(camera)\n\n        vp_pos = np.concatenate(\n            [\n                camera.inv_mat.ravel(\"F\").d,\n                self.back_center.ravel().in_units(\"code_length\").d,\n            ]\n        )\n\n        sampler_params = {\n            \"vp_pos\": vp_pos,\n            \"vp_dir\": self.box_vectors[2],  # All the same\n            \"center\": self.back_center,\n            \"bounds\": (\n                -camera.width[0] / 2.0,\n                camera.width[0] / 2.0,\n                -camera.width[1] / 2.0,\n                camera.width[1] / 2.0,\n            ),\n            \"x_vec\": camera.unit_vectors[0],\n            \"y_vec\": camera.unit_vectors[1],\n            \"width\": np.array(camera.width, dtype=\"float64\"),\n            \"image\": image,\n            \"lens_type\": \"plane-parallel\",\n        }\n        return sampler_params\n\n    def set_viewpoint(self, camera):\n        \"\"\"Set the viewpoint based on the camera\"\"\"\n        # This is a hack that should be replaced by an alternate plane-parallel\n        # traversal. Put the camera really far away so that the effective\n        # viewpoint is infinitely far away, making for parallel rays.\n        self.viewpoint = (\n            self.front_center + camera.unit_vectors[2] * 1.0e6 * camera.width[2]\n        )\n\n    def project_to_plane(self, camera, pos, res=None):\n        if res is None:\n            res = camera.resolution\n\n        origin = self.origin.in_units(\"code_length\").d\n        front_center = self.front_center.in_units(\"code_length\").d\n        width = camera.width.in_units(\"code_length\").d\n\n        dx = np.array(np.dot(pos - origin, camera.unit_vectors[1]))\n        dy = np.array(np.dot(pos - origin, camera.unit_vectors[0]))\n        dz = np.array(np.dot(pos - front_center, -camera.unit_vectors[2]))\n        # Transpose into image coords.\n        px = (res[0] * (dy / width[0])).astype(\"int64\")\n        py = (res[1] * (dx / width[1])).astype(\"int64\")\n        return px, py, dz\n\n    def __repr__(self):\n        return (\n            f\"<Lens Object>:\\n\\tlens_type:plane-parallel\\n\\tviewpoint:{self.viewpoint}\"\n        )\n\n\nclass PerspectiveLens(Lens):\n    r\"\"\"A lens for viewing a scene with a set of rays within an opening angle.\n\n    The scene will have an element of perspective to it since the rays are not\n    parallel.\n    \"\"\"\n\n    def __init__(self):\n        super().__init__()\n\n    def new_image(self, camera):\n        self.current_image = ImageArray(\n            np.zeros(\n                (camera.resolution[0], camera.resolution[1], 4),\n                dtype=\"float64\",\n                order=\"C\",\n            ),\n            info={\"imtype\": \"rendering\"},\n        )\n        return self.current_image\n\n    def _get_sampler_params(self, camera, render_source):\n        # Enforce width[1] / width[0] = resolution[1] / resolution[0]\n        camera.width[1] = camera.width[0] * (\n            camera.resolution[1] / camera.resolution[0]\n        )\n\n        if render_source.zbuffer is not None:\n            image = render_source.zbuffer.rgba\n        else:\n            image = self.new_image(camera)\n\n        east_vec = camera.unit_vectors[0]\n        north_vec = camera.unit_vectors[1]\n        normal_vec = camera.unit_vectors[2]\n\n        px = np.linspace(-0.5, 0.5, camera.resolution[0])[np.newaxis, :]\n        py = np.linspace(-0.5, 0.5, camera.resolution[1])[np.newaxis, :]\n\n        sample_x = camera.width[0] * np.array(east_vec.reshape(3, 1) * px)\n        sample_x = sample_x.transpose()\n        sample_y = camera.width[1] * np.array(north_vec.reshape(3, 1) * py)\n        sample_y = sample_y.transpose()\n\n        vectors = np.zeros(\n            (camera.resolution[0], camera.resolution[1], 3), dtype=\"float64\", order=\"C\"\n        )\n\n        sample_x = np.repeat(\n            sample_x.reshape(camera.resolution[0], 1, 3), camera.resolution[1], axis=1\n        )\n        sample_y = np.repeat(\n            sample_y.reshape(1, camera.resolution[1], 3), camera.resolution[0], axis=0\n        )\n\n        normal_vecs = np.tile(normal_vec, camera.resolution[0] * camera.resolution[1])\n        normal_vecs = normal_vecs.reshape(camera.resolution[0], camera.resolution[1], 3)\n\n        # The maximum possible length of ray\n        max_length = unorm(camera.position - camera._domain_center) + 0.5 * unorm(\n            camera._domain_width\n        )\n\n        # Rescale the ray to be long enough to cover the entire domain\n        vectors = (sample_x + sample_y + normal_vecs * camera.width[2]) * (\n            max_length / camera.width[2]\n        )\n\n        positions = np.tile(\n            camera.position, camera.resolution[0] * camera.resolution[1]\n        )\n        positions = positions.reshape(camera.resolution[0], camera.resolution[1], 3)\n\n        uv = np.ones(3, dtype=\"float64\")\n\n        image = self.new_image(camera)\n\n        sampler_params = {\n            \"vp_pos\": positions,\n            \"vp_dir\": vectors,\n            \"center\": self.back_center,\n            \"bounds\": (0.0, 1.0, 0.0, 1.0),\n            \"x_vec\": uv,\n            \"y_vec\": uv,\n            \"width\": np.zeros(3, dtype=\"float64\"),\n            \"image\": image,\n            \"lens_type\": \"perspective\",\n        }\n\n        return sampler_params\n\n    def set_viewpoint(self, camera):\n        \"\"\"\n        For a PerspectiveLens, the viewpoint is the front center.\n        \"\"\"\n        self.viewpoint = self.front_center\n\n    def project_to_plane(self, camera, pos, res=None):\n        if res is None:\n            res = camera.resolution\n\n        width = camera.width.in_units(\"code_length\").d\n        position = camera.position.in_units(\"code_length\").d\n\n        width[1] = width[0] * res[1] / res[0]\n\n        sight_vector = pos - position\n\n        pos1 = sight_vector\n\n        for i in range(0, sight_vector.shape[0]):\n            sight_vector_norm = np.sqrt(np.dot(sight_vector[i], sight_vector[i]))\n            if sight_vector_norm != 0:\n                sight_vector[i] = sight_vector[i] / sight_vector_norm\n\n        sight_center = camera.position + camera.width[2] * camera.unit_vectors[2]\n\n        sight_center = sight_center.in_units(\"code_length\").d\n\n        for i in range(0, sight_vector.shape[0]):\n            sight_angle_cos = np.dot(sight_vector[i], camera.unit_vectors[2])\n            # clip sight_angle_cos since floating point noise might\n            # go outside the domain of arccos\n            sight_angle_cos = np.clip(sight_angle_cos, -1.0, 1.0)\n            if np.arccos(sight_angle_cos) < 0.5 * np.pi:\n                sight_length = width[2] / sight_angle_cos\n            else:\n                # If the corner is on the backwards, then we put it outside of\n                # the image It can not be simply removed because it may connect\n                # to other corner within the image, which produces visible\n                # domain boundary line\n                sight_length = np.sqrt(width[0] ** 2 + width[1] ** 2)\n                sight_length = sight_length / np.sqrt(1 - sight_angle_cos**2)\n            pos1[i] = position + sight_length * sight_vector[i]\n\n        dx = np.dot(pos1 - sight_center, camera.unit_vectors[0])\n        dy = np.dot(pos1 - sight_center, camera.unit_vectors[1])\n        dz = np.dot(pos - position, camera.unit_vectors[2])\n\n        # Transpose into image coords.\n        px = (res[0] * 0.5 + res[0] / width[0] * dx).astype(\"int64\")\n        py = (res[1] * 0.5 + res[1] / width[1] * dy).astype(\"int64\")\n\n        return px, py, dz\n\n    def __repr__(self):\n        disp = f\"<Lens Object>:\\n\\tlens_type:perspective\\n\\tviewpoint:{self.viewpoint}\"\n        return disp\n\n\nclass StereoPerspectiveLens(Lens):\n    \"\"\"A lens that includes two sources for perspective rays, for 3D viewing\n\n    The degree of differences between the left and right images is controlled by\n    the disparity (the maximum distance between corresponding points in the left\n    and right images). By default, the disparity is set to be 3 pixels.\n    \"\"\"\n\n    def __init__(self):\n        super().__init__()\n        self.disparity = None\n\n    def new_image(self, camera):\n        \"\"\"Initialize a new ImageArray to be used with this lens.\"\"\"\n        self.current_image = ImageArray(\n            np.zeros(\n                (camera.resolution[0], camera.resolution[1], 4),\n                dtype=\"float64\",\n                order=\"C\",\n            ),\n            info={\"imtype\": \"rendering\"},\n        )\n        return self.current_image\n\n    def _get_sampler_params(self, camera, render_source):\n        # Enforce width[1] / width[0] = 2 * resolution[1] / resolution[0]\n        # For stereo-type lens, images for left and right eye are pasted together,\n        # so the resolution of single-eye image will be 50% of the whole one.\n        camera.width[1] = camera.width[0] * (\n            2.0 * camera.resolution[1] / camera.resolution[0]\n        )\n\n        if self.disparity is None:\n            self.disparity = 3.0 * camera.width[0] / camera.resolution[0]\n\n        if render_source.zbuffer is not None:\n            image = render_source.zbuffer.rgba\n        else:\n            image = self.new_image(camera)\n\n        vectors_left, positions_left = self._get_positions_vectors(\n            camera, -self.disparity\n        )\n        vectors_right, positions_right = self._get_positions_vectors(\n            camera, self.disparity\n        )\n\n        uv = np.ones(3, dtype=\"float64\")\n\n        image = self.new_image(camera).reshape(*camera.resolution, 4)\n        vectors_comb = uvstack([vectors_left, vectors_right]).reshape(\n            *camera.resolution, 3\n        )\n        positions_comb = uvstack([positions_left, positions_right]).reshape(\n            *camera.resolution, 3\n        )\n\n        sampler_params = {\n            \"vp_pos\": positions_comb,\n            \"vp_dir\": vectors_comb,\n            \"center\": self.back_center,\n            \"bounds\": (0.0, 1.0, 0.0, 1.0),\n            \"x_vec\": uv,\n            \"y_vec\": uv,\n            \"width\": np.zeros(3, dtype=\"float64\"),\n            \"image\": image,\n            \"lens_type\": \"stereo-perspective\",\n        }\n\n        return sampler_params\n\n    def _get_positions_vectors(self, camera, disparity):\n        single_resolution_x = int(np.floor(camera.resolution[0]) / 2)\n\n        east_vec = camera.unit_vectors[0]\n        north_vec = camera.unit_vectors[1]\n        normal_vec = camera.unit_vectors[2]\n\n        angle_disparity = -np.arctan2(\n            disparity.in_units(camera.width.units), camera.width[2]\n        )\n        R = get_rotation_matrix(angle_disparity, north_vec)\n\n        east_vec_rot = np.dot(R, east_vec)\n        normal_vec_rot = np.dot(R, normal_vec)\n\n        px = np.linspace(-0.5, 0.5, single_resolution_x)[np.newaxis, :]\n        py = np.linspace(-0.5, 0.5, camera.resolution[1])[np.newaxis, :]\n\n        sample_x = camera.width[0] * np.array(east_vec_rot.reshape(3, 1) * px)\n        sample_x = sample_x.transpose()\n        sample_y = camera.width[1] * np.array(north_vec.reshape(3, 1) * py)\n        sample_y = sample_y.transpose()\n\n        vectors = np.zeros(\n            (single_resolution_x, camera.resolution[1], 3), dtype=\"float64\", order=\"C\"\n        )\n\n        sample_x = np.repeat(\n            sample_x.reshape(single_resolution_x, 1, 3), camera.resolution[1], axis=1\n        )\n        sample_y = np.repeat(\n            sample_y.reshape(1, camera.resolution[1], 3), single_resolution_x, axis=0\n        )\n\n        normal_vecs = np.tile(\n            normal_vec_rot, single_resolution_x * camera.resolution[1]\n        )\n        normal_vecs = normal_vecs.reshape(single_resolution_x, camera.resolution[1], 3)\n        east_vecs = np.tile(east_vec_rot, single_resolution_x * camera.resolution[1])\n        east_vecs = east_vecs.reshape(single_resolution_x, camera.resolution[1], 3)\n\n        # The maximum possible length of ray\n        max_length = (\n            unorm(camera.position - camera._domain_center)\n            + 0.5 * unorm(camera._domain_width)\n            + np.abs(self.disparity)\n        )\n        # Rescale the ray to be long enough to cover the entire domain\n        vectors = (sample_x + sample_y + normal_vecs * camera.width[2]) * (\n            max_length / camera.width[2]\n        )\n\n        positions = np.tile(camera.position, single_resolution_x * camera.resolution[1])\n        positions = positions.reshape(single_resolution_x, camera.resolution[1], 3)\n\n        # Here the east_vecs is non-rotated one\n        positions = positions + east_vecs * disparity\n\n        return vectors, positions\n\n    def project_to_plane(self, camera, pos, res=None):\n        if res is None:\n            res = camera.resolution\n\n        # Enforce width[1] / width[0] = 2 * resolution[1] / resolution[0]\n        # For stereo-type lens, images for left and right eye are pasted together,\n        # so the resolution of single-eye image will be 50% of the whole one.\n        camera.width[1] = camera.width[0] * (2.0 * res[1] / res[0])\n\n        if self.disparity is None:\n            self.disparity = 3.0 * camera.width[0] / camera.resolution[0]\n\n        px_left, py_left, dz_left = self._get_px_py_dz(\n            camera, pos, res, -self.disparity\n        )\n        px_right, py_right, dz_right = self._get_px_py_dz(\n            camera, pos, res, self.disparity\n        )\n\n        px = uvstack([px_left, px_right])\n        py = uvstack([py_left, py_right])\n        dz = uvstack([dz_left, dz_right])\n\n        return px, py, dz\n\n    def _get_px_py_dz(self, camera, pos, res, disparity):\n        res0_h = np.floor(res[0]) / 2\n\n        east_vec = camera.unit_vectors[0]\n        north_vec = camera.unit_vectors[1]\n        normal_vec = camera.unit_vectors[2]\n\n        angle_disparity = -np.arctan2(disparity.d, camera.width[2].d)\n        R = get_rotation_matrix(angle_disparity, north_vec)\n\n        east_vec_rot = np.dot(R, east_vec)\n        normal_vec_rot = np.dot(R, normal_vec)\n\n        camera_position_shift = camera.position + east_vec * disparity\n        camera_position_shift = camera_position_shift.in_units(\"code_length\").d\n        width = camera.width.in_units(\"code_length\").d\n        sight_vector = pos - camera_position_shift\n        pos1 = sight_vector\n\n        for i in range(0, sight_vector.shape[0]):\n            sight_vector_norm = np.sqrt(np.dot(sight_vector[i], sight_vector[i]))\n            sight_vector[i] = sight_vector[i] / sight_vector_norm\n        sight_center = camera_position_shift + camera.width[2] * normal_vec_rot\n\n        for i in range(0, sight_vector.shape[0]):\n            sight_angle_cos = np.dot(sight_vector[i], normal_vec_rot)\n            # clip sight_angle_cos since floating point noise might\n            # cause it go outside the domain of arccos\n            sight_angle_cos = np.clip(sight_angle_cos, -1.0, 1.0)\n            if np.arccos(sight_angle_cos) < 0.5 * np.pi:\n                sight_length = width[2] / sight_angle_cos\n            else:\n                # If the corner is on the backwards, then we put it outside of\n                # the image It can not be simply removed because it may connect\n                # to other corner within the image, which produces visible\n                # domain boundary line\n                sight_length = np.sqrt(width[0] ** 2 + width[1] ** 2)\n                sight_length = sight_length / np.sqrt(1 - sight_angle_cos**2)\n            pos1[i] = camera_position_shift + sight_length * sight_vector[i]\n\n        dx = np.dot(pos1 - sight_center, east_vec_rot)\n        dy = np.dot(pos1 - sight_center, north_vec)\n        dz = np.dot(pos - camera_position_shift, normal_vec_rot)\n\n        # Transpose into image coords.\n        if disparity > 0:\n            px = (res0_h * 0.5 + res0_h / camera.width[0].d * dx).astype(\"int64\")\n        else:\n            px = (res0_h * 1.5 + res0_h / camera.width[0].d * dx).astype(\"int64\")\n        py = (res[1] * 0.5 + res[1] / camera.width[1].d * dy).astype(\"int64\")\n\n        return px, py, dz\n\n    def set_viewpoint(self, camera):\n        \"\"\"\n        For a PerspectiveLens, the viewpoint is the front center.\n        \"\"\"\n        self.viewpoint = self.front_center\n\n    def __repr__(self):\n        disp = f\"<Lens Object>:\\n\\tlens_type:perspective\\n\\tviewpoint:{self.viewpoint}\"\n        return disp\n\n\nclass FisheyeLens(Lens):\n    r\"\"\"A lens for dome-based renderings\n\n    This lens type accepts a field-of-view property, fov, that describes how\n    wide an angle the fisheye can see. Fisheye images are typically used for\n    dome-based presentations; the Hayden planetarium for instance has a field of\n    view of 194.6.  The images returned by this camera will be flat pixel images\n    that can and should be reshaped to the resolution.\n\n    \"\"\"\n\n    def __init__(self):\n        super().__init__()\n        self.fov = 180.0\n        self.radius = 1.0\n        self.center = None\n        self.rotation_matrix = np.eye(3)\n\n    def setup_box_properties(self, camera):\n        \"\"\"Set up the view and stage based on the properties of the camera.\"\"\"\n        self.radius = camera.width.max()\n        super().setup_box_properties(camera)\n        self.set_viewpoint(camera)\n\n    def new_image(self, camera):\n        \"\"\"Initialize a new ImageArray to be used with this lens.\"\"\"\n        self.current_image = ImageArray(\n            np.zeros(\n                (camera.resolution[0], camera.resolution[0], 4),\n                dtype=\"float64\",\n                order=\"C\",\n            ),\n            info={\"imtype\": \"rendering\"},\n        )\n        return self.current_image\n\n    def _get_sampler_params(self, camera, render_source):\n        shape = (camera.resolution[0], camera.resolution[0], 3)\n        vp = -arr_fisheye_vectors(camera.resolution[0], self.fov).reshape(shape)\n        vp = vp.dot(np.linalg.inv(self.rotation_matrix))\n        vp *= self.radius\n        uv = np.ones(3, dtype=\"float64\")\n        positions = np.full(shape, camera.position, dtype=\"float64\")\n\n        if render_source.zbuffer is not None:\n            image = render_source.zbuffer.rgba\n        else:\n            image = self.new_image(camera)\n\n        sampler_params = {\n            \"vp_pos\": positions,\n            \"vp_dir\": vp,\n            \"center\": self.center,\n            \"bounds\": (0.0, 1.0, 0.0, 1.0),\n            \"x_vec\": uv,\n            \"y_vec\": uv,\n            \"width\": np.zeros(3, dtype=\"float64\"),\n            \"image\": image,\n            \"lens_type\": \"fisheye\",\n        }\n\n        return sampler_params\n\n    def set_viewpoint(self, camera):\n        \"\"\"For a FisheyeLens, the viewpoint is the camera's position\"\"\"\n        self.viewpoint = camera.position\n\n    def __repr__(self):\n        disp = (\n            f\"<Lens Object>:\\n\\tlens_type:fisheye\\n\\tviewpoint:{self.viewpoint}\"\n            f\"\\nt\\tfov:{self.fov}\\n\\tradius:{self.radius}\"\n        )\n        return disp\n\n    def project_to_plane(self, camera, pos, res=None):\n        if res is None:\n            res = camera.resolution\n        # the return values here need to be px, py, dz\n        # these are the coordinates and dz for the resultant image.\n        # Basically, what we need is an inverse projection from the fisheye\n        # vectors back onto the plane.  arr_fisheye_vectors goes from px, py to\n        # vector, and we need the reverse.\n        # First, we transform lpos into *relative to the camera* coordinates.\n\n        position = camera.position.in_units(\"code_length\").d\n\n        lpos = position - pos\n        lpos = lpos.dot(self.rotation_matrix)\n        mag = (lpos * lpos).sum(axis=1) ** 0.5\n\n        # screen out NaN values that would result from dividing by mag\n        mag[mag == 0] = 1\n        lpos /= mag[:, None]\n\n        dz = (mag / self.radius).in_units(\"1/code_length\").d\n        theta = np.arccos(lpos[:, 2])\n        fov_rad = self.fov * np.pi / 180.0\n        r = 2.0 * theta / fov_rad\n        phi = np.arctan2(lpos[:, 1], lpos[:, 0])\n        px = r * np.cos(phi)\n        py = r * np.sin(phi)\n\n        # dz is distance the ray would travel\n        px = (px + 1.0) * res[0] / 2.0\n        py = (py + 1.0) * res[1] / 2.0\n        # px and py should be dimensionless\n        px = np.rint(px, dtype=\"int64\")\n        py = np.rint(py, dtype=\"int64\")\n        return px, py, dz\n\n\nclass SphericalLens(Lens):\n    r\"\"\"A lens for cylindrical-spherical projection.\n\n    Movies rendered in this way can be displayed in head-tracking devices or\n    in YouTube 360 view.\n\n    \"\"\"\n\n    def __init__(self):\n        super().__init__()\n        self.radius = 1.0\n        self.center = None\n        self.rotation_matrix = np.eye(3)\n\n    def setup_box_properties(self, camera):\n        \"\"\"Set up the view and stage based on the properties of the camera.\"\"\"\n        self.radius = camera.width.max()\n        super().setup_box_properties(camera)\n        self.set_viewpoint(camera)\n\n    def _get_sampler_params(self, camera, render_source):\n        px = np.linspace(-np.pi, np.pi, camera.resolution[0], endpoint=True)[:, None]\n        py = np.linspace(\n            -np.pi / 2.0, np.pi / 2.0, camera.resolution[1], endpoint=True\n        )[None, :]\n\n        positions = np.tile(camera.position, np.prod(camera.resolution)).reshape(\n            *camera.resolution, 3\n        )\n\n        R1 = get_rotation_matrix(0.5 * np.pi, [1, 0, 0])\n        R2 = get_rotation_matrix(0.5 * np.pi, [0, 0, 1])\n        uv = np.dot(R1, camera.unit_vectors)\n        uv = np.dot(R2, uv)\n\n        vectors = np.empty((*camera.resolution, 3), dtype=\"float64\", order=\"C\")\n        vectors[:, :, 0] = np.cos(px) * np.cos(py)\n        vectors[:, :, 1] = np.sin(px) * np.cos(py)\n        vectors[:, :, 2] = np.sin(py)\n\n        # The maximum possible length of ray\n        max_length = unorm(camera.position - camera._domain_center) + 0.5 * unorm(\n            camera._domain_width\n        )\n        vectors = np.dot((vectors * max_length), uv).reshape(*camera.resolution, 3)\n\n        if render_source.zbuffer is not None:\n            image = render_source.zbuffer.rgba\n        else:\n            image = self.new_image(camera)\n\n        image = image.reshape(*camera.resolution, 4)\n        dummy = np.ones(3, dtype=\"float64\")\n\n        sampler_params = {\n            \"vp_pos\": positions,\n            \"vp_dir\": vectors,\n            \"center\": self.back_center,\n            \"bounds\": (0.0, 1.0, 0.0, 1.0),\n            \"x_vec\": dummy,\n            \"y_vec\": dummy,\n            \"width\": np.zeros(3, dtype=\"float64\"),\n            \"image\": image,\n            \"lens_type\": \"spherical\",\n        }\n        return sampler_params\n\n    def set_viewpoint(self, camera):\n        \"\"\"For a SphericalLens, the viewpoint is the camera's position\"\"\"\n        self.viewpoint = camera.position\n\n    def project_to_plane(self, camera, pos, res=None):\n        if res is None:\n            res = camera.resolution\n        # Much of our setup here is the same as in the fisheye, except for the\n        # actual conversion back to the px, py values.\n        position = camera.position.in_units(\"code_length\").d\n\n        lpos = position - pos\n        mag = (lpos * lpos).sum(axis=1) ** 0.5\n\n        # screen out NaN values that would result from dividing by mag\n        mag[mag == 0] = 1\n        lpos /= mag[:, None]\n\n        # originally:\n        #  the x vector is cos(px) * cos(py)\n        #  the y vector is sin(px) * cos(py)\n        #  the z vector is sin(py)\n        # y / x = tan(px), so arctan2(lpos[:,1], lpos[:,0]) => px\n        # z = sin(py) so arcsin(z) = py\n        # px runs from -pi to pi\n        # py runs from -pi/2 to pi/2\n        px = np.arctan2(lpos[:, 1], lpos[:, 0])\n        py = np.arcsin(lpos[:, 2])\n        dz = mag / self.radius\n        # dz is distance the ray would travel\n        px = ((-px + np.pi) / (2.0 * np.pi)) * res[0]\n        py = ((-py + np.pi / 2.0) / np.pi) * res[1]\n        # px and py should be dimensionless\n        px = np.rint(px).astype(\"int64\")\n        py = np.rint(py).astype(\"int64\")\n        return px, py, dz\n\n\nclass StereoSphericalLens(Lens):\n    r\"\"\"A lens for a stereo cylindrical-spherical projection.\n\n    Movies rendered in this way can be displayed in VR devices or stereo youtube\n    360 degree movies.\n    \"\"\"\n\n    def __init__(self):\n        super().__init__()\n        self.radius = 1.0\n        self.center = None\n        self.disparity = None\n        self.rotation_matrix = np.eye(3)\n\n    def setup_box_properties(self, camera):\n        self.radius = camera.width.max()\n        super().setup_box_properties(camera)\n        self.set_viewpoint(camera)\n\n    def _get_sampler_params(self, camera, render_source):\n        if self.disparity is None:\n            self.disparity = camera.width[0] / 1000.0\n\n        single_resolution_y = int(np.floor(camera.resolution[1]) / 2)\n        px = np.linspace(-np.pi, np.pi, camera.resolution[0], endpoint=True)[:, None]\n        py = np.linspace(-np.pi / 2.0, np.pi / 2.0, single_resolution_y, endpoint=True)[\n            None, :\n        ]\n\n        vectors = np.zeros(\n            (camera.resolution[0], single_resolution_y, 3), dtype=\"float64\", order=\"C\"\n        )\n        vectors[:, :, 0] = np.cos(px) * np.cos(py)\n        vectors[:, :, 1] = np.sin(px) * np.cos(py)\n        vectors[:, :, 2] = np.sin(py)\n\n        # The maximum possible length of ray\n        max_length = (\n            unorm(camera.position - camera._domain_center)\n            + 0.5 * unorm(camera._domain_width)\n            + np.abs(self.disparity)\n        )\n        # Rescale the ray to be long enough to cover the entire domain\n        vectors = vectors * max_length\n\n        R1 = get_rotation_matrix(0.5 * np.pi, [1, 0, 0])\n        R2 = get_rotation_matrix(0.5 * np.pi, [0, 0, 1])\n        uv = np.dot(R1, camera.unit_vectors)\n        uv = np.dot(R2, uv)\n\n        vectors.reshape((camera.resolution[0] * single_resolution_y, 3))\n        vectors = np.dot(vectors, uv)\n        vectors.reshape((camera.resolution[0], single_resolution_y, 3))\n\n        vectors2 = np.zeros(\n            (camera.resolution[0], single_resolution_y, 3), dtype=\"float64\", order=\"C\"\n        )\n        vectors2[:, :, 0] = -np.sin(px) * np.ones((1, single_resolution_y))\n        vectors2[:, :, 1] = np.cos(px) * np.ones((1, single_resolution_y))\n        vectors2[:, :, 2] = 0\n\n        vectors2.reshape((camera.resolution[0] * single_resolution_y, 3))\n        vectors2 = np.dot(vectors2, uv)\n        vectors2.reshape((camera.resolution[0], single_resolution_y, 3))\n\n        positions = np.tile(camera.position, camera.resolution[0] * single_resolution_y)\n        positions = positions.reshape(camera.resolution[0], single_resolution_y, 3)\n\n        # The left and right are switched here since VR is in LHS.\n        positions_left = positions + vectors2 * self.disparity\n        positions_right = positions + vectors2 * (-self.disparity)\n\n        if render_source.zbuffer is not None:\n            image = render_source.zbuffer.rgba\n        else:\n            image = self.new_image(camera)\n\n        image = image.reshape(*camera.resolution, 4)\n\n        dummy = np.ones(3, dtype=\"float64\")\n\n        vectors_comb = uhstack([vectors, vectors]).reshape(*camera.resolution, 3)\n        positions_comb = uhstack([positions_left, positions_right]).reshape(\n            *camera.resolution, 3\n        )\n\n        sampler_params = {\n            \"vp_pos\": positions_comb,\n            \"vp_dir\": vectors_comb,\n            \"center\": self.back_center,\n            \"bounds\": (0.0, 1.0, 0.0, 1.0),\n            \"x_vec\": dummy,\n            \"y_vec\": dummy,\n            \"width\": np.zeros(3, dtype=\"float64\"),\n            \"image\": image,\n            \"lens_type\": \"stereo-spherical\",\n        }\n        return sampler_params\n\n    def set_viewpoint(self, camera):\n        \"\"\"\n        For a PerspectiveLens, the viewpoint is the front center.\n        \"\"\"\n        self.viewpoint = camera.position\n\n\nlenses = {\n    \"plane-parallel\": PlaneParallelLens,\n    \"perspective\": PerspectiveLens,\n    \"stereo-perspective\": StereoPerspectiveLens,\n    \"fisheye\": FisheyeLens,\n    \"spherical\": SphericalLens,\n    \"stereo-spherical\": StereoSphericalLens,\n}\n"
  },
  {
    "path": "yt/visualization/volume_rendering/off_axis_projection.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.api import ImageArray\nfrom yt.funcs import is_sequence, mylog\nfrom yt.geometry.oct_geometry_handler import OctreeIndex\nfrom yt.units.unit_object import Unit  # type: ignore\nfrom yt.utilities.lib.image_utilities import add_cells_to_image_offaxis\nfrom yt.utilities.lib.partitioned_grid import PartitionedGrid\nfrom yt.utilities.lib.pixelization_routines import (\n    normalization_2d_utility,\n    off_axis_projection_SPH,\n)\nfrom yt.visualization.volume_rendering.lens import PlaneParallelLens\n\nfrom .render_source import KDTreeVolumeSource\nfrom .scene import Scene\nfrom .transfer_functions import ProjectionTransferFunction\nfrom .utils import data_source_or_all\n\n\ndef off_axis_projection(\n    data_source,\n    center,\n    normal_vector,\n    width,\n    resolution,\n    item,\n    weight=None,\n    volume=None,\n    no_ghost=False,\n    interpolated=False,\n    north_vector=None,\n    depth=None,\n    num_threads=1,\n    method=\"integrate\",\n):\n    r\"\"\"Project through a dataset, off-axis, and return the image plane.\n\n    This function will accept the necessary items to integrate through a volume\n    at an arbitrary angle and return the integrated field of view to the user.\n    Note that if a weight is supplied, it will multiply the pre-interpolated\n    values together, then create cell-centered values, then interpolate within\n    the cell to conduct the integration.\n\n    Parameters\n    ----------\n    data_source : ~yt.data_objects.static_output.Dataset\n                  or ~yt.data_objects.data_containers.YTSelectionDataContainer\n        This is the dataset or data object to volume render.\n    center : array_like\n        The current 'center' of the view port -- the focal point for the\n        camera.\n    normal_vector : array_like\n        The vector between the camera position and the center.\n    width : float or list of floats\n        The current width of the image.  If a single float, the volume is\n        cubical, but if not, it is left/right, top/bottom, front/back\n    resolution : int or list of ints\n        The number of pixels in each direction.\n    item: tuple[str, str] or FieldKey\n        The field to project through the volume, e.g. (\"gas\", \"density\").\n        This uses YT's (field_type, field_name) field pair. Common field types\n        include \"stream\", \"gas\", \"index\"; common field names include \"density\",\n        \"number_density\", \"velocity_x\", \"velocity_y\", and \"velocity_z\".\n    weight : optional, default None\n        If supplied, the field will be pre-multiplied by this, then divided by\n        the integrated value of this field.  This returns an average rather\n        than a sum.\n    volume : `yt.extensions.volume_rendering.AMRKDTree`, optional\n        The volume to ray cast through.  Can be specified for finer-grained\n        control, but otherwise will be automatically generated.\n    no_ghost: bool, optional\n        Optimization option.  If True, homogenized bricks will\n        extrapolate out from grid instead of interpolating from\n        ghost zones that have to first be calculated.  This can\n        lead to large speed improvements, but at a loss of\n        accuracy/smoothness in resulting image.  The effects are\n        less notable when the transfer function is smooth and\n        broad. Default: True\n    interpolated : optional, default False\n        If True, the data is first interpolated to vertex-centered data,\n        then tri-linearly interpolated along the ray. Not suggested for\n        quantitative studies.\n    north_vector : optional, array_like, default None\n        A vector that, if specified, restricts the orientation such that the\n        north vector dotted into the image plane points \"up\". Useful for rotations\n    depth: float, tuple[float, str], or unyt_array of size 1.\n        specify the depth of the projection region (size along the\n        line of sight). If no units are given (unyt_array or second\n        tuple element), code units are assumed.\n    num_threads: integer, optional, default 1\n        Use this many OpenMP threads during projection.\n    method : string\n        The method of projection.  Valid methods are:\n\n        \"integrate\" with no weight_field specified : integrate the requested\n        field along the line of sight.\n\n        \"integrate\" with a weight_field specified : weight the requested\n        field by the weighting field and integrate along the line of sight.\n\n        \"sum\" : This method is the same as integrate, except that it does not\n        multiply by a path length when performing the integration, and is\n        just a straight summation of the field along the given axis. WARNING:\n        This should only be used for uniform resolution grid datasets, as other\n        datasets may result in unphysical images.\n        or camera movements.\n    Returns\n    -------\n    image : array\n        An (N,N) array of the final integrated values, in float64 form.\n\n    Examples\n    --------\n\n    >>> image = off_axis_projection(\n    ...     ds,\n    ...     [0.5, 0.5, 0.5],\n    ...     [0.2, 0.3, 0.4],\n    ...     0.2,\n    ...     N,\n    ...     (\"gas\", \"temperature\"),\n    ...     (\"gas\", \"density\"),\n    ... )\n    >>> write_image(np.log10(image), \"offaxis.png\")\n\n    \"\"\"\n    if method not in (\"integrate\", \"sum\"):\n        raise NotImplementedError(\n            \"Only 'integrate' or 'sum' methods are valid for off-axis-projections\"\n        )\n\n    if interpolated:\n        raise NotImplementedError(\n            \"Only interpolated=False methods are currently implemented \"\n            \"for off-axis-projections\"\n        )\n\n    data_source = data_source_or_all(data_source)\n\n    item = data_source._determine_fields([item])[0]\n\n    # Assure vectors are numpy arrays as expected by cython code\n    normal_vector = np.array(normal_vector, dtype=\"float64\")\n    if north_vector is not None:\n        north_vector = np.array(north_vector, dtype=\"float64\")\n    # Add the normal as a field parameter to the data source\n    # so line of sight fields can use it\n    data_source.set_field_parameter(\"axis\", normal_vector)\n\n    # Sanitize units\n    if not hasattr(center, \"units\"):\n        center = data_source.ds.arr(center, \"code_length\")\n    if not hasattr(width, \"units\"):\n        width = data_source.ds.arr(width, \"code_length\")\n    if depth is not None:\n        # handle units (intrinsic or as a tuple),\n        # then convert to code length\n        # float -> assumed to be in code units\n        if isinstance(depth, tuple):\n            depth = data_source.ds.arr(np.array([depth[0]]), depth[1])\n        if hasattr(depth, \"units\"):\n            depth = depth.to(\"code_length\").d\n\n        # depth = data_source.ds.arr(depth, \"code_length\")\n\n    if hasattr(data_source.ds, \"_sph_ptypes\"):\n        if method != \"integrate\":\n            raise NotImplementedError(\"SPH Only allows 'integrate' method\")\n\n        sph_ptypes = data_source.ds._sph_ptypes\n        fi = data_source.ds.field_info[item]\n\n        raise_error = False\n\n        ptype = sph_ptypes[0]\n        ppos = [f\"particle_position_{ax}\" for ax in \"xyz\"]\n        # Assure that the field we're trying to off-axis project\n        # has a field type as the SPH particle type or if the field is an\n        # alias to an SPH field or is a 'gas' field\n        if item[0] in data_source.ds.known_filters:\n            if item[0] not in sph_ptypes:\n                raise_error = True\n            else:\n                ptype = item[0]\n                ppos = [\"x\", \"y\", \"z\"]\n        elif fi.is_alias:\n            if fi.alias_name[0] not in sph_ptypes:\n                raise_error = True\n            elif item[0] != \"gas\":\n                ptype = item[0]\n        else:\n            if fi.name[0] not in sph_ptypes and fi.name[0] != \"gas\":\n                raise_error = True\n\n        if raise_error:\n            raise RuntimeError(\n                \"Can only perform off-axis projections for SPH fields, \"\n                f\"Received {item!r}\"\n            )\n\n        normal = np.array(normal_vector)\n        normal = normal / np.linalg.norm(normal)\n\n        # If north_vector is None, we set the default here.\n        # This is chosen so that if normal_vector is one of the\n        # cartesian coordinate axes, the projection will match\n        # the corresponding on-axis projection.\n        if north_vector is None:\n            vecs = np.identity(3)\n            t = np.cross(vecs, normal).sum(axis=1)\n            ax = t.argmax()\n            east_vector = np.cross(vecs[ax, :], normal).ravel()\n            north = np.cross(normal, east_vector).ravel()\n        else:\n            north = np.array(north_vector)\n            north = north / np.linalg.norm(north)\n            east_vector = np.cross(north, normal).ravel()\n\n        # if weight is None:\n        buf = np.zeros((resolution[0], resolution[1]), dtype=\"float64\")\n        mask = np.ones_like(buf, dtype=\"uint8\")\n\n        ## width from fixed_resolution.py is just the size of the domain\n        # x_min = center[0] - width[0] / 2\n        # x_max = center[0] + width[0] / 2\n        # y_min = center[1] - width[1] / 2\n        # y_max = center[1] + width[1] / 2\n        # z_min = center[2] - width[2] / 2\n        # z_max = center[2] + width[2] / 2\n\n        periodic = data_source.ds.periodicity\n        le = data_source.ds.domain_left_edge.to(\"code_length\").d\n        re = data_source.ds.domain_right_edge.to(\"code_length\").d\n        x_min, y_min, z_min = le\n        x_max, y_max, z_max = re\n        bounds = [x_min, x_max, y_min, y_max, z_min, z_max]\n        # only need (rotated) x/y widths\n        _width = (width.to(\"code_length\").d)[:2]\n        finfo = data_source.ds.field_info[item]\n        ounits = finfo.output_units\n        kernel_name = None\n        if hasattr(data_source.ds, \"kernel_name\"):\n            kernel_name = data_source.ds.kernel_name\n        if kernel_name is None:\n            kernel_name = \"cubic\"\n        if weight is None:\n            for chunk in data_source.chunks([], \"io\"):\n                off_axis_projection_SPH(\n                    chunk[ptype, ppos[0]].to(\"code_length\").d,\n                    chunk[ptype, ppos[1]].to(\"code_length\").d,\n                    chunk[ptype, ppos[2]].to(\"code_length\").d,\n                    chunk[ptype, \"mass\"].to(\"code_mass\").d,\n                    chunk[ptype, \"density\"].to(\"code_density\").d,\n                    chunk[ptype, \"smoothing_length\"].to(\"code_length\").d,\n                    bounds,\n                    center.to(\"code_length\").d,\n                    _width,\n                    periodic,\n                    chunk[item].in_units(ounits),\n                    buf,\n                    mask,\n                    normal_vector,\n                    north,\n                    depth=depth,\n                    kernel_name=kernel_name,\n                )\n\n            # Assure that the path length unit is in the default length units\n            # for the dataset by scaling the units of the smoothing length,\n            # which in the above calculation is set to be code_length\n            path_length_unit = Unit(\n                \"code_length\", registry=data_source.ds.unit_registry\n            )\n            default_path_length_unit = data_source.ds.unit_system[\"length\"]\n            buf *= data_source.ds.quan(1, path_length_unit).in_units(\n                default_path_length_unit\n            )\n            item_unit = data_source.ds._get_field_info(item).units\n            item_unit = Unit(item_unit, registry=data_source.ds.unit_registry)\n            funits = item_unit * default_path_length_unit\n\n        else:\n            # if there is a weight field, take two projections:\n            # one of field*weight, the other of just weight, and divide them\n            weight_buff = np.zeros((resolution[0], resolution[1]), dtype=\"float64\")\n            wounits = data_source.ds.field_info[weight].output_units\n\n            for chunk in data_source.chunks([], \"io\"):\n                off_axis_projection_SPH(\n                    chunk[ptype, ppos[0]].to(\"code_length\").d,\n                    chunk[ptype, ppos[1]].to(\"code_length\").d,\n                    chunk[ptype, ppos[2]].to(\"code_length\").d,\n                    chunk[ptype, \"mass\"].to(\"code_mass\").d,\n                    chunk[ptype, \"density\"].to(\"code_density\").d,\n                    chunk[ptype, \"smoothing_length\"].to(\"code_length\").d,\n                    bounds,\n                    center.to(\"code_length\").d,\n                    _width,\n                    periodic,\n                    chunk[item].in_units(ounits),\n                    buf,\n                    mask,\n                    normal_vector,\n                    north,\n                    weight_field=chunk[weight].in_units(wounits),\n                    depth=depth,\n                    kernel_name=kernel_name,\n                )\n\n            for chunk in data_source.chunks([], \"io\"):\n                off_axis_projection_SPH(\n                    chunk[ptype, ppos[0]].to(\"code_length\").d,\n                    chunk[ptype, ppos[1]].to(\"code_length\").d,\n                    chunk[ptype, ppos[2]].to(\"code_length\").d,\n                    chunk[ptype, \"mass\"].to(\"code_mass\").d,\n                    chunk[ptype, \"density\"].to(\"code_density\").d,\n                    chunk[ptype, \"smoothing_length\"].to(\"code_length\").d,\n                    bounds,\n                    center.to(\"code_length\").d,\n                    _width,\n                    periodic,\n                    chunk[weight].to(wounits),\n                    weight_buff,\n                    mask,\n                    normal_vector,\n                    north,\n                    depth=depth,\n                    kernel_name=kernel_name,\n                )\n\n            normalization_2d_utility(buf, weight_buff)\n            item_unit = data_source.ds._get_field_info(item).units\n            item_unit = Unit(item_unit, registry=data_source.ds.unit_registry)\n            funits = item_unit\n\n        myinfo = {\n            \"field\": item,\n            \"east_vector\": east_vector,\n            \"north_vector\": north_vector,\n            \"normal_vector\": normal_vector,\n            \"width\": width,\n            \"depth\": depth,\n            \"units\": funits,\n            \"type\": \"SPH smoothed projection\",\n        }\n\n        return ImageArray(\n            buf, funits, registry=data_source.ds.unit_registry, info=myinfo\n        )\n\n    sc = Scene()\n    data_source.ds.index\n    if item is None:\n        field = data_source.ds.field_list[0]\n        mylog.info(\"Setting default field to %s\", field.__repr__())\n\n    funits = data_source.ds._get_field_info(item).units\n\n    vol = KDTreeVolumeSource(data_source, item)\n    vol.num_threads = num_threads\n    if weight is None:\n        vol.set_field(item)\n    else:\n        # This is a temporary field, which we will remove at the end.\n        weightfield = (\"index\", \"temp_weightfield\")\n\n        def _make_wf(f, w):\n            def temp_weightfield(data):\n                tr = data[f].astype(\"float64\") * data[w]\n                return tr.d\n\n            return temp_weightfield\n\n        data_source.ds.add_field(\n            weightfield,\n            sampling_type=\"cell\",\n            function=_make_wf(item, weight),\n            units=\"\",\n        )\n        vol.set_field(weightfield)\n        vol.set_weight_field(weight)\n    ptf = ProjectionTransferFunction()\n    vol.set_transfer_function(ptf)\n    camera = sc.add_camera(data_source)\n    camera.set_width(width)\n    if not is_sequence(resolution):\n        resolution = [resolution] * 2\n    camera.resolution = resolution\n    if not is_sequence(width):\n        width = data_source.ds.arr([width] * 3)\n    normal = np.array(normal_vector)\n    normal = normal / np.linalg.norm(normal)\n\n    camera.position = center - width[2] * normal\n    camera.focus = center\n\n    # If north_vector is None, we set the default here.\n    # This is chosen so that if normal_vector is one of the\n    # cartesian coordinate axes, the projection will match\n    # the corresponding on-axis projection.\n    if north_vector is None:\n        vecs = np.identity(3)\n        t = np.cross(vecs, normal).sum(axis=1)\n        ax = t.argmax()\n        east_vector = np.cross(vecs[ax, :], normal).ravel()\n        north = np.cross(normal, east_vector).ravel()\n    else:\n        north = np.array(north_vector)\n        north = north / np.linalg.norm(north)\n    camera.switch_orientation(normal, north)\n\n    sc.add_source(vol)\n\n    vol.set_sampler(camera, interpolated=False)\n    assert vol.sampler is not None\n\n    fields = [vol.field]\n    if vol.weight_field is not None:\n        fields.append(vol.weight_field)\n\n    mylog.debug(\"Casting rays\")\n    index = data_source.ds.index\n    lens = camera.lens\n\n    # This implementation is optimized for octrees with plane-parallel lenses\n    # and implicitely assumes that the cells are cubic.\n    # NOTE: we should be able to relax the cubic assumption to a rectangular\n    #       assumption (if all cells have the same aspect ratio) with some\n    #       renormalization of the coordinates and the projection axes.\n    #       This is NOT done in the following.\n    dom_width = data_source.ds.domain_width\n    cubic_domain = dom_width.max() == dom_width.min()\n    if (\n        isinstance(index, OctreeIndex)\n        and isinstance(lens, PlaneParallelLens)\n        and cubic_domain\n    ):\n        fields.extend((\"index\", k) for k in \"xyz\")\n        fields.append((\"index\", \"dx\"))\n\n        data_source.get_data(fields)\n        # We need the width of the plot window in projected coordinates,\n        # i.e. we ignore the z-component\n        wmax = width[:2].max().to(\"code_length\")\n        xyz = data_source.ds.arr(\n            np.zeros((len(data_source[vol.field]), 3)), \"code_length\"\n        )\n\n        for idim, periodic in enumerate(data_source.ds.periodicity):\n            axis = data_source.ds.coordinates.axis_order[idim]\n            # Recenter positions w.r.t. center of the plot window\n            xyz[..., idim] = (data_source[\"index\", axis] - center[idim]).to(\n                \"code_length\"\n            )\n            if not periodic:\n                continue\n            # If we have periodic boundaries, we need to wrap the corresponding\n            # coordinates into [-w/2, +w/2]\n            w = data_source.ds.domain_width[idim].to(\"code_length\")\n            xyz[..., idim] = (xyz[..., idim] + w / 2) % w - w / 2\n\n        # Rescale to [-0.5, +0.5]\n        xyz = (xyz / wmax).to(\"1\").d\n\n        dx = (data_source[\"index\", \"dx\"] / wmax).to(\"1\").d\n\n        if vol.weight_field is None:\n            weight_field = np.ones_like(dx)\n        else:\n            weight_field = data_source[vol.weight_field]\n\n        projected_weighted_qty = np.zeros(resolution)\n        projected_weight = np.zeros(resolution)\n\n        add_cells_to_image_offaxis(\n            Xp=xyz,\n            dXp=dx,\n            qty=data_source[vol.field],\n            weight=weight_field,\n            rotation=camera.inv_mat.T,\n            buffer=projected_weighted_qty,\n            buffer_weight=projected_weight,\n            Nx=resolution[0],\n            Ny=resolution[1],\n        )\n        # Note: since dx was divided by wmax, we need to rescale by it\n        projected_weighted_qty *= wmax.d / np.sqrt(3)\n        projected_weight *= wmax.d / np.sqrt(3)\n\n        image = ImageArray(\n            data_source.ds.arr(\n                np.stack([projected_weighted_qty, projected_weight], axis=-1),\n                \"dimensionless\",\n            ),\n            funits,\n            registry=data_source.ds.unit_registry,\n            info={\"imtype\": \"rendering\"},\n        )\n\n        # Clear temporary field data associated to weightfield\n        if weight is not None:\n            data_source.clear_data(weightfield)\n\n    else:\n        for grid, mask in data_source.blocks:\n            data = []\n            for f in fields:\n                # strip units before multiplying by mask for speed\n                grid_data = grid[f]\n                units = grid_data.units\n                data.append(\n                    data_source.ds.arr(grid_data.d * mask, units, dtype=\"float64\")\n                )\n            pg = PartitionedGrid(\n                grid.id,\n                data,\n                mask.astype(\"uint8\"),\n                grid.LeftEdge,\n                grid.RightEdge,\n                grid.ActiveDimensions.astype(\"int64\"),\n            )\n            grid.clear_data()\n            vol.sampler(pg, num_threads=num_threads)\n\n        image = vol.finalize_image(camera, vol.sampler.aimage)\n\n        image = ImageArray(\n            image, funits, registry=data_source.ds.unit_registry, info=image.info\n        )\n\n    # Remove the temporary weight field\n    if weight is not None:\n        data_source.ds.field_info.pop(weightfield)\n        data_source.ds.field_dependencies.pop(weightfield)\n\n    if method == \"integrate\":\n        if weight is None:\n            dl = width[2].in_units(data_source.ds.unit_system[\"length\"])\n            image *= dl\n        else:\n            mask = image[:, :, 1] == 0\n            nmask = np.logical_not(mask)\n            image[:, :, 0][nmask] /= image[:, :, 1][nmask]\n            image[mask] = 0\n\n    return image[:, :, 0]\n"
  },
  {
    "path": "yt/visualization/volume_rendering/old_camera.py",
    "content": "from copy import deepcopy\n\nimport numpy as np\n\nfrom yt._maintenance.ipython_compat import IS_IPYTHON\nfrom yt.config import ytcfg\nfrom yt.data_objects.api import ImageArray\nfrom yt.funcs import ensure_numpy_array, get_num_threads, get_pbar, is_sequence, mylog\nfrom yt.units.yt_array import YTArray\nfrom yt.utilities.amr_kdtree.api import AMRKDTree\nfrom yt.utilities.exceptions import YTNotInsideNotebook\nfrom yt.utilities.lib.grid_traversal import (\n    arr_fisheye_vectors,\n    arr_pix2vec_nest,\n    pixelize_healpix,\n)\nfrom yt.utilities.lib.image_samplers import (\n    InterpolatedProjectionSampler,\n    LightSourceRenderSampler,\n    ProjectionSampler,\n    VolumeRenderSampler,\n)\nfrom yt.utilities.lib.misc_utilities import lines\nfrom yt.utilities.lib.partitioned_grid import PartitionedGrid\nfrom yt.utilities.math_utils import get_rotation_matrix\nfrom yt.utilities.object_registries import data_object_registry\nfrom yt.utilities.orientation import Orientation\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    ParallelAnalysisInterface,\n    parallel_objects,\n)\nfrom yt.visualization.image_writer import apply_colormap, write_bitmap, write_image\nfrom yt.visualization.volume_rendering.blenders import enhance_rgba\n\nfrom .transfer_functions import ProjectionTransferFunction\n\n\ndef get_corners(le, re):\n    return np.array(\n        [\n            [le[0], le[1], le[2]],\n            [re[0], le[1], le[2]],\n            [re[0], re[1], le[2]],\n            [le[0], re[1], le[2]],\n            [le[0], le[1], re[2]],\n            [re[0], le[1], re[2]],\n            [re[0], re[1], re[2]],\n            [le[0], re[1], re[2]],\n        ],\n        dtype=\"float64\",\n    )\n\n\nclass Camera(ParallelAnalysisInterface):\n    r\"\"\"A viewpoint into a volume, for volume rendering.\n\n    The camera represents the eye of an observer, which will be used to\n    generate ray-cast volume renderings of the domain.\n\n    Parameters\n    ----------\n    center : array_like\n        The current \"center\" of the view port -- the focal point for the\n        camera.\n    normal_vector : array_like\n        The vector between the camera position and the center.\n    width : float or list of floats\n        The current width of the image.  If a single float, the volume is\n        cubical, but if not, it is left/right, top/bottom, front/back.\n    resolution : int or list of ints\n        The number of pixels in each direction.\n    transfer_function : `yt.visualization.volume_rendering.TransferFunction`\n        The transfer function used to map values to colors in an image.  If\n        not specified, defaults to a ProjectionTransferFunction.\n    north_vector : array_like, optional\n        The 'up' direction for the plane of rays.  If not specific, calculated\n        automatically.\n    steady_north : bool, optional\n        Boolean to control whether to normalize the north_vector\n        by subtracting off the dot product of it and the normal\n        vector.  Makes it easier to do rotations along a single\n        axis.  If north_vector is specified, is switched to\n        True. Default: False\n    volume : `yt.extensions.volume_rendering.AMRKDTree`, optional\n        The volume to ray cast through.  Can be specified for finer-grained\n        control, but otherwise will be automatically generated.\n    fields : list of fields, optional\n        This is the list of fields we want to volume render; defaults to\n        Density.\n    log_fields : list of bool, optional\n        Whether we should take the log of the fields before supplying them to\n        the volume rendering mechanism.\n    sub_samples : int, optional\n        The number of samples to take inside every cell per ray.\n    ds : ~yt.data_objects.static_output.Dataset\n        For now, this is a require parameter!  But in the future it will become\n        optional.  This is the dataset to volume render.\n    max_level: int, optional\n        Specifies the maximum level to be rendered.  Also\n        specifies the maximum level used in the kd-Tree\n        construction.  Defaults to None (all levels), and only\n        applies if use_kd=True.\n    no_ghost: bool, optional\n        Optimization option.  If True, homogenized bricks will\n        extrapolate out from grid instead of interpolating from\n        ghost zones that have to first be calculated.  This can\n        lead to large speed improvements, but at a loss of\n        accuracy/smoothness in resulting image.  The effects are\n        less notable when the transfer function is smooth and\n        broad. Default: True\n    data_source: data container, optional\n        Optionally specify an arbitrary data source to the volume rendering.\n        All cells not included in the data source will be ignored during ray\n        casting. By default this will get set to ds.all_data().\n\n    Examples\n    --------\n\n    >>> import yt.visualization.volume_rendering.api as vr\n\n    >>> ds = load(\"DD1701\")  # Load a dataset\n    >>> c = [0.5] * 3  # Center\n    >>> L = [1.0, 1.0, 1.0]  # Viewpoint\n    >>> W = np.sqrt(3)  # Width\n    >>> N = 1024  # Pixels (1024^2)\n\n    # Get density min, max\n    >>> mi, ma = ds.all_data().quantities[\"Extrema\"](\"Density\")[0]\n    >>> mi, ma = np.log10(mi), np.log10(ma)\n\n    # Construct transfer function\n    >>> tf = vr.ColorTransferFunction((mi - 2, ma + 2))\n    # Sample transfer function with 5 gaussians.  Use new col_bounds keyword.\n    >>> tf.add_layers(5, w=0.05, col_bounds=(mi + 1, ma), colormap=\"nipy_spectral\")\n\n    # Create the camera object\n    >>> cam = vr.Camera(c, L, W, (N, N), transfer_function=tf, ds=ds)\n\n    # Ray cast, and save the image.\n    >>> image = cam.snapshot(fn=\"my_rendering.png\")\n\n    \"\"\"\n\n    _sampler_object = VolumeRenderSampler\n    _tf_figure = None\n    _render_figure = None\n\n    def __init__(\n        self,\n        center,\n        normal_vector,\n        width,\n        resolution,\n        transfer_function=None,\n        north_vector=None,\n        steady_north=False,\n        volume=None,\n        fields=None,\n        log_fields=None,\n        sub_samples=5,\n        ds=None,\n        min_level=None,\n        max_level=None,\n        no_ghost=True,\n        data_source=None,\n        use_light=False,\n    ):\n        ParallelAnalysisInterface.__init__(self)\n        if ds is not None:\n            self.ds = ds\n        if not is_sequence(resolution):\n            resolution = (resolution, resolution)\n        self.resolution = resolution\n        self.sub_samples = sub_samples\n        self.rotation_vector = north_vector\n        if is_sequence(width) and len(width) > 1 and isinstance(width[1], str):\n            width = self.ds.quan(width[0], units=width[1])\n            # Now convert back to code length for subsequent manipulation\n            width = width.in_units(\"code_length\").value\n        if not is_sequence(width):\n            width = (width, width, width)  # left/right, top/bottom, front/back\n        if not isinstance(width, YTArray):\n            width = self.ds.arr(width, units=\"code_length\")\n        if not isinstance(center, YTArray):\n            center = self.ds.arr(center, units=\"code_length\")\n        # Ensure that width and center are in the same units\n        # Cf. https://bitbucket.org/yt_analysis/yt/issue/1080\n        width = width.in_units(\"code_length\")\n        center = center.in_units(\"code_length\")\n        self.orienter = Orientation(\n            normal_vector, north_vector=north_vector, steady_north=steady_north\n        )\n        if not steady_north:\n            self.rotation_vector = self.orienter.unit_vectors[1]\n        self._setup_box_properties(width, center, self.orienter.unit_vectors)\n        if fields is None:\n            fields = [(\"gas\", \"density\")]\n        self.fields = fields\n        if transfer_function is None:\n            transfer_function = ProjectionTransferFunction()\n        self.transfer_function = transfer_function\n        self.log_fields = log_fields\n        dd = self.ds.all_data()\n        efields = dd._determine_fields(self.fields)\n        if self.log_fields is None:\n            self.log_fields = [self.ds._get_field_info(f).take_log for f in efields]\n        self.no_ghost = no_ghost\n        self.use_light = use_light\n        self.light_dir = None\n        self.light_rgba = None\n        if self.no_ghost:\n            mylog.warning(\n                \"no_ghost is currently True (default). \"\n                \"This may lead to artifacts at grid boundaries.\"\n            )\n\n        if data_source is None:\n            data_source = self.ds.all_data()\n        self.data_source = data_source\n\n        if volume is None:\n            volume = AMRKDTree(\n                self.ds,\n                min_level=min_level,\n                max_level=max_level,\n                data_source=self.data_source,\n            )\n        self.volume = volume\n\n    def _setup_box_properties(self, width, center, unit_vectors):\n        self.width = width\n        self.center = center\n        self.box_vectors = YTArray(\n            [\n                unit_vectors[0] * width[0],\n                unit_vectors[1] * width[1],\n                unit_vectors[2] * width[2],\n            ]\n        )\n        self.origin = center - 0.5 * width.dot(YTArray(unit_vectors, \"\"))\n        self.back_center = center - 0.5 * width[2] * unit_vectors[2]\n        self.front_center = center + 0.5 * width[2] * unit_vectors[2]\n\n    def update_view_from_matrix(self, mat):\n        pass\n\n    def project_to_plane(self, pos, res=None):\n        if res is None:\n            res = self.resolution\n        dx = np.dot(pos - self.origin, self.orienter.unit_vectors[1])\n        dy = np.dot(pos - self.origin, self.orienter.unit_vectors[0])\n        dz = np.dot(pos - self.center, self.orienter.unit_vectors[2])\n        # Transpose into image coords.\n        py = (res[0] * (dx / self.width[0])).astype(\"int64\")\n        px = (res[1] * (dy / self.width[1])).astype(\"int64\")\n        return px, py, dz\n\n    def draw_grids(self, im, alpha=0.3, cmap=None, min_level=None, max_level=None):\n        r\"\"\"Draws Grids on an existing volume rendering.\n\n        By mapping grid level to a color, draws edges of grids on\n        a volume rendering using the camera orientation.\n\n        Parameters\n        ----------\n        im: Numpy ndarray\n            Existing image that has the same resolution as the Camera,\n            which will be painted by grid lines.\n        alpha : float, optional\n            The alpha value for the grids being drawn.  Used to control\n            how bright the grid lines are with respect to the image.\n            Default : 0.3\n        cmap : string, optional\n            Colormap to be used mapping grid levels to colors.\n        min_level : int, optional\n            Optional parameter to specify the min level grid boxes\n            to overplot on the image.\n        max_level : int, optional\n            Optional parameters to specify the max level grid boxes\n            to overplot on the image.\n\n        Returns\n        -------\n        None\n\n        Examples\n        --------\n        >>> im = cam.snapshot()\n        >>> cam.add_grids(im)\n        >>> write_bitmap(im, \"render_with_grids.png\")\n\n        \"\"\"\n        if cmap is None:\n            cmap = ytcfg.get(\"yt\", \"default_colormap\")\n        region = self.data_source\n        corners = []\n        levels = []\n        for block, _mask in region.blocks:\n            block_corners = np.array(\n                [\n                    [block.LeftEdge[0], block.LeftEdge[1], block.LeftEdge[2]],\n                    [block.RightEdge[0], block.LeftEdge[1], block.LeftEdge[2]],\n                    [block.RightEdge[0], block.RightEdge[1], block.LeftEdge[2]],\n                    [block.LeftEdge[0], block.RightEdge[1], block.LeftEdge[2]],\n                    [block.LeftEdge[0], block.LeftEdge[1], block.RightEdge[2]],\n                    [block.RightEdge[0], block.LeftEdge[1], block.RightEdge[2]],\n                    [block.RightEdge[0], block.RightEdge[1], block.RightEdge[2]],\n                    [block.LeftEdge[0], block.RightEdge[1], block.RightEdge[2]],\n                ],\n                dtype=\"float64\",\n            )\n            corners.append(block_corners)\n            levels.append(block.Level)\n        corners = np.dstack(corners)\n        levels = np.array(levels)\n\n        if max_level is not None:\n            subset = levels <= max_level\n            levels = levels[subset]\n            corners = corners[:, :, subset]\n        if min_level is not None:\n            subset = levels >= min_level\n            levels = levels[subset]\n            corners = corners[:, :, subset]\n\n        colors = (\n            apply_colormap(\n                levels * 1.0, color_bounds=[0, self.ds.index.max_level], cmap_name=cmap\n            )[0, :, :]\n            * 1.0\n            / 255.0\n        )\n        colors[:, 3] = alpha\n\n        order = [0, 1, 1, 2, 2, 3, 3, 0]\n        order += [4, 5, 5, 6, 6, 7, 7, 4]\n        order += [0, 4, 1, 5, 2, 6, 3, 7]\n\n        vertices = np.empty([corners.shape[2] * 2 * 12, 3])\n        vertices = self.ds.arr(vertices, \"code_length\")\n        for i in range(3):\n            vertices[:, i] = corners[order, i, ...].ravel(order=\"F\")\n\n        px, py, dz = self.project_to_plane(vertices, res=im.shape[:2])\n\n        # Must normalize the image\n        nim = im.rescale(inline=False)\n        enhance_rgba(nim)\n        nim.add_background_color(\"black\", inline=True)\n\n        # we flipped it in snapshot to get the orientation correct, so\n        # flip the lines\n        lines(nim.d, px.d, py.d, colors, 24, flip=1)\n\n        return nim\n\n    def draw_coordinate_vectors(self, im, length=0.05, thickness=1):\n        r\"\"\"Draws three coordinate vectors in the corner of a rendering.\n\n        Modifies an existing image to have three lines corresponding to the\n        coordinate directions colored by {x,y,z} = {r,g,b}.  Currently only\n        functional for plane-parallel volume rendering.\n\n        Parameters\n        ----------\n        im: Numpy ndarray\n            Existing image that has the same resolution as the Camera,\n            which will be painted by grid lines.\n        length: float, optional\n            The length of the lines, as a fraction of the image size.\n            Default : 0.05\n        thickness : int, optional\n            Thickness in pixels of the line to be drawn.\n\n        Returns\n        -------\n        None\n\n        Modifies\n        --------\n        im: The original image.\n\n        Examples\n        --------\n        >>> im = cam.snapshot()\n        >>> cam.draw_coordinate_vectors(im)\n        >>> im.write_png(\"render_with_grids.png\")\n\n        \"\"\"\n        length_pixels = length * self.resolution[0]\n        # Put the starting point in the lower left\n        px0 = int(length * self.resolution[0])\n        # CS coordinates!\n        py0 = int((1.0 - length) * self.resolution[1])\n\n        alpha = im[:, :, 3].max()\n        if alpha == 0.0:\n            alpha = 1.0\n\n        coord_vectors = [\n            np.array([length_pixels, 0.0, 0.0]),\n            np.array([0.0, length_pixels, 0.0]),\n            np.array([0.0, 0.0, length_pixels]),\n        ]\n        colors = [\n            np.array([1.0, 0.0, 0.0, alpha]),\n            np.array([0.0, 1.0, 0.0, alpha]),\n            np.array([0.0, 0.0, 1.0, alpha]),\n        ]\n\n        # we flipped it in snapshot to get the orientation correct, so\n        # flip the lines\n        for vec, color in zip(coord_vectors, colors, strict=True):\n            dx = int(np.dot(vec, self.orienter.unit_vectors[0]))\n            dy = int(np.dot(vec, self.orienter.unit_vectors[1]))\n            px = np.array([px0, px0 + dx], dtype=\"int64\")\n            py = np.array([py0, py0 + dy], dtype=\"int64\")\n            lines(im.d, px, py, np.array([color, color]), 1, thickness, flip=1)\n\n    def draw_line(self, im, x0, x1, color=None):\n        r\"\"\"Draws a line on an existing volume rendering.\n        Given starting and ending positions x0 and x1, draws a line on\n        a volume rendering using the camera orientation.\n\n        Parameters\n        ----------\n        im : ImageArray or 2D ndarray\n            Existing image that has the same resolution as the Camera,\n            which will be painted by grid lines.\n        x0 : YTArray or ndarray\n            Starting coordinate.  If passed in as an ndarray,\n            assumed to be in code units.\n        x1 : YTArray or ndarray\n            Ending coordinate, in simulation coordinates.  If passed in as\n            an ndarray, assumed to be in code units.\n        color : array like, optional\n            Color of the line (r, g, b, a). Defaults to white.\n\n        Returns\n        -------\n        None\n\n        Examples\n        --------\n        >>> im = cam.snapshot()\n        >>> cam.draw_line(im, np.array([0.1, 0.2, 0.3]), np.array([0.5, 0.6, 0.7]))\n        >>> write_bitmap(im, \"render_with_line.png\")\n\n        \"\"\"\n        if color is None:\n            color = np.array([1.0, 1.0, 1.0, 1.0])\n\n        if not hasattr(x0, \"units\"):\n            x0 = self.ds.arr(x0, \"code_length\")\n        if not hasattr(x1, \"units\"):\n            x1 = self.ds.arr(x1, \"code_length\")\n\n        dx0 = ((x0 - self.origin) * self.orienter.unit_vectors[1]).sum()\n        dx1 = ((x1 - self.origin) * self.orienter.unit_vectors[1]).sum()\n        dy0 = ((x0 - self.origin) * self.orienter.unit_vectors[0]).sum()\n        dy1 = ((x1 - self.origin) * self.orienter.unit_vectors[0]).sum()\n        py0 = int(self.resolution[0] * (dx0 / self.width[0]))\n        py1 = int(self.resolution[0] * (dx1 / self.width[0]))\n        px0 = int(self.resolution[1] * (dy0 / self.width[1]))\n        px1 = int(self.resolution[1] * (dy1 / self.width[1]))\n        px = np.array([px0, px1], dtype=\"int64\")\n        py = np.array([py0, py1], dtype=\"int64\")\n        # we flipped it in snapshot to get the orientation correct, so\n        # flip the lines\n        lines(im.d, px, py, np.array([color, color]), flip=1)\n\n    def draw_domain(self, im, alpha=0.3):\n        r\"\"\"Draws domain edges on an existing volume rendering.\n\n        Draws a white wireframe on the domain edges.\n\n        Parameters\n        ----------\n        im: Numpy ndarray\n            Existing image that has the same resolution as the Camera,\n            which will be painted by grid lines.\n        alpha : float, optional\n            The alpha value for the wireframe being drawn.  Used to control\n            how bright the lines are with respect to the image.\n            Default : 0.3\n\n        Returns\n        -------\n        nim: Numpy ndarray\n            A new image with the domain lines drawn\n\n        Examples\n        --------\n        >>> im = cam.snapshot()\n        >>> nim = cam.draw_domain(im)\n        >>> write_bitmap(nim, \"render_with_domain_boundary.png\")\n\n        \"\"\"\n        # Must normalize the image\n        nim = im.rescale(inline=False)\n        enhance_rgba(nim)\n        nim.add_background_color(\"black\", inline=True)\n\n        self.draw_box(\n            nim,\n            self.ds.domain_left_edge,\n            self.ds.domain_right_edge,\n            color=np.array([1.0, 1.0, 1.0, alpha]),\n        )\n        return nim\n\n    def draw_box(self, im, le, re, color=None):\n        r\"\"\"Draws a box on an existing volume rendering.\n\n        Draws a box defined by a left and right edge by modifying an\n        existing volume rendering\n\n        Parameters\n        ----------\n        im: Numpy ndarray\n            Existing image that has the same resolution as the Camera,\n            which will be painted by grid lines.\n        le: Numpy ndarray\n            Left corner of the box\n        re : Numpy ndarray\n            Right corner of the box\n        color : array like, optional\n            Color of the box (r, g, b, a). Defaults to white.\n\n        Returns\n        -------\n        None\n\n        Examples\n        --------\n        >>> im = cam.snapshot()\n        >>> cam.draw_box(im, np.array([0.1, 0.2, 0.3]), np.array([0.5, 0.6, 0.7]))\n        >>> write_bitmap(im, \"render_with_box.png\")\n\n        \"\"\"\n\n        if color is None:\n            color = np.array([1.0, 1.0, 1.0, 1.0])\n        corners = get_corners(le, re)\n        order = [0, 1, 1, 2, 2, 3, 3, 0]\n        order += [4, 5, 5, 6, 6, 7, 7, 4]\n        order += [0, 4, 1, 5, 2, 6, 3, 7]\n\n        vertices = np.empty([24, 3])\n        vertices = self.ds.arr(vertices, \"code_length\")\n        for i in range(3):\n            vertices[:, i] = corners[order, i, ...].ravel(order=\"F\")\n\n        px, py, dz = self.project_to_plane(vertices, res=im.shape[:2])\n\n        # we flipped it in snapshot to get the orientation correct, so\n        # flip the lines\n        lines(\n            im.d,\n            px.d.astype(\"int64\"),\n            py.d.astype(\"int64\"),\n            color.reshape(1, 4),\n            24,\n            flip=1,\n        )\n\n    def look_at(self, new_center, north_vector=None):\n        r\"\"\"Change the view direction based on a new focal point.\n\n        This will recalculate all the necessary vectors and vector planes to orient\n        the image plane so that it points at a new location.\n\n        Parameters\n        ----------\n        new_center : array_like\n            The new \"center\" of the view port -- the focal point for the\n            camera.\n        north_vector : array_like, optional\n            The \"up\" direction for the plane of rays.  If not specific,\n            calculated automatically.\n        \"\"\"\n        normal_vector = self.front_center - new_center\n        self.orienter.switch_orientation(\n            normal_vector=normal_vector, north_vector=north_vector\n        )\n\n    def switch_orientation(self, normal_vector=None, north_vector=None):\n        r\"\"\"\n        Change the view direction based on any of the orientation parameters.\n\n        This will recalculate all the necessary vectors and vector planes\n        related to an orientable object.\n\n        Parameters\n        ----------\n        normal_vector: array_like, optional\n            The new looking vector.\n        north_vector : array_like, optional\n            The 'up' direction for the plane of rays.  If not specific,\n            calculated automatically.\n        \"\"\"\n        if north_vector is None:\n            north_vector = self.north_vector\n        if normal_vector is None:\n            normal_vector = self.normal_vector\n        self.orienter._setup_normalized_vectors(normal_vector, north_vector)\n\n    def switch_view(\n        self, normal_vector=None, width=None, center=None, north_vector=None\n    ):\n        r\"\"\"Change the view based on any of the view parameters.\n\n        This will recalculate the orientation and width based on any of\n        normal_vector, width, center, and north_vector.\n\n        Parameters\n        ----------\n        normal_vector: array_like, optional\n            The new looking vector.\n        width: float or array of floats, optional\n            The new width.  Can be a single value W -> [W,W,W] or an\n            array [W1, W2, W3] (left/right, top/bottom, front/back)\n        center: array_like, optional\n            Specifies the new center.\n        north_vector : array_like, optional\n            The 'up' direction for the plane of rays.  If not specific,\n            calculated automatically.\n        \"\"\"\n        if width is None:\n            width = self.width\n        if not is_sequence(width):\n            width = (width, width, width)  # left/right, tom/bottom, front/back\n        self.width = width\n        if center is not None:\n            self.center = center\n        if north_vector is None:\n            north_vector = self.orienter.north_vector\n        if normal_vector is None:\n            normal_vector = self.orienter.normal_vector\n        self.switch_orientation(normal_vector=normal_vector, north_vector=north_vector)\n        self._setup_box_properties(width, self.center, self.orienter.unit_vectors)\n\n    def new_image(self):\n        image = np.zeros(\n            (self.resolution[0], self.resolution[1], 4), dtype=\"float64\", order=\"C\"\n        )\n        return image\n\n    def get_sampler_args(self, image):\n        rotp = np.concatenate(\n            [self.orienter.inv_mat.ravel(\"F\"), self.back_center.ravel().ndview]\n        )\n        args = (\n            np.atleast_3d(rotp),\n            np.atleast_3d(self.box_vectors[2]),\n            self.back_center,\n            (\n                -self.width[0] / 2.0,\n                self.width[0] / 2.0,\n                -self.width[1] / 2.0,\n                self.width[1] / 2.0,\n            ),\n            image,\n            self.orienter.unit_vectors[0],\n            self.orienter.unit_vectors[1],\n            np.array(self.width, dtype=\"float64\"),\n            \"KDTree\",\n            self.transfer_function,\n            self.sub_samples,\n        )\n        kwargs = {\n            \"lens_type\": \"plane-parallel\",\n        }\n        return args, kwargs\n\n    def get_sampler(self, args, kwargs):\n        if self.use_light:\n            if self.light_dir is None:\n                self.set_default_light_dir()\n            temp_dir = np.empty(3, dtype=\"float64\")\n            temp_dir = (\n                self.light_dir[0] * self.orienter.unit_vectors[1]\n                + self.light_dir[1] * self.orienter.unit_vectors[2]\n                + self.light_dir[2] * self.orienter.unit_vectors[0]\n            )\n            if self.light_rgba is None:\n                self.set_default_light_rgba()\n            sampler = LightSourceRenderSampler(\n                *args, light_dir=temp_dir, light_rgba=self.light_rgba, **kwargs\n            )\n        else:\n            sampler = self._sampler_object(*args, **kwargs)\n        return sampler\n\n    def finalize_image(self, image):\n        view_pos = (\n            self.front_center + self.orienter.unit_vectors[2] * 1.0e6 * self.width[2]\n        )\n        image = self.volume.reduce_tree_images(image, view_pos)\n        if not self.transfer_function.grey_opacity:\n            image[:, :, 3] = 1.0\n        return image\n\n    def _render(self, double_check, num_threads, image, sampler):\n        ncells = sum(b.source_mask.size for b in self.volume.bricks)\n        pbar = get_pbar(\"Ray casting\", ncells)\n        total_cells = 0\n        if double_check:\n            for brick in self.volume.bricks:\n                for data in brick.my_data:\n                    if np.any(np.isnan(data)):\n                        raise RuntimeError\n\n        view_pos = (\n            self.front_center + self.orienter.unit_vectors[2] * 1.0e6 * self.width[2]\n        )\n        for brick in self.volume.traverse(view_pos):\n            sampler(brick, num_threads=num_threads)\n            total_cells += brick.source_mask.size\n            pbar.update(total_cells)\n\n        pbar.finish()\n        return self.finalize_image(sampler.aimage)\n\n    def _pyplot(self):\n        from matplotlib import pyplot\n\n        return pyplot\n\n    def show_tf(self):\n        if self._tf_figure is None:\n            self._tf_figure = self._pyplot.figure(2)\n            self.transfer_function.show(ax=self._tf_figure.axes)\n        self._pyplot.draw()\n\n    def annotate(self, ax, enhance=True, label_fmt=None):\n        ax.get_xaxis().set_visible(False)\n        ax.get_xaxis().set_ticks([])\n        ax.get_yaxis().set_visible(False)\n        ax.get_yaxis().set_ticks([])\n        cb = self._pyplot.colorbar(\n            ax.images[0], pad=0.0, fraction=0.05, drawedges=True, shrink=0.9\n        )\n        label = self.ds._get_field_info(self.fields[0]).get_label()\n        if self.log_fields[0]:\n            label = r\"$\\rm{log}\\ $\" + label\n        self.transfer_function.vert_cbar(ax=cb.ax, label=label, label_fmt=label_fmt)\n\n    def show_mpl(self, im, enhance=True, clear_fig=True):\n        if self._render_figure is None:\n            self._render_figure = self._pyplot.figure(1)\n        if clear_fig:\n            self._render_figure.clf()\n\n        if enhance:\n            nz = im[im > 0.0]\n            nim = im / (nz.mean() + 6.0 * np.std(nz))\n            nim[nim > 1.0] = 1.0\n            nim[nim < 0.0] = 0.0\n            del nz\n        else:\n            nim = im\n        ax = self._pyplot.imshow(nim[:, :, :3] / nim[:, :, :3].max(), origin=\"upper\")\n        return ax\n\n    def draw(self):\n        self._pyplot.draw()\n\n    def save_annotated(\n        self, fn, image, enhance=True, dpi=100, clear_fig=True, label_fmt=None\n    ):\n        \"\"\"\n        Save an image with the transfer function represented as a colorbar.\n\n        Parameters\n        ----------\n        fn : str\n           The output filename\n        image : ImageArray\n           The image to annotate\n        enhance : bool, optional\n           Enhance the contrast (default: True)\n        dpi : int, optional\n           Dots per inch in the output image (default: 100)\n        clear_fig : bool, optional\n           Reset the figure (through matplotlib.pyplot.clf()) before drawing.  Setting\n           this to false can allow us to overlay the image onto an\n           existing figure\n        label_fmt : str, optional\n           A format specifier (e.g., label_fmt=\"%.2g\") to use in formatting\n           the data values that label the transfer function colorbar.\n\n        \"\"\"\n        image = image.swapaxes(0, 1)\n        ax = self.show_mpl(image, enhance=enhance, clear_fig=clear_fig)\n        self.annotate(ax.axes, enhance, label_fmt=label_fmt)\n        self._pyplot.savefig(fn, bbox_inches=\"tight\", facecolor=\"black\", dpi=dpi)\n\n    def save_image(self, image, fn=None, clip_ratio=None, transparent=False):\n        if self.comm.rank == 0 and fn is not None:\n            background = None if transparent else \"black\"\n            image.write_png(fn, rescale=True, background=background)\n\n    def initialize_source(self):\n        return self.volume.initialize_source(\n            self.fields, self.log_fields, self.no_ghost\n        )\n\n    def get_information(self):\n        info_dict = {\n            \"fields\": self.fields,\n            \"type\": self.__class__.__name__,\n            \"east_vector\": self.orienter.unit_vectors[0],\n            \"north_vector\": self.orienter.unit_vectors[1],\n            \"normal_vector\": self.orienter.unit_vectors[2],\n            \"width\": self.width,\n            \"dataset\": self.ds.directory,\n        }\n        return info_dict\n\n    def snapshot(\n        self,\n        fn=None,\n        clip_ratio=None,\n        double_check=False,\n        num_threads=0,\n        transparent=False,\n    ):\n        r\"\"\"Ray-cast the camera.\n\n        This method instructs the camera to take a snapshot -- i.e., call the ray\n        caster -- based on its current settings.\n\n        Parameters\n        ----------\n        fn : string, optional\n            If supplied, the image will be saved out to this before being\n            returned.  Scaling will be to the maximum value.\n        clip_ratio : float, optional\n            If supplied, the 'max_val' argument to write_bitmap will be handed\n            clip_ratio * image.std()\n        double_check : bool, optional\n            Optionally makes sure that the data contains only valid entries.\n            Used for debugging.\n        num_threads : int, optional\n            If supplied, will use 'num_threads' number of OpenMP threads during\n            the rendering.  Defaults to 0, which uses the environment variable\n            OMP_NUM_THREADS.\n        transparent: bool, optional\n            Optionally saves out the 4-channel rgba image, which can appear\n            empty if the alpha channel is low everywhere. Default: False\n\n        Returns\n        -------\n        image : array\n            An (N,M,3) array of the final returned values, in float64 form.\n        \"\"\"\n        if num_threads is None:\n            num_threads = get_num_threads()\n        image = self.new_image()\n        args, kwargs = self.get_sampler_args(image)\n        sampler = self.get_sampler(args, kwargs)\n        self.initialize_source()\n        image = ImageArray(\n            self._render(double_check, num_threads, image, sampler),\n            info=self.get_information(),\n        )\n\n        # flip it up/down to handle how the png orientation is done\n        image = image[:, ::-1, :]\n        self.save_image(image, fn=fn, clip_ratio=clip_ratio, transparent=transparent)\n        return image\n\n    def show(self, clip_ratio=None):\n        r\"\"\"This will take a snapshot and display the resultant image in the\n        IPython notebook.\n\n        If yt is being run from within an IPython session, and it is able to\n        determine this, this function will snapshot and send the resultant\n        image to the IPython notebook for display.\n\n        If yt can't determine if it's inside an IPython session, it will raise\n        YTNotInsideNotebook.\n\n        Parameters\n        ----------\n        clip_ratio : float, optional\n            If supplied, the 'max_val' argument to write_bitmap will be handed\n            clip_ratio * image.std()\n\n        Examples\n        --------\n\n        >>> cam.show()\n\n        \"\"\"\n        if IS_IPYTHON:\n            from IPython.core.displaypub import publish_display_data\n\n            image = self.snapshot()[:, :, :3]\n            if clip_ratio is not None:\n                clip_ratio *= image.std()\n            data = write_bitmap(image, None, clip_ratio)\n            publish_display_data(\n                data={\"image/png\": data},\n                source=\"yt.visualization.volume_rendering.camera.Camera\",\n            )\n        else:\n            raise YTNotInsideNotebook\n\n    def set_default_light_dir(self):\n        self.light_dir = [1.0, 1.0, 1.0]\n\n    def set_default_light_rgba(self):\n        self.light_rgba = [1.0, 1.0, 1.0, 1.0]\n\n    def zoom(self, factor):\n        r\"\"\"Change the distance to the focal point.\n\n        This will zoom the camera in by some `factor` toward the focal point,\n        along the current view direction, modifying the left/right and up/down\n        extents as well.\n\n        Parameters\n        ----------\n        factor : float\n            The factor by which to reduce the distance to the focal point.\n\n\n        Notes\n        -----\n\n        You will need to call snapshot() again to get a new image.\n\n        \"\"\"\n        self.width /= factor\n        self._setup_box_properties(self.width, self.center, self.orienter.unit_vectors)\n\n    def zoomin(self, final, n_steps, clip_ratio=None):\n        r\"\"\"Loop over a zoomin and return snapshots along the way.\n\n        This will yield `n_steps` snapshots until the current view has been\n        zooming in to a final factor of `final`.\n\n        Parameters\n        ----------\n        final : float\n            The zoom factor, with respect to current, desired at the end of the\n            sequence.\n        n_steps : int\n            The number of zoom snapshots to make.\n        clip_ratio : float, optional\n            If supplied, the 'max_val' argument to write_bitmap will be handed\n            clip_ratio * image.std()\n\n\n        Examples\n        --------\n\n        >>> for i, snapshot in enumerate(cam.zoomin(100.0, 10)):\n        ...     iw.write_bitmap(snapshot, \"zoom_%04i.png\" % i)\n        \"\"\"\n        f = final ** (1.0 / n_steps)\n        for _ in range(n_steps):\n            self.zoom(f)\n            yield self.snapshot(clip_ratio=clip_ratio)\n\n    def move_to(\n        self, final, n_steps, final_width=None, exponential=False, clip_ratio=None\n    ):\n        r\"\"\"Loop over a look_at\n\n        This will yield `n_steps` snapshots until the current view has been\n        moved to a final center of `final` with a final width of final_width.\n\n        Parameters\n        ----------\n        final : array_like\n            The final center to move to after `n_steps`\n        n_steps : int\n            The number of look_at snapshots to make.\n        final_width: float or array_like, optional\n            Specifies the final width after `n_steps`.  Useful for\n            moving and zooming at the same time.\n        exponential : boolean\n            Specifies whether the move/zoom transition follows an\n            exponential path toward the destination or linear\n        clip_ratio : float, optional\n            If supplied, the 'max_val' argument to write_bitmap will be handed\n            clip_ratio * image.std()\n\n        Examples\n        --------\n\n        >>> for i, snapshot in enumerate(cam.move_to([0.2, 0.3, 0.6], 10)):\n        ...     iw.write_bitmap(snapshot, \"move_%04i.png\" % i)\n        \"\"\"\n        dW = None\n        if not isinstance(final, YTArray):\n            final = self.ds.arr(final, units=\"code_length\")\n        if exponential:\n            if final_width is not None:\n                if not is_sequence(final_width):\n                    final_width = [final_width, final_width, final_width]\n                if not isinstance(final_width, YTArray):\n                    final_width = self.ds.arr(final_width, units=\"code_length\")\n                    # left/right, top/bottom, front/back\n                if (self.center == 0.0).all():\n                    self.center += (final - self.center) / (10.0 * n_steps)\n                final_zoom = final_width / self.width\n                dW = final_zoom ** (1.0 / n_steps)\n            else:\n                dW = self.ds.arr([1.0, 1.0, 1.0], \"code_length\")\n            position_diff = final / self.center\n            dx = position_diff ** (1.0 / n_steps)\n        else:\n            if final_width is not None:\n                if not is_sequence(final_width):\n                    final_width = [final_width, final_width, final_width]\n                if not isinstance(final_width, YTArray):\n                    final_width = self.ds.arr(final_width, units=\"code_length\")\n                    # left/right, top/bottom, front/back\n                dW = (1.0 * final_width - self.width) / n_steps\n            else:\n                dW = self.ds.arr([0.0, 0.0, 0.0], \"code_length\")\n            dx = (final - self.center) * 1.0 / n_steps\n        for _ in range(n_steps):\n            if exponential:\n                self.switch_view(center=self.center * dx, width=self.width * dW)\n            else:\n                self.switch_view(center=self.center + dx, width=self.width + dW)\n            yield self.snapshot(clip_ratio=clip_ratio)\n\n    def rotate(self, theta, rot_vector=None):\n        r\"\"\"Rotate by a given angle\n\n        Rotate the view.  If `rot_vector` is None, rotation will occur\n        around the `north_vector`.\n\n        Parameters\n        ----------\n        theta : float, in radians\n             Angle (in radians) by which to rotate the view.\n        rot_vector  : array_like, optional\n            Specify the rotation vector around which rotation will\n            occur.  Defaults to None, which sets rotation around\n            `north_vector`\n\n        Examples\n        --------\n\n        >>> cam.rotate(np.pi / 4)\n        \"\"\"\n        rotate_all = rot_vector is not None\n        if rot_vector is None:\n            rot_vector = self.rotation_vector\n        else:\n            rot_vector = ensure_numpy_array(rot_vector)\n            rot_vector = rot_vector / np.linalg.norm(rot_vector)\n\n        R = get_rotation_matrix(theta, rot_vector)\n\n        normal_vector = self.front_center - self.center\n        normal_vector = normal_vector / np.sqrt((normal_vector**2).sum())\n\n        if rotate_all:\n            self.switch_view(\n                normal_vector=np.dot(R, normal_vector),\n                north_vector=np.dot(R, self.orienter.unit_vectors[1]),\n            )\n        else:\n            self.switch_view(normal_vector=np.dot(R, normal_vector))\n\n    def pitch(self, theta):\n        r\"\"\"Rotate by a given angle about the horizontal axis\n\n        Pitch the view.\n\n        Parameters\n        ----------\n        theta : float, in radians\n             Angle (in radians) by which to pitch the view.\n\n        Examples\n        --------\n\n        >>> cam.pitch(np.pi / 4)\n        \"\"\"\n        rot_vector = self.orienter.unit_vectors[0]\n        R = get_rotation_matrix(theta, rot_vector)\n        self.switch_view(\n            normal_vector=np.dot(R, self.orienter.unit_vectors[2]),\n            north_vector=np.dot(R, self.orienter.unit_vectors[1]),\n        )\n        if self.orienter.steady_north:\n            self.orienter.north_vector = self.orienter.unit_vectors[1]\n\n    def yaw(self, theta):\n        r\"\"\"Rotate by a given angle about the vertical axis\n\n        Yaw the view.\n\n        Parameters\n        ----------\n        theta : float, in radians\n             Angle (in radians) by which to yaw the view.\n\n        Examples\n        --------\n\n        >>> cam.yaw(np.pi / 4)\n        \"\"\"\n        rot_vector = self.orienter.unit_vectors[1]\n        R = get_rotation_matrix(theta, rot_vector)\n        self.switch_view(normal_vector=np.dot(R, self.orienter.unit_vectors[2]))\n\n    def roll(self, theta):\n        r\"\"\"Rotate by a given angle about the view normal axis\n\n        Roll the view.\n\n        Parameters\n        ----------\n        theta : float, in radians\n             Angle (in radians) by which to roll the view.\n\n        Examples\n        --------\n\n        >>> cam.roll(np.pi / 4)\n        \"\"\"\n        rot_vector = self.orienter.unit_vectors[2]\n        R = get_rotation_matrix(theta, rot_vector)\n        self.switch_view(\n            normal_vector=np.dot(R, self.orienter.unit_vectors[2]),\n            north_vector=np.dot(R, self.orienter.unit_vectors[1]),\n        )\n        if self.orienter.steady_north:\n            self.orienter.north_vector = np.dot(R, self.orienter.north_vector)\n\n    def rotation(self, theta, n_steps, rot_vector=None, clip_ratio=None):\n        r\"\"\"Loop over rotate, creating a rotation\n\n        This will yield `n_steps` snapshots until the current view has been\n        rotated by an angle `theta`\n\n        Parameters\n        ----------\n        theta : float, in radians\n            Angle (in radians) by which to rotate the view.\n        n_steps : int\n            The number of look_at snapshots to make.\n        rot_vector  : array_like, optional\n            Specify the rotation vector around which rotation will\n            occur.  Defaults to None, which sets rotation around the\n            original `north_vector`\n        clip_ratio : float, optional\n            If supplied, the 'max_val' argument to write_bitmap will be handed\n            clip_ratio * image.std()\n\n        Examples\n        --------\n\n        >>> for i, snapshot in enumerate(cam.rotation(np.pi, 10)):\n        ...     iw.write_bitmap(snapshot, \"rotation_%04i.png\" % i)\n        \"\"\"\n\n        dtheta = (1.0 * theta) / n_steps\n        for _ in range(n_steps):\n            self.rotate(dtheta, rot_vector=rot_vector)\n            yield self.snapshot(clip_ratio=clip_ratio)\n\n\ndata_object_registry[\"camera\"] = Camera\n\n\nclass InteractiveCamera(Camera):\n    frames: list[ImageArray] = []\n\n    def snapshot(self, fn=None, clip_ratio=None):\n        self._pyplot.figure(2)\n        self.transfer_function.show()\n        self._pyplot.draw()\n        im = Camera.snapshot(self, fn, clip_ratio)\n        self._pyplot.figure(1)\n        self._pyplot.imshow(im / im.max())\n        self._pyplot.draw()\n        self.frames.append(im)\n\n    def rotation(self, theta, n_steps, rot_vector=None):\n        for frame in Camera.rotation(self, theta, n_steps, rot_vector):\n            if frame is not None:\n                self.frames.append(frame)\n\n    def zoomin(self, final, n_steps):\n        for frame in Camera.zoomin(self, final, n_steps):\n            if frame is not None:\n                self.frames.append(frame)\n\n    def clear_frames(self):\n        del self.frames\n        self.frames = []\n\n    def save(self, fn):\n        self._pyplot.savefig(fn, bbox_inches=\"tight\", facecolor=\"black\")\n\n    def save_frames(self, basename, clip_ratio=None):\n        for i, frame in enumerate(self.frames):\n            fn = f\"{basename}_{i:04}.png\"\n            if clip_ratio is not None:\n                write_bitmap(frame, fn, clip_ratio * frame.std())\n            else:\n                write_bitmap(frame, fn)\n\n\ndata_object_registry[\"interactive_camera\"] = InteractiveCamera\n\n\nclass PerspectiveCamera(Camera):\n    r\"\"\"A viewpoint into a volume, for perspective volume rendering.\n\n    The camera represents the eye of an observer, which will be used to\n    generate ray-cast volume renderings of the domain. The rays start from\n    the camera and end on the image plane, which generates a perspective\n    view.\n\n    Note: at the moment, this results in a left-handed coordinate\n    system view\n\n    Parameters\n    ----------\n    center : array_like\n        The location of the camera\n    normal_vector : array_like\n        The vector from the camera position to the center of the image plane\n    width : float or list of floats\n        width[0] and width[1] give the width and height of the image plane, and\n        width[2] gives the depth of the image plane (distance between the camera\n        and the center of the image plane).\n        The view angles thus become:\n        2 * arctan(0.5 * width[0] / width[2]) in horizontal direction\n        2 * arctan(0.5 * width[1] / width[2]) in vertical direction\n    (The following parameters are identical with the definitions in Camera class)\n    resolution : int or list of ints\n        The number of pixels in each direction.\n    transfer_function : `yt.visualization.volume_rendering.TransferFunction`\n        The transfer function used to map values to colors in an image.  If\n        not specified, defaults to a ProjectionTransferFunction.\n    north_vector : array_like, optional\n        The 'up' direction for the plane of rays.  If not specific, calculated\n        automatically.\n    steady_north : bool, optional\n        Boolean to control whether to normalize the north_vector\n        by subtracting off the dot product of it and the normal\n        vector.  Makes it easier to do rotations along a single\n        axis.  If north_vector is specified, is switched to\n        True. Default: False\n    volume : `yt.extensions.volume_rendering.AMRKDTree`, optional\n        The volume to ray cast through.  Can be specified for finer-grained\n        control, but otherwise will be automatically generated.\n    fields : list of fields, optional\n        This is the list of fields we want to volume render; defaults to\n        Density.\n    log_fields : list of bool, optional\n        Whether we should take the log of the fields before supplying them to\n        the volume rendering mechanism.\n    sub_samples : int, optional\n        The number of samples to take inside every cell per ray.\n    ds : ~yt.data_objects.static_output.Dataset\n        For now, this is a require parameter!  But in the future it will become\n        optional.  This is the dataset to volume render.\n    use_kd: bool, optional\n        Specifies whether or not to use a kd-Tree framework for\n        the Homogenized Volume and ray-casting.  Default to True.\n    max_level: int, optional\n        Specifies the maximum level to be rendered.  Also\n        specifies the maximum level used in the kd-Tree\n        construction.  Defaults to None (all levels), and only\n        applies if use_kd=True.\n    no_ghost: bool, optional\n        Optimization option.  If True, homogenized bricks will\n        extrapolate out from grid instead of interpolating from\n        ghost zones that have to first be calculated.  This can\n        lead to large speed improvements, but at a loss of\n        accuracy/smoothness in resulting image.  The effects are\n        less notable when the transfer function is smooth and\n        broad. Default: True\n    data_source: data container, optional\n        Optionally specify an arbitrary data source to the volume rendering.\n        All cells not included in the data source will be ignored during ray\n        casting. By default this will get set to ds.all_data().\n\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        Camera.__init__(self, *args, **kwargs)\n\n    def get_sampler_args(self, image):\n        east_vec = self.orienter.unit_vectors[0].reshape(3, 1)\n        north_vec = self.orienter.unit_vectors[1].reshape(3, 1)\n\n        px = np.linspace(-0.5, 0.5, self.resolution[0])[np.newaxis, :]\n        py = np.linspace(-0.5, 0.5, self.resolution[1])[np.newaxis, :]\n\n        sample_x = self.width[0] * np.array(east_vec * px).transpose()\n        sample_y = self.width[1] * np.array(north_vec * py).transpose()\n\n        vectors = np.zeros(\n            (self.resolution[0], self.resolution[1], 3), dtype=\"float64\", order=\"C\"\n        )\n\n        sample_x = np.repeat(\n            sample_x.reshape(self.resolution[0], 1, 3), self.resolution[1], axis=1\n        )\n        sample_y = np.repeat(\n            sample_y.reshape(1, self.resolution[1], 3), self.resolution[0], axis=0\n        )\n\n        normal_vec = np.empty((*self.resolution, 3), dtype=\"float64\", order=\"C\")\n        normal_vec[:, :, 0] = self.orienter.unit_vectors[2, 0]\n        normal_vec[:, :, 1] = self.orienter.unit_vectors[2, 1]\n        normal_vec[:, :, 2] = self.orienter.unit_vectors[2, 2]\n\n        vectors = sample_x + sample_y + normal_vec * self.width[2]\n\n        positions = np.empty((*self.resolution, 3), dtype=\"float64\", order=\"C\")\n        positions[:, :, 0] = self.center[0]\n        positions[:, :, 1] = self.center[1]\n        positions[:, :, 2] = self.center[2]\n\n        positions = self.ds.arr(positions, units=\"code_length\")\n\n        dummy = np.ones(3, dtype=\"float64\")\n        image = image.reshape(*self.resolution, 4)\n\n        args = (\n            positions,\n            vectors,\n            self.back_center,\n            (0.0, 1.0, 0.0, 1.0),\n            image,\n            dummy,\n            dummy,\n            np.zeros(3, dtype=\"float64\"),\n            \"KDTree\",\n            self.transfer_function,\n            self.sub_samples,\n        )\n        kwargs = {\n            \"lens_type\": \"perspective\",\n        }\n        return args, kwargs\n\n    def _render(self, double_check, num_threads, image, sampler):\n        ncells = sum(b.source_mask.size for b in self.volume.bricks)\n        pbar = get_pbar(\"Ray casting\", ncells)\n        total_cells = 0\n        if double_check:\n            for brick in self.volume.bricks:\n                for data in brick.my_data:\n                    if np.any(np.isnan(data)):\n                        raise RuntimeError\n\n        for brick in self.volume.traverse(self.front_center):\n            sampler(brick, num_threads=num_threads)\n            total_cells += brick.source_mask.size\n            pbar.update(total_cells)\n\n        pbar.finish()\n        return self.finalize_image(sampler.aimage)\n\n    def finalize_image(self, image):\n        view_pos = self.front_center\n        image = self.volume.reduce_tree_images(\n            image.reshape(*self.resolution, 4), view_pos\n        )\n        if not self.transfer_function.grey_opacity:\n            image[:, :, 3] = 1.0\n        return image\n\n    def project_to_plane(self, pos, res=None):\n        if res is None:\n            res = self.resolution\n        sight_vector = pos - self.center\n        pos1 = sight_vector\n        for i in range(0, sight_vector.shape[0]):\n            sight_vector_norm = np.sqrt(np.dot(sight_vector[i], sight_vector[i]))\n            sight_vector[i] = sight_vector[i] / sight_vector_norm\n        sight_vector = self.ds.arr(sight_vector.value, units=\"dimensionless\")\n        sight_center = self.center + self.width[2] * self.orienter.unit_vectors[2]\n\n        for i in range(0, sight_vector.shape[0]):\n            sight_angle_cos = np.dot(sight_vector[i], self.orienter.unit_vectors[2])\n            if np.arccos(sight_angle_cos) < 0.5 * np.pi:\n                sight_length = self.width[2] / sight_angle_cos\n            else:\n                # The corner is on the backwards, then put it outside of the\n                # image It can not be simply removed because it may connect to\n                # other corner within the image, which produces visible domain\n                # boundary line\n                sight_length = np.sqrt(\n                    self.width[0] ** 2 + self.width[1] ** 2\n                ) / np.sqrt(1 - sight_angle_cos**2)\n            pos1[i] = self.center + sight_length * sight_vector[i]\n\n        dx = np.dot(pos1 - sight_center, self.orienter.unit_vectors[0])\n        dy = np.dot(pos1 - sight_center, self.orienter.unit_vectors[1])\n        dz = np.dot(pos1 - sight_center, self.orienter.unit_vectors[2])\n        # Transpose into image coords.\n        px = (res[0] * 0.5 + res[0] / self.width[0] * dx).astype(\"int64\")\n        py = (res[1] * 0.5 + res[1] / self.width[1] * dy).astype(\"int64\")\n        return px, py, dz\n\n    def yaw(self, theta, rot_center):\n        r\"\"\"Rotate by a given angle about the vertical axis through the\n        point center.  This is accomplished by rotating the\n        focal point and then setting the looking vector to point\n        to the center.\n\n        Yaw the view.\n\n        Parameters\n        ----------\n        theta : float, in radians\n             Angle (in radians) by which to yaw the view.\n\n        rot_center : a tuple (x, y, z)\n             The point to rotate about\n\n        Examples\n        --------\n\n        >>> cam.yaw(np.pi / 4, (0.0, 0.0, 0.0))\n        \"\"\"\n\n        rot_vector = self.orienter.unit_vectors[1]\n\n        focal_point = self.center - rot_center\n        R = get_rotation_matrix(theta, rot_vector)\n        focal_point = np.dot(R, focal_point) + rot_center\n\n        normal_vector = rot_center - focal_point\n        normal_vector = normal_vector / np.sqrt((normal_vector**2).sum())\n\n        self.switch_view(normal_vector=normal_vector, center=focal_point)\n\n\ndata_object_registry[\"perspective_camera\"] = PerspectiveCamera\n\n\ndef corners(left_edge, right_edge):\n    return np.array(\n        [\n            [left_edge[:, 0], left_edge[:, 1], left_edge[:, 2]],\n            [right_edge[:, 0], left_edge[:, 1], left_edge[:, 2]],\n            [right_edge[:, 0], right_edge[:, 1], left_edge[:, 2]],\n            [right_edge[:, 0], right_edge[:, 1], right_edge[:, 2]],\n            [left_edge[:, 0], right_edge[:, 1], right_edge[:, 2]],\n            [left_edge[:, 0], left_edge[:, 1], right_edge[:, 2]],\n            [right_edge[:, 0], left_edge[:, 1], right_edge[:, 2]],\n            [left_edge[:, 0], right_edge[:, 1], left_edge[:, 2]],\n        ],\n        dtype=\"float64\",\n    )\n\n\nclass HEALpixCamera(Camera):\n    _sampler_object = None\n\n    def __init__(\n        self,\n        center,\n        radius,\n        nside,\n        transfer_function=None,\n        fields=None,\n        sub_samples=5,\n        log_fields=None,\n        volume=None,\n        ds=None,\n        use_kd=True,\n        no_ghost=False,\n        use_light=False,\n        inner_radius=10,\n    ):\n        mylog.error(\"I am sorry, HEALpix Camera does not work yet in 3.0\")\n        raise NotImplementedError\n\n    def new_image(self):\n        image = np.zeros((12 * self.nside**2, 1, 4), dtype=\"float64\", order=\"C\")\n        return image\n\n    def get_sampler_args(self, image):\n        nv = 12 * self.nside**2\n        vs = arr_pix2vec_nest(self.nside, np.arange(nv)).reshape(nv, 1, 3)\n        vs += 1e-8\n        uv = np.ones(3, dtype=\"float64\")\n        positions = np.ones((nv, 1, 3), dtype=\"float64\") * self.center\n        dx = min(g.dds.min() for g in self.ds.index.find_point(self.center)[0])\n        positions += self.inner_radius * dx * vs\n        vs *= self.radius\n        args = (\n            positions,\n            vs,\n            self.center,\n            (0.0, 1.0, 0.0, 1.0),\n            image,\n            uv,\n            uv,\n            np.zeros(3, dtype=\"float64\"),\n            \"KDTree\",\n        )\n        if self._needs_tf:\n            args += (self.transfer_function,)\n        args += (self.sub_samples,)\n\n        return args, {}\n\n    def _render(self, double_check, num_threads, image, sampler):\n        pbar = get_pbar(\n            \"Ray casting\", (self.volume.brick_dimensions + 1).prod(axis=-1).sum()\n        )\n        total_cells = 0\n        if double_check:\n            for brick in self.volume.bricks:\n                for data in brick.my_data:\n                    if np.any(np.isnan(data)):\n                        raise RuntimeError\n\n        view_pos = self.center\n        for brick in self.volume.traverse(view_pos):\n            sampler(brick, num_threads=num_threads)\n            total_cells += np.prod(brick.my_data[0].shape)\n            pbar.update(total_cells)\n\n        pbar.finish()\n        return self.finalize_image(sampler.aimage)\n\n    def finalize_image(self, image):\n        return self.volume.reduce_tree_images(image, self.center)\n\n    def get_information(self):\n        info_dict = {\n            \"fields\": self.fields,\n            \"type\": self.__class__.__name__,\n            \"center\": self.center,\n            \"radius\": self.radius,\n            \"dataset\": self.ds.directory,\n        }\n        return info_dict\n\n    def snapshot(\n        self,\n        fn=None,\n        clip_ratio=None,\n        double_check=False,\n        num_threads=0,\n        clim=None,\n        label=None,\n    ):\n        r\"\"\"Ray-cast the camera.\n\n        This method instructs the camera to take a snapshot -- i.e., call the ray\n        caster -- based on its current settings.\n\n        Parameters\n        ----------\n        fn : string, optional\n            If supplied, the image will be saved out to this before being\n            returned.  Scaling will be to the maximum value.\n        clip_ratio : float, optional\n            If supplied, the 'max_val' argument to write_bitmap will be handed\n            clip_ratio * image.std()\n\n        Returns\n        -------\n        image : array\n            An (N,M,3) array of the final returned values, in float64 form.\n        \"\"\"\n        if num_threads is None:\n            num_threads = get_num_threads()\n        image = self.new_image()\n        args, kwargs = self.get_sampler_args(image)\n        sampler = self.get_sampler(args, kwargs)\n        self.volume.initialize_source()\n        image = ImageArray(\n            self._render(double_check, num_threads, image, sampler),\n            info=self.get_information(),\n        )\n        self.save_image(image, fn=fn, clim=clim, label=label)\n        return image\n\n    def save_image(self, image, fn=None, clim=None, label=None):\n        if self.comm.rank == 0 and fn is not None:\n            # This assumes Density; this is a relatively safe assumption.\n            if label is None:\n                label = f\"Projected {self.fields[0]}\"\n            if clim is not None:\n                cmin, cmax = clim\n            else:\n                cmin = cmax = None\n            plot_allsky_healpix(\n                image[:, 0, 0], self.nside, fn, label, cmin=cmin, cmax=cmax\n            )\n\n\nclass StereoPairCamera(Camera):\n    def __init__(self, original_camera, relative_separation=0.005):\n        ParallelAnalysisInterface.__init__(self)\n        self.original_camera = original_camera\n        self.relative_separation = relative_separation\n\n    def split(self):\n        oc = self.original_camera\n        uv = oc.orienter.unit_vectors\n        c = oc.center\n        fc = oc.front_center\n        wx, wy, wz = oc.width\n        left_normal = fc + uv[1] * 0.5 * self.relative_separation * wx - c\n        right_normal = fc - uv[1] * 0.5 * self.relative_separation * wx - c\n        left_camera = Camera(\n            c,\n            left_normal,\n            oc.width,\n            oc.resolution,\n            oc.transfer_function,\n            north_vector=uv[0],\n            volume=oc.volume,\n            fields=oc.fields,\n            log_fields=oc.log_fields,\n            sub_samples=oc.sub_samples,\n            ds=oc.ds,\n        )\n        right_camera = Camera(\n            c,\n            right_normal,\n            oc.width,\n            oc.resolution,\n            oc.transfer_function,\n            north_vector=uv[0],\n            volume=oc.volume,\n            fields=oc.fields,\n            log_fields=oc.log_fields,\n            sub_samples=oc.sub_samples,\n            ds=oc.ds,\n        )\n        return (left_camera, right_camera)\n\n\nclass FisheyeCamera(Camera):\n    def __init__(\n        self,\n        center,\n        radius,\n        fov,\n        resolution,\n        transfer_function=None,\n        fields=None,\n        sub_samples=5,\n        log_fields=None,\n        volume=None,\n        ds=None,\n        no_ghost=False,\n        rotation=None,\n        use_light=False,\n    ):\n        ParallelAnalysisInterface.__init__(self)\n        self.use_light = use_light\n        self.light_dir = None\n        self.light_rgba = None\n        if rotation is None:\n            rotation = np.eye(3)\n        self.rotation_matrix = rotation\n        self.no_ghost = no_ghost\n        if ds is not None:\n            self.ds = ds\n        self.center = np.array(center, dtype=\"float64\")\n        self.radius = radius\n        self.fov = fov\n        if is_sequence(resolution):\n            raise RuntimeError(\"Resolution must be a single int\")\n        self.resolution = resolution\n        if transfer_function is None:\n            transfer_function = ProjectionTransferFunction()\n        self.transfer_function = transfer_function\n        if fields is None:\n            fields = [(\"gas\", \"density\")]\n        dd = self.ds.all_data()\n        fields = dd._determine_fields(fields)\n        self.fields = fields\n        if log_fields is None:\n            log_fields = [self.ds._get_field_info(f).take_log for f in fields]\n        self.log_fields = log_fields\n        self.sub_samples = sub_samples\n        if volume is None:\n            volume = AMRKDTree(self.ds)\n            volume.set_fields(fields, log_fields, no_ghost)\n        self.volume = volume\n\n    def get_information(self):\n        return {}\n\n    def new_image(self):\n        image = np.zeros((self.resolution**2, 1, 4), dtype=\"float64\", order=\"C\")\n        return image\n\n    def get_sampler_args(self, image):\n        vp = arr_fisheye_vectors(self.resolution, self.fov).reshape(\n            self.resolution**2, 1, 3\n        )\n        vp2 = vp.copy()\n        for i in range(3):\n            vp[:, :, i] = (vp2 * self.rotation_matrix[:, i]).sum(axis=2)\n        del vp2\n        vp *= self.radius\n        uv = np.ones(3, dtype=\"float64\")\n        positions = np.ones((self.resolution**2, 1, 3), dtype=\"float64\") * self.center\n\n        args = (\n            positions,\n            vp,\n            self.center,\n            (0.0, 1.0, 0.0, 1.0),\n            image,\n            uv,\n            uv,\n            np.zeros(3, dtype=\"float64\"),\n            \"KDTree\",\n            self.transfer_function,\n            self.sub_samples,\n        )\n        return args, {}\n\n    def finalize_image(self, image):\n        return image.reshape(self.resolution, self.resolution, 4)\n\n    def _render(self, double_check, num_threads, image, sampler):\n        pbar = get_pbar(\n            \"Ray casting\", (self.volume.brick_dimensions + 1).prod(axis=-1).sum()\n        )\n        total_cells = 0\n        if double_check:\n            for brick in self.volume.bricks:\n                for data in brick.my_data:\n                    if np.any(np.isnan(data)):\n                        raise RuntimeError\n\n        view_pos = self.center\n        for brick in self.volume.traverse(view_pos):\n            sampler(brick, num_threads=num_threads)\n            total_cells += np.prod(brick.my_data[0].shape)\n            pbar.update(total_cells)\n\n        pbar.finish()\n        return self.finalize_image(sampler.aimage)\n\n\nclass MosaicCamera(Camera):\n    def __init__(\n        self,\n        center,\n        normal_vector,\n        width,\n        resolution,\n        transfer_function=None,\n        north_vector=None,\n        steady_north=False,\n        volume=None,\n        fields=None,\n        log_fields=None,\n        sub_samples=5,\n        ds=None,\n        use_kd=True,\n        l_max=None,\n        no_ghost=True,\n        tree_type=\"domain\",\n        expand_factor=1.0,\n        le=None,\n        re=None,\n        nimx=1,\n        nimy=1,\n        procs_per_wg=None,\n        preload=True,\n        use_light=False,\n    ):\n        ParallelAnalysisInterface.__init__(self)\n\n        self.procs_per_wg = procs_per_wg\n        if ds is not None:\n            self.ds = ds\n        if not is_sequence(resolution):\n            resolution = (int(resolution / nimx), int(resolution / nimy))\n        self.resolution = resolution\n        self.nimx = nimx\n        self.nimy = nimy\n        self.sub_samples = sub_samples\n        if not is_sequence(width):\n            width = (width, width, width)  # front/back, left/right, top/bottom\n        self.width = np.array([width[0], width[1], width[2]])\n        self.center = center\n        self.steady_north = steady_north\n        self.expand_factor = expand_factor\n        # This seems to be necessary for now.  Not sure what goes wrong when not true.\n        if north_vector is not None:\n            self.steady_north = True\n        self.north_vector = north_vector\n        self.normal_vector = normal_vector\n        if fields is None:\n            fields = [(\"gas\", \"density\")]\n        self.fields = fields\n        if transfer_function is None:\n            transfer_function = ProjectionTransferFunction()\n        self.transfer_function = transfer_function\n        self.log_fields = log_fields\n        self.use_kd = use_kd\n        self.l_max = l_max\n        self.no_ghost = no_ghost\n        self.preload = preload\n\n        self.use_light = use_light\n        self.light_dir = None\n        self.light_rgba = None\n        self.le = le\n        self.re = re\n        self.width[0] /= self.nimx\n        self.width[1] /= self.nimy\n\n        self.orienter = Orientation(\n            normal_vector, north_vector=north_vector, steady_north=steady_north\n        )\n        self.rotation_vector = self.orienter.north_vector\n        # self._setup_box_properties(width, center, self.orienter.unit_vectors)\n\n        if self.no_ghost:\n            mylog.warning(\n                \"no_ghost is currently True (default). \"\n                \"This may lead to artifacts at grid boundaries.\"\n            )\n        self.tree_type = tree_type\n        self.volume = volume\n\n        # self.cameras = np.empty(self.nimx*self.nimy)\n\n    def build_volume(\n        self, volume, fields, log_fields, l_max, no_ghost, tree_type, le, re\n    ):\n        if volume is None:\n            if self.use_kd:\n                raise NotImplementedError\n            volume = AMRKDTree(\n                self.ds,\n                l_max=l_max,\n                fields=self.fields,\n                no_ghost=no_ghost,\n                tree_type=tree_type,\n                log_fields=log_fields,\n                le=le,\n                re=re,\n            )\n        else:\n            self.use_kd = isinstance(volume, AMRKDTree)\n        return volume\n\n    def new_image(self):\n        image = np.zeros(\n            (self.resolution[0], self.resolution[1], 4), dtype=\"float64\", order=\"C\"\n        )\n        return image\n\n    def _setup_box_properties(self, width, center, unit_vectors):\n        owidth = deepcopy(width)\n        self.width = width\n        self.origin = (\n            self.center\n            - 0.5 * self.nimx * self.width[0] * self.orienter.unit_vectors[0]\n            - 0.5 * self.nimy * self.width[1] * self.orienter.unit_vectors[1]\n            - 0.5 * self.width[2] * self.orienter.unit_vectors[2]\n        )\n        dx = self.width[0]\n        dy = self.width[1]\n        offi = self.imi + 0.5\n        offj = self.imj + 0.5\n        mylog.info(\"Mosaic offset: %f %f\", offi, offj)\n        global_center = self.center\n        self.center = self.origin\n        self.center += offi * dx * self.orienter.unit_vectors[0]\n        self.center += offj * dy * self.orienter.unit_vectors[1]\n\n        self.box_vectors = np.array(\n            [\n                self.orienter.unit_vectors[0] * dx * self.nimx,\n                self.orienter.unit_vectors[1] * dy * self.nimy,\n                self.orienter.unit_vectors[2] * self.width[2],\n            ]\n        )\n        self.back_center = (\n            self.center - 0.5 * self.width[0] * self.orienter.unit_vectors[2]\n        )\n        self.front_center = (\n            self.center + 0.5 * self.width[0] * self.orienter.unit_vectors[2]\n        )\n        self.center = global_center\n        self.width = owidth\n\n    def snapshot(self, fn=None, clip_ratio=None, double_check=False, num_threads=0):\n        my_storage = {}\n        offx, offy = np.meshgrid(range(self.nimx), range(self.nimy))\n        offxy = zip(offx.ravel(), offy.ravel(), strict=True)\n\n        for sto, xy in parallel_objects(\n            offxy, self.procs_per_wg, storage=my_storage, dynamic=True\n        ):\n            self.volume = self.build_volume(\n                self.volume,\n                self.fields,\n                self.log_fields,\n                self.l_max,\n                self.no_ghost,\n                self.tree_type,\n                self.le,\n                self.re,\n            )\n            self.initialize_source()\n\n            self.imi, self.imj = xy\n            mylog.debug(\"Working on: %i %i\", self.imi, self.imj)\n            self._setup_box_properties(\n                self.width, self.center, self.orienter.unit_vectors\n            )\n            image = self.new_image()\n            args, kwargs = self.get_sampler_args(image)\n            sampler = self.get_sampler(args, kwargs)\n            image = self._render(double_check, num_threads, image, sampler)\n            sto.id = self.imj * self.nimx + self.imi\n            sto.result = image\n        image = self.reduce_images(my_storage)\n        self.save_image(image, fn=fn, clip_ratio=clip_ratio)\n        return image\n\n    def reduce_images(self, im_dict):\n        final_image = 0\n        if self.comm.rank == 0:\n            offx, offy = np.meshgrid(range(self.nimx), range(self.nimy))\n            offxy = zip(offx.ravel(), offy.ravel(), strict=True)\n            nx, ny = self.resolution\n            final_image = np.empty(\n                (nx * self.nimx, ny * self.nimy, 4), dtype=\"float64\", order=\"C\"\n            )\n            for xy in offxy:\n                i, j = xy\n                ind = j * self.nimx + i\n                final_image[i * nx : (i + 1) * nx, j * ny : (j + 1) * ny, :] = im_dict[\n                    ind\n                ]\n        return final_image\n\n\ndata_object_registry[\"mosaic_camera\"] = MosaicCamera\n\n\ndef plot_allsky_healpix(\n    image,\n    nside,\n    fn,\n    label=\"\",\n    rotation=None,\n    take_log=True,\n    resolution=512,\n    cmin=None,\n    cmax=None,\n):\n    import matplotlib.backends.backend_agg\n    import matplotlib.figure\n\n    if rotation is None:\n        rotation = np.eye(3, dtype=\"float64\")\n\n    img, count = pixelize_healpix(nside, image, resolution, resolution, rotation)\n\n    fig = matplotlib.figure.Figure((10, 5))\n    ax = fig.add_subplot(1, 1, 1, projection=\"aitoff\")\n    if take_log:\n        func = np.log10\n    else:\n\n        def _identity(x):\n            return x\n\n        func = _identity\n    implot = ax.imshow(\n        func(img),\n        extent=(-np.pi, np.pi, -np.pi / 2, np.pi / 2),\n        clip_on=False,\n        aspect=0.5,\n        vmin=cmin,\n        vmax=cmax,\n    )\n    cb = fig.colorbar(implot, orientation=\"horizontal\")\n    cb.set_label(label)\n    ax.xaxis.set_ticks(())\n    ax.yaxis.set_ticks(())\n    canvas = matplotlib.backends.backend_agg.FigureCanvasAgg(fig)\n    canvas.print_figure(fn)\n    return img, count\n\n\nclass ProjectionCamera(Camera):\n    def __init__(\n        self,\n        center,\n        normal_vector,\n        width,\n        resolution,\n        field,\n        weight=None,\n        volume=None,\n        no_ghost=False,\n        north_vector=None,\n        ds=None,\n        interpolated=False,\n        method=\"integrate\",\n    ):\n        if not interpolated:\n            volume = 1\n\n        self.interpolated = interpolated\n        self.field = field\n        self.weight = weight\n        self.resolution = resolution\n        self.method = method\n\n        fields = [field]\n        if self.weight is not None:\n            # This is a temporary field, which we will remove at the end\n            # it is given a unique name to avoid conflicting with other\n            # class instances\n            self.weightfield = (\"index\", f\"temp_weightfield_{id(self)}\")\n\n            def _make_wf(f, w):\n                def temp_weightfield(data):\n                    tr = data[f].astype(\"float64\") * data[w]\n                    return data.apply_units(tr, field.units)\n\n                return temp_weightfield\n\n            ds.field_info.add_field(\n                self.weightfield, function=_make_wf(self.field, self.weight)\n            )\n            # Now we have to tell the dataset to add it and to calculate\n            # its dependencies..\n            deps, _ = ds.field_info.check_derived_fields([self.weightfield])\n            ds.field_dependencies.update(deps)\n            fields = [self.weightfield, self.weight]\n\n        self.fields = fields\n        self.log_fields = [False] * len(self.fields)\n        Camera.__init__(\n            self,\n            center,\n            normal_vector,\n            width,\n            resolution,\n            None,\n            fields=fields,\n            ds=ds,\n            volume=volume,\n            log_fields=self.log_fields,\n            north_vector=north_vector,\n            no_ghost=no_ghost,\n        )\n\n    # this would be better in an __exit__ function, but that would require\n    # changes in code that uses this class\n    def __del__(self):\n        if hasattr(self, \"weightfield\") and hasattr(self, \"ds\"):\n            try:\n                self.ds.field_info.pop(self.weightfield)\n                self.ds.field_dependencies.pop(self.weightfield)\n            except KeyError:\n                pass\n        try:\n            Camera.__del__(self)\n        except AttributeError:\n            pass\n\n    def get_sampler(self, args, kwargs):\n        if self.interpolated:\n            sampler = InterpolatedProjectionSampler(*args, **kwargs)\n        else:\n            sampler = ProjectionSampler(*args, **kwargs)\n        return sampler\n\n    def initialize_source(self):\n        if self.interpolated:\n            Camera.initialize_source(self)\n        else:\n            pass\n\n    def get_sampler_args(self, image):\n        rotp = np.concatenate(\n            [self.orienter.inv_mat.ravel(\"F\"), self.back_center.ravel().ndview]\n        )\n        args = (\n            np.atleast_3d(rotp),\n            np.atleast_3d(self.box_vectors[2]),\n            self.back_center,\n            (\n                -self.width[0] / 2.0,\n                self.width[0] / 2.0,\n                -self.width[1] / 2.0,\n                self.width[1] / 2.0,\n            ),\n            image,\n            self.orienter.unit_vectors[0],\n            self.orienter.unit_vectors[1],\n            np.array(self.width, dtype=\"float64\"),\n            \"KDTree\",\n            self.sub_samples,\n        )\n        kwargs = {\"lens_type\": \"plane-parallel\"}\n        return args, kwargs\n\n    def finalize_image(self, image):\n        ds = self.ds\n        dd = ds.all_data()\n        field = dd._determine_fields([self.field])[0]\n        finfo = ds._get_field_info(field)\n        dl = 1.0\n        if self.method == \"integrate\":\n            if self.weight is None:\n                dl = self.width[2].in_units(ds.unit_system[\"length\"])\n            else:\n                image[:, :, 0] /= image[:, :, 1]\n\n        return ImageArray(image[:, :, 0], finfo.units, registry=ds.unit_registry) * dl\n\n    def _render(self, double_check, num_threads, image, sampler):\n        # Calculate the eight corners of the box\n        # Back corners ...\n        if self.interpolated:\n            return Camera._render(self, double_check, num_threads, image, sampler)\n        ds = self.ds\n        width = self.width[2]\n        north_vector = self.orienter.unit_vectors[0]\n        east_vector = self.orienter.unit_vectors[1]\n        normal_vector = self.orienter.unit_vectors[2]\n        fields = self.fields\n\n        mi = ds.domain_right_edge.copy()\n        ma = ds.domain_left_edge.copy()\n        for off1 in [-1, 1]:\n            for off2 in [-1, 1]:\n                for off3 in [-1, 1]:\n                    this_point = (\n                        self.center\n                        + width / 2.0 * off1 * north_vector\n                        + width / 2.0 * off2 * east_vector\n                        + width / 2.0 * off3 * normal_vector\n                    )\n                    np.minimum(mi, this_point, out=mi)\n                    np.maximum(ma, this_point, out=ma)\n        # Now we have a bounding box.\n        data_source = ds.region(self.center, mi, ma)\n\n        for grid, mask in data_source.blocks:\n            data = [(grid[field] * mask).astype(\"float64\") for field in fields]\n            pg = PartitionedGrid(\n                grid.id,\n                data,\n                mask.astype(\"uint8\"),\n                grid.LeftEdge,\n                grid.RightEdge,\n                grid.ActiveDimensions.astype(\"int64\"),\n            )\n            grid.clear_data()\n            sampler(pg, num_threads=num_threads)\n\n        return self.finalize_image(sampler.aimage)\n\n    def save_image(self, image, fn=None, clip_ratio=None):\n        dd = self.ds.all_data()\n        field = dd._determine_fields([self.field])[0]\n        finfo = self.ds._get_field_info(field)\n        if finfo.take_log:\n            im = np.log10(image)\n        else:\n            im = image\n        if self.comm.rank == 0 and fn is not None:\n            if clip_ratio is not None:\n                write_image(im, fn)\n            else:\n                write_image(im, fn)\n\n    def snapshot(self, fn=None, clip_ratio=None, double_check=False, num_threads=0):\n        if num_threads is None:\n            num_threads = get_num_threads()\n\n        image = self.new_image()\n\n        args, kwargs = self.get_sampler_args(image)\n\n        sampler = self.get_sampler(args, kwargs)\n\n        self.initialize_source()\n\n        image = ImageArray(\n            self._render(double_check, num_threads, image, sampler),\n            info=self.get_information(),\n        )\n\n        self.save_image(image, fn=fn, clip_ratio=clip_ratio)\n\n        return image\n\n    snapshot.__doc__ = Camera.snapshot.__doc__\n\n\ndata_object_registry[\"projection_camera\"] = ProjectionCamera\n\n\nclass SphericalCamera(Camera):\n    def __init__(self, *args, **kwargs):\n        Camera.__init__(self, *args, **kwargs)\n        if self.resolution[0] / self.resolution[1] != 2:\n            mylog.info(\"Warning: It's recommended to set the aspect ratio to 2:1\")\n        self.resolution = np.asarray(self.resolution) + 2\n\n    def get_sampler_args(self, image):\n        px = np.linspace(-np.pi, np.pi, self.resolution[0], endpoint=True)[:, None]\n        py = np.linspace(-np.pi / 2.0, np.pi / 2.0, self.resolution[1], endpoint=True)[\n            None, :\n        ]\n\n        vectors = np.empty((*self.resolution, 3), dtype=\"float64\", order=\"C\")\n        vectors[:, :, 0] = np.cos(px) * np.cos(py)\n        vectors[:, :, 1] = np.sin(px) * np.cos(py)\n        vectors[:, :, 2] = np.sin(py)\n\n        vectors = vectors * self.width[0]\n        positions = self.center + vectors * 0\n        R1 = get_rotation_matrix(0.5 * np.pi, [1, 0, 0])\n        R2 = get_rotation_matrix(0.5 * np.pi, [0, 0, 1])\n        uv = np.dot(R1, self.orienter.unit_vectors)\n        uv = np.dot(R2, uv)\n        vectors = np.dot(vectors, uv)\n\n        dummy = np.ones(3, dtype=\"float64\")\n        size = np.prod(self.resolution)\n        image = image.reshape(*size, 1, 4)\n        vectors = vectors.reshape(size, 1, 3)\n        positions = positions.reshape(size, 1, 3)\n        args = (\n            positions,\n            vectors,\n            self.back_center,\n            (0.0, 1.0, 0.0, 1.0),\n            image,\n            dummy,\n            dummy,\n            np.zeros(3, dtype=\"float64\"),\n            self.transfer_function,\n            self.sub_samples,\n        )\n        return args, {\"lens_type\": \"spherical\"}\n\n    def _render(self, double_check, num_threads, image, sampler):\n        ncells = sum(b.source_mask.size for b in self.volume.bricks)\n        pbar = get_pbar(\"Ray casting\", ncells)\n        total_cells = 0\n        if double_check:\n            for brick in self.volume.bricks:\n                for data in brick.my_data:\n                    if np.any(np.isnan(data)):\n                        raise RuntimeError\n\n        for brick in self.volume.traverse(self.front_center):\n            sampler(brick, num_threads=num_threads)\n            total_cells += brick.source_mask.size\n            pbar.update(total_cells)\n\n        pbar.finish()\n        return self.finalize_image(sampler.aimage)\n\n    def finalize_image(self, image):\n        view_pos = self.front_center\n        image = self.volume.reduce_tree_images(\n            image.reshape(*self.resolution, 4), view_pos\n        )\n        if not self.transfer_function.grey_opacity:\n            image[:, :, 3] = 1.0\n        return image[1:-1, 1:-1, :]\n\n\ndata_object_registry[\"spherical_camera\"] = SphericalCamera\n\n\nclass StereoSphericalCamera(Camera):\n    def __init__(self, *args, **kwargs):\n        self.disparity = kwargs.pop(\"disparity\", 0.0)\n        Camera.__init__(self, *args, **kwargs)\n        self.disparity = self.ds.arr(self.disparity, units=\"code_length\")\n        self.disparity_s = self.ds.arr(0.0, units=\"code_length\")\n        if self.resolution[0] / self.resolution[1] != 2:\n            mylog.info(\"Warning: It's recommended to set the aspect ratio to be 2:1\")\n        self.resolution = np.asarray(self.resolution) + 2\n        if self.disparity <= 0.0:\n            self.disparity = self.width[0] / 1000.0\n            mylog.info(\n                \"Warning: Invalid value of disparity; now reset it to %f\",\n                self.disparity,\n            )\n\n    def get_sampler_args(self, image):\n        px = np.linspace(-np.pi, np.pi, self.resolution[0], endpoint=True)[:, None]\n        py = np.linspace(-np.pi / 2.0, np.pi / 2.0, self.resolution[1], endpoint=True)[\n            None, :\n        ]\n\n        vectors = np.empty((*self.resolution, 3), dtype=\"float64\", order=\"C\")\n        vectors[:, :, 0] = np.cos(px) * np.cos(py)\n        vectors[:, :, 1] = np.sin(px) * np.cos(py)\n        vectors[:, :, 2] = np.sin(py)\n        vectors2 = np.empty((*self.resolution, 3), dtype=\"float64\", order=\"C\")\n        vectors2[:, :, 0] = -np.sin(px) * np.ones((1, self.resolution[1]))\n        vectors2[:, :, 1] = np.cos(px) * np.ones((1, self.resolution[1]))\n        vectors2[:, :, 2] = 0\n\n        positions = self.center + vectors2 * self.disparity_s\n        vectors *= self.width[0]\n        R1 = get_rotation_matrix(0.5 * np.pi, [1, 0, 0])\n        R2 = get_rotation_matrix(0.5 * np.pi, [0, 0, 1])\n        uv = np.dot(R1, self.orienter.unit_vectors)\n        uv = np.dot(R2, uv)\n        vectors = np.dot(vectors, uv)\n\n        dummy = np.ones(3, dtype=\"float64\")\n        size = np.prod(self.resolution)\n        image = image.reshape(size, 1, 4)\n        vectors = vectors.reshape(size, 1, 3)\n        positions = positions.reshape(size, 1, 3)\n        args = (\n            positions,\n            vectors,\n            self.back_center,\n            (0.0, 1.0, 0.0, 1.0),\n            image,\n            dummy,\n            dummy,\n            np.zeros(3, dtype=\"float64\"),\n            \"KDTree\",\n            self.transfer_function,\n            self.sub_samples,\n        )\n        kwargs = {\"lens_type\": \"stereo-spherical\"}\n        return args, kwargs\n\n    def snapshot(\n        self,\n        fn=None,\n        clip_ratio=None,\n        double_check=False,\n        num_threads=0,\n        transparent=False,\n    ):\n        if num_threads is None:\n            num_threads = get_num_threads()\n\n        self.disparity_s = self.disparity\n        image1 = self.new_image()\n        args1, kwargs1 = self.get_sampler_args(image1)\n        sampler1 = self.get_sampler(args1, kwargs1)\n        self.initialize_source()\n        image1 = self._render(double_check, num_threads, image1, sampler1, \"(Left) \")\n\n        self.disparity_s = -self.disparity\n        image2 = self.new_image()\n        args2, kwargs2 = self.get_sampler_args(image2)\n        sampler2 = self.get_sampler(args2, kwargs2)\n        self.initialize_source()\n        image2 = self._render(double_check, num_threads, image2, sampler2, \"(Right)\")\n\n        image = np.hstack([image1, image2])\n        image = self.volume.reduce_tree_images(image, self.center)\n        image = ImageArray(image, info=self.get_information())\n        self.save_image(image, fn=fn, clip_ratio=clip_ratio, transparent=transparent)\n        return image\n\n    def _render(self, double_check, num_threads, image, sampler, msg):\n        ncells = sum(b.source_mask.size for b in self.volume.bricks)\n        pbar = get_pbar(\"Ray casting \" + msg, ncells)\n        total_cells = 0\n        if double_check:\n            for brick in self.volume.bricks:\n                for data in brick.my_data:\n                    if np.any(np.isnan(data)):\n                        raise RuntimeError\n\n        for brick in self.volume.traverse(self.front_center):\n            sampler(brick, num_threads=num_threads)\n            total_cells += brick.source_mask.size\n            pbar.update(total_cells)\n\n        pbar.finish()\n\n        image = sampler.aimage.copy().reshape(*self.resolution, 4)\n        if not self.transfer_function.grey_opacity:\n            image[:, :, 3] = 1.0\n        image = image[1:-1, 1:-1, :]\n        return image\n\n\ndata_object_registry[\"stereospherical_camera\"] = StereoSphericalCamera\n\n\n# replaced in volume_rendering API by the function of the same name in\n# yt/visualization/volume_rendering/off_axis_projection\ndef off_axis_projection(\n    ds,\n    center,\n    normal_vector,\n    width,\n    resolution,\n    field,\n    weight=None,\n    volume=None,\n    no_ghost=False,\n    interpolated=False,\n    north_vector=None,\n    method=\"integrate\",\n):\n    r\"\"\"Project through a dataset, off-axis, and return the image plane.\n\n    This function will accept the necessary items to integrate through a volume\n    at an arbitrary angle and return the integrated field of view to the user.\n    Note that if a weight is supplied, it will multiply the pre-interpolated\n    values together, then create cell-centered values, then interpolate within\n    the cell to conduct the integration.\n\n    Parameters\n    ----------\n    ds : ~yt.data_objects.static_output.Dataset\n        This is the dataset to volume render.\n    center : array_like\n        The current 'center' of the view port -- the focal point for the\n        camera.\n    normal_vector : array_like\n        The vector between the camera position and the center.\n    width : float or list of floats\n        The current width of the image.  If a single float, the volume is\n        cubical, but if not, it is left/right, top/bottom, front/back\n    resolution : int or list of ints\n        The number of pixels in each direction.\n    field : string\n        The field to project through the volume\n    weight : optional, default None\n        If supplied, the field will be pre-multiplied by this, then divided by\n        the integrated value of this field.  This returns an average rather\n        than a sum.\n    volume : `yt.extensions.volume_rendering.AMRKDTree`, optional\n        The volume to ray cast through.  Can be specified for finer-grained\n        control, but otherwise will be automatically generated.\n    no_ghost: bool, optional\n        Optimization option.  If True, homogenized bricks will\n        extrapolate out from grid instead of interpolating from\n        ghost zones that have to first be calculated.  This can\n        lead to large speed improvements, but at a loss of\n        accuracy/smoothness in resulting image.  The effects are\n        less notable when the transfer function is smooth and\n        broad. Default: True\n    interpolated : optional, default False\n        If True, the data is first interpolated to vertex-centered data,\n        then tri-linearly interpolated along the ray. Not suggested for\n        quantitative studies.\n    method : string\n         The method of projection.  Valid methods are:\n\n         \"integrate\" with no weight_field specified : integrate the requested\n         field along the line of sight.\n\n         \"integrate\" with a weight_field specified : weight the requested\n         field by the weighting field and integrate along the line of sight.\n\n         \"sum\" : This method is the same as integrate, except that it does not\n         multiply by a path length when performing the integration, and is\n         just a straight summation of the field along the given axis. WARNING:\n         This should only be used for uniform resolution grid datasets, as other\n         datasets may result in unphysical images.\n\n    Returns\n    -------\n    image : array\n        An (N,N) array of the final integrated values, in float64 form.\n\n    Examples\n    --------\n\n    >>> image = off_axis_projection(\n    ...     ds, [0.5, 0.5, 0.5], [0.2, 0.3, 0.4], 0.2, N, \"temperature\", \"density\"\n    ... )\n    >>> write_image(np.log10(image), \"offaxis.png\")\n\n    \"\"\"\n    projcam = ProjectionCamera(\n        center,\n        normal_vector,\n        width,\n        resolution,\n        field,\n        weight=weight,\n        ds=ds,\n        volume=volume,\n        no_ghost=no_ghost,\n        interpolated=interpolated,\n        north_vector=north_vector,\n        method=method,\n    )\n    image = projcam.snapshot()\n    return image[:, :]\n"
  },
  {
    "path": "yt/visualization/volume_rendering/render_source.py",
    "content": "import abc\nimport warnings\nfrom functools import wraps\nfrom types import ModuleType\nfrom typing import Literal\n\nimport numpy as np\n\nfrom yt.config import ytcfg\nfrom yt.data_objects.image_array import ImageArray\nfrom yt.funcs import ensure_numpy_array, is_sequence, mylog\nfrom yt.geometry.grid_geometry_handler import GridIndex\nfrom yt.geometry.oct_geometry_handler import OctreeIndex\nfrom yt.utilities.amr_kdtree.api import AMRKDTree\nfrom yt.utilities.configure import YTConfig, configuration_callbacks\nfrom yt.utilities.lib.bounding_volume_hierarchy import BVH\nfrom yt.utilities.lib.misc_utilities import zlines, zpoints\nfrom yt.utilities.lib.octree_raytracing import OctreeRayTracing\nfrom yt.utilities.lib.partitioned_grid import PartitionedGrid\nfrom yt.utilities.on_demand_imports import NotAModule\nfrom yt.utilities.parallel_tools.parallel_analysis_interface import (\n    ParallelAnalysisInterface,\n)\nfrom yt.visualization.image_writer import apply_colormap\n\nfrom .transfer_function_helper import TransferFunctionHelper\nfrom .transfer_functions import (\n    ColorTransferFunction,\n    ProjectionTransferFunction,\n    TransferFunction,\n)\nfrom .utils import (\n    data_source_or_all,\n    get_corners,\n    new_interpolated_projection_sampler,\n    new_mesh_sampler,\n    new_projection_sampler,\n    new_volume_render_sampler,\n)\nfrom .zbuffer_array import ZBuffer\n\nOptionalModule = ModuleType | NotAModule\nmesh_traversal: OptionalModule = NotAModule(\"pyembree\")\nmesh_construction: OptionalModule = NotAModule(\"pyembree\")\n\n\ndef set_raytracing_engine(\n    engine: Literal[\"yt\", \"embree\"],\n) -> None:\n    \"\"\"\n    Safely switch raytracing engines at runtime.\n\n    Parameters\n    ----------\n\n    engine: 'yt' or 'embree'\n      - 'yt' selects the default engine.\n      - 'embree' requires extra installation steps, see\n        https://yt-project.org/doc/visualizing/unstructured_mesh_rendering.html?highlight=pyembree#optional-embree-installation\n\n    Raises\n    ------\n\n    UserWarning\n      Raised if the required engine is not available.\n      In this case, the default engine is restored.\n\n    \"\"\"\n    from yt.config import ytcfg\n\n    global mesh_traversal, mesh_construction\n\n    if engine == \"embree\":\n        try:\n            from yt.utilities.lib.embree_mesh import (  # type: ignore\n                mesh_construction,\n                mesh_traversal,\n            )\n        except (ImportError, ValueError) as exc:\n            # Catch ValueError in case size of objects in Cython change\n            warnings.warn(\n                \"Failed to switch to embree raytracing engine. \"\n                f\"The following error was raised:\\n{exc}\",\n                stacklevel=2,\n            )\n            mesh_traversal = NotAModule(\"pyembree\")\n            mesh_construction = NotAModule(\"pyembree\")\n            ytcfg[\"yt\", \"ray_tracing_engine\"] = \"yt\"\n        else:\n            ytcfg[\"yt\", \"ray_tracing_engine\"] = \"embree\"\n    else:\n        mesh_traversal = NotAModule(\"pyembree\")\n        mesh_construction = NotAModule(\"pyembree\")\n        ytcfg[\"yt\", \"ray_tracing_engine\"] = \"yt\"\n\n\ndef _init_raytracing_engine(ytcfg: YTConfig) -> None:\n    # validate option from configuration file or fall back to default engine\n    set_raytracing_engine(engine=ytcfg[\"yt\", \"ray_tracing_engine\"])\n\n\nconfiguration_callbacks.append(_init_raytracing_engine)\n\n\ndef invalidate_volume(f):\n    @wraps(f)\n    def wrapper(*args, **kwargs):\n        ret = f(*args, **kwargs)\n        obj = args[0]\n        if isinstance(obj._transfer_function, ProjectionTransferFunction):\n            obj.sampler_type = \"projection\"\n            obj._log_field = False\n            obj._use_ghost_zones = False\n        del obj.volume\n        obj._volume_valid = False\n        return ret\n\n    return wrapper\n\n\ndef validate_volume(f):\n    @wraps(f)\n    def wrapper(*args, **kwargs):\n        obj = args[0]\n        fields = [obj.field]\n        log_fields = [obj.log_field]\n        if obj.weight_field is not None:\n            fields.append(obj.weight_field)\n            log_fields.append(obj.log_field)\n        if not obj._volume_valid:\n            obj.volume.set_fields(\n                fields, log_fields, no_ghost=(not obj.use_ghost_zones)\n            )\n        obj._volume_valid = True\n        return f(*args, **kwargs)\n\n    return wrapper\n\n\nclass RenderSource(ParallelAnalysisInterface, abc.ABC):\n    \"\"\"Base Class for Render Sources.\n\n    Will be inherited for volumes, streamlines, etc.\n\n    \"\"\"\n\n    volume_method: str | None = None\n\n    def __init__(self):\n        super().__init__()\n        self.opaque = False\n        self.zbuffer = None\n\n    @abc.abstractmethod\n    def render(self, camera, zbuffer=None):\n        pass\n\n    @abc.abstractmethod\n    def _validate(self):\n        pass\n\n\nclass OpaqueSource(RenderSource):\n    \"\"\"A base class for opaque render sources.\n\n    Will be inherited from for LineSources, BoxSources, etc.\n\n    \"\"\"\n\n    def __init__(self):\n        super().__init__()\n        self.opaque = True\n\n    def set_zbuffer(self, zbuffer):\n        self.zbuffer = zbuffer\n\n\ndef create_volume_source(data_source, field):\n    data_source = data_source_or_all(data_source)\n    ds = data_source.ds\n    index_class = ds.index.__class__\n    if issubclass(index_class, GridIndex):\n        return KDTreeVolumeSource(data_source, field)\n    elif issubclass(index_class, OctreeIndex):\n        return OctreeVolumeSource(data_source, field)\n    else:\n        raise NotImplementedError\n\n\nclass VolumeSource(RenderSource, abc.ABC):\n    \"\"\"A class for rendering data from a volumetric data source\n\n    Examples of such sources include a sphere, cylinder, or the\n    entire computational domain.\n\n    A :class:`VolumeSource` provides the framework to decompose an arbitrary\n    yt data source into bricks that can be traversed and volume rendered.\n\n    Parameters\n    ----------\n    data_source: :class:`AMR3DData` or :class:`Dataset`, optional\n        This is the source to be rendered, which can be any arbitrary yt\n        data object or dataset.\n    field : string\n        The name of the field to be rendered.\n\n    Examples\n    --------\n\n    The easiest way to make a VolumeSource is to use the volume_render\n    function, so that the VolumeSource gets created automatically. This\n    example shows how to do this and then access the resulting source:\n\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> im, sc = yt.volume_render(ds)\n    >>> volume_source = sc.get_source(0)\n\n    You can also create VolumeSource instances by hand and add them to Scenes.\n    This example manually creates a VolumeSource, adds it to a scene, sets the\n    camera, and renders an image.\n\n    >>> import yt\n    >>> from yt.visualization.volume_rendering.api import (\n    ...     Camera, Scene, create_volume_source)\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> sc = Scene()\n    >>> source = create_volume_source(ds.all_data(), \"density\")\n    >>> sc.add_source(source)\n    >>> sc.add_camera()\n    >>> im = sc.render()\n\n    \"\"\"\n\n    _image = None\n    data_source = None\n\n    def __init__(self, data_source, field):\n        r\"\"\"Initialize a new volumetric source for rendering.\"\"\"\n        super().__init__()\n        self.data_source = data_source_or_all(data_source)\n        field = self.data_source._determine_fields(field)[0]\n        self.current_image = None\n        self.check_nans = False\n        self.num_threads = 0\n        self.num_samples = 10\n        self.sampler_type = \"volume-render\"\n\n        self._volume_valid = False\n\n        # these are caches for properties, defined below\n        self._volume = None\n        self._transfer_function = None\n        self._field = field\n        self._log_field = self.data_source.ds.field_info[field].take_log\n        self._use_ghost_zones = False\n        self._weight_field = None\n\n        self.tfh = TransferFunctionHelper(self.data_source.pf)\n        self.tfh.set_field(self.field)\n\n    @property\n    def transfer_function(self):\n        \"\"\"The transfer function associated with this VolumeSource\"\"\"\n        if self._transfer_function is not None:\n            return self._transfer_function\n\n        if self.tfh.tf is not None:\n            self._transfer_function = self.tfh.tf\n            return self._transfer_function\n\n        mylog.info(\"Creating transfer function\")\n        self.tfh.set_field(self.field)\n        self.tfh.set_log(self.log_field)\n        self.tfh.build_transfer_function()\n        self.tfh.setup_default()\n        self._transfer_function = self.tfh.tf\n\n        return self._transfer_function\n\n    @transfer_function.setter\n    def transfer_function(self, value):\n        self.tfh.tf = None\n        valid_types = (\n            TransferFunction,\n            ColorTransferFunction,\n            ProjectionTransferFunction,\n            type(None),\n        )\n        if not isinstance(value, valid_types):\n            raise RuntimeError(\n                \"transfer_function not a valid type, \"\n                f\"received object of type {type(value)}\"\n            )\n        if isinstance(value, ProjectionTransferFunction):\n            self.sampler_type = \"projection\"\n            if self._volume is not None:\n                fields = [self.field]\n                if self.weight_field is not None:\n                    fields.append(self.weight_field)\n                self._volume_valid = False\n        self._transfer_function = value\n\n    @property\n    def volume(self):\n        \"\"\"The abstract volume associated with this VolumeSource\n\n        This object does the heavy lifting to access data in an efficient manner\n        using a KDTree\n        \"\"\"\n        return self._get_volume()\n\n    @volume.setter\n    def volume(self, value):\n        assert isinstance(value, AMRKDTree)\n        del self._volume\n        self._field = value.fields\n        self._log_field = value.log_fields\n        self._volume = value\n        assert self._volume_valid\n\n    @volume.deleter\n    def volume(self):\n        del self._volume\n        self._volume = None\n\n    @property\n    def field(self):\n        \"\"\"The field to be rendered\"\"\"\n        return self._field\n\n    @field.setter\n    @invalidate_volume\n    def field(self, value):\n        field = self.data_source._determine_fields(value)\n        if len(field) > 1:\n            raise RuntimeError(\n                \"VolumeSource.field can only be a single field but received \"\n                f\"multiple fields: {field}\"\n            )\n        field = field[0]\n        if self._field != field:\n            log_field = self.data_source.ds.field_info[field].take_log\n            self.tfh.bounds = None\n        else:\n            log_field = self._log_field\n        self._log_field = log_field\n        self._field = value\n        self.transfer_function = None\n        self.tfh.set_field(value)\n        self.tfh.set_log(log_field)\n\n    @property\n    def log_field(self):\n        \"\"\"Whether or not the field rendering is computed in log space\"\"\"\n        return self._log_field\n\n    @log_field.setter\n    @invalidate_volume\n    def log_field(self, value):\n        self.transfer_function = None\n        self.tfh.set_log(value)\n        self._log_field = value\n\n    @property\n    def use_ghost_zones(self):\n        \"\"\"Whether or not ghost zones are used to estimate vertex-centered data\n        values at grid boundaries\"\"\"\n        return self._use_ghost_zones\n\n    @use_ghost_zones.setter\n    @invalidate_volume\n    def use_ghost_zones(self, value):\n        self._use_ghost_zones = value\n\n    @property\n    def weight_field(self):\n        \"\"\"The weight field for the rendering\n\n        Currently this is only used for off-axis projections.\n        \"\"\"\n        return self._weight_field\n\n    @weight_field.setter\n    @invalidate_volume\n    def weight_field(self, value):\n        self._weight_field = value\n\n    def set_transfer_function(self, transfer_function):\n        \"\"\"Set transfer function for this source\"\"\"\n        self.transfer_function = transfer_function\n        return self\n\n    def _validate(self):\n        \"\"\"Make sure that all dependencies have been met\"\"\"\n        if self.data_source is None:\n            raise RuntimeError(\"Data source not initialized\")\n\n    def set_volume(self, volume):\n        \"\"\"Associates an AMRKDTree with the VolumeSource\"\"\"\n        self.volume = volume\n        return self\n\n    def set_field(self, field):\n        \"\"\"Set the source's field to render\n\n        Parameters\n        ----------\n\n        field: field name\n            The field to render\n        \"\"\"\n        self.field = field\n        return self\n\n    def set_log(self, log_field):\n        \"\"\"Set whether the rendering of the source's field is done in log space\n\n        Generally volume renderings of data whose values span a large dynamic\n        range should be done on log space and volume renderings of data with\n        small dynamic range should be done in linear space.\n\n        Parameters\n        ----------\n\n        log_field: boolean\n            If True, the volume rendering will be done in log space, and if False\n            will be done in linear space.\n        \"\"\"\n        self.log_field = log_field\n        return self\n\n    def set_weight_field(self, weight_field):\n        \"\"\"Set the source's weight field\n\n        .. note::\n\n          This is currently only used for renderings using the\n          ProjectionTransferFunction\n\n        Parameters\n        ----------\n\n        weight_field: field name\n            The weight field to use in the rendering\n        \"\"\"\n        self.weight_field = weight_field\n        return self\n\n    def set_use_ghost_zones(self, use_ghost_zones):\n        \"\"\"Set whether or not interpolation at grid edges uses ghost zones\n\n        Parameters\n        ----------\n\n        use_ghost_zones: boolean\n            If True, the AMRKDTree estimates vertex centered data using ghost\n            zones, which can eliminate seams in the resulting volume rendering.\n            Defaults to False for performance reasons.\n\n        \"\"\"\n        self.use_ghost_zones = use_ghost_zones\n        return self\n\n    def set_sampler(self, camera, interpolated=True):\n        \"\"\"Sets a volume render sampler\n\n        The type of sampler is determined based on the ``sampler_type`` attribute\n        of the VolumeSource. Currently the ``volume_render`` and ``projection``\n        sampler types are supported.\n\n        The 'interpolated' argument is only meaningful for projections. If True,\n        the data is first interpolated to the cell vertices, and then\n        tri-linearly interpolated to the ray sampling positions. If False, then\n        the cell-centered data is simply accumulated along the\n        ray. Interpolation is always performed for volume renderings.\n\n        \"\"\"\n        if self.sampler_type == \"volume-render\":\n            sampler = new_volume_render_sampler(camera, self)\n        elif self.sampler_type == \"projection\" and interpolated:\n            sampler = new_interpolated_projection_sampler(camera, self)\n        elif self.sampler_type == \"projection\":\n            sampler = new_projection_sampler(camera, self)\n        else:\n            NotImplementedError(f\"{self.sampler_type} not implemented yet\")\n        self.sampler = sampler\n        assert self.sampler is not None\n\n    @abc.abstractmethod\n    def _get_volume(self):\n        \"\"\"The abstract volume associated with this VolumeSource\n\n        This object does the heavy lifting to access data in an efficient manner\n        using a KDTree\n        \"\"\"\n        pass\n\n    @abc.abstractmethod\n    @validate_volume\n    def render(self, camera, zbuffer=None):\n        \"\"\"Renders an image using the provided camera\n\n        Parameters\n        ----------\n        camera: :class:`yt.visualization.volume_rendering.camera.Camera` instance\n            A volume rendering camera. Can be any type of camera.\n        zbuffer: :class:`yt.visualization.volume_rendering.zbuffer_array.Zbuffer` instance  # noqa: E501\n            A zbuffer array. This is used for opaque sources to determine the\n            z position of the source relative to other sources. Only useful if\n            you are manually calling render on multiple sources. Scene.render\n            uses this internally.\n\n        Returns\n        -------\n        A :class:`yt.data_objects.image_array.ImageArray` instance containing\n        the rendered image.\n\n        \"\"\"\n        pass\n\n    def finalize_image(self, camera, image):\n        \"\"\"Parallel reduce the image.\n\n        Parameters\n        ----------\n        camera: :class:`yt.visualization.volume_rendering.camera.Camera` instance\n            The camera used to produce the volume rendering image.\n        image: :class:`yt.data_objects.image_array.ImageArray` instance\n            A reference to an image to fill\n        \"\"\"\n        image = image.reshape(*camera.resolution, 4)\n        # If the call is from VR, the image is rotated by 180 to get correct\n        # up direction\n        if not self.transfer_function.grey_opacity:\n            image[:, :, 3] = 1\n        return image\n\n    def __repr__(self):\n        disp = f\"<Volume Source>:{str(self.data_source)} \"\n        disp += f\"transfer_function:{str(self._transfer_function)}\"\n        return disp\n\n\nclass KDTreeVolumeSource(VolumeSource):\n    volume_method = \"KDTree\"\n\n    def _get_volume(self):\n        \"\"\"The abstract volume associated with this VolumeSource\n\n        This object does the heavy lifting to access data in an efficient manner\n        using a KDTree\n        \"\"\"\n\n        if self._volume is None:\n            mylog.info(\"Creating volume\")\n            volume = AMRKDTree(self.data_source.ds, data_source=self.data_source)\n            self._volume = volume\n\n        return self._volume\n\n    @validate_volume\n    def render(self, camera, zbuffer=None):\n        \"\"\"Renders an image using the provided camera\n\n        Parameters\n        ----------\n        camera: :class:`yt.visualization.volume_rendering.camera.Camera`\n            A volume rendering camera. Can be any type of camera.\n        zbuffer: :class:`yt.visualization.volume_rendering.zbuffer_array.Zbuffer`\n            A zbuffer array. This is used for opaque sources to determine the\n            z position of the source relative to other sources. Only useful if\n            you are manually calling render on multiple sources. Scene.render\n            uses this internally.\n\n        Returns\n        -------\n        A :class:`yt.data_objects.image_array.ImageArray` containing\n        the rendered image.\n\n        \"\"\"\n        self.zbuffer = zbuffer\n        self.set_sampler(camera)\n        assert self.sampler is not None\n\n        mylog.debug(\"Casting rays\")\n        total_cells = 0\n        if self.check_nans:\n            for brick in self.volume.bricks:\n                for data in brick.my_data:\n                    if np.any(np.isnan(data)):\n                        raise RuntimeError\n\n        for brick in self.volume.traverse(camera.lens.viewpoint):\n            mylog.debug(\"Using sampler %s\", self.sampler)\n            self.sampler(brick, num_threads=self.num_threads)\n            total_cells += np.prod(brick.my_data[0].shape)\n        mylog.debug(\"Done casting rays\")\n        self.current_image = self.finalize_image(camera, self.sampler.aimage)\n\n        if zbuffer is None:\n            self.zbuffer = ZBuffer(\n                self.current_image, np.full(self.current_image.shape[:2], np.inf)\n            )\n\n        return self.current_image\n\n    def finalize_image(self, camera, image):\n        if self._volume is not None:\n            image = self.volume.reduce_tree_images(\n                image,\n                camera.lens.viewpoint,\n                use_opacity=self.transfer_function.grey_opacity,\n            )\n\n        return super().finalize_image(camera, image)\n\n\nclass OctreeVolumeSource(VolumeSource):\n    volume_method = \"Octree\"\n\n    def __init__(self, *args, **kwa):\n        super().__init__(*args, **kwa)\n        self.set_use_ghost_zones(True)\n\n    def _get_volume(self):\n        \"\"\"The abstract volume associated with this VolumeSource\n\n        This object does the heavy lifting to access data in an efficient manner\n        using an octree.\n        \"\"\"\n\n        if self._volume is None:\n            mylog.info(\"Creating volume\")\n            volume = OctreeRayTracing(self.data_source)\n            self._volume = volume\n\n        return self._volume\n\n    @validate_volume\n    def render(self, camera, zbuffer=None):\n        \"\"\"Renders an image using the provided camera\n\n        Parameters\n        ----------\n        camera: :class:`yt.visualization.volume_rendering.camera.Camera` instance\n            A volume rendering camera. Can be any type of camera.\n        zbuffer: :class:`yt.visualization.volume_rendering.zbuffer_array.Zbuffer` instance  # noqa: E501\n            A zbuffer array. This is used for opaque sources to determine the\n            z position of the source relative to other sources. Only useful if\n            you are manually calling render on multiple sources. Scene.render\n            uses this internally.\n\n        Returns\n        -------\n        A :class:`yt.data_objects.image_array.ImageArray` instance containing\n        the rendered image.\n\n        \"\"\"\n        self.zbuffer = zbuffer\n        self.set_sampler(camera)\n        if self.sampler is None:\n            raise RuntimeError(\n                \"No sampler set. This is likely a bug as it should never happen.\"\n            )\n\n        data = self.data_source\n\n        dx = data[\"dx\"].to_value(\"unitary\")[:, None]\n        xyz = np.stack([data[_].to_value(\"unitary\") for _ in \"xyz\"], axis=-1)\n        LE = xyz - dx / 2\n        RE = xyz + dx / 2\n\n        mylog.debug(\"Gathering data\")\n        dt = np.stack(list(self.volume.data) + [*LE.T, *RE.T], axis=-1).reshape(\n            1, len(dx), 14, 1\n        )\n        mask = np.full(dt.shape[1:], 1, dtype=np.uint8)\n        dims = np.array([1, 1, 1], dtype=\"int64\")\n        pg = PartitionedGrid(0, dt, mask, LE.flatten(), RE.flatten(), dims, n_fields=1)\n\n        mylog.debug(\"Casting rays\")\n        self.sampler(pg, oct=self.volume.octree)\n        mylog.debug(\"Done casting rays\")\n\n        self.current_image = self.finalize_image(camera, self.sampler.aimage)\n\n        if zbuffer is None:\n            self.zbuffer = ZBuffer(\n                self.current_image, np.full(self.current_image.shape[:2], np.inf)\n            )\n\n        return self.current_image\n\n\nclass MeshSource(OpaqueSource):\n    \"\"\"A source for unstructured mesh data.\n\n    This functionality requires the embree ray-tracing engine and the\n    associated pyembree python bindings to be installed in order to\n    function.\n\n    A :class:`MeshSource` provides the framework to volume render\n    unstructured mesh data.\n\n    Parameters\n    ----------\n    data_source: :class:`AMR3DData` or :class:`Dataset`, optional\n        This is the source to be rendered, which can be any arbitrary yt\n        data object or dataset.\n    field : string\n        The name of the field to be rendered.\n\n    Examples\n    --------\n    >>> source = MeshSource(ds, (\"connect1\", \"convected\"))\n    \"\"\"\n\n    _image = None\n    data_source = None\n\n    def __init__(self, data_source, field):\n        r\"\"\"Initialize a new unstructured mesh source for rendering.\"\"\"\n        super().__init__()\n        self.data_source = data_source_or_all(data_source)\n        field = self.data_source._determine_fields(field)[0]\n        self.field = field\n        self.volume = None\n        self.current_image = None\n        self.engine = ytcfg.get(\"yt\", \"ray_tracing_engine\")\n\n        # default color map\n        self._cmap = ytcfg.get(\"yt\", \"default_colormap\")\n        self._color_bounds = None\n\n        # default mesh annotation options\n        self._annotate_mesh = False\n        self._mesh_line_color = None\n        self._mesh_line_alpha = 1.0\n\n        # Error checking\n        assert self.field is not None\n        assert self.data_source is not None\n        if self.field[0] == \"all\":\n            raise NotImplementedError(\n                \"Mesh unions are not implemented for 3D rendering\"\n            )\n\n        if self.engine == \"embree\":\n            self.volume = mesh_traversal.YTEmbreeScene()\n            self.build_volume_embree()\n        elif self.engine == \"yt\":\n            self.build_volume_bvh()\n        else:\n            raise NotImplementedError(\n                \"Invalid ray-tracing engine selected. Choices are 'embree' and 'yt'.\"\n            )\n\n    @property\n    def cmap(self):\n        \"\"\"\n        This is the name of the colormap that will be used when rendering\n        this MeshSource object. Should be a string, like 'cmyt.arbre', or 'cmyt.dusk'.\n\n        \"\"\"\n        return self._cmap\n\n    @cmap.setter\n    def cmap(self, cmap_name):\n        self._cmap = cmap_name\n        if hasattr(self, \"data\"):\n            self.current_image = self.apply_colormap()\n\n    @property\n    def color_bounds(self):\n        \"\"\"\n        These are the bounds that will be used with the colormap to the display\n        the rendered image. Should be a (vmin, vmax) tuple, like (0.0, 2.0). If\n        None, the bounds will be automatically inferred from the max and min of\n        the rendered data.\n\n        \"\"\"\n        return self._color_bounds\n\n    @color_bounds.setter\n    def color_bounds(self, bounds):\n        self._color_bounds = bounds\n        if hasattr(self, \"data\"):\n            self.current_image = self.apply_colormap()\n\n    def _validate(self):\n        \"\"\"Make sure that all dependencies have been met\"\"\"\n        if self.data_source is None:\n            raise RuntimeError(\"Data source not initialized.\")\n\n        if self.volume is None:\n            raise RuntimeError(\"Volume not initialized.\")\n\n    def build_volume_embree(self):\n        \"\"\"\n\n        This constructs the mesh that will be ray-traced by pyembree.\n\n        \"\"\"\n        ftype, fname = self.field\n        mesh_id = int(ftype[-1]) - 1\n        index = self.data_source.ds.index\n        offset = index.meshes[mesh_id]._index_offset\n        field_data = self.data_source[self.field].d  # strip units\n\n        vertices = index.meshes[mesh_id].connectivity_coords\n        indices = index.meshes[mesh_id].connectivity_indices - offset\n\n        # if this is an element field, promote to 2D here\n        if len(field_data.shape) == 1:\n            field_data = np.expand_dims(field_data, 1)\n\n        # Here, we decide whether to render based on high-order or\n        # low-order geometry. Right now, high-order geometry is only\n        # implemented for 20-point hexes.\n        if indices.shape[1] == 20 or indices.shape[1] == 10:\n            self.mesh = mesh_construction.QuadraticElementMesh(\n                self.volume, vertices, indices, field_data\n            )\n        else:\n            # if this is another type of higher-order element, we demote\n            # to 1st order here, for now.\n            if indices.shape[1] == 27:\n                # hexahedral\n                mylog.warning(\"27-node hexes not yet supported, dropping to 1st order.\")\n                field_data = field_data[:, 0:8]\n                indices = indices[:, 0:8]\n\n            self.mesh = mesh_construction.LinearElementMesh(\n                self.volume, vertices, indices, field_data\n            )\n\n    def build_volume_bvh(self):\n        \"\"\"\n\n        This constructs the mesh that will be ray-traced.\n\n        \"\"\"\n        ftype, fname = self.field\n        mesh_id = int(ftype[-1]) - 1\n        index = self.data_source.ds.index\n        offset = index.meshes[mesh_id]._index_offset\n        field_data = self.data_source[self.field].d  # strip units\n\n        vertices = index.meshes[mesh_id].connectivity_coords\n        indices = index.meshes[mesh_id].connectivity_indices - offset\n\n        # if this is an element field, promote to 2D here\n        if len(field_data.shape) == 1:\n            field_data = np.expand_dims(field_data, 1)\n\n        # Here, we decide whether to render based on high-order or\n        # low-order geometry.\n        if indices.shape[1] == 27:\n            # hexahedral\n            mylog.warning(\"27-node hexes not yet supported, dropping to 1st order.\")\n            field_data = field_data[:, 0:8]\n            indices = indices[:, 0:8]\n\n        self.volume = BVH(vertices, indices, field_data)\n\n    def render(self, camera, zbuffer=None):\n        \"\"\"Renders an image using the provided camera\n\n        Parameters\n        ----------\n        camera: :class:`yt.visualization.volume_rendering.camera.Camera`\n            A volume rendering camera. Can be any type of camera.\n        zbuffer: :class:`yt.visualization.volume_rendering.zbuffer_array.Zbuffer`\n            A zbuffer array. This is used for opaque sources to determine the\n            z position of the source relative to other sources. Only useful if\n            you are manually calling render on multiple sources. Scene.render\n            uses this internally.\n\n        Returns\n        -------\n        A :class:`yt.data_objects.image_array.ImageArray` containing\n        the rendered image.\n\n        \"\"\"\n\n        shape = (camera.resolution[0], camera.resolution[1], 4)\n        if zbuffer is None:\n            empty = np.empty(shape, dtype=\"float64\")\n            z = np.empty(empty.shape[:2], dtype=\"float64\")\n            empty[:] = 0.0\n            z[:] = np.inf\n            zbuffer = ZBuffer(empty, z)\n        elif zbuffer.rgba.shape != shape:\n            zbuffer = ZBuffer(zbuffer.rgba.reshape(shape), zbuffer.z.reshape(shape[:2]))\n        self.zbuffer = zbuffer\n\n        self.sampler = new_mesh_sampler(camera, self, engine=self.engine)\n\n        mylog.debug(\"Casting rays\")\n        self.sampler(self.volume)\n        mylog.debug(\"Done casting rays\")\n\n        self.finalize_image(camera)\n        self.current_image = self.apply_colormap()\n\n        zbuffer += ZBuffer(self.current_image.astype(\"float64\"), self.sampler.azbuffer)\n        zbuffer.rgba = ImageArray(zbuffer.rgba)\n        self.zbuffer = zbuffer\n        self.current_image = self.zbuffer.rgba\n\n        if self._annotate_mesh:\n            self.current_image = self.annotate_mesh_lines(\n                self._mesh_line_color, self._mesh_line_alpha\n            )\n\n        return self.current_image\n\n    def finalize_image(self, camera):\n        sam = self.sampler\n\n        # reshape data\n        Nx = camera.resolution[0]\n        Ny = camera.resolution[1]\n        self.data = sam.aimage[:, :, 0].reshape(Nx, Ny)\n\n    def annotate_mesh_lines(self, color=None, alpha=1.0):\n        r\"\"\"\n\n        Modifies this MeshSource by drawing the mesh lines.\n        This modifies the current image by drawing the element\n        boundaries and returns the modified image.\n\n        Parameters\n        ----------\n        color: array_like of shape (4,), optional\n            The RGBA value to use to draw the mesh lines.\n            Default is black.\n        alpha : float, optional\n            The opacity of the mesh lines. Default is 255 (solid).\n\n        \"\"\"\n\n        self.annotate_mesh = True\n        self._mesh_line_color = color\n        self._mesh_line_alpha = alpha\n\n        if color is None:\n            color = np.array([0, 0, 0, alpha])\n\n        locs = (self.sampler.amesh_lines == 1,)\n\n        self.current_image[:, :, 0][locs] = color[0]\n        self.current_image[:, :, 1][locs] = color[1]\n        self.current_image[:, :, 2][locs] = color[2]\n        self.current_image[:, :, 3][locs] = color[3]\n\n        return self.current_image\n\n    def apply_colormap(self):\n        \"\"\"\n\n        Applies a colormap to the current image without re-rendering.\n\n        Returns\n        -------\n        current_image : A new image with the specified color scale applied to\n            the underlying data.\n\n\n        \"\"\"\n\n        image = (\n            apply_colormap(\n                self.data, color_bounds=self._color_bounds, cmap_name=self._cmap\n            )\n            / 255.0\n        )\n        alpha = image[:, :, 3]\n        alpha[self.sampler.aimage_used == -1] = 0.0\n        image[:, :, 3] = alpha\n        return image\n\n    def __repr__(self):\n        disp = f\"<Mesh Source>:{str(self.data_source)} \"\n        return disp\n\n\nclass PointSource(OpaqueSource):\n    r\"\"\"A rendering source of opaque points in the scene.\n\n    This class provides a mechanism for adding points to a scene; these\n    points will be opaque, and can also be colored.\n\n    Parameters\n    ----------\n    positions: array_like of shape (N, 3)\n        The positions of points to be added to the scene. If specified with no\n        units, the positions will be assumed to be in code units.\n    colors : array_like of shape (N, 4), optional\n        The colors of the points, including an alpha channel, in floating\n        point running from 0..1.\n    color_stride : int, optional\n        The stride with which to access the colors when putting them on the\n        scene.\n    radii : array_like of shape (N), optional\n        The radii of the points in the final image, in pixels (int)\n\n    Examples\n    --------\n\n    This example creates a volume rendering and adds 1000 random points to\n    the image:\n\n    >>> import yt\n    >>> import numpy as np\n    >>> from yt.visualization.volume_rendering.api import PointSource\n    >>> from yt.units import kpc\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    >>> im, sc = yt.volume_render(ds)\n\n    >>> npoints = 1000\n    >>> vertices = np.random.random([npoints, 3]) * 1000 * kpc\n    >>> colors = np.random.random([npoints, 4])\n    >>> colors[:, 3] = 1.0\n\n    >>> points = PointSource(vertices, colors=colors)\n    >>> sc.add_source(points)\n\n    >>> im = sc.render()\n\n    \"\"\"\n\n    _image = None\n    data_source = None\n\n    def __init__(self, positions, colors=None, color_stride=1, radii=None):\n        assert positions.ndim == 2 and positions.shape[1] == 3\n        if colors is not None:\n            assert colors.ndim == 2 and colors.shape[1] == 4\n            assert colors.shape[0] == positions.shape[0]\n        if not is_sequence(radii):\n            if radii is not None:  # broadcast the value\n                radii = radii * np.ones(positions.shape[0], dtype=\"int64\")\n            else:  # default radii to 0 pixels (i.e. point is 1 pixel wide)\n                radii = np.zeros(positions.shape[0], dtype=\"int64\")\n        else:\n            assert radii.ndim == 1\n            assert radii.shape[0] == positions.shape[0]\n        self.positions = positions\n        # If colors aren't individually set, make black with full opacity\n        if colors is None:\n            colors = np.ones((len(positions), 4))\n        self.colors = colors\n        self.color_stride = color_stride\n        self.radii = radii\n\n    def _validate(self):\n        pass\n\n    def render(self, camera, zbuffer=None):\n        \"\"\"Renders an image using the provided camera\n\n        Parameters\n        ----------\n        camera: :class:`yt.visualization.volume_rendering.camera.Camera`\n            A volume rendering camera. Can be any type of camera.\n        zbuffer: :class:`yt.visualization.volume_rendering.zbuffer_array.Zbuffer`\n            A zbuffer array. This is used for opaque sources to determine the\n            z position of the source relative to other sources. Only useful if\n            you are manually calling render on multiple sources. Scene.render\n            uses this internally.\n\n        Returns\n        -------\n        A :class:`yt.data_objects.image_array.ImageArray` containing\n        the rendered image.\n\n        \"\"\"\n        vertices = self.positions\n        if zbuffer is None:\n            empty = camera.lens.new_image(camera)\n            z = np.empty(empty.shape[:2], dtype=\"float64\")\n            empty[:] = 0.0\n            z[:] = np.inf\n            zbuffer = ZBuffer(empty, z)\n        else:\n            empty = zbuffer.rgba\n            z = zbuffer.z\n\n        # DRAW SOME POINTS\n        camera.lens.setup_box_properties(camera)\n        px, py, dz = camera.lens.project_to_plane(camera, vertices)\n\n        zpoints(empty, z, px, py, dz, self.colors, self.radii, self.color_stride)\n\n        self.zbuffer = zbuffer\n        return zbuffer\n\n    def __repr__(self):\n        disp = \"<Point Source>\"\n        return disp\n\n\nclass LineSource(OpaqueSource):\n    r\"\"\"A render source for a sequence of opaque line segments.\n\n    This class provides a mechanism for adding lines to a scene; these\n    points will be opaque, and can also be colored.\n\n    .. note::\n\n        If adding a LineSource to your rendering causes the image to appear\n        blank or fades a VolumeSource, try lowering the values specified in\n        the alpha channel of the ``colors`` array.\n\n    Parameters\n    ----------\n    positions: array_like of shape (N, 2, 3)\n        The positions of the starting and stopping points for each line.\n        For example,positions[0][0] and positions[0][1] would give the (x, y, z)\n        coordinates of the beginning and end points of the first line,\n        respectively. If specified with no units, assumed to be in code units.\n    colors : array_like of shape (N, 4), optional\n        The colors of the points, including an alpha channel, in floating\n        point running from 0..1.  The four channels correspond to r, g, b, and\n        alpha values. Note that they correspond to the line segment succeeding\n        each point; this means that strictly speaking they need only be (N-1)\n        in length.\n    color_stride : int, optional\n        The stride with which to access the colors when putting them on the\n        scene.\n\n    Examples\n    --------\n\n    This example creates a volume rendering and then adds some random lines\n    to the image:\n\n    >>> import yt\n    >>> import numpy as np\n    >>> from yt.visualization.volume_rendering.api import LineSource\n    >>> from yt.units import kpc\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    >>> im, sc = yt.volume_render(ds)\n\n    >>> nlines = 4\n    >>> vertices = np.random.random([nlines, 2, 3]) * 600 * kpc\n    >>> colors = np.random.random([nlines, 4])\n    >>> colors[:, 3] = 1.0\n\n    >>> lines = LineSource(vertices, colors)\n    >>> sc.add_source(lines)\n\n    >>> im = sc.render()\n\n    \"\"\"\n\n    _image = None\n    data_source = None\n\n    def __init__(self, positions, colors=None, color_stride=1):\n        super().__init__()\n\n        assert positions.ndim == 3\n        assert positions.shape[1] == 2\n        assert positions.shape[2] == 3\n        if colors is not None:\n            assert colors.ndim == 2\n            assert colors.shape[1] == 4\n\n        # convert the positions to the shape expected by zlines, below\n        N = positions.shape[0]\n        self.positions = positions.reshape((2 * N, 3))\n\n        # If colors aren't individually set, make black with full opacity\n        if colors is None:\n            colors = np.ones((len(positions), 4))\n        self.colors = colors\n        self.color_stride = color_stride\n\n    def _validate(self):\n        pass\n\n    def render(self, camera, zbuffer=None):\n        \"\"\"Renders an image using the provided camera\n\n        Parameters\n        ----------\n        camera: :class:`yt.visualization.volume_rendering.camera.Camera`\n            A volume rendering camera. Can be any type of camera.\n        zbuffer: :class:`yt.visualization.volume_rendering.zbuffer_array.Zbuffer`\n            z position of the source relative to other sources. Only useful if\n            you are manually calling render on multiple sources. Scene.render\n            uses this internally.\n\n        Returns\n        -------\n        A :class:`yt.data_objects.image_array.ImageArray` containing\n        the rendered image.\n\n        \"\"\"\n        vertices = self.positions\n        if zbuffer is None:\n            empty = camera.lens.new_image(camera)\n            z = np.empty(empty.shape[:2], dtype=\"float64\")\n            empty[:] = 0.0\n            z[:] = np.inf\n            zbuffer = ZBuffer(empty, z)\n        else:\n            empty = zbuffer.rgba\n            z = zbuffer.z\n\n        # DRAW SOME LINES\n        camera.lens.setup_box_properties(camera)\n        px, py, dz = camera.lens.project_to_plane(camera, vertices)\n\n        px = px.astype(\"int64\")\n        py = py.astype(\"int64\")\n\n        if len(px.shape) == 1:\n            zlines(\n                empty, z, px, py, dz, self.colors.astype(\"float64\"), self.color_stride\n            )\n        else:\n            # For stereo-lens, two sets of pos for each eye are contained\n            # in px...pz\n            zlines(\n                empty,\n                z,\n                px[0, :],\n                py[0, :],\n                dz[0, :],\n                self.colors.astype(\"float64\"),\n                self.color_stride,\n            )\n            zlines(\n                empty,\n                z,\n                px[1, :],\n                py[1, :],\n                dz[1, :],\n                self.colors.astype(\"float64\"),\n                self.color_stride,\n            )\n\n        self.zbuffer = zbuffer\n        return zbuffer\n\n    def __repr__(self):\n        disp = \"<Line Source>\"\n        return disp\n\n\nclass BoxSource(LineSource):\n    r\"\"\"A render source for a box drawn with line segments.\n    This render source will draw a box, with transparent faces, in data\n    space coordinates.  This is useful for annotations.\n\n    Parameters\n    ----------\n    left_edge: array-like of shape (3,), float\n        The left edge coordinates of the box.\n    right_edge : array-like of shape (3,), float\n        The right edge coordinates of the box.\n    color : array-like of shape (4,), float, optional\n        The colors (including alpha) to use for the lines.\n        Default is black with an alpha of 1.0.\n\n    Examples\n    --------\n\n    This example shows how to use BoxSource to add an outline of the\n    domain boundaries to a volume rendering.\n\n    >>> import yt\n    >>> from yt.visualization.volume_rendering.api import BoxSource\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    >>> im, sc = yt.volume_render(ds)\n\n    >>> box_source = BoxSource(\n    ...     ds.domain_left_edge, ds.domain_right_edge, [1.0, 1.0, 1.0, 1.0]\n    ... )\n    >>> sc.add_source(box_source)\n\n    >>> im = sc.render()\n\n    \"\"\"\n\n    def __init__(self, left_edge, right_edge, color=None):\n        assert left_edge.shape == (3,)\n        assert right_edge.shape == (3,)\n\n        if color is None:\n            color = np.array([[1.0, 1.0, 1.0, 1.0]])\n        else:\n            color = np.atleast_2d(ensure_numpy_array(color))\n        assert color.shape == (1, 4)\n\n        corners = get_corners(left_edge.copy(), right_edge.copy())\n        order = [0, 1, 1, 2, 2, 3, 3, 0]\n        order += [4, 5, 5, 6, 6, 7, 7, 4]\n        order += [0, 4, 1, 5, 2, 6, 3, 7]\n        vertices = np.empty([24, 3])\n        for i in range(3):\n            vertices[:, i] = corners[order, i, ...].ravel(order=\"F\")\n        vertices = vertices.reshape((12, 2, 3))\n\n        super().__init__(vertices, color, color_stride=24)\n\n    def _validate(self):\n        pass\n\n\nclass GridSource(LineSource):\n    r\"\"\"A render source for drawing grids in a scene.\n\n    This render source will draw blocks that are within a given data\n    source, by default coloring them by their level of resolution.\n\n    Parameters\n    ----------\n    data_source: :class:`~yt.data_objects.api.DataContainer`\n        The data container that will be used to identify grids to draw.\n    alpha : float\n        The opacity of the grids to draw.\n    cmap : color map name\n        The color map to use to map resolution levels to color.\n    min_level : int, optional\n        Minimum level to draw\n    max_level : int, optional\n        Maximum level to draw\n\n    Examples\n    --------\n\n    This example makes a volume rendering and adds outlines of all the\n    AMR grids in the simulation:\n\n    >>> import yt\n    >>> from yt.visualization.volume_rendering.api import GridSource\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    >>> im, sc = yt.volume_render(ds)\n\n    >>> grid_source = GridSource(ds.all_data(), alpha=1.0)\n\n    >>> sc.add_source(grid_source)\n\n    >>> im = sc.render()\n\n    This example does the same thing, except it only draws the grids\n    that are inside a sphere of radius (0.1, \"unitary\") located at the\n    domain center:\n\n    >>> import yt\n    >>> from yt.visualization.volume_rendering.api import GridSource\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    >>> im, sc = yt.volume_render(ds)\n\n    >>> dd = ds.sphere(\"c\", (0.1, \"unitary\"))\n    >>> grid_source = GridSource(dd, alpha=1.0)\n\n    >>> sc.add_source(grid_source)\n\n    >>> im = sc.render()\n\n    \"\"\"\n\n    def __init__(\n        self, data_source, alpha=0.3, cmap=None, min_level=None, max_level=None\n    ):\n        self.data_source = data_source_or_all(data_source)\n        corners = []\n        levels = []\n        for block, _mask in self.data_source.blocks:\n            block_corners = np.array(\n                [\n                    [block.LeftEdge[0], block.LeftEdge[1], block.LeftEdge[2]],\n                    [block.RightEdge[0], block.LeftEdge[1], block.LeftEdge[2]],\n                    [block.RightEdge[0], block.RightEdge[1], block.LeftEdge[2]],\n                    [block.LeftEdge[0], block.RightEdge[1], block.LeftEdge[2]],\n                    [block.LeftEdge[0], block.LeftEdge[1], block.RightEdge[2]],\n                    [block.RightEdge[0], block.LeftEdge[1], block.RightEdge[2]],\n                    [block.RightEdge[0], block.RightEdge[1], block.RightEdge[2]],\n                    [block.LeftEdge[0], block.RightEdge[1], block.RightEdge[2]],\n                ],\n                dtype=\"float64\",\n            )\n            corners.append(block_corners)\n            levels.append(block.Level)\n        corners = np.dstack(corners)\n        levels = np.array(levels)\n        if cmap is None:\n            cmap = ytcfg.get(\"yt\", \"default_colormap\")\n\n        if max_level is not None:\n            subset = levels <= max_level\n            levels = levels[subset]\n            corners = corners[:, :, subset]\n        if min_level is not None:\n            subset = levels >= min_level\n            levels = levels[subset]\n            corners = corners[:, :, subset]\n\n        colors = (\n            apply_colormap(\n                levels * 1.0,\n                color_bounds=[0, self.data_source.ds.index.max_level],\n                cmap_name=cmap,\n            )[0, :, :]\n            / 255.0\n        )\n        colors[:, 3] = alpha\n\n        order = [0, 1, 1, 2, 2, 3, 3, 0]\n        order += [4, 5, 5, 6, 6, 7, 7, 4]\n        order += [0, 4, 1, 5, 2, 6, 3, 7]\n\n        vertices = np.empty([corners.shape[2] * 2 * 12, 3])\n        for i in range(3):\n            vertices[:, i] = corners[order, i, ...].ravel(order=\"F\")\n        vertices = vertices.reshape((corners.shape[2] * 12, 2, 3))\n\n        super().__init__(vertices, colors, color_stride=24)\n\n\nclass CoordinateVectorSource(OpaqueSource):\n    r\"\"\"Draw coordinate vectors on the scene.\n\n    This will draw a set of coordinate vectors on the camera image.  They\n    will appear in the lower right of the image.\n\n    Parameters\n    ----------\n    colors: array-like of shape (3,4), optional\n        The RGBA values to use to draw the x, y, and z vectors. The default is\n        [[1, 0, 0, alpha], [0, 1, 0, alpha], [0, 0, 1, alpha]]  where ``alpha``\n        is set by the parameter below. If ``colors`` is set then ``alpha`` is\n        ignored.\n    alpha : float, optional\n        The opacity of the vectors.\n    thickness : int, optional\n        The line thickness\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> from yt.visualization.volume_rendering.api import \\\n    ...     CoordinateVectorSource\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    >>> im, sc = yt.volume_render(ds)\n\n    >>> coord_source = CoordinateVectorSource()\n\n    >>> sc.add_source(coord_source)\n\n    >>> im = sc.render()\n\n    \"\"\"\n\n    def __init__(self, colors=None, alpha=1.0, *, thickness=1):\n        super().__init__()\n        # If colors aren't individually set, make black with full opacity\n        if colors is None:\n            colors = np.zeros((3, 4))\n            colors[0, 0] = 1.0  # x is red\n            colors[1, 1] = 1.0  # y is green\n            colors[2, 2] = 1.0  # z is blue\n            colors[:, 3] = alpha\n        self.colors = colors\n        self.thick = thickness\n\n    def _validate(self):\n        pass\n\n    def render(self, camera, zbuffer=None):\n        \"\"\"Renders an image using the provided camera\n\n        Parameters\n        ----------\n        camera: :class:`yt.visualization.volume_rendering.camera.Camera`\n            A volume rendering camera. Can be any type of camera.\n        zbuffer: :class:`yt.visualization.volume_rendering.zbuffer_array.Zbuffer`\n            A zbuffer array. This is used for opaque sources to determine the\n            z position of the source relative to other sources. Only useful if\n            you are manually calling render on multiple sources. Scene.render\n            uses this internally.\n\n        Returns\n        -------\n        A :class:`yt.data_objects.image_array.ImageArray` containing\n        the rendered image.\n\n        \"\"\"\n        camera.lens.setup_box_properties(camera)\n        center = camera.focus\n        # Get positions at the focus\n        positions = np.zeros([6, 3])\n        positions[:] = center\n\n        # Create vectors in the x,y,z directions\n        for i in range(3):\n            positions[2 * i + 1, i] += camera.width.in_units(\"code_length\").d[i] / 16.0\n\n        # Project to the image plane\n        px, py, dz = camera.lens.project_to_plane(camera, positions)\n\n        if len(px.shape) == 1:\n            dpx = px[1::2] - px[::2]\n            dpy = py[1::2] - py[::2]\n\n            # Set the center of the coordinates to be in the lower left of the image\n            lpx = camera.resolution[0] / 8\n            lpy = camera.resolution[1] - camera.resolution[1] / 8  # Upside-downsies\n\n            # Offset the pixels according to the projections above\n            px[::2] = lpx\n            px[1::2] = lpx + dpx\n            py[::2] = lpy\n            py[1::2] = lpy + dpy\n            dz[:] = 0.0\n        else:\n            # For stereo-lens, two sets of pos for each eye are contained in px...pz\n            dpx = px[:, 1::2] - px[:, ::2]\n            dpy = py[:, 1::2] - py[:, ::2]\n\n            lpx = camera.resolution[0] / 16\n            lpy = camera.resolution[1] - camera.resolution[1] / 8  # Upside-downsies\n\n            # Offset the pixels according to the projections above\n            px[:, ::2] = lpx\n            px[:, 1::2] = lpx + dpx\n            px[1, :] += camera.resolution[0] / 2\n            py[:, ::2] = lpy\n            py[:, 1::2] = lpy + dpy\n            dz[:, :] = 0.0\n\n        # Create a zbuffer if needed\n        if zbuffer is None:\n            empty = camera.lens.new_image(camera)\n            z = np.empty(empty.shape[:2], dtype=\"float64\")\n            empty[:] = 0.0\n            z[:] = np.inf\n            zbuffer = ZBuffer(empty, z)\n        else:\n            empty = zbuffer.rgba\n            z = zbuffer.z\n\n        # Draw the vectors\n\n        px = px.astype(\"int64\")\n        py = py.astype(\"int64\")\n\n        if len(px.shape) == 1:\n            zlines(\n                empty, z, px, py, dz, self.colors.astype(\"float64\"), thick=self.thick\n            )\n        else:\n            # For stereo-lens, two sets of pos for each eye are contained\n            # in px...pz\n            zlines(\n                empty,\n                z,\n                px[0, :],\n                py[0, :],\n                dz[0, :],\n                self.colors.astype(\"float64\"),\n                thick=self.thick,\n            )\n            zlines(\n                empty,\n                z,\n                px[1, :],\n                py[1, :],\n                dz[1, :],\n                self.colors.astype(\"float64\"),\n                thick=self.thick,\n            )\n\n        # Set the new zbuffer\n        self.zbuffer = zbuffer\n        return zbuffer\n\n    def __repr__(self):\n        disp = \"<Coordinates Source>\"\n        return disp\n"
  },
  {
    "path": "yt/visualization/volume_rendering/scene.py",
    "content": "import functools\nfrom collections import OrderedDict\n\nimport numpy as np\n\nfrom yt._maintenance.ipython_compat import IS_IPYTHON\nfrom yt.config import ytcfg\nfrom yt.funcs import mylog\nfrom yt.units.dimensions import length  # type: ignore\nfrom yt.units.unit_registry import UnitRegistry  # type: ignore\nfrom yt.units.yt_array import YTArray, YTQuantity\nfrom yt.utilities.exceptions import YTNotInsideNotebook\nfrom yt.visualization._commons import get_canvas, validate_image_name\n\nfrom .camera import Camera\nfrom .render_source import (\n    BoxSource,\n    CoordinateVectorSource,\n    GridSource,\n    LineSource,\n    MeshSource,\n    OpaqueSource,\n    PointSource,\n    RenderSource,\n    VolumeSource,\n)\nfrom .zbuffer_array import ZBuffer\n\n\nclass Scene:\n    \"\"\"A virtual landscape for a volume rendering.\n\n    The Scene class is meant to be the primary container for the\n    new volume rendering framework. A single scene may contain\n    several Camera and RenderSource instances, and is the primary\n    driver behind creating a volume rendering.\n\n    This sets up the basics needed to add sources and cameras.\n    This does very little setup, and requires additional input\n    to do anything useful.\n\n    Examples\n    --------\n\n    This example shows how to create an empty scene and add a VolumeSource\n    and a Camera.\n\n    >>> import yt\n    >>> from yt.visualization.volume_rendering.api import (\n    ...     Camera, Scene, create_volume_source)\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n    >>> sc = Scene()\n    >>> source = create_volume_source(ds.all_data(), \"density\")\n    >>> sc.add_source(source)\n    >>> cam = sc.add_camera()\n    >>> im = sc.render()\n\n    Alternatively, you can use the create_scene function to set up defaults\n    and then modify the Scene later:\n\n    >>> import yt\n    >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n    >>> sc = yt.create_scene(ds)\n    >>> # Modify camera, sources, etc...\n    >>> im = sc.render()\n\n    \"\"\"\n\n    _current = None\n    _camera = None\n    _unit_registry = None\n\n    def __init__(self):\n        r\"\"\"Create a new Scene instance\"\"\"\n        super().__init__()\n        self.sources = OrderedDict()\n        self._last_render = None\n        # A non-public attribute used to get around the fact that we can't\n        # pass kwargs into _repr_png_()\n        self._sigma_clip = None\n\n    def get_source(self, source_num=0):\n        \"\"\"Returns the volume rendering source indexed by ``source_num``\"\"\"\n        return list(self.sources.values())[source_num]\n\n    def __getitem__(self, item):\n        if item in self.sources:\n            return self.sources[item]\n        return self.get_source(item)\n\n    @property\n    def opaque_sources(self):\n        \"\"\"\n        Iterate over opaque RenderSource objects,\n        returning a tuple of (key, source)\n        \"\"\"\n        for k, source in self.sources.items():\n            if isinstance(source, OpaqueSource) or issubclass(\n                OpaqueSource, type(source)\n            ):\n                yield k, source\n\n    @property\n    def transparent_sources(self):\n        \"\"\"\n        Iterate over transparent RenderSource objects,\n        returning a tuple of (key, source)\n        \"\"\"\n        for k, source in self.sources.items():\n            if not isinstance(source, OpaqueSource):\n                yield k, source\n\n    def add_source(self, render_source, keyname=None):\n        \"\"\"Add a render source to the scene.\n\n        This will autodetect the type of source.\n\n        Parameters\n        ----------\n        render_source:\n            :class:`yt.visualization.volume_rendering.render_source.RenderSource`\n            A source to contribute to the volume rendering scene.\n\n        keyname: string (optional)\n            The dictionary key used to reference the source in the sources\n            dictionary.\n        \"\"\"\n        if keyname is None:\n            keyname = f\"source_{len(self.sources):02}\"\n\n        data_sources = (VolumeSource, MeshSource, GridSource)\n\n        if isinstance(render_source, data_sources):\n            self._set_new_unit_registry(render_source.data_source.ds.unit_registry)\n\n        line_annotation_sources = (GridSource, BoxSource, CoordinateVectorSource)\n\n        if isinstance(render_source, line_annotation_sources):\n            lens_str = str(self.camera.lens)\n            if \"fisheye\" in lens_str or \"spherical\" in lens_str:\n                raise NotImplementedError(\n                    \"Line annotation sources are not supported \"\n                    f\"for {type(self.camera.lens).__name__}.\"\n                )\n\n        if isinstance(render_source, (LineSource, PointSource)):\n            if isinstance(render_source.positions, YTArray):\n                render_source.positions = (\n                    self.arr(render_source.positions).in_units(\"code_length\").d\n                )\n\n        self.sources[keyname] = render_source\n\n        return self\n\n    def __setitem__(self, key, value):\n        return self.add_source(value, key)\n\n    def _set_new_unit_registry(self, input_registry):\n        self.unit_registry = UnitRegistry(\n            add_default_symbols=False, lut=input_registry.lut\n        )\n\n        # Validate that the new unit registry makes sense\n        current_scaling = self.unit_registry[\"unitary\"][0]\n        if current_scaling != input_registry[\"unitary\"][0]:\n            for source in self.sources.items():\n                data_source = getattr(source, \"data_source\", None)\n                if data_source is None:\n                    continue\n                scaling = data_source.ds.unit_registry[\"unitary\"][0]\n                if scaling != current_scaling:\n                    raise NotImplementedError(\n                        \"Simultaneously rendering data from datasets with \"\n                        \"different units is not supported\"\n                    )\n\n    def render(self, camera=None):\n        r\"\"\"Render all sources in the Scene.\n\n        Use the current state of the Scene object to render all sources\n        currently in the scene.  Returns the image array.  If you want to\n        save the output to a file, call the save() function.\n\n        Parameters\n        ----------\n        camera: :class:`Camera`, optional\n            If specified, use a different :class:`Camera` to render the scene.\n\n        Returns\n        -------\n        A :class:`yt.data_objects.image_array.ImageArray` instance containing\n        the current rendering image.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> sc = yt.create_scene(ds)\n        >>> # Modify camera, sources, etc...\n        >>> im = sc.render()\n        >>> sc.save(sigma_clip=4.0, render=False)\n\n        Altneratively, if you do not need the image array, you can just call\n        ``save`` as follows.\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> sc = yt.create_scene(ds)\n        >>> # Modify camera, sources, etc...\n        >>> sc.save(sigma_clip=4.0)\n\n        \"\"\"\n        mylog.info(\"Rendering scene (Can take a while).\")\n        if camera is None:\n            camera = self.camera\n        assert camera is not None\n        self._validate()\n        bmp = self.composite(camera=camera)\n        self._last_render = bmp\n        return bmp\n\n    def _render_on_demand(self, render):\n        # checks for existing render before rendering, in most cases we want to\n        # render every time, but in some cases pulling the previous render is\n        # desirable (e.g., if only changing sigma_clip or\n        # saving after a call to sc.show()).\n\n        if self._last_render is not None and not render:\n            mylog.info(\"Found previously rendered image to save.\")\n            return\n\n        if self._last_render is None:\n            mylog.warning(\"No previously rendered image found, rendering now.\")\n        elif render:\n            mylog.warning(\n                \"Previously rendered image exists, but rendering anyway. \"\n                \"Supply 'render=False' to save previously rendered image directly.\"\n            )\n        self.render()\n\n    def _get_render_sources(self):\n        return [s for s in self.sources.values() if isinstance(s, RenderSource)]\n\n    def _setup_save(self, fname, render) -> str:\n        self._render_on_demand(render)\n\n        rensources = self._get_render_sources()\n        if fname is None:\n            # if a volume source present, use its affiliated ds for fname\n            if len(rensources) > 0:\n                rs = rensources[0]\n                basename = rs.data_source.ds.basename\n                if isinstance(rs.field, str):\n                    field = rs.field\n                else:\n                    field = rs.field[-1]\n                fname = f\"{basename}_Render_{field}\"\n            # if no volume source present, use a default filename\n            else:\n                fname = \"Render_opaque\"\n\n        fname = validate_image_name(fname)\n        mylog.info(\"Saving rendered image to %s\", fname)\n        return fname\n\n    def save(\n        self,\n        fname: str | None = None,\n        sigma_clip: float | None = None,\n        render: bool = True,\n    ):\n        r\"\"\"Saves a rendered image of the Scene to disk.\n\n        Once you have created a scene, this saves an image array to disk with\n        an optional filename. This function calls render() to generate an\n        image array, unless the render parameter is set to False, in which case\n        the most recently rendered scene is used if it exists.\n\n        Parameters\n        ----------\n        fname: string, optional\n            If specified, save the rendering as to the file \"fname\".\n            If unspecified, it creates a default based on the dataset filename.\n            The file format is inferred from the filename's suffix.\n            Supported formats depend on which version of matplotlib is installed.\n\n            Default: None\n        sigma_clip: float, optional\n            Image values greater than this number times the standard deviation\n            plus the mean of the image will be clipped before saving. Useful\n            for enhancing images as it gets rid of rare high pixel values.\n            Default: None\n\n            floor(vals > std_dev*sigma_clip + mean)\n        render: boolean, optional\n            If True, will always render the scene before saving.\n            If False, will use results of previous render if it exists.\n            Default: True\n\n        Returns\n        -------\n            Nothing\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> sc = yt.create_scene(ds)\n        >>> # Modify camera, sources, etc...\n        >>> sc.save(\"test.png\", sigma_clip=4)\n\n        When saving multiple images without modifying the scene (camera,\n        sources,etc.), render=False can be used to avoid re-rendering.\n        This is useful for generating images at a range of sigma_clip values:\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> sc = yt.create_scene(ds)\n        >>> # save with different sigma clipping values\n        >>> sc.save(\"raw.png\")  # The initial render call happens here\n        >>> sc.save(\"clipped_2.png\", sigma_clip=2, render=False)\n        >>> sc.save(\"clipped_4.png\", sigma_clip=4, render=False)\n\n        \"\"\"\n        fname = self._setup_save(fname, render)\n\n        # We can render pngs natively but for other formats we defer to\n        # matplotlib.\n        if fname.endswith(\".png\"):\n            self._last_render.write_png(fname, sigma_clip=sigma_clip)\n        else:\n            from matplotlib.figure import Figure\n\n            shape = self._last_render.shape\n            fig = Figure((shape[0] / 100.0, shape[1] / 100.0))\n            canvas = get_canvas(fig, fname)\n\n            ax = fig.add_axes((0, 0, 1, 1))\n            ax.set_axis_off()\n            out = self._last_render\n            if sigma_clip is not None:\n                max_val = out._clipping_value(sigma_clip)\n            else:\n                max_val = out[:, :, :3].max()\n            alpha = 255 * out[:, :, 3].astype(\"uint8\")\n            out = np.clip(out[:, :, :3] / max_val, 0.0, 1.0) * 255\n            out = np.concatenate([out.astype(\"uint8\"), alpha[..., None]], axis=-1)\n            # not sure why we need rot90, but this makes the orientation\n            # match the png writer\n            ax.imshow(np.rot90(out), origin=\"lower\")\n            canvas.print_figure(fname, dpi=100)\n\n    def save_annotated(\n        self,\n        fname: str | None = None,\n        label_fmt: str | None = None,\n        text_annotate=None,\n        dpi: int = 100,\n        sigma_clip: float | None = None,\n        render: bool = True,\n        tf_rect: list[float] | None = None,\n        *,\n        label_fontsize: float | str = 10,\n    ):\n        r\"\"\"Saves the most recently rendered image of the Scene to disk,\n        including an image of the transfer function and and user-defined\n        text.\n\n        Once you have created a scene and rendered that scene to an image\n        array, this saves that image array to disk with an optional filename.\n        If an image has not yet been rendered for the current scene object,\n        it forces one and writes it out.\n\n        Parameters\n        ----------\n        fname: string, optional\n            If specified, save the rendering as a bitmap to the file \"fname\".\n            If unspecified, it creates a default based on the dataset filename.\n            Default: None\n        sigma_clip: float, optional\n            Image values greater than this number times the standard deviation\n            plus the mean of the image will be clipped before saving. Useful\n            for enhancing images as it gets rid of rare high pixel values.\n            Default: None\n\n            floor(vals > std_dev*sigma_clip + mean)\n        dpi: integer, optional\n            By default, the resulting image will be the same size as the camera\n            parameters.  If you supply a dpi, then the image will be scaled\n            accordingly (from the default 100 dpi)\n        label_fmt : str, optional\n           A format specifier (e.g., label_fmt=\"%.2g\") to use in formatting\n           the data values that label the transfer function colorbar.\n        label_fontsize : float or string, optional\n           The fontsize used to display the numbers on the transfer function\n           colorbar.  This can be any matplotlib font size specification, e.g.,\n           \"large\" or 12. (default: 10)\n        text_annotate : list of iterables\n           Any text that you wish to display on the image.  This should be an\n           list containing a tuple of coordinates (in normalized figure\n           coordinates), the text to display, and, optionally, a dictionary of\n           keyword/value pairs to pass through to the matplotlib text()\n           function.\n\n           Each item in the main list is a separate string to write.\n        render: boolean, optional\n            If True, will render the scene before saving.\n            If False, will use results of previous render if it exists.\n            Default: True\n        tf_rect : sequence of floats, optional\n           A rectangle that defines the location of the transfer\n           function legend.  This is only used for the case where\n           there are multiple volume sources with associated transfer\n           functions.  tf_rect is of the form [x0, y0, width, height],\n           in figure coordinates.\n\n        Returns\n        -------\n            Nothing\n\n\n        Examples\n        --------\n\n        >>> sc.save_annotated(\n        ...     \"fig.png\",\n        ...     text_annotate=[\n        ...         [\n        ...             (0.05, 0.05),\n        ...             f\"t = {ds.current_time.d}\",\n        ...             dict(horizontalalignment=\"left\"),\n        ...         ],\n        ...         [\n        ...             (0.5, 0.95),\n        ...             \"simulation title\",\n        ...             dict(color=\"y\", fontsize=\"24\", horizontalalignment=\"center\"),\n        ...         ],\n        ...     ],\n        ... )\n\n        \"\"\"\n        fname = self._setup_save(fname, render)\n\n        ax = self._show_mpl(\n            self._last_render.swapaxes(0, 1), sigma_clip=sigma_clip, dpi=dpi\n        )\n\n        # number of transfer functions?\n        num_trans_func = 0\n        for rs in self._get_render_sources():\n            if hasattr(rs, \"transfer_function\"):\n                num_trans_func += 1\n\n        # which transfer function?\n        if num_trans_func == 1:\n            rs = self._get_render_sources()[0]\n            tf = rs.transfer_function\n            label = rs.data_source.ds._get_field_info(rs.field).get_label()\n            self._annotate(\n                ax.axes,\n                tf,\n                rs,\n                label=label,\n                label_fmt=label_fmt,\n                label_fontsize=label_fontsize,\n            )\n        else:\n            # set the origin and width and height of the colorbar region\n            if tf_rect is None:\n                tf_rect = [0.80, 0.12, 0.12, 0.9]\n            cbx0, cby0, cbw, cbh = tf_rect\n\n            cbh_each = cbh / num_trans_func\n\n            for i, rs in enumerate(self._get_render_sources()):\n                ax = self._render_figure.add_axes(\n                    [cbx0, cby0 + i * cbh_each, 0.8 * cbw, 0.8 * cbh_each]\n                )\n                try:\n                    tf = rs.transfer_function\n                except AttributeError:\n                    pass\n                else:\n                    label = rs.data_source.ds._get_field_info(rs.field).get_label()\n                    self._annotate_multi(\n                        ax,\n                        tf,\n                        rs,\n                        label=label,\n                        label_fmt=label_fmt,\n                        label_fontsize=label_fontsize,\n                    )\n\n        # any text?\n        if text_annotate is not None:\n            f = self._render_figure\n            for t in text_annotate:\n                xy = t[0]\n                string = t[1]\n                if len(t) == 3:\n                    opt = t[2]\n                else:\n                    opt = {}\n\n                # sane default\n                if \"color\" not in opt:\n                    opt[\"color\"] = \"w\"\n\n                ax.axes.text(xy[0], xy[1], string, transform=f.transFigure, **opt)\n\n        self._render_figure.canvas = get_canvas(self._render_figure, fname)\n        self._render_figure.tight_layout()\n        self._render_figure.savefig(fname, facecolor=\"black\", pad_inches=0)\n\n    def _show_mpl(self, im, sigma_clip=None, dpi=100):\n        from matplotlib.figure import Figure\n\n        s = im.shape\n        self._render_figure = Figure(\n            figsize=(s[1] / float(dpi), s[0] / float(dpi)), dpi=dpi\n        )\n        self._render_figure.clf()\n        ax = self._render_figure.add_subplot(111)\n        ax.set_position([0, 0, 1, 1])\n\n        if sigma_clip is not None:\n            nim = im / im._clipping_value(sigma_clip)\n            nim[nim > 1.0] = 1.0\n            nim[nim < 0.0] = 0.0\n        else:\n            nim = im\n        axim = ax.imshow(nim[:, :, :3] / nim[:, :, :3].max(), interpolation=\"bilinear\")\n\n        return axim\n\n    def _annotate(self, ax, tf, source, label=\"\", label_fmt=None, label_fontsize=10):\n        ax.get_xaxis().set_visible(False)\n        ax.get_xaxis().set_ticks([])\n        ax.get_yaxis().set_visible(False)\n        ax.get_yaxis().set_ticks([])\n        cb = self._render_figure.colorbar(\n            ax.images[0], pad=0.0, fraction=0.05, drawedges=True\n        )\n        tf.vert_cbar(\n            ax=cb.ax,\n            label=label,\n            label_fmt=label_fmt,\n            label_fontsize=label_fontsize,\n            resolution=self.camera.resolution[0],\n            log_scale=source.log_field,\n        )\n\n    def _annotate_multi(self, ax, tf, source, label, label_fmt, label_fontsize=10):\n        ax.yaxis.set_label_position(\"right\")\n        ax.yaxis.tick_right()\n        tf.vert_cbar(\n            ax=ax,\n            label=label,\n            label_fmt=label_fmt,\n            label_fontsize=label_fontsize,\n            resolution=self.camera.resolution[0],\n            log_scale=source.log_field,\n            size=6,\n        )\n\n    def _validate(self):\n        r\"\"\"Validate the current state of the scene.\"\"\"\n\n        for source in self.sources.values():\n            source._validate()\n        return\n\n    def composite(self, camera=None):\n        r\"\"\"Create a composite image of the current scene.\n\n        First iterate over the opaque sources and set the ZBuffer.\n        Then iterate over the transparent sources, rendering from the value\n        of the zbuffer to the front of the box. Typically this function is\n        accessed through the .render() command.\n\n        Parameters\n        ----------\n        camera: :class:`Camera`, optional\n            If specified, use a specific :class:`Camera` to render the scene.\n\n        Returns\n        -------\n        im: :class:`ImageArray`\n            ImageArray instance of the current rendering image.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> sc = yt.create_scene(ds)\n        >>> # Modify camera, sources, etc...\n        >>> im = sc.composite()\n\n        \"\"\"\n        if camera is None:\n            camera = self.camera\n        empty = camera.lens.new_image(camera)\n        opaque = ZBuffer(empty, np.full(empty.shape[:2], np.inf))\n\n        for _, source in self.opaque_sources:\n            source.render(camera, zbuffer=opaque)\n            im = source.zbuffer.rgba\n\n        for _, source in self.transparent_sources:\n            im = source.render(camera, zbuffer=opaque)\n            opaque.rgba = im\n\n        # rotate image 180 degrees so orientation agrees with e.g.\n        # a PlotWindow plot\n        return np.rot90(im, k=2)\n\n    def add_camera(self, data_source=None, lens_type=\"plane-parallel\", auto=False):\n        r\"\"\"Add a new camera to the Scene.\n\n        The camera is defined by a position (the location of the camera\n        in the simulation domain,), a focus (the point at which the\n        camera is pointed), a width (the width of the snapshot that will\n        be taken, a resolution (the number of pixels in the image), and\n        a north_vector (the \"up\" direction in the resulting image). A\n        camera can use a variety of different Lens objects.\n\n        If the scene already has a camera associated with it, this function\n        will create a new camera and discard the old one.\n\n        Parameters\n        ----------\n        data_source: :class:`AMR3DData` or :class:`Dataset`, optional\n            This is the source to be rendered, which can be any arbitrary yt\n            data object or dataset.\n        lens_type: string, optional\n            This specifies the type of lens to use for rendering. Current\n            options are 'plane-parallel', 'perspective', and 'fisheye'. See\n            :class:`yt.visualization.volume_rendering.lens.Lens` for details.\n            Default: 'plane-parallel'\n        auto: boolean\n            If True, build smart defaults using the data source extent. This\n            can be time-consuming to iterate over the entire dataset to find\n            the positional bounds. Default: False\n\n        Examples\n        --------\n\n        In this example, the camera is set using defaults that are chosen\n        to be reasonable for the argument Dataset.\n\n        >>> import yt\n        >>> from yt.visualization.volume_rendering.api import Camera, Scene\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> sc = Scene()\n        >>> sc.add_camera()\n\n        Here, we set the camera properties manually:\n\n        >>> import yt\n        >>> from yt.visualization.volume_rendering.api import Camera, Scene\n        >>> sc = Scene()\n        >>> cam = sc.add_camera()\n        >>> cam.position = np.array([0.5, 0.5, -1.0])\n        >>> cam.focus = np.array([0.5, 0.5, 0.0])\n        >>> cam.north_vector = np.array([1.0, 0.0, 0.0])\n\n        Finally, we create a camera with a non-default lens:\n\n        >>> import yt\n        >>> from yt.visualization.volume_rendering.api import Camera\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n        >>> sc = Scene()\n        >>> sc.add_camera(ds, lens_type=\"perspective\")\n\n        \"\"\"\n        self._camera = Camera(self, data_source, lens_type, auto)\n        return self.camera\n\n    @property\n    def camera(self):\n        r\"\"\"The camera property.\n\n        This is the default camera that will be used when rendering. Can be set\n        manually, but Camera type will be checked for validity.\n        \"\"\"\n        return self._camera\n\n    @camera.setter\n    def camera(self, value):\n        value.width = self.arr(value.width)\n        value.focus = self.arr(value.focus)\n        value.position = self.arr(value.position)\n        self._camera = value\n\n    @camera.deleter\n    def camera(self):\n        del self._camera\n        self._camera = None\n\n    @property\n    def unit_registry(self):\n        ur = self._unit_registry\n        if ur is None:\n            ur = UnitRegistry()\n            # This will be updated when we add a volume source\n            ur.add(\"unitary\", 1.0, length)\n        self._unit_registry = ur\n        return self._unit_registry\n\n    @unit_registry.setter\n    def unit_registry(self, value):\n        self._unit_registry = value\n        if self.camera is not None:\n            self.camera.width = YTArray(\n                self.camera.width.in_units(\"unitary\"), registry=value\n            )\n            self.camera.focus = YTArray(\n                self.camera.focus.in_units(\"unitary\"), registry=value\n            )\n            self.camera.position = YTArray(\n                self.camera.position.in_units(\"unitary\"), registry=value\n            )\n\n    @unit_registry.deleter\n    def unit_registry(self):\n        del self._unit_registry\n        self._unit_registry = None\n\n    def set_camera(self, camera):\n        r\"\"\"\n\n        Set the camera to be used by this scene.\n\n        \"\"\"\n        self.camera = camera\n\n    def get_camera(self):\n        r\"\"\"\n\n        Get the camera currently used by this scene.\n\n        \"\"\"\n        return self.camera\n\n    def annotate_domain(self, ds, color=None):\n        r\"\"\"\n\n        Modifies this scene by drawing the edges of the computational domain.\n        This adds a new BoxSource to the scene corresponding to the domain\n        boundaries and returns the modified scene object.\n\n        Parameters\n        ----------\n\n        ds : :class:`yt.data_objects.static_output.Dataset`\n            This is the dataset object corresponding to the\n            simulation being rendered. Used to get the domain bounds.\n        color : array_like of shape (4,), optional\n            The RGBA value to use to draw the domain boundaries.\n            Default is black with an alpha of 1.0.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> sc = yt.create_scene(ds)\n        >>> sc.annotate_domain(ds)\n        >>> im = sc.render()\n\n        \"\"\"\n        box_source = BoxSource(ds.domain_left_edge, ds.domain_right_edge, color=color)\n        self.add_source(box_source)\n        return self\n\n    def annotate_grids(\n        self, data_source, alpha=0.3, cmap=None, min_level=None, max_level=None\n    ):\n        r\"\"\"\n\n        Modifies this scene by drawing the edges of the AMR grids.\n        This adds a new GridSource to the scene that represents the AMR grid\n        and returns the resulting Scene object.\n\n        Parameters\n        ----------\n\n        data_source: :class:`~yt.data_objects.api.DataContainer`\n            The data container that will be used to identify grids to draw.\n        alpha : float\n            The opacity of the grids to draw.\n        cmap : color map name\n            The color map to use to map resolution levels to color.\n        min_level : int, optional\n            Minimum level to draw\n        max_level : int, optional\n            Maximum level to draw\n\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> sc = yt.create_scene(ds)\n        >>> sc.annotate_grids(ds.all_data())\n        >>> im = sc.render()\n\n        \"\"\"\n        if cmap is None:\n            cmap = ytcfg.get(\"yt\", \"default_colormap\")\n        grids = GridSource(\n            data_source,\n            alpha=alpha,\n            cmap=cmap,\n            min_level=min_level,\n            max_level=max_level,\n        )\n        self.add_source(grids)\n        return self\n\n    def annotate_mesh_lines(self, color=None, alpha=1.0):\n        \"\"\"\n\n        Modifies this Scene by drawing the mesh line boundaries\n        on all MeshSources.\n\n        Parameters\n        ----------\n        color : array_like of shape (4,), optional\n            The RGBA value to use to draw the mesh lines.\n            Default is black with an alpha of 1.0.\n        alpha : float, optional\n            The opacity of the mesh lines. Default is 255 (solid).\n\n        \"\"\"\n        for _, source in self.opaque_sources:\n            if isinstance(source, MeshSource):\n                source.annotate_mesh_lines(color=color, alpha=alpha)\n        return self\n\n    def annotate_axes(self, colors=None, alpha=1.0, *, thickness=1):\n        r\"\"\"\n\n        Modifies this scene by drawing the coordinate axes.\n        This adds a new CoordinateVectorSource to the scene\n        and returns the modified scene object.\n\n        Parameters\n        ----------\n        colors: array-like of shape (3,4), optional\n            The RGBA values to use to draw the x, y, and z vectors. The default\n            is  [[1, 0, 0, alpha], [0, 1, 0, alpha], [0, 0, 1, alpha]] where\n            ``alpha`` is set by the parameter below. If ``colors`` is set then\n            ``alpha`` is ignored.\n        alpha : float, optional\n            The opacity of the vectors.\n        thickness : int, optional\n            The line thickness\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> sc = yt.create_scene(ds)\n        >>> sc.annotate_axes(alpha=0.5)\n        >>> im = sc.render()\n\n        \"\"\"\n        coords = CoordinateVectorSource(colors, alpha, thickness=thickness)\n        self.add_source(coords)\n        return self\n\n    def show(self, sigma_clip=None):\n        r\"\"\"This will send the most recently rendered image to the IPython\n        notebook.\n\n        If yt is being run from within an IPython session, and it is able to\n        determine this, this function will send the current image of this Scene\n        to the notebook for display. If there is no current image, it will\n        run the render() method on this Scene before sending the result to the\n        notebook.\n\n        If yt can't determine if it's inside an IPython session, this will raise\n        YTNotInsideNotebook.\n\n        Examples\n        --------\n\n        >>> import yt\n        >>> ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n\n        >>> sc = yt.create_scene(ds)\n        >>> sc.show()\n\n        \"\"\"\n        if IS_IPYTHON:\n            from IPython.display import display\n\n            self._sigma_clip = sigma_clip\n            display(self)\n        else:\n            raise YTNotInsideNotebook\n\n    _arr = None\n\n    @property\n    def arr(self):\n        \"\"\"Converts an array into a :class:`yt.units.yt_array.YTArray`\n\n        The returned YTArray will be dimensionless by default, but can be\n        cast to arbitrary units using the ``units`` keyword argument.\n\n        Parameters\n        ----------\n\n        input_array : Iterable\n            A tuple, list, or array to attach units to\n        units: String unit specification, unit symbol object, or astropy units object\n            The units of the array. Powers must be specified using python syntax\n            (cm**3, not cm^3).\n        dtype : string or NumPy dtype object\n            The dtype of the returned array data\n\n        Examples\n        --------\n\n        >>> a = sc.arr([1, 2, 3], \"cm\")\n        >>> b = sc.arr([4, 5, 6], \"m\")\n        >>> a + b\n        YTArray([ 401.,  502.,  603.]) cm\n        >>> b + a\n        YTArray([ 4.01,  5.02,  6.03]) m\n\n        Arrays returned by this function know about the scene's unit system\n\n        >>> a = sc.arr(np.ones(5), \"unitary\")\n        >>> a.in_units(\"Mpc\")\n        YTArray([ 1.00010449,  1.00010449,  1.00010449,  1.00010449,\n                 1.00010449]) Mpc\n\n        \"\"\"\n        if self._arr is not None:\n            return self._arr\n        self._arr = functools.partial(YTArray, registry=self.unit_registry)\n        return self._arr\n\n    _quan = None\n\n    @property\n    def quan(self):\n        \"\"\"Converts an scalar into a :class:`yt.units.yt_array.YTQuantity`\n\n        The returned YTQuantity will be dimensionless by default, but can be\n        cast to arbitrary units using the ``units`` keyword argument.\n\n        Parameters\n        ----------\n\n        input_scalar : an integer or floating point scalar\n            The scalar to attach units to\n        units : String unit specification, unit symbol object, or astropy\n            units\n        input_units : deprecated in favor of 'units'\n            The units of the quantity. Powers must be specified using python\n            syntax (cm**3, not cm^3).\n        dtype : string or NumPy dtype object\n            The dtype of the array data.\n\n        Examples\n        --------\n\n        >>> a = sc.quan(1, \"cm\")\n        >>> b = sc.quan(2, \"m\")\n        >>> a + b\n        201.0 cm\n        >>> b + a\n        2.01 m\n\n        Quantities created this way automatically know about the unit system\n        of the scene\n\n        >>> a = ds.quan(5, \"unitary\")\n        >>> a.in_cgs()\n        1.543e+25 cm\n\n        \"\"\"\n        if self._quan is not None:\n            return self._quan\n        self._quan = functools.partial(YTQuantity, registry=self.unit_registry)\n        return self._quan\n\n    def _repr_png_(self):\n        if self._last_render is None:\n            self.render()\n        png = self._last_render.write_png(\n            filename=None, sigma_clip=self._sigma_clip, background=\"black\"\n        )\n        self._sigma_clip = None\n        return png\n\n    def __repr__(self):\n        disp = \"<Scene Object>:\"\n        disp += \"\\nSources: \\n\"\n        for k, v in self.sources.items():\n            disp += f\"    {k}: {v}\\n\"\n        disp += \"Camera: \\n\"\n        disp += f\"    {self.camera}\"\n        return disp\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/__init__.py",
    "content": ""
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_camera_attributes.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal, assert_equal\n\nimport yt.units as u\nfrom yt.testing import fake_random_ds\nfrom yt.visualization.volume_rendering.api import Scene\n\nvalid_lens_types = [\n    \"plane-parallel\",\n    \"perspective\",\n    \"stereo-perspective\",\n    \"fisheye\",\n    \"spherical\",\n    \"stereo-spherical\",\n]\n\n\ndef test_scene_and_camera_attributes():\n    ds = fake_random_ds(64, length_unit=2, bbox=np.array([[-1, 1], [-1, 1], [-1, 1]]))\n    sc = Scene()\n    cam = sc.add_camera(ds)\n\n    # test that initial values are correct in code units\n    assert_equal(cam.width, ds.arr([3, 3, 3], \"code_length\"))\n    assert_equal(cam.position, ds.arr([1, 1, 1], \"code_length\"))\n    assert_equal(cam.focus, ds.arr([0, 0, 0], \"code_length\"))\n\n    # test setting the attributes in various ways\n\n    attribute_values = [\n        (\n            1,\n            ds.arr([2, 2, 2], \"code_length\"),\n        ),\n        (\n            [1],\n            ds.arr([2, 2, 2], \"code_length\"),\n        ),\n        (\n            [1, 2],\n            RuntimeError,\n        ),\n        (\n            [1, 1, 1],\n            ds.arr([2, 2, 2], \"code_length\"),\n        ),\n        (\n            (1, \"code_length\"),\n            ds.arr([1, 1, 1], \"code_length\"),\n        ),\n        (\n            ((1, \"code_length\"), (1, \"code_length\")),\n            RuntimeError,\n        ),\n        (\n            ((1, \"cm\"), (2, \"cm\"), (3, \"cm\")),\n            ds.arr([0.5, 1, 1.5], \"code_length\"),\n        ),\n        (\n            2 * u.cm,\n            ds.arr([1, 1, 1], \"code_length\"),\n        ),\n        (\n            ds.arr(2, \"cm\"),\n            ds.arr([1, 1, 1], \"code_length\"),\n        ),\n        (\n            [2 * u.cm],\n            ds.arr([1, 1, 1], \"code_length\"),\n        ),\n        (\n            [1, 2, 3] * u.cm,\n            ds.arr([0.5, 1, 1.5], \"code_length\"),\n        ),\n        (\n            [1, 2] * u.cm,\n            RuntimeError,\n        ),\n        (\n            [u.cm * w for w in [1, 2, 3]],\n            ds.arr([0.5, 1, 1.5], \"code_length\"),\n        ),\n    ]\n\n    # define default values to avoid accidentally setting focus = position\n    default_values = {\n        \"focus\": [0, 0, 0],\n        \"position\": [4, 4, 4],\n        \"width\": [1, 1, 1],\n    }\n    attribute_list = list(default_values.keys())\n\n    for attribute in attribute_list:\n        for other_attribute in [a for a in attribute_list if a != attribute]:\n            setattr(cam, other_attribute, default_values[other_attribute])\n        for attribute_value, expected_result in attribute_values:\n            try:\n                # test properties\n                setattr(cam, attribute, attribute_value)\n                assert_almost_equal(getattr(cam, attribute), expected_result)\n            except RuntimeError:\n                assert expected_result is RuntimeError\n\n            try:\n                # test setters/getters\n                getattr(cam, f\"set_{attribute}\")(attribute_value)\n                assert_almost_equal(getattr(cam, f\"get_{attribute}\")(), expected_result)\n            except RuntimeError:\n                assert expected_result is RuntimeError\n\n    resolution_values = (\n        (\n            512,\n            (512, 512),\n        ),\n        (\n            (512, 512),\n            (512, 512),\n        ),\n        (\n            (256, 512),\n            (256, 512),\n        ),\n        ((256, 256, 256), RuntimeError),\n    )\n\n    for resolution_value, expected_result in resolution_values:\n        try:\n            # test properties\n            cam.resolution = resolution_value\n            assert_equal(cam.resolution, expected_result)\n        except RuntimeError:\n            assert expected_result is RuntimeError\n\n        try:\n            # test setters/getters\n            cam.set_resolution(resolution_value)\n            assert_equal(cam.get_resolution(), expected_result)\n        except RuntimeError:\n            assert expected_result is RuntimeError\n\n    for lens_type in valid_lens_types:\n        cam.set_lens(lens_type)\n\n    # See issue #1287\n    cam.focus = [0, 0, 0]\n    cam_pos = [1, 0, 0]\n    north_vector = [0, 1, 0]\n    cam.set_position(cam_pos, north_vector)\n    cam_pos = [0, 1, 0]\n    north_vector = [0, 0, 1]\n    cam.set_position(cam_pos, north_vector)\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_composite.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport numpy as np\n\nfrom yt.data_objects.api import ImageArray\nfrom yt.testing import fake_random_ds\nfrom yt.visualization.volume_rendering.api import (\n    BoxSource,\n    LineSource,\n    Scene,\n    create_volume_source,\n)\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass CompositeVRTest(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n\n    def setUp(self):\n        np.random.seed(0)\n        if self.use_tmpdir:\n            self.curdir = os.getcwd()\n            # Perform I/O in safe place instead of yt main dir\n            self.tmpdir = tempfile.mkdtemp()\n            os.chdir(self.tmpdir)\n        else:\n            self.curdir, self.tmpdir = None, None\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            os.chdir(self.curdir)\n            shutil.rmtree(self.tmpdir)\n\n    def test_composite_vr(self):\n        ds = fake_random_ds(64)\n        dd = ds.sphere(ds.domain_center, 0.45 * ds.domain_width[0])\n\n        # Trigger creation of index\n        ds.index\n        ds.field_info[ds.field_list[0]].take_log = False\n\n        sc = Scene()\n        cam = sc.add_camera(ds)\n        cam.resolution = (512, 512)\n        vr = create_volume_source(dd, field=ds.field_list[0])\n        vr.transfer_function.clear()\n        vr.transfer_function.grey_opacity = True\n        vr.transfer_function.map_to_colormap(0.0, 1.0, scale=3.0, colormap=\"Reds\")\n        sc.add_source(vr)\n\n        cam.set_width(1.8 * ds.domain_width)\n        cam.lens.setup_box_properties(cam)\n\n        # DRAW SOME LINES\n        npoints = 100\n        vertices = np.random.random([npoints, 2, 3])\n        colors = np.random.random([npoints, 4])\n        colors[:, 3] = 0.10\n\n        box_source = BoxSource(\n            ds.domain_left_edge, ds.domain_right_edge, color=[1.0, 1.0, 1.0, 1.0]\n        )\n        sc.add_source(box_source)\n\n        LE = ds.domain_left_edge + np.array([0.1, 0.0, 0.3]) * ds.domain_left_edge.uq\n        RE = ds.domain_right_edge - np.array([0.1, 0.2, 0.3]) * ds.domain_left_edge.uq\n        color = np.array([0.0, 1.0, 0.0, 0.10])\n        box_source = BoxSource(LE, RE, color=color)\n        sc.add_source(box_source)\n\n        line_source = LineSource(vertices, colors)\n        sc.add_source(line_source)\n\n        im = sc.render()\n        im = ImageArray(im.d)\n        im.write_png(\"composite.png\")\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_lenses.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport numpy as np\n\nfrom yt.testing import fake_random_ds\nfrom yt.visualization.volume_rendering.api import Scene, create_volume_source\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass LensTest(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n\n    def setUp(self):\n        if self.use_tmpdir:\n            self.curdir = os.getcwd()\n            # Perform I/O in safe place instead of yt main dir\n            self.tmpdir = tempfile.mkdtemp()\n            os.chdir(self.tmpdir)\n        else:\n            self.curdir, self.tmpdir = None, None\n\n        self.field = (\"gas\", \"density\")\n        self.ds = fake_random_ds(32, fields=(self.field,), units=(\"g/cm**3\",))\n        self.ds.index\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            os.chdir(self.curdir)\n            shutil.rmtree(self.tmpdir)\n\n    def test_perspective_lens(self):\n        sc = Scene()\n        cam = sc.add_camera(self.ds, lens_type=\"perspective\")\n        cam.position = self.ds.arr(np.array([1.0, 1.0, 1.0]), \"code_length\")\n        vol = create_volume_source(self.ds, field=self.field)\n        tf = vol.transfer_function\n        tf.grey_opacity = True\n        sc.add_source(vol)\n        sc.save(f\"test_perspective_{self.field[1]}.png\", sigma_clip=6.0)\n\n    def test_stereoperspective_lens(self):\n        sc = Scene()\n        cam = sc.add_camera(self.ds, lens_type=\"stereo-perspective\")\n        cam.resolution = [256, 128]\n        cam.position = self.ds.arr(np.array([0.7, 0.7, 0.7]), \"code_length\")\n        vol = create_volume_source(self.ds, field=self.field)\n        tf = vol.transfer_function\n        tf.grey_opacity = True\n        sc.add_source(vol)\n        sc.save(f\"test_stereoperspective_{self.field[1]}.png\", sigma_clip=6.0)\n\n    def test_fisheye_lens(self):\n        dd = self.ds.sphere(self.ds.domain_center, self.ds.domain_width[0] / 10)\n        sc = Scene()\n        cam = sc.add_camera(dd, lens_type=\"fisheye\")\n        cam.lens.fov = 360.0\n        cam.set_width(self.ds.domain_width)\n        v, c = self.ds.find_max((\"gas\", \"density\"))\n        cam.set_position(c - 0.0005 * self.ds.domain_width)\n        vol = create_volume_source(dd, field=self.field)\n        tf = vol.transfer_function\n        tf.grey_opacity = True\n        sc.add_source(vol)\n        sc.save(f\"test_fisheye_{self.field[1]}.png\", sigma_clip=6.0)\n\n    def test_plane_lens(self):\n        dd = self.ds.sphere(self.ds.domain_center, self.ds.domain_width[0] / 10)\n        sc = Scene()\n        cam = sc.add_camera(dd, lens_type=\"plane-parallel\")\n        cam.set_width(self.ds.domain_width * 1e-2)\n        v, c = self.ds.find_max((\"gas\", \"density\"))\n        vol = create_volume_source(dd, field=self.field)\n        tf = vol.transfer_function\n        tf.grey_opacity = True\n        sc.add_source(vol)\n        sc.save(f\"test_plane_{self.field[1]}.png\", sigma_clip=6.0)\n\n    def test_spherical_lens(self):\n        sc = Scene()\n        cam = sc.add_camera(self.ds, lens_type=\"spherical\")\n        cam.resolution = [256, 128]\n        cam.position = self.ds.arr(np.array([0.6, 0.5, 0.5]), \"code_length\")\n        vol = create_volume_source(self.ds, field=self.field)\n        tf = vol.transfer_function\n        tf.grey_opacity = True\n        sc.add_source(vol)\n        sc.save(f\"test_spherical_{self.field[1]}.png\", sigma_clip=6.0)\n\n    def test_stereospherical_lens(self):\n        w = (self.ds.domain_width).in_units(\"code_length\")\n        w = self.ds.arr(w, \"code_length\")\n        sc = Scene()\n        cam = sc.add_camera(self.ds, lens_type=\"stereo-spherical\")\n        cam.resolution = [256, 256]\n        cam.position = self.ds.arr(np.array([0.6, 0.5, 0.5]), \"code_length\")\n        vol = create_volume_source(self.ds, field=self.field)\n        tf = vol.transfer_function\n        tf.grey_opacity = True\n        sc.add_source(vol)\n        sc.save(f\"test_stereospherical_{self.field[1]}.png\", sigma_clip=6.0)\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_mesh_render.py",
    "content": "import matplotlib.pyplot as plt\nimport pytest\n\nfrom yt.config import ytcfg\nfrom yt.testing import (\n    fake_hexahedral_ds,\n    fake_tetrahedral_ds,\n    requires_file,\n    requires_module,\n)\nfrom yt.utilities.answer_testing.framework import data_dir_load, data_dir_load_v2\nfrom yt.visualization.volume_rendering.api import MeshSource, Scene, create_scene\nfrom yt.visualization.volume_rendering.render_source import set_raytracing_engine\n\n\n@pytest.fixture\n@requires_module(\"pyembree\")\ndef with_pyembree_ray_tracing_engine():\n    old = ytcfg[\"yt\", \"ray_tracing_engine\"]\n\n    # the @requires_module decorator only guards against pyembree not being\n    # available for import, but it might not be installed properly regardless\n    # so we need to be extra careful not to run tests with the default engine\n    try:\n        set_raytracing_engine(\"embree\")\n    except UserWarning as exc:\n        pytest.skip(str(exc))\n    else:\n        if ytcfg[\"yt\", \"ray_tracing_engine\"] != \"embree\":\n            pytest.skip(\"Error while setting embree raytracing engine\")\n\n    yield\n\n    set_raytracing_engine(old)\n\n\n@pytest.fixture\ndef with_default_ray_tracing_engine():\n    old = ytcfg[\"yt\", \"ray_tracing_engine\"]\n    set_raytracing_engine(\"yt\")\n    yield\n    set_raytracing_engine(old)\n\n\n@pytest.mark.usefixtures(\"with_default_ray_tracing_engine\")\nclass TestMeshRenderDefaultEngine:\n    @classmethod\n    def setup_class(cls):\n        cls.images = {}\n\n        cls.ds_t = fake_tetrahedral_ds()\n        for field in cls.ds_t.field_list:\n            if field[0] == \"all\":\n                continue\n            sc = Scene()\n            sc.add_source(MeshSource(cls.ds_t, field))\n            sc.add_camera()\n            im = sc.render()\n            cls.images[\"tetrahedral_\" + \"_\".join(field)] = im\n\n        cls.ds_h = fake_hexahedral_ds()\n        for field in cls.ds_t.field_list:\n            if field[0] == \"all\":\n                continue\n            sc = Scene()\n            sc.add_source(MeshSource(cls.ds_h, field))\n            sc.add_camera()\n            im = sc.render()\n            cls.images[\"hexahedral_\" + \"_\".join(field)] = im\n\n    @pytest.mark.parametrize(\"kind\", [\"tetrahedral\", \"hexahedral\"])\n    @pytest.mark.parametrize(\"fname\", [\"elem\", \"test\"])\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_default_engine(self, kind, fname):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[f\"{kind}_connect1_{fname}\"])\n        return fig\n\n\n@pytest.mark.usefixtures(\"with_pyembree_ray_tracing_engine\")\nclass TestMeshRenderPyembreeEngine:\n    @classmethod\n    def setup_class(cls):\n        cls.images = {}\n\n        cls.ds_t = fake_tetrahedral_ds()\n        for field in cls.ds_t.field_list:\n            if field[0] == \"all\":\n                continue\n            sc = Scene()\n            sc.add_source(MeshSource(cls.ds_t, field))\n            sc.add_camera()\n            im = sc.render()\n            cls.images[\"tetrahedral_\" + \"_\".join(field)] = im\n\n        cls.ds_h = fake_hexahedral_ds()\n        for field in cls.ds_t.field_list:\n            if field[0] == \"all\":\n                continue\n            sc = Scene()\n            sc.add_source(MeshSource(cls.ds_h, field))\n            sc.add_camera()\n            im = sc.render()\n            cls.images[\"hexahedral_\" + \"_\".join(field)] = im\n\n    @pytest.mark.parametrize(\"kind\", [\"tetrahedral\", \"hexahedral\"])\n    @pytest.mark.parametrize(\"fname\", [\"elem\", \"test\"])\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_pyembree_engine(self, kind, fname):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[f\"{kind}_connect1_{fname}\"])\n        return fig\n\n\nhex8 = \"MOOSE_sample_data/out.e-s010\"\n\n\n@pytest.mark.usefixtures(\"with_default_ray_tracing_engine\")\nclass TestHex8DefaultEngine:\n    @requires_file(hex8)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(hex8, step=-1)\n        cls.images = {}\n        for field in [(\"connect1\", \"diffused\"), (\"connect2\", \"convected\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_default_engine_hex8_connect1_diffused(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect1_diffused\"])\n        return fig\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_default_engine_hex8_connect2_convected(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect2_convected\"])\n        return fig\n\n\n@pytest.mark.usefixtures(\"with_pyembree_ray_tracing_engine\")\nclass TestHex8PyembreeEngine:\n    @requires_file(hex8)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(hex8, step=-1)\n        cls.images = {}\n        for field in [(\"connect1\", \"diffused\"), (\"connect2\", \"convected\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_pyembree_engine_hex8_connect1_diffused(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect1_diffused\"])\n        return fig\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_pyembree_engine_hex8_connect2_convected(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect2_convected\"])\n        return fig\n\n\ntet4 = \"MOOSE_sample_data/high_order_elems_tet4_refine_out.e\"\n\n\n@pytest.mark.usefixtures(\"with_default_ray_tracing_engine\")\nclass TestTet4DefaultEngine:\n    @requires_file(tet4)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(tet4, step=-1)\n        cls.images = {}\n        for field in [(\"connect1\", \"u\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_default_engine_tet4(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect1_u\"])\n        return fig\n\n\n@pytest.mark.usefixtures(\"with_pyembree_ray_tracing_engine\")\nclass TestTet4PyembreeEngine:\n    @requires_file(tet4)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(tet4, step=-1)\n        cls.images = {}\n        for field in [(\"connect1\", \"u\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_pyembree_engine_tet4(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect1_u\"])\n        return fig\n\n\nhex20 = \"MOOSE_sample_data/mps_out.e\"\n\n\n@pytest.mark.usefixtures(\"with_default_ray_tracing_engine\")\nclass TestHex20DefaultEngine:\n    @requires_file(hex20)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(hex20, step=-1)\n        cls.images = {}\n        for field in [(\"connect2\", \"temp\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_default_engine_hex20(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect2_temp\"])\n        return fig\n\n\n@pytest.mark.usefixtures(\"with_pyembree_ray_tracing_engine\")\nclass TestHex20PyembreeEngine:\n    @requires_file(hex20)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(hex20, step=-1)\n        cls.images = {}\n        for field in [(\"connect2\", \"temp\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_pyembree_engine_hex20(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect2_temp\"])\n        return fig\n\n\nwedge6 = \"MOOSE_sample_data/wedge_out.e\"\n\n\n@pytest.mark.usefixtures(\"with_default_ray_tracing_engine\")\nclass TestWedge6DefaultEngine:\n    @requires_file(wedge6)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(wedge6, step=-1)\n        cls.images = {}\n        for field in [(\"connect1\", \"diffused\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_default_engine_wedge6(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect1_diffused\"])\n        return fig\n\n\n@pytest.mark.usefixtures(\"with_pyembree_ray_tracing_engine\")\nclass TestWedge6PyembreeEngine:\n    @requires_file(wedge6)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(wedge6, step=-1)\n        cls.images = {}\n        for field in [(\"connect1\", \"diffused\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_pyembree_engine_wedge6(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect1_diffused\"])\n        return fig\n\n\ntet10 = \"SecondOrderTets/tet10_unstructured_out.e\"\n\n\n@pytest.mark.usefixtures(\"with_default_ray_tracing_engine\")\nclass TestTet10DefaultEngine:\n    @requires_file(tet10)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(tet10, step=-1)\n        cls.images = {}\n        for field in [(\"connect1\", \"uz\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_default_engine_tet10(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect1_uz\"])\n        return fig\n\n\n@pytest.mark.usefixtures(\"with_pyembree_ray_tracing_engine\")\nclass TestTet10PyembreeEngine:\n    @requires_file(tet10)\n    @classmethod\n    def setup_class(cls):\n        cls.ds = data_dir_load_v2(tet10, step=-1)\n        cls.images = {}\n        for field in [(\"connect1\", \"uz\")]:\n            sc = create_scene(cls.ds, field)\n            im = sc.render()\n            cls.images[\"_\".join(field)] = im\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_mesh_render_pyembree_engine_tet10(self):\n        fig, ax = plt.subplots()\n        ax.imshow(self.images[\"connect1_uz\"])\n        return fig\n\n\n@requires_file(hex8)\n@pytest.mark.usefixtures(\"with_default_ray_tracing_engine\")\n@pytest.mark.mpl_image_compare\ndef test_perspective_mesh_render_default():\n    ds = data_dir_load(hex8)\n    sc = create_scene(ds, (\"connect2\", \"diffused\"))\n    cam = sc.add_camera(ds, lens_type=\"perspective\")\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam_pos = ds.arr([-4.5, 4.5, -4.5], \"code_length\")\n    north_vector = ds.arr([0.0, -1.0, -1.0], \"dimensionless\")\n    cam.set_position(cam_pos, north_vector)\n    cam.resolution = (800, 800)\n    im = sc.render()\n\n    fig, ax = plt.subplots()\n    ax.imshow(im)\n    return fig\n\n\n@requires_file(hex8)\n@pytest.mark.usefixtures(\"with_pyembree_ray_tracing_engine\")\n@pytest.mark.mpl_image_compare\ndef test_perspective_mesh_render_pyembree():\n    ds = data_dir_load(hex8)\n    sc = create_scene(ds, (\"connect2\", \"diffused\"))\n    cam = sc.add_camera(ds, lens_type=\"perspective\")\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam_pos = ds.arr([-4.5, 4.5, -4.5], \"code_length\")\n    north_vector = ds.arr([0.0, -1.0, -1.0], \"dimensionless\")\n    cam.set_position(cam_pos, north_vector)\n    cam.resolution = (800, 800)\n    im = sc.render()\n\n    fig, ax = plt.subplots()\n    ax.imshow(im)\n    return fig\n\n\n@requires_file(hex8)\n@pytest.mark.usefixtures(\"with_default_ray_tracing_engine\")\n@pytest.mark.mpl_image_compare\ndef test_composite_mesh_render_default():\n    ds = data_dir_load(hex8)\n    sc = Scene()\n    cam = sc.add_camera(ds)\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam.set_position(\n        ds.arr([-3.0, 3.0, -3.0], \"code_length\"),\n        ds.arr([0.0, -1.0, 0.0], \"dimensionless\"),\n    )\n    cam.set_width = ds.arr([8.0, 8.0, 8.0], \"code_length\")\n    cam.resolution = (800, 800)\n    ms1 = MeshSource(ds, (\"connect1\", \"diffused\"))\n    ms2 = MeshSource(ds, (\"connect2\", \"diffused\"))\n    sc.add_source(ms1)\n    sc.add_source(ms2)\n    im = sc.render()\n\n    fig, ax = plt.subplots()\n    ax.imshow(im)\n    return fig\n\n\n@requires_file(hex8)\n@pytest.mark.usefixtures(\"with_pyembree_ray_tracing_engine\")\n@pytest.mark.mpl_image_compare\ndef test_composite_mesh_render_pyembree():\n    ds = data_dir_load(hex8)\n    sc = Scene()\n    cam = sc.add_camera(ds)\n    cam.focus = ds.arr([0.0, 0.0, 0.0], \"code_length\")\n    cam.set_position(\n        ds.arr([-3.0, 3.0, -3.0], \"code_length\"),\n        ds.arr([0.0, -1.0, 0.0], \"dimensionless\"),\n    )\n    cam.set_width = ds.arr([8.0, 8.0, 8.0], \"code_length\")\n    cam.resolution = (800, 800)\n    ms1 = MeshSource(ds, (\"connect1\", \"diffused\"))\n    ms2 = MeshSource(ds, (\"connect2\", \"diffused\"))\n    sc.add_source(ms1)\n    sc.add_source(ms2)\n    im = sc.render()\n\n    fig, ax = plt.subplots()\n    ax.imshow(im)\n    return fig\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_off_axis_SPH.py",
    "content": "import numpy as np\nfrom numpy.testing import assert_almost_equal\n\nfrom yt.testing import fake_sph_orientation_ds, requires_module\nfrom yt.utilities.lib.pixelization_routines import pixelize_sph_kernel_projection\nfrom yt.utilities.on_demand_imports import _scipy\nfrom yt.visualization.volume_rendering import off_axis_projection as OffAP\n\nspatial = _scipy.spatial\nndimage = _scipy.ndimage\n\n\ndef test_no_rotation():\n    \"\"\"Determines if a projection processed through\n    off_axis_projection with no rotation will give the same\n    image buffer if processed directly through\n    pixelize_sph_kernel_projection\n    \"\"\"\n    normal_vector = [0.0, 0.0, 1.0]\n    resolution = (64, 64)\n    ds = fake_sph_orientation_ds()\n    ad = ds.all_data()\n    left_edge = ds.domain_left_edge\n    right_edge = ds.domain_right_edge\n    center = (left_edge + right_edge) / 2\n    width = right_edge - left_edge\n    px = ad[\"all\", \"particle_position_x\"]\n    py = ad[\"all\", \"particle_position_y\"]\n    pz = ad[\"all\", \"particle_position_y\"]\n    hsml = ad[\"all\", \"smoothing_length\"]\n    quantity_to_smooth = ad[\"gas\", \"density\"]\n    density = ad[\"io\", \"density\"]\n    mass = ad[\"io\", \"particle_mass\"]\n    bounds = [-4, 4, -4, 4, -4, 4]\n\n    buf2 = np.zeros(resolution)\n    mask = np.ones_like(buf2, dtype=\"uint8\")\n    buf1 = OffAP.off_axis_projection(\n        ds, center, normal_vector, width, resolution, (\"gas\", \"density\")\n    )\n    pixelize_sph_kernel_projection(\n        buf2, mask, px, py, pz, hsml, mass, density, quantity_to_smooth, bounds\n    )\n    assert_almost_equal(buf1.ndarray_view(), buf2)\n\n\n@requires_module(\"scipy\")\ndef test_basic_rotation_1():\n    \"\"\"All particles on Z-axis should now be on the negative Y-Axis\n    fake_sph_orientation has three z-axis particles,\n    so there should be three y-axis particles after rotation\n    (0, 0, 1) -> (0, -1)\n    (0, 0, 2) -> (0, -2)\n    (0, 0, 3) -> (0, -3)\n    In addition, we should find a local maxima at (0, 0) due to:\n    (0, 0, 0) -> (0, 0)\n    (0, 1, 0) -> (0, 0)\n    (0, 2, 0) -> (0, 0)\n    and the one particle on the x-axis should not change its position:\n    (1, 0, 0) -> (1, 0)\n    \"\"\"\n    expected_maxima = ([0.0, 0.0, 0.0, 0.0, 1.0], [0.0, -1.0, -2.0, -3.0, 0.0])\n    normal_vector = [0.0, 1.0, 0.0]\n    north_vector = [0.0, 0.0, -1.0]\n    resolution = (64, 64)\n    ds = fake_sph_orientation_ds()\n    left_edge = ds.domain_left_edge\n    right_edge = ds.domain_right_edge\n    center = (left_edge + right_edge) / 2\n    width = right_edge - left_edge\n    buf1 = OffAP.off_axis_projection(\n        ds,\n        center,\n        normal_vector,\n        width,\n        resolution,\n        (\"gas\", \"density\"),\n        north_vector=north_vector,\n    )\n    find_compare_maxima(expected_maxima, buf1, resolution, width)\n\n\n@requires_module(\"scipy\")\ndef test_basic_rotation_2():\n    \"\"\"Rotation of x-axis onto z-axis.\n    All particles on z-axis should now be on the negative x-Axis fake_sph_orientation\n    has three z-axis particles, so there should be three x-axis particles after rotation\n    (0, 0, 1) -> (-1, 0)\n    (0, 0, 2) -> (-2, 0)\n    (0, 0, 3) -> (-3, 0)\n    In addition, we should find a local maxima at (0, 0) due to:\n    (0, 0, 0) -> (0, 0)\n    (1, 0, 0) -> (0, 0)\n    and the two particles on the y-axis should not change its position:\n    (0, 1, 0) -> (0, 1)\n    (0, 2, 0) -> (0, 2)\n    \"\"\"\n    expected_maxima = (\n        [-1.0, -2.0, -3.0, 0.0, 0.0, 0.0],\n        [0.0, 0.0, 0.0, 0.0, 1.0, 2.0],\n    )\n    normal_vector = [1.0, 0.0, 0.0]\n    north_vector = [0.0, 1.0, 0.0]\n    resolution = (64, 64)\n    ds = fake_sph_orientation_ds()\n    left_edge = ds.domain_left_edge\n    right_edge = ds.domain_right_edge\n    center = (left_edge + right_edge) / 2\n    width = right_edge - left_edge\n    buf1 = OffAP.off_axis_projection(\n        ds,\n        center,\n        normal_vector,\n        width,\n        resolution,\n        (\"gas\", \"density\"),\n        north_vector=north_vector,\n    )\n    find_compare_maxima(expected_maxima, buf1, resolution, width)\n\n\n@requires_module(\"scipy\")\ndef test_basic_rotation_3():\n    \"\"\"Rotation of z-axis onto negative z-axis.\n    All fake particles on z-axis should now be of the negative z-Axis.\n    fake_sph_orientation has three z-axis particles,\n    so we should have a local maxima at (0, 0)\n    (0, 0, 1) -> (0, 0)\n    (0, 0, 2) -> (0, 0)\n    (0, 0, 3) -> (0, 0)\n    In addition, (0, 0, 0) should also contribute to the local maxima at (0, 0):\n    (0, 0, 0) -> (0, 0)\n    x-axis particles should be rotated as such:\n    (1, 0, 0) -> (0, -1)\n    and same goes for y-axis particles:\n    (0, 1, 0) -> (-1, 0)\n    (0, 2, 0) -> (-2, 0)\n    \"\"\"\n    expected_maxima = ([0.0, 0.0, -1.0, -2.0], [0.0, -1.0, 0.0, 0.0])\n    normal_vector = [0.0, 0.0, -1.0]\n    resolution = (64, 64)\n    ds = fake_sph_orientation_ds()\n    left_edge = ds.domain_left_edge\n    right_edge = ds.domain_right_edge\n    center = (left_edge + right_edge) / 2\n    width = right_edge - left_edge\n    buf1 = OffAP.off_axis_projection(\n        ds, center, normal_vector, width, resolution, (\"gas\", \"density\")\n    )\n    find_compare_maxima(expected_maxima, buf1, resolution, width)\n\n\n@requires_module(\"scipy\")\ndef test_basic_rotation_4():\n    \"\"\"Rotation of x-axis to z-axis and original z-axis to y-axis with the use\n    of the north_vector. All fake particles on z-axis should now be on the\n    y-Axis.  All fake particles on the x-axis should now be on the z-axis, and\n    all fake particles on the y-axis should now be on the x-axis.\n\n    (0, 0, 1) -> (0, 1)\n    (0, 0, 2) -> (0, 2)\n    (0, 0, 3) -> (0, 3)\n    In addition, (0, 0, 0) should contribute to the local maxima at (0, 0):\n    (0, 0, 0) -> (0, 0)\n    x-axis particles should be rotated and contribute to the local maxima at (0, 0):\n    (1, 0, 0) -> (0, 0)\n    and the y-axis particles shift into the positive x direction:\n    (0, 1, 0) -> (1, 0)\n    (0, 2, 0) -> (2, 0)\n    \"\"\"\n    expected_maxima = ([0.0, 0.0, 0.0, 0.0, 1.0, 2.0], [1.0, 2.0, 3.0, 0.0, 0.0, 0.0])\n    normal_vector = [1.0, 0.0, 0.0]\n    north_vector = [0.0, 0.0, 1.0]\n    resolution = (64, 64)\n    ds = fake_sph_orientation_ds()\n    left_edge = ds.domain_left_edge\n    right_edge = ds.domain_right_edge\n    center = (left_edge + right_edge) / 2\n    width = right_edge - left_edge\n    buf1 = OffAP.off_axis_projection(\n        ds,\n        center,\n        normal_vector,\n        width,\n        resolution,\n        (\"gas\", \"density\"),\n        north_vector=north_vector,\n    )\n    find_compare_maxima(expected_maxima, buf1, resolution, width)\n\n\n@requires_module(\"scipy\")\ndef test_center_1():\n    \"\"\"Change the center to [0, 3, 0]\n    Every point will be shifted by 3 in the y-domain\n    With this, we should not be able to see any of the y-axis particles\n    (0, 1, 0) -> (0, -2)\n    (0, 2, 0) -> (0, -1)\n    (0, 0, 1) -> (0, -3)\n    (0, 0, 2) -> (0, -3)\n    (0, 0, 3) -> (0, -3)\n    (0, 0, 0) -> (0, -3)\n    (1, 0, 0) -> (1, -3)\n    \"\"\"\n    expected_maxima = ([0.0, 0.0, 0.0, 1.0], [-2.0, -1.0, -3.0, -3.0])\n    normal_vector = [0.0, 0.0, 1.0]\n    resolution = (64, 64)\n    ds = fake_sph_orientation_ds()\n    left_edge = ds.domain_left_edge\n    right_edge = ds.domain_right_edge\n    # center = [(left_edge[0] + right_edge[0])/2,\n    #            left_edge[1],\n    #           (left_edge[2] + right_edge[2])/2]\n    center = [0.0, 3.0, 0.0]\n    width = right_edge - left_edge\n    buf1 = OffAP.off_axis_projection(\n        ds, center, normal_vector, width, resolution, (\"gas\", \"density\")\n    )\n    find_compare_maxima(expected_maxima, buf1, resolution, width)\n\n\n@requires_module(\"scipy\")\ndef test_center_2():\n    \"\"\"Change the center to [0, -1, 0]\n    Every point will be shifted by 1 in the y-domain\n    With this, we should not be able to see any of the y-axis particles\n    (0, 1, 0) -> (0, 2)\n    (0, 2, 0) -> (0, 3)\n    (0, 0, 1) -> (0, 1)\n    (0, 0, 2) -> (0, 1)\n    (0, 0, 3) -> (0, 1)\n    (0, 0, 0) -> (0, 1)\n    (1, 0, 0) -> (1, 1)\n    \"\"\"\n    expected_maxima = ([0.0, 0.0, 0.0, 1.0], [2.0, 3.0, 1.0, 1.0])\n    normal_vector = [0.0, 0.0, 1.0]\n    resolution = (64, 64)\n    ds = fake_sph_orientation_ds()\n    left_edge = ds.domain_left_edge\n    right_edge = ds.domain_right_edge\n    center = [0.0, -1.0, 0.0]\n    width = right_edge - left_edge\n    buf1 = OffAP.off_axis_projection(\n        ds, center, normal_vector, width, resolution, (\"gas\", \"density\")\n    )\n    find_compare_maxima(expected_maxima, buf1, resolution, width)\n\n\n@requires_module(\"scipy\")\ndef test_center_3():\n    \"\"\"Change the center to the left edge, or [0, -8, 0]\n    Every point will be shifted by 8 in the y-domain\n    With this, we should not be able to see anything !\n    \"\"\"\n    expected_maxima = ([], [])\n    normal_vector = [0.0, 0.0, 1.0]\n    resolution = (64, 64)\n    ds = fake_sph_orientation_ds()\n    left_edge = ds.domain_left_edge\n    right_edge = ds.domain_right_edge\n    center = [0.0, -1.0, 0.0]\n    width = [\n        (right_edge[0] - left_edge[0]),\n        left_edge[1],\n        (right_edge[2] - left_edge[2]),\n    ]\n    buf1 = OffAP.off_axis_projection(\n        ds, center, normal_vector, width, resolution, (\"gas\", \"density\")\n    )\n    find_compare_maxima(expected_maxima, buf1, resolution, width)\n\n\n@requires_module(\"scipy\")\ndef find_compare_maxima(expected_maxima, buf, resolution, width):\n    buf_ndarray = buf.ndarray_view()\n    max_filter_buf = ndimage.maximum_filter(buf_ndarray, size=5)\n    maxima = np.isclose(max_filter_buf, buf_ndarray, rtol=1e-09)\n\n    # ignore contributions from zones of no smoothing\n    for i in range(len(maxima)):\n        for j in range(len(maxima[i])):\n            if np.isclose(buf_ndarray[i, j], 0.0, 1e-09):\n                maxima[i, j] = False\n    coords = ([], [])\n\n    for i in range(len(maxima)):\n        for j in range(len(maxima[i])):\n            if maxima[i, j]:\n                coords[0].append(i)\n                coords[1].append(j)\n    pixel_tolerance = 2.0\n    x_scaling_factor = resolution[0] / width[0]\n    y_scaling_factor = resolution[1] / width[1]\n    for i in range(len(expected_maxima[0])):\n        found_match = False\n        for j in range(len(coords[0])):\n            # normalize coordinates\n            x_coord = coords[0][j]\n            y_coord = coords[1][j]\n            x_coord -= resolution[0] / 2\n            y_coord -= resolution[1] / 2\n            x_coord /= x_scaling_factor\n            y_coord /= y_scaling_factor\n            if (\n                spatial.distance.euclidean(\n                    [x_coord, y_coord], [expected_maxima[0][i], expected_maxima[1][i]]\n                )\n                < pixel_tolerance\n            ):\n                found_match = True\n                break\n        if found_match is not True:\n            raise AssertionError\n    pass\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_points.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport numpy as np\n\nfrom yt.testing import fake_random_ds\nfrom yt.visualization.volume_rendering.api import (\n    PointSource,\n    Scene,\n    create_volume_source,\n)\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass PointsVRTest(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n\n    def setUp(self):\n        np.random.seed(0)\n        if self.use_tmpdir:\n            self.curdir = os.getcwd()\n            # Perform I/O in safe place instead of yt main dir\n            self.tmpdir = tempfile.mkdtemp()\n            os.chdir(self.tmpdir)\n        else:\n            self.curdir, self.tmpdir = None, None\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            os.chdir(self.curdir)\n            shutil.rmtree(self.tmpdir)\n\n    def test_points_vr(self):\n        ds = fake_random_ds(64)\n        dd = ds.sphere(ds.domain_center, 0.45 * ds.domain_width[0])\n\n        # Trigger creation of index\n        ds.index\n        ds.field_info[ds.field_list[0]].take_log = False\n\n        sc = Scene()\n        cam = sc.add_camera(ds)\n        cam.resolution = (512, 512)\n        vr = create_volume_source(dd, field=ds.field_list[0])\n        vr.transfer_function.clear()\n        vr.transfer_function.grey_opacity = False\n        vr.transfer_function.map_to_colormap(0.0, 1.0, scale=10.0, colormap=\"Reds\")\n        sc.add_source(vr)\n\n        cam.set_width(1.8 * ds.domain_width)\n        cam.lens.setup_box_properties(cam)\n\n        # DRAW SOME POINTS\n        npoints = 1000\n        vertices = np.random.random([npoints, 3])\n        colors = np.random.random([npoints, 4])\n        colors[:, 3] = 0.10\n\n        points_source = PointSource(vertices, colors=colors)\n        sc.add_source(points_source)\n        im = sc.render()\n        im.write_png(\"points.png\")\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_save_render.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport yt\nfrom yt.testing import fake_random_ds\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass SaveRenderTest(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n    tmpdir = \"./\"\n\n    def setUp(self):\n        if self.use_tmpdir:\n            tempfile.mkdtemp()\n            self.tmpdir = tempfile.mkdtemp()\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            shutil.rmtree(self.tmpdir)\n\n    def test_save_render(self):\n        ds = fake_random_ds(ndims=32)\n        sc = yt.create_scene(ds)\n\n        # make sure it renders if nothing exists, even if render = False\n        sc.save(os.path.join(self.tmpdir, \"raw.png\"), render=False)\n        # make sure it re-renders\n        sc.save(os.path.join(self.tmpdir, \"raw_2.png\"), render=True)\n        # make sure sigma clip does not re-render\n        sc.save(os.path.join(self.tmpdir, \"clip_2.png\"), sigma_clip=2.0, render=False)\n        sc.save(os.path.join(self.tmpdir, \"clip_4.png\"), sigma_clip=4.0, render=False)\n\n        # save a different format with/without sigma clips\n        sc.save(os.path.join(self.tmpdir, \"no_clip.jpg\"), render=False)\n        sc.save(os.path.join(self.tmpdir, \"clip_2.jpg\"), sigma_clip=2, render=False)\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_scene.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport numpy as np\n\nfrom yt.testing import assert_fname, fake_random_ds, fake_vr_orientation_test_ds\nfrom yt.visualization.volume_rendering.api import (\n    create_scene,\n    create_volume_source,\n    volume_render,\n)\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass RotationTest(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n\n    def setUp(self):\n        if self.use_tmpdir:\n            self.curdir = os.getcwd()\n            # Perform I/O in safe place instead of yt main dir\n            self.tmpdir = tempfile.mkdtemp()\n            os.chdir(self.tmpdir)\n        else:\n            self.curdir, self.tmpdir = None, None\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            os.chdir(self.curdir)\n            shutil.rmtree(self.tmpdir)\n\n    def test_rotation(self):\n        ds = fake_random_ds(32)\n        ds2 = fake_random_ds(32)\n        dd = ds.sphere(ds.domain_center, ds.domain_width[0] / 2)\n        dd2 = ds2.sphere(ds2.domain_center, ds2.domain_width[0] / 2)\n\n        im, sc = volume_render(dd, field=(\"gas\", \"density\"))\n        im.write_png(\"test.png\")\n\n        vol = sc.get_source(0)\n        tf = vol.transfer_function\n        tf.clear()\n        mi, ma = dd.quantities.extrema((\"gas\", \"density\"))\n        mi = np.log10(mi)\n        ma = np.log10(ma)\n        mi_bound = ((ma - mi) * (0.10)) + mi\n        ma_bound = ((ma - mi) * (0.90)) + mi\n        tf.map_to_colormap(mi_bound, ma_bound, scale=0.01, colormap=\"Blues_r\")\n\n        vol2 = create_volume_source(dd2, field=(\"gas\", \"density\"))\n        sc.add_source(vol2)\n\n        tf = vol2.transfer_function\n        tf.clear()\n        mi, ma = dd2.quantities.extrema((\"gas\", \"density\"))\n        mi = np.log10(mi)\n        ma = np.log10(ma)\n        mi_bound = ((ma - mi) * (0.10)) + mi\n        ma_bound = ((ma - mi) * (0.90)) + mi\n        tf.map_to_colormap(mi_bound, ma_bound, scale=0.01, colormap=\"Reds_r\")\n        fname = \"test_scene.pdf\"\n        sc.save(fname, sigma_clip=6.0)\n        assert_fname(fname)\n\n        fname = \"test_rot.png\"\n        sc.camera.pitch(np.pi)\n        sc.render()\n        sc.save(fname, sigma_clip=6.0, render=False)\n        assert_fname(fname)\n\n\ndef test_annotations():\n    from matplotlib.image import imread\n\n    curdir = os.getcwd()\n    tmpdir = tempfile.mkdtemp()\n    os.chdir(tmpdir)\n    ds = fake_vr_orientation_test_ds(N=16)\n    sc = create_scene(ds)\n    sc.annotate_axes()\n    sc.annotate_domain(ds)\n    sc.render()\n    # ensure that there are actually red, green, blue, and white pixels\n    # in the image. see Issue #1595\n    im = sc._last_render\n    for c in ([1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 1], [1, 1, 1, 1]):\n        assert np.where((im == c).all(axis=-1))[0].shape[0] > 0\n    sc[0].tfh.tf.add_layers(10, colormap=\"cubehelix\")\n    sc.save_annotated(\n        \"test_scene_annotated.png\",\n        text_annotate=[[(0.1, 1.05), \"test_string\"]],\n    )\n    image = imread(\"test_scene_annotated.png\")\n    assert image.shape == sc.camera.resolution + (4,)\n    os.chdir(curdir)\n    shutil.rmtree(tmpdir)\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_sigma_clip.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport yt\nfrom yt.testing import fake_random_ds\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass SigmaClipTest(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n\n    def setUp(self):\n        if self.use_tmpdir:\n            self.curdir = os.getcwd()\n            # Perform I/O in safe place instead of yt main dir\n            self.tmpdir = tempfile.mkdtemp()\n            os.chdir(self.tmpdir)\n        else:\n            self.curdir, self.tmpdir = None, None\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            os.chdir(self.curdir)\n            shutil.rmtree(self.tmpdir)\n\n    def test_sigma_clip(self):\n        ds = fake_random_ds(32)\n        sc = yt.create_scene(ds)\n        sc.save(\"clip_2.png\", sigma_clip=2)\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_simple_vr.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport yt\nfrom yt.testing import fake_random_ds\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass SimpleVRTest(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n\n    def setUp(self):\n        if self.use_tmpdir:\n            self.curdir = os.getcwd()\n            # Perform I/O in safe place instead of yt main dir\n            self.tmpdir = tempfile.mkdtemp()\n            os.chdir(self.tmpdir)\n        else:\n            self.curdir, self.tmpdir = None, None\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            os.chdir(self.curdir)\n            shutil.rmtree(self.tmpdir)\n\n    def test_simple_vr(self):\n        ds = fake_random_ds(32)\n        _im, _sc = yt.volume_render(ds, fname=\"test.png\", sigma_clip=4.0)\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_varia.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport numpy as np\n\nimport yt\nfrom yt.testing import fake_random_ds\nfrom yt.visualization.volume_rendering.api import Scene, create_volume_source\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass VariousVRTests(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n\n    def setUp(self):\n        if self.use_tmpdir:\n            self.curdir = os.getcwd()\n            # Perform I/O in safe place instead of yt main dir\n            self.tmpdir = tempfile.mkdtemp()\n            os.chdir(self.tmpdir)\n        else:\n            self.curdir, self.tmpdir = None, None\n\n        self.ds = fake_random_ds(32)\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            os.chdir(self.curdir)\n            shutil.rmtree(self.tmpdir)\n        del self.ds\n\n    def test_simple_scene_creation(self):\n        yt.create_scene(self.ds)\n\n    def test_modify_transfer_function(self):\n        im, sc = yt.volume_render(self.ds)\n\n        volume_source = sc.get_source(0)\n        tf = volume_source.transfer_function\n        tf.clear()\n        tf.grey_opacity = True\n        tf.add_layers(3, colormap=\"RdBu\")\n        sc.render()\n\n    def test_multiple_fields(self):\n        im, sc = yt.volume_render(self.ds)\n\n        volume_source = sc.get_source(0)\n        volume_source.set_field((\"gas\", \"velocity_x\"))\n        volume_source.set_weight_field((\"gas\", \"density\"))\n        sc.render()\n\n    def test_rotation_volume_rendering(self):\n        im, sc = yt.volume_render(self.ds)\n\n        sc.camera.yaw(np.pi)\n        sc.render()\n\n    def test_simple_volume_rendering(self):\n        im, sc = yt.volume_render(self.ds, sigma_clip=4.0)\n\n    def test_lazy_volume_source_construction(self):\n        sc = Scene()\n        source = create_volume_source(self.ds.all_data(), (\"gas\", \"density\"))\n\n        assert source._volume is None\n        assert source._transfer_function is None\n\n        source.tfh.bounds = (0.1, 1)\n\n        source.set_log(False)\n\n        assert not source.log_field\n        assert source.transfer_function.x_bounds == [0.1, 1]\n        assert source._volume is None\n\n        source.set_log(True)\n\n        assert source.log_field\n        assert source.transfer_function.x_bounds == [-1, 0]\n        assert source._volume is None\n\n        source.transfer_function = None\n        source.tfh.bounds = None\n\n        ad = self.ds.all_data()\n\n        np.testing.assert_allclose(\n            source.transfer_function.x_bounds,\n            np.log10(ad.quantities.extrema((\"gas\", \"density\"))),\n        )\n        assert source.tfh.log == source.log_field\n\n        source.set_field((\"gas\", \"velocity_x\"))\n        source.set_log(False)\n\n        assert source.transfer_function.x_bounds == list(\n            ad.quantities.extrema((\"gas\", \"velocity_x\"))\n        )\n        assert source._volume is None\n\n        source.set_field((\"gas\", \"density\"))\n\n        assert source.volume is not None\n        assert not source.volume._initialized\n        assert source.volume.fields is None\n\n        del source.volume\n        assert source._volume is None\n\n        sc.add_source(source)\n\n        sc.add_camera()\n\n        sc.render()\n\n        assert source.volume is not None\n        assert source.volume._initialized\n        assert source.volume.fields == [(\"gas\", \"density\")]\n        assert source.volume.log_fields == [True]\n\n        source.set_field((\"gas\", \"velocity_x\"))\n        source.set_log(False)\n\n        sc.render()\n\n        assert source.volume is not None\n        assert source.volume._initialized\n        assert source.volume.fields == [(\"gas\", \"velocity_x\")]\n        assert source.volume.log_fields == [False]\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_vr_cameras.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport numpy as np\n\nfrom yt.testing import assert_fname, fake_random_ds\nfrom yt.visualization.volume_rendering.api import (\n    ColorTransferFunction,\n    ProjectionTransferFunction,\n)\nfrom yt.visualization.volume_rendering.old_camera import (\n    FisheyeCamera,\n    InteractiveCamera,\n    PerspectiveCamera,\n    ProjectionCamera,\n    StereoPairCamera,\n)\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass CameraTest(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n\n    def setUp(self):\n        if self.use_tmpdir:\n            self.curdir = os.getcwd()\n            # Perform I/O in safe place instead of yt main dir\n            self.tmpdir = tempfile.mkdtemp()\n            os.chdir(self.tmpdir)\n        else:\n            self.curdir, self.tmpdir = None, None\n\n        self.ds = fake_random_ds(64)\n        self.c = self.ds.domain_center\n        self.L = np.array([0.5, 0.5, 0.5])\n        self.W = 1.5 * self.ds.domain_width\n        self.N = 64\n        self.field = (\"gas\", \"density\")\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            os.chdir(self.curdir)\n            shutil.rmtree(self.tmpdir)\n\n    def setup_transfer_function(self, camera_type):\n        if camera_type in [\"perspective\", \"camera\", \"stereopair\", \"interactive\"]:\n            mi, ma = self.ds.all_data().quantities[\"Extrema\"](self.field)\n            tf = ColorTransferFunction((mi, ma), grey_opacity=True)\n            tf.map_to_colormap(mi, ma, scale=10.0, colormap=\"RdBu_r\")\n            return tf\n        elif camera_type in [\"healpix\"]:\n            return ProjectionTransferFunction()\n        else:\n            pass\n\n    def test_camera(self):\n        tf = self.setup_transfer_function(\"camera\")\n        cam = self.ds.camera(\n            self.c, self.L, self.W, self.N, transfer_function=tf, log_fields=[False]\n        )\n        cam.snapshot(\"camera.png\")\n        assert_fname(\"camera.png\")\n\n        im = cam.snapshot()\n        im = cam.draw_domain(im)\n        cam.draw_coordinate_vectors(im)\n        cam.draw_line(im, [0, 0, 0], [1, 1, 0])\n\n    def test_data_source_camera(self):\n        ds = self.ds\n        tf = self.setup_transfer_function(\"camera\")\n        data_source = ds.sphere(ds.domain_center, ds.domain_width[0] * 0.5)\n\n        cam = ds.camera(\n            self.c,\n            self.L,\n            self.W,\n            self.N,\n            log_fields=[False],\n            transfer_function=tf,\n            data_source=data_source,\n        )\n        cam.snapshot(\"data_source_camera.png\")\n        assert_fname(\"data_source_camera.png\")\n\n    def test_perspective_camera(self):\n        ds = self.ds\n        tf = self.setup_transfer_function(\"camera\")\n\n        cam = PerspectiveCamera(\n            self.c,\n            self.L,\n            self.W,\n            self.N,\n            ds=ds,\n            transfer_function=tf,\n            log_fields=[False],\n        )\n        cam.snapshot(\"perspective.png\")\n        assert_fname(\"perspective.png\")\n\n    def test_interactive_camera(self):\n        ds = self.ds\n        tf = self.setup_transfer_function(\"camera\")\n\n        cam = InteractiveCamera(\n            self.c,\n            self.L,\n            self.W,\n            self.N,\n            ds=ds,\n            transfer_function=tf,\n            log_fields=[False],\n        )\n        del cam\n        # Can't take a snapshot here since IC uses matplotlib.'\n\n    def test_projection_camera(self):\n        ds = self.ds\n\n        cam = ProjectionCamera(self.c, self.L, self.W, self.N, ds=ds, field=self.field)\n        cam.snapshot(\"projection.png\")\n        assert_fname(\"projection.png\")\n\n    def test_stereo_camera(self):\n        ds = self.ds\n        tf = self.setup_transfer_function(\"camera\")\n\n        cam = ds.camera(\n            self.c, self.L, self.W, self.N, transfer_function=tf, log_fields=[False]\n        )\n        stereo_cam = StereoPairCamera(cam)\n        # Take image\n        cam1, cam2 = stereo_cam.split()\n        cam1.snapshot(fn=\"stereo1.png\")\n        cam2.snapshot(fn=\"stereo2.png\")\n        assert_fname(\"stereo1.png\")\n        assert_fname(\"stereo2.png\")\n\n    def test_camera_movement(self):\n        ds = self.ds\n        tf = self.setup_transfer_function(\"camera\")\n\n        cam = ds.camera(\n            self.c,\n            self.L,\n            self.W,\n            self.N,\n            transfer_function=tf,\n            log_fields=[False],\n            north_vector=[0.0, 0.0, 1.0],\n        )\n        cam.zoom(0.5)\n        for snap in cam.zoomin(2.0, 3):\n            snap\n        for snap in cam.move_to(\n            np.array(self.c) + 0.1, 3, final_width=None, exponential=False\n        ):\n            snap\n        for snap in cam.move_to(\n            np.array(self.c) - 0.1, 3, final_width=2.0 * self.W, exponential=False\n        ):\n            snap\n        for snap in cam.move_to(\n            np.array(self.c), 3, final_width=1.0 * self.W, exponential=True\n        ):\n            snap\n        cam.rotate(np.pi / 10)\n        cam.pitch(np.pi / 10)\n        cam.yaw(np.pi / 10)\n        cam.roll(np.pi / 10)\n        for snap in cam.rotation(np.pi, 3, rot_vector=None):\n            snap\n        for snap in cam.rotation(np.pi, 3, rot_vector=np.random.random(3)):\n            snap\n        cam.snapshot(\"final.png\")\n        assert_fname(\"final.png\")\n\n    def test_fisheye(self):\n        ds = self.ds\n        tf = self.setup_transfer_function(\"camera\")\n        cam = FisheyeCamera(\n            ds.domain_center,\n            ds.domain_width[0],\n            360.0,\n            256,\n            transfer_function=tf,\n            ds=ds,\n        )\n        cam.snapshot(\"fisheye.png\")\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_vr_orientation.py",
    "content": "import os\nimport tempfile\n\nimport matplotlib as mpl\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pytest\n\nfrom yt.testing import fake_vr_orientation_test_ds\nfrom yt.visualization.volume_rendering.api import (\n    Scene,\n    create_volume_source,\n    off_axis_projection,\n)\n\n\ndef scene_to_mpl_figure(scene):\n    \"\"\"helper function to convert a scene image rendering to matplotlib\n    so we can rely on pytest-mpl to compare images\n    \"\"\"\n    tmpfd, tmpname = tempfile.mkstemp(suffix=\".png\")\n    os.close(tmpfd)\n    scene.save(tmpname, sigma_clip=1.0)\n    image = mpl.image.imread(tmpname)\n    os.remove(tmpname)\n\n    fig, ax = plt.subplots()\n    ax.set(aspect=\"equal\")\n    ax.imshow(image)\n    return fig\n\n\nclass TestOrientation:\n    @classmethod\n    def setup_class(cls):\n        cls.ds = fake_vr_orientation_test_ds()\n\n        cls.scene = Scene()\n\n        vol = create_volume_source(cls.ds, field=(\"gas\", \"density\"))\n        cls.scene.add_source(vol)\n\n        cls._last_lense_type = None\n\n    @classmethod\n    def set_camera(cls, lens_type):\n        # this method isn't thread-safe\n        # if lens_type == cls._last_lense_type:\n        #    return\n\n        cls._last_lense_type = lens_type\n\n        cam = cls.scene.add_camera(cls.ds, lens_type=lens_type)\n        cam.resolution = (1000, 1000)\n        cam.position = cls.ds.arr(np.array([-4.0, 0.0, 0.0]), \"code_length\")\n        cam.switch_orientation(\n            normal_vector=[1.0, 0.0, 0.0], north_vector=[0.0, 0.0, 1.0]\n        )\n        cam.set_width(cls.ds.domain_width * 2.0)\n        cls.camera = cam\n\n    @pytest.mark.parametrize(\"lens_type\", [\"perspective\", \"plane-parallel\"])\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_vr_orientation_lense_type(self, lens_type):\n        # note that a previous version of this test proved flaky\n        # and required a much lower precision for plane-parallel\n        # https://github.com/yt-project/yt/issue/3069\n        # https://github.com/yt-project/yt/pull/3068\n        # https://github.com/yt-project/yt/pull/3294\n        self.set_camera(lens_type)\n        return scene_to_mpl_figure(self.scene)\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_vr_orientation_yaw(self):\n        self.set_camera(\"plane-parallel\")\n        center = self.ds.arr([0, 0, 0], \"code_length\")\n        self.camera.yaw(np.pi, rot_center=center)\n        return scene_to_mpl_figure(self.scene)\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_vr_orientation_pitch(self):\n        self.set_camera(\"plane-parallel\")\n        center = self.ds.arr([0, 0, 0], \"code_length\")\n        self.camera.pitch(np.pi, rot_center=center)\n        return scene_to_mpl_figure(self.scene)\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_vr_orientation_roll(self):\n        self.set_camera(\"plane-parallel\")\n        center = self.ds.arr([0, 0, 0], \"code_length\")\n        self.camera.roll(np.pi, rot_center=center)\n        return scene_to_mpl_figure(self.scene)\n\n    @pytest.mark.mpl_image_compare(remove_text=True)\n    def test_vr_orientation_off_axis_projection(self):\n        image = off_axis_projection(\n            self.ds,\n            center=[0.5, 0.5, 0.5],\n            normal_vector=[-0.3, -0.1, 0.8],\n            width=[1.0, 1.0, 1.0],\n            resolution=512,\n            item=(\"gas\", \"density\"),\n            no_ghost=False,\n        )\n\n        fig, ax = plt.subplots()\n        ax.imshow(image)\n        return fig\n"
  },
  {
    "path": "yt/visualization/volume_rendering/tests/test_zbuff.py",
    "content": "import os\nimport shutil\nimport tempfile\nfrom unittest import TestCase\n\nimport numpy as np\nfrom numpy.testing import assert_almost_equal\n\nfrom yt.testing import fake_random_ds\nfrom yt.visualization.volume_rendering.api import (\n    OpaqueSource,\n    Scene,\n    ZBuffer,\n    create_volume_source,\n)\n\n\nclass FakeOpaqueSource(OpaqueSource):\n    # A minimal (mock) concrete implementation of OpaqueSource\n    def render(self, camera, zbuffer=None):\n        pass\n\n    def _validate(self):\n        pass\n\n\ndef setup_module():\n    \"\"\"Test specific setup.\"\"\"\n    from yt.config import ytcfg\n\n    ytcfg[\"yt\", \"internals\", \"within_testing\"] = True\n\n\nclass ZBufferTest(TestCase):\n    # This toggles using a temporary directory. Turn off to examine images.\n    use_tmpdir = True\n\n    def setUp(self):\n        np.random.seed(0)\n        if self.use_tmpdir:\n            self.curdir = os.getcwd()\n            # Perform I/O in safe place instead of yt main dir\n            self.tmpdir = tempfile.mkdtemp()\n            os.chdir(self.tmpdir)\n        else:\n            self.curdir, self.tmpdir = None, None\n\n    def tearDown(self):\n        if self.use_tmpdir:\n            os.chdir(self.curdir)\n            shutil.rmtree(self.tmpdir)\n\n    def test_composite_vr(self):\n        ds = fake_random_ds(64)\n        dd = ds.sphere(ds.domain_center, 0.45 * ds.domain_width[0])\n        # Trigger creation of index\n        ds.index\n        ds.field_info[ds.field_list[0]].take_log = False\n\n        sc = Scene()\n        cam = sc.add_camera(ds)\n        cam.resolution = (512, 512)\n        vr = create_volume_source(dd, field=ds.field_list[0])\n        vr.transfer_function.clear()\n        vr.transfer_function.grey_opacity = True\n        vr.transfer_function.map_to_colormap(0.0, 1.0, scale=10.0, colormap=\"Reds\")\n        sc.add_source(vr)\n\n        cam.set_width(1.8 * ds.domain_width)\n        cam.lens.setup_box_properties(cam)\n\n        # Create Arbitrary Z-buffer\n        empty = cam.lens.new_image(cam)\n        z = np.empty(empty.shape[:2], dtype=\"float64\")\n        # Let's put a blue plane right through the center\n        z[:] = cam.width[2] / 2.0\n        empty[:, :, 2] = 1.0  # Set blue to 1's\n        empty[:, :, 3] = 1.0  # Set alpha to 1's\n        zbuffer = ZBuffer(empty, z)\n        zsource = FakeOpaqueSource()\n        zsource.set_zbuffer(zbuffer)\n        sc.add_source(zsource)\n\n        im = sc.render()\n        im.write_png(\"composite.png\")\n\n    def test_nonrectangular_add(self):\n        rgba1 = np.ones((64, 1, 4))\n        z1 = np.expand_dims(np.arange(64.0), 1)\n\n        rgba2 = np.zeros((64, 1, 4))\n        z2 = np.expand_dims(np.arange(63.0, -1.0, -1.0), 1)\n\n        exact_rgba = np.concatenate((np.ones(32), np.zeros(32)))\n        exact_rgba = np.expand_dims(exact_rgba, 1)\n        exact_rgba = np.dstack((exact_rgba, exact_rgba, exact_rgba, exact_rgba))\n\n        exact_z = np.concatenate((np.arange(32.0), np.arange(31.0, -1.0, -1.0)))\n        exact_z = np.expand_dims(exact_z, 1)\n\n        buff1 = ZBuffer(rgba1, z1)\n        buff2 = ZBuffer(rgba2, z2)\n\n        buff = buff1 + buff2\n\n        assert_almost_equal(buff.rgba, exact_rgba)\n        assert_almost_equal(buff.z, exact_z)\n\n    def test_rectangular_add(self):\n        rgba1 = np.ones((8, 8, 4))\n        z1 = np.arange(64.0)\n        z1 = z1.reshape((8, 8))\n        buff1 = ZBuffer(rgba1, z1)\n\n        rgba2 = np.zeros((8, 8, 4))\n        z2 = np.arange(63.0, -1.0, -1.0)\n        z2 = z2.reshape((8, 8))\n        buff2 = ZBuffer(rgba2, z2)\n\n        buff = buff1 + buff2\n\n        exact_rgba = np.empty((8, 8, 4), dtype=np.float64)\n        exact_rgba[0:4, 0:8, :] = 1.0\n        exact_rgba[4:8, 0:8, :] = 0.0\n\n        exact_z = np.concatenate((np.arange(32.0), np.arange(31.0, -1.0, -1.0)))\n        exact_z = np.expand_dims(exact_z, 1)\n        exact_z = exact_z.reshape(8, 8)\n\n        assert_almost_equal(buff.rgba, exact_rgba)\n        assert_almost_equal(buff.z, exact_z)\n"
  },
  {
    "path": "yt/visualization/volume_rendering/transfer_function_helper.py",
    "content": "from io import BytesIO\n\nimport numpy as np\n\nfrom yt.data_objects.profiles import create_profile\nfrom yt.funcs import mylog\nfrom yt.visualization.volume_rendering.transfer_functions import ColorTransferFunction\n\n\nclass TransferFunctionHelper:\n    r\"\"\"A transfer function helper.\n\n    This attempts to help set up a good transfer function by finding\n    bounds, handling linear/log options, and displaying the transfer\n    function combined with 1D profiles of rendering quantity.\n\n    Parameters\n    ----------\n    ds: A Dataset instance\n        A static output that is currently being rendered. This is used to\n        help set up data bounds.\n\n    Notes\n    -----\n    \"\"\"\n\n    profiles = None\n\n    def __init__(self, ds):\n        self.ds = ds\n        self.field = None\n        self.log = False\n        self.tf = None\n        self.bounds = None\n        self.grey_opacity = False\n        self.profiles = {}\n\n    def set_bounds(self, bounds=None):\n        \"\"\"\n        Set the bounds of the transfer function.\n\n        Parameters\n        ----------\n        bounds: array-like, length 2, optional\n            A length 2 list/array in the form [min, max]. These should be the\n            raw values and not the logarithm of the min and max. If bounds is\n            None, the bounds of the data are calculated from all of the data\n            in the dataset.  This can be slow for very large datasets.\n        \"\"\"\n        if bounds is None:\n            bounds = self.ds.all_data().quantities[\"Extrema\"](self.field, non_zero=True)\n            bounds = [b.ndarray_view() for b in bounds]\n        self.bounds = bounds\n\n        # Do some error checking.\n        assert len(self.bounds) == 2\n        if self.log:\n            assert self.bounds[0] > 0.0\n            assert self.bounds[1] > 0.0\n        return\n\n    def set_field(self, field):\n        \"\"\"\n        Set the field to be rendered\n\n        Parameters\n        ----------\n        field: string\n            The field to be rendered.\n        \"\"\"\n        if field != self.field:\n            self.log = self.ds._get_field_info(field).take_log\n        self.field = field\n\n    def set_log(self, log):\n        \"\"\"\n        Set whether or not the transfer function should be in log or linear\n        space. Also modifies the ds.field_info[field].take_log attribute to\n        stay in sync with this setting.\n\n        Parameters\n        ----------\n        log: boolean\n            Sets whether the transfer function should use log or linear space.\n        \"\"\"\n        self.log = log\n\n    def build_transfer_function(self):\n        \"\"\"\n        Builds the transfer function according to the current state of the\n        TransferFunctionHelper.\n\n\n        Returns\n        -------\n\n        A ColorTransferFunction object.\n\n        \"\"\"\n        if self.bounds is None:\n            mylog.info(\n                \"Calculating data bounds. This may take a while. \"\n                \"Set the TransferFunctionHelper.bounds to avoid this.\"\n            )\n            self.set_bounds()\n\n        if self.log:\n            mi, ma = np.log10(self.bounds[0]), np.log10(self.bounds[1])\n        else:\n            mi, ma = self.bounds\n\n        self.tf = ColorTransferFunction(\n            (mi, ma), grey_opacity=self.grey_opacity, nbins=512\n        )\n        return self.tf\n\n    def setup_default(self):\n        \"\"\"Setup a default colormap\n\n        Creates a ColorTransferFunction including 10 gaussian layers whose\n        colors sample the 'nipy_spectral' colormap. Also attempts to scale the\n        transfer function to produce a natural contrast ratio.\n\n        \"\"\"\n        self.tf.add_layers(10, colormap=\"nipy_spectral\")\n        factor = self.tf.funcs[-1].y.size / self.tf.funcs[-1].y.sum()\n        self.tf.funcs[-1].y *= 2 * factor\n\n    def plot(self, fn=None, profile_field=None, profile_weight=None):\n        \"\"\"\n        Save the current transfer function to a bitmap, or display\n        it inline.\n\n        Parameters\n        ----------\n        fn: string, optional\n            Filename to save the image to. If None, the returns an image\n            to an IPython session.\n\n        Returns\n        -------\n\n        If fn is None, will return an image to an IPython notebook.\n\n        \"\"\"\n        from matplotlib.backends.backend_agg import FigureCanvasAgg\n        from matplotlib.figure import Figure\n\n        if self.tf is None:\n            self.build_transfer_function()\n            self.setup_default()\n        tf = self.tf\n        if self.log:\n            xfunc = np.logspace\n            xmi, xma = np.log10(self.bounds[0]), np.log10(self.bounds[1])\n        else:\n            xfunc = np.linspace\n            xmi, xma = self.bounds\n\n        x = xfunc(xmi, xma, tf.nbins)\n        y = tf.funcs[3].y\n        w = np.append(x[1:] - x[:-1], x[-1] - x[-2])\n        colors = np.array(\n            [tf.funcs[0].y, tf.funcs[1].y, tf.funcs[2].y, np.ones_like(x)]\n        ).T\n\n        fig = Figure(figsize=[6, 3])\n        canvas = FigureCanvasAgg(fig)\n        ax = fig.add_axes([0.2, 0.2, 0.75, 0.75])\n        ax.bar(\n            x,\n            tf.funcs[3].y,\n            w,\n            edgecolor=[0.0, 0.0, 0.0, 0.0],\n            log=self.log,\n            color=colors,\n            bottom=[0],\n        )\n\n        if profile_field is not None:\n            try:\n                prof = self.profiles[self.field]\n            except KeyError:\n                self.setup_profile(profile_field, profile_weight)\n                prof = self.profiles[self.field]\n            try:\n                prof[profile_field]\n            except KeyError:\n                prof.add_fields([profile_field])\n            xplot = prof.x\n            yplot = (\n                prof[profile_field] * tf.funcs[3].y.max() / prof[profile_field].max()\n            )\n            ax.plot(xplot, yplot, color=\"w\", linewidth=3)\n            ax.plot(xplot, yplot, color=\"k\")\n\n        ax.set_xscale({True: \"log\", False: \"linear\"}[self.log])\n        ax.set_xlim(x.min(), x.max())\n        ax.set_xlabel(self.ds._get_field_info(self.field).get_label())\n        ax.set_ylabel(r\"$\\mathrm{alpha}$\")\n        ax.set_ylim(y.max() * 1.0e-3, y.max() * 2)\n\n        if fn is None:\n            from IPython.core.display import Image\n\n            f = BytesIO()\n            canvas.print_figure(f)\n            f.seek(0)\n            img = f.read()\n            return Image(img)\n        else:\n            fig.savefig(fn)\n\n    def setup_profile(self, profile_field=None, profile_weight=None):\n        if profile_field is None:\n            profile_field = \"cell_volume\"\n        prof = create_profile(\n            self.ds.all_data(),\n            self.field,\n            profile_field,\n            n_bins=128,\n            extrema={self.field: self.bounds},\n            weight_field=profile_weight,\n            logs={self.field: self.log},\n        )\n        self.profiles[self.field] = prof\n        return\n"
  },
  {
    "path": "yt/visualization/volume_rendering/transfer_functions.py",
    "content": "import numpy as np\nfrom more_itertools import always_iterable\n\nfrom yt.funcs import mylog\nfrom yt.utilities.physical_constants import clight, hcgs, kboltz\n\n\nclass TransferFunction:\n    r\"\"\"A transfer function governs the transmission of emission and\n    absorption through a volume.\n\n    Transfer functions are defined by boundaries, bins, and the value that\n    governs transmission through that bin.  This is scaled between 0 and 1.\n    When integrating through a volume the value through a given cell is\n    defined by the value calculated in the transfer function.\n\n    Parameters\n    ----------\n    x_bounds : tuple of floats\n        The min and max for the transfer function.  Values below or above\n        these values are discarded.\n    nbins : int\n        How many bins to calculate; in between, linear interpolation is\n        used, so low values are typically fine.\n\n    Notes\n    -----\n    Typically, raw transfer functions are not generated unless particular\n    and specific control over the integration is desired.  Usually either\n    color transfer functions, where the color values are calculated from\n    color tables, or multivariate transfer functions are used.\n    \"\"\"\n\n    def __init__(self, x_bounds, nbins=256):\n        self.pass_through = 0\n        self.nbins = nbins\n        # Strip units off of x_bounds, if any\n        x_bounds = [np.float64(xb) for xb in x_bounds]\n        self.x_bounds = x_bounds\n        self.x = np.linspace(x_bounds[0], x_bounds[1], nbins, dtype=\"float64\")\n        self.y = np.zeros(nbins, dtype=\"float64\")\n        self.grad_field = -1\n        self.light_source_v = self.light_source_c = np.zeros(3, \"float64\")\n        self.features = []\n\n    def add_gaussian(self, location, width, height):\n        r\"\"\"Add a Gaussian distribution to the transfer function.\n\n        Typically, when rendering isocontours, a Gaussian distribution is the\n        easiest way to draw out features.  The spread provides a softness.\n        The values are calculated as :math:`f(x) = h \\exp{-(x-x_0)^2 / w}`.\n\n        Parameters\n        ----------\n        location : float\n            The centroid of the Gaussian (:math:`x_0` in the above equation.)\n        width : float\n            The relative width (:math:`w` in the above equation.)\n        height : float\n            The peak height (:math:`h` in the above equation.)  Note that while\n            values greater 1.0 will be accepted, the values of the transmission\n            function are clipped at 1.0.\n\n        Examples\n        --------\n\n        >>> tf = TransferFunction((-10.0, -5.0))\n        >>> tf.add_gaussian(-9.0, 0.01, 1.0)\n        \"\"\"\n        vals = height * np.exp(-((self.x - location) ** 2.0) / width)\n        self.y = np.clip(np.maximum(vals, self.y), 0.0, np.inf)\n        self.features.append(\n            (\n                \"gaussian\",\n                f\"location(x):{location:3.2g}\",\n                f\"width(x):{width:3.2g}\",\n                f\"height(y):{height:3.2g}\",\n            )\n        )\n\n    def add_line(self, start, stop):\n        r\"\"\"Add a line between two points to the transmission function.\n\n        This will accept a starting point in (x,y) and an ending point in (x,y)\n        and set the values of the transmission function between those x-values\n        to be along the line connecting the y values.\n\n        Parameters\n        ----------\n        start : tuple of floats\n            (x0, y0), the starting point.  x0 is between the bounds of the\n            transfer function and y0 must be between 0.0 and 1.0.\n        stop : tuple of floats\n            (x1, y1), the ending point.  x1 is between the bounds of the\n            transfer function and y1 must be between 0.0 and 1.0.\n\n        Examples\n        --------\n        This will set the transfer function to be linear from 0.0 to 1.0,\n        across the bounds of the function.\n\n        >>> tf = TransferFunction((-10.0, -5.0))\n        >>> tf.add_line((-10.0, 0.0), (-5.0, 1.0))\n        \"\"\"\n        x0, y0 = start\n        x1, y1 = stop\n        slope = (y1 - y0) / (x1 - x0)\n        # We create a whole new set of values and then backout the ones that do\n        # not satisfy our bounding box arguments\n        vals = slope * (self.x - x0) + y0\n        vals[~((self.x >= x0) & (self.x <= x1))] = 0.0\n        self.y = np.clip(np.maximum(vals, self.y), 0.0, np.inf)\n        self.features.append(\n            (\n                \"line\",\n                f\"start(x,y):({start[0]:3.2g}, {start[1]:3.2g})\",\n                f\"stop(x,y):({stop[0]:3.2g}, {stop[1]:3.2g})\",\n            )\n        )\n\n    def add_step(self, start, stop, value):\n        r\"\"\"Adds a step function to the transfer function.\n\n        This accepts a `start` and a `stop`, and then in between those points the\n        transfer function is set to the maximum of the transfer function and\n        the `value`.\n\n        Parameters\n        ----------\n        start : float\n            This is the beginning of the step function; must be within domain\n            of the transfer function.\n        stop : float\n            This is the ending of the step function; must be within domain\n            of the transfer function.\n        value : float\n            The value the transfer function will be set to between `start` and\n            `stop`.  Note that the transfer function will *actually* be set to\n            max(y, value) where y is the existing value of the transfer\n            function.\n\n        Examples\n        --------\n        Note that in this example, we have added a step function, but the\n        Gaussian that already exists will \"win\" where it exceeds 0.5.\n\n        >>> tf = TransferFunction((-10.0, -5.0))\n        >>> tf.add_gaussian(-7.0, 0.01, 1.0)\n        >>> tf.add_step(-8.0, -6.0, 0.5)\n        \"\"\"\n        vals = np.zeros(self.x.shape, \"float64\")\n        vals[(self.x >= start) & (self.x <= stop)] = value\n        self.y = np.clip(np.maximum(vals, self.y), 0.0, np.inf)\n        self.features.append(\n            (\n                \"step\",\n                f\"start(x):{start:3.2g}\",\n                f\"stop(x):{stop:3.2g}\",\n                f\"value(y):{value:3.2g}\",\n            )\n        )\n\n    def add_filtered_planck(self, wavelength, trans):\n        from yt._maintenance.numpy2_compat import trapezoid\n\n        vals = np.zeros(self.x.shape, \"float64\")\n        nu = clight / (wavelength * 1e-8)\n        nu = nu[::-1]\n\n        for i, logT in enumerate(self.x):\n            T = 10**logT\n            # Black body at this nu, T\n            Bnu = ((2.0 * hcgs * nu**3) / clight**2.0) / (\n                np.exp(hcgs * nu / (kboltz * T)) - 1.0\n            )\n            # transmission\n            f = Bnu * trans[::-1]\n            # integrate transmission over nu\n            vals[i] = trapezoid(f, nu)\n\n        # normalize by total transmission over filter\n        self.y = vals / trans.sum()\n        # self.y = np.clip(np.maximum(vals, self.y), 0.0, 1.0)\n\n    def plot(self, filename):\n        r\"\"\"Save an image file of the transfer function.\n\n        This function loads up matplotlib, plots the transfer function and saves.\n\n        Parameters\n        ----------\n        filename : string\n            The file to save out the plot as.\n\n        Examples\n        --------\n\n        >>> tf = TransferFunction((-10.0, -5.0))\n        >>> tf.add_gaussian(-9.0, 0.01, 1.0)\n        >>> tf.plot(\"sample.png\")\n        \"\"\"\n        import matplotlib\n        import matplotlib.pyplot as plt\n\n        matplotlib.use(\"Agg\")\n\n        plt.clf()\n        plt.plot(self.x, self.y, \"xk-\")\n        plt.xlim(*self.x_bounds)\n        plt.ylim(0.0, 1.0)\n        plt.savefig(filename)\n\n    def show(self):\n        r\"\"\"Display an image of the transfer function\n\n        This function loads up matplotlib and displays the current transfer function.\n\n        Parameters\n        ----------\n\n        Examples\n        --------\n\n        >>> tf = TransferFunction((-10.0, -5.0))\n        >>> tf.add_gaussian(-9.0, 0.01, 1.0)\n        >>> tf.show()\n        \"\"\"\n        import matplotlib.pyplot as plt\n\n        plt.clf()\n        plt.plot(self.x, self.y, \"xk-\")\n        plt.xlim(*self.x_bounds)\n        plt.ylim(0.0, 1.0)\n        plt.draw()\n\n    def clear(self):\n        self.y[:] = 0.0\n        self.features = []\n\n    def __repr__(self):\n        disp = (\n            \"<Transfer Function Object>: \"\n            f\"x_bounds:({self.x_bounds[0]:3.2g}, {self.x_bounds[1]:3.2g}) \"\n            f\"nbins:{self.nbins:3.2g} features:{self.features}\"\n        )\n        return disp\n\n\nclass MultiVariateTransferFunction:\n    r\"\"\"This object constructs a set of field tables that allow for\n    multiple field variables to control the integration through a volume.\n\n    The integration through a volume typically only utilizes a single field\n    variable (for instance, Density) to set up and control the values\n    returned at the end of the integration.  For things like isocontours,\n    this is fine.  However, more complicated schema are possible by using\n    this object.  For instance, density-weighted emission that produces\n    colors based on the temperature of the fluid.\n\n    Parameters\n    ----------\n    grey_opacity : bool\n        Should opacity be calculated on a channel-by-channel basis, or\n        overall?  Useful for opaque renderings. Default: False\n\n    \"\"\"\n\n    def __init__(self, grey_opacity=False):\n        self.n_field_tables = 0\n        self.tables = []  # Tables are interpolation tables\n        self.field_ids = [0] * 6  # This correlates fields with tables\n        self.weight_field_ids = [-1] * 6  # This correlates\n        self.field_table_ids = [0] * 6\n        self.weight_table_ids = [-1] * 6\n        self.grad_field = -1\n        self.light_source_v = self.light_source_c = np.zeros(3, \"float64\")\n        self.grey_opacity = grey_opacity\n\n    def add_field_table(self, table, field_id, weight_field_id=-1, weight_table_id=-1):\n        r\"\"\"This accepts a table describing integration.\n\n        A \"field table\" is a tabulated set of values that govern the\n        integration through a given field.  These are defined not only by the\n        transmission coefficient, interpolated from the table itself, but the\n        `field_id` that describes which of several fields the integration\n        coefficient is to be calculated from.\n\n        Parameters\n        ----------\n        table : `TransferFunction`\n            The integration table to be added to the set of tables used during\n            the integration.\n        field_id : int\n            Each volume has an associated set of fields.  This identifies which\n            of those fields will be used to calculate the integration\n            coefficient from this table.\n        weight_field_id : int, optional\n            If specified, the value of the field this identifies will be\n            multiplied against the integration coefficient.\n        weight_table_id : int, optional\n            If specified, the value from the *table* this identifies will be\n            multiplied against the integration coefficient.\n\n        Notes\n        -----\n        This can be rather complicated.  It's recommended that if you are\n        interested in manipulating this in detail that you examine the source\n        code, specifically the function FIT_get_value in\n        yt/_amr_utils/VolumeIntegrator.pyx.\n\n        Examples\n        --------\n        This example shows how to link a new transfer function against field 0.\n        Note that this by itself does not link a *channel* for integration\n        against a field.  This is because the weighting system does not mandate\n        that all tables contribute to a channel, only that they contribute a\n        value which may be used by other field tables.\n\n        >>> mv = MultiVariateTransferFunction()\n        >>> tf = TransferFunction((-10.0, -5.0))\n        >>> tf.add_gaussian(-7.0, 0.01, 1.0)\n        >>> mv.add_field_table(tf, 0)\n        \"\"\"\n        self.tables.append(table)\n        self.field_ids[self.n_field_tables] = field_id\n        self.weight_field_ids[self.n_field_tables] = weight_field_id\n        self.weight_table_ids[self.n_field_tables] = weight_table_id\n        self.n_field_tables += 1\n\n    def link_channels(self, table_id, channels=0):\n        r\"\"\"Link an image channel to a field table.\n\n        Once a field table has been added, it can be linked against a channel (any\n        one of the six -- red, green, blue, red absorption, green absorption, blue\n        absorption) and then the value calculated for that field table will be\n        added to the integration for that channel.  Not all tables must be linked\n        against channels.\n\n        Parameters\n        ----------\n        table_id : int\n            The 0-indexed table to link.\n        channels : int or list of ints\n            The channel or channels to link with this table's calculated value.\n\n\n        Examples\n        --------\n        This example shows how to link a new transfer function against field 0, and\n        then link that table against all three RGB channels.  Typically an\n        absorption (or 'alpha') channel is also linked.\n\n        >>> mv = MultiVariateTransferFunction()\n        >>> tf = TransferFunction((-10.0, -5.0))\n        >>> tf.add_gaussian(-7.0, 0.01, 1.0)\n        >>> mv.add_field_table(tf, 0)\n        >>> mv.link_channels(0, [0, 1, 2])\n        \"\"\"\n        for c in always_iterable(channels):\n            self.field_table_ids[c] = table_id\n\n\nclass ColorTransferFunction(MultiVariateTransferFunction):\n    r\"\"\"A complete set of transfer functions for standard color-mapping.\n\n    This is the best and easiest way to set up volume rendering.  It\n    creates field tables for all three colors, their alphas, and has\n    support for sampling color maps and adding independent color values at\n    all locations.  It will correctly set up the\n    `MultiVariateTransferFunction`.\n\n    Parameters\n    ----------\n    x_bounds : tuple of floats\n        The min and max for the transfer function.  Values below or above\n        these values are discarded.\n    nbins : int\n        How many bins to calculate; in between, linear interpolation is\n        used, so low values are typically fine.\n    grey_opacity : bool\n        Should opacity be calculated on a channel-by-channel basis, or\n        overall?  Useful for opaque renderings.\n    \"\"\"\n\n    def __init__(self, x_bounds, nbins=256, grey_opacity=False):\n        MultiVariateTransferFunction.__init__(self)\n        # Strip units off of x_bounds, if any\n        x_bounds = [np.float64(xb) for xb in x_bounds]\n        self.x_bounds = x_bounds\n        self.nbins = nbins\n        # This is all compatibility and convenience.\n        self.red = TransferFunction(x_bounds, nbins)\n        self.green = TransferFunction(x_bounds, nbins)\n        self.blue = TransferFunction(x_bounds, nbins)\n        self.alpha = TransferFunction(x_bounds, nbins)\n        self.funcs = (self.red, self.green, self.blue, self.alpha)\n        self.grey_opacity = grey_opacity\n        self.features = []\n\n        # Now we do the multivariate stuff\n        # We assign to Density, but do not weight\n        for i, tf in enumerate(self.funcs[:3]):\n            self.add_field_table(tf, 0, weight_table_id=3)\n            self.link_channels(i, i)\n        self.add_field_table(self.funcs[3], 0)\n        self.link_channels(3, 3)\n        # We don't have a fifth table, so the value will *always* be zero.\n        # self.link_channels(4, [3,4,5])\n\n    def add_gaussian(self, location, width, height):\n        r\"\"\"Add a Gaussian distribution to the transfer function.\n\n        Typically, when rendering isocontours, a Gaussian distribution is the\n        easiest way to draw out features.  The spread provides a softness.\n        The values are calculated as :math:`f(x) = h \\exp{-(x-x_0)^2 / w}`.\n\n        Parameters\n        ----------\n        location : float\n            The centroid of the Gaussian (:math:`x_0` in the above equation.)\n        width : float\n            The relative width (:math:`w` in the above equation.)\n        height : list of 4 float\n            The peak height (:math:`h` in the above equation.)  Note that while\n            values greater 1.0 will be accepted, the values of the transmission\n            function are clipped at 1.0.  This must be a list, and it is in the\n            order of (red, green, blue, alpha).\n\n        Examples\n        --------\n        This adds a red spike.\n\n        >>> tf = ColorTransferFunction((-10.0, -5.0))\n        >>> tf.add_gaussian(-9.0, 0.01, [1.0, 0.0, 0.0, 1.0])\n        \"\"\"\n        for tf, v in zip(self.funcs, height, strict=True):\n            tf.add_gaussian(location, width, v)\n        self.features.append(\n            (\n                \"gaussian\",\n                f\"location(x):{location:3.2g}\",\n                f\"width(x):{width:3.2g}\",\n                f\"height(y):({height[0]:3.2g}, {height[1]:3.2g}, {height[2]:3.2g}, {height[3]:3.2g})\",\n            )\n        )\n\n    def add_step(self, start, stop, value):\n        r\"\"\"Adds a step function to the transfer function.\n\n        This accepts a `start` and a `stop`, and then in between those points the\n        transfer function is set to the maximum of the transfer function and\n        the `value`.\n\n        Parameters\n        ----------\n        start : float\n            This is the beginning of the step function; must be within domain\n            of the transfer function.\n        stop : float\n            This is the ending of the step function; must be within domain\n            of the transfer function.\n        value : list of 4 floats\n            The value the transfer function will be set to between `start` and\n            `stop`.  Note that the transfer function will *actually* be set to\n            max(y, value) where y is the existing value of the transfer\n            function.  This must be a list, and it is in the order of (red,\n            green, blue, alpha).\n\n\n        Examples\n        --------\n        This adds a step function that will produce a white value at > -6.0.\n\n        >>> tf = ColorTransferFunction((-10.0, -5.0))\n        >>> tf.add_step(-6.0, -5.0, [1.0, 1.0, 1.0, 1.0])\n        \"\"\"\n        for tf, v in zip(self.funcs, value, strict=True):\n            tf.add_step(start, stop, v)\n        self.features.append(\n            (\n                \"step\",\n                f\"start(x):{start:3.2g}\",\n                f\"stop(x):{stop:3.2g}\",\n                f\"value(y):({value[0]:3.2g}, {value[1]:3.2g}, {value[2]:3.2g}, {value[3]:3.2g})\",\n            )\n        )\n\n    def plot(self, filename):\n        r\"\"\"Save an image file of the transfer function.\n\n        This function loads up matplotlib, plots all of the constituent\n        transfer functions and saves.\n\n        Parameters\n        ----------\n        filename : string\n            The file to save out the plot as.\n\n        Examples\n        --------\n\n        >>> tf = ColorTransferFunction((-10.0, -5.0))\n        >>> tf.add_layers(8)\n        >>> tf.plot(\"sample.png\")\n        \"\"\"\n        from matplotlib import pyplot\n        from matplotlib.ticker import FuncFormatter\n\n        pyplot.clf()\n        ax = pyplot.axes()\n        i_data = np.zeros((self.alpha.x.size, self.funcs[0].y.size, 3))\n        i_data[:, :, 0] = np.outer(np.ones(self.alpha.x.size), self.funcs[0].y)\n        i_data[:, :, 1] = np.outer(np.ones(self.alpha.x.size), self.funcs[1].y)\n        i_data[:, :, 2] = np.outer(np.ones(self.alpha.x.size), self.funcs[2].y)\n        ax.imshow(i_data, origin=\"lower\")\n        ax.fill_between(\n            np.arange(self.alpha.y.size),\n            self.alpha.x.size * self.alpha.y,\n            y2=self.alpha.x.size,\n            color=\"white\",\n        )\n        ax.set_xlim(0, self.alpha.x.size)\n        xticks = (\n            np.arange(np.ceil(self.alpha.x[0]), np.floor(self.alpha.x[-1]) + 1, 1)\n            - self.alpha.x[0]\n        )\n        xticks *= (self.alpha.x.size - 1) / (self.alpha.x[-1] - self.alpha.x[0])\n        ax.xaxis.set_ticks(xticks)\n\n        def x_format(x, pos):\n            return \"%.1f\" % (\n                x * (self.alpha.x[-1] - self.alpha.x[0]) / (self.alpha.x.size - 1)\n                + self.alpha.x[0]\n            )\n\n        ax.xaxis.set_major_formatter(FuncFormatter(x_format))\n        yticks = np.linspace(0, 1, 5) * self.alpha.y.size\n        ax.yaxis.set_ticks(yticks)\n\n        def y_format(y, pos):\n            return y / self.alpha.y.size\n\n        ax.yaxis.set_major_formatter(FuncFormatter(y_format))\n        ax.set_ylabel(\"Transmission\")\n        ax.set_xlabel(\"Value\")\n        pyplot.savefig(filename)\n\n    def show(self, ax=None):\n        r\"\"\"Display an image of the transfer function\n\n        This function loads up matplotlib and displays the current transfer function.\n\n        Parameters\n        ----------\n\n        Examples\n        --------\n\n        >>> tf = TransferFunction((-10.0, -5.0))\n        >>> tf.add_gaussian(-9.0, 0.01, 1.0)\n        >>> tf.show()\n        \"\"\"\n        from matplotlib import pyplot\n        from matplotlib.ticker import FuncFormatter\n\n        pyplot.clf()\n        ax = pyplot.axes()\n        i_data = np.zeros((self.alpha.x.size, self.funcs[0].y.size, 3))\n        i_data[:, :, 0] = np.outer(np.ones(self.alpha.x.size), self.funcs[0].y)\n        i_data[:, :, 1] = np.outer(np.ones(self.alpha.x.size), self.funcs[1].y)\n        i_data[:, :, 2] = np.outer(np.ones(self.alpha.x.size), self.funcs[2].y)\n        ax.imshow(i_data, origin=\"lower\")\n        ax.fill_between(\n            np.arange(self.alpha.y.size),\n            self.alpha.x.size * self.alpha.y,\n            y2=self.alpha.x.size,\n            color=\"white\",\n        )\n        ax.set_xlim(0, self.alpha.x.size)\n        xticks = (\n            np.arange(np.ceil(self.alpha.x[0]), np.floor(self.alpha.x[-1]) + 1, 1)\n            - self.alpha.x[0]\n        )\n        xticks *= (self.alpha.x.size - 1) / (self.alpha.x[-1] - self.alpha.x[0])\n        if len(xticks) > 5:\n            xticks = xticks[:: len(xticks) // 5]\n        ax.xaxis.set_ticks(xticks)\n\n        def x_format(x, pos):\n            return \"%.1f\" % (\n                x * (self.alpha.x[-1] - self.alpha.x[0]) / (self.alpha.x.size - 1)\n                + self.alpha.x[0]\n            )\n\n        ax.xaxis.set_major_formatter(FuncFormatter(x_format))\n        yticks = np.linspace(0, 1, 5) * self.alpha.y.size\n        ax.yaxis.set_ticks(yticks)\n\n        def y_format(y, pos):\n            s = f\"{y:0.2f}\"\n            return s\n\n        ax.yaxis.set_major_formatter(FuncFormatter(y_format))\n        ax.set_ylabel(\"Opacity\")\n        ax.set_xlabel(\"Value\")\n\n    def vert_cbar(\n        self,\n        resolution,\n        log_scale,\n        ax,\n        label=None,\n        label_fmt=None,\n        *,\n        label_fontsize=10,\n        size=10,\n    ):\n        r\"\"\"Display an image of the transfer function\n\n        This function loads up matplotlib and displays the current transfer function.\n\n        Parameters\n        ----------\n\n        Examples\n        --------\n\n        >>> tf = TransferFunction((-10.0, -5.0))\n        >>> tf.add_gaussian(-9.0, 0.01, 1.0)\n        >>> tf.show()\n        \"\"\"\n        from matplotlib.ticker import FuncFormatter\n\n        if label is None:\n            label = \"\"\n        alpha = self.alpha.y\n        max_alpha = alpha.max()\n        i_data = np.zeros((self.alpha.x.size, self.funcs[0].y.size, 3))\n        i_data[:, :, 0] = np.outer(self.funcs[0].y, np.ones(self.alpha.x.size))\n        i_data[:, :, 1] = np.outer(self.funcs[1].y, np.ones(self.alpha.x.size))\n        i_data[:, :, 2] = np.outer(self.funcs[2].y, np.ones(self.alpha.x.size))\n\n        ax.imshow(i_data, origin=\"lower\", aspect=\"auto\")\n        ax.plot(alpha, np.arange(self.alpha.y.size), \"w\")\n\n        # Set TF limits based on what is visible\n        visible = np.argwhere(self.alpha.y > 1.0e-3 * self.alpha.y.max())\n\n        # Display colobar values\n        xticks = (\n            np.arange(np.ceil(self.alpha.x[0]), np.floor(self.alpha.x[-1]) + 1, 1)\n            - self.alpha.x[0]\n        )\n        xticks *= (self.alpha.x.size - 1) / (self.alpha.x[-1] - self.alpha.x[0])\n        if len(xticks) > 5:\n            xticks = xticks[:: len(xticks) // 5]\n\n        # Add colorbar limits to the ticks (May not give ideal results)\n        xticks = np.append(visible[0], xticks)\n        xticks = np.append(visible[-1], xticks)\n        # remove dupes\n        xticks = list(set(xticks))\n        ax.yaxis.set_ticks(xticks)\n\n        def x_format(x, pos):\n            val = (\n                x * (self.alpha.x[-1] - self.alpha.x[0]) / (self.alpha.x.size - 1)\n                + self.alpha.x[0]\n            )\n            if log_scale:\n                val = 10**val\n            if label_fmt is None:\n                if abs(val) < 1.0e-3 or abs(val) > 1.0e4:\n                    if not val == 0.0:\n                        e = np.floor(np.log10(abs(val)))\n                        return rf\"${val / 10.0**e:.2f}\\times 10^{{ {int(e):d} }}$\"\n                    else:\n                        return r\"$0$\"\n                else:\n                    return f\"{val:.1g}\"\n            else:\n                return label_fmt % (val)\n\n        ax.yaxis.set_major_formatter(FuncFormatter(x_format))\n\n        yticks = np.linspace(0, 1, 2, endpoint=True) * max_alpha\n        ax.xaxis.set_ticks(yticks)\n\n        def y_format(y, pos):\n            s = f\"{y:0.2f}\"\n            return s\n\n        ax.xaxis.set_major_formatter(FuncFormatter(y_format))\n        ax.set_xlim(0.0, max_alpha)\n        ax.get_xaxis().set_ticks([])\n        ax.set_ylim(visible[0].item(), visible[-1].item())\n        ax.tick_params(axis=\"y\", colors=\"white\", labelsize=label_fontsize)\n        ax.set_ylabel(label, color=\"white\", size=size * resolution / 512.0)\n\n    def sample_colormap(self, v, w, alpha=None, colormap=\"gist_stern\", col_bounds=None):\n        r\"\"\"Add a Gaussian based on an existing colormap.\n\n        Constructing pleasing Gaussians in a transfer function can pose some\n        challenges, so this function will add a single Gaussian whose colors\n        are taken from a colormap scaled between the bounds of the transfer\n        function.  As with `TransferFunction.add_gaussian`, the value is\n        calculated as :math:`f(x) = h \\exp{-(x-x_0)^2 / w}` but with the height\n        for each color calculated from the colormap.\n\n        Parameters\n        ----------\n        v : float\n            The value at which the Gaussian is to be added.\n        w : float\n            The relative width (:math:`w` in the above equation.)\n        alpha : float, optional\n            The alpha value height for the Gaussian\n        colormap : string, optional\n            An acceptable colormap.  See either yt.visualization.color_maps or\n            https://scipy-cookbook.readthedocs.io/items/Matplotlib_Show_colormaps.html .\n        col_bounds: array_like, optional\n            Limits ([min, max]) the values over which the colormap spans to\n            these values.  Useful for sampling an entire colormap over a range\n            smaller than the transfer function bounds.\n\n        See Also\n        --------\n        ColorTransferFunction.add_layers : Many-at-a-time adder\n\n        Examples\n        --------\n\n        >>> tf = ColorTransferFunction((-10.0, -5.0))\n        >>> tf.sample_colormap(-7.0, 0.01, colormap=\"cmyt.arbre\")\n        \"\"\"\n        import matplotlib as mpl\n\n        v = np.float64(v)\n        if col_bounds is None:\n            rel = (v - self.x_bounds[0]) / (self.x_bounds[1] - self.x_bounds[0])\n        else:\n            rel = (v - col_bounds[0]) / (col_bounds[1] - col_bounds[0])\n        cmap = mpl.colormaps[colormap]\n        r, g, b, a = cmap(rel)\n        if alpha is None:\n            alpha = a\n        self.add_gaussian(v, w, [r, g, b, alpha])\n        mylog.debug(\n            \"Adding gaussian at %s with width %s and colors %s\", v, w, (r, g, b, alpha)\n        )\n\n    def map_to_colormap(\n        self, mi, ma, scale=1.0, colormap=\"gist_stern\", scale_func=None\n    ):\n        r\"\"\"Map a range of values to a full colormap.\n\n        Given a minimum and maximum value in the TransferFunction, map a full\n        colormap over that range at an alpha level of `scale`.\n        Optionally specify a scale_func function that modifies the alpha as\n        a function of the transfer function value.\n\n        Parameters\n        ----------\n        mi : float\n            The start of the TransferFunction to map the colormap\n        ma : float\n            The end of the TransferFunction to map the colormap\n        scale: float, optional\n            The alpha value to be used for the height of the transfer function.\n            Larger values will be more opaque.\n        colormap : string, optional\n            An acceptable colormap.  See either yt.visualization.color_maps or\n            https://scipy-cookbook.readthedocs.io/items/Matplotlib_Show_colormaps.html .\n        scale_func: function(:obj:`!value`, :obj:`!minval`, :obj:`!maxval`), optional\n            A user-defined function that can be used to scale the alpha channel\n            as a function of the TransferFunction field values. Function maps\n            value to somewhere between minval and maxval.\n\n        Examples\n        --------\n\n        >>> def linramp(vals, minval, maxval):\n        ...     return (vals - vals.min()) / (vals.max() - vals.min())\n        >>> tf = ColorTransferFunction((-10.0, -5.0))\n        >>> tf.map_to_colormap(-8.0, -6.0, scale=10.0, colormap=\"cmyt.arbre\")\n        >>> tf.map_to_colormap(\n        ...     -6.0, -5.0, scale=10.0, colormap=\"cmyt.arbre\", scale_func=linramp\n        ... )\n        \"\"\"\n        import matplotlib as mpl\n\n        mi = np.float64(mi)\n        ma = np.float64(ma)\n        rel0 = int(\n            self.nbins * (mi - self.x_bounds[0]) / (self.x_bounds[1] - self.x_bounds[0])\n        )\n        rel1 = int(\n            self.nbins * (ma - self.x_bounds[0]) / (self.x_bounds[1] - self.x_bounds[0])\n        )\n        rel0 = max(rel0, 0)\n        rel1 = min(rel1, self.nbins - 1) + 1\n        tomap = np.linspace(0.0, 1.0, num=rel1 - rel0)\n        cmap = mpl.colormaps[colormap]\n        cc = cmap(tomap)\n        if scale_func is None:\n            scale_mult = 1.0\n        else:\n            scale_mult = scale_func(tomap, 0.0, 1.0)\n        self.red.y[rel0:rel1] = cc[:, 0] * scale_mult\n        self.green.y[rel0:rel1] = cc[:, 1] * scale_mult\n        self.blue.y[rel0:rel1] = cc[:, 2] * scale_mult\n        self.alpha.y[rel0:rel1] = scale * cc[:, 3] * scale_mult\n        self.features.append(\n            (\n                \"map_to_colormap\",\n                f\"start(x):{mi:3.2g}\",\n                f\"stop(x):{ma:3.2g}\",\n                f\"value(y):{scale:3.2g}\",\n            )\n        )\n\n    def add_layers(\n        self,\n        N,\n        w=None,\n        mi=None,\n        ma=None,\n        alpha=None,\n        colormap=\"gist_stern\",\n        col_bounds=None,\n    ):\n        r\"\"\"Add a set of Gaussians based on an existing colormap.\n\n        Constructing pleasing Gaussians in a transfer function can pose some\n        challenges, so this function will add several evenly-spaced Gaussians\n        whose colors are taken from a colormap scaled between the bounds of the\n        transfer function.   For each Gaussian to be added,\n        `ColorTransferFunction.sample_colormap` is called.\n\n        Parameters\n        ----------\n        N : int\n            How many Gaussians to add\n        w : float\n            The relative width of each Gaussian.  If not included, it is\n            calculated as 0.001 * (max_val - min_val) / N\n        mi : float, optional\n            If only a subset of the data range is to have the Gaussians added,\n            this is the minimum for that subset\n        ma : float, optional\n            If only a subset of the data range is to have the Gaussians added,\n            this is the maximum for that subset\n        alpha : list of floats, optional\n            The alpha value height for each Gaussian.  If not supplied, it is\n            set as 1.0 everywhere.\n        colormap : string, optional\n            An acceptable colormap.  See either yt.visualization.color_maps or\n            https://scipy-cookbook.readthedocs.io/items/Matplotlib_Show_colormaps.html .\n        col_bounds: array_like, optional\n            Limits ([min, max]) the values over which the colormap spans to\n            these values.  Useful for sampling an entire colormap over a range\n            smaller than the transfer function bounds.\n\n        See Also\n        --------\n        ColorTransferFunction.sample_colormap : Single Gaussian adder\n\n        Examples\n        --------\n\n        >>> tf = ColorTransferFunction((-10.0, -5.0))\n        >>> tf.add_layers(8)\n        \"\"\"\n        if col_bounds is None:\n            dist = self.x_bounds[1] - self.x_bounds[0]\n            if mi is None:\n                mi = self.x_bounds[0] + dist / (10.0 * N)\n            if ma is None:\n                ma = self.x_bounds[1] - dist / (10.0 * N)\n        else:\n            dist = col_bounds[1] - col_bounds[0]\n            if mi is None:\n                mi = col_bounds[0] + dist / (10.0 * N)\n            if ma is None:\n                ma = col_bounds[1] - dist / (10.0 * N)\n        if w is None:\n            w = 0.001 * (ma - mi) / N\n            w = max(w, 1.0 / self.nbins)\n        if alpha is None and self.grey_opacity:\n            alpha = np.ones(N, dtype=\"float64\")\n        elif alpha is None and not self.grey_opacity:\n            alpha = np.logspace(-3, 0, N)\n        for v, a in zip(np.mgrid[mi : ma : N * 1j], alpha, strict=True):\n            self.sample_colormap(v, w, a, colormap=colormap, col_bounds=col_bounds)\n\n    def get_colormap_image(self, height, width):\n        image = np.zeros((height, width, 3), dtype=\"uint8\")\n        hvals = np.mgrid[self.x_bounds[0] : self.x_bounds[1] : height * 1j]\n        for i, f in enumerate(self.funcs[:3]):\n            vals = np.interp(hvals, f.x, f.y)\n            image[:, :, i] = (vals[:, None] * 255).astype(\"uint8\")\n        image = image[::-1, :, :]\n        return image\n\n    def clear(self):\n        for f in self.funcs:\n            f.clear()\n        self.features = []\n\n    def __repr__(self):\n        disp = (\n            \"<Color Transfer Function Object>:\\n\"\n            f\"x_bounds:[{self.x_bounds[0]:3.2g}, {self.x_bounds[1]:3.2g}] \"\n            f\"nbins:{self.nbins} features:\\n\"\n        )\n        for f in self.features:\n            disp += f\"\\t{str(f)}\\n\"\n        return disp\n\n\nclass ProjectionTransferFunction(MultiVariateTransferFunction):\n    r\"\"\"A transfer function that defines a simple projection.\n\n    To generate an interpolated, off-axis projection through a dataset,\n    this transfer function should be used.  It will create a very simple\n    table that merely sums along each ray.  Note that the end product will\n    need to be scaled by the total width through which the rays were cast,\n    a piece of information inaccessible to the transfer function.\n\n    Parameters\n    ----------\n    x_bounds : tuple of floats, optional\n        If any of your values lie outside this range, they will be\n        truncated.\n    n_fields : int, optional\n        How many fields we're going to project and pass through\n\n    Notes\n    -----\n    When you use this transfer function, you may need to explicitly disable\n    logging of fields.\n\n    \"\"\"\n\n    def __init__(self, x_bounds=(-1e60, 1e60), n_fields=1):\n        if n_fields > 3:\n            raise NotImplementedError(\n                f\"supplied ${n_fields} but n_fields > 3 not implemented.\"\n            )\n        MultiVariateTransferFunction.__init__(self)\n        # Strip units off of x_bounds, if any\n        x_bounds = [np.float64(xb) for xb in x_bounds]\n        self.x_bounds = x_bounds\n        self.nbins = 2\n        self.linear_mapping = TransferFunction(x_bounds, 2)\n        self.linear_mapping.pass_through = 1\n        self.link_channels(0, [0, 1, 2])  # same emission for all rgb, default\n        for i in range(n_fields):\n            self.add_field_table(self.linear_mapping, i)\n            self.link_channels(i, i)\n        self.link_channels(n_fields, [3, 4, 5])  # this will remove absorption\n\n\nclass PlanckTransferFunction(MultiVariateTransferFunction):\n    \"\"\"\n    This sets up a planck function for multivariate emission and\n    absorption.  We assume that the emission is black body, which is then\n    convolved with appropriate Johnson filters for *red*, *green* and\n    *blue*.  *T_bounds* and *rho_bounds* define the limits of tabulated\n    emission and absorption functions.  *anorm* is a \"fudge factor\" that\n    defines the somewhat arbitrary normalization to the scattering\n    approximation: because everything is done largely unit-free, and is\n    really not terribly accurate anyway, feel free to adjust this to change\n    the relative amount of reddening.  Maybe in some future version this\n    will be unitful.\n    \"\"\"\n\n    def __init__(\n        self, T_bounds, rho_bounds, nbins=256, red=\"R\", green=\"V\", blue=\"B\", anorm=1e6\n    ):\n        MultiVariateTransferFunction.__init__(self)\n        mscat = -1\n        from .UBVRI import johnson_filters\n\n        for i, f in enumerate([red, green, blue]):\n            jf = johnson_filters[f]\n            tf = TransferFunction(T_bounds)\n            tf.add_filtered_planck(jf[\"wavelen\"], jf[\"trans\"])\n            self.add_field_table(tf, 0, 1)\n            self.link_channels(i, i)  # 0 => 0, 1 => 1, 2 => 2\n            mscat = max(mscat, jf[\"Lchar\"] ** -4)\n\n        for i, f in enumerate([red, green, blue]):\n            # Now we set up the scattering\n            scat = (johnson_filters[f][\"Lchar\"] ** -4 / mscat) * anorm\n            tf = TransferFunction(rho_bounds)\n            mylog.debug(\"Adding: %s with relative scattering %s\", f, scat)\n            tf.y *= 0.0\n            tf.y += scat\n            self.add_field_table(tf, 1, weight_field_id=1)\n            self.link_channels(i + 3, i + 3)\n\n        self._normalize()\n        self.grey_opacity = False\n\n    def _normalize(self):\n        fmax = np.array([f.y for f in self.tables[:3]])\n        normal = fmax.max(axis=0)\n        for f in self.tables[:3]:\n            f.y = f.y / normal\n\n\nif __name__ == \"__main__\":\n    tf = ColorTransferFunction((-20, -5))\n    tf.add_gaussian(-16.0, 0.4, [0.2, 0.3, 0.1])\n    tf.add_gaussian(-14.0, 0.8, [0.4, 0.1, 0.2])\n    tf.add_gaussian(-10.0, 1.0, [0.0, 0.0, 1.0])\n    tf.plot(\"tf.png\")\n"
  },
  {
    "path": "yt/visualization/volume_rendering/utils.py",
    "content": "import numpy as np\n\nfrom yt.data_objects.selection_objects.data_selection_objects import (\n    YTSelectionContainer3D,\n)\nfrom yt.data_objects.static_output import Dataset\nfrom yt.utilities.lib import bounding_volume_hierarchy\nfrom yt.utilities.lib.image_samplers import (\n    InterpolatedProjectionSampler,\n    ProjectionSampler,\n    VolumeRenderSampler,\n)\nfrom yt.utilities.on_demand_imports import NotAModule\n\ntry:\n    from yt.utilities.lib.embree_mesh import mesh_traversal  # type: ignore\n# Catch ValueError in case size of objects in Cython change\nexcept (ImportError, ValueError):\n    mesh_traversal = NotAModule(\"pyembree\")\n\n\ndef data_source_or_all(data_source):\n    if isinstance(data_source, Dataset):\n        data_source = data_source.all_data()\n    if not isinstance(data_source, (YTSelectionContainer3D, type(None))):\n        raise RuntimeError(\n            \"The data_source is not a valid 3D data container.\\n\"\n            \"Expected an object of type YTSelectionContainer3D but received \"\n            f\"an object of type {type(data_source)}.\"\n        )\n    return data_source\n\n\ndef new_mesh_sampler(camera, render_source, engine):\n    params = ensure_code_unit_params(camera._get_sampler_params(render_source))\n    args = (\n        np.atleast_3d(params[\"vp_pos\"]),\n        np.atleast_3d(params[\"vp_dir\"]),\n        params[\"center\"],\n        params[\"bounds\"],\n        np.atleast_3d(params[\"image\"]).astype(\"float64\"),\n        params[\"x_vec\"],\n        params[\"y_vec\"],\n        params[\"width\"],\n        render_source.volume_method,\n    )\n    kwargs = {\"lens_type\": params[\"lens_type\"]}\n    if engine == \"embree\":\n        sampler = mesh_traversal.EmbreeMeshSampler(*args, **kwargs)\n    elif engine == \"yt\":\n        sampler = bounding_volume_hierarchy.BVHMeshSampler(*args, **kwargs)\n    return sampler\n\n\ndef new_volume_render_sampler(camera, render_source):\n    params = ensure_code_unit_params(camera._get_sampler_params(render_source))\n    params.update(transfer_function=render_source.transfer_function)\n    params.update(transfer_function=render_source.transfer_function)\n    params.update(num_samples=render_source.num_samples)\n    args = (\n        np.atleast_3d(params[\"vp_pos\"]),\n        np.atleast_3d(params[\"vp_dir\"]),\n        params[\"center\"],\n        params[\"bounds\"],\n        params[\"image\"],\n        params[\"x_vec\"],\n        params[\"y_vec\"],\n        params[\"width\"],\n        render_source.volume_method,\n        params[\"transfer_function\"],\n        params[\"num_samples\"],\n    )\n    kwargs = {\n        \"lens_type\": params[\"lens_type\"],\n    }\n    if \"camera_data\" in params:\n        kwargs[\"camera_data\"] = params[\"camera_data\"]\n    if render_source.zbuffer is not None:\n        kwargs[\"zbuffer\"] = render_source.zbuffer.z\n        args[4][:] = np.reshape(\n            render_source.zbuffer.rgba[:],\n            (camera.resolution[0], camera.resolution[1], 4),\n        )\n    else:\n        kwargs[\"zbuffer\"] = np.ones(params[\"image\"].shape[:2], \"float64\")\n    sampler = VolumeRenderSampler(*args, **kwargs)\n    return sampler\n\n\ndef new_interpolated_projection_sampler(camera, render_source):\n    params = ensure_code_unit_params(camera._get_sampler_params(render_source))\n    params.update(transfer_function=render_source.transfer_function)\n    params.update(num_samples=render_source.num_samples)\n    args = (\n        np.atleast_3d(params[\"vp_pos\"]),\n        np.atleast_3d(params[\"vp_dir\"]),\n        params[\"center\"],\n        params[\"bounds\"],\n        params[\"image\"],\n        params[\"x_vec\"],\n        params[\"y_vec\"],\n        params[\"width\"],\n        render_source.volume_method,\n        params[\"num_samples\"],\n    )\n    kwargs = {\"lens_type\": params[\"lens_type\"]}\n    if render_source.zbuffer is not None:\n        kwargs[\"zbuffer\"] = render_source.zbuffer.z\n    else:\n        kwargs[\"zbuffer\"] = np.ones(params[\"image\"].shape[:2], \"float64\")\n    sampler = InterpolatedProjectionSampler(*args, **kwargs)\n    return sampler\n\n\ndef new_projection_sampler(camera, render_source):\n    params = ensure_code_unit_params(camera._get_sampler_params(render_source))\n    params.update(transfer_function=render_source.transfer_function)\n    params.update(num_samples=render_source.num_samples)\n    args = (\n        np.atleast_3d(params[\"vp_pos\"]),\n        np.atleast_3d(params[\"vp_dir\"]),\n        params[\"center\"],\n        params[\"bounds\"],\n        params[\"image\"],\n        params[\"x_vec\"],\n        params[\"y_vec\"],\n        params[\"width\"],\n        render_source.volume_method,\n        params[\"num_samples\"],\n    )\n    kwargs = {\n        \"lens_type\": params[\"lens_type\"],\n    }\n    if render_source.zbuffer is not None:\n        kwargs[\"zbuffer\"] = render_source.zbuffer.z\n    else:\n        kwargs[\"zbuffer\"] = np.ones(params[\"image\"].shape[:2], \"float64\")\n    sampler = ProjectionSampler(*args, **kwargs)\n    return sampler\n\n\ndef get_corners(le, re):\n    return np.array(\n        [\n            [le[0], le[1], le[2]],\n            [re[0], le[1], le[2]],\n            [re[0], re[1], le[2]],\n            [le[0], re[1], le[2]],\n            [le[0], le[1], re[2]],\n            [re[0], le[1], re[2]],\n            [re[0], re[1], re[2]],\n            [le[0], re[1], re[2]],\n        ],\n        dtype=\"float64\",\n    )\n\n\ndef ensure_code_unit_params(params):\n    for param_name in [\"center\", \"vp_pos\", \"vp_dir\", \"width\"]:\n        param = params[param_name]\n        if hasattr(param, \"in_units\"):\n            params[param_name] = param.in_units(\"code_length\")\n    bounds = params[\"bounds\"]\n    if hasattr(bounds[0], \"units\"):\n        params[\"bounds\"] = tuple(b.in_units(\"code_length\").d for b in bounds)\n    return params\n"
  },
  {
    "path": "yt/visualization/volume_rendering/volume_rendering.py",
    "content": "from yt.funcs import mylog\nfrom yt.utilities.exceptions import YTSceneFieldNotFound\n\nfrom .api import MeshSource, Scene, create_volume_source\nfrom .utils import data_source_or_all\n\n\ndef create_scene(data_source, field=None, lens_type=\"plane-parallel\"):\n    r\"\"\"Set up a scene object with sensible defaults for use in volume\n    rendering.\n\n    A helper function that creates a default camera view, transfer\n    function, and image size. Using these, it returns an instance\n    of the Scene class, allowing one to further modify their rendering.\n\n    This function is the same as volume_render() except it doesn't render\n    the image.\n\n    Parameters\n    ----------\n    data_source : :class:`yt.data_objects.data_containers.AMR3DData`\n        This is the source to be rendered, which can be any arbitrary yt\n        3D object\n    field: string, tuple, optional\n        The field to be rendered. If unspecified, this will use the\n        default_field for your dataset's frontend--usually ('gas', 'density').\n        A default transfer function will be built that spans the range of\n        values for that given field, and the field will be logarithmically\n        scaled if the field_info object specifies as such.\n    lens_type: string, optional\n        This specifies the type of lens to use for rendering. Current\n        options are 'plane-parallel', 'perspective', and 'fisheye'. See\n        :class:`yt.visualization.volume_rendering.lens.Lens` for details.\n        Default: 'plane-parallel'\n\n    Returns\n    -------\n    sc: Scene\n        A :class:`yt.visualization.volume_rendering.scene.Scene` object\n        that was constructed during the rendering. Useful for further\n        modifications, rotations, etc.\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"Enzo_64/DD0046/DD0046\")\n    >>> sc = yt.create_scene(ds)\n    \"\"\"\n\n    data_source = data_source_or_all(data_source)\n    sc = Scene()\n    if field is None:\n        field = data_source.ds.default_field\n        if field not in data_source.ds.derived_field_list:\n            raise YTSceneFieldNotFound(\n                f\"\"\"Could not find field '{field}' in {data_source.ds}.\n                  Please specify a field in create_scene()\"\"\"\n            )\n        mylog.info(\"Setting default field to %s\", field.__repr__())\n\n    if hasattr(data_source.ds.index, \"meshes\"):\n        source = MeshSource(data_source, field=field)\n    else:\n        source = create_volume_source(data_source, field=field)\n\n    sc.add_source(source)\n    sc.add_camera(data_source=data_source, lens_type=lens_type)\n    return sc\n\n\ndef volume_render(\n    data_source, field=None, fname=None, sigma_clip=None, lens_type=\"plane-parallel\"\n):\n    r\"\"\"Create a simple volume rendering of a data source.\n\n    A helper function that creates a default camera view, transfer\n    function, and image size. Using these, it returns an image and\n    an instance of the Scene class, allowing one to further modify\n    their rendering.\n\n    Parameters\n    ----------\n    data_source : :class:`yt.data_objects.data_containers.AMR3DData`\n        This is the source to be rendered, which can be any arbitrary yt\n        3D object\n    field: string, tuple, optional\n        The field to be rendered. If unspecified, this will use the\n        default_field for your dataset's frontend--usually ('gas', 'density').\n        A default transfer function will be built that spans the range of\n        values for that given field, and the field will be logarithmically\n        scaled if the field_info object specifies as such.\n    fname: string, optional\n        If specified, the resulting rendering will be saved to this filename\n        in png format.\n    sigma_clip: float, optional\n        If specified, the resulting image will be clipped before saving,\n        using a threshold based on sigma_clip multiplied by the standard\n        deviation of the pixel values. Recommended values are between 2 and 6.\n        Default: None\n    lens_type: string, optional\n        This specifies the type of lens to use for rendering. Current\n        options are 'plane-parallel', 'perspective', and 'fisheye'. See\n        :class:`yt.visualization.volume_rendering.lens.Lens` for details.\n        Default: 'plane-parallel'\n\n    Returns\n    -------\n    im: ImageArray\n        The resulting image, stored as an ImageArray object.\n    sc: Scene\n        A :class:`yt.visualization.volume_rendering.scene.Scene` object\n        that was constructed during the rendering. Useful for further\n        modifications, rotations, etc.\n\n    Examples\n    --------\n\n    >>> import yt\n    >>> ds = yt.load(\"Enzo_64/DD0046/DD0046\")\n    >>> im, sc = yt.volume_render(ds, fname=\"test.png\", sigma_clip=4.0)\n    \"\"\"\n    sc = create_scene(data_source, field=field)\n    im = sc.render()\n    sc.save(fname=fname, sigma_clip=sigma_clip, render=False)\n    return im, sc\n"
  },
  {
    "path": "yt/visualization/volume_rendering/zbuffer_array.py",
    "content": "import numpy as np\n\n\nclass ZBuffer:\n    \"\"\"A container object for z-buffer arrays\n\n    A zbuffer is a companion array for an image that allows the volume rendering\n    infrastructure to determine whether one opaque source is in front of another\n    opaque source.  The z buffer encodes the distance to the opaque source\n    relative to the camera position.\n\n    Parameters\n    ----------\n    rgba: MxNx4 image\n        The image the z buffer corresponds to\n    z: MxN image\n        The z depth of each pixel in the image. The shape of the image must be\n        the same as each RGBA channel in the original image.\n\n    Examples\n    --------\n    >>> import numpy as np\n    >>> shape = (64, 64)\n    >>> rng = np.random.default_rng()\n    >>> b1 = Zbuffer(rng.random(shape), np.ones(shape))\n    >>> b2 = Zbuffer(rng.random(shape), np.zeros(shape))\n    >>> c = b1 + b2\n    >>> np.all(c.rgba == b2.rgba)\n    True\n    >>> np.all(c.z == b2.z)\n    True\n    >>> np.all(c == b2)\n    True\n\n    \"\"\"\n\n    def __init__(self, rgba, z):\n        super().__init__()\n        assert rgba.shape[: len(z.shape)] == z.shape\n        self.rgba = rgba\n        self.z = z\n        self.shape = z.shape\n\n    def __add__(self, other):\n        assert self.shape == other.shape\n        f = self.z < other.z\n        if self.z.shape[1] == 1:\n            # Non-rectangular\n            rgba = self.rgba * f[:, None, :]\n            rgba += other.rgba * (1.0 - f)[:, None, :]\n        else:\n            b = self.z > other.z\n            rgba = np.zeros(self.rgba.shape)\n            rgba[f] = self.rgba[f]\n            rgba[b] = other.rgba[b]\n        z = np.min([self.z, other.z], axis=0)\n        return ZBuffer(rgba, z)\n\n    def __iadd__(self, other):\n        tmp = self + other\n        self.rgba = tmp.rgba\n        self.z = tmp.z\n        return self\n\n    def __eq__(self, other):\n        equal = True\n        equal *= np.all(self.rgba == other.rgba)\n        equal *= np.all(self.z == other.z)\n        return equal\n\n    def paint(self, ind, value, z):\n        if z < self.z[ind]:\n            self.rgba[ind] = value\n            self.z[ind] = z\n\n\nif __name__ == \"__main__\":\n    shape: tuple[int, ...] = (64, 64)\n    shapes: list[tuple[int, ...]] = [(64, 64), (16, 16, 4), (128,), (16, 32)]\n    rng = np.random.default_rng()\n    for shape in shapes:\n        b1 = ZBuffer(rng.random(shape), np.ones(shape))\n        b2 = ZBuffer(rng.random(shape), np.zeros(shape))\n        c = b1 + b2\n        assert np.all(c.rgba == b2.rgba)\n        assert np.all(c.z == b2.z)\n        assert np.all(c == b2)\n"
  }
]